1
0
Fork 0
mirror of https://github.com/borgbackup/borg.git synced 2024-12-24 16:55:36 +00:00

Detect non-upgraded Attic repositories

When opening a repository, always try to read the magic number of the
latest segment and compare it to the Attic segment magic (unless the
repository is opened for upgrading). If an Attic segment is detected,
raise a dedicated exception, telling the user to upgrade the repository
first.

Fixes #1933.
This commit is contained in:
Lukas Fleischer 2017-08-06 16:46:08 +02:00
parent a1840e9238
commit 0943b322e3
6 changed files with 52 additions and 5 deletions

View file

@ -499,6 +499,8 @@ Errors
Insufficient free space to complete transaction (required: {}, available: {}).
Repository.InvalidRepository
{} is not a valid repository. Check repo config.
Repository.AtticRepository
Attic repository detected. Please run "borg upgrade {}".
Repository.ObjectNotFound
Object with key {} not found in repository {}.

View file

@ -726,6 +726,11 @@ def handle_error(unpacked):
raise IntegrityError('(not available)')
else:
raise IntegrityError(args[0].decode())
elif error == 'AtticRepository':
if old_server:
raise Repository.AtticRepository('(not available)')
else:
raise Repository.AtticRepository(args[0].decode())
elif error == 'PathNotAllowed':
if old_server:
raise PathNotAllowed('(unknown)')

View file

@ -30,6 +30,8 @@
MAGIC = b'BORG_SEG'
MAGIC_LEN = len(MAGIC)
ATTIC_MAGIC = b'ATTICSEG'
assert len(ATTIC_MAGIC) == MAGIC_LEN
TAG_PUT = 0
TAG_DELETE = 1
TAG_COMMIT = 2
@ -116,6 +118,9 @@ class AlreadyExists(Error):
class InvalidRepository(Error):
"""{} is not a valid repository. Check repo config."""
class AtticRepository(Error):
"""Attic repository detected. Please run "borg upgrade {}"."""
class CheckNeeded(ErrorWithTraceback):
"""Inconsistency detected. Please run "borg check {}"."""
@ -134,7 +139,7 @@ class StorageQuotaExceeded(Error):
"""The storage quota ({}) has been exceeded ({}). Try deleting some archives."""
def __init__(self, path, create=False, exclusive=False, lock_wait=None, lock=True,
append_only=False, storage_quota=None):
append_only=False, storage_quota=None, check_segment_magic=True):
self.path = os.path.abspath(path)
self._location = Location('file://%s' % self.path)
self.io = None # type: LoggedIO
@ -154,6 +159,7 @@ def __init__(self, path, create=False, exclusive=False, lock_wait=None, lock=Tru
self.storage_quota = storage_quota
self.storage_quota_use = 0
self.transaction_doomed = None
self.check_segment_magic = check_segment_magic
def __del__(self):
if self.lock:
@ -370,6 +376,12 @@ def open(self, path, exclusive, lock_wait=None, lock=True):
self.storage_quota = self.config.getint('repository', 'storage_quota', fallback=0)
self.id = unhexlify(self.config.get('repository', 'id').strip())
self.io = LoggedIO(self.path, self.max_segment_size, self.segments_per_dir)
if self.check_segment_magic:
# read a segment and check whether we are dealing with a non-upgraded Attic repository
segment = self.io.get_latest_segment()
if segment is not None and self.io.get_segment_magic(segment) == ATTIC_MAGIC:
self.close()
raise self.AtticRepository(path)
def close(self):
if self.lock:
@ -1245,6 +1257,11 @@ def segment_exists(self, segment):
def segment_size(self, segment):
return os.path.getsize(self.segment_filename(segment))
def get_segment_magic(self, segment):
fd = self.get_fd(segment)
fd.seek(0)
return fd.read(MAGIC_LEN)
def iter_objects(self, segment, offset=0, include_data=False, read_data=True):
"""
Return object iterator for *segment*.

View file

@ -55,6 +55,7 @@
from . import BaseTestCase, changedir, environment_variable, no_selinux
from . import are_symlinks_supported, are_hardlinks_supported, are_fifos_supported, is_utime_fully_supported
from .platform import fakeroot_detected
from .upgrader import attic_repo
from . import key
@ -2725,6 +2726,27 @@ def test_extract_hardlinks(self):
assert os.stat('input/dir1/aaaa').st_nlink == 2
assert os.stat('input/dir1/source2').st_nlink == 2
def test_detect_attic_repo(self):
path = attic_repo(self.repository_path)
cmds = [
['create', path + '::test', self.tmpdir],
['extract', path + '::test'],
['check', path],
['rename', path + '::test', 'newname'],
['list', path],
['delete', path],
['prune', path],
['info', path + '::test'],
['mount', path, self.tmpdir],
['key', 'export', path, 'exported'],
['key', 'import', path, 'import'],
['change-passphrase', path],
['break-lock', path],
]
for args in cmds:
output = self.cmd(*args, fork=True, exit_code=2)
assert 'Attic repository detected.' in output
@unittest.skipUnless('binary' in BORG_EXES, 'no borg.exe available')
class ArchiverTestCaseBinary(ArchiverTestCase):

View file

@ -85,8 +85,8 @@ def test_convert_segments(attic_repo, inplace):
:param attic_repo: a populated attic repository (fixture)
"""
repo_path = attic_repo
# check should fail because of magic number
assert not repo_valid(repo_path)
with pytest.raises(Repository.AtticRepository):
repo_valid(repo_path)
repository = AtticRepositoryUpgrader(repo_path, create=False)
with repository:
segments = [filename for i, filename in repository.io.segment_iterator()]
@ -149,8 +149,8 @@ def test_convert_all(attic_repo, attic_key_file, inplace):
"""
repo_path = attic_repo
# check should fail because of magic number
assert not repo_valid(repo_path)
with pytest.raises(Repository.AtticRepository):
repo_valid(repo_path)
def stat_segment(path):
return os.stat(os.path.join(path, 'data', '0', '0'))

View file

@ -19,6 +19,7 @@
class AtticRepositoryUpgrader(Repository):
def __init__(self, *args, **kw):
kw['lock'] = False # do not create borg lock files (now) in attic repo
kw['check_segment_magic'] = False # skip the Attic check when upgrading
super().__init__(*args, **kw)
def upgrade(self, dryrun=True, inplace=False, progress=False):