feat(#2862): fd-carrying line framing for the priv socket

First half of the fd-passing work, and deliberately the half with the
real failure mode in it. No syscalls here — the caller does the
recvmsg and feeds this (bytes, fds); it hands back complete messages
paired with the descriptor each one owns.

Association is the whole point. A descriptor does not arrive neatly
paired with the request that wants it: recvmsg returns whatever bytes
happen to be available plus whatever ancillary data rode along, so a
descriptor can arrive with a chunk holding only part of its request's
line, with a chunk whose bytes finish the previous request, ahead of
any of its own bytes, or alongside several complete requests at once.

Pairing "the fd from this chunk" with "the request in this chunk" is
therefore wrong in the worst way: the types are identical either way,
so nothing catches it, and the failure is one request executing
against another's descriptor — in this process, writing one agent's
state into a different transfer's socket. So descriptors queue on
arrival and each message claims the oldest unclaimed one at the moment
it completes.

Two consequences worth stating: a line that fails to decode does NOT
consume a descriptor (closing it there would destroy something
belonging to a request nobody processed), and unclaimed descriptors
are drainable so the teardown path can close them instead of leaking
one per abandoned message in a long-lived helper.

Lives in hive-priv-sock, not hive-priv: clippy's dead-code error was
right that an unwired module doesn't belong in the binary, and chasing
that produced the better home anyway — both ends need this. The daemon
sends descriptors and the helper reassembles them, so framing is part
of the wire contract rather than one side's implementation detail.
This commit is contained in:
atlas 2026-07-31 20:37:19 +02:00 committed by mara
commit ec4ba4c7fa
2 changed files with 285 additions and 0 deletions

View file

@ -12,6 +12,12 @@
use serde::{Deserialize, Serialize};
// Line framing that also carries passed file descriptors. Part of the
// wire contract rather than one side's implementation detail: the
// daemon sends descriptors and the helper reassembles them, so both
// ends have to agree on how a descriptor is bound to a request.
pub mod framing;
/// Default socket path for the privileged helper.
pub const PRIV_SOCK: &str = "/run/hive/priv.sock";