0c06f6960ce3cc143b7f27fa6d518936bdd0e4bf
[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/master/fileflags
12 diff --git a/compat.c b/compat.c
13 --- a/compat.c
14 +++ b/compat.c
15 @@ -51,6 +51,7 @@ extern int protect_args;
16  extern int preserve_uid;
17  extern int preserve_gid;
18  extern int preserve_atimes;
19 +extern int preserve_crtimes;
20  extern int preserve_acls;
21  extern int preserve_xattrs;
22  extern int preserve_fileflags;
23 @@ -70,7 +71,7 @@ extern char *iconv_opt;
24  #endif
25  
26  /* These index values are for the file-list's extra-attribute array. */
27 -int pathname_ndx, depth_ndx, atimes_ndx, uid_ndx, gid_ndx, fileflags_ndx, acls_ndx, xattrs_ndx, unsort_ndx;
28 +int pathname_ndx, depth_ndx, atimes_ndx, crtimes_ndx, uid_ndx, gid_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 @@ -150,6 +151,8 @@ void setup_protocol(int f_out,int f_in)
33          * aligned for direct int64-pointer memory access. */
34         if (preserve_atimes)
35                 atimes_ndx = (file_extra_cnt += EXTRA64_CNT);
36 +       if (preserve_crtimes)
37 +               crtimes_ndx = (file_extra_cnt += EXTRA64_CNT);
38         if (am_sender) /* This is most likely in the in64 union as well. */
39                 pathname_ndx = (file_extra_cnt += PTR_EXTRA_CNT);
40         else
41 @@ -303,6 +306,10 @@ void setup_protocol(int f_out,int f_in)
42                 want_xattr_optim = protocol_version >= 31 && !(compat_flags & CF_AVOID_XATTR_OPTIM);
43                 proper_seed_order = compat_flags & CF_CHKSUM_SEED_FIX ? 1 : 0;
44                 xfer_flags_as_varint = compat_flags & CF_VARINT_FLIST_FLAGS ? 1 : 0;
45 +               if (!xfer_flags_as_varint && preserve_crtimes) {
46 +                       fprintf(stderr, "Both rsync versions must be at least 3.2.0 for --crtimes.\n");
47 +                       exit_cleanup(RERR_PROTOCOL);
48 +               }
49                 if (!xfer_flags_as_varint && preserve_fileflags) {
50                         fprintf(stderr, "Both rsync versions must be at least 3.2.0 for --fileflags.\n");
51                         exit_cleanup(RERR_PROTOCOL);
52 diff --git a/flist.c b/flist.c
53 --- a/flist.c
54 +++ b/flist.c
55 @@ -57,6 +57,7 @@ extern int delete_during;
56  extern int missing_args;
57  extern int eol_nulls;
58  extern int atimes_ndx;
59 +extern int crtimes_ndx;
60  extern int relative_paths;
61  extern int implied_dirs;
62  extern int ignore_perishable;
63 @@ -383,6 +384,9 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
64                             int ndx, int first_ndx)
65  {
66         static time_t modtime, atime;
67 +#ifdef SUPPORT_CRTIMES
68 +       static time_t crtime;
69 +#endif
70         static mode_t mode;
71  #ifdef SUPPORT_FILEFLAGS
72         static uint32 fileflags;
73 @@ -499,6 +503,13 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
74                 else
75                         atime = F_ATIME(file);
76         }
77 +#ifdef SUPPORT_CRTIMES
78 +       if (crtimes_ndx) {
79 +               crtime = F_CRTIME(file);
80 +               if (crtime == modtime)
81 +                       xflags |= XMIT_CRTIME_EQ_MTIME;
82 +       }
83 +#endif
84  
85  #ifdef SUPPORT_HARD_LINKS
86         if (tmp_dev != -1) {
87 @@ -586,6 +597,10 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
88         }
89         if (xflags & XMIT_MOD_NSEC)
90                 write_varint(f, F_MOD_NSEC(file));
91 +#ifdef SUPPORT_CRTIMES
92 +       if (crtimes_ndx && !(xflags & XMIT_CRTIME_EQ_MTIME))
93 +               write_varlong(f, crtime, 4);
94 +#endif
95         if (!(xflags & XMIT_SAME_MODE))
96                 write_int(f, to_wire_mode(mode));
97  #ifdef SUPPORT_FILEFLAGS
98 @@ -682,6 +697,9 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
99  static struct file_struct *recv_file_entry(int f, struct file_list *flist, int xflags)
100  {
101         static int64 modtime, atime;
102 +#ifdef SUPPORT_CRTIMES
103 +       static time_t crtime;
104 +#endif
105         static mode_t mode;
106  #ifdef SUPPORT_FILEFLAGS
107         static uint32 fileflags;
108 @@ -795,6 +813,10 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x
109                         mode = first->mode;
110                         if (atimes_ndx && !S_ISDIR(mode))
111                                 atime = F_ATIME(first);
112 +#ifdef SUPPORT_CRTIMES
113 +                       if (crtimes_ndx)
114 +                               crtime = F_CRTIME(first);
115 +#endif
116  #ifdef SUPPORT_FILEFLAGS
117                         if (preserve_fileflags)
118                                 fileflags = F_FFLAGS(first);
119 @@ -835,6 +857,21 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x
120                 modtime_nsec = read_varint(f);
121         else
122                 modtime_nsec = 0;
123 +#ifdef SUPPORT_CRTIMES
124 +       if (crtimes_ndx) {
125 +               if (xflags & XMIT_CRTIME_EQ_MTIME)
126 +                       crtime = modtime;
127 +               else
128 +                       crtime = read_varlong(f, 4);
129 +#if SIZEOF_TIME_T < SIZEOF_INT64
130 +               if (!am_generator && (int64)(time_t)crtime != crtime) {
131 +                       rprintf(FERROR_XFER,
132 +                               "Create time value of %s truncated on receiver.\n",
133 +                               lastname);
134 +               }
135 +#endif
136 +       }
137 +#endif
138         if (!(xflags & XMIT_SAME_MODE))
139                 mode = from_wire_mode(read_int(f));
140         if (atimes_ndx && !S_ISDIR(mode) && !(xflags & XMIT_SAME_ATIME)) {
141 @@ -1024,6 +1061,10 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x
142         }
143         if (atimes_ndx && !S_ISDIR(mode))
144                 F_ATIME(file) = atime;
145 +#ifdef SUPPORT_CRTIMES
146 +       if (crtimes_ndx)
147 +               F_CRTIME(file) = crtime;
148 +#endif
149         if (unsort_ndx)
150                 F_NDX(file) = flist->used + flist->ndx_start;
151  
152 @@ -1427,6 +1468,10 @@ struct file_struct *make_file(const char *fname, struct file_list *flist,
153                 file->flags |= FLAG_OWNED_BY_US;
154         if (atimes_ndx && !S_ISDIR(file->mode))
155                 F_ATIME(file) = st.st_atime;
156 +#ifdef SUPPORT_CRTIMES
157 +       if (crtimes_ndx)
158 +               F_CRTIME(file) = get_create_time(fname);
159 +#endif
160  
161         if (basename != thisname)
162                 file->dirname = lastdir;
163 diff --git a/generator.c b/generator.c
164 --- a/generator.c
165 +++ b/generator.c
166 @@ -41,6 +41,7 @@ extern int preserve_links;
167  extern int preserve_devices;
168  extern int write_devices;
169  extern int preserve_specials;
170 +extern int preserve_fileflags;
171  extern int preserve_hard_links;
172  extern int preserve_executability;
173  extern int preserve_fileflags;
174 @@ -395,6 +396,19 @@ static inline int time_diff(STRUCT_STAT *stp, struct file_struct *file)
175  #endif
176  }
177  
178 +static inline int all_time_diff(stat_x *sxp, struct file_struct *file, UNUSED(const char *fname))
179 +{
180 +       int diff = time_diff(&sxp->st, file);
181 +#ifdef SUPPORT_CRTIMES
182 +       if (!diff && crtimes_ndx) {
183 +               if (sxp->crtime == 0)
184 +                       sxp->crtime = get_create_time(fname);
185 +               diff = cmp_time(sxp->crtime, 0, F_CRTIME(file), 0);
186 +       }
187 +#endif
188 +       return diff;
189 +}
190 +
191  static inline int perms_differ(struct file_struct *file, stat_x *sxp)
192  {
193         if (preserve_perms)
194 @@ -449,7 +463,7 @@ int unchanged_attrs(const char *fname, struct file_struct *file, stat_x *sxp)
195  {
196         if (S_ISLNK(file->mode)) {
197  #ifdef CAN_SET_SYMLINK_TIMES
198 -               if (preserve_times & PRESERVE_LINK_TIMES && time_diff(&sxp->st, file))
199 +               if (preserve_times & PRESERVE_LINK_TIMES && all_time_diff(sxp, file, fname))
200                         return 0;
201  #endif
202  #ifdef CAN_CHMOD_SYMLINK
203 @@ -469,7 +483,7 @@ int unchanged_attrs(const char *fname, struct file_struct *file, stat_x *sxp)
204                         return 0;
205  #endif
206         } else {
207 -               if (preserve_times && time_diff(&sxp->st, file))
208 +               if (preserve_times && all_time_diff(sxp, file, fname))
209                         return 0;
210                 if (perms_differ(file, sxp))
211                         return 0;
212 @@ -515,6 +529,14 @@ void itemize(const char *fnamecmp, struct file_struct *file, int ndx, int statre
213                 if (atimes_ndx && !S_ISDIR(file->mode) && !S_ISLNK(file->mode)
214                  && cmp_time(F_ATIME(file), 0, sxp->st.st_atime, 0) != 0)
215                         iflags |= ITEM_REPORT_ATIME;
216 +#ifdef SUPPORT_CRTIMES
217 +               if (crtimes_ndx) {
218 +                       if (sxp->crtime == 0)
219 +                               sxp->crtime = get_create_time(fnamecmp);
220 +                       if (cmp_time(sxp->crtime, 0L, F_CRTIME(file), 0L) != 0)
221 +                               iflags |= ITEM_REPORT_CRTIME;
222 +               }
223 +#endif
224  #if !defined HAVE_LCHMOD && !defined HAVE_SETATTRLIST
225                 if (S_ISLNK(file->mode)) {
226                         ;
227 @@ -1140,6 +1162,7 @@ static void list_file_entry(struct file_struct *f)
228         int size_width = human_readable ? 14 : 11;
229         int mtime_width = 1 + strlen(mtime_str);
230         int atime_width = atimes_ndx ? mtime_width : 0;
231 +       int crtime_width = crtimes_ndx ? mtime_width : 0;
232  
233         if (!F_IS_ACTIVE(f)) {
234                 /* this can happen if duplicate names were removed */
235 @@ -1150,10 +1173,11 @@ static void list_file_entry(struct file_struct *f)
236  
237         if (missing_args == 2 && f->mode == 0) {
238                 rprintf(FINFO, "%-*s %s\n",
239 -                       10 + 1 + size_width + mtime_width + atime_width, "*missing",
240 +                       10 + 1 + size_width + mtime_width + atime_width + crtime_width, "*missing",
241                         f_name(f, NULL));
242         } else {
243                 const char *atime_str = atimes_ndx && !S_ISDIR(f->mode) ? timestring(F_ATIME(f)) : "";
244 +               const char *crtime_str = crtimes_ndx ? timestring(F_CRTIME(f)) : "";
245                 const char *arrow, *lnk;
246  
247                 permstring(permbuf, f->mode);
248 @@ -1166,9 +1190,9 @@ static void list_file_entry(struct file_struct *f)
249  #endif
250                         arrow = lnk = "";
251  
252 -               rprintf(FINFO, "%s %*s %s%*s %s%s%s\n",
253 +               rprintf(FINFO, "%s %*s %s%*s%*s %s%s%s\n",
254                         permbuf, size_width, human_num(F_LENGTH(f)),
255 -                       timestring(f->modtime), atime_width, atime_str,
256 +                       timestring(f->modtime), atime_width, atime_str, crtime_width, crtime_str,
257                         f_name(f, NULL), arrow, lnk);
258         }
259  }
260 @@ -1264,6 +1288,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
261                         return;
262                 }
263         }
264 +       sx.crtime = 0;
265  
266         if (dry_run > 1 || (dry_missing_dir && is_below(file, dry_missing_dir))) {
267                 int i;
268 diff --git a/log.c b/log.c
269 --- a/log.c
270 +++ b/log.c
271 @@ -720,7 +720,8 @@ static void log_formatted(enum logcode code, const char *format, const char *op,
272                         c[9] = !(iflags & ITEM_REPORT_ACL) ? '.' : 'a';
273                         c[10] = !(iflags & ITEM_REPORT_XATTR) ? '.' : 'x';
274                         c[11] = !(iflags & ITEM_REPORT_FFLAGS) ? '.' : 'f';
275 -                       c[12] = '\0';
276 +                       c[12] = !(iflags & ITEM_REPORT_CRTIME) ? '.' : 'n';
277 +                       c[13] = '\0';
278  
279                         if (iflags & (ITEM_IS_NEW|ITEM_MISSING_DATA)) {
280                                 char ch = iflags & ITEM_IS_NEW ? '+' : '?';
281 diff --git a/options.c b/options.c
282 --- a/options.c
283 +++ b/options.c
284 @@ -65,6 +65,7 @@ int preserve_uid = 0;
285  int preserve_gid = 0;
286  int preserve_times = 0;
287  int preserve_atimes = 0;
288 +int preserve_crtimes = 0;
289  int update_only = 0;
290  int open_noatime = 0;
291  int cvs_exclude = 0;
292 @@ -582,6 +583,7 @@ static void print_rsync_version(enum logcode f)
293         char const *ipv6 = "no ";
294         char const *sse2 = "no ";
295         char const *fileflags = "no ";
296 +       char const *crtimes = "no ";
297         STRUCT_STAT *dumstat;
298  
299  #if SUBPROTOCOL_VERSION != 0
300 @@ -624,6 +626,9 @@ static void print_rsync_version(enum logcode f)
301  #ifdef SUPPORT_FILEFLAGS
302         fileflags = "";
303  #endif
304 +#ifdef SUPPORT_CRTIMES
305 +       crtimes = "";
306 +#endif
307  
308         rprintf(f, "%s  version %s  protocol version %d%s\n",
309                 RSYNC_NAME, RSYNC_VERSION, PROTOCOL_VERSION, subprotocol);
310 @@ -639,8 +644,8 @@ static void print_rsync_version(enum logcode f)
311                 got_socketpair, hardlinks, links, ipv6, have_inplace);
312         rprintf(f, "    %sappend, %sACLs, %sxattrs, %siconv, %ssymtimes, %sprealloc, %ssse2,\n",
313                 have_inplace, acls, xattrs, iconv, symtimes, prealloc, sse2);
314 -       rprintf(f, "    %sfile-flags\n",
315 -               fileflags);
316 +       rprintf(f, "    %sfile-flags, %scrtimes\n",
317 +               fileflags, crtimes);
318  
319  #ifdef MAINTAINER_MODE
320         rprintf(f, "Panic Action: \"%s\"\n", get_panic_action());
321 @@ -730,6 +735,9 @@ void usage(enum logcode F)
322    rprintf(F," -t, --times                 preserve modification times\n");
323    rprintf(F," -U, --atimes                preserve access (last-used) times\n");
324    rprintf(F,"     --open-noatime          avoid changing the atime on opened files\n");
325 +#ifdef SUPPORT_CRTIMES
326 +  rprintf(F," -N, --crtimes               preserve create times (newness)\n");
327 +#endif
328    rprintf(F," -O, --omit-dir-times        omit directories from --times\n");
329    rprintf(F," -J, --omit-link-times       omit symlinks from --times\n");
330    rprintf(F,"     --super                 receiver attempts super-user activities\n");
331 @@ -905,6 +913,11 @@ static struct poptOption long_options[] = {
332    {"no-U",             0,  POPT_ARG_VAL,    &preserve_atimes, 0, 0, 0 },
333    {"open-noatime",     0,  POPT_ARG_VAL,    &open_noatime, 1, 0, 0 },
334    {"no-open-noatime",  0,  POPT_ARG_VAL,    &open_noatime, 0, 0, 0 },
335 +#ifdef SUPPORT_CRTIMES
336 +  {"crtimes",         'N', POPT_ARG_VAL,    &preserve_crtimes, 1, 0, 0 },
337 +  {"no-crtimes",       0,  POPT_ARG_VAL,    &preserve_crtimes, 0, 0, 0 },
338 +  {"no-N",             0,  POPT_ARG_VAL,    &preserve_crtimes, 0, 0, 0 },
339 +#endif
340    {"omit-dir-times",  'O', POPT_ARG_VAL,    &omit_dir_times, 1, 0, 0 },
341    {"no-omit-dir-times",0,  POPT_ARG_VAL,    &omit_dir_times, 0, 0, 0 },
342    {"no-O",             0,  POPT_ARG_VAL,    &omit_dir_times, 0, 0, 0 },
343 @@ -2604,6 +2617,10 @@ void server_options(char **args, int *argc_p)
344                 if (preserve_atimes > 1)
345                         argstr[x++] = 'U';
346         }
347 +#ifdef SUPPORT_CRTIMES
348 +       if (preserve_crtimes)
349 +               argstr[x++] = 'N';
350 +#endif
351         if (preserve_perms)
352                 argstr[x++] = 'p';
353         else if (preserve_executability && am_sender)
354 diff --git a/rsync.c b/rsync.c
355 --- a/rsync.c
356 +++ b/rsync.c
357 @@ -604,6 +604,9 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
358                 memcpy(&sx2.st, &sxp->st, sizeof (sx2.st));
359         if (!atimes_ndx || S_ISDIR(sxp->st.st_mode))
360                 flags |= ATTRS_SKIP_ATIME;
361 +       /* Don't set the creation date on the root folder of an HFS+ volume. */
362 +       if (sxp->st.st_ino == 2 && S_ISDIR(sxp->st.st_mode))
363 +               flags |= ATTRS_SKIP_CRTIME;
364         if (!(flags & ATTRS_SKIP_MTIME)
365          && (sxp->st.st_mtime != file->modtime
366  #ifdef ST_MTIME_NSEC
367 @@ -638,6 +641,16 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
368                         file->flags |= FLAG_TIME_FAILED;
369                 }
370         }
371 +#ifdef SUPPORT_CRTIMES
372 +       if (crtimes_ndx && !(flags & ATTRS_SKIP_CRTIME)) {
373 +               time_t file_crtime = F_CRTIME(file);
374 +               if (sxp->crtime == 0)
375 +                       sxp->crtime = get_create_time(fname);
376 +               if (cmp_time(sxp->crtime, 0L, file_crtime, 0L) != 0
377 +                && set_create_time(fname, file_crtime) == 0)
378 +                       updated = 1;
379 +       }
380 +#endif
381  
382  #ifdef SUPPORT_ACLS
383         /* It's OK to call set_acl() now, even for a dir, as the generator
384 @@ -757,7 +770,7 @@ int finish_transfer(const char *fname, const char *fnametmp,
385         /* Change permissions before putting the file into place. */
386         set_file_attrs(fnametmp, file, NULL, fnamecmp,
387                        ATTRS_DELAY_IMMUTABLE
388 -                      | (ok_to_set_time ? ATTRS_SET_NANO : ATTRS_SKIP_MTIME | ATTRS_SKIP_ATIME));
389 +                      | (ok_to_set_time ? ATTRS_SET_NANO : ATTRS_SKIP_MTIME | ATTRS_SKIP_ATIME | ATTRS_SKIP_CRTIME));
390  
391         /* move tmp file over real file */
392         if (DEBUG_GTE(RECV, 1))
393 @@ -786,7 +799,7 @@ int finish_transfer(const char *fname, const char *fnametmp,
394  
395    do_set_file_attrs:
396         set_file_attrs(fnametmp, file, NULL, fnamecmp,
397 -                      ok_to_set_time ? ATTRS_SET_NANO : ATTRS_SKIP_MTIME | ATTRS_SKIP_ATIME);
398 +                      ok_to_set_time ? ATTRS_SET_NANO : ATTRS_SKIP_MTIME | ATTRS_SKIP_ATIME | ATTRS_SKIP_CRTIME);
399  
400         if (temp_copy_name) {
401                 if (do_rename(fnametmp, fname) < 0) {
402 diff --git a/rsync.h b/rsync.h
403 --- a/rsync.h
404 +++ b/rsync.h
405 @@ -69,7 +69,7 @@
406  /* The following XMIT flags require an rsync that uses a varint for the flag values */
407  
408  #define XMIT_SAME_FLAGS (1<<16)        /* any protocol - restricted by command-line option */
409 -#define XMIT_RESERVED_17 (1<<17)       /* reserved for future crtimes use */
410 +#define XMIT_CRTIME_EQ_MTIME (1<<17)   /* any protocol - restricted by command-line option */
411  
412  /* These flags are used in the live flist data. */
413  
414 @@ -178,6 +178,7 @@
415  #define ATTRS_SET_NANO         (1<<2)
416  #define ATTRS_SKIP_ATIME       (1<<3)
417  #define ATTRS_DELAY_IMMUTABLE  (1<<4)
418 +#define ATTRS_SKIP_CRTIME      (1<<5)
419  
420  #define FULL_FLUSH     1
421  #define NORMAL_FLUSH   0
422 @@ -205,6 +206,7 @@
423  #define ITEM_REPORT_ACL (1<<7)
424  #define ITEM_REPORT_XATTR (1<<8)
425  #define ITEM_REPORT_FFLAGS (1<<9)
426 +#define ITEM_REPORT_CRTIME (1<<10)
427  #define ITEM_BASIS_TYPE_FOLLOWS (1<<11)
428  #define ITEM_XNAME_FOLLOWS (1<<12)
429  #define ITEM_IS_NEW (1<<13)
430 @@ -567,6 +569,10 @@ typedef unsigned int size_t;
431  #define ST_FLAGS(st) NO_FFLAGS
432  #endif
433  
434 +#ifdef HAVE_GETATTRLIST
435 +#define SUPPORT_CRTIMES 1
436 +#endif
437 +
438  /* Find a variable that is either exactly 32-bits or longer.
439   * If some code depends on 32-bit truncation, it will need to
440   * take special action in a "#if SIZEOF_INT32 > 4" section. */
441 @@ -773,6 +779,7 @@ struct file_struct {
442  extern int file_extra_cnt;
443  extern int inc_recurse;
444  extern int atimes_ndx;
445 +extern int crtimes_ndx;
446  extern int pathname_ndx;
447  extern int depth_ndx;
448  extern int uid_ndx;
449 @@ -837,6 +844,7 @@ extern int xattrs_ndx;
450  #define F_XATTR(f) REQ_EXTRA(f, xattrs_ndx)->num
451  #define F_NDX(f) REQ_EXTRA(f, unsort_ndx)->num
452  #define F_ATIME(f) REQ_EXTRA64(f, atimes_ndx)->num
453 +#define F_CRTIME(f) REQ_EXTRA64(f, crtimes_ndx)->num
454  
455  /* These items are per-entry optional: */
456  #define F_HL_GNUM(f) OPT_EXTRA(f, START_BUMP(f))->num /* non-dirs */
457 @@ -1079,6 +1087,7 @@ typedef struct {
458  
459  typedef struct {
460      STRUCT_STAT st;
461 +    time_t crtime;
462  #ifdef SUPPORT_ACLS
463      struct rsync_acl *acc_acl; /* access ACL */
464      struct rsync_acl *def_acl; /* default ACL */
465 diff --git a/rsync.yo b/rsync.yo
466 --- a/rsync.yo
467 +++ b/rsync.yo
468 @@ -381,6 +381,7 @@ to the detailed description below for a complete description.  verb(
469   -t, --times                 preserve modification times
470   -U, --atimes                preserve access (use) times
471       --open-noatime          avoid changing the atime on opened files
472 + -N, --crtimes               preserve create times (newness)
473   -O, --omit-dir-times        omit directories from --times
474   -J, --omit-link-times       omit symlinks from --times
475       --super                 receiver attempts super-user activities
476 @@ -1289,6 +1290,9 @@ flag then rsync will silently ignore this option. Note also that some
477  filesystems are mounted to avoid updating the atime on read access even
478  without the O_NOATIME flag being set.
479  
480 +dit(bf(-N, --crtimes)) This tells rsync to set the create times (newness) of
481 +the destination files to the same value as the source files.
482 +
483  dit(bf(-O, --omit-dir-times)) This tells rsync to omit directories when
484  it is preserving modification times (see bf(--times)).  If NFS is sharing
485  the directories on the receiving side, it is a good idea to use bf(-O).
486 @@ -2261,7 +2265,7 @@ with older versions of rsync, but that also turns on the output of other
487  verbose messages).
488  
489  The "%i" escape has a cryptic output that is 11 letters long.  The general
490 -format is like the string bf(YXcstpogfax), where bf(Y) is replaced by the
491 +format is like the string bf(YXcstpogfaxn), where bf(Y) is replaced by the
492  type of update being done, bf(X) is replaced by the file-type, and the
493  other letters represent attributes that may be output if they are being
494  modified.
495 @@ -2323,6 +2327,8 @@ quote(itemization(
496    when a symlink or directory is updated.
497    it() The bf(a) means that the ACL information changed.
498    it() The bf(x) means that the extended attribute information changed.
499 +  it() A bf(n) means the create time (newness) is different and is being
500 +  updated to the sender's value (requires bf(--crtimes)).
501  ))
502  
503  One other output is possible:  when deleting files, the "%i" will output
504 diff --git a/syscall.c b/syscall.c
505 --- a/syscall.c
506 +++ b/syscall.c
507 @@ -55,6 +55,13 @@ extern int open_noatime;
508  # endif
509  #endif
510  
511 +#pragma pack(push, 4)
512 +struct create_time {
513 +       uint32 length;
514 +       struct timespec crtime;
515 +};
516 +#pragma pack(pop)
517 +
518  #define RETURN_ERROR_IF(x,e) \
519         do { \
520                 if (x) { \
521 @@ -493,6 +500,40 @@ int do_setattrlist_times(const char *fname, STRUCT_STAT *stp)
522  }
523  #endif
524  
525 +#ifdef SUPPORT_CRTIMES
526 +time_t get_create_time(const char *path)
527 +{
528 +       static struct create_time attrBuf;
529 +       struct attrlist attrList;
530 +
531 +       memset(&attrList, 0, sizeof attrList);
532 +       attrList.bitmapcount = ATTR_BIT_MAP_COUNT;
533 +       attrList.commonattr = ATTR_CMN_CRTIME;
534 +       if (getattrlist(path, &attrList, &attrBuf, sizeof attrBuf, FSOPT_NOFOLLOW) < 0)
535 +               return 0;
536 +       return attrBuf.crtime.tv_sec;
537 +}
538 +#endif
539 +
540 +#ifdef SUPPORT_CRTIMES
541 +int set_create_time(const char *path, time_t crtime)
542 +{
543 +       struct attrlist attrList;
544 +       struct timespec ts;
545 +
546 +       if (dry_run) return 0;
547 +       RETURN_ERROR_IF_RO_OR_LO;
548 +
549 +       ts.tv_sec = crtime;
550 +       ts.tv_nsec = 0;
551 +
552 +       memset(&attrList, 0, sizeof attrList);
553 +       attrList.bitmapcount = ATTR_BIT_MAP_COUNT;
554 +       attrList.commonattr = ATTR_CMN_CRTIME;
555 +       return setattrlist(path, &attrList, &ts, sizeof ts, FSOPT_NOFOLLOW);
556 +}
557 +#endif
558 +
559  #ifdef HAVE_UTIMENSAT
560  int do_utimensat(const char *fname, STRUCT_STAT *stp)
561  {
562 diff --git a/testsuite/crtimes.test b/testsuite/crtimes.test
563 new file mode 100644
564 --- /dev/null
565 +++ b/testsuite/crtimes.test
566 @@ -0,0 +1,26 @@
567 +#! /bin/sh
568 +
569 +# Test rsync copying create times
570 +
571 +. "$suitedir/rsync.fns"
572 +
573 +$RSYNC --version | grep "  crtimes" >/dev/null || test_skipped "Rsync is configured without crtimes support"
574 +
575 +# Setting an older time via touch sets the create time to the mtime.
576 +# Setting it to a newer time affects just the mtime.
577 +
578 +mkdir "$fromdir"
579 +echo hiho "$fromdir/foo"
580 +
581 +touch -t 200101011111.11 "$fromdir"
582 +touch -t 200202022222.22 "$fromdir"
583 +
584 +touch -t 200111111111.11 "$fromdir/foo"
585 +touch -t 200212122222.22 "$fromdir/foo"
586 +
587 +TLS_ARGS=--crtimes
588 +
589 +checkit "$RSYNC -rtgvvv --crtimes \"$fromdir/\" \"$todir/\"" "$fromdir" "$todir"
590 +
591 +# The script would have aborted on error, so getting here means we've won.
592 +exit 0
593 diff --git a/testsuite/rsync.fns b/testsuite/rsync.fns
594 --- a/testsuite/rsync.fns
595 +++ b/testsuite/rsync.fns
596 @@ -23,9 +23,9 @@ todir="$tmpdir/to"
597  chkdir="$tmpdir/chk"
598  
599  # For itemized output:
600 -all_plus='++++++++++'
601 -allspace='          '
602 -dots='......' # trailing dots after changes
603 +all_plus='+++++++++++'
604 +allspace='           '
605 +dots='.......' # trailing dots after changes
606  tab_ch='       ' # a single tab character
607  
608  # Berkley's nice.
609 diff --git a/tls.c b/tls.c
610 --- a/tls.c
611 +++ b/tls.c
612 @@ -108,6 +108,9 @@ static int stat_xattr(const char *fname, STRUCT_STAT *fst)
613  #endif
614  
615  static int display_atimes = 0;
616 +#ifdef SUPPORT_CRTIMES
617 +static int display_crtimes = 0;
618 +#endif
619  
620  static void failed(char const *what, char const *where)
621  {
622 @@ -143,14 +146,22 @@ static void storetime(char *dest, size_t destsize, time_t t, int nsecs)
623  static void list_file(const char *fname)
624  {
625         STRUCT_STAT buf;
626 +#ifdef SUPPORT_CRTIMES
627 +       time_t crtime = 0;
628 +#endif
629         char permbuf[PERMSTRING_SIZE];
630         char mtimebuf[50];
631         char atimebuf[50];
632 +       char crtimebuf[50];
633         char linkbuf[4096];
634         int nsecs;
635  
636         if (do_lstat(fname, &buf) < 0)
637                 failed("stat", fname);
638 +#ifdef SUPPORT_CRTIMES
639 +       if (display_crtimes && (crtime = get_create_time(fname)) == 0)
640 +               failed("get_create_time", fname);
641 +#endif
642  #ifdef SUPPORT_XATTRS
643         if (am_root < 0)
644                 stat_xattr(fname, &buf);
645 @@ -195,6 +206,12 @@ static void list_file(const char *fname)
646                 storetime(atimebuf, sizeof atimebuf, S_ISDIR(buf.st_mode) ? 0 : buf.st_atime, -1);
647         else
648                 atimebuf[0] = '\0';
649 +#ifdef SUPPORT_CRTIMES
650 +       if (display_crtimes)
651 +               storetime(crtimebuf, sizeof crtimebuf, crtime, -1);
652 +       else
653 +#endif
654 +               crtimebuf[0] = '\0';
655  
656         /* TODO: Perhaps escape special characters in fname? */
657         printf("%s ", permbuf);
658 @@ -206,14 +223,17 @@ static void list_file(const char *fname)
659         } else
660                 printf("%15s", do_big_num(buf.st_size, 1, NULL));
661  
662 -       printf(" %6ld.%-6ld %6ld%s%s %s%s\n",
663 +       printf(" %6ld.%-6ld %6ld%s%s%s %s%s\n",
664                (long)buf.st_uid, (long)buf.st_gid, (long)buf.st_nlink,
665 -              mtimebuf, atimebuf, fname, linkbuf);
666 +              mtimebuf, atimebuf, crtimebuf, fname, linkbuf);
667  }
668  
669  static struct poptOption long_options[] = {
670    /* longName, shortName, argInfo, argPtr, value, descrip, argDesc */
671    {"atimes",          'U', POPT_ARG_NONE,   &display_atimes, 0, 0, 0},
672 +#ifdef SUPPORT_CRTIMES
673 +  {"crtimes",         'N', POPT_ARG_NONE,   &display_crtimes, 0, 0, 0},
674 +#endif
675    {"link-times",      'l', POPT_ARG_NONE,   &link_times, 0, 0, 0 },
676    {"link-owner",      'L', POPT_ARG_NONE,   &link_owner, 0, 0, 0 },
677  #ifdef SUPPORT_XATTRS
678 @@ -233,6 +253,9 @@ static void NORETURN tls_usage(int ret)
679    fprintf(F,"Trivial file listing program for portably checking rsync\n");
680    fprintf(F,"\nOptions:\n");
681    fprintf(F," -U, --atimes                display access (last-used) times\n");
682 +#ifdef SUPPORT_CRTIMES
683 +  fprintf(F," -N, --crtimes               display create times (newness)\n");
684 +#endif
685    fprintf(F," -l, --link-times            display the time on a symlink\n");
686    fprintf(F," -L, --link-owner            display the owner+group on a symlink\n");
687  #ifdef SUPPORT_XATTRS