lints

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

rustc.rs (10955B)


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