diff --git a/library/std/src/sys/unix/process/process_unix.rs b/library/std/src/sys/unix/process/process_unix.rs index 3963e7f52d55..7d10a16cfd16 100644 --- a/library/std/src/sys/unix/process/process_unix.rs +++ b/library/std/src/sys/unix/process/process_unix.rs @@ -1061,3 +1061,8 @@ impl crate::os::linux::process::ChildExt for crate::process::Child { #[cfg(test)] #[path = "process_unix/tests.rs"] mod tests; + +// See [`process_unsupported_wait_status::compare_with_linux`]; +#[cfg(all(test, target_os = "linux"))] +#[path = "process_unsupported/wait_status.rs"] +mod process_unsupported_wait_status; diff --git a/library/std/src/sys/unix/process/process_unsupported.rs b/library/std/src/sys/unix/process/process_unsupported.rs index 8e0b971af731..325d3f23be7c 100644 --- a/library/std/src/sys/unix/process/process_unsupported.rs +++ b/library/std/src/sys/unix/process/process_unsupported.rs @@ -55,56 +55,8 @@ impl Process { } } -#[derive(PartialEq, Eq, Clone, Copy, Debug, Default)] -pub struct ExitStatus(c_int); - -impl ExitStatus { - #[cfg_attr(target_os = "horizon", allow(unused))] - pub fn success(&self) -> bool { - self.code() == Some(0) - } - - pub fn exit_ok(&self) -> Result<(), ExitStatusError> { - Err(ExitStatusError(1.try_into().unwrap())) - } - - pub fn code(&self) -> Option { - None - } - - pub fn signal(&self) -> Option { - None - } - - pub fn core_dumped(&self) -> bool { - false - } - - pub fn stopped_signal(&self) -> Option { - None - } - - pub fn continued(&self) -> bool { - false - } - - pub fn into_raw(&self) -> c_int { - 0 - } -} - -/// Converts a raw `c_int` to a type-safe `ExitStatus` by wrapping it without copying. -impl From for ExitStatus { - fn from(a: c_int) -> ExitStatus { - ExitStatus(a as i32) - } -} - -impl fmt::Display for ExitStatus { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "exit code: {}", self.0) - } -} +mod wait_status; +pub use wait_status::ExitStatus; #[derive(PartialEq, Eq, Clone, Copy, Debug)] pub struct ExitStatusError(NonZero_c_int); diff --git a/library/std/src/sys/unix/process/process_unsupported/wait_status.rs b/library/std/src/sys/unix/process/process_unsupported/wait_status.rs new file mode 100644 index 000000000000..30fc78db9a18 --- /dev/null +++ b/library/std/src/sys/unix/process/process_unsupported/wait_status.rs @@ -0,0 +1,113 @@ +//! Emulated wait status for non-Unix #[cfg(unix) platforms +//! +//! Separate module to facilitate testing against a real Unix implementation. + +use crate::ffi::c_int; +use crate::fmt; + +/// Emulated wait status for use by `process_unsupported.rs` +/// +/// Uses the "traditional unix" encoding. For use on platfors which are `#[cfg(unix)]` +/// but do not actually support subprocesses at all. +/// +/// These platforms aren't Unix, but are simply pretending to be for porting convenience. +/// So, we provide a faithful pretence here. +#[derive(PartialEq, Eq, Clone, Copy, Debug, Default)] +pub struct ExitStatus { + wait_status: c_int, +} + +/// Converts a raw `c_int` to a type-safe `ExitStatus` by wrapping it +impl From for ExitStatus { + fn from(wait_status: c_int) -> ExitStatus { + ExitStatus { wait_status } + } +} + +impl fmt::Display for ExitStatus { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "emulated wait status: {}", self.wait_status) + } +} + +impl ExitStatus { + pub fn code(&self) -> Option { + // Linux and FreeBSD both agree that values linux 0x80 + // count as "WIFEXITED" even though this is quite mad. + // Likewise the macros disregard all the high bits, so are happy to declare + // out-of-range values to be WIFEXITED, WIFSTOPPED, etc. + let w = self.wait_status; + if (w & 0x7f) == 0 { + Some((w & 0xff00) >> 8) + } else { + None + } + } + + pub fn signal(&self) -> Option { + let signal = self.wait_status & 0x007f; + if signal > 0 && signal < 0x7f { + Some(signal) + } else { + None + } + } + + pub fn core_dumped(&self) -> bool { + self.signal().is_some() && (self.wait_status & 0x80) != 0 + } + + pub fn stopped_signal(&self) -> Option { + let w = self.wait_status; + if (w & 0xff) == 0x7f { + Some((w & 0xff00) >> 8) + } else { + None + } + } + + pub fn continued(&self) -> bool { + self.wait_status == 0xffff + } + + pub fn into_raw(&self) -> c_int { + self.wait_status + } +} + +// Test that our emulation exactly matches Linux +// +// This test runs *on Linux* but it tests +// the implementation used on non-Unix `#[cfg(unix)]` platforms. +// +// I.e. we're using Linux as a proxy for "trad unix". +#[cfg(all(test, target_os = "linux"))] +mod compare_with_linux { + use super::ExitStatus as Emulated; + use crate::process::ExitStatus as Real; + use crate::os::unix::process::ExitStatusExt as _; + + #[test] + fn compare() { + // Check that we handle out-of-range values similarly, too. + for wstatus in -0xf_ffff..0xf_ffff { + let emulated = Emulated::from(wstatus); + let real = Real::from_raw(wstatus); + + macro_rules! compare { { $method:ident } => { + assert_eq!( + emulated.$method(), + real.$method(), + "{wstatus:#x}.{}()", + stringify!($method), + ); + } } + compare!(code); + compare!(signal); + compare!(core_dumped); + compare!(stopped_signal); + compare!(continued); + compare!(into_raw); + } + } +}