rsync 3.4.1 drop-in parity (#285-#297) + parity completion (protocol 2.26.0) #298
+102
-33
@@ -148,47 +148,96 @@ static int set_positive_int_option(int* dest, const char* value, const char* opt
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Set and validate the compression algorithm selected by the client. rsync
|
/* Set and validate the compression algorithm selected by the client. rsync
|
||||||
* 3.4.1 can be built with zstd, none, lz4, zlibx, zlib and auto; FastSync only
|
* 3.4.1 can be built with zstd, none, lz4, zlibx, zlib and auto; all of those
|
||||||
* implements zstd (and no compression). "auto" is accepted as the default
|
* names are accepted and mapped to a real codec here. "auto" resolves through
|
||||||
* zstd choice; any other rsync choice is rejected by name instead of being
|
* FastSync's compiled-in preference order (rsync 3.4.1's list). An unknown
|
||||||
* silently accepted and ignored. */
|
* name is a hard error with rsync's exit code 4, never a silent no-op. */
|
||||||
static int set_compression_choice(Config* config, const char* value) {
|
static int set_compression_choice(Config* config, const char* value) {
|
||||||
/* rsync's "auto" is normalized to the canonical "zstd" at parse time (like
|
if (!value) {
|
||||||
--checksum-choice=auto), so the value that crosses the wire is always one
|
config->cli_exit_code = 4;
|
||||||
the receiver accepts. */
|
|
||||||
const char* canonical = strcmp(value, "auto") == 0 ? "zstd" : value;
|
|
||||||
if (strcmp(canonical, "zstd") != 0 && strcmp(canonical, "none") != 0) {
|
|
||||||
log_message(LOG_LEVEL_ERROR,
|
|
||||||
"--compress-choice '%s' is not implemented; FastSync supports zstd, none or auto "
|
|
||||||
"(rsync's lz4/zlib/zlibx are rejected, never silently ignored)",
|
|
||||||
value);
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
int algo;
|
||||||
|
if (strcasecmp(value, "auto") == 0)
|
||||||
|
algo = (int)compression_negotiate_default();
|
||||||
|
else
|
||||||
|
algo = compression_algo_from_name(value);
|
||||||
|
if (algo < 0) {
|
||||||
|
log_message(LOG_LEVEL_ERROR,
|
||||||
|
"--compress-choice '%s' is not a supported algorithm; FastSync supports zstd, "
|
||||||
|
"lz4, zlib, zlibx, none or auto",
|
||||||
|
value);
|
||||||
|
config->cli_exit_code = 4;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
const char* canonical = compression_algo_name((CompressionAlgo)algo);
|
||||||
if (set_string_option(&config->compress_choice, canonical, "--compress-choice") != 0)
|
if (set_string_option(&config->compress_choice, canonical, "--compress-choice") != 0)
|
||||||
return -1;
|
return -1;
|
||||||
config->use_compression = strcmp(canonical, "none") != 0;
|
config->compression_algo = algo;
|
||||||
|
config->use_compression = (algo != (int)COMPRESSION_ALGO_NONE);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Validate and store the --checksum-choice/--cc algorithm. Only the algorithms
|
/* Store one algorithm name into *out. Returns 0 for a valid name, 1 for
|
||||||
* the engine genuinely supports are accepted (xxh64/xxhash, xxh3, xxh128, md5);
|
* "auto" (caller resolves it), -1 for an unknown/too-long name. */
|
||||||
* rsync's compiled-in choices that FastSync does not implement (md4, sha1,
|
static int resolve_checksum_name(const char* name, size_t len, int* out) {
|
||||||
* none) and the two-name transfer/pre-transfer syntax are a clear error, never
|
char buf[64];
|
||||||
* a silent no-op. "auto" (rsync's default automatic choice) selects FastSync's
|
if (len == 0 || len >= sizeof(buf))
|
||||||
* default algorithm. */
|
return -1;
|
||||||
static int set_checksum_choice(Config* config, const char* value) {
|
memcpy(buf, name, len);
|
||||||
if (strcasecmp(value, "auto") == 0)
|
buf[len] = '\0';
|
||||||
|
if (strcasecmp(buf, "auto") == 0)
|
||||||
|
return 1;
|
||||||
|
int algo = checksum_algo_from_name(buf);
|
||||||
|
if (algo < 0)
|
||||||
|
return -1;
|
||||||
|
*out = algo;
|
||||||
return 0;
|
return 0;
|
||||||
int algo = checksum_algo_from_name(value);
|
}
|
||||||
if (algo < 0) {
|
|
||||||
log_message(LOG_LEVEL_ERROR,
|
/* Validate and store the --checksum-choice/--cc algorithm. rsync 3.4.1 accepts
|
||||||
"--checksum-choice '%s' is not implemented; FastSync supports xxh64 (or xxhash), "
|
* a single name (used for both the transfer and pre-transfer checksums) or the
|
||||||
"xxh3, xxh128, md5 or auto (rsync's md4/sha1/none and the two-name "
|
* two-name "TRANSFER,PRE-TRANSFER" form (only one comma is significant). The
|
||||||
"transfer,pre-transfer form are rejected, never silently ignored)",
|
* pre-transfer half is FastSync's whole-file digest; the transfer half is
|
||||||
value);
|
* validated for parity and, when "none", forces --whole-file like rsync. An
|
||||||
|
* unknown name (including an empty half or a second comma) is exit 4. "auto"
|
||||||
|
* resolves to FastSync's negotiated default (xxh128). */
|
||||||
|
static int set_checksum_choice(Config* config, const char* value) {
|
||||||
|
if (!value) {
|
||||||
|
config->cli_exit_code = 4;
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
config->checksum_algo = algo;
|
const char* comma = strchr(value, ',');
|
||||||
|
const char* name1 = value;
|
||||||
|
size_t len1 = comma ? (size_t)(comma - value) : strlen(value);
|
||||||
|
const char* name2 = comma ? comma + 1 : NULL;
|
||||||
|
size_t len2 = name2 ? strlen(name2) : 0;
|
||||||
|
|
||||||
|
int transfer = -1;
|
||||||
|
int pre = -1;
|
||||||
|
int rc1 = resolve_checksum_name(name1, len1, &transfer);
|
||||||
|
int rc2 = name2 ? resolve_checksum_name(name2, len2, &pre) : 1;
|
||||||
|
if (rc1 < 0 || rc2 < 0) {
|
||||||
|
log_message(LOG_LEVEL_ERROR,
|
||||||
|
"--checksum-choice '%s' is invalid; FastSync supports xxh64 (or xxhash), xxh128, "
|
||||||
|
"xxh3, md5, md4, sha1, none or auto, optionally as 'transfer,pre-transfer'",
|
||||||
|
value);
|
||||||
|
config->cli_exit_code = 4;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
ChecksumAlgo negotiated = checksum_negotiate_default();
|
||||||
|
if (rc1 == 1)
|
||||||
|
transfer = (int)negotiated;
|
||||||
|
if (!name2)
|
||||||
|
pre = transfer;
|
||||||
|
else if (rc2 == 1)
|
||||||
|
pre = (int)negotiated;
|
||||||
|
|
||||||
|
config->checksum_algo = pre;
|
||||||
|
config->checksum_transfer_algo = transfer;
|
||||||
|
/* rsync: "none" for the transfer checksum forces --whole-file. */
|
||||||
|
if (transfer == (int)CHECKSUM_ALGO_NONE)
|
||||||
|
config->whole_file = true;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2263,8 +2312,23 @@ static bool cli_handle_outbuf_option(CliParseCtx* ctx) {
|
|||||||
* -1 on error. */
|
* -1 on error. */
|
||||||
static int cli_finalize_config(Config* config, bool verbose, bool no_delta, bool no_incremental) {
|
static int cli_finalize_config(Config* config, bool verbose, bool no_delta, bool no_incremental) {
|
||||||
set_log_level(config->quiet ? LOG_LEVEL_ERROR : (verbose ? LOG_LEVEL_DEBUG : LOG_LEVEL_WARNING));
|
set_log_level(config->quiet ? LOG_LEVEL_ERROR : (verbose ? LOG_LEVEL_DEBUG : LOG_LEVEL_WARNING));
|
||||||
if (config->compress_choice)
|
if (config->compress_choice) {
|
||||||
config->use_compression = strcmp(config->compress_choice, "none") != 0;
|
int algo = compression_algo_from_name(config->compress_choice);
|
||||||
|
if (algo >= 0) {
|
||||||
|
config->compression_algo = algo;
|
||||||
|
config->use_compression = (algo != (int)COMPRESSION_ALGO_NONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (config->use_compression && config->compression_algo == (int)COMPRESSION_ALGO_NONE)
|
||||||
|
config->compression_algo = (int)compression_negotiate_default();
|
||||||
|
/* rsync parity: "none" as the pre-transfer checksum cannot be combined with
|
||||||
|
* --checksum (exit 4). The check runs here because --checksum may appear on
|
||||||
|
* either side of --checksum-choice. */
|
||||||
|
if (config->checksum && config->checksum_algo == (int)CHECKSUM_ALGO_NONE) {
|
||||||
|
log_message(LOG_LEVEL_ERROR, "Invalid checksum-choice for --checksum: none");
|
||||||
|
config->cli_exit_code = 4;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
/* rsync randomizes the checksum seed for every transfer when the user did not
|
/* rsync randomizes the checksum seed for every transfer when the user did not
|
||||||
* supply one (a seed of 0, including an explicit --checksum-seed=0), using
|
* supply one (a seed of 0, including an explicit --checksum-seed=0), using
|
||||||
@@ -2692,7 +2756,7 @@ int main(int argc, char* argv[]) {
|
|||||||
int parse_ret = parse_args(config, argc, argv, positional_args, &positional_count);
|
int parse_ret = parse_args(config, argc, argv, positional_args, &positional_count);
|
||||||
if (parse_ret != 0) {
|
if (parse_ret != 0) {
|
||||||
if (parse_ret < 0)
|
if (parse_ret < 0)
|
||||||
exit_code = 1;
|
exit_code = config->cli_exit_code ? config->cli_exit_code : 1;
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2782,6 +2846,11 @@ int main(int argc, char* argv[]) {
|
|||||||
goto cleanup;
|
goto cleanup;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Install the negotiated codec for this process before any transfer thread
|
||||||
|
* is spawned; the compressed frames are self-describing, so the receiver's
|
||||||
|
* decompressor does not need this, but the sender compressor does. */
|
||||||
|
compression_set_algo((CompressionAlgo)config->compression_algo);
|
||||||
|
|
||||||
/* --iconv: install the sender-side local->wire conversion before any path is
|
/* --iconv: install the sender-side local->wire conversion before any path is
|
||||||
scanned or serialized (the scanner and the chunk/data path read windows are
|
scanned or serialized (the scanner and the chunk/data path read windows are
|
||||||
all driven from this process, so one global initialization covers every
|
all driven from this process, so one global initialization covers every
|
||||||
|
|||||||
+6
-5
@@ -136,10 +136,10 @@ void print_usage(void) {
|
|||||||
printf(" --link-dest <dir> Like --copy-dest, but hard-links the unchanged file from DIR\n");
|
printf(" --link-dest <dir> Like --copy-dest, but hard-links the unchanged file from DIR\n");
|
||||||
printf(" into the destination (repeatable; earlier DIRs win)\n");
|
printf(" into the destination (repeatable; earlier DIRs win)\n");
|
||||||
printf(" --checksum-choice, --cc <alg> Whole-file checksum algorithm for --incremental/\n");
|
printf(" --checksum-choice, --cc <alg> Whole-file checksum algorithm for --incremental/\n");
|
||||||
printf(" --checksum compares. Accepted: xxh64 (aka xxhash), xxh3,\n");
|
printf(" --checksum compares. Accepted: xxh128 (default), xxh3, xxh64\n");
|
||||||
printf(" xxh128, md5, or auto (default xxh64). rsync choices FastSync\n");
|
printf(" (aka xxhash), md5, md4, sha1, or none. A two-name\n");
|
||||||
printf(" does not implement (md4, sha1, none) and the two-name\n");
|
printf(" 'transfer,pre-transfer' form is accepted like rsync; 'none' as\n");
|
||||||
printf(" transfer,pre-transfer form are rejected by name\n");
|
printf(" the pre-transfer algorithm is rejected with --checksum\n");
|
||||||
printf(" --checksum-seed <num> Seed for the whole-file xxHash digest (and the delta\n");
|
printf(" --checksum-seed <num> Seed for the whole-file xxHash digest (and the delta\n");
|
||||||
printf(" block strong hash, low 32 bits); md5 ignores the seed. A seed\n");
|
printf(" block strong hash, low 32 bits); md5 ignores the seed. A seed\n");
|
||||||
printf(" of 0 (the default) is randomized per transfer, exactly like\n");
|
printf(" of 0 (the default) is randomized per transfer, exactly like\n");
|
||||||
@@ -166,7 +166,8 @@ void print_usage(void) {
|
|||||||
printf(" SSH argv is already built injection-safe)\n");
|
printf(" SSH argv is already built injection-safe)\n");
|
||||||
printf(" --sendfile Enable sendfile zero-copy (TCP only; long form only;\n");
|
printf(" --sendfile Enable sendfile zero-copy (TCP only; long form only;\n");
|
||||||
printf(" -f is bound to --filter, not --sendfile)\n");
|
printf(" -f is bound to --filter, not --sendfile)\n");
|
||||||
printf(" --compress-choice <alg> Compression algorithm (default: zstd)\n");
|
printf(" --compress-choice <alg> Compression algorithm: zstd (default), lz4, zlib,\n");
|
||||||
|
printf(" zlibx, none, or auto\n");
|
||||||
printf(" --zc <alg> Alias for --compress-choice\n");
|
printf(" --zc <alg> Alias for --compress-choice\n");
|
||||||
printf(" -v, --verbose Enable debug logging\n");
|
printf(" -v, --verbose Enable debug logging\n");
|
||||||
printf(" -q, --quiet Suppress non-error output\n");
|
printf(" -q, --quiet Suppress non-error output\n");
|
||||||
|
|||||||
@@ -741,6 +741,10 @@ void handler(int file_descriptor) {
|
|||||||
* received config. */
|
* received config. */
|
||||||
if (gate_ctx.super_mode_override != -1)
|
if (gate_ctx.super_mode_override != -1)
|
||||||
config->super_mode = (SuperMode)gate_ctx.super_mode_override;
|
config->super_mode = (SuperMode)gate_ctx.super_mode_override;
|
||||||
|
/* Install the codec this connection negotiated before the receiver/writer
|
||||||
|
* threads start (the server forks per connection, so the process-global
|
||||||
|
* codec is private to this session). */
|
||||||
|
compression_set_algo((CompressionAlgo)config->compression_algo);
|
||||||
/* If the client requested ownership but the effective super mode forbids it
|
/* If the client requested ownership but the effective super mode forbids it
|
||||||
* (operator --no-super, a privileged standalone receiver's secure default, or
|
* (operator --no-super, a privileged standalone receiver's secure default, or
|
||||||
* a daemon module without `client owner = yes`), say so ONCE per connection so
|
* a daemon module without `client owner = yes`), say so ONCE per connection so
|
||||||
|
|||||||
+66
-18
@@ -14,6 +14,7 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <strings.h>
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
|
||||||
@@ -67,6 +68,8 @@ static void config_set_defaults(Config* config) {
|
|||||||
config->human_readable = false;
|
config->human_readable = false;
|
||||||
config->ignore_errors = false;
|
config->ignore_errors = false;
|
||||||
config->ignore_missing_args = false;
|
config->ignore_missing_args = false;
|
||||||
|
config->checksum_transfer_algo = CHECKSUM_ALGO_DEFAULT;
|
||||||
|
config->cli_exit_code = 0;
|
||||||
config->filters = NULL;
|
config->filters = NULL;
|
||||||
config->files_from = NULL;
|
config->files_from = NULL;
|
||||||
config->files_from_set = NULL;
|
config->files_from_set = NULL;
|
||||||
@@ -196,12 +199,13 @@ static bool validate_received_config(const Config* config) {
|
|||||||
valid_wire_bool(config->partial) && valid_wire_bool(config->delete_before) &&
|
valid_wire_bool(config->partial) && valid_wire_bool(config->delete_before) &&
|
||||||
valid_wire_bool(config->checksum) && valid_wire_bool(config->eight_bit_output) &&
|
valid_wire_bool(config->checksum) && valid_wire_bool(config->eight_bit_output) &&
|
||||||
valid_wire_bool(config->dry_run) && checksum_algo_valid(config->checksum_algo) &&
|
valid_wire_bool(config->dry_run) && checksum_algo_valid(config->checksum_algo) &&
|
||||||
identity_wire_valid(config) && valid_wire_bool(config->preserve_atimes) &&
|
compression_algo_valid(config->compression_algo) && identity_wire_valid(config) &&
|
||||||
valid_wire_bool(config->preserve_crtimes) && valid_wire_bool(config->omit_dir_times) &&
|
valid_wire_bool(config->preserve_atimes) && valid_wire_bool(config->preserve_crtimes) &&
|
||||||
valid_wire_bool(config->omit_link_times) && valid_wire_bool(config->preserve_perms) &&
|
valid_wire_bool(config->omit_dir_times) && valid_wire_bool(config->omit_link_times) &&
|
||||||
valid_wire_bool(config->preserve_times) && valid_wire_bool(config->preserve_owner) &&
|
valid_wire_bool(config->preserve_perms) && valid_wire_bool(config->preserve_times) &&
|
||||||
valid_wire_bool(config->preserve_group) && valid_wire_bool(config->munge_links) &&
|
valid_wire_bool(config->preserve_owner) && valid_wire_bool(config->preserve_group) &&
|
||||||
valid_wire_bool(config->keep_dirlinks) && valid_wire_bool(config->fake_super) &&
|
valid_wire_bool(config->munge_links) && valid_wire_bool(config->keep_dirlinks) &&
|
||||||
|
valid_wire_bool(config->fake_super) &&
|
||||||
(!config->copy_as_set || (config->copy_as_uid >= 0 && config->copy_as_gid >= 0)) &&
|
(!config->copy_as_set || (config->copy_as_uid >= 0 && config->copy_as_gid >= 0)) &&
|
||||||
(!config->use_compression ||
|
(!config->use_compression ||
|
||||||
(config->compression_level >= 1 && config->compression_level <= 22)) &&
|
(config->compression_level >= 1 && config->compression_level <= 22)) &&
|
||||||
@@ -880,6 +884,14 @@ static bool config_receive_checksum_algo(int fd, int* value) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool config_receive_compression_algo(int fd, int* value) {
|
||||||
|
int algo;
|
||||||
|
if (!receive_int(fd, &algo) || !compression_algo_valid(algo))
|
||||||
|
return false;
|
||||||
|
*value = algo;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
static bool config_receive_super_mode(int fd, SuperMode* value) {
|
static bool config_receive_super_mode(int fd, SuperMode* value) {
|
||||||
int mode;
|
int mode;
|
||||||
if (!receive_int(fd, &mode) || mode < SUPER_MODE_AUTO || mode > SUPER_MODE_OFF)
|
if (!receive_int(fd, &mode) || mode < SUPER_MODE_AUTO || mode > SUPER_MODE_OFF)
|
||||||
@@ -1081,6 +1093,9 @@ fail:
|
|||||||
#define CONFIG_SEND_INT_CHECKSUM_ALGO(name) send_int(fd, c->name)
|
#define CONFIG_SEND_INT_CHECKSUM_ALGO(name) send_int(fd, c->name)
|
||||||
#define CONFIG_RECV_INT_CHECKSUM_ALGO(name) config_receive_checksum_algo(fd, &c->name)
|
#define CONFIG_RECV_INT_CHECKSUM_ALGO(name) config_receive_checksum_algo(fd, &c->name)
|
||||||
|
|
||||||
|
#define CONFIG_SEND_INT_COMPRESSION_ALGO(name) send_int(fd, c->name)
|
||||||
|
#define CONFIG_RECV_INT_COMPRESSION_ALGO(name) config_receive_compression_algo(fd, &c->name)
|
||||||
|
|
||||||
#define CONFIG_SEND_SUPERMODE(name) send_int(fd, (int)c->name)
|
#define CONFIG_SEND_SUPERMODE(name) send_int(fd, (int)c->name)
|
||||||
#define CONFIG_RECV_SUPERMODE(name) config_receive_super_mode(fd, &c->name)
|
#define CONFIG_RECV_SUPERMODE(name) config_receive_super_mode(fd, &c->name)
|
||||||
|
|
||||||
@@ -1156,6 +1171,7 @@ CONFIG_DEFINE_SEND(send_iconv_spec, CONFIG_WIRE_ICONV_FIELDS)
|
|||||||
CONFIG_DEFINE_SEND(send_privilege_options, CONFIG_WIRE_PRIVILEGE_FIELDS)
|
CONFIG_DEFINE_SEND(send_privilege_options, CONFIG_WIRE_PRIVILEGE_FIELDS)
|
||||||
CONFIG_DEFINE_SEND(send_copy_as_options, CONFIG_WIRE_COPY_AS_FIELDS)
|
CONFIG_DEFINE_SEND(send_copy_as_options, CONFIG_WIRE_COPY_AS_FIELDS)
|
||||||
CONFIG_DEFINE_SEND(send_output_options, CONFIG_WIRE_OUTPUT_FIELDS)
|
CONFIG_DEFINE_SEND(send_output_options, CONFIG_WIRE_OUTPUT_FIELDS)
|
||||||
|
CONFIG_DEFINE_SEND(send_codec_options, CONFIG_WIRE_CODEC_FIELDS)
|
||||||
|
|
||||||
CONFIG_DEFINE_RECV(receive_core_fields, CONFIG_WIRE_CORE_FIELDS)
|
CONFIG_DEFINE_RECV(receive_core_fields, CONFIG_WIRE_CORE_FIELDS)
|
||||||
CONFIG_DEFINE_RECV(receive_delta_fields, CONFIG_WIRE_DELTA_FIELDS)
|
CONFIG_DEFINE_RECV(receive_delta_fields, CONFIG_WIRE_DELTA_FIELDS)
|
||||||
@@ -1175,6 +1191,7 @@ CONFIG_DEFINE_RECV(receive_iconv_spec, CONFIG_WIRE_ICONV_FIELDS)
|
|||||||
CONFIG_DEFINE_RECV(receive_privilege_options, CONFIG_WIRE_PRIVILEGE_FIELDS)
|
CONFIG_DEFINE_RECV(receive_privilege_options, CONFIG_WIRE_PRIVILEGE_FIELDS)
|
||||||
CONFIG_DEFINE_RECV(receive_copy_as_options, CONFIG_WIRE_COPY_AS_FIELDS)
|
CONFIG_DEFINE_RECV(receive_copy_as_options, CONFIG_WIRE_COPY_AS_FIELDS)
|
||||||
CONFIG_DEFINE_RECV(receive_output_options, CONFIG_WIRE_OUTPUT_FIELDS)
|
CONFIG_DEFINE_RECV(receive_output_options, CONFIG_WIRE_OUTPUT_FIELDS)
|
||||||
|
CONFIG_DEFINE_RECV(receive_codec_options, CONFIG_WIRE_CODEC_FIELDS)
|
||||||
|
|
||||||
#undef XSEND
|
#undef XSEND
|
||||||
#undef XRECV
|
#undef XRECV
|
||||||
@@ -1292,7 +1309,8 @@ bool config_send_wire_block(int file_descriptor, const Config* config) {
|
|||||||
send_iconv_spec(file_descriptor, config) &&
|
send_iconv_spec(file_descriptor, config) &&
|
||||||
send_privilege_options(file_descriptor, config) &&
|
send_privilege_options(file_descriptor, config) &&
|
||||||
send_copy_as_options(file_descriptor, config) &&
|
send_copy_as_options(file_descriptor, config) &&
|
||||||
send_output_options(file_descriptor, config);
|
send_output_options(file_descriptor, config) &&
|
||||||
|
send_codec_options(file_descriptor, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool config_send(int file_descriptor, const Config* config) {
|
bool config_send(int file_descriptor, const Config* config) {
|
||||||
@@ -1363,29 +1381,59 @@ Config* config_receive_with_validate(int file_descriptor, ConfigValidateFunc val
|
|||||||
!receive_iconv_spec(file_descriptor, config, &budget) ||
|
!receive_iconv_spec(file_descriptor, config, &budget) ||
|
||||||
!receive_privilege_options(file_descriptor, config, &budget) ||
|
!receive_privilege_options(file_descriptor, config, &budget) ||
|
||||||
!receive_copy_as_options(file_descriptor, config, &budget) ||
|
!receive_copy_as_options(file_descriptor, config, &budget) ||
|
||||||
!receive_output_options(file_descriptor, config, &budget))
|
!receive_output_options(file_descriptor, config, &budget) ||
|
||||||
|
!receive_codec_options(file_descriptor, config, &budget))
|
||||||
goto error;
|
goto error;
|
||||||
if (config->compress_choice[0] != '\0' && strcmp(config->compress_choice, "zstd") != 0 &&
|
/* Validate/normalize the negotiated codec. compress_choice is the human
|
||||||
strcmp(config->compress_choice, "none") != 0 &&
|
* spelling (NULL or "" when -z was not given); compression_algo is the
|
||||||
strcmp(config->compress_choice, "auto") != 0) {
|
* concrete codec id the sender used. They must agree, and "auto" is
|
||||||
|
* canonicalized to FastSync's negotiated default so the stored spelling is
|
||||||
|
* always concrete (a hostile/older client may still send "auto"). */
|
||||||
|
if (config->compress_choice && config->compress_choice[0] != '\0') {
|
||||||
|
int choice_algo = compression_algo_from_name(config->compress_choice);
|
||||||
|
if (choice_algo < 0 && strcasecmp(config->compress_choice, "auto") != 0) {
|
||||||
char* escaped_choice = output_escape(config->compress_choice, config->eight_bit_output);
|
char* escaped_choice = output_escape(config->compress_choice, config->eight_bit_output);
|
||||||
log_message(LOG_LEVEL_ERROR, "Unsupported compression choice: %s",
|
log_message(LOG_LEVEL_ERROR, "Unsupported compression choice: %s",
|
||||||
escaped_choice ? escaped_choice : "<allocation failed>");
|
escaped_choice ? escaped_choice : "<allocation failed>");
|
||||||
char detail[128];
|
char detail[160];
|
||||||
snprintf(detail, sizeof(detail), "unsupported compression choice: %s",
|
snprintf(detail, sizeof(detail), "unsupported compression choice: %s",
|
||||||
escaped_choice ? escaped_choice : "<allocation failed>");
|
escaped_choice ? escaped_choice : "<allocation failed>");
|
||||||
send_error_detail(file_descriptor, detail);
|
send_error_detail(file_descriptor, detail);
|
||||||
free(escaped_choice);
|
free(escaped_choice);
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
/* Defensive: an older/hostile client may still send "auto"; canonicalize it
|
if (choice_algo < 0)
|
||||||
to zstd (its effective choice) so the stored value is always concrete. */
|
choice_algo = (int)compression_negotiate_default();
|
||||||
if (strcmp(config->compress_choice, "auto") == 0) {
|
if (strcasecmp(config->compress_choice, "auto") == 0 ||
|
||||||
char* canonical = str_dup("zstd");
|
choice_algo == (int)COMPRESSION_ALGO_NONE) {
|
||||||
if (!canonical)
|
const char* canonical = compression_algo_name((CompressionAlgo)choice_algo);
|
||||||
|
char* dup = str_dup(canonical);
|
||||||
|
if (!dup)
|
||||||
goto error;
|
goto error;
|
||||||
free(config->compress_choice);
|
free(config->compress_choice);
|
||||||
config->compress_choice = canonical;
|
config->compress_choice = dup;
|
||||||
|
}
|
||||||
|
if (config->compression_algo != choice_algo) {
|
||||||
|
log_message(LOG_LEVEL_ERROR, "Compression choice '%s' does not match codec id %d",
|
||||||
|
config->compress_choice, config->compression_algo);
|
||||||
|
send_error_detail(file_descriptor, "compression choice/codec mismatch");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* The concrete codec must exist only when compression is on. A client that
|
||||||
|
* left -z off has no codec in effect, but the field keeps whatever id it
|
||||||
|
* carried (the receiver never dispatches on it without use_compression), so
|
||||||
|
* the wire value round-trips untouched. */
|
||||||
|
if (config->use_compression && config->compression_algo == (int)COMPRESSION_ALGO_NONE) {
|
||||||
|
log_message(LOG_LEVEL_ERROR, "Compression requested with the 'none' codec");
|
||||||
|
send_error_detail(file_descriptor, "compression requested with the none codec");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
/* rsync: "none" as the pre-transfer checksum is invalid with --checksum. */
|
||||||
|
if (config->checksum && config->checksum_algo == (int)CHECKSUM_ALGO_NONE) {
|
||||||
|
log_message(LOG_LEVEL_ERROR, "Invalid checksum-choice for --checksum: none");
|
||||||
|
send_error_detail(file_descriptor, "checksum-choice 'none' cannot be used with --checksum");
|
||||||
|
goto error;
|
||||||
}
|
}
|
||||||
if (!validate_received_config(config)) {
|
if (!validate_received_config(config)) {
|
||||||
log_message(LOG_LEVEL_ERROR, "Invalid configuration received from client");
|
log_message(LOG_LEVEL_ERROR, "Invalid configuration received from client");
|
||||||
|
|||||||
+53
-5
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#include "array_list.h"
|
#include "array_list.h"
|
||||||
#include "checksum.h"
|
#include "checksum.h"
|
||||||
|
#include "compression.h"
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
@@ -81,7 +82,7 @@ typedef struct {
|
|||||||
typedef enum SuperMode { SUPER_MODE_AUTO = 0, SUPER_MODE_ON = 1, SUPER_MODE_OFF = 2 } SuperMode;
|
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.23.0).
|
* Config wire-field table (single source of truth for protocol 2.26.0).
|
||||||
*
|
*
|
||||||
* Every field below crosses the wire. The table is the ONLY place a
|
* 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
|
* serialized field is named: config.h expands CONFIG_WIRE_FIELDS() to declare
|
||||||
@@ -203,7 +204,7 @@ typedef enum SuperMode { SUPER_MODE_AUTO = 0, SUPER_MODE_ON = 1, SUPER_MODE_OFF
|
|||||||
#define CONFIG_WIRE_FUZZY_FIELDS(X) X(fuzzy, bool, false, BOOL)
|
#define CONFIG_WIRE_FUZZY_FIELDS(X) X(fuzzy, bool, false, BOOL)
|
||||||
|
|
||||||
#define CONFIG_WIRE_CHECKSUM_FIELDS(X) \
|
#define CONFIG_WIRE_CHECKSUM_FIELDS(X) \
|
||||||
X(checksum_algo, int, CHECKSUM_ALGO_XXH64, INT_CHECKSUM_ALGO) \
|
X(checksum_algo, int, CHECKSUM_ALGO_DEFAULT, INT_CHECKSUM_ALGO) \
|
||||||
X(checksum_seed, uint64_t, 0, RAW)
|
X(checksum_seed, uint64_t, 0, RAW)
|
||||||
|
|
||||||
#define CONFIG_WIRE_IDENTITY_FIELDS(X) \
|
#define CONFIG_WIRE_IDENTITY_FIELDS(X) \
|
||||||
@@ -253,6 +254,27 @@ typedef enum SuperMode { SUPER_MODE_AUTO = 0, SUPER_MODE_ON = 1, SUPER_MODE_OFF
|
|||||||
* output; the transfer decision itself is unchanged. */
|
* output; the transfer decision itself is unchanged. */
|
||||||
#define CONFIG_WIRE_OUTPUT_FIELDS(X) X(report_dest_info, bool, false, BOOL)
|
#define CONFIG_WIRE_OUTPUT_FIELDS(X) X(report_dest_info, bool, false, BOOL)
|
||||||
|
|
||||||
|
/* Codec-negotiation wave (protocol 2.26.0). compression_algo is the concrete
|
||||||
|
* codec the client selected for this transfer (a CompressionAlgo id) and is the
|
||||||
|
* value the receiver validates and installs. It is the resolved result of
|
||||||
|
* --compress-choice / the "auto" negotiation so both peers agree exactly.
|
||||||
|
*
|
||||||
|
* Negotiation model: FastSync enforces a strict same-version handshake, so both
|
||||||
|
* peers carry the identical compiled-in codec set. The client resolves the
|
||||||
|
* effective algorithm deterministically and serializes it here; "auto" picks
|
||||||
|
* the first entry of the rsync 3.4.1 preference order
|
||||||
|
* (compression: zstd lz4 zlibx zlib none; checksum: xxh128 xxh3 xxh64 md5 md4
|
||||||
|
* sha1 none), and an explicit request wins. The receiver rejects (before
|
||||||
|
* STATUS_OK) any algorithm outside its own supported set, which is rsync's
|
||||||
|
* "no common choice is an error" behavior. The same resolver runs on both
|
||||||
|
* sides (compression_negotiate_default / checksum_negotiate_default), so the
|
||||||
|
* fallback is consistent.
|
||||||
|
*
|
||||||
|
* The field is appended after the output block so every pre-2.26 field keeps
|
||||||
|
* its wire position. */
|
||||||
|
#define CONFIG_WIRE_CODEC_FIELDS(X) \
|
||||||
|
X(compression_algo, int, COMPRESSION_ALGO_ZSTD, INT_COMPRESSION_ALGO)
|
||||||
|
|
||||||
/* All serialized fields, in exact wire order. Concatenating the per-segment
|
/* All serialized fields, in exact wire order. Concatenating the per-segment
|
||||||
* lists here is what keeps the declaration order = the wire order. */
|
* lists here is what keeps the declaration order = the wire order. */
|
||||||
#define CONFIG_WIRE_FIELDS(X) \
|
#define CONFIG_WIRE_FIELDS(X) \
|
||||||
@@ -274,7 +296,8 @@ typedef enum SuperMode { SUPER_MODE_AUTO = 0, SUPER_MODE_ON = 1, SUPER_MODE_OFF
|
|||||||
CONFIG_WIRE_ICONV_FIELDS(X) \
|
CONFIG_WIRE_ICONV_FIELDS(X) \
|
||||||
CONFIG_WIRE_PRIVILEGE_FIELDS(X) \
|
CONFIG_WIRE_PRIVILEGE_FIELDS(X) \
|
||||||
CONFIG_WIRE_COPY_AS_FIELDS(X) \
|
CONFIG_WIRE_COPY_AS_FIELDS(X) \
|
||||||
CONFIG_WIRE_OUTPUT_FIELDS(X)
|
CONFIG_WIRE_OUTPUT_FIELDS(X) \
|
||||||
|
CONFIG_WIRE_CODEC_FIELDS(X)
|
||||||
|
|
||||||
typedef struct Config {
|
typedef struct Config {
|
||||||
/* -j/--threads=N: number of parallel scanner worker threads for the -m
|
/* -j/--threads=N: number of parallel scanner worker threads for the -m
|
||||||
@@ -369,6 +392,16 @@ typedef struct Config {
|
|||||||
* enters the keep-set. Implied by --delete-missing-args. */
|
* enters the keep-set. Implied by --delete-missing-args. */
|
||||||
bool ignore_missing_args;
|
bool ignore_missing_args;
|
||||||
|
|
||||||
|
/* Codec-negotiation CLI state (all client-only, never serialized). The
|
||||||
|
* effective pre-transfer checksum is Config->checksum_algo (serialized);
|
||||||
|
* checksum_transfer_algo is the rsync "transfer" half of a two-name
|
||||||
|
* --checksum-choice form (validated and used only to mirror rsync's
|
||||||
|
* whole-file forcing, since FastSync's per-block strong hash is fixed).
|
||||||
|
* cli_exit_code carries a parser-requested process exit status (rsync uses 4
|
||||||
|
* for an unsupported checksum/compress algorithm) so main() can mirror it. */
|
||||||
|
int checksum_transfer_algo;
|
||||||
|
int cli_exit_code;
|
||||||
|
|
||||||
// Issue #129: Advanced file selection. These fields are CLIENT-ONLY: they are
|
// Issue #129: Advanced file selection. These fields are CLIENT-ONLY: they are
|
||||||
// never serialized to the wire (the receiver must not learn them).
|
// never serialized to the wire (the receiver must not learn them).
|
||||||
ArrayList* filters; /* --filter=RULE rule strings, in order */
|
ArrayList* filters; /* --filter=RULE rule strings, in order */
|
||||||
@@ -911,8 +944,23 @@ typedef struct Config {
|
|||||||
* snapshot of the old entry) before its ordinary verdict when the config frame
|
* snapshot of the old entry) before its ordinary verdict when the config frame
|
||||||
* carries the new report_dest_info bool appended after the --copy-as block.
|
* carries the new report_dest_info bool appended after the --copy-as block.
|
||||||
* This is both a config-frame layout change (one trailing bool) and a frame
|
* This is both a config-frame layout change (one trailing bool) and a frame
|
||||||
* sequence change (the new status). */
|
* sequence change (the new status).
|
||||||
#define PROTOCOL_VERSION "2.23.0"
|
*
|
||||||
|
* Codec-Breadth + Negotiation Wave: 2.23.0 -> 2.26.0.
|
||||||
|
*
|
||||||
|
* WHY the bump, grounded in the wire: the config frame gains one trailing int,
|
||||||
|
* compression_algo (a CompressionAlgo id), appended after the output block.
|
||||||
|
* It is the negotiated/effective compression codec and is what the receiver's
|
||||||
|
* self-describing decompressor validates against its own supported set. The
|
||||||
|
* checksum_algo wire value now also accepts md4/sha1/none, and its default
|
||||||
|
* changes to the rsync 3.4.1 auto-negotiated xxh128. Any config-frame layout
|
||||||
|
* change must bump the protocol version: a peer that does not parse the new
|
||||||
|
* trailing int would desynchronize on the frame boundary, and the strict
|
||||||
|
* same-version handshake (config_receive rejects a mismatched version before
|
||||||
|
* parsing anything else) is what keeps a 2.26 client and an older server from
|
||||||
|
* ever reaching that state. (2.24/2.25 are reserved for the other waves
|
||||||
|
* landing alongside this one; this busy-work bump keeps 2.26.0 for codecs.) */
|
||||||
|
#define PROTOCOL_VERSION "2.26.0"
|
||||||
#define DEFAULT_CHUNK_SIZE (10 * 1024 * 1024)
|
#define DEFAULT_CHUNK_SIZE (10 * 1024 * 1024)
|
||||||
/* Upper bound on total basis-dir entries (rsync caps --link-dest at 20). */
|
/* Upper bound on total basis-dir entries (rsync caps --link-dest at 20). */
|
||||||
#define MAX_BASIS_DIRS 64
|
#define MAX_BASIS_DIRS 64
|
||||||
|
|||||||
Reference in New Issue
Block a user