/// # Errors /// Returns an error when the fail-closed pipe cannot be created. pub fn launch_barrier() -> io::Result<(BarrierRelease, BarrierWait)> { let (reader, writer) = pipe_with(PipeFlags::empty())?; fcntl_setfd(writer.as_fd(), FdFlags::CLOEXEC)?; Ok(( BarrierRelease(File::from(writer)), BarrierWait(File::from(reader)), )) } #[derive(Debug)] pub struct BarrierRelease(File); impl BarrierRelease { /// # Errors /// Returns an error unless the complete release token is written. pub fn release(mut self) -> io::Result<()> { self.0.write_all(BARRIER_RELEASE)?; self.0.flush() } } #[derive(Debug)] pub struct BarrierWait(File); impl BarrierWait { #[must_use] pub fn into_stdio(self) -> Stdio { Stdio::from(self.0) } /// # Errors /// Returns an error when the inherited standard-input descriptor cannot be duplicated. pub fn from_stdin() -> io::Result { Ok(Self(File::open("/proc/self/fd/0")?)) } /// # Errors /// Refuses EOF, partial, altered, or overlong releases. pub fn wait(mut self) -> io::Result<()> { let mut release = [0_u8; BARRIER_RELEASE.len()]; self.0.read_exact(&mut release)?; let mut excess = [0_u8; 1]; if release != *BARRIER_RELEASE || self.0.read(&mut excess)? != 0 { return Err(io::Error::other("malformed launch-barrier release")); } Ok(()) } } #[allow(clippy::disallowed_types)] pub fn append_parent_identity( command: &mut std::process::Command, identity: PidfsProcessIdentityV1, ) { let authority = identity.authority(); command .arg("--parent-boot") .arg(hex(authority.boot_id.as_bytes())) .arg("--parent-device") .arg(authority.pidfs_st_dev.get().to_string()) .arg("--parent-inode") .arg(authority.pidfs_st_ino.get().to_string()) .arg("--parent-pid") .arg(identity.host_pid().get().to_string()) .arg("--parent-start") .arg(identity.start_time_ticks().get().to_string()); } /// # Errors /// Refuses missing, reordered, malformed, or incomplete wrapper arguments. pub fn parse_wrapper_arguments( arguments: impl IntoIterator, ) -> io::Result<(PidfsProcessIdentityV1, TargetCommand)> { let mut arguments = arguments.into_iter(); let boot = named_argument(&mut arguments, "--parent-boot")?; let device = number_argument(&mut arguments, "--parent-device")?; let inode = number_argument(&mut arguments, "--parent-inode")?; let pid = number_argument(&mut arguments, "--parent-pid")?; let start = number_argument(&mut arguments, "--parent-start")?; let parent_edge = match named_argument(&mut arguments, "--parent-action")?.to_str() { Some("kill") => ParentEdge::ParentDeathKill, Some("lifetime-pipe") => ParentEdge::LifetimePipe, _ => return Err(io::Error::other("wrapper parent action is invalid")), }; let next = arguments.next(); let stdin = if next.as_deref() == Some(OsStr::new("--target-stdin-fd")) { let descriptor = arguments .next() .and_then(|value| value.to_str().and_then(|value| value.parse::().ok())) .filter(|descriptor| *descriptor > 2) .ok_or_else(|| io::Error::other("wrapper target stdin descriptor is invalid"))?; let access = TargetStdinAccess::from_descriptor(descriptor)?; if arguments.next().as_deref() != Some(OsStr::new("--")) { return Err(io::Error::other("wrapper target separator is missing")); } Some(access.reopen(descriptor)?) } else if next.as_deref() == Some(OsStr::new("--")) { None } else { return Err(io::Error::other("wrapper target separator is missing")); }; let program = arguments .next() .ok_or_else(|| io::Error::other("wrapper target is missing"))?; let target = TargetCommand { program, arguments: arguments.collect(), parent_edge, executable: None, stdin, }; let boot = parse_boot_hex(&boot)?; Ok(( PidfsProcessIdentityV1::new( PidfsProcessAuthorityV1 { boot_id: boot, pidfs_st_dev: PidfsDeviceId::new(device), pidfs_st_ino: PidfsInodeId::new(inode), }, HostPidLocator::new( u32::try_from(pid).map_err(|_| io::Error::other("parent PID is out of range"))?, ), ProcessStartTimeTicks::new(start), ), target, )) } fn named_argument( arguments: &mut impl Iterator, name: &str, ) -> io::Result { if arguments.next().as_deref() != Some(OsStr::new(name)) { return Err(io::Error::other(format!( "wrapper argument {name} is missing" ))); } arguments .next() .ok_or_else(|| io::Error::other(format!("wrapper argument {name} has no value"))) } fn number_argument(arguments: &mut impl Iterator, name: &str) -> io::Result { named_argument(arguments, name)? .to_str() .and_then(|value| value.parse().ok()) .ok_or_else(|| io::Error::other(format!("wrapper argument {name} is invalid"))) } fn parse_boot_hex(value: &OsStr) -> io::Result { let value = value .to_str() .ok_or_else(|| io::Error::other("parent boot ID is not UTF-8"))?; if value.len() != 32 || !value.bytes().all(|byte| byte.is_ascii_hexdigit()) { return Err(io::Error::other("parent boot ID is invalid")); } let mut bytes = [0_u8; 16]; for (index, byte) in bytes.iter_mut().enumerate() { *byte = u8::from_str_radix(&value[index * 2..index * 2 + 2], 16) .map_err(|_| io::Error::other("parent boot ID is invalid"))?; } Ok(BootId::new(bytes)) } fn hex(bytes: &[u8]) -> String { use std::fmt::Write as _; bytes.iter().fold(String::new(), |mut output, byte| { write!(output, "{byte:02x}").expect("writing to a String cannot fail"); output }) } #[allow(clippy::disallowed_methods, clippy::disallowed_types)] #[must_use] pub fn exec_target(target: TargetCommand) -> io::Error { use std::os::unix::process::CommandExt; std::process::Command::new(target.program) .args(target.arguments) .exec() } /// Stops this process until the host resumes it. /// /// # Errors /// Returns an error when the kernel refuses the stop signal. #[allow(clippy::disallowed_methods)] pub fn suspend_current_process() -> io::Result<()> { Ok(rustix::process::kill_process( rustix::process::getpid(), Signal::STOP, )?) }