ksud: refactor module mount (#384)

This commit is contained in:
5ec1cff
2023-04-14 22:30:34 +08:00
committed by GitHub
parent 029061177b
commit c058cb8848
5 changed files with 158 additions and 265 deletions

View File

@@ -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 ${pstoreFile.absolutePath} /sys/fs/pstore").exec()
shell.newJob().add("tar -czf ${diagFile.absolutePath} /data/vendor/diag").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("cat /proc/filesystems > ${fileSystemsFile.absolutePath}").exec()
shell.newJob().add("ls -alRZ /data/adb > ${ksuFileTree.absolutePath}").exec() shell.newJob().add("ls -alRZ /data/adb > ${ksuFileTree.absolutePath}").exec()
shell.newJob().add("cat /data/system/packages.list > ${appListFile.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() shell.newJob().add("chmod 0644 ${targetFile.absolutePath}").exec()
return targetFile return targetFile
} }

View File

@@ -72,6 +72,8 @@ enum Debug {
/// Get kernel version /// Get kernel version
Version, Version,
Mount,
/// For testing /// For testing
Test, Test,
} }
@@ -187,6 +189,7 @@ pub fn run() -> Result<()> {
Ok(()) Ok(())
} }
Debug::Su => crate::ksu::grant_root(), Debug::Su => crate::ksu::grant_root(),
Debug::Mount => event::mount_systemlessly(defs::MODULE_DIR),
Debug::Test => todo!(), Debug::Test => todo!(),
}, },
}; };

View File

@@ -5,7 +5,7 @@ pub const WORKING_DIR: &str = concatcp!(ADB_DIR, "ksu/");
pub const BINARY_DIR: &str = concatcp!(WORKING_DIR, "bin/"); pub const BINARY_DIR: &str = concatcp!(WORKING_DIR, "bin/");
pub const KSURC_PATH: &str = concatcp!(WORKING_DIR, ".ksurc"); 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"); pub const DAEMON_PATH: &str = concatcp!(ADB_DIR, "ksud");
#[cfg(target_os = "android")] #[cfg(target_os = "android")]

View File

@@ -1,5 +1,6 @@
use anyhow::{bail, Context, Result}; use anyhow::{bail, Context, Result};
use log::{info, warn}; use log::{info, warn};
use std::path::PathBuf;
use std::{collections::HashMap, path::Path}; use std::{collections::HashMap, path::Path};
use crate::{ use crate::{
@@ -7,44 +8,21 @@ use crate::{
utils::{self, ensure_clean_dir, ensure_dir_exists}, utils::{self, ensure_clean_dir, ensure_dir_exists},
}; };
fn mount_partition(partition: &str, lowerdir: &mut Vec<String>) -> Result<()> { fn mount_partition(partition: &str, lowerdir: &Vec<String>) -> Result<()> {
if lowerdir.is_empty() { if lowerdir.is_empty() {
warn!("partition: {partition} lowerdir is empty"); warn!("partition: {partition} lowerdir is empty");
return Ok(()); 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 /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"); warn!("partition: {partition} is a symlink");
return Ok(()); return Ok(());
} }
// handle stock mounts under /partition, we should restore the mount point after overlay mount::mount_overlay(&partition, lowerdir)
// 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
} }
pub fn mount_systemlessly(module_dir: &str) -> Result<()> { 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"); let module_system = Path::new(&module).join("system");
if module_system.exists() { if module_system.is_dir() {
system_lowerdir.push(format!("{}", module_system.display())); 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 // 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! // otherwise it must be a symlink and we don't need to overlay!
let part_path = Path::new(&module).join(part); let part_path = Path::new(&module).join(part);
if !part_path.exists() { if part_path.is_dir() {
continue; if let Some(v) = partition_lowerdir.get_mut(*part) {
} v.push(format!("{}", part_path.display()));
if let Some(v) = partition_lowerdir.get_mut(*part) { }
v.push(format!("{}", part_path.display()));
} }
} }
} }
// mount /system first // 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); warn!("mount system failed: {:#}", e);
} }
// mount other partitions // mount other partitions
for (k, mut v) in partition_lowerdir { for (k, v) in partition_lowerdir {
if let Err(e) = mount_partition(&k, &mut v) { if let Err(e) = mount_partition(&k, &v) {
warn!("mount {k} failed: {:#}", e); warn!("mount {k} failed: {:#}", e);
} }
} }
@@ -184,17 +161,11 @@ pub fn on_post_data_fs() -> Result<()> {
warn!("load system.prop failed: {}", e); 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 // mount moduke systemlessly by overlay
if let Err(e) = mount_systemlessly(module_dir) { if let Err(e) = mount_systemlessly(module_dir) {
warn!("do systemless mount failed: {}", e); warn!("do systemless mount failed: {}", e);
} }
stock_overlay.mount_all();
Ok(()) Ok(())
} }
@@ -257,7 +228,6 @@ pub fn install() -> Result<()> {
#[cfg(target_os = "android")] #[cfg(target_os = "android")]
fn link_ksud_to_bin() -> Result<()> { fn link_ksud_to_bin() -> Result<()> {
use std::path::PathBuf;
let ksu_bin = PathBuf::from(defs::DAEMON_PATH); let ksu_bin = PathBuf::from(defs::DAEMON_PATH);
let ksu_bin_link = PathBuf::from(defs::DAEMON_LINK_PATH); let ksu_bin_link = PathBuf::from(defs::DAEMON_LINK_PATH);
if ksu_bin.exists() && !ksu_bin_link.exists() { if ksu_bin.exists() && !ksu_bin_link.exists() {

View File

@@ -7,12 +7,17 @@ use retry::delay::NoDelay;
#[cfg(any(target_os = "linux", target_os = "android"))] #[cfg(any(target_os = "linux", target_os = "android"))]
use sys_mount::{unmount, FilesystemType, Mount, MountFlags, Unmount, UnmountFlags}; 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"))] #[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"))] #[cfg(any(target_os = "linux", target_os = "android"))]
use std::collections::HashSet; use std::fs::File;
#[cfg(any(target_os = "linux", target_os = "android"))]
use crate::utils; 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 { pub struct AutoMountExt4 {
mnt: String, mnt: String,
@@ -130,14 +135,136 @@ pub fn umount_dir(src: &str) -> Result<()> {
} }
#[cfg(any(target_os = "linux", target_os = "android"))] #[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<Path>,
dest: impl AsRef<Path>,
) -> Result<()> {
let options = format!(
"lowerdir={}:{}",
lower_dirs.join(":"),
lowest.as_ref().display()
);
info!(
"mount overlayfs on {}, options={}",
dest.as_ref().display(),
options
);
Mount::builder() Mount::builder()
.fstype(FilesystemType::from("overlay")) .fstype(FilesystemType::from("overlay"))
.flags(MountFlags::RDONLY) .data(&options)
.data(&format!("lowerdir={lowerdir}")) .mount(KSU_OVERLAY_SOURCE, dest.as_ref())
.mount("overlay", mnt) .with_context(|| {
.map(|_| ()) format!(
.map_err(|e| anyhow::anyhow!("mount partition: {mnt} overlay failed: {e}")) "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<Path>, to: impl AsRef<Path>) -> 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<String>,
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<String> = 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<String>) -> 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::<Vec<_>>();
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")))] #[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")))] #[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<String>) -> Result<()> {
unimplemented!() unimplemented!()
} }
pub struct StockOverlay {
#[cfg(any(target_os = "linux", target_os = "android"))]
mountinfos: Vec<MountInfo>,
}
#[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::<Vec<_>>();
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<Self> {
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::<Vec<_>>();
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<Self> {
Ok(Self {
mnt: mnt.to_string(),
})
}
pub fn remount(&self) -> Result<()> {
unimplemented!()
}
}