From 487b0489eb1887060c340959429da8044614636c Mon Sep 17 00:00:00 2001 From: JingMatrix Date: Sat, 13 Sep 2025 16:48:08 +0200 Subject: [PATCH] Correct database migration and initialization logic 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. --- .../lsposed/lspd/service/ConfigManager.java | 145 +++++++++--------- 1 file changed, 72 insertions(+), 73 deletions(-) diff --git a/daemon/src/main/java/org/lsposed/lspd/service/ConfigManager.java b/daemon/src/main/java/org/lsposed/lspd/service/ConfigManager.java index e058e68a3..4cdc43903 100644 --- a/daemon/src/main/java/org/lsposed/lspd/service/ConfigManager.java +++ b/daemon/src/main/java/org/lsposed/lspd/service/ConfigManager.java @@ -137,16 +137,15 @@ public int hashCode() { } } - private final SQLiteStatement createModulesTable = db.compileStatement("CREATE TABLE IF NOT EXISTS modules (" + + private static final String CREATE_MODULES_TABLE = "CREATE TABLE IF NOT EXISTS modules (" + "mid integer PRIMARY KEY AUTOINCREMENT," + "module_pkg_name text NOT NULL UNIQUE," + "apk_path text NOT NULL, " + "enabled BOOLEAN DEFAULT 0 " + - "CHECK (enabled IN (0, 1))," + - "auto_include BOOLEAN DEFAULT 0 " + - "CHECK (auto_include IN (0, 1))" + - ");"); - private final SQLiteStatement createScopeTable = db.compileStatement("CREATE TABLE IF NOT EXISTS scope (" + + "CHECK (enabled IN (0, 1))" + + ");"; + + private static final String CREATE_SCOPE_TABLE = "CREATE TABLE IF NOT EXISTS scope (" + "mid integer," + "app_pkg_name text NOT NULL," + "user_id integer NOT NULL," + @@ -155,8 +154,9 @@ public int hashCode() { " FOREIGN KEY (mid)" + " REFERENCES modules (mid)" + " ON DELETE CASCADE" + - ");"); - private final SQLiteStatement createConfigTable = db.compileStatement("CREATE TABLE IF NOT EXISTS configs (" + + ");"; + + private static final String CREATE_CONFIG_TABLE = "CREATE TABLE IF NOT EXISTS configs (" + "module_pkg_name text NOT NULL," + "user_id integer NOT NULL," + "`group` text NOT NULL," + @@ -167,7 +167,7 @@ public int hashCode() { " FOREIGN KEY (module_pkg_name)" + " REFERENCES modules (module_pkg_name)" + " ON DELETE CASCADE" + - ");"); + ");"; private final Map> cachedScope = new ConcurrentHashMap<>(); @@ -376,74 +376,73 @@ private void executeInTransaction(Runnable execution) { } private void initDB() { + db.setForeignKeyConstraintsEnabled(true); + int oldVersion = db.getVersion(); + if (oldVersion >= 4) { + // Database is already up to date. + return; + } + + Log.i(TAG, "Initializing/Upgrading database from version " + oldVersion + " to 4"); + db.beginTransaction(); try { - db.setForeignKeyConstraintsEnabled(true); - switch (db.getVersion()) { - case 0: - executeInTransaction(() -> { - createModulesTable.execute(); - createScopeTable.execute(); - createConfigTable.execute(); - var values = new ContentValues(); - values.put("module_pkg_name", "lspd"); - values.put("apk_path", ConfigFileManager.managerApkPath.toString()); - // dummy module for config - db.insertWithOnConflict("modules", null, values, SQLiteDatabase.CONFLICT_IGNORE); - db.setVersion(1); - }); - case 1: - executeInTransaction(() -> { - db.compileStatement("DROP INDEX IF EXISTS configs_idx;").execute(); - db.compileStatement("DROP TABLE IF EXISTS config;").execute(); - db.compileStatement("ALTER TABLE scope RENAME TO old_scope;").execute(); - db.compileStatement("ALTER TABLE configs RENAME TO old_configs;").execute(); - createConfigTable.execute(); - createScopeTable.execute(); - db.compileStatement("CREATE INDEX IF NOT EXISTS configs_idx ON configs (module_pkg_name, user_id);").execute(); - executeInTransaction(() -> { - try { - db.compileStatement("INSERT INTO scope SELECT * FROM old_scope;").execute(); - } catch (Throwable e) { - Log.w(TAG, "migrate scope", e); - } - }); - executeInTransaction(() -> { - try { - executeInTransaction(() -> db.compileStatement("INSERT INTO configs SELECT * FROM old_configs;").execute()); - } catch (Throwable e) { - Log.w(TAG, "migrate config", e); - } - }); - db.compileStatement("DROP TABLE old_scope;").execute(); - db.compileStatement("DROP TABLE old_configs;").execute(); - db.setVersion(2); - }); - case 2: - executeInTransaction(() -> { - db.compileStatement("UPDATE scope SET app_pkg_name = 'system' WHERE app_pkg_name = 'android';").execute(); - db.setVersion(3); - }); - case 3: - try { - executeInTransaction(() -> { - db.compileStatement("ALTER TABLE modules ADD COLUMN auto_include BOOLEAN DEFAULT 0 CHECK (auto_include IN (0, 1));").execute(); - db.setVersion(4); - }); - } catch (SQLiteException ex) { - // Fix wrong init code for new column auto_include - if (ex.getMessage().startsWith("duplicate column name: auto_include")) { - db.setVersion(4); - } else { - throw ex; - } - } - default: - break; + if (oldVersion == 0) { + db.execSQL(CREATE_MODULES_TABLE); + db.execSQL(CREATE_SCOPE_TABLE); + db.execSQL(CREATE_CONFIG_TABLE); + + var values = new ContentValues(); + values.put("module_pkg_name", "lspd"); + values.put("apk_path", ConfigFileManager.managerApkPath.toString()); + db.insertWithOnConflict("modules", null, values, SQLiteDatabase.CONFLICT_IGNORE); + oldVersion = 1; } + if (oldVersion < 2) { + // Upgrade from 1 to 2: Recreate tables to enforce constraints and clean up. + db.compileStatement("DROP INDEX IF EXISTS configs_idx;").execute(); + db.compileStatement("DROP TABLE IF EXISTS config;").execute(); + db.compileStatement("ALTER TABLE scope RENAME TO old_scope;").execute(); + db.compileStatement("ALTER TABLE configs RENAME TO old_configs;").execute(); + + db.execSQL(CREATE_SCOPE_TABLE); + db.execSQL(CREATE_CONFIG_TABLE); + + try { + db.compileStatement("INSERT INTO scope SELECT * FROM old_scope;").execute(); + } catch (Throwable e) { + Log.w(TAG, "Failed to migrate scope data", e); + } + try { + db.compileStatement("INSERT INTO configs SELECT * FROM old_configs;").execute(); + } catch (Throwable e) { + Log.w(TAG, "Failed to migrate config data", e); + } + + db.compileStatement("DROP TABLE old_scope;").execute(); + db.compileStatement("DROP TABLE old_configs;").execute(); + db.compileStatement("CREATE INDEX IF NOT EXISTS configs_idx ON configs (module_pkg_name, user_id);").execute(); + } + if (oldVersion < 3) { + // Upgrade from 2 to 3: Rename 'android' scope to 'system'. + db.compileStatement("UPDATE scope SET app_pkg_name = 'system' WHERE app_pkg_name = 'android';").execute(); + } + if (oldVersion < 4) { + // Upgrade from 3 to 4: Add the 'auto_include' column to the modules table. + try { + db.compileStatement("ALTER TABLE modules ADD COLUMN auto_include BOOLEAN DEFAULT 0 CHECK (auto_include IN (0, 1));").execute(); + } catch (SQLiteException ex) { + // This might happen if the column already exists from a previous buggy run. + Log.w(TAG, "Could not add auto_include column, it may already exist.", ex); + } + } + db.setVersion(4); + db.setTransactionSuccessful(); + Log.i(TAG, "Database upgrade to version 4 successful."); } catch (Throwable e) { - Log.e(TAG, "init db", e); + Log.e(TAG, "Failed to initialize or upgrade database, transaction rolled back.", e); + } finally { + db.endTransaction(); } - } private List getAssociatedProcesses(Application app) throws RemoteException {