Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions .github/workflows/almalinux-8-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,11 @@ jobs:
- name: info
run: ./rsync --version
- name: check
# In the container we already run as root, so no sudo. The
# crtimes-not-supported skip matches the other Linux jobs.
run: RSYNC_EXPECT_SKIPPED=crtimes make check
# In the container we already run as root, so no sudo. crtimes
# has no kernel support here; chattr(1) isn't installed in the
# AlmaLinux 8 base image so the fileflags test self-skips even
# though the rsync build has FS_IOC_GETFLAGS support.
run: RSYNC_EXPECT_SKIPPED=crtimes,fileflags make check
- name: ssl file list
run: ./rsync-ssl --no-motd download.samba.org::rsyncftp/ || true
- name: save artifact
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/cygwin-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ jobs:
- name: info
run: bash -c '/usr/local/bin/rsync --version'
- name: check
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'
run: bash -c 'RSYNC_EXPECT_SKIPPED=acls-default,acls,bare-do-open-symlink-race,chdir-symlink-race,chmod-symlink-race,chown,daemon-chroot-acl,daemon-refuse-fileflags,devices,dir-sgid,fileflags,open-noatime,protected-regular,sender-flist-symlink-leak,simd-checksum,symlink-dirlink-basis make check'
- name: ssl file list
run: bash -c 'PATH="/usr/local/bin:$PATH" rsync-ssl --no-motd download.samba.org::rsyncftp/ || true'
- name: save artifact
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ubuntu-22.04-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ jobs:
- name: check30
run: sudo RSYNC_EXPECT_SKIPPED=crtimes make check30
- name: check29
run: sudo RSYNC_EXPECT_SKIPPED=crtimes make check29
run: sudo RSYNC_EXPECT_SKIPPED=crtimes,daemon-refuse-fileflags,fileflags make check29
- name: ssl file list
run: rsync-ssl --no-motd download.samba.org::rsyncftp/ || true
- name: save artifact
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ubuntu-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ jobs:
- name: check30
run: sudo RSYNC_EXPECT_SKIPPED=crtimes make check30
- name: check29
run: sudo RSYNC_EXPECT_SKIPPED=crtimes make check29
run: sudo RSYNC_EXPECT_SKIPPED=crtimes,daemon-refuse-fileflags,fileflags make check29
- name: ssl file list
run: rsync-ssl --no-motd download.samba.org::rsyncftp/ || true
- name: save artifact
Expand Down
13 changes: 7 additions & 6 deletions Makefile.in
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ GENFILES=configure.sh aclocal.m4 config.h.in rsync.1 rsync.1.html \
HEADERS=byteorder.h config.h errcode.h proto.h rsync.h ifuncs.h itypes.h inums.h \
lib/pool_alloc.h lib/mdigest.h lib/md-defines.h
LIBOBJ=lib/wildmatch.o lib/compat.o lib/snprintf.o lib/mdfour.o lib/md5.o \
lib/permstring.o lib/pool_alloc.o lib/sysacls.o lib/sysxattrs.o @LIBOBJS@
lib/permstring.o lib/pool_alloc.o lib/sysacls.o lib/sysxattrs.o \
lib/fileflags.o @LIBOBJS@
zlib_OBJS=zlib/deflate.o zlib/inffast.o zlib/inflate.o zlib/inftrees.o \
zlib/trees.o zlib/zutil.o zlib/adler32.o zlib/compress.o zlib/crc32.o
OBJS1=flist.o rsync.o generator.o receiver.o cleanup.o sender.o exclude.o \
Expand All @@ -53,7 +54,7 @@ popt_OBJS= popt/popt.o popt/poptconfig.o \
popt/popthelp.o popt/poptparse.o popt/poptint.o
OBJS=$(OBJS1) $(OBJS2) $(OBJS3) $(DAEMON_OBJ) $(LIBOBJ) @BUILD_ZLIB@ @BUILD_POPT@

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@
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@

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

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

T_UNSAFE_OBJ = t_unsafe.o syscall.o util1.o util2.o t_stub.o lib/compat.o lib/snprintf.o lib/wildmatch.o
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
t_unsafe$(EXEEXT): $(T_UNSAFE_OBJ)
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(T_UNSAFE_OBJ) $(LIBS)

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
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
t_chmod_secure$(EXEEXT): $(T_CHMOD_SECURE_OBJ)
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(T_CHMOD_SECURE_OBJ) $(LIBS)

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
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
t_secure_relpath$(EXEEXT): $(T_SECURE_RELPATH_OBJ)
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(T_SECURE_RELPATH_OBJ) $(LIBS)

Expand Down
8 changes: 8 additions & 0 deletions batch.c
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ static int tweaked_append;
static int tweaked_append_verify;
static int tweaked_iconv;

/* preserve_fileflags is referenced by flag_ptr even when SUPPORT_FILEFLAGS
* is not defined (so the bit position in the batch stream is stable
* across builds with and without chflags support). It's defined in
* options.c on every build. */
extern int preserve_fileflags;

static int *flag_ptr[] = {
&recurse, /* 0 */
&preserve_uid, /* 1 */
Expand All @@ -72,6 +78,7 @@ static int *flag_ptr[] = {
&inplace, /* 12 (protocol 30) */
&tweaked_append, /* 13 (protocol 30) */
&tweaked_append_verify, /* 14 (protocol 30) */
&preserve_fileflags, /* 15 (protocol 30+, requires varint flags) */
NULL
};

Expand All @@ -91,6 +98,7 @@ static const char *const flag_name[] = {
"--inplace",
"--append",
"--append-verify",
"--fileflags",
NULL
};

Expand Down
22 changes: 21 additions & 1 deletion compat.c
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,15 @@ extern int checksum_seed;
extern int basis_dir_cnt;
extern int prune_empty_dirs;
extern int protocol_version;
extern int force_change;
extern int protect_args;
extern int preserve_uid;
extern int preserve_gid;
extern int preserve_atimes;
extern int preserve_crtimes;
extern int preserve_acls;
extern int preserve_xattrs;
extern int preserve_fileflags;
extern int xfer_flags_as_varint;
extern int need_messages_from_generator;
extern int delete_mode, delete_before, delete_during, delete_after;
Expand Down Expand Up @@ -87,7 +89,7 @@ struct name_num_item *xattr_sum_nni;
int xattr_sum_len = 0;

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

int receiver_symlink_times = 0; /* receiver can set the time on a symlink */
int sender_symlink_iconv = 0; /* sender should convert symlink content */
Expand Down Expand Up @@ -589,6 +591,8 @@ void setup_protocol(int f_out,int f_in)
uid_ndx = ++file_extra_cnt;
if (preserve_gid)
gid_ndx = ++file_extra_cnt;
if (preserve_fileflags || (force_change && !am_sender))
fileflags_ndx = ++file_extra_cnt;
if (preserve_acls && !am_sender)
acls_ndx = ++file_extra_cnt;
if (preserve_xattrs)
Expand Down Expand Up @@ -752,6 +756,10 @@ void setup_protocol(int f_out,int f_in)
fprintf(stderr, "Both rsync versions must be at least 3.2.0 for --crtimes.\n");
exit_cleanup(RERR_PROTOCOL);
}
/* --fileflags rejection moved to after the if/else if cascade
* so it also runs for protocol < 30 (where xfer_flags_as_varint
* stays 0 and the receiver wouldn't understand the extra
* fileflags word that flist.c would write). */
if (am_sender) {
receiver_symlink_times = am_server
? strchr(client_info, 'L') != NULL
Expand Down Expand Up @@ -783,6 +791,18 @@ void setup_protocol(int f_out,int f_in)
#endif
}

/* --fileflags writes an extra word per file in the file-list stream
* and uses the XMIT_SAME_FLAGS bit (1<<16), both of which require the
* varint flag-encoding negotiated by CF_VARINT_FLIST_FLAGS in
* compat_flags. That negotiation only happens in the protocol >= 30
* branch above, so for protocol < 30 xfer_flags_as_varint stays 0.
* Without this check, --fileflags --protocol=29 would silently emit
* bytes the receiver doesn't expect and desync the file-list stream. */
if (!xfer_flags_as_varint && preserve_fileflags) {
fprintf(stderr, "Both rsync versions must be at least 3.2.0 for --fileflags.\n");
exit_cleanup(RERR_PROTOCOL);
}

if (read_batch)
do_negotiated_strings = 0;

Expand Down
31 changes: 30 additions & 1 deletion configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ AC_CHECK_HEADERS(sys/fcntl.h sys/select.h fcntl.h sys/time.h sys/unistd.h \
sys/acl.h acl/libacl.h attr/xattr.h sys/xattr.h sys/extattr.h dl.h \
popt.h popt/popt.h linux/falloc.h netinet/in_systm.h netgroup.h \
zlib.h xxhash.h openssl/md4.h openssl/md5.h zstd.h lz4.h sys/file.h \
bsd/string.h)
bsd/string.h linux/fs.h)
AC_CHECK_HEADERS([netinet/ip.h], [], [], [[#include <netinet/in.h>]])
AC_HEADER_MAJOR_FIXED

Expand Down Expand Up @@ -928,6 +928,35 @@ AC_PREPROC_IFELSE([AC_LANG_SOURCE([[
]
)

AC_MSG_CHECKING([for FS_IOC_GETFLAGS])
AC_PREPROC_IFELSE([AC_LANG_SOURCE([[
#include <sys/ioctl.h>
#ifdef HAVE_LINUX_FS_H
#include <linux/fs.h>
#endif
#ifndef FS_IOC_GETFLAGS
#error FS_IOC_GETFLAGS is missing
#endif
#ifndef FS_IOC_SETFLAGS
#error FS_IOC_SETFLAGS is missing
#endif
#ifndef FS_NODUMP_FL
#error FS_NODUMP_FL is missing
#endif
#ifndef FS_IMMUTABLE_FL
#error FS_IMMUTABLE_FL is missing
#endif
#ifndef FS_APPEND_FL
#error FS_APPEND_FL is missing
#endif
]])], [
AC_MSG_RESULT([yes])
AC_DEFINE([HAVE_FS_IOC_GETFLAGS], [1], [Define if FS_IOC_GETFLAGS / FS_IOC_SETFLAGS ioctls are available (Linux chattr-style file flags).])
], [
AC_MSG_RESULT([no])
]
)

AC_MSG_CHECKING([for FALLOC_FL_ZERO_RANGE])
AC_PREPROC_IFELSE([AC_LANG_SOURCE([[
#define _GNU_SOURCE 1
Expand Down
69 changes: 67 additions & 2 deletions delete.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
extern int am_root;
extern int make_backups;
extern int max_delete;
extern int force_change;
extern char *backup_dir;
extern char *backup_suffix;
extern int backup_suffix_len;
Expand Down Expand Up @@ -97,15 +98,49 @@ static enum delret delete_dir_contents(char *fname, uint16 flags)
}

strlcpy(p, fp->basename, remainder);
#ifdef SUPPORT_FORCE_CHANGE
/* For SUB-DIRECTORIES only: clear the immutable bits so the
* recursive delete_dir_contents below can unlink entries
* inside (the dir's +i blocks all add/remove operations on
* its children). Track so we can undo on failure.
*
* Non-dirs are deliberately NOT pre-cleared here: the
* underlying do_unlink_at / do_rmdir_at force_change recovery
* uses fd-based fchflags, and that fd survives a rename or
* hardlink performed by make_backup() -- so the recovery
* correctly preserves the immutable flag on the inode after
* it lands at its backup location. A path-based pre-clear
* here would lose the flag because our undo only knows the
* original path, which is gone after make_backup moves the
* inode into the backup tree. */
int entry_unmuted = 0;
uint32 entry_saved_flags = 0;
if (S_ISDIR(fp->mode) && force_change
&& (F_FFLAGS(fp) & force_change)) {
if (make_mutable(fname, fp->mode, F_FFLAGS(fp), force_change) > 0) {
entry_unmuted = 1;
entry_saved_flags = F_FFLAGS(fp);
}
}
#endif
if (!(fp->mode & S_IWUSR) && !am_root && fp->flags & FLAG_OWNED_BY_US)
do_chmod_at(fname, fp->mode | S_IWUSR);
/* Save stack by recursing to ourself directly. */
if (S_ISDIR(fp->mode)) {
if (delete_dir_contents(fname, flags | DEL_RECURSE) != DR_SUCCESS)
ret = DR_NOT_EMPTY;
}
if (delete_item(fname, fp->mode, flags) != DR_SUCCESS)
enum delret item_ret = delete_item(fname, fp->mode, flags);
if (item_ret != DR_SUCCESS)
ret = DR_NOT_EMPTY;
#ifdef SUPPORT_FORCE_CHANGE
/* Restore the entry's flags if the delete didn't actually take
* place. DR_SUCCESS means the inode is gone, no need to
* restore; everything else (DR_NOT_EMPTY, DR_AT_LIMIT,
* DR_FAILURE) leaves the file in place. */
if (entry_unmuted && item_ret != DR_SUCCESS)
undo_make_mutable(fname, entry_saved_flags);
#endif
}

fname[dlen] = '\0';
Expand All @@ -132,6 +167,15 @@ enum delret delete_item(char *fbuf, uint16 mode, uint16 flags)
enum delret ret;
char *what;
int ok;
#ifdef SUPPORT_FORCE_CHANGE
/* Track whether we cleared the dir's immutable bits so we can
* restore them if the directory ends up NOT being removed (delete
* skipped, contents non-empty, rmdir failed). Without this the
* receiver would be left with a less-protected directory than it
* started with. */
int dir_unmuted = 0;
uint32 dir_saved_flags = 0;
#endif

if (DEBUG_GTE(DEL, 2)) {
rprintf(FINFO, "delete_item(%s) mode=%o flags=%d\n",
Expand All @@ -144,6 +188,18 @@ enum delret delete_item(char *fbuf, uint16 mode, uint16 flags)
if (S_ISDIR(mode) && !(flags & DEL_DIR_IS_EMPTY)) {
/* This only happens on the first call to delete_item() since
* delete_dir_contents() always calls us w/DEL_DIR_IS_EMPTY. */
#ifdef SUPPORT_FORCE_CHANGE
if (force_change) {
STRUCT_STAT st;
if (x_lstat(fbuf, &st, NULL) == 0) {
uint32 ff = rsync_lgetflags(fbuf, st.st_mode, &st);
if (ff != NO_FFLAGS && make_mutable(fbuf, st.st_mode, ff, force_change) > 0) {
dir_unmuted = 1;
dir_saved_flags = ff;
}
}
}
#endif
ignore_perishable = 1;
/* If DEL_RECURSE is not set, this just reports emptiness. */
ret = delete_dir_contents(fbuf, flags);
Expand All @@ -155,7 +211,8 @@ enum delret delete_item(char *fbuf, uint16 mode, uint16 flags)

if (!(flags & DEL_MAKE_ROOM) && max_delete >= 0 && stats.deleted_files >= max_delete) {
skipped_deletes++;
return DR_AT_LIMIT;
ret = DR_AT_LIMIT;
goto check_ret;
}

if (S_ISDIR(mode)) {
Expand Down Expand Up @@ -207,6 +264,14 @@ enum delret delete_item(char *fbuf, uint16 mode, uint16 flags)
}

check_ret:
#ifdef SUPPORT_FORCE_CHANGE
/* If we made the directory mutable but it's still present (delete
* skipped, contents non-empty, rmdir EPERM after recovery, etc.),
* restore its original flags so we don't leave the receiver with
* weaker protection than it started with. */
if (dir_unmuted && ret != DR_SUCCESS)
undo_make_mutable(fbuf, dir_saved_flags);
#endif
if (ret != DR_SUCCESS && flags & DEL_MAKE_ROOM) {
const char *desc;
switch (flags & DEL_MAKE_ROOM) {
Expand Down
Loading
Loading