Fix alignment issue on 64-bit. Solution from Steve Ortiz.
[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 */
4c85a55c 363@@ -693,6 +695,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;
4c85a55c 371@@ -700,6 +703,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)
4c85a55c 379@@ -979,6 +983,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
4c85a55c 398@@ -1110,6 +1111,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
4c85a55c 408@@ -1922,7 +1926,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.
4c85a55c 417@@ -1981,6 +1985,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
4c85a55c 429@@ -38,6 +38,14 @@ extern int force_change;
a5e6228a
WD
430 extern int preserve_perms;
431 extern int preserve_executability;
432
4c85a55c
WD
433+#pragma pack(push)
434+#pragma pack(4)
a5e6228a 435+struct create_time {
4120ade3 436+ uint32 length;
a5e6228a
WD
437+ struct timespec crtime;
438+};
4c85a55c 439+#pragma pack(pop)
a5e6228a
WD
440+
441 #define RETURN_ERROR_IF(x,e) \
442 do { \
443 if (x) { \
4c85a55c 444@@ -439,3 +447,33 @@ OFF_T do_lseek(int fd, OFF_T offset, int whence)
c0c7984e 445 return lseek(fd, offset, whence);
a5e6228a
WD
446 #endif
447 }
448+
449+time_t get_create_time(const char *path)
450+{
451+ static struct create_time attrBuf;
452+ struct attrlist attrList;
453+
454+ memset(&attrList, 0, sizeof attrList);
455+ attrList.bitmapcount = ATTR_BIT_MAP_COUNT;
456+ attrList.commonattr = ATTR_CMN_CRTIME;
457+ if (getattrlist(path, &attrList, &attrBuf, sizeof attrBuf, FSOPT_NOFOLLOW) < 0)
458+ return 0;
459+ return attrBuf.crtime.tv_sec;
460+}
461+
462+int set_create_time(const char *path, time_t crtime)
463+{
464+ struct attrlist attrList;
465+ struct timespec ts;
466+
467+ if (dry_run) return 0;
468+ RETURN_ERROR_IF_RO_OR_LO;
469+
470+ ts.tv_sec = crtime;
471+ ts.tv_nsec = 0;
472+
473+ memset(&attrList, 0, sizeof attrList);
474+ attrList.bitmapcount = ATTR_BIT_MAP_COUNT;
475+ attrList.commonattr = ATTR_CMN_CRTIME;
476+ return setattrlist(path, &attrList, &ts, sizeof ts, FSOPT_NOFOLLOW);
477+}
478diff --git a/testsuite/crtimes.test b/testsuite/crtimes.test
479new file mode 100644
480--- /dev/null
481+++ b/testsuite/crtimes.test
482@@ -0,0 +1,24 @@
483+#! /bin/sh
484+
485+# Test rsync copying create times
486+
487+. "$suitedir/rsync.fns"
488+
489+# Setting an older time via touch sets the create time to the mtime.
490+# Setting it to a newer time affects just the mtime.
491+
492+mkdir "$fromdir"
493+echo hiho "$fromdir/foo"
494+
495+touch -t 200101011111.11 "$fromdir"
496+touch -t 200202022222.22 "$fromdir"
497+
498+touch -t 200111111111.11 "$fromdir/foo"
499+touch -t 200212122222.22 "$fromdir/foo"
500+
501+TLS_ARGS=--crtimes
502+
503+checkit "$RSYNC -rtgvvv --crtimes \"$fromdir/\" \"$todir/\"" "$fromdir" "$todir"
504+
505+# The script would have aborted on error, so getting here means we've won.
506+exit 0
f9df736a
WD
507diff --git a/testsuite/rsync.fns b/testsuite/rsync.fns
508--- a/testsuite/rsync.fns
509+++ b/testsuite/rsync.fns
5214a41b 510@@ -23,9 +23,9 @@ todir="$tmpdir/to"
f9df736a
WD
511 chkdir="$tmpdir/chk"
512
513 # For itemized output:
514-all_plus='+++++++++'
515-allspace=' '
516-dots='.....' # trailing dots after changes
517+all_plus='++++++++++'
518+allspace=' '
519+dots='......' # trailing dots after changes
520
521 # Berkley's nice.
522 PATH="$PATH:/usr/ucb"
a5e6228a
WD
523diff --git a/tls.c b/tls.c
524--- a/tls.c
525+++ b/tls.c
72e5645e 526@@ -118,6 +118,8 @@ static int stat_xattr(const char *fname, STRUCT_STAT *fst)
a5e6228a
WD
527
528 #endif
529
530+static int display_crtimes = 0;
f9df736a 531+
a5e6228a
WD
532 static void failed(char const *what, char const *where)
533 {
534 fprintf(stderr, PROGRAM ": %s %s: %s\n",
72e5645e 535@@ -125,16 +127,44 @@ static void failed(char const *what, char const *where)
a5e6228a
WD
536 exit(1);
537 }
538
72e5645e 539+static void storetime(char *dest, size_t destsize, time_t t, int nsecs)
a5e6228a
WD
540+{
541+ if (t) {
72e5645e 542+ int len;
a5e6228a
WD
543+ struct tm *mt = gmtime(&t);
544+
72e5645e
WD
545+ len = snprintf(dest, destsize,
546+ "%04d-%02d-%02d %02d:%02d:%02d",
a5e6228a
WD
547+ (int)mt->tm_year + 1900,
548+ (int)mt->tm_mon + 1,
549+ (int)mt->tm_mday,
550+ (int)mt->tm_hour,
551+ (int)mt->tm_min,
552+ (int)mt->tm_sec);
72e5645e
WD
553+ if (nsecs >= 0) {
554+ snprintf(datebuf + len, sizeof datebuf - len, ".%09d", nsecs);
555+ }
556+ } else {
557+ int has_nsecs = nsecs >= 0 ? 1 : 0;
558+ int len = MIN(19 + 9*nsec_times, (int)sizeof datebuf - 1);
559+ memset(datebuf, ' ', len);
560+ datebuf[len] = '\0';
561+ }
a5e6228a
WD
562+}
563+
564 static void list_file(const char *fname)
565 {
566 STRUCT_STAT buf;
567+ time_t crtime = 0;
568 char permbuf[PERMSTRING_SIZE];
569- struct tm *mt;
570- char datebuf[50];
571+ char mtimebuf[50];
572+ char crtimebuf[50];
573 char linkbuf[4096];
574
575 if (do_lstat(fname, &buf) < 0)
576 failed("stat", fname);
577+ if (display_crtimes && (crtime = get_create_time(fname)) == 0)
578+ failed("get_create_time", fname);
579 #ifdef SUPPORT_XATTRS
580 if (am_root < 0)
581 stat_xattr(fname, &buf);
72e5645e 582@@ -169,29 +199,11 @@ static void list_file(const char *fname)
a5e6228a
WD
583
584 permstring(permbuf, buf.st_mode);
585
586- if (buf.st_mtime) {
72e5645e 587- int len;
a5e6228a
WD
588- mt = gmtime(&buf.st_mtime);
589-
72e5645e 590- len = snprintf(datebuf, sizeof datebuf,
a5e6228a
WD
591- "%04d-%02d-%02d %02d:%02d:%02d",
592- (int)mt->tm_year + 1900,
593- (int)mt->tm_mon + 1,
594- (int)mt->tm_mday,
595- (int)mt->tm_hour,
596- (int)mt->tm_min,
597- (int)mt->tm_sec);
72e5645e
WD
598-#ifdef ST_MTIME_NSEC
599- if (nsec_times) {
600- snprintf(datebuf + len, sizeof datebuf - len,
601- ".%09d", (int)buf.ST_MTIME_NSEC);
602- }
603-#endif
604- } else {
605- int len = MIN(19 + 9*nsec_times, (int)sizeof datebuf - 1);
606- memset(datebuf, ' ', len);
607- datebuf[len] = '\0';
608- }
609+ storetime(mtimebuf, sizeof mtimebuf, buf.st_mtime, nsecs);
a5e6228a 610+ if (display_crtimes)
72e5645e 611+ storetime(crtimebuf, sizeof crtimebuf, crtime, -1);
a5e6228a
WD
612+ else
613+ crtimebuf[0] = '\0';
614
615 /* TODO: Perhaps escape special characters in fname? */
616
72e5645e 617@@ -202,13 +214,14 @@ static void list_file(const char *fname)
a5e6228a 618 (long)minor(buf.st_rdev));
fc557362
WD
619 } else
620 printf("%15s", do_big_num(buf.st_size, 1, NULL));
a5e6228a
WD
621- printf(" %6ld.%-6ld %6ld %s %s%s\n",
622+ printf(" %6ld.%-6ld %6ld %s%s%s%s\n",
623 (long)buf.st_uid, (long)buf.st_gid, (long)buf.st_nlink,
624- datebuf, fname, linkbuf);
625+ mtimebuf, crtimebuf, fname, linkbuf);
626 }
627
628 static struct poptOption long_options[] = {
629 /* longName, shortName, argInfo, argPtr, value, descrip, argDesc */
630+ {"crtimes", 'N', POPT_ARG_NONE, &display_crtimes, 0, 0, 0},
e66d6d51
WD
631 {"link-times", 'l', POPT_ARG_NONE, &link_times, 0, 0, 0 },
632 {"link-owner", 'L', POPT_ARG_NONE, &link_owner, 0, 0, 0 },
a5e6228a 633 #ifdef SUPPORT_XATTRS
72e5645e 634@@ -227,6 +240,7 @@ static void tls_usage(int ret)
a5e6228a
WD
635 fprintf(F,"usage: " PROGRAM " [OPTIONS] FILE ...\n");
636 fprintf(F,"Trivial file listing program for portably checking rsync\n");
637 fprintf(F,"\nOptions:\n");
f9df736a 638+ fprintf(F," -N, --crtimes display create times (newness)\n");
e66d6d51
WD
639 fprintf(F," -l, --link-times display the time on a symlink\n");
640 fprintf(F," -L, --link-owner display the owner+group on a symlink\n");
a5e6228a 641 #ifdef SUPPORT_XATTRS