Procházet zdrojové kódy

Handle permission and similar errors on the index

Marian Beermann před 9 roky
rodič
revize
d979a84f37
3 změnil soubory, kde provedl 41 přidání a 14 odebrání
  1. 10 0
      borg/hashindex.pyx
  2. 14 8
      borg/repository.py
  3. 17 6
      borg/testsuite/repository.py

+ 10 - 0
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)

+ 14 - 8
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

+ 17 - 6
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):