From 4ba3caeaf43001bd418b4232dd2fa98e69e64418 Mon Sep 17 00:00:00 2001 From: Alexander Bakker Date: Sun, 2 Feb 2020 13:23:45 +0100 Subject: [PATCH] Protect writes of the vault file against corruption with AtomicFile This adds a recovery mechanism for (probably extremely rare) cases where the app may be killed before it is finished writing the vault file to disk. In the example below, we see that AtomicFile moved ``aegis.json`` to ``aegis.json.bak`` before writing to ``aegis.json``. ``` bonito:/ # ls -lah /data/data/com.beemdevelopment.aegis.debug/files total 27M drwxrwx--x 2 u0_a306 u0_a306 3.4K 2020-02-02 13:22 . drwx------ 6 u0_a306 u0_a306 3.4K 2020-02-01 19:51 .. -rw------- 1 u0_a306 u0_a306 19M 2020-02-02 13:22 aegis.json -rw------- 1 u0_a306 u0_a306 34M 2020-02-02 13:21 aegis.json.bak ``` Because the app was killed before it could finish writing, it is only 19M in size, instead of the expected 34M. The next time the app starts, AtomicFile will notice that the .bak file is still present, and use that instead of the corrupted ``aegis.json`` file. --- .../aegis/vault/VaultManager.java | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/com/beemdevelopment/aegis/vault/VaultManager.java b/app/src/main/java/com/beemdevelopment/aegis/vault/VaultManager.java index 1d4ec33a..b378ad6c 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/vault/VaultManager.java +++ b/app/src/main/java/com/beemdevelopment/aegis/vault/VaultManager.java @@ -3,13 +3,13 @@ package com.beemdevelopment.aegis.vault; import android.content.Context; import android.content.Intent; +import androidx.core.util.AtomicFile; + import com.beemdevelopment.aegis.services.NotificationService; import org.json.JSONObject; -import java.io.DataInputStream; import java.io.File; -import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; @@ -41,12 +41,9 @@ public class VaultManager { public void load() throws VaultManagerException { assertState(true, false); - try (FileInputStream file = _context.openFileInput(FILENAME)) { - byte[] fileBytes = new byte[(int) file.getChannel().size()]; - DataInputStream stream = new DataInputStream(file); - stream.readFully(fileBytes); - stream.close(); - + AtomicFile file = new AtomicFile(new File(_context.getFilesDir(), FILENAME)); + try { + byte[] fileBytes = file.readFully(); _file = VaultFile.fromBytes(fileBytes); _encrypt = _file.isEncrypted(); if (!isEncryptionEnabled()) { @@ -77,11 +74,19 @@ public class VaultManager { } } - public static void save(Context context, VaultFile file) throws VaultManagerException { - byte[] bytes = file.toBytes(); - try (FileOutputStream stream = context.openFileOutput(FILENAME, Context.MODE_PRIVATE)) { + public static void save(Context context, VaultFile vaultFile) throws VaultManagerException { + byte[] bytes = vaultFile.toBytes(); + AtomicFile file = new AtomicFile(new File(context.getFilesDir(), FILENAME)); + + FileOutputStream stream = null; + try { + stream = file.startWrite(); stream.write(bytes); + file.finishWrite(stream); } catch (IOException e) { + if (stream != null) { + file.failWrite(stream); + } throw new VaultManagerException(e); } }