1 To use this patch, run these commands for a successful build:
3 patch -p1 <patches/atimes.diff
4 ./configure (optional if already run)
7 diff --git a/compat.c b/compat.c
10 @@ -44,6 +44,7 @@ extern int protocol_version;
11 extern int protect_args;
12 extern int preserve_uid;
13 extern int preserve_gid;
14 +extern int preserve_atimes;
15 extern int preserve_acls;
16 extern int preserve_xattrs;
17 extern int need_messages_from_generator;
18 @@ -60,7 +61,7 @@ extern iconv_t ic_send, ic_recv;
21 /* These index values are for the file-list's extra-attribute array. */
22 -int uid_ndx, gid_ndx, acls_ndx, xattrs_ndx, unsort_ndx;
23 +int uid_ndx, gid_ndx, atimes_ndx, acls_ndx, xattrs_ndx, unsort_ndx;
26 int filesfrom_convert = 0;
27 @@ -124,6 +125,8 @@ void setup_protocol(int f_out,int f_in)
28 uid_ndx = ++file_extra_cnt;
30 gid_ndx = ++file_extra_cnt;
31 + if (preserve_atimes)
32 + atimes_ndx = (file_extra_cnt += TIME_EXTRA_CNT);
33 if (preserve_acls && !am_sender)
34 acls_ndx = ++file_extra_cnt;
36 diff --git a/flist.c b/flist.c
39 @@ -53,6 +53,7 @@ extern int preserve_specials;
43 +extern int atimes_ndx;
44 extern int relative_paths;
45 extern int implied_dirs;
46 extern int file_extra_cnt;
47 @@ -342,7 +343,7 @@ int push_pathname(const char *dir, int len)
49 static void send_file_entry(int f, struct file_struct *file, int ndx, int first_ndx)
51 - static time_t modtime;
52 + static time_t modtime, atime;
54 #ifdef SUPPORT_HARD_LINKS
56 @@ -450,6 +451,13 @@ static void send_file_entry(int f, struct file_struct *file, int ndx, int first_
57 xflags |= XMIT_SAME_TIME;
59 modtime = file->modtime;
60 + if (atimes_ndx && !S_ISDIR(mode)) {
61 + time_t file_atime = f_atime(file);
62 + if (file_atime == atime)
63 + xflags |= XMIT_SAME_ATIME;
68 #ifdef SUPPORT_HARD_LINKS
70 @@ -522,6 +530,8 @@ static void send_file_entry(int f, struct file_struct *file, int ndx, int first_
72 if (!(xflags & XMIT_SAME_MODE))
73 write_int(f, to_wire_mode(mode));
74 + if (atimes_ndx && !S_ISDIR(mode) && !(xflags & XMIT_SAME_ATIME))
75 + write_varlong(f, atime, 4);
76 if (uid_ndx && !(xflags & XMIT_SAME_UID)) {
77 if (protocol_version < 30)
79 @@ -608,7 +618,7 @@ static void send_file_entry(int f, struct file_struct *file, int ndx, int first_
80 static struct file_struct *recv_file_entry(struct file_list *flist,
83 - static int64 modtime;
84 + static int64 modtime, atime;
86 #ifdef SUPPORT_HARD_LINKS
88 @@ -741,6 +751,16 @@ static struct file_struct *recv_file_entry(struct file_list *flist,
90 if (!(xflags & XMIT_SAME_MODE))
91 mode = from_wire_mode(read_int(f));
92 + if (atimes_ndx && !S_ISDIR(mode) && !(xflags & XMIT_SAME_ATIME)) {
93 + atime = read_varlong(f, 4);
94 +#if SIZEOF_TIME_T < SIZEOF_INT64
95 + if (!am_generator && (int64)(time_t)atime != atime) {
96 + rprintf(FERROR_XFER,
97 + "Access time value of %s truncated on receiver.\n",
103 if (chmod_modes && !S_ISLNK(mode))
104 mode = tweak_mode(mode, chmod_modes);
105 @@ -871,6 +891,8 @@ static struct file_struct *recv_file_entry(struct file_list *flist,
107 file->flags |= gid_flags;
110 + f_atime_set(file, (time_t)atime);
112 F_NDX(file) = flist->used + flist->ndx_start;
114 @@ -1203,6 +1225,8 @@ struct file_struct *make_file(const char *fname, struct file_list *flist,
115 F_OWNER(file) = st.st_uid;
117 F_GROUP(file) = st.st_gid;
119 + f_atime_set(file, st.st_atime);
121 if (basename != thisname)
122 file->dirname = lastdir;
123 diff --git a/generator.c b/generator.c
134 @@ -605,6 +606,9 @@ void itemize(const char *fnamecmp, struct file_struct *file, int ndx, int statre
135 && (!(iflags & ITEM_XNAME_FOLLOWS) || *xname))
136 || (keep_time && cmp_time(file->modtime, sxp->st.st_mtime) != 0))
137 iflags |= ITEM_REPORT_TIME;
138 + if (atimes_ndx && !S_ISDIR(file->mode) && !S_ISLNK(file->mode)
139 + && cmp_time(f_atime(file), sxp->st.st_atime) != 0)
140 + iflags |= ITEM_REPORT_ATIME;
141 #if !defined HAVE_LCHMOD && !defined HAVE_SETATTRLIST
142 if (S_ISLNK(file->mode)) {
144 @@ -958,6 +962,8 @@ static int try_dests_reg(struct file_struct *file, char *fname, int ndx,
146 if (!hard_link_one(file, fname, cmpbuf, 1))
149 + set_file_attrs(fname, file, sxp, NULL, 0);
150 if (preserve_hard_links && F_IS_HLINKED(file))
151 finish_hard_link(file, fname, ndx, &sxp->st, itemizing, code, j);
152 if (itemizing && (verbose > 1 || stdout_format_has_i > 1)) {
153 @@ -1144,6 +1150,7 @@ static int try_dests_non(struct file_struct *file, char *fname, int ndx,
154 static void list_file_entry(struct file_struct *f)
156 char permbuf[PERMSTRING_SIZE];
157 + time_t atime = atimes_ndx ? f_atime(f) : 0;
160 if (!F_IS_ACTIVE(f)) {
161 @@ -1158,14 +1165,16 @@ static void list_file_entry(struct file_struct *f)
164 if (preserve_links && S_ISLNK(f->mode)) {
165 - rprintf(FINFO, "%s %11.0f %s %s -> %s\n",
166 + rprintf(FINFO, "%s %11.0f %s %s %s -> %s\n",
167 permbuf, len, timestring(f->modtime),
168 + atimes_ndx ? timestring(atime) : "",
169 f_name(f, NULL), F_SYMLINK(f));
173 - rprintf(FINFO, "%s %11.0f %s %s\n",
174 + rprintf(FINFO, "%s %11.0f %s %s %s\n",
175 permbuf, len, timestring(f->modtime),
176 + atimes_ndx ? timestring(atime) : "",
180 @@ -1916,7 +1925,7 @@ static void touch_up_dirs(struct file_list *flist, int ndx)
181 if (!(file->mode & S_IWUSR))
182 do_chmod(fname, file->mode);
183 if (need_retouch_dir_times)
184 - set_modtime(fname, file->modtime, file->mode);
185 + set_times(fname, file->modtime, file->modtime, file->mode);
186 if (allowed_lull && !(counter % lull_mod))
187 maybe_send_keepalive();
188 else if (!(counter & 0xFF))
189 diff --git a/ifuncs.h b/ifuncs.h
192 @@ -57,6 +57,28 @@ from_wire_mode(int mode)
196 +static inline time_t
197 +f_atime(struct file_struct *fp)
199 +#if SIZEOF_TIME_T > 4
201 + memcpy(&atime, &REQ_EXTRA(fp, atimes_ndx)->unum, SIZEOF_TIME_T);
204 + return REQ_EXTRA(fp, atimes_ndx)->unum;
209 +f_atime_set(struct file_struct *fp, time_t atime)
211 +#if SIZEOF_TIME_T > 4
212 + memcpy(&REQ_EXTRA(fp, atimes_ndx)->unum, &atime, SIZEOF_TIME_T);
214 + REQ_EXTRA(fp, atimes_ndx)->unum = (uint32)atime;
219 isDigit(const char *ptr)
221 diff --git a/log.c b/log.c
224 @@ -642,7 +642,8 @@ static void log_formatted(enum logcode code, const char *format, const char *op,
225 c[5] = !(iflags & ITEM_REPORT_PERMS) ? '.' : 'p';
226 c[6] = !(iflags & ITEM_REPORT_OWNER) ? '.' : 'o';
227 c[7] = !(iflags & ITEM_REPORT_GROUP) ? '.' : 'g';
228 - c[8] = !(iflags & ITEM_REPORT_ATIME) ? '.' : 'u';
229 + c[8] = !(iflags & ITEM_REPORT_ATIME) ? '.'
230 + : S_ISLNK(file->mode) ? 'U' : 'u';
231 c[9] = !(iflags & ITEM_REPORT_ACL) ? '.' : 'a';
232 c[10] = !(iflags & ITEM_REPORT_XATTR) ? '.' : 'x';
234 diff --git a/options.c b/options.c
237 @@ -58,6 +58,7 @@ int preserve_specials = 0;
238 int preserve_uid = 0;
239 int preserve_gid = 0;
240 int preserve_times = 0;
241 +int preserve_atimes = 0;
245 @@ -348,6 +349,7 @@ void usage(enum logcode F)
246 rprintf(F," -D same as --devices --specials\n");
247 rprintf(F," -t, --times preserve modification times\n");
248 rprintf(F," -O, --omit-dir-times omit directories from --times\n");
249 + rprintf(F," -U, --atimes preserve access (last-used) times\n");
250 rprintf(F," --super receiver attempts super-user activities\n");
251 #ifdef SUPPORT_XATTRS
252 rprintf(F," --fake-super store/recover privileged attrs using xattrs\n");
253 @@ -483,6 +485,9 @@ static struct poptOption long_options[] = {
254 {"times", 't', POPT_ARG_VAL, &preserve_times, 2, 0, 0 },
255 {"no-times", 0, POPT_ARG_VAL, &preserve_times, 0, 0, 0 },
256 {"no-t", 0, POPT_ARG_VAL, &preserve_times, 0, 0, 0 },
257 + {"atimes", 'U', POPT_ARG_VAL, &preserve_atimes, 1, 0, 0 },
258 + {"no-atimes", 0, POPT_ARG_VAL, &preserve_atimes, 0, 0, 0 },
259 + {"no-U", 0, POPT_ARG_VAL, &preserve_atimes, 0, 0, 0 },
260 {"omit-dir-times", 'O', POPT_ARG_VAL, &omit_dir_times, 1, 0, 0 },
261 {"no-omit-dir-times",0, POPT_ARG_VAL, &omit_dir_times, 0, 0, 0 },
262 {"no-O", 0, POPT_ARG_VAL, &omit_dir_times, 0, 0, 0 },
263 @@ -1725,6 +1730,8 @@ void server_options(char **args, int *argc_p)
267 + if (preserve_atimes)
271 else if (preserve_executability && am_sender)
272 diff --git a/rsync.c b/rsync.c
275 @@ -344,6 +344,7 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
278 int change_uid, change_gid;
279 + time_t atime, mtime;
280 mode_t new_mode = file->mode;
283 @@ -387,18 +388,36 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
284 set_xattr(fname, file, fnamecmp, sxp);
287 + /* This code must be the first update in the function due to
288 + * how it uses the "updated" variable. */
289 if (!preserve_times || (S_ISDIR(sxp->st.st_mode) && preserve_times == 1))
290 flags |= ATTRS_SKIP_MTIME;
291 + if (!atimes_ndx || S_ISDIR(sxp->st.st_mode))
292 + flags |= ATTRS_SKIP_ATIME;
293 if (!(flags & ATTRS_SKIP_MTIME)
294 && cmp_time(sxp->st.st_mtime, file->modtime) != 0) {
295 - int ret = set_modtime(fname, file->modtime, sxp->st.st_mode);
296 + mtime = file->modtime;
299 + mtime = sxp->st.st_mtime;
300 + if (!(flags & ATTRS_SKIP_ATIME)) {
301 + time_t file_atime = f_atime(file);
302 + if (cmp_time(sxp->st.st_atime, file_atime) != 0) {
303 + atime = file_atime;
306 + atime = sxp->st.st_atime;
308 + atime = sxp->st.st_atime;
310 + int ret = set_times(fname, mtime, atime, sxp->st.st_mode);
312 rsyserr(FERROR_XFER, errno, "failed to set times on %s",
316 - if (ret == 0) /* ret == 1 if symlink could not be set */
318 + if (ret > 0) /* ret == 1 if symlink could not be set */
322 change_uid = am_root && uid_ndx && sxp->st.st_uid != (uid_t)F_OWNER(file);
323 @@ -528,7 +547,7 @@ int finish_transfer(const char *fname, const char *fnametmp,
325 /* Change permissions before putting the file into place. */
326 set_file_attrs(fnametmp, file, NULL, fnamecmp,
327 - ok_to_set_time ? 0 : ATTRS_SKIP_MTIME);
328 + ok_to_set_time ? 0 : ATTRS_SKIP_MTIME | ATTRS_SKIP_ATIME);
330 /* move tmp file over real file */
332 @@ -555,7 +574,7 @@ int finish_transfer(const char *fname, const char *fnametmp,
335 set_file_attrs(fnametmp, file, NULL, fnamecmp,
336 - ok_to_set_time ? 0 : ATTRS_SKIP_MTIME);
337 + ok_to_set_time ? 0 : ATTRS_SKIP_MTIME | ATTRS_SKIP_ATIME);
339 if (temp_copy_name) {
340 if (do_rename(fnametmp, fname) < 0) {
341 diff --git a/rsync.h b/rsync.h
345 #define XMIT_RDEV_MINOR_8_pre30 (1<<11) /* protocols 28 - 29 */
346 #define XMIT_GROUP_NAME_FOLLOWS (1<<11) /* protocols 30 - now */
347 #define XMIT_HLINK_FIRST (1<<12) /* protocols 30 - now (HLINKED files only) */
348 +#define XMIT_SAME_ATIME (1<<13) /* protocols ?? - now */
350 /* These flags are used in the live flist data. */
354 #define ATTRS_REPORT (1<<0)
355 #define ATTRS_SKIP_MTIME (1<<1)
356 +#define ATTRS_SKIP_ATIME (1<<2)
359 #define NORMAL_FLUSH 0
360 @@ -619,12 +621,14 @@ extern int file_extra_cnt;
361 extern int inc_recurse;
364 +extern int atimes_ndx;
366 extern int xattrs_ndx;
368 #define FILE_STRUCT_LEN (offsetof(struct file_struct, basename))
369 #define EXTRA_LEN (sizeof (union file_extras))
370 #define PTR_EXTRA_CNT ((sizeof (char *) + EXTRA_LEN - 1) / EXTRA_LEN)
371 +#define TIME_EXTRA_CNT ((SIZEOF_TIME_T + EXTRA_LEN - 1) / EXTRA_LEN)
372 #define DEV_EXTRA_CNT 2
373 #define DIRNODE_EXTRA_CNT 3
374 #define SUM_EXTRA_CNT ((MAX_DIGEST_LEN + EXTRA_LEN - 1) / EXTRA_LEN)
375 diff --git a/rsync.yo b/rsync.yo
378 @@ -349,6 +349,7 @@ to the detailed description below for a complete description. verb(
379 -D same as --devices --specials
380 -t, --times preserve modification times
381 -O, --omit-dir-times omit directories from --times
382 + -U, --atimes preserve access (use) times
383 --super receiver attempts super-user activities
384 --fake-super store/recover privileged attrs using xattrs
385 -S, --sparse handle sparse files efficiently
386 @@ -987,6 +988,12 @@ it is preserving modification times (see bf(--times)). If NFS is sharing
387 the directories on the receiving side, it is a good idea to use bf(-O).
388 This option is inferred if you use bf(--backup) without bf(--backup-dir).
390 +dit(bf(-U, --atimes)) This tells rsync to set the access (use) times of the
391 +destination files to the same value as the source files. Note that the
392 +reading of the source file may update the atime of the source files, so
393 +repeated rsync runs with --atimes may be needed if you want to force the
394 +access-time values to be 100% identical on the two systems.
396 dit(bf(--super)) This tells the receiving side to attempt super-user
397 activities even if the receiving rsync wasn't run by the super-user. These
398 activities include: preserving users via the bf(--owner) option, preserving
399 @@ -1687,8 +1694,10 @@ quote(itemization(
400 sender's value (requires bf(--owner) and super-user privileges).
401 it() A bf(g) means the group is different and is being updated to the
402 sender's value (requires bf(--group) and the authority to set the group).
403 - it() The bf(u) slot is reserved for reporting update (access) time changes
404 - (a feature that is not yet released).
405 + it() A bf(u) means the access (use) time is different and is being updated to
406 + the sender's value (requires bf(--atimes)). An alternate value of bf(U)
407 + means that the access time will be set to the transfer time, which happens
408 + when a symlink or directory is updated.
409 it() The bf(a) means that the ACL information changed.
410 it() The bf(x) slot is reserved for reporting extended attribute changes
411 (a feature that is not yet released).
412 diff --git a/testsuite/atimes.test b/testsuite/atimes.test
415 +++ b/testsuite/atimes.test
419 +# Test rsync copying atimes
421 +. "$suitedir/rsync.fns"
425 +touch "$fromdir/foo"
426 +touch -a -t 200102031717.42 "$fromdir/foo"
430 +checkit "$RSYNC -rtUgvvv \"$fromdir/\" \"$todir/\"" "$fromdir" "$todir"
432 +# The script would have aborted on error, so getting here means we've won.
434 diff --git a/testsuite/rsync.fns b/testsuite/rsync.fns
435 --- a/testsuite/rsync.fns
436 +++ b/testsuite/rsync.fns
437 @@ -187,6 +187,10 @@ checkit() {
438 # We can just write everything to stdout/stderr, because the
439 # wrapper hides it unless there is a problem.
441 + if test x$TLS_ARGS = x--atimes; then
442 + ( cd "$2" && rsync_ls_lR . ) > "$tmpdir/ls-from"
445 echo "Running: \"$1\""
448 @@ -194,10 +198,13 @@ checkit() {
452 + if test x$TLS_ARGS != x--atimes; then
453 + ( cd "$2" && rsync_ls_lR . ) > "$tmpdir/ls-from"
457 echo "check how the directory listings compare with diff:"
459 - ( cd "$2" && rsync_ls_lR . ) > "$tmpdir/ls-from"
460 ( cd "$3" && rsync_ls_lR . ) > "$tmpdir/ls-to"
461 diff $diffopt "$tmpdir/ls-from" "$tmpdir/ls-to" || failed=YES
463 diff --git a/tls.c b/tls.c
466 @@ -105,6 +105,8 @@ static int stat_xattr(const char *fname, STRUCT_STAT *fst)
470 +static int display_atimes = 0;
472 static void failed(char const *what, char const *where)
474 fprintf(stderr, PROGRAM ": %s %s: %s\n",
475 @@ -112,12 +114,29 @@ static void failed(char const *what, char const *where)
479 +static void storetime(char *dest, time_t t, size_t destsize)
482 + struct tm *mt = gmtime(&t);
484 + snprintf(dest, destsize,
485 + "%04d-%02d-%02d %02d:%02d:%02d ",
486 + (int)mt->tm_year + 1900,
487 + (int)mt->tm_mon + 1,
493 + strlcpy(dest, " ", destsize);
496 static void list_file(const char *fname)
499 char permbuf[PERMSTRING_SIZE];
506 if (do_lstat(fname, &buf) < 0)
507 @@ -154,19 +173,11 @@ static void list_file(const char *fname)
509 permstring(permbuf, buf.st_mode);
511 - if (buf.st_mtime) {
512 - mt = gmtime(&buf.st_mtime);
514 - snprintf(datebuf, sizeof datebuf,
515 - "%04d-%02d-%02d %02d:%02d:%02d",
516 - (int)mt->tm_year + 1900,
517 - (int)mt->tm_mon + 1,
523 - strlcpy(datebuf, " ", sizeof datebuf);
524 + storetime(mtimebuf, buf.st_mtime, sizeof mtimebuf);
525 + if (display_atimes)
526 + storetime(atimebuf, S_ISDIR(buf.st_mode) ? 0 : buf.st_atime, sizeof atimebuf);
528 + atimebuf[0] = '\0';
530 /* TODO: Perhaps escape special characters in fname? */
532 @@ -177,13 +188,14 @@ static void list_file(const char *fname)
533 (long)minor(buf.st_rdev));
534 } else /* NB: use double for size since it might not fit in a long. */
535 printf("%12.0f", (double)buf.st_size);
536 - printf(" %6ld.%-6ld %6ld %s %s%s\n",
537 + printf(" %6ld.%-6ld %6ld %s%s%s%s\n",
538 (long)buf.st_uid, (long)buf.st_gid, (long)buf.st_nlink,
539 - datebuf, fname, linkbuf);
540 + mtimebuf, atimebuf, fname, linkbuf);
543 static struct poptOption long_options[] = {
544 /* longName, shortName, argInfo, argPtr, value, descrip, argDesc */
545 + {"atimes", 'U', POPT_ARG_NONE, &display_atimes, 0, 0, 0},
546 #ifdef SUPPORT_XATTRS
547 {"fake-super", 'f', POPT_ARG_VAL, &am_root, -1, 0, 0 },
549 @@ -197,6 +209,7 @@ static void tls_usage(int ret)
550 fprintf(F,"usage: " PROGRAM " [OPTIONS] FILE ...\n");
551 fprintf(F,"Trivial file listing program for portably checking rsync\n");
552 fprintf(F,"\nOptions:\n");
553 + fprintf(F," -U, --atimes display access (last-used) times\n");
554 #ifdef SUPPORT_XATTRS
555 fprintf(F," -f, --fake-super display attributes including fake-super xattrs\n");
557 diff --git a/util.c b/util.c
560 @@ -122,7 +122,7 @@ NORETURN void overflow_exit(const char *str)
561 exit_cleanup(RERR_MALLOC);
564 -int set_modtime(const char *fname, time_t modtime, mode_t mode)
565 +int set_times(const char *fname, time_t modtime, time_t atime, mode_t mode)
567 #if !defined HAVE_LUTIMES || !defined HAVE_UTIMES
569 @@ -130,9 +130,13 @@ int set_modtime(const char *fname, time_t modtime, mode_t mode)
573 - rprintf(FINFO, "set modtime of %s to (%ld) %s",
574 + char mtimebuf[200];
576 + strlcpy(mtimebuf, timestring(modtime), sizeof mtimebuf);
578 + "set modtime, atime of %s to (%ld) %s, (%ld) %s\n",
579 fname, (long)modtime,
580 - asctime(localtime(&modtime)));
581 + mtimebuf, (long)atime, timestring(atime));
585 @@ -141,7 +145,7 @@ int set_modtime(const char *fname, time_t modtime, mode_t mode)
589 - t[0].tv_sec = time(NULL);
590 + t[0].tv_sec = atime;
592 t[1].tv_sec = modtime;
594 @@ -155,12 +159,12 @@ int set_modtime(const char *fname, time_t modtime, mode_t mode)
595 return utimes(fname, t);
596 #elif defined HAVE_STRUCT_UTIMBUF
598 - tbuf.actime = time(NULL);
599 + tbuf.actime = atime;
600 tbuf.modtime = modtime;
601 return utime(fname,&tbuf);
602 #elif defined HAVE_UTIME
607 return utime(fname,t);