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/flags.diff
7 patch -p1 <patches/crtimes.diff
8 ./configure (optional if already run)
11 diff --git a/compat.c b/compat.c
14 @@ -44,6 +44,7 @@ extern int protocol_version;
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 @@ -61,7 +62,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;
30 int filesfrom_convert = 0;
31 @@ -125,6 +126,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)
38 fileflags_ndx = ++file_extra_cnt;
39 if (preserve_acls && !am_sender)
40 diff --git a/flist.c b/flist.c
43 @@ -54,6 +54,7 @@ extern int fileflags_ndx;
47 +extern int crtimes_ndx;
48 extern int relative_paths;
49 extern int implied_dirs;
50 extern int file_extra_cnt;
51 @@ -343,7 +344,7 @@ int push_pathname(const char *dir, int len)
53 static void send_file_entry(int f, struct file_struct *file, int ndx, int first_ndx)
55 - static time_t modtime;
56 + static time_t modtime, crtime;
59 static uint32 fileflags;
60 @@ -462,6 +463,13 @@ static void send_file_entry(int f, struct file_struct *file, int ndx, int first_
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 @@ -532,6 +540,8 @@ static void send_file_entry(int f, struct file_struct *file, int ndx, int first_
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));
83 @@ -624,7 +634,7 @@ static void send_file_entry(int f, struct file_struct *file, int ndx, int first_
84 static struct file_struct *recv_file_entry(struct file_list *flist,
87 - static int64 modtime;
88 + static int64 modtime, crtime;
91 static uint32 fileflags;
92 @@ -758,6 +768,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 @@ -898,6 +921,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 @@ -1234,6 +1259,8 @@ struct file_struct *make_file(const char *fname, struct file_list *flist,
122 F_OWNER(file) = st.st_uid;
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 @@ -41,6 +42,7 @@ extern int preserve_links;
142 extern int preserve_devices;
143 extern int preserve_specials;
144 extern int preserve_hard_links;
145 +extern int preserve_fileflags;
146 extern int preserve_perms;
147 extern int preserve_times;
149 @@ -137,6 +139,7 @@ enum delret {
150 /* Forward declaration for delete_item(). */
151 static enum delret delete_dir_contents(char *fname, int flags);
154 static int is_backup_file(char *fn)
156 int k = strlen(fn) - backup_suffix_len;
157 @@ -598,6 +601,13 @@ int unchanged_attrs(const char *fname, struct file_struct *file, stat_x *sxp)
158 if (gid_ndx && !(file->flags & FLAG_SKIP_GROUP) && sxp->st.st_gid != (gid_t)F_GROUP(file))
162 + if (sxp->crtime == 0)
163 + sxp->crtime = get_create_time(fname);
164 + if (cmp_time(sxp->crtime, f_crtime(file)) != 0)
169 if (preserve_acls && !S_ISLNK(file->mode)) {
170 if (!ACL_READY(*sxp))
171 @@ -634,6 +644,12 @@ void itemize(const char *fnamecmp, struct file_struct *file, int ndx, int statre
172 && (!(iflags & ITEM_XNAME_FOLLOWS) || *xname))
173 || (keep_time && cmp_time(file->modtime, sxp->st.st_mtime) != 0))
174 iflags |= ITEM_REPORT_TIME;
176 + if (sxp->crtime == 0)
177 + sxp->crtime = get_create_time(fnamecmp);
178 + if (cmp_time(sxp->crtime, f_crtime(file)) != 0)
179 + iflags |= ITEM_REPORT_CRTIME;
181 #if !defined HAVE_LCHMOD && !defined HAVE_SETATTRLIST
182 if (S_ISLNK(file->mode)) {
184 @@ -1173,6 +1189,7 @@ static int try_dests_non(struct file_struct *file, char *fname, int ndx,
185 static void list_file_entry(struct file_struct *f)
187 char permbuf[PERMSTRING_SIZE];
188 + time_t crtime = crtimes_ndx ? f_crtime(f) : 0;
191 if (!F_IS_ACTIVE(f)) {
192 @@ -1187,14 +1204,16 @@ static void list_file_entry(struct file_struct *f)
195 if (preserve_links && S_ISLNK(f->mode)) {
196 - rprintf(FINFO, "%s %11.0f %s %s -> %s\n",
197 + rprintf(FINFO, "%s %11.0f %s %s %s -> %s\n",
198 permbuf, len, timestring(f->modtime),
199 + crtimes_ndx ? timestring(crtime) : "",
200 f_name(f, NULL), F_SYMLINK(f));
204 - rprintf(FINFO, "%s %11.0f %s %s\n",
205 + rprintf(FINFO, "%s %11.0f %s %s %s\n",
206 permbuf, len, timestring(f->modtime),
207 + crtimes_ndx ? timestring(crtime) : "",
211 @@ -1277,6 +1296,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
217 sx.acc_acl = sx.def_acl = NULL;
219 diff --git a/ifuncs.h b/ifuncs.h
222 @@ -57,6 +57,28 @@ from_wire_mode(int mode)
226 +static inline time_t
227 +f_crtime(struct file_struct *fp)
229 +#if SIZEOF_TIME_T > 4
231 + memcpy(&crtime, &REQ_EXTRA(fp, crtimes_ndx)->unum, SIZEOF_TIME_T);
234 + return REQ_EXTRA(fp, crtimes_ndx)->unum;
239 +f_crtime_set(struct file_struct *fp, time_t crtime)
241 +#if SIZEOF_TIME_T > 4
242 + memcpy(&REQ_EXTRA(fp, crtimes_ndx)->unum, &crtime, SIZEOF_TIME_T);
244 + REQ_EXTRA(fp, crtimes_ndx)->unum = (uint32)crtime;
249 isDigit(const char *ptr)
251 diff --git a/log.c b/log.c
254 @@ -642,7 +642,7 @@ static void log_formatted(enum logcode code, const char *format, const char *op,
255 c[5] = !(iflags & ITEM_REPORT_PERMS) ? '.' : 'p';
256 c[6] = !(iflags & ITEM_REPORT_OWNER) ? '.' : 'o';
257 c[7] = !(iflags & ITEM_REPORT_GROUP) ? '.' : 'g';
258 - c[8] = !(iflags & ITEM_REPORT_ATIME) ? '.' : 'u';
259 + c[8] = !(iflags & ITEM_REPORT_CRTIME) ? '.' : 'n';
260 c[9] = !(iflags & ITEM_REPORT_ACL) ? '.' : 'a';
261 c[10] = !(iflags & ITEM_REPORT_XATTR) ? '.' : 'x';
263 diff --git a/options.c b/options.c
266 @@ -59,6 +59,7 @@ int preserve_specials = 0;
267 int preserve_uid = 0;
268 int preserve_gid = 0;
269 int preserve_times = 0;
270 +int preserve_crtimes = 0;
274 @@ -354,6 +355,7 @@ void usage(enum logcode F)
275 rprintf(F," -D same as --devices --specials\n");
276 rprintf(F," -t, --times preserve modification times\n");
277 rprintf(F," -O, --omit-dir-times omit directories from --times\n");
278 + rprintf(F," -N, --crtimes preserve create (newness) times\n");
279 rprintf(F," --super receiver attempts super-user activities\n");
280 #ifdef SUPPORT_XATTRS
281 rprintf(F," --fake-super store/recover privileged attrs using xattrs\n");
282 @@ -491,6 +493,9 @@ static struct poptOption long_options[] = {
283 {"times", 't', POPT_ARG_VAL, &preserve_times, 2, 0, 0 },
284 {"no-times", 0, POPT_ARG_VAL, &preserve_times, 0, 0, 0 },
285 {"no-t", 0, POPT_ARG_VAL, &preserve_times, 0, 0, 0 },
286 + {"crtimes", 'N', POPT_ARG_VAL, &preserve_crtimes, 1, 0, 0 },
287 + {"no-crtimes", 0, POPT_ARG_VAL, &preserve_crtimes, 0, 0, 0 },
288 + {"no-N", 0, POPT_ARG_VAL, &preserve_crtimes, 0, 0, 0 },
289 {"omit-dir-times", 'O', POPT_ARG_VAL, &omit_dir_times, 1, 0, 0 },
290 {"no-omit-dir-times",0, POPT_ARG_VAL, &omit_dir_times, 0, 0, 0 },
291 {"no-O", 0, POPT_ARG_VAL, &omit_dir_times, 0, 0, 0 },
292 @@ -1742,6 +1747,8 @@ void server_options(char **args, int *argc_p)
296 + if (preserve_crtimes)
300 else if (preserve_executability && am_sender)
301 diff --git a/rsync.c b/rsync.c
304 @@ -436,6 +436,14 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
305 if (ret == 0) /* ret == 1 if symlink could not be set */
308 + if (crtimes_ndx && !(flags & ATTRS_SKIP_CRTIME)) {
309 + time_t file_crtime = f_crtime(file);
310 + if (sxp->crtime == 0)
311 + sxp->crtime = get_create_time(fname);
312 + if (cmp_time(sxp->crtime, file_crtime) != 0
313 + && set_create_time(fname, file_crtime) == 0)
317 change_uid = am_root && uid_ndx && sxp->st.st_uid != (uid_t)F_OWNER(file);
318 change_gid = gid_ndx && !(file->flags & FLAG_SKIP_GROUP)
319 @@ -573,7 +581,7 @@ int finish_transfer(const char *fname, const char *fnametmp,
321 /* Change permissions before putting the file into place. */
322 set_file_attrs(fnametmp, file, NULL, fnamecmp,
323 - ok_to_set_time ? 0 : ATTRS_SKIP_MTIME);
324 + ok_to_set_time ? 0 : ATTRS_SKIP_MTIME | ATTRS_SKIP_CRTIME);
327 if (preserve_fileflags)
328 @@ -608,7 +616,7 @@ int finish_transfer(const char *fname, const char *fnametmp,
331 set_file_attrs(fnametmp, file, NULL, fnamecmp,
332 - ok_to_set_time ? 0 : ATTRS_SKIP_MTIME);
333 + ok_to_set_time ? 0 : ATTRS_SKIP_MTIME | ATTRS_SKIP_CRTIME);
335 if (temp_copy_name) {
336 if (do_rename(fnametmp, fname) < 0) {
337 diff --git a/rsync.h b/rsync.h
341 #define XMIT_RDEV_MINOR_8_pre30 (1<<11) /* protocols 28 - 29 */
342 #define XMIT_GROUP_NAME_FOLLOWS (1<<11) /* protocols 30 - now */
343 #define XMIT_HLINK_FIRST (1<<12) /* protocols 30 - now (HLINKED files only) */
344 +#define XMIT_CRTIME_EQ_MTIME (1<<13) /* protocols ?? - now */
345 #define XMIT_SAME_FLAGS (1<<14) /* protocols ?? - now */
347 /* These flags are used in the live flist data. */
350 #define ATTRS_REPORT (1<<0)
351 #define ATTRS_SKIP_MTIME (1<<1)
352 +#define ATTRS_SKIP_CRTIME (1<<2)
355 #define NORMAL_FLUSH 0
357 #define FNAMECMP_FUZZY 0x83
359 /* For use by the itemize_changes code */
360 -#define ITEM_REPORT_ATIME (1<<0)
361 +#define ITEM_REPORT_CRTIME (1<<0)
362 #define ITEM_REPORT_CHECKSUM (1<<1)
363 #define ITEM_REPORT_SIZE (1<<2)
364 #define ITEM_REPORT_TIME (1<<3)
365 @@ -635,6 +637,7 @@ extern int file_extra_cnt;
366 extern int inc_recurse;
369 +extern int crtimes_ndx;
370 extern int fileflags_ndx;
372 extern int xattrs_ndx;
373 @@ -642,6 +645,7 @@ extern int xattrs_ndx;
374 #define FILE_STRUCT_LEN (offsetof(struct file_struct, basename))
375 #define EXTRA_LEN (sizeof (union file_extras))
376 #define PTR_EXTRA_CNT ((sizeof (char *) + EXTRA_LEN - 1) / EXTRA_LEN)
377 +#define TIME_EXTRA_CNT ((SIZEOF_TIME_T + EXTRA_LEN - 1) / EXTRA_LEN)
378 #define DEV_EXTRA_CNT 2
379 #define DIRNODE_EXTRA_CNT 3
380 #define SUM_EXTRA_CNT ((MAX_DIGEST_LEN + EXTRA_LEN - 1) / EXTRA_LEN)
381 @@ -896,6 +900,7 @@ typedef struct {
387 struct rsync_acl *acc_acl; /* access ACL */
388 struct rsync_acl *def_acl; /* default ACL */
389 diff --git a/rsync.yo b/rsync.yo
392 @@ -350,6 +350,7 @@ to the detailed description below for a complete description. verb(
393 -D same as --devices --specials
394 -t, --times preserve modification times
395 -O, --omit-dir-times omit directories from --times
396 + -N, --crtimes preserve create (newness) times
397 --super receiver attempts super-user activities
398 --fake-super store/recover privileged attrs using xattrs
399 -S, --sparse handle sparse files efficiently
400 @@ -996,6 +997,9 @@ it is preserving modification times (see bf(--times)). If NFS is sharing
401 the directories on the receiving side, it is a good idea to use bf(-O).
402 This option is inferred if you use bf(--backup) without bf(--backup-dir).
404 +dit(bf(-N, --crtimes)) This tells rsync to set the create (newness) times of
405 +the destination files to the same value as the source files.
407 dit(bf(--super)) This tells the receiving side to attempt super-user
408 activities even if the receiving rsync wasn't run by the super-user. These
409 activities include: preserving users via the bf(--owner) option, preserving
410 @@ -1647,7 +1651,7 @@ with older versions of rsync, but that also turns on the output of other
413 The "%i" escape has a cryptic output that is 11 letters long. The general
414 -format is like the string bf(YXcstpoguax), where bf(Y) is replaced by the
415 +format is like the string bf(YXcstpognax), where bf(Y) is replaced by the
416 type of update being done, bf(X) is replaced by the file-type, and the
417 other letters represent attributes that may be output if they are being
419 @@ -1696,8 +1700,8 @@ quote(itemization(
420 sender's value (requires bf(--owner) and super-user privileges).
421 it() A bf(g) means the group is different and is being updated to the
422 sender's value (requires bf(--group) and the authority to set the group).
423 - it() The bf(u) slot is reserved for reporting update (access) time changes
424 - (a feature that is not yet released).
425 + it() A bf(n) means the create (newness) time is different and is being
426 + updated to the sender's value (requires bf(--crtimes)).
427 it() The bf(a) means that the ACL information changed.
428 it() The bf(x) slot is reserved for reporting extended attribute changes
429 (a feature that is not yet released).
430 diff --git a/syscall.c b/syscall.c
433 @@ -36,6 +36,11 @@ extern int list_only;
434 extern int preserve_perms;
435 extern int preserve_executability;
437 +struct create_time {
438 + unsigned long length;
439 + struct timespec crtime;
442 #define RETURN_ERROR_IF(x,e) \
445 @@ -300,3 +305,33 @@ char *d_name(struct dirent *di)
450 +time_t get_create_time(const char *path)
452 + static struct create_time attrBuf;
453 + struct attrlist attrList;
455 + memset(&attrList, 0, sizeof attrList);
456 + attrList.bitmapcount = ATTR_BIT_MAP_COUNT;
457 + attrList.commonattr = ATTR_CMN_CRTIME;
458 + if (getattrlist(path, &attrList, &attrBuf, sizeof attrBuf, FSOPT_NOFOLLOW) < 0)
460 + return attrBuf.crtime.tv_sec;
463 +int set_create_time(const char *path, time_t crtime)
465 + struct attrlist attrList;
466 + struct timespec ts;
468 + if (dry_run) return 0;
469 + RETURN_ERROR_IF_RO_OR_LO;
471 + ts.tv_sec = crtime;
474 + memset(&attrList, 0, sizeof attrList);
475 + attrList.bitmapcount = ATTR_BIT_MAP_COUNT;
476 + attrList.commonattr = ATTR_CMN_CRTIME;
477 + return setattrlist(path, &attrList, &ts, sizeof ts, FSOPT_NOFOLLOW);
479 diff --git a/testsuite/crtimes.test b/testsuite/crtimes.test
482 +++ b/testsuite/crtimes.test
486 +# Test rsync copying create times
488 +. "$suitedir/rsync.fns"
490 +# Setting an older time via touch sets the create time to the mtime.
491 +# Setting it to a newer time affects just the mtime.
494 +echo hiho "$fromdir/foo"
496 +touch -t 200101011111.11 "$fromdir"
497 +touch -t 200202022222.22 "$fromdir"
499 +touch -t 200111111111.11 "$fromdir/foo"
500 +touch -t 200212122222.22 "$fromdir/foo"
504 +checkit "$RSYNC -rtgvvv --crtimes \"$fromdir/\" \"$todir/\"" "$fromdir" "$todir"
506 +# The script would have aborted on error, so getting here means we've won.
508 diff --git a/tls.c b/tls.c
511 @@ -105,6 +105,8 @@ static int stat_xattr(const char *fname, STRUCT_STAT *fst)
515 +static int display_crtimes = 0;
517 static void failed(char const *what, char const *where)
519 fprintf(stderr, PROGRAM ": %s %s: %s\n",
520 @@ -112,16 +114,36 @@ static void failed(char const *what, char const *where)
524 +static void storetime(char *dest, time_t t, size_t destsize)
527 + struct tm *mt = gmtime(&t);
529 + snprintf(dest, destsize,
530 + "%04d-%02d-%02d %02d:%02d:%02d ",
531 + (int)mt->tm_year + 1900,
532 + (int)mt->tm_mon + 1,
538 + strlcpy(dest, " ", destsize);
541 static void list_file(const char *fname)
545 char permbuf[PERMSTRING_SIZE];
549 + char crtimebuf[50];
552 if (do_lstat(fname, &buf) < 0)
553 failed("stat", fname);
554 + if (display_crtimes && (crtime = get_create_time(fname)) == 0)
555 + failed("get_create_time", fname);
556 #ifdef SUPPORT_XATTRS
558 stat_xattr(fname, &buf);
559 @@ -154,19 +176,11 @@ static void list_file(const char *fname)
561 permstring(permbuf, buf.st_mode);
563 - if (buf.st_mtime) {
564 - mt = gmtime(&buf.st_mtime);
566 - snprintf(datebuf, sizeof datebuf,
567 - "%04d-%02d-%02d %02d:%02d:%02d",
568 - (int)mt->tm_year + 1900,
569 - (int)mt->tm_mon + 1,
575 - strlcpy(datebuf, " ", sizeof datebuf);
576 + storetime(mtimebuf, buf.st_mtime, sizeof mtimebuf);
577 + if (display_crtimes)
578 + storetime(crtimebuf, crtime, sizeof crtimebuf);
580 + crtimebuf[0] = '\0';
582 /* TODO: Perhaps escape special characters in fname? */
584 @@ -177,13 +191,14 @@ static void list_file(const char *fname)
585 (long)minor(buf.st_rdev));
586 } else /* NB: use double for size since it might not fit in a long. */
587 printf("%12.0f", (double)buf.st_size);
588 - printf(" %6ld.%-6ld %6ld %s %s%s\n",
589 + printf(" %6ld.%-6ld %6ld %s%s%s%s\n",
590 (long)buf.st_uid, (long)buf.st_gid, (long)buf.st_nlink,
591 - datebuf, fname, linkbuf);
592 + mtimebuf, crtimebuf, fname, linkbuf);
595 static struct poptOption long_options[] = {
596 /* longName, shortName, argInfo, argPtr, value, descrip, argDesc */
597 + {"crtimes", 'N', POPT_ARG_NONE, &display_crtimes, 0, 0, 0},
598 #ifdef SUPPORT_XATTRS
599 {"fake-super", 'f', POPT_ARG_VAL, &am_root, -1, 0, 0 },
601 @@ -197,6 +212,7 @@ static void tls_usage(int ret)
602 fprintf(F,"usage: " PROGRAM " [OPTIONS] FILE ...\n");
603 fprintf(F,"Trivial file listing program for portably checking rsync\n");
604 fprintf(F,"\nOptions:\n");
605 + fprintf(F," -N, --crtimes display create (newness) times\n");
606 #ifdef SUPPORT_XATTRS
607 fprintf(F," -f, --fake-super display attributes including fake-super xattrs\n");