feat: basis-dir config/CLI for --compare-dest/--copy-dest/--link-dest

Replace the vestigial single compare_dest/copy_dest/link_dest Config fields with
an ordered BasisDest list (type + path per entry) that is serialized to the
receiver and interpreted relative to the destination root.  Paths must be
relative with no '.'/'..' components (confined like --backup-dir); trailing
slashes are normalized.  Wire layout changes, so PROTOCOL_VERSION -> 2.8.0.

CLI: each flag is parsed in both --flag=DIR and --flag DIR forms, is
repeatable, and keeps command-line order as basis priority.  Supplying any
basis dir implies --incremental (and therefore metadata) on the sender because
the unchanged decision is receiver-side; combining basis dirs with -s chunk
serialization is rejected in validate_config.  Usage text updated.
This commit is contained in:
2026-09-06 18:47:20 +02:00
parent 4cf34026e7
commit df890f76ea
5 changed files with 206 additions and 12 deletions
+32 -4
View File
@@ -12,6 +12,22 @@ typedef enum { TRANSPORT_TCP, TRANSPORT_SSH } TransportType;
Config can carry it; the concrete type lives in delay_updates.h. */
typedef struct DelayUpdatesContext DelayUpdatesContext;
/* Alternate basis-directory modes (--compare-dest / --copy-dest /
* --link-dest). Each flag adds one entry to the ordered Config->basis_dirs
* list; the receiver consults entries in command-line order and stops at the
* first exact match, mirroring rsync's basis-dir priority rules. */
typedef enum {
BASIS_DEST_NONE = 0,
BASIS_DEST_COMPARE, /* compare only: never copies, never materializes */
BASIS_DEST_COPY, /* local copy of the matched basis file */
BASIS_DEST_LINK /* hard link to the matched basis file */
} BasisDestType;
typedef struct BasisDest {
BasisDestType type;
char* path; /* relative to the destination root (receiver-confined) */
} BasisDest;
typedef struct Config {
char* version;
char* send_directory;
@@ -134,9 +150,12 @@ typedef struct Config {
char* rsync_path;
bool old_args;
char* temp_dir;
char* compare_dest;
char* copy_dest;
char* link_dest;
/* 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;
@@ -174,8 +193,10 @@ typedef struct Config {
DelayUpdatesContext* delay_context;
} Config;
#define PROTOCOL_VERSION "2.7.0"
#define PROTOCOL_VERSION "2.8.0"
#define DEFAULT_CHUNK_SIZE (10 * 1024 * 1024)
/* Upper bound on total basis-dir entries (rsync caps --link-dest at 20). */
#define MAX_BASIS_DIRS 64
Config* config_create(void);
void config_delete(Config* config);
@@ -184,4 +205,11 @@ Config* config_receive(int file_descriptor);
bool config_is_remote_dest(const char* s);
void config_parse_ssh_dest(Config* config);
/* True when at least one --compare-dest/--copy-dest/--link-dest was set. */
bool config_has_basis(const Config* config);
/* Append one basis-dir entry. Returns 0 on success, -1 on allocation failure. */
int config_basis_append(Config* config, BasisDestType type, const char* path);
/* Validate a client-provided basis-dir path (relative, confined, non-empty). */
bool config_basis_path_valid(const char* path);
#endif