diff --git a/native/src/Android.mk b/native/src/Android.mk index 76c66ae28..a569f58fd 100644 --- a/native/src/Android.mk +++ b/native/src/Android.mk @@ -24,6 +24,7 @@ LOCAL_SRC_FILES := \ core/package.cpp \ core/scripting.cpp \ core/selinux.cpp \ + core/sqlite.cpp \ core/module.cpp \ core/thread.cpp \ core/core-rs.cpp \ diff --git a/native/src/core/db.cpp b/native/src/core/db.cpp index 631d9b351..b07cddd0e 100644 --- a/native/src/core/db.cpp +++ b/native/src/core/db.cpp @@ -1,114 +1,21 @@ #include -#include #include #include #include #include +#include #include #define DB_VERSION 12 using namespace std; -struct sqlite3; -struct sqlite3_stmt; - static sqlite3 *mDB = nullptr; #define DBLOGV(...) //#define DBLOGV(...) LOGD("magiskdb: " __VA_ARGS__) -// SQLite APIs - -#define SQLITE_OPEN_READWRITE 0x00000002 /* Ok for sqlite3_open_v2() */ -#define SQLITE_OPEN_CREATE 0x00000004 /* Ok for sqlite3_open_v2() */ -#define SQLITE_OPEN_FULLMUTEX 0x00010000 /* Ok for sqlite3_open_v2() */ - -#define SQLITE_OK 0 /* Successful result */ -#define SQLITE_ROW 100 /* sqlite3_step() has another row ready */ -#define SQLITE_DONE 101 /* sqlite3_step() has finished executing */ - -static int (*sqlite3_open_v2)(const char *filename, sqlite3 **ppDb, int flags, const char *zVfs); -static int (*sqlite3_close)(sqlite3 *db); -static int (*sqlite3_prepare_v2)(sqlite3 *db, const char *zSql, int nByte, sqlite3_stmt **ppStmt, const char **pzTail); -static int (*sqlite3_bind_parameter_count)(sqlite3_stmt*); -static int (*sqlite3_bind_int)(sqlite3_stmt*, int, int); -static int (*sqlite3_bind_text)(sqlite3_stmt*,int,const char*,int,void(*)(void*)); -static int (*sqlite3_column_count)(sqlite3_stmt *pStmt); -static const char *(*sqlite3_column_name)(sqlite3_stmt*, int N); -static const char *(*sqlite3_column_text)(sqlite3_stmt*, int iCol); -static int (*sqlite3_step)(sqlite3_stmt*); -static int (*sqlite3_finalize)(sqlite3_stmt *pStmt); -static const char *(*sqlite3_errstr)(int); - -// Internal Android linker APIs - -static void (*android_get_LD_LIBRARY_PATH)(char *buffer, size_t buffer_size); -static void (*android_update_LD_LIBRARY_PATH)(const char *ld_library_path); - -#define DLERR(ptr) if (!(ptr)) { \ - LOGE("db: %s\n", dlerror()); \ - return false; \ -} - -#define DLOAD(handle, arg) {\ - auto f = dlsym(handle, #arg); \ - DLERR(f) \ - *(void **) &(arg) = f; \ -} - -#ifdef __LP64__ -constexpr char apex_path[] = "/apex/com.android.runtime/lib64:/apex/com.android.art/lib64:/apex/com.android.i18n/lib64:"; -#else -constexpr char apex_path[] = "/apex/com.android.runtime/lib:/apex/com.android.art/lib:/apex/com.android.i18n/lib:"; -#endif - -static bool dload_sqlite() { - static int dl_init = 0; - if (dl_init) - return dl_init > 0; - dl_init = -1; - - auto sqlite = dlopen("libsqlite.so", RTLD_LAZY); - if (!sqlite) { - // Should only happen on Android 10+ - auto dl = dlopen("libdl_android.so", RTLD_LAZY); - DLERR(dl); - - DLOAD(dl, android_get_LD_LIBRARY_PATH); - DLOAD(dl, android_update_LD_LIBRARY_PATH); - - // Inject APEX into LD_LIBRARY_PATH - char ld_path[4096]; - memcpy(ld_path, apex_path, sizeof(apex_path)); - constexpr int len = sizeof(apex_path) - 1; - android_get_LD_LIBRARY_PATH(ld_path + len, sizeof(ld_path) - len); - android_update_LD_LIBRARY_PATH(ld_path); - sqlite = dlopen("libsqlite.so", RTLD_LAZY); - - // Revert LD_LIBRARY_PATH just in case - android_update_LD_LIBRARY_PATH(ld_path + len); - } - DLERR(sqlite); - - DLOAD(sqlite, sqlite3_open_v2); - DLOAD(sqlite, sqlite3_close); - DLOAD(sqlite, sqlite3_prepare_v2); - DLOAD(sqlite, sqlite3_bind_parameter_count); - DLOAD(sqlite, sqlite3_bind_int); - DLOAD(sqlite, sqlite3_bind_text); - DLOAD(sqlite, sqlite3_step); - DLOAD(sqlite, sqlite3_column_count); - DLOAD(sqlite, sqlite3_column_name); - DLOAD(sqlite, sqlite3_column_text); - DLOAD(sqlite, sqlite3_finalize); - DLOAD(sqlite, sqlite3_errstr); - - dl_init = 1; - return true; -} - int db_strings::get_idx(string_view key) const { int idx = 0; for (const char *k : DB_STRING_KEYS) { @@ -139,80 +46,27 @@ int db_settings::get_idx(string_view key) const { return idx; } -static void ver_cb(void *ver, auto, rust::Slice data) { - *((int *) ver) = parse_int(data[0].c_str()); -} - -db_result::db_result(int code) : err(code == SQLITE_OK ? "" : (sqlite3_errstr(code) ?: "")) {} - -bool db_result::check_err() { - if (!err.empty()) { - LOGE("sqlite3: %s\n", err.data()); +struct db_result { + db_result() = default; + db_result(const char *s) : err(s) {} + db_result(int code) : err(code == SQLITE_OK ? "" : (sqlite3_errstr(code) ?: "")) {} + operator bool() { + if (!err.empty()) { + LOGE("sqlite3: %s\n", err.data()); + return false; + } return true; } - return false; -} - -using StringVec = rust::Vec; -using StringSlice = rust::Slice; -using StrSlice = rust::Slice; -using sqlite_row_callback = void(*)(void*, StringSlice, StringSlice); - -#define fn_run_ret(fn, ...) if (int rc = fn(__VA_ARGS__); rc != SQLITE_OK) return rc - -static int sql_exec(sqlite3 *db, rust::Str zSql, StrSlice args, sqlite_row_callback callback, void *v) { - const char *sql = zSql.begin(); - auto arg_it = args.begin(); - unique_ptr stmt(nullptr, sqlite3_finalize); - - while (sql != zSql.end()) { - // Step 1: prepare statement - { - sqlite3_stmt *st = nullptr; - fn_run_ret(sqlite3_prepare_v2, db, sql, zSql.end() - sql, &st, &sql); - if (st == nullptr) continue; - stmt.reset(st); - } - - // Step 2: bind arguments - if (int count = sqlite3_bind_parameter_count(stmt.get())) { - for (int i = 1; i <= count && arg_it != args.end(); ++i, ++arg_it) { - fn_run_ret(sqlite3_bind_text, stmt.get(), i, arg_it->data(), arg_it->size(), nullptr); - } - } - - // Step 3: execute - bool first = true; - StringVec columns; - for (;;) { - int rc = sqlite3_step(stmt.get()); - if (rc == SQLITE_DONE) break; - if (rc != SQLITE_ROW) return rc; - if (callback == nullptr) continue; - if (first) { - int count = sqlite3_column_count(stmt.get()); - for (int i = 0; i < count; ++i) { - columns.emplace_back(sqlite3_column_name(stmt.get(), i)); - } - first = false; - } - StringVec data; - for (int i = 0; i < columns.size(); ++i) { - data.emplace_back(sqlite3_column_text(stmt.get(), i)); - } - callback(v, StringSlice(columns), StringSlice(data)); - } - } - - return SQLITE_OK; -} +private: + string err; +}; static int sql_exec(sqlite3 *db, const char *sql, sqlite_row_callback callback = nullptr, void *v = nullptr) { return sql_exec(db, sql, {}, callback, v); } static db_result open_and_init_db() { - if (!dload_sqlite()) + if (!load_sqlite()) return "Cannot load libsqlite.so"; unique_ptr db(nullptr, sqlite3_close); @@ -225,6 +79,9 @@ static db_result open_and_init_db() { int ver = 0; bool upgrade = false; + auto ver_cb = [](void *ver, auto, StringSlice data) { + *((int *) ver) = parse_int(data[0].c_str()); + }; fn_run_ret(sql_exec, db.get(), "PRAGMA user_version", ver_cb, &ver); if (ver > DB_VERSION) { // Don't support downgrading database @@ -339,43 +196,41 @@ static db_result open_and_init_db() { static db_result ensure_db() { if (mDB == nullptr) { - auto res = open_and_init_db(); - if (res.check_err()) { + db_result res = open_and_init_db(); + if (!res) { // Open fails, remove and reconstruct unlink(MAGISKDB); - res = open_and_init_db(); - if (!res) return res; + return open_and_init_db(); } } return {}; } -db_result db_exec(const char *sql) { - if (auto res = ensure_db(); !res) return res; - if (mDB) { - return sql_exec(mDB, sql); +bool db_exec(const char *sql) { + if (ensure_db() && mDB) { + db_result res = sql_exec(mDB, sql); + return res; } - return {}; + return false; } -static void row_to_db_row(void *cb, rust::Slice columns, rust::Slice data) { - auto &func = *static_cast(cb); - db_row row; - for (int i = 0; i < columns.size(); ++i) - row[columns[i].c_str()] = data[i].c_str(); - func(row); -} - -db_result db_exec(const char *sql, const db_row_cb &fn) { - if (auto res = ensure_db(); !res) return res; - if (mDB) { - return sql_exec(mDB, sql, row_to_db_row, (void *) &fn); +bool db_exec(const char *sql, const db_row_cb &fn) { + if (ensure_db() && mDB) { + auto convert = [](void *cb, StringSlice columns, StringSlice data) { + auto &func = *static_cast(cb); + db_row row; + for (int i = 0; i < columns.size(); ++i) + row[columns[i].c_str()] = data[i].c_str(); + func(row); + }; + db_result res = sql_exec(mDB, sql, convert, (void *) &fn); + return res; } - return {}; + return false; } int get_db_settings(db_settings &cfg, int key) { - db_result res; + bool res; auto settings_cb = [&](db_row &row) -> bool { cfg[row["key"]] = parse_int(row["value"]); DBLOGV("query %s=[%s]\n", row["key"].data(), row["value"].data()); @@ -388,18 +243,18 @@ int get_db_settings(db_settings &cfg, int key) { } else { res = db_exec("SELECT * FROM settings", settings_cb); } - return res.check_err() ? 1 : 0; + return res ? 0 : 1; } int set_db_settings(int key, int value) { char sql[128]; ssprintf(sql, sizeof(sql), "INSERT OR REPLACE INTO settings VALUES ('%s', %d)", DB_SETTING_KEYS[key], value); - return db_exec(sql).check_err() ? 1 : 0; + return db_exec(sql) ? 0 : 1; } int get_db_strings(db_strings &str, int key) { - db_result res; + bool res; auto string_cb = [&](db_row &row) -> bool { str[row["key"]] = row["value"]; DBLOGV("query %s=[%s]\n", row["key"].data(), row["value"].data()); @@ -412,18 +267,18 @@ int get_db_strings(db_strings &str, int key) { } else { res = db_exec("SELECT * FROM strings", string_cb); } - return res.check_err() ? 1 : 0; + return res ? 0 : 1; } void rm_db_strings(int key) { char query[128]; ssprintf(query, sizeof(query), "DELETE FROM strings WHERE key == '%s'", DB_STRING_KEYS[key]); - db_exec(query).check_err(); + db_exec(query); } void exec_sql(owned_fd client) { string sql = read_string(client); - auto res = db_exec(sql.data(), [fd = (int) client](db_row &row) -> bool { + db_exec(sql.data(), [fd = (int) client](db_row &row) -> bool { string out; bool first = true; for (auto it : row) { @@ -437,5 +292,4 @@ void exec_sql(owned_fd client) { return true; }); write_int(client, 0); - res.check_err(); } diff --git a/native/src/core/deny/utils.cpp b/native/src/core/deny/utils.cpp index 158ef00d8..e3e6d74c2 100644 --- a/native/src/core/deny/utils.cpp +++ b/native/src/core/deny/utils.cpp @@ -201,7 +201,7 @@ void scan_deny_apps() { LOGI("denylist rm: [%s]\n", it->first.data()); ssprintf(sql, sizeof(sql), "DELETE FROM denylist WHERE package_name='%s'", it->first.data()); - db_exec(sql).check_err(); + db_exec(sql); it = pkg_to_procs.erase(it); } else { update_app_id(app_id, it->first, false); @@ -222,11 +222,11 @@ static bool ensure_data() { LOGI("denylist: initializing internal data structures\n"); default_new(pkg_to_procs_); - auto res = db_exec("SELECT * FROM denylist", [](db_row &row) -> bool { + bool res = db_exec("SELECT * FROM denylist", [](db_row &row) -> bool { add_hide_set(row["package_name"].data(), row["process"].data()); return true; }); - if (res.check_err()) + if (!res) goto error; default_new(app_id_to_pkgs_); @@ -263,7 +263,7 @@ static int add_list(const char *pkg, const char *proc) { char sql[4096]; ssprintf(sql, sizeof(sql), "INSERT INTO denylist (package_name, process) VALUES('%s', '%s')", pkg, proc); - return db_exec(sql).check_err() ? DenyResponse::ERROR : DenyResponse::OK; + return db_exec(sql) ? DenyResponse::OK : DenyResponse::ERROR; } int add_list(int client) { @@ -307,7 +307,7 @@ static int rm_list(const char *pkg, const char *proc) { else ssprintf(sql, sizeof(sql), "DELETE FROM denylist WHERE package_name='%s' AND process='%s'", pkg, proc); - return db_exec(sql).check_err() ? DenyResponse::ERROR : DenyResponse::OK; + return db_exec(sql) ? DenyResponse::OK : DenyResponse::ERROR; } int rm_list(int client) { diff --git a/native/src/core/include/db.hpp b/native/src/core/include/db.hpp index 34b6320e3..71b70491c 100644 --- a/native/src/core/include/db.hpp +++ b/native/src/core/include/db.hpp @@ -126,20 +126,10 @@ using db_row = std::map; using db_row_cb = std::function; struct owned_fd; -struct db_result { - db_result() = default; - db_result(const char *s) : err(s) {} - db_result(int code); - bool check_err(); - operator bool() { return err.empty(); } -private: - std::string err; -}; - int get_db_settings(db_settings &cfg, int key = -1); int set_db_settings(int key, int value); int get_db_strings(db_strings &str, int key = -1); void rm_db_strings(int key); void exec_sql(owned_fd client); -db_result db_exec(const char *sql); -db_result db_exec(const char *sql, const db_row_cb &fn); +bool db_exec(const char *sql); +bool db_exec(const char *sql, const db_row_cb &fn); diff --git a/native/src/core/include/sqlite.hpp b/native/src/core/include/sqlite.hpp new file mode 100644 index 000000000..4acb56e60 --- /dev/null +++ b/native/src/core/include/sqlite.hpp @@ -0,0 +1,37 @@ +#pragma once + +#include + +#define SQLITE_OPEN_READWRITE 0x00000002 /* Ok for sqlite3_open_v2() */ +#define SQLITE_OPEN_CREATE 0x00000004 /* Ok for sqlite3_open_v2() */ +#define SQLITE_OPEN_FULLMUTEX 0x00010000 /* Ok for sqlite3_open_v2() */ + +#define SQLITE_OK 0 /* Successful result */ +#define SQLITE_ROW 100 /* sqlite3_step() has another row ready */ +#define SQLITE_DONE 101 /* sqlite3_step() has finished executing */ + +struct sqlite3; +struct sqlite3_stmt; + +extern int (*sqlite3_open_v2)(const char *filename, sqlite3 **ppDb, int flags, const char *zVfs); +extern int (*sqlite3_close)(sqlite3 *db); +extern int (*sqlite3_prepare_v2)(sqlite3 *db, const char *zSql, int nByte, sqlite3_stmt **ppStmt, const char **pzTail); +extern int (*sqlite3_bind_parameter_count)(sqlite3_stmt*); +extern int (*sqlite3_bind_int)(sqlite3_stmt*, int, int); +extern int (*sqlite3_bind_text)(sqlite3_stmt*,int,const char*,int,void(*)(void*)); +extern int (*sqlite3_column_count)(sqlite3_stmt *pStmt); +extern const char *(*sqlite3_column_name)(sqlite3_stmt*, int N); +extern const char *(*sqlite3_column_text)(sqlite3_stmt*, int iCol); +extern int (*sqlite3_step)(sqlite3_stmt*); +extern int (*sqlite3_finalize)(sqlite3_stmt *pStmt); +extern const char *(*sqlite3_errstr)(int); + +using StringVec = rust::Vec; +using StringSlice = rust::Slice; +using StrSlice = rust::Slice; +using sqlite_row_callback = void(*)(void*, StringSlice, StringSlice); + +#define fn_run_ret(fn, ...) if (int rc = fn(__VA_ARGS__); rc != SQLITE_OK) return rc + +bool load_sqlite(); +int sql_exec(sqlite3 *db, rust::Str zSql, StrSlice args, sqlite_row_callback callback, void *v); diff --git a/native/src/core/sqlite.cpp b/native/src/core/sqlite.cpp new file mode 100644 index 000000000..b81fabe62 --- /dev/null +++ b/native/src/core/sqlite.cpp @@ -0,0 +1,135 @@ +#include + +#include +#include + +using namespace std; + +// SQLite APIs + +int (*sqlite3_open_v2)(const char *filename, sqlite3 **ppDb, int flags, const char *zVfs); +int (*sqlite3_close)(sqlite3 *db); +int (*sqlite3_prepare_v2)(sqlite3 *db, const char *zSql, int nByte, sqlite3_stmt **ppStmt, const char **pzTail); +int (*sqlite3_bind_parameter_count)(sqlite3_stmt*); +int (*sqlite3_bind_int)(sqlite3_stmt*, int, int); +int (*sqlite3_bind_text)(sqlite3_stmt*,int,const char*,int,void(*)(void*)); +int (*sqlite3_column_count)(sqlite3_stmt *pStmt); +const char *(*sqlite3_column_name)(sqlite3_stmt*, int N); +const char *(*sqlite3_column_text)(sqlite3_stmt*, int iCol); +int (*sqlite3_step)(sqlite3_stmt*); +int (*sqlite3_finalize)(sqlite3_stmt *pStmt); +const char *(*sqlite3_errstr)(int); + +// Internal Android linker APIs + +static void (*android_get_LD_LIBRARY_PATH)(char *buffer, size_t buffer_size); +static void (*android_update_LD_LIBRARY_PATH)(const char *ld_library_path); + +#define DLERR(ptr) if (!(ptr)) { \ + LOGE("db: %s\n", dlerror()); \ + return false; \ +} + +#define DLOAD(handle, arg) {\ + auto f = dlsym(handle, #arg); \ + DLERR(f) \ + *(void **) &(arg) = f; \ +} + +#ifdef __LP64__ +constexpr char apex_path[] = "/apex/com.android.runtime/lib64:/apex/com.android.art/lib64:/apex/com.android.i18n/lib64:"; +#else +constexpr char apex_path[] = "/apex/com.android.runtime/lib:/apex/com.android.art/lib:/apex/com.android.i18n/lib:"; +#endif + +bool load_sqlite() { + static int dl_init = 0; + if (dl_init) + return dl_init > 0; + dl_init = -1; + + auto sqlite = dlopen("libsqlite.so", RTLD_LAZY); + if (!sqlite) { + // Should only happen on Android 10+ + auto dl = dlopen("libdl_android.so", RTLD_LAZY); + DLERR(dl); + + DLOAD(dl, android_get_LD_LIBRARY_PATH); + DLOAD(dl, android_update_LD_LIBRARY_PATH); + + // Inject APEX into LD_LIBRARY_PATH + char ld_path[4096]; + memcpy(ld_path, apex_path, sizeof(apex_path)); + constexpr int len = sizeof(apex_path) - 1; + android_get_LD_LIBRARY_PATH(ld_path + len, sizeof(ld_path) - len); + android_update_LD_LIBRARY_PATH(ld_path); + sqlite = dlopen("libsqlite.so", RTLD_LAZY); + + // Revert LD_LIBRARY_PATH just in case + android_update_LD_LIBRARY_PATH(ld_path + len); + } + DLERR(sqlite); + + DLOAD(sqlite, sqlite3_open_v2); + DLOAD(sqlite, sqlite3_close); + DLOAD(sqlite, sqlite3_prepare_v2); + DLOAD(sqlite, sqlite3_bind_parameter_count); + DLOAD(sqlite, sqlite3_bind_int); + DLOAD(sqlite, sqlite3_bind_text); + DLOAD(sqlite, sqlite3_step); + DLOAD(sqlite, sqlite3_column_count); + DLOAD(sqlite, sqlite3_column_name); + DLOAD(sqlite, sqlite3_column_text); + DLOAD(sqlite, sqlite3_finalize); + DLOAD(sqlite, sqlite3_errstr); + + dl_init = 1; + return true; +} + +int sql_exec(sqlite3 *db, rust::Str zSql, StrSlice args, sqlite_row_callback callback, void *v) { + const char *sql = zSql.begin(); + auto arg_it = args.begin(); + unique_ptr stmt(nullptr, sqlite3_finalize); + + while (sql != zSql.end()) { + // Step 1: prepare statement + { + sqlite3_stmt *st = nullptr; + fn_run_ret(sqlite3_prepare_v2, db, sql, zSql.end() - sql, &st, &sql); + if (st == nullptr) continue; + stmt.reset(st); + } + + // Step 2: bind arguments + if (int count = sqlite3_bind_parameter_count(stmt.get())) { + for (int i = 1; i <= count && arg_it != args.end(); ++i, ++arg_it) { + fn_run_ret(sqlite3_bind_text, stmt.get(), i, arg_it->data(), arg_it->size(), nullptr); + } + } + + // Step 3: execute + bool first = true; + StringVec columns; + for (;;) { + int rc = sqlite3_step(stmt.get()); + if (rc == SQLITE_DONE) break; + if (rc != SQLITE_ROW) return rc; + if (callback == nullptr) continue; + if (first) { + int count = sqlite3_column_count(stmt.get()); + for (int i = 0; i < count; ++i) { + columns.emplace_back(sqlite3_column_name(stmt.get(), i)); + } + first = false; + } + StringVec data; + for (int i = 0; i < columns.size(); ++i) { + data.emplace_back(sqlite3_column_text(stmt.get(), i)); + } + callback(v, StringSlice(columns), StringSlice(data)); + } + } + + return SQLITE_OK; +} diff --git a/native/src/core/su/su_daemon.cpp b/native/src/core/su/su_daemon.cpp index cc020c9dc..1031ca87a 100644 --- a/native/src/core/su/su_daemon.cpp +++ b/native/src/core/su/su_daemon.cpp @@ -67,7 +67,7 @@ void su_info::check_db() { ssprintf(query, sizeof(query), "SELECT policy, logging, notification FROM policies " "WHERE uid=%d AND (until=0 OR until>%li)", eval_uid, time(nullptr)); - auto res = db_exec(query, [&](db_row &row) -> bool { + bool res = db_exec(query, [&](db_row &row) -> bool { access.policy = (policy_t) parse_int(row["policy"]); access.log = parse_int(row["logging"]); access.notify = parse_int(row["notification"]); @@ -75,7 +75,7 @@ void su_info::check_db() { access.policy, access.log, access.notify); return true; }); - if (res.check_err()) + if (!res) return; } @@ -128,11 +128,11 @@ bool uid_granted_root(int uid) { ssprintf(query, sizeof(query), "SELECT policy FROM policies WHERE uid=%d AND (until=0 OR until>%li)", uid, time(nullptr)); - auto res = db_exec(query, [&](db_row &row) -> bool { + bool res = db_exec(query, [&](db_row &row) -> bool { granted = parse_int(row["policy"]) == ALLOW; return true; }); - if (res.check_err()) + if (!res) return false; return granted; @@ -142,7 +142,7 @@ void prune_su_access() { cached.reset(); vector app_no_list = get_app_no_list(); vector rm_uids; - auto res = db_exec("SELECT uid FROM policies", [&](db_row &row) -> bool { + bool res = db_exec("SELECT uid FROM policies", [&](db_row &row) -> bool { int uid = parse_int(row["uid"]); int app_id = to_app_id(uid); if (app_id >= AID_APP_START && app_id <= AID_APP_END) { @@ -154,13 +154,13 @@ void prune_su_access() { } return true; }); - if (res.check_err()) + if (!res) return; for (int uid : rm_uids) { char query[256]; ssprintf(query, sizeof(query), "DELETE FROM policies WHERE uid == %d", uid); - db_exec(query).check_err(); + db_exec(query); } }