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 | |
10 | use crate::error::Error as CtrlcError; |
11 | use nix::unistd; |
12 | use std::os::unix::io::RawFd; |
13 | |
14 | static mut PIPE: (RawFd, RawFd) = (-1, -1); |
15 | |
16 | /// Platform specific error type |
17 | pub type Error = nix::Error; |
18 | |
19 | /// Platform specific signal type |
20 | pub type Signal = nix::sys::signal::Signal; |
21 | |
22 | extern "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 | ))] |
38 | fn 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 | )))] |
75 | fn 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 ] |
88 | pub 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 ] |
169 | pub 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 | |