Added the munge-links.diff patch.
[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/fileflags.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 @@ -45,6 +45,7 @@ extern int force_change;
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 @@ -62,7 +63,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  int receiver_symlink_times = 0; /* receiver can set the time on a symlink */
30  
31 @@ -136,6 +137,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 || (force_change && !am_sender))
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 @@ -56,6 +56,7 @@ extern int preserve_fileflags;
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 @@ -389,7 +390,7 @@ int change_pathname(struct file_struct *file, const char *dir, int dirlen)
52  
53  static void send_file_entry(int f, const char *fname, 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_FILEFLAGS
59         static uint32 fileflags;
60 @@ -474,6 +475,13 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
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 @@ -543,6 +551,8 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
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_FILEFLAGS
83 @@ -635,7 +645,7 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
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_FILEFLAGS
91         static uint32 fileflags;
92 @@ -770,6 +780,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 @@ -922,6 +945,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 @@ -1272,6 +1297,8 @@ struct file_struct *make_file(const char *fname, struct file_list *flist,
122                 F_OWNER(file) = st.st_uid;
123         if (gid_ndx) /* Check gid_ndx instead of preserve_gid for del support */
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 @@ -40,6 +41,7 @@ extern int preserve_xattrs;
142  extern int preserve_links;
143  extern int preserve_devices;
144  extern int preserve_specials;
145 +extern int preserve_fileflags;
146  extern int preserve_hard_links;
147  extern int preserve_executability;
148  extern int preserve_fileflags;
149 @@ -620,6 +622,13 @@ int unchanged_attrs(const char *fname, struct file_struct *file, stat_x *sxp)
150         if (gid_ndx && !(file->flags & FLAG_SKIP_GROUP) && sxp->st.st_gid != (gid_t)F_GROUP(file))
151                 return 0;
152  
153 +       if (crtimes_ndx) {
154 +               if (sxp->crtime == 0)
155 +                       sxp->crtime = get_create_time(fname);
156 +               if (cmp_time(sxp->crtime, f_crtime(file)) != 0)
157 +                       return 0;
158 +       }
159 +
160  #ifdef SUPPORT_ACLS
161         if (preserve_acls && !S_ISLNK(file->mode)) {
162                 if (!ACL_READY(*sxp))
163 @@ -663,6 +672,12 @@ void itemize(const char *fnamecmp, struct file_struct *file, int ndx, int statre
164                  : iflags & (ITEM_TRANSFER|ITEM_LOCAL_CHANGE) && !(iflags & ITEM_MATCHED)
165                   && (!(iflags & ITEM_XNAME_FOLLOWS) || *xname))
166                         iflags |= ITEM_REPORT_TIME;
167 +               if (crtimes_ndx) {
168 +                       if (sxp->crtime == 0)
169 +                               sxp->crtime = get_create_time(fnamecmp);
170 +                       if (cmp_time(sxp->crtime, f_crtime(file)) != 0)
171 +                               iflags |= ITEM_REPORT_CRTIME;
172 +               }
173  #if !defined HAVE_LCHMOD && !defined HAVE_SETATTRLIST
174                 if (S_ISLNK(file->mode)) {
175                         ;
176 @@ -1223,6 +1238,7 @@ static int try_dests_non(struct file_struct *file, char *fname, int ndx,
177  static void list_file_entry(struct file_struct *f)
178  {
179         char permbuf[PERMSTRING_SIZE];
180 +       time_t crtime = crtimes_ndx ? f_crtime(f) : 0;
181         double len;
182  
183         if (!F_IS_ACTIVE(f)) {
184 @@ -1237,14 +1253,16 @@ static void list_file_entry(struct file_struct *f)
185  
186  #ifdef SUPPORT_LINKS
187         if (preserve_links && S_ISLNK(f->mode)) {
188 -               rprintf(FINFO, "%s %11.0f %s %s -> %s\n",
189 +               rprintf(FINFO, "%s %11.0f %s %s %s -> %s\n",
190                         permbuf, len, timestring(f->modtime),
191 +                       crtimes_ndx ? timestring(crtime) : "",
192                         f_name(f, NULL), F_SYMLINK(f));
193         } else
194  #endif
195         {
196 -               rprintf(FINFO, "%s %11.0f %s %s\n",
197 +               rprintf(FINFO, "%s %11.0f %s %s %s\n",
198                         permbuf, len, timestring(f->modtime),
199 +                       crtimes_ndx ? timestring(crtime) : "",
200                         f_name(f, NULL));
201         }
202  }
203 @@ -1336,6 +1354,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
204                         return;
205                 }
206         }
207 +       sx.crtime = 0;
208  
209  #ifdef SUPPORT_ACLS
210         sx.acc_acl = sx.def_acl = NULL;
211 diff --git a/ifuncs.h b/ifuncs.h
212 --- a/ifuncs.h
213 +++ b/ifuncs.h
214 @@ -67,6 +67,28 @@ d_name(struct dirent *di)
215  #endif
216  }
217  
218 +static inline time_t
219 +f_crtime(struct file_struct *fp)
220 +{
221 +#if SIZEOF_TIME_T > 4
222 +       time_t crtime;
223 +       memcpy(&crtime, &REQ_EXTRA(fp, crtimes_ndx)->unum, SIZEOF_TIME_T);
224 +       return crtime;
225 +#else
226 +       return REQ_EXTRA(fp, crtimes_ndx)->unum;
227 +#endif
228 +}
229 +
230 +static inline void
231 +f_crtime_set(struct file_struct *fp, time_t crtime)
232 +{
233 +#if SIZEOF_TIME_T > 4
234 +       memcpy(&REQ_EXTRA(fp, crtimes_ndx)->unum, &crtime, SIZEOF_TIME_T);
235 +#else
236 +       REQ_EXTRA(fp, crtimes_ndx)->unum = (uint32)crtime;
237 +#endif
238 +}
239 +
240  static inline int
241  isDigit(const char *ptr)
242  {
243 diff --git a/log.c b/log.c
244 --- a/log.c
245 +++ b/log.c
246 @@ -659,7 +659,8 @@ static void log_formatted(enum logcode code, const char *format, const char *op,
247                         c[8] = !(iflags & ITEM_REPORT_FFLAGS) ? '.' : 'f';
248                         c[9] = !(iflags & ITEM_REPORT_ACL) ? '.' : 'a';
249                         c[10] = !(iflags & ITEM_REPORT_XATTR) ? '.' : 'x';
250 -                       c[11] = '\0';
251 +                       c[11] = !(iflags & ITEM_REPORT_CRTIME) ? '.' : 'n';
252 +                       c[12] = '\0';
253  
254                         if (iflags & (ITEM_IS_NEW|ITEM_MISSING_DATA)) {
255                                 char ch = iflags & ITEM_IS_NEW ? '+' : '?';
256 diff --git a/options.c b/options.c
257 --- a/options.c
258 +++ b/options.c
259 @@ -60,6 +60,7 @@ int preserve_specials = 0;
260  int preserve_uid = 0;
261  int preserve_gid = 0;
262  int preserve_times = 0;
263 +int preserve_crtimes = 0;
264  int update_only = 0;
265  int cvs_exclude = 0;
266  int dry_run = 0;
267 @@ -362,6 +363,7 @@ void usage(enum logcode F)
268    rprintf(F," -D                          same as --devices --specials\n");
269    rprintf(F," -t, --times                 preserve modification times\n");
270    rprintf(F," -O, --omit-dir-times        omit directories from --times\n");
271 +  rprintf(F," -N, --crtimes               preserve create times (newness)\n");
272    rprintf(F,"     --super                 receiver attempts super-user activities\n");
273  #ifdef SUPPORT_XATTRS
274    rprintf(F,"     --fake-super            store/recover privileged attrs using xattrs\n");
275 @@ -508,6 +510,9 @@ static struct poptOption long_options[] = {
276    {"times",           't', POPT_ARG_VAL,    &preserve_times, 2, 0, 0 },
277    {"no-times",         0,  POPT_ARG_VAL,    &preserve_times, 0, 0, 0 },
278    {"no-t",             0,  POPT_ARG_VAL,    &preserve_times, 0, 0, 0 },
279 +  {"crtimes",         'N', POPT_ARG_VAL,    &preserve_crtimes, 1, 0, 0 },
280 +  {"no-crtimes",       0,  POPT_ARG_VAL,    &preserve_crtimes, 0, 0, 0 },
281 +  {"no-N",             0,  POPT_ARG_VAL,    &preserve_crtimes, 0, 0, 0 },
282    {"omit-dir-times",  'O', POPT_ARG_VAL,    &omit_dir_times, 1, 0, 0 },
283    {"no-omit-dir-times",0,  POPT_ARG_VAL,    &omit_dir_times, 0, 0, 0 },
284    {"no-O",             0,  POPT_ARG_VAL,    &omit_dir_times, 0, 0, 0 },
285 @@ -1799,6 +1804,8 @@ void server_options(char **args, int *argc_p)
286                 argstr[x++] = 'D';
287         if (preserve_times)
288                 argstr[x++] = 't';
289 +       if (preserve_crtimes)
290 +               argstr[x++] = 'N';
291         if (preserve_perms)
292                 argstr[x++] = 'p';
293         else if (preserve_executability && am_sender)
294 diff --git a/rsync.c b/rsync.c
295 --- a/rsync.c
296 +++ b/rsync.c
297 @@ -472,6 +472,14 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
298                 else
299                         file->flags |= FLAG_TIME_FAILED;
300         }
301 +       if (crtimes_ndx && !(flags & ATTRS_SKIP_CRTIME)) {
302 +               time_t file_crtime = f_crtime(file);
303 +               if (sxp->crtime == 0)
304 +                       sxp->crtime = get_create_time(fname);
305 +               if (cmp_time(sxp->crtime, file_crtime) != 0
306 +                && set_create_time(fname, file_crtime) == 0)
307 +                       updated = 1;
308 +       }
309  
310         change_uid = am_root && uid_ndx && sxp->st.st_uid != (uid_t)F_OWNER(file);
311         change_gid = gid_ndx && !(file->flags & FLAG_SKIP_GROUP)
312 @@ -619,7 +627,7 @@ int finish_transfer(const char *fname, const char *fnametmp,
313         /* Change permissions before putting the file into place. */
314         set_file_attrs(fnametmp, file, NULL, fnamecmp,
315                        ATTRS_DELAY_IMMUTABLE
316 -                      | (ok_to_set_time ? 0 : ATTRS_SKIP_MTIME));
317 +                      | (ok_to_set_time ? 0 : ATTRS_SKIP_MTIME | ATTRS_SKIP_CRTIME));
318  
319         /* move tmp file over real file */
320         if (verbose > 2)
321 @@ -650,7 +658,7 @@ int finish_transfer(const char *fname, const char *fnametmp,
322  
323    do_set_file_attrs:
324         set_file_attrs(fnametmp, file, NULL, fnamecmp,
325 -                      ok_to_set_time ? 0 : ATTRS_SKIP_MTIME);
326 +                      ok_to_set_time ? 0 : ATTRS_SKIP_MTIME | ATTRS_SKIP_CRTIME);
327  
328         if (temp_copy_name) {
329                 if (do_rename(fnametmp, fname) < 0) {
330 diff --git a/rsync.h b/rsync.h
331 --- a/rsync.h
332 +++ b/rsync.h
333 @@ -60,6 +60,7 @@
334  #define XMIT_RDEV_MINOR_8_pre30 (1<<11)        /* protocols 28 - 29  */
335  #define XMIT_GROUP_NAME_FOLLOWS (1<<11) /* protocols 30 - now */
336  #define XMIT_HLINK_FIRST (1<<12)       /* protocols 30 - now (HLINKED files only) */
337 +#define XMIT_CRTIME_EQ_MTIME (1<<13)   /* protocols ?? - now */
338  #define XMIT_SAME_FLAGS (1<<14)                /* protocols ?? - now */
339  
340  /* These flags are used in the live flist data. */
341 @@ -156,6 +157,7 @@
342  #define ATTRS_REPORT           (1<<0)
343  #define ATTRS_SKIP_MTIME       (1<<1)
344  #define ATTRS_DELAY_IMMUTABLE  (1<<2)
345 +#define ATTRS_SKIP_CRTIME      (1<<3)
346  
347  #define FULL_FLUSH     1
348  #define NORMAL_FLUSH   0
349 @@ -172,7 +174,7 @@
350  #define FNAMECMP_FUZZY         0x83
351  
352  /* For use by the itemize_changes code */
353 -#define ITEM_REPORT_ATIME (1<<0)
354 +#define ITEM_REPORT_CRTIME (1<<0)
355  #define ITEM_REPORT_CHANGE (1<<1)
356  #define ITEM_REPORT_SIZE (1<<2)     /* regular files only */
357  #define ITEM_REPORT_TIMEFAIL (1<<2) /* symlinks only */
358 @@ -655,6 +657,7 @@ extern int file_extra_cnt;
359  extern int inc_recurse;
360  extern int uid_ndx;
361  extern int gid_ndx;
362 +extern int crtimes_ndx;
363  extern int fileflags_ndx;
364  extern int acls_ndx;
365  extern int xattrs_ndx;
366 @@ -662,6 +665,7 @@ extern int xattrs_ndx;
367  #define FILE_STRUCT_LEN (offsetof(struct file_struct, basename))
368  #define EXTRA_LEN (sizeof (union file_extras))
369  #define PTR_EXTRA_CNT ((sizeof (char *) + EXTRA_LEN - 1) / EXTRA_LEN)
370 +#define TIME_EXTRA_CNT ((SIZEOF_TIME_T + EXTRA_LEN - 1) / EXTRA_LEN)
371  #define DEV_EXTRA_CNT 2
372  #define DIRNODE_EXTRA_CNT 3
373  #define SUM_EXTRA_CNT ((MAX_DIGEST_LEN + EXTRA_LEN - 1) / EXTRA_LEN)
374 @@ -920,6 +924,7 @@ typedef struct {
375  
376  typedef struct {
377      STRUCT_STAT st;
378 +    time_t crtime;
379  #ifdef SUPPORT_ACLS
380      struct rsync_acl *acc_acl; /* access ACL */
381      struct rsync_acl *def_acl; /* default ACL */
382 diff --git a/rsync.yo b/rsync.yo
383 --- a/rsync.yo
384 +++ b/rsync.yo
385 @@ -350,6 +350,7 @@ to the detailed description below for a complete description.  verb(
386   -D                          same as --devices --specials
387   -t, --times                 preserve modification times
388   -O, --omit-dir-times        omit directories from --times
389 + -N, --crtimes               preserve create times (newness)
390       --super                 receiver attempts super-user activities
391       --fake-super            store/recover privileged attrs using xattrs
392   -S, --sparse                handle sparse files efficiently
393 @@ -1028,6 +1029,9 @@ it is preserving modification times (see bf(--times)).  If NFS is sharing
394  the directories on the receiving side, it is a good idea to use bf(-O).
395  This option is inferred if you use bf(--backup) without bf(--backup-dir).
396  
397 +dit(bf(-N, --crtimes)) This tells rsync to set the create times (newness) of
398 +the destination files to the same value as the source files.
399 +
400  dit(bf(--super)) This tells the receiving side to attempt super-user
401  activities even if the receiving rsync wasn't run by the super-user.  These
402  activities include: preserving users via the bf(--owner) option, preserving
403 @@ -1680,7 +1684,7 @@ with older versions of rsync, but that also turns on the output of other
404  verbose messages).
405  
406  The "%i" escape has a cryptic output that is 11 letters long.  The general
407 -format is like the string bf(YXcstpogfax), where bf(Y) is replaced by the
408 +format is like the string bf(YXcstpogfaxn), where bf(Y) is replaced by the
409  type of update being done, bf(X) is replaced by the file-type, and the
410  other letters represent attributes that may be output if they are being
411  modified.
412 @@ -1739,6 +1743,8 @@ quote(itemization(
413    it() The bf(f) means that the fileflags information changed.
414    it() The bf(a) means that the ACL information changed.
415    it() The bf(x) means that the extended attribute information changed.
416 +  it() A bf(n) means the create time (newness) is different and is being
417 +  updated to the sender's value (requires bf(--crtimes)).
418  ))
419  
420  One other output is possible:  when deleting files, the "%i" will output
421 diff --git a/syscall.c b/syscall.c
422 --- a/syscall.c
423 +++ b/syscall.c
424 @@ -37,6 +37,11 @@ extern int force_change;
425  extern int preserve_perms;
426  extern int preserve_executability;
427  
428 +struct create_time {
429 +       unsigned long length;
430 +       struct timespec crtime;
431 +};
432 +
433  #define RETURN_ERROR_IF(x,e) \
434         do { \
435                 if (x) { \
436 @@ -394,3 +399,33 @@ OFF_T do_lseek(int fd, OFF_T offset, int whence)
437         return lseek(fd, offset, whence);
438  #endif
439  }
440 +
441 +time_t get_create_time(const char *path)
442 +{
443 +       static struct create_time attrBuf;
444 +       struct attrlist attrList;
445 +
446 +       memset(&attrList, 0, sizeof attrList);
447 +       attrList.bitmapcount = ATTR_BIT_MAP_COUNT;
448 +       attrList.commonattr = ATTR_CMN_CRTIME;
449 +       if (getattrlist(path, &attrList, &attrBuf, sizeof attrBuf, FSOPT_NOFOLLOW) < 0)
450 +               return 0;
451 +       return attrBuf.crtime.tv_sec;
452 +}
453 +
454 +int set_create_time(const char *path, time_t crtime)
455 +{
456 +       struct attrlist attrList;
457 +       struct timespec ts;
458 +
459 +       if (dry_run) return 0;
460 +       RETURN_ERROR_IF_RO_OR_LO;
461 +
462 +       ts.tv_sec = crtime;
463 +       ts.tv_nsec = 0;
464 +
465 +       memset(&attrList, 0, sizeof attrList);
466 +       attrList.bitmapcount = ATTR_BIT_MAP_COUNT;
467 +       attrList.commonattr = ATTR_CMN_CRTIME;
468 +       return setattrlist(path, &attrList, &ts, sizeof ts, FSOPT_NOFOLLOW);
469 +}
470 diff --git a/testsuite/crtimes.test b/testsuite/crtimes.test
471 new file mode 100644
472 --- /dev/null
473 +++ b/testsuite/crtimes.test
474 @@ -0,0 +1,24 @@
475 +#! /bin/sh
476 +
477 +# Test rsync copying create times
478 +
479 +. "$suitedir/rsync.fns"
480 +
481 +# Setting an older time via touch sets the create time to the mtime.
482 +# Setting it to a newer time affects just the mtime.
483 +
484 +mkdir "$fromdir"
485 +echo hiho "$fromdir/foo"
486 +
487 +touch -t 200101011111.11 "$fromdir"
488 +touch -t 200202022222.22 "$fromdir"
489 +
490 +touch -t 200111111111.11 "$fromdir/foo"
491 +touch -t 200212122222.22 "$fromdir/foo"
492 +
493 +TLS_ARGS=--crtimes
494 +
495 +checkit "$RSYNC -rtgvvv --crtimes \"$fromdir/\" \"$todir/\"" "$fromdir" "$todir"
496 +
497 +# The script would have aborted on error, so getting here means we've won.
498 +exit 0
499 diff --git a/testsuite/rsync.fns b/testsuite/rsync.fns
500 --- a/testsuite/rsync.fns
501 +++ b/testsuite/rsync.fns
502 @@ -24,9 +24,9 @@ todir="$tmpdir/to"
503  chkdir="$tmpdir/chk"
504  
505  # For itemized output:
506 -all_plus='+++++++++'
507 -allspace='         '
508 -dots='.....' # trailing dots after changes
509 +all_plus='++++++++++'
510 +allspace='          '
511 +dots='......' # trailing dots after changes
512  
513  # Berkley's nice.
514  PATH="$PATH:/usr/ucb"
515 diff --git a/tls.c b/tls.c
516 --- a/tls.c
517 +++ b/tls.c
518 @@ -107,6 +107,8 @@ static int stat_xattr(const char *fname, STRUCT_STAT *fst)
519  
520  #endif
521  
522 +static int display_crtimes = 0;
523 +
524  static void failed(char const *what, char const *where)
525  {
526         fprintf(stderr, PROGRAM ": %s %s: %s\n",
527 @@ -114,16 +116,36 @@ static void failed(char const *what, char const *where)
528         exit(1);
529  }
530  
531 +static void storetime(char *dest, time_t t, size_t destsize)
532 +{
533 +       if (t) {
534 +               struct tm *mt = gmtime(&t);
535 +
536 +               snprintf(dest, destsize,
537 +                       "%04d-%02d-%02d %02d:%02d:%02d ",
538 +                       (int)mt->tm_year + 1900,
539 +                       (int)mt->tm_mon + 1,
540 +                       (int)mt->tm_mday,
541 +                       (int)mt->tm_hour,
542 +                       (int)mt->tm_min,
543 +                       (int)mt->tm_sec);
544 +       } else
545 +               strlcpy(dest, "                    ", destsize);
546 +}
547 +
548  static void list_file(const char *fname)
549  {
550         STRUCT_STAT buf;
551 +       time_t crtime = 0;
552         char permbuf[PERMSTRING_SIZE];
553 -       struct tm *mt;
554 -       char datebuf[50];
555 +       char mtimebuf[50];
556 +       char crtimebuf[50];
557         char linkbuf[4096];
558  
559         if (do_lstat(fname, &buf) < 0)
560                 failed("stat", fname);
561 +       if (display_crtimes && (crtime = get_create_time(fname)) == 0)
562 +               failed("get_create_time", fname);
563  #ifdef SUPPORT_XATTRS
564         if (am_root < 0)
565                 stat_xattr(fname, &buf);
566 @@ -158,19 +180,11 @@ static void list_file(const char *fname)
567  
568         permstring(permbuf, buf.st_mode);
569  
570 -       if (buf.st_mtime) {
571 -               mt = gmtime(&buf.st_mtime);
572 -
573 -               snprintf(datebuf, sizeof datebuf,
574 -                       "%04d-%02d-%02d %02d:%02d:%02d",
575 -                       (int)mt->tm_year + 1900,
576 -                       (int)mt->tm_mon + 1,
577 -                       (int)mt->tm_mday,
578 -                       (int)mt->tm_hour,
579 -                       (int)mt->tm_min,
580 -                       (int)mt->tm_sec);
581 -       } else
582 -               strlcpy(datebuf, "                   ", sizeof datebuf);
583 +       storetime(mtimebuf, buf.st_mtime, sizeof mtimebuf);
584 +       if (display_crtimes)
585 +               storetime(crtimebuf, crtime, sizeof crtimebuf);
586 +       else
587 +               crtimebuf[0] = '\0';
588  
589         /* TODO: Perhaps escape special characters in fname? */
590  
591 @@ -181,13 +195,14 @@ static void list_file(const char *fname)
592                     (long)minor(buf.st_rdev));
593         } else /* NB: use double for size since it might not fit in a long. */
594                 printf("%12.0f", (double)buf.st_size);
595 -       printf(" %6ld.%-6ld %6ld %s %s%s\n",
596 +       printf(" %6ld.%-6ld %6ld %s%s%s%s\n",
597                (long)buf.st_uid, (long)buf.st_gid, (long)buf.st_nlink,
598 -              datebuf, fname, linkbuf);
599 +              mtimebuf, crtimebuf, fname, linkbuf);
600  }
601  
602  static struct poptOption long_options[] = {
603    /* longName, shortName, argInfo, argPtr, value, descrip, argDesc */
604 +  {"crtimes",         'N', POPT_ARG_NONE,   &display_crtimes, 0, 0, 0},
605    {"link-times",      'l', POPT_ARG_NONE,   &link_times, 0, 0, 0 },
606    {"link-owner",      'L', POPT_ARG_NONE,   &link_owner, 0, 0, 0 },
607  #ifdef SUPPORT_XATTRS
608 @@ -203,6 +218,7 @@ static void tls_usage(int ret)
609    fprintf(F,"usage: " PROGRAM " [OPTIONS] FILE ...\n");
610    fprintf(F,"Trivial file listing program for portably checking rsync\n");
611    fprintf(F,"\nOptions:\n");
612 +  fprintf(F," -N, --crtimes               display create times (newness)\n");
613    fprintf(F," -l, --link-times            display the time on a symlink\n");
614    fprintf(F," -L, --link-owner            display the owner+group on a symlink\n");
615  #ifdef SUPPORT_XATTRS