From 8343746210ed40f583ac7078b10382d4c2ee48b6 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Sat, 9 May 2026 01:35:20 +0200 Subject: [PATCH] Fix slashdot hack excluding source directory metadata, fixes #9534 When using the slashdot hack (e.g. `borg create ARCHIVE rootfs/./`), the source directory's metadata was being excluded instead of archived as the archive root. This happened because `create_helper` treated the slashdot target directory the same as its parent directories (which should be stripped), rather than recognizing it as the root of the archive. Added a new condition in `create_helper` to detect when the current path matches the strip prefix target exactly (`path + "/" == strip_prefix`) and archive it as `"."` (the archive root) instead of excluding it. --- src/borg/archive.py | 10 +++++++--- .../testsuite/archiver/create_cmd_test.py | 19 +++++++++++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/borg/archive.py b/src/borg/archive.py index dcc85deef..f381d1c71 100644 --- a/src/borg/archive.py +++ b/src/borg/archive.py @@ -1277,12 +1277,16 @@ class FilesystemObjectProcessors: def create_helper(self, path, st, status=None, hardlinkable=True, strip_prefix=None): if strip_prefix is not None: assert not path.endswith("/") - if strip_prefix.startswith(path + "/"): + if path + "/" == strip_prefix: + # this is the directory the slashdot hack points to - archive it as the root. + path = "." + elif strip_prefix.startswith(path + "/"): # still on a directory level that shall be stripped - do not create an item for this! yield None, "x", False, None return - # adjust path, remove stripped directory levels - path = path.removeprefix(strip_prefix) + else: + # adjust path, remove stripped directory levels + path = path.removeprefix(strip_prefix) sanitized_path = remove_dotdot_prefixes(path) item = Item(path=sanitized_path) diff --git a/src/borg/testsuite/archiver/create_cmd_test.py b/src/borg/testsuite/archiver/create_cmd_test.py index 498e02e89..b292bd0ff 100644 --- a/src/borg/testsuite/archiver/create_cmd_test.py +++ b/src/borg/testsuite/archiver/create_cmd_test.py @@ -1002,6 +1002,25 @@ def test_create_dotslash_hack(archivers, request): assert "secondB/thirdB" in output +def test_create_dotslash_hack_root_metadata(archivers, request): + """Test that the slashdot hack archives the source directory metadata as the archive root.""" + archiver = request.getfixturevalue(archivers) + os.makedirs(os.path.join(archiver.input_path, "first", "subdir")) + create_regular_file(archiver.input_path, "first/file1", contents=b"hello") + cmd(archiver, "repo-create", RK_ENCRYPTION) + cmd(archiver, "create", "test", "input/first/./") # slashdot hack + output = cmd(archiver, "list", "test") + # the root directory "." must be in the archive (this was the bug in #9534). + lines = output.splitlines() + assert lines[0].endswith(" .") + # children of the slashdot target must be archived. + assert "subdir" in output + assert "file1" in output + # parent directories must NOT be in the archive. + assert "input" not in output + assert "first" not in output + + def test_log_json(archivers, request): archiver = request.getfixturevalue(archivers) create_test_files(archiver.input_path)