Преглед на файлове

borg with-lock REPO CMD ARGS

Thomas Waldmann преди 9 години
родител
ревизия
962c2e9d54
променени са 4 файла, в които са добавени 83 реда и са изтрити 0 реда
  1. 42 0
      borg/archiver.py
  2. 6 0
      borg/testsuite/archiver.py
  3. 3 0
      docs/usage.rst
  4. 32 0
      docs/usage/with-lock.rst.inc

+ 42 - 0
borg/archiver.py

@@ -12,6 +12,7 @@ import os
 import shlex
 import shlex
 import signal
 import signal
 import stat
 import stat
+import subprocess
 import sys
 import sys
 import textwrap
 import textwrap
 import traceback
 import traceback
@@ -895,6 +896,21 @@ class Archiver:
         cache.commit()
         cache.commit()
         return self.exit_code
         return self.exit_code
 
 
+    @with_repository(manifest=False)
+    def do_with_lock(self, args, repository):
+        """run a user specified command with the repository lock held"""
+        # 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)
+        try:
+            # we exit with the return code we get from the subprocess
+            return subprocess.call([args.command] + args.args)
+        finally:
+            repository.rollback()
+
     @with_repository()
     @with_repository()
     def do_debug_dump_archive_items(self, args, repository, manifest, key):
     def do_debug_dump_archive_items(self, args, repository, manifest, key):
         """dump (decrypted, decompressed) archive items metadata (not: data)"""
         """dump (decrypted, decompressed) archive items metadata (not: data)"""
@@ -1831,6 +1847,32 @@ class Archiver:
         subparser.add_argument('paths', metavar='PATH', nargs='*', type=str,
         subparser.add_argument('paths', metavar='PATH', nargs='*', type=str,
                                help='paths to recreate; patterns are supported')
                                help='paths to recreate; patterns are supported')
 
 
+        with_lock_epilog = textwrap.dedent("""
+        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, obviously. Thus, before using borg on the copy, you need to
+              use "borg break-lock" on it.
+        """)
+        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('location', metavar='REPOSITORY',
+                               type=location_validator(archive=False),
+                               help='repository to lock')
+        subparser.add_argument('command', metavar='COMMAND',
+                               help='command to run')
+        subparser.add_argument('args', metavar='ARGS', nargs=argparse.REMAINDER,
+                               help='command arguments')
+
         subparser = subparsers.add_parser('help', parents=[common_parser], add_help=False,
         subparser = subparsers.add_parser('help', parents=[common_parser], add_help=False,
                                           description='Extra help')
                                           description='Extra help')
         subparser.add_argument('--epilog-only', dest='epilog_only',
         subparser.add_argument('--epilog-only', dest='epilog_only',

+ 6 - 0
borg/testsuite/archiver.py

@@ -1399,6 +1399,12 @@ class ArchiverTestCase(ArchiverTestCaseBase):
         info_after = self.cmd('info', self.repository_location + '::test')
         info_after = self.cmd('info', self.repository_location + '::test')
         assert info_before == info_after  # includes archive ID
         assert info_before == info_after  # includes archive ID
 
 
+    def test_with_lock(self):
+        self.cmd('init', self.repository_location)
+        lock_path = os.path.join(self.repository_path, 'lock.exclusive')
+        cmd = 'python3', '-c', 'import os, sys; sys.exit(42 if os.path.exists("%s") else 23)' % lock_path
+        self.cmd('with-lock', self.repository_location, *cmd, fork=True, exit_code=42)
+
 
 
 @unittest.skipUnless('binary' in BORG_EXES, 'no borg.exe available')
 @unittest.skipUnless('binary' in BORG_EXES, 'no borg.exe available')
 class ArchiverTestCaseBinary(ArchiverTestCase):
 class ArchiverTestCaseBinary(ArchiverTestCase):

+ 3 - 0
docs/usage.rst

@@ -647,6 +647,9 @@ Examples
     ...
     ...
 
 
 
 
+.. include:: usage/with-lock.rst.inc
+
+
 .. include:: usage/break-lock.rst.inc
 .. include:: usage/break-lock.rst.inc
 
 
 
 

+ 32 - 0
docs/usage/with-lock.rst.inc

@@ -0,0 +1,32 @@
+.. _borg_with-lock:
+
+borg with-lock
+--------------
+::
+
+    borg with-lock <options> REPOSITORY COMMAND ARGS
+
+positional arguments
+    REPOSITORY
+        repository to lock
+    COMMAND
+        command to run
+    ARGS
+        command arguments
+
+`Common options`_
+    |
+
+Description
+~~~~~~~~~~~
+
+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, obviously. Thus, before using borg on the copy, you need to
+      use "borg break-lock" on it.