fix: address c-review for -R/--dirs/--mkpath row

B1: destination-root existence/creation mishandled two legitimate forms.
file_directory_exists_secure/file_ensure_directory_secure now normalize a
trailing-slash destination (so the last component is never an empty leaf)
and treat a destination equal to the already-open authorized root as present
(no spurious <root>/<basename> nested dir with --mkpath). Confinement and
O_NOFOLLOW probing are unchanged. Regression integration tests: existing
dest with trailing slash works with and without --mkpath; dest == authorized
root works and creates no stray nested dir.

W1: chunk serialize/deserialize round-trip unit test for a directory entry
mixed with a regular file, with and without metadata; --dirs coverage under
-s and -s -m.
W2: --list-only and --dry-run now print file_wire_path() so all outputs show
the -R transformed relative name, matching -i/--out-format.
W3: RSYNC_COMPAT -d/--dirs note: dirs are created immediately under
--delay-updates (only regular files are staged).
W4: --dirs generator flushes chunks by element count too, so a long
--files-from list of empty directories cannot exceed the per-chunk file cap.
N1: STATUS_MKDIR added to status_to_string.
N2: removed dead TestMkpath._transfer.
N3: removed redundant --no-implied-dirs OPTION_TABLE row.
N4: integration tests: --dirs --delete keeps the just-created empty dir (and
deletes extras); a listed dir colliding with a regular file at the dest fails
cleanly.
This commit is contained in:
2026-09-06 16:10:00 +02:00
parent c2cf231158
commit 3275b6c978
8 changed files with 226 additions and 15 deletions
+92 -8
View File
@@ -1842,20 +1842,67 @@ class TestDirs:
assert not os.path.exists(os.path.join(received, "sub")), \
"unlisted subtree appeared"
@pytest.mark.parametrize("mt", [False, True])
def test_dirs_chunk_serialization(self, shared_server, mt):
"""--dirs entries survive the chunk-serialization wire path (type
marker round-trips); a listed dir lands empty and a listed file lands
with content, with no protocol desync under -s -m."""
source = self._make()
dest = os.path.join(TEST_DATA_DIR, "dirs_s_dst")
clean_dir(dest)
lst = _write_rel_list(b"dir1\nsub/x.txt\n")
flags = ["--files-from", lst, "--dirs", "-R", "-s"] + (["-m"] if mt else [])
result, _ = run_client(source, dest, flags=flags, port=shared_server.port)
assert result.returncode == 0, f"dirs -s sync failed: {result.stderr[:200]}"
assert os.path.isdir(os.path.join(dest, "dir1")), "listed dir was not created"
assert not os.path.exists(os.path.join(dest, "dir1", "keep.txt")), \
"--dirs must not descend into a listed directory"
assert _read_file(os.path.join(dest, "sub", "x.txt")) == b"x\n", \
"listed file content was not transferred"
def test_dirs_delete_keeps_transferred_empty_dir(self):
"""Directory entries appear in the delete manifest, so the empty dir a
--dirs run just created is not pruned as an extra by --delete."""
source = self._make()
dest = os.path.join(TEST_DATA_DIR, "dirs_del_dst")
clean_dir(dest)
extra = os.path.join(dest, "extra.txt")
with open(extra, "wb") as fh:
fh.write(b"delete me")
with ServerManager() as server:
server.start(extra_args=["--allow-delete"])
result, _ = run_client(source, dest, flags=["--dirs", "--delete"],
port=server.port)
assert result.returncode == 0, f"--dirs --delete sync failed: {result.stderr[:200]}"
assert not os.path.exists(extra), "--delete did not remove the extra file"
mirror = get_dest_received_dir(dest, source)
assert os.path.isdir(mirror), "transferred empty dir was pruned as an extra"
files = []
for root, _dirs, names in os.walk(mirror):
files.extend(os.path.relpath(os.path.join(root, n), mirror) for n in names)
assert files == [], f"--dirs descended into contents: {files}"
def test_dirs_listed_dir_colliding_with_file_fails(self, shared_server):
"""A listed directory that already exists as a regular file at the
destination fails the transfer cleanly instead of clobbering the file."""
source = self._make()
dest = os.path.join(TEST_DATA_DIR, "dirs_coll_dst")
clean_dir(dest)
blocker = os.path.join(dest, "dir1")
with open(blocker, "wb") as fh:
fh.write(b"blocking file")
lst = _write_rel_list(b"dir1\n")
result, _ = run_client(source, dest, flags=["--files-from", lst, "--dirs", "-R"],
port=shared_server.port)
assert result.returncode != 0, "dir entry over an existing file did not fail"
assert os.path.isfile(blocker), "blocking regular file was clobbered"
class TestMkpath:
"""--mkpath tells the server to create the destination root directory (and
missing leading components) when it does not exist yet; without it a missing
destination root fails the transfer."""
def _transfer(self, dest, mt, mkpath):
source = _make_relative_source("mkpath_src")
flags = ["--mkpath"] if mkpath else []
if mt:
flags += ["-m"]
result, _ = run_client(source, dest, flags=flags, port=self.server.port)
return result
@pytest.mark.parametrize("mt", [False, True])
def test_missing_root_fails_without_mkpath(self, mt):
source = _make_relative_source("mkpath_fail_src")
@@ -1882,6 +1929,43 @@ class TestMkpath:
assert _read_file(os.path.join(received, "sub", "x.txt")) == b"x\n", \
"file not transferred into the --mkpath-created root"
@pytest.mark.parametrize("mkpath", [False, True])
def test_existing_dest_with_trailing_slash(self, shared_server, mkpath):
"""A destination root written with a trailing slash must keep working:
an existing root is accepted both with and without --mkpath."""
source = _make_relative_source("mkpath_trail_src")
dest = os.path.join(TEST_DATA_DIR, "mkpath_trail_dst")
clean_dir(dest)
dest_slash = dest + "/"
flags = ["--mkpath"] if mkpath else []
result, _ = run_client(source, dest_slash, flags=flags, port=shared_server.port)
assert result.returncode == 0, \
f"trailing-slash dest sync (mkpath={mkpath}) failed: {result.stderr[:200]}"
received = get_dest_received_dir(dest, source)
assert _read_file(os.path.join(received, "sub", "x.txt")) == b"x\n", \
"file not transferred into the trailing-slash destination root"
@pytest.mark.parametrize("mkpath", [False, True])
def test_dest_equal_authorized_root(self, mkpath):
"""A destination that is exactly the server's authorized root works
without --mkpath, and with --mkpath creates no stray <root>/<basename>
nested directory."""
root = os.path.join(TEST_DATA_DIR, "mkpath_eq_root")
clean_dir(root)
source = _make_relative_source("mkpath_eq_src")
with ServerManager() as server:
server.start(extra_args=["--destination-root", root])
flags = ["--mkpath"] if mkpath else []
result, _ = run_client(source, root, flags=flags, port=server.port)
assert result.returncode == 0, \
f"dest==authorized-root sync (mkpath={mkpath}) failed: {result.stderr[:200]}"
received = get_dest_received_dir(root, source)
assert _read_file(os.path.join(received, "sub", "x.txt")) == b"x\n", \
"file not transferred when the dest equals the authorized root"
basename = os.path.basename(root.rstrip(os.sep))
assert not os.path.exists(os.path.join(root, basename)), \
"--mkpath created a spurious nested <root>/<basename> directory"
class TestFilters:
"""--filter/-C/-F rule layer: excludes prune, ordering is first-match-wins,