From 839dbff2aaf0277471e1986a3cd0f869e0bdda24 Mon Sep 17 00:00:00 2001 From: Wayne Davison Date: Sun, 24 Jan 2016 11:12:38 -0800 Subject: [PATCH] Add support for comparing nanoseconds on the receiver. This patch adds the ability to specify --modify-window=-1 (aka -@-1) to ask rsync to compare files with the full nanosecond timestamps. The default is still -@0 for the moment, which ignores nanoseconds in time comparisons. Changing the default to -1 would cause a copy from ext4 to ext3 to constantly compare as different, or a copy there and back again to do a full copy as it zeroed all the nanosecond times. Such a change might be too much of a functional difference for things like backup solutions to handle without a warning period. The current plan is to support nanosecond comparisons for those that want them, and possibly change the default window value from 0 to -1 at some point in the future. --- generator.c | 24 +++++++++++++----------- options.c | 9 +++++---- rsync.yo | 24 +++++++++++++++++------- util.c | 17 +++++++++++------ 4 files changed, 46 insertions(+), 28 deletions(-) diff --git a/generator.c b/generator.c index ddf44a02..fd7c8f86 100644 --- a/generator.c +++ b/generator.c @@ -382,9 +382,13 @@ static void do_delete_pass(void) rprintf(FINFO, " \r"); } -static inline int time_differs(struct file_struct *file, stat_x *sxp) +static inline int time_diff(STRUCT_STAT *stp, struct file_struct *file) { - return cmp_time(sxp->st.st_mtime, file->modtime); +#ifdef ST_MTIME_NSEC + return cmp_time(stp->st_mtime, stp->ST_MTIME_NSEC, file->modtime, F_MOD_NSEC(file)); +#else + return cmp_time(stp->st_mtime, 0L, file->modtime, 0L); +#endif } static inline int perms_differ(struct file_struct *file, stat_x *sxp) @@ -441,7 +445,7 @@ int unchanged_attrs(const char *fname, struct file_struct *file, stat_x *sxp) { if (S_ISLNK(file->mode)) { #ifdef CAN_SET_SYMLINK_TIMES - if (preserve_times & PRESERVE_LINK_TIMES && time_differs(file, sxp)) + if (preserve_times & PRESERVE_LINK_TIMES && time_diff(&sxp->st, file)) return 0; #endif #ifdef CAN_CHMOD_SYMLINK @@ -461,7 +465,7 @@ int unchanged_attrs(const char *fname, struct file_struct *file, stat_x *sxp) return 0; #endif } else { - if (preserve_times && time_differs(file, sxp)) + if (preserve_times && time_diff(&sxp->st, file)) return 0; if (perms_differ(file, sxp)) return 0; @@ -496,7 +500,7 @@ void itemize(const char *fnamecmp, struct file_struct *file, int ndx, int statre if (iflags & ITEM_LOCAL_CHANGE) iflags |= symlink_timeset_failed_flags; } else if (keep_time - ? cmp_time(file->modtime, sxp->st.st_mtime) != 0 + ? time_diff(&sxp->st, file) : iflags & (ITEM_TRANSFER|ITEM_LOCAL_CHANGE) && !(iflags & ITEM_MATCHED) && (!(iflags & ITEM_XNAME_FOLLOWS) || *xname)) iflags |= ITEM_REPORT_TIME; @@ -588,7 +592,7 @@ int unchanged_file(char *fn, struct file_struct *file, STRUCT_STAT *st) if (ignore_times) return 0; - return cmp_time(st->st_mtime, file->modtime) == 0; + return time_diff(st, file) == 0; } @@ -765,7 +769,7 @@ static struct file_struct *find_fuzzy(struct file_struct *file, struct file_list if (!S_ISREG(fp->mode) || !F_LENGTH(fp) || fp->flags & FLAG_FILE_SENT) continue; - if (F_LENGTH(fp) == F_LENGTH(file) && cmp_time(fp->modtime, file->modtime) == 0) { + if (F_LENGTH(fp) == F_LENGTH(file) && cmp_time(fp->modtime, 0L, file->modtime, 0L) == 0) { if (DEBUG_GTE(FUZZY, 2)) rprintf(FINFO, "fuzzy size/modtime match for %s\n", f_name(fp, NULL)); *fnamecmp_type_ptr = FNAMECMP_FUZZY + i; @@ -1674,8 +1678,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx, goto cleanup; } - if (update_only > 0 && statret == 0 - && cmp_time(sx.st.st_mtime, file->modtime) > 0) { + if (update_only > 0 && statret == 0 && time_diff(&sx.st, file) > 0) { if (INFO_GTE(SKIP, 1)) rprintf(FINFO, "%s is newer\n", fname); #ifdef SUPPORT_HARD_LINKS @@ -2062,8 +2065,7 @@ static void touch_up_dirs(struct file_list *flist, int ndx) do_chmod(fname, file->mode); if (need_retouch_dir_times) { STRUCT_STAT st; - if (link_stat(fname, &st, 0) == 0 - && cmp_time(st.st_mtime, file->modtime) != 0) + if (link_stat(fname, &st, 0) == 0 && time_diff(&st, file)) set_modtime(fname, file->modtime, F_MOD_NSEC(file), file->mode); } if (counter >= loopchk_limit) { diff --git a/options.c b/options.c index 74239bf2..4a5cdc8b 100644 --- a/options.c +++ b/options.c @@ -755,7 +755,7 @@ void usage(enum logcode F) rprintf(F," -I, --ignore-times don't skip files that match in size and mod-time\n"); rprintf(F," -M, --remote-option=OPTION send OPTION to the remote side only\n"); rprintf(F," --size-only skip files that match in size\n"); - rprintf(F," --modify-window=NUM compare mod-times with reduced accuracy\n"); + rprintf(F," -@, --modify-window=NUM set the accuracy for mod-time comparisons\n"); rprintf(F," -T, --temp-dir=DIR create temporary files in directory DIR\n"); rprintf(F," -y, --fuzzy find similar file for basis if no dest file\n"); rprintf(F," --compare-dest=DIR also compare destination files relative to DIR\n"); @@ -871,7 +871,7 @@ static struct poptOption long_options[] = { {"omit-link-times", 'J', POPT_ARG_VAL, &omit_link_times, 1, 0, 0 }, {"no-omit-link-times",0, POPT_ARG_VAL, &omit_link_times, 0, 0, 0 }, {"no-J", 0, POPT_ARG_VAL, &omit_link_times, 0, 0, 0 }, - {"modify-window", 0, POPT_ARG_INT, &modify_window, OPT_MODIFY_WINDOW, 0, 0 }, + {"modify-window", '@', POPT_ARG_INT, &modify_window, OPT_MODIFY_WINDOW, 0, 0 }, {"super", 0, POPT_ARG_VAL, &am_root, 2, 0, 0 }, {"no-super", 0, POPT_ARG_VAL, &am_root, 0, 0, 0 }, {"fake-super", 0, POPT_ARG_VAL, &am_root, -1, 0, 0 }, @@ -2649,8 +2649,9 @@ void server_options(char **args, int *argc_p) else if (missing_args == 1 && !am_sender) args[ac++] = "--ignore-missing-args"; - if (modify_window_set) { - if (asprintf(&arg, "--modify-window=%d", modify_window) < 0) + if (modify_window_set && am_sender) { + char *fmt = modify_window < 0 ? "-@%d" : "--modify-window=%d"; + if (asprintf(&arg, fmt, modify_window) < 0) goto oom; args[ac++] = arg; } diff --git a/rsync.yo b/rsync.yo index 09ee92f6..89718286 100644 --- a/rsync.yo +++ b/rsync.yo @@ -413,7 +413,7 @@ to the detailed description below for a complete description. verb( --contimeout=SECONDS set daemon connection timeout in seconds -I, --ignore-times don't skip files that match size and time --size-only skip files that match in size - --modify-window=NUM compare mod-times with reduced accuracy + -@, --modify-window=NUM set the accuracy for mod-time comparisons -T, --temp-dir=DIR create temporary files in directory DIR -y, --fuzzy find similar file for basis if no dest file --compare-dest=DIR also compare received files relative to DIR @@ -605,13 +605,23 @@ time to just looking for files that have changed in size. This is useful when starting to use rsync after using another mirroring system which may not preserve timestamps exactly. -dit(bf(--modify-window)) When comparing two timestamps, rsync treats the +dit(bf(-@, --modify-window)) When comparing two timestamps, rsync treats the timestamps as being equal if they differ by no more than the modify-window -value. This is normally 0 (for an exact match), but you may find it useful -to set this to a larger value in some situations. In particular, when -transferring to or from an MS Windows FAT filesystem (which represents -times with a 2-second resolution), bf(--modify-window=1) is useful -(allowing times to differ by up to 1 second). +value. The default is 0, which matches just integer seconds. If you specify a +negative value (and the receiver is at least version 3.1.3) then nanoseconds +will also be taken into account. Specifying 1 is useful for copies to/from MS +Windows FAT filesystems, because FAT represents times with a 2-second +resolution (allowing times to differ from the original by up to 1 second). + +If you want all your transfers to default to comparing nanoseconds, you can +create a ~/.popt file and put these lines in it: + +quote(tt( rsync alias -a -a@-1)) +quote(tt( rsync alias -t -t@-1)) + +With that as the default, you'd need to specify bf(--modify-window=0) (aka +bf(-@0)) to override it and ignore nanoseconds, e.g. if you're copying between +ext3 and ext4, or if the receiving rsync is older than 3.1.3. dit(bf(-c, --checksum)) This changes the way rsync checks if the files have been changed and are in need of a transfer. Without this option, rsync diff --git a/util.c b/util.c index baee467c..2a3e5ba9 100644 --- a/util.c +++ b/util.c @@ -1324,15 +1324,20 @@ char *timestring(time_t t) * * @retval -1 if the 2nd is later **/ -int cmp_time(time_t file1, time_t file2) +int cmp_time(time_t f1_sec, unsigned long f1_nsec, time_t f2_sec, unsigned long f2_nsec) { - if (file2 > file1) { + if (f2_sec > f1_sec) { /* The final comparison makes sure that modify_window doesn't overflow a - * time_t, which would mean that file2 must be in the equality window. */ - if (!modify_window || (file2 > file1 + modify_window && file1 + modify_window > file1)) + * time_t, which would mean that f2_sec must be in the equality window. */ + if (modify_window <= 0 || (f2_sec > f1_sec + modify_window && f1_sec + modify_window > f1_sec)) return -1; - } else if (file1 > file2) { - if (!modify_window || (file1 > file2 + modify_window && file2 + modify_window > file2)) + } else if (f1_sec > f2_sec) { + if (modify_window <= 0 || (f1_sec > f2_sec + modify_window && f2_sec + modify_window > f2_sec)) + return 1; + } else if (modify_window < 0) { + if (f2_nsec > f1_nsec) + return -1; + else if (f1_nsec > f2_nsec) return 1; } return 0; -- 2.34.1