1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
//! Synchronous, async-signal-safe SIGINT handling for the scan lifecycle.
//!
//! The CLI runs on a `current_thread` tokio runtime. A long synchronous scan
//! (`subcommands::scan::run`) blocks that single-threaded runtime, so a
//! `tokio::signal::ctrl_c` task spawned at startup never gets polled, its
//! signal handler is never registered, SIGINT falls through to the DEFAULT
//! disposition, and the process dies by signal (status code `None`) with no
//! "Scan interrupted" message instead of the documented exit 130. Installing a
//! real OS handler synchronously before starting the scan fixes this: it fires
//! regardless of runtime scheduling.
//!
//! The handler touches only async-signal-safe operations: relaxed atomic LOADS
//! (via [`crate::interrupt_counts`]), stack-buffer integer formatting (no
//! allocation, no locks), `write(2)`, and `_exit`: so it is safe even if
//! SIGINT lands while the progress ticker holds the stderr lock (an
//! `eprintln!`-based handler could deadlock there).
#[cfg(unix)]
fn append(buf: &mut [u8; 256], len: &mut usize, src: &[u8]) {
for &byte in src {
if *len < buf.len() {
buf[*len] = byte;
*len += 1;
}
}
}
#[cfg(unix)]
fn append_usize(buf: &mut [u8; 256], len: &mut usize, mut value: usize) {
if value == 0 {
append(buf, len, b"0");
return;
}
let mut digits = [0u8; 20];
let mut count = 0;
while value > 0 {
digits[count] = b'0' + (value % 10) as u8;
value /= 10;
count += 1;
}
while count > 0 {
count -= 1;
let digit = digits[count];
append(buf, len, &[digit]);
}
}
#[cfg(unix)]
extern "C" fn handle_sigint(_signum: libc::c_int) {
let (scanned, total, findings) = crate::interrupt_counts();
let mut buf = [0u8; 256];
let mut len = 0;
append(&mut buf, &mut len, b"\nScan interrupted. ");
append_usize(&mut buf, &mut len, scanned);
append(&mut buf, &mut len, b"/");
append_usize(&mut buf, &mut len, total);
append(&mut buf, &mut len, b" files scanned. ");
append_usize(&mut buf, &mut len, findings);
append(&mut buf, &mut len, b" findings.\n");
if crate::operator_profile_active() {
append(
&mut buf,
&mut len,
b"profile outcome status=failed coverage=cancelled errors=1 exit=130 interruption=sigint\n",
);
}
// SAFETY: async-signal-safe primitives only: `write(2)` over a valid
// stack buffer + length, then `_exit` with the documented interrupt
// code (128 + SIGINT). The code is the compile-time `EXIT_INTERRUPTED`
// constant (an immediate value, no allocation/locks/Drop glue), so the
// ONE exit-code owner is honored without breaking signal-safety.
unsafe {
libc::write(2, buf.as_ptr().cast(), len);
libc::_exit(crate::exit_codes::EXIT_INTERRUPTED as libc::c_int);
}
}
pub(crate) fn install() {
#[cfg(unix)]
{
// SAFETY: registering a process-wide handler before the scan runtime starts;
// `handle_sigint` is async-signal-safe.
unsafe {
// Cast the fn item via a thin pointer first: a direct fn-item -> integer
// cast trips `function_casts_as_integer` (a fn item is not an address).
libc::signal(
libc::SIGINT,
handle_sigint as *const () as libc::sighandler_t,
);
}
}
}