diff --git a/app/src/main/java/com/stevesoltys/seedvault/repo/Loader.kt b/app/src/main/java/com/stevesoltys/seedvault/repo/Loader.kt index 2c527f57..243ca3b7 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/repo/Loader.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/repo/Loader.kt @@ -96,6 +96,7 @@ internal class Loader( } } catch (e: Exception) { log.error(e) { "Error writing cache file $cacheFile: " } + cacheFile?.delete() } // get associated data for version, used for authenticated decryption val ad = crypto.getAdForVersion(version) diff --git a/app/src/main/java/com/stevesoltys/seedvault/repo/Pruner.kt b/app/src/main/java/com/stevesoltys/seedvault/repo/Pruner.kt index 6175eaa7..4284e292 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/repo/Pruner.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/repo/Pruner.kt @@ -11,6 +11,7 @@ import com.stevesoltys.seedvault.proto.Snapshot import io.github.oshai.kotlinlogging.KotlinLogging import org.calyxos.seedvault.core.backends.AppBackupFileType import org.calyxos.seedvault.core.backends.TopLevelFolder +import java.security.GeneralSecurityException import java.time.LocalDate import java.time.temporal.ChronoField import java.time.temporal.TemporalAdjuster @@ -42,9 +43,14 @@ internal class Pruner( val snapshotMap = mutableMapOf() val snapshots = mutableListOf() snapshotHandles.forEach { handle -> - val snapshot = snapshotManager.loadSnapshot(handle) // exception is allowed to bubble up - snapshotMap[snapshot.token] = handle - snapshots.add(snapshot) + try { + val snapshot = snapshotManager.loadSnapshot(handle) + snapshotMap[snapshot.token] = handle + snapshots.add(snapshot) + } catch (e: GeneralSecurityException) { + log.error(e) { "Error loading snapshot $handle, will remove: " } + snapshotManager.removeSnapshot(handle) + } // other exceptions (like IOException) are allowed to bubble up, so we try again } // find out which snapshots to keep val toKeep = getTokenToKeep(snapshotMap.keys) diff --git a/app/src/main/java/com/stevesoltys/seedvault/worker/ApkBackup.kt b/app/src/main/java/com/stevesoltys/seedvault/worker/ApkBackup.kt index 1176d194..7b945845 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/worker/ApkBackup.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/worker/ApkBackup.kt @@ -113,6 +113,7 @@ internal class ApkBackup( val blobMap = chunkIds.associateWith { chunkId -> latestSnapshot.blobsMap[chunkId] ?: error("Missing blob for $chunkId") } + // TODO could also check if all blobs are (still) available in BlobCache // important: add old APK to snapshot or it wouldn't be part of backup snapshotCreator.onApkBackedUp(packageInfo, oldApk, blobMap) return diff --git a/app/src/test/java/com/stevesoltys/seedvault/repo/PrunerTest.kt b/app/src/test/java/com/stevesoltys/seedvault/repo/PrunerTest.kt index 8f559223..0b40708b 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/repo/PrunerTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/repo/PrunerTest.kt @@ -23,6 +23,7 @@ import org.calyxos.seedvault.core.backends.FileInfo import org.calyxos.seedvault.core.backends.TopLevelFolder import org.calyxos.seedvault.core.toHexString import org.junit.jupiter.api.Test +import java.security.GeneralSecurityException import java.time.LocalDateTime import java.time.ZoneOffset.UTC import java.util.concurrent.TimeUnit @@ -61,6 +62,32 @@ internal class PrunerTest : TransportTest() { pruner.removeOldSnapshotsAndPruneUnusedBlobs() } + @Test + fun `corrupted snapshot gets removed`() = runBlocking { + val snapshot = snapshot.copy { token = System.currentTimeMillis() } + every { crypto.repoId } returns repoId + every { backendManager.backend } returns backend + coEvery { + backend.list(folder, AppBackupFileType.Snapshot::class, callback = captureLambda()) + } answers { + val fileInfo = FileInfo(snapshotHandle1, Random.nextLong(Long.MAX_VALUE)) + lambda<(FileInfo) -> Unit>().captured.invoke(fileInfo) + } + coEvery { snapshotManager.loadSnapshot(snapshotHandle1) } throws GeneralSecurityException() + coEvery { snapshotManager.removeSnapshot(snapshotHandle1) } just Runs + + // we only find blobs that are in snapshots + val blobIds = snapshot.blobsMap.values.map { it.id.hexFromProto() } + expectLoadingBlobs(blobIds) + + // but since all snapshots got removed, we remove all blobs :( + blobIds.forEach { + coEvery { backend.remove(AppBackupFileType.Blob(repoId, it)) } just Runs + } + + pruner.removeOldSnapshotsAndPruneUnusedBlobs() + } + @Test fun `three snapshots from same day don't remove blobs`() = runBlocking { val snapshotMap = mapOf(