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: 3b8f8192227b14e708bf535072485e50f4362270
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 @@ -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
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.in b/configure.in
69 @@ -574,7 +574,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 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 @@ -98,8 +99,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 && (uid_t)F_OWNER(fp) == our_uid)
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 @@ -140,7 +145,7 @@ 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 int save_uid_ndx = uid_ndx;
112 @@ -148,6 +153,13 @@ enum delret delete_item(char *fbuf, uint16 mode, uint16 flags)
113 * delete_dir_contents() always calls us w/DEL_DIR_IS_EMPTY. */
115 uid_ndx = ++file_extra_cnt;
116 +#ifdef SUPPORT_FORCE_CHANGE
117 + if (force_change) {
119 + if (x_lstat(fbuf, &st, NULL) == 0)
120 + make_mutable(fbuf, st.st_mode, st.st_flags, force_change);
123 ignore_perishable = 1;
124 /* If DEL_RECURSE is not set, this just reports emptiness. */
125 ret = delete_dir_contents(fbuf, flags);
126 diff --git a/flist.c b/flist.c
129 @@ -51,6 +51,7 @@ extern int preserve_links;
130 extern int preserve_hard_links;
131 extern int preserve_devices;
132 extern int preserve_specials;
133 +extern int preserve_fileflags;
134 extern int missing_args;
137 @@ -407,6 +408,9 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
139 static time_t modtime;
141 +#ifdef SUPPORT_FILEFLAGS
142 + static uint32 fileflags;
144 #ifdef SUPPORT_HARD_LINKS
147 @@ -450,6 +454,14 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
148 xflags |= XMIT_SAME_MODE;
151 +#ifdef SUPPORT_FILEFLAGS
152 + if (preserve_fileflags) {
153 + if (F_FFLAGS(file) == fileflags)
154 + xflags |= XMIT_SAME_FLAGS;
156 + fileflags = F_FFLAGS(file);
160 if (preserve_devices && IS_DEVICE(mode)) {
161 if (protocol_version < 28) {
162 @@ -591,6 +603,10 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
163 write_varint(f, F_MOD_NSEC(file));
164 if (!(xflags & XMIT_SAME_MODE))
165 write_int(f, to_wire_mode(mode));
166 +#ifdef SUPPORT_FILEFLAGS
167 + if (preserve_fileflags && !(xflags & XMIT_SAME_FLAGS))
168 + write_int(f, (int)fileflags);
170 if (preserve_uid && !(xflags & XMIT_SAME_UID)) {
171 if (protocol_version < 30)
173 @@ -678,6 +694,9 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x
175 static int64 modtime;
177 +#ifdef SUPPORT_FILEFLAGS
178 + static uint32 fileflags;
180 #ifdef SUPPORT_HARD_LINKS
183 @@ -823,6 +842,10 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x
185 if (chmod_modes && !S_ISLNK(mode) && mode)
186 mode = tweak_mode(mode, chmod_modes);
187 +#ifdef SUPPORT_FILEFLAGS
188 + if (preserve_fileflags && !(xflags & XMIT_SAME_FLAGS))
189 + fileflags = (uint32)read_int(f);
192 if (preserve_uid && !(xflags & XMIT_SAME_UID)) {
193 if (protocol_version < 30)
194 @@ -974,6 +997,10 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x
198 +#ifdef SUPPORT_FILEFLAGS
199 + if (preserve_fileflags)
200 + F_FFLAGS(file) = fileflags;
205 @@ -1371,6 +1398,10 @@ struct file_struct *make_file(const char *fname, struct file_list *flist,
208 file->mode = st.st_mode;
209 +#if defined SUPPORT_FILEFLAGS || defined SUPPORT_FORCE_CHANGE
211 + F_FFLAGS(file) = st.st_flags;
213 if (uid_ndx) /* Check uid_ndx instead of preserve_uid for del support */
214 F_OWNER(file) = st.st_uid;
215 if (gid_ndx) /* Check gid_ndx instead of preserve_gid for del support */
216 diff --git a/generator.c b/generator.c
219 @@ -42,8 +42,10 @@ extern int preserve_devices;
220 extern int preserve_specials;
221 extern int preserve_hard_links;
222 extern int preserve_executability;
223 +extern int preserve_fileflags;
224 extern int preserve_perms;
225 extern int preserve_times;
226 +extern int force_change;
229 extern int delete_mode;
230 @@ -407,6 +409,11 @@ int unchanged_attrs(const char *fname, struct file_struct *file, stat_x *sxp)
231 && ((sxp->st.st_mode & 0111 ? 1 : 0) ^ (file->mode & 0111 ? 1 : 0)))
234 +#ifdef SUPPORT_FILEFLAGS
235 + if (preserve_fileflags && !S_ISLNK(file->mode) && sxp->st.st_flags != F_FFLAGS(file))
239 if (am_root && uid_ndx && sxp->st.st_uid != (uid_t)F_OWNER(file))
242 @@ -472,6 +479,11 @@ void itemize(const char *fnamecmp, struct file_struct *file, int ndx, int statre
243 if (gid_ndx && !(file->flags & FLAG_SKIP_GROUP)
244 && sxp->st.st_gid != (gid_t)F_GROUP(file))
245 iflags |= ITEM_REPORT_GROUP;
246 +#ifdef SUPPORT_FILEFLAGS
247 + if (preserve_fileflags && !S_ISLNK(file->mode)
248 + && sxp->st.st_flags != F_FFLAGS(file))
249 + iflags |= ITEM_REPORT_FFLAGS;
252 if (preserve_acls && !S_ISLNK(file->mode)) {
253 if (!ACL_READY(*sxp))
254 @@ -1266,6 +1278,10 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
255 file->mode = dest_mode(file->mode, sx.st.st_mode,
256 dflt_perms, statret == 0);
258 +#ifdef SUPPORT_FORCE_CHANGE
259 + if (force_change && !preserve_fileflags)
260 + F_FFLAGS(file) = sx.st.st_flags;
262 if (statret != 0 && basis_dir[0] != NULL) {
263 int j = try_dests_non(file, fname, ndx, fnamecmpbuf, &sx,
265 @@ -1306,10 +1322,15 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
266 /* We need to ensure that the dirs in the transfer have writable
267 * permissions during the time we are putting files within them.
268 * This is then fixed after the transfer is done. */
269 +#ifdef SUPPORT_FORCE_CHANGE
270 + if (force_change && F_FFLAGS(file) & force_change
271 + && make_mutable(fname, file->mode, F_FFLAGS(file), force_change))
272 + need_retouch_dir_perms = 1;
275 if (!am_root && !(file->mode & S_IWUSR) && dir_tweaking) {
276 mode_t mode = file->mode | S_IWUSR;
277 - if (do_chmod(fname, mode) < 0) {
278 + if (do_chmod(fname, mode, 0) < 0) {
279 rsyserr(FERROR_XFER, errno,
280 "failed to modify permissions on %s",
282 @@ -1344,6 +1365,10 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
283 file->mode = dest_mode(file->mode, sx.st.st_mode, dflt_perms,
286 +#ifdef SUPPORT_FORCE_CHANGE
287 + if (force_change && !preserve_fileflags)
288 + F_FFLAGS(file) = sx.st.st_flags;
291 #ifdef SUPPORT_HARD_LINKS
292 if (preserve_hard_links && F_HLINK_NOT_FIRST(file)
293 @@ -1918,13 +1943,17 @@ static void touch_up_dirs(struct file_list *flist, int ndx)
295 fname = f_name(file, NULL);
296 if (!(file->mode & S_IWUSR))
297 - do_chmod(fname, file->mode);
298 + do_chmod(fname, file->mode, 0);
299 if (need_retouch_dir_times) {
301 if (link_stat(fname, &st, 0) == 0
302 && cmp_time(st.st_mtime, file->modtime) != 0)
303 - set_modtime(fname, file->modtime, F_MOD_NSEC(file), file->mode);
304 + set_modtime(fname, file->modtime, F_MOD_NSEC(file), file->mode, 0);
306 +#ifdef SUPPORT_FORCE_CHANGE
307 + if (force_change && F_FFLAGS(file) & force_change)
308 + undo_make_mutable(fname, F_FFLAGS(file));
310 if (counter >= loopchk_limit) {
312 maybe_send_keepalive();
313 diff --git a/log.c b/log.c
316 @@ -715,7 +715,7 @@ static void log_formatted(enum logcode code, const char *format, const char *op,
317 c[5] = !(iflags & ITEM_REPORT_PERMS) ? '.' : 'p';
318 c[6] = !(iflags & ITEM_REPORT_OWNER) ? '.' : 'o';
319 c[7] = !(iflags & ITEM_REPORT_GROUP) ? '.' : 'g';
320 - c[8] = !(iflags & ITEM_REPORT_ATIME) ? '.' : 'u';
321 + c[8] = !(iflags & ITEM_REPORT_FFLAGS) ? '.' : 'f';
322 c[9] = !(iflags & ITEM_REPORT_ACL) ? '.' : 'a';
323 c[10] = !(iflags & ITEM_REPORT_XATTR) ? '.' : 'x';
325 diff --git a/options.c b/options.c
328 @@ -53,6 +53,7 @@ int preserve_hard_links = 0;
329 int preserve_acls = 0;
330 int preserve_xattrs = 0;
331 int preserve_perms = 0;
332 +int preserve_fileflags = 0;
333 int preserve_executability = 0;
334 int preserve_devices = 0;
335 int preserve_specials = 0;
336 @@ -86,6 +87,7 @@ int numeric_ids = 0;
338 int allow_8bit_chars = 0;
339 int force_delete = 0;
340 +int force_change = 0;
342 int prune_empty_dirs = 0;
344 @@ -567,6 +569,7 @@ static void print_rsync_version(enum logcode f)
345 char const *links = "no ";
346 char const *iconv = "no ";
347 char const *ipv6 = "no ";
348 + char const *fileflags = "no ";
349 STRUCT_STAT *dumstat;
351 #if SUBPROTOCOL_VERSION != 0
352 @@ -600,6 +603,9 @@ static void print_rsync_version(enum logcode f)
353 #ifdef CAN_SET_SYMLINK_TIMES
356 +#ifdef SUPPORT_FILEFLAGS
360 rprintf(f, "%s version %s protocol version %d%s\n",
361 RSYNC_NAME, RSYNC_VERSION, PROTOCOL_VERSION, subprotocol);
362 @@ -613,8 +619,8 @@ static void print_rsync_version(enum logcode f)
363 (int)(sizeof (int64) * 8));
364 rprintf(f, " %ssocketpairs, %shardlinks, %ssymlinks, %sIPv6, batchfiles, %sinplace,\n",
365 got_socketpair, hardlinks, links, ipv6, have_inplace);
366 - rprintf(f, " %sappend, %sACLs, %sxattrs, %siconv, %ssymtimes\n",
367 - have_inplace, acls, xattrs, iconv, symtimes);
368 + rprintf(f, " %sappend, %sACLs, %sxattrs, %siconv, %ssymtimes, %sfile-flags\n",
369 + have_inplace, acls, xattrs, iconv, symtimes, fileflags);
371 #ifdef MAINTAINER_MODE
372 rprintf(f, "Panic Action: \"%s\"\n", get_panic_action());
373 @@ -684,6 +690,9 @@ void usage(enum logcode F)
374 rprintf(F," -K, --keep-dirlinks treat symlinked dir on receiver as dir\n");
375 rprintf(F," -H, --hard-links preserve hard links\n");
376 rprintf(F," -p, --perms preserve permissions\n");
377 +#ifdef SUPPORT_FILEFLAGS
378 + rprintf(F," --fileflags preserve file-flags (aka chflags)\n");
380 rprintf(F," -E, --executability preserve the file's executability\n");
381 rprintf(F," --chmod=CHMOD affect file and/or directory permissions\n");
383 @@ -723,7 +732,12 @@ void usage(enum logcode F)
384 rprintf(F," --ignore-missing-args ignore missing source args without error\n");
385 rprintf(F," --delete-missing-args delete missing source args from destination\n");
386 rprintf(F," --ignore-errors delete even if there are I/O errors\n");
387 - rprintf(F," --force force deletion of directories even if not empty\n");
388 + rprintf(F," --force-delete force deletion of directories even if not empty\n");
389 +#ifdef SUPPORT_FORCE_CHANGE
390 + rprintf(F," --force-change affect user-/system-immutable files/dirs\n");
391 + rprintf(F," --force-uchange affect user-immutable files/dirs\n");
392 + rprintf(F," --force-schange affect system-immutable files/dirs\n");
394 rprintf(F," --max-delete=NUM don't delete more than NUM files\n");
395 rprintf(F," --max-size=SIZE don't transfer any file larger than SIZE\n");
396 rprintf(F," --min-size=SIZE don't transfer any file smaller than SIZE\n");
397 @@ -836,6 +850,10 @@ static struct poptOption long_options[] = {
398 {"perms", 'p', POPT_ARG_VAL, &preserve_perms, 1, 0, 0 },
399 {"no-perms", 0, POPT_ARG_VAL, &preserve_perms, 0, 0, 0 },
400 {"no-p", 0, POPT_ARG_VAL, &preserve_perms, 0, 0, 0 },
401 +#ifdef SUPPORT_FILEFLAGS
402 + {"fileflags", 0, POPT_ARG_VAL, &preserve_fileflags, 1, 0, 0 },
403 + {"no-fileflags", 0, POPT_ARG_VAL, &preserve_fileflags, 0, 0, 0 },
405 {"executability", 'E', POPT_ARG_NONE, &preserve_executability, 0, 0, 0 },
406 {"acls", 'A', POPT_ARG_NONE, 0, 'A', 0, 0 },
407 {"no-acls", 0, POPT_ARG_VAL, &preserve_acls, 0, 0, 0 },
408 @@ -918,6 +936,14 @@ static struct poptOption long_options[] = {
409 {"remove-source-files",0,POPT_ARG_VAL, &remove_source_files, 1, 0, 0 },
410 {"force", 0, POPT_ARG_VAL, &force_delete, 1, 0, 0 },
411 {"no-force", 0, POPT_ARG_VAL, &force_delete, 0, 0, 0 },
412 + {"force-delete", 0, POPT_ARG_VAL, &force_delete, 1, 0, 0 },
413 + {"no-force-delete", 0, POPT_ARG_VAL, &force_delete, 0, 0, 0 },
414 +#ifdef SUPPORT_FORCE_CHANGE
415 + {"force-change", 0, POPT_ARG_VAL, &force_change, ALL_IMMUTABLE, 0, 0 },
416 + {"no-force-change", 0, POPT_ARG_VAL, &force_change, 0, 0, 0 },
417 + {"force-uchange", 0, POPT_ARG_VAL, &force_change, USR_IMMUTABLE, 0, 0 },
418 + {"force-schange", 0, POPT_ARG_VAL, &force_change, SYS_IMMUTABLE, 0, 0 },
420 {"ignore-errors", 0, POPT_ARG_VAL, &ignore_errors, 1, 0, 0 },
421 {"no-ignore-errors", 0, POPT_ARG_VAL, &ignore_errors, 0, 0, 0 },
422 {"max-delete", 0, POPT_ARG_INT, &max_delete, 0, 0, 0 },
423 @@ -2401,6 +2427,9 @@ void server_options(char **args, int *argc_p)
424 if (xfer_dirs && !recurse && delete_mode && am_sender)
425 args[ac++] = "--no-r";
427 + if (preserve_fileflags)
428 + args[ac++] = "--fileflags";
430 if (do_compression && def_compress_level != Z_DEFAULT_COMPRESSION) {
431 if (asprintf(&arg, "--compress-level=%d", def_compress_level) < 0)
433 @@ -2488,6 +2517,16 @@ void server_options(char **args, int *argc_p)
434 args[ac++] = "--delete-excluded";
436 args[ac++] = "--force";
437 +#ifdef SUPPORT_FORCE_CHANGE
438 + if (force_change) {
439 + if (force_change == ALL_IMMUTABLE)
440 + args[ac++] = "--force-change";
441 + else if (force_change == USR_IMMUTABLE)
442 + args[ac++] = "--force-uchange";
443 + else if (force_change == SYS_IMMUTABLE)
444 + args[ac++] = "--force-schange";
448 args[ac++] = "--only-write-batch=X";
450 diff --git a/rsync.c b/rsync.c
453 @@ -31,6 +31,7 @@ extern int dry_run;
454 extern int preserve_acls;
455 extern int preserve_xattrs;
456 extern int preserve_perms;
457 +extern int preserve_fileflags;
458 extern int preserve_executability;
459 extern int preserve_times;
461 @@ -428,6 +429,39 @@ mode_t dest_mode(mode_t flist_mode, mode_t stat_mode, int dflt_perms,
465 +#if defined SUPPORT_FILEFLAGS || defined SUPPORT_FORCE_CHANGE
466 +/* Set a file's st_flags. */
467 +static int set_fileflags(const char *fname, uint32 fileflags)
469 + if (do_chflags(fname, fileflags) != 0) {
470 + rsyserr(FERROR_XFER, errno,
471 + "failed to set file flags on %s",
472 + full_fname(fname));
479 +/* Remove immutable flags from an object, so it can be altered/removed. */
480 +int make_mutable(const char *fname, mode_t mode, uint32 fileflags, uint32 iflags)
482 + if (S_ISLNK(mode) || !(fileflags & iflags))
484 + if (!set_fileflags(fname, fileflags & ~iflags))
489 +/* Undo a prior make_mutable() call that returned a 1. */
490 +int undo_make_mutable(const char *fname, uint32 fileflags)
492 + if (!set_fileflags(fname, fileflags))
498 int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
499 const char *fnamecmp, int flags)
501 @@ -476,7 +510,7 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
502 flags |= ATTRS_SKIP_MTIME;
503 if (!(flags & ATTRS_SKIP_MTIME)
504 && cmp_time(sxp->st.st_mtime, file->modtime) != 0) {
505 - int ret = set_modtime(fname, file->modtime, F_MOD_NSEC(file), sxp->st.st_mode);
506 + int ret = set_modtime(fname, file->modtime, F_MOD_NSEC(file), sxp->st.st_mode, ST_FLAGS(sxp->st));
508 rsyserr(FERROR_XFER, errno, "failed to set times on %s",
510 @@ -512,7 +546,8 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
513 change_uid ? (uid_t)F_OWNER(file) : sxp->st.st_uid,
514 - change_gid ? (gid_t)F_GROUP(file) : sxp->st.st_gid) != 0) {
515 + change_gid ? (gid_t)F_GROUP(file) : sxp->st.st_gid,
516 + sxp->st.st_mode, ST_FLAGS(sxp->st)) != 0) {
517 /* We shouldn't have attempted to change uid
518 * or gid unless have the privilege. */
519 rsyserr(FERROR_XFER, errno, "%s %s failed",
520 @@ -546,7 +581,7 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
523 if (!BITS_EQUAL(sxp->st.st_mode, new_mode, CHMOD_BITS)) {
524 - int ret = am_root < 0 ? 0 : do_chmod(fname, new_mode);
525 + int ret = am_root < 0 ? 0 : do_chmod(fname, new_mode, ST_FLAGS(sxp->st));
527 rsyserr(FERROR_XFER, errno,
528 "failed to set permissions on %s",
529 @@ -558,6 +593,19 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
533 +#ifdef SUPPORT_FILEFLAGS
534 + if (preserve_fileflags && !S_ISLNK(sxp->st.st_mode)
535 + && sxp->st.st_flags != F_FFLAGS(file)) {
536 + uint32 fileflags = F_FFLAGS(file);
537 + if (flags & ATTRS_DELAY_IMMUTABLE)
538 + fileflags &= ~ALL_IMMUTABLE;
539 + if (sxp->st.st_flags != fileflags
540 + && !set_fileflags(fname, fileflags))
546 if (INFO_GTE(NAME, 2) && flags & ATTRS_REPORT) {
548 rprintf(FCLIENT, "%s\n", fname);
549 @@ -622,7 +670,8 @@ int finish_transfer(const char *fname, const char *fnametmp,
551 /* Change permissions before putting the file into place. */
552 set_file_attrs(fnametmp, file, NULL, fnamecmp,
553 - ok_to_set_time ? 0 : ATTRS_SKIP_MTIME);
554 + ATTRS_DELAY_IMMUTABLE
555 + | (ok_to_set_time ? 0 : ATTRS_SKIP_MTIME));
557 /* move tmp file over real file */
558 if (DEBUG_GTE(RECV, 1))
559 @@ -641,6 +690,10 @@ int finish_transfer(const char *fname, const char *fnametmp,
562 /* The file was moved into place (not copied), so it's done. */
563 +#ifdef SUPPORT_FILEFLAGS
564 + if (preserve_fileflags && F_FFLAGS(file) & ALL_IMMUTABLE)
565 + set_fileflags(fname, F_FFLAGS(file));
569 /* The file was copied, so tweak the perms of the copied file. If it
570 diff --git a/rsync.h b/rsync.h
574 #define XMIT_HLINK_FIRST (1<<12) /* protocols 30 - now (HLINKED files only) */
575 #define XMIT_IO_ERROR_ENDLIST (1<<12) /* protocols 31 - now (w/XMIT_EXTENDED_FLAGS) */
576 #define XMIT_MOD_NSEC (1<<13) /* protocols 31 - now */
577 +#define XMIT_SAME_FLAGS (1<<14) /* protocols ?? - now */
579 /* These flags are used in the live flist data. */
583 #define ATTRS_REPORT (1<<0)
584 #define ATTRS_SKIP_MTIME (1<<1)
585 +#define ATTRS_DELAY_IMMUTABLE (1<<2)
588 #define NORMAL_FLUSH 0
590 #define ITEM_REPORT_GROUP (1<<6)
591 #define ITEM_REPORT_ACL (1<<7)
592 #define ITEM_REPORT_XATTR (1<<8)
593 +#define ITEM_REPORT_FFLAGS (1<<9)
594 #define ITEM_BASIS_TYPE_FOLLOWS (1<<11)
595 #define ITEM_XNAME_FOLLOWS (1<<12)
596 #define ITEM_IS_NEW (1<<13)
597 @@ -490,6 +493,28 @@ typedef unsigned int size_t;
601 +#define NO_FFLAGS ((uint32)-1)
604 +#define SUPPORT_FILEFLAGS 1
605 +#define SUPPORT_FORCE_CHANGE 1
608 +#if defined SUPPORT_FILEFLAGS || defined SUPPORT_FORCE_CHANGE
610 +#define UF_NOUNLINK 0
613 +#define SF_NOUNLINK 0
615 +#define USR_IMMUTABLE (UF_IMMUTABLE|UF_NOUNLINK|UF_APPEND)
616 +#define SYS_IMMUTABLE (SF_IMMUTABLE|SF_NOUNLINK|SF_APPEND)
617 +#define ALL_IMMUTABLE (USR_IMMUTABLE|SYS_IMMUTABLE)
618 +#define ST_FLAGS(st) (st.st_flags)
620 +#define ST_FLAGS(st) NO_FFLAGS
623 /* Find a variable that is either exactly 32-bits or longer.
624 * If some code depends on 32-bit truncation, it will need to
625 * take special action in a "#if SIZEOF_INT32 > 4" section. */
626 @@ -660,6 +685,7 @@ extern int file_extra_cnt;
627 extern int inc_recurse;
630 +extern int fileflags_ndx;
632 extern int xattrs_ndx;
634 @@ -701,6 +727,11 @@ extern int xattrs_ndx;
635 /* When the associated option is on, all entries will have these present: */
636 #define F_OWNER(f) REQ_EXTRA(f, uid_ndx)->unum
637 #define F_GROUP(f) REQ_EXTRA(f, gid_ndx)->unum
638 +#if defined SUPPORT_FILEFLAGS || defined SUPPORT_FORCE_CHANGE
639 +#define F_FFLAGS(f) REQ_EXTRA(f, fileflags_ndx)->unum
641 +#define F_FFLAGS(f) NO_FFLAGS
643 #define F_ACL(f) REQ_EXTRA(f, acls_ndx)->num
644 #define F_XATTR(f) REQ_EXTRA(f, xattrs_ndx)->num
645 #define F_NDX(f) REQ_EXTRA(f, unsort_ndx)->num
646 diff --git a/rsync.yo b/rsync.yo
649 @@ -345,6 +345,7 @@ to the detailed description below for a complete description. verb(
650 -K, --keep-dirlinks treat symlinked dir on receiver as dir
651 -H, --hard-links preserve hard links
652 -p, --perms preserve permissions
653 + --fileflags preserve file-flags (aka chflags)
654 -E, --executability preserve executability
655 --chmod=CHMOD affect file and/or directory permissions
656 -A, --acls preserve ACLs (implies -p)
657 @@ -378,7 +379,10 @@ to the detailed description below for a complete description. verb(
658 --ignore-missing-args ignore missing source args without error
659 --delete-missing-args delete missing source args from destination
660 --ignore-errors delete even if there are I/O errors
661 - --force force deletion of dirs even if not empty
662 + --force-delete force deletion of dirs even if not empty
663 + --force-change affect user/system immutable files/dirs
664 + --force-uchange affect user-immutable files/dirs
665 + --force-schange affect system-immutable files/dirs
666 --max-delete=NUM don't delete more than NUM files
667 --max-size=SIZE don't transfer any file larger than SIZE
668 --min-size=SIZE don't transfer any file smaller than SIZE
669 @@ -595,7 +599,8 @@ specified, in which case bf(-r) is not implied.
671 Note that bf(-a) bf(does not preserve hardlinks), because
672 finding multiply-linked files is expensive. You must separately
674 +specify bf(-H). Note also that for backward compatibility, bf(-a)
675 +currently does bf(not) imply the bf(--fileflags) option.
677 dit(--no-OPTION) You may turn off one or more implied options by prefixing
678 the option name with "no-". Not all options may be prefixed with a "no-":
679 @@ -876,7 +881,7 @@ they would be using bf(--copy-links).
680 Without this option, if the sending side has replaced a directory with a
681 symlink to a directory, the receiving side will delete anything that is in
682 the way of the new symlink, including a directory hierarchy (as long as
683 -bf(--force) or bf(--delete) is in effect).
684 +bf(--force-delete) or bf(--delete) is in effect).
686 See also bf(--keep-dirlinks) for an analogous option for the receiving
688 @@ -1013,6 +1018,29 @@ super-user copies all namespaces except system.*. A normal user only copies
689 the user.* namespace. To be able to backup and restore non-user namespaces as
690 a normal user, see the bf(--fake-super) option.
692 +dit(bf(--fileflags)) This option causes rsync to update the file-flags to be
693 +the same as the source files and directories (if your OS supports the
694 +bf(chflags)(2) system call). Some flags can only be altered by the super-user
695 +and some might only be unset below a certain secure-level (usually single-user
696 +mode). It will not make files alterable that are set to immutable on the
697 +receiver. To do that, see bf(--force-change), bf(--force-uchange), and
698 +bf(--force-schange).
700 +dit(bf(--force-change)) This option causes rsync to disable both user-immutable
701 +and system-immutable flags on files and directories that are being updated or
702 +deleted on the receiving side. This option overrides bf(--force-uchange) and
703 +bf(--force-schange).
705 +dit(bf(--force-uchange)) This option causes rsync to disable user-immutable
706 +flags on files and directories that are being updated or deleted on the
707 +receiving side. It does not try to affect system flags. This option overrides
708 +bf(--force-change) and bf(--force-schange).
710 +dit(bf(--force-schange)) This option causes rsync to disable system-immutable
711 +flags on files and directories that are being updated or deleted on the
712 +receiving side. It does not try to affect user flags. This option overrides
713 +bf(--force-change) and bf(--force-schange).
715 dit(bf(--chmod)) This option tells rsync to apply one or more
716 comma-separated "chmod" strings to the permission of the files in the
717 transfer. The resulting value is treated as though it were the permissions
718 @@ -1301,12 +1329,13 @@ display as a "*missing" entry in the bf(--list-only) output.
719 dit(bf(--ignore-errors)) Tells bf(--delete) to go ahead and delete files
720 even when there are I/O errors.
722 -dit(bf(--force)) This option tells rsync to delete a non-empty directory
723 +dit(bf(--force-delete)) This option tells rsync to delete a non-empty directory
724 when it is to be replaced by a non-directory. This is only relevant if
725 deletions are not active (see bf(--delete) for details).
727 -Note for older rsync versions: bf(--force) used to still be required when
728 -using bf(--delete-after), and it used to be non-functional unless the
729 +This option can be abbreviated bf(--force) for backward compatibility.
730 +Note that some older rsync versions used to still require bf(--force)
731 +when using bf(--delete-after), and it used to be non-functional unless the
732 bf(--recursive) option was also enabled.
734 dit(bf(--max-delete=NUM)) This tells rsync not to delete more than NUM
735 @@ -1874,7 +1903,7 @@ with older versions of rsync, but that also turns on the output of other
738 The "%i" escape has a cryptic output that is 11 letters long. The general
739 -format is like the string bf(YXcstpoguax), where bf(Y) is replaced by the
740 +format is like the string bf(YXcstpogfax), where bf(Y) is replaced by the
741 type of update being done, bf(X) is replaced by the file-type, and the
742 other letters represent attributes that may be output if they are being
744 @@ -1930,7 +1959,7 @@ quote(itemization(
745 sender's value (requires bf(--owner) and super-user privileges).
746 it() A bf(g) means the group is different and is being updated to the
747 sender's value (requires bf(--group) and the authority to set the group).
748 - it() The bf(u) slot is reserved for future use.
749 + it() The bf(f) means that the fileflags information changed.
750 it() The bf(a) means that the ACL information changed.
751 it() The bf(x) means that the extended attribute information changed.
753 diff --git a/syscall.c b/syscall.c
756 @@ -34,6 +34,7 @@ extern int am_root;
757 extern int am_sender;
758 extern int read_only;
759 extern int list_only;
760 +extern int force_change;
761 extern int preserve_perms;
762 extern int preserve_executability;
764 @@ -51,7 +52,23 @@ int do_unlink(const char *fname)
766 if (dry_run) return 0;
767 RETURN_ERROR_IF_RO_OR_LO;
768 - return unlink(fname);
769 + if (unlink(fname) == 0)
771 +#ifdef SUPPORT_FORCE_CHANGE
772 + if (force_change && errno == EPERM) {
775 + if (x_lstat(fname, &st, NULL) == 0
776 + && make_mutable(fname, st.st_mode, st.st_flags, force_change) > 0) {
777 + if (unlink(fname) == 0)
779 + undo_make_mutable(fname, st.st_flags);
781 + /* TODO: handle immutable directories */
789 @@ -112,14 +129,37 @@ int do_link(const char *fname1, const char *fname2)
793 -int do_lchown(const char *path, uid_t owner, gid_t group)
794 +int do_lchown(const char *path, uid_t owner, gid_t group, mode_t mode, uint32 fileflags)
796 if (dry_run) return 0;
797 RETURN_ERROR_IF_RO_OR_LO;
801 - return lchown(path, owner, group);
802 + if (lchown(path, owner, group) == 0)
804 +#ifdef SUPPORT_FORCE_CHANGE
805 + if (force_change && errno == EPERM) {
806 + if (fileflags == NO_FFLAGS) {
808 + if (x_lstat(path, &st, NULL) == 0) {
810 + fileflags = st.st_flags;
813 + if (fileflags != NO_FFLAGS
814 + && make_mutable(path, mode, fileflags, force_change) > 0) {
815 + int ret = lchown(path, owner, group);
816 + undo_make_mutable(path, fileflags);
823 + mode = fileflags = 0; /* avoid compiler warning */
828 int do_mknod(const char *pathname, mode_t mode, dev_t dev)
829 @@ -159,7 +199,7 @@ int do_mknod(const char *pathname, mode_t mode, dev_t dev)
833 - return do_chmod(pathname, mode);
834 + return do_chmod(pathname, mode, 0);
838 @@ -176,7 +216,22 @@ int do_rmdir(const char *pathname)
840 if (dry_run) return 0;
841 RETURN_ERROR_IF_RO_OR_LO;
842 - return rmdir(pathname);
843 + if (rmdir(pathname) == 0)
845 +#ifdef SUPPORT_FORCE_CHANGE
846 + if (force_change && errno == EPERM) {
849 + if (x_lstat(pathname, &st, NULL) == 0
850 + && make_mutable(pathname, st.st_mode, st.st_flags, force_change) > 0) {
851 + if (rmdir(pathname) == 0)
853 + undo_make_mutable(pathname, st.st_flags);
861 int do_open(const char *pathname, int flags, mode_t mode)
862 @@ -190,7 +245,7 @@ int do_open(const char *pathname, int flags, mode_t mode)
866 -int do_chmod(const char *path, mode_t mode)
867 +int do_chmod(const char *path, mode_t mode, uint32 fileflags)
870 if (dry_run) return 0;
871 @@ -211,17 +266,74 @@ int do_chmod(const char *path, mode_t mode)
874 code = chmod(path, mode & CHMOD_BITS); /* DISCOURAGED FUNCTION */
875 +#ifdef SUPPORT_FORCE_CHANGE
876 + if (code < 0 && force_change && errno == EPERM && !S_ISLNK(mode)) {
877 + if (fileflags == NO_FFLAGS) {
879 + if (x_lstat(path, &st, NULL) == 0)
880 + fileflags = st.st_flags;
882 + if (fileflags != NO_FFLAGS
883 + && make_mutable(path, mode, fileflags, force_change) > 0) {
884 + code = chmod(path, mode & CHMOD_BITS);
885 + undo_make_mutable(path, fileflags);
892 + fileflags = 0; /* avoid compiler warning */
894 if (code != 0 && (preserve_perms || preserve_executability))
901 +int do_chflags(const char *path, uint32 fileflags)
903 + if (dry_run) return 0;
904 + RETURN_ERROR_IF_RO_OR_LO;
905 + return chflags(path, fileflags);
909 int do_rename(const char *fname1, const char *fname2)
911 if (dry_run) return 0;
912 RETURN_ERROR_IF_RO_OR_LO;
913 - return rename(fname1, fname2);
914 + if (rename(fname1, fname2) == 0)
916 +#ifdef SUPPORT_FORCE_CHANGE
917 + if (force_change && errno == EPERM) {
918 + STRUCT_STAT st1, st2;
919 + int became_mutable;
921 + if (x_lstat(fname1, &st1, NULL) != 0)
923 + became_mutable = make_mutable(fname1, st1.st_mode, st1.st_flags, force_change) > 0;
924 + if (became_mutable && rename(fname1, fname2) == 0)
926 + if (x_lstat(fname2, &st2, NULL) == 0
927 + && make_mutable(fname2, st2.st_mode, st2.st_flags, force_change) > 0) {
928 + if (rename(fname1, fname2) == 0) {
930 + if (became_mutable) /* Yes, use fname2 and st1! */
931 + undo_make_mutable(fname2, st1.st_flags);
934 + undo_make_mutable(fname2, st2.st_flags);
936 + /* TODO: handle immutable directories */
937 + if (became_mutable)
938 + undo_make_mutable(fname1, st1.st_flags);
946 void trim_trailing_slashes(char *name)
947 diff --git a/t_stub.c b/t_stub.c
950 @@ -25,6 +25,7 @@ int modify_window = 0;
952 int relative_paths = 0;
953 int module_dirlen = 0;
954 +int force_change = 0;
955 int preserve_xattrs = 0;
956 mode_t orig_umask = 002;
957 char number_separator = ',';
958 @@ -84,3 +85,23 @@ filter_rule_list daemon_filter_list;
963 +#if defined SUPPORT_FILEFLAGS || defined SUPPORT_FORCE_CHANGE
964 + int make_mutable(UNUSED(const char *fname), UNUSED(mode_t mode), UNUSED(uint32 fileflags), UNUSED(uint32 iflags))
969 +/* Undo a prior make_mutable() call that returned a 1. */
970 + int undo_make_mutable(UNUSED(const char *fname), UNUSED(uint32 fileflags))
976 +#ifdef SUPPORT_XATTRS
977 + int x_lstat(UNUSED(const char *fname), UNUSED(STRUCT_STAT *fst), UNUSED(STRUCT_STAT *xst))
982 diff --git a/util.c b/util.c
985 @@ -30,6 +30,7 @@ extern int module_id;
986 extern int modify_window;
987 extern int relative_paths;
988 extern int preserve_xattrs;
989 +extern int force_change;
990 extern char *module_dir;
991 extern unsigned int module_dirlen;
992 extern mode_t orig_umask;
993 @@ -123,7 +124,7 @@ NORETURN void overflow_exit(const char *str)
994 exit_cleanup(RERR_MALLOC);
997 -int set_modtime(const char *fname, time_t modtime, uint32 mod_nsec, mode_t mode)
998 +int set_modtime(const char *fname, time_t modtime, uint32 mod_nsec, mode_t mode, uint32 fileflags)
1000 #ifndef CAN_SET_SYMLINK_TIMES
1002 @@ -140,15 +141,14 @@ int set_modtime(const char *fname, time_t modtime, uint32 mod_nsec, mode_t mode)
1007 #ifdef HAVE_UTIMENSAT
1008 struct timespec t[2];
1010 t[0].tv_nsec = UTIME_NOW;
1011 t[1].tv_sec = modtime;
1012 t[1].tv_nsec = mod_nsec;
1013 - if (utimensat(AT_FDCWD, fname, t, AT_SYMLINK_NOFOLLOW) < 0)
1014 - return S_ISLNK(mode) && errno == ENOSYS ? 1 : -1;
1016 +#define SET_THE_TIME(fn) utimensat(AT_FDCWD, fn, t, AT_SYMLINK_NOFOLLOW)
1017 #elif defined HAVE_UTIMES || defined HAVE_LUTIMES
1018 struct timeval t[2];
1019 t[0].tv_sec = time(NULL);
1020 @@ -156,25 +156,44 @@ int set_modtime(const char *fname, time_t modtime, uint32 mod_nsec, mode_t mode)
1021 t[1].tv_sec = modtime;
1022 t[1].tv_usec = mod_nsec / 1000;
1023 # ifdef HAVE_LUTIMES
1024 - if (lutimes(fname, t) < 0)
1025 - return S_ISLNK(mode) && errno == ENOSYS ? 1 : -1;
1027 +#define SET_THE_TIME(fn) lutimes(fn, t)
1029 - return utimes(fname, t);
1030 +#define SET_THE_TIME(fn) utimes(fn, t)
1032 #elif defined HAVE_STRUCT_UTIMBUF
1033 struct utimbuf tbuf;
1034 tbuf.actime = time(NULL);
1035 tbuf.modtime = modtime;
1036 - return utime(fname,&tbuf);
1037 +#define SET_THE_TIME(fn) utime(fn, &tbuf)
1038 #elif defined HAVE_UTIME
1042 - return utime(fname,t);
1043 +#define SET_THE_TIME(fn) utime(fn, t)
1045 #error No file-time-modification routine found!
1047 + ret = SET_THE_TIME(fname);
1048 + if (ret != 0 && S_ISLNK(mode) && errno == ENOSYS)
1050 +#ifdef SUPPORT_FORCE_CHANGE
1051 + if (ret != 0 && force_change && errno == EPERM) {
1052 + if (fileflags == NO_FFLAGS) {
1054 + if (x_lstat(fname, &st, NULL) == 0)
1055 + fileflags = st.st_flags;
1057 + if (fileflags != NO_FFLAGS
1058 + && make_mutable(fname, mode, fileflags, force_change) > 0) {
1059 + ret = SET_THE_TIME(fname);
1060 + undo_make_mutable(fname, fileflags);
1065 + fileflags = 0; /* avoid compiler warning */
1071 diff --git a/xattrs.c b/xattrs.c
1074 @@ -1033,7 +1033,7 @@ int set_stat_xattr(const char *fname, struct file_struct *file, mode_t new_mode)
1075 mode = (fst.st_mode & _S_IFMT) | (fmode & ACCESSPERMS)
1076 | (S_ISDIR(fst.st_mode) ? 0700 : 0600);
1077 if (fst.st_mode != mode)
1078 - do_chmod(fname, mode);
1079 + do_chmod(fname, mode, ST_FLAGS(fst));
1080 if (!IS_DEVICE(fst.st_mode))
1081 fst.st_rdev = 0; /* just in case */