08397f9dfc9b0443371dd66c79b58c314cd53c13
[rsync.git/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 @@ -43,6 +43,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 @@ -76,7 +77,7 @@ int inplace_partial = 0;
24  int do_negotiated_strings = 0;
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 @@ -505,6 +506,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 @@ -670,6 +673,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 @@ -378,6 +379,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 @@ -494,6 +498,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 @@ -581,6 +592,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 @@ -677,6 +692,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 @@ -790,6 +808,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 @@ -830,6 +852,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 @@ -1019,6 +1056,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 @@ -1420,6 +1461,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 @@ -398,6 +399,19 @@ static inline int mtime_differs(STRUCT_STAT *stp, struct file_struct *file)
175  #endif
176  }
177  
178 +static inline int any_time_differs(stat_x *sxp, struct file_struct *file, UNUSED(const char *fname))
179 +{
180 +       int differs = mtime_differs(&sxp->st, file);
181 +#ifdef SUPPORT_CRTIMES
182 +       if (!differs && crtimes_ndx) {
183 +               if (sxp->crtime == 0)
184 +                       sxp->crtime = get_create_time(fname);
185 +               differs = !same_time(sxp->crtime, 0, F_CRTIME(file), 0);
186 +       }
187 +#endif
188 +       return differs;
189 +}
190 +
191  static inline int perms_differ(struct file_struct *file, stat_x *sxp)
192  {
193         if (preserve_perms)
194 @@ -452,7 +466,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 && mtime_differs(&sxp->st, file))
199 +               if (preserve_times & PRESERVE_LINK_TIMES && any_time_differs(sxp, file, fname))
200                         return 0;
201  #endif
202  #ifdef CAN_CHMOD_SYMLINK
203 @@ -472,7 +486,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 && mtime_differs(&sxp->st, file))
208 +               if (preserve_times && any_time_differs(sxp, file, fname))
209                         return 0;
210                 if (perms_differ(file, sxp))
211                         return 0;
212 @@ -518,6 +532,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                  && !same_time(F_ATIME(file), 0, sxp->st.st_atime, 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 (!same_time(sxp->crtime, 0, F_CRTIME(file), 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 @@ -1142,6 +1164,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 @@ -1152,10 +1175,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 @@ -1168,9 +1192,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 @@ -1266,6 +1290,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 @@ -721,7 +721,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 @@ -668,6 +669,11 @@ static void print_capabilities(enum logcode f)
293  #endif
294                         "file-flags",
295  
296 +#ifndef SUPPORT_CRTIMES
297 +               "no "
298 +#endif
299 +                       "crtimes",
300 +
301         "*" /* All options after this point are hidden w/o -V -V */
302  #ifndef HAVE_SIMD
303                 "no "
304 @@ -846,6 +852,9 @@ static struct poptOption long_options[] = {
305    {"no-U",             0,  POPT_ARG_VAL,    &preserve_atimes, 0, 0, 0 },
306    {"open-noatime",     0,  POPT_ARG_VAL,    &open_noatime, 1, 0, 0 },
307    {"no-open-noatime",  0,  POPT_ARG_VAL,    &open_noatime, 0, 0, 0 },
308 +  {"crtimes",         'N', POPT_ARG_VAL,    &preserve_crtimes, 1, 0, 0 },
309 +  {"no-crtimes",       0,  POPT_ARG_VAL,    &preserve_crtimes, 0, 0, 0 },
310 +  {"no-N",             0,  POPT_ARG_VAL,    &preserve_crtimes, 0, 0, 0 },
311    {"omit-dir-times",  'O', POPT_ARG_VAL,    &omit_dir_times, 1, 0, 0 },
312    {"no-omit-dir-times",0,  POPT_ARG_VAL,    &omit_dir_times, 0, 0, 0 },
313    {"no-O",             0,  POPT_ARG_VAL,    &omit_dir_times, 0, 0, 0 },
314 @@ -1227,6 +1236,9 @@ static void set_refuse_options(void)
315         parse_one_refuse_match(0, "force-uchange", list_end);
316         parse_one_refuse_match(0, "force-schange", list_end);
317  #endif
318 +#ifndef SUPPORT_CRTIMES
319 +       parse_one_refuse_match(0, "crtimes", list_end);
320 +#endif
321  
322         /* Now we use the descrip values to actually mark the options for refusal. */
323         for (op = long_options; op != list_end; op++) {
324 @@ -2570,6 +2582,10 @@ void server_options(char **args, int *argc_p)
325                 if (preserve_atimes > 1)
326                         argstr[x++] = 'U';
327         }
328 +#ifdef SUPPORT_CRTIMES
329 +       if (preserve_crtimes)
330 +               argstr[x++] = 'N';
331 +#endif
332         if (preserve_perms)
333                 argstr[x++] = 'p';
334         else if (preserve_executability && am_sender)
335 diff --git a/rsync.1.md b/rsync.1.md
336 --- a/rsync.1.md
337 +++ b/rsync.1.md
338 @@ -373,6 +373,7 @@ detailed description below for a complete description.
339  --times, -t              preserve modification times
340  --atimes, -U             preserve access (use) times
341  --open-noatime           avoid changing the atime on opened files
342 +--crtimes, -N            preserve create times (newness)
343  --omit-dir-times, -O     omit directories from --times
344  --omit-link-times, -J    omit symlinks from --times
345  --super                  receiver attempts super-user activities
346 @@ -1371,6 +1372,11 @@ your home directory (remove the '=' for that).
347      mounted to avoid updating the atime on read access even without the
348      O_NOATIME flag being set.
349  
350 +0.  `--crtimes`, `-N,`
351 +
352 +    This tells rsync to set the create times (newness) of +the destination
353 +    files to the same value as the source files.
354 +
355  0.  `--omit-dir-times`, `-O`
356  
357      This tells rsync to omit directories when it is preserving modification
358 @@ -2633,7 +2639,7 @@ your home directory (remove the '=' for that).
359      output of other verbose messages).
360  
361      The "%i" escape has a cryptic output that is 11 letters long.  The general
362 -    format is like the string `YXcstpoguaxf`, where **Y** is replaced by the type
363 +    format is like the string `YXcstpoguaxfn`, where **Y** is replaced by the type
364      of update being done, **X** is replaced by the file-type, and the other
365      letters represent attributes that may be output if they are being modified.
366  
367 @@ -2690,6 +2696,8 @@ your home directory (remove the '=' for that).
368        happens when a symlink or directory is updated.
369      - The `a` means that the ACL information changed.
370      - The `x` means that the extended attribute information changed.
371 +    - A `n` means the create time (newness) is different and is being updated
372 +      to the sender's value (requires `--crtimes`).
373  
374      One other output is possible: when deleting files, the "%i" will output the
375      string "`*deleting`" for each item that is being removed (assuming that you
376 diff --git a/rsync.c b/rsync.c
377 --- a/rsync.c
378 +++ b/rsync.c
379 @@ -618,6 +618,9 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
380                 memcpy(&sx2.st, &sxp->st, sizeof (sx2.st));
381         if (!atimes_ndx || S_ISDIR(sxp->st.st_mode))
382                 flags |= ATTRS_SKIP_ATIME;
383 +       /* Don't set the creation date on the root folder of an HFS+ volume. */
384 +       if (sxp->st.st_ino == 2 && S_ISDIR(sxp->st.st_mode))
385 +               flags |= ATTRS_SKIP_CRTIME;
386         if (!(flags & ATTRS_SKIP_MTIME) && !same_mtime(file, &sxp->st, flags & ATTRS_ACCURATE_TIME)) {
387                 sx2.st.st_mtime = file->modtime;
388  #ifdef ST_MTIME_NSEC
389 @@ -647,6 +650,16 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
390                         file->flags |= FLAG_TIME_FAILED;
391                 }
392         }
393 +#ifdef SUPPORT_CRTIMES
394 +       if (crtimes_ndx && !(flags & ATTRS_SKIP_CRTIME)) {
395 +               time_t file_crtime = F_CRTIME(file);
396 +               if (sxp->crtime == 0)
397 +                       sxp->crtime = get_create_time(fname);
398 +               if (!same_time(sxp->crtime, 0L, file_crtime, 0L)
399 +                && set_create_time(fname, file_crtime) == 0)
400 +                       updated = 1;
401 +       }
402 +#endif
403  
404  #ifdef SUPPORT_ACLS
405         /* It's OK to call set_acl() now, even for a dir, as the generator
406 @@ -766,7 +779,7 @@ int finish_transfer(const char *fname, const char *fnametmp,
407         /* Change permissions before putting the file into place. */
408         set_file_attrs(fnametmp, file, NULL, fnamecmp,
409                        ATTRS_DELAY_IMMUTABLE
410 -                      | (ok_to_set_time ? ATTRS_ACCURATE_TIME : ATTRS_SKIP_MTIME | ATTRS_SKIP_ATIME));
411 +                      | (ok_to_set_time ? ATTRS_ACCURATE_TIME : ATTRS_SKIP_MTIME | ATTRS_SKIP_ATIME | ATTRS_SKIP_CRTIME));
412  
413         /* move tmp file over real file */
414         if (DEBUG_GTE(RECV, 1))
415 @@ -795,7 +808,7 @@ int finish_transfer(const char *fname, const char *fnametmp,
416  
417    do_set_file_attrs:
418         set_file_attrs(fnametmp, file, NULL, fnamecmp,
419 -                      ok_to_set_time ? ATTRS_ACCURATE_TIME : ATTRS_SKIP_MTIME | ATTRS_SKIP_ATIME);
420 +                      ok_to_set_time ? ATTRS_ACCURATE_TIME : ATTRS_SKIP_MTIME | ATTRS_SKIP_ATIME | ATTRS_SKIP_CRTIME);
421  
422         if (temp_copy_name) {
423                 if (do_rename(fnametmp, fname) < 0) {
424 diff --git a/rsync.h b/rsync.h
425 --- a/rsync.h
426 +++ b/rsync.h
427 @@ -69,7 +69,7 @@
428  /* The following XMIT flags require an rsync that uses a varint for the flag values */
429  
430  #define XMIT_SAME_FLAGS (1<<16)        /* any protocol - restricted by command-line option */
431 -#define XMIT_RESERVED_17 (1<<17)       /* reserved for future crtimes use */
432 +#define XMIT_CRTIME_EQ_MTIME (1<<17)   /* any protocol - restricted by command-line option */
433  
434  /* These flags are used in the live flist data. */
435  
436 @@ -182,6 +182,7 @@
437  #define ATTRS_ACCURATE_TIME    (1<<2)
438  #define ATTRS_SKIP_ATIME       (1<<3)
439  #define ATTRS_DELAY_IMMUTABLE  (1<<4)
440 +#define ATTRS_SKIP_CRTIME      (1<<5)
441  
442  #define MSG_FLUSH      2
443  #define FULL_FLUSH     1
444 @@ -210,6 +211,7 @@
445  #define ITEM_REPORT_ACL (1<<7)
446  #define ITEM_REPORT_XATTR (1<<8)
447  #define ITEM_REPORT_FFLAGS (1<<9)
448 +#define ITEM_REPORT_CRTIME (1<<10)
449  #define ITEM_BASIS_TYPE_FOLLOWS (1<<11)
450  #define ITEM_XNAME_FOLLOWS (1<<12)
451  #define ITEM_IS_NEW (1<<13)
452 @@ -575,6 +577,10 @@ typedef unsigned int size_t;
453  #define ST_FLAGS(st) NO_FFLAGS
454  #endif
455  
456 +#ifdef HAVE_GETATTRLIST
457 +#define SUPPORT_CRTIMES 1
458 +#endif
459 +
460  /* Find a variable that is either exactly 32-bits or longer.
461   * If some code depends on 32-bit truncation, it will need to
462   * take special action in a "#if SIZEOF_INT32 > 4" section. */
463 @@ -784,6 +790,7 @@ struct file_struct {
464  extern int file_extra_cnt;
465  extern int inc_recurse;
466  extern int atimes_ndx;
467 +extern int crtimes_ndx;
468  extern int pathname_ndx;
469  extern int depth_ndx;
470  extern int uid_ndx;
471 @@ -848,6 +855,7 @@ extern int xattrs_ndx;
472  #define F_XATTR(f) REQ_EXTRA(f, xattrs_ndx)->num
473  #define F_NDX(f) REQ_EXTRA(f, unsort_ndx)->num
474  #define F_ATIME(f) REQ_EXTRA64(f, atimes_ndx)->num
475 +#define F_CRTIME(f) REQ_EXTRA64(f, crtimes_ndx)->num
476  
477  /* These items are per-entry optional: */
478  #define F_HL_GNUM(f) OPT_EXTRA(f, START_BUMP(f))->num /* non-dirs */
479 @@ -1090,6 +1098,7 @@ typedef struct {
480  
481  typedef struct {
482      STRUCT_STAT st;
483 +    time_t crtime;
484  #ifdef SUPPORT_ACLS
485      struct rsync_acl *acc_acl; /* access ACL */
486      struct rsync_acl *def_acl; /* default ACL */
487 diff --git a/syscall.c b/syscall.c
488 --- a/syscall.c
489 +++ b/syscall.c
490 @@ -55,6 +55,13 @@ extern int open_noatime;
491  # endif
492  #endif
493  
494 +#pragma pack(push, 4)
495 +struct create_time {
496 +       uint32 length;
497 +       struct timespec crtime;
498 +};
499 +#pragma pack(pop)
500 +
501  #define RETURN_ERROR_IF(x,e) \
502         do { \
503                 if (x) { \
504 @@ -493,6 +500,40 @@ int do_setattrlist_times(const char *fname, STRUCT_STAT *stp)
505  }
506  #endif
507  
508 +#ifdef SUPPORT_CRTIMES
509 +time_t get_create_time(const char *path)
510 +{
511 +       static struct create_time attrBuf;
512 +       struct attrlist attrList;
513 +
514 +       memset(&attrList, 0, sizeof attrList);
515 +       attrList.bitmapcount = ATTR_BIT_MAP_COUNT;
516 +       attrList.commonattr = ATTR_CMN_CRTIME;
517 +       if (getattrlist(path, &attrList, &attrBuf, sizeof attrBuf, FSOPT_NOFOLLOW) < 0)
518 +               return 0;
519 +       return attrBuf.crtime.tv_sec;
520 +}
521 +#endif
522 +
523 +#ifdef SUPPORT_CRTIMES
524 +int set_create_time(const char *path, time_t crtime)
525 +{
526 +       struct attrlist attrList;
527 +       struct timespec ts;
528 +
529 +       if (dry_run) return 0;
530 +       RETURN_ERROR_IF_RO_OR_LO;
531 +
532 +       ts.tv_sec = crtime;
533 +       ts.tv_nsec = 0;
534 +
535 +       memset(&attrList, 0, sizeof attrList);
536 +       attrList.bitmapcount = ATTR_BIT_MAP_COUNT;
537 +       attrList.commonattr = ATTR_CMN_CRTIME;
538 +       return setattrlist(path, &attrList, &ts, sizeof ts, FSOPT_NOFOLLOW);
539 +}
540 +#endif
541 +
542  #ifdef HAVE_UTIMENSAT
543  int do_utimensat(const char *fname, STRUCT_STAT *stp)
544  {
545 diff --git a/testsuite/crtimes.test b/testsuite/crtimes.test
546 new file mode 100644
547 --- /dev/null
548 +++ b/testsuite/crtimes.test
549 @@ -0,0 +1,26 @@
550 +#! /bin/sh
551 +
552 +# Test rsync copying create times
553 +
554 +. "$suitedir/rsync.fns"
555 +
556 +$RSYNC --version | grep "  crtimes" >/dev/null || test_skipped "Rsync is configured without crtimes support"
557 +
558 +# Setting an older time via touch sets the create time to the mtime.
559 +# Setting it to a newer time affects just the mtime.
560 +
561 +mkdir "$fromdir"
562 +echo hiho "$fromdir/foo"
563 +
564 +touch -t 200101011111.11 "$fromdir"
565 +touch -t 200202022222.22 "$fromdir"
566 +
567 +touch -t 200111111111.11 "$fromdir/foo"
568 +touch -t 200212122222.22 "$fromdir/foo"
569 +
570 +TLS_ARGS=--crtimes
571 +
572 +checkit "$RSYNC -rtgvvv --crtimes \"$fromdir/\" \"$todir/\"" "$fromdir" "$todir"
573 +
574 +# The script would have aborted on error, so getting here means we've won.
575 +exit 0
576 diff --git a/testsuite/rsync.fns b/testsuite/rsync.fns
577 --- a/testsuite/rsync.fns
578 +++ b/testsuite/rsync.fns
579 @@ -23,9 +23,9 @@ todir="$tmpdir/to"
580  chkdir="$tmpdir/chk"
581  
582  # For itemized output:
583 -all_plus='++++++++++'
584 -allspace='          '
585 -dots='......' # trailing dots after changes
586 +all_plus='+++++++++++'
587 +allspace='           '
588 +dots='.......' # trailing dots after changes
589  tab_ch='       ' # a single tab character
590  
591  # Berkley's nice.
592 diff --git a/tls.c b/tls.c
593 --- a/tls.c
594 +++ b/tls.c
595 @@ -108,6 +108,9 @@ static int stat_xattr(const char *fname, STRUCT_STAT *fst)
596  #endif
597  
598  static int display_atimes = 0;
599 +#ifdef SUPPORT_CRTIMES
600 +static int display_crtimes = 0;
601 +#endif
602  
603  static void failed(char const *what, char const *where)
604  {
605 @@ -143,14 +146,22 @@ static void storetime(char *dest, size_t destsize, time_t t, int nsecs)
606  static void list_file(const char *fname)
607  {
608         STRUCT_STAT buf;
609 +#ifdef SUPPORT_CRTIMES
610 +       time_t crtime = 0;
611 +#endif
612         char permbuf[PERMSTRING_SIZE];
613         char mtimebuf[50];
614         char atimebuf[50];
615 +       char crtimebuf[50];
616         char linkbuf[4096];
617         int nsecs;
618  
619         if (do_lstat(fname, &buf) < 0)
620                 failed("stat", fname);
621 +#ifdef SUPPORT_CRTIMES
622 +       if (display_crtimes && (crtime = get_create_time(fname)) == 0)
623 +               failed("get_create_time", fname);
624 +#endif
625  #ifdef SUPPORT_XATTRS
626         if (am_root < 0)
627                 stat_xattr(fname, &buf);
628 @@ -195,6 +206,12 @@ static void list_file(const char *fname)
629                 storetime(atimebuf, sizeof atimebuf, S_ISDIR(buf.st_mode) ? 0 : buf.st_atime, -1);
630         else
631                 atimebuf[0] = '\0';
632 +#ifdef SUPPORT_CRTIMES
633 +       if (display_crtimes)
634 +               storetime(crtimebuf, sizeof crtimebuf, crtime, -1);
635 +       else
636 +#endif
637 +               crtimebuf[0] = '\0';
638  
639         /* TODO: Perhaps escape special characters in fname? */
640         printf("%s ", permbuf);
641 @@ -204,14 +221,17 @@ static void list_file(const char *fname)
642         } else
643                 printf("%15s", do_big_num(buf.st_size, 1, NULL));
644  
645 -       printf(" %6ld.%-6ld %6ld%s%s %s%s\n",
646 +       printf(" %6ld.%-6ld %6ld%s%s%s %s%s\n",
647                (long)buf.st_uid, (long)buf.st_gid, (long)buf.st_nlink,
648 -              mtimebuf, atimebuf, fname, linkbuf);
649 +              mtimebuf, atimebuf, crtimebuf, fname, linkbuf);
650  }
651  
652  static struct poptOption long_options[] = {
653    /* longName, shortName, argInfo, argPtr, value, descrip, argDesc */
654    {"atimes",          'U', POPT_ARG_NONE,   &display_atimes, 0, 0, 0},
655 +#ifdef SUPPORT_CRTIMES
656 +  {"crtimes",         'N', POPT_ARG_NONE,   &display_crtimes, 0, 0, 0},
657 +#endif
658    {"link-times",      'l', POPT_ARG_NONE,   &link_times, 0, 0, 0 },
659    {"link-owner",      'L', POPT_ARG_NONE,   &link_owner, 0, 0, 0 },
660  #ifdef SUPPORT_XATTRS
661 @@ -231,6 +251,9 @@ static void NORETURN tls_usage(int ret)
662    fprintf(F,"Trivial file listing program for portably checking rsync\n");
663    fprintf(F,"\nOptions:\n");
664    fprintf(F," -U, --atimes                display access (last-used) times\n");
665 +#ifdef SUPPORT_CRTIMES
666 +  fprintf(F," -N, --crtimes               display create times (newness)\n");
667 +#endif
668    fprintf(F," -l, --link-times            display the time on a symlink\n");
669    fprintf(F," -L, --link-owner            display the owner+group on a symlink\n");
670  #ifdef SUPPORT_XATTRS