Better database error handling.

This commit is contained in:
Greyson Parrelli 2021-06-09 15:04:16 -04:00 committed by GitHub
parent f8d2044356
commit d5f63da9e4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 74 additions and 4 deletions

View file

@ -101,7 +101,7 @@ public class JobDatabase extends SQLiteOpenHelper implements SignalDatabase {
}
public JobDatabase(@NonNull Application application, @NonNull DatabaseSecret databaseSecret) {
super(application, DATABASE_NAME, null, DATABASE_VERSION, new SqlCipherDatabaseHook());
super(application, DATABASE_NAME, null, DATABASE_VERSION, new SqlCipherDatabaseHook(), new SqlCipherErrorHandler(DATABASE_NAME));
this.application = application;
this.databaseSecret = databaseSecret;

View file

@ -61,7 +61,7 @@ public class KeyValueDatabase extends SQLiteOpenHelper implements SignalDatabase
}
public KeyValueDatabase(@NonNull Application application, @NonNull DatabaseSecret databaseSecret) {
super(application, DATABASE_NAME, null, DATABASE_VERSION, new SqlCipherDatabaseHook());
super(application, DATABASE_NAME, null, DATABASE_VERSION, new SqlCipherDatabaseHook(), new SqlCipherErrorHandler(DATABASE_NAME));
this.application = application;
this.databaseSecret = databaseSecret;

View file

@ -64,7 +64,7 @@ public class MegaphoneDatabase extends SQLiteOpenHelper implements SignalDatabas
}
public MegaphoneDatabase(@NonNull Application application, @NonNull DatabaseSecret databaseSecret) {
super(application, DATABASE_NAME, null, DATABASE_VERSION, new SqlCipherDatabaseHook());
super(application, DATABASE_NAME, null, DATABASE_VERSION, new SqlCipherDatabaseHook(), new SqlCipherErrorHandler(DATABASE_NAME));
this.application = application;
this.databaseSecret = databaseSecret;

View file

@ -0,0 +1,53 @@
package org.thoughtcrime.securesms.database;
import android.database.Cursor;
import androidx.annotation.NonNull;
import net.sqlcipher.DatabaseErrorHandler;
import net.sqlcipher.database.SQLiteDatabase;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.util.CursorUtil;
/**
* The default error handler wipes the file. This one instead prints some diagnostics and then crashes so the original corrupt file isn't lost.
*/
public final class SqlCipherErrorHandler implements DatabaseErrorHandler {
private static final String TAG = Log.tag(SqlCipherErrorHandler.class);
private final String tableName;
public SqlCipherErrorHandler(@NonNull String tableName) {
this.tableName = tableName;
}
@Override
public void onCorruption(SQLiteDatabase db) {
Log.e(TAG, "Database '" + tableName + "' corrupted! Going to try to run some diagnostics.");
Log.w(TAG, " ===== PRAGMA integrity_check =====");
try (Cursor cursor = db.rawQuery("PRAGMA integrity_check", null)) {
while (cursor.moveToNext()) {
Log.w(TAG, CursorUtil.readRowAsString(cursor));
}
} catch (Throwable t) {
Log.e(TAG, "Failed to do integrity_check!", t);
}
Log.w(TAG, "===== PRAGMA cipher_integrity_check =====");
try (Cursor cursor = db.rawQuery("PRAGMA cipher_integrity_check", null)) {
while (cursor.moveToNext()) {
Log.w(TAG, CursorUtil.readRowAsString(cursor));
}
} catch (Throwable t) {
Log.e(TAG, "Failed to do cipher_integrity_check!", t);
}
throw new DatabaseCorruptedError();
}
public static final class DatabaseCorruptedError extends Error {
}
}

View file

@ -52,6 +52,7 @@ import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.SignedPreKeyDatabase;
import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.database.SqlCipherDatabaseHook;
import org.thoughtcrime.securesms.database.SqlCipherErrorHandler;
import org.thoughtcrime.securesms.database.StickerDatabase;
import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.database.UnknownStorageIdDatabase;
@ -205,7 +206,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper implements SignalDatab
private final DatabaseSecret databaseSecret;
public SQLCipherOpenHelper(@NonNull Context context, @NonNull DatabaseSecret databaseSecret) {
super(context, DATABASE_NAME, null, DATABASE_VERSION, new SqlCipherDatabaseHook());
super(context, DATABASE_NAME, null, DATABASE_VERSION, new SqlCipherDatabaseHook(), new SqlCipherErrorHandler(DATABASE_NAME));
this.context = context.getApplicationContext();
this.databaseSecret = databaseSecret;

View file

@ -77,4 +77,20 @@ public final class CursorUtil {
return Optional.fromNullable(requireBlob(cursor, column));
}
}
/**
* Reads each column as a string, and concatenates them together into a single string separated by |
*/
public static String readRowAsString(@NonNull Cursor cursor) {
StringBuilder row = new StringBuilder();
for (int i = 0, len = cursor.getColumnCount(); i < len; i++) {
row.append(cursor.getString(i));
if (i < len - 1) {
row.append(" | ");
}
}
return row.toString();
}
}