Adding filter-attribute-mods patch; updating patches.
[rsync/rsync-patches.git] / crtimes.diff
CommitLineData
a5e6228a
WD
1This patch adds a --crtimes (-N) option that will preserve
2create times on OS X.
3
4To use this patch, run these commands for a successful build:
5
f9df736a 6 patch -p1 <patches/fileflags.diff
a5e6228a
WD
7 patch -p1 <patches/crtimes.diff
8 ./configure (optional if already run)
9 make
10
7170ca8d 11based-on: patch/fileflags
a5e6228a
WD
12diff --git a/compat.c b/compat.c
13--- a/compat.c
14+++ b/compat.c
fc557362 15@@ -44,6 +44,7 @@ extern int force_change;
a5e6228a
WD
16 extern int protect_args;
17 extern int preserve_uid;
18 extern int preserve_gid;
19+extern int preserve_crtimes;
20 extern int preserve_fileflags;
21 extern int preserve_acls;
22 extern int preserve_xattrs;
fc557362 23@@ -62,7 +63,7 @@ extern char *iconv_opt;
a5e6228a
WD
24 #endif
25
26 /* These index values are for the file-list's extra-attribute array. */
27-int uid_ndx, gid_ndx, fileflags_ndx, acls_ndx, xattrs_ndx, unsort_ndx;
28+int uid_ndx, gid_ndx, crtimes_ndx, fileflags_ndx, acls_ndx, xattrs_ndx, unsort_ndx;
29
85096e5e 30 int receiver_symlink_times = 0; /* receiver can set the time on a symlink */
ae306a29 31 int sender_symlink_iconv = 0; /* sender should convert symlink content */
fc557362 32@@ -138,6 +139,8 @@ void setup_protocol(int f_out,int f_in)
a5e6228a
WD
33 uid_ndx = ++file_extra_cnt;
34 if (preserve_gid)
35 gid_ndx = ++file_extra_cnt;
36+ if (preserve_crtimes)
37+ crtimes_ndx = (file_extra_cnt += TIME_EXTRA_CNT);
f9df736a 38 if (preserve_fileflags || (force_change && !am_sender))
a5e6228a
WD
39 fileflags_ndx = ++file_extra_cnt;
40 if (preserve_acls && !am_sender)
41diff --git a/flist.c b/flist.c
42--- a/flist.c
43+++ b/flist.c
fc557362 44@@ -56,6 +56,7 @@ extern int missing_args;
a5e6228a
WD
45 extern int uid_ndx;
46 extern int gid_ndx;
47 extern int eol_nulls;
48+extern int crtimes_ndx;
49 extern int relative_paths;
50 extern int implied_dirs;
51 extern int file_extra_cnt;
fc557362 52@@ -397,7 +398,7 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
ae306a29
WD
53 #endif
54 int ndx, int first_ndx)
a5e6228a
WD
55 {
56- static time_t modtime;
57+ static time_t modtime, crtime;
58 static mode_t mode;
f9df736a 59 #ifdef SUPPORT_FILEFLAGS
a5e6228a 60 static uint32 fileflags;
7170ca8d 61@@ -506,6 +507,13 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
a5e6228a
WD
62 xflags |= XMIT_SAME_TIME;
63 else
64 modtime = file->modtime;
65+ if (crtimes_ndx) {
66+ time_t file_crtime = f_crtime(file);
67+ if (file_crtime == modtime)
68+ xflags |= XMIT_CRTIME_EQ_MTIME;
69+ else
70+ crtime = file_crtime;
71+ }
72
73 #ifdef SUPPORT_HARD_LINKS
74 if (tmp_dev != 0) {
7170ca8d 75@@ -588,6 +596,8 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
a5e6228a
WD
76 else
77 write_int(f, modtime);
78 }
79+ if (crtimes_ndx && !(xflags & XMIT_CRTIME_EQ_MTIME))
80+ write_varlong(f, crtime, 4);
81 if (!(xflags & XMIT_SAME_MODE))
82 write_int(f, to_wire_mode(mode));
f9df736a 83 #ifdef SUPPORT_FILEFLAGS
7170ca8d 84@@ -680,7 +690,7 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
a5e6228a
WD
85 static struct file_struct *recv_file_entry(struct file_list *flist,
86 int xflags, int f)
87 {
88- static int64 modtime;
89+ static int64 modtime, crtime;
90 static mode_t mode;
f9df736a 91 #ifdef SUPPORT_FILEFLAGS
a5e6228a 92 static uint32 fileflags;
7170ca8d 93@@ -819,6 +829,19 @@ static struct file_struct *recv_file_entry(struct file_list *flist,
a5e6228a
WD
94 } else
95 modtime = read_int(f);
96 }
97+ if (crtimes_ndx) {
98+ if (!(xflags & XMIT_CRTIME_EQ_MTIME)) {
99+ crtime = read_varlong(f, 4);
100+#if SIZEOF_TIME_T < SIZEOF_INT64
101+ if (!am_generator && (int64)(time_t)crtime != crtime) {
102+ rprintf(FERROR_XFER,
103+ "Create time value of %s truncated on receiver.\n",
104+ lastname);
105+ }
106+#endif
107+ } else
108+ crtime = modtime;
109+ }
110 if (!(xflags & XMIT_SAME_MODE))
111 mode = from_wire_mode(read_int(f));
112
7170ca8d 113@@ -979,6 +1002,8 @@ static struct file_struct *recv_file_entry(struct file_list *flist,
a5e6228a
WD
114 F_GROUP(file) = gid;
115 file->flags |= gid_flags;
116 }
117+ if (crtimes_ndx)
118+ f_crtime_set(file, (time_t)crtime);
119 if (unsort_ndx)
120 F_NDX(file) = flist->used + flist->ndx_start;
121
7170ca8d 122@@ -1368,6 +1393,8 @@ struct file_struct *make_file(const char *fname, struct file_list *flist,
a5e6228a 123 F_OWNER(file) = st.st_uid;
f9df736a 124 if (gid_ndx) /* Check gid_ndx instead of preserve_gid for del support */
a5e6228a
WD
125 F_GROUP(file) = st.st_gid;
126+ if (crtimes_ndx)
127+ f_crtime_set(file, get_create_time(fname));
128
129 if (basename != thisname)
130 file->dirname = lastdir;
131diff --git a/generator.c b/generator.c
132--- a/generator.c
133+++ b/generator.c
fc557362 134@@ -40,6 +40,7 @@ extern int preserve_xattrs;
85096e5e 135 extern int preserve_links;
a5e6228a
WD
136 extern int preserve_devices;
137 extern int preserve_specials;
a5e6228a 138+extern int preserve_fileflags;
85096e5e
WD
139 extern int preserve_hard_links;
140 extern int preserve_executability;
f9df736a 141 extern int preserve_fileflags;
fc557362 142@@ -419,6 +420,13 @@ int unchanged_attrs(const char *fname, struct file_struct *file, stat_x *sxp)
a5e6228a
WD
143 if (gid_ndx && !(file->flags & FLAG_SKIP_GROUP) && sxp->st.st_gid != (gid_t)F_GROUP(file))
144 return 0;
145
146+ if (crtimes_ndx) {
147+ if (sxp->crtime == 0)
148+ sxp->crtime = get_create_time(fname);
149+ if (cmp_time(sxp->crtime, f_crtime(file)) != 0)
150+ return 0;
151+ }
152+
153 #ifdef SUPPORT_ACLS
154 if (preserve_acls && !S_ISLNK(file->mode)) {
155 if (!ACL_READY(*sxp))
fc557362 156@@ -462,6 +470,12 @@ void itemize(const char *fnamecmp, struct file_struct *file, int ndx, int statre
c0c7984e 157 : iflags & (ITEM_TRANSFER|ITEM_LOCAL_CHANGE) && !(iflags & ITEM_MATCHED)
a5e6228a 158 && (!(iflags & ITEM_XNAME_FOLLOWS) || *xname))
a5e6228a
WD
159 iflags |= ITEM_REPORT_TIME;
160+ if (crtimes_ndx) {
161+ if (sxp->crtime == 0)
162+ sxp->crtime = get_create_time(fnamecmp);
163+ if (cmp_time(sxp->crtime, f_crtime(file)) != 0)
164+ iflags |= ITEM_REPORT_CRTIME;
165+ }
166 #if !defined HAVE_LCHMOD && !defined HAVE_SETATTRLIST
167 if (S_ISLNK(file->mode)) {
168 ;
fc557362 169@@ -1022,6 +1036,7 @@ static int try_dests_non(struct file_struct *file, char *fname, int ndx,
a5e6228a
WD
170 static void list_file_entry(struct file_struct *f)
171 {
172 char permbuf[PERMSTRING_SIZE];
173+ time_t crtime = crtimes_ndx ? f_crtime(f) : 0;
fc557362
WD
174 int64 len;
175 int colwidth = human_readable ? 14 : 11;
a5e6228a 176
fc557362 177@@ -1037,10 +1052,11 @@ static void list_file_entry(struct file_struct *f)
a5e6228a
WD
178
179 #ifdef SUPPORT_LINKS
180 if (preserve_links && S_ISLNK(f->mode)) {
fc557362
WD
181- rprintf(FINFO, "%s %*s %s %s -> %s\n",
182+ rprintf(FINFO, "%s %*s %s %s %s -> %s\n",
183 permbuf, colwidth, comma_num(len),
184- timestring(f->modtime), f_name(f, NULL),
185- F_SYMLINK(f));
186+ timestring(f->modtime),
a5e6228a 187+ crtimes_ndx ? timestring(crtime) : "",
fc557362 188+ f_name(f, NULL), F_SYMLINK(f));
a5e6228a
WD
189 } else
190 #endif
fc557362
WD
191 if (missing_args == 2 && f->mode == 0) {
192@@ -1048,9 +1064,11 @@ static void list_file_entry(struct file_struct *f)
193 colwidth + 31, "*missing",
a5e6228a 194 f_name(f, NULL));
fc557362
WD
195 } else {
196- rprintf(FINFO, "%s %*s %s %s\n",
197+ rprintf(FINFO, "%s %*s %s %s %s\n",
198 permbuf, colwidth, comma_num(len),
199- timestring(f->modtime), f_name(f, NULL));
200+ timestring(f->modtime),
201+ crtimes_ndx ? timestring(crtime) : "",
202+ f_name(f, NULL));
a5e6228a
WD
203 }
204 }
fc557362
WD
205
206@@ -1141,6 +1159,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
a5e6228a
WD
207 return;
208 }
209 }
210+ sx.crtime = 0;
c0c7984e 211
fc557362
WD
212 init_stat_x(&sx);
213 if (dry_run > 1 || (dry_missing_dir && is_below(file, dry_missing_dir))) {
a5e6228a
WD
214diff --git a/ifuncs.h b/ifuncs.h
215--- a/ifuncs.h
216+++ b/ifuncs.h
fc557362
WD
217@@ -35,6 +35,28 @@ realloc_xbuf(xbuf *xb, size_t sz)
218 xb->size = sz;
a5e6228a
WD
219 }
220
221+static inline time_t
222+f_crtime(struct file_struct *fp)
223+{
224+#if SIZEOF_TIME_T > 4
225+ time_t crtime;
226+ memcpy(&crtime, &REQ_EXTRA(fp, crtimes_ndx)->unum, SIZEOF_TIME_T);
227+ return crtime;
228+#else
229+ return REQ_EXTRA(fp, crtimes_ndx)->unum;
230+#endif
231+}
232+
233+static inline void
234+f_crtime_set(struct file_struct *fp, time_t crtime)
235+{
236+#if SIZEOF_TIME_T > 4
237+ memcpy(&REQ_EXTRA(fp, crtimes_ndx)->unum, &crtime, SIZEOF_TIME_T);
238+#else
239+ REQ_EXTRA(fp, crtimes_ndx)->unum = (uint32)crtime;
240+#endif
241+}
242+
243 static inline int
fc557362 244 to_wire_mode(mode_t mode)
a5e6228a
WD
245 {
246diff --git a/log.c b/log.c
247--- a/log.c
248+++ b/log.c
fc557362 249@@ -718,7 +718,8 @@ static void log_formatted(enum logcode code, const char *format, const char *op,
f9df736a 250 c[8] = !(iflags & ITEM_REPORT_FFLAGS) ? '.' : 'f';
a5e6228a
WD
251 c[9] = !(iflags & ITEM_REPORT_ACL) ? '.' : 'a';
252 c[10] = !(iflags & ITEM_REPORT_XATTR) ? '.' : 'x';
f9df736a
WD
253- c[11] = '\0';
254+ c[11] = !(iflags & ITEM_REPORT_CRTIME) ? '.' : 'n';
255+ c[12] = '\0';
256
257 if (iflags & (ITEM_IS_NEW|ITEM_MISSING_DATA)) {
258 char ch = iflags & ITEM_IS_NEW ? '+' : '?';
a5e6228a
WD
259diff --git a/options.c b/options.c
260--- a/options.c
261+++ b/options.c
c0c7984e 262@@ -60,6 +60,7 @@ int preserve_specials = 0;
a5e6228a
WD
263 int preserve_uid = 0;
264 int preserve_gid = 0;
265 int preserve_times = 0;
266+int preserve_crtimes = 0;
267 int update_only = 0;
268 int cvs_exclude = 0;
269 int dry_run = 0;
fc557362 270@@ -707,6 +708,7 @@ void usage(enum logcode F)
a5e6228a
WD
271 rprintf(F," -D same as --devices --specials\n");
272 rprintf(F," -t, --times preserve modification times\n");
273 rprintf(F," -O, --omit-dir-times omit directories from --times\n");
f9df736a 274+ rprintf(F," -N, --crtimes preserve create times (newness)\n");
a5e6228a
WD
275 rprintf(F," --super receiver attempts super-user activities\n");
276 #ifdef SUPPORT_XATTRS
277 rprintf(F," --fake-super store/recover privileged attrs using xattrs\n");
fc557362 278@@ -863,6 +865,9 @@ static struct poptOption long_options[] = {
a5e6228a
WD
279 {"times", 't', POPT_ARG_VAL, &preserve_times, 2, 0, 0 },
280 {"no-times", 0, POPT_ARG_VAL, &preserve_times, 0, 0, 0 },
281 {"no-t", 0, POPT_ARG_VAL, &preserve_times, 0, 0, 0 },
282+ {"crtimes", 'N', POPT_ARG_VAL, &preserve_crtimes, 1, 0, 0 },
283+ {"no-crtimes", 0, POPT_ARG_VAL, &preserve_crtimes, 0, 0, 0 },
284+ {"no-N", 0, POPT_ARG_VAL, &preserve_crtimes, 0, 0, 0 },
285 {"omit-dir-times", 'O', POPT_ARG_VAL, &omit_dir_times, 1, 0, 0 },
286 {"no-omit-dir-times",0, POPT_ARG_VAL, &omit_dir_times, 0, 0, 0 },
287 {"no-O", 0, POPT_ARG_VAL, &omit_dir_times, 0, 0, 0 },
7170ca8d 288@@ -2332,6 +2337,8 @@ void server_options(char **args, int *argc_p)
a5e6228a
WD
289 argstr[x++] = 'D';
290 if (preserve_times)
291 argstr[x++] = 't';
292+ if (preserve_crtimes)
293+ argstr[x++] = 'N';
294 if (preserve_perms)
295 argstr[x++] = 'p';
296 else if (preserve_executability && am_sender)
297diff --git a/rsync.c b/rsync.c
298--- a/rsync.c
299+++ b/rsync.c
fc557362 300@@ -471,6 +471,14 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
c0c7984e 301 else
85096e5e 302 file->flags |= FLAG_TIME_FAILED;
a5e6228a
WD
303 }
304+ if (crtimes_ndx && !(flags & ATTRS_SKIP_CRTIME)) {
305+ time_t file_crtime = f_crtime(file);
306+ if (sxp->crtime == 0)
307+ sxp->crtime = get_create_time(fname);
308+ if (cmp_time(sxp->crtime, file_crtime) != 0
309+ && set_create_time(fname, file_crtime) == 0)
310+ updated = 1;
311+ }
312
313 change_uid = am_root && uid_ndx && sxp->st.st_uid != (uid_t)F_OWNER(file);
314 change_gid = gid_ndx && !(file->flags & FLAG_SKIP_GROUP)
fc557362 315@@ -619,7 +627,7 @@ int finish_transfer(const char *fname, const char *fnametmp,
a5e6228a
WD
316 /* Change permissions before putting the file into place. */
317 set_file_attrs(fnametmp, file, NULL, fnamecmp,
f9df736a
WD
318 ATTRS_DELAY_IMMUTABLE
319- | (ok_to_set_time ? 0 : ATTRS_SKIP_MTIME));
320+ | (ok_to_set_time ? 0 : ATTRS_SKIP_MTIME | ATTRS_SKIP_CRTIME));
a5e6228a 321
f9df736a 322 /* move tmp file over real file */
fc557362
WD
323 if (DEBUG_GTE(RECV, 1))
324@@ -650,7 +658,7 @@ int finish_transfer(const char *fname, const char *fnametmp,
a5e6228a
WD
325
326 do_set_file_attrs:
327 set_file_attrs(fnametmp, file, NULL, fnamecmp,
328- ok_to_set_time ? 0 : ATTRS_SKIP_MTIME);
329+ ok_to_set_time ? 0 : ATTRS_SKIP_MTIME | ATTRS_SKIP_CRTIME);
330
331 if (temp_copy_name) {
332 if (do_rename(fnametmp, fname) < 0) {
333diff --git a/rsync.h b/rsync.h
334--- a/rsync.h
335+++ b/rsync.h
fc557362 336@@ -61,6 +61,7 @@
a5e6228a
WD
337 #define XMIT_GROUP_NAME_FOLLOWS (1<<11) /* protocols 30 - now */
338 #define XMIT_HLINK_FIRST (1<<12) /* protocols 30 - now (HLINKED files only) */
fc557362 339 #define XMIT_IO_ERROR_ENDLIST (1<<12) /* protocols 31 - now (w/XMIT_EXTENDED_FLAGS) */
a5e6228a
WD
340+#define XMIT_CRTIME_EQ_MTIME (1<<13) /* protocols ?? - now */
341 #define XMIT_SAME_FLAGS (1<<14) /* protocols ?? - now */
342
343 /* These flags are used in the live flist data. */
fc557362 344@@ -157,6 +158,7 @@
a5e6228a
WD
345 #define ATTRS_REPORT (1<<0)
346 #define ATTRS_SKIP_MTIME (1<<1)
f9df736a
WD
347 #define ATTRS_DELAY_IMMUTABLE (1<<2)
348+#define ATTRS_SKIP_CRTIME (1<<3)
a5e6228a
WD
349
350 #define FULL_FLUSH 1
351 #define NORMAL_FLUSH 0
fc557362 352@@ -173,7 +175,7 @@
a5e6228a
WD
353 #define FNAMECMP_FUZZY 0x83
354
355 /* For use by the itemize_changes code */
356-#define ITEM_REPORT_ATIME (1<<0)
357+#define ITEM_REPORT_CRTIME (1<<0)
c0c7984e
WD
358 #define ITEM_REPORT_CHANGE (1<<1)
359 #define ITEM_REPORT_SIZE (1<<2) /* regular files only */
360 #define ITEM_REPORT_TIMEFAIL (1<<2) /* symlinks only */
7170ca8d 361@@ -681,6 +683,7 @@ extern int file_extra_cnt;
a5e6228a
WD
362 extern int inc_recurse;
363 extern int uid_ndx;
364 extern int gid_ndx;
365+extern int crtimes_ndx;
366 extern int fileflags_ndx;
367 extern int acls_ndx;
368 extern int xattrs_ndx;
7170ca8d 369@@ -688,6 +691,7 @@ extern int xattrs_ndx;
a5e6228a
WD
370 #define FILE_STRUCT_LEN (offsetof(struct file_struct, basename))
371 #define EXTRA_LEN (sizeof (union file_extras))
372 #define PTR_EXTRA_CNT ((sizeof (char *) + EXTRA_LEN - 1) / EXTRA_LEN)
373+#define TIME_EXTRA_CNT ((SIZEOF_TIME_T + EXTRA_LEN - 1) / EXTRA_LEN)
374 #define DEV_EXTRA_CNT 2
375 #define DIRNODE_EXTRA_CNT 3
376 #define SUM_EXTRA_CNT ((MAX_DIGEST_LEN + EXTRA_LEN - 1) / EXTRA_LEN)
7170ca8d 377@@ -955,6 +959,7 @@ typedef struct {
a5e6228a
WD
378
379 typedef struct {
380 STRUCT_STAT st;
381+ time_t crtime;
382 #ifdef SUPPORT_ACLS
383 struct rsync_acl *acc_acl; /* access ACL */
384 struct rsync_acl *def_acl; /* default ACL */
385diff --git a/rsync.yo b/rsync.yo
386--- a/rsync.yo
387+++ b/rsync.yo
fc557362 388@@ -357,6 +357,7 @@ to the detailed description below for a complete description. verb(
a5e6228a
WD
389 -D same as --devices --specials
390 -t, --times preserve modification times
391 -O, --omit-dir-times omit directories from --times
f9df736a 392+ -N, --crtimes preserve create times (newness)
a5e6228a
WD
393 --super receiver attempts super-user activities
394 --fake-super store/recover privileged attrs using xattrs
395 -S, --sparse handle sparse files efficiently
7170ca8d 396@@ -1106,6 +1107,9 @@ it is preserving modification times (see bf(--times)). If NFS is sharing
a5e6228a
WD
397 the directories on the receiving side, it is a good idea to use bf(-O).
398 This option is inferred if you use bf(--backup) without bf(--backup-dir).
399
f9df736a 400+dit(bf(-N, --crtimes)) This tells rsync to set the create times (newness) of
a5e6228a
WD
401+the destination files to the same value as the source files.
402+
403 dit(bf(--super)) This tells the receiving side to attempt super-user
404 activities even if the receiving rsync wasn't run by the super-user. These
405 activities include: preserving users via the bf(--owner) option, preserving
7170ca8d 406@@ -1880,7 +1884,7 @@ with older versions of rsync, but that also turns on the output of other
a5e6228a
WD
407 verbose messages).
408
409 The "%i" escape has a cryptic output that is 11 letters long. The general
f9df736a
WD
410-format is like the string bf(YXcstpogfax), where bf(Y) is replaced by the
411+format is like the string bf(YXcstpogfaxn), where bf(Y) is replaced by the
a5e6228a
WD
412 type of update being done, bf(X) is replaced by the file-type, and the
413 other letters represent attributes that may be output if they are being
414 modified.
7170ca8d 415@@ -1939,6 +1943,8 @@ quote(itemization(
f9df736a 416 it() The bf(f) means that the fileflags information changed.
a5e6228a 417 it() The bf(a) means that the ACL information changed.
f9df736a
WD
418 it() The bf(x) means that the extended attribute information changed.
419+ it() A bf(n) means the create time (newness) is different and is being
420+ updated to the sender's value (requires bf(--crtimes)).
421 ))
422
423 One other output is possible: when deleting files, the "%i" will output
a5e6228a
WD
424diff --git a/syscall.c b/syscall.c
425--- a/syscall.c
426+++ b/syscall.c
f9df736a 427@@ -37,6 +37,11 @@ extern int force_change;
a5e6228a
WD
428 extern int preserve_perms;
429 extern int preserve_executability;
430
431+struct create_time {
432+ unsigned long length;
433+ struct timespec crtime;
434+};
435+
436 #define RETURN_ERROR_IF(x,e) \
437 do { \
438 if (x) { \
c0c7984e
WD
439@@ -394,3 +399,33 @@ OFF_T do_lseek(int fd, OFF_T offset, int whence)
440 return lseek(fd, offset, whence);
a5e6228a
WD
441 #endif
442 }
443+
444+time_t get_create_time(const char *path)
445+{
446+ static struct create_time attrBuf;
447+ struct attrlist attrList;
448+
449+ memset(&attrList, 0, sizeof attrList);
450+ attrList.bitmapcount = ATTR_BIT_MAP_COUNT;
451+ attrList.commonattr = ATTR_CMN_CRTIME;
452+ if (getattrlist(path, &attrList, &attrBuf, sizeof attrBuf, FSOPT_NOFOLLOW) < 0)
453+ return 0;
454+ return attrBuf.crtime.tv_sec;
455+}
456+
457+int set_create_time(const char *path, time_t crtime)
458+{
459+ struct attrlist attrList;
460+ struct timespec ts;
461+
462+ if (dry_run) return 0;
463+ RETURN_ERROR_IF_RO_OR_LO;
464+
465+ ts.tv_sec = crtime;
466+ ts.tv_nsec = 0;
467+
468+ memset(&attrList, 0, sizeof attrList);
469+ attrList.bitmapcount = ATTR_BIT_MAP_COUNT;
470+ attrList.commonattr = ATTR_CMN_CRTIME;
471+ return setattrlist(path, &attrList, &ts, sizeof ts, FSOPT_NOFOLLOW);
472+}
473diff --git a/testsuite/crtimes.test b/testsuite/crtimes.test
474new file mode 100644
475--- /dev/null
476+++ b/testsuite/crtimes.test
477@@ -0,0 +1,24 @@
478+#! /bin/sh
479+
480+# Test rsync copying create times
481+
482+. "$suitedir/rsync.fns"
483+
484+# Setting an older time via touch sets the create time to the mtime.
485+# Setting it to a newer time affects just the mtime.
486+
487+mkdir "$fromdir"
488+echo hiho "$fromdir/foo"
489+
490+touch -t 200101011111.11 "$fromdir"
491+touch -t 200202022222.22 "$fromdir"
492+
493+touch -t 200111111111.11 "$fromdir/foo"
494+touch -t 200212122222.22 "$fromdir/foo"
495+
496+TLS_ARGS=--crtimes
497+
498+checkit "$RSYNC -rtgvvv --crtimes \"$fromdir/\" \"$todir/\"" "$fromdir" "$todir"
499+
500+# The script would have aborted on error, so getting here means we've won.
501+exit 0
f9df736a
WD
502diff --git a/testsuite/rsync.fns b/testsuite/rsync.fns
503--- a/testsuite/rsync.fns
504+++ b/testsuite/rsync.fns
505@@ -24,9 +24,9 @@ todir="$tmpdir/to"
506 chkdir="$tmpdir/chk"
507
508 # For itemized output:
509-all_plus='+++++++++'
510-allspace=' '
511-dots='.....' # trailing dots after changes
512+all_plus='++++++++++'
513+allspace=' '
514+dots='......' # trailing dots after changes
515
516 # Berkley's nice.
517 PATH="$PATH:/usr/ucb"
a5e6228a
WD
518diff --git a/tls.c b/tls.c
519--- a/tls.c
520+++ b/tls.c
fc557362 521@@ -108,6 +108,8 @@ static int stat_xattr(const char *fname, STRUCT_STAT *fst)
a5e6228a
WD
522
523 #endif
524
525+static int display_crtimes = 0;
f9df736a 526+
a5e6228a
WD
527 static void failed(char const *what, char const *where)
528 {
529 fprintf(stderr, PROGRAM ": %s %s: %s\n",
fc557362 530@@ -115,16 +117,36 @@ static void failed(char const *what, char const *where)
a5e6228a
WD
531 exit(1);
532 }
533
534+static void storetime(char *dest, time_t t, size_t destsize)
535+{
536+ if (t) {
537+ struct tm *mt = gmtime(&t);
538+
539+ snprintf(dest, destsize,
540+ "%04d-%02d-%02d %02d:%02d:%02d ",
541+ (int)mt->tm_year + 1900,
542+ (int)mt->tm_mon + 1,
543+ (int)mt->tm_mday,
544+ (int)mt->tm_hour,
545+ (int)mt->tm_min,
546+ (int)mt->tm_sec);
547+ } else
548+ strlcpy(dest, " ", destsize);
549+}
550+
551 static void list_file(const char *fname)
552 {
553 STRUCT_STAT buf;
554+ time_t crtime = 0;
555 char permbuf[PERMSTRING_SIZE];
556- struct tm *mt;
557- char datebuf[50];
558+ char mtimebuf[50];
559+ char crtimebuf[50];
560 char linkbuf[4096];
561
562 if (do_lstat(fname, &buf) < 0)
563 failed("stat", fname);
564+ if (display_crtimes && (crtime = get_create_time(fname)) == 0)
565+ failed("get_create_time", fname);
566 #ifdef SUPPORT_XATTRS
567 if (am_root < 0)
568 stat_xattr(fname, &buf);
fc557362 569@@ -159,19 +181,11 @@ static void list_file(const char *fname)
a5e6228a
WD
570
571 permstring(permbuf, buf.st_mode);
572
573- if (buf.st_mtime) {
574- mt = gmtime(&buf.st_mtime);
575-
576- snprintf(datebuf, sizeof datebuf,
577- "%04d-%02d-%02d %02d:%02d:%02d",
578- (int)mt->tm_year + 1900,
579- (int)mt->tm_mon + 1,
580- (int)mt->tm_mday,
581- (int)mt->tm_hour,
582- (int)mt->tm_min,
583- (int)mt->tm_sec);
584- } else
585- strlcpy(datebuf, " ", sizeof datebuf);
586+ storetime(mtimebuf, buf.st_mtime, sizeof mtimebuf);
587+ if (display_crtimes)
588+ storetime(crtimebuf, crtime, sizeof crtimebuf);
589+ else
590+ crtimebuf[0] = '\0';
591
592 /* TODO: Perhaps escape special characters in fname? */
593
fc557362 594@@ -182,13 +196,14 @@ static void list_file(const char *fname)
a5e6228a 595 (long)minor(buf.st_rdev));
fc557362
WD
596 } else
597 printf("%15s", do_big_num(buf.st_size, 1, NULL));
a5e6228a
WD
598- printf(" %6ld.%-6ld %6ld %s %s%s\n",
599+ printf(" %6ld.%-6ld %6ld %s%s%s%s\n",
600 (long)buf.st_uid, (long)buf.st_gid, (long)buf.st_nlink,
601- datebuf, fname, linkbuf);
602+ mtimebuf, crtimebuf, fname, linkbuf);
603 }
604
605 static struct poptOption long_options[] = {
606 /* longName, shortName, argInfo, argPtr, value, descrip, argDesc */
607+ {"crtimes", 'N', POPT_ARG_NONE, &display_crtimes, 0, 0, 0},
e66d6d51
WD
608 {"link-times", 'l', POPT_ARG_NONE, &link_times, 0, 0, 0 },
609 {"link-owner", 'L', POPT_ARG_NONE, &link_owner, 0, 0, 0 },
a5e6228a 610 #ifdef SUPPORT_XATTRS
fc557362 611@@ -204,6 +219,7 @@ static void tls_usage(int ret)
a5e6228a
WD
612 fprintf(F,"usage: " PROGRAM " [OPTIONS] FILE ...\n");
613 fprintf(F,"Trivial file listing program for portably checking rsync\n");
614 fprintf(F,"\nOptions:\n");
f9df736a 615+ fprintf(F," -N, --crtimes display create times (newness)\n");
e66d6d51
WD
616 fprintf(F," -l, --link-times display the time on a symlink\n");
617 fprintf(F," -L, --link-owner display the owner+group on a symlink\n");
a5e6228a 618 #ifdef SUPPORT_XATTRS