ctrlc/platform/unix/
mod.rs

1// Copyright (c) 2017 CtrlC developers
2// Licensed under the Apache License, Version 2.0
3// <LICENSE-APACHE or
4// http://www.apache.org/licenses/LICENSE-2.0> or the MIT
5// license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
6// at your option. All files in the project carrying such
7// notice may not be copied, modified, or distributed except
8// according to those terms.
9
10use crate::error::Error as CtrlcError;
11
12#[cfg(not(target_vendor = "apple"))]
13#[allow(static_mut_refs)] // rust-version = "1.69.0"
14mod implementation {
15    static mut SEMAPHORE: nix::libc::sem_t = unsafe { std::mem::zeroed() };
16    const SEM_THREAD_SHARED: nix::libc::c_int = 0;
17
18    pub unsafe fn sem_init() {
19        nix::libc::sem_init(&mut SEMAPHORE as *mut _, SEM_THREAD_SHARED, 0);
20    }
21
22    pub unsafe fn sem_post() {
23        // No errors apply. EOVERFLOW is hypothetically possible but it's equivalent to a success for our oneshot use-case.
24        let _ = nix::libc::sem_post(&mut SEMAPHORE as *mut _);
25    }
26
27    pub unsafe fn sem_wait_forever() {
28        // The only realistic error is EINTR, which is restartable.
29        while nix::libc::sem_wait(&mut SEMAPHORE as *mut _) == -1 {}
30    }
31}
32
33#[cfg(target_vendor = "apple")]
34mod implementation {
35    use dispatch2::{DispatchRetained, DispatchSemaphore, DispatchTime};
36
37    static mut SEMAPHORE: Option<DispatchRetained<DispatchSemaphore>> = None;
38
39    pub unsafe fn sem_init() {
40        SEMAPHORE = Some(DispatchSemaphore::new(0));
41    }
42
43    #[allow(static_mut_refs)]
44    pub unsafe fn sem_post() {
45        SEMAPHORE.as_deref().unwrap().signal();
46    }
47
48    #[allow(static_mut_refs)]
49    pub unsafe fn sem_wait_forever() {
50        SEMAPHORE.as_deref().unwrap().wait(DispatchTime::FOREVER);
51    }
52}
53
54/// Platform specific error type
55pub type Error = nix::Error;
56
57/// Platform specific signal type
58pub type Signal = nix::sys::signal::Signal;
59
60extern "C" fn os_handler(_: nix::libc::c_int) {
61    unsafe {
62        implementation::sem_post();
63    }
64}
65
66/// Register os signal handler.
67///
68/// Must be called before calling [`block_ctrl_c()`](fn.block_ctrl_c.html)
69/// and should only be called once.
70///
71/// # Errors
72/// Will return an error if a system error occurred.
73///
74#[inline]
75pub unsafe fn init_os_handler(overwrite: bool) -> Result<(), Error> {
76    use nix::sys::signal;
77
78    implementation::sem_init();
79
80    let handler = signal::SigHandler::Handler(os_handler);
81    #[cfg(not(target_os = "nto"))]
82    let new_action = signal::SigAction::new(
83        handler,
84        signal::SaFlags::SA_RESTART,
85        signal::SigSet::empty(),
86    );
87    // SA_RESTART is not supported on QNX Neutrino 7.1 and before
88    #[cfg(target_os = "nto")]
89    let new_action =
90        signal::SigAction::new(handler, signal::SaFlags::empty(), signal::SigSet::empty());
91
92    let sigint_old = signal::sigaction(signal::Signal::SIGINT, &new_action)?;
93    if !overwrite && sigint_old.handler() != signal::SigHandler::SigDfl {
94        signal::sigaction(signal::Signal::SIGINT, &sigint_old).unwrap();
95        return Err(nix::Error::EEXIST);
96    }
97
98    #[cfg(feature = "termination")]
99    {
100        let sigterm_old = match signal::sigaction(signal::Signal::SIGTERM, &new_action) {
101            Ok(old) => old,
102            Err(e) => {
103                signal::sigaction(signal::Signal::SIGINT, &sigint_old).unwrap();
104                return Err(e);
105            }
106        };
107        if !overwrite && sigterm_old.handler() != signal::SigHandler::SigDfl {
108            signal::sigaction(signal::Signal::SIGINT, &sigint_old).unwrap();
109            signal::sigaction(signal::Signal::SIGTERM, &sigterm_old).unwrap();
110            return Err(nix::Error::EEXIST);
111        }
112        let sighup_old = match signal::sigaction(signal::Signal::SIGHUP, &new_action) {
113            Ok(old) => old,
114            Err(e) => {
115                signal::sigaction(signal::Signal::SIGINT, &sigint_old).unwrap();
116                signal::sigaction(signal::Signal::SIGTERM, &sigterm_old).unwrap();
117                return Err(e);
118            }
119        };
120        if !overwrite && sighup_old.handler() != signal::SigHandler::SigDfl {
121            signal::sigaction(signal::Signal::SIGINT, &sigint_old).unwrap();
122            signal::sigaction(signal::Signal::SIGTERM, &sigterm_old).unwrap();
123            signal::sigaction(signal::Signal::SIGHUP, &sighup_old).unwrap();
124            return Err(nix::Error::EEXIST);
125        }
126    }
127
128    Ok(())
129}
130
131/// Blocks until a Ctrl-C signal is received.
132///
133/// Must be called after calling [`init_os_handler()`](fn.init_os_handler.html).
134///
135/// # Errors
136/// None.
137///
138#[inline]
139pub unsafe fn block_ctrl_c() -> Result<(), CtrlcError> {
140    implementation::sem_wait_forever();
141    Ok(())
142}