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:
ShirkNeko
2025-11-19 19:33:01 +08:00
parent 4f79c94ab9
commit a8acea9180
28 changed files with 1617 additions and 807 deletions

4
userspace/meta-overlayfs/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
/target
/out
Cargo.lock
*.log

View 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

View 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

View 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'"

View 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"

View 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"

View 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

View 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

View 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

View 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

View 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";

View 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(())
}

View 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")
}