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