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: 2a87d78f693f10fe5ad13af0bb9311bd3714077d
12 diff --git a/Makefile.in b/Makefile.in
15 @@ -46,7 +46,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) @BUILD_ZLIB@ @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 @@ -129,7 +129,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 @@ -45,9 +45,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 @@ -65,7 +67,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 @@ -144,6 +146,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 @@ -598,6 +598,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/delete.c b/delete.c
82 extern int make_backups;
83 extern int max_delete;
84 +extern int force_change;
85 extern char *backup_dir;
86 extern char *backup_suffix;
87 extern int backup_suffix_len;
88 @@ -97,8 +98,12 @@ static enum delret delete_dir_contents(char *fname, uint16 flags)
91 strlcpy(p, fp->basename, remainder);
92 +#ifdef SUPPORT_FORCE_CHANGE
94 + make_mutable(fname, fp->mode, F_FFLAGS(fp), force_change);
96 if (!(fp->mode & S_IWUSR) && !am_root && fp->flags & FLAG_OWNED_BY_US)
97 - do_chmod(fname, fp->mode | S_IWUSR);
98 + do_chmod(fname, fp->mode | S_IWUSR, NO_FFLAGS);
99 /* Save stack by recursing to ourself directly. */
100 if (S_ISDIR(fp->mode)) {
101 if (delete_dir_contents(fname, flags | DEL_RECURSE) != DR_SUCCESS)
102 @@ -139,11 +144,18 @@ enum delret delete_item(char *fbuf, uint16 mode, uint16 flags)
105 if (flags & DEL_NO_UID_WRITE)
106 - do_chmod(fbuf, mode | S_IWUSR);
107 + do_chmod(fbuf, mode | S_IWUSR, NO_FFLAGS);
109 if (S_ISDIR(mode) && !(flags & DEL_DIR_IS_EMPTY)) {
110 /* This only happens on the first call to delete_item() since
111 * delete_dir_contents() always calls us w/DEL_DIR_IS_EMPTY. */
112 +#ifdef SUPPORT_FORCE_CHANGE
113 + if (force_change) {
115 + if (x_lstat(fbuf, &st, NULL) == 0)
116 + make_mutable(fbuf, st.st_mode, st.st_flags, force_change);
119 ignore_perishable = 1;
120 /* If DEL_RECURSE is not set, this just reports emptiness. */
121 ret = delete_dir_contents(fbuf, flags);
122 diff --git a/flist.c b/flist.c
125 @@ -52,6 +52,7 @@ extern int preserve_links;
126 extern int preserve_hard_links;
127 extern int preserve_devices;
128 extern int preserve_specials;
129 +extern int preserve_fileflags;
130 extern int delete_during;
131 extern int missing_args;
132 extern int eol_nulls;
133 @@ -381,6 +382,9 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
135 static time_t modtime;
137 +#ifdef SUPPORT_FILEFLAGS
138 + static uint32 fileflags;
140 #ifdef SUPPORT_HARD_LINKS
143 @@ -424,6 +428,14 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
144 xflags |= XMIT_SAME_MODE;
147 +#ifdef SUPPORT_FILEFLAGS
148 + if (preserve_fileflags) {
149 + if (F_FFLAGS(file) == fileflags)
150 + xflags |= XMIT_SAME_FLAGS;
152 + fileflags = F_FFLAGS(file);
156 if (preserve_devices && IS_DEVICE(mode)) {
157 if (protocol_version < 28) {
158 @@ -565,6 +577,10 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
159 write_varint(f, F_MOD_NSEC(file));
160 if (!(xflags & XMIT_SAME_MODE))
161 write_int(f, to_wire_mode(mode));
162 +#ifdef SUPPORT_FILEFLAGS
163 + if (preserve_fileflags && !(xflags & XMIT_SAME_FLAGS))
164 + write_int(f, (int)fileflags);
166 if (preserve_uid && !(xflags & XMIT_SAME_UID)) {
167 if (protocol_version < 30)
169 @@ -654,6 +670,9 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x
171 static int64 modtime;
173 +#ifdef SUPPORT_FILEFLAGS
174 + static uint32 fileflags;
176 #ifdef SUPPORT_HARD_LINKS
179 @@ -761,6 +780,10 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x
180 modtime = first->modtime;
181 modtime_nsec = F_MOD_NSEC_or_0(first);
183 +#ifdef SUPPORT_FILEFLAGS
184 + if (preserve_fileflags)
185 + fileflags = F_FFLAGS(first);
188 uid = F_OWNER(first);
190 @@ -802,6 +825,10 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x
192 if (chmod_modes && !S_ISLNK(mode) && mode)
193 mode = tweak_mode(mode, chmod_modes);
194 +#ifdef SUPPORT_FILEFLAGS
195 + if (preserve_fileflags && !(xflags & XMIT_SAME_FLAGS))
196 + fileflags = (uint32)read_int(f);
199 if (preserve_uid && !(xflags & XMIT_SAME_UID)) {
200 if (protocol_version < 30)
201 @@ -960,6 +987,10 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x
205 +#ifdef SUPPORT_FILEFLAGS
206 + if (preserve_fileflags)
207 + F_FFLAGS(file) = fileflags;
212 @@ -1357,6 +1388,10 @@ struct file_struct *make_file(const char *fname, struct file_list *flist,
215 file->mode = st.st_mode;
216 +#if defined SUPPORT_FILEFLAGS || defined SUPPORT_FORCE_CHANGE
218 + F_FFLAGS(file) = st.st_flags;
221 F_OWNER(file) = st.st_uid;
223 diff --git a/generator.c b/generator.c
226 @@ -43,8 +43,10 @@ extern int write_devices;
227 extern int preserve_specials;
228 extern int preserve_hard_links;
229 extern int preserve_executability;
230 +extern int preserve_fileflags;
231 extern int preserve_perms;
232 extern int preserve_times;
233 +extern int force_change;
234 extern int delete_mode;
235 extern int delete_before;
236 extern int delete_during;
237 @@ -471,6 +473,10 @@ int unchanged_attrs(const char *fname, struct file_struct *file, stat_x *sxp)
239 if (perms_differ(file, sxp))
241 +#ifdef SUPPORT_FILEFLAGS
242 + if (preserve_fileflags && sxp->st.st_flags != F_FFLAGS(file))
245 if (ownership_differs(file, sxp))
248 @@ -522,6 +528,11 @@ void itemize(const char *fnamecmp, struct file_struct *file, int ndx, int statre
249 if (gid_ndx && !(file->flags & FLAG_SKIP_GROUP)
250 && sxp->st.st_gid != (gid_t)F_GROUP(file))
251 iflags |= ITEM_REPORT_GROUP;
252 +#ifdef SUPPORT_FILEFLAGS
253 + if (preserve_fileflags && !S_ISLNK(file->mode)
254 + && sxp->st.st_flags != F_FFLAGS(file))
255 + iflags |= ITEM_REPORT_FFLAGS;
258 if (preserve_acls && !S_ISLNK(file->mode)) {
259 if (!ACL_READY(*sxp))
260 @@ -1411,6 +1422,10 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
261 file->mode = dest_mode(file->mode, sx.st.st_mode,
262 dflt_perms, statret == 0);
264 +#ifdef SUPPORT_FORCE_CHANGE
265 + if (force_change && !preserve_fileflags)
266 + F_FFLAGS(file) = sx.st.st_flags;
268 if (statret != 0 && basis_dir[0] != NULL) {
269 int j = try_dests_non(file, fname, ndx, fnamecmpbuf, &sx,
271 @@ -1455,10 +1470,15 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
272 * readable and writable permissions during the time we are
273 * putting files within them. This is then restored to the
274 * former permissions after the transfer is done. */
275 +#ifdef SUPPORT_FORCE_CHANGE
276 + if (force_change && F_FFLAGS(file) & force_change
277 + && make_mutable(fname, file->mode, F_FFLAGS(file), force_change))
278 + need_retouch_dir_perms = 1;
281 if (!am_root && (file->mode & S_IRWXU) != S_IRWXU && dir_tweaking) {
282 mode_t mode = file->mode | S_IRWXU;
283 - if (do_chmod(fname, mode) < 0) {
284 + if (do_chmod(fname, mode, 0) < 0) {
285 rsyserr(FERROR_XFER, errno,
286 "failed to modify permissions on %s",
288 @@ -1494,6 +1514,10 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
289 file->mode = dest_mode(file->mode, sx.st.st_mode, dflt_perms,
292 +#ifdef SUPPORT_FORCE_CHANGE
293 + if (force_change && !preserve_fileflags)
294 + F_FFLAGS(file) = sx.st.st_flags;
297 #ifdef SUPPORT_HARD_LINKS
298 if (preserve_hard_links && F_HLINK_NOT_FIRST(file)
299 @@ -2061,12 +2085,16 @@ static void touch_up_dirs(struct file_list *flist, int ndx)
301 fname = f_name(file, NULL);
303 - do_chmod(fname, file->mode);
304 + do_chmod(fname, file->mode, 0);
305 if (need_retouch_dir_times) {
307 if (link_stat(fname, &st, 0) == 0 && time_diff(&st, file))
308 - set_modtime(fname, file->modtime, F_MOD_NSEC_or_0(file), file->mode);
309 + set_modtime(fname, file->modtime, F_MOD_NSEC_or_0(file), file->mode, 0);
311 +#ifdef SUPPORT_FORCE_CHANGE
312 + if (force_change && F_FFLAGS(file) & force_change)
313 + undo_make_mutable(fname, F_FFLAGS(file));
315 if (counter >= loopchk_limit) {
317 maybe_send_keepalive(time(NULL), MSK_ALLOW_FLUSH);
318 diff --git a/log.c b/log.c
321 @@ -716,7 +716,7 @@ static void log_formatted(enum logcode code, const char *format, const char *op,
322 c[5] = !(iflags & ITEM_REPORT_PERMS) ? '.' : 'p';
323 c[6] = !(iflags & ITEM_REPORT_OWNER) ? '.' : 'o';
324 c[7] = !(iflags & ITEM_REPORT_GROUP) ? '.' : 'g';
325 - c[8] = !(iflags & ITEM_REPORT_ATIME) ? '.' : 'u';
326 + c[8] = !(iflags & ITEM_REPORT_FFLAGS) ? '.' : 'f';
327 c[9] = !(iflags & ITEM_REPORT_ACL) ? '.' : 'a';
328 c[10] = !(iflags & ITEM_REPORT_XATTR) ? '.' : 'x';
330 diff --git a/main.c b/main.c
334 #if defined CONFIG_LOCALE && defined HAVE_LOCALE_H
337 +#ifdef SUPPORT_FORCE_CHANGE
338 +#include <sys/sysctl.h>
342 extern int list_only;
343 @@ -52,6 +55,7 @@ extern int copy_unsafe_links;
344 extern int keep_dirlinks;
345 extern int preserve_hard_links;
346 extern int protocol_version;
347 +extern int force_change;
348 extern int file_total;
350 extern int xfer_dirs;
351 @@ -944,6 +948,22 @@ static int do_recv(int f_in, int f_out, char *local_name)
352 * points to an identical file won't be replaced by the referent. */
353 copy_links = copy_dirlinks = copy_unsafe_links = 0;
355 +#ifdef SUPPORT_FORCE_CHANGE
356 + if (force_change & SYS_IMMUTABLE) {
357 + /* Determine whether we'll be able to unlock a system immutable item. */
359 + int securityLevel = 0;
360 + size_t len = sizeof securityLevel;
363 + mib[1] = KERN_SECURELVL;
364 + if (sysctl(mib, 2, &securityLevel, &len, NULL, 0) == 0 && securityLevel > 0) {
365 + rprintf(FERROR, "System security level is too high to force mutability on system immutable files and directories.\n");
366 + exit_cleanup(RERR_UNSUPPORTED);
371 #ifdef SUPPORT_HARD_LINKS
372 if (preserve_hard_links && !inc_recurse)
373 match_hard_links(first_flist);
374 diff --git a/options.c b/options.c
377 @@ -57,6 +57,7 @@ int preserve_hard_links = 0;
378 int preserve_acls = 0;
379 int preserve_xattrs = 0;
380 int preserve_perms = 0;
381 +int preserve_fileflags = 0;
382 int preserve_executability = 0;
383 int preserve_devices = 0;
384 int preserve_specials = 0;
385 @@ -91,6 +92,7 @@ int numeric_ids = 0;
387 int allow_8bit_chars = 0;
388 int force_delete = 0;
389 +int force_change = 0;
391 int prune_empty_dirs = 0;
393 @@ -576,6 +578,7 @@ static void print_rsync_version(enum logcode f)
394 char const *links = "no ";
395 char const *iconv = "no ";
396 char const *ipv6 = "no ";
397 + char const *fileflags = "no ";
398 STRUCT_STAT *dumstat;
400 #if SUBPROTOCOL_VERSION != 0
401 @@ -612,6 +615,9 @@ static void print_rsync_version(enum logcode f)
402 #ifdef CAN_SET_SYMLINK_TIMES
405 +#ifdef SUPPORT_FILEFLAGS
409 rprintf(f, "%s version %s protocol version %d%s\n",
410 RSYNC_NAME, RSYNC_VERSION, PROTOCOL_VERSION, subprotocol);
411 @@ -625,8 +631,8 @@ static void print_rsync_version(enum logcode f)
412 (int)(sizeof (int64) * 8));
413 rprintf(f, " %ssocketpairs, %shardlinks, %ssymlinks, %sIPv6, batchfiles, %sinplace,\n",
414 got_socketpair, hardlinks, links, ipv6, have_inplace);
415 - rprintf(f, " %sappend, %sACLs, %sxattrs, %siconv, %ssymtimes, %sprealloc\n",
416 - have_inplace, acls, xattrs, iconv, symtimes, prealloc);
417 + rprintf(f, " %sappend, %sACLs, %sxattrs, %siconv, %ssymtimes, %sprealloc, %sfile-flags\n",
418 + have_inplace, acls, xattrs, iconv, symtimes, prealloc, fileflags);
420 #ifdef MAINTAINER_MODE
421 rprintf(f, "Panic Action: \"%s\"\n", get_panic_action());
422 @@ -697,6 +703,9 @@ void usage(enum logcode F)
423 rprintf(F," -K, --keep-dirlinks treat symlinked dir on receiver as dir\n");
424 rprintf(F," -H, --hard-links preserve hard links\n");
425 rprintf(F," -p, --perms preserve permissions\n");
426 +#ifdef SUPPORT_FILEFLAGS
427 + rprintf(F," --fileflags preserve file-flags (aka chflags)\n");
429 rprintf(F," -E, --executability preserve the file's executability\n");
430 rprintf(F," --chmod=CHMOD affect file and/or directory permissions\n");
432 @@ -744,7 +753,12 @@ void usage(enum logcode F)
433 rprintf(F," --ignore-missing-args ignore missing source args without error\n");
434 rprintf(F," --delete-missing-args delete missing source args from destination\n");
435 rprintf(F," --ignore-errors delete even if there are I/O errors\n");
436 - rprintf(F," --force force deletion of directories even if not empty\n");
437 + rprintf(F," --force-delete force deletion of directories even if not empty\n");
438 +#ifdef SUPPORT_FORCE_CHANGE
439 + rprintf(F," --force-change affect user-/system-immutable files/dirs\n");
440 + rprintf(F," --force-uchange affect user-immutable files/dirs\n");
441 + rprintf(F," --force-schange affect system-immutable files/dirs\n");
443 rprintf(F," --max-delete=NUM don't delete more than NUM files\n");
444 rprintf(F," --max-size=SIZE don't transfer any file larger than SIZE\n");
445 rprintf(F," --min-size=SIZE don't transfer any file smaller than SIZE\n");
446 @@ -862,6 +876,10 @@ static struct poptOption long_options[] = {
447 {"perms", 'p', POPT_ARG_VAL, &preserve_perms, 1, 0, 0 },
448 {"no-perms", 0, POPT_ARG_VAL, &preserve_perms, 0, 0, 0 },
449 {"no-p", 0, POPT_ARG_VAL, &preserve_perms, 0, 0, 0 },
450 +#ifdef SUPPORT_FILEFLAGS
451 + {"fileflags", 0, POPT_ARG_VAL, &preserve_fileflags, 1, 0, 0 },
452 + {"no-fileflags", 0, POPT_ARG_VAL, &preserve_fileflags, 0, 0, 0 },
454 {"executability", 'E', POPT_ARG_NONE, &preserve_executability, 0, 0, 0 },
455 {"acls", 'A', POPT_ARG_NONE, 0, 'A', 0, 0 },
456 {"no-acls", 0, POPT_ARG_VAL, &preserve_acls, 0, 0, 0 },
457 @@ -950,6 +968,14 @@ static struct poptOption long_options[] = {
458 {"remove-source-files",0,POPT_ARG_VAL, &remove_source_files, 1, 0, 0 },
459 {"force", 0, POPT_ARG_VAL, &force_delete, 1, 0, 0 },
460 {"no-force", 0, POPT_ARG_VAL, &force_delete, 0, 0, 0 },
461 + {"force-delete", 0, POPT_ARG_VAL, &force_delete, 1, 0, 0 },
462 + {"no-force-delete", 0, POPT_ARG_VAL, &force_delete, 0, 0, 0 },
463 +#ifdef SUPPORT_FORCE_CHANGE
464 + {"force-change", 0, POPT_ARG_VAL, &force_change, ALL_IMMUTABLE, 0, 0 },
465 + {"no-force-change", 0, POPT_ARG_VAL, &force_change, 0, 0, 0 },
466 + {"force-uchange", 0, POPT_ARG_VAL, &force_change, USR_IMMUTABLE, 0, 0 },
467 + {"force-schange", 0, POPT_ARG_VAL, &force_change, SYS_IMMUTABLE, 0, 0 },
469 {"ignore-errors", 0, POPT_ARG_VAL, &ignore_errors, 1, 0, 0 },
470 {"no-ignore-errors", 0, POPT_ARG_VAL, &ignore_errors, 0, 0, 0 },
471 {"max-delete", 0, POPT_ARG_INT, &max_delete, 0, 0, 0 },
472 @@ -2595,6 +2621,9 @@ void server_options(char **args, int *argc_p)
473 if (xfer_dirs && !recurse && delete_mode && am_sender)
474 args[ac++] = "--no-r";
476 + if (preserve_fileflags)
477 + args[ac++] = "--fileflags";
479 if (do_compression && def_compress_level != Z_DEFAULT_COMPRESSION) {
480 if (asprintf(&arg, "--compress-level=%d", def_compress_level) < 0)
482 @@ -2688,6 +2717,16 @@ void server_options(char **args, int *argc_p)
483 args[ac++] = "--delete-excluded";
485 args[ac++] = "--force";
486 +#ifdef SUPPORT_FORCE_CHANGE
487 + if (force_change) {
488 + if (force_change == ALL_IMMUTABLE)
489 + args[ac++] = "--force-change";
490 + else if (force_change == USR_IMMUTABLE)
491 + args[ac++] = "--force-uchange";
492 + else if (force_change == SYS_IMMUTABLE)
493 + args[ac++] = "--force-schange";
497 args[ac++] = "--only-write-batch=X";
499 diff --git a/rsync.c b/rsync.c
502 @@ -31,6 +31,7 @@ extern int dry_run;
503 extern int preserve_acls;
504 extern int preserve_xattrs;
505 extern int preserve_perms;
506 +extern int preserve_fileflags;
507 extern int preserve_executability;
508 extern int preserve_times;
510 @@ -459,6 +460,39 @@ mode_t dest_mode(mode_t flist_mode, mode_t stat_mode, int dflt_perms,
514 +#if defined SUPPORT_FILEFLAGS || defined SUPPORT_FORCE_CHANGE
515 +/* Set a file's st_flags. */
516 +static int set_fileflags(const char *fname, uint32 fileflags)
518 + if (do_chflags(fname, fileflags) != 0) {
519 + rsyserr(FERROR_XFER, errno,
520 + "failed to set file flags on %s",
521 + full_fname(fname));
528 +/* Remove immutable flags from an object, so it can be altered/removed. */
529 +int make_mutable(const char *fname, mode_t mode, uint32 fileflags, uint32 iflags)
531 + if (S_ISLNK(mode) || !(fileflags & iflags))
533 + if (!set_fileflags(fname, fileflags & ~iflags))
538 +/* Undo a prior make_mutable() call that returned a 1. */
539 +int undo_make_mutable(const char *fname, uint32 fileflags)
541 + if (!set_fileflags(fname, fileflags))
547 int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
548 const char *fnamecmp, int flags)
550 @@ -520,7 +554,7 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
552 uid_t uid = change_uid ? (uid_t)F_OWNER(file) : sxp->st.st_uid;
553 gid_t gid = change_gid ? (gid_t)F_GROUP(file) : sxp->st.st_gid;
554 - if (do_lchown(fname, uid, gid) != 0) {
555 + if (do_lchown(fname, uid, gid, sxp->st.st_mode, ST_FLAGS(sxp->st)) != 0) {
556 /* We shouldn't have attempted to change uid
557 * or gid unless have the privilege. */
558 rsyserr(FERROR_XFER, errno, "%s %s failed",
559 @@ -560,7 +594,7 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
560 || (flags & ATTRS_SET_NANO && NSEC_BUMP(file) && (uint32)sxp->st.ST_MTIME_NSEC != F_MOD_NSEC(file))
563 - int ret = set_modtime(fname, file->modtime, F_MOD_NSEC_or_0(file), sxp->st.st_mode);
564 + int ret = set_modtime(fname, file->modtime, F_MOD_NSEC_or_0(file), sxp->st.st_mode, ST_FLAGS(sxp->st));
566 rsyserr(FERROR_XFER, errno, "failed to set times on %s",
568 @@ -587,7 +621,7 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
571 if (!BITS_EQUAL(sxp->st.st_mode, new_mode, CHMOD_BITS)) {
572 - int ret = am_root < 0 ? 0 : do_chmod(fname, new_mode);
573 + int ret = am_root < 0 ? 0 : do_chmod(fname, new_mode, ST_FLAGS(sxp->st));
575 rsyserr(FERROR_XFER, errno,
576 "failed to set permissions on %s",
577 @@ -599,6 +633,19 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
581 +#ifdef SUPPORT_FILEFLAGS
582 + if (preserve_fileflags && !S_ISLNK(sxp->st.st_mode)
583 + && sxp->st.st_flags != F_FFLAGS(file)) {
584 + uint32 fileflags = F_FFLAGS(file);
585 + if (flags & ATTRS_DELAY_IMMUTABLE)
586 + fileflags &= ~ALL_IMMUTABLE;
587 + if (sxp->st.st_flags != fileflags
588 + && !set_fileflags(fname, fileflags))
594 if (INFO_GTE(NAME, 2) && flags & ATTRS_REPORT) {
596 rprintf(FCLIENT, "%s\n", fname);
597 @@ -676,7 +723,8 @@ int finish_transfer(const char *fname, const char *fnametmp,
599 /* Change permissions before putting the file into place. */
600 set_file_attrs(fnametmp, file, NULL, fnamecmp,
601 - ok_to_set_time ? ATTRS_SET_NANO : ATTRS_SKIP_MTIME);
602 + ATTRS_DELAY_IMMUTABLE
603 + | (ok_to_set_time ? ATTRS_SET_NANO : ATTRS_SKIP_MTIME));
605 /* move tmp file over real file */
606 if (DEBUG_GTE(RECV, 1))
607 @@ -693,6 +741,10 @@ int finish_transfer(const char *fname, const char *fnametmp,
610 /* The file was moved into place (not copied), so it's done. */
611 +#ifdef SUPPORT_FILEFLAGS
612 + if (preserve_fileflags && F_FFLAGS(file) & ALL_IMMUTABLE)
613 + set_fileflags(fname, F_FFLAGS(file));
617 /* The file was copied, so tweak the perms of the copied file. If it
618 diff --git a/rsync.h b/rsync.h
622 #define XMIT_HLINK_FIRST (1<<12) /* protocols 30 - now (HLINKED files only) */
623 #define XMIT_IO_ERROR_ENDLIST (1<<12) /* protocols 31*- now (w/XMIT_EXTENDED_FLAGS) (also protocol 30 w/'f' compat flag) */
624 #define XMIT_MOD_NSEC (1<<13) /* protocols 31 - now */
625 +#define XMIT_SAME_FLAGS (1<<14) /* protocols ?? - now */
627 /* These flags are used in the live flist data. */
630 #define ATTRS_REPORT (1<<0)
631 #define ATTRS_SKIP_MTIME (1<<1)
632 #define ATTRS_SET_NANO (1<<2)
633 +#define ATTRS_DELAY_IMMUTABLE (1<<4)
636 #define NORMAL_FLUSH 0
638 #define ITEM_REPORT_GROUP (1<<6)
639 #define ITEM_REPORT_ACL (1<<7)
640 #define ITEM_REPORT_XATTR (1<<8)
641 +#define ITEM_REPORT_FFLAGS (1<<9)
642 #define ITEM_BASIS_TYPE_FOLLOWS (1<<11)
643 #define ITEM_XNAME_FOLLOWS (1<<12)
644 #define ITEM_IS_NEW (1<<13)
645 @@ -531,6 +534,28 @@ typedef unsigned int size_t;
649 +#define NO_FFLAGS ((uint32)-1)
652 +#define SUPPORT_FILEFLAGS 1
653 +#define SUPPORT_FORCE_CHANGE 1
656 +#if defined SUPPORT_FILEFLAGS || defined SUPPORT_FORCE_CHANGE
658 +#define UF_NOUNLINK 0
661 +#define SF_NOUNLINK 0
663 +#define USR_IMMUTABLE (UF_IMMUTABLE|UF_NOUNLINK|UF_APPEND)
664 +#define SYS_IMMUTABLE (SF_IMMUTABLE|SF_NOUNLINK|SF_APPEND)
665 +#define ALL_IMMUTABLE (USR_IMMUTABLE|SYS_IMMUTABLE)
666 +#define ST_FLAGS(st) (st.st_flags)
668 +#define ST_FLAGS(st) NO_FFLAGS
671 /* Find a variable that is either exactly 32-bits or longer.
672 * If some code depends on 32-bit truncation, it will need to
673 * take special action in a "#if SIZEOF_INT32 > 4" section. */
674 @@ -718,6 +743,7 @@ extern int file_extra_cnt;
675 extern int inc_recurse;
678 +extern int fileflags_ndx;
680 extern int xattrs_ndx;
682 @@ -760,6 +786,11 @@ extern int xattrs_ndx;
683 /* When the associated option is on, all entries will have these present: */
684 #define F_OWNER(f) REQ_EXTRA(f, uid_ndx)->unum
685 #define F_GROUP(f) REQ_EXTRA(f, gid_ndx)->unum
686 +#if defined SUPPORT_FILEFLAGS || defined SUPPORT_FORCE_CHANGE
687 +#define F_FFLAGS(f) REQ_EXTRA(f, fileflags_ndx)->unum
689 +#define F_FFLAGS(f) NO_FFLAGS
691 #define F_ACL(f) REQ_EXTRA(f, acls_ndx)->num
692 #define F_XATTR(f) REQ_EXTRA(f, xattrs_ndx)->num
693 #define F_NDX(f) REQ_EXTRA(f, unsort_ndx)->num
694 diff --git a/rsync.yo b/rsync.yo
697 @@ -368,6 +368,7 @@ to the detailed description below for a complete description. verb(
698 -K, --keep-dirlinks treat symlinked dir on receiver as dir
699 -H, --hard-links preserve hard links
700 -p, --perms preserve permissions
701 + --fileflags preserve file-flags (aka chflags)
702 -E, --executability preserve executability
703 --chmod=CHMOD affect file and/or directory permissions
704 -A, --acls preserve ACLs (implies -p)
705 @@ -405,7 +406,10 @@ to the detailed description below for a complete description. verb(
706 --ignore-missing-args ignore missing source args without error
707 --delete-missing-args delete missing source args from destination
708 --ignore-errors delete even if there are I/O errors
709 - --force force deletion of dirs even if not empty
710 + --force-delete force deletion of dirs even if not empty
711 + --force-change affect user/system immutable files/dirs
712 + --force-uchange affect user-immutable files/dirs
713 + --force-schange affect system-immutable files/dirs
714 --max-delete=NUM don't delete more than NUM files
715 --max-size=SIZE don't transfer any file larger than SIZE
716 --min-size=SIZE don't transfer any file smaller than SIZE
717 @@ -666,7 +670,8 @@ specified, in which case bf(-r) is not implied.
719 Note that bf(-a) bf(does not preserve hardlinks), because
720 finding multiply-linked files is expensive. You must separately
722 +specify bf(-H). Note also that for backward compatibility, bf(-a)
723 +currently does bf(not) imply the bf(--fileflags) option.
725 dit(--no-OPTION) You may turn off one or more implied options by prefixing
726 the option name with "no-". Not all options may be prefixed with a "no-":
727 @@ -979,7 +984,7 @@ they would be using bf(--copy-links).
728 Without this option, if the sending side has replaced a directory with a
729 symlink to a directory, the receiving side will delete anything that is in
730 the way of the new symlink, including a directory hierarchy (as long as
731 -bf(--force) or bf(--delete) is in effect).
732 +bf(--force-delete) or bf(--delete) is in effect).
734 See also bf(--keep-dirlinks) for an analogous option for the receiving
736 @@ -1163,6 +1168,29 @@ Note that the bf(-X) option does not copy rsync's special xattr values (e.g.
737 those used by bf(--fake-super)) unless you repeat the option (e.g. -XX).
738 This "copy all xattrs" mode cannot be used with bf(--fake-super).
740 +dit(bf(--fileflags)) This option causes rsync to update the file-flags to be
741 +the same as the source files and directories (if your OS supports the
742 +bf(chflags)(2) system call). Some flags can only be altered by the super-user
743 +and some might only be unset below a certain secure-level (usually single-user
744 +mode). It will not make files alterable that are set to immutable on the
745 +receiver. To do that, see bf(--force-change), bf(--force-uchange), and
746 +bf(--force-schange).
748 +dit(bf(--force-change)) This option causes rsync to disable both user-immutable
749 +and system-immutable flags on files and directories that are being updated or
750 +deleted on the receiving side. This option overrides bf(--force-uchange) and
751 +bf(--force-schange).
753 +dit(bf(--force-uchange)) This option causes rsync to disable user-immutable
754 +flags on files and directories that are being updated or deleted on the
755 +receiving side. It does not try to affect system flags. This option overrides
756 +bf(--force-change) and bf(--force-schange).
758 +dit(bf(--force-schange)) This option causes rsync to disable system-immutable
759 +flags on files and directories that are being updated or deleted on the
760 +receiving side. It does not try to affect user flags. This option overrides
761 +bf(--force-change) and bf(--force-uchange).
763 dit(bf(--chmod)) This option tells rsync to apply one or more
764 comma-separated "chmod" modes to the permission of the files in the
765 transfer. The resulting value is treated as though it were the permissions
766 @@ -1528,12 +1556,13 @@ display as a "*missing" entry in the bf(--list-only) output.
767 dit(bf(--ignore-errors)) Tells bf(--delete) to go ahead and delete files
768 even when there are I/O errors.
770 -dit(bf(--force)) This option tells rsync to delete a non-empty directory
771 +dit(bf(--force-delete)) This option tells rsync to delete a non-empty directory
772 when it is to be replaced by a non-directory. This is only relevant if
773 deletions are not active (see bf(--delete) for details).
775 -Note for older rsync versions: bf(--force) used to still be required when
776 -using bf(--delete-after), and it used to be non-functional unless the
777 +This option can be abbreviated bf(--force) for backward compatibility.
778 +Note that some older rsync versions used to still require bf(--force)
779 +when using bf(--delete-after), and it used to be non-functional unless the
780 bf(--recursive) option was also enabled.
782 dit(bf(--max-delete=NUM)) This tells rsync not to delete more than NUM
783 @@ -2207,7 +2236,7 @@ with older versions of rsync, but that also turns on the output of other
786 The "%i" escape has a cryptic output that is 11 letters long. The general
787 -format is like the string bf(YXcstpoguax), where bf(Y) is replaced by the
788 +format is like the string bf(YXcstpogfax), where bf(Y) is replaced by the
789 type of update being done, bf(X) is replaced by the file-type, and the
790 other letters represent attributes that may be output if they are being
792 @@ -2263,7 +2292,7 @@ quote(itemization(
793 sender's value (requires bf(--owner) and super-user privileges).
794 it() A bf(g) means the group is different and is being updated to the
795 sender's value (requires bf(--group) and the authority to set the group).
796 - it() The bf(u) slot is reserved for future use.
797 + it() The bf(f) means that the fileflags information changed.
798 it() The bf(a) means that the ACL information changed.
799 it() The bf(x) means that the extended attribute information changed.
801 diff --git a/syscall.c b/syscall.c
804 @@ -38,6 +38,7 @@ extern int am_root;
805 extern int am_sender;
806 extern int read_only;
807 extern int list_only;
808 +extern int force_change;
810 extern int preallocate_files;
811 extern int preserve_perms;
812 @@ -67,7 +68,23 @@ int do_unlink(const char *fname)
814 if (dry_run) return 0;
815 RETURN_ERROR_IF_RO_OR_LO;
816 - return unlink(fname);
817 + if (unlink(fname) == 0)
819 +#ifdef SUPPORT_FORCE_CHANGE
820 + if (force_change && errno == EPERM) {
823 + if (x_lstat(fname, &st, NULL) == 0
824 + && make_mutable(fname, st.st_mode, st.st_flags, force_change) > 0) {
825 + if (unlink(fname) == 0)
827 + undo_make_mutable(fname, st.st_flags);
829 + /* TODO: handle immutable directories */
837 @@ -128,14 +145,37 @@ int do_link(const char *fname1, const char *fname2)
841 -int do_lchown(const char *path, uid_t owner, gid_t group)
842 +int do_lchown(const char *path, uid_t owner, gid_t group, mode_t mode, uint32 fileflags)
844 if (dry_run) return 0;
845 RETURN_ERROR_IF_RO_OR_LO;
849 - return lchown(path, owner, group);
850 + if (lchown(path, owner, group) == 0)
852 +#ifdef SUPPORT_FORCE_CHANGE
853 + if (force_change && errno == EPERM) {
854 + if (fileflags == NO_FFLAGS) {
856 + if (x_lstat(path, &st, NULL) == 0) {
858 + fileflags = st.st_flags;
861 + if (fileflags != NO_FFLAGS
862 + && make_mutable(path, mode, fileflags, force_change) > 0) {
863 + int ret = lchown(path, owner, group);
864 + undo_make_mutable(path, fileflags);
871 + mode = fileflags = 0; /* avoid compiler warning */
876 int do_mknod(const char *pathname, mode_t mode, dev_t dev)
877 @@ -175,7 +215,7 @@ int do_mknod(const char *pathname, mode_t mode, dev_t dev)
881 - return do_chmod(pathname, mode);
882 + return do_chmod(pathname, mode, 0);
886 @@ -192,7 +232,22 @@ int do_rmdir(const char *pathname)
888 if (dry_run) return 0;
889 RETURN_ERROR_IF_RO_OR_LO;
890 - return rmdir(pathname);
891 + if (rmdir(pathname) == 0)
893 +#ifdef SUPPORT_FORCE_CHANGE
894 + if (force_change && errno == EPERM) {
897 + if (x_lstat(pathname, &st, NULL) == 0
898 + && make_mutable(pathname, st.st_mode, st.st_flags, force_change) > 0) {
899 + if (rmdir(pathname) == 0)
901 + undo_make_mutable(pathname, st.st_flags);
909 int do_open(const char *pathname, int flags, mode_t mode)
910 @@ -206,7 +261,7 @@ int do_open(const char *pathname, int flags, mode_t mode)
914 -int do_chmod(const char *path, mode_t mode)
915 +int do_chmod(const char *path, mode_t mode, uint32 fileflags)
918 if (dry_run) return 0;
919 @@ -229,17 +284,74 @@ int do_chmod(const char *path, mode_t mode)
921 code = chmod(path, mode & CHMOD_BITS); /* DISCOURAGED FUNCTION */
922 #endif /* !HAVE_LCHMOD */
923 +#ifdef SUPPORT_FORCE_CHANGE
924 + if (code < 0 && force_change && errno == EPERM && !S_ISLNK(mode)) {
925 + if (fileflags == NO_FFLAGS) {
927 + if (x_lstat(path, &st, NULL) == 0)
928 + fileflags = st.st_flags;
930 + if (fileflags != NO_FFLAGS
931 + && make_mutable(path, mode, fileflags, force_change) > 0) {
932 + code = chmod(path, mode & CHMOD_BITS);
933 + undo_make_mutable(path, fileflags);
940 + fileflags = 0; /* avoid compiler warning */
942 if (code != 0 && (preserve_perms || preserve_executability))
949 +int do_chflags(const char *path, uint32 fileflags)
951 + if (dry_run) return 0;
952 + RETURN_ERROR_IF_RO_OR_LO;
953 + return chflags(path, fileflags);
957 int do_rename(const char *fname1, const char *fname2)
959 if (dry_run) return 0;
960 RETURN_ERROR_IF_RO_OR_LO;
961 - return rename(fname1, fname2);
962 + if (rename(fname1, fname2) == 0)
964 +#ifdef SUPPORT_FORCE_CHANGE
965 + if (force_change && errno == EPERM) {
966 + STRUCT_STAT st1, st2;
967 + int became_mutable;
969 + if (x_lstat(fname1, &st1, NULL) != 0)
971 + became_mutable = make_mutable(fname1, st1.st_mode, st1.st_flags, force_change) > 0;
972 + if (became_mutable && rename(fname1, fname2) == 0)
974 + if (x_lstat(fname2, &st2, NULL) == 0
975 + && make_mutable(fname2, st2.st_mode, st2.st_flags, force_change) > 0) {
976 + if (rename(fname1, fname2) == 0) {
978 + if (became_mutable) /* Yes, use fname2 and st1! */
979 + undo_make_mutable(fname2, st1.st_flags);
982 + undo_make_mutable(fname2, st2.st_flags);
984 + /* TODO: handle immutable directories */
985 + if (became_mutable)
986 + undo_make_mutable(fname1, st1.st_flags);
994 #ifdef HAVE_FTRUNCATE
995 diff --git a/t_stub.c b/t_stub.c
998 @@ -28,6 +28,7 @@ int protect_args = 0;
1000 int relative_paths = 0;
1001 int module_dirlen = 0;
1002 +int force_change = 0;
1003 int preserve_acls = 0;
1004 int preserve_times = 0;
1005 int preserve_xattrs = 0;
1006 @@ -102,3 +103,23 @@ filter_rule_list daemon_filter_list;
1008 return cst || !flg ? 16 : 1;
1011 +#if defined SUPPORT_FILEFLAGS || defined SUPPORT_FORCE_CHANGE
1012 + int make_mutable(UNUSED(const char *fname), UNUSED(mode_t mode), UNUSED(uint32 fileflags), UNUSED(uint32 iflags))
1017 +/* Undo a prior make_mutable() call that returned a 1. */
1018 + int undo_make_mutable(UNUSED(const char *fname), UNUSED(uint32 fileflags))
1024 +#ifdef SUPPORT_XATTRS
1025 + int x_lstat(UNUSED(const char *fname), UNUSED(STRUCT_STAT *fst), UNUSED(STRUCT_STAT *xst))
1030 diff --git a/util.c b/util.c
1033 @@ -33,6 +33,7 @@ extern int relative_paths;
1034 extern int preserve_times;
1035 extern int preserve_xattrs;
1036 extern int preallocate_files;
1037 +extern int force_change;
1038 extern char *module_dir;
1039 extern unsigned int module_dirlen;
1040 extern char *partial_dir;
1041 @@ -115,9 +116,36 @@ void print_child_argv(const char *prefix, char **cmd)
1042 rprintf(FCLIENT, " (%d args)\n", cnt);
1045 +#ifdef SUPPORT_FORCE_CHANGE
1046 +static int try_a_force_change(const char *fname, time_t modtime, uint32 mod_nsec, mode_t mode, uint32 fileflags)
1048 + if (fileflags == NO_FFLAGS) {
1050 + if (x_lstat(fname, &st, NULL) == 0)
1051 + fileflags = st.st_flags;
1054 + if (fileflags != NO_FFLAGS && make_mutable(fname, mode, fileflags, force_change) > 0) {
1055 + int ret, save_force_change = force_change;
1057 + force_change = 0; /* Make certain we can't come back here. */
1058 + ret = set_modtime(fname, modtime, mod_nsec, mode, fileflags);
1059 + force_change = save_force_change;
1061 + undo_make_mutable(fname, fileflags);
1072 /* This returns 0 for success, 1 for a symlink if symlink time-setting
1073 * is not possible, or -1 for any other error. */
1074 -int set_modtime(const char *fname, time_t modtime, uint32 mod_nsec, mode_t mode)
1075 +int set_modtime(const char *fname, time_t modtime, uint32 mod_nsec, mode_t mode, uint32 fileflags)
1077 static int switch_step = 0;
1079 @@ -141,6 +169,11 @@ int set_modtime(const char *fname, time_t modtime, uint32 mod_nsec, mode_t mode)
1081 if (do_utimensat(fname, modtime, mod_nsec) == 0)
1083 +#ifdef SUPPORT_FORCE_CHANGE
1084 + if (force_change && errno == EPERM
1085 + && try_a_force_change(fname, modtime, mod_nsec, mode, fileflags) == 0)
1088 if (errno != ENOSYS)
1091 @@ -150,6 +183,11 @@ int set_modtime(const char *fname, time_t modtime, uint32 mod_nsec, mode_t mode)
1093 if (do_lutimes(fname, modtime, mod_nsec) == 0)
1095 +#ifdef SUPPORT_FORCE_CHANGE
1096 + if (force_change && errno == EPERM
1097 + && try_a_force_change(fname, modtime, mod_nsec, mode, fileflags) == 0)
1100 if (errno != ENOSYS)
1103 @@ -171,6 +209,13 @@ int set_modtime(const char *fname, time_t modtime, uint32 mod_nsec, mode_t mode)
1104 if (do_utime(fname, modtime, mod_nsec) == 0)
1107 +#ifdef SUPPORT_FORCE_CHANGE
1108 + if (force_change && errno == EPERM
1109 + && try_a_force_change(fname, modtime, mod_nsec, mode, fileflags) == 0)
1112 + fileflags = 0; /* avoid compiler warning */
1117 diff --git a/xattrs.c b/xattrs.c
1120 @@ -1224,7 +1224,7 @@ int set_stat_xattr(const char *fname, struct file_struct *file, mode_t new_mode)
1121 mode = (fst.st_mode & _S_IFMT) | (fmode & ACCESSPERMS)
1122 | (S_ISDIR(fst.st_mode) ? 0700 : 0600);
1123 if (fst.st_mode != mode)
1124 - do_chmod(fname, mode);
1125 + do_chmod(fname, mode, ST_FLAGS(fst));
1126 if (!IS_DEVICE(fst.st_mode))
1127 fst.st_rdev = 0; /* just in case */