Revert 5b41d03592
This commit is contained in:
parent
ceee9808fc
commit
ddd4a55bf6
15 changed files with 1012 additions and 1120 deletions
301
build.sh
301
build.sh
|
|
@ -9,13 +9,25 @@ NC='\033[0m' # No Color
|
|||
# Default values
|
||||
VERSION=""
|
||||
BUILD_TYPES=()
|
||||
BASE_URL="https://github.com/7a72/meta-magic_mount/releases/download"
|
||||
UPDATE_JSON_URL="https://raw.githubusercontent.com/7a72/meta-magic_mount/public/update.json"
|
||||
CHANGELOG_URL="https://raw.githubusercontent.com/7a72/meta-magic_mount/public/changelog.md"
|
||||
|
||||
# Rust targets
|
||||
RUST_TARGETS=(
|
||||
"x86_64-unknown-linux-musl"
|
||||
"armv7-unknown-linux-musleabihf"
|
||||
"aarch64-unknown-linux-musl"
|
||||
)
|
||||
|
||||
# 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 "Usage: $0 [--release] [--debug] [--url <base_url>] [--update-json-url <url>]"
|
||||
echo " --release Build release version"
|
||||
echo " --debug Build debug version"
|
||||
echo " --url <url> Set base URL for downloads (default: $BASE_URL)"
|
||||
echo " --update-json-url Set update JSON URL (default: $UPDATE_JSON_URL)"
|
||||
echo " (no option) Build both release and debug versions"
|
||||
echo ""
|
||||
echo "Version number will be automatically obtained from git tag"
|
||||
exit 1
|
||||
|
|
@ -32,6 +44,14 @@ while [[ $# -gt 0 ]]; do
|
|||
BUILD_TYPES+=("debug")
|
||||
shift
|
||||
;;
|
||||
--url)
|
||||
BASE_URL="$2"
|
||||
shift 2
|
||||
;;
|
||||
--update-json-url)
|
||||
UPDATE_JSON_URL="$2"
|
||||
shift 2
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
;;
|
||||
|
|
@ -54,6 +74,19 @@ if ! git rev-parse --git-dir > /dev/null 2>&1; then
|
|||
exit 1
|
||||
fi
|
||||
|
||||
# Check if cargo is installed
|
||||
if ! command -v cargo &> /dev/null; then
|
||||
echo -e "${RED}Error: cargo is not installed${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if cargo-zigbuild is installed
|
||||
if ! cargo zigbuild --help &> /dev/null 2>&1; then
|
||||
echo -e "${RED}Error: cargo-zigbuild is not installed${NC}"
|
||||
echo -e "${YELLOW}Install it with: cargo install cargo-zigbuild${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get git tag as version number
|
||||
VERSION=$(git describe --tags --exact-match 2>/dev/null)
|
||||
if [ -z "$VERSION" ]; then
|
||||
|
|
@ -85,6 +118,161 @@ else
|
|||
fi
|
||||
fi
|
||||
|
||||
# Function to generate changelog from git commits
|
||||
generate_changelog() {
|
||||
local CHANGELOG_FILE="$1"
|
||||
local PREV_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null)
|
||||
|
||||
echo -e "${YELLOW}Generating changelog...${NC}"
|
||||
|
||||
# Create changelog header
|
||||
cat > "$CHANGELOG_FILE" << EOF
|
||||
# Changelog
|
||||
|
||||
## Version $VERSION ($(date +%Y-%m-%d))
|
||||
|
||||
### Changes
|
||||
|
||||
EOF
|
||||
|
||||
# Add commits since last tag
|
||||
if [ -n "$PREV_TAG" ]; then
|
||||
echo "Changes since $PREV_TAG:" >> "$CHANGELOG_FILE"
|
||||
echo "" >> "$CHANGELOG_FILE"
|
||||
git log --pretty=format:"- %s (%h)" "$PREV_TAG"..HEAD >> "$CHANGELOG_FILE"
|
||||
else
|
||||
echo "Initial release" >> "$CHANGELOG_FILE"
|
||||
echo "" >> "$CHANGELOG_FILE"
|
||||
git log --pretty=format:"- %s (%h)" >> "$CHANGELOG_FILE"
|
||||
fi
|
||||
|
||||
echo "" >> "$CHANGELOG_FILE"
|
||||
echo "" >> "$CHANGELOG_FILE"
|
||||
echo "---" >> "$CHANGELOG_FILE"
|
||||
echo "Full Changelog: https://github.com/7a72/meta-magic_mount/compare/${PREV_TAG}...${VERSION}" >> "$CHANGELOG_FILE"
|
||||
|
||||
echo -e "${GREEN}Changelog generated: $CHANGELOG_FILE${NC}"
|
||||
}
|
||||
|
||||
# Function to generate main update JSON (for release version)
|
||||
generate_main_update_json() {
|
||||
local OUTPUT_NAME=$1
|
||||
local VERSION_CODE=$2
|
||||
local JSON_FILE="build/update.json"
|
||||
|
||||
echo -e "${YELLOW}Generating main update JSON...${NC}"
|
||||
|
||||
cat > "$JSON_FILE" << EOF
|
||||
{
|
||||
"versionCode": $VERSION_CODE,
|
||||
"version": "$VERSION",
|
||||
"zipUrl": "$BASE_URL/$VERSION/$OUTPUT_NAME",
|
||||
"changelog": "$CHANGELOG_URL"
|
||||
}
|
||||
EOF
|
||||
|
||||
echo -e "${GREEN}Main update JSON generated: $JSON_FILE${NC}"
|
||||
}
|
||||
|
||||
# Function to build Rust project for all targets
|
||||
build_rust_binaries() {
|
||||
local BUILD_TYPE=$1
|
||||
local BIN_DIR="src/bin"
|
||||
|
||||
echo -e "\n${YELLOW}Building Rust binaries for all targets...${NC}"
|
||||
|
||||
# Create bin directory if it doesn't exist
|
||||
if [ -d "$BIN_DIR" ]; then
|
||||
rm -rf "$BIN_DIR"
|
||||
fi
|
||||
mkdir -p "$BIN_DIR"
|
||||
|
||||
# Change to src directory
|
||||
if [ ! -d "src" ]; then
|
||||
echo -e "${RED}Error: src directory does not exist${NC}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
cd src || return 1
|
||||
|
||||
# Clean previous builds
|
||||
cargo clean
|
||||
|
||||
# Build for each target
|
||||
for target in "${RUST_TARGETS[@]}"; do
|
||||
echo -e "${YELLOW}Building for target: $target${NC}"
|
||||
|
||||
# Determine build command based on build type
|
||||
if [ "$BUILD_TYPE" = "release" ]; then
|
||||
cargo zigbuild --release --target "$target"
|
||||
else
|
||||
cargo zigbuild --target "$target"
|
||||
fi
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${RED}Error: Failed to build for target $target${NC}"
|
||||
cd ..
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Determine build output directory
|
||||
if [ "$BUILD_TYPE" = "release" ]; then
|
||||
BUILD_OUTPUT_DIR="target/$target/release"
|
||||
else
|
||||
BUILD_OUTPUT_DIR="target/$target/debug"
|
||||
fi
|
||||
|
||||
# Get the binary name from Cargo.toml (assuming single binary project)
|
||||
BINARY_NAME=$(cargo metadata --format-version 1 --no-deps 2>/dev/null | grep -o '"name":"[^"]*"' | head -1 | cut -d'"' -f4)
|
||||
|
||||
if [ -z "$BINARY_NAME" ]; then
|
||||
echo -e "${RED}Error: Could not determine binary name${NC}"
|
||||
cd ..
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Copy binary to bin directory with target-specific name
|
||||
# Map target to Android ABI
|
||||
case "$target" in
|
||||
"x86_64-unknown-linux-musl")
|
||||
ABI_DIR="x86_64"
|
||||
;;
|
||||
"armv7-unknown-linux-musleabihf")
|
||||
ABI_DIR="armeabi-v7a"
|
||||
;;
|
||||
"aarch64-unknown-linux-musl")
|
||||
ABI_DIR="arm64-v8a"
|
||||
;;
|
||||
*)
|
||||
echo -e "${RED}Error: Unknown target $target${NC}"
|
||||
cd ..
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# Create ABI directory
|
||||
mkdir -p "bin/$ABI_DIR"
|
||||
|
||||
# Copy binary
|
||||
if [ -f "$BUILD_OUTPUT_DIR/$BINARY_NAME" ]; then
|
||||
cp "$BUILD_OUTPUT_DIR/$BINARY_NAME" "bin/$ABI_DIR/$BINARY_NAME"
|
||||
# Strip binary to reduce size (for release builds)
|
||||
if [ "$BUILD_TYPE" = "release" ]; then
|
||||
strip "bin/$ABI_DIR/$BINARY_NAME" 2>/dev/null || true
|
||||
fi
|
||||
echo -e "${GREEN}Binary copied to bin/$ABI_DIR/$BINARY_NAME${NC}"
|
||||
else
|
||||
echo -e "${RED}Error: Binary not found at $BUILD_OUTPUT_DIR/$BINARY_NAME${NC}"
|
||||
cd ..
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
|
||||
cd ..
|
||||
echo -e "${GREEN}All Rust binaries built successfully${NC}"
|
||||
return 0
|
||||
}
|
||||
|
||||
# Function to build for a specific type
|
||||
build_for_type() {
|
||||
local BUILD_TYPE=$1
|
||||
|
|
@ -108,7 +296,7 @@ build_for_type() {
|
|||
mkdir -p "$BUILD_DIR"
|
||||
|
||||
# Step 1: Copy template to build directory
|
||||
echo -e "\n${YELLOW}[1/6] Copying template to build directory...${NC}"
|
||||
echo -e "\n${YELLOW}[1/7] Copying template to build directory...${NC}"
|
||||
if [ ! -d "template" ]; then
|
||||
echo -e "${RED}Error: template directory does not exist${NC}"
|
||||
return 1
|
||||
|
|
@ -121,66 +309,48 @@ build_for_type() {
|
|||
fi
|
||||
echo -e "${GREEN}Template copied${NC}"
|
||||
|
||||
# Step 2: Compile source code in src directory
|
||||
echo -e "\n${YELLOW}[2/6] 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"
|
||||
# Step 2: Build Rust binaries
|
||||
echo -e "\n${YELLOW}[2/7] Building Rust binaries...${NC}"
|
||||
build_rust_binaries "$BUILD_TYPE"
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${RED}Error: Compilation failed${NC}"
|
||||
cd ..
|
||||
echo -e "${RED}Error: Rust build failed${NC}"
|
||||
return 1
|
||||
fi
|
||||
cd ..
|
||||
echo -e "${GREEN}Compilation completed${NC}"
|
||||
echo -e "${GREEN}Rust build completed${NC}"
|
||||
|
||||
# Step 3: Modify log level in metamount.sh (in build directory)
|
||||
echo -e "\n${YELLOW}[3/6] Modifying metamount.sh configuration...${NC}"
|
||||
echo -e "\n${YELLOW}[3/7] Modifying metamount.sh configuration...${NC}"
|
||||
if [ ! -f "$BUILD_DIR/metamount.sh" ]; then
|
||||
echo -e "${RED}Error: $BUILD_DIR/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/" "$BUILD_DIR/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 4: Modify module.prop (in build directory)
|
||||
echo -e "\n${YELLOW}[4/6] Modifying module.prop...${NC}"
|
||||
echo -e "\n${YELLOW}[4/7] Modifying module.prop...${NC}"
|
||||
if [ ! -f "$BUILD_DIR/module.prop" ]; then
|
||||
echo -e "${RED}Error: $BUILD_DIR/module.prop does not exist${NC}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Generate versionCode (format: yymmddhhmm)
|
||||
VERSION_CODE=$(date +%y%m%d%H%M)
|
||||
# Generate versionCode
|
||||
VERSION_CODE=$(git rev-list --count HEAD)
|
||||
|
||||
# Modify version and versionCode
|
||||
sed -i "s/^version=.*/version=$VERSION/" "$BUILD_DIR/module.prop"
|
||||
sed -i "s/^versionCode=.*/versionCode=$VERSION_CODE/" "$BUILD_DIR/module.prop"
|
||||
# Modify version, versionCode, and updateJson
|
||||
sed -i "s|^version=.*|version=$VERSION|" "$BUILD_DIR/module.prop"
|
||||
sed -i "s|^versionCode=.*|versionCode=$VERSION_CODE|" "$BUILD_DIR/module.prop"
|
||||
sed -i "s|^updateJson=.*|updateJson=$UPDATE_JSON_URL|" "$BUILD_DIR/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}"
|
||||
echo -e "${GREEN}module.prop configuration completed${NC}"
|
||||
echo -e "${GREEN} version=$VERSION${NC}"
|
||||
echo -e "${GREEN} versionCode=$VERSION_CODE${NC}"
|
||||
echo -e "${GREEN} updateJson=$UPDATE_JSON_URL${NC}"
|
||||
|
||||
# Step 5: Copy bin directory to build directory
|
||||
echo -e "\n${YELLOW}[5/6] Copying bin directory...${NC}"
|
||||
echo -e "\n${YELLOW}[5/7] Copying bin directory...${NC}"
|
||||
if [ ! -d "src/bin" ]; then
|
||||
echo -e "${RED}Error: src/bin directory does not exist${NC}"
|
||||
return 1
|
||||
|
|
@ -194,14 +364,10 @@ build_for_type() {
|
|||
echo -e "${GREEN}bin directory copied${NC}"
|
||||
|
||||
# Step 6: Package as zip
|
||||
echo -e "\n${YELLOW}[6/6] Packaging module...${NC}"
|
||||
echo -e "\n${YELLOW}[6/7] 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
|
||||
OUTPUT_NAME="meta-magic_mount-${VERSION}-${VERSION_CODE}-${GIT_SUFFIX}-${BUILD_TYPE}.zip"
|
||||
|
||||
# Enter build directory and package
|
||||
cd "$BUILD_DIR" || return 1
|
||||
|
|
@ -213,6 +379,11 @@ build_for_type() {
|
|||
fi
|
||||
cd ../..
|
||||
|
||||
# Step 7: Generate update JSON for this build
|
||||
if [ "$BUILD_TYPE" = "release" ]; then
|
||||
generate_main_update_json "$OUTPUT_NAME" "$VERSION_CODE"
|
||||
fi
|
||||
|
||||
# Clean up temporary build directory
|
||||
rm -rf "$BUILD_DIR"
|
||||
|
||||
|
|
@ -224,8 +395,16 @@ build_for_type() {
|
|||
echo -e "\n${GREEN}========================================${NC}"
|
||||
echo -e "${GREEN}Build completed!${NC}"
|
||||
echo -e "${GREEN}Output file: build/$OUTPUT_NAME${NC}"
|
||||
echo -e "${GREEN}Update JSON: build/update_${BUILD_TYPE}.json${NC}"
|
||||
if [ "$BUILD_TYPE" = "release" ]; then
|
||||
echo -e "${GREEN}Main update JSON: build/update.json${NC}"
|
||||
fi
|
||||
echo -e "${GREEN}========================================${NC}"
|
||||
|
||||
# Export for summary
|
||||
LAST_OUTPUT_NAME="$OUTPUT_NAME"
|
||||
LAST_VERSION_CODE="$VERSION_CODE"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
|
|
@ -234,9 +413,14 @@ if [ ! -d "build" ]; then
|
|||
mkdir build
|
||||
fi
|
||||
|
||||
# Generate changelog once for all builds
|
||||
generate_changelog "build/changelog.md"
|
||||
|
||||
# Build for each specified type
|
||||
SUCCESS_COUNT=0
|
||||
FAIL_COUNT=0
|
||||
LAST_OUTPUT_NAME=""
|
||||
LAST_VERSION_CODE=""
|
||||
|
||||
for BUILD_TYPE in "${BUILD_TYPES[@]}"; do
|
||||
build_for_type "$BUILD_TYPE"
|
||||
|
|
@ -251,11 +435,30 @@ done
|
|||
# Print summary
|
||||
echo -e "\n${GREEN}========================================${NC}"
|
||||
echo -e "${GREEN}Build Summary${NC}"
|
||||
echo -e "${GREEN}========================================${NC}"
|
||||
echo -e "${GREEN}Version: $VERSION${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}"
|
||||
echo -e "${GREEN}Generated files:${NC}"
|
||||
echo -e "${GREEN} - Changelog: build/changelog.md${NC}"
|
||||
for BUILD_TYPE in "${BUILD_TYPES[@]}"; do
|
||||
echo -e "${GREEN} - Update JSON (${BUILD_TYPE}): build/update_${BUILD_TYPE}.json${NC}"
|
||||
done
|
||||
# Check if release was built
|
||||
for BUILD_TYPE in "${BUILD_TYPES[@]}"; do
|
||||
if [ "$BUILD_TYPE" = "release" ]; then
|
||||
echo -e "${GREEN} - Main update JSON: build/update.json${NC}"
|
||||
break
|
||||
fi
|
||||
done
|
||||
echo -e "${GREEN} - ZIP packages: build/*.zip${NC}"
|
||||
echo -e "${GREEN}========================================${NC}"
|
||||
echo -e "${YELLOW}Note: Update JSON URL in module.prop: $UPDATE_JSON_URL${NC}"
|
||||
echo -e "${YELLOW}Make sure to upload update.json to the correct location!${NC}"
|
||||
echo -e "${GREEN}========================================${NC}"
|
||||
|
||||
# Exit with error if any build failed
|
||||
|
|
@ -263,4 +466,4 @@ if [ $FAIL_COUNT -gt 0 ]; then
|
|||
exit 1
|
||||
fi
|
||||
|
||||
exit 0
|
||||
exit 0
|
||||
5
src/.gitignore
vendored
5
src/.gitignore
vendored
|
|
@ -1 +1,4 @@
|
|||
/bin
|
||||
/target
|
||||
/out
|
||||
Cargo.lock
|
||||
*.log
|
||||
19
src/Cargo.toml
Normal file
19
src/Cargo.toml
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
[package]
|
||||
name = "magic_mount_rs"
|
||||
version = "0.1.1"
|
||||
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
|
||||
87
src/Makefile
87
src/Makefile
|
|
@ -1,87 +0,0 @@
|
|||
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)
|
||||
|
|
@ -1,702 +0,0 @@
|
|||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/mount.h>
|
||||
#include <sys/xattr.h>
|
||||
#include <dirent.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#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++;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
#ifndef MAGIC_MOUNT_H
|
||||
#define MAGIC_MOUNT_H
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#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 */
|
||||
104
src/main.c
104
src/main.c
|
|
@ -1,104 +0,0 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
489
src/src/magic_mount.rs
Normal file
489
src/src/magic_mount.rs
Normal file
|
|
@ -0,0 +1,489 @@
|
|||
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<Self> {
|
||||
if file_type.is_file() {
|
||||
Some(RegularFile)
|
||||
} else if file_type.is_dir() {
|
||||
Some(Directory)
|
||||
} else if file_type.is_symlink() {
|
||||
Some(Symlink)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Node {
|
||||
name: String,
|
||||
file_type: NodeFileType,
|
||||
children: HashMap<String, Node>,
|
||||
// the module that owned this node
|
||||
module_path: Option<PathBuf>,
|
||||
replace: bool,
|
||||
skip: bool,
|
||||
}
|
||||
|
||||
impl Node {
|
||||
fn collect_module_files<T: AsRef<Path>>(&mut self, module_dir: T) -> Result<bool> {
|
||||
let dir = module_dir.as_ref();
|
||||
let mut has_file = false;
|
||||
for entry in dir.read_dir()?.flatten() {
|
||||
let name = entry.file_name().to_string_lossy().to_string();
|
||||
|
||||
let node = match self.children.entry(name.clone()) {
|
||||
Entry::Occupied(o) => Some(o.into_mut()),
|
||||
Entry::Vacant(v) => Self::new_module(&name, &entry).map(|it| v.insert(it)),
|
||||
};
|
||||
|
||||
if let Some(node) = node {
|
||||
has_file |= if node.file_type == Directory {
|
||||
node.collect_module_files(dir.join(&node.name))? || node.replace
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(has_file)
|
||||
}
|
||||
|
||||
fn new_root<T: ToString>(name: T) -> Self {
|
||||
Node {
|
||||
name: name.to_string(),
|
||||
file_type: Directory,
|
||||
children: Default::default(),
|
||||
module_path: None,
|
||||
replace: false,
|
||||
skip: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn new_module<T: ToString>(name: T, entry: &DirEntry) -> Option<Self> {
|
||||
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<Option<Node>> {
|
||||
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<Src: AsRef<Path>, Dst: AsRef<Path>>(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<P: AsRef<Path>, WP: AsRef<Path>>(
|
||||
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<P: AsRef<Path>, WP: AsRef<Path>>(
|
||||
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<T: AsRef<Path>>(
|
||||
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(())
|
||||
}
|
||||
}
|
||||
82
src/src/main.rs
Normal file
82
src/src/main.rs
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
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<PathBuf>,
|
||||
|
||||
/// 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<String>,
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
159
src/src/utils.rs
Normal file
159
src/src/utils.rs
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
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<P: AsRef<Path>>(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<P: AsRef<Path>>(path: P) -> Result<String> {
|
||||
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<P: AsRef<Path>>(path: P) -> Result<String> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
pub fn ensure_dir_exists<T: AsRef<Path>>(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<PathBuf> {
|
||||
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());
|
||||
}
|
||||
}
|
||||
87
src/utils.c
87
src/utils.c
|
|
@ -1,87 +0,0 @@
|
|||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/statfs.h>
|
||||
#include <dirent.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#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, ".."));
|
||||
}
|
||||
27
src/utils.h
27
src/utils.h
|
|
@ -1,27 +0,0 @@
|
|||
#ifndef UTILS_H
|
||||
#define UTILS_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#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 */
|
||||
|
|
@ -10,15 +10,15 @@ ui_print "- Detected ABI: $ABI"
|
|||
case "$ABI" in
|
||||
arm64-v8a)
|
||||
ui_print "- Selected architecture: ARM64"
|
||||
ARCH_BINARY="mm_arm64"
|
||||
ARCH_BINARY="arm64-v8a/magic_mount_rs"
|
||||
;;
|
||||
armeabi-v7a)
|
||||
ui_print "- Selected architecture: ARM"
|
||||
ARCH_BINARY="mm_armv7"
|
||||
ARCH_BINARY="armeabi-v7a/magic_mount_rs"
|
||||
;;
|
||||
x86_64)
|
||||
ui_print "- Selected architecture: AMD64"
|
||||
ARCH_BINARY="mm_amd64"
|
||||
ARCH_BINARY="x86_64/magic_mount_rs"
|
||||
;;
|
||||
*)
|
||||
abort "! Unsupported architecture: $ABI"
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ fi
|
|||
# Set dual-directory environment variables
|
||||
MODULE_METADATA_DIR="/data/adb/modules"
|
||||
MODULE_METADATA_SOURCE="KSU"
|
||||
MODULE_METADATA_LOGLEVEL=3
|
||||
MODULE_METADATA_LOGFILE="/data/adb/mm.log"
|
||||
|
||||
if [ -f "$MODULE_METADATA_LOGFILE" ]; then
|
||||
|
|
@ -23,7 +22,7 @@ if [ -f "$MODULE_METADATA_LOGFILE" ]; then
|
|||
fi
|
||||
|
||||
# Execute the mount binary
|
||||
"$BINARY" -s "$MODULE_METADATA_SOURCE" -m "$MODULE_METADATA_DIR" -v "$MODULE_METADATA_LOGLEVEL" -l "$MODULE_METADATA_LOGFILE"
|
||||
"$BINARY" -s "$MODULE_METADATA_SOURCE" -m "$MODULE_METADATA_DIR" -v -l "$MODULE_METADATA_LOGFILE"
|
||||
|
||||
EXIT_CODE=$?
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
id=meta-mm
|
||||
metamodule=1
|
||||
name=Magic Mount MetaModule
|
||||
version=v0.1.0
|
||||
versionCode=2511201925
|
||||
version=version
|
||||
versionCode=versionCode
|
||||
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
|
||||
updateJson=updateJson
|
||||
|
|
|
|||
Loading…
Reference in a new issue