uucore/features/signals.rs
1// This file is part of the uutils coreutils package.
2//
3// For the full copyright and license information, please view the LICENSE
4// file that was distributed with this source code.
5
6// spell-checker:ignore (vars/api) fcntl setrlimit setitimer rubout pollable sysconf pgrp GETFD pfds revents POLLRDBAND POLLERR
7// spell-checker:ignore (vars/signals) ABRT ALRM CHLD SEGV SIGABRT SIGALRM SIGBUS SIGCHLD SIGCONT SIGDANGER SIGEMT SIGFPE SIGHUP SIGILL SIGINFO SIGINT SIGIO SIGIOT SIGKILL SIGMIGRATE SIGMSG SIGPIPE SIGPRE SIGPROF SIGPWR SIGQUIT SIGSEGV SIGSTOP SIGSYS SIGTALRM SIGTERM SIGTRAP SIGTSTP SIGTHR SIGTTIN SIGTTOU SIGURG SIGUSR SIGVIRT SIGVTALRM SIGWINCH SIGXCPU SIGXFSZ STKFLT PWR THR TSTP TTIN TTOU VIRT VTALRM XCPU XFSZ SIGCLD SIGPOLL SIGWAITING SIGAIOCANCEL SIGLWP SIGFREEZE SIGTHAW SIGCANCEL SIGLOST SIGXRES SIGJVM SIGRTMIN SIGRT SIGRTMAX TALRM AIOCANCEL XRES RTMIN RTMAX LTOSTOP
8
9//! This module provides a way to handle signals in a platform-independent way.
10//! It provides a way to convert signal names to their corresponding values and vice versa.
11//! It also provides a way to ignore the SIGINT signal and enable pipe errors.
12
13#[cfg(unix)]
14use nix::errno::Errno;
15#[cfg(any(target_os = "linux", target_os = "android"))]
16use nix::libc;
17#[cfg(unix)]
18use nix::sys::signal::{
19 SaFlags, SigAction, SigHandler, SigHandler::SigDfl, SigHandler::SigIgn, SigSet, Signal,
20 Signal::SIGINT, Signal::SIGPIPE, sigaction, signal,
21};
22
23/// The default signal value.
24pub static DEFAULT_SIGNAL: usize = 15;
25
26/*
27
28Linux Programmer's Manual
29
30 1 HUP 2 INT 3 QUIT 4 ILL 5 TRAP 6 ABRT 7 BUS
31 8 FPE 9 KILL 10 USR1 11 SEGV 12 USR2 13 PIPE 14 ALRM
3215 TERM 16 STKFLT 17 CHLD 18 CONT 19 STOP 20 TSTP 21 TTIN
3322 TTOU 23 URG 24 XCPU 25 XFSZ 26 VTALRM 27 PROF 28 WINCH
3429 POLL 30 PWR 31 SYS
35
36
37*/
38
39/// The list of all signals.
40#[cfg(any(target_os = "linux", target_os = "android", target_os = "redox"))]
41pub static ALL_SIGNALS: [&str; 32] = [
42 "EXIT", "HUP", "INT", "QUIT", "ILL", "TRAP", "ABRT", "BUS", "FPE", "KILL", "USR1", "SEGV",
43 "USR2", "PIPE", "ALRM", "TERM", "STKFLT", "CHLD", "CONT", "STOP", "TSTP", "TTIN", "TTOU",
44 "URG", "XCPU", "XFSZ", "VTALRM", "PROF", "WINCH", "POLL", "PWR", "SYS",
45];
46
47/*
48
49
50https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man3/signal.3.html
51
52
53No Name Default Action Description
541 SIGHUP terminate process terminal line hangup
552 SIGINT terminate process interrupt program
563 SIGQUIT create core image quit program
574 SIGILL create core image illegal instruction
585 SIGTRAP create core image trace trap
596 SIGABRT create core image abort program (formerly SIGIOT)
607 SIGEMT create core image emulate instruction executed
618 SIGFPE create core image floating-point exception
629 SIGKILL terminate process kill program
6310 SIGBUS create core image bus error
6411 SIGSEGV create core image segmentation violation
6512 SIGSYS create core image non-existent system call invoked
6613 SIGPIPE terminate process write on a pipe with no reader
6714 SIGALRM terminate process real-time timer expired
6815 SIGTERM terminate process software termination signal
6916 SIGURG discard signal urgent condition present on socket
7017 SIGSTOP stop process stop (cannot be caught or ignored)
7118 SIGTSTP stop process stop signal generated from keyboard
7219 SIGCONT discard signal continue after stop
7320 SIGCHLD discard signal child status has changed
7421 SIGTTIN stop process background read attempted from control terminal
7522 SIGTTOU stop process background write attempted to control terminal
7623 SIGIO discard signal I/O is possible on a descriptor (see fcntl(2))
7724 SIGXCPU terminate process cpu time limit exceeded (see setrlimit(2))
7825 SIGXFSZ terminate process file size limit exceeded (see setrlimit(2))
7926 SIGVTALRM terminate process virtual time alarm (see setitimer(2))
8027 SIGPROF terminate process profiling timer alarm (see setitimer(2))
8128 SIGWINCH discard signal Window size change
8229 SIGINFO discard signal status request from keyboard
8330 SIGUSR1 terminate process User defined signal 1
8431 SIGUSR2 terminate process User defined signal 2
85
86*/
87
88#[cfg(any(target_vendor = "apple", target_os = "freebsd"))]
89pub static ALL_SIGNALS: [&str; 32] = [
90 "EXIT", "HUP", "INT", "QUIT", "ILL", "TRAP", "ABRT", "EMT", "FPE", "KILL", "BUS", "SEGV",
91 "SYS", "PIPE", "ALRM", "TERM", "URG", "STOP", "TSTP", "CONT", "CHLD", "TTIN", "TTOU", "IO",
92 "XCPU", "XFSZ", "VTALRM", "PROF", "WINCH", "INFO", "USR1", "USR2",
93];
94
95/*
96
97 The following signals are defined in NetBSD:
98
99 SIGHUP 1 Hangup
100 SIGINT 2 Interrupt
101 SIGQUIT 3 Quit
102 SIGILL 4 Illegal instruction
103 SIGTRAP 5 Trace/BPT trap
104 SIGABRT 6 Abort trap
105 SIGEMT 7 EMT trap
106 SIGFPE 8 Floating point exception
107 SIGKILL 9 Killed
108 SIGBUS 10 Bus error
109 SIGSEGV 11 Segmentation fault
110 SIGSYS 12 Bad system call
111 SIGPIPE 13 Broken pipe
112 SIGALRM 14 Alarm clock
113 SIGTERM 15 Terminated
114 SIGURG 16 Urgent I/O condition
115 SIGSTOP 17 Suspended (signal)
116 SIGTSTP 18 Suspended
117 SIGCONT 19 Continued
118 SIGCHLD 20 Child exited, stopped or continued
119 SIGTTIN 21 Stopped (tty input)
120 SIGTTOU 22 Stopped (tty output)
121 SIGIO 23 I/O possible
122 SIGXCPU 24 CPU time limit exceeded
123 SIGXFSZ 25 File size limit exceeded
124 SIGVTALRM 26 Virtual timer expired
125 SIGPROF 27 Profiling timer expired
126 SIGWINCH 28 Window size changed
127 SIGINFO 29 Information request
128 SIGUSR1 30 User defined signal 1
129 SIGUSR2 31 User defined signal 2
130 SIGPWR 32 Power fail/restart
131*/
132
133#[cfg(target_os = "netbsd")]
134pub static ALL_SIGNALS: [&str; 33] = [
135 "EXIT", "HUP", "INT", "QUIT", "ILL", "TRAP", "ABRT", "EMT", "FPE", "KILL", "BUS", "SEGV",
136 "SYS", "PIPE", "ALRM", "TERM", "URG", "STOP", "TSTP", "CONT", "CHLD", "TTIN", "TTOU", "IO",
137 "XCPU", "XFSZ", "VTALRM", "PROF", "WINCH", "INFO", "USR1", "USR2", "PWR",
138];
139
140/*
141
142 The following signals are defined in OpenBSD:
143
144 SIGHUP terminate process terminal line hangup
145 SIGINT terminate process interrupt program
146 SIGQUIT create core image quit program
147 SIGILL create core image illegal instruction
148 SIGTRAP create core image trace trap
149 SIGABRT create core image abort(3) call (formerly SIGIOT)
150 SIGEMT create core image emulate instruction executed
151 SIGFPE create core image floating-point exception
152 SIGKILL terminate process kill program (cannot be caught or
153 ignored)
154 SIGBUS create core image bus error
155 SIGSEGV create core image segmentation violation
156 SIGSYS create core image system call given invalid argument
157 SIGPIPE terminate process write on a pipe with no reader
158 SIGALRM terminate process real-time timer expired
159 SIGTERM terminate process software termination signal
160 SIGURG discard signal urgent condition present on socket
161 SIGSTOP stop process stop (cannot be caught or ignored)
162 SIGTSTP stop process stop signal generated from keyboard
163 SIGCONT discard signal continue after stop
164 SIGCHLD discard signal child status has changed
165 SIGTTIN stop process background read attempted from control
166 terminal
167 SIGTTOU stop process background write attempted to control
168 terminal
169 SIGIO discard signal I/O is possible on a descriptor (see
170 fcntl(2))
171 SIGXCPU terminate process CPU time limit exceeded (see
172 setrlimit(2))
173 SIGXFSZ terminate process file size limit exceeded (see
174 setrlimit(2))
175 SIGVTALRM terminate process virtual time alarm (see setitimer(2))
176 SIGPROF terminate process profiling timer alarm (see
177 setitimer(2))
178 SIGWINCH discard signal window size change
179 SIGINFO discard signal status request from keyboard
180 SIGUSR1 terminate process user-defined signal 1
181 SIGUSR2 terminate process user-defined signal 2
182 SIGTHR discard signal thread AST
183*/
184
185#[cfg(target_os = "openbsd")]
186pub static ALL_SIGNALS: [&str; 33] = [
187 "EXIT", "HUP", "INT", "QUIT", "ILL", "TRAP", "ABRT", "EMT", "FPE", "KILL", "BUS", "SEGV",
188 "SYS", "PIPE", "ALRM", "TERM", "URG", "STOP", "TSTP", "CONT", "CHLD", "TTIN", "TTOU", "IO",
189 "XCPU", "XFSZ", "VTALRM", "PROF", "WINCH", "INFO", "USR1", "USR2", "THR",
190];
191
192/*
193 The following signals are defined in Solaris and illumos;
194 (the signals for illumos are the same as Solaris, but illumos still has SIGLWP
195 as well as the alias for SIGLWP (SIGAIOCANCEL)):
196
197 SIGHUP 1 hangup
198 SIGINT 2 interrupt (rubout)
199 SIGQUIT 3 quit (ASCII FS)
200 SIGILL 4 illegal instruction (not reset when caught)
201 SIGTRAP 5 trace trap (not reset when caught)
202 SIGIOT 6 IOT instruction
203 SIGABRT 6 used by abort, replace SIGIOT in the future
204 SIGEMT 7 EMT instruction
205 SIGFPE 8 floating point exception
206 SIGKILL 9 kill (cannot be caught or ignored)
207 SIGBUS 10 bus error
208 SIGSEGV 11 segmentation violation
209 SIGSYS 12 bad argument to system call
210 SIGPIPE 13 write on a pipe with no one to read it
211 SIGALRM 14 alarm clock
212 SIGTERM 15 software termination signal from kill
213 SIGUSR1 16 user defined signal 1
214 SIGUSR2 17 user defined signal 2
215 SIGCLD 18 child status change
216 SIGCHLD 18 child status change alias (POSIX)
217 SIGPWR 19 power-fail restart
218 SIGWINCH 20 window size change
219 SIGURG 21 urgent socket condition
220 SIGPOLL 22 pollable event occurred
221 SIGIO SIGPOLL socket I/O possible (SIGPOLL alias)
222 SIGSTOP 23 stop (cannot be caught or ignored)
223 SIGTSTP 24 user stop requested from tty
224 SIGCONT 25 stopped process has been continued
225 SIGTTIN 26 background tty read attempted
226 SIGTTOU 27 background tty write attempted
227 SIGVTALRM 28 virtual timer expired
228 SIGPROF 29 profiling timer expired
229 SIGXCPU 30 exceeded cpu limit
230 SIGXFSZ 31 exceeded file size limit
231 SIGWAITING 32 reserved signal no longer used by threading code
232 SIGAIOCANCEL 33 reserved signal no longer used by threading code (formerly SIGLWP)
233 SIGFREEZE 34 special signal used by CPR
234 SIGTHAW 35 special signal used by CPR
235 SIGCANCEL 36 reserved signal for thread cancellation
236 SIGLOST 37 resource lost (eg, record-lock lost)
237 SIGXRES 38 resource control exceeded
238 SIGJVM1 39 reserved signal for Java Virtual Machine
239 SIGJVM2 40 reserved signal for Java Virtual Machine
240 SIGINFO 41 information request
241 SIGRTMIN ((int)_sysconf(_SC_SIGRT_MIN)) first realtime signal
242 SIGRTMAX ((int)_sysconf(_SC_SIGRT_MAX)) last realtime signal
243*/
244
245#[cfg(target_os = "solaris")]
246const SIGNALS_SIZE: usize = 46;
247
248#[cfg(target_os = "illumos")]
249const SIGNALS_SIZE: usize = 47;
250
251#[cfg(any(target_os = "solaris", target_os = "illumos"))]
252static ALL_SIGNALS: [&str; SIGNALS_SIZE] = [
253 "HUP",
254 "INT",
255 "QUIT",
256 "ILL",
257 "TRAP",
258 "IOT",
259 "ABRT",
260 "EMT",
261 "FPE",
262 "KILL",
263 "BUS",
264 "SEGV",
265 "SYS",
266 "PIPE",
267 "ALRM",
268 "TERM",
269 "USR1",
270 "USR2",
271 "CLD",
272 "CHLD",
273 "PWR",
274 "WINCH",
275 "URG",
276 "POLL",
277 "IO",
278 "STOP",
279 "TSTP",
280 "CONT",
281 "TTIN",
282 "TTOU",
283 "VTALRM",
284 "PROF",
285 "XCPU",
286 "XFSZ",
287 "WAITING",
288 "AIOCANCEL",
289 #[cfg(target_os = "illumos")]
290 "LWP",
291 "FREEZE",
292 "THAW",
293 "CANCEL",
294 "LOST",
295 "XRES",
296 "JVM1",
297 "JVM2",
298 "INFO",
299 "RTMIN",
300 "RTMAX",
301];
302
303/*
304 The following signals are defined in AIX:
305
306 SIGHUP hangup, generated when terminal disconnects
307 SIGINT interrupt, generated from terminal special char
308 SIGQUIT quit, generated from terminal special char
309 SIGILL illegal instruction (not reset when caught)
310 SIGTRAP trace trap (not reset when caught)
311 SIGABRT abort process
312 SIGEMT EMT instruction
313 SIGFPE floating point exception
314 SIGKILL kill (cannot be caught or ignored)
315 SIGBUS bus error (specification exception)
316 SIGSEGV segmentation violation
317 SIGSYS bad argument to system call
318 SIGPIPE write on a pipe with no one to read it
319 SIGALRM alarm clock timeout
320 SIGTERM software termination signal
321 SIGURG urgent condition on I/O channel
322 SIGSTOP stop (cannot be caught or ignored)
323 SIGTSTP interactive stop
324 SIGCONT continue (cannot be caught or ignored)
325 SIGCHLD sent to parent on child stop or exit
326 SIGTTIN background read attempted from control terminal
327 SIGTTOU background write attempted to control terminal
328 SIGIO I/O possible, or completed
329 SIGXCPU cpu time limit exceeded (see setrlimit())
330 SIGXFSZ file size limit exceeded (see setrlimit())
331 SIGMSG input data is in the ring buffer
332 SIGWINCH window size changed
333 SIGPWR power-fail restart
334 SIGUSR1 user defined signal 1
335 SIGUSR2 user defined signal 2
336 SIGPROF profiling time alarm (see setitimer)
337 SIGDANGER system crash imminent; free up some page space
338 SIGVTALRM virtual time alarm (see setitimer)
339 SIGMIGRATE migrate process
340 SIGPRE programming exception
341 SIGVIRT AIX virtual time alarm
342 SIGTALRM per-thread alarm clock
343*/
344#[cfg(target_os = "aix")]
345pub static ALL_SIGNALS: [&str; 37] = [
346 "HUP", "INT", "QUIT", "ILL", "TRAP", "ABRT", "EMT", "FPE", "KILL", "BUS", "SEGV", "SYS",
347 "PIPE", "ALRM", "TERM", "URG", "STOP", "TSTP", "CONT", "CHLD", "TTIN", "TTOU", "IO", "XCPU",
348 "XFSZ", "MSG", "WINCH", "PWR", "USR1", "USR2", "PROF", "DANGER", "VTALRM", "MIGRATE", "PRE",
349 "VIRT", "TALRM",
350];
351
352/*
353 The following signals are defined in Cygwin
354 https://cygwin.com/cgit/newlib-cygwin/tree/winsup/cygwin/include/cygwin/signal.h
355
356 SIGHUP 1 hangup
357 SIGINT 2 interrupt
358 SIGQUIT 3 quit
359 SIGILL 4 illegal instruction (not reset when caught)
360 SIGTRAP 5 trace trap (not reset when caught)
361 SIGABRT 6 used by abort
362 SIGEMT 7 EMT instruction
363 SIGFPE 8 floating point exception
364 SIGKILL 9 kill (cannot be caught or ignored)
365 SIGBUS 10 bus error
366 SIGSEGV 11 segmentation violation
367 SIGSYS 12 bad argument to system call
368 SIGPIPE 13 write on a pipe with no one to read it
369 SIGALRM 14 alarm clock
370 SIGTERM 15 software termination signal from kill
371 SIGURG 16 urgent condition on IO channel
372 SIGSTOP 17 sendable stop signal not from tty
373 SIGTSTP 18 stop signal from tty
374 SIGCONT 19 continue a stopped process
375 SIGCHLD 20 to parent on child stop or exit
376 SIGTTIN 21 to readers pgrp upon background tty read
377 SIGTTOU 22 like TTIN for output if (tp->t_local<OSTOP)
378 SIGIO 23 input/output possible signal
379 SIGXCPU 24 exceeded CPU time limit
380 SIGXFSZ 25 exceeded file size limit
381 SIGVTALRM 26 virtual time alarm
382 SIGPROF 27 profiling time alarm
383 SIGWINCH 28 window changed
384 SIGLOST 29 resource lost (eg, record-lock lost)
385 SIGUSR1 30 user defined signal 1
386 SIGUSR2 31 user defined signal 2
387*/
388#[cfg(target_os = "cygwin")]
389pub static ALL_SIGNALS: [&str; 32] = [
390 "EXIT", "HUP", "INT", "QUIT", "ILL", "TRAP", "ABRT", "EMT", "FPE", "KILL", "BUS", "SEGV",
391 "SYS", "PIPE", "ALRM", "TERM", "URG", "STOP", "TSTP", "CONT", "CHLD", "TTIN", "TTOU", "IO",
392 "XCPU", "XFSZ", "VTALRM", "PROF", "WINCH", "PWR", "USR1", "USR2",
393];
394
395/// Returns the signal number for a given signal name or value.
396pub fn signal_by_name_or_value(signal_name_or_value: &str) -> Option<usize> {
397 let signal_name_upcase = signal_name_or_value.to_uppercase();
398 if let Ok(value) = signal_name_upcase.parse() {
399 if is_signal(value) {
400 return Some(value);
401 }
402 return realtime_signal_bounds()
403 .filter(|&(rtmin, rtmax)| value >= rtmin && value <= rtmax)
404 .map(|_| value);
405 }
406 let signal_name = signal_name_upcase.trim_start_matches("SIG");
407
408 if let Some(pos) = ALL_SIGNALS.iter().position(|&s| s == signal_name) {
409 return Some(pos);
410 }
411
412 realtime_signal_bounds().and_then(|(rtmin, rtmax)| match signal_name {
413 "RTMIN" => Some(rtmin),
414 "RTMAX" => Some(rtmax),
415 _ => None,
416 })
417}
418
419/// Returns true if the given number is a valid signal number.
420pub fn is_signal(num: usize) -> bool {
421 num < ALL_SIGNALS.len()
422}
423
424/// Returns the signal name for a given signal value.
425pub fn signal_name_by_value(signal_value: usize) -> Option<&'static str> {
426 ALL_SIGNALS.get(signal_value).copied()
427}
428
429#[cfg(any(target_os = "linux", target_os = "android"))]
430fn realtime_signal_bounds() -> Option<(usize, usize)> {
431 let rtmin = libc::SIGRTMIN();
432 let rtmax = libc::SIGRTMAX();
433
434 (0 < rtmin && rtmin <= rtmax).then_some((rtmin as usize, rtmax as usize))
435}
436
437#[cfg(not(any(target_os = "linux", target_os = "android")))]
438fn realtime_signal_bounds() -> Option<(usize, usize)> {
439 None
440}
441
442/// Returns the largest signal number that list-style interfaces should accept.
443pub fn signal_number_upper_bound() -> usize {
444 let base = ALL_SIGNALS.len() - 1;
445
446 realtime_signal_bounds().map_or(base, |(_, rtmax)| rtmax.max(base))
447}
448
449/// Returns the signal name for list-style interfaces.
450pub fn signal_list_name_by_value(signal_value: usize) -> Option<String> {
451 if let Some(signal_name) = signal_name_by_value(signal_value) {
452 return Some(signal_name.to_string());
453 }
454
455 realtime_signal_bounds().and_then(|(rtmin, rtmax)| {
456 if signal_value == rtmin {
457 Some("RTMIN".to_string())
458 } else if signal_value == rtmax {
459 Some("RTMAX".to_string())
460 } else {
461 None
462 }
463 })
464}
465
466/// Returns the signal value for list-style interfaces.
467pub fn signal_list_value_by_name_or_number(spec: &str) -> Option<usize> {
468 let spec_upcase = spec.to_uppercase();
469
470 if let Ok(value) = spec_upcase.parse::<usize>() {
471 return (value <= signal_number_upper_bound()).then_some(value);
472 }
473
474 if let Some(value) = signal_by_name_or_value(&spec_upcase) {
475 return Some(value);
476 }
477
478 let signal_name = spec_upcase.trim_start_matches("SIG");
479 realtime_signal_bounds().and_then(|(rtmin, rtmax)| match signal_name {
480 "RTMIN" => Some(rtmin),
481 "RTMAX" => Some(rtmax),
482 _ => None,
483 })
484}
485
486/// Restores SIGPIPE to default behavior (process terminates on broken pipe).
487#[cfg(unix)]
488pub fn enable_pipe_errors() -> Result<(), Errno> {
489 // We pass the error as is, the return value would just be Ok(SigDfl), so we can safely ignore it.
490 // SAFETY: this function is safe as long as we do not use a custom SigHandler -- we use the default one.
491 unsafe { signal(SIGPIPE, SigDfl) }.map(|_| ())
492}
493
494/// Ignores SIGPIPE signal (broken pipe errors are returned instead of terminating).
495/// Use this to override the default SIGPIPE handling when you need to handle
496/// broken pipe errors gracefully (e.g., tee with --output-error).
497#[cfg(unix)]
498pub fn disable_pipe_errors() -> Result<(), Errno> {
499 // SAFETY: this function is safe as long as we do not use a custom SigHandler -- we use the default one.
500 unsafe { signal(SIGPIPE, SigIgn) }.map(|_| ())
501}
502
503/// Ignores the SIGINT signal.
504#[cfg(unix)]
505pub fn ignore_interrupts() -> Result<(), Errno> {
506 // We pass the error as is, the return value would just be Ok(SigIgn), so we can safely ignore it.
507 // SAFETY: this function is safe as long as we do not use a custom SigHandler -- we use the default one.
508 unsafe { signal(SIGINT, SigIgn) }.map(|_| ())
509}
510
511/// Installs a signal handler. The handler must be async-signal-safe.
512#[cfg(unix)]
513pub fn install_signal_handler(
514 sig: Signal,
515 handler: extern "C" fn(std::os::raw::c_int),
516) -> Result<(), Errno> {
517 let action = SigAction::new(
518 SigHandler::Handler(handler),
519 SaFlags::SA_RESTART,
520 SigSet::empty(),
521 );
522 unsafe { sigaction(sig, &action) }?;
523 Ok(())
524}
525
526// Detect closed stdin/stdout before Rust reopens them as /dev/null (see issue #2873)
527#[cfg(unix)]
528use std::sync::atomic::{AtomicBool, Ordering};
529
530#[cfg(unix)]
531static STDIN_WAS_CLOSED: AtomicBool = AtomicBool::new(false);
532#[cfg(unix)]
533static STDOUT_WAS_CLOSED: AtomicBool = AtomicBool::new(false);
534#[cfg(unix)]
535static STDERR_WAS_CLOSED: AtomicBool = AtomicBool::new(false);
536
537// SIGPIPE state capture - captures whether SIGPIPE was ignored at process startup
538#[cfg(unix)]
539static SIGPIPE_WAS_IGNORED: AtomicBool = AtomicBool::new(false);
540
541#[cfg(unix)]
542static STARTUP_STATE_WAS_CAPTURED: AtomicBool = AtomicBool::new(false);
543
544/// Captures stdio and SIGPIPE state at process initialization, before main() runs.
545///
546/// # Safety
547/// Called from `.init_array` before main(). Only reads current state.
548#[cfg(unix)]
549#[allow(clippy::missing_safety_doc)]
550pub unsafe extern "C" fn capture_startup_state() {
551 use nix::libc;
552 use std::mem::MaybeUninit;
553 use std::ptr;
554
555 // No spinlock because we're single-threaded at this point
556 if STARTUP_STATE_WAS_CAPTURED.swap(true, Ordering::Relaxed) {
557 return;
558 }
559
560 // Capture stdio state
561 unsafe {
562 STDIN_WAS_CLOSED.store(
563 libc::fcntl(libc::STDIN_FILENO, libc::F_GETFD) == -1,
564 Ordering::Relaxed,
565 );
566 STDOUT_WAS_CLOSED.store(
567 libc::fcntl(libc::STDOUT_FILENO, libc::F_GETFD) == -1,
568 Ordering::Relaxed,
569 );
570 STDERR_WAS_CLOSED.store(
571 libc::fcntl(libc::STDERR_FILENO, libc::F_GETFD) == -1,
572 Ordering::Relaxed,
573 );
574 }
575
576 // Capture SIGPIPE state
577 let mut current = MaybeUninit::<libc::sigaction>::uninit();
578 // SAFETY: sigaction with null new-action just queries current state
579 if unsafe { libc::sigaction(libc::SIGPIPE, ptr::null(), current.as_mut_ptr()) } == 0 {
580 // SAFETY: sigaction succeeded, so current is initialized
581 let ignored = unsafe { current.assume_init() }.sa_sigaction == libc::SIG_IGN;
582 SIGPIPE_WAS_IGNORED.store(ignored, Ordering::Release);
583 }
584}
585
586/// Initializes startup state capture. Call once at crate root level.
587#[macro_export]
588#[cfg(unix)]
589macro_rules! init_startup_state_capture {
590 () => {
591 #[cfg(not(target_os = "macos"))]
592 #[used]
593 #[unsafe(link_section = ".init_array")]
594 static CAPTURE_STARTUP_STATE: unsafe extern "C" fn() =
595 $crate::signals::capture_startup_state;
596
597 #[cfg(target_os = "macos")]
598 #[used]
599 #[unsafe(link_section = "__DATA,__mod_init_func")]
600 static CAPTURE_STARTUP_STATE: unsafe extern "C" fn() =
601 $crate::signals::capture_startup_state;
602 };
603}
604
605#[macro_export]
606#[cfg(not(unix))]
607macro_rules! init_startup_state_capture {
608 () => {};
609}
610
611#[cfg(unix)]
612pub fn stdin_was_closed() -> bool {
613 STDIN_WAS_CLOSED.load(Ordering::Relaxed)
614}
615
616#[cfg(not(unix))]
617pub const fn stdin_was_closed() -> bool {
618 false
619}
620
621#[cfg(unix)]
622pub fn stdout_was_closed() -> bool {
623 STDOUT_WAS_CLOSED.load(Ordering::Relaxed)
624}
625
626#[cfg(not(unix))]
627pub const fn stdout_was_closed() -> bool {
628 false
629}
630
631#[cfg(unix)]
632pub fn stderr_was_closed() -> bool {
633 STDERR_WAS_CLOSED.load(Ordering::Relaxed)
634}
635
636#[cfg(not(unix))]
637pub const fn stderr_was_closed() -> bool {
638 false
639}
640
641/// Returns whether SIGPIPE was ignored at process startup.
642#[cfg(unix)]
643pub fn sigpipe_was_ignored() -> bool {
644 SIGPIPE_WAS_IGNORED.load(Ordering::Acquire)
645}
646
647#[cfg(not(unix))]
648pub const fn sigpipe_was_ignored() -> bool {
649 false
650}
651
652#[cfg(target_os = "linux")]
653pub fn ensure_stdout_not_broken() -> std::io::Result<bool> {
654 use nix::{
655 poll::{PollFd, PollFlags, PollTimeout, poll},
656 sys::stat::{SFlag, fstat},
657 };
658 use std::io::stdout;
659 use std::os::fd::AsFd;
660
661 let out = stdout();
662
663 // First, check that stdout is a fifo and return true if it's not the case
664 let stat = fstat(out.as_fd())?;
665 if !SFlag::from_bits_truncate(stat.st_mode).contains(SFlag::S_IFIFO) {
666 return Ok(true);
667 }
668
669 // POLLRDBAND is the flag used by GNU tee.
670 let mut pfds = [PollFd::new(out.as_fd(), PollFlags::POLLRDBAND)];
671
672 // Then, ensure that the pipe is not broken.
673 // Use ZERO timeout to return immediately - we just want to check the current state.
674 let res = poll(&mut pfds, PollTimeout::ZERO)?;
675
676 if res > 0 {
677 // poll returned with events ready - check if POLLERR is set (pipe broken)
678 let error = pfds.iter().any(|pfd| {
679 if let Some(revents) = pfd.revents() {
680 revents.contains(PollFlags::POLLERR)
681 } else {
682 true
683 }
684 });
685 return Ok(!error);
686 }
687
688 // res == 0 means no events ready (timeout reached immediately with ZERO timeout).
689 // This means the pipe is healthy (not broken).
690 // res < 0 would be an error, but nix returns Err in that case.
691 Ok(true)
692}
693
694#[test]
695fn signal_by_value() {
696 assert_eq!(signal_by_name_or_value("0"), Some(0));
697 for (value, _signal) in ALL_SIGNALS.iter().enumerate() {
698 assert_eq!(signal_by_name_or_value(&value.to_string()), Some(value));
699 }
700}
701
702#[test]
703fn signal_by_short_name() {
704 for (value, signal) in ALL_SIGNALS.iter().enumerate() {
705 assert_eq!(signal_by_name_or_value(signal), Some(value));
706 }
707}
708
709#[test]
710fn signal_by_long_name() {
711 for (value, signal) in ALL_SIGNALS.iter().enumerate() {
712 assert_eq!(
713 signal_by_name_or_value(&format!("SIG{signal}")),
714 Some(value)
715 );
716 }
717}
718
719#[test]
720fn name() {
721 for (value, signal) in ALL_SIGNALS.iter().enumerate() {
722 assert_eq!(signal_name_by_value(value), Some(*signal));
723 }
724}
725
726#[test]
727fn list_signal_names_match_static_signal_names() {
728 for (value, signal) in ALL_SIGNALS.iter().enumerate() {
729 assert_eq!(signal_list_name_by_value(value), Some(signal.to_string()));
730 }
731}
732
733#[test]
734fn list_signal_numbers_follow_upper_bound() {
735 assert_eq!(
736 signal_list_value_by_name_or_number(&signal_number_upper_bound().to_string()),
737 Some(signal_number_upper_bound())
738 );
739 assert_eq!(
740 signal_list_value_by_name_or_number(&(signal_number_upper_bound() + 1).to_string()),
741 None
742 );
743}
744
745#[cfg(any(target_os = "linux", target_os = "android"))]
746#[test]
747fn linux_realtime_signal_upper_bound_includes_rtmax() {
748 let (_, rtmax) = realtime_signal_bounds().unwrap();
749 assert!(signal_number_upper_bound() >= rtmax);
750}
751
752#[cfg(any(target_os = "linux", target_os = "android"))]
753#[test]
754fn linux_realtime_signal_names_are_listed() {
755 let (rtmin, rtmax) = realtime_signal_bounds().unwrap();
756
757 assert_eq!(signal_list_name_by_value(rtmin), Some("RTMIN".to_string()));
758 assert_eq!(signal_list_name_by_value(rtmax), Some("RTMAX".to_string()));
759}
760
761#[cfg(any(target_os = "linux", target_os = "android"))]
762#[test]
763fn linux_realtime_signal_names_resolve_to_runtime_values() {
764 let (rtmin, rtmax) = realtime_signal_bounds().unwrap();
765
766 assert_eq!(signal_list_value_by_name_or_number("RTMIN"), Some(rtmin));
767 assert_eq!(signal_list_value_by_name_or_number("RTMAX"), Some(rtmax));
768 assert_eq!(signal_list_value_by_name_or_number("SIGRTMIN"), Some(rtmin));
769 assert_eq!(signal_list_value_by_name_or_number("SIGRTMAX"), Some(rtmax));
770}
771
772#[cfg(any(target_os = "linux", target_os = "android"))]
773#[test]
774fn linux_unnamed_signal_numbers_are_valid_for_lists() {
775 assert_eq!(signal_list_value_by_name_or_number("32"), Some(32));
776 assert_eq!(signal_list_value_by_name_or_number("33"), Some(33));
777}
778
779#[cfg(any(target_os = "linux", target_os = "android"))]
780#[test]
781fn linux_realtime_signals_resolve_by_name_or_value() {
782 let (rtmin, rtmax) = realtime_signal_bounds().unwrap();
783
784 // By name
785 assert_eq!(signal_by_name_or_value("RTMIN"), Some(rtmin));
786 assert_eq!(signal_by_name_or_value("RTMAX"), Some(rtmax));
787 assert_eq!(signal_by_name_or_value("SIGRTMIN"), Some(rtmin));
788 assert_eq!(signal_by_name_or_value("SIGRTMAX"), Some(rtmax));
789
790 // By numeric value
791 assert_eq!(signal_by_name_or_value(&rtmin.to_string()), Some(rtmin));
792 assert_eq!(signal_by_name_or_value(&rtmax.to_string()), Some(rtmax));
793}