This patch provides --fileflags, which preserves the st_flags stat() field. Modified from a patch that was written by Rolf Grossmann. To use this patch, run these commands for a successful build: patch -p1 mode; +#ifdef SUPPORT_FLAGS + if (fileflags_ndx) { + if (F_FFLAGS(file) == fileflags) + xflags |= XMIT_SAME_FLAGS; + else + fileflags = F_FFLAGS(file); + } +#endif if ((preserve_devices && IS_DEVICE(mode)) || (preserve_specials && IS_SPECIAL(mode))) { @@ -522,6 +534,10 @@ static void send_file_entry(int f, struct file_struct *file, int ndx, int first_ } if (!(xflags & XMIT_SAME_MODE)) write_int(f, to_wire_mode(mode)); +#ifdef SUPPORT_FLAGS + if (fileflags_ndx && !(xflags & XMIT_SAME_FLAGS)) + write_int(f, (int)fileflags); +#endif if (uid_ndx && !(xflags & XMIT_SAME_UID)) { if (protocol_version < 30) write_int(f, uid); @@ -610,6 +626,9 @@ static struct file_struct *recv_file_entry(struct file_list *flist, { static int64 modtime; static mode_t mode; +#ifdef SUPPORT_FLAGS + static uint32 fileflags; +#endif #ifdef SUPPORT_HARD_LINKS static int64 dev; #endif @@ -744,6 +763,10 @@ static struct file_struct *recv_file_entry(struct file_list *flist, if (chmod_modes && !S_ISLNK(mode)) mode = tweak_mode(mode, chmod_modes); +#ifdef SUPPORT_FLAGS + if (fileflags_ndx && !(xflags & XMIT_SAME_FLAGS)) + fileflags = (uint32)read_int(f); +#endif if (uid_ndx && !(xflags & XMIT_SAME_UID)) { if (protocol_version < 30) @@ -865,6 +888,10 @@ static struct file_struct *recv_file_entry(struct file_list *flist, OPT_EXTRA(file, 0)->unum = (uint32)(file_length >> 32); } file->mode = mode; +#ifdef SUPPORT_FLAGS + if (fileflags_ndx) + F_FFLAGS(file) = fileflags; +#endif if (uid_ndx) F_OWNER(file) = uid; if (gid_ndx) { @@ -1199,6 +1226,10 @@ struct file_struct *make_file(const char *fname, struct file_list *flist, OPT_EXTRA(file, 0)->unum = (uint32)(st.st_size >> 32); } file->mode = st.st_mode; +#ifdef SUPPORT_FLAGS + if (fileflags_ndx) + F_FFLAGS(file) = st.st_flags; +#endif if (uid_ndx) F_OWNER(file) = st.st_uid; if (gid_ndx) diff --git a/generator.c b/generator.c --- a/generator.c +++ b/generator.c @@ -122,6 +122,7 @@ static const char *solo_file = NULL; #define DEL_FOR_SYMLINK (1<<5) /* making room for a replacement symlink */ #define DEL_FOR_DEVICE (1<<6) /* making room for a replacement device */ #define DEL_FOR_SPECIAL (1<<7) /* making room for a replacement special */ +#define DEL_AN_IMMUTABLE (1<<8) /* item has an immutable flag set */ #define DEL_MAKE_ROOM (DEL_FOR_FILE|DEL_FOR_DIR|DEL_FOR_SYMLINK|DEL_FOR_DEVICE|DEL_FOR_SPECIAL) @@ -161,6 +162,10 @@ static enum delret delete_item(char *fbuf, int mode, int flags) if (!am_root && !(mode & S_IWUSR) && flags & DEL_OWNED_BY_US) do_chmod(fbuf, mode |= S_IWUSR); +#ifdef SUPPORT_FLAGS + if (fileflags_ndx && flags & DEL_AN_IMMUTABLE) + make_mutable(fbuf, mode, NODELETE_FLAGS); +#endif if (S_ISDIR(mode) && !(flags & DEL_DIR_IS_EMPTY)) { ignore_perishable = 1; @@ -282,6 +287,14 @@ static enum delret delete_dir_contents(char *fname, int flags) flags |= DEL_OWNED_BY_US; else flags &= ~DEL_OWNED_BY_US; +#ifdef SUPPORT_FLAGS + if (fileflags_ndx) { + if (F_FFLAGS(fp) & NODELETE_FLAGS) + flags |= DEL_AN_IMMUTABLE; + else + flags &= ~DEL_AN_IMMUTABLE; + } +#endif /* Save stack by recursing to ourself directly. */ if (S_ISDIR(fp->mode)) { if (!am_root && !(fp->mode & S_IWUSR) && flags & DEL_OWNED_BY_US) @@ -341,15 +354,22 @@ static int flush_delete_delay(void) static int remember_delete(struct file_struct *file, const char *fname, int flags) { - const char *plus = (!am_root && !(file->mode & S_IWUSR) && flags & DEL_OWNED_BY_US) - ? "+" : ""; + char buf[16], *bp = buf; int len; + if (!am_root && !(file->mode & S_IWUSR) && flags & DEL_OWNED_BY_US) + *bp++ = '+'; +#ifdef SUPPORT_FLAGS + if (flags & DEL_AN_IMMUTABLE) + *bp++ = '-'; +#endif + *bp = '\0'; + while (1) { len = snprintf(deldelay_buf + deldelay_cnt, deldelay_size - deldelay_cnt, "%s%x %s%c", - plus, (int)file->mode, fname, '\0'); + buf, (int)file->mode, fname, '\0'); if ((deldelay_cnt += len) <= deldelay_size) break; if (deldelay_fd < 0 && !start_delete_delay_temp()) @@ -362,10 +382,10 @@ static int remember_delete(struct file_struct *file, const char *fname, int flag return 1; } -static int read_delay_line(char *buf, int *own_flag_p) +static int read_delay_line(char *buf, int *flags_p) { static int read_pos = 0; - int j, len, mode; + int j, len, mode, flags = 0; char *bp, *past_space; while (1) { @@ -405,9 +425,15 @@ static int read_delay_line(char *buf, int *own_flag_p) bp = deldelay_buf + read_pos; if (*bp == '+') { bp++; - *own_flag_p = DEL_OWNED_BY_US; - } else - *own_flag_p = 0; + flags |= DEL_OWNED_BY_US; + } +#ifdef SUPPORT_FLAGS + if (*bp == '-') { + bp++; + flags |= DEL_AN_IMMUTABLE; + } +#endif + *flags_p = flags; if (sscanf(bp, "%x ", &mode) != 1) { invalid_data: @@ -432,15 +458,15 @@ static int read_delay_line(char *buf, int *own_flag_p) static void do_delayed_deletions(char *delbuf) { - int mode, own_flag; + int mode, flags; if (deldelay_fd >= 0) { if (deldelay_cnt && !flush_delete_delay()) return; lseek(deldelay_fd, 0, 0); } - while ((mode = read_delay_line(delbuf, &own_flag)) >= 0) - delete_item(delbuf, mode, own_flag | DEL_RECURSE); + while ((mode = read_delay_line(delbuf, &flags)) >= 0) + delete_item(delbuf, mode, DEL_RECURSE | flags); if (deldelay_fd >= 0) close(deldelay_fd); } @@ -503,6 +529,9 @@ static void delete_in_dir(char *fbuf, struct file_struct *file, dev_t *fs_dev) } if (flist_find(cur_flist, fp) < 0) { int flags = DEL_RECURSE +#ifdef SUPPORT_FLAGS + | (fileflags_ndx && F_FFLAGS(fp) & NODELETE_FLAGS ? DEL_AN_IMMUTABLE : 0) +#endif | (!uid_ndx || (uid_t)F_OWNER(fp) == our_uid ? DEL_OWNED_BY_US : 0); f_name(fp, delbuf); if (delete_during == 2) { @@ -1316,6 +1345,10 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx, if (statret == 0 && sx.st.st_uid == our_uid) del_opts |= DEL_OWNED_BY_US; +#ifdef SUPPORT_FLAGS + if (statret == 0 && fileflags_ndx && sx.st.st_flags & NODELETE_FLAGS) + del_opts |= DEL_AN_IMMUTABLE; +#endif if (S_ISDIR(file->mode)) { if (!implied_dirs && file->flags & FLAG_IMPLIED_DIR) diff --git a/options.c b/options.c --- a/options.c +++ b/options.c @@ -52,6 +52,7 @@ int preserve_hard_links = 0; int preserve_acls = 0; int preserve_xattrs = 0; int preserve_perms = 0; +int preserve_fileflags = 0; int preserve_executability = 0; int preserve_devices = 0; int preserve_specials = 0; @@ -223,6 +224,7 @@ static void print_rsync_version(enum logcode f) char const *links = "no "; char const *iconv = "no "; char const *ipv6 = "no "; + char const *fileflags = "no "; STRUCT_STAT *dumstat; #if SUBPROTOCOL_VERSION != 0 @@ -252,6 +254,9 @@ static void print_rsync_version(enum logcode f) #ifdef ICONV_OPTION iconv = ""; #endif +#ifdef SUPPORT_FLAGS + fileflags = ""; +#endif rprintf(f, "%s version %s protocol version %d%s\n", RSYNC_NAME, RSYNC_VERSION, PROTOCOL_VERSION, subprotocol); @@ -265,8 +270,8 @@ static void print_rsync_version(enum logcode f) (int)(sizeof (int64) * 8)); rprintf(f, " %ssocketpairs, %shardlinks, %ssymlinks, %sIPv6, batchfiles, %sinplace,\n", got_socketpair, hardlinks, links, ipv6, have_inplace); - rprintf(f, " %sappend, %sACLs, %sxattrs, %siconv\n", - have_inplace, acls, xattrs, iconv); + rprintf(f, " %sappend, %sACLs, %sxattrs, %siconv, %sfile-flags\n", + have_inplace, acls, xattrs, iconv, fileflags); #ifdef MAINTAINER_MODE rprintf(f, "Panic Action: \"%s\"\n", get_panic_action()); @@ -333,6 +338,7 @@ void usage(enum logcode F) rprintf(F," -K, --keep-dirlinks treat symlinked dir on receiver as dir\n"); rprintf(F," -H, --hard-links preserve hard links\n"); rprintf(F," -p, --perms preserve permissions\n"); + rprintf(F," --fileflags preserve file-flags\n"); rprintf(F," -E, --executability preserve the file's executability\n"); rprintf(F," --chmod=CHMOD affect file and/or directory permissions\n"); #ifdef SUPPORT_ACLS @@ -473,6 +479,8 @@ static struct poptOption long_options[] = { {"perms", 'p', POPT_ARG_VAL, &preserve_perms, 1, 0, 0 }, {"no-perms", 0, POPT_ARG_VAL, &preserve_perms, 0, 0, 0 }, {"no-p", 0, POPT_ARG_VAL, &preserve_perms, 0, 0, 0 }, + {"fileflags", 0, POPT_ARG_VAL, &preserve_fileflags, 1, 0, 0 }, + {"no-fileflags", 0, POPT_ARG_VAL, &preserve_fileflags, 0, 0, 0 }, {"executability", 'E', POPT_ARG_NONE, &preserve_executability, 0, 0, 0 }, {"acls", 'A', POPT_ARG_NONE, 0, 'A', 0, 0 }, {"no-acls", 0, POPT_ARG_VAL, &preserve_acls, 0, 0, 0 }, @@ -1289,6 +1297,15 @@ int parse_arguments(int *argc_p, const char ***argv_p, int frommain) } #endif +#ifndef SUPPORT_FLAGS + if (preserve_fileflags) { + snprintf(err_buf, sizeof err_buf, + "the --fileflags option is not supported on this %s\n", + am_server ? "server" : "client"); + return 0; + } +#endif + if (write_batch && read_batch) { snprintf(err_buf, sizeof err_buf, "--write-batch and --read-batch can not be used together\n"); @@ -1804,6 +1821,9 @@ void server_options(char **args, int *argc_p) if (xfer_dirs && !recurse && delete_mode && am_sender) args[ac++] = "--no-r"; + if (preserve_fileflags) + args[ac++] = "--fileflags"; + if (do_compression && def_compress_level != Z_DEFAULT_COMPRESSION) { if (asprintf(&arg, "--compress-level=%d", def_compress_level) < 0) goto oom; diff --git a/rsync.c b/rsync.c --- a/rsync.c +++ b/rsync.c @@ -32,6 +32,7 @@ extern int dry_run; extern int preserve_acls; extern int preserve_xattrs; extern int preserve_perms; +extern int preserve_fileflags; extern int preserve_executability; extern int preserve_times; extern int am_root; @@ -338,6 +339,41 @@ mode_t dest_mode(mode_t flist_mode, mode_t stat_mode, int dflt_perms, return new_mode; } +#ifdef SUPPORT_FLAGS +/* Set a file's st_flags. */ +static int set_fileflags(const char *fname, uint32 fileflags) +{ + if (do_chflags(fname, fileflags) != 0) { + rsyserr(FERROR_XFER, errno, + "failed to set file flags on %s", + full_fname(fname)); + return 0; + } + + return 1; +} + +/* Remove immutable flags from an object, so it can be altered/removed. */ +void make_mutable(const char *fname, mode_t mode, uint32 fileflags) +{ + if (S_ISLNK(mode)) + return; + + if (fileflags & NOCHANGE_FLAGS) + set_fileflags(fname, fileflags & ~NOCHANGE_FLAGS); +} + +/* Undo a prior make_mutable() call. */ +void undo_make_mutable(const char *fname, mode_t mode, uint32 fileflags) +{ + if (S_ISLNK(mode)) + return; + + if (fileflags & NOCHANGE_FLAGS) + set_fileflags(fname, fileflags); +} +#endif + int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp, const char *fnamecmp, int flags) { @@ -469,6 +505,15 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp, } #endif +#ifdef SUPPORT_FLAGS + if (preserve_fileflags && !S_ISLNK(sxp->st.st_mode) + && sxp->st.st_flags != F_FFLAGS(file)) { + if (!set_fileflags(fname, F_FFLAGS(file))) + return 0; + updated = 1; + } +#endif + if (verbose > 1 && flags & ATTRS_REPORT) { if (updated) rprintf(FCLIENT, "%s\n", fname); @@ -530,6 +575,10 @@ int finish_transfer(const char *fname, const char *fnametmp, set_file_attrs(fnametmp, file, NULL, fnamecmp, ok_to_set_time ? 0 : ATTRS_SKIP_MTIME); +#ifdef SUPPORT_FLAGS + if (preserve_fileflags) + make_mutable(fnametmp, file->mode, F_FFLAGS(file)); +#endif /* move tmp file over real file */ if (verbose > 2) rprintf(FINFO, "renaming %s to %s\n", fnametmp, fname); @@ -547,6 +596,10 @@ int finish_transfer(const char *fname, const char *fnametmp, } if (ret == 0) { /* The file was moved into place (not copied), so it's done. */ +#ifdef SUPPORT_FLAGS + if (preserve_fileflags) + undo_make_mutable(fname, file->mode, F_FFLAGS(file)); +#endif return 1; } /* The file was copied, so tweak the perms of the copied file. If it diff --git a/rsync.h b/rsync.h --- a/rsync.h +++ b/rsync.h @@ -60,6 +60,7 @@ #define XMIT_RDEV_MINOR_8_pre30 (1<<11) /* protocols 28 - 29 */ #define XMIT_GROUP_NAME_FOLLOWS (1<<11) /* protocols 30 - now */ #define XMIT_HLINK_FIRST (1<<12) /* protocols 30 - now (HLINKED files only) */ +#define XMIT_SAME_FLAGS (1<<14) /* protocols ?? - now */ /* These flags are used in the live flist data. */ @@ -451,6 +452,21 @@ typedef unsigned int size_t; #endif #endif +#ifdef HAVE_CHFLAGS +#define SUPPORT_FLAGS 1 +#endif + +#ifdef SUPPORT_FLAGS +#ifndef UF_NOUNLINK +#define UF_NOUNLINK 0 +#endif +#ifndef SF_NOUNLINK +#define SF_NOUNLINK 0 +#endif +#define NODELETE_FLAGS (UF_IMMUTABLE|UF_NOUNLINK|SF_IMMUTABLE|SF_NOUNLINK) +#define NOCHANGE_FLAGS (NODELETE_FLAGS|UF_APPEND|SF_APPEND) +#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. */ @@ -619,6 +635,7 @@ extern int file_extra_cnt; extern int inc_recurse; extern int uid_ndx; extern int gid_ndx; +extern int fileflags_ndx; extern int acls_ndx; extern int xattrs_ndx; @@ -656,6 +673,7 @@ extern int xattrs_ndx; /* When the associated option is on, all entries will have these present: */ #define F_OWNER(f) REQ_EXTRA(f, uid_ndx)->unum #define F_GROUP(f) REQ_EXTRA(f, gid_ndx)->unum +#define F_FFLAGS(f) REQ_EXTRA(f, fileflags_ndx)->unum #define F_ACL(f) REQ_EXTRA(f, acls_ndx)->num #define F_XATTR(f) REQ_EXTRA(f, xattrs_ndx)->num #define F_NDX(f) REQ_EXTRA(f, unsort_ndx)->num diff --git a/rsync.yo b/rsync.yo --- a/rsync.yo +++ b/rsync.yo @@ -338,6 +338,7 @@ to the detailed description below for a complete description. verb( -K, --keep-dirlinks treat symlinked dir on receiver as dir -H, --hard-links preserve hard links -p, --perms preserve permissions + --fileflags preserve file-flags -E, --executability preserve executability --chmod=CHMOD affect file and/or directory permissions -A, --acls preserve ACLs (implies -p) @@ -540,7 +541,8 @@ specified, in which case bf(-r) is not implied. Note that bf(-a) bf(does not preserve hardlinks), because finding multiply-linked files is expensive. You must separately -specify bf(-H). +specify bf(-H). Note also that for backward compatibility, bf(-a) +currently does bf(not) imply the bf(--fileflags) option. dit(--no-OPTION) You may turn off one or more implied options by prefixing the option name with "no-". Not all options may be prefixed with a "no-": @@ -922,6 +924,13 @@ super-user copies all namespaces except system.*. A normal user only copies the user.* namespace. To be able to backup and restore non-user namespaces as a normal user, see the bf(--fake-super) option. +dit(bf(--fileflags)) This option causes rsync to update the file-flags +to be the same as the source file, if your OS supports the bf(chflags)(2) +system call. In any case, an attempt is made to remove flags that would +prevent a file to be altered. Some flags can only be altered by the +super-user and can only be unset below a certain secure-level (usually +single-user mode). + dit(bf(--chmod)) This option tells rsync to apply one or more comma-separated "chmod" strings to the permission of the files in the transfer. The resulting value is treated as though it was the permissions diff --git a/syscall.c b/syscall.c --- a/syscall.c +++ b/syscall.c @@ -174,6 +174,15 @@ int do_chmod(const char *path, mode_t mode) } #endif +#ifdef SUPPORT_FLAGS +int do_chflags(const char *path, u_long flags) +{ + if (dry_run) return 0; + RETURN_ERROR_IF_RO_OR_LO; + return chflags(path, flags); +} +#endif + int do_rename(const char *fname1, const char *fname2) { if (dry_run) return 0;