use super::launch::{BarrierWait, append_parent_identity, exec_target, parse_wrapper_arguments}; use super::recovery::{ arm_parent_death_and_recheck, authority_from_pidfd, current_process_identity, read_boot_id, read_start_time, require_default_sigchld, require_host_pid_namespace, require_reapable_sigchld, }; use super::{ AcknowledgedChildAcquisition, AsFd, BARRIER_RELEASE, BarrierRelease, BootstrapChild, CapturedProcess, ChildBroker, ChildStandardIo, ChildStandardIoPolicy, File, HostPidLocator, MAPPED_WRAPPER_ROLE, OFlags, OsString, OwnedFd, PRIVATE_PROCESS_ROLE, ParentEdge, Pid, PidfdFlags, PidfsProcessIdentityV1, PollFd, PollFlags, ProcessExit, ProcessOwnershipCommitAck, ProcessOwnershipContext, ProcessStartTimeTicks, RetainedProcess, SIGCHLD_PROBE_ROLE, Signal, StagedProcessOwnership, Stdio, TargetCommand, Timespec, WRAPPER_ROLE, WaitId, WaitIdOptions, fcntl_getfl, fcntl_setfl, io, launch_barrier, poll, recover_process, require_captured_parent_edge, require_parent_edge_standard_io, }; use crate::ipc::private_stream_pair; use crate::user_namespace::UserNamespaceMap; use std::io::{Read as _, Write as _}; 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 { 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 /// Returns an error when causal launch, exact waiting, or the command exit status fails. #[allow(clippy::disallowed_methods, clippy::disallowed_types)] pub fn run_transient(&self, target: &TargetCommand) -> io::Result { self.run_transient_with_stdout(target, None) } /// Runs a non-durable direct child with one private inherited stdout descriptor. /// /// # Errors /// Returns an error when causal launch, exact waiting, or the command exit status fails. pub fn run_transient_with_private_stdout( &self, target: &TargetCommand, stdout: OwnedFd, ) -> io::Result { self.run_transient_with_stdout(target, Some(stdout)) } #[allow(clippy::disallowed_methods, clippy::disallowed_types)] fn run_transient_with_stdout( &self, target: &TargetCommand, stdout: Option, ) -> io::Result { let _guard = self .exclusion .lock() .map_err(|_| io::Error::other("child broker lock poisoned"))?; require_parent_edge_standard_io(target.parent_edge(), ChildStandardIoPolicy::Silenced)?; require_default_sigchld()?; require_reapable_sigchld()?; let parent = current_process_identity()?; let (release, wait) = launch_barrier()?; let mut command = private_role_command(WRAPPER_ROLE); append_parent_identity(&mut command, parent); target.append_to(&mut command); command .stdin(wait.into_stdio()) .stdout(stdout.map_or_else(Stdio::null, Stdio::from)) .stderr(Stdio::null()); let mut child = command.spawn()?; let process = match acquire_spawned_child(child.id()) { Ok(process) => process, Err(error) => return fail_before_pidfd(release, &mut child, error), }; if let Err(error) = release.release() { return fail_after_pidfd_closed(process, error); } drop(child); let status = rustix::process::waitid(WaitId::PidFd(process.pidfd.as_fd()), WaitIdOptions::EXITED)? .ok_or_else(|| io::Error::other("transient child exited without wait status"))?; let exit = ProcessExit { exit_code: status.exit_status(), terminating_signal: status.terminating_signal(), }; drop(process); if exit.exit_code == Some(0) && exit.terminating_signal.is_none() { Ok(exit) } else { Err(io::Error::other(format!( "transient child failed with exit {:?} and signal {:?}", exit.exit_code, exit.terminating_signal ))) } } /// Runs a non-durable direct child as root in the supplied caller-configured user namespace. /// /// # Errors /// Refuses invalid mapping, causal launch, helper execution, map attestation, or child exit. #[allow(clippy::disallowed_methods, clippy::disallowed_types)] pub fn run_transient_in_user_namespace( &self, target: &TargetCommand, map: &UserNamespaceMap, ) -> io::Result { let _guard = self .exclusion .lock() .map_err(|_| io::Error::other("child broker lock poisoned"))?; require_parent_edge_standard_io(target.parent_edge(), ChildStandardIoPolicy::Inherit)?; require_default_sigchld()?; require_reapable_sigchld()?; map.validate()?; let parent = current_process_identity()?; let (handshake, child_handshake) = private_stream_pair()?; let mut command = mapped_wrapper_command(); append_parent_identity(&mut command, parent); target.append_to(&mut command); command .stdin(Stdio::from(child_handshake)) .stdout(Stdio::null()) .stderr(Stdio::inherit()); let mut child = command.spawn()?; let process = match acquire_spawned_child(child.id()) { Ok(process) => process, Err(error) => { drop(handshake); let _ = child.wait(); return Err(error); } }; if let Err(error) = configure_user_namespace(&process, File::from(handshake), map) { return fail_running_mapped(process, child, error); } let status = rustix::process::waitid(WaitId::PidFd(process.pidfd.as_fd()), WaitIdOptions::EXITED)? .ok_or_else(|| io::Error::other("mapped transient child exited without status"))?; let exit = ProcessExit { exit_code: status.exit_status(), terminating_signal: status.terminating_signal(), }; drop(child); drop(process); if exit.exit_code == Some(0) && exit.terminating_signal.is_none() { Ok(exit) } else { Err(io::Error::other(format!( "mapped transient child failed with exit {:?} and signal {:?}", exit.exit_code, exit.terminating_signal ))) } } /// Starts a non-durable direct child with split captured output and exact retained authority. /// /// # Errors /// Refuses invalid parent-edge or SIGCHLD state and cleans up on spawn, pidfd, barrier, or /// pipe-acquisition failure. #[allow(clippy::disallowed_methods, clippy::disallowed_types)] pub fn run_transient_captured(&self, target: &TargetCommand) -> io::Result { let _guard = self .exclusion .lock() .map_err(|_| io::Error::other("child broker lock poisoned"))?; require_captured_parent_edge(target.parent_edge())?; require_default_sigchld()?; require_reapable_sigchld()?; let parent = current_process_identity()?; let (release, wait) = launch_barrier()?; let mut command = private_role_command(WRAPPER_ROLE); append_parent_identity(&mut command, parent); target.append_to(&mut command); command .stdin(wait.into_stdio()) .stdout(Stdio::piped()) .stderr(Stdio::piped()); let mut child = command.spawn()?; let process = match acquire_spawned_child(child.id()) { Ok(process) => process, Err(error) => return fail_before_pidfd(release, &mut child, error), }; if let Err(error) = release.release() { return fail_after_pidfd_closed(process, error); } captured_process(process, child) } /// Spawns the repository wrapper, stages exact ownership, accepts its acknowledgment, /// and only then releases target execution. /// /// # Errors /// Refuses and reaps the blocked wrapper on every failed acquisition or callback. #[allow(clippy::disallowed_methods, clippy::disallowed_types)] pub fn acquire( &self, expected_parent: PidfsProcessIdentityV1, ownership: ProcessOwnershipContext, target: &TargetCommand, stage: Stage, acknowledge: Acknowledge, ) -> io::Result where Stage: FnOnce(StagedProcessOwnership) -> io::Result<()>, Acknowledge: FnOnce(StagedProcessOwnership) -> io::Result, { self.acquire_with_stdio( expected_parent, ownership, target, ChildStandardIo::inherit(), stage, acknowledge, ) } /// # Errors /// Refuses and reaps the blocked wrapper on every failed acquisition or callback. #[allow(clippy::disallowed_methods, clippy::disallowed_types)] pub fn acquire_with_stdio( &self, expected_parent: PidfsProcessIdentityV1, ownership: ProcessOwnershipContext, target: &TargetCommand, standard_io: ChildStandardIo, stage: Stage, acknowledge: Acknowledge, ) -> io::Result where Stage: FnOnce(StagedProcessOwnership) -> io::Result<()>, Acknowledge: FnOnce(StagedProcessOwnership) -> io::Result, { self.acquire_with_stdio_after_ack( AcknowledgedChildAcquisition { expected_parent, ownership, target, standard_io, }, stage, acknowledge, || Ok(()), ) } /// Acquires an exact child as root in a helper-configured subordinate user namespace. /// /// # Errors /// Refuses staging, acknowledgment, namespace helper, live-map attestation, or release /// failures and reaps the child on every failure. #[allow(clippy::disallowed_methods, clippy::disallowed_types)] pub fn acquire_with_stdio_in_user_namespace( &self, acquisition: AcknowledgedChildAcquisition<'_>, map: &UserNamespaceMap, stage: Stage, acknowledge: Acknowledge, ) -> io::Result where Stage: FnOnce(StagedProcessOwnership) -> io::Result<()>, Acknowledge: FnOnce(StagedProcessOwnership) -> io::Result, { let _guard = self .exclusion .lock() .map_err(|_| io::Error::other("child broker lock poisoned"))?; require_parent_edge_standard_io( acquisition.target.parent_edge(), acquisition.standard_io.policy(), )?; require_default_sigchld()?; require_reapable_sigchld()?; map.validate()?; let (handshake, child_handshake) = private_stream_pair()?; let mut command = mapped_wrapper_command(); append_parent_identity(&mut command, acquisition.expected_parent); acquisition.target.append_to(&mut command); command.stdin(Stdio::from(child_handshake)); let mut standard_io = acquisition.standard_io; if let Some(stdout) = standard_io.stdout.take() { command.stdout(stdout); } if let Some(stderr) = standard_io.stderr.take() { command.stderr(stderr); } let mut child = command.spawn()?; let process = match acquire_spawned_child(child.id()) { Ok(process) => process, Err(error) => { drop(handshake); let _ = child.wait(); return Err(error); } }; let staged = StagedProcessOwnership::v1( acquisition.ownership.domain_id, acquisition.ownership.generation_id, acquisition.ownership.coordinator_id, acquisition.ownership.slot, process.identity, ); if let Err(error) = stage(staged) { drop(handshake); return fail_after_pidfd_closed(process, error); } let acknowledgment = match acknowledge(staged) { Ok(acknowledgment) => acknowledgment, Err(error) => { drop(handshake); return fail_after_pidfd_closed(process, error); } }; if !acknowledgment.acknowledges(&staged) { drop(handshake); return fail_after_pidfd_closed( process, io::Error::other( "process ownership acknowledgment does not match staged ownership", ), ); } if let Err(error) = configure_user_namespace(&process, File::from(handshake), map) { return fail_running_mapped(process, child, error); } drop(child); Ok(process) } /// Acquires an exact child while retaining its blocked wrapper across one acknowledged /// parent transition. /// /// # Errors /// Refuses and reaps the blocked wrapper when staging, acknowledgment, transition, or release /// fails. #[allow(clippy::disallowed_methods, clippy::disallowed_types)] pub fn acquire_with_stdio_after_ack( &self, acquisition: AcknowledgedChildAcquisition<'_>, stage: Stage, acknowledge: Acknowledge, transition: Transition, ) -> io::Result where Stage: FnOnce(StagedProcessOwnership) -> io::Result<()>, Acknowledge: FnOnce(StagedProcessOwnership) -> io::Result, Transition: FnOnce() -> io::Result<()>, { let _guard = self .exclusion .lock() .map_err(|_| io::Error::other("child broker lock poisoned"))?; require_parent_edge_standard_io( acquisition.target.parent_edge(), acquisition.standard_io.policy(), )?; require_default_sigchld()?; require_reapable_sigchld()?; let (release, wait) = launch_barrier()?; let mut command = private_role_command(WRAPPER_ROLE); append_parent_identity(&mut command, acquisition.expected_parent); acquisition.target.append_to(&mut command); command.stdin(wait.into_stdio()); let mut standard_io = acquisition.standard_io; if let Some(stdout) = standard_io.stdout.take() { command.stdout(stdout); } if let Some(stderr) = standard_io.stderr.take() { command.stderr(stderr); } let mut child = command.spawn()?; let host_pid = child.id(); let acquired = acquire_spawned_child(host_pid); let process = match acquired { Ok(process) => process, Err(error) => return fail_before_pidfd(release, &mut child, error), }; let staged = StagedProcessOwnership::v1( acquisition.ownership.domain_id, acquisition.ownership.generation_id, acquisition.ownership.coordinator_id, acquisition.ownership.slot, process.identity, ); if let Err(error) = stage(staged) { return fail_after_pidfd(release, process, error); } let acknowledgment = match acknowledge(staged) { Ok(acknowledgment) => acknowledgment, Err(error) => return fail_after_pidfd(release, process, error), }; if !acknowledgment.acknowledges(&staged) { return fail_after_pidfd( release, process, io::Error::other( "process ownership acknowledgment does not match staged ownership", ), ); } if let Err(error) = transition() { return fail_after_pidfd(release, process, error); } if let Err(error) = release.release() { return fail_after_pidfd_closed(process, error); } drop(child); Ok(process) } /// Acquires an exact child with bounded-output pipe ownership retained by the caller. /// /// # Errors /// Refuses and reaps the blocked wrapper on every failed acquisition or callback. #[allow(clippy::disallowed_methods, clippy::disallowed_types)] pub fn acquire_captured( &self, expected_parent: PidfsProcessIdentityV1, ownership: ProcessOwnershipContext, target: &TargetCommand, stage: Stage, acknowledge: Acknowledge, ) -> io::Result where Stage: FnOnce(StagedProcessOwnership) -> io::Result<()>, Acknowledge: FnOnce(StagedProcessOwnership) -> io::Result, { let _guard = self .exclusion .lock() .map_err(|_| io::Error::other("child broker lock poisoned"))?; require_captured_parent_edge(target.parent_edge())?; require_default_sigchld()?; require_reapable_sigchld()?; let (release, wait) = launch_barrier()?; let mut command = private_role_command(WRAPPER_ROLE); append_parent_identity(&mut command, expected_parent); target.append_to(&mut command); command .stdin(wait.into_stdio()) .stdout(Stdio::piped()) .stderr(Stdio::piped()); let mut child = command.spawn()?; let host_pid = child.id(); let process = match acquire_spawned_child(host_pid) { Ok(process) => process, Err(error) => return fail_before_pidfd(release, &mut child, error), }; let staged = StagedProcessOwnership::v1( ownership.domain_id, ownership.generation_id, ownership.coordinator_id, ownership.slot, process.identity, ); if let Err(error) = stage(staged) { return fail_after_pidfd(release, process, error); } let acknowledgment = match acknowledge(staged) { Ok(acknowledgment) => acknowledgment, Err(error) => return fail_after_pidfd(release, process, error), }; if !acknowledgment.acknowledges(&staged) { return fail_after_pidfd( release, process, io::Error::other( "process ownership acknowledgment does not match staged ownership", ), ); } if let Err(error) = release.release() { return fail_after_pidfd_closed(process, error); } captured_process(process, child) } /// # Errors /// Returns an error when serialization or exact pidfd reaping fails. #[allow(clippy::disallowed_methods)] pub fn reap(&self, process: RetainedProcess) -> io::Result<()> { self.reap_status(process).map(|_| ()) } /// # Errors /// Returns an error when serialization or exact pidfd reaping fails. #[allow(clippy::disallowed_methods)] pub fn reap_status(&self, process: RetainedProcess) -> io::Result { let _guard = self .exclusion .lock() .map_err(|_| io::Error::other("child broker lock poisoned"))?; let status = rustix::process::waitid(WaitId::PidFd(process.pidfd.as_fd()), WaitIdOptions::EXITED)? .ok_or_else(|| io::Error::other("exact child exited without wait status"))?; let exit = ProcessExit { exit_code: status.exit_status(), terminating_signal: status.terminating_signal(), }; drop(process); Ok(exit) } } #[allow(clippy::disallowed_types)] fn captured_process( process: RetainedProcess, mut child: std::process::Child, ) -> io::Result { let Some(stdout) = child.stdout.take() else { return fail_running_captured( process, child, io::Error::other("captured child stdout is unavailable"), ); }; let Some(stderr) = child.stderr.take() else { return fail_running_captured( process, child, io::Error::other("captured child stderr is unavailable"), ); }; let stdout = match child_pipe_file(stdout.into()) { Ok(stdout) => stdout, Err(error) => return fail_running_captured(process, child, error), }; let stderr = match child_pipe_file(stderr.into()) { Ok(stderr) => stderr, Err(error) => return fail_running_captured(process, child, error), }; drop(child); Ok(CapturedProcess { process, stdout, stderr, }) } fn child_pipe_file(fd: OwnedFd) -> io::Result { let flags = fcntl_getfl(fd.as_fd())?; fcntl_setfl(fd.as_fd(), flags | OFlags::NONBLOCK)?; Ok(File::from(fd)) } #[allow(clippy::disallowed_types)] fn mapped_wrapper_command() -> std::process::Command { private_role_command(MAPPED_WRAPPER_ROLE) } fn configure_user_namespace( process: &RetainedProcess, mut handshake: File, map: &UserNamespaceMap, ) -> io::Result<()> { handshake.write_all(BARRIER_RELEASE)?; handshake.flush()?; let mut marker = [0_u8; 1]; handshake.read_exact(&mut marker)?; if marker != *b"u" { return Err(io::Error::other( "mapped child sent an invalid user-namespace marker", )); } let pid = process.identity.host_pid().get(); run_mapping_helper("/usr/bin/newuidmap", &map.uid_helper_arguments(pid))?; run_mapping_helper("/usr/bin/newgidmap", &map.gid_helper_arguments(pid))?; map.verify_process(pid)?; handshake.write_all(b"m")?; handshake.flush() } #[allow(clippy::disallowed_methods, clippy::disallowed_types)] fn run_mapping_helper(program: &str, arguments: &[String]) -> io::Result<()> { let mut command = std::process::Command::new(program); command .args(arguments) .stdin(Stdio::null()) .stdout(Stdio::null()) .stderr(Stdio::piped()); let mut child = command.spawn()?; let process = match acquire_spawned_child(child.id()) { Ok(process) => process, Err(error) => { let _ = child.kill(); let _ = child.wait(); return Err(error); } }; let status = rustix::process::waitid(WaitId::PidFd(process.pidfd.as_fd()), WaitIdOptions::EXITED)? .ok_or_else(|| io::Error::other("ID-map helper exited without status"))?; let mut detail = String::new(); if let Some(stderr) = child.stderr.take() { stderr.take(4096).read_to_string(&mut detail)?; } drop(child); drop(process); if status.exit_status() == Some(0) && status.terminating_signal().is_none() { Ok(()) } else { Err(io::Error::other(format!( "{program} failed with exit {:?} and signal {:?}: {}", status.exit_status(), status.terminating_signal(), detail.trim() ))) } } #[allow(clippy::disallowed_methods, clippy::disallowed_types)] fn fail_running_mapped( process: RetainedProcess, child: std::process::Child, error: io::Error, ) -> io::Result { let _ = process.send_signal(Signal::KILL); rustix::process::waitid(WaitId::PidFd(process.pidfd.as_fd()), WaitIdOptions::EXITED)?; drop(child); drop(process); Err(error) } #[allow(clippy::disallowed_methods, clippy::disallowed_types)] fn fail_running_captured( process: RetainedProcess, child: std::process::Child, error: io::Error, ) -> io::Result { let _ = process.send_signal(Signal::KILL); rustix::process::waitid(WaitId::PidFd(process.pidfd.as_fd()), WaitIdOptions::EXITED)?; drop(child); drop(process); Err(error) } #[allow(clippy::disallowed_types)] pub(super) fn private_role_command(role: &str) -> std::process::Command { let mut command = std::process::Command::new("/proc/self/exe"); command.arg(PRIVATE_PROCESS_ROLE).arg(role); command } /// Runs a private self-exec role before normal argument handling. /// /// # Errors /// Returns the role's fail-closed error when a private invocation is malformed. pub fn dispatch_private_process_role(arguments: &[OsString]) -> Option> { if arguments .first() .is_none_or(|argument| argument != PRIVATE_PROCESS_ROLE) { return None; } let role = arguments.get(1).and_then(|argument| argument.to_str()); match role { Some(SIGCHLD_PROBE_ROLE) if arguments.len() == 2 => { Some(BarrierWait::from_stdin().and_then(BarrierWait::wait)) } Some(WRAPPER_ROLE) => Some(run_wrapper_role(arguments[2..].to_vec())), Some(MAPPED_WRAPPER_ROLE) => Some(run_mapped_wrapper_role(arguments[2..].to_vec())), _ => Some(Err(io::Error::other( "unknown or malformed private process role", ))), } } #[allow(deprecated)] fn run_mapped_wrapper_role(arguments: Vec) -> io::Result<()> { let (expected_parent, target) = parse_wrapper_arguments(arguments)?; let mut release = [0_u8; BARRIER_RELEASE.len()]; std::io::stdin().read_exact(&mut release)?; if release != *BARRIER_RELEASE { return Err(io::Error::other( "mapped wrapper launch release is malformed", )); } match target.parent_edge { ParentEdge::ParentDeathKill => { arm_parent_death_and_recheck(Signal::KILL, expected_parent)?; } ParentEdge::LifetimePipe => { require_lifetime_pipe_and_recheck(expected_parent)?; } } rustix::thread::unshare(rustix::thread::UnshareFlags::NEWUSER)?; let stdin = std::io::stdin(); rustix::io::write(&stdin, b"u")?; let mut marker = [0_u8; 1]; if rustix::io::read(&stdin, &mut marker)? != 1 || marker != *b"m" { return Err(io::Error::other( "mapped wrapper did not receive a valid map release", )); } rustix::io::fcntl_setfd(&stdin, rustix::io::FdFlags::CLOEXEC)?; rustix::thread::set_thread_res_gid( rustix::process::Gid::ROOT, rustix::process::Gid::ROOT, rustix::process::Gid::ROOT, )?; rustix::thread::set_thread_res_uid( rustix::process::Uid::ROOT, rustix::process::Uid::ROOT, rustix::process::Uid::ROOT, )?; if let Some(stdin) = target.stdin.as_ref() { rustix::stdio::dup2_stdin(stdin)?; } Err(exec_target(target)) } fn run_wrapper_role(arguments: Vec) -> io::Result<()> { let (expected_parent, target) = parse_wrapper_arguments(arguments)?; BarrierWait::from_stdin()?.wait()?; rustix::io::fcntl_setfd(std::io::stdin(), rustix::io::FdFlags::CLOEXEC)?; match target.parent_edge { ParentEdge::ParentDeathKill => { arm_parent_death_and_recheck(Signal::KILL, expected_parent)?; } ParentEdge::LifetimePipe => { require_lifetime_pipe_and_recheck(expected_parent)?; } } if let Some(stdin) = target.stdin.as_ref() { rustix::stdio::dup2_stdin(stdin)?; } Err(exec_target(target)) } fn require_lifetime_pipe_and_recheck( expected_parent: PidfsProcessIdentityV1, ) -> io::Result { let stderr = std::io::stderr(); let mut descriptors = [PollFd::new(&stderr, PollFlags::IN)]; poll(&mut descriptors, Some(&Timespec::default()))?; if descriptors[0] .revents() .intersects(PollFlags::IN | PollFlags::HUP | PollFlags::ERR) { return Err(io::Error::other("parent lifetime pipe is not held")); } let parent = recover_process(expected_parent)?; let observed_parent = rustix::process::getppid() .map(Pid::as_raw_nonzero) .map(i32::from) .and_then(|pid| u32::try_from(pid).ok()); if observed_parent != Some(expected_parent.host_pid().get()) { return Err(io::Error::other( "parent changed while arming lifetime pipe", )); } Ok(parent) } #[allow(clippy::disallowed_methods)] fn acquire_spawned_child(host_pid: u32) -> io::Result { let raw_pid = i32::try_from(host_pid) .map_err(|_| io::Error::other("spawn returned out-of-range child PID"))?; let pid = Pid::from_raw(raw_pid) .ok_or_else(|| io::Error::other("spawn returned invalid child PID"))?; require_host_pid_namespace(host_pid)?; let pidfd = rustix::process::pidfd_open(pid, PidfdFlags::empty())?; verify_direct_child_pidfd(pidfd.as_fd())?; let authority = authority_from_pidfd(pidfd.as_fd(), read_boot_id()?)?; let start_time = read_start_time(host_pid)?; Ok(RetainedProcess { pidfd, identity: PidfsProcessIdentityV1::new( authority, HostPidLocator::new(host_pid), ProcessStartTimeTicks::new(start_time), ), }) } /// # Errors /// Refuses descriptors that do not name a waitable direct child of this broker process. #[allow(clippy::disallowed_methods)] pub fn verify_direct_child_pidfd(pidfd: impl AsFd) -> io::Result<()> { rustix::process::waitid( WaitId::PidFd(pidfd.as_fd()), WaitIdOptions::EXITED | WaitIdOptions::NOHANG | WaitIdOptions::NOWAIT, )?; Ok(()) } #[allow(clippy::disallowed_methods, clippy::disallowed_types)] fn fail_before_pidfd( release: BarrierRelease, child: &mut std::process::Child, error: io::Error, ) -> io::Result { drop(release); child.wait()?; Err(error) } fn fail_after_pidfd( release: BarrierRelease, process: RetainedProcess, error: io::Error, ) -> io::Result { drop(release); fail_after_pidfd_closed(process, error) } #[allow(clippy::disallowed_methods)] fn fail_after_pidfd_closed(process: RetainedProcess, error: io::Error) -> io::Result { rustix::process::waitid(WaitId::PidFd(process.pidfd.as_fd()), WaitIdOptions::EXITED)?; drop(process); Err(error) }