Updated patches to work with the current trunk.
[rsync/rsync-patches.git] / atimes.diff
... / ...
CommitLineData
1To use this patch, run these commands for a successful build:
2
3 patch -p1 <patches/atimes.diff
4 ./configure (optional if already run)
5 make
6
7based-on: 24079e988fc31af4eba56cd2701fdc5a4154980d
8diff --git a/compat.c b/compat.c
9--- a/compat.c
10+++ b/compat.c
11@@ -43,6 +43,7 @@ extern int protocol_version;
12 extern int protect_args;
13 extern int preserve_uid;
14 extern int preserve_gid;
15+extern int preserve_atimes;
16 extern int preserve_acls;
17 extern int preserve_xattrs;
18 extern int need_messages_from_generator;
19@@ -60,7 +61,7 @@ extern char *iconv_opt;
20 #endif
21
22 /* These index values are for the file-list's extra-attribute array. */
23-int uid_ndx, gid_ndx, acls_ndx, xattrs_ndx, unsort_ndx;
24+int uid_ndx, gid_ndx, atimes_ndx, acls_ndx, xattrs_ndx, unsort_ndx;
25
26 int receiver_symlink_times = 0; /* receiver can set the time on a symlink */
27 int sender_symlink_iconv = 0; /* sender should convert symlink content */
28@@ -136,6 +137,8 @@ void setup_protocol(int f_out,int f_in)
29 uid_ndx = ++file_extra_cnt;
30 if (preserve_gid)
31 gid_ndx = ++file_extra_cnt;
32+ if (preserve_atimes)
33+ atimes_ndx = (file_extra_cnt += TIME_EXTRA_CNT);
34 if (preserve_acls && !am_sender)
35 acls_ndx = ++file_extra_cnt;
36 if (preserve_xattrs)
37diff --git a/flist.c b/flist.c
38--- a/flist.c
39+++ b/flist.c
40@@ -54,6 +54,7 @@ extern int missing_args;
41 extern int uid_ndx;
42 extern int gid_ndx;
43 extern int eol_nulls;
44+extern int atimes_ndx;
45 extern int relative_paths;
46 extern int implied_dirs;
47 extern int file_extra_cnt;
48@@ -404,7 +405,7 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
49 #endif
50 int ndx, int first_ndx)
51 {
52- static time_t modtime;
53+ static time_t modtime, atime;
54 static mode_t mode;
55 #ifdef SUPPORT_HARD_LINKS
56 static int64 dev;
57@@ -504,6 +505,13 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
58 modtime = file->modtime;
59 if (NSEC_BUMP(file) && protocol_version >= 31)
60 xflags |= XMIT_MOD_NSEC;
61+ if (atimes_ndx && !S_ISDIR(mode)) {
62+ time_t file_atime = f_atime(file);
63+ if (file_atime == atime)
64+ xflags |= XMIT_SAME_ATIME;
65+ else
66+ atime = file_atime;
67+ }
68
69 #ifdef SUPPORT_HARD_LINKS
70 if (tmp_dev != 0) {
71@@ -590,6 +598,8 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
72 write_varint(f, F_MOD_NSEC(file));
73 if (!(xflags & XMIT_SAME_MODE))
74 write_int(f, to_wire_mode(mode));
75+ if (atimes_ndx && !S_ISDIR(mode) && !(xflags & XMIT_SAME_ATIME))
76+ write_varlong(f, atime, 4);
77 if (preserve_uid && !(xflags & XMIT_SAME_UID)) {
78 if (protocol_version < 30)
79 write_int(f, uid);
80@@ -675,7 +685,7 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
81
82 static struct file_struct *recv_file_entry(int f, struct file_list *flist, int xflags)
83 {
84- static int64 modtime;
85+ static int64 modtime, atime;
86 static mode_t mode;
87 #ifdef SUPPORT_HARD_LINKS
88 static int64 dev;
89@@ -819,6 +829,16 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x
90 modtime_nsec = 0;
91 if (!(xflags & XMIT_SAME_MODE))
92 mode = from_wire_mode(read_int(f));
93+ if (atimes_ndx && !S_ISDIR(mode) && !(xflags & XMIT_SAME_ATIME)) {
94+ atime = read_varlong(f, 4);
95+#if SIZEOF_TIME_T < SIZEOF_INT64
96+ if (!am_generator && (int64)(time_t)atime != atime) {
97+ rprintf(FERROR_XFER,
98+ "Access time value of %s truncated on receiver.\n",
99+ lastname);
100+ }
101+#endif
102+ }
103
104 if (chmod_modes && !S_ISLNK(mode) && mode)
105 mode = tweak_mode(mode, chmod_modes);
106@@ -979,6 +999,8 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x
107 F_GROUP(file) = gid;
108 file->flags |= gid_flags;
109 }
110+ if (atimes_ndx)
111+ f_atime_set(file, (time_t)atime);
112 if (unsort_ndx)
113 F_NDX(file) = flist->used + flist->ndx_start;
114
115@@ -1374,6 +1396,8 @@ struct file_struct *make_file(const char *fname, struct file_list *flist,
116 F_OWNER(file) = st.st_uid;
117 if (gid_ndx) /* Check gid_ndx instead of preserve_gid for del support */
118 F_GROUP(file) = st.st_gid;
119+ if (atimes_ndx)
120+ f_atime_set(file, st.st_atime);
121
122 if (basename != thisname)
123 file->dirname = lastdir;
124diff --git a/generator.c b/generator.c
125--- a/generator.c
126+++ b/generator.c
127@@ -455,6 +455,9 @@ void itemize(const char *fnamecmp, struct file_struct *file, int ndx, int statre
128 : iflags & (ITEM_TRANSFER|ITEM_LOCAL_CHANGE) && !(iflags & ITEM_MATCHED)
129 && (!(iflags & ITEM_XNAME_FOLLOWS) || *xname))
130 iflags |= ITEM_REPORT_TIME;
131+ if (atimes_ndx && !S_ISDIR(file->mode) && !S_ISLNK(file->mode)
132+ && cmp_time(f_atime(file), sxp->st.st_atime) != 0)
133+ iflags |= ITEM_REPORT_ATIME;
134 #if !defined HAVE_LCHMOD && !defined HAVE_SETATTRLIST
135 if (S_ISLNK(file->mode)) {
136 ;
137@@ -825,6 +828,8 @@ static int try_dests_reg(struct file_struct *file, char *fname, int ndx,
138 if (link_dest) {
139 if (!hard_link_one(file, fname, cmpbuf, 1))
140 goto try_a_copy;
141+ if (atimes_ndx)
142+ set_file_attrs(fname, file, sxp, NULL, 0);
143 if (preserve_hard_links && F_IS_HLINKED(file))
144 finish_hard_link(file, fname, ndx, &sxp->st, itemizing, code, j);
145 if (!maybe_ATTRS_REPORT && (INFO_GTE(NAME, 2) || stdout_format_has_i > 1)) {
146@@ -1017,6 +1022,7 @@ static int try_dests_non(struct file_struct *file, char *fname, int ndx,
147 static void list_file_entry(struct file_struct *f)
148 {
149 char permbuf[PERMSTRING_SIZE];
150+ time_t atime = atimes_ndx ? f_atime(f) : 0;
151 int64 len;
152 int colwidth = human_readable ? 14 : 11;
153
154@@ -1032,10 +1038,11 @@ static void list_file_entry(struct file_struct *f)
155
156 #ifdef SUPPORT_LINKS
157 if (preserve_links && S_ISLNK(f->mode)) {
158- rprintf(FINFO, "%s %*s %s %s -> %s\n",
159+ rprintf(FINFO, "%s %*s %s %s %s -> %s\n",
160 permbuf, colwidth, comma_num(len),
161- timestring(f->modtime), f_name(f, NULL),
162- F_SYMLINK(f));
163+ timestring(f->modtime),
164+ atimes_ndx ? timestring(atime) : "",
165+ f_name(f, NULL), F_SYMLINK(f));
166 } else
167 #endif
168 if (missing_args == 2 && f->mode == 0) {
169@@ -1043,9 +1050,11 @@ static void list_file_entry(struct file_struct *f)
170 colwidth + 31, "*missing",
171 f_name(f, NULL));
172 } else {
173- rprintf(FINFO, "%s %*s %s %s\n",
174+ rprintf(FINFO, "%s %*s %s %s %s\n",
175 permbuf, colwidth, comma_num(len),
176- timestring(f->modtime), f_name(f, NULL));
177+ timestring(f->modtime),
178+ atimes_ndx ? timestring(atime) : "",
179+ f_name(f, NULL));
180 }
181 }
182
183@@ -1925,7 +1934,7 @@ static void touch_up_dirs(struct file_list *flist, int ndx)
184 STRUCT_STAT st;
185 if (link_stat(fname, &st, 0) == 0
186 && cmp_time(st.st_mtime, file->modtime) != 0)
187- set_modtime(fname, file->modtime, F_MOD_NSEC(file), file->mode);
188+ set_times(fname, file->modtime, F_MOD_NSEC(file), file->modtime, file->mode);
189 }
190 if (counter >= loopchk_limit) {
191 if (allowed_lull)
192diff --git a/ifuncs.h b/ifuncs.h
193--- a/ifuncs.h
194+++ b/ifuncs.h
195@@ -43,6 +43,28 @@ free_xbuf(xbuf *xb)
196 memset(xb, 0, sizeof (xbuf));
197 }
198
199+static inline time_t
200+f_atime(struct file_struct *fp)
201+{
202+#if SIZEOF_TIME_T > 4
203+ time_t atime;
204+ memcpy(&atime, &REQ_EXTRA(fp, atimes_ndx)->unum, SIZEOF_TIME_T);
205+ return atime;
206+#else
207+ return REQ_EXTRA(fp, atimes_ndx)->unum;
208+#endif
209+}
210+
211+static inline void
212+f_atime_set(struct file_struct *fp, time_t atime)
213+{
214+#if SIZEOF_TIME_T > 4
215+ memcpy(&REQ_EXTRA(fp, atimes_ndx)->unum, &atime, SIZEOF_TIME_T);
216+#else
217+ REQ_EXTRA(fp, atimes_ndx)->unum = (uint32)atime;
218+#endif
219+}
220+
221 static inline int
222 to_wire_mode(mode_t mode)
223 {
224diff --git a/log.c b/log.c
225--- a/log.c
226+++ b/log.c
227@@ -733,7 +733,8 @@ static void log_formatted(enum logcode code, const char *format, const char *op,
228 c[5] = !(iflags & ITEM_REPORT_PERMS) ? '.' : 'p';
229 c[6] = !(iflags & ITEM_REPORT_OWNER) ? '.' : 'o';
230 c[7] = !(iflags & ITEM_REPORT_GROUP) ? '.' : 'g';
231- c[8] = !(iflags & ITEM_REPORT_ATIME) ? '.' : 'u';
232+ c[8] = !(iflags & ITEM_REPORT_ATIME) ? '.'
233+ : S_ISLNK(file->mode) ? 'U' : 'u';
234 c[9] = !(iflags & ITEM_REPORT_ACL) ? '.' : 'a';
235 c[10] = !(iflags & ITEM_REPORT_XATTR) ? '.' : 'x';
236 c[11] = '\0';
237diff --git a/options.c b/options.c
238--- a/options.c
239+++ b/options.c
240@@ -59,6 +59,7 @@ int preserve_specials = 0;
241 int preserve_uid = 0;
242 int preserve_gid = 0;
243 int preserve_times = 0;
244+int preserve_atimes = 0;
245 int update_only = 0;
246 int cvs_exclude = 0;
247 int dry_run = 0;
248@@ -699,6 +700,7 @@ void usage(enum logcode F)
249 rprintf(F," -D same as --devices --specials\n");
250 rprintf(F," -t, --times preserve modification times\n");
251 rprintf(F," -O, --omit-dir-times omit directories from --times\n");
252+ rprintf(F," -U, --atimes preserve access (last-used) times\n");
253 rprintf(F," --super receiver attempts super-user activities\n");
254 #ifdef SUPPORT_XATTRS
255 rprintf(F," --fake-super store/recover privileged attrs using xattrs\n");
256@@ -846,6 +848,9 @@ static struct poptOption long_options[] = {
257 {"times", 't', POPT_ARG_VAL, &preserve_times, 2, 0, 0 },
258 {"no-times", 0, POPT_ARG_VAL, &preserve_times, 0, 0, 0 },
259 {"no-t", 0, POPT_ARG_VAL, &preserve_times, 0, 0, 0 },
260+ {"atimes", 'U', POPT_ARG_VAL, &preserve_atimes, 1, 0, 0 },
261+ {"no-atimes", 0, POPT_ARG_VAL, &preserve_atimes, 0, 0, 0 },
262+ {"no-U", 0, POPT_ARG_VAL, &preserve_atimes, 0, 0, 0 },
263 {"omit-dir-times", 'O', POPT_ARG_VAL, &omit_dir_times, 1, 0, 0 },
264 {"no-omit-dir-times",0, POPT_ARG_VAL, &omit_dir_times, 0, 0, 0 },
265 {"no-O", 0, POPT_ARG_VAL, &omit_dir_times, 0, 0, 0 },
266@@ -2326,6 +2331,8 @@ void server_options(char **args, int *argc_p)
267 argstr[x++] = 'D';
268 if (preserve_times)
269 argstr[x++] = 't';
270+ if (preserve_atimes)
271+ argstr[x++] = 'U';
272 if (preserve_perms)
273 argstr[x++] = 'p';
274 else if (preserve_executability && am_sender)
275diff --git a/rsync.c b/rsync.c
276--- a/rsync.c
277+++ b/rsync.c
278@@ -451,6 +451,7 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
279 int updated = 0;
280 stat_x sx2;
281 int change_uid, change_gid;
282+ time_t atime, mtime;
283 mode_t new_mode = file->mode;
284 int inherit;
285
286@@ -489,20 +490,38 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
287 set_xattr(fname, file, fnamecmp, sxp);
288 #endif
289
290+ /* This code must be the first update in the function due to
291+ * how it uses the "updated" variable. */
292 if (!preserve_times || (S_ISDIR(sxp->st.st_mode) && preserve_times == 1))
293 flags |= ATTRS_SKIP_MTIME;
294+ if (!atimes_ndx || S_ISDIR(sxp->st.st_mode))
295+ flags |= ATTRS_SKIP_ATIME;
296 if (!(flags & ATTRS_SKIP_MTIME)
297 && cmp_time(sxp->st.st_mtime, file->modtime) != 0) {
298- int ret = set_modtime(fname, file->modtime, F_MOD_NSEC(file), sxp->st.st_mode);
299+ mtime = file->modtime;
300+ updated = 1;
301+ } else
302+ mtime = sxp->st.st_mtime;
303+ if (!(flags & ATTRS_SKIP_ATIME)) {
304+ time_t file_atime = f_atime(file);
305+ if (cmp_time(sxp->st.st_atime, file_atime) != 0) {
306+ atime = file_atime;
307+ updated = 1;
308+ } else
309+ atime = sxp->st.st_atime;
310+ } else
311+ atime = sxp->st.st_atime;
312+ if (updated) {
313+ int ret = set_times(fname, mtime, F_MOD_NSEC(file), atime, sxp->st.st_mode);
314 if (ret < 0) {
315 rsyserr(FERROR_XFER, errno, "failed to set times on %s",
316 full_fname(fname));
317 goto cleanup;
318 }
319- if (ret == 0) /* ret == 1 if symlink could not be set */
320- updated = 1;
321- else
322+ if (ret > 0) { /* ret == 1 if symlink could not be set */
323+ updated = 0;
324 file->flags |= FLAG_TIME_FAILED;
325+ }
326 }
327
328 change_uid = am_root && uid_ndx && sxp->st.st_uid != (uid_t)F_OWNER(file);
329@@ -639,7 +658,7 @@ int finish_transfer(const char *fname, const char *fnametmp,
330
331 /* Change permissions before putting the file into place. */
332 set_file_attrs(fnametmp, file, NULL, fnamecmp,
333- ok_to_set_time ? 0 : ATTRS_SKIP_MTIME);
334+ ok_to_set_time ? 0 : ATTRS_SKIP_MTIME | ATTRS_SKIP_ATIME);
335
336 /* move tmp file over real file */
337 if (DEBUG_GTE(RECV, 1))
338@@ -666,7 +685,7 @@ int finish_transfer(const char *fname, const char *fnametmp,
339
340 do_set_file_attrs:
341 set_file_attrs(fnametmp, file, NULL, fnamecmp,
342- ok_to_set_time ? 0 : ATTRS_SKIP_MTIME);
343+ ok_to_set_time ? 0 : ATTRS_SKIP_MTIME | ATTRS_SKIP_ATIME);
344
345 if (temp_copy_name) {
346 if (do_rename(fnametmp, fname) < 0) {
347diff --git a/rsync.h b/rsync.h
348--- a/rsync.h
349+++ b/rsync.h
350@@ -62,6 +62,7 @@
351 #define XMIT_HLINK_FIRST (1<<12) /* protocols 30 - now (HLINKED files only) */
352 #define XMIT_IO_ERROR_ENDLIST (1<<12) /* protocols 31 - now (w/XMIT_EXTENDED_FLAGS) */
353 #define XMIT_MOD_NSEC (1<<13) /* protocols 31 - now */
354+#define XMIT_SAME_ATIME (1<<14) /* protocols ?? - now */
355
356 /* These flags are used in the live flist data. */
357
358@@ -160,6 +161,7 @@
359
360 #define ATTRS_REPORT (1<<0)
361 #define ATTRS_SKIP_MTIME (1<<1)
362+#define ATTRS_SKIP_ATIME (1<<2)
363
364 #define FULL_FLUSH 1
365 #define NORMAL_FLUSH 0
366@@ -664,12 +666,14 @@ extern int file_extra_cnt;
367 extern int inc_recurse;
368 extern int uid_ndx;
369 extern int gid_ndx;
370+extern int atimes_ndx;
371 extern int acls_ndx;
372 extern int xattrs_ndx;
373
374 #define FILE_STRUCT_LEN (offsetof(struct file_struct, basename))
375 #define EXTRA_LEN (sizeof (union file_extras))
376 #define PTR_EXTRA_CNT ((sizeof (char *) + EXTRA_LEN - 1) / EXTRA_LEN)
377+#define TIME_EXTRA_CNT ((SIZEOF_TIME_T + EXTRA_LEN - 1) / EXTRA_LEN)
378 #define DEV_EXTRA_CNT 2
379 #define DIRNODE_EXTRA_CNT 3
380 #define SUM_EXTRA_CNT ((MAX_DIGEST_LEN + EXTRA_LEN - 1) / EXTRA_LEN)
381diff --git a/rsync.yo b/rsync.yo
382--- a/rsync.yo
383+++ b/rsync.yo
384@@ -356,6 +356,7 @@ to the detailed description below for a complete description. verb(
385 -D same as --devices --specials
386 -t, --times preserve modification times
387 -O, --omit-dir-times omit directories from --times
388+ -U, --atimes preserve access (use) times
389 --super receiver attempts super-user activities
390 --fake-super store/recover privileged attrs using xattrs
391 -S, --sparse handle sparse files efficiently
392@@ -1078,6 +1079,12 @@ it is preserving modification times (see bf(--times)). If NFS is sharing
393 the directories on the receiving side, it is a good idea to use bf(-O).
394 This option is inferred if you use bf(--backup) without bf(--backup-dir).
395
396+dit(bf(-U, --atimes)) This tells rsync to set the access (use) times of the
397+destination files to the same value as the source files. Note that the
398+reading of the source file may update the atime of the source files, so
399+repeated rsync runs with --atimes may be needed if you want to force the
400+access-time values to be 100% identical on the two systems.
401+
402 dit(bf(--super)) This tells the receiving side to attempt super-user
403 activities even if the receiving rsync wasn't run by the super-user. These
404 activities include: preserving users via the bf(--owner) option, preserving
405@@ -1931,7 +1938,10 @@ quote(itemization(
406 sender's value (requires bf(--owner) and super-user privileges).
407 it() A bf(g) means the group is different and is being updated to the
408 sender's value (requires bf(--group) and the authority to set the group).
409- it() The bf(u) slot is reserved for future use.
410+ it() A bf(u) means the access (use) time is different and is being updated to
411+ the sender's value (requires bf(--atimes)). An alternate value of bf(U)
412+ means that the access time will be set to the transfer time, which happens
413+ when a symlink or directory is updated.
414 it() The bf(a) means that the ACL information changed.
415 it() The bf(x) means that the extended attribute information changed.
416 ))
417diff --git a/testsuite/atimes.test b/testsuite/atimes.test
418new file mode 100644
419--- /dev/null
420+++ b/testsuite/atimes.test
421@@ -0,0 +1,17 @@
422+#! /bin/sh
423+
424+# Test rsync copying atimes
425+
426+. "$suitedir/rsync.fns"
427+
428+mkdir "$fromdir"
429+
430+touch "$fromdir/foo"
431+touch -a -t 200102031717.42 "$fromdir/foo"
432+
433+TLS_ARGS=--atimes
434+
435+checkit "$RSYNC -rtUgvvv \"$fromdir/\" \"$todir/\"" "$fromdir" "$todir"
436+
437+# The script would have aborted on error, so getting here means we've won.
438+exit 0
439diff --git a/testsuite/rsync.fns b/testsuite/rsync.fns
440--- a/testsuite/rsync.fns
441+++ b/testsuite/rsync.fns
442@@ -214,6 +214,10 @@ checkit() {
443 # We can just write everything to stdout/stderr, because the
444 # wrapper hides it unless there is a problem.
445
446+ if test x$TLS_ARGS = x--atimes; then
447+ ( cd "$2" && rsync_ls_lR . ) > "$tmpdir/ls-from"
448+ fi
449+
450 echo "Running: \"$1\""
451 eval "$1"
452 status=$?
453@@ -221,10 +225,13 @@ checkit() {
454 failed="YES";
455 fi
456
457+ if test x$TLS_ARGS != x--atimes; then
458+ ( cd "$2" && rsync_ls_lR . ) > "$tmpdir/ls-from"
459+ fi
460+
461 echo "-------------"
462 echo "check how the directory listings compare with diff:"
463 echo ""
464- ( cd "$2" && rsync_ls_lR . ) > "$tmpdir/ls-from"
465 ( cd "$3" && rsync_ls_lR . ) > "$tmpdir/ls-to"
466 diff $diffopt "$tmpdir/ls-from" "$tmpdir/ls-to" || failed=YES
467
468diff --git a/tls.c b/tls.c
469--- a/tls.c
470+++ b/tls.c
471@@ -118,6 +118,8 @@ static int stat_xattr(const char *fname, STRUCT_STAT *fst)
472
473 #endif
474
475+static int display_atimes = 0;
476+
477 static void failed(char const *what, char const *where)
478 {
479 fprintf(stderr, PROGRAM ": %s %s: %s\n",
480@@ -125,12 +127,37 @@ static void failed(char const *what, char const *where)
481 exit(1);
482 }
483
484+static void storetime(char *dest, size_t destsize, time_t t, int nsecs)
485+{
486+ if (t) {
487+ int len;
488+ struct tm *mt = gmtime(&t);
489+
490+ len = snprintf(dest, destsize,
491+ "%04d-%02d-%02d %02d:%02d:%02d",
492+ (int)mt->tm_year + 1900,
493+ (int)mt->tm_mon + 1,
494+ (int)mt->tm_mday,
495+ (int)mt->tm_hour,
496+ (int)mt->tm_min,
497+ (int)mt->tm_sec);
498+ if (nsecs >= 0) {
499+ snprintf(datebuf + len, sizeof datebuf - len, ".%09d", nsecs);
500+ }
501+ } else {
502+ int has_nsecs = nsecs >= 0 ? 1 : 0;
503+ int len = MIN(19 + 9*nsec_times, (int)sizeof datebuf - 1);
504+ memset(datebuf, ' ', len);
505+ datebuf[len] = '\0';
506+ }
507+}
508+
509 static void list_file(const char *fname)
510 {
511 STRUCT_STAT buf;
512 char permbuf[PERMSTRING_SIZE];
513- struct tm *mt;
514- char datebuf[50];
515+ char mtimebuf[50];
516+ char atimebuf[50];
517 char linkbuf[4096];
518
519 if (do_lstat(fname, &buf) < 0)
520@@ -168,30 +195,17 @@ static void list_file(const char *fname)
521 }
522
523 permstring(permbuf, buf.st_mode);
524-
525- if (buf.st_mtime) {
526- int len;
527- mt = gmtime(&buf.st_mtime);
528-
529- len = snprintf(datebuf, sizeof datebuf,
530- "%04d-%02d-%02d %02d:%02d:%02d",
531- (int)mt->tm_year + 1900,
532- (int)mt->tm_mon + 1,
533- (int)mt->tm_mday,
534- (int)mt->tm_hour,
535- (int)mt->tm_min,
536- (int)mt->tm_sec);
537 #ifdef ST_MTIME_NSEC
538- if (nsec_times) {
539- snprintf(datebuf + len, sizeof datebuf - len,
540- ".%09d", (int)buf.ST_MTIME_NSEC);
541- }
542+ if (nsec_times)
543+ nsecs = (int)buf.ST_MTIME_NSEC
544+ else
545 #endif
546- } else {
547- int len = MIN(19 + 9*nsec_times, (int)sizeof datebuf - 1);
548- memset(datebuf, ' ', len);
549- datebuf[len] = '\0';
550- }
551+ nsecs = -1;
552+ storetime(mtimebuf, sizeof mtimebuf, buf.st_mtime, nsecs);
553+ if (display_atimes)
554+ storetime(atimebuf, sizeof atimebuf, S_ISDIR(buf.st_mode) ? 0 : buf.st_atime, -1);
555+ else
556+ atimebuf[0] = '\0';
557
558 /* TODO: Perhaps escape special characters in fname? */
559
560@@ -202,13 +216,14 @@ static void list_file(const char *fname)
561 (long)minor(buf.st_rdev));
562 } else
563 printf("%15s", do_big_num(buf.st_size, 1, NULL));
564- printf(" %6ld.%-6ld %6ld %s %s%s\n",
565+ printf(" %6ld.%-6ld %6ld %s%s%s%s\n",
566 (long)buf.st_uid, (long)buf.st_gid, (long)buf.st_nlink,
567- datebuf, fname, linkbuf);
568+ mtimebuf, atimebuf, fname, linkbuf);
569 }
570
571 static struct poptOption long_options[] = {
572 /* longName, shortName, argInfo, argPtr, value, descrip, argDesc */
573+ {"atimes", 'U', POPT_ARG_NONE, &display_atimes, 0, 0, 0},
574 {"link-times", 'l', POPT_ARG_NONE, &link_times, 0, 0, 0 },
575 {"link-owner", 'L', POPT_ARG_NONE, &link_owner, 0, 0, 0 },
576 #ifdef SUPPORT_XATTRS
577@@ -227,6 +242,7 @@ static void tls_usage(int ret)
578 fprintf(F,"usage: " PROGRAM " [OPTIONS] FILE ...\n");
579 fprintf(F,"Trivial file listing program for portably checking rsync\n");
580 fprintf(F,"\nOptions:\n");
581+ fprintf(F," -U, --atimes display access (last-used) times\n");
582 fprintf(F," -l, --link-times display the time on a symlink\n");
583 fprintf(F," -L, --link-owner display the owner+group on a symlink\n");
584 #ifdef SUPPORT_XATTRS
585diff --git a/util.c b/util.c
586--- a/util.c
587+++ b/util.c
588@@ -123,7 +123,7 @@ NORETURN void overflow_exit(const char *str)
589 exit_cleanup(RERR_MALLOC);
590 }
591
592-int set_modtime(const char *fname, time_t modtime, uint32 mod_nsec, mode_t mode)
593+int set_times(const char *fname, time_t modtime, uint32 mod_nsec, time_t atime, mode_t mode)
594 {
595 #ifndef CAN_SET_SYMLINK_TIMES
596 if (S_ISLNK(mode))
597@@ -131,9 +131,13 @@ int set_modtime(const char *fname, time_t modtime, uint32 mod_nsec, mode_t mode)
598 #endif
599
600 if (DEBUG_GTE(TIME, 1)) {
601- rprintf(FINFO, "set modtime of %s to (%ld) %s",
602+ char mtimebuf[200];
603+
604+ strlcpy(mtimebuf, timestring(modtime), sizeof mtimebuf);
605+ rprintf(FINFO,
606+ "set modtime, atime of %s to (%ld) %s, (%ld) %s\n",
607 fname, (long)modtime,
608- asctime(localtime(&modtime)));
609+ mtimebuf, (long)atime, timestring(atime));
610 }
611
612 if (dry_run)
613@@ -142,8 +146,8 @@ int set_modtime(const char *fname, time_t modtime, uint32 mod_nsec, mode_t mode)
614 {
615 #ifdef HAVE_UTIMENSAT
616 struct timespec t[2];
617- t[0].tv_sec = 0;
618- t[0].tv_nsec = UTIME_NOW;
619+ t[0].tv_sec = atime;
620+ t[0].tv_nsec = 0;
621 t[1].tv_sec = modtime;
622 t[1].tv_nsec = mod_nsec;
623 if (utimensat(AT_FDCWD, fname, t, AT_SYMLINK_NOFOLLOW) < 0)
624@@ -151,7 +155,7 @@ int set_modtime(const char *fname, time_t modtime, uint32 mod_nsec, mode_t mode)
625 return 0;
626 #elif defined HAVE_UTIMES || defined HAVE_LUTIMES
627 struct timeval t[2];
628- t[0].tv_sec = time(NULL);
629+ t[0].tv_sec = atime;
630 t[0].tv_usec = 0;
631 t[1].tv_sec = modtime;
632 t[1].tv_usec = mod_nsec / 1000;
633@@ -164,12 +168,12 @@ int set_modtime(const char *fname, time_t modtime, uint32 mod_nsec, mode_t mode)
634 # endif
635 #elif defined HAVE_STRUCT_UTIMBUF
636 struct utimbuf tbuf;
637- tbuf.actime = time(NULL);
638+ tbuf.actime = atime;
639 tbuf.modtime = modtime;
640 return utime(fname,&tbuf);
641 #elif defined HAVE_UTIME
642 time_t t[2];
643- t[0] = time(NULL);
644+ t[0] = atime;
645 t[1] = modtime;
646 return utime(fname,t);
647 #else