meta-overlayfs: Moved to module repo
This commit is contained in:
56
.github/workflows/meta-overlay.yml
vendored
56
.github/workflows/meta-overlay.yml
vendored
@@ -1,56 +0,0 @@
|
|||||||
name: Build meta-overlayfs
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ "main", "dev", "ci", "miuix" ]
|
|
||||||
paths:
|
|
||||||
- '.github/workflows/meta-overlay.yml'
|
|
||||||
- 'userspace/meta-overlayfs/**'
|
|
||||||
pull_request:
|
|
||||||
branches: [ "main", "dev", "miuix" ]
|
|
||||||
paths:
|
|
||||||
- '.github/workflows/meta-overlay.yml'
|
|
||||||
- 'userspace/meta-overlayfs/**'
|
|
||||||
workflow_call:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
shell: bash
|
|
||||||
working-directory: userspace/meta-overlayfs
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v5
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Setup Rust
|
|
||||||
run: |
|
|
||||||
rustup update stable
|
|
||||||
rustup target add aarch64-linux-android
|
|
||||||
rustup target add x86_64-linux-android
|
|
||||||
|
|
||||||
- name: Cache Cargo
|
|
||||||
uses: Swatinem/rust-cache@v2
|
|
||||||
with:
|
|
||||||
workspaces: userspace/meta-overlayfs
|
|
||||||
cache-targets: false
|
|
||||||
|
|
||||||
- name: Install cross
|
|
||||||
run: |
|
|
||||||
RUSTFLAGS="" cargo install cross --git https://github.com/cross-rs/cross --rev 66845c1
|
|
||||||
|
|
||||||
- name: Build meta-overlayfs metamodule
|
|
||||||
run: chmod +x build.sh && ./build.sh
|
|
||||||
|
|
||||||
- name: Upload artifact
|
|
||||||
uses: actions/upload-artifact@v5
|
|
||||||
with:
|
|
||||||
name: meta-overlayfs
|
|
||||||
path: |
|
|
||||||
userspace/meta-overlayfs/target/meta-overlayfs-*.zip
|
|
||||||
userspace/meta-overlayfs/metamodule/meta-overlayfs-changelog.md
|
|
||||||
if-no-files-found: error
|
|
||||||
4
userspace/meta-overlayfs/.gitignore
vendored
4
userspace/meta-overlayfs/.gitignore
vendored
@@ -1,4 +0,0 @@
|
|||||||
/target
|
|
||||||
/out
|
|
||||||
Cargo.lock
|
|
||||||
*.log
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "meta-overlayfs"
|
|
||||||
version = "1.0.0"
|
|
||||||
edition = "2024"
|
|
||||||
authors = ["KernelSU Developers"]
|
|
||||||
description = "An implementation of a metamodule using OverlayFS for KernelSU"
|
|
||||||
license = "GPL-3.0"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
anyhow = "1"
|
|
||||||
log = "0.4"
|
|
||||||
env_logger = { version = "0.11", default-features = false }
|
|
||||||
hole-punch = { git = "https://github.com/tiann/hole-punch" }
|
|
||||||
|
|
||||||
[target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies]
|
|
||||||
rustix = { git = "https://github.com/Kernel-SU/rustix.git", rev = "4a53fbc7cb7a07cabe87125cc21dbc27db316259", features = ["all-apis"] }
|
|
||||||
procfs = "0.17"
|
|
||||||
|
|
||||||
[profile.release]
|
|
||||||
strip = true
|
|
||||||
opt-level = "z" # Minimize binary size
|
|
||||||
lto = true # Link-time optimization
|
|
||||||
codegen-units = 1 # Maximum optimization
|
|
||||||
panic = "abort" # Reduce binary size
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
# meta-overlayfs
|
|
||||||
|
|
||||||
Official overlayfs mount handler for KernelSU metamodules.
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
```bash
|
|
||||||
adb push meta-overlayfs-v1.0.0.zip /sdcard/
|
|
||||||
adb shell su -c 'ksud module install /sdcard/meta-overlayfs-v1.0.0.zip'
|
|
||||||
adb reboot
|
|
||||||
```
|
|
||||||
|
|
||||||
Or install via KernelSU Manager → Modules.
|
|
||||||
|
|
||||||
**Note**: The metamodule is now installed as a regular module to `/data/adb/modules/meta-overlay/`, with a symlink created at `/data/adb/metamodule` pointing to it.
|
|
||||||
|
|
||||||
## How It Works
|
|
||||||
|
|
||||||
Uses dual-directory architecture for ext4 image support:
|
|
||||||
|
|
||||||
- **Metadata**: `/data/adb/modules/` - Contains `module.prop`, `disable`, `skip_mount` markers
|
|
||||||
- **Content**: `/data/adb/metamodule/mnt/` - Contains `system/`, `vendor/` etc. directories from ext4 images
|
|
||||||
|
|
||||||
Scans metadata directory for enabled modules, then mounts their content directories as overlayfs layers.
|
|
||||||
|
|
||||||
### Supported Partitions
|
|
||||||
|
|
||||||
system, vendor, product, system_ext, odm, oem
|
|
||||||
|
|
||||||
### Read-Write Layer
|
|
||||||
|
|
||||||
Optional upperdir/workdir support via `/data/adb/modules/.rw/`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mkdir -p /data/adb/modules/.rw/system/{upperdir,workdir}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Environment Variables
|
|
||||||
|
|
||||||
- `MODULE_METADATA_DIR` - Metadata location (default: `/data/adb/modules/`)
|
|
||||||
- `MODULE_CONTENT_DIR` - Content location (default: `/data/adb/metamodule/mnt/`)
|
|
||||||
- `RUST_LOG` - Log level (debug, info, warn, error)
|
|
||||||
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
Automatically selects aarch64 or x86_64 binary during installation (~500KB).
|
|
||||||
|
|
||||||
## Building
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./build.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
Output: `target/meta-overlayfs-v1.0.0.zip`
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
GPL-3.0
|
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
set -e
|
|
||||||
|
|
||||||
# Configuration
|
|
||||||
VERSION=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)".*/\1/')
|
|
||||||
OUTPUT_DIR="target"
|
|
||||||
METAMODULE_DIR="metamodule"
|
|
||||||
MODULE_PROP_FILE="$METAMODULE_DIR/module.prop"
|
|
||||||
UPDATE_JSON_FILE="$METAMODULE_DIR/update.json"
|
|
||||||
MODULE_OUTPUT_DIR="$OUTPUT_DIR/module"
|
|
||||||
|
|
||||||
MODULE_VERSION=$(grep -m1 '^version=' "$MODULE_PROP_FILE" | cut -d'=' -f2- | tr -d '\r')
|
|
||||||
MODULE_VERSION_CODE=$(grep -m1 '^versionCode=' "$MODULE_PROP_FILE" | cut -d'=' -f2- | tr -d '\r')
|
|
||||||
|
|
||||||
if [ -z "$MODULE_VERSION" ] || [ -z "$MODULE_VERSION_CODE" ]; then
|
|
||||||
echo "Error: Failed to read module version information from $MODULE_PROP_FILE"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "=========================================="
|
|
||||||
echo "Building meta-overlayfs v${VERSION}"
|
|
||||||
echo "=========================================="
|
|
||||||
|
|
||||||
# Detect build tool
|
|
||||||
if command -v cross >/dev/null 2>&1; then
|
|
||||||
BUILD_TOOL="cross"
|
|
||||||
echo "Using cross for compilation"
|
|
||||||
else
|
|
||||||
BUILD_TOOL="cargo-ndk"
|
|
||||||
echo "Using cargo ndk for compilation"
|
|
||||||
if ! command -v cargo-ndk >/dev/null 2>&1; then
|
|
||||||
echo "Error: Neither cross nor cargo-ndk found!"
|
|
||||||
echo "Please install one of them:"
|
|
||||||
echo " - cross: cargo install cross"
|
|
||||||
echo " - cargo-ndk: cargo install cargo-ndk"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Clean output directory
|
|
||||||
echo "Cleaning output directory..."
|
|
||||||
rm -rf "$OUTPUT_DIR"
|
|
||||||
mkdir -p "$MODULE_OUTPUT_DIR"
|
|
||||||
|
|
||||||
# Build for multiple architectures
|
|
||||||
echo ""
|
|
||||||
echo "Building for aarch64-linux-android..."
|
|
||||||
if [ "$BUILD_TOOL" = "cross" ]; then
|
|
||||||
cross build --release --target aarch64-linux-android
|
|
||||||
else
|
|
||||||
cargo ndk build -t arm64-v8a --release
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "Building for x86_64-linux-android..."
|
|
||||||
if [ "$BUILD_TOOL" = "cross" ]; then
|
|
||||||
cross build --release --target x86_64-linux-android
|
|
||||||
else
|
|
||||||
cargo ndk build -t x86_64 --release
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Copy binaries
|
|
||||||
echo ""
|
|
||||||
echo "Copying binaries..."
|
|
||||||
cp target/aarch64-linux-android/release/meta-overlayfs \
|
|
||||||
"$MODULE_OUTPUT_DIR/meta-overlayfs-aarch64"
|
|
||||||
cp target/x86_64-linux-android/release/meta-overlayfs \
|
|
||||||
"$MODULE_OUTPUT_DIR/meta-overlayfs-x86_64"
|
|
||||||
|
|
||||||
# Copy metamodule files
|
|
||||||
echo "Copying metamodule files..."
|
|
||||||
cp "$METAMODULE_DIR"/module.prop "$MODULE_OUTPUT_DIR/"
|
|
||||||
cp "$METAMODULE_DIR"/*.sh "$MODULE_OUTPUT_DIR/"
|
|
||||||
|
|
||||||
# Set permissions
|
|
||||||
echo "Setting permissions..."
|
|
||||||
chmod 755 "$MODULE_OUTPUT_DIR"/*.sh
|
|
||||||
chmod 755 "$MODULE_OUTPUT_DIR"/meta-overlayfs-*
|
|
||||||
|
|
||||||
# Display binary sizes
|
|
||||||
echo ""
|
|
||||||
echo "Binary sizes:"
|
|
||||||
echo " aarch64: $(du -h "$MODULE_OUTPUT_DIR"/meta-overlayfs-aarch64 | awk '{print $1}')"
|
|
||||||
echo " x86_64: $(du -h "$MODULE_OUTPUT_DIR"/meta-overlayfs-x86_64 | awk '{print $1}')"
|
|
||||||
|
|
||||||
# Package
|
|
||||||
echo ""
|
|
||||||
echo "Packaging..."
|
|
||||||
cd "$MODULE_OUTPUT_DIR"
|
|
||||||
ZIP_NAME="meta-overlayfs-v${MODULE_VERSION}.zip"
|
|
||||||
zip -r "../$ZIP_NAME" .
|
|
||||||
cd ../..
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "Generating update.json..."
|
|
||||||
if ! LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null); then
|
|
||||||
echo "Error: Unable to determine the latest git tag."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
ZIP_URL="https://github.com/tiann/KernelSU/releases/download/${LATEST_TAG}/${ZIP_NAME}"
|
|
||||||
CHANGELOG_URL="https://github.com/tiann/KernelSU/releases/download/${LATEST_TAG}/meta-overlayfs-changelog.md"
|
|
||||||
|
|
||||||
cat > "$UPDATE_JSON_FILE" <<EOF
|
|
||||||
{
|
|
||||||
"version": "${MODULE_VERSION}",
|
|
||||||
"versionCode": ${MODULE_VERSION_CODE},
|
|
||||||
"zipUrl": "${ZIP_URL}",
|
|
||||||
"changelog": "${CHANGELOG_URL}"
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
echo "Generated $UPDATE_JSON_FILE"
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "=========================================="
|
|
||||||
echo "Build completed successfully!"
|
|
||||||
echo "Output: $OUTPUT_DIR/$ZIP_NAME"
|
|
||||||
echo "=========================================="
|
|
||||||
echo ""
|
|
||||||
echo "To install:"
|
|
||||||
echo " adb push $OUTPUT_DIR/$ZIP_NAME /sdcard/"
|
|
||||||
echo " adb shell su -c 'ksud module install /sdcard/$ZIP_NAME'"
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
#!/system/bin/sh
|
|
||||||
|
|
||||||
ui_print "- Detecting device architecture..."
|
|
||||||
|
|
||||||
# Detect architecture using ro.product.cpu.abi
|
|
||||||
ABI=$(grep_get_prop ro.product.cpu.abi)
|
|
||||||
ui_print "- Detected ABI: $ABI"
|
|
||||||
|
|
||||||
# Select the correct binary based on architecture
|
|
||||||
case "$ABI" in
|
|
||||||
arm64-v8a)
|
|
||||||
ARCH_BINARY="meta-overlayfs-aarch64"
|
|
||||||
REMOVE_BINARY="meta-overlayfs-x86_64"
|
|
||||||
ui_print "- Selected architecture: ARM64"
|
|
||||||
;;
|
|
||||||
x86_64)
|
|
||||||
ARCH_BINARY="meta-overlayfs-x86_64"
|
|
||||||
REMOVE_BINARY="meta-overlayfs-aarch64"
|
|
||||||
ui_print "- Selected architecture: x86_64"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
abort "! Unsupported architecture: $ABI"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
# Verify the selected binary exists
|
|
||||||
if [ ! -f "$MODPATH/$ARCH_BINARY" ]; then
|
|
||||||
abort "! Binary not found: $ARCH_BINARY"
|
|
||||||
fi
|
|
||||||
|
|
||||||
ui_print "- Installing $ARCH_BINARY as meta-overlayfs"
|
|
||||||
|
|
||||||
# Rename the selected binary to the generic name
|
|
||||||
mv "$MODPATH/$ARCH_BINARY" "$MODPATH/meta-overlayfs" || abort "! Failed to rename binary"
|
|
||||||
|
|
||||||
# Remove the unused binary
|
|
||||||
rm -f "$MODPATH/$REMOVE_BINARY"
|
|
||||||
|
|
||||||
# Ensure the binary is executable
|
|
||||||
chmod 755 "$MODPATH/meta-overlayfs" || abort "! Failed to set permissions"
|
|
||||||
|
|
||||||
ui_print "- Architecture-specific binary installed successfully"
|
|
||||||
|
|
||||||
# Create ext4 image for module content storage
|
|
||||||
IMG_FILE="$MODPATH/modules.img"
|
|
||||||
IMG_SIZE_MB=2048
|
|
||||||
EXISTING_IMG="/data/adb/modules/$MODID/modules.img"
|
|
||||||
|
|
||||||
if [ -f "$EXISTING_IMG" ]; then
|
|
||||||
ui_print "- Reusing modules image from previous install"
|
|
||||||
"$MODPATH/meta-overlayfs" xcp "$EXISTING_IMG" "$IMG_FILE" || \
|
|
||||||
abort "! Failed to copy existing modules image"
|
|
||||||
else
|
|
||||||
ui_print "- Creating 2GB ext4 image for module storage"
|
|
||||||
|
|
||||||
# Create sparse file (2GB logical size, 0 bytes actual)
|
|
||||||
truncate -s ${IMG_SIZE_MB}M "$IMG_FILE" || \
|
|
||||||
abort "! Failed to create image file"
|
|
||||||
|
|
||||||
# Format as ext4 with small journal (8MB) for safety with minimal overhead
|
|
||||||
/system/bin/mke2fs -t ext4 -J size=8 -F "$IMG_FILE" >/dev/null 2>&1 || \
|
|
||||||
abort "! Failed to format ext4 image"
|
|
||||||
|
|
||||||
ui_print "- Image created successfully (sparse file)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
ui_print "- Installation complete"
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
|
|
||||||
# v1.1.0 Changelog
|
|
||||||
|
|
||||||
Fix bootloop
|
|
||||||
@@ -1,100 +0,0 @@
|
|||||||
#!/system/bin/sh
|
|
||||||
############################################
|
|
||||||
# meta-overlayfs metainstall.sh
|
|
||||||
# Module installation hook for ext4 image support
|
|
||||||
############################################
|
|
||||||
|
|
||||||
# Constants
|
|
||||||
IMG_FILE="/data/adb/metamodule/modules.img"
|
|
||||||
MNT_DIR="/data/adb/metamodule/mnt"
|
|
||||||
|
|
||||||
# Ensure ext4 image is mounted
|
|
||||||
ensure_image_mounted() {
|
|
||||||
if ! mountpoint -q "$MNT_DIR" 2>/dev/null; then
|
|
||||||
ui_print "- Mounting modules image"
|
|
||||||
mkdir -p "$MNT_DIR"
|
|
||||||
mount -t ext4 -o loop,rw,noatime "$IMG_FILE" "$MNT_DIR" || {
|
|
||||||
abort "! Failed to mount modules image"
|
|
||||||
}
|
|
||||||
ui_print "- Image mounted successfully"
|
|
||||||
else
|
|
||||||
ui_print "- Image already mounted"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Determine whether this module should be moved into the ext4 image.
|
|
||||||
# We only relocate payloads that expose system/ overlays and do not opt out via skip_mount.
|
|
||||||
module_requires_overlay_move() {
|
|
||||||
if [ -f "$MODPATH/skip_mount" ]; then
|
|
||||||
ui_print "- skip_mount flag detected; keeping files under /data/adb/modules"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ ! -d "$MODPATH/system" ]; then
|
|
||||||
ui_print "- No system/ directory detected; keeping files under /data/adb/modules"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
# Copy SELinux contexts from src tree to destination by mirroring each entry.
|
|
||||||
copy_selinux_contexts() {
|
|
||||||
command -v chcon >/dev/null 2>&1 || return 0
|
|
||||||
|
|
||||||
SRC="$1"
|
|
||||||
DST="$2"
|
|
||||||
|
|
||||||
if [ -z "$SRC" ] || [ -z "$DST" ] || [ ! -e "$SRC" ] || [ ! -e "$DST" ]; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
chcon --reference="$SRC" "$DST" 2>/dev/null || true
|
|
||||||
|
|
||||||
find "$SRC" -print | while IFS= read -r PATH_SRC; do
|
|
||||||
if [ "$PATH_SRC" = "$SRC" ]; then
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
REL_PATH="${PATH_SRC#"${SRC}/"}"
|
|
||||||
PATH_DST="$DST/$REL_PATH"
|
|
||||||
if [ -e "$PATH_DST" ] || [ -L "$PATH_DST" ]; then
|
|
||||||
chcon --reference="$PATH_SRC" "$PATH_DST" 2>/dev/null || true
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
# Post-installation: move partition directories to ext4 image
|
|
||||||
post_install_to_image() {
|
|
||||||
ui_print "- Copying module content to image"
|
|
||||||
|
|
||||||
set_perm "$MNT_DIR" 0 0 0755 0644
|
|
||||||
|
|
||||||
MOD_IMG_DIR="$MNT_DIR/$MODID"
|
|
||||||
mkdir -p "$MOD_IMG_DIR"
|
|
||||||
set_perm "$MOD_IMG_DIR" 0 0 0755 0644
|
|
||||||
|
|
||||||
# Move all partition directories
|
|
||||||
for partition in system vendor product system_ext odm oem; do
|
|
||||||
if [ -d "$MODPATH/$partition" ]; then
|
|
||||||
ui_print "- Copying $partition/"
|
|
||||||
cp -af "$MODPATH/$partition" "$MOD_IMG_DIR/" || {
|
|
||||||
ui_print "! Warning: Failed to move $partition"
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
copy_selinux_contexts "$MODPATH/$partition" "$MOD_IMG_DIR/$partition"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
ui_print "- Using meta-overlayfs metainstall"
|
|
||||||
|
|
||||||
install_module
|
|
||||||
|
|
||||||
if module_requires_overlay_move; then
|
|
||||||
ensure_image_mounted
|
|
||||||
post_install_to_image
|
|
||||||
else
|
|
||||||
ui_print "- Skipping move to modules image"
|
|
||||||
fi
|
|
||||||
|
|
||||||
ui_print "- Installation complete"
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
#!/system/bin/sh
|
|
||||||
# meta-overlayfs Module Mount Handler
|
|
||||||
# This script is the entry point for dual-directory module mounting
|
|
||||||
|
|
||||||
MODDIR="${0%/*}"
|
|
||||||
IMG_FILE="$MODDIR/modules.img"
|
|
||||||
MNT_DIR="$MODDIR/mnt"
|
|
||||||
|
|
||||||
# Log function
|
|
||||||
log() {
|
|
||||||
echo "[meta-overlayfs] $1"
|
|
||||||
}
|
|
||||||
|
|
||||||
log "Starting module mount process"
|
|
||||||
|
|
||||||
# Ensure ext4 image is mounted
|
|
||||||
if ! mountpoint -q "$MNT_DIR" 2>/dev/null; then
|
|
||||||
log "Image not mounted, mounting now..."
|
|
||||||
|
|
||||||
# Check if image file exists
|
|
||||||
if [ ! -f "$IMG_FILE" ]; then
|
|
||||||
log "ERROR: Image file not found at $IMG_FILE"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Create mount point
|
|
||||||
mkdir -p "$MNT_DIR"
|
|
||||||
|
|
||||||
# Mount the ext4 image
|
|
||||||
mount -t ext4 -o loop,rw,noatime "$IMG_FILE" "$MNT_DIR" || {
|
|
||||||
log "ERROR: Failed to mount image"
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
log "Image mounted successfully at $MNT_DIR"
|
|
||||||
else
|
|
||||||
log "Image already mounted at $MNT_DIR"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Binary path (architecture-specific binary selected during installation)
|
|
||||||
BINARY="$MODDIR/meta-overlayfs"
|
|
||||||
|
|
||||||
if [ ! -f "$BINARY" ]; then
|
|
||||||
log "ERROR: Binary not found: $BINARY"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Set dual-directory environment variables
|
|
||||||
export MODULE_METADATA_DIR="/data/adb/modules"
|
|
||||||
export MODULE_CONTENT_DIR="$MNT_DIR"
|
|
||||||
|
|
||||||
log "Metadata directory: $MODULE_METADATA_DIR"
|
|
||||||
log "Content directory: $MODULE_CONTENT_DIR"
|
|
||||||
log "Executing $BINARY"
|
|
||||||
|
|
||||||
# Execute the mount binary
|
|
||||||
"$BINARY"
|
|
||||||
EXIT_CODE=$?
|
|
||||||
|
|
||||||
if [ $EXIT_CODE -ne 0 ]; then
|
|
||||||
log "Mount failed with exit code $EXIT_CODE"
|
|
||||||
exit $EXIT_CODE
|
|
||||||
fi
|
|
||||||
|
|
||||||
log "Mount completed successfully"
|
|
||||||
exit 0
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
#!/system/bin/sh
|
|
||||||
############################################
|
|
||||||
# mm-overlayfs metauninstall.sh
|
|
||||||
# Module uninstallation hook for ext4 image cleanup
|
|
||||||
############################################
|
|
||||||
|
|
||||||
# Constants
|
|
||||||
MNT_DIR="/data/adb/metamodule/mnt"
|
|
||||||
|
|
||||||
if [ -z "$MODULE_ID" ]; then
|
|
||||||
echo "! Error: MODULE_ID not provided"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "- Cleaning up module content from image: $MODULE_ID"
|
|
||||||
|
|
||||||
# Check if image is mounted
|
|
||||||
if ! mountpoint -q "$MNT_DIR" 2>/dev/null; then
|
|
||||||
echo "! Warning: Image not mounted, skipping cleanup"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Remove module content from image
|
|
||||||
MOD_IMG_DIR="$MNT_DIR/$MODULE_ID"
|
|
||||||
if [ -d "$MOD_IMG_DIR" ]; then
|
|
||||||
echo " Removing $MOD_IMG_DIR"
|
|
||||||
rm -rf "$MOD_IMG_DIR" || {
|
|
||||||
echo "! Warning: Failed to remove module content from image"
|
|
||||||
}
|
|
||||||
echo "- Module content removed from image"
|
|
||||||
else
|
|
||||||
echo "- No module content found in image, skipping"
|
|
||||||
fi
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
id=meta-overlayfs
|
|
||||||
metamodule=1
|
|
||||||
name=OverlayFS MetaModule
|
|
||||||
version=1.1.0
|
|
||||||
versionCode=1100
|
|
||||||
author=KernelSU Developers
|
|
||||||
description=An implementation of a metamodule using OverlayFS for KernelSU
|
|
||||||
updateJson=https://raw.githubusercontent.com/tiann/KernelSU/refs/heads/main/userspace/meta-overlayfs/metamodule/update.json
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
#!/system/bin/sh
|
|
||||||
|
|
||||||
ksud kernel nuke-ext4-sysfs /data/adb/modules/meta-overlayfs/mnt
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
#!/system/bin/sh
|
|
||||||
############################################
|
|
||||||
# mm-overlayfs uninstall.sh
|
|
||||||
# Cleanup script for metamodule removal
|
|
||||||
############################################
|
|
||||||
|
|
||||||
MODDIR="${0%/*}"
|
|
||||||
MNT_DIR="$MODDIR/mnt"
|
|
||||||
|
|
||||||
echo "- Uninstalling metamodule..."
|
|
||||||
|
|
||||||
# Unmount the ext4 image if mounted
|
|
||||||
if mountpoint -q "$MNT_DIR" 2>/dev/null; then
|
|
||||||
echo "- Unmounting image..."
|
|
||||||
umount "$MNT_DIR" 2>/dev/null || {
|
|
||||||
echo "- Warning: Failed to unmount cleanly"
|
|
||||||
umount -l "$MNT_DIR" 2>/dev/null
|
|
||||||
}
|
|
||||||
echo "- Image unmounted"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "- Uninstall complete"
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"version": "1.1.0",
|
|
||||||
"versionCode": 1100,
|
|
||||||
"zipUrl": "https://github.com/tiann/KernelSU/releases/download/v2.1.2/meta-overlayfs-v1.1.0.zip",
|
|
||||||
"changelog": "https://github.com/tiann/KernelSU/releases/download/v2.1.2/meta-overlayfs-changelog.md"
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
// Constants for KernelSU module mounting
|
|
||||||
|
|
||||||
// Dual-directory support for ext4 image
|
|
||||||
pub const MODULE_METADATA_DIR: &str = "/data/adb/modules/";
|
|
||||||
pub const MODULE_CONTENT_DIR: &str = "/data/adb/metamodule/mnt/";
|
|
||||||
|
|
||||||
// Legacy constant (for backwards compatibility)
|
|
||||||
pub const _MODULE_DIR: &str = "/data/adb/modules/";
|
|
||||||
|
|
||||||
// Status marker files
|
|
||||||
pub const DISABLE_FILE_NAME: &str = "disable";
|
|
||||||
pub const _REMOVE_FILE_NAME: &str = "remove";
|
|
||||||
pub const SKIP_MOUNT_FILE_NAME: &str = "skip_mount";
|
|
||||||
|
|
||||||
// System directories
|
|
||||||
pub const SYSTEM_RW_DIR: &str = "/data/adb/modules/.rw/";
|
|
||||||
pub const KSU_OVERLAY_SOURCE: &str = "KSU";
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
use anyhow::Result;
|
|
||||||
use log::info;
|
|
||||||
|
|
||||||
mod defs;
|
|
||||||
mod mount;
|
|
||||||
mod xcp;
|
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
|
||||||
let args: Vec<String> = std::env::args().collect();
|
|
||||||
if matches!(args.get(1), Some(cmd) if cmd == "xcp") {
|
|
||||||
return xcp::run(&args[2..]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize logger
|
|
||||||
env_logger::builder()
|
|
||||||
.filter_level(log::LevelFilter::Info)
|
|
||||||
.init();
|
|
||||||
|
|
||||||
info!("meta-overlayfs v{}", env!("CARGO_PKG_VERSION"));
|
|
||||||
|
|
||||||
// Dual-directory support: metadata + content
|
|
||||||
let metadata_dir = std::env::var("MODULE_METADATA_DIR")
|
|
||||||
.unwrap_or_else(|_| defs::MODULE_METADATA_DIR.to_string());
|
|
||||||
let content_dir = std::env::var("MODULE_CONTENT_DIR")
|
|
||||||
.unwrap_or_else(|_| defs::MODULE_CONTENT_DIR.to_string());
|
|
||||||
|
|
||||||
info!("Metadata directory: {}", metadata_dir);
|
|
||||||
info!("Content directory: {}", content_dir);
|
|
||||||
|
|
||||||
// Execute dual-directory mounting
|
|
||||||
mount::mount_modules_systemlessly(&metadata_dir, &content_dir)?;
|
|
||||||
|
|
||||||
info!("Mount completed successfully");
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
@@ -1,376 +0,0 @@
|
|||||||
// Overlayfs mounting implementation
|
|
||||||
// Migrated from ksud/src/mount.rs and ksud/src/init_event.rs
|
|
||||||
|
|
||||||
use anyhow::{Context, Result, bail};
|
|
||||||
use log::{info, warn};
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
|
|
||||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
|
||||||
use procfs::process::Process;
|
|
||||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
|
||||||
use rustix::{fd::AsFd, fs::CWD, mount::*};
|
|
||||||
|
|
||||||
use crate::defs::{DISABLE_FILE_NAME, KSU_OVERLAY_SOURCE, SKIP_MOUNT_FILE_NAME, SYSTEM_RW_DIR};
|
|
||||||
|
|
||||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
|
||||||
pub fn mount_overlayfs(
|
|
||||||
lower_dirs: &[String],
|
|
||||||
lowest: &str,
|
|
||||||
upperdir: Option<PathBuf>,
|
|
||||||
workdir: Option<PathBuf>,
|
|
||||||
dest: impl AsRef<Path>,
|
|
||||||
) -> Result<()> {
|
|
||||||
let lowerdir_config = lower_dirs
|
|
||||||
.iter()
|
|
||||||
.map(|s| s.as_ref())
|
|
||||||
.chain(std::iter::once(lowest))
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join(":");
|
|
||||||
info!(
|
|
||||||
"mount overlayfs on {:?}, lowerdir={}, upperdir={:?}, workdir={:?}",
|
|
||||||
dest.as_ref(),
|
|
||||||
lowerdir_config,
|
|
||||||
upperdir,
|
|
||||||
workdir
|
|
||||||
);
|
|
||||||
|
|
||||||
let upperdir = upperdir
|
|
||||||
.filter(|up| up.exists())
|
|
||||||
.map(|e| e.display().to_string());
|
|
||||||
let workdir = workdir
|
|
||||||
.filter(|wd| wd.exists())
|
|
||||||
.map(|e| e.display().to_string());
|
|
||||||
|
|
||||||
let result = (|| {
|
|
||||||
let fs = fsopen("overlay", FsOpenFlags::FSOPEN_CLOEXEC)?;
|
|
||||||
let fs = fs.as_fd();
|
|
||||||
fsconfig_set_string(fs, "lowerdir", &lowerdir_config)?;
|
|
||||||
if let (Some(upperdir), Some(workdir)) = (&upperdir, &workdir) {
|
|
||||||
fsconfig_set_string(fs, "upperdir", upperdir)?;
|
|
||||||
fsconfig_set_string(fs, "workdir", workdir)?;
|
|
||||||
}
|
|
||||||
fsconfig_set_string(fs, "source", KSU_OVERLAY_SOURCE)?;
|
|
||||||
fsconfig_create(fs)?;
|
|
||||||
let mount = fsmount(fs, FsMountFlags::FSMOUNT_CLOEXEC, MountAttrFlags::empty())?;
|
|
||||||
move_mount(
|
|
||||||
mount.as_fd(),
|
|
||||||
"",
|
|
||||||
CWD,
|
|
||||||
dest.as_ref(),
|
|
||||||
MoveMountFlags::MOVE_MOUNT_F_EMPTY_PATH,
|
|
||||||
)
|
|
||||||
})();
|
|
||||||
|
|
||||||
if let Err(e) = result {
|
|
||||||
warn!("fsopen mount failed: {e:#}, fallback to mount");
|
|
||||||
let mut data = format!("lowerdir={lowerdir_config}");
|
|
||||||
if let (Some(upperdir), Some(workdir)) = (upperdir, workdir) {
|
|
||||||
data = format!("{data},upperdir={upperdir},workdir={workdir}");
|
|
||||||
}
|
|
||||||
mount(
|
|
||||||
KSU_OVERLAY_SOURCE,
|
|
||||||
dest.as_ref(),
|
|
||||||
"overlay",
|
|
||||||
MountFlags::empty(),
|
|
||||||
data,
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
|
||||||
pub fn bind_mount(from: impl AsRef<Path>, to: impl AsRef<Path>) -> Result<()> {
|
|
||||||
info!(
|
|
||||||
"bind mount {} -> {}",
|
|
||||||
from.as_ref().display(),
|
|
||||||
to.as_ref().display()
|
|
||||||
);
|
|
||||||
let tree = open_tree(
|
|
||||||
CWD,
|
|
||||||
from.as_ref(),
|
|
||||||
OpenTreeFlags::OPEN_TREE_CLOEXEC
|
|
||||||
| OpenTreeFlags::OPEN_TREE_CLONE
|
|
||||||
| OpenTreeFlags::AT_RECURSIVE,
|
|
||||||
)?;
|
|
||||||
move_mount(
|
|
||||||
tree.as_fd(),
|
|
||||||
"",
|
|
||||||
CWD,
|
|
||||||
to.as_ref(),
|
|
||||||
MoveMountFlags::MOVE_MOUNT_F_EMPTY_PATH,
|
|
||||||
)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
|
||||||
fn mount_overlay_child(
|
|
||||||
mount_point: &str,
|
|
||||||
relative: &String,
|
|
||||||
module_roots: &Vec<String>,
|
|
||||||
stock_root: &String,
|
|
||||||
) -> Result<()> {
|
|
||||||
if !module_roots
|
|
||||||
.iter()
|
|
||||||
.any(|lower| Path::new(&format!("{lower}{relative}")).exists())
|
|
||||||
{
|
|
||||||
return bind_mount(stock_root, mount_point);
|
|
||||||
}
|
|
||||||
if !Path::new(&stock_root).is_dir() {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
let mut lower_dirs: Vec<String> = vec![];
|
|
||||||
for lower in module_roots {
|
|
||||||
let lower_dir = format!("{lower}{relative}");
|
|
||||||
let path = Path::new(&lower_dir);
|
|
||||||
if path.is_dir() {
|
|
||||||
lower_dirs.push(lower_dir);
|
|
||||||
} else if path.exists() {
|
|
||||||
// stock root has been blocked by this file
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if lower_dirs.is_empty() {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
// merge modules and stock
|
|
||||||
if let Err(e) = mount_overlayfs(&lower_dirs, stock_root, None, None, mount_point) {
|
|
||||||
warn!("failed: {e:#}, fallback to bind mount");
|
|
||||||
bind_mount(stock_root, mount_point)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
|
||||||
pub fn mount_overlay(
|
|
||||||
root: &String,
|
|
||||||
module_roots: &Vec<String>,
|
|
||||||
workdir: Option<PathBuf>,
|
|
||||||
upperdir: Option<PathBuf>,
|
|
||||||
) -> Result<()> {
|
|
||||||
info!("mount overlay for {root}");
|
|
||||||
std::env::set_current_dir(root).with_context(|| format!("failed to chdir to {root}"))?;
|
|
||||||
let stock_root = ".";
|
|
||||||
|
|
||||||
// collect child mounts before mounting the root
|
|
||||||
let mounts = Process::myself()?
|
|
||||||
.mountinfo()
|
|
||||||
.with_context(|| "get mountinfo")?;
|
|
||||||
let mut mount_seq = mounts
|
|
||||||
.0
|
|
||||||
.iter()
|
|
||||||
.filter(|m| {
|
|
||||||
m.mount_point.starts_with(root) && !Path::new(&root).starts_with(&m.mount_point)
|
|
||||||
})
|
|
||||||
.map(|m| m.mount_point.to_str())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
mount_seq.sort();
|
|
||||||
mount_seq.dedup();
|
|
||||||
|
|
||||||
mount_overlayfs(module_roots, root, upperdir, workdir, root)
|
|
||||||
.with_context(|| "mount overlayfs for root failed")?;
|
|
||||||
for mount_point in mount_seq.iter() {
|
|
||||||
let Some(mount_point) = mount_point else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
let relative = mount_point.replacen(root, "", 1);
|
|
||||||
let stock_root: String = format!("{stock_root}{relative}");
|
|
||||||
if !Path::new(&stock_root).exists() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if let Err(e) = mount_overlay_child(mount_point, &relative, module_roots, &stock_root) {
|
|
||||||
warn!("failed to mount overlay for child {mount_point}: {e:#}, revert");
|
|
||||||
umount_dir(root).with_context(|| format!("failed to revert {root}"))?;
|
|
||||||
bail!(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
|
||||||
pub fn umount_dir(src: impl AsRef<Path>) -> Result<()> {
|
|
||||||
unmount(src.as_ref(), UnmountFlags::empty())
|
|
||||||
.with_context(|| format!("Failed to umount {}", src.as_ref().display()))?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(any(target_os = "linux", target_os = "android")))]
|
|
||||||
pub fn mount_overlay(
|
|
||||||
_root: &String,
|
|
||||||
_module_roots: &Vec<String>,
|
|
||||||
_workdir: Option<PathBuf>,
|
|
||||||
_upperdir: Option<PathBuf>,
|
|
||||||
) -> Result<()> {
|
|
||||||
unimplemented!("mount_overlay is only supported on Linux/Android")
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(any(target_os = "linux", target_os = "android")))]
|
|
||||||
pub fn mount_overlayfs(
|
|
||||||
_lower_dirs: &[String],
|
|
||||||
_lowest: &str,
|
|
||||||
_upperdir: Option<PathBuf>,
|
|
||||||
_workdir: Option<PathBuf>,
|
|
||||||
_dest: impl AsRef<Path>,
|
|
||||||
) -> Result<()> {
|
|
||||||
unimplemented!("mount_overlayfs is only supported on Linux/Android")
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(any(target_os = "linux", target_os = "android")))]
|
|
||||||
pub fn bind_mount(_from: impl AsRef<Path>, _to: impl AsRef<Path>) -> Result<()> {
|
|
||||||
unimplemented!("bind_mount is only supported on Linux/Android")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========== Mount coordination logic (from init_event.rs) ==========
|
|
||||||
|
|
||||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
|
||||||
fn mount_partition(partition_name: &str, lowerdir: &Vec<String>) -> Result<()> {
|
|
||||||
if lowerdir.is_empty() {
|
|
||||||
warn!("partition: {partition_name} lowerdir is empty");
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
let partition = format!("/{partition_name}");
|
|
||||||
|
|
||||||
// if /partition is a symlink and linked to /system/partition, then we don't need to overlay it separately
|
|
||||||
if Path::new(&partition).read_link().is_ok() {
|
|
||||||
warn!("partition: {partition} is a symlink");
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut workdir = None;
|
|
||||||
let mut upperdir = None;
|
|
||||||
let system_rw_dir = Path::new(SYSTEM_RW_DIR);
|
|
||||||
if system_rw_dir.exists() {
|
|
||||||
workdir = Some(system_rw_dir.join(partition_name).join("workdir"));
|
|
||||||
upperdir = Some(system_rw_dir.join(partition_name).join("upperdir"));
|
|
||||||
}
|
|
||||||
|
|
||||||
mount_overlay(&partition, lowerdir, workdir, upperdir)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Collect enabled module IDs from metadata directory
|
|
||||||
///
|
|
||||||
/// Reads module list and status from metadata directory, returns enabled module IDs
|
|
||||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
|
||||||
fn collect_enabled_modules(metadata_dir: &str) -> Result<Vec<String>> {
|
|
||||||
let dir = std::fs::read_dir(metadata_dir)
|
|
||||||
.with_context(|| format!("Failed to read metadata directory: {}", metadata_dir))?;
|
|
||||||
|
|
||||||
let mut enabled = Vec::new();
|
|
||||||
|
|
||||||
for entry in dir.flatten() {
|
|
||||||
let path = entry.path();
|
|
||||||
if !path.is_dir() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let module_id = match entry.file_name().to_str() {
|
|
||||||
Some(id) => id.to_string(),
|
|
||||||
None => continue,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Check status markers
|
|
||||||
if path.join(DISABLE_FILE_NAME).exists() {
|
|
||||||
info!("Module {} is disabled, skipping", module_id);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if path.join(SKIP_MOUNT_FILE_NAME).exists() {
|
|
||||||
info!("Module {} has skip_mount, skipping", module_id);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Optional: verify module.prop exists
|
|
||||||
if !path.join("module.prop").exists() {
|
|
||||||
warn!("Module {} has no module.prop, skipping", module_id);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
info!("Module {} enabled", module_id);
|
|
||||||
enabled.push(module_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(enabled)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Dual-directory version of mount_modules_systemlessly
|
|
||||||
///
|
|
||||||
/// Parameters:
|
|
||||||
/// - metadata_dir: Metadata directory, stores module.prop, disable, skip_mount, etc.
|
|
||||||
/// - content_dir: Content directory, stores system/, vendor/ and other partition content (ext4 image mount point)
|
|
||||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
|
||||||
pub fn mount_modules_systemlessly(metadata_dir: &str, content_dir: &str) -> Result<()> {
|
|
||||||
info!("Scanning modules (dual-directory mode)");
|
|
||||||
info!(" Metadata: {}", metadata_dir);
|
|
||||||
info!(" Content: {}", content_dir);
|
|
||||||
|
|
||||||
// 1. Traverse metadata directory, collect enabled module IDs
|
|
||||||
let enabled_modules = collect_enabled_modules(metadata_dir)?;
|
|
||||||
|
|
||||||
if enabled_modules.is_empty() {
|
|
||||||
info!("No enabled modules found");
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
info!("Found {} enabled module(s)", enabled_modules.len());
|
|
||||||
|
|
||||||
// 2. Initialize partition lowerdir lists
|
|
||||||
let partition = vec!["vendor", "product", "system_ext", "odm", "oem"];
|
|
||||||
let mut system_lowerdir: Vec<String> = Vec::new();
|
|
||||||
let mut partition_lowerdir: HashMap<String, Vec<String>> = HashMap::new();
|
|
||||||
|
|
||||||
for part in &partition {
|
|
||||||
partition_lowerdir.insert((*part).to_string(), Vec::new());
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Read module content from content directory
|
|
||||||
for module_id in &enabled_modules {
|
|
||||||
let module_content_path = Path::new(content_dir).join(module_id);
|
|
||||||
|
|
||||||
if !module_content_path.exists() {
|
|
||||||
warn!("Module {} has no content directory, skipping", module_id);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
info!("Processing module: {}", module_id);
|
|
||||||
|
|
||||||
// Collect system partition
|
|
||||||
let system_path = module_content_path.join("system");
|
|
||||||
if system_path.is_dir() {
|
|
||||||
system_lowerdir.push(system_path.display().to_string());
|
|
||||||
info!(" + system/");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Collect other partitions
|
|
||||||
for part in &partition {
|
|
||||||
let part_path = module_content_path.join(part);
|
|
||||||
if part_path.is_dir()
|
|
||||||
&& let Some(v) = partition_lowerdir.get_mut(*part)
|
|
||||||
{
|
|
||||||
v.push(part_path.display().to_string());
|
|
||||||
info!(" + {}/", part);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. Mount partitions
|
|
||||||
info!("Mounting partitions...");
|
|
||||||
|
|
||||||
if let Err(e) = mount_partition("system", &system_lowerdir) {
|
|
||||||
warn!("mount system failed: {e:#}");
|
|
||||||
}
|
|
||||||
|
|
||||||
for (k, v) in partition_lowerdir {
|
|
||||||
if let Err(e) = mount_partition(&k, &v) {
|
|
||||||
warn!("mount {k} failed: {e:#}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
info!("All partitions processed");
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(any(target_os = "linux", target_os = "android")))]
|
|
||||||
pub fn mount_modules_systemlessly(_metadata_dir: &str, _content_dir: &str) -> Result<()> {
|
|
||||||
unimplemented!("mount_modules_systemlessly is only supported on Linux/Android")
|
|
||||||
}
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
use anyhow::{bail, Context, Result};
|
|
||||||
use hole_punch::*;
|
|
||||||
use std::{
|
|
||||||
fs::{File, OpenOptions},
|
|
||||||
io::{Read, Seek, SeekFrom, Write},
|
|
||||||
path::Path,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Handle the `xcp` command: copy sparse file with optional hole punching.
|
|
||||||
pub fn run(args: &[String]) -> Result<()> {
|
|
||||||
let mut positional: Vec<&str> = Vec::with_capacity(2);
|
|
||||||
let mut punch_hole = false;
|
|
||||||
|
|
||||||
for arg in args {
|
|
||||||
match arg.as_str() {
|
|
||||||
"--punch-hole" => punch_hole = true,
|
|
||||||
"-h" | "--help" => {
|
|
||||||
print_usage();
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
_ => positional.push(arg),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if positional.len() < 2 {
|
|
||||||
print_usage();
|
|
||||||
bail!("xcp requires source and destination paths");
|
|
||||||
}
|
|
||||||
if positional.len() > 2 {
|
|
||||||
bail!("unexpected argument: {}", positional[2]);
|
|
||||||
}
|
|
||||||
|
|
||||||
copy_sparse_file(positional[0], positional[1], punch_hole)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn print_usage() {
|
|
||||||
eprintln!("Usage: meta-overlayfs xcp <src> <dst> [--punch-hole]");
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: use libxcp to improve the speed if cross's MSRV is 1.70
|
|
||||||
pub fn copy_sparse_file<P: AsRef<Path>, Q: AsRef<Path>>(
|
|
||||||
src: P,
|
|
||||||
dst: Q,
|
|
||||||
punch_hole: bool,
|
|
||||||
) -> Result<()> {
|
|
||||||
let mut src_file = File::open(src.as_ref())
|
|
||||||
.with_context(|| format!("failed to open {}", src.as_ref().display()))?;
|
|
||||||
let mut dst_file = OpenOptions::new()
|
|
||||||
.write(true)
|
|
||||||
.create(true)
|
|
||||||
.truncate(true)
|
|
||||||
.open(dst.as_ref())
|
|
||||||
.with_context(|| format!("failed to open {}", dst.as_ref().display()))?;
|
|
||||||
|
|
||||||
dst_file.set_len(src_file.metadata()?.len())?;
|
|
||||||
|
|
||||||
let segments = src_file.scan_chunks()?;
|
|
||||||
for segment in segments {
|
|
||||||
if let SegmentType::Data = segment.segment_type {
|
|
||||||
let start = segment.start;
|
|
||||||
let end = segment.end + 1;
|
|
||||||
|
|
||||||
src_file.seek(SeekFrom::Start(start))?;
|
|
||||||
dst_file.seek(SeekFrom::Start(start))?;
|
|
||||||
|
|
||||||
let mut buffer = [0; 4096];
|
|
||||||
let mut total_bytes_copied = 0;
|
|
||||||
|
|
||||||
while total_bytes_copied < end - start {
|
|
||||||
let bytes_to_read =
|
|
||||||
std::cmp::min(buffer.len() as u64, end - start - total_bytes_copied);
|
|
||||||
let bytes_read = src_file.read(&mut buffer[..bytes_to_read as usize])?;
|
|
||||||
|
|
||||||
if bytes_read == 0 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if punch_hole && buffer[..bytes_read].iter().all(|&x| x == 0) {
|
|
||||||
dst_file.seek(SeekFrom::Current(bytes_read as i64))?;
|
|
||||||
total_bytes_copied += bytes_read as u64;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
dst_file.write_all(&buffer[..bytes_read])?;
|
|
||||||
total_bytes_copied += bytes_read as u64;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user