@@ -1,14 +1,17 @@
use anyhow ::{ anyhow , bail , Result } ;
use libc ::{ c_int , c_ulong , prctl } ;
use notify ::{ RecursiveMode , Watcher } ;
use std ::ffi ::{ CString , OsStr } ;
use std ::fs ;
use std ::os ::unix ::fs ::PermissionsExt ;
use std ::path ::{ Path , PathBuf } ;
use std ::ptr ;
use std ::{
ffi ::{ CStr , CString , OsStr } ,
path ::{ Path , PathBuf } ,
os ::unix ::fs ::PermissionsExt ,
fs ,
ptr ,
} ;
pub const KPM_DIR : & str = " /data/adb/kpm " ;
// SukiSU KPM prctl command space
const KSU_OPTIONS : c_int = 0xdeadbeef_ u32 as c_int ;
const SUKISU_KPM_LOAD : c_int = 28 ;
const SUKISU_KPM_UNLOAD : c_int = 29 ;
@@ -18,239 +21,224 @@ const SUKISU_KPM_INFO: c_int = 32;
const SUKISU_KPM_CONTROL :c_int = 33 ;
const SUKISU_KPM_VERSION :c_int = 34 ;
/// Convert raw kernel return code to `Result`.
#[ inline(always) ]
fn check_out ( out : c_int ) -> Result < c_int > {
if out < 0 {
bail! ( " KPM error: {} " , std ::io ::Error ::from_raw_os_error ( - out ) ) ;
fn check_out ( rc : c_int ) -> Result < c_int > {
if rc < 0 {
bail! ( " KPM error: {} " , std ::io ::Error ::from_raw_os_error ( - rc ) ) ;
}
Ok ( out )
Ok ( rc )
}
/// Load a `.kpm` into kernel space.
pub fn kpm_load ( path : & str , args : Option < & str > ) -> Result < ( ) > {
let path_c = CString ::new ( path ) ? ;
let args_c = args . map ( CString ::new ) . transpose ( ) ? ;
let mut out : c_int = - 1 ;
let mut rc : c_int = - 1 ;
// SAFETY: pointers live through the prctl; null-check done by CString.
unsafe {
prctl (
KSU_OPTIONS ,
SUKISU_KPM_LOAD ,
path_c . as_ptr ( ) as c_ulong ,
args_c . as_ref ( )
. map_or ( ptr ::null ( ) , | s | s . as_ptr ( ) ) as c_ulong ,
& mut out as * mut c_int as c_ulong ,
args_c . as_ref ( ) . map_or ( ptr ::null ( ) , | s | s . as_ptr ( ) ) as c_ulong ,
& mut rc as * mut c_int as c_ulong ,
) ;
}
check_out ( out ) ? ;
check_out ( rc ) ? ;
println! ( " Success " ) ;
Ok ( ( ) )
}
/// Unload by module name.
pub fn kpm_unload ( name : & str ) -> Result < ( ) > {
let name_c = CString ::new ( name ) ? ;
let mut out : c_int = - 1 ;
let mut rc = - 1 ;
unsafe {
prctl (
KSU_OPTIONS ,
SUKISU_KPM_UNLOAD ,
name_c . as_ptr ( ) as c_ulong ,
0 ,
& mut out as * mut c_int as c_ulong ,
) ;
prctl ( KSU_OPTIONS , SUKISU_KPM_UNLOAD , name_c . as_ptr ( ) as c_ulong , 0 , & mut rc as * mut _ as c_ulong ) ;
}
check_out ( out ) ? ;
check_out ( rc ) ? ;
Ok ( ( ) )
}
/// Return loaded module count.
pub fn kpm_num ( ) -> Result < i32 > {
let mut out : c_int = - 1 ;
unsafe {
prctl (
KSU_OPTIONS ,
SUKISU_KPM_NUM ,
0 ,
0 ,
& mut out as * mut c_int as c_ulong ,
) ;
}
let n = check_out ( out ) ? ;
println! ( " {} " , n ) ;
let mut rc = - 1 ;
unsafe { prctl ( KSU_OPTIONS , SUKISU_KPM_NUM , 0 , 0 , & mut rc as * mut _ as c_ulong ) } ;
let n = check_out ( rc ) ? ;
println! ( " {n} " ) ;
Ok ( n )
}
/// Print name list of loaded modules.
pub fn kpm_list ( ) -> Result < ( ) > {
let mut buf = vec! [ 0 u8 ; 1024 ] ;
let mut out : c_int = - 1 ;
let mut rc = - 1 ;
unsafe {
prctl (
KSU_OPTIONS ,
SUKISU_KPM_LIST ,
buf . as_mut_ptr ( ) as c_ulong ,
buf . len ( ) as c_ulong ,
& mut out as * mut c_int as c_ulong ,
& mut rc as * mut _ as c_ulong ,
) ;
}
check_out ( out ) ? ;
print! ( " {} " , buf2string ( & buf ) ) ;
check_out ( rc ) ? ;
print! ( " {} " , buf2str ( & buf ) ) ;
Ok ( ( ) )
}
/// Print single module info.
pub fn kpm_info ( name : & str ) -> Result < ( ) > {
let name_c = CString ::new ( name ) ? ;
let mut buf = vec! [ 0 u8 ; 256 ] ;
let mut out : c_int = - 1 ;
let mut rc = - 1 ;
unsafe {
prctl (
KSU_OPTIONS ,
SUKISU_KPM_INFO ,
name_c . as_ptr ( ) as c_ulong ,
buf . as_mut_ptr ( ) as c_ulong ,
& mut out as * mut c_int as c_ulong ,
& mut rc as * mut _ as c_ulong ,
) ;
}
check_out ( out ) ? ;
println! ( " {} " , buf2string ( & buf ) ) ;
check_out ( rc ) ? ;
println! ( " {} " , buf2str ( & buf ) ) ;
Ok ( ( ) )
}
/// Send control string to a module; returns kernel answer.
pub fn kpm_control ( name : & str , args : & str ) -> Result < i32 > {
let name_c = CString ::new ( name ) ? ;
let args_c = CString ::new ( args ) ? ;
let mut out : c_int = - 1 ;
let mut rc = - 1 ;
unsafe {
prctl (
KSU_OPTIONS ,
SUKISU_KPM_CONTROL ,
name_c . as_ptr ( ) as c_ulong ,
args_c . as_ptr ( ) as c_ulong ,
& mut out as * mut c_int as c_ulong ,
& mut rc as * mut _ as c_ulong ,
) ;
}
check_out ( out ) ? ;
Ok ( out )
check_out ( rc ) . map ( | v | v as i32 )
}
/// Print loader version string.
pub fn kpm_version_loader ( ) -> Result < ( ) > {
let mut buf = vec! [ 0 u8 ; 1024 ] ;
let mut out : c_int = - 1 ;
let mut rc = - 1 ;
unsafe {
prctl (
KSU_OPTIONS ,
SUKISU_KPM_VERSION ,
buf . as_mut_ptr ( ) as c_ulong ,
buf . len ( ) as c_ulong ,
& mut out as * mut c_int as c_ulong ,
& mut rc as * mut _ as c_ulong ,
) ;
}
check_out ( out ) ? ;
print! ( " {} " , buf2string ( & buf ) ) ;
check_out ( rc ) ? ;
print! ( " {} " , buf2str ( & buf ) ) ;
Ok ( ( ) )
}
/// Validate loader version; empty or "Error*" => fail.
pub fn check_kpm_version ( ) -> Result < String > {
let mut buf = vec! [ 0 u8 ; 1024 ] ;
let mut out : c_int = - 1 ;
let mut rc = - 1 ;
unsafe {
prctl (
KSU_OPTIONS ,
SUKISU_KPM_VERSION ,
buf . as_mut_ptr ( ) as c_ulong ,
buf . len ( ) as c_ulong ,
& mut out as * mut c_int as c_ulong ,
& mut rc as * mut _ as c_ulong ,
) ;
}
check_out ( out ) ? ;
let ver = buf2string ( & buf ) ;
check_out ( rc ) ? ;
let ver = buf2str ( & buf ) ;
if ver . is_empty ( ) | | ver . starts_with ( " Error " ) {
bail! ( " KPM: I nvalid version response: {} " , ver );
bail! ( " KPM: i nvalid version response: {ver } " ) ;
}
log ::info! ( " KPM: V ersion check result: {} " , ver ) ;
log ::info! ( " KPM: v ersion check ok: {ver} " ) ;
Ok ( ver )
}
/// Create `/data/adb/kpm` with 0o777 if missing.
pub fn ensure_kpm_dir ( ) -> Result < ( ) > {
let path = Path ::new ( KPM_DIR ) ;
if ! path . exists ( ) {
fs ::create_dir_all ( path ) ? ;
}
let meta = fs ::metadata ( path ) ? ;
fs ::create_dir_all ( KPM_DIR ) ? ;
let meta = fs ::metadata ( KPM_DIR ) ? ;
if meta . permissions ( ) . mode ( ) & 0o777 ! = 0o777 {
fs ::set_permissions ( path , fs ::Permissions ::from_mode ( 0o777 ) ) ? ;
fs ::set_permissions ( KPM_DIR , fs ::Permissions ::from_mode ( 0o777 ) ) ? ;
}
Ok ( ( ) )
}
/// Start file watcher for hot-(un)load.
pub fn start_kpm_watcher ( ) -> Result < ( ) > {
check_kpm_version ( ) ? ; // 版本不对直接返回
check_kpm_version ( ) ? ; // bails if loader too old
ensure_kpm_dir ( ) ? ;
if crate ::utils ::is_safe_mode ( ) {
log ::warn! ( " KPM: S afe mode – removing all KPM modules " ) ;
log ::warn! ( " KPM: s afe- mode – removing all modules " ) ;
remove_all_kpms ( ) ? ;
return Ok ( ( ) ) ;
}
let mut watcher = notify ::recommended_watcher ( | res | match res {
Ok ( even t ) = > handle_kpm_event ( even t ) ,
Err ( e ) = > log ::error! ( " KPM: File monito r error: {:?} " , e ),
let mut watcher = notify ::recommended_watcher ( | res : Result < _ , _ > | match res {
Ok ( evt ) = > handle_kpm_event ( evt ) ,
Err ( e ) = > log ::error! ( " KPM: watche r error: {e :?} " ) ,
} ) ? ;
watcher . watch ( Path ::new ( KPM_DIR ) , RecursiveMode ::NonRecursive ) ? ;
log ::info! ( " KPM: File watcher started on {} " , KPM_DIR ) ;
log ::info! ( " KPM: watcher active on { KPM_DIR} " ) ;
Ok ( ( ) )
}
fn handle_kpm_event ( even t : notify ::Event ) {
match even t. kind {
notify ::EventKind ::Create ( _ ) = > {
for p in event . paths {
if p . extension ( ) = = Some ( OsStr ::new ( " kpm " ) ) {
if let Err ( e ) = load_kpm ( & p ) {
log ::warn! ( " KPM: Failed to load {}: {} " , p . display ( ) , e ) ;
fn handle_kpm_event ( evt : notify ::Event ) {
if let notify ::EventKind ::Create ( _ ) = ev t. kind {
for p in evt . paths {
i f p . extension ( ) = = Some ( OsStr ::new ( " kpm " ) ) & & load_kpm ( & p ) . is_err ( ) {
log ::warn! ( " KPM: failed to load {} " , p . display ( ) ) ;
}
}
}
}
notify ::EventKind ::Modify ( _ ) = > {
for p in event . paths {
log ::info! ( " KPM: Modified file: {} " , p . display ( ) ) ;
}
}
_ = > { }
}
}
/// Load single `.kpm` file.
pub fn load_kpm ( path : & Path ) -> Result < ( ) > {
let path_str = path . to_str ( ) . ok_or_else ( | | anyhow! ( " Invali d path" ) ) ? ;
kpm_load ( path_str , None )
let s = path . to_str ( ) . ok_or_else ( | | anyhow! ( " ba d path" ) ) ? ;
kpm_load ( s , None )
}
/// Unload module and delete file.
pub fn unload_kpm ( name : & str ) -> Result < ( ) > {
kpm_unload ( name ) ? ;
if let Some ( p ) = find_kpm_file ( name ) ? {
fs ::remove_file ( & p ) . ok ( ) ;
log ::info! ( " KPM: D eleted file {} " , p . display ( ) ) ;
let _ = fs ::remove_file ( & p ) ;
log ::info! ( " KPM: d eleted {} " , p . display ( ) ) ;
}
Ok ( ( ) )
}
/// Locate `/data/adb/kpm/<name>.kpm`.
fn find_kpm_file ( name : & str ) -> Result < Option < PathBuf > > {
let dir = Path ::new ( KPM_DIR ) ;
if ! dir . exists ( ) {
if ! dir . is_dir ( ) {
return Ok ( None ) ;
}
for entry in fs ::read_dir ( dir ) ? {
let p = entry ? . path ( ) ;
if p . extension ( ) = = Some ( OsStr ::new ( " kpm " ) )
& & p . file_stem ( ) . map_or ( false , | s | s = = name )
{
if p . extension ( ) = = Some ( OsStr ::new ( " kpm " ) ) & & p . file_stem ( ) = = Some ( OsStr ::new ( name ) ) {
return Ok ( Some ( p ) ) ;
}
}
Ok ( None )
}
/// Remove every `.kpm` file and unload it.
pub fn remove_all_kpms ( ) -> Result < ( ) > {
let dir = Path ::new ( KPM_DIR ) ;
if ! dir . exists ( ) {
if ! dir . is_dir ( ) {
return Ok ( ( ) ) ;
}
for entry in fs ::read_dir ( dir ) ? {
@@ -258,7 +246,7 @@ pub fn remove_all_kpms() -> Result<()> {
if p . extension ( ) = = Some ( OsStr ::new ( " kpm " ) ) {
if let Some ( name ) = p . file_stem ( ) . and_then ( | s | s . to_str ( ) ) {
if let Err ( e ) = unload_kpm ( name ) {
log ::error! ( " KPM: Failed to unload {} : {} " , name , e );
log ::error! ( " KPM: unload {name} failed : {e } " ) ;
}
}
}
@@ -266,11 +254,12 @@ pub fn remove_all_kpms() -> Result<()> {
Ok ( ( ) )
}
/// Bulk-load existing `.kpm`s at boot.
pub fn load_kpm_modules ( ) -> Result < ( ) > {
check_kpm_version ( ) ? ;
ensure_kpm_dir ( ) ? ;
let dir = Path ::new ( KPM_DIR ) ;
if ! dir . exists ( ) {
if ! dir . is_dir ( ) {
return Ok ( ( ) ) ;
}
let ( mut ok , mut ng ) = ( 0 , 0 ) ;
@@ -280,20 +269,18 @@ pub fn load_kpm_modules() -> Result<()> {
match load_kpm ( & p ) {
Ok ( _ ) = > ok + = 1 ,
Err ( e ) = > {
log ::warn! ( " KPM: Failed to load {}: {} " , p . display ( ) , e );
log ::warn! ( " KPM: load {} failed : {e } " , p . display ( ) ) ;
ng + = 1 ;
}
}
}
}
log ::info! ( " KPM: L oad done – ok: {}, failed: {} " , ok , ng );
log ::info! ( " KPM: bulk-l oad done – ok: {ok }, failed: {ng } " ) ;
Ok ( ( ) )
}
fn buf2string ( buf : & [ u8 ] ) -> String {
unsafe {
std ::ffi ::CStr ::from_ptr ( buf . as_ptr ( ) as * const _ )
. to_string_lossy ( )
. into_owned ( )
}
/// Convert zero-padded kernel buffer to owned String.
fn buf2str ( buf : & [ u8 ] ) -> String {
// SAFETY: buffer is always NUL-terminated by kernel.
unsafe { CStr ::from_ptr ( buf . as_ptr ( ) . cast ( ) ) . to_string_lossy ( ) . into_owned ( ) }
}