ctrlc_async/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::unix::io::RawFd;
13use std::future::Future;
14
15static mut PIPE: (RawFd, RawFd) = (-1, -1);
16
17/// Platform specific error type
18pub type Error = nix::Error;
19
20/// Platform specific signal type
21pub type Signal = nix::sys::signal::Signal;
22
23extern "C" fn os_handler(_: nix::libc::c_int) {
24    // Assuming this always succeeds. Can't really handle errors in any meaningful way.
25    unsafe {
26        let _ = unistd::write(PIPE.1, &[0u8]);
27    }
28}
29
30// pipe2(2) is not available on macOS or iOS, so we need to use pipe(2) and fcntl(2)
31#[inline]
32#[cfg(any(target_os = "ios", target_os = "macos"))]
33fn pipe2(flags: nix::fcntl::OFlag) -> nix::Result<(RawFd, RawFd)> {
34    use nix::fcntl::{fcntl, FcntlArg, FdFlag, OFlag};
35
36    let pipe = unistd::pipe()?;
37
38    let mut res = Ok(0);
39
40    if flags.contains(OFlag::O_CLOEXEC) {
41        res = res
42            .and_then(|_| fcntl(pipe.0, FcntlArg::F_SETFD(FdFlag::FD_CLOEXEC)))
43            .and_then(|_| fcntl(pipe.1, FcntlArg::F_SETFD(FdFlag::FD_CLOEXEC)));
44    }
45
46    if flags.contains(OFlag::O_NONBLOCK) {
47        res = res
48            .and_then(|_| fcntl(pipe.0, FcntlArg::F_SETFL(OFlag::O_NONBLOCK)))
49            .and_then(|_| fcntl(pipe.1, FcntlArg::F_SETFL(OFlag::O_NONBLOCK)));
50    }
51
52    match res {
53        Ok(_) => Ok(pipe),
54        Err(e) => {
55            let _ = unistd::close(pipe.0);
56            let _ = unistd::close(pipe.1);
57            Err(e)
58        }
59    }
60}
61
62#[inline]
63#[cfg(not(any(target_os = "ios", target_os = "macos")))]
64fn pipe2(flags: nix::fcntl::OFlag) -> nix::Result<(RawFd, RawFd)> {
65    unistd::pipe2(flags)
66}
67
68/// Register os signal handler.
69///
70/// Must be called before calling [`block_ctrl_c()`](fn.block_ctrl_c.html)
71/// and should only be called once.
72///
73/// # Errors
74/// Will return an error if a system error occurred.
75///
76#[inline]
77pub unsafe fn init_os_handler() -> Result<impl Future<Output=Result<(), CtrlcError>>, Error>
78{
79    use nix::fcntl;
80    use nix::sys::signal;
81
82    PIPE = pipe2(fcntl::OFlag::O_CLOEXEC)?;
83
84    let close_pipe = |e: nix::Error| -> Error {
85        // Try to close the pipes. close() should not fail,
86        // but if it does, there isn't much we can do
87        let _ = unistd::close(PIPE.1);
88        let _ = unistd::close(PIPE.0);
89        e
90    };
91
92    // Make sure we never block on write in the os handler.
93    if let Err(e) = fcntl::fcntl(PIPE.1, fcntl::FcntlArg::F_SETFL(fcntl::OFlag::O_NONBLOCK)) {
94        return Err(close_pipe(e));
95    }
96
97    let handler = signal::SigHandler::Handler(os_handler);
98    let new_action = signal::SigAction::new(
99        handler,
100        signal::SaFlags::SA_RESTART,
101        signal::SigSet::empty(),
102    );
103
104    #[allow(unused_variables)]
105    let sigint_old = match signal::sigaction(signal::Signal::SIGINT, &new_action) {
106        Ok(old) => old,
107        Err(e) => return Err(close_pipe(e)),
108    };
109
110    #[cfg(feature = "termination")]
111    {
112        let sigterm_old = match signal::sigaction(signal::Signal::SIGTERM, &new_action) {
113            Ok(old) => old,
114            Err(e) => {
115                signal::sigaction(signal::Signal::SIGINT, &sigint_old).unwrap();
116                return Err(close_pipe(e));
117            }
118        };
119        match signal::sigaction(signal::Signal::SIGHUP, &new_action) {
120            Ok(_) => {}
121            Err(e) => {
122                signal::sigaction(signal::Signal::SIGINT, &sigint_old).unwrap();
123                signal::sigaction(signal::Signal::SIGTERM, &sigterm_old).unwrap();
124                return Err(close_pipe(e));
125            }
126        }
127    }
128
129    Ok(
130        async move {
131            use std::io;
132            use nix::sys::aio::AioCb;
133            use nix::sys::aio::LioOpcode;
134            use nix::sys::signal::SigevNotify;
135            let mut buf = [0u8];
136
137            // TODO: Can we safely convert the pipe fd into a std::io::Read
138            // with std::os::unix::io::FromRawFd, this would handle EINTR
139            // and everything for us.
140            loop {
141                let mut aio = AioCb::from_mut_slice( PIPE.0, 0, &mut buf[..], 0, SigevNotify::SigevNone, LioOpcode::LIO_NOP);
142                aio.read()?;
143                while aio.error() == Err(nix::errno::Errno::EINPROGRESS) {
144                    #[cfg(feature = "tokio")]
145                    tokio::time::sleep(std::time::Duration::from_millis(10)).await;
146                    #[cfg(not(feature = "tokio"))]
147                    std::thread::sleep(std::time::Duration::from_millis(10));
148                }
149                match aio.aio_return() {
150                    Ok(1) => break,
151                    Ok(_) => {
152                        return Err(CtrlcError::System(io::ErrorKind::UnexpectedEof.into()))
153                    },
154                    Err(nix::errno::Errno::EINTR) => {}
155                    Err(e) => return Err(e.into()),
156                }
157            }
158            Ok(())
159        }
160    )
161}