Release v2.29.0 #312

Merged
TapTap merged 123 commits from dev into main 2026-09-23 02:05:14 +02:00
3 changed files with 87 additions and 31 deletions
Showing only changes of commit b478a59a81 - Show all commits
+1 -1
View File
@@ -53,7 +53,7 @@ bool validate_config(const Config* config) {
return false; return false;
} }
if (config->compression_threads > 0 && !config->use_compression) { if (config->compression_threads > 0 && !config->use_compression) {
log_message(LOG_LEVEL_ERROR, "--compress-threads requires compression (-c or -z)"); log_message(LOG_LEVEL_ERROR, "--compress-threads requires compression (-z/--compress)");
return false; return false;
} }
if (config->transport == TRANSPORT_SSH && config->use_sendfile) { if (config->transport == TRANSPORT_SSH && config->use_sendfile) {
+22 -10
View File
@@ -19,7 +19,9 @@ void print_usage(void) {
printf("\n"); printf("\n");
printf("Options:\n"); printf("Options:\n");
printf(" -c, --checksum Verify content by checksum instead of size+mtime\n"); printf(" -c, --checksum Verify content by checksum instead of size+mtime\n");
printf(" -z, --compress [level] Enable compression (level 1-22, default 5)\n"); printf(" -z, --compress [level] Enable compression. The default level is\n");
printf(" per-codec: zstd 3 (range 1-22), zlib/zlibx 6, lz4\n");
printf(" ignores the level\n");
printf(" -a, --archive rsync archive mode (-rlptgoD): links, perms, times,\n"); printf(" -a, --archive rsync archive mode (-rlptgoD): links, perms, times,\n");
printf(" owner, group, devices and specials; not\n"); printf(" owner, group, devices and specials; not\n");
printf(" compression/multithreading\n"); printf(" compression/multithreading\n");
@@ -36,8 +38,8 @@ void print_usage(void) {
printf(" arguments, e.g. -e \"ssh -p 2222\"\n"); printf(" arguments, e.g. -e \"ssh -p 2222\"\n");
printf(" --rsync-path <path> Alias for --fastsync-server-path (path to the\n"); printf(" --rsync-path <path> Alias for --fastsync-server-path (path to the\n");
printf(" fastsync server binary on the remote side)\n"); printf(" fastsync server binary on the remote side)\n");
printf(" --blocking-io Leave the SSH transport socket without read/write\n"); printf(" --blocking-io SSH transport only: leave the socket without read/write\n");
printf(" timeouts so it blocks naturally\n"); printf(" timeouts so it blocks naturally (no effect on TCP)\n");
printf(" --outbuf=MODE stdout/stderr buffering: N (none/unbuffered),\n"); printf(" --outbuf=MODE stdout/stderr buffering: N (none/unbuffered),\n");
printf(" L (line-buffered), or B (block-buffered, default)\n"); printf(" L (line-buffered), or B (block-buffered, default)\n");
printf(" --progress Show transfer progress\n"); printf(" --progress Show transfer progress\n");
@@ -49,6 +51,7 @@ void print_usage(void) {
printf(" converted before transmission and back on receipt; a\n"); printf(" converted before transmission and back on receipt; a\n");
printf(" name that cannot be represented in the target charset\n"); printf(" name that cannot be represented in the target charset\n");
printf(" fails that transfer cleanly (rsync-compatible)\n"); printf(" fails that transfer cleanly (rsync-compatible)\n");
printf(" --no-iconv Disable --iconv charset conversion (same as --iconv=-)\n");
printf(" --protocol=NUM Force the wire protocol version (must equal the current\n"); printf(" --protocol=NUM Force the wire protocol version (must equal the current\n");
printf(" PROTOCOL_VERSION; FastSync cannot speak older/virtual\n"); printf(" PROTOCOL_VERSION; FastSync cannot speak older/virtual\n");
printf(" wire formats)\n"); printf(" wire formats)\n");
@@ -192,6 +195,10 @@ void print_usage(void) {
printf(" -U, --atimes Preserve access times\n"); printf(" -U, --atimes Preserve access times\n");
printf(" -N, --crtimes Capture birth time; cannot be applied (documented\n"); printf(" -N, --crtimes Capture birth time; cannot be applied (documented\n");
printf(" divergence)\n"); printf(" divergence)\n");
printf(" -O, --omit-dir-times Do not apply modification times to directories\n");
printf(" -J, --omit-link-times Do not apply times to symlinks\n");
printf(" --open-noatime Open source files with O_NOATIME so reading for a\n");
printf(" transfer does not update their access time\n");
printf(" -X, --xattrs Preserve user extended attributes (user.* only;\n"); printf(" -X, --xattrs Preserve user extended attributes (user.* only;\n");
printf(" privileged security.*/trusted.* namespaces are\n"); printf(" privileged security.*/trusted.* namespaces are\n");
printf(" never captured or applied)\n"); printf(" never captured or applied)\n");
@@ -287,6 +294,10 @@ void print_usage(void) {
printf(" -x, --one-file-system Do not cross filesystem boundaries\n"); printf(" -x, --one-file-system Do not cross filesystem boundaries\n");
printf(" --log-file <path>, --log-file=<path> Write log messages to file\n"); printf(" --log-file <path>, --log-file=<path> Write log messages to file\n");
printf(" --stderr=MODE Route logging to stderr: errors or all\n"); printf(" --stderr=MODE Route logging to stderr: errors or all\n");
printf(" --msgs2stderr Route all messages to stderr (deprecated spelling of\n");
printf(" --stderr=all)\n");
printf(" --no-msgs2stderr Select errors-only stderr (deprecated spelling; the\n");
printf(" default)\n");
printf(" --partial Keep partial files on interrupted transfer\n"); printf(" --partial Keep partial files on interrupted transfer\n");
printf(" --partial-dir <dir> Directory for partial files (implies --partial)\n"); printf(" --partial-dir <dir> Directory for partial files (implies --partial)\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");
@@ -337,7 +348,8 @@ void print_usage(void) {
printf(" --append-verify Like --append, but verifies the retained prefix checksum\n"); printf(" --append-verify Like --append, but verifies the retained prefix checksum\n");
printf(" before appending (falls back to a full transfer on mismatch)\n"); printf(" before appending (falls back to a full transfer on mismatch)\n");
printf(" --fsync Fsync every written file before publication\n"); printf(" --fsync Fsync every written file before publication\n");
printf(" --compress-level <n> Compression level (default: 5)\n"); printf(" --compress-level <n> Compression level (per-codec default: zstd 3,\n");
printf(" zlib/zlibx 6, lz4 ignores it)\n");
printf(" --zl <n> Alias for --compress-level\n"); printf(" --zl <n> Alias for --compress-level\n");
printf(" --skip-compress=LIST Skip compression for suffixes in LIST (separated by\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(" '/' as in rsync, or ','); a leading dot is optional. The\n");
@@ -349,19 +361,19 @@ void print_usage(void) {
} }
void print_debug_usage(void) { void print_debug_usage(void) {
printf("Emitting debug flags: IO,PROTO,PACK,UTIL,ALL,NONE\n"); printf("Emitting debug flags: IO,PROTO,PACK,UTIL,FLIST,DEL,HASH,DELTASUM,\n");
printf("RECV,FILTER,SEND,ALL,NONE\n");
printf("Also accepted for rsync CLI parity (silent): ACL,BACKUP,BIND,CHDIR,\n"); printf("Also accepted for rsync CLI parity (silent): ACL,BACKUP,BIND,CHDIR,\n");
printf("CONNECT,CMD,DEL,DELTASUM,DUP,EXIT,FILTER,FLIST,FUZZY,GENR,HASH,HLINK,\n"); printf("CONNECT,CMD,DUP,EXIT,FUZZY,GENR,HLINK,ICONV,NSTR,OWN,TIME.\n");
printf("ICONV,NSTR,OWN,RECV,SEND,TIME.\n");
printf("Flags may be comma-separated, for example: --debug=io,proto\n"); printf("Flags may be comma-separated, for example: --debug=io,proto\n");
printf("An optional level suffix is accepted (e.g. --debug=io2); level 0\n"); printf("An optional level suffix is accepted (e.g. --debug=io2); level 0\n");
printf("silences that item. Unknown names are rejected.\n"); printf("silences that item. Unknown names are rejected.\n");
} }
void print_info_usage(void) { void print_info_usage(void) {
printf("Emitting info flags: COPY,NAME,MISC,SKIP,STATS,ALL,NONE\n"); printf("Emitting info flags: COPY,MISC,SKIP,STATS,DEL,REMOVE,NAME,FLIST,\n");
printf("Also accepted for rsync CLI parity (silent): BACKUP,DEL,FLIST,MOUNT,\n"); printf("NONREG,PROGRESS,MOUNT,ALL,NONE\n");
printf("NONREG,PROGRESS,REMOVE,SYMSAFE.\n"); printf("Also accepted for rsync CLI parity (silent): BACKUP,SYMS,SYMSAFE.\n");
printf("Flags may be comma-separated, for example: --info=name,stats\n"); printf("Flags may be comma-separated, for example: --info=name,stats\n");
printf("An optional level suffix is accepted (e.g. --info=stats2); level 0\n"); printf("An optional level suffix is accepted (e.g. --info=stats2); level 0\n");
printf("silences that item. Unknown names are rejected.\n"); printf("silences that item. Unknown names are rejected.\n");
+64 -20
View File
@@ -1329,35 +1329,78 @@ static void test_parse_args_delete_timing_without_delete_rejected() {
config_delete(cfg); config_delete(cfg);
} }
/* Parsed-but-unimplemented options must fail instead of being silently accepted. */ /* Truly-unknown options (including server-only spellings) must be rejected
static void test_parse_args_rejects_unimplemented_options() { * through the unknown-option path instead of being silently accepted. */
static const char* const options[] = {"--silent", static void test_parse_args_rejects_unknown_options() {
"--queue-size", static const char* const options[] = {"--silent", "--queue-size", "--bind-address",
"-A", "--daemon", "--config", "--server"};
"--acls",
"-X",
"--xattrs",
"-D",
"--devices",
"--delete-excluded",
"--max-delete",
"--prune-empty-dirs",
"--bind-address",
"--daemon",
"--config",
"--server"};
for (size_t i = 0; i < sizeof(options) / sizeof(options[0]); i++) { for (size_t i = 0; i < sizeof(options) / sizeof(options[0]); i++) {
Config* cfg = config_create(); Config* cfg = config_create();
char* argv[] = {"fastsync", (char*)options[i], "dummy", "/src", "/dst"}; char* argv[] = {"fastsync", (char*)options[i], "/src", "/dst"};
int positional_args[2]; int positional_args[2];
int positional_count = 0; int positional_count = 0;
EXPECT_EQ_INT(parse_args(cfg, 5, argv, positional_args, &positional_count), -1); EXPECT_EQ_INT(parse_args(cfg, 4, argv, positional_args, &positional_count), -1);
config_delete(cfg); config_delete(cfg);
} }
} }
/* Options that are genuinely implemented must parse successfully and record
* their effect, rather than being lumped in with the unknown-option set. */
static void test_parse_args_accepts_implemented_metadata_options() {
Config* cfg = config_create();
char* argv_x[] = {"fastsync", "-X", "/src", "/dst"};
int positional_args[2];
int positional_count = 0;
EXPECT_EQ_INT(parse_args(cfg, 4, argv_x, positional_args, &positional_count), 0);
EXPECT_TRUE(cfg->preserve_xattrs);
config_delete(cfg);
cfg = config_create();
positional_count = 0;
char* argv_acls[] = {"fastsync", "--acls", "/src", "/dst"};
EXPECT_EQ_INT(parse_args(cfg, 4, argv_acls, positional_args, &positional_count), 0);
EXPECT_TRUE(cfg->preserve_acls);
config_delete(cfg);
cfg = config_create();
positional_count = 0;
char* argv_d[] = {"fastsync", "-D", "/src", "/dst"};
EXPECT_EQ_INT(parse_args(cfg, 4, argv_d, positional_args, &positional_count), 0);
EXPECT_TRUE(cfg->preserve_devices);
EXPECT_TRUE(cfg->preserve_specials);
config_delete(cfg);
cfg = config_create();
positional_count = 0;
char* argv_devices[] = {"fastsync", "--devices", "/src", "/dst"};
EXPECT_EQ_INT(parse_args(cfg, 4, argv_devices, positional_args, &positional_count), 0);
EXPECT_TRUE(cfg->preserve_devices);
config_delete(cfg);
cfg = config_create();
positional_count = 0;
char* argv_delete_excluded[] = {"fastsync", "--delete-excluded", "/src", "/dst"};
EXPECT_EQ_INT(parse_args(cfg, 4, argv_delete_excluded, positional_args, &positional_count), 0);
EXPECT_TRUE(cfg->delete_excluded);
config_delete(cfg);
cfg = config_create();
positional_count = 0;
char* argv_max_delete[] = {"fastsync", "--max-delete=5", "/src", "/dst"};
EXPECT_EQ_INT(parse_args(cfg, 4, argv_max_delete, positional_args, &positional_count), 0);
EXPECT_EQ_INT(cfg->max_delete, 5);
config_delete(cfg);
cfg = config_create();
positional_count = 0;
char* argv_prune[] = {"fastsync", "--prune-empty-dirs", "/src", "/dst"};
EXPECT_EQ_INT(parse_args(cfg, 4, argv_prune, positional_args, &positional_count), 0);
EXPECT_TRUE(cfg->prune_empty_dirs);
config_delete(cfg);
}
/* Test both rsync-compatible quiet spellings and option ordering. */ /* Test both rsync-compatible quiet spellings and option ordering. */
static void test_parse_args_quiet() { static void test_parse_args_quiet() {
static const char* const options[][2] = { static const char* const options[][2] = {
@@ -4908,7 +4951,8 @@ void test_client_cli() {
test_parse_args_delete_default_timing_and_commit(); test_parse_args_delete_default_timing_and_commit();
test_parse_args_delete_timing_conflict_rejected(); test_parse_args_delete_timing_conflict_rejected();
test_parse_args_delete_timing_without_delete_rejected(); test_parse_args_delete_timing_without_delete_rejected();
test_parse_args_rejects_unimplemented_options(); test_parse_args_rejects_unknown_options();
test_parse_args_accepts_implemented_metadata_options();
test_parse_args_quiet(); test_parse_args_quiet();
test_parse_args_human_readable(); test_parse_args_human_readable();
test_parse_args_hard_links(); test_parse_args_hard_links();