Skip to main content

ferrijs_std/utils/
signals.rs

1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3use rquickjs::{prelude::Opt, Ctx, Exception, Result, Value};
4
5use crate::utils::result::ResultExt;
6use std::io;
7
8#[cfg(unix)]
9macro_rules! generate_signal_from_str_fn {
10    ($($signal:path),*) => {
11        pub fn signal_from_str(signal: &str) -> Option<i32> {
12            let signal = ["libc::", signal].concat();
13            match signal.as_str() {
14                $(stringify!($signal) => Some($signal),)*
15                _ => None,
16            }
17        }
18
19        pub fn signal_str_from_i32(signal: i32) -> Option<&'static str> {
20            $(if signal == $signal {
21                return Some(&stringify!($signal)[6..]);
22            })*
23            None
24        }
25    };
26}
27
28#[cfg(unix)]
29generate_signal_from_str_fn!(
30    libc::SIGHUP,
31    libc::SIGINT,
32    libc::SIGQUIT,
33    libc::SIGILL,
34    libc::SIGABRT,
35    libc::SIGFPE,
36    libc::SIGKILL,
37    libc::SIGSEGV,
38    libc::SIGPIPE,
39    libc::SIGALRM,
40    libc::SIGTERM
41);
42
43#[cfg(not(unix))]
44static WINDOWS_SIGTERM: i32 = -1;
45
46pub fn parse_signal(signal: Option<Value<'_>>) -> Result<i32> {
47    let Some(val) = signal else {
48        #[cfg(unix)]
49        return Ok(libc::SIGTERM);
50        #[cfg(not(unix))]
51        return Ok(WINDOWS_SIGTERM);
52    };
53
54    if let Some(num) = val.as_number() {
55        let sig = num as i32;
56        #[cfg(unix)]
57        return Ok(sig);
58        // On Windows: 0 checks existence, anything else kills
59        #[cfg(not(unix))]
60        return Ok(if sig == 0 { 0 } else { WINDOWS_SIGTERM });
61    }
62
63    if let Some(str_val) = val.as_string() {
64        let s = str_val.to_string()?;
65
66        #[cfg(unix)]
67        let mapped_sig = signal_from_str(&s);
68
69        #[cfg(not(unix))]
70        let mapped_sig = match s.as_str() {
71            "SIGINT" | "SIGTERM" | "SIGKILL" | "SIGQUIT" | "SIGHUP" | "SIGUSR1" => {
72                Some(WINDOWS_SIGTERM)
73            },
74            _ => None,
75        };
76
77        return match mapped_sig {
78            Some(sig) => Ok(sig),
79            None => Err(Exception::throw_type(
80                val.ctx(),
81                &format!("Unknown signal: {}", s),
82            )),
83        };
84    }
85
86    Err(Exception::throw_type(val.ctx(), "Invalid signal type"))
87}
88
89#[cfg(unix)]
90pub fn kill_process_raw(pid: u32, signal: i32) -> io::Result<()> {
91    // libc::kill returns 0 on success, -1 on error
92    // SAFETY: kill is a safe system call as long as the signal value is valid, which is ensured by parse_signal
93    if unsafe { libc::kill(pid as i32, signal) } == 0 {
94        Ok(())
95    } else {
96        Err(io::Error::last_os_error())
97    }
98}
99
100#[cfg(windows)]
101pub fn kill_process_raw(pid: u32, signal: i32) -> io::Result<()> {
102    use windows_sys::Win32::Foundation::CloseHandle;
103    use windows_sys::Win32::System::Threading::{OpenProcess, TerminateProcess, PROCESS_TERMINATE};
104
105    // SAFETY: OpenProcess is safe to call with valid parameters, and PROCESS_TERMINATE is a valid access right
106    let handle = unsafe { OpenProcess(PROCESS_TERMINATE, 0, pid) };
107    if handle == std::ptr::null_mut() {
108        return Err(io::Error::last_os_error());
109    }
110
111    let result = if signal == 0 {
112        Ok(())
113    } else {
114        // SAFETY: TerminateProcess is safe to call with a valid process handle obtained from OpenProcess
115        if unsafe { TerminateProcess(handle, 1) } != 0 {
116            Ok(())
117        } else {
118            Err(io::Error::last_os_error())
119        }
120    };
121
122    // SAFETY: CloseHandle is safe to call with a valid handle obtained from OpenProcess
123    unsafe { CloseHandle(handle) };
124    result
125}
126
127pub fn kill(ctx: &Ctx<'_>, pid: u32, signal: Opt<Value<'_>>) -> Result<bool> {
128    let signal = parse_signal(signal.0)?;
129
130    kill_process_raw(pid, signal)
131        .map(|_| true)
132        .or_else(|e| {
133            // Handle "Process Not Found" / "Existence Check" logic
134            // If signal is 0 (check existence) and we hit a specific error, return Ok(false).
135
136            #[cfg(unix)]
137            let is_not_found = e.raw_os_error() == Some(libc::ESRCH); // Error 3
138
139            #[cfg(windows)]
140            let is_not_found = true; // On Windows, any OpenProcess failure during check implies "not found" (or not accessible)
141
142            if signal == 0 && is_not_found {
143                Ok(false)
144            } else {
145                Err(e)
146            }
147        })
148        .or_throw(ctx)
149}