Skip to content

Commit

Permalink
feat(builtins): implement more of kill builtin
Browse files Browse the repository at this point in the history
  • Loading branch information
reubeno committed Jan 6, 2025
1 parent b1a79d4 commit 128e084
Show file tree
Hide file tree
Showing 5 changed files with 97 additions and 17 deletions.
76 changes: 66 additions & 10 deletions brush-core/src/builtins/kill.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use clap::Parser;
use std::io::Write;

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

/// Signal a job or process.
#[derive(Parser)]
Expand All @@ -22,6 +22,7 @@ pub(crate) struct KillCommand {
list_signals: bool,

// Interpretation of these depends on whether -l is present.
#[arg(allow_hyphen_values = true)]
args: Vec<String>,
}

Expand All @@ -30,26 +31,82 @@ impl builtins::Command for KillCommand {
&self,
context: commands::ExecutionContext<'_>,
) -> Result<crate::builtins::ExitCode, crate::error::Error> {
if self.signal_name.is_some() {
return error::unimp("kill -s");
// Default signal is SIGKILL.
let mut trap_signal = TrapSignal::Signal(nix::sys::signal::Signal::SIGKILL);

// Try parsing the signal name (if specified).
if let Some(signal_name) = &self.signal_name {
if let Ok(parsed_trap_signal) = TrapSignal::try_from(signal_name.as_str()) {
trap_signal = parsed_trap_signal;
} else {
writeln!(
context.stderr(),
"{}: invalid signal name: {}",
context.command_name,
signal_name
)?;
return Ok(builtins::ExitCode::InvalidUsage);
}
}

// Try parsing the signal number (if specified).
if let Some(signal_number) = &self.signal_number {
#[allow(clippy::cast_possible_truncation)]
#[allow(clippy::cast_possible_wrap)]
if let Ok(parsed_trap_signal) = TrapSignal::try_from(*signal_number as i32) {
trap_signal = parsed_trap_signal;
} else {
writeln!(
context.stderr(),
"{}: invalid signal number: {}",
context.command_name,
signal_number
)?;
return Ok(builtins::ExitCode::InvalidUsage);
}
}
if self.signal_number.is_some() {
return error::unimp("kill -n");

// Look through the remaining args for a pid/job spec or a -sigspec style option.
let mut pid_or_job_spec = None;
for arg in &self.args {
// See if this is -sigspec syntax.
if let Some(possible_sigspec) = arg.strip_prefix("-") {
// See if this is -sigspec syntax.
if let Ok(parsed_trap_signal) = TrapSignal::try_from(possible_sigspec) {
trap_signal = parsed_trap_signal;
} else {
writeln!(
context.stderr(),
"{}: invalid signal name",
context.command_name
)?;
return Ok(builtins::ExitCode::InvalidUsage);
}
} else if pid_or_job_spec.is_none() {
pid_or_job_spec = Some(arg);
} else {
writeln!(
context.stderr(),
"{}: too many jobs or processes specified",
context.command_name
)?;
return Ok(builtins::ExitCode::InvalidUsage);
}
}

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

let pid_or_job_spec = &self.args[0];
let pid_or_job_spec = pid_or_job_spec.unwrap();
if pid_or_job_spec.starts_with('%') {
// It's a job spec.
if let Some(job) = context.shell.jobs.resolve_job_spec(pid_or_job_spec) {
job.kill()?;
job.kill(trap_signal)?;
} else {
writeln!(
context.stderr(),
Expand All @@ -63,8 +120,7 @@ impl builtins::Command for KillCommand {
let pid = pid_or_job_spec.parse::<i32>()?;

// It's a pid.
nix::sys::signal::kill(nix::unistd::Pid::from_raw(pid), nix::sys::signal::SIGKILL)
.map_err(|_errno| error::Error::FailedToSendSignal)?;
sys::signal::kill_process(pid, trap_signal)?;
}
}
Ok(builtins::ExitCode::Success)
Expand Down
5 changes: 3 additions & 2 deletions brush-core/src/jobs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use crate::error;
use crate::processes;
use crate::sys;
use crate::trace_categories;
use crate::traps;
use crate::ExecutionResult;

pub(crate) type JobJoinHandle = tokio::task::JoinHandle<Result<ExecutionResult, error::Error>>;
Expand Down Expand Up @@ -409,9 +410,9 @@ impl Job {
}

/// Kills the job.
pub fn kill(&mut self) -> Result<(), error::Error> {
pub fn kill(&mut self, signal: traps::TrapSignal) -> Result<(), error::Error> {
if let Some(pid) = self.get_process_group_id() {
sys::signal::kill_process(pid)
sys::signal::kill_process(pid, signal)
} else {
Err(error::Error::FailedToSendSignal)
}
Expand Down
7 changes: 5 additions & 2 deletions brush-core/src/sys/stubs/signal.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
use crate::{error, sys};
use crate::{error, sys, traps};

pub(crate) fn continue_process(_pid: sys::process::ProcessId) -> Result<(), error::Error> {
error::unimp("continue process")
}

pub(crate) fn kill_process(_pid: sys::process::ProcessId) -> Result<(), error::Error> {
pub(crate) fn kill_process(
_pid: sys::process::ProcessId,
_signal: traps::TrapSignal,
) -> Result<(), error::Error> {
error::unimp("kill process")
}

Expand Down
16 changes: 13 additions & 3 deletions brush-core/src/sys/unix/signal.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{error, sys};
use crate::{error, sys, traps};

pub(crate) fn continue_process(pid: sys::process::ProcessId) -> Result<(), error::Error> {
#[allow(clippy::cast_possible_wrap)]
Expand All @@ -7,8 +7,18 @@ pub(crate) fn continue_process(pid: sys::process::ProcessId) -> Result<(), error
Ok(())
}

pub(crate) fn kill_process(pid: sys::process::ProcessId) -> Result<(), error::Error> {
nix::sys::signal::kill(nix::unistd::Pid::from_raw(pid), nix::sys::signal::SIGKILL)
pub(crate) fn kill_process(
pid: sys::process::ProcessId,
signal: traps::TrapSignal,
) -> Result<(), error::Error> {
let translated_signal = match signal {
traps::TrapSignal::Signal(signal) => signal,
traps::TrapSignal::Debug | traps::TrapSignal::Err | traps::TrapSignal::Exit => {
return Err(error::Error::InvalidSignal(signal.to_string()));
}
};

nix::sys::signal::kill(nix::unistd::Pid::from_raw(pid), translated_signal)
.map_err(|_errno| error::Error::FailedToSendSignal)?;

Ok(())
Expand Down
10 changes: 10 additions & 0 deletions brush-shell/tests/cases/builtins/kill.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,14 @@ cases:
kill -l SIGHUP
kill -l EXIT
- name: "kill -s"
stdin: |
kill -s USR1 $$
- name: "kill -n"
stdin: |
kill -n 9 $$
- name: "kill -sigspec"
stdin: |
kill -USR1 $$

0 comments on commit 128e084

Please sign in to comment.