Merge branch 'feat/parity-network' into feat/rsync-parity
This commit is contained in:
+71
-22
@@ -126,26 +126,40 @@ static int set_positive_int_option(int* dest, const char* value, const char* opt
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Set and validate the compression algorithm selected by the client. */
|
||||
/* 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
|
||||
* implements zstd (and no compression). "auto" is accepted as the default
|
||||
* zstd choice; any other rsync choice is rejected by name instead of being
|
||||
* silently accepted and ignored. */
|
||||
static int set_compression_choice(Config* config, const char* value) {
|
||||
if (strcmp(value, "zstd") != 0 && strcmp(value, "none") != 0) {
|
||||
log_message(LOG_LEVEL_ERROR, "--compress-choice must be zstd or none");
|
||||
if (strcmp(value, "zstd") != 0 && strcmp(value, "none") != 0 && strcmp(value, "auto") != 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;
|
||||
}
|
||||
if (set_string_option(&config->compress_choice, value, "--compress-choice") != 0)
|
||||
return -1;
|
||||
config->use_compression = strcmp(value, "zstd") == 0;
|
||||
config->use_compression = strcmp(value, "none") != 0;
|
||||
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. */
|
||||
* the engine genuinely supports are accepted (xxh64/xxhash, xxh3, xxh128, md5);
|
||||
* rsync's compiled-in choices that FastSync does not implement (md4, sha1,
|
||||
* none) and the two-name transfer/pre-transfer syntax are a clear error, never
|
||||
* a silent no-op. "auto" (rsync's default automatic choice) selects FastSync's
|
||||
* default algorithm. */
|
||||
static int set_checksum_choice(Config* config, const char* value) {
|
||||
if (strcasecmp(value, "auto") == 0)
|
||||
return 0;
|
||||
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')",
|
||||
log_message(LOG_LEVEL_ERROR,
|
||||
"--checksum-choice '%s' is not implemented; FastSync supports xxh64 (or xxhash), "
|
||||
"xxh3, xxh128, md5 or auto (rsync's md4/sha1/none and the two-name "
|
||||
"transfer,pre-transfer form are rejected, never silently ignored)",
|
||||
value);
|
||||
return -1;
|
||||
}
|
||||
@@ -518,10 +532,6 @@ static int parse_size_arg_allow_zero(const char* value, unsigned long long* out,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_size_arg(const char* value, unsigned long long* out) {
|
||||
return parse_size_arg_allow_zero(value, out, false);
|
||||
}
|
||||
|
||||
/* Append a duplicated pattern to a growable pattern array. Returns 0 on success, -1 on error. */
|
||||
static int config_add_pattern(char*** patterns, int* count, const char* value,
|
||||
const char* optname) {
|
||||
@@ -573,7 +583,10 @@ static int parse_skip_compress(Config* config, const char* value) {
|
||||
if (!list)
|
||||
return -1;
|
||||
config->skip_compress_set = true;
|
||||
for (char* token = strtok(list, ","); token; token = strtok(NULL, ",")) {
|
||||
/* rsync documents the LIST as slash-separated; accept that along with the
|
||||
* historical comma-separated spelling. A leading dot is optional (rsync's
|
||||
* suffixes have none, FastSync historically used them). */
|
||||
for (char* token = strtok(list, ",/"); token; token = strtok(NULL, ",/")) {
|
||||
while (*token == ' ' || *token == '\t')
|
||||
token++;
|
||||
size_t len = strlen(token);
|
||||
@@ -741,8 +754,8 @@ static const OptionEntry OPTION_TABLE[] = {
|
||||
{"--compress-choice", "--zc", OPT_STRING, offsetof(Config, compress_choice)},
|
||||
{"--compress-level", "--zl", OPT_POS_INT, offsetof(Config, compression_level)},
|
||||
|
||||
{"--timeout", NULL, OPT_POS_INT, offsetof(Config, timeout)},
|
||||
{"--contimeout", NULL, OPT_POS_INT, offsetof(Config, contimeout)},
|
||||
{"--timeout", NULL, OPT_NONNEG_INT, offsetof(Config, timeout)},
|
||||
{"--contimeout", NULL, OPT_NONNEG_INT, offsetof(Config, contimeout)},
|
||||
{"--max-depth", NULL, OPT_NONNEG_INT, offsetof(Config, max_depth)},
|
||||
{"--address", NULL, OPT_STRING, offsetof(Config, address)},
|
||||
{"--ipv4", "-4", OPT_FLAG, offsetof(Config, ipv4)},
|
||||
@@ -781,6 +794,7 @@ static const NegatableOption NEGATABLE_OPTIONS[] = {
|
||||
{"delete", NULL, offsetof(Config, use_delete)},
|
||||
{"incremental", NULL, offsetof(Config, use_incremental)},
|
||||
{"delta", NULL, offsetof(Config, use_delta)},
|
||||
{"whole-file", "W", offsetof(Config, whole_file)},
|
||||
{"fuzzy", NULL, offsetof(Config, fuzzy)},
|
||||
{"save-to-disk", NULL, offsetof(Config, save_to_disk)},
|
||||
{"progress", NULL, offsetof(Config, show_progress)},
|
||||
@@ -995,6 +1009,17 @@ static bool cli_handle_pre_negation(CliParseCtx* ctx) {
|
||||
config->super_mode = SUPER_MODE_OFF;
|
||||
return true;
|
||||
}
|
||||
/* rsync's --no-timeout / --no-contimeout explicit spellings clear the
|
||||
* corresponding (integer) deadline; handled before the generic --no-* branch
|
||||
* because the negation table only models boolean fields. */
|
||||
if (strcmp(arg, "--no-timeout") == 0) {
|
||||
config->timeout = 0;
|
||||
return true;
|
||||
}
|
||||
if (strcmp(arg, "--no-contimeout") == 0) {
|
||||
config->contimeout = 0;
|
||||
return true;
|
||||
}
|
||||
if (strncmp(arg, "--no-", strlen("--no-")) == 0) {
|
||||
if (strcmp(arg, "--no-delta") == 0)
|
||||
ctx->no_delta = true;
|
||||
@@ -1051,7 +1076,8 @@ static bool cli_handle_range_time_options(CliParseCtx* ctx) {
|
||||
}
|
||||
if (strncmp(arg, "--stop-at=", 10) == 0) {
|
||||
if (!stop_parse_at_time(arg + 10, time(NULL), &config->stop_at)) {
|
||||
log_message(LOG_LEVEL_ERROR, "--stop-at must be HH:MM[:SS] or now+N[smhd]");
|
||||
log_message(LOG_LEVEL_ERROR, "--stop-at must be a date/time such as 2000-12-31T23:59, 12-31, "
|
||||
"14:00, :59, HH:MM[:SS] or now+N[smhd]");
|
||||
ctx->exit_code = -1;
|
||||
return true;
|
||||
}
|
||||
@@ -1065,7 +1091,8 @@ static bool cli_handle_range_time_options(CliParseCtx* ctx) {
|
||||
return true;
|
||||
}
|
||||
if (!stop_parse_at_time(ctx->argv[++ctx->i], time(NULL), &config->stop_at)) {
|
||||
log_message(LOG_LEVEL_ERROR, "--stop-at must be HH:MM[:SS] or now+N[smhd]");
|
||||
log_message(LOG_LEVEL_ERROR, "--stop-at must be a date/time such as 2000-12-31T23:59, 12-31, "
|
||||
"14:00, :59, HH:MM[:SS] or now+N[smhd]");
|
||||
ctx->exit_code = -1;
|
||||
return true;
|
||||
}
|
||||
@@ -1089,8 +1116,11 @@ static bool cli_handle_range_time_options(CliParseCtx* ctx) {
|
||||
}
|
||||
value = ctx->argv[++ctx->i];
|
||||
}
|
||||
if (parse_size_arg(value, &config->max_alloc) != 0) {
|
||||
log_message(LOG_LEVEL_ERROR, "--max-alloc must be a positive size (B, K, M, G, T, P, or E)");
|
||||
/* rsync: --max-alloc=0 means "no alloc limit" (it maps to SIZE_MAX). A
|
||||
* size with an optional binary suffix is also accepted. */
|
||||
if (parse_size_arg_allow_zero(value, &config->max_alloc, true) != 0) {
|
||||
log_message(LOG_LEVEL_ERROR, "--max-alloc must be a size (0 = no limit; B, K, M, G, T, P, E "
|
||||
"suffixes allowed)");
|
||||
ctx->exit_code = -1;
|
||||
}
|
||||
return true;
|
||||
@@ -1401,7 +1431,7 @@ static bool cli_handle_transfer_flags(CliParseCtx* ctx) {
|
||||
const char* arg = ctx->argv[ctx->i];
|
||||
if (opt_is(arg, "-z", "--compress")) {
|
||||
config->use_compression =
|
||||
!config->compress_choice || strcmp(config->compress_choice, "zstd") == 0;
|
||||
!config->compress_choice || strcmp(config->compress_choice, "none") != 0;
|
||||
log_info_message(LOG_INFO_MISC, "Enabled Compression");
|
||||
if (ctx->i + 1 < ctx->argc) {
|
||||
char* end_ptr;
|
||||
@@ -2011,7 +2041,16 @@ static bool cli_handle_outbuf_option(CliParseCtx* ctx) {
|
||||
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));
|
||||
if (config->compress_choice)
|
||||
config->use_compression = strcmp(config->compress_choice, "zstd") == 0;
|
||||
config->use_compression = strcmp(config->compress_choice, "none") != 0;
|
||||
|
||||
/* 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
|
||||
* time(NULL) ^ (getpid() << 6), and transmits it so both ends agree. Mirror
|
||||
* that: the wire config carries the value, so the receiver uses the exact
|
||||
* seed this sender hashed with. A non-zero --checksum-seed is honored
|
||||
* verbatim (deterministic). */
|
||||
if (config->checksum_seed == 0)
|
||||
config->checksum_seed = (uint64_t)time(NULL) ^ ((uint64_t)getpid() << 6);
|
||||
|
||||
/* --files-from is loaded after every argument is seen so that -0/--from0 may
|
||||
* appear anywhere on the command line. A missing or unreadable file, and
|
||||
@@ -2063,6 +2102,16 @@ static int cli_finalize_config(Config* config, bool verbose, bool no_delta, bool
|
||||
config->use_incremental = true;
|
||||
}
|
||||
|
||||
/* -c/--checksum switches the per-file quick-check from size+mtime to a
|
||||
* content digest; FastSync expresses that comparison through the incremental
|
||||
* handshake, so -c implies --incremental. rsync's -c does NOT imply -t (the
|
||||
* digest alone decides), so the incremental auto-preserve below must not be
|
||||
* triggered by a checksum-only implication: capture the explicitly requested
|
||||
* incremental/delta state first. */
|
||||
bool preserve_implied = config->use_incremental || config->use_delta;
|
||||
if (config->checksum)
|
||||
config->use_incremental = true;
|
||||
|
||||
/* --incremental/--delta historically auto-enabled the metadata path, which
|
||||
* applied mode+mtime (README: "--incremental Auto-enables --preserve").
|
||||
* Restore that behavior by turning on the two attributes unless the user
|
||||
@@ -2070,7 +2119,7 @@ static int cli_finalize_config(Config* config, bool verbose, bool no_delta, bool
|
||||
* BEFORE the derived use_metadata bit so the transport frame is still sent
|
||||
* for the incremental/delta handshake even when both attributes were negated
|
||||
* via --no-preserve (metadata_explicitly_disabled handles that opt-out). */
|
||||
if ((config->use_incremental || config->use_delta) && !config->metadata_explicitly_disabled) {
|
||||
if (preserve_implied && !config->metadata_explicitly_disabled) {
|
||||
if (!config->preserve_perms_explicit_off)
|
||||
config->preserve_perms = true;
|
||||
if (!config->preserve_times_explicit_off)
|
||||
|
||||
@@ -60,6 +60,16 @@ bool validate_config(const Config* config) {
|
||||
log_message(LOG_LEVEL_ERROR, "-f/--sendfile is not supported with SSH transport");
|
||||
return false;
|
||||
}
|
||||
/* -M/--remote-option appends an option to the REMOTE server's argv, which
|
||||
* only exists on the SSH (user@host:path) transport. A daemon
|
||||
* (host::module/path) or local TCP destination has no remote command line,
|
||||
* so the option would be silently ignored; reject it by name instead. */
|
||||
if (config->remote_option_count > 0 && config->transport != TRANSPORT_SSH) {
|
||||
log_message(LOG_LEVEL_ERROR,
|
||||
"-M/--remote-option is only valid with the SSH transport (user@host:path); it "
|
||||
"cannot be used with a daemon (host::module/path) or local TCP destination");
|
||||
return false;
|
||||
}
|
||||
/* -4 and -6 are mutually exclusive: a socket address family cannot be both. */
|
||||
if (config->ipv4 && config->ipv6) {
|
||||
log_message(LOG_LEVEL_ERROR, "-4/--ipv4 and -6/--ipv6 are mutually exclusive");
|
||||
|
||||
+50
-26
@@ -59,6 +59,8 @@ void print_usage(void) {
|
||||
printf(" Emit the batch file only (no destination, no server)\n");
|
||||
printf(" --read-batch=FILE Apply the batch file to the destination (no source, no\n");
|
||||
printf(" server); takes only the destination as an argument\n");
|
||||
printf(" NOTE: the FastSync batch format is NOT interoperable with rsync's batch\n");
|
||||
printf(" files (different container format); do not mix the two tools.\n");
|
||||
printf(" --delete Delete files on receiver not in source\n");
|
||||
printf(" (default timing: delete only after the whole\n");
|
||||
printf(" transfer has succeeded)\n");
|
||||
@@ -118,7 +120,8 @@ void print_usage(void) {
|
||||
printf(" -F Apply per-directory .rsync-filter files during the scan\n");
|
||||
printf(" --max-size <n> Skip files larger than n bytes\n");
|
||||
printf(" --min-size <n> Skip files smaller than n bytes\n");
|
||||
printf(" --max-alloc <SIZE> Maximum single allocation (default: 1G)\n");
|
||||
printf(" --max-alloc <SIZE> Maximum single allocation (default: 1G; 0 = no limit,\n");
|
||||
printf(" matching rsync)\n");
|
||||
printf(" --incremental Skip files unchanged since last transfer\n");
|
||||
printf(" --size-only Skip incremental files matching in size, ignoring mtime\n");
|
||||
printf(" -I, --ignore-times Transfer files even when size and mtime match\n");
|
||||
@@ -133,13 +136,17 @@ void print_usage(void) {
|
||||
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> 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(" --checksum compares. Accepted: xxh64 (aka xxhash), xxh3,\n");
|
||||
printf(" xxh128, md5, or auto (default xxh64). rsync choices FastSync\n");
|
||||
printf(" does not implement (md4, sha1, none) and the two-name\n");
|
||||
printf(" transfer,pre-transfer form are rejected by name\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(" of 0 (the default) is randomized per transfer, exactly like\n");
|
||||
printf(" rsync, and the chosen seed is sent to the receiver\n");
|
||||
printf(" --delta Delta transfer for changed files (requires --incremental)\n");
|
||||
printf(" -W, --whole-file Transfer changed files without delta processing\n");
|
||||
printf(" --no-whole-file rsync spelling that clears -W/--whole-file\n");
|
||||
printf(" -y, --fuzzy Use a similar-named file already in the destination\n");
|
||||
printf(" directory as the delta basis when the destination has no\n");
|
||||
printf(" usable file at the exact path (saves bandwidth; implies\n");
|
||||
@@ -223,9 +230,10 @@ void print_usage(void) {
|
||||
printf(" --server-port <n> Server port (default: 8080)\n");
|
||||
printf(" --port <n> Alias for --server-port\n");
|
||||
printf(" --password-file <f> Authenticate a host::module/path daemon destination.\n");
|
||||
printf(" The file's first user:password line supplies the\n");
|
||||
printf(" username and password (only a SHA-256 digest of the\n");
|
||||
printf(" password is sent; keep the file mode 0600)\n");
|
||||
printf(" FastSync-native SCRAM/PBKDF2 credential scheme (NOT\n");
|
||||
printf(" rsync's --password-file): the file's first user:password\n");
|
||||
printf(" line supplies the username and password; no password or\n");
|
||||
printf(" reusable digest is sent (keep the file mode 0600)\n");
|
||||
printf(" --no-motd Suppress display of the daemon's MOTD (the server\n");
|
||||
printf(" still sends it; the client just does not show it)\n");
|
||||
printf(" --bwlimit <KB/s> Bandwidth limit in kilobytes per second\n");
|
||||
@@ -233,15 +241,19 @@ void print_usage(void) {
|
||||
printf(" --cert <path> TLS certificate file (PEM)\n");
|
||||
printf(" --key <path> TLS private key file (PEM)\n");
|
||||
printf(" --ca <path> TLS CA certificate file (PEM)\n");
|
||||
printf(" --timeout <sec> I/O timeout in seconds (default: 30; long form only)\n");
|
||||
printf(" --contimeout <sec> Connection timeout in seconds (default: 10)\n");
|
||||
printf(" --timeout <sec> I/O timeout in seconds (default: 0 = disabled, matching\n");
|
||||
printf(" rsync). 0 disables it; --no-timeout is the same\n");
|
||||
printf(" --contimeout <sec> Connection timeout in seconds (default: 60, matching\n");
|
||||
printf(" rsync); 0 disables it (--no-contimeout)\n");
|
||||
printf(" --stop-after=MINS Stop the transfer after MINS minutes (a positive\n");
|
||||
printf(" integer); whatever was already transferred is kept\n");
|
||||
printf(" --stop-at=TIME Stop at an absolute time: HH:MM, HH:MM:SS, or\n");
|
||||
printf(" now+N[smhd] (a time already in the past stops the\n");
|
||||
printf(" transfer immediately; client-only). An early stop\n");
|
||||
printf(" skips the late --delete keep-set so it cannot delete\n");
|
||||
printf(" source mirrors that were not yet scanned\n");
|
||||
printf(" --stop-at=TIME Stop at an absolute time. Accepts rsync's date form\n");
|
||||
printf(" (Y-M-DTh:m, Y/M/DTh:m, abbreviable fields such as 12-31,\n");
|
||||
printf(" 14:00, :59, 1) plus FastSync's HH:MM[:SS] and now+N[smhd]\n");
|
||||
printf(" (a time already in the past stops the transfer\n");
|
||||
printf(" immediately; client-only). An early stop skips the late\n");
|
||||
printf(" --delete keep-set so it cannot delete source mirrors that\n");
|
||||
printf(" were not yet scanned\n");
|
||||
printf(" --address <ip> Bind the outgoing client socket to this source address\n");
|
||||
printf(" -4, --ipv4 Force IPv4 for destination resolution\n");
|
||||
printf(" -6, --ipv6 Force IPv6 for destination resolution\n");
|
||||
@@ -262,19 +274,29 @@ void print_usage(void) {
|
||||
printf(" --stderr=MODE Route logging to stderr: errors or all\n");
|
||||
printf(" --partial Keep partial files on interrupted transfer\n");
|
||||
printf(" --partial-dir <dir> Directory for partial files\n");
|
||||
printf(" -T, --temp-dir <dir> Scratch dir for temp files before atomic install\n");
|
||||
printf(" -T, --temp-dir <dir> Scratch dir for temp files before atomic install.\n");
|
||||
printf(" Relative dirs resolve below the destination root; absolute\n");
|
||||
printf(" dirs are used as-is (rsync semantics). The dir must\n");
|
||||
printf(" already exist; a different filesystem falls back to a\n");
|
||||
printf(" non-atomic copy instead of aborting\n");
|
||||
printf(" --fastsync-server-path <path>\n");
|
||||
printf(" Path to fastsync-server on remote (default: fastsync-server)\n");
|
||||
printf(" --old-args Accepted for rsync CLI compatibility; no effect (the\n");
|
||||
printf(" remote server path is always safely quoted now)\n");
|
||||
printf(" -M, --remote-option=OPT Append OPT to the REMOTE server invocation over SSH\n");
|
||||
printf(" (repeatable; each value is single-quote-escaped on the remote\n");
|
||||
printf(" command line; empty values and values with control characters\n");
|
||||
printf(" are rejected; -M OPT, -M=OPT and --remote-option=OPT work)\n");
|
||||
printf(" --trust-sender Trust the remote sender's file list: the receiver skips its\n");
|
||||
printf(" own up-front path-traversal/containment re-validation of the\n");
|
||||
printf(" incoming file list (fewer checks, faster, potentially unsafe).\n");
|
||||
printf(" Local receiver policy: never sent to the peer, off by default\n");
|
||||
printf(" -M, --remote-option=OPT Append OPT to the REMOTE server invocation. SSH\n");
|
||||
printf(" transport ONLY (user@host:path): a daemon (host::module) or\n");
|
||||
printf(" local TCP destination rejects it (no remote command line to\n");
|
||||
printf(" append to). Repeatable; each value is single-quote-escaped on\n");
|
||||
printf(" the remote command line; empty values and values with control\n");
|
||||
printf(" characters are rejected; -M OPT, -M=OPT and\n");
|
||||
printf(" --remote-option=OPT work\n");
|
||||
printf(" --trust-sender RECEIVER-LOCAL policy: trust the remote sender's file list\n");
|
||||
printf(" and skip the receiver's own up-front path-traversal/\n");
|
||||
printf(" containment re-validation of the incoming list (fewer checks,\n");
|
||||
printf(" faster, potentially unsafe). It is never sent to the peer, so\n");
|
||||
printf(" for a push it must be enabled on the receiving SERVER\n");
|
||||
printf(" (fastsync-server --trust-sender) or forwarded with\n");
|
||||
printf(" -M--trust-sender; the client flag alone has no effect\n");
|
||||
printf(" -l, --links Copy symlinks as symlinks\n");
|
||||
printf(" -L, --copy-links Transform symlinks into referent files\n");
|
||||
printf(" --safe-links Skip symlinks whose target points outside the tree\n");
|
||||
@@ -302,7 +324,9 @@ void print_usage(void) {
|
||||
printf(" --fsync Fsync every written file before publication\n");
|
||||
printf(" --compress-level <n> Compression level (default: 5)\n");
|
||||
printf(" --zl <n> Alias for --compress-level\n");
|
||||
printf(" --skip-compress=LIST Skip compression for comma-separated suffixes\n");
|
||||
printf(" --skip-compress=LIST Skip compression for suffixes in LIST (separated by\n");
|
||||
printf(" '/' as in rsync, or ','); a leading dot is optional. The\n");
|
||||
printf(" default is rsync 3.4.1's built-in skip-compress list\n");
|
||||
printf(" --compress-threads <n> Compression worker threads (requires zstd threaded support)\n");
|
||||
printf(" --no-OPTION Disable a supported boolean option\n");
|
||||
printf(" --help Show this help\n");
|
||||
|
||||
Reference in New Issue
Block a user