implement borg break-lock REPO command, fixes #157

due to borg's architecture, breaking the repo lock needs first creating a repository object.
this would usually try to get a lock and then block if there already is one.
thus I added a flag to open without trying to create a lock.
This commit is contained in:
Thomas Waldmann 2015-11-21 20:50:53 +01:00
parent 1093894be8
commit 38994c78fc
5 changed files with 54 additions and 11 deletions

View file

@ -42,11 +42,11 @@ class Archiver:
self.verbose = verbose
self.lock_wait = lock_wait
def open_repository(self, location, create=False, exclusive=False):
def open_repository(self, location, create=False, exclusive=False, lock=True):
if location.proto == 'ssh':
repository = RemoteRepository(location, create=create, lock_wait=self.lock_wait)
repository = RemoteRepository(location, create=create, lock_wait=self.lock_wait, lock=lock)
else:
repository = Repository(location.path, create=create, exclusive=exclusive, lock_wait=self.lock_wait)
repository = Repository(location.path, create=create, exclusive=exclusive, lock_wait=self.lock_wait, lock=lock)
repository._location = location
return repository
@ -573,6 +573,16 @@ class Archiver:
print('Done.')
return EXIT_SUCCESS
def do_break_lock(self, args):
"""Break the repository lock (e.g. in case it was left by a dead borg."""
repository = self.open_repository(args.repository, lock=False)
try:
repository.break_lock()
Cache.break_lock(repository)
finally:
repository.close()
return self.exit_code
helptext = {}
helptext['patterns'] = '''
Exclude patterns use a variant of shell pattern syntax, with '*' matching any
@ -972,6 +982,20 @@ class Archiver:
type=location_validator(archive=True),
help='archive to display information about')
break_lock_epilog = textwrap.dedent("""
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],
description=self.do_break_lock.__doc__,
epilog=break_lock_epilog,
formatter_class=argparse.RawDescriptionHelpFormatter)
subparser.set_defaults(func=self.do_break_lock)
subparser.add_argument('repository', metavar='REPOSITORY',
type=location_validator(archive=False),
help='repository for which to break the locks')
prune_epilog = textwrap.dedent("""
The prune command prunes a repository by deleting archives not matching
any of the specified retention options. This command is normally used by

View file

@ -32,6 +32,11 @@ class Cache:
class EncryptionMethodMismatch(Error):
"""Repository encryption method changed since last access, refusing to continue"""
@staticmethod
def break_lock(repository, path=None):
path = path or os.path.join(get_cache_dir(), hexlify(repository.id).decode('ascii'))
UpgradableLock(os.path.join(path, 'lock'), exclusive=True).break_lock()
def __init__(self, repository, key, manifest, path=None, sync=True, do_files=False, warn_if_unencrypted=True,
lock_wait=None):
self.lock = None

View file

@ -50,6 +50,7 @@ class RepositoryServer: # pragma: no cover
'rollback',
'save_key',
'load_key',
'break_lock',
)
def __init__(self, restrict_to_paths):
@ -97,7 +98,7 @@ class RepositoryServer: # pragma: no cover
def negotiate(self, versions):
return 1
def open(self, path, create=False, lock_wait=None):
def open(self, path, create=False, lock_wait=None, lock=True):
path = os.fsdecode(path)
if path.startswith('/~'):
path = path[1:]
@ -108,7 +109,7 @@ class RepositoryServer: # pragma: no cover
break
else:
raise PathNotAllowed(path)
self.repository = Repository(path, create, lock_wait=lock_wait)
self.repository = Repository(path, create, lock_wait=lock_wait, lock=lock)
return self.repository.id
@ -122,7 +123,7 @@ class RemoteRepository:
def __init__(self, name):
self.name = name
def __init__(self, location, create=False, lock_wait=None):
def __init__(self, location, create=False, lock_wait=None, lock=True):
self.location = location
self.preload_ids = []
self.msgid = 0
@ -154,7 +155,7 @@ class RemoteRepository:
raise ConnectionClosedWithHint('Is borg working on the server?')
if version != 1:
raise Exception('Server insisted on using unsupported protocol version %d' % version)
self.id = self.call('open', location.path, create, lock_wait)
self.id = self.call('open', location.path, create, lock_wait, lock)
def __del__(self):
self.close()
@ -308,6 +309,9 @@ class RemoteRepository:
def load_key(self):
return self.call('load_key')
def break_lock(self):
return self.call('break_lock')
def close(self):
if self.p:
self.p.stdin.close()

View file

@ -51,7 +51,7 @@ class Repository:
class ObjectNotFound(ErrorWithTraceback):
"""Object with key {} not found in repository {}."""
def __init__(self, path, create=False, exclusive=False, lock_wait=None):
def __init__(self, path, create=False, exclusive=False, lock_wait=None, lock=True):
self.path = os.path.abspath(path)
self.io = None
self.lock = None
@ -59,7 +59,7 @@ class Repository:
self._active_txn = False
if create:
self.create(self.path)
self.open(self.path, exclusive, lock_wait=lock_wait)
self.open(self.path, exclusive, lock_wait=lock_wait, lock=lock)
def __del__(self):
self.close()
@ -129,11 +129,17 @@ class Repository:
self.replay_segments(replay_from, segments_transaction_id)
return self.get_index_transaction_id()
def open(self, path, exclusive, lock_wait=None):
def break_lock(self):
UpgradableLock(os.path.join(self.path, 'lock')).break_lock()
def open(self, path, exclusive, lock_wait=None, lock=True):
self.path = path
if not os.path.isdir(path):
raise self.DoesNotExist(path)
self.lock = UpgradableLock(os.path.join(path, 'lock'), exclusive, timeout=lock_wait).acquire()
if lock:
self.lock = UpgradableLock(os.path.join(path, 'lock'), exclusive, timeout=lock_wait).acquire()
else:
self.lock = None
self.config = ConfigParser(interpolation=None)
self.config.read(os.path.join(self.path, 'config'))
if 'repository' not in self.config.sections() or self.config.getint('repository', 'version') != 1:

View file

@ -724,6 +724,10 @@ class ArchiverTestCase(ArchiverTestCaseBase):
self.assert_in('test-2', output)
self.assert_not_in('something-else', output)
def test_break_lock(self):
self.cmd('init', self.repository_location)
self.cmd('break-lock', self.repository_location)
def test_usage(self):
if self.FORK_DEFAULT:
self.cmd(exit_code=0)