Skip to content

Commit 2feec29

Browse files
author
Chris Brody
committed
Updated changes with optional corrupt database handler (ref: sqlcipher#169)
1 parent 4589bbb commit 2feec29

File tree

4 files changed

+250
-8
lines changed

4 files changed

+250
-8
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Copyright (C) 2010 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package net.sqlcipher.database;
18+
19+
import net.sqlcipher.database.SQLiteDatabase;
20+
21+
/**
22+
* An interface to let the apps define the actions to take when the following errors are detected
23+
* database corruption
24+
*/
25+
public interface DatabaseErrorHandler {
26+
27+
/**
28+
* defines the method to be invoked when database corruption is detected.
29+
* @param dbObj the {@link SQLiteDatabase} object representing the database on which corruption
30+
* is detected.
31+
*/
32+
void onCorruption(SQLiteDatabase dbObj);
33+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* Copyright (C) 2010 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package net.sqlcipher.database;
18+
19+
import java.io.File;
20+
import java.util.List;
21+
22+
import net.sqlcipher.database.SQLiteDatabase;
23+
import net.sqlcipher.database.SQLiteException;
24+
25+
import android.util.Log;
26+
import android.util.Pair;
27+
28+
/**
29+
* Default class used to define the actions to take when the database corruption is reported
30+
* by sqlite.
31+
* <p>
32+
* If null is specified for DatabaeErrorHandler param in the above calls, then this class is used
33+
* as the default {@link DatabaseErrorHandler}.
34+
*/
35+
public final class DefaultDatabaseErrorHandler implements DatabaseErrorHandler {
36+
37+
private static final String TAG = "DefaultDatabaseErrorHandler";
38+
39+
/**
40+
* defines the default method to be invoked when database corruption is detected.
41+
* @param dbObj the {@link SQLiteDatabase} object representing the database on which corruption
42+
* is detected.
43+
*/
44+
public void onCorruption(SQLiteDatabase dbObj) {
45+
Log.e(TAG, "Corruption reported by sqlite on database, deleting and re-creating: " + dbObj.getPath());
46+
47+
if (dbObj.isOpen()) {
48+
Log.e(TAG, "Database object for corrupted database is open, not expected for this implementation");
49+
try {
50+
dbObj.close();
51+
} catch (Exception e) {
52+
/* ignore */
53+
Log.e(TAG, "Exception closing Database object for corrupted database, ignored", e);
54+
}
55+
}
56+
57+
deleteDatabaseFile(dbObj.getPath());
58+
}
59+
60+
private void deleteDatabaseFile(String fileName) {
61+
if (fileName.equalsIgnoreCase(":memory:") || fileName.trim().length() == 0) {
62+
return;
63+
}
64+
Log.e(TAG, "deleting the database file: " + fileName);
65+
try {
66+
new File(fileName).delete();
67+
} catch (Exception e) {
68+
/* print warning and ignore exception */
69+
Log.w(TAG, "delete failed: " + e.getMessage());
70+
}
71+
}
72+
}

src/net/sqlcipher/database/SQLiteDatabase.java

Lines changed: 110 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,7 @@ protected void onAllReferencesReleased() {
444444
* @param lockingEnabled set to true to enable locks, false otherwise
445445
*/
446446
public void setLockingEnabled(boolean lockingEnabled) {
447+
/* XXX TBD check if open? */
447448
mLockingEnabled = lockingEnabled;
448449
}
449450

@@ -741,6 +742,7 @@ public void setTransactionSuccessful() {
741742
* return true if there is a transaction pending
742743
*/
743744
public boolean inTransaction() {
745+
/* XXX TBD check if open? */
744746
return mLock.getHoldCount() > 0;
745747
}
746748

@@ -750,6 +752,7 @@ public boolean inTransaction() {
750752
* @return true, if this thread is holding the database lock.
751753
*/
752754
public boolean isDbLockedByCurrentThread() {
755+
/* XXX TBD check if open? */
753756
return mLock.isHeldByCurrentThread();
754757
}
755758

@@ -948,6 +951,7 @@ public static SQLiteDatabase openDatabase(String path, String password, CursorFa
948951
return openDatabase(path, password.toCharArray(), factory, flags, null);
949952
}
950953

954+
<<<<<<< HEAD
951955
/**
952956
* Open the database according to the flags {@link #OPEN_READWRITE}
953957
* {@link #OPEN_READONLY} {@link #CREATE_IF_NECESSARY} and/or {@link #NO_LOCALIZED_COLLATORS}.
@@ -991,7 +995,7 @@ public static SQLiteDatabase openDatabase(String path, char[] password, CursorFa
991995
* @throws IllegalArgumentException if the database path is null
992996
*/
993997
public static SQLiteDatabase openDatabase(String path, String password, CursorFactory factory, int flags, SQLiteDatabaseHook hook) {
994-
return openDatabase(path, password.toCharArray(), factory, flags, hook);
998+
return openDatabase(path, password.toCharArray(), factory, flags, hook, new DefaultDatabaseErrorHandler());
995999
}
9961000

9971001
/**
@@ -1015,6 +1019,21 @@ public static SQLiteDatabase openDatabase(String path, String password, CursorFa
10151019
* @throws IllegalArgumentException if the database path is null
10161020
*/
10171021
public static SQLiteDatabase openDatabase(String path, char[] password, CursorFactory factory, int flags, SQLiteDatabaseHook hook) {
1022+
return openDatabase(path, password, factory, flags, hook, new DefaultDatabaseErrorHandler());
1023+
}
1024+
1025+
public static SQLiteDatabase openDatabase(String path, String password, CursorFactory factory, int flags,
1026+
SQLiteDatabaseHook databaseHook, DatabaseErrorHandler errorHandler) {
1027+
return openDatabase(path, password.toCharArray(), factory, flags, databaseHook, new DefaultDatabaseErrorHandler());
1028+
}
1029+
1030+
public static SQLiteDatabase openDatabase(String path, String password, CursorFactory factory, int flags,
1031+
SQLiteDatabaseHook databaseHook, DatabaseErrorHandler errorHandler) {
1032+
return openDatabase(path, password.toCharArray(), factory, flags, databaseHook, errorHandler);
1033+
}
1034+
1035+
public static SQLiteDatabase openDatabase(String path, char[] password, CursorFactory factory, int flags,
1036+
SQLiteDatabaseHook databaseHook, DatabaseErrorHandler errorHandler) {
10181037
SQLiteDatabase sqliteDatabase = null;
10191038

10201039
try {
@@ -1031,13 +1050,43 @@ public static SQLiteDatabase openDatabase(String path, char[] password, CursorFa
10311050
} catch (SQLiteDatabaseCorruptException e) {
10321051
// Try to recover from this, if we can.
10331052
// TODO: should we do this for other open failures?
1053+
1054+
Log.e(TAG, "Calling error handler for corrupt database " + path, e);
1055+
// XXX Chris TODO TODO:
1056+
try {
1057+
errorHandler.onCorruption(new SQLiteDatabase(path)); // HORRIBLE HACK to support DatabaseErrorHandler API
1058+
} catch (Exception e2) {
1059+
Log.e(TAG, "error handler failed with exception", e2);
1060+
//throw e;
1061+
1062+
synchronized (sActiveDatabases) {
1063+
sActiveDatabases.put(sqliteDatabase, null);
1064+
}
1065+
sqliteDatabase = openDatabase(path, password, factory, flags, databaseHook, errorHandler);
1066+
}
1067+
return sqliteDatabase;
1068+
}
1069+
1070+
<<<<<<< HEAD
1071+
// Try to recover from this, if we can.
1072+
// TODO: should we do this for other open failures?
10341073
Log.e(TAG, "Deleting and re-creating corrupt database " + path, e);
10351074
// EventLog.writeEvent(EVENT_DB_CORRUPT, path);
10361075

1076+
=======
1077+
Log.e(TAG, "Calling error handler for corrupt database " + path, e);
1078+
try {
1079+
errorHandler.onCorruption(new SQLiteDatabase(path)); // HORRIBLE HACK to support DatabaseErrorHandler API
1080+
} catch (Exception e2) {
1081+
Log.e(TAG, "error handler failed with exception", e2);
1082+
//throw e;
1083+
>>>>>>> d3e4c49... Updated changes with optional corrupt database handler (ref: sqlcipher/android-database-sqlcipher#169)
10371084
if (!path.equalsIgnoreCase(":memory")) {
10381085
// delete is only for non-memory database files
1086+
Log.e(TAG, "delete and recreate", e);
10391087
new File(path).delete();
10401088
}
1089+
<<<<<<< HEAD
10411090

10421091
sqliteDatabase = new SQLiteDatabase(path, factory, flags);
10431092

@@ -1047,14 +1096,22 @@ public static SQLiteDatabase openDatabase(String path, char[] password, CursorFa
10471096

10481097
synchronized (sActiveDatabases) {
10491098
sActiveDatabases.put(sqliteDatabase, null);
1099+
=======
1100+
}
1101+
sqliteDatabase = openDatabase(path, password, factory, flags, databaseHook, errorHandler);
1102+
>>>>>>> d3e4c49... Updated changes with optional corrupt database handler (ref: sqlcipher/android-database-sqlcipher#169)
10501103
}
10511104
return sqliteDatabase;
10521105
}
10531106

1107+
<<<<<<< HEAD
10541108
/**
10551109
* Equivalent to openDatabase(file.getPath(), password, factory, CREATE_IF_NECESSARY, databaseHook).
10561110
*/
10571111
public static SQLiteDatabase openOrCreateDatabase(File file, String password, CursorFactory factory, SQLiteDatabaseHook databaseHook){
1112+
=======
1113+
public static SQLiteDatabase openOrCreateDatabase(File file, String password, CursorFactory factory, SQLiteDatabaseHook databaseHook) {
1114+
>>>>>>> d3e4c49... Updated changes with optional corrupt database handler (ref: sqlcipher/android-database-sqlcipher#169)
10581115
return openOrCreateDatabase(file.getPath(), password, factory, databaseHook);
10591116
}
10601117

@@ -1065,13 +1122,31 @@ public static SQLiteDatabase openOrCreateDatabase(String path, String password,
10651122
return openDatabase(path, password, factory, CREATE_IF_NECESSARY, databaseHook);
10661123
}
10671124

1125+
<<<<<<< HEAD
10681126
/**
10691127
* Equivalent to openDatabase(path, password, factory, CREATE_IF_NECESSARY, databaseHook).
10701128
*/
1129+
=======
1130+
public static SQLiteDatabase openOrCreateDatabase(File file, String password, CursorFactory factory, SQLiteDatabaseHook databaseHook,
1131+
DatabaseErrorHandler errorHandler) {
1132+
return openDatabase(file.getPath(), password.toCharArray(), factory, CREATE_IF_NECESSARY, databaseHook, errorHandler);
1133+
}
1134+
1135+
public static SQLiteDatabase openOrCreateDatabase(String path, String password, CursorFactory factory, SQLiteDatabaseHook databaseHook,
1136+
DatabaseErrorHandler errorHandler) {
1137+
return openDatabase(path, password.toCharArray(), factory, CREATE_IF_NECESSARY, databaseHook, errorHandler);
1138+
}
1139+
1140+
>>>>>>> d3e4c49... Updated changes with optional corrupt database handler (ref: sqlcipher/android-database-sqlcipher#169)
10711141
public static SQLiteDatabase openOrCreateDatabase(String path, char[] password, CursorFactory factory, SQLiteDatabaseHook databaseHook) {
10721142
return openDatabase(path, password, factory, CREATE_IF_NECESSARY, databaseHook);
10731143
}
10741144

1145+
public static SQLiteDatabase openOrCreateDatabase(String path, char[] password, CursorFactory factory, SQLiteDatabaseHook databaseHook,
1146+
DatabaseErrorHandler errorHandler) {
1147+
return openDatabase(path, password, factory, CREATE_IF_NECESSARY, databaseHook, errorHandler);
1148+
}
1149+
10751150
/**
10761151
* Equivalent to openDatabase(file.getPath(), password, factory, CREATE_IF_NECESSARY).
10771152
*/
@@ -1305,6 +1380,14 @@ public void setPageSize(long numBytes) {
13051380
* syncable table
13061381
*/
13071382
public void markTableSyncable(String table, String deletedTable) {
1383+
// XXX TBD TODO:
1384+
/* Protect against HORRIBLE HACK to support DatabaseErrorHandler API: */
1385+
/*
1386+
if (!isOpen()) {
1387+
throw new IllegalStateException("database not open");
1388+
}
1389+
*/
1390+
13081391
markTableSyncable(table, "_id", table, deletedTable);
13091392
}
13101393

@@ -1321,6 +1404,14 @@ public void markTableSyncable(String table, String deletedTable) {
13211404
*/
13221405
public void markTableSyncable(String table, String foreignKey,
13231406
String updateTable) {
1407+
// XXX TBD TODO:
1408+
/* Protect against HORRIBLE HACK to support DatabaseErrorHandler API: */
1409+
/*
1410+
if (!isOpen()) {
1411+
throw new IllegalStateException("database not open");
1412+
}
1413+
*/
1414+
13241415
markTableSyncable(table, foreignKey, updateTable, null);
13251416
}
13261417

@@ -1870,7 +1961,7 @@ public long insertWithOnConflict(String table, String nullColumnHack,
18701961
}
18711962
return insertedRowId;
18721963
} catch (SQLiteDatabaseCorruptException e) {
1873-
onCorruption();
1964+
onCorruptionFromInsert();
18741965
throw e;
18751966
} finally {
18761967
if (statement != null) {
@@ -2237,6 +2328,20 @@ private void openDatabaseInternal(char[] password, SQLiteDatabaseHook databaseHo
22372328
}
22382329
}
22392330

2331+
/**
2332+
* Private constructor for HORRIBLE HACK to support DatabaseErrorHandler API
2333+
* (SQLiteDatabase from this [private] constructor will never connect to a real database)
2334+
*
2335+
* @param path The full path to the database (will be available for DatabaseErrorHandler)
2336+
*/
2337+
private SQLiteDatabase(String path) {
2338+
mPath = path;
2339+
/* These other members _should_ project certain public functions from this HORRIBLE HACK. */
2340+
mFlags = OPEN_READONLY; /* treat corrupt database as "read-only" */
2341+
mSlowQueryThreshold = -1;
2342+
/* NOTE: mNativeHandle is expected to be and remain 0 to indicate this handle is (never) open. */
2343+
}
2344+
22402345
private String getTime() {
22412346
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS ").format(System.currentTimeMillis());
22422347
}
@@ -2257,6 +2362,7 @@ public boolean isOpen() {
22572362
}
22582363

22592364
public boolean needUpgrade(int newVersion) {
2365+
/* NOTE: getVersion() will throw if database is not open. */
22602366
return newVersion > getVersion();
22612367
}
22622368

@@ -2350,6 +2456,8 @@ private String getPathForLogs() {
23502456
return mPathForLogs;
23512457
}
23522458

2459+
// XXX TODO check HH??
2460+
23532461
/**
23542462
* Sets the locale for this database. Does nothing if this database has
23552463
* the NO_LOCALIZED_COLLATORS flag set or was opened read only.

0 commit comments

Comments
 (0)