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: 8b7f20b6d35ee7458665c33114919567f5793503
12 diff --git a/Makefile.in b/Makefile.in
15 @@ -42,7 +42,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 @@ -107,7 +107,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 @@ -43,9 +43,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 @@ -63,7 +65,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 @@ -140,6 +142,8 @@ void setup_protocol(int f_out,int f_in)
58 uid_ndx = ++file_extra_cnt;
60 gid_ndx = ++file_extra_cnt;
61 + if (preserve_fileflags || (force_change && !am_sender))
62 + fileflags_ndx = ++file_extra_cnt;
63 if (preserve_acls && !am_sender)
64 acls_ndx = ++file_extra_cnt;
66 diff --git a/configure.ac b/configure.ac
69 @@ -567,6 +567,7 @@ AC_FUNC_UTIME_NULL
71 AC_CHECK_FUNCS(waitpid wait4 getcwd strdup chown chmod lchmod mknod mkfifo \
72 fchmod fstat ftruncate strchr readlink link utime utimes lutimes strftime \
74 memmove lchown vsnprintf snprintf vasprintf asprintf setsid strpbrk \
75 strlcat strlcpy strtol mallinfo getgroups setgroups geteuid getegid \
76 setlocale setmode open64 lseek64 mkstemp64 mtrace va_copy __va_copy \
77 diff --git a/flist.c b/flist.c
80 @@ -51,6 +51,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;
85 extern int delete_during;
87 extern int relative_paths;
88 @@ -394,6 +395,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 @@ -423,6 +427,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 if (protocol_version < 28) {
113 @@ -547,6 +559,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 @@ -634,6 +650,9 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x
126 static int64 modtime;
128 +#ifdef SUPPORT_FILEFLAGS
129 + static uint32 fileflags;
131 #ifdef SUPPORT_HARD_LINKS
134 @@ -768,6 +787,10 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x
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 @@ -909,6 +932,10 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x
149 +#ifdef SUPPORT_FILEFLAGS
150 + if (preserve_fileflags)
151 + F_FFLAGS(file) = fileflags;
156 @@ -1283,6 +1310,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;
165 F_OWNER(file) = st.st_uid;
167 diff --git a/generator.c b/generator.c
170 @@ -42,8 +42,10 @@ extern int preserve_devices;
171 extern int preserve_specials;
172 extern int preserve_hard_links;
173 extern int preserve_executability;
174 +extern int preserve_fileflags;
175 extern int preserve_perms;
176 extern int preserve_times;
177 +extern int force_change;
178 extern int delete_mode;
179 extern int delete_before;
180 extern int delete_during;
181 @@ -164,11 +166,18 @@ static enum delret delete_item(char *fbuf, uint16 mode, uint16 flags)
184 if (flags & DEL_NO_UID_WRITE)
185 - do_chmod(fbuf, mode | S_IWUSR);
186 + do_chmod(fbuf, mode | S_IWUSR, NO_FFLAGS);
188 if (S_ISDIR(mode) && !(flags & DEL_DIR_IS_EMPTY)) {
189 /* This only happens on the first call to delete_item() since
190 * delete_dir_contents() always calls us w/DEL_DIR_IS_EMPTY. */
191 +#ifdef SUPPORT_FORCE_CHANGE
192 + if (force_change) {
194 + if (x_lstat(fbuf, &st, NULL) == 0)
195 + make_mutable(fbuf, st.st_mode, st.st_flags, force_change);
198 ignore_perishable = 1;
199 /* If DEL_RECURSE is not set, this just reports emptiness. */
200 ret = delete_dir_contents(fbuf, flags);
201 @@ -285,8 +294,12 @@ static enum delret delete_dir_contents(char *fname, uint16 flags)
204 strlcpy(p, fp->basename, remainder);
205 +#ifdef SUPPORT_FORCE_CHANGE
207 + make_mutable(fname, fp->mode, F_FFLAGS(fp), force_change);
209 if (!(fp->mode & S_IWUSR) && !am_root && fp->flags & FLAG_OWNED_BY_US)
210 - do_chmod(fname, fp->mode | S_IWUSR);
211 + do_chmod(fname, fp->mode | S_IWUSR, NO_FFLAGS);
212 /* Save stack by recursing to ourself directly. */
213 if (S_ISDIR(fp->mode)) {
214 if (delete_dir_contents(fname, flags | DEL_RECURSE) != DR_SUCCESS)
215 @@ -647,6 +660,10 @@ int unchanged_attrs(const char *fname, struct file_struct *file, stat_x *sxp)
217 if (perms_differ(file, sxp))
219 +#ifdef SUPPORT_FILEFLAGS
220 + if (preserve_fileflags && sxp->st.st_flags != F_FFLAGS(file))
223 if (ownership_differs(file, sxp))
226 @@ -698,6 +715,11 @@ void itemize(const char *fnamecmp, struct file_struct *file, int ndx, int statre
227 if (gid_ndx && !(file->flags & FLAG_SKIP_GROUP)
228 && sxp->st.st_gid != (gid_t)F_GROUP(file))
229 iflags |= ITEM_REPORT_GROUP;
230 +#ifdef SUPPORT_FILEFLAGS
231 + if (preserve_fileflags && !S_ISLNK(file->mode)
232 + && sxp->st.st_flags != F_FFLAGS(file))
233 + iflags |= ITEM_REPORT_FFLAGS;
236 if (preserve_acls && !S_ISLNK(file->mode)) {
237 if (!ACL_READY(*sxp))
238 @@ -1491,6 +1513,10 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
239 file->mode = dest_mode(file->mode, sx.st.st_mode,
240 dflt_perms, statret == 0);
242 +#ifdef SUPPORT_FORCE_CHANGE
243 + if (force_change && !preserve_fileflags)
244 + F_FFLAGS(file) = sx.st.st_flags;
246 if (statret != 0 && basis_dir[0] != NULL) {
247 int j = try_dests_non(file, fname, ndx, fnamecmpbuf, &sx,
249 @@ -1533,10 +1559,15 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
250 /* We need to ensure that the dirs in the transfer have writable
251 * permissions during the time we are putting files within them.
252 * This is then fixed after the transfer is done. */
253 +#ifdef SUPPORT_FORCE_CHANGE
254 + if (force_change && F_FFLAGS(file) & force_change
255 + && make_mutable(fname, file->mode, F_FFLAGS(file), force_change))
256 + need_retouch_dir_perms = 1;
259 if (!am_root && !(file->mode & S_IWUSR) && dir_tweaking) {
260 mode_t mode = file->mode | S_IWUSR;
261 - if (do_chmod(fname, mode) < 0) {
262 + if (do_chmod(fname, mode, 0) < 0) {
263 rsyserr(FERROR_XFER, errno,
264 "failed to modify permissions on %s",
266 @@ -1571,6 +1602,10 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
267 file->mode = dest_mode(file->mode, sx.st.st_mode, dflt_perms,
270 +#ifdef SUPPORT_FORCE_CHANGE
271 + if (force_change && !preserve_fileflags)
272 + F_FFLAGS(file) = sx.st.st_flags;
275 #ifdef SUPPORT_HARD_LINKS
276 if (preserve_hard_links && F_HLINK_NOT_FIRST(file)
277 @@ -2114,13 +2149,17 @@ static void touch_up_dirs(struct file_list *flist, int ndx)
279 fname = f_name(file, NULL);
281 - do_chmod(fname, file->mode);
282 + do_chmod(fname, file->mode, 0);
283 if (need_retouch_dir_times) {
285 if (link_stat(fname, &st, 0) == 0
286 && cmp_time(st.st_mtime, file->modtime) != 0)
287 - set_modtime(fname, file->modtime, file->mode);
288 + set_modtime(fname, file->modtime, file->mode, 0);
290 +#ifdef SUPPORT_FORCE_CHANGE
291 + if (force_change && F_FFLAGS(file) & force_change)
292 + undo_make_mutable(fname, F_FFLAGS(file));
294 if (counter >= loopchk_limit) {
296 maybe_send_keepalive();
297 diff --git a/log.c b/log.c
300 @@ -658,7 +658,7 @@ static void log_formatted(enum logcode code, const char *format, const char *op,
301 c[5] = !(iflags & ITEM_REPORT_PERMS) ? '.' : 'p';
302 c[6] = !(iflags & ITEM_REPORT_OWNER) ? '.' : 'o';
303 c[7] = !(iflags & ITEM_REPORT_GROUP) ? '.' : 'g';
304 - c[8] = !(iflags & ITEM_REPORT_ATIME) ? '.' : 'u';
305 + c[8] = !(iflags & ITEM_REPORT_FFLAGS) ? '.' : 'f';
306 c[9] = !(iflags & ITEM_REPORT_ACL) ? '.' : 'a';
307 c[10] = !(iflags & ITEM_REPORT_XATTR) ? '.' : 'x';
309 diff --git a/options.c b/options.c
312 @@ -53,6 +53,7 @@ int preserve_hard_links = 0;
313 int preserve_acls = 0;
314 int preserve_xattrs = 0;
315 int preserve_perms = 0;
316 +int preserve_fileflags = 0;
317 int preserve_executability = 0;
318 int preserve_devices = 0;
319 int preserve_specials = 0;
320 @@ -84,6 +85,7 @@ int implied_dirs = 1;
322 int allow_8bit_chars = 0;
323 int force_delete = 0;
324 +int force_change = 0;
326 int prune_empty_dirs = 0;
328 @@ -223,6 +225,7 @@ static void print_rsync_version(enum logcode f)
329 char const *links = "no ";
330 char const *iconv = "no ";
331 char const *ipv6 = "no ";
332 + char const *fileflags = "no ";
333 STRUCT_STAT *dumstat;
335 #if SUBPROTOCOL_VERSION != 0
336 @@ -256,6 +259,9 @@ static void print_rsync_version(enum logcode f)
337 #ifdef CAN_SET_SYMLINK_TIMES
340 +#ifdef SUPPORT_FILEFLAGS
344 rprintf(f, "%s version %s protocol version %d%s\n",
345 RSYNC_NAME, RSYNC_VERSION, PROTOCOL_VERSION, subprotocol);
346 @@ -269,8 +275,8 @@ static void print_rsync_version(enum logcode f)
347 (int)(sizeof (int64) * 8));
348 rprintf(f, " %ssocketpairs, %shardlinks, %ssymlinks, %sIPv6, batchfiles, %sinplace,\n",
349 got_socketpair, hardlinks, links, ipv6, have_inplace);
350 - rprintf(f, " %sappend, %sACLs, %sxattrs, %siconv, %ssymtimes\n",
351 - have_inplace, acls, xattrs, iconv, symtimes);
352 + rprintf(f, " %sappend, %sACLs, %sxattrs, %siconv, %ssymtimes, %sfile-flags\n",
353 + have_inplace, acls, xattrs, iconv, symtimes, fileflags);
355 #ifdef MAINTAINER_MODE
356 rprintf(f, "Panic Action: \"%s\"\n", get_panic_action());
357 @@ -337,6 +343,9 @@ void usage(enum logcode F)
358 rprintf(F," -K, --keep-dirlinks treat symlinked dir on receiver as dir\n");
359 rprintf(F," -H, --hard-links preserve hard links\n");
360 rprintf(F," -p, --perms preserve permissions\n");
361 +#ifdef SUPPORT_FILEFLAGS
362 + rprintf(F," --fileflags preserve file-flags (aka chflags)\n");
364 rprintf(F," -E, --executability preserve the file's executability\n");
365 rprintf(F," --chmod=CHMOD affect file and/or directory permissions\n");
367 @@ -374,7 +383,12 @@ void usage(enum logcode F)
368 rprintf(F," --delete-after receiver deletes after transfer, not during\n");
369 rprintf(F," --delete-excluded also delete excluded files from destination dirs\n");
370 rprintf(F," --ignore-errors delete even if there are I/O errors\n");
371 - rprintf(F," --force force deletion of directories even if not empty\n");
372 + rprintf(F," --force-delete force deletion of directories even if not empty\n");
373 +#ifdef SUPPORT_FORCE_CHANGE
374 + rprintf(F," --force-change affect user-/system-immutable files/dirs\n");
375 + rprintf(F," --force-uchange affect user-immutable files/dirs\n");
376 + rprintf(F," --force-schange affect system-immutable files/dirs\n");
378 rprintf(F," --max-delete=NUM don't delete more than NUM files\n");
379 rprintf(F," --max-size=SIZE don't transfer any file larger than SIZE\n");
380 rprintf(F," --min-size=SIZE don't transfer any file smaller than SIZE\n");
381 @@ -479,6 +493,10 @@ static struct poptOption long_options[] = {
382 {"perms", 'p', POPT_ARG_VAL, &preserve_perms, 1, 0, 0 },
383 {"no-perms", 0, POPT_ARG_VAL, &preserve_perms, 0, 0, 0 },
384 {"no-p", 0, POPT_ARG_VAL, &preserve_perms, 0, 0, 0 },
385 +#ifdef SUPPORT_FILEFLAGS
386 + {"fileflags", 0, POPT_ARG_VAL, &preserve_fileflags, 1, 0, 0 },
387 + {"no-fileflags", 0, POPT_ARG_VAL, &preserve_fileflags, 0, 0, 0 },
389 {"executability", 'E', POPT_ARG_NONE, &preserve_executability, 0, 0, 0 },
390 {"acls", 'A', POPT_ARG_NONE, 0, 'A', 0, 0 },
391 {"no-acls", 0, POPT_ARG_VAL, &preserve_acls, 0, 0, 0 },
392 @@ -557,6 +575,14 @@ static struct poptOption long_options[] = {
393 {"remove-source-files",0,POPT_ARG_VAL, &remove_source_files, 1, 0, 0 },
394 {"force", 0, POPT_ARG_VAL, &force_delete, 1, 0, 0 },
395 {"no-force", 0, POPT_ARG_VAL, &force_delete, 0, 0, 0 },
396 + {"force-delete", 0, POPT_ARG_VAL, &force_delete, 1, 0, 0 },
397 + {"no-force-delete", 0, POPT_ARG_VAL, &force_delete, 0, 0, 0 },
398 +#ifdef SUPPORT_FORCE_CHANGE
399 + {"force-change", 0, POPT_ARG_VAL, &force_change, ALL_IMMUTABLE, 0, 0 },
400 + {"no-force-change", 0, POPT_ARG_VAL, &force_change, 0, 0, 0 },
401 + {"force-uchange", 0, POPT_ARG_VAL, &force_change, USR_IMMUTABLE, 0, 0 },
402 + {"force-schange", 0, POPT_ARG_VAL, &force_change, SYS_IMMUTABLE, 0, 0 },
404 {"ignore-errors", 0, POPT_ARG_VAL, &ignore_errors, 1, 0, 0 },
405 {"no-ignore-errors", 0, POPT_ARG_VAL, &ignore_errors, 0, 0, 0 },
406 {"max-delete", 0, POPT_ARG_INT, &max_delete, 0, 0, 0 },
407 @@ -1879,6 +1905,9 @@ void server_options(char **args, int *argc_p)
408 if (xfer_dirs && !recurse && delete_mode && am_sender)
409 args[ac++] = "--no-r";
411 + if (preserve_fileflags)
412 + args[ac++] = "--fileflags";
414 if (do_compression && def_compress_level != Z_DEFAULT_COMPRESSION) {
415 if (asprintf(&arg, "--compress-level=%d", def_compress_level) < 0)
417 @@ -1966,6 +1995,16 @@ void server_options(char **args, int *argc_p)
418 args[ac++] = "--delete-excluded";
420 args[ac++] = "--force";
421 +#ifdef SUPPORT_FORCE_CHANGE
422 + if (force_change) {
423 + if (force_change == ALL_IMMUTABLE)
424 + args[ac++] = "--force-change";
425 + else if (force_change == USR_IMMUTABLE)
426 + args[ac++] = "--force-uchange";
427 + else if (force_change == SYS_IMMUTABLE)
428 + args[ac++] = "--force-schange";
432 args[ac++] = "--only-write-batch=X";
434 diff --git a/rsync.c b/rsync.c
437 @@ -32,6 +32,7 @@ extern int dry_run;
438 extern int preserve_acls;
439 extern int preserve_xattrs;
440 extern int preserve_perms;
441 +extern int preserve_fileflags;
442 extern int preserve_executability;
443 extern int preserve_times;
445 @@ -374,6 +375,39 @@ mode_t dest_mode(mode_t flist_mode, mode_t stat_mode, int dflt_perms,
449 +#if defined SUPPORT_FILEFLAGS || defined SUPPORT_FORCE_CHANGE
450 +/* Set a file's st_flags. */
451 +static int set_fileflags(const char *fname, uint32 fileflags)
453 + if (do_chflags(fname, fileflags) != 0) {
454 + rsyserr(FERROR_XFER, errno,
455 + "failed to set file flags on %s",
456 + full_fname(fname));
463 +/* Remove immutable flags from an object, so it can be altered/removed. */
464 +int make_mutable(const char *fname, mode_t mode, uint32 fileflags, uint32 iflags)
466 + if (S_ISLNK(mode) || !(fileflags & iflags))
468 + if (!set_fileflags(fname, fileflags & ~iflags))
473 +/* Undo a prior make_mutable() call that returned a 1. */
474 +int undo_make_mutable(const char *fname, uint32 fileflags)
476 + if (!set_fileflags(fname, fileflags))
482 int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
483 const char *fnamecmp, int flags)
485 @@ -429,7 +463,7 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
486 flags |= ATTRS_SKIP_MTIME;
487 if (!(flags & ATTRS_SKIP_MTIME)
488 && cmp_time(sxp->st.st_mtime, file->modtime) != 0) {
489 - int ret = set_modtime(fname, file->modtime, sxp->st.st_mode);
490 + int ret = set_modtime(fname, file->modtime, sxp->st.st_mode, ST_FLAGS(sxp->st));
492 rsyserr(FERROR_XFER, errno, "failed to set times on %s",
494 @@ -465,7 +499,8 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
497 change_uid ? (uid_t)F_OWNER(file) : sxp->st.st_uid,
498 - change_gid ? (gid_t)F_GROUP(file) : sxp->st.st_gid) != 0) {
499 + change_gid ? (gid_t)F_GROUP(file) : sxp->st.st_gid,
500 + sxp->st.st_mode, ST_FLAGS(sxp->st)) != 0) {
501 /* We shouldn't have attempted to change uid
502 * or gid unless have the privilege. */
503 rsyserr(FERROR_XFER, errno, "%s %s failed",
504 @@ -499,7 +534,7 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
507 if (!BITS_EQUAL(sxp->st.st_mode, new_mode, CHMOD_BITS)) {
508 - int ret = am_root < 0 ? 0 : do_chmod(fname, new_mode);
509 + int ret = am_root < 0 ? 0 : do_chmod(fname, new_mode, ST_FLAGS(sxp->st));
511 rsyserr(FERROR_XFER, errno,
512 "failed to set permissions on %s",
513 @@ -511,6 +546,19 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
517 +#ifdef SUPPORT_FILEFLAGS
518 + if (preserve_fileflags && !S_ISLNK(sxp->st.st_mode)
519 + && sxp->st.st_flags != F_FFLAGS(file)) {
520 + uint32 fileflags = F_FFLAGS(file);
521 + if (flags & ATTRS_DELAY_IMMUTABLE)
522 + fileflags &= ~ALL_IMMUTABLE;
523 + if (sxp->st.st_flags != fileflags
524 + && !set_fileflags(fname, fileflags))
530 if (verbose > 1 && flags & ATTRS_REPORT) {
532 rprintf(FCLIENT, "%s\n", fname);
533 @@ -574,7 +622,8 @@ int finish_transfer(const char *fname, const char *fnametmp,
535 /* Change permissions before putting the file into place. */
536 set_file_attrs(fnametmp, file, NULL, fnamecmp,
537 - ok_to_set_time ? 0 : ATTRS_SKIP_MTIME);
538 + ATTRS_DELAY_IMMUTABLE
539 + | (ok_to_set_time ? 0 : ATTRS_SKIP_MTIME));
541 /* move tmp file over real file */
543 @@ -593,6 +642,10 @@ int finish_transfer(const char *fname, const char *fnametmp,
546 /* The file was moved into place (not copied), so it's done. */
547 +#ifdef SUPPORT_FILEFLAGS
548 + if (preserve_fileflags && F_FFLAGS(file) & ALL_IMMUTABLE)
549 + set_fileflags(fname, F_FFLAGS(file));
553 /* The file was copied, so tweak the perms of the copied file. If it
554 diff --git a/rsync.h b/rsync.h
558 #define XMIT_GROUP_NAME_FOLLOWS (1<<11) /* protocols 30 - now */
559 #define XMIT_HLINK_FIRST (1<<12) /* protocols 30 - now (HLINKED files only) */
560 #define XMIT_IO_ERROR_ENDLIST (1<<12) /* protocols 31*- now (w/XMIT_EXTENDED_FLAGS) (also protocol 30 w/'f' compat flag) */
561 +#define XMIT_SAME_FLAGS (1<<14) /* protocols ?? - now */
563 /* These flags are used in the live flist data. */
567 #define ATTRS_REPORT (1<<0)
568 #define ATTRS_SKIP_MTIME (1<<1)
569 +#define ATTRS_DELAY_IMMUTABLE (1<<2)
572 #define NORMAL_FLUSH 0
574 #define ITEM_REPORT_GROUP (1<<6)
575 #define ITEM_REPORT_ACL (1<<7)
576 #define ITEM_REPORT_XATTR (1<<8)
577 +#define ITEM_REPORT_FFLAGS (1<<9)
578 #define ITEM_BASIS_TYPE_FOLLOWS (1<<11)
579 #define ITEM_XNAME_FOLLOWS (1<<12)
580 #define ITEM_IS_NEW (1<<13)
581 @@ -482,6 +485,28 @@ typedef unsigned int size_t;
585 +#define NO_FFLAGS ((uint32)-1)
588 +#define SUPPORT_FILEFLAGS 1
589 +#define SUPPORT_FORCE_CHANGE 1
592 +#if defined SUPPORT_FILEFLAGS || defined SUPPORT_FORCE_CHANGE
594 +#define UF_NOUNLINK 0
597 +#define SF_NOUNLINK 0
599 +#define USR_IMMUTABLE (UF_IMMUTABLE|UF_NOUNLINK|UF_APPEND)
600 +#define SYS_IMMUTABLE (SF_IMMUTABLE|SF_NOUNLINK|SF_APPEND)
601 +#define ALL_IMMUTABLE (USR_IMMUTABLE|SYS_IMMUTABLE)
602 +#define ST_FLAGS(st) (st.st_flags)
604 +#define ST_FLAGS(st) NO_FFLAGS
607 /* Find a variable that is either exactly 32-bits or longer.
608 * If some code depends on 32-bit truncation, it will need to
609 * take special action in a "#if SIZEOF_INT32 > 4" section. */
610 @@ -652,6 +677,7 @@ extern int file_extra_cnt;
611 extern int inc_recurse;
614 +extern int fileflags_ndx;
616 extern int xattrs_ndx;
618 @@ -689,6 +715,11 @@ extern int xattrs_ndx;
619 /* When the associated option is on, all entries will have these present: */
620 #define F_OWNER(f) REQ_EXTRA(f, uid_ndx)->unum
621 #define F_GROUP(f) REQ_EXTRA(f, gid_ndx)->unum
622 +#if defined SUPPORT_FILEFLAGS || defined SUPPORT_FORCE_CHANGE
623 +#define F_FFLAGS(f) REQ_EXTRA(f, fileflags_ndx)->unum
625 +#define F_FFLAGS(f) NO_FFLAGS
627 #define F_ACL(f) REQ_EXTRA(f, acls_ndx)->num
628 #define F_XATTR(f) REQ_EXTRA(f, xattrs_ndx)->num
629 #define F_NDX(f) REQ_EXTRA(f, unsort_ndx)->num
630 diff --git a/rsync.yo b/rsync.yo
633 @@ -342,6 +342,7 @@ to the detailed description below for a complete description. verb(
634 -K, --keep-dirlinks treat symlinked dir on receiver as dir
635 -H, --hard-links preserve hard links
636 -p, --perms preserve permissions
637 + --fileflags preserve file-flags (aka chflags)
638 -E, --executability preserve executability
639 --chmod=CHMOD affect file and/or directory permissions
640 -A, --acls preserve ACLs (implies -p)
641 @@ -373,7 +374,10 @@ to the detailed description below for a complete description. verb(
642 --delete-after receiver deletes after transfer, not before
643 --delete-excluded also delete excluded files from dest dirs
644 --ignore-errors delete even if there are I/O errors
645 - --force force deletion of dirs even if not empty
646 + --force-delete force deletion of dirs even if not empty
647 + --force-change affect user/system immutable files/dirs
648 + --force-uchange affect user-immutable files/dirs
649 + --force-schange affect system-immutable files/dirs
650 --max-delete=NUM don't delete more than NUM files
651 --max-size=SIZE don't transfer any file larger than SIZE
652 --min-size=SIZE don't transfer any file smaller than SIZE
653 @@ -547,7 +551,8 @@ specified, in which case bf(-r) is not implied.
655 Note that bf(-a) bf(does not preserve hardlinks), because
656 finding multiply-linked files is expensive. You must separately
658 +specify bf(-H). Note also that for backward compatibility, bf(-a)
659 +currently does bf(not) imply the bf(--fileflags) option.
661 dit(--no-OPTION) You may turn off one or more implied options by prefixing
662 the option name with "no-". Not all options may be prefixed with a "no-":
663 @@ -827,7 +832,7 @@ they would be using bf(--copy-links).
664 Without this option, if the sending side has replaced a directory with a
665 symlink to a directory, the receiving side will delete anything that is in
666 the way of the new symlink, including a directory hierarchy (as long as
667 -bf(--force) or bf(--delete) is in effect).
668 +bf(--force-delete) or bf(--delete) is in effect).
670 See also bf(--keep-dirlinks) for an analogous option for the receiving
672 @@ -989,6 +994,29 @@ Note that this option does not copy rsyncs special xattr values (e.g. those
673 used by bf(--fake-super)) unless you repeat the option (e.g. -XX). This
674 "copy all xattrs" mode cannot be used with bf(--fake-super).
676 +dit(bf(--fileflags)) This option causes rsync to update the file-flags to be
677 +the same as the source files and directories (if your OS supports the
678 +bf(chflags)(2) system call). Some flags can only be altered by the super-user
679 +and some might only be unset below a certain secure-level (usually single-user
680 +mode). It will not make files alterable that are set to immutable on the
681 +receiver. To do that, see bf(--force-change), bf(--force-uchange), and
682 +bf(--force-schange).
684 +dit(bf(--force-change)) This option causes rsync to disable both user-immutable
685 +and system-immutable flags on files and directories that are being updated or
686 +deleted on the receiving side. This option overrides bf(--force-uchange) and
687 +bf(--force-schange).
689 +dit(bf(--force-uchange)) This option causes rsync to disable user-immutable
690 +flags on files and directories that are being updated or deleted on the
691 +receiving side. It does not try to affect system flags. This option overrides
692 +bf(--force-change) and bf(--force-schange).
694 +dit(bf(--force-schange)) This option causes rsync to disable system-immutable
695 +flags on files and directories that are being updated or deleted on the
696 +receiving side. It does not try to affect user flags. This option overrides
697 +bf(--force-change) and bf(--force-schange).
699 dit(bf(--chmod)) This option tells rsync to apply one or more
700 comma-separated "chmod" strings to the permission of the files in the
701 transfer. The resulting value is treated as though it were the permissions
702 @@ -1263,12 +1291,13 @@ See bf(--delete) (which is implied) for more details on file-deletion.
703 dit(bf(--ignore-errors)) Tells bf(--delete) to go ahead and delete files
704 even when there are I/O errors.
706 -dit(bf(--force)) This option tells rsync to delete a non-empty directory
707 +dit(bf(--force-delete)) This option tells rsync to delete a non-empty directory
708 when it is to be replaced by a non-directory. This is only relevant if
709 deletions are not active (see bf(--delete) for details).
711 -Note for older rsync versions: bf(--force) used to still be required when
712 -using bf(--delete-after), and it used to be non-functional unless the
713 +This option can be abbreviated bf(--force) for backward compatibility.
714 +Note that some older rsync versions used to still require bf(--force)
715 +when using bf(--delete-after), and it used to be non-functional unless the
716 bf(--recursive) option was also enabled.
718 dit(bf(--max-delete=NUM)) This tells rsync not to delete more than NUM
719 @@ -1756,7 +1785,7 @@ with older versions of rsync, but that also turns on the output of other
722 The "%i" escape has a cryptic output that is 11 letters long. The general
723 -format is like the string bf(YXcstpoguax), where bf(Y) is replaced by the
724 +format is like the string bf(YXcstpogfax), where bf(Y) is replaced by the
725 type of update being done, bf(X) is replaced by the file-type, and the
726 other letters represent attributes that may be output if they are being
728 @@ -1812,7 +1841,7 @@ quote(itemization(
729 sender's value (requires bf(--owner) and super-user privileges).
730 it() A bf(g) means the group is different and is being updated to the
731 sender's value (requires bf(--group) and the authority to set the group).
732 - it() The bf(u) slot is reserved for future use.
733 + it() The bf(f) means that the fileflags information changed.
734 it() The bf(a) means that the ACL information changed.
735 it() The bf(x) means that the extended attribute information changed.
737 diff --git a/syscall.c b/syscall.c
740 @@ -33,6 +33,7 @@ extern int dry_run;
742 extern int read_only;
743 extern int list_only;
744 +extern int force_change;
745 extern int preserve_perms;
746 extern int preserve_executability;
748 @@ -50,7 +51,23 @@ int do_unlink(const char *fname)
750 if (dry_run) return 0;
751 RETURN_ERROR_IF_RO_OR_LO;
752 - return unlink(fname);
753 + if (unlink(fname) == 0)
755 +#ifdef SUPPORT_FORCE_CHANGE
756 + if (force_change && errno == EPERM) {
759 + if (x_lstat(fname, &st, NULL) == 0
760 + && make_mutable(fname, st.st_mode, st.st_flags, force_change) > 0) {
761 + if (unlink(fname) == 0)
763 + undo_make_mutable(fname, st.st_flags);
765 + /* TODO: handle immutable directories */
772 int do_symlink(const char *fname1, const char *fname2)
773 @@ -69,14 +86,37 @@ int do_link(const char *fname1, const char *fname2)
777 -int do_lchown(const char *path, uid_t owner, gid_t group)
778 +int do_lchown(const char *path, uid_t owner, gid_t group, mode_t mode, uint32 fileflags)
780 if (dry_run) return 0;
781 RETURN_ERROR_IF_RO_OR_LO;
785 - return lchown(path, owner, group);
786 + if (lchown(path, owner, group) == 0)
788 +#ifdef SUPPORT_FORCE_CHANGE
789 + if (force_change && errno == EPERM) {
790 + if (fileflags == NO_FFLAGS) {
792 + if (x_lstat(path, &st, NULL) == 0) {
794 + fileflags = st.st_flags;
797 + if (fileflags != NO_FFLAGS
798 + && make_mutable(path, mode, fileflags, force_change) > 0) {
799 + int ret = lchown(path, owner, group);
800 + undo_make_mutable(path, fileflags);
807 + mode = fileflags = 0; /* avoid compiler warning */
812 int do_mknod(const char *pathname, mode_t mode, dev_t dev)
813 @@ -116,7 +156,7 @@ int do_mknod(const char *pathname, mode_t mode, dev_t dev)
817 - return do_chmod(pathname, mode);
818 + return do_chmod(pathname, mode, 0);
822 @@ -133,7 +173,22 @@ int do_rmdir(const char *pathname)
824 if (dry_run) return 0;
825 RETURN_ERROR_IF_RO_OR_LO;
826 - return rmdir(pathname);
827 + if (rmdir(pathname) == 0)
829 +#ifdef SUPPORT_FORCE_CHANGE
830 + if (force_change && errno == EPERM) {
833 + if (x_lstat(pathname, &st, NULL) == 0
834 + && make_mutable(pathname, st.st_mode, st.st_flags, force_change) > 0) {
835 + if (rmdir(pathname) == 0)
837 + undo_make_mutable(pathname, st.st_flags);
845 int do_open(const char *pathname, int flags, mode_t mode)
846 @@ -147,7 +202,7 @@ int do_open(const char *pathname, int flags, mode_t mode)
850 -int do_chmod(const char *path, mode_t mode)
851 +int do_chmod(const char *path, mode_t mode, uint32 fileflags)
854 if (dry_run) return 0;
855 @@ -170,17 +225,78 @@ int do_chmod(const char *path, mode_t mode)
857 code = chmod(path, mode & CHMOD_BITS); /* DISCOURAGED FUNCTION */
858 #endif /* !HAVE_LCHMOD */
859 +#ifdef SUPPORT_FORCE_CHANGE
860 + if (code < 0 && force_change && errno == EPERM && !S_ISLNK(mode)) {
861 + if (fileflags == NO_FFLAGS) {
863 + if (x_lstat(path, &st, NULL) == 0)
864 + fileflags = st.st_flags;
866 + if (fileflags != NO_FFLAGS
867 + && make_mutable(path, mode, fileflags, force_change) > 0) {
869 + code = lchmod(path, mode & CHMOD_BITS);
871 + code = chmod(path, mode & CHMOD_BITS);
873 + undo_make_mutable(path, fileflags);
880 + fileflags = 0; /* avoid compiler warning */
882 if (code != 0 && (preserve_perms || preserve_executability))
889 +int do_chflags(const char *path, uint32 fileflags)
891 + if (dry_run) return 0;
892 + RETURN_ERROR_IF_RO_OR_LO;
893 + return chflags(path, fileflags);
897 int do_rename(const char *fname1, const char *fname2)
899 if (dry_run) return 0;
900 RETURN_ERROR_IF_RO_OR_LO;
901 - return rename(fname1, fname2);
902 + if (rename(fname1, fname2) == 0)
904 +#ifdef SUPPORT_FORCE_CHANGE
905 + if (force_change && errno == EPERM) {
906 + STRUCT_STAT st1, st2;
907 + int became_mutable;
909 + if (x_lstat(fname1, &st1, NULL) != 0)
911 + became_mutable = make_mutable(fname1, st1.st_mode, st1.st_flags, force_change) > 0;
912 + if (became_mutable && rename(fname1, fname2) == 0)
914 + if (x_lstat(fname2, &st2, NULL) == 0
915 + && make_mutable(fname2, st2.st_mode, st2.st_flags, force_change) > 0) {
916 + if (rename(fname1, fname2) == 0) {
918 + if (became_mutable) /* Yes, use fname2 and st1! */
919 + undo_make_mutable(fname2, st1.st_flags);
922 + undo_make_mutable(fname2, st2.st_flags);
924 + /* TODO: handle immutable directories */
925 + if (became_mutable)
926 + undo_make_mutable(fname1, st1.st_flags);
934 #ifdef HAVE_FTRUNCATE
935 diff --git a/t_stub.c b/t_stub.c
938 @@ -26,6 +26,7 @@ int module_id = -1;
939 int relative_paths = 0;
940 int human_readable = 0;
941 int module_dirlen = 0;
942 +int force_change = 0;
943 int preserve_times = 0;
944 int preserve_xattrs = 0;
945 mode_t orig_umask = 002;
946 @@ -90,3 +91,23 @@ struct filter_list_struct daemon_filter_list;
951 +#if defined SUPPORT_FILEFLAGS || defined SUPPORT_FORCE_CHANGE
952 + int make_mutable(UNUSED(const char *fname), UNUSED(mode_t mode), UNUSED(uint32 fileflags), UNUSED(uint32 iflags))
957 +/* Undo a prior make_mutable() call that returned a 1. */
958 + int undo_make_mutable(UNUSED(const char *fname), UNUSED(uint32 fileflags))
964 +#ifdef SUPPORT_XATTRS
965 + int x_lstat(UNUSED(const char *fname), UNUSED(STRUCT_STAT *fst), UNUSED(STRUCT_STAT *xst))
970 diff --git a/util.c b/util.c
973 @@ -30,6 +30,7 @@ extern int modify_window;
974 extern int relative_paths;
975 extern int preserve_times;
976 extern int human_readable;
977 +extern int force_change;
978 extern int preserve_xattrs;
979 extern char *module_dir;
980 extern unsigned int module_dirlen;
981 @@ -124,9 +125,34 @@ NORETURN void overflow_exit(const char *str)
982 exit_cleanup(RERR_MALLOC);
985 +#ifdef SUPPORT_FORCE_CHANGE
986 +static int try_a_force_change(const char *fname, time_t modtime, mode_t mode, uint32 fileflags)
988 + if (fileflags == NO_FFLAGS) {
990 + if (x_lstat(fname, &st, NULL) == 0)
991 + fileflags = st.st_flags;
994 + if (fileflags != NO_FFLAGS && make_mutable(fname, mode, fileflags, force_change) > 0) {
995 + int save_force_change = force_change;
997 + force_change = 0; /* Make certain we can't come back here. */
998 + ret = set_modtime(fname, modtime, mode, fileflags);
999 + force_change = save_force_change;
1001 + undo_make_mutable(fname, fileflags);
1010 /* This returns 0 for success, 1 for a symlink if symlink time-setting
1011 * is not possible, or -1 for any other error. */
1012 -int set_modtime(const char *fname, time_t modtime, mode_t mode)
1013 +int set_modtime(const char *fname, time_t modtime, mode_t mode, uint32 fileflags)
1015 static int switch_step = 0;
1017 @@ -141,6 +167,11 @@ int set_modtime(const char *fname, time_t modtime, mode_t mode)
1019 if (do_utimensat(fname, modtime, 0) == 0)
1021 +#ifdef SUPPORT_FORCE_CHANGE
1022 + if (force_change && errno == EPERM
1023 + && try_a_force_change(fname, modtime, mode, fileflags) == 0)
1026 if (errno != ENOSYS)
1029 @@ -151,6 +182,11 @@ int set_modtime(const char *fname, time_t modtime, mode_t mode)
1031 if (do_lutimes(fname, modtime, 0) == 0)
1033 +#ifdef SUPPORT_FORCE_CHANGE
1034 + if (force_change && errno == EPERM
1035 + && try_a_force_change(fname, modtime, mode, fileflags) == 0)
1038 if (errno != ENOSYS)
1041 @@ -174,6 +210,13 @@ int set_modtime(const char *fname, time_t modtime, mode_t mode)
1042 if (do_utime(fname, modtime, 0) == 0)
1045 +#ifdef SUPPORT_FORCE_CHANGE
1046 + if (force_change && errno == EPERM
1047 + && try_a_force_change(fname, modtime, mode, fileflags) == 0)
1050 + fileflags = 0; /* avoid compiler warning */
1055 diff --git a/xattrs.c b/xattrs.c
1058 @@ -1041,7 +1041,7 @@ int set_stat_xattr(const char *fname, struct file_struct *file, mode_t new_mode)
1059 mode = (fst.st_mode & _S_IFMT) | (fmode & ACCESSPERMS)
1060 | (S_ISDIR(fst.st_mode) ? 0700 : 0600);
1061 if (fst.st_mode != mode)
1062 - do_chmod(fname, mode);
1063 + do_chmod(fname, mode, ST_FLAGS(fst));
1064 if (!IS_DEVICE(fst.st_mode))
1065 fst.st_rdev = 0; /* just in case */