Adding filter-attribute-mods patch; updating patches.
[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
7170ca8d 11based-on: 181c9faf928faad08ef095f4667afe460ec3bef6
f9df736a
WD
12diff --git a/Makefile.in b/Makefile.in
13--- a/Makefile.in
14+++ b/Makefile.in
fc557362 15@@ -43,7 +43,7 @@ popt_OBJS=popt/findme.o popt/popt.o popt/poptconfig.o \
f9df736a
WD
16 popt/popthelp.o popt/poptparse.o
17 OBJS=$(OBJS1) $(OBJS2) $(OBJS3) $(DAEMON_OBJ) $(LIBOBJ) $(ZLIBOBJ) @BUILD_POPT@
18
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@
21
22 # Programs we must have to run the test cases
23 CHECK_PROGS = rsync$(EXEEXT) tls$(EXEEXT) getgroups$(EXEEXT) getfsdev$(EXEEXT) \
fc557362 24@@ -108,7 +108,7 @@ getgroups$(EXEEXT): getgroups.o
f9df736a
WD
25 getfsdev$(EXEEXT): getfsdev.o
26 $(CC) $(CFLAGS) $(LDFLAGS) -o $@ getfsdev.o $(LIBS)
27
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)
32
cc3e685d
WD
33diff --git a/compat.c b/compat.c
34--- a/compat.c
35+++ b/compat.c
fc557362 36@@ -40,9 +40,11 @@ extern int checksum_seed;
f9df736a
WD
37 extern int basis_dir_cnt;
38 extern int prune_empty_dirs;
39 extern int protocol_version;
40+extern int force_change;
ccdb48f6 41 extern int protect_args;
898a2112
WD
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;
9c25eef5 47 extern int need_messages_from_generator;
fc557362 48@@ -60,7 +62,7 @@ extern char *iconv_opt;
ccdb48f6 49 #endif
898a2112
WD
50
51 /* These index values are for the file-list's extra-attribute array. */
d4dd2dd5
WD
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;
898a2112 54
85096e5e 55 int receiver_symlink_times = 0; /* receiver can set the time on a symlink */
ae306a29 56 int sender_symlink_iconv = 0; /* sender should convert symlink content */
fc557362 57@@ -136,6 +138,8 @@ void setup_protocol(int f_out,int f_in)
898a2112 58 uid_ndx = ++file_extra_cnt;
fdf967c7 59 if (preserve_gid)
898a2112 60 gid_ndx = ++file_extra_cnt;
f9df736a 61+ if (preserve_fileflags || (force_change && !am_sender))
898a2112 62+ fileflags_ndx = ++file_extra_cnt;
ffc18846 63 if (preserve_acls && !am_sender)
898a2112 64 acls_ndx = ++file_extra_cnt;
5795bf59 65 if (preserve_xattrs)
cc3e685d
WD
66diff --git a/configure.in b/configure.in
67--- a/configure.in
68+++ b/configure.in
c0c7984e 69@@ -553,7 +553,7 @@ AC_CHECK_FUNCS(waitpid wait4 getcwd strdup chown chmod lchmod mknod mkfifo \
31611b92 70 setlocale setmode open64 lseek64 mkstemp64 mtrace va_copy __va_copy \
fc557362
WD
71 strerror putenv iconv_open locale_charset nl_langinfo getxattr \
72 extattr_get_link sigaction sigprocmask setattrlist getgrouplist \
73- initgroups)
74+ initgroups chflags)
31611b92 75
4c15e800 76 dnl cygwin iconv.h defines iconv_open as libiconv_open
fc557362
WD
77 if test x"$ac_cv_func_iconv_open" != x"yes"; then
78diff --git a/delete.c b/delete.c
fc557362
WD
79--- a/delete.c
80+++ b/delete.c
81@@ -25,6 +25,7 @@
82 extern int am_root;
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)
90 }
91
92 strlcpy(p, fp->basename, remainder);
93+#ifdef SUPPORT_FORCE_CHANGE
94+ if (force_change)
95+ make_mutable(fname, fp->mode, F_FFLAGS(fp), force_change);
96+#endif
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)
104 }
105
106 if (flags & DEL_NO_UID_WRITE)
107- do_chmod(fbuf, mode | S_IWUSR);
108+ do_chmod(fbuf, mode | S_IWUSR, NO_FFLAGS);
109
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. */
114 if (!uid_ndx)
115 uid_ndx = ++file_extra_cnt;
116+#ifdef SUPPORT_FORCE_CHANGE
117+ if (force_change) {
118+ STRUCT_STAT st;
119+ if (x_lstat(fbuf, &st, NULL) == 0)
120+ make_mutable(fbuf, st.st_mode, st.st_flags, force_change);
121+ }
122+#endif
123 ignore_perishable = 1;
124 /* If DEL_RECURSE is not set, this just reports emptiness. */
125 ret = delete_dir_contents(fbuf, flags);
cc3e685d
WD
126diff --git a/flist.c b/flist.c
127--- a/flist.c
128+++ b/flist.c
fc557362 129@@ -51,6 +51,7 @@ extern int preserve_links;
790ba11a
WD
130 extern int preserve_hard_links;
131 extern int preserve_devices;
31611b92 132 extern int preserve_specials;
f9df736a 133+extern int preserve_fileflags;
fc557362 134 extern int missing_args;
898a2112
WD
135 extern int uid_ndx;
136 extern int gid_ndx;
fc557362 137@@ -398,6 +399,9 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
7e27b6c0 138 {
31611b92
WD
139 static time_t modtime;
140 static mode_t mode;
f9df736a 141+#ifdef SUPPORT_FILEFLAGS
31611b92
WD
142+ static uint32 fileflags;
143+#endif
6240d1e4 144 #ifdef SUPPORT_HARD_LINKS
31611b92 145 static int64 dev;
6240d1e4 146 #endif
fc557362 147@@ -441,6 +445,14 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
99650e0d 148 xflags |= XMIT_SAME_MODE;
31611b92
WD
149 else
150 mode = file->mode;
f9df736a
WD
151+#ifdef SUPPORT_FILEFLAGS
152+ if (preserve_fileflags) {
a5e6228a
WD
153+ if (F_FFLAGS(file) == fileflags)
154+ xflags |= XMIT_SAME_FLAGS;
155+ else
156+ fileflags = F_FFLAGS(file);
157+ }
31611b92 158+#endif
99650e0d 159
7170ca8d
WD
160 if (preserve_devices && IS_DEVICE(mode)) {
161 if (protocol_version < 28) {
162@@ -578,6 +590,10 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
82c2230a 163 }
99650e0d 164 if (!(xflags & XMIT_SAME_MODE))
31611b92 165 write_int(f, to_wire_mode(mode));
f9df736a
WD
166+#ifdef SUPPORT_FILEFLAGS
167+ if (preserve_fileflags && !(xflags & XMIT_SAME_FLAGS))
31611b92
WD
168+ write_int(f, (int)fileflags);
169+#endif
f9df736a 170 if (preserve_uid && !(xflags & XMIT_SAME_UID)) {
caf38d8d
WD
171 if (protocol_version < 30)
172 write_int(f, uid);
7170ca8d 173@@ -666,6 +682,9 @@ static struct file_struct *recv_file_entry(struct file_list *flist,
31611b92 174 {
82c2230a 175 static int64 modtime;
31611b92 176 static mode_t mode;
f9df736a 177+#ifdef SUPPORT_FILEFLAGS
8871c12a 178+ static uint32 fileflags;
31611b92 179+#endif
6240d1e4 180 #ifdef SUPPORT_HARD_LINKS
31611b92 181 static int64 dev;
6240d1e4 182 #endif
7170ca8d 183@@ -805,6 +824,10 @@ static struct file_struct *recv_file_entry(struct file_list *flist,
a5e6228a 184
fc557362 185 if (chmod_modes && !S_ISLNK(mode) && mode)
31611b92 186 mode = tweak_mode(mode, chmod_modes);
f9df736a
WD
187+#ifdef SUPPORT_FILEFLAGS
188+ if (preserve_fileflags && !(xflags & XMIT_SAME_FLAGS))
31611b92
WD
189+ fileflags = (uint32)read_int(f);
190+#endif
191
f9df736a 192 if (preserve_uid && !(xflags & XMIT_SAME_UID)) {
caf38d8d 193 if (protocol_version < 30)
7170ca8d 194@@ -946,6 +969,10 @@ static struct file_struct *recv_file_entry(struct file_list *flist,
1aa236e1 195 }
7f0bf1cb 196 #endif
31611b92 197 file->mode = mode;
f9df736a
WD
198+#ifdef SUPPORT_FILEFLAGS
199+ if (preserve_fileflags)
70891d26 200+ F_FFLAGS(file) = fileflags;
31611b92 201+#endif
f9df736a 202 if (preserve_uid)
fc068916 203 F_OWNER(file) = uid;
f9df736a 204 if (preserve_gid) {
7170ca8d 205@@ -1333,6 +1360,10 @@ struct file_struct *make_file(const char *fname, struct file_list *flist,
1aa236e1 206 }
7f0bf1cb 207 #endif
31611b92 208 file->mode = st.st_mode;
f9df736a 209+#if defined SUPPORT_FILEFLAGS || defined SUPPORT_FORCE_CHANGE
898a2112 210+ if (fileflags_ndx)
70891d26 211+ F_FFLAGS(file) = st.st_flags;
31611b92 212+#endif
f9df736a 213 if (uid_ndx) /* Check uid_ndx instead of preserve_uid for del support */
fc068916 214 F_OWNER(file) = st.st_uid;
f9df736a 215 if (gid_ndx) /* Check gid_ndx instead of preserve_gid for del support */
7170ca8d 216@@ -1476,6 +1507,7 @@ static struct file_struct *send_file_name(int f, struct file_list *flist,
7f0bf1cb
WD
217 #endif
218 #ifdef SUPPORT_XATTRS
219 if (preserve_xattrs) {
220+ sx.st.st_mode = file->mode;
7f0bf1cb
WD
221 if (get_xattr(fname, &sx) < 0) {
222 io_error |= IOERR_GENERAL;
fc557362 223 return NULL;
cc3e685d
WD
224diff --git a/generator.c b/generator.c
225--- a/generator.c
226+++ b/generator.c
c0c7984e 227@@ -42,8 +42,10 @@ extern int preserve_devices;
f9df736a
WD
228 extern int preserve_specials;
229 extern int preserve_hard_links;
230 extern int preserve_executability;
231+extern int preserve_fileflags;
232 extern int preserve_perms;
233 extern int preserve_times;
234+extern int force_change;
235 extern int uid_ndx;
236 extern int gid_ndx;
237 extern int delete_mode;
fc557362 238@@ -406,6 +408,11 @@ int unchanged_attrs(const char *fname, struct file_struct *file, stat_x *sxp)
c0c7984e 239 && ((sxp->st.st_mode & 0111 ? 1 : 0) ^ (file->mode & 0111 ? 1 : 0)))
f9df736a 240 return 0;
a5e6228a 241
f9df736a
WD
242+#ifdef SUPPORT_FILEFLAGS
243+ if (preserve_fileflags && !S_ISLNK(file->mode) && sxp->st.st_flags != F_FFLAGS(file))
244+ return 0;
245+#endif
a5e6228a 246+
c0c7984e 247 if (am_root && uid_ndx && sxp->st.st_uid != (uid_t)F_OWNER(file))
f9df736a 248 return 0;
ee76479d 249
fc557362 250@@ -471,6 +478,11 @@ void itemize(const char *fnamecmp, struct file_struct *file, int ndx, int statre
f9df736a
WD
251 if (gid_ndx && !(file->flags & FLAG_SKIP_GROUP)
252 && sxp->st.st_gid != (gid_t)F_GROUP(file))
253 iflags |= ITEM_REPORT_GROUP;
254+#ifdef SUPPORT_FILEFLAGS
255+ if (preserve_fileflags && !S_ISLNK(file->mode)
256+ && sxp->st.st_flags != F_FFLAGS(file))
257+ iflags |= ITEM_REPORT_FFLAGS;
a5e6228a 258+#endif
f9df736a
WD
259 #ifdef SUPPORT_ACLS
260 if (preserve_acls && !S_ISLNK(file->mode)) {
261 if (!ACL_READY(*sxp))
fc557362 262@@ -1258,6 +1270,10 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
f9df736a
WD
263 file->mode = dest_mode(file->mode, sx.st.st_mode,
264 dflt_perms, statret == 0);
f90a882e 265 }
f9df736a
WD
266+#ifdef SUPPORT_FORCE_CHANGE
267+ if (force_change && !preserve_fileflags)
268+ F_FFLAGS(file) = sx.st.st_flags;
a5e6228a 269+#endif
f9df736a
WD
270 if (statret != 0 && basis_dir[0] != NULL) {
271 int j = try_dests_non(file, fname, ndx, fnamecmpbuf, &sx,
272 itemizing, code);
fc557362 273@@ -1298,10 +1314,15 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
f9df736a
WD
274 /* We need to ensure that the dirs in the transfer have writable
275 * permissions during the time we are putting files within them.
276 * This is then fixed after the transfer is done. */
277+#ifdef SUPPORT_FORCE_CHANGE
278+ if (force_change && F_FFLAGS(file) & force_change
279+ && make_mutable(fname, file->mode, F_FFLAGS(file), force_change))
280+ need_retouch_dir_perms = 1;
281+#endif
282 #ifdef HAVE_CHMOD
283 if (!am_root && !(file->mode & S_IWUSR) && dir_tweaking) {
284 mode_t mode = file->mode | S_IWUSR;
285- if (do_chmod(fname, mode) < 0) {
286+ if (do_chmod(fname, mode, 0) < 0) {
287 rsyserr(FERROR_XFER, errno,
288 "failed to modify permissions on %s",
289 full_fname(fname));
fc557362 290@@ -1336,6 +1357,10 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
f9df736a
WD
291 file->mode = dest_mode(file->mode, sx.st.st_mode, dflt_perms,
292 exists);
293 }
294+#ifdef SUPPORT_FORCE_CHANGE
295+ if (force_change && !preserve_fileflags)
296+ F_FFLAGS(file) = sx.st.st_flags;
a5e6228a
WD
297+#endif
298
f9df736a
WD
299 #ifdef SUPPORT_HARD_LINKS
300 if (preserve_hard_links && F_HLINK_NOT_FIRST(file)
7170ca8d 301@@ -1865,13 +1890,17 @@ static void touch_up_dirs(struct file_list *flist, int ndx)
f9df736a
WD
302 continue;
303 fname = f_name(file, NULL);
304 if (!(file->mode & S_IWUSR))
305- do_chmod(fname, file->mode);
306+ do_chmod(fname, file->mode, 0);
c0c7984e
WD
307 if (need_retouch_dir_times) {
308 STRUCT_STAT st;
309 if (link_stat(fname, &st, 0) == 0
310 && cmp_time(st.st_mtime, file->modtime) != 0)
311- set_modtime(fname, file->modtime, file->mode);
312+ set_modtime(fname, file->modtime, file->mode, 0);
313 }
f9df736a
WD
314+#ifdef SUPPORT_FORCE_CHANGE
315+ if (force_change && F_FFLAGS(file) & force_change)
316+ undo_make_mutable(fname, F_FFLAGS(file));
317+#endif
e2e42a01
WD
318 if (counter >= loopchk_limit) {
319 if (allowed_lull)
320 maybe_send_keepalive();
f9df736a
WD
321diff --git a/log.c b/log.c
322--- a/log.c
323+++ b/log.c
fc557362 324@@ -715,7 +715,7 @@ static void log_formatted(enum logcode code, const char *format, const char *op,
f9df736a
WD
325 c[5] = !(iflags & ITEM_REPORT_PERMS) ? '.' : 'p';
326 c[6] = !(iflags & ITEM_REPORT_OWNER) ? '.' : 'o';
327 c[7] = !(iflags & ITEM_REPORT_GROUP) ? '.' : 'g';
328- c[8] = !(iflags & ITEM_REPORT_ATIME) ? '.' : 'u';
329+ c[8] = !(iflags & ITEM_REPORT_FFLAGS) ? '.' : 'f';
330 c[9] = !(iflags & ITEM_REPORT_ACL) ? '.' : 'a';
331 c[10] = !(iflags & ITEM_REPORT_XATTR) ? '.' : 'x';
332 c[11] = '\0';
cc3e685d
WD
333diff --git a/options.c b/options.c
334--- a/options.c
335+++ b/options.c
c0c7984e 336@@ -53,6 +53,7 @@ int preserve_hard_links = 0;
ffc18846 337 int preserve_acls = 0;
5795bf59 338 int preserve_xattrs = 0;
31611b92 339 int preserve_perms = 0;
5e3c6c93 340+int preserve_fileflags = 0;
31611b92
WD
341 int preserve_executability = 0;
342 int preserve_devices = 0;
343 int preserve_specials = 0;
fc557362
WD
344@@ -86,6 +87,7 @@ int numeric_ids = 0;
345 int msgs2stderr = 0;
f9df736a
WD
346 int allow_8bit_chars = 0;
347 int force_delete = 0;
348+int force_change = 0;
349 int io_timeout = 0;
f9df736a 350 int prune_empty_dirs = 0;
fc557362
WD
351 int use_qsort = 0;
352@@ -566,6 +568,7 @@ static void print_rsync_version(enum logcode f)
31611b92 353 char const *links = "no ";
58b399b9 354 char const *iconv = "no ";
31611b92 355 char const *ipv6 = "no ";
8871c12a 356+ char const *fileflags = "no ";
31611b92
WD
357 STRUCT_STAT *dumstat;
358
ac2da598 359 #if SUBPROTOCOL_VERSION != 0
fc557362 360@@ -599,6 +602,9 @@ static void print_rsync_version(enum logcode f)
85096e5e
WD
361 #if defined HAVE_LUTIMES && defined HAVE_UTIMES
362 symtimes = "";
31611b92 363 #endif
f9df736a 364+#ifdef SUPPORT_FILEFLAGS
5e3c6c93 365+ fileflags = "";
31611b92 366+#endif
ac2da598
WD
367
368 rprintf(f, "%s version %s protocol version %d%s\n",
369 RSYNC_NAME, RSYNC_VERSION, PROTOCOL_VERSION, subprotocol);
fc557362 370@@ -612,8 +618,8 @@ static void print_rsync_version(enum logcode f)
5e3c6c93
WD
371 (int)(sizeof (int64) * 8));
372 rprintf(f, " %ssocketpairs, %shardlinks, %ssymlinks, %sIPv6, batchfiles, %sinplace,\n",
373 got_socketpair, hardlinks, links, ipv6, have_inplace);
85096e5e
WD
374- rprintf(f, " %sappend, %sACLs, %sxattrs, %siconv, %ssymtimes\n",
375- have_inplace, acls, xattrs, iconv, symtimes);
376+ rprintf(f, " %sappend, %sACLs, %sxattrs, %siconv, %ssymtimes, %sfile-flags\n",
377+ have_inplace, acls, xattrs, iconv, symtimes, fileflags);
5e3c6c93 378
31611b92 379 #ifdef MAINTAINER_MODE
5e3c6c93 380 rprintf(f, "Panic Action: \"%s\"\n", get_panic_action());
fc557362 381@@ -683,6 +689,9 @@ void usage(enum logcode F)
31611b92
WD
382 rprintf(F," -K, --keep-dirlinks treat symlinked dir on receiver as dir\n");
383 rprintf(F," -H, --hard-links preserve hard links\n");
384 rprintf(F," -p, --perms preserve permissions\n");
f9df736a
WD
385+#ifdef SUPPORT_FILEFLAGS
386+ rprintf(F," --fileflags preserve file-flags (aka chflags)\n");
387+#endif
31611b92 388 rprintf(F," -E, --executability preserve the file's executability\n");
063cf77b 389 rprintf(F," --chmod=CHMOD affect file and/or directory permissions\n");
ffc18846 390 #ifdef SUPPORT_ACLS
fc557362
WD
391@@ -722,7 +731,12 @@ void usage(enum logcode F)
392 rprintf(F," --ignore-missing-args ignore missing source args without error\n");
393 rprintf(F," --delete-missing-args delete missing source args from destination\n");
f9df736a
WD
394 rprintf(F," --ignore-errors delete even if there are I/O errors\n");
395- rprintf(F," --force force deletion of directories even if not empty\n");
396+ rprintf(F," --force-delete force deletion of directories even if not empty\n");
397+#ifdef SUPPORT_FORCE_CHANGE
398+ rprintf(F," --force-change affect user-/system-immutable files/dirs\n");
399+ rprintf(F," --force-uchange affect user-immutable files/dirs\n");
400+ rprintf(F," --force-schange affect system-immutable files/dirs\n");
401+#endif
402 rprintf(F," --max-delete=NUM don't delete more than NUM files\n");
403 rprintf(F," --max-size=SIZE don't transfer any file larger than SIZE\n");
404 rprintf(F," --min-size=SIZE don't transfer any file smaller than SIZE\n");
fc557362 405@@ -835,6 +849,10 @@ static struct poptOption long_options[] = {
31611b92
WD
406 {"perms", 'p', POPT_ARG_VAL, &preserve_perms, 1, 0, 0 },
407 {"no-perms", 0, POPT_ARG_VAL, &preserve_perms, 0, 0, 0 },
408 {"no-p", 0, POPT_ARG_VAL, &preserve_perms, 0, 0, 0 },
f9df736a 409+#ifdef SUPPORT_FILEFLAGS
5e3c6c93
WD
410+ {"fileflags", 0, POPT_ARG_VAL, &preserve_fileflags, 1, 0, 0 },
411+ {"no-fileflags", 0, POPT_ARG_VAL, &preserve_fileflags, 0, 0, 0 },
f9df736a 412+#endif
31611b92 413 {"executability", 'E', POPT_ARG_NONE, &preserve_executability, 0, 0, 0 },
ffc18846
WD
414 {"acls", 'A', POPT_ARG_NONE, 0, 'A', 0, 0 },
415 {"no-acls", 0, POPT_ARG_VAL, &preserve_acls, 0, 0, 0 },
fc557362 416@@ -917,6 +935,14 @@ static struct poptOption long_options[] = {
f9df736a 417 {"remove-source-files",0,POPT_ARG_VAL, &remove_source_files, 1, 0, 0 },
c0c7984e
WD
418 {"force", 0, POPT_ARG_VAL, &force_delete, 1, 0, 0 },
419 {"no-force", 0, POPT_ARG_VAL, &force_delete, 0, 0, 0 },
420+ {"force-delete", 0, POPT_ARG_VAL, &force_delete, 1, 0, 0 },
421+ {"no-force-delete", 0, POPT_ARG_VAL, &force_delete, 0, 0, 0 },
f9df736a
WD
422+#ifdef SUPPORT_FORCE_CHANGE
423+ {"force-change", 0, POPT_ARG_VAL, &force_change, ALL_IMMUTABLE, 0, 0 },
424+ {"no-force-change", 0, POPT_ARG_VAL, &force_change, 0, 0, 0 },
425+ {"force-uchange", 0, POPT_ARG_VAL, &force_change, USR_IMMUTABLE, 0, 0 },
426+ {"force-schange", 0, POPT_ARG_VAL, &force_change, SYS_IMMUTABLE, 0, 0 },
31611b92 427+#endif
c0c7984e
WD
428 {"ignore-errors", 0, POPT_ARG_VAL, &ignore_errors, 1, 0, 0 },
429 {"no-ignore-errors", 0, POPT_ARG_VAL, &ignore_errors, 0, 0, 0 },
f9df736a 430 {"max-delete", 0, POPT_ARG_INT, &max_delete, 0, 0, 0 },
7170ca8d 431@@ -2400,6 +2426,9 @@ void server_options(char **args, int *argc_p)
31611b92
WD
432 if (xfer_dirs && !recurse && delete_mode && am_sender)
433 args[ac++] = "--no-r";
434
5e3c6c93 435+ if (preserve_fileflags)
a5e6228a 436+ args[ac++] = "--fileflags";
31611b92
WD
437+
438 if (do_compression && def_compress_level != Z_DEFAULT_COMPRESSION) {
439 if (asprintf(&arg, "--compress-level=%d", def_compress_level) < 0)
440 goto oom;
7170ca8d 441@@ -2487,6 +2516,16 @@ void server_options(char **args, int *argc_p)
f9df736a
WD
442 args[ac++] = "--delete-excluded";
443 if (force_delete)
444 args[ac++] = "--force";
445+#ifdef SUPPORT_FORCE_CHANGE
446+ if (force_change) {
447+ if (force_change == ALL_IMMUTABLE)
448+ args[ac++] = "--force-change";
449+ else if (force_change == USR_IMMUTABLE)
450+ args[ac++] = "--force-uchange";
451+ else if (force_change == SYS_IMMUTABLE)
452+ args[ac++] = "--force-schange";
453+ }
454+#endif
455 if (write_batch < 0)
456 args[ac++] = "--only-write-batch=X";
457 if (am_root > 1)
cc3e685d
WD
458diff --git a/rsync.c b/rsync.c
459--- a/rsync.c
460+++ b/rsync.c
fc557362 461@@ -31,6 +31,7 @@ extern int dry_run;
ffc18846 462 extern int preserve_acls;
5795bf59 463 extern int preserve_xattrs;
31611b92 464 extern int preserve_perms;
5e3c6c93 465+extern int preserve_fileflags;
31611b92
WD
466 extern int preserve_executability;
467 extern int preserve_times;
9c25eef5 468 extern int am_root;
fc557362 469@@ -378,6 +379,39 @@ mode_t dest_mode(mode_t flist_mode, mode_t stat_mode, int dflt_perms,
f90a882e
WD
470 return new_mode;
471 }
472
f9df736a 473+#if defined SUPPORT_FILEFLAGS || defined SUPPORT_FORCE_CHANGE
f90a882e
WD
474+/* Set a file's st_flags. */
475+static int set_fileflags(const char *fname, uint32 fileflags)
476+{
477+ if (do_chflags(fname, fileflags) != 0) {
cc3e685d 478+ rsyserr(FERROR_XFER, errno,
f90a882e
WD
479+ "failed to set file flags on %s",
480+ full_fname(fname));
481+ return 0;
482+ }
483+
484+ return 1;
485+}
486+
487+/* Remove immutable flags from an object, so it can be altered/removed. */
f9df736a 488+int make_mutable(const char *fname, mode_t mode, uint32 fileflags, uint32 iflags)
f90a882e 489+{
f9df736a
WD
490+ if (S_ISLNK(mode) || !(fileflags & iflags))
491+ return 0;
492+ if (!set_fileflags(fname, fileflags & ~iflags))
493+ return -1;
494+ return 1;
f90a882e
WD
495+}
496+
f9df736a
WD
497+/* Undo a prior make_mutable() call that returned a 1. */
498+int undo_make_mutable(const char *fname, uint32 fileflags)
f90a882e 499+{
f9df736a
WD
500+ if (!set_fileflags(fname, fileflags))
501+ return -1;
502+ return 1;
f90a882e
WD
503+}
504+#endif
505+
52e09c4e 506 int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
5795bf59 507 const char *fnamecmp, int flags)
f90a882e 508 {
fc557362 509@@ -426,7 +460,7 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
f9df736a
WD
510 flags |= ATTRS_SKIP_MTIME;
511 if (!(flags & ATTRS_SKIP_MTIME)
512 && cmp_time(sxp->st.st_mtime, file->modtime) != 0) {
513- int ret = set_modtime(fname, file->modtime, sxp->st.st_mode);
514+ int ret = set_modtime(fname, file->modtime, sxp->st.st_mode, ST_FLAGS(sxp->st));
515 if (ret < 0) {
516 rsyserr(FERROR_XFER, errno, "failed to set times on %s",
517 full_fname(fname));
fc557362 518@@ -462,7 +496,8 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
f9df736a
WD
519 if (am_root >= 0) {
520 if (do_lchown(fname,
521 change_uid ? (uid_t)F_OWNER(file) : sxp->st.st_uid,
522- change_gid ? (gid_t)F_GROUP(file) : sxp->st.st_gid) != 0) {
523+ change_gid ? (gid_t)F_GROUP(file) : sxp->st.st_gid,
524+ sxp->st.st_mode, ST_FLAGS(sxp->st)) != 0) {
525 /* We shouldn't have attempted to change uid
526 * or gid unless have the privilege. */
527 rsyserr(FERROR_XFER, errno, "%s %s failed",
fc557362 528@@ -494,7 +529,7 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
f9df736a
WD
529
530 #ifdef HAVE_CHMOD
531 if (!BITS_EQUAL(sxp->st.st_mode, new_mode, CHMOD_BITS)) {
532- int ret = am_root < 0 ? 0 : do_chmod(fname, new_mode);
533+ int ret = am_root < 0 ? 0 : do_chmod(fname, new_mode, ST_FLAGS(sxp->st));
534 if (ret < 0) {
535 rsyserr(FERROR_XFER, errno,
536 "failed to set permissions on %s",
fc557362 537@@ -506,6 +541,19 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
31611b92
WD
538 }
539 #endif
540
f9df736a 541+#ifdef SUPPORT_FILEFLAGS
7d006b5b
WD
542+ if (preserve_fileflags && !S_ISLNK(sxp->st.st_mode)
543+ && sxp->st.st_flags != F_FFLAGS(file)) {
f9df736a
WD
544+ uint32 fileflags = F_FFLAGS(file);
545+ if (flags & ATTRS_DELAY_IMMUTABLE)
546+ fileflags &= ~ALL_IMMUTABLE;
547+ if (sxp->st.st_flags != fileflags
548+ && !set_fileflags(fname, fileflags))
85096e5e 549+ goto cleanup;
31611b92
WD
550+ updated = 1;
551+ }
552+#endif
553+
fc557362 554 if (INFO_GTE(NAME, 2) && flags & ATTRS_REPORT) {
ff318e90
WD
555 if (updated)
556 rprintf(FCLIENT, "%s\n", fname);
fc557362 557@@ -570,7 +618,8 @@ int finish_transfer(const char *fname, const char *fnametmp,
f9df736a
WD
558
559 /* Change permissions before putting the file into place. */
5795bf59 560 set_file_attrs(fnametmp, file, NULL, fnamecmp,
f9df736a
WD
561- ok_to_set_time ? 0 : ATTRS_SKIP_MTIME);
562+ ATTRS_DELAY_IMMUTABLE
563+ | (ok_to_set_time ? 0 : ATTRS_SKIP_MTIME));
31611b92 564
f90a882e 565 /* move tmp file over real file */
fc557362
WD
566 if (DEBUG_GTE(RECV, 1))
567@@ -589,6 +638,10 @@ int finish_transfer(const char *fname, const char *fnametmp,
f90a882e
WD
568 }
569 if (ret == 0) {
570 /* The file was moved into place (not copied), so it's done. */
f9df736a
WD
571+#ifdef SUPPORT_FILEFLAGS
572+ if (preserve_fileflags && F_FFLAGS(file) & ALL_IMMUTABLE)
573+ set_fileflags(fname, F_FFLAGS(file));
31611b92 574+#endif
4c15e800 575 return 1;
f90a882e
WD
576 }
577 /* The file was copied, so tweak the perms of the copied file. If it
cc3e685d
WD
578diff --git a/rsync.h b/rsync.h
579--- a/rsync.h
580+++ b/rsync.h
fc557362 581@@ -61,6 +61,7 @@
9c25eef5
WD
582 #define XMIT_GROUP_NAME_FOLLOWS (1<<11) /* protocols 30 - now */
583 #define XMIT_HLINK_FIRST (1<<12) /* protocols 30 - now (HLINKED files only) */
fc557362 584 #define XMIT_IO_ERROR_ENDLIST (1<<12) /* protocols 31 - now (w/XMIT_EXTENDED_FLAGS) */
9c25eef5 585+#define XMIT_SAME_FLAGS (1<<14) /* protocols ?? - now */
31611b92
WD
586
587 /* These flags are used in the live flist data. */
588
fc557362 589@@ -155,6 +156,7 @@
f9df736a
WD
590
591 #define ATTRS_REPORT (1<<0)
592 #define ATTRS_SKIP_MTIME (1<<1)
593+#define ATTRS_DELAY_IMMUTABLE (1<<2)
594
595 #define FULL_FLUSH 1
596 #define NORMAL_FLUSH 0
fc557362 597@@ -181,6 +183,7 @@
f9df736a
WD
598 #define ITEM_REPORT_GROUP (1<<6)
599 #define ITEM_REPORT_ACL (1<<7)
600 #define ITEM_REPORT_XATTR (1<<8)
601+#define ITEM_REPORT_FFLAGS (1<<9)
602 #define ITEM_BASIS_TYPE_FOLLOWS (1<<11)
603 #define ITEM_XNAME_FOLLOWS (1<<12)
604 #define ITEM_IS_NEW (1<<13)
7170ca8d 605@@ -486,6 +489,28 @@ typedef unsigned int size_t;
7e27b6c0 606 #endif
6e6f514c
WD
607 #endif
608
f9df736a
WD
609+#define NO_FFLAGS ((uint32)-1)
610+
6e6f514c 611+#ifdef HAVE_CHFLAGS
f9df736a
WD
612+#define SUPPORT_FILEFLAGS 1
613+#define SUPPORT_FORCE_CHANGE 1
6e6f514c 614+#endif
a5e6228a 615+
f9df736a 616+#if defined SUPPORT_FILEFLAGS || defined SUPPORT_FORCE_CHANGE
a5e6228a
WD
617+#ifndef UF_NOUNLINK
618+#define UF_NOUNLINK 0
619+#endif
620+#ifndef SF_NOUNLINK
621+#define SF_NOUNLINK 0
622+#endif
c0c7984e
WD
623+#define USR_IMMUTABLE (UF_IMMUTABLE|UF_NOUNLINK|UF_APPEND)
624+#define SYS_IMMUTABLE (SF_IMMUTABLE|SF_NOUNLINK|SF_APPEND)
f9df736a
WD
625+#define ALL_IMMUTABLE (USR_IMMUTABLE|SYS_IMMUTABLE)
626+#define ST_FLAGS(st) (st.st_flags)
627+#else
628+#define ST_FLAGS(st) NO_FFLAGS
a5e6228a 629+#endif
6e6f514c
WD
630+
631 /* Find a variable that is either exactly 32-bits or longer.
632 * If some code depends on 32-bit truncation, it will need to
633 * take special action in a "#if SIZEOF_INT32 > 4" section. */
7170ca8d 634@@ -656,6 +681,7 @@ extern int file_extra_cnt;
9c25eef5 635 extern int inc_recurse;
898a2112
WD
636 extern int uid_ndx;
637 extern int gid_ndx;
638+extern int fileflags_ndx;
639 extern int acls_ndx;
640 extern int xattrs_ndx;
fdf967c7 641
7170ca8d 642@@ -693,6 +719,11 @@ extern int xattrs_ndx;
1aa236e1 643 /* When the associated option is on, all entries will have these present: */
898a2112
WD
644 #define F_OWNER(f) REQ_EXTRA(f, uid_ndx)->unum
645 #define F_GROUP(f) REQ_EXTRA(f, gid_ndx)->unum
f9df736a 646+#if defined SUPPORT_FILEFLAGS || defined SUPPORT_FORCE_CHANGE
898a2112 647+#define F_FFLAGS(f) REQ_EXTRA(f, fileflags_ndx)->unum
f9df736a
WD
648+#else
649+#define F_FFLAGS(f) NO_FFLAGS
650+#endif
898a2112
WD
651 #define F_ACL(f) REQ_EXTRA(f, acls_ndx)->num
652 #define F_XATTR(f) REQ_EXTRA(f, xattrs_ndx)->num
d4dd2dd5 653 #define F_NDX(f) REQ_EXTRA(f, unsort_ndx)->num
cc3e685d
WD
654diff --git a/rsync.yo b/rsync.yo
655--- a/rsync.yo
656+++ b/rsync.yo
fc557362 657@@ -345,6 +345,7 @@ to the detailed description below for a complete description. verb(
1ed0b5c9
WD
658 -K, --keep-dirlinks treat symlinked dir on receiver as dir
659 -H, --hard-links preserve hard links
31611b92 660 -p, --perms preserve permissions
f9df736a 661+ --fileflags preserve file-flags (aka chflags)
1ed0b5c9 662 -E, --executability preserve executability
063cf77b 663 --chmod=CHMOD affect file and/or directory permissions
ffc18846 664 -A, --acls preserve ACLs (implies -p)
fc557362
WD
665@@ -378,7 +379,10 @@ to the detailed description below for a complete description. verb(
666 --ignore-missing-args ignore missing source args without error
667 --delete-missing-args delete missing source args from destination
f9df736a
WD
668 --ignore-errors delete even if there are I/O errors
669- --force force deletion of dirs even if not empty
670+ --force-delete force deletion of dirs even if not empty
671+ --force-change affect user/system immutable files/dirs
672+ --force-uchange affect user-immutable files/dirs
673+ --force-schange affect system-immutable files/dirs
674 --max-delete=NUM don't delete more than NUM files
675 --max-size=SIZE don't transfer any file larger than SIZE
676 --min-size=SIZE don't transfer any file smaller than SIZE
7170ca8d 677@@ -595,7 +599,8 @@ specified, in which case bf(-r) is not implied.
31611b92
WD
678
679 Note that bf(-a) bf(does not preserve hardlinks), because
680 finding multiply-linked files is expensive. You must separately
681-specify bf(-H).
a5e6228a
WD
682+specify bf(-H). Note also that for backward compatibility, bf(-a)
683+currently does bf(not) imply the bf(--fileflags) option.
31611b92
WD
684
685 dit(--no-OPTION) You may turn off one or more implied options by prefixing
686 the option name with "no-". Not all options may be prefixed with a "no-":
7170ca8d 687@@ -876,7 +881,7 @@ they would be using bf(--copy-links).
f9df736a
WD
688 Without this option, if the sending side has replaced a directory with a
689 symlink to a directory, the receiving side will delete anything that is in
690 the way of the new symlink, including a directory hierarchy (as long as
691-bf(--force) or bf(--delete) is in effect).
692+bf(--force-delete) or bf(--delete) is in effect).
693
694 See also bf(--keep-dirlinks) for an analogous option for the receiving
695 side.
7170ca8d 696@@ -1013,6 +1018,29 @@ super-user copies all namespaces except system.*. A normal user only copies
c8a8b4a7
WD
697 the user.* namespace. To be able to backup and restore non-user namespaces as
698 a normal user, see the bf(--fake-super) option.
31611b92 699
f9df736a
WD
700+dit(bf(--fileflags)) This option causes rsync to update the file-flags to be
701+the same as the source files and directories (if your OS supports the
702+bf(chflags)(2) system call). Some flags can only be altered by the super-user
703+and some might only be unset below a certain secure-level (usually single-user
704+mode). It will not make files alterable that are set to immutable on the
705+receiver. To do that, see bf(--force-change), bf(--force-uchange), and
706+bf(--force-schange).
707+
708+dit(bf(--force-change)) This option causes rsync to disable both user-immutable
709+and system-immutable flags on files and directories that are being updated or
710+deleted on the receiving side. This option overrides bf(--force-uchange) and
711+bf(--force-schange).
712+
713+dit(bf(--force-uchange)) This option causes rsync to disable user-immutable
714+flags on files and directories that are being updated or deleted on the
715+receiving side. It does not try to affect system flags. This option overrides
716+bf(--force-change) and bf(--force-schange).
717+
718+dit(bf(--force-schange)) This option causes rsync to disable system-immutable
719+flags on files and directories that are being updated or deleted on the
720+receiving side. It does not try to affect user flags. This option overrides
721+bf(--force-change) and bf(--force-schange).
31611b92
WD
722+
723 dit(bf(--chmod)) This option tells rsync to apply one or more
724 comma-separated "chmod" strings to the permission of the files in the
725 transfer. The resulting value is treated as though it was the permissions
7170ca8d 726@@ -1300,12 +1328,13 @@ display as a "*missing" entry in the bf(--list-only) output.
f9df736a
WD
727 dit(bf(--ignore-errors)) Tells bf(--delete) to go ahead and delete files
728 even when there are I/O errors.
729
730-dit(bf(--force)) This option tells rsync to delete a non-empty directory
731+dit(bf(--force-delete)) This option tells rsync to delete a non-empty directory
732 when it is to be replaced by a non-directory. This is only relevant if
733 deletions are not active (see bf(--delete) for details).
734
735-Note for older rsync versions: bf(--force) used to still be required when
736-using bf(--delete-after), and it used to be non-functional unless the
737+This option can be abbreviated bf(--force) for backward compatibility.
738+Note that some older rsync versions used to still require bf(--force)
739+when using bf(--delete-after), and it used to be non-functional unless the
740 bf(--recursive) option was also enabled.
741
742 dit(bf(--max-delete=NUM)) This tells rsync not to delete more than NUM
7170ca8d 743@@ -1851,7 +1880,7 @@ with older versions of rsync, but that also turns on the output of other
f9df736a
WD
744 verbose messages).
745
746 The "%i" escape has a cryptic output that is 11 letters long. The general
747-format is like the string bf(YXcstpoguax), where bf(Y) is replaced by the
748+format is like the string bf(YXcstpogfax), where bf(Y) is replaced by the
749 type of update being done, bf(X) is replaced by the file-type, and the
750 other letters represent attributes that may be output if they are being
751 modified.
7170ca8d 752@@ -1907,7 +1936,7 @@ quote(itemization(
f9df736a
WD
753 sender's value (requires bf(--owner) and super-user privileges).
754 it() A bf(g) means the group is different and is being updated to the
755 sender's value (requires bf(--group) and the authority to set the group).
756- it() The bf(u) slot is reserved for future use.
757+ it() The bf(f) means that the fileflags information changed.
758 it() The bf(a) means that the ACL information changed.
759 it() The bf(x) means that the extended attribute information changed.
760 ))
cc3e685d
WD
761diff --git a/syscall.c b/syscall.c
762--- a/syscall.c
763+++ b/syscall.c
f9df736a
WD
764@@ -33,6 +33,7 @@ extern int dry_run;
765 extern int am_root;
766 extern int read_only;
767 extern int list_only;
768+extern int force_change;
769 extern int preserve_perms;
770 extern int preserve_executability;
771
772@@ -50,7 +51,23 @@ int do_unlink(const char *fname)
773 {
774 if (dry_run) return 0;
775 RETURN_ERROR_IF_RO_OR_LO;
776- return unlink(fname);
777+ if (unlink(fname) == 0)
778+ return 0;
779+#ifdef SUPPORT_FORCE_CHANGE
780+ if (force_change && errno == EPERM) {
781+ STRUCT_STAT st;
782+
783+ if (x_lstat(fname, &st, NULL) == 0
784+ && make_mutable(fname, st.st_mode, st.st_flags, force_change) > 0) {
785+ if (unlink(fname) == 0)
786+ return 0;
787+ undo_make_mutable(fname, st.st_flags);
788+ }
789+ /* TODO: handle immutable directories */
790+ errno = EPERM;
791+ }
792+#endif
793+ return -1;
794 }
795
796 int do_symlink(const char *fname1, const char *fname2)
797@@ -69,14 +86,37 @@ int do_link(const char *fname1, const char *fname2)
798 }
799 #endif
800
801-int do_lchown(const char *path, uid_t owner, gid_t group)
802+int do_lchown(const char *path, uid_t owner, gid_t group, mode_t mode, uint32 fileflags)
803 {
804 if (dry_run) return 0;
805 RETURN_ERROR_IF_RO_OR_LO;
806 #ifndef HAVE_LCHOWN
807 #define lchown chown
808 #endif
809- return lchown(path, owner, group);
810+ if (lchown(path, owner, group) == 0)
811+ return 0;
812+#ifdef SUPPORT_FORCE_CHANGE
813+ if (force_change && errno == EPERM) {
814+ if (fileflags == NO_FFLAGS) {
815+ STRUCT_STAT st;
816+ if (x_lstat(path, &st, NULL) == 0) {
817+ mode = st.st_mode;
818+ fileflags = st.st_flags;
819+ }
820+ }
821+ if (fileflags != NO_FFLAGS
822+ && make_mutable(path, mode, fileflags, force_change) > 0) {
823+ int ret = lchown(path, owner, group);
824+ undo_make_mutable(path, fileflags);
825+ if (ret == 0)
826+ return 0;
827+ }
828+ errno = EPERM;
829+ }
830+#else
831+ mode = fileflags = 0; /* avoid compiler warning */
832+#endif
833+ return -1;
31611b92 834 }
f9df736a
WD
835
836 int do_mknod(const char *pathname, mode_t mode, dev_t dev)
837@@ -116,7 +156,7 @@ int do_mknod(const char *pathname, mode_t mode, dev_t dev)
838 return -1;
839 close(sock);
840 #ifdef HAVE_CHMOD
841- return do_chmod(pathname, mode);
842+ return do_chmod(pathname, mode, 0);
843 #else
844 return 0;
31611b92 845 #endif
f9df736a
WD
846@@ -133,7 +173,22 @@ int do_rmdir(const char *pathname)
847 {
848 if (dry_run) return 0;
849 RETURN_ERROR_IF_RO_OR_LO;
850- return rmdir(pathname);
851+ if (rmdir(pathname) == 0)
852+ return 0;
853+#ifdef SUPPORT_FORCE_CHANGE
854+ if (force_change && errno == EPERM) {
855+ STRUCT_STAT st;
856+
857+ if (x_lstat(pathname, &st, NULL) == 0
858+ && make_mutable(pathname, st.st_mode, st.st_flags, force_change) > 0) {
859+ if (rmdir(pathname) == 0)
860+ return 0;
861+ undo_make_mutable(pathname, st.st_flags);
862+ }
863+ errno = EPERM;
864+ }
865+#endif
866+ return -1;
867 }
868
869 int do_open(const char *pathname, int flags, mode_t mode)
870@@ -147,7 +202,7 @@ int do_open(const char *pathname, int flags, mode_t mode)
871 }
31611b92 872
f9df736a
WD
873 #ifdef HAVE_CHMOD
874-int do_chmod(const char *path, mode_t mode)
875+int do_chmod(const char *path, mode_t mode, uint32 fileflags)
876 {
877 int code;
878 if (dry_run) return 0;
879@@ -168,17 +223,74 @@ int do_chmod(const char *path, mode_t mode)
880 #endif
881 } else
c0c7984e 882 code = chmod(path, mode & CHMOD_BITS); /* DISCOURAGED FUNCTION */
f9df736a
WD
883+#ifdef SUPPORT_FORCE_CHANGE
884+ if (code < 0 && force_change && errno == EPERM && !S_ISLNK(mode)) {
885+ if (fileflags == NO_FFLAGS) {
886+ STRUCT_STAT st;
887+ if (x_lstat(path, &st, NULL) == 0)
888+ fileflags = st.st_flags;
889+ }
890+ if (fileflags != NO_FFLAGS
891+ && make_mutable(path, mode, fileflags, force_change) > 0) {
892+ code = chmod(path, mode & CHMOD_BITS);
893+ undo_make_mutable(path, fileflags);
894+ if (code == 0)
895+ return 0;
896+ }
897+ errno = EPERM;
898+ }
899+#else
900+ fileflags = 0; /* avoid compiler warning */
901+#endif
902 if (code != 0 && (preserve_perms || preserve_executability))
c0c7984e 903 return code;
f9df736a
WD
904 return 0;
905 }
906 #endif
907
908+#ifdef HAVE_CHFLAGS
909+int do_chflags(const char *path, uint32 fileflags)
31611b92
WD
910+{
911+ if (dry_run) return 0;
912+ RETURN_ERROR_IF_RO_OR_LO;
f9df736a 913+ return chflags(path, fileflags);
31611b92
WD
914+}
915+#endif
916+
917 int do_rename(const char *fname1, const char *fname2)
918 {
919 if (dry_run) return 0;
f9df736a
WD
920 RETURN_ERROR_IF_RO_OR_LO;
921- return rename(fname1, fname2);
922+ if (rename(fname1, fname2) == 0)
923+ return 0;
924+#ifdef SUPPORT_FORCE_CHANGE
925+ if (force_change && errno == EPERM) {
926+ STRUCT_STAT st1, st2;
927+ int became_mutable;
928+
929+ if (x_lstat(fname1, &st1, NULL) != 0)
930+ goto failed;
931+ became_mutable = make_mutable(fname1, st1.st_mode, st1.st_flags, force_change) > 0;
932+ if (became_mutable && rename(fname1, fname2) == 0)
933+ goto success;
934+ if (x_lstat(fname2, &st2, NULL) == 0
935+ && make_mutable(fname2, st2.st_mode, st2.st_flags, force_change) > 0) {
936+ if (rename(fname1, fname2) == 0) {
937+ success:
938+ if (became_mutable) /* Yes, use fname2 and st1! */
939+ undo_make_mutable(fname2, st1.st_flags);
940+ return 0;
941+ }
942+ undo_make_mutable(fname2, st2.st_flags);
943+ }
944+ /* TODO: handle immutable directories */
945+ if (became_mutable)
946+ undo_make_mutable(fname1, st1.st_flags);
947+ failed:
948+ errno = EPERM;
949+ }
950+#endif
951+ return -1;
952 }
953
954 void trim_trailing_slashes(char *name)
955diff --git a/t_stub.c b/t_stub.c
956--- a/t_stub.c
957+++ b/t_stub.c
fc557362
WD
958@@ -25,6 +25,7 @@ int modify_window = 0;
959 int module_id = -1;
f9df736a 960 int relative_paths = 0;
f9df736a
WD
961 int module_dirlen = 0;
962+int force_change = 0;
91270139 963 int preserve_xattrs = 0;
f9df736a 964 mode_t orig_umask = 002;
fc557362 965 char number_separator = ',';
7170ca8d 966@@ -84,3 +85,23 @@ filter_rule_list daemon_filter_list;
f9df736a
WD
967 {
968 return "tester";
969 }
970+
971+#if defined SUPPORT_FILEFLAGS || defined SUPPORT_FORCE_CHANGE
972+ int make_mutable(UNUSED(const char *fname), UNUSED(mode_t mode), UNUSED(uint32 fileflags), UNUSED(uint32 iflags))
973+{
974+ return 0;
975+}
976+
977+/* Undo a prior make_mutable() call that returned a 1. */
978+ int undo_make_mutable(UNUSED(const char *fname), UNUSED(uint32 fileflags))
979+{
980+ return 0;
981+}
982+#endif
983+
984+#ifdef SUPPORT_XATTRS
985+ int x_lstat(UNUSED(const char *fname), UNUSED(STRUCT_STAT *fst), UNUSED(STRUCT_STAT *xst))
986+{
987+ return -1;
988+}
989+#endif
990diff --git a/util.c b/util.c
991--- a/util.c
992+++ b/util.c
fc557362 993@@ -30,6 +30,7 @@ extern int module_id;
f9df736a
WD
994 extern int modify_window;
995 extern int relative_paths;
91270139 996 extern int preserve_xattrs;
fc557362 997+extern int force_change;
f9df736a
WD
998 extern char *module_dir;
999 extern unsigned int module_dirlen;
fc557362 1000 extern mode_t orig_umask;
91270139 1001@@ -123,7 +124,7 @@ NORETURN void overflow_exit(const char *str)
f9df736a
WD
1002 exit_cleanup(RERR_MALLOC);
1003 }
1004
1005-int set_modtime(const char *fname, time_t modtime, mode_t mode)
1006+int set_modtime(const char *fname, time_t modtime, mode_t mode, uint32 fileflags)
1007 {
1008 #if !defined HAVE_LUTIMES || !defined HAVE_UTIMES
1009 if (S_ISLNK(mode))
91270139 1010@@ -140,6 +141,7 @@ int set_modtime(const char *fname, time_t modtime, mode_t mode)
f9df736a
WD
1011 return 0;
1012
1013 {
1014+ int ret;
1015 #ifdef HAVE_UTIMES
1016 struct timeval t[2];
1017 t[0].tv_sec = time(NULL);
91270139 1018@@ -153,20 +155,39 @@ int set_modtime(const char *fname, time_t modtime, mode_t mode)
f9df736a
WD
1019 return 0;
1020 }
1021 # endif
1022- return utimes(fname, t);
1023+#define SET_THE_TIME(fn) utimes(fn, t)
1024 #elif defined HAVE_STRUCT_UTIMBUF
1025 struct utimbuf tbuf;
1026 tbuf.actime = time(NULL);
1027 tbuf.modtime = modtime;
1028- return utime(fname,&tbuf);
1029+#define SET_THE_TIME(fn) utime(fn, &tbuf)
1030 #elif defined HAVE_UTIME
1031 time_t t[2];
1032 t[0] = time(NULL);
1033 t[1] = modtime;
1034- return utime(fname,t);
1035+#define SET_THE_TIME(fn) utime(fn, t)
1036 #else
1037 #error No file-time-modification routine found!
1038 #endif
1039+ ret = SET_THE_TIME(fname);
1040+#ifdef SUPPORT_FORCE_CHANGE
1041+ if (ret != 0 && force_change && errno == EPERM) {
1042+ if (fileflags == NO_FFLAGS) {
1043+ STRUCT_STAT st;
1044+ if (x_lstat(fname, &st, NULL) == 0)
1045+ fileflags = st.st_flags;
1046+ }
1047+ if (fileflags != NO_FFLAGS
1048+ && make_mutable(fname, mode, fileflags, force_change) > 0) {
1049+ ret = SET_THE_TIME(fname);
1050+ undo_make_mutable(fname, fileflags);
1051+ }
1052+ errno = EPERM;
1053+ }
1054+#else
1055+ fileflags = 0; /* avoid compiler warning */
1056+#endif
1057+ return ret;
1058 }
1059 }
1060
1061diff --git a/xattrs.c b/xattrs.c
1062--- a/xattrs.c
1063+++ b/xattrs.c
7170ca8d 1064@@ -284,6 +284,10 @@ int get_xattr(const char *fname, stat_x *sxp)
7f0bf1cb
WD
1065 {
1066 sxp->xattr = new(item_list);
1067 *sxp->xattr = empty_xattr;
1068+
1069+ if (IS_SPECIAL(sxp->st.st_mode) || IS_DEVICE(sxp->st.st_mode))
1070+ return 0;
1071+
1072 if (rsync_xal_get(fname, sxp->xattr) < 0) {
1073 free_xattr(sxp);
1074 return -1;
7170ca8d 1075@@ -884,6 +888,11 @@ int set_xattr(const char *fname, const struct file_struct *file,
7f0bf1cb
WD
1076 return -1;
1077 }
1078
1079+ if (IS_SPECIAL(sxp->st.st_mode) || IS_DEVICE(sxp->st.st_mode)) {
1080+ errno = ENOTSUP;
1081+ return -1;
1082+ }
1083+
1084 ndx = F_XATTR(file);
1085 return rsync_xal_set(fname, lst + ndx, fnamecmp, sxp);
1086 }
7170ca8d 1087@@ -1000,7 +1009,7 @@ int set_stat_xattr(const char *fname, struct file_struct *file, mode_t new_mode)
f9df736a
WD
1088 mode = (fst.st_mode & _S_IFMT) | (fmode & ACCESSPERMS)
1089 | (S_ISDIR(fst.st_mode) ? 0700 : 0600);
1090 if (fst.st_mode != mode)
1091- do_chmod(fname, mode);
1092+ do_chmod(fname, mode, ST_FLAGS(fst));
7170ca8d 1093 if (!IS_DEVICE(fst.st_mode))
f9df736a
WD
1094 fst.st_rdev = 0; /* just in case */
1095