summaryrefslogtreecommitdiffstats
path: root/third_party/rust/lucet-runtime-internals-wasmsbx/src/instance
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
commit2aa4a82499d4becd2284cdb482213d541b8804dd (patch)
treeb80bf8bf13c3766139fbacc530efd0dd9d54394c /third_party/rust/lucet-runtime-internals-wasmsbx/src/instance
parentInitial commit. (diff)
downloadfirefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz
firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/lucet-runtime-internals-wasmsbx/src/instance')
-rw-r--r--third_party/rust/lucet-runtime-internals-wasmsbx/src/instance/siginfo_ext.c6
-rw-r--r--third_party/rust/lucet-runtime-internals-wasmsbx/src/instance/siginfo_ext.rs15
-rw-r--r--third_party/rust/lucet-runtime-internals-wasmsbx/src/instance/signals.rs447
-rw-r--r--third_party/rust/lucet-runtime-internals-wasmsbx/src/instance/state.rs178
4 files changed, 646 insertions, 0 deletions
diff --git a/third_party/rust/lucet-runtime-internals-wasmsbx/src/instance/siginfo_ext.c b/third_party/rust/lucet-runtime-internals-wasmsbx/src/instance/siginfo_ext.c
new file mode 100644
index 0000000000..4dd9b58b4c
--- /dev/null
+++ b/third_party/rust/lucet-runtime-internals-wasmsbx/src/instance/siginfo_ext.c
@@ -0,0 +1,6 @@
+#include <signal.h>
+
+void *siginfo_si_addr(siginfo_t *si)
+{
+ return si->si_addr;
+}
diff --git a/third_party/rust/lucet-runtime-internals-wasmsbx/src/instance/siginfo_ext.rs b/third_party/rust/lucet-runtime-internals-wasmsbx/src/instance/siginfo_ext.rs
new file mode 100644
index 0000000000..863d8a8684
--- /dev/null
+++ b/third_party/rust/lucet-runtime-internals-wasmsbx/src/instance/siginfo_ext.rs
@@ -0,0 +1,15 @@
+use libc::{c_void, siginfo_t};
+
+extern "C" {
+ fn siginfo_si_addr(si: *const siginfo_t) -> *const c_void;
+}
+
+pub trait SiginfoExt {
+ fn si_addr_ext(&self) -> *const c_void;
+}
+
+impl SiginfoExt for siginfo_t {
+ fn si_addr_ext(&self) -> *const c_void {
+ unsafe { siginfo_si_addr(self as *const siginfo_t) }
+ }
+}
diff --git a/third_party/rust/lucet-runtime-internals-wasmsbx/src/instance/signals.rs b/third_party/rust/lucet-runtime-internals-wasmsbx/src/instance/signals.rs
new file mode 100644
index 0000000000..d52be927c7
--- /dev/null
+++ b/third_party/rust/lucet-runtime-internals-wasmsbx/src/instance/signals.rs
@@ -0,0 +1,447 @@
+use crate::context::Context;
+use crate::error::Error;
+use crate::instance::{
+ siginfo_ext::SiginfoExt, FaultDetails, Instance, State, TerminationDetails, CURRENT_INSTANCE,
+ HOST_CTX,
+};
+use crate::sysdeps::UContextPtr;
+use lazy_static::lazy_static;
+use libc::{c_int, c_void, siginfo_t, SIGBUS, SIGSEGV};
+use lucet_module::TrapCode;
+use nix::sys::signal::{
+ pthread_sigmask, raise, sigaction, SaFlags, SigAction, SigHandler, SigSet, SigmaskHow, Signal,
+};
+use std::mem::MaybeUninit;
+use std::panic;
+use std::sync::{Arc, Mutex};
+
+lazy_static! {
+ // TODO: work out an alternative to this that is signal-safe for `reraise_host_signal_in_handler`
+ static ref LUCET_SIGNAL_STATE: Mutex<Option<SignalState>> = Mutex::new(None);
+}
+
+/// The value returned by
+/// [`Instance.signal_handler`](struct.Instance.html#structfield.signal_handler) to determine the
+/// outcome of a handled signal.
+pub enum SignalBehavior {
+ /// Use default behavior, which switches back to the host with `State::Fault` populated.
+ Default,
+ /// Override default behavior and cause the instance to continue.
+ Continue,
+ /// Override default behavior and cause the instance to terminate.
+ Terminate,
+}
+
+pub type SignalHandler = dyn Fn(
+ &Instance,
+ &Option<TrapCode>,
+ libc::c_int,
+ *const siginfo_t,
+ *const c_void,
+) -> SignalBehavior;
+
+pub fn signal_handler_none(
+ _inst: &Instance,
+ _trapcode: &Option<TrapCode>,
+ _signum: libc::c_int,
+ _siginfo_ptr: *const siginfo_t,
+ _ucontext_ptr: *const c_void,
+) -> SignalBehavior {
+ SignalBehavior::Default
+}
+
+impl Instance {
+ pub(crate) fn with_signals_on<F, R>(&mut self, f: F) -> Result<R, Error>
+ where
+ F: FnOnce(&mut Instance) -> Result<R, Error>,
+ {
+ // Set up the signal stack for this thread. Note that because signal stacks are per-thread,
+ // rather than per-process, we do this for every run, while the signal handler is installed
+ // only once per process.
+ let guest_sigstack = SigStack::new(
+ self.alloc.slot().sigstack,
+ SigStackFlags::empty(),
+ self.alloc.slot().limits.signal_stack_size,
+ );
+ let previous_sigstack = unsafe { sigaltstack(Some(guest_sigstack)) }
+ .expect("enabling or changing the signal stack succeeds");
+ if let Some(previous_sigstack) = previous_sigstack {
+ assert!(
+ !previous_sigstack
+ .flags()
+ .contains(SigStackFlags::SS_ONSTACK),
+ "an instance was created with a signal stack"
+ );
+ }
+ let mut ostate = LUCET_SIGNAL_STATE.lock().unwrap();
+ if let Some(ref mut state) = *ostate {
+ state.counter += 1;
+ } else {
+ unsafe {
+ setup_guest_signal_state(&mut ostate);
+ }
+ }
+ drop(ostate);
+
+ // run the body
+ let res = f(self);
+
+ let mut ostate = LUCET_SIGNAL_STATE.lock().unwrap();
+ let counter_zero = if let Some(ref mut state) = *ostate {
+ state.counter -= 1;
+ if state.counter == 0 {
+ unsafe {
+ restore_host_signal_state(state);
+ }
+ true
+ } else {
+ false
+ }
+ } else {
+ panic!("signal handlers weren't installed at instance exit");
+ };
+ if counter_zero {
+ *ostate = None;
+ }
+
+ unsafe {
+ // restore the host signal stack for this thread
+ if !altstack_flags()
+ .expect("the current stack flags could be retrieved")
+ .contains(SigStackFlags::SS_ONSTACK)
+ {
+ sigaltstack(previous_sigstack).expect("sigaltstack restoration succeeds");
+ }
+ }
+
+ res
+ }
+}
+
+/// Signal handler installed during instance execution.
+///
+/// This function is only designed to handle signals that are the direct result of execution of a
+/// hardware instruction from the faulting WASM thread. It thus safely assumes the signal is
+/// directed specifically at this thread (i.e. not a different thread or the process as a whole).
+extern "C" fn handle_signal(signum: c_int, siginfo_ptr: *mut siginfo_t, ucontext_ptr: *mut c_void) {
+ let signal = Signal::from_c_int(signum).expect("signum is a valid signal");
+ if !(signal == Signal::SIGBUS
+ || signal == Signal::SIGSEGV
+ || signal == Signal::SIGILL
+ || signal == Signal::SIGFPE)
+ {
+ panic!("unexpected signal in guest signal handler: {:?}", signal);
+ }
+ assert!(!siginfo_ptr.is_null(), "siginfo must not be null");
+
+ // Safety: when using a SA_SIGINFO sigaction, the third argument can be cast to a `ucontext_t`
+ // pointer per the manpage
+ assert!(!ucontext_ptr.is_null(), "ucontext_ptr must not be null");
+ let ctx = UContextPtr::new(ucontext_ptr);
+ let rip = ctx.get_ip();
+
+ let switch_to_host = CURRENT_INSTANCE.with(|current_instance| {
+ let mut current_instance = current_instance.borrow_mut();
+
+ if current_instance.is_none() {
+ // If there is no current instance, we've caught a signal raised by a thread that's not
+ // running a lucet instance. Restore the host signal handler and reraise the signal,
+ // then return if the host handler returns
+ unsafe {
+ reraise_host_signal_in_handler(signal, signum, siginfo_ptr, ucontext_ptr);
+ }
+ // don't try context-switching
+ return false;
+ }
+
+ // Safety: the memory pointed to by CURRENT_INSTANCE should be a valid instance. This is not
+ // a trivial property, but relies on the compiler not emitting guest programs that can
+ // overwrite the instance.
+ let inst = unsafe {
+ current_instance
+ .as_mut()
+ .expect("current instance exists")
+ .as_mut()
+ };
+
+ let trapcode = inst.module.lookup_trapcode(rip);
+
+ let behavior = (inst.signal_handler)(inst, &trapcode, signum, siginfo_ptr, ucontext_ptr);
+ match behavior {
+ SignalBehavior::Continue => {
+ // return to the guest context without making any modifications to the instance
+ false
+ }
+ SignalBehavior::Terminate => {
+ // set the state before jumping back to the host context
+ inst.state = State::Terminating {
+ details: TerminationDetails::Signal,
+ };
+ true
+ }
+ SignalBehavior::Default => {
+ // safety: pointer is checked for null at the top of the function, and the
+ // manpage guarantees that a siginfo_t will be passed as the second argument
+ let siginfo = unsafe { *siginfo_ptr };
+ let rip_addr = rip as usize;
+ // If the trap table lookup returned unknown, it is a fatal error
+ let unknown_fault = trapcode.is_none();
+
+ // If the trap was a segv or bus fault and the addressed memory was outside the
+ // guard pages, it is also a fatal error
+ let outside_guard = (siginfo.si_signo == SIGSEGV || siginfo.si_signo == SIGBUS)
+ && !inst.alloc.addr_in_guard_page(siginfo.si_addr_ext());
+
+ // record the fault and jump back to the host context
+ inst.state = State::Faulted {
+ details: FaultDetails {
+ fatal: unknown_fault || outside_guard,
+ trapcode: trapcode,
+ rip_addr,
+ // Details set to `None` here: have to wait until `verify_trap_safety` to
+ // fill in these details, because access may not be signal safe.
+ rip_addr_details: None,
+ },
+ siginfo,
+ context: ctx.into(),
+ };
+ true
+ }
+ }
+ });
+
+ if switch_to_host {
+ HOST_CTX.with(|host_ctx| unsafe {
+ Context::set_from_signal(&*host_ctx.get())
+ .expect("can successfully switch back to the host context");
+ });
+ unreachable!()
+ }
+}
+
+struct SignalState {
+ counter: usize,
+ saved_sigbus: SigAction,
+ saved_sigfpe: SigAction,
+ saved_sigill: SigAction,
+ saved_sigsegv: SigAction,
+ saved_panic_hook: Option<Arc<Box<dyn Fn(&panic::PanicInfo<'_>) + Sync + Send + 'static>>>,
+}
+
+// raw pointers in the saved types
+unsafe impl Send for SignalState {}
+
+unsafe fn setup_guest_signal_state(ostate: &mut Option<SignalState>) {
+ let mut masked_signals = SigSet::empty();
+ masked_signals.add(Signal::SIGBUS);
+ masked_signals.add(Signal::SIGFPE);
+ masked_signals.add(Signal::SIGILL);
+ masked_signals.add(Signal::SIGSEGV);
+
+ // setup signal handlers
+ let sa = SigAction::new(
+ SigHandler::SigAction(handle_signal),
+ SaFlags::SA_RESTART | SaFlags::SA_SIGINFO | SaFlags::SA_ONSTACK,
+ masked_signals,
+ );
+ let saved_sigbus = sigaction(Signal::SIGBUS, &sa).expect("sigaction succeeds");
+ let saved_sigfpe = sigaction(Signal::SIGFPE, &sa).expect("sigaction succeeds");
+ let saved_sigill = sigaction(Signal::SIGILL, &sa).expect("sigaction succeeds");
+ let saved_sigsegv = sigaction(Signal::SIGSEGV, &sa).expect("sigaction succeeds");
+
+ let saved_panic_hook = Some(setup_guest_panic_hook());
+
+ *ostate = Some(SignalState {
+ counter: 1,
+ saved_sigbus,
+ saved_sigfpe,
+ saved_sigill,
+ saved_sigsegv,
+ saved_panic_hook,
+ });
+}
+
+fn setup_guest_panic_hook() -> Arc<Box<dyn Fn(&panic::PanicInfo<'_>) + Sync + Send + 'static>> {
+ let saved_panic_hook = Arc::new(panic::take_hook());
+ let closure_saved_panic_hook = saved_panic_hook.clone();
+ std::panic::set_hook(Box::new(move |panic_info| {
+ if panic_info
+ .payload()
+ .downcast_ref::<TerminationDetails>()
+ .is_none()
+ {
+ closure_saved_panic_hook(panic_info);
+ } else {
+ // this is a panic used to implement instance termination (such as
+ // `lucet_hostcall_terminate!`), so we don't want to print a backtrace; instead, we do
+ // nothing
+ }
+ }));
+ saved_panic_hook
+}
+
+unsafe fn restore_host_signal_state(state: &mut SignalState) {
+ // restore signal handlers
+ sigaction(Signal::SIGBUS, &state.saved_sigbus).expect("sigaction succeeds");
+ sigaction(Signal::SIGFPE, &state.saved_sigfpe).expect("sigaction succeeds");
+ sigaction(Signal::SIGILL, &state.saved_sigill).expect("sigaction succeeds");
+ sigaction(Signal::SIGSEGV, &state.saved_sigsegv).expect("sigaction succeeds");
+
+ // restore panic hook
+ drop(panic::take_hook());
+ state
+ .saved_panic_hook
+ .take()
+ .map(|hook| Arc::try_unwrap(hook).map(|hook| panic::set_hook(hook)));
+}
+
+unsafe fn reraise_host_signal_in_handler(
+ sig: Signal,
+ signum: libc::c_int,
+ siginfo_ptr: *mut libc::siginfo_t,
+ ucontext_ptr: *mut c_void,
+) {
+ let saved_handler = {
+ // TODO: avoid taking a mutex here, probably by having some static muts just for this
+ // function
+ if let Some(ref state) = *LUCET_SIGNAL_STATE.lock().unwrap() {
+ match sig {
+ Signal::SIGBUS => state.saved_sigbus.clone(),
+ Signal::SIGFPE => state.saved_sigfpe.clone(),
+ Signal::SIGILL => state.saved_sigill.clone(),
+ Signal::SIGSEGV => state.saved_sigsegv.clone(),
+ sig => panic!(
+ "unexpected signal in reraise_host_signal_in_handler: {:?}",
+ sig
+ ),
+ }
+ } else {
+ // this case is very fishy; it can arise when the last lucet instance spins down and
+ // uninstalls the lucet handlers while a signal handler is running on this thread, but
+ // before taking the mutex above. The theory is that if this has happened, the host
+ // handler has been reinstalled, so we shouldn't end up back here if we reraise
+
+ // unmask the signal to reraise; we don't have to restore it because the handler will return
+ // after this. If it signals again between here and now, that's a double fault and the
+ // process is going to die anyway
+ let mut unmask = SigSet::empty();
+ unmask.add(sig);
+ pthread_sigmask(SigmaskHow::SIG_UNBLOCK, Some(&unmask), None)
+ .expect("pthread_sigmask succeeds");
+ // if there's no current signal state, just re-raise and hope for the best
+ raise(sig).expect("raise succeeds");
+ return;
+ }
+ };
+
+ match saved_handler.handler() {
+ SigHandler::SigDfl => {
+ // reinstall default signal handler and reraise the signal; this should terminate the
+ // program
+ sigaction(sig, &saved_handler).expect("sigaction succeeds");
+ let mut unmask = SigSet::empty();
+ unmask.add(sig);
+ pthread_sigmask(SigmaskHow::SIG_UNBLOCK, Some(&unmask), None)
+ .expect("pthread_sigmask succeeds");
+ raise(sig).expect("raise succeeds");
+ }
+ SigHandler::SigIgn => {
+ // don't do anything; if we hit this case, whatever program is hosting us is almost
+ // certainly doing something wrong, because our set of signals requires intervention to
+ // proceed
+ return;
+ }
+ SigHandler::Handler(f) => {
+ // call the saved handler directly so there is no altstack confusion
+ f(signum)
+ }
+ SigHandler::SigAction(f) => {
+ // call the saved handler directly so there is no altstack confusion
+ f(signum, siginfo_ptr, ucontext_ptr)
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// A collection of wrappers that will be upstreamed to the `nix` crate eventually.
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+use bitflags::bitflags;
+
+#[derive(Copy, Clone)]
+pub struct SigStack {
+ stack: libc::stack_t,
+}
+
+impl SigStack {
+ pub fn new(sp: *mut libc::c_void, flags: SigStackFlags, size: libc::size_t) -> SigStack {
+ let stack = libc::stack_t {
+ ss_sp: sp,
+ ss_flags: flags.bits(),
+ ss_size: size,
+ };
+ SigStack { stack }
+ }
+
+ pub fn disabled() -> SigStack {
+ let stack = libc::stack_t {
+ ss_sp: std::ptr::null_mut(),
+ ss_flags: SigStackFlags::SS_DISABLE.bits(),
+ ss_size: libc::SIGSTKSZ,
+ };
+ SigStack { stack }
+ }
+
+ pub fn flags(&self) -> SigStackFlags {
+ SigStackFlags::from_bits_truncate(self.stack.ss_flags)
+ }
+}
+
+impl AsRef<libc::stack_t> for SigStack {
+ fn as_ref(&self) -> &libc::stack_t {
+ &self.stack
+ }
+}
+
+impl AsMut<libc::stack_t> for SigStack {
+ fn as_mut(&mut self) -> &mut libc::stack_t {
+ &mut self.stack
+ }
+}
+
+bitflags! {
+ pub struct SigStackFlags: libc::c_int {
+ const SS_ONSTACK = libc::SS_ONSTACK;
+ const SS_DISABLE = libc::SS_DISABLE;
+ }
+}
+
+pub unsafe fn sigaltstack(new_sigstack: Option<SigStack>) -> nix::Result<Option<SigStack>> {
+ let mut previous_stack = MaybeUninit::<libc::stack_t>::uninit();
+ let disabled_sigstack = SigStack::disabled();
+ let new_stack = match new_sigstack {
+ None => &disabled_sigstack.stack,
+ Some(ref new_stack) => &new_stack.stack,
+ };
+ let res = libc::sigaltstack(
+ new_stack as *const libc::stack_t,
+ previous_stack.as_mut_ptr(),
+ );
+ nix::errno::Errno::result(res).map(|_| {
+ let sigstack = SigStack {
+ stack: previous_stack.assume_init(),
+ };
+ if sigstack.flags().contains(SigStackFlags::SS_DISABLE) {
+ None
+ } else {
+ Some(sigstack)
+ }
+ })
+}
+
+pub unsafe fn altstack_flags() -> nix::Result<SigStackFlags> {
+ let mut current_stack = MaybeUninit::<libc::stack_t>::uninit();
+ let res = libc::sigaltstack(std::ptr::null_mut(), current_stack.as_mut_ptr());
+ nix::errno::Errno::result(res)
+ .map(|_| SigStackFlags::from_bits_truncate(current_stack.assume_init().ss_flags))
+}
diff --git a/third_party/rust/lucet-runtime-internals-wasmsbx/src/instance/state.rs b/third_party/rust/lucet-runtime-internals-wasmsbx/src/instance/state.rs
new file mode 100644
index 0000000000..1be0346675
--- /dev/null
+++ b/third_party/rust/lucet-runtime-internals-wasmsbx/src/instance/state.rs
@@ -0,0 +1,178 @@
+use crate::instance::siginfo_ext::SiginfoExt;
+use crate::instance::{FaultDetails, TerminationDetails, YieldedVal};
+use crate::sysdeps::UContext;
+use libc::{SIGBUS, SIGSEGV};
+use std::any::Any;
+use std::ffi::{CStr, CString};
+
+/// The representation of a Lucet instance's state machine.
+pub enum State {
+ /// The instance is ready to run.
+ ///
+ /// Transitions to `Running` when the instance is run, or to `Ready` when it's reset.
+ Ready,
+
+ /// The instance is running.
+ ///
+ /// Transitions to `Ready` when the guest function returns normally, or to `Faulted`,
+ /// `Terminating`, or `Yielding` if the instance faults, terminates, or yields.
+ Running,
+
+ /// The instance has faulted, potentially fatally.
+ ///
+ /// Transitions to `Faulted` when filling in additional fault details, to `Running` if
+ /// re-running a non-fatally faulted instance, or to `Ready` when the instance is reset.
+ Faulted {
+ details: FaultDetails,
+ siginfo: libc::siginfo_t,
+ context: UContext,
+ },
+
+ /// The instance is in the process of terminating.
+ ///
+ /// Transitions only to `Terminated`; the `TerminationDetails` are always extracted into a
+ /// `RunResult` before anything else happens to the instance.
+ Terminating { details: TerminationDetails },
+
+ /// The instance has terminated, and must be reset before running again.
+ ///
+ /// Transitions to `Ready` if the instance is reset.
+ Terminated,
+
+ /// The instance is in the process of yielding.
+ ///
+ /// Transitions only to `Yielded`; the `YieldedVal` is always extracted into a
+ /// `RunResult` before anything else happens to the instance.
+ Yielding {
+ val: YieldedVal,
+ /// A phantom value carrying the type of the expected resumption value.
+ ///
+ /// Concretely, this should only ever be `Box<PhantomData<R>>` where `R` is the type
+ /// the guest expects upon resumption.
+ expecting: Box<dyn Any>,
+ },
+
+ /// The instance has yielded.
+ ///
+ /// Transitions to `Running` if the instance is resumed, or to `Ready` if the instance is reset.
+ Yielded {
+ /// A phantom value carrying the type of the expected resumption value.
+ ///
+ /// Concretely, this should only ever be `Box<PhantomData<R>>` where `R` is the type
+ /// the guest expects upon resumption.
+ expecting: Box<dyn Any>,
+ },
+
+ /// A placeholder state used with `std::mem::replace()` when a new state must be constructed by
+ /// moving values out of an old state.
+ ///
+ /// This is used so that we do not need a `Clone` impl for this type, which would add
+ /// unnecessary constraints to the types of values instances could yield or terminate with.
+ ///
+ /// It is an error for this state to appear outside of a transition between other states.
+ Transitioning,
+}
+
+impl std::fmt::Display for State {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ State::Ready => write!(f, "ready"),
+ State::Running => write!(f, "running"),
+ State::Faulted {
+ details, siginfo, ..
+ } => {
+ write!(f, "{}", details)?;
+ write!(
+ f,
+ " triggered by {}: ",
+ strsignal_wrapper(siginfo.si_signo)
+ .into_string()
+ .expect("strsignal returns valid UTF-8")
+ )?;
+
+ if siginfo.si_signo == SIGSEGV || siginfo.si_signo == SIGBUS {
+ // We know this is inside the heap guard, because by the time we get here,
+ // `lucet_error_verify_trap_safety` will have run and validated it.
+ write!(
+ f,
+ " accessed memory at {:p} (inside heap guard)",
+ siginfo.si_addr_ext()
+ )?;
+ }
+ Ok(())
+ }
+ State::Terminated { .. } => write!(f, "terminated"),
+ State::Terminating { .. } => write!(f, "terminating"),
+ State::Yielding { .. } => write!(f, "yielding"),
+ State::Yielded { .. } => write!(f, "yielded"),
+ State::Transitioning { .. } => {
+ write!(f, "transitioning (IF YOU SEE THIS, THERE'S PROBABLY A BUG)")
+ }
+ }
+ }
+}
+
+impl State {
+ pub fn is_ready(&self) -> bool {
+ if let State::Ready { .. } = self {
+ true
+ } else {
+ false
+ }
+ }
+
+ pub fn is_running(&self) -> bool {
+ if let State::Running = self {
+ true
+ } else {
+ false
+ }
+ }
+
+ pub fn is_fault(&self) -> bool {
+ if let State::Faulted { .. } = self {
+ true
+ } else {
+ false
+ }
+ }
+
+ pub fn is_fatal(&self) -> bool {
+ if let State::Faulted {
+ details: FaultDetails { fatal, .. },
+ ..
+ } = self
+ {
+ *fatal
+ } else {
+ false
+ }
+ }
+
+ pub fn is_terminated(&self) -> bool {
+ if let State::Terminated { .. } = self {
+ true
+ } else {
+ false
+ }
+ }
+
+ pub fn is_yielded(&self) -> bool {
+ if let State::Yielded { .. } = self {
+ true
+ } else {
+ false
+ }
+ }
+}
+
+// TODO: PR into `libc`
+extern "C" {
+ #[no_mangle]
+ fn strsignal(sig: libc::c_int) -> *mut libc::c_char;
+}
+
+// TODO: PR into `nix`
+fn strsignal_wrapper(sig: libc::c_int) -> CString {
+ unsafe { CStr::from_ptr(strsignal(sig)).to_owned() }
+}