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