feat: add -x/--one-file-system to stay within the source filesystem
Capture the transfer root's device (st_dev) at scanner creation and skip descending into any subdirectory on a different device (a mount point). Implemented sender/client-side only: sequential BFS and parallel (-m) root scan apply the same scanner_same_filesystem decision; no wire/protocol change and default behavior is unchanged. Unit tests cover the pure decision, same device scanning in both modes, and CLI parsing; integration tests prove -x leaves a single-filesystem tree byte-identical and, when root can mount a tmpfs, skips a genuine cross-device subtree.
This commit is contained in:
+95
-2
@@ -394,8 +394,8 @@ static void test_parallel_scanner_root_chunks_without_workers() {
|
||||
create_test_file(file1, "a");
|
||||
create_test_file(file2, "b");
|
||||
|
||||
ScannerOptions options = {false, 1, NULL, 0, NULL, 0, 0, 0,
|
||||
0, 0, false, false, false, false, false};
|
||||
ScannerOptions options = {false, 1, NULL, 0, NULL, 0, 0, 0,
|
||||
0, 0, false, false, false, false, false, false};
|
||||
ParallelScanner* scanner = parallel_scanner_create_with_options(dir, &options, NULL);
|
||||
EXPECT_NOT_NULL(scanner);
|
||||
|
||||
@@ -414,6 +414,96 @@ static void test_parallel_scanner_root_chunks_without_workers() {
|
||||
rmdir(dir);
|
||||
}
|
||||
|
||||
/* --one-file-system (-x) decision is a pure device comparison. */
|
||||
static void test_scanner_one_file_system_decision() {
|
||||
/* Option disabled: every device is allowed (unchanged default behavior). */
|
||||
EXPECT_TRUE(scanner_same_filesystem(false, 0, 123));
|
||||
EXPECT_TRUE(scanner_same_filesystem(false, 7, 999));
|
||||
/* Option enabled: only entries on the root device may be descended into. */
|
||||
EXPECT_TRUE(scanner_same_filesystem(true, 7, 7));
|
||||
EXPECT_FALSE(scanner_same_filesystem(true, 7, 8));
|
||||
}
|
||||
|
||||
/* With -x over an ordinary tree (all one device) nothing may be skipped. */
|
||||
static void test_scanner_one_file_system_same_device() {
|
||||
const char* root = "test_scan_ofs";
|
||||
const char* sub = "test_scan_ofs/sub";
|
||||
const char* deeper = "test_scan_ofs/sub/deeper";
|
||||
const char* root_file = "test_scan_ofs/root.txt";
|
||||
const char* sub_file = "test_scan_ofs/sub/inner.txt";
|
||||
const char* deep_file = "test_scan_ofs/sub/deeper/deep.txt";
|
||||
|
||||
EXPECT_EQ_INT(mkdir(root, 0755), 0);
|
||||
EXPECT_EQ_INT(mkdir(sub, 0755), 0);
|
||||
EXPECT_EQ_INT(mkdir(deeper, 0755), 0);
|
||||
create_test_file(root_file, "root");
|
||||
create_test_file(sub_file, "inner");
|
||||
create_test_file(deep_file, "deep");
|
||||
|
||||
ScannerOptions options = {0};
|
||||
options.one_file_system = true;
|
||||
DirectoryScanner* scanner = directory_scanner_create_with_options(root, &options);
|
||||
EXPECT_NOT_NULL(scanner);
|
||||
|
||||
int total_files = 0;
|
||||
Chunk* chunk;
|
||||
while ((chunk = directory_scanner_next(scanner)) != NULL) {
|
||||
total_files += chunk->element_count;
|
||||
chunk_destroy(chunk);
|
||||
}
|
||||
EXPECT_EQ_INT(total_files, 3);
|
||||
EXPECT_FALSE(directory_scanner_failed(scanner));
|
||||
|
||||
directory_scanner_destroy(scanner);
|
||||
unlink(root_file);
|
||||
unlink(sub_file);
|
||||
unlink(deep_file);
|
||||
rmdir(deeper);
|
||||
rmdir(sub);
|
||||
rmdir(root);
|
||||
}
|
||||
|
||||
/* Multithreaded (-m) scan with -x over a single-device tree must match the
|
||||
* single-threaded result. */
|
||||
static void test_parallel_scanner_one_file_system_same_device() {
|
||||
const char* root = "test_parallel_scan_ofs";
|
||||
const char* sub = "test_parallel_scan_ofs/sub";
|
||||
const char* sub2 = "test_parallel_scan_ofs/sub2";
|
||||
const char* root_file = "test_parallel_scan_ofs/root.txt";
|
||||
const char* sub_file = "test_parallel_scan_ofs/sub/inner.txt";
|
||||
const char* sub2_file = "test_parallel_scan_ofs/sub2/inner2.txt";
|
||||
|
||||
EXPECT_EQ_INT(mkdir(root, 0755), 0);
|
||||
EXPECT_EQ_INT(mkdir(sub, 0755), 0);
|
||||
EXPECT_EQ_INT(mkdir(sub2, 0755), 0);
|
||||
create_test_file(root_file, "root");
|
||||
create_test_file(sub_file, "inner");
|
||||
create_test_file(sub2_file, "inner2");
|
||||
|
||||
ScannerOptions options = {0};
|
||||
options.one_file_system = true;
|
||||
options.num_threads = 2;
|
||||
ParallelScanner* scanner = parallel_scanner_create_with_options(root, &options, NULL);
|
||||
EXPECT_NOT_NULL(scanner);
|
||||
|
||||
int total_files = 0;
|
||||
Chunk* chunk;
|
||||
while ((chunk = parallel_scanner_next(scanner)) != NULL) {
|
||||
total_files += chunk->element_count;
|
||||
chunk_destroy(chunk);
|
||||
}
|
||||
EXPECT_EQ_INT(total_files, 3);
|
||||
EXPECT_FALSE(parallel_scanner_failed(scanner));
|
||||
|
||||
parallel_scanner_destroy(scanner);
|
||||
unlink(root_file);
|
||||
unlink(sub_file);
|
||||
unlink(sub2_file);
|
||||
rmdir(sub);
|
||||
rmdir(sub2);
|
||||
rmdir(root);
|
||||
}
|
||||
|
||||
void test_scanner() {
|
||||
test_scanner_single_file();
|
||||
test_scanner_multiple_files();
|
||||
@@ -429,4 +519,7 @@ void test_scanner() {
|
||||
test_scanner_mixed_patterns();
|
||||
test_scanner_no_patterns();
|
||||
test_parallel_scanner_root_chunks_without_workers();
|
||||
test_scanner_one_file_system_decision();
|
||||
test_scanner_one_file_system_same_device();
|
||||
test_parallel_scanner_one_file_system_same_device();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user