Replace ExitStatus with our own type in Event

This commit is contained in:
Félix Saparelli 2021-10-22 05:38:48 +13:00
parent 6671863f2f
commit 470cdd698b
No known key found for this signature in database
GPG Key ID: B948C4BAE44FC474
5 changed files with 111 additions and 10 deletions

5
Cargo.lock generated
View File

@ -1033,9 +1033,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.103"
version = "0.2.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd8f7255a17a627354f321ef0055d63b898c6fb27eff628af4d1b66b7331edf6"
checksum = "7b2f96d100e1cf1929e7719b7edb3b90ab5298072638fccd77be9ce942ecdfce"
[[package]]
name = "libgit2-sys"
@ -2619,6 +2619,7 @@ dependencies = [
"git2",
"globset",
"ignore",
"libc",
"miette",
"nom 7.0.0",
"notify",

View File

@ -58,5 +58,8 @@ features = [
version = "0.1.7"
features = ["fs"]
[target.'cfg(unix)'.dependencies]
libc = "0.2.104"
[dev-dependencies]
tracing-subscriber = "0.2.19"

View File

@ -143,7 +143,7 @@ impl Supervisor {
let event = Event {
tags: vec![
Tag::Source(Source::Internal),
Tag::ProcessCompletion(status),
Tag::ProcessCompletion(status.map(|s| s.into())),
],
metadata: Default::default(),
};

View File

@ -9,13 +9,14 @@
use std::{
collections::HashMap,
fmt,
num::{NonZeroI32, NonZeroI64},
path::{Path, PathBuf},
process::ExitStatus,
};
use notify::EventKind;
use crate::signal::source::MainSignal;
use crate::signal::{process::SubSignal, source::MainSignal};
/// An event, as far as watchexec cares about.
#[derive(Clone, Debug, Default, Eq, PartialEq)]
@ -53,8 +54,7 @@ pub enum Tag {
Signal(MainSignal),
/// The event is about the subprocess exiting.
// TODO: replace ExitStatus with something we can de/serialize.
ProcessCompletion(Option<ExitStatus>),
ProcessCompletion(Option<ProcessEnd>),
}
impl Tag {
@ -76,7 +76,6 @@ impl Tag {
/// This is a simplification of the [`std::fs::FileType`] type, which is not constructable and may
/// differ on different platforms.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum FileType {
/// A regular file.
File,
@ -116,6 +115,103 @@ impl fmt::Display for FileType {
}
}
/// The end status of a process.
///
/// This is a sort-of equivalent of the [`std::process::ExitStatus`] type, which is while
/// constructable, differs on various platforms. The native type is an integer that is interpreted
/// either through convention or via platform-dependent libc or kernel calls; our type is a more
/// structured representation for the purpose of being clearer and transportable.
///
/// On Unix, one can tell whether a process dumped core from the exit status; this is not replicated
/// in this structure; if that's desirable you can obtain it manually via `libc::WCOREDUMP` and the
/// `ExitSignal` variant.
///
/// On Unix and Windows, the exit status is a 32-bit integer; on Fuchsia it's a 64-bit integer. For
/// portability, we use `i64`. On all platforms, the "success" value is zero, so we special-case
/// that as a variant and use `NonZeroI*` to niche the other values.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ProcessEnd {
/// The process ended successfully, with exit status = 0.
Success,
/// The process exited with a non-zero exit status.
ExitError(NonZeroI64),
/// The process exited due to a signal.
ExitSignal(SubSignal),
/// The process was stopped (but not terminated) (`libc::WIFSTOPPED`).
ExitStop(NonZeroI32),
/// The process suffered an unhandled exception or warning (typically Windows only).
Exception(NonZeroI32),
/// The process was continued (`libc::WIFCONTINUED`).
Continued,
}
impl From<ExitStatus> for ProcessEnd {
#[cfg(target_os = "fuchsia")]
fn from(es: ExitStatus) -> Self {
// Once https://github.com/rust-lang/rust/pull/88300 (unix_process_wait_more) lands, use
// that API instead of doing the transmute, and clean up the forbid condition at crate root.
let raw: i64 = unsafe { std::mem::transmute(es) };
NonZeroI64::try_from(raw)
.map(Self::ExitError)
.unwrap_or(Self::Success)
}
#[cfg(all(unix, not(target_os = "fuchsia")))]
fn from(es: ExitStatus) -> Self {
use std::os::unix::process::ExitStatusExt;
match (es.code(), es.signal()) {
(Some(_), Some(_)) => {
unreachable!("exitstatus cannot both be code and signal?!")
}
(Some(code), None) => match NonZeroI64::try_from(i64::from(code)) {
Ok(code) => Self::ExitError(code),
Err(_) if cfg!(debug_assertions) => {
unreachable!("exitstatus code cannot be zero?!")
}
Err(_) => Self::Success,
},
// TODO: once unix_process_wait_more lands, use stopped_signal() instead and clear the libc dep
(None, Some(signal)) if libc::WIFSTOPPED(-signal) => {
match NonZeroI32::try_from(libc::WSTOPSIG(-signal)) {
Ok(signal) => Self::ExitStop(signal),
Err(_) if cfg!(debug_assertions) => {
unreachable!("exitsignal code cannot be zero?!")
}
Err(_) => Self::Success,
}
}
// TODO: once unix_process_wait_more lands, use continued() instead and clear the libc dep
#[cfg(not(target_os = "vxworks"))]
(None, Some(signal)) if libc::WIFCONTINUED(-signal) => Self::Continued,
(None, Some(signal)) => Self::ExitSignal(signal.into()),
(None, None) => Self::Success,
}
}
#[cfg(windows)]
fn from(es: ExitStatus) -> Self {
match es.code().map(NonZeroI32::try_from) {
None | Some(Err(_)) => Self::Success,
Some(Ok(code)) if code & 0x80000000 != 0 => Self::Exception(code),
Some(Ok(code)) => Self::ExitError(code.into()),
}
}
#[cfg(not(any(unix, windows)))]
fn from(es: ExitStatus) -> Self {
if es.success() {
Self::Success
} else {
Self::ExitError(NonZeroI64::new(1).unwrap())
}
}
}
/// The general origin of the event.
///
/// This is set by the event source. Note that not all of these are currently used.
@ -188,7 +284,7 @@ impl Event {
}
/// Return all process completions in the event's tags.
pub fn completions(&self) -> impl Iterator<Item = Option<ExitStatus>> + '_ {
pub fn completions(&self) -> impl Iterator<Item = Option<ProcessEnd>> + '_ {
self.tags.iter().filter_map(|p| match p {
Tag::ProcessCompletion(s) => Some(*s),
_ => None,
@ -212,7 +308,7 @@ impl fmt::Display for Event {
Tag::Process(p) => write!(f, " process={}", p)?,
Tag::Signal(s) => write!(f, " signal={:?}", s)?,
Tag::ProcessCompletion(None) => write!(f, " command-completed")?,
Tag::ProcessCompletion(Some(c)) => write!(f, " command-completed({})", c)?,
Tag::ProcessCompletion(Some(c)) => write!(f, " command-completed({:?})", c)?,
}
}

View File

@ -95,7 +95,8 @@
#![doc(html_favicon_url = "https://watchexec.github.io/logo:watchexec.svg")]
#![doc(html_logo_url = "https://watchexec.github.io/logo:watchexec.svg")]
#![warn(clippy::unwrap_used, missing_docs)]
#![forbid(unsafe_code)]
#![cfg_attr(not(target_os = "fuchsia"), forbid(unsafe_code))]
// see event::ProcessEnd for why this is disabled on fuchsia
// the toolkit to make your own
pub mod action;