mirror of
https://github.com/borgbackup/borg.git
synced 2026-05-28 04:03:21 -04:00
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:
parent
1093894be8
commit
38994c78fc
5 changed files with 54 additions and 11 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in a new issue