Updated patches to work with the current trunk.
[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 index ef1882d..132ff90 100644
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)
41 diff --git a/flist.c b/flist.c
42 index e1d01be..3287447 100644
43 --- a/flist.c
44 +++ b/flist.c
45 @@ -56,6 +56,7 @@ extern int missing_args;
46  extern int uid_ndx;
47  extern int gid_ndx;
48  extern int eol_nulls;
49 +extern int crtimes_ndx;
50  extern int relative_paths;
51  extern int implied_dirs;
52  extern int file_extra_cnt;
53 @@ -397,7 +398,7 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
54  #endif
55                             int ndx, int first_ndx)
56  {
57 -       static time_t modtime;
58 +       static time_t modtime, crtime;
59         static mode_t mode;
60  #ifdef SUPPORT_FILEFLAGS
61         static uint32 fileflags;
62 @@ -496,6 +497,13 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
63                 xflags |= XMIT_SAME_TIME;
64         else
65                 modtime = file->modtime;
66 +       if (crtimes_ndx) {
67 +               time_t file_crtime = f_crtime(file);
68 +               if (file_crtime == modtime)
69 +                       xflags |= XMIT_CRTIME_EQ_MTIME;
70 +               else
71 +                       crtime = file_crtime;
72 +       }
73  
74  #ifdef SUPPORT_HARD_LINKS
75         if (tmp_dev != 0) {
76 @@ -578,6 +586,8 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
77                 else
78                         write_int(f, modtime);
79         }
80 +       if (crtimes_ndx && !(xflags & XMIT_CRTIME_EQ_MTIME))
81 +               write_varlong(f, crtime, 4);
82         if (!(xflags & XMIT_SAME_MODE))
83                 write_int(f, to_wire_mode(mode));
84  #ifdef SUPPORT_FILEFLAGS
85 @@ -670,7 +680,7 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
86  static struct file_struct *recv_file_entry(struct file_list *flist,
87                                            int xflags, int f)
88  {
89 -       static int64 modtime;
90 +       static int64 modtime, crtime;
91         static mode_t mode;
92  #ifdef SUPPORT_FILEFLAGS
93         static uint32 fileflags;
94 @@ -810,6 +820,19 @@ static struct file_struct *recv_file_entry(struct file_list *flist,
95                 } else
96                         modtime = read_int(f);
97         }
98 +       if (crtimes_ndx) {
99 +               if (!(xflags & XMIT_CRTIME_EQ_MTIME)) {
100 +                       crtime = read_varlong(f, 4);
101 +#if SIZEOF_TIME_T < SIZEOF_INT64
102 +                       if (!am_generator && (int64)(time_t)crtime != crtime) {
103 +                               rprintf(FERROR_XFER,
104 +                                   "Create time value of %s truncated on receiver.\n",
105 +                                   lastname);
106 +                       }
107 +#endif
108 +               } else
109 +                       crtime = modtime;
110 +       }
111         if (!(xflags & XMIT_SAME_MODE))
112                 mode = from_wire_mode(read_int(f));
113  
114 @@ -969,6 +992,8 @@ static struct file_struct *recv_file_entry(struct file_list *flist,
115                 F_GROUP(file) = gid;
116                 file->flags |= gid_flags;
117         }
118 +       if (crtimes_ndx)
119 +               f_crtime_set(file, (time_t)crtime);
120         if (unsort_ndx)
121                 F_NDX(file) = flist->used + flist->ndx_start;
122  
123 @@ -1358,6 +1383,8 @@ struct file_struct *make_file(const char *fname, struct file_list *flist,
124                 F_OWNER(file) = st.st_uid;
125         if (gid_ndx) /* Check gid_ndx instead of preserve_gid for del support */
126                 F_GROUP(file) = st.st_gid;
127 +       if (crtimes_ndx)
128 +               f_crtime_set(file, get_create_time(fname));
129  
130         if (basename != thisname)
131                 file->dirname = lastdir;
132 diff --git a/generator.c b/generator.c
133 index eee42e8..932f81c 100644
134 --- a/generator.c
135 +++ b/generator.c
136 @@ -40,6 +40,7 @@ extern int preserve_xattrs;
137  extern int preserve_links;
138  extern int preserve_devices;
139  extern int preserve_specials;
140 +extern int preserve_fileflags;
141  extern int preserve_hard_links;
142  extern int preserve_executability;
143  extern int preserve_fileflags;
144 @@ -419,6 +420,13 @@ int unchanged_attrs(const char *fname, struct file_struct *file, stat_x *sxp)
145         if (gid_ndx && !(file->flags & FLAG_SKIP_GROUP) && sxp->st.st_gid != (gid_t)F_GROUP(file))
146                 return 0;
147  
148 +       if (crtimes_ndx) {
149 +               if (sxp->crtime == 0)
150 +                       sxp->crtime = get_create_time(fname);
151 +               if (cmp_time(sxp->crtime, f_crtime(file)) != 0)
152 +                       return 0;
153 +       }
154 +
155  #ifdef SUPPORT_ACLS
156         if (preserve_acls && !S_ISLNK(file->mode)) {
157                 if (!ACL_READY(*sxp))
158 @@ -462,6 +470,12 @@ void itemize(const char *fnamecmp, struct file_struct *file, int ndx, int statre
159                  : iflags & (ITEM_TRANSFER|ITEM_LOCAL_CHANGE) && !(iflags & ITEM_MATCHED)
160                   && (!(iflags & ITEM_XNAME_FOLLOWS) || *xname))
161                         iflags |= ITEM_REPORT_TIME;
162 +               if (crtimes_ndx) {
163 +                       if (sxp->crtime == 0)
164 +                               sxp->crtime = get_create_time(fnamecmp);
165 +                       if (cmp_time(sxp->crtime, f_crtime(file)) != 0)
166 +                               iflags |= ITEM_REPORT_CRTIME;
167 +               }
168  #if !defined HAVE_LCHMOD && !defined HAVE_SETATTRLIST
169                 if (S_ISLNK(file->mode)) {
170                         ;
171 @@ -1022,6 +1036,7 @@ static int try_dests_non(struct file_struct *file, char *fname, int ndx,
172  static void list_file_entry(struct file_struct *f)
173  {
174         char permbuf[PERMSTRING_SIZE];
175 +       time_t crtime = crtimes_ndx ? f_crtime(f) : 0;
176         int64 len;
177         int colwidth = human_readable ? 14 : 11;
178  
179 @@ -1037,10 +1052,11 @@ static void list_file_entry(struct file_struct *f)
180  
181  #ifdef SUPPORT_LINKS
182         if (preserve_links && S_ISLNK(f->mode)) {
183 -               rprintf(FINFO, "%s %*s %s %s -> %s\n",
184 +               rprintf(FINFO, "%s %*s %s %s %s -> %s\n",
185                         permbuf, colwidth, comma_num(len),
186 -                       timestring(f->modtime), f_name(f, NULL),
187 -                       F_SYMLINK(f));
188 +                       timestring(f->modtime),
189 +                       crtimes_ndx ? timestring(crtime) : "",
190 +                       f_name(f, NULL), F_SYMLINK(f));
191         } else
192  #endif
193         if (missing_args == 2 && f->mode == 0) {
194 @@ -1048,9 +1064,11 @@ static void list_file_entry(struct file_struct *f)
195                         colwidth + 31, "*missing",
196                         f_name(f, NULL));
197         } else {
198 -               rprintf(FINFO, "%s %*s %s %s\n",
199 +               rprintf(FINFO, "%s %*s %s %s %s\n",
200                         permbuf, colwidth, comma_num(len),
201 -                       timestring(f->modtime), f_name(f, NULL));
202 +                       timestring(f->modtime),
203 +                       crtimes_ndx ? timestring(crtime) : "",
204 +                       f_name(f, NULL));
205         }
206  }
207  
208 @@ -1141,6 +1159,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
209                         return;
210                 }
211         }
212 +       sx.crtime = 0;
213  
214         init_stat_x(&sx);
215         if (dry_run > 1 || (dry_missing_dir && is_below(file, dry_missing_dir))) {
216 diff --git a/ifuncs.h b/ifuncs.h
217 index 8c128d5..4254dfb 100644
218 --- a/ifuncs.h
219 +++ b/ifuncs.h
220 @@ -35,6 +35,28 @@ realloc_xbuf(xbuf *xb, size_t sz)
221         xb->size = sz;
222  }
223  
224 +static inline time_t
225 +f_crtime(struct file_struct *fp)
226 +{
227 +#if SIZEOF_TIME_T > 4
228 +       time_t crtime;
229 +       memcpy(&crtime, &REQ_EXTRA(fp, crtimes_ndx)->unum, SIZEOF_TIME_T);
230 +       return crtime;
231 +#else
232 +       return REQ_EXTRA(fp, crtimes_ndx)->unum;
233 +#endif
234 +}
235 +
236 +static inline void
237 +f_crtime_set(struct file_struct *fp, time_t crtime)
238 +{
239 +#if SIZEOF_TIME_T > 4
240 +       memcpy(&REQ_EXTRA(fp, crtimes_ndx)->unum, &crtime, SIZEOF_TIME_T);
241 +#else
242 +       REQ_EXTRA(fp, crtimes_ndx)->unum = (uint32)crtime;
243 +#endif
244 +}
245 +
246  static inline int
247  to_wire_mode(mode_t mode)
248  {
249 diff --git a/log.c b/log.c
250 index 83948b1..7a1d9ce 100644
251 --- a/log.c
252 +++ b/log.c
253 @@ -718,7 +718,8 @@ static void log_formatted(enum logcode code, const char *format, const char *op,
254                         c[8] = !(iflags & ITEM_REPORT_FFLAGS) ? '.' : 'f';
255                         c[9] = !(iflags & ITEM_REPORT_ACL) ? '.' : 'a';
256                         c[10] = !(iflags & ITEM_REPORT_XATTR) ? '.' : 'x';
257 -                       c[11] = '\0';
258 +                       c[11] = !(iflags & ITEM_REPORT_CRTIME) ? '.' : 'n';
259 +                       c[12] = '\0';
260  
261                         if (iflags & (ITEM_IS_NEW|ITEM_MISSING_DATA)) {
262                                 char ch = iflags & ITEM_IS_NEW ? '+' : '?';
263 diff --git a/options.c b/options.c
264 index ae3d2d0..bb3bad3 100644
265 --- a/options.c
266 +++ b/options.c
267 @@ -60,6 +60,7 @@ int preserve_specials = 0;
268  int preserve_uid = 0;
269  int preserve_gid = 0;
270  int preserve_times = 0;
271 +int preserve_crtimes = 0;
272  int update_only = 0;
273  int cvs_exclude = 0;
274  int dry_run = 0;
275 @@ -707,6 +708,7 @@ void usage(enum logcode F)
276    rprintf(F," -D                          same as --devices --specials\n");
277    rprintf(F," -t, --times                 preserve modification times\n");
278    rprintf(F," -O, --omit-dir-times        omit directories from --times\n");
279 +  rprintf(F," -N, --crtimes               preserve create times (newness)\n");
280    rprintf(F,"     --super                 receiver attempts super-user activities\n");
281  #ifdef SUPPORT_XATTRS
282    rprintf(F,"     --fake-super            store/recover privileged attrs using xattrs\n");
283 @@ -863,6 +865,9 @@ static struct poptOption long_options[] = {
284    {"times",           't', POPT_ARG_VAL,    &preserve_times, 2, 0, 0 },
285    {"no-times",         0,  POPT_ARG_VAL,    &preserve_times, 0, 0, 0 },
286    {"no-t",             0,  POPT_ARG_VAL,    &preserve_times, 0, 0, 0 },
287 +  {"crtimes",         'N', POPT_ARG_VAL,    &preserve_crtimes, 1, 0, 0 },
288 +  {"no-crtimes",       0,  POPT_ARG_VAL,    &preserve_crtimes, 0, 0, 0 },
289 +  {"no-N",             0,  POPT_ARG_VAL,    &preserve_crtimes, 0, 0, 0 },
290    {"omit-dir-times",  'O', POPT_ARG_VAL,    &omit_dir_times, 1, 0, 0 },
291    {"no-omit-dir-times",0,  POPT_ARG_VAL,    &omit_dir_times, 0, 0, 0 },
292    {"no-O",             0,  POPT_ARG_VAL,    &omit_dir_times, 0, 0, 0 },
293 @@ -2315,6 +2320,8 @@ void server_options(char **args, int *argc_p)
294                 argstr[x++] = 'D';
295         if (preserve_times)
296                 argstr[x++] = 't';
297 +       if (preserve_crtimes)
298 +               argstr[x++] = 'N';
299         if (preserve_perms)
300                 argstr[x++] = 'p';
301         else if (preserve_executability && am_sender)
302 diff --git a/rsync.c b/rsync.c
303 index 3188535..ab4f8e4 100644
304 --- a/rsync.c
305 +++ b/rsync.c
306 @@ -471,6 +471,14 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
307                 else
308                         file->flags |= FLAG_TIME_FAILED;
309         }
310 +       if (crtimes_ndx && !(flags & ATTRS_SKIP_CRTIME)) {
311 +               time_t file_crtime = f_crtime(file);
312 +               if (sxp->crtime == 0)
313 +                       sxp->crtime = get_create_time(fname);
314 +               if (cmp_time(sxp->crtime, file_crtime) != 0
315 +                && set_create_time(fname, file_crtime) == 0)
316 +                       updated = 1;
317 +       }
318  
319         change_uid = am_root && uid_ndx && sxp->st.st_uid != (uid_t)F_OWNER(file);
320         change_gid = gid_ndx && !(file->flags & FLAG_SKIP_GROUP)
321 @@ -619,7 +627,7 @@ int finish_transfer(const char *fname, const char *fnametmp,
322         /* Change permissions before putting the file into place. */
323         set_file_attrs(fnametmp, file, NULL, fnamecmp,
324                        ATTRS_DELAY_IMMUTABLE
325 -                      | (ok_to_set_time ? 0 : ATTRS_SKIP_MTIME));
326 +                      | (ok_to_set_time ? 0 : ATTRS_SKIP_MTIME | ATTRS_SKIP_CRTIME));
327  
328         /* move tmp file over real file */
329         if (DEBUG_GTE(RECV, 1))
330 @@ -650,7 +658,7 @@ int finish_transfer(const char *fname, const char *fnametmp,
331  
332    do_set_file_attrs:
333         set_file_attrs(fnametmp, file, NULL, fnamecmp,
334 -                      ok_to_set_time ? 0 : ATTRS_SKIP_MTIME);
335 +                      ok_to_set_time ? 0 : ATTRS_SKIP_MTIME | ATTRS_SKIP_CRTIME);
336  
337         if (temp_copy_name) {
338                 if (do_rename(fnametmp, fname) < 0) {
339 diff --git a/rsync.h b/rsync.h
340 index 16820fd..b3973c8 100644
341 --- a/rsync.h
342 +++ b/rsync.h
343 @@ -61,6 +61,7 @@
344  #define XMIT_GROUP_NAME_FOLLOWS (1<<11) /* protocols 30 - now */
345  #define XMIT_HLINK_FIRST (1<<12)       /* protocols 30 - now (HLINKED files only) */
346  #define XMIT_IO_ERROR_ENDLIST (1<<12)  /* protocols 31 - now (w/XMIT_EXTENDED_FLAGS) */
347 +#define XMIT_CRTIME_EQ_MTIME (1<<13)   /* protocols ?? - now */
348  #define XMIT_SAME_FLAGS (1<<14)                /* protocols ?? - now */
349  
350  /* These flags are used in the live flist data. */
351 @@ -157,6 +158,7 @@
352  #define ATTRS_REPORT           (1<<0)
353  #define ATTRS_SKIP_MTIME       (1<<1)
354  #define ATTRS_DELAY_IMMUTABLE  (1<<2)
355 +#define ATTRS_SKIP_CRTIME      (1<<3)
356  
357  #define FULL_FLUSH     1
358  #define NORMAL_FLUSH   0
359 @@ -173,7 +175,7 @@
360  #define FNAMECMP_FUZZY         0x83
361  
362  /* For use by the itemize_changes code */
363 -#define ITEM_REPORT_ATIME (1<<0)
364 +#define ITEM_REPORT_CRTIME (1<<0)
365  #define ITEM_REPORT_CHANGE (1<<1)
366  #define ITEM_REPORT_SIZE (1<<2)     /* regular files only */
367  #define ITEM_REPORT_TIMEFAIL (1<<2) /* symlinks only */
368 @@ -677,6 +679,7 @@ extern int file_extra_cnt;
369  extern int inc_recurse;
370  extern int uid_ndx;
371  extern int gid_ndx;
372 +extern int crtimes_ndx;
373  extern int fileflags_ndx;
374  extern int acls_ndx;
375  extern int xattrs_ndx;
376 @@ -684,6 +687,7 @@ extern int xattrs_ndx;
377  #define FILE_STRUCT_LEN (offsetof(struct file_struct, basename))
378  #define EXTRA_LEN (sizeof (union file_extras))
379  #define PTR_EXTRA_CNT ((sizeof (char *) + EXTRA_LEN - 1) / EXTRA_LEN)
380 +#define TIME_EXTRA_CNT ((SIZEOF_TIME_T + EXTRA_LEN - 1) / EXTRA_LEN)
381  #define DEV_EXTRA_CNT 2
382  #define DIRNODE_EXTRA_CNT 3
383  #define SUM_EXTRA_CNT ((MAX_DIGEST_LEN + EXTRA_LEN - 1) / EXTRA_LEN)
384 @@ -954,6 +958,7 @@ typedef struct {
385  
386  typedef struct {
387      STRUCT_STAT st;
388 +    time_t crtime;
389  #ifdef SUPPORT_ACLS
390      struct rsync_acl *acc_acl; /* access ACL */
391      struct rsync_acl *def_acl; /* default ACL */
392 diff --git a/rsync.yo b/rsync.yo
393 index 7b41d5f..5670c46 100644
394 --- a/rsync.yo
395 +++ b/rsync.yo
396 @@ -357,6 +357,7 @@ to the detailed description below for a complete description.  verb(
397   -D                          same as --devices --specials
398   -t, --times                 preserve modification times
399   -O, --omit-dir-times        omit directories from --times
400 + -N, --crtimes               preserve create times (newness)
401       --super                 receiver attempts super-user activities
402       --fake-super            store/recover privileged attrs using xattrs
403   -S, --sparse                handle sparse files efficiently
404 @@ -1099,6 +1100,9 @@ it is preserving modification times (see bf(--times)).  If NFS is sharing
405  the directories on the receiving side, it is a good idea to use bf(-O).
406  This option is inferred if you use bf(--backup) without bf(--backup-dir).
407  
408 +dit(bf(-N, --crtimes)) This tells rsync to set the create times (newness) of
409 +the destination files to the same value as the source files.
410 +
411  dit(bf(--super)) This tells the receiving side to attempt super-user
412  activities even if the receiving rsync wasn't run by the super-user.  These
413  activities include: preserving users via the bf(--owner) option, preserving
414 @@ -1861,7 +1865,7 @@ with older versions of rsync, but that also turns on the output of other
415  verbose messages).
416  
417  The "%i" escape has a cryptic output that is 11 letters long.  The general
418 -format is like the string bf(YXcstpogfax), where bf(Y) is replaced by the
419 +format is like the string bf(YXcstpogfaxn), where bf(Y) is replaced by the
420  type of update being done, bf(X) is replaced by the file-type, and the
421  other letters represent attributes that may be output if they are being
422  modified.
423 @@ -1920,6 +1924,8 @@ quote(itemization(
424    it() The bf(f) means that the fileflags information changed.
425    it() The bf(a) means that the ACL information changed.
426    it() The bf(x) means that the extended attribute information changed.
427 +  it() A bf(n) means the create time (newness) is different and is being
428 +  updated to the sender's value (requires bf(--crtimes)).
429  ))
430  
431  One other output is possible:  when deleting files, the "%i" will output
432 diff --git a/syscall.c b/syscall.c
433 index 45604b1..c463997 100644
434 --- a/syscall.c
435 +++ b/syscall.c
436 @@ -37,6 +37,11 @@ extern int force_change;
437  extern int preserve_perms;
438  extern int preserve_executability;
439  
440 +struct create_time {
441 +       unsigned long length;
442 +       struct timespec crtime;
443 +};
444 +
445  #define RETURN_ERROR_IF(x,e) \
446         do { \
447                 if (x) { \
448 @@ -394,3 +399,33 @@ OFF_T do_lseek(int fd, OFF_T offset, int whence)
449         return lseek(fd, offset, whence);
450  #endif
451  }
452 +
453 +time_t get_create_time(const char *path)
454 +{
455 +       static struct create_time attrBuf;
456 +       struct attrlist attrList;
457 +
458 +       memset(&attrList, 0, sizeof attrList);
459 +       attrList.bitmapcount = ATTR_BIT_MAP_COUNT;
460 +       attrList.commonattr = ATTR_CMN_CRTIME;
461 +       if (getattrlist(path, &attrList, &attrBuf, sizeof attrBuf, FSOPT_NOFOLLOW) < 0)
462 +               return 0;
463 +       return attrBuf.crtime.tv_sec;
464 +}
465 +
466 +int set_create_time(const char *path, time_t crtime)
467 +{
468 +       struct attrlist attrList;
469 +       struct timespec ts;
470 +
471 +       if (dry_run) return 0;
472 +       RETURN_ERROR_IF_RO_OR_LO;
473 +
474 +       ts.tv_sec = crtime;
475 +       ts.tv_nsec = 0;
476 +
477 +       memset(&attrList, 0, sizeof attrList);
478 +       attrList.bitmapcount = ATTR_BIT_MAP_COUNT;
479 +       attrList.commonattr = ATTR_CMN_CRTIME;
480 +       return setattrlist(path, &attrList, &ts, sizeof ts, FSOPT_NOFOLLOW);
481 +}
482 diff --git a/testsuite/crtimes.test b/testsuite/crtimes.test
483 new file mode 100644
484 index 0000000..b904e16
485 --- /dev/null
486 +++ b/testsuite/crtimes.test
487 @@ -0,0 +1,24 @@
488 +#! /bin/sh
489 +
490 +# Test rsync copying create times
491 +
492 +. "$suitedir/rsync.fns"
493 +
494 +# Setting an older time via touch sets the create time to the mtime.
495 +# Setting it to a newer time affects just the mtime.
496 +
497 +mkdir "$fromdir"
498 +echo hiho "$fromdir/foo"
499 +
500 +touch -t 200101011111.11 "$fromdir"
501 +touch -t 200202022222.22 "$fromdir"
502 +
503 +touch -t 200111111111.11 "$fromdir/foo"
504 +touch -t 200212122222.22 "$fromdir/foo"
505 +
506 +TLS_ARGS=--crtimes
507 +
508 +checkit "$RSYNC -rtgvvv --crtimes \"$fromdir/\" \"$todir/\"" "$fromdir" "$todir"
509 +
510 +# The script would have aborted on error, so getting here means we've won.
511 +exit 0
512 diff --git a/testsuite/rsync.fns b/testsuite/rsync.fns
513 index b26aee3..4faaf93 100644
514 --- a/testsuite/rsync.fns
515 +++ b/testsuite/rsync.fns
516 @@ -24,9 +24,9 @@ todir="$tmpdir/to"
517  chkdir="$tmpdir/chk"
518  
519  # For itemized output:
520 -all_plus='+++++++++'
521 -allspace='         '
522 -dots='.....' # trailing dots after changes
523 +all_plus='++++++++++'
524 +allspace='          '
525 +dots='......' # trailing dots after changes
526  
527  # Berkley's nice.
528  PATH="$PATH:/usr/ucb"
529 diff --git a/tls.c b/tls.c
530 index 8cc5748..6da4df9 100644
531 --- a/tls.c
532 +++ b/tls.c
533 @@ -108,6 +108,8 @@ static int stat_xattr(const char *fname, STRUCT_STAT *fst)
534  
535  #endif
536  
537 +static int display_crtimes = 0;
538 +
539  static void failed(char const *what, char const *where)
540  {
541         fprintf(stderr, PROGRAM ": %s %s: %s\n",
542 @@ -115,16 +117,36 @@ static void failed(char const *what, char const *where)
543         exit(1);
544  }
545  
546 +static void storetime(char *dest, time_t t, size_t destsize)
547 +{
548 +       if (t) {
549 +               struct tm *mt = gmtime(&t);
550 +
551 +               snprintf(dest, destsize,
552 +                       "%04d-%02d-%02d %02d:%02d:%02d ",
553 +                       (int)mt->tm_year + 1900,
554 +                       (int)mt->tm_mon + 1,
555 +                       (int)mt->tm_mday,
556 +                       (int)mt->tm_hour,
557 +                       (int)mt->tm_min,
558 +                       (int)mt->tm_sec);
559 +       } else
560 +               strlcpy(dest, "                    ", destsize);
561 +}
562 +
563  static void list_file(const char *fname)
564  {
565         STRUCT_STAT buf;
566 +       time_t crtime = 0;
567         char permbuf[PERMSTRING_SIZE];
568 -       struct tm *mt;
569 -       char datebuf[50];
570 +       char mtimebuf[50];
571 +       char crtimebuf[50];
572         char linkbuf[4096];
573  
574         if (do_lstat(fname, &buf) < 0)
575                 failed("stat", fname);
576 +       if (display_crtimes && (crtime = get_create_time(fname)) == 0)
577 +               failed("get_create_time", fname);
578  #ifdef SUPPORT_XATTRS
579         if (am_root < 0)
580                 stat_xattr(fname, &buf);
581 @@ -159,19 +181,11 @@ static void list_file(const char *fname)
582  
583         permstring(permbuf, buf.st_mode);
584  
585 -       if (buf.st_mtime) {
586 -               mt = gmtime(&buf.st_mtime);
587 -
588 -               snprintf(datebuf, sizeof datebuf,
589 -                       "%04d-%02d-%02d %02d:%02d:%02d",
590 -                       (int)mt->tm_year + 1900,
591 -                       (int)mt->tm_mon + 1,
592 -                       (int)mt->tm_mday,
593 -                       (int)mt->tm_hour,
594 -                       (int)mt->tm_min,
595 -                       (int)mt->tm_sec);
596 -       } else
597 -               strlcpy(datebuf, "                   ", sizeof datebuf);
598 +       storetime(mtimebuf, buf.st_mtime, sizeof mtimebuf);
599 +       if (display_crtimes)
600 +               storetime(crtimebuf, crtime, sizeof crtimebuf);
601 +       else
602 +               crtimebuf[0] = '\0';
603  
604         /* TODO: Perhaps escape special characters in fname? */
605  
606 @@ -182,13 +196,14 @@ static void list_file(const char *fname)
607                     (long)minor(buf.st_rdev));
608         } else
609                 printf("%15s", do_big_num(buf.st_size, 1, NULL));
610 -       printf(" %6ld.%-6ld %6ld %s %s%s\n",
611 +       printf(" %6ld.%-6ld %6ld %s%s%s%s\n",
612                (long)buf.st_uid, (long)buf.st_gid, (long)buf.st_nlink,
613 -              datebuf, fname, linkbuf);
614 +              mtimebuf, crtimebuf, fname, linkbuf);
615  }
616  
617  static struct poptOption long_options[] = {
618    /* longName, shortName, argInfo, argPtr, value, descrip, argDesc */
619 +  {"crtimes",         'N', POPT_ARG_NONE,   &display_crtimes, 0, 0, 0},
620    {"link-times",      'l', POPT_ARG_NONE,   &link_times, 0, 0, 0 },
621    {"link-owner",      'L', POPT_ARG_NONE,   &link_owner, 0, 0, 0 },
622  #ifdef SUPPORT_XATTRS
623 @@ -204,6 +219,7 @@ static void tls_usage(int ret)
624    fprintf(F,"usage: " PROGRAM " [OPTIONS] FILE ...\n");
625    fprintf(F,"Trivial file listing program for portably checking rsync\n");
626    fprintf(F,"\nOptions:\n");
627 +  fprintf(F," -N, --crtimes               display create times (newness)\n");
628    fprintf(F," -l, --link-times            display the time on a symlink\n");
629    fprintf(F," -L, --link-owner            display the owner+group on a symlink\n");
630  #ifdef SUPPORT_XATTRS