The patches for 3.0.0pre7.
[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     ./prepare-source
5     ./configure                      (optional if already run)
6     make
7
8 TODO:  need to fix this to handle 64-bit time_t values!
9
10 diff --git a/compat.c b/compat.c
11 --- a/compat.c
12 +++ b/compat.c
13 @@ -44,6 +44,7 @@ extern int protocol_version;
14  extern int protect_args;
15  extern int preserve_uid;
16  extern int preserve_gid;
17 +extern int preserve_atimes;
18  extern int preserve_acls;
19  extern int preserve_xattrs;
20  extern int need_messages_from_generator;
21 @@ -60,7 +61,7 @@ extern iconv_t ic_send, ic_recv;
22  #endif
23  
24  /* These index values are for the file-list's extra-attribute array. */
25 -int uid_ndx, gid_ndx, acls_ndx, xattrs_ndx, unsort_ndx;
26 +int uid_ndx, gid_ndx, atimes_ndx, acls_ndx, xattrs_ndx, unsort_ndx;
27  
28  #ifdef ICONV_OPTION
29  int filesfrom_convert = 0;
30 @@ -124,6 +125,8 @@ void setup_protocol(int f_out,int f_in)
31                 uid_ndx = ++file_extra_cnt;
32         if (preserve_gid)
33                 gid_ndx = ++file_extra_cnt;
34 +       if (preserve_atimes)
35 +               atimes_ndx = ++file_extra_cnt;
36         if (preserve_acls && !am_sender)
37                 acls_ndx = ++file_extra_cnt;
38         if (preserve_xattrs)
39 diff --git a/flist.c b/flist.c
40 --- a/flist.c
41 +++ b/flist.c
42 @@ -53,6 +53,7 @@ extern int preserve_specials;
43  extern int uid_ndx;
44  extern int gid_ndx;
45  extern int eol_nulls;
46 +extern int atimes_ndx;
47  extern int relative_paths;
48  extern int implied_dirs;
49  extern int file_extra_cnt;
50 @@ -343,6 +344,7 @@ int push_pathname(const char *dir, int len)
51  static void send_file_entry(int f, struct file_struct *file, int ndx, int first_ndx)
52  {
53         static time_t modtime;
54 +       static time_t atime;
55         static mode_t mode;
56  #ifdef SUPPORT_HARD_LINKS
57         static int64 dev;
58 @@ -450,6 +452,13 @@ static void send_file_entry(int f, struct file_struct *file, int ndx, int first_
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 @@ -522,6 +531,8 @@ static void send_file_entry(int f, struct file_struct *file, int ndx, int first_
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 (uid_ndx && !(xflags & XMIT_SAME_UID)) {
79                 if (protocol_version < 30)
80                         write_int(f, uid);
81 @@ -608,7 +619,7 @@ static void send_file_entry(int f, struct file_struct *file, int ndx, int first_
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 @@ -741,6 +752,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 ((atime > INT_MAX || atime < INT_MIN) && !am_generator) {
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))
106                 mode = tweak_mode(mode, chmod_modes);
107 @@ -871,6 +892,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(file) = (uint32)atime;
113         if (unsort_ndx)
114                 F_NDX(file) = flist->used + flist->ndx_start;
115  
116 @@ -1197,6 +1220,8 @@ struct file_struct *make_file(const char *fname, struct file_list *flist,
117                 F_OWNER(file) = st.st_uid;
118         if (gid_ndx)
119                 F_GROUP(file) = st.st_gid;
120 +       if (atimes_ndx)
121 +               F_ATIME(file) = (uint32)st.st_atime;
122  
123         if (basename != thisname)
124                 file->dirname = lastdir;
125 diff --git a/generator.c b/generator.c
126 --- a/generator.c
127 +++ b/generator.c
128 @@ -43,6 +43,7 @@ extern int preserve_specials;
129  extern int preserve_hard_links;
130  extern int preserve_perms;
131  extern int preserve_times;
132 +extern int preserve_atimes;
133  extern int uid_ndx;
134  extern int gid_ndx;
135  extern int delete_mode;
136 @@ -568,6 +569,9 @@ void itemize(const char *fnamecmp, struct file_struct *file, int ndx, int statre
137                   && (!(iflags & ITEM_XNAME_FOLLOWS) || *xname))
138                  || (keep_time && cmp_time(file->modtime, sxp->st.st_mtime) != 0))
139                         iflags |= ITEM_REPORT_TIME;
140 +               if (preserve_atimes && !S_ISDIR(file->mode) && !S_ISLNK(file->mode)
141 +                && cmp_time(F_ATIME(file), sxp->st.st_atime) != 0)
142 +                       iflags |= ITEM_REPORT_ATIME;
143  #if !defined HAVE_LCHMOD && !defined HAVE_SETATTRLIST
144                 if (S_ISLNK(file->mode)) {
145                         ;
146 @@ -923,6 +927,8 @@ static int try_dests_reg(struct file_struct *file, char *fname, int ndx,
147                 if (link_dest) {
148                         if (!hard_link_one(file, fname, cmpbuf, 1))
149                                 goto try_a_copy;
150 +                       if (preserve_atimes)
151 +                               set_file_attrs(fname, file, sxp, NULL, 0);
152                         if (preserve_hard_links && F_IS_HLINKED(file))
153                                 finish_hard_link(file, fname, ndx, &sxp->st, itemizing, code, j);
154                         if (itemizing && (verbose > 1 || stdout_format_has_i > 1)) {
155 @@ -1113,6 +1119,7 @@ static int try_dests_non(struct file_struct *file, char *fname, int ndx,
156  static void list_file_entry(struct file_struct *f)
157  {
158         char permbuf[PERMSTRING_SIZE];
159 +       time_t atime = atimes_ndx ? F_ATIME(f) : 0;
160         double len;
161  
162         if (!F_IS_ACTIVE(f)) {
163 @@ -1127,14 +1134,16 @@ static void list_file_entry(struct file_struct *f)
164  
165  #ifdef SUPPORT_LINKS
166         if (preserve_links && S_ISLNK(f->mode)) {
167 -               rprintf(FINFO, "%s %11.0f %s %s -> %s\n",
168 +               rprintf(FINFO, "%s %11.0f %s %s %s -> %s\n",
169                         permbuf, len, timestring(f->modtime),
170 +                       atimes_ndx ? timestring(atime) : "",
171                         f_name(f, NULL), F_SYMLINK(f));
172         } else
173  #endif
174         {
175 -               rprintf(FINFO, "%s %11.0f %s %s\n",
176 +               rprintf(FINFO, "%s %11.0f %s %s %s\n",
177                         permbuf, len, timestring(f->modtime),
178 +                       atimes_ndx ? timestring(atime) : "",
179                         f_name(f, NULL));
180         }
181  }
182 @@ -1884,7 +1893,7 @@ static void touch_up_dirs(struct file_list *flist, int ndx)
183                 if (!(file->mode & S_IWUSR))
184                         do_chmod(fname, file->mode);
185                 if (need_retouch_dir_times)
186 -                       set_modtime(fname, file->modtime, file->mode);
187 +                       set_times(fname, file->modtime, file->modtime, file->mode);
188                 if (allowed_lull && !(counter % lull_mod))
189                         maybe_send_keepalive();
190                 else if (!(counter & 0xFF))
191 diff --git a/log.c b/log.c
192 --- a/log.c
193 +++ b/log.c
194 @@ -642,7 +642,8 @@ static void log_formatted(enum logcode code, const char *format, const char *op,
195                         c[5] = !(iflags & ITEM_REPORT_PERMS) ? '.' : 'p';
196                         c[6] = !(iflags & ITEM_REPORT_OWNER) ? '.' : 'o';
197                         c[7] = !(iflags & ITEM_REPORT_GROUP) ? '.' : 'g';
198 -                       c[8] = !(iflags & ITEM_REPORT_ATIME) ? '.' : 'u';
199 +                       c[8] = !(iflags & ITEM_REPORT_ATIME) ? '.'
200 +                            : S_ISLNK(file->mode) ? 'U' : 'u';
201                         c[9] = !(iflags & ITEM_REPORT_ACL) ? '.' : 'a';
202                         c[10] = !(iflags & ITEM_REPORT_XATTR) ? '.' : 'x';
203                         c[11] = '\0';
204 diff --git a/options.c b/options.c
205 --- a/options.c
206 +++ b/options.c
207 @@ -58,6 +58,7 @@ int preserve_specials = 0;
208  int preserve_uid = 0;
209  int preserve_gid = 0;
210  int preserve_times = 0;
211 +int preserve_atimes = 0;
212  int update_only = 0;
213  int cvs_exclude = 0;
214  int dry_run = 0;
215 @@ -348,6 +349,7 @@ void usage(enum logcode F)
216    rprintf(F," -D                          same as --devices --specials\n");
217    rprintf(F," -t, --times                 preserve modification times\n");
218    rprintf(F," -O, --omit-dir-times        omit directories from --times\n");
219 +  rprintf(F," -U, --atimes                preserve access (last-used) times\n");
220    rprintf(F,"     --super                 receiver attempts super-user activities\n");
221  #ifdef SUPPORT_XATTRS
222    rprintf(F,"     --fake-super            store/recover privileged attrs using xattrs\n");
223 @@ -483,6 +485,9 @@ static struct poptOption long_options[] = {
224    {"times",           't', POPT_ARG_VAL,    &preserve_times, 2, 0, 0 },
225    {"no-times",         0,  POPT_ARG_VAL,    &preserve_times, 0, 0, 0 },
226    {"no-t",             0,  POPT_ARG_VAL,    &preserve_times, 0, 0, 0 },
227 +  {"atimes",          'U', POPT_ARG_VAL,    &preserve_atimes, 1, 0, 0 },
228 +  {"no-atimes",        0,  POPT_ARG_VAL,    &preserve_atimes, 0, 0, 0 },
229 +  {"no-U",             0,  POPT_ARG_VAL,    &preserve_atimes, 0, 0, 0 },
230    {"omit-dir-times",  'O', POPT_ARG_VAL,    &omit_dir_times, 1, 0, 0 },
231    {"no-omit-dir-times",0,  POPT_ARG_VAL,    &omit_dir_times, 0, 0, 0 },
232    {"no-O",             0,  POPT_ARG_VAL,    &omit_dir_times, 0, 0, 0 },
233 @@ -1724,6 +1729,8 @@ void server_options(char **args, int *argc_p)
234                 argstr[x++] = 'D';
235         if (preserve_times)
236                 argstr[x++] = 't';
237 +       if (preserve_atimes)
238 +               argstr[x++] = 'U';
239         if (preserve_perms)
240                 argstr[x++] = 'p';
241         else if (preserve_executability && am_sender)
242 diff --git a/rsync.c b/rsync.c
243 --- a/rsync.c
244 +++ b/rsync.c
245 @@ -33,6 +33,7 @@ extern int preserve_acls;
246  extern int preserve_xattrs;
247  extern int preserve_perms;
248  extern int preserve_executability;
249 +extern int preserve_atimes;
250  extern int preserve_times;
251  extern int am_root;
252  extern int am_server;
253 @@ -344,6 +345,7 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
254         int updated = 0;
255         stat_x sx2;
256         int change_uid, change_gid;
257 +       time_t atime, mtime;
258         mode_t new_mode = file->mode;
259         int inherit;
260  
261 @@ -387,18 +389,36 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
262                 set_xattr(fname, file, fnamecmp, sxp);
263  #endif
264  
265 +       /* This code must be the first update in the function due to
266 +        * how it uses the "updated" variable. */
267         if (!preserve_times || (S_ISDIR(sxp->st.st_mode) && preserve_times == 1))
268                 flags |= ATTRS_SKIP_MTIME;
269 +       if (!preserve_atimes || S_ISDIR(sxp->st.st_mode))
270 +               flags |= ATTRS_SKIP_ATIME;
271         if (!(flags & ATTRS_SKIP_MTIME)
272             && cmp_time(sxp->st.st_mtime, file->modtime) != 0) {
273 -               int ret = set_modtime(fname, file->modtime, sxp->st.st_mode);
274 +               mtime = file->modtime;
275 +               updated = 1;
276 +       } else
277 +               mtime = sxp->st.st_mtime;
278 +       if (!(flags & ATTRS_SKIP_ATIME)) {
279 +               time_t file_atime = F_ATIME(file);
280 +               if (cmp_time(sxp->st.st_atime, file_atime) != 0) {
281 +                       atime = file_atime;
282 +                       updated = 1;
283 +               } else
284 +                       atime = sxp->st.st_atime;
285 +       } else
286 +               atime = sxp->st.st_atime;
287 +       if (updated) {
288 +               int ret = set_times(fname, mtime, atime, sxp->st.st_mode);
289                 if (ret < 0) {
290                         rsyserr(FERROR_XFER, errno, "failed to set times on %s",
291                                 full_fname(fname));
292                         goto cleanup;
293                 }
294 -               if (ret == 0) /* ret == 1 if symlink could not be set */
295 -                       updated = 1;
296 +               if (ret > 0) /* ret == 1 if symlink could not be set */
297 +                       updated = 0;
298         }
299  
300         change_uid = am_root && uid_ndx && sxp->st.st_uid != (uid_t)F_OWNER(file);
301 diff --git a/rsync.h b/rsync.h
302 --- a/rsync.h
303 +++ b/rsync.h
304 @@ -60,6 +60,7 @@
305  #define XMIT_RDEV_MINOR_8_pre30 (1<<11)        /* protocols 28 - 29  */
306  #define XMIT_GROUP_NAME_FOLLOWS (1<<11) /* protocols 30 - now */
307  #define XMIT_HLINK_FIRST (1<<12)       /* protocols 30 - now (HLINKED files only) */
308 +#define XMIT_SAME_ATIME (1<<13)                /* protocols ?? - now */
309  
310  /* These flags are used in the live flist data. */
311  
312 @@ -148,6 +149,7 @@
313  
314  #define ATTRS_REPORT           (1<<0)
315  #define ATTRS_SKIP_MTIME       (1<<1)
316 +#define ATTRS_SKIP_ATIME       (1<<2)
317  
318  #define FULL_FLUSH     1
319  #define NORMAL_FLUSH   0
320 @@ -618,6 +620,7 @@ extern int file_extra_cnt;
321  extern int inc_recurse;
322  extern int uid_ndx;
323  extern int gid_ndx;
324 +extern int atimes_ndx;
325  extern int acls_ndx;
326  extern int xattrs_ndx;
327  
328 @@ -655,6 +658,7 @@ extern int xattrs_ndx;
329  /* When the associated option is on, all entries will have these present: */
330  #define F_OWNER(f) REQ_EXTRA(f, uid_ndx)->unum
331  #define F_GROUP(f) REQ_EXTRA(f, gid_ndx)->unum
332 +#define F_ATIME(f) REQ_EXTRA(f, atimes_ndx)->unum
333  #define F_ACL(f) REQ_EXTRA(f, acls_ndx)->num
334  #define F_XATTR(f) REQ_EXTRA(f, xattrs_ndx)->num
335  #define F_NDX(f) REQ_EXTRA(f, unsort_ndx)->num
336 diff --git a/rsync.yo b/rsync.yo
337 --- a/rsync.yo
338 +++ b/rsync.yo
339 @@ -349,6 +349,7 @@ to the detailed description below for a complete description.  verb(
340   -D                          same as --devices --specials
341   -t, --times                 preserve modification times
342   -O, --omit-dir-times        omit directories from --times
343 + -U, --atimes                preserve access (use) times
344       --super                 receiver attempts super-user activities
345       --fake-super            store/recover privileged attrs using xattrs
346   -S, --sparse                handle sparse files efficiently
347 @@ -974,6 +975,12 @@ it is preserving modification times (see bf(--times)).  If NFS is sharing
348  the directories on the receiving side, it is a good idea to use bf(-O).
349  This option is inferred if you use bf(--backup) without bf(--backup-dir).
350  
351 +dit(bf(-U, --atimes)) This tells rsync to set the access (use) times of the
352 +destination files to the same value as the source files.  Note that the
353 +reading of the source file may update the atime of the source files, so
354 +repeated rsync runs with --atimes may be needed if you want to force the
355 +access-time values to be 100% identical on the two systems.
356 +
357  dit(bf(--super)) This tells the receiving side to attempt super-user
358  activities even if the receiving rsync wasn't run by the super-user.  These
359  activities include: preserving users via the bf(--owner) option, preserving
360 @@ -1671,8 +1678,10 @@ quote(itemization(
361    sender's value (requires bf(--owner) and super-user privileges).
362    it() A bf(g) means the group is different and is being updated to the
363    sender's value (requires bf(--group) and the authority to set the group).
364 -  it() The bf(u) slot is reserved for reporting update (access) time changes
365 -  (a feature that is not yet released).
366 +  it() A bf(u) means the access (use) time is different and is being updated to
367 +  the sender's value (requires bf(--atimes)).  An alternate value of bf(U)
368 +  means that the access time will be set to the transfer time, which happens
369 +  when a symlink or directory is updated.
370    it() The bf(a) means that the ACL information changed.
371    it() The bf(x) slot is reserved for reporting extended attribute changes
372    (a feature that is not yet released).
373 diff --git a/sender.c b/sender.c
374 --- a/sender.c
375 +++ b/sender.c
376 @@ -43,6 +43,7 @@ extern int do_progress;
377  extern int inplace;
378  extern int batch_fd;
379  extern int write_batch;
380 +extern unsigned int file_struct_len;
381  extern struct stats stats;
382  extern struct file_list *cur_flist, *first_flist, *dir_flist;
383  
384 diff --git a/testsuite/atimes.test b/testsuite/atimes.test
385 new file mode 100644
386 --- /dev/null
387 +++ b/testsuite/atimes.test
388 @@ -0,0 +1,19 @@
389 +#! /bin/sh
390 +
391 +# Test rsync copying atimes
392 +
393 +. "$suitedir/rsync.fns"
394 +
395 +set -x
396 +
397 +mkdir "$fromdir"
398 +
399 +touch "$fromdir/foo"
400 +touch -a -t 200102031717.42 "$fromdir/foo"
401 +
402 +TLS_ARGS=--atime
403 +
404 +checkit "$RSYNC -rtUgvvv \"$fromdir/\" \"$todir/\"" "$fromdir" "$todir"
405 +
406 +# The script would have aborted on error, so getting here means we've won.
407 +exit 0
408 diff --git a/testsuite/rsync.fns b/testsuite/rsync.fns
409 --- a/testsuite/rsync.fns
410 +++ b/testsuite/rsync.fns
411 @@ -187,6 +187,10 @@ checkit() {
412      # We can just write everything to stdout/stderr, because the
413      # wrapper hides it unless there is a problem.
414  
415 +    if test x$TLS_ARGS = x--atime; then
416 +       ( cd "$2" && rsync_ls_lR . ) > "$tmpdir/ls-from"
417 +    fi
418 +
419      echo "Running: \"$1\""  
420      eval "$1" 
421      status=$?
422 @@ -194,10 +198,13 @@ checkit() {
423         failed="YES";
424      fi
425  
426 +    if test x$TLS_ARGS != x--atime; then
427 +       ( cd "$2" && rsync_ls_lR . ) > "$tmpdir/ls-from"
428 +    fi
429 +
430      echo "-------------"
431      echo "check how the directory listings compare with diff:"
432      echo ""
433 -    ( cd "$2" && rsync_ls_lR . ) > "$tmpdir/ls-from"
434      ( cd "$3" && rsync_ls_lR . ) > "$tmpdir/ls-to"
435      diff $diffopt "$tmpdir/ls-from" "$tmpdir/ls-to" || failed=YES
436  
437 diff --git a/tls.c b/tls.c
438 --- a/tls.c
439 +++ b/tls.c
440 @@ -104,6 +104,8 @@ static int stat_xattr(const char *fname, STRUCT_STAT *fst)
441  
442  #endif
443  
444 +static int display_atime = 0;
445
446  static void failed(char const *what, char const *where)
447  {
448         fprintf(stderr, PROGRAM ": %s %s: %s\n",
449 @@ -111,12 +113,29 @@ static void failed(char const *what, char const *where)
450         exit(1);
451  }
452  
453 +static void storetime(char *dest, time_t t, size_t destsize)
454 +{
455 +       if (t) {
456 +               struct tm *mt = gmtime(&t);
457 +
458 +               snprintf(dest, destsize,
459 +                       "%04d-%02d-%02d %02d:%02d:%02d ",
460 +                       (int)mt->tm_year + 1900,
461 +                       (int)mt->tm_mon + 1,
462 +                       (int)mt->tm_mday,
463 +                       (int)mt->tm_hour,
464 +                       (int)mt->tm_min,
465 +                       (int)mt->tm_sec);
466 +       } else
467 +               strlcpy(dest, "                    ", destsize);
468 +}
469 +
470  static void list_file(const char *fname)
471  {
472         STRUCT_STAT buf;
473         char permbuf[PERMSTRING_SIZE];
474 -       struct tm *mt;
475 -       char datebuf[50];
476 +       char mtimebuf[50];
477 +       char atimebuf[50];
478         char linkbuf[4096];
479  
480         if (do_lstat(fname, &buf) < 0)
481 @@ -153,19 +172,8 @@ static void list_file(const char *fname)
482  
483         permstring(permbuf, buf.st_mode);
484  
485 -       if (buf.st_mtime) {
486 -               mt = gmtime(&buf.st_mtime);
487 -
488 -               snprintf(datebuf, sizeof datebuf,
489 -                       "%04d-%02d-%02d %02d:%02d:%02d",
490 -                       (int)mt->tm_year + 1900,
491 -                       (int)mt->tm_mon + 1,
492 -                       (int)mt->tm_mday,
493 -                       (int)mt->tm_hour,
494 -                       (int)mt->tm_min,
495 -                       (int)mt->tm_sec);
496 -       } else
497 -               strlcpy(datebuf, "                   ", sizeof datebuf);
498 +       storetime(mtimebuf, buf.st_mtime, sizeof mtimebuf);
499 +       storetime(atimebuf, buf.st_atime, sizeof atimebuf);
500  
501         /* TODO: Perhaps escape special characters in fname? */
502  
503 @@ -176,13 +184,15 @@ static void list_file(const char *fname)
504                     (long)minor(buf.st_rdev));
505         } else /* NB: use double for size since it might not fit in a long. */
506                 printf("%12.0f", (double)buf.st_size);
507 -       printf(" %6ld.%-6ld %6ld %s %s%s\n",
508 +       printf(" %6ld.%-6ld %6ld %s%s%s%s\n",
509                (long)buf.st_uid, (long)buf.st_gid, (long)buf.st_nlink,
510 -              datebuf, fname, linkbuf);
511 +              mtimebuf, display_atime && !S_ISDIR(buf.st_mode) ? atimebuf : "",
512 +              fname, linkbuf);
513  }
514  
515  static struct poptOption long_options[] = {
516    /* longName, shortName, argInfo, argPtr, value, descrip, argDesc */
517 +  {"atime",           'u', POPT_ARG_NONE,   &display_atime, 0, 0, 0},
518  #ifdef SUPPORT_XATTRS
519    {"fake-super",      'f', POPT_ARG_VAL,    &am_root, -1, 0, 0 },
520  #endif
521 @@ -196,6 +206,7 @@ static void tls_usage(int ret)
522    fprintf(F,"usage: " PROGRAM " [OPTIONS] FILE ...\n");
523    fprintf(F,"Trivial file listing program for portably checking rsync\n");
524    fprintf(F,"\nOptions:\n");
525 +  rprintf(F," -U, --atimes                display access (last-used) times\n");
526  #ifdef SUPPORT_XATTRS
527    fprintf(F," -f, --fake-super            display attributes including fake-super xattrs\n");
528  #endif
529 diff --git a/util.c b/util.c
530 --- a/util.c
531 +++ b/util.c
532 @@ -122,7 +122,7 @@ NORETURN void overflow_exit(const char *str)
533         exit_cleanup(RERR_MALLOC);
534  }
535  
536 -int set_modtime(const char *fname, time_t modtime, mode_t mode)
537 +int set_times(const char *fname, time_t modtime, time_t atime, mode_t mode)
538  {
539  #if !defined HAVE_LUTIMES || !defined HAVE_UTIMES
540         if (S_ISLNK(mode))
541 @@ -130,9 +130,13 @@ int set_modtime(const char *fname, time_t modtime, mode_t mode)
542  #endif
543  
544         if (verbose > 2) {
545 -               rprintf(FINFO, "set modtime of %s to (%ld) %s",
546 +               char mtimebuf[200];
547 +
548 +               strlcpy(mtimebuf, timestring(modtime), sizeof mtimebuf);
549 +               rprintf(FINFO,
550 +                       "set modtime, atime of %s to (%ld) %s, (%ld) %s\n",
551                         fname, (long)modtime,
552 -                       asctime(localtime(&modtime)));
553 +                       mtimebuf, (long)atime, timestring(atime));
554         }
555  
556         if (dry_run)
557 @@ -141,7 +145,7 @@ int set_modtime(const char *fname, time_t modtime, mode_t mode)
558         {
559  #ifdef HAVE_UTIMES
560                 struct timeval t[2];
561 -               t[0].tv_sec = time(NULL);
562 +               t[0].tv_sec = atime;
563                 t[0].tv_usec = 0;
564                 t[1].tv_sec = modtime;
565                 t[1].tv_usec = 0;
566 @@ -154,12 +158,12 @@ int set_modtime(const char *fname, time_t modtime, mode_t mode)
567                 return utimes(fname, t);
568  #elif defined HAVE_STRUCT_UTIMBUF
569                 struct utimbuf tbuf;
570 -               tbuf.actime = time(NULL);
571 +               tbuf.actime = atime;
572                 tbuf.modtime = modtime;
573                 return utime(fname,&tbuf);
574  #elif defined HAVE_UTIME
575                 time_t t[2];
576 -               t[0] = time(NULL);
577 +               t[0] = atime;
578                 t[1] = modtime;
579                 return utime(fname,t);
580  #else