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.
This commit is contained in:
Alexander Bakker 2020-02-02 13:23:45 +01:00
parent 833e75ade1
commit 4ba3caeaf4

View file

@ -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);
}
}