Files
punktfunk/crates
enricobuehler 7f1f7ba87c fix(pads/windows): say WHY a pad index is taken, and stop the devtest lying when it is
Debugging the on-glass session, a devtest run died with

    error=create gamepad bootstrap mailbox Global\pfds-boot-0: Zugriff verweigert (0x80070005)
    (install/repair: punktfunk-host.exe driver install --gamepad)

and then — this is the part that cost real time — kept printing "virtual Xbox One S Controller up",
streamed frames into nothing, and let the operator measure the INCUMBENT pad on that index. The
XInput packet count sat frozen and read as "the pad is dead", which was a wrong conclusion drawn
from a harness that had already failed and not said so.

WHAT IT ACTUALLY WAS. Pad lifetime is deliberately tied to the SESSION (native/input.rs: "the
gamepads are created and torn down with the session"), and a live session's pad legitimately owns
`Global\pfds-boot-0`. The mailbox's SDDL is `D:P(A;;GA;;;SY)(A;;GA;;;LS)` — SYSTEM and LocalService
only — and the host service runs as LocalSystem while a hand-run devtest runs as an elevated
Administrator, which is in neither ACE. `CreateFileMappingW` over an existing name is really an
OPEN, access-checked against the incumbent's DACL, so it returned ACCESS_DENIED and bailed at the
`?` BEFORE reaching the `ERROR_ALREADY_EXISTS` branch that already had the right sentence. That
branch only ever fires when both processes run as the same account.

The name is per-index on purpose and stays that way: `Global\pfds-boot-{index}` is the rendezvous
the driver polls, and its existence doubles as host-liveness. Making it per-process would let two
hosts build two devices on one wire index — the "the game sees two controllers" bug. The collision
is correct; only the diagnosis was wrong.

  * `gamepad_raii.rs` classifies the failure: on ACCESS_DENIED it probes with `OpenFileMappingW`,
    which separates what the OS collapsed — object-manager lookup precedes the access check, so
    absent gives FILE_NOT_FOUND and present-but-forbidden gives ACCESS_DENIED. It now says the
    mailbox belongs to a live session's pad and that nothing is wrong with the drivers.
  * `pad_slots.rs` carries that as a typed `PadCreateFault` through the anyhow chain, so `ensure`
    prints the fault's remedy instead of the per-backend reinstall hint, plus the pad index.
  * `devtest.rs` now BAILS when no pad was actually built, instead of announcing success. This is
    the fix that matters: every probe an operator runs next will still find a device on that index.
  * `native.rs` names what a detached input thread still holds, since that is one of the ways a pad
    can outlive its session.

DELIBERATELY NOT CHANGED, with reasons: the session-scoped pad lifetime (intentional and
documented); the mailbox naming (load-bearing, above); the retry/backoff (latching would resurrect
the `broken` flag `PadGate` exists to kill); the 10 s thread-detach in `serve_session` and the
service's `TerminateProcess` shutdown — both are real ways a devnode can outlive its owner, but
neither is evidenced in the field case and inventing a fix for an unobserved path is how you get a
regression instead of a bugfix.

`pf-inject/lib.rs` drops the `cfg(any(linux, windows))` gate on `pad_gate`/`pad_slots`. Neither
touches an OS pad API, and the gate meant a classification whose entire subject is a `cfg(windows)`
failure could not be tested on a dev machine at all.

VERIFIED
  * ON WINDOWS (.173): `cargo test -p pf-inject --lib` 109/109; `cargo build -p punktfunk-host`
    clean. Both agents' Windows code was compile-UNVERIFIED before this run.
  * macOS: 5 new tests, including one that pins the anyhow downcast through the exact three-layer
    context chain the Windows code builds — the assumption that could not otherwise be checked.
  * `cargo fmt --all --check` clean.

NOT VERIFIED
  * That a LocalSystem-owned mailbox really answers `OpenFileMappingW` with ACCESS_DENIED rather
    than FILE_NOT_FOUND from an Administrator token. That is reasoned from the object manager's
    lookup-then-access-check order, not measured. Repro on .173: hold a session pad on index 0, run
    the devtest from an elevated console, and check the new sentence appears.
2026-08-09 23:29:30 +02:00
..