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