support metamodule, remove built-in overlayfs mount
Co-authored-by: weishu <twsxtd@gmail.com> Co-authored-by: YuKongA <70465933+YuKongA@users.noreply.github.com> Co-authored-by: Ylarod <me@ylarod.cn>
This commit is contained in:
5
.github/workflows/build-manager.yml
vendored
5
.github/workflows/build-manager.yml
vendored
@@ -2,7 +2,7 @@ name: Build Manager
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ "main", "dev", "ci" ]
|
branches: [ "main", "dev", "ci", "miuix" ]
|
||||||
paths:
|
paths:
|
||||||
- '.github/workflows/build-manager.yml'
|
- '.github/workflows/build-manager.yml'
|
||||||
- '.github/workflows/build-lkm.yml'
|
- '.github/workflows/build-lkm.yml'
|
||||||
@@ -11,13 +11,14 @@ on:
|
|||||||
- 'userspace/ksud/**'
|
- 'userspace/ksud/**'
|
||||||
- 'userspace/user_scanner/**'
|
- 'userspace/user_scanner/**'
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ "main", "dev" ]
|
branches: [ "main", "dev", "miuix" ]
|
||||||
paths:
|
paths:
|
||||||
- '.github/workflows/build-manager.yml'
|
- '.github/workflows/build-manager.yml'
|
||||||
- '.github/workflows/build-lkm.yml'
|
- '.github/workflows/build-lkm.yml'
|
||||||
- 'manager/**'
|
- 'manager/**'
|
||||||
- 'kernel/**'
|
- 'kernel/**'
|
||||||
- 'userspace/ksud/**'
|
- 'userspace/ksud/**'
|
||||||
|
- 'userspace/user_scanner/**'
|
||||||
workflow_call:
|
workflow_call:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|||||||
54
.github/workflows/meta-overlay.yml
vendored
Normal file
54
.github/workflows/meta-overlay.yml
vendored
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
name: Build meta-overlayfs
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "main", "dev", "ci", "miuix" ]
|
||||||
|
paths:
|
||||||
|
- '.github/workflows/meta-overlay.yml'
|
||||||
|
- 'userspace/meta-overlayfs/**'
|
||||||
|
pull_request:
|
||||||
|
branches: [ "main", "dev", "miuix" ]
|
||||||
|
paths:
|
||||||
|
- '.github/workflows/meta-overlay.yml'
|
||||||
|
- 'userspace/meta-overlayfs/**'
|
||||||
|
workflow_call:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: bash
|
||||||
|
working-directory: userspace/meta-overlayfs
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v5
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Setup Rust
|
||||||
|
run: |
|
||||||
|
rustup update stable
|
||||||
|
rustup target add aarch64-linux-android
|
||||||
|
rustup target add x86_64-linux-android
|
||||||
|
|
||||||
|
- name: Cache Cargo
|
||||||
|
uses: Swatinem/rust-cache@v2
|
||||||
|
with:
|
||||||
|
workspaces: userspace/meta-overlayfs
|
||||||
|
cache-targets: false
|
||||||
|
|
||||||
|
- name: Install cross
|
||||||
|
run: |
|
||||||
|
RUSTFLAGS="" cargo install cross --git https://github.com/cross-rs/cross --rev 66845c1
|
||||||
|
|
||||||
|
- name: Build meta-overlayfs metamodule
|
||||||
|
run: chmod +x build.sh && ./build.sh
|
||||||
|
|
||||||
|
- name: Upload artifact
|
||||||
|
uses: actions/upload-artifact@v5
|
||||||
|
with:
|
||||||
|
name: meta-overlayfs
|
||||||
|
path: userspace/meta-overlayfs/target/meta-overlayfs-*.zip
|
||||||
|
if-no-files-found: error
|
||||||
Binary file not shown.
Binary file not shown.
@@ -1,15 +1,13 @@
|
|||||||
use anyhow::{Ok, Result};
|
use anyhow::{Ok, Result};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::PathBuf;
|
||||||
|
|
||||||
#[cfg(target_os = "android")]
|
#[cfg(target_os = "android")]
|
||||||
use android_logger::Config;
|
use android_logger::Config;
|
||||||
#[cfg(target_os = "android")]
|
#[cfg(target_os = "android")]
|
||||||
use log::LevelFilter;
|
use log::LevelFilter;
|
||||||
|
|
||||||
use crate::{
|
use crate::{apk_sign, assets, debug, defs, init_event, ksucalls, module, utils};
|
||||||
apk_sign, assets, debug, defs, defs::KSUD_VERBOSE_LOG_FILE, init_event, ksucalls, module, utils,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// KernelSU userspace cli
|
/// KernelSU userspace cli
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
@@ -17,9 +15,6 @@ use crate::{
|
|||||||
struct Args {
|
struct Args {
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
command: Commands,
|
command: Commands,
|
||||||
|
|
||||||
#[arg(short, long, default_value_t = cfg!(debug_assertions))]
|
|
||||||
verbose: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(clap::Subcommand, Debug)]
|
#[derive(clap::Subcommand, Debug)]
|
||||||
@@ -209,8 +204,6 @@ enum Debug {
|
|||||||
/// Get kernel version
|
/// Get kernel version
|
||||||
Version,
|
Version,
|
||||||
|
|
||||||
Mount,
|
|
||||||
|
|
||||||
/// For testing
|
/// For testing
|
||||||
Test,
|
Test,
|
||||||
|
|
||||||
@@ -277,14 +270,14 @@ enum Module {
|
|||||||
zip: String,
|
zip: String,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Uninstall module <id>
|
/// Undo module uninstall mark <id>
|
||||||
Uninstall {
|
UndoUninstall {
|
||||||
/// module id
|
/// module id
|
||||||
id: String,
|
id: String,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Restore module <id>
|
/// Uninstall module <id>
|
||||||
Restore {
|
Uninstall {
|
||||||
/// module id
|
/// module id
|
||||||
id: String,
|
id: String,
|
||||||
},
|
},
|
||||||
@@ -498,10 +491,6 @@ pub fn run() -> Result<()> {
|
|||||||
|
|
||||||
let cli = Args::parse();
|
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);
|
log::info!("command: {:?}", cli.command);
|
||||||
|
|
||||||
let result = match cli.command {
|
let result = match cli.command {
|
||||||
@@ -515,8 +504,8 @@ pub fn run() -> Result<()> {
|
|||||||
}
|
}
|
||||||
match command {
|
match command {
|
||||||
Module::Install { zip } => module::install_module(&zip),
|
Module::Install { zip } => module::install_module(&zip),
|
||||||
|
Module::UndoUninstall { id } => module::undo_uninstall_module(&id),
|
||||||
Module::Uninstall { id } => module::uninstall_module(&id),
|
Module::Uninstall { id } => module::uninstall_module(&id),
|
||||||
Module::Restore { id } => module::restore_uninstall_module(&id),
|
|
||||||
Module::Enable { id } => module::enable_module(&id),
|
Module::Enable { id } => module::enable_module(&id),
|
||||||
Module::Disable { id } => module::disable_module(&id),
|
Module::Disable { id } => module::disable_module(&id),
|
||||||
Module::Action { id } => module::run_action(&id),
|
Module::Action { id } => module::run_action(&id),
|
||||||
@@ -563,7 +552,6 @@ pub fn run() -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Debug::Su { global_mnt } => crate::su::grant_root(global_mnt),
|
Debug::Su { global_mnt } => crate::su::grant_root(global_mnt),
|
||||||
Debug::Mount => init_event::mount_modules_systemlessly(),
|
|
||||||
Debug::Test => assets::ensure_binaries(false),
|
Debug::Test => assets::ensure_binaries(false),
|
||||||
Debug::Mark { command } => match command {
|
Debug::Mark { command } => match command {
|
||||||
MarkCommand::Get { pid } => debug::mark_get(pid),
|
MarkCommand::Get { pid } => debug::mark_get(pid),
|
||||||
@@ -672,11 +660,7 @@ pub fn run() -> Result<()> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if let Err(e) = &result {
|
if let Err(e) = &result {
|
||||||
for c in e.chain() {
|
log::error!("Error: {e:?}");
|
||||||
log::error!("{c:#?}");
|
|
||||||
}
|
|
||||||
|
|
||||||
log::error!("{:#?}", e.backtrace());
|
|
||||||
}
|
}
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ pub const PROFILE_SELINUX_DIR: &str = concatcp!(PROFILE_DIR, "selinux/");
|
|||||||
pub const PROFILE_TEMPLATE_DIR: &str = concatcp!(PROFILE_DIR, "templates/");
|
pub const PROFILE_TEMPLATE_DIR: &str = concatcp!(PROFILE_DIR, "templates/");
|
||||||
|
|
||||||
pub const KSURC_PATH: &str = concatcp!(WORKING_DIR, ".ksurc");
|
pub const KSURC_PATH: &str = concatcp!(WORKING_DIR, ".ksurc");
|
||||||
pub const KSU_MOUNT_SOURCE: &str = "KSU";
|
|
||||||
pub const DAEMON_PATH: &str = concatcp!(ADB_DIR, "ksud");
|
pub const DAEMON_PATH: &str = concatcp!(ADB_DIR, "ksud");
|
||||||
pub const MAGISKBOOT_PATH: &str = concatcp!(BINARY_DIR, "magiskboot");
|
pub const MAGISKBOOT_PATH: &str = concatcp!(BINARY_DIR, "magiskboot");
|
||||||
|
|
||||||
@@ -18,18 +17,19 @@ pub const MAGISKBOOT_PATH: &str = concatcp!(BINARY_DIR, "magiskboot");
|
|||||||
pub const DAEMON_LINK_PATH: &str = concatcp!(BINARY_DIR, "ksud");
|
pub const DAEMON_LINK_PATH: &str = concatcp!(BINARY_DIR, "ksud");
|
||||||
|
|
||||||
pub const MODULE_DIR: &str = concatcp!(ADB_DIR, "modules/");
|
pub const MODULE_DIR: &str = concatcp!(ADB_DIR, "modules/");
|
||||||
|
|
||||||
// warning: this directory should not change, or you need to change the code in module_installer.sh!!!
|
|
||||||
pub const MODULE_UPDATE_DIR: &str = concatcp!(ADB_DIR, "modules_update/");
|
pub const MODULE_UPDATE_DIR: &str = concatcp!(ADB_DIR, "modules_update/");
|
||||||
|
pub const METAMODULE_DIR: &str = concatcp!(ADB_DIR, "metamodule/");
|
||||||
pub const KSUD_VERBOSE_LOG_FILE: &str = concatcp!(ADB_DIR, "verbose");
|
|
||||||
|
|
||||||
pub const MODULE_WEB_DIR: &str = "webroot";
|
pub const MODULE_WEB_DIR: &str = "webroot";
|
||||||
pub const MODULE_ACTION_SH: &str = "action.sh";
|
pub const MODULE_ACTION_SH: &str = "action.sh";
|
||||||
pub const DISABLE_FILE_NAME: &str = "disable";
|
pub const DISABLE_FILE_NAME: &str = "disable";
|
||||||
pub const UPDATE_FILE_NAME: &str = "update";
|
pub const UPDATE_FILE_NAME: &str = "update";
|
||||||
pub const REMOVE_FILE_NAME: &str = "remove";
|
pub const REMOVE_FILE_NAME: &str = "remove";
|
||||||
pub const SKIP_MOUNT_FILE_NAME: &str = "skip_mount";
|
|
||||||
|
// Metamodule support
|
||||||
|
pub const METAMODULE_MOUNT_SCRIPT: &str = "metamount.sh";
|
||||||
|
pub const METAMODULE_METAINSTALL_SCRIPT: &str = "metainstall.sh";
|
||||||
|
pub const METAMODULE_METAUNINSTALL_SCRIPT: &str = "metauninstall.sh";
|
||||||
|
|
||||||
pub const VERSION_CODE: &str = include_str!(concat!(env!("OUT_DIR"), "/VERSION_CODE"));
|
pub const VERSION_CODE: &str = include_str!(concat!(env!("OUT_DIR"), "/VERSION_CODE"));
|
||||||
pub const VERSION_NAME: &str = include_str!(concat!(env!("OUT_DIR"), "/VERSION_NAME"));
|
pub const VERSION_NAME: &str = include_str!(concat!(env!("OUT_DIR"), "/VERSION_NAME"));
|
||||||
@@ -37,6 +37,3 @@ pub const VERSION_NAME: &str = include_str!(concat!(env!("OUT_DIR"), "/VERSION_N
|
|||||||
pub const KSU_BACKUP_DIR: &str = WORKING_DIR;
|
pub const KSU_BACKUP_DIR: &str = WORKING_DIR;
|
||||||
pub const KSU_BACKUP_FILE_PREFIX: &str = "ksu_backup_";
|
pub const KSU_BACKUP_FILE_PREFIX: &str = "ksu_backup_";
|
||||||
pub const BACKUP_FILENAME: &str = "stock_image.sha1";
|
pub const BACKUP_FILENAME: &str = "stock_image.sha1";
|
||||||
|
|
||||||
pub const NO_TMPFS_PATH: &str = concatcp!(WORKING_DIR, ".notmpfs");
|
|
||||||
pub const NO_MOUNT_PATH: &str = concatcp!(WORKING_DIR, ".nomount");
|
|
||||||
|
|||||||
@@ -1,28 +1,14 @@
|
|||||||
#[cfg(target_arch = "aarch64")]
|
|
||||||
use crate::kpm;
|
|
||||||
use crate::utils::is_safe_mode;
|
|
||||||
use crate::{
|
|
||||||
assets, defs,
|
|
||||||
defs::{KSU_MOUNT_SOURCE, NO_MOUNT_PATH, NO_TMPFS_PATH},
|
|
||||||
ksucalls,
|
|
||||||
module::{handle_updated_modules, prune_modules},
|
|
||||||
restorecon, uid_scanner, utils,
|
|
||||||
utils::find_tmp_path,
|
|
||||||
};
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use log::{info, warn};
|
use log::{info, warn};
|
||||||
use rustix::fs::{MountFlags, mount};
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
#[cfg(target_arch = "aarch64")]
|
||||||
#[cfg(target_os = "android")]
|
use crate::kpm;
|
||||||
pub fn mount_modules_systemlessly() -> Result<()> {
|
use crate::module::{handle_updated_modules, prune_modules};
|
||||||
crate::magic_mount::magic_mount(&find_tmp_path())
|
use crate::utils::is_safe_mode;
|
||||||
}
|
use crate::{
|
||||||
|
assets, defs, ksucalls, metamodule, restorecon,
|
||||||
#[cfg(not(target_os = "android"))]
|
utils::{self},
|
||||||
pub fn mount_modules_systemlessly() -> Result<()> {
|
};
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn on_post_data_fs() -> Result<()> {
|
pub fn on_post_data_fs() -> Result<()> {
|
||||||
ksucalls::report_post_fs_data();
|
ksucalls::report_post_fs_data();
|
||||||
@@ -39,9 +25,11 @@ pub fn on_post_data_fs() -> Result<()> {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let safe_mode = utils::is_safe_mode();
|
let safe_mode = crate::utils::is_safe_mode();
|
||||||
|
|
||||||
if safe_mode {
|
if safe_mode {
|
||||||
|
// we should still ensure module directory exists in safe mode
|
||||||
|
// because we may need to operate the module dir in safe mode
|
||||||
warn!("safe mode, skip common post-fs-data.d scripts");
|
warn!("safe mode, skip common post-fs-data.d scripts");
|
||||||
} else {
|
} else {
|
||||||
// Then exec common post-fs-data scripts
|
// Then exec common post-fs-data scripts
|
||||||
@@ -50,18 +38,18 @@ pub fn on_post_data_fs() -> Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let module_dir = defs::MODULE_DIR;
|
||||||
|
|
||||||
assets::ensure_binaries(true).with_context(|| "Failed to extract bin assets")?;
|
assets::ensure_binaries(true).with_context(|| "Failed to extract bin assets")?;
|
||||||
|
|
||||||
// Start UID scanner daemon with highest priority
|
// Start UID scanner daemon with highest priority
|
||||||
uid_scanner::start_uid_scanner_daemon()?;
|
crate::uid_scanner::uid_scanner::start_uid_scanner_daemon()?;
|
||||||
|
|
||||||
if is_safe_mode() {
|
if is_safe_mode() {
|
||||||
warn!("safe mode, skip load feature config");
|
warn!("safe mode, skip load feature config");
|
||||||
} else if let Err(e) = crate::umount_manager::load_and_apply_config() {
|
} else if let Err(e) = crate::umount_manager::load_and_apply_config() {
|
||||||
warn!("Failed to load umount config: {e}");
|
warn!("Failed to load umount config: {e}");
|
||||||
}
|
}
|
||||||
// tell kernel that we've mount the module, so that it can do some optimization
|
|
||||||
ksucalls::report_module_mounted();
|
|
||||||
|
|
||||||
// if we are in safe mode, we should disable all modules
|
// if we are in safe mode, we should disable all modules
|
||||||
if safe_mode {
|
if safe_mode {
|
||||||
@@ -72,14 +60,14 @@ pub fn on_post_data_fs() -> Result<()> {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(e) = prune_modules() {
|
|
||||||
warn!("prune modules failed: {e}");
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Err(e) = handle_updated_modules() {
|
if let Err(e) = handle_updated_modules() {
|
||||||
warn!("handle updated modules failed: {e}");
|
warn!("handle updated modules failed: {e}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Err(e) = prune_modules() {
|
||||||
|
warn!("prune modules failed: {e}");
|
||||||
|
}
|
||||||
|
|
||||||
if let Err(e) = restorecon::restorecon() {
|
if let Err(e) = restorecon::restorecon() {
|
||||||
warn!("restorecon failed: {e}");
|
warn!("restorecon failed: {e}");
|
||||||
}
|
}
|
||||||
@@ -110,23 +98,9 @@ pub fn on_post_data_fs() -> Result<()> {
|
|||||||
warn!("KPM: Failed to load KPM modules: {e}");
|
warn!("KPM: Failed to load KPM modules: {e}");
|
||||||
}
|
}
|
||||||
|
|
||||||
let tmpfs_path = find_tmp_path();
|
// execute metamodule post-fs-data script first (priority)
|
||||||
// for compatibility
|
if let Err(e) = metamodule::exec_stage_script("post-fs-data", true) {
|
||||||
let no_mount = Path::new(NO_TMPFS_PATH).exists() || Path::new(NO_MOUNT_PATH).exists();
|
warn!("exec metamodule post-fs-data script failed: {e}");
|
||||||
|
|
||||||
// mount temp dir
|
|
||||||
if !no_mount {
|
|
||||||
if let Err(e) = mount(
|
|
||||||
KSU_MOUNT_SOURCE,
|
|
||||||
&tmpfs_path,
|
|
||||||
"tmpfs",
|
|
||||||
MountFlags::empty(),
|
|
||||||
"",
|
|
||||||
) {
|
|
||||||
warn!("do temp dir mount failed: {e}");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
info!("no tmpfs requested");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// exec modules post-fs-data scripts
|
// exec modules post-fs-data scripts
|
||||||
@@ -140,18 +114,15 @@ pub fn on_post_data_fs() -> Result<()> {
|
|||||||
warn!("load system.prop failed: {e}");
|
warn!("load system.prop failed: {e}");
|
||||||
}
|
}
|
||||||
|
|
||||||
// mount module systemlessly by magic mount
|
// execute metamodule mount script
|
||||||
#[cfg(target_os = "android")]
|
if let Err(e) = metamodule::exec_mount_script(module_dir) {
|
||||||
if !no_mount {
|
warn!("execute metamodule mount failed: {e}");
|
||||||
if let Err(e) = crate::magic_mount::magic_mount(&tmpfs_path) {
|
|
||||||
warn!("do systemless mount failed: {e}");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
info!("no mount requested");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
run_stage("post-mount", true);
|
run_stage("post-mount", true);
|
||||||
|
|
||||||
|
std::env::set_current_dir("/").with_context(|| "failed to chdir to /")?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,6 +142,13 @@ fn run_stage(stage: &str, block: bool) {
|
|||||||
if let Err(e) = crate::module::exec_common_scripts(&format!("{stage}.d"), block) {
|
if let Err(e) = crate::module::exec_common_scripts(&format!("{stage}.d"), block) {
|
||||||
warn!("Failed to exec common {stage} scripts: {e}");
|
warn!("Failed to exec common {stage} scripts: {e}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// execute metamodule stage script first (priority)
|
||||||
|
if let Err(e) = metamodule::exec_stage_script(stage, block) {
|
||||||
|
warn!("Failed to exec metamodule {stage} script: {e}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// execute regular modules stage scripts
|
||||||
if let Err(e) = crate::module::exec_stage_script(stage, block) {
|
if let Err(e) = crate::module::exec_stage_script(stage, block) {
|
||||||
warn!("Failed to exec {stage} scripts: {e}");
|
warn!("Failed to exec {stage} scripts: {e}");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ setup_flashable() {
|
|||||||
$BOOTMODE && return
|
$BOOTMODE && return
|
||||||
if [ -z $OUTFD ] || readlink /proc/$$/fd/$OUTFD | grep -q /tmp; then
|
if [ -z $OUTFD ] || readlink /proc/$$/fd/$OUTFD | grep -q /tmp; then
|
||||||
# We will have to manually find out OUTFD
|
# We will have to manually find out OUTFD
|
||||||
for FD in /proc/$$/fd/*; do
|
for FD in `ls /proc/$$/fd`; do
|
||||||
if readlink /proc/$$/fd/$FD | grep -q pipe; then
|
if readlink /proc/$$/fd/$FD | grep -q pipe; then
|
||||||
if ps | grep -v grep | grep -qE " 3 $FD |status_fd=$FD"; then
|
if ps | grep -v grep | grep -qE " 3 $FD |status_fd=$FD"; then
|
||||||
OUTFD=$FD
|
OUTFD=$FD
|
||||||
@@ -313,14 +313,6 @@ mark_remove() {
|
|||||||
chmod 644 $1
|
chmod 644 $1
|
||||||
}
|
}
|
||||||
|
|
||||||
mark_replace() {
|
|
||||||
# REPLACE must be directory!!!
|
|
||||||
# https://docs.kernel.org/filesystems/overlayfs.html#whiteouts-and-opaque-directories
|
|
||||||
mkdir -p $1 2>/dev/null
|
|
||||||
setfattr -n trusted.overlay.opaque -v y $1
|
|
||||||
chmod 644 $1
|
|
||||||
}
|
|
||||||
|
|
||||||
request_size_check() {
|
request_size_check() {
|
||||||
reqSizeM=`du -ms "$1" | cut -f1`
|
reqSizeM=`du -ms "$1" | cut -f1`
|
||||||
}
|
}
|
||||||
@@ -338,16 +330,19 @@ is_legacy_script() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handle_partition() {
|
handle_partition() {
|
||||||
PARTITION="$1"
|
# if /system/vendor is a symlink, we need to move it out of $MODPATH/system
|
||||||
REQUIRE_SYMLINK="$2"
|
# if /system/vendor is a normal directory, no special handling is needed.
|
||||||
if [ ! -e "$MODPATH/system/$PARTITION" ]; then
|
if [ ! -e $MODPATH/system/$1 ]; then
|
||||||
# no partition found
|
# no partition found
|
||||||
return;
|
return;
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$REQUIRE_SYMLINK" = "false" ] || [ -L "/system/$PARTITION" ] && [ "$(readlink -f "/system/$PARTITION")" = "/$PARTITION" ]; then
|
# we move the folder to / only if it is a native folder that is not a symlink
|
||||||
ui_print "- Handle partition /$PARTITION"
|
if [ -d "/$1" ] && [ ! -L "/$1" ]; then
|
||||||
ln -sf "./system/$PARTITION" "$MODPATH/$PARTITION"
|
ui_print "- Handle partition /$1"
|
||||||
|
# we create a symlink if module want to access $MODPATH/system/$1
|
||||||
|
# but it doesn't always work(ie. write it in post-fs-data.sh would fail because it is readonly)
|
||||||
|
mv -f $MODPATH/system/$1 $MODPATH/$1 && ln -sf ../$1 $MODPATH/system/$1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -428,23 +423,23 @@ install_module() {
|
|||||||
[ -f $MODPATH/customize.sh ] && . $MODPATH/customize.sh
|
[ -f $MODPATH/customize.sh ] && . $MODPATH/customize.sh
|
||||||
fi
|
fi
|
||||||
|
|
||||||
handle_partition vendor true
|
|
||||||
handle_partition system_ext true
|
|
||||||
handle_partition product true
|
|
||||||
handle_partition odm false
|
|
||||||
|
|
||||||
# Handle replace folders
|
# Handle replace folders
|
||||||
for TARGET in $REPLACE; do
|
for TARGET in $REPLACE; do
|
||||||
ui_print "- Replace target: $TARGET"
|
ui_print "- Replace target: $TARGET"
|
||||||
mark_replace "$MODPATH$TARGET"
|
mark_replace $MODPATH$TARGET
|
||||||
done
|
done
|
||||||
|
|
||||||
# Handle remove files
|
# Handle remove files
|
||||||
for TARGET in $REMOVE; do
|
for TARGET in $REMOVE; do
|
||||||
ui_print "- Remove target: $TARGET"
|
ui_print "- Remove target: $TARGET"
|
||||||
mark_remove "$MODPATH$TARGET"
|
mark_remove $MODPATH$TARGET
|
||||||
done
|
done
|
||||||
|
|
||||||
|
handle_partition vendor
|
||||||
|
handle_partition system_ext
|
||||||
|
handle_partition product
|
||||||
|
handle_partition odm
|
||||||
|
|
||||||
if $BOOTMODE; then
|
if $BOOTMODE; then
|
||||||
mktouch $NVBASE/modules/$MODID/update
|
mktouch $NVBASE/modules/$MODID/update
|
||||||
rm -rf $NVBASE/modules/$MODID/remove 2>/dev/null
|
rm -rf $NVBASE/modules/$MODID/remove 2>/dev/null
|
||||||
|
|||||||
@@ -1,465 +0,0 @@
|
|||||||
use std::{
|
|
||||||
cmp::PartialEq,
|
|
||||||
collections::{HashMap, hash_map::Entry},
|
|
||||||
fs::{self, DirEntry, FileType, create_dir, create_dir_all, read_dir, read_link},
|
|
||||||
os::unix::fs::{FileTypeExt, symlink},
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
};
|
|
||||||
|
|
||||||
use anyhow::{Context, Result, bail};
|
|
||||||
use extattr::lgetxattr;
|
|
||||||
use rustix::{
|
|
||||||
fs::{
|
|
||||||
Gid, MetadataExt, Mode, MountFlags, MountPropagationFlags, Uid, UnmountFlags, bind_mount,
|
|
||||||
chmod, chown, mount, move_mount, remount, unmount,
|
|
||||||
},
|
|
||||||
mount::mount_change,
|
|
||||||
path::Arg,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
defs::{DISABLE_FILE_NAME, KSU_MOUNT_SOURCE, MODULE_DIR, SKIP_MOUNT_FILE_NAME},
|
|
||||||
magic_mount::NodeFileType::{Directory, RegularFile, Symlink, Whiteout},
|
|
||||||
restorecon::{lgetfilecon, lsetfilecon},
|
|
||||||
utils::ensure_dir_exists,
|
|
||||||
};
|
|
||||||
|
|
||||||
const REPLACE_DIR_XATTR: &str = "trusted.overlay.opaque";
|
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Hash, Clone, Debug)]
|
|
||||||
enum NodeFileType {
|
|
||||||
RegularFile,
|
|
||||||
Directory,
|
|
||||||
Symlink,
|
|
||||||
Whiteout,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NodeFileType {
|
|
||||||
fn from_file_type(file_type: FileType) -> Option<Self> {
|
|
||||||
if file_type.is_file() {
|
|
||||||
Some(RegularFile)
|
|
||||||
} else if file_type.is_dir() {
|
|
||||||
Some(Directory)
|
|
||||||
} else if file_type.is_symlink() {
|
|
||||||
Some(Symlink)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct Node {
|
|
||||||
name: String,
|
|
||||||
file_type: NodeFileType,
|
|
||||||
children: HashMap<String, Node>,
|
|
||||||
// the module that owned this node
|
|
||||||
module_path: Option<PathBuf>,
|
|
||||||
replace: bool,
|
|
||||||
skip: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Node {
|
|
||||||
fn collect_module_files<P>(&mut self, module_dir: P) -> Result<bool>
|
|
||||||
where
|
|
||||||
P: AsRef<Path>,
|
|
||||||
{
|
|
||||||
let dir = module_dir.as_ref();
|
|
||||||
let mut has_file = false;
|
|
||||||
for entry in dir.read_dir()?.flatten() {
|
|
||||||
let name = entry.file_name().to_string_lossy().to_string();
|
|
||||||
|
|
||||||
let node = match self.children.entry(name.clone()) {
|
|
||||||
Entry::Occupied(o) => Some(o.into_mut()),
|
|
||||||
Entry::Vacant(v) => Self::new_module(&name, &entry).map(|it| v.insert(it)),
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(node) = node {
|
|
||||||
has_file |= if node.file_type == Directory {
|
|
||||||
node.collect_module_files(dir.join(&node.name))? || node.replace
|
|
||||||
} else {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(has_file)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new_root<T>(name: T) -> Self
|
|
||||||
where
|
|
||||||
T: ToString,
|
|
||||||
{
|
|
||||||
Node {
|
|
||||||
name: name.to_string(),
|
|
||||||
file_type: Directory,
|
|
||||||
children: Default::default(),
|
|
||||||
module_path: None,
|
|
||||||
replace: false,
|
|
||||||
skip: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new_module<T>(name: T, entry: &DirEntry) -> Option<Self>
|
|
||||||
where
|
|
||||||
T: ToString,
|
|
||||||
{
|
|
||||||
if let Ok(metadata) = entry.metadata() {
|
|
||||||
let path = entry.path();
|
|
||||||
let file_type = if metadata.file_type().is_char_device() && metadata.rdev() == 0 {
|
|
||||||
Some(Whiteout)
|
|
||||||
} else {
|
|
||||||
NodeFileType::from_file_type(metadata.file_type())
|
|
||||||
};
|
|
||||||
if let Some(file_type) = file_type {
|
|
||||||
let mut replace = false;
|
|
||||||
if file_type == Directory
|
|
||||||
&& let Ok(v) = lgetxattr(&path, REPLACE_DIR_XATTR)
|
|
||||||
&& String::from_utf8_lossy(&v) == "y"
|
|
||||||
{
|
|
||||||
replace = true;
|
|
||||||
}
|
|
||||||
return Some(Node {
|
|
||||||
name: name.to_string(),
|
|
||||||
file_type,
|
|
||||||
children: Default::default(),
|
|
||||||
module_path: Some(path),
|
|
||||||
replace,
|
|
||||||
skip: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn collect_module_files() -> Result<Option<Node>> {
|
|
||||||
let mut root = Node::new_root("");
|
|
||||||
let mut system = Node::new_root("system");
|
|
||||||
let module_root = Path::new(MODULE_DIR);
|
|
||||||
let mut has_file = false;
|
|
||||||
for entry in module_root.read_dir()?.flatten() {
|
|
||||||
if !entry.file_type()?.is_dir() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if entry.path().join(DISABLE_FILE_NAME).exists()
|
|
||||||
|| entry.path().join(SKIP_MOUNT_FILE_NAME).exists()
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mod_system = entry.path().join("system");
|
|
||||||
if !mod_system.is_dir() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
log::debug!("collecting {}", entry.path().display());
|
|
||||||
|
|
||||||
has_file |= system.collect_module_files(&mod_system)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if has_file {
|
|
||||||
for (partition, require_symlink) in [
|
|
||||||
("vendor", true),
|
|
||||||
("system_ext", true),
|
|
||||||
("product", true),
|
|
||||||
("odm", false),
|
|
||||||
] {
|
|
||||||
let path_of_root = Path::new("/").join(partition);
|
|
||||||
let path_of_system = Path::new("/system").join(partition);
|
|
||||||
if path_of_root.is_dir() && (!require_symlink || path_of_system.is_symlink()) {
|
|
||||||
let name = partition.to_string();
|
|
||||||
if let Some(node) = system.children.remove(&name) {
|
|
||||||
root.children.insert(name, node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
root.children.insert("system".to_string(), system);
|
|
||||||
Ok(Some(root))
|
|
||||||
} else {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn clone_symlink<P>(src: P, dst: P) -> Result<()>
|
|
||||||
where
|
|
||||||
P: AsRef<Path>,
|
|
||||||
{
|
|
||||||
let src_symlink = read_link(src.as_ref())?;
|
|
||||||
symlink(&src_symlink, dst.as_ref())?;
|
|
||||||
lsetfilecon(dst.as_ref(), lgetfilecon(src.as_ref())?.as_str())?;
|
|
||||||
log::debug!(
|
|
||||||
"clone symlink {} -> {}({})",
|
|
||||||
dst.as_ref().display(),
|
|
||||||
dst.as_ref().display(),
|
|
||||||
src_symlink.display()
|
|
||||||
);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn mount_mirror<P>(path: P, work_dir_path: P, entry: &DirEntry) -> Result<()>
|
|
||||||
where
|
|
||||||
P: AsRef<Path>,
|
|
||||||
{
|
|
||||||
let path = path.as_ref().join(entry.file_name());
|
|
||||||
let work_dir_path = work_dir_path.as_ref().join(entry.file_name());
|
|
||||||
let file_type = entry.file_type()?;
|
|
||||||
|
|
||||||
if file_type.is_file() {
|
|
||||||
log::debug!(
|
|
||||||
"mount mirror file {} -> {}",
|
|
||||||
path.display(),
|
|
||||||
work_dir_path.display()
|
|
||||||
);
|
|
||||||
fs::File::create(&work_dir_path)?;
|
|
||||||
bind_mount(&path, &work_dir_path)?;
|
|
||||||
} else if file_type.is_dir() {
|
|
||||||
log::debug!(
|
|
||||||
"mount mirror dir {} -> {}",
|
|
||||||
path.display(),
|
|
||||||
work_dir_path.display()
|
|
||||||
);
|
|
||||||
create_dir(&work_dir_path)?;
|
|
||||||
let metadata = entry.metadata()?;
|
|
||||||
chmod(&work_dir_path, Mode::from_raw_mode(metadata.mode()))?;
|
|
||||||
unsafe {
|
|
||||||
chown(
|
|
||||||
&work_dir_path,
|
|
||||||
Some(Uid::from_raw(metadata.uid())),
|
|
||||||
Some(Gid::from_raw(metadata.gid())),
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
lsetfilecon(&work_dir_path, lgetfilecon(&path)?.as_str())?;
|
|
||||||
for entry in read_dir(&path)?.flatten() {
|
|
||||||
mount_mirror(&path, &work_dir_path, &entry)?;
|
|
||||||
}
|
|
||||||
} else if file_type.is_symlink() {
|
|
||||||
log::debug!(
|
|
||||||
"create mirror symlink {} -> {}",
|
|
||||||
path.display(),
|
|
||||||
work_dir_path.display()
|
|
||||||
);
|
|
||||||
clone_symlink(&path, &work_dir_path)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn do_magic_mount<P, WP>(path: P, work_dir_path: WP, current: Node, has_tmpfs: bool) -> Result<()>
|
|
||||||
where
|
|
||||||
P: AsRef<Path>,
|
|
||||||
WP: AsRef<Path>,
|
|
||||||
{
|
|
||||||
let mut current = current;
|
|
||||||
let path = path.as_ref().join(¤t.name);
|
|
||||||
let work_dir_path = work_dir_path.as_ref().join(¤t.name);
|
|
||||||
match current.file_type {
|
|
||||||
RegularFile => {
|
|
||||||
let target_path = if has_tmpfs {
|
|
||||||
fs::File::create(&work_dir_path)?;
|
|
||||||
&work_dir_path
|
|
||||||
} else {
|
|
||||||
&path
|
|
||||||
};
|
|
||||||
if let Some(module_path) = ¤t.module_path {
|
|
||||||
log::debug!(
|
|
||||||
"mount module file {} -> {}",
|
|
||||||
module_path.display(),
|
|
||||||
work_dir_path.display()
|
|
||||||
);
|
|
||||||
bind_mount(module_path, target_path).with_context(|| {
|
|
||||||
format!("mount module file {module_path:?} -> {work_dir_path:?}")
|
|
||||||
})?;
|
|
||||||
// we should use MS_REMOUNT | MS_BIND | MS_xxx to change mount flags
|
|
||||||
if let Err(e) = remount(target_path, MountFlags::RDONLY | MountFlags::BIND, "") {
|
|
||||||
log::warn!("make file {target_path:?} ro: {e:#?}");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
bail!("cannot mount root file {}!", path.display());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Symlink => {
|
|
||||||
if let Some(module_path) = ¤t.module_path {
|
|
||||||
log::debug!(
|
|
||||||
"create module symlink {} -> {}",
|
|
||||||
module_path.display(),
|
|
||||||
work_dir_path.display()
|
|
||||||
);
|
|
||||||
clone_symlink(module_path, &work_dir_path).with_context(|| {
|
|
||||||
format!("create module symlink {module_path:?} -> {work_dir_path:?}")
|
|
||||||
})?;
|
|
||||||
} else {
|
|
||||||
bail!("cannot mount root symlink {}!", path.display());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Directory => {
|
|
||||||
let mut create_tmpfs = !has_tmpfs && current.replace && current.module_path.is_some();
|
|
||||||
if !has_tmpfs && !create_tmpfs {
|
|
||||||
for it in &mut current.children {
|
|
||||||
let (name, node) = it;
|
|
||||||
let real_path = path.join(name);
|
|
||||||
let need = match node.file_type {
|
|
||||||
Symlink => true,
|
|
||||||
Whiteout => real_path.exists(),
|
|
||||||
_ => {
|
|
||||||
if let Ok(metadata) = real_path.symlink_metadata() {
|
|
||||||
let file_type = NodeFileType::from_file_type(metadata.file_type())
|
|
||||||
.unwrap_or(Whiteout);
|
|
||||||
file_type != node.file_type || file_type == Symlink
|
|
||||||
} else {
|
|
||||||
// real path not exists
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if need {
|
|
||||||
if current.module_path.is_none() {
|
|
||||||
log::error!(
|
|
||||||
"cannot create tmpfs on {}, ignore: {name}",
|
|
||||||
path.display()
|
|
||||||
);
|
|
||||||
node.skip = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
create_tmpfs = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let has_tmpfs = has_tmpfs || create_tmpfs;
|
|
||||||
|
|
||||||
if has_tmpfs {
|
|
||||||
log::debug!(
|
|
||||||
"creating tmpfs skeleton for {} at {}",
|
|
||||||
path.display(),
|
|
||||||
work_dir_path.display()
|
|
||||||
);
|
|
||||||
create_dir_all(&work_dir_path)?;
|
|
||||||
let (metadata, path) = if path.exists() {
|
|
||||||
(path.metadata()?, &path)
|
|
||||||
} else if let Some(module_path) = ¤t.module_path {
|
|
||||||
(module_path.metadata()?, module_path)
|
|
||||||
} else {
|
|
||||||
bail!("cannot mount root dir {}!", path.display());
|
|
||||||
};
|
|
||||||
chmod(&work_dir_path, Mode::from_raw_mode(metadata.mode()))?;
|
|
||||||
unsafe {
|
|
||||||
chown(
|
|
||||||
&work_dir_path,
|
|
||||||
Some(Uid::from_raw(metadata.uid())),
|
|
||||||
Some(Gid::from_raw(metadata.gid())),
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
lsetfilecon(&work_dir_path, lgetfilecon(path)?.as_str())?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if create_tmpfs {
|
|
||||||
log::debug!(
|
|
||||||
"creating tmpfs for {} at {}",
|
|
||||||
path.display(),
|
|
||||||
work_dir_path.display()
|
|
||||||
);
|
|
||||||
bind_mount(&work_dir_path, &work_dir_path)
|
|
||||||
.context("bind self")
|
|
||||||
.with_context(|| format!("creating tmpfs for {path:?} at {work_dir_path:?}"))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if path.exists() && !current.replace {
|
|
||||||
for entry in path.read_dir()?.flatten() {
|
|
||||||
let name = entry.file_name().to_string_lossy().to_string();
|
|
||||||
let result = if let Some(node) = current.children.remove(&name) {
|
|
||||||
if node.skip {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
do_magic_mount(&path, &work_dir_path, node, has_tmpfs)
|
|
||||||
.with_context(|| format!("magic mount {}/{name}", path.display()))
|
|
||||||
} else if has_tmpfs {
|
|
||||||
mount_mirror(&path, &work_dir_path, &entry)
|
|
||||||
.with_context(|| format!("mount mirror {}/{name}", path.display()))
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Err(e) = result {
|
|
||||||
if has_tmpfs {
|
|
||||||
return Err(e);
|
|
||||||
} else {
|
|
||||||
log::error!("mount child {}/{name} failed: {e:#?}", path.display());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if current.replace {
|
|
||||||
if current.module_path.is_none() {
|
|
||||||
bail!(
|
|
||||||
"dir {} is declared as replaced but it is root!",
|
|
||||||
path.display()
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
log::debug!("dir {} is replaced", path.display());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (name, node) in current.children.into_iter() {
|
|
||||||
if node.skip {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if let Err(e) = do_magic_mount(&path, &work_dir_path, node, has_tmpfs)
|
|
||||||
.with_context(|| format!("magic mount {}/{name}", path.display()))
|
|
||||||
{
|
|
||||||
if has_tmpfs {
|
|
||||||
return Err(e);
|
|
||||||
} else {
|
|
||||||
log::error!("mount child {}/{name} failed: {e:#?}", path.display());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if create_tmpfs {
|
|
||||||
log::debug!(
|
|
||||||
"moving tmpfs {} -> {}",
|
|
||||||
work_dir_path.display(),
|
|
||||||
path.display()
|
|
||||||
);
|
|
||||||
if let Err(e) = remount(&work_dir_path, MountFlags::RDONLY | MountFlags::BIND, "") {
|
|
||||||
log::warn!("make dir {path:?} ro: {e:#?}");
|
|
||||||
}
|
|
||||||
move_mount(&work_dir_path, &path)
|
|
||||||
.context("move self")
|
|
||||||
.with_context(|| format!("moving tmpfs {work_dir_path:?} -> {path:?}"))?;
|
|
||||||
// make private to reduce peer group count
|
|
||||||
if let Err(e) = mount_change(&path, MountPropagationFlags::PRIVATE) {
|
|
||||||
log::warn!("make dir {path:?} private: {e:#?}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Whiteout => {
|
|
||||||
log::debug!("file {} is removed", path.display());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn magic_mount(tmp_path: &String) -> Result<()> {
|
|
||||||
if let Some(root) = collect_module_files()? {
|
|
||||||
log::debug!("collected: {:#?}", root);
|
|
||||||
let tmp_dir = Path::new(tmp_path).join("workdir");
|
|
||||||
ensure_dir_exists(&tmp_dir)?;
|
|
||||||
mount(KSU_MOUNT_SOURCE, &tmp_dir, "tmpfs", MountFlags::empty(), "").context("mount tmp")?;
|
|
||||||
mount_change(&tmp_dir, MountPropagationFlags::PRIVATE).context("make tmp private")?;
|
|
||||||
let result = do_magic_mount("/", &tmp_dir, root, false);
|
|
||||||
if let Err(e) = unmount(&tmp_dir, UnmountFlags::DETACH) {
|
|
||||||
log::error!("failed to unmount tmp {}", e);
|
|
||||||
}
|
|
||||||
fs::remove_dir(tmp_dir).ok();
|
|
||||||
result
|
|
||||||
} else {
|
|
||||||
log::info!("no modules to mount, skipping!");
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -9,13 +9,13 @@ mod init_event;
|
|||||||
#[cfg(target_arch = "aarch64")]
|
#[cfg(target_arch = "aarch64")]
|
||||||
mod kpm;
|
mod kpm;
|
||||||
mod ksucalls;
|
mod ksucalls;
|
||||||
#[cfg(target_os = "android")]
|
mod metamodule;
|
||||||
mod magic_mount;
|
|
||||||
mod module;
|
mod module;
|
||||||
mod profile;
|
mod profile;
|
||||||
mod restorecon;
|
mod restorecon;
|
||||||
mod sepolicy;
|
mod sepolicy;
|
||||||
mod su;
|
mod su;
|
||||||
|
#[cfg(target_os = "android")]
|
||||||
mod uid_scanner;
|
mod uid_scanner;
|
||||||
mod umount_manager;
|
mod umount_manager;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|||||||
287
userspace/ksud/src/metamodule.rs
Normal file
287
userspace/ksud/src/metamodule.rs
Normal file
@@ -0,0 +1,287 @@
|
|||||||
|
//! Metamodule management
|
||||||
|
//!
|
||||||
|
//! This module handles all metamodule-related functionality.
|
||||||
|
//! Metamodules are special modules that manage how regular modules are mounted
|
||||||
|
//! and provide hooks for module installation/uninstallation.
|
||||||
|
|
||||||
|
use anyhow::{Context, Result, ensure};
|
||||||
|
use log::{info, warn};
|
||||||
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
process::Command,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::module::ModuleType::All;
|
||||||
|
use crate::{assets, defs};
|
||||||
|
|
||||||
|
/// Determine whether the provided module properties mark it as a metamodule
|
||||||
|
pub fn is_metamodule(props: &HashMap<String, String>) -> bool {
|
||||||
|
props
|
||||||
|
.get("metamodule")
|
||||||
|
.map(|s| {
|
||||||
|
let trimmed = s.trim();
|
||||||
|
trimmed == "1" || trimmed.eq_ignore_ascii_case("true")
|
||||||
|
})
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get metamodule path if it exists
|
||||||
|
/// The metamodule is stored in /data/adb/modules/{id} with a symlink at /data/adb/metamodule
|
||||||
|
pub fn get_metamodule_path() -> Option<PathBuf> {
|
||||||
|
let path = Path::new(defs::METAMODULE_DIR);
|
||||||
|
|
||||||
|
// Check if symlink exists and resolve it
|
||||||
|
if path.is_symlink()
|
||||||
|
&& let Ok(target) = std::fs::read_link(path)
|
||||||
|
{
|
||||||
|
// If target is relative, resolve it
|
||||||
|
let resolved = if target.is_absolute() {
|
||||||
|
target
|
||||||
|
} else {
|
||||||
|
path.parent()?.join(target)
|
||||||
|
};
|
||||||
|
|
||||||
|
if resolved.exists() && resolved.is_dir() {
|
||||||
|
return Some(resolved);
|
||||||
|
} else {
|
||||||
|
warn!(
|
||||||
|
"Metamodule symlink points to non-existent path: {:?}",
|
||||||
|
resolved
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: search for metamodule=1 in modules directory
|
||||||
|
let mut result = None;
|
||||||
|
let _ = crate::module::foreach_module(All, |module_path| {
|
||||||
|
if let Ok(props) = crate::module::read_module_prop(module_path)
|
||||||
|
&& is_metamodule(&props)
|
||||||
|
{
|
||||||
|
info!("Found metamodule in modules directory: {:?}", module_path);
|
||||||
|
result = Some(module_path.to_path_buf());
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if metamodule exists
|
||||||
|
pub fn has_metamodule() -> bool {
|
||||||
|
get_metamodule_path().is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if it's safe to install a regular module
|
||||||
|
/// Returns Ok(()) if safe, Err(is_disabled) if blocked
|
||||||
|
/// - Err(true) means metamodule is disabled
|
||||||
|
/// - Err(false) means metamodule is in other unstable state
|
||||||
|
pub fn check_install_safety() -> Result<(), bool> {
|
||||||
|
// No metamodule → safe
|
||||||
|
let Some(metamodule_path) = get_metamodule_path() else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
// No metainstall.sh → safe (uses default installer)
|
||||||
|
// The staged update directory may contain the latest scripts, so check both locations
|
||||||
|
let has_metainstall = metamodule_path
|
||||||
|
.join(defs::METAMODULE_METAINSTALL_SCRIPT)
|
||||||
|
.exists()
|
||||||
|
|| metamodule_path.file_name().is_some_and(|module_id| {
|
||||||
|
Path::new(defs::MODULE_UPDATE_DIR)
|
||||||
|
.join(module_id)
|
||||||
|
.join(defs::METAMODULE_METAINSTALL_SCRIPT)
|
||||||
|
.exists()
|
||||||
|
});
|
||||||
|
if !has_metainstall {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for marker files
|
||||||
|
let has_update = metamodule_path.join(defs::UPDATE_FILE_NAME).exists();
|
||||||
|
let has_remove = metamodule_path.join(defs::REMOVE_FILE_NAME).exists();
|
||||||
|
let has_disable = metamodule_path.join(defs::DISABLE_FILE_NAME).exists();
|
||||||
|
|
||||||
|
// Stable state (no markers) → safe
|
||||||
|
if !has_update && !has_remove && !has_disable {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return true if disabled, false for other unstable states
|
||||||
|
Err(has_disable && !has_update && !has_remove)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create or update the metamodule symlink
|
||||||
|
/// Points /data/adb/metamodule -> /data/adb/modules/{module_id}
|
||||||
|
pub(crate) fn ensure_symlink(module_path: &Path) -> Result<()> {
|
||||||
|
// METAMODULE_DIR might have trailing slash, so we need to trim it
|
||||||
|
let symlink_path = Path::new(defs::METAMODULE_DIR.trim_end_matches('/'));
|
||||||
|
|
||||||
|
info!(
|
||||||
|
"Creating metamodule symlink: {:?} -> {:?}",
|
||||||
|
symlink_path, module_path
|
||||||
|
);
|
||||||
|
|
||||||
|
// Remove existing symlink if it exists
|
||||||
|
if symlink_path.exists() || symlink_path.is_symlink() {
|
||||||
|
info!("Removing old metamodule symlink/path");
|
||||||
|
if symlink_path.is_symlink() {
|
||||||
|
std::fs::remove_file(symlink_path).with_context(|| "Failed to remove old symlink")?;
|
||||||
|
} else {
|
||||||
|
// Could be a directory, remove it
|
||||||
|
std::fs::remove_dir_all(symlink_path)
|
||||||
|
.with_context(|| "Failed to remove old directory")?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create symlink
|
||||||
|
#[cfg(unix)]
|
||||||
|
std::os::unix::fs::symlink(module_path, symlink_path)
|
||||||
|
.with_context(|| format!("Failed to create symlink to {:?}", module_path))?;
|
||||||
|
|
||||||
|
info!("Metamodule symlink created successfully");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove the metamodule symlink
|
||||||
|
pub(crate) fn remove_symlink() -> Result<()> {
|
||||||
|
let symlink_path = Path::new(defs::METAMODULE_DIR.trim_end_matches('/'));
|
||||||
|
|
||||||
|
if symlink_path.is_symlink() {
|
||||||
|
std::fs::remove_file(symlink_path)
|
||||||
|
.with_context(|| "Failed to remove metamodule symlink")?;
|
||||||
|
info!("Metamodule symlink removed");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the install script content, using metainstall.sh from metamodule if available
|
||||||
|
/// Returns the script content to be executed
|
||||||
|
pub(crate) fn get_install_script(
|
||||||
|
is_metamodule: bool,
|
||||||
|
installer_content: &str,
|
||||||
|
install_module_script: &str,
|
||||||
|
) -> Result<String> {
|
||||||
|
// Check if there's a metamodule with metainstall.sh
|
||||||
|
// Only apply this logic for regular modules (not when installing metamodule itself)
|
||||||
|
let install_script = if !is_metamodule {
|
||||||
|
if let Some(metamodule_path) = get_metamodule_path() {
|
||||||
|
if metamodule_path.join(defs::DISABLE_FILE_NAME).exists() {
|
||||||
|
info!("Metamodule is disabled, using default installer");
|
||||||
|
install_module_script.to_string()
|
||||||
|
} else {
|
||||||
|
let metainstall_path = metamodule_path.join(defs::METAMODULE_METAINSTALL_SCRIPT);
|
||||||
|
|
||||||
|
if metainstall_path.exists() {
|
||||||
|
info!("Using metainstall.sh from metamodule");
|
||||||
|
let metamodule_content = std::fs::read_to_string(&metainstall_path)
|
||||||
|
.with_context(|| "Failed to read metamodule metainstall.sh")?;
|
||||||
|
format!("{}\n{}\nexit 0\n", installer_content, metamodule_content)
|
||||||
|
} else {
|
||||||
|
info!("Metamodule exists but has no metainstall.sh, using default installer");
|
||||||
|
install_module_script.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
info!("No metamodule found, using default installer");
|
||||||
|
install_module_script.to_string()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
info!("Installing metamodule, using default installer");
|
||||||
|
install_module_script.to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(install_script)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if metamodule script exists and is ready to execute
|
||||||
|
/// Returns None if metamodule doesn't exist, is disabled, or script is missing
|
||||||
|
/// Returns Some(script_path) if script is ready to execute
|
||||||
|
fn check_metamodule_script(script_name: &str) -> Option<PathBuf> {
|
||||||
|
// Check if metamodule exists
|
||||||
|
let metamodule_path = get_metamodule_path()?;
|
||||||
|
|
||||||
|
// Check if metamodule is disabled
|
||||||
|
if metamodule_path.join(defs::DISABLE_FILE_NAME).exists() {
|
||||||
|
info!("Metamodule is disabled, skipping {}", script_name);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if script exists
|
||||||
|
let script_path = metamodule_path.join(script_name);
|
||||||
|
if !script_path.exists() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(script_path)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Execute metamodule's metauninstall.sh for a specific module
|
||||||
|
pub(crate) fn exec_metauninstall_script(module_id: &str) -> Result<()> {
|
||||||
|
let Some(metauninstall_path) = check_metamodule_script(defs::METAMODULE_METAUNINSTALL_SCRIPT)
|
||||||
|
else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
info!(
|
||||||
|
"Executing metamodule metauninstall.sh for module: {}",
|
||||||
|
module_id
|
||||||
|
);
|
||||||
|
|
||||||
|
let result = Command::new(assets::BUSYBOX_PATH)
|
||||||
|
.args(["sh", metauninstall_path.to_str().unwrap()])
|
||||||
|
.current_dir(metauninstall_path.parent().unwrap())
|
||||||
|
.envs(crate::module::get_common_script_envs())
|
||||||
|
.env("MODULE_ID", module_id)
|
||||||
|
.status()?;
|
||||||
|
|
||||||
|
ensure!(
|
||||||
|
result.success(),
|
||||||
|
"Metamodule metauninstall.sh failed for module {}: {:?}",
|
||||||
|
module_id,
|
||||||
|
result
|
||||||
|
);
|
||||||
|
|
||||||
|
info!(
|
||||||
|
"Metamodule metauninstall.sh executed successfully for {}",
|
||||||
|
module_id
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Execute metamodule mount script
|
||||||
|
pub fn exec_mount_script(module_dir: &str) -> Result<()> {
|
||||||
|
let Some(mount_script) = check_metamodule_script(defs::METAMODULE_MOUNT_SCRIPT) else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
info!("Executing mount script for metamodule");
|
||||||
|
|
||||||
|
let result = Command::new(assets::BUSYBOX_PATH)
|
||||||
|
.args(["sh", mount_script.to_str().unwrap()])
|
||||||
|
.envs(crate::module::get_common_script_envs())
|
||||||
|
.env("MODULE_DIR", module_dir)
|
||||||
|
.status()?;
|
||||||
|
|
||||||
|
ensure!(
|
||||||
|
result.success(),
|
||||||
|
"Metamodule mount script failed with status: {:?}",
|
||||||
|
result
|
||||||
|
);
|
||||||
|
|
||||||
|
info!("Metamodule mount script executed successfully");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Execute metamodule script for a specific stage
|
||||||
|
pub fn exec_stage_script(stage: &str, block: bool) -> Result<()> {
|
||||||
|
let Some(script_path) = check_metamodule_script(&format!("{}.sh", stage)) else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
info!("Executing metamodule {}.sh", stage);
|
||||||
|
crate::module::exec_script(&script_path, block)?;
|
||||||
|
info!("Metamodule {}.sh executed successfully", stage);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -1,14 +1,9 @@
|
|||||||
use std::fs::{copy, rename};
|
#[allow(clippy::wildcard_imports)]
|
||||||
#[cfg(unix)]
|
use crate::utils::*;
|
||||||
use std::os::unix::{prelude::PermissionsExt, process::CommandExt};
|
use crate::{
|
||||||
use std::{
|
assets, defs, ksucalls, metamodule,
|
||||||
collections::HashMap,
|
restorecon::{restore_syscon, setsyscon},
|
||||||
env::var as env_var,
|
sepolicy,
|
||||||
fs::{File, Permissions, remove_dir_all, remove_file, set_permissions},
|
|
||||||
io::Cursor,
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
process::Command,
|
|
||||||
str::FromStr,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::{Context, Result, anyhow, bail, ensure};
|
use anyhow::{Context, Result, anyhow, bail, ensure};
|
||||||
@@ -16,17 +11,23 @@ use const_format::concatcp;
|
|||||||
use is_executable::is_executable;
|
use is_executable::is_executable;
|
||||||
use java_properties::PropertiesIter;
|
use java_properties::PropertiesIter;
|
||||||
use log::{info, warn};
|
use log::{info, warn};
|
||||||
|
|
||||||
|
use std::fs::{copy, rename};
|
||||||
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
env::var as env_var,
|
||||||
|
fs::{File, Permissions, remove_dir_all, set_permissions},
|
||||||
|
io::Cursor,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
process::Command,
|
||||||
|
str::FromStr,
|
||||||
|
};
|
||||||
use zip_extensions::zip_extract_file_to_memory;
|
use zip_extensions::zip_extract_file_to_memory;
|
||||||
|
|
||||||
#[allow(clippy::wildcard_imports)]
|
use crate::defs::{MODULE_DIR, MODULE_UPDATE_DIR, UPDATE_FILE_NAME};
|
||||||
use crate::{
|
use crate::module::ModuleType::{Active, All};
|
||||||
assets,
|
#[cfg(unix)]
|
||||||
defs::{self, MODULE_DIR, MODULE_UPDATE_DIR, UPDATE_FILE_NAME},
|
use std::os::unix::{prelude::PermissionsExt, process::CommandExt};
|
||||||
ksucalls,
|
|
||||||
restorecon::{restore_syscon, setsyscon},
|
|
||||||
sepolicy,
|
|
||||||
utils::*,
|
|
||||||
};
|
|
||||||
|
|
||||||
const INSTALLER_CONTENT: &str = include_str!("./installer.sh");
|
const INSTALLER_CONTENT: &str = include_str!("./installer.sh");
|
||||||
const INSTALL_MODULE_SCRIPT: &str = concatcp!(
|
const INSTALL_MODULE_SCRIPT: &str = concatcp!(
|
||||||
@@ -38,27 +39,36 @@ const INSTALL_MODULE_SCRIPT: &str = concatcp!(
|
|||||||
"\n"
|
"\n"
|
||||||
);
|
);
|
||||||
|
|
||||||
fn exec_install_script(module_file: &str) -> Result<()> {
|
/// Get common environment variables for script execution
|
||||||
let realpath = std::fs::canonicalize(module_file)
|
pub(crate) fn get_common_script_envs() -> Vec<(&'static str, String)> {
|
||||||
.with_context(|| format!("realpath: {module_file} failed"))?;
|
vec![
|
||||||
|
("ASH_STANDALONE", "1".to_string()),
|
||||||
let result = Command::new(assets::BUSYBOX_PATH)
|
("KSU", "true".to_string()),
|
||||||
.args(["sh", "-c", INSTALL_MODULE_SCRIPT])
|
("KSU_KERNEL_VER_CODE", ksucalls::get_version().to_string()),
|
||||||
.env("ASH_STANDALONE", "1")
|
("KSU_VER_CODE", defs::VERSION_CODE.to_string()),
|
||||||
.env(
|
("KSU_VER", defs::VERSION_NAME.to_string()),
|
||||||
|
(
|
||||||
"PATH",
|
"PATH",
|
||||||
format!(
|
format!(
|
||||||
"{}:{}",
|
"{}:{}",
|
||||||
env_var("PATH").unwrap(),
|
env_var("PATH").unwrap_or_default(),
|
||||||
defs::BINARY_DIR.trim_end_matches('/')
|
defs::BINARY_DIR.trim_end_matches('/')
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
.env("KSU", "true")
|
]
|
||||||
.env("KSU_SUKISU", "true")
|
}
|
||||||
.env("KSU_KERNEL_VER_CODE", ksucalls::get_version().to_string())
|
|
||||||
.env("KSU_VER", defs::VERSION_NAME)
|
fn exec_install_script(module_file: &str, is_metamodule: bool) -> Result<()> {
|
||||||
.env("KSU_VER_CODE", defs::VERSION_CODE)
|
let realpath = std::fs::canonicalize(module_file)
|
||||||
.env("KSU_MAGIC_MOUNT", "true")
|
.with_context(|| format!("realpath: {module_file} failed"))?;
|
||||||
|
|
||||||
|
// Get install script from metamodule module
|
||||||
|
let install_script =
|
||||||
|
metamodule::get_install_script(is_metamodule, INSTALLER_CONTENT, INSTALL_MODULE_SCRIPT)?;
|
||||||
|
|
||||||
|
let result = Command::new(assets::BUSYBOX_PATH)
|
||||||
|
.args(["sh", "-c", &install_script])
|
||||||
|
.envs(get_common_script_envs())
|
||||||
.env("OUTFD", "1")
|
.env("OUTFD", "1")
|
||||||
.env("ZIPFILE", realpath)
|
.env("ZIPFILE", realpath)
|
||||||
.status()?;
|
.status()?;
|
||||||
@@ -66,10 +76,7 @@ fn exec_install_script(module_file: &str) -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// becuase we use something like A-B update
|
// Check if Android boot is completed before installing modules
|
||||||
// we need to update the module state after the boot_completed
|
|
||||||
// if someone(such as the module) install a module before the boot_completed
|
|
||||||
// then it may cause some problems, just forbid it
|
|
||||||
fn ensure_boot_completed() -> Result<()> {
|
fn ensure_boot_completed() -> Result<()> {
|
||||||
// ensure getprop sys.boot_completed == 1
|
// ensure getprop sys.boot_completed == 1
|
||||||
if getprop("sys.boot_completed").as_deref() != Some("1") {
|
if getprop("sys.boot_completed").as_deref() != Some("1") {
|
||||||
@@ -78,34 +85,21 @@ fn ensure_boot_completed() -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mark_module_state(module: &str, flag_file: &str, create: bool) -> Result<()> {
|
|
||||||
let module_state_file = Path::new(MODULE_DIR).join(module).join(flag_file);
|
|
||||||
if create {
|
|
||||||
ensure_file_exists(module_state_file)
|
|
||||||
} else {
|
|
||||||
if module_state_file.exists() {
|
|
||||||
remove_file(module_state_file)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(PartialEq, Eq)]
|
#[derive(PartialEq, Eq)]
|
||||||
enum ModuleType {
|
pub(crate) enum ModuleType {
|
||||||
All,
|
All,
|
||||||
Active,
|
Active,
|
||||||
Updated,
|
Updated,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn foreach_module(module_type: ModuleType, mut f: impl FnMut(&Path) -> Result<()>) -> Result<()> {
|
pub(crate) fn foreach_module(
|
||||||
|
module_type: ModuleType,
|
||||||
|
mut f: impl FnMut(&Path) -> Result<()>,
|
||||||
|
) -> Result<()> {
|
||||||
let modules_dir = Path::new(match module_type {
|
let modules_dir = Path::new(match module_type {
|
||||||
ModuleType::Updated => MODULE_UPDATE_DIR,
|
ModuleType::Updated => MODULE_UPDATE_DIR,
|
||||||
_ => defs::MODULE_DIR,
|
_ => defs::MODULE_DIR,
|
||||||
});
|
});
|
||||||
if !modules_dir.is_dir() {
|
|
||||||
warn!("{} is not a directory, skip", modules_dir.display());
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
let dir = std::fs::read_dir(modules_dir)?;
|
let dir = std::fs::read_dir(modules_dir)?;
|
||||||
for entry in dir.flatten() {
|
for entry in dir.flatten() {
|
||||||
let path = entry.path();
|
let path = entry.path();
|
||||||
@@ -114,11 +108,11 @@ fn foreach_module(module_type: ModuleType, mut f: impl FnMut(&Path) -> Result<()
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if module_type == ModuleType::Active && path.join(defs::DISABLE_FILE_NAME).exists() {
|
if module_type == Active && path.join(defs::DISABLE_FILE_NAME).exists() {
|
||||||
info!("{} is disabled, skip", path.display());
|
info!("{} is disabled, skip", path.display());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if module_type == ModuleType::Active && path.join(defs::REMOVE_FILE_NAME).exists() {
|
if module_type == Active && path.join(defs::REMOVE_FILE_NAME).exists() {
|
||||||
warn!("{} is removed, skip", path.display());
|
warn!("{} is removed, skip", path.display());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -130,7 +124,7 @@ fn foreach_module(module_type: ModuleType, mut f: impl FnMut(&Path) -> Result<()
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn foreach_active_module(f: impl FnMut(&Path) -> Result<()>) -> Result<()> {
|
fn foreach_active_module(f: impl FnMut(&Path) -> Result<()>) -> Result<()> {
|
||||||
foreach_module(ModuleType::Active, f)
|
foreach_module(Active, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_sepolicy_rule() -> Result<()> {
|
pub fn load_sepolicy_rule() -> Result<()> {
|
||||||
@@ -150,7 +144,7 @@ pub fn load_sepolicy_rule() -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn exec_script<T: AsRef<Path>>(path: T, wait: bool) -> Result<()> {
|
pub fn exec_script<T: AsRef<Path>>(path: T, wait: bool) -> Result<()> {
|
||||||
info!("exec {}", path.as_ref().display());
|
info!("exec {}", path.as_ref().display());
|
||||||
|
|
||||||
let mut command = &mut Command::new(assets::BUSYBOX_PATH);
|
let mut command = &mut Command::new(assets::BUSYBOX_PATH);
|
||||||
@@ -169,21 +163,7 @@ fn exec_script<T: AsRef<Path>>(path: T, wait: bool) -> Result<()> {
|
|||||||
.current_dir(path.as_ref().parent().unwrap())
|
.current_dir(path.as_ref().parent().unwrap())
|
||||||
.arg("sh")
|
.arg("sh")
|
||||||
.arg(path.as_ref())
|
.arg(path.as_ref())
|
||||||
.env("ASH_STANDALONE", "1")
|
.envs(get_common_script_envs());
|
||||||
.env("KSU", "true")
|
|
||||||
.env("KSU_SUKISU", "true")
|
|
||||||
.env("KSU_KERNEL_VER_CODE", ksucalls::get_version().to_string())
|
|
||||||
.env("KSU_VER_CODE", defs::VERSION_CODE)
|
|
||||||
.env("KSU_VER", defs::VERSION_NAME)
|
|
||||||
.env("KSU_MAGIC_MOUNT", "true")
|
|
||||||
.env(
|
|
||||||
"PATH",
|
|
||||||
format!(
|
|
||||||
"{}:{}",
|
|
||||||
env_var("PATH").unwrap(),
|
|
||||||
defs::BINARY_DIR.trim_end_matches('/')
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
let result = if wait {
|
let result = if wait {
|
||||||
command.status().map(|_| ())
|
command.status().map(|_| ())
|
||||||
@@ -251,45 +231,89 @@ pub fn load_system_prop() -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn prune_modules() -> Result<()> {
|
pub fn prune_modules() -> Result<()> {
|
||||||
foreach_module(ModuleType::All, |module| {
|
foreach_module(All, |module| {
|
||||||
if module.join(defs::REMOVE_FILE_NAME).exists() {
|
if !module.join(defs::REMOVE_FILE_NAME).exists() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
info!("remove module: {}", module.display());
|
info!("remove module: {}", module.display());
|
||||||
|
|
||||||
|
// Execute metamodule's metauninstall.sh first
|
||||||
|
let module_id = module.file_name().and_then(|n| n.to_str()).unwrap_or("");
|
||||||
|
|
||||||
|
// Check if this is a metamodule
|
||||||
|
let is_metamodule = read_module_prop(module)
|
||||||
|
.map(|props| metamodule::is_metamodule(&props))
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
|
if is_metamodule {
|
||||||
|
info!("Removing metamodule symlink");
|
||||||
|
if let Err(e) = metamodule::remove_symlink() {
|
||||||
|
warn!("Failed to remove metamodule symlink: {}", e);
|
||||||
|
}
|
||||||
|
} else if let Err(e) = metamodule::exec_metauninstall_script(module_id) {
|
||||||
|
warn!(
|
||||||
|
"Failed to exec metamodule uninstall for {}: {}",
|
||||||
|
module_id, e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then execute module's own uninstall.sh
|
||||||
let uninstaller = module.join("uninstall.sh");
|
let uninstaller = module.join("uninstall.sh");
|
||||||
if uninstaller.exists()
|
if uninstaller.exists()
|
||||||
&& let Err(e) = exec_script(uninstaller, true)
|
&& let Err(e) = exec_script(uninstaller, true)
|
||||||
{
|
{
|
||||||
warn!("Failed to exec uninstaller: {}", e);
|
warn!("Failed to exec uninstaller: {e}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(e) = remove_dir_all(module) {
|
if let Err(e) = remove_dir_all(module) {
|
||||||
warn!("Failed to remove {}: {}", module.display(), e);
|
warn!("Failed to remove {}: {}", module.display(), e);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
remove_file(module.join(defs::UPDATE_FILE_NAME)).ok();
|
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
// collect remaining modules, if none, clean up metamodule record
|
||||||
|
let remaining_modules: Vec<_> = std::fs::read_dir(defs::MODULE_DIR)?
|
||||||
|
.filter_map(|entry| entry.ok())
|
||||||
|
.filter(|entry| entry.path().join("module.prop").exists())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if remaining_modules.is_empty() {
|
||||||
|
info!("no remaining modules.");
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_updated_modules() -> Result<()> {
|
pub fn handle_updated_modules() -> Result<()> {
|
||||||
let modules_root = Path::new(MODULE_DIR);
|
let modules_root = Path::new(MODULE_DIR);
|
||||||
foreach_module(ModuleType::Updated, |module| {
|
foreach_module(ModuleType::Updated, |updated_module| {
|
||||||
if !module.is_dir() {
|
if !updated_module.is_dir() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(name) = module.file_name() {
|
if let Some(name) = updated_module.file_name() {
|
||||||
let old_dir = modules_root.join(name);
|
let module_dir = modules_root.join(name);
|
||||||
if old_dir.exists()
|
let mut disabled = false;
|
||||||
&& let Err(e) = remove_dir_all(&old_dir)
|
let mut removed = false;
|
||||||
{
|
if module_dir.exists() {
|
||||||
log::error!("Failed to remove old {}: {}", old_dir.display(), e);
|
// If the old module is disabled, we need to also disable the new one
|
||||||
|
disabled = module_dir.join(defs::DISABLE_FILE_NAME).exists();
|
||||||
|
removed = module_dir.join(defs::REMOVE_FILE_NAME).exists();
|
||||||
|
remove_dir_all(&module_dir)?;
|
||||||
|
}
|
||||||
|
rename(updated_module, &module_dir)?;
|
||||||
|
if removed {
|
||||||
|
let path = module_dir.join(defs::REMOVE_FILE_NAME);
|
||||||
|
if let Err(e) = ensure_file_exists(&path) {
|
||||||
|
warn!("Failed to create {}: {}", path.display(), e);
|
||||||
|
}
|
||||||
|
} else if disabled {
|
||||||
|
let path = module_dir.join(defs::DISABLE_FILE_NAME);
|
||||||
|
if let Err(e) = ensure_file_exists(&path) {
|
||||||
|
warn!("Failed to create {}: {}", path.display(), e);
|
||||||
}
|
}
|
||||||
if let Err(e) = rename(module, &old_dir) {
|
|
||||||
log::error!("Failed to move new module {}: {}", module.display(), e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -297,8 +321,7 @@ pub fn handle_updated_modules() -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn install_module(zip: &str) -> Result<()> {
|
fn _install_module(zip: &str) -> Result<()> {
|
||||||
fn inner(zip: &str) -> Result<()> {
|
|
||||||
ensure_boot_completed()?;
|
ensure_boot_completed()?;
|
||||||
|
|
||||||
// print banner
|
// print banner
|
||||||
@@ -323,81 +346,157 @@ pub fn install_module(zip: &str) -> Result<()> {
|
|||||||
module_prop.insert(k, v);
|
module_prop.insert(k, v);
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
info!("module prop: {:?}", module_prop);
|
info!("module prop: {module_prop:?}");
|
||||||
|
|
||||||
let Some(module_id) = module_prop.get("id") else {
|
let Some(module_id) = module_prop.get("id") else {
|
||||||
bail!("module id not found in module.prop!");
|
bail!("module id not found in module.prop!");
|
||||||
};
|
};
|
||||||
let module_id = module_id.trim();
|
let module_id = module_id.trim();
|
||||||
|
|
||||||
let zip_uncompressed_size = get_zip_uncompressed_size(zip)?;
|
// Check if this module is a metamodule
|
||||||
|
let is_metamodule = metamodule::is_metamodule(&module_prop);
|
||||||
|
|
||||||
|
// Check if it's safe to install regular module
|
||||||
|
if !is_metamodule && let Err(is_disabled) = metamodule::check_install_safety() {
|
||||||
|
println!("\n❌ Installation Blocked");
|
||||||
|
println!("┌────────────────────────────────");
|
||||||
|
println!("│ A metamodule with custom installer is active");
|
||||||
|
println!("│");
|
||||||
|
if is_disabled {
|
||||||
|
println!("│ Current state: Disabled");
|
||||||
|
println!("│ Action required: Re-enable or uninstall it, then reboot");
|
||||||
|
} else {
|
||||||
|
println!("│ Current state: Pending changes");
|
||||||
|
println!("│ Action required: Reboot to apply changes first");
|
||||||
|
}
|
||||||
|
println!("└─────────────────────────────────\n");
|
||||||
|
bail!("Metamodule installation blocked");
|
||||||
|
}
|
||||||
|
|
||||||
|
// All modules (including metamodules) are installed to MODULE_UPDATE_DIR
|
||||||
|
let updated_dir = Path::new(defs::MODULE_UPDATE_DIR).join(module_id);
|
||||||
|
|
||||||
|
if is_metamodule {
|
||||||
|
info!("Installing metamodule: {}", module_id);
|
||||||
|
|
||||||
|
// Check if there's already a metamodule installed
|
||||||
|
if metamodule::has_metamodule()
|
||||||
|
&& let Some(existing_path) = metamodule::get_metamodule_path()
|
||||||
|
{
|
||||||
|
let existing_id = read_module_prop(&existing_path)
|
||||||
|
.ok()
|
||||||
|
.and_then(|m| m.get("id").cloned())
|
||||||
|
.unwrap_or_else(|| "unknown".to_string());
|
||||||
|
|
||||||
|
if existing_id != module_id {
|
||||||
|
println!("\n❌ Installation Failed");
|
||||||
|
println!("┌────────────────────────────────");
|
||||||
|
println!("│ A metamodule is already installed");
|
||||||
|
println!("│ Current metamodule: {}", existing_id);
|
||||||
|
println!("│");
|
||||||
|
println!("│ Only one metamodule can be active at a time.");
|
||||||
|
println!("│");
|
||||||
|
println!("│ To install this metamodule:");
|
||||||
|
println!("│ 1. Uninstall the current metamodule");
|
||||||
|
println!("│ 2. Reboot your device");
|
||||||
|
println!("│ 3. Install the new metamodule");
|
||||||
|
println!("└─────────────────────────────────\n");
|
||||||
|
bail!("Cannot install multiple metamodules");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let zip_uncompressed_size = get_zip_uncompressed_size(zip)?;
|
||||||
info!(
|
info!(
|
||||||
"zip uncompressed size: {}",
|
"zip uncompressed size: {}",
|
||||||
humansize::format_size(zip_uncompressed_size, humansize::DECIMAL)
|
humansize::format_size(zip_uncompressed_size, humansize::DECIMAL)
|
||||||
);
|
);
|
||||||
|
|
||||||
println!("- Preparing Zip");
|
|
||||||
println!(
|
println!(
|
||||||
"- Module size: {}",
|
"- Module size: {}",
|
||||||
humansize::format_size(zip_uncompressed_size, humansize::DECIMAL)
|
humansize::format_size(zip_uncompressed_size, humansize::DECIMAL)
|
||||||
);
|
);
|
||||||
|
|
||||||
// ensure modules_update exists
|
// Ensure module directory exists and set SELinux context
|
||||||
ensure_dir_exists(MODULE_UPDATE_DIR)?;
|
ensure_dir_exists(defs::MODULE_UPDATE_DIR)?;
|
||||||
setsyscon(MODULE_UPDATE_DIR)?;
|
setsyscon(defs::MODULE_UPDATE_DIR)?;
|
||||||
|
|
||||||
let update_module_dir = Path::new(MODULE_UPDATE_DIR).join(module_id);
|
// Prepare target directory
|
||||||
ensure_clean_dir(&update_module_dir)?;
|
println!("- Installing to {}", updated_dir.display());
|
||||||
info!("module dir: {}", update_module_dir.display());
|
ensure_clean_dir(&updated_dir)?;
|
||||||
|
info!("target dir: {}", updated_dir.display());
|
||||||
|
|
||||||
let do_install = || -> Result<()> {
|
// Extract zip to target directory
|
||||||
// unzip the image and move it to modules_update/<id> dir
|
println!("- Extracting module files");
|
||||||
let file = File::open(zip)?;
|
let file = File::open(zip)?;
|
||||||
let mut archive = zip::ZipArchive::new(file)?;
|
let mut archive = zip::ZipArchive::new(file)?;
|
||||||
archive.extract(&update_module_dir)?;
|
archive.extract(&updated_dir)?;
|
||||||
|
|
||||||
// set permission and selinux context for $MOD/system
|
// Set permission and selinux context for $MOD/system
|
||||||
let module_system_dir = update_module_dir.join("system");
|
let module_system_dir = updated_dir.join("system");
|
||||||
if module_system_dir.exists() {
|
if module_system_dir.exists() {
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
set_permissions(&module_system_dir, Permissions::from_mode(0o755))?;
|
set_permissions(&module_system_dir, Permissions::from_mode(0o755))?;
|
||||||
restore_syscon(&module_system_dir)?;
|
restore_syscon(&module_system_dir)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
exec_install_script(zip)?;
|
// Execute install script
|
||||||
|
println!("- Running module installer");
|
||||||
|
exec_install_script(zip, is_metamodule)?;
|
||||||
|
|
||||||
let module_dir = Path::new(MODULE_DIR).join(module_id);
|
let module_dir = Path::new(MODULE_DIR).join(module_id);
|
||||||
ensure_dir_exists(&module_dir)?;
|
ensure_dir_exists(&module_dir)?;
|
||||||
copy(
|
copy(
|
||||||
update_module_dir.join("module.prop"),
|
updated_dir.join("module.prop"),
|
||||||
module_dir.join("module.prop"),
|
module_dir.join("module.prop"),
|
||||||
)?;
|
)?;
|
||||||
ensure_file_exists(module_dir.join(UPDATE_FILE_NAME))?;
|
ensure_file_exists(module_dir.join(UPDATE_FILE_NAME))?;
|
||||||
|
|
||||||
info!("Module install successfully!");
|
// Create symlink for metamodule
|
||||||
|
if is_metamodule {
|
||||||
|
println!("- Creating metamodule symlink");
|
||||||
|
metamodule::ensure_symlink(&module_dir)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("- Module installed successfully!");
|
||||||
|
info!("Module {} installed successfully!", module_id);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
};
|
}
|
||||||
let result = do_install();
|
|
||||||
if result.is_err() {
|
pub fn install_module(zip: &str) -> Result<()> {
|
||||||
remove_dir_all(&update_module_dir).ok();
|
let result = _install_module(zip);
|
||||||
}
|
|
||||||
result
|
|
||||||
}
|
|
||||||
let result = inner(zip);
|
|
||||||
if let Err(ref e) = result {
|
if let Err(ref e) = result {
|
||||||
println!("- Error: {e}");
|
println!("- Error: {e}");
|
||||||
}
|
}
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn uninstall_module(id: &str) -> Result<()> {
|
pub fn undo_uninstall_module(id: &str) -> Result<()> {
|
||||||
mark_module_state(id, defs::REMOVE_FILE_NAME, true)
|
let module_path = Path::new(defs::MODULE_DIR).join(id);
|
||||||
|
ensure!(module_path.exists(), "Module {} not found", id);
|
||||||
|
|
||||||
|
// Remove the remove mark
|
||||||
|
let remove_file = module_path.join(defs::REMOVE_FILE_NAME);
|
||||||
|
if remove_file.exists() {
|
||||||
|
std::fs::remove_file(&remove_file)
|
||||||
|
.with_context(|| format!("Failed to delete remove file for module '{}'", id))?;
|
||||||
|
info!("Removed the remove mark for module {}", id);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn restore_uninstall_module(id: &str) -> Result<()> {
|
pub fn uninstall_module(id: &str) -> Result<()> {
|
||||||
mark_module_state(id, defs::REMOVE_FILE_NAME, false)
|
let module_path = Path::new(defs::MODULE_DIR).join(id);
|
||||||
|
ensure!(module_path.exists(), "Module {} not found", id);
|
||||||
|
|
||||||
|
// Mark for removal
|
||||||
|
let remove_file = module_path.join(defs::REMOVE_FILE_NAME);
|
||||||
|
File::create(remove_file).with_context(|| "Failed to create remove file")?;
|
||||||
|
|
||||||
|
info!("Module {} marked for removal", id);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_action(id: &str) -> Result<()> {
|
pub fn run_action(id: &str) -> Result<()> {
|
||||||
@@ -406,11 +505,30 @@ pub fn run_action(id: &str) -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn enable_module(id: &str) -> Result<()> {
|
pub fn enable_module(id: &str) -> Result<()> {
|
||||||
mark_module_state(id, defs::DISABLE_FILE_NAME, false)
|
let module_path = Path::new(defs::MODULE_DIR).join(id);
|
||||||
|
ensure!(module_path.exists(), "Module {} not found", id);
|
||||||
|
|
||||||
|
let disable_path = module_path.join(defs::DISABLE_FILE_NAME);
|
||||||
|
if disable_path.exists() {
|
||||||
|
std::fs::remove_file(&disable_path).with_context(|| {
|
||||||
|
format!("Failed to remove disable file: {}", disable_path.display())
|
||||||
|
})?;
|
||||||
|
info!("Module {} enabled", id);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn disable_module(id: &str) -> Result<()> {
|
pub fn disable_module(id: &str) -> Result<()> {
|
||||||
mark_module_state(id, defs::DISABLE_FILE_NAME, true)
|
let module_path = Path::new(defs::MODULE_DIR).join(id);
|
||||||
|
ensure!(module_path.exists(), "Module {} not found", id);
|
||||||
|
|
||||||
|
let disable_path = module_path.join(defs::DISABLE_FILE_NAME);
|
||||||
|
ensure_file_exists(disable_path)?;
|
||||||
|
|
||||||
|
info!("Module {} disabled", id);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn disable_all_modules() -> Result<()> {
|
pub fn disable_all_modules() -> Result<()> {
|
||||||
@@ -418,11 +536,13 @@ pub fn disable_all_modules() -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn uninstall_all_modules() -> Result<()> {
|
pub fn uninstall_all_modules() -> Result<()> {
|
||||||
|
info!("Uninstalling all modules");
|
||||||
mark_all_modules(defs::REMOVE_FILE_NAME)
|
mark_all_modules(defs::REMOVE_FILE_NAME)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mark_all_modules(flag_file: &str) -> Result<()> {
|
fn mark_all_modules(flag_file: &str) -> Result<()> {
|
||||||
let dir = std::fs::read_dir(MODULE_DIR)?;
|
// we assume the module dir is already mounted
|
||||||
|
let dir = std::fs::read_dir(defs::MODULE_DIR)?;
|
||||||
for entry in dir.flatten() {
|
for entry in dir.flatten() {
|
||||||
let path = entry.path();
|
let path = entry.path();
|
||||||
let flag = path.join(flag_file);
|
let flag = path.join(flag_file);
|
||||||
@@ -472,6 +592,7 @@ fn _list_modules(path: &str) -> Vec<HashMap<String, String>> {
|
|||||||
if !path.join("module.prop").exists() {
|
if !path.join("module.prop").exists() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut module_prop_map = match read_module_prop(&path) {
|
let mut module_prop_map = match read_module_prop(&path) {
|
||||||
Ok(prop) => prop,
|
Ok(prop) => prop,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@@ -481,26 +602,33 @@ fn _list_modules(path: &str) -> Vec<HashMap<String, String>> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// If id is missing or empty, use directory name as fallback
|
// If id is missing or empty, use directory name as fallback
|
||||||
let dir_id = entry.file_name().to_string_lossy().to_string();
|
|
||||||
module_prop_map.insert("dir_id".to_owned(), dir_id.clone());
|
|
||||||
|
|
||||||
if !module_prop_map.contains_key("id") || module_prop_map["id"].is_empty() {
|
if !module_prop_map.contains_key("id") || module_prop_map["id"].is_empty() {
|
||||||
info!("Use dir name as module id: {dir_id}");
|
match entry.file_name().to_str() {
|
||||||
module_prop_map.insert("id".to_owned(), dir_id.clone());
|
Some(id) => {
|
||||||
|
info!("Use dir name as module id: {id}");
|
||||||
|
module_prop_map.insert("id".to_owned(), id.to_owned());
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
info!("Failed to get module id from dir name");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add enabled, update, remove flags
|
// Add enabled, update, remove, web, action flags
|
||||||
let enabled = !path.join(defs::DISABLE_FILE_NAME).exists();
|
let enabled = !path.join(defs::DISABLE_FILE_NAME).exists();
|
||||||
let update = path.join(defs::UPDATE_FILE_NAME).exists();
|
let update = path.join(defs::UPDATE_FILE_NAME).exists();
|
||||||
let remove = path.join(defs::REMOVE_FILE_NAME).exists();
|
let remove = path.join(defs::REMOVE_FILE_NAME).exists();
|
||||||
let web = path.join(defs::MODULE_WEB_DIR).exists();
|
let web = path.join(defs::MODULE_WEB_DIR).exists();
|
||||||
let action = path.join(defs::MODULE_ACTION_SH).exists();
|
let action = path.join(defs::MODULE_ACTION_SH).exists();
|
||||||
|
let need_mount = path.join("system").exists() && !path.join("skip_mount").exists();
|
||||||
|
|
||||||
module_prop_map.insert("enabled".to_owned(), enabled.to_string());
|
module_prop_map.insert("enabled".to_owned(), enabled.to_string());
|
||||||
module_prop_map.insert("update".to_owned(), update.to_string());
|
module_prop_map.insert("update".to_owned(), update.to_string());
|
||||||
module_prop_map.insert("remove".to_owned(), remove.to_string());
|
module_prop_map.insert("remove".to_owned(), remove.to_string());
|
||||||
module_prop_map.insert("web".to_owned(), web.to_string());
|
module_prop_map.insert("web".to_owned(), web.to_string());
|
||||||
module_prop_map.insert("action".to_owned(), action.to_string());
|
module_prop_map.insert("action".to_owned(), action.to_string());
|
||||||
|
module_prop_map.insert("mount".to_owned(), need_mount.to_string());
|
||||||
|
|
||||||
modules.push(module_prop_map);
|
modules.push(module_prop_map);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,11 +62,11 @@ pub fn restore_syscon<P: AsRef<Path>>(dir: P) -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn restore_modules_con<P: AsRef<Path>>(dir: P) -> Result<()> {
|
fn restore_syscon_if_unlabeled<P: AsRef<Path>>(dir: P) -> Result<()> {
|
||||||
for dir_entry in WalkDir::new(dir).parallelism(Serial) {
|
for dir_entry in WalkDir::new(dir).parallelism(Serial) {
|
||||||
if let Some(path) = dir_entry.ok().map(|dir_entry| dir_entry.path())
|
if let Some(path) = dir_entry.ok().map(|dir_entry| dir_entry.path())
|
||||||
&& let Result::Ok(con) = lgetfilecon(&path)
|
&& let Result::Ok(con) = lgetfilecon(&path)
|
||||||
&& (con == ADB_CON || con == UNLABEL_CON || con.is_empty())
|
&& (con == UNLABEL_CON || con.is_empty())
|
||||||
{
|
{
|
||||||
lsetfilecon(&path, SYSTEM_CON)?;
|
lsetfilecon(&path, SYSTEM_CON)?;
|
||||||
}
|
}
|
||||||
@@ -76,6 +76,6 @@ fn restore_modules_con<P: AsRef<Path>>(dir: P) -> Result<()> {
|
|||||||
|
|
||||||
pub fn restorecon() -> Result<()> {
|
pub fn restorecon() -> Result<()> {
|
||||||
lsetfilecon(defs::DAEMON_PATH, ADB_CON)?;
|
lsetfilecon(defs::DAEMON_PATH, ADB_CON)?;
|
||||||
restore_modules_con(defs::MODULE_DIR)?;
|
restore_syscon_if_unlabeled(defs::MODULE_DIR)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +1,28 @@
|
|||||||
#[cfg(unix)]
|
use anyhow::{Context, Error, Ok, Result, bail};
|
||||||
use std::os::unix::prelude::PermissionsExt;
|
|
||||||
use std::{
|
use std::{
|
||||||
fs::{self, File, OpenOptions, create_dir_all, remove_file, write},
|
fs::{File, OpenOptions, create_dir_all, remove_file, write},
|
||||||
fs::{Permissions, set_permissions},
|
|
||||||
io::{
|
io::{
|
||||||
ErrorKind::{AlreadyExists, NotFound},
|
ErrorKind::{AlreadyExists, NotFound},
|
||||||
Write,
|
Write,
|
||||||
},
|
},
|
||||||
path::{Path, PathBuf},
|
path::Path,
|
||||||
process::Command,
|
process::Command,
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::{Context, Error, Ok, Result, bail};
|
use crate::{assets, boot_patch, defs, ksucalls, module, restorecon};
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
use std::fs::{Permissions, set_permissions};
|
||||||
|
#[cfg(unix)]
|
||||||
|
use std::os::unix::prelude::PermissionsExt;
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||||
use rustix::{
|
use rustix::{
|
||||||
process,
|
process,
|
||||||
thread::{LinkNameSpaceType, move_into_link_name_space},
|
thread::{LinkNameSpaceType, move_into_link_name_space},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{assets, boot_patch, defs, ksucalls, module, restorecon};
|
|
||||||
|
|
||||||
pub fn ensure_clean_dir(dir: impl AsRef<Path>) -> Result<()> {
|
pub fn ensure_clean_dir(dir: impl AsRef<Path>) -> Result<()> {
|
||||||
let path = dir.as_ref();
|
let path = dir.as_ref();
|
||||||
log::debug!("ensure_clean_dir: {}", path.display());
|
log::debug!("ensure_clean_dir: {}", path.display());
|
||||||
@@ -32,7 +35,7 @@ pub fn ensure_clean_dir(dir: impl AsRef<Path>) -> Result<()> {
|
|||||||
|
|
||||||
pub fn ensure_file_exists<T: AsRef<Path>>(file: T) -> Result<()> {
|
pub fn ensure_file_exists<T: AsRef<Path>>(file: T) -> Result<()> {
|
||||||
match File::options().write(true).create_new(true).open(&file) {
|
match File::options().write(true).create_new(true).open(&file) {
|
||||||
Result::Ok(_) => Ok(()),
|
std::result::Result::Ok(_) => Ok(()),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
if err.kind() == AlreadyExists && file.as_ref().is_file() {
|
if err.kind() == AlreadyExists && file.as_ref().is_file() {
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -172,27 +175,6 @@ pub fn has_magisk() -> bool {
|
|||||||
which::which("magisk").is_ok()
|
which::which("magisk").is_ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_ok_empty(dir: &str) -> bool {
|
|
||||||
use std::result::Result::Ok;
|
|
||||||
|
|
||||||
match fs::read_dir(dir) {
|
|
||||||
Ok(mut entries) => entries.next().is_none(),
|
|
||||||
Err(_) => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn find_tmp_path() -> String {
|
|
||||||
let dirs = ["/debug_ramdisk", "/patch_hw", "/oem", "/root", "/sbin"];
|
|
||||||
|
|
||||||
// find empty directory
|
|
||||||
for dir in dirs {
|
|
||||||
if is_ok_empty(dir) {
|
|
||||||
return dir.to_string();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"".to_string()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "android")]
|
#[cfg(target_os = "android")]
|
||||||
fn link_ksud_to_bin() -> Result<()> {
|
fn link_ksud_to_bin() -> Result<()> {
|
||||||
let ksu_bin = PathBuf::from(defs::DAEMON_PATH);
|
let ksu_bin = PathBuf::from(defs::DAEMON_PATH);
|
||||||
|
|||||||
4
userspace/meta-overlayfs/.gitignore
vendored
Normal file
4
userspace/meta-overlayfs/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
/target
|
||||||
|
/out
|
||||||
|
Cargo.lock
|
||||||
|
*.log
|
||||||
23
userspace/meta-overlayfs/Cargo.toml
Normal file
23
userspace/meta-overlayfs/Cargo.toml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
[package]
|
||||||
|
name = "meta-overlayfs"
|
||||||
|
version = "1.0.0"
|
||||||
|
edition = "2024"
|
||||||
|
authors = ["KernelSU Developers"]
|
||||||
|
description = "An implementation of a metamodule using OverlayFS for KernelSU"
|
||||||
|
license = "GPL-3.0"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = "1"
|
||||||
|
log = "0.4"
|
||||||
|
env_logger = { version = "0.11", default-features = false }
|
||||||
|
|
||||||
|
[target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies]
|
||||||
|
rustix = { git = "https://github.com/Kernel-SU/rustix.git", rev = "4a53fbc7cb7a07cabe87125cc21dbc27db316259", features = ["all-apis"] }
|
||||||
|
procfs = "0.17"
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
strip = true
|
||||||
|
opt-level = "z" # Minimize binary size
|
||||||
|
lto = true # Link-time optimization
|
||||||
|
codegen-units = 1 # Maximum optimization
|
||||||
|
panic = "abort" # Reduce binary size
|
||||||
58
userspace/meta-overlayfs/README.md
Normal file
58
userspace/meta-overlayfs/README.md
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
# meta-overlayfs
|
||||||
|
|
||||||
|
Official overlayfs mount handler for KernelSU metamodules.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
adb push meta-overlayfs-v1.0.0.zip /sdcard/
|
||||||
|
adb shell su -c 'ksud module install /sdcard/meta-overlayfs-v1.0.0.zip'
|
||||||
|
adb reboot
|
||||||
|
```
|
||||||
|
|
||||||
|
Or install via KernelSU Manager → Modules.
|
||||||
|
|
||||||
|
**Note**: The metamodule is now installed as a regular module to `/data/adb/modules/meta-overlay/`, with a symlink created at `/data/adb/metamodule` pointing to it.
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
Uses dual-directory architecture for ext4 image support:
|
||||||
|
|
||||||
|
- **Metadata**: `/data/adb/modules/` - Contains `module.prop`, `disable`, `skip_mount` markers
|
||||||
|
- **Content**: `/data/adb/metamodule/mnt/` - Contains `system/`, `vendor/` etc. directories from ext4 images
|
||||||
|
|
||||||
|
Scans metadata directory for enabled modules, then mounts their content directories as overlayfs layers.
|
||||||
|
|
||||||
|
### Supported Partitions
|
||||||
|
|
||||||
|
system, vendor, product, system_ext, odm, oem
|
||||||
|
|
||||||
|
### Read-Write Layer
|
||||||
|
|
||||||
|
Optional upperdir/workdir support via `/data/adb/modules/.rw/`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p /data/adb/modules/.rw/system/{upperdir,workdir}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
- `MODULE_METADATA_DIR` - Metadata location (default: `/data/adb/modules/`)
|
||||||
|
- `MODULE_CONTENT_DIR` - Content location (default: `/data/adb/metamodule/mnt/`)
|
||||||
|
- `RUST_LOG` - Log level (debug, info, warn, error)
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
Automatically selects aarch64 or x86_64 binary during installation (~500KB).
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./build.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Output: `target/meta-overlayfs-v1.0.0.zip`
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
GPL-3.0
|
||||||
92
userspace/meta-overlayfs/build.sh
Normal file
92
userspace/meta-overlayfs/build.sh
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
VERSION=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)".*/\1/')
|
||||||
|
OUTPUT_DIR="target"
|
||||||
|
METAMODULE_DIR="metamodule"
|
||||||
|
MODULE_OUTPUT_DIR="$OUTPUT_DIR/module"
|
||||||
|
|
||||||
|
echo "=========================================="
|
||||||
|
echo "Building meta-overlayfs v${VERSION}"
|
||||||
|
echo "=========================================="
|
||||||
|
|
||||||
|
# Detect build tool
|
||||||
|
if command -v cross >/dev/null 2>&1; then
|
||||||
|
BUILD_TOOL="cross"
|
||||||
|
echo "Using cross for compilation"
|
||||||
|
else
|
||||||
|
BUILD_TOOL="cargo-ndk"
|
||||||
|
echo "Using cargo ndk for compilation"
|
||||||
|
if ! command -v cargo-ndk >/dev/null 2>&1; then
|
||||||
|
echo "Error: Neither cross nor cargo-ndk found!"
|
||||||
|
echo "Please install one of them:"
|
||||||
|
echo " - cross: cargo install cross"
|
||||||
|
echo " - cargo-ndk: cargo install cargo-ndk"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Clean output directory
|
||||||
|
echo "Cleaning output directory..."
|
||||||
|
rm -rf "$OUTPUT_DIR"
|
||||||
|
mkdir -p "$MODULE_OUTPUT_DIR"
|
||||||
|
|
||||||
|
# Build for multiple architectures
|
||||||
|
echo ""
|
||||||
|
echo "Building for aarch64-linux-android..."
|
||||||
|
if [ "$BUILD_TOOL" = "cross" ]; then
|
||||||
|
cross build --release --target aarch64-linux-android
|
||||||
|
else
|
||||||
|
cargo ndk build -t arm64-v8a --release
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Building for x86_64-linux-android..."
|
||||||
|
if [ "$BUILD_TOOL" = "cross" ]; then
|
||||||
|
cross build --release --target x86_64-linux-android
|
||||||
|
else
|
||||||
|
cargo ndk build -t x86_64 --release
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Copy binaries
|
||||||
|
echo ""
|
||||||
|
echo "Copying binaries..."
|
||||||
|
cp target/aarch64-linux-android/release/meta-overlayfs \
|
||||||
|
"$MODULE_OUTPUT_DIR/meta-overlayfs-aarch64"
|
||||||
|
cp target/x86_64-linux-android/release/meta-overlayfs \
|
||||||
|
"$MODULE_OUTPUT_DIR/meta-overlayfs-x86_64"
|
||||||
|
|
||||||
|
# Copy metamodule files
|
||||||
|
echo "Copying metamodule files..."
|
||||||
|
cp "$METAMODULE_DIR"/module.prop "$MODULE_OUTPUT_DIR/"
|
||||||
|
cp "$METAMODULE_DIR"/*.sh "$MODULE_OUTPUT_DIR/"
|
||||||
|
|
||||||
|
# Set permissions
|
||||||
|
echo "Setting permissions..."
|
||||||
|
chmod 755 "$MODULE_OUTPUT_DIR"/*.sh
|
||||||
|
chmod 755 "$MODULE_OUTPUT_DIR"/meta-overlayfs-*
|
||||||
|
|
||||||
|
# Display binary sizes
|
||||||
|
echo ""
|
||||||
|
echo "Binary sizes:"
|
||||||
|
echo " aarch64: $(du -h "$MODULE_OUTPUT_DIR"/meta-overlayfs-aarch64 | awk '{print $1}')"
|
||||||
|
echo " x86_64: $(du -h "$MODULE_OUTPUT_DIR"/meta-overlayfs-x86_64 | awk '{print $1}')"
|
||||||
|
|
||||||
|
# Package
|
||||||
|
echo ""
|
||||||
|
echo "Packaging..."
|
||||||
|
cd "$MODULE_OUTPUT_DIR"
|
||||||
|
ZIP_NAME="meta-overlayfs-v${VERSION}.zip"
|
||||||
|
zip -r "../$ZIP_NAME" .
|
||||||
|
cd ../..
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=========================================="
|
||||||
|
echo "Build completed successfully!"
|
||||||
|
echo "Output: $OUTPUT_DIR/$ZIP_NAME"
|
||||||
|
echo "=========================================="
|
||||||
|
echo ""
|
||||||
|
echo "To install:"
|
||||||
|
echo " adb push $OUTPUT_DIR/$ZIP_NAME /sdcard/"
|
||||||
|
echo " adb shell su -c 'ksud module install /sdcard/$ZIP_NAME'"
|
||||||
0
userspace/meta-overlayfs/metamodule/.gitkeep
Normal file
0
userspace/meta-overlayfs/metamodule/.gitkeep
Normal file
77
userspace/meta-overlayfs/metamodule/customize.sh
Normal file
77
userspace/meta-overlayfs/metamodule/customize.sh
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
#!/system/bin/sh
|
||||||
|
|
||||||
|
ui_print "- Detecting device architecture..."
|
||||||
|
|
||||||
|
# Detect architecture using ro.product.cpu.abi
|
||||||
|
ABI=$(grep_get_prop ro.product.cpu.abi)
|
||||||
|
ui_print "- Detected ABI: $ABI"
|
||||||
|
|
||||||
|
# Select the correct binary based on architecture
|
||||||
|
case "$ABI" in
|
||||||
|
arm64-v8a)
|
||||||
|
ARCH_BINARY="meta-overlayfs-aarch64"
|
||||||
|
REMOVE_BINARY="meta-overlayfs-x86_64"
|
||||||
|
ui_print "- Selected architecture: ARM64"
|
||||||
|
;;
|
||||||
|
x86_64)
|
||||||
|
ARCH_BINARY="meta-overlayfs-x86_64"
|
||||||
|
REMOVE_BINARY="meta-overlayfs-aarch64"
|
||||||
|
ui_print "- Selected architecture: x86_64"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
abort "! Unsupported architecture: $ABI"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Verify the selected binary exists
|
||||||
|
if [ ! -f "$MODPATH/$ARCH_BINARY" ]; then
|
||||||
|
abort "! Binary not found: $ARCH_BINARY"
|
||||||
|
fi
|
||||||
|
|
||||||
|
ui_print "- Installing $ARCH_BINARY as meta-overlayfs"
|
||||||
|
|
||||||
|
# Rename the selected binary to the generic name
|
||||||
|
mv "$MODPATH/$ARCH_BINARY" "$MODPATH/meta-overlayfs" || abort "! Failed to rename binary"
|
||||||
|
|
||||||
|
# Remove the unused binary
|
||||||
|
rm -f "$MODPATH/$REMOVE_BINARY"
|
||||||
|
|
||||||
|
# Ensure the binary is executable
|
||||||
|
chmod 755 "$MODPATH/meta-overlayfs" || abort "! Failed to set permissions"
|
||||||
|
|
||||||
|
ui_print "- Architecture-specific binary installed successfully"
|
||||||
|
|
||||||
|
# Create ext4 image for module content storage
|
||||||
|
IMG_FILE="$MODPATH/modules.img"
|
||||||
|
MNT_DIR="$MODPATH/mnt"
|
||||||
|
IMG_SIZE_MB=2048
|
||||||
|
|
||||||
|
if [ ! -f "$IMG_FILE" ]; then
|
||||||
|
ui_print "- Creating 2GB ext4 image for module storage"
|
||||||
|
|
||||||
|
# Create sparse file (2GB logical size, 0 bytes actual)
|
||||||
|
truncate -s ${IMG_SIZE_MB}M "$IMG_FILE" || \
|
||||||
|
abort "! Failed to create image file"
|
||||||
|
|
||||||
|
# Format as ext4 with small journal (8MB) for safety with minimal overhead
|
||||||
|
/system/bin/mke2fs -t ext4 -J size=8 -F "$IMG_FILE" >/dev/null 2>&1 || \
|
||||||
|
abort "! Failed to format ext4 image"
|
||||||
|
|
||||||
|
ui_print "- Image created successfully (sparse file)"
|
||||||
|
else
|
||||||
|
ui_print "- Existing image found, keeping it"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Mount image immediately for use
|
||||||
|
ui_print "- Mounting image for immediate use..."
|
||||||
|
mkdir -p "$MNT_DIR"
|
||||||
|
if ! mountpoint -q "$MNT_DIR" 2>/dev/null; then
|
||||||
|
mount -t ext4 -o loop,rw,noatime "$IMG_FILE" "$MNT_DIR" || \
|
||||||
|
abort "! Failed to mount image"
|
||||||
|
ui_print "- Image mounted successfully"
|
||||||
|
else
|
||||||
|
ui_print "- Image already mounted"
|
||||||
|
fi
|
||||||
|
|
||||||
|
ui_print "- Installation complete"
|
||||||
|
ui_print "- Image is ready for module installations"
|
||||||
61
userspace/meta-overlayfs/metamodule/metainstall.sh
Normal file
61
userspace/meta-overlayfs/metamodule/metainstall.sh
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
#!/system/bin/sh
|
||||||
|
############################################
|
||||||
|
# meta-overlayfs metainstall.sh
|
||||||
|
# Module installation hook for ext4 image support
|
||||||
|
############################################
|
||||||
|
|
||||||
|
# Constants
|
||||||
|
IMG_FILE="/data/adb/metamodule/modules.img"
|
||||||
|
MNT_DIR="/data/adb/metamodule/mnt"
|
||||||
|
|
||||||
|
# Ensure ext4 image is mounted
|
||||||
|
ensure_image_mounted() {
|
||||||
|
if ! mountpoint -q "$MNT_DIR" 2>/dev/null; then
|
||||||
|
ui_print "- Mounting modules image"
|
||||||
|
mkdir -p "$MNT_DIR"
|
||||||
|
mount -t ext4 -o loop,rw,noatime "$IMG_FILE" "$MNT_DIR" || {
|
||||||
|
abort "! Failed to mount modules image"
|
||||||
|
}
|
||||||
|
ui_print "- Image mounted successfully"
|
||||||
|
else
|
||||||
|
ui_print "- Image already mounted"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Post-installation: move partition directories to ext4 image
|
||||||
|
post_install_to_image() {
|
||||||
|
ui_print "- Moving module content to image"
|
||||||
|
|
||||||
|
MOD_IMG_DIR="$MNT_DIR/$MODID"
|
||||||
|
mkdir -p "$MOD_IMG_DIR"
|
||||||
|
|
||||||
|
# Move all partition directories
|
||||||
|
for partition in system vendor product system_ext odm oem; do
|
||||||
|
if [ -d "$MODPATH/$partition" ]; then
|
||||||
|
ui_print " Moving $partition/"
|
||||||
|
mv "$MODPATH/$partition" "$MOD_IMG_DIR/" || {
|
||||||
|
ui_print "! Warning: Failed to move $partition"
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Set permissions
|
||||||
|
chown -R 0:0 "$MOD_IMG_DIR" 2>/dev/null
|
||||||
|
chmod -R 755 "$MOD_IMG_DIR" 2>/dev/null
|
||||||
|
|
||||||
|
ui_print "- Module content moved to image"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main installation flow
|
||||||
|
ui_print "- Using meta-overlayfs metainstall"
|
||||||
|
|
||||||
|
# 1. Ensure ext4 image is mounted
|
||||||
|
ensure_image_mounted
|
||||||
|
|
||||||
|
# 2. Call standard install_module function (defined in installer.sh)
|
||||||
|
install_module
|
||||||
|
|
||||||
|
# 3. Post-process: move content to image
|
||||||
|
post_install_to_image
|
||||||
|
|
||||||
|
ui_print "- Installation complete"
|
||||||
65
userspace/meta-overlayfs/metamodule/metamount.sh
Normal file
65
userspace/meta-overlayfs/metamodule/metamount.sh
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
#!/system/bin/sh
|
||||||
|
# meta-overlayfs Module Mount Handler
|
||||||
|
# This script is the entry point for dual-directory module mounting
|
||||||
|
|
||||||
|
MODDIR="${0%/*}"
|
||||||
|
IMG_FILE="$MODDIR/modules.img"
|
||||||
|
MNT_DIR="$MODDIR/mnt"
|
||||||
|
|
||||||
|
# Log function
|
||||||
|
log() {
|
||||||
|
echo "[meta-overlayfs] $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log "Starting module mount process"
|
||||||
|
|
||||||
|
# Ensure ext4 image is mounted
|
||||||
|
if ! mountpoint -q "$MNT_DIR" 2>/dev/null; then
|
||||||
|
log "Image not mounted, mounting now..."
|
||||||
|
|
||||||
|
# Check if image file exists
|
||||||
|
if [ ! -f "$IMG_FILE" ]; then
|
||||||
|
log "ERROR: Image file not found at $IMG_FILE"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create mount point
|
||||||
|
mkdir -p "$MNT_DIR"
|
||||||
|
|
||||||
|
# Mount the ext4 image
|
||||||
|
mount -t ext4 -o loop,rw,noatime "$IMG_FILE" "$MNT_DIR" || {
|
||||||
|
log "ERROR: Failed to mount image"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
log "Image mounted successfully at $MNT_DIR"
|
||||||
|
else
|
||||||
|
log "Image already mounted at $MNT_DIR"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Binary path (architecture-specific binary selected during installation)
|
||||||
|
BINARY="$MODDIR/meta-overlayfs"
|
||||||
|
|
||||||
|
if [ ! -f "$BINARY" ]; then
|
||||||
|
log "ERROR: Binary not found: $BINARY"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Set dual-directory environment variables
|
||||||
|
export MODULE_METADATA_DIR="/data/adb/modules"
|
||||||
|
export MODULE_CONTENT_DIR="$MNT_DIR"
|
||||||
|
|
||||||
|
log "Metadata directory: $MODULE_METADATA_DIR"
|
||||||
|
log "Content directory: $MODULE_CONTENT_DIR"
|
||||||
|
log "Executing $BINARY"
|
||||||
|
|
||||||
|
# Execute the mount binary
|
||||||
|
"$BINARY"
|
||||||
|
EXIT_CODE=$?
|
||||||
|
|
||||||
|
if [ $EXIT_CODE -ne 0 ]; then
|
||||||
|
log "Mount failed with exit code $EXIT_CODE"
|
||||||
|
exit $EXIT_CODE
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Mount completed successfully"
|
||||||
|
exit 0
|
||||||
35
userspace/meta-overlayfs/metamodule/metauninstall.sh
Normal file
35
userspace/meta-overlayfs/metamodule/metauninstall.sh
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
#!/system/bin/sh
|
||||||
|
############################################
|
||||||
|
# mm-overlayfs metauninstall.sh
|
||||||
|
# Module uninstallation hook for ext4 image cleanup
|
||||||
|
############################################
|
||||||
|
|
||||||
|
# Constants
|
||||||
|
MNT_DIR="/data/adb/metamodule/mnt"
|
||||||
|
|
||||||
|
if [ -z "$MODULE_ID" ]; then
|
||||||
|
echo "! Error: MODULE_ID not provided"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "- Cleaning up module content from image: $MODULE_ID"
|
||||||
|
|
||||||
|
# Check if image is mounted
|
||||||
|
if ! mountpoint -q "$MNT_DIR" 2>/dev/null; then
|
||||||
|
echo "! Warning: Image not mounted, skipping cleanup"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Remove module content from image
|
||||||
|
MOD_IMG_DIR="$MNT_DIR/$MODULE_ID"
|
||||||
|
if [ -d "$MOD_IMG_DIR" ]; then
|
||||||
|
echo " Removing $MOD_IMG_DIR"
|
||||||
|
rm -rf "$MOD_IMG_DIR" || {
|
||||||
|
echo "! Warning: Failed to remove module content from image"
|
||||||
|
}
|
||||||
|
echo "- Module content removed from image"
|
||||||
|
else
|
||||||
|
echo "- No module content found in image, skipping"
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit 0
|
||||||
8
userspace/meta-overlayfs/metamodule/module.prop
Normal file
8
userspace/meta-overlayfs/metamodule/module.prop
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
id=meta-overlayfs
|
||||||
|
metamodule=1
|
||||||
|
name=OverlayFS MetaModule
|
||||||
|
version=1.0.0
|
||||||
|
versionCode=1
|
||||||
|
author=KernelSU Developers
|
||||||
|
description=An implementation of a metamodule using OverlayFS for KernelSU
|
||||||
|
updateJson=https://raw.githubusercontent.com/tiann/KernelSU/main/userspace/meta-overlayfs/update.json
|
||||||
24
userspace/meta-overlayfs/metamodule/uninstall.sh
Normal file
24
userspace/meta-overlayfs/metamodule/uninstall.sh
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#!/system/bin/sh
|
||||||
|
############################################
|
||||||
|
# mm-overlayfs uninstall.sh
|
||||||
|
# Cleanup script for metamodule removal
|
||||||
|
############################################
|
||||||
|
|
||||||
|
MODDIR="${0%/*}"
|
||||||
|
MNT_DIR="$MODDIR/mnt"
|
||||||
|
|
||||||
|
echo "- Uninstalling metamodule..."
|
||||||
|
|
||||||
|
# Unmount the ext4 image if mounted
|
||||||
|
if mountpoint -q "$MNT_DIR" 2>/dev/null; then
|
||||||
|
echo "- Unmounting image..."
|
||||||
|
umount "$MNT_DIR" 2>/dev/null || {
|
||||||
|
echo "- Warning: Failed to unmount cleanly"
|
||||||
|
umount -l "$MNT_DIR" 2>/dev/null
|
||||||
|
}
|
||||||
|
echo "- Image unmounted"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "- Uninstall complete"
|
||||||
|
|
||||||
|
exit 0
|
||||||
17
userspace/meta-overlayfs/src/defs.rs
Normal file
17
userspace/meta-overlayfs/src/defs.rs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
// Constants for KernelSU module mounting
|
||||||
|
|
||||||
|
// Dual-directory support for ext4 image
|
||||||
|
pub const MODULE_METADATA_DIR: &str = "/data/adb/modules/";
|
||||||
|
pub const MODULE_CONTENT_DIR: &str = "/data/adb/metamodule/mnt/";
|
||||||
|
|
||||||
|
// Legacy constant (for backwards compatibility)
|
||||||
|
pub const _MODULE_DIR: &str = "/data/adb/modules/";
|
||||||
|
|
||||||
|
// Status marker files
|
||||||
|
pub const DISABLE_FILE_NAME: &str = "disable";
|
||||||
|
pub const _REMOVE_FILE_NAME: &str = "remove";
|
||||||
|
pub const SKIP_MOUNT_FILE_NAME: &str = "skip_mount";
|
||||||
|
|
||||||
|
// System directories
|
||||||
|
pub const SYSTEM_RW_DIR: &str = "/data/adb/modules/.rw/";
|
||||||
|
pub const KSU_OVERLAY_SOURCE: &str = "KSU";
|
||||||
29
userspace/meta-overlayfs/src/main.rs
Normal file
29
userspace/meta-overlayfs/src/main.rs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
use log::info;
|
||||||
|
|
||||||
|
mod defs;
|
||||||
|
mod mount;
|
||||||
|
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
// Initialize logger
|
||||||
|
env_logger::builder()
|
||||||
|
.filter_level(log::LevelFilter::Info)
|
||||||
|
.init();
|
||||||
|
|
||||||
|
info!("meta-overlayfs v{}", env!("CARGO_PKG_VERSION"));
|
||||||
|
|
||||||
|
// Dual-directory support: metadata + content
|
||||||
|
let metadata_dir = std::env::var("MODULE_METADATA_DIR")
|
||||||
|
.unwrap_or_else(|_| defs::MODULE_METADATA_DIR.to_string());
|
||||||
|
let content_dir = std::env::var("MODULE_CONTENT_DIR")
|
||||||
|
.unwrap_or_else(|_| defs::MODULE_CONTENT_DIR.to_string());
|
||||||
|
|
||||||
|
info!("Metadata directory: {}", metadata_dir);
|
||||||
|
info!("Content directory: {}", content_dir);
|
||||||
|
|
||||||
|
// Execute dual-directory mounting
|
||||||
|
mount::mount_modules_systemlessly(&metadata_dir, &content_dir)?;
|
||||||
|
|
||||||
|
info!("Mount completed successfully");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
376
userspace/meta-overlayfs/src/mount.rs
Normal file
376
userspace/meta-overlayfs/src/mount.rs
Normal file
@@ -0,0 +1,376 @@
|
|||||||
|
// Overlayfs mounting implementation
|
||||||
|
// Migrated from ksud/src/mount.rs and ksud/src/init_event.rs
|
||||||
|
|
||||||
|
use anyhow::{Context, Result, bail};
|
||||||
|
use log::{info, warn};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||||
|
use procfs::process::Process;
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||||
|
use rustix::{fd::AsFd, fs::CWD, mount::*};
|
||||||
|
|
||||||
|
use crate::defs::{DISABLE_FILE_NAME, KSU_OVERLAY_SOURCE, SKIP_MOUNT_FILE_NAME, SYSTEM_RW_DIR};
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||||
|
pub fn mount_overlayfs(
|
||||||
|
lower_dirs: &[String],
|
||||||
|
lowest: &str,
|
||||||
|
upperdir: Option<PathBuf>,
|
||||||
|
workdir: Option<PathBuf>,
|
||||||
|
dest: impl AsRef<Path>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let lowerdir_config = lower_dirs
|
||||||
|
.iter()
|
||||||
|
.map(|s| s.as_ref())
|
||||||
|
.chain(std::iter::once(lowest))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(":");
|
||||||
|
info!(
|
||||||
|
"mount overlayfs on {:?}, lowerdir={}, upperdir={:?}, workdir={:?}",
|
||||||
|
dest.as_ref(),
|
||||||
|
lowerdir_config,
|
||||||
|
upperdir,
|
||||||
|
workdir
|
||||||
|
);
|
||||||
|
|
||||||
|
let upperdir = upperdir
|
||||||
|
.filter(|up| up.exists())
|
||||||
|
.map(|e| e.display().to_string());
|
||||||
|
let workdir = workdir
|
||||||
|
.filter(|wd| wd.exists())
|
||||||
|
.map(|e| e.display().to_string());
|
||||||
|
|
||||||
|
let result = (|| {
|
||||||
|
let fs = fsopen("overlay", FsOpenFlags::FSOPEN_CLOEXEC)?;
|
||||||
|
let fs = fs.as_fd();
|
||||||
|
fsconfig_set_string(fs, "lowerdir", &lowerdir_config)?;
|
||||||
|
if let (Some(upperdir), Some(workdir)) = (&upperdir, &workdir) {
|
||||||
|
fsconfig_set_string(fs, "upperdir", upperdir)?;
|
||||||
|
fsconfig_set_string(fs, "workdir", workdir)?;
|
||||||
|
}
|
||||||
|
fsconfig_set_string(fs, "source", KSU_OVERLAY_SOURCE)?;
|
||||||
|
fsconfig_create(fs)?;
|
||||||
|
let mount = fsmount(fs, FsMountFlags::FSMOUNT_CLOEXEC, MountAttrFlags::empty())?;
|
||||||
|
move_mount(
|
||||||
|
mount.as_fd(),
|
||||||
|
"",
|
||||||
|
CWD,
|
||||||
|
dest.as_ref(),
|
||||||
|
MoveMountFlags::MOVE_MOUNT_F_EMPTY_PATH,
|
||||||
|
)
|
||||||
|
})();
|
||||||
|
|
||||||
|
if let Err(e) = result {
|
||||||
|
warn!("fsopen mount failed: {e:#}, fallback to mount");
|
||||||
|
let mut data = format!("lowerdir={lowerdir_config}");
|
||||||
|
if let (Some(upperdir), Some(workdir)) = (upperdir, workdir) {
|
||||||
|
data = format!("{data},upperdir={upperdir},workdir={workdir}");
|
||||||
|
}
|
||||||
|
mount(
|
||||||
|
KSU_OVERLAY_SOURCE,
|
||||||
|
dest.as_ref(),
|
||||||
|
"overlay",
|
||||||
|
MountFlags::empty(),
|
||||||
|
data,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||||
|
pub fn bind_mount(from: impl AsRef<Path>, to: impl AsRef<Path>) -> Result<()> {
|
||||||
|
info!(
|
||||||
|
"bind mount {} -> {}",
|
||||||
|
from.as_ref().display(),
|
||||||
|
to.as_ref().display()
|
||||||
|
);
|
||||||
|
let tree = open_tree(
|
||||||
|
CWD,
|
||||||
|
from.as_ref(),
|
||||||
|
OpenTreeFlags::OPEN_TREE_CLOEXEC
|
||||||
|
| OpenTreeFlags::OPEN_TREE_CLONE
|
||||||
|
| OpenTreeFlags::AT_RECURSIVE,
|
||||||
|
)?;
|
||||||
|
move_mount(
|
||||||
|
tree.as_fd(),
|
||||||
|
"",
|
||||||
|
CWD,
|
||||||
|
to.as_ref(),
|
||||||
|
MoveMountFlags::MOVE_MOUNT_F_EMPTY_PATH,
|
||||||
|
)?;
|
||||||
|
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, None, None, mount_point) {
|
||||||
|
warn!("failed: {e:#}, fallback to bind mount");
|
||||||
|
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>,
|
||||||
|
workdir: Option<PathBuf>,
|
||||||
|
upperdir: Option<PathBuf>,
|
||||||
|
) -> Result<()> {
|
||||||
|
info!("mount overlay for {root}");
|
||||||
|
std::env::set_current_dir(root).with_context(|| format!("failed to chdir to {root}"))?;
|
||||||
|
let stock_root = ".";
|
||||||
|
|
||||||
|
// collect child mounts before mounting the root
|
||||||
|
let mounts = Process::myself()?
|
||||||
|
.mountinfo()
|
||||||
|
.with_context(|| "get mountinfo")?;
|
||||||
|
let mut mount_seq = mounts
|
||||||
|
.0
|
||||||
|
.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, upperdir, workdir, 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 {mount_point}: {e:#}, revert");
|
||||||
|
umount_dir(root).with_context(|| format!("failed to revert {root}"))?;
|
||||||
|
bail!(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||||
|
pub fn umount_dir(src: impl AsRef<Path>) -> Result<()> {
|
||||||
|
unmount(src.as_ref(), UnmountFlags::empty())
|
||||||
|
.with_context(|| format!("Failed to umount {}", src.as_ref().display()))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(any(target_os = "linux", target_os = "android")))]
|
||||||
|
pub fn mount_overlay(
|
||||||
|
_root: &String,
|
||||||
|
_module_roots: &Vec<String>,
|
||||||
|
_workdir: Option<PathBuf>,
|
||||||
|
_upperdir: Option<PathBuf>,
|
||||||
|
) -> Result<()> {
|
||||||
|
unimplemented!("mount_overlay is only supported on Linux/Android")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(any(target_os = "linux", target_os = "android")))]
|
||||||
|
pub fn mount_overlayfs(
|
||||||
|
_lower_dirs: &[String],
|
||||||
|
_lowest: &str,
|
||||||
|
_upperdir: Option<PathBuf>,
|
||||||
|
_workdir: Option<PathBuf>,
|
||||||
|
_dest: impl AsRef<Path>,
|
||||||
|
) -> Result<()> {
|
||||||
|
unimplemented!("mount_overlayfs is only supported on Linux/Android")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(any(target_os = "linux", target_os = "android")))]
|
||||||
|
pub fn bind_mount(_from: impl AsRef<Path>, _to: impl AsRef<Path>) -> Result<()> {
|
||||||
|
unimplemented!("bind_mount is only supported on Linux/Android")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== Mount coordination logic (from init_event.rs) ==========
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||||
|
fn mount_partition(partition_name: &str, lowerdir: &Vec<String>) -> Result<()> {
|
||||||
|
if lowerdir.is_empty() {
|
||||||
|
warn!("partition: {partition_name} lowerdir is empty");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let partition = format!("/{partition_name}");
|
||||||
|
|
||||||
|
// if /partition is a symlink and linked to /system/partition, then we don't need to overlay it separately
|
||||||
|
if Path::new(&partition).read_link().is_ok() {
|
||||||
|
warn!("partition: {partition} is a symlink");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut workdir = None;
|
||||||
|
let mut upperdir = None;
|
||||||
|
let system_rw_dir = Path::new(SYSTEM_RW_DIR);
|
||||||
|
if system_rw_dir.exists() {
|
||||||
|
workdir = Some(system_rw_dir.join(partition_name).join("workdir"));
|
||||||
|
upperdir = Some(system_rw_dir.join(partition_name).join("upperdir"));
|
||||||
|
}
|
||||||
|
|
||||||
|
mount_overlay(&partition, lowerdir, workdir, upperdir)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Collect enabled module IDs from metadata directory
|
||||||
|
///
|
||||||
|
/// Reads module list and status from metadata directory, returns enabled module IDs
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||||
|
fn collect_enabled_modules(metadata_dir: &str) -> Result<Vec<String>> {
|
||||||
|
let dir = std::fs::read_dir(metadata_dir)
|
||||||
|
.with_context(|| format!("Failed to read metadata directory: {}", metadata_dir))?;
|
||||||
|
|
||||||
|
let mut enabled = Vec::new();
|
||||||
|
|
||||||
|
for entry in dir.flatten() {
|
||||||
|
let path = entry.path();
|
||||||
|
if !path.is_dir() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let module_id = match entry.file_name().to_str() {
|
||||||
|
Some(id) => id.to_string(),
|
||||||
|
None => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check status markers
|
||||||
|
if path.join(DISABLE_FILE_NAME).exists() {
|
||||||
|
info!("Module {} is disabled, skipping", module_id);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if path.join(SKIP_MOUNT_FILE_NAME).exists() {
|
||||||
|
info!("Module {} has skip_mount, skipping", module_id);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional: verify module.prop exists
|
||||||
|
if !path.join("module.prop").exists() {
|
||||||
|
warn!("Module {} has no module.prop, skipping", module_id);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("Module {} enabled", module_id);
|
||||||
|
enabled.push(module_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(enabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Dual-directory version of mount_modules_systemlessly
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// - metadata_dir: Metadata directory, stores module.prop, disable, skip_mount, etc.
|
||||||
|
/// - content_dir: Content directory, stores system/, vendor/ and other partition content (ext4 image mount point)
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||||
|
pub fn mount_modules_systemlessly(metadata_dir: &str, content_dir: &str) -> Result<()> {
|
||||||
|
info!("Scanning modules (dual-directory mode)");
|
||||||
|
info!(" Metadata: {}", metadata_dir);
|
||||||
|
info!(" Content: {}", content_dir);
|
||||||
|
|
||||||
|
// 1. Traverse metadata directory, collect enabled module IDs
|
||||||
|
let enabled_modules = collect_enabled_modules(metadata_dir)?;
|
||||||
|
|
||||||
|
if enabled_modules.is_empty() {
|
||||||
|
info!("No enabled modules found");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("Found {} enabled module(s)", enabled_modules.len());
|
||||||
|
|
||||||
|
// 2. Initialize partition lowerdir lists
|
||||||
|
let partition = vec!["vendor", "product", "system_ext", "odm", "oem"];
|
||||||
|
let mut system_lowerdir: Vec<String> = Vec::new();
|
||||||
|
let mut partition_lowerdir: HashMap<String, Vec<String>> = HashMap::new();
|
||||||
|
|
||||||
|
for part in &partition {
|
||||||
|
partition_lowerdir.insert((*part).to_string(), Vec::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Read module content from content directory
|
||||||
|
for module_id in &enabled_modules {
|
||||||
|
let module_content_path = Path::new(content_dir).join(module_id);
|
||||||
|
|
||||||
|
if !module_content_path.exists() {
|
||||||
|
warn!("Module {} has no content directory, skipping", module_id);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("Processing module: {}", module_id);
|
||||||
|
|
||||||
|
// Collect system partition
|
||||||
|
let system_path = module_content_path.join("system");
|
||||||
|
if system_path.is_dir() {
|
||||||
|
system_lowerdir.push(system_path.display().to_string());
|
||||||
|
info!(" + system/");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect other partitions
|
||||||
|
for part in &partition {
|
||||||
|
let part_path = module_content_path.join(part);
|
||||||
|
if part_path.is_dir()
|
||||||
|
&& let Some(v) = partition_lowerdir.get_mut(*part)
|
||||||
|
{
|
||||||
|
v.push(part_path.display().to_string());
|
||||||
|
info!(" + {}/", part);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Mount partitions
|
||||||
|
info!("Mounting partitions...");
|
||||||
|
|
||||||
|
if let Err(e) = mount_partition("system", &system_lowerdir) {
|
||||||
|
warn!("mount system failed: {e:#}");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (k, v) in partition_lowerdir {
|
||||||
|
if let Err(e) = mount_partition(&k, &v) {
|
||||||
|
warn!("mount {k} failed: {e:#}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("All partitions processed");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(any(target_os = "linux", target_os = "android")))]
|
||||||
|
pub fn mount_modules_systemlessly(_metadata_dir: &str, _content_dir: &str) -> Result<()> {
|
||||||
|
unimplemented!("mount_modules_systemlessly is only supported on Linux/Android")
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user