Merge branch 'fix/parity-fs' into feat/parity-fixes

# Conflicts:
#	RSYNC_COMPAT.md
This commit is contained in:
2026-09-18 21:19:31 +02:00
22 changed files with 1684 additions and 175 deletions
+15 -10
View File
@@ -168,9 +168,14 @@ bool charset_spec_valid_direction(const char* from_charset, const char* to_chars
return direction_probe_valid(from_charset, to_charset);
}
/* The receiver's real conversion is wire(client REMOTE) -> server-local (the
* server's own --iconv LOCAL half, or the client's LOCAL half when the server
* has no --iconv). A dedicated pre-ack check so an impossible direction is
/* The receiver's conversion is wire charset -> destination charset. rsync's
* CONVERT_SPEC is LOCAL,REMOTE and "stays the same whether you're pushing or
* pulling", so for a PUSH (FastSync's only direction) the destination end's
* charset is the spec's REMOTE half: the client converts LOCAL -> REMOTE on the
* sender and the receiver writes the wire bytes verbatim. Only a server that
* declares its OWN --iconv (the daemon "charset" analog) has a different local
* charset, and then it is that spec's LOCAL half and the receiver converts
* wire -> server-local. A dedicated pre-ack check so an impossible direction is
* rejected before the connection instead of refusing mid-transfer. */
bool charset_wire_receiver_spec_valid(const char* spec, const char* server_spec) {
if (!spec)
@@ -180,7 +185,7 @@ bool charset_wire_receiver_spec_valid(const char* spec, const char* server_spec)
if (charset_spec_parse(spec, &local, &remote) != 0)
return false;
const char* wire = remote;
const char* target_local = local;
const char* target_local = remote;
char* server_local = NULL;
char* server_remote = NULL;
if (server_spec) {
@@ -302,13 +307,13 @@ bool charset_wire_init_receiver(const char* spec, const char* server_spec) {
char* remote;
if (charset_spec_parse(spec, &local, &remote) != 0)
return false;
/* The wire charset is the client spec's REMOTE half; the local charset is
* the client spec's LOCAL half unless the server was itself started with
* --iconv naming a different local charset (the server halves above never
* travel, so the server's own flag is the only way its local charset can
* differ from what the client assumed). */
/* The wire charset is the client spec's REMOTE half (rsync's LOCAL,REMOTE
* spec stays the same push or pull, so on a push the destination end's
* charset is REMOTE and the receiver writes the wire bytes verbatim). Only a
* server started with its own --iconv declares a different local charset (the
* server halves above never travel), and then it is that spec's LOCAL half. */
const char* wire = remote;
const char* target_local = local;
const char* target_local = remote;
char* server_local = NULL;
char* server_remote = NULL;
if (server_spec) {
+6 -5
View File
@@ -57,8 +57,9 @@ void charset_conversion_close(void* conversion);
/* Process-wide wire conversion. charset_wire_init_sender (client side) opens
* LOCAL->REMOTE; charset_wire_init_receiver (server side) opens
* wire(REMOTE)->server-local. server_spec is the server's own --iconv, whose
* LOCAL half may override the local charset the client assumed; NULL reuses
* the client spec's LOCAL half. Both return false on an unsupported spec.
* LOCAL half overrides the destination charset; NULL means the destination
* charset is the client spec's REMOTE half (rsync's push semantics: the wire
* bytes are written verbatim). Both return false on an unsupported spec.
* The state is freed with charset_wire_free. */
bool charset_wire_init_sender(const char* spec);
bool charset_wire_init_receiver(const char* spec, const char* server_spec);
@@ -66,9 +67,9 @@ void charset_wire_free(void);
bool charset_wire_active(void);
/* Pre-ack receiver-direction sanity (see charset_wire_init_receiver): true
* when the exact wire->server-local conversion the receiver will use (client
* spec's REMOTE half into the server's own LOCAL half, or the client's LOCAL
* half when the server has no --iconv) opens and produces NUL-free output. */
* when the exact wire->destination conversion the receiver will use (client
* spec's REMOTE half into the server's own LOCAL half, or REMOTE->REMOTE when
* the server has no --iconv) opens and produces NUL-free output. */
bool charset_wire_receiver_spec_valid(const char* spec, const char* server_spec);
/* Convert a path across the wire in the process direction. Returns a malloc'd
+4 -2
View File
@@ -432,8 +432,10 @@ typedef struct Config {
* repeated -F adds --filter='- .rsync-filter' so they are excluded too. */
int per_dir_filter_count;
bool one_file_system; /* -x/--one-file-system: do not cross filesystem boundaries */
/* --no-implied-dirs: client-only. With -R + --files-from, refuse to place a
* listed file whose ancestor directory is not itself explicitly listed. */
/* --no-implied-dirs: client-only. With -R, do not transfer the source
* metadata of the parent directories implied by a listed path; an unlisted
* implied parent is still created (with default attributes) so the listed
* file can be placed, matching rsync. */
bool no_implied_dirs;
/* -d/--dirs: client-only. Transfer the directory entries named by the
* source argument / --files-from list without recursing into contents. */
+20
View File
@@ -807,6 +807,26 @@ bool file_ensure_directory_secure(const char* path) {
} else if (errno == EEXIST) {
dir_fd = openat(parent_fd, leaf, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
}
} else if (dir_fd < 0 && (errno == ENOTDIR || errno == ELOOP)) {
/* rsync replaces a destination non-directory (regular file or symlink)
with an incoming directory. Confined to the already-opened secure
parent fd: the leaf is unlinked by name (never followed) and only a
non-directory is ever removed, so this cannot escape the authorized
root or remove a pre-existing directory tree. A symlink is left alone:
replacing it is not required for FastSync's transferred directories and
keeps --keep-dirlinks semantics untouched. */
struct stat leaf_st;
if (fstatat(parent_fd, leaf, &leaf_st, AT_SYMLINK_NOFOLLOW) == 0 && !S_ISDIR(leaf_st.st_mode) &&
!S_ISLNK(leaf_st.st_mode)) {
if (unlinkat(parent_fd, leaf, 0) == 0) {
if (mkdirat(parent_fd, leaf, (mode_t)(0777 & ~(mode_t)file_process_umask())) == 0) {
created = true;
} else if (errno != EEXIST) {
/* leave dir_fd < 0 so the caller sees the failure */
}
dir_fd = openat(parent_fd, leaf, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
}
}
}
bool ok = dir_fd >= 0;
/* --copy-as owns a directory this call just created (the final component;