#!/system/bin/sh # Driver Manager v2 - Per-App & System-Wide Scope Manager # LSPosed-style driver scoping via mount namespace isolation MODDIR=${MODDIR:-/data/adb/modules/driver-manager} . "$MODDIR/scripts/core.sh" log_init "$LOGDIR/scope.log" SCOPES_FILE="$CONFIGDIR/scopes.json" REGISTRY="$CONFIGDIR/drivers.json" # --- Clone file metadata from stock to custom --- clone_metadata() { local stock="$1" custom="$2" # Copy ownership local owner=$(stat -c '%u:%g' "$stock" 2>/dev/null) [ -n "$owner" ] && chown "$owner" "$custom" 2>/dev/null # Copy permissions local perms=$(stat -c '%a' "$stock" 2>/dev/null) [ -n "$perms" ] && chmod "$perms" "$custom" 2>/dev/null # Copy SELinux context local ctx=$(get_selinux_context "$stock") [ -n "$ctx" ] && chcon "$ctx" "$custom" 2>/dev/null # Copy timestamps touch -r "$stock" "$custom" 2>/dev/null } # --- Apply a bind mount for a specific process (per-app scope) --- scope_for_process() { local pkg="$1" local stock_path="$2" local custom_path="$3" if [ ! -f "$custom_path" ]; then log_error "Custom driver not found: $custom_path" return 1 fi clone_metadata "$stock_path" "$custom_path" # Find all PIDs for this package local pids=$(pgrep -f "$pkg" 2>/dev/null) if [ -z "$pids" ]; then log "No running processes for $pkg, scope will apply on next launch" return 0 fi for pid in $pids; do nsenter --mount=/proc/$pid/ns/mnt -- mount --bind "$custom_path" "$stock_path" 2>/dev/null if [ $? -eq 0 ]; then log "Scoped $stock_path -> $custom_path for PID $pid ($pkg)" else log_error "Failed to scope for PID $pid ($pkg)" fi done } # --- Apply a system-wide bind mount --- scope_system() { local stock_path="$1" local custom_path="$2" if [ ! -f "$custom_path" ]; then log_error "Custom driver not found: $custom_path" return 1 fi clone_metadata "$stock_path" "$custom_path" mount --bind "$custom_path" "$stock_path" 2>/dev/null if [ $? -eq 0 ]; then log "System-wide scope: $stock_path -> $custom_path" else log_error "Failed system-wide scope: $stock_path" return 1 fi } # --- Unmount a scope --- unscope() { local stock_path="$1" local pkg="$2" if [ -n "$pkg" ] && [ "$pkg" != "system" ]; then local pids=$(pgrep -f "$pkg" 2>/dev/null) for pid in $pids; do nsenter --mount=/proc/$pid/ns/mnt -- umount "$stock_path" 2>/dev/null done log "Unscoped $stock_path for $pkg" else umount "$stock_path" 2>/dev/null log "Unscoped system-wide: $stock_path" fi } # --- Apply all configured scopes --- apply_all() { [ ! -f "$SCOPES_FILE" ] && return 0 log "Applying all scopes" # Parse scopes.json - each scope: { driver_id, variant, mode, apps[] } local count=0 # Use a simple line-by-line parser for the scopes array local in_scope=false local driver_id="" variant="" mode="" stock_path="" custom_path="" local apps="" while IFS= read -r line; do case "$line" in *'"driver_id"'*) driver_id=$(echo "$line" | sed 's/.*: *"\([^"]*\)".*/\1/') ;; *'"variant_path"'*) custom_path=$(echo "$line" | sed 's/.*: *"\([^"]*\)".*/\1/') ;; *'"stock_path"'*) stock_path=$(echo "$line" | sed 's/.*: *"\([^"]*\)".*/\1/') ;; *'"mode"'*) mode=$(echo "$line" | sed 's/.*: *"\([^"]*\)".*/\1/') ;; *'"apps"'*\[*) apps="" ;; *'"'*'"'*) if [ -n "$stock_path" ] && [ -z "$mode" -o "$mode" = "app" ]; then local app=$(echo "$line" | sed 's/.*"\([^"]*\)".*/\1/') [ -n "$app" ] && apps="$apps $app" fi ;; *\}*) if [ -n "$driver_id" ] && [ -n "$stock_path" ] && [ -n "$custom_path" ]; then if [ "$mode" = "system" ]; then scope_system "$stock_path" "$custom_path" count=$((count + 1)) elif [ -n "$apps" ]; then for app in $apps; do scope_for_process "$app" "$stock_path" "$custom_path" count=$((count + 1)) done fi fi driver_id="" variant="" mode="" stock_path="" custom_path="" apps="" ;; esac done < "$SCOPES_FILE" log "Applied $count scopes" } # --- Unmount all active scopes --- unmount_all() { log "Unmounting all scopes" [ ! -f "$SCOPES_FILE" ] && return 0 while IFS= read -r line; do case "$line" in *'"stock_path"'*) local path=$(echo "$line" | sed 's/.*: *"\([^"]*\)".*/\1/') [ -n "$path" ] && umount "$path" 2>/dev/null ;; esac done < "$SCOPES_FILE" log "All scopes unmounted" } # --- Set a scope (called from API) --- # Usage: scope_manager.sh set [app1,app2,...] set_scope() { local driver_id="$1" local stock_path="$2" local variant_path="$3" local mode="$4" local apps_csv="$5" # Build apps JSON array local apps_json="[]" if [ -n "$apps_csv" ] && [ "$mode" = "app" ]; then apps_json="[$(echo "$apps_csv" | sed 's/,/","/g' | sed 's/^/"/;s/$/"/' )]" fi # Check if scope already exists for this driver, update it if grep -q "\"$driver_id\"" "$SCOPES_FILE" 2>/dev/null; then # Remove existing scope for this driver and re-add remove_scope "$driver_id" fi # Append to scopes file local tmp="$CONFIGDIR/.scopes_tmp.json" if [ -f "$SCOPES_FILE" ] && [ -s "$SCOPES_FILE" ]; then # Insert before the closing ]} sed '$ s/\]}//' "$SCOPES_FILE" > "$tmp" # Add comma if there are existing scopes if grep -q '"driver_id"' "$SCOPES_FILE" 2>/dev/null; then echo "," >> "$tmp" fi else echo '{"scopes":[' > "$tmp" fi cat >> "$tmp" << SCOPE { "driver_id": "$driver_id", "stock_path": "$stock_path", "variant_path": "$variant_path", "mode": "$mode", "apps": $apps_json } ]} SCOPE mv "$tmp" "$SCOPES_FILE" log "Set scope for $driver_id (mode: $mode)" # Apply immediately if [ "$mode" = "system" ]; then scope_system "$stock_path" "$variant_path" else local IFS=',' for app in $apps_csv; do scope_for_process "$app" "$stock_path" "$variant_path" done fi echo "{\"status\": \"ok\", \"driver\": \"$driver_id\", \"mode\": \"$mode\"}" } # --- Remove a scope --- remove_scope() { local driver_id="$1" [ ! -f "$SCOPES_FILE" ] && return 0 # Get stock_path before removing so we can unmount local stock_path=$(grep -A3 "\"$driver_id\"" "$SCOPES_FILE" | grep '"stock_path"' | sed 's/.*: *"\([^"]*\)".*/\1/') [ -n "$stock_path" ] && umount "$stock_path" 2>/dev/null # Rebuild scopes file without this driver local tmp="$CONFIGDIR/.scopes_tmp.json" echo '{"scopes":[' > "$tmp" local first=true skip=false while IFS= read -r line; do case "$line" in *"\"$driver_id\""*) skip=true ;; *\}*) if [ "$skip" = true ]; then skip=false continue fi ;; esac [ "$skip" = true ] && continue # Skip the scopes header/footer echo "$line" | grep -q '"scopes"' && continue echo "$line" | grep -qE '^\[|^\]' && continue echo "$line" >> "$tmp" done < "$SCOPES_FILE" echo "]}" >> "$tmp" mv "$tmp" "$SCOPES_FILE" log "Removed scope for $driver_id" echo "{\"status\": \"ok\", \"removed\": \"$driver_id\"}" } # --- List active scopes --- list_scopes() { [ -f "$SCOPES_FILE" ] && cat "$SCOPES_FILE" || echo '{"scopes":[]}' } # --- Monitor Zygote for new app launches --- # Watches for new processes and applies per-app scopes monitor_zygote() { log "Zygote monitor started" local known_pids="" while true; do sleep 5 [ ! -f "$SCOPES_FILE" ] && continue # Get list of scoped apps local scoped_apps=$(grep '"apps"' -A50 "$SCOPES_FILE" 2>/dev/null | grep -o '"[a-z][a-zA-Z0-9.]*"' | tr -d '"' | sort -u) [ -z "$scoped_apps" ] && continue for app in $scoped_apps; do local pids=$(pgrep -f "$app" 2>/dev/null) [ -z "$pids" ] && continue for pid in $pids; do # Skip if we already applied scope to this PID echo "$known_pids" | grep -q " $pid " && continue # Find the scope config for this app local stock_path="" custom_path="" local in_scope=false app_match=false while IFS= read -r line; do case "$line" in *'"stock_path"'*) stock_path=$(echo "$line" | sed 's/.*: *"\([^"]*\)".*/\1/') ;; *'"variant_path"'*) custom_path=$(echo "$line" | sed 's/.*: *"\([^"]*\)".*/\1/') ;; *"\"$app\""*) app_match=true ;; *\}*) if [ "$app_match" = true ] && [ -n "$stock_path" ] && [ -n "$custom_path" ]; then nsenter --mount=/proc/$pid/ns/mnt -- mount --bind "$custom_path" "$stock_path" 2>/dev/null [ $? -eq 0 ] && log "Auto-scoped $app PID $pid" fi stock_path="" custom_path="" app_match=false ;; esac done < "$SCOPES_FILE" known_pids="$known_pids $pid " done done # Clean dead PIDs from known list periodically local new_known="" for pid in $known_pids; do [ -d "/proc/$pid" ] && new_known="$new_known $pid " done known_pids="$new_known" done } # --- Main --- case "$1" in apply) apply_all ;; set) set_scope "$2" "$3" "$4" "$5" "$6" ;; remove) remove_scope "$2" ;; list) list_scopes ;; unmount_all) unmount_all ;; monitor) monitor_zygote ;; *) echo "Usage: scope_manager.sh {apply|set|remove|list|unmount_all|monitor} [args]" ;; esac