ctrlc_tiny/lib.rs
1mod bindings;
2
3use std::{ffi, io, mem, sync::Once};
4
5static INIT: Once = Once::new();
6
7fn init_ctrlc_internal(message: Option<&str>) -> io::Result<()> {
8 let mut result = Ok(());
9 INIT.call_once(|| unsafe {
10 let message = if let Some(message) = message {
11 let c_string = ffi::CString::new(message).unwrap();
12 let ptr = c_string.as_ptr();
13 mem::forget(c_string);
14 ptr
15 } else {
16 std::ptr::null()
17 };
18 if bindings::init_sigint_handler(message) != 0 {
19 result = Err(io::Error::last_os_error());
20 }
21 });
22 result
23}
24
25/// Initializes the SIGINT (Ctrl-C) signal handler.
26///
27/// This function installs a minimal, signal-safe handler for `SIGINT`.
28/// Once installed, any incoming Ctrl-C will set an internal flag,
29/// which can later be queried via [`is_ctrlc_received()`].
30///
31/// This function may be called multiple times;
32/// the signal handler will only be installed once.
33/// Repeated calls are safe and have no additional effect.
34///
35/// # Note
36///
37/// Use either this function OR [`init_ctrlc_with_print()`], not both.
38///
39/// # Errors
40///
41/// Returns an `Err` if the underlying system call (`sigaction`)
42/// fails during handler installation. This typically indicates a
43/// low-level OS error or permission issue.
44///
45/// # Examples
46///
47/// ```rust,no_run
48/// ctrlc_tiny::init_ctrlc()?;
49/// while !ctrlc_tiny::is_ctrlc_received() {
50/// // Do work here
51/// }
52/// # Ok::<_, std::io::Error>(())
53/// ```
54pub fn init_ctrlc() -> io::Result<()> {
55 init_ctrlc_internal(None)
56}
57
58/// Initializes the SIGINT (Ctrl-C) signal handler with a custom message.
59///
60/// This function installs a minimal, signal-safe handler for `SIGINT`.
61/// Once installed, any incoming Ctrl-C will set an internal flag and
62/// print the specified message to stderr.
63/// The flag can later be queried via [`is_ctrlc_received()`].
64///
65/// This function may be called multiple times;
66/// the signal handler will only be installed once.
67/// Repeated calls are safe and have no additional effect.
68///
69/// # Note
70///
71/// Use either this function OR [`init_ctrlc()`], not both.
72///
73/// # Arguments
74///
75/// * `message` - The message to print to stderr when Ctrl-C is pressed
76///
77/// # Errors
78///
79/// Returns an `Err` if the underlying system call (`sigaction`)
80/// fails during handler installation. This typically indicates a
81/// low-level OS error or permission issue.
82///
83/// # Examples
84///
85/// ```rust,no_run
86/// ctrlc_tiny::init_ctrlc_with_print("Ctrl+C pressed\n")?;
87/// while !ctrlc_tiny::is_ctrlc_received() {
88/// // Do work here
89/// }
90/// # Ok::<_, std::io::Error>(())
91/// ```
92pub fn init_ctrlc_with_print(message: &str) -> io::Result<()> {
93 init_ctrlc_internal(Some(message))
94}
95
96/// Checks whether Ctrl-C (SIGINT) has been received.
97///
98/// Returns `true` if a `SIGINT` signal (typically from Ctrl-C)
99/// has been delivered since [`init_ctrlc()`] was called.
100///
101/// Once set, the flag remains `true` for the lifetime of the process.
102///
103/// This function is safe to call from any thread at any time
104/// after initialization.
105///
106/// # Examples
107///
108/// ```rust,no_run
109/// ctrlc_tiny::init_ctrlc_with_print("Ctrl+C pressed\n")?;
110/// while !ctrlc_tiny::is_ctrlc_received() {
111/// // Do work here
112/// }
113/// # Ok::<_, std::io::Error>(())
114/// ```
115pub fn is_ctrlc_received() -> bool {
116 unsafe { bindings::get_is_sigint_received() != 0 }
117}
118
119/// Resets the internal Ctrl-C received flag to `false`.
120///
121/// This can be useful if you want to detect multiple Ctrl-C presses
122/// independently (e.g. "exit on second Ctrl-C").
123///
124/// # Safety
125///
126/// Internally, this clears a `sig_atomic_t` flag that may be concurrently
127/// modified by the signal handler. This is safe but may cause a signal
128/// received during the reset to be missed.
129///
130/// # Examples
131///
132/// ```rust,no_run
133/// ctrlc_tiny::init_ctrlc_with_print("Ctrl+C pressed\n")?;
134/// let mut count = 0;
135/// loop {
136/// if ctrlc_tiny::is_ctrlc_received() {
137/// ctrlc_tiny::reset_ctrlc_received();
138/// count += 1;
139/// if count == 2 {
140/// break;
141/// }
142/// }
143/// }
144/// # Ok::<_, std::io::Error>(())
145/// ```
146pub fn reset_ctrlc_received() {
147 unsafe {
148 bindings::reset_is_sigint_received();
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155
156 #[test]
157 fn init_ctrlc_should_succeed_and_be_idempotent() {
158 assert!(init_ctrlc().is_ok());
159 assert!(init_ctrlc().is_ok());
160 }
161
162 #[test]
163 fn init_ctrlc_with_print_should_succeed() {
164 assert!(init_ctrlc_with_print("Test message\n").is_ok());
165 }
166
167 #[test]
168 fn is_ctrlc_received_initially_false() {
169 assert!(!is_ctrlc_received());
170 reset_ctrlc_received();
171 assert!(!is_ctrlc_received());
172 }
173}