mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-10-27 12:59:34 +00:00
Migrate resetprop to Rust
This commit is contained in:
@@ -22,7 +22,7 @@ LOCAL_SRC_FILES := \
|
||||
core/sqlite.cpp \
|
||||
core/thread.cpp \
|
||||
core/core-rs.cpp \
|
||||
core/resetprop/resetprop.cpp \
|
||||
core/resetprop/sys.cpp \
|
||||
core/su/su.cpp \
|
||||
core/zygisk/entry.cpp \
|
||||
core/zygisk/module.cpp \
|
||||
@@ -123,7 +123,7 @@ LOCAL_STATIC_LIBRARIES := \
|
||||
|
||||
LOCAL_SRC_FILES := \
|
||||
core/applet_stub.cpp \
|
||||
core/resetprop/resetprop.cpp \
|
||||
core/resetprop/sys.cpp \
|
||||
core/core-rs.cpp
|
||||
|
||||
LOCAL_CFLAGS := -DAPPLET_STUB_MAIN=resetprop_main
|
||||
|
||||
1
native/src/Cargo.lock
generated
1
native/src/Cargo.lock
generated
@@ -601,6 +601,7 @@ dependencies = [
|
||||
name = "magisk"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"argh",
|
||||
"base",
|
||||
"bit-set",
|
||||
"bytemuck",
|
||||
|
||||
@@ -25,3 +25,4 @@ quick-protobuf = { workspace = true }
|
||||
bytemuck = { workspace = true, features = ["derive"] }
|
||||
thiserror = { workspace = true }
|
||||
bit-set = { workspace = true }
|
||||
argh = { workspace = true }
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <consts.hpp>
|
||||
#include <base.hpp>
|
||||
#include <core.hpp>
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
if (argc < 1)
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
#include <libgen.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <consts.hpp>
|
||||
#include <base.hpp>
|
||||
#include <core.hpp>
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
||||
@@ -2,12 +2,13 @@ use crate::consts::{MAGISK_FULL_VER, MAGISK_PROC_CON, MAIN_CONFIG, ROOTMNT, ROOT
|
||||
use crate::db::Sqlite3;
|
||||
use crate::ffi::{
|
||||
DbEntryKey, ModuleInfo, RequestCode, check_key_combo, exec_common_scripts, exec_module_scripts,
|
||||
get_magisk_tmp, get_prop, initialize_denylist, set_prop, setup_magisk_env,
|
||||
get_magisk_tmp, initialize_denylist, setup_magisk_env,
|
||||
};
|
||||
use crate::logging::{magisk_logging, setup_logfile, start_log_daemon};
|
||||
use crate::module::disable_modules;
|
||||
use crate::mount::{clean_mounts, setup_preinit_dir};
|
||||
use crate::package::ManagerInfo;
|
||||
use crate::resetprop::{get_prop, set_prop};
|
||||
use crate::selinux::restore_tmpcon;
|
||||
use crate::su::SuInfo;
|
||||
use crate::zygisk::ZygiskState;
|
||||
@@ -121,8 +122,8 @@ impl MagiskD {
|
||||
.log()
|
||||
.ok();
|
||||
let safe_mode = boot_cnt >= 2
|
||||
|| get_prop(cstr!("persist.sys.safemode"), true) == "1"
|
||||
|| get_prop(cstr!("ro.sys.safemode"), false) == "1"
|
||||
|| get_prop(cstr!("persist.sys.safemode")) == "1"
|
||||
|| get_prop(cstr!("ro.sys.safemode")) == "1"
|
||||
|| check_key_combo();
|
||||
|
||||
if safe_mode {
|
||||
@@ -232,9 +233,9 @@ pub fn daemon_entry() {
|
||||
magisk_logging();
|
||||
info!("Magisk {} daemon started", MAGISK_FULL_VER);
|
||||
|
||||
let is_emulator = get_prop(cstr!("ro.kernel.qemu"), false) == "1"
|
||||
|| get_prop(cstr!("ro.boot.qemu"), false) == "1"
|
||||
|| get_prop(cstr!("ro.product.device"), false).contains("vsoc");
|
||||
let is_emulator = get_prop(cstr!("ro.kernel.qemu")) == "1"
|
||||
|| get_prop(cstr!("ro.boot.qemu")) == "1"
|
||||
|| get_prop(cstr!("ro.product.device")).contains("vsoc");
|
||||
|
||||
// Load config status
|
||||
let magisk_tmp = get_magisk_tmp();
|
||||
@@ -265,7 +266,7 @@ pub fn daemon_entry() {
|
||||
}
|
||||
if sdk_int < 0 {
|
||||
// In case some devices do not store this info in build.prop, fallback to getprop
|
||||
sdk_int = get_prop(cstr!("ro.build.version.sdk"), false)
|
||||
sdk_int = get_prop(cstr!("ro.build.version.sdk"))
|
||||
.parse::<i32>()
|
||||
.unwrap_or(-1);
|
||||
}
|
||||
@@ -278,13 +279,13 @@ pub fn daemon_entry() {
|
||||
switch_cgroup("/acct", pid);
|
||||
switch_cgroup("/dev/cg2_bpf", pid);
|
||||
switch_cgroup("/sys/fs/cgroup", pid);
|
||||
if get_prop(cstr!("ro.config.per_app_memcg"), false) != "false" {
|
||||
if get_prop(cstr!("ro.config.per_app_memcg")) != "false" {
|
||||
switch_cgroup("/dev/memcg/apps", pid);
|
||||
}
|
||||
|
||||
// Samsung workaround #7887
|
||||
if cstr!("/system_ext/app/mediatek-res/mediatek-res.apk").exists() {
|
||||
set_prop(cstr!("ro.vendor.mtk_model"), cstr!("0"), false);
|
||||
set_prop(cstr!("ro.vendor.mtk_model"), cstr!("0"));
|
||||
}
|
||||
|
||||
// Cleanup pre-init mounts
|
||||
@@ -346,14 +347,14 @@ fn check_data() -> bool {
|
||||
if !mnt {
|
||||
return false;
|
||||
}
|
||||
let crypto = get_prop(cstr!("ro.crypto.state"), false);
|
||||
let crypto = get_prop(cstr!("ro.crypto.state"));
|
||||
return if !crypto.is_empty() {
|
||||
if crypto != "encrypted" {
|
||||
// Unencrypted, we can directly access data
|
||||
true
|
||||
} else {
|
||||
// Encrypted, check whether vold is started
|
||||
!get_prop(cstr!("init.svc.vold"), false).is_empty()
|
||||
!get_prop(cstr!("init.svc.vold")).is_empty()
|
||||
}
|
||||
} else {
|
||||
// ro.crypto.state is not set, assume it's unencrypted
|
||||
|
||||
@@ -17,6 +17,11 @@
|
||||
#define to_app_id(uid) (uid % AID_USER_OFFSET)
|
||||
#define to_user_id(uid) (uid / AID_USER_OFFSET)
|
||||
|
||||
// Multi-call entrypoints
|
||||
int magisk_main(int argc, char *argv[]);
|
||||
int su_client_main(int argc, char *argv[]);
|
||||
int zygisk_main(int argc, char *argv[]);
|
||||
|
||||
// Return codes for daemon
|
||||
enum class RespondCode : int {
|
||||
ERROR = -1,
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <cxx.h>
|
||||
|
||||
struct prop_info;
|
||||
|
||||
struct prop_callback {
|
||||
virtual ~prop_callback() = default;
|
||||
virtual void exec(const char *name, const char *value, uint32_t serial) = 0;
|
||||
void exec(Utf8CStr name, Utf8CStr value) {
|
||||
exec(name.data(), value.data(), INT_MAX);
|
||||
}
|
||||
void read(const prop_info *pi);
|
||||
};
|
||||
|
||||
// System properties
|
||||
std::string get_prop(const char *name, bool persist = false);
|
||||
int delete_prop(const char *name, bool persist = false);
|
||||
int set_prop(const char *name, const char *value, bool skip_svc = false);
|
||||
void load_prop_file(const char *filename, bool skip_svc = false);
|
||||
|
||||
// Rust bindings
|
||||
rust::String get_prop_rs(Utf8CStr name, bool persist);
|
||||
static inline int set_prop_rs(Utf8CStr name, Utf8CStr value, bool skip_svc) {
|
||||
return set_prop(name.data(), value.data(), skip_svc);
|
||||
}
|
||||
static inline void load_prop_file_rs(Utf8CStr filename, bool skip_svc) {
|
||||
load_prop_file(filename.data(), skip_svc);
|
||||
}
|
||||
@@ -15,7 +15,7 @@ use derive::Decodable;
|
||||
use logging::{android_logging, setup_logfile, zygisk_close_logd, zygisk_get_logd, zygisk_logging};
|
||||
use module::remove_modules;
|
||||
use mount::{find_preinit_device, revert_unmount};
|
||||
use resetprop::{persist_delete_prop, persist_get_prop, persist_get_props, persist_set_prop};
|
||||
use resetprop::{get_prop, resetprop_main};
|
||||
use selinux::{lgetfilecon, lsetfilecon, restorecon, setfilecon};
|
||||
use socket::{recv_fd, recv_fds, send_fd, send_fds};
|
||||
use std::fs::File;
|
||||
@@ -164,19 +164,6 @@ pub mod ffi {
|
||||
fn get_text(self: &DbValues, index: i32) -> &str;
|
||||
fn bind_text(self: Pin<&mut DbStatement>, index: i32, val: &str) -> i32;
|
||||
fn bind_int64(self: Pin<&mut DbStatement>, index: i32, val: i64) -> i32;
|
||||
|
||||
include!("include/resetprop.hpp");
|
||||
|
||||
#[cxx_name = "prop_callback"]
|
||||
type PropCallback;
|
||||
fn exec(self: Pin<&mut PropCallback>, name: Utf8CStrRef, value: Utf8CStrRef);
|
||||
|
||||
#[cxx_name = "get_prop_rs"]
|
||||
fn get_prop(name: Utf8CStrRef, persist: bool) -> String;
|
||||
#[cxx_name = "set_prop_rs"]
|
||||
fn set_prop(name: Utf8CStrRef, value: Utf8CStrRef, skip_svc: bool) -> i32;
|
||||
#[cxx_name = "load_prop_file_rs"]
|
||||
fn load_prop_file(filename: Utf8CStrRef, skip_svc: bool);
|
||||
}
|
||||
|
||||
extern "Rust" {
|
||||
@@ -189,10 +176,6 @@ pub mod ffi {
|
||||
fn revert_unmount(pid: i32);
|
||||
fn remove_modules();
|
||||
fn zygisk_should_load_module(flags: u32) -> bool;
|
||||
unsafe fn persist_get_prop(name: Utf8CStrRef, prop_cb: Pin<&mut PropCallback>);
|
||||
unsafe fn persist_get_props(prop_cb: Pin<&mut PropCallback>);
|
||||
unsafe fn persist_delete_prop(name: Utf8CStrRef) -> bool;
|
||||
unsafe fn persist_set_prop(name: Utf8CStrRef, value: Utf8CStrRef) -> bool;
|
||||
fn send_fd(socket: i32, fd: i32) -> bool;
|
||||
fn send_fds(socket: i32, fds: &[i32]) -> bool;
|
||||
fn recv_fd(socket: i32) -> i32;
|
||||
@@ -206,6 +189,9 @@ pub mod ffi {
|
||||
fn setfilecon(path: Utf8CStrRef, con: Utf8CStrRef) -> bool;
|
||||
fn lsetfilecon(path: Utf8CStrRef, con: Utf8CStrRef) -> bool;
|
||||
|
||||
fn get_prop(name: Utf8CStrRef) -> String;
|
||||
unsafe fn resetprop_main(argc: i32, argv: *mut *mut c_char) -> i32;
|
||||
|
||||
#[namespace = "rust"]
|
||||
fn daemon_entry();
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use crate::consts::{MODULEMNT, MODULEROOT, MODULEUPGRADE, WORKERDIR};
|
||||
use crate::daemon::MagiskD;
|
||||
use crate::ffi::{ModuleInfo, exec_module_scripts, exec_script, get_magisk_tmp, load_prop_file};
|
||||
use crate::ffi::{ModuleInfo, exec_module_scripts, exec_script, get_magisk_tmp};
|
||||
use crate::mount::setup_module_mount;
|
||||
use crate::resetprop::load_prop_file;
|
||||
use base::{
|
||||
DirEntry, Directory, FsPathBuilder, LibcReturn, LoggedResult, OsResultStatic, ResultExt,
|
||||
SilentLogExt, Utf8CStr, Utf8CStrBuf, Utf8CString, WalkResult, clone_attr, cstr, debug, error,
|
||||
@@ -804,8 +805,7 @@ impl MagiskD {
|
||||
// Read props
|
||||
let prop = module_paths.append("system.prop");
|
||||
if prop.module().exists() {
|
||||
// Do NOT go through property service as it could cause boot lock
|
||||
load_prop_file(prop.module(), true);
|
||||
load_prop_file(prop.module());
|
||||
}
|
||||
}
|
||||
{
|
||||
|
||||
@@ -12,7 +12,8 @@ use base::{
|
||||
};
|
||||
|
||||
use crate::consts::{MODULEMNT, MODULEROOT, PREINITDEV, PREINITMIRR, WORKERDIR};
|
||||
use crate::ffi::{get_magisk_tmp, get_prop, resolve_preinit_dir, switch_mnt_ns};
|
||||
use crate::ffi::{get_magisk_tmp, resolve_preinit_dir, switch_mnt_ns};
|
||||
use crate::resetprop::get_prop;
|
||||
|
||||
pub fn setup_preinit_dir() {
|
||||
let magisk_tmp = get_magisk_tmp();
|
||||
@@ -109,11 +110,11 @@ enum EncryptType {
|
||||
}
|
||||
|
||||
pub fn find_preinit_device() -> String {
|
||||
let encrypt_type = if get_prop(cstr!("ro.crypto.state"), false) != "encrypted" {
|
||||
let encrypt_type = if get_prop(cstr!("ro.crypto.state")) != "encrypted" {
|
||||
EncryptType::None
|
||||
} else if get_prop(cstr!("ro.crypto.type"), false) == "block" {
|
||||
} else if get_prop(cstr!("ro.crypto.type")) == "block" {
|
||||
EncryptType::Block
|
||||
} else if get_prop(cstr!("ro.crypto.metadata.enabled"), false) == "true" {
|
||||
} else if get_prop(cstr!("ro.crypto.metadata.enabled")) == "true" {
|
||||
EncryptType::Metadata
|
||||
} else {
|
||||
EncryptType::File
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
pub use persist::{persist_delete_prop, persist_get_prop, persist_get_props, persist_set_prop};
|
||||
|
||||
mod persist;
|
||||
mod proto;
|
||||
325
native/src/core/resetprop/cli.rs
Normal file
325
native/src/core/resetprop/cli.rs
Normal file
@@ -0,0 +1,325 @@
|
||||
use super::{
|
||||
PropInfo, PropReader, SYS_PROP,
|
||||
persist::{persist_delete_prop, persist_get_all_props, persist_get_prop, persist_set_prop},
|
||||
};
|
||||
use argh::{EarlyExit, FromArgs, MissingRequirements};
|
||||
use base::libc::PROP_VALUE_MAX;
|
||||
use base::{
|
||||
BufReadExt, CmdArgs, EarlyExitExt, LogLevel, LoggedResult, ResultExt, Utf8CStr, Utf8CStrBuf,
|
||||
Utf8CString, cstr, debug, libc, log_err, set_log_level_state,
|
||||
};
|
||||
use std::collections::BTreeMap;
|
||||
use std::ffi::c_char;
|
||||
use std::io::BufReader;
|
||||
|
||||
#[derive(FromArgs, Default)]
|
||||
struct ResetProp {
|
||||
#[argh(switch, short = 'v')]
|
||||
verbose: bool,
|
||||
#[argh(switch, short = 'w')]
|
||||
wait_mode: bool,
|
||||
#[argh(switch, short = 'p')]
|
||||
persist: bool,
|
||||
#[argh(switch, short = 'P')]
|
||||
persist_only: bool,
|
||||
#[argh(switch, short = 'Z')]
|
||||
context: bool,
|
||||
#[argh(switch, short = 'n')]
|
||||
skip_svc: bool,
|
||||
#[argh(option, short = 'f')]
|
||||
file: Option<Utf8CString>,
|
||||
#[argh(option, long = "delete", short = 'd')]
|
||||
delete_key: Option<Utf8CString>,
|
||||
#[argh(positional)]
|
||||
args: Vec<Utf8CString>,
|
||||
}
|
||||
|
||||
fn print_usage(cmd: &str) {
|
||||
eprintln!(
|
||||
r#"resetprop - System Property Manipulation Tool
|
||||
|
||||
Usage: {cmd} [flags] [arguments...]
|
||||
|
||||
Read mode arguments:
|
||||
(no arguments) print all properties
|
||||
NAME get property of NAME
|
||||
|
||||
Write mode arguments:
|
||||
NAME VALUE set property NAME as VALUE
|
||||
-f,--file FILE load and set properties from FILE
|
||||
-d,--delete NAME delete property
|
||||
|
||||
Wait mode arguments (toggled with -w):
|
||||
NAME wait until property NAME changes
|
||||
NAME OLD_VALUE if value of property NAME is not OLD_VALUE, get value
|
||||
or else wait until property NAME changes
|
||||
|
||||
General flags:
|
||||
-h,--help show this message
|
||||
-v print verbose output to stderr
|
||||
-w switch to wait mode
|
||||
|
||||
Read mode flags:
|
||||
-p also read persistent properties from storage
|
||||
-P only read persistent properties from storage
|
||||
-Z get property context instead of value
|
||||
|
||||
Write mode flags:
|
||||
-n set properties bypassing property_service
|
||||
-p always write persistent prop changes to storage
|
||||
"#
|
||||
);
|
||||
}
|
||||
|
||||
impl ResetProp {
|
||||
fn get(&self, key: &Utf8CStr) -> Option<String> {
|
||||
if self.context {
|
||||
return Some(SYS_PROP.get_context(key).to_string());
|
||||
}
|
||||
|
||||
let mut val = if !self.persist_only {
|
||||
SYS_PROP.find(key).map(|info| {
|
||||
let mut v = String::new();
|
||||
info.read(&mut PropReader::Value(&mut v));
|
||||
debug!("resetprop: get prop [{key}]=[{v}]");
|
||||
v
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if val.is_none() && (self.persist || self.persist_only) && key.starts_with("persist.") {
|
||||
val = persist_get_prop(key).ok();
|
||||
}
|
||||
|
||||
if val.is_none() {
|
||||
debug!("resetprop: prop [{key}] does not exist");
|
||||
}
|
||||
|
||||
val
|
||||
}
|
||||
|
||||
fn print_all(&self) {
|
||||
let mut map: BTreeMap<String, String> = BTreeMap::new();
|
||||
if !self.persist_only {
|
||||
SYS_PROP.for_each(&mut PropReader::List(&mut map));
|
||||
}
|
||||
if self.persist || self.persist_only {
|
||||
persist_get_all_props(&mut PropReader::List(&mut map)).log_ok();
|
||||
}
|
||||
for (mut k, v) in map.into_iter() {
|
||||
if self.context {
|
||||
println!(
|
||||
"[{k}]: [{}]",
|
||||
SYS_PROP.get_context(Utf8CStr::from_string(&mut k))
|
||||
);
|
||||
} else {
|
||||
println!("[{k}]: [{v}]");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set(&self, key: &Utf8CStr, val: &Utf8CStr) {
|
||||
let mut skip_svc = self.skip_svc;
|
||||
let mut info = SYS_PROP.find_mut(key);
|
||||
|
||||
// Delete existing read-only properties if they are or will be long properties,
|
||||
// which cannot directly go through __system_property_update
|
||||
if key.starts_with("ro.") {
|
||||
skip_svc = true;
|
||||
if let Some(pi) = &info
|
||||
&& (pi.is_long() || val.len() >= PROP_VALUE_MAX as usize)
|
||||
{
|
||||
// Skip pruning nodes as we will add it back ASAP
|
||||
SYS_PROP.delete(key, false);
|
||||
info = None;
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
let msg = if skip_svc {
|
||||
"direct modification"
|
||||
} else {
|
||||
"property_service"
|
||||
};
|
||||
|
||||
if let Some(pi) = info {
|
||||
if skip_svc {
|
||||
pi.update(val);
|
||||
} else {
|
||||
SYS_PROP.set(key, val);
|
||||
}
|
||||
debug!("resetprop: update prop [{key}]=[{val}] by {msg}");
|
||||
} else {
|
||||
if skip_svc {
|
||||
SYS_PROP.add(key, val);
|
||||
} else {
|
||||
SYS_PROP.set(key, val);
|
||||
}
|
||||
debug!("resetprop: create prop [{key}]=[{val}] by {msg}");
|
||||
}
|
||||
|
||||
// When bypassing property_service, persistent props won't be stored in storage.
|
||||
// Explicitly handle this situation.
|
||||
if skip_svc && self.persist && key.starts_with("persist.") {
|
||||
persist_set_prop(key, val).log_ok();
|
||||
}
|
||||
}
|
||||
|
||||
fn delete(&self, key: &Utf8CStr) -> bool {
|
||||
debug!("resetprop: delete prop [{key}]");
|
||||
let mut ret = false;
|
||||
ret |= SYS_PROP.delete(key, true);
|
||||
if self.persist && key.starts_with("persist.") {
|
||||
ret |= persist_delete_prop(key).is_ok()
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
fn wait(&self) {
|
||||
let key = &self.args[0];
|
||||
let val = self.args.get(1).map(|s| &**s);
|
||||
|
||||
// Find PropInfo
|
||||
let info: &PropInfo;
|
||||
loop {
|
||||
let i = SYS_PROP.find(key);
|
||||
if let Some(i) = i {
|
||||
info = i;
|
||||
break;
|
||||
} else {
|
||||
debug!("resetprop: waiting for prop [{key}] to exist");
|
||||
let mut serial = SYS_PROP.area_serial();
|
||||
SYS_PROP.wait(None, serial, &mut serial);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(val) = val {
|
||||
let mut curr_val = String::new();
|
||||
let mut serial = 0;
|
||||
loop {
|
||||
let mut r = PropReader::ValueSerial(&mut curr_val, &mut serial);
|
||||
SYS_PROP.read(info, &mut r);
|
||||
if *val != *curr_val {
|
||||
debug!("resetprop: get prop [{key}]=[{curr_val}]");
|
||||
break;
|
||||
}
|
||||
debug!("resetprop: waiting for prop [{key}]!=[{val}]");
|
||||
SYS_PROP.wait(Some(info), serial, &mut serial);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn load_file(&self, file: &Utf8CStr) -> LoggedResult<()> {
|
||||
let fd = file.open(libc::O_RDONLY | libc::O_CLOEXEC)?;
|
||||
let mut key = cstr::buf::dynamic(128);
|
||||
let mut val = cstr::buf::dynamic(128);
|
||||
BufReader::new(fd).for_each_prop(|k, v| {
|
||||
key.clear();
|
||||
val.clear();
|
||||
key.push_str(k);
|
||||
val.push_str(v);
|
||||
self.set(&key, &val);
|
||||
true
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run(self) -> LoggedResult<()> {
|
||||
if self.wait_mode {
|
||||
self.wait();
|
||||
} else if let Some(file) = &self.file {
|
||||
self.load_file(file)?;
|
||||
} else if let Some(key) = &self.delete_key {
|
||||
if !self.delete(key) {
|
||||
return log_err!();
|
||||
}
|
||||
} else {
|
||||
match self.args.len() {
|
||||
0 => self.print_all(),
|
||||
1 => {
|
||||
if let Some(val) = self.get(&self.args[0]) {
|
||||
println!("{val}");
|
||||
} else {
|
||||
return log_err!();
|
||||
}
|
||||
}
|
||||
2 => self.set(&self.args[0], &self.args[1]),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resetprop_main(argc: i32, argv: *mut *mut c_char) -> i32 {
|
||||
set_log_level_state(LogLevel::Debug, false);
|
||||
let cmds = CmdArgs::new(argc, argv.cast());
|
||||
let cmds = cmds.as_slice();
|
||||
|
||||
let cli = ResetProp::from_args(&[cmds[0]], &cmds[1..])
|
||||
.and_then(|cli| {
|
||||
let mut special_mode = 0;
|
||||
if cli.wait_mode {
|
||||
if cli.args.is_empty() {
|
||||
let mut missing = MissingRequirements::default();
|
||||
missing.missing_positional_arg("NAME");
|
||||
missing.err_on_any()?;
|
||||
}
|
||||
special_mode += 1;
|
||||
}
|
||||
if cli.file.is_some() {
|
||||
special_mode += 1;
|
||||
}
|
||||
if cli.delete_key.is_some() {
|
||||
special_mode += 1;
|
||||
}
|
||||
if special_mode > 1 {
|
||||
return Err(EarlyExit::from(
|
||||
"Multiple operation mode detected!\n".to_string(),
|
||||
));
|
||||
}
|
||||
if cli.args.len() > 2 {
|
||||
return Err(EarlyExit::from(format!(
|
||||
"Unrecognized argument: {}\n",
|
||||
cli.args[2]
|
||||
)));
|
||||
}
|
||||
Ok(cli)
|
||||
})
|
||||
.on_early_exit(|| print_usage(cmds[0]));
|
||||
|
||||
if cli.verbose {
|
||||
set_log_level_state(LogLevel::Debug, true);
|
||||
}
|
||||
|
||||
if cli.run().is_ok() { 0 } else { 1 }
|
||||
}
|
||||
|
||||
// Magisk's own helper functions
|
||||
|
||||
pub fn set_prop(key: &Utf8CStr, val: &Utf8CStr) {
|
||||
let prop = ResetProp {
|
||||
// All Magisk's internal usage should skip property_service
|
||||
skip_svc: true,
|
||||
..Default::default()
|
||||
};
|
||||
prop.set(key, val);
|
||||
}
|
||||
|
||||
pub fn load_prop_file(file: &Utf8CStr) {
|
||||
let prop = ResetProp {
|
||||
// All Magisk's internal usage should skip property_service
|
||||
skip_svc: true,
|
||||
..Default::default()
|
||||
};
|
||||
prop.load_file(file).ok();
|
||||
}
|
||||
|
||||
pub fn get_prop(key: &Utf8CStr) -> String {
|
||||
let prop = ResetProp {
|
||||
persist: key.starts_with("persist."),
|
||||
..Default::default()
|
||||
};
|
||||
prop.get(key).unwrap_or_default()
|
||||
}
|
||||
181
native/src/core/resetprop/mod.rs
Normal file
181
native/src/core/resetprop/mod.rs
Normal file
@@ -0,0 +1,181 @@
|
||||
use base::libc::c_char;
|
||||
use base::{Utf8CStr, libc};
|
||||
pub use cli::{get_prop, load_prop_file, resetprop_main, set_prop};
|
||||
use libc::timespec;
|
||||
use std::collections::BTreeMap;
|
||||
use std::ffi::CStr;
|
||||
use std::ptr;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
mod cli;
|
||||
mod persist;
|
||||
mod proto;
|
||||
|
||||
static SYS_PROP: LazyLock<SysProp> = LazyLock::new(|| unsafe { get_sys_prop() });
|
||||
|
||||
#[repr(C)]
|
||||
struct PropInfo {
|
||||
_private: cxx::private::Opaque,
|
||||
}
|
||||
|
||||
type CharPtr = *const c_char;
|
||||
type ReadCallback = unsafe extern "C" fn(&mut PropReader, CharPtr, CharPtr, u32);
|
||||
type ForEachCallback = unsafe extern "C" fn(&PropInfo, &mut PropReader);
|
||||
|
||||
enum PropReader<'a> {
|
||||
Value(&'a mut String),
|
||||
ValueSerial(&'a mut String, &'a mut u32),
|
||||
List(&'a mut BTreeMap<String, String>),
|
||||
}
|
||||
|
||||
impl PropReader<'_> {
|
||||
fn put_cstr(&mut self, key: CharPtr, val: CharPtr, serial: u32) {
|
||||
let key = unsafe { CStr::from_ptr(key) };
|
||||
let val = unsafe { CStr::from_ptr(val) };
|
||||
match self {
|
||||
PropReader::Value(v) => {
|
||||
**v = String::from_utf8_lossy(val.to_bytes()).into_owned();
|
||||
}
|
||||
PropReader::ValueSerial(v, s) => {
|
||||
**v = String::from_utf8_lossy(val.to_bytes()).into_owned();
|
||||
**s = serial;
|
||||
}
|
||||
PropReader::List(map) => {
|
||||
map.insert(
|
||||
String::from_utf8_lossy(key.to_bytes()).into_owned(),
|
||||
String::from_utf8_lossy(val.to_bytes()).into_owned(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn put_str(&mut self, key: String, val: String, serial: u32) {
|
||||
match self {
|
||||
PropReader::Value(v) => {
|
||||
**v = val;
|
||||
}
|
||||
PropReader::ValueSerial(v, s) => {
|
||||
**v = val;
|
||||
**s = serial;
|
||||
}
|
||||
PropReader::List(map) => {
|
||||
map.insert(key, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" {
|
||||
// SAFETY: the improper_ctypes warning is about PropReader. We only pass PropReader
|
||||
// to C functions as raw pointers, and all actual usage happens on the Rust side.
|
||||
#[allow(improper_ctypes)]
|
||||
fn get_sys_prop() -> SysProp;
|
||||
|
||||
fn prop_info_is_long(info: &PropInfo) -> bool;
|
||||
#[link_name = "__system_property_find2"]
|
||||
fn sys_prop_find(key: CharPtr) -> Option<&'static mut PropInfo>;
|
||||
#[link_name = "__system_property_update2"]
|
||||
fn sys_prop_update(info: &mut PropInfo, val: CharPtr, val_len: u32) -> i32;
|
||||
#[link_name = "__system_property_add2"]
|
||||
fn sys_prop_add(key: CharPtr, key_len: u32, val: CharPtr, val_len: u32) -> i32;
|
||||
#[link_name = "__system_property_delete"]
|
||||
fn sys_prop_delete(key: CharPtr, prune: bool) -> i32;
|
||||
#[link_name = "__system_property_get_context"]
|
||||
fn sys_prop_get_context(key: CharPtr) -> CharPtr;
|
||||
#[link_name = "__system_property_area_serial2"]
|
||||
fn sys_prop_area_serial() -> u32;
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
struct SysProp {
|
||||
set: unsafe extern "C" fn(CharPtr, CharPtr) -> i32,
|
||||
find: unsafe extern "C" fn(CharPtr) -> Option<&'static PropInfo>,
|
||||
read_callback: unsafe extern "C" fn(&PropInfo, ReadCallback, &mut PropReader) -> i32,
|
||||
foreach: unsafe extern "C" fn(ForEachCallback, &mut PropReader) -> i32,
|
||||
wait: unsafe extern "C" fn(Option<&PropInfo>, u32, &mut u32, *const timespec) -> i32,
|
||||
}
|
||||
|
||||
// Safe abstractions over raw C APIs
|
||||
|
||||
impl PropInfo {
|
||||
fn read(&self, reader: &mut PropReader) {
|
||||
SYS_PROP.read(self, reader);
|
||||
}
|
||||
|
||||
fn update(&mut self, val: &Utf8CStr) {
|
||||
SYS_PROP.update(self, val);
|
||||
}
|
||||
|
||||
fn is_long(&self) -> bool {
|
||||
unsafe { prop_info_is_long(self) }
|
||||
}
|
||||
}
|
||||
|
||||
impl SysProp {
|
||||
fn read(&self, info: &PropInfo, reader: &mut PropReader) {
|
||||
unsafe extern "C" fn read_fn(r: &mut PropReader, key: CharPtr, val: CharPtr, serial: u32) {
|
||||
r.put_cstr(key, val, serial);
|
||||
}
|
||||
unsafe {
|
||||
(self.read_callback)(info, read_fn, reader);
|
||||
}
|
||||
}
|
||||
|
||||
fn find(&self, key: &Utf8CStr) -> Option<&'static PropInfo> {
|
||||
unsafe { (self.find)(key.as_ptr()) }
|
||||
}
|
||||
|
||||
fn find_mut(&self, key: &Utf8CStr) -> Option<&'static mut PropInfo> {
|
||||
unsafe { sys_prop_find(key.as_ptr()) }
|
||||
}
|
||||
|
||||
fn set(&self, key: &Utf8CStr, val: &Utf8CStr) {
|
||||
unsafe {
|
||||
(self.set)(key.as_ptr(), val.as_ptr());
|
||||
}
|
||||
}
|
||||
|
||||
fn add(&self, key: &Utf8CStr, val: &Utf8CStr) {
|
||||
unsafe {
|
||||
sys_prop_add(
|
||||
key.as_ptr(),
|
||||
key.len() as u32,
|
||||
val.as_ptr(),
|
||||
val.len() as u32,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&self, info: &mut PropInfo, val: &Utf8CStr) {
|
||||
unsafe {
|
||||
sys_prop_update(info, val.as_ptr(), val.len() as u32);
|
||||
}
|
||||
}
|
||||
|
||||
fn delete(&self, key: &Utf8CStr, prune: bool) -> bool {
|
||||
unsafe { sys_prop_delete(key.as_ptr(), prune) == 0 }
|
||||
}
|
||||
|
||||
fn for_each(&self, reader: &mut PropReader) {
|
||||
unsafe extern "C" fn for_each_fn(info: &PropInfo, vals: &mut PropReader) {
|
||||
SYS_PROP.read(info, vals);
|
||||
}
|
||||
unsafe {
|
||||
(self.foreach)(for_each_fn, reader);
|
||||
}
|
||||
}
|
||||
|
||||
fn wait(&self, info: Option<&PropInfo>, old_serial: u32, new_serial: &mut u32) {
|
||||
unsafe {
|
||||
(self.wait)(info, old_serial, new_serial, ptr::null());
|
||||
}
|
||||
}
|
||||
|
||||
fn get_context(&self, key: &Utf8CStr) -> &'static Utf8CStr {
|
||||
unsafe { Utf8CStr::from_ptr_unchecked(sys_prop_get_context(key.as_ptr())) }
|
||||
}
|
||||
|
||||
fn area_serial(&self) -> u32 {
|
||||
unsafe { sys_prop_area_serial() }
|
||||
}
|
||||
}
|
||||
@@ -3,12 +3,11 @@ use std::{
|
||||
fs::File,
|
||||
io::{BufWriter, Write},
|
||||
os::fd::FromRawFd,
|
||||
pin::Pin,
|
||||
};
|
||||
|
||||
use quick_protobuf::{BytesReader, MessageRead, MessageWrite, Writer};
|
||||
|
||||
use crate::ffi::PropCallback;
|
||||
use crate::resetprop::PropReader;
|
||||
use crate::resetprop::proto::persistent_properties::{
|
||||
PersistentProperties, mod_PersistentProperties::PersistentPropertyRecord,
|
||||
};
|
||||
@@ -16,7 +15,7 @@ use base::const_format::concatcp;
|
||||
use base::libc::{O_CLOEXEC, O_RDONLY};
|
||||
use base::{
|
||||
Directory, FsPathBuilder, LibcReturn, LoggedResult, MappedFile, SilentLogExt, Utf8CStr,
|
||||
Utf8CStrBuf, WalkResult, clone_attr, cstr, debug, libc::mkstemp,
|
||||
Utf8CStrBuf, WalkResult, clone_attr, cstr, debug, libc::mkstemp, log_err,
|
||||
};
|
||||
|
||||
const PERSIST_PROP_DIR: &str = "/data/property";
|
||||
@@ -24,7 +23,7 @@ const PERSIST_PROP: &str = concatcp!(PERSIST_PROP_DIR, "/persistent_properties")
|
||||
|
||||
trait PropExt {
|
||||
fn find_index(&self, name: &Utf8CStr) -> Result<usize, usize>;
|
||||
fn find(&mut self, name: &Utf8CStr) -> LoggedResult<&mut PersistentPropertyRecord>;
|
||||
fn find(self, name: &Utf8CStr) -> Option<PersistentPropertyRecord>;
|
||||
}
|
||||
|
||||
impl PropExt for PersistentProperties {
|
||||
@@ -33,9 +32,9 @@ impl PropExt for PersistentProperties {
|
||||
.binary_search_by(|p| p.name.as_deref().cmp(&Some(name.as_str())))
|
||||
}
|
||||
|
||||
fn find(&mut self, name: &Utf8CStr) -> LoggedResult<&mut PersistentPropertyRecord> {
|
||||
let idx = self.find_index(name).silent()?;
|
||||
Ok(&mut self.properties[idx])
|
||||
fn find(self, name: &Utf8CStr) -> Option<PersistentPropertyRecord> {
|
||||
let idx = self.find_index(name).ok()?;
|
||||
self.properties.into_iter().nth(idx)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,92 +107,80 @@ fn proto_write_props(props: &PersistentProperties) -> LoggedResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn persist_get_prop(name: &Utf8CStr, prop_cb: Pin<&mut PropCallback>) {
|
||||
let res: LoggedResult<()> = try {
|
||||
if check_proto() {
|
||||
let mut props = proto_read_props()?;
|
||||
let prop = props.find(name)?;
|
||||
pub(super) fn persist_get_prop(key: &Utf8CStr) -> LoggedResult<String> {
|
||||
if check_proto() {
|
||||
let props = proto_read_props()?;
|
||||
let prop = props.find(key).silent()?;
|
||||
if let PersistentPropertyRecord {
|
||||
name: Some(_),
|
||||
value: Some(v),
|
||||
} = prop
|
||||
{
|
||||
return Ok(v);
|
||||
}
|
||||
} else {
|
||||
let value = file_get_prop(key)?;
|
||||
debug!("resetprop: get persist prop [{}]=[{}]", key, value);
|
||||
return Ok(value);
|
||||
}
|
||||
log_err!()
|
||||
}
|
||||
|
||||
pub(super) fn persist_get_all_props(reader: &mut PropReader) -> LoggedResult<()> {
|
||||
if check_proto() {
|
||||
let props = proto_read_props()?;
|
||||
props.properties.into_iter().for_each(|prop| {
|
||||
if let PersistentPropertyRecord {
|
||||
name: Some(n),
|
||||
value: Some(v),
|
||||
} = prop
|
||||
{
|
||||
prop_cb.exec(Utf8CStr::from_string(n), Utf8CStr::from_string(v));
|
||||
reader.put_str(n, v, 0);
|
||||
}
|
||||
} else {
|
||||
let mut value = file_get_prop(name)?;
|
||||
prop_cb.exec(name, Utf8CStr::from_string(&mut value));
|
||||
debug!("resetprop: found prop [{}] = [{}]", name, value);
|
||||
}
|
||||
};
|
||||
res.ok();
|
||||
}
|
||||
|
||||
pub fn persist_get_props(mut prop_cb: Pin<&mut PropCallback>) {
|
||||
let res: LoggedResult<()> = try {
|
||||
if check_proto() {
|
||||
let mut props = proto_read_props()?;
|
||||
props.properties.iter_mut().for_each(|prop| {
|
||||
if let PersistentPropertyRecord {
|
||||
name: Some(n),
|
||||
value: Some(v),
|
||||
} = prop
|
||||
{
|
||||
prop_cb
|
||||
.as_mut()
|
||||
.exec(Utf8CStr::from_string(n), Utf8CStr::from_string(v));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
let mut dir = Directory::open(cstr!(PERSIST_PROP_DIR))?;
|
||||
dir.pre_order_walk(|e| {
|
||||
if e.is_file()
|
||||
&& let Ok(mut value) = file_get_prop(e.name())
|
||||
{
|
||||
prop_cb
|
||||
.as_mut()
|
||||
.exec(e.name(), Utf8CStr::from_string(&mut value));
|
||||
}
|
||||
// Do not traverse recursively
|
||||
Ok(WalkResult::Skip)
|
||||
})?;
|
||||
}
|
||||
};
|
||||
res.ok();
|
||||
}
|
||||
|
||||
pub fn persist_delete_prop(name: &Utf8CStr) -> bool {
|
||||
let res: LoggedResult<()> = try {
|
||||
if check_proto() {
|
||||
let mut props = proto_read_props()?;
|
||||
let idx = props.find_index(name).silent()?;
|
||||
props.properties.remove(idx);
|
||||
proto_write_props(&props)?;
|
||||
} else {
|
||||
file_set_prop(name, None)?;
|
||||
}
|
||||
};
|
||||
res.is_ok()
|
||||
}
|
||||
|
||||
pub fn persist_set_prop(name: &Utf8CStr, value: &Utf8CStr) -> bool {
|
||||
let res: LoggedResult<()> = try {
|
||||
if check_proto() {
|
||||
let mut props = proto_read_props()?;
|
||||
match props.find_index(name) {
|
||||
Ok(idx) => props.properties[idx].value = Some(value.to_string()),
|
||||
Err(idx) => props.properties.insert(
|
||||
idx,
|
||||
PersistentPropertyRecord {
|
||||
name: Some(name.to_string()),
|
||||
value: Some(value.to_string()),
|
||||
},
|
||||
),
|
||||
});
|
||||
} else {
|
||||
let mut dir = Directory::open(cstr!(PERSIST_PROP_DIR))?;
|
||||
dir.pre_order_walk(|e| {
|
||||
if e.is_file()
|
||||
&& let Ok(value) = file_get_prop(e.name())
|
||||
{
|
||||
reader.put_str(e.name().to_string(), value, 0);
|
||||
}
|
||||
proto_write_props(&props)?;
|
||||
} else {
|
||||
file_set_prop(name, Some(value))?;
|
||||
}
|
||||
};
|
||||
res.is_ok()
|
||||
// Do not traverse recursively
|
||||
Ok(WalkResult::Skip)
|
||||
})?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn persist_delete_prop(key: &Utf8CStr) -> LoggedResult<()> {
|
||||
if check_proto() {
|
||||
let mut props = proto_read_props()?;
|
||||
let idx = props.find_index(key).silent()?;
|
||||
props.properties.remove(idx);
|
||||
proto_write_props(&props)?;
|
||||
} else {
|
||||
file_set_prop(key, None)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn persist_set_prop(key: &Utf8CStr, val: &Utf8CStr) -> LoggedResult<()> {
|
||||
if check_proto() {
|
||||
let mut props = proto_read_props()?;
|
||||
match props.find_index(key) {
|
||||
Ok(idx) => props.properties[idx].value = Some(val.to_string()),
|
||||
Err(idx) => props.properties.insert(
|
||||
idx,
|
||||
PersistentPropertyRecord {
|
||||
name: Some(key.to_string()),
|
||||
value: Some(val.to_string()),
|
||||
},
|
||||
),
|
||||
}
|
||||
proto_write_props(&props)?;
|
||||
} else {
|
||||
file_set_prop(key, Some(val))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,473 +0,0 @@
|
||||
#include <dlfcn.h>
|
||||
#include <sys/types.h>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
#include <base.hpp>
|
||||
#include <core.hpp>
|
||||
#include <resetprop.hpp>
|
||||
|
||||
#include <api/system_properties.h>
|
||||
#include <system_properties/prop_info.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
#ifdef APPLET_STUB_MAIN
|
||||
#define system_property_set __system_property_set
|
||||
#define system_property_find __system_property_find
|
||||
#define system_property_read_callback __system_property_read_callback
|
||||
#define system_property_foreach __system_property_foreach
|
||||
#define system_property_wait __system_property_wait
|
||||
#else
|
||||
static int (*system_property_set)(const char*, const char*);
|
||||
static int (*system_property_read)(const prop_info*, char*, char*);
|
||||
static const prop_info *(*system_property_find)(const char*);
|
||||
static void (*system_property_read_callback)(
|
||||
const prop_info*, void (*)(void*, const char*, const char*, uint32_t), void*);
|
||||
static int (*system_property_foreach)(void (*)(const prop_info*, void*), void*);
|
||||
static bool (*system_property_wait)(const prop_info*, uint32_t, uint32_t*, const struct timespec*);
|
||||
#endif
|
||||
|
||||
struct PropFlags {
|
||||
void setSkipSvc() { flags |= 1; }
|
||||
void setPersist() { flags |= (1 << 1); }
|
||||
void setContext() { flags |= (1 << 2); }
|
||||
void setPersistOnly() { flags |= (1 << 3); setPersist(); }
|
||||
void setWait() { flags |= (1 << 4); }
|
||||
bool isSkipSvc() const { return flags & 1; }
|
||||
bool isPersist() const { return flags & (1 << 1); }
|
||||
bool isContext() const { return flags & (1 << 2); }
|
||||
bool isPersistOnly() const { return flags & (1 << 3); }
|
||||
bool isWait() const { return flags & (1 << 4); }
|
||||
private:
|
||||
uint32_t flags = 0;
|
||||
};
|
||||
|
||||
[[noreturn]] static void usage(char* arg0) {
|
||||
fprintf(stderr,
|
||||
R"EOF(resetprop - System Property Manipulation Tool
|
||||
|
||||
Usage: %s [flags] [arguments...]
|
||||
|
||||
Read mode arguments:
|
||||
(no arguments) print all properties
|
||||
NAME get property of NAME
|
||||
|
||||
Write mode arguments:
|
||||
NAME VALUE set property NAME as VALUE
|
||||
-f,--file FILE load and set properties from FILE
|
||||
-d,--delete NAME delete property
|
||||
|
||||
Wait mode arguments (toggled with -w):
|
||||
NAME wait until property NAME changes
|
||||
NAME OLD_VALUE if value of property NAME is not OLD_VALUE, get value
|
||||
or else wait until property NAME changes
|
||||
|
||||
General flags:
|
||||
-h,--help show this message
|
||||
-v print verbose output to stderr
|
||||
-w switch to wait mode
|
||||
|
||||
Read mode flags:
|
||||
-p also read persistent props from storage
|
||||
-P only read persistent props from storage
|
||||
-Z get property context instead of value
|
||||
|
||||
Write mode flags:
|
||||
-n set properties bypassing property_service
|
||||
-p always write persistent prop changes to storage
|
||||
|
||||
)EOF", arg0);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
static bool check_legal_property_name(const char *name) {
|
||||
int namelen = strlen(name);
|
||||
|
||||
if (namelen < 1) goto illegal;
|
||||
if (name[0] == '.') goto illegal;
|
||||
if (name[namelen - 1] == '.') goto illegal;
|
||||
|
||||
/* Only allow alphanumeric, plus '.', '-', '@', ':', or '_' */
|
||||
/* Don't allow ".." to appear in a property name */
|
||||
for (size_t i = 0; i < namelen; i++) {
|
||||
if (name[i] == '.') {
|
||||
// i=0 is guaranteed to never have a dot. See above.
|
||||
if (name[i-1] == '.') goto illegal;
|
||||
continue;
|
||||
}
|
||||
if (name[i] == '_' || name[i] == '-' || name[i] == '@' || name[i] == ':') continue;
|
||||
if (name[i] >= 'a' && name[i] <= 'z') continue;
|
||||
if (name[i] >= 'A' && name[i] <= 'Z') continue;
|
||||
if (name[i] >= '0' && name[i] <= '9') continue;
|
||||
goto illegal;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
illegal:
|
||||
LOGE("Illegal property name: [%s]\n", name);
|
||||
return false;
|
||||
}
|
||||
|
||||
void prop_callback::read(const prop_info *pi) {
|
||||
auto fn = [](void *cb, const char *name, const char *value, uint32_t serial) {
|
||||
static_cast<prop_callback*>(cb)->exec(name, value, serial);
|
||||
};
|
||||
system_property_read_callback(pi, fn, this);
|
||||
}
|
||||
|
||||
template<class StringType>
|
||||
struct prop_to_string : prop_callback {
|
||||
void exec(const char *, const char *value, uint32_t s) override {
|
||||
val = value;
|
||||
serial = s;
|
||||
}
|
||||
StringType val;
|
||||
uint32_t serial = 0;
|
||||
};
|
||||
|
||||
template<> void prop_to_string<rust::String>::exec(const char *, const char *value, uint32_t s) {
|
||||
// We do not want to crash when values are not UTF-8
|
||||
val = rust::String::lossy(value);
|
||||
serial = s;
|
||||
}
|
||||
|
||||
static bool str_starts(std::string_view s, std::string_view ss) {
|
||||
return s.starts_with(ss);
|
||||
}
|
||||
|
||||
static int set_prop(const char *name, const char *value, PropFlags flags) {
|
||||
if (!check_legal_property_name(name))
|
||||
return 1;
|
||||
|
||||
auto pi = const_cast<prop_info *>(__system_property_find(name));
|
||||
|
||||
// Delete existing read-only properties if they are or will be long properties,
|
||||
// which cannot directly go through __system_property_update
|
||||
if (str_starts(name, "ro.")) {
|
||||
if (pi != nullptr && (pi->is_long() || strlen(value) >= PROP_VALUE_MAX)) {
|
||||
// Skip pruning nodes as we will add it back ASAP
|
||||
__system_property_delete(name, false);
|
||||
pi = nullptr;
|
||||
}
|
||||
flags.setSkipSvc();
|
||||
}
|
||||
|
||||
const char *msg = flags.isSkipSvc() ? "direct modification" : "property_service";
|
||||
|
||||
int ret;
|
||||
if (pi != nullptr) {
|
||||
if (flags.isSkipSvc()) {
|
||||
ret = __system_property_update(pi, value, strlen(value));
|
||||
} else {
|
||||
ret = system_property_set(name, value);
|
||||
}
|
||||
LOGD("resetprop: update prop [%s]: [%s] by %s\n", name, value, msg);
|
||||
} else {
|
||||
if (flags.isSkipSvc()) {
|
||||
ret = __system_property_add(name, strlen(name), value, strlen(value));
|
||||
} else {
|
||||
ret = system_property_set(name, value);
|
||||
}
|
||||
LOGD("resetprop: create prop [%s]: [%s] by %s\n", name, value, msg);
|
||||
}
|
||||
|
||||
// When bypassing property_service, persistent props won't be stored in storage.
|
||||
// Explicitly handle this situation.
|
||||
if (ret == 0 && flags.isSkipSvc() && flags.isPersist() && str_starts(name, "persist.")) {
|
||||
ret = persist_set_prop(name, value) ? 0 : 1;
|
||||
}
|
||||
|
||||
if (ret) {
|
||||
LOGW("resetprop: set prop error\n");
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<class StringType>
|
||||
static StringType get_prop(const char *name, PropFlags flags) {
|
||||
if (!check_legal_property_name(name))
|
||||
return {};
|
||||
|
||||
prop_to_string<StringType> cb;
|
||||
|
||||
if (flags.isContext()) {
|
||||
auto context = __system_property_get_context(name) ?: "";
|
||||
LOGD("resetprop: prop context [%s]: [%s]\n", name, context);
|
||||
cb.exec(name, context, -1);
|
||||
return cb.val;
|
||||
}
|
||||
|
||||
if (!flags.isPersistOnly()) {
|
||||
if (auto pi = system_property_find(name)) {
|
||||
cb.read(pi);
|
||||
LOGD("resetprop: get prop [%s]: [%s]\n", name, cb.val.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
if (cb.val.empty() && flags.isPersist() && str_starts(name, "persist."))
|
||||
persist_get_prop(name, cb);
|
||||
if (cb.val.empty())
|
||||
LOGD("resetprop: prop [%s] does not exist\n", name);
|
||||
|
||||
return cb.val;
|
||||
}
|
||||
|
||||
template<class StringType>
|
||||
static StringType wait_prop(const char *name, const char *old_value) {
|
||||
if (!check_legal_property_name(name))
|
||||
return {};
|
||||
|
||||
const prop_info *pi;
|
||||
auto serial = __system_property_area_serial();
|
||||
while (!(pi = system_property_find(name))) {
|
||||
LOGD("resetprop: waiting for prop [%s] to exist\n", name);
|
||||
system_property_wait(nullptr, serial, &serial, nullptr);
|
||||
}
|
||||
|
||||
prop_to_string<StringType> cb;
|
||||
cb.read(pi);
|
||||
|
||||
while (old_value == nullptr || cb.val == old_value) {
|
||||
LOGD("resetprop: waiting for prop [%s]\n", name);
|
||||
uint32_t new_serial;
|
||||
system_property_wait(pi, cb.serial, &new_serial, nullptr);
|
||||
cb.read(pi);
|
||||
if (old_value == nullptr) break;
|
||||
}
|
||||
|
||||
LOGD("resetprop: get prop [%s]: [%s]\n", name, cb.val.c_str());
|
||||
return cb.val;
|
||||
}
|
||||
|
||||
struct prop_collector : prop_callback {
|
||||
void exec(const char *name, const char *value, uint32_t) override {
|
||||
list.insert({name, value});
|
||||
}
|
||||
map<string, string> list;
|
||||
};
|
||||
|
||||
static void print_props(PropFlags flags) {
|
||||
prop_collector collector;
|
||||
if (!flags.isPersistOnly()) {
|
||||
system_property_foreach([](const prop_info *pi, void *cb) {
|
||||
static_cast<prop_callback*>(cb)->read(pi);
|
||||
}, &collector);
|
||||
}
|
||||
if (flags.isPersist())
|
||||
persist_get_props(collector);
|
||||
for (auto &[key, val] : collector.list) {
|
||||
const char *v = flags.isContext() ?
|
||||
(__system_property_get_context(key.data()) ?: "") :
|
||||
val.data();
|
||||
printf("[%s]: [%s]\n", key.data(), v);
|
||||
}
|
||||
}
|
||||
|
||||
static int delete_prop(const char *name, PropFlags flags) {
|
||||
if (!check_legal_property_name(name))
|
||||
return 1;
|
||||
|
||||
LOGD("resetprop: delete prop [%s]\n", name);
|
||||
|
||||
int ret = __system_property_delete(name, true);
|
||||
if (flags.isPersist() && str_starts(name, "persist.")) {
|
||||
if (persist_delete_prop(name))
|
||||
ret = 0;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void load_file(const char *filename, PropFlags flags) {
|
||||
LOGD("resetprop: Parse prop file [%s]\n", filename);
|
||||
parse_prop_file(filename, [=](auto key, auto val) -> bool {
|
||||
set_prop(key.data(), val.data(), flags);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
struct Initialize {
|
||||
Initialize() {
|
||||
#ifndef APPLET_STUB_MAIN
|
||||
#define DLOAD(name) (*(void **) &name = dlsym(RTLD_DEFAULT, "__" #name))
|
||||
// Load platform implementations
|
||||
DLOAD(system_property_set);
|
||||
DLOAD(system_property_read);
|
||||
DLOAD(system_property_find);
|
||||
DLOAD(system_property_read_callback);
|
||||
DLOAD(system_property_foreach);
|
||||
DLOAD(system_property_wait);
|
||||
#undef DLOAD
|
||||
if (system_property_wait == nullptr) {
|
||||
// The platform API only exist on API 26+
|
||||
system_property_wait = __system_property_wait;
|
||||
}
|
||||
if (system_property_read_callback == nullptr) {
|
||||
// The platform API only exist on API 26+, create a polyfill
|
||||
system_property_read_callback = [](const prop_info *pi, auto fn, void *cookie) {
|
||||
char name[PROP_NAME_MAX];
|
||||
char value[PROP_VALUE_MAX];
|
||||
name[0] = '\0';
|
||||
value[0] = '\0';
|
||||
system_property_read(pi, name, value);
|
||||
fn(cookie, name, value, pi->serial);
|
||||
};
|
||||
}
|
||||
#endif
|
||||
if (__system_properties_init()) {
|
||||
LOGE("resetprop: __system_properties_init error\n");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static void InitOnce() {
|
||||
static Initialize init;
|
||||
}
|
||||
|
||||
#define consume_next(val) \
|
||||
if (argc != 2) usage(argv0); \
|
||||
val = argv[1]; \
|
||||
stop_parse = true; \
|
||||
|
||||
int resetprop_main(int argc, char *argv[]) {
|
||||
PropFlags flags;
|
||||
char *argv0 = argv[0];
|
||||
set_log_level_state(LogLevel::Debug, false);
|
||||
|
||||
const char *prop_file = nullptr;
|
||||
const char *prop_to_rm = nullptr;
|
||||
|
||||
--argc;
|
||||
++argv;
|
||||
|
||||
// Parse flags and -- options
|
||||
while (argc && argv[0][0] == '-') {
|
||||
bool stop_parse = false;
|
||||
for (int idx = 1; true; ++idx) {
|
||||
switch (argv[0][idx]) {
|
||||
case '-':
|
||||
if (argv[0] == "--file"sv) {
|
||||
consume_next(prop_file);
|
||||
} else if (argv[0] == "--delete"sv) {
|
||||
consume_next(prop_to_rm);
|
||||
} else {
|
||||
usage(argv0);
|
||||
}
|
||||
break;
|
||||
case 'd':
|
||||
consume_next(prop_to_rm);
|
||||
continue;
|
||||
case 'f':
|
||||
consume_next(prop_file);
|
||||
continue;
|
||||
case 'n':
|
||||
flags.setSkipSvc();
|
||||
continue;
|
||||
case 'p':
|
||||
flags.setPersist();
|
||||
continue;
|
||||
case 'P':
|
||||
flags.setPersistOnly();
|
||||
continue;
|
||||
case 'v':
|
||||
set_log_level_state(LogLevel::Debug, true);
|
||||
continue;
|
||||
case 'Z':
|
||||
flags.setContext();
|
||||
continue;
|
||||
case 'w':
|
||||
flags.setWait();
|
||||
continue;
|
||||
case '\0':
|
||||
break;
|
||||
default:
|
||||
usage(argv0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
--argc;
|
||||
++argv;
|
||||
if (stop_parse)
|
||||
break;
|
||||
}
|
||||
|
||||
InitOnce();
|
||||
|
||||
if (prop_to_rm) {
|
||||
return delete_prop(prop_to_rm, flags);
|
||||
}
|
||||
|
||||
if (prop_file) {
|
||||
load_file(prop_file, flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (flags.isWait()) {
|
||||
if (argc == 0) usage(argv0);
|
||||
auto val = wait_prop<string>(argv[0], argv[1]);
|
||||
if (val.empty())
|
||||
return 1;
|
||||
printf("%s\n", val.data());
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch (argc) {
|
||||
case 0:
|
||||
print_props(flags);
|
||||
return 0;
|
||||
case 1: {
|
||||
auto val = get_prop<string>(argv[0], flags);
|
||||
if (val.empty())
|
||||
return 1;
|
||||
printf("%s\n", val.data());
|
||||
return 0;
|
||||
}
|
||||
case 2:
|
||||
return set_prop(argv[0], argv[1], flags);
|
||||
default:
|
||||
usage(argv0);
|
||||
}
|
||||
}
|
||||
|
||||
/***************
|
||||
* Public APIs
|
||||
****************/
|
||||
|
||||
template<class StringType>
|
||||
static StringType get_prop_impl(const char *name, bool persist) {
|
||||
InitOnce();
|
||||
PropFlags flags;
|
||||
if (persist) flags.setPersist();
|
||||
return get_prop<StringType>(name, flags);
|
||||
}
|
||||
|
||||
rust::String get_prop_rs(Utf8CStr name, bool persist) {
|
||||
return get_prop_impl<rust::String>(name.data(), persist);
|
||||
}
|
||||
|
||||
string get_prop(const char *name, bool persist) {
|
||||
return get_prop_impl<string>(name, persist);
|
||||
}
|
||||
|
||||
int delete_prop(const char *name, bool persist) {
|
||||
InitOnce();
|
||||
PropFlags flags;
|
||||
if (persist) flags.setPersist();
|
||||
return delete_prop(name, flags);
|
||||
}
|
||||
|
||||
int set_prop(const char *name, const char *value, bool skip_svc) {
|
||||
InitOnce();
|
||||
PropFlags flags;
|
||||
if (skip_svc) flags.setSkipSvc();
|
||||
return set_prop(name, value, flags);
|
||||
}
|
||||
|
||||
void load_prop_file(const char *filename, bool skip_svc) {
|
||||
InitOnce();
|
||||
PropFlags flags;
|
||||
if (skip_svc) flags.setSkipSvc();
|
||||
load_file(filename, flags);
|
||||
}
|
||||
55
native/src/core/resetprop/sys.cpp
Normal file
55
native/src/core/resetprop/sys.cpp
Normal file
@@ -0,0 +1,55 @@
|
||||
#include <dlfcn.h>
|
||||
|
||||
#include <base.hpp>
|
||||
#include <core.hpp>
|
||||
|
||||
#include <api/system_properties.h>
|
||||
#include <system_properties/prop_info.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
// This has to keep in sync with SysProp in mod.rs
|
||||
struct SysProp {
|
||||
int (*set)(const char*, const char*);
|
||||
const prop_info *(*find)(const char*);
|
||||
void (*read_callback)(const prop_info*, void (*)(void*, const char*, const char*, uint32_t), void*);
|
||||
int (*foreach)(void (*)(const prop_info*, void*), void*);
|
||||
bool (*wait)(const prop_info*, uint32_t, uint32_t*, const timespec*);
|
||||
};
|
||||
|
||||
extern "C" bool prop_info_is_long(const prop_info &info) {
|
||||
return info.is_long();
|
||||
}
|
||||
|
||||
extern "C" SysProp get_sys_prop() {
|
||||
SysProp prop{};
|
||||
#ifdef APPLET_STUB_MAIN
|
||||
// Use internal implementation
|
||||
prop.set = __system_property_set;
|
||||
prop.find = __system_property_find;
|
||||
prop.read_callback = __system_property_read_callback;
|
||||
prop.foreach = __system_property_foreach;
|
||||
prop.wait = __system_property_wait;
|
||||
#else
|
||||
#define DLOAD(name) (*(void **) &prop.name = dlsym(RTLD_DEFAULT, "__system_property_" #name))
|
||||
// Dynamic load platform implementation
|
||||
DLOAD(set);
|
||||
DLOAD(find);
|
||||
DLOAD(read_callback);
|
||||
DLOAD(foreach);
|
||||
DLOAD(wait);
|
||||
#undef DLOAD
|
||||
if (prop.wait == nullptr) {
|
||||
// This platform API only exist on API 26+
|
||||
prop.wait = __system_property_wait;
|
||||
}
|
||||
if (prop.read_callback == nullptr) {
|
||||
// This platform API only exist on API 26+
|
||||
prop.read_callback = __system_property_read_callback;
|
||||
}
|
||||
#endif
|
||||
if (__system_properties_init()) {
|
||||
LOGE("resetprop: __system_properties_init error\n");
|
||||
}
|
||||
return prop;
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
use crate::consts::MODULEROOT;
|
||||
use crate::daemon::{MagiskD, to_user_id};
|
||||
use crate::ffi::{
|
||||
ZygiskRequest, ZygiskStateFlags, get_magisk_tmp, get_prop, set_prop, update_deny_flags,
|
||||
};
|
||||
use crate::ffi::{ZygiskRequest, ZygiskStateFlags, get_magisk_tmp, update_deny_flags};
|
||||
use crate::resetprop::{get_prop, set_prop};
|
||||
use crate::socket::{IpcRead, UnixSocketExt};
|
||||
use base::libc::{O_CLOEXEC, O_CREAT, O_RDONLY, STDOUT_FILENO};
|
||||
use base::{
|
||||
@@ -132,18 +131,18 @@ impl ZygiskState {
|
||||
if !self.lib_name.is_empty() {
|
||||
return;
|
||||
}
|
||||
let orig = get_prop(NBPROP, false);
|
||||
let orig = get_prop(NBPROP);
|
||||
self.lib_name = if orig.is_empty() || orig == "0" {
|
||||
ZYGISKLDR.to_string()
|
||||
} else {
|
||||
orig + ZYGISKLDR
|
||||
};
|
||||
set_prop(NBPROP, Utf8CStr::from_string(&mut self.lib_name), false);
|
||||
set_prop(NBPROP, Utf8CStr::from_string(&mut self.lib_name));
|
||||
// Whether Huawei's Maple compiler is enabled.
|
||||
// If so, system server will be created by a special Zygote which ignores the native bridge
|
||||
// and make system server out of our control. Avoid it by disabling.
|
||||
if get_prop(cstr!("ro.maple.enable"), false) == "1" {
|
||||
set_prop(cstr!("ro.maple.enable"), cstr!("0"), false);
|
||||
if get_prop(cstr!("ro.maple.enable")) == "1" {
|
||||
set_prop(cstr!("ro.maple.enable"), cstr!("0"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,7 +151,7 @@ impl ZygiskState {
|
||||
if self.lib_name.len() > ZYGISKLDR.len() {
|
||||
orig = self.lib_name[ZYGISKLDR.len()..].to_string();
|
||||
}
|
||||
set_prop(NBPROP, Utf8CStr::from_string(&mut orig), false);
|
||||
set_prop(NBPROP, Utf8CStr::from_string(&mut orig));
|
||||
self.lib_name.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#include <android/dlext.h>
|
||||
#include <dlfcn.h>
|
||||
|
||||
#include <consts.hpp>
|
||||
#include <base.hpp>
|
||||
#include <core.hpp>
|
||||
|
||||
|
||||
@@ -366,7 +366,7 @@ void HookContext::post_native_bridge_load(void *handle) {
|
||||
auto nb = get_prop(NBPROP);
|
||||
auto len = sizeof(ZYGISKLDR) - 1;
|
||||
if (nb.size() > len) {
|
||||
arg.load_native_bridge(nb.data() + len, arg.callbacks);
|
||||
arg.load_native_bridge(nb.c_str() + len, arg.callbacks);
|
||||
}
|
||||
runtime_callbacks = arg.callbacks;
|
||||
}
|
||||
|
||||
@@ -37,9 +37,3 @@ constexpr const char *applet_names[] = { "su", "resetprop", nullptr };
|
||||
|
||||
extern int SDK_INT;
|
||||
#define APP_DATA_DIR (SDK_INT >= 24 ? "/data/user_de" : "/data/user")
|
||||
|
||||
// Multi-call entrypoints
|
||||
int magisk_main(int argc, char *argv[]);
|
||||
int su_client_main(int argc, char *argv[]);
|
||||
int resetprop_main(int argc, char *argv[]);
|
||||
int zygisk_main(int argc, char *argv[]);
|
||||
|
||||
Reference in New Issue
Block a user