lints

`rustc` lints.
git clone https://git.philomathiclife.com/repos/lints
Log | Files | Refs | README

rustc.rs (11324B)


      1 extern crate alloc;
      2 use super::{
      3     Error, ExitCode,
      4     io::{self, Write as _},
      5 };
      6 #[cfg(target_os = "openbsd")]
      7 use super::{Permissions, Promises, env};
      8 use alloc::string::FromUtf8Error;
      9 #[cfg(target_os = "openbsd")]
     10 use std::{ffi::OsStr, fs, io::ErrorKind};
     11 use std::{
     12     io::Read as _,
     13     path::{Path, PathBuf},
     14     process::{Command, Stdio},
     15 };
     16 /// Error when executing `rustc -Whelp`.
     17 pub(crate) enum E {
     18     /// I/O error.
     19     Io(Error),
     20     /// Error when there is no `$PATH` variable.
     21     #[cfg(target_os = "openbsd")]
     22     NoPathVariable,
     23     /// Error when `"rustc"` is unable to be located in `$PATH`.
     24     #[cfg(target_os = "openbsd")]
     25     NoRustcInPath,
     26     /// `rustc -Whelp` didn't return a status code, and nothing was written to `stderr`.
     27     NoStatusNoErr,
     28     /// `rustc -Whelp` didn't return a status code, and invalid UTF-8 was written to `stderr`.
     29     NoStatusInvalidUtf8(FromUtf8Error),
     30     /// `rustc -Whelp` didn't return a status code, and the contained `String` was written to `stderr`.
     31     NoStatusErr(String),
     32     /// `rustc -Whelp` returned an error code, but nothing was written to `stderr`.
     33     ErrStatusNoErr(i32),
     34     /// `rustc -Whelp` returned an error code, but `stderr` contained invalid UTF-8.
     35     ErrStatusInvalidUtf8(i32, FromUtf8Error),
     36     /// `rustc -Whelp` returned an error code, and the contained `String` was written to `stderr`.
     37     ErrStatus(i32, String),
     38     /// `rustc -Whelp` returned a success code, but `stderr` contained invalid UTF-8.
     39     SuccessErrInvalidUtf8(FromUtf8Error),
     40     /// `rustc -Whelp` returned a success code, but the contained `String` was written to `stderr`.
     41     SuccessErr(String),
     42 }
     43 impl E {
     44     /// Writes `self` into `stderr`.
     45     pub(crate) fn into_exit_code(self) -> ExitCode {
     46         let mut stderr = io::stderr().lock();
     47         match self {
     48             Self::Io(err) => writeln!(stderr, "I/O issue: {err}."),
     49             #[cfg(target_os = "openbsd")]
     50             Self::NoPathVariable => writeln!(
     51                 stderr,
     52                 "No PATH variable."
     53             ),
     54             #[cfg(target_os = "openbsd")]
     55             Self::NoRustcInPath => writeln!(
     56                 stderr,
     57                 "rustc could not be found based on the PATH variable."
     58             ),
     59             Self::NoStatusNoErr => writeln!(
     60                 stderr,
     61                 "rustc -Whelp didn't return a status code but didn't write anything to stderr."
     62             ),
     63             Self::NoStatusInvalidUtf8(err) => writeln!(
     64                 stderr,
     65                 "rustc -Whelp didn't return a status code, but stderr contained invalid UTF-8: {err}."
     66             ),
     67             Self::NoStatusErr(err) => writeln!(
     68                 stderr,
     69                 "rustc -Whelp didn't return a status code, and the following was written to stderr: {err}."
     70             ),
     71             Self::ErrStatusNoErr(code) => writeln!(
     72                 stderr,
     73                 "rustc -Whelp returned status {code}, but didn't write anything to stderr."
     74             ),
     75             Self::ErrStatusInvalidUtf8(code, err) => writeln!(
     76                 stderr,
     77                 "rustc -Whelp returned status {code}, but stderr contained invalid UTF-8: {err}."
     78             ),
     79             Self::ErrStatus(code, err) => writeln!(
     80                 stderr,
     81                 "rustc -Whelp returned status {code}, and the following was written to stderr: {err}."
     82             ),
     83             Self::SuccessErrInvalidUtf8(err) => writeln!(
     84                 stderr,
     85                 "rustc -Whelp returned a successful status code, but stderr contained invalid UTF-8: {err}."
     86             ),
     87             Self::SuccessErr(err) => writeln!(
     88                 stderr,
     89                 "rustc -Whelp returned a successful status code, but the following was written to stderr: {err}."
     90             ),
     91         }.map_or(ExitCode::FAILURE, |()| ExitCode::FAILURE)
     92     }
     93 }
     94 /// `"rustc"`.
     95 const RUSTC: &str = "rustc";
     96 /// Returns [`RUSTC`] as a `PathBuf`.
     97 #[expect(clippy::unnecessary_wraps, reason = "unify with OpenBSD")]
     98 #[cfg(not(target_os = "openbsd"))]
     99 fn priv_sep<Never>() -> Result<PathBuf, Never> {
    100     Ok(RUSTC.into())
    101 }
    102 /// `unveil(2)`s the file system for read-only permissions.
    103 /// Traverses `$PATH` to find `"rustc"`; when found, removes read permissions on the file system before
    104 /// `unveil(2)`ing `"rustc"` with execute permissions. Last, `pledge(2)`s `c"exec proc stdio unveil"`.
    105 #[expect(unsafe_code, reason = "comment justifies correctness")]
    106 #[expect(clippy::option_if_let_else, reason = "false positive")]
    107 #[cfg(target_os = "openbsd")]
    108 fn priv_sep() -> Result<PathBuf, E> {
    109     Permissions::unveil_raw(c"/", c"r")
    110         .map_err(|e| E::Io(e.into()))
    111         .and_then(|()| {
    112             env::var_os("PATH").map_or(Err(E::NoPathVariable), |path| {
    113                 path.as_encoded_bytes()
    114                     .split(|b| *b == b':')
    115                     .try_fold((), |(), dir| {
    116                         // SAFETY:
    117                         // `dir` is obtained directly from `path.as_encoded_bytes` with at most a single
    118                         // `b':'` removed ensuring any valid UTF-8 that existed before still does.
    119                         let dir_os = unsafe { OsStr::from_encoded_bytes_unchecked(dir) };
    120                         fs::read_dir(dir_os).map_or_else(
    121                             |e| {
    122                                 if matches!(e.kind(), ErrorKind::NotADirectory) {
    123                                     let val = PathBuf::from(dir_os);
    124                                     match val.file_name() {
    125                                         None => Ok(()),
    126                                         Some(file) => {
    127                                             if file == RUSTC {
    128                                                 Err(val)
    129                                             } else {
    130                                                 Ok(())
    131                                             }
    132                                         }
    133                                     }
    134                                 } else {
    135                                     Ok(())
    136                                 }
    137                             },
    138                             |mut ents| {
    139                                 ents.try_fold((), |(), ent_res| {
    140                                     ent_res.map_or(Ok(()), |ent| {
    141                                         if ent.file_name() == RUSTC {
    142                                             Err(PathBuf::from(dir_os).join(RUSTC))
    143                                         } else {
    144                                             Ok(())
    145                                         }
    146                                     })
    147                                 })
    148                             },
    149                         )
    150                     })
    151                     .map_or_else(
    152                         |rustc| {
    153                             Permissions::unveil_raw(c"/", c"")
    154                                 .and_then(|()| {
    155                                     Permissions::unveil_raw(&rustc, c"x").and_then(|()| {
    156                                         Promises::pledge_raw(c"exec proc stdio unveil")
    157                                             .map(|()| rustc)
    158                                     })
    159                                 })
    160                                 .map_err(|e| E::Io(e.into()))
    161                         },
    162                         |()| Err(E::NoRustcInPath),
    163                     )
    164             })
    165         })
    166 }
    167 /// No-op.
    168 #[expect(clippy::unnecessary_wraps, reason = "unify with OpenBSD")]
    169 #[cfg(not(target_os = "openbsd"))]
    170 const fn priv_sep_final<Never>(_: &Path) -> Result<(), Never> {
    171     Ok(())
    172 }
    173 /// Removes execute permissions on `path` before `pledge(2)`ing `c"stdio"`.
    174 #[cfg(target_os = "openbsd")]
    175 fn priv_sep_final(path: &Path) -> Result<(), E> {
    176     Permissions::unveil_raw(path, c"")
    177         .and_then(|()| Promises::pledge_raw(c"stdio"))
    178         .map_err(|e| E::Io(e.into()))
    179 }
    180 /// No-op.
    181 #[expect(clippy::unnecessary_wraps, reason = "unify with OpenBSD")]
    182 #[cfg(not(target_os = "openbsd"))]
    183 const fn priv_sep_stdin<Never>() -> Result<(), Never> {
    184     Ok(())
    185 }
    186 /// `pledge(2)`s `c"stdio"`.
    187 #[cfg(target_os = "openbsd")]
    188 fn priv_sep_stdin() -> Result<(), E> {
    189     Promises::pledge_raw(c"stdio").map_err(|e| E::Io(e.into()))
    190 }
    191 /// Gets output of `rustc -Whelp`.
    192 pub(crate) fn execute(read_stdin: bool) -> Result<Vec<u8>, E> {
    193     if read_stdin {
    194         priv_sep_stdin().and_then(|()| {
    195             #[cfg(target_pointer_width = "16")]
    196             let cap = 0x2000;
    197             #[cfg(not(target_pointer_width = "16"))]
    198             let cap = 0x10000;
    199             let mut output = Vec::with_capacity(cap);
    200             io::stdin()
    201                 .lock()
    202                 .read_to_end(&mut output)
    203                 .map_err(E::Io)
    204                 .map(|_| output)
    205         })
    206     } else {
    207         priv_sep().and_then(|path| {
    208             Command::new(&path)
    209                 .arg("-Whelp")
    210                 .stderr(Stdio::piped())
    211                 .stdin(Stdio::null())
    212                 .stdout(Stdio::piped())
    213                 .output()
    214                 .map_err(E::Io)
    215                 .and_then(|output| {
    216                     priv_sep_final(&path).and_then(|()| match output.status.code() {
    217                         None => {
    218                             if output.stderr.is_empty() {
    219                                 Err(E::NoStatusNoErr)
    220                             } else {
    221                                 String::from_utf8(output.stderr)
    222                                     .map_err(E::NoStatusInvalidUtf8)
    223                                     .and_then(|err| Err(E::NoStatusErr(err)))
    224                             }
    225                         }
    226                         Some(code) => {
    227                             if code == 0i32 {
    228                                 if output.stderr.is_empty() {
    229                                     Ok(output.stdout)
    230                                 } else {
    231                                     String::from_utf8(output.stderr)
    232                                         .map_err(E::SuccessErrInvalidUtf8)
    233                                         .and_then(|err| Err(E::SuccessErr(err)))
    234                                 }
    235                             } else if output.stderr.is_empty() {
    236                                 Err(E::ErrStatusNoErr(code))
    237                             } else {
    238                                 String::from_utf8(output.stderr)
    239                                     .map_err(|err| E::ErrStatusInvalidUtf8(code, err))
    240                                     .and_then(|err| Err(E::ErrStatus(code, err)))
    241                             }
    242                         }
    243                     })
    244                 })
    245         })
    246     }
    247 }
    248 #[cfg(test)]
    249 mod tests {
    250     #[cfg(not(target_os = "openbsd"))]
    251     use core::convert::Infallible;
    252     #[cfg(target_os = "openbsd")]
    253     use std::ffi::OsString;
    254     #[test]
    255     #[cfg(not(target_os = "openbsd"))]
    256     fn priv_sep() {
    257         assert_eq!(super::priv_sep::<Infallible>(), Ok("rustc".into()));
    258     }
    259     #[ignore = "interferes with testing. should run separately to avoid issues."]
    260     #[test]
    261     #[cfg(target_os = "openbsd")]
    262     fn priv_sep() {
    263         assert!(super::priv_sep().is_ok_and(
    264             |path| path.is_absolute() && path.file_name() == Some(&OsString::from("rustc"))
    265         ));
    266     }
    267 }