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