Browse Source

move locking commands to archiver.locks

Thomas Waldmann 2 years ago
parent
commit
f906d9d246
2 changed files with 106 additions and 89 deletions
  1. 3 89
      src/borg/archiver/__init__.py
  2. 103 0
      src/borg/archiver/locks.py

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

@@ -58,7 +58,6 @@ try:
     from ..helpers import ErrorIgnoringTextIOWrapper
     from ..helpers import ProgressIndicatorPercent
     from ..helpers import basic_json_data, json_print
-    from ..helpers import prepare_subprocess_env
     from ..helpers import umount
     from ..helpers import flags_root, flags_dir, flags_special_follow, flags_special
     from ..helpers import msgpack
@@ -107,10 +106,11 @@ def get_func(args):
 from .benchmarks import BenchmarkMixIn
 from .debug import DebugMixIn
 from .keys import KeysMixIn
+from .locks import LocksMixIn
 from .tar import TarMixIn
 
 
-class Archiver(DebugMixIn, TarMixIn, BenchmarkMixIn, KeysMixIn):
+class Archiver(DebugMixIn, TarMixIn, BenchmarkMixIn, KeysMixIn, LocksMixIn):
     def __init__(self, lock_wait=None, prog=None):
         self.exit_code = EXIT_SUCCESS
         self.lock_wait = lock_wait
@@ -1376,36 +1376,6 @@ class Archiver(DebugMixIn, TarMixIn, BenchmarkMixIn, KeysMixIn):
             cache.commit()
         return self.exit_code
 
-    @with_repository(manifest=False, exclusive=True)
-    def do_with_lock(self, args, repository):
-        """run a user specified command with the repository lock held"""
-        # for a new server, this will immediately take an exclusive lock.
-        # to support old servers, that do not have "exclusive" arg in open()
-        # RPC API, we also do it the old way:
-        # re-write manifest to start a repository transaction - this causes a
-        # lock upgrade to exclusive for remote (and also for local) repositories.
-        # by using manifest=False in the decorator, we avoid having to require
-        # the encryption key (and can operate just with encrypted data).
-        data = repository.get(Manifest.MANIFEST_ID)
-        repository.put(Manifest.MANIFEST_ID, data)
-        # usually, a 0 byte (open for writing) segment file would be visible in the filesystem here.
-        # we write and close this file, to rather have a valid segment file on disk, before invoking the subprocess.
-        # we can only do this for local repositories (with .io), though:
-        if hasattr(repository, "io"):
-            repository.io.close_segment()
-        env = prepare_subprocess_env(system=True)
-        try:
-            # we exit with the return code we get from the subprocess
-            return subprocess.call([args.command] + args.args, env=env)
-        finally:
-            # we need to commit the "no change" operation we did to the manifest
-            # because it created a new segment file in the repository. if we would
-            # roll back, the same file would be later used otherwise (for other content).
-            # that would be bad if somebody uses rsync with ignore-existing (or
-            # any other mechanism relying on existing segment data not changing).
-            # see issue #1867.
-            repository.commit(compact=False)
-
     @with_repository(manifest=False, exclusive=True)
     def do_compact(self, args, repository):
         """compact segment files in the repository"""
@@ -1547,13 +1517,6 @@ class Archiver(DebugMixIn, TarMixIn, BenchmarkMixIn, KeysMixIn):
             if args.cache:
                 cache.close()
 
-    @with_repository(lock=False, manifest=False)
-    def do_break_lock(self, args, repository):
-        """Break the repository lock (e.g. in case it was left by a dead borg."""
-        repository.break_lock()
-        Cache.break_lock(repository)
-        return self.exit_code
-
     helptext = collections.OrderedDict()
     helptext["patterns"] = textwrap.dedent(
         """
@@ -2424,25 +2387,7 @@ class Archiver(DebugMixIn, TarMixIn, BenchmarkMixIn, KeysMixIn):
         subparsers = parser.add_subparsers(title="required arguments", metavar="<command>")
 
         self.build_parser_benchmarks(subparsers, common_parser, mid_common_parser)
-
-        # borg break-lock
-        break_lock_epilog = process_epilog(
-            """
-        This command breaks the repository and cache locks.
-        Please use carefully and only while no borg process (on any machine) is
-        trying to access the Cache or the Repository.
-        """
-        )
-        subparser = subparsers.add_parser(
-            "break-lock",
-            parents=[common_parser],
-            add_help=False,
-            description=self.do_break_lock.__doc__,
-            epilog=break_lock_epilog,
-            formatter_class=argparse.RawDescriptionHelpFormatter,
-            help="break repository and cache locks",
-        )
-        subparser.set_defaults(func=self.do_break_lock)
+        self.build_parser_locks(subparsers, common_parser, mid_common_parser)
 
         # borg check
         check_epilog = process_epilog(
@@ -4107,37 +4052,6 @@ class Archiver(DebugMixIn, TarMixIn, BenchmarkMixIn, KeysMixIn):
             "mountpoint", metavar="MOUNTPOINT", type=str, help="mountpoint of the filesystem to umount"
         )
 
-        # borg with-lock
-        with_lock_epilog = process_epilog(
-            """
-        This command runs a user-specified command while the repository lock is held.
-
-        It will first try to acquire the lock (make sure that no other operation is
-        running in the repo), then execute the given command as a subprocess and wait
-        for its termination, release the lock and return the user command's return
-        code as borg's return code.
-
-        .. note::
-
-            If you copy a repository with the lock held, the lock will be present in
-            the copy. Thus, before using borg on the copy from a different host,
-            you need to use "borg break-lock" on the copied repository, because
-            Borg is cautious and does not automatically remove stale locks made by a different host.
-        """
-        )
-        subparser = subparsers.add_parser(
-            "with-lock",
-            parents=[common_parser],
-            add_help=False,
-            description=self.do_with_lock.__doc__,
-            epilog=with_lock_epilog,
-            formatter_class=argparse.RawDescriptionHelpFormatter,
-            help="run user command with lock held",
-        )
-        subparser.set_defaults(func=self.do_with_lock)
-        subparser.add_argument("command", metavar="COMMAND", help="command to run")
-        subparser.add_argument("args", metavar="ARGS", nargs=argparse.REMAINDER, help="command arguments")
-
         self.build_parser_tar(subparsers, common_parser, mid_common_parser)
 
         return parser

+ 103 - 0
src/borg/archiver/locks.py

@@ -0,0 +1,103 @@
+import argparse
+import subprocess
+
+from .common import with_repository
+from ..cache import Cache
+from ..constants import *  # NOQA
+from ..helpers import Manifest
+from ..helpers import prepare_subprocess_env
+
+from ..logger import create_logger
+
+logger = create_logger()
+
+
+class LocksMixIn:
+    @with_repository(manifest=False, exclusive=True)
+    def do_with_lock(self, args, repository):
+        """run a user specified command with the repository lock held"""
+        # for a new server, this will immediately take an exclusive lock.
+        # to support old servers, that do not have "exclusive" arg in open()
+        # RPC API, we also do it the old way:
+        # re-write manifest to start a repository transaction - this causes a
+        # lock upgrade to exclusive for remote (and also for local) repositories.
+        # by using manifest=False in the decorator, we avoid having to require
+        # the encryption key (and can operate just with encrypted data).
+        data = repository.get(Manifest.MANIFEST_ID)
+        repository.put(Manifest.MANIFEST_ID, data)
+        # usually, a 0 byte (open for writing) segment file would be visible in the filesystem here.
+        # we write and close this file, to rather have a valid segment file on disk, before invoking the subprocess.
+        # we can only do this for local repositories (with .io), though:
+        if hasattr(repository, "io"):
+            repository.io.close_segment()
+        env = prepare_subprocess_env(system=True)
+        try:
+            # we exit with the return code we get from the subprocess
+            return subprocess.call([args.command] + args.args, env=env)
+        finally:
+            # we need to commit the "no change" operation we did to the manifest
+            # because it created a new segment file in the repository. if we would
+            # roll back, the same file would be later used otherwise (for other content).
+            # that would be bad if somebody uses rsync with ignore-existing (or
+            # any other mechanism relying on existing segment data not changing).
+            # see issue #1867.
+            repository.commit(compact=False)
+
+    @with_repository(lock=False, manifest=False)
+    def do_break_lock(self, args, repository):
+        """Break the repository lock (e.g. in case it was left by a dead borg."""
+        repository.break_lock()
+        Cache.break_lock(repository)
+        return self.exit_code
+
+    def build_parser_locks(self, subparsers, common_parser, mid_common_parser):
+
+        from .common import process_epilog
+
+        break_lock_epilog = process_epilog(
+            """
+        This command breaks the repository and cache locks.
+        Please use carefully and only while no borg process (on any machine) is
+        trying to access the Cache or the Repository.
+        """
+        )
+        subparser = subparsers.add_parser(
+            "break-lock",
+            parents=[common_parser],
+            add_help=False,
+            description=self.do_break_lock.__doc__,
+            epilog=break_lock_epilog,
+            formatter_class=argparse.RawDescriptionHelpFormatter,
+            help="break repository and cache locks",
+        )
+        subparser.set_defaults(func=self.do_break_lock)
+
+        with_lock_epilog = process_epilog(
+            """
+        This command runs a user-specified command while the repository lock is held.
+
+        It will first try to acquire the lock (make sure that no other operation is
+        running in the repo), then execute the given command as a subprocess and wait
+        for its termination, release the lock and return the user command's return
+        code as borg's return code.
+
+        .. note::
+
+            If you copy a repository with the lock held, the lock will be present in
+            the copy. Thus, before using borg on the copy from a different host,
+            you need to use "borg break-lock" on the copied repository, because
+            Borg is cautious and does not automatically remove stale locks made by a different host.
+        """
+        )
+        subparser = subparsers.add_parser(
+            "with-lock",
+            parents=[common_parser],
+            add_help=False,
+            description=self.do_with_lock.__doc__,
+            epilog=with_lock_epilog,
+            formatter_class=argparse.RawDescriptionHelpFormatter,
+            help="run user command with lock held",
+        )
+        subparser.set_defaults(func=self.do_with_lock)
+        subparser.add_argument("command", metavar="COMMAND", help="command to run")
+        subparser.add_argument("args", metavar="ARGS", nargs=argparse.REMAINDER, help="command arguments")