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