rustpython_vm/stdlib/
signal.rs

1use crate::{builtins::PyModule, PyRef, VirtualMachine};
2
3pub(crate) fn make_module(vm: &VirtualMachine) -> PyRef<PyModule> {
4    let module = _signal::make_module(vm);
5
6    #[cfg(any(unix, windows))]
7    _signal::init_signal_handlers(&module, vm);
8
9    module
10}
11
12#[pymodule]
13pub(crate) mod _signal {
14    use crate::{
15        convert::{IntoPyException, TryFromBorrowedObject},
16        signal, Py, PyObjectRef, PyResult, VirtualMachine,
17    };
18    use std::sync::atomic::{self, Ordering};
19
20    #[cfg(any(unix, windows))]
21    use libc::sighandler_t;
22    #[allow(non_camel_case_types)]
23    #[cfg(not(any(unix, windows)))]
24    type sighandler_t = usize;
25
26    cfg_if::cfg_if! {
27        if #[cfg(windows)] {
28            type WakeupFdRaw = libc::SOCKET;
29            struct WakeupFd(WakeupFdRaw);
30            const INVALID_WAKEUP: libc::SOCKET = windows_sys::Win32::Networking::WinSock::INVALID_SOCKET;
31            static WAKEUP: atomic::AtomicUsize = atomic::AtomicUsize::new(INVALID_WAKEUP);
32            // windows doesn't use the same fds for files and sockets like windows does, so we need
33            // this to know whether to send() or write()
34            static WAKEUP_IS_SOCKET: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
35
36            impl<'a> TryFromBorrowedObject<'a> for WakeupFd {
37                fn try_from_borrowed_object(vm: &VirtualMachine, obj: &'a crate::PyObject) -> PyResult<Self> {
38                    use num_traits::One;
39
40                    let fd: &crate::Py<crate::builtins::PyInt> = obj.try_to_value(vm)?;
41                    match fd.try_to_primitive::<usize>(vm) {
42                        Ok(fd) => Ok(WakeupFd(fd as _)),
43                        Err(e) => if (-fd.as_bigint()).is_one() {
44                            Ok(WakeupFd(INVALID_WAKEUP))
45                        } else {
46                            Err(e)
47                        },
48                    }
49                }
50            }
51        } else {
52            type WakeupFdRaw = i32;
53            type WakeupFd = WakeupFdRaw;
54            const INVALID_WAKEUP: WakeupFd = -1;
55            static WAKEUP: atomic::AtomicI32 = atomic::AtomicI32::new(INVALID_WAKEUP);
56        }
57    }
58
59    #[cfg(unix)]
60    pub use libc::SIG_ERR;
61    #[cfg(unix)]
62    pub use nix::unistd::alarm as sig_alarm;
63
64    #[cfg(unix)]
65    #[pyattr]
66    pub use libc::{SIG_DFL, SIG_IGN};
67
68    #[cfg(not(unix))]
69    #[pyattr]
70    pub const SIG_DFL: sighandler_t = 0;
71    #[cfg(not(unix))]
72    #[pyattr]
73    pub const SIG_IGN: sighandler_t = 1;
74    #[cfg(not(unix))]
75    #[allow(dead_code)]
76    pub const SIG_ERR: sighandler_t = -1 as _;
77
78    #[cfg(all(unix, not(target_os = "redox")))]
79    extern "C" {
80        fn siginterrupt(sig: i32, flag: i32) -> i32;
81    }
82
83    #[pyattr]
84    use crate::signal::NSIG;
85
86    #[cfg(any(unix, windows))]
87    #[pyattr]
88    pub use libc::{SIGABRT, SIGFPE, SIGILL, SIGINT, SIGSEGV, SIGTERM};
89
90    #[cfg(unix)]
91    #[pyattr]
92    use libc::{
93        SIGALRM, SIGBUS, SIGCHLD, SIGCONT, SIGHUP, SIGIO, SIGKILL, SIGPIPE, SIGPROF, SIGQUIT,
94        SIGSTOP, SIGSYS, SIGTRAP, SIGTSTP, SIGTTIN, SIGTTOU, SIGURG, SIGUSR1, SIGUSR2, SIGVTALRM,
95        SIGWINCH, SIGXCPU, SIGXFSZ,
96    };
97
98    #[cfg(unix)]
99    #[cfg(not(any(
100        target_vendor = "apple",
101        target_os = "openbsd",
102        target_os = "freebsd",
103        target_os = "netbsd"
104    )))]
105    #[pyattr]
106    use libc::{SIGPWR, SIGSTKFLT};
107
108    #[cfg(any(unix, windows))]
109    pub(super) fn init_signal_handlers(
110        module: &Py<crate::builtins::PyModule>,
111        vm: &VirtualMachine,
112    ) {
113        let sig_dfl = vm.new_pyobj(SIG_DFL as u8);
114        let sig_ign = vm.new_pyobj(SIG_IGN as u8);
115
116        for signum in 1..NSIG {
117            let handler = unsafe { libc::signal(signum as i32, SIG_IGN) };
118            if handler != SIG_ERR {
119                unsafe { libc::signal(signum as i32, handler) };
120            }
121            let py_handler = if handler == SIG_DFL {
122                Some(sig_dfl.clone())
123            } else if handler == SIG_IGN {
124                Some(sig_ign.clone())
125            } else {
126                None
127            };
128            vm.signal_handlers.as_deref().unwrap().borrow_mut()[signum] = py_handler;
129        }
130
131        let int_handler = module
132            .get_attr("default_int_handler", vm)
133            .expect("_signal does not have this attr?");
134        if vm.state.settings.install_signal_handlers {
135            signal(libc::SIGINT, int_handler, vm).expect("Failed to set sigint handler");
136        }
137    }
138
139    #[cfg(not(any(unix, windows)))]
140    #[pyfunction]
141    pub fn signal(
142        _signalnum: i32,
143        _handler: PyObjectRef,
144        vm: &VirtualMachine,
145    ) -> PyResult<Option<PyObjectRef>> {
146        Err(vm.new_not_implemented_error("signal is not implemented on this platform".to_owned()))
147    }
148
149    #[cfg(any(unix, windows))]
150    #[pyfunction]
151    pub fn signal(
152        signalnum: i32,
153        handler: PyObjectRef,
154        vm: &VirtualMachine,
155    ) -> PyResult<Option<PyObjectRef>> {
156        signal::assert_in_range(signalnum, vm)?;
157        let signal_handlers = vm
158            .signal_handlers
159            .as_deref()
160            .ok_or_else(|| vm.new_value_error("signal only works in main thread".to_owned()))?;
161
162        let sig_handler =
163            match usize::try_from_borrowed_object(vm, &handler).ok() {
164                Some(SIG_DFL) => SIG_DFL,
165                Some(SIG_IGN) => SIG_IGN,
166                None if handler.is_callable() => run_signal as sighandler_t,
167                _ => return Err(vm.new_type_error(
168                    "signal handler must be signal.SIG_IGN, signal.SIG_DFL, or a callable object"
169                        .to_owned(),
170                )),
171            };
172        signal::check_signals(vm)?;
173
174        let old = unsafe { libc::signal(signalnum, sig_handler) };
175        if old == SIG_ERR {
176            return Err(vm.new_os_error("Failed to set signal".to_owned()));
177        }
178        #[cfg(all(unix, not(target_os = "redox")))]
179        unsafe {
180            siginterrupt(signalnum, 1);
181        }
182
183        let old_handler = std::mem::replace(
184            &mut signal_handlers.borrow_mut()[signalnum as usize],
185            Some(handler),
186        );
187        Ok(old_handler)
188    }
189
190    #[pyfunction]
191    fn getsignal(signalnum: i32, vm: &VirtualMachine) -> PyResult {
192        signal::assert_in_range(signalnum, vm)?;
193        let signal_handlers = vm
194            .signal_handlers
195            .as_deref()
196            .ok_or_else(|| vm.new_value_error("getsignal only works in main thread".to_owned()))?;
197        let handler = signal_handlers.borrow()[signalnum as usize]
198            .clone()
199            .unwrap_or_else(|| vm.ctx.none());
200        Ok(handler)
201    }
202
203    #[cfg(unix)]
204    #[pyfunction]
205    fn alarm(time: u32) -> u32 {
206        let prev_time = if time == 0 {
207            sig_alarm::cancel()
208        } else {
209            sig_alarm::set(time)
210        };
211        prev_time.unwrap_or(0)
212    }
213
214    #[pyfunction]
215    fn default_int_handler(
216        _signum: PyObjectRef,
217        _arg: PyObjectRef,
218        vm: &VirtualMachine,
219    ) -> PyResult {
220        Err(vm.new_exception_empty(vm.ctx.exceptions.keyboard_interrupt.to_owned()))
221    }
222
223    #[derive(FromArgs)]
224    struct SetWakeupFdArgs {
225        fd: WakeupFd,
226        #[pyarg(named, default = "true")]
227        warn_on_full_buffer: bool,
228    }
229
230    #[pyfunction]
231    fn set_wakeup_fd(args: SetWakeupFdArgs, vm: &VirtualMachine) -> PyResult<WakeupFdRaw> {
232        // TODO: implement warn_on_full_buffer
233        let _ = args.warn_on_full_buffer;
234        #[cfg(windows)]
235        let fd = args.fd.0;
236        #[cfg(not(windows))]
237        let fd = args.fd;
238
239        if vm.signal_handlers.is_none() {
240            return Err(vm.new_value_error("signal only works in main thread".to_owned()));
241        }
242
243        #[cfg(windows)]
244        let is_socket = if fd != INVALID_WAKEUP {
245            use windows_sys::Win32::Networking::WinSock;
246
247            crate::windows::init_winsock();
248            let mut res = 0i32;
249            let mut res_size = std::mem::size_of::<i32>() as i32;
250            let res = unsafe {
251                WinSock::getsockopt(
252                    fd,
253                    WinSock::SOL_SOCKET,
254                    WinSock::SO_ERROR,
255                    &mut res as *mut i32 as *mut _,
256                    &mut res_size,
257                )
258            };
259            // if getsockopt succeeded, fd is for sure a socket
260            let is_socket = res == 0;
261            if !is_socket {
262                let err = std::io::Error::last_os_error();
263                // if getsockopt failed for some other reason, throw
264                if err.raw_os_error() != Some(WinSock::WSAENOTSOCK) {
265                    return Err(err.into_pyexception(vm));
266                }
267            }
268            is_socket
269        } else {
270            false
271        };
272        #[cfg(unix)]
273        if fd != INVALID_WAKEUP {
274            use nix::fcntl;
275            let oflags = fcntl::fcntl(fd, fcntl::F_GETFL).map_err(|e| e.into_pyexception(vm))?;
276            let nonblock =
277                fcntl::OFlag::from_bits_truncate(oflags).contains(fcntl::OFlag::O_NONBLOCK);
278            if !nonblock {
279                return Err(vm.new_value_error(format!("the fd {fd} must be in non-blocking mode")));
280            }
281        }
282
283        let old_fd = WAKEUP.swap(fd, Ordering::Relaxed);
284        #[cfg(windows)]
285        WAKEUP_IS_SOCKET.store(is_socket, Ordering::Relaxed);
286
287        Ok(old_fd)
288    }
289
290    #[cfg(all(unix, not(target_os = "redox")))]
291    #[pyfunction(name = "siginterrupt")]
292    fn py_siginterrupt(signum: i32, flag: i32, vm: &VirtualMachine) -> PyResult<()> {
293        signal::assert_in_range(signum, vm)?;
294        let res = unsafe { siginterrupt(signum, flag) };
295        if res < 0 {
296            Err(crate::stdlib::os::errno_err(vm))
297        } else {
298            Ok(())
299        }
300    }
301
302    #[cfg(any(unix, windows))]
303    pub extern "C" fn run_signal(signum: i32) {
304        signal::TRIGGERS[signum as usize].store(true, Ordering::Relaxed);
305        signal::set_triggered();
306        let wakeup_fd = WAKEUP.load(Ordering::Relaxed);
307        if wakeup_fd != INVALID_WAKEUP {
308            let sigbyte = signum as u8;
309            #[cfg(windows)]
310            if WAKEUP_IS_SOCKET.load(Ordering::Relaxed) {
311                let _res = unsafe {
312                    windows_sys::Win32::Networking::WinSock::send(
313                        wakeup_fd,
314                        &sigbyte as *const u8 as *const _,
315                        1,
316                        0,
317                    )
318                };
319                return;
320            }
321            let _res = unsafe { libc::write(wakeup_fd as _, &sigbyte as *const u8 as *const _, 1) };
322            // TODO: handle _res < 1, support warn_on_full_buffer
323        }
324    }
325}