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
14 @@ -45,6 +45,7 @@ extern int force_change;
15 extern int protect_args;
16 extern int preserve_uid;
17 extern int preserve_gid;
18 +extern int preserve_crtimes;
19 extern int preserve_fileflags;
20 extern int preserve_acls;
21 extern int preserve_xattrs;
22 @@ -62,7 +63,7 @@ extern iconv_t ic_send, ic_recv;
25 /* These index values are for the file-list's extra-attribute array. */
26 -int uid_ndx, gid_ndx, fileflags_ndx, acls_ndx, xattrs_ndx, unsort_ndx;
27 +int uid_ndx, gid_ndx, crtimes_ndx, fileflags_ndx, acls_ndx, xattrs_ndx, unsort_ndx;
29 int receiver_symlink_times = 0; /* receiver can set the time on a symlink */
31 @@ -136,6 +137,8 @@ void setup_protocol(int f_out,int f_in)
32 uid_ndx = ++file_extra_cnt;
34 gid_ndx = ++file_extra_cnt;
35 + if (preserve_crtimes)
36 + crtimes_ndx = (file_extra_cnt += TIME_EXTRA_CNT);
37 if (preserve_fileflags || (force_change && !am_sender))
38 fileflags_ndx = ++file_extra_cnt;
39 if (preserve_acls && !am_sender)
40 diff --git a/flist.c b/flist.c
43 @@ -56,6 +56,7 @@ extern int preserve_fileflags;
47 +extern int crtimes_ndx;
48 extern int relative_paths;
49 extern int implied_dirs;
50 extern int file_extra_cnt;
51 @@ -389,7 +390,7 @@ int change_pathname(struct file_struct *file, const char *dir, int dirlen)
53 static void send_file_entry(int f, const char *fname, struct file_struct *file, int ndx, int first_ndx)
55 - static time_t modtime;
56 + static time_t modtime, crtime;
58 #ifdef SUPPORT_FILEFLAGS
59 static uint32 fileflags;
60 @@ -474,6 +475,13 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
61 xflags |= XMIT_SAME_TIME;
63 modtime = file->modtime;
65 + time_t file_crtime = f_crtime(file);
66 + if (file_crtime == modtime)
67 + xflags |= XMIT_CRTIME_EQ_MTIME;
69 + crtime = file_crtime;
72 #ifdef SUPPORT_HARD_LINKS
74 @@ -543,6 +551,8 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
76 write_int(f, modtime);
78 + if (crtimes_ndx && !(xflags & XMIT_CRTIME_EQ_MTIME))
79 + write_varlong(f, crtime, 4);
80 if (!(xflags & XMIT_SAME_MODE))
81 write_int(f, to_wire_mode(mode));
82 #ifdef SUPPORT_FILEFLAGS
83 @@ -635,7 +645,7 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
84 static struct file_struct *recv_file_entry(struct file_list *flist,
87 - static int64 modtime;
88 + static int64 modtime, crtime;
90 #ifdef SUPPORT_FILEFLAGS
91 static uint32 fileflags;
92 @@ -770,6 +780,19 @@ static struct file_struct *recv_file_entry(struct file_list *flist,
94 modtime = read_int(f);
97 + if (!(xflags & XMIT_CRTIME_EQ_MTIME)) {
98 + crtime = read_varlong(f, 4);
99 +#if SIZEOF_TIME_T < SIZEOF_INT64
100 + if (!am_generator && (int64)(time_t)crtime != crtime) {
101 + rprintf(FERROR_XFER,
102 + "Create time value of %s truncated on receiver.\n",
109 if (!(xflags & XMIT_SAME_MODE))
110 mode = from_wire_mode(read_int(f));
112 @@ -909,6 +932,8 @@ static struct file_struct *recv_file_entry(struct file_list *flist,
114 file->flags |= gid_flags;
117 + f_crtime_set(file, (time_t)crtime);
119 F_NDX(file) = flist->used + flist->ndx_start;
121 @@ -1261,6 +1286,8 @@ struct file_struct *make_file(const char *fname, struct file_list *flist,
122 F_OWNER(file) = st.st_uid;
123 if (gid_ndx) /* Check gid_ndx instead of preserve_gid for del support */
124 F_GROUP(file) = st.st_gid;
126 + f_crtime_set(file, get_create_time(fname));
128 if (basename != thisname)
129 file->dirname = lastdir;
130 diff --git a/generator.c b/generator.c
141 @@ -40,6 +41,7 @@ extern int preserve_xattrs;
142 extern int preserve_links;
143 extern int preserve_devices;
144 extern int preserve_specials;
145 +extern int preserve_fileflags;
146 extern int preserve_hard_links;
147 extern int preserve_executability;
148 extern int preserve_fileflags;
149 @@ -620,6 +622,13 @@ int unchanged_attrs(const char *fname, struct file_struct *file, stat_x *sxp)
150 if (gid_ndx && !(file->flags & FLAG_SKIP_GROUP) && sxp->st.st_gid != (gid_t)F_GROUP(file))
154 + if (sxp->crtime == 0)
155 + sxp->crtime = get_create_time(fname);
156 + if (cmp_time(sxp->crtime, f_crtime(file)) != 0)
161 if (preserve_acls && !S_ISLNK(file->mode)) {
162 if (!ACL_READY(*sxp))
163 @@ -663,6 +672,12 @@ void itemize(const char *fnamecmp, struct file_struct *file, int ndx, int statre
164 : iflags & (ITEM_TRANSFER|ITEM_LOCAL_CHANGE) && !(iflags & ITEM_MATCHED)
165 && (!(iflags & ITEM_XNAME_FOLLOWS) || *xname))
166 iflags |= ITEM_REPORT_TIME;
168 + if (sxp->crtime == 0)
169 + sxp->crtime = get_create_time(fnamecmp);
170 + if (cmp_time(sxp->crtime, f_crtime(file)) != 0)
171 + iflags |= ITEM_REPORT_CRTIME;
173 #if !defined HAVE_LCHMOD && !defined HAVE_SETATTRLIST
174 if (S_ISLNK(file->mode)) {
176 @@ -1210,6 +1225,7 @@ static int try_dests_non(struct file_struct *file, char *fname, int ndx,
177 static void list_file_entry(struct file_struct *f)
179 char permbuf[PERMSTRING_SIZE];
180 + time_t crtime = crtimes_ndx ? f_crtime(f) : 0;
183 if (!F_IS_ACTIVE(f)) {
184 @@ -1224,14 +1240,16 @@ static void list_file_entry(struct file_struct *f)
187 if (preserve_links && S_ISLNK(f->mode)) {
188 - rprintf(FINFO, "%s %11.0f %s %s -> %s\n",
189 + rprintf(FINFO, "%s %11.0f %s %s %s -> %s\n",
190 permbuf, len, timestring(f->modtime),
191 + crtimes_ndx ? timestring(crtime) : "",
192 f_name(f, NULL), F_SYMLINK(f));
196 - rprintf(FINFO, "%s %11.0f %s %s\n",
197 + rprintf(FINFO, "%s %11.0f %s %s %s\n",
198 permbuf, len, timestring(f->modtime),
199 + crtimes_ndx ? timestring(crtime) : "",
203 @@ -1323,6 +1341,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
210 sx.acc_acl = sx.def_acl = NULL;
211 diff --git a/ifuncs.h b/ifuncs.h
214 @@ -67,6 +67,28 @@ d_name(struct dirent *di)
218 +static inline time_t
219 +f_crtime(struct file_struct *fp)
221 +#if SIZEOF_TIME_T > 4
223 + memcpy(&crtime, &REQ_EXTRA(fp, crtimes_ndx)->unum, SIZEOF_TIME_T);
226 + return REQ_EXTRA(fp, crtimes_ndx)->unum;
231 +f_crtime_set(struct file_struct *fp, time_t crtime)
233 +#if SIZEOF_TIME_T > 4
234 + memcpy(&REQ_EXTRA(fp, crtimes_ndx)->unum, &crtime, SIZEOF_TIME_T);
236 + REQ_EXTRA(fp, crtimes_ndx)->unum = (uint32)crtime;
241 isDigit(const char *ptr)
243 diff --git a/log.c b/log.c
246 @@ -653,7 +653,8 @@ static void log_formatted(enum logcode code, const char *format, const char *op,
247 c[8] = !(iflags & ITEM_REPORT_FFLAGS) ? '.' : 'f';
248 c[9] = !(iflags & ITEM_REPORT_ACL) ? '.' : 'a';
249 c[10] = !(iflags & ITEM_REPORT_XATTR) ? '.' : 'x';
251 + c[11] = !(iflags & ITEM_REPORT_CRTIME) ? '.' : 'n';
254 if (iflags & (ITEM_IS_NEW|ITEM_MISSING_DATA)) {
255 char ch = iflags & ITEM_IS_NEW ? '+' : '?';
256 diff --git a/options.c b/options.c
259 @@ -60,6 +60,7 @@ int preserve_specials = 0;
260 int preserve_uid = 0;
261 int preserve_gid = 0;
262 int preserve_times = 0;
263 +int preserve_crtimes = 0;
267 @@ -362,6 +363,7 @@ void usage(enum logcode F)
268 rprintf(F," -D same as --devices --specials\n");
269 rprintf(F," -t, --times preserve modification times\n");
270 rprintf(F," -O, --omit-dir-times omit directories from --times\n");
271 + rprintf(F," -N, --crtimes preserve create times (newness)\n");
272 rprintf(F," --super receiver attempts super-user activities\n");
273 #ifdef SUPPORT_XATTRS
274 rprintf(F," --fake-super store/recover privileged attrs using xattrs\n");
275 @@ -508,6 +510,9 @@ static struct poptOption long_options[] = {
276 {"times", 't', POPT_ARG_VAL, &preserve_times, 2, 0, 0 },
277 {"no-times", 0, POPT_ARG_VAL, &preserve_times, 0, 0, 0 },
278 {"no-t", 0, POPT_ARG_VAL, &preserve_times, 0, 0, 0 },
279 + {"crtimes", 'N', POPT_ARG_VAL, &preserve_crtimes, 1, 0, 0 },
280 + {"no-crtimes", 0, POPT_ARG_VAL, &preserve_crtimes, 0, 0, 0 },
281 + {"no-N", 0, POPT_ARG_VAL, &preserve_crtimes, 0, 0, 0 },
282 {"omit-dir-times", 'O', POPT_ARG_VAL, &omit_dir_times, 1, 0, 0 },
283 {"no-omit-dir-times",0, POPT_ARG_VAL, &omit_dir_times, 0, 0, 0 },
284 {"no-O", 0, POPT_ARG_VAL, &omit_dir_times, 0, 0, 0 },
285 @@ -1799,6 +1804,8 @@ void server_options(char **args, int *argc_p)
289 + if (preserve_crtimes)
293 else if (preserve_executability && am_sender)
294 diff --git a/rsync.c b/rsync.c
297 @@ -472,6 +472,14 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
299 file->flags |= FLAG_TIME_FAILED;
301 + if (crtimes_ndx && !(flags & ATTRS_SKIP_CRTIME)) {
302 + time_t file_crtime = f_crtime(file);
303 + if (sxp->crtime == 0)
304 + sxp->crtime = get_create_time(fname);
305 + if (cmp_time(sxp->crtime, file_crtime) != 0
306 + && set_create_time(fname, file_crtime) == 0)
310 change_uid = am_root && uid_ndx && sxp->st.st_uid != (uid_t)F_OWNER(file);
311 change_gid = gid_ndx && !(file->flags & FLAG_SKIP_GROUP)
312 @@ -618,7 +626,7 @@ int finish_transfer(const char *fname, const char *fnametmp,
313 /* Change permissions before putting the file into place. */
314 set_file_attrs(fnametmp, file, NULL, fnamecmp,
315 ATTRS_DELAY_IMMUTABLE
316 - | (ok_to_set_time ? 0 : ATTRS_SKIP_MTIME));
317 + | (ok_to_set_time ? 0 : ATTRS_SKIP_MTIME | ATTRS_SKIP_CRTIME));
319 /* move tmp file over real file */
321 @@ -649,7 +657,7 @@ int finish_transfer(const char *fname, const char *fnametmp,
324 set_file_attrs(fnametmp, file, NULL, fnamecmp,
325 - ok_to_set_time ? 0 : ATTRS_SKIP_MTIME);
326 + ok_to_set_time ? 0 : ATTRS_SKIP_MTIME | ATTRS_SKIP_CRTIME);
328 if (temp_copy_name) {
329 if (do_rename(fnametmp, fname) < 0) {
330 diff --git a/rsync.h b/rsync.h
334 #define XMIT_RDEV_MINOR_8_pre30 (1<<11) /* protocols 28 - 29 */
335 #define XMIT_GROUP_NAME_FOLLOWS (1<<11) /* protocols 30 - now */
336 #define XMIT_HLINK_FIRST (1<<12) /* protocols 30 - now (HLINKED files only) */
337 +#define XMIT_CRTIME_EQ_MTIME (1<<13) /* protocols ?? - now */
338 #define XMIT_SAME_FLAGS (1<<14) /* protocols ?? - now */
340 /* These flags are used in the live flist data. */
342 #define ATTRS_REPORT (1<<0)
343 #define ATTRS_SKIP_MTIME (1<<1)
344 #define ATTRS_DELAY_IMMUTABLE (1<<2)
345 +#define ATTRS_SKIP_CRTIME (1<<3)
348 #define NORMAL_FLUSH 0
350 #define FNAMECMP_FUZZY 0x83
352 /* For use by the itemize_changes code */
353 -#define ITEM_REPORT_ATIME (1<<0)
354 +#define ITEM_REPORT_CRTIME (1<<0)
355 #define ITEM_REPORT_CHANGE (1<<1)
356 #define ITEM_REPORT_SIZE (1<<2) /* regular files only */
357 #define ITEM_REPORT_TIMEFAIL (1<<2) /* symlinks only */
358 @@ -651,6 +653,7 @@ extern int file_extra_cnt;
359 extern int inc_recurse;
362 +extern int crtimes_ndx;
363 extern int fileflags_ndx;
365 extern int xattrs_ndx;
366 @@ -658,6 +661,7 @@ extern int xattrs_ndx;
367 #define FILE_STRUCT_LEN (offsetof(struct file_struct, basename))
368 #define EXTRA_LEN (sizeof (union file_extras))
369 #define PTR_EXTRA_CNT ((sizeof (char *) + EXTRA_LEN - 1) / EXTRA_LEN)
370 +#define TIME_EXTRA_CNT ((SIZEOF_TIME_T + EXTRA_LEN - 1) / EXTRA_LEN)
371 #define DEV_EXTRA_CNT 2
372 #define DIRNODE_EXTRA_CNT 3
373 #define SUM_EXTRA_CNT ((MAX_DIGEST_LEN + EXTRA_LEN - 1) / EXTRA_LEN)
374 @@ -916,6 +920,7 @@ typedef struct {
380 struct rsync_acl *acc_acl; /* access ACL */
381 struct rsync_acl *def_acl; /* default ACL */
382 diff --git a/rsync.yo b/rsync.yo
385 @@ -350,6 +350,7 @@ to the detailed description below for a complete description. verb(
386 -D same as --devices --specials
387 -t, --times preserve modification times
388 -O, --omit-dir-times omit directories from --times
389 + -N, --crtimes preserve create times (newness)
390 --super receiver attempts super-user activities
391 --fake-super store/recover privileged attrs using xattrs
392 -S, --sparse handle sparse files efficiently
393 @@ -1028,6 +1029,9 @@ it is preserving modification times (see bf(--times)). If NFS is sharing
394 the directories on the receiving side, it is a good idea to use bf(-O).
395 This option is inferred if you use bf(--backup) without bf(--backup-dir).
397 +dit(bf(-N, --crtimes)) This tells rsync to set the create times (newness) of
398 +the destination files to the same value as the source files.
400 dit(bf(--super)) This tells the receiving side to attempt super-user
401 activities even if the receiving rsync wasn't run by the super-user. These
402 activities include: preserving users via the bf(--owner) option, preserving
403 @@ -1680,7 +1684,7 @@ with older versions of rsync, but that also turns on the output of other
406 The "%i" escape has a cryptic output that is 11 letters long. The general
407 -format is like the string bf(YXcstpogfax), where bf(Y) is replaced by the
408 +format is like the string bf(YXcstpogfaxn), where bf(Y) is replaced by the
409 type of update being done, bf(X) is replaced by the file-type, and the
410 other letters represent attributes that may be output if they are being
412 @@ -1739,6 +1743,8 @@ quote(itemization(
413 it() The bf(f) means that the fileflags information changed.
414 it() The bf(a) means that the ACL information changed.
415 it() The bf(x) means that the extended attribute information changed.
416 + it() A bf(n) means the create time (newness) is different and is being
417 + updated to the sender's value (requires bf(--crtimes)).
420 One other output is possible: when deleting files, the "%i" will output
421 diff --git a/syscall.c b/syscall.c
424 @@ -37,6 +37,11 @@ extern int force_change;
425 extern int preserve_perms;
426 extern int preserve_executability;
428 +struct create_time {
429 + unsigned long length;
430 + struct timespec crtime;
433 #define RETURN_ERROR_IF(x,e) \
436 @@ -394,3 +399,33 @@ OFF_T do_lseek(int fd, OFF_T offset, int whence)
437 return lseek(fd, offset, whence);
441 +time_t get_create_time(const char *path)
443 + static struct create_time attrBuf;
444 + struct attrlist attrList;
446 + memset(&attrList, 0, sizeof attrList);
447 + attrList.bitmapcount = ATTR_BIT_MAP_COUNT;
448 + attrList.commonattr = ATTR_CMN_CRTIME;
449 + if (getattrlist(path, &attrList, &attrBuf, sizeof attrBuf, FSOPT_NOFOLLOW) < 0)
451 + return attrBuf.crtime.tv_sec;
454 +int set_create_time(const char *path, time_t crtime)
456 + struct attrlist attrList;
457 + struct timespec ts;
459 + if (dry_run) return 0;
460 + RETURN_ERROR_IF_RO_OR_LO;
462 + ts.tv_sec = crtime;
465 + memset(&attrList, 0, sizeof attrList);
466 + attrList.bitmapcount = ATTR_BIT_MAP_COUNT;
467 + attrList.commonattr = ATTR_CMN_CRTIME;
468 + return setattrlist(path, &attrList, &ts, sizeof ts, FSOPT_NOFOLLOW);
470 diff --git a/testsuite/crtimes.test b/testsuite/crtimes.test
473 +++ b/testsuite/crtimes.test
477 +# Test rsync copying create times
479 +. "$suitedir/rsync.fns"
481 +# Setting an older time via touch sets the create time to the mtime.
482 +# Setting it to a newer time affects just the mtime.
485 +echo hiho "$fromdir/foo"
487 +touch -t 200101011111.11 "$fromdir"
488 +touch -t 200202022222.22 "$fromdir"
490 +touch -t 200111111111.11 "$fromdir/foo"
491 +touch -t 200212122222.22 "$fromdir/foo"
495 +checkit "$RSYNC -rtgvvv --crtimes \"$fromdir/\" \"$todir/\"" "$fromdir" "$todir"
497 +# The script would have aborted on error, so getting here means we've won.
499 diff --git a/testsuite/rsync.fns b/testsuite/rsync.fns
500 --- a/testsuite/rsync.fns
501 +++ b/testsuite/rsync.fns
502 @@ -24,9 +24,9 @@ todir="$tmpdir/to"
505 # For itemized output:
506 -all_plus='+++++++++'
508 -dots='.....' # trailing dots after changes
509 +all_plus='++++++++++'
511 +dots='......' # trailing dots after changes
514 PATH="$PATH:/usr/ucb"
515 diff --git a/tls.c b/tls.c
518 @@ -107,6 +107,8 @@ static int stat_xattr(const char *fname, STRUCT_STAT *fst)
522 +static int display_crtimes = 0;
524 static void failed(char const *what, char const *where)
526 fprintf(stderr, PROGRAM ": %s %s: %s\n",
527 @@ -114,16 +116,36 @@ static void failed(char const *what, char const *where)
531 +static void storetime(char *dest, time_t t, size_t destsize)
534 + struct tm *mt = gmtime(&t);
536 + snprintf(dest, destsize,
537 + "%04d-%02d-%02d %02d:%02d:%02d ",
538 + (int)mt->tm_year + 1900,
539 + (int)mt->tm_mon + 1,
545 + strlcpy(dest, " ", destsize);
548 static void list_file(const char *fname)
552 char permbuf[PERMSTRING_SIZE];
556 + char crtimebuf[50];
559 if (do_lstat(fname, &buf) < 0)
560 failed("stat", fname);
561 + if (display_crtimes && (crtime = get_create_time(fname)) == 0)
562 + failed("get_create_time", fname);
563 #ifdef SUPPORT_XATTRS
565 stat_xattr(fname, &buf);
566 @@ -158,19 +180,11 @@ static void list_file(const char *fname)
568 permstring(permbuf, buf.st_mode);
570 - if (buf.st_mtime) {
571 - mt = gmtime(&buf.st_mtime);
573 - snprintf(datebuf, sizeof datebuf,
574 - "%04d-%02d-%02d %02d:%02d:%02d",
575 - (int)mt->tm_year + 1900,
576 - (int)mt->tm_mon + 1,
582 - strlcpy(datebuf, " ", sizeof datebuf);
583 + storetime(mtimebuf, buf.st_mtime, sizeof mtimebuf);
584 + if (display_crtimes)
585 + storetime(crtimebuf, crtime, sizeof crtimebuf);
587 + crtimebuf[0] = '\0';
589 /* TODO: Perhaps escape special characters in fname? */
591 @@ -181,13 +195,14 @@ static void list_file(const char *fname)
592 (long)minor(buf.st_rdev));
593 } else /* NB: use double for size since it might not fit in a long. */
594 printf("%12.0f", (double)buf.st_size);
595 - printf(" %6ld.%-6ld %6ld %s %s%s\n",
596 + printf(" %6ld.%-6ld %6ld %s%s%s%s\n",
597 (long)buf.st_uid, (long)buf.st_gid, (long)buf.st_nlink,
598 - datebuf, fname, linkbuf);
599 + mtimebuf, crtimebuf, fname, linkbuf);
602 static struct poptOption long_options[] = {
603 /* longName, shortName, argInfo, argPtr, value, descrip, argDesc */
604 + {"crtimes", 'N', POPT_ARG_NONE, &display_crtimes, 0, 0, 0},
605 {"link-times", 'l', POPT_ARG_NONE, &link_times, 0, 0, 0 },
606 {"link-owner", 'L', POPT_ARG_NONE, &link_owner, 0, 0, 0 },
607 #ifdef SUPPORT_XATTRS
608 @@ -203,6 +218,7 @@ static void tls_usage(int ret)
609 fprintf(F,"usage: " PROGRAM " [OPTIONS] FILE ...\n");
610 fprintf(F,"Trivial file listing program for portably checking rsync\n");
611 fprintf(F,"\nOptions:\n");
612 + fprintf(F," -N, --crtimes display create times (newness)\n");
613 fprintf(F," -l, --link-times display the time on a symlink\n");
614 fprintf(F," -L, --link-owner display the owner+group on a symlink\n");
615 #ifdef SUPPORT_XATTRS