Browse Source

cache integrity: handle interference from old versions

Marian Beermann 8 years ago
parent
commit
d35d388d9c
2 changed files with 33 additions and 4 deletions
  1. 16 4
      src/borg/cache.py
  2. 17 0
      src/borg/testsuite/archiver.py

+ 16 - 4
src/borg/cache.py

@@ -240,6 +240,7 @@ class CacheConfig:
         config.set('cache', 'repository', self.repository.id_str)
         config.set('cache', 'repository', self.repository.id_str)
         config.set('cache', 'manifest', '')
         config.set('cache', 'manifest', '')
         config.add_section('integrity')
         config.add_section('integrity')
+        config.set('integrity', 'manifest', '')
         with SaveFile(self.config_path) as fd:
         with SaveFile(self.config_path) as fd:
             config.write(fd)
             config.write(fd)
 
 
@@ -256,11 +257,19 @@ class CacheConfig:
         self.manifest_id = unhexlify(self._config.get('cache', 'manifest'))
         self.manifest_id = unhexlify(self._config.get('cache', 'manifest'))
         self.timestamp = self._config.get('cache', 'timestamp', fallback=None)
         self.timestamp = self._config.get('cache', 'timestamp', fallback=None)
         self.key_type = self._config.get('cache', 'key_type', fallback=None)
         self.key_type = self._config.get('cache', 'key_type', fallback=None)
-        if not self._config.has_section('integrity'):
-            self._config.add_section('integrity')
         try:
         try:
             self.integrity = dict(self._config.items('integrity'))
             self.integrity = dict(self._config.items('integrity'))
+            if self._config.get('cache', 'manifest') != self.integrity.pop('manifest'):
+                # The cache config file is updated (parsed with ConfigParser, the state of the ConfigParser
+                # is modified and then written out.), not re-created.
+                # Thus, older versions will leave our [integrity] section alone, making the section's data invalid.
+                # Therefore, we also add the manifest ID to this section and
+                # can discern whether an older version interfere by comparing the manifest IDs of this section
+                # and the main [cache] section.
+                self.integrity = {}
+                logger.warning('Cache integrity data lost: old Borg version modified the cache.')
         except configparser.NoSectionError:
         except configparser.NoSectionError:
+            logger.debug('Cache integrity: No integrity data found (files, chunks). Cache is from old version.')
             self.integrity = {}
             self.integrity = {}
         previous_location = self._config.get('cache', 'previous_location', fallback=None)
         previous_location = self._config.get('cache', 'previous_location', fallback=None)
         if previous_location:
         if previous_location:
@@ -272,11 +281,14 @@ class CacheConfig:
         if manifest:
         if manifest:
             self._config.set('cache', 'manifest', manifest.id_str)
             self._config.set('cache', 'manifest', manifest.id_str)
             self._config.set('cache', 'timestamp', manifest.timestamp)
             self._config.set('cache', 'timestamp', manifest.timestamp)
+            if not self._config.has_section('integrity'):
+                self._config.add_section('integrity')
+            for file, integrity_data in self.integrity.items():
+                self._config.set('integrity', file, integrity_data)
+            self._config.set('integrity', 'manifest', manifest.id_str)
         if key:
         if key:
             self._config.set('cache', 'key_type', str(key.TYPE))
             self._config.set('cache', 'key_type', str(key.TYPE))
         self._config.set('cache', 'previous_location', self.repository._location.canonical_path())
         self._config.set('cache', 'previous_location', self.repository._location.canonical_path())
-        for file, integrity_data in self.integrity.items():
-            self._config.set('integrity', file, integrity_data)
         with SaveFile(self.config_path) as fd:
         with SaveFile(self.config_path) as fd:
             self._config.write(fd)
             self._config.write(fd)
 
 

+ 17 - 0
src/borg/testsuite/archiver.py

@@ -2949,6 +2949,23 @@ class ArchiverCorruptionTestCase(ArchiverTestCaseBase):
         assert 'Cached archive chunk index of test1 is corrupted' in out
         assert 'Cached archive chunk index of test1 is corrupted' in out
         assert 'Fetching and building archive index for test1' in out
         assert 'Fetching and building archive index for test1' in out
 
 
+    def test_old_version_intefered(self):
+        self.create_test_files()
+        self.cmd('init', '--encryption=repokey', self.repository_location)
+        cache_path = json.loads(self.cmd('info', self.repository_location, '--json'))['cache']['path']
+
+        # Modify the main manifest ID without touching the manifest ID in the integrity section.
+        # This happens if a version without integrity checking modifies the cache.
+        config_path = os.path.join(cache_path, 'config')
+        config = ConfigParser(interpolation=None)
+        config.read(config_path)
+        config.set('cache', 'manifest', bin_to_hex(bytes(32)))
+        with open(config_path, 'w') as fd:
+            config.write(fd)
+
+        out = self.cmd('info', self.repository_location)
+        assert 'Cache integrity data lost: old Borg version modified the cache.' in out
+
 
 
 class DiffArchiverTestCase(ArchiverTestCaseBase):
 class DiffArchiverTestCase(ArchiverTestCaseBase):
     def test_basic_functionality(self):
     def test_basic_functionality(self):