1 This patch adds a --crtimes (-N) option that will preserve
4 To use this patch, run these commands for a successful build:
6 patch -p1 <patches/fileflags.diff
7 patch -p1 <patches/crtimes.diff
8 ./configure (optional if already run)
11 diff --git a/compat.c b/compat.c
12 index ef1882d..132ff90 100644
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;
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;
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;
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
45 @@ -56,6 +56,7 @@ extern int missing_args;
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,
55 int ndx, int first_ndx)
57 - static time_t modtime;
58 + static time_t modtime, crtime;
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;
65 modtime = file->modtime;
67 + time_t file_crtime = f_crtime(file);
68 + if (file_crtime == modtime)
69 + xflags |= XMIT_CRTIME_EQ_MTIME;
71 + crtime = file_crtime;
74 #ifdef SUPPORT_HARD_LINKS
76 @@ -578,6 +586,8 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
78 write_int(f, modtime);
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,
89 - static int64 modtime;
90 + static int64 modtime, crtime;
92 #ifdef SUPPORT_FILEFLAGS
93 static uint32 fileflags;
94 @@ -810,6 +820,19 @@ static struct file_struct *recv_file_entry(struct file_list *flist,
96 modtime = read_int(f);
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",
111 if (!(xflags & XMIT_SAME_MODE))
112 mode = from_wire_mode(read_int(f));
114 @@ -969,6 +992,8 @@ static struct file_struct *recv_file_entry(struct file_list *flist,
116 file->flags |= gid_flags;
119 + f_crtime_set(file, (time_t)crtime);
121 F_NDX(file) = flist->used + flist->ndx_start;
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;
128 + f_crtime_set(file, get_create_time(fname));
130 if (basename != thisname)
131 file->dirname = lastdir;
132 diff --git a/generator.c b/generator.c
133 index eee42e8..932f81c 100644
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))
149 + if (sxp->crtime == 0)
150 + sxp->crtime = get_create_time(fname);
151 + if (cmp_time(sxp->crtime, f_crtime(file)) != 0)
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;
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;
168 #if !defined HAVE_LCHMOD && !defined HAVE_SETATTRLIST
169 if (S_ISLNK(file->mode)) {
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)
174 char permbuf[PERMSTRING_SIZE];
175 + time_t crtime = crtimes_ndx ? f_crtime(f) : 0;
177 int colwidth = human_readable ? 14 : 11;
179 @@ -1037,10 +1052,11 @@ static void list_file_entry(struct file_struct *f)
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),
188 + timestring(f->modtime),
189 + crtimes_ndx ? timestring(crtime) : "",
190 + f_name(f, NULL), F_SYMLINK(f));
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",
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) : "",
208 @@ -1141,6 +1159,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
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
220 @@ -35,6 +35,28 @@ realloc_xbuf(xbuf *xb, size_t sz)
224 +static inline time_t
225 +f_crtime(struct file_struct *fp)
227 +#if SIZEOF_TIME_T > 4
229 + memcpy(&crtime, &REQ_EXTRA(fp, crtimes_ndx)->unum, SIZEOF_TIME_T);
232 + return REQ_EXTRA(fp, crtimes_ndx)->unum;
237 +f_crtime_set(struct file_struct *fp, time_t crtime)
239 +#if SIZEOF_TIME_T > 4
240 + memcpy(&REQ_EXTRA(fp, crtimes_ndx)->unum, &crtime, SIZEOF_TIME_T);
242 + REQ_EXTRA(fp, crtimes_ndx)->unum = (uint32)crtime;
247 to_wire_mode(mode_t mode)
249 diff --git a/log.c b/log.c
250 index 83948b1..7a1d9ce 100644
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';
258 + c[11] = !(iflags & ITEM_REPORT_CRTIME) ? '.' : 'n';
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
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;
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)
297 + if (preserve_crtimes)
301 else if (preserve_executability && am_sender)
302 diff --git a/rsync.c b/rsync.c
303 index 3188535..ab4f8e4 100644
306 @@ -471,6 +471,14 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
308 file->flags |= FLAG_TIME_FAILED;
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)
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));
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,
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);
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
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 */
350 /* These flags are used in the live flist data. */
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)
358 #define NORMAL_FLUSH 0
360 #define FNAMECMP_FUZZY 0x83
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;
372 +extern int crtimes_ndx;
373 extern int fileflags_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 {
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
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).
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.
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
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
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)).
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
436 @@ -37,6 +37,11 @@ extern int force_change;
437 extern int preserve_perms;
438 extern int preserve_executability;
440 +struct create_time {
441 + unsigned long length;
442 + struct timespec crtime;
445 #define RETURN_ERROR_IF(x,e) \
448 @@ -394,3 +399,33 @@ OFF_T do_lseek(int fd, OFF_T offset, int whence)
449 return lseek(fd, offset, whence);
453 +time_t get_create_time(const char *path)
455 + static struct create_time attrBuf;
456 + struct attrlist attrList;
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)
463 + return attrBuf.crtime.tv_sec;
466 +int set_create_time(const char *path, time_t crtime)
468 + struct attrlist attrList;
469 + struct timespec ts;
471 + if (dry_run) return 0;
472 + RETURN_ERROR_IF_RO_OR_LO;
474 + ts.tv_sec = crtime;
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);
482 diff --git a/testsuite/crtimes.test b/testsuite/crtimes.test
484 index 0000000..b904e16
486 +++ b/testsuite/crtimes.test
490 +# Test rsync copying create times
492 +. "$suitedir/rsync.fns"
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.
498 +echo hiho "$fromdir/foo"
500 +touch -t 200101011111.11 "$fromdir"
501 +touch -t 200202022222.22 "$fromdir"
503 +touch -t 200111111111.11 "$fromdir/foo"
504 +touch -t 200212122222.22 "$fromdir/foo"
508 +checkit "$RSYNC -rtgvvv --crtimes \"$fromdir/\" \"$todir/\"" "$fromdir" "$todir"
510 +# The script would have aborted on error, so getting here means we've won.
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"
519 # For itemized output:
520 -all_plus='+++++++++'
522 -dots='.....' # trailing dots after changes
523 +all_plus='++++++++++'
525 +dots='......' # trailing dots after changes
528 PATH="$PATH:/usr/ucb"
529 diff --git a/tls.c b/tls.c
530 index 8cc5748..6da4df9 100644
533 @@ -108,6 +108,8 @@ static int stat_xattr(const char *fname, STRUCT_STAT *fst)
537 +static int display_crtimes = 0;
539 static void failed(char const *what, char const *where)
541 fprintf(stderr, PROGRAM ": %s %s: %s\n",
542 @@ -115,16 +117,36 @@ static void failed(char const *what, char const *where)
546 +static void storetime(char *dest, time_t t, size_t destsize)
549 + struct tm *mt = gmtime(&t);
551 + snprintf(dest, destsize,
552 + "%04d-%02d-%02d %02d:%02d:%02d ",
553 + (int)mt->tm_year + 1900,
554 + (int)mt->tm_mon + 1,
560 + strlcpy(dest, " ", destsize);
563 static void list_file(const char *fname)
567 char permbuf[PERMSTRING_SIZE];
571 + char crtimebuf[50];
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
580 stat_xattr(fname, &buf);
581 @@ -159,19 +181,11 @@ static void list_file(const char *fname)
583 permstring(permbuf, buf.st_mode);
585 - if (buf.st_mtime) {
586 - mt = gmtime(&buf.st_mtime);
588 - snprintf(datebuf, sizeof datebuf,
589 - "%04d-%02d-%02d %02d:%02d:%02d",
590 - (int)mt->tm_year + 1900,
591 - (int)mt->tm_mon + 1,
597 - strlcpy(datebuf, " ", sizeof datebuf);
598 + storetime(mtimebuf, buf.st_mtime, sizeof mtimebuf);
599 + if (display_crtimes)
600 + storetime(crtimebuf, crtime, sizeof crtimebuf);
602 + crtimebuf[0] = '\0';
604 /* TODO: Perhaps escape special characters in fname? */
606 @@ -182,13 +196,14 @@ static void list_file(const char *fname)
607 (long)minor(buf.st_rdev));
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);
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