Fix alignment issue on 64-bit. Solution from Steve Ortiz.
[rsync/rsync-patches.git] / atimes.diff
CommitLineData
03019e41 1To use this patch, run these commands for a successful build:
8a529471 2
03019e41 3 patch -p1 <patches/atimes.diff
27e96866
WD
4 ./configure (optional if already run)
5 make
8a529471 6
c1ff70aa 7based-on: a01e3b490eb36ccf9e704840e1b6683dab867550
cc3e685d
WD
8diff --git a/compat.c b/compat.c
9--- a/compat.c
10+++ b/compat.c
c1ff70aa 11@@ -44,6 +44,7 @@ extern int protocol_version;
ccdb48f6 12 extern int protect_args;
898a2112
WD
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;
9c25eef5 18 extern int need_messages_from_generator;
c1ff70aa 19@@ -61,7 +62,7 @@ extern char *iconv_opt;
ccdb48f6 20 #endif
898a2112
WD
21
22 /* These index values are for the file-list's extra-attribute array. */
d4dd2dd5
WD
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;
898a2112 25
85096e5e 26 int receiver_symlink_times = 0; /* receiver can set the time on a symlink */
ae306a29 27 int sender_symlink_iconv = 0; /* sender should convert symlink content */
c1ff70aa 28@@ -138,6 +139,8 @@ void setup_protocol(int f_out,int f_in)
898a2112 29 uid_ndx = ++file_extra_cnt;
fdf967c7 30 if (preserve_gid)
898a2112 31 gid_ndx = ++file_extra_cnt;
fdf967c7 32+ if (preserve_atimes)
a5e6228a 33+ atimes_ndx = (file_extra_cnt += TIME_EXTRA_CNT);
4306c620 34 if (preserve_acls && !am_sender)
898a2112 35 acls_ndx = ++file_extra_cnt;
5795bf59 36 if (preserve_xattrs)
cc3e685d
WD
37diff --git a/flist.c b/flist.c
38--- a/flist.c
39+++ b/flist.c
c1ff70aa 40@@ -55,6 +55,7 @@ extern int missing_args;
898a2112
WD
41 extern int uid_ndx;
42 extern int gid_ndx;
790ba11a 43 extern int eol_nulls;
898a2112 44+extern int atimes_ndx;
81c32ffd 45 extern int relative_paths;
a5e0f697 46 extern int implied_dirs;
7e27b6c0 47 extern int file_extra_cnt;
c1ff70aa 48@@ -406,7 +407,7 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
ae306a29
WD
49 #endif
50 int ndx, int first_ndx)
43581f16 51 {
a5e6228a
WD
52- static time_t modtime;
53+ static time_t modtime, atime;
43581f16 54 static mode_t mode;
f1f4dbd1 55 #ifdef SUPPORT_HARD_LINKS
ba50e96c 56 static int64 dev;
c1ff70aa 57@@ -506,6 +507,13 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
43581f16 58 modtime = file->modtime;
72e5645e
WD
59 if (NSEC_BUMP(file) && protocol_version >= 31)
60 xflags |= XMIT_MOD_NSEC;
898a2112 61+ if (atimes_ndx && !S_ISDIR(mode)) {
a5e6228a 62+ time_t file_atime = f_atime(file);
672ad041 63+ if (file_atime == atime)
99650e0d 64+ xflags |= XMIT_SAME_ATIME;
43581f16 65+ else
672ad041 66+ atime = file_atime;
43581f16
WD
67+ }
68
09fb8f03 69 #ifdef SUPPORT_HARD_LINKS
fdf967c7 70 if (tmp_dev != 0) {
c1ff70aa 71@@ -592,6 +600,8 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
72e5645e 72 write_varint(f, F_MOD_NSEC(file));
99650e0d 73 if (!(xflags & XMIT_SAME_MODE))
43581f16 74 write_int(f, to_wire_mode(mode));
99650e0d 75+ if (atimes_ndx && !S_ISDIR(mode) && !(xflags & XMIT_SAME_ATIME))
a2dabe5e 76+ write_varlong(f, atime, 4);
f9df736a 77 if (preserve_uid && !(xflags & XMIT_SAME_UID)) {
caf38d8d
WD
78 if (protocol_version < 30)
79 write_int(f, uid);
c1ff70aa 80@@ -677,7 +687,7 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
72e5645e
WD
81
82 static struct file_struct *recv_file_entry(int f, struct file_list *flist, int xflags)
43581f16 83 {
a2dabe5e
WD
84- static int64 modtime;
85+ static int64 modtime, atime;
43581f16 86 static mode_t mode;
f1f4dbd1 87 #ifdef SUPPORT_HARD_LINKS
ba50e96c 88 static int64 dev;
c1ff70aa 89@@ -821,6 +831,16 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x
72e5645e 90 modtime_nsec = 0;
761f1b71 91 if (!(xflags & XMIT_SAME_MODE))
43581f16 92 mode = from_wire_mode(read_int(f));
898a2112 93+ if (atimes_ndx && !S_ISDIR(mode) && !(xflags & XMIT_SAME_ATIME)) {
a2dabe5e
WD
94+ atime = read_varlong(f, 4);
95+#if SIZEOF_TIME_T < SIZEOF_INT64
a5e6228a 96+ if (!am_generator && (int64)(time_t)atime != atime) {
cc3e685d 97+ rprintf(FERROR_XFER,
a2dabe5e
WD
98+ "Access time value of %s truncated on receiver.\n",
99+ lastname);
100+ }
101+#endif
102+ }
43581f16 103
fc557362 104 if (chmod_modes && !S_ISLNK(mode) && mode)
1cb60481 105 mode = tweak_mode(mode, chmod_modes);
c1ff70aa 106@@ -981,6 +1001,8 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x
fc068916 107 F_GROUP(file) = gid;
761f1b71
WD
108 file->flags |= gid_flags;
109 }
898a2112 110+ if (atimes_ndx)
a5e6228a 111+ f_atime_set(file, (time_t)atime);
d4dd2dd5 112 if (unsort_ndx)
671e613f 113 F_NDX(file) = flist->used + flist->ndx_start;
d4dd2dd5 114
c1ff70aa 115@@ -1376,6 +1398,8 @@ struct file_struct *make_file(const char *fname, struct file_list *flist,
fc068916 116 F_OWNER(file) = st.st_uid;
f9df736a 117 if (gid_ndx) /* Check gid_ndx instead of preserve_gid for del support */
fc068916 118 F_GROUP(file) = st.st_gid;
898a2112 119+ if (atimes_ndx)
a5e6228a 120+ f_atime_set(file, st.st_atime);
1cb60481 121
fc068916
WD
122 if (basename != thisname)
123 file->dirname = lastdir;
cc3e685d
WD
124diff --git a/generator.c b/generator.c
125--- a/generator.c
126+++ b/generator.c
c1ff70aa 127@@ -454,6 +454,9 @@ void itemize(const char *fnamecmp, struct file_struct *file, int ndx, int statre
c0c7984e 128 : iflags & (ITEM_TRANSFER|ITEM_LOCAL_CHANGE) && !(iflags & ITEM_MATCHED)
9a70b743 129 && (!(iflags & ITEM_XNAME_FOLLOWS) || *xname))
f20eb450 130 iflags |= ITEM_REPORT_TIME;
a5e6228a
WD
131+ if (atimes_ndx && !S_ISDIR(file->mode) && !S_ISLNK(file->mode)
132+ && cmp_time(f_atime(file), sxp->st.st_atime) != 0)
55602791 133+ iflags |= ITEM_REPORT_ATIME;
7c4c2959 134 #if !defined HAVE_LCHMOD && !defined HAVE_SETATTRLIST
03edfc6b
WD
135 if (S_ISLNK(file->mode)) {
136 ;
c1ff70aa 137@@ -824,6 +827,8 @@ static int try_dests_reg(struct file_struct *file, char *fname, int ndx,
8aec0853
WD
138 if (link_dest) {
139 if (!hard_link_one(file, fname, cmpbuf, 1))
8a04c9a7 140 goto try_a_copy;
a5e6228a 141+ if (atimes_ndx)
97669ae9 142+ set_file_attrs(fname, file, sxp, NULL, 0);
1aa236e1 143 if (preserve_hard_links && F_IS_HLINKED(file))
ccdb48f6 144 finish_hard_link(file, fname, ndx, &sxp->st, itemizing, code, j);
fc557362 145 if (!maybe_ATTRS_REPORT && (INFO_GTE(NAME, 2) || stdout_format_has_i > 1)) {
c1ff70aa 146@@ -1016,6 +1021,7 @@ static int try_dests_non(struct file_struct *file, char *fname, int ndx,
cdcd2137
WD
147 static void list_file_entry(struct file_struct *f)
148 {
149 char permbuf[PERMSTRING_SIZE];
a5e6228a 150+ time_t atime = atimes_ndx ? f_atime(f) : 0;
fc557362
WD
151 int64 len;
152 int colwidth = human_readable ? 14 : 11;
cdcd2137 153
c1ff70aa 154@@ -1031,10 +1037,11 @@ static void list_file_entry(struct file_struct *f)
cdcd2137
WD
155
156 #ifdef SUPPORT_LINKS
157 if (preserve_links && S_ISLNK(f->mode)) {
fc557362
WD
158- rprintf(FINFO, "%s %*s %s %s -> %s\n",
159+ rprintf(FINFO, "%s %*s %s %s %s -> %s\n",
160 permbuf, colwidth, comma_num(len),
161- timestring(f->modtime), f_name(f, NULL),
162- F_SYMLINK(f));
163+ timestring(f->modtime),
cdcd2137 164+ atimes_ndx ? timestring(atime) : "",
fc557362 165+ f_name(f, NULL), F_SYMLINK(f));
cdcd2137
WD
166 } else
167 #endif
fc557362 168 if (missing_args == 2 && f->mode == 0) {
c1ff70aa 169@@ -1042,9 +1049,11 @@ static void list_file_entry(struct file_struct *f)
fc557362 170 colwidth + 31, "*missing",
cdcd2137 171 f_name(f, NULL));
fc557362
WD
172 } else {
173- rprintf(FINFO, "%s %*s %s %s\n",
174+ rprintf(FINFO, "%s %*s %s %s %s\n",
175 permbuf, colwidth, comma_num(len),
176- timestring(f->modtime), f_name(f, NULL));
177+ timestring(f->modtime),
178+ atimes_ndx ? timestring(atime) : "",
179+ f_name(f, NULL));
cdcd2137
WD
180 }
181 }
fc557362 182
c1ff70aa 183@@ -1924,7 +1933,7 @@ static void touch_up_dirs(struct file_list *flist, int ndx)
c0c7984e
WD
184 STRUCT_STAT st;
185 if (link_stat(fname, &st, 0) == 0
186 && cmp_time(st.st_mtime, file->modtime) != 0)
72e5645e
WD
187- set_modtime(fname, file->modtime, F_MOD_NSEC(file), file->mode);
188+ set_times(fname, file->modtime, F_MOD_NSEC(file), file->modtime, file->mode);
c0c7984e 189 }
e2e42a01
WD
190 if (counter >= loopchk_limit) {
191 if (allowed_lull)
a5e6228a
WD
192diff --git a/ifuncs.h b/ifuncs.h
193--- a/ifuncs.h
194+++ b/ifuncs.h
5214a41b
WD
195@@ -43,6 +43,28 @@ free_xbuf(xbuf *xb)
196 memset(xb, 0, sizeof (xbuf));
a5e6228a
WD
197 }
198
199+static inline time_t
200+f_atime(struct file_struct *fp)
201+{
202+#if SIZEOF_TIME_T > 4
203+ time_t atime;
204+ memcpy(&atime, &REQ_EXTRA(fp, atimes_ndx)->unum, SIZEOF_TIME_T);
205+ return atime;
206+#else
207+ return REQ_EXTRA(fp, atimes_ndx)->unum;
208+#endif
209+}
210+
211+static inline void
212+f_atime_set(struct file_struct *fp, time_t atime)
213+{
214+#if SIZEOF_TIME_T > 4
215+ memcpy(&REQ_EXTRA(fp, atimes_ndx)->unum, &atime, SIZEOF_TIME_T);
216+#else
217+ REQ_EXTRA(fp, atimes_ndx)->unum = (uint32)atime;
218+#endif
219+}
220+
221 static inline int
fc557362 222 to_wire_mode(mode_t mode)
a5e6228a 223 {
cc3e685d
WD
224diff --git a/log.c b/log.c
225--- a/log.c
226+++ b/log.c
5214a41b 227@@ -733,7 +733,8 @@ static void log_formatted(enum logcode code, const char *format, const char *op,
fb11cdd7
WD
228 c[5] = !(iflags & ITEM_REPORT_PERMS) ? '.' : 'p';
229 c[6] = !(iflags & ITEM_REPORT_OWNER) ? '.' : 'o';
230 c[7] = !(iflags & ITEM_REPORT_GROUP) ? '.' : 'g';
4306c620 231- c[8] = !(iflags & ITEM_REPORT_ATIME) ? '.' : 'u';
fb11cdd7 232+ c[8] = !(iflags & ITEM_REPORT_ATIME) ? '.'
55602791 233+ : S_ISLNK(file->mode) ? 'U' : 'u';
4306c620
WD
234 c[9] = !(iflags & ITEM_REPORT_ACL) ? '.' : 'a';
235 c[10] = !(iflags & ITEM_REPORT_XATTR) ? '.' : 'x';
236 c[11] = '\0';
cc3e685d
WD
237diff --git a/options.c b/options.c
238--- a/options.c
239+++ b/options.c
c0c7984e 240@@ -59,6 +59,7 @@ int preserve_specials = 0;
a54a2c4d 241 int preserve_uid = 0;
43581f16
WD
242 int preserve_gid = 0;
243 int preserve_times = 0;
81c32ffd 244+int preserve_atimes = 0;
43581f16
WD
245 int update_only = 0;
246 int cvs_exclude = 0;
247 int dry_run = 0;
72e5645e 248@@ -699,6 +700,7 @@ void usage(enum logcode F)
4a65fe72 249 rprintf(F," -D same as --devices --specials\n");
671e613f
WD
250 rprintf(F," -t, --times preserve modification times\n");
251 rprintf(F," -O, --omit-dir-times omit directories from --times\n");
12b04b40 252+ rprintf(F," -U, --atimes preserve access (last-used) times\n");
4a65fe72 253 rprintf(F," --super receiver attempts super-user activities\n");
12b04b40
WD
254 #ifdef SUPPORT_XATTRS
255 rprintf(F," --fake-super store/recover privileged attrs using xattrs\n");
72e5645e 256@@ -846,6 +848,9 @@ static struct poptOption long_options[] = {
a54a2c4d 257 {"times", 't', POPT_ARG_VAL, &preserve_times, 2, 0, 0 },
489b0a72
WD
258 {"no-times", 0, POPT_ARG_VAL, &preserve_times, 0, 0, 0 },
259 {"no-t", 0, POPT_ARG_VAL, &preserve_times, 0, 0, 0 },
55602791 260+ {"atimes", 'U', POPT_ARG_VAL, &preserve_atimes, 1, 0, 0 },
489b0a72 261+ {"no-atimes", 0, POPT_ARG_VAL, &preserve_atimes, 0, 0, 0 },
70891d26 262+ {"no-U", 0, POPT_ARG_VAL, &preserve_atimes, 0, 0, 0 },
a54a2c4d
WD
263 {"omit-dir-times", 'O', POPT_ARG_VAL, &omit_dir_times, 1, 0, 0 },
264 {"no-omit-dir-times",0, POPT_ARG_VAL, &omit_dir_times, 0, 0, 0 },
265 {"no-O", 0, POPT_ARG_VAL, &omit_dir_times, 0, 0, 0 },
c1ff70aa 266@@ -2341,6 +2346,8 @@ void server_options(char **args, int *argc_p)
43581f16
WD
267 argstr[x++] = 'D';
268 if (preserve_times)
269 argstr[x++] = 't';
81c32ffd 270+ if (preserve_atimes)
55602791 271+ argstr[x++] = 'U';
43581f16 272 if (preserve_perms)
4a65fe72
WD
273 argstr[x++] = 'p';
274 else if (preserve_executability && am_sender)
cc3e685d
WD
275diff --git a/rsync.c b/rsync.c
276--- a/rsync.c
277+++ b/rsync.c
5214a41b 278@@ -451,6 +451,7 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
9a21ad72 279 int updated = 0;
c8a8b4a7 280 stat_x sx2;
9a21ad72
WD
281 int change_uid, change_gid;
282+ time_t atime, mtime;
1ed0b5c9 283 mode_t new_mode = file->mode;
ccdb48f6 284 int inherit;
9a21ad72 285
5214a41b 286@@ -489,20 +490,38 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
cc3e685d 287 set_xattr(fname, file, fnamecmp, sxp);
4306c620 288 #endif
9a21ad72 289
8a04c9a7
WD
290+ /* This code must be the first update in the function due to
291+ * how it uses the "updated" variable. */
a54a2c4d 292 if (!preserve_times || (S_ISDIR(sxp->st.st_mode) && preserve_times == 1))
4a65fe72 293 flags |= ATTRS_SKIP_MTIME;
a5e6228a 294+ if (!atimes_ndx || S_ISDIR(sxp->st.st_mode))
4a65fe72
WD
295+ flags |= ATTRS_SKIP_ATIME;
296 if (!(flags & ATTRS_SKIP_MTIME)
4306c620 297 && cmp_time(sxp->st.st_mtime, file->modtime) != 0) {
72e5645e 298- int ret = set_modtime(fname, file->modtime, F_MOD_NSEC(file), sxp->st.st_mode);
9a21ad72 299+ mtime = file->modtime;
9e355bf1 300+ updated = 1;
9a21ad72 301+ } else
4306c620 302+ mtime = sxp->st.st_mtime;
672ad041 303+ if (!(flags & ATTRS_SKIP_ATIME)) {
a5e6228a 304+ time_t file_atime = f_atime(file);
4306c620 305+ if (cmp_time(sxp->st.st_atime, file_atime) != 0) {
672ad041
WD
306+ atime = file_atime;
307+ updated = 1;
308+ } else
4306c620 309+ atime = sxp->st.st_atime;
81c32ffd 310+ } else
4306c620 311+ atime = sxp->st.st_atime;
9e355bf1 312+ if (updated) {
72e5645e 313+ int ret = set_times(fname, mtime, F_MOD_NSEC(file), atime, sxp->st.st_mode);
9e355bf1 314 if (ret < 0) {
cc3e685d 315 rsyserr(FERROR_XFER, errno, "failed to set times on %s",
9e355bf1 316 full_fname(fname));
4306c620 317 goto cleanup;
9e355bf1
WD
318 }
319- if (ret == 0) /* ret == 1 if symlink could not be set */
320- updated = 1;
c0c7984e 321- else
85096e5e 322+ if (ret > 0) { /* ret == 1 if symlink could not be set */
9e355bf1 323+ updated = 0;
c0c7984e 324 file->flags |= FLAG_TIME_FAILED;
85096e5e 325+ }
43581f16
WD
326 }
327
898a2112 328 change_uid = am_root && uid_ndx && sxp->st.st_uid != (uid_t)F_OWNER(file);
5214a41b 329@@ -639,7 +658,7 @@ int finish_transfer(const char *fname, const char *fnametmp,
a5e6228a
WD
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 */
fc557362 337 if (DEBUG_GTE(RECV, 1))
5214a41b 338@@ -666,7 +685,7 @@ int finish_transfer(const char *fname, const char *fnametmp,
a5e6228a
WD
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) {
cc3e685d
WD
347diff --git a/rsync.h b/rsync.h
348--- a/rsync.h
349+++ b/rsync.h
72e5645e 350@@ -62,6 +62,7 @@
9c25eef5 351 #define XMIT_HLINK_FIRST (1<<12) /* protocols 30 - now (HLINKED files only) */
fc557362 352 #define XMIT_IO_ERROR_ENDLIST (1<<12) /* protocols 31 - now (w/XMIT_EXTENDED_FLAGS) */
72e5645e
WD
353 #define XMIT_MOD_NSEC (1<<13) /* protocols 31 - now */
354+#define XMIT_SAME_ATIME (1<<14) /* protocols ?? - now */
43581f16
WD
355
356 /* These flags are used in the live flist data. */
357
5214a41b 358@@ -160,6 +161,7 @@
8a529471 359
4a65fe72
WD
360 #define ATTRS_REPORT (1<<0)
361 #define ATTRS_SKIP_MTIME (1<<1)
362+#define ATTRS_SKIP_ATIME (1<<2)
8a529471
WD
363
364 #define FULL_FLUSH 1
365 #define NORMAL_FLUSH 0
5214a41b 366@@ -664,12 +666,14 @@ extern int file_extra_cnt;
9c25eef5 367 extern int inc_recurse;
898a2112
WD
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;
fdf967c7 373
a5e6228a
WD
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)
cc3e685d
WD
381diff --git a/rsync.yo b/rsync.yo
382--- a/rsync.yo
383+++ b/rsync.yo
fc557362 384@@ -356,6 +356,7 @@ to the detailed description below for a complete description. verb(
4a65fe72 385 -D same as --devices --specials
671e613f
WD
386 -t, --times preserve modification times
387 -O, --omit-dir-times omit directories from --times
55602791 388+ -U, --atimes preserve access (use) times
4a65fe72 389 --super receiver attempts super-user activities
12b04b40 390 --fake-super store/recover privileged attrs using xattrs
43581f16 391 -S, --sparse handle sparse files efficiently
7170ca8d 392@@ -1078,6 +1079,12 @@ it is preserving modification times (see bf(--times)). If NFS is sharing
a7219d20 393 the directories on the receiving side, it is a good idea to use bf(-O).
333b8af4 394 This option is inferred if you use bf(--backup) without bf(--backup-dir).
7b675ff5 395
55602791
WD
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
81c32ffd
WD
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.
7b675ff5 401+
4a65fe72
WD
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
c1ff70aa 405@@ -1945,7 +1952,10 @@ quote(itemization(
ccc3a12c
WD
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).
f9df736a 409- it() The bf(u) slot is reserved for future use.
55602791
WD
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
ccc3a12c 413+ when a symlink or directory is updated.
4306c620 414 it() The bf(a) means that the ACL information changed.
f9df736a
WD
415 it() The bf(x) means that the extended attribute information changed.
416 ))
cc3e685d
WD
417diff --git a/testsuite/atimes.test b/testsuite/atimes.test
418new file mode 100644
419--- /dev/null
420+++ b/testsuite/atimes.test
a5e6228a 421@@ -0,0 +1,17 @@
13bed3dd
WD
422+#! /bin/sh
423+
424+# Test rsync copying atimes
425+
426+. "$suitedir/rsync.fns"
427+
13bed3dd
WD
428+mkdir "$fromdir"
429+
430+touch "$fromdir/foo"
431+touch -a -t 200102031717.42 "$fromdir/foo"
432+
a5e6228a 433+TLS_ARGS=--atimes
13bed3dd 434+
55602791 435+checkit "$RSYNC -rtUgvvv \"$fromdir/\" \"$todir/\"" "$fromdir" "$todir"
13bed3dd
WD
436+
437+# The script would have aborted on error, so getting here means we've won.
438+exit 0
cc3e685d
WD
439diff --git a/testsuite/rsync.fns b/testsuite/rsync.fns
440--- a/testsuite/rsync.fns
441+++ b/testsuite/rsync.fns
c1ff70aa 442@@ -218,6 +218,10 @@ checkit() {
13bed3dd
WD
443 # We can just write everything to stdout/stderr, because the
444 # wrapper hides it unless there is a problem.
445
a5e6228a 446+ if test x$TLS_ARGS = x--atimes; then
81c32ffd
WD
447+ ( cd "$2" && rsync_ls_lR . ) > "$tmpdir/ls-from"
448+ fi
13bed3dd
WD
449+
450 echo "Running: \"$1\""
451 eval "$1"
452 status=$?
c1ff70aa 453@@ -225,10 +229,13 @@ checkit() {
81c32ffd
WD
454 failed="YES";
455 fi
456
a5e6228a 457+ if test x$TLS_ARGS != x--atimes; then
81c32ffd
WD
458+ ( cd "$2" && rsync_ls_lR . ) > "$tmpdir/ls-from"
459+ fi
460+
13bed3dd 461 echo "-------------"
b78a6aba
WD
462 echo "check how the directory listings compare with diff:"
463 echo ""
13bed3dd 464- ( cd "$2" && rsync_ls_lR . ) > "$tmpdir/ls-from"
b78a6aba
WD
465 ( cd "$3" && rsync_ls_lR . ) > "$tmpdir/ls-to"
466 diff $diffopt "$tmpdir/ls-from" "$tmpdir/ls-to" || failed=YES
4da25dad 467
cc3e685d
WD
468diff --git a/tls.c b/tls.c
469--- a/tls.c
470+++ b/tls.c
72e5645e 471@@ -118,6 +118,8 @@ static int stat_xattr(const char *fname, STRUCT_STAT *fst)
58b399b9
WD
472
473 #endif
43581f16 474
a5e6228a 475+static int display_atimes = 0;
e2e42a01 476+
fe6407b5
WD
477 static void failed(char const *what, char const *where)
478 {
b3593e7c 479 fprintf(stderr, PROGRAM ": %s %s: %s\n",
72e5645e 480@@ -125,12 +127,37 @@ static void failed(char const *what, char const *where)
fe6407b5 481 exit(1);
43581f16
WD
482 }
483
72e5645e 484+static void storetime(char *dest, size_t destsize, time_t t, int nsecs)
43581f16
WD
485+{
486+ if (t) {
72e5645e 487+ int len;
43581f16 488+ struct tm *mt = gmtime(&t);
b3593e7c 489+
72e5645e
WD
490+ len = snprintf(dest, destsize,
491+ "%04d-%02d-%02d %02d:%02d:%02d",
9d95bd65
WD
492+ (int)mt->tm_year + 1900,
493+ (int)mt->tm_mon + 1,
494+ (int)mt->tm_mday,
495+ (int)mt->tm_hour,
496+ (int)mt->tm_min,
497+ (int)mt->tm_sec);
72e5645e
WD
498+ if (nsecs >= 0) {
499+ snprintf(datebuf + len, sizeof datebuf - len, ".%09d", nsecs);
500+ }
501+ } else {
502+ int has_nsecs = nsecs >= 0 ? 1 : 0;
503+ int len = MIN(19 + 9*nsec_times, (int)sizeof datebuf - 1);
504+ memset(datebuf, ' ', len);
505+ datebuf[len] = '\0';
506+ }
b3593e7c
WD
507+}
508+
fe6407b5 509 static void list_file(const char *fname)
43581f16
WD
510 {
511 STRUCT_STAT buf;
512 char permbuf[PERMSTRING_SIZE];
513- struct tm *mt;
514- char datebuf[50];
515+ char mtimebuf[50];
516+ char atimebuf[50];
517 char linkbuf[4096];
518
ba50e96c 519 if (do_lstat(fname, &buf) < 0)
72e5645e
WD
520@@ -168,30 +195,17 @@ static void list_file(const char *fname)
521 }
43581f16
WD
522
523 permstring(permbuf, buf.st_mode);
72e5645e 524-
43581f16 525- if (buf.st_mtime) {
72e5645e 526- int len;
43581f16
WD
527- mt = gmtime(&buf.st_mtime);
528-
72e5645e 529- len = snprintf(datebuf, sizeof datebuf,
3f053c45 530- "%04d-%02d-%02d %02d:%02d:%02d",
9d95bd65
WD
531- (int)mt->tm_year + 1900,
532- (int)mt->tm_mon + 1,
533- (int)mt->tm_mday,
534- (int)mt->tm_hour,
535- (int)mt->tm_min,
536- (int)mt->tm_sec);
72e5645e
WD
537 #ifdef ST_MTIME_NSEC
538- if (nsec_times) {
539- snprintf(datebuf + len, sizeof datebuf - len,
540- ".%09d", (int)buf.ST_MTIME_NSEC);
541- }
542+ if (nsec_times)
543+ nsecs = (int)buf.ST_MTIME_NSEC
544+ else
545 #endif
546- } else {
547- int len = MIN(19 + 9*nsec_times, (int)sizeof datebuf - 1);
548- memset(datebuf, ' ', len);
549- datebuf[len] = '\0';
550- }
551+ nsecs = -1;
552+ storetime(mtimebuf, sizeof mtimebuf, buf.st_mtime, nsecs);
a5e6228a 553+ if (display_atimes)
72e5645e 554+ storetime(atimebuf, sizeof atimebuf, S_ISDIR(buf.st_mode) ? 0 : buf.st_atime, -1);
a5e6228a
WD
555+ else
556+ atimebuf[0] = '\0';
43581f16
WD
557
558 /* TODO: Perhaps escape special characters in fname? */
559
72e5645e 560@@ -202,13 +216,14 @@ static void list_file(const char *fname)
43581f16 561 (long)minor(buf.st_rdev));
fc557362
WD
562 } else
563 printf("%15s", do_big_num(buf.st_size, 1, NULL));
43581f16
WD
564- printf(" %6ld.%-6ld %6ld %s %s%s\n",
565+ printf(" %6ld.%-6ld %6ld %s%s%s%s\n",
566 (long)buf.st_uid, (long)buf.st_gid, (long)buf.st_nlink,
567- datebuf, fname, linkbuf);
a5e6228a 568+ mtimebuf, atimebuf, fname, linkbuf);
b3593e7c 569 }
43581f16 570
12b04b40
WD
571 static struct poptOption long_options[] = {
572 /* longName, shortName, argInfo, argPtr, value, descrip, argDesc */
a5e6228a 573+ {"atimes", 'U', POPT_ARG_NONE, &display_atimes, 0, 0, 0},
e66d6d51
WD
574 {"link-times", 'l', POPT_ARG_NONE, &link_times, 0, 0, 0 },
575 {"link-owner", 'L', POPT_ARG_NONE, &link_owner, 0, 0, 0 },
58b399b9 576 #ifdef SUPPORT_XATTRS
72e5645e 577@@ -227,6 +242,7 @@ static void tls_usage(int ret)
12b04b40
WD
578 fprintf(F,"usage: " PROGRAM " [OPTIONS] FILE ...\n");
579 fprintf(F,"Trivial file listing program for portably checking rsync\n");
580 fprintf(F,"\nOptions:\n");
a5e6228a 581+ fprintf(F," -U, --atimes display access (last-used) times\n");
e66d6d51
WD
582 fprintf(F," -l, --link-times display the time on a symlink\n");
583 fprintf(F," -L, --link-owner display the owner+group on a symlink\n");
58b399b9 584 #ifdef SUPPORT_XATTRS
cc3e685d
WD
585diff --git a/util.c b/util.c
586--- a/util.c
587+++ b/util.c
91270139 588@@ -123,7 +123,7 @@ NORETURN void overflow_exit(const char *str)
577db5e2
WD
589 exit_cleanup(RERR_MALLOC);
590 }
43581f16 591
72e5645e
WD
592-int set_modtime(const char *fname, time_t modtime, uint32 mod_nsec, mode_t mode)
593+int set_times(const char *fname, time_t modtime, uint32 mod_nsec, time_t atime, mode_t mode)
43581f16 594 {
72e5645e 595 #ifndef CAN_SET_SYMLINK_TIMES
9e355bf1 596 if (S_ISLNK(mode))
72e5645e 597@@ -131,9 +131,13 @@ int set_modtime(const char *fname, time_t modtime, uint32 mod_nsec, mode_t mode)
9e355bf1
WD
598 #endif
599
fc557362 600 if (DEBUG_GTE(TIME, 1)) {
43581f16
WD
601- rprintf(FINFO, "set modtime of %s to (%ld) %s",
602+ char mtimebuf[200];
43581f16 603+
125d7fca 604+ strlcpy(mtimebuf, timestring(modtime), sizeof mtimebuf);
43581f16
WD
605+ rprintf(FINFO,
606+ "set modtime, atime of %s to (%ld) %s, (%ld) %s\n",
93ca4d27 607 fname, (long)modtime,
43581f16 608- asctime(localtime(&modtime)));
9e355bf1 609+ mtimebuf, (long)atime, timestring(atime));
43581f16
WD
610 }
611
ba50e96c 612 if (dry_run)
72e5645e 613@@ -142,8 +146,8 @@ int set_modtime(const char *fname, time_t modtime, uint32 mod_nsec, mode_t mode)
43581f16 614 {
72e5645e
WD
615 #ifdef HAVE_UTIMENSAT
616 struct timespec t[2];
617- t[0].tv_sec = 0;
618- t[0].tv_nsec = UTIME_NOW;
619+ t[0].tv_sec = atime;
620+ t[0].tv_nsec = 0;
621 t[1].tv_sec = modtime;
622 t[1].tv_nsec = mod_nsec;
623 if (utimensat(AT_FDCWD, fname, t, AT_SYMLINK_NOFOLLOW) < 0)
624@@ -151,7 +155,7 @@ int set_modtime(const char *fname, time_t modtime, uint32 mod_nsec, mode_t mode)
625 return 0;
626 #elif defined HAVE_UTIMES || defined HAVE_LUTIMES
9e355bf1
WD
627 struct timeval t[2];
628- t[0].tv_sec = time(NULL);
629+ t[0].tv_sec = atime;
630 t[0].tv_usec = 0;
631 t[1].tv_sec = modtime;
72e5645e
WD
632 t[1].tv_usec = mod_nsec / 1000;
633@@ -164,12 +168,12 @@ int set_modtime(const char *fname, time_t modtime, uint32 mod_nsec, mode_t mode)
634 # endif
d4dd2dd5 635 #elif defined HAVE_STRUCT_UTIMBUF
43581f16
WD
636 struct utimbuf tbuf;
637- tbuf.actime = time(NULL);
638+ tbuf.actime = atime;
639 tbuf.modtime = modtime;
640 return utime(fname,&tbuf);
09fb8f03 641 #elif defined HAVE_UTIME
43581f16
WD
642 time_t t[2];
643- t[0] = time(NULL);
644+ t[0] = atime;
645 t[1] = modtime;
646 return utime(fname,t);
647 #else