refactor(config): single X-macro table for serialized fields
Every Config field that crosses the wire was declared in up to six places (struct member, config_set_defaults, send_*, receive_*, and the two CLI option tables) and could drift silently. Add CONFIG_WIRE_FIELDS in config.h: one ordered per-segment table where each serialized field is declared once with its C type, default and wire codec (KIND). config.h now expands the table to declare the struct members; config_set_defaults() expands it to assign the defaults; and config_send_wire_block()/config_receive() expand the per-segment lists to emit/consume the frame. The per-segment function names, call order and segment boundaries are preserved exactly. Fields with genuinely custom logic keep dedicated helpers but are still declared once in the table: the protocol-version handshake (HEADER), daemon SCRAM auth (STR_REDACTED_AUTH), the daemon module name (STR_MODULE), the repeated count+array blocks (BLOCK_SKIP_SUFFIXES/BLOCK_BASIS/BLOCK_IDMAP), --copy-as presence/ids (COPY_AS_*), and the derived --delta / use_xattrs bits (DERIVED_DELTA, BOOL_XATTR_DERIVE). The version field remains a special header (validated before any other field is parsed) and is sent by config_send_wire_block() explicitly. No public field is renamed and PROTOCOL_VERSION stays "2.20.0". Because the struct declaration order is no longer the wire order, the wire order is now enforced solely by the table and by a byte-exact golden test (follow-up commit). Add config_send_wire_block() so that test can hash the frame body without the STATUS_OK handshake.
This commit is contained in:
+369
-597
File diff suppressed because it is too large
Load Diff
+366
-269
@@ -75,87 +75,203 @@ typedef struct {
|
||||
* privilege_super_mode_permitted() in identity.h. */
|
||||
typedef enum SuperMode { SUPER_MODE_AUTO = 0, SUPER_MODE_ON = 1, SUPER_MODE_OFF = 2 } SuperMode;
|
||||
|
||||
/* ===========================================================================
|
||||
* Config wire-field table (single source of truth for protocol 2.20.0).
|
||||
*
|
||||
* Every field below crosses the wire. The table is the ONLY place a
|
||||
* serialized field is named: config.h expands CONFIG_WIRE_FIELDS() to declare
|
||||
* the struct member, config_set_defaults() expands it to assign the default,
|
||||
* and config_send_wire_block()/config_receive_with_validate() expand the
|
||||
* per-segment lists to emit/consume the frame in exactly this order. Do NOT
|
||||
* reorder entries and do NOT change a field's segment/KIND without a
|
||||
* PROTOCOL_VERSION bump: the resulting byte stream is pinned by
|
||||
* test_config_wire_golden().
|
||||
*
|
||||
* Entry layout: X(MEMBER, CTYPE, DEFAULT, KIND)
|
||||
* MEMBER struct member name (public; never rename)
|
||||
* CTYPE C type of the member
|
||||
* DEFAULT default-value expression used by config_set_defaults()
|
||||
* KIND wire codec, dispatched to CONFIG_SEND_<KIND>/CONFIG_RECV_<KIND>
|
||||
* in config.c (strings receive through a ConfigStringBudget).
|
||||
*
|
||||
* Fields with genuinely custom logic keep dedicated helpers but are still
|
||||
* declared here exactly once: the protocol-version handshake (HEADER), the
|
||||
* daemon SCRAM auth username (STR_REDACTED_AUTH), the daemon module name
|
||||
* (STR_MODULE), repeated count+array blocks (BLOCK_*), --copy-as presence
|
||||
* (COPY_AS_*), and the derived --delta / use_xattrs bits (DERIVED_DELTA,
|
||||
* BOOL_XATTR_DERIVE).
|
||||
* =========================================================================== */
|
||||
#define CONFIG_WIRE_HEADER_FIELDS(X) X(version, char*, str_dup(PROTOCOL_VERSION), STR)
|
||||
|
||||
#define CONFIG_WIRE_CORE_FIELDS(X) \
|
||||
X(eight_bit_output, bool, false, BOOL_8BIT) \
|
||||
X(max_alloc, unsigned long long, DEFAULT_MAX_ALLOC, RAW_MAXALLOC) \
|
||||
X(send_directory, char*, NULL, STR) \
|
||||
X(receive_root_directory, char*, NULL, STR) \
|
||||
X(save_to_disk, bool, false, BOOL) \
|
||||
X(use_multithreading, bool, false, BOOL) \
|
||||
X(use_chunk_serialization, bool, false, BOOL) \
|
||||
X(use_compression, bool, false, BOOL) \
|
||||
X(use_metadata, bool, false, BOOL) \
|
||||
X(use_executability, bool, false, BOOL) \
|
||||
X(compression_level, int, 5, INT) \
|
||||
X(chunk_size, unsigned long long, DEFAULT_CHUNK_SIZE, RAW) \
|
||||
X(use_sendfile, bool, false, BOOL)
|
||||
|
||||
#define CONFIG_WIRE_DELTA_FIELDS(X) \
|
||||
X(use_delete, bool, false, BOOL) \
|
||||
X(use_incremental, bool, false, BOOL) \
|
||||
X(size_only, bool, false, BOOL) \
|
||||
X(ignore_times, bool, false, BOOL) \
|
||||
X(use_delta, bool, false, DERIVED_DELTA) \
|
||||
X(delta_block_size, uint32_t, DELTA_BLOCK_SIZE_DEFAULT, RAW) \
|
||||
X(delta_max_file_size, unsigned long long, DELTA_MAX_FILE_SIZE, RAW)
|
||||
|
||||
#define CONFIG_WIRE_FILE_OPTIONS_FIELDS(X) \
|
||||
X(backup, bool, false, BOOL) \
|
||||
X(backup_dir, char*, NULL, STR_OPT) \
|
||||
X(remove_source_files, bool, false, BOOL) \
|
||||
X(follow_symlinks, bool, false, BOOL) \
|
||||
X(copy_links, bool, false, BOOL) \
|
||||
X(safe_links, bool, false, BOOL) \
|
||||
X(copy_unsafe_links, bool, false, BOOL) \
|
||||
X(preserve_hard_links, bool, false, BOOL) \
|
||||
X(preserve_acls, bool, false, BOOL) \
|
||||
X(preserve_xattrs, bool, false, BOOL) \
|
||||
X(preserve_devices, bool, false, BOOL) \
|
||||
X(preserve_sparse, bool, false, BOOL) \
|
||||
X(preserve_specials, bool, false, BOOL) \
|
||||
X(copy_devices, bool, false, BOOL) \
|
||||
X(write_devices, bool, false, BOOL)
|
||||
|
||||
#define CONFIG_WIRE_SELECTION_FIELDS(X) \
|
||||
X(ignore_existing, bool, false, BOOL) \
|
||||
X(existing, bool, false, BOOL) \
|
||||
X(update, bool, false, BOOL) \
|
||||
X(inplace, bool, false, BOOL) \
|
||||
X(delay_updates, bool, false, BOOL) \
|
||||
X(append, bool, false, BOOL) \
|
||||
X(use_fsync, bool, false, BOOL) \
|
||||
X(append_verify, bool, false, BOOL) \
|
||||
X(delete_excluded, bool, false, BOOL) \
|
||||
X(force_delete, bool, false, BOOL) \
|
||||
X(delete_missing_args, bool, false, BOOL) \
|
||||
X(delete_after, bool, false, BOOL) \
|
||||
X(preallocate, bool, false, BOOL) \
|
||||
X(max_delete, int, -1, RAW) \
|
||||
X(relative, bool, false, BOOL) \
|
||||
X(prune_empty_dirs, bool, false, BOOL) \
|
||||
X(mkpath, bool, false, BOOL) \
|
||||
X(delete_during, bool, false, BOOL) \
|
||||
X(delete_delay, bool, false, BOOL)
|
||||
|
||||
#define CONFIG_WIRE_RESUME_FIELDS(X) \
|
||||
X(temp_dir, char*, NULL, STR_OPT) \
|
||||
X(partial, bool, false, BOOL) \
|
||||
X(partial_dir, char*, NULL, STR_OPT) \
|
||||
X(suffix, char*, NULL, STR_OPT) \
|
||||
X(delete_before, bool, false, BOOL) \
|
||||
X(checksum, bool, false, BOOL) \
|
||||
X(modify_window, int, 0, RAW) \
|
||||
X(compress_choice, char*, NULL, STR_KEEP) \
|
||||
X(chmod_spec, char*, NULL, STR_KEEP) \
|
||||
X(skip_compress_set, bool, false, BOOL) \
|
||||
X(skip_compress_count, int, 0, INT_SKIPCOUNT) \
|
||||
X(skip_compress_suffixes, char**, NULL, BLOCK_SKIP_SUFFIXES)
|
||||
|
||||
#define CONFIG_WIRE_BASIS_FIELDS(X) \
|
||||
X(basis_count, int, 0, INT_BASISCOUNT) \
|
||||
X(basis_dirs, BasisDest*, NULL, BLOCK_BASIS)
|
||||
|
||||
#define CONFIG_WIRE_FUZZY_FIELDS(X) X(fuzzy, bool, false, BOOL)
|
||||
|
||||
#define CONFIG_WIRE_CHECKSUM_FIELDS(X) \
|
||||
X(checksum_algo, int, CHECKSUM_ALGO_XXH64, INT_CHECKSUM_ALGO) \
|
||||
X(checksum_seed, uint64_t, 0, RAW)
|
||||
|
||||
#define CONFIG_WIRE_IDENTITY_FIELDS(X) \
|
||||
X(numeric_ids, bool, false, BOOL) \
|
||||
X(chown_uid_set, bool, false, BOOL) \
|
||||
X(chown_uid, int32_t, 0, INT_IDENTITY) \
|
||||
X(chown_gid_set, bool, false, BOOL) \
|
||||
X(chown_gid, int32_t, 0, INT_IDENTITY) \
|
||||
X(usermap_count, int, 0, INT_IDMAPCOUNT) \
|
||||
X(usermap, IdentityMap*, NULL, BLOCK_IDMAP) \
|
||||
X(groupmap_count, int, 0, INT_IDMAPCOUNT) \
|
||||
X(groupmap, IdentityMap*, NULL, BLOCK_IDMAP)
|
||||
|
||||
#define CONFIG_WIRE_METADATA_TIMES_FIELDS(X) \
|
||||
X(preserve_atimes, bool, false, BOOL) \
|
||||
X(preserve_crtimes, bool, false, BOOL) \
|
||||
X(omit_dir_times, bool, false, BOOL) \
|
||||
X(omit_link_times, bool, false, BOOL)
|
||||
|
||||
#define CONFIG_WIRE_SYMLINK_TRUST_FIELDS(X) \
|
||||
X(munge_links, bool, false, BOOL) \
|
||||
X(keep_dirlinks, bool, false, BOOL)
|
||||
|
||||
#define CONFIG_WIRE_XATTR_FIELDS(X) X(fake_super, bool, false, BOOL_XATTR_DERIVE)
|
||||
|
||||
#define CONFIG_WIRE_MODULE_FIELDS(X) X(module, char*, NULL, STR_MODULE)
|
||||
|
||||
#define CONFIG_WIRE_DAEMON_AUTH_FIELDS(X) X(auth_user, char*, NULL, STR_REDACTED_AUTH)
|
||||
|
||||
#define CONFIG_WIRE_ICONV_FIELDS(X) X(iconv_spec, char*, NULL, STR_OPT)
|
||||
|
||||
#define CONFIG_WIRE_PRIVILEGE_FIELDS(X) X(super_mode, SuperMode, SUPER_MODE_AUTO, SUPERMODE)
|
||||
|
||||
#define CONFIG_WIRE_COPY_AS_FIELDS(X) \
|
||||
X(copy_as_set, bool, false, COPY_AS_PRESENCE) \
|
||||
X(copy_as_uid, int32_t, 0, COPY_AS_ID) \
|
||||
X(copy_as_gid, int32_t, 0, COPY_AS_ID)
|
||||
|
||||
/* All serialized fields, in exact wire order. Concatenating the per-segment
|
||||
* lists here is what keeps the declaration order = the wire order. */
|
||||
#define CONFIG_WIRE_FIELDS(X) \
|
||||
CONFIG_WIRE_HEADER_FIELDS(X) \
|
||||
CONFIG_WIRE_CORE_FIELDS(X) \
|
||||
CONFIG_WIRE_DELTA_FIELDS(X) \
|
||||
CONFIG_WIRE_FILE_OPTIONS_FIELDS(X) \
|
||||
CONFIG_WIRE_SELECTION_FIELDS(X) \
|
||||
CONFIG_WIRE_RESUME_FIELDS(X) \
|
||||
CONFIG_WIRE_BASIS_FIELDS(X) \
|
||||
CONFIG_WIRE_FUZZY_FIELDS(X) \
|
||||
CONFIG_WIRE_CHECKSUM_FIELDS(X) \
|
||||
CONFIG_WIRE_IDENTITY_FIELDS(X) \
|
||||
CONFIG_WIRE_METADATA_TIMES_FIELDS(X) \
|
||||
CONFIG_WIRE_SYMLINK_TRUST_FIELDS(X) \
|
||||
CONFIG_WIRE_XATTR_FIELDS(X) \
|
||||
CONFIG_WIRE_MODULE_FIELDS(X) \
|
||||
CONFIG_WIRE_DAEMON_AUTH_FIELDS(X) \
|
||||
CONFIG_WIRE_ICONV_FIELDS(X) \
|
||||
CONFIG_WIRE_PRIVILEGE_FIELDS(X) \
|
||||
CONFIG_WIRE_COPY_AS_FIELDS(X)
|
||||
|
||||
typedef struct Config {
|
||||
char* version;
|
||||
char* send_directory;
|
||||
char* receive_root_directory;
|
||||
bool save_to_disk;
|
||||
bool use_multithreading;
|
||||
/* -j/--threads=N: number of parallel scanner worker threads for the -m
|
||||
* pipeline. 0 (the default, also set by bare -j/--threads) means "use the
|
||||
* scanner's built-in default" (4). CLIENT-ONLY: it is a local scheduling
|
||||
* concern and is NEVER serialized into the wire config frame. */
|
||||
int scanner_threads;
|
||||
bool use_chunk_serialization;
|
||||
bool use_compression;
|
||||
bool use_sendfile;
|
||||
bool use_metadata;
|
||||
bool use_executability;
|
||||
bool metadata_explicitly_disabled;
|
||||
bool show_progress;
|
||||
bool dry_run;
|
||||
bool remove_source_files;
|
||||
bool use_delete;
|
||||
int compression_level;
|
||||
int compression_threads;
|
||||
unsigned long long chunk_size;
|
||||
int ssh_port;
|
||||
TransportType transport;
|
||||
char* ssh_destination;
|
||||
/* Daemon module selection (Wave A, protocol 2.15.0). Client-composed from a
|
||||
* host::module/path destination; NULL or "" means "no module" (the ordinary
|
||||
* standalone-server path). Crosses the wire as a trailing config-frame
|
||||
* string so the daemon can look the module up in its own config and confine
|
||||
* the connection to the module's root (never a client-chosen root). */
|
||||
char* module;
|
||||
/* Daemon password authentication (A7 remediation, protocol 2.19.0).
|
||||
* Client-composed from a --password-file whose first meaningful line is
|
||||
* `user:password`: the client sends ONLY the username in the config frame
|
||||
* (auth_user); the literal password is kept in auth_password CLIENT-SIDE for
|
||||
* the duration of the SCRAM challenge/response and is NEVER serialized. Both
|
||||
* are NULL when the client has no credentials to present; a module WITHOUT
|
||||
* `auth users` stays open and the server ignores any credentials that do
|
||||
* arrive (the client sends them opportunistically and the server decides). */
|
||||
char* auth_user;
|
||||
char* auth_password;
|
||||
/* Client-only path of --password-file (never crosses the wire; it is read to
|
||||
* populate auth_user/auth_password before connecting). */
|
||||
char* password_file;
|
||||
char* fastsync_server_path;
|
||||
/* --iconv=CONVERT_SPEC (protocol 2.16.0, rsync compatibility): convert the
|
||||
* charset of FILE NAMES at the wire boundary. CONVERT_SPEC is
|
||||
* "LOCAL[,REMOTE]": LOCAL is the charset of our own file names, REMOTE is
|
||||
* the remote side's charset and defaults to LOCAL. The sender converts
|
||||
* every path LOCAL->REMOTE before transmitting it; the receiver converts
|
||||
* every received path back REMOTE->LOCAL before creating/writing it. The
|
||||
* FULL SPEC crosses the wire as a trailing config-frame string so each end
|
||||
* derives its own LOCAL and the wire (REMOTE) charset symmetrically. NULL
|
||||
* (or "") means no conversion: identity with zero overhead. See charset.c
|
||||
* and the PROTOCOL_VERSION note below. */
|
||||
char* iconv_spec;
|
||||
char** exclude_patterns;
|
||||
int exclude_count;
|
||||
char** include_patterns;
|
||||
int include_count;
|
||||
unsigned long long max_size;
|
||||
unsigned long long min_size;
|
||||
unsigned long long max_alloc;
|
||||
bool use_incremental;
|
||||
bool ignore_times;
|
||||
bool size_only;
|
||||
bool use_delta;
|
||||
bool whole_file;
|
||||
/* -y/--fuzzy: when a file must be transferred and the destination holds no
|
||||
* usable file at the exact path, the receiver may reuse a SIMILAR-named
|
||||
* existing regular file in the same destination directory as the delta
|
||||
* basis so the sender transmits only the differences. Crosses the wire
|
||||
* (the receiver performs the candidate search); the CLI implies
|
||||
* --incremental + --delta because the similar-basis only matters on the
|
||||
* receiver-driven delta path. Off by default. */
|
||||
bool fuzzy;
|
||||
int modify_window;
|
||||
uint32_t delta_block_size;
|
||||
unsigned long long delta_max_file_size;
|
||||
bool use_tls;
|
||||
char* server_host;
|
||||
int server_port;
|
||||
@@ -170,18 +286,10 @@ typedef struct Config {
|
||||
/* --contimeout: connect()/accept timeout, transport layer only. */
|
||||
int contimeout;
|
||||
bool quiet;
|
||||
bool backup;
|
||||
char* backup_dir;
|
||||
bool stats;
|
||||
int max_depth;
|
||||
FILE* log_file;
|
||||
bool follow_symlinks;
|
||||
bool partial;
|
||||
|
||||
// Issue #120: Symlink handling
|
||||
bool copy_links;
|
||||
bool safe_links;
|
||||
bool copy_unsafe_links;
|
||||
/* Phase 4 symlink-trust. -k/--copy-dirlinks and --munge-links are
|
||||
* CLIENT/sender-side only (they decide how the SENDER scans and rewrites
|
||||
* symlinks; the receiver never reads them), so they never cross the wire.
|
||||
@@ -189,31 +297,6 @@ typedef struct Config {
|
||||
* symlink-to-directory as a directory) and CROSSES the wire along with
|
||||
* --munge-links (so the receiver knows to unmunge). */
|
||||
bool copy_dirlinks; /* client-only, sender-side (-k) */
|
||||
bool munge_links; /* crosses the wire */
|
||||
bool keep_dirlinks; /* crosses the wire (-K) */
|
||||
|
||||
// Issue #121: Extended metadata preservation
|
||||
bool preserve_hard_links;
|
||||
bool preserve_acls;
|
||||
bool preserve_xattrs;
|
||||
bool preserve_devices;
|
||||
bool preserve_sparse;
|
||||
/* Phase 4 special/devices: preserve special files (FIFOs, sockets) and device
|
||||
* nodes on the destination by recreating them (mknod/mkfifo) instead of
|
||||
* transferring content. preserve_specials mirrors rsync --specials (the
|
||||
* special-file half of -D); preserve_devices mirrors --devices (the device
|
||||
* half of -D); both CROSS the wire so the receiver knows a special/device
|
||||
* entry must be recreated rather than written as a regular file. */
|
||||
bool preserve_specials;
|
||||
/* --copy-devices: copy the CONTENT of a source device as an ordinary regular
|
||||
* file on the destination (rsync's non-privileged safe mode), instead of
|
||||
* recreating the device node. CROSSES the wire (receiver treats the entry as
|
||||
* a regular file, which is the default, so this is belt-and-braces). */
|
||||
bool copy_devices;
|
||||
/* --write-devices: write the received data directly INTO an existing device
|
||||
* node on the destination instead of creating a regular file. Dangeroud;
|
||||
* see RSYNC_COMPAT.md for the tight gating. CROSSES the wire. */
|
||||
bool write_devices;
|
||||
|
||||
// Issue #122: Output/logging options
|
||||
bool itemize_changes;
|
||||
@@ -223,57 +306,17 @@ typedef struct Config {
|
||||
int debug_level;
|
||||
bool list_only;
|
||||
bool human_readable;
|
||||
bool eight_bit_output;
|
||||
|
||||
// Issue #127: Transfer modes
|
||||
bool existing;
|
||||
bool ignore_existing;
|
||||
bool update;
|
||||
bool inplace;
|
||||
bool delay_updates;
|
||||
bool use_fsync;
|
||||
bool append;
|
||||
bool append_verify;
|
||||
/* --preallocate: allocates the destination file's full expected space up
|
||||
* front (before any data is written) so a transfer that would overflow disk
|
||||
* fails fast at allocation time and the file is laid out contiguously,
|
||||
* avoiding fragmentation. Receiver-side, crosses the wire. */
|
||||
bool preallocate;
|
||||
|
||||
// Issue #128: Extended delete options
|
||||
/* --delete-excluded: also delete destination entries that were excluded on
|
||||
* the source. Default (off) matches rsync: excluded paths are protected from
|
||||
* deletion. Crosses the wire (the sender encodes the choice by whether it
|
||||
* transmits a protected-prefix list with the keep-set manifest). */
|
||||
bool delete_excluded;
|
||||
bool delete_after;
|
||||
/* --max-delete=NUM: the receiver refuses to delete more than NUM entries per
|
||||
* run (all-or-nothing: when the extras would exceed NUM nothing is removed and
|
||||
* the transfer fails with a distinct error). -1 == no client limit (the
|
||||
* server hard bound MAX_SERVER_DELETE_COUNT still applies). */
|
||||
int max_delete;
|
||||
/* --ignore-errors (client-only, never serialized): a sender-side source I/O
|
||||
* error (an unreadable directory during the scan) normally aborts the run so
|
||||
* no deletion happens; with --ignore-errors the scan continues and the
|
||||
* (partial) keep-set is still transmitted so the deletion runs. */
|
||||
bool ignore_errors;
|
||||
/* --force (receiver-side): a regular file may replace a destination
|
||||
* directory by removing that (possibly non-empty, symlink-safe) directory
|
||||
* tree first, instead of failing the write. Crosses the wire. */
|
||||
bool force_delete;
|
||||
/* --ignore-missing-args (client-only, never serialized): a --files-from
|
||||
* entry that does not exist under the source is silently skipped instead of
|
||||
* failing the run. Sender-side only: nothing is sent for it and it never
|
||||
* enters the keep-set. Implied by --delete-missing-args. */
|
||||
bool ignore_missing_args;
|
||||
/* --delete-missing-args: implies --ignore-missing-args; additionally each
|
||||
* missing entry's destination mirror (computed like a present entry's wire
|
||||
* path) is deleted receiver-side. Crosses the wire and is gated by the
|
||||
* server's --allow-delete policy like --delete. rsync-parity: independent
|
||||
* of ordinary --delete processing (it does not imply --delete); a non-empty
|
||||
* directory mirror is only removed with --force or --delete in effect, and
|
||||
* the missing-args deletions are not counted toward --max-delete. */
|
||||
bool delete_missing_args;
|
||||
|
||||
// Issue #129: Advanced file selection. These fields are CLIENT-ONLY: they are
|
||||
// never serialized to the wire (the receiver must not learn them).
|
||||
@@ -283,23 +326,14 @@ typedef struct Config {
|
||||
bool from0; /* -0/--from0: NUL-delimited *-from files */
|
||||
bool cvs_exclude; /* -C/--cvs-exclude: standard CVS ignore set */
|
||||
bool per_dir_filter; /* -F: apply per-directory .rsync-filter files */
|
||||
bool prune_empty_dirs;
|
||||
bool one_file_system; /* -x/--one-file-system: do not cross filesystem boundaries */
|
||||
/* -R/--relative: crosses the wire; with --files-from listed entries keep
|
||||
* their bare relative destination path (no source-root mirror prefix). */
|
||||
bool relative;
|
||||
/* --no-implied-dirs: client-only. With -R + --files-from, refuse to place a
|
||||
* listed file whose ancestor directory is not itself explicitly listed. */
|
||||
bool no_implied_dirs;
|
||||
/* -d/--dirs: client-only. Transfer the directory entries named by the
|
||||
* source argument / --files-from list without recursing into contents. */
|
||||
bool dirs;
|
||||
/* --mkpath: crosses the wire. Tells the server to create the destination
|
||||
* root directory (and missing leading components below its authorized root)
|
||||
* at connection start instead of requiring it to already exist. */
|
||||
bool mkpath;
|
||||
|
||||
// Issue #130: Remote shell/connection options
|
||||
/* -e/--rsh: the remote-shell program used to establish the SSH transport.
|
||||
* NULL means the default "ssh". Client-only launch concern: NEVER crosses
|
||||
* the wire (it is not meaningful to the daemon/server handshake). */
|
||||
@@ -312,7 +346,6 @@ typedef struct Config {
|
||||
* concern: NEVER crosses the wire. */
|
||||
int outbuf;
|
||||
bool old_args;
|
||||
char* temp_dir;
|
||||
/* --remote-option=OPT (Phase 5, long form only): one or more extra command-line
|
||||
* options to append to the REMOTE server invocation over SSH. CLIENT-ONLY:
|
||||
* they are composed into the remote command line by ssh_build_remote_command()
|
||||
@@ -321,34 +354,6 @@ typedef struct Config {
|
||||
* do NOT cross the wire and are never parsed on the receiver process. */
|
||||
char** remote_options;
|
||||
int remote_option_count;
|
||||
/* Alternate basis directories, ordered by command-line appearance. Each
|
||||
* entry's type selects compare/copy/link behavior on an exact match. These
|
||||
* cross the wire so the receiver can consult them; they are interpreted
|
||||
* relative to the destination root and confined there. */
|
||||
BasisDest* basis_dirs;
|
||||
int basis_count;
|
||||
|
||||
// PR #174: Partial transfer resumption
|
||||
char* partial_dir;
|
||||
|
||||
// PR #178: Backup versioning
|
||||
char* suffix;
|
||||
|
||||
// PR #179: Delete policies
|
||||
bool delete_before;
|
||||
|
||||
/* rsync deletion-timing family (real from Phase 3). At most one of
|
||||
delete_before / delete_during / delete_delay / delete_after may be set, and
|
||||
only together with use_delete (the CLI implies --delete for each of them).
|
||||
delete_before and delete_during select the EARLY engine mode: the keep-set
|
||||
manifest is transmitted before any file data and extras are removed then,
|
||||
acknowledged, before the first data byte. delete_delay and delete_after
|
||||
select the LATE commit mode: extras are removed only after the whole
|
||||
transfer has succeeded (plain --delete keeps this mode). The exact
|
||||
semantics and the divergences from rsync are documented in RSYNC_COMPAT.md
|
||||
and in config_delete_timing_early() below. */
|
||||
bool delete_during;
|
||||
bool delete_delay;
|
||||
|
||||
// PR #181: IPv6 and bind address
|
||||
char* address;
|
||||
@@ -370,119 +375,19 @@ typedef struct Config {
|
||||
* MOTD is shown when a daemon offers one). */
|
||||
bool no_motd;
|
||||
|
||||
// PR #183: Checksum comparison
|
||||
bool checksum;
|
||||
|
||||
// PR #184: Compression algorithm negotiation
|
||||
char* compress_choice;
|
||||
char* chmod_spec;
|
||||
|
||||
/* --checksum-choice / --cc and --checksum-seed. checksum_algo is the id of
|
||||
* the whole-file content-digest algorithm used by the per-file --incremental
|
||||
* handshake (sender computes it, receiver compares it to skip unchanged
|
||||
* files) and by the basis-dir content verification. checksum_seed is passed
|
||||
* to xxHash64 (and to the delta block strong hash, low 32 bits); md5 has no
|
||||
* seed so it is ignored there. Both cross the wire: the receiver MUST hash
|
||||
* the on-disk old file with the same algorithm and seed to reach a matching
|
||||
* digest. Defaults (XXH64 / seed 0) reproduce the pre-existing behavior
|
||||
* byte-for-byte. */
|
||||
int checksum_algo; /* ChecksumAlgo, default CHECKSUM_ALGO_XXH64 */
|
||||
uint64_t checksum_seed; /* default 0 */
|
||||
|
||||
char** skip_compress_suffixes;
|
||||
int skip_compress_count;
|
||||
bool skip_compress_set;
|
||||
|
||||
// Issue #131: Identity mapping. These configure whether and how the receiver
|
||||
// applies ownership when it is actually preserved/applied. ALL of them cross
|
||||
// the wire (protocol 2.11.0) so the receiver resolves and applies ownership
|
||||
// with the exact policy the client requested. Plain -M/--preserve still does
|
||||
// NOT apply ownership (FastSync's deliberate conservative default); it is
|
||||
// only attempted when at least one of these is set (see identity.h).
|
||||
/* --numeric-ids: no name lookup, use the transmitted numeric ids raw. */
|
||||
bool numeric_ids;
|
||||
/* --chown USER (owner) override; IDENTITY_CURRENT = the receiver's euid. */
|
||||
bool chown_uid_set;
|
||||
int32_t chown_uid;
|
||||
/* --chown :GROUP (group) override; IDENTITY_CURRENT = the receiver's egid. */
|
||||
bool chown_gid_set;
|
||||
int32_t chown_gid;
|
||||
/* --usermap / --groupmap entries, in order (first match wins). */
|
||||
IdentityMap* usermap;
|
||||
int usermap_count;
|
||||
IdentityMap* groupmap;
|
||||
int groupmap_count;
|
||||
|
||||
/* --super / --no-super (P7 Wave E, protocol 2.18.0): receiver-side privilege
|
||||
* policy for super-user activities confined below the authorized receive
|
||||
* root. SUPER_MODE_AUTO (default) preserves the pre-existing best-effort
|
||||
* behavior: the confined super-user operation is ALWAYS attempted and an
|
||||
* unprivileged attempt is refused by the kernel and skipped per entry.
|
||||
* SUPER_MODE_ON (--super) explicitly REQUESTS those activities (char/block
|
||||
* device-node creation, --write-devices); it does NOT imply --numeric-ids and
|
||||
* never enables ownership application on its own. SUPER_MODE_OFF
|
||||
* (--no-super) FORBIDS them even when running as root. FastSync NEVER
|
||||
* elevates privileges (no setuid/seteuid/setgid) and never bypasses the
|
||||
* fd-relative confinement (file_open_secure_parent, O_NOFOLLOW, root checks);
|
||||
* --super only permits an attempt that is already confined. Crosses the wire
|
||||
* as a trailing int so the receiver can enforce the policy. See
|
||||
* privilege_super_permitted() and identity_ownership_requested() in
|
||||
* identity.h. */
|
||||
SuperMode super_mode;
|
||||
|
||||
// Receiver-side runtime staging registry for --delay-updates. Never sent
|
||||
// over the wire and never set on the sender side.
|
||||
DelayUpdatesContext* delay_context;
|
||||
|
||||
// Phase 4: metadata time preservation. -U/--atimes and -N/--crtimes capture
|
||||
// and transmit the source access / birth time (both sender and receiver
|
||||
// effect, so they CROSS the wire). --omit-dir-times/-O and
|
||||
// --omit-link-times/-J are receiver-side prefs (CROSS the wire). Their
|
||||
// exact capture/transmit/apply semantics are documented in RSYNC_COMPAT.md.
|
||||
/* -U/--atimes: preserve source access times on the destination. */
|
||||
bool preserve_atimes;
|
||||
/* -N/--crtimes: capture+transmit source birth time; see RSYNC_COMPAT for the
|
||||
* receiver not-applied divergence. */
|
||||
bool preserve_crtimes;
|
||||
/* -O/--omit-dir-times: do not apply mtimes to directories. */
|
||||
bool omit_dir_times;
|
||||
/* -J/--omit-link-times: do not apply times to symlinks. */
|
||||
bool omit_link_times;
|
||||
/* --open-noatime: CLIENT-ONLY (never crosses the wire). The sender opens
|
||||
* source files with O_NOATIME so reading for transfer does not bump the
|
||||
* source access time. */
|
||||
bool open_noatime;
|
||||
|
||||
// Phase 4: xattr / ACL / fake-super preservation.
|
||||
/* -X/--xattrs and -A/--acls toggle the sender's capture and the receiver's
|
||||
* application of per-file extended attributes (xattrs). Both cross the wire:
|
||||
* the sender only transmits the bounded, whitelisted attribute set it
|
||||
* captures and the receiver re-validates namespaces/sizes before applying
|
||||
* fd-relative. With neither set (the default) no xattr block is sent, so the
|
||||
* wire is byte-identical to prior protocol versions for unaffected runs. */
|
||||
/* true when preserve_xattrs || preserve_acls; the sender/receiver gate the
|
||||
* xattr wire block on this single flag. */
|
||||
bool use_xattrs;
|
||||
/* --fake-super: receiver-only. When set, each written file additionally gets
|
||||
* a reserved user.fastsync.stat xattr recording the source uid/gid/mode/mtime
|
||||
* so a later privileged restore could re-apply them. Crosses the wire. */
|
||||
bool fake_super;
|
||||
/* --copy-as=USER[:GROUP] (P7 Wave E, protocol 2.18.0). Safe-subset
|
||||
* implementation, a documented divergence from rsync's real identity switch:
|
||||
* the receiver does NOT change its process credentials (FastSync's receiver
|
||||
* is multithreaded, so a setuid/seteuid drop would be unsafe). Instead the
|
||||
* receiver FORCES the ownership of every entry it writes to copy_as_uid /
|
||||
* copy_as_gid through the existing confined, fd-relative identity path
|
||||
* (fchown/fchownat), which REQUIRES receiver privilege (root); an
|
||||
* unprivileged receiver REFUSES the whole transfer up front at the config
|
||||
* handshake (never a silent wrong-ownership result). All three fields CROSS
|
||||
* the wire as a trailing config-frame block so the receiver learns the
|
||||
* requested ids; see the PROTOCOL_VERSION note below. */
|
||||
bool copy_as_set;
|
||||
int32_t copy_as_uid;
|
||||
int32_t copy_as_gid;
|
||||
|
||||
// Phase 5: --trust-sender
|
||||
/* Long-form-only, receiver-local policy. rsync's --trust-sender tells the
|
||||
* receiving side to trust that the sender already produced a sane file list,
|
||||
* relaxing the receiver's own up-front re-validation of every incoming path.
|
||||
@@ -501,7 +406,6 @@ typedef struct Config {
|
||||
* default; only relaxes validation when explicitly requested. */
|
||||
bool trust_sender;
|
||||
|
||||
// Phase 6: --stop-after / --stop-at
|
||||
/* Client-only sender-side transfer stop deadlines. --stop-after=MINS stops
|
||||
* the transfer after a number of elapsed minutes (checked against
|
||||
* CLOCK_MONOTONIC so clock changes do not skew it); --stop-at=TIME stops at
|
||||
@@ -513,7 +417,6 @@ typedef struct Config {
|
||||
time_t stop_at; /* --stop-at=... absolute wall-clock deadline */
|
||||
bool stop_at_set; /* true when --stop-at was given */
|
||||
|
||||
// Phase 6: --write-batch / --only-write-batch / --read-batch
|
||||
/* Client-only residual-batch paths. A residual batch is a self-contained
|
||||
* single-file record of the whole source tree (full file images using the
|
||||
* chunk codec), independent of any live server. --write-batch=FILE runs the
|
||||
@@ -525,6 +428,196 @@ typedef struct Config {
|
||||
char* write_batch; /* --write-batch=FILE path, or NULL */
|
||||
char* only_write_batch; /* --only-write-batch=FILE path, or NULL */
|
||||
char* read_batch; /* --read-batch=FILE path, or NULL */
|
||||
|
||||
/* ===================================================================
|
||||
* Serialized wire fields. Their members, defaults and send/receive
|
||||
* sequence are generated from the CONFIG_WIRE_*_FIELDS table above (the
|
||||
* single source of truth); they are declared here in exact wire order.
|
||||
* The per-field notes were moved here from their original positions and
|
||||
* are listed in wire order.
|
||||
* =================================================================== */
|
||||
/* copy_links */
|
||||
// Issue #120: Symlink handling
|
||||
/* preserve_hard_links */
|
||||
// Issue #121: Extended metadata preservation
|
||||
/* preserve_specials */
|
||||
/* Phase 4 special/devices: preserve special files (FIFOs, sockets) and device
|
||||
* nodes on the destination by recreating them (mknod/mkfifo) instead of
|
||||
* transferring content. preserve_specials mirrors rsync --specials (the
|
||||
* special-file half of -D); preserve_devices mirrors --devices (the device
|
||||
* half of -D); both CROSS the wire so the receiver knows a special/device
|
||||
* entry must be recreated rather than written as a regular file. */
|
||||
/* copy_devices */
|
||||
/* --copy-devices: copy the CONTENT of a source device as an ordinary regular
|
||||
* file on the destination (rsync's non-privileged safe mode), instead of
|
||||
* recreating the device node. CROSSES the wire (receiver treats the entry as
|
||||
* a regular file, which is the default, so this is belt-and-braces). */
|
||||
/* write_devices */
|
||||
/* --write-devices: write the received data directly INTO an existing device
|
||||
* node on the destination instead of creating a regular file. Dangeroud;
|
||||
* see RSYNC_COMPAT.md for the tight gating. CROSSES the wire. */
|
||||
/* existing */
|
||||
// Issue #127: Transfer modes
|
||||
/* delete_excluded */
|
||||
/* --delete-excluded: also delete destination entries that were excluded on
|
||||
* the source. Default (off) matches rsync: excluded paths are protected from
|
||||
* deletion. Crosses the wire (the sender encodes the choice by whether it
|
||||
* transmits a protected-prefix list with the keep-set manifest). */
|
||||
/* force_delete */
|
||||
/* --force (receiver-side): a regular file may replace a destination
|
||||
* directory by removing that (possibly non-empty, symlink-safe) directory
|
||||
* tree first, instead of failing the write. Crosses the wire. */
|
||||
/* delete_missing_args */
|
||||
/* --delete-missing-args: implies --ignore-missing-args; additionally each
|
||||
* missing entry's destination mirror (computed like a present entry's wire
|
||||
* path) is deleted receiver-side. Crosses the wire and is gated by the
|
||||
* server's --allow-delete policy like --delete. rsync-parity: independent
|
||||
* of ordinary --delete processing (it does not imply --delete); a non-empty
|
||||
* directory mirror is only removed with --force or --delete in effect, and
|
||||
* the missing-args deletions are not counted toward --max-delete. */
|
||||
/* preallocate */
|
||||
/* --preallocate: allocates the destination file's full expected space up
|
||||
* front (before any data is written) so a transfer that would overflow disk
|
||||
* fails fast at allocation time and the file is laid out contiguously,
|
||||
* avoiding fragmentation. Receiver-side, crosses the wire. */
|
||||
/* max_delete */
|
||||
/* --max-delete=NUM: the receiver refuses to delete more than NUM entries per
|
||||
* run (all-or-nothing: when the extras would exceed NUM nothing is removed and
|
||||
* the transfer fails with a distinct error). -1 == no client limit (the
|
||||
* server hard bound MAX_SERVER_DELETE_COUNT still applies). */
|
||||
/* relative */
|
||||
/* -R/--relative: crosses the wire; with --files-from listed entries keep
|
||||
* their bare relative destination path (no source-root mirror prefix). */
|
||||
/* mkpath */
|
||||
/* --mkpath: crosses the wire. Tells the server to create the destination
|
||||
* root directory (and missing leading components below its authorized root)
|
||||
* at connection start instead of requiring it to already exist. */
|
||||
/* delete_during */
|
||||
/* rsync deletion-timing family (real from Phase 3). At most one of
|
||||
delete_before / delete_during / delete_delay / delete_after may be set, and
|
||||
only together with use_delete (the CLI implies --delete for each of them).
|
||||
delete_before and delete_during select the EARLY engine mode: the keep-set
|
||||
manifest is transmitted before any file data and extras are removed then,
|
||||
acknowledged, before the first data byte. delete_delay and delete_after
|
||||
select the LATE commit mode: extras are removed only after the whole
|
||||
transfer has succeeded (plain --delete keeps this mode). The exact
|
||||
semantics and the divergences from rsync are documented in RSYNC_COMPAT.md
|
||||
and in config_delete_timing_early() below. */
|
||||
/* partial_dir */
|
||||
// PR #174: Partial transfer resumption
|
||||
/* suffix */
|
||||
// PR #178: Backup versioning
|
||||
/* delete_before */
|
||||
// PR #179: Delete policies
|
||||
/* checksum */
|
||||
// PR #183: Checksum comparison
|
||||
/* compress_choice */
|
||||
// PR #184: Compression algorithm negotiation
|
||||
/* basis_dirs */
|
||||
/* Alternate basis directories, ordered by command-line appearance. Each
|
||||
* entry's type selects compare/copy/link behavior on an exact match. These
|
||||
* cross the wire so the receiver can consult them; they are interpreted
|
||||
* relative to the destination root and confined there. */
|
||||
/* fuzzy */
|
||||
/* -y/--fuzzy: when a file must be transferred and the destination holds no
|
||||
* usable file at the exact path, the receiver may reuse a SIMILAR-named
|
||||
* existing regular file in the same destination directory as the delta
|
||||
* basis so the sender transmits only the differences. Crosses the wire
|
||||
* (the receiver performs the candidate search); the CLI implies
|
||||
* --incremental + --delta because the similar-basis only matters on the
|
||||
* receiver-driven delta path. Off by default. */
|
||||
/* checksum_algo / checksum_seed */
|
||||
/* --checksum-choice / --cc and --checksum-seed. checksum_algo is the id of
|
||||
* the whole-file content-digest algorithm used by the per-file --incremental
|
||||
* handshake (sender computes it, receiver compares it to skip unchanged
|
||||
* files) and by the basis-dir content verification. checksum_seed is passed
|
||||
* to xxHash64 (and to the delta block strong hash, low 32 bits); md5 has no
|
||||
* seed so it is ignored there. Both cross the wire: the receiver MUST hash
|
||||
* the on-disk old file with the same algorithm and seed to reach a matching
|
||||
* digest. */
|
||||
/* munge_links / keep_dirlinks */
|
||||
/* Phase 4 symlink-trust: both cross the wire (the receiver unmunges symlink
|
||||
* targets and, with -K, follows an in-root destination symlink-to-directory);
|
||||
* -k/--copy-dirlinks is sender-only and is never serialized. */
|
||||
/* numeric_ids */
|
||||
/* --numeric-ids: no name lookup, use the transmitted numeric ids raw. */
|
||||
/* chown_uid_set */
|
||||
/* --chown USER (owner) override; IDENTITY_CURRENT = the receiver's euid. */
|
||||
/* chown_gid_set */
|
||||
/* --chown :GROUP (group) override; IDENTITY_CURRENT = the receiver's egid. */
|
||||
/* usermap */
|
||||
/* --usermap / --groupmap entries, in order (first match wins). */
|
||||
/* preserve_atimes */
|
||||
/* -U/--atimes: preserve source access times on the destination. */
|
||||
/* preserve_crtimes */
|
||||
/* -N/--crtimes: capture+transmit source birth time; see RSYNC_COMPAT for the
|
||||
* receiver not-applied divergence. */
|
||||
/* omit_dir_times */
|
||||
/* -O/--omit-dir-times: do not apply mtimes to directories. */
|
||||
/* omit_link_times */
|
||||
/* -J/--omit-link-times: do not apply times to symlinks. */
|
||||
/* fake_super */
|
||||
/* --fake-super: receiver-only. When set, each written file additionally gets
|
||||
* a reserved user.fastsync.stat xattr recording the source uid/gid/mode/mtime
|
||||
* so a later privileged restore could re-apply them. Crosses the wire. */
|
||||
/* module */
|
||||
/* Daemon module selection (Wave A, protocol 2.15.0). Client-composed from a
|
||||
* host::module/path destination; NULL or "" means "no module" (the ordinary
|
||||
* standalone-server path). Crosses the wire as a trailing config-frame
|
||||
* string so the daemon can look the module up in its own config and confine
|
||||
* the connection to the module's root (never a client-chosen root). */
|
||||
/* auth_user */
|
||||
/* Daemon password authentication (A7 remediation, protocol 2.19.0).
|
||||
* Client-composed from a --password-file whose first meaningful line is
|
||||
* `user:password`: the client sends ONLY the username in the config frame
|
||||
* (auth_user); the literal password is kept in auth_password CLIENT-SIDE for
|
||||
* the duration of the SCRAM challenge/response and is NEVER serialized. Both
|
||||
* are NULL when the client has no credentials to present; a module WITHOUT
|
||||
* `auth users` stays open and the server ignores any credentials that do
|
||||
* arrive (the client sends them opportunistically and the server decides). */
|
||||
/* iconv_spec */
|
||||
/* --iconv=CONVERT_SPEC (protocol 2.16.0, rsync compatibility): convert the
|
||||
* charset of FILE NAMES at the wire boundary. CONVERT_SPEC is
|
||||
* "LOCAL[,REMOTE]": LOCAL is the charset of our own file names, REMOTE is
|
||||
* the remote side's charset and defaults to LOCAL. The sender converts
|
||||
* every path LOCAL->REMOTE before transmitting it; the receiver converts
|
||||
* every received path back REMOTE->LOCAL before creating/writing it. The
|
||||
* FULL SPEC crosses the wire as a trailing config-frame string so each end
|
||||
* derives its own LOCAL and the wire (REMOTE) charset symmetrically. NULL
|
||||
* (or "") means no conversion: identity with zero overhead. See charset.c
|
||||
* and the PROTOCOL_VERSION note below. */
|
||||
/* super_mode */
|
||||
/* --super / --no-super (P7 Wave E, protocol 2.18.0): receiver-side privilege
|
||||
* policy for super-user activities confined below the authorized receive
|
||||
* root. SUPER_MODE_AUTO (default) preserves the pre-existing best-effort
|
||||
* behavior: the confined super-user operation is ALWAYS attempted and an
|
||||
* unprivileged attempt is refused by the kernel and skipped per entry.
|
||||
* SUPER_MODE_ON (--super) explicitly REQUESTS those activities (char/block
|
||||
* device-node creation, --write-devices); it does NOT imply --numeric-ids and
|
||||
* never enables ownership application on its own. SUPER_MODE_OFF
|
||||
* (--no-super) FORBIDS them even when running as root. FastSync NEVER
|
||||
* elevates privileges (no setuid/seteuid/setgid) and never bypasses the
|
||||
* fd-relative confinement (file_open_secure_parent, O_NOFOLLOW, root checks);
|
||||
* --super only permits an attempt that is already confined. Crosses the wire
|
||||
* as a trailing int so the receiver can enforce the policy. See
|
||||
* privilege_super_permitted() and identity_ownership_requested() in
|
||||
* identity.h. */
|
||||
/* copy_as_set */
|
||||
/* --copy-as=USER[:GROUP] (P7 Wave E, protocol 2.18.0). Safe-subset
|
||||
* implementation, a documented divergence from rsync's real identity switch:
|
||||
* the receiver does NOT change its process credentials (FastSync's receiver
|
||||
* is multithreaded, so a setuid/seteuid drop would be unsafe). Instead the
|
||||
* receiver FORCES the ownership of every entry it writes to copy_as_uid /
|
||||
* copy_as_gid through the existing confined, fd-relative identity path
|
||||
* (fchown/fchownat), which REQUIRES receiver privilege (root); an
|
||||
* unprivileged receiver REFUSES the whole transfer up front at the config
|
||||
* handshake (never a silent wrong-ownership result). All three fields CROSS
|
||||
* the wire as a trailing config-frame block so the receiver learns the
|
||||
* requested ids; see the PROTOCOL_VERSION note below. */
|
||||
|
||||
#define CONFIG_STRUCT_MEMBER(name, ctype, def, kind) ctype name;
|
||||
CONFIG_WIRE_FIELDS(CONFIG_STRUCT_MEMBER)
|
||||
#undef CONFIG_STRUCT_MEMBER
|
||||
} Config;
|
||||
|
||||
/* Phase 5 (remote-option wave): 2.13.0 -> 2.14.0.
|
||||
@@ -699,6 +792,10 @@ void config_delete(Config* config);
|
||||
void config_burn_auth(Config* config);
|
||||
|
||||
bool config_send(int file_descriptor, const Config* config);
|
||||
/* Emit the config frame BODY (every serialized field, in wire order) without
|
||||
* the trailing STATUS_OK handshake. config_send() is this plus the handshake;
|
||||
* the wire-compatibility golden test uses it to hash the exact byte stream. */
|
||||
bool config_send_wire_block(int file_descriptor, const Config* config);
|
||||
Config* config_receive(int file_descriptor);
|
||||
bool config_is_remote_dest(const char* s);
|
||||
void config_parse_ssh_dest(Config* config);
|
||||
|
||||
Reference in New Issue
Block a user