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