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;
11use nix::unistd;
12use std::os::fd::BorrowedFd;
13use std::os::fd::IntoRawFd;
14use std::os::unix::io::RawFd;
15
16static mut PIPE: (RawFd, RawFd) = (-1, -1);
17
18/// Platform specific error type
19pub type Error = nix::Error;
20
21/// Platform specific signal type
22pub type Signal = nix::sys::signal::Signal;
23
24extern "C" fn os_handler(_: nix::libc::c_int) {
25    // Assuming this always succeeds. Can't really handle errors in any meaningful way.
26    unsafe {
27        let fd = BorrowedFd::borrow_raw(PIPE.1);
28        let _ = unistd::write(fd, &[0u8]);
29    }
30}
31
32// pipe2(2) is not available on macOS, iOS, AIX, Haiku, etc., so we need to use pipe(2) and fcntl(2)
33#[inline]
34#[cfg(any(
35    target_vendor = "apple",
36    target_os = "haiku",
37    target_os = "aix",
38    target_os = "nto",
39))]
40fn pipe2(flags: nix::fcntl::OFlag) -> nix::Result<(RawFd, RawFd)> {
41    use nix::fcntl::{fcntl, FcntlArg, FdFlag, OFlag};
42
43    let pipe = unistd::pipe()?;
44    let pipe = (pipe.0.into_raw_fd(), pipe.1.into_raw_fd());
45
46    let mut res = Ok(0);
47
48    if flags.contains(OFlag::O_CLOEXEC) {
49        res = res
50            .and_then(|_| fcntl(pipe.0, FcntlArg::F_SETFD(FdFlag::FD_CLOEXEC)))
51            .and_then(|_| fcntl(pipe.1, FcntlArg::F_SETFD(FdFlag::FD_CLOEXEC)));
52    }
53
54    if flags.contains(OFlag::O_NONBLOCK) {
55        res = res
56            .and_then(|_| fcntl(pipe.0, FcntlArg::F_SETFL(OFlag::O_NONBLOCK)))
57            .and_then(|_| fcntl(pipe.1, FcntlArg::F_SETFL(OFlag::O_NONBLOCK)));
58    }
59
60    match res {
61        Ok(_) => Ok(pipe),
62        Err(e) => {
63            let _ = unistd::close(pipe.0);
64            let _ = unistd::close(pipe.1);
65            Err(e)
66        }
67    }
68}
69
70#[inline]
71#[cfg(not(any(
72    target_vendor = "apple",
73    target_os = "haiku",
74    target_os = "aix",
75    target_os = "nto",
76)))]
77fn pipe2(flags: nix::fcntl::OFlag) -> nix::Result<(RawFd, RawFd)> {
78    let pipe = unistd::pipe2(flags)?;
79    Ok((pipe.0.into_raw_fd(), pipe.1.into_raw_fd()))
80}
81
82/// Register os signal handler.
83///
84/// Must be called before calling [`block_ctrl_c()`](fn.block_ctrl_c.html)
85/// and should only be called once.
86///
87/// # Errors
88/// Will return an error if a system error occurred.
89///
90#[inline]
91pub unsafe fn init_os_handler(overwrite: bool) -> Result<(), Error> {
92    use nix::fcntl;
93    use nix::sys::signal;
94
95    PIPE = pipe2(fcntl::OFlag::O_CLOEXEC)?;
96
97    let close_pipe = |e: nix::Error| -> Error {
98        // Try to close the pipes. close() should not fail,
99        // but if it does, there isn't much we can do
100        let _ = unistd::close(PIPE.1);
101        let _ = unistd::close(PIPE.0);
102        e
103    };
104
105    // Make sure we never block on write in the os handler.
106    if let Err(e) = fcntl::fcntl(PIPE.1, fcntl::FcntlArg::F_SETFL(fcntl::OFlag::O_NONBLOCK)) {
107        return Err(close_pipe(e));
108    }
109
110    let handler = signal::SigHandler::Handler(os_handler);
111    #[cfg(not(target_os = "nto"))]
112    let new_action = signal::SigAction::new(
113        handler,
114        signal::SaFlags::SA_RESTART,
115        signal::SigSet::empty(),
116    );
117    // SA_RESTART is not supported on QNX Neutrino 7.1 and before
118    #[cfg(target_os = "nto")]
119    let new_action =
120        signal::SigAction::new(handler, signal::SaFlags::empty(), signal::SigSet::empty());
121
122    let sigint_old = match signal::sigaction(signal::Signal::SIGINT, &new_action) {
123        Ok(old) => old,
124        Err(e) => return Err(close_pipe(e)),
125    };
126    if !overwrite && sigint_old.handler() != signal::SigHandler::SigDfl {
127        signal::sigaction(signal::Signal::SIGINT, &sigint_old).unwrap();
128        return Err(close_pipe(nix::Error::EEXIST));
129    }
130
131    #[cfg(feature = "termination")]
132    {
133        let sigterm_old = match signal::sigaction(signal::Signal::SIGTERM, &new_action) {
134            Ok(old) => old,
135            Err(e) => {
136                signal::sigaction(signal::Signal::SIGINT, &sigint_old).unwrap();
137                return Err(close_pipe(e));
138            }
139        };
140        if !overwrite && sigterm_old.handler() != signal::SigHandler::SigDfl {
141            signal::sigaction(signal::Signal::SIGINT, &sigint_old).unwrap();
142            signal::sigaction(signal::Signal::SIGTERM, &sigterm_old).unwrap();
143            return Err(close_pipe(nix::Error::EEXIST));
144        }
145        let sighup_old = match signal::sigaction(signal::Signal::SIGHUP, &new_action) {
146            Ok(old) => old,
147            Err(e) => {
148                signal::sigaction(signal::Signal::SIGINT, &sigint_old).unwrap();
149                signal::sigaction(signal::Signal::SIGTERM, &sigterm_old).unwrap();
150                return Err(close_pipe(e));
151            }
152        };
153        if !overwrite && sighup_old.handler() != signal::SigHandler::SigDfl {
154            signal::sigaction(signal::Signal::SIGINT, &sigint_old).unwrap();
155            signal::sigaction(signal::Signal::SIGTERM, &sigterm_old).unwrap();
156            signal::sigaction(signal::Signal::SIGHUP, &sighup_old).unwrap();
157            return Err(close_pipe(nix::Error::EEXIST));
158        }
159    }
160
161    Ok(())
162}
163
164/// Blocks until a Ctrl-C signal is received.
165///
166/// Must be called after calling [`init_os_handler()`](fn.init_os_handler.html).
167///
168/// # Errors
169/// Will return an error if a system error occurred.
170///
171#[inline]
172pub unsafe fn block_ctrl_c() -> Result<(), CtrlcError> {
173    use std::io;
174    let mut buf = [0u8];
175
176    // TODO: Can we safely convert the pipe fd into a std::io::Read
177    // with std::os::unix::io::FromRawFd, this would handle EINTR
178    // and everything for us.
179    loop {
180        match unistd::read(PIPE.0, &mut buf[..]) {
181            Ok(1) => break,
182            Ok(_) => return Err(CtrlcError::System(io::ErrorKind::UnexpectedEof.into())),
183            Err(nix::errno::Errno::EINTR) => {}
184            Err(e) => return Err(e.into()),
185        }
186    }
187
188    Ok(())
189}