Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F160165578
D57645.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
49 KB
Referenced Files
None
Subscribers
None
D57645.diff
View Options
Index: net/rsync/Makefile
===================================================================
--- net/rsync/Makefile
+++ net/rsync/Makefile
@@ -1,5 +1,6 @@
PORTNAME= rsync
DISTVERSION= 3.4.4
+PORTREVISION= 1
CATEGORIES= net
MASTER_SITES= https://www.mirrorservice.org/sites/rsync.samba.org/src/ \
http://rsync.mirror.garr.it/src/ \
@@ -46,15 +47,15 @@
PORTDOCS= NEWS.md README.md csprotocol.txt tech_report.tex
# define options
-OPTIONS_DEFINE= DOCS ICONV POPT_PORT SSH ZLIB_BASE
-OPTIONS_DEFAULT= ICONV SSH ZLIB_BASE
-OPTIONS_RADIO= PTS
+OPTIONS_DEFINE= DOCS FLAGS ICONV POPT_PORT SSH ZLIB_BASE
+OPTIONS_DEFAULT= FLAGS ICONV SSH ZLIB_BASE
# options provided upstream
POPT_PORT_DESC= Use popt from devel/popt instead of bundled one
-PTS_DESC= Functionality provided by third party patches
SSH_DESC= Use SSH instead of RSH
+FLAGS_DESC= File system flags support patch, adds --file-flags
+FLAGS_EXTRA_PATCHES= ${FILESDIR}/extra-patch-file-flags.diff
ZLIB_BASE_DESC= Use zlib from base instead of bundled one
ICONV_USES= iconv:translit
Index: net/rsync/files/extra-patch-file-flags.diff
===================================================================
--- /dev/null
+++ net/rsync/files/extra-patch-file-flags.diff
@@ -0,0 +1,1382 @@
+Add support for preserving BSD file flags
+
+Original patch by Rolf Grossmann <grossman@progtech.net>
+Rsync 3.4.4 port by Dag-Erling Smørgrav <des@FreeBSD.org>
+
+--- backup.c.orig
++++ backup.c
+@@ -207,7 +207,7 @@ static inline int link_or_rename(const char *from, const char *to,
+ return 0;
+ }
+ #endif
+- if (do_rename_at(from, to) == 0) {
++ if (do_rename_at(from, to, stp->st_mode, ST_FLAGS(*stp)) == 0) {
+ if (stp->st_nlink > 1 && !S_ISDIR(stp->st_mode)) {
+ /* If someone has hard-linked the file into the backup
+ * dir, rename() might return success but do nothing! */
+--- compat.c.orig
++++ compat.c
+@@ -40,6 +40,7 @@ extern int checksum_seed;
+ extern int basis_dir_cnt;
+ extern int prune_empty_dirs;
+ extern int protocol_version;
++extern int force_change;
+ extern int protect_args;
+ extern int preserve_uid;
+ extern int preserve_gid;
+@@ -47,6 +48,7 @@ extern int preserve_atimes;
+ extern int preserve_crtimes;
+ extern int preserve_acls;
+ extern int preserve_xattrs;
++extern int preserve_file_flags;
+ extern int xfer_flags_as_varint;
+ extern int need_messages_from_generator;
+ extern int delete_mode, delete_before, delete_during, delete_after;
+@@ -87,7 +89,7 @@ struct name_num_item *xattr_sum_nni;
+ int xattr_sum_len = 0;
+
+ /* These index values are for the file-list's extra-attribute array. */
+-int pathname_ndx, depth_ndx, atimes_ndx, crtimes_ndx, uid_ndx, gid_ndx, acls_ndx, xattrs_ndx, unsort_ndx;
++int pathname_ndx, depth_ndx, atimes_ndx, crtimes_ndx, uid_ndx, gid_ndx, file_flags_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 */
+@@ -589,6 +591,8 @@ void setup_protocol(int f_out,int f_in)
+ uid_ndx = ++file_extra_cnt;
+ if (preserve_gid)
+ gid_ndx = ++file_extra_cnt;
++ if (preserve_file_flags || (force_change && !am_sender))
++ file_flags_ndx = ++file_extra_cnt;
+ if (preserve_acls && !am_sender)
+ acls_ndx = ++file_extra_cnt;
+ if (preserve_xattrs)
+@@ -752,6 +756,10 @@ void setup_protocol(int f_out,int f_in)
+ fprintf(stderr, "Both rsync versions must be at least 3.2.0 for --crtimes.\n");
+ exit_cleanup(RERR_PROTOCOL);
+ }
++ if (!xfer_flags_as_varint && preserve_file_flags) {
++ fprintf(stderr, "Both rsync versions must be at least 3.2.0 for --file-flags.\n");
++ exit_cleanup(RERR_PROTOCOL);
++ }
+ if (am_sender) {
+ receiver_symlink_times = am_server
+ ? strchr(client_info, 'L') != NULL
+--- delete.c.orig
++++ delete.c
+@@ -25,6 +25,7 @@
+ extern int am_root;
+ extern int make_backups;
+ extern int max_delete;
++extern int force_change;
+ extern char *backup_dir;
+ extern char *backup_suffix;
+ extern int backup_suffix_len;
+@@ -97,8 +98,12 @@ static enum delret delete_dir_contents(char *fname, uint16 flags)
+ }
+
+ strlcpy(p, fp->basename, remainder);
++#ifdef SUPPORT_FORCE_CHANGE
++ if (force_change)
++ make_mutable(fname, fp->mode, F_FFLAGS(fp), force_change);
++#endif
+ if (!(fp->mode & S_IWUSR) && !am_root && fp->flags & FLAG_OWNED_BY_US)
+- do_chmod_at(fname, fp->mode | S_IWUSR);
++ do_chmod_at(fname, fp->mode | S_IWUSR, NO_FFLAGS);
+ /* Save stack by recursing to ourself directly. */
+ if (S_ISDIR(fp->mode)) {
+ if (delete_dir_contents(fname, flags | DEL_RECURSE) != DR_SUCCESS)
+@@ -139,11 +144,18 @@ enum delret delete_item(char *fbuf, uint16 mode, uint16 flags)
+ }
+
+ if (flags & DEL_NO_UID_WRITE)
+- do_chmod_at(fbuf, mode | S_IWUSR);
++ do_chmod_at(fbuf, mode | S_IWUSR, NO_FFLAGS);
+
+ if (S_ISDIR(mode) && !(flags & DEL_DIR_IS_EMPTY)) {
+ /* This only happens on the first call to delete_item() since
+ * delete_dir_contents() always calls us w/DEL_DIR_IS_EMPTY. */
++#ifdef SUPPORT_FORCE_CHANGE
++ if (force_change) {
++ STRUCT_STAT st;
++ if (x_lstat(fbuf, &st, NULL) == 0)
++ make_mutable(fbuf, st.st_mode, st.st_flags, force_change);
++ }
++#endif
+ ignore_perishable = 1;
+ /* If DEL_RECURSE is not set, this just reports emptiness. */
+ ret = delete_dir_contents(fbuf, flags);
+--- flist.c.orig
++++ flist.c
+@@ -52,6 +52,7 @@ extern int preserve_links;
+ extern int preserve_hard_links;
+ extern int preserve_devices;
+ extern int preserve_specials;
++extern int preserve_file_flags;
+ extern int delete_during;
+ extern int missing_args;
+ extern int eol_nulls;
+@@ -400,6 +401,9 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
+ static time_t crtime;
+ #endif
+ static mode_t mode;
++#ifdef SUPPORT_FILE_FLAGS
++ static uint32 file_flags;
++#endif
+ #ifdef SUPPORT_HARD_LINKS
+ static int64 dev;
+ #endif
+@@ -443,6 +447,14 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
+ xflags |= XMIT_SAME_MODE;
+ else
+ mode = file->mode;
++#ifdef SUPPORT_FILE_FLAGS
++ if (preserve_file_flags) {
++ if (F_FFLAGS(file) == file_flags)
++ xflags |= XMIT_SAME_FLAGS;
++ else
++ file_flags = F_FFLAGS(file);
++ }
++#endif
+
+ if (preserve_devices && IS_DEVICE(mode)) {
+ if (protocol_version < 28) {
+@@ -604,6 +616,10 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
+ #endif
+ if (!(xflags & XMIT_SAME_MODE))
+ write_int(f, to_wire_mode(mode));
++#ifdef SUPPORT_FILE_FLAGS
++ if (preserve_file_flags && !(xflags & XMIT_SAME_FLAGS))
++ write_int(f, (int)file_flags);
++#endif
+ if (atimes_ndx && !S_ISDIR(mode) && !(xflags & XMIT_SAME_ATIME))
+ write_varlong(f, atime, 4);
+ if (preserve_uid && !(xflags & XMIT_SAME_UID)) {
+@@ -698,6 +714,9 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x
+ static time_t crtime;
+ #endif
+ static mode_t mode;
++#ifdef SUPPORT_FILE_FLAGS
++ static uint32 file_flags;
++#endif
+ #ifdef SUPPORT_HARD_LINKS
+ static int64 dev;
+ #endif
+@@ -815,6 +834,10 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x
+ #ifdef SUPPORT_CRTIMES
+ if (crtimes_ndx)
+ crtime = F_CRTIME(first);
++#endif
++#ifdef SUPPORT_FILE_FLAGS
++ if (preserve_file_flags)
++ file_flags = F_FFLAGS(first);
+ #endif
+ if (preserve_uid)
+ uid = F_OWNER(first);
+@@ -904,6 +927,10 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x
+
+ if (chmod_modes && !S_ISLNK(mode) && mode)
+ mode = tweak_mode(mode, chmod_modes);
++#ifdef SUPPORT_FILE_FLAGS
++ if (preserve_file_flags && !(xflags & XMIT_SAME_FLAGS))
++ file_flags = (uint32)read_int(f);
++#endif
+
+ if (preserve_uid && !(xflags & XMIT_SAME_UID)) {
+ if (protocol_version < 30)
+@@ -1085,6 +1112,10 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x
+ }
+ #endif
+ file->mode = mode;
++#ifdef SUPPORT_FILE_FLAGS
++ if (preserve_file_flags)
++ F_FFLAGS(file) = file_flags;
++#endif
+ if (preserve_uid)
+ F_OWNER(file) = uid;
+ if (preserve_gid) {
+@@ -1506,6 +1537,10 @@ struct file_struct *make_file(const char *fname, struct file_list *flist,
+ }
+ #endif
+ file->mode = st.st_mode;
++#if defined SUPPORT_FILE_FLAGS || defined SUPPORT_FORCE_CHANGE
++ if (file_flags_ndx)
++ F_FFLAGS(file) = st.st_flags;
++#endif
+ if (preserve_uid)
+ F_OWNER(file) = st.st_uid;
+ if (preserve_gid)
+--- generator.c.orig
++++ generator.c
+@@ -43,10 +43,12 @@ extern int preserve_devices;
+ extern int preserve_specials;
+ extern int preserve_hard_links;
+ extern int preserve_executability;
++extern int preserve_file_flags;
+ extern int preserve_perms;
+ extern int preserve_mtimes;
+ extern int omit_dir_times;
+ extern int omit_link_times;
++extern int force_change;
+ extern int delete_mode;
+ extern int delete_before;
+ extern int delete_during;
+@@ -493,6 +495,10 @@ int unchanged_attrs(const char *fname, struct file_struct *file, stat_x *sxp)
+ return 0;
+ if (perms_differ(file, sxp))
+ return 0;
++#ifdef SUPPORT_FILE_FLAGS
++ if (preserve_file_flags && sxp->st.st_flags != F_FFLAGS(file))
++ return 0;
++#endif
+ if (ownership_differs(file, sxp))
+ return 0;
+ #ifdef SUPPORT_ACLS
+@@ -554,6 +560,11 @@ void itemize(const char *fnamecmp, struct file_struct *file, int ndx, int statre
+ iflags |= ITEM_REPORT_OWNER;
+ if (gid_ndx && !(file->flags & FLAG_SKIP_GROUP) && sxp->st.st_gid != (gid_t)F_GROUP(file))
+ iflags |= ITEM_REPORT_GROUP;
++#ifdef SUPPORT_FILE_FLAGS
++ if (preserve_file_flags && !S_ISLNK(file->mode)
++ && sxp->st.st_flags != F_FFLAGS(file))
++ iflags |= ITEM_REPORT_FFLAGS;
++#endif
+ #ifdef SUPPORT_ACLS
+ if (preserve_acls && !S_ISLNK(file->mode)) {
+ if (!ACL_READY(*sxp))
+@@ -1466,6 +1477,10 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
+ if (!preserve_perms) { /* See comment in non-dir code below. */
+ file->mode = dest_mode(file->mode, sx.st.st_mode, dflt_perms, statret == 0);
+ }
++#ifdef SUPPORT_FORCE_CHANGE
++ if (force_change && !preserve_file_flags)
++ F_FFLAGS(file) = sx.st.st_flags;
++#endif
+ if (statret != 0 && basis_dir[0] != NULL) {
+ int j = try_dests_non(file, fname, ndx, fnamecmpbuf, &sx, itemizing, code);
+ if (j == -2) {
+@@ -1508,10 +1523,15 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
+ * readable and writable permissions during the time we are
+ * putting files within them. This is then restored to the
+ * former permissions after the transfer is done. */
++#ifdef SUPPORT_FORCE_CHANGE
++ if (force_change && F_FFLAGS(file) & force_change
++ && make_mutable(fname, file->mode, F_FFLAGS(file), force_change))
++ need_retouch_dir_perms = 1;
++#endif
+ #ifdef HAVE_CHMOD
+ if (!am_root && (file->mode & S_IRWXU) != S_IRWXU && dir_tweaking) {
+ mode_t mode = file->mode | S_IRWXU;
+- if (do_chmod_at(fname, mode) < 0) {
++ if (do_chmod_at(fname, mode, 0) < 0) {
+ rsyserr(FERROR_XFER, errno,
+ "failed to modify permissions on %s",
+ full_fname(fname));
+@@ -1546,6 +1566,10 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
+ int exists = statret == 0 && stype != FT_DIR;
+ file->mode = dest_mode(file->mode, sx.st.st_mode, dflt_perms, exists);
+ }
++#ifdef SUPPORT_FORCE_CHANGE
++ if (force_change && !preserve_file_flags)
++ F_FFLAGS(file) = sx.st.st_flags;
++#endif
+
+ #ifdef SUPPORT_HARD_LINKS
+ if (preserve_hard_links && F_HLINK_NOT_FIRST(file)
+@@ -2053,7 +2077,7 @@ int atomic_create(struct file_struct *file, char *fname, const char *slnk, const
+ }
+
+ if (!skip_atomic) {
+- if (do_rename_at(tmpname, fname) < 0) {
++ if (do_rename_at(tmpname, fname, file->mode, NO_FFLAGS) < 0) {
+ char *full_tmpname = strdup(full_fname(tmpname));
+ if (full_tmpname == NULL)
+ out_of_memory("atomic_create");
+@@ -2124,17 +2148,25 @@ static void touch_up_dirs(struct file_list *flist, int ndx)
+ continue;
+ fname = f_name(file, NULL);
+ if (fix_dir_perms)
+- do_chmod_at(fname, file->mode);
++ do_chmod_at(fname, file->mode, 0);
+ if (need_retouch_dir_times) {
+ STRUCT_STAT st;
+ if (link_stat(fname, &st, 0) == 0 && mtime_differs(&st, file)) {
+ st.st_mtime = file->modtime;
+ #ifdef ST_MTIME_NSEC
+ st.ST_MTIME_NSEC = F_MOD_NSEC_or_0(file);
++#endif
++#ifdef SUPPORT_FORCE_CHANGE
++ st.st_mode = file->mode;
++ st.st_flags = 0;
+ #endif
+ set_times(fname, &st);
+ }
+ }
++#ifdef SUPPORT_FORCE_CHANGE
++ if (force_change && F_FFLAGS(file) & force_change)
++ undo_make_mutable(fname, F_FFLAGS(file));
++#endif
+ if (counter >= loopchk_limit) {
+ if (allowed_lull)
+ maybe_send_keepalive(time(NULL), MSK_ALLOW_FLUSH);
+--- log.c.orig
++++ log.c
+@@ -731,7 +731,8 @@ static void log_formatted(enum logcode code, const char *format, const char *op,
+ : iflags & ITEM_REPORT_ATIME ? 'u' : 'n';
+ c[9] = !(iflags & ITEM_REPORT_ACL) ? '.' : 'a';
+ c[10] = !(iflags & ITEM_REPORT_XATTR) ? '.' : 'x';
+- c[11] = '\0';
++ c[11] = !(iflags & ITEM_REPORT_FFLAGS) ? '.' : 'f';
++ c[12] = '\0';
+
+ if (iflags & (ITEM_IS_NEW|ITEM_MISSING_DATA)) {
+ char ch = iflags & ITEM_IS_NEW ? '+' : '?';
+--- main.c.orig
++++ main.c
+@@ -31,6 +31,9 @@
+ #ifdef __TANDEM
+ #include <floss.h(floss_execlp)>
+ #endif
++#ifdef SUPPORT_FORCE_CHANGE
++#include <sys/sysctl.h>
++#endif
+
+ extern int dry_run;
+ extern int list_only;
+@@ -49,6 +52,7 @@ extern int need_messages_from_generator;
+ extern int kluge_around_eof;
+ extern int got_xfer_error;
+ extern int old_style_args;
++extern int force_change;
+ extern int msgs2stderr;
+ extern int module_id;
+ extern int read_only;
+@@ -995,6 +999,22 @@ static int do_recv(int f_in, int f_out, char *local_name)
+ * points to an identical file won't be replaced by the referent. */
+ copy_links = copy_dirlinks = copy_unsafe_links = 0;
+
++#ifdef SUPPORT_FORCE_CHANGE
++ if (force_change & SYS_IMMUTABLE) {
++ /* Determine whether we'll be able to unlock a system immutable item. */
++ int mib[2];
++ int securityLevel = 0;
++ size_t len = sizeof securityLevel;
++
++ mib[0] = CTL_KERN;
++ mib[1] = KERN_SECURELVL;
++ if (sysctl(mib, 2, &securityLevel, &len, NULL, 0) == 0 && securityLevel > 0) {
++ rprintf(FERROR, "System security level is too high to force mutability on system immutable files and directories.\n");
++ exit_cleanup(RERR_UNSUPPORTED);
++ }
++ }
++#endif
++
+ #ifdef SUPPORT_HARD_LINKS
+ if (preserve_hard_links && !inc_recurse)
+ match_hard_links(first_flist);
+--- options.c.orig
++++ options.c
+@@ -56,6 +56,7 @@ int preserve_hard_links = 0;
+ int preserve_acls = 0;
+ int preserve_xattrs = 0;
+ int preserve_perms = 0;
++int preserve_file_flags = 0;
+ int preserve_executability = 0;
+ int preserve_devices = 0;
+ int preserve_specials = 0;
+@@ -99,6 +100,7 @@ int msgs2stderr = 2; /* Default: send errors to stderr for local & remote-shell
+ int saw_stderr_opt = 0;
+ int allow_8bit_chars = 0;
+ int force_delete = 0;
++int force_change = 0;
+ int io_timeout = 0;
+ int prune_empty_dirs = 0;
+ int use_qsort = 0;
+@@ -633,6 +635,10 @@ 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 },
++ {"file-flags", 0, POPT_ARG_VAL, &preserve_file_flags, 1, 0, 0 },
++ {"fileflags", 0, POPT_ARG_VAL, &preserve_file_flags, 1, 0, 0 },
++ {"no-file-flags", 0, POPT_ARG_VAL, &preserve_file_flags, 0, 0, 0 },
++ {"no-fileflags", 0, POPT_ARG_VAL, &preserve_file_flags, 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 },
+@@ -731,6 +737,12 @@ static struct poptOption long_options[] = {
+ {"remove-source-files",0,POPT_ARG_VAL, &remove_source_files, 1, 0, 0 },
+ {"force", 0, POPT_ARG_VAL, &force_delete, 1, 0, 0 },
+ {"no-force", 0, POPT_ARG_VAL, &force_delete, 0, 0, 0 },
++ {"force-delete", 0, POPT_ARG_VAL, &force_delete, 1, 0, 0 },
++ {"no-force-delete", 0, POPT_ARG_VAL, &force_delete, 0, 0, 0 },
++ {"force-change", 0, POPT_ARG_VAL, &force_change, ALL_IMMUTABLE, 0, 0 },
++ {"no-force-change", 0, POPT_ARG_VAL, &force_change, 0, 0, 0 },
++ {"force-uchange", 0, POPT_ARG_VAL, &force_change, USR_IMMUTABLE, 0, 0 },
++ {"force-schange", 0, POPT_ARG_VAL, &force_change, SYS_IMMUTABLE, 0, 0 },
+ {"ignore-errors", 0, POPT_ARG_VAL, &ignore_errors, 1, 0, 0 },
+ {"no-ignore-errors", 0, POPT_ARG_VAL, &ignore_errors, 0, 0, 0 },
+ {"max-delete", 0, POPT_ARG_INT, &max_delete, 0, 0, 0 },
+@@ -1028,6 +1040,15 @@ static void set_refuse_options(void)
+ #ifndef SUPPORT_CRTIMES
+ parse_one_refuse_match(0, "crtimes", list_end);
+ #endif
++#ifndef SUPPORT_FILE_FLAGS
++ parse_one_refuse_match(0, "file-flags", list_end);
++ parse_one_refuse_match(0, "fileflags", list_end);
++#endif
++#ifndef SUPPORT_FORCE_CHANGE
++ parse_one_refuse_match(0, "force-change", list_end);
++ parse_one_refuse_match(0, "force-uchange", list_end);
++ parse_one_refuse_match(0, "force-schange", list_end);
++#endif
+
+ /* Now we use the descrip values to actually mark the options for refusal. */
+ for (op = long_options; op != list_end; op++) {
+@@ -2752,6 +2773,9 @@ void server_options(char **args, int *argc_p)
+ if (xfer_dirs && !recurse && delete_mode && am_sender)
+ args[ac++] = "--no-r";
+
++ if (preserve_file_flags)
++ args[ac++] = "--fileflags";
++
+ if (do_compression && do_compression_level != CLVL_NOT_SPECIFIED) {
+ if (asprintf(&arg, "--compress-level=%d", do_compression_level) < 0)
+ goto oom;
+@@ -2847,6 +2871,16 @@ void server_options(char **args, int *argc_p)
+ args[ac++] = "--delete-excluded";
+ if (force_delete)
+ args[ac++] = "--force";
++#ifdef SUPPORT_FORCE_CHANGE
++ if (force_change) {
++ if (force_change == ALL_IMMUTABLE)
++ args[ac++] = "--force-change";
++ else if (force_change == USR_IMMUTABLE)
++ args[ac++] = "--force-uchange";
++ else if (force_change == SYS_IMMUTABLE)
++ args[ac++] = "--force-schange";
++ }
++#endif
+ if (write_batch < 0)
+ args[ac++] = "--only-write-batch=X";
+ if (am_root > 1)
+--- receiver.c.orig
++++ receiver.c
+@@ -543,7 +543,7 @@ static void handle_delayed_updates(char *local_name)
+ }
+ /* We don't use robust_rename() here because the
+ * partial-dir must be on the same drive. */
+- if (do_rename_at(partialptr, fname) < 0) {
++ if (do_rename_at(partialptr, fname, 0, NO_FFLAGS) < 0) {
+ rsyserr(FERROR_XFER, errno,
+ "rename failed for %s (from %s)",
+ full_fname(fname), partialptr);
+--- rsync.1.md.orig
++++ rsync.1.md
+@@ -446,6 +446,7 @@ has its own detailed description later in this manpage.
+ --keep-dirlinks, -K treat symlinked dir on receiver as dir
+ --hard-links, -H preserve hard links
+ --perms, -p preserve permissions
++--file-flags preserve file flags (aka chflags)
+ --executability, -E preserve executability
+ --chmod=CHMOD affect file and/or directory permissions
+ --acls, -A preserve ACLs (implies --perms)
+@@ -487,7 +488,11 @@ has its own detailed description later in this manpage.
+ --ignore-missing-args ignore missing source args without error
+ --delete-missing-args delete missing source args from destination
+ --ignore-errors delete even if there are I/O errors
+---force force deletion of dirs even if not empty
++--force an alias for --force-delete
++--force-delete force deletion of directories even if not empty
++--force-change affect user-/system-immutable files/dirs
++--force-uchange affect user-immutable files/dirs
++--force-schange affect system-immutable files/dirs
+ --max-delete=NUM don't delete more than NUM files
+ --max-size=SIZE don't transfer any file larger than SIZE
+ --min-size=SIZE don't transfer any file smaller than SIZE
+@@ -832,6 +837,7 @@ expand it.
+ recursion and want to preserve almost everything. Be aware that it does
+ **not** include preserving ACLs (`-A`), xattrs (`-X`), atimes (`-U`),
+ crtimes (`-N`), nor the finding and preserving of hardlinks (`-H`).
++ It also does **not** imply [`--file-flags`](#opt).
+
+ The only exception to the above equivalence is when [`--files-from`](#opt)
+ is specified, in which case [`-r`](#opt) is not implied.
+@@ -1296,7 +1302,7 @@ expand it.
+ Without this option, if the sending side has replaced a directory with a
+ symlink to a directory, the receiving side will delete anything that is in
+ the way of the new symlink, including a directory hierarchy (as long as
+- [`--force`](#opt) or [`--delete`](#opt) is in effect).
++ [`--force-delete`](#opt) or [`--delete`](#opt) is in effect).
+
+ See also [`--keep-dirlinks`](#opt) for an analogous option for the
+ receiving side.
+@@ -1491,6 +1497,37 @@ expand it.
+ those used by [`--fake-super`](#opt)) unless you repeat the option (e.g. `-XX`).
+ This "copy all xattrs" mode cannot be used with [`--fake-super`](#opt).
+
++0. `--file-flags`
++
++ This option causes rsync to update the file flags to be the same as the
++ source files and directories (if your OS supports the **chflags**(2) system
++ call). Some flags can only be altered by the super-user and some might
++ only be unset below a certain secure-level (usually single-user mode). It
++ will not make files alterable that are set to immutable on the receiver.
++ To do that, see [`--force-change`](#opt), [`--force-uchange`](#opt), and
++ [`--force-schange`](#opt).
++
++0. `--force-change`
++
++ This option causes rsync to disable both user-immutable and
++ system-immutable flags on files and directories that are being updated or
++ deleted on the receiving side. This option overrides
++ [`--force-uchange`](#opt) and [`--force-schange`](#opt).
++
++0. `--force-uchange`
++
++ This option causes rsync to disable user-immutable flags on files and
++ directories that are being updated or deleted on the receiving side. It
++ does not try to affect system flags. This option overrides
++ [`--force-change`](#opt) and [`--force-schange`](#opt).
++
++0. `--force-schange`
++
++ This option causes rsync to disable system-immutable flags on files and
++ directories that are being updated or deleted on the receiving side. It
++ does not try to affect user flags. This option overrides
++ [`--force-change`](#opt) and [`--force-uchange`](#opt).
++
+ 0. `--chmod=CHMOD`
+
+ This option tells rsync to apply one or more comma-separated "chmod" modes
+@@ -2020,8 +2057,8 @@ expand it.
+ [`--ignore-missing-args`](#opt) option a step farther: each missing arg
+ will become a deletion request of the corresponding destination file on the
+ receiving side (should it exist). If the destination file is a non-empty
+- directory, it will only be successfully deleted if [`--force`](#opt) or
+- [`--delete`](#opt) are in effect. Other than that, this option is
++ directory, it will only be successfully deleted if [`--force-delete`](#opt)
++ or [`--delete`](#opt) are in effect. Other than that, this option is
+ independent of any other type of delete processing.
+
+ The missing source files are represented by special file-list entries which
+@@ -2032,14 +2069,14 @@ expand it.
+ Tells [`--delete`](#opt) to go ahead and delete files even when there are
+ I/O errors.
+
+-0. `--force`
++0. `--force-delete`, `--force`
+
+ This option tells rsync to delete a non-empty directory when it is to be
+ replaced by a non-directory. This is only relevant if deletions are not
+ active (see [`--delete`](#opt) for details).
+
+- Note for older rsync versions: `--force` used to still be required when
+- using [`--delete-after`](#opt), and it used to be non-functional unless the
++ Note that some older rsync versions used to require `--force` when using
++ [`--delete-after`](#opt), and it used to be non-functional unless the
+ [`--recursive`](#opt) option was also enabled.
+
+ 0. `--max-delete=NUM`
+@@ -3116,7 +3153,7 @@ expand it.
+ also turns on the output of other verbose messages).
+
+ The "%i" escape has a cryptic output that is 11 letters long. The general
+- format is like the string `YXcstpoguax`, where **Y** is replaced by the type
++ format is like the string `YXcstpoguaxf`, where **Y** is replaced by the type
+ of update being done, **X** is replaced by the file-type, and the other
+ letters represent attributes that may be output if they are being modified.
+
+--- rsync.c.orig
++++ rsync.c
+@@ -31,6 +31,7 @@ extern int dry_run;
+ extern int preserve_acls;
+ extern int preserve_xattrs;
+ extern int preserve_perms;
++extern int preserve_file_flags;
+ extern int preserve_executability;
+ extern int preserve_mtimes;
+ extern int omit_dir_times;
+@@ -471,6 +472,39 @@ mode_t dest_mode(mode_t flist_mode, mode_t stat_mode, int dflt_perms,
+ return new_mode;
+ }
+
++#if defined SUPPORT_FILE_FLAGS || defined SUPPORT_FORCE_CHANGE
++/* Set a file's st_flags. */
++static int set_file_flags(const char *fname, uint32 file_flags)
++{
++ if (do_chflags(fname, file_flags) != 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. */
++int make_mutable(const char *fname, mode_t mode, uint32 file_flags, uint32 iflags)
++{
++ if (S_ISLNK(mode) || !(file_flags & iflags))
++ return 0;
++ if (!set_file_flags(fname, file_flags & ~iflags))
++ return -1;
++ return 1;
++}
++
++/* Undo a prior make_mutable() call that returned a 1. */
++int undo_make_mutable(const char *fname, uint32 file_flags)
++{
++ if (!set_file_flags(fname, file_flags))
++ return -1;
++ return 1;
++}
++#endif
++
+ static int same_mtime(struct file_struct *file, STRUCT_STAT *st, int extra_accuracy)
+ {
+ #ifdef ST_MTIME_NSEC
+@@ -547,7 +581,7 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
+ if (am_root >= 0) {
+ uid_t uid = change_uid ? (uid_t)F_OWNER(file) : sxp->st.st_uid;
+ gid_t gid = change_gid ? (gid_t)F_GROUP(file) : sxp->st.st_gid;
+- if (do_lchown_at(fname, uid, gid) != 0) {
++ if (do_lchown_at(fname, uid, gid, sxp->st.st_mode, ST_FLAGS(sxp->st)) != 0) {
+ /* We shouldn't have attempted to change uid
+ * or gid unless have the privilege. */
+ rsyserr(FERROR_XFER, errno, "%s %s failed",
+@@ -657,7 +691,7 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
+
+ #ifdef HAVE_CHMOD
+ if (!BITS_EQUAL(sxp->st.st_mode, new_mode, CHMOD_BITS)) {
+- int ret = am_root < 0 ? 0 : do_chmod_at(fname, new_mode);
++ int ret = am_root < 0 ? 0 : do_chmod_at(fname, new_mode, ST_FLAGS(sxp->st));
+ if (ret < 0) {
+ rsyserr(FERROR_XFER, errno,
+ "failed to set permissions on %s",
+@@ -669,6 +703,19 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
+ }
+ #endif
+
++#ifdef SUPPORT_FILE_FLAGS
++ if (preserve_file_flags && !S_ISLNK(sxp->st.st_mode)
++ && sxp->st.st_flags != F_FFLAGS(file)) {
++ uint32 file_flags = F_FFLAGS(file);
++ if (flags & ATTRS_DELAY_IMMUTABLE)
++ file_flags &= ~ALL_IMMUTABLE;
++ if (sxp->st.st_flags != file_flags
++ && !set_file_flags(fname, file_flags))
++ goto cleanup;
++ updated = 1;
++ }
++#endif
++
+ if (INFO_GTE(NAME, 2) && flags & ATTRS_REPORT) {
+ if (updated)
+ rprintf(FCLIENT, "%s\n", fname);
+@@ -746,7 +793,8 @@ 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 | ATTRS_SKIP_CRTIME);
++ ATTRS_DELAY_IMMUTABLE
++ | (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))
+@@ -763,6 +811,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_FILE_FLAGS
++ if (preserve_file_flags && F_FFLAGS(file) & ALL_IMMUTABLE)
++ set_file_flags(fname, F_FFLAGS(file));
++#endif
+ return 1;
+ }
+ /* The file was copied, so tweak the perms of the copied file. If it
+@@ -774,7 +826,7 @@ int finish_transfer(const char *fname, const char *fnametmp,
+ ok_to_set_time ? ATTRS_ACCURATE_TIME : ATTRS_SKIP_MTIME | ATTRS_SKIP_ATIME | ATTRS_SKIP_CRTIME);
+
+ if (temp_copy_name) {
+- if (do_rename_at(fnametmp, fname) < 0) {
++ if (do_rename_at(fnametmp, fname, file->mode, NO_FFLAGS) < 0) {
+ rsyserr(FERROR_XFER, errno, "rename %s -> \"%s\"",
+ full_fname(fnametmp), fname);
+ return 0;
+--- rsync.h.orig
++++ rsync.h
+@@ -69,7 +69,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_SAME_FLAGS (1<<16) /* any protocol - restricted by command-line option */
+ #define XMIT_CRTIME_EQ_MTIME (1<<17) /* any protocol - restricted by command-line option */
+
+ /* These flags are used in the live flist data. */
+@@ -216,6 +216,7 @@
+ #define ATTRS_SKIP_MTIME (1<<1)
+ #define ATTRS_ACCURATE_TIME (1<<2)
+ #define ATTRS_SKIP_ATIME (1<<3)
++#define ATTRS_DELAY_IMMUTABLE (1<<4)
+ #define ATTRS_SKIP_CRTIME (1<<5)
+
+ #define MSG_FLUSH 2
+@@ -244,6 +245,7 @@
+ #define ITEM_REPORT_GROUP (1<<6)
+ #define ITEM_REPORT_ACL (1<<7)
+ #define ITEM_REPORT_XATTR (1<<8)
++#define ITEM_REPORT_FFLAGS (1<<9)
+ #define ITEM_REPORT_CRTIME (1<<10)
+ #define ITEM_BASIS_TYPE_FOLLOWS (1<<11)
+ #define ITEM_XNAME_FOLLOWS (1<<12)
+@@ -611,6 +613,31 @@ typedef unsigned int size_t;
+ #define SUPPORT_CRTIMES 1
+ #endif
+
++#define NO_FFLAGS ((uint32)-1)
++
++#ifdef HAVE_CHFLAGS
++#define SUPPORT_FILE_FLAGS 1
++#define SUPPORT_FORCE_CHANGE 1
++#endif
++
++#if defined SUPPORT_FILE_FLAGS || defined SUPPORT_FORCE_CHANGE
++#ifndef UF_NOUNLINK
++#define UF_NOUNLINK 0
++#endif
++#ifndef SF_NOUNLINK
++#define SF_NOUNLINK 0
++#endif
++#define USR_IMMUTABLE (UF_IMMUTABLE|UF_NOUNLINK|UF_APPEND)
++#define SYS_IMMUTABLE (SF_IMMUTABLE|SF_NOUNLINK|SF_APPEND)
++#define ALL_IMMUTABLE (USR_IMMUTABLE|SYS_IMMUTABLE)
++#define ST_FLAGS(st) ((st).st_flags)
++#else
++#define USR_IMMUTABLE 0
++#define SYS_IMMUTABLE 0
++#define ALL_IMMUTABLE 0
++#define ST_FLAGS(st) NO_FFLAGS
++#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. */
+@@ -842,6 +869,7 @@ extern int pathname_ndx;
+ extern int depth_ndx;
+ extern int uid_ndx;
+ extern int gid_ndx;
++extern int file_flags_ndx;
+ extern int acls_ndx;
+ extern int xattrs_ndx;
+ extern int file_sum_extra_cnt;
+@@ -897,6 +925,11 @@ extern int file_sum_extra_cnt;
+ /* 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
++#if defined SUPPORT_FILE_FLAGS || defined SUPPORT_FORCE_CHANGE
++#define F_FFLAGS(f) REQ_EXTRA(f, file_flags_ndx)->unum
++#else
++#define F_FFLAGS(f) NO_FFLAGS
++#endif
+ #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
+--- syscall.c.orig
++++ syscall.c
+@@ -45,6 +45,7 @@ extern int am_root;
+ extern int am_sender;
+ extern int read_only;
+ extern int list_only;
++extern int force_change;
+ extern int inplace;
+ extern int preallocate_files;
+ extern int preserve_perms;
+@@ -90,7 +91,23 @@ int do_unlink(const char *path)
+ {
+ if (dry_run) return 0;
+ RETURN_ERROR_IF_RO_OR_LO;
+- return unlink(path);
++ if (unlink(path) == 0)
++ return 0;
++#ifdef SUPPORT_FORCE_CHANGE
++ if (force_change && errno == EPERM) {
++ STRUCT_STAT st;
++
++ if (do_lstat(path, &st) == 0
++ && make_mutable(path, st.st_mode, st.st_flags, force_change) > 0) {
++ if (unlink(path) == 0)
++ return 0;
++ undo_make_mutable(path, st.st_flags);
++ }
++ /* TODO: handle immutable directories */
++ errno = EPERM;
++ }
++#endif
++ return -1;
+ }
+
+ /*
+@@ -142,6 +159,18 @@ int do_unlink_at(const char *path)
+
+ ret = unlinkat(dfd, bname, 0);
+ e = errno;
++#ifdef SUPPORT_FORCE_CHANGE
++ if (force_change && e == EPERM) {
++ STRUCT_STAT st;
++ if (do_lstat(path, &st) == 0
++ && make_mutable(path, st.st_mode, st.st_flags, force_change) > 0) {
++ ret = unlinkat(dfd, bname, 0);
++ e = errno;
++ if (ret != 0)
++ undo_make_mutable(path, st.st_flags);
++ }
++ }
++#endif
+ close(dfd);
+ errno = e;
+ return ret;
+@@ -399,14 +428,35 @@ int do_link_at(const char *old_path, const char *new_path)
+ }
+ #endif
+
+-int do_lchown(const char *path, uid_t owner, gid_t group)
++int do_lchown(const char *path, uid_t owner, gid_t group, UNUSED(mode_t mode), UNUSED(uint32 file_flags))
+ {
+ if (dry_run) return 0;
+ RETURN_ERROR_IF_RO_OR_LO;
+ #ifndef HAVE_LCHOWN
+ #define lchown chown
+ #endif
+- return lchown(path, owner, group);
++ if (lchown(path, owner, group) == 0)
++ return 0;
++#ifdef SUPPORT_FORCE_CHANGE
++ if (force_change && errno == EPERM) {
++ if (file_flags == NO_FFLAGS) {
++ STRUCT_STAT st;
++ if (do_lstat(path, &st) == 0) {
++ mode = st.st_mode;
++ file_flags = st.st_flags;
++ }
++ }
++ if (file_flags != NO_FFLAGS
++ && make_mutable(path, mode, file_flags, force_change) > 0) {
++ int ret = lchown(path, owner, group);
++ undo_make_mutable(path, file_flags);
++ if (ret == 0)
++ return 0;
++ }
++ errno = EPERM;
++ }
++#endif
++ return -1;
+ }
+
+ /*
+@@ -423,7 +473,7 @@ int do_lchown(const char *path, uid_t owner, gid_t group)
+ Falls through to do_lchown() in the dry-run / non-daemon / chrooted /
+ absolute-path / no-parent cases, identical to do_chmod_at().
+ */
+-int do_lchown_at(const char *fname, uid_t owner, gid_t group)
++int do_lchown_at(const char *fname, uid_t owner, gid_t group, UNUSED(mode_t mode), UNUSED(uint32 file_flags))
+ {
+ #ifdef AT_FDCWD
+ extern int am_daemon, am_chrooted;
+@@ -437,14 +487,14 @@ int do_lchown_at(const char *fname, uid_t owner, gid_t group)
+ RETURN_ERROR_IF_RO_OR_LO;
+
+ if (!am_daemon || am_chrooted)
+- return do_lchown(fname, owner, group);
++ return do_lchown(fname, owner, group, mode, file_flags);
+
+ if (!fname || !*fname || *fname == '/')
+- return do_lchown(fname, owner, group);
++ return do_lchown(fname, owner, group, mode, file_flags);
+
+ slash = strrchr(fname, '/');
+ if (!slash)
+- return do_lchown(fname, owner, group);
++ return do_lchown(fname, owner, group, mode, file_flags);
+
+ dlen = slash - fname;
+ if (dlen >= sizeof dirpath) {
+@@ -461,6 +511,23 @@ int do_lchown_at(const char *fname, uid_t owner, gid_t group)
+
+ ret = fchownat(dfd, bname, owner, group, AT_SYMLINK_NOFOLLOW);
+ e = errno;
++#ifdef SUPPORT_FORCE_CHANGE
++ if (force_change && e == EPERM) {
++ if (file_flags == NO_FFLAGS) {
++ STRUCT_STAT st;
++ if (do_lstat(fname, &st) == 0) {
++ mode = st.st_mode;
++ file_flags = st.st_flags;
++ }
++ }
++ if (file_flags != NO_FFLAGS
++ && make_mutable(fname, mode, file_flags, force_change) > 0) {
++ ret = fchownat(dfd, bname, owner, group, AT_SYMLINK_NOFOLLOW);
++ e = errno;
++ undo_make_mutable(fname, file_flags);
++ }
++ }
++#endif
+ close(dfd);
+ errno = e;
+ return ret;
+@@ -506,7 +573,7 @@ int do_mknod(const char *pathname, mode_t mode, dev_t dev)
+ return -1;
+ close(sock);
+ #ifdef HAVE_CHMOD
+- return do_chmod(pathname, mode);
++ return do_chmod(pathname, mode, 0);
+ #else
+ return 0;
+ #endif
+@@ -618,7 +685,21 @@ int do_rmdir(const char *pathname)
+ {
+ if (dry_run) return 0;
+ RETURN_ERROR_IF_RO_OR_LO;
+- return rmdir(pathname);
++ if (rmdir(pathname) == 0)
++ return 0;
++#ifdef SUPPORT_FORCE_CHANGE
++ if (force_change && errno == EPERM) {
++ STRUCT_STAT st;
++ if (do_lstat(pathname, &st) == 0
++ && make_mutable(pathname, st.st_mode, st.st_flags, force_change) > 0) {
++ if (rmdir(pathname) == 0)
++ return 0;
++ undo_make_mutable(pathname, st.st_flags);
++ }
++ errno = EPERM;
++ }
++#endif
++ return -1;
+ }
+
+ /*
+@@ -664,6 +745,18 @@ int do_rmdir_at(const char *pathname)
+
+ ret = unlinkat(dfd, bname, AT_REMOVEDIR);
+ e = errno;
++#ifdef SUPPORT_FORCE_CHANGE
++ if (force_change && e == EPERM) {
++ STRUCT_STAT st;
++ if (do_lstat(pathname, &st) == 0
++ && make_mutable(pathname, st.st_mode, st.st_flags, force_change) > 0) {
++ ret = unlinkat(dfd, bname, AT_REMOVEDIR);
++ e = errno;
++ if (ret != 0)
++ undo_make_mutable(pathname, st.st_flags);
++ }
++ }
++#endif
+ close(dfd);
+ errno = e;
+ return ret;
+@@ -758,7 +851,7 @@ int do_open_at(const char *pathname, int flags, mode_t mode)
+ }
+
+ #ifdef HAVE_CHMOD
+-int do_chmod(const char *path, mode_t mode)
++int do_chmod(const char *path, mode_t mode, UNUSED(uint32 file_flags))
+ {
+ static int switch_step = 0;
+ int code;
+@@ -797,6 +890,23 @@ int do_chmod(const char *path, mode_t mode)
+ code = chmod(path, mode & CHMOD_BITS); /* DISCOURAGED FUNCTION */
+ break;
+ }
++#ifdef SUPPORT_FORCE_CHANGE
++ if (code < 0 && force_change && errno == EPERM && !S_ISLNK(mode)) {
++ if (file_flags == NO_FFLAGS) {
++ STRUCT_STAT st;
++ if (do_lstat(path, &st) == 0)
++ file_flags = st.st_flags;
++ }
++ if (file_flags != NO_FFLAGS
++ && make_mutable(path, mode, file_flags, force_change) > 0) {
++ code = chmod(path, mode & CHMOD_BITS);
++ undo_make_mutable(path, file_flags);
++ if (code == 0)
++ return 0;
++ }
++ errno = EPERM;
++ }
++#endif
+ if (code != 0 && (preserve_perms || preserve_executability))
+ return code;
+ return 0;
+@@ -830,7 +940,7 @@ int do_chmod(const char *path, mode_t mode)
+ Falls back to do_chmod() for absolute paths and for paths with no parent
+ component, where there is nothing to protect against.
+ */
+-int do_chmod_at(const char *fname, mode_t mode)
++int do_chmod_at(const char *fname, mode_t mode, UNUSED(uint32 file_flags))
+ {
+ #ifdef AT_FDCWD
+ extern int am_daemon, am_chrooted;
+@@ -850,14 +960,14 @@ int do_chmod_at(const char *fname, mode_t mode)
+ * already access. Everywhere else, fall through to plain
+ * do_chmod() to avoid the dirfd-open overhead on every call. */
+ if (!am_daemon || am_chrooted)
+- return do_chmod(fname, mode);
++ return do_chmod(fname, mode, file_flags);
+
+ if (!fname || !*fname || *fname == '/' || S_ISLNK(mode))
+- return do_chmod(fname, mode);
++ return do_chmod(fname, mode, file_flags);
+
+ slash = strrchr(fname, '/');
+ if (!slash)
+- return do_chmod(fname, mode);
++ return do_chmod(fname, mode, file_flags);
+
+ dlen = slash - fname;
+ if (dlen >= sizeof dirpath) {
+@@ -872,8 +982,23 @@ int do_chmod_at(const char *fname, mode_t mode)
+ if (dfd < 0)
+ return -1;
+
+- ret = fchmodat(dfd, bname, mode, 0);
++ ret = fchmodat(dfd, bname, mode & CHMOD_BITS, 0);
+ e = errno;
++#ifdef SUPPORT_FORCE_CHANGE
++ if (ret < 0 && force_change && e == EPERM && !S_ISLNK(mode)) {
++ if (file_flags == NO_FFLAGS) {
++ STRUCT_STAT st;
++ if (do_lstat(fname, &st) == 0)
++ file_flags = st.st_flags;
++ }
++ if (file_flags != NO_FFLAGS
++ && make_mutable(fname, mode, file_flags, force_change) > 0) {
++ ret = fchmodat(dfd, bname, mode & CHMOD_BITS, 0);
++ e = errno;
++ undo_make_mutable(fname, file_flags);
++ }
++ }
++#endif
+ close(dfd);
+ errno = e;
+ return ret;
+@@ -883,11 +1008,40 @@ int do_chmod_at(const char *fname, mode_t mode)
+ }
+ #endif
+
+-int do_rename(const char *old_path, const char *new_path)
++#ifdef HAVE_CHFLAGS
++int do_chflags(const char *path, uint32 file_flags)
+ {
+ if (dry_run) return 0;
+ RETURN_ERROR_IF_RO_OR_LO;
+- return rename(old_path, new_path);
++ return chflags(path, file_flags);
++}
++#endif
++
++int do_rename(const char *old_path, const char *new_path, UNUSED(mode_t mode), UNUSED(uint32 file_flags))
++{
++ int ret;
++
++ if (dry_run) return 0;
++ RETURN_ERROR_IF_RO_OR_LO;
++ ret = rename(old_path, new_path);
++#ifdef SUPPORT_FORCE_CHANGE
++ if (ret < 0 && force_change && errno == EPERM) {
++ if (file_flags == NO_FFLAGS) {
++ STRUCT_STAT st;
++ if (do_lstat(new_path, &st) == 0)
++ file_flags = st.st_flags;
++ }
++ if (file_flags != NO_FFLAGS
++ && make_mutable(new_path, mode, file_flags, force_change) > 0) {
++ ret = rename(old_path, new_path);
++ undo_make_mutable(new_path, file_flags);
++ if (ret == 0)
++ return 0;
++ }
++ errno = EPERM;
++ }
++#endif
++ return ret;
+ }
+
+ /*
+@@ -907,7 +1061,7 @@ int do_rename(const char *old_path, const char *new_path)
+ parent and absolute-path cases, identical to the other do_*_at()
+ wrappers.
+ */
+-int do_rename_at(const char *old_path, const char *new_path)
++int do_rename_at(const char *old_path, const char *new_path, UNUSED(mode_t mode), UNUSED(uint32 file_flags))
+ {
+ #ifdef AT_FDCWD
+ extern int am_daemon, am_chrooted;
+@@ -921,16 +1075,16 @@ int do_rename_at(const char *old_path, const char *new_path)
+ RETURN_ERROR_IF_RO_OR_LO;
+
+ if (!am_daemon || am_chrooted)
+- return do_rename(old_path, new_path);
++ return do_rename(old_path, new_path, mode, file_flags);
+
+ if (!old_path || !*old_path || *old_path == '/'
+ || !new_path || !*new_path || *new_path == '/')
+- return do_rename(old_path, new_path);
++ return do_rename(old_path, new_path, mode, file_flags);
+
+ old_slash = strrchr(old_path, '/');
+ new_slash = strrchr(new_path, '/');
+ if (!old_slash || !new_slash)
+- return do_rename(old_path, new_path);
++ return do_rename(old_path, new_path, mode, file_flags);
+
+ old_dlen = old_slash - old_path;
+ new_dlen = new_slash - new_path;
+@@ -963,6 +1117,24 @@ int do_rename_at(const char *old_path, const char *new_path)
+
+ ret = renameat(old_dfd, old_bname, new_dfd, new_bname);
+ e = errno;
++#ifdef SUPPORT_FORCE_CHANGE
++ if (ret < 0 && force_change && e == EPERM) {
++ if (file_flags == NO_FFLAGS) {
++ STRUCT_STAT st;
++ if (do_lstat(new_path, &st) == 0)
++ file_flags = st.st_flags;
++ }
++ if (file_flags != NO_FFLAGS
++ && make_mutable(new_path, mode, file_flags, force_change) > 0) {
++ ret = renameat(old_dfd, old_bname, new_dfd, new_bname);
++ e = errno;
++ undo_make_mutable(new_path, file_flags);
++ if (ret == 0)
++ return 0;
++ }
++ errno = EPERM;
++ }
++#endif
+ if (new_dfd != old_dfd)
+ close(new_dfd);
+ close(old_dfd);
+--- t_chmod_secure.c.orig
++++ t_chmod_secure.c
+@@ -88,26 +88,26 @@ int main(int argc, char **argv)
+ */
+
+ /* Scenario A: legitimate parent dir-symlink, chmod must succeed. */
+- int rc = do_chmod_at("inside_link/sentinel", 0640);
++ int rc = do_chmod_at("inside_link/sentinel", 0640, 0);
+ check("A: legit dir-symlink within tree",
+ rc, 1, "realdir/sentinel", 0640);
+
+ /* Scenario B: parent symlink escapes the tree -- chmod must be
+ * rejected and the outside file's mode must be unchanged. */
+- rc = do_chmod_at("escape_link/sentinel", 0666);
++ rc = do_chmod_at("escape_link/sentinel", 0666, 0);
+ check("B: parent symlink escapes tree (the attack)",
+ rc, 0, "../trap/sentinel", 0600);
+
+ /* Scenario C: plain relative path with no symlink components,
+ * regression check that the safe wrapper doesn't break the
+ * normal case. */
+- rc = do_chmod_at("realdir/sentinel", 0644);
++ rc = do_chmod_at("realdir/sentinel", 0644, 0);
+ check("C: plain relative path (regression check)",
+ rc, 1, "realdir/sentinel", 0644);
+
+ /* Scenario D: top-level file, no parent directory component.
+ * Falls back to do_chmod(); should succeed. */
+- rc = do_chmod_at("topfile", 0640);
++ rc = do_chmod_at("topfile", 0640, 0);
+ check("D: top-level file, no parent component",
+ rc, 1, "topfile", 0640);
+
+--- t_stub.c.orig
++++ t_stub.c
+@@ -30,7 +30,9 @@ int preallocate_files = 0;
+ int protect_args = 0;
+ int module_id = -1;
+ int relative_paths = 0;
++int force_change = 0;
+ unsigned int module_dirlen = 0;
++int preserve_acls = 0;
+ int preserve_xattrs = 0;
+ int preserve_perms = 0;
+ int preserve_executability = 0;
+@@ -118,3 +120,23 @@ filter_rule_list daemon_filter_list;
+ {
+ return cst ? 0 : 0;
+ }
++
++#if defined SUPPORT_FILE_FLAGS || defined SUPPORT_FORCE_CHANGE
++ int make_mutable(UNUSED(const char *fname), UNUSED(mode_t mode), UNUSED(uint32 file_flags), UNUSED(uint32 iflags))
++{
++ return 0;
++}
++
++/* Undo a prior make_mutable() call that returned a 1. */
++ int undo_make_mutable(UNUSED(const char *fname), UNUSED(uint32 file_flags))
++{
++ return 0;
++}
++#endif
++
++#ifdef SUPPORT_XATTRS
++ int x_lstat(UNUSED(const char *fname), UNUSED(STRUCT_STAT *fst), UNUSED(STRUCT_STAT *xst))
++{
++ return -1;
++}
++#endif
+--- testsuite/rsync.fns.orig
++++ testsuite/rsync.fns
+@@ -26,9 +26,9 @@ chkfile="$scratchdir/rsync.chk"
+ outfile="$scratchdir/rsync.out"
+
+ # For itemized output:
+-all_plus='+++++++++'
+-allspace=' '
+-dots='.....' # trailing dots after changes
++all_plus='++++++++++'
++allspace=' '
++dots='......' # trailing dots after changes
+ tab_ch=' ' # a single tab character
+
+ # Berkley's nice.
+--- usage.c.orig
++++ usage.c
+@@ -138,6 +138,11 @@ static void print_info_flags(enum logcode f)
+ #endif
+ "crtimes",
+
++#ifndef SUPPORT_FILE_FLAGS
++ "no "
++#endif
++ "file-flags",
++
+ "*Optimizations",
+
+ #ifndef USE_ROLL_SIMD
+--- util1.c.orig
++++ util1.c
+@@ -34,6 +34,7 @@ extern int relative_paths;
+ extern int preserve_xattrs;
+ extern int omit_link_times;
+ extern int preallocate_files;
++extern int force_change;
+ extern char *module_dir;
+ extern unsigned int module_dirlen;
+ extern char *partial_dir;
+@@ -116,6 +117,33 @@ void print_child_argv(const char *prefix, char **cmd)
+ rprintf(FCLIENT, " (%d args)\n", cnt);
+ }
+
++#ifdef SUPPORT_FORCE_CHANGE
++static int try_a_force_change(const char *fname, STRUCT_STAT *stp)
++{
++ uint32 file_flags = ST_FLAGS(*stp);
++ if (file_flags == NO_FFLAGS) {
++ STRUCT_STAT st;
++ if (x_lstat(fname, &st, NULL) == 0)
++ file_flags = st.st_flags;
++ }
++ if (file_flags != NO_FFLAGS && make_mutable(fname, stp->st_mode, file_flags, force_change) > 0) {
++ int ret, save_force_change = force_change;
++
++ force_change = 0; /* Make certain we can't come back here. */
++ ret = set_times(fname, stp);
++ force_change = save_force_change;
++
++ undo_make_mutable(fname, file_flags);
++
++ return ret;
++ }
++
++ errno = EPERM;
++
++ return -1;
++}
++#endif
++
+ /* This returns 0 for success, 1 for a symlink if symlink time-setting
+ * is not possible, or -1 for any other error. */
+ int set_times(const char *fname, STRUCT_STAT *stp)
+@@ -143,6 +171,10 @@ int set_times(const char *fname, STRUCT_STAT *stp)
+ #include "case_N.h"
+ if (do_utimensat_at(fname, stp) == 0)
+ break;
++#ifdef SUPPORT_FORCE_CHANGE
++ if (force_change && errno == EPERM && try_a_force_change(fname, stp) == 0)
++ break;
++#endif
+ if (errno != ENOSYS)
+ return -1;
+ switch_step++;
+@@ -152,6 +184,10 @@ int set_times(const char *fname, STRUCT_STAT *stp)
+ #include "case_N.h"
+ if (do_lutimes(fname, stp) == 0)
+ break;
++#ifdef SUPPORT_FORCE_CHANGE
++ if (force_change && errno == EPERM && try_a_force_change(fname, stp) == 0)
++ break;
++#endif
+ if (errno != ENOSYS)
+ return -1;
+ switch_step++;
+@@ -173,6 +209,10 @@ int set_times(const char *fname, STRUCT_STAT *stp)
+ if (do_utime(fname, stp) == 0)
+ break;
+ #endif
++#ifdef SUPPORT_FORCE_CHANGE
++ if (force_change && errno == EPERM && try_a_force_change(fname, stp) == 0)
++ break;
++#endif
+
+ return -1;
+ }
+@@ -532,7 +572,7 @@ int robust_unlink(const char *fname)
+ }
+
+ /* maybe we should return rename()'s exit status? Nah. */
+- if (do_rename_at(fname, path) != 0) {
++ if (do_rename_at(fname, path, 0, NO_FFLAGS) != 0) {
+ errno = ETXTBSY;
+ return -1;
+ }
+@@ -555,7 +595,7 @@ int robust_rename(const char *from, const char *to, const char *partialptr,
+ return 0;
+
+ while (tries--) {
+- if (do_rename_at(from, to) == 0)
++ if (do_rename_at(from, to, 0, NO_FFLAGS) == 0)
+ return 0;
+
+ switch (errno) {
+--- xattrs.c.orig
++++ xattrs.c
+@@ -1094,7 +1094,7 @@ int set_xattr(const char *fname, const struct file_struct *file, const char *fna
+ && !S_ISLNK(sxp->st.st_mode)
+ #endif
+ && access(fname, W_OK) < 0
+- && do_chmod_at(fname, (sxp->st.st_mode & CHMOD_BITS) | S_IWUSR) == 0)
++ && do_chmod_at(fname, (sxp->st.st_mode & CHMOD_BITS) | S_IWUSR, ST_FLAGS(sxp->st)) == 0)
+ added_write_perm = 1;
+
+ ndx = F_XATTR(file);
+@@ -1102,7 +1102,7 @@ int set_xattr(const char *fname, const struct file_struct *file, const char *fna
+ lst = &glst->xa_items;
+ int return_value = rsync_xal_set(fname, lst, fnamecmp, sxp);
+ if (added_write_perm) /* remove the temporary write permission */
+- do_chmod_at(fname, sxp->st.st_mode);
++ do_chmod_at(fname, sxp->st.st_mode, ST_FLAGS(sxp->st));
+ return return_value;
+ }
+
+@@ -1219,7 +1219,7 @@ int set_stat_xattr(const char *fname, struct file_struct *file, mode_t new_mode)
+ mode = (fst.st_mode & _S_IFMT) | (fmode & ACCESSPERMS)
+ | (S_ISDIR(fst.st_mode) ? 0700 : 0600);
+ if (fst.st_mode != mode)
+- do_chmod_at(fname, mode);
++ do_chmod_at(fname, mode, ST_FLAGS(fst));
+ if (!IS_DEVICE(fst.st_mode))
+ fst.st_rdev = 0; /* just in case */
+
Index: net/rsync/files/extrapatch-main.c
===================================================================
--- net/rsync/files/extrapatch-main.c
+++ /dev/null
@@ -1,10 +0,0 @@
---- main.c.orig 2013-10-02 10:47:24.479295402 +0200
-+++ main.c 2013-10-02 10:47:43.659318579 +0200
-@@ -30,6 +30,7 @@
- #include <sys/sysctl.h>
- #endif
-
-+extern int force_change;
- extern int dry_run;
- extern int list_only;
- extern int io_timeout;
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Mon, Jun 22, 8:47 PM (13 m, 21 s)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
34166721
Default Alt Text
D57645.diff (49 KB)
Attached To
Mode
D57645: net/rsync: Add back the FLAGS option
Attached
Detach File
Event Timeline
Log In to Comment