Implement kill -0 - #1367
Implement kill -0#1367pepa65 wants to merge 7 commits into
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
Job targets may be killed instead of checked, later signal specifications are ignored, and compatibility tests are missing.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Adds kill -0 PID and kill -n 0 PID process-existence checks.
Changes:
- Adds platform-specific process-check helpers.
- Routes zero-signal PID requests through the helper.
- Supports zero in numeric signal parsing.
File summaries
| File | Description |
|---|---|
brush-core/src/sys/unix/signal.rs |
Implements signal-zero checks on Unix. |
brush-core/src/sys/stubs/signal.rs |
Adds unsupported-platform fallback. |
brush-builtins/src/kill.rs |
Parses and executes zero-signal requests. |
Review details
Suppressed comments (2)
brush-builtins/src/kill.rs:86
- A later nonzero sigspec updates
trap_signalbut leavessignal_zeroset, sokill -0 -TERM PIDstill performs only the existence check instead of sendingSIGTERM. Clear zero mode when a subsequent valid signal is parsed, or reject multiple signal specifications.
} else if let Ok(parsed_trap_signal) = possible_sigspec.parse::<TrapSignal>() {
trap_signal = parsed_trap_signal;
brush-builtins/src/kill.rs:134
signal_zerois only consulted for PID targets. For a job target such askill -0 %1, the resolved job still executesjob.kill(trap_signal)with the defaultSIGKILL, so this supposedly non-signaling check can terminate the job. Apply the zero-signal check to the job's process-group ID as well.
if signal_zero {
sys::signal::check_process(pid)?;
- Files reviewed: 3/3 changed files
- Comments generated: 2
- Review effort level: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| #[expect(clippy::cast_possible_truncation)] | ||
| #[expect(clippy::cast_possible_wrap)] | ||
| if let Ok(parsed_trap_signal) = TrapSignal::try_from(*signal_number as i32) { | ||
| trap_signal = parsed_trap_signal; |
Public API changes for crate: brush-coreAdded itemsPerformance Benchmark Report
Code Coverage Report: Only Changed Files listed
Minimum allowed coverage is Test Summary: bash-completion test suite
|
reubeno
left a comment
There was a problem hiding this comment.
Thanks for the contribution, @pepa65! This looks like a great start.
I've left a couple of comments in addition to the ones that copilot left (which I'd agree with). In particular, it would be ideal to add some YAML-based test cases to our compatibility test suite; that's the best way to demonstrate that the functionality as implemented now matches bash.
There should be plenty of examples of tests like that, and tests for kill in particular. Holler if you could use some pointers.
| // See if this is -sigspec syntax. The sigspec may be a signal name | ||
| // (e.g., -TERM) or a signal number (e.g., -9). | ||
| if let Ok(parsed_trap_signal) = possible_sigspec.parse::<TrapSignal>() { | ||
| if possible_sigspec == "0" { |
| pid: sys::process::ProcessId, | ||
| ) -> Result<(), error::Error> { | ||
| nix::sys::signal::kill(nix::unistd::Pid::from_raw(pid), None) | ||
| .map_err(|_errno| error::ErrorKind::FailedToSendSignal)?; |
There was a problem hiding this comment.
question(blocking): Since kill -0 is typically used to check for the existence of a process, can we differentiate the return values and faithfully bubble them up? In other words, if the underlying call fails because the process couldn't be found -- ideally we'd be able to report via the kill builtin that the process doesn't exist. This is in contrast with, for example, a permissions issue.
There was a problem hiding this comment.
To do the same as bash and as kill in procps, where only 0 (signalable) and 1 (insufficient permissions or non-existing process) are returned, brush should do the same in order to not break compatibility for existing scripts.
|
What do I do with such a test result: Firstly, the output differs, but that seems to be common for brush vs. bash..? But I guess here is differentiation, even if the return code is always 1 for non-signalable processes: the error text is different for the non-existent and lacking-permissions cases. This is what should be implemented, with the error texts corresponding to the bash output in both cases. |
reubeno
left a comment
There was a problem hiding this comment.
Thanks for iterating on the changes and for adding the tests; it's looking in pretty good shape.
I've left a few additional comments/requests -- much smaller this time.
| possible_sigspec | ||
| )?; | ||
| return Ok(ExecutionResult::general_error()); | ||
| if let Ok(signal_number) = possible_sigspec.parse::<i32>() { |
There was a problem hiding this comment.
suggestion: This can be slightly simplified. Instead of the if let Ok(...) ... followed by the comparison against 0, we can instead do something like:
if let Ok(0) = possible_sigspec.parse::<i32>() {
signal_zero = true;
} else if let Ok(parsed_trap_signal) = possible_sigspec.parse::<TrapSignal>() {
...
} else {
...
}|
Thank you for your encouraging and helpful comments. I think brush is great, I just bumped into it last week, and it's basically ready to replace bash in most cases already..! (I am building it as a musl static binary, and that works great, and isn't too big.) |
|
Sorry for wasting everybody's time with the untested push... |
|
Not sure what to do about this (below). The oracle included the pid that doesn't exist in the error, should I mirror this? If you do a normal |
@pepa65 -- good question. We probably could be doing something better about this in the project, but for now, we enable the With that open set (just on the test that fails in that way), then exit code, stdout, file output, etc. are all verified -- just |
|
Okay, fixed that. On my test locally I also got: (But I didn't 'fix' that.) |

Implement the
kill -0 PID(andkill -n 0 PID) command, to mirror bash's behavior.This would close Issue #1206.
Assisted-by: ChatGPT