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