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