From d979a84f3724522a80a7f0b19582ec40c0b8efbe Mon Sep 17 00:00:00 2001 From: Marian Beermann Date: Fri, 22 Apr 2016 11:40:16 +0200 Subject: [PATCH] Handle permission and similar errors on the index --- borg/hashindex.pyx | 10 ++++++++++ borg/repository.py | 22 ++++++++++++++-------- borg/testsuite/repository.py | 23 +++++++++++++++++------ 3 files changed, 41 insertions(+), 14 deletions(-) diff --git a/borg/hashindex.pyx b/borg/hashindex.pyx index 459eed7b0..e55de7fe7 100644 --- a/borg/hashindex.pyx +++ b/borg/hashindex.pyx @@ -27,6 +27,14 @@ cdef extern from "_hashindex.c": uint32_t _le32toh(uint32_t v) +cdef extern from "errno.h": + int errno + + +cdef extern from "string.h": + char *strerror(int errnum) + + cdef _NoDefault = object() """ @@ -63,6 +71,8 @@ cdef class IndexBase: path = os.fsencode(path) self.index = hashindex_read(path) if not self.index: + if errno: + raise OSError(errno, strerror(errno), path) raise RuntimeError('hashindex_read failed') else: self.index = hashindex_init(capacity, self.key_size, self.value_size) diff --git a/borg/repository.py b/borg/repository.py index d59466358..05c0aa6f8 100644 --- a/borg/repository.py +++ b/borg/repository.py @@ -238,20 +238,24 @@ class Repository: def open_index(self, transaction_id, auto_recover=True): if transaction_id is None: return NSIndex() - index_path = (os.path.join(self.path, 'index.%d') % transaction_id).encode('utf-8') + index_path = os.path.join(self.path, 'index.%d' % transaction_id).encode('utf-8') try: return NSIndex.read(index_path) - except RuntimeError as re: - assert str(re) == 'hashindex_read failed' # everything else means we're in *deep* trouble + except RuntimeError as error: + assert str(error) == 'hashindex_read failed' # everything else means we're in *deep* trouble # corrupted index file, need to replay segments - os.unlink(os.path.join(self.path, 'hints.%d' % transaction_id)) - os.unlink(os.path.join(self.path, 'index.%d' % transaction_id)) + try: + os.unlink(index_path) + except OSError as e: + raise InternalOSError from e if not auto_recover: raise self.prepare_txn(self.get_transaction_id()) # don't leave an open transaction around self.commit() return self.open_index(self.get_transaction_id()) + except OSError as e: + raise InternalOSError from e def prepare_txn(self, transaction_id, do_cleanup=True): self._active_txn = True @@ -275,15 +279,17 @@ class Repository: else: if do_cleanup: self.io.cleanup(transaction_id) + hints_path = os.path.join(self.path, 'hints.%d' % transaction_id) + index_path = os.path.join(self.path, 'index.%d' % transaction_id) try: - with open(os.path.join(self.path, 'hints.%d' % transaction_id), 'rb') as fd: + with open(hints_path, 'rb') as fd: hints = msgpack.unpack(fd) except (msgpack.UnpackException, msgpack.ExtraData, FileNotFoundError) as e: # corrupted or deleted hints file, need to replay segments if not isinstance(e, FileNotFoundError): - os.unlink(os.path.join(self.path, 'hints.%d' % transaction_id)) + os.unlink(hints_path) # index must exist at this point - os.unlink(os.path.join(self.path, 'index.%d' % transaction_id)) + os.unlink(index_path) self.check_transaction() self.prepare_txn(transaction_id) return diff --git a/borg/testsuite/repository.py b/borg/testsuite/repository.py index 346711424..6b758fb78 100644 --- a/borg/testsuite/repository.py +++ b/borg/testsuite/repository.py @@ -283,14 +283,18 @@ class RepositoryAuxiliaryCorruptionTestCase(RepositoryTestCaseBase): self.repository.commit() def test_corrupted_hints(self): - with open(os.path.join(self.repository.path, 'hints.0'), 'ab') as fp: - fp.write(b'123456789') + with open(os.path.join(self.repository.path, 'hints.0'), 'ab') as fd: + fd.write(b'123456789') self.do_commit() def test_deleted_hints(self): os.unlink(os.path.join(self.repository.path, 'hints.0')) self.do_commit() + def test_deleted_index(self): + os.unlink(os.path.join(self.repository.path, 'index.0')) + self.do_commit() + def test_unreadable_hints(self): hints = os.path.join(self.repository.path, 'hints.0') os.unlink(hints) @@ -299,16 +303,23 @@ class RepositoryAuxiliaryCorruptionTestCase(RepositoryTestCaseBase): self.do_commit() def test_index(self): - with open(os.path.join(self.repository.path, 'index.0'), 'wb') as fp: - fp.write(b'123456789') + with open(os.path.join(self.repository.path, 'index.0'), 'wb') as fd: + fd.write(b'123456789') self.do_commit() def test_index_outside_transaction(self): - with open(os.path.join(self.repository.path, 'index.0'), 'wb') as fp: - fp.write(b'123456789') + with open(os.path.join(self.repository.path, 'index.0'), 'wb') as fd: + fd.write(b'123456789') with self.repository: assert len(self.repository) == 1 + def test_unreadable_index(self): + index = os.path.join(self.repository.path, 'index.0') + os.unlink(index) + os.mkdir(index) + with self.assert_raises(InternalOSError): + self.do_commit() + class RepositoryCheckTestCase(RepositoryTestCaseBase):