extern crate alloc;
use super::{
Error, ExitCode,
io::{self, Write as _},
};
#[cfg(target_os = "openbsd")]
use super::{Permissions, Promises, env};
use alloc::string::FromUtf8Error;
#[cfg(target_os = "openbsd")]
use std::{ffi::OsStr, fs, io::ErrorKind};
use std::{
io::Read as _,
path::{Path, PathBuf},
process::{Command, Stdio},
};
pub(crate) enum E {
Io(Error),
#[cfg(target_os = "openbsd")]
NoPathVariable,
#[cfg(target_os = "openbsd")]
NoRustcInPath,
NoStatusNoErr,
NoStatusInvalidUtf8(FromUtf8Error),
NoStatusErr(String),
ErrStatusNoErr(i32),
ErrStatusInvalidUtf8(i32, FromUtf8Error),
ErrStatus(i32, String),
SuccessErrInvalidUtf8(FromUtf8Error),
SuccessErr(String),
}
impl E {
pub(crate) fn into_exit_code(self) -> ExitCode {
let mut stderr = io::stderr().lock();
match self {
Self::Io(err) => writeln!(stderr, "I/O issue: {err}."),
#[cfg(target_os = "openbsd")]
Self::NoPathVariable => writeln!(
stderr,
"No PATH variable."
),
#[cfg(target_os = "openbsd")]
Self::NoRustcInPath => writeln!(
stderr,
"rustc could not be found based on the PATH variable."
),
Self::NoStatusNoErr => writeln!(
stderr,
"rustc -Whelp didn't return a status code but didn't write anything to stderr."
),
Self::NoStatusInvalidUtf8(err) => writeln!(
stderr,
"rustc -Whelp didn't return a status code, but stderr contained invalid UTF-8: {err}."
),
Self::NoStatusErr(err) => writeln!(
stderr,
"rustc -Whelp didn't return a status code, and the following was written to stderr: {err}."
),
Self::ErrStatusNoErr(code) => writeln!(
stderr,
"rustc -Whelp returned status {code}, but didn't write anything to stderr."
),
Self::ErrStatusInvalidUtf8(code, err) => writeln!(
stderr,
"rustc -Whelp returned status {code}, but stderr contained invalid UTF-8: {err}."
),
Self::ErrStatus(code, err) => writeln!(
stderr,
"rustc -Whelp returned status {code}, and the following was written to stderr: {err}."
),
Self::SuccessErrInvalidUtf8(err) => writeln!(
stderr,
"rustc -Whelp returned a successful status code, but stderr contained invalid UTF-8: {err}."
),
Self::SuccessErr(err) => writeln!(
stderr,
"rustc -Whelp returned a successful status code, but the following was written to stderr: {err}."
),
}.map_or(ExitCode::FAILURE, |()| ExitCode::FAILURE)
}
}
const RUSTC: &str = "rustc";
#[expect(clippy::unnecessary_wraps, reason = "unify with OpenBSD")]
#[cfg(not(target_os = "openbsd"))]
fn priv_sep<Never>() -> Result<PathBuf, Never> {
Ok(RUSTC.into())
}
#[expect(unsafe_code, reason = "comment justifies correctness")]
#[expect(clippy::option_if_let_else, reason = "false positive")]
#[cfg(target_os = "openbsd")]
fn priv_sep() -> Result<PathBuf, E> {
Permissions::unveil_raw(c"/", c"r")
.map_err(|e| E::Io(e.into()))
.and_then(|()| {
env::var_os("PATH").map_or(Err(E::NoPathVariable), |path| {
path.as_encoded_bytes()
.split(|b| *b == b':')
.try_fold((), |(), dir| {
let dir_os = unsafe { OsStr::from_encoded_bytes_unchecked(dir) };
fs::read_dir(dir_os).map_or_else(
|e| {
if matches!(e.kind(), ErrorKind::NotADirectory) {
let val = PathBuf::from(dir_os);
match val.file_name() {
None => Ok(()),
Some(file) => {
if file == RUSTC {
Err(val)
} else {
Ok(())
}
}
}
} else {
Ok(())
}
},
|mut ents| {
ents.try_fold((), |(), ent_res| {
ent_res.map_or(Ok(()), |ent| {
if ent.file_name() == RUSTC {
Err(PathBuf::from(dir_os).join(RUSTC))
} else {
Ok(())
}
})
})
},
)
})
.map_or_else(
|rustc| {
Permissions::unveil_raw(c"/", c"")
.and_then(|()| {
Permissions::unveil_raw(&rustc, c"x").and_then(|()| {
Promises::pledge_raw(c"exec proc stdio unveil")
.map(|()| rustc)
})
})
.map_err(|e| E::Io(e.into()))
},
|()| Err(E::NoRustcInPath),
)
})
})
}
#[expect(clippy::unnecessary_wraps, reason = "unify with OpenBSD")]
#[cfg(not(target_os = "openbsd"))]
const fn priv_sep_final<Never>(_: &Path) -> Result<(), Never> {
Ok(())
}
#[cfg(target_os = "openbsd")]
fn priv_sep_final(path: &Path) -> Result<(), E> {
Permissions::unveil_raw(path, c"")
.and_then(|()| Promises::pledge_raw(c"stdio"))
.map_err(|e| E::Io(e.into()))
}
#[expect(clippy::unnecessary_wraps, reason = "unify with OpenBSD")]
#[cfg(not(target_os = "openbsd"))]
const fn priv_sep_stdin<Never>() -> Result<(), Never> {
Ok(())
}
#[cfg(target_os = "openbsd")]
fn priv_sep_stdin() -> Result<(), E> {
Promises::pledge_raw(c"stdio").map_err(|e| E::Io(e.into()))
}
pub(crate) fn execute(read_stdin: bool) -> Result<Vec<u8>, E> {
if read_stdin {
priv_sep_stdin().and_then(|()| {
let mut output = Vec::with_capacity(0x8000);
io::stdin()
.lock()
.read_to_end(&mut output)
.map_err(E::Io)
.map(|_| output)
})
} else {
priv_sep().and_then(|path| {
Command::new(&path)
.arg("-Whelp")
.stderr(Stdio::piped())
.stdin(Stdio::null())
.stdout(Stdio::piped())
.output()
.map_err(E::Io)
.and_then(|output| {
priv_sep_final(&path).and_then(|()| match output.status.code() {
None => {
if output.stderr.is_empty() {
Err(E::NoStatusNoErr)
} else {
String::from_utf8(output.stderr)
.map_err(E::NoStatusInvalidUtf8)
.and_then(|err| Err(E::NoStatusErr(err)))
}
}
Some(code) => {
if code == 0i32 {
if output.stderr.is_empty() {
Ok(output.stdout)
} else {
String::from_utf8(output.stderr)
.map_err(E::SuccessErrInvalidUtf8)
.and_then(|err| Err(E::SuccessErr(err)))
}
} else if output.stderr.is_empty() {
Err(E::ErrStatusNoErr(code))
} else {
String::from_utf8(output.stderr)
.map_err(|err| E::ErrStatusInvalidUtf8(code, err))
.and_then(|err| Err(E::ErrStatus(code, err)))
}
}
})
})
})
}
}
#[cfg(test)]
mod tests {
#[cfg(not(target_os = "openbsd"))]
use core::convert::Infallible;
#[cfg(target_os = "openbsd")]
use std::ffi::OsString;
#[test]
#[cfg(not(target_os = "openbsd"))]
fn priv_sep() {
assert_eq!(super::priv_sep::<Infallible>(), Ok("rustc".into()));
}
#[ignore = "interferes with testing. should run separately to avoid issues."]
#[test]
#[cfg(target_os = "openbsd")]
fn priv_sep() {
assert!(super::priv_sep().is_ok_and(
|path| path.is_absolute() && path.file_name() == Some(&OsString::from("rustc"))
));
}
}