diff --git a/userspace/ksud/bin/aarch64/bootctl b/userspace/ksud/bin/aarch64/bootctl new file mode 100644 index 000000000000..cf5c61368467 Binary files /dev/null and b/userspace/ksud/bin/aarch64/bootctl differ diff --git a/userspace/ksud/bin/aarch64/ksuinit b/userspace/ksud/bin/aarch64/ksuinit new file mode 100755 index 000000000000..ad930455cbd8 Binary files /dev/null and b/userspace/ksud/bin/aarch64/ksuinit differ diff --git a/userspace/ksud/bin/x86_64/ksuinit b/userspace/ksud/bin/x86_64/ksuinit new file mode 100755 index 000000000000..13ac21ee7163 Binary files /dev/null and b/userspace/ksud/bin/x86_64/ksuinit differ diff --git a/userspace/ksud/src/assets.rs b/userspace/ksud/src/assets.rs index 2be03cf37d71..ba665ebff9ce 100644 --- a/userspace/ksud/src/assets.rs +++ b/userspace/ksud/src/assets.rs @@ -1,6 +1,7 @@ use anyhow::Result; use const_format::concatcp; use rust_embed::RustEmbed; +use std::path::Path; use crate::{defs::BINARY_DIR, utils}; @@ -19,11 +20,17 @@ struct Asset; pub fn ensure_binaries(ignore_if_exist: bool) -> Result<()> { for file in Asset::iter() { - utils::ensure_binary( - format!("{BINARY_DIR}{file}"), - &Asset::get(&file).unwrap().data, - ignore_if_exist, - )? + if file == "ksuinit" { + continue; + } + let asset = Asset::get(&file).ok_or(anyhow::anyhow!("asset not found: {}", file))?; + utils::ensure_binary(format!("{BINARY_DIR}{file}"), &asset.data, ignore_if_exist)? } Ok(()) } + +pub fn copy_assets_to_file(name: &str, dst: impl AsRef) -> Result<()> { + let asset = Asset::get(name).ok_or(anyhow::anyhow!("asset not found: {}", name))?; + std::fs::write(dst, asset.data)?; + Ok(()) +} diff --git a/userspace/ksud/src/boot_patch.rs b/userspace/ksud/src/boot_patch.rs index 0a59204316bc..cbc148095a5e 100644 --- a/userspace/ksud/src/boot_patch.rs +++ b/userspace/ksud/src/boot_patch.rs @@ -1,27 +1,52 @@ #[cfg(unix)] use std::os::unix::fs::PermissionsExt; +use anyhow::anyhow; use anyhow::bail; use anyhow::ensure; use anyhow::Context; use anyhow::Result; -use is_executable::IsExecutable; use std::path::Path; use std::path::PathBuf; use std::process::Command; use std::process::Stdio; +use which::which; -use crate::utils; +use crate::{assets, utils}; #[cfg(unix)] fn ensure_gki_kernel() -> Result<()> { - let version = - procfs::sys::kernel::Version::current().with_context(|| "get kernel version failed")?; - let is_gki = version.major == 5 && version.minor >= 10 || version.major > 5; + let version = get_kernel_version()?; + let is_gki = version.0 == 5 && version.1 >= 10 || version.2 > 5; ensure!(is_gki, "only support GKI kernel"); Ok(()) } +#[cfg(unix)] +pub fn get_kernel_version() -> Result<(i32, i32, i32)> { + use regex::Regex; + let uname = rustix::system::uname(); + let version = uname.release().to_string_lossy(); + let re = Regex::new(r"(\d+)\.(\d+)\.(\d+)")?; + if let Some(captures) = re.captures(&version) { + let major = captures + .get(1) + .and_then(|m| m.as_str().parse::().ok()) + .ok_or_else(|| anyhow!("Major version parse error"))?; + let minor = captures + .get(2) + .and_then(|m| m.as_str().parse::().ok()) + .ok_or_else(|| anyhow!("Minor version parse error"))?; + let patch = captures + .get(3) + .and_then(|m| m.as_str().parse::().ok()) + .ok_or_else(|| anyhow!("Patch version parse error"))?; + Ok((major, minor, patch)) + } else { + Err(anyhow!("Invalid kernel version string")) + } +} + fn do_cpio_cmd(magiskboot: &Path, workding_dir: &Path, cmd: &str) -> Result<()> { let status = Command::new(magiskboot) .current_dir(workding_dir) @@ -65,7 +90,7 @@ pub fn patch( ) -> Result<()> { let result = do_patch(image, kernel, kmod, init, ota, flash, out, magiskboot_path); if let Err(ref e) = result { - println!("Error: {e}"); + println!("-Install Error: {e}"); } result } @@ -96,19 +121,17 @@ fn do_patch( "init and module must not be specified." ); } else { - ensure!( - init.is_some() && kmod.is_some(), - "init and module must be specified" - ); + ensure!(kmod.is_some(), "module must be specified"); } - let workding_dir = tempdir::TempDir::new("KernelSU")?; + let workding_dir = + tempdir::TempDir::new("KernelSU").with_context(|| "create temp dir failed")?; let bootimage; let mut bootdevice = None; - if let Some(image) = image { + if let Some(ref image) = image { ensure!(image.exists(), "boot image not found"); bootimage = std::fs::canonicalize(image)?; } else { @@ -131,7 +154,7 @@ fn do_patch( format!("/dev/block/by-name/boot{slot_suffix}") }; - println!("bootdevice: {boot_partition}"); + println!("- Bootdevice: {boot_partition}"); let tmp_boot_path = workding_dir.path().join("boot.img"); dd(&boot_partition, &tmp_boot_path)?; @@ -142,38 +165,53 @@ fn do_patch( bootdevice = Some(boot_partition); }; - println!("- Boot image: {bootimage:?}"); - - let magiskboot = magiskboot_path - .map(std::fs::canonicalize) - .transpose()? - .unwrap_or_else(|| "magiskboot".into()); - - if !magiskboot.is_executable() { - #[cfg(unix)] - std::fs::set_permissions(&magiskboot, std::fs::Permissions::from_mode(0o755)) - .with_context(|| "set magiskboot executable failed".to_string())?; - } - - ensure!(magiskboot.exists(), "magiskboot not found"); + // extract magiskboot + let magiskboot = { + if which("magiskboot").is_ok() { + let _ = assets::ensure_binaries(true); + "magiskboot".into() + } else { + // magiskboot is not in $PATH, use builtin or specified one + let magiskboot = if let Some(magiskboot_path) = magiskboot_path { + std::fs::canonicalize(magiskboot_path)? + } else { + let magiskboot_path = workding_dir.path().join("magiskboot"); + assets::copy_assets_to_file("magiskboot", &magiskboot_path) + .with_context(|| "copy magiskboot failed")?; + magiskboot_path + }; + ensure!(magiskboot.exists(), "{magiskboot:?} is not exist"); + #[cfg(unix)] + let _ = std::fs::set_permissions(&magiskboot, std::fs::Permissions::from_mode(0o755)); + magiskboot + } + }; if let Some(kernel) = kernel { std::fs::copy(kernel, workding_dir.path().join("kernel")) .with_context(|| "copy kernel from failed".to_string())?; } - println!("- Patching boot image..."); - if let (Some(kmod), Some(init)) = (kmod, init) { + if let Some(kmod) = kmod { + println!("- Preparing assets"); + std::fs::copy(kmod, workding_dir.path().join("kernelsu.ko")) .with_context(|| "copy kernel module failed".to_string())?; - std::fs::copy(init, workding_dir.path().join("init")) - .with_context(|| "copy init failed".to_string())?; + let init_file = workding_dir.path().join("init"); + if let Some(init) = init { + std::fs::copy(init, workding_dir.path().join("init")) + .with_context(|| "copy init failed".to_string())?; + } else { + crate::assets::copy_assets_to_file("ksuinit", init_file) + .with_context(|| "copy ksuinit failed")?; + } // magiskboot unpack boot.img // magiskboot cpio ramdisk.cpio 'cp init init.real' // magiskboot cpio ramdisk.cpio 'add 0755 ksuinit init' // magiskboot cpio ramdisk.cpio 'add 0755 kernelsu.ko' + println!("- Unpacking boot image"); let status = Command::new(&magiskboot) .current_dir(workding_dir.path()) .stdout(Stdio::null()) @@ -183,6 +221,7 @@ fn do_patch( .status()?; ensure!(status.success(), "magiskboot unpack failed"); + println!("- Adding KernelSU LKM"); let is_kernelsu_patched = do_cpio_cmd(&magiskboot, workding_dir.path(), "exists kernelsu.ko").is_ok(); if !is_kernelsu_patched { @@ -201,6 +240,7 @@ fn do_patch( )?; } + println!("- Repacking boot image"); // magiskboot repack boot.img let status = Command::new(&magiskboot) .current_dir(workding_dir.path()) @@ -210,20 +250,25 @@ fn do_patch( .arg(bootimage.display().to_string()) .status()?; ensure!(status.success(), "magiskboot repack failed"); + let new_boot = workding_dir.path().join("new-boot.img"); + + if image.is_some() { + // if image is specified, write to output file + let output_dir = out.unwrap_or(std::env::current_dir()?); + let now = chrono::Utc::now(); + let output_image = + output_dir.join(format!("kernelsu_boot_{}.img", now.format("%Y%m%d_%H%M%S"))); + + if std::fs::rename(&new_boot, &output_image).is_err() { + std::fs::copy(&new_boot, &output_image) + .with_context(|| "copy out new boot failed".to_string())?; + } + println!("- Output file is written to"); + println!("- {}", output_image.display().to_string().trim_matches('"')); + } - let out = out.unwrap_or(std::env::current_dir()?); - - let now = chrono::Utc::now(); - let output_image = out.join(format!( - "kernelsu_patched_boot_{}.img", - now.format("%Y%m%d_%H%M%S") - )); - std::fs::copy(workding_dir.path().join("new-boot.img"), &output_image) - .with_context(|| "copy out new boot failed".to_string())?; - - println!("- Boot image patched: {output_image:?}"); if flash { - println!("- Flashing boot image..."); + println!("- Flashing new boot image"); let Some(bootdevice) = bootdevice else { bail!("boot device not found") }; @@ -233,9 +278,9 @@ fn do_patch( .status()?; ensure!(status.success(), "set boot device rw failed"); - dd(&output_image, &bootdevice).with_context(|| "flash boot failed")?; + dd(&new_boot, &bootdevice).with_context(|| "flash boot failed")?; } - println!("- Done."); + println!("- Done!"); Ok(()) } diff --git a/userspace/ksud/src/cli.rs b/userspace/ksud/src/cli.rs index ffca157a2142..1d272319ce7a 100644 --- a/userspace/ksud/src/cli.rs +++ b/userspace/ksud/src/cli.rs @@ -7,7 +7,7 @@ use android_logger::Config; #[cfg(target_os = "android")] use log::LevelFilter; -use crate::{apk_sign, debug, defs, init_event, ksucalls, module, utils}; +use crate::{apk_sign, assets, debug, defs, init_event, ksucalls, module, utils}; /// KernelSU userspace cli #[derive(Parser, Debug)] @@ -60,10 +60,10 @@ enum Commands { kernel: Option, /// LKM module path to replace - #[arg(short, long, requires("init"))] + #[arg(short, long)] module: Option, - /// init to be replaced, if use LKM, this must be specified + /// init to be replaced #[arg(short, long, requires("module"))] init: Option, @@ -304,7 +304,7 @@ pub fn run() -> Result<()> { utils::copy_sparse_file(src, dst, punch_hole)?; Ok(()) } - Debug::Test => todo!(), + Debug::Test => assets::ensure_binaries(false), }, Commands::BootPatch {