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 diff --git a/Makefile.in b/Makefile.in
12 index feacb90..cd4cf2d 100644
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 @@ -108,7 +108,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
34 index 6e00072..ef1882d 100644
37 @@ -40,9 +40,11 @@ extern int checksum_seed;
38 extern int basis_dir_cnt;
39 extern int prune_empty_dirs;
40 extern int protocol_version;
41 +extern int force_change;
42 extern int protect_args;
43 extern int preserve_uid;
44 extern int preserve_gid;
45 +extern int preserve_fileflags;
46 extern int preserve_acls;
47 extern int preserve_xattrs;
48 extern int need_messages_from_generator;
49 @@ -60,7 +62,7 @@ extern char *iconv_opt;
52 /* These index values are for the file-list's extra-attribute array. */
53 -int uid_ndx, gid_ndx, acls_ndx, xattrs_ndx, unsort_ndx;
54 +int uid_ndx, gid_ndx, fileflags_ndx, acls_ndx, xattrs_ndx, unsort_ndx;
56 int receiver_symlink_times = 0; /* receiver can set the time on a symlink */
57 int sender_symlink_iconv = 0; /* sender should convert symlink content */
58 @@ -136,6 +138,8 @@ void setup_protocol(int f_out,int f_in)
59 uid_ndx = ++file_extra_cnt;
61 gid_ndx = ++file_extra_cnt;
62 + if (preserve_fileflags || (force_change && !am_sender))
63 + fileflags_ndx = ++file_extra_cnt;
64 if (preserve_acls && !am_sender)
65 acls_ndx = ++file_extra_cnt;
67 diff --git a/configure.in b/configure.in
68 index bc7d4a7..8709fa4 100644
71 @@ -553,7 +553,7 @@ AC_CHECK_FUNCS(waitpid wait4 getcwd strdup chown chmod lchmod mknod mkfifo \
72 setlocale setmode open64 lseek64 mkstemp64 mtrace va_copy __va_copy \
73 strerror putenv iconv_open locale_charset nl_langinfo getxattr \
74 extattr_get_link sigaction sigprocmask setattrlist getgrouplist \
78 dnl cygwin iconv.h defines iconv_open as libiconv_open
79 if test x"$ac_cv_func_iconv_open" != x"yes"; then
80 diff --git a/delete.c b/delete.c
81 index 33fdd0e..1c0df57 100644
86 extern int make_backups;
87 extern int max_delete;
88 +extern int force_change;
89 extern char *backup_dir;
90 extern char *backup_suffix;
91 extern int backup_suffix_len;
92 @@ -98,8 +99,12 @@ static enum delret delete_dir_contents(char *fname, uint16 flags)
95 strlcpy(p, fp->basename, remainder);
96 +#ifdef SUPPORT_FORCE_CHANGE
98 + make_mutable(fname, fp->mode, F_FFLAGS(fp), force_change);
100 if (!(fp->mode & S_IWUSR) && !am_root && (uid_t)F_OWNER(fp) == our_uid)
101 - do_chmod(fname, fp->mode | S_IWUSR);
102 + do_chmod(fname, fp->mode | S_IWUSR, NO_FFLAGS);
103 /* Save stack by recursing to ourself directly. */
104 if (S_ISDIR(fp->mode)) {
105 if (delete_dir_contents(fname, flags | DEL_RECURSE) != DR_SUCCESS)
106 @@ -140,7 +145,7 @@ enum delret delete_item(char *fbuf, uint16 mode, uint16 flags)
109 if (flags & DEL_NO_UID_WRITE)
110 - do_chmod(fbuf, mode | S_IWUSR);
111 + do_chmod(fbuf, mode | S_IWUSR, NO_FFLAGS);
113 if (S_ISDIR(mode) && !(flags & DEL_DIR_IS_EMPTY)) {
114 int save_uid_ndx = uid_ndx;
115 @@ -148,6 +153,13 @@ enum delret delete_item(char *fbuf, uint16 mode, uint16 flags)
116 * delete_dir_contents() always calls us w/DEL_DIR_IS_EMPTY. */
118 uid_ndx = ++file_extra_cnt;
119 +#ifdef SUPPORT_FORCE_CHANGE
120 + if (force_change) {
122 + if (x_lstat(fbuf, &st, NULL) == 0)
123 + make_mutable(fbuf, st.st_mode, st.st_flags, force_change);
126 ignore_perishable = 1;
127 /* If DEL_RECURSE is not set, this just reports emptiness. */
128 ret = delete_dir_contents(fbuf, flags);
129 diff --git a/flist.c b/flist.c
130 index 09b4fc5..e1d01be 100644
133 @@ -51,6 +51,7 @@ extern int preserve_links;
134 extern int preserve_hard_links;
135 extern int preserve_devices;
136 extern int preserve_specials;
137 +extern int preserve_fileflags;
138 extern int missing_args;
141 @@ -398,6 +399,9 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
143 static time_t modtime;
145 +#ifdef SUPPORT_FILEFLAGS
146 + static uint32 fileflags;
148 #ifdef SUPPORT_HARD_LINKS
151 @@ -441,6 +445,14 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
152 xflags |= XMIT_SAME_MODE;
155 +#ifdef SUPPORT_FILEFLAGS
156 + if (preserve_fileflags) {
157 + if (F_FFLAGS(file) == fileflags)
158 + xflags |= XMIT_SAME_FLAGS;
160 + fileflags = F_FFLAGS(file);
164 if ((preserve_devices && IS_DEVICE(mode))
165 || (preserve_specials && IS_SPECIAL(mode))) {
166 @@ -568,6 +580,10 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
168 if (!(xflags & XMIT_SAME_MODE))
169 write_int(f, to_wire_mode(mode));
170 +#ifdef SUPPORT_FILEFLAGS
171 + if (preserve_fileflags && !(xflags & XMIT_SAME_FLAGS))
172 + write_int(f, (int)fileflags);
174 if (preserve_uid && !(xflags & XMIT_SAME_UID)) {
175 if (protocol_version < 30)
177 @@ -656,6 +672,9 @@ static struct file_struct *recv_file_entry(struct file_list *flist,
179 static int64 modtime;
181 +#ifdef SUPPORT_FILEFLAGS
182 + static uint32 fileflags;
184 #ifdef SUPPORT_HARD_LINKS
187 @@ -796,6 +815,10 @@ static struct file_struct *recv_file_entry(struct file_list *flist,
189 if (chmod_modes && !S_ISLNK(mode) && mode)
190 mode = tweak_mode(mode, chmod_modes);
191 +#ifdef SUPPORT_FILEFLAGS
192 + if (preserve_fileflags && !(xflags & XMIT_SAME_FLAGS))
193 + fileflags = (uint32)read_int(f);
196 if (preserve_uid && !(xflags & XMIT_SAME_UID)) {
197 if (protocol_version < 30)
198 @@ -936,6 +959,10 @@ static struct file_struct *recv_file_entry(struct file_list *flist,
202 +#ifdef SUPPORT_FILEFLAGS
203 + if (preserve_fileflags)
204 + F_FFLAGS(file) = fileflags;
209 @@ -1323,6 +1350,10 @@ struct file_struct *make_file(const char *fname, struct file_list *flist,
212 file->mode = st.st_mode;
213 +#if defined SUPPORT_FILEFLAGS || defined SUPPORT_FORCE_CHANGE
215 + F_FFLAGS(file) = st.st_flags;
217 if (uid_ndx) /* Check uid_ndx instead of preserve_uid for del support */
218 F_OWNER(file) = st.st_uid;
219 if (gid_ndx) /* Check gid_ndx instead of preserve_gid for del support */
220 @@ -1466,6 +1497,7 @@ static struct file_struct *send_file_name(int f, struct file_list *flist,
222 #ifdef SUPPORT_XATTRS
223 if (preserve_xattrs) {
224 + sx.st.st_mode = file->mode;
225 if (get_xattr(fname, &sx) < 0) {
226 io_error |= IOERR_GENERAL;
228 diff --git a/generator.c b/generator.c
229 index 12007a1..eee42e8 100644
232 @@ -42,8 +42,10 @@ extern int preserve_devices;
233 extern int preserve_specials;
234 extern int preserve_hard_links;
235 extern int preserve_executability;
236 +extern int preserve_fileflags;
237 extern int preserve_perms;
238 extern int preserve_times;
239 +extern int force_change;
242 extern int delete_mode;
243 @@ -406,6 +408,11 @@ int unchanged_attrs(const char *fname, struct file_struct *file, stat_x *sxp)
244 && ((sxp->st.st_mode & 0111 ? 1 : 0) ^ (file->mode & 0111 ? 1 : 0)))
247 +#ifdef SUPPORT_FILEFLAGS
248 + if (preserve_fileflags && !S_ISLNK(file->mode) && sxp->st.st_flags != F_FFLAGS(file))
252 if (am_root && uid_ndx && sxp->st.st_uid != (uid_t)F_OWNER(file))
255 @@ -471,6 +478,11 @@ void itemize(const char *fnamecmp, struct file_struct *file, int ndx, int statre
256 if (gid_ndx && !(file->flags & FLAG_SKIP_GROUP)
257 && sxp->st.st_gid != (gid_t)F_GROUP(file))
258 iflags |= ITEM_REPORT_GROUP;
259 +#ifdef SUPPORT_FILEFLAGS
260 + if (preserve_fileflags && !S_ISLNK(file->mode)
261 + && sxp->st.st_flags != F_FFLAGS(file))
262 + iflags |= ITEM_REPORT_FFLAGS;
265 if (preserve_acls && !S_ISLNK(file->mode)) {
266 if (!ACL_READY(*sxp))
267 @@ -1258,6 +1270,10 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
268 file->mode = dest_mode(file->mode, sx.st.st_mode,
269 dflt_perms, statret == 0);
271 +#ifdef SUPPORT_FORCE_CHANGE
272 + if (force_change && !preserve_fileflags)
273 + F_FFLAGS(file) = sx.st.st_flags;
275 if (statret != 0 && basis_dir[0] != NULL) {
276 int j = try_dests_non(file, fname, ndx, fnamecmpbuf, &sx,
278 @@ -1298,10 +1314,15 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
279 /* We need to ensure that the dirs in the transfer have writable
280 * permissions during the time we are putting files within them.
281 * This is then fixed after the transfer is done. */
282 +#ifdef SUPPORT_FORCE_CHANGE
283 + if (force_change && F_FFLAGS(file) & force_change
284 + && make_mutable(fname, file->mode, F_FFLAGS(file), force_change))
285 + need_retouch_dir_perms = 1;
288 if (!am_root && !(file->mode & S_IWUSR) && dir_tweaking) {
289 mode_t mode = file->mode | S_IWUSR;
290 - if (do_chmod(fname, mode) < 0) {
291 + if (do_chmod(fname, mode, 0) < 0) {
292 rsyserr(FERROR_XFER, errno,
293 "failed to modify permissions on %s",
295 @@ -1336,6 +1357,10 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
296 file->mode = dest_mode(file->mode, sx.st.st_mode, dflt_perms,
299 +#ifdef SUPPORT_FORCE_CHANGE
300 + if (force_change && !preserve_fileflags)
301 + F_FFLAGS(file) = sx.st.st_flags;
304 #ifdef SUPPORT_HARD_LINKS
305 if (preserve_hard_links && F_HLINK_NOT_FIRST(file)
306 @@ -1870,13 +1895,17 @@ static void touch_up_dirs(struct file_list *flist, int ndx)
308 fname = f_name(file, NULL);
309 if (!(file->mode & S_IWUSR))
310 - do_chmod(fname, file->mode);
311 + do_chmod(fname, file->mode, 0);
312 if (need_retouch_dir_times) {
314 if (link_stat(fname, &st, 0) == 0
315 && cmp_time(st.st_mtime, file->modtime) != 0)
316 - set_modtime(fname, file->modtime, file->mode);
317 + set_modtime(fname, file->modtime, file->mode, 0);
319 +#ifdef SUPPORT_FORCE_CHANGE
320 + if (force_change && F_FFLAGS(file) & force_change)
321 + undo_make_mutable(fname, F_FFLAGS(file));
323 if (counter >= loopchk_limit) {
325 maybe_send_keepalive();
326 diff --git a/log.c b/log.c
327 index a687375..83948b1 100644
330 @@ -715,7 +715,7 @@ static void log_formatted(enum logcode code, const char *format, const char *op,
331 c[5] = !(iflags & ITEM_REPORT_PERMS) ? '.' : 'p';
332 c[6] = !(iflags & ITEM_REPORT_OWNER) ? '.' : 'o';
333 c[7] = !(iflags & ITEM_REPORT_GROUP) ? '.' : 'g';
334 - c[8] = !(iflags & ITEM_REPORT_ATIME) ? '.' : 'u';
335 + c[8] = !(iflags & ITEM_REPORT_FFLAGS) ? '.' : 'f';
336 c[9] = !(iflags & ITEM_REPORT_ACL) ? '.' : 'a';
337 c[10] = !(iflags & ITEM_REPORT_XATTR) ? '.' : 'x';
339 diff --git a/options.c b/options.c
340 index e7c6c61..ae3d2d0 100644
343 @@ -53,6 +53,7 @@ int preserve_hard_links = 0;
344 int preserve_acls = 0;
345 int preserve_xattrs = 0;
346 int preserve_perms = 0;
347 +int preserve_fileflags = 0;
348 int preserve_executability = 0;
349 int preserve_devices = 0;
350 int preserve_specials = 0;
351 @@ -86,6 +87,7 @@ int numeric_ids = 0;
353 int allow_8bit_chars = 0;
354 int force_delete = 0;
355 +int force_change = 0;
357 int prune_empty_dirs = 0;
359 @@ -566,6 +568,7 @@ static void print_rsync_version(enum logcode f)
360 char const *links = "no ";
361 char const *iconv = "no ";
362 char const *ipv6 = "no ";
363 + char const *fileflags = "no ";
364 STRUCT_STAT *dumstat;
366 #if SUBPROTOCOL_VERSION != 0
367 @@ -599,6 +602,9 @@ static void print_rsync_version(enum logcode f)
368 #if defined HAVE_LUTIMES && defined HAVE_UTIMES
371 +#ifdef SUPPORT_FILEFLAGS
375 rprintf(f, "%s version %s protocol version %d%s\n",
376 RSYNC_NAME, RSYNC_VERSION, PROTOCOL_VERSION, subprotocol);
377 @@ -612,8 +618,8 @@ static void print_rsync_version(enum logcode f)
378 (int)(sizeof (int64) * 8));
379 rprintf(f, " %ssocketpairs, %shardlinks, %ssymlinks, %sIPv6, batchfiles, %sinplace,\n",
380 got_socketpair, hardlinks, links, ipv6, have_inplace);
381 - rprintf(f, " %sappend, %sACLs, %sxattrs, %siconv, %ssymtimes\n",
382 - have_inplace, acls, xattrs, iconv, symtimes);
383 + rprintf(f, " %sappend, %sACLs, %sxattrs, %siconv, %ssymtimes, %sfile-flags\n",
384 + have_inplace, acls, xattrs, iconv, symtimes, fileflags);
386 #ifdef MAINTAINER_MODE
387 rprintf(f, "Panic Action: \"%s\"\n", get_panic_action());
388 @@ -683,6 +689,9 @@ void usage(enum logcode F)
389 rprintf(F," -K, --keep-dirlinks treat symlinked dir on receiver as dir\n");
390 rprintf(F," -H, --hard-links preserve hard links\n");
391 rprintf(F," -p, --perms preserve permissions\n");
392 +#ifdef SUPPORT_FILEFLAGS
393 + rprintf(F," --fileflags preserve file-flags (aka chflags)\n");
395 rprintf(F," -E, --executability preserve the file's executability\n");
396 rprintf(F," --chmod=CHMOD affect file and/or directory permissions\n");
398 @@ -722,7 +731,12 @@ void usage(enum logcode F)
399 rprintf(F," --ignore-missing-args ignore missing source args without error\n");
400 rprintf(F," --delete-missing-args delete missing source args from destination\n");
401 rprintf(F," --ignore-errors delete even if there are I/O errors\n");
402 - rprintf(F," --force force deletion of directories even if not empty\n");
403 + rprintf(F," --force-delete force deletion of directories even if not empty\n");
404 +#ifdef SUPPORT_FORCE_CHANGE
405 + rprintf(F," --force-change affect user-/system-immutable files/dirs\n");
406 + rprintf(F," --force-uchange affect user-immutable files/dirs\n");
407 + rprintf(F," --force-schange affect system-immutable files/dirs\n");
409 rprintf(F," --max-delete=NUM don't delete more than NUM files\n");
410 rprintf(F," --max-size=SIZE don't transfer any file larger than SIZE\n");
411 rprintf(F," --min-size=SIZE don't transfer any file smaller than SIZE\n");
412 @@ -835,6 +849,10 @@ static struct poptOption long_options[] = {
413 {"perms", 'p', POPT_ARG_VAL, &preserve_perms, 1, 0, 0 },
414 {"no-perms", 0, POPT_ARG_VAL, &preserve_perms, 0, 0, 0 },
415 {"no-p", 0, POPT_ARG_VAL, &preserve_perms, 0, 0, 0 },
416 +#ifdef SUPPORT_FILEFLAGS
417 + {"fileflags", 0, POPT_ARG_VAL, &preserve_fileflags, 1, 0, 0 },
418 + {"no-fileflags", 0, POPT_ARG_VAL, &preserve_fileflags, 0, 0, 0 },
420 {"executability", 'E', POPT_ARG_NONE, &preserve_executability, 0, 0, 0 },
421 {"acls", 'A', POPT_ARG_NONE, 0, 'A', 0, 0 },
422 {"no-acls", 0, POPT_ARG_VAL, &preserve_acls, 0, 0, 0 },
423 @@ -917,6 +935,14 @@ static struct poptOption long_options[] = {
424 {"remove-source-files",0,POPT_ARG_VAL, &remove_source_files, 1, 0, 0 },
425 {"force", 0, POPT_ARG_VAL, &force_delete, 1, 0, 0 },
426 {"no-force", 0, POPT_ARG_VAL, &force_delete, 0, 0, 0 },
427 + {"force-delete", 0, POPT_ARG_VAL, &force_delete, 1, 0, 0 },
428 + {"no-force-delete", 0, POPT_ARG_VAL, &force_delete, 0, 0, 0 },
429 +#ifdef SUPPORT_FORCE_CHANGE
430 + {"force-change", 0, POPT_ARG_VAL, &force_change, ALL_IMMUTABLE, 0, 0 },
431 + {"no-force-change", 0, POPT_ARG_VAL, &force_change, 0, 0, 0 },
432 + {"force-uchange", 0, POPT_ARG_VAL, &force_change, USR_IMMUTABLE, 0, 0 },
433 + {"force-schange", 0, POPT_ARG_VAL, &force_change, SYS_IMMUTABLE, 0, 0 },
435 {"ignore-errors", 0, POPT_ARG_VAL, &ignore_errors, 1, 0, 0 },
436 {"no-ignore-errors", 0, POPT_ARG_VAL, &ignore_errors, 0, 0, 0 },
437 {"max-delete", 0, POPT_ARG_INT, &max_delete, 0, 0, 0 },
438 @@ -2383,6 +2409,9 @@ void server_options(char **args, int *argc_p)
439 if (xfer_dirs && !recurse && delete_mode && am_sender)
440 args[ac++] = "--no-r";
442 + if (preserve_fileflags)
443 + args[ac++] = "--fileflags";
445 if (do_compression && def_compress_level != Z_DEFAULT_COMPRESSION) {
446 if (asprintf(&arg, "--compress-level=%d", def_compress_level) < 0)
448 @@ -2470,6 +2499,16 @@ void server_options(char **args, int *argc_p)
449 args[ac++] = "--delete-excluded";
451 args[ac++] = "--force";
452 +#ifdef SUPPORT_FORCE_CHANGE
453 + if (force_change) {
454 + if (force_change == ALL_IMMUTABLE)
455 + args[ac++] = "--force-change";
456 + else if (force_change == USR_IMMUTABLE)
457 + args[ac++] = "--force-uchange";
458 + else if (force_change == SYS_IMMUTABLE)
459 + args[ac++] = "--force-schange";
463 args[ac++] = "--only-write-batch=X";
465 diff --git a/rsync.c b/rsync.c
466 index 2c026a2..3188535 100644
469 @@ -31,6 +31,7 @@ extern int dry_run;
470 extern int preserve_acls;
471 extern int preserve_xattrs;
472 extern int preserve_perms;
473 +extern int preserve_fileflags;
474 extern int preserve_executability;
475 extern int preserve_times;
477 @@ -378,6 +379,39 @@ mode_t dest_mode(mode_t flist_mode, mode_t stat_mode, int dflt_perms,
481 +#if defined SUPPORT_FILEFLAGS || defined SUPPORT_FORCE_CHANGE
482 +/* Set a file's st_flags. */
483 +static int set_fileflags(const char *fname, uint32 fileflags)
485 + if (do_chflags(fname, fileflags) != 0) {
486 + rsyserr(FERROR_XFER, errno,
487 + "failed to set file flags on %s",
488 + full_fname(fname));
495 +/* Remove immutable flags from an object, so it can be altered/removed. */
496 +int make_mutable(const char *fname, mode_t mode, uint32 fileflags, uint32 iflags)
498 + if (S_ISLNK(mode) || !(fileflags & iflags))
500 + if (!set_fileflags(fname, fileflags & ~iflags))
505 +/* Undo a prior make_mutable() call that returned a 1. */
506 +int undo_make_mutable(const char *fname, uint32 fileflags)
508 + if (!set_fileflags(fname, fileflags))
514 int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
515 const char *fnamecmp, int flags)
517 @@ -426,7 +460,7 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
518 flags |= ATTRS_SKIP_MTIME;
519 if (!(flags & ATTRS_SKIP_MTIME)
520 && cmp_time(sxp->st.st_mtime, file->modtime) != 0) {
521 - int ret = set_modtime(fname, file->modtime, sxp->st.st_mode);
522 + int ret = set_modtime(fname, file->modtime, sxp->st.st_mode, ST_FLAGS(sxp->st));
524 rsyserr(FERROR_XFER, errno, "failed to set times on %s",
526 @@ -462,7 +496,8 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
529 change_uid ? (uid_t)F_OWNER(file) : sxp->st.st_uid,
530 - change_gid ? (gid_t)F_GROUP(file) : sxp->st.st_gid) != 0) {
531 + change_gid ? (gid_t)F_GROUP(file) : sxp->st.st_gid,
532 + sxp->st.st_mode, ST_FLAGS(sxp->st)) != 0) {
533 /* We shouldn't have attempted to change uid
534 * or gid unless have the privilege. */
535 rsyserr(FERROR_XFER, errno, "%s %s failed",
536 @@ -494,7 +529,7 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
539 if (!BITS_EQUAL(sxp->st.st_mode, new_mode, CHMOD_BITS)) {
540 - int ret = am_root < 0 ? 0 : do_chmod(fname, new_mode);
541 + int ret = am_root < 0 ? 0 : do_chmod(fname, new_mode, ST_FLAGS(sxp->st));
543 rsyserr(FERROR_XFER, errno,
544 "failed to set permissions on %s",
545 @@ -506,6 +541,19 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
549 +#ifdef SUPPORT_FILEFLAGS
550 + if (preserve_fileflags && !S_ISLNK(sxp->st.st_mode)
551 + && sxp->st.st_flags != F_FFLAGS(file)) {
552 + uint32 fileflags = F_FFLAGS(file);
553 + if (flags & ATTRS_DELAY_IMMUTABLE)
554 + fileflags &= ~ALL_IMMUTABLE;
555 + if (sxp->st.st_flags != fileflags
556 + && !set_fileflags(fname, fileflags))
562 if (INFO_GTE(NAME, 2) && flags & ATTRS_REPORT) {
564 rprintf(FCLIENT, "%s\n", fname);
565 @@ -570,7 +618,8 @@ int finish_transfer(const char *fname, const char *fnametmp,
567 /* Change permissions before putting the file into place. */
568 set_file_attrs(fnametmp, file, NULL, fnamecmp,
569 - ok_to_set_time ? 0 : ATTRS_SKIP_MTIME);
570 + ATTRS_DELAY_IMMUTABLE
571 + | (ok_to_set_time ? 0 : ATTRS_SKIP_MTIME));
573 /* move tmp file over real file */
574 if (DEBUG_GTE(RECV, 1))
575 @@ -589,6 +638,10 @@ int finish_transfer(const char *fname, const char *fnametmp,
578 /* The file was moved into place (not copied), so it's done. */
579 +#ifdef SUPPORT_FILEFLAGS
580 + if (preserve_fileflags && F_FFLAGS(file) & ALL_IMMUTABLE)
581 + set_fileflags(fname, F_FFLAGS(file));
585 /* The file was copied, so tweak the perms of the copied file. If it
586 diff --git a/rsync.h b/rsync.h
587 index be7cf8a..16820fd 100644
591 #define XMIT_GROUP_NAME_FOLLOWS (1<<11) /* protocols 30 - now */
592 #define XMIT_HLINK_FIRST (1<<12) /* protocols 30 - now (HLINKED files only) */
593 #define XMIT_IO_ERROR_ENDLIST (1<<12) /* protocols 31 - now (w/XMIT_EXTENDED_FLAGS) */
594 +#define XMIT_SAME_FLAGS (1<<14) /* protocols ?? - now */
596 /* These flags are used in the live flist data. */
600 #define ATTRS_REPORT (1<<0)
601 #define ATTRS_SKIP_MTIME (1<<1)
602 +#define ATTRS_DELAY_IMMUTABLE (1<<2)
605 #define NORMAL_FLUSH 0
607 #define ITEM_REPORT_GROUP (1<<6)
608 #define ITEM_REPORT_ACL (1<<7)
609 #define ITEM_REPORT_XATTR (1<<8)
610 +#define ITEM_REPORT_FFLAGS (1<<9)
611 #define ITEM_BASIS_TYPE_FOLLOWS (1<<11)
612 #define ITEM_XNAME_FOLLOWS (1<<12)
613 #define ITEM_IS_NEW (1<<13)
614 @@ -482,6 +485,28 @@ typedef unsigned int size_t;
618 +#define NO_FFLAGS ((uint32)-1)
621 +#define SUPPORT_FILEFLAGS 1
622 +#define SUPPORT_FORCE_CHANGE 1
625 +#if defined SUPPORT_FILEFLAGS || defined SUPPORT_FORCE_CHANGE
627 +#define UF_NOUNLINK 0
630 +#define SF_NOUNLINK 0
632 +#define USR_IMMUTABLE (UF_IMMUTABLE|UF_NOUNLINK|UF_APPEND)
633 +#define SYS_IMMUTABLE (SF_IMMUTABLE|SF_NOUNLINK|SF_APPEND)
634 +#define ALL_IMMUTABLE (USR_IMMUTABLE|SYS_IMMUTABLE)
635 +#define ST_FLAGS(st) (st.st_flags)
637 +#define ST_FLAGS(st) NO_FFLAGS
640 /* Find a variable that is either exactly 32-bits or longer.
641 * If some code depends on 32-bit truncation, it will need to
642 * take special action in a "#if SIZEOF_INT32 > 4" section. */
643 @@ -652,6 +677,7 @@ extern int file_extra_cnt;
644 extern int inc_recurse;
647 +extern int fileflags_ndx;
649 extern int xattrs_ndx;
651 @@ -689,6 +715,11 @@ extern int xattrs_ndx;
652 /* When the associated option is on, all entries will have these present: */
653 #define F_OWNER(f) REQ_EXTRA(f, uid_ndx)->unum
654 #define F_GROUP(f) REQ_EXTRA(f, gid_ndx)->unum
655 +#if defined SUPPORT_FILEFLAGS || defined SUPPORT_FORCE_CHANGE
656 +#define F_FFLAGS(f) REQ_EXTRA(f, fileflags_ndx)->unum
658 +#define F_FFLAGS(f) NO_FFLAGS
660 #define F_ACL(f) REQ_EXTRA(f, acls_ndx)->num
661 #define F_XATTR(f) REQ_EXTRA(f, xattrs_ndx)->num
662 #define F_NDX(f) REQ_EXTRA(f, unsort_ndx)->num
663 diff --git a/rsync.yo b/rsync.yo
664 index 941f7a5..7b41d5f 100644
667 @@ -345,6 +345,7 @@ to the detailed description below for a complete description. verb(
668 -K, --keep-dirlinks treat symlinked dir on receiver as dir
669 -H, --hard-links preserve hard links
670 -p, --perms preserve permissions
671 + --fileflags preserve file-flags (aka chflags)
672 -E, --executability preserve executability
673 --chmod=CHMOD affect file and/or directory permissions
674 -A, --acls preserve ACLs (implies -p)
675 @@ -378,7 +379,10 @@ to the detailed description below for a complete description. verb(
676 --ignore-missing-args ignore missing source args without error
677 --delete-missing-args delete missing source args from destination
678 --ignore-errors delete even if there are I/O errors
679 - --force force deletion of dirs even if not empty
680 + --force-delete force deletion of dirs even if not empty
681 + --force-change affect user/system immutable files/dirs
682 + --force-uchange affect user-immutable files/dirs
683 + --force-schange affect system-immutable files/dirs
684 --max-delete=NUM don't delete more than NUM files
685 --max-size=SIZE don't transfer any file larger than SIZE
686 --min-size=SIZE don't transfer any file smaller than SIZE
687 @@ -592,7 +596,8 @@ specified, in which case bf(-r) is not implied.
689 Note that bf(-a) bf(does not preserve hardlinks), because
690 finding multiply-linked files is expensive. You must separately
692 +specify bf(-H). Note also that for backward compatibility, bf(-a)
693 +currently does bf(not) imply the bf(--fileflags) option.
695 dit(--no-OPTION) You may turn off one or more implied options by prefixing
696 the option name with "no-". Not all options may be prefixed with a "no-":
697 @@ -869,7 +874,7 @@ they would be using bf(--copy-links).
698 Without this option, if the sending side has replaced a directory with a
699 symlink to a directory, the receiving side will delete anything that is in
700 the way of the new symlink, including a directory hierarchy (as long as
701 -bf(--force) or bf(--delete) is in effect).
702 +bf(--force-delete) or bf(--delete) is in effect).
704 See also bf(--keep-dirlinks) for an analogous option for the receiving
706 @@ -1006,6 +1011,29 @@ super-user copies all namespaces except system.*. A normal user only copies
707 the user.* namespace. To be able to backup and restore non-user namespaces as
708 a normal user, see the bf(--fake-super) option.
710 +dit(bf(--fileflags)) This option causes rsync to update the file-flags to be
711 +the same as the source files and directories (if your OS supports the
712 +bf(chflags)(2) system call). Some flags can only be altered by the super-user
713 +and some might only be unset below a certain secure-level (usually single-user
714 +mode). It will not make files alterable that are set to immutable on the
715 +receiver. To do that, see bf(--force-change), bf(--force-uchange), and
716 +bf(--force-schange).
718 +dit(bf(--force-change)) This option causes rsync to disable both user-immutable
719 +and system-immutable flags on files and directories that are being updated or
720 +deleted on the receiving side. This option overrides bf(--force-uchange) and
721 +bf(--force-schange).
723 +dit(bf(--force-uchange)) This option causes rsync to disable user-immutable
724 +flags on files and directories that are being updated or deleted on the
725 +receiving side. It does not try to affect system flags. This option overrides
726 +bf(--force-change) and bf(--force-schange).
728 +dit(bf(--force-schange)) This option causes rsync to disable system-immutable
729 +flags on files and directories that are being updated or deleted on the
730 +receiving side. It does not try to affect user flags. This option overrides
731 +bf(--force-change) and bf(--force-schange).
733 dit(bf(--chmod)) This option tells rsync to apply one or more
734 comma-separated "chmod" strings to the permission of the files in the
735 transfer. The resulting value is treated as though it was the permissions
736 @@ -1285,12 +1313,13 @@ display as a "*missing" entry in the bf(--list-only) output.
737 dit(bf(--ignore-errors)) Tells bf(--delete) to go ahead and delete files
738 even when there are I/O errors.
740 -dit(bf(--force)) This option tells rsync to delete a non-empty directory
741 +dit(bf(--force-delete)) This option tells rsync to delete a non-empty directory
742 when it is to be replaced by a non-directory. This is only relevant if
743 deletions are not active (see bf(--delete) for details).
745 -Note for older rsync versions: bf(--force) used to still be required when
746 -using bf(--delete-after), and it used to be non-functional unless the
747 +This option can be abbreviated bf(--force) for backward compatibility.
748 +Note that some older rsync versions used to still require bf(--force)
749 +when using bf(--delete-after), and it used to be non-functional unless the
750 bf(--recursive) option was also enabled.
752 dit(bf(--max-delete=NUM)) This tells rsync not to delete more than NUM
753 @@ -1832,7 +1861,7 @@ with older versions of rsync, but that also turns on the output of other
756 The "%i" escape has a cryptic output that is 11 letters long. The general
757 -format is like the string bf(YXcstpoguax), where bf(Y) is replaced by the
758 +format is like the string bf(YXcstpogfax), where bf(Y) is replaced by the
759 type of update being done, bf(X) is replaced by the file-type, and the
760 other letters represent attributes that may be output if they are being
762 @@ -1888,7 +1917,7 @@ quote(itemization(
763 sender's value (requires bf(--owner) and super-user privileges).
764 it() A bf(g) means the group is different and is being updated to the
765 sender's value (requires bf(--group) and the authority to set the group).
766 - it() The bf(u) slot is reserved for future use.
767 + it() The bf(f) means that the fileflags information changed.
768 it() The bf(a) means that the ACL information changed.
769 it() The bf(x) means that the extended attribute information changed.
771 diff --git a/syscall.c b/syscall.c
772 index cfabc3e..45604b1 100644
775 @@ -33,6 +33,7 @@ extern int dry_run;
777 extern int read_only;
778 extern int list_only;
779 +extern int force_change;
780 extern int preserve_perms;
781 extern int preserve_executability;
783 @@ -50,7 +51,23 @@ int do_unlink(const char *fname)
785 if (dry_run) return 0;
786 RETURN_ERROR_IF_RO_OR_LO;
787 - return unlink(fname);
788 + if (unlink(fname) == 0)
790 +#ifdef SUPPORT_FORCE_CHANGE
791 + if (force_change && errno == EPERM) {
794 + if (x_lstat(fname, &st, NULL) == 0
795 + && make_mutable(fname, st.st_mode, st.st_flags, force_change) > 0) {
796 + if (unlink(fname) == 0)
798 + undo_make_mutable(fname, st.st_flags);
800 + /* TODO: handle immutable directories */
807 int do_symlink(const char *fname1, const char *fname2)
808 @@ -69,14 +86,37 @@ int do_link(const char *fname1, const char *fname2)
812 -int do_lchown(const char *path, uid_t owner, gid_t group)
813 +int do_lchown(const char *path, uid_t owner, gid_t group, mode_t mode, uint32 fileflags)
815 if (dry_run) return 0;
816 RETURN_ERROR_IF_RO_OR_LO;
820 - return lchown(path, owner, group);
821 + if (lchown(path, owner, group) == 0)
823 +#ifdef SUPPORT_FORCE_CHANGE
824 + if (force_change && errno == EPERM) {
825 + if (fileflags == NO_FFLAGS) {
827 + if (x_lstat(path, &st, NULL) == 0) {
829 + fileflags = st.st_flags;
832 + if (fileflags != NO_FFLAGS
833 + && make_mutable(path, mode, fileflags, force_change) > 0) {
834 + int ret = lchown(path, owner, group);
835 + undo_make_mutable(path, fileflags);
842 + mode = fileflags = 0; /* avoid compiler warning */
847 int do_mknod(const char *pathname, mode_t mode, dev_t dev)
848 @@ -116,7 +156,7 @@ int do_mknod(const char *pathname, mode_t mode, dev_t dev)
852 - return do_chmod(pathname, mode);
853 + return do_chmod(pathname, mode, 0);
857 @@ -133,7 +173,22 @@ int do_rmdir(const char *pathname)
859 if (dry_run) return 0;
860 RETURN_ERROR_IF_RO_OR_LO;
861 - return rmdir(pathname);
862 + if (rmdir(pathname) == 0)
864 +#ifdef SUPPORT_FORCE_CHANGE
865 + if (force_change && errno == EPERM) {
868 + if (x_lstat(pathname, &st, NULL) == 0
869 + && make_mutable(pathname, st.st_mode, st.st_flags, force_change) > 0) {
870 + if (rmdir(pathname) == 0)
872 + undo_make_mutable(pathname, st.st_flags);
880 int do_open(const char *pathname, int flags, mode_t mode)
881 @@ -147,7 +202,7 @@ int do_open(const char *pathname, int flags, mode_t mode)
885 -int do_chmod(const char *path, mode_t mode)
886 +int do_chmod(const char *path, mode_t mode, uint32 fileflags)
889 if (dry_run) return 0;
890 @@ -168,17 +223,74 @@ int do_chmod(const char *path, mode_t mode)
893 code = chmod(path, mode & CHMOD_BITS); /* DISCOURAGED FUNCTION */
894 +#ifdef SUPPORT_FORCE_CHANGE
895 + if (code < 0 && force_change && errno == EPERM && !S_ISLNK(mode)) {
896 + if (fileflags == NO_FFLAGS) {
898 + if (x_lstat(path, &st, NULL) == 0)
899 + fileflags = st.st_flags;
901 + if (fileflags != NO_FFLAGS
902 + && make_mutable(path, mode, fileflags, force_change) > 0) {
903 + code = chmod(path, mode & CHMOD_BITS);
904 + undo_make_mutable(path, fileflags);
911 + fileflags = 0; /* avoid compiler warning */
913 if (code != 0 && (preserve_perms || preserve_executability))
920 +int do_chflags(const char *path, uint32 fileflags)
922 + if (dry_run) return 0;
923 + RETURN_ERROR_IF_RO_OR_LO;
924 + return chflags(path, fileflags);
928 int do_rename(const char *fname1, const char *fname2)
930 if (dry_run) return 0;
931 RETURN_ERROR_IF_RO_OR_LO;
932 - return rename(fname1, fname2);
933 + if (rename(fname1, fname2) == 0)
935 +#ifdef SUPPORT_FORCE_CHANGE
936 + if (force_change && errno == EPERM) {
937 + STRUCT_STAT st1, st2;
938 + int became_mutable;
940 + if (x_lstat(fname1, &st1, NULL) != 0)
942 + became_mutable = make_mutable(fname1, st1.st_mode, st1.st_flags, force_change) > 0;
943 + if (became_mutable && rename(fname1, fname2) == 0)
945 + if (x_lstat(fname2, &st2, NULL) == 0
946 + && make_mutable(fname2, st2.st_mode, st2.st_flags, force_change) > 0) {
947 + if (rename(fname1, fname2) == 0) {
949 + if (became_mutable) /* Yes, use fname2 and st1! */
950 + undo_make_mutable(fname2, st1.st_flags);
953 + undo_make_mutable(fname2, st2.st_flags);
955 + /* TODO: handle immutable directories */
956 + if (became_mutable)
957 + undo_make_mutable(fname1, st1.st_flags);
965 void trim_trailing_slashes(char *name)
966 diff --git a/t_stub.c b/t_stub.c
967 index 02cfa69..9228d0c 100644
970 @@ -25,6 +25,7 @@ int modify_window = 0;
972 int relative_paths = 0;
973 int module_dirlen = 0;
974 +int force_change = 0;
975 int preserve_xattrs = 0;
976 mode_t orig_umask = 002;
977 char number_separator = ',';
978 @@ -89,3 +90,23 @@ struct filter_list_struct daemon_filter_list;
983 +#if defined SUPPORT_FILEFLAGS || defined SUPPORT_FORCE_CHANGE
984 + int make_mutable(UNUSED(const char *fname), UNUSED(mode_t mode), UNUSED(uint32 fileflags), UNUSED(uint32 iflags))
989 +/* Undo a prior make_mutable() call that returned a 1. */
990 + int undo_make_mutable(UNUSED(const char *fname), UNUSED(uint32 fileflags))
996 +#ifdef SUPPORT_XATTRS
997 + int x_lstat(UNUSED(const char *fname), UNUSED(STRUCT_STAT *fst), UNUSED(STRUCT_STAT *xst))
1002 diff --git a/util.c b/util.c
1003 index 0cafed6..ed26a05 100644
1006 @@ -30,6 +30,7 @@ extern int module_id;
1007 extern int modify_window;
1008 extern int relative_paths;
1009 extern int preserve_xattrs;
1010 +extern int force_change;
1011 extern char *module_dir;
1012 extern unsigned int module_dirlen;
1013 extern mode_t orig_umask;
1014 @@ -123,7 +124,7 @@ NORETURN void overflow_exit(const char *str)
1015 exit_cleanup(RERR_MALLOC);
1018 -int set_modtime(const char *fname, time_t modtime, mode_t mode)
1019 +int set_modtime(const char *fname, time_t modtime, mode_t mode, uint32 fileflags)
1021 #if !defined HAVE_LUTIMES || !defined HAVE_UTIMES
1023 @@ -140,6 +141,7 @@ int set_modtime(const char *fname, time_t modtime, mode_t mode)
1029 struct timeval t[2];
1030 t[0].tv_sec = time(NULL);
1031 @@ -153,20 +155,39 @@ int set_modtime(const char *fname, time_t modtime, mode_t mode)
1035 - return utimes(fname, t);
1036 +#define SET_THE_TIME(fn) utimes(fn, t)
1037 #elif defined HAVE_STRUCT_UTIMBUF
1038 struct utimbuf tbuf;
1039 tbuf.actime = time(NULL);
1040 tbuf.modtime = modtime;
1041 - return utime(fname,&tbuf);
1042 +#define SET_THE_TIME(fn) utime(fn, &tbuf)
1043 #elif defined HAVE_UTIME
1047 - return utime(fname,t);
1048 +#define SET_THE_TIME(fn) utime(fn, t)
1050 #error No file-time-modification routine found!
1052 + ret = SET_THE_TIME(fname);
1053 +#ifdef SUPPORT_FORCE_CHANGE
1054 + if (ret != 0 && force_change && errno == EPERM) {
1055 + if (fileflags == NO_FFLAGS) {
1057 + if (x_lstat(fname, &st, NULL) == 0)
1058 + fileflags = st.st_flags;
1060 + if (fileflags != NO_FFLAGS
1061 + && make_mutable(fname, mode, fileflags, force_change) > 0) {
1062 + ret = SET_THE_TIME(fname);
1063 + undo_make_mutable(fname, fileflags);
1068 + fileflags = 0; /* avoid compiler warning */
1074 diff --git a/xattrs.c b/xattrs.c
1075 index 2d0e050..5d04599 100644
1078 @@ -282,6 +282,10 @@ int get_xattr(const char *fname, stat_x *sxp)
1080 sxp->xattr = new(item_list);
1081 *sxp->xattr = empty_xattr;
1083 + if (IS_SPECIAL(sxp->st.st_mode) || IS_DEVICE(sxp->st.st_mode))
1086 if (rsync_xal_get(fname, sxp->xattr) < 0) {
1089 @@ -865,6 +869,11 @@ int set_xattr(const char *fname, const struct file_struct *file,
1093 + if (IS_SPECIAL(sxp->st.st_mode) || IS_DEVICE(sxp->st.st_mode)) {
1098 ndx = F_XATTR(file);
1099 return rsync_xal_set(fname, lst + ndx, fnamecmp, sxp);
1101 @@ -981,7 +990,7 @@ int set_stat_xattr(const char *fname, struct file_struct *file, mode_t new_mode)
1102 mode = (fst.st_mode & _S_IFMT) | (fmode & ACCESSPERMS)
1103 | (S_ISDIR(fst.st_mode) ? 0700 : 0600);
1104 if (fst.st_mode != mode)
1105 - do_chmod(fname, mode);
1106 + do_chmod(fname, mode, ST_FLAGS(fst));
1107 if (!IS_DEVICE(fst.st_mode) && !IS_SPECIAL(fst.st_mode))
1108 fst.st_rdev = 0; /* just in case */