feat: --append / --append-verify tail-only resume

Implement rsync's append modes: when an existing destination file is SHORTER
than the source, the receiver negotiates a resume offset and only the tail is
transferred; the full file (retained prefix + tail) is rebuilt and installed
through the normal atomic store path, so the result is byte-identical to the
source whenever the prefix matches.

- --append: sends the tail without content-verifying the retained prefix
  (rsync parity; the documented prefix-trust risk).
- --append-verify: verifies the retained prefix against the source's prefix
  xxHash64 before appending and, on a mismatch, falls back to a clean full
  transfer (never a corrupt prefix+tail blend).

New wire frames STATUS_APPEND / STATUS_APPEND_SIG / STATUS_APPEND_OK /
STATUS_APPEND_DATA; PROTOCOL_VERSION bumped 2.9.0 -> 2.10.0 (peers must match).
Both flags imply --incremental and are incompatible with -s (chunk
serialization) and --whole-file (rejected up front). Respects --inplace,
--partial/--partial-dir and --delay-updates via the shared store engine.
This commit is contained in:
2026-09-07 15:43:29 +02:00
parent 6701c103cb
commit 6ad3887aa1
11 changed files with 470 additions and 51 deletions
+13
View File
@@ -435,6 +435,8 @@ static const OptionEntry OPTION_TABLE[] = {
{"--copy-unsafe-links", NULL, OPT_FLAG, offsetof(Config, copy_unsafe_links)},
{"--sparse", "-S", OPT_FLAG, offsetof(Config, preserve_sparse)},
{"--inplace", NULL, OPT_FLAG, offsetof(Config, inplace)},
{"--append", NULL, OPT_FLAG, offsetof(Config, append)},
{"--append-verify", NULL, OPT_FLAG, offsetof(Config, append_verify)},
{"--fsync", NULL, OPT_FLAG, offsetof(Config, use_fsync)},
{"--checksum", NULL, OPT_FLAG, offsetof(Config, checksum)},
{"--8-bit-output", "-8", OPT_FLAG, offsetof(Config, eight_bit_output)},
@@ -1102,6 +1104,17 @@ int parse_args(Config* config, int argc, char* argv[], int* positional_args,
config->use_delta = true;
}
/* --append / --append-verify resume a shorter existing destination file.
* The receiver must run the per-file STATUS_CHECK handshake to learn the
* destination length and reply STATUS_APPEND, so an append mode forces
* --incremental on (exactly like the basis-dir options: the handshake is
* required, not optional). The resume itself is a dedicated tail-only
* exchange, not the block delta, so no delta implication is made. When both
* spelling are given the safer --append-verify semantics win. */
if (config->append || config->append_verify) {
config->use_incremental = true;
}
/* Incremental and delta transfers need metadata unless the user disabled it. */
if ((config->use_incremental || config->use_delta) && !config->use_metadata &&
!config->metadata_explicitly_disabled) {