eead498d6774ab96e78a354b1ee62c70843aacf0
[rsync-patches.git] / fileflags.diff
1 This patch provides --fileflags, which preserves the st_flags stat() field.
2 Modified from a patch that was written by Rolf Grossmann.
3
4 To use this patch, run these commands for a successful build:
5
6     patch -p1 <patches/fileflags.diff
7     ./prepare-source
8     ./configure
9     make
10
11 diff --git a/Makefile.in b/Makefile.in
12 --- a/Makefile.in
13 +++ b/Makefile.in
14 @@ -42,7 +42,7 @@ popt_OBJS=popt/findme.o  popt/popt.o  popt/poptconfig.o \
15         popt/popthelp.o popt/poptparse.o
16  OBJS=$(OBJS1) $(OBJS2) $(OBJS3) $(DAEMON_OBJ) $(LIBOBJ) $(ZLIBOBJ) @BUILD_POPT@
17  
18 -TLS_OBJ = tls.o syscall.o lib/compat.o lib/snprintf.o lib/permstring.o lib/sysxattrs.o @BUILD_POPT@
19 +TLS_OBJ = tls.o syscall.o t_stub.o lib/compat.o lib/snprintf.o lib/permstring.o lib/sysxattrs.o @BUILD_POPT@
20  
21  # Programs we must have to run the test cases
22  CHECK_PROGS = rsync$(EXEEXT) tls$(EXEEXT) getgroups$(EXEEXT) getfsdev$(EXEEXT) \
23 @@ -105,7 +105,7 @@ getgroups$(EXEEXT): getgroups.o
24  getfsdev$(EXEEXT): getfsdev.o
25         $(CC) $(CFLAGS) $(LDFLAGS) -o $@ getfsdev.o $(LIBS)
26  
27 -TRIMSLASH_OBJ = trimslash.o syscall.o lib/compat.o lib/snprintf.o
28 +TRIMSLASH_OBJ = trimslash.o syscall.o t_stub.o lib/compat.o lib/snprintf.o
29  trimslash$(EXEEXT): $(TRIMSLASH_OBJ)
30         $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(TRIMSLASH_OBJ) $(LIBS)
31  
32 diff --git a/compat.c b/compat.c
33 --- a/compat.c
34 +++ b/compat.c
35 @@ -41,9 +41,11 @@ extern int checksum_seed;
36  extern int basis_dir_cnt;
37  extern int prune_empty_dirs;
38  extern int protocol_version;
39 +extern int force_change;
40  extern int protect_args;
41  extern int preserve_uid;
42  extern int preserve_gid;
43 +extern int preserve_fileflags;
44  extern int preserve_acls;
45  extern int preserve_xattrs;
46  extern int need_messages_from_generator;
47 @@ -60,7 +62,7 @@ extern iconv_t ic_send, ic_recv;
48  #endif
49  
50  /* These index values are for the file-list's extra-attribute array. */
51 -int uid_ndx, gid_ndx, acls_ndx, xattrs_ndx, unsort_ndx;
52 +int uid_ndx, gid_ndx, fileflags_ndx, acls_ndx, xattrs_ndx, unsort_ndx;
53  
54  int receiver_symlink_times = 0; /* receiver can set the time on a symlink */
55  
56 @@ -134,6 +136,8 @@ void setup_protocol(int f_out,int f_in)
57                 uid_ndx = ++file_extra_cnt;
58         if (preserve_gid)
59                 gid_ndx = ++file_extra_cnt;
60 +       if (preserve_fileflags || (force_change && !am_sender))
61 +               fileflags_ndx = ++file_extra_cnt;
62         if (preserve_acls && !am_sender)
63                 acls_ndx = ++file_extra_cnt;
64         if (preserve_xattrs)
65 diff --git a/configure.in b/configure.in
66 --- a/configure.in
67 +++ b/configure.in
68 @@ -551,7 +551,7 @@ AC_CHECK_FUNCS(waitpid wait4 getcwd strdup chown chmod lchmod mknod mkfifo \
69      memmove lchown vsnprintf snprintf vasprintf asprintf setsid glob strpbrk \
70      strlcat strlcpy strtol mallinfo getgroups setgroups geteuid getegid \
71      setlocale setmode open64 lseek64 mkstemp64 mtrace va_copy __va_copy \
72 -    strerror putenv iconv_open locale_charset nl_langinfo getxattr \
73 +    chflags strerror putenv iconv_open locale_charset nl_langinfo getxattr \
74      extattr_get_link sigaction sigprocmask setattrlist)
75  
76  dnl cygwin iconv.h defines iconv_open as libiconv_open
77 diff --git a/flist.c b/flist.c
78 --- a/flist.c
79 +++ b/flist.c
80 @@ -52,6 +52,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 uid_ndx;
86  extern int gid_ndx;
87  extern int eol_nulls;
88 @@ -346,6 +347,9 @@ static void send_file_entry(int f, struct file_struct *file, int ndx, int first_
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 @@ -405,6 +409,14 @@ static void send_file_entry(int f, struct file_struct *file, int ndx, int first_
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          || (preserve_specials && IS_SPECIAL(mode))) {
113 @@ -524,6 +536,10 @@ static void send_file_entry(int f, struct file_struct *file, int ndx, int first_
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 @@ -612,6 +628,9 @@ static struct file_struct *recv_file_entry(struct file_list *flist,
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 @@ -746,6 +765,10 @@ static struct file_struct *recv_file_entry(struct file_list *flist,
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 @@ -867,6 +890,10 @@ static struct file_struct *recv_file_entry(struct file_list *flist,
146                 OPT_EXTRA(file, 0)->unum = (uint32)(file_length >> 32);
147         }
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 @@ -1201,6 +1228,10 @@ struct file_struct *make_file(const char *fname, struct file_list *flist,
157                 OPT_EXTRA(file, 0)->unum = (uint32)(st.st_size >> 32);
158         }
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 (uid_ndx) /* Check uid_ndx instead of preserve_uid for del support */
165                 F_OWNER(file) = st.st_uid;
166         if (gid_ndx) /* Check gid_ndx instead of preserve_gid for del support */
167 diff --git a/generator.c b/generator.c
168 --- a/generator.c
169 +++ b/generator.c
170 @@ -43,8 +43,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 uid_ndx;
179  extern int gid_ndx;
180  extern int delete_mode;
181 @@ -162,7 +164,7 @@ 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                 int save_uid_ndx = uid_ndx;
190 @@ -170,6 +172,13 @@ static enum delret delete_item(char *fbuf, uint16 mode, uint16 flags)
191                  * delete_dir_contents() always calls us w/DEL_DIR_IS_EMPTY. */
192                 if (!uid_ndx)
193                         uid_ndx = ++file_extra_cnt;
194 +#ifdef SUPPORT_FORCE_CHANGE
195 +               if (force_change) {
196 +                       STRUCT_STAT st;
197 +                       if (x_lstat(fbuf, &st, NULL) == 0)
198 +                               make_mutable(fbuf, st.st_mode, st.st_flags, force_change);
199 +               }
200 +#endif
201                 ignore_perishable = 1;
202                 /* If DEL_RECURSE is not set, this just reports emptiness. */
203                 ret = delete_dir_contents(fbuf, flags);
204 @@ -290,8 +299,12 @@ static enum delret delete_dir_contents(char *fname, uint16 flags)
205                 }
206  
207                 strlcpy(p, fp->basename, remainder);
208 +#ifdef SUPPORT_FORCE_CHANGE
209 +               if (force_change)
210 +                       make_mutable(fname, fp->mode, F_FFLAGS(fp), force_change);
211 +#endif
212                 if (!(fp->mode & S_IWUSR) && !am_root && (uid_t)F_OWNER(fp) == our_uid)
213 -                       do_chmod(fname, fp->mode | S_IWUSR);
214 +                       do_chmod(fname, fp->mode | S_IWUSR, NO_FFLAGS);
215                 /* Save stack by recursing to ourself directly. */
216                 if (S_ISDIR(fp->mode)) {
217                         if (delete_dir_contents(fname, flags | DEL_RECURSE) != DR_SUCCESS)
218 @@ -585,6 +598,11 @@ int unchanged_attrs(const char *fname, struct file_struct *file, stat_x *sxp)
219         if (preserve_perms && !BITS_EQUAL(sxp->st.st_mode, file->mode, CHMOD_BITS))
220                 return 0;
221  
222 +#ifdef SUPPORT_FILEFLAGS
223 +       if (preserve_fileflags && !S_ISLNK(file->mode) && sxp->st.st_flags != F_FFLAGS(file))
224 +               return 0;
225 +#endif
226 +
227         if (preserve_executability && ((sxp->st.st_mode & 0111 ? 1 : 0) ^ (file->mode & 0111 ? 1 : 0)))
228                 return 0;
229  
230 @@ -646,6 +664,11 @@ void itemize(const char *fnamecmp, struct file_struct *file, int ndx, int statre
231                 if (gid_ndx && !(file->flags & FLAG_SKIP_GROUP)
232                     && sxp->st.st_gid != (gid_t)F_GROUP(file))
233                         iflags |= ITEM_REPORT_GROUP;
234 +#ifdef SUPPORT_FILEFLAGS
235 +               if (preserve_fileflags && !S_ISLNK(file->mode)
236 +                && sxp->st.st_flags != F_FFLAGS(file))
237 +                       iflags |= ITEM_REPORT_FFLAGS;
238 +#endif
239  #ifdef SUPPORT_ACLS
240                 if (preserve_acls && !S_ISLNK(file->mode)) {
241                         if (!ACL_READY(*sxp))
242 @@ -1391,6 +1414,10 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
243                         file->mode = dest_mode(file->mode, sx.st.st_mode,
244                                                dflt_perms, statret == 0);
245                 }
246 +#ifdef SUPPORT_FORCE_CHANGE
247 +               if (force_change && !preserve_fileflags)
248 +                       F_FFLAGS(file) = sx.st.st_flags;
249 +#endif
250                 if (statret != 0 && basis_dir[0] != NULL) {
251                         int j = try_dests_non(file, fname, ndx, fnamecmpbuf, &sx,
252                                               itemizing, code);
253 @@ -1427,10 +1454,15 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
254                 /* We need to ensure that the dirs in the transfer have writable
255                  * permissions during the time we are putting files within them.
256                  * This is then fixed after the transfer is done. */
257 +#ifdef SUPPORT_FORCE_CHANGE
258 +               if (force_change && F_FFLAGS(file) & force_change
259 +                && make_mutable(fname, file->mode, F_FFLAGS(file), force_change))
260 +                       need_retouch_dir_perms = 1;
261 +#endif
262  #ifdef HAVE_CHMOD
263                 if (!am_root && !(file->mode & S_IWUSR) && dir_tweaking) {
264                         mode_t mode = file->mode | S_IWUSR;
265 -                       if (do_chmod(fname, mode) < 0) {
266 +                       if (do_chmod(fname, mode, 0) < 0) {
267                                 rsyserr(FERROR_XFER, errno,
268                                         "failed to modify permissions on %s",
269                                         full_fname(fname));
270 @@ -1461,6 +1493,10 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
271                 file->mode = dest_mode(file->mode, sx.st.st_mode, dflt_perms,
272                                        exists);
273         }
274 +#ifdef SUPPORT_FORCE_CHANGE
275 +       if (force_change && !preserve_fileflags)
276 +               F_FFLAGS(file) = sx.st.st_flags;
277 +#endif
278  
279  #ifdef SUPPORT_HARD_LINKS
280         if (preserve_hard_links && F_HLINK_NOT_FIRST(file)
281 @@ -1948,9 +1984,13 @@ static void touch_up_dirs(struct file_list *flist, int ndx)
282                         continue;
283                 fname = f_name(file, NULL);
284                 if (!(file->mode & S_IWUSR))
285 -                       do_chmod(fname, file->mode);
286 +                       do_chmod(fname, file->mode, 0);
287                 if (need_retouch_dir_times)
288 -                       set_modtime(fname, file->modtime, file->mode);
289 +                       set_modtime(fname, file->modtime, file->mode, 0);
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 (allowed_lull && !(counter % lull_mod))
295                         maybe_send_keepalive();
296                 else if (!(counter & 0xFF))
297 diff --git a/log.c b/log.c
298 --- a/log.c
299 +++ b/log.c
300 @@ -644,7 +644,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 @@ -52,6 +52,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 allowed_lull = 0;
327  int prune_empty_dirs = 0;
328 @@ -224,6 +226,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  #if defined HAVE_LUTIMES && defined HAVE_UTIMES
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 @@ -477,6 +491,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 @@ -551,6 +569,13 @@ static struct poptOption long_options[] = {
393    {"remove-sent-files",0,  POPT_ARG_VAL,    &remove_source_files, 2, 0, 0 }, /* deprecated */
394    {"remove-source-files",0,POPT_ARG_VAL,    &remove_source_files, 1, 0, 0 },
395    {"force",            0,  POPT_ARG_NONE,   &force_delete, 0, 0, 0 },
396 +  {"force-delete",     0,  POPT_ARG_NONE,   &force_delete, 0, 0, 0 },
397 +#ifdef SUPPORT_FORCE_CHANGE
398 +  {"force-change",     0,  POPT_ARG_VAL,    &force_change, ALL_IMMUTABLE, 0, 0 },
399 +  {"no-force-change",  0,  POPT_ARG_VAL,    &force_change, 0, 0, 0 },
400 +  {"force-uchange",    0,  POPT_ARG_VAL,    &force_change, USR_IMMUTABLE, 0, 0 },
401 +  {"force-schange",    0,  POPT_ARG_VAL,    &force_change, SYS_IMMUTABLE, 0, 0 },
402 +#endif
403    {"ignore-errors",    0,  POPT_ARG_NONE,   &ignore_errors, 0, 0, 0 },
404    {"max-delete",       0,  POPT_ARG_INT,    &max_delete, 0, 0, 0 },
405    {0,                 'F', POPT_ARG_NONE,   0, 'F', 0, 0 },
406 @@ -1826,6 +1851,9 @@ void server_options(char **args, int *argc_p)
407         if (xfer_dirs && !recurse && delete_mode && am_sender)
408                 args[ac++] = "--no-r";
409  
410 +       if (preserve_fileflags)
411 +               args[ac++] = "--fileflags";
412 +
413         if (do_compression && def_compress_level != Z_DEFAULT_COMPRESSION) {
414                 if (asprintf(&arg, "--compress-level=%d", def_compress_level) < 0)
415                         goto oom;
416 @@ -1913,6 +1941,16 @@ void server_options(char **args, int *argc_p)
417                         args[ac++] = "--delete-excluded";
418                 if (force_delete)
419                         args[ac++] = "--force";
420 +#ifdef SUPPORT_FORCE_CHANGE
421 +               if (force_change) {
422 +                       if (force_change == ALL_IMMUTABLE)
423 +                               args[ac++] = "--force-change";
424 +                       else if (force_change == USR_IMMUTABLE)
425 +                               args[ac++] = "--force-uchange";
426 +                       else if (force_change == SYS_IMMUTABLE)
427 +                               args[ac++] = "--force-schange";
428 +               }
429 +#endif
430                 if (write_batch < 0)
431                         args[ac++] = "--only-write-batch=X";
432                 if (am_root > 1)
433 diff --git a/rsync.c b/rsync.c
434 --- a/rsync.c
435 +++ b/rsync.c
436 @@ -32,6 +32,7 @@ extern int dry_run;
437  extern int preserve_acls;
438  extern int preserve_xattrs;
439  extern int preserve_perms;
440 +extern int preserve_fileflags;
441  extern int preserve_executability;
442  extern int preserve_times;
443  extern int am_root;
444 @@ -339,6 +340,39 @@ mode_t dest_mode(mode_t flist_mode, mode_t stat_mode, int dflt_perms,
445         return new_mode;
446  }
447  
448 +#if defined SUPPORT_FILEFLAGS || defined SUPPORT_FORCE_CHANGE
449 +/* Set a file's st_flags. */
450 +static int set_fileflags(const char *fname, uint32 fileflags)
451 +{
452 +       if (do_chflags(fname, fileflags) != 0) {
453 +               rsyserr(FERROR_XFER, errno,
454 +                       "failed to set file flags on %s",
455 +                       full_fname(fname));
456 +               return 0;
457 +       }
458 +
459 +       return 1;
460 +}
461 +
462 +/* Remove immutable flags from an object, so it can be altered/removed. */
463 +int make_mutable(const char *fname, mode_t mode, uint32 fileflags, uint32 iflags)
464 +{
465 +       if (S_ISLNK(mode) || !(fileflags & iflags))
466 +               return 0;
467 +       if (!set_fileflags(fname, fileflags & ~iflags))
468 +               return -1;
469 +       return 1;
470 +}
471 +
472 +/* Undo a prior make_mutable() call that returned a 1. */
473 +int undo_make_mutable(const char *fname, uint32 fileflags)
474 +{
475 +       if (!set_fileflags(fname, fileflags))
476 +               return -1;
477 +       return 1;
478 +}
479 +#endif
480 +
481  int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
482                    const char *fnamecmp, int flags)
483  {
484 @@ -392,7 +426,7 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
485                 flags |= ATTRS_SKIP_MTIME;
486         if (!(flags & ATTRS_SKIP_MTIME)
487             && cmp_time(sxp->st.st_mtime, file->modtime) != 0) {
488 -               int ret = set_modtime(fname, file->modtime, sxp->st.st_mode);
489 +               int ret = set_modtime(fname, file->modtime, sxp->st.st_mode, ST_FLAGS(sxp->st));
490                 if (ret < 0) {
491                         rsyserr(FERROR_XFER, errno, "failed to set times on %s",
492                                 full_fname(fname));
493 @@ -428,7 +462,8 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
494                 if (am_root >= 0) {
495                         if (do_lchown(fname,
496                             change_uid ? (uid_t)F_OWNER(file) : sxp->st.st_uid,
497 -                           change_gid ? (gid_t)F_GROUP(file) : sxp->st.st_gid) != 0) {
498 +                           change_gid ? (gid_t)F_GROUP(file) : sxp->st.st_gid,
499 +                           sxp->st.st_mode, ST_FLAGS(sxp->st)) != 0) {
500                                 /* We shouldn't have attempted to change uid
501                                  * or gid unless have the privilege. */
502                                 rsyserr(FERROR_XFER, errno, "%s %s failed",
503 @@ -460,7 +495,7 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
504  
505  #ifdef HAVE_CHMOD
506         if (!BITS_EQUAL(sxp->st.st_mode, new_mode, CHMOD_BITS)) {
507 -               int ret = am_root < 0 ? 0 : do_chmod(fname, new_mode);
508 +               int ret = am_root < 0 ? 0 : do_chmod(fname, new_mode, ST_FLAGS(sxp->st));
509                 if (ret < 0) {
510                         rsyserr(FERROR_XFER, errno,
511                                 "failed to set permissions on %s",
512 @@ -472,6 +507,19 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
513         }
514  #endif
515  
516 +#ifdef SUPPORT_FILEFLAGS
517 +       if (preserve_fileflags && !S_ISLNK(sxp->st.st_mode)
518 +        && sxp->st.st_flags != F_FFLAGS(file)) {
519 +               uint32 fileflags = F_FFLAGS(file);
520 +               if (flags & ATTRS_DELAY_IMMUTABLE)
521 +                       fileflags &= ~ALL_IMMUTABLE;
522 +               if (sxp->st.st_flags != fileflags
523 +                && !set_fileflags(fname, fileflags))
524 +                       goto cleanup;
525 +               updated = 1;
526 +       }
527 +#endif
528 +
529         if (verbose > 1 && flags & ATTRS_REPORT) {
530                 if (updated)
531                         rprintf(FCLIENT, "%s\n", fname);
532 @@ -531,7 +579,8 @@ int finish_transfer(const char *fname, const char *fnametmp,
533  
534         /* Change permissions before putting the file into place. */
535         set_file_attrs(fnametmp, file, NULL, fnamecmp,
536 -                      ok_to_set_time ? 0 : ATTRS_SKIP_MTIME);
537 +                      ATTRS_DELAY_IMMUTABLE
538 +                      | (ok_to_set_time ? 0 : ATTRS_SKIP_MTIME));
539  
540         /* move tmp file over real file */
541         if (verbose > 2)
542 @@ -550,6 +599,10 @@ int finish_transfer(const char *fname, const char *fnametmp,
543         }
544         if (ret == 0) {
545                 /* The file was moved into place (not copied), so it's done. */
546 +#ifdef SUPPORT_FILEFLAGS
547 +               if (preserve_fileflags && F_FFLAGS(file) & ALL_IMMUTABLE)
548 +                       set_fileflags(fname, F_FFLAGS(file));
549 +#endif
550                 return 1;
551         }
552         /* The file was copied, so tweak the perms of the copied file.  If it
553 diff --git a/rsync.h b/rsync.h
554 --- a/rsync.h
555 +++ b/rsync.h
556 @@ -60,6 +60,7 @@
557  #define XMIT_RDEV_MINOR_8_pre30 (1<<11)        /* protocols 28 - 29  */
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_SAME_FLAGS (1<<14)                /* protocols ?? - now */
561  
562  /* These flags are used in the live flist data. */
563  
564 @@ -149,6 +150,7 @@
565  
566  #define ATTRS_REPORT           (1<<0)
567  #define ATTRS_SKIP_MTIME       (1<<1)
568 +#define ATTRS_DELAY_IMMUTABLE  (1<<2)
569  
570  #define FULL_FLUSH     1
571  #define NORMAL_FLUSH   0
572 @@ -174,6 +176,7 @@
573  #define ITEM_REPORT_GROUP (1<<6)
574  #define ITEM_REPORT_ACL (1<<7)
575  #define ITEM_REPORT_XATTR (1<<8)
576 +#define ITEM_REPORT_FFLAGS (1<<9)
577  #define ITEM_BASIS_TYPE_FOLLOWS (1<<11)
578  #define ITEM_XNAME_FOLLOWS (1<<12)
579  #define ITEM_IS_NEW (1<<13)
580 @@ -452,6 +455,28 @@ typedef unsigned int size_t;
581  #endif
582  #endif
583  
584 +#define NO_FFLAGS ((uint32)-1)
585 +
586 +#ifdef HAVE_CHFLAGS
587 +#define SUPPORT_FILEFLAGS 1
588 +#define SUPPORT_FORCE_CHANGE 1
589 +#endif
590 +
591 +#if defined SUPPORT_FILEFLAGS || defined SUPPORT_FORCE_CHANGE
592 +#ifndef UF_NOUNLINK
593 +#define UF_NOUNLINK 0
594 +#endif
595 +#ifndef SF_NOUNLINK
596 +#define SF_NOUNLINK 0
597 +#endif
598 +#define USR_IMMUTABLE (UF_IMMUTABLE|UF_NOUNLINK)
599 +#define SYS_IMMUTABLE (SF_IMMUTABLE|SF_NOUNLINK)
600 +#define ALL_IMMUTABLE (USR_IMMUTABLE|SYS_IMMUTABLE)
601 +#define ST_FLAGS(st) (st.st_flags)
602 +#else
603 +#define ST_FLAGS(st) NO_FFLAGS
604 +#endif
605 +
606  /* Find a variable that is either exactly 32-bits or longer.
607   * If some code depends on 32-bit truncation, it will need to
608   * take special action in a "#if SIZEOF_INT32 > 4" section. */
609 @@ -620,6 +645,7 @@ extern int file_extra_cnt;
610  extern int inc_recurse;
611  extern int uid_ndx;
612  extern int gid_ndx;
613 +extern int fileflags_ndx;
614  extern int acls_ndx;
615  extern int xattrs_ndx;
616  
617 @@ -657,6 +683,11 @@ extern int xattrs_ndx;
618  /* When the associated option is on, all entries will have these present: */
619  #define F_OWNER(f) REQ_EXTRA(f, uid_ndx)->unum
620  #define F_GROUP(f) REQ_EXTRA(f, gid_ndx)->unum
621 +#if defined SUPPORT_FILEFLAGS || defined SUPPORT_FORCE_CHANGE
622 +#define F_FFLAGS(f) REQ_EXTRA(f, fileflags_ndx)->unum
623 +#else
624 +#define F_FFLAGS(f) NO_FFLAGS
625 +#endif
626  #define F_ACL(f) REQ_EXTRA(f, acls_ndx)->num
627  #define F_XATTR(f) REQ_EXTRA(f, xattrs_ndx)->num
628  #define F_NDX(f) REQ_EXTRA(f, unsort_ndx)->num
629 diff --git a/rsync.yo b/rsync.yo
630 --- a/rsync.yo
631 +++ b/rsync.yo
632 @@ -338,6 +338,7 @@ to the detailed description below for a complete description.  verb(
633   -K, --keep-dirlinks         treat symlinked dir on receiver as dir
634   -H, --hard-links            preserve hard links
635   -p, --perms                 preserve permissions
636 +     --fileflags             preserve file-flags (aka chflags)
637   -E, --executability         preserve executability
638       --chmod=CHMOD           affect file and/or directory permissions
639   -A, --acls                  preserve ACLs (implies -p)
640 @@ -369,7 +370,10 @@ to the detailed description below for a complete description.  verb(
641       --delete-after          receiver deletes after transfer, not before
642       --delete-excluded       also delete excluded files from dest dirs
643       --ignore-errors         delete even if there are I/O errors
644 -     --force                 force deletion of dirs even if not empty
645 +     --force-delete          force deletion of dirs even if not empty
646 +     --force-change          affect user/system immutable files/dirs
647 +     --force-uchange         affect user-immutable files/dirs
648 +     --force-schange         affect system-immutable files/dirs
649       --max-delete=NUM        don't delete more than NUM files
650       --max-size=SIZE         don't transfer any file larger than SIZE
651       --min-size=SIZE         don't transfer any file smaller than SIZE
652 @@ -540,7 +544,8 @@ specified, in which case bf(-r) is not implied.
653  
654  Note that bf(-a) bf(does not preserve hardlinks), because
655  finding multiply-linked files is expensive.  You must separately
656 -specify bf(-H).
657 +specify bf(-H).  Note also that for backward compatibility, bf(-a)
658 +currently does bf(not) imply the bf(--fileflags) option.
659  
660  dit(--no-OPTION) You may turn off one or more implied options by prefixing
661  the option name with "no-".  Not all options may be prefixed with a "no-":
662 @@ -785,7 +790,7 @@ they would be using bf(--copy-links).
663  Without this option, if the sending side has replaced a directory with a
664  symlink to a directory, the receiving side will delete anything that is in
665  the way of the new symlink, including a directory hierarchy (as long as
666 -bf(--force) or bf(--delete) is in effect).
667 +bf(--force-delete) or bf(--delete) is in effect).
668  
669  See also bf(--keep-dirlinks) for an analogous option for the receiving
670  side.
671 @@ -922,6 +927,29 @@ super-user copies all namespaces except system.*.  A normal user only copies
672  the user.* namespace.  To be able to backup and restore non-user namespaces as
673  a normal user, see the bf(--fake-super) option.
674  
675 +dit(bf(--fileflags)) This option causes rsync to update the file-flags to be
676 +the same as the source files and directories (if your OS supports the
677 +bf(chflags)(2) system call).   Some flags can only be altered by the super-user
678 +and some might only be unset below a certain secure-level (usually single-user
679 +mode). It will not make files alterable that are set to immutable on the
680 +receiver.  To do that, see bf(--force-change), bf(--force-uchange), and
681 +bf(--force-schange).
682 +
683 +dit(bf(--force-change)) This option causes rsync to disable both user-immutable
684 +and system-immutable flags on files and directories that are being updated or
685 +deleted on the receiving side.  This option overrides bf(--force-uchange) and
686 +bf(--force-schange).
687 +
688 +dit(bf(--force-uchange)) This option causes rsync to disable user-immutable
689 +flags on files and directories that are being updated or deleted on the
690 +receiving side.  It does not try to affect system flags.  This option overrides
691 +bf(--force-change) and bf(--force-schange).
692 +
693 +dit(bf(--force-schange)) This option causes rsync to disable system-immutable
694 +flags on files and directories that are being updated or deleted on the
695 +receiving side.  It does not try to affect user flags.  This option overrides
696 +bf(--force-change) and bf(--force-schange).
697 +
698  dit(bf(--chmod)) This option tells rsync to apply one or more
699  comma-separated "chmod" strings to the permission of the files in the
700  transfer.  The resulting value is treated as though it was the permissions
701 @@ -1177,12 +1205,13 @@ See bf(--delete) (which is implied) for more details on file-deletion.
702  dit(bf(--ignore-errors)) Tells bf(--delete) to go ahead and delete files
703  even when there are I/O errors.
704  
705 -dit(bf(--force)) This option tells rsync to delete a non-empty directory
706 +dit(bf(--force-delete)) This option tells rsync to delete a non-empty directory
707  when it is to be replaced by a non-directory.  This is only relevant if
708  deletions are not active (see bf(--delete) for details).
709  
710 -Note for older rsync versions: bf(--force) used to still be required when
711 -using bf(--delete-after), and it used to be non-functional unless the
712 +This option can be abbreviated bf(--force) for backward compatibility.
713 +Note that some older rsync versions used to still require bf(--force)
714 +when using bf(--delete-after), and it used to be non-functional unless the
715  bf(--recursive) option was also enabled.
716  
717  dit(bf(--max-delete=NUM)) This tells rsync not to delete more than NUM
718 @@ -1638,7 +1667,7 @@ with older versions of rsync, but that also turns on the output of other
719  verbose messages).
720  
721  The "%i" escape has a cryptic output that is 11 letters long.  The general
722 -format is like the string bf(YXcstpoguax), where bf(Y) is replaced by the
723 +format is like the string bf(YXcstpogfax), where bf(Y) is replaced by the
724  type of update being done, bf(X) is replaced by the file-type, and the
725  other letters represent attributes that may be output if they are being
726  modified.
727 @@ -1689,7 +1718,7 @@ quote(itemization(
728    sender's value (requires bf(--owner) and super-user privileges).
729    it() A bf(g) means the group is different and is being updated to the
730    sender's value (requires bf(--group) and the authority to set the group).
731 -  it() The bf(u) slot is reserved for future use.
732 +  it() The bf(f) means that the fileflags information changed.
733    it() The bf(a) means that the ACL information changed.
734    it() The bf(x) means that the extended attribute information changed.
735  ))
736 diff --git a/syscall.c b/syscall.c
737 --- a/syscall.c
738 +++ b/syscall.c
739 @@ -33,6 +33,7 @@ extern int dry_run;
740  extern int am_root;
741  extern int read_only;
742  extern int list_only;
743 +extern int force_change;
744  extern int preserve_perms;
745  extern int preserve_executability;
746  
747 @@ -50,7 +51,23 @@ int do_unlink(const char *fname)
748  {
749         if (dry_run) return 0;
750         RETURN_ERROR_IF_RO_OR_LO;
751 -       return unlink(fname);
752 +       if (unlink(fname) == 0)
753 +               return 0;
754 +#ifdef SUPPORT_FORCE_CHANGE
755 +       if (force_change && errno == EPERM) {
756 +               STRUCT_STAT st;
757 +
758 +               if (x_lstat(fname, &st, NULL) == 0
759 +                && make_mutable(fname, st.st_mode, st.st_flags, force_change) > 0) {
760 +                       if (unlink(fname) == 0)
761 +                               return 0;
762 +                       undo_make_mutable(fname, st.st_flags);
763 +               }
764 +               /* TODO: handle immutable directories */
765 +               errno = EPERM;
766 +       }
767 +#endif
768 +       return -1;
769  }
770  
771  int do_symlink(const char *fname1, const char *fname2)
772 @@ -69,14 +86,37 @@ int do_link(const char *fname1, const char *fname2)
773  }
774  #endif
775  
776 -int do_lchown(const char *path, uid_t owner, gid_t group)
777 +int do_lchown(const char *path, uid_t owner, gid_t group, mode_t mode, uint32 fileflags)
778  {
779         if (dry_run) return 0;
780         RETURN_ERROR_IF_RO_OR_LO;
781  #ifndef HAVE_LCHOWN
782  #define lchown chown
783  #endif
784 -       return lchown(path, owner, group);
785 +       if (lchown(path, owner, group) == 0)
786 +               return 0;
787 +#ifdef SUPPORT_FORCE_CHANGE
788 +       if (force_change && errno == EPERM) {
789 +               if (fileflags == NO_FFLAGS) {
790 +                       STRUCT_STAT st;
791 +                       if (x_lstat(path, &st, NULL) == 0) {
792 +                               mode = st.st_mode;
793 +                               fileflags = st.st_flags;
794 +                       }
795 +               }
796 +               if (fileflags != NO_FFLAGS
797 +                && make_mutable(path, mode, fileflags, force_change) > 0) {
798 +                       int ret = lchown(path, owner, group);
799 +                       undo_make_mutable(path, fileflags);
800 +                       if (ret == 0)
801 +                               return 0;
802 +               }
803 +               errno = EPERM;
804 +       }
805 +#else
806 +       mode = fileflags = 0; /* avoid compiler warning */
807 +#endif
808 +       return -1;
809  }
810  
811  int do_mknod(const char *pathname, mode_t mode, dev_t dev)
812 @@ -116,7 +156,7 @@ int do_mknod(const char *pathname, mode_t mode, dev_t dev)
813                         return -1;
814                 close(sock);
815  #ifdef HAVE_CHMOD
816 -               return do_chmod(pathname, mode);
817 +               return do_chmod(pathname, mode, 0);
818  #else
819                 return 0;
820  #endif
821 @@ -133,7 +173,22 @@ int do_rmdir(const char *pathname)
822  {
823         if (dry_run) return 0;
824         RETURN_ERROR_IF_RO_OR_LO;
825 -       return rmdir(pathname);
826 +       if (rmdir(pathname) == 0)
827 +               return 0;
828 +#ifdef SUPPORT_FORCE_CHANGE
829 +       if (force_change && errno == EPERM) {
830 +               STRUCT_STAT st;
831 +
832 +               if (x_lstat(pathname, &st, NULL) == 0
833 +                && make_mutable(pathname, st.st_mode, st.st_flags, force_change) > 0) {
834 +                       if (rmdir(pathname) == 0)
835 +                               return 0;
836 +                       undo_make_mutable(pathname, st.st_flags);
837 +               }
838 +               errno = EPERM;
839 +       }
840 +#endif
841 +       return -1;
842  }
843  
844  int do_open(const char *pathname, int flags, mode_t mode)
845 @@ -147,7 +202,7 @@ int do_open(const char *pathname, int flags, mode_t mode)
846  }
847  
848  #ifdef HAVE_CHMOD
849 -int do_chmod(const char *path, mode_t mode)
850 +int do_chmod(const char *path, mode_t mode, uint32 fileflags)
851  {
852         int code;
853         if (dry_run) return 0;
854 @@ -168,17 +223,74 @@ int do_chmod(const char *path, mode_t mode)
855  #endif
856         } else
857                 code = chmod(path, mode & CHMOD_BITS);
858 +#ifdef SUPPORT_FORCE_CHANGE
859 +       if (code < 0 && force_change && errno == EPERM && !S_ISLNK(mode)) {
860 +               if (fileflags == NO_FFLAGS) {
861 +                       STRUCT_STAT st;
862 +                       if (x_lstat(path, &st, NULL) == 0)
863 +                               fileflags = st.st_flags;
864 +               }
865 +               if (fileflags != NO_FFLAGS
866 +                && make_mutable(path, mode, fileflags, force_change) > 0) {
867 +                       code = chmod(path, mode & CHMOD_BITS);
868 +                       undo_make_mutable(path, fileflags);
869 +                       if (code == 0)
870 +                               return 0;
871 +               }
872 +               errno = EPERM;
873 +       }
874 +#else
875 +       fileflags = 0; /* avoid compiler warning */
876 +#endif
877         if (code != 0 && (preserve_perms || preserve_executability))
878             return code;
879         return 0;
880  }
881  #endif
882  
883 +#ifdef HAVE_CHFLAGS
884 +int do_chflags(const char *path, uint32 fileflags)
885 +{
886 +       if (dry_run) return 0;
887 +       RETURN_ERROR_IF_RO_OR_LO;
888 +       return chflags(path, fileflags);
889 +}
890 +#endif
891 +
892  int do_rename(const char *fname1, const char *fname2)
893  {
894         if (dry_run) return 0;
895         RETURN_ERROR_IF_RO_OR_LO;
896 -       return rename(fname1, fname2);
897 +       if (rename(fname1, fname2) == 0)
898 +               return 0;
899 +#ifdef SUPPORT_FORCE_CHANGE
900 +       if (force_change && errno == EPERM) {
901 +               STRUCT_STAT st1, st2;
902 +               int became_mutable;
903 +
904 +               if (x_lstat(fname1, &st1, NULL) != 0)
905 +                       goto failed;
906 +               became_mutable = make_mutable(fname1, st1.st_mode, st1.st_flags, force_change) > 0;
907 +               if (became_mutable && rename(fname1, fname2) == 0)
908 +                       goto success;
909 +               if (x_lstat(fname2, &st2, NULL) == 0
910 +                && make_mutable(fname2, st2.st_mode, st2.st_flags, force_change) > 0) {
911 +                       if (rename(fname1, fname2) == 0) {
912 +                         success:
913 +                               if (became_mutable) /* Yes, use fname2 and st1! */
914 +                                       undo_make_mutable(fname2, st1.st_flags);
915 +                               return 0;
916 +                       }
917 +                       undo_make_mutable(fname2, st2.st_flags);
918 +               }
919 +               /* TODO: handle immutable directories */
920 +               if (became_mutable)
921 +                       undo_make_mutable(fname1, st1.st_flags);
922 +         failed:
923 +               errno = EPERM;
924 +       }
925 +#endif
926 +       return -1;
927  }
928  
929  void trim_trailing_slashes(char *name)
930 diff --git a/t_stub.c b/t_stub.c
931 --- a/t_stub.c
932 +++ b/t_stub.c
933 @@ -26,6 +26,7 @@ int module_id = -1;
934  int relative_paths = 0;
935  int human_readable = 0;
936  int module_dirlen = 0;
937 +int force_change = 0;
938  mode_t orig_umask = 002;
939  char *partial_dir;
940  char *module_dir;
941 @@ -83,3 +84,23 @@ struct filter_list_struct server_filter_list;
942  {
943         return "tester";
944  }
945 +
946 +#if defined SUPPORT_FILEFLAGS || defined SUPPORT_FORCE_CHANGE
947 + int make_mutable(UNUSED(const char *fname), UNUSED(mode_t mode), UNUSED(uint32 fileflags), UNUSED(uint32 iflags))
948 +{
949 +       return 0;
950 +}
951 +
952 +/* Undo a prior make_mutable() call that returned a 1. */
953 + int undo_make_mutable(UNUSED(const char *fname), UNUSED(uint32 fileflags))
954 +{
955 +       return 0;
956 +}
957 +#endif
958 +
959 +#ifdef SUPPORT_XATTRS
960 + int x_lstat(UNUSED(const char *fname), UNUSED(STRUCT_STAT *fst), UNUSED(STRUCT_STAT *xst))
961 +{
962 +       return -1;
963 +}
964 +#endif
965 diff --git a/util.c b/util.c
966 --- a/util.c
967 +++ b/util.c
968 @@ -29,6 +29,7 @@ extern int module_id;
969  extern int modify_window;
970  extern int relative_paths;
971  extern int human_readable;
972 +extern int force_change;
973  extern char *module_dir;
974  extern unsigned int module_dirlen;
975  extern mode_t orig_umask;
976 @@ -122,7 +123,7 @@ NORETURN void overflow_exit(const char *str)
977         exit_cleanup(RERR_MALLOC);
978  }
979  
980 -int set_modtime(const char *fname, time_t modtime, mode_t mode)
981 +int set_modtime(const char *fname, time_t modtime, mode_t mode, uint32 fileflags)
982  {
983  #if !defined HAVE_LUTIMES || !defined HAVE_UTIMES
984         if (S_ISLNK(mode))
985 @@ -139,6 +140,7 @@ int set_modtime(const char *fname, time_t modtime, mode_t mode)
986                 return 0;
987  
988         {
989 +               int ret;
990  #ifdef HAVE_UTIMES
991                 struct timeval t[2];
992                 t[0].tv_sec = time(NULL);
993 @@ -152,20 +154,39 @@ int set_modtime(const char *fname, time_t modtime, mode_t mode)
994                         return 0;
995                 }
996  # endif
997 -               return utimes(fname, t);
998 +#define SET_THE_TIME(fn) utimes(fn, t)
999  #elif defined HAVE_STRUCT_UTIMBUF
1000                 struct utimbuf tbuf;
1001                 tbuf.actime = time(NULL);
1002                 tbuf.modtime = modtime;
1003 -               return utime(fname,&tbuf);
1004 +#define SET_THE_TIME(fn) utime(fn, &tbuf)
1005  #elif defined HAVE_UTIME
1006                 time_t t[2];
1007                 t[0] = time(NULL);
1008                 t[1] = modtime;
1009 -               return utime(fname,t);
1010 +#define SET_THE_TIME(fn) utime(fn, t)
1011  #else
1012  #error No file-time-modification routine found!
1013  #endif
1014 +               ret = SET_THE_TIME(fname);
1015 +#ifdef SUPPORT_FORCE_CHANGE
1016 +               if (ret != 0 && force_change && errno == EPERM) {
1017 +                       if (fileflags == NO_FFLAGS) {
1018 +                               STRUCT_STAT st;
1019 +                               if (x_lstat(fname, &st, NULL) == 0)
1020 +                                       fileflags = st.st_flags;
1021 +                       }
1022 +                       if (fileflags != NO_FFLAGS
1023 +                        && make_mutable(fname, mode, fileflags, force_change) > 0) {
1024 +                               ret = SET_THE_TIME(fname);
1025 +                               undo_make_mutable(fname, fileflags);
1026 +                       }
1027 +                       errno = EPERM;
1028 +               }
1029 +#else
1030 +               fileflags = 0; /* avoid compiler warning */
1031 +#endif
1032 +               return ret;
1033         }
1034  }
1035  
1036 diff --git a/xattrs.c b/xattrs.c
1037 --- a/xattrs.c
1038 +++ b/xattrs.c
1039 @@ -915,7 +915,7 @@ int set_stat_xattr(const char *fname, struct file_struct *file, mode_t new_mode)
1040         mode = (fst.st_mode & _S_IFMT) | (fmode & ACCESSPERMS)
1041              | (S_ISDIR(fst.st_mode) ? 0700 : 0600);
1042         if (fst.st_mode != mode)
1043 -               do_chmod(fname, mode);
1044 +               do_chmod(fname, mode, ST_FLAGS(fst));
1045         if (!IS_DEVICE(fst.st_mode) && !IS_SPECIAL(fst.st_mode))
1046                 fst.st_rdev = 0; /* just in case */
1047