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
14 @@ -42,7 +42,7 @@ popt_OBJS=popt/findme.o popt/popt.o popt/poptconfig.o \
15 popt/popthelp.o popt/poptparse.o
16 OBJS=$(OBJS1) $(OBJS2) $(OBJS3) $(DAEMON_OBJ) $(LIBOBJ) $(ZLIBOBJ) @BUILD_POPT@
18 -TLS_OBJ = tls.o syscall.o lib/compat.o lib/snprintf.o lib/permstring.o lib/sysxattrs.o @BUILD_POPT@
19 +TLS_OBJ = tls.o syscall.o t_stub.o lib/compat.o lib/snprintf.o lib/permstring.o lib/sysxattrs.o @BUILD_POPT@
21 # Programs we must have to run the test cases
22 CHECK_PROGS = rsync$(EXEEXT) tls$(EXEEXT) getgroups$(EXEEXT) getfsdev$(EXEEXT) \
23 @@ -107,7 +107,7 @@ getgroups$(EXEEXT): getgroups.o
24 getfsdev$(EXEEXT): getfsdev.o
25 $(CC) $(CFLAGS) $(LDFLAGS) -o $@ getfsdev.o $(LIBS)
27 -TRIMSLASH_OBJ = trimslash.o syscall.o lib/compat.o lib/snprintf.o
28 +TRIMSLASH_OBJ = trimslash.o syscall.o t_stub.o lib/compat.o lib/snprintf.o
29 trimslash$(EXEEXT): $(TRIMSLASH_OBJ)
30 $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(TRIMSLASH_OBJ) $(LIBS)
32 diff --git a/compat.c b/compat.c
35 @@ -41,9 +41,11 @@ extern int checksum_seed;
36 extern int basis_dir_cnt;
37 extern int prune_empty_dirs;
38 extern int protocol_version;
39 +extern int force_change;
40 extern int protect_args;
41 extern int preserve_uid;
42 extern int preserve_gid;
43 +extern int preserve_fileflags;
44 extern int preserve_acls;
45 extern int preserve_xattrs;
46 extern int need_messages_from_generator;
47 @@ -61,7 +63,7 @@ extern char *iconv_opt;
50 /* These index values are for the file-list's extra-attribute array. */
51 -int uid_ndx, gid_ndx, acls_ndx, xattrs_ndx, unsort_ndx;
52 +int uid_ndx, gid_ndx, fileflags_ndx, acls_ndx, xattrs_ndx, unsort_ndx;
54 int receiver_symlink_times = 0; /* receiver can set the time on a symlink */
55 int sender_symlink_iconv = 0; /* sender should convert symlink content */
56 @@ -137,6 +139,8 @@ void setup_protocol(int f_out,int f_in)
57 uid_ndx = ++file_extra_cnt;
59 gid_ndx = ++file_extra_cnt;
60 + if (preserve_fileflags || (force_change && !am_sender))
61 + fileflags_ndx = ++file_extra_cnt;
62 if (preserve_acls && !am_sender)
63 acls_ndx = ++file_extra_cnt;
65 diff --git a/configure.in b/configure.in
68 @@ -553,7 +553,7 @@ AC_CHECK_FUNCS(waitpid wait4 getcwd strdup chown chmod lchmod mknod mkfifo \
69 memmove lchown vsnprintf snprintf vasprintf asprintf setsid strpbrk \
70 strlcat strlcpy strtol mallinfo getgroups setgroups geteuid getegid \
71 setlocale setmode open64 lseek64 mkstemp64 mtrace va_copy __va_copy \
72 - strerror putenv iconv_open locale_charset nl_langinfo getxattr \
73 + chflags strerror putenv iconv_open locale_charset nl_langinfo getxattr \
74 extattr_get_link sigaction sigprocmask setattrlist)
76 dnl cygwin iconv.h defines iconv_open as libiconv_open
77 diff --git a/flist.c b/flist.c
80 @@ -52,6 +52,7 @@ extern int preserve_links;
81 extern int preserve_hard_links;
82 extern int preserve_devices;
83 extern int preserve_specials;
84 +extern int preserve_fileflags;
88 @@ -395,6 +396,9 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
90 static time_t modtime;
92 +#ifdef SUPPORT_FILEFLAGS
93 + static uint32 fileflags;
95 #ifdef SUPPORT_HARD_LINKS
98 @@ -424,6 +428,14 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
99 xflags |= XMIT_SAME_MODE;
102 +#ifdef SUPPORT_FILEFLAGS
103 + if (preserve_fileflags) {
104 + if (F_FFLAGS(file) == fileflags)
105 + xflags |= XMIT_SAME_FLAGS;
107 + fileflags = F_FFLAGS(file);
111 if ((preserve_devices && IS_DEVICE(mode))
112 || (preserve_specials && IS_SPECIAL(mode))) {
113 @@ -538,6 +550,10 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
115 if (!(xflags & XMIT_SAME_MODE))
116 write_int(f, to_wire_mode(mode));
117 +#ifdef SUPPORT_FILEFLAGS
118 + if (preserve_fileflags && !(xflags & XMIT_SAME_FLAGS))
119 + write_int(f, (int)fileflags);
121 if (preserve_uid && !(xflags & XMIT_SAME_UID)) {
122 if (protocol_version < 30)
124 @@ -624,6 +640,9 @@ static struct file_struct *recv_file_entry(struct file_list *flist,
126 static int64 modtime;
128 +#ifdef SUPPORT_FILEFLAGS
129 + static uint32 fileflags;
131 #ifdef SUPPORT_HARD_LINKS
134 @@ -759,6 +778,10 @@ static struct file_struct *recv_file_entry(struct file_list *flist,
136 if (chmod_modes && !S_ISLNK(mode))
137 mode = tweak_mode(mode, chmod_modes);
138 +#ifdef SUPPORT_FILEFLAGS
139 + if (preserve_fileflags && !(xflags & XMIT_SAME_FLAGS))
140 + fileflags = (uint32)read_int(f);
143 if (preserve_uid && !(xflags & XMIT_SAME_UID)) {
144 if (protocol_version < 30)
145 @@ -899,6 +922,10 @@ static struct file_struct *recv_file_entry(struct file_list *flist,
149 +#ifdef SUPPORT_FILEFLAGS
150 + if (preserve_fileflags)
151 + F_FFLAGS(file) = fileflags;
156 @@ -1273,6 +1300,10 @@ struct file_struct *make_file(const char *fname, struct file_list *flist,
159 file->mode = st.st_mode;
160 +#if defined SUPPORT_FILEFLAGS || defined SUPPORT_FORCE_CHANGE
162 + F_FFLAGS(file) = st.st_flags;
164 if (uid_ndx) /* Check uid_ndx instead of preserve_uid for del support */
165 F_OWNER(file) = st.st_uid;
166 if (gid_ndx) /* Check gid_ndx instead of preserve_gid for del support */
167 @@ -1427,6 +1458,7 @@ static struct file_struct *send_file_name(int f, struct file_list *flist,
169 #ifdef SUPPORT_XATTRS
170 if (preserve_xattrs) {
171 + sx.st.st_mode = file->mode;
173 if (get_xattr(fname, &sx) < 0) {
174 io_error |= IOERR_GENERAL;
175 diff --git a/generator.c b/generator.c
178 @@ -42,8 +42,10 @@ extern int preserve_devices;
179 extern int preserve_specials;
180 extern int preserve_hard_links;
181 extern int preserve_executability;
182 +extern int preserve_fileflags;
183 extern int preserve_perms;
184 extern int preserve_times;
185 +extern int force_change;
188 extern int delete_mode;
189 @@ -166,7 +168,7 @@ static enum delret delete_item(char *fbuf, uint16 mode, uint16 flags)
192 if (flags & DEL_NO_UID_WRITE)
193 - do_chmod(fbuf, mode | S_IWUSR);
194 + do_chmod(fbuf, mode | S_IWUSR, NO_FFLAGS);
196 if (S_ISDIR(mode) && !(flags & DEL_DIR_IS_EMPTY)) {
197 int save_uid_ndx = uid_ndx;
198 @@ -174,6 +176,13 @@ static enum delret delete_item(char *fbuf, uint16 mode, uint16 flags)
199 * delete_dir_contents() always calls us w/DEL_DIR_IS_EMPTY. */
201 uid_ndx = ++file_extra_cnt;
202 +#ifdef SUPPORT_FORCE_CHANGE
203 + if (force_change) {
205 + if (x_lstat(fbuf, &st, NULL) == 0)
206 + make_mutable(fbuf, st.st_mode, st.st_flags, force_change);
209 ignore_perishable = 1;
210 /* If DEL_RECURSE is not set, this just reports emptiness. */
211 ret = delete_dir_contents(fbuf, flags);
212 @@ -294,8 +303,12 @@ static enum delret delete_dir_contents(char *fname, uint16 flags)
215 strlcpy(p, fp->basename, remainder);
216 +#ifdef SUPPORT_FORCE_CHANGE
218 + make_mutable(fname, fp->mode, F_FFLAGS(fp), force_change);
220 if (!(fp->mode & S_IWUSR) && !am_root && (uid_t)F_OWNER(fp) == our_uid)
221 - do_chmod(fname, fp->mode | S_IWUSR);
222 + do_chmod(fname, fp->mode | S_IWUSR, NO_FFLAGS);
223 /* Save stack by recursing to ourself directly. */
224 if (S_ISDIR(fp->mode)) {
225 if (delete_dir_contents(fname, flags | DEL_RECURSE) != DR_SUCCESS)
226 @@ -596,6 +609,11 @@ int unchanged_attrs(const char *fname, struct file_struct *file, stat_x *sxp)
227 && ((sxp->st.st_mode & 0111 ? 1 : 0) ^ (file->mode & 0111 ? 1 : 0)))
230 +#ifdef SUPPORT_FILEFLAGS
231 + if (preserve_fileflags && !S_ISLNK(file->mode) && sxp->st.st_flags != F_FFLAGS(file))
235 if (am_root && uid_ndx && sxp->st.st_uid != (uid_t)F_OWNER(file))
238 @@ -661,6 +679,11 @@ void itemize(const char *fnamecmp, struct file_struct *file, int ndx, int statre
239 if (gid_ndx && !(file->flags & FLAG_SKIP_GROUP)
240 && sxp->st.st_gid != (gid_t)F_GROUP(file))
241 iflags |= ITEM_REPORT_GROUP;
242 +#ifdef SUPPORT_FILEFLAGS
243 + if (preserve_fileflags && !S_ISLNK(file->mode)
244 + && sxp->st.st_flags != F_FFLAGS(file))
245 + iflags |= ITEM_REPORT_FFLAGS;
248 if (preserve_acls && !S_ISLNK(file->mode)) {
249 if (!ACL_READY(*sxp))
250 @@ -1439,6 +1462,10 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
251 file->mode = dest_mode(file->mode, sx.st.st_mode,
252 dflt_perms, statret == 0);
254 +#ifdef SUPPORT_FORCE_CHANGE
255 + if (force_change && !preserve_fileflags)
256 + F_FFLAGS(file) = sx.st.st_flags;
258 if (statret != 0 && basis_dir[0] != NULL) {
259 int j = try_dests_non(file, fname, ndx, fnamecmpbuf, &sx,
261 @@ -1479,10 +1506,15 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
262 /* We need to ensure that the dirs in the transfer have writable
263 * permissions during the time we are putting files within them.
264 * This is then fixed after the transfer is done. */
265 +#ifdef SUPPORT_FORCE_CHANGE
266 + if (force_change && F_FFLAGS(file) & force_change
267 + && make_mutable(fname, file->mode, F_FFLAGS(file), force_change))
268 + need_retouch_dir_perms = 1;
271 if (!am_root && !(file->mode & S_IWUSR) && dir_tweaking) {
272 mode_t mode = file->mode | S_IWUSR;
273 - if (do_chmod(fname, mode) < 0) {
274 + if (do_chmod(fname, mode, 0) < 0) {
275 rsyserr(FERROR_XFER, errno,
276 "failed to modify permissions on %s",
278 @@ -1517,6 +1549,10 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
279 file->mode = dest_mode(file->mode, sx.st.st_mode, dflt_perms,
282 +#ifdef SUPPORT_FORCE_CHANGE
283 + if (force_change && !preserve_fileflags)
284 + F_FFLAGS(file) = sx.st.st_flags;
287 #ifdef SUPPORT_HARD_LINKS
288 if (preserve_hard_links && F_HLINK_NOT_FIRST(file)
289 @@ -2051,13 +2087,17 @@ static void touch_up_dirs(struct file_list *flist, int ndx)
291 fname = f_name(file, NULL);
292 if (!(file->mode & S_IWUSR))
293 - do_chmod(fname, file->mode);
294 + do_chmod(fname, file->mode, 0);
295 if (need_retouch_dir_times) {
297 if (link_stat(fname, &st, 0) == 0
298 && cmp_time(st.st_mtime, file->modtime) != 0)
299 - set_modtime(fname, file->modtime, file->mode);
300 + set_modtime(fname, file->modtime, file->mode, 0);
302 +#ifdef SUPPORT_FORCE_CHANGE
303 + if (force_change && F_FFLAGS(file) & force_change)
304 + undo_make_mutable(fname, F_FFLAGS(file));
306 if (counter >= loopchk_limit) {
308 maybe_send_keepalive();
309 diff --git a/log.c b/log.c
312 @@ -660,7 +660,7 @@ static void log_formatted(enum logcode code, const char *format, const char *op,
313 c[5] = !(iflags & ITEM_REPORT_PERMS) ? '.' : 'p';
314 c[6] = !(iflags & ITEM_REPORT_OWNER) ? '.' : 'o';
315 c[7] = !(iflags & ITEM_REPORT_GROUP) ? '.' : 'g';
316 - c[8] = !(iflags & ITEM_REPORT_ATIME) ? '.' : 'u';
317 + c[8] = !(iflags & ITEM_REPORT_FFLAGS) ? '.' : 'f';
318 c[9] = !(iflags & ITEM_REPORT_ACL) ? '.' : 'a';
319 c[10] = !(iflags & ITEM_REPORT_XATTR) ? '.' : 'x';
321 diff --git a/options.c b/options.c
324 @@ -53,6 +53,7 @@ int preserve_hard_links = 0;
325 int preserve_acls = 0;
326 int preserve_xattrs = 0;
327 int preserve_perms = 0;
328 +int preserve_fileflags = 0;
329 int preserve_executability = 0;
330 int preserve_devices = 0;
331 int preserve_specials = 0;
332 @@ -85,6 +86,7 @@ int implied_dirs = 1;
334 int allow_8bit_chars = 0;
335 int force_delete = 0;
336 +int force_change = 0;
338 int allowed_lull = 0;
339 int prune_empty_dirs = 0;
340 @@ -225,6 +227,7 @@ static void print_rsync_version(enum logcode f)
341 char const *links = "no ";
342 char const *iconv = "no ";
343 char const *ipv6 = "no ";
344 + char const *fileflags = "no ";
345 STRUCT_STAT *dumstat;
347 #if SUBPROTOCOL_VERSION != 0
348 @@ -257,6 +260,9 @@ static void print_rsync_version(enum logcode f)
349 #if defined HAVE_LUTIMES && defined HAVE_UTIMES
352 +#ifdef SUPPORT_FILEFLAGS
356 rprintf(f, "%s version %s protocol version %d%s\n",
357 RSYNC_NAME, RSYNC_VERSION, PROTOCOL_VERSION, subprotocol);
358 @@ -270,8 +276,8 @@ static void print_rsync_version(enum logcode f)
359 (int)(sizeof (int64) * 8));
360 rprintf(f, " %ssocketpairs, %shardlinks, %ssymlinks, %sIPv6, batchfiles, %sinplace,\n",
361 got_socketpair, hardlinks, links, ipv6, have_inplace);
362 - rprintf(f, " %sappend, %sACLs, %sxattrs, %siconv, %ssymtimes\n",
363 - have_inplace, acls, xattrs, iconv, symtimes);
364 + rprintf(f, " %sappend, %sACLs, %sxattrs, %siconv, %ssymtimes, %sfile-flags\n",
365 + have_inplace, acls, xattrs, iconv, symtimes, fileflags);
367 #ifdef MAINTAINER_MODE
368 rprintf(f, "Panic Action: \"%s\"\n", get_panic_action());
369 @@ -338,6 +344,9 @@ void usage(enum logcode F)
370 rprintf(F," -K, --keep-dirlinks treat symlinked dir on receiver as dir\n");
371 rprintf(F," -H, --hard-links preserve hard links\n");
372 rprintf(F," -p, --perms preserve permissions\n");
373 +#ifdef SUPPORT_FILEFLAGS
374 + rprintf(F," --fileflags preserve file-flags (aka chflags)\n");
376 rprintf(F," -E, --executability preserve the file's executability\n");
377 rprintf(F," --chmod=CHMOD affect file and/or directory permissions\n");
379 @@ -375,7 +384,12 @@ void usage(enum logcode F)
380 rprintf(F," --delete-after receiver deletes after transfer, not during\n");
381 rprintf(F," --delete-excluded also delete excluded files from destination dirs\n");
382 rprintf(F," --ignore-errors delete even if there are I/O errors\n");
383 - rprintf(F," --force force deletion of directories even if not empty\n");
384 + rprintf(F," --force-delete force deletion of directories even if not empty\n");
385 +#ifdef SUPPORT_FORCE_CHANGE
386 + rprintf(F," --force-change affect user-/system-immutable files/dirs\n");
387 + rprintf(F," --force-uchange affect user-immutable files/dirs\n");
388 + rprintf(F," --force-schange affect system-immutable files/dirs\n");
390 rprintf(F," --max-delete=NUM don't delete more than NUM files\n");
391 rprintf(F," --max-size=SIZE don't transfer any file larger than SIZE\n");
392 rprintf(F," --min-size=SIZE don't transfer any file smaller than SIZE\n");
393 @@ -480,6 +494,10 @@ static struct poptOption long_options[] = {
394 {"perms", 'p', POPT_ARG_VAL, &preserve_perms, 1, 0, 0 },
395 {"no-perms", 0, POPT_ARG_VAL, &preserve_perms, 0, 0, 0 },
396 {"no-p", 0, POPT_ARG_VAL, &preserve_perms, 0, 0, 0 },
397 +#ifdef SUPPORT_FILEFLAGS
398 + {"fileflags", 0, POPT_ARG_VAL, &preserve_fileflags, 1, 0, 0 },
399 + {"no-fileflags", 0, POPT_ARG_VAL, &preserve_fileflags, 0, 0, 0 },
401 {"executability", 'E', POPT_ARG_NONE, &preserve_executability, 0, 0, 0 },
402 {"acls", 'A', POPT_ARG_NONE, 0, 'A', 0, 0 },
403 {"no-acls", 0, POPT_ARG_VAL, &preserve_acls, 0, 0, 0 },
404 @@ -558,6 +576,14 @@ static struct poptOption long_options[] = {
405 {"remove-source-files",0,POPT_ARG_VAL, &remove_source_files, 1, 0, 0 },
406 {"force", 0, POPT_ARG_VAL, &force_delete, 1, 0, 0 },
407 {"no-force", 0, POPT_ARG_VAL, &force_delete, 0, 0, 0 },
408 + {"force-delete", 0, POPT_ARG_VAL, &force_delete, 1, 0, 0 },
409 + {"no-force-delete", 0, POPT_ARG_VAL, &force_delete, 0, 0, 0 },
410 +#ifdef SUPPORT_FORCE_CHANGE
411 + {"force-change", 0, POPT_ARG_VAL, &force_change, ALL_IMMUTABLE, 0, 0 },
412 + {"no-force-change", 0, POPT_ARG_VAL, &force_change, 0, 0, 0 },
413 + {"force-uchange", 0, POPT_ARG_VAL, &force_change, USR_IMMUTABLE, 0, 0 },
414 + {"force-schange", 0, POPT_ARG_VAL, &force_change, SYS_IMMUTABLE, 0, 0 },
416 {"ignore-errors", 0, POPT_ARG_VAL, &ignore_errors, 1, 0, 0 },
417 {"no-ignore-errors", 0, POPT_ARG_VAL, &ignore_errors, 0, 0, 0 },
418 {"max-delete", 0, POPT_ARG_INT, &max_delete, 0, 0, 0 },
419 @@ -1866,6 +1892,9 @@ void server_options(char **args, int *argc_p)
420 if (xfer_dirs && !recurse && delete_mode && am_sender)
421 args[ac++] = "--no-r";
423 + if (preserve_fileflags)
424 + args[ac++] = "--fileflags";
426 if (do_compression && def_compress_level != Z_DEFAULT_COMPRESSION) {
427 if (asprintf(&arg, "--compress-level=%d", def_compress_level) < 0)
429 @@ -1953,6 +1982,16 @@ void server_options(char **args, int *argc_p)
430 args[ac++] = "--delete-excluded";
432 args[ac++] = "--force";
433 +#ifdef SUPPORT_FORCE_CHANGE
434 + if (force_change) {
435 + if (force_change == ALL_IMMUTABLE)
436 + args[ac++] = "--force-change";
437 + else if (force_change == USR_IMMUTABLE)
438 + args[ac++] = "--force-uchange";
439 + else if (force_change == SYS_IMMUTABLE)
440 + args[ac++] = "--force-schange";
444 args[ac++] = "--only-write-batch=X";
446 diff --git a/rsync.c b/rsync.c
449 @@ -32,6 +32,7 @@ extern int dry_run;
450 extern int preserve_acls;
451 extern int preserve_xattrs;
452 extern int preserve_perms;
453 +extern int preserve_fileflags;
454 extern int preserve_executability;
455 extern int preserve_times;
457 @@ -374,6 +375,39 @@ mode_t dest_mode(mode_t flist_mode, mode_t stat_mode, int dflt_perms,
461 +#if defined SUPPORT_FILEFLAGS || defined SUPPORT_FORCE_CHANGE
462 +/* Set a file's st_flags. */
463 +static int set_fileflags(const char *fname, uint32 fileflags)
465 + if (do_chflags(fname, fileflags) != 0) {
466 + rsyserr(FERROR_XFER, errno,
467 + "failed to set file flags on %s",
468 + full_fname(fname));
475 +/* Remove immutable flags from an object, so it can be altered/removed. */
476 +int make_mutable(const char *fname, mode_t mode, uint32 fileflags, uint32 iflags)
478 + if (S_ISLNK(mode) || !(fileflags & iflags))
480 + if (!set_fileflags(fname, fileflags & ~iflags))
485 +/* Undo a prior make_mutable() call that returned a 1. */
486 +int undo_make_mutable(const char *fname, uint32 fileflags)
488 + if (!set_fileflags(fname, fileflags))
494 int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
495 const char *fnamecmp, int flags)
497 @@ -427,7 +461,7 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
498 flags |= ATTRS_SKIP_MTIME;
499 if (!(flags & ATTRS_SKIP_MTIME)
500 && cmp_time(sxp->st.st_mtime, file->modtime) != 0) {
501 - int ret = set_modtime(fname, file->modtime, sxp->st.st_mode);
502 + int ret = set_modtime(fname, file->modtime, sxp->st.st_mode, ST_FLAGS(sxp->st));
504 rsyserr(FERROR_XFER, errno, "failed to set times on %s",
506 @@ -463,7 +497,8 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
509 change_uid ? (uid_t)F_OWNER(file) : sxp->st.st_uid,
510 - change_gid ? (gid_t)F_GROUP(file) : sxp->st.st_gid) != 0) {
511 + change_gid ? (gid_t)F_GROUP(file) : sxp->st.st_gid,
512 + sxp->st.st_mode, ST_FLAGS(sxp->st)) != 0) {
513 /* We shouldn't have attempted to change uid
514 * or gid unless have the privilege. */
515 rsyserr(FERROR_XFER, errno, "%s %s failed",
516 @@ -495,7 +530,7 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
519 if (!BITS_EQUAL(sxp->st.st_mode, new_mode, CHMOD_BITS)) {
520 - int ret = am_root < 0 ? 0 : do_chmod(fname, new_mode);
521 + int ret = am_root < 0 ? 0 : do_chmod(fname, new_mode, ST_FLAGS(sxp->st));
523 rsyserr(FERROR_XFER, errno,
524 "failed to set permissions on %s",
525 @@ -507,6 +542,19 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
529 +#ifdef SUPPORT_FILEFLAGS
530 + if (preserve_fileflags && !S_ISLNK(sxp->st.st_mode)
531 + && sxp->st.st_flags != F_FFLAGS(file)) {
532 + uint32 fileflags = F_FFLAGS(file);
533 + if (flags & ATTRS_DELAY_IMMUTABLE)
534 + fileflags &= ~ALL_IMMUTABLE;
535 + if (sxp->st.st_flags != fileflags
536 + && !set_fileflags(fname, fileflags))
542 if (verbose > 1 && flags & ATTRS_REPORT) {
544 rprintf(FCLIENT, "%s\n", fname);
545 @@ -570,7 +618,8 @@ int finish_transfer(const char *fname, const char *fnametmp,
547 /* Change permissions before putting the file into place. */
548 set_file_attrs(fnametmp, file, NULL, fnamecmp,
549 - ok_to_set_time ? 0 : ATTRS_SKIP_MTIME);
550 + ATTRS_DELAY_IMMUTABLE
551 + | (ok_to_set_time ? 0 : ATTRS_SKIP_MTIME));
553 /* move tmp file over real file */
555 @@ -589,6 +638,10 @@ int finish_transfer(const char *fname, const char *fnametmp,
558 /* The file was moved into place (not copied), so it's done. */
559 +#ifdef SUPPORT_FILEFLAGS
560 + if (preserve_fileflags && F_FFLAGS(file) & ALL_IMMUTABLE)
561 + set_fileflags(fname, F_FFLAGS(file));
565 /* The file was copied, so tweak the perms of the copied file. If it
566 diff --git a/rsync.h b/rsync.h
570 #define XMIT_RDEV_MINOR_8_pre30 (1<<11) /* protocols 28 - 29 */
571 #define XMIT_GROUP_NAME_FOLLOWS (1<<11) /* protocols 30 - now */
572 #define XMIT_HLINK_FIRST (1<<12) /* protocols 30 - now (HLINKED files only) */
573 +#define XMIT_SAME_FLAGS (1<<14) /* protocols ?? - now */
575 /* These flags are used in the live flist data. */
579 #define ATTRS_REPORT (1<<0)
580 #define ATTRS_SKIP_MTIME (1<<1)
581 +#define ATTRS_DELAY_IMMUTABLE (1<<2)
584 #define NORMAL_FLUSH 0
586 #define ITEM_REPORT_GROUP (1<<6)
587 #define ITEM_REPORT_ACL (1<<7)
588 #define ITEM_REPORT_XATTR (1<<8)
589 +#define ITEM_REPORT_FFLAGS (1<<9)
590 #define ITEM_BASIS_TYPE_FOLLOWS (1<<11)
591 #define ITEM_XNAME_FOLLOWS (1<<12)
592 #define ITEM_IS_NEW (1<<13)
593 @@ -462,6 +465,28 @@ typedef unsigned int size_t;
597 +#define NO_FFLAGS ((uint32)-1)
600 +#define SUPPORT_FILEFLAGS 1
601 +#define SUPPORT_FORCE_CHANGE 1
604 +#if defined SUPPORT_FILEFLAGS || defined SUPPORT_FORCE_CHANGE
606 +#define UF_NOUNLINK 0
609 +#define SF_NOUNLINK 0
611 +#define USR_IMMUTABLE (UF_IMMUTABLE|UF_NOUNLINK|UF_APPEND)
612 +#define SYS_IMMUTABLE (SF_IMMUTABLE|SF_NOUNLINK|SF_APPEND)
613 +#define ALL_IMMUTABLE (USR_IMMUTABLE|SYS_IMMUTABLE)
614 +#define ST_FLAGS(st) (st.st_flags)
616 +#define ST_FLAGS(st) NO_FFLAGS
619 /* Find a variable that is either exactly 32-bits or longer.
620 * If some code depends on 32-bit truncation, it will need to
621 * take special action in a "#if SIZEOF_INT32 > 4" section. */
622 @@ -632,6 +657,7 @@ extern int file_extra_cnt;
623 extern int inc_recurse;
626 +extern int fileflags_ndx;
628 extern int xattrs_ndx;
630 @@ -669,6 +695,11 @@ extern int xattrs_ndx;
631 /* When the associated option is on, all entries will have these present: */
632 #define F_OWNER(f) REQ_EXTRA(f, uid_ndx)->unum
633 #define F_GROUP(f) REQ_EXTRA(f, gid_ndx)->unum
634 +#if defined SUPPORT_FILEFLAGS || defined SUPPORT_FORCE_CHANGE
635 +#define F_FFLAGS(f) REQ_EXTRA(f, fileflags_ndx)->unum
637 +#define F_FFLAGS(f) NO_FFLAGS
639 #define F_ACL(f) REQ_EXTRA(f, acls_ndx)->num
640 #define F_XATTR(f) REQ_EXTRA(f, xattrs_ndx)->num
641 #define F_NDX(f) REQ_EXTRA(f, unsort_ndx)->num
642 diff --git a/rsync.yo b/rsync.yo
645 @@ -338,6 +338,7 @@ to the detailed description below for a complete description. verb(
646 -K, --keep-dirlinks treat symlinked dir on receiver as dir
647 -H, --hard-links preserve hard links
648 -p, --perms preserve permissions
649 + --fileflags preserve file-flags (aka chflags)
650 -E, --executability preserve executability
651 --chmod=CHMOD affect file and/or directory permissions
652 -A, --acls preserve ACLs (implies -p)
653 @@ -369,7 +370,10 @@ to the detailed description below for a complete description. verb(
654 --delete-after receiver deletes after transfer, not before
655 --delete-excluded also delete excluded files from dest dirs
656 --ignore-errors delete even if there are I/O errors
657 - --force force deletion of dirs even if not empty
658 + --force-delete force deletion of dirs even if not empty
659 + --force-change affect user/system immutable files/dirs
660 + --force-uchange affect user-immutable files/dirs
661 + --force-schange affect system-immutable files/dirs
662 --max-delete=NUM don't delete more than NUM files
663 --max-size=SIZE don't transfer any file larger than SIZE
664 --min-size=SIZE don't transfer any file smaller than SIZE
665 @@ -540,7 +544,8 @@ specified, in which case bf(-r) is not implied.
667 Note that bf(-a) bf(does not preserve hardlinks), because
668 finding multiply-linked files is expensive. You must separately
670 +specify bf(-H). Note also that for backward compatibility, bf(-a)
671 +currently does bf(not) imply the bf(--fileflags) option.
673 dit(--no-OPTION) You may turn off one or more implied options by prefixing
674 the option name with "no-". Not all options may be prefixed with a "no-":
675 @@ -798,7 +803,7 @@ they would be using bf(--copy-links).
676 Without this option, if the sending side has replaced a directory with a
677 symlink to a directory, the receiving side will delete anything that is in
678 the way of the new symlink, including a directory hierarchy (as long as
679 -bf(--force) or bf(--delete) is in effect).
680 +bf(--force-delete) or bf(--delete) is in effect).
682 See also bf(--keep-dirlinks) for an analogous option for the receiving
684 @@ -935,6 +940,29 @@ super-user copies all namespaces except system.*. A normal user only copies
685 the user.* namespace. To be able to backup and restore non-user namespaces as
686 a normal user, see the bf(--fake-super) option.
688 +dit(bf(--fileflags)) This option causes rsync to update the file-flags to be
689 +the same as the source files and directories (if your OS supports the
690 +bf(chflags)(2) system call). Some flags can only be altered by the super-user
691 +and some might only be unset below a certain secure-level (usually single-user
692 +mode). It will not make files alterable that are set to immutable on the
693 +receiver. To do that, see bf(--force-change), bf(--force-uchange), and
694 +bf(--force-schange).
696 +dit(bf(--force-change)) This option causes rsync to disable both user-immutable
697 +and system-immutable flags on files and directories that are being updated or
698 +deleted on the receiving side. This option overrides bf(--force-uchange) and
699 +bf(--force-schange).
701 +dit(bf(--force-uchange)) This option causes rsync to disable user-immutable
702 +flags on files and directories that are being updated or deleted on the
703 +receiving side. It does not try to affect system flags. This option overrides
704 +bf(--force-change) and bf(--force-schange).
706 +dit(bf(--force-schange)) This option causes rsync to disable system-immutable
707 +flags on files and directories that are being updated or deleted on the
708 +receiving side. It does not try to affect user flags. This option overrides
709 +bf(--force-change) and bf(--force-schange).
711 dit(bf(--chmod)) This option tells rsync to apply one or more
712 comma-separated "chmod" strings to the permission of the files in the
713 transfer. The resulting value is treated as though it was the permissions
714 @@ -1197,12 +1225,13 @@ See bf(--delete) (which is implied) for more details on file-deletion.
715 dit(bf(--ignore-errors)) Tells bf(--delete) to go ahead and delete files
716 even when there are I/O errors.
718 -dit(bf(--force)) This option tells rsync to delete a non-empty directory
719 +dit(bf(--force-delete)) This option tells rsync to delete a non-empty directory
720 when it is to be replaced by a non-directory. This is only relevant if
721 deletions are not active (see bf(--delete) for details).
723 -Note for older rsync versions: bf(--force) used to still be required when
724 -using bf(--delete-after), and it used to be non-functional unless the
725 +This option can be abbreviated bf(--force) for backward compatibility.
726 +Note that some older rsync versions used to still require bf(--force)
727 +when using bf(--delete-after), and it used to be non-functional unless the
728 bf(--recursive) option was also enabled.
730 dit(bf(--max-delete=NUM)) This tells rsync not to delete more than NUM
731 @@ -1663,7 +1692,7 @@ with older versions of rsync, but that also turns on the output of other
734 The "%i" escape has a cryptic output that is 11 letters long. The general
735 -format is like the string bf(YXcstpoguax), where bf(Y) is replaced by the
736 +format is like the string bf(YXcstpogfax), where bf(Y) is replaced by the
737 type of update being done, bf(X) is replaced by the file-type, and the
738 other letters represent attributes that may be output if they are being
740 @@ -1719,7 +1748,7 @@ quote(itemization(
741 sender's value (requires bf(--owner) and super-user privileges).
742 it() A bf(g) means the group is different and is being updated to the
743 sender's value (requires bf(--group) and the authority to set the group).
744 - it() The bf(u) slot is reserved for future use.
745 + it() The bf(f) means that the fileflags information changed.
746 it() The bf(a) means that the ACL information changed.
747 it() The bf(x) means that the extended attribute information changed.
749 diff --git a/syscall.c b/syscall.c
752 @@ -33,6 +33,7 @@ extern int dry_run;
754 extern int read_only;
755 extern int list_only;
756 +extern int force_change;
757 extern int preserve_perms;
758 extern int preserve_executability;
760 @@ -50,7 +51,23 @@ int do_unlink(const char *fname)
762 if (dry_run) return 0;
763 RETURN_ERROR_IF_RO_OR_LO;
764 - return unlink(fname);
765 + if (unlink(fname) == 0)
767 +#ifdef SUPPORT_FORCE_CHANGE
768 + if (force_change && errno == EPERM) {
771 + if (x_lstat(fname, &st, NULL) == 0
772 + && make_mutable(fname, st.st_mode, st.st_flags, force_change) > 0) {
773 + if (unlink(fname) == 0)
775 + undo_make_mutable(fname, st.st_flags);
777 + /* TODO: handle immutable directories */
784 int do_symlink(const char *fname1, const char *fname2)
785 @@ -69,14 +86,37 @@ int do_link(const char *fname1, const char *fname2)
789 -int do_lchown(const char *path, uid_t owner, gid_t group)
790 +int do_lchown(const char *path, uid_t owner, gid_t group, mode_t mode, uint32 fileflags)
792 if (dry_run) return 0;
793 RETURN_ERROR_IF_RO_OR_LO;
797 - return lchown(path, owner, group);
798 + if (lchown(path, owner, group) == 0)
800 +#ifdef SUPPORT_FORCE_CHANGE
801 + if (force_change && errno == EPERM) {
802 + if (fileflags == NO_FFLAGS) {
804 + if (x_lstat(path, &st, NULL) == 0) {
806 + fileflags = st.st_flags;
809 + if (fileflags != NO_FFLAGS
810 + && make_mutable(path, mode, fileflags, force_change) > 0) {
811 + int ret = lchown(path, owner, group);
812 + undo_make_mutable(path, fileflags);
819 + mode = fileflags = 0; /* avoid compiler warning */
824 int do_mknod(const char *pathname, mode_t mode, dev_t dev)
825 @@ -116,7 +156,7 @@ int do_mknod(const char *pathname, mode_t mode, dev_t dev)
829 - return do_chmod(pathname, mode);
830 + return do_chmod(pathname, mode, 0);
834 @@ -133,7 +173,22 @@ int do_rmdir(const char *pathname)
836 if (dry_run) return 0;
837 RETURN_ERROR_IF_RO_OR_LO;
838 - return rmdir(pathname);
839 + if (rmdir(pathname) == 0)
841 +#ifdef SUPPORT_FORCE_CHANGE
842 + if (force_change && errno == EPERM) {
845 + if (x_lstat(pathname, &st, NULL) == 0
846 + && make_mutable(pathname, st.st_mode, st.st_flags, force_change) > 0) {
847 + if (rmdir(pathname) == 0)
849 + undo_make_mutable(pathname, st.st_flags);
857 int do_open(const char *pathname, int flags, mode_t mode)
858 @@ -147,7 +202,7 @@ int do_open(const char *pathname, int flags, mode_t mode)
862 -int do_chmod(const char *path, mode_t mode)
863 +int do_chmod(const char *path, mode_t mode, uint32 fileflags)
866 if (dry_run) return 0;
867 @@ -168,17 +223,74 @@ int do_chmod(const char *path, mode_t mode)
870 code = chmod(path, mode & CHMOD_BITS); /* DISCOURAGED FUNCTION */
871 +#ifdef SUPPORT_FORCE_CHANGE
872 + if (code < 0 && force_change && errno == EPERM && !S_ISLNK(mode)) {
873 + if (fileflags == NO_FFLAGS) {
875 + if (x_lstat(path, &st, NULL) == 0)
876 + fileflags = st.st_flags;
878 + if (fileflags != NO_FFLAGS
879 + && make_mutable(path, mode, fileflags, force_change) > 0) {
880 + code = chmod(path, mode & CHMOD_BITS);
881 + undo_make_mutable(path, fileflags);
888 + fileflags = 0; /* avoid compiler warning */
890 if (code != 0 && (preserve_perms || preserve_executability))
897 +int do_chflags(const char *path, uint32 fileflags)
899 + if (dry_run) return 0;
900 + RETURN_ERROR_IF_RO_OR_LO;
901 + return chflags(path, fileflags);
905 int do_rename(const char *fname1, const char *fname2)
907 if (dry_run) return 0;
908 RETURN_ERROR_IF_RO_OR_LO;
909 - return rename(fname1, fname2);
910 + if (rename(fname1, fname2) == 0)
912 +#ifdef SUPPORT_FORCE_CHANGE
913 + if (force_change && errno == EPERM) {
914 + STRUCT_STAT st1, st2;
915 + int became_mutable;
917 + if (x_lstat(fname1, &st1, NULL) != 0)
919 + became_mutable = make_mutable(fname1, st1.st_mode, st1.st_flags, force_change) > 0;
920 + if (became_mutable && rename(fname1, fname2) == 0)
922 + if (x_lstat(fname2, &st2, NULL) == 0
923 + && make_mutable(fname2, st2.st_mode, st2.st_flags, force_change) > 0) {
924 + if (rename(fname1, fname2) == 0) {
926 + if (became_mutable) /* Yes, use fname2 and st1! */
927 + undo_make_mutable(fname2, st1.st_flags);
930 + undo_make_mutable(fname2, st2.st_flags);
932 + /* TODO: handle immutable directories */
933 + if (became_mutable)
934 + undo_make_mutable(fname1, st1.st_flags);
942 void trim_trailing_slashes(char *name)
943 diff --git a/t_stub.c b/t_stub.c
946 @@ -26,6 +26,7 @@ int module_id = -1;
947 int relative_paths = 0;
948 int human_readable = 0;
949 int module_dirlen = 0;
950 +int force_change = 0;
951 int preserve_xattrs = 0;
952 mode_t orig_umask = 002;
954 @@ -89,3 +90,23 @@ struct filter_list_struct daemon_filter_list;
959 +#if defined SUPPORT_FILEFLAGS || defined SUPPORT_FORCE_CHANGE
960 + int make_mutable(UNUSED(const char *fname), UNUSED(mode_t mode), UNUSED(uint32 fileflags), UNUSED(uint32 iflags))
965 +/* Undo a prior make_mutable() call that returned a 1. */
966 + int undo_make_mutable(UNUSED(const char *fname), UNUSED(uint32 fileflags))
972 +#ifdef SUPPORT_XATTRS
973 + int x_lstat(UNUSED(const char *fname), UNUSED(STRUCT_STAT *fst), UNUSED(STRUCT_STAT *xst))
978 diff --git a/util.c b/util.c
981 @@ -29,6 +29,7 @@ extern int module_id;
982 extern int modify_window;
983 extern int relative_paths;
984 extern int human_readable;
985 +extern int force_change;
986 extern int preserve_xattrs;
987 extern char *module_dir;
988 extern unsigned int module_dirlen;
989 @@ -123,7 +124,7 @@ NORETURN void overflow_exit(const char *str)
990 exit_cleanup(RERR_MALLOC);
993 -int set_modtime(const char *fname, time_t modtime, mode_t mode)
994 +int set_modtime(const char *fname, time_t modtime, mode_t mode, uint32 fileflags)
996 #if !defined HAVE_LUTIMES || !defined HAVE_UTIMES
998 @@ -140,6 +141,7 @@ int set_modtime(const char *fname, time_t modtime, mode_t mode)
1004 struct timeval t[2];
1005 t[0].tv_sec = time(NULL);
1006 @@ -153,20 +155,39 @@ int set_modtime(const char *fname, time_t modtime, mode_t mode)
1010 - return utimes(fname, t);
1011 +#define SET_THE_TIME(fn) utimes(fn, t)
1012 #elif defined HAVE_STRUCT_UTIMBUF
1013 struct utimbuf tbuf;
1014 tbuf.actime = time(NULL);
1015 tbuf.modtime = modtime;
1016 - return utime(fname,&tbuf);
1017 +#define SET_THE_TIME(fn) utime(fn, &tbuf)
1018 #elif defined HAVE_UTIME
1022 - return utime(fname,t);
1023 +#define SET_THE_TIME(fn) utime(fn, t)
1025 #error No file-time-modification routine found!
1027 + ret = SET_THE_TIME(fname);
1028 +#ifdef SUPPORT_FORCE_CHANGE
1029 + if (ret != 0 && force_change && errno == EPERM) {
1030 + if (fileflags == NO_FFLAGS) {
1032 + if (x_lstat(fname, &st, NULL) == 0)
1033 + fileflags = st.st_flags;
1035 + if (fileflags != NO_FFLAGS
1036 + && make_mutable(fname, mode, fileflags, force_change) > 0) {
1037 + ret = SET_THE_TIME(fname);
1038 + undo_make_mutable(fname, fileflags);
1043 + fileflags = 0; /* avoid compiler warning */
1049 diff --git a/xattrs.c b/xattrs.c
1052 @@ -281,6 +281,10 @@ int get_xattr(const char *fname, stat_x *sxp)
1054 sxp->xattr = new(item_list);
1055 *sxp->xattr = empty_xattr;
1057 + if (IS_SPECIAL(sxp->st.st_mode) || IS_DEVICE(sxp->st.st_mode))
1060 if (rsync_xal_get(fname, sxp->xattr) < 0) {
1063 @@ -864,6 +868,11 @@ int set_xattr(const char *fname, const struct file_struct *file,
1067 + if (IS_SPECIAL(sxp->st.st_mode) || IS_DEVICE(sxp->st.st_mode)) {
1072 ndx = F_XATTR(file);
1073 return rsync_xal_set(fname, lst + ndx, fnamecmp, sxp);
1075 @@ -980,7 +989,7 @@ int set_stat_xattr(const char *fname, struct file_struct *file, mode_t new_mode)
1076 mode = (fst.st_mode & _S_IFMT) | (fmode & ACCESSPERMS)
1077 | (S_ISDIR(fst.st_mode) ? 0700 : 0600);
1078 if (fst.st_mode != mode)
1079 - do_chmod(fname, mode);
1080 + do_chmod(fname, mode, ST_FLAGS(fst));
1081 if (!IS_DEVICE(fst.st_mode) && !IS_SPECIAL(fst.st_mode))
1082 fst.st_rdev = 0; /* just in case */