1use nu_engine::command_prelude::*;
2use nu_protocol::shell_error::generic::GenericError;
3use nu_system::build_kill_command;
4use std::process::Stdio;
5
6#[derive(Clone)]
7pub struct Kill;
8
9impl Command for Kill {
10 fn name(&self) -> &str {
11 "kill"
12 }
13
14 fn description(&self) -> &str {
15 "Kill a process using its process ID."
16 }
17
18 fn signature(&self) -> Signature {
19 let signature = Signature::build("kill")
20 .input_output_types(vec![(Type::Nothing, Type::Any)])
21 .allow_variants_without_examples(true)
22 .rest(
23 "pid",
24 SyntaxShape::Int,
25 "Process ids of processes that are to be killed.",
26 )
27 .switch("force", "Forcefully kill the process.", Some('f'))
28 .switch("quiet", "Won't print anything to the console.", Some('q'))
29 .category(Category::Platform);
30
31 if cfg!(windows) {
32 return signature;
33 }
34
35 signature.named(
36 "signal",
37 SyntaxShape::Int,
38 "Signal decimal number to be sent instead of the default 15 (unsupported on Windows).",
39 Some('s'),
40 )
41 }
42
43 fn search_terms(&self) -> Vec<&str> {
44 vec!["stop", "end", "close", "taskkill"]
45 }
46
47 fn run(
48 &self,
49 engine_state: &EngineState,
50 stack: &mut Stack,
51 call: &Call,
52 _input: PipelineData,
53 ) -> Result<PipelineData, ShellError> {
54 let pids: Vec<Spanned<i64>> = call.rest(engine_state, stack, 0)?;
55 let force: bool = call.has_flag(engine_state, stack, "force")?;
56 let signal: Option<Spanned<i64>> = call.get_flag(engine_state, stack, "signal")?;
57 let quiet: bool = call.has_flag(engine_state, stack, "quiet")?;
58
59 if cfg!(unix)
60 && signal.is_none()
61 && let Some((span, signal_number)) = pids.iter().find_map(|pid| {
62 inferred_signal_number(engine_state.get_span_contents(pid.span), pid.item)
63 .map(|signal_number| (pid.span, signal_number))
64 })
65 {
66 return Err(ShellError::IncorrectValue {
67 msg: format!(
68 "negative pid shorthand is not supported; use `kill -s {signal_number} <pid>`"
69 ),
70 val_span: span,
71 call_span: call.head,
72 });
73 }
74
75 if pids.is_empty() {
76 return Err(ShellError::MissingParameter {
77 param_name: "pid".to_string(),
78 span: call.arguments_span(),
79 });
80 }
81
82 if cfg!(unix)
83 && let (
84 true,
85 Some(Spanned {
86 item: _,
87 span: signal_span,
88 }),
89 ) = (force, signal)
90 {
91 return Err(ShellError::IncompatibleParameters {
92 left_message: "force".to_string(),
93 left_span: call
94 .get_flag_span(stack, "force")
95 .expect("Had flag force, but didn't have span for flag"),
96 right_message: "signal".to_string(),
97 right_span: Span::merge(
98 call.get_flag_span(stack, "signal")
99 .expect("Had flag signal, but didn't have span for flag"),
100 signal_span,
101 ),
102 });
103 };
104
105 let mut cmd = build_kill_command(
106 force,
107 pids.iter().copied().map(|spanned| spanned.item),
108 signal.map(|spanned| spanned.item as u32),
109 );
110
111 if quiet {
113 cmd.stdin(Stdio::null())
114 .stdout(Stdio::null())
115 .stderr(Stdio::null());
116 }
117
118 let output = cmd.output().map_err(|e| {
119 ShellError::Generic(GenericError::new(
120 "failed to execute shell command",
121 e.to_string(),
122 call.head,
123 ))
124 })?;
125
126 if !quiet && !output.status.success() {
127 return Err(ShellError::Generic(GenericError::new(
128 "process didn't terminate successfully",
129 String::from_utf8(output.stderr).unwrap_or_default(),
130 call.head,
131 )));
132 }
133
134 let mut output = String::from_utf8(output.stdout).map_err(|e| {
135 ShellError::Generic(GenericError::new(
136 "failed to convert output to string",
137 e.to_string(),
138 call.head,
139 ))
140 })?;
141
142 output.truncate(output.trim_end().len());
143
144 if output.is_empty() {
145 Ok(Value::nothing(call.head).into_pipeline_data())
146 } else {
147 Ok(Value::string(output, call.head).into_pipeline_data())
148 }
149 }
150
151 fn examples(&self) -> Vec<Example<'_>> {
152 vec![
153 Example {
154 description: "Kill the pid using the most memory.",
155 example: "ps | sort-by mem | last | kill $in.pid",
156 result: None,
157 },
158 Example {
159 description: "Force kill a given pid.",
160 example: "kill --force 12345",
161 result: None,
162 },
163 #[cfg(not(target_os = "windows"))]
164 Example {
165 description: "Send INT signal.",
166 example: "kill -s 2 12345",
167 result: None,
168 },
169 ]
170 }
171}
172
173fn inferred_signal_number(raw: &[u8], pid: i64) -> Option<u64> {
174 if pid < 0 {
175 return Some(pid.unsigned_abs());
176 }
177
178 let stripped = std::str::from_utf8(raw).ok()?.strip_prefix('-')?;
179
180 if stripped.is_empty() || !stripped.bytes().all(|byte| byte.is_ascii_digit()) {
181 return None;
182 }
183
184 stripped.parse().ok()
185}
186
187#[cfg(test)]
188mod tests {
189 use super::*;
190
191 #[test]
192 fn examples_work_as_expected() -> nu_test_support::Result {
193 nu_test_support::test().examples(Kill)
194 }
195
196 #[test]
197 fn inferred_signal_number_detects_negative_pid_shorthand() {
198 assert_eq!(inferred_signal_number(b"-0", 0), Some(0));
199 assert_eq!(inferred_signal_number(b"-00", 0), Some(0));
200 assert_eq!(inferred_signal_number(b"-9", -9), Some(9));
201 assert_eq!(inferred_signal_number(b"0", 0), None);
202 assert_eq!(inferred_signal_number(b"9", 9), None);
203 }
204}