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