Migrate resetprop to Rust

This commit is contained in:
topjohnwu
2025-08-27 14:03:04 -07:00
committed by John Wu
parent 42d9f87bc9
commit c4e2985677
21 changed files with 679 additions and 656 deletions

View File

@@ -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
View File

@@ -601,6 +601,7 @@ dependencies = [
name = "magisk"
version = "0.0.0"
dependencies = [
"argh",
"base",
"bit-set",
"bytemuck",

View File

@@ -25,3 +25,4 @@ quick-protobuf = { workspace = true }
bytemuck = { workspace = true, features = ["derive"] }
thiserror = { workspace = true }
bit-set = { workspace = true }
argh = { workspace = true }

View File

@@ -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)

View File

@@ -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;

View File

@@ -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

View File

@@ -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,

View File

@@ -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);
}

View File

@@ -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();
}

View File

@@ -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());
}
}
{

View File

@@ -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

View File

@@ -1,4 +0,0 @@
pub use persist::{persist_delete_prop, persist_get_prop, persist_get_props, persist_set_prop};
mod persist;
mod proto;

View 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()
}

View 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() }
}
}

View File

@@ -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(())
}

View File

@@ -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);
}

View 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;
}

View File

@@ -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();
}
}

View File

@@ -2,7 +2,6 @@
#include <android/dlext.h>
#include <dlfcn.h>
#include <consts.hpp>
#include <base.hpp>
#include <core.hpp>

View File

@@ -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;
}

View File

@@ -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[]);