Updated patches to work with the latest code.
[rsync-patches.git] / fileflags.diff
1 This patch provides --fileflags, which preserves the st_flags stat() field.
2 Modified from a patch that was written by Rolf Grossmann.
3
4 To use this patch, run these commands for a successful build:
5
6     patch -p1 <patches/fileflags.diff
7     ./prepare-source
8     ./configure
9     make
10
11 based-on: 3bd9f51917ed5718275c6132006be155239a0550
12 diff --git a/Makefile.in b/Makefile.in
13 --- a/Makefile.in
14 +++ b/Makefile.in
15 @@ -43,7 +43,7 @@ popt_OBJS=popt/findme.o  popt/popt.o  popt/poptconfig.o \
16         popt/popthelp.o popt/poptparse.o
17  OBJS=$(OBJS1) $(OBJS2) $(OBJS3) $(DAEMON_OBJ) $(LIBOBJ) $(ZLIBOBJ) @BUILD_POPT@
18  
19 -TLS_OBJ = tls.o syscall.o lib/compat.o lib/snprintf.o lib/permstring.o lib/sysxattrs.o @BUILD_POPT@
20 +TLS_OBJ = tls.o syscall.o t_stub.o lib/compat.o lib/snprintf.o lib/permstring.o lib/sysxattrs.o @BUILD_POPT@
21  
22  # Programs we must have to run the test cases
23  CHECK_PROGS = rsync$(EXEEXT) tls$(EXEEXT) getgroups$(EXEEXT) getfsdev$(EXEEXT) \
24 @@ -111,7 +111,7 @@ getgroups$(EXEEXT): getgroups.o
25  getfsdev$(EXEEXT): getfsdev.o
26         $(CC) $(CFLAGS) $(LDFLAGS) -o $@ getfsdev.o $(LIBS)
27  
28 -TRIMSLASH_OBJ = trimslash.o syscall.o lib/compat.o lib/snprintf.o
29 +TRIMSLASH_OBJ = trimslash.o syscall.o t_stub.o lib/compat.o lib/snprintf.o
30  trimslash$(EXEEXT): $(TRIMSLASH_OBJ)
31         $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(TRIMSLASH_OBJ) $(LIBS)
32  
33 diff --git a/compat.c b/compat.c
34 --- a/compat.c
35 +++ b/compat.c
36 @@ -42,9 +42,11 @@ extern int checksum_seed;
37  extern int basis_dir_cnt;
38  extern int prune_empty_dirs;
39  extern int protocol_version;
40 +extern int force_change;
41  extern int protect_args;
42  extern int preserve_uid;
43  extern int preserve_gid;
44 +extern int preserve_fileflags;
45  extern int preserve_acls;
46  extern int preserve_xattrs;
47  extern int need_messages_from_generator;
48 @@ -62,7 +64,7 @@ extern char *iconv_opt;
49  #endif
50  
51  /* These index values are for the file-list's extra-attribute array. */
52 -int uid_ndx, gid_ndx, acls_ndx, xattrs_ndx, unsort_ndx;
53 +int uid_ndx, gid_ndx, fileflags_ndx, acls_ndx, xattrs_ndx, unsort_ndx;
54  
55  int receiver_symlink_times = 0; /* receiver can set the time on a symlink */
56  int sender_symlink_iconv = 0;  /* sender should convert symlink content */
57 @@ -139,6 +141,8 @@ void setup_protocol(int f_out,int f_in)
58                 uid_ndx = ++file_extra_cnt;
59         if (preserve_gid)
60                 gid_ndx = ++file_extra_cnt;
61 +       if (preserve_fileflags || (force_change && !am_sender))
62 +               fileflags_ndx = ++file_extra_cnt;
63         if (preserve_acls && !am_sender)
64                 acls_ndx = ++file_extra_cnt;
65         if (preserve_xattrs)
66 diff --git a/configure.ac b/configure.ac
67 --- a/configure.ac
68 +++ b/configure.ac
69 @@ -589,7 +589,7 @@ AC_CHECK_FUNCS(waitpid wait4 getcwd strdup chown chmod lchmod mknod mkfifo \
70      setlocale setmode open64 lseek64 mkstemp64 mtrace va_copy __va_copy \
71      seteuid strerror putenv iconv_open locale_charset nl_langinfo getxattr \
72      extattr_get_link sigaction sigprocmask setattrlist getgrouplist \
73 -    initgroups utimensat)
74 +    initgroups utimensat chflags)
75  
76  dnl cygwin iconv.h defines iconv_open as libiconv_open
77  if test x"$ac_cv_func_iconv_open" != x"yes"; then
78 diff --git a/delete.c b/delete.c
79 --- a/delete.c
80 +++ b/delete.c
81 @@ -25,6 +25,7 @@
82  extern int am_root;
83  extern int make_backups;
84  extern int max_delete;
85 +extern int force_change;
86  extern char *backup_dir;
87  extern char *backup_suffix;
88  extern int backup_suffix_len;
89 @@ -97,8 +98,12 @@ static enum delret delete_dir_contents(char *fname, uint16 flags)
90                 }
91  
92                 strlcpy(p, fp->basename, remainder);
93 +#ifdef SUPPORT_FORCE_CHANGE
94 +               if (force_change)
95 +                       make_mutable(fname, fp->mode, F_FFLAGS(fp), force_change);
96 +#endif
97                 if (!(fp->mode & S_IWUSR) && !am_root && fp->flags & FLAG_OWNED_BY_US)
98 -                       do_chmod(fname, fp->mode | S_IWUSR);
99 +                       do_chmod(fname, fp->mode | S_IWUSR, NO_FFLAGS);
100                 /* Save stack by recursing to ourself directly. */
101                 if (S_ISDIR(fp->mode)) {
102                         if (delete_dir_contents(fname, flags | DEL_RECURSE) != DR_SUCCESS)
103 @@ -139,11 +144,18 @@ enum delret delete_item(char *fbuf, uint16 mode, uint16 flags)
104         }
105  
106         if (flags & DEL_NO_UID_WRITE)
107 -               do_chmod(fbuf, mode | S_IWUSR);
108 +               do_chmod(fbuf, mode | S_IWUSR, NO_FFLAGS);
109  
110         if (S_ISDIR(mode) && !(flags & DEL_DIR_IS_EMPTY)) {
111                 /* This only happens on the first call to delete_item() since
112                  * delete_dir_contents() always calls us w/DEL_DIR_IS_EMPTY. */
113 +#ifdef SUPPORT_FORCE_CHANGE
114 +               if (force_change) {
115 +                       STRUCT_STAT st;
116 +                       if (x_lstat(fbuf, &st, NULL) == 0)
117 +                               make_mutable(fbuf, st.st_mode, st.st_flags, force_change);
118 +               }
119 +#endif
120                 ignore_perishable = 1;
121                 /* If DEL_RECURSE is not set, this just reports emptiness. */
122                 ret = delete_dir_contents(fbuf, flags);
123 diff --git a/flist.c b/flist.c
124 --- a/flist.c
125 +++ b/flist.c
126 @@ -50,6 +50,7 @@ extern int preserve_links;
127  extern int preserve_hard_links;
128  extern int preserve_devices;
129  extern int preserve_specials;
130 +extern int preserve_fileflags;
131  extern int delete_during;
132  extern int missing_args;
133  extern int eol_nulls;
134 @@ -406,6 +407,9 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
135  {
136         static time_t modtime;
137         static mode_t mode;
138 +#ifdef SUPPORT_FILEFLAGS
139 +       static uint32 fileflags;
140 +#endif
141  #ifdef SUPPORT_HARD_LINKS
142         static int64 dev;
143  #endif
144 @@ -449,6 +453,14 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
145                 xflags |= XMIT_SAME_MODE;
146         else
147                 mode = file->mode;
148 +#ifdef SUPPORT_FILEFLAGS
149 +       if (preserve_fileflags) {
150 +               if (F_FFLAGS(file) == fileflags)
151 +                       xflags |= XMIT_SAME_FLAGS;
152 +               else
153 +                       fileflags = F_FFLAGS(file);
154 +       }
155 +#endif
156  
157         if (preserve_devices && IS_DEVICE(mode)) {
158                 if (protocol_version < 28) {
159 @@ -590,6 +602,10 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
160                 write_varint(f, F_MOD_NSEC(file));
161         if (!(xflags & XMIT_SAME_MODE))
162                 write_int(f, to_wire_mode(mode));
163 +#ifdef SUPPORT_FILEFLAGS
164 +       if (preserve_fileflags && !(xflags & XMIT_SAME_FLAGS))
165 +               write_int(f, (int)fileflags);
166 +#endif
167         if (preserve_uid && !(xflags & XMIT_SAME_UID)) {
168                 if (protocol_version < 30)
169                         write_int(f, uid);
170 @@ -679,6 +695,9 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x
171  {
172         static int64 modtime;
173         static mode_t mode;
174 +#ifdef SUPPORT_FILEFLAGS
175 +       static uint32 fileflags;
176 +#endif
177  #ifdef SUPPORT_HARD_LINKS
178         static int64 dev;
179  #endif
180 @@ -824,6 +843,10 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x
181  
182         if (chmod_modes && !S_ISLNK(mode) && mode)
183                 mode = tweak_mode(mode, chmod_modes);
184 +#ifdef SUPPORT_FILEFLAGS
185 +       if (preserve_fileflags && !(xflags & XMIT_SAME_FLAGS))
186 +               fileflags = (uint32)read_int(f);
187 +#endif
188  
189         if (preserve_uid && !(xflags & XMIT_SAME_UID)) {
190                 if (protocol_version < 30)
191 @@ -975,6 +998,10 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x
192         }
193  #endif
194         file->mode = mode;
195 +#ifdef SUPPORT_FILEFLAGS
196 +       if (preserve_fileflags)
197 +               F_FFLAGS(file) = fileflags;
198 +#endif
199         if (preserve_uid)
200                 F_OWNER(file) = uid;
201         if (preserve_gid) {
202 @@ -1372,6 +1399,10 @@ struct file_struct *make_file(const char *fname, struct file_list *flist,
203         }
204  #endif
205         file->mode = st.st_mode;
206 +#if defined SUPPORT_FILEFLAGS || defined SUPPORT_FORCE_CHANGE
207 +       if (fileflags_ndx)
208 +               F_FFLAGS(file) = st.st_flags;
209 +#endif
210         if (preserve_uid)
211                 F_OWNER(file) = st.st_uid;
212         if (preserve_gid)
213 diff --git a/generator.c b/generator.c
214 --- a/generator.c
215 +++ b/generator.c
216 @@ -42,8 +42,10 @@ extern int preserve_devices;
217  extern int preserve_specials;
218  extern int preserve_hard_links;
219  extern int preserve_executability;
220 +extern int preserve_fileflags;
221  extern int preserve_perms;
222  extern int preserve_times;
223 +extern int force_change;
224  extern int delete_mode;
225  extern int delete_before;
226  extern int delete_during;
227 @@ -460,6 +462,10 @@ int unchanged_attrs(const char *fname, struct file_struct *file, stat_x *sxp)
228                         return 0;
229                 if (perms_differ(file, sxp))
230                         return 0;
231 +#ifdef SUPPORT_FILEFLAGS
232 +               if (preserve_fileflags && sxp->st.st_flags != F_FFLAGS(file))
233 +                       return 0;
234 +#endif
235                 if (ownership_differs(file, sxp))
236                         return 0;
237  #ifdef SUPPORT_ACLS
238 @@ -511,6 +517,11 @@ void itemize(const char *fnamecmp, struct file_struct *file, int ndx, int statre
239                 if (gid_ndx && !(file->flags & FLAG_SKIP_GROUP)
240                     && sxp->st.st_gid != (gid_t)F_GROUP(file))
241                         iflags |= ITEM_REPORT_GROUP;
242 +#ifdef SUPPORT_FILEFLAGS
243 +               if (preserve_fileflags && !S_ISLNK(file->mode)
244 +                && sxp->st.st_flags != F_FFLAGS(file))
245 +                       iflags |= ITEM_REPORT_FFLAGS;
246 +#endif
247  #ifdef SUPPORT_ACLS
248                 if (preserve_acls && !S_ISLNK(file->mode)) {
249                         if (!ACL_READY(*sxp))
250 @@ -1320,6 +1331,10 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
251                         file->mode = dest_mode(file->mode, sx.st.st_mode,
252                                                dflt_perms, statret == 0);
253                 }
254 +#ifdef SUPPORT_FORCE_CHANGE
255 +               if (force_change && !preserve_fileflags)
256 +                       F_FFLAGS(file) = sx.st.st_flags;
257 +#endif
258                 if (statret != 0 && basis_dir[0] != NULL) {
259                         int j = try_dests_non(file, fname, ndx, fnamecmpbuf, &sx,
260                                               itemizing, code);
261 @@ -1362,10 +1377,15 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
262                 /* We need to ensure that the dirs in the transfer have writable
263                  * permissions during the time we are putting files within them.
264                  * This is then fixed after the transfer is done. */
265 +#ifdef SUPPORT_FORCE_CHANGE
266 +               if (force_change && F_FFLAGS(file) & force_change
267 +                && make_mutable(fname, file->mode, F_FFLAGS(file), force_change))
268 +                       need_retouch_dir_perms = 1;
269 +#endif
270  #ifdef HAVE_CHMOD
271                 if (!am_root && !(file->mode & S_IWUSR) && dir_tweaking) {
272                         mode_t mode = file->mode | S_IWUSR;
273 -                       if (do_chmod(fname, mode) < 0) {
274 +                       if (do_chmod(fname, mode, 0) < 0) {
275                                 rsyserr(FERROR_XFER, errno,
276                                         "failed to modify permissions on %s",
277                                         full_fname(fname));
278 @@ -1400,6 +1420,10 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
279                 file->mode = dest_mode(file->mode, sx.st.st_mode, dflt_perms,
280                                        exists);
281         }
282 +#ifdef SUPPORT_FORCE_CHANGE
283 +       if (force_change && !preserve_fileflags)
284 +               F_FFLAGS(file) = sx.st.st_flags;
285 +#endif
286  
287  #ifdef SUPPORT_HARD_LINKS
288         if (preserve_hard_links && F_HLINK_NOT_FIRST(file)
289 @@ -1979,13 +2003,17 @@ static void touch_up_dirs(struct file_list *flist, int ndx)
290                         continue;
291                 fname = f_name(file, NULL);
292                 if (fix_dir_perms)
293 -                       do_chmod(fname, file->mode);
294 +                       do_chmod(fname, file->mode, 0);
295                 if (need_retouch_dir_times) {
296                         STRUCT_STAT st;
297                         if (link_stat(fname, &st, 0) == 0
298                          && cmp_time(st.st_mtime, file->modtime) != 0)
299 -                               set_modtime(fname, file->modtime, F_MOD_NSEC(file), file->mode);
300 +                               set_modtime(fname, file->modtime, F_MOD_NSEC(file), file->mode, 0);
301                 }
302 +#ifdef SUPPORT_FORCE_CHANGE
303 +               if (force_change && F_FFLAGS(file) & force_change)
304 +                       undo_make_mutable(fname, F_FFLAGS(file));
305 +#endif
306                 if (counter >= loopchk_limit) {
307                         if (allowed_lull)
308                                 maybe_send_keepalive(time(NULL), MSK_ALLOW_FLUSH);
309 diff --git a/log.c b/log.c
310 --- a/log.c
311 +++ b/log.c
312 @@ -731,7 +731,7 @@ static void log_formatted(enum logcode code, const char *format, const char *op,
313                         c[5] = !(iflags & ITEM_REPORT_PERMS) ? '.' : 'p';
314                         c[6] = !(iflags & ITEM_REPORT_OWNER) ? '.' : 'o';
315                         c[7] = !(iflags & ITEM_REPORT_GROUP) ? '.' : 'g';
316 -                       c[8] = !(iflags & ITEM_REPORT_ATIME) ? '.' : 'u';
317 +                       c[8] = !(iflags & ITEM_REPORT_FFLAGS) ? '.' : 'f';
318                         c[9] = !(iflags & ITEM_REPORT_ACL) ? '.' : 'a';
319                         c[10] = !(iflags & ITEM_REPORT_XATTR) ? '.' : 'x';
320                         c[11] = '\0';
321 diff --git a/options.c b/options.c
322 --- a/options.c
323 +++ b/options.c
324 @@ -53,6 +53,7 @@ int preserve_hard_links = 0;
325  int preserve_acls = 0;
326  int preserve_xattrs = 0;
327  int preserve_perms = 0;
328 +int preserve_fileflags = 0;
329  int preserve_executability = 0;
330  int preserve_devices = 0;
331  int preserve_specials = 0;
332 @@ -86,6 +87,7 @@ int numeric_ids = 0;
333  int msgs2stderr = 0;
334  int allow_8bit_chars = 0;
335  int force_delete = 0;
336 +int force_change = 0;
337  int io_timeout = 0;
338  int prune_empty_dirs = 0;
339  int use_qsort = 0;
340 @@ -568,6 +570,7 @@ static void print_rsync_version(enum logcode f)
341         char const *links = "no ";
342         char const *iconv = "no ";
343         char const *ipv6 = "no ";
344 +       char const *fileflags = "no ";
345         STRUCT_STAT *dumstat;
346  
347  #if SUBPROTOCOL_VERSION != 0
348 @@ -601,6 +604,9 @@ static void print_rsync_version(enum logcode f)
349  #ifdef CAN_SET_SYMLINK_TIMES
350         symtimes = "";
351  #endif
352 +#ifdef SUPPORT_FILEFLAGS
353 +       fileflags = "";
354 +#endif
355  
356         rprintf(f, "%s  version %s  protocol version %d%s\n",
357                 RSYNC_NAME, RSYNC_VERSION, PROTOCOL_VERSION, subprotocol);
358 @@ -614,8 +620,8 @@ static void print_rsync_version(enum logcode f)
359                 (int)(sizeof (int64) * 8));
360         rprintf(f, "    %ssocketpairs, %shardlinks, %ssymlinks, %sIPv6, batchfiles, %sinplace,\n",
361                 got_socketpair, hardlinks, links, ipv6, have_inplace);
362 -       rprintf(f, "    %sappend, %sACLs, %sxattrs, %siconv, %ssymtimes\n",
363 -               have_inplace, acls, xattrs, iconv, symtimes);
364 +       rprintf(f, "    %sappend, %sACLs, %sxattrs, %siconv, %ssymtimes, %sfile-flags\n",
365 +               have_inplace, acls, xattrs, iconv, symtimes, fileflags);
366  
367  #ifdef MAINTAINER_MODE
368         rprintf(f, "Panic Action: \"%s\"\n", get_panic_action());
369 @@ -685,6 +691,9 @@ void usage(enum logcode F)
370    rprintf(F," -K, --keep-dirlinks         treat symlinked dir on receiver as dir\n");
371    rprintf(F," -H, --hard-links            preserve hard links\n");
372    rprintf(F," -p, --perms                 preserve permissions\n");
373 +#ifdef SUPPORT_FILEFLAGS
374 +  rprintf(F,"     --fileflags             preserve file-flags (aka chflags)\n");
375 +#endif
376    rprintf(F," -E, --executability         preserve the file's executability\n");
377    rprintf(F,"     --chmod=CHMOD           affect file and/or directory permissions\n");
378  #ifdef SUPPORT_ACLS
379 @@ -725,7 +734,12 @@ void usage(enum logcode F)
380    rprintf(F,"     --ignore-missing-args   ignore missing source args without error\n");
381    rprintf(F,"     --delete-missing-args   delete missing source args from destination\n");
382    rprintf(F,"     --ignore-errors         delete even if there are I/O errors\n");
383 -  rprintf(F,"     --force                 force deletion of directories even if not empty\n");
384 +  rprintf(F,"     --force-delete          force deletion of directories even if not empty\n");
385 +#ifdef SUPPORT_FORCE_CHANGE
386 +  rprintf(F,"     --force-change          affect user-/system-immutable files/dirs\n");
387 +  rprintf(F,"     --force-uchange         affect user-immutable files/dirs\n");
388 +  rprintf(F,"     --force-schange         affect system-immutable files/dirs\n");
389 +#endif
390    rprintf(F,"     --max-delete=NUM        don't delete more than NUM files\n");
391    rprintf(F,"     --max-size=SIZE         don't transfer any file larger than SIZE\n");
392    rprintf(F,"     --min-size=SIZE         don't transfer any file smaller than SIZE\n");
393 @@ -838,6 +852,10 @@ static struct poptOption long_options[] = {
394    {"perms",           'p', POPT_ARG_VAL,    &preserve_perms, 1, 0, 0 },
395    {"no-perms",         0,  POPT_ARG_VAL,    &preserve_perms, 0, 0, 0 },
396    {"no-p",             0,  POPT_ARG_VAL,    &preserve_perms, 0, 0, 0 },
397 +#ifdef SUPPORT_FILEFLAGS
398 +  {"fileflags",        0,  POPT_ARG_VAL,    &preserve_fileflags, 1, 0, 0 },
399 +  {"no-fileflags",     0,  POPT_ARG_VAL,    &preserve_fileflags, 0, 0, 0 },
400 +#endif
401    {"executability",   'E', POPT_ARG_NONE,   &preserve_executability, 0, 0, 0 },
402    {"acls",            'A', POPT_ARG_NONE,   0, 'A', 0, 0 },
403    {"no-acls",          0,  POPT_ARG_VAL,    &preserve_acls, 0, 0, 0 },
404 @@ -923,6 +941,14 @@ static struct poptOption long_options[] = {
405    {"remove-source-files",0,POPT_ARG_VAL,    &remove_source_files, 1, 0, 0 },
406    {"force",            0,  POPT_ARG_VAL,    &force_delete, 1, 0, 0 },
407    {"no-force",         0,  POPT_ARG_VAL,    &force_delete, 0, 0, 0 },
408 +  {"force-delete",     0,  POPT_ARG_VAL,    &force_delete, 1, 0, 0 },
409 +  {"no-force-delete",  0,  POPT_ARG_VAL,    &force_delete, 0, 0, 0 },
410 +#ifdef SUPPORT_FORCE_CHANGE
411 +  {"force-change",     0,  POPT_ARG_VAL,    &force_change, ALL_IMMUTABLE, 0, 0 },
412 +  {"no-force-change",  0,  POPT_ARG_VAL,    &force_change, 0, 0, 0 },
413 +  {"force-uchange",    0,  POPT_ARG_VAL,    &force_change, USR_IMMUTABLE, 0, 0 },
414 +  {"force-schange",    0,  POPT_ARG_VAL,    &force_change, SYS_IMMUTABLE, 0, 0 },
415 +#endif
416    {"ignore-errors",    0,  POPT_ARG_VAL,    &ignore_errors, 1, 0, 0 },
417    {"no-ignore-errors", 0,  POPT_ARG_VAL,    &ignore_errors, 0, 0, 0 },
418    {"max-delete",       0,  POPT_ARG_INT,    &max_delete, 0, 0, 0 },
419 @@ -2450,6 +2476,9 @@ void server_options(char **args, int *argc_p)
420         if (xfer_dirs && !recurse && delete_mode && am_sender)
421                 args[ac++] = "--no-r";
422  
423 +       if (preserve_fileflags)
424 +               args[ac++] = "--fileflags";
425 +
426         if (do_compression && def_compress_level != Z_DEFAULT_COMPRESSION) {
427                 if (asprintf(&arg, "--compress-level=%d", def_compress_level) < 0)
428                         goto oom;
429 @@ -2537,6 +2566,16 @@ void server_options(char **args, int *argc_p)
430                         args[ac++] = "--delete-excluded";
431                 if (force_delete)
432                         args[ac++] = "--force";
433 +#ifdef SUPPORT_FORCE_CHANGE
434 +               if (force_change) {
435 +                       if (force_change == ALL_IMMUTABLE)
436 +                               args[ac++] = "--force-change";
437 +                       else if (force_change == USR_IMMUTABLE)
438 +                               args[ac++] = "--force-uchange";
439 +                       else if (force_change == SYS_IMMUTABLE)
440 +                               args[ac++] = "--force-schange";
441 +               }
442 +#endif
443                 if (write_batch < 0)
444                         args[ac++] = "--only-write-batch=X";
445                 if (am_root > 1)
446 diff --git a/rsync.c b/rsync.c
447 --- a/rsync.c
448 +++ b/rsync.c
449 @@ -31,6 +31,7 @@ extern int dry_run;
450  extern int preserve_acls;
451  extern int preserve_xattrs;
452  extern int preserve_perms;
453 +extern int preserve_fileflags;
454  extern int preserve_executability;
455  extern int preserve_times;
456  extern int am_root;
457 @@ -443,6 +444,39 @@ mode_t dest_mode(mode_t flist_mode, mode_t stat_mode, int dflt_perms,
458         return new_mode;
459  }
460  
461 +#if defined SUPPORT_FILEFLAGS || defined SUPPORT_FORCE_CHANGE
462 +/* Set a file's st_flags. */
463 +static int set_fileflags(const char *fname, uint32 fileflags)
464 +{
465 +       if (do_chflags(fname, fileflags) != 0) {
466 +               rsyserr(FERROR_XFER, errno,
467 +                       "failed to set file flags on %s",
468 +                       full_fname(fname));
469 +               return 0;
470 +       }
471 +
472 +       return 1;
473 +}
474 +
475 +/* Remove immutable flags from an object, so it can be altered/removed. */
476 +int make_mutable(const char *fname, mode_t mode, uint32 fileflags, uint32 iflags)
477 +{
478 +       if (S_ISLNK(mode) || !(fileflags & iflags))
479 +               return 0;
480 +       if (!set_fileflags(fname, fileflags & ~iflags))
481 +               return -1;
482 +       return 1;
483 +}
484 +
485 +/* Undo a prior make_mutable() call that returned a 1. */
486 +int undo_make_mutable(const char *fname, uint32 fileflags)
487 +{
488 +       if (!set_fileflags(fname, fileflags))
489 +               return -1;
490 +       return 1;
491 +}
492 +#endif
493 +
494  int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
495                    const char *fnamecmp, int flags)
496  {
497 @@ -493,7 +527,7 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
498                 flags |= ATTRS_SKIP_MTIME;
499         if (!(flags & ATTRS_SKIP_MTIME)
500             && cmp_time(sxp->st.st_mtime, file->modtime) != 0) {
501 -               int ret = set_modtime(fname, file->modtime, F_MOD_NSEC(file), sxp->st.st_mode);
502 +               int ret = set_modtime(fname, file->modtime, F_MOD_NSEC(file), sxp->st.st_mode, ST_FLAGS(sxp->st));
503                 if (ret < 0) {
504                         rsyserr(FERROR_XFER, errno, "failed to set times on %s",
505                                 full_fname(fname));
506 @@ -529,7 +563,8 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
507                 if (am_root >= 0) {
508                         if (do_lchown(fname,
509                             change_uid ? (uid_t)F_OWNER(file) : sxp->st.st_uid,
510 -                           change_gid ? (gid_t)F_GROUP(file) : sxp->st.st_gid) != 0) {
511 +                           change_gid ? (gid_t)F_GROUP(file) : sxp->st.st_gid,
512 +                           sxp->st.st_mode, ST_FLAGS(sxp->st)) != 0) {
513                                 /* We shouldn't have attempted to change uid
514                                  * or gid unless have the privilege. */
515                                 rsyserr(FERROR_XFER, errno, "%s %s failed",
516 @@ -563,7 +598,7 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
517  
518  #ifdef HAVE_CHMOD
519         if (!BITS_EQUAL(sxp->st.st_mode, new_mode, CHMOD_BITS)) {
520 -               int ret = am_root < 0 ? 0 : do_chmod(fname, new_mode);
521 +               int ret = am_root < 0 ? 0 : do_chmod(fname, new_mode, ST_FLAGS(sxp->st));
522                 if (ret < 0) {
523                         rsyserr(FERROR_XFER, errno,
524                                 "failed to set permissions on %s",
525 @@ -575,6 +610,19 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
526         }
527  #endif
528  
529 +#ifdef SUPPORT_FILEFLAGS
530 +       if (preserve_fileflags && !S_ISLNK(sxp->st.st_mode)
531 +        && sxp->st.st_flags != F_FFLAGS(file)) {
532 +               uint32 fileflags = F_FFLAGS(file);
533 +               if (flags & ATTRS_DELAY_IMMUTABLE)
534 +                       fileflags &= ~ALL_IMMUTABLE;
535 +               if (sxp->st.st_flags != fileflags
536 +                && !set_fileflags(fname, fileflags))
537 +                       goto cleanup;
538 +               updated = 1;
539 +       }
540 +#endif
541 +
542         if (INFO_GTE(NAME, 2) && flags & ATTRS_REPORT) {
543                 if (updated)
544                         rprintf(FCLIENT, "%s\n", fname);
545 @@ -639,7 +687,8 @@ int finish_transfer(const char *fname, const char *fnametmp,
546  
547         /* Change permissions before putting the file into place. */
548         set_file_attrs(fnametmp, file, NULL, fnamecmp,
549 -                      ok_to_set_time ? 0 : ATTRS_SKIP_MTIME);
550 +                      ATTRS_DELAY_IMMUTABLE
551 +                      | (ok_to_set_time ? 0 : ATTRS_SKIP_MTIME));
552  
553         /* move tmp file over real file */
554         if (DEBUG_GTE(RECV, 1))
555 @@ -656,6 +705,10 @@ int finish_transfer(const char *fname, const char *fnametmp,
556         }
557         if (ret == 0) {
558                 /* The file was moved into place (not copied), so it's done. */
559 +#ifdef SUPPORT_FILEFLAGS
560 +               if (preserve_fileflags && F_FFLAGS(file) & ALL_IMMUTABLE)
561 +                       set_fileflags(fname, F_FFLAGS(file));
562 +#endif
563                 return 1;
564         }
565         /* The file was copied, so tweak the perms of the copied file.  If it
566 diff --git a/rsync.h b/rsync.h
567 --- a/rsync.h
568 +++ b/rsync.h
569 @@ -62,6 +62,7 @@
570  #define XMIT_HLINK_FIRST (1<<12)       /* protocols 30 - now (HLINKED files only) */
571  #define XMIT_IO_ERROR_ENDLIST (1<<12)  /* protocols 31*- now (w/XMIT_EXTENDED_FLAGS) (also protocol 30 w/'f' compat flag) */
572  #define XMIT_MOD_NSEC (1<<13)          /* protocols 31 - now */
573 +#define XMIT_SAME_FLAGS (1<<14)                /* protocols ?? - now */
574  
575  /* These flags are used in the live flist data. */
576  
577 @@ -165,6 +166,7 @@
578  
579  #define ATTRS_REPORT           (1<<0)
580  #define ATTRS_SKIP_MTIME       (1<<1)
581 +#define ATTRS_DELAY_IMMUTABLE  (1<<2)
582  
583  #define FULL_FLUSH     1
584  #define NORMAL_FLUSH   0
585 @@ -191,6 +193,7 @@
586  #define ITEM_REPORT_GROUP (1<<6)
587  #define ITEM_REPORT_ACL (1<<7)
588  #define ITEM_REPORT_XATTR (1<<8)
589 +#define ITEM_REPORT_FFLAGS (1<<9)
590  #define ITEM_BASIS_TYPE_FOLLOWS (1<<11)
591  #define ITEM_XNAME_FOLLOWS (1<<12)
592  #define ITEM_IS_NEW (1<<13)
593 @@ -513,6 +516,28 @@ typedef unsigned int size_t;
594  #endif
595  #endif
596  
597 +#define NO_FFLAGS ((uint32)-1)
598 +
599 +#ifdef HAVE_CHFLAGS
600 +#define SUPPORT_FILEFLAGS 1
601 +#define SUPPORT_FORCE_CHANGE 1
602 +#endif
603 +
604 +#if defined SUPPORT_FILEFLAGS || defined SUPPORT_FORCE_CHANGE
605 +#ifndef UF_NOUNLINK
606 +#define UF_NOUNLINK 0
607 +#endif
608 +#ifndef SF_NOUNLINK
609 +#define SF_NOUNLINK 0
610 +#endif
611 +#define USR_IMMUTABLE (UF_IMMUTABLE|UF_NOUNLINK|UF_APPEND)
612 +#define SYS_IMMUTABLE (SF_IMMUTABLE|SF_NOUNLINK|SF_APPEND)
613 +#define ALL_IMMUTABLE (USR_IMMUTABLE|SYS_IMMUTABLE)
614 +#define ST_FLAGS(st) (st.st_flags)
615 +#else
616 +#define ST_FLAGS(st) NO_FFLAGS
617 +#endif
618 +
619  /* Find a variable that is either exactly 32-bits or longer.
620   * If some code depends on 32-bit truncation, it will need to
621   * take special action in a "#if SIZEOF_INT32 > 4" section. */
622 @@ -683,6 +708,7 @@ extern int file_extra_cnt;
623  extern int inc_recurse;
624  extern int uid_ndx;
625  extern int gid_ndx;
626 +extern int fileflags_ndx;
627  extern int acls_ndx;
628  extern int xattrs_ndx;
629  
630 @@ -724,6 +750,11 @@ extern int xattrs_ndx;
631  /* When the associated option is on, all entries will have these present: */
632  #define F_OWNER(f) REQ_EXTRA(f, uid_ndx)->unum
633  #define F_GROUP(f) REQ_EXTRA(f, gid_ndx)->unum
634 +#if defined SUPPORT_FILEFLAGS || defined SUPPORT_FORCE_CHANGE
635 +#define F_FFLAGS(f) REQ_EXTRA(f, fileflags_ndx)->unum
636 +#else
637 +#define F_FFLAGS(f) NO_FFLAGS
638 +#endif
639  #define F_ACL(f) REQ_EXTRA(f, acls_ndx)->num
640  #define F_XATTR(f) REQ_EXTRA(f, xattrs_ndx)->num
641  #define F_NDX(f) REQ_EXTRA(f, unsort_ndx)->num
642 diff --git a/rsync.yo b/rsync.yo
643 --- a/rsync.yo
644 +++ b/rsync.yo
645 @@ -345,6 +345,7 @@ to the detailed description below for a complete description.  verb(
646   -K, --keep-dirlinks         treat symlinked dir on receiver as dir
647   -H, --hard-links            preserve hard links
648   -p, --perms                 preserve permissions
649 +     --fileflags             preserve file-flags (aka chflags)
650   -E, --executability         preserve executability
651       --chmod=CHMOD           affect file and/or directory permissions
652   -A, --acls                  preserve ACLs (implies -p)
653 @@ -379,7 +380,10 @@ to the detailed description below for a complete description.  verb(
654       --ignore-missing-args   ignore missing source args without error
655       --delete-missing-args   delete missing source args from destination
656       --ignore-errors         delete even if there are I/O errors
657 -     --force                 force deletion of dirs even if not empty
658 +     --force-delete          force deletion of dirs even if not empty
659 +     --force-change          affect user/system immutable files/dirs
660 +     --force-uchange         affect user-immutable files/dirs
661 +     --force-schange         affect system-immutable files/dirs
662       --max-delete=NUM        don't delete more than NUM files
663       --max-size=SIZE         don't transfer any file larger than SIZE
664       --min-size=SIZE         don't transfer any file smaller than SIZE
665 @@ -596,7 +600,8 @@ specified, in which case bf(-r) is not implied.
666  
667  Note that bf(-a) bf(does not preserve hardlinks), because
668  finding multiply-linked files is expensive.  You must separately
669 -specify bf(-H).
670 +specify bf(-H).  Note also that for backward compatibility, bf(-a)
671 +currently does bf(not) imply the bf(--fileflags) option.
672  
673  dit(--no-OPTION) You may turn off one or more implied options by prefixing
674  the option name with "no-".  Not all options may be prefixed with a "no-":
675 @@ -895,7 +900,7 @@ they would be using bf(--copy-links).
676  Without this option, if the sending side has replaced a directory with a
677  symlink to a directory, the receiving side will delete anything that is in
678  the way of the new symlink, including a directory hierarchy (as long as
679 -bf(--force) or bf(--delete) is in effect).
680 +bf(--force-delete) or bf(--delete) is in effect).
681  
682  See also bf(--keep-dirlinks) for an analogous option for the receiving
683  side.
684 @@ -1057,6 +1062,29 @@ Note that this option does not copy rsyncs special xattr values (e.g. those
685  used by bf(--fake-super)) unless you repeat the option (e.g. -XX).  This
686  "copy all xattrs" mode cannot be used with bf(--fake-super).
687  
688 +dit(bf(--fileflags)) This option causes rsync to update the file-flags to be
689 +the same as the source files and directories (if your OS supports the
690 +bf(chflags)(2) system call).   Some flags can only be altered by the super-user
691 +and some might only be unset below a certain secure-level (usually single-user
692 +mode). It will not make files alterable that are set to immutable on the
693 +receiver.  To do that, see bf(--force-change), bf(--force-uchange), and
694 +bf(--force-schange).
695 +
696 +dit(bf(--force-change)) This option causes rsync to disable both user-immutable
697 +and system-immutable flags on files and directories that are being updated or
698 +deleted on the receiving side.  This option overrides bf(--force-uchange) and
699 +bf(--force-schange).
700 +
701 +dit(bf(--force-uchange)) This option causes rsync to disable user-immutable
702 +flags on files and directories that are being updated or deleted on the
703 +receiving side.  It does not try to affect system flags.  This option overrides
704 +bf(--force-change) and bf(--force-schange).
705 +
706 +dit(bf(--force-schange)) This option causes rsync to disable system-immutable
707 +flags on files and directories that are being updated or deleted on the
708 +receiving side.  It does not try to affect user flags.  This option overrides
709 +bf(--force-change) and bf(--force-schange).
710 +
711  dit(bf(--chmod)) This option tells rsync to apply one or more
712  comma-separated "chmod" modes to the permission of the files in the
713  transfer.  The resulting value is treated as though it were the permissions
714 @@ -1355,12 +1383,13 @@ display as a "*missing" entry in the bf(--list-only) output.
715  dit(bf(--ignore-errors)) Tells bf(--delete) to go ahead and delete files
716  even when there are I/O errors.
717  
718 -dit(bf(--force)) This option tells rsync to delete a non-empty directory
719 +dit(bf(--force-delete)) This option tells rsync to delete a non-empty directory
720  when it is to be replaced by a non-directory.  This is only relevant if
721  deletions are not active (see bf(--delete) for details).
722  
723 -Note for older rsync versions: bf(--force) used to still be required when
724 -using bf(--delete-after), and it used to be non-functional unless the
725 +This option can be abbreviated bf(--force) for backward compatibility.
726 +Note that some older rsync versions used to still require bf(--force)
727 +when using bf(--delete-after), and it used to be non-functional unless the
728  bf(--recursive) option was also enabled.
729  
730  dit(bf(--max-delete=NUM)) This tells rsync not to delete more than NUM
731 @@ -1954,7 +1983,7 @@ with older versions of rsync, but that also turns on the output of other
732  verbose messages).
733  
734  The "%i" escape has a cryptic output that is 11 letters long.  The general
735 -format is like the string bf(YXcstpoguax), where bf(Y) is replaced by the
736 +format is like the string bf(YXcstpogfax), where bf(Y) is replaced by the
737  type of update being done, bf(X) is replaced by the file-type, and the
738  other letters represent attributes that may be output if they are being
739  modified.
740 @@ -2010,7 +2039,7 @@ quote(itemization(
741    sender's value (requires bf(--owner) and super-user privileges).
742    it() A bf(g) means the group is different and is being updated to the
743    sender's value (requires bf(--group) and the authority to set the group).
744 -  it() The bf(u) slot is reserved for future use.
745 +  it() The bf(f) means that the fileflags information changed.
746    it() The bf(a) means that the ACL information changed.
747    it() The bf(x) means that the extended attribute information changed.
748  ))
749 diff --git a/syscall.c b/syscall.c
750 --- a/syscall.c
751 +++ b/syscall.c
752 @@ -34,6 +34,7 @@ extern int am_root;
753  extern int am_sender;
754  extern int read_only;
755  extern int list_only;
756 +extern int force_change;
757  extern int preserve_perms;
758  extern int preserve_executability;
759  
760 @@ -51,7 +52,23 @@ int do_unlink(const char *fname)
761  {
762         if (dry_run) return 0;
763         RETURN_ERROR_IF_RO_OR_LO;
764 -       return unlink(fname);
765 +       if (unlink(fname) == 0)
766 +               return 0;
767 +#ifdef SUPPORT_FORCE_CHANGE
768 +       if (force_change && errno == EPERM) {
769 +               STRUCT_STAT st;
770 +
771 +               if (x_lstat(fname, &st, NULL) == 0
772 +                && make_mutable(fname, st.st_mode, st.st_flags, force_change) > 0) {
773 +                       if (unlink(fname) == 0)
774 +                               return 0;
775 +                       undo_make_mutable(fname, st.st_flags);
776 +               }
777 +               /* TODO: handle immutable directories */
778 +               errno = EPERM;
779 +       }
780 +#endif
781 +       return -1;
782  }
783  
784  #ifdef SUPPORT_LINKS
785 @@ -112,14 +129,37 @@ int do_link(const char *fname1, const char *fname2)
786  }
787  #endif
788  
789 -int do_lchown(const char *path, uid_t owner, gid_t group)
790 +int do_lchown(const char *path, uid_t owner, gid_t group, mode_t mode, uint32 fileflags)
791  {
792         if (dry_run) return 0;
793         RETURN_ERROR_IF_RO_OR_LO;
794  #ifndef HAVE_LCHOWN
795  #define lchown chown
796  #endif
797 -       return lchown(path, owner, group);
798 +       if (lchown(path, owner, group) == 0)
799 +               return 0;
800 +#ifdef SUPPORT_FORCE_CHANGE
801 +       if (force_change && errno == EPERM) {
802 +               if (fileflags == NO_FFLAGS) {
803 +                       STRUCT_STAT st;
804 +                       if (x_lstat(path, &st, NULL) == 0) {
805 +                               mode = st.st_mode;
806 +                               fileflags = st.st_flags;
807 +                       }
808 +               }
809 +               if (fileflags != NO_FFLAGS
810 +                && make_mutable(path, mode, fileflags, force_change) > 0) {
811 +                       int ret = lchown(path, owner, group);
812 +                       undo_make_mutable(path, fileflags);
813 +                       if (ret == 0)
814 +                               return 0;
815 +               }
816 +               errno = EPERM;
817 +       }
818 +#else
819 +       mode = fileflags = 0; /* avoid compiler warning */
820 +#endif
821 +       return -1;
822  }
823  
824  int do_mknod(const char *pathname, mode_t mode, dev_t dev)
825 @@ -159,7 +199,7 @@ int do_mknod(const char *pathname, mode_t mode, dev_t dev)
826                         return -1;
827                 close(sock);
828  #ifdef HAVE_CHMOD
829 -               return do_chmod(pathname, mode);
830 +               return do_chmod(pathname, mode, 0);
831  #else
832                 return 0;
833  #endif
834 @@ -176,7 +216,22 @@ int do_rmdir(const char *pathname)
835  {
836         if (dry_run) return 0;
837         RETURN_ERROR_IF_RO_OR_LO;
838 -       return rmdir(pathname);
839 +       if (rmdir(pathname) == 0)
840 +               return 0;
841 +#ifdef SUPPORT_FORCE_CHANGE
842 +       if (force_change && errno == EPERM) {
843 +               STRUCT_STAT st;
844 +
845 +               if (x_lstat(pathname, &st, NULL) == 0
846 +                && make_mutable(pathname, st.st_mode, st.st_flags, force_change) > 0) {
847 +                       if (rmdir(pathname) == 0)
848 +                               return 0;
849 +                       undo_make_mutable(pathname, st.st_flags);
850 +               }
851 +               errno = EPERM;
852 +       }
853 +#endif
854 +       return -1;
855  }
856  
857  int do_open(const char *pathname, int flags, mode_t mode)
858 @@ -190,7 +245,7 @@ int do_open(const char *pathname, int flags, mode_t mode)
859  }
860  
861  #ifdef HAVE_CHMOD
862 -int do_chmod(const char *path, mode_t mode)
863 +int do_chmod(const char *path, mode_t mode, uint32 fileflags)
864  {
865         int code;
866         if (dry_run) return 0;
867 @@ -213,17 +268,74 @@ int do_chmod(const char *path, mode_t mode)
868         } else
869                 code = chmod(path, mode & CHMOD_BITS); /* DISCOURAGED FUNCTION */
870  #endif /* !HAVE_LCHMOD */
871 +#ifdef SUPPORT_FORCE_CHANGE
872 +       if (code < 0 && force_change && errno == EPERM && !S_ISLNK(mode)) {
873 +               if (fileflags == NO_FFLAGS) {
874 +                       STRUCT_STAT st;
875 +                       if (x_lstat(path, &st, NULL) == 0)
876 +                               fileflags = st.st_flags;
877 +               }
878 +               if (fileflags != NO_FFLAGS
879 +                && make_mutable(path, mode, fileflags, force_change) > 0) {
880 +                       code = chmod(path, mode & CHMOD_BITS);
881 +                       undo_make_mutable(path, fileflags);
882 +                       if (code == 0)
883 +                               return 0;
884 +               }
885 +               errno = EPERM;
886 +       }
887 +#else
888 +       fileflags = 0; /* avoid compiler warning */
889 +#endif
890         if (code != 0 && (preserve_perms || preserve_executability))
891                 return code;
892         return 0;
893  }
894  #endif
895  
896 +#ifdef HAVE_CHFLAGS
897 +int do_chflags(const char *path, uint32 fileflags)
898 +{
899 +       if (dry_run) return 0;
900 +       RETURN_ERROR_IF_RO_OR_LO;
901 +       return chflags(path, fileflags);
902 +}
903 +#endif
904 +
905  int do_rename(const char *fname1, const char *fname2)
906  {
907         if (dry_run) return 0;
908         RETURN_ERROR_IF_RO_OR_LO;
909 -       return rename(fname1, fname2);
910 +       if (rename(fname1, fname2) == 0)
911 +               return 0;
912 +#ifdef SUPPORT_FORCE_CHANGE
913 +       if (force_change && errno == EPERM) {
914 +               STRUCT_STAT st1, st2;
915 +               int became_mutable;
916 +
917 +               if (x_lstat(fname1, &st1, NULL) != 0)
918 +                       goto failed;
919 +               became_mutable = make_mutable(fname1, st1.st_mode, st1.st_flags, force_change) > 0;
920 +               if (became_mutable && rename(fname1, fname2) == 0)
921 +                       goto success;
922 +               if (x_lstat(fname2, &st2, NULL) == 0
923 +                && make_mutable(fname2, st2.st_mode, st2.st_flags, force_change) > 0) {
924 +                       if (rename(fname1, fname2) == 0) {
925 +                         success:
926 +                               if (became_mutable) /* Yes, use fname2 and st1! */
927 +                                       undo_make_mutable(fname2, st1.st_flags);
928 +                               return 0;
929 +                       }
930 +                       undo_make_mutable(fname2, st2.st_flags);
931 +               }
932 +               /* TODO: handle immutable directories */
933 +               if (became_mutable)
934 +                       undo_make_mutable(fname1, st1.st_flags);
935 +         failed:
936 +               errno = EPERM;
937 +       }
938 +#endif
939 +       return -1;
940  }
941  
942  #ifdef HAVE_FTRUNCATE
943 diff --git a/t_stub.c b/t_stub.c
944 --- a/t_stub.c
945 +++ b/t_stub.c
946 @@ -25,6 +25,7 @@ int modify_window = 0;
947  int module_id = -1;
948  int relative_paths = 0;
949  int module_dirlen = 0;
950 +int force_change = 0;
951  int preserve_times = 0;
952  int preserve_xattrs = 0;
953  char number_separator = ',';
954 @@ -84,3 +85,23 @@ filter_rule_list daemon_filter_list;
955  {
956         return "tester";
957  }
958 +
959 +#if defined SUPPORT_FILEFLAGS || defined SUPPORT_FORCE_CHANGE
960 + int make_mutable(UNUSED(const char *fname), UNUSED(mode_t mode), UNUSED(uint32 fileflags), UNUSED(uint32 iflags))
961 +{
962 +       return 0;
963 +}
964 +
965 +/* Undo a prior make_mutable() call that returned a 1. */
966 + int undo_make_mutable(UNUSED(const char *fname), UNUSED(uint32 fileflags))
967 +{
968 +       return 0;
969 +}
970 +#endif
971 +
972 +#ifdef SUPPORT_XATTRS
973 + int x_lstat(UNUSED(const char *fname), UNUSED(STRUCT_STAT *fst), UNUSED(STRUCT_STAT *xst))
974 +{
975 +       return -1;
976 +}
977 +#endif
978 diff --git a/util.c b/util.c
979 --- a/util.c
980 +++ b/util.c
981 @@ -30,6 +30,7 @@ extern int modify_window;
982  extern int relative_paths;
983  extern int preserve_times;
984  extern int preserve_xattrs;
985 +extern int force_change;
986  extern char *module_dir;
987  extern unsigned int module_dirlen;
988  extern char *partial_dir;
989 @@ -122,9 +123,34 @@ NORETURN void overflow_exit(const char *str)
990         exit_cleanup(RERR_MALLOC);
991  }
992  
993 +#ifdef SUPPORT_FORCE_CHANGE
994 +static int try_a_force_change(const char *fname, time_t modtime, uint32 mod_nsec, mode_t mode, uint32 fileflags)
995 +{
996 +       if (fileflags == NO_FFLAGS) {
997 +               STRUCT_STAT st;
998 +               if (x_lstat(fname, &st, NULL) == 0)
999 +                       fileflags = st.st_flags;
1000 +       }
1001 +
1002 +       if (fileflags != NO_FFLAGS && make_mutable(fname, mode, fileflags, force_change) > 0) {
1003 +               int save_force_change = force_change;
1004 +
1005 +               force_change = 0; /* Make certain we can't come back here. */
1006 +               ret = set_modtime(fname, modtime, mod_nsec, mode, fileflags);
1007 +               force_change = save_force_change;
1008 +
1009 +               undo_make_mutable(fname, fileflags);
1010 +       }
1011 +
1012 +       errno = EPERM;
1013 +
1014 +       return -1;
1015 +}
1016 +#endif
1017 +
1018  /* This returns 0 for success, 1 for a symlink if symlink time-setting
1019   * is not possible, or -1 for any other error. */
1020 -int set_modtime(const char *fname, time_t modtime, uint32 mod_nsec, mode_t mode)
1021 +int set_modtime(const char *fname, time_t modtime, uint32 mod_nsec, mode_t mode, uint32 fileflags)
1022  {
1023         static int switch_step = 0;
1024  
1025 @@ -139,6 +165,11 @@ int set_modtime(const char *fname, time_t modtime, uint32 mod_nsec, mode_t mode)
1026  #include "case_N.h"
1027                 if (do_utimensat(fname, modtime, mod_nsec) == 0)
1028                         break;
1029 +#ifdef SUPPORT_FORCE_CHANGE
1030 +               if (force_change && errno == EPERM
1031 +                && try_a_force_change(fname, modtime, mod_nsec, mode, fileflags) == 0)
1032 +                       break;
1033 +#endif
1034                 if (errno != ENOSYS)
1035                         return -1;
1036                 switch_step++;
1037 @@ -149,6 +180,11 @@ int set_modtime(const char *fname, time_t modtime, uint32 mod_nsec, mode_t mode)
1038  #include "case_N.h"
1039                 if (do_lutimes(fname, modtime, mod_nsec) == 0)
1040                         break;
1041 +#ifdef SUPPORT_FORCE_CHANGE
1042 +               if (force_change && errno == EPERM
1043 +                && try_a_force_change(fname, modtime, mod_nsec, mode, fileflags) == 0)
1044 +                       break;
1045 +#endif
1046                 if (errno != ENOSYS)
1047                         return -1;
1048                 switch_step++;
1049 @@ -172,6 +208,13 @@ int set_modtime(const char *fname, time_t modtime, uint32 mod_nsec, mode_t mode)
1050                 if (do_utime(fname, modtime, mod_nsec) == 0)
1051                         break;
1052  #endif
1053 +#ifdef SUPPORT_FORCE_CHANGE
1054 +               if (force_change && errno == EPERM
1055 +                && try_a_force_change(fname, modtime, mod_nsec, mode, fileflags) == 0)
1056 +                       break;
1057 +#else
1058 +               fileflags = 0; /* avoid compiler warning */
1059 +#endif
1060  
1061                 return -1;
1062         }
1063 diff --git a/xattrs.c b/xattrs.c
1064 --- a/xattrs.c
1065 +++ b/xattrs.c
1066 @@ -1042,7 +1042,7 @@ int set_stat_xattr(const char *fname, struct file_struct *file, mode_t new_mode)
1067         mode = (fst.st_mode & _S_IFMT) | (fmode & ACCESSPERMS)
1068              | (S_ISDIR(fst.st_mode) ? 0700 : 0600);
1069         if (fst.st_mode != mode)
1070 -               do_chmod(fname, mode);
1071 +               do_chmod(fname, mode, ST_FLAGS(fst));
1072         if (!IS_DEVICE(fst.st_mode))
1073                 fst.st_rdev = 0; /* just in case */
1074