Files
driver-manager/scripts/scope_manager.sh

343 lines
11 KiB
Bash
Raw Normal View History

#!/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 <driver_id> <stock_path> <variant_path> <mode> [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