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