Commit e7f2993
feat(core): streaming-stdio popen → Process with per-stream StdioMode
Add Sandbox::popen, the first piece of the streaming-stdio process API
(RFC #67). It generalizes the do_create capture/inherit boolean into a
per-stream StdioMode { Inherit, Piped, Null } and returns a live Process
whose Piped stream ends the caller owns (take_stdin/stdout/stderr), so a
confined process can be driven over stdio while alive (MCP/LSP/REPL/
JSON-RPC) — which capture-mode run cannot express.
- StdioMode is #[repr(u32)] with pinned discriminants so the FFI/Python
bindings (later PRs) bind a stable C enum.
- do_create is now a thin wrapper over do_create_stdio, so run/spawn/create
and their interactive variants keep byte-identical capture/inherit behavior.
- Child-side fd wiring is collision-safe. The post-fork path must survive a
supervisor started with 0/1/2 closed (common for daemons): pipe2 can then
allocate a pipe end onto a std fd, so a naive dup2 would alias the target,
let a sibling stream clobber an end still needed, or act on a stale raw-fd
snapshot. Each Piped source is first relocated to a fresh high fd (>= 3) via
F_DUPFD_CLOEXEC, then dup2'd down onto its 0/1/2 target through one audited
helper; the original O_CLOEXEC ends are mem::forget'd (they close at execve)
so their Drop can't close a fd we reassigned. dup2 failure fails closed.
- Null wires the stream to /dev/null and fails closed: if it cannot be opened
the fd is closed, never left inherited from the supervisor.
- Piped stdin adds a parent-held write end; wait() drops an untaken one so a
child reading stdin still reaches EOF instead of hanging. A *taken* stdin the
caller must close before wait() (documented; same contract as std).
- popen does not poll for execve (a streaming reader blocks until bytes
arrive, and the poll would spuriously time out on a fast-exiting child).
- Process borrows the Sandbox (it does not own the process like
std::process::Child); the child is killed and reaped by Sandbox::drop or
Process::kill (whole process group). #[must_use] flags an abandoned handle.
Folding the legacy spawn/wait into Process is deferred to a later phase.
Tests (multi-threaded runtime — a blocking pipe read must not starve the
seccomp supervisor, documented on popen): stdin+stdout roundtrip, stdout-only,
stderr-only, all-three piped, Null stdout proven to be /dev/null, Null stdin
EOF, untaken-stdin EOF, explicit kill + non-success status, whole-group
kill-on-drop reaching a forked grandchild (polls for ESRCH to avoid racing
zombie reaping), second-popen-on-used-Sandbox rejected, and a regression that
capture-mode run still buffers stdout.
Refs #671 parent 495902e commit e7f2993
5 files changed
Lines changed: 564 additions & 34 deletions
File tree
- crates/sandlock-core
- src
- sandbox
- tests
- integration
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
35 | 35 | | |
36 | 36 | | |
37 | 37 | | |
38 | | - | |
| 38 | + | |
39 | 39 | | |
40 | 40 | | |
41 | 41 | | |
| |||
0 commit comments