The patches for 3.0.0pre6.
[rsync/rsync-patches.git] / flags.diff
... / ...
CommitLineData
1This patch provides --flags, which preserves the st_flags 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/flags.diff
7 ./prepare-source
8 ./configure
9 make
10
11diff --git a/compat.c b/compat.c
12--- a/compat.c
13+++ b/compat.c
14@@ -44,6 +44,7 @@ extern int protocol_version;
15 extern int protect_args;
16 extern int preserve_uid;
17 extern int preserve_gid;
18+extern int preserve_fileflags;
19 extern int preserve_acls;
20 extern int preserve_xattrs;
21 extern int need_messages_from_generator;
22@@ -60,7 +61,7 @@ extern iconv_t ic_send, ic_recv;
23 #endif
24
25 /* These index values are for the file-list's extra-attribute array. */
26-int uid_ndx, gid_ndx, acls_ndx, xattrs_ndx, unsort_ndx;
27+int uid_ndx, gid_ndx, fileflags_ndx, acls_ndx, xattrs_ndx, unsort_ndx;
28
29 #ifdef ICONV_OPTION
30 int filesfrom_convert = 0;
31@@ -124,6 +125,8 @@ void setup_protocol(int f_out,int f_in)
32 uid_ndx = ++file_extra_cnt;
33 if (preserve_gid)
34 gid_ndx = ++file_extra_cnt;
35+ if (preserve_fileflags)
36+ fileflags_ndx = ++file_extra_cnt;
37 if (preserve_acls && !am_sender)
38 acls_ndx = ++file_extra_cnt;
39 if (preserve_xattrs)
40diff --git a/configure.in b/configure.in
41--- a/configure.in
42+++ b/configure.in
43@@ -551,7 +551,7 @@ AC_CHECK_FUNCS(waitpid wait4 getcwd strdup chown chmod lchmod mknod mkfifo \
44 memmove lchown vsnprintf snprintf vasprintf asprintf setsid glob strpbrk \
45 strlcat strlcpy strtol mallinfo getgroups setgroups geteuid getegid \
46 setlocale setmode open64 lseek64 mkstemp64 mtrace va_copy __va_copy \
47- strerror putenv iconv_open locale_charset nl_langinfo getxattr \
48+ chflags strerror putenv iconv_open locale_charset nl_langinfo getxattr \
49 extattr_get_link sigaction sigprocmask setattrlist)
50
51 AC_CHECK_FUNCS(getpgrp tcgetpgrp)
52diff --git a/flist.c b/flist.c
53--- a/flist.c
54+++ b/flist.c
55@@ -50,6 +50,7 @@ extern int preserve_links;
56 extern int preserve_hard_links;
57 extern int preserve_devices;
58 extern int preserve_specials;
59+extern int fileflags_ndx;
60 extern int uid_ndx;
61 extern int gid_ndx;
62 extern int eol_nulls;
63@@ -345,6 +346,9 @@ static void send_file_entry(int f, struct file_struct *file, int ndx, int first_
64 {
65 static time_t modtime;
66 static mode_t mode;
67+#ifdef SUPPORT_FLAGS
68+ static uint32 fileflags;
69+#endif
70 #ifdef SUPPORT_HARD_LINKS
71 static int64 dev;
72 #endif
73@@ -403,6 +407,12 @@ static void send_file_entry(int f, struct file_struct *file, int ndx, int first_
74 xflags |= XMIT_SAME_MODE;
75 else
76 mode = file->mode;
77+#ifdef SUPPORT_FLAGS
78+ if (F_FFLAGS(file) == fileflags)
79+ xflags |= XMIT_SAME_FLAGS;
80+ else
81+ fileflags = F_FFLAGS(file);
82+#endif
83
84 if ((preserve_devices && IS_DEVICE(mode))
85 || (preserve_specials && IS_SPECIAL(mode))) {
86@@ -523,6 +533,10 @@ static void send_file_entry(int f, struct file_struct *file, int ndx, int first_
87 }
88 if (!(xflags & XMIT_SAME_MODE))
89 write_int(f, to_wire_mode(mode));
90+#ifdef SUPPORT_FLAGS
91+ if (fileflags_ndx && !(xflags & XMIT_SAME_FLAGS))
92+ write_int(f, (int)fileflags);
93+#endif
94 if (uid_ndx && !(xflags & XMIT_SAME_UID)) {
95 if (protocol_version < 30)
96 write_int(f, uid);
97@@ -611,6 +625,9 @@ static struct file_struct *recv_file_entry(struct file_list *flist,
98 {
99 static int64 modtime;
100 static mode_t mode;
101+#ifdef SUPPORT_FLAGS
102+ static uint32 fileflags;
103+#endif
104 #ifdef SUPPORT_HARD_LINKS
105 static int64 dev;
106 #endif
107@@ -742,9 +759,12 @@ static struct file_struct *recv_file_entry(struct file_list *flist,
108 }
109 if (!(xflags & XMIT_SAME_MODE))
110 mode = from_wire_mode(read_int(f));
111-
112 if (chmod_modes && !S_ISLNK(mode))
113 mode = tweak_mode(mode, chmod_modes);
114+#ifdef SUPPORT_FLAGS
115+ if (fileflags_ndx && !(xflags & XMIT_SAME_FLAGS))
116+ fileflags = (uint32)read_int(f);
117+#endif
118
119 if (uid_ndx && !(xflags & XMIT_SAME_UID)) {
120 if (protocol_version < 30)
121@@ -866,6 +886,10 @@ static struct file_struct *recv_file_entry(struct file_list *flist,
122 OPT_EXTRA(file, 0)->unum = (uint32)(file_length >> 32);
123 }
124 file->mode = mode;
125+#ifdef SUPPORT_FLAGS
126+ if (fileflags_ndx)
127+ F_FFLAGS(file) = fileflags;
128+#endif
129 if (uid_ndx)
130 F_OWNER(file) = uid;
131 if (gid_ndx) {
132@@ -1196,6 +1220,10 @@ struct file_struct *make_file(const char *fname, struct file_list *flist,
133 OPT_EXTRA(file, 0)->unum = (uint32)(st.st_size >> 32);
134 }
135 file->mode = st.st_mode;
136+#ifdef SUPPORT_FLAGS
137+ if (fileflags_ndx)
138+ F_FFLAGS(file) = st.st_flags;
139+#endif
140 if (uid_ndx)
141 F_OWNER(file) = st.st_uid;
142 if (gid_ndx)
143diff --git a/generator.c b/generator.c
144--- a/generator.c
145+++ b/generator.c
146@@ -112,6 +112,14 @@ static int need_retouch_dir_times;
147 static int need_retouch_dir_perms;
148 static const char *solo_file = NULL;
149
150+#ifdef SUPPORT_FLAGS
151+#define FF_PTR(p) F_FFLAGS(p)
152+#define FF_STAT(s) s.st_flags
153+#else
154+#define FF_PTR(p) 0
155+#define FF_STAT(s) 0
156+#endif
157+
158 /* For calling delete_item() and delete_dir_contents(). */
159 #define DEL_RECURSE (1<<1) /* recurse */
160 #define DEL_DIR_IS_EMPTY (1<<2) /* internal delete_FUNCTIONS use only */
161@@ -127,7 +135,6 @@ enum delret {
162 /* Forward declaration for delete_item(). */
163 static enum delret delete_dir_contents(char *fname, int flags);
164
165-
166 static int is_backup_file(char *fn)
167 {
168 int k = strlen(fn) - backup_suffix_len;
169@@ -140,17 +147,20 @@ static int is_backup_file(char *fn)
170 * Note that fbuf must point to a MAXPATHLEN buffer if the mode indicates it's
171 * a directory! (The buffer is used for recursion, but returned unchanged.)
172 */
173-static enum delret delete_item(char *fbuf, int mode, char *replace, int flags)
174+static enum delret delete_item(char *fbuf, int mode, uint32 fileflags, char *replace, int flags)
175 {
176 enum delret ret;
177 char *what;
178 int ok;
179
180 if (verbose > 2) {
181- rprintf(FINFO, "delete_item(%s) mode=%o flags=%d\n",
182- fbuf, mode, flags);
183+ rprintf(FINFO, "delete_item(%s) mode=%o fileflags=%o flags=%d\n",
184+ fbuf, mode, fileflags, flags);
185 }
186
187+#ifdef SUPPORT_FLAGS
188+ make_mutable(fbuf, mode, fileflags);
189+#endif
190 if (S_ISDIR(mode) && !(flags & DEL_DIR_IS_EMPTY)) {
191 ignore_perishable = 1;
192 /* If DEL_RECURSE is not set, this just reports emptiness. */
193@@ -262,7 +272,7 @@ static enum delret delete_dir_contents(char *fname, int flags)
194 if (S_ISDIR(fp->mode)
195 && delete_dir_contents(fname, flags | DEL_RECURSE) != DR_SUCCESS)
196 ret = DR_NOT_EMPTY;
197- if (delete_item(fname, fp->mode, NULL, flags) != DR_SUCCESS)
198+ if (delete_item(fname, fp->mode, FF_PTR(fp), NULL, flags) != DR_SUCCESS)
199 ret = DR_NOT_EMPTY;
200 }
201
202@@ -318,8 +328,9 @@ static int remember_delete(struct file_struct *file, const char *fname)
203
204 while (1) {
205 len = snprintf(deldelay_buf + deldelay_cnt,
206- deldelay_size - deldelay_cnt,
207- "%x %s%c", (int)file->mode, fname, '\0');
208+ deldelay_size - deldelay_cnt, "%x %x %s%c",
209+ (int)file->mode, (int)FF_PTR(file),
210+ fname, '\0');
211 if ((deldelay_cnt += len) <= deldelay_size)
212 break;
213 if (deldelay_fd < 0 && !start_delete_delay_temp())
214@@ -332,7 +343,7 @@ static int remember_delete(struct file_struct *file, const char *fname)
215 return 1;
216 }
217
218-static int read_delay_line(char *buf)
219+static int read_delay_line(char *buf, int *fileflags_p)
220 {
221 static int read_pos = 0;
222 int j, len, mode;
223@@ -374,12 +385,12 @@ static int read_delay_line(char *buf)
224
225 bp = deldelay_buf + read_pos;
226
227- if (sscanf(bp, "%x ", &mode) != 1) {
228+ if (sscanf(bp, "%x %x ", &mode, fileflags_p) != 2) {
229 invalid_data:
230 rprintf(FERROR, "ERROR: invalid data in delete-delay file.\n");
231 return -1;
232 }
233- past_space = strchr(bp, ' ') + 1;
234+ past_space = strchr(strchr(bp, ' ') + 1, ' ') + 1;
235 len = j - read_pos - (past_space - bp) + 1; /* count the '\0' */
236 read_pos = j + 1;
237
238@@ -397,15 +408,15 @@ static int read_delay_line(char *buf)
239
240 static void do_delayed_deletions(char *delbuf)
241 {
242- int mode;
243+ int mode, fileflags;
244
245 if (deldelay_fd >= 0) {
246 if (deldelay_cnt && !flush_delete_delay())
247 return;
248 lseek(deldelay_fd, 0, 0);
249 }
250- while ((mode = read_delay_line(delbuf)) >= 0)
251- delete_item(delbuf, mode, NULL, DEL_RECURSE);
252+ while ((mode = read_delay_line(delbuf, &fileflags)) >= 0)
253+ delete_item(delbuf, mode, fileflags, NULL, DEL_RECURSE);
254 if (deldelay_fd >= 0)
255 close(deldelay_fd);
256 }
257@@ -472,7 +483,7 @@ static void delete_in_dir(char *fbuf, struct file_struct *file, dev_t *fs_dev)
258 if (!remember_delete(fp, delbuf))
259 break;
260 } else
261- delete_item(delbuf, fp->mode, NULL, DEL_RECURSE);
262+ delete_item(delbuf, fp->mode, FF_PTR(fp), NULL, DEL_RECURSE);
263 }
264 }
265
266@@ -1296,7 +1307,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
267 * full later (right before we handle its contents). */
268 if (statret == 0
269 && (S_ISDIR(sx.st.st_mode)
270- || delete_item(fname, sx.st.st_mode, "directory", del_opts) != 0))
271+ || delete_item(fname, sx.st.st_mode, FF_STAT(sx.st), "directory", del_opts) != 0))
272 goto cleanup; /* Any errors get reported later. */
273 if (do_mkdir(fname, file->mode & 0700) == 0)
274 file->flags |= FLAG_DIR_CREATED;
275@@ -1308,7 +1319,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
276 * we need to delete it. If it doesn't exist, then
277 * (perhaps recursively) create it. */
278 if (statret == 0 && !S_ISDIR(sx.st.st_mode)) {
279- if (delete_item(fname, sx.st.st_mode, "directory", del_opts) != 0)
280+ if (delete_item(fname, sx.st.st_mode, FF_STAT(sx.st), "directory", del_opts) != 0)
281 goto skipping_dir_contents;
282 statret = -1;
283 }
284@@ -1437,7 +1448,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
285 }
286 /* Not the right symlink (or not a symlink), so
287 * delete it. */
288- if (delete_item(fname, sx.st.st_mode, "symlink", del_opts) != 0)
289+ if (delete_item(fname, sx.st.st_mode, FF_STAT(sx.st), "symlink", del_opts) != 0)
290 goto cleanup;
291 } else if (basis_dir[0] != NULL) {
292 int j = try_dests_non(file, fname, ndx, fnamecmpbuf, &sx,
293@@ -1516,7 +1527,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
294 goto return_with_success;
295 goto cleanup;
296 }
297- if (delete_item(fname, sx.st.st_mode, t, del_opts) != 0)
298+ if (delete_item(fname, sx.st.st_mode, FF_STAT(sx.st), t, del_opts) != 0)
299 goto cleanup;
300 } else if (basis_dir[0] != NULL) {
301 int j = try_dests_non(file, fname, ndx, fnamecmpbuf, &sx,
302@@ -1607,7 +1618,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
303 fnamecmp_type = FNAMECMP_FNAME;
304
305 if (statret == 0 && !S_ISREG(sx.st.st_mode)) {
306- if (delete_item(fname, sx.st.st_mode, "regular file", del_opts) != 0)
307+ if (delete_item(fname, sx.st.st_mode, FF_STAT(sx.st), "regular file", del_opts) != 0)
308 goto cleanup;
309 statret = -1;
310 stat_errno = ENOENT;
311diff --git a/options.c b/options.c
312--- a/options.c
313+++ b/options.c
314@@ -51,6 +51,7 @@ int preserve_hard_links = 0;
315 int preserve_acls = 0;
316 int preserve_xattrs = 0;
317 int preserve_perms = 0;
318+int preserve_fileflags = 0;
319 int preserve_executability = 0;
320 int preserve_devices = 0;
321 int preserve_specials = 0;
322@@ -222,6 +223,7 @@ static void print_rsync_version(enum logcode f)
323 char const *links = "no ";
324 char const *iconv = "no ";
325 char const *ipv6 = "no ";
326+ char const *fileflags = "no ";
327 STRUCT_STAT *dumstat;
328
329 #if SUBPROTOCOL_VERSION != 0
330@@ -251,6 +253,9 @@ static void print_rsync_version(enum logcode f)
331 #ifdef ICONV_OPTION
332 iconv = "";
333 #endif
334+#ifdef SUPPORT_FLAGS
335+ fileflags = "";
336+#endif
337
338 rprintf(f, "%s version %s protocol version %d%s\n",
339 RSYNC_NAME, RSYNC_VERSION, PROTOCOL_VERSION, subprotocol);
340@@ -264,8 +269,8 @@ static void print_rsync_version(enum logcode f)
341 (int)(sizeof (int64) * 8));
342 rprintf(f, " %ssocketpairs, %shardlinks, %ssymlinks, %sIPv6, batchfiles, %sinplace,\n",
343 got_socketpair, hardlinks, links, ipv6, have_inplace);
344- rprintf(f, " %sappend, %sACLs, %sxattrs, %siconv\n",
345- have_inplace, acls, xattrs, iconv);
346+ rprintf(f, " %sappend, %sACLs, %sxattrs, %siconv, %sfile-flags\n",
347+ have_inplace, acls, xattrs, iconv, fileflags);
348
349 #ifdef MAINTAINER_MODE
350 rprintf(f, "Panic Action: \"%s\"\n", get_panic_action());
351@@ -332,6 +337,7 @@ void usage(enum logcode F)
352 rprintf(F," -K, --keep-dirlinks treat symlinked dir on receiver as dir\n");
353 rprintf(F," -H, --hard-links preserve hard links\n");
354 rprintf(F," -p, --perms preserve permissions\n");
355+ rprintf(F," --fileflags preserve file-flags\n");
356 rprintf(F," -E, --executability preserve the file's executability\n");
357 rprintf(F," --chmod=CHMOD affect file and/or directory permissions\n");
358 #ifdef SUPPORT_ACLS
359@@ -472,6 +478,8 @@ static struct poptOption long_options[] = {
360 {"perms", 'p', POPT_ARG_VAL, &preserve_perms, 1, 0, 0 },
361 {"no-perms", 0, POPT_ARG_VAL, &preserve_perms, 0, 0, 0 },
362 {"no-p", 0, POPT_ARG_VAL, &preserve_perms, 0, 0, 0 },
363+ {"fileflags", 0, POPT_ARG_VAL, &preserve_fileflags, 1, 0, 0 },
364+ {"no-fileflags", 0, POPT_ARG_VAL, &preserve_fileflags, 0, 0, 0 },
365 {"executability", 'E', POPT_ARG_NONE, &preserve_executability, 0, 0, 0 },
366 {"acls", 'A', POPT_ARG_NONE, 0, 'A', 0, 0 },
367 {"no-acls", 0, POPT_ARG_VAL, &preserve_acls, 0, 0, 0 },
368@@ -1287,6 +1295,15 @@ int parse_arguments(int *argc_p, const char ***argv_p, int frommain)
369 }
370 #endif
371
372+#ifndef SUPPORT_FLAGS
373+ if (preserve_fileflags) {
374+ snprintf(err_buf, sizeof err_buf,
375+ "file flags are not supported on this %s\n",
376+ am_server ? "server" : "client");
377+ return 0;
378+ }
379+#endif
380+
381 if (write_batch && read_batch) {
382 snprintf(err_buf, sizeof err_buf,
383 "--write-batch and --read-batch can not be used together\n");
384@@ -1795,6 +1812,9 @@ void server_options(char **args, int *argc_p)
385 if (xfer_dirs && !recurse && delete_mode && am_sender)
386 args[ac++] = "--no-r";
387
388+ if (preserve_fileflags)
389+ args[ac++] = "--flags";
390+
391 if (do_compression && def_compress_level != Z_DEFAULT_COMPRESSION) {
392 if (asprintf(&arg, "--compress-level=%d", def_compress_level) < 0)
393 goto oom;
394diff --git a/rsync.c b/rsync.c
395--- a/rsync.c
396+++ b/rsync.c
397@@ -32,6 +32,7 @@ extern int dry_run;
398 extern int preserve_acls;
399 extern int preserve_xattrs;
400 extern int preserve_perms;
401+extern int preserve_fileflags;
402 extern int preserve_executability;
403 extern int preserve_times;
404 extern int am_root;
405@@ -60,6 +61,16 @@ iconv_t ic_chck = (iconv_t)-1;
406 iconv_t ic_send = (iconv_t)-1, ic_recv = (iconv_t)-1;
407 # endif
408
409+#ifdef SUPPORT_FLAGS
410+#ifndef UF_NOUNLINK
411+#define UF_NOUNLINK 0
412+#endif
413+#ifndef SF_NOUNLINK
414+#define SF_NOUNLINK 0
415+#endif
416+#define NOCHANGE_FLAGS (UF_IMMUTABLE|UF_APPEND|UF_NOUNLINK|SF_IMMUTABLE|SF_APPEND|SF_NOUNLINK)
417+#endif
418+
419 static const char *default_charset(void)
420 {
421 # if defined HAVE_LIBCHARSET_H && defined HAVE_LOCALE_CHARSET
422@@ -337,6 +348,41 @@ mode_t dest_mode(mode_t flist_mode, mode_t stat_mode, int dflt_perms,
423 return new_mode;
424 }
425
426+#ifdef SUPPORT_FLAGS
427+/* Set a file's st_flags. */
428+static int set_fileflags(const char *fname, uint32 fileflags)
429+{
430+ if (do_chflags(fname, fileflags) != 0) {
431+ rsyserr(FERROR_XFER, errno,
432+ "failed to set file flags on %s",
433+ full_fname(fname));
434+ return 0;
435+ }
436+
437+ return 1;
438+}
439+
440+/* Remove immutable flags from an object, so it can be altered/removed. */
441+void make_mutable(const char *fname, mode_t mode, uint32 fileflags)
442+{
443+ if (!preserve_fileflags && S_ISLNK(mode))
444+ return;
445+
446+ if (fileflags & NOCHANGE_FLAGS)
447+ set_fileflags(fname, fileflags & ~NOCHANGE_FLAGS);
448+}
449+
450+/* Undo a prior make_mutable() call. */
451+void undo_make_mutable(const char *fname, mode_t mode, uint32 fileflags)
452+{
453+ if (!preserve_fileflags && S_ISLNK(mode))
454+ return;
455+
456+ if (fileflags & NOCHANGE_FLAGS)
457+ set_fileflags(fname, fileflags);
458+}
459+#endif
460+
461 int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
462 const char *fnamecmp, int flags)
463 {
464@@ -468,6 +514,15 @@ int set_file_attrs(const char *fname, struct file_struct *file, stat_x *sxp,
465 }
466 #endif
467
468+#ifdef SUPPORT_FLAGS
469+ if (preserve_fileflags && !S_ISLNK(sxp->st.st_mode)
470+ && sxp->st.st_flags != F_FFLAGS(file)) {
471+ if (!set_fileflags(fname, F_FFLAGS(file)))
472+ return 0;
473+ updated = 1;
474+ }
475+#endif
476+
477 if (verbose > 1 && flags & ATTRS_REPORT) {
478 if (updated)
479 rprintf(FCLIENT, "%s\n", fname);
480@@ -527,6 +582,9 @@ void finish_transfer(const char *fname, const char *fnametmp,
481 set_file_attrs(fnametmp, file, NULL, fnamecmp,
482 ok_to_set_time ? 0 : ATTRS_SKIP_MTIME);
483
484+#ifdef SUPPORT_FLAGS
485+ make_mutable(fnametmp, file->mode, F_FFLAGS(file));
486+#endif
487 /* move tmp file over real file */
488 if (verbose > 2)
489 rprintf(FINFO, "renaming %s to %s\n", fnametmp, fname);
490@@ -541,6 +599,9 @@ void finish_transfer(const char *fname, const char *fnametmp,
491 }
492 if (ret == 0) {
493 /* The file was moved into place (not copied), so it's done. */
494+#ifdef SUPPORT_FLAGS
495+ undo_make_mutable(fname, file->mode, F_FFLAGS(file));
496+#endif
497 return;
498 }
499 /* The file was copied, so tweak the perms of the copied file. If it
500diff --git a/rsync.h b/rsync.h
501--- a/rsync.h
502+++ b/rsync.h
503@@ -60,6 +60,7 @@
504 #define XMIT_RDEV_MINOR_8_pre30 (1<<11) /* protocols 28 - 29 */
505 #define XMIT_GROUP_NAME_FOLLOWS (1<<11) /* protocols 30 - now */
506 #define XMIT_HLINK_FIRST (1<<12) /* protocols 30 - now (HLINKED files only) */
507+#define XMIT_SAME_FLAGS (1<<14) /* protocols ?? - now */
508
509 /* These flags are used in the live flist data. */
510
511@@ -451,6 +452,10 @@ typedef unsigned int size_t;
512 #endif
513 #endif
514
515+#ifdef HAVE_CHFLAGS
516+#define SUPPORT_FLAGS 1
517+#endif
518+
519 /* Find a variable that is either exactly 32-bits or longer.
520 * If some code depends on 32-bit truncation, it will need to
521 * take special action in a "#if SIZEOF_INT32 > 4" section. */
522@@ -618,6 +623,7 @@ extern int file_extra_cnt;
523 extern int inc_recurse;
524 extern int uid_ndx;
525 extern int gid_ndx;
526+extern int fileflags_ndx;
527 extern int acls_ndx;
528 extern int xattrs_ndx;
529
530@@ -655,6 +661,7 @@ extern int xattrs_ndx;
531 /* When the associated option is on, all entries will have these present: */
532 #define F_OWNER(f) REQ_EXTRA(f, uid_ndx)->unum
533 #define F_GROUP(f) REQ_EXTRA(f, gid_ndx)->unum
534+#define F_FFLAGS(f) REQ_EXTRA(f, fileflags_ndx)->unum
535 #define F_ACL(f) REQ_EXTRA(f, acls_ndx)->num
536 #define F_XATTR(f) REQ_EXTRA(f, xattrs_ndx)->num
537 #define F_NDX(f) REQ_EXTRA(f, unsort_ndx)->num
538diff --git a/rsync.yo b/rsync.yo
539--- a/rsync.yo
540+++ b/rsync.yo
541@@ -338,6 +338,7 @@ to the detailed description below for a complete description. verb(
542 -K, --keep-dirlinks treat symlinked dir on receiver as dir
543 -H, --hard-links preserve hard links
544 -p, --perms preserve permissions
545+ --flags preserve file flags
546 -E, --executability preserve executability
547 --chmod=CHMOD affect file and/or directory permissions
548 -A, --acls preserve ACLs (implies -p)
549@@ -540,7 +541,9 @@ specified, in which case bf(-r) is not implied.
550
551 Note that bf(-a) bf(does not preserve hardlinks), because
552 finding multiply-linked files is expensive. You must separately
553-specify bf(-H).
554+specify bf(-H). Note also that for compatibility, bf(-a)
555+currently bf(does not include --flags) (see there) to include preserving
556+change file flags (if supported by the OS).
557
558 dit(--no-OPTION) You may turn off one or more implied options by prefixing
559 the option name with "no-". Not all options may be prefixed with a "no-":
560@@ -909,6 +912,13 @@ super-user copies all namespaces except system.*. A normal user only copies
561 the user.* namespace. To be able to backup and restore non-user namespaces as
562 a normal user, see the bf(--fake-super) option.
563
564+dit(bf(--flags)) This option causes rsync to update the change file flags
565+to be the same as the source file, if your OS supports the bf(chflags)(2)
566+system call. In any case, an attempt is made to remove flags that would
567+prevent a file to be altered. Some flags can only be altered by the
568+super-user and can only be unset below a certain secure-level (usually
569+single-user mode).
570+
571 dit(bf(--chmod)) This option tells rsync to apply one or more
572 comma-separated "chmod" strings to the permission of the files in the
573 transfer. The resulting value is treated as though it was the permissions
574diff --git a/syscall.c b/syscall.c
575--- a/syscall.c
576+++ b/syscall.c
577@@ -173,6 +173,15 @@ int do_chmod(const char *path, mode_t mode)
578 }
579 #endif
580
581+#ifdef SUPPORT_FLAGS
582+int do_chflags(const char *path, u_long flags)
583+{
584+ if (dry_run) return 0;
585+ RETURN_ERROR_IF_RO_OR_LO;
586+ return chflags(path, flags);
587+}
588+#endif
589+
590 int do_rename(const char *fname1, const char *fname2)
591 {
592 if (dry_run) return 0;