feat(checksum): --checksum-choice/--cc and --checksum-seed for whole-file digest
Adds real algorithm selection (xxh64 default, plus md5 via OpenSSL EVP) and a 64-bit seed for the per-file whole-file digest used by the --incremental/ --checksum handshake and basis-dir content verification. The seed also feeds the delta path's per-block xxHash32 strong checksum (low 32 bits) so an explicit seed deterministically changes those digests too. Sender and receiver hash identically: the algorithm id and seed cross the config wire frame and the STATUS_CHECK handshake now carries a length-prefixed, bounded digest instead of a fixed 64-bit value. Unsupported algorithm names are rejected at parse time (never a silent no-op). PROTOCOL_VERSION bumped 2.9.0 -> 2.10.0; defaults (xxh64, seed 0) preserve prior byte-for-byte behavior.
This commit is contained in:
+59
-2
@@ -94,6 +94,47 @@ static int set_compression_choice(Config* config, const char* value) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Validate and store the --checksum-choice/--cc algorithm. Only the algorithms
|
||||
* the engine genuinely supports are accepted (xxHash64 and md5); anything else
|
||||
* is a clear error, never a silent no-op. "xxhash" is accepted as rsync's
|
||||
* spelling of xxHash64. */
|
||||
static int set_checksum_choice(Config* config, const char* value) {
|
||||
int algo = checksum_algo_from_name(value);
|
||||
if (algo < 0) {
|
||||
log_message(LOG_LEVEL_ERROR, "--checksum-choice must be xxh64 (or xxhash) or md5 (got '%s')",
|
||||
value);
|
||||
return -1;
|
||||
}
|
||||
config->checksum_algo = algo;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* parse_ull_arg is defined later in this file; declared here for the seed
|
||||
parser below. */
|
||||
static int parse_ull_arg(const char* val, unsigned long long* out, const char* optname);
|
||||
|
||||
/* Parse --checksum-seed=NUM as a strict decimal 0..UINT64_MAX. A blank value,
|
||||
* a sign, or any non-digit (which parse_ull_arg's strtoull would silently
|
||||
* coerce) is rejected: an explicit seed must be an exact unsigned integer or
|
||||
* the run fails with a clear error rather than quietly ignoring the value. */
|
||||
static int set_checksum_seed(Config* config, const char* value) {
|
||||
if (!value || *value == '\0') {
|
||||
log_message(LOG_LEVEL_ERROR, "--checksum-seed must be a non-negative integer");
|
||||
return -1;
|
||||
}
|
||||
for (const char* p = value; *p; p++) {
|
||||
if (*p < '0' || *p > '9') {
|
||||
log_message(LOG_LEVEL_ERROR, "--checksum-seed must be a non-negative integer");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
unsigned long long seed;
|
||||
if (parse_ull_arg(value, &seed, "--checksum-seed") != 0)
|
||||
return -1;
|
||||
config->checksum_seed = seed;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int set_compression_threads_option(int* dest, const char* value) {
|
||||
if (set_positive_int_option(dest, value, "--compress-threads") != 0)
|
||||
return -1;
|
||||
@@ -1009,8 +1050,24 @@ int parse_args(Config* config, int argc, char* argv[], int* positional_args,
|
||||
log_message(LOG_LEVEL_ERROR, "missing argument for %s", argv[i]);
|
||||
return -1;
|
||||
}
|
||||
log_message(LOG_LEVEL_ERROR, "%s is not supported yet (xxHash64 is used)", argv[i]);
|
||||
return -1;
|
||||
if (set_checksum_choice(config, argv[++i]) != 0)
|
||||
return -1;
|
||||
} else if (strncmp(argv[i], "--checksum-choice=", 18) == 0) {
|
||||
if (set_checksum_choice(config, argv[i] + 18) != 0)
|
||||
return -1;
|
||||
} else if (strncmp(argv[i], "--cc=", 5) == 0) {
|
||||
if (set_checksum_choice(config, argv[i] + 5) != 0)
|
||||
return -1;
|
||||
} else if (strncmp(argv[i], "--checksum-seed=", 16) == 0) {
|
||||
if (set_checksum_seed(config, argv[i] + 16) != 0)
|
||||
return -1;
|
||||
} else if (opt_is(argv[i], "--checksum-seed", NULL)) {
|
||||
if (i + 1 >= argc) {
|
||||
log_message(LOG_LEVEL_ERROR, "missing argument for --checksum-seed");
|
||||
return -1;
|
||||
}
|
||||
if (set_checksum_seed(config, argv[++i]) != 0)
|
||||
return -1;
|
||||
} else if (strncmp(argv[i], "--compare-dest=", 15) == 0) {
|
||||
if (set_basis_dest_option(config, BASIS_DEST_COMPARE, argv[i] + 15, "--compare-dest") != 0)
|
||||
return -1;
|
||||
|
||||
@@ -733,12 +733,18 @@ static int incremental_check(Client* client, File* file, const Config* config,
|
||||
if (!send_n_data(client->file_descriptor, &mtime_nsec, sizeof(mtime_nsec)))
|
||||
return -1;
|
||||
/* With alternate basis directories the receiver must be able to verify the
|
||||
* content of every candidate basis file, so the sender supplies its xxHash64
|
||||
* for every file even when --checksum was not requested. */
|
||||
* content of every candidate basis file, so the sender supplies its whole-file
|
||||
* digest (computed with the negotiated --checksum-choice algorithm and
|
||||
* --checksum-seed) for every file even when --checksum was not requested. */
|
||||
if (config->checksum || config_has_basis(config)) {
|
||||
uint64_t checksum;
|
||||
if (!file_checksum(file, &checksum) ||
|
||||
!send_n_data(client->file_descriptor, &checksum, sizeof(checksum)))
|
||||
uint8_t digest[CHECKSUM_MAX_DIGEST_LEN];
|
||||
size_t digest_len = 0;
|
||||
if (!file_checksum(file, (ChecksumAlgo)config->checksum_algo, config->checksum_seed, digest,
|
||||
sizeof(digest), &digest_len))
|
||||
return -1;
|
||||
uint8_t wire_len = (uint8_t)digest_len;
|
||||
if (!send_n_data(client->file_descriptor, &wire_len, sizeof(wire_len)) ||
|
||||
!send_n_data(client->file_descriptor, digest, wire_len))
|
||||
return -1;
|
||||
}
|
||||
Status s;
|
||||
@@ -774,7 +780,8 @@ static int incremental_check(Client* client, File* file, const Config* config,
|
||||
}
|
||||
|
||||
static int send_delta(Client* client, File* file, DeltaSignature* sig, Config* config) {
|
||||
Delta* delta = delta_compute(file->data->data, file->data->size, sig, config->delta_block_size);
|
||||
Delta* delta = delta_compute_seeded(file->data->data, file->data->size, sig,
|
||||
config->delta_block_size, (uint32_t)config->checksum_seed);
|
||||
/* The receiver is blocked after sending the signature. Every local
|
||||
fallback therefore needs the explicit NEXT response before full data. */
|
||||
if (!delta)
|
||||
|
||||
+6
-2
@@ -91,8 +91,12 @@ void print_usage(void) {
|
||||
printf(" into the destination instead of transferring its data\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(" --checksum-choice, --cc <alg> Checksum algorithm (not supported yet; xxHash64 is "
|
||||
"used)\n");
|
||||
printf(" --checksum-choice, --cc <alg> Whole-file checksum algorithm for --incremental/\n");
|
||||
printf(" --checksum compares (xxh64/xxhash or md5; default xxh64 with\n");
|
||||
printf(" seed 0). The seed comes from --checksum-seed\n");
|
||||
printf(" --checksum-seed <num> Seed for the whole-file xxHash64 digest (and the delta\n");
|
||||
printf(" block strong hash, low 32 bits); md5 ignores the seed. The\n");
|
||||
printf(" digest algorithm and seed must match on sender and receiver\n");
|
||||
printf(" --delta Delta transfer for changed files (requires --incremental)\n");
|
||||
printf(" -W, --whole-file Transfer changed files without delta processing\n");
|
||||
printf(" -y, --fuzzy Use a similar-named file already in the destination\n");
|
||||
|
||||
Reference in New Issue
Block a user