Skip to content

Commit

Permalink
feat: implement kill -l (#221)
Browse files Browse the repository at this point in the history
Leverage nix to enumerate and translate signal values.
  • Loading branch information
39555 authored Oct 26, 2024
1 parent 651ebdb commit 917961f
Show file tree
Hide file tree
Showing 8 changed files with 202 additions and 90 deletions.
55 changes: 51 additions & 4 deletions brush-core/src/builtins/kill.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use clap::Parser;
use std::io::Write;

use crate::traps::TrapSignal;
use crate::{builtins, commands, error};

/// Signal a job or process.
Expand Down Expand Up @@ -37,15 +38,13 @@ impl builtins::Command for KillCommand {
}

if self.list_signals {
error::unimp("kill -l")
return print_signals(&context, self.args.as_ref());
} else {
if self.args.len() != 1 {
writeln!(context.stderr(), "{}: invalid usage", context.command_name)?;
return Ok(builtins::ExitCode::InvalidUsage);
}

let exit_code = builtins::ExitCode::Success;

let pid_or_job_spec = &self.args[0];
if pid_or_job_spec.starts_with('%') {
// It's a job spec.
Expand All @@ -67,8 +66,56 @@ impl builtins::Command for KillCommand {
nix::sys::signal::kill(nix::unistd::Pid::from_raw(pid), nix::sys::signal::SIGKILL)
.map_err(|_errno| error::Error::FailedToSendSignal)?;
}
}
Ok(builtins::ExitCode::Success)
}
}

Ok(exit_code)
fn print_signals(
context: &commands::ExecutionContext<'_>,
signals: &[String],
) -> Result<builtins::ExitCode, error::Error> {
let mut exit_code = builtins::ExitCode::Success;
if !signals.is_empty() {
for s in signals {
// If the user gives us a code, we print the name; if they give a name, we print its
// code.
enum PrintSignal {
Name(&'static str),
Num(i32),
}

let signal = if let Ok(n) = s.parse::<i32>() {
// bash compatibility. `SIGHUP` -> `HUP`
TrapSignal::try_from(n).map(|s| {
PrintSignal::Name(s.as_str().strip_prefix("SIG").unwrap_or(s.as_str()))
})
} else {
TrapSignal::try_from(s.as_str()).map(|sig| {
i32::try_from(sig).map_or(PrintSignal::Name(sig.as_str()), PrintSignal::Num)
})
};

match signal {
Ok(PrintSignal::Num(n)) => {
writeln!(context.stdout(), "{n}")?;
}
Ok(PrintSignal::Name(s)) => {
writeln!(context.stdout(), "{s}")?;
}
Err(e) => {
writeln!(context.stderr(), "{e}")?;
exit_code = builtins::ExitCode::Custom(1);
}
}
}
} else {
return crate::traps::format_signals(
context.stdout(),
TrapSignal::iterator().filter(|s| !matches!(s, TrapSignal::Exit)),
)
.map(|()| builtins::ExitCode::Success);
}

Ok(exit_code)
}
54 changes: 10 additions & 44 deletions brush-core/src/builtins/trap.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use clap::Parser;
use std::io::Write;

use crate::{builtins, commands, error, sys, traps};
use crate::traps::TrapSignal;
use crate::{builtins, commands, error};

/// Manage signal traps.
#[derive(Parser)]
Expand All @@ -23,29 +24,27 @@ impl builtins::Command for TrapCommand {
mut context: commands::ExecutionContext<'_>,
) -> Result<builtins::ExitCode, crate::error::Error> {
if self.list_signals {
Self::display_signals(&context)?;
Ok(builtins::ExitCode::Success)
crate::traps::format_signals(context.stdout(), TrapSignal::iterator())
.map(|()| builtins::ExitCode::Success)
} else if self.print_trap_commands || self.args.is_empty() {
if !self.args.is_empty() {
for signal_type in &self.args {
let signal_type = parse_signal(signal_type)?;
Self::display_handlers_for(&context, signal_type)?;
Self::display_handlers_for(&context, signal_type.parse()?)?;
}
} else {
Self::display_all_handlers(&context)?;
}
Ok(builtins::ExitCode::Success)
} else if self.args.len() == 1 {
let signal = self.args[0].as_str();
let signal_type = parse_signal(signal)?;
Self::remove_all_handlers(&mut context, signal_type);
Self::remove_all_handlers(&mut context, signal.parse()?);
Ok(builtins::ExitCode::Success)
} else {
let handler = &self.args[0];

let mut signal_types = vec![];
for signal in &self.args[1..] {
signal_types.push(parse_signal(signal)?);
signal_types.push(signal.parse()?);
}

Self::register_handler(&mut context, signal_types, handler.as_str());
Expand All @@ -56,16 +55,6 @@ impl builtins::Command for TrapCommand {

#[allow(unused_variables)]
impl TrapCommand {
#[allow(clippy::unnecessary_wraps)]
fn display_signals(context: &commands::ExecutionContext<'_>) -> Result<(), error::Error> {
#[cfg(unix)]
for signal in nix::sys::signal::Signal::iterator() {
writeln!(context.stdout(), "{}: {signal}", signal as i32)?;
}

Ok(())
}

fn display_all_handlers(context: &commands::ExecutionContext<'_>) -> Result<(), error::Error> {
for signal in context.shell.traps.handlers.keys() {
Self::display_handlers_for(context, *signal)?;
Expand All @@ -75,7 +64,7 @@ impl TrapCommand {

fn display_handlers_for(
context: &commands::ExecutionContext<'_>,
signal_type: traps::TrapSignal,
signal_type: TrapSignal,
) -> Result<(), error::Error> {
if let Some(handler) = context.shell.traps.handlers.get(&signal_type) {
writeln!(context.stdout(), "trap -- '{handler}' {signal_type}")?;
Expand All @@ -85,14 +74,14 @@ impl TrapCommand {

fn remove_all_handlers(
context: &mut crate::commands::ExecutionContext<'_>,
signal: traps::TrapSignal,
signal: TrapSignal,
) {
context.shell.traps.remove_handlers(signal);
}

fn register_handler(
context: &mut crate::commands::ExecutionContext<'_>,
signals: Vec<traps::TrapSignal>,
signals: Vec<TrapSignal>,
handler: &str,
) {
for signal in signals {
Expand All @@ -103,26 +92,3 @@ impl TrapCommand {
}
}
}

fn parse_signal(signal: &str) -> Result<traps::TrapSignal, error::Error> {
if signal.chars().all(|c| c.is_ascii_digit()) {
let digits = signal
.parse::<i32>()
.map_err(|_| error::Error::InvalidSignal)?;

sys::signal::parse_numeric_signal(digits)
} else {
let mut signal_to_parse = signal.to_ascii_uppercase();

if !signal_to_parse.starts_with("SIG") {
signal_to_parse.insert_str(0, "SIG");
}

match signal_to_parse {
s if s == "SIGDEBUG" => Ok(traps::TrapSignal::Debug),
s if s == "SIGERR" => Ok(traps::TrapSignal::Err),
s if s == "SIGEXIT" => Ok(traps::TrapSignal::Exit),
s => sys::signal::parse_os_signal_name(s.as_str()),
}
}
}
7 changes: 3 additions & 4 deletions brush-core/src/completion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -487,10 +487,9 @@ impl Spec {
}
}
CompleteAction::Signal => {
for signal in traps::TrapSignal::all_values() {
let signal_str = signal.to_string();
if signal_str.starts_with(token) {
candidates.insert(signal_str);
for signal in traps::TrapSignal::iterator() {
if signal.as_str().starts_with(token) {
candidates.insert(signal.as_str().to_string());
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions brush-core/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,8 @@ pub enum Error {
ThreadingError(#[from] tokio::task::JoinError),

/// An invalid signal was referenced.
#[error("invalid signal")]
InvalidSignal,
#[error("{0}: invalid signal specification")]
InvalidSignal(String),

/// A system error occurred.
#[cfg(unix)]
Expand Down
8 changes: 0 additions & 8 deletions brush-core/src/sys/stubs/signal.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,5 @@
use crate::{error, sys, traps};

pub(crate) fn parse_numeric_signal(_signal: i32) -> Result<traps::TrapSignal, error::Error> {
Err(error::Error::InvalidSignal)
}

pub(crate) fn parse_os_signal_name(_signal: &str) -> Result<traps::TrapSignal, error::Error> {
Err(error::Error::InvalidSignal)
}

pub(crate) fn continue_process(_pid: sys::process::ProcessId) -> Result<(), error::Error> {
error::unimp("continue process")
}
Expand Down
16 changes: 1 addition & 15 deletions brush-core/src/sys/unix/signal.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,4 @@
use std::str::FromStr;

use crate::{error, sys, traps};

pub(crate) fn parse_numeric_signal(signal: i32) -> Result<traps::TrapSignal, error::Error> {
Ok(traps::TrapSignal::Signal(
nix::sys::signal::Signal::try_from(signal).map_err(|_| error::Error::InvalidSignal)?,
))
}

pub(crate) fn parse_os_signal_name(signal: &str) -> Result<traps::TrapSignal, error::Error> {
Ok(traps::TrapSignal::Signal(
nix::sys::signal::Signal::from_str(signal).map_err(|_| error::Error::InvalidSignal)?,
))
}
use crate::{error, sys};

pub(crate) fn continue_process(pid: sys::process::ProcessId) -> Result<(), error::Error> {
#[allow(clippy::cast_possible_wrap)]
Expand Down
Loading

0 comments on commit 917961f

Please sign in to comment.