This patch adds --tweak, --no-tweak-hlinked, and --no-tweak options that specify that rsync is allowed to tweak the attributes of all destination files, only singly linked files, or no files (respectively). --no-tweak-hlinked is important when receiving into a backup that may contain files hard-linked from previous backups. --no-tweak may be useful when all attributes at the destination path need to change simultaneously. TODO: Make --no-tweak-hlinked compatible with --inplace and --append. TODO: When rsync is updating a destination file with matching data but differing abbreviated xattrs, find a way to update those xattrs before moving the recreated destination file into place. --- old/generator.c +++ new/generator.c @@ -57,6 +57,7 @@ extern int update_only; extern int ignore_existing; extern int ignore_non_existing; extern int inplace; +extern int tweak_attrs; extern int append_mode; extern int make_backups; extern int csum_length; @@ -1144,6 +1145,11 @@ static void list_file_entry(struct file_ } } +static int may_tweak(STRUCT_STAT *st) +{ + return tweak_attrs == 2 || (tweak_attrs == 1 && st->st_nlink == 1); +} + static int phase = 0; static int dflt_perms; @@ -1682,13 +1688,28 @@ static void recv_generator(char *fname, else if (fnamecmp_type == FNAMECMP_FUZZY) ; else if (unchanged_file(fnamecmp, file, &sx.st)) { + /* fnamecmp == fname, fnamecmp_type == FNAMECMP_FNAME */ + int iflags = 0; + if (partialptr) { do_unlink(partialptr); handle_partial_dir(partialptr, PDIR_DELETE); } - set_file_attrs(fname, file, &sx, NULL, maybe_ATTRS_REPORT); + if (may_tweak(&sx.st)) { + if (verbose > 2) + rprintf(FINFO, "Tweaking attributes of %s\n", fname); + set_file_attrs(fname, file, &sx, NULL, maybe_ATTRS_REPORT); + } else if (!unchanged_attrs(fname, file, &sx)) { + /* Need to recreate the file. + * copy_altdest_file sets its attributes, etc. */ + if (verbose > 2) + rprintf(FINFO, "Recreating %s in order to tweak its attributes\n", fname); + if (!dry_run && copy_altdest_file(fnamecmp, fname, file)) + goto cleanup; + iflags |= ITEM_LOCAL_CHANGE; + } if (itemizing) - itemize(fnamecmp, file, ndx, statret, &sx, 0, 0, NULL); + itemize(fnamecmp, file, ndx, statret, &sx, iflags, 0, NULL); #ifdef SUPPORT_HARD_LINKS if (preserve_hard_links && F_IS_HLINKED(file)) finish_hard_link(file, fname, ndx, &sx.st, itemizing, code, -1); --- old/options.c +++ new/options.c @@ -118,6 +118,7 @@ int modify_window = 0; int blocking_io = -1; int checksum_seed = 0; int inplace = 0; +int tweak_attrs = 2; /* 2 = always, 1 = if not hlinked, 0 = never */ int delay_updates = 0; long block_size = 0; /* "long" because popt can't set an int32. */ char *skip_compress = NULL; @@ -322,6 +323,8 @@ void usage(enum logcode F) rprintf(F," --inplace update destination files in-place (SEE MAN PAGE)\n"); rprintf(F," --append append data onto shorter files\n"); rprintf(F," --append-verify like --append, but with old data in file checksum\n"); + rprintf(F," --no-tweak recreate dest files instead of tweaking attrs\n"); + rprintf(F," --no-tweak-hlinked ... if they have multiple hard links\n"); rprintf(F," -d, --dirs transfer directories without recursing\n"); rprintf(F," -l, --links copy symlinks as symlinks\n"); rprintf(F," -L, --copy-links transform symlink into referent file/dir\n"); @@ -532,6 +535,9 @@ static struct poptOption long_options[] {"append", 0, POPT_ARG_NONE, 0, OPT_APPEND, 0, 0 }, {"append-verify", 0, POPT_ARG_VAL, &append_mode, 2, 0, 0 }, {"no-append", 0, POPT_ARG_VAL, &append_mode, 0, 0, 0 }, + {"no-tweak", 0, POPT_ARG_VAL, &tweak_attrs, 0, 0, 0 }, + {"no-tweak-hlinked", 0, POPT_ARG_VAL, &tweak_attrs, 1, 0, 0 }, + {"tweak", 0, POPT_ARG_VAL, &tweak_attrs, 2, 0, 0 }, {"del", 0, POPT_ARG_NONE, &delete_during, 0, 0, 0 }, {"delete", 0, POPT_ARG_NONE, &delete_mode, 0, 0, 0 }, {"delete-before", 0, POPT_ARG_NONE, &delete_before, 0, 0, 0 }, @@ -1536,11 +1542,24 @@ int parse_arguments(int *argc_p, const c partial_dir = tmp_partialdir; if (inplace) { + char *inplace_opt = append_mode ? "append" : "inplace"; #ifdef HAVE_FTRUNCATE + if (tweak_attrs == 0) { + snprintf(err_buf, sizeof err_buf, + "--no-tweak controverts --%s. Remove --%s.\n", + inplace_opt, inplace_opt); + return 0; + } + if (tweak_attrs == 1) { + snprintf(err_buf, sizeof err_buf, + "The combination of --no-tweak-hlinked and --%s is not implemented.\n", + inplace_opt); + return 0; + } if (partial_dir) { snprintf(err_buf, sizeof err_buf, "--%s cannot be used with --%s\n", - append_mode ? "append" : "inplace", + inplace_opt, delay_updates ? "delay-updates" : "partial-dir"); return 0; } @@ -1554,7 +1573,7 @@ int parse_arguments(int *argc_p, const c #else snprintf(err_buf, sizeof err_buf, "--%s is not supported on this %s\n", - append_mode ? "append" : "inplace", + inplace_opt, am_server ? "server" : "client"); return 0; #endif @@ -1885,6 +1904,8 @@ void server_options(char **args, int *ar args[ac++] = "--super"; if (size_only) args[ac++] = "--size-only"; + if (tweak_attrs != 2) + args[ac++] = (tweak_attrs == 1) ? "--no-tweak-hlinked" : "--no-tweak"; } else { if (skip_compress) { if (asprintf(&arg, "--skip-compress=%s", skip_compress) < 0) --- old/receiver.c +++ new/receiver.c @@ -448,6 +448,9 @@ int recv_files(int f_in, char *local_nam maybe_log_item(file, iflags, itemizing, xname); #ifdef SUPPORT_XATTRS if (preserve_xattrs && iflags & ITEM_REPORT_XATTR && !dry_run) + /* If the file needed to be recreated before we + * set xattrs, the generator has already noticed + * the difference in xattrs and done so. */ set_file_attrs(fname, file, NULL, fname, 0); #endif continue; --- old/rsync.1 +++ new/rsync.1 @@ -404,6 +404,8 @@ to the detailed description below for a \-\-inplace update destination files in-place \-\-append append data onto shorter files \-\-append\-verify \-\-append w/old data in file cheksum + \-\-no\-tweak recreate dest files rather than tweak attrs + \-\-no\-tweak\-hlinked ... if they are hard-linked \-d, \-\-dirs transfer directories without recursing \-l, \-\-links copy symlinks as symlinks \-L, \-\-copy\-links transform symlink into referent file/dir @@ -855,6 +857,28 @@ Note: prior to rsync 3.0.0, the \fB\-\-a transfer is using a protocol prior to 30), specifying either append option will initiate an \fB\-\-append\-verify\fP transfer. .IP +.IP "\fB\-\-no\-tweak\fP" +If corresponding source and destination files are +determined to have identical data but differ in preserved attributes, +rsync normally tweaks the attributes of the destination file in place. +This option makes it recreate the destination file instead. This may +be useful if all attributes at the destination path need to change +simultaneously. Caveat: in the current implementation, some extended +attributes may be set after the recreated file is moved into place. +.IP +This option doesn't make sense in combination with \fB\-\-inplace\fP or +\fB\-\-append\fP. +.IP +.IP "\fB\-\-no\-tweak\-hlinked\fP" +Tells rsync to recreate a destination file +instead of tweaking its attributes if it has multiple hard links. +You can use \fB\-\-no\-tweak\-linked\fP to safely update a backup +that has files hard-linked from a previous backup. +.IP +This option currently conflicts with \fB\-\-inplace\fP and \fB\-\-append\fP. In +the future it might selectively disable those options for multiply linked +destination files. +.IP .IP "\fB\-d, \-\-dirs\fP" Tell the sending side to include any directories that are encountered. Unlike \fB\-\-recursive\fP, a directory's contents are not copied --- old/rsync.yo +++ new/rsync.yo @@ -329,6 +329,8 @@ to the detailed description below for a --inplace update destination files in-place --append append data onto shorter files --append-verify --append w/old data in file cheksum + --no-tweak recreate dest files rather than tweak attrs + --no-tweak-hlinked ... if they are hard-linked -d, --dirs transfer directories without recursing -l, --links copy symlinks as symlinks -L, --copy-links transform symlink into referent file/dir @@ -739,6 +741,26 @@ bf(--append-verify), so if you are inter transfer is using a protocol prior to 30), specifying either append option will initiate an bf(--append-verify) transfer. +dit(bf(--no-tweak)) If corresponding source and destination files are +determined to have identical data but differ in preserved attributes, +rsync normally tweaks the attributes of the destination file in place. +This option makes it recreate the destination file instead. This may +be useful if all attributes at the destination path need to change +simultaneously. Caveat: in the current implementation, some extended +attributes may be set after the recreated file is moved into place. + +This option doesn't make sense in combination with bf(--inplace) or +bf(--append). + +dit(bf(--no-tweak-hlinked)) Tells rsync to recreate a destination file +instead of tweaking its attributes if it has multiple hard links. +You can use bf(--no-tweak-linked) to safely update a backup +that has files hard-linked from a previous backup. + +This option currently conflicts with bf(--inplace) and bf(--append). In +the future it might selectively disable those options for multiply linked +destination files. + dit(bf(-d, --dirs)) Tell the sending side to include any directories that are encountered. Unlike bf(--recursive), a directory's contents are not copied unless the directory name specified is "." or ends with a trailing slash --- /dev/null +++ new/testsuite/tweak-opts.test @@ -0,0 +1,105 @@ +#! /bin/sh + +# This program is distributable under the terms of the GNU GPL (see +# COPYING). + +# Test the --tweak, --no-tweak, and --no-tweak-hlinked options. + +. $srcdir/testsuite/rsync.fns + +chkfile="$scratchdir/rsync.chk" +outfile="$scratchdir/rsync.out" + +makepath "$fromdir" +echo data >"$fromdir/file1" +echo data >"$fromdir/file2" +echo data >"$fromdir/file3" +chmod 600 "$fromdir/file1" "$fromdir/file2" "$fromdir/file3" + +# testit