Updated patches to work with the current trunk.
[rsync/rsync-patches.git] / crtimes.diff
... / ...
CommitLineData
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
6 patch -p1 <patches/fileflags.diff
7 patch -p1 <patches/crtimes.diff
8 ./configure (optional if already run)
9 make
10
11based-on: patch/fileflags
12diff --git a/compat.c b/compat.c
13--- a/compat.c
14+++ b/compat.c
15@@ -44,6 +44,7 @@ extern int force_change;
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;
23@@ -62,7 +63,7 @@ extern char *iconv_opt;
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
30 int receiver_symlink_times = 0; /* receiver can set the time on a symlink */
31 int sender_symlink_iconv = 0; /* sender should convert symlink content */
32@@ -138,6 +139,8 @@ void setup_protocol(int f_out,int f_in)
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);
38 if (preserve_fileflags || (force_change && !am_sender))
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
44@@ -55,6 +55,7 @@ extern int missing_args;
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;
52@@ -405,7 +406,7 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
53 #endif
54 int ndx, int first_ndx)
55 {
56- static time_t modtime;
57+ static time_t modtime, crtime;
58 static mode_t mode;
59 #ifdef SUPPORT_FILEFLAGS
60 static uint32 fileflags;
61@@ -516,6 +517,13 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
62 modtime = file->modtime;
63 if (NSEC_BUMP(file) && protocol_version >= 31)
64 xflags |= XMIT_MOD_NSEC;
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) {
75@@ -600,6 +608,8 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
76 }
77 if (xflags & XMIT_MOD_NSEC)
78 write_varint(f, F_MOD_NSEC(file));
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));
83 #ifdef SUPPORT_FILEFLAGS
84@@ -691,7 +701,7 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
85
86 static struct file_struct *recv_file_entry(int f, struct file_list *flist, int xflags)
87 {
88- static int64 modtime;
89+ static int64 modtime, crtime;
90 static mode_t mode;
91 #ifdef SUPPORT_FILEFLAGS
92 static uint32 fileflags;
93@@ -836,6 +846,19 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x
94 modtime_nsec = read_varint(f);
95 else
96 modtime_nsec = 0;
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
113@@ -1006,6 +1029,8 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x
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
122@@ -1405,6 +1430,8 @@ struct file_struct *make_file(const char *fname, struct file_list *flist,
123 F_OWNER(file) = st.st_uid;
124 if (gid_ndx) /* Check gid_ndx instead of preserve_gid for del support */
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
134@@ -40,6 +40,7 @@ extern int preserve_xattrs;
135 extern int preserve_links;
136 extern int preserve_devices;
137 extern int preserve_specials;
138+extern int preserve_fileflags;
139 extern int preserve_hard_links;
140 extern int preserve_executability;
141 extern int preserve_fileflags;
142@@ -419,6 +420,13 @@ int unchanged_attrs(const char *fname, struct file_struct *file, stat_x *sxp)
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))
156@@ -462,6 +470,12 @@ void itemize(const char *fnamecmp, struct file_struct *file, int ndx, int statre
157 : iflags & (ITEM_TRANSFER|ITEM_LOCAL_CHANGE) && !(iflags & ITEM_MATCHED)
158 && (!(iflags & ITEM_XNAME_FOLLOWS) || *xname))
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 ;
169@@ -1029,6 +1043,7 @@ static int try_dests_non(struct file_struct *file, char *fname, int ndx,
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;
174 int64 len;
175 int colwidth = human_readable ? 14 : 11;
176
177@@ -1044,10 +1059,11 @@ static void list_file_entry(struct file_struct *f)
178
179 #ifdef SUPPORT_LINKS
180 if (preserve_links && S_ISLNK(f->mode)) {
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),
187+ crtimes_ndx ? timestring(crtime) : "",
188+ f_name(f, NULL), F_SYMLINK(f));
189 } else
190 #endif
191 if (missing_args == 2 && f->mode == 0) {
192@@ -1055,9 +1071,11 @@ static void list_file_entry(struct file_struct *f)
193 colwidth + 31, "*missing",
194 f_name(f, NULL));
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));
203 }
204 }
205
206@@ -1148,6 +1166,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
207 return;
208 }
209 }
210+ sx.crtime = 0;
211
212 init_stat_x(&sx);
213 if (dry_run > 1 || (dry_missing_dir && is_below(file, dry_missing_dir))) {
214diff --git a/ifuncs.h b/ifuncs.h
215--- a/ifuncs.h
216+++ b/ifuncs.h
217@@ -43,6 +43,28 @@ free_xbuf(xbuf *xb)
218 memset(xb, 0, sizeof (xbuf));
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
244 to_wire_mode(mode_t mode)
245 {
246diff --git a/log.c b/log.c
247--- a/log.c
248+++ b/log.c
249@@ -736,7 +736,8 @@ static void log_formatted(enum logcode code, const char *format, const char *op,
250 c[8] = !(iflags & ITEM_REPORT_FFLAGS) ? '.' : 'f';
251 c[9] = !(iflags & ITEM_REPORT_ACL) ? '.' : 'a';
252 c[10] = !(iflags & ITEM_REPORT_XATTR) ? '.' : 'x';
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 ? '+' : '?';
259diff --git a/options.c b/options.c
260--- a/options.c
261+++ b/options.c
262@@ -60,6 +60,7 @@ int preserve_specials = 0;
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;
270@@ -708,6 +709,7 @@ void usage(enum logcode F)
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");
274+ rprintf(F," -N, --crtimes preserve create times (newness)\n");
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");
278@@ -864,6 +866,9 @@ static struct poptOption long_options[] = {
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 },
288@@ -2352,6 +2357,8 @@ void server_options(char **args, int *argc_p)
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
300@@ -538,6 +538,14 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
301 else
302 file->flags |= FLAG_TIME_FAILED;
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)
315@@ -688,7 +696,7 @@ int finish_transfer(const char *fname, const char *fnametmp,
316 /* Change permissions before putting the file into place. */
317 set_file_attrs(fnametmp, file, NULL, fnamecmp,
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));
321
322 /* move tmp file over real file */
323 if (DEBUG_GTE(RECV, 1))
324@@ -719,7 +727,7 @@ int finish_transfer(const char *fname, const char *fnametmp,
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
336@@ -62,7 +62,8 @@
337 #define XMIT_HLINK_FIRST (1<<12) /* protocols 30 - now (HLINKED files only) */
338 #define XMIT_IO_ERROR_ENDLIST (1<<12) /* protocols 31 - now (w/XMIT_EXTENDED_FLAGS) */
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 */
343
344 /* These flags are used in the live flist data. */
345
346@@ -162,6 +163,7 @@
347 #define ATTRS_REPORT (1<<0)
348 #define ATTRS_SKIP_MTIME (1<<1)
349 #define ATTRS_DELAY_IMMUTABLE (1<<2)
350+#define ATTRS_SKIP_CRTIME (1<<3)
351
352 #define FULL_FLUSH 1
353 #define NORMAL_FLUSH 0
354@@ -178,7 +180,7 @@
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)
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 */
363@@ -689,6 +691,7 @@ extern int file_extra_cnt;
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;
371@@ -696,6 +699,7 @@ extern int xattrs_ndx;
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)
379@@ -975,6 +979,7 @@ typedef struct {
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
390@@ -357,6 +357,7 @@ to the detailed description below for a complete description. verb(
391 -D same as --devices --specials
392 -t, --times preserve modification times
393 -O, --omit-dir-times omit directories from --times
394+ -N, --crtimes preserve create times (newness)
395 --super receiver attempts super-user activities
396 --fake-super store/recover privileged attrs using xattrs
397 -S, --sparse handle sparse files efficiently
398@@ -1106,6 +1107,9 @@ it is preserving modification times (see bf(--times)). If NFS is sharing
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
402+dit(bf(-N, --crtimes)) This tells rsync to set the create times (newness) of
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
408@@ -1904,7 +1908,7 @@ with older versions of rsync, but that also turns on the output of other
409 verbose messages).
410
411 The "%i" escape has a cryptic output that is 11 letters long. The general
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
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.
417@@ -1963,6 +1967,8 @@ quote(itemization(
418 it() The bf(f) means that the fileflags information changed.
419 it() The bf(a) means that the ACL information changed.
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
426diff --git a/syscall.c b/syscall.c
427--- a/syscall.c
428+++ b/syscall.c
429@@ -38,6 +38,11 @@ extern int force_change;
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) { \
441@@ -437,3 +442,33 @@ OFF_T do_lseek(int fd, OFF_T offset, int whence)
442 return lseek(fd, offset, whence);
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
504diff --git a/testsuite/rsync.fns b/testsuite/rsync.fns
505--- a/testsuite/rsync.fns
506+++ b/testsuite/rsync.fns
507@@ -23,9 +23,9 @@ todir="$tmpdir/to"
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"
520diff --git a/tls.c b/tls.c
521--- a/tls.c
522+++ b/tls.c
523@@ -118,6 +118,8 @@ static int stat_xattr(const char *fname, STRUCT_STAT *fst)
524
525 #endif
526
527+static int display_crtimes = 0;
528+
529 static void failed(char const *what, char const *where)
530 {
531 fprintf(stderr, PROGRAM ": %s %s: %s\n",
532@@ -125,16 +127,44 @@ static void failed(char const *what, char const *where)
533 exit(1);
534 }
535
536+static void storetime(char *dest, size_t destsize, time_t t, int nsecs)
537+{
538+ if (t) {
539+ int len;
540+ struct tm *mt = gmtime(&t);
541+
542+ len = snprintf(dest, destsize,
543+ "%04d-%02d-%02d %02d:%02d:%02d",
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);
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+ }
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);
579@@ -169,29 +199,11 @@ static void list_file(const char *fname)
580
581 permstring(permbuf, buf.st_mode);
582
583- if (buf.st_mtime) {
584- int len;
585- mt = gmtime(&buf.st_mtime);
586-
587- len = snprintf(datebuf, sizeof datebuf,
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);
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);
607+ if (display_crtimes)
608+ storetime(crtimebuf, sizeof crtimebuf, crtime, -1);
609+ else
610+ crtimebuf[0] = '\0';
611
612 /* TODO: Perhaps escape special characters in fname? */
613
614@@ -202,13 +214,14 @@ static void list_file(const char *fname)
615 (long)minor(buf.st_rdev));
616 } else
617 printf("%15s", do_big_num(buf.st_size, 1, NULL));
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},
628 {"link-times", 'l', POPT_ARG_NONE, &link_times, 0, 0, 0 },
629 {"link-owner", 'L', POPT_ARG_NONE, &link_owner, 0, 0, 0 },
630 #ifdef SUPPORT_XATTRS
631@@ -227,6 +240,7 @@ static void tls_usage(int ret)
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");
635+ fprintf(F," -N, --crtimes display create times (newness)\n");
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");
638 #ifdef SUPPORT_XATTRS