2025-01-31 02:52:27 +08:00

347 lines
9.6 KiB
Rust

#![allow(improper_ctypes, improper_ctypes_definitions)]
use crate::daemon::{MagiskD, MAGISKD};
use crate::ffi::{
open_and_init_db, sqlite3, sqlite3_errstr, DbEntryKey, DbSettings, DbStatement, DbValues,
MntNsMode, MultiuserMode, RootAccess,
};
use crate::socket::{IpcRead, IpcWrite};
use base::{LoggedResult, ResultExt, Utf8CStr};
use std::ffi::c_void;
use std::fs::File;
use std::io::{BufReader, BufWriter};
use std::os::fd::{FromRawFd, OwnedFd, RawFd};
use std::pin::Pin;
use std::ptr;
use std::ptr::NonNull;
use thiserror::Error;
use DbArg::{Integer, Text};
fn sqlite_err_str(code: i32) -> &'static Utf8CStr {
// SAFETY: sqlite3 always returns UTF-8 strings
unsafe { Utf8CStr::from_ptr_unchecked(sqlite3_errstr(code)) }
}
#[repr(transparent)]
#[derive(Error, Debug)]
#[error("sqlite3: {}", sqlite_err_str(self.0))]
pub struct SqliteError(i32);
pub type SqliteResult<T> = Result<T, SqliteError>;
pub trait SqliteReturn {
fn sql_result(self) -> SqliteResult<()>;
}
impl SqliteReturn for i32 {
fn sql_result(self) -> SqliteResult<()> {
if self != 0 {
Err(SqliteError(self))
} else {
Ok(())
}
}
}
pub trait SqlTable {
fn on_row(&mut self, columns: &[String], values: &DbValues);
}
impl<T> SqlTable for T
where
T: FnMut(&[String], &DbValues),
{
fn on_row(&mut self, columns: &[String], values: &DbValues) {
self.call_mut((columns, values))
}
}
impl Default for RootAccess {
fn default() -> Self {
RootAccess::AppsAndAdb
}
}
impl Default for MultiuserMode {
fn default() -> Self {
MultiuserMode::OwnerOnly
}
}
impl Default for MntNsMode {
fn default() -> Self {
MntNsMode::Requester
}
}
pub fn get_default_db_settings() -> DbSettings {
DbSettings::default()
}
impl DbEntryKey {
fn to_str(self) -> &'static str {
match self {
DbEntryKey::RootAccess => "root_access",
DbEntryKey::SuMultiuserMode => "multiuser_mode",
DbEntryKey::SuMntNs => "mnt_ns",
DbEntryKey::DenylistConfig => "denylist",
DbEntryKey::ZygiskConfig => "zygisk",
DbEntryKey::BootloopCount => "bootloop",
DbEntryKey::SuManager => "requester",
_ => "",
}
}
}
impl SqlTable for DbSettings {
fn on_row(&mut self, columns: &[String], values: &DbValues) {
let mut key = "";
let mut value = 0;
for (i, column) in columns.iter().enumerate() {
if column == "key" {
key = values.get_text(i as i32);
} else if column == "value" {
value = values.get_int(i as i32);
}
}
match key {
"root_access" => self.root_access = RootAccess { repr: value },
"multiuser_mode" => self.multiuser_mode = MultiuserMode { repr: value },
"mnt_ns" => self.mnt_ns = MntNsMode { repr: value },
"denylist" => self.denylist = value != 0,
"zygisk" => self.zygisk = value != 0,
"bootloop" => self.boot_count = value,
_ => {}
}
}
}
#[repr(transparent)]
pub struct Sqlite3(NonNull<sqlite3>);
unsafe impl Send for Sqlite3 {}
type SqlBindCallback = Option<unsafe extern "C" fn(*mut c_void, i32, Pin<&mut DbStatement>) -> i32>;
type SqlExecCallback = Option<unsafe extern "C" fn(*mut c_void, &[String], &DbValues)>;
extern "C" {
fn sql_exec_impl(
db: *mut sqlite3,
sql: &str,
bind_callback: SqlBindCallback,
bind_cookie: *mut c_void,
exec_callback: SqlExecCallback,
exec_cookie: *mut c_void,
) -> i32;
}
pub enum DbArg<'a> {
Text(&'a str),
Integer(i64),
}
struct DbArgs<'a> {
args: &'a [DbArg<'a>],
curr: usize,
}
unsafe extern "C" fn bind_arguments(v: *mut c_void, idx: i32, stmt: Pin<&mut DbStatement>) -> i32 {
let args = &mut *(v as *mut DbArgs<'_>);
if args.curr < args.args.len() {
let arg = &args.args[args.curr];
args.curr += 1;
match *arg {
Text(v) => stmt.bind_text(idx, v),
Integer(v) => stmt.bind_int64(idx, v),
}
} else {
0
}
}
unsafe extern "C" fn read_db_row<T: SqlTable>(
v: *mut c_void,
columns: &[String],
values: &DbValues,
) {
let table = &mut *(v as *mut T);
table.on_row(columns, values);
}
impl MagiskD {
fn with_db<F: FnOnce(*mut sqlite3) -> i32>(&self, f: F) -> i32 {
let mut db = self.sql_connection.lock().unwrap();
if db.is_none() {
let raw_db = open_and_init_db();
*db = NonNull::new(raw_db).map(Sqlite3);
}
if let Some(ref mut db) = *db {
f(db.0.as_ptr())
} else {
-1
}
}
fn db_exec_impl(
&self,
sql: &str,
args: &[DbArg],
exec_callback: SqlExecCallback,
exec_cookie: *mut c_void,
) -> i32 {
let mut bind_callback: SqlBindCallback = None;
let mut bind_cookie: *mut c_void = ptr::null_mut();
let mut db_args = DbArgs { args, curr: 0 };
if !args.is_empty() {
bind_callback = Some(bind_arguments);
bind_cookie = (&mut db_args) as *mut DbArgs as *mut c_void;
}
self.with_db(|db| unsafe {
sql_exec_impl(
db,
sql,
bind_callback,
bind_cookie,
exec_callback,
exec_cookie,
)
})
}
pub fn db_exec_with_rows<T: SqlTable>(&self, sql: &str, args: &[DbArg], out: &mut T) -> i32 {
self.db_exec_impl(
sql,
args,
Some(read_db_row::<T>),
out as *mut T as *mut c_void,
)
}
pub fn db_exec(&self, sql: &str, args: &[DbArg]) -> i32 {
self.db_exec_impl(sql, args, None, ptr::null_mut())
}
pub fn set_db_setting(&self, key: DbEntryKey, value: i32) -> SqliteResult<()> {
self.db_exec(
"INSERT OR REPLACE INTO settings (key,value) VALUES(?,?)",
&[Text(key.to_str()), Integer(value as i64)],
)
.sql_result()
}
pub fn get_db_setting(&self, key: DbEntryKey) -> i32 {
// Get default values
let mut val = match key {
DbEntryKey::RootAccess => RootAccess::default().repr,
DbEntryKey::SuMultiuserMode => MultiuserMode::default().repr,
DbEntryKey::SuMntNs => MntNsMode::default().repr,
DbEntryKey::DenylistConfig => 0,
DbEntryKey::ZygiskConfig => self.is_emulator as i32,
DbEntryKey::BootloopCount => 0,
_ => -1,
};
let mut func = |_: &[String], values: &DbValues| {
val = values.get_int(0);
};
self.db_exec_with_rows(
"SELECT value FROM settings WHERE key=?",
&[Text(key.to_str())],
&mut func,
)
.sql_result()
.log()
.ok();
val
}
pub fn get_db_settings(&self) -> SqliteResult<DbSettings> {
let mut cfg = DbSettings {
zygisk: self.is_emulator,
..Default::default()
};
self.db_exec_with_rows("SELECT * FROM settings", &[], &mut cfg)
.sql_result()?;
Ok(cfg)
}
pub fn get_db_string(&self, key: DbEntryKey) -> String {
let mut val = "".to_string();
let mut func = |_: &[String], values: &DbValues| {
val.push_str(values.get_text(0));
};
self.db_exec_with_rows(
"SELECT value FROM strings WHERE key=?",
&[Text(key.to_str())],
&mut func,
)
.sql_result()
.log()
.ok();
val
}
pub fn rm_db_string(&self, key: DbEntryKey) -> SqliteResult<()> {
self.db_exec("DELETE FROM strings WHERE key=?", &[Text(key.to_str())])
.sql_result()
}
fn db_exec_for_client(&self, fd: OwnedFd) -> LoggedResult<()> {
let mut file = File::from(fd);
let mut reader = BufReader::new(&mut file);
let sql = reader.ipc_read_string()?;
let mut writer = BufWriter::new(&mut file);
let mut output_fn = |columns: &[String], values: &DbValues| {
let mut out = "".to_string();
for (i, column) in columns.iter().enumerate() {
if i != 0 {
out.push('|');
}
out.push_str(column);
out.push('=');
out.push_str(values.get_text(i as i32));
}
writer.ipc_write_string(&out).log().ok();
};
self.db_exec_with_rows(&sql, &[], &mut output_fn);
writer.ipc_write_string("").log()
}
}
impl MagiskD {
pub fn get_db_settings_for_cxx(&self, cfg: &mut DbSettings) -> bool {
cfg.zygisk = self.is_emulator;
self.db_exec_with_rows("SELECT * FROM settings", &[], cfg)
.sql_result()
.log()
.is_ok()
}
pub fn set_db_setting_for_cxx(&self, key: DbEntryKey, value: i32) -> bool {
self.set_db_setting(key, value).log().is_ok()
}
pub fn db_exec_for_cxx(&self, client_fd: RawFd) {
// Take ownership
let fd = unsafe { OwnedFd::from_raw_fd(client_fd) };
self.db_exec_for_client(fd).ok();
}
}
#[export_name = "sql_exec_rs"]
unsafe extern "C" fn sql_exec_for_cxx(
sql: &str,
bind_callback: SqlBindCallback,
bind_cookie: *mut c_void,
exec_callback: SqlExecCallback,
exec_cookie: *mut c_void,
) -> i32 {
MAGISKD.get().unwrap_unchecked().with_db(|db| {
sql_exec_impl(
db,
sql,
bind_callback,
bind_cookie,
exec_callback,
exec_cookie,
)
})
}