Updated patches to work with the current trunk.
[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
c1ff70aa 11based-on: patch/master/fileflags
a5e6228a
WD
12diff --git a/compat.c b/compat.c
13--- a/compat.c
14+++ b/compat.c
c1ff70aa 15@@ -45,6 +45,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;
c1ff70aa 23@@ -63,7 +64,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 */
c1ff70aa 32@@ -140,6 +141,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
c1ff70aa 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;
c1ff70aa 52@@ -407,7 +408,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;
c1ff70aa 61@@ -518,6 +519,13 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
a5e6228a 62 modtime = file->modtime;
72e5645e
WD
63 if (NSEC_BUMP(file) && protocol_version >= 31)
64 xflags |= XMIT_MOD_NSEC;
a5e6228a
WD
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) {
c1ff70aa 75@@ -602,6 +610,8 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
a5e6228a 76 }
72e5645e
WD
77 if (xflags & XMIT_MOD_NSEC)
78 write_varint(f, F_MOD_NSEC(file));
a5e6228a
WD
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
c1ff70aa 84@@ -693,7 +703,7 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
72e5645e
WD
85
86 static struct file_struct *recv_file_entry(int f, struct file_list *flist, int xflags)
a5e6228a
WD
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;
c1ff70aa 93@@ -838,6 +848,19 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x
72e5645e
WD
94 modtime_nsec = read_varint(f);
95 else
96 modtime_nsec = 0;
a5e6228a
WD
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
c1ff70aa 113@@ -1008,6 +1031,8 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x
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
c1ff70aa 122@@ -1407,6 +1432,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;
c1ff70aa 142@@ -418,6 +419,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))
c1ff70aa 156@@ -461,6 +469,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 ;
c1ff70aa 169@@ -1028,6 +1042,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
c1ff70aa 177@@ -1043,10 +1058,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 191 if (missing_args == 2 && f->mode == 0) {
c1ff70aa 192@@ -1054,9 +1070,11 @@ static void list_file_entry(struct file_struct *f)
fc557362 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 205
c1ff70aa 206@@ -1147,6 +1165,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
5214a41b
WD
217@@ -43,6 +43,28 @@ free_xbuf(xbuf *xb)
218 memset(xb, 0, sizeof (xbuf));
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
5214a41b 249@@ -736,7 +736,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;
72e5645e 270@@ -708,6 +709,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");
72e5645e 278@@ -864,6 +866,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 },
c1ff70aa 288@@ -2367,6 +2372,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
5214a41b 300@@ -538,6 +538,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)
5214a41b 315@@ -688,7 +696,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 323 if (DEBUG_GTE(RECV, 1))
5214a41b 324@@ -719,7 +727,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
72e5645e 336@@ -62,7 +62,8 @@
a5e6228a 337 #define XMIT_HLINK_FIRST (1<<12) /* protocols 30 - now (HLINKED files only) */
fc557362 338 #define XMIT_IO_ERROR_ENDLIST (1<<12) /* protocols 31 - now (w/XMIT_EXTENDED_FLAGS) */
72e5645e
WD
339 #define XMIT_MOD_NSEC (1<<13) /* protocols 31 - now */
340-#define XMIT_SAME_FLAGS (1<<14) /* protocols ?? - now */
341+#define XMIT_CRTIME_EQ_MTIME (1<<14) /* protocols ?? - now */
342+#define XMIT_SAME_FLAGS (1<<15) /* protocols ?? - now */
a5e6228a
WD
343
344 /* These flags are used in the live flist data. */
72e5645e 345
5214a41b 346@@ -162,6 +163,7 @@
a5e6228a
WD
347 #define ATTRS_REPORT (1<<0)
348 #define ATTRS_SKIP_MTIME (1<<1)
f9df736a
WD
349 #define ATTRS_DELAY_IMMUTABLE (1<<2)
350+#define ATTRS_SKIP_CRTIME (1<<3)
a5e6228a
WD
351
352 #define FULL_FLUSH 1
353 #define NORMAL_FLUSH 0
5214a41b 354@@ -178,7 +180,7 @@
a5e6228a
WD
355 #define FNAMECMP_FUZZY 0x83
356
357 /* For use by the itemize_changes code */
358-#define ITEM_REPORT_ATIME (1<<0)
359+#define ITEM_REPORT_CRTIME (1<<0)
c0c7984e
WD
360 #define ITEM_REPORT_CHANGE (1<<1)
361 #define ITEM_REPORT_SIZE (1<<2) /* regular files only */
362 #define ITEM_REPORT_TIMEFAIL (1<<2) /* symlinks only */
5214a41b 363@@ -689,6 +691,7 @@ extern int file_extra_cnt;
a5e6228a
WD
364 extern int inc_recurse;
365 extern int uid_ndx;
366 extern int gid_ndx;
367+extern int crtimes_ndx;
368 extern int fileflags_ndx;
369 extern int acls_ndx;
370 extern int xattrs_ndx;
5214a41b 371@@ -696,6 +699,7 @@ extern int xattrs_ndx;
a5e6228a
WD
372 #define FILE_STRUCT_LEN (offsetof(struct file_struct, basename))
373 #define EXTRA_LEN (sizeof (union file_extras))
374 #define PTR_EXTRA_CNT ((sizeof (char *) + EXTRA_LEN - 1) / EXTRA_LEN)
375+#define TIME_EXTRA_CNT ((SIZEOF_TIME_T + EXTRA_LEN - 1) / EXTRA_LEN)
376 #define DEV_EXTRA_CNT 2
377 #define DIRNODE_EXTRA_CNT 3
378 #define SUM_EXTRA_CNT ((MAX_DIGEST_LEN + EXTRA_LEN - 1) / EXTRA_LEN)
5214a41b 379@@ -975,6 +979,7 @@ typedef struct {
a5e6228a
WD
380
381 typedef struct {
382 STRUCT_STAT st;
383+ time_t crtime;
384 #ifdef SUPPORT_ACLS
385 struct rsync_acl *acc_acl; /* access ACL */
386 struct rsync_acl *def_acl; /* default ACL */
387diff --git a/rsync.yo b/rsync.yo
388--- a/rsync.yo
389+++ b/rsync.yo
fc557362 390@@ -357,6 +357,7 @@ to the detailed description below for a complete description. verb(
a5e6228a
WD
391 -D same as --devices --specials
392 -t, --times preserve modification times
393 -O, --omit-dir-times omit directories from --times
f9df736a 394+ -N, --crtimes preserve create times (newness)
a5e6228a
WD
395 --super receiver attempts super-user activities
396 --fake-super store/recover privileged attrs using xattrs
397 -S, --sparse handle sparse files efficiently
7170ca8d 398@@ -1106,6 +1107,9 @@ it is preserving modification times (see bf(--times)). If NFS is sharing
a5e6228a
WD
399 the directories on the receiving side, it is a good idea to use bf(-O).
400 This option is inferred if you use bf(--backup) without bf(--backup-dir).
401
f9df736a 402+dit(bf(-N, --crtimes)) This tells rsync to set the create times (newness) of
a5e6228a
WD
403+the destination files to the same value as the source files.
404+
405 dit(bf(--super)) This tells the receiving side to attempt super-user
406 activities even if the receiving rsync wasn't run by the super-user. These
407 activities include: preserving users via the bf(--owner) option, preserving
c1ff70aa 408@@ -1918,7 +1922,7 @@ with older versions of rsync, but that also turns on the output of other
a5e6228a
WD
409 verbose messages).
410
411 The "%i" escape has a cryptic output that is 11 letters long. The general
f9df736a
WD
412-format is like the string bf(YXcstpogfax), where bf(Y) is replaced by the
413+format is like the string bf(YXcstpogfaxn), where bf(Y) is replaced by the
a5e6228a
WD
414 type of update being done, bf(X) is replaced by the file-type, and the
415 other letters represent attributes that may be output if they are being
416 modified.
c1ff70aa 417@@ -1977,6 +1981,8 @@ quote(itemization(
f9df736a 418 it() The bf(f) means that the fileflags information changed.
a5e6228a 419 it() The bf(a) means that the ACL information changed.
f9df736a
WD
420 it() The bf(x) means that the extended attribute information changed.
421+ it() A bf(n) means the create time (newness) is different and is being
422+ updated to the sender's value (requires bf(--crtimes)).
423 ))
424
425 One other output is possible: when deleting files, the "%i" will output
a5e6228a
WD
426diff --git a/syscall.c b/syscall.c
427--- a/syscall.c
428+++ b/syscall.c
72e5645e 429@@ -38,6 +38,11 @@ extern int force_change;
a5e6228a
WD
430 extern int preserve_perms;
431 extern int preserve_executability;
432
433+struct create_time {
434+ unsigned long length;
435+ struct timespec crtime;
436+};
437+
438 #define RETURN_ERROR_IF(x,e) \
439 do { \
440 if (x) { \
72e5645e 441@@ -437,3 +442,33 @@ OFF_T do_lseek(int fd, OFF_T offset, int whence)
c0c7984e 442 return lseek(fd, offset, whence);
a5e6228a
WD
443 #endif
444 }
445+
446+time_t get_create_time(const char *path)
447+{
448+ static struct create_time attrBuf;
449+ struct attrlist attrList;
450+
451+ memset(&attrList, 0, sizeof attrList);
452+ attrList.bitmapcount = ATTR_BIT_MAP_COUNT;
453+ attrList.commonattr = ATTR_CMN_CRTIME;
454+ if (getattrlist(path, &attrList, &attrBuf, sizeof attrBuf, FSOPT_NOFOLLOW) < 0)
455+ return 0;
456+ return attrBuf.crtime.tv_sec;
457+}
458+
459+int set_create_time(const char *path, time_t crtime)
460+{
461+ struct attrlist attrList;
462+ struct timespec ts;
463+
464+ if (dry_run) return 0;
465+ RETURN_ERROR_IF_RO_OR_LO;
466+
467+ ts.tv_sec = crtime;
468+ ts.tv_nsec = 0;
469+
470+ memset(&attrList, 0, sizeof attrList);
471+ attrList.bitmapcount = ATTR_BIT_MAP_COUNT;
472+ attrList.commonattr = ATTR_CMN_CRTIME;
473+ return setattrlist(path, &attrList, &ts, sizeof ts, FSOPT_NOFOLLOW);
474+}
475diff --git a/testsuite/crtimes.test b/testsuite/crtimes.test
476new file mode 100644
477--- /dev/null
478+++ b/testsuite/crtimes.test
479@@ -0,0 +1,24 @@
480+#! /bin/sh
481+
482+# Test rsync copying create times
483+
484+. "$suitedir/rsync.fns"
485+
486+# Setting an older time via touch sets the create time to the mtime.
487+# Setting it to a newer time affects just the mtime.
488+
489+mkdir "$fromdir"
490+echo hiho "$fromdir/foo"
491+
492+touch -t 200101011111.11 "$fromdir"
493+touch -t 200202022222.22 "$fromdir"
494+
495+touch -t 200111111111.11 "$fromdir/foo"
496+touch -t 200212122222.22 "$fromdir/foo"
497+
498+TLS_ARGS=--crtimes
499+
500+checkit "$RSYNC -rtgvvv --crtimes \"$fromdir/\" \"$todir/\"" "$fromdir" "$todir"
501+
502+# The script would have aborted on error, so getting here means we've won.
503+exit 0
f9df736a
WD
504diff --git a/testsuite/rsync.fns b/testsuite/rsync.fns
505--- a/testsuite/rsync.fns
506+++ b/testsuite/rsync.fns
5214a41b 507@@ -23,9 +23,9 @@ todir="$tmpdir/to"
f9df736a
WD
508 chkdir="$tmpdir/chk"
509
510 # For itemized output:
511-all_plus='+++++++++'
512-allspace=' '
513-dots='.....' # trailing dots after changes
514+all_plus='++++++++++'
515+allspace=' '
516+dots='......' # trailing dots after changes
517
518 # Berkley's nice.
519 PATH="$PATH:/usr/ucb"
a5e6228a
WD
520diff --git a/tls.c b/tls.c
521--- a/tls.c
522+++ b/tls.c
72e5645e 523@@ -118,6 +118,8 @@ static int stat_xattr(const char *fname, STRUCT_STAT *fst)
a5e6228a
WD
524
525 #endif
526
527+static int display_crtimes = 0;
f9df736a 528+
a5e6228a
WD
529 static void failed(char const *what, char const *where)
530 {
531 fprintf(stderr, PROGRAM ": %s %s: %s\n",
72e5645e 532@@ -125,16 +127,44 @@ static void failed(char const *what, char const *where)
a5e6228a
WD
533 exit(1);
534 }
535
72e5645e 536+static void storetime(char *dest, size_t destsize, time_t t, int nsecs)
a5e6228a
WD
537+{
538+ if (t) {
72e5645e 539+ int len;
a5e6228a
WD
540+ struct tm *mt = gmtime(&t);
541+
72e5645e
WD
542+ len = snprintf(dest, destsize,
543+ "%04d-%02d-%02d %02d:%02d:%02d",
a5e6228a
WD
544+ (int)mt->tm_year + 1900,
545+ (int)mt->tm_mon + 1,
546+ (int)mt->tm_mday,
547+ (int)mt->tm_hour,
548+ (int)mt->tm_min,
549+ (int)mt->tm_sec);
72e5645e
WD
550+ if (nsecs >= 0) {
551+ snprintf(datebuf + len, sizeof datebuf - len, ".%09d", nsecs);
552+ }
553+ } else {
554+ int has_nsecs = nsecs >= 0 ? 1 : 0;
555+ int len = MIN(19 + 9*nsec_times, (int)sizeof datebuf - 1);
556+ memset(datebuf, ' ', len);
557+ datebuf[len] = '\0';
558+ }
a5e6228a
WD
559+}
560+
561 static void list_file(const char *fname)
562 {
563 STRUCT_STAT buf;
564+ time_t crtime = 0;
565 char permbuf[PERMSTRING_SIZE];
566- struct tm *mt;
567- char datebuf[50];
568+ char mtimebuf[50];
569+ char crtimebuf[50];
570 char linkbuf[4096];
571
572 if (do_lstat(fname, &buf) < 0)
573 failed("stat", fname);
574+ if (display_crtimes && (crtime = get_create_time(fname)) == 0)
575+ failed("get_create_time", fname);
576 #ifdef SUPPORT_XATTRS
577 if (am_root < 0)
578 stat_xattr(fname, &buf);
72e5645e 579@@ -169,29 +199,11 @@ static void list_file(const char *fname)
a5e6228a
WD
580
581 permstring(permbuf, buf.st_mode);
582
583- if (buf.st_mtime) {
72e5645e 584- int len;
a5e6228a
WD
585- mt = gmtime(&buf.st_mtime);
586-
72e5645e 587- len = snprintf(datebuf, sizeof datebuf,
a5e6228a
WD
588- "%04d-%02d-%02d %02d:%02d:%02d",
589- (int)mt->tm_year + 1900,
590- (int)mt->tm_mon + 1,
591- (int)mt->tm_mday,
592- (int)mt->tm_hour,
593- (int)mt->tm_min,
594- (int)mt->tm_sec);
72e5645e
WD
595-#ifdef ST_MTIME_NSEC
596- if (nsec_times) {
597- snprintf(datebuf + len, sizeof datebuf - len,
598- ".%09d", (int)buf.ST_MTIME_NSEC);
599- }
600-#endif
601- } else {
602- int len = MIN(19 + 9*nsec_times, (int)sizeof datebuf - 1);
603- memset(datebuf, ' ', len);
604- datebuf[len] = '\0';
605- }
606+ storetime(mtimebuf, sizeof mtimebuf, buf.st_mtime, nsecs);
a5e6228a 607+ if (display_crtimes)
72e5645e 608+ storetime(crtimebuf, sizeof crtimebuf, crtime, -1);
a5e6228a
WD
609+ else
610+ crtimebuf[0] = '\0';
611
612 /* TODO: Perhaps escape special characters in fname? */
613
72e5645e 614@@ -202,13 +214,14 @@ static void list_file(const char *fname)
a5e6228a 615 (long)minor(buf.st_rdev));
fc557362
WD
616 } else
617 printf("%15s", do_big_num(buf.st_size, 1, NULL));
a5e6228a
WD
618- printf(" %6ld.%-6ld %6ld %s %s%s\n",
619+ printf(" %6ld.%-6ld %6ld %s%s%s%s\n",
620 (long)buf.st_uid, (long)buf.st_gid, (long)buf.st_nlink,
621- datebuf, fname, linkbuf);
622+ mtimebuf, crtimebuf, fname, linkbuf);
623 }
624
625 static struct poptOption long_options[] = {
626 /* longName, shortName, argInfo, argPtr, value, descrip, argDesc */
627+ {"crtimes", 'N', POPT_ARG_NONE, &display_crtimes, 0, 0, 0},
e66d6d51
WD
628 {"link-times", 'l', POPT_ARG_NONE, &link_times, 0, 0, 0 },
629 {"link-owner", 'L', POPT_ARG_NONE, &link_owner, 0, 0, 0 },
a5e6228a 630 #ifdef SUPPORT_XATTRS
72e5645e 631@@ -227,6 +240,7 @@ static void tls_usage(int ret)
a5e6228a
WD
632 fprintf(F,"usage: " PROGRAM " [OPTIONS] FILE ...\n");
633 fprintf(F,"Trivial file listing program for portably checking rsync\n");
634 fprintf(F,"\nOptions:\n");
f9df736a 635+ fprintf(F," -N, --crtimes display create times (newness)\n");
e66d6d51
WD
636 fprintf(F," -l, --link-times display the time on a symlink\n");
637 fprintf(F," -L, --link-owner display the owner+group on a symlink\n");
a5e6228a 638 #ifdef SUPPORT_XATTRS