This idea is borrowed from simonpunk's susfs4ksu. What we see here is that, yeah well, lets just have userspace send us what it wants unmounted, this is better than hardcoding everything. This also solves that issue where MNT_DETACH fails, as long as we send unmountables in proper order. A small anti-duplicate mechanism is also added. While in-kernel umount is a bit worse than zygisk-provider-based ones, this can still serve as a healthy alternative. --------- - Remove duplicate checks Signed-off-by: backslashxx <118538522+backslashxx@users.noreply.github.com> Co-authored-by: weishu <twsxtd@gmail.com> Co-authored-by: ShirkNeko <109797057+ShirkNeko@users.noreply.github.com>
686 lines
19 KiB
Rust
686 lines
19 KiB
Rust
use anyhow::{Ok, Result};
|
|
use clap::Parser;
|
|
use std::path::{Path, PathBuf};
|
|
|
|
#[cfg(target_os = "android")]
|
|
use android_logger::Config;
|
|
#[cfg(target_os = "android")]
|
|
use log::LevelFilter;
|
|
|
|
use crate::{
|
|
apk_sign, assets, debug, defs, defs::KSUD_VERBOSE_LOG_FILE, init_event, ksucalls, module, utils,
|
|
};
|
|
|
|
/// KernelSU userspace cli
|
|
#[derive(Parser, Debug)]
|
|
#[command(author, version = defs::VERSION_NAME, about, long_about = None)]
|
|
struct Args {
|
|
#[command(subcommand)]
|
|
command: Commands,
|
|
|
|
#[arg(short, long, default_value_t = cfg!(debug_assertions))]
|
|
verbose: bool,
|
|
}
|
|
|
|
#[derive(clap::Subcommand, Debug)]
|
|
enum Commands {
|
|
/// Manage KernelSU modules
|
|
Module {
|
|
#[command(subcommand)]
|
|
command: Module,
|
|
},
|
|
|
|
/// Trigger `post-fs-data` event
|
|
PostFsData,
|
|
|
|
/// Trigger `service` event
|
|
Services,
|
|
|
|
/// Trigger `boot-complete` event
|
|
BootCompleted,
|
|
|
|
/// Install KernelSU userspace component to system
|
|
Install {
|
|
#[arg(long, default_value = None)]
|
|
magiskboot: Option<PathBuf>,
|
|
},
|
|
|
|
/// Uninstall KernelSU modules and itself(LKM Only)
|
|
Uninstall {
|
|
/// magiskboot path, if not specified, will search from $PATH
|
|
#[arg(long, default_value = None)]
|
|
magiskboot: Option<PathBuf>,
|
|
},
|
|
|
|
/// SELinux policy Patch tool
|
|
Sepolicy {
|
|
#[command(subcommand)]
|
|
command: Sepolicy,
|
|
},
|
|
|
|
/// Manage App Profiles
|
|
Profile {
|
|
#[command(subcommand)]
|
|
command: Profile,
|
|
},
|
|
|
|
/// Manage kernel features
|
|
Feature {
|
|
#[command(subcommand)]
|
|
command: Feature,
|
|
},
|
|
|
|
/// Patch boot or init_boot images to apply KernelSU
|
|
BootPatch {
|
|
/// boot image path, if not specified, will try to find the boot image automatically
|
|
#[arg(short, long)]
|
|
boot: Option<PathBuf>,
|
|
|
|
/// kernel image path to replace
|
|
#[arg(short, long)]
|
|
kernel: Option<PathBuf>,
|
|
|
|
/// LKM module path to replace, if not specified, will use the builtin one
|
|
#[arg(short, long)]
|
|
module: Option<PathBuf>,
|
|
|
|
/// init to be replaced
|
|
#[arg(short, long, requires("module"))]
|
|
init: Option<PathBuf>,
|
|
|
|
/// will use another slot when boot image is not specified
|
|
#[arg(short = 'u', long, default_value = "false")]
|
|
ota: bool,
|
|
|
|
/// Flash it to boot partition after patch
|
|
#[arg(short, long, default_value = "false")]
|
|
flash: bool,
|
|
|
|
/// output path, if not specified, will use current directory
|
|
#[arg(short, long, default_value = None)]
|
|
out: Option<PathBuf>,
|
|
|
|
/// magiskboot path, if not specified, will search from $PATH
|
|
#[arg(long, default_value = None)]
|
|
magiskboot: Option<PathBuf>,
|
|
|
|
/// KMI version, if specified, will use the specified KMI
|
|
#[arg(long, default_value = None)]
|
|
kmi: Option<String>,
|
|
|
|
/// target partition override (init_boot | boot | vendor_boot)
|
|
#[arg(long, default_value = None)]
|
|
partition: Option<String>,
|
|
},
|
|
|
|
/// Restore boot or init_boot images patched by KernelSU
|
|
BootRestore {
|
|
/// boot image path, if not specified, will try to find the boot image automatically
|
|
#[arg(short, long)]
|
|
boot: Option<PathBuf>,
|
|
|
|
/// Flash it to boot partition after patch
|
|
#[arg(short, long, default_value = "false")]
|
|
flash: bool,
|
|
|
|
/// magiskboot path, if not specified, will search from $PATH
|
|
#[arg(long, default_value = None)]
|
|
magiskboot: Option<PathBuf>,
|
|
},
|
|
|
|
/// Show boot information
|
|
BootInfo {
|
|
#[command(subcommand)]
|
|
command: BootInfo,
|
|
},
|
|
|
|
/// KPM module manager
|
|
#[cfg(target_arch = "aarch64")]
|
|
Kpm {
|
|
#[command(subcommand)]
|
|
command: kpm_cmd::Kpm,
|
|
},
|
|
|
|
/// Manage kernel umount paths
|
|
Umount {
|
|
#[command(subcommand)]
|
|
command: Umount,
|
|
},
|
|
|
|
/// For developers
|
|
Debug {
|
|
#[command(subcommand)]
|
|
command: Debug,
|
|
},
|
|
/// Kernel interface
|
|
Kernel {
|
|
#[command(subcommand)]
|
|
command: Kernel,
|
|
},
|
|
}
|
|
|
|
#[derive(clap::Subcommand, Debug)]
|
|
enum BootInfo {
|
|
/// show current kmi version
|
|
CurrentKmi,
|
|
|
|
/// show supported kmi versions
|
|
SupportedKmis,
|
|
|
|
/// check if device is A/B capable
|
|
IsAbDevice,
|
|
|
|
/// show auto-selected boot partition name
|
|
DefaultPartition,
|
|
|
|
/// list available partitions for current or OTA toggled slot
|
|
AvailablePartitions,
|
|
|
|
/// show slot suffix for current or OTA toggled slot
|
|
SlotSuffix {
|
|
/// toggle to another slot
|
|
#[arg(short = 'u', long, default_value = "false")]
|
|
ota: bool,
|
|
},
|
|
}
|
|
|
|
#[derive(clap::Subcommand, Debug)]
|
|
enum Debug {
|
|
/// Set the manager app, kernel CONFIG_KSU_DEBUG should be enabled.
|
|
SetManager {
|
|
/// manager package name
|
|
#[arg(default_value_t = String::from("me.weishu.kernelsu"))]
|
|
apk: String,
|
|
},
|
|
|
|
/// Get apk size and hash
|
|
GetSign {
|
|
/// apk path
|
|
apk: String,
|
|
},
|
|
|
|
/// Root Shell
|
|
Su {
|
|
/// switch to gloabl mount namespace
|
|
#[arg(short, long, default_value = "false")]
|
|
global_mnt: bool,
|
|
},
|
|
|
|
/// Get kernel version
|
|
Version,
|
|
|
|
Mount,
|
|
|
|
/// For testing
|
|
Test,
|
|
|
|
/// Process mark management
|
|
Mark {
|
|
#[command(subcommand)]
|
|
command: MarkCommand,
|
|
},
|
|
}
|
|
|
|
#[derive(clap::Subcommand, Debug)]
|
|
enum MarkCommand {
|
|
/// Get mark status for a process (or all)
|
|
Get {
|
|
/// target pid (0 for total count)
|
|
#[arg(default_value = "0")]
|
|
pid: i32,
|
|
},
|
|
|
|
/// Mark a process
|
|
Mark {
|
|
/// target pid (0 for all processes)
|
|
#[arg(default_value = "0")]
|
|
pid: i32,
|
|
},
|
|
|
|
/// Unmark a process
|
|
Unmark {
|
|
/// target pid (0 for all processes)
|
|
#[arg(default_value = "0")]
|
|
pid: i32,
|
|
},
|
|
|
|
/// Refresh mark for all running processes
|
|
Refresh,
|
|
}
|
|
|
|
#[derive(clap::Subcommand, Debug)]
|
|
enum Sepolicy {
|
|
/// Patch sepolicy
|
|
Patch {
|
|
/// sepolicy statements
|
|
sepolicy: String,
|
|
},
|
|
|
|
/// Apply sepolicy from file
|
|
Apply {
|
|
/// sepolicy file path
|
|
file: String,
|
|
},
|
|
|
|
/// Check if sepolicy statement is supported/valid
|
|
Check {
|
|
/// sepolicy statements
|
|
sepolicy: String,
|
|
},
|
|
}
|
|
|
|
#[derive(clap::Subcommand, Debug)]
|
|
enum Module {
|
|
/// Install module <ZIP>
|
|
Install {
|
|
/// module zip file path
|
|
zip: String,
|
|
},
|
|
|
|
/// Uninstall module <id>
|
|
Uninstall {
|
|
/// module id
|
|
id: String,
|
|
},
|
|
|
|
/// Restore module <id>
|
|
Restore {
|
|
/// module id
|
|
id: String,
|
|
},
|
|
|
|
/// enable module <id>
|
|
Enable {
|
|
/// module id
|
|
id: String,
|
|
},
|
|
|
|
/// disable module <id>
|
|
Disable {
|
|
// module id
|
|
id: String,
|
|
},
|
|
|
|
/// run action for module <id>
|
|
Action {
|
|
// module id
|
|
id: String,
|
|
},
|
|
|
|
/// list all modules
|
|
List,
|
|
}
|
|
|
|
#[derive(clap::Subcommand, Debug)]
|
|
enum Profile {
|
|
/// get root profile's selinux policy of <package-name>
|
|
GetSepolicy {
|
|
/// package name
|
|
package: String,
|
|
},
|
|
|
|
/// set root profile's selinux policy of <package-name> to <profile>
|
|
SetSepolicy {
|
|
/// package name
|
|
package: String,
|
|
/// policy statements
|
|
policy: String,
|
|
},
|
|
|
|
/// get template of <id>
|
|
GetTemplate {
|
|
/// template id
|
|
id: String,
|
|
},
|
|
|
|
/// set template of <id> to <template string>
|
|
SetTemplate {
|
|
/// template id
|
|
id: String,
|
|
/// template string
|
|
template: String,
|
|
},
|
|
|
|
/// delete template of <id>
|
|
DeleteTemplate {
|
|
/// template id
|
|
id: String,
|
|
},
|
|
|
|
/// list all templates
|
|
ListTemplates,
|
|
}
|
|
|
|
#[derive(clap::Subcommand, Debug)]
|
|
enum Feature {
|
|
/// Get feature value and support status
|
|
Get {
|
|
/// Feature ID or name (su_compat, kernel_umount)
|
|
id: String,
|
|
},
|
|
|
|
/// Set feature value
|
|
Set {
|
|
/// Feature ID or name
|
|
id: String,
|
|
/// Feature value (0=disable, 1=enable)
|
|
value: u64,
|
|
},
|
|
|
|
/// List all available features
|
|
List,
|
|
|
|
/// Check feature status (supported/unsupported/managed)
|
|
Check {
|
|
/// Feature ID or name (su_compat, kernel_umount)
|
|
id: String,
|
|
},
|
|
|
|
/// Load configuration from file and apply to kernel
|
|
Load,
|
|
|
|
/// Save current kernel feature states to file
|
|
Save,
|
|
}
|
|
|
|
#[derive(clap::Subcommand, Debug)]
|
|
enum Kernel {
|
|
/// Nuke ext4 sysfs
|
|
NukeExt4Sysfs {
|
|
/// mount point
|
|
mnt: String,
|
|
},
|
|
/// Manage umount list
|
|
Umount {
|
|
#[command(subcommand)]
|
|
command: UmountOp,
|
|
},
|
|
/// Notify that module is mounted
|
|
NotifyModuleMounted,
|
|
}
|
|
|
|
#[derive(clap::Subcommand, Debug)]
|
|
enum UmountOp {
|
|
/// Add mount point to umount list
|
|
Add {
|
|
/// mount point path
|
|
mnt: String,
|
|
/// umount flags (default: 0, MNT_DETACH: 2)
|
|
#[arg(short, long, default_value = "0")]
|
|
flags: u32,
|
|
},
|
|
/// Delete mount point from umount list
|
|
Del {
|
|
/// mount point path
|
|
mnt: String,
|
|
},
|
|
/// Wipe all entries from umount list
|
|
Wipe,
|
|
}
|
|
|
|
#[cfg(target_arch = "aarch64")]
|
|
mod kpm_cmd {
|
|
use clap::Subcommand;
|
|
use std::path::PathBuf;
|
|
|
|
#[derive(Subcommand, Debug)]
|
|
pub enum Kpm {
|
|
/// Load a KPM module: load <path> [args]
|
|
Load { path: PathBuf, args: Option<String> },
|
|
/// Unload a KPM module: unload <name>
|
|
Unload { name: String },
|
|
/// Get number of loaded modules
|
|
Num,
|
|
/// List loaded KPM modules
|
|
List,
|
|
/// Get info of a KPM module: info <name>
|
|
Info { name: String },
|
|
/// Send control command to a KPM module: control <name> <args>
|
|
Control { name: String, args: String },
|
|
/// Print KPM Loader version
|
|
Version,
|
|
}
|
|
}
|
|
|
|
#[derive(clap::Subcommand, Debug)]
|
|
enum Umount {
|
|
/// Add custom umount path
|
|
Add {
|
|
/// Mount path to add
|
|
path: String,
|
|
|
|
/// Check mount type (overlay)
|
|
#[arg(long, default_value = "false")]
|
|
|
|
/// Umount flags (0 or 8 for MNT_DETACH)
|
|
#[arg(long, default_value = "-1")]
|
|
flags: i32,
|
|
},
|
|
|
|
/// Remove custom umount path
|
|
Remove {
|
|
/// Mount path to remove
|
|
path: String,
|
|
},
|
|
|
|
/// List all umount paths
|
|
List,
|
|
|
|
/// Clear all custom paths (keep defaults)
|
|
ClearCustom,
|
|
|
|
/// Save configuration to file
|
|
Save,
|
|
|
|
/// Load and apply configuration from file
|
|
Load,
|
|
|
|
/// Apply current configuration to kernel
|
|
Apply,
|
|
}
|
|
|
|
pub fn run() -> Result<()> {
|
|
#[cfg(target_os = "android")]
|
|
android_logger::init_once(
|
|
Config::default()
|
|
.with_max_level(LevelFilter::Trace) // limit log level
|
|
.with_tag("KernelSU"), // logs will show under mytag tag
|
|
);
|
|
|
|
#[cfg(not(target_os = "android"))]
|
|
env_logger::init();
|
|
|
|
// the kernel executes su with argv[0] = "su" and replace it with us
|
|
let arg0 = std::env::args().next().unwrap_or_default();
|
|
if arg0 == "su" || arg0 == "/system/bin/su" {
|
|
return crate::su::root_shell();
|
|
}
|
|
|
|
let cli = Args::parse();
|
|
|
|
if !cli.verbose && !Path::new(KSUD_VERBOSE_LOG_FILE).exists() {
|
|
log::set_max_level(LevelFilter::Info);
|
|
}
|
|
|
|
log::info!("command: {:?}", cli.command);
|
|
|
|
let result = match cli.command {
|
|
Commands::PostFsData => init_event::on_post_data_fs(),
|
|
Commands::BootCompleted => init_event::on_boot_completed(),
|
|
|
|
Commands::Module { command } => {
|
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
|
{
|
|
utils::switch_mnt_ns(1)?;
|
|
}
|
|
match command {
|
|
Module::Install { zip } => module::install_module(&zip),
|
|
Module::Uninstall { id } => module::uninstall_module(&id),
|
|
Module::Restore { id } => module::restore_uninstall_module(&id),
|
|
Module::Enable { id } => module::enable_module(&id),
|
|
Module::Disable { id } => module::disable_module(&id),
|
|
Module::Action { id } => module::run_action(&id),
|
|
Module::List => module::list_modules(),
|
|
}
|
|
}
|
|
Commands::Install { magiskboot } => utils::install(magiskboot),
|
|
Commands::Uninstall { magiskboot } => utils::uninstall(magiskboot),
|
|
Commands::Sepolicy { command } => match command {
|
|
Sepolicy::Patch { sepolicy } => crate::sepolicy::live_patch(&sepolicy),
|
|
Sepolicy::Apply { file } => crate::sepolicy::apply_file(file),
|
|
Sepolicy::Check { sepolicy } => crate::sepolicy::check_rule(&sepolicy),
|
|
},
|
|
Commands::Services => init_event::on_services(),
|
|
Commands::Profile { command } => match command {
|
|
Profile::GetSepolicy { package } => crate::profile::get_sepolicy(package),
|
|
Profile::SetSepolicy { package, policy } => {
|
|
crate::profile::set_sepolicy(package, policy)
|
|
}
|
|
Profile::GetTemplate { id } => crate::profile::get_template(id),
|
|
Profile::SetTemplate { id, template } => crate::profile::set_template(id, template),
|
|
Profile::DeleteTemplate { id } => crate::profile::delete_template(id),
|
|
Profile::ListTemplates => crate::profile::list_templates(),
|
|
},
|
|
|
|
Commands::Feature { command } => match command {
|
|
Feature::Get { id } => crate::feature::get_feature(id),
|
|
Feature::Set { id, value } => crate::feature::set_feature(id, value),
|
|
Feature::List => crate::feature::list_features(),
|
|
Feature::Check { id } => crate::feature::check_feature(id),
|
|
Feature::Load => crate::feature::load_config_and_apply(),
|
|
Feature::Save => crate::feature::save_config(),
|
|
},
|
|
|
|
Commands::Debug { command } => match command {
|
|
Debug::SetManager { apk } => debug::set_manager(&apk),
|
|
Debug::GetSign { apk } => {
|
|
let sign = apk_sign::get_apk_signature(&apk)?;
|
|
println!("size: {:#x}, hash: {}", sign.0, sign.1);
|
|
Ok(())
|
|
}
|
|
Debug::Version => {
|
|
println!("Kernel Version: {}", ksucalls::get_version());
|
|
Ok(())
|
|
}
|
|
Debug::Su { global_mnt } => crate::su::grant_root(global_mnt),
|
|
Debug::Mount => init_event::mount_modules_systemlessly(),
|
|
Debug::Test => assets::ensure_binaries(false),
|
|
Debug::Mark { command } => match command {
|
|
MarkCommand::Get { pid } => debug::mark_get(pid),
|
|
MarkCommand::Mark { pid } => debug::mark_set(pid),
|
|
MarkCommand::Unmark { pid } => debug::mark_unset(pid),
|
|
MarkCommand::Refresh => debug::mark_refresh(),
|
|
},
|
|
},
|
|
|
|
Commands::BootPatch {
|
|
boot,
|
|
init,
|
|
kernel,
|
|
module,
|
|
ota,
|
|
flash,
|
|
out,
|
|
magiskboot,
|
|
kmi,
|
|
partition,
|
|
} => crate::boot_patch::patch(
|
|
boot, kernel, module, init, ota, flash, out, magiskboot, kmi, partition,
|
|
),
|
|
|
|
Commands::BootInfo { command } => match command {
|
|
BootInfo::CurrentKmi => {
|
|
let kmi = crate::boot_patch::get_current_kmi()?;
|
|
println!("{kmi}");
|
|
// return here to avoid printing the error message
|
|
return Ok(());
|
|
}
|
|
BootInfo::SupportedKmis => {
|
|
let kmi = crate::assets::list_supported_kmi()?;
|
|
kmi.iter().for_each(|kmi| println!("{kmi}"));
|
|
return Ok(());
|
|
}
|
|
BootInfo::IsAbDevice => {
|
|
let val = crate::utils::getprop("ro.build.ab_update")
|
|
.unwrap_or_else(|| String::from("false"));
|
|
let is_ab = val.trim().to_lowercase() == "true";
|
|
println!("{}", if is_ab { "true" } else { "false" });
|
|
return Ok(());
|
|
}
|
|
BootInfo::DefaultPartition => {
|
|
let kmi = crate::boot_patch::get_current_kmi().unwrap_or_else(|_| String::from(""));
|
|
let name = crate::boot_patch::choose_boot_partition(&kmi, false, &None);
|
|
println!("{name}");
|
|
return Ok(());
|
|
}
|
|
BootInfo::SlotSuffix { ota } => {
|
|
let suffix = crate::boot_patch::get_slot_suffix(ota);
|
|
println!("{suffix}");
|
|
return Ok(());
|
|
}
|
|
BootInfo::AvailablePartitions => {
|
|
let parts = crate::boot_patch::list_available_partitions();
|
|
parts.iter().for_each(|p| println!("{p}"));
|
|
return Ok(());
|
|
}
|
|
},
|
|
Commands::BootRestore {
|
|
boot,
|
|
magiskboot,
|
|
flash,
|
|
} => crate::boot_patch::restore(boot, magiskboot, flash),
|
|
Commands::Kernel { command } => match command {
|
|
Kernel::NukeExt4Sysfs { mnt } => ksucalls::nuke_ext4_sysfs(&mnt),
|
|
Kernel::Umount { command } => match command {
|
|
UmountOp::Add { mnt, flags } => ksucalls::umount_list_add(&mnt, flags),
|
|
UmountOp::Del { mnt } => ksucalls::umount_list_del(&mnt),
|
|
UmountOp::Wipe => ksucalls::umount_list_wipe().map_err(Into::into),
|
|
},
|
|
Kernel::NotifyModuleMounted => {
|
|
ksucalls::report_module_mounted();
|
|
Ok(())
|
|
}
|
|
},
|
|
#[cfg(target_arch = "aarch64")]
|
|
Commands::Kpm { command } => {
|
|
use crate::cli::kpm_cmd::Kpm;
|
|
match command {
|
|
Kpm::Load { path, args } => {
|
|
crate::kpm::kpm_load(path.to_str().unwrap(), args.as_deref())
|
|
}
|
|
Kpm::Unload { name } => crate::kpm::kpm_unload(&name),
|
|
Kpm::Num => crate::kpm::kpm_num().map(|_| ()),
|
|
Kpm::List => crate::kpm::kpm_list(),
|
|
Kpm::Info { name } => crate::kpm::kpm_info(&name),
|
|
Kpm::Control { name, args } => {
|
|
let ret = crate::kpm::kpm_control(&name, &args)?;
|
|
println!("{}", ret);
|
|
Ok(())
|
|
}
|
|
Kpm::Version => crate::kpm::kpm_version_loader(),
|
|
}
|
|
}
|
|
Commands::Umount { command } => match command {
|
|
Umount::Add {
|
|
path,
|
|
flags,
|
|
} => crate::umount_manager::add_umount_path(&path, flags),
|
|
Umount::Remove { path } => crate::umount_manager::remove_umount_path(&path),
|
|
Umount::List => crate::umount_manager::list_umount_paths(),
|
|
Umount::ClearCustom => crate::umount_manager::clear_custom_paths(),
|
|
Umount::Save => crate::umount_manager::save_umount_config(),
|
|
Umount::Load => crate::umount_manager::load_and_apply_config(),
|
|
Umount::Apply => crate::umount_manager::apply_config_to_kernel(),
|
|
},
|
|
};
|
|
|
|
if let Err(e) = &result {
|
|
for c in e.chain() {
|
|
log::error!("{c:#?}");
|
|
}
|
|
|
|
log::error!("{:#?}", e.backtrace());
|
|
}
|
|
result
|
|
}
|