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: d52aeae4e9af689aed4ae6af2b7602552c1383e7
12 diff --git a/Makefile.in b/Makefile.in
15 @@ -43,7 +43,7 @@ popt_OBJS=popt/findme.o popt/popt.o popt/poptconfig.o \
16 popt/popthelp.o popt/poptparse.o
17 OBJS=$(OBJS1) $(OBJS2) $(OBJS3) $(DAEMON_OBJ) $(LIBOBJ) $(ZLIBOBJ) @BUILD_POPT@
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 @@ -111,7 +111,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 @@ -42,9 +42,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 @@ -62,7 +64,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 @@ -139,6 +141,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 @@ -589,7 +589,7 @@ AC_CHECK_FUNCS(waitpid wait4 getcwd strdup chown chmod lchmod mknod mkfifo \
70 setlocale setmode open64 lseek64 mkstemp64 mtrace va_copy __va_copy \
71 seteuid strerror putenv iconv_open locale_charset nl_langinfo getxattr \
72 extattr_get_link sigaction sigprocmask setattrlist getgrouplist \
73 - initgroups utimensat)
74 + initgroups utimensat chflags)
76 dnl cygwin iconv.h defines iconv_open as libiconv_open
77 if test x"$ac_cv_func_iconv_open" != x"yes"; then
78 diff --git a/delete.c b/delete.c
83 extern int make_backups;
84 extern int max_delete;
85 +extern int force_change;
86 extern char *backup_dir;
87 extern char *backup_suffix;
88 extern int backup_suffix_len;
89 @@ -97,8 +98,12 @@ static enum delret delete_dir_contents(char *fname, uint16 flags)
92 strlcpy(p, fp->basename, remainder);
93 +#ifdef SUPPORT_FORCE_CHANGE
95 + make_mutable(fname, fp->mode, F_FFLAGS(fp), force_change);
97 if (!(fp->mode & S_IWUSR) && !am_root && fp->flags & FLAG_OWNED_BY_US)
98 - do_chmod(fname, fp->mode | S_IWUSR);
99 + do_chmod(fname, fp->mode | S_IWUSR, NO_FFLAGS);
100 /* Save stack by recursing to ourself directly. */
101 if (S_ISDIR(fp->mode)) {
102 if (delete_dir_contents(fname, flags | DEL_RECURSE) != DR_SUCCESS)
103 @@ -139,11 +144,18 @@ enum delret delete_item(char *fbuf, uint16 mode, uint16 flags)
106 if (flags & DEL_NO_UID_WRITE)
107 - do_chmod(fbuf, mode | S_IWUSR);
108 + do_chmod(fbuf, mode | S_IWUSR, NO_FFLAGS);
110 if (S_ISDIR(mode) && !(flags & DEL_DIR_IS_EMPTY)) {
111 /* This only happens on the first call to delete_item() since
112 * delete_dir_contents() always calls us w/DEL_DIR_IS_EMPTY. */
113 +#ifdef SUPPORT_FORCE_CHANGE
114 + if (force_change) {
116 + if (x_lstat(fbuf, &st, NULL) == 0)
117 + make_mutable(fbuf, st.st_mode, st.st_flags, force_change);
120 ignore_perishable = 1;
121 /* If DEL_RECURSE is not set, this just reports emptiness. */
122 ret = delete_dir_contents(fbuf, flags);
123 diff --git a/flist.c b/flist.c
126 @@ -50,6 +50,7 @@ extern int preserve_links;
127 extern int preserve_hard_links;
128 extern int preserve_devices;
129 extern int preserve_specials;
130 +extern int preserve_fileflags;
131 extern int delete_during;
132 extern int missing_args;
133 extern int eol_nulls;
134 @@ -406,6 +407,9 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
136 static time_t modtime;
138 +#ifdef SUPPORT_FILEFLAGS
139 + static uint32 fileflags;
141 #ifdef SUPPORT_HARD_LINKS
144 @@ -449,6 +453,14 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
145 xflags |= XMIT_SAME_MODE;
148 +#ifdef SUPPORT_FILEFLAGS
149 + if (preserve_fileflags) {
150 + if (F_FFLAGS(file) == fileflags)
151 + xflags |= XMIT_SAME_FLAGS;
153 + fileflags = F_FFLAGS(file);
157 if (preserve_devices && IS_DEVICE(mode)) {
158 if (protocol_version < 28) {
159 @@ -590,6 +602,10 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
160 write_varint(f, F_MOD_NSEC(file));
161 if (!(xflags & XMIT_SAME_MODE))
162 write_int(f, to_wire_mode(mode));
163 +#ifdef SUPPORT_FILEFLAGS
164 + if (preserve_fileflags && !(xflags & XMIT_SAME_FLAGS))
165 + write_int(f, (int)fileflags);
167 if (preserve_uid && !(xflags & XMIT_SAME_UID)) {
168 if (protocol_version < 30)
170 @@ -679,6 +695,9 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x
172 static int64 modtime;
174 +#ifdef SUPPORT_FILEFLAGS
175 + static uint32 fileflags;
177 #ifdef SUPPORT_HARD_LINKS
180 @@ -783,6 +802,10 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x
181 modtime = first->modtime;
182 modtime_nsec = F_MOD_NSEC(first);
184 +#ifdef SUPPORT_FILEFLAGS
185 + if (preserve_fileflags)
186 + fileflags = F_FFLAGS(first);
189 uid = F_OWNER(first);
191 @@ -824,6 +847,10 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x
193 if (chmod_modes && !S_ISLNK(mode) && mode)
194 mode = tweak_mode(mode, chmod_modes);
195 +#ifdef SUPPORT_FILEFLAGS
196 + if (preserve_fileflags && !(xflags & XMIT_SAME_FLAGS))
197 + fileflags = (uint32)read_int(f);
200 if (preserve_uid && !(xflags & XMIT_SAME_UID)) {
201 if (protocol_version < 30)
202 @@ -975,6 +1002,10 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x
206 +#ifdef SUPPORT_FILEFLAGS
207 + if (preserve_fileflags)
208 + F_FFLAGS(file) = fileflags;
213 @@ -1372,6 +1403,10 @@ struct file_struct *make_file(const char *fname, struct file_list *flist,
216 file->mode = st.st_mode;
217 +#if defined SUPPORT_FILEFLAGS || defined SUPPORT_FORCE_CHANGE
219 + F_FFLAGS(file) = st.st_flags;
222 F_OWNER(file) = st.st_uid;
224 diff --git a/generator.c b/generator.c
227 @@ -42,8 +42,10 @@ extern int preserve_devices;
228 extern int preserve_specials;
229 extern int preserve_hard_links;
230 extern int preserve_executability;
231 +extern int preserve_fileflags;
232 extern int preserve_perms;
233 extern int preserve_times;
234 +extern int force_change;
235 extern int delete_mode;
236 extern int delete_before;
237 extern int delete_during;
238 @@ -460,6 +462,10 @@ int unchanged_attrs(const char *fname, struct file_struct *file, stat_x *sxp)
240 if (perms_differ(file, sxp))
242 +#ifdef SUPPORT_FILEFLAGS
243 + if (preserve_fileflags && sxp->st.st_flags != F_FFLAGS(file))
246 if (ownership_differs(file, sxp))
249 @@ -511,6 +517,11 @@ void itemize(const char *fnamecmp, struct file_struct *file, int ndx, int statre
250 if (gid_ndx && !(file->flags & FLAG_SKIP_GROUP)
251 && sxp->st.st_gid != (gid_t)F_GROUP(file))
252 iflags |= ITEM_REPORT_GROUP;
253 +#ifdef SUPPORT_FILEFLAGS
254 + if (preserve_fileflags && !S_ISLNK(file->mode)
255 + && sxp->st.st_flags != F_FFLAGS(file))
256 + iflags |= ITEM_REPORT_FFLAGS;
259 if (preserve_acls && !S_ISLNK(file->mode)) {
260 if (!ACL_READY(*sxp))
261 @@ -1320,6 +1331,10 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
262 file->mode = dest_mode(file->mode, sx.st.st_mode,
263 dflt_perms, statret == 0);
265 +#ifdef SUPPORT_FORCE_CHANGE
266 + if (force_change && !preserve_fileflags)
267 + F_FFLAGS(file) = sx.st.st_flags;
269 if (statret != 0 && basis_dir[0] != NULL) {
270 int j = try_dests_non(file, fname, ndx, fnamecmpbuf, &sx,
272 @@ -1362,10 +1377,15 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
273 /* We need to ensure that the dirs in the transfer have writable
274 * permissions during the time we are putting files within them.
275 * This is then fixed after the transfer is done. */
276 +#ifdef SUPPORT_FORCE_CHANGE
277 + if (force_change && F_FFLAGS(file) & force_change
278 + && make_mutable(fname, file->mode, F_FFLAGS(file), force_change))
279 + need_retouch_dir_perms = 1;
282 if (!am_root && !(file->mode & S_IWUSR) && dir_tweaking) {
283 mode_t mode = file->mode | S_IWUSR;
284 - if (do_chmod(fname, mode) < 0) {
285 + if (do_chmod(fname, mode, 0) < 0) {
286 rsyserr(FERROR_XFER, errno,
287 "failed to modify permissions on %s",
289 @@ -1400,6 +1420,10 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
290 file->mode = dest_mode(file->mode, sx.st.st_mode, dflt_perms,
293 +#ifdef SUPPORT_FORCE_CHANGE
294 + if (force_change && !preserve_fileflags)
295 + F_FFLAGS(file) = sx.st.st_flags;
298 #ifdef SUPPORT_HARD_LINKS
299 if (preserve_hard_links && F_HLINK_NOT_FIRST(file)
300 @@ -1979,13 +2003,17 @@ static void touch_up_dirs(struct file_list *flist, int ndx)
302 fname = f_name(file, NULL);
304 - do_chmod(fname, file->mode);
305 + do_chmod(fname, file->mode, 0);
306 if (need_retouch_dir_times) {
308 if (link_stat(fname, &st, 0) == 0
309 && cmp_time(st.st_mtime, file->modtime) != 0)
310 - set_modtime(fname, file->modtime, F_MOD_NSEC(file), file->mode);
311 + set_modtime(fname, file->modtime, F_MOD_NSEC(file), file->mode, 0);
313 +#ifdef SUPPORT_FORCE_CHANGE
314 + if (force_change && F_FFLAGS(file) & force_change)
315 + undo_make_mutable(fname, F_FFLAGS(file));
317 if (counter >= loopchk_limit) {
319 maybe_send_keepalive(time(NULL), MSK_ALLOW_FLUSH);
320 diff --git a/log.c b/log.c
323 @@ -731,7 +731,7 @@ static void log_formatted(enum logcode code, const char *format, const char *op,
324 c[5] = !(iflags & ITEM_REPORT_PERMS) ? '.' : 'p';
325 c[6] = !(iflags & ITEM_REPORT_OWNER) ? '.' : 'o';
326 c[7] = !(iflags & ITEM_REPORT_GROUP) ? '.' : 'g';
327 - c[8] = !(iflags & ITEM_REPORT_ATIME) ? '.' : 'u';
328 + c[8] = !(iflags & ITEM_REPORT_FFLAGS) ? '.' : 'f';
329 c[9] = !(iflags & ITEM_REPORT_ACL) ? '.' : 'a';
330 c[10] = !(iflags & ITEM_REPORT_XATTR) ? '.' : 'x';
332 diff --git a/options.c b/options.c
335 @@ -53,6 +53,7 @@ int preserve_hard_links = 0;
336 int preserve_acls = 0;
337 int preserve_xattrs = 0;
338 int preserve_perms = 0;
339 +int preserve_fileflags = 0;
340 int preserve_executability = 0;
341 int preserve_devices = 0;
342 int preserve_specials = 0;
343 @@ -86,6 +87,7 @@ int numeric_ids = 0;
345 int allow_8bit_chars = 0;
346 int force_delete = 0;
347 +int force_change = 0;
349 int prune_empty_dirs = 0;
351 @@ -568,6 +570,7 @@ static void print_rsync_version(enum logcode f)
352 char const *links = "no ";
353 char const *iconv = "no ";
354 char const *ipv6 = "no ";
355 + char const *fileflags = "no ";
356 STRUCT_STAT *dumstat;
358 #if SUBPROTOCOL_VERSION != 0
359 @@ -601,6 +604,9 @@ static void print_rsync_version(enum logcode f)
360 #ifdef CAN_SET_SYMLINK_TIMES
363 +#ifdef SUPPORT_FILEFLAGS
367 rprintf(f, "%s version %s protocol version %d%s\n",
368 RSYNC_NAME, RSYNC_VERSION, PROTOCOL_VERSION, subprotocol);
369 @@ -614,8 +620,8 @@ static void print_rsync_version(enum logcode f)
370 (int)(sizeof (int64) * 8));
371 rprintf(f, " %ssocketpairs, %shardlinks, %ssymlinks, %sIPv6, batchfiles, %sinplace,\n",
372 got_socketpair, hardlinks, links, ipv6, have_inplace);
373 - rprintf(f, " %sappend, %sACLs, %sxattrs, %siconv, %ssymtimes\n",
374 - have_inplace, acls, xattrs, iconv, symtimes);
375 + rprintf(f, " %sappend, %sACLs, %sxattrs, %siconv, %ssymtimes, %sfile-flags\n",
376 + have_inplace, acls, xattrs, iconv, symtimes, fileflags);
378 #ifdef MAINTAINER_MODE
379 rprintf(f, "Panic Action: \"%s\"\n", get_panic_action());
380 @@ -685,6 +691,9 @@ void usage(enum logcode F)
381 rprintf(F," -K, --keep-dirlinks treat symlinked dir on receiver as dir\n");
382 rprintf(F," -H, --hard-links preserve hard links\n");
383 rprintf(F," -p, --perms preserve permissions\n");
384 +#ifdef SUPPORT_FILEFLAGS
385 + rprintf(F," --fileflags preserve file-flags (aka chflags)\n");
387 rprintf(F," -E, --executability preserve the file's executability\n");
388 rprintf(F," --chmod=CHMOD affect file and/or directory permissions\n");
390 @@ -725,7 +734,12 @@ void usage(enum logcode F)
391 rprintf(F," --ignore-missing-args ignore missing source args without error\n");
392 rprintf(F," --delete-missing-args delete missing source args from destination\n");
393 rprintf(F," --ignore-errors delete even if there are I/O errors\n");
394 - rprintf(F," --force force deletion of directories even if not empty\n");
395 + rprintf(F," --force-delete force deletion of directories even if not empty\n");
396 +#ifdef SUPPORT_FORCE_CHANGE
397 + rprintf(F," --force-change affect user-/system-immutable files/dirs\n");
398 + rprintf(F," --force-uchange affect user-immutable files/dirs\n");
399 + rprintf(F," --force-schange affect system-immutable files/dirs\n");
401 rprintf(F," --max-delete=NUM don't delete more than NUM files\n");
402 rprintf(F," --max-size=SIZE don't transfer any file larger than SIZE\n");
403 rprintf(F," --min-size=SIZE don't transfer any file smaller than SIZE\n");
404 @@ -838,6 +852,10 @@ static struct poptOption long_options[] = {
405 {"perms", 'p', POPT_ARG_VAL, &preserve_perms, 1, 0, 0 },
406 {"no-perms", 0, POPT_ARG_VAL, &preserve_perms, 0, 0, 0 },
407 {"no-p", 0, POPT_ARG_VAL, &preserve_perms, 0, 0, 0 },
408 +#ifdef SUPPORT_FILEFLAGS
409 + {"fileflags", 0, POPT_ARG_VAL, &preserve_fileflags, 1, 0, 0 },
410 + {"no-fileflags", 0, POPT_ARG_VAL, &preserve_fileflags, 0, 0, 0 },
412 {"executability", 'E', POPT_ARG_NONE, &preserve_executability, 0, 0, 0 },
413 {"acls", 'A', POPT_ARG_NONE, 0, 'A', 0, 0 },
414 {"no-acls", 0, POPT_ARG_VAL, &preserve_acls, 0, 0, 0 },
415 @@ -923,6 +941,14 @@ static struct poptOption long_options[] = {
416 {"remove-source-files",0,POPT_ARG_VAL, &remove_source_files, 1, 0, 0 },
417 {"force", 0, POPT_ARG_VAL, &force_delete, 1, 0, 0 },
418 {"no-force", 0, POPT_ARG_VAL, &force_delete, 0, 0, 0 },
419 + {"force-delete", 0, POPT_ARG_VAL, &force_delete, 1, 0, 0 },
420 + {"no-force-delete", 0, POPT_ARG_VAL, &force_delete, 0, 0, 0 },
421 +#ifdef SUPPORT_FORCE_CHANGE
422 + {"force-change", 0, POPT_ARG_VAL, &force_change, ALL_IMMUTABLE, 0, 0 },
423 + {"no-force-change", 0, POPT_ARG_VAL, &force_change, 0, 0, 0 },
424 + {"force-uchange", 0, POPT_ARG_VAL, &force_change, USR_IMMUTABLE, 0, 0 },
425 + {"force-schange", 0, POPT_ARG_VAL, &force_change, SYS_IMMUTABLE, 0, 0 },
427 {"ignore-errors", 0, POPT_ARG_VAL, &ignore_errors, 1, 0, 0 },
428 {"no-ignore-errors", 0, POPT_ARG_VAL, &ignore_errors, 0, 0, 0 },
429 {"max-delete", 0, POPT_ARG_INT, &max_delete, 0, 0, 0 },
430 @@ -2450,6 +2476,9 @@ void server_options(char **args, int *argc_p)
431 if (xfer_dirs && !recurse && delete_mode && am_sender)
432 args[ac++] = "--no-r";
434 + if (preserve_fileflags)
435 + args[ac++] = "--fileflags";
437 if (do_compression && def_compress_level != Z_DEFAULT_COMPRESSION) {
438 if (asprintf(&arg, "--compress-level=%d", def_compress_level) < 0)
440 @@ -2537,6 +2566,16 @@ void server_options(char **args, int *argc_p)
441 args[ac++] = "--delete-excluded";
443 args[ac++] = "--force";
444 +#ifdef SUPPORT_FORCE_CHANGE
445 + if (force_change) {
446 + if (force_change == ALL_IMMUTABLE)
447 + args[ac++] = "--force-change";
448 + else if (force_change == USR_IMMUTABLE)
449 + args[ac++] = "--force-uchange";
450 + else if (force_change == SYS_IMMUTABLE)
451 + args[ac++] = "--force-schange";
455 args[ac++] = "--only-write-batch=X";
457 diff --git a/rsync.c b/rsync.c
460 @@ -31,6 +31,7 @@ extern int dry_run;
461 extern int preserve_acls;
462 extern int preserve_xattrs;
463 extern int preserve_perms;
464 +extern int preserve_fileflags;
465 extern int preserve_executability;
466 extern int preserve_times;
468 @@ -443,6 +444,39 @@ mode_t dest_mode(mode_t flist_mode, mode_t stat_mode, int dflt_perms,
472 +#if defined SUPPORT_FILEFLAGS || defined SUPPORT_FORCE_CHANGE
473 +/* Set a file's st_flags. */
474 +static int set_fileflags(const char *fname, uint32 fileflags)
476 + if (do_chflags(fname, fileflags) != 0) {
477 + rsyserr(FERROR_XFER, errno,
478 + "failed to set file flags on %s",
479 + full_fname(fname));
486 +/* Remove immutable flags from an object, so it can be altered/removed. */
487 +int make_mutable(const char *fname, mode_t mode, uint32 fileflags, uint32 iflags)
489 + if (S_ISLNK(mode) || !(fileflags & iflags))
491 + if (!set_fileflags(fname, fileflags & ~iflags))
496 +/* Undo a prior make_mutable() call that returned a 1. */
497 +int undo_make_mutable(const char *fname, uint32 fileflags)
499 + if (!set_fileflags(fname, fileflags))
505 int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
506 const char *fnamecmp, int flags)
508 @@ -493,7 +527,7 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
509 flags |= ATTRS_SKIP_MTIME;
510 if (!(flags & ATTRS_SKIP_MTIME)
511 && cmp_time(sxp->st.st_mtime, file->modtime) != 0) {
512 - int ret = set_modtime(fname, file->modtime, F_MOD_NSEC(file), sxp->st.st_mode);
513 + int ret = set_modtime(fname, file->modtime, F_MOD_NSEC(file), sxp->st.st_mode, ST_FLAGS(sxp->st));
515 rsyserr(FERROR_XFER, errno, "failed to set times on %s",
517 @@ -529,7 +563,7 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
519 uid_t uid = change_uid ? (uid_t)F_OWNER(file) : sxp->st.st_uid;
520 gid_t gid = change_gid ? (gid_t)F_GROUP(file) : sxp->st.st_gid;
521 - if (do_lchown(fname, uid, gid) != 0) {
522 + if (do_lchown(fname, uid, gid, sxp->st.st_mode, ST_FLAGS(sxp->st)) != 0) {
523 /* We shouldn't have attempted to change uid
524 * or gid unless have the privilege. */
525 rsyserr(FERROR_XFER, errno, "%s %s failed",
526 @@ -567,7 +601,7 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
529 if (!BITS_EQUAL(sxp->st.st_mode, new_mode, CHMOD_BITS)) {
530 - int ret = am_root < 0 ? 0 : do_chmod(fname, new_mode);
531 + int ret = am_root < 0 ? 0 : do_chmod(fname, new_mode, ST_FLAGS(sxp->st));
533 rsyserr(FERROR_XFER, errno,
534 "failed to set permissions on %s",
535 @@ -579,6 +613,19 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
539 +#ifdef SUPPORT_FILEFLAGS
540 + if (preserve_fileflags && !S_ISLNK(sxp->st.st_mode)
541 + && sxp->st.st_flags != F_FFLAGS(file)) {
542 + uint32 fileflags = F_FFLAGS(file);
543 + if (flags & ATTRS_DELAY_IMMUTABLE)
544 + fileflags &= ~ALL_IMMUTABLE;
545 + if (sxp->st.st_flags != fileflags
546 + && !set_fileflags(fname, fileflags))
552 if (INFO_GTE(NAME, 2) && flags & ATTRS_REPORT) {
554 rprintf(FCLIENT, "%s\n", fname);
555 @@ -643,7 +690,8 @@ int finish_transfer(const char *fname, const char *fnametmp,
557 /* Change permissions before putting the file into place. */
558 set_file_attrs(fnametmp, file, NULL, fnamecmp,
559 - ok_to_set_time ? 0 : ATTRS_SKIP_MTIME);
560 + ATTRS_DELAY_IMMUTABLE
561 + | (ok_to_set_time ? 0 : ATTRS_SKIP_MTIME));
563 /* move tmp file over real file */
564 if (DEBUG_GTE(RECV, 1))
565 @@ -660,6 +708,10 @@ int finish_transfer(const char *fname, const char *fnametmp,
568 /* The file was moved into place (not copied), so it's done. */
569 +#ifdef SUPPORT_FILEFLAGS
570 + if (preserve_fileflags && F_FFLAGS(file) & ALL_IMMUTABLE)
571 + set_fileflags(fname, F_FFLAGS(file));
575 /* The file was copied, so tweak the perms of the copied file. If it
576 diff --git a/rsync.h b/rsync.h
580 #define XMIT_HLINK_FIRST (1<<12) /* protocols 30 - now (HLINKED files only) */
581 #define XMIT_IO_ERROR_ENDLIST (1<<12) /* protocols 31*- now (w/XMIT_EXTENDED_FLAGS) (also protocol 30 w/'f' compat flag) */
582 #define XMIT_MOD_NSEC (1<<13) /* protocols 31 - now */
583 +#define XMIT_SAME_FLAGS (1<<14) /* protocols ?? - now */
585 /* These flags are used in the live flist data. */
589 #define ATTRS_REPORT (1<<0)
590 #define ATTRS_SKIP_MTIME (1<<1)
591 +#define ATTRS_DELAY_IMMUTABLE (1<<2)
594 #define NORMAL_FLUSH 0
596 #define ITEM_REPORT_GROUP (1<<6)
597 #define ITEM_REPORT_ACL (1<<7)
598 #define ITEM_REPORT_XATTR (1<<8)
599 +#define ITEM_REPORT_FFLAGS (1<<9)
600 #define ITEM_BASIS_TYPE_FOLLOWS (1<<11)
601 #define ITEM_XNAME_FOLLOWS (1<<12)
602 #define ITEM_IS_NEW (1<<13)
603 @@ -513,6 +516,28 @@ typedef unsigned int size_t;
607 +#define NO_FFLAGS ((uint32)-1)
610 +#define SUPPORT_FILEFLAGS 1
611 +#define SUPPORT_FORCE_CHANGE 1
614 +#if defined SUPPORT_FILEFLAGS || defined SUPPORT_FORCE_CHANGE
616 +#define UF_NOUNLINK 0
619 +#define SF_NOUNLINK 0
621 +#define USR_IMMUTABLE (UF_IMMUTABLE|UF_NOUNLINK|UF_APPEND)
622 +#define SYS_IMMUTABLE (SF_IMMUTABLE|SF_NOUNLINK|SF_APPEND)
623 +#define ALL_IMMUTABLE (USR_IMMUTABLE|SYS_IMMUTABLE)
624 +#define ST_FLAGS(st) (st.st_flags)
626 +#define ST_FLAGS(st) NO_FFLAGS
629 /* Find a variable that is either exactly 32-bits or longer.
630 * If some code depends on 32-bit truncation, it will need to
631 * take special action in a "#if SIZEOF_INT32 > 4" section. */
632 @@ -683,6 +708,7 @@ extern int file_extra_cnt;
633 extern int inc_recurse;
636 +extern int fileflags_ndx;
638 extern int xattrs_ndx;
640 @@ -724,6 +750,11 @@ extern int xattrs_ndx;
641 /* When the associated option is on, all entries will have these present: */
642 #define F_OWNER(f) REQ_EXTRA(f, uid_ndx)->unum
643 #define F_GROUP(f) REQ_EXTRA(f, gid_ndx)->unum
644 +#if defined SUPPORT_FILEFLAGS || defined SUPPORT_FORCE_CHANGE
645 +#define F_FFLAGS(f) REQ_EXTRA(f, fileflags_ndx)->unum
647 +#define F_FFLAGS(f) NO_FFLAGS
649 #define F_ACL(f) REQ_EXTRA(f, acls_ndx)->num
650 #define F_XATTR(f) REQ_EXTRA(f, xattrs_ndx)->num
651 #define F_NDX(f) REQ_EXTRA(f, unsort_ndx)->num
652 diff --git a/rsync.yo b/rsync.yo
655 @@ -345,6 +345,7 @@ to the detailed description below for a complete description. verb(
656 -K, --keep-dirlinks treat symlinked dir on receiver as dir
657 -H, --hard-links preserve hard links
658 -p, --perms preserve permissions
659 + --fileflags preserve file-flags (aka chflags)
660 -E, --executability preserve executability
661 --chmod=CHMOD affect file and/or directory permissions
662 -A, --acls preserve ACLs (implies -p)
663 @@ -379,7 +380,10 @@ to the detailed description below for a complete description. verb(
664 --ignore-missing-args ignore missing source args without error
665 --delete-missing-args delete missing source args from destination
666 --ignore-errors delete even if there are I/O errors
667 - --force force deletion of dirs even if not empty
668 + --force-delete force deletion of dirs even if not empty
669 + --force-change affect user/system immutable files/dirs
670 + --force-uchange affect user-immutable files/dirs
671 + --force-schange affect system-immutable files/dirs
672 --max-delete=NUM don't delete more than NUM files
673 --max-size=SIZE don't transfer any file larger than SIZE
674 --min-size=SIZE don't transfer any file smaller than SIZE
675 @@ -596,7 +600,8 @@ specified, in which case bf(-r) is not implied.
677 Note that bf(-a) bf(does not preserve hardlinks), because
678 finding multiply-linked files is expensive. You must separately
680 +specify bf(-H). Note also that for backward compatibility, bf(-a)
681 +currently does bf(not) imply the bf(--fileflags) option.
683 dit(--no-OPTION) You may turn off one or more implied options by prefixing
684 the option name with "no-". Not all options may be prefixed with a "no-":
685 @@ -895,7 +900,7 @@ they would be using bf(--copy-links).
686 Without this option, if the sending side has replaced a directory with a
687 symlink to a directory, the receiving side will delete anything that is in
688 the way of the new symlink, including a directory hierarchy (as long as
689 -bf(--force) or bf(--delete) is in effect).
690 +bf(--force-delete) or bf(--delete) is in effect).
692 See also bf(--keep-dirlinks) for an analogous option for the receiving
694 @@ -1058,6 +1063,29 @@ Note that this option does not copy rsyncs special xattr values (e.g. those
695 used by bf(--fake-super)) unless you repeat the option (e.g. -XX). This
696 "copy all xattrs" mode cannot be used with bf(--fake-super).
698 +dit(bf(--fileflags)) This option causes rsync to update the file-flags to be
699 +the same as the source files and directories (if your OS supports the
700 +bf(chflags)(2) system call). Some flags can only be altered by the super-user
701 +and some might only be unset below a certain secure-level (usually single-user
702 +mode). It will not make files alterable that are set to immutable on the
703 +receiver. To do that, see bf(--force-change), bf(--force-uchange), and
704 +bf(--force-schange).
706 +dit(bf(--force-change)) This option causes rsync to disable both user-immutable
707 +and system-immutable flags on files and directories that are being updated or
708 +deleted on the receiving side. This option overrides bf(--force-uchange) and
709 +bf(--force-schange).
711 +dit(bf(--force-uchange)) This option causes rsync to disable user-immutable
712 +flags on files and directories that are being updated or deleted on the
713 +receiving side. It does not try to affect system flags. This option overrides
714 +bf(--force-change) and bf(--force-schange).
716 +dit(bf(--force-schange)) This option causes rsync to disable system-immutable
717 +flags on files and directories that are being updated or deleted on the
718 +receiving side. It does not try to affect user flags. This option overrides
719 +bf(--force-change) and bf(--force-schange).
721 dit(bf(--chmod)) This option tells rsync to apply one or more
722 comma-separated "chmod" modes to the permission of the files in the
723 transfer. The resulting value is treated as though it were the permissions
724 @@ -1352,12 +1380,13 @@ display as a "*missing" entry in the bf(--list-only) output.
725 dit(bf(--ignore-errors)) Tells bf(--delete) to go ahead and delete files
726 even when there are I/O errors.
728 -dit(bf(--force)) This option tells rsync to delete a non-empty directory
729 +dit(bf(--force-delete)) This option tells rsync to delete a non-empty directory
730 when it is to be replaced by a non-directory. This is only relevant if
731 deletions are not active (see bf(--delete) for details).
733 -Note for older rsync versions: bf(--force) used to still be required when
734 -using bf(--delete-after), and it used to be non-functional unless the
735 +This option can be abbreviated bf(--force) for backward compatibility.
736 +Note that some older rsync versions used to still require bf(--force)
737 +when using bf(--delete-after), and it used to be non-functional unless the
738 bf(--recursive) option was also enabled.
740 dit(bf(--max-delete=NUM)) This tells rsync not to delete more than NUM
741 @@ -1951,7 +1980,7 @@ with older versions of rsync, but that also turns on the output of other
744 The "%i" escape has a cryptic output that is 11 letters long. The general
745 -format is like the string bf(YXcstpoguax), where bf(Y) is replaced by the
746 +format is like the string bf(YXcstpogfax), where bf(Y) is replaced by the
747 type of update being done, bf(X) is replaced by the file-type, and the
748 other letters represent attributes that may be output if they are being
750 @@ -2007,7 +2036,7 @@ quote(itemization(
751 sender's value (requires bf(--owner) and super-user privileges).
752 it() A bf(g) means the group is different and is being updated to the
753 sender's value (requires bf(--group) and the authority to set the group).
754 - it() The bf(u) slot is reserved for future use.
755 + it() The bf(f) means that the fileflags information changed.
756 it() The bf(a) means that the ACL information changed.
757 it() The bf(x) means that the extended attribute information changed.
759 diff --git a/syscall.c b/syscall.c
762 @@ -34,6 +34,7 @@ extern int am_root;
763 extern int am_sender;
764 extern int read_only;
765 extern int list_only;
766 +extern int force_change;
767 extern int preserve_perms;
768 extern int preserve_executability;
770 @@ -51,7 +52,23 @@ int do_unlink(const char *fname)
772 if (dry_run) return 0;
773 RETURN_ERROR_IF_RO_OR_LO;
774 - return unlink(fname);
775 + if (unlink(fname) == 0)
777 +#ifdef SUPPORT_FORCE_CHANGE
778 + if (force_change && errno == EPERM) {
781 + if (x_lstat(fname, &st, NULL) == 0
782 + && make_mutable(fname, st.st_mode, st.st_flags, force_change) > 0) {
783 + if (unlink(fname) == 0)
785 + undo_make_mutable(fname, st.st_flags);
787 + /* TODO: handle immutable directories */
795 @@ -112,14 +129,37 @@ int do_link(const char *fname1, const char *fname2)
799 -int do_lchown(const char *path, uid_t owner, gid_t group)
800 +int do_lchown(const char *path, uid_t owner, gid_t group, mode_t mode, uint32 fileflags)
802 if (dry_run) return 0;
803 RETURN_ERROR_IF_RO_OR_LO;
807 - return lchown(path, owner, group);
808 + if (lchown(path, owner, group) == 0)
810 +#ifdef SUPPORT_FORCE_CHANGE
811 + if (force_change && errno == EPERM) {
812 + if (fileflags == NO_FFLAGS) {
814 + if (x_lstat(path, &st, NULL) == 0) {
816 + fileflags = st.st_flags;
819 + if (fileflags != NO_FFLAGS
820 + && make_mutable(path, mode, fileflags, force_change) > 0) {
821 + int ret = lchown(path, owner, group);
822 + undo_make_mutable(path, fileflags);
829 + mode = fileflags = 0; /* avoid compiler warning */
834 int do_mknod(const char *pathname, mode_t mode, dev_t dev)
835 @@ -159,7 +199,7 @@ int do_mknod(const char *pathname, mode_t mode, dev_t dev)
839 - return do_chmod(pathname, mode);
840 + return do_chmod(pathname, mode, 0);
844 @@ -176,7 +216,22 @@ int do_rmdir(const char *pathname)
846 if (dry_run) return 0;
847 RETURN_ERROR_IF_RO_OR_LO;
848 - return rmdir(pathname);
849 + if (rmdir(pathname) == 0)
851 +#ifdef SUPPORT_FORCE_CHANGE
852 + if (force_change && errno == EPERM) {
855 + if (x_lstat(pathname, &st, NULL) == 0
856 + && make_mutable(pathname, st.st_mode, st.st_flags, force_change) > 0) {
857 + if (rmdir(pathname) == 0)
859 + undo_make_mutable(pathname, st.st_flags);
867 int do_open(const char *pathname, int flags, mode_t mode)
868 @@ -190,7 +245,7 @@ int do_open(const char *pathname, int flags, mode_t mode)
872 -int do_chmod(const char *path, mode_t mode)
873 +int do_chmod(const char *path, mode_t mode, uint32 fileflags)
876 if (dry_run) return 0;
877 @@ -213,17 +268,74 @@ int do_chmod(const char *path, mode_t mode)
879 code = chmod(path, mode & CHMOD_BITS); /* DISCOURAGED FUNCTION */
880 #endif /* !HAVE_LCHMOD */
881 +#ifdef SUPPORT_FORCE_CHANGE
882 + if (code < 0 && force_change && errno == EPERM && !S_ISLNK(mode)) {
883 + if (fileflags == NO_FFLAGS) {
885 + if (x_lstat(path, &st, NULL) == 0)
886 + fileflags = st.st_flags;
888 + if (fileflags != NO_FFLAGS
889 + && make_mutable(path, mode, fileflags, force_change) > 0) {
890 + code = chmod(path, mode & CHMOD_BITS);
891 + undo_make_mutable(path, fileflags);
898 + fileflags = 0; /* avoid compiler warning */
900 if (code != 0 && (preserve_perms || preserve_executability))
907 +int do_chflags(const char *path, uint32 fileflags)
909 + if (dry_run) return 0;
910 + RETURN_ERROR_IF_RO_OR_LO;
911 + return chflags(path, fileflags);
915 int do_rename(const char *fname1, const char *fname2)
917 if (dry_run) return 0;
918 RETURN_ERROR_IF_RO_OR_LO;
919 - return rename(fname1, fname2);
920 + if (rename(fname1, fname2) == 0)
922 +#ifdef SUPPORT_FORCE_CHANGE
923 + if (force_change && errno == EPERM) {
924 + STRUCT_STAT st1, st2;
925 + int became_mutable;
927 + if (x_lstat(fname1, &st1, NULL) != 0)
929 + became_mutable = make_mutable(fname1, st1.st_mode, st1.st_flags, force_change) > 0;
930 + if (became_mutable && rename(fname1, fname2) == 0)
932 + if (x_lstat(fname2, &st2, NULL) == 0
933 + && make_mutable(fname2, st2.st_mode, st2.st_flags, force_change) > 0) {
934 + if (rename(fname1, fname2) == 0) {
936 + if (became_mutable) /* Yes, use fname2 and st1! */
937 + undo_make_mutable(fname2, st1.st_flags);
940 + undo_make_mutable(fname2, st2.st_flags);
942 + /* TODO: handle immutable directories */
943 + if (became_mutable)
944 + undo_make_mutable(fname1, st1.st_flags);
952 #ifdef HAVE_FTRUNCATE
953 diff --git a/t_stub.c b/t_stub.c
956 @@ -25,6 +25,7 @@ int modify_window = 0;
958 int relative_paths = 0;
959 int module_dirlen = 0;
960 +int force_change = 0;
961 int preserve_times = 0;
962 int preserve_xattrs = 0;
963 char number_separator = ',';
964 @@ -84,3 +85,23 @@ filter_rule_list daemon_filter_list;
969 +#if defined SUPPORT_FILEFLAGS || defined SUPPORT_FORCE_CHANGE
970 + int make_mutable(UNUSED(const char *fname), UNUSED(mode_t mode), UNUSED(uint32 fileflags), UNUSED(uint32 iflags))
975 +/* Undo a prior make_mutable() call that returned a 1. */
976 + int undo_make_mutable(UNUSED(const char *fname), UNUSED(uint32 fileflags))
982 +#ifdef SUPPORT_XATTRS
983 + int x_lstat(UNUSED(const char *fname), UNUSED(STRUCT_STAT *fst), UNUSED(STRUCT_STAT *xst))
988 diff --git a/util.c b/util.c
991 @@ -30,6 +30,7 @@ extern int modify_window;
992 extern int relative_paths;
993 extern int preserve_times;
994 extern int preserve_xattrs;
995 +extern int force_change;
996 extern char *module_dir;
997 extern unsigned int module_dirlen;
998 extern char *partial_dir;
999 @@ -122,9 +123,34 @@ NORETURN void overflow_exit(const char *str)
1000 exit_cleanup(RERR_MALLOC);
1003 +#ifdef SUPPORT_FORCE_CHANGE
1004 +static int try_a_force_change(const char *fname, time_t modtime, uint32 mod_nsec, mode_t mode, uint32 fileflags)
1006 + if (fileflags == NO_FFLAGS) {
1008 + if (x_lstat(fname, &st, NULL) == 0)
1009 + fileflags = st.st_flags;
1012 + if (fileflags != NO_FFLAGS && make_mutable(fname, mode, fileflags, force_change) > 0) {
1013 + int save_force_change = force_change;
1015 + force_change = 0; /* Make certain we can't come back here. */
1016 + ret = set_modtime(fname, modtime, mod_nsec, mode, fileflags);
1017 + force_change = save_force_change;
1019 + undo_make_mutable(fname, fileflags);
1028 /* This returns 0 for success, 1 for a symlink if symlink time-setting
1029 * is not possible, or -1 for any other error. */
1030 -int set_modtime(const char *fname, time_t modtime, uint32 mod_nsec, mode_t mode)
1031 +int set_modtime(const char *fname, time_t modtime, uint32 mod_nsec, mode_t mode, uint32 fileflags)
1033 static int switch_step = 0;
1035 @@ -139,6 +165,11 @@ int set_modtime(const char *fname, time_t modtime, uint32 mod_nsec, mode_t mode)
1037 if (do_utimensat(fname, modtime, mod_nsec) == 0)
1039 +#ifdef SUPPORT_FORCE_CHANGE
1040 + if (force_change && errno == EPERM
1041 + && try_a_force_change(fname, modtime, mod_nsec, mode, fileflags) == 0)
1044 if (errno != ENOSYS)
1047 @@ -149,6 +180,11 @@ int set_modtime(const char *fname, time_t modtime, uint32 mod_nsec, mode_t mode)
1049 if (do_lutimes(fname, modtime, mod_nsec) == 0)
1051 +#ifdef SUPPORT_FORCE_CHANGE
1052 + if (force_change && errno == EPERM
1053 + && try_a_force_change(fname, modtime, mod_nsec, mode, fileflags) == 0)
1056 if (errno != ENOSYS)
1059 @@ -172,6 +208,13 @@ int set_modtime(const char *fname, time_t modtime, uint32 mod_nsec, mode_t mode)
1060 if (do_utime(fname, modtime, mod_nsec) == 0)
1063 +#ifdef SUPPORT_FORCE_CHANGE
1064 + if (force_change && errno == EPERM
1065 + && try_a_force_change(fname, modtime, mod_nsec, mode, fileflags) == 0)
1068 + fileflags = 0; /* avoid compiler warning */
1073 diff --git a/xattrs.c b/xattrs.c
1076 @@ -1042,7 +1042,7 @@ int set_stat_xattr(const char *fname, struct file_struct *file, mode_t new_mode)
1077 mode = (fst.st_mode & _S_IFMT) | (fmode & ACCESSPERMS)
1078 | (S_ISDIR(fst.st_mode) ? 0700 : 0600);
1079 if (fst.st_mode != mode)
1080 - do_chmod(fname, mode);
1081 + do_chmod(fname, mode, ST_FLAGS(fst));
1082 if (!IS_DEVICE(fst.st_mode))
1083 fst.st_rdev = 0; /* just in case */