[PR #73] [MERGED] fix(lxc): private tmpfs for /dev/input so virtual input nodes are per-container #76

Closed
opened 2026-10-15 12:51:49 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/games-on-whales/LXC2Docker/pull/73
Author: @JBailes
Created: 6/8/2026
Status: ✅ Merged
Merged: 6/8/2026
Merged by: @JBailes

Base: main ← Head: fix/devinput-private-tmpfs


📝 Commits (1)

  • 6465e64 fix(lxc): private tmpfs for /dev/input so virtual input nodes are per-container

📊 Changes

13 files changed (+81 additions, -20 deletions)

View changed files

➕ .aimee/.last-verify (+8 -0)
➕ .aimee/worktrees/0e7449d4/main (+1 -0)
➕ .aimee/worktrees/1f9a2758/main (+1 -0)
➕ .aimee/worktrees/31b246cc/main (+1 -0)
➕ .aimee/worktrees/42f72487/main (+1 -0)
➕ .aimee/worktrees/52803940/main (+1 -0)
➕ .aimee/worktrees/5f0c2881/main (+1 -0)
➕ .aimee/worktrees/65bd228e/main (+1 -0)
➕ .aimee/worktrees/7952d60b/main (+1 -0)
➕ .aimee/worktrees/dac9382e/main (+1 -0)
➕ .aimee/worktrees/registry.tsv (+9 -0)
📝 internal/lxc/config.go (+20 -20)
📝 internal/lxc/config_test.go (+35 -0)

📄 Description

Problem

Controllers and keyboard input in Wolf sessions die intermittently ("off and on"): a game (e.g. Bravely Default) or the Wolf UI stops accepting all input, even though the virtual input device is alive in the kernel.

autoMountDeviceDirs bind-mounts the host's /dev/input directory into every container. On the host that directory is the single global devtmpfs, and a bind mount shares its superblock — so mknod/unlink of a device node inside one container is visible to every other container and the host. rprivate only stops sub-mount propagation, not node operations within the shared filesystem.

gow/Wolf manages its virtual input devices per session by execing mknod/rm of /dev/input/eventN inside each container (controller and keyboard plug/unplug), built for Docker's private per-container /dev. On the shared devtmpfs those operations collide: one session's unplug (or a UI->game transition) deletes the node out from under another still-running session, leaving the kernel device alive but with no openable node.

Evidence (live, PVE)

  • Kernel device healthy: input34 "Wolf X-Box One (virtual) pad", event19=13:83, js0=13:0.
  • Yet /dev/input/event19+js0 missing from the host and all containers simultaneously — only possible if unlinked from the shared devtmpfs.
  • Wolf log shows mknod: /dev/input/event19: File exists, then the node is gone.
  • Host, wolf CT, wolf-ui CT and the game CT all mount the same 0:7 udev devtmpfs at /dev/input.

Fix

Mount a private tmpfs at the device directory instead of bind-mounting the host dir (no nodev, so it allows device nodes), reproducing Docker's per-container /dev isolation. Wolf populates it via its own mknod on plug, and the c 13:* rwm wildcard already authorises any minor, so the per-node cgroup enumeration is dropped.

Scope is only /dev/input (majorDirMap); per-node device binds (/dev/dri, /dev/nvidia*) are unaffected. Unrelated to the linked-clone path (#71) and the cgroup:mixed fix (#69), both of which are working correctly.

Tests

Added TestAutoMountDeviceDirsUsesPrivateTmpfs and TestAutoMountDeviceDirsIgnoresNonWildcardAndUnknownMajors. go build ./..., go vet, and go test ./... all pass.


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/games-on-whales/LXC2Docker/pull/73 **Author:** [@JBailes](https://github.com/JBailes) **Created:** 6/8/2026 **Status:** ✅ Merged **Merged:** 6/8/2026 **Merged by:** [@JBailes](https://github.com/JBailes) **Base:** `main` ← **Head:** `fix/devinput-private-tmpfs` --- ### 📝 Commits (1) - [`6465e64`](https://github.com/games-on-whales/LXC2Docker/commit/6465e64f219868e59e6bfc538d59a0025d5a5b2e) fix(lxc): private tmpfs for /dev/input so virtual input nodes are per-container ### 📊 Changes **13 files changed** (+81 additions, -20 deletions) <details> <summary>View changed files</summary> ➕ `.aimee/.last-verify` (+8 -0) ➕ `.aimee/worktrees/0e7449d4/main` (+1 -0) ➕ `.aimee/worktrees/1f9a2758/main` (+1 -0) ➕ `.aimee/worktrees/31b246cc/main` (+1 -0) ➕ `.aimee/worktrees/42f72487/main` (+1 -0) ➕ `.aimee/worktrees/52803940/main` (+1 -0) ➕ `.aimee/worktrees/5f0c2881/main` (+1 -0) ➕ `.aimee/worktrees/65bd228e/main` (+1 -0) ➕ `.aimee/worktrees/7952d60b/main` (+1 -0) ➕ `.aimee/worktrees/dac9382e/main` (+1 -0) ➕ `.aimee/worktrees/registry.tsv` (+9 -0) 📝 `internal/lxc/config.go` (+20 -20) 📝 `internal/lxc/config_test.go` (+35 -0) </details> ### 📄 Description ## Problem Controllers and keyboard input in Wolf sessions die intermittently ("off and on"): a game (e.g. Bravely Default) or the Wolf UI stops accepting all input, even though the virtual input device is alive in the kernel. `autoMountDeviceDirs` bind-mounts the host's `/dev/input` directory into every container. On the host that directory is the **single global `devtmpfs`**, and a bind mount shares its superblock — so `mknod`/`unlink` of a device node inside one container is visible to every other container and the host. `rprivate` only stops sub-mount propagation, not node operations within the shared filesystem. gow/Wolf manages its virtual input devices per session by `exec`ing `mknod`/`rm` of `/dev/input/eventN` inside each container (controller and keyboard plug/unplug), built for Docker's private per-container `/dev`. On the shared devtmpfs those operations collide: one session's unplug (or a UI->game transition) deletes the node out from under another still-running session, leaving the kernel device alive but with no openable node. ### Evidence (live, PVE) - Kernel device healthy: `input34` "Wolf X-Box One (virtual) pad", `event19`=`13:83`, `js0`=`13:0`. - Yet `/dev/input/event19`+`js0` missing from the host **and** all containers simultaneously — only possible if unlinked from the shared devtmpfs. - Wolf log shows `mknod: /dev/input/event19: File exists`, then the node is gone. - Host, wolf CT, wolf-ui CT and the game CT all mount the same `0:7 udev` devtmpfs at `/dev/input`. ## Fix Mount a **private tmpfs** at the device directory instead of bind-mounting the host dir (no `nodev`, so it allows device nodes), reproducing Docker's per-container `/dev` isolation. Wolf populates it via its own `mknod` on plug, and the `c 13:* rwm` wildcard already authorises any minor, so the per-node cgroup enumeration is dropped. Scope is only `/dev/input` (`majorDirMap`); per-node device binds (`/dev/dri`, `/dev/nvidia*`) are unaffected. Unrelated to the linked-clone path (#71) and the cgroup:mixed fix (#69), both of which are working correctly. ## Tests Added `TestAutoMountDeviceDirsUsesPrivateTmpfs` and `TestAutoMountDeviceDirsIgnoresNonWildcardAndUnknownMajors`. `go build ./...`, `go vet`, and `go test ./...` all pass. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-10-15 12:51:49 +00:00
Sign in to join this conversation.
No labels
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
games-on-whales/LXC2Docker#76
No description provided.