343 lines
11 KiB
Bash
343 lines
11 KiB
Bash
|
|
#!/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
|