Repository: borgbackup/borg

Issue:
sporadic test failure with UnsupportedPayloadError
1.1-maint branch:
```
_______________ RemoteArchiverTestCase.test_delete_double_force ________________
[gw3] linux -- Python 3.9.1 /home/runner/work/borg/borg/.tox/py39/bin/python

self = <borg.testsuite.archiver.RemoteArchiverTestCase testMethod=test_delete_double_force>

    def test_delete_double_force(self):
        self.cmd('init', '--encryption=none', self.repository_location)
        self.create_src_archive('test')
        with Repository(self.repository_path, exclusive=True) as repository:
            manifest, key = Manifest.load(repository, Manifest.NO_OPERATION_CHECK)
            archive = Archive(repository, key, manifest, 'test')
            id = archive.metadata.items[0]
            repository.put(id, b'corrupted items metadata stream chunk')
            repository.commit()
        self.cmd('delete', '--force', '--force', self.repository_location + '::test')
>       self.cmd('check', '--repair', self.repository_location)

.tox/py39/lib/python3.9/site-packages/borg/testsuite/archiver.py:1572: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.tox/py39/lib/python3.9/site-packages/borg/testsuite/archiver.py:290: in cmd
    ret, output = exec_cmd(*args, fork=fork, exe=self.EXE, archiver=self.archiver, **kw)
.tox/py39/lib/python3.9/site-packages/borg/testsuite/archiver.py:110: in exec_cmd
    ret = archiver.run(args)
.tox/py39/lib/python3.9/site-packages/borg/archiver.py:4601: in run
    return set_ec(func(args))
.tox/py39/lib/python3.9/site-packages/borg/archiver.py:177: in wrapper
    return method(self, args, repository=repository, **kwargs)
.tox/py39/lib/python3.9/site-packages/borg/archiver.py:347: in do_check
    if not args.repo_only and not ArchiveChecker().check(
.tox/py39/lib/python3.9/site-packages/borg/archive.py:1234: in check
    self.key = self.identify_key(repository)
.tox/py39/lib/python3.9/site-packages/borg/archive.py:1283: in identify_key
    return key_factory(repository, cdata)
.tox/py39/lib/python3.9/site-packages/borg/crypto/key.py:134: in key_factory
    return identify_key(manifest_data).detect(repository, manifest_data)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

manifest_data = b'corrupted items metadata stream chunk'

    def identify_key(manifest_data):
        key_type = manifest_data[0]
        if key_type == PassphraseKey.TYPE:
            # we just dispatch to repokey mode and assume the passphrase was migrated to a repokey.
            # see also comment in PassphraseKey class.
            return RepoKey
    
        for key in AVAILABLE_KEY_TYPES:
            if key.TYPE == key_type:
                return key
        else:
>           raise UnsupportedPayloadError(key_type)
E           borg.crypto.key.UnsupportedPayloadError: Unsupported payload type 99. A newer version is required to access this repository.

.tox/py39/lib/python3.9/site-packages/borg/crypto/key.py:130: UnsupportedPayloadError
```

Notable:
- `ord('c') == 99` --- 'c' like in "corrupted..."
- this is a "RemoteArchiver" test (not sure if it matters)


Discussion:

Resolving patch:
diff --git a/src/borg/archive.py b/src/borg/archive.py
--- a/src/borg/archive.py
+++ b/src/borg/archive.py
@@ -21,7 +21,7 @@
 from . import xattr
 from .chunker import get_chunker, Chunk
 from .cache import ChunkListEntry
-from .crypto.key import key_factory
+from .crypto.key import key_factory, UnsupportedPayloadError
 from .compress import Compressor, CompressionSpec
 from .constants import *  # NOQA
 from .crypto.low_level import IntegrityError as IntegrityErrorBase
@@ -1612,7 +1612,7 @@ def check(self, repository, repair=False, archive=None, first=0, last=0, sort_by
         if not self.chunks:
             logger.error('Repository contains no apparent data at all, cannot continue check/repair.')
             return False
-        self.key = self.identify_key(repository)
+        self.key = self.make_key(repository)
         if verify_data:
             self.verify_data()
         if Manifest.MANIFEST_ID not in self.chunks:
@@ -1653,14 +1653,24 @@ def init_chunks(self):
             for id_ in result:
                 self.chunks[id_] = init_entry
 
-    def identify_key(self, repository):
-        try:
-            some_chunkid, _ = next(self.chunks.iteritems())
-        except StopIteration:
-            # repo is completely empty, no chunks
-            return None
-        cdata = repository.get(some_chunkid)
-        return key_factory(repository, cdata)
+    def make_key(self, repository):
+        attempt = 0
+        for chunkid, _ in self.chunks.iteritems():
+            attempt += 1
+            if attempt > 999:
+                # we did a lot of attempts, but could not create the key via key_factory, give up.
+                break
+            cdata = repository.get(chunkid)
+            try:
+                return key_factory(repository, cdata)
+            except UnsupportedPayloadError:
+                # we get here, if the cdata we got has a corrupted key type byte
+                pass  # ignore it, just try the next chunk
+        if attempt == 0:
+            msg = 'make_key: repository has no chunks at all!'
+        else:
+            msg = 'make_key: failed to create the key (tried %d chunks)' % attempt
+        raise IntegrityError(msg)
 
     def verify_data(self):
         logger.info('Starting cryptographic data integrity verification...')


Test patch: