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