From 84245ef2bf97c91b4dae96dd3a6a13be51d038ee Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Sat, 5 Oct 2024 20:52:26 +0200 Subject: [PATCH 1/2] mount: create unique directory names, fixes #8461 --- src/borg/fuse.py | 5 +++-- src/borg/testsuite/archiver/mount_cmds_test.py | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/borg/fuse.py b/src/borg/fuse.py index 654c350e7..a060f9938 100644 --- a/src/borg/fuse.py +++ b/src/borg/fuse.py @@ -39,7 +39,7 @@ def async_wrapper(fn): from .archiver._common import build_matcher, build_filter from .archive import Archive, get_item_uid_gid from .hashindex import FuseVersionsIndex -from .helpers import daemonize, daemonizing, signal_handler, format_file_size +from .helpers import daemonize, daemonizing, signal_handler, format_file_size, bin_to_hex from .helpers import HardLinkManager from .helpers import msgpack from .helpers.lrucache import LRUCache @@ -284,7 +284,8 @@ def _create_filesystem(self): else: # lazily load archives, create archive placeholder inode archive_inode = self._create_dir(parent=1, mtime=int(archive.ts.timestamp() * 1e9)) - self.contents[1][os.fsencode(archive.name)] = archive_inode + name = f"{archive.name}-{bin_to_hex(archive.id):.8}" # archive names may be duplicate! + self.contents[1][os.fsencode(name)] = archive_inode self.pending_archives[archive_inode] = archive def get_item(self, inode): diff --git a/src/borg/testsuite/archiver/mount_cmds_test.py b/src/borg/testsuite/archiver/mount_cmds_test.py index 7c60e394c..7f15ca938 100644 --- a/src/borg/testsuite/archiver/mount_cmds_test.py +++ b/src/borg/testsuite/archiver/mount_cmds_test.py @@ -200,6 +200,20 @@ def test_fuse_versions_view(archivers, request): assert open(hl3, "rb").read() == b"123456" +@pytest.mark.skipif(not llfuse, reason="llfuse not installed") +def test_fuse_duplicate_name(archivers, request): + archiver = request.getfixturevalue(archivers) + cmd(archiver, "repo-create", RK_ENCRYPTION) + cmd(archiver, "create", "archive", "input") + cmd(archiver, "create", "archive", "input") + mountpoint = os.path.join(archiver.tmpdir, "mountpoint") + # mount the whole repository, archives show up as toplevel directories: + with fuse_mount(archiver, mountpoint): + path = os.path.join(mountpoint) + dirs = os.listdir(path) + assert len(set(dirs)) == 2 # there must be 2 unique dir names for 2 archives + + @pytest.mark.skipif(not llfuse, reason="llfuse not installed") def test_fuse_allow_damaged_files(archivers, request): archiver = request.getfixturevalue(archivers) From f624f76c13cb01e28793d5ebb433c5a66fae3adc Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Sat, 5 Oct 2024 21:14:30 +0200 Subject: [PATCH 2/2] mount: only append archive id when needed --- src/borg/fuse.py | 11 ++++++++--- src/borg/testsuite/archiver/mount_cmds_test.py | 10 +++++++--- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/borg/fuse.py b/src/borg/fuse.py index a060f9938..9636c932e 100644 --- a/src/borg/fuse.py +++ b/src/borg/fuse.py @@ -7,7 +7,7 @@ import sys import tempfile import time -from collections import defaultdict +from collections import defaultdict, Counter from signal import SIGINT from .constants import ROBJ_FILE_STREAM @@ -277,14 +277,19 @@ def __init__(self, manifest, args, decrypted_repository): def _create_filesystem(self): self._create_dir(parent=1) # first call, create root dir (inode == 1) self.versions_index = FuseVersionsIndex() - for archive in self._manifest.archives.list_considering(self._args): + archives = self._manifest.archives.list_considering(self._args) + name_counter = Counter(a.name for a in archives) + duplicate_names = {a.name for a in archives if name_counter[a.name] > 1} + for archive in archives: if self.versions: # process archives immediately self._process_archive(archive.id) else: # lazily load archives, create archive placeholder inode archive_inode = self._create_dir(parent=1, mtime=int(archive.ts.timestamp() * 1e9)) - name = f"{archive.name}-{bin_to_hex(archive.id):.8}" # archive names may be duplicate! + name = f"{archive.name}" + if name in duplicate_names: + name += f"-{bin_to_hex(archive.id):.8}" self.contents[1][os.fsencode(name)] = archive_inode self.pending_archives[archive_inode] = archive diff --git a/src/borg/testsuite/archiver/mount_cmds_test.py b/src/borg/testsuite/archiver/mount_cmds_test.py index 7f15ca938..1a1ea1292 100644 --- a/src/borg/testsuite/archiver/mount_cmds_test.py +++ b/src/borg/testsuite/archiver/mount_cmds_test.py @@ -204,14 +204,18 @@ def test_fuse_versions_view(archivers, request): def test_fuse_duplicate_name(archivers, request): archiver = request.getfixturevalue(archivers) cmd(archiver, "repo-create", RK_ENCRYPTION) - cmd(archiver, "create", "archive", "input") - cmd(archiver, "create", "archive", "input") + cmd(archiver, "create", "duplicate", "input") + cmd(archiver, "create", "duplicate", "input") + cmd(archiver, "create", "unique1", "input") + cmd(archiver, "create", "unique2", "input") mountpoint = os.path.join(archiver.tmpdir, "mountpoint") # mount the whole repository, archives show up as toplevel directories: with fuse_mount(archiver, mountpoint): path = os.path.join(mountpoint) dirs = os.listdir(path) - assert len(set(dirs)) == 2 # there must be 2 unique dir names for 2 archives + assert len(set(dirs)) == 4 # there must be 4 unique dir names for 4 archives + assert "unique1" in dirs # if an archive has a unique name, do not append the archive id + assert "unique2" in dirs @pytest.mark.skipif(not llfuse, reason="llfuse not installed")