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