8eaff2e69474aaf17dcf6d43ad4b875111481915
[rsync.git/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 based-on: 7cb0de6326c915a72253fd103dae93308031ec3f
8 diff --git a/compat.c b/compat.c
9 --- a/compat.c
10 +++ b/compat.c
11 @@ -47,6 +47,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 @@ -64,7 +65,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 @@ -142,6 +143,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 --- a/flist.c
39 +++ b/flist.c
40 @@ -53,6 +53,7 @@ extern int preserve_specials;
41  extern int delete_during;
42  extern int missing_args;
43  extern int eol_nulls;
44 +extern int atimes_ndx;
45  extern int relative_paths;
46  extern int implied_dirs;
47  extern int ignore_perishable;
48 @@ -397,7 +398,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 @@ -497,6 +498,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 != -1) {
71 @@ -583,6 +591,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 @@ -670,7 +680,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 @@ -817,6 +827,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 @@ -984,6 +1004,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 @@ -1381,6 +1403,8 @@ struct file_struct *make_file(const char *fname, struct file_list *flist,
116                 F_GROUP(file) = st.st_gid;
117         if (am_generator && st.st_uid == our_uid)
118                 file->flags |= FLAG_OWNED_BY_US;
119 +       if (atimes_ndx)
120 +               f_atime_set(file, st.st_atime);
121  
122         if (basename != thisname)
123                 file->dirname = lastdir;
124 diff --git a/generator.c b/generator.c
125 --- a/generator.c
126 +++ b/generator.c
127 @@ -500,6 +500,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 @@ -917,6 +920,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 @@ -1119,6 +1124,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 @@ -1134,10 +1140,12 @@ 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 -> %s\n",
160                         permbuf, colwidth, human_num(len),
161 -                       timestring(f->modtime), f_name(f, NULL),
162 -                       F_SYMLINK(f));
163 +                       timestring(f->modtime),
164 +                       atimes_ndx ? " " : "",
165 +                       atimes_ndx ? timestring(atime) : "",
166 +                       f_name(f, NULL), F_SYMLINK(f));
167         } else
168  #endif
169         if (missing_args == 2 && f->mode == 0) {
170 @@ -1145,9 +1153,12 @@ static void list_file_entry(struct file_struct *f)
171                         colwidth + 31, "*missing",
172                         f_name(f, NULL));
173         } else {
174 -               rprintf(FINFO, "%s %*s %s %s\n",
175 +               rprintf(FINFO, "%s %*s %s%s%s %s\n",
176                         permbuf, colwidth, human_num(len),
177 -                       timestring(f->modtime), f_name(f, NULL));
178 +                       timestring(f->modtime),
179 +                       atimes_ndx ? " " : "",
180 +                       atimes_ndx ? timestring(atime) : "",
181 +                       f_name(f, NULL));
182         }
183  }
184  
185 @@ -2050,7 +2061,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, F_MOD_NSEC(file), file->mode);
190 +                               set_times(fname, file->modtime, F_MOD_NSEC(file), file->modtime, file->mode);
191                 }
192                 if (counter >= loopchk_limit) {
193                         if (allowed_lull)
194 diff --git a/ifuncs.h b/ifuncs.h
195 --- a/ifuncs.h
196 +++ b/ifuncs.h
197 @@ -43,6 +43,28 @@ free_xbuf(xbuf *xb)
198         memset(xb, 0, sizeof (xbuf));
199  }
200  
201 +static inline time_t
202 +f_atime(struct file_struct *fp)
203 +{
204 +#if SIZEOF_TIME_T > 4
205 +       time_t atime;
206 +       memcpy(&atime, &REQ_EXTRA(fp, atimes_ndx)->unum, SIZEOF_TIME_T);
207 +       return atime;
208 +#else
209 +       return REQ_EXTRA(fp, atimes_ndx)->unum;
210 +#endif
211 +}
212 +
213 +static inline void
214 +f_atime_set(struct file_struct *fp, time_t atime)
215 +{
216 +#if SIZEOF_TIME_T > 4
217 +       memcpy(&REQ_EXTRA(fp, atimes_ndx)->unum, &atime, SIZEOF_TIME_T);
218 +#else
219 +       REQ_EXTRA(fp, atimes_ndx)->unum = (uint32)atime;
220 +#endif
221 +}
222 +
223  static inline int
224  to_wire_mode(mode_t mode)
225  {
226 diff --git a/log.c b/log.c
227 --- a/log.c
228 +++ b/log.c
229 @@ -720,7 +720,8 @@ static void log_formatted(enum logcode code, const char *format, const char *op,
230                         c[5] = !(iflags & ITEM_REPORT_PERMS) ? '.' : 'p';
231                         c[6] = !(iflags & ITEM_REPORT_OWNER) ? '.' : 'o';
232                         c[7] = !(iflags & ITEM_REPORT_GROUP) ? '.' : 'g';
233 -                       c[8] = !(iflags & ITEM_REPORT_ATIME) ? '.' : 'u';
234 +                       c[8] = !(iflags & ITEM_REPORT_ATIME) ? '.'
235 +                            : S_ISLNK(file->mode) ? 'U' : 'u';
236                         c[9] = !(iflags & ITEM_REPORT_ACL) ? '.' : 'a';
237                         c[10] = !(iflags & ITEM_REPORT_XATTR) ? '.' : 'x';
238                         c[11] = '\0';
239 diff --git a/options.c b/options.c
240 --- a/options.c
241 +++ b/options.c
242 @@ -61,6 +61,7 @@ int preserve_specials = 0;
243  int preserve_uid = 0;
244  int preserve_gid = 0;
245  int preserve_times = 0;
246 +int preserve_atimes = 0;
247  int update_only = 0;
248  int cvs_exclude = 0;
249  int dry_run = 0;
250 @@ -709,6 +710,7 @@ void usage(enum logcode F)
251    rprintf(F,"     --specials              preserve special files\n");
252    rprintf(F," -D                          same as --devices --specials\n");
253    rprintf(F," -t, --times                 preserve modification times\n");
254 +  rprintf(F," -U, --atimes                preserve access (last-used) times\n");
255    rprintf(F," -O, --omit-dir-times        omit directories from --times\n");
256    rprintf(F," -J, --omit-link-times       omit symlinks from --times\n");
257    rprintf(F,"     --super                 receiver attempts super-user activities\n");
258 @@ -867,6 +869,9 @@ static struct poptOption long_options[] = {
259    {"times",           't', POPT_ARG_VAL,    &preserve_times, 1, 0, 0 },
260    {"no-times",         0,  POPT_ARG_VAL,    &preserve_times, 0, 0, 0 },
261    {"no-t",             0,  POPT_ARG_VAL,    &preserve_times, 0, 0, 0 },
262 +  {"atimes",          'U', POPT_ARG_VAL,    &preserve_atimes, 1, 0, 0 },
263 +  {"no-atimes",        0,  POPT_ARG_VAL,    &preserve_atimes, 0, 0, 0 },
264 +  {"no-U",             0,  POPT_ARG_VAL,    &preserve_atimes, 0, 0, 0 },
265    {"omit-dir-times",  'O', POPT_ARG_VAL,    &omit_dir_times, 1, 0, 0 },
266    {"no-omit-dir-times",0,  POPT_ARG_VAL,    &omit_dir_times, 0, 0, 0 },
267    {"no-O",             0,  POPT_ARG_VAL,    &omit_dir_times, 0, 0, 0 },
268 @@ -2439,6 +2444,8 @@ void server_options(char **args, int *argc_p)
269                 argstr[x++] = 'D';
270         if (preserve_times)
271                 argstr[x++] = 't';
272 +       if (preserve_atimes)
273 +               argstr[x++] = 'U';
274         if (preserve_perms)
275                 argstr[x++] = 'p';
276         else if (preserve_executability && am_sender)
277 diff --git a/rsync.c b/rsync.c
278 --- a/rsync.c
279 +++ b/rsync.c
280 @@ -458,6 +458,7 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
281         int updated = 0;
282         stat_x sx2;
283         int change_uid, change_gid;
284 +       time_t atime, mtime;
285         mode_t new_mode = file->mode;
286         int inherit;
287  
288 @@ -543,22 +544,40 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
289                 set_xattr(fname, file, fnamecmp, sxp);
290  #endif
291  
292 +       /* This code must be the first update in the function due to
293 +        * how it uses the "updated" variable. */
294         if (!preserve_times
295          || (!(preserve_times & PRESERVE_DIR_TIMES) && S_ISDIR(sxp->st.st_mode))
296          || (!(preserve_times & PRESERVE_LINK_TIMES) && S_ISLNK(sxp->st.st_mode)))
297                 flags |= ATTRS_SKIP_MTIME;
298 +       if (!atimes_ndx || S_ISDIR(sxp->st.st_mode))
299 +               flags |= ATTRS_SKIP_ATIME;
300         if (!(flags & ATTRS_SKIP_MTIME)
301             && cmp_time(sxp->st.st_mtime, file->modtime) != 0) {
302 -               int ret = set_modtime(fname, file->modtime, F_MOD_NSEC(file), sxp->st.st_mode);
303 +               mtime = file->modtime;
304 +               updated = 1;
305 +       } else
306 +               mtime = sxp->st.st_mtime;
307 +       if (!(flags & ATTRS_SKIP_ATIME)) {
308 +               time_t file_atime = f_atime(file);
309 +               if (cmp_time(sxp->st.st_atime, file_atime) != 0) {
310 +                       atime = file_atime;
311 +                       updated = 1;
312 +               } else
313 +                       atime = sxp->st.st_atime;
314 +       } else
315 +               atime = sxp->st.st_atime;
316 +       if (updated) {
317 +               int ret = set_times(fname, mtime, F_MOD_NSEC(file), atime, sxp->st.st_mode);
318                 if (ret < 0) {
319                         rsyserr(FERROR_XFER, errno, "failed to set times on %s",
320                                 full_fname(fname));
321                         goto cleanup;
322                 }
323 -               if (ret == 0) /* ret == 1 if symlink could not be set */
324 -                       updated = 1;
325 -               else
326 +               if (ret > 0) { /* ret == 1 if symlink could not be set */
327 +                       updated = 0;
328                         file->flags |= FLAG_TIME_FAILED;
329 +               }
330         }
331  
332  #ifdef SUPPORT_ACLS
333 @@ -662,7 +681,7 @@ int finish_transfer(const char *fname, const char *fnametmp,
334  
335         /* Change permissions before putting the file into place. */
336         set_file_attrs(fnametmp, file, NULL, fnamecmp,
337 -                      ok_to_set_time ? 0 : ATTRS_SKIP_MTIME);
338 +                      ok_to_set_time ? 0 : ATTRS_SKIP_MTIME | ATTRS_SKIP_ATIME);
339  
340         /* move tmp file over real file */
341         if (DEBUG_GTE(RECV, 1))
342 @@ -687,7 +706,7 @@ int finish_transfer(const char *fname, const char *fnametmp,
343  
344    do_set_file_attrs:
345         set_file_attrs(fnametmp, file, NULL, fnamecmp,
346 -                      ok_to_set_time ? 0 : ATTRS_SKIP_MTIME);
347 +                      ok_to_set_time ? 0 : ATTRS_SKIP_MTIME | ATTRS_SKIP_ATIME);
348  
349         if (temp_copy_name) {
350                 if (do_rename(fnametmp, fname) < 0) {
351 diff --git a/rsync.h b/rsync.h
352 --- a/rsync.h
353 +++ b/rsync.h
354 @@ -62,6 +62,7 @@
355  #define XMIT_HLINK_FIRST (1<<12)       /* protocols 30 - now (HLINKED files only) */
356  #define XMIT_IO_ERROR_ENDLIST (1<<12)  /* protocols 31*- now (w/XMIT_EXTENDED_FLAGS) (also protocol 30 w/'f' compat flag) */
357  #define XMIT_MOD_NSEC (1<<13)          /* protocols 31 - now */
358 +#define XMIT_SAME_ATIME (1<<14)                /* protocols ?? - now */
359  
360  /* These flags are used in the live flist data. */
361  
362 @@ -165,6 +166,7 @@
363  
364  #define ATTRS_REPORT           (1<<0)
365  #define ATTRS_SKIP_MTIME       (1<<1)
366 +#define ATTRS_SKIP_ATIME       (1<<2)
367  
368  #define FULL_FLUSH     1
369  #define NORMAL_FLUSH   0
370 @@ -709,12 +711,14 @@ extern int file_extra_cnt;
371  extern int inc_recurse;
372  extern int uid_ndx;
373  extern int gid_ndx;
374 +extern int atimes_ndx;
375  extern int acls_ndx;
376  extern int xattrs_ndx;
377  
378  #define FILE_STRUCT_LEN (offsetof(struct file_struct, basename))
379  #define EXTRA_LEN (sizeof (union file_extras))
380  #define PTR_EXTRA_CNT ((sizeof (char *) + EXTRA_LEN - 1) / EXTRA_LEN)
381 +#define TIME_EXTRA_CNT ((SIZEOF_TIME_T + EXTRA_LEN - 1) / EXTRA_LEN)
382  #define DEV_EXTRA_CNT 2
383  #define DIRNODE_EXTRA_CNT 3
384  #define SUM_EXTRA_CNT ((MAX_DIGEST_LEN + EXTRA_LEN - 1) / EXTRA_LEN)
385 diff --git a/rsync.yo b/rsync.yo
386 --- a/rsync.yo
387 +++ b/rsync.yo
388 @@ -372,6 +372,7 @@ to the detailed description below for a complete description.  verb(
389       --specials              preserve special files
390   -D                          same as --devices --specials
391   -t, --times                 preserve modification times
392 + -U, --atimes                preserve access (use) times
393   -O, --omit-dir-times        omit directories from --times
394   -J, --omit-link-times       omit symlinks from --times
395       --super                 receiver attempts super-user activities
396 @@ -1173,6 +1174,12 @@ cause the next transfer to behave as if it used bf(-I), causing all files to be
397  updated (though rsync's delta-transfer algorithm will make the update fairly efficient
398  if the files haven't actually changed, you're much better off using bf(-t)).
399  
400 +dit(bf(-U, --atimes)) This tells rsync to set the access (use) times of the
401 +destination files to the same value as the source files.  Note that the
402 +reading of the source file may update the atime of the source files, so
403 +repeated rsync runs with --atimes may be needed if you want to force the
404 +access-time values to be 100% identical on the two systems.
405 +
406  dit(bf(-O, --omit-dir-times)) This tells rsync to omit directories when
407  it is preserving modification times (see bf(--times)).  If NFS is sharing
408  the directories on the receiving side, it is a good idea to use bf(-O).
409 @@ -2130,7 +2137,10 @@ quote(itemization(
410    sender's value (requires bf(--owner) and super-user privileges).
411    it() A bf(g) means the group is different and is being updated to the
412    sender's value (requires bf(--group) and the authority to set the group).
413 -  it() The bf(u) slot is reserved for future use.
414 +  it() A bf(u) means the access (use) time is different and is being updated to
415 +  the sender's value (requires bf(--atimes)).  An alternate value of bf(U)
416 +  means that the access time will be set to the transfer time, which happens
417 +  when a symlink or directory is updated.
418    it() The bf(a) means that the ACL information changed.
419    it() The bf(x) means that the extended attribute information changed.
420  ))
421 diff --git a/syscall.c b/syscall.c
422 --- a/syscall.c
423 +++ b/syscall.c
424 @@ -349,15 +349,15 @@ OFF_T do_lseek(int fd, OFF_T offset, int whence)
425  }
426  
427  #ifdef HAVE_UTIMENSAT
428 -int do_utimensat(const char *fname, time_t modtime, uint32 mod_nsec)
429 +int do_utimensat(const char *fname, time_t modtime, uint32 mod_nsec, time_t atime, uint32 a_nsec)
430  {
431         struct timespec t[2];
432  
433         if (dry_run) return 0;
434         RETURN_ERROR_IF_RO_OR_LO;
435  
436 -       t[0].tv_sec = 0;
437 -       t[0].tv_nsec = UTIME_NOW;
438 +       t[0].tv_sec = atime;
439 +       t[0].tv_nsec = a_nsec;
440         t[1].tv_sec = modtime;
441         t[1].tv_nsec = mod_nsec;
442         return utimensat(AT_FDCWD, fname, t, AT_SYMLINK_NOFOLLOW);
443 @@ -365,15 +365,15 @@ int do_utimensat(const char *fname, time_t modtime, uint32 mod_nsec)
444  #endif
445  
446  #ifdef HAVE_LUTIMES
447 -int do_lutimes(const char *fname, time_t modtime, uint32 mod_nsec)
448 +int do_lutimes(const char *fname, time_t modtime, uint32 mod_nsec, time_t atime, uint32 a_nsec)
449  {
450         struct timeval t[2];
451  
452         if (dry_run) return 0;
453         RETURN_ERROR_IF_RO_OR_LO;
454  
455 -       t[0].tv_sec = time(NULL);
456 -       t[0].tv_usec = 0;
457 +       t[0].tv_sec = atime;
458 +       t[0].tv_usec = a_nsec;
459         t[1].tv_sec = modtime;
460         t[1].tv_usec = mod_nsec / 1000;
461         return lutimes(fname, t);
462 @@ -381,22 +381,22 @@ int do_lutimes(const char *fname, time_t modtime, uint32 mod_nsec)
463  #endif
464  
465  #ifdef HAVE_UTIMES
466 -int do_utimes(const char *fname, time_t modtime, uint32 mod_nsec)
467 +int do_utimes(const char *fname, time_t modtime, uint32 mod_nsec, time_t atime, uint32 a_nsec)
468  {
469         struct timeval t[2];
470  
471         if (dry_run) return 0;
472         RETURN_ERROR_IF_RO_OR_LO;
473  
474 -       t[0].tv_sec = time(NULL);
475 -       t[0].tv_usec = 0;
476 +       t[0].tv_sec = atime;
477 +       t[0].tv_usec = a_nsec;
478         t[1].tv_sec = modtime;
479         t[1].tv_usec = mod_nsec / 1000;
480         return utimes(fname, t);
481  }
482  
483  #elif defined HAVE_UTIME
484 -int do_utime(const char *fname, time_t modtime, UNUSED(uint32 mod_nsec))
485 +int do_utime(const char *fname, time_t modtime, UNUSED(uint32 mod_nsec), time_t atime, UNUSED(uint32 a_nsec))
486  {
487  #ifdef HAVE_STRUCT_UTIMBUF
488         struct utimbuf tbuf;
489 @@ -408,11 +408,11 @@ int do_utime(const char *fname, time_t modtime, UNUSED(uint32 mod_nsec))
490         RETURN_ERROR_IF_RO_OR_LO;
491  
492  # ifdef HAVE_STRUCT_UTIMBUF
493 -       tbuf.actime = time(NULL);
494 +       tbuf.actime = atime;
495         tbuf.modtime = modtime;
496         return utime(fname, &tbuf);
497  # else
498 -       t[0] = time(NULL);
499 +       t[0] = atime;
500         t[1] = modtime;
501         return utime(fname, t);
502  # endif
503 diff --git a/testsuite/atimes.test b/testsuite/atimes.test
504 new file mode 100644
505 --- /dev/null
506 +++ b/testsuite/atimes.test
507 @@ -0,0 +1,17 @@
508 +#! /bin/sh
509 +
510 +# Test rsync copying atimes
511 +
512 +. "$suitedir/rsync.fns"
513 +
514 +mkdir "$fromdir"
515 +
516 +touch "$fromdir/foo"
517 +touch -a -t 200102031717.42 "$fromdir/foo"
518 +
519 +TLS_ARGS=--atimes
520 +
521 +checkit "$RSYNC -rtUgvvv \"$fromdir/\" \"$todir/\"" "$fromdir" "$todir"
522 +
523 +# The script would have aborted on error, so getting here means we've won.
524 +exit 0
525 diff --git a/testsuite/daemon.test b/testsuite/daemon.test
526 --- a/testsuite/daemon.test
527 +++ b/testsuite/daemon.test
528 @@ -27,7 +27,7 @@ outfile="$scratchdir/rsync.out"
529  SSH="src/support/lsh.sh --no-cd"
530  FILE_REPL='s/^\([^d][^ ]*\) *\(..........[0-9]\) /\1 \2 /'
531  DIR_REPL='s/^\(d[^ ]*\)  *[0-9][.,0-9]* /\1         DIR /'
532 -LS_REPL='s;[0-9][0-9][0-9][0-9]/[0-9][0-9]/[0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9];####/##/## ##:##:##;'
533 +LS_REPL='s;[0-9][0-9][0-9][0-9]/[0-9][0-9]/[0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9] ;####/##/## ##:##:## ;'
534  
535  build_rsyncd_conf
536  
537 diff --git a/testsuite/rsync.fns b/testsuite/rsync.fns
538 --- a/testsuite/rsync.fns
539 +++ b/testsuite/rsync.fns
540 @@ -219,6 +219,14 @@ checkit() {
541      # We can just write everything to stdout/stderr, because the
542      # wrapper hides it unless there is a problem.
543  
544 +    case "x$TLS_ARGS" in
545 +    *--atimes*)
546 +       ( cd "$2" && rsync_ls_lR . ) > "$tmpdir/ls-from"
547 +       ;;
548 +    *)
549 +       ;;
550 +    esac
551 +
552      echo "Running: \"$1\""  
553      eval "$1" 
554      status=$?
555 @@ -226,10 +234,17 @@ checkit() {
556         failed="$failed status=$status"
557      fi
558  
559 +    case "x$TLS_ARGS" in
560 +    *--atimes*)
561 +       ;;
562 +    *)
563 +       ( cd "$2" && rsync_ls_lR . ) > "$tmpdir/ls-from"
564 +       ;;
565 +    esac
566 +
567      echo "-------------"
568      echo "check how the directory listings compare with diff:"
569      echo ""
570 -    ( cd "$2" && rsync_ls_lR . ) > "$tmpdir/ls-from"
571      ( cd "$3" && rsync_ls_lR . ) > "$tmpdir/ls-to"
572      diff $diffopt "$tmpdir/ls-from" "$tmpdir/ls-to" || failed="$failed dir-diff"
573  
574 diff --git a/tls.c b/tls.c
575 --- a/tls.c
576 +++ b/tls.c
577 @@ -109,6 +109,8 @@ static int stat_xattr(const char *fname, STRUCT_STAT *fst)
578  
579  #endif
580  
581 +static int display_atimes = 0;
582 +
583  static void failed(char const *what, char const *where)
584  {
585         fprintf(stderr, PROGRAM ": %s %s: %s\n",
586 @@ -116,13 +118,38 @@ static void failed(char const *what, char const *where)
587         exit(1);
588  }
589  
590 +static void storetime(char *dest, size_t destsize, time_t t, int nsecs)
591 +{
592 +       if (t) {
593 +               int len;
594 +               struct tm *mt = gmtime(&t);
595 +
596 +               len = snprintf(dest, destsize,
597 +                       "%04d-%02d-%02d %02d:%02d:%02d",
598 +                       (int)mt->tm_year + 1900,
599 +                       (int)mt->tm_mon + 1,
600 +                       (int)mt->tm_mday,
601 +                       (int)mt->tm_hour,
602 +                       (int)mt->tm_min,
603 +                       (int)mt->tm_sec);
604 +               if (nsecs >= 0 && len >= 0)
605 +                       snprintf(dest + len, destsize - len, ".%09d", nsecs);
606 +       } else {
607 +               int has_nsecs = nsecs >= 0 ? 1 : 0;
608 +               int len = MIN(19 + 9*has_nsecs, (int)destsize - 1);
609 +               memset(dest, ' ', len);
610 +               dest[len] = '\0';
611 +       }
612 +}
613 +
614  static void list_file(const char *fname)
615  {
616         STRUCT_STAT buf;
617         char permbuf[PERMSTRING_SIZE];
618 -       struct tm *mt;
619 -       char datebuf[50];
620 +       char mtimebuf[50];
621 +       char atimebuf[50];
622         char linkbuf[4096];
623 +       int nsecs;
624  
625         if (do_lstat(fname, &buf) < 0)
626                 failed("stat", fname);
627 @@ -159,30 +186,17 @@ static void list_file(const char *fname)
628         }
629  
630         permstring(permbuf, buf.st_mode);
631 -
632 -       if (buf.st_mtime) {
633 -               int len;
634 -               mt = gmtime(&buf.st_mtime);
635 -
636 -               len = snprintf(datebuf, sizeof datebuf,
637 -                       "%04d-%02d-%02d %02d:%02d:%02d",
638 -                       (int)mt->tm_year + 1900,
639 -                       (int)mt->tm_mon + 1,
640 -                       (int)mt->tm_mday,
641 -                       (int)mt->tm_hour,
642 -                       (int)mt->tm_min,
643 -                       (int)mt->tm_sec);
644  #ifdef ST_MTIME_NSEC
645 -               if (nsec_times) {
646 -                       snprintf(datebuf + len, sizeof datebuf - len,
647 -                               ".%09d", (int)buf.ST_MTIME_NSEC);
648 -               }
649 +       if (nsec_times)
650 +               nsecs = (int)buf.ST_MTIME_NSEC;
651 +       else
652  #endif
653 -       } else {
654 -               int len = MIN(19 + 9*nsec_times, (int)sizeof datebuf - 1);
655 -               memset(datebuf, ' ', len);
656 -               datebuf[len] = '\0';
657 -       }
658 +               nsecs = -1;
659 +       storetime(mtimebuf, sizeof mtimebuf, buf.st_mtime, nsecs);
660 +       if (display_atimes)
661 +               storetime(atimebuf, sizeof atimebuf, S_ISDIR(buf.st_mode) ? 0 : buf.st_atime, -1);
662 +       else
663 +               atimebuf[0] = '\0';
664  
665         /* TODO: Perhaps escape special characters in fname? */
666  
667 @@ -193,13 +207,14 @@ static void list_file(const char *fname)
668                     (long)minor(buf.st_rdev));
669         } else
670                 printf("%15s", do_big_num(buf.st_size, 1, NULL));
671 -       printf(" %6ld.%-6ld %6ld %s %s%s\n",
672 +       printf(" %6ld.%-6ld %6ld %s%s%s%s\n",
673                (long)buf.st_uid, (long)buf.st_gid, (long)buf.st_nlink,
674 -              datebuf, fname, linkbuf);
675 +              mtimebuf, atimebuf, fname, linkbuf);
676  }
677  
678  static struct poptOption long_options[] = {
679    /* longName, shortName, argInfo, argPtr, value, descrip, argDesc */
680 +  {"atimes",          'U', POPT_ARG_NONE,   &display_atimes, 0, 0, 0},
681    {"link-times",      'l', POPT_ARG_NONE,   &link_times, 0, 0, 0 },
682    {"link-owner",      'L', POPT_ARG_NONE,   &link_owner, 0, 0, 0 },
683  #ifdef SUPPORT_XATTRS
684 @@ -218,6 +233,7 @@ static void tls_usage(int ret)
685    fprintf(F,"usage: " PROGRAM " [OPTIONS] FILE ...\n");
686    fprintf(F,"Trivial file listing program for portably checking rsync\n");
687    fprintf(F,"\nOptions:\n");
688 +  fprintf(F," -U, --atimes                display access (last-used) times\n");
689    fprintf(F," -l, --link-times            display the time on a symlink\n");
690    fprintf(F," -L, --link-owner            display the owner+group on a symlink\n");
691  #ifdef SUPPORT_XATTRS
692 diff --git a/util.c b/util.c
693 --- a/util.c
694 +++ b/util.c
695 @@ -117,20 +117,24 @@ void print_child_argv(const char *prefix, char **cmd)
696  
697  /* This returns 0 for success, 1 for a symlink if symlink time-setting
698   * is not possible, or -1 for any other error. */
699 -int set_modtime(const char *fname, time_t modtime, uint32 mod_nsec, mode_t mode)
700 +int set_times(const char *fname, time_t modtime, uint32 mod_nsec, time_t atime, mode_t mode)
701  {
702         static int switch_step = 0;
703  
704         if (DEBUG_GTE(TIME, 1)) {
705 -               rprintf(FINFO, "set modtime of %s to (%ld) %s",
706 +               char mtimebuf[200];
707 +
708 +               strlcpy(mtimebuf, timestring(modtime), sizeof mtimebuf);
709 +               rprintf(FINFO,
710 +                       "set modtime, atime of %s to (%ld) %s, (%ld) %s\n",
711                         fname, (long)modtime,
712 -                       asctime(localtime(&modtime)));
713 +                       mtimebuf, (long)atime, timestring(atime));
714         }
715  
716         switch (switch_step) {
717  #ifdef HAVE_UTIMENSAT
718  #include "case_N.h"
719 -               if (do_utimensat(fname, modtime, mod_nsec) == 0)
720 +               if (do_utimensat(fname, modtime, mod_nsec, atime, 0) == 0)
721                         break;
722                 if (errno != ENOSYS)
723                         return -1;
724 @@ -140,7 +144,7 @@ int set_modtime(const char *fname, time_t modtime, uint32 mod_nsec, mode_t mode)
725  
726  #ifdef HAVE_LUTIMES
727  #include "case_N.h"
728 -               if (do_lutimes(fname, modtime, mod_nsec) == 0)
729 +               if (do_lutimes(fname, modtime, mod_nsec, atime, 0) == 0)
730                         break;
731                 if (errno != ENOSYS)
732                         return -1;
733 @@ -159,10 +163,10 @@ int set_modtime(const char *fname, time_t modtime, uint32 mod_nsec, mode_t mode)
734  
735  #include "case_N.h"
736  #ifdef HAVE_UTIMES
737 -               if (do_utimes(fname, modtime, mod_nsec) == 0)
738 +               if (do_utimes(fname, modtime, mod_nsec, atime, 0) == 0)
739                         break;
740  #else
741 -               if (do_utime(fname, modtime, mod_nsec) == 0)
742 +               if (do_utime(fname, modtime, mod_nsec, atime, 0) == 0)
743                         break;
744  #endif
745