mirror of
https://github.com/borgbackup/borg.git
synced 2025-01-01 04:37:34 +00:00
Merge pull request #2964 from ThomasWaldmann/detect-attic-repos-1.1
Detect non-upgraded Attic repositories
This commit is contained in:
commit
1b7b58e712
6 changed files with 52 additions and 5 deletions
|
@ -500,6 +500,8 @@ Errors
|
||||||
Insufficient free space to complete transaction (required: {}, available: {}).
|
Insufficient free space to complete transaction (required: {}, available: {}).
|
||||||
Repository.InvalidRepository
|
Repository.InvalidRepository
|
||||||
{} is not a valid repository. Check repo config.
|
{} is not a valid repository. Check repo config.
|
||||||
|
Repository.AtticRepository
|
||||||
|
Attic repository detected. Please run "borg upgrade {}".
|
||||||
Repository.ObjectNotFound
|
Repository.ObjectNotFound
|
||||||
Object with key {} not found in repository {}.
|
Object with key {} not found in repository {}.
|
||||||
|
|
||||||
|
|
|
@ -733,6 +733,11 @@ def handle_error(unpacked):
|
||||||
raise IntegrityError('(not available)')
|
raise IntegrityError('(not available)')
|
||||||
else:
|
else:
|
||||||
raise IntegrityError(args[0].decode())
|
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':
|
elif error == 'PathNotAllowed':
|
||||||
if old_server:
|
if old_server:
|
||||||
raise PathNotAllowed('(unknown)')
|
raise PathNotAllowed('(unknown)')
|
||||||
|
|
|
@ -30,6 +30,8 @@
|
||||||
|
|
||||||
MAGIC = b'BORG_SEG'
|
MAGIC = b'BORG_SEG'
|
||||||
MAGIC_LEN = len(MAGIC)
|
MAGIC_LEN = len(MAGIC)
|
||||||
|
ATTIC_MAGIC = b'ATTICSEG'
|
||||||
|
assert len(ATTIC_MAGIC) == MAGIC_LEN
|
||||||
TAG_PUT = 0
|
TAG_PUT = 0
|
||||||
TAG_DELETE = 1
|
TAG_DELETE = 1
|
||||||
TAG_COMMIT = 2
|
TAG_COMMIT = 2
|
||||||
|
@ -116,6 +118,9 @@ class AlreadyExists(Error):
|
||||||
class InvalidRepository(Error):
|
class InvalidRepository(Error):
|
||||||
"""{} is not a valid repository. Check repo config."""
|
"""{} is not a valid repository. Check repo config."""
|
||||||
|
|
||||||
|
class AtticRepository(Error):
|
||||||
|
"""Attic repository detected. Please run "borg upgrade {}"."""
|
||||||
|
|
||||||
class CheckNeeded(ErrorWithTraceback):
|
class CheckNeeded(ErrorWithTraceback):
|
||||||
"""Inconsistency detected. Please run "borg check {}"."""
|
"""Inconsistency detected. Please run "borg check {}"."""
|
||||||
|
|
||||||
|
@ -134,7 +139,7 @@ class StorageQuotaExceeded(Error):
|
||||||
"""The storage quota ({}) has been exceeded ({}). Try deleting some archives."""
|
"""The storage quota ({}) has been exceeded ({}). Try deleting some archives."""
|
||||||
|
|
||||||
def __init__(self, path, create=False, exclusive=False, lock_wait=None, lock=True,
|
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.path = os.path.abspath(path)
|
||||||
self._location = Location('file://%s' % self.path)
|
self._location = Location('file://%s' % self.path)
|
||||||
self.io = None # type: LoggedIO
|
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 = storage_quota
|
||||||
self.storage_quota_use = 0
|
self.storage_quota_use = 0
|
||||||
self.transaction_doomed = None
|
self.transaction_doomed = None
|
||||||
|
self.check_segment_magic = check_segment_magic
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
if self.lock:
|
if self.lock:
|
||||||
|
@ -375,6 +381,12 @@ def open(self, path, exclusive, lock_wait=None, lock=True):
|
||||||
self.storage_quota = self.config.getint('repository', 'storage_quota', fallback=0)
|
self.storage_quota = self.config.getint('repository', 'storage_quota', fallback=0)
|
||||||
self.id = unhexlify(self.config.get('repository', 'id').strip())
|
self.id = unhexlify(self.config.get('repository', 'id').strip())
|
||||||
self.io = LoggedIO(self.path, self.max_segment_size, self.segments_per_dir)
|
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):
|
def close(self):
|
||||||
if self.lock:
|
if self.lock:
|
||||||
|
@ -1250,6 +1262,11 @@ def segment_exists(self, segment):
|
||||||
def segment_size(self, segment):
|
def segment_size(self, segment):
|
||||||
return os.path.getsize(self.segment_filename(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):
|
def iter_objects(self, segment, offset=0, include_data=False, read_data=True):
|
||||||
"""
|
"""
|
||||||
Return object iterator for *segment*.
|
Return object iterator for *segment*.
|
||||||
|
|
|
@ -55,6 +55,7 @@
|
||||||
from . import BaseTestCase, changedir, environment_variable, no_selinux
|
from . import BaseTestCase, changedir, environment_variable, no_selinux
|
||||||
from . import are_symlinks_supported, are_hardlinks_supported, are_fifos_supported, is_utime_fully_supported
|
from . import are_symlinks_supported, are_hardlinks_supported, are_fifos_supported, is_utime_fully_supported
|
||||||
from .platform import fakeroot_detected
|
from .platform import fakeroot_detected
|
||||||
|
from .upgrader import attic_repo
|
||||||
from . import key
|
from . import key
|
||||||
|
|
||||||
|
|
||||||
|
@ -2733,6 +2734,27 @@ def test_extract_hardlinks(self):
|
||||||
assert os.stat('input/dir1/aaaa').st_nlink == 2
|
assert os.stat('input/dir1/aaaa').st_nlink == 2
|
||||||
assert os.stat('input/dir1/source2').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')
|
@unittest.skipUnless('binary' in BORG_EXES, 'no borg.exe available')
|
||||||
class ArchiverTestCaseBinary(ArchiverTestCase):
|
class ArchiverTestCaseBinary(ArchiverTestCase):
|
||||||
|
|
|
@ -85,8 +85,8 @@ def test_convert_segments(attic_repo, inplace):
|
||||||
:param attic_repo: a populated attic repository (fixture)
|
:param attic_repo: a populated attic repository (fixture)
|
||||||
"""
|
"""
|
||||||
repo_path = attic_repo
|
repo_path = attic_repo
|
||||||
# check should fail because of magic number
|
with pytest.raises(Repository.AtticRepository):
|
||||||
assert not repo_valid(repo_path)
|
repo_valid(repo_path)
|
||||||
repository = AtticRepositoryUpgrader(repo_path, create=False)
|
repository = AtticRepositoryUpgrader(repo_path, create=False)
|
||||||
with repository:
|
with repository:
|
||||||
segments = [filename for i, filename in repository.io.segment_iterator()]
|
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
|
repo_path = attic_repo
|
||||||
|
|
||||||
# check should fail because of magic number
|
with pytest.raises(Repository.AtticRepository):
|
||||||
assert not repo_valid(repo_path)
|
repo_valid(repo_path)
|
||||||
|
|
||||||
def stat_segment(path):
|
def stat_segment(path):
|
||||||
return os.stat(os.path.join(path, 'data', '0', '0'))
|
return os.stat(os.path.join(path, 'data', '0', '0'))
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
class AtticRepositoryUpgrader(Repository):
|
class AtticRepositoryUpgrader(Repository):
|
||||||
def __init__(self, *args, **kw):
|
def __init__(self, *args, **kw):
|
||||||
kw['lock'] = False # do not create borg lock files (now) in attic repo
|
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)
|
super().__init__(*args, **kw)
|
||||||
|
|
||||||
def upgrade(self, dryrun=True, inplace=False, progress=False):
|
def upgrade(self, dryrun=True, inplace=False, progress=False):
|
||||||
|
|
Loading…
Reference in a new issue