watchexec_events/
process.rs

1use std::{
2	num::{NonZeroI32, NonZeroI64},
3	process::ExitStatus,
4};
5
6use watchexec_signals::Signal;
7
8/// The end status of a process.
9///
10/// This is a sort-of equivalent of the [`std::process::ExitStatus`] type which, while
11/// constructable, differs on various platforms. The native type is an integer that is interpreted
12/// either through convention or via platform-dependent libc or kernel calls; our type is a more
13/// structured representation for the purpose of being clearer and transportable.
14///
15/// On Unix, one can tell whether a process dumped core from the exit status; this is not replicated
16/// in this structure; if that's desirable you can obtain it manually via `libc::WCOREDUMP` and the
17/// `ExitSignal` variant.
18///
19/// On Unix and Windows, the exit status is a 32-bit integer; on Fuchsia it's a 64-bit integer. For
20/// portability, we use `i64`. On all platforms, the "success" value is zero, so we special-case
21/// that as a variant and use `NonZeroI*` to limit the other values.
22#[derive(Clone, Copy, Debug, Eq, PartialEq)]
23#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
24#[cfg_attr(feature = "serde", serde(tag = "disposition", content = "code"))]
25pub enum ProcessEnd {
26	/// The process ended successfully, with exit status = 0.
27	#[cfg_attr(feature = "serde", serde(rename = "success"))]
28	Success,
29
30	/// The process exited with a non-zero exit status.
31	#[cfg_attr(feature = "serde", serde(rename = "error"))]
32	ExitError(NonZeroI64),
33
34	/// The process exited due to a signal.
35	#[cfg_attr(feature = "serde", serde(rename = "signal"))]
36	ExitSignal(Signal),
37
38	/// The process was stopped (but not terminated) (`libc::WIFSTOPPED`).
39	#[cfg_attr(feature = "serde", serde(rename = "stop"))]
40	ExitStop(NonZeroI32),
41
42	/// The process suffered an unhandled exception or warning (typically Windows only).
43	#[cfg_attr(feature = "serde", serde(rename = "exception"))]
44	Exception(NonZeroI32),
45
46	/// The process was continued (`libc::WIFCONTINUED`).
47	#[cfg_attr(feature = "serde", serde(rename = "continued"))]
48	Continued,
49}
50
51impl From<ExitStatus> for ProcessEnd {
52	#[cfg(unix)]
53	fn from(es: ExitStatus) -> Self {
54		use std::os::unix::process::ExitStatusExt;
55
56		match (es.code(), es.signal(), es.stopped_signal()) {
57			(Some(_), Some(_), _) => {
58				unreachable!("exitstatus cannot both be code and signal?!")
59			}
60			(Some(code), None, _) => {
61				NonZeroI64::try_from(i64::from(code)).map_or(Self::Success, Self::ExitError)
62			}
63			(None, Some(_), Some(stopsig)) => {
64				NonZeroI32::try_from(stopsig).map_or(Self::Success, Self::ExitStop)
65			}
66			#[cfg(not(target_os = "vxworks"))]
67			(None, Some(_), _) if es.continued() => Self::Continued,
68			(None, Some(signal), _) => Self::ExitSignal(signal.into()),
69			(None, None, _) => Self::Success,
70		}
71	}
72
73	#[cfg(windows)]
74	fn from(es: ExitStatus) -> Self {
75		match es.code().map(NonZeroI32::try_from) {
76			None | Some(Err(_)) => Self::Success,
77			Some(Ok(code)) if code.get() < 0 => Self::Exception(code),
78			Some(Ok(code)) => Self::ExitError(code.into()),
79		}
80	}
81
82	#[cfg(not(any(unix, windows)))]
83	fn from(es: ExitStatus) -> Self {
84		if es.success() {
85			Self::Success
86		} else {
87			Self::ExitError(NonZeroI64::new(1).unwrap())
88		}
89	}
90}
91
92impl ProcessEnd {
93	/// Convert a `ProcessEnd` to an `ExitStatus`.
94	///
95	/// This is a testing function only! **It will panic** if the `ProcessEnd` is not representable
96	/// as an `ExitStatus` on Unix. This is also not guaranteed to be accurate, as the `waitpid()`
97	/// status union is platform-specific. Exit codes and signals are implemented, other variants
98	/// are not.
99	#[cfg(unix)]
100	#[must_use]
101	pub fn into_exitstatus(self) -> ExitStatus {
102		use std::os::unix::process::ExitStatusExt;
103		match self {
104			Self::Success => ExitStatus::from_raw(0),
105			Self::ExitError(code) => {
106				ExitStatus::from_raw(i32::from(u8::try_from(code.get()).unwrap_or_default()) << 8)
107			}
108			Self::ExitSignal(signal) => {
109				ExitStatus::from_raw(signal.to_nix().map_or(0, |sig| sig as i32))
110			}
111			Self::Continued => ExitStatus::from_raw(0xffff),
112			_ => unimplemented!(),
113		}
114	}
115
116	/// Convert a `ProcessEnd` to an `ExitStatus`.
117	///
118	/// This is a testing function only! **It will panic** if the `ProcessEnd` is not representable
119	/// as an `ExitStatus` on Windows.
120	#[cfg(windows)]
121	#[must_use]
122	pub fn into_exitstatus(self) -> ExitStatus {
123		use std::os::windows::process::ExitStatusExt;
124		match self {
125			Self::Success => ExitStatus::from_raw(0),
126			Self::ExitError(code) => ExitStatus::from_raw(code.get().try_into().unwrap()),
127			_ => unimplemented!(),
128		}
129	}
130
131	/// Unimplemented on this platform.
132	#[cfg(not(any(unix, windows)))]
133	#[must_use]
134	pub fn into_exitstatus(self) -> ExitStatus {
135		unimplemented!()
136	}
137}