Run non-interactive sessions through production coordination
This commit is contained in:
parent
308c5a1b56
commit
71b0266cf2
1 changed files with 3770 additions and 44 deletions
|
|
@ -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();
|
||||
|
|
|
|||
Loading…
Reference in a new issue