From 5b41d03592ce3daeb1e326d8b39f5143cfe88187 Mon Sep 17 00:00:00 2001 From: 7a72 Date: Thu, 20 Nov 2025 19:27:26 +0800 Subject: [PATCH] refactor: rewrite in C Migrate from Rust to C for better compatibility and smaller binaries. All functionality preserved. --- .gitignore | 1 + build.sh | 246 +++++ magic_mount/.gitignore | 2 - magic_mount/Cargo.toml | 19 - magic_mount/src/magic_mount.rs | 489 ---------- magic_mount/src/main.rs | 82 -- magic_mount/src/utils.rs | 159 ---- module/magic_mount.c | 1271 ------------------------- src/.gitignore | 1 + src/Makefile | 87 ++ src/magic_mount.c | 702 ++++++++++++++ src/magic_mount.h | 55 ++ src/main.c | 104 ++ src/utils.c | 87 ++ src/utils.h | 27 + {module => template}/customize.sh | 10 +- {module => template}/metainstall.sh | 0 {module => template}/metamount.sh | 7 +- {module => template}/metauninstall.sh | 0 {module => template}/module.prop | 4 +- {module => template}/uninstall.sh | 0 21 files changed, 1323 insertions(+), 2030 deletions(-) create mode 100644 .gitignore create mode 100644 build.sh delete mode 100644 magic_mount/.gitignore delete mode 100644 magic_mount/Cargo.toml delete mode 100644 magic_mount/src/magic_mount.rs delete mode 100644 magic_mount/src/main.rs delete mode 100644 magic_mount/src/utils.rs delete mode 100644 module/magic_mount.c create mode 100644 src/.gitignore create mode 100644 src/Makefile create mode 100644 src/magic_mount.c create mode 100644 src/magic_mount.h create mode 100644 src/main.c create mode 100644 src/utils.c create mode 100644 src/utils.h rename {module => template}/customize.sh (80%) rename {module => template}/metainstall.sh (100%) rename {module => template}/metamount.sh (73%) rename {module => template}/metauninstall.sh (100%) rename {module => template}/module.prop (85%) rename {module => template}/uninstall.sh (100%) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/build diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..acb37eb --- /dev/null +++ b/build.sh @@ -0,0 +1,246 @@ +#!/bin/bash + +# Color definitions +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Default values +VERSION="" +BUILD_TYPES=() + +# Show usage +usage() { + echo "Usage: $0 [--release] [--debug]" + echo " --release Build release version" + echo " --debug Build debug version" + echo " (no option) Build both release and debug versions" + echo "" + echo "Version number will be automatically obtained from git tag" + exit 1 +} + +# Parse command line arguments +while [[ $# -gt 0 ]]; do + case $1 in + --release) + BUILD_TYPES+=("release") + shift + ;; + --debug) + BUILD_TYPES+=("debug") + shift + ;; + -h|--help) + usage + ;; + *) + echo -e "${RED}Error: Unknown parameter $1${NC}" + usage + ;; + esac +done + +# If no build type specified, build both +if [ ${#BUILD_TYPES[@]} -eq 0 ]; then + BUILD_TYPES=("release" "debug") + echo -e "${YELLOW}No build type specified, building both release and debug versions${NC}" +fi + +# Check if in git repository +if ! git rev-parse --git-dir > /dev/null 2>&1; then + echo -e "${RED}Error: Not in a git repository${NC}" + exit 1 +fi + +# Get git tag as version number +VERSION=$(git describe --tags --exact-match 2>/dev/null) +if [ -z "$VERSION" ]; then + # If current commit has no tag, try to get the nearest tag + VERSION=$(git describe --tags --abbrev=0 2>/dev/null) + if [ -z "$VERSION" ]; then + echo -e "${RED}Error: Unable to get git tag, please create a tag first${NC}" + echo -e "${YELLOW}Hint: Use 'git tag v1.0.0' to create a tag${NC}" + exit 1 + else + echo -e "${YELLOW}Warning: Current commit has no tag, using nearest tag: $VERSION${NC}" + fi +fi + +# Get git short commit or branch name +GIT_SUFFIX="" +GIT_COMMIT=$(git rev-parse --short HEAD 2>/dev/null) +if [ -n "$GIT_COMMIT" ]; then + GIT_SUFFIX="$GIT_COMMIT" +else + # If unable to get commit, use branch name + GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null) + if [ -n "$GIT_BRANCH" ]; then + # Clean branch name, remove special characters + GIT_SUFFIX=$(echo "$GIT_BRANCH" | sed 's/[^a-zA-Z0-9_-]/_/g') + echo -e "${YELLOW}Warning: Unable to get commit, using branch name: $GIT_BRANCH${NC}" + else + echo -e "${YELLOW}Warning: Unable to get commit and branch information${NC}" + fi +fi + +# Function to build for a specific type +build_for_type() { + local BUILD_TYPE=$1 + + echo -e "\n${GREEN}========================================${NC}" + echo -e "${GREEN}Building $BUILD_TYPE version${NC}" + echo -e "${GREEN}Version: $VERSION${NC}" + echo -e "${GREEN}Build Type: $BUILD_TYPE${NC}" + if [ -n "$GIT_COMMIT" ]; then + echo -e "${GREEN}Git Commit: $GIT_COMMIT${NC}" + elif [ -n "$GIT_SUFFIX" ]; then + echo -e "${GREEN}Git Branch: $GIT_SUFFIX${NC}" + fi + echo -e "${GREEN}========================================${NC}" + + # Step 1: Enter src directory and execute make + echo -e "\n${YELLOW}[1/5] Compiling source code...${NC}" + if [ ! -d "src" ]; then + echo -e "${RED}Error: src directory does not exist${NC}" + return 1 + fi + + cd src || return 1 + make clean + make $BUILD_TYPE version="$VERSION" + if [ $? -ne 0 ]; then + echo -e "${RED}Error: Compilation failed${NC}" + cd .. + return 1 + fi + cd .. + echo -e "${GREEN}Compilation completed${NC}" + + # Step 2: Modify log level in metamount.sh + echo -e "\n${YELLOW}[2/5] Modifying metamount.sh configuration...${NC}" + if [ ! -f "template/metamount.sh" ]; then + echo -e "${RED}Error: template/metamount.sh does not exist${NC}" + return 1 + fi + + if [ "$BUILD_TYPE" = "debug" ]; then + LOG_LEVEL=3 + else + LOG_LEVEL=2 + fi + + # Use sed to modify log level + sed -i "s/MODULE_METADATA_LOGLEVEL=.*/MODULE_METADATA_LOGLEVEL=$LOG_LEVEL/" template/metamount.sh + if [ $? -ne 0 ]; then + echo -e "${RED}Error: Failed to modify metamount.sh${NC}" + return 1 + fi + echo -e "${GREEN}metamount.sh configuration completed (LOG_LEVEL=$LOG_LEVEL)${NC}" + + # Step 3: Modify module.prop + echo -e "\n${YELLOW}[3/5] Modifying module.prop...${NC}" + if [ ! -f "template/module.prop" ]; then + echo -e "${RED}Error: template/module.prop does not exist${NC}" + return 1 + fi + + # Generate versionCode (format: yymmddhhmm) + VERSION_CODE=$(date +%y%m%d%H%M) + + # Modify version and versionCode + sed -i "s/^version=.*/version=$VERSION/" template/module.prop + sed -i "s/^versionCode=.*/versionCode=$VERSION_CODE/" template/module.prop + if [ $? -ne 0 ]; then + echo -e "${RED}Error: Failed to modify module.prop${NC}" + return 1 + fi + echo -e "${GREEN}module.prop configuration completed (version=$VERSION, versionCode=$VERSION_CODE)${NC}" + + # Step 4: Move bin directory + echo -e "\n${YELLOW}[4/5] Moving bin directory...${NC}" + if [ ! -d "src/bin" ]; then + echo -e "${RED}Error: src/bin directory does not exist${NC}" + return 1 + fi + + # Remove target if it exists + if [ -d "template/bin" ]; then + rm -rf template/bin + fi + + cp -r src/bin template/ + if [ $? -ne 0 ]; then + echo -e "${RED}Error: Failed to copy bin directory${NC}" + return 1 + fi + echo -e "${GREEN}bin directory copied${NC}" + + # Step 5: Package as zip + echo -e "\n${YELLOW}[5/5] Packaging module...${NC}" + + # Build filename + if [ -n "$GIT_SUFFIX" ]; then + OUTPUT_NAME="meta-magic_mount-${VERSION}-${BUILD_TYPE}-${VERSION_CODE}-${GIT_SUFFIX}.zip" + else + OUTPUT_NAME="meta-magic_mount-${VERSION}-${BUILD_TYPE}-${VERSION_CODE}.zip" + fi + mkdir build + + # Enter template directory and package + cd template || return 1 + zip -r "../build/$OUTPUT_NAME" ./* -x "*.git*" + if [ $? -ne 0 ]; then + echo -e "${RED}Error: Packaging failed${NC}" + cd .. + return 1 + fi + cd .. + + # Remove target if it exists + if [ -d "template/bin" ]; then + rm -rf template/bin + fi + if [ -d "src/bin" ]; then + rm -rf src/bin + fi + + echo -e "\n${GREEN}========================================${NC}" + echo -e "${GREEN}Build completed!${NC}" + echo -e "${GREEN}Output file: $OUTPUT_NAME${NC}" + echo -e "${GREEN}========================================${NC}" + + return 0 +} + +# Build for each specified type +SUCCESS_COUNT=0 +FAIL_COUNT=0 + +for BUILD_TYPE in "${BUILD_TYPES[@]}"; do + build_for_type "$BUILD_TYPE" + if [ $? -eq 0 ]; then + ((SUCCESS_COUNT++)) + else + ((FAIL_COUNT++)) + echo -e "${RED}Failed to build $BUILD_TYPE version${NC}" + fi +done + +# Print summary +echo -e "\n${GREEN}========================================${NC}" +echo -e "${GREEN}Build Summary${NC}" +echo -e "${GREEN}Total builds: ${#BUILD_TYPES[@]}${NC}" +echo -e "${GREEN}Successful: $SUCCESS_COUNT${NC}" +if [ $FAIL_COUNT -gt 0 ]; then + echo -e "${RED}Failed: $FAIL_COUNT${NC}" +fi +echo -e "${GREEN}========================================${NC}" + +# Exit with error if any build failed +if [ $FAIL_COUNT -gt 0 ]; then + exit 1 +fi + +exit 0 diff --git a/magic_mount/.gitignore b/magic_mount/.gitignore deleted file mode 100644 index dcafa97..0000000 --- a/magic_mount/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/target -.cargo/ diff --git a/magic_mount/Cargo.toml b/magic_mount/Cargo.toml deleted file mode 100644 index 19c06c2..0000000 --- a/magic_mount/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[package] -name = "magic_mount_rs" -version = "0.1.0" -edition = "2024" - -[dependencies] -anyhow = "1" -clap = { version = "4", features = ["derive"] } -extattr = "1" -log = "0.4" -simplelog = "0.12" -rustix = { version = "0.38", features = ["fs", "mount"] } -libc = "0.2" - -[profile.release] -strip = true -opt-level = "z" -lto = true -codegen-units = 1 diff --git a/magic_mount/src/magic_mount.rs b/magic_mount/src/magic_mount.rs deleted file mode 100644 index 70c0326..0000000 --- a/magic_mount/src/magic_mount.rs +++ /dev/null @@ -1,489 +0,0 @@ -use crate::magic_mount::NodeFileType::{Directory, RegularFile, Symlink, Whiteout}; -use crate::utils::{lgetfilecon, lsetfilecon, ensure_dir_exists}; -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, -}; -use rustix::mount::mount_change; -use rustix::path::Arg; -use std::cmp::PartialEq; -use std::collections::HashMap; -use std::collections::hash_map::Entry; -use std::fs; -use std::fs::{DirEntry, FileType, create_dir, create_dir_all, read_dir, read_link}; -use std::os::unix::fs::{FileTypeExt, symlink}; -use std::path::{Path, PathBuf}; - -const DISABLE_FILE_NAME: &str = "disable"; -const REMOVE_FILE_NAME: &str = "remove"; -const SKIP_MOUNT_FILE_NAME: &str = "skip_mount"; - -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 { - 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, - // the module that owned this node - module_path: Option, - replace: bool, - skip: bool, -} - -impl Node { - fn collect_module_files>(&mut self, module_dir: T) -> Result { - 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(name: T) -> Self { - Node { - name: name.to_string(), - file_type: Directory, - children: Default::default(), - module_path: None, - replace: false, - skip: false, - } - } - - fn new_module(name: T, entry: &DirEntry) -> Option { - 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(module_dir: &Path, extra_partitions: &[String]) -> Result> { - let mut root = Node::new_root(""); - let mut system = Node::new_root("system"); - let module_root = 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(REMOVE_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 { - const BUILTIN_PARTITIONS: [(&str, bool); 4] = [ - ("vendor", true), - ("system_ext", true), - ("product", true), - ("odm", false), - ]; - - for (partition, require_symlink) in BUILTIN_PARTITIONS { - 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); - } - } - } - - for partition in extra_partitions { - if BUILTIN_PARTITIONS.iter().any(|(p, _)| p == partition) { - continue; - } - if partition == "system" { - continue; - } - - let path_of_root = Path::new("/").join(partition); - let path_of_system = Path::new("/system").join(partition); - let require_symlink = false; - - 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) { - log::debug!("attach extra partition '{}' to root", name); - root.children.insert(name, node); - } - } - } - - root.children.insert("system".to_string(), system); - Ok(Some(root)) - } else { - Ok(None) - } -} - -fn clone_symlink, Dst: AsRef>(src: Src, dst: Dst) -> Result<()> { - 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, WP: AsRef>( - path: P, - work_dir_path: WP, - entry: &DirEntry, -) -> Result<()> { - 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, WP: AsRef>( - path: P, - work_dir_path: WP, - current: Node, - has_tmpfs: bool, -) -> Result<()> { - 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: T, - module_dir: &Path, - mount_source: &str, - extra_partitions: &[String], -) -> Result<()> { - if let Some(root) = collect_module_files(module_dir, extra_partitions)? { - log::debug!("collected: {:#?}", root); - - let tmp_root = tmp_path.as_ref(); - let tmp_dir = tmp_root.join("workdir"); - ensure_dir_exists(&tmp_dir)?; - - mount(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(()) - } -} diff --git a/magic_mount/src/main.rs b/magic_mount/src/main.rs deleted file mode 100644 index 558c1c4..0000000 --- a/magic_mount/src/main.rs +++ /dev/null @@ -1,82 +0,0 @@ -mod magic_mount; -mod utils; - -use anyhow::{Context, Result}; -use clap::Parser; -use std::path::PathBuf; - -const MODULE_DIR_DEFAULT: &str = "/data/adb/modules/"; -const MOUNT_SOURCE_DEFAULT: &str = "MaGIcMounT"; -const LOG_FILE_DEFAULT: &str = "/data/adb/mm.log"; - -#[derive(Parser, Debug)] -#[command(name = "magic_mount", version, about = "Magic Mount Metamodule")] -struct Cli { - /// Module directory path - #[arg(short = 'm', long = "moduledir", default_value = MODULE_DIR_DEFAULT)] - moduledir: PathBuf, - - /// Temporary directory path (auto-selected if not specified) - #[arg(short = 't', long = "tempdir")] - tempdir: Option, - - /// Mount source name - #[arg(short = 's', long = "mountsource", default_value = MOUNT_SOURCE_DEFAULT)] - mountsource: String, - - /// Log file path - #[arg(short = 'l', long = "logfile", default_value = LOG_FILE_DEFAULT)] - logfile: PathBuf, - - /// Enable verbose (debug) logging - #[arg(short = 'v', long = "verbose")] - verbose: bool, - - /// Extra partitions, comma-separated, eg: -p mi_ext,my_stock - #[arg(short = 'p', long = "partitions", value_delimiter = ',')] - partitions: Vec, -} - -fn main() -> Result<()> { - let cli = Cli::parse(); - - utils::init_logger(&cli.logfile, cli.verbose)?; - - log::info!("Magic Mount Starting"); - log::info!("module dir : {}", cli.moduledir.display()); - - let tempdir = if let Some(temp) = cli.tempdir { - log::info!("temp dir (user) : {}", temp.display()); - temp - } else { - let temp = utils::select_temp_dir() - .context("failed to select temp dir automatically")?; - log::info!("temp dir (auto) : {}", temp.display()); - temp - }; - - log::info!("mount source : {}", cli.mountsource); - log::info!("log file : {}", cli.logfile.display()); - log::info!("verbose mode : {}", cli.verbose); - if !cli.partitions.is_empty() { - log::info!("extra partitions: {:?}", cli.partitions); - } - - utils::ensure_temp_dir(&tempdir)?; - - let result = magic_mount::magic_mount(&tempdir, &cli.moduledir, &cli.mountsource, &cli.partitions); - - utils::cleanup_temp_dir(&tempdir); - - match result { - Ok(_) => { - log::info!("Magic Mount Completed Successfully"); - Ok(()) - } - Err(e) => { - log::error!("Magic Mount Failed"); - log::error!("error: {:#}", e); - Err(e) - } - } -} diff --git a/magic_mount/src/utils.rs b/magic_mount/src/utils.rs deleted file mode 100644 index 2403880..0000000 --- a/magic_mount/src/utils.rs +++ /dev/null @@ -1,159 +0,0 @@ -use anyhow::{bail, Context, Result}; -use log::LevelFilter; -use simplelog::{Config, WriteLogger}; -use std::fs::{OpenOptions, write, remove_file, create_dir_all, remove_dir_all, read_to_string}; -use std::path::{Path, PathBuf}; - -#[cfg(any(target_os = "linux", target_os = "android"))] -use extattr::{lsetxattr, Flags as XattrFlags}; - -const SELINUX_XATTR: &str = "security.selinux"; - -const TEMP_DIR_SUFFIX: &str = ".magic_mount"; - -const TMPFS_CANDIDATES: &[&str] = &["/mnt/vendor", "/mnt", "/debug_ramdisk"]; - -pub fn lsetfilecon>(path: P, con: &str) -> Result<()> { - #[cfg(any(target_os = "linux", target_os = "android"))] - lsetxattr(&path, SELINUX_XATTR, con, XattrFlags::empty()).with_context(|| { - format!( - "Failed to change SELinux context for {}", - path.as_ref().display() - ) - })?; - Ok(()) -} - -#[cfg(any(target_os = "linux", target_os = "android"))] -pub fn lgetfilecon>(path: P) -> Result { - let con = extattr::lgetxattr(&path, SELINUX_XATTR).with_context(|| { - format!( - "Failed to get SELinux context for {}", - path.as_ref().display() - ) - })?; - let con = String::from_utf8_lossy(&con); - Ok(con.to_string()) -} - -#[cfg(not(any(target_os = "linux", target_os = "android")))] -pub fn lgetfilecon>(path: P) -> Result { - unimplemented!() -} - -pub fn ensure_dir_exists>(dir: T) -> Result<()> { - let result = create_dir_all(&dir); - if dir.as_ref().is_dir() && result.is_ok() { - Ok(()) - } else { - bail!("{} is not a regular directory", dir.as_ref().display()) - } -} - -pub fn init_logger(logfile: &Path, verbose: bool) -> Result<()> { - let level = if verbose { - LevelFilter::Debug - } else if cfg!(debug_assertions) { - LevelFilter::Debug - } else { - LevelFilter::Info - }; - - let file = OpenOptions::new() - .create(true) - .append(true) - .open(logfile) - .with_context(|| format!("failed to open log file {logfile:?}"))?; - - WriteLogger::init(level, Config::default(), file) - .context("failed to init file logger")?; - - log::info!("log level: {:?}", level); - - Ok(()) -} - -fn is_writable_tmpfs(path: &Path) -> bool { - if !path.is_dir() { - log::debug!(" {} is not a directory", path.display()); - return false; - } - - if let Ok(mounts) = read_to_string("/proc/mounts") { - let path_str = path.to_string_lossy(); - let is_tmpfs = mounts.lines().any(|line| { - let parts: Vec<&str> = line.split_whitespace().collect(); - parts.len() >= 3 && parts[1] == path_str && parts[2] == "tmpfs" - }); - - if !is_tmpfs { - log::debug!(" {} is not a tmpfs", path.display()); - return false; - } - log::debug!(" {} is a tmpfs", path.display()); - } else { - log::debug!(" failed to read /proc/mounts"); - } - - let test_file = path.join(format!(".mm_test_{}", std::process::id())); - let writable = write(&test_file, b"test").is_ok(); - - if writable { - let _ = remove_file(&test_file); - log::debug!(" {} is writable", path.display()); - } else { - log::debug!(" {} is not writable", path.display()); - } - - writable -} - -pub fn select_temp_dir() -> Result { - log::debug!("searching for suitable tmpfs mount point..."); - - for candidate in TMPFS_CANDIDATES { - let path = Path::new(candidate); - log::debug!("checking tmpfs candidate: {}", path.display()); - - if is_writable_tmpfs(path) { - let temp_dir = path.join(TEMP_DIR_SUFFIX); - log::info!( - "selected tmpfs: {} -> {}", - path.display(), - temp_dir.display() - ); - return Ok(temp_dir); - } - } - - bail!( - "no writable tmpfs found in candidates: {}", - TMPFS_CANDIDATES.join(", ") - ) -} - -pub fn ensure_temp_dir(temp_dir: &Path) -> Result<()> { - if temp_dir.exists() { - log::debug!("cleaning existing temp dir: {}", temp_dir.display()); - remove_dir_all(temp_dir) - .with_context(|| format!("failed to clean temp dir {temp_dir:?}"))?; - } - - create_dir_all(temp_dir) - .with_context(|| format!("failed to create temp dir {temp_dir:?}"))?; - - log::debug!("temp dir ready: {}", temp_dir.display()); - Ok(()) -} - -pub fn cleanup_temp_dir(temp_dir: &Path) { - if let Err(e) = remove_dir_all(temp_dir) { - log::warn!( - "failed to clean up temp dir {}: {:#}", - temp_dir.display(), - e - ); - } else { - log::debug!("cleaned up temp dir: {}", temp_dir.display()); - } -} diff --git a/module/magic_mount.c b/module/magic_mount.c deleted file mode 100644 index 06f6cae..0000000 --- a/module/magic_mount.c +++ /dev/null @@ -1,1271 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -int setns(int fd, int nstype); - -/*============================================================================== - * 常量 & 宏 - *============================================================================*/ - -#define MAGIC_MOUNT_VERSION "0.1.0" - -#define DEFAULT_MOUNT_SOURCE "KSU" -#define DEFAULT_MODULE_DIR "/data/adb/modules" -#define DEFAULT_TEMP_DIR "/dev/.magic_mount" // fallback - -#define DISABLE_FILE_NAME "disable" -#define REMOVE_FILE_NAME "remove" -#define SKIP_MOUNT_FILE_NAME "skip_mount" - -#define REPLACE_DIR_XATTR "trusted.overlay.opaque" -#define SELINUX_XATTR "security.selinux" - -#ifndef PATH_MAX -#define PATH_MAX 4096 -#endif - -#ifndef TMPFS_MAGIC -#define TMPFS_MAGIC 0x01021994 -#endif - -#define UNUSED(x) (void)(x) - -/*============================================================================== - * 统计信息 & 全局配置 - *============================================================================*/ - -typedef struct { - int modules_total; - int nodes_total; - int nodes_mounted; - int nodes_skipped; - int nodes_whiteout; - int nodes_fail; -} MountStats; - -static MountStats g_stats = {0}; - -static const char *g_module_dir = DEFAULT_MODULE_DIR; -static const char *g_mount_source = DEFAULT_MOUNT_SOURCE; -static FILE *g_log_file = NULL; // NULL => stderr -static int g_log_level = 2; // 0=ERROR,1=WARN,2=INFO,3=DEBUG - -/* 记录发生失败的模块名 & 额外分区列表 */ -static char **g_failed_modules = NULL; -static int g_failed_modules_count = 0; - -static char **g_extra_parts = NULL; // 额外分区名,如 "my_custom" -static int g_extra_parts_count = 0; - -/*============================================================================== - * 日志 - *============================================================================*/ - -static void vlog(const char *lv, const char *fmt, va_list ap) { - FILE *out = g_log_file ? g_log_file : stderr; - fprintf(out, "[%s] ", lv); - vfprintf(out, fmt, ap); - fputc('\n', out); - fflush(out); -} - -static void log_msg(const char *lv, const char *fmt, ...) { - va_list ap; - va_start(ap, fmt); - vlog(lv, fmt, ap); - va_end(ap); -} - -#define LV_ERROR 0 -#define LV_WARN 1 -#define LV_INFO 2 -#define LV_DEBUG 3 - -#define LOG(lv, ...) \ - do { \ - if ((lv) <= g_log_level) \ - log_msg((lv)==LV_ERROR?"ERROR": \ - (lv)==LV_WARN ?"WARN": \ - (lv)==LV_INFO ?"INFO":"DEBUG", \ - __VA_ARGS__); \ - } while (0) - -#define LOGE(...) LOG(LV_ERROR, __VA_ARGS__) -#define LOGW(...) LOG(LV_WARN, __VA_ARGS__) -#define LOGI(...) LOG(LV_INFO, __VA_ARGS__) -#define LOGD(...) LOG(LV_DEBUG, __VA_ARGS__) - -/*============================================================================== - * 工具函数 - *============================================================================*/ - -static int path_join(const char *base, const char *name, - char *buf, size_t n) -{ - if (!base) { errno = EINVAL; return -1; } - - if (!name || !name[0]) { - if (snprintf(buf, n, "%s", base) >= (int)n) { - errno = ENAMETOOLONG; - return -1; - } - return 0; - } - - if (!base[0] || (base[0]=='/' && !base[1])) { - if (snprintf(buf, n, "/%s", name) >= (int)n) { - errno = ENAMETOOLONG; - return -1; - } - } else { - size_t len = strlen(base); - if (base[len-1] == '/') { - if (snprintf(buf, n, "%s%s", base, name) >= (int)n) { - errno = ENAMETOOLONG; - return -1; - } - } else { - if (snprintf(buf, n, "%s/%s", base, name) >= (int)n) { - errno = ENAMETOOLONG; - return -1; - } - } - } - return 0; -} - -static bool path_exists(const char *p) { - struct stat st; - return (stat(p, &st) == 0); -} - -static bool path_is_dir(const char *p) { - struct stat st; - return (stat(p, &st) == 0) && S_ISDIR(st.st_mode); -} - -static bool path_is_symlink(const char *p) { - struct stat st; - return (lstat(p, &st) == 0) && S_ISLNK(st.st_mode); -} - -static int mkdir_p(const char *dir) { - if (!dir || !dir[0]) { - errno = EINVAL; - return -1; - } - - struct stat st; - if (stat(dir, &st) == 0) { - if (S_ISDIR(st.st_mode)) return 0; - LOGE("%s is not directory", dir); - errno = ENOTDIR; - return -1; - } - - char tmp[PATH_MAX]; - if (strlen(dir) >= sizeof(tmp)) { - errno = ENAMETOOLONG; - return -1; - } - - strcpy(tmp, dir); - char *s = strrchr(tmp, '/'); - if (s && s != tmp) { - *s = 0; - if (mkdir_p(tmp) != 0) return -1; - } - - if (mkdir(dir, 0755) == 0 || errno == EEXIST) return 0; - - LOGE("mkdir %s: %s", dir, strerror(errno)); - return -1; -} - -static bool is_rw_tmpfs(const char *path) { - if (!path_is_dir(path)) return false; - - struct statfs s; - if (statfs(path, &s) < 0) return false; - if ((unsigned long)s.f_type != (unsigned long)TMPFS_MAGIC) return false; - - char tmpl[PATH_MAX]; - if (path_join(path, ".magic_mount_testXXXXXX", tmpl, sizeof(tmpl)) != 0) - return false; - - int fd = mkstemp(tmpl); - if (fd < 0) return false; - close(fd); - unlink(tmpl); - return true; -} - -/* 释放字符串数组并清零 */ -static void free_string_array(char ***arr, int *count) { - if (!arr || !*arr || !count) return; - for (int i = 0; i < *count; ++i) free((*arr)[i]); - free(*arr); - *arr = NULL; - *count = 0; -} - -/* 自动选择 tempdir:第一个 rw tmpfs 下的 .magic_mount,否则 fallback */ -static const char *select_auto_tempdir(char buf[PATH_MAX]) { - const char *tmp_list[] = {"/mnt/vendor", "/mnt", "/debug_ramdisk"}; - size_t n = sizeof(tmp_list) / sizeof(tmp_list[0]); - - for (size_t i = 0; i < n; ++i) { - const char *base = tmp_list[i]; - if (!is_rw_tmpfs(base)) continue; - if (path_join(base, ".magic_mount", buf, PATH_MAX) != 0) continue; - - LOGI("auto tempdir selected: %s (from %s)", buf, base); - return buf; - } - - LOGW("no rw tmpfs found in candidates, fallback to %s", DEFAULT_TEMP_DIR); - return DEFAULT_TEMP_DIR; -} - -/* 记录模块失败(去重) */ -static void register_module_failure(const char *module_name) { - if (!module_name) return; - - for (int i = 0; i < g_failed_modules_count; ++i) { - if (strcmp(g_failed_modules[i], module_name) == 0) - return; - } - - char **arr = realloc( - g_failed_modules, - (size_t)(g_failed_modules_count + 1) * sizeof(char*) - ); - if (!arr) return; - - g_failed_modules = arr; - g_failed_modules[g_failed_modules_count] = strdup(module_name); - if (!g_failed_modules[g_failed_modules_count]) return; - - g_failed_modules_count++; -} - -/* 添加额外分区名,name 为单个 token(不含逗号) */ -static void add_extra_part_token(const char *start, size_t len) { - while (len > 0 && (start[0] == ' ' || start[0] == '\t')) { start++; len--; } - while (len > 0 && (start[len-1] == ' ' || start[len-1] == '\t')) len--; - - if (len == 0) return; - - char *name = malloc(len + 1); - if (!name) return; - - memcpy(name, start, len); - name[len] = '\0'; - - char **arr = realloc( - g_extra_parts, - (size_t)(g_extra_parts_count + 1) * sizeof(char*) - ); - if (!arr) { - free(name); - return; - } - - g_extra_parts = arr; - g_extra_parts[g_extra_parts_count++] = name; -} - -/*============================================================================== - * SELinux xattr(通用接口) - *============================================================================*/ - -static int lsetfilecon(const char *path, const char *con) { -#if defined(__linux__) - if (!path || !con) { - LOGD("lsetfilecon called with path=%p, con=%p (skip)", - (void*)path, (void*)con); - return 0; - } - - LOGD("lsetfilecon(%s, \"%s\")", path, con); - - if (lsetxattr(path, SELINUX_XATTR, con, strlen(con), 0) < 0) { - LOGW("setcon %s: %s", path, strerror(errno)); - return -1; - } - - LOGD("lsetfilecon(%s) ok", path); -#else - LOGD("lsetfilecon(%s, %s) skipped (no SELinux on this platform)", - path ? path : "(null)", con ? con : "(null)"); - UNUSED(path); - UNUSED(con); -#endif - return 0; -} - -static int lgetfilecon(const char *path, char **out) { - *out = NULL; -#if defined(__linux__) - if (!path) { - LOGD("lgetfilecon called with NULL path"); - errno = EINVAL; - return -1; - } - - LOGD("lgetfilecon(%s)", path); - - ssize_t sz = lgetxattr(path, SELINUX_XATTR, NULL, 0); - if (sz < 0) { - LOGW("getcon %s: %s", path, strerror(errno)); - return -1; - } - - char *buf = malloc((size_t)sz + 1); - if (!buf) { - errno = ENOMEM; - return -1; - } - - ssize_t r = lgetxattr(path, SELINUX_XATTR, buf, (size_t)sz); - if (r < 0) { - LOGW("getcon %s (read): %s", path, strerror(errno)); - free(buf); - return -1; - } - buf[r] = '\0'; - *out = buf; - - LOGD("lgetfilecon(%s) -> \"%s\"", path, buf); -#else - LOGD("lgetfilecon(%s) skipped (no SELinux on this platform)", - path ? path : "(null)"); - UNUSED(path); -#endif - return 0; -} - -/*============================================================================== - * Node 结构 & 模块扫描 - *============================================================================*/ - -typedef enum { - NFT_REGULAR, - NFT_DIRECTORY, - NFT_SYMLINK, - NFT_WHITEOUT -} NodeFileType; - -typedef struct Node { - char *name; - NodeFileType type; - struct Node **children; - size_t child_count; - char *module_path; // 在模块中的完整路径 - char *module_name; // 模块名(目录名),例如 "foo" - bool replace; - bool skip; - bool done; -} Node; - -static Node *node_new(const char *name, NodeFileType t) { - Node *n = calloc(1, sizeof(Node)); - if (!n) return NULL; - - n->name = strdup(name ? name : ""); - n->type = t; - return n; -} - -static void node_free(Node *n) { - if (!n) return; - - for (size_t i = 0; i < n->child_count; ++i) - node_free(n->children[i]); - - free(n->children); - free(n->name); - free(n->module_path); - free(n->module_name); - free(n); -} - -static NodeFileType node_type_from_stat(const struct stat *st) { - if (S_ISCHR(st->st_mode) && st->st_rdev == 0) return NFT_WHITEOUT; - if (S_ISREG(st->st_mode)) return NFT_REGULAR; - if (S_ISDIR(st->st_mode)) return NFT_DIRECTORY; - if (S_ISLNK(st->st_mode)) return NFT_SYMLINK; - return NFT_WHITEOUT; -} - -static bool dir_is_replace(const char *path) { - char buf[8]; - ssize_t len = lgetxattr(path, REPLACE_DIR_XATTR, buf, sizeof(buf) - 1); - if (len <= 0) return false; - - buf[len] = '\0'; - return (strcmp(buf, "y") == 0); -} - -/* module_name 为模块名,例如 "foo" */ -static Node *node_new_module(const char *name, - const char *path, - const char *module_name) -{ - struct stat st; - if (lstat(path, &st) < 0) return NULL; - - if (!(S_ISCHR(st.st_mode) || - S_ISREG(st.st_mode) || - S_ISDIR(st.st_mode) || - S_ISLNK(st.st_mode))) - return NULL; - - NodeFileType t = node_type_from_stat(&st); - Node *n = node_new(name, t); - if (!n) return NULL; - - n->module_path = strdup(path); - if (module_name) n->module_name = strdup(module_name); - n->replace = (t == NFT_DIRECTORY) && dir_is_replace(path); - - g_stats.nodes_total++; - return n; -} - -static int node_add_child(Node *parent, Node *child) { - Node **arr = realloc( - parent->children, - (parent->child_count + 1) * sizeof(Node*) - ); - if (!arr) { - errno = ENOMEM; - return -1; - } - - parent->children = arr; - parent->children[parent->child_count++] = child; - return 0; -} - -static Node *node_find_child(Node *parent, const char *name) { - for (size_t i = 0; i < parent->child_count; ++i) { - if (strcmp(parent->children[i]->name, name) == 0) - return parent->children[i]; - } - return NULL; -} - -static Node *node_take_child(Node *parent, const char *name) { - for (size_t i = 0; i < parent->child_count; ++i) { - if (strcmp(parent->children[i]->name, name) == 0) { - Node *n = parent->children[i]; - memmove(&parent->children[i], - &parent->children[i + 1], - (parent->child_count - i - 1) * sizeof(Node*)); - parent->child_count--; - return n; - } - } - return NULL; -} - -static bool module_disabled(const char *mod_dir) { - char buf[PATH_MAX]; - - path_join(mod_dir, DISABLE_FILE_NAME, buf, sizeof(buf)); - if (path_exists(buf)) return true; - - path_join(mod_dir, REMOVE_FILE_NAME, buf, sizeof(buf)); - if (path_exists(buf)) return true; - - path_join(mod_dir, SKIP_MOUNT_FILE_NAME, buf, sizeof(buf)); - if (path_exists(buf)) return true; - - return false; -} - -/* 递归收集模块文件,module_name 为模块名 */ -static int node_collect(Node *self, - const char *dir, - const char *module_name, - bool *has_any) -{ - DIR *d = opendir(dir); - if (!d) { - LOGE("opendir %s: %s", dir, strerror(errno)); - return -1; - } - - struct dirent *de; - bool any = false; - char path[PATH_MAX]; - - while ((de = readdir(d))) { - if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) - continue; - - if (path_join(dir, de->d_name, path, sizeof(path)) != 0) { - closedir(d); - return -1; - } - - Node *child = node_find_child(self, de->d_name); - if (!child) { - Node *n = node_new_module(de->d_name, path, module_name); - if (n) { - if (node_add_child(self, n) != 0) { - node_free(n); - closedir(d); - return -1; - } - child = n; - } - } - - if (child) { - if (child->type == NFT_DIRECTORY) { - bool sub = false; - if (node_collect(child, path, module_name, &sub) != 0) { - closedir(d); - return -1; - } - if (sub || child->replace) any = true; - } else { - any = true; - } - } - } - - closedir(d); - *has_any = any; - return 0; -} - -static Node *collect_root(void) { - const char *mdir = g_module_dir; - Node *root = node_new("", NFT_DIRECTORY); - Node *system = node_new("system", NFT_DIRECTORY); - - if (!root || !system) { - node_free(root); - node_free(system); - return NULL; - } - - DIR *d = opendir(mdir); - if (!d) { - LOGE("opendir %s: %s", mdir, strerror(errno)); - node_free(root); - node_free(system); - return NULL; - } - - struct dirent *de; - bool has_any = false; - char mod[PATH_MAX]; - char mod_sys[PATH_MAX]; - - while ((de = readdir(d))) { - if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) - continue; - - if (path_join(mdir, de->d_name, mod, sizeof(mod)) != 0) { - closedir(d); - node_free(root); - node_free(system); - return NULL; - } - - struct stat st; - if (stat(mod, &st) < 0 || !S_ISDIR(st.st_mode)) - continue; - - if (module_disabled(mod)) - continue; - - if (path_join(mod, "system", mod_sys, sizeof(mod_sys)) != 0) { - closedir(d); - node_free(root); - node_free(system); - return NULL; - } - - if (!path_is_dir(mod_sys)) - continue; - - LOGD("collecting module %s", de->d_name); - g_stats.modules_total++; - - bool sub = false; - if (node_collect(system, mod_sys, de->d_name, &sub) != 0) { - closedir(d); - node_free(root); - node_free(system); - return NULL; - } - if (sub) has_any = true; - } - - closedir(d); - - if (!has_any) { - node_free(root); - node_free(system); - return NULL; - } - - /* root 和 system 自身也计入节点数 */ - g_stats.nodes_total += 2; - - /* - * 内置分区: - * vendor, system_ext, product, odm - * 其中前三个要求 /system/ 是 symlink。 - * 额外分区(-p)行为类似 odm:只检查 / 是否为目录。 - */ - struct { - const char *name; - bool need_symlink; - } builtin_parts[] = { - { "vendor", true }, - { "system_ext", true }, - { "product", true }, - { "odm", false }, - }; - - char rp[PATH_MAX]; - char sp[PATH_MAX]; - - // builtin 分区 - for (size_t i = 0; i < sizeof(builtin_parts)/sizeof(builtin_parts[0]); ++i) { - if (path_join("/", builtin_parts[i].name, rp, sizeof(rp)) != 0 || - path_join("/system", builtin_parts[i].name, sp, sizeof(sp)) != 0) { - node_free(root); - node_free(system); - return NULL; - } - - if (!path_is_dir(rp)) - continue; - - if (builtin_parts[i].need_symlink && !path_is_symlink(sp)) - continue; - - Node *child = node_take_child(system, builtin_parts[i].name); - if (child && node_add_child(root, child) != 0) { - node_free(child); - node_free(root); - node_free(system); - return NULL; - } - } - - // 额外分区(行为类似 odm) - for (int i = 0; i < g_extra_parts_count; ++i) { - const char *name = g_extra_parts[i]; - - if (path_join("/", name, rp, sizeof(rp)) != 0) { - node_free(root); - node_free(system); - return NULL; - } - - if (!path_is_dir(rp)) - continue; - - Node *child = node_take_child(system, name); - if (child && node_add_child(root, child) != 0) { - node_free(child); - node_free(root); - node_free(system); - return NULL; - } - } - - if (node_add_child(root, system) != 0) { - node_free(root); - node_free(system); - return NULL; - } - - return root; -} - -/*============================================================================== - * magic mount 挂载逻辑 - *============================================================================*/ - -static int clone_symlink(const char *src, const char *dst) { - char target[PATH_MAX]; - - ssize_t len = readlink(src, target, sizeof(target) - 1); - if (len < 0) { - LOGE("readlink %s: %s", src, strerror(errno)); - return -1; - } - - target[len] = '\0'; - - if (symlink(target, dst) < 0) { - LOGE("symlink %s->%s: %s", dst, target, strerror(errno)); - return -1; - } - - char *con = NULL; - if (lgetfilecon(src, &con) == 0 && con) { - lsetfilecon(dst, con); - free(con); - } - - LOGD("clone symlink %s -> %s (%s)", src, dst, target); - return 0; -} - -static int mirror(const char *path, const char *work, const char *name); - -static int do_magic(const char *base, - const char *wbase, - Node *node, - bool has_tmpfs) -{ - char path[PATH_MAX]; - char wpath[PATH_MAX]; - - if (path_join(base, node->name, path, sizeof(path)) != 0 || - path_join(wbase, node->name, wpath, sizeof(wpath)) != 0) - return -1; - - switch (node->type) { - case NFT_REGULAR: { - const char *target = has_tmpfs ? wpath : path; - - if (has_tmpfs) { - if (mkdir_p(wbase) != 0) return -1; - - int fd = open(wpath, O_CREAT | O_WRONLY, 0644); - if (fd < 0) { - LOGE("create %s: %s", wpath, strerror(errno)); - return -1; - } - close(fd); - } - - if (!node->module_path) { - LOGE("no module file for %s", path); - errno = EINVAL; - return -1; - } - - LOGD("bind %s -> %s", node->module_path, target); - - if (mount(node->module_path, target, NULL, MS_BIND, NULL) < 0) { - LOGE("bind %s->%s: %s", - node->module_path, target, strerror(errno)); - return -1; - } - - (void)mount(NULL, target, NULL, - MS_REMOUNT | MS_BIND | MS_RDONLY, NULL); - - g_stats.nodes_mounted++; - return 0; - } - - case NFT_SYMLINK: { - if (!node->module_path) { - LOGE("no module symlink for %s", path); - errno = EINVAL; - return -1; - } - - if (clone_symlink(node->module_path, wpath) != 0) - return -1; - - g_stats.nodes_mounted++; - return 0; - } - - case NFT_WHITEOUT: - LOGD("whiteout %s", path); - g_stats.nodes_whiteout++; - return 0; - - case NFT_DIRECTORY: { - bool create_tmp = (!has_tmpfs && - node->replace && - node->module_path); - - if (!has_tmpfs && !create_tmp) { - for (size_t i = 0; i < node->child_count; ++i) { - Node *c = node->children[i]; - char rp[PATH_MAX]; - - if (path_join(path, c->name, rp, sizeof(rp)) != 0) - return -1; - - bool need = false; - - if (c->type == NFT_SYMLINK) { - need = true; - } else if (c->type == NFT_WHITEOUT) { - need = path_exists(rp); - } else { - struct stat st; - if (lstat(rp, &st) == 0) { - NodeFileType rt = node_type_from_stat(&st); - if (rt != c->type || rt == NFT_SYMLINK) - need = true; - } else { - need = true; - } - } - - if (need) { - if (!node->module_path) { - LOGE("cannot create tmpfs on %s (%s)", - path, c->name); - c->skip = true; - g_stats.nodes_skipped++; - continue; - } - create_tmp = true; - break; - } - } - } - - bool now_tmp = has_tmpfs || create_tmp; - - if (now_tmp) { - if (mkdir_p(wpath) != 0) return -1; - - struct stat st; - const char *meta_path = NULL; - - if (stat(path, &st) == 0) { - meta_path = path; - } else if (node->module_path && - stat(node->module_path, &st) == 0) { - meta_path = node->module_path; - } else { - LOGE("no dir meta for %s", path); - errno = ENOENT; - return -1; - } - - chmod(wpath, st.st_mode & 07777); - chown(wpath, st.st_uid, st.st_gid); - - char *con = NULL; - if (lgetfilecon(meta_path, &con) == 0 && con) { - lsetfilecon(wpath, con); - free(con); - } - } - - if (create_tmp) { - if (mount(wpath, wpath, NULL, MS_BIND, NULL) < 0) { - LOGE("bind self %s: %s", wpath, strerror(errno)); - return -1; - } - } - - if (path_exists(path) && !node->replace) { - DIR *d = opendir(path); - if (!d) { - LOGE("opendir %s: %s", path, strerror(errno)); - if (now_tmp) return -1; - } else { - struct dirent *de; - while ((de = readdir(d))) { - if (!strcmp(de->d_name, ".") || - !strcmp(de->d_name, "..")) { - continue; - } - - Node *c = node_find_child(node, de->d_name); - int r = 0; - - if (c) { - if (c->skip) { - c->done = true; - continue; - } - c->done = true; - r = do_magic(path, wpath, c, now_tmp); - } else if (now_tmp) { - r = mirror(path, wpath, de->d_name); - } - - if (r != 0) { - const char *mn = NULL; - - if (c && c->module_name) - mn = c->module_name; - else if (node->module_name) - mn = node->module_name; - - if (mn) { - LOGE("child %s/%s failed (module: %s)", - path, - c ? c->name : de->d_name, - mn); - register_module_failure(mn); - } else { - LOGE("child %s/%s failed (no module_name)", - path, - c ? c->name : de->d_name); - } - - g_stats.nodes_fail++; - - if (now_tmp) { - closedir(d); - return -1; - } - } - } - closedir(d); - } - } - - for (size_t i = 0; i < node->child_count; ++i) { - Node *c = node->children[i]; - if (c->skip || c->done) continue; - - int r = do_magic(path, wpath, c, now_tmp); - if (r != 0) { - const char *mn = c->module_name ? - c->module_name : - node->module_name; - - if (mn) { - LOGE("child %s/%s failed (module: %s)", - path, c->name, mn); - register_module_failure(mn); - } else { - LOGE("child %s/%s failed (no module_name)", - path, c->name); - } - - g_stats.nodes_fail++; - if (now_tmp) return -1; - } - } - - if (create_tmp) { - (void)mount(NULL, wpath, NULL, - MS_REMOUNT | MS_BIND | MS_RDONLY, NULL); - - if (mount(wpath, path, NULL, MS_MOVE, NULL) < 0) { - LOGE("move %s->%s failed: %s", - wpath, path, strerror(errno)); - if (node->module_name) - register_module_failure(node->module_name); - return -1; - } - - LOGI("move mountpoint success: %s -> %s", wpath, path); - (void)mount(NULL, path, NULL, MS_REC | MS_PRIVATE, NULL); - } - - g_stats.nodes_mounted++; - return 0; - } - } - - return 0; -} - -static int mirror(const char *path, const char *work, const char *name) { - char src[PATH_MAX]; - char dst[PATH_MAX]; - - if (path_join(path, name, src, sizeof(src)) != 0 || - path_join(work, name, dst, sizeof(dst)) != 0) - return -1; - - struct stat st; - if (lstat(src, &st) < 0) { - LOGW("lstat %s: %s", src, strerror(errno)); - return 0; // 忽略 - } - - if (S_ISREG(st.st_mode)) { - int fd = open(dst, O_CREAT | O_WRONLY, st.st_mode & 07777); - if (fd < 0) { - LOGE("create %s: %s", dst, strerror(errno)); - return -1; - } - close(fd); - - if (mount(src, dst, NULL, MS_BIND, NULL) < 0) { - LOGE("bind %s->%s: %s", src, dst, strerror(errno)); - return -1; - } - } else if (S_ISDIR(st.st_mode)) { - if (mkdir(dst, st.st_mode & 07777) < 0 && errno != EEXIST) { - LOGE("mkdir %s: %s", dst, strerror(errno)); - return -1; - } - - chmod(dst, st.st_mode & 07777); - chown(dst, st.st_uid, st.st_gid); - - char *con = NULL; - if (lgetfilecon(src, &con) == 0 && con) { - lsetfilecon(dst, con); - free(con); - } - - DIR *d = opendir(src); - if (!d) { - LOGE("opendir %s: %s", src, strerror(errno)); - return -1; - } - - struct dirent *de; - while ((de = readdir(d))) { - if (!strcmp(de->d_name, ".") || - !strcmp(de->d_name, "..")) { - continue; - } - if (mirror(src, dst, de->d_name) != 0) { - closedir(d); - return -1; - } - } - closedir(d); - } else if (S_ISLNK(st.st_mode)) { - if (clone_symlink(src, dst) != 0) - return -1; - } - - return 0; -} - -static int magic_mount(const char *tmp_root) { - Node *root = collect_root(); - if (!root) { - LOGI("no modules, magic_mount skipped"); - return 0; - } - - char tmp_dir[PATH_MAX]; - if (path_join(tmp_root, "workdir", tmp_dir, sizeof(tmp_dir)) != 0) { - node_free(root); - return -1; - } - - if (mkdir_p(tmp_dir) != 0) { - node_free(root); - return -1; - } - - LOGI("starting magic_mount core logic: tmpfs_source=%s tmp_dir=%s", - g_mount_source, tmp_dir); - - if (mount(g_mount_source, tmp_dir, "tmpfs", 0, "") < 0) { - LOGE("mount tmpfs %s: %s", tmp_dir, strerror(errno)); - node_free(root); - return -1; - } - - (void)mount(NULL, tmp_dir, NULL, MS_REC | MS_PRIVATE, NULL); - - int rc = do_magic("/", tmp_dir, root, false); - if (rc != 0) - g_stats.nodes_fail++; - - if (umount2(tmp_dir, MNT_DETACH) < 0) - LOGE("umount %s: %s", tmp_dir, strerror(errno)); - - (void)rmdir(tmp_dir); - - node_free(root); - return rc; -} - -/*============================================================================== - * 进程环境 & CLI - *============================================================================*/ - -static void usage(const char *prog) { - fprintf(stderr, - "Magic Mount Version: %s\n" - "Usage: %s [options]\n" - " -m DIR module dir (default: %s)\n" - " -t DIR temp dir (default: auto rw tmpfs + \".magic_mount\")\n" - " -s SRC mount src (default: %s)\n" - " -p LIST extra partitions, comma-separated (e.g. mi_ext,my_stock)\n" - " -l FILE log file (default: stderr, '-'=stdout)\n" - " -v N verbose (0=ERROR,1=WARN,2=INFO,3=DEBUG)\n" - " -h help\n", - MAGIC_MOUNT_VERSION, - prog, - DEFAULT_MODULE_DIR, - DEFAULT_MOUNT_SOURCE); -} - -static int enter_pid1_ns(void) { - int fd = open("/proc/1/ns/mnt", O_RDONLY); - if (fd < 0) { - LOGE("open /proc/1/ns/mnt: %s", strerror(errno)); - return -1; - } - - if (setns(fd, 0) < 0) { - LOGE("setns: %s", strerror(errno)); - close(fd); - return -1; - } - - close(fd); - LOGI("entered pid1 mnt ns"); - return 0; -} - -int main(int argc, char **argv) { - const char *tmp_dir = NULL; - const char *log_path = NULL; - char auto_tmp[PATH_MAX] = {0}; - - for (int i = 1; i < argc; ++i) { - const char *arg = argv[i]; - - if ((strcmp(arg, "-m") == 0 || strcmp(arg, "--module-dir") == 0) && - i + 1 < argc) { - g_module_dir = argv[++i]; - - } else if ((strcmp(arg, "-t") == 0 || strcmp(arg, "--temp-dir") == 0) && - i + 1 < argc) { - tmp_dir = argv[++i]; - - } else if ((strcmp(arg, "-s") == 0 || - strcmp(arg, "--mount-source") == 0) && - i + 1 < argc) { - g_mount_source = argv[++i]; - - } else if ((strcmp(arg, "-l") == 0 || - strcmp(arg, "--log-file") == 0) && - i + 1 < argc) { - log_path = argv[++i]; - - } else if ((strcmp(arg, "-v") == 0 || - strcmp(arg, "--verbose") == 0) && - i + 1 < argc) { - g_log_level = atoi(argv[++i]); - if (g_log_level < 0) g_log_level = 0; - if (g_log_level > 3) g_log_level = 3; - - } else if ((strcmp(arg, "-p") == 0 || - strcmp(arg, "--partitions") == 0) && - i + 1 < argc) { - const char *list = argv[++i]; - const char *p = list; - while (*p) { - const char *start = p; - while (*p && *p != ',') p++; - size_t len = (size_t)(p - start); - add_extra_part_token(start, len); - if (*p == ',') p++; - } - - } else if (!strcmp(arg, "-h") || !strcmp(arg, "--help")) { - usage(argv[0]); - return 0; - - } else { - fprintf(stderr, "Unknown arg: %s\n", arg); - usage(argv[0]); - return 1; - } - } - - if (log_path) { - if (!strcmp(log_path, "-")) { - g_log_file = stdout; - } else { - g_log_file = fopen(log_path, "a"); - if (!g_log_file) { - fprintf(stderr, "open log %s: %s\n", - log_path, strerror(errno)); - return 1; - } - } - } - - if (!tmp_dir) { - tmp_dir = select_auto_tempdir(auto_tmp); - } - - if (geteuid() != 0) { - LOGE("must run as root (euid=%d)", (int)geteuid()); - if (g_log_file && g_log_file != stdout && g_log_file != stderr) - fclose(g_log_file); - free_string_array(&g_extra_parts, &g_extra_parts_count); - return 1; - } - - if (enter_pid1_ns() != 0) { - if (g_log_file && g_log_file != stdout && g_log_file != stderr) - fclose(g_log_file); - free_string_array(&g_extra_parts, &g_extra_parts_count); - return 1; - } - - int rc = 0; - - LOGI("starting magic_mount process: module_dir=%s temp_dir=%s " - "mount_source=%s log_level=%d", - g_module_dir, tmp_dir, g_mount_source, g_log_level); - - rc = magic_mount(tmp_dir); - - if (rc == 0) { - LOGI("magic_mount completed successfully"); - } else { - LOGE("magic_mount failed with rc=%d " - "(check previous logs for details)", rc); - } - - LOGI("magic_mount summary: modules=%d nodes_total=%d mounted=%d " - "skipped=%d whiteouts=%d failures=%d", - g_stats.modules_total, - g_stats.nodes_total, - g_stats.nodes_mounted, - g_stats.nodes_skipped, - g_stats.nodes_whiteout, - g_stats.nodes_fail); - - if (g_failed_modules_count > 0) { - LOGW("modules with failures (%d):", g_failed_modules_count); - for (int i = 0; i < g_failed_modules_count; ++i) { - LOGW(" failed module: %s", g_failed_modules[i]); - } - } - - - if (g_log_file && g_log_file != stdout && g_log_file != stderr) - fclose(g_log_file); - - free_string_array(&g_failed_modules, &g_failed_modules_count); - free_string_array(&g_extra_parts, &g_extra_parts_count); - - return rc == 0 ? 0 : 1; -} - diff --git a/src/.gitignore b/src/.gitignore new file mode 100644 index 0000000..5e56e04 --- /dev/null +++ b/src/.gitignore @@ -0,0 +1 @@ +/bin diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 0000000..f709dbb --- /dev/null +++ b/src/Makefile @@ -0,0 +1,87 @@ +PROJECT = magic_mount +VERSION ?= 0.1.0 + +# use zig cc +ZIG ?= zig +CC := $(ZIG) cc +STRIPPER := strip + +# source files +SRCS := utils.c magic_mount.c main.c + +# output directory +OUTDIR := bin + +# common flags +CFLAGS_COMMON := -D_POSIX_C_SOURCE=200809L -std=c23 -Wall -Wextra -pedantic +CFLAGS_COMMON += -DVERSION=\"$(VERSION)\" + +LDFLAGS_COMMON := -static + +# build mode specific flags +CFLAGS_RELEASE := -O2 -DNDEBUG +CFLAGS_DEBUG := -O0 -g -DDEBUG + +# zig target triples +TARGET_AMD64 ?= x86_64-linux-musl +TARGET_ARM64 ?= aarch64-linux-musl +TARGET_ARMV7 ?= arm-linux-musleabihf + +# output binary names +BIN_AMD64 := $(OUTDIR)/mm_amd64 +BIN_ARM64 := $(OUTDIR)/mm_arm64 +BIN_ARMV7 := $(OUTDIR)/mm_armv7 + +BINS := $(BIN_AMD64) $(BIN_ARM64) $(BIN_ARMV7) + +.PHONY: all clean release debug amd64 arm64 armv7 dirs help strip-bins + +# default target +all: release + +help: + @echo "Usage:" + @echo " make release [version=X.Y.Z] - Build release version (optimized, stripped)" + @echo " make debug [version=X.Y.Z] - Build debug version (with symbols)" + @echo " make clean - Clean build artifacts" + @echo "" + @echo "Individual targets:" + @echo " make amd64 - Build for x86_64" + @echo " make arm64 - Build for aarch64" + @echo " make armv7 - Build for armv7" + +dirs: + mkdir -p $(OUTDIR) + +# Release build (default) +release: export CFLAGS := $(CFLAGS_COMMON) $(CFLAGS_RELEASE) +release: dirs $(BINS) strip-bins + @echo "Release build completed: $(VERSION)" + +# Debug build +debug: export CFLAGS := $(CFLAGS_COMMON) $(CFLAGS_DEBUG) +debug: dirs $(BINS) + @echo "Debug build completed: $(VERSION)" + +strip-bins: + @echo "Stripping binaries..." + @for bin in $(BINS); do \ + echo " Stripping $$bin"; \ + $(STRIPPER) $$bin; \ + done + +amd64: $(BIN_AMD64) +arm64: $(BIN_ARM64) +armv7: $(BIN_ARMV7) + +$(BIN_AMD64): $(SRCS) + $(CC) -target $(TARGET_AMD64) $(CFLAGS) $^ -o $@ $(LDFLAGS_COMMON) + +$(BIN_ARM64): $(SRCS) + $(CC) -target $(TARGET_ARM64) $(CFLAGS) $^ -o $@ $(LDFLAGS_COMMON) + +$(BIN_ARMV7): $(SRCS) + $(CC) -target $(TARGET_ARMV7) $(CFLAGS) $^ -o $@ $(LDFLAGS_COMMON) + +clean: + rm -rf $(OUTDIR) diff --git a/src/magic_mount.c b/src/magic_mount.c new file mode 100644 index 0000000..c1eab06 --- /dev/null +++ b/src/magic_mount.c @@ -0,0 +1,702 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "magic_mount.h" +#include "utils.h" + +typedef enum { + NFT_REGULAR, + NFT_DIRECTORY, + NFT_SYMLINK, + NFT_WHITEOUT +} NodeType; + +typedef struct Node { + char *name; + NodeType type; + struct Node **children; + size_t child_count; + char *module_path; + char *module_name; + bool replace; + bool skip; + bool done; +} Node; + +typedef struct { + int modules_total; + int nodes_total; + int nodes_mounted; + int nodes_skipped; + int nodes_whiteout; + int nodes_fail; +} Stats; + +static Stats g_stats = {0}; + +Config g_config = { + .module_dir = DEFAULT_MODULE_DIR, + .mount_source = MOUNT_SOURCE, + .log_file = NULL, + .log_level = 2, + .failed_modules = NULL, + .failed_count = 0, + .extra_parts = NULL, + .extra_count = 0 +}; + +void magic_log_write(LogLevel level, const char *fmt, ...) { + if ((int)level > g_config.log_level) return; + static const char *level_str[] = {"ERROR", "WARN", "INFO", "DEBUG"}; + FILE *out = g_config.log_file ? g_config.log_file : stderr; + + fprintf(out, "[%s] ", level_str[level]); + va_list ap; + va_start(ap, fmt); + vfprintf(out, fmt, ap); + va_end(ap); + fputc('\n', out); + fflush(out); +} + +static int set_selinux_context(const char *path, const char *ctx) { + if (!path || !ctx) return 0; + LOGD("set_selinux(%s, \"%s\")", path, ctx); + if (lsetxattr(path, XATTR_SELINUX, ctx, strlen(ctx), 0) < 0) { + LOGW("set_selinux %s: %s", path, strerror(errno)); + return -1; + } + return 0; +} + +static char *get_selinux_context(const char *path) { + if (!path) return NULL; + ssize_t sz = lgetxattr(path, XATTR_SELINUX, NULL, 0); + if (sz <= 0) return NULL; + char *buf = malloc((size_t)sz + 1); + if (!buf) return NULL; + if (lgetxattr(path, XATTR_SELINUX, buf, (size_t)sz) != sz) { + free(buf); + return NULL; + } + buf[sz] = '\0'; + LOGD("get_selinux(%s) -> \"%s\"", path, buf); + return buf; +} + +static void copy_selinux_context(const char *src, const char *dst) { + char *ctx = get_selinux_context(src); + if (ctx) { + set_selinux_context(dst, ctx); + free(ctx); + } +} + +static Node *node_new(const char *name, NodeType type) { + Node *n = calloc(1, sizeof(Node)); + if (!n) return NULL; + n->name = strdup(name ? name : ""); + n->type = type; + return n; +} + +static void node_free(Node *n) { + if (!n) return; + for (size_t i = 0; i < n->child_count; i++) + node_free(n->children[i]); + free(n->children); + free(n->name); + free(n->module_path); + free(n->module_name); + free(n); +} + +static NodeType get_node_type(const struct stat *st) { + if (S_ISCHR(st->st_mode) && st->st_rdev == 0) return NFT_WHITEOUT; + if (S_ISREG(st->st_mode)) return NFT_REGULAR; + if (S_ISDIR(st->st_mode)) return NFT_DIRECTORY; + if (S_ISLNK(st->st_mode)) return NFT_SYMLINK; + return NFT_WHITEOUT; +} + +static bool is_dir_opaque(const char *path) { +#ifdef __linux__ + char buf[8]; + ssize_t len = lgetxattr(path, XATTR_OPAQUE, buf, sizeof(buf) - 1); + return len > 0 && buf[0] == 'y'; +#else + (void)path; + return false; +#endif +} + +static int node_add_child(Node *parent, Node *child) { + Node **arr = realloc(parent->children, (parent->child_count + 1) * sizeof(Node*)); + if (!arr) return -1; + parent->children = arr; + parent->children[parent->child_count++] = child; + return 0; +} + +static Node *node_find_child(Node *parent, const char *name) { + for (size_t i = 0; i < parent->child_count; i++) + if (strcmp(parent->children[i]->name, name) == 0) + return parent->children[i]; + return NULL; +} + +static Node *node_take_child(Node *parent, const char *name) { + for (size_t i = 0; i < parent->child_count; i++) { + if (strcmp(parent->children[i]->name, name) == 0) { + Node *n = parent->children[i]; + memmove(&parent->children[i], &parent->children[i + 1], + (parent->child_count - i - 1) * sizeof(Node*)); + parent->child_count--; + return n; + } + } + return NULL; +} + +static Node *create_node_from_path(const char *name, const char *path, + const char *module_name) { + struct stat st; + if (lstat(path, &st) < 0) return NULL; + if (!(S_ISCHR(st.st_mode) || S_ISREG(st.st_mode) || + S_ISDIR(st.st_mode) || S_ISLNK(st.st_mode))) + return NULL; + NodeType type = get_node_type(&st); + Node *n = node_new(name, type); + if (!n) return NULL; + n->module_path = strdup(path); + n->module_name = module_name ? strdup(module_name) : NULL; + n->replace = (type == NFT_DIRECTORY) && is_dir_opaque(path); + g_stats.nodes_total++; + return n; +} + +static bool is_module_disabled(const char *mod_dir) { + char buf[PATH_MAX]; + const char *disable_files[] = {DISABLE_FILE, REMOVE_FILE, SKIP_MOUNT_FILE}; + for (size_t i = 0; i < ARR_LEN(disable_files); i++) { + if (path_join(buf, sizeof(buf), mod_dir, disable_files[i]) == 0 && + path_exists(buf)) + return true; + } + return false; +} + +static int collect_module_files(Node *node, const char *dir, + const char *module_name, bool *has_any) { + DIR *d = opendir(dir); + if (!d) { + LOGE("opendir %s: %s", dir, strerror(errno)); + return -1; + } + + char path[PATH_MAX]; + struct dirent *de; + bool any = false; + + while ((de = readdir(d))) { + if (is_dot_or_dotdot(de->d_name)) continue; + if (path_join(path, sizeof(path), dir, de->d_name) != 0) { + closedir(d); + return -1; + } + Node *child = node_find_child(node, de->d_name); + if (!child) { + child = create_node_from_path(de->d_name, path, module_name); + if (child && node_add_child(node, child) != 0) { + node_free(child); + closedir(d); + return -1; + } + } + if (child && child->type == NFT_DIRECTORY) { + bool sub = false; + if (collect_module_files(child, path, module_name, &sub) != 0) { + closedir(d); + return -1; + } + if (sub || child->replace) any = true; + } else if (child) { + any = true; + } + } + closedir(d); + *has_any = any; + return 0; +} + +static int add_partition_nodes(Node *root, Node *system) { + struct { const char *name; bool need_symlink; } parts[] = { + {"vendor", true}, + {"system_ext", true}, + {"product", true}, + {"odm", false} + }; + + char root_path[PATH_MAX], sys_path[PATH_MAX]; + + for (size_t i = 0; i < ARR_LEN(parts); i++) { + if (path_join(root_path, sizeof(root_path), "/", parts[i].name) != 0 || + path_join(sys_path, sizeof(sys_path), "/system", parts[i].name) != 0) + return -1; + if (!is_dir(root_path)) continue; + if (parts[i].need_symlink && !is_symlink(sys_path)) continue; + Node *child = node_take_child(system, parts[i].name); + if (child && node_add_child(root, child) != 0) { + node_free(child); + return -1; + } + } + + for (int i = 0; i < g_config.extra_count; i++) { + if (path_join(root_path, sizeof(root_path), "/", g_config.extra_parts[i]) != 0) + return -1; + if (!is_dir(root_path)) continue; + Node *child = node_take_child(system, g_config.extra_parts[i]); + if (child && node_add_child(root, child) != 0) { + node_free(child); + return -1; + } + } + return 0; +} + +static Node *scan_modules(void) { + Node *root = node_new("", NFT_DIRECTORY); + Node *system = node_new("system", NFT_DIRECTORY); + if (!root || !system) { + node_free(root); + node_free(system); + return NULL; + } + + DIR *d = opendir(g_config.module_dir); + if (!d) { + LOGE("opendir %s: %s", g_config.module_dir, strerror(errno)); + node_free(root); + node_free(system); + return NULL; + } + + char mod_path[PATH_MAX], sys_path[PATH_MAX]; + struct dirent *de; + bool has_any = false; + + while ((de = readdir(d))) { + if (is_dot_or_dotdot(de->d_name)) continue; + if (path_join(mod_path, sizeof(mod_path), g_config.module_dir, de->d_name) != 0) + continue; + if (!is_dir(mod_path) || is_module_disabled(mod_path)) continue; + if (path_join(sys_path, sizeof(sys_path), mod_path, "system") != 0) + continue; + if (!is_dir(sys_path)) continue; + + LOGD("scanning module: %s", de->d_name); + g_stats.modules_total++; + + bool sub = false; + if (collect_module_files(system, sys_path, de->d_name, &sub) == 0 && sub) + has_any = true; + } + + closedir(d); + + if (!has_any) { + node_free(root); + node_free(system); + return NULL; + } + + g_stats.nodes_total += 2; /* root + system */ + + if (add_partition_nodes(root, system) != 0) { + node_free(root); + node_free(system); + return NULL; + } + + if (node_add_child(root, system) != 0) { + node_free(root); + node_free(system); + return NULL; + } + return root; +} + +static int clone_symlink(const char *src, const char *dst) { + char target[PATH_MAX]; + ssize_t len = readlink(src, target, sizeof(target) - 1); + if (len < 0) { + LOGE("readlink %s: %s", src, strerror(errno)); + return -1; + } + target[len] = '\0'; + if (symlink(target, dst) < 0) { + LOGE("symlink %s->%s: %s", dst, target, strerror(errno)); + return -1; + } + copy_selinux_context(src, dst); + LOGD("clone_symlink: %s -> %s", dst, target); + return 0; +} + +static int mirror_file(const char *src, const char *dst, const struct stat *st) { + int fd = open(dst, O_CREAT | O_WRONLY, st->st_mode & 07777); + if (fd < 0) { + LOGE("create %s: %s", dst, strerror(errno)); + return -1; + } + close(fd); + if (mount(src, dst, NULL, MS_BIND, NULL) < 0) { + LOGE("bind %s->%s: %s", src, dst, strerror(errno)); + return -1; + } + return 0; +} + +static int mirror_dir(const char *src, const char *dst, const struct stat *st); + +static int mirror_entry(const char *src_base, const char *dst_base, const char *name) { + char src[PATH_MAX], dst[PATH_MAX]; + if (path_join(src, sizeof(src), src_base, name) != 0 || + path_join(dst, sizeof(dst), dst_base, name) != 0) + return -1; + struct stat st; + if (lstat(src, &st) < 0) { + LOGW("lstat %s: %s", src, strerror(errno)); + return 0; + } + if (S_ISREG(st.st_mode)) return mirror_file(src, dst, &st); + else if (S_ISDIR(st.st_mode)) return mirror_dir(src, dst, &st); + else if (S_ISLNK(st.st_mode)) return clone_symlink(src, dst); + return 0; +} + +static int mirror_dir(const char *src, const char *dst, const struct stat *st) { + if (mkdir(dst, st->st_mode & 07777) < 0 && errno != EEXIST) { + LOGE("mkdir %s: %s", dst, strerror(errno)); + return -1; + } + chmod(dst, st->st_mode & 07777); + chown(dst, st->st_uid, st->st_gid); + copy_selinux_context(src, dst); + + DIR *d = opendir(src); + if (!d) { + LOGE("opendir %s: %s", src, strerror(errno)); + return -1; + } + + struct dirent *de; + int ret = 0; + while ((de = readdir(d))) { + if (is_dot_or_dotdot(de->d_name)) continue; + if (mirror_entry(src, dst, de->d_name) != 0) { + ret = -1; + break; + } + } + closedir(d); + return ret; +} + +static int mount_regular_file(Node *node, const char *path, + const char *wpath, bool has_tmpfs) { + const char *target = has_tmpfs ? wpath : path; + if (has_tmpfs) { + int fd = open(wpath, O_CREAT | O_WRONLY, 0644); + if (fd < 0) { + LOGE("create %s: %s", wpath, strerror(errno)); + return -1; + } + close(fd); + } + if (!node->module_path) { + LOGE("no module_path for %s", path); + return -1; + } + LOGD("bind %s -> %s", node->module_path, target); + if (mount(node->module_path, target, NULL, MS_BIND, NULL) < 0) { + LOGE("bind %s->%s: %s", node->module_path, target, strerror(errno)); + return -1; + } + mount(NULL, target, NULL, MS_REMOUNT | MS_BIND | MS_RDONLY, NULL); + g_stats.nodes_mounted++; + return 0; +} + +static int mount_directory(Node *node, const char *path, + const char *wpath, bool has_tmpfs); + +static int process_child(Node *child, const char *path, + const char *wpath, bool has_tmpfs) { + if (child->skip) { + child->done = true; + return 0; + } + child->done = true; + return mount_directory(child, path, wpath, has_tmpfs); +} + +static bool need_tmpfs_for_child(Node *child, const char *real_path) { + char path[PATH_MAX]; + if (path_join(path, sizeof(path), real_path, child->name) != 0) + return false; + if (child->type == NFT_SYMLINK) return true; + if (child->type == NFT_WHITEOUT) return path_exists(path); + + struct stat st; + if (lstat(path, &st) < 0) return true; + NodeType real_type = get_node_type(&st); + return real_type != child->type || real_type == NFT_SYMLINK; +} + +static bool check_need_tmpfs(Node *node, const char *path, bool has_tmpfs) { + if (!has_tmpfs && node->replace && node->module_path) + return true; + for (size_t i = 0; i < node->child_count; i++) + if (need_tmpfs_for_child(node->children[i], path)) + return true; + return false; +} + +static int setup_tmpfs_dir(const char *wpath, const char *path, + const char *module_path) { + if (mkdir_recursive(wpath) != 0) return -1; + struct stat st; + const char *meta = NULL; + if (stat(path, &st) == 0) meta = path; + else if (module_path && stat(module_path, &st) == 0) meta = module_path; + else { + LOGE("no metadata for %s", path); + return -1; + } + chmod(wpath, st.st_mode & 07777); + chown(wpath, st.st_uid, st.st_gid); + copy_selinux_context(meta, wpath); + return 0; +} + +static int mount_directory(Node *node, const char *base_path, + const char *base_work, bool has_tmpfs) { + char path[PATH_MAX], wpath[PATH_MAX]; + if (path_join(path, sizeof(path), base_path, node->name) != 0 || + path_join(wpath, sizeof(wpath), base_work, node->name) != 0) + return -1; + + bool create_tmpfs = check_need_tmpfs(node, path, has_tmpfs); + bool now_tmpfs = has_tmpfs || create_tmpfs; + + if (now_tmpfs && setup_tmpfs_dir(wpath, path, node->module_path) != 0) + return -1; + + if (create_tmpfs && + mount(wpath, wpath, NULL, MS_BIND, NULL) < 0) { + LOGE("bind self %s: %s", wpath, strerror(errno)); + return -1; + } + + /* 处理原目录内容 */ + if (path_exists(path) && !node->replace) { + DIR *d = opendir(path); + if (d) { + struct dirent *de; + while ((de = readdir(d))) { + if (is_dot_or_dotdot(de->d_name)) continue; + Node *child = node_find_child(node, de->d_name); + int ret = child + ? process_child(child, path, wpath, now_tmpfs) + : (now_tmpfs ? mirror_entry(path, wpath, de->d_name) : 0); + if (ret != 0) { + const char *mod = (child && child->module_name) + ? child->module_name : node->module_name; + if (mod) { + LOGE("child %s/%s failed (module: %s)", path, de->d_name, mod); + add_to_array(&g_config.failed_modules, + &g_config.failed_count, mod); + } + g_stats.nodes_fail++; + if (now_tmpfs) { + closedir(d); + return -1; + } + } + } + closedir(d); + } else if (now_tmpfs) { + LOGE("opendir %s: %s", path, strerror(errno)); + return -1; + } + } + + for (size_t i = 0; i < node->child_count; i++) { + Node *child = node->children[i]; + if (child->skip || child->done) continue; + if (process_child(child, path, wpath, now_tmpfs) != 0) { + const char *mod = child->module_name ? child->module_name : node->module_name; + if (mod) { + LOGE("child %s/%s failed (module: %s)", path, child->name, mod); + add_to_array(&g_config.failed_modules, &g_config.failed_count, mod); + } + g_stats.nodes_fail++; + if (now_tmpfs) return -1; + } + } + + if (create_tmpfs) { + mount(NULL, wpath, NULL, MS_REMOUNT | MS_BIND | MS_RDONLY, NULL); + if (mount(wpath, path, NULL, MS_MOVE, NULL) < 0) { + LOGE("move %s->%s: %s", wpath, path, strerror(errno)); + if (node->module_name) + add_to_array(&g_config.failed_modules, + &g_config.failed_count, node->module_name); + return -1; + } + LOGI("moved tmpfs: %s -> %s", wpath, path); + mount(NULL, path, NULL, MS_REC | MS_PRIVATE, NULL); + } + + g_stats.nodes_mounted++; + return 0; +} + +static int mount_node(Node *node, const char *path, + const char *wpath, bool has_tmpfs) { + switch (node->type) { + case NFT_REGULAR: + return mount_regular_file(node, path, wpath, has_tmpfs); + case NFT_SYMLINK: + if (!node->module_path) { + LOGE("no module_path for symlink %s", path); + return -1; + } + if (clone_symlink(node->module_path, wpath) == 0) { + g_stats.nodes_mounted++; + return 0; + } + return -1; + case NFT_WHITEOUT: + LOGD("whiteout: %s", path); + g_stats.nodes_whiteout++; + return 0; + case NFT_DIRECTORY: + return mount_directory(node, "/", wpath, has_tmpfs); + } + return 0; +} + +const char *magic_mount_select_temp_dir(char *buf) { + const char *candidates[] = {"/mnt/vendor", "/mnt", "/debug_ramdisk"}; + for (size_t i = 0; i < ARR_LEN(candidates); i++) { + if (!is_rw_tmpfs(candidates[i])) continue; + if (path_join(buf, PATH_MAX, candidates[i], ".magic_mount") == 0) { + LOGI("auto temp_dir: %s (from %s)", buf, candidates[i]); + return buf; + } + } + LOGW("no rw tmpfs, using fallback: %s", DEFAULT_TEMP_DIR); + return DEFAULT_TEMP_DIR; +} + +int magic_mount_run(const char *temp_root) { + Node *root = scan_modules(); + if (!root) { + LOGI("no modules found"); + return 0; + } + + char tmpfs_dir[PATH_MAX]; + if (path_join(tmpfs_dir, sizeof(tmpfs_dir), temp_root, "workdir") != 0) { + node_free(root); + return -1; + } + if (mkdir_recursive(tmpfs_dir) != 0) { + node_free(root); + return -1; + } + + LOGI("mounting tmpfs: %s (source=%s)", tmpfs_dir, g_config.mount_source); + if (mount(g_config.mount_source, tmpfs_dir, "tmpfs", 0, "") < 0) { + LOGE("mount tmpfs %s: %s", tmpfs_dir, strerror(errno)); + node_free(root); + return -1; + } + mount(NULL, tmpfs_dir, NULL, MS_REC | MS_PRIVATE, NULL); + + int ret = mount_node(root, "/", tmpfs_dir, false); + if (ret != 0) g_stats.nodes_fail++; + + umount2(tmpfs_dir, MNT_DETACH); + rmdir(tmpfs_dir); + node_free(root); + return ret; +} + +int magic_mount_enter_pid1_mntns(void) { + int fd = open("/proc/1/ns/mnt", O_RDONLY); + if (fd < 0) { + LOGE("open /proc/1/ns/mnt: %s", strerror(errno)); + return -1; + } + if (setns(fd, 0) < 0) { + LOGE("setns: %s", strerror(errno)); + close(fd); + return -1; + } + close(fd); + LOGI("entered pid1 mount namespace"); + return 0; +} + +void magic_mount_print_summary(void) { + LOGI("summary: modules=%d nodes=%d mounted=%d skipped=%d whiteouts=%d failures=%d", + g_stats.modules_total, g_stats.nodes_total, g_stats.nodes_mounted, + g_stats.nodes_skipped, g_stats.nodes_whiteout, g_stats.nodes_fail); + if (g_config.failed_count > 0) { + LOGW("failed modules (%d):", g_config.failed_count); + for (int i = 0; i < g_config.failed_count; i++) + LOGW(" - %s", g_config.failed_modules[i]); + } +} + +void magic_mount_parse_partitions(const char *list) { + const char *p = list; + while (*p) { + const char *start = p; + while (*p && *p != ',') p++; + size_t len = (size_t)(p - start); + + while (len > 0 && (start[0] == ' ' || start[0] == '\t')) { + start++; + len--; + } + while (len > 0 && (start[len-1] == ' ' || start[len-1] == '\t')) + len--; + + if (len > 0) { + char *name = malloc(len + 1); + if (name) { + memcpy(name, start, len); + name[len] = '\0'; + add_to_array(&g_config.extra_parts, &g_config.extra_count, name); + free(name); + } + } + if (*p == ',') p++; + } +} diff --git a/src/magic_mount.h b/src/magic_mount.h new file mode 100644 index 0000000..7b836fc --- /dev/null +++ b/src/magic_mount.h @@ -0,0 +1,55 @@ +#ifndef MAGIC_MOUNT_H +#define MAGIC_MOUNT_H + +#include + +#define DEFAULT_MODULE_DIR "/data/adb/modules" +#define DEFAULT_TEMP_DIR "/dev/.magic_mount" +#define MOUNT_SOURCE "KSU" + +#define DISABLE_FILE "disable" +#define REMOVE_FILE "remove" +#define SKIP_MOUNT_FILE "skip_mount" + +#define XATTR_OPAQUE "trusted.overlay.opaque" +#define XATTR_SELINUX "security.selinux" + +#define ARR_LEN(a) (sizeof(a) / sizeof((a)[0])) + +typedef enum { + LOG_ERROR = 0, + LOG_WARN = 1, + LOG_INFO = 2, + LOG_DEBUG = 3 +} LogLevel; + +typedef struct { + const char *module_dir; + const char *mount_source; + FILE *log_file; + int log_level; + char **failed_modules; + int failed_count; + char **extra_parts; + int extra_count; +} Config; + +extern Config g_config; + +// shim +int setns(int fd, int nstype); + +void magic_log_write(LogLevel level, const char *fmt, ...); + +#define LOGE(...) magic_log_write(LOG_ERROR, __VA_ARGS__) +#define LOGW(...) magic_log_write(LOG_WARN, __VA_ARGS__) +#define LOGI(...) magic_log_write(LOG_INFO, __VA_ARGS__) +#define LOGD(...) magic_log_write(LOG_DEBUG, __VA_ARGS__) + +const char *magic_mount_select_temp_dir(char *buf); +int magic_mount_run(const char *temp_root); +int magic_mount_enter_pid1_mntns(void); +void magic_mount_print_summary(void); +void magic_mount_parse_partitions(const char *list); + +#endif /* MAGIC_MOUNT_H */ diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..959cb73 --- /dev/null +++ b/src/main.c @@ -0,0 +1,104 @@ +#include +#include +#include +#include +#include + +#include "magic_mount.h" +#include "utils.h" + +static void usage(const char *prog) { + fprintf(stderr, + "Magic Mount v%s\n" + "Usage: %s [options]\n" + " -m DIR module directory (default: %s)\n" + " -t DIR temp directory (default: auto)\n" + " -s SRC mount source (default: %s)\n" + " -p LIST extra partitions (-p mi_ext,my_stock)\n" + " -l FILE log file ('-' for stdout, default: stderr)\n" + " -v N log level (0-3, default: 2)\n" + " -h show this help\n", + VERSION, prog, DEFAULT_MODULE_DIR, MOUNT_SOURCE); +} + +int main(int argc, char **argv) { + const char *temp_dir = NULL; + const char *log_path = NULL; + char auto_temp[PATH_MAX] = {0}; + int ret = 1; + + /* 解析参数 */ + for (int i = 1; i < argc; i++) { + if ((strcmp(argv[i], "-m") == 0 || strcmp(argv[i], "--module-dir") == 0) && + i + 1 < argc) { + g_config.module_dir = argv[++i]; + } else if ((strcmp(argv[i], "-t") == 0 || strcmp(argv[i], "--temp-dir") == 0) && + i + 1 < argc) { + temp_dir = argv[++i]; + } else if ((strcmp(argv[i], "-s") == 0 || strcmp(argv[i], "--mount-source") == 0) && + i + 1 < argc) { + g_config.mount_source = argv[++i]; + } else if ((strcmp(argv[i], "-l") == 0 || strcmp(argv[i], "--log-file") == 0) && + i + 1 < argc) { + log_path = argv[++i]; + } else if ((strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--verbose") == 0) && + i + 1 < argc) { + g_config.log_level = atoi(argv[++i]); + if (g_config.log_level < 0) g_config.log_level = 0; + if (g_config.log_level > 3) g_config.log_level = 3; + } else if ((strcmp(argv[i], "-p") == 0 || strcmp(argv[i], "--partitions") == 0) && + i + 1 < argc) { + magic_mount_parse_partitions(argv[++i]); + } else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) { + usage(argv[0]); + return 0; + } else { + fprintf(stderr, "Unknown option: %s\n", argv[i]); + usage(argv[0]); + return 1; + } + } + + /* 打开日志文件 */ + if (log_path) { + g_config.log_file = strcmp(log_path, "-") == 0 ? + stdout : fopen(log_path, "a"); + if (!g_config.log_file && strcmp(log_path, "-") != 0) { + fprintf(stderr, "Failed to open log: %s\n", strerror(errno)); + return 1; + } + } + + /* 选择临时目录 */ + if (!temp_dir) + temp_dir = magic_mount_select_temp_dir(auto_temp); + + /* 权限检查 */ + if (geteuid() != 0) { + fprintf(stderr, "must run as root\n"); + goto cleanup; + } + + /* 进入 PID1 命名空间 */ + if (magic_mount_enter_pid1_mntns() != 0) + goto cleanup; + + /* 执行挂载 */ + fprintf(stderr, "starting: module_dir=%s temp_dir=%s source=%s level=%d\n", + g_config.module_dir, temp_dir, g_config.mount_source, g_config.log_level); + + ret = magic_mount_run(temp_dir); + + fprintf(stderr, "%s\n", ret == 0 ? "completed successfully" : "failed"); + magic_mount_print_summary(); + +cleanup: + if (g_config.log_file && g_config.log_file != stdout && + g_config.log_file != stderr) + fclose(g_config.log_file); + + free_str_array(&g_config.failed_modules, &g_config.failed_count); + free_str_array(&g_config.extra_parts, &g_config.extra_count); + + return ret == 0 ? 0 : 1; +} diff --git a/src/utils.c b/src/utils.c new file mode 100644 index 0000000..91ba551 --- /dev/null +++ b/src/utils.c @@ -0,0 +1,87 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "utils.h" + +int path_join(char *buf, size_t n, const char *base, const char *name) { + if (!base) return -1; + if (!name || !name[0]) return snprintf(buf, n, "%s", base) >= (int)n ? -1 : 0; + size_t len = strlen(base); + bool has_slash = (len > 0 && base[len-1] == '/'); + return snprintf(buf, n, has_slash ? "%s%s" : "%s/%s", base, name) >= (int)n ? -1 : 0; +} + +bool path_exists(const char *p) { return access(p, F_OK) == 0; } + +bool is_dir(const char *p) { + struct stat st; + return stat(p, &st) == 0 && S_ISDIR(st.st_mode); +} + +bool is_symlink(const char *p) { + struct stat st; + return lstat(p, &st) == 0 && S_ISLNK(st.st_mode); +} + +int mkdir_recursive(const char *dir) { + if (!dir || !dir[0]) return -1; + if (is_dir(dir)) return 0; + + char tmp[PATH_MAX]; + if (snprintf(tmp, sizeof(tmp), "%s", dir) >= (int)sizeof(tmp)) return -1; + + char *p = strrchr(tmp, '/'); + if (p && p != tmp) { + *p = 0; + if (mkdir_recursive(tmp) != 0) return -1; + } + return (mkdir(dir, 0755) == 0 || errno == EEXIST) ? 0 : -1; +} + +bool is_rw_tmpfs(const char *path) { + struct statfs s; + if (!is_dir(path) || statfs(path, &s) < 0) return false; + if ((unsigned long)s.f_type != TMPFS_MAGIC) return false; + + char tmp[PATH_MAX]; + if (path_join(tmp, sizeof(tmp), path, ".test_XXXXXX") != 0) return false; + + int fd = mkstemp(tmp); + if (fd < 0) return false; + close(fd); + unlink(tmp); + return true; +} + +void free_str_array(char ***arr, int *count) { + if (!arr || !*arr) return; + for (int i = 0; i < *count; i++) free((*arr)[i]); + free(*arr); + *arr = NULL; + *count = 0; +} + +void add_to_array(char ***arr, int *count, const char *str) { + for (int i = 0; i < *count; i++) + if (strcmp((*arr)[i], str) == 0) return; + + char **new_arr = realloc(*arr, (size_t)(*count + 1) * sizeof(char*)); + if (!new_arr) return; + + *arr = new_arr; + (*arr)[*count] = strdup(str); + if ((*arr)[*count]) (*count)++; +} + +bool is_dot_or_dotdot(const char *n) { + return n && (!strcmp(n, ".") || !strcmp(n, "..")); +} diff --git a/src/utils.h b/src/utils.h new file mode 100644 index 0000000..d400db4 --- /dev/null +++ b/src/utils.h @@ -0,0 +1,27 @@ +#ifndef UTILS_H +#define UTILS_H + +#include +#include + +#ifndef PATH_MAX +#define PATH_MAX 4096 +#endif + +#ifndef TMPFS_MAGIC +#define TMPFS_MAGIC 0x01021994 +#endif + +int path_join(char *buf, size_t n, const char *base, const char *name); +bool path_exists(const char *p); +bool is_dir(const char *p); +bool is_symlink(const char *p); +int mkdir_recursive(const char *dir); +bool is_rw_tmpfs(const char *path); + +void free_str_array(char ***arr, int *count); +void add_to_array(char ***arr, int *count, const char *str); + +bool is_dot_or_dotdot(const char *n); + +#endif /* UTILS_H */ diff --git a/module/customize.sh b/template/customize.sh similarity index 80% rename from module/customize.sh rename to template/customize.sh index 0805bf4..8e3051c 100644 --- a/module/customize.sh +++ b/template/customize.sh @@ -14,7 +14,11 @@ case "$ABI" in ;; armeabi-v7a) ui_print "- Selected architecture: ARM" - ARCH_BINARY="mm_arm" + ARCH_BINARY="mm_armv7" + ;; + x86_64) + ui_print "- Selected architecture: AMD64" + ARCH_BINARY="mm_amd64" ;; *) abort "! Unsupported architecture: $ABI" @@ -29,10 +33,10 @@ fi ui_print "- Installing $ARCH_BINARY as meta-mm" # Rename the selected binary to the generic name -mv "$MODPATH/$ARCH_BINARY" "$MODPATH/meta-mm" || abort "! Failed to rename binary" +mv "$MODPATH/bin/$ARCH_BINARY" "$MODPATH/meta-mm" || abort "! Failed to rename binary" # Remove the unused binary -# rm -f "$MODPATH/$REMOVE_BINARY" +rm -rf "$MODPATH/bin" # Ensure the binary is executable chmod 755 "$MODPATH/meta-mm" || abort "! Failed to set permissions" diff --git a/module/metainstall.sh b/template/metainstall.sh similarity index 100% rename from module/metainstall.sh rename to template/metainstall.sh diff --git a/module/metamount.sh b/template/metamount.sh similarity index 73% rename from module/metamount.sh rename to template/metamount.sh index 74d0574..50f969a 100644 --- a/module/metamount.sh +++ b/template/metamount.sh @@ -14,15 +14,16 @@ fi # Set dual-directory environment variables MODULE_METADATA_DIR="/data/adb/modules" -MODULE_SOURCE="KSU" +MODULE_METADATA_SOURCE="KSU" +MODULE_METADATA_LOGLEVEL=3 MODULE_METADATA_LOGFILE="/data/adb/mm.log" if [ -f "$MODULE_METADATA_LOGFILE" ]; then - mv "$MODULE_METADATA_LOGFILE" "$MODULE_METADATA_LOGFILE".bak + mv "$MODULE_METADATA_LOGFILE" "$MODULE_METADATA_LOGFILE".old fi # Execute the mount binary -"$BINARY" -s "$MODULE_SOURCE" -m "$MODULE_METADATA_DIR" -v -l "$MODULE_METADATA_LOGFILE" +"$BINARY" -s "$MODULE_METADATA_SOURCE" -m "$MODULE_METADATA_DIR" -v "$MODULE_METADATA_LOGLEVEL" -l "$MODULE_METADATA_LOGFILE" EXIT_CODE=$? diff --git a/module/metauninstall.sh b/template/metauninstall.sh similarity index 100% rename from module/metauninstall.sh rename to template/metauninstall.sh diff --git a/module/module.prop b/template/module.prop similarity index 85% rename from module/module.prop rename to template/module.prop index 5454040..79de0d0 100644 --- a/module/module.prop +++ b/template/module.prop @@ -1,8 +1,8 @@ id=meta-mm metamodule=1 name=Magic Mount MetaModule -version=0.1.0 -versionCode=2511190013 +version=v0.1.0 +versionCode=2511201925 author=7a72 description=An implementation of a metamodule using Magic Mount, Based on MKSU, Template is from meta-overlay updateJson=https://zrlab.org/api/m/k/mm/update.json diff --git a/module/uninstall.sh b/template/uninstall.sh similarity index 100% rename from module/uninstall.sh rename to template/uninstall.sh