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