The patches for 3.0.0pre10.
[rsync/rsync-patches.git] / atimes.diff
1 To 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
7 diff --git a/compat.c b/compat.c
8 --- a/compat.c
9 +++ 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;
19  #endif
20  
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;
24  
25  int receiver_symlink_times = 0; /* receiver can set the time on a symlink */
26  
27 @@ -134,6 +135,8 @@ void setup_protocol(int f_out,int f_in)
28                 uid_ndx = ++file_extra_cnt;
29         if (preserve_gid)
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;
35         if (preserve_xattrs)
36 diff --git a/flist.c b/flist.c
37 --- a/flist.c
38 +++ b/flist.c
39 @@ -53,6 +53,7 @@ extern int preserve_specials;
40  extern int uid_ndx;
41  extern int gid_ndx;
42  extern int eol_nulls;
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)
48  
49  static void send_file_entry(int f, struct file_struct *file, int ndx, int first_ndx)
50  {
51 -       static time_t modtime;
52 +       static time_t modtime, atime;
53         static mode_t mode;
54  #ifdef SUPPORT_HARD_LINKS
55         static int64 dev;
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;
58         else
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;
64 +               else
65 +                       atime = file_atime;
66 +       }
67  
68  #ifdef SUPPORT_HARD_LINKS
69         if (tmp_dev != 0) {
70 @@ -522,6 +530,8 @@ static void send_file_entry(int f, struct file_struct *file, int ndx, int first_
71         }
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)
78                         write_int(f, uid);
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,
81                                            int xflags, int f)
82  {
83 -       static int64 modtime;
84 +       static int64 modtime, atime;
85         static mode_t mode;
86  #ifdef SUPPORT_HARD_LINKS
87         static int64 dev;
88 @@ -741,6 +751,16 @@ static struct file_struct *recv_file_entry(struct file_list *flist,
89         }
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",
98 +                               lastname);
99 +               }
100 +#endif
101 +       }
102  
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,
106                 F_GROUP(file) = gid;
107                 file->flags |= gid_flags;
108         }
109 +       if (atimes_ndx)
110 +               f_atime_set(file, (time_t)atime);
111         if (unsort_ndx)
112                 F_NDX(file) = flist->used + flist->ndx_start;
113  
114 @@ -1203,6 +1225,8 @@ struct file_struct *make_file(const char *fname, struct file_list *flist,
115                 F_OWNER(file) = st.st_uid;
116         if (gid_ndx)
117                 F_GROUP(file) = st.st_gid;
118 +       if (atimes_ndx)
119 +               f_atime_set(file, st.st_atime);
120  
121         if (basename != thisname)
122                 file->dirname = lastdir;
123 diff --git a/generator.c b/generator.c
124 --- a/generator.c
125 +++ b/generator.c
126 @@ -21,6 +21,7 @@
127   */
128  
129  #include "rsync.h"
130 +#include "ifuncs.h"
131  
132  extern int verbose;
133  extern int dry_run;
134 @@ -613,6 +614,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)) {
143                         ;
144 @@ -967,6 +971,8 @@ static int try_dests_reg(struct file_struct *file, char *fname, int ndx,
145                 if (link_dest) {
146                         if (!hard_link_one(file, fname, cmpbuf, 1))
147                                 goto try_a_copy;
148 +                       if (atimes_ndx)
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 (!maybe_ATTRS_REPORT && (verbose > 1 || stdout_format_has_i > 1)) {
153 @@ -1153,6 +1159,7 @@ static int try_dests_non(struct file_struct *file, char *fname, int ndx,
154  static void list_file_entry(struct file_struct *f)
155  {
156         char permbuf[PERMSTRING_SIZE];
157 +       time_t atime = atimes_ndx ? f_atime(f) : 0;
158         double len;
159  
160         if (!F_IS_ACTIVE(f)) {
161 @@ -1167,14 +1174,16 @@ static void list_file_entry(struct file_struct *f)
162  
163  #ifdef SUPPORT_LINKS
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));
170         } else
171  #endif
172         {
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) : "",
177                         f_name(f, NULL));
178         }
179  }
180 @@ -1929,7 +1938,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
190 --- a/ifuncs.h
191 +++ b/ifuncs.h
192 @@ -57,6 +57,28 @@ from_wire_mode(int mode)
193         return mode;
194  }
195  
196 +static inline time_t
197 +f_atime(struct file_struct *fp)
198 +{
199 +#if SIZEOF_TIME_T > 4
200 +       time_t atime;
201 +       memcpy(&atime, &REQ_EXTRA(fp, atimes_ndx)->unum, SIZEOF_TIME_T);
202 +       return atime;
203 +#else
204 +       return REQ_EXTRA(fp, atimes_ndx)->unum;
205 +#endif
206 +}
207 +
208 +static inline void
209 +f_atime_set(struct file_struct *fp, time_t atime)
210 +{
211 +#if SIZEOF_TIME_T > 4
212 +       memcpy(&REQ_EXTRA(fp, atimes_ndx)->unum, &atime, SIZEOF_TIME_T);
213 +#else
214 +       REQ_EXTRA(fp, atimes_ndx)->unum = (uint32)atime;
215 +#endif
216 +}
217 +
218  static inline int
219  isDigit(const char *ptr)
220  {
221 diff --git a/log.c b/log.c
222 --- a/log.c
223 +++ b/log.c
224 @@ -644,7 +644,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';
233                         c[11] = '\0';
234 diff --git a/options.c b/options.c
235 --- a/options.c
236 +++ 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;
242  int update_only = 0;
243  int cvs_exclude = 0;
244  int dry_run = 0;
245 @@ -352,6 +353,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 @@ -487,6 +489,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 @@ -1747,6 +1752,8 @@ void server_options(char **args, int *argc_p)
264                 argstr[x++] = 'D';
265         if (preserve_times)
266                 argstr[x++] = 't';
267 +       if (preserve_atimes)
268 +               argstr[x++] = 'U';
269         if (preserve_perms)
270                 argstr[x++] = 'p';
271         else if (preserve_executability && am_sender)
272 diff --git a/rsync.c b/rsync.c
273 --- a/rsync.c
274 +++ b/rsync.c
275 @@ -345,6 +345,7 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
276         int updated = 0;
277         stat_x sx2;
278         int change_uid, change_gid;
279 +       time_t atime, mtime;
280         mode_t new_mode = file->mode;
281         int inherit;
282  
283 @@ -388,20 +389,39 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
284                 set_xattr(fname, file, fnamecmp, sxp);
285  #endif
286  
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;
297 +               updated = 1;
298 +       } else
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;
304 +                       updated = 1;
305 +               } else
306 +                       atime = sxp->st.st_atime;
307 +       } else
308 +               atime = sxp->st.st_atime;
309 +       if (updated) {
310 +               int ret = set_times(fname, mtime, atime, sxp->st.st_mode);
311                 if (ret < 0) {
312                         rsyserr(FERROR_XFER, errno, "failed to set times on %s",
313                                 full_fname(fname));
314                         goto cleanup;
315                 }
316 -               if (ret == 0) /* ret == 1 if symlink could not be set */
317 -                       updated = 1;
318 -               else if (receiver_symlink_times)
319 -                       file->flags |= FLAG_TIME_FAILED;
320 +               if (ret > 0) { /* ret == 1 if symlink could not be set */
321 +                       updated = 0;
322 +                       if (receiver_symlink_times)
323 +                               file->flags |= FLAG_TIME_FAILED;
324 +               }
325         }
326  
327         change_uid = am_root && uid_ndx && sxp->st.st_uid != (uid_t)F_OWNER(file);
328 @@ -531,7 +551,7 @@ int finish_transfer(const char *fname, const char *fnametmp,
329  
330         /* Change permissions before putting the file into place. */
331         set_file_attrs(fnametmp, file, NULL, fnamecmp,
332 -                      ok_to_set_time ? 0 : ATTRS_SKIP_MTIME);
333 +                      ok_to_set_time ? 0 : ATTRS_SKIP_MTIME | ATTRS_SKIP_ATIME);
334  
335         /* move tmp file over real file */
336         if (verbose > 2)
337 @@ -558,7 +578,7 @@ int finish_transfer(const char *fname, const char *fnametmp,
338  
339    do_set_file_attrs:
340         set_file_attrs(fnametmp, file, NULL, fnamecmp,
341 -                      ok_to_set_time ? 0 : ATTRS_SKIP_MTIME);
342 +                      ok_to_set_time ? 0 : ATTRS_SKIP_MTIME | ATTRS_SKIP_ATIME);
343  
344         if (temp_copy_name) {
345                 if (do_rename(fnametmp, fname) < 0) {
346 diff --git a/rsync.h b/rsync.h
347 --- a/rsync.h
348 +++ b/rsync.h
349 @@ -60,6 +60,7 @@
350  #define XMIT_RDEV_MINOR_8_pre30 (1<<11)        /* protocols 28 - 29  */
351  #define XMIT_GROUP_NAME_FOLLOWS (1<<11) /* protocols 30 - now */
352  #define XMIT_HLINK_FIRST (1<<12)       /* protocols 30 - now (HLINKED files only) */
353 +#define XMIT_SAME_ATIME (1<<13)                /* protocols ?? - now */
354  
355  /* These flags are used in the live flist data. */
356  
357 @@ -149,6 +150,7 @@
358  
359  #define ATTRS_REPORT           (1<<0)
360  #define ATTRS_SKIP_MTIME       (1<<1)
361 +#define ATTRS_SKIP_ATIME       (1<<2)
362  
363  #define FULL_FLUSH     1
364  #define NORMAL_FLUSH   0
365 @@ -620,12 +622,14 @@ extern int file_extra_cnt;
366  extern int inc_recurse;
367  extern int uid_ndx;
368  extern int gid_ndx;
369 +extern int atimes_ndx;
370  extern int acls_ndx;
371  extern int xattrs_ndx;
372  
373  #define FILE_STRUCT_LEN (offsetof(struct file_struct, basename))
374  #define EXTRA_LEN (sizeof (union file_extras))
375  #define PTR_EXTRA_CNT ((sizeof (char *) + EXTRA_LEN - 1) / EXTRA_LEN)
376 +#define TIME_EXTRA_CNT ((SIZEOF_TIME_T + EXTRA_LEN - 1) / EXTRA_LEN)
377  #define DEV_EXTRA_CNT 2
378  #define DIRNODE_EXTRA_CNT 3
379  #define SUM_EXTRA_CNT ((MAX_DIGEST_LEN + EXTRA_LEN - 1) / EXTRA_LEN)
380 diff --git a/rsync.yo b/rsync.yo
381 --- a/rsync.yo
382 +++ b/rsync.yo
383 @@ -349,6 +349,7 @@ to the detailed description below for a complete description.  verb(
384   -D                          same as --devices --specials
385   -t, --times                 preserve modification times
386   -O, --omit-dir-times        omit directories from --times
387 + -U, --atimes                preserve access (use) times
388       --super                 receiver attempts super-user activities
389       --fake-super            store/recover privileged attrs using xattrs
390   -S, --sparse                handle sparse files efficiently
391 @@ -987,6 +988,12 @@ it is preserving modification times (see bf(--times)).  If NFS is sharing
392  the directories on the receiving side, it is a good idea to use bf(-O).
393  This option is inferred if you use bf(--backup) without bf(--backup-dir).
394  
395 +dit(bf(-U, --atimes)) This tells rsync to set the access (use) times of the
396 +destination files to the same value as the source files.  Note that the
397 +reading of the source file may update the atime of the source files, so
398 +repeated rsync runs with --atimes may be needed if you want to force the
399 +access-time values to be 100% identical on the two systems.
400 +
401  dit(bf(--super)) This tells the receiving side to attempt super-user
402  activities even if the receiving rsync wasn't run by the super-user.  These
403  activities include: preserving users via the bf(--owner) option, preserving
404 @@ -1689,8 +1696,10 @@ quote(itemization(
405    sender's value (requires bf(--owner) and super-user privileges).
406    it() A bf(g) means the group is different and is being updated to the
407    sender's value (requires bf(--group) and the authority to set the group).
408 -  it() The bf(u) slot is reserved for reporting update (access) time changes
409 -  (a feature that is not yet released).
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) slot is reserved for reporting extended attribute changes
416    (a feature that is not yet released).
417 diff --git a/testsuite/atimes.test b/testsuite/atimes.test
418 new 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
439 diff --git a/testsuite/rsync.fns b/testsuite/rsync.fns
440 --- a/testsuite/rsync.fns
441 +++ b/testsuite/rsync.fns
442 @@ -187,6 +187,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 @@ -194,10 +198,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  
468 diff --git a/tls.c b/tls.c
469 --- a/tls.c
470 +++ b/tls.c
471 @@ -105,6 +105,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 @@ -112,12 +114,29 @@ static void failed(char const *what, char const *where)
481         exit(1);
482  }
483  
484 +static void storetime(char *dest, time_t t, size_t destsize)
485 +{
486 +       if (t) {
487 +               struct tm *mt = gmtime(&t);
488 +
489 +               snprintf(dest, destsize,
490 +                       "%04d-%02d-%02d %02d:%02d:%02d ",
491 +                       (int)mt->tm_year + 1900,
492 +                       (int)mt->tm_mon + 1,
493 +                       (int)mt->tm_mday,
494 +                       (int)mt->tm_hour,
495 +                       (int)mt->tm_min,
496 +                       (int)mt->tm_sec);
497 +       } else
498 +               strlcpy(dest, "                    ", destsize);
499 +}
500 +
501  static void list_file(const char *fname)
502  {
503         STRUCT_STAT buf;
504         char permbuf[PERMSTRING_SIZE];
505 -       struct tm *mt;
506 -       char datebuf[50];
507 +       char mtimebuf[50];
508 +       char atimebuf[50];
509         char linkbuf[4096];
510  
511         if (do_lstat(fname, &buf) < 0)
512 @@ -154,19 +173,11 @@ static void list_file(const char *fname)
513  
514         permstring(permbuf, buf.st_mode);
515  
516 -       if (buf.st_mtime) {
517 -               mt = gmtime(&buf.st_mtime);
518 -
519 -               snprintf(datebuf, sizeof datebuf,
520 -                       "%04d-%02d-%02d %02d:%02d:%02d",
521 -                       (int)mt->tm_year + 1900,
522 -                       (int)mt->tm_mon + 1,
523 -                       (int)mt->tm_mday,
524 -                       (int)mt->tm_hour,
525 -                       (int)mt->tm_min,
526 -                       (int)mt->tm_sec);
527 -       } else
528 -               strlcpy(datebuf, "                   ", sizeof datebuf);
529 +       storetime(mtimebuf, buf.st_mtime, sizeof mtimebuf);
530 +       if (display_atimes)
531 +               storetime(atimebuf, S_ISDIR(buf.st_mode) ? 0 : buf.st_atime, sizeof atimebuf);
532 +       else
533 +               atimebuf[0] = '\0';
534  
535         /* TODO: Perhaps escape special characters in fname? */
536  
537 @@ -177,13 +188,14 @@ static void list_file(const char *fname)
538                     (long)minor(buf.st_rdev));
539         } else /* NB: use double for size since it might not fit in a long. */
540                 printf("%12.0f", (double)buf.st_size);
541 -       printf(" %6ld.%-6ld %6ld %s %s%s\n",
542 +       printf(" %6ld.%-6ld %6ld %s%s%s%s\n",
543                (long)buf.st_uid, (long)buf.st_gid, (long)buf.st_nlink,
544 -              datebuf, fname, linkbuf);
545 +              mtimebuf, atimebuf, fname, linkbuf);
546  }
547  
548  static struct poptOption long_options[] = {
549    /* longName, shortName, argInfo, argPtr, value, descrip, argDesc */
550 +  {"atimes",          'U', POPT_ARG_NONE,   &display_atimes, 0, 0, 0},
551  #ifdef SUPPORT_XATTRS
552    {"fake-super",      'f', POPT_ARG_VAL,    &am_root, -1, 0, 0 },
553  #endif
554 @@ -197,6 +209,7 @@ static void tls_usage(int ret)
555    fprintf(F,"usage: " PROGRAM " [OPTIONS] FILE ...\n");
556    fprintf(F,"Trivial file listing program for portably checking rsync\n");
557    fprintf(F,"\nOptions:\n");
558 +  fprintf(F," -U, --atimes                display access (last-used) times\n");
559  #ifdef SUPPORT_XATTRS
560    fprintf(F," -f, --fake-super            display attributes including fake-super xattrs\n");
561  #endif
562 diff --git a/util.c b/util.c
563 --- a/util.c
564 +++ b/util.c
565 @@ -122,7 +122,7 @@ NORETURN void overflow_exit(const char *str)
566         exit_cleanup(RERR_MALLOC);
567  }
568  
569 -int set_modtime(const char *fname, time_t modtime, mode_t mode)
570 +int set_times(const char *fname, time_t modtime, time_t atime, mode_t mode)
571  {
572  #if !defined HAVE_LUTIMES || !defined HAVE_UTIMES
573         if (S_ISLNK(mode))
574 @@ -130,9 +130,13 @@ int set_modtime(const char *fname, time_t modtime, mode_t mode)
575  #endif
576  
577         if (verbose > 2) {
578 -               rprintf(FINFO, "set modtime of %s to (%ld) %s",
579 +               char mtimebuf[200];
580 +
581 +               strlcpy(mtimebuf, timestring(modtime), sizeof mtimebuf);
582 +               rprintf(FINFO,
583 +                       "set modtime, atime of %s to (%ld) %s, (%ld) %s\n",
584                         fname, (long)modtime,
585 -                       asctime(localtime(&modtime)));
586 +                       mtimebuf, (long)atime, timestring(atime));
587         }
588  
589         if (dry_run)
590 @@ -141,7 +145,7 @@ int set_modtime(const char *fname, time_t modtime, mode_t mode)
591         {
592  #ifdef HAVE_UTIMES
593                 struct timeval t[2];
594 -               t[0].tv_sec = time(NULL);
595 +               t[0].tv_sec = atime;
596                 t[0].tv_usec = 0;
597                 t[1].tv_sec = modtime;
598                 t[1].tv_usec = 0;
599 @@ -155,12 +159,12 @@ int set_modtime(const char *fname, time_t modtime, mode_t mode)
600                 return utimes(fname, t);
601  #elif defined HAVE_STRUCT_UTIMBUF
602                 struct utimbuf tbuf;
603 -               tbuf.actime = time(NULL);
604 +               tbuf.actime = atime;
605                 tbuf.modtime = modtime;
606                 return utime(fname,&tbuf);
607  #elif defined HAVE_UTIME
608                 time_t t[2];
609 -               t[0] = time(NULL);
610 +               t[0] = atime;
611                 t[1] = modtime;
612                 return utime(fname,t);
613  #else