lazaret/crates/lazaret-linux/src/process/launch.rs

196 lines
6.6 KiB
Rust
Raw Normal View History

/// # 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<Self> {
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<Item = OsString>,
) -> 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::<i32>().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<Item = OsString>,
name: &str,
) -> io::Result<OsString> {
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<Item = OsString>, name: &str) -> io::Result<u64> {
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<BootId> {
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,
)?)
}