diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SqlCipherErrorHandler.java b/app/src/main/java/org/thoughtcrime/securesms/database/SqlCipherErrorHandler.java index 6fa02aeab..cd8a4b1c9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SqlCipherErrorHandler.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SqlCipherErrorHandler.java @@ -9,6 +9,7 @@ import net.zetetic.database.sqlcipher.SQLiteDatabase; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.util.CursorUtil; +import org.thoughtcrime.securesms.util.Util; /** * The default error handler wipes the file. This one instead prints some diagnostics and then crashes so the original corrupt file isn't lost. @@ -24,30 +25,60 @@ public final class SqlCipherErrorHandler implements DatabaseErrorHandler { } @Override + @SuppressWarnings("ConstantConditions") public void onCorruption(SQLiteDatabase db) { - Log.e(TAG, "Database '" + databaseName + "' corrupted! Going to try to run some diagnostics."); + StringBuilder output = new StringBuilder(); - Log.w(TAG, " ===== PRAGMA integrity_check ====="); + output.append("Database '").append(databaseName).append("' corrupted! Going to try to run some diagnostics.").append("\n"); + + boolean pragma1Passes = false; + boolean pragma2Passes = true; + + output.append(" ===== PRAGMA integrity_check =====").append("\n"); try (Cursor cursor = db.rawQuery("PRAGMA integrity_check", null)) { while (cursor.moveToNext()) { - Log.w(TAG, CursorUtil.readRowAsString(cursor)); + String row = CursorUtil.readRowAsString(cursor); + output.append(row).append("\n"); + if (row.toLowerCase().contains("ok")) { + pragma1Passes = true; + } } } catch (Throwable t) { - Log.e(TAG, "Failed to do integrity_check!", t); + output.append("Failed to do integrity_check!").append("\n") + .append(Util.convertThrowableToString(t)); } - Log.w(TAG, "===== PRAGMA cipher_integrity_check ====="); + output.append("\n").append("===== PRAGMA cipher_integrity_check =====").append("\n"); try (Cursor cursor = db.rawQuery("PRAGMA cipher_integrity_check", null)) { while (cursor.moveToNext()) { - Log.w(TAG, CursorUtil.readRowAsString(cursor)); + output.append(CursorUtil.readRowAsString(cursor)).append("\n"); + pragma2Passes = false; } } catch (Throwable t) { - Log.e(TAG, "Failed to do cipher_integrity_check!", t); + output.append("Failed to do cipher_integrity_check!").append("\n") + .append(Util.convertThrowableToString(t)); } - throw new DatabaseCorruptedError(); + Log.e(TAG, output.toString()); + + if (pragma1Passes && pragma2Passes) { + throw new DatabaseCorruptedError_BothChecksPass(); + } else if (!pragma1Passes && pragma2Passes) { + throw new DatabaseCorruptedError_NormalCheckFailsCipherCheckPasses(); + } else if (pragma1Passes && !pragma2Passes) { + throw new DatabaseCorruptedError_NormalCheckPassesCipherCheckFails(); + } else { + throw new DatabaseCorruptedError_BothChecksFail(); + } } - public static final class DatabaseCorruptedError extends Error { + + public static final class DatabaseCorruptedError_BothChecksPass extends Error { + } + public static final class DatabaseCorruptedError_BothChecksFail extends Error { + } + public static final class DatabaseCorruptedError_NormalCheckFailsCipherCheckPasses extends Error { + } + public static final class DatabaseCorruptedError_NormalCheckPassesCipherCheckFails extends Error { } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/Util.java b/app/src/main/java/org/thoughtcrime/securesms/util/Util.java index e25ccba18..7bcc33423 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/Util.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/Util.java @@ -54,6 +54,7 @@ import org.whispersystems.libsignal.util.guava.Optional; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.PrintStream; import java.io.UnsupportedEncodingException; import java.security.SecureRandom; import java.util.ArrayList; @@ -535,4 +536,10 @@ public class Util { return primary; } + + public static @NonNull String convertThrowableToString(@NonNull Throwable throwable) { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + throwable.printStackTrace(new PrintStream(outputStream)); + return outputStream.toString(); + } }