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

feat: implement kill -l #221

Merged
merged 9 commits into from
Oct 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading