Skip to content

Commit 58d4936

Browse files
committed
Improved error handling and documentation
- Try to recover from errors. - Document errors and panics. - Simplify error enum. - Use sigaction() insted of signal(). - Use nix crate instead of raw libc bindings. - Use std::io::Error for system errors and errno.
1 parent c542229 commit 58d4936

File tree

2 files changed

+177
-106
lines changed

2 files changed

+177
-106
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ homepage = "https://github.com/Detegr/rust-ctrlc"
1111
repository = "https://github.com/Detegr/rust-ctrlc.git"
1212

1313
[dependencies]
14-
libc = "0.2"
14+
nix = "0.8"
1515
winapi = "0.2"
1616
kernel32-sys = "0.2"
1717

src/lib.rs

Lines changed: 176 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
// notice may not be copied, modified, or distributed except
88
// according to those terms.
99

10+
#![warn(missing_docs)]
11+
1012
//! A simple easy to use wrapper around Ctrl-C.
1113
//! # Example
1214
//! ```no_run
@@ -32,120 +34,121 @@ use std::thread;
3234

3335
static INIT: AtomicBool = ATOMIC_BOOL_INIT;
3436

37+
/// Ctrl-C error.
3538
#[derive(Debug)]
3639
pub enum Error {
37-
Init(String),
38-
MultipleHandlers(String),
39-
SetHandler,
40+
/// Ctrl-C signal handler already registered.
41+
MultipleHandlers,
42+
/// Unexpected system error.
43+
System(std::io::Error),
4044
}
4145

4246
impl fmt::Display for Error {
4347
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
4448
use std::error::Error;
45-
46-
write!(f, "CtrlC error: {}", self.description())
49+
write!(f, "Ctrl-C error: {}", self.description())
4750
}
4851
}
4952

5053
impl std::error::Error for Error {
5154
fn description(&self) -> &str {
5255
match *self {
53-
Error::Init(ref msg) => &msg,
54-
Error::MultipleHandlers(ref msg) => &msg,
55-
Error::SetHandler => "Error setting handler"
56+
Error::MultipleHandlers => "Ctrl-C signal handler already registered",
57+
Error::System(_) => "Unexpected system error",
58+
}
59+
}
60+
61+
fn cause(&self) -> Option<&std::error::Error> {
62+
match *self {
63+
Error::System(ref e) => Some(e),
64+
_ => None,
5665
}
5766
}
5867
}
5968

6069
#[cfg(unix)]
6170
mod platform {
62-
extern crate libc;
63-
64-
use ::Error;
65-
use self::libc::c_int;
66-
use self::libc::{signal, sighandler_t, SIGINT, SIG_ERR, EINTR};
67-
68-
type PipeReadEnd = i32;
69-
type PipeWriteEnd = i32;
70-
pub static mut PIPE_FDS: (PipeReadEnd, PipeWriteEnd) = (-1, -1);
71-
72-
pub use self::libc::{c_void, fcntl, FD_CLOEXEC, F_SETFD, pipe, read, write};
73-
74-
#[cfg(feature = "termination")]
75-
use self::libc::SIGTERM;
76-
77-
extern "C" {
78-
#[cfg_attr(any(target_os = "macos",
79-
target_os = "ios",
80-
target_os = "freebsd"),
81-
link_name = "__error")]
82-
#[cfg_attr(target_os = "dragonfly",
83-
link_name = "__dfly_error")]
84-
#[cfg_attr(any(target_os = "openbsd",
85-
target_os = "bitrig",
86-
target_os = "android"),
87-
link_name = "__errno")]
88-
#[cfg_attr(target_os = "linux",
89-
link_name = "__errno_location")]
90-
fn errno_location() -> *mut c_int;
91-
}
71+
extern crate nix;
9272

93-
unsafe extern "C" fn os_handler(_: c_int) {
94-
// Assuming this always succeeds. Can't really handle errors in any meaningful way.
95-
write(PIPE_FDS.1, &mut 0u8 as *mut _ as *mut c_void, 1);
96-
}
73+
use super::Error;
74+
use self::nix::unistd;
75+
use self::nix::sys::signal;
76+
use std::os::unix::io::RawFd;
77+
use std::io;
9778

98-
#[cfg(feature = "termination")]
99-
#[inline]
100-
unsafe fn set_os_handler(handler: unsafe extern "C" fn(c_int)) -> Result<(), Error> {
101-
if signal(SIGINT, ::std::mem::transmute::<_, sighandler_t>(handler)) == SIG_ERR {
102-
return Err(Error::SetHandler);
103-
}
104-
if signal(SIGTERM, ::std::mem::transmute::<_, sighandler_t>(handler)) == SIG_ERR {
105-
return Err(Error::SetHandler);
106-
}
107-
Ok(())
108-
}
79+
static mut PIPE: (RawFd, RawFd) = (-1, -1);
10980

110-
#[cfg(not(feature = "termination"))]
111-
#[inline]
112-
unsafe fn set_os_handler(handler: unsafe extern "C" fn(c_int)) -> Result<(), Error> {
113-
if signal(SIGINT, ::std::mem::transmute::<_, sighandler_t>(handler)) == SIG_ERR {
114-
return Err(Error::SetHandler);
81+
extern fn os_handler(_: nix::c_int) {
82+
// Assuming this always succeeds. Can't really handle errors in any meaningful way.
83+
unsafe {
84+
unistd::write(PIPE.1, &[0u8]).is_ok();
11585
}
116-
Ok(())
11786
}
11887

119-
/// Register os signal handler, must be called before calling `block_ctrl_c()`.
120-
/// Should only be called once.
88+
/// Register os signal handler.
89+
///
90+
/// Must be called before calling [`block_ctrl_c()`](fn.block_ctrl_c.html)
91+
/// and should only be called once.
92+
///
93+
/// # Errors
94+
/// Will return an error if a recoverable system error occur. An error should leave
95+
/// the system in a constant state, where trying again might succeed.
96+
///
97+
/// # Panics
98+
/// Will panic if a non recoverable system error occur.
99+
///
121100
#[inline]
122101
pub unsafe fn init_os_handler() -> Result<(), Error> {
123-
let mut fds = [0i32, 0];
124-
let pipe_fds = fds.as_mut_ptr();
125-
if pipe(pipe_fds) == -1 {
126-
return Err(Error::Init(format!("pipe failed with error {}", *errno_location())));
127-
}
128-
PIPE_FDS = (*pipe_fds.offset(0), *pipe_fds.offset(1));
129-
if fcntl(PIPE_FDS.0, F_SETFD, FD_CLOEXEC) == -1 {
130-
return Err(Error::Init(format!("fcntl failed with error {}", *errno_location())));
131-
}
132-
if fcntl(PIPE_FDS.1, F_SETFD, FD_CLOEXEC) == -1 {
133-
return Err(Error::Init(format!("fcntl failed with error {}", *errno_location())));
134-
}
135-
set_os_handler(os_handler)
102+
// Should only fail if invalid parameters, FD limit or out of memory.
103+
PIPE = unistd::pipe2(nix::fcntl::O_CLOEXEC).map_err(|e| Error::System(e.into()))?;
104+
105+
let handler = signal::SigHandler::Handler(os_handler);
106+
let new_action = signal::SigAction::new(handler,
107+
signal::SA_RESTART,
108+
signal::SigSet::empty()
109+
);
110+
111+
// Should only fail if invalid parameters,
112+
signal::sigaction(signal::Signal::SIGINT, &new_action).unwrap();
113+
114+
#[cfg(feature = "termination")]
115+
signal::sigaction(signal::Signal::SIGTERM, &new_action).unwrap();
116+
117+
// TODO: Maybe throw an error if old action is not SigDfl.
118+
// Inspecting a SigAction is currently not possible with nix.
119+
120+
Ok(())
136121
}
137122

138123
/// Blocks until a Ctrl-C signal is received.
124+
///
125+
/// Must be called after calling [`init_os_handler()`](fn.init_os_handler.html).
126+
///
127+
/// # Errors
128+
/// Will return an error if a system error occurs.
129+
///
139130
#[inline]
140-
pub unsafe fn block_ctrl_c() {
141-
let mut buf = 0u8;
131+
pub unsafe fn block_ctrl_c() -> Result<(), Error> {
132+
let mut buf = [0u8];
133+
142134
loop {
143-
if read(PIPE_FDS.0, &mut buf as *mut _ as *mut c_void, 1) == -1 {
144-
assert_eq!(*errno_location(), EINTR);
145-
} else {
146-
break;
135+
// TODO: Can we safely convert the pipe fd into a std::io::Read reader
136+
// with std::os::unix::io::FromRawFd, this would handle EINTR
137+
// and everything for us.
138+
match unistd::read(PIPE.0, &mut buf[..]) {
139+
Ok(1) => break,
140+
Ok(_) => return Err(Error::System(io::ErrorKind::UnexpectedEof.into()).into()),
141+
Err(nix::Error::Sys(nix::Errno::EINTR)) => {},
142+
Err(e) => return Err(Error::System(e.into())),
147143
}
148144
}
145+
146+
Ok(())
147+
}
148+
149+
#[cfg(test)]
150+
pub fn raise_ctrl_c() {
151+
signal::raise(signal::Signal::SIGINT).unwrap();
149152
}
150153
}
151154

@@ -154,65 +157,122 @@ mod platform {
154157
extern crate winapi;
155158
extern crate kernel32;
156159

157-
use ::Error;
158-
use self::kernel32::{SetConsoleCtrlHandler, CreateSemaphoreA, ReleaseSemaphore,
159-
WaitForSingleObject};
160-
use self::winapi::{HANDLE, BOOL, DWORD, TRUE, FALSE, INFINITE, WAIT_OBJECT_0, c_long};
161-
160+
use super::Error;
161+
use self::winapi::{HANDLE, BOOL, DWORD, TRUE, FALSE, c_long};
162162
use std::ptr;
163+
use std::io;
163164

164165
const MAX_SEM_COUNT: c_long = 255;
165166
static mut SEMAPHORE: HANDLE = 0 as HANDLE;
166167

167168
unsafe extern "system" fn os_handler(_: DWORD) -> BOOL {
168-
// ReleaseSemaphore() should only fail when the semaphore
169-
// counter has reached its maximum value or if the semaphore
170-
// is invalid, we can therefore safely ignore return value.
171-
ReleaseSemaphore(SEMAPHORE, 1, ptr::null_mut());
169+
// Assuming this always succeeds. Can't really handle errors in any meaningful way.
170+
self::kernel32::ReleaseSemaphore(SEMAPHORE, 1, ptr::null_mut());
172171
TRUE
173172
}
174173

175-
/// Register os signal handler, must be called before calling block_ctrl_c().
176-
/// Should only be called once.
174+
/// Register os signal handler.
175+
///
176+
/// Must be called before calling [`block_ctrl_c()`](fn.block_ctrl_c.html)
177+
/// and should only be called once.
178+
///
179+
/// # Errors
180+
/// Will return an error if a recoverable system error occur. An error should leave
181+
/// the system in a constant state, where trying again might succeed.
182+
///
183+
/// # Panics
184+
/// Will panic if a non recoverable system error occur.
185+
///
177186
#[inline]
178187
pub unsafe fn init_os_handler() -> Result<(), Error> {
179-
SEMAPHORE = CreateSemaphoreA(ptr::null_mut(), 0, MAX_SEM_COUNT, ptr::null());
188+
SEMAPHORE = self::kernel32::CreateSemaphoreA(ptr::null_mut(), 0, MAX_SEM_COUNT, ptr::null());
180189
if SEMAPHORE.is_null() {
181-
return Err(Error::Init("CreateSemaphoreA failed".into()));
190+
return Err(Error::System(io::Error::last_os_error()));
182191
}
183-
if SetConsoleCtrlHandler(Some(os_handler), TRUE) == FALSE {
184-
return Err(Error::SetHandler);
192+
193+
if self::kernel32::SetConsoleCtrlHandler(Some(os_handler), TRUE) == FALSE {
194+
let e = io::Error::last_os_error();
195+
self::kernel32::CloseHandle(SEMAPHORE);
196+
SEMAPHORE = 0 as HANDLE;
197+
return Err(Error::System(e));
185198
}
199+
186200
Ok(())
187201
}
188202

189203
/// Blocks until a Ctrl-C signal is received.
204+
///
205+
/// Must be called after calling [`init_os_handler()`](fn.init_os_handler.html).
206+
///
207+
/// # Errors
208+
/// Will return an error if a system error occurs.
209+
///
190210
#[inline]
191-
pub unsafe fn block_ctrl_c() {
192-
assert_eq!(WaitForSingleObject(SEMAPHORE, INFINITE), WAIT_OBJECT_0);
211+
pub unsafe fn block_ctrl_c() -> Result<(), Error> {
212+
use self::winapi::{INFINITE, WAIT_OBJECT_0, WAIT_FAILED};
213+
214+
match self::kernel32::WaitForSingleObject(SEMAPHORE, INFINITE) {
215+
WAIT_OBJECT_0 => Ok(()),
216+
WAIT_FAILED => Err(Error::System(io::Error::last_os_error())),
217+
ret => Err(Error::System(io::Error::new(
218+
io::ErrorKind::Other,
219+
format!("WaitForSingleObject(), unexpected return value \"{:x}\"", ret),
220+
))),
221+
}
222+
}
223+
224+
#[cfg(test)]
225+
pub fn raise_ctrl_c() {
226+
unsafe {
227+
// This will signal the whole process group.
228+
assert!(self::kernel32::GenerateConsoleCtrlEvent(self::winapi::CTRL_C_EVENT, 0) != 0);
229+
}
193230
}
194231
}
195232

196233
/// Sets up the signal handler for Ctrl-C.
234+
///
197235
/// # Example
198236
/// ```
199237
/// ctrlc::set_handler(|| println!("Hello world!")).expect("Error setting Ctrl-C handler");
200238
/// ```
239+
///
240+
/// # Warning
241+
/// On Unix, any existing SIGINT, SIGTERM(if termination feature is enabled) or SA_SIGINFO posix
242+
/// signal handlers will be overwritten. On Windows, multiple CTRL+C handlers are allowed, but only
243+
/// the first one will be executed.
244+
///
245+
/// # Errors
246+
/// Will return an error if another `ctrlc::set_handler()` handler exists or if a recoverable
247+
/// system error occur while setting the handler. An error should leave the system in a
248+
/// constant state, where trying again might succeed.
249+
///
250+
/// # Panics
251+
/// Will panic if a non recoverable system error occur while setting the handler.
252+
/// The handler runs on its own thread, any panics in handler or other system errors
253+
/// on this thread will not be caught, leading to thread being brought down.
254+
///
201255
pub fn set_handler<F>(user_handler: F) -> Result<(), Error>
202256
where F: Fn() -> () + 'static + Send
203257
{
204-
if INIT.swap(true, Ordering::SeqCst) != false {
205-
return Err(Error::MultipleHandlers("Ctrl-C signal handler already registered".into()));
258+
if INIT.compare_and_swap(false, true, Ordering::SeqCst) {
259+
return Err(Error::MultipleHandlers);
206260
}
207261

208262
unsafe {
209-
try!(platform::init_os_handler());
263+
match platform::init_os_handler() {
264+
Ok(_) => {},
265+
err => {
266+
INIT.store(false, Ordering::SeqCst);
267+
return err;
268+
},
269+
}
210270
}
211271

212272
thread::spawn(move || {
213273
loop {
214274
unsafe {
215-
platform::block_ctrl_c();
275+
platform::block_ctrl_c().expect("Critical system error while waiting for Ctrl-C");
216276
}
217277
user_handler();
218278
}
@@ -221,8 +281,19 @@ pub fn set_handler<F>(user_handler: F) -> Result<(), Error>
221281
Ok(())
222282
}
223283

224-
#[test]
225-
fn test_multiple_handlers() {
226-
assert!(set_handler(|| {}).is_ok());
227-
assert!(set_handler(|| {}).is_err());
284+
#[cfg(test)]
285+
mod tests {
286+
#[test]
287+
fn test_set_handler() {
288+
let (tx, rx) = ::std::sync::mpsc::channel();
289+
super::set_handler(move || {
290+
tx.send(true).unwrap();
291+
}).unwrap();
292+
293+
super::platform::raise_ctrl_c();
294+
295+
rx.recv_timeout(::std::time::Duration::from_secs(1)).unwrap();
296+
297+
assert!(super::set_handler(|| {}).is_err());
298+
}
228299
}

0 commit comments

Comments
 (0)