1 This patch provides --fileflags, which preserves the st_flags stat() field.
2 Modified from a patch that was written by Rolf Grossmann.
4 To use this patch, run these commands for a successful build:
6 patch -p1 <patches/fileflags.diff
11 based-on: 40afd365cc8ca968fd16e161d24df5b8a8a520cc
12 diff --git a/Makefile.in b/Makefile.in
15 @@ -42,7 +42,7 @@ popt_OBJS=popt/findme.o popt/popt.o popt/poptconfig.o \
16 popt/popthelp.o popt/poptparse.o
17 OBJS=$(OBJS1) $(OBJS2) $(OBJS3) $(DAEMON_OBJ) $(LIBOBJ) $(ZLIBOBJ) @BUILD_POPT@
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@
22 # Programs we must have to run the test cases
23 CHECK_PROGS = rsync$(EXEEXT) tls$(EXEEXT) getgroups$(EXEEXT) getfsdev$(EXEEXT) \
24 @@ -107,7 +107,7 @@ getgroups$(EXEEXT): getgroups.o
25 getfsdev$(EXEEXT): getfsdev.o
26 $(CC) $(CFLAGS) $(LDFLAGS) -o $@ getfsdev.o $(LIBS)
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)
33 diff --git a/compat.c b/compat.c
36 @@ -43,9 +43,11 @@ extern int checksum_seed;
37 extern int basis_dir_cnt;
38 extern int prune_empty_dirs;
39 extern int protocol_version;
40 +extern int force_change;
41 extern int protect_args;
42 extern int preserve_uid;
43 extern int preserve_gid;
44 +extern int preserve_fileflags;
45 extern int preserve_acls;
46 extern int preserve_xattrs;
47 extern int need_messages_from_generator;
48 @@ -63,7 +65,7 @@ extern char *iconv_opt;
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;
55 int receiver_symlink_times = 0; /* receiver can set the time on a symlink */
56 int sender_symlink_iconv = 0; /* sender should convert symlink content */
57 @@ -140,6 +142,8 @@ void setup_protocol(int f_out,int f_in)
58 uid_ndx = ++file_extra_cnt;
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;
66 diff --git a/configure.ac b/configure.ac
69 @@ -569,6 +569,7 @@ AC_FUNC_UTIME_NULL
71 AC_CHECK_FUNCS(waitpid wait4 getcwd strdup chown chmod lchmod mknod mkfifo \
72 fchmod fstat ftruncate strchr readlink link utime utimes lutimes strftime \
74 memmove lchown vsnprintf snprintf vasprintf asprintf setsid strpbrk \
75 strlcat strlcpy strtol mallinfo getgroups setgroups geteuid getegid \
76 setlocale setmode open64 lseek64 mkstemp64 mtrace va_copy __va_copy \
77 diff --git a/flist.c b/flist.c
80 @@ -51,6 +51,7 @@ extern int preserve_links;
81 extern int preserve_hard_links;
82 extern int preserve_devices;
83 extern int preserve_specials;
84 +extern int preserve_fileflags;
85 extern int delete_during;
87 extern int relative_paths;
88 @@ -394,6 +395,9 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
90 static time_t modtime;
92 +#ifdef SUPPORT_FILEFLAGS
93 + static uint32 fileflags;
95 #ifdef SUPPORT_HARD_LINKS
98 @@ -423,6 +427,14 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
99 xflags |= XMIT_SAME_MODE;
102 +#ifdef SUPPORT_FILEFLAGS
103 + if (preserve_fileflags) {
104 + if (F_FFLAGS(file) == fileflags)
105 + xflags |= XMIT_SAME_FLAGS;
107 + fileflags = F_FFLAGS(file);
111 if (preserve_devices && IS_DEVICE(mode)) {
112 if (protocol_version < 28) {
113 @@ -547,6 +559,10 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
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);
121 if (preserve_uid && !(xflags & XMIT_SAME_UID)) {
122 if (protocol_version < 30)
124 @@ -634,6 +650,9 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x
126 static int64 modtime;
128 +#ifdef SUPPORT_FILEFLAGS
129 + static uint32 fileflags;
131 #ifdef SUPPORT_HARD_LINKS
134 @@ -731,6 +750,10 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x
135 file_length = F_LENGTH(first);
136 modtime = first->modtime;
138 +#ifdef SUPPORT_FILEFLAGS
139 + if (preserve_fileflags)
140 + fileflags = F_FFLAGS(first);
143 uid = F_OWNER(first);
145 @@ -768,6 +791,10 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x
147 if (chmod_modes && !S_ISLNK(mode))
148 mode = tweak_mode(mode, chmod_modes);
149 +#ifdef SUPPORT_FILEFLAGS
150 + if (preserve_fileflags && !(xflags & XMIT_SAME_FLAGS))
151 + fileflags = (uint32)read_int(f);
154 if (preserve_uid && !(xflags & XMIT_SAME_UID)) {
155 if (protocol_version < 30)
156 @@ -909,6 +936,10 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x
160 +#ifdef SUPPORT_FILEFLAGS
161 + if (fileflags_ndx) /* check the ndx for force_change w/o preserve_fileflags */
162 + F_FFLAGS(file) = fileflags;
167 @@ -1283,6 +1314,10 @@ struct file_struct *make_file(const char *fname, struct file_list *flist,
170 file->mode = st.st_mode;
171 +#if defined SUPPORT_FILEFLAGS || defined SUPPORT_FORCE_CHANGE
173 + F_FFLAGS(file) = st.st_flags;
176 F_OWNER(file) = st.st_uid;
178 @@ -1429,6 +1464,9 @@ static struct file_struct *send_file_name(int f, struct file_list *flist,
179 #ifdef SUPPORT_XATTRS
180 if (preserve_xattrs) {
181 sx.st.st_mode = file->mode;
182 +#ifdef SUPPORT_FILEFLAGS
183 + sx.st.st_flags = preserve_fileflags ? F_FFLAGS(file) : 0;
186 if (get_xattr(fname, &sx) < 0) {
187 io_error |= IOERR_GENERAL;
188 diff --git a/generator.c b/generator.c
191 @@ -35,6 +35,7 @@ extern int do_progress;
192 extern int relative_paths;
193 extern int implied_dirs;
194 extern int keep_dirlinks;
195 +extern int force_change;
196 extern int preserve_acls;
197 extern int preserve_xattrs;
198 extern int preserve_links;
199 @@ -42,6 +43,7 @@ extern int preserve_devices;
200 extern int preserve_specials;
201 extern int preserve_hard_links;
202 extern int preserve_executability;
203 +extern int preserve_fileflags;
204 extern int preserve_perms;
205 extern int preserve_times;
206 extern int delete_mode;
207 @@ -164,11 +166,15 @@ static enum delret delete_item(char *fbuf, uint16 mode, uint16 flags)
210 if (flags & DEL_NO_UID_WRITE)
211 - do_chmod(fbuf, mode | S_IWUSR);
212 + do_chmod(fbuf, mode | S_IWUSR, NO_FFLAGS);
214 if (S_ISDIR(mode) && !(flags & DEL_DIR_IS_EMPTY)) {
215 /* This only happens on the first call to delete_item() since
216 * delete_dir_contents() always calls us w/DEL_DIR_IS_EMPTY. */
217 +#ifdef SUPPORT_FORCE_CHANGE
219 + make_mutable(fbuf, NULL, NO_FFLAGS, force_change);
221 ignore_perishable = 1;
222 /* If DEL_RECURSE is not set, this just reports emptiness. */
223 ret = delete_dir_contents(fbuf, flags);
224 @@ -285,8 +291,14 @@ static enum delret delete_dir_contents(char *fname, uint16 flags)
227 strlcpy(p, fp->basename, remainder);
228 +#ifdef SUPPORT_FORCE_CHANGE
229 + if (force_change) {
230 + mode_t mode = fp->mode;
231 + make_mutable(fname, &mode, F_FFLAGS(fp), force_change);
234 if (!(fp->mode & S_IWUSR) && !am_root && fp->flags & FLAG_OWNED_BY_US)
235 - do_chmod(fname, fp->mode | S_IWUSR);
236 + do_chmod(fname, fp->mode | S_IWUSR, NO_FFLAGS);
237 /* Save stack by recursing to ourself directly. */
238 if (S_ISDIR(fp->mode)) {
239 if (delete_dir_contents(fname, flags | DEL_RECURSE) != DR_SUCCESS)
240 @@ -647,6 +659,10 @@ int unchanged_attrs(const char *fname, struct file_struct *file, stat_x *sxp)
242 if (perms_differ(file, sxp))
244 +#ifdef SUPPORT_FILEFLAGS
245 + if (preserve_fileflags && sxp->st.st_flags != F_FFLAGS(file))
248 if (ownership_differs(file, sxp))
251 @@ -698,6 +714,11 @@ void itemize(const char *fnamecmp, struct file_struct *file, int ndx, int statre
252 if (gid_ndx && !(file->flags & FLAG_SKIP_GROUP)
253 && sxp->st.st_gid != (gid_t)F_GROUP(file))
254 iflags |= ITEM_REPORT_GROUP;
255 +#ifdef SUPPORT_FILEFLAGS
256 + if (preserve_fileflags && !S_ISLNK(file->mode)
257 + && sxp->st.st_flags != F_FFLAGS(file))
258 + iflags |= ITEM_REPORT_FFLAGS;
261 if (preserve_acls && !S_ISLNK(file->mode)) {
262 if (!ACL_READY(*sxp))
263 @@ -1491,6 +1512,10 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
264 file->mode = dest_mode(file->mode, sx.st.st_mode,
265 dflt_perms, statret == 0);
267 +#ifdef SUPPORT_FORCE_CHANGE
268 + if (force_change && !preserve_fileflags)
269 + F_FFLAGS(file) = sx.st.st_flags;
271 if (statret != 0 && basis_dir[0] != NULL) {
272 int j = try_dests_non(file, fname, ndx, fnamecmpbuf, &sx,
274 @@ -1535,10 +1560,17 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
275 * readable and writable permissions during the time we are
276 * putting files within them. This is then restored to the
277 * former permissions after the transfer is done. */
278 +#ifdef SUPPORT_FORCE_CHANGE
279 + if (force_change && F_FFLAGS(file) & force_change) {
280 + mode_t mode = file->mode;
281 + if (make_mutable(fname, &mode, F_FFLAGS(file), force_change))
282 + need_retouch_dir_perms = 1;
286 if (!am_root && (file->mode & S_IRWXU) != S_IRWXU && dir_tweaking) {
287 mode_t mode = file->mode | S_IRWXU;
288 - if (do_chmod(fname, mode) < 0) {
289 + if (do_chmod(fname, mode, 0) < 0) {
290 rsyserr(FERROR_XFER, errno,
291 "failed to modify permissions on %s",
293 @@ -1573,6 +1605,10 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
294 file->mode = dest_mode(file->mode, sx.st.st_mode, dflt_perms,
297 +#ifdef SUPPORT_FORCE_CHANGE
298 + if (force_change && !preserve_fileflags)
299 + F_FFLAGS(file) = sx.st.st_flags;
302 #ifdef SUPPORT_HARD_LINKS
303 if (preserve_hard_links && F_HLINK_NOT_FIRST(file)
304 @@ -2116,13 +2152,17 @@ static void touch_up_dirs(struct file_list *flist, int ndx)
306 fname = f_name(file, NULL);
308 - do_chmod(fname, file->mode);
309 + do_chmod(fname, file->mode, 0);
310 if (need_retouch_dir_times) {
312 if (link_stat(fname, &st, 0) == 0
313 && cmp_time(st.st_mtime, file->modtime) != 0)
314 - set_modtime(fname, file->modtime, file->mode);
315 + set_modtime(fname, file->modtime, file->mode, 0);
317 +#ifdef SUPPORT_FORCE_CHANGE
318 + if (force_change && F_FFLAGS(file) & force_change)
319 + undo_make_mutable(fname, F_FFLAGS(file));
321 if (counter >= loopchk_limit) {
323 maybe_send_keepalive();
324 diff --git a/log.c b/log.c
327 @@ -658,7 +658,7 @@ static void log_formatted(enum logcode code, const char *format, const char *op,
328 c[5] = !(iflags & ITEM_REPORT_PERMS) ? '.' : 'p';
329 c[6] = !(iflags & ITEM_REPORT_OWNER) ? '.' : 'o';
330 c[7] = !(iflags & ITEM_REPORT_GROUP) ? '.' : 'g';
331 - c[8] = !(iflags & ITEM_REPORT_ATIME) ? '.' : 'u';
332 + c[8] = !(iflags & ITEM_REPORT_FFLAGS) ? '.' : 'f';
333 c[9] = !(iflags & ITEM_REPORT_ACL) ? '.' : 'a';
334 c[10] = !(iflags & ITEM_REPORT_XATTR) ? '.' : 'x';
336 diff --git a/main.c b/main.c
340 #if defined CONFIG_LOCALE && defined HAVE_LOCALE_H
343 +#ifdef SUPPORT_FORCE_CHANGE
344 +#include <sys/sysctl.h>
349 @@ -51,6 +54,7 @@ extern int protocol_version;
350 extern int file_total;
352 extern int xfer_dirs;
353 +extern int force_change;
354 extern int protect_args;
355 extern int relative_paths;
356 extern int sanitize_paths;
357 @@ -753,6 +757,22 @@ static int do_recv(int f_in, int f_out, char *local_name)
358 * points to an identical file won't be replaced by the referent. */
359 copy_links = copy_dirlinks = copy_unsafe_links = 0;
361 +#ifdef SUPPORT_FORCE_CHANGE
362 + if (force_change & SYS_IMMUTABLE) {
363 + /* Determine whether we'll be able to unlock a system immutable item. */
365 + int securityLevel = 0;
366 + size_t len = sizeof securityLevel;
369 + mib[1] = KERN_SECURELVL;
370 + if (sysctl(mib, 2, &securityLevel, &len, NULL, 0) == 0 && securityLevel > 0) {
371 + rprintf(FERROR, "System security level is too high to force mutability on system immutable files and directories.\n");
372 + exit_cleanup(RERR_UNSUPPORTED);
377 #ifdef SUPPORT_HARD_LINKS
378 if (preserve_hard_links && !inc_recurse)
379 match_hard_links(first_flist);
380 diff --git a/options.c b/options.c
383 @@ -53,6 +53,7 @@ int preserve_hard_links = 0;
384 int preserve_acls = 0;
385 int preserve_xattrs = 0;
386 int preserve_perms = 0;
387 +int preserve_fileflags = 0;
388 int preserve_executability = 0;
389 int preserve_devices = 0;
390 int preserve_specials = 0;
391 @@ -84,6 +85,7 @@ int implied_dirs = 1;
393 int allow_8bit_chars = 0;
394 int force_delete = 0;
395 +int force_change = 0;
397 int prune_empty_dirs = 0;
399 @@ -223,6 +225,7 @@ static void print_rsync_version(enum logcode f)
400 char const *links = "no ";
401 char const *iconv = "no ";
402 char const *ipv6 = "no ";
403 + char const *fileflags = "no ";
404 STRUCT_STAT *dumstat;
406 #if SUBPROTOCOL_VERSION != 0
407 @@ -256,6 +259,9 @@ static void print_rsync_version(enum logcode f)
408 #ifdef CAN_SET_SYMLINK_TIMES
411 +#ifdef SUPPORT_FILEFLAGS
415 rprintf(f, "%s version %s protocol version %d%s\n",
416 RSYNC_NAME, RSYNC_VERSION, PROTOCOL_VERSION, subprotocol);
417 @@ -269,8 +275,8 @@ static void print_rsync_version(enum logcode f)
418 (int)(sizeof (int64) * 8));
419 rprintf(f, " %ssocketpairs, %shardlinks, %ssymlinks, %sIPv6, batchfiles, %sinplace,\n",
420 got_socketpair, hardlinks, links, ipv6, have_inplace);
421 - rprintf(f, " %sappend, %sACLs, %sxattrs, %siconv, %ssymtimes\n",
422 - have_inplace, acls, xattrs, iconv, symtimes);
423 + rprintf(f, " %sappend, %sACLs, %sxattrs, %siconv, %ssymtimes, %sfile-flags\n",
424 + have_inplace, acls, xattrs, iconv, symtimes, fileflags);
426 #ifdef MAINTAINER_MODE
427 rprintf(f, "Panic Action: \"%s\"\n", get_panic_action());
428 @@ -337,6 +343,9 @@ void usage(enum logcode F)
429 rprintf(F," -K, --keep-dirlinks treat symlinked dir on receiver as dir\n");
430 rprintf(F," -H, --hard-links preserve hard links\n");
431 rprintf(F," -p, --perms preserve permissions\n");
432 +#ifdef SUPPORT_FILEFLAGS
433 + rprintf(F," --fileflags preserve file-flags (aka chflags)\n");
435 rprintf(F," -E, --executability preserve the file's executability\n");
436 rprintf(F," --chmod=CHMOD affect file and/or directory permissions\n");
438 @@ -374,7 +383,12 @@ void usage(enum logcode F)
439 rprintf(F," --delete-after receiver deletes after transfer, not during\n");
440 rprintf(F," --delete-excluded also delete excluded files from destination dirs\n");
441 rprintf(F," --ignore-errors delete even if there are I/O errors\n");
442 - rprintf(F," --force force deletion of directories even if not empty\n");
443 + rprintf(F," --force-delete force deletion of directories even if not empty\n");
444 +#ifdef SUPPORT_FORCE_CHANGE
445 + rprintf(F," --force-change affect user-/system-immutable files/dirs\n");
446 + rprintf(F," --force-uchange affect user-immutable files/dirs\n");
447 + rprintf(F," --force-schange affect system-immutable files/dirs\n");
449 rprintf(F," --max-delete=NUM don't delete more than NUM files\n");
450 rprintf(F," --max-size=SIZE don't transfer any file larger than SIZE\n");
451 rprintf(F," --min-size=SIZE don't transfer any file smaller than SIZE\n");
452 @@ -479,6 +493,10 @@ static struct poptOption long_options[] = {
453 {"perms", 'p', POPT_ARG_VAL, &preserve_perms, 1, 0, 0 },
454 {"no-perms", 0, POPT_ARG_VAL, &preserve_perms, 0, 0, 0 },
455 {"no-p", 0, POPT_ARG_VAL, &preserve_perms, 0, 0, 0 },
456 +#ifdef SUPPORT_FILEFLAGS
457 + {"fileflags", 0, POPT_ARG_VAL, &preserve_fileflags, 1, 0, 0 },
458 + {"no-fileflags", 0, POPT_ARG_VAL, &preserve_fileflags, 0, 0, 0 },
460 {"executability", 'E', POPT_ARG_NONE, &preserve_executability, 0, 0, 0 },
461 {"acls", 'A', POPT_ARG_NONE, 0, 'A', 0, 0 },
462 {"no-acls", 0, POPT_ARG_VAL, &preserve_acls, 0, 0, 0 },
463 @@ -557,6 +575,14 @@ static struct poptOption long_options[] = {
464 {"remove-source-files",0,POPT_ARG_VAL, &remove_source_files, 1, 0, 0 },
465 {"force", 0, POPT_ARG_VAL, &force_delete, 1, 0, 0 },
466 {"no-force", 0, POPT_ARG_VAL, &force_delete, 0, 0, 0 },
467 + {"force-delete", 0, POPT_ARG_VAL, &force_delete, 1, 0, 0 },
468 + {"no-force-delete", 0, POPT_ARG_VAL, &force_delete, 0, 0, 0 },
469 +#ifdef SUPPORT_FORCE_CHANGE
470 + {"force-change", 0, POPT_ARG_VAL, &force_change, ALL_IMMUTABLE, 0, 0 },
471 + {"no-force-change", 0, POPT_ARG_VAL, &force_change, 0, 0, 0 },
472 + {"force-uchange", 0, POPT_ARG_VAL, &force_change, USR_IMMUTABLE, 0, 0 },
473 + {"force-schange", 0, POPT_ARG_VAL, &force_change, SYS_IMMUTABLE, 0, 0 },
475 {"ignore-errors", 0, POPT_ARG_VAL, &ignore_errors, 1, 0, 0 },
476 {"no-ignore-errors", 0, POPT_ARG_VAL, &ignore_errors, 0, 0, 0 },
477 {"max-delete", 0, POPT_ARG_INT, &max_delete, 0, 0, 0 },
478 @@ -1879,6 +1905,9 @@ void server_options(char **args, int *argc_p)
479 if (xfer_dirs && !recurse && delete_mode && am_sender)
480 args[ac++] = "--no-r";
482 + if (preserve_fileflags)
483 + args[ac++] = "--fileflags";
485 if (do_compression && def_compress_level != Z_DEFAULT_COMPRESSION) {
486 if (asprintf(&arg, "--compress-level=%d", def_compress_level) < 0)
488 @@ -1966,6 +1995,16 @@ void server_options(char **args, int *argc_p)
489 args[ac++] = "--delete-excluded";
491 args[ac++] = "--force";
492 +#ifdef SUPPORT_FORCE_CHANGE
493 + if (force_change) {
494 + if (force_change == ALL_IMMUTABLE)
495 + args[ac++] = "--force-change";
496 + else if (force_change == USR_IMMUTABLE)
497 + args[ac++] = "--force-uchange";
498 + else if (force_change == SYS_IMMUTABLE)
499 + args[ac++] = "--force-schange";
503 args[ac++] = "--only-write-batch=X";
505 diff --git a/rsync.c b/rsync.c
512 +extern int force_change;
513 extern int preserve_acls;
514 extern int preserve_xattrs;
515 extern int preserve_perms;
516 +extern int preserve_fileflags;
517 extern int preserve_executability;
518 extern int preserve_times;
520 @@ -374,6 +376,74 @@ mode_t dest_mode(mode_t flist_mode, mode_t stat_mode, int dflt_perms,
524 +#if defined SUPPORT_FILEFLAGS || defined SUPPORT_FORCE_CHANGE
525 +/* Set a file's st_flags. */
526 +static int set_fileflags(const char *fname, uint32 fileflags)
528 + if (do_chflags(fname, fileflags) != 0) {
529 + rsyserr(FERROR_XFER, errno,
530 + "failed to set fileflags (%x) on %s",
531 + fileflags, full_fname(fname));
538 +/* Remove immutable flags from an object, so it can be altered/removed.
539 + * Returns the fileflags if flags were removed, otherwise 0. If the
540 + * fileflags value is NO_FFLAGS, we will stat the fname to figure out
541 + * what the flags are, and return the mode via *mode_ptr (if non-NULL). */
542 +uint32 make_mutable(const char *fname, mode_t *mode_ptr, uint32 fileflags, uint32 iflags)
544 + if (fileflags == NO_FFLAGS) {
546 + if (x_lstat(fname, &st, NULL) < 0)
548 + fileflags = st.st_flags;
550 + *mode_ptr = st.st_mode;
552 + mode_ptr = &st.st_mode;
555 + if ((mode_ptr && S_ISLNK(*mode_ptr)) || !(fileflags & iflags))
558 + if (!set_fileflags(fname, fileflags & ~iflags))
564 +/* Undo a prior make_mutable() call that returned a 1. */
565 +int undo_make_mutable(const char *fname, uint32 fileflags)
567 + if (!set_fileflags(fname, fileflags)) {
568 + rsyserr(FINFO, errno, "failed to relock %s", full_fname(fname));
574 +/* This returns the st_flags value if the parent directory was made mutable, otherwise 0.
575 + * It stores the parent directory path into parent_dirbuf. */
576 +int make_parentdir_mutable(const char *fname, uint32 iflags, char *parent_dirbuf, int parent_dirbuf_size)
578 + char *slash = strrchr(fname, '/');
581 + int len = slash - fname;
582 + if (len >= parent_dirbuf_size)
584 + strlcpy(parent_dirbuf, fname, len+1);
586 + strlcpy(parent_dirbuf, ".", parent_dirbuf_size);
588 + return make_mutable(parent_dirbuf, NULL, NO_FFLAGS, iflags);
592 int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
593 const char *fnamecmp, int flags)
595 @@ -382,6 +452,9 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
596 int change_uid, change_gid;
597 mode_t new_mode = file->mode;
599 +#ifdef SUPPORT_FORCE_CHANGE
600 + int became_mutable = 0;
605 @@ -411,6 +484,11 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
606 if (daemon_chmod_modes && !S_ISLNK(new_mode))
607 new_mode = tweak_mode(new_mode, daemon_chmod_modes);
609 +#ifdef SUPPORT_FORCE_CHANGE
611 + became_mutable = make_mutable(fname, &sxp->st.st_mode, sxp->st.st_flags, force_change);
615 if (preserve_acls && !S_ISLNK(file->mode) && !ACL_READY(*sxp))
617 @@ -429,7 +507,7 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
618 flags |= ATTRS_SKIP_MTIME;
619 if (!(flags & ATTRS_SKIP_MTIME)
620 && cmp_time(sxp->st.st_mtime, file->modtime) != 0) {
621 - int ret = set_modtime(fname, file->modtime, sxp->st.st_mode);
622 + int ret = set_modtime(fname, file->modtime, sxp->st.st_mode, ST_FLAGS(sxp->st));
624 rsyserr(FERROR_XFER, errno, "failed to set times on %s",
626 @@ -465,7 +543,7 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
628 uid_t uid = change_uid ? (uid_t)F_OWNER(file) : sxp->st.st_uid;
629 gid_t gid = change_gid ? (gid_t)F_GROUP(file) : sxp->st.st_gid;
630 - if (do_lchown(fname, uid, gid) != 0) {
631 + if (do_lchown(fname, uid, gid, sxp->st.st_mode, ST_FLAGS(sxp->st)) != 0) {
632 /* We shouldn't have attempted to change uid
633 * or gid unless have the privilege. */
634 rsyserr(FERROR_XFER, errno, "%s %s failed",
635 @@ -503,7 +581,7 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
638 if (!BITS_EQUAL(sxp->st.st_mode, new_mode, CHMOD_BITS)) {
639 - int ret = am_root < 0 ? 0 : do_chmod(fname, new_mode);
640 + int ret = am_root < 0 ? 0 : do_chmod(fname, new_mode, ST_FLAGS(sxp->st));
642 rsyserr(FERROR_XFER, errno,
643 "failed to set permissions on %s",
644 @@ -515,6 +593,24 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
648 +#ifdef SUPPORT_FORCE_CHANGE
649 + if (became_mutable)
650 + undo_make_mutable(fname, sxp->st.st_flags);
653 +#ifdef SUPPORT_FILEFLAGS
654 + if (preserve_fileflags && !S_ISLNK(sxp->st.st_mode)
655 + && sxp->st.st_flags != F_FFLAGS(file)) {
656 + uint32 fileflags = F_FFLAGS(file);
657 + if (flags & ATTRS_DELAY_IMMUTABLE)
658 + fileflags &= ~ALL_IMMUTABLE;
659 + if (sxp->st.st_flags != fileflags
660 + && !set_fileflags(fname, fileflags))
666 if (verbose > 1 && flags & ATTRS_REPORT) {
668 rprintf(FCLIENT, "%s\n", fname);
669 @@ -578,7 +674,8 @@ int finish_transfer(const char *fname, const char *fnametmp,
671 /* Change permissions before putting the file into place. */
672 set_file_attrs(fnametmp, file, NULL, fnamecmp,
673 - ok_to_set_time ? 0 : ATTRS_SKIP_MTIME);
674 + ATTRS_DELAY_IMMUTABLE
675 + | (ok_to_set_time ? 0 : ATTRS_SKIP_MTIME));
677 /* move tmp file over real file */
679 @@ -597,6 +694,10 @@ int finish_transfer(const char *fname, const char *fnametmp,
682 /* The file was moved into place (not copied), so it's done. */
683 +#ifdef SUPPORT_FILEFLAGS
684 + if (preserve_fileflags && F_FFLAGS(file) & ALL_IMMUTABLE)
685 + set_fileflags(fname, F_FFLAGS(file));
689 /* The file was copied, so tweak the perms of the copied file. If it
690 diff --git a/rsync.h b/rsync.h
694 #define XMIT_GROUP_NAME_FOLLOWS (1<<11) /* protocols 30 - now */
695 #define XMIT_HLINK_FIRST (1<<12) /* protocols 30 - now (HLINKED files only) */
696 #define XMIT_IO_ERROR_ENDLIST (1<<12) /* protocols 31*- now (w/XMIT_EXTENDED_FLAGS) (also protocol 30 w/'f' compat flag) */
697 +#define XMIT_SAME_FLAGS (1<<14) /* protocols ?? - now */
699 /* These flags are used in the live flist data. */
703 #define ATTRS_REPORT (1<<0)
704 #define ATTRS_SKIP_MTIME (1<<1)
705 +#define ATTRS_DELAY_IMMUTABLE (1<<2)
708 #define NORMAL_FLUSH 0
710 #define ITEM_REPORT_GROUP (1<<6)
711 #define ITEM_REPORT_ACL (1<<7)
712 #define ITEM_REPORT_XATTR (1<<8)
713 +#define ITEM_REPORT_FFLAGS (1<<9)
714 #define ITEM_BASIS_TYPE_FOLLOWS (1<<11)
715 #define ITEM_XNAME_FOLLOWS (1<<12)
716 #define ITEM_IS_NEW (1<<13)
717 @@ -482,6 +485,28 @@ typedef unsigned int size_t;
721 +#define NO_FFLAGS ((uint32)-1)
724 +#define SUPPORT_FILEFLAGS 1
725 +#define SUPPORT_FORCE_CHANGE 1
728 +#if defined SUPPORT_FILEFLAGS || defined SUPPORT_FORCE_CHANGE
730 +#define UF_NOUNLINK 0
733 +#define SF_NOUNLINK 0
735 +#define USR_IMMUTABLE (UF_IMMUTABLE|UF_NOUNLINK|UF_APPEND)
736 +#define SYS_IMMUTABLE (SF_IMMUTABLE|SF_NOUNLINK|SF_APPEND)
737 +#define ALL_IMMUTABLE (USR_IMMUTABLE|SYS_IMMUTABLE)
738 +#define ST_FLAGS(st) (st.st_flags)
740 +#define ST_FLAGS(st) NO_FFLAGS
743 /* Find a variable that is either exactly 32-bits or longer.
744 * If some code depends on 32-bit truncation, it will need to
745 * take special action in a "#if SIZEOF_INT32 > 4" section. */
746 @@ -652,6 +677,7 @@ extern int file_extra_cnt;
747 extern int inc_recurse;
750 +extern int fileflags_ndx;
752 extern int xattrs_ndx;
754 @@ -689,6 +715,11 @@ extern int xattrs_ndx;
755 /* When the associated option is on, all entries will have these present: */
756 #define F_OWNER(f) REQ_EXTRA(f, uid_ndx)->unum
757 #define F_GROUP(f) REQ_EXTRA(f, gid_ndx)->unum
758 +#if defined SUPPORT_FILEFLAGS || defined SUPPORT_FORCE_CHANGE
759 +#define F_FFLAGS(f) REQ_EXTRA(f, fileflags_ndx)->unum
761 +#define F_FFLAGS(f) NO_FFLAGS
763 #define F_ACL(f) REQ_EXTRA(f, acls_ndx)->num
764 #define F_XATTR(f) REQ_EXTRA(f, xattrs_ndx)->num
765 #define F_NDX(f) REQ_EXTRA(f, unsort_ndx)->num
766 diff --git a/rsync.yo b/rsync.yo
769 @@ -355,6 +355,7 @@ to the detailed description below for a complete description. verb(
770 -K, --keep-dirlinks treat symlinked dir on receiver as dir
771 -H, --hard-links preserve hard links
772 -p, --perms preserve permissions
773 + --fileflags preserve file-flags (aka chflags)
774 -E, --executability preserve executability
775 --chmod=CHMOD affect file and/or directory permissions
776 -A, --acls preserve ACLs (implies -p)
777 @@ -386,7 +387,10 @@ to the detailed description below for a complete description. verb(
778 --delete-after receiver deletes after transfer, not during
779 --delete-excluded also delete excluded files from dest dirs
780 --ignore-errors delete even if there are I/O errors
781 - --force force deletion of dirs even if not empty
782 + --force-delete force deletion of dirs even if not empty
783 + --force-change affect user/system immutable files/dirs
784 + --force-uchange affect user-immutable files/dirs
785 + --force-schange affect system-immutable files/dirs
786 --max-delete=NUM don't delete more than NUM files
787 --max-size=SIZE don't transfer any file larger than SIZE
788 --min-size=SIZE don't transfer any file smaller than SIZE
789 @@ -566,7 +570,8 @@ specified, in which case bf(-r) is not implied.
791 Note that bf(-a) bf(does not preserve hardlinks), because
792 finding multiply-linked files is expensive. You must separately
794 +specify bf(-H). Note also that for backward compatibility, bf(-a)
795 +currently does bf(not) imply the bf(--fileflags) option.
797 dit(--no-OPTION) You may turn off one or more implied options by prefixing
798 the option name with "no-". Not all options may be prefixed with a "no-":
799 @@ -846,7 +851,7 @@ they would be using bf(--copy-links).
800 Without this option, if the sending side has replaced a directory with a
801 symlink to a directory, the receiving side will delete anything that is in
802 the way of the new symlink, including a directory hierarchy (as long as
803 -bf(--force) or bf(--delete) is in effect).
804 +bf(--force-delete) or bf(--delete) is in effect).
806 See also bf(--keep-dirlinks) for an analogous option for the receiving
808 @@ -1009,6 +1014,29 @@ Note that this option does not copy rsyncs special xattr values (e.g. those
809 used by bf(--fake-super)) unless you repeat the option (e.g. -XX). This
810 "copy all xattrs" mode cannot be used with bf(--fake-super).
812 +dit(bf(--fileflags)) This option causes rsync to update the file-flags to be
813 +the same as the source files and directories (if your OS supports the
814 +bf(chflags)(2) system call). Some flags can only be altered by the super-user
815 +and some might only be unset below a certain secure-level (usually single-user
816 +mode). It will not make files alterable that are set to immutable on the
817 +receiver. To do that, see bf(--force-change), bf(--force-uchange), and
818 +bf(--force-schange).
820 +dit(bf(--force-change)) This option causes rsync to disable both user-immutable
821 +and system-immutable flags on files and directories that are being updated or
822 +deleted on the receiving side. This option overrides bf(--force-uchange) and
823 +bf(--force-schange).
825 +dit(bf(--force-uchange)) This option causes rsync to disable user-immutable
826 +flags on files and directories that are being updated or deleted on the
827 +receiving side. It does not try to affect system flags. This option overrides
828 +bf(--force-change) and bf(--force-schange).
830 +dit(bf(--force-schange)) This option causes rsync to disable system-immutable
831 +flags on files and directories that are being updated or deleted on the
832 +receiving side. It does not try to affect user flags. This option overrides
833 +bf(--force-change) and bf(--force-schange).
835 dit(bf(--chmod)) This option tells rsync to apply one or more
836 comma-separated "chmod" modes to the permission of the files in the
837 transfer. The resulting value is treated as though it were the permissions
838 @@ -1289,12 +1317,13 @@ See bf(--delete) (which is implied) for more details on file-deletion.
839 dit(bf(--ignore-errors)) Tells bf(--delete) to go ahead and delete files
840 even when there are I/O errors.
842 -dit(bf(--force)) This option tells rsync to delete a non-empty directory
843 +dit(bf(--force-delete)) This option tells rsync to delete a non-empty directory
844 when it is to be replaced by a non-directory. This is only relevant if
845 deletions are not active (see bf(--delete) for details).
847 -Note for older rsync versions: bf(--force) used to still be required when
848 -using bf(--delete-after), and it used to be non-functional unless the
849 +This option can be abbreviated bf(--force) for backward compatibility.
850 +Note that some older rsync versions used to still require bf(--force)
851 +when using bf(--delete-after), and it used to be non-functional unless the
852 bf(--recursive) option was also enabled.
854 dit(bf(--max-delete=NUM)) This tells rsync not to delete more than NUM
855 @@ -1782,7 +1811,7 @@ with older versions of rsync, but that also turns on the output of other
858 The "%i" escape has a cryptic output that is 11 letters long. The general
859 -format is like the string bf(YXcstpoguax), where bf(Y) is replaced by the
860 +format is like the string bf(YXcstpogfax), where bf(Y) is replaced by the
861 type of update being done, bf(X) is replaced by the file-type, and the
862 other letters represent attributes that may be output if they are being
864 @@ -1838,7 +1867,7 @@ quote(itemization(
865 sender's value (requires bf(--owner) and super-user privileges).
866 it() A bf(g) means the group is different and is being updated to the
867 sender's value (requires bf(--group) and the authority to set the group).
868 - it() The bf(u) slot is reserved for future use.
869 + it() The bf(f) means that the fileflags information changed.
870 it() The bf(a) means that the ACL information changed.
871 it() The bf(x) means that the extended attribute information changed.
873 diff --git a/syscall.c b/syscall.c
876 @@ -33,6 +33,7 @@ extern int dry_run;
878 extern int read_only;
879 extern int list_only;
880 +extern int force_change;
881 extern int preserve_perms;
882 extern int preserve_executability;
884 @@ -50,14 +51,56 @@ int do_unlink(const char *fname)
886 if (dry_run) return 0;
887 RETURN_ERROR_IF_RO_OR_LO;
888 - return unlink(fname);
889 + if (unlink(fname) == 0)
892 +#ifdef SUPPORT_FORCE_CHANGE
893 + if (force_change && (errno == EPERM || errno == EACCES)) {
894 + char parent[MAXPATHLEN];
896 + int saved_errno = errno;
897 + int file_flags = make_mutable(fname, NULL, NO_FFLAGS, force_change);
898 + if (file_flags && unlink(fname) == 0)
900 + parent_flags = make_parentdir_mutable(fname, force_change, parent, sizeof parent);
901 + if (parent_flags) {
902 + int ret = unlink(fname);
903 + undo_make_mutable(parent, parent_flags);
908 + undo_make_mutable(fname, file_flags);
909 + errno = saved_errno;
916 int do_symlink(const char *fname1, const char *fname2)
918 if (dry_run) return 0;
919 RETURN_ERROR_IF_RO_OR_LO;
920 - return symlink(fname1, fname2);
921 + if (symlink(fname1, fname2) == 0)
924 +#ifdef SUPPORT_FORCE_CHANGE
925 + if (force_change && (errno == EPERM || errno == EACCES)) {
926 + char parent[MAXPATHLEN];
927 + int saved_errno = errno;
928 + int parent_flags = make_parentdir_mutable(fname2, force_change, parent, sizeof parent);
929 + if (parent_flags) {
930 + int ret = symlink(fname1, fname2);
931 + undo_make_mutable(parent, parent_flags);
935 + errno = saved_errno;
943 @@ -65,18 +108,55 @@ int do_link(const char *fname1, const char *fname2)
945 if (dry_run) return 0;
946 RETURN_ERROR_IF_RO_OR_LO;
947 - return link(fname1, fname2);
948 + if (link(fname1, fname2) == 0)
951 +#ifdef SUPPORT_FORCE_CHANGE
952 + if (force_change && (errno == EPERM || errno == EACCES)) {
953 + char parent[MAXPATHLEN];
954 + int saved_errno = errno;
955 + int parent_flags = make_parentdir_mutable(fname2, force_change, parent, sizeof parent);
956 + if (parent_flags) {
957 + int ret = link(fname1, fname2);
958 + undo_make_mutable(parent, parent_flags);
962 + errno = saved_errno;
970 -int do_lchown(const char *path, uid_t owner, gid_t group)
971 +int do_lchown(const char *path, uid_t owner, gid_t group, mode_t mode, uint32 fileflags)
973 if (dry_run) return 0;
974 RETURN_ERROR_IF_RO_OR_LO;
978 - return lchown(path, owner, group);
979 + if (lchown(path, owner, group) == 0)
982 +#ifdef SUPPORT_FORCE_CHANGE
983 + if (force_change && (errno == EPERM || errno == EACCES)) {
984 + int saved_errno = errno;
985 + fileflags = make_mutable(path, &mode, fileflags, force_change);
987 + int ret = lchown(path, owner, group);
988 + undo_make_mutable(path, fileflags);
992 + errno = saved_errno;
995 + mode = fileflags = 0; /* avoid compiler warning */
1001 int do_mknod(const char *pathname, mode_t mode, dev_t dev)
1002 @@ -116,7 +196,7 @@ int do_mknod(const char *pathname, mode_t mode, dev_t dev)
1006 - return do_chmod(pathname, mode);
1007 + return do_chmod(pathname, mode, 0);
1011 @@ -133,21 +213,63 @@ int do_rmdir(const char *pathname)
1013 if (dry_run) return 0;
1014 RETURN_ERROR_IF_RO_OR_LO;
1015 - return rmdir(pathname);
1016 + if (rmdir(pathname) == 0)
1019 +#ifdef SUPPORT_FORCE_CHANGE
1020 + if (force_change && (errno == EPERM || errno == EACCES)) {
1021 + char parent[MAXPATHLEN];
1023 + int saved_errno = errno;
1024 + int file_flags = make_mutable(pathname, NULL, NO_FFLAGS, force_change);
1025 + if (file_flags && rmdir(pathname) == 0)
1027 + parent_flags = make_parentdir_mutable(pathname, force_change, parent, sizeof parent);
1028 + if (parent_flags) {
1029 + int ret = rmdir(pathname);
1030 + undo_make_mutable(parent, parent_flags);
1035 + undo_make_mutable(pathname, file_flags);
1036 + errno = saved_errno;
1043 int do_open(const char *pathname, int flags, mode_t mode)
1046 if (flags != O_RDONLY) {
1047 RETURN_ERROR_IF(dry_run, 0);
1048 RETURN_ERROR_IF_RO_OR_LO;
1050 + if ((fd = open(pathname, flags | O_BINARY, mode)) >= 0)
1053 +#ifdef SUPPORT_FORCE_CHANGE
1054 + if (force_change && (errno == EPERM || errno == EACCES)) {
1055 + char parent[MAXPATHLEN];
1056 + int saved_errno = errno;
1057 + int parent_flags = make_parentdir_mutable(pathname, force_change, parent, sizeof parent);
1058 + if (parent_flags) {
1059 + fd = open(pathname, flags | O_BINARY, mode);
1060 + undo_make_mutable(parent, parent_flags);
1064 + errno = saved_errno;
1068 - return open(pathname, flags | O_BINARY, mode);
1073 -int do_chmod(const char *path, mode_t mode)
1074 +int do_chmod(const char *path, mode_t mode, uint32 fileflags)
1077 if (dry_run) return 0;
1078 @@ -170,17 +292,93 @@ int do_chmod(const char *path, mode_t mode)
1080 code = chmod(path, mode & CHMOD_BITS); /* DISCOURAGED FUNCTION */
1081 #endif /* !HAVE_LCHMOD */
1082 +#ifdef SUPPORT_FORCE_CHANGE
1083 + if (code < 0 && force_change && (errno == EPERM || errno == EACCES) && !S_ISLNK(mode)) {
1084 + int saved_errno = errno;
1085 + fileflags = make_mutable(path, &mode, fileflags, force_change);
1088 + code = lchmod(path, mode & CHMOD_BITS);
1090 + code = chmod(path, mode & CHMOD_BITS);
1092 + undo_make_mutable(path, fileflags);
1096 + errno = saved_errno;
1099 + fileflags = 0; /* avoid compiler warning */
1101 if (code != 0 && (preserve_perms || preserve_executability))
1107 +#ifdef HAVE_CHFLAGS
1108 +int do_chflags(const char *path, uint32 fileflags)
1110 + if (dry_run) return 0;
1111 + RETURN_ERROR_IF_RO_OR_LO;
1112 + return chflags(path, fileflags);
1116 int do_rename(const char *fname1, const char *fname2)
1118 if (dry_run) return 0;
1119 RETURN_ERROR_IF_RO_OR_LO;
1120 - return rename(fname1, fname2);
1121 + if (rename(fname1, fname2) == 0)
1124 +#ifdef SUPPORT_FORCE_CHANGE
1125 + if (force_change && (errno == EPERM || errno == EACCES)) {
1126 + int saved_errno = errno;
1127 + int ret = -1, file2_flags = 0;
1128 + int file1_flags = make_mutable(fname1, NULL, NO_FFLAGS, force_change);
1129 + if (file1_flags && rename(fname1, fname2) == 0)
1132 + file2_flags = make_mutable(fname2, NULL, NO_FFLAGS, force_change);
1133 + if (file2_flags && rename(fname1, fname2) == 0)
1136 + char parent1[MAXPATHLEN];
1137 + int parent1_flags = make_parentdir_mutable(fname1, force_change,
1138 + parent1, sizeof parent1);
1139 + if (parent1_flags && rename(fname1, fname2) == 0)
1142 + char parent2[MAXPATHLEN];
1143 + int parent2_flags = make_parentdir_mutable(fname2, force_change,
1144 + parent2, sizeof parent2);
1145 + if (parent2_flags) {
1146 + if (rename(fname1, fname2) == 0)
1148 + undo_make_mutable(parent2, parent2_flags);
1151 + if (parent1_flags)
1152 + undo_make_mutable(parent1, parent1_flags);
1157 + file2_flags = file1_flags; /* file1 is now file2 */
1158 + else if (file1_flags)
1159 + undo_make_mutable(fname1, file1_flags);
1161 + undo_make_mutable(fname2, file2_flags);
1165 + errno = saved_errno;
1172 #ifdef HAVE_FTRUNCATE
1173 @@ -222,7 +420,25 @@ int do_mkdir(char *fname, mode_t mode)
1174 if (dry_run) return 0;
1175 RETURN_ERROR_IF_RO_OR_LO;
1176 trim_trailing_slashes(fname);
1177 - return mkdir(fname, mode);
1178 + if (mkdir(fname, mode) == 0)
1181 +#ifdef SUPPORT_FORCE_CHANGE
1182 + if (force_change && (errno == EPERM || errno == EACCES)) {
1183 + char parent[MAXPATHLEN];
1184 + int saved_errno = errno;
1185 + int parent_flags = make_parentdir_mutable(fname, force_change, parent, sizeof parent);
1186 + if (parent_flags) {
1187 + int ret = mkdir(fname, mode);
1188 + undo_make_mutable(parent, parent_flags);
1192 + errno = saved_errno;
1199 /* like mkstemp but forces permissions */
1200 @@ -235,7 +451,19 @@ int do_mkstemp(char *template, mode_t perms)
1201 #if defined HAVE_SECURE_MKSTEMP && defined HAVE_FCHMOD && (!defined HAVE_OPEN64 || defined HAVE_MKSTEMP64)
1203 int fd = mkstemp(template);
1205 +#ifdef SUPPORT_FORCE_CHANGE
1206 + if (fd < 0 && force_change) {
1207 + char parent[MAXPATHLEN];
1208 + int saved_errno = errno;
1209 + int parent_flags = make_parentdir_mutable(template, force_change, parent, sizeof parent);
1210 + if (parent_flags) {
1211 + fd = mkstemp(template);
1212 + undo_make_mutable(parent, parent_flags);
1214 + errno = saved_errno;
1219 if (fchmod(fd, perms) != 0 && preserve_perms) {
1220 int errno_save = errno;
1221 @@ -302,7 +530,7 @@ OFF_T do_lseek(int fd, OFF_T offset, int whence)
1224 #ifdef HAVE_UTIMENSAT
1225 -int do_utimensat(const char *fname, time_t modtime, uint32 mod_nsec)
1226 +int do_utimensat(const char *fname, time_t modtime, uint32 mod_nsec, mode_t mode, uint32 fileflags)
1228 struct timespec t[2];
1230 @@ -313,12 +541,26 @@ int do_utimensat(const char *fname, time_t modtime, uint32 mod_nsec)
1231 t[0].tv_nsec = UTIME_NOW;
1232 t[1].tv_sec = modtime;
1233 t[1].tv_nsec = mod_nsec;
1234 - return utimensat(AT_FDCWD, fname, t, AT_SYMLINK_NOFOLLOW);
1235 + if (utimensat(AT_FDCWD, fname, t, AT_SYMLINK_NOFOLLOW) == 0)
1238 +#ifdef SUPPORT_FORCE_CHANGE
1239 + fileflags = make_mutable(fname, &mode, fileflags, force_change);
1241 + if (utimensat(AT_FDCWD, fname, t, AT_SYMLINK_NOFOLLOW) == 0)
1243 + undo_make_mutable(fname, fileflags);
1246 + mode = fileflags; /* avoid compiler warning */
1254 -int do_lutimes(const char *fname, time_t modtime, uint32 mod_nsec)
1255 +int do_lutimes(const char *fname, time_t modtime, uint32 mod_nsec, mode_t mode, uint32 fileflags)
1257 struct timeval t[2];
1259 @@ -329,12 +571,26 @@ int do_lutimes(const char *fname, time_t modtime, uint32 mod_nsec)
1261 t[1].tv_sec = modtime;
1262 t[1].tv_usec = mod_nsec / 1000;
1263 - return lutimes(fname, t);
1264 + if (lutimes(fname, t) == 0)
1267 +#ifdef SUPPORT_FORCE_CHANGE
1268 + fileflags = make_mutable(fname, &mode, fileflags, force_change);
1270 + if (lutimes(fname, t) == 0)
1272 + undo_make_mutable(fname, fileflags);
1275 + mode = fileflags; /* avoid compiler warning */
1283 -int do_utimes(const char *fname, time_t modtime, uint32 mod_nsec)
1284 +int do_utimes(const char *fname, time_t modtime, uint32 mod_nsec, mode_t mode, uint32 fileflags)
1286 struct timeval t[2];
1288 @@ -345,14 +601,28 @@ int do_utimes(const char *fname, time_t modtime, uint32 mod_nsec)
1290 t[1].tv_sec = modtime;
1291 t[1].tv_usec = mod_nsec / 1000;
1292 - return utimes(fname, t);
1293 + if (utimes(fname, t) == 0)
1296 +#ifdef SUPPORT_FORCE_CHANGE
1297 + fileflags = make_mutable(fname, &mode, fileflags, force_change);
1299 + if (utimes(fname, t) == 0)
1301 + undo_make_mutable(fname, fileflags);
1304 + mode = fileflags; /* avoid compiler warning */
1310 #elif defined HAVE_UTIME
1311 -int do_utime(const char *fname, time_t modtime, UNUSED(uint32 mod_nsec))
1312 +int do_utime(const char *fname, time_t modtime, UNUSED(uint32 mod_nsec), mode_t mode, uint32 fileflags)
1314 #ifdef HAVE_STRUCT_UTIMBUF
1315 - struct utimbuf tbuf;
1316 + struct utimbuf tbuf, *t = &tbuf;
1320 @@ -360,15 +630,28 @@ int do_utime(const char *fname, time_t modtime, UNUSED(uint32 mod_nsec))
1321 if (dry_run) return 0;
1322 RETURN_ERROR_IF_RO_OR_LO;
1324 -# ifdef HAVE_STRUCT_UTIMBUF
1325 +#ifdef HAVE_STRUCT_UTIMBUF
1326 tbuf.actime = time(NULL);
1327 tbuf.modtime = modtime;
1328 - return utime(fname, &tbuf);
1333 - return utime(fname, t);
1336 + if (utime(fname, t) == 0)
1339 +#ifdef SUPPORT_FORCE_CHANGE
1340 + fileflags = make_mutable(fname, &mode, fileflags, force_change);
1342 + if (utime(fname, t) == 0)
1344 + undo_make_mutable(fname, fileflags);
1347 + mode = fileflags; /* avoid compiler warning */
1354 diff --git a/t_stub.c b/t_stub.c
1357 @@ -26,6 +26,7 @@ int module_id = -1;
1358 int relative_paths = 0;
1359 int human_readable = 0;
1360 int module_dirlen = 0;
1361 +int force_change = 0;
1362 int preserve_times = 0;
1363 int preserve_xattrs = 0;
1364 mode_t orig_umask = 002;
1365 @@ -90,3 +91,27 @@ struct filter_list_struct daemon_filter_list;
1370 +#if defined SUPPORT_FILEFLAGS || defined SUPPORT_FORCE_CHANGE
1371 + uint32 make_mutable(UNUSED(const char *fname), UNUSED(mode_t *mode), UNUSED(uint32 fileflags), UNUSED(uint32 iflags))
1376 + int undo_make_mutable(UNUSED(const char *fname), UNUSED(uint32 fileflags))
1381 + int make_parentdir_mutable(UNUSED(const char *fname), UNUSED(uint32 iflags), UNUSED(char *parent_dirbuf), UNUSED(int parent_dirbuf_size))
1387 +#ifdef SUPPORT_XATTRS
1388 + int x_lstat(UNUSED(const char *fname), UNUSED(STRUCT_STAT *fst), UNUSED(STRUCT_STAT *xst))
1393 diff --git a/util.c b/util.c
1396 @@ -125,7 +125,7 @@ NORETURN void overflow_exit(const char *str)
1398 /* This returns 0 for success, 1 for a symlink if symlink time-setting
1399 * is not possible, or -1 for any other error. */
1400 -int set_modtime(const char *fname, time_t modtime, mode_t mode)
1401 +int set_modtime(const char *fname, time_t modtime, mode_t mode, uint32 fileflags)
1403 static int switch_step = 0;
1405 @@ -138,7 +138,7 @@ int set_modtime(const char *fname, time_t modtime, mode_t mode)
1406 switch (switch_step) {
1407 #ifdef HAVE_UTIMENSAT
1409 - if (do_utimensat(fname, modtime, 0) == 0)
1410 + if (do_utimensat(fname, modtime, 0, mode, fileflags) == 0)
1412 if (errno != ENOSYS)
1414 @@ -148,7 +148,7 @@ int set_modtime(const char *fname, time_t modtime, mode_t mode)
1418 - if (do_lutimes(fname, modtime, 0) == 0)
1419 + if (do_lutimes(fname, modtime, 0, mode, fileflags) == 0)
1421 if (errno != ENOSYS)
1423 @@ -167,10 +167,10 @@ int set_modtime(const char *fname, time_t modtime, mode_t mode)
1427 - if (do_utimes(fname, modtime, 0) == 0)
1428 + if (do_utimes(fname, modtime, 0, mode, fileflags) == 0)
1431 - if (do_utime(fname, modtime, 0) == 0)
1432 + if (do_utime(fname, modtime, 0, mode, fileflags) == 0)
1436 diff --git a/xattrs.c b/xattrs.c
1439 @@ -1042,7 +1042,7 @@ int set_stat_xattr(const char *fname, struct file_struct *file, mode_t new_mode)
1440 mode = (fst.st_mode & _S_IFMT) | (fmode & ACCESSPERMS)
1441 | (S_ISDIR(fst.st_mode) ? 0700 : 0600);
1442 if (fst.st_mode != mode)
1443 - do_chmod(fname, mode);
1444 + do_chmod(fname, mode, ST_FLAGS(fst));
1445 if (!IS_DEVICE(fst.st_mode))
1446 fst.st_rdev = 0; /* just in case */