Commit 21cddef2 authored by Wayne Davison's avatar Wayne Davison

Improved the backup code:

- Backups do not interfere with an atomic update (when possible).
- Backing up a file will remove a directory that is in the way
  and visa versa.
- Unify the backup-dir and non-backup-dir code in backup.c.
- Improved the backup tests a little bit.
parent b3bf9b9d
...@@ -51,43 +51,6 @@ char *get_backup_name(const char *fname) ...@@ -51,43 +51,6 @@ char *get_backup_name(const char *fname)
return NULL; return NULL;
} }
/* simple backup creates a backup with a suffix in the same directory */
static int make_simple_backup(const char *fname)
{
int rename_errno;
const char *fnamebak = get_backup_name(fname);
if (!fnamebak)
return 0;
while (1) {
if (do_rename(fname, fnamebak) == 0) {
if (INFO_GTE(BACKUP, 1)) {
rprintf(FINFO, "backed up %s to %s\n",
fname, fnamebak);
}
break;
}
/* cygwin (at least version b19) reports EINVAL */
if (errno == ENOENT || errno == EINVAL)
break;
rename_errno = errno;
if (errno == EISDIR && do_rmdir(fnamebak) == 0)
continue;
if (errno == ENOTDIR && do_unlink(fnamebak) == 0)
continue;
rsyserr(FERROR, rename_errno, "rename %s to backup %s",
fname, fnamebak);
errno = rename_errno;
return 0;
}
return 1;
}
/**************************************************************************** /****************************************************************************
Create a directory given an absolute path, perms based upon another directory Create a directory given an absolute path, perms based upon another directory
path path
...@@ -172,50 +135,87 @@ int make_bak_dir(const char *fullpath) ...@@ -172,50 +135,87 @@ int make_bak_dir(const char *fullpath)
return 0; return 0;
} }
/* robustly move a file, creating new directory structures if necessary */ /* Has same return codes as make_backup(). */
static int robust_move(const char *src, char *dst) static inline int link_or_rename(const char *from, const char *to,
BOOL prefer_rename, STRUCT_STAT *stp)
{ {
if (robust_rename(src, dst, NULL, 0755) < 0) { if (S_ISLNK(stp->st_mode)) {
int save_errno = errno ? errno : EINVAL; /* 0 paranoia */ if (prefer_rename)
if (errno == ENOENT && make_bak_dir(dst) == 0) { goto do_rename;
if (robust_rename(src, dst, NULL, 0755) < 0) #ifndef CAN_HARDLINK_SYMLINK
save_errno = errno ? errno : save_errno; return 0; /* Use copy code. */
else #endif
save_errno = 0; }
} if (IS_SPECIAL(stp->st_mode) || IS_DEVICE(stp->st_mode)) {
if (save_errno) { if (prefer_rename)
errno = save_errno; goto do_rename;
return -1; #ifndef CAN_HARDLINK_SPECIAL
return 0; /* Use copy code. */
#endif
}
#ifdef SUPPORT_HARD_LINKS
if (!S_ISDIR(stp->st_mode)) {
if (do_link(from, to) == 0)
return 2;
return 0;
}
#endif
do_rename:
if (do_rename(from, to) == 0) {
if (stp->st_nlink > 1 && !S_ISDIR(stp->st_mode)) {
/* If someone has hard-linked the file into the backup
* dir, rename() might return success but do nothing! */
robust_unlink(to); /* Just in case... */
} }
return 1;
} }
return 0; return 0;
} }
/* Hard-link, rename, or copy an item to the backup name. Returns 2 if item
/* If we have a --backup-dir, then we get here from make_backup(). * was duplicated into backup area, 1 if item was moved, or 0 for failure.*/
* We will move the file to be deleted into a parallel directory tree. */ int make_backup(const char *fname, BOOL prefer_rename)
static int keep_backup(const char *fname)
{ {
stat_x sx; stat_x sx;
struct file_struct *file; struct file_struct *file;
char *buf; int save_preserve_xattrs;
int save_preserve_xattrs = preserve_xattrs; char *buf = get_backup_name(fname);
int kept = 0; int ret = 0;
int ret_code;
if (!buf)
return 0;
init_stat_x(&sx); init_stat_x(&sx);
/* Return success if no file to keep. */ /* Return success if no file to keep. */
if (x_lstat(fname, &sx.st, NULL) < 0) if (x_lstat(fname, &sx.st, NULL) < 0)
return 1; return 1;
if (!(file = make_file(fname, NULL, NULL, 0, NO_FILTERS))) /* Try a hard-link or a rename first. Using rename is not atomic, but
return 1; /* the file could have disappeared */ * is more efficient than forcing a copy for larger files when no hard-
* linking is possible. */
if (!(buf = get_backup_name(fname))) { if ((ret = link_or_rename(fname, buf, prefer_rename, &sx.st)) != 0)
unmake_file(file); goto success;
return 0; if (errno == EEXIST) {
STRUCT_STAT bakst;
if (do_lstat(buf, &bakst) == 0) {
int flags = get_del_for_flag(bakst.st_mode) | DEL_FOR_BACKUP | DEL_RECURSE;
if (delete_item(buf, bakst.st_mode, flags) != 0)
return 0;
}
if ((ret = link_or_rename(fname, buf, prefer_rename, &sx.st)) != 0)
goto success;
} else if (backup_dir && errno == ENOENT) {
/* If the backup dir is missing, try again after making it. */
if (make_bak_dir(buf) != 0)
return 0;
if ((ret = link_or_rename(fname, buf, prefer_rename, &sx.st)) != 0)
goto success;
} }
/* Fall back to making a copy. */
if (!(file = make_file(fname, NULL, &sx.st, 0, NO_FILTERS)))
return 1; /* the file could have disappeared */
#ifdef SUPPORT_ACLS #ifdef SUPPORT_ACLS
if (preserve_acls && !S_ISLNK(file->mode)) { if (preserve_acls && !S_ISLNK(file->mode)) {
get_acl(fname, &sx); get_acl(fname, &sx);
...@@ -235,7 +235,6 @@ static int keep_backup(const char *fname) ...@@ -235,7 +235,6 @@ static int keep_backup(const char *fname)
if ((am_root && preserve_devices && IS_DEVICE(file->mode)) if ((am_root && preserve_devices && IS_DEVICE(file->mode))
|| (preserve_specials && IS_SPECIAL(file->mode))) { || (preserve_specials && IS_SPECIAL(file->mode))) {
int save_errno; int save_errno;
do_unlink(buf);
if (do_mknod(buf, file->mode, sx.st.st_rdev) < 0) { if (do_mknod(buf, file->mode, sx.st.st_rdev) < 0) {
save_errno = errno ? errno : EINVAL; /* 0 paranoia */ save_errno = errno ? errno : EINVAL; /* 0 paranoia */
if (errno == ENOENT && make_bak_dir(buf) == 0) { if (errno == ENOENT && make_bak_dir(buf) == 0) {
...@@ -254,11 +253,11 @@ static int keep_backup(const char *fname) ...@@ -254,11 +253,11 @@ static int keep_backup(const char *fname)
rprintf(FINFO, "make_backup: DEVICE %s successful.\n", rprintf(FINFO, "make_backup: DEVICE %s successful.\n",
fname); fname);
} }
kept = 1; ret = 2;
do_unlink(fname);
} }
if (!kept && S_ISDIR(file->mode)) { if (!ret && S_ISDIR(file->mode)) {
int ret_code;
/* make an empty directory */ /* make an empty directory */
if (do_mkdir(buf, file->mode) < 0) { if (do_mkdir(buf, file->mode) < 0) {
int save_errno = errno ? errno : EINVAL; /* 0 paranoia */ int save_errno = errno ? errno : EINVAL; /* 0 paranoia */
...@@ -279,20 +278,19 @@ static int keep_backup(const char *fname) ...@@ -279,20 +278,19 @@ static int keep_backup(const char *fname)
rprintf(FINFO, "make_backup: RMDIR %s returns %i\n", rprintf(FINFO, "make_backup: RMDIR %s returns %i\n",
full_fname(fname), ret_code); full_fname(fname), ret_code);
} }
kept = 1; ret = 2;
} }
#ifdef SUPPORT_LINKS #ifdef SUPPORT_LINKS
if (!kept && preserve_links && S_ISLNK(file->mode)) { if (!ret && preserve_links && S_ISLNK(file->mode)) {
const char *sl = F_SYMLINK(file); const char *sl = F_SYMLINK(file);
if (safe_symlinks && unsafe_symlink(sl, buf)) { if (safe_symlinks && unsafe_symlink(sl, buf)) {
if (INFO_GTE(SYMSAFE, 1)) { if (INFO_GTE(SYMSAFE, 1)) {
rprintf(FINFO, "ignoring unsafe symlink %s -> %s\n", rprintf(FINFO, "ignoring unsafe symlink %s -> %s\n",
full_fname(buf), sl); full_fname(buf), sl);
} }
kept = 1; ret = 2;
} else { } else {
do_unlink(buf);
if (do_symlink(sl, buf) < 0) { if (do_symlink(sl, buf) < 0) {
int save_errno = errno ? errno : EINVAL; /* 0 paranoia */ int save_errno = errno ? errno : EINVAL; /* 0 paranoia */
if (errno == ENOENT && make_bak_dir(buf) == 0) { if (errno == ENOENT && make_bak_dir(buf) == 0) {
...@@ -306,47 +304,40 @@ static int keep_backup(const char *fname) ...@@ -306,47 +304,40 @@ static int keep_backup(const char *fname)
full_fname(buf), sl); full_fname(buf), sl);
} }
} }
do_unlink(fname); ret = 2;
kept = 1;
} }
} }
#endif #endif
if (!kept && !S_ISREG(file->mode)) { if (!ret && !S_ISREG(file->mode)) {
rprintf(FINFO, "make_bak: skipping non-regular file %s\n", rprintf(FINFO, "make_bak: skipping non-regular file %s\n",
fname); fname);
unmake_file(file); unmake_file(file);
return 1; return 2;
} }
/* move to keep tree if a file */ /* Copy to backup tree if a file. */
if (!kept) { if (!ret) {
if (robust_move(fname, buf) != 0) { if (copy_file(fname, buf, -1, file->mode, 1) < 0) {
rsyserr(FERROR, errno, "keep_backup failed: %s -> \"%s\"", rsyserr(FERROR, errno, "keep_backup failed: %s -> \"%s\"",
full_fname(fname), buf); full_fname(fname), buf);
} else if (sx.st.st_nlink > 1) { unmake_file(file);
/* If someone has hard-linked the file into the backup return 0;
* dir, rename() might return success but do nothing! */
robust_unlink(fname); /* Just in case... */
} }
ret = 2;
} }
save_preserve_xattrs = preserve_xattrs;
preserve_xattrs = 0; preserve_xattrs = 0;
set_file_attrs(buf, file, NULL, fname, 0); set_file_attrs(buf, file, NULL, fname, 0);
preserve_xattrs = save_preserve_xattrs; preserve_xattrs = save_preserve_xattrs;
unmake_file(file); unmake_file(file);
success:
if (INFO_GTE(BACKUP, 1)) { if (INFO_GTE(BACKUP, 1)) {
rprintf(FINFO, "backed up %s to %s\n", rprintf(FINFO, "backed up %s to %s\n",
fname, buf); fname, buf);
} }
return 1; return ret;
}
/* main backup switch routine */
int make_backup(const char *fname)
{
if (backup_dir)
return keep_backup(fname);
return make_simple_backup(fname);
} }
...@@ -169,12 +169,18 @@ enum delret delete_item(char *fbuf, uint16 mode, uint16 flags) ...@@ -169,12 +169,18 @@ enum delret delete_item(char *fbuf, uint16 mode, uint16 flags)
if (S_ISDIR(mode)) { if (S_ISDIR(mode)) {
what = "rmdir"; what = "rmdir";
ok = do_rmdir(fbuf) == 0; ok = do_rmdir(fbuf) == 0;
} else if (make_backups > 0 && (backup_dir || !is_backup_file(fbuf))) {
what = "make_backup";
ok = make_backup(fbuf);
} else { } else {
what = "unlink"; if (make_backups > 0 && (backup_dir || !is_backup_file(fbuf))) {
ok = robust_unlink(fbuf) == 0; what = "make_backup";
ok = make_backup(fbuf, True);
if (ok == 2) {
what = "unlink";
ok = robust_unlink(fbuf) == 0;
}
} else {
what = "unlink";
ok = robust_unlink(fbuf) == 0;
}
} }
if (ok) { if (ok) {
...@@ -219,8 +225,24 @@ enum delret delete_item(char *fbuf, uint16 mode, uint16 flags) ...@@ -219,8 +225,24 @@ enum delret delete_item(char *fbuf, uint16 mode, uint16 flags)
case DEL_FOR_SPECIAL: desc = "special file"; break; case DEL_FOR_SPECIAL: desc = "special file"; break;
default: exit_cleanup(RERR_UNSUPPORTED); /* IMPOSSIBLE */ default: exit_cleanup(RERR_UNSUPPORTED); /* IMPOSSIBLE */
} }
rprintf(FERROR_XFER, "could not make way for new %s: %s\n", rprintf(FERROR_XFER, "could not make way for %s %s: %s\n",
flags & DEL_FOR_BACKUP ? "backup" : "new",
desc, fbuf); desc, fbuf);
} }
return ret; return ret;
} }
uint16 get_del_for_flag(uint16 mode)
{
if (S_ISREG(mode))
return DEL_FOR_FILE;
if (S_ISDIR(mode))
return DEL_FOR_DIR;
if (S_ISLNK(mode))
return DEL_FOR_SYMLINK;
if (IS_DEVICE(mode))
return DEL_FOR_DEVICE;
if (IS_SPECIAL(mode))
return DEL_FOR_SPECIAL;
exit_cleanup(RERR_UNSUPPORTED); /* IMPOSSIBLE */
}
...@@ -217,6 +217,7 @@ static int maybe_hard_link(struct file_struct *file, int ndx, ...@@ -217,6 +217,7 @@ static int maybe_hard_link(struct file_struct *file, int ndx,
const char *realname, int itemizing, enum logcode code) const char *realname, int itemizing, enum logcode code)
{ {
if (statret == 0) { if (statret == 0) {
int ok = 0;
if (sxp->st.st_dev == old_stp->st_dev if (sxp->st.st_dev == old_stp->st_dev
&& sxp->st.st_ino == old_stp->st_ino) { && sxp->st.st_ino == old_stp->st_ino) {
if (itemizing) { if (itemizing) {
...@@ -229,10 +230,9 @@ static int maybe_hard_link(struct file_struct *file, int ndx, ...@@ -229,10 +230,9 @@ static int maybe_hard_link(struct file_struct *file, int ndx,
file->flags |= FLAG_HLINK_DONE; file->flags |= FLAG_HLINK_DONE;
return 0; return 0;
} }
if (make_backups > 0) { if (make_backups > 0 && (ok = make_backup(fname, True)) == 0)
if (!make_backup(fname)) return -1;
return -1; if (ok != 1 && robust_unlink(fname) && errno != ENOENT) {
} else if (robust_unlink(fname)) {
rsyserr(FERROR_XFER, errno, "unlink %s failed", rsyserr(FERROR_XFER, errno, "unlink %s failed",
full_fname(fname)); full_fname(fname));
return -1; return -1;
......
...@@ -331,7 +331,7 @@ static void handle_delayed_updates(char *local_name) ...@@ -331,7 +331,7 @@ static void handle_delayed_updates(char *local_name)
struct file_struct *file = cur_flist->files[ndx]; struct file_struct *file = cur_flist->files[ndx];
fname = local_name ? local_name : f_name(file, NULL); fname = local_name ? local_name : f_name(file, NULL);
if ((partialptr = partial_dir_fname(fname)) != NULL) { if ((partialptr = partial_dir_fname(fname)) != NULL) {
if (make_backups > 0 && !make_backup(fname)) if (make_backups > 0 && !make_backup(fname, False))
continue; continue;
if (DEBUG_GTE(RECV, 1)) { if (DEBUG_GTE(RECV, 1)) {
rprintf(FINFO, "renaming %s to %s\n", rprintf(FINFO, "renaming %s to %s\n",
......
...@@ -561,9 +561,10 @@ int finish_transfer(const char *fname, const char *fnametmp, ...@@ -561,9 +561,10 @@ int finish_transfer(const char *fname, const char *fnametmp,
} }
if (make_backups > 0 && overwriting_basis) { if (make_backups > 0 && overwriting_basis) {
if (!make_backup(fname)) int ok = make_backup(fname, False);
if (!ok)
return 1; return 1;
if (fnamecmp == fname) if (ok == 1 && fnamecmp == fname)
fnamecmp = get_backup_name(fname); fnamecmp = get_backup_name(fname);
} }
......
...@@ -249,6 +249,7 @@ enum msgcode { ...@@ -249,6 +249,7 @@ enum msgcode {
#define DEL_FOR_SYMLINK (1<<5) /* making room for a replacement symlink */ #define DEL_FOR_SYMLINK (1<<5) /* making room for a replacement symlink */
#define DEL_FOR_DEVICE (1<<6) /* making room for a replacement device */ #define DEL_FOR_DEVICE (1<<6) /* making room for a replacement device */
#define DEL_FOR_SPECIAL (1<<7) /* making room for a replacement special */ #define DEL_FOR_SPECIAL (1<<7) /* making room for a replacement special */
#define DEL_FOR_BACKUP (1<<8) /* the delete is for a backup operation */
#define DEL_MAKE_ROOM (DEL_FOR_FILE|DEL_FOR_DIR|DEL_FOR_SYMLINK|DEL_FOR_DEVICE|DEL_FOR_SPECIAL) #define DEL_MAKE_ROOM (DEL_FOR_FILE|DEL_FOR_DIR|DEL_FOR_SYMLINK|DEL_FOR_DEVICE|DEL_FOR_SPECIAL)
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
bakdir="$tmpdir/bak" bakdir="$tmpdir/bak"
makepath "$fromdir/deep" "$bakdir" makepath "$fromdir/deep" "$bakdir/dname"
name1="$fromdir/deep/name1" name1="$fromdir/deep/name1"
name2="$fromdir/deep/name2" name2="$fromdir/deep/name2"
...@@ -20,13 +20,13 @@ outfile="$scratchdir/rsync.out" ...@@ -20,13 +20,13 @@ outfile="$scratchdir/rsync.out"
cat "$srcdir"/[gr]*.[ch] > "$name1" cat "$srcdir"/[gr]*.[ch] > "$name1"
cat "$srcdir"/[et]*.[ch] > "$name2" cat "$srcdir"/[et]*.[ch] > "$name2"
checkit "$RSYNC -avv '$fromdir/' '$todir/'" "$fromdir" "$todir" checkit "$RSYNC -ai --info=backup '$fromdir/' '$todir/'" "$fromdir" "$todir"
checkit "$RSYNC -avv '$fromdir/' '$chkdir/'" "$fromdir" "$chkdir" checkit "$RSYNC -ai --info=backup '$fromdir/' '$chkdir/'" "$fromdir" "$chkdir"
cat "$srcdir"/[fgpr]*.[ch] > "$name1" cat "$srcdir"/[fgpr]*.[ch] > "$name1"
cat "$srcdir"/[etw]*.[ch] > "$name2" cat "$srcdir"/[etw]*.[ch] > "$name2"
$RSYNC -avv --no-whole-file --backup "$fromdir/" "$todir/" \ $RSYNC -ai --info=backup --no-whole-file --backup "$fromdir/" "$todir/" \
| tee "$outfile" | tee "$outfile"
for fn in deep/name1 deep/name2; do for fn in deep/name1 deep/name2; do
grep "backed up $fn to $fn~" "$outfile" >/dev/null || test_fail "no backup message output for $fn" grep "backed up $fn to $fn~" "$outfile" >/dev/null || test_fail "no backup message output for $fn"
...@@ -38,7 +38,7 @@ done ...@@ -38,7 +38,7 @@ done
echo deleted-file >"$todir/dname" echo deleted-file >"$todir/dname"
cp_touch "$todir/dname" "$chkdir" cp_touch "$todir/dname" "$chkdir"
checkit "$RSYNC -avv --no-whole-file --delete-delay \ checkit "$RSYNC -ai --info=backup --no-whole-file --delete-delay \
--backup --backup-dir='$bakdir' '$fromdir/' '$todir/'" "$fromdir" "$todir" \ --backup --backup-dir='$bakdir' '$fromdir/' '$todir/'" "$fromdir" "$todir" \
| tee "$outfile" | tee "$outfile"
...@@ -48,11 +48,11 @@ done ...@@ -48,11 +48,11 @@ done
diff -r $diffopt "$chkdir" "$bakdir" || test_fail "backup dir contents are bogus" diff -r $diffopt "$chkdir" "$bakdir" || test_fail "backup dir contents are bogus"
rm "$bakdir/dname" rm "$bakdir/dname"
checkit "$RSYNC -avv --del '$fromdir/' '$chkdir/'" "$fromdir" "$chkdir" checkit "$RSYNC -ai --info=backup --del '$fromdir/' '$chkdir/'" "$fromdir" "$chkdir"
cat "$srcdir"/[efgr]*.[ch] > "$name1" cat "$srcdir"/[efgr]*.[ch] > "$name1"
cat "$srcdir"/[ew]*.[ch] > "$name2" cat "$srcdir"/[ew]*.[ch] > "$name2"
checkit "$RSYNC -avv --inplace --no-whole-file --backup --backup-dir='$bakdir' '$fromdir/' '$todir/'" "$fromdir" "$todir" \ checkit "$RSYNC -ai --info=backup --inplace --no-whole-file --backup --backup-dir='$bakdir' '$fromdir/' '$todir/'" "$fromdir" "$todir" \
| tee "$outfile" | tee "$outfile"
for fn in deep/name1 deep/name2; do for fn in deep/name1 deep/name2; do
...@@ -60,7 +60,7 @@ for fn in deep/name1 deep/name2; do ...@@ -60,7 +60,7 @@ for fn in deep/name1 deep/name2; do
done done
diff -r $diffopt "$chkdir" "$bakdir" || test_fail "backup dir contents are bogus" diff -r $diffopt "$chkdir" "$bakdir" || test_fail "backup dir contents are bogus"
checkit "$RSYNC -avv --inplace --no-whole-file '$fromdir/' '$bakdir/'" "$fromdir" "$bakdir" checkit "$RSYNC -ai --info=backup --inplace --no-whole-file '$fromdir/' '$bakdir/'" "$fromdir" "$bakdir"
# The script would have aborted on error, so getting here means we've won. # The script would have aborted on error, so getting here means we've won.
exit 0 exit 0
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment