#!/bin/sh
# Can one of a user's Windows programs read and write another's memory -- the
# way ReadProcessMemory/WriteProcessMemory, debuggers and crash handlers need?
#
# Multi-user debt D16/D19 (ADR 0014). The machine-level wineserver runs as the
# SYSTEM account and the kernel refuses it ptrace on an ordinary user's
# processes, so the server delegates those operations to sg-procagent, which
# runs as the user. This proves the round trip works with the agent, and --
# the mutant -- fails without it, so the check cannot pass vacuously.
#
# Runs as the session user. wine output goes to a file, never a pipe or $(...):
# the probe spawns a child that would hold a substitution pipe open, and wine's
# service processes outlive the command. timeout uses SIGKILL because wine
# ignores SIGTERM.
set -u

SG_LIB="${SG_LIB:-/usr/lib/stained-glass}"
# shellcheck source=lib/sg-common.sh
. "$SG_LIB/sg-common.sh"
sg_wine_env
SG_LIBEXEC="${SG_LIBEXEC:-/usr/libexec/stained-glass}"

RC=0
pass() { printf 'PASS  %s\n' "$*"; }
fail() { printf 'FAIL  %s\n' "$*"; RC=1; }

probe="$SG_LIBEXEC/sg-procmem-probe.exe"
[ -f "$probe" ] || { echo "SKIP: sg-procmem-probe.exe not built"; exit 77; }
[ -x "$SG_LIBEXEC/sg-procagent" ] || { echo "SKIP: sg-procagent not built"; exit 77; }

echo "checking cross-process memory for $(id -un)..."
_probe_w=$(timeout -s KILL 20 winepath -w "$probe" 2>/dev/null || true)
[ -n "$_probe_w" ] || { echo "SKIP: wineserver not responding"; exit 77; }

_out=$(mktemp)
# Run the probe, output to a file (no pipe, no command substitution around
# wine); return whether it reported success. A hard SIGKILL bounds it.
run_probe() {
    : > "$_out"
    timeout -s KILL 30 wine "$_probe_w" > "$_out" 2>/dev/null </dev/null || true
    grep -q "PROCMEM OK" "$_out"
}

# Do NOT pkill by pattern here: "[s]g-procagent" also matches this script's own
# name, sg-procagent-check, so it would signal itself (the pkill -f self-match).
# Removing the socket is enough; sg-procagent unlinks and rebinds it anyway, and
# the only agent we start is our own, killed at the end.
_sock="$SG_PREFIX/.sg-procagent.$(id -u)"
rm -f "$_sock" 2>/dev/null || true
sleep 1

# 1. Without the agent, the cross-process access must be refused.
if run_probe; then
    fail "cross-process memory worked with no agent -- the gate proves nothing"
else
    pass "without sg-procagent, cross-process memory is refused"
fi

# 2. With the agent, it must succeed.
"$SG_LIBEXEC/sg-procagent" "$SG_PREFIX" >/dev/null 2>&1 </dev/null &
_agent=$!
for _ in 1 2 3 4 5; do [ -S "$_sock" ] && break; sleep 1; done
if run_probe; then
    pass "with sg-procagent, a program reads and writes another's memory"
else
    fail "cross-process memory failed with the agent running"
fi
kill "$_agent" 2>/dev/null || true
rm -f "$_out" "$_sock" 2>/dev/null || true

echo
if [ "$RC" -eq 0 ]; then echo "RESULT: PASS"; else echo "RESULT: FAIL"; fi
exit "$RC"
