Skip to content

Commit 679ad50

Browse files
tridgeclaude
andcommitted
fileflags: portable BSD chflags + Linux chattr implementation
Squash of pr-fileflags work, rebased onto 3.4.3. Original branch had 12 commits walking through: 1. apply rsync-patches/fileflags.diff (BSD-only --fileflags option) 2. testsuite/fileflags.test 3. CI fixes 4. portable stat -f %f readback 5. phase-1 security: fd-based chflags, sender filter, daemon refuse 6. phase-2 security: dirfd-anchored force_change recovery 7. phase-3 security: secure_relative_open bound to dest subtree 8. --force-change rebalance: default to USR_IMMUTABLE only 9. set_refuse_options POPT_BIT_SET fix 10. user-visible regressions on non-chflags builds 11. t_stub fd-based stubs + weak curr_dir 12. Linux port via FS_IOC_{GET,SET}FLAGS Squashed and rebased because 3.4.3 (CVE-2026-29518 + family) overlaps heavily with the security work in phases 1-3 -- master added do_chmod_at / do_lchown_at / secure_relative_open hardening that duplicates the bits the fileflags patch needed. The fileflags work keeps the original do_chmod / do_lchown signatures (no UNUSED arg addition), so master's _at variants and call sites are unchanged. Resulting feature set: --fileflags preserve file flags (chflags on BSD, chattr {+d, +i, +a} on Linux). SAFE_FILEFLAGS mask (UF_NODUMP|UF_IMMUTABLE|UF_APPEND[|UF_HIDDEN]) applied by default; sender-supplied SF_* and UF_NOUNLINK dropped to avoid DoS where a hostile source pins permanent flags on the receiver. --unsafe-fileflags widens the mask to the full sender value. --force-change clear USR_IMMUTABLE (UF_*) on dest files being updated/deleted so the op can proceed. --force-uchange alias for --force-change. --force-schange also clear SYS_IMMUTABLE (SF_*); separate opt-in. --no-force-{u,s,}change to clear bits. Implementation: - lib/fileflags.c: portable rsync_fchflags / rsync_fgetflags / rsync_lgetflags / stat_x_get_fileflags + BSD<->Linux bit translation. Wire format is BSD bits. Linux side does read-modify-write of just LINUX_WIRE_MASK so the kernel doesn't reject the call when fs-internal bits like FS_EXTENT_FL would otherwise be cleared. - stat_x grows a (fileflags, fileflags_cached) pair so the per-file open()+ioctl cost on Linux happens at most once per stat_x life. init_stat_x() in ifuncs.h zeroes both. - syscall.c do_unlink / do_rmdir / do_chmod / do_lchown / do_rename grow dirfd-anchored force_change recovery using force_change_open_parent / force_change_open_target / fchflags / fchmod / fchown / unlinkat / renameat. The recovery opens via secure_relative_open(curr_dir, dirpart, O_RDONLY|O_DIRECTORY| O_NOFOLLOW) so RESOLVE_BENEATH (where available) bounds the operation to the destination subtree. Symlinks rejected at open. - Daemon mode refuses fileflags / unsafe-fileflags / force-change / force-uchange / force-schange / no-force-uchange / no-force-schange by default; opt-in per-module via "refuse options = !fileflags". - set_refuse_options POPT_BIT_SET / POPT_BIT_CLR fix: the original rsync check was `op->argInfo == POPT_ARG_VAL` literal, which missed POPT_BIT_SET (= POPT_ARG_VAL|POPT_ARGFLAG_OR). Refused bit-set options slipped through. Now masks via POPT_ARG_MASK. - testsuite/fileflags.test picks chflags(1) or chattr(1) depending on what's available; on Linux it parses lsattr down to the transferable letters (a, d, i, u). On Linux non-root the uchg portion self-skips (CAP_LINUX_IMMUTABLE required). - CI: fileflags removed from RSYNC_EXPECT_SKIPPED on Linux jobs; Cygwin keeps it (no chattr). - Linux ioctl path is gated behind autoconf check for FS_IOC_GETFLAGS / FS_IOC_SETFLAGS / FS_NODUMP_FL / FS_IMMUTABLE_FL / FS_APPEND_FL in <linux/fs.h>; falls through to "no fileflags support" if absent (older kernels, non-Linux non-BSD). Verified on Linux/ext4: 59 passed / 2 skipped / 0 failed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 2c7777a commit 679ad50

21 files changed

Lines changed: 1302 additions & 26 deletions

.github/workflows/cygwin-build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ jobs:
3939
- name: info
4040
run: bash -c '/usr/local/bin/rsync --version'
4141
- name: check
42-
run: bash -c 'RSYNC_EXPECT_SKIPPED=acls-default,acls,bare-do-open-symlink-race,chdir-symlink-race,chmod-symlink-race,chown,daemon-chroot-acl,devices,dir-sgid,open-noatime,protected-regular,sender-flist-symlink-leak,simd-checksum,symlink-dirlink-basis make check'
42+
run: bash -c 'RSYNC_EXPECT_SKIPPED=acls-default,acls,bare-do-open-symlink-race,chdir-symlink-race,chmod-symlink-race,chown,daemon-chroot-acl,devices,dir-sgid,fileflags,open-noatime,protected-regular,sender-flist-symlink-leak,simd-checksum,symlink-dirlink-basis make check'
4343
- name: ssl file list
4444
run: bash -c 'PATH="/usr/local/bin:$PATH" rsync-ssl --no-motd download.samba.org::rsyncftp/ || true'
4545
- name: save artifact

Makefile.in

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ GENFILES=configure.sh aclocal.m4 config.h.in rsync.1 rsync.1.html \
4040
HEADERS=byteorder.h config.h errcode.h proto.h rsync.h ifuncs.h itypes.h inums.h \
4141
lib/pool_alloc.h lib/mdigest.h lib/md-defines.h
4242
LIBOBJ=lib/wildmatch.o lib/compat.o lib/snprintf.o lib/mdfour.o lib/md5.o \
43-
lib/permstring.o lib/pool_alloc.o lib/sysacls.o lib/sysxattrs.o @LIBOBJS@
43+
lib/permstring.o lib/pool_alloc.o lib/sysacls.o lib/sysxattrs.o \
44+
lib/fileflags.o @LIBOBJS@
4445
zlib_OBJS=zlib/deflate.o zlib/inffast.o zlib/inflate.o zlib/inftrees.o \
4546
zlib/trees.o zlib/zutil.o zlib/adler32.o zlib/compress.o zlib/crc32.o
4647
OBJS1=flist.o rsync.o generator.o receiver.o cleanup.o sender.o exclude.o \
@@ -53,7 +54,7 @@ popt_OBJS= popt/popt.o popt/poptconfig.o \
5354
popt/popthelp.o popt/poptparse.o popt/poptint.o
5455
OBJS=$(OBJS1) $(OBJS2) $(OBJS3) $(DAEMON_OBJ) $(LIBOBJ) @BUILD_ZLIB@ @BUILD_POPT@
5556

56-
TLS_OBJ = tls.o syscall.o util2.o t_stub.o lib/compat.o lib/snprintf.o lib/permstring.o lib/sysxattrs.o @BUILD_POPT@
57+
TLS_OBJ = tls.o syscall.o util2.o t_stub.o lib/compat.o lib/snprintf.o lib/permstring.o lib/sysxattrs.o lib/fileflags.o @BUILD_POPT@
5758

5859
# Programs we must have to run the test cases
5960
CHECK_PROGS = rsync$(EXEEXT) tls$(EXEEXT) getgroups$(EXEEXT) getfsdev$(EXEEXT) \
@@ -171,19 +172,19 @@ getgroups$(EXEEXT): getgroups.o
171172
getfsdev$(EXEEXT): getfsdev.o
172173
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ getfsdev.o $(LIBS)
173174

174-
TRIMSLASH_OBJ = trimslash.o syscall.o util2.o t_stub.o lib/compat.o lib/snprintf.o
175+
TRIMSLASH_OBJ = trimslash.o syscall.o util2.o t_stub.o lib/compat.o lib/snprintf.o lib/fileflags.o
175176
trimslash$(EXEEXT): $(TRIMSLASH_OBJ)
176177
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(TRIMSLASH_OBJ) $(LIBS)
177178

178-
T_UNSAFE_OBJ = t_unsafe.o syscall.o util1.o util2.o t_stub.o lib/compat.o lib/snprintf.o lib/wildmatch.o
179+
T_UNSAFE_OBJ = t_unsafe.o syscall.o util1.o util2.o t_stub.o lib/compat.o lib/snprintf.o lib/wildmatch.o lib/fileflags.o
179180
t_unsafe$(EXEEXT): $(T_UNSAFE_OBJ)
180181
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(T_UNSAFE_OBJ) $(LIBS)
181182

182-
T_CHMOD_SECURE_OBJ = t_chmod_secure.o syscall.o util1.o util2.o t_stub.o lib/compat.o lib/snprintf.o lib/wildmatch.o lib/permstring.o
183+
T_CHMOD_SECURE_OBJ = t_chmod_secure.o syscall.o util1.o util2.o t_stub.o lib/compat.o lib/snprintf.o lib/wildmatch.o lib/permstring.o lib/fileflags.o
183184
t_chmod_secure$(EXEEXT): $(T_CHMOD_SECURE_OBJ)
184185
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(T_CHMOD_SECURE_OBJ) $(LIBS)
185186

186-
T_SECURE_RELPATH_OBJ = t_secure_relpath.o syscall.o util1.o util2.o t_stub.o lib/compat.o lib/snprintf.o lib/wildmatch.o lib/permstring.o
187+
T_SECURE_RELPATH_OBJ = t_secure_relpath.o syscall.o util1.o util2.o t_stub.o lib/compat.o lib/snprintf.o lib/wildmatch.o lib/permstring.o lib/fileflags.o
187188
t_secure_relpath$(EXEEXT): $(T_SECURE_RELPATH_OBJ)
188189
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(T_SECURE_RELPATH_OBJ) $(LIBS)
189190

compat.c

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,15 @@ extern int checksum_seed;
4040
extern int basis_dir_cnt;
4141
extern int prune_empty_dirs;
4242
extern int protocol_version;
43+
extern int force_change;
4344
extern int protect_args;
4445
extern int preserve_uid;
4546
extern int preserve_gid;
4647
extern int preserve_atimes;
4748
extern int preserve_crtimes;
4849
extern int preserve_acls;
4950
extern int preserve_xattrs;
51+
extern int preserve_fileflags;
5052
extern int xfer_flags_as_varint;
5153
extern int need_messages_from_generator;
5254
extern int delete_mode, delete_before, delete_during, delete_after;
@@ -87,7 +89,7 @@ struct name_num_item *xattr_sum_nni;
8789
int xattr_sum_len = 0;
8890

8991
/* These index values are for the file-list's extra-attribute array. */
90-
int pathname_ndx, depth_ndx, atimes_ndx, crtimes_ndx, uid_ndx, gid_ndx, acls_ndx, xattrs_ndx, unsort_ndx;
92+
int pathname_ndx, depth_ndx, atimes_ndx, crtimes_ndx, uid_ndx, gid_ndx, fileflags_ndx, acls_ndx, xattrs_ndx, unsort_ndx;
9193

9294
int receiver_symlink_times = 0; /* receiver can set the time on a symlink */
9395
int sender_symlink_iconv = 0; /* sender should convert symlink content */
@@ -589,6 +591,8 @@ void setup_protocol(int f_out,int f_in)
589591
uid_ndx = ++file_extra_cnt;
590592
if (preserve_gid)
591593
gid_ndx = ++file_extra_cnt;
594+
if (preserve_fileflags || (force_change && !am_sender))
595+
fileflags_ndx = ++file_extra_cnt;
592596
if (preserve_acls && !am_sender)
593597
acls_ndx = ++file_extra_cnt;
594598
if (preserve_xattrs)
@@ -752,6 +756,10 @@ void setup_protocol(int f_out,int f_in)
752756
fprintf(stderr, "Both rsync versions must be at least 3.2.0 for --crtimes.\n");
753757
exit_cleanup(RERR_PROTOCOL);
754758
}
759+
if (!xfer_flags_as_varint && preserve_fileflags) {
760+
fprintf(stderr, "Both rsync versions must be at least 3.2.0 for --fileflags.\n");
761+
exit_cleanup(RERR_PROTOCOL);
762+
}
755763
if (am_sender) {
756764
receiver_symlink_times = am_server
757765
? strchr(client_info, 'L') != NULL

configure.ac

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ AC_CHECK_HEADERS(sys/fcntl.h sys/select.h fcntl.h sys/time.h sys/unistd.h \
1313
sys/acl.h acl/libacl.h attr/xattr.h sys/xattr.h sys/extattr.h dl.h \
1414
popt.h popt/popt.h linux/falloc.h netinet/in_systm.h netgroup.h \
1515
zlib.h xxhash.h openssl/md4.h openssl/md5.h zstd.h lz4.h sys/file.h \
16-
bsd/string.h)
16+
bsd/string.h linux/fs.h)
1717
AC_CHECK_HEADERS([netinet/ip.h], [], [], [[#include <netinet/in.h>]])
1818
AC_HEADER_MAJOR_FIXED
1919

@@ -928,6 +928,35 @@ AC_PREPROC_IFELSE([AC_LANG_SOURCE([[
928928
]
929929
)
930930

931+
AC_MSG_CHECKING([for FS_IOC_GETFLAGS])
932+
AC_PREPROC_IFELSE([AC_LANG_SOURCE([[
933+
#include <sys/ioctl.h>
934+
#ifdef HAVE_LINUX_FS_H
935+
#include <linux/fs.h>
936+
#endif
937+
#ifndef FS_IOC_GETFLAGS
938+
#error FS_IOC_GETFLAGS is missing
939+
#endif
940+
#ifndef FS_IOC_SETFLAGS
941+
#error FS_IOC_SETFLAGS is missing
942+
#endif
943+
#ifndef FS_NODUMP_FL
944+
#error FS_NODUMP_FL is missing
945+
#endif
946+
#ifndef FS_IMMUTABLE_FL
947+
#error FS_IMMUTABLE_FL is missing
948+
#endif
949+
#ifndef FS_APPEND_FL
950+
#error FS_APPEND_FL is missing
951+
#endif
952+
]])], [
953+
AC_MSG_RESULT([yes])
954+
AC_DEFINE([HAVE_FS_IOC_GETFLAGS], [1], [Define if FS_IOC_GETFLAGS / FS_IOC_SETFLAGS ioctls are available (Linux chattr-style file flags).])
955+
], [
956+
AC_MSG_RESULT([no])
957+
]
958+
)
959+
931960
AC_MSG_CHECKING([for FALLOC_FL_ZERO_RANGE])
932961
AC_PREPROC_IFELSE([AC_LANG_SOURCE([[
933962
#define _GNU_SOURCE 1

delete.c

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
extern int am_root;
2626
extern int make_backups;
2727
extern int max_delete;
28+
extern int force_change;
2829
extern char *backup_dir;
2930
extern char *backup_suffix;
3031
extern int backup_suffix_len;
@@ -97,6 +98,10 @@ static enum delret delete_dir_contents(char *fname, uint16 flags)
9798
}
9899

99100
strlcpy(p, fp->basename, remainder);
101+
#ifdef SUPPORT_FORCE_CHANGE
102+
if (force_change)
103+
make_mutable(fname, fp->mode, F_FFLAGS(fp), force_change);
104+
#endif
100105
if (!(fp->mode & S_IWUSR) && !am_root && fp->flags & FLAG_OWNED_BY_US)
101106
do_chmod_at(fname, fp->mode | S_IWUSR);
102107
/* Save stack by recursing to ourself directly. */
@@ -144,6 +149,16 @@ enum delret delete_item(char *fbuf, uint16 mode, uint16 flags)
144149
if (S_ISDIR(mode) && !(flags & DEL_DIR_IS_EMPTY)) {
145150
/* This only happens on the first call to delete_item() since
146151
* delete_dir_contents() always calls us w/DEL_DIR_IS_EMPTY. */
152+
#ifdef SUPPORT_FORCE_CHANGE
153+
if (force_change) {
154+
STRUCT_STAT st;
155+
if (x_lstat(fbuf, &st, NULL) == 0) {
156+
uint32 ff = rsync_lgetflags(fbuf, st.st_mode, &st);
157+
if (ff != NO_FFLAGS)
158+
make_mutable(fbuf, st.st_mode, ff, force_change);
159+
}
160+
}
161+
#endif
147162
ignore_perishable = 1;
148163
/* If DEL_RECURSE is not set, this just reports emptiness. */
149164
ret = delete_dir_contents(fbuf, flags);

flist.c

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ extern int preserve_links;
5252
extern int preserve_hard_links;
5353
extern int preserve_devices;
5454
extern int preserve_specials;
55+
extern int preserve_fileflags;
5556
extern int delete_during;
5657
extern int missing_args;
5758
extern int eol_nulls;
@@ -388,6 +389,9 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
388389
static time_t crtime;
389390
#endif
390391
static mode_t mode;
392+
#ifdef SUPPORT_FILEFLAGS
393+
static uint32 fileflags;
394+
#endif
391395
#ifdef SUPPORT_HARD_LINKS
392396
static int64 dev;
393397
#endif
@@ -431,6 +435,14 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
431435
xflags |= XMIT_SAME_MODE;
432436
else
433437
mode = file->mode;
438+
#ifdef SUPPORT_FILEFLAGS
439+
if (preserve_fileflags) {
440+
if (F_FFLAGS(file) == fileflags)
441+
xflags |= XMIT_SAME_FLAGS;
442+
else
443+
fileflags = F_FFLAGS(file);
444+
}
445+
#endif
434446

435447
if (preserve_devices && IS_DEVICE(mode)) {
436448
if (protocol_version < 28) {
@@ -592,6 +604,10 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
592604
#endif
593605
if (!(xflags & XMIT_SAME_MODE))
594606
write_int(f, to_wire_mode(mode));
607+
#ifdef SUPPORT_FILEFLAGS
608+
if (preserve_fileflags && !(xflags & XMIT_SAME_FLAGS))
609+
write_int(f, (int)fileflags);
610+
#endif
595611
if (atimes_ndx && !S_ISDIR(mode) && !(xflags & XMIT_SAME_ATIME))
596612
write_varlong(f, atime, 4);
597613
if (preserve_uid && !(xflags & XMIT_SAME_UID)) {
@@ -686,6 +702,9 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x
686702
static time_t crtime;
687703
#endif
688704
static mode_t mode;
705+
#ifdef SUPPORT_FILEFLAGS
706+
static uint32 fileflags;
707+
#endif
689708
#ifdef SUPPORT_HARD_LINKS
690709
static int64 dev;
691710
#endif
@@ -803,6 +822,10 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x
803822
#ifdef SUPPORT_CRTIMES
804823
if (crtimes_ndx)
805824
crtime = F_CRTIME(first);
825+
#endif
826+
#ifdef SUPPORT_FILEFLAGS
827+
if (preserve_fileflags)
828+
fileflags = F_FFLAGS(first);
806829
#endif
807830
if (preserve_uid)
808831
uid = F_OWNER(first);
@@ -887,6 +910,10 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x
887910

888911
if (chmod_modes && !S_ISLNK(mode) && mode)
889912
mode = tweak_mode(mode, chmod_modes);
913+
#ifdef SUPPORT_FILEFLAGS
914+
if (preserve_fileflags && !(xflags & XMIT_SAME_FLAGS))
915+
fileflags = (uint32)read_int(f);
916+
#endif
890917

891918
if (preserve_uid && !(xflags & XMIT_SAME_UID)) {
892919
if (protocol_version < 30)
@@ -1068,6 +1095,10 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x
10681095
}
10691096
#endif
10701097
file->mode = mode;
1098+
#ifdef SUPPORT_FILEFLAGS
1099+
if (preserve_fileflags)
1100+
F_FFLAGS(file) = fileflags;
1101+
#endif
10711102
if (preserve_uid)
10721103
F_OWNER(file) = uid;
10731104
if (preserve_gid) {
@@ -1481,6 +1512,14 @@ struct file_struct *make_file(const char *fname, struct file_list *flist,
14811512
}
14821513
#endif
14831514
file->mode = st.st_mode;
1515+
#if defined SUPPORT_FILEFLAGS || defined SUPPORT_FORCE_CHANGE
1516+
if (fileflags_ndx) {
1517+
/* On BSD st.st_flags is free; on Linux the helper does an
1518+
* open()+ioctl() per file -- but only when fileflags_ndx is
1519+
* set, which only happens with --fileflags / --force-change. */
1520+
F_FFLAGS(file) = rsync_lgetflags(fname, st.st_mode, &st);
1521+
}
1522+
#endif
14841523
if (preserve_uid)
14851524
F_OWNER(file) = st.st_uid;
14861525
if (preserve_gid)

generator.c

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,13 @@ extern int preserve_devices;
4343
extern int preserve_specials;
4444
extern int preserve_hard_links;
4545
extern int preserve_executability;
46+
extern int preserve_fileflags;
47+
extern int preserve_unsafe_fileflags;
4648
extern int preserve_perms;
4749
extern int preserve_mtimes;
4850
extern int omit_dir_times;
4951
extern int omit_link_times;
52+
extern int force_change;
5053
extern int delete_mode;
5154
extern int delete_before;
5255
extern int delete_during;
@@ -492,6 +495,13 @@ int unchanged_attrs(const char *fname, struct file_struct *file, stat_x *sxp)
492495
return 0;
493496
if (perms_differ(file, sxp))
494497
return 0;
498+
#ifdef SUPPORT_FILEFLAGS
499+
if (preserve_fileflags) {
500+
uint32 current = stat_x_get_fileflags(sxp, fname);
501+
if (current != filter_recv_fileflags(F_FFLAGS(file), current))
502+
return 0;
503+
}
504+
#endif
495505
if (ownership_differs(file, sxp))
496506
return 0;
497507
#ifdef SUPPORT_ACLS
@@ -553,6 +563,13 @@ void itemize(const char *fnamecmp, struct file_struct *file, int ndx, int statre
553563
iflags |= ITEM_REPORT_OWNER;
554564
if (gid_ndx && !(file->flags & FLAG_SKIP_GROUP) && sxp->st.st_gid != (gid_t)F_GROUP(file))
555565
iflags |= ITEM_REPORT_GROUP;
566+
#ifdef SUPPORT_FILEFLAGS
567+
if (preserve_fileflags && !S_ISLNK(file->mode)) {
568+
uint32 current = stat_x_get_fileflags(sxp, fnamecmp);
569+
if (current != filter_recv_fileflags(F_FFLAGS(file), current))
570+
iflags |= ITEM_REPORT_FFLAGS;
571+
}
572+
#endif
556573
#ifdef SUPPORT_ACLS
557574
if (preserve_acls && !S_ISLNK(file->mode)) {
558575
if (!ACL_READY(*sxp))
@@ -1460,6 +1477,10 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
14601477
if (!preserve_perms) { /* See comment in non-dir code below. */
14611478
file->mode = dest_mode(file->mode, sx.st.st_mode, dflt_perms, statret == 0);
14621479
}
1480+
#ifdef SUPPORT_FORCE_CHANGE
1481+
if (force_change && !preserve_fileflags)
1482+
F_FFLAGS(file) = stat_x_get_fileflags(&sx, fname);
1483+
#endif
14631484
if (statret != 0 && basis_dir[0] != NULL) {
14641485
int j = try_dests_non(file, fname, ndx, fnamecmpbuf, &sx, itemizing, code);
14651486
if (j == -2) {
@@ -1502,6 +1523,11 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
15021523
* readable and writable permissions during the time we are
15031524
* putting files within them. This is then restored to the
15041525
* former permissions after the transfer is done. */
1526+
#ifdef SUPPORT_FORCE_CHANGE
1527+
if (force_change && F_FFLAGS(file) & force_change
1528+
&& make_mutable(fname, file->mode, F_FFLAGS(file), force_change))
1529+
need_retouch_dir_perms = 1;
1530+
#endif
15051531
#ifdef HAVE_CHMOD
15061532
if (!am_root && (file->mode & S_IRWXU) != S_IRWXU && dir_tweaking) {
15071533
mode_t mode = file->mode | S_IRWXU;
@@ -1540,6 +1566,10 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx,
15401566
int exists = statret == 0 && stype != FT_DIR;
15411567
file->mode = dest_mode(file->mode, sx.st.st_mode, dflt_perms, exists);
15421568
}
1569+
#ifdef SUPPORT_FORCE_CHANGE
1570+
if (force_change && !preserve_fileflags)
1571+
F_FFLAGS(file) = stat_x_get_fileflags(&sx, fname);
1572+
#endif
15431573

15441574
#ifdef SUPPORT_HARD_LINKS
15451575
if (preserve_hard_links && F_HLINK_NOT_FIRST(file)
@@ -2124,10 +2154,23 @@ static void touch_up_dirs(struct file_list *flist, int ndx)
21242154
st.st_mtime = file->modtime;
21252155
#ifdef ST_MTIME_NSEC
21262156
st.ST_MTIME_NSEC = F_MOD_NSEC_or_0(file);
2157+
#endif
2158+
#ifdef SUPPORT_FORCE_CHANGE
2159+
st.st_mode = file->mode;
2160+
#ifdef HAVE_CHFLAGS
2161+
/* Tell set_times' try_a_force_change there's nothing
2162+
* to bypass yet; the real flags come from rsync_lgetflags.
2163+
* On Linux STRUCT_STAT has no st_flags so this is a no-op. */
2164+
st.st_flags = 0;
2165+
#endif
21272166
#endif
21282167
set_times(fname, &st);
21292168
}
21302169
}
2170+
#ifdef SUPPORT_FORCE_CHANGE
2171+
if (force_change && F_FFLAGS(file) & force_change)
2172+
undo_make_mutable(fname, F_FFLAGS(file));
2173+
#endif
21312174
if (counter >= loopchk_limit) {
21322175
if (allowed_lull)
21332176
maybe_send_keepalive(time(NULL), MSK_ALLOW_FLUSH);

ifuncs.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,10 @@ static inline void
7676
init_stat_x(stat_x *sx_p)
7777
{
7878
sx_p->crtime = 0;
79+
#ifdef SUPPORT_FILEFLAGS
80+
sx_p->fileflags = 0;
81+
sx_p->fileflags_cached = 0;
82+
#endif
7983
#ifdef SUPPORT_ACLS
8084
sx_p->acc_acl = sx_p->def_acl = NULL;
8185
#endif

0 commit comments

Comments
 (0)