Run non-interactive sessions through production coordination

This commit is contained in:
ms2k 2026-07-16 16:28:18 +09:00
commit 71b0266cf2

View file

@ -90,6 +90,16 @@ impl TargetCommand {
self.parent_edge
}
#[must_use]
pub fn program(&self) -> &OsStr {
&self.program
}
#[must_use]
pub fn arguments(&self) -> &[OsString] {
&self.arguments
}
#[allow(clippy::disallowed_types)]
fn append_to(&self, command: &mut std::process::Command) {
let _ = &self.executable;
@ -183,6 +193,37 @@ pub struct CapturedProcess {
stderr: File,
}
pub struct BootstrapChild {
process: RetainedProcess,
status: File,
}
impl BootstrapChild {
/// Reads the private bootstrap acquisition status byte.
///
/// # Errors
/// Returns an error when the child closes or corrupts its status channel.
pub fn acquisition_status(&mut self) -> io::Result<u8> {
let mut status = [0_u8; 1];
self.status.read_exact(&mut status)?;
Ok(status[0])
}
/// # Errors
/// Returns an error when polling the exact child pidfd fails.
pub fn has_exited(&self) -> io::Result<bool> {
self.process.has_exited()
}
/// Reaps the exact retained child through its pidfd.
///
/// # Errors
/// Returns an error when serialized exact reaping fails.
pub fn reap(self) -> io::Result<ProcessExit> {
child_broker().reap_status(self.process)
}
}
impl CapturedProcess {
#[must_use]
pub const fn process(&self) -> &RetainedProcess {
@ -279,6 +320,53 @@ fn require_captured_parent_edge(parent_edge: ParentEdge) -> io::Result<()> {
}
impl ChildBroker {
/// Spawns the private Host Coordinator bootstrap with exact pipe ownership and pidfd reaping.
///
/// # Errors
/// Refuses a command without the lifetime-pipe edge or any spawn, pidfd, or status failure.
#[allow(clippy::disallowed_methods, clippy::disallowed_types)]
pub fn spawn_bootstrap(
&self,
target: &TargetCommand,
lifetime_reader: File,
) -> io::Result<BootstrapChild> {
let _guard = self
.exclusion
.lock()
.map_err(|_| io::Error::other("child broker lock poisoned"))?;
if target.parent_edge() != ParentEdge::LifetimePipe {
return Err(io::Error::other(
"bootstrap child requires the lifetime-pipe edge",
));
}
require_default_sigchld()?;
require_reapable_sigchld()?;
let mut command = std::process::Command::new(&target.program);
command
.args(&target.arguments)
.stdin(Stdio::from(lifetime_reader))
.stdout(Stdio::piped())
.stderr(Stdio::inherit());
let mut child = command.spawn()?;
let status = child
.stdout
.take()
.ok_or_else(|| io::Error::other("bootstrap status pipe is absent"))?;
let process = match acquire_spawned_child(child.id()) {
Ok(process) => process,
Err(error) => {
let _ = child.kill();
let _ = child.wait();
return Err(error);
}
};
drop(child);
Ok(BootstrapChild {
process,
status: File::from(OwnedFd::from(status)),
})
}
/// Runs a non-durable direct child under the same spawn/reap exclusion as owned children.
///
/// # Errors
@ -1296,6 +1384,31 @@ mod tests {
assert_ne!(wait.0.as_raw_fd(), release.0.as_raw_fd());
}
#[test]
fn bootstrap_child_retains_exact_pipe_owner_and_pidfd_reap() {
let (reader, writer) = pipe_with(PipeFlags::CLOEXEC).unwrap();
let target = TargetCommand::new("/bin/sh")
.argument("-c")
.argument("printf L; read -r _ || exit 0")
.with_lifetime_pipe_parent_edge();
let mut child = child_broker()
.spawn_bootstrap(&target, File::from(reader))
.unwrap();
assert_eq!(child.acquisition_status().unwrap(), b'L');
assert!(!child.has_exited().unwrap());
drop(writer);
let status = child.reap().unwrap();
assert_eq!(status.exit_code, Some(0));
assert_eq!(status.terminating_signal, None);
let (reader, _writer) = pipe_with(PipeFlags::CLOEXEC).unwrap();
assert!(
child_broker()
.spawn_bootstrap(&TargetCommand::new("/bin/true"), File::from(reader))
.is_err()
);
}
#[test]
fn boot_id_requires_exact_canonical_uuid_syntax() {
parse_boot_id("00112233-4455-6677-8899-aabbccddeeff\n").unwrap();