From 962c2e9d5458618d8cf3018575d81ac72608d45f Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Thu, 28 Apr 2016 01:28:43 +0200 Subject: [PATCH] borg with-lock REPO CMD ARGS --- borg/archiver.py | 42 ++++++++++++++++++++++++++++++++++++ borg/testsuite/archiver.py | 6 ++++++ docs/usage.rst | 3 +++ docs/usage/with-lock.rst.inc | 32 +++++++++++++++++++++++++++ 4 files changed, 83 insertions(+) create mode 100644 docs/usage/with-lock.rst.inc diff --git a/borg/archiver.py b/borg/archiver.py index 5729f51f5..949d8fbf7 100644 --- a/borg/archiver.py +++ b/borg/archiver.py @@ -12,6 +12,7 @@ import os import shlex import signal import stat +import subprocess import sys import textwrap import traceback @@ -895,6 +896,21 @@ class Archiver: cache.commit() 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() def do_debug_dump_archive_items(self, args, repository, manifest, key): """dump (decrypted, decompressed) archive items metadata (not: data)""" @@ -1831,6 +1847,32 @@ class Archiver: subparser.add_argument('paths', metavar='PATH', nargs='*', type=str, 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, description='Extra help') subparser.add_argument('--epilog-only', dest='epilog_only', diff --git a/borg/testsuite/archiver.py b/borg/testsuite/archiver.py index bbefea3f2..73632ee95 100644 --- a/borg/testsuite/archiver.py +++ b/borg/testsuite/archiver.py @@ -1399,6 +1399,12 @@ class ArchiverTestCase(ArchiverTestCaseBase): info_after = self.cmd('info', self.repository_location + '::test') 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') class ArchiverTestCaseBinary(ArchiverTestCase): diff --git a/docs/usage.rst b/docs/usage.rst index 13d02aa93..d1cb8a934 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -647,6 +647,9 @@ Examples ... +.. include:: usage/with-lock.rst.inc + + .. include:: usage/break-lock.rst.inc diff --git a/docs/usage/with-lock.rst.inc b/docs/usage/with-lock.rst.inc new file mode 100644 index 000000000..3037ee809 --- /dev/null +++ b/docs/usage/with-lock.rst.inc @@ -0,0 +1,32 @@ +.. _borg_with-lock: + +borg with-lock +-------------- +:: + + borg with-lock 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.