Handle magisk.db completely natively

Prevent database corruption due to different Android application sqlite default settings
This commit is contained in:
topjohnwu
2018-10-27 17:54:48 -04:00
parent 2a65c3dc8f
commit dbb8b8a439
10 changed files with 357 additions and 87 deletions

View File

@@ -52,6 +52,7 @@ public class Const {
public static final int SEPOL_REFACTOR = 1640;
public static final int FIX_ENV = 1650;
public static final int DBVER_SIX = 17000;
public static final int CMDLINE_DB = 17305;
}
public static class ID {

View File

@@ -252,7 +252,7 @@ public class SuRequestActivity extends BaseActivity {
policy.policy = action;
if (time >= 0) {
policy.until = (time == 0) ? 0 : (System.currentTimeMillis() / 1000 + time * 60);
mm.mDB.addPolicy(policy);
mm.mDB.updatePolicy(policy);
}
handleAction();
}

View File

@@ -1,60 +1,66 @@
package com.topjohnwu.magisk.database;
import com.topjohnwu.magisk.Const;
import com.topjohnwu.magisk.Data;
import com.topjohnwu.magisk.container.Policy;
import com.topjohnwu.magisk.container.SuLogEntry;
import com.topjohnwu.magisk.utils.Utils;
import com.topjohnwu.superuser.Shell;
import java.io.File;
import java.util.Collections;
import java.util.List;
import androidx.annotation.NonNull;
public abstract class MagiskDB {
public class MagiskDB {
public static final int DATABASE_VER = 6;
public static final int OLD_DATABASE_VER = 5;
public static final String POLICY_TABLE = "policies";
public static final String LOG_TABLE = "logs";
public static final String SETTINGS_TABLE = "settings";
public static final String STRINGS_TABLE = "strings";
public static final File LEGACY_MANAGER_DB =
static final String POLICY_TABLE = "policies";
static final String LOG_TABLE = "logs";
static final String SETTINGS_TABLE = "settings";
static final String STRINGS_TABLE = "strings";
static final File LEGACY_MANAGER_DB =
new File(Utils.fmt("/sbin/.core/db-%d/magisk.db", Const.USER_ID));
@NonNull
public static MagiskDB getInstance() {
return MagiskDBLegacy.newInstance();
if (LEGACY_MANAGER_DB.canWrite()) {
return MagiskDBLegacy.newInstance();
} else if (Shell.rootAccess()) {
return Data.magiskVersionCode >= Const.MAGISK_VER.CMDLINE_DB ?
new MagiskDBCmdline() : MagiskDBLegacy.newInstance();
} else {
return new MagiskDB();
}
}
public abstract void clearOutdated();
public void clearOutdated() {}
public void deletePolicy(Policy policy) {
deletePolicy(policy.uid);
}
public abstract void deletePolicy(String pkg);
public void deletePolicy(String pkg) {}
public abstract void deletePolicy(int uid);
public void deletePolicy(int uid) {}
public abstract Policy getPolicy(int uid);
public Policy getPolicy(int uid) { return null; }
public abstract void addPolicy(Policy policy);
public void updatePolicy(Policy policy) {}
public abstract void updatePolicy(Policy policy);
public List<Policy> getPolicyList() { return Collections.emptyList(); }
public abstract List<Policy> getPolicyList();
public List<List<SuLogEntry>> getLogs() { return Collections.emptyList(); }
public abstract List<List<SuLogEntry>> getLogs();
public void addLog(SuLogEntry log) {}
public abstract void addLog(SuLogEntry log);
public void clearLogs() {}
public abstract void clearLogs();
public void setSettings(String key, int value) {}
public abstract void setSettings(String key, int value);
public int getSettings(String key, int defaultValue) { return defaultValue; }
public abstract int getSettings(String key, int defaultValue);
public void setStrings(String key, String value) {}
public abstract void setStrings(String key, String value);
public abstract String getStrings(String key, String defaultValue);
public String getStrings(String key, String defaultValue) { return defaultValue; }
}

View File

@@ -0,0 +1,185 @@
package com.topjohnwu.magisk.database;
import android.content.ContentValues;
import android.content.pm.PackageManager;
import android.text.TextUtils;
import com.topjohnwu.magisk.Const;
import com.topjohnwu.magisk.Data;
import com.topjohnwu.magisk.container.Policy;
import com.topjohnwu.magisk.container.SuLogEntry;
import com.topjohnwu.magisk.utils.LocaleManager;
import com.topjohnwu.magisk.utils.Utils;
import com.topjohnwu.superuser.Shell;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
public class MagiskDBCmdline extends MagiskDB {
private PackageManager pm;
public MagiskDBCmdline() {
pm = Data.MM().getPackageManager();
}
private List<String> rawSQL(String fmt, Object... args) {
return Shell.su("magisk --sqlite '" + Utils.fmt(fmt, args) + "'").exec().getOut();
}
private List<ContentValues> SQL(String fmt, Object... args) {
List<ContentValues> list = new ArrayList<>();
for (String raw : rawSQL(fmt, args)) {
ContentValues values = new ContentValues();
String[] cols = raw.split("\\|");
for (String col : cols) {
String[] pair = col.split("=", 2);
values.put(pair[0], pair[1]);
}
list.add(values);
}
return list;
}
private String toSQL(ContentValues values) {
StringBuilder keys = new StringBuilder(), vals = new StringBuilder();
keys.append('(');
vals.append("VALUES(");
boolean first = true;
for (Map.Entry<String, Object> entry : values.valueSet()) {
if (!first) {
keys.append(',');
vals.append(',');
} else {
first = false;
}
keys.append(entry.getKey());
vals.append('"');
vals.append(entry.getValue());
vals.append('"');
}
keys.append(')');
vals.append(')');
keys.append(vals);
return keys.toString();
}
@Override
public void clearOutdated() {
rawSQL(
"DELETE FROM %s WHERE until > 0 AND until < %d;" +
"DELETE FROM %s WHERE time < %d",
POLICY_TABLE, System.currentTimeMillis() / 1000,
LOG_TABLE, System.currentTimeMillis() - Data.suLogTimeout * 86400000
);
}
@Override
public void deletePolicy(String pkg) {
rawSQL("DELETE FROM %s WHERE package_name=\"%s\"", POLICY_TABLE, pkg);
}
@Override
public void deletePolicy(int uid) {
rawSQL("DELETE FROM %s WHERE uid=%d", POLICY_TABLE, uid);
}
@Override
public Policy getPolicy(int uid) {
List<ContentValues> res =
SQL("SELECT * FROM %s WHERE uid=%d", POLICY_TABLE, uid);
if (!res.isEmpty()) {
try {
return new Policy(res.get(0), pm);
} catch (PackageManager.NameNotFoundException e) {
deletePolicy(uid);
}
}
return null;
}
@Override
public void updatePolicy(Policy policy) {
rawSQL("REPLACE INTO %s %s", POLICY_TABLE, toSQL(policy.getContentValues()));
}
@Override
public List<Policy> getPolicyList() {
List<Policy> list = new ArrayList<>();
for (ContentValues values : SQL("SELECT * FROM %s WHERE uid/100000=%d", POLICY_TABLE, Const.USER_ID)) {
try {
list.add(new Policy(values, pm));
} catch (PackageManager.NameNotFoundException e) {
deletePolicy(values.getAsInteger("uid"));
}
}
return list;
}
@Override
public List<List<SuLogEntry>> getLogs() {
List<List<SuLogEntry>> ret = new ArrayList<>();
List<SuLogEntry> list = null;
String dateString = null, newString;
for (ContentValues values : SQL("SELECT * FROM %s ORDER BY time DESC", LOG_TABLE)) {
Date date = new Date(values.getAsLong("time"));
newString = DateFormat.getDateInstance(DateFormat.MEDIUM, LocaleManager.locale).format(date);
if (!TextUtils.equals(dateString, newString)) {
dateString = newString;
list = new ArrayList<>();
ret.add(list);
}
list.add(new SuLogEntry(values));
}
return ret;
}
@Override
public void addLog(SuLogEntry log) {
rawSQL("INSERT INTO %s %s", LOG_TABLE, toSQL(log.getContentValues()));
}
@Override
public void clearLogs() {
rawSQL("DELETE FROM %s", LOG_TABLE);
}
@Override
public void setSettings(String key, int value) {
ContentValues data = new ContentValues();
data.put("key", key);
data.put("value", value);
rawSQL("REPLACE INTO %s %s", SETTINGS_TABLE, toSQL(data));
}
@Override
public int getSettings(String key, int defaultValue) {
List<ContentValues> res = SQL("SELECT value FROM %s WHERE key=\"%s\"", SETTINGS_TABLE, key);
if (res.isEmpty())
return defaultValue;
return res.get(0).getAsInteger("value");
}
@Override
public void setStrings(String key, String value) {
if (value == null) {
rawSQL("DELETE FROM %s WHERE key=\"%s\"", STRINGS_TABLE);
return;
}
ContentValues data = new ContentValues();
data.put("key", key);
data.put("value", value);
rawSQL("REPLACE INTO %s %s", STRINGS_TABLE, toSQL(data));
}
@Override
public String getStrings(String key, String defaultValue) {
List<ContentValues> res = SQL("SELECT value FROM %s WHERE key=\"%s\"", STRINGS_TABLE, key);
if (res.isEmpty())
return defaultValue;
return res.get(0).getAsString("value");
}
}

View File

@@ -30,6 +30,9 @@ import java.util.List;
public class MagiskDBLegacy extends MagiskDB {
private static final int DATABASE_VER = 6;
private static final int OLD_DATABASE_VER = 5;
private PackageManager pm;
private SQLiteDatabase db;
@@ -193,14 +196,9 @@ public class MagiskDBLegacy extends MagiskDB {
return policy;
}
@Override
public void addPolicy(Policy policy) {
db.replace(POLICY_TABLE, null, policy.getContentValues());
}
@Override
public void updatePolicy(Policy policy) {
db.update(POLICY_TABLE, policy.getContentValues(), Utils.fmt("uid=%d", policy.uid), null);
db.replace(POLICY_TABLE, null, policy.getContentValues());
}
@Override