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