Use "use warnings" rather than -w on the #! line.
[rsync/rsync-patches.git] / fileflags.diff
CommitLineData
a5e6228a 1This patch provides --fileflags, which preserves the st_flags stat() field.
9f62ddf8 2Modified from a patch that was written by Rolf Grossmann.
31611b92 3
03019e41 4To use this patch, run these commands for a successful build:
9f62ddf8 5
f9df736a 6 patch -p1 <patches/fileflags.diff
9f62ddf8
WD
7 ./prepare-source
8 ./configure
9 make
31611b92 10
f9df736a
WD
11diff --git a/Makefile.in b/Makefile.in
12--- a/Makefile.in
13+++ 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@
17
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@
20
21 # Programs we must have to run the test cases
22 CHECK_PROGS = rsync$(EXEEXT) tls$(EXEEXT) getgroups$(EXEEXT) getfsdev$(EXEEXT) \
c0c7984e 23@@ -107,7 +107,7 @@ getgroups$(EXEEXT): getgroups.o
f9df736a
WD
24 getfsdev$(EXEEXT): getfsdev.o
25 $(CC) $(CFLAGS) $(LDFLAGS) -o $@ getfsdev.o $(LIBS)
26
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)
31
cc3e685d
WD
32diff --git a/compat.c b/compat.c
33--- a/compat.c
34+++ b/compat.c
f9df736a
WD
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;
ccdb48f6 40 extern int protect_args;
898a2112
WD
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;
9c25eef5 46 extern int need_messages_from_generator;
ae306a29 47@@ -61,7 +63,7 @@ extern char *iconv_opt;
ccdb48f6 48 #endif
898a2112
WD
49
50 /* These index values are for the file-list's extra-attribute array. */
d4dd2dd5
WD
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;
898a2112 53
85096e5e 54 int receiver_symlink_times = 0; /* receiver can set the time on a symlink */
ae306a29
WD
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)
898a2112 57 uid_ndx = ++file_extra_cnt;
fdf967c7 58 if (preserve_gid)
898a2112 59 gid_ndx = ++file_extra_cnt;
f9df736a 60+ if (preserve_fileflags || (force_change && !am_sender))
898a2112 61+ fileflags_ndx = ++file_extra_cnt;
ffc18846 62 if (preserve_acls && !am_sender)
898a2112 63 acls_ndx = ++file_extra_cnt;
5795bf59 64 if (preserve_xattrs)
cc3e685d
WD
65diff --git a/configure.in b/configure.in
66--- a/configure.in
67+++ b/configure.in
c0c7984e
WD
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 \
31611b92
WD
70 strlcat strlcpy strtol mallinfo getgroups setgroups geteuid getegid \
71 setlocale setmode open64 lseek64 mkstemp64 mtrace va_copy __va_copy \
80c89075
WD
72- strerror putenv iconv_open locale_charset nl_langinfo getxattr \
73+ chflags strerror putenv iconv_open locale_charset nl_langinfo getxattr \
7c4c2959 74 extattr_get_link sigaction sigprocmask setattrlist)
31611b92 75
4c15e800 76 dnl cygwin iconv.h defines iconv_open as libiconv_open
cc3e685d
WD
77diff --git a/flist.c b/flist.c
78--- a/flist.c
79+++ b/flist.c
f9df736a 80@@ -52,6 +52,7 @@ extern int preserve_links;
790ba11a
WD
81 extern int preserve_hard_links;
82 extern int preserve_devices;
31611b92 83 extern int preserve_specials;
f9df736a 84+extern int preserve_fileflags;
898a2112
WD
85 extern int uid_ndx;
86 extern int gid_ndx;
790ba11a 87 extern int eol_nulls;
ae306a29 88@@ -395,6 +396,9 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
7e27b6c0 89 {
31611b92
WD
90 static time_t modtime;
91 static mode_t mode;
f9df736a 92+#ifdef SUPPORT_FILEFLAGS
31611b92
WD
93+ static uint32 fileflags;
94+#endif
6240d1e4 95 #ifdef SUPPORT_HARD_LINKS
31611b92 96 static int64 dev;
6240d1e4 97 #endif
ae306a29 98@@ -424,6 +428,14 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
99650e0d 99 xflags |= XMIT_SAME_MODE;
31611b92
WD
100 else
101 mode = file->mode;
f9df736a
WD
102+#ifdef SUPPORT_FILEFLAGS
103+ if (preserve_fileflags) {
a5e6228a
WD
104+ if (F_FFLAGS(file) == fileflags)
105+ xflags |= XMIT_SAME_FLAGS;
106+ else
107+ fileflags = F_FFLAGS(file);
108+ }
31611b92 109+#endif
99650e0d 110
2ad6fab3
WD
111 if ((preserve_devices && IS_DEVICE(mode))
112 || (preserve_specials && IS_SPECIAL(mode))) {
ae306a29 113@@ -538,6 +550,10 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
82c2230a 114 }
99650e0d 115 if (!(xflags & XMIT_SAME_MODE))
31611b92 116 write_int(f, to_wire_mode(mode));
f9df736a
WD
117+#ifdef SUPPORT_FILEFLAGS
118+ if (preserve_fileflags && !(xflags & XMIT_SAME_FLAGS))
31611b92
WD
119+ write_int(f, (int)fileflags);
120+#endif
f9df736a 121 if (preserve_uid && !(xflags & XMIT_SAME_UID)) {
caf38d8d
WD
122 if (protocol_version < 30)
123 write_int(f, uid);
ae306a29 124@@ -624,6 +640,9 @@ static struct file_struct *recv_file_entry(struct file_list *flist,
31611b92 125 {
82c2230a 126 static int64 modtime;
31611b92 127 static mode_t mode;
f9df736a 128+#ifdef SUPPORT_FILEFLAGS
8871c12a 129+ static uint32 fileflags;
31611b92 130+#endif
6240d1e4 131 #ifdef SUPPORT_HARD_LINKS
31611b92 132 static int64 dev;
6240d1e4 133 #endif
ae306a29 134@@ -759,6 +778,10 @@ static struct file_struct *recv_file_entry(struct file_list *flist,
a5e6228a 135
31611b92
WD
136 if (chmod_modes && !S_ISLNK(mode))
137 mode = tweak_mode(mode, chmod_modes);
f9df736a
WD
138+#ifdef SUPPORT_FILEFLAGS
139+ if (preserve_fileflags && !(xflags & XMIT_SAME_FLAGS))
31611b92
WD
140+ fileflags = (uint32)read_int(f);
141+#endif
142
f9df736a 143 if (preserve_uid && !(xflags & XMIT_SAME_UID)) {
caf38d8d 144 if (protocol_version < 30)
ae306a29 145@@ -899,6 +922,10 @@ static struct file_struct *recv_file_entry(struct file_list *flist,
1aa236e1 146 }
7f0bf1cb 147 #endif
31611b92 148 file->mode = mode;
f9df736a
WD
149+#ifdef SUPPORT_FILEFLAGS
150+ if (preserve_fileflags)
70891d26 151+ F_FFLAGS(file) = fileflags;
31611b92 152+#endif
f9df736a 153 if (preserve_uid)
fc068916 154 F_OWNER(file) = uid;
f9df736a 155 if (preserve_gid) {
ae306a29 156@@ -1273,6 +1300,10 @@ struct file_struct *make_file(const char *fname, struct file_list *flist,
1aa236e1 157 }
7f0bf1cb 158 #endif
31611b92 159 file->mode = st.st_mode;
f9df736a 160+#if defined SUPPORT_FILEFLAGS || defined SUPPORT_FORCE_CHANGE
898a2112 161+ if (fileflags_ndx)
70891d26 162+ F_FFLAGS(file) = st.st_flags;
31611b92 163+#endif
f9df736a 164 if (uid_ndx) /* Check uid_ndx instead of preserve_uid for del support */
fc068916 165 F_OWNER(file) = st.st_uid;
f9df736a 166 if (gid_ndx) /* Check gid_ndx instead of preserve_gid for del support */
abd3adb8 167@@ -1408,6 +1439,7 @@ static struct file_struct *send_file_name(int f, struct file_list *flist,
7f0bf1cb
WD
168 #endif
169 #ifdef SUPPORT_XATTRS
170 if (preserve_xattrs) {
171+ sx.st.st_mode = file->mode;
172 sx.xattr = NULL;
173 if (get_xattr(fname, &sx) < 0) {
174 io_error |= IOERR_GENERAL;
cc3e685d
WD
175diff --git a/generator.c b/generator.c
176--- a/generator.c
177+++ b/generator.c
c0c7984e 178@@ -42,8 +42,10 @@ extern int preserve_devices;
f9df736a
WD
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;
186 extern int uid_ndx;
187 extern int gid_ndx;
188 extern int delete_mode;
4c107044 189@@ -166,7 +168,7 @@ static enum delret delete_item(char *fbuf, uint16 mode, uint16 flags)
f9df736a 190 }
87d0091c 191
f9df736a
WD
192 if (flags & DEL_NO_UID_WRITE)
193- do_chmod(fbuf, mode | S_IWUSR);
194+ do_chmod(fbuf, mode | S_IWUSR, NO_FFLAGS);
a5e6228a 195
d16b5fd6 196 if (S_ISDIR(mode) && !(flags & DEL_DIR_IS_EMPTY)) {
f9df736a 197 int save_uid_ndx = uid_ndx;
4c107044 198@@ -174,6 +176,13 @@ static enum delret delete_item(char *fbuf, uint16 mode, uint16 flags)
f9df736a
WD
199 * delete_dir_contents() always calls us w/DEL_DIR_IS_EMPTY. */
200 if (!uid_ndx)
201 uid_ndx = ++file_extra_cnt;
202+#ifdef SUPPORT_FORCE_CHANGE
203+ if (force_change) {
204+ STRUCT_STAT st;
205+ if (x_lstat(fbuf, &st, NULL) == 0)
206+ make_mutable(fbuf, st.st_mode, st.st_flags, force_change);
a5e6228a 207+ }
f9df736a
WD
208+#endif
209 ignore_perishable = 1;
210 /* If DEL_RECURSE is not set, this just reports emptiness. */
211 ret = delete_dir_contents(fbuf, flags);
4c107044 212@@ -294,8 +303,12 @@ static enum delret delete_dir_contents(char *fname, uint16 flags)
f9df736a
WD
213 }
214
215 strlcpy(p, fp->basename, remainder);
216+#ifdef SUPPORT_FORCE_CHANGE
217+ if (force_change)
218+ make_mutable(fname, fp->mode, F_FFLAGS(fp), force_change);
219+#endif
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);
a5e6228a
WD
223 /* Save stack by recursing to ourself directly. */
224 if (S_ISDIR(fp->mode)) {
f9df736a 225 if (delete_dir_contents(fname, flags | DEL_RECURSE) != DR_SUCCESS)
abd3adb8 226@@ -599,6 +612,11 @@ int unchanged_attrs(const char *fname, struct file_struct *file, stat_x *sxp)
c0c7984e 227 && ((sxp->st.st_mode & 0111 ? 1 : 0) ^ (file->mode & 0111 ? 1 : 0)))
f9df736a 228 return 0;
a5e6228a 229
f9df736a
WD
230+#ifdef SUPPORT_FILEFLAGS
231+ if (preserve_fileflags && !S_ISLNK(file->mode) && sxp->st.st_flags != F_FFLAGS(file))
232+ return 0;
233+#endif
a5e6228a 234+
c0c7984e 235 if (am_root && uid_ndx && sxp->st.st_uid != (uid_t)F_OWNER(file))
f9df736a 236 return 0;
ee76479d 237
abd3adb8 238@@ -664,6 +682,11 @@ void itemize(const char *fnamecmp, struct file_struct *file, int ndx, int statre
f9df736a
WD
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;
a5e6228a 246+#endif
f9df736a
WD
247 #ifdef SUPPORT_ACLS
248 if (preserve_acls && !S_ISLNK(file->mode)) {
249 if (!ACL_READY(*sxp))
abd3adb8 250@@ -1442,6 +1465,10 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
f9df736a
WD
251 file->mode = dest_mode(file->mode, sx.st.st_mode,
252 dflt_perms, statret == 0);
f90a882e 253 }
f9df736a
WD
254+#ifdef SUPPORT_FORCE_CHANGE
255+ if (force_change && !preserve_fileflags)
256+ F_FFLAGS(file) = sx.st.st_flags;
a5e6228a 257+#endif
f9df736a
WD
258 if (statret != 0 && basis_dir[0] != NULL) {
259 int j = try_dests_non(file, fname, ndx, fnamecmpbuf, &sx,
260 itemizing, code);
abd3adb8 261@@ -1482,10 +1509,15 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
f9df736a
WD
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;
269+#endif
270 #ifdef HAVE_CHMOD
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",
277 full_fname(fname));
abd3adb8 278@@ -1520,6 +1552,10 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
f9df736a
WD
279 file->mode = dest_mode(file->mode, sx.st.st_mode, dflt_perms,
280 exists);
281 }
282+#ifdef SUPPORT_FORCE_CHANGE
283+ if (force_change && !preserve_fileflags)
284+ F_FFLAGS(file) = sx.st.st_flags;
a5e6228a
WD
285+#endif
286
f9df736a
WD
287 #ifdef SUPPORT_HARD_LINKS
288 if (preserve_hard_links && F_HLINK_NOT_FIRST(file)
abd3adb8 289@@ -2054,13 +2090,17 @@ static void touch_up_dirs(struct file_list *flist, int ndx)
f9df736a
WD
290 continue;
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);
c0c7984e
WD
295 if (need_retouch_dir_times) {
296 STRUCT_STAT st;
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);
301 }
f9df736a
WD
302+#ifdef SUPPORT_FORCE_CHANGE
303+ if (force_change && F_FFLAGS(file) & force_change)
304+ undo_make_mutable(fname, F_FFLAGS(file));
305+#endif
e2e42a01
WD
306 if (counter >= loopchk_limit) {
307 if (allowed_lull)
308 maybe_send_keepalive();
f9df736a
WD
309diff --git a/log.c b/log.c
310--- a/log.c
311+++ b/log.c
ae306a29 312@@ -660,7 +660,7 @@ static void log_formatted(enum logcode code, const char *format, const char *op,
f9df736a
WD
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';
320 c[11] = '\0';
cc3e685d
WD
321diff --git a/options.c b/options.c
322--- a/options.c
323+++ b/options.c
c0c7984e 324@@ -53,6 +53,7 @@ int preserve_hard_links = 0;
ffc18846 325 int preserve_acls = 0;
5795bf59 326 int preserve_xattrs = 0;
31611b92 327 int preserve_perms = 0;
5e3c6c93 328+int preserve_fileflags = 0;
31611b92
WD
329 int preserve_executability = 0;
330 int preserve_devices = 0;
331 int preserve_specials = 0;
c0c7984e 332@@ -85,6 +86,7 @@ int implied_dirs = 1;
f9df736a
WD
333 int numeric_ids = 0;
334 int allow_8bit_chars = 0;
335 int force_delete = 0;
336+int force_change = 0;
337 int io_timeout = 0;
338 int allowed_lull = 0;
339 int prune_empty_dirs = 0;
c0c7984e 340@@ -225,6 +227,7 @@ static void print_rsync_version(enum logcode f)
31611b92 341 char const *links = "no ";
58b399b9 342 char const *iconv = "no ";
31611b92 343 char const *ipv6 = "no ";
8871c12a 344+ char const *fileflags = "no ";
31611b92
WD
345 STRUCT_STAT *dumstat;
346
ac2da598 347 #if SUBPROTOCOL_VERSION != 0
abd3adb8 348@@ -258,6 +261,9 @@ static void print_rsync_version(enum logcode f)
85096e5e
WD
349 #if defined HAVE_LUTIMES && defined HAVE_UTIMES
350 symtimes = "";
31611b92 351 #endif
f9df736a 352+#ifdef SUPPORT_FILEFLAGS
5e3c6c93 353+ fileflags = "";
31611b92 354+#endif
ac2da598
WD
355
356 rprintf(f, "%s version %s protocol version %d%s\n",
357 RSYNC_NAME, RSYNC_VERSION, PROTOCOL_VERSION, subprotocol);
abd3adb8 358@@ -271,8 +277,8 @@ static void print_rsync_version(enum logcode f)
5e3c6c93
WD
359 (int)(sizeof (int64) * 8));
360 rprintf(f, " %ssocketpairs, %shardlinks, %ssymlinks, %sIPv6, batchfiles, %sinplace,\n",
361 got_socketpair, hardlinks, links, ipv6, have_inplace);
85096e5e
WD
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);
5e3c6c93 366
31611b92 367 #ifdef MAINTAINER_MODE
5e3c6c93 368 rprintf(f, "Panic Action: \"%s\"\n", get_panic_action());
abd3adb8 369@@ -339,6 +345,9 @@ void usage(enum logcode F)
31611b92
WD
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");
f9df736a
WD
373+#ifdef SUPPORT_FILEFLAGS
374+ rprintf(F," --fileflags preserve file-flags (aka chflags)\n");
375+#endif
31611b92 376 rprintf(F," -E, --executability preserve the file's executability\n");
063cf77b 377 rprintf(F," --chmod=CHMOD affect file and/or directory permissions\n");
ffc18846 378 #ifdef SUPPORT_ACLS
abd3adb8 379@@ -376,7 +385,12 @@ void usage(enum logcode F)
f9df736a
WD
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");
389+#endif
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");
abd3adb8 393@@ -481,6 +495,10 @@ static struct poptOption long_options[] = {
31611b92
WD
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 },
f9df736a 397+#ifdef SUPPORT_FILEFLAGS
5e3c6c93
WD
398+ {"fileflags", 0, POPT_ARG_VAL, &preserve_fileflags, 1, 0, 0 },
399+ {"no-fileflags", 0, POPT_ARG_VAL, &preserve_fileflags, 0, 0, 0 },
f9df736a 400+#endif
31611b92 401 {"executability", 'E', POPT_ARG_NONE, &preserve_executability, 0, 0, 0 },
ffc18846
WD
402 {"acls", 'A', POPT_ARG_NONE, 0, 'A', 0, 0 },
403 {"no-acls", 0, POPT_ARG_VAL, &preserve_acls, 0, 0, 0 },
abd3adb8 404@@ -559,6 +577,14 @@ static struct poptOption long_options[] = {
f9df736a 405 {"remove-source-files",0,POPT_ARG_VAL, &remove_source_files, 1, 0, 0 },
c0c7984e
WD
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 },
f9df736a
WD
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 },
31611b92 415+#endif
c0c7984e
WD
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 },
f9df736a 418 {"max-delete", 0, POPT_ARG_INT, &max_delete, 0, 0, 0 },
abd3adb8 419@@ -1868,6 +1894,9 @@ void server_options(char **args, int *argc_p)
31611b92
WD
420 if (xfer_dirs && !recurse && delete_mode && am_sender)
421 args[ac++] = "--no-r";
422
5e3c6c93 423+ if (preserve_fileflags)
a5e6228a 424+ args[ac++] = "--fileflags";
31611b92
WD
425+
426 if (do_compression && def_compress_level != Z_DEFAULT_COMPRESSION) {
427 if (asprintf(&arg, "--compress-level=%d", def_compress_level) < 0)
428 goto oom;
abd3adb8 429@@ -1955,6 +1984,16 @@ void server_options(char **args, int *argc_p)
f9df736a
WD
430 args[ac++] = "--delete-excluded";
431 if (force_delete)
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";
441+ }
442+#endif
443 if (write_batch < 0)
444 args[ac++] = "--only-write-batch=X";
445 if (am_root > 1)
cc3e685d
WD
446diff --git a/rsync.c b/rsync.c
447--- a/rsync.c
448+++ b/rsync.c
52e09c4e 449@@ -32,6 +32,7 @@ extern int dry_run;
ffc18846 450 extern int preserve_acls;
5795bf59 451 extern int preserve_xattrs;
31611b92 452 extern int preserve_perms;
5e3c6c93 453+extern int preserve_fileflags;
31611b92
WD
454 extern int preserve_executability;
455 extern int preserve_times;
9c25eef5 456 extern int am_root;
963ca808 457@@ -377,6 +378,39 @@ mode_t dest_mode(mode_t flist_mode, mode_t stat_mode, int dflt_perms,
f90a882e
WD
458 return new_mode;
459 }
460
f9df736a 461+#if defined SUPPORT_FILEFLAGS || defined SUPPORT_FORCE_CHANGE
f90a882e
WD
462+/* Set a file's st_flags. */
463+static int set_fileflags(const char *fname, uint32 fileflags)
464+{
465+ if (do_chflags(fname, fileflags) != 0) {
cc3e685d 466+ rsyserr(FERROR_XFER, errno,
f90a882e
WD
467+ "failed to set file flags on %s",
468+ full_fname(fname));
469+ return 0;
470+ }
471+
472+ return 1;
473+}
474+
475+/* Remove immutable flags from an object, so it can be altered/removed. */
f9df736a 476+int make_mutable(const char *fname, mode_t mode, uint32 fileflags, uint32 iflags)
f90a882e 477+{
f9df736a
WD
478+ if (S_ISLNK(mode) || !(fileflags & iflags))
479+ return 0;
480+ if (!set_fileflags(fname, fileflags & ~iflags))
481+ return -1;
482+ return 1;
f90a882e
WD
483+}
484+
f9df736a
WD
485+/* Undo a prior make_mutable() call that returned a 1. */
486+int undo_make_mutable(const char *fname, uint32 fileflags)
f90a882e 487+{
f9df736a
WD
488+ if (!set_fileflags(fname, fileflags))
489+ return -1;
490+ return 1;
f90a882e
WD
491+}
492+#endif
493+
52e09c4e 494 int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
5795bf59 495 const char *fnamecmp, int flags)
f90a882e 496 {
963ca808 497@@ -430,7 +464,7 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
f9df736a
WD
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));
503 if (ret < 0) {
504 rsyserr(FERROR_XFER, errno, "failed to set times on %s",
505 full_fname(fname));
963ca808 506@@ -466,7 +500,8 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
f9df736a
WD
507 if (am_root >= 0) {
508 if (do_lchown(fname,
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",
963ca808 516@@ -498,7 +533,7 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
f9df736a
WD
517
518 #ifdef HAVE_CHMOD
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));
522 if (ret < 0) {
523 rsyserr(FERROR_XFER, errno,
524 "failed to set permissions on %s",
963ca808 525@@ -510,6 +545,19 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
31611b92
WD
526 }
527 #endif
528
f9df736a 529+#ifdef SUPPORT_FILEFLAGS
7d006b5b
WD
530+ if (preserve_fileflags && !S_ISLNK(sxp->st.st_mode)
531+ && sxp->st.st_flags != F_FFLAGS(file)) {
f9df736a
WD
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))
85096e5e 537+ goto cleanup;
31611b92
WD
538+ updated = 1;
539+ }
540+#endif
541+
542 if (verbose > 1 && flags & ATTRS_REPORT) {
ff318e90
WD
543 if (updated)
544 rprintf(FCLIENT, "%s\n", fname);
963ca808 545@@ -573,7 +621,8 @@ int finish_transfer(const char *fname, const char *fnametmp,
f9df736a
WD
546
547 /* Change permissions before putting the file into place. */
5795bf59 548 set_file_attrs(fnametmp, file, NULL, fnamecmp,
f9df736a
WD
549- ok_to_set_time ? 0 : ATTRS_SKIP_MTIME);
550+ ATTRS_DELAY_IMMUTABLE
551+ | (ok_to_set_time ? 0 : ATTRS_SKIP_MTIME));
31611b92 552
f90a882e
WD
553 /* move tmp file over real file */
554 if (verbose > 2)
963ca808 555@@ -592,6 +641,10 @@ int finish_transfer(const char *fname, const char *fnametmp,
f90a882e
WD
556 }
557 if (ret == 0) {
558 /* The file was moved into place (not copied), so it's done. */
f9df736a
WD
559+#ifdef SUPPORT_FILEFLAGS
560+ if (preserve_fileflags && F_FFLAGS(file) & ALL_IMMUTABLE)
561+ set_fileflags(fname, F_FFLAGS(file));
31611b92 562+#endif
4c15e800 563 return 1;
f90a882e
WD
564 }
565 /* The file was copied, so tweak the perms of the copied file. If it
cc3e685d
WD
566diff --git a/rsync.h b/rsync.h
567--- a/rsync.h
568+++ b/rsync.h
569@@ -60,6 +60,7 @@
caf38d8d 570 #define XMIT_RDEV_MINOR_8_pre30 (1<<11) /* protocols 28 - 29 */
9c25eef5
WD
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 */
31611b92
WD
574
575 /* These flags are used in the live flist data. */
576
e8972101 577@@ -154,6 +155,7 @@
f9df736a
WD
578
579 #define ATTRS_REPORT (1<<0)
580 #define ATTRS_SKIP_MTIME (1<<1)
581+#define ATTRS_DELAY_IMMUTABLE (1<<2)
582
583 #define FULL_FLUSH 1
584 #define NORMAL_FLUSH 0
e8972101 585@@ -180,6 +182,7 @@
f9df736a
WD
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)
ae306a29 593@@ -462,6 +465,28 @@ typedef unsigned int size_t;
7e27b6c0 594 #endif
6e6f514c
WD
595 #endif
596
f9df736a
WD
597+#define NO_FFLAGS ((uint32)-1)
598+
6e6f514c 599+#ifdef HAVE_CHFLAGS
f9df736a
WD
600+#define SUPPORT_FILEFLAGS 1
601+#define SUPPORT_FORCE_CHANGE 1
6e6f514c 602+#endif
a5e6228a 603+
f9df736a 604+#if defined SUPPORT_FILEFLAGS || defined SUPPORT_FORCE_CHANGE
a5e6228a
WD
605+#ifndef UF_NOUNLINK
606+#define UF_NOUNLINK 0
607+#endif
608+#ifndef SF_NOUNLINK
609+#define SF_NOUNLINK 0
610+#endif
c0c7984e
WD
611+#define USR_IMMUTABLE (UF_IMMUTABLE|UF_NOUNLINK|UF_APPEND)
612+#define SYS_IMMUTABLE (SF_IMMUTABLE|SF_NOUNLINK|SF_APPEND)
f9df736a
WD
613+#define ALL_IMMUTABLE (USR_IMMUTABLE|SYS_IMMUTABLE)
614+#define ST_FLAGS(st) (st.st_flags)
615+#else
616+#define ST_FLAGS(st) NO_FFLAGS
a5e6228a 617+#endif
6e6f514c
WD
618+
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. */
ae306a29 622@@ -632,6 +657,7 @@ extern int file_extra_cnt;
9c25eef5 623 extern int inc_recurse;
898a2112
WD
624 extern int uid_ndx;
625 extern int gid_ndx;
626+extern int fileflags_ndx;
627 extern int acls_ndx;
628 extern int xattrs_ndx;
fdf967c7 629
ae306a29 630@@ -669,6 +695,11 @@ extern int xattrs_ndx;
1aa236e1 631 /* When the associated option is on, all entries will have these present: */
898a2112
WD
632 #define F_OWNER(f) REQ_EXTRA(f, uid_ndx)->unum
633 #define F_GROUP(f) REQ_EXTRA(f, gid_ndx)->unum
f9df736a 634+#if defined SUPPORT_FILEFLAGS || defined SUPPORT_FORCE_CHANGE
898a2112 635+#define F_FFLAGS(f) REQ_EXTRA(f, fileflags_ndx)->unum
f9df736a
WD
636+#else
637+#define F_FFLAGS(f) NO_FFLAGS
638+#endif
898a2112
WD
639 #define F_ACL(f) REQ_EXTRA(f, acls_ndx)->num
640 #define F_XATTR(f) REQ_EXTRA(f, xattrs_ndx)->num
d4dd2dd5 641 #define F_NDX(f) REQ_EXTRA(f, unsort_ndx)->num
cc3e685d
WD
642diff --git a/rsync.yo b/rsync.yo
643--- a/rsync.yo
644+++ b/rsync.yo
abd3adb8 645@@ -342,6 +342,7 @@ to the detailed description below for a complete description. verb(
1ed0b5c9
WD
646 -K, --keep-dirlinks treat symlinked dir on receiver as dir
647 -H, --hard-links preserve hard links
31611b92 648 -p, --perms preserve permissions
f9df736a 649+ --fileflags preserve file-flags (aka chflags)
1ed0b5c9 650 -E, --executability preserve executability
063cf77b 651 --chmod=CHMOD affect file and/or directory permissions
ffc18846 652 -A, --acls preserve ACLs (implies -p)
abd3adb8 653@@ -373,7 +374,10 @@ to the detailed description below for a complete description. verb(
f9df736a
WD
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
abd3adb8 665@@ -544,7 +548,8 @@ specified, in which case bf(-r) is not implied.
31611b92
WD
666
667 Note that bf(-a) bf(does not preserve hardlinks), because
668 finding multiply-linked files is expensive. You must separately
669-specify bf(-H).
a5e6228a
WD
670+specify bf(-H). Note also that for backward compatibility, bf(-a)
671+currently does bf(not) imply the bf(--fileflags) option.
31611b92
WD
672
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-":
abd3adb8 675@@ -802,7 +807,7 @@ they would be using bf(--copy-links).
f9df736a
WD
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).
681
682 See also bf(--keep-dirlinks) for an analogous option for the receiving
683 side.
abd3adb8 684@@ -939,6 +944,29 @@ super-user copies all namespaces except system.*. A normal user only copies
c8a8b4a7
WD
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.
31611b92 687
f9df736a
WD
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).
695+
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).
700+
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).
705+
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).
31611b92
WD
710+
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
abd3adb8 714@@ -1201,12 +1229,13 @@ See bf(--delete) (which is implied) for more details on file-deletion.
f9df736a
WD
715 dit(bf(--ignore-errors)) Tells bf(--delete) to go ahead and delete files
716 even when there are I/O errors.
717
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).
722
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.
729
730 dit(bf(--max-delete=NUM)) This tells rsync not to delete more than NUM
abd3adb8 731@@ -1667,7 +1696,7 @@ with older versions of rsync, but that also turns on the output of other
f9df736a
WD
732 verbose messages).
733
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
739 modified.
abd3adb8 740@@ -1723,7 +1752,7 @@ quote(itemization(
f9df736a
WD
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.
748 ))
cc3e685d
WD
749diff --git a/syscall.c b/syscall.c
750--- a/syscall.c
751+++ b/syscall.c
f9df736a
WD
752@@ -33,6 +33,7 @@ extern int dry_run;
753 extern int am_root;
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;
759
760@@ -50,7 +51,23 @@ int do_unlink(const char *fname)
761 {
762 if (dry_run) return 0;
763 RETURN_ERROR_IF_RO_OR_LO;
764- return unlink(fname);
765+ if (unlink(fname) == 0)
766+ return 0;
767+#ifdef SUPPORT_FORCE_CHANGE
768+ if (force_change && errno == EPERM) {
769+ STRUCT_STAT st;
770+
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)
774+ return 0;
775+ undo_make_mutable(fname, st.st_flags);
776+ }
777+ /* TODO: handle immutable directories */
778+ errno = EPERM;
779+ }
780+#endif
781+ return -1;
782 }
783
784 int do_symlink(const char *fname1, const char *fname2)
785@@ -69,14 +86,37 @@ int do_link(const char *fname1, const char *fname2)
786 }
787 #endif
788
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)
791 {
792 if (dry_run) return 0;
793 RETURN_ERROR_IF_RO_OR_LO;
794 #ifndef HAVE_LCHOWN
795 #define lchown chown
796 #endif
797- return lchown(path, owner, group);
798+ if (lchown(path, owner, group) == 0)
799+ return 0;
800+#ifdef SUPPORT_FORCE_CHANGE
801+ if (force_change && errno == EPERM) {
802+ if (fileflags == NO_FFLAGS) {
803+ STRUCT_STAT st;
804+ if (x_lstat(path, &st, NULL) == 0) {
805+ mode = st.st_mode;
806+ fileflags = st.st_flags;
807+ }
808+ }
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);
813+ if (ret == 0)
814+ return 0;
815+ }
816+ errno = EPERM;
817+ }
818+#else
819+ mode = fileflags = 0; /* avoid compiler warning */
820+#endif
821+ return -1;
31611b92 822 }
f9df736a
WD
823
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)
826 return -1;
827 close(sock);
828 #ifdef HAVE_CHMOD
829- return do_chmod(pathname, mode);
830+ return do_chmod(pathname, mode, 0);
831 #else
832 return 0;
31611b92 833 #endif
f9df736a
WD
834@@ -133,7 +173,22 @@ int do_rmdir(const char *pathname)
835 {
836 if (dry_run) return 0;
837 RETURN_ERROR_IF_RO_OR_LO;
838- return rmdir(pathname);
839+ if (rmdir(pathname) == 0)
840+ return 0;
841+#ifdef SUPPORT_FORCE_CHANGE
842+ if (force_change && errno == EPERM) {
843+ STRUCT_STAT st;
844+
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)
848+ return 0;
849+ undo_make_mutable(pathname, st.st_flags);
850+ }
851+ errno = EPERM;
852+ }
853+#endif
854+ return -1;
855 }
856
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)
859 }
31611b92 860
f9df736a
WD
861 #ifdef HAVE_CHMOD
862-int do_chmod(const char *path, mode_t mode)
863+int do_chmod(const char *path, mode_t mode, uint32 fileflags)
864 {
865 int code;
866 if (dry_run) return 0;
867@@ -168,17 +223,74 @@ int do_chmod(const char *path, mode_t mode)
868 #endif
869 } else
c0c7984e 870 code = chmod(path, mode & CHMOD_BITS); /* DISCOURAGED FUNCTION */
f9df736a
WD
871+#ifdef SUPPORT_FORCE_CHANGE
872+ if (code < 0 && force_change && errno == EPERM && !S_ISLNK(mode)) {
873+ if (fileflags == NO_FFLAGS) {
874+ STRUCT_STAT st;
875+ if (x_lstat(path, &st, NULL) == 0)
876+ fileflags = st.st_flags;
877+ }
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);
882+ if (code == 0)
883+ return 0;
884+ }
885+ errno = EPERM;
886+ }
887+#else
888+ fileflags = 0; /* avoid compiler warning */
889+#endif
890 if (code != 0 && (preserve_perms || preserve_executability))
c0c7984e 891 return code;
f9df736a
WD
892 return 0;
893 }
894 #endif
895
896+#ifdef HAVE_CHFLAGS
897+int do_chflags(const char *path, uint32 fileflags)
31611b92
WD
898+{
899+ if (dry_run) return 0;
900+ RETURN_ERROR_IF_RO_OR_LO;
f9df736a 901+ return chflags(path, fileflags);
31611b92
WD
902+}
903+#endif
904+
905 int do_rename(const char *fname1, const char *fname2)
906 {
907 if (dry_run) return 0;
f9df736a
WD
908 RETURN_ERROR_IF_RO_OR_LO;
909- return rename(fname1, fname2);
910+ if (rename(fname1, fname2) == 0)
911+ return 0;
912+#ifdef SUPPORT_FORCE_CHANGE
913+ if (force_change && errno == EPERM) {
914+ STRUCT_STAT st1, st2;
915+ int became_mutable;
916+
917+ if (x_lstat(fname1, &st1, NULL) != 0)
918+ goto failed;
919+ became_mutable = make_mutable(fname1, st1.st_mode, st1.st_flags, force_change) > 0;
920+ if (became_mutable && rename(fname1, fname2) == 0)
921+ goto success;
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) {
925+ success:
926+ if (became_mutable) /* Yes, use fname2 and st1! */
927+ undo_make_mutable(fname2, st1.st_flags);
928+ return 0;
929+ }
930+ undo_make_mutable(fname2, st2.st_flags);
931+ }
932+ /* TODO: handle immutable directories */
933+ if (became_mutable)
934+ undo_make_mutable(fname1, st1.st_flags);
935+ failed:
936+ errno = EPERM;
937+ }
938+#endif
939+ return -1;
940 }
941
942 void trim_trailing_slashes(char *name)
943diff --git a/t_stub.c b/t_stub.c
944--- a/t_stub.c
945+++ 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;
91270139 951 int preserve_xattrs = 0;
f9df736a
WD
952 mode_t orig_umask = 002;
953 char *partial_dir;
91270139 954@@ -89,3 +90,23 @@ struct filter_list_struct daemon_filter_list;
f9df736a
WD
955 {
956 return "tester";
957 }
958+
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))
961+{
962+ return 0;
963+}
964+
965+/* Undo a prior make_mutable() call that returned a 1. */
966+ int undo_make_mutable(UNUSED(const char *fname), UNUSED(uint32 fileflags))
967+{
968+ return 0;
969+}
970+#endif
971+
972+#ifdef SUPPORT_XATTRS
973+ int x_lstat(UNUSED(const char *fname), UNUSED(STRUCT_STAT *fst), UNUSED(STRUCT_STAT *xst))
974+{
975+ return -1;
976+}
977+#endif
978diff --git a/util.c b/util.c
979--- a/util.c
980+++ 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;
91270139 986 extern int preserve_xattrs;
f9df736a
WD
987 extern char *module_dir;
988 extern unsigned int module_dirlen;
91270139 989@@ -123,7 +124,7 @@ NORETURN void overflow_exit(const char *str)
f9df736a
WD
990 exit_cleanup(RERR_MALLOC);
991 }
992
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)
995 {
996 #if !defined HAVE_LUTIMES || !defined HAVE_UTIMES
997 if (S_ISLNK(mode))
91270139 998@@ -140,6 +141,7 @@ int set_modtime(const char *fname, time_t modtime, mode_t mode)
f9df736a
WD
999 return 0;
1000
1001 {
1002+ int ret;
1003 #ifdef HAVE_UTIMES
1004 struct timeval t[2];
1005 t[0].tv_sec = time(NULL);
91270139 1006@@ -153,20 +155,39 @@ int set_modtime(const char *fname, time_t modtime, mode_t mode)
f9df736a
WD
1007 return 0;
1008 }
1009 # endif
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
1019 time_t t[2];
1020 t[0] = time(NULL);
1021 t[1] = modtime;
1022- return utime(fname,t);
1023+#define SET_THE_TIME(fn) utime(fn, t)
1024 #else
1025 #error No file-time-modification routine found!
1026 #endif
1027+ ret = SET_THE_TIME(fname);
1028+#ifdef SUPPORT_FORCE_CHANGE
1029+ if (ret != 0 && force_change && errno == EPERM) {
1030+ if (fileflags == NO_FFLAGS) {
1031+ STRUCT_STAT st;
1032+ if (x_lstat(fname, &st, NULL) == 0)
1033+ fileflags = st.st_flags;
1034+ }
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);
1039+ }
1040+ errno = EPERM;
1041+ }
1042+#else
1043+ fileflags = 0; /* avoid compiler warning */
1044+#endif
1045+ return ret;
1046 }
1047 }
1048
1049diff --git a/xattrs.c b/xattrs.c
1050--- a/xattrs.c
1051+++ b/xattrs.c
7f0bf1cb
WD
1052@@ -281,6 +281,10 @@ int get_xattr(const char *fname, stat_x *sxp)
1053 {
1054 sxp->xattr = new(item_list);
1055 *sxp->xattr = empty_xattr;
1056+
1057+ if (IS_SPECIAL(sxp->st.st_mode) || IS_DEVICE(sxp->st.st_mode))
1058+ return 0;
1059+
1060 if (rsync_xal_get(fname, sxp->xattr) < 0) {
1061 free_xattr(sxp);
1062 return -1;
e2e42a01 1063@@ -864,6 +868,11 @@ int set_xattr(const char *fname, const struct file_struct *file,
7f0bf1cb
WD
1064 return -1;
1065 }
1066
1067+ if (IS_SPECIAL(sxp->st.st_mode) || IS_DEVICE(sxp->st.st_mode)) {
1068+ errno = ENOTSUP;
1069+ return -1;
1070+ }
1071+
1072 ndx = F_XATTR(file);
1073 return rsync_xal_set(fname, lst + ndx, fnamecmp, sxp);
1074 }
e2e42a01 1075@@ -980,7 +989,7 @@ int set_stat_xattr(const char *fname, struct file_struct *file, mode_t new_mode)
f9df736a
WD
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 */
1083