fix: filter precedence, files-from errors, NUL/CRLF and filter-rule rejections
Address an independent c-review of the files-from/filter feature: - .rsync-filter precedence now matches rsync: evaluate the innermost (current) directory's rules first, then ancestors, then the command-line base (--filter/-C), so a deeper file's '+' can re-include what a shallower '-' excluded (regression tests in both scan modes; single-thread and -m). - --files-from: a listed entry missing on disk and an empty list are now hard errors surfaced pre-transfer in send_files, send_files_multithreaded, dry-run and --list-only; '.' (whole tree) and empty listed dirs stay valid. - scanner_path_relative now handles a transfer root of / (previously the scanner aborted on children of /). - Reject unsupported rsync filter syntax explicitly (no silent no-ops): +/- modifiers other than '/' (! C s r p x) and rules beginning with ':'/'.' /'!' (merge/dir-merge/list-clear shorthands). Docs updated. - -0/--from0 NUL mode preserves entry bytes (no CR/LF trimming); only newline mode trims. Absolute-entry error message no longer includes the newline. - --no-from0/--no-cvs-exclude registered as negatable booleans. - RSYNC_COMPAT rows updated for the precedence, rejection list, NUL-mode detail and the documented O(entries x files) scalability bound of the allow-set (Summary unchanged: 62/3/5/1/76 = 147).
This commit is contained in:
+53
-1
@@ -1289,6 +1289,29 @@ static void test_parse_args_filter_rules() {
|
||||
char* missing_argv[] = {"fastsync", "/src", "/dst", "--filter"};
|
||||
EXPECT_EQ_INT(parse_args(cfg, 4, missing_argv, positional_args, &positional_count), -1);
|
||||
config_delete(cfg);
|
||||
|
||||
/* rsync shorthands/modifiers we do not support are rejected instead of being
|
||||
* silently parsed as literal patterns. */
|
||||
static const char* const unsupported[] = {
|
||||
": .rsync-filter", ". /tmp/rules", "-s foo", "-p bar", "-C", "-! *.o", "!",
|
||||
};
|
||||
for (size_t i = 0; i < sizeof(unsupported) / sizeof(unsupported[0]); i++) {
|
||||
cfg = config_create();
|
||||
positional_count = 0;
|
||||
char* rule_argv[] = {"fastsync", "--filter", (char*)unsupported[i], "/src", "/dst"};
|
||||
EXPECT_EQ_INT(parse_args(cfg, 5, rule_argv, positional_args, &positional_count), -1);
|
||||
config_delete(cfg);
|
||||
}
|
||||
|
||||
/* Supported spellings still parse: space- or slash-separated, attached
|
||||
* wildcards, and anchored rules. */
|
||||
cfg = config_create();
|
||||
positional_count = 0;
|
||||
char* ok_argv[] = {"fastsync", "--filter=-*.o", "--filter=- /foo",
|
||||
"--filter=+ /bar/", "/src", "/dst"};
|
||||
EXPECT_EQ_INT(parse_args(cfg, 6, ok_argv, positional_args, &positional_count), 0);
|
||||
EXPECT_EQ_INT(cfg->filters->size, 3);
|
||||
config_delete(cfg);
|
||||
}
|
||||
|
||||
/* -0/--from0, -C/--cvs-exclude and -F wire into their config flags. */
|
||||
@@ -1316,6 +1339,19 @@ static void test_parse_args_from0_cvs_filter_file_flags() {
|
||||
EXPECT_EQ_INT(cfg->per_dir_filter, cases[i].per_dir);
|
||||
config_delete(cfg);
|
||||
}
|
||||
|
||||
/* The plain booleans are negatable (--no-* simply clears the flag). */
|
||||
static const char* const on[][2] = {{"--from0", "--no-from0"}, {"-C", "--no-cvs-exclude"}};
|
||||
for (size_t i = 0; i < sizeof(on) / sizeof(on[0]); i++) {
|
||||
Config* cfg = config_create();
|
||||
int positional_args[2];
|
||||
int positional_count = 0;
|
||||
char* argv[] = {"fastsync", (char*)on[i][0], (char*)on[i][1], "/src", "/dst"};
|
||||
EXPECT_EQ_INT(parse_args(cfg, 5, argv, positional_args, &positional_count), 0);
|
||||
EXPECT_FALSE(cfg->from0);
|
||||
EXPECT_FALSE(cfg->cvs_exclude);
|
||||
config_delete(cfg);
|
||||
}
|
||||
}
|
||||
|
||||
static void write_file_bytes(const char* path, const char* bytes, size_t len) {
|
||||
@@ -1347,7 +1383,8 @@ static void test_parse_args_files_from() {
|
||||
config_delete(cfg);
|
||||
remove(list_path);
|
||||
|
||||
/* -0 switches the separator to NUL regardless of argument order. */
|
||||
/* -0 switches the separator to NUL regardless of argument order, and NUL
|
||||
* mode preserves entry bytes exactly (a trailing CR/LF is part of the name). */
|
||||
write_file_bytes(list_path, "x.txt\0y/z.bin\0", 14);
|
||||
cfg = config_create();
|
||||
positional_count = 0;
|
||||
@@ -1365,6 +1402,21 @@ static void test_parse_args_files_from() {
|
||||
config_delete(cfg);
|
||||
remove(list_path);
|
||||
|
||||
write_file_bytes(list_path, "crlf\n\0tail\0", 11);
|
||||
cfg = config_create();
|
||||
positional_count = 0;
|
||||
char* nul_nl_argv[] = {"fastsync",
|
||||
"--files-from="
|
||||
"cli_files_from_list.txt",
|
||||
"-0", "/src", "/dst"};
|
||||
EXPECT_EQ_INT(parse_args(cfg, 5, nul_nl_argv, positional_args, &positional_count), 0);
|
||||
set = (FileListSet*)cfg->files_from_set;
|
||||
EXPECT_NOT_NULL(set);
|
||||
EXPECT_TRUE(file_list_affects(set, "crlf\n"));
|
||||
EXPECT_TRUE(file_list_affects(set, "tail"));
|
||||
config_delete(cfg);
|
||||
remove(list_path);
|
||||
|
||||
/* A missing list file is a hard parse-time error. */
|
||||
cfg = config_create();
|
||||
positional_count = 0;
|
||||
|
||||
Reference in New Issue
Block a user