196 lines
6.6 KiB
Rust
196 lines
6.6 KiB
Rust
|
|
/// # 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,
|
||
|
|
)?)
|
||
|
|
}
|