Skip to content

Commit 2666bda

Browse files
authored
Correct database migration and initialization logic (#236)
Reworks the `initDB` function to resolve critical failures. - Prevents a "no such table: configs" crash on fresh installs by deferring the compilation of CREATE TABLE statements until their dependencies are met. - Fixes a "duplicate column" error by correcting the initial schema and letting migration logic add new columns. - Replaces the fragile fall-through switch and nested transactions with a robust, sequential upgrade process inside a single atomic transaction.
1 parent 6703b45 commit 2666bda

File tree

1 file changed

+72
-73
lines changed

1 file changed

+72
-73
lines changed

daemon/src/main/java/org/lsposed/lspd/service/ConfigManager.java

Lines changed: 72 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -137,16 +137,15 @@ public int hashCode() {
137137
}
138138
}
139139

140-
private final SQLiteStatement createModulesTable = db.compileStatement("CREATE TABLE IF NOT EXISTS modules (" +
140+
private static final String CREATE_MODULES_TABLE = "CREATE TABLE IF NOT EXISTS modules (" +
141141
"mid integer PRIMARY KEY AUTOINCREMENT," +
142142
"module_pkg_name text NOT NULL UNIQUE," +
143143
"apk_path text NOT NULL, " +
144144
"enabled BOOLEAN DEFAULT 0 " +
145-
"CHECK (enabled IN (0, 1))," +
146-
"auto_include BOOLEAN DEFAULT 0 " +
147-
"CHECK (auto_include IN (0, 1))" +
148-
");");
149-
private final SQLiteStatement createScopeTable = db.compileStatement("CREATE TABLE IF NOT EXISTS scope (" +
145+
"CHECK (enabled IN (0, 1))" +
146+
");";
147+
148+
private static final String CREATE_SCOPE_TABLE = "CREATE TABLE IF NOT EXISTS scope (" +
150149
"mid integer," +
151150
"app_pkg_name text NOT NULL," +
152151
"user_id integer NOT NULL," +
@@ -155,8 +154,9 @@ public int hashCode() {
155154
" FOREIGN KEY (mid)" +
156155
" REFERENCES modules (mid)" +
157156
" ON DELETE CASCADE" +
158-
");");
159-
private final SQLiteStatement createConfigTable = db.compileStatement("CREATE TABLE IF NOT EXISTS configs (" +
157+
");";
158+
159+
private static final String CREATE_CONFIG_TABLE = "CREATE TABLE IF NOT EXISTS configs (" +
160160
"module_pkg_name text NOT NULL," +
161161
"user_id integer NOT NULL," +
162162
"`group` text NOT NULL," +
@@ -167,7 +167,7 @@ public int hashCode() {
167167
" FOREIGN KEY (module_pkg_name)" +
168168
" REFERENCES modules (module_pkg_name)" +
169169
" ON DELETE CASCADE" +
170-
");");
170+
");";
171171

172172
private final Map<ProcessScope, List<Module>> cachedScope = new ConcurrentHashMap<>();
173173

@@ -376,74 +376,73 @@ private void executeInTransaction(Runnable execution) {
376376
}
377377

378378
private void initDB() {
379+
db.setForeignKeyConstraintsEnabled(true);
380+
int oldVersion = db.getVersion();
381+
if (oldVersion >= 4) {
382+
// Database is already up to date.
383+
return;
384+
}
385+
386+
Log.i(TAG, "Initializing/Upgrading database from version " + oldVersion + " to 4");
387+
db.beginTransaction();
379388
try {
380-
db.setForeignKeyConstraintsEnabled(true);
381-
switch (db.getVersion()) {
382-
case 0:
383-
executeInTransaction(() -> {
384-
createModulesTable.execute();
385-
createScopeTable.execute();
386-
createConfigTable.execute();
387-
var values = new ContentValues();
388-
values.put("module_pkg_name", "lspd");
389-
values.put("apk_path", ConfigFileManager.managerApkPath.toString());
390-
// dummy module for config
391-
db.insertWithOnConflict("modules", null, values, SQLiteDatabase.CONFLICT_IGNORE);
392-
db.setVersion(1);
393-
});
394-
case 1:
395-
executeInTransaction(() -> {
396-
db.compileStatement("DROP INDEX IF EXISTS configs_idx;").execute();
397-
db.compileStatement("DROP TABLE IF EXISTS config;").execute();
398-
db.compileStatement("ALTER TABLE scope RENAME TO old_scope;").execute();
399-
db.compileStatement("ALTER TABLE configs RENAME TO old_configs;").execute();
400-
createConfigTable.execute();
401-
createScopeTable.execute();
402-
db.compileStatement("CREATE INDEX IF NOT EXISTS configs_idx ON configs (module_pkg_name, user_id);").execute();
403-
executeInTransaction(() -> {
404-
try {
405-
db.compileStatement("INSERT INTO scope SELECT * FROM old_scope;").execute();
406-
} catch (Throwable e) {
407-
Log.w(TAG, "migrate scope", e);
408-
}
409-
});
410-
executeInTransaction(() -> {
411-
try {
412-
executeInTransaction(() -> db.compileStatement("INSERT INTO configs SELECT * FROM old_configs;").execute());
413-
} catch (Throwable e) {
414-
Log.w(TAG, "migrate config", e);
415-
}
416-
});
417-
db.compileStatement("DROP TABLE old_scope;").execute();
418-
db.compileStatement("DROP TABLE old_configs;").execute();
419-
db.setVersion(2);
420-
});
421-
case 2:
422-
executeInTransaction(() -> {
423-
db.compileStatement("UPDATE scope SET app_pkg_name = 'system' WHERE app_pkg_name = 'android';").execute();
424-
db.setVersion(3);
425-
});
426-
case 3:
427-
try {
428-
executeInTransaction(() -> {
429-
db.compileStatement("ALTER TABLE modules ADD COLUMN auto_include BOOLEAN DEFAULT 0 CHECK (auto_include IN (0, 1));").execute();
430-
db.setVersion(4);
431-
});
432-
} catch (SQLiteException ex) {
433-
// Fix wrong init code for new column auto_include
434-
if (ex.getMessage().startsWith("duplicate column name: auto_include")) {
435-
db.setVersion(4);
436-
} else {
437-
throw ex;
438-
}
439-
}
440-
default:
441-
break;
389+
if (oldVersion == 0) {
390+
db.execSQL(CREATE_MODULES_TABLE);
391+
db.execSQL(CREATE_SCOPE_TABLE);
392+
db.execSQL(CREATE_CONFIG_TABLE);
393+
394+
var values = new ContentValues();
395+
values.put("module_pkg_name", "lspd");
396+
values.put("apk_path", ConfigFileManager.managerApkPath.toString());
397+
db.insertWithOnConflict("modules", null, values, SQLiteDatabase.CONFLICT_IGNORE);
398+
oldVersion = 1;
442399
}
400+
if (oldVersion < 2) {
401+
// Upgrade from 1 to 2: Recreate tables to enforce constraints and clean up.
402+
db.compileStatement("DROP INDEX IF EXISTS configs_idx;").execute();
403+
db.compileStatement("DROP TABLE IF EXISTS config;").execute();
404+
db.compileStatement("ALTER TABLE scope RENAME TO old_scope;").execute();
405+
db.compileStatement("ALTER TABLE configs RENAME TO old_configs;").execute();
406+
407+
db.execSQL(CREATE_SCOPE_TABLE);
408+
db.execSQL(CREATE_CONFIG_TABLE);
409+
410+
try {
411+
db.compileStatement("INSERT INTO scope SELECT * FROM old_scope;").execute();
412+
} catch (Throwable e) {
413+
Log.w(TAG, "Failed to migrate scope data", e);
414+
}
415+
try {
416+
db.compileStatement("INSERT INTO configs SELECT * FROM old_configs;").execute();
417+
} catch (Throwable e) {
418+
Log.w(TAG, "Failed to migrate config data", e);
419+
}
420+
421+
db.compileStatement("DROP TABLE old_scope;").execute();
422+
db.compileStatement("DROP TABLE old_configs;").execute();
423+
db.compileStatement("CREATE INDEX IF NOT EXISTS configs_idx ON configs (module_pkg_name, user_id);").execute();
424+
}
425+
if (oldVersion < 3) {
426+
// Upgrade from 2 to 3: Rename 'android' scope to 'system'.
427+
db.compileStatement("UPDATE scope SET app_pkg_name = 'system' WHERE app_pkg_name = 'android';").execute();
428+
}
429+
if (oldVersion < 4) {
430+
// Upgrade from 3 to 4: Add the 'auto_include' column to the modules table.
431+
try {
432+
db.compileStatement("ALTER TABLE modules ADD COLUMN auto_include BOOLEAN DEFAULT 0 CHECK (auto_include IN (0, 1));").execute();
433+
} catch (SQLiteException ex) {
434+
// This might happen if the column already exists from a previous buggy run.
435+
Log.w(TAG, "Could not add auto_include column, it may already exist.", ex);
436+
}
437+
}
438+
db.setVersion(4);
439+
db.setTransactionSuccessful();
440+
Log.i(TAG, "Database upgrade to version 4 successful.");
443441
} catch (Throwable e) {
444-
Log.e(TAG, "init db", e);
442+
Log.e(TAG, "Failed to initialize or upgrade database, transaction rolled back.", e);
443+
} finally {
444+
db.endTransaction();
445445
}
446-
447446
}
448447

449448
private List<ProcessScope> getAssociatedProcesses(Application app) throws RemoteException {

0 commit comments

Comments
 (0)