Ver Fonte

integrate mount2/umount2 into mount/umount, use BORG_FUSE_IMPL

Thomas Waldmann há 4 semanas atrás
pai
commit
ead93b6d12

+ 5 - 5
pyproject.toml

@@ -190,7 +190,7 @@ set_env = {BORG_FUSE_IMPL = "pyfuse3"}
 extras = ["pyfuse3", "sftp", "s3"]
 extras = ["pyfuse3", "sftp", "s3"]
 
 
 [tool.tox.env.py310-mfuse]
 [tool.tox.env.py310-mfuse]
-set_env = {BORG_FUSE_IMPL = "none"}
+set_env = {BORG_FUSE_IMPL = "mfusepy"}
 extras = ["mfusepy", "sftp", "s3"]
 extras = ["mfusepy", "sftp", "s3"]
 
 
 [tool.tox.env.py311-none]
 [tool.tox.env.py311-none]
@@ -204,7 +204,7 @@ set_env = {BORG_FUSE_IMPL = "pyfuse3"}
 extras = ["pyfuse3", "sftp", "s3"]
 extras = ["pyfuse3", "sftp", "s3"]
 
 
 [tool.tox.env.py311-mfuse]
 [tool.tox.env.py311-mfuse]
-set_env = {BORG_FUSE_IMPL = "none"}
+set_env = {BORG_FUSE_IMPL = "mfusepy"}
 extras = ["mfusepy", "sftp", "s3"]
 extras = ["mfusepy", "sftp", "s3"]
 
 
 [tool.tox.env.py312-none]
 [tool.tox.env.py312-none]
@@ -218,7 +218,7 @@ set_env = {BORG_FUSE_IMPL = "pyfuse3"}
 extras = ["pyfuse3", "sftp", "s3"]
 extras = ["pyfuse3", "sftp", "s3"]
 
 
 [tool.tox.env.py312-mfuse]
 [tool.tox.env.py312-mfuse]
-set_env = {BORG_FUSE_IMPL = "none"}
+set_env = {BORG_FUSE_IMPL = "mfusepy"}
 extras = ["mfusepy", "sftp", "s3"]
 extras = ["mfusepy", "sftp", "s3"]
 
 
 [tool.tox.env.py313-none]
 [tool.tox.env.py313-none]
@@ -232,7 +232,7 @@ set_env = {BORG_FUSE_IMPL = "pyfuse3"}
 extras = ["pyfuse3", "sftp", "s3"]
 extras = ["pyfuse3", "sftp", "s3"]
 
 
 [tool.tox.env.py313-mfuse]
 [tool.tox.env.py313-mfuse]
-set_env = {BORG_FUSE_IMPL = "none"}
+set_env = {BORG_FUSE_IMPL = "mfusepy"}
 extras = ["mfusepy", "sftp", "s3"]
 extras = ["mfusepy", "sftp", "s3"]
 
 
 [tool.tox.env.py314-none]
 [tool.tox.env.py314-none]
@@ -246,7 +246,7 @@ set_env = {BORG_FUSE_IMPL = "pyfuse3"}
 extras = ["pyfuse3", "sftp", "s3"]
 extras = ["pyfuse3", "sftp", "s3"]
 
 
 [tool.tox.env.py314-mfuse]
 [tool.tox.env.py314-mfuse]
-set_env = {BORG_FUSE_IMPL = "none"}
+set_env = {BORG_FUSE_IMPL = "mfusepy"}
 extras = ["mfusepy", "sftp", "s3"]
 extras = ["mfusepy", "sftp", "s3"]
 
 
 [tool.tox.env.ruff]
 [tool.tox.env.ruff]

+ 0 - 3
src/borg/archiver/__init__.py

@@ -91,7 +91,6 @@ from .key_cmds import KeysMixIn
 from .list_cmd import ListMixIn
 from .list_cmd import ListMixIn
 from .lock_cmds import LocksMixIn
 from .lock_cmds import LocksMixIn
 from .mount_cmds import MountMixIn
 from .mount_cmds import MountMixIn
-from .mount2_cmds import Mount2MixIn
 from .prune_cmd import PruneMixIn
 from .prune_cmd import PruneMixIn
 from .repo_compress_cmd import RepoCompressMixIn
 from .repo_compress_cmd import RepoCompressMixIn
 from .recreate_cmd import RecreateMixIn
 from .recreate_cmd import RecreateMixIn
@@ -126,7 +125,6 @@ class Archiver(
     ListMixIn,
     ListMixIn,
     LocksMixIn,
     LocksMixIn,
     MountMixIn,
     MountMixIn,
-    Mount2MixIn,
     PruneMixIn,
     PruneMixIn,
     RecreateMixIn,
     RecreateMixIn,
     RenameMixIn,
     RenameMixIn,
@@ -371,7 +369,6 @@ class Archiver(
         self.build_parser_list(subparsers, common_parser, mid_common_parser)
         self.build_parser_list(subparsers, common_parser, mid_common_parser)
         self.build_parser_locks(subparsers, common_parser, mid_common_parser)
         self.build_parser_locks(subparsers, common_parser, mid_common_parser)
         self.build_parser_mount_umount(subparsers, common_parser, mid_common_parser)
         self.build_parser_mount_umount(subparsers, common_parser, mid_common_parser)
-        self.build_parser_mount2_umount2(subparsers, common_parser, mid_common_parser)
         self.build_parser_prune(subparsers, common_parser, mid_common_parser)
         self.build_parser_prune(subparsers, common_parser, mid_common_parser)
         self.build_parser_repo_compress(subparsers, common_parser, mid_common_parser)
         self.build_parser_repo_compress(subparsers, common_parser, mid_common_parser)
         self.build_parser_repo_create(subparsers, common_parser, mid_common_parser)
         self.build_parser_repo_create(subparsers, common_parser, mid_common_parser)

+ 0 - 116
src/borg/archiver/mount2_cmds.py

@@ -1,116 +0,0 @@
-import argparse
-import os
-
-from ._common import with_repository, Highlander
-from ..constants import *  # NOQA
-from ..helpers import RTError
-from ..helpers import PathSpec
-from ..helpers import umount
-from ..manifest import Manifest
-
-from ..logger import create_logger
-
-logger = create_logger()
-
-
-class Mount2MixIn:
-    def do_mount2(self, args):
-        """Mounts an archive or an entire repository as a FUSE filesystem."""
-        # Perform these checks before opening the repository and asking for a passphrase.
-
-        try:
-            from ..fuse2 import mfuse
-        except ImportError:
-            mfuse = None
-
-        if mfuse is None:
-            raise RTError("borg mount2 not available: mfuse not installed.")
-
-        if not os.path.isdir(args.mountpoint):
-            raise RTError(f"{args.mountpoint}: Mountpoint must be an **existing directory**")
-
-        if not os.access(args.mountpoint, os.R_OK | os.W_OK | os.X_OK):
-            raise RTError(f"{args.mountpoint}: Mountpoint must be a **writable** directory")
-
-        self._do_mount2(args)
-
-    @with_repository(compatibility=(Manifest.Operation.READ,))
-    def _do_mount2(self, args, repository, manifest):
-        from ..fuse2 import borgfs
-
-        operations = borgfs(manifest, args, repository)
-        logger.info("Mounting filesystem")
-        try:
-            operations.mount(args.mountpoint, args.options, args.foreground, args.show_rc)
-        except RuntimeError:
-            # Relevant error message already printed to stderr by FUSE
-            raise RTError("FUSE mount failed")
-
-    def do_umount2(self, args):
-        """Unmounts the FUSE filesystem."""
-        umount(args.mountpoint)
-
-    def build_parser_mount2_umount2(self, subparsers, common_parser, mid_common_parser):
-        from ._common import process_epilog
-
-        mount_epilog = process_epilog(
-            """
-        This command mounts a repository or an archive as a FUSE filesystem.
-        This can be useful for browsing or restoring individual files.
-
-        This is an alternative implementation using mfusepy.
-        """
-        )
-        subparser = subparsers.add_parser(
-            "mount2",
-            parents=[common_parser],
-            add_help=False,
-            description=self.do_mount2.__doc__,
-            epilog=mount_epilog,
-            formatter_class=argparse.RawDescriptionHelpFormatter,
-            help="mount a repository (new implementation)",
-        )
-        self._define_borg_mount2(subparser)
-
-        umount_epilog = process_epilog(
-            """
-        This command unmounts a FUSE filesystem that was mounted with ``borg mount2``.
-
-        This is a convenience wrapper that just calls the platform-specific shell
-        command - usually this is either umount or fusermount -u.
-        """
-        )
-        subparser = subparsers.add_parser(
-            "umount2",
-            parents=[common_parser],
-            add_help=False,
-            description=self.do_umount2.__doc__,
-            epilog=umount_epilog,
-            formatter_class=argparse.RawDescriptionHelpFormatter,
-            help="unmount a repository (new implementation)",
-        )
-        subparser.set_defaults(func=self.do_umount2)
-        subparser.add_argument(
-            "mountpoint", metavar="MOUNTPOINT", type=str, help="mountpoint of the filesystem to unmount"
-        )
-
-    def _define_borg_mount2(self, parser):
-        from ._common import define_exclusion_group, define_archive_filters_group
-
-        parser.set_defaults(func=self.do_mount2)
-        parser.add_argument("mountpoint", metavar="MOUNTPOINT", type=str, help="where to mount the filesystem")
-        parser.add_argument(
-            "-f", "--foreground", dest="foreground", action="store_true", help="stay in foreground, do not daemonize"
-        )
-        parser.add_argument("-o", dest="options", type=str, action=Highlander, help="extra mount options")
-        parser.add_argument(
-            "--numeric-ids",
-            dest="numeric_ids",
-            action="store_true",
-            help="use numeric user and group identifiers from archives",
-        )
-        define_archive_filters_group(parser)
-        parser.add_argument(
-            "paths", metavar="PATH", nargs="*", type=PathSpec, help="paths to extract; patterns are supported"
-        )
-        define_exclusion_group(parser, strip_components=True)

+ 20 - 5
src/borg/archiver/mount_cmds.py

@@ -19,9 +19,9 @@ class MountMixIn:
         """Mounts an archive or an entire repository as a FUSE filesystem."""
         """Mounts an archive or an entire repository as a FUSE filesystem."""
         # Perform these checks before opening the repository and asking for a passphrase.
         # Perform these checks before opening the repository and asking for a passphrase.
 
 
-        from ..fuse_impl import llfuse, BORG_FUSE_IMPL
+        from ..fuse_impl import llfuse, has_mfusepy, BORG_FUSE_IMPL
 
 
-        if llfuse is None:
+        if llfuse is None and not has_mfusepy:
             raise RTError("borg mount not available: no FUSE support, BORG_FUSE_IMPL=%s." % BORG_FUSE_IMPL)
             raise RTError("borg mount not available: no FUSE support, BORG_FUSE_IMPL=%s." % BORG_FUSE_IMPL)
 
 
         if not os.path.isdir(args.mountpoint):
         if not os.path.isdir(args.mountpoint):
@@ -34,16 +34,31 @@ class MountMixIn:
 
 
     @with_repository(compatibility=(Manifest.Operation.READ,))
     @with_repository(compatibility=(Manifest.Operation.READ,))
     def _do_mount(self, args, repository, manifest):
     def _do_mount(self, args, repository, manifest):
-        from ..fuse import FuseOperations
+        from ..fuse_impl import has_mfusepy
 
 
-        with cache_if_remote(repository, decrypted_cache=manifest.repo_objs) as cached_repo:
-            operations = FuseOperations(manifest, args, cached_repo)
+        if has_mfusepy:
+            # Use mfusepy implementation
+            from ..fuse2 import borgfs
+
+            operations = borgfs(manifest, args, repository)
             logger.info("Mounting filesystem")
             logger.info("Mounting filesystem")
             try:
             try:
                 operations.mount(args.mountpoint, args.options, args.foreground, args.show_rc)
                 operations.mount(args.mountpoint, args.options, args.foreground, args.show_rc)
             except RuntimeError:
             except RuntimeError:
                 # Relevant error message already printed to stderr by FUSE
                 # Relevant error message already printed to stderr by FUSE
                 raise RTError("FUSE mount failed")
                 raise RTError("FUSE mount failed")
+        else:
+            # Use llfuse/pyfuse3 implementation
+            from ..fuse import FuseOperations
+
+            with cache_if_remote(repository, decrypted_cache=manifest.repo_objs) as cached_repo:
+                operations = FuseOperations(manifest, args, cached_repo)
+                logger.info("Mounting filesystem")
+                try:
+                    operations.mount(args.mountpoint, args.options, args.foreground, args.show_rc)
+                except RuntimeError:
+                    # Relevant error message already printed to stderr by FUSE
+                    raise RTError("FUSE mount failed")
 
 
     def do_umount(self, args):
     def do_umount(self, args):
         """Unmounts the FUSE filesystem."""
         """Unmounts the FUSE filesystem."""

+ 16 - 2
src/borg/fuse_impl.py

@@ -1,10 +1,10 @@
 """
 """
-Loads the library for the low-level FUSE implementation.
+Loads the library for the FUSE implementation.
 """
 """
 
 
 import os
 import os
 
 
-BORG_FUSE_IMPL = os.environ.get("BORG_FUSE_IMPL", "pyfuse3,llfuse")
+BORG_FUSE_IMPL = os.environ.get("BORG_FUSE_IMPL", "mfusepy,pyfuse3,llfuse")
 
 
 for FUSE_IMPL in BORG_FUSE_IMPL.split(","):
 for FUSE_IMPL in BORG_FUSE_IMPL.split(","):
     FUSE_IMPL = FUSE_IMPL.strip()
     FUSE_IMPL = FUSE_IMPL.strip()
@@ -16,6 +16,7 @@ for FUSE_IMPL in BORG_FUSE_IMPL.split(","):
         else:
         else:
             has_llfuse = False
             has_llfuse = False
             has_pyfuse3 = True
             has_pyfuse3 = True
+            has_mfusepy = False
             break
             break
     elif FUSE_IMPL == "llfuse":
     elif FUSE_IMPL == "llfuse":
         try:
         try:
@@ -25,6 +26,18 @@ for FUSE_IMPL in BORG_FUSE_IMPL.split(","):
         else:
         else:
             has_llfuse = True
             has_llfuse = True
             has_pyfuse3 = False
             has_pyfuse3 = False
+            has_mfusepy = False
+            break
+    elif FUSE_IMPL == "mfusepy":
+        try:
+            from .fuse2 import mfuse  # noqa
+        except ImportError:
+            pass
+        else:
+            llfuse = None  # noqa
+            has_llfuse = False
+            has_pyfuse3 = False
+            has_mfusepy = True
             break
             break
     elif FUSE_IMPL == "none":
     elif FUSE_IMPL == "none":
         pass
         pass
@@ -34,3 +47,4 @@ else:
     llfuse = None  # noqa
     llfuse = None  # noqa
     has_llfuse = False
     has_llfuse = False
     has_pyfuse3 = False
     has_pyfuse3 = False
+    has_mfusepy = False

+ 20 - 45
src/borg/testsuite/archiver/mount2_cmds_test.py

@@ -1,3 +1,5 @@
+# this is testing the mount/umount commands with mfusepy implementation
+
 import errno
 import errno
 import os
 import os
 import sys
 import sys
@@ -38,70 +40,35 @@ pytest_generate_tests = lambda metafunc: generate_archiver_tests(metafunc, kinds
 def fuse_mount2(archiver, mountpoint, *args, **kwargs):
 def fuse_mount2(archiver, mountpoint, *args, **kwargs):
     os.makedirs(mountpoint, exist_ok=True)
     os.makedirs(mountpoint, exist_ok=True)
 
 
-    # We use subprocess to run borg mount2 to ensure it runs in a separate process
+    # We use subprocess to run borg mount to ensure it runs in a separate process
     # and we can control it via signals if needed.
     # and we can control it via signals if needed.
     # We use --foreground to keep it running.
     # We use --foreground to keep it running.
 
 
-    cmd_args = ["mount2", "--foreground"]
-
-    # If the first arg is a path (not starting with -), it might be a path inside the repo
-    # But mount2 syntax is: borg mount2 [options] repo_or_archive mountpoint [path]
-    # Wait, standard mount is: borg mount repo mountpoint
-    # mount2 is: borg mount2 repo mountpoint
+    cmd_args = ["mount", "--foreground"]
 
 
     # We need to construct the command line carefully.
     # We need to construct the command line carefully.
     # args might contain options or paths.
     # args might contain options or paths.
 
 
-    # Let's assume usage: fuse_mount2(archiver, mountpoint, options...)
+    # Usage: fuse_mount2(archiver, mountpoint, options...)
     # The repo path is archiver.repository_path
     # The repo path is archiver.repository_path
 
 
-    # If we want to mount a specific archive: fuse_mount2(archiver, mountpoint, "archive_name")
-    # But mount2 takes "repo::archive" as location.
-
-    # Let's look at how test_fuse uses it.
-    # fuse_mount(archiver, mountpoint, "-a", "test", ...)
-
-    # mount2 supports "repo" or "repo::archive".
+    # If we want to mount a specific archive: fuse_mount2(archiver, mountpoint, "-a", "archive_name", ...)
+    # The mount command uses: borg mount --repo REPO [options] MOUNTPOINT
 
 
     location = archiver.repository_path
     location = archiver.repository_path
 
 
     # Check if we have extra args that look like options
     # Check if we have extra args that look like options
     # Just pass all args to the command
     # Just pass all args to the command
     # We put mountpoint first, then --repo location, then all other args
     # We put mountpoint first, then --repo location, then all other args
-    # This assumes mount2 supports: borg mount2 mountpoint --repo location [options] [paths]
-    # or: borg mount2 mountpoint --repo location -a archive [paths]
+    # This supports: borg mount [options] MOUNTPOINT --repo LOCATION [more options]
 
 
     borg_cmd = [sys.executable, "-m", "borg"]
     borg_cmd = [sys.executable, "-m", "borg"]
     full_cmd = borg_cmd + cmd_args + [mountpoint, "--repo", location] + list(args)
     full_cmd = borg_cmd + cmd_args + [mountpoint, "--repo", location] + list(args)
 
 
-    # If other_args has something, it might be that we want to mount a specific archive
-    # or a path inside the archive?
-    # mount2 currently supports: borg mount2 repo::archive mountpoint
-    # It does NOT support: borg mount2 repo mountpoint path
-    # It DOES support: borg mount2 repo mountpoint
-
-    # If the test passes "-a", "archive", we should handle it.
-    # But mount2 might not support -a yet?
-    # Let's check mount2_cmds.py arguments.
-    # It supports "location" and "mountpoint".
-    # It also supports --options (-o).
-    # It does NOT seem to support -a / --match-archives yet based on my previous read,
-    # OR it does via list_considering?
-    # Re-reading mount2_cmds.py would be good, but I recall it uses `self._args.name`
-    # if provided via `location` parsing.
+    # The mount command supports various options like -a/--match-archives, -o, paths, etc.
+    # All options are passed through in args.
 
 
-    # If the test wants to mount a specific archive, it should probably pass it in location.
-    # But `fuse_mount` in `mount_cmds_test.py` takes `*options`.
-
-    # Let's try to be smart.
-    # If "-a" is in options, mount2 probably doesn't support it directly as a flag
-    # if it expects repo::archive.
-    # But wait, `list_considering` was used.
-
-    # Let's just pass all args to the command and see.
-    # But we need to put location and mountpoint in the right place.
-
-    # Command: borg mount2 [options] MOUNTPOINT --repo=LOCATION
+    # Command: borg mount [options] MOUNTPOINT --repo=LOCATION
 
 
     borg_cmd = [sys.executable, "-m", "borg"]
     borg_cmd = [sys.executable, "-m", "borg"]
     # We pass mountpoint as positional arg, and repo as --repo
     # We pass mountpoint as positional arg, and repo as --repo
@@ -109,6 +76,9 @@ def fuse_mount2(archiver, mountpoint, *args, **kwargs):
     # full_cmd constructed above
     # full_cmd constructed above
 
 
     env = os.environ.copy()
     env = os.environ.copy()
+    # Set BORG_FUSE_IMPL to use mfusepy implementation
+    env["BORG_FUSE_IMPL"] = "mfusepy"
+
     # env["BORG_REPO"] = archiver.repository_location # Not needed if --repo is used, but keeps it safe?
     # env["BORG_REPO"] = archiver.repository_location # Not needed if --repo is used, but keeps it safe?
     # Actually, if we use --repo, we don't need BORG_REPO env var for the command,
     # Actually, if we use --repo, we don't need BORG_REPO env var for the command,
     # but we might need it for other things?
     # but we might need it for other things?
@@ -173,8 +143,13 @@ def test_mount2_missing_mfuse(archivers, request):
 
 
         from ...helpers import CommandError
         from ...helpers import CommandError
 
 
+        # Set BORG_FUSE_IMPL to mfusepy, but it won't be available
+        env = os.environ.copy()
+        env["BORG_FUSE_IMPL"] = "mfusepy"
+
         try:
         try:
-            cmd(archiver, "mount2", archiver.repository_path + "::archive", mountpoint)
+            # This should fail because mfusepy is not available
+            cmd(archiver, "mount", "--repo", archiver.repository_path, "-a", "archive", mountpoint, fork=True, env=env)
         except CommandError:
         except CommandError:
             # We expect it to fail because mfuse is missing
             # We expect it to fail because mfuse is missing
             # The error message might vary depending on how it's handled
             # The error message might vary depending on how it's handled