diff --git a/manager/app/src/main/java/me/weishu/kernelsu/ui/util/LogEvent.kt b/manager/app/src/main/java/me/weishu/kernelsu/ui/util/LogEvent.kt index 28e75245..d6b9c875 100644 --- a/manager/app/src/main/java/me/weishu/kernelsu/ui/util/LogEvent.kt +++ b/manager/app/src/main/java/me/weishu/kernelsu/ui/util/LogEvent.kt @@ -37,7 +37,7 @@ fun getBugreportFile(context: Context): File { shell.newJob().add("tar -czf ${pstoreFile.absolutePath} /sys/fs/pstore").exec() shell.newJob().add("tar -czf ${diagFile.absolutePath} /data/vendor/diag").exec() - shell.newJob().add("cat /proc/mounts > ${mountsFile.absolutePath}").exec() + shell.newJob().add("cat /proc/1/mountinfo > ${mountsFile.absolutePath}").exec() shell.newJob().add("cat /proc/filesystems > ${fileSystemsFile.absolutePath}").exec() shell.newJob().add("ls -alRZ /data/adb > ${ksuFileTree.absolutePath}").exec() shell.newJob().add("cat /data/system/packages.list > ${appListFile.absolutePath}").exec() @@ -84,4 +84,4 @@ fun getBugreportFile(context: Context): File { shell.newJob().add("chmod 0644 ${targetFile.absolutePath}").exec() return targetFile -} \ No newline at end of file +} diff --git a/userspace/ksud/src/cli.rs b/userspace/ksud/src/cli.rs index 5d29855f..6b2ec51c 100644 --- a/userspace/ksud/src/cli.rs +++ b/userspace/ksud/src/cli.rs @@ -72,6 +72,8 @@ enum Debug { /// Get kernel version Version, + Mount, + /// For testing Test, } @@ -187,6 +189,7 @@ pub fn run() -> Result<()> { Ok(()) } Debug::Su => crate::ksu::grant_root(), + Debug::Mount => event::mount_systemlessly(defs::MODULE_DIR), Debug::Test => todo!(), }, }; diff --git a/userspace/ksud/src/defs.rs b/userspace/ksud/src/defs.rs index 8de511df..2cb1ffa1 100644 --- a/userspace/ksud/src/defs.rs +++ b/userspace/ksud/src/defs.rs @@ -5,7 +5,7 @@ pub const WORKING_DIR: &str = concatcp!(ADB_DIR, "ksu/"); pub const BINARY_DIR: &str = concatcp!(WORKING_DIR, "bin/"); pub const KSURC_PATH: &str = concatcp!(WORKING_DIR, ".ksurc"); -pub const STOCK_MNT_ROOT: &str = concatcp!(WORKING_DIR, ".mnt"); +pub const KSU_OVERLAY_SOURCE: &str = "KSU"; pub const DAEMON_PATH: &str = concatcp!(ADB_DIR, "ksud"); #[cfg(target_os = "android")] diff --git a/userspace/ksud/src/event.rs b/userspace/ksud/src/event.rs index 156e9477..cc9e3970 100644 --- a/userspace/ksud/src/event.rs +++ b/userspace/ksud/src/event.rs @@ -1,5 +1,6 @@ use anyhow::{bail, Context, Result}; use log::{info, warn}; +use std::path::PathBuf; use std::{collections::HashMap, path::Path}; use crate::{ @@ -7,44 +8,21 @@ use crate::{ utils::{self, ensure_clean_dir, ensure_dir_exists}, }; -fn mount_partition(partition: &str, lowerdir: &mut Vec) -> Result<()> { +fn mount_partition(partition: &str, lowerdir: &Vec) -> Result<()> { if lowerdir.is_empty() { warn!("partition: {partition} lowerdir is empty"); return Ok(()); } + let partition = format!("/{partition}"); + // if /partition is a symlink and linked to /system/partition, then we don't need to overlay it separately - if Path::new(&format!("/{partition}")).read_link().is_ok() { + if Path::new(&partition).read_link().is_ok() { warn!("partition: {partition} is a symlink"); return Ok(()); } - // handle stock mounts under /partition, we should restore the mount point after overlay - // because the overlayfs mount will "overlay" the bind mount such as /vendor/bt_firmware, /vendor/dsp - // which will cause the system bootloop or bluetooth/dsp not working - let stock_mount = mount::StockMount::new(&format!("/{partition}/")) - .with_context(|| format!("get stock mount of partition: {partition} failed"))?; - - // add /partition as the lowerest dir - let lowest_dir = format!("/{partition}"); - lowerdir.push(lowest_dir.clone()); - - let lowerdir = lowerdir.join(":"); - info!("partition: {partition} lowerdir: {lowerdir}"); - - let result = mount::mount_overlay(&lowerdir, &lowest_dir); - - if let Err(e) = stock_mount.remount() { - if result.is_ok() { - // if mount overlay ok but stock remount failed, we should umount overlay - warn!("remount stock failed: {:?}, umount overlay {lowest_dir}", e); - if mount::umount_dir(&lowest_dir).is_err() { - warn!("umount overlay {lowest_dir} failed"); - } - } - } - - result + mount::mount_overlay(&partition, lowerdir) } pub fn mount_systemlessly(module_dir: &str) -> Result<()> { @@ -74,7 +52,7 @@ pub fn mount_systemlessly(module_dir: &str) -> Result<()> { } let module_system = Path::new(&module).join("system"); - if module_system.exists() { + if module_system.is_dir() { system_lowerdir.push(format!("{}", module_system.display())); } @@ -82,23 +60,22 @@ pub fn mount_systemlessly(module_dir: &str) -> Result<()> { // if /partition is a mountpoint, we would move it to $MODPATH/$partition when install // otherwise it must be a symlink and we don't need to overlay! let part_path = Path::new(&module).join(part); - if !part_path.exists() { - continue; - } - if let Some(v) = partition_lowerdir.get_mut(*part) { - v.push(format!("{}", part_path.display())); + if part_path.is_dir() { + if let Some(v) = partition_lowerdir.get_mut(*part) { + v.push(format!("{}", part_path.display())); + } } } } // mount /system first - if let Err(e) = mount_partition("system", &mut system_lowerdir) { + if let Err(e) = mount_partition("system", &system_lowerdir) { warn!("mount system failed: {:#}", e); } // mount other partitions - for (k, mut v) in partition_lowerdir { - if let Err(e) = mount_partition(&k, &mut v) { + for (k, v) in partition_lowerdir { + if let Err(e) = mount_partition(&k, &v) { warn!("mount {k} failed: {:#}", e); } } @@ -184,17 +161,11 @@ pub fn on_post_data_fs() -> Result<()> { warn!("load system.prop failed: {}", e); } - // Finally, we should do systemless mount - // But we should backup all stock overlayfs and remount them after module mounted - let stock_overlay = mount::StockOverlay::new(); - // mount moduke systemlessly by overlay if let Err(e) = mount_systemlessly(module_dir) { warn!("do systemless mount failed: {}", e); } - stock_overlay.mount_all(); - Ok(()) } @@ -257,7 +228,6 @@ pub fn install() -> Result<()> { #[cfg(target_os = "android")] fn link_ksud_to_bin() -> Result<()> { - use std::path::PathBuf; let ksu_bin = PathBuf::from(defs::DAEMON_PATH); let ksu_bin_link = PathBuf::from(defs::DAEMON_LINK_PATH); if ksu_bin.exists() && !ksu_bin_link.exists() { diff --git a/userspace/ksud/src/mount.rs b/userspace/ksud/src/mount.rs index f86d1034..444ae703 100644 --- a/userspace/ksud/src/mount.rs +++ b/userspace/ksud/src/mount.rs @@ -7,12 +7,17 @@ use retry::delay::NoDelay; #[cfg(any(target_os = "linux", target_os = "android"))] use sys_mount::{unmount, FilesystemType, Mount, MountFlags, Unmount, UnmountFlags}; +use crate::defs::KSU_OVERLAY_SOURCE; +use log::{info, warn}; #[cfg(any(target_os = "linux", target_os = "android"))] -use procfs::process::{MountInfo, Process}; +use procfs::process::Process; #[cfg(any(target_os = "linux", target_os = "android"))] -use std::collections::HashSet; - -use crate::utils; +use std::fs::File; +#[cfg(any(target_os = "linux", target_os = "android"))] +use std::os::fd::AsRawFd; +#[cfg(any(target_os = "linux", target_os = "android"))] +use std::os::unix::fs::OpenOptionsExt; +use std::path::Path; pub struct AutoMountExt4 { mnt: String, @@ -130,14 +135,136 @@ pub fn umount_dir(src: &str) -> Result<()> { } #[cfg(any(target_os = "linux", target_os = "android"))] -pub fn mount_overlay(lowerdir: &str, mnt: &str) -> Result<()> { +fn mount_overlayfs( + lower_dirs: &[String], + lowest: impl AsRef, + dest: impl AsRef, +) -> Result<()> { + let options = format!( + "lowerdir={}:{}", + lower_dirs.join(":"), + lowest.as_ref().display() + ); + info!( + "mount overlayfs on {}, options={}", + dest.as_ref().display(), + options + ); Mount::builder() .fstype(FilesystemType::from("overlay")) - .flags(MountFlags::RDONLY) - .data(&format!("lowerdir={lowerdir}")) - .mount("overlay", mnt) - .map(|_| ()) - .map_err(|e| anyhow::anyhow!("mount partition: {mnt} overlay failed: {e}")) + .data(&options) + .mount(KSU_OVERLAY_SOURCE, dest.as_ref()) + .with_context(|| { + format!( + "mount overlayfs on {} options {} failed", + dest.as_ref().display(), + options + ) + })?; + Ok(()) +} + +#[cfg(any(target_os = "linux", target_os = "android"))] +fn bind_mount(from: impl AsRef, to: impl AsRef) -> Result<()> { + info!( + "bind mount {} -> {}", + from.as_ref().display(), + to.as_ref().display() + ); + Mount::builder() + .flags(MountFlags::BIND) + .mount(from.as_ref(), to.as_ref()) + .with_context(|| { + format!( + "bind mount failed: {} -> {}", + from.as_ref().display(), + to.as_ref().display() + ) + })?; + Ok(()) +} + +#[cfg(any(target_os = "linux", target_os = "android"))] +fn mount_overlay_child( + mount_point: &str, + relative: &String, + module_roots: &Vec, + stock_root: &String, +) -> Result<()> { + if !module_roots + .iter() + .any(|lower| Path::new(&format!("{}{}", lower, relative)).exists()) + { + return bind_mount(stock_root, mount_point); + } + if !Path::new(&stock_root).is_dir() { + return Ok(()); + } + let mut lower_dirs: Vec = vec![]; + for lower in module_roots { + let lower_dir = format!("{}{}", lower, relative); + let path = Path::new(&lower_dir); + if path.is_dir() { + lower_dirs.push(lower_dir); + } else if path.exists() { + // stock root has been blocked by this file + return Ok(()); + } + } + if lower_dirs.is_empty() { + return Ok(()); + } + // merge modules and stock + if let Err(e) = mount_overlayfs(&lower_dirs, stock_root, mount_point) { + warn!("failed: {:#}, fallback to bind mount", e); + bind_mount(stock_root, mount_point)?; + } + Ok(()) +} + +#[cfg(any(target_os = "linux", target_os = "android"))] +pub fn mount_overlay(root: &String, module_roots: &Vec) -> Result<()> { + info!("mount overlay for {}", root); + let stock_root = File::options() + .read(true) + .custom_flags(libc::O_PATH) + .open(root)?; + let stock_root = format!("/proc/self/fd/{}", stock_root.as_raw_fd()); + + // collect child mounts before mounting the root + let mounts = Process::myself()? + .mountinfo() + .with_context(|| "get mountinfo")?; + let mut mount_seq = mounts + .iter() + .filter(|m| { + m.mount_point.starts_with(root) && !Path::new(&root).starts_with(&m.mount_point) + }) + .map(|m| m.mount_point.to_str()) + .collect::>(); + mount_seq.sort(); + mount_seq.dedup(); + + mount_overlayfs(module_roots, root, root).with_context(|| "mount overlayfs for root failed")?; + for mount_point in mount_seq.iter() { + let Some(mount_point) = mount_point else { + continue; + }; + let relative = mount_point.replacen(root, "", 1); + let stock_root: String = format!("{}{}", stock_root, relative); + if !Path::new(&stock_root).exists() { + continue; + } + if let Err(e) = mount_overlay_child(mount_point, &relative, module_roots, &stock_root) { + warn!( + "failed to mount overlay for child {}: {:#}, revert", + mount_point, e + ); + umount_dir(root).with_context(|| format!("failed to revert {}", root))?; + bail!(e); + } + } + Ok(()) } #[cfg(not(any(target_os = "linux", target_os = "android")))] @@ -151,213 +278,6 @@ pub fn umount_dir(_src: &str) -> Result<()> { } #[cfg(not(any(target_os = "linux", target_os = "android")))] -pub fn mount_overlay(_lowerdir: &str, _mnt: &str) -> Result<()> { +pub fn mount_overlay(_dest: &String, _lower_dirs: &Vec) -> Result<()> { unimplemented!() } - -pub struct StockOverlay { - #[cfg(any(target_os = "linux", target_os = "android"))] - mountinfos: Vec, -} - -#[cfg(not(any(target_os = "linux", target_os = "android")))] -impl StockOverlay { - pub fn new() -> Self { - unimplemented!() - } - - pub fn mount_all(&self) { - unimplemented!() - } -} - -#[cfg(any(target_os = "linux", target_os = "android"))] -impl StockOverlay { - pub fn new() -> Self { - if let std::result::Result::Ok(process) = Process::myself() { - if let std::result::Result::Ok(mountinfos) = process.mountinfo() { - let overlay_mounts = mountinfos - .into_iter() - .filter(|m| m.fs_type == "overlay") - .collect::>(); - return Self { - mountinfos: overlay_mounts, - }; - } - } - Self { mountinfos: vec![] } - } - - pub fn mount_all(&self) { - log::info!("stock overlay: mount all: {:?}", self.mountinfos); - for mount in self.mountinfos.clone() { - let Some(mnt) = mount.mount_point.to_str() else { - log::warn!("Failed to get mount point"); - continue; - }; - - if mnt == "/system" { - log::warn!("stock overlay found /system, skip!"); - continue; - } - - let (_flags, b): (HashSet<_>, HashSet<_>) = mount - .mount_options - .into_iter() - .chain(mount.super_options) - .partition(|(_, m)| m.is_none()); - - let mut overlay_opts = vec![]; - for (opt, val) in b { - if let Some(val) = val { - overlay_opts.push(format!("{opt}={val}")); - } else { - log::warn!("opt empty: {}", opt); - } - } - let overlay_data = overlay_opts.join(","); - let result = Mount::builder() - .fstype(FilesystemType::from("overlay")) - .flags(MountFlags::RDONLY) - .data(&overlay_data) - .mount("overlay", mnt); - if let Err(e) = result { - log::error!( - "stock mount overlay: {} failed: {}", - mount.mount_point.display(), - e - ); - } else { - log::info!( - "stock mount :{} overlay_opts: {}", - mount.mount_point.display(), - overlay_opts.join(",") - ); - } - } - } -} - -// some ROMs mount device(ext4,exfat) to /vendor, when we do overlay mount, it will overlay -// the stock mounts, these mounts include bt_firmware, wifi_firmware, etc. -// so we to remount these mounts when we do overlay mount. -// this is a workaround, we should find a better way to do this. -#[derive(Debug)] -pub struct StockMount { - mnt: String, - #[cfg(any(target_os = "linux", target_os = "android"))] - mountlist: Vec<(proc_mounts::MountInfo, std::path::PathBuf)>, - #[cfg(any(target_os = "linux", target_os = "android"))] - rootmount: sys_mount::Mount, -} - -#[cfg(any(target_os = "linux", target_os = "android"))] -impl StockMount { - pub fn new(mnt: &str) -> Result { - let mountlist = proc_mounts::MountList::new()?; - let mut mounts = mountlist - .destination_starts_with(std::path::Path::new(mnt)) - .filter(|m| m.fstype != "overlay" && m.fstype != "rootfs") - .collect::>(); - mounts.sort_by(|a, b| b.dest.cmp(&a.dest)); // inverse order - - let mntroot = std::path::Path::new(crate::defs::STOCK_MNT_ROOT); - utils::ensure_dir_exists(mntroot)?; - log::info!("stock mount root: {}", mntroot.display()); - - let rootdir = mntroot.join( - mnt.strip_prefix('/') - .ok_or(anyhow::anyhow!("invalid mnt: {}!", mnt))?, - ); - utils::ensure_dir_exists(&rootdir)?; - let rootmount = Mount::builder().fstype("tmpfs").mount("tmpfs", &rootdir)?; - - let mut ms = vec![]; - for m in mounts { - let dest = &m.dest; - if dest == std::path::Path::new(mnt) { - continue; - } - - let path = rootdir.join(dest.strip_prefix("/")?); - log::info!("rootdir: {}, path: {}", rootdir.display(), path.display()); - if dest.is_dir() { - utils::ensure_dir_exists(&path) - .with_context(|| format!("Failed to create dir: {}", path.display(),))?; - } else if dest.is_file() { - if !path.exists() { - let parent = path - .parent() - .with_context(|| format!("Failed to get parent: {}", path.display()))?; - utils::ensure_dir_exists(parent).with_context(|| { - format!("Failed to create parent: {}", parent.display()) - })?; - std::fs::File::create(&path) - .with_context(|| format!("Failed to create file: {}", path.display(),))?; - } - } else { - bail!("unknown file type: {:?}", dest) - } - log::info!("bind stock mount: {} -> {}", dest.display(), path.display()); - Mount::builder() - .flags(MountFlags::BIND) - .mount(dest, &path) - .with_context(|| { - format!("Failed to mount: {} -> {}", dest.display(), path.display()) - })?; - - ms.push((m.clone(), path)); - } - - Ok(Self { - mnt: mnt.to_string(), - mountlist: ms, - rootmount, - }) - } - - // Yes, we move self here! - pub fn remount(self) -> Result<()> { - log::info!("remount stock for {} : {:?}", self.mnt, self.mountlist); - let mut result = Ok(()); - for (m, src) in self.mountlist { - let dst = m.dest; - - log::info!("begin remount: {} -> {}", src.display(), dst.display()); - let mount_result = Mount::builder() - .flags(MountFlags::BIND | MountFlags::MOVE) - .mount(&src, &dst); - if let Err(e) = mount_result { - log::error!("remount failed: {}", e); - result = Err(e.into()); - } else { - log::info!( - "remount {}({}) -> {} succeed!", - m.source.display(), - src.display(), - dst.display() - ); - } - } - - // umount the root tmpfs mount - if let Err(e) = self.rootmount.unmount(UnmountFlags::DETACH) { - log::warn!("umount root mount failed: {}", e); - } - - result - } -} - -#[cfg(not(any(target_os = "linux", target_os = "android")))] -impl StockMount { - pub fn new(mnt: &str) -> Result { - Ok(Self { - mnt: mnt.to_string(), - }) - } - - pub fn remount(&self) -> Result<()> { - unimplemented!() - } -}