]> git.ipfire.org Git - thirdparty/rsync.git/commitdiff
Add --crtimes option.
authorWayne Davison <wayne@opencoder.net>
Wed, 22 Jul 2020 19:12:18 +0000 (12:12 -0700)
committerWayne Davison <wayne@opencoder.net>
Wed, 22 Jul 2020 19:12:18 +0000 (12:12 -0700)
13 files changed:
.github/workflows/build.yml
NEWS.md
compat.c
flist.c
generator.c
log.c
options.c
rsync.1.md
rsync.c
rsync.h
syscall.c
testsuite/crtimes.test [new file with mode: 0644]
tls.c

index 8f52f2511ae656f333a7ca7109ff342e5d83d623..af45eead48ccd3d9776f503364be9d1da88c1621 100644 (file)
@@ -27,11 +27,11 @@ jobs:
     - name: info
       run: rsync --version
     - name: check
-      run: sudo RSYNC_MAX_SKIPPED=0 make check
+      run: sudo RSYNC_MAX_SKIPPED=1 make check
     - name: check30
-      run: sudo RSYNC_MAX_SKIPPED=0 make check30
+      run: sudo RSYNC_MAX_SKIPPED=1 make check30
     - name: check29
-      run: sudo RSYNC_MAX_SKIPPED=0 make check29
+      run: sudo RSYNC_MAX_SKIPPED=1 make check29
     - name: ssl file list
       run: rsync-ssl --no-motd download.samba.org::rsyncftp/ || true
     - name: save artifact
diff --git a/NEWS.md b/NEWS.md
index 3ae5f74c03393254cd374837b69e26ea46668b51..36d79af93f85f3690fdc12b564276ed998574709 100644 (file)
--- a/NEWS.md
+++ b/NEWS.md
@@ -39,6 +39,9 @@
    protocol (so if you used this patch in the past, be sure to update your
    converter script to use newlines instead of null chars).
 
+ - Added `--crtimes` (`-N`) option for preserving the file's create time (on
+   an OS that supports that, such as macOS).
+
  - Added the ability to specify "@netgroup" names to the `hosts allow` and
    `hosts deny` daemon parameters.  This is a finalized version of the
    netgroup-auth patch from the patches repo.
index 4719ef56a7fdd87e1178858adaa60872767c3ccc..bbabd11708bb4fc34ba78c1a16eeae0584ba0fb6 100644 (file)
--- a/compat.c
+++ b/compat.c
@@ -43,6 +43,7 @@ extern int protect_args;
 extern int preserve_uid;
 extern int preserve_gid;
 extern int preserve_atimes;
+extern int preserve_crtimes;
 extern int preserve_acls;
 extern int preserve_xattrs;
 extern int xfer_flags_as_varint;
@@ -76,7 +77,7 @@ int do_negotiated_strings = 0;
 int xmit_id0_names = 0;
 
 /* These index values are for the file-list's extra-attribute array. */
-int pathname_ndx, depth_ndx, atimes_ndx, uid_ndx, gid_ndx, acls_ndx, xattrs_ndx, unsort_ndx;
+int pathname_ndx, depth_ndx, atimes_ndx, crtimes_ndx, uid_ndx, gid_ndx, acls_ndx, xattrs_ndx, unsort_ndx;
 
 int receiver_symlink_times = 0; /* receiver can set the time on a symlink */
 int sender_symlink_iconv = 0;  /* sender should convert symlink content */
@@ -555,6 +556,8 @@ void setup_protocol(int f_out,int f_in)
         * aligned for direct int64-pointer memory access. */
        if (preserve_atimes)
                atimes_ndx = (file_extra_cnt += EXTRA64_CNT);
+       if (preserve_crtimes)
+               crtimes_ndx = (file_extra_cnt += EXTRA64_CNT);
        if (am_sender) /* This is most likely in the in64 union as well. */
                pathname_ndx = (file_extra_cnt += PTR_EXTRA_CNT);
        else
@@ -719,6 +722,10 @@ void setup_protocol(int f_out,int f_in)
                proper_seed_order = compat_flags & CF_CHKSUM_SEED_FIX ? 1 : 0;
                xfer_flags_as_varint = compat_flags & CF_VARINT_FLIST_FLAGS ? 1 : 0;
                xmit_id0_names = compat_flags & CF_ID0_NAMES ? 1 : 0;
+               if (!xfer_flags_as_varint && preserve_crtimes) {
+                       fprintf(stderr, "Both rsync versions must be at least 3.2.0 for --crtimes.\n");
+                       exit_cleanup(RERR_PROTOCOL);
+               }
                if (am_sender) {
                        receiver_symlink_times = am_server
                            ? strchr(client_info, 'L') != NULL
diff --git a/flist.c b/flist.c
index 6c2543cd2e9e3b0be1bf1ca4ff3b20a6734a6cb8..feec96d4966d58eb9bb9c23ee92be56d9f46ce26 100644 (file)
--- a/flist.c
+++ b/flist.c
@@ -56,6 +56,7 @@ extern int delete_during;
 extern int missing_args;
 extern int eol_nulls;
 extern int atimes_ndx;
+extern int crtimes_ndx;
 extern int relative_paths;
 extern int implied_dirs;
 extern int ignore_perishable;
@@ -378,6 +379,9 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
                            int ndx, int first_ndx)
 {
        static time_t modtime, atime;
+#ifdef SUPPORT_CRTIMES
+       static time_t crtime;
+#endif
        static mode_t mode;
 #ifdef SUPPORT_HARD_LINKS
        static int64 dev;
@@ -483,6 +487,13 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
                else
                        atime = F_ATIME(file);
        }
+#ifdef SUPPORT_CRTIMES
+       if (crtimes_ndx) {
+               crtime = F_CRTIME(file);
+               if (crtime == modtime)
+                       xflags |= XMIT_CRTIME_EQ_MTIME;
+       }
+#endif
 
 #ifdef SUPPORT_HARD_LINKS
        if (tmp_dev != -1) {
@@ -570,6 +581,10 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
        }
        if (xflags & XMIT_MOD_NSEC)
                write_varint(f, F_MOD_NSEC(file));
+#ifdef SUPPORT_CRTIMES
+       if (crtimes_ndx && !(xflags & XMIT_CRTIME_EQ_MTIME))
+               write_varlong(f, crtime, 4);
+#endif
        if (!(xflags & XMIT_SAME_MODE))
                write_int(f, to_wire_mode(mode));
        if (atimes_ndx && !S_ISDIR(mode) && !(xflags & XMIT_SAME_ATIME))
@@ -662,6 +677,9 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
 static struct file_struct *recv_file_entry(int f, struct file_list *flist, int xflags)
 {
        static int64 modtime, atime;
+#ifdef SUPPORT_CRTIMES
+       static time_t crtime;
+#endif
        static mode_t mode;
 #ifdef SUPPORT_HARD_LINKS
        static int64 dev;
@@ -776,6 +794,10 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x
                        mode = first->mode;
                        if (atimes_ndx && !S_ISDIR(mode))
                                atime = F_ATIME(first);
+#ifdef SUPPORT_CRTIMES
+                       if (crtimes_ndx)
+                               crtime = F_CRTIME(first);
+#endif
                        if (preserve_uid)
                                uid = F_OWNER(first);
                        if (preserve_gid)
@@ -815,6 +837,21 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x
                modtime_nsec = read_varint(f);
        else
                modtime_nsec = 0;
+#endif
+#ifdef SUPPORT_CRTIMES
+       if (crtimes_ndx) {
+               if (xflags & XMIT_CRTIME_EQ_MTIME)
+                       crtime = modtime;
+               else
+                       crtime = read_varlong(f, 4);
+#if SIZEOF_TIME_T < SIZEOF_INT64
+               if (!am_generator && (int64)(time_t)crtime != crtime) {
+                       rprintf(FERROR_XFER,
+                               "Create time value of %s truncated on receiver.\n",
+                               lastname);
+               }
+#endif
+       }
 #endif
        if (!(xflags & XMIT_SAME_MODE))
                mode = from_wire_mode(read_int(f));
@@ -997,6 +1034,10 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x
        }
        if (atimes_ndx && !S_ISDIR(mode))
                F_ATIME(file) = atime;
+#ifdef SUPPORT_CRTIMES
+       if (crtimes_ndx)
+               F_CRTIME(file) = crtime;
+#endif
        if (unsort_ndx)
                F_NDX(file) = flist->used + flist->ndx_start;
 
@@ -1394,6 +1435,10 @@ struct file_struct *make_file(const char *fname, struct file_list *flist,
                file->flags |= FLAG_OWNED_BY_US;
        if (atimes_ndx && !S_ISDIR(file->mode))
                F_ATIME(file) = st.st_atime;
+#ifdef SUPPORT_CRTIMES
+       if (crtimes_ndx)
+               F_CRTIME(file) = get_create_time(fname);
+#endif
 
        if (basename != thisname)
                file->dirname = lastdir;
index 1648db1c1df1c3f897a0d8a64cba8a330ca99716..f1780838aec5c87aff73b82b213e098c03470bda 100644 (file)
@@ -396,6 +396,19 @@ static inline int mtime_differs(STRUCT_STAT *stp, struct file_struct *file)
 #endif
 }
 
+static inline int any_time_differs(stat_x *sxp, struct file_struct *file, UNUSED(const char *fname))
+{
+       int differs = mtime_differs(&sxp->st, file);
+#ifdef SUPPORT_CRTIMES
+       if (!differs && crtimes_ndx) {
+               if (sxp->crtime == 0)
+                       sxp->crtime = get_create_time(fname);
+               differs = !same_time(sxp->crtime, 0, F_CRTIME(file), 0);
+       }
+#endif
+       return differs;
+}
+
 static inline int perms_differ(struct file_struct *file, stat_x *sxp)
 {
        if (preserve_perms)
@@ -450,7 +463,7 @@ int unchanged_attrs(const char *fname, struct file_struct *file, stat_x *sxp)
 {
        if (S_ISLNK(file->mode)) {
 #ifdef CAN_SET_SYMLINK_TIMES
-               if (preserve_times & PRESERVE_LINK_TIMES && mtime_differs(&sxp->st, file))
+               if (preserve_times & PRESERVE_LINK_TIMES && any_time_differs(sxp, file, fname))
                        return 0;
 #endif
 #ifdef CAN_CHMOD_SYMLINK
@@ -470,7 +483,7 @@ int unchanged_attrs(const char *fname, struct file_struct *file, stat_x *sxp)
                        return 0;
 #endif
        } else {
-               if (preserve_times && mtime_differs(&sxp->st, file))
+               if (preserve_times && any_time_differs(sxp, file, fname))
                        return 0;
                if (perms_differ(file, sxp))
                        return 0;
@@ -512,6 +525,14 @@ void itemize(const char *fnamecmp, struct file_struct *file, int ndx, int statre
                if (atimes_ndx && !S_ISDIR(file->mode) && !S_ISLNK(file->mode)
                 && !same_time(F_ATIME(file), 0, sxp->st.st_atime, 0))
                        iflags |= ITEM_REPORT_ATIME;
+#ifdef SUPPORT_CRTIMES
+               if (crtimes_ndx) {
+                       if (sxp->crtime == 0)
+                               sxp->crtime = get_create_time(fnamecmp);
+                       if (!same_time(sxp->crtime, 0, F_CRTIME(file), 0))
+                               iflags |= ITEM_REPORT_CRTIME;
+               }
+#endif
 #if !defined HAVE_LCHMOD && !defined HAVE_SETATTRLIST
                if (S_ISLNK(file->mode)) {
                        ;
@@ -1131,6 +1152,7 @@ static void list_file_entry(struct file_struct *f)
        int size_width = human_readable ? 14 : 11;
        int mtime_width = 1 + strlen(mtime_str);
        int atime_width = atimes_ndx ? mtime_width : 0;
+       int crtime_width = crtimes_ndx ? mtime_width : 0;
 
        if (!F_IS_ACTIVE(f)) {
                /* this can happen if duplicate names were removed */
@@ -1141,10 +1163,11 @@ static void list_file_entry(struct file_struct *f)
 
        if (missing_args == 2 && f->mode == 0) {
                rprintf(FINFO, "%-*s %s\n",
-                       10 + 1 + size_width + mtime_width + atime_width, "*missing",
+                       10 + 1 + size_width + mtime_width + atime_width + crtime_width, "*missing",
                        f_name(f, NULL));
        } else {
                const char *atime_str = atimes_ndx && !S_ISDIR(f->mode) ? timestring(F_ATIME(f)) : "";
+               const char *crtime_str = crtimes_ndx ? timestring(F_CRTIME(f)) : "";
                const char *arrow, *lnk;
 
                permstring(permbuf, f->mode);
@@ -1157,9 +1180,9 @@ static void list_file_entry(struct file_struct *f)
 #endif
                        arrow = lnk = "";
 
-               rprintf(FINFO, "%s %*s %s%*s %s%s%s\n",
+               rprintf(FINFO, "%s %*s %s%*s%*s %s%s%s\n",
                        permbuf, size_width, human_num(F_LENGTH(f)),
-                       timestring(f->modtime), atime_width, atime_str,
+                       timestring(f->modtime), atime_width, atime_str, crtime_width, crtime_str,
                        f_name(f, NULL), arrow, lnk);
        }
 }
@@ -1255,6 +1278,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
                        return;
                }
        }
+       sx.crtime = 0;
 
        if (dry_run > 1 || (dry_missing_dir && is_below(file, dry_missing_dir))) {
                int i;
diff --git a/log.c b/log.c
index 85eae3d5ff383a2b3f29c927c5d2d7a0fbaac42a..0dc26331ae260da691c0765617111de2a1b878ad 100644 (file)
--- a/log.c
+++ b/log.c
@@ -720,8 +720,9 @@ static void log_formatted(enum logcode code, const char *format, const char *op,
                        c[5] = !(iflags & ITEM_REPORT_PERMS) ? '.' : 'p';
                        c[6] = !(iflags & ITEM_REPORT_OWNER) ? '.' : 'o';
                        c[7] = !(iflags & ITEM_REPORT_GROUP) ? '.' : 'g';
-                       c[8] = !(iflags & ITEM_REPORT_ATIME) ? '.'
-                            : S_ISLNK(file->mode) ? 'U' : 'u';
+                       c[8] = !(iflags & (ITEM_REPORT_ATIME|ITEM_REPORT_CRTIME)) ? '.'
+                            : BITS_SET(iflags, ITEM_REPORT_ATIME|ITEM_REPORT_CRTIME) ? 'b'
+                            : iflags & ITEM_REPORT_ATIME ? 'u' : 'n';
                        c[9] = !(iflags & ITEM_REPORT_ACL) ? '.' : 'a';
                        c[10] = !(iflags & ITEM_REPORT_XATTR) ? '.' : 'x';
                        c[11] = '\0';
index 0ecabe8d6dcdd4c54915139a188a4b2dd9200113..593352690dab1ec14ea71f8c022081b94c9a2346 100644 (file)
--- a/options.c
+++ b/options.c
@@ -64,6 +64,7 @@ int preserve_uid = 0;
 int preserve_gid = 0;
 int preserve_times = 0;
 int preserve_atimes = 0;
+int preserve_crtimes = 0;
 int update_only = 0;
 int open_noatime = 0;
 int cvs_exclude = 0;
@@ -670,6 +671,11 @@ static void print_info_flags(enum logcode f)
 #endif
                        "stop-at",
 
+#ifndef SUPPORT_CRTIMES
+               "no "
+#endif
+                       "crtimes",
+
        "*Optimizations",
 
 #ifndef HAVE_SIMD
@@ -838,6 +844,9 @@ static struct poptOption long_options[] = {
   {"no-U",             0,  POPT_ARG_VAL,    &preserve_atimes, 0, 0, 0 },
   {"open-noatime",     0,  POPT_ARG_VAL,    &open_noatime, 1, 0, 0 },
   {"no-open-noatime",  0,  POPT_ARG_VAL,    &open_noatime, 0, 0, 0 },
+  {"crtimes",         'N', POPT_ARG_VAL,    &preserve_crtimes, 1, 0, 0 },
+  {"no-crtimes",       0,  POPT_ARG_VAL,    &preserve_crtimes, 0, 0, 0 },
+  {"no-N",             0,  POPT_ARG_VAL,    &preserve_crtimes, 0, 0, 0 },
   {"omit-dir-times",  'O', POPT_ARG_VAL,    &omit_dir_times, 1, 0, 0 },
   {"no-omit-dir-times",0,  POPT_ARG_VAL,    &omit_dir_times, 0, 0, 0 },
   {"no-O",             0,  POPT_ARG_VAL,    &omit_dir_times, 0, 0, 0 },
@@ -1211,6 +1220,9 @@ static void set_refuse_options(void)
 #ifndef HAVE_SETVBUF
        parse_one_refuse_match(0, "outbuf", list_end);
 #endif
+#ifndef SUPPORT_CRTIMES
+       parse_one_refuse_match(0, "crtimes", list_end);
+#endif
 
        /* Now we use the descrip values to actually mark the options for refusal. */
        for (op = long_options; op != list_end; op++) {
@@ -2738,6 +2750,10 @@ void server_options(char **args, int *argc_p)
                if (preserve_atimes > 1)
                        argstr[x++] = 'U';
        }
+#ifdef SUPPORT_CRTIMES
+       if (preserve_crtimes)
+               argstr[x++] = 'N';
+#endif
        if (preserve_perms)
                argstr[x++] = 'p';
        else if (preserve_executability && am_sender)
index 59619d7ca19a81c10b5c6f9fdfa9a18c4737e885..dcf69e52534a012e558b038a656666928d885e53 100644 (file)
@@ -372,6 +372,7 @@ detailed description below for a complete description.
 --times, -t              preserve modification times
 --atimes, -U             preserve access (use) times
 --open-noatime           avoid changing the atime on opened files
+--crtimes, -N            preserve create times (newness)
 --omit-dir-times, -O     omit directories from --times
 --omit-link-times, -J    omit symlinks from --times
 --super                  receiver attempts super-user activities
@@ -1341,6 +1342,11 @@ your home directory (remove the '=' for that).
     mounted to avoid updating the atime on read access even without the
     O_NOATIME flag being set.
 
+0.  `--crtimes`, `-N,`
+
+    This tells rsync to set the create times (newness) of +the destination
+    files to the same value as the source files.
+
 0.  `--omit-dir-times`, `-O`
 
     This tells rsync to omit directories when it is preserving modification
@@ -2673,10 +2679,11 @@ your home directory (remove the '=' for that).
       value (requires `--owner` and super-user privileges).
     - A `g` means the group is different and is being updated to the sender's
       value (requires `--group` and the authority to set the group).
-    - A `u` means the access (use) time is different and is being updated to
-      the sender's value (requires `--atimes`).  An alternate value of `U`
-      means that the access time will be set to the transfer time, which
-      happens when a symlink or directory is updated.
+    - A `u`|`n`|`b` indicates the following information: `u`  means the access
+      (use) time is different and is being updated to the sender's value
+      (requires `--atimes`); `n` means the create time (newness) is different
+      and is being updated to the sender's value (requires `--crtimes`); `b`
+      means that both the access and create times are being updated.
     - The `a` means that the ACL information is being changed.
     - The `x` means that the extended attribute information is being changed.
 
diff --git a/rsync.c b/rsync.c
index e091cbef52f2dbe94e14e8f276fc78a7304a9c66..e7f1f96a8f814c0494bd5935bce9eb5615b4c9ec 100644 (file)
--- a/rsync.c
+++ b/rsync.c
@@ -584,6 +584,9 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
                memcpy(&sx2.st, &sxp->st, sizeof (sx2.st));
        if (!atimes_ndx || S_ISDIR(sxp->st.st_mode))
                flags |= ATTRS_SKIP_ATIME;
+       /* Don't set the creation date on the root folder of an HFS+ volume. */
+       if (sxp->st.st_ino == 2 && S_ISDIR(sxp->st.st_mode))
+               flags |= ATTRS_SKIP_CRTIME;
        if (!(flags & ATTRS_SKIP_MTIME) && !same_mtime(file, &sxp->st, flags & ATTRS_ACCURATE_TIME)) {
                sx2.st.st_mtime = file->modtime;
 #ifdef ST_MTIME_NSEC
@@ -613,6 +616,16 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
                        file->flags |= FLAG_TIME_FAILED;
                }
        }
+#ifdef SUPPORT_CRTIMES
+       if (crtimes_ndx && !(flags & ATTRS_SKIP_CRTIME)) {
+               time_t file_crtime = F_CRTIME(file);
+               if (sxp->crtime == 0)
+                       sxp->crtime = get_create_time(fname);
+               if (!same_time(sxp->crtime, 0L, file_crtime, 0L)
+                && set_create_time(fname, file_crtime) == 0)
+                       updated = 1;
+       }
+#endif
 
 #ifdef SUPPORT_ACLS
        /* It's OK to call set_acl() now, even for a dir, as the generator
@@ -718,7 +731,7 @@ int finish_transfer(const char *fname, const char *fnametmp,
 
        /* Change permissions before putting the file into place. */
        set_file_attrs(fnametmp, file, NULL, fnamecmp,
-                      ok_to_set_time ? ATTRS_ACCURATE_TIME : ATTRS_SKIP_MTIME | ATTRS_SKIP_ATIME);
+                      ok_to_set_time ? ATTRS_ACCURATE_TIME : ATTRS_SKIP_MTIME | ATTRS_SKIP_ATIME | ATTRS_SKIP_CRTIME);
 
        /* move tmp file over real file */
        if (DEBUG_GTE(RECV, 1))
@@ -743,7 +756,7 @@ int finish_transfer(const char *fname, const char *fnametmp,
 
   do_set_file_attrs:
        set_file_attrs(fnametmp, file, NULL, fnamecmp,
-                      ok_to_set_time ? ATTRS_ACCURATE_TIME : ATTRS_SKIP_MTIME | ATTRS_SKIP_ATIME);
+                      ok_to_set_time ? ATTRS_ACCURATE_TIME : ATTRS_SKIP_MTIME | ATTRS_SKIP_ATIME | ATTRS_SKIP_CRTIME);
 
        if (temp_copy_name) {
                if (do_rename(fnametmp, fname) < 0) {
diff --git a/rsync.h b/rsync.h
index c871574748c92459a5bc792c69212529883e989e..b8bdd786234f72a5ad90e3380173e2ad9649b852 100644 (file)
--- a/rsync.h
+++ b/rsync.h
@@ -70,7 +70,7 @@
 /* The following XMIT flags require an rsync that uses a varint for the flag values */
 
 #define XMIT_RESERVED_16 (1<<16)       /* reserved for future fileflags use */
-#define XMIT_RESERVED_17 (1<<17)       /* reserved for future crtimes use */
+#define XMIT_CRTIME_EQ_MTIME (1<<17)   /* any protocol - restricted by command-line option */
 
 /* These flags are used in the live flist data. */
 
 #define ATTRS_SKIP_MTIME       (1<<1)
 #define ATTRS_ACCURATE_TIME    (1<<2)
 #define ATTRS_SKIP_ATIME       (1<<3)
+#define ATTRS_SKIP_CRTIME      (1<<5)
 
 #define MSG_FLUSH      2
 #define FULL_FLUSH     1
 #define ITEM_REPORT_GROUP (1<<6)
 #define ITEM_REPORT_ACL (1<<7)
 #define ITEM_REPORT_XATTR (1<<8)
+#define ITEM_REPORT_CRTIME (1<<10)
 #define ITEM_BASIS_TYPE_FOLLOWS (1<<11)
 #define ITEM_XNAME_FOLLOWS (1<<12)
 #define ITEM_IS_NEW (1<<13)
@@ -568,6 +570,10 @@ typedef unsigned int size_t;
 #endif
 #endif
 
+#ifdef HAVE_GETATTRLIST
+#define SUPPORT_CRTIMES 1
+#endif
+
 /* Find a variable that is either exactly 32-bits or longer.
  * If some code depends on 32-bit truncation, it will need to
  * take special action in a "#if SIZEOF_INT32 > 4" section. */
@@ -789,6 +795,7 @@ struct file_struct {
 extern int file_extra_cnt;
 extern int inc_recurse;
 extern int atimes_ndx;
+extern int crtimes_ndx;
 extern int pathname_ndx;
 extern int depth_ndx;
 extern int uid_ndx;
@@ -851,6 +858,7 @@ extern int xattrs_ndx;
 #define F_XATTR(f) REQ_EXTRA(f, xattrs_ndx)->num
 #define F_NDX(f) REQ_EXTRA(f, unsort_ndx)->num
 #define F_ATIME(f) REQ_EXTRA64(f, atimes_ndx)->num
+#define F_CRTIME(f) REQ_EXTRA64(f, crtimes_ndx)->num
 
 /* These items are per-entry optional: */
 #define F_HL_GNUM(f) OPT_EXTRA(f, START_BUMP(f))->num /* non-dirs */
@@ -1103,6 +1111,7 @@ typedef struct {
 
 typedef struct {
     STRUCT_STAT st;
+    time_t crtime;
 #ifdef SUPPORT_ACLS
     struct rsync_acl *acc_acl; /* access ACL */
     struct rsync_acl *def_acl; /* default ACL */
index e98bc98ee869b96425ceb5c333323e6e02f7bb52..80cac204bf3095a38de6fff8eea794d36cdef671 100644 (file)
--- a/syscall.c
+++ b/syscall.c
@@ -54,6 +54,15 @@ extern int open_noatime;
 # endif
 #endif
 
+#ifdef SUPPORT_CRTIMES
+#pragma pack(push, 4)
+struct create_time {
+       uint32 length;
+       struct timespec crtime;
+};
+#pragma pack(pop)
+#endif
+
 #define RETURN_ERROR_IF(x,e) \
        do { \
                if (x) { \
@@ -385,6 +394,40 @@ int do_setattrlist_times(const char *fname, STRUCT_STAT *stp)
 }
 #endif
 
+#ifdef SUPPORT_CRTIMES
+time_t get_create_time(const char *path)
+{
+       static struct create_time attrBuf;
+       struct attrlist attrList;
+
+       memset(&attrList, 0, sizeof attrList);
+       attrList.bitmapcount = ATTR_BIT_MAP_COUNT;
+       attrList.commonattr = ATTR_CMN_CRTIME;
+       if (getattrlist(path, &attrList, &attrBuf, sizeof attrBuf, FSOPT_NOFOLLOW) < 0)
+               return 0;
+       return attrBuf.crtime.tv_sec;
+}
+#endif
+
+#ifdef SUPPORT_CRTIMES
+int set_create_time(const char *path, time_t crtime)
+{
+       struct attrlist attrList;
+       struct timespec ts;
+
+       if (dry_run) return 0;
+       RETURN_ERROR_IF_RO_OR_LO;
+
+       ts.tv_sec = crtime;
+       ts.tv_nsec = 0;
+
+       memset(&attrList, 0, sizeof attrList);
+       attrList.bitmapcount = ATTR_BIT_MAP_COUNT;
+       attrList.commonattr = ATTR_CMN_CRTIME;
+       return setattrlist(path, &attrList, &ts, sizeof ts, FSOPT_NOFOLLOW);
+}
+#endif
+
 #ifdef HAVE_UTIMENSAT
 int do_utimensat(const char *fname, STRUCT_STAT *stp)
 {
diff --git a/testsuite/crtimes.test b/testsuite/crtimes.test
new file mode 100644 (file)
index 0000000..aa71b45
--- /dev/null
@@ -0,0 +1,26 @@
+#! /bin/sh
+
+# Test rsync copying create times
+
+. "$suitedir/rsync.fns"
+
+$RSYNC --version | grep "[, ] crtimes" >/dev/null || test_skipped "Rsync is configured without crtimes support"
+
+# Setting an older time via touch sets the create time to the mtime.
+# Setting it to a newer time affects just the mtime.
+
+mkdir "$fromdir"
+echo hiho "$fromdir/foo"
+
+touch -t 200101011111.11 "$fromdir"
+touch -t 200202022222.22 "$fromdir"
+
+touch -t 200111111111.11 "$fromdir/foo"
+touch -t 200212122222.22 "$fromdir/foo"
+
+TLS_ARGS=--crtimes
+
+checkit "$RSYNC -rtgvvv --crtimes \"$fromdir/\" \"$todir/\"" "$fromdir" "$todir"
+
+# The script would have aborted on error, so getting here means we've won.
+exit 0
diff --git a/tls.c b/tls.c
index ed4490bb68507aeca101ffa1f5140be0853c7a0f..c50fa6c3e81bc8f87c5b842b873e58428324db2c 100644 (file)
--- a/tls.c
+++ b/tls.c
@@ -108,6 +108,9 @@ static int stat_xattr(const char *fname, STRUCT_STAT *fst)
 #endif
 
 static int display_atimes = 0;
+#ifdef SUPPORT_CRTIMES
+static int display_crtimes = 0;
+#endif
 
 static void failed(char const *what, char const *where)
 {
@@ -143,14 +146,22 @@ static void storetime(char *dest, size_t destsize, time_t t, int nsecs)
 static void list_file(const char *fname)
 {
        STRUCT_STAT buf;
+#ifdef SUPPORT_CRTIMES
+       time_t crtime = 0;
+#endif
        char permbuf[PERMSTRING_SIZE];
        char mtimebuf[50];
        char atimebuf[50];
+       char crtimebuf[50];
        char linkbuf[4096];
        int nsecs;
 
        if (do_lstat(fname, &buf) < 0)
                failed("stat", fname);
+#ifdef SUPPORT_CRTIMES
+       if (display_crtimes && (crtime = get_create_time(fname)) == 0)
+               failed("get_create_time", fname);
+#endif
 #ifdef SUPPORT_XATTRS
        if (am_root < 0)
                stat_xattr(fname, &buf);
@@ -195,6 +206,12 @@ static void list_file(const char *fname)
                storetime(atimebuf, sizeof atimebuf, S_ISDIR(buf.st_mode) ? 0 : buf.st_atime, -1);
        else
                atimebuf[0] = '\0';
+#ifdef SUPPORT_CRTIMES
+       if (display_crtimes)
+               storetime(crtimebuf, sizeof crtimebuf, crtime, -1);
+       else
+#endif
+               crtimebuf[0] = '\0';
 
        /* TODO: Perhaps escape special characters in fname? */
        printf("%s ", permbuf);
@@ -204,14 +221,17 @@ static void list_file(const char *fname)
        } else
                printf("%15s", do_big_num(buf.st_size, 1, NULL));
 
-       printf(" %6ld.%-6ld %6ld%s%s %s%s\n",
+       printf(" %6ld.%-6ld %6ld%s%s%s %s%s\n",
               (long)buf.st_uid, (long)buf.st_gid, (long)buf.st_nlink,
-              mtimebuf, atimebuf, fname, linkbuf);
+              mtimebuf, atimebuf, crtimebuf, fname, linkbuf);
 }
 
 static struct poptOption long_options[] = {
   /* longName, shortName, argInfo, argPtr, value, descrip, argDesc */
   {"atimes",          'U', POPT_ARG_NONE,   &display_atimes, 0, 0, 0},
+#ifdef SUPPORT_CRTIMES
+  {"crtimes",         'N', POPT_ARG_NONE,   &display_crtimes, 0, 0, 0},
+#endif
   {"link-times",      'l', POPT_ARG_NONE,   &link_times, 0, 0, 0 },
   {"link-owner",      'L', POPT_ARG_NONE,   &link_owner, 0, 0, 0 },
 #ifdef SUPPORT_XATTRS
@@ -231,6 +251,9 @@ static void NORETURN tls_usage(int ret)
   fprintf(F,"Trivial file listing program for portably checking rsync\n");
   fprintf(F,"\nOptions:\n");
   fprintf(F," -U, --atimes                display access (last-used) times\n");
+#ifdef SUPPORT_CRTIMES
+  fprintf(F," -N, --crtimes               display create times (newness)\n");
+#endif
   fprintf(F," -l, --link-times            display the time on a symlink\n");
   fprintf(F," -L, --link-owner            display the owner+group on a symlink\n");
 #ifdef SUPPORT_XATTRS