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