The patches for 3.0.9.
[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: 40afd365cc8ca968fd16e161d24df5b8a8a520cc
8 diff --git a/compat.c b/compat.c
9 --- a/compat.c
10 +++ b/compat.c
11 @@ -46,6 +46,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 @@ -63,7 +64,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 @@ -140,6 +141,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_devices;
41  extern int preserve_specials;
42  extern int delete_during;
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 @@ -392,7 +393,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 @@ -476,6 +477,13 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
58                 xflags |= XMIT_SAME_TIME;
59         else
60                 modtime = file->modtime;
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 @@ -547,6 +555,8 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
72         }
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 @@ -632,7 +642,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 @@ -765,6 +775,16 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x
90         }
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))
105                 mode = tweak_mode(mode, chmod_modes);
106 @@ -915,6 +935,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 @@ -1289,6 +1311,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 @@ -21,6 +21,7 @@
128   */
129  
130  #include "rsync.h"
131 +#include "ifuncs.h"
132  
133  extern int verbose;
134  extern int dry_run;
135 @@ -682,6 +683,9 @@ void itemize(const char *fnamecmp, struct file_struct *file, int ndx, int statre
136                  : iflags & (ITEM_TRANSFER|ITEM_LOCAL_CHANGE) && !(iflags & ITEM_MATCHED)
137                   && (!(iflags & ITEM_XNAME_FOLLOWS) || *xname))
138                         iflags |= ITEM_REPORT_TIME;
139 +               if (atimes_ndx && !S_ISDIR(file->mode) && !S_ISLNK(file->mode)
140 +                && cmp_time(f_atime(file), sxp->st.st_atime) != 0)
141 +                       iflags |= ITEM_REPORT_ATIME;
142  #if !defined HAVE_LCHMOD && !defined HAVE_SETATTRLIST
143                 if (S_ISLNK(file->mode)) {
144                         ;
145 @@ -1058,6 +1062,8 @@ static int try_dests_reg(struct file_struct *file, char *fname, int ndx,
146                 if (link_dest) {
147                         if (!hard_link_one(file, fname, cmpbuf, 1))
148                                 goto try_a_copy;
149 +                       if (atimes_ndx)
150 +                               set_file_attrs(fname, file, sxp, NULL, 0);
151                         if (preserve_hard_links && F_IS_HLINKED(file))
152                                 finish_hard_link(file, fname, ndx, &sxp->st, itemizing, code, j);
153                         if (!maybe_ATTRS_REPORT && (verbose > 1 || stdout_format_has_i > 1)) {
154 @@ -1243,6 +1249,7 @@ static int try_dests_non(struct file_struct *file, char *fname, int ndx,
155  static void list_file_entry(struct file_struct *f)
156  {
157         char permbuf[PERMSTRING_SIZE];
158 +       time_t atime = atimes_ndx ? f_atime(f) : 0;
159         double len;
160  
161         if (!F_IS_ACTIVE(f)) {
162 @@ -1257,14 +1264,16 @@ static void list_file_entry(struct file_struct *f)
163  
164  #ifdef SUPPORT_LINKS
165         if (preserve_links && S_ISLNK(f->mode)) {
166 -               rprintf(FINFO, "%s %11.0f %s %s -> %s\n",
167 +               rprintf(FINFO, "%s %11.0f %s %s %s -> %s\n",
168                         permbuf, len, timestring(f->modtime),
169 +                       atimes_ndx ? timestring(atime) : "",
170                         f_name(f, NULL), F_SYMLINK(f));
171         } else
172  #endif
173         {
174 -               rprintf(FINFO, "%s %11.0f %s %s\n",
175 +               rprintf(FINFO, "%s %11.0f %s %s %s\n",
176                         permbuf, len, timestring(f->modtime),
177 +                       atimes_ndx ? timestring(atime) : "",
178                         f_name(f, NULL));
179         }
180  }
181 @@ -2121,7 +2130,7 @@ static void touch_up_dirs(struct file_list *flist, int ndx)
182                         STRUCT_STAT st;
183                         if (link_stat(fname, &st, 0) == 0
184                          && cmp_time(st.st_mtime, file->modtime) != 0)
185 -                               set_modtime(fname, file->modtime, file->mode);
186 +                               set_times(fname, file->modtime, file->modtime, file->mode);
187                 }
188                 if (counter >= loopchk_limit) {
189                         if (allowed_lull)
190 diff --git a/ifuncs.h b/ifuncs.h
191 --- a/ifuncs.h
192 +++ b/ifuncs.h
193 @@ -67,6 +67,28 @@ d_name(struct dirent *di)
194  #endif
195  }
196  
197 +static inline time_t
198 +f_atime(struct file_struct *fp)
199 +{
200 +#if SIZEOF_TIME_T > 4
201 +       time_t atime;
202 +       memcpy(&atime, &REQ_EXTRA(fp, atimes_ndx)->unum, SIZEOF_TIME_T);
203 +       return atime;
204 +#else
205 +       return REQ_EXTRA(fp, atimes_ndx)->unum;
206 +#endif
207 +}
208 +
209 +static inline void
210 +f_atime_set(struct file_struct *fp, time_t atime)
211 +{
212 +#if SIZEOF_TIME_T > 4
213 +       memcpy(&REQ_EXTRA(fp, atimes_ndx)->unum, &atime, SIZEOF_TIME_T);
214 +#else
215 +       REQ_EXTRA(fp, atimes_ndx)->unum = (uint32)atime;
216 +#endif
217 +}
218 +
219  static inline int
220  isDigit(const char *ptr)
221  {
222 diff --git a/log.c b/log.c
223 --- a/log.c
224 +++ b/log.c
225 @@ -658,7 +658,8 @@ static void log_formatted(enum logcode code, const char *format, const char *op,
226                         c[5] = !(iflags & ITEM_REPORT_PERMS) ? '.' : 'p';
227                         c[6] = !(iflags & ITEM_REPORT_OWNER) ? '.' : 'o';
228                         c[7] = !(iflags & ITEM_REPORT_GROUP) ? '.' : 'g';
229 -                       c[8] = !(iflags & ITEM_REPORT_ATIME) ? '.' : 'u';
230 +                       c[8] = !(iflags & ITEM_REPORT_ATIME) ? '.'
231 +                            : S_ISLNK(file->mode) ? 'U' : 'u';
232                         c[9] = !(iflags & ITEM_REPORT_ACL) ? '.' : 'a';
233                         c[10] = !(iflags & ITEM_REPORT_XATTR) ? '.' : 'x';
234                         c[11] = '\0';
235 diff --git a/options.c b/options.c
236 --- a/options.c
237 +++ b/options.c
238 @@ -59,6 +59,7 @@ int preserve_specials = 0;
239  int preserve_uid = 0;
240  int preserve_gid = 0;
241  int preserve_times = 0;
242 +int preserve_atimes = 0;
243  int update_only = 0;
244  int cvs_exclude = 0;
245  int dry_run = 0;
246 @@ -352,6 +353,7 @@ void usage(enum logcode F)
247    rprintf(F," -D                          same as --devices --specials\n");
248    rprintf(F," -t, --times                 preserve modification times\n");
249    rprintf(F," -O, --omit-dir-times        omit directories from --times\n");
250 +  rprintf(F," -U, --atimes                preserve access (last-used) times\n");
251    rprintf(F,"     --super                 receiver attempts super-user activities\n");
252  #ifdef SUPPORT_XATTRS
253    rprintf(F,"     --fake-super            store/recover privileged attrs using xattrs\n");
254 @@ -489,6 +491,9 @@ static struct poptOption long_options[] = {
255    {"times",           't', POPT_ARG_VAL,    &preserve_times, 1, 0, 0 },
256    {"no-times",         0,  POPT_ARG_VAL,    &preserve_times, 0, 0, 0 },
257    {"no-t",             0,  POPT_ARG_VAL,    &preserve_times, 0, 0, 0 },
258 +  {"atimes",          'U', POPT_ARG_VAL,    &preserve_atimes, 1, 0, 0 },
259 +  {"no-atimes",        0,  POPT_ARG_VAL,    &preserve_atimes, 0, 0, 0 },
260 +  {"no-U",             0,  POPT_ARG_VAL,    &preserve_atimes, 0, 0, 0 },
261    {"omit-dir-times",  'O', POPT_ARG_VAL,    &omit_dir_times, 1, 0, 0 },
262    {"no-omit-dir-times",0,  POPT_ARG_VAL,    &omit_dir_times, 0, 0, 0 },
263    {"no-O",             0,  POPT_ARG_VAL,    &omit_dir_times, 0, 0, 0 },
264 @@ -1784,6 +1789,8 @@ void server_options(char **args, int *argc_p)
265                 argstr[x++] = 'D';
266         if (preserve_times)
267                 argstr[x++] = 't';
268 +       if (preserve_atimes)
269 +               argstr[x++] = 'U';
270         if (preserve_perms)
271                 argstr[x++] = 'p';
272         else if (preserve_executability && am_sender)
273 diff --git a/rsync.c b/rsync.c
274 --- a/rsync.c
275 +++ b/rsync.c
276 @@ -380,6 +380,7 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
277         int updated = 0;
278         stat_x sx2;
279         int change_uid, change_gid;
280 +       time_t atime, mtime;
281         mode_t new_mode = file->mode;
282         int inherit;
283  
284 @@ -423,22 +424,40 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
285                 set_xattr(fname, file, fnamecmp, sxp);
286  #endif
287  
288 +       /* This code must be the first update in the function due to
289 +        * how it uses the "updated" variable. */
290         if (!preserve_times
291          || (!(preserve_times & PRESERVE_DIR_TIMES) && S_ISDIR(sxp->st.st_mode))
292          || (!(preserve_times & PRESERVE_LINK_TIMES) && S_ISLNK(sxp->st.st_mode)))
293                 flags |= ATTRS_SKIP_MTIME;
294 +       if (!atimes_ndx || S_ISDIR(sxp->st.st_mode))
295 +               flags |= ATTRS_SKIP_ATIME;
296         if (!(flags & ATTRS_SKIP_MTIME)
297             && cmp_time(sxp->st.st_mtime, file->modtime) != 0) {
298 -               int ret = set_modtime(fname, file->modtime, sxp->st.st_mode);
299 +               mtime = file->modtime;
300 +               updated = 1;
301 +       } else
302 +               mtime = sxp->st.st_mtime;
303 +       if (!(flags & ATTRS_SKIP_ATIME)) {
304 +               time_t file_atime = f_atime(file);
305 +               if (cmp_time(sxp->st.st_atime, file_atime) != 0) {
306 +                       atime = file_atime;
307 +                       updated = 1;
308 +               } else
309 +                       atime = sxp->st.st_atime;
310 +       } else
311 +               atime = sxp->st.st_atime;
312 +       if (updated) {
313 +               int ret = set_times(fname, mtime, atime, sxp->st.st_mode);
314                 if (ret < 0) {
315                         rsyserr(FERROR_XFER, errno, "failed to set times on %s",
316                                 full_fname(fname));
317                         goto cleanup;
318                 }
319 -               if (ret == 0) /* ret == 1 if symlink could not be set */
320 -                       updated = 1;
321 -               else
322 +               if (ret > 0) { /* ret == 1 if symlink could not be set */
323 +                       updated = 0;
324                         file->flags |= FLAG_TIME_FAILED;
325 +               }
326         }
327  
328         change_uid = am_root && uid_ndx && sxp->st.st_uid != (uid_t)F_OWNER(file);
329 @@ -578,7 +597,7 @@ int finish_transfer(const char *fname, const char *fnametmp,
330  
331         /* Change permissions before putting the file into place. */
332         set_file_attrs(fnametmp, file, NULL, fnamecmp,
333 -                      ok_to_set_time ? 0 : ATTRS_SKIP_MTIME);
334 +                      ok_to_set_time ? 0 : ATTRS_SKIP_MTIME | ATTRS_SKIP_ATIME);
335  
336         /* move tmp file over real file */
337         if (verbose > 2)
338 @@ -605,7 +624,7 @@ int finish_transfer(const char *fname, const char *fnametmp,
339  
340    do_set_file_attrs:
341         set_file_attrs(fnametmp, file, NULL, fnamecmp,
342 -                      ok_to_set_time ? 0 : ATTRS_SKIP_MTIME);
343 +                      ok_to_set_time ? 0 : ATTRS_SKIP_MTIME | ATTRS_SKIP_ATIME);
344  
345         if (temp_copy_name) {
346                 if (do_rename(fnametmp, fname) < 0) {
347 diff --git a/rsync.h b/rsync.h
348 --- a/rsync.h
349 +++ b/rsync.h
350 @@ -61,6 +61,7 @@
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_IO_ERROR_ENDLIST (1<<12)  /* protocols 31*- now (w/XMIT_EXTENDED_FLAGS) (also protocol 30 w/'f' compat flag) */
354 +#define XMIT_SAME_ATIME (1<<13)                /* protocols ?? - now */
355  
356  /* These flags are used in the live flist data. */
357  
358 @@ -160,6 +161,7 @@
359  
360  #define ATTRS_REPORT           (1<<0)
361  #define ATTRS_SKIP_MTIME       (1<<1)
362 +#define ATTRS_SKIP_ATIME       (1<<2)
363  
364  #define FULL_FLUSH     1
365  #define NORMAL_FLUSH   0
366 @@ -652,12 +654,14 @@ extern int file_extra_cnt;
367  extern int inc_recurse;
368  extern int uid_ndx;
369  extern int gid_ndx;
370 +extern int atimes_ndx;
371  extern int acls_ndx;
372  extern int xattrs_ndx;
373  
374  #define FILE_STRUCT_LEN (offsetof(struct file_struct, basename))
375  #define EXTRA_LEN (sizeof (union file_extras))
376  #define PTR_EXTRA_CNT ((sizeof (char *) + EXTRA_LEN - 1) / EXTRA_LEN)
377 +#define TIME_EXTRA_CNT ((SIZEOF_TIME_T + EXTRA_LEN - 1) / EXTRA_LEN)
378  #define DEV_EXTRA_CNT 2
379  #define DIRNODE_EXTRA_CNT 3
380  #define SUM_EXTRA_CNT ((MAX_DIGEST_LEN + EXTRA_LEN - 1) / EXTRA_LEN)
381 diff --git a/rsync.yo b/rsync.yo
382 --- a/rsync.yo
383 +++ b/rsync.yo
384 @@ -366,6 +366,7 @@ to the detailed description below for a complete description.  verb(
385   -D                          same as --devices --specials
386   -t, --times                 preserve modification times
387   -O, --omit-dir-times        omit directories from --times
388 + -U, --atimes                preserve access (use) times
389       --super                 receiver attempts super-user activities
390       --fake-super            store/recover privileged attrs using xattrs
391   -S, --sparse                handle sparse files efficiently
392 @@ -1077,6 +1078,12 @@ it is preserving modification times (see bf(--times)).  If NFS is sharing
393  the directories on the receiving side, it is a good idea to use bf(-O).
394  This option is inferred if you use bf(--backup) without bf(--backup-dir).
395  
396 +dit(bf(-U, --atimes)) This tells rsync to set the access (use) times of the
397 +destination files to the same value as the source files.  Note that the
398 +reading of the source file may update the atime of the source files, so
399 +repeated rsync runs with --atimes may be needed if you want to force the
400 +access-time values to be 100% identical on the two systems.
401 +
402  dit(bf(--super)) This tells the receiving side to attempt super-user
403  activities even if the receiving rsync wasn't run by the super-user.  These
404  activities include: preserving users via the bf(--owner) option, preserving
405 @@ -1838,7 +1845,10 @@ quote(itemization(
406    sender's value (requires bf(--owner) and super-user privileges).
407    it() A bf(g) means the group is different and is being updated to the
408    sender's value (requires bf(--group) and the authority to set the group).
409 -  it() The bf(u) slot is reserved for future use.
410 +  it() A bf(u) means the access (use) time is different and is being updated to
411 +  the sender's value (requires bf(--atimes)).  An alternate value of bf(U)
412 +  means that the access time will be set to the transfer time, which happens
413 +  when a symlink or directory is updated.
414    it() The bf(a) means that the ACL information changed.
415    it() The bf(x) means that the extended attribute information changed.
416  ))
417 diff --git a/syscall.c b/syscall.c
418 --- a/syscall.c
419 +++ b/syscall.c
420 @@ -302,15 +302,15 @@ OFF_T do_lseek(int fd, OFF_T offset, int whence)
421  }
422  
423  #ifdef HAVE_UTIMENSAT
424 -int do_utimensat(const char *fname, time_t modtime, uint32 mod_nsec)
425 +int do_utimensat(const char *fname, time_t modtime, uint32 mod_nsec, time_t atime, uint32 a_nsec)
426  {
427         struct timespec t[2];
428  
429         if (dry_run) return 0;
430         RETURN_ERROR_IF_RO_OR_LO;
431  
432 -       t[0].tv_sec = 0;
433 -       t[0].tv_nsec = UTIME_NOW;
434 +       t[0].tv_sec = atime;
435 +       t[0].tv_nsec = a_nsec;
436         t[1].tv_sec = modtime;
437         t[1].tv_nsec = mod_nsec;
438         return utimensat(AT_FDCWD, fname, t, AT_SYMLINK_NOFOLLOW);
439 @@ -318,15 +318,15 @@ int do_utimensat(const char *fname, time_t modtime, uint32 mod_nsec)
440  #endif
441  
442  #ifdef HAVE_LUTIMES
443 -int do_lutimes(const char *fname, time_t modtime, uint32 mod_nsec)
444 +int do_lutimes(const char *fname, time_t modtime, uint32 mod_nsec, time_t atime, uint32 a_nsec)
445  {
446         struct timeval t[2];
447  
448         if (dry_run) return 0;
449         RETURN_ERROR_IF_RO_OR_LO;
450  
451 -       t[0].tv_sec = time(NULL);
452 -       t[0].tv_usec = 0;
453 +       t[0].tv_sec = atime;
454 +       t[0].tv_usec = a_nsec;
455         t[1].tv_sec = modtime;
456         t[1].tv_usec = mod_nsec / 1000;
457         return lutimes(fname, t);
458 @@ -334,22 +334,22 @@ int do_lutimes(const char *fname, time_t modtime, uint32 mod_nsec)
459  #endif
460  
461  #ifdef HAVE_UTIMES
462 -int do_utimes(const char *fname, time_t modtime, uint32 mod_nsec)
463 +int do_utimes(const char *fname, time_t modtime, uint32 mod_nsec, time_t atime, uint32 a_nsec)
464  {
465         struct timeval t[2];
466  
467         if (dry_run) return 0;
468         RETURN_ERROR_IF_RO_OR_LO;
469  
470 -       t[0].tv_sec = time(NULL);
471 -       t[0].tv_usec = 0;
472 +       t[0].tv_sec = atime;
473 +       t[0].tv_usec = a_nsec;
474         t[1].tv_sec = modtime;
475         t[1].tv_usec = mod_nsec / 1000;
476         return utimes(fname, t);
477  }
478  
479  #elif defined HAVE_UTIME
480 -int do_utime(const char *fname, time_t modtime, UNUSED(uint32 mod_nsec))
481 +int do_utime(const char *fname, time_t modtime, UNUSED(uint32 mod_nsec), time_t atime, UNUSED(uint32 a_nsec))
482  {
483  #ifdef HAVE_STRUCT_UTIMBUF
484         struct utimbuf tbuf;
485 @@ -361,11 +361,11 @@ int do_utime(const char *fname, time_t modtime, UNUSED(uint32 mod_nsec))
486         RETURN_ERROR_IF_RO_OR_LO;
487  
488  # ifdef HAVE_STRUCT_UTIMBUF
489 -       tbuf.actime = time(NULL);
490 +       tbuf.actime = atime;
491         tbuf.modtime = modtime;
492         return utime(fname, &tbuf);
493  # else
494 -       t[0] = time(NULL);
495 +       t[0] = atime;
496         t[1] = modtime;
497         return utime(fname, t);
498  # endif
499 diff --git a/testsuite/atimes.test b/testsuite/atimes.test
500 new file mode 100644
501 --- /dev/null
502 +++ b/testsuite/atimes.test
503 @@ -0,0 +1,17 @@
504 +#! /bin/sh
505 +
506 +# Test rsync copying atimes
507 +
508 +. "$suitedir/rsync.fns"
509 +
510 +mkdir "$fromdir"
511 +
512 +touch "$fromdir/foo"
513 +touch -a -t 200102031717.42 "$fromdir/foo"
514 +
515 +TLS_ARGS=--atimes
516 +
517 +checkit "$RSYNC -rtUgvvv \"$fromdir/\" \"$todir/\"" "$fromdir" "$todir"
518 +
519 +# The script would have aborted on error, so getting here means we've won.
520 +exit 0
521 diff --git a/testsuite/rsync.fns b/testsuite/rsync.fns
522 --- a/testsuite/rsync.fns
523 +++ b/testsuite/rsync.fns
524 @@ -220,6 +220,10 @@ checkit() {
525      # We can just write everything to stdout/stderr, because the
526      # wrapper hides it unless there is a problem.
527  
528 +    if test x$TLS_ARGS = x--atimes; then
529 +       ( cd "$2" && rsync_ls_lR . ) > "$tmpdir/ls-from"
530 +    fi
531 +
532      echo "Running: \"$1\""  
533      eval "$1" 
534      status=$?
535 @@ -227,10 +231,13 @@ checkit() {
536         failed="$failed status=$status"
537      fi
538  
539 +    if test x$TLS_ARGS != x--atimes; then
540 +       ( cd "$2" && rsync_ls_lR . ) > "$tmpdir/ls-from"
541 +    fi
542 +
543      echo "-------------"
544      echo "check how the directory listings compare with diff:"
545      echo ""
546 -    ( cd "$2" && rsync_ls_lR . ) > "$tmpdir/ls-from"
547      ( cd "$3" && rsync_ls_lR . ) > "$tmpdir/ls-to"
548      diff $diffopt "$tmpdir/ls-from" "$tmpdir/ls-to" || failed="$failed dir-diff"
549  
550 diff --git a/tls.c b/tls.c
551 --- a/tls.c
552 +++ b/tls.c
553 @@ -107,6 +107,8 @@ static int stat_xattr(const char *fname, STRUCT_STAT *fst)
554  
555  #endif
556  
557 +static int display_atimes = 0;
558 +
559  static void failed(char const *what, char const *where)
560  {
561         fprintf(stderr, PROGRAM ": %s %s: %s\n",
562 @@ -114,12 +116,29 @@ static void failed(char const *what, char const *where)
563         exit(1);
564  }
565  
566 +static void storetime(char *dest, time_t t, size_t destsize)
567 +{
568 +       if (t) {
569 +               struct tm *mt = gmtime(&t);
570 +
571 +               snprintf(dest, destsize,
572 +                       "%04d-%02d-%02d %02d:%02d:%02d ",
573 +                       (int)mt->tm_year + 1900,
574 +                       (int)mt->tm_mon + 1,
575 +                       (int)mt->tm_mday,
576 +                       (int)mt->tm_hour,
577 +                       (int)mt->tm_min,
578 +                       (int)mt->tm_sec);
579 +       } else
580 +               strlcpy(dest, "                    ", destsize);
581 +}
582 +
583  static void list_file(const char *fname)
584  {
585         STRUCT_STAT buf;
586         char permbuf[PERMSTRING_SIZE];
587 -       struct tm *mt;
588 -       char datebuf[50];
589 +       char mtimebuf[50];
590 +       char atimebuf[50];
591         char linkbuf[4096];
592  
593         if (do_lstat(fname, &buf) < 0)
594 @@ -158,19 +177,11 @@ static void list_file(const char *fname)
595  
596         permstring(permbuf, buf.st_mode);
597  
598 -       if (buf.st_mtime) {
599 -               mt = gmtime(&buf.st_mtime);
600 -
601 -               snprintf(datebuf, sizeof datebuf,
602 -                       "%04d-%02d-%02d %02d:%02d:%02d",
603 -                       (int)mt->tm_year + 1900,
604 -                       (int)mt->tm_mon + 1,
605 -                       (int)mt->tm_mday,
606 -                       (int)mt->tm_hour,
607 -                       (int)mt->tm_min,
608 -                       (int)mt->tm_sec);
609 -       } else
610 -               strlcpy(datebuf, "                   ", sizeof datebuf);
611 +       storetime(mtimebuf, buf.st_mtime, sizeof mtimebuf);
612 +       if (display_atimes)
613 +               storetime(atimebuf, S_ISDIR(buf.st_mode) ? 0 : buf.st_atime, sizeof atimebuf);
614 +       else
615 +               atimebuf[0] = '\0';
616  
617         /* TODO: Perhaps escape special characters in fname? */
618  
619 @@ -181,13 +192,14 @@ static void list_file(const char *fname)
620                     (long)minor(buf.st_rdev));
621         } else /* NB: use double for size since it might not fit in a long. */
622                 printf("%12.0f", (double)buf.st_size);
623 -       printf(" %6ld.%-6ld %6ld %s %s%s\n",
624 +       printf(" %6ld.%-6ld %6ld %s%s%s%s\n",
625                (long)buf.st_uid, (long)buf.st_gid, (long)buf.st_nlink,
626 -              datebuf, fname, linkbuf);
627 +              mtimebuf, atimebuf, fname, linkbuf);
628  }
629  
630  static struct poptOption long_options[] = {
631    /* longName, shortName, argInfo, argPtr, value, descrip, argDesc */
632 +  {"atimes",          'U', POPT_ARG_NONE,   &display_atimes, 0, 0, 0},
633    {"link-times",      'l', POPT_ARG_NONE,   &link_times, 0, 0, 0 },
634    {"link-owner",      'L', POPT_ARG_NONE,   &link_owner, 0, 0, 0 },
635  #ifdef SUPPORT_XATTRS
636 @@ -203,6 +215,7 @@ static void tls_usage(int ret)
637    fprintf(F,"usage: " PROGRAM " [OPTIONS] FILE ...\n");
638    fprintf(F,"Trivial file listing program for portably checking rsync\n");
639    fprintf(F,"\nOptions:\n");
640 +  fprintf(F," -U, --atimes                display access (last-used) times\n");
641    fprintf(F," -l, --link-times            display the time on a symlink\n");
642    fprintf(F," -L, --link-owner            display the owner+group on a symlink\n");
643  #ifdef SUPPORT_XATTRS
644 diff --git a/util.c b/util.c
645 --- a/util.c
646 +++ b/util.c
647 @@ -125,20 +125,24 @@ NORETURN void overflow_exit(const char *str)
648  
649  /* This returns 0 for success, 1 for a symlink if symlink time-setting
650   * is not possible, or -1 for any other error. */
651 -int set_modtime(const char *fname, time_t modtime, mode_t mode)
652 +int set_times(const char *fname, time_t modtime, time_t atime, mode_t mode)
653  {
654         static int switch_step = 0;
655  
656         if (verbose > 2) {
657 -               rprintf(FINFO, "set modtime of %s to (%ld) %s",
658 +               char mtimebuf[200];
659 +
660 +               strlcpy(mtimebuf, timestring(modtime), sizeof mtimebuf);
661 +               rprintf(FINFO,
662 +                       "set modtime, atime of %s to (%ld) %s, (%ld) %s\n",
663                         fname, (long)modtime,
664 -                       asctime(localtime(&modtime)));
665 +                       mtimebuf, (long)atime, timestring(atime));
666         }
667  
668         switch (switch_step) {
669  #ifdef HAVE_UTIMENSAT
670  #include "case_N.h"
671 -               if (do_utimensat(fname, modtime, 0) == 0)
672 +               if (do_utimensat(fname, modtime, 0, atime, 0) == 0)
673                         break;
674                 if (errno != ENOSYS)
675                         return -1;
676 @@ -148,7 +152,7 @@ int set_modtime(const char *fname, time_t modtime, mode_t mode)
677  
678  #ifdef HAVE_LUTIMES
679  #include "case_N.h"
680 -               if (do_lutimes(fname, modtime, 0) == 0)
681 +               if (do_lutimes(fname, modtime, 0, atime, 0) == 0)
682                         break;
683                 if (errno != ENOSYS)
684                         return -1;
685 @@ -167,10 +171,10 @@ int set_modtime(const char *fname, time_t modtime, mode_t mode)
686  
687  #include "case_N.h"
688  #ifdef HAVE_UTIMES
689 -               if (do_utimes(fname, modtime, 0) == 0)
690 +               if (do_utimes(fname, modtime, 0, atime, 0) == 0)
691                         break;
692  #else
693 -               if (do_utime(fname, modtime, 0) == 0)
694 +               if (do_utime(fname, modtime, 0, atime, 0) == 0)
695                         break;
696  #endif
697