Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate to our own Signal type #365

Closed
wants to merge 14 commits into from
Closed
8 changes: 6 additions & 2 deletions yash-builtin/src/fg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,6 @@ use yash_env::job::id::parse;
use yash_env::job::JobList;
use yash_env::job::Pid;
use yash_env::job::ProcessState;
use yash_env::semantics::ExitStatus;
use yash_env::semantics::Field;
use yash_env::system::Errno;
use yash_env::system::System as _;
Expand Down Expand Up @@ -189,7 +188,11 @@ pub async fn main(env: &mut Env, args: Vec<Field>) -> crate::Result {
};

match result {
Ok(state) => ExitStatus::try_from(state).unwrap().into(),
Ok(state) => env
.system
.exit_status_for_process_state(state)
.unwrap()
.into(),
Err(error) => report_simple_failure(env, &error.to_string()).await,
}
}
Expand All @@ -208,6 +211,7 @@ mod tests {
use yash_env::job::ProcessState;
use yash_env::option::Option::Monitor;
use yash_env::option::State::On;
use yash_env::semantics::ExitStatus;
use yash_env::subshell::JobControl;
use yash_env::subshell::Subshell;
use yash_env::system::r#virtual::Process;
Expand Down
23 changes: 18 additions & 5 deletions yash-builtin/src/kill/syntax.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
use super::Command;
use std::ffi::c_int;
use thiserror::Error;
use yash_env::semantics::ExitStatus;
use yash_env::semantics::Field;
use yash_env::trap::Signal;
use yash_env::Env;
Expand Down Expand Up @@ -238,8 +237,12 @@ fn parse_signals<I: Iterator<Item = Field>>(
allow_sig_prefix: bool,
) -> Result<Vec<Signal>, Error> {
let parse_one = move |operand: Field| {
if let Some(exit_status) = operand.value.parse().ok().map(ExitStatus) {
exit_status.try_into().ok()
if let Ok(exit_status) = operand.value.parse::<c_int>() {
[0x180, 0x80, 0].into_iter().find_map(|offset| {
(exit_status > offset)
.then(|| (exit_status - offset).try_into().ok())
.flatten()
})
} else {
parse_signal_name(&operand.value, allow_sig_prefix)
}
Expand Down Expand Up @@ -374,6 +377,9 @@ pub fn parse(_env: &Env, args: Vec<Field>) -> Result<Command, Error> {
#[cfg(test)]
mod tests {
use super::*;
use yash_env::trap::Signal2;
use yash_env::System as _;
use yash_env::SystemEx as _;

#[test]
fn parse_signal_names() {
Expand Down Expand Up @@ -612,10 +618,17 @@ mod tests {
}

#[test]
#[ignore = "Need to merge the two Signal definitions"] // TODO
fn option_l_with_operands() {
let env = Env::new_virtual();
let exit_status = &ExitStatus::from(Signal::SIGQUIT).to_string();
let result = parse(&env, Field::dummies(["-l", "TERM", "1", exit_status]));
let number = &env
.system
.signal_to_raw_number(Signal2::Hup)
.unwrap()
.to_string();
let exit_status = &env.system.exit_status_for_signal(Signal2::Quit).to_string();

let result = parse(&env, Field::dummies(["-l", "TERM", number, exit_status]));
assert_eq!(
result,
Ok(Command::Print {
Expand Down
7 changes: 6 additions & 1 deletion yash-builtin/src/wait.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ use yash_env::option::Option::Monitor;
use yash_env::semantics::ExitStatus;
use yash_env::semantics::Field;
use yash_env::Env;
use yash_env::System as _;
use yash_env::SystemEx as _;

/// Job specification (job ID or process ID)
///
Expand Down Expand Up @@ -168,7 +170,10 @@ impl Command {
match Self::await_jobs(env, indexes).await {
Ok(exit_status) => exit_status.into(),
Err(core::Error::Trapped(signal, divert)) => {
crate::Result::with_exit_status_and_divert(ExitStatus::from(signal), divert)
let signal = env.system.raw_number_to_signal(signal as _);
let signal = signal.unwrap_or(yash_env::trap::Signal2::Number(0));
let exit_status = env.system.exit_status_for_signal(signal);
crate::Result::with_exit_status_and_divert(exit_status, divert)
}
Err(error) => report_simple_failure(env, &error.to_string()).await,
}
Expand Down
80 changes: 40 additions & 40 deletions yash-builtin/src/wait/status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,20 +32,21 @@ use yash_env::job::JobList;
use yash_env::job::ProcessState;
use yash_env::option::State;
use yash_env::semantics::ExitStatus;
use yash_env::system::SystemEx as _;
use yash_env::Env;

/// Waits while the given job is running.
///
/// This function keeps calling [`wait_for_any_job_or_trap`] until the given
/// closure returns [`ControlFlow::Break`], whose value is returned from this
/// function.
/// closure returns [`ControlFlow::Break`], then returns the exit status
/// corresponding to the final state of the job.
pub async fn wait_while_running(
env: &mut Env,
job_status: &mut dyn FnMut(&mut JobList) -> ControlFlow<ExitStatus>,
job_status: &mut dyn FnMut(&mut JobList) -> ControlFlow<ProcessState>,
) -> Result<ExitStatus, Error> {
loop {
if let ControlFlow::Break(exit_status) = job_status(&mut env.jobs) {
return Ok(exit_status);
if let ControlFlow::Break(state) = job_status(&mut env.jobs) {
return Ok(env.system.exit_status_for_process_state(state).unwrap());
}
wait_for_any_job_or_trap(env).await?;
}
Expand All @@ -58,37 +59,30 @@ pub async fn wait_while_running(
/// The disowned job is removed from the job list.
///
/// If the job has finished (either exited or signaled), the closure removes the
/// job from the job list and returns [`ControlFlow::Break`] with the job's exit
/// status. If `job_control` is `On` and the job has been stopped, the closure
/// returns [`ControlFlow::Break`] with an exit status that indicates the signal
/// that stopped the job.
/// Otherwise, the closure returns [`ControlFlow::Continue`].
/// job from the job list and returns [`ControlFlow::Break`] with the job's
/// final state. The closure returns also if `job_control` is `On` and the job
/// has been stopped. Otherwise, the closure returns [`ControlFlow::Continue`].
pub fn job_status(
index: usize,
job_control: State,
) -> impl FnMut(&mut JobList) -> ControlFlow<ExitStatus> {
) -> impl FnMut(&mut JobList) -> ControlFlow<ProcessState> {
move |jobs| {
let Some(job) = jobs.get(index) else {
return ControlFlow::Break(ExitStatus::NOT_FOUND);
return ControlFlow::Break(ProcessState::Exited(ExitStatus::NOT_FOUND));
};

if !job.is_owned {
jobs.remove(index);
return ControlFlow::Break(ExitStatus::NOT_FOUND);
return ControlFlow::Break(ProcessState::Exited(ExitStatus::NOT_FOUND));
}

match job.state {
ProcessState::Exited(exit_status) => {
let state = job.state;
match state {
ProcessState::Exited(_) | ProcessState::Signaled { .. } => {
jobs.remove(index);
ControlFlow::Break(exit_status)
}
ProcessState::Signaled { signal, .. } => {
jobs.remove(index);
ControlFlow::Break(ExitStatus::from(signal))
}
ProcessState::Stopped(signal) if job_control.into() => {
ControlFlow::Break(ExitStatus::from(signal))
ControlFlow::Break(state)
}
ProcessState::Stopped(_) if job_control.into() => ControlFlow::Break(state),
_ => ControlFlow::Continue(()),
}
}
Expand All @@ -101,14 +95,14 @@ pub fn job_status(
/// status of 0. Otherwise, the closure returns [`ControlFlow::Continue`].
pub fn any_job_is_running(
job_control: State,
) -> impl FnMut(&mut JobList) -> ControlFlow<ExitStatus> {
) -> impl FnMut(&mut JobList) -> ControlFlow<ProcessState> {
move |jobs| {
let Some((max_index, _)) = jobs.iter().next_back() else {
return ControlFlow::Break(ExitStatus::SUCCESS);
return ControlFlow::Break(ProcessState::Exited(ExitStatus::SUCCESS));
};

if (0..=max_index).all(|index| job_status(index, job_control)(jobs).is_break()) {
ControlFlow::Break(ExitStatus::SUCCESS)
ControlFlow::Break(ProcessState::Exited(ExitStatus::SUCCESS))
} else {
ControlFlow::Continue(())
}
Expand All @@ -127,15 +121,15 @@ mod tests {
let mut jobs = JobList::new();
assert_eq!(
job_status(0, Off)(&mut jobs),
ControlFlow::Break(ExitStatus::NOT_FOUND),
ControlFlow::Break(ProcessState::Exited(ExitStatus::NOT_FOUND)),
);
assert_eq!(
job_status(1, Off)(&mut jobs),
ControlFlow::Break(ExitStatus::NOT_FOUND),
ControlFlow::Break(ProcessState::Exited(ExitStatus::NOT_FOUND)),
);
assert_eq!(
job_status(0, On)(&mut jobs),
ControlFlow::Break(ExitStatus::NOT_FOUND),
ControlFlow::Break(ProcessState::Exited(ExitStatus::NOT_FOUND)),
);
assert_eq!(jobs.len(), 0);
}
Expand All @@ -149,7 +143,7 @@ mod tests {

assert_eq!(
job_status(index, Off)(&mut jobs),
ControlFlow::Break(ExitStatus::SUCCESS),
ControlFlow::Break(ProcessState::Exited(ExitStatus::SUCCESS)),
);
assert_eq!(jobs.get(index), None);

Expand All @@ -159,7 +153,7 @@ mod tests {

assert_eq!(
job_status(index, On)(&mut jobs),
ControlFlow::Break(ExitStatus(42)),
ControlFlow::Break(ProcessState::Exited(ExitStatus(42))),
);
assert_eq!(jobs.get(index), None);
}
Expand All @@ -176,7 +170,10 @@ mod tests {

assert_eq!(
job_status(index, Off)(&mut jobs),
ControlFlow::Break(ExitStatus::from(Signal::SIGHUP)),
ControlFlow::Break(ProcessState::Signaled {
signal: Signal::SIGHUP,
core_dump: false
}),
);
assert_eq!(jobs.get(index), None);

Expand All @@ -189,7 +186,10 @@ mod tests {

assert_eq!(
job_status(index, On)(&mut jobs),
ControlFlow::Break(ExitStatus::from(Signal::SIGABRT)),
ControlFlow::Break(ProcessState::Signaled {
signal: Signal::SIGABRT,
core_dump: true,
}),
);
assert_eq!(jobs.get(index), None);
}
Expand Down Expand Up @@ -221,7 +221,7 @@ mod tests {

assert_eq!(
job_status(index, On)(&mut jobs),
ControlFlow::Break(ExitStatus::from(Signal::SIGTSTP)),
ControlFlow::Break(ProcessState::Stopped(Signal::SIGTSTP)),
);
assert_eq!(jobs[index].pid, Pid(123));

Expand All @@ -231,7 +231,7 @@ mod tests {

assert_eq!(
job_status(index, On)(&mut jobs),
ControlFlow::Break(ExitStatus::from(Signal::SIGSTOP)),
ControlFlow::Break(ProcessState::Stopped(Signal::SIGSTOP)),
);
assert_eq!(jobs[index].pid, Pid(456));
}
Expand Down Expand Up @@ -263,7 +263,7 @@ mod tests {

assert_eq!(
job_status(index, Off)(&mut jobs),
ControlFlow::Break(ExitStatus::NOT_FOUND),
ControlFlow::Break(ProcessState::Exited(ExitStatus::NOT_FOUND)),
);
assert_eq!(jobs.get(index), None);
}
Expand All @@ -273,11 +273,11 @@ mod tests {
let mut jobs = JobList::new();
assert_eq!(
any_job_is_running(Off)(&mut jobs),
ControlFlow::Break(ExitStatus::SUCCESS),
ControlFlow::Break(ProcessState::Exited(ExitStatus::SUCCESS)),
);
assert_eq!(
any_job_is_running(On)(&mut jobs),
ControlFlow::Break(ExitStatus::SUCCESS),
ControlFlow::Break(ProcessState::Exited(ExitStatus::SUCCESS)),
);
}

Expand All @@ -290,7 +290,7 @@ mod tests {

assert_eq!(
any_job_is_running(Off)(&mut jobs),
ControlFlow::Break(ExitStatus::SUCCESS),
ControlFlow::Break(ProcessState::Exited(ExitStatus::SUCCESS)),
);

let mut job = Job::new(Pid(456));
Expand All @@ -299,7 +299,7 @@ mod tests {

assert_eq!(
any_job_is_running(On)(&mut jobs),
ControlFlow::Break(ExitStatus::SUCCESS),
ControlFlow::Break(ProcessState::Exited(ExitStatus::SUCCESS)),
);
}

Expand Down
62 changes: 0 additions & 62 deletions yash-env/src/job.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@

use crate::semantics::ExitStatus;
use crate::trap::Signal;
use nix::sys::wait::WaitStatus;
use slab::Slab;
use std::collections::HashMap;
use std::iter::FusedIterator;
Expand Down Expand Up @@ -140,67 +139,6 @@ impl ProcessState {
ProcessState::Exited(_) | ProcessState::Signaled { .. } => false,
}
}

/// Converts `ProcessState` to `WaitStatus`.
///
/// This function returns a type defined in the `nix` crate, which is not
/// covered by the semantic versioning policy of this crate.
#[must_use]
pub fn to_wait_status(self, pid: Pid) -> WaitStatus {
match self {
ProcessState::Running => WaitStatus::Continued(pid.into()),
ProcessState::Exited(exit_status) => WaitStatus::Exited(pid.into(), exit_status.0),
ProcessState::Stopped(signal) => WaitStatus::Stopped(pid.into(), signal),
ProcessState::Signaled { signal, core_dump } => {
WaitStatus::Signaled(pid.into(), signal, core_dump)
}
}
}

/// Converts `WaitStatus` to `ProcessState`.
///
/// If the given `WaitStatus` represents a change in the process state, this
/// function returns the new state with the process ID. Otherwise, it
/// returns `None`.
///
/// The `WaitStatus` type is defined in the `nix` crate, which is not
/// covered by the semantic versioning policy of this crate.
#[must_use]
pub fn from_wait_status(status: WaitStatus) -> Option<(Pid, Self)> {
match status {
WaitStatus::Continued(pid) => Some((pid.into(), ProcessState::Running)),
WaitStatus::Exited(pid, exit_status) => {
Some((pid.into(), ProcessState::Exited(ExitStatus(exit_status))))
}
WaitStatus::Stopped(pid, signal) => Some((pid.into(), ProcessState::Stopped(signal))),
WaitStatus::Signaled(pid, signal, core_dump) => {
Some((pid.into(), ProcessState::Signaled { signal, core_dump }))
}
_ => None,
}
}
}

/// Error value indicating that the process is running.
///
/// This error value may be returned by [`TryFrom<ProcessState>::try_from`].
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct RunningProcess;

/// Converts `ProcessState` to `ExitStatus`.
///
/// For the `Running` state, the conversion fails with [`RunningProcess`].
impl TryFrom<ProcessState> for ExitStatus {
type Error = RunningProcess;
fn try_from(state: ProcessState) -> Result<Self, RunningProcess> {
match state {
ProcessState::Exited(exit_status) => Ok(exit_status),
ProcessState::Signaled { signal, .. } | ProcessState::Stopped(signal) => {
Ok(ExitStatus::from(signal))
}
ProcessState::Running => Err(RunningProcess),
}
}
}

/// Set of one or more processes executing a pipeline
Expand Down
Loading
Loading