From 216ee8b1083ca408674d640ba9e081fae1ee5b37 Mon Sep 17 00:00:00 2001 From: Henning Ottesen Date: Tue, 30 May 2017 22:10:46 +0200 Subject: [PATCH 1/2] Improved error handling and documentation - Avoid panics. - Cleanup resources on error. - Document errors. - Document SIGTERM. - `no_run` examples. - Use nix crate instead of raw libc bindings. - Use std::io::Error for system errors and errno. - Simplify Error enum. - Use sigaction() instead of signal(). --- Cargo.toml | 2 +- src/lib.rs | 316 +++++++++++++++++++++++++++++++++++------------------ 2 files changed, 211 insertions(+), 107 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1634f89..e16f468 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ homepage = "https://github.com/Detegr/rust-ctrlc" repository = "https://github.com/Detegr/rust-ctrlc.git" [dependencies] -libc = "0.2" +nix = "0.8" winapi = "0.2" kernel32-sys = "0.2" diff --git a/src/lib.rs b/src/lib.rs index 87d1ced..9d1ed16 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,7 +7,20 @@ // notice may not be copied, modified, or distributed except // according to those terms. -//! A simple easy to use wrapper around Ctrl-C. +#![warn(missing_docs)] + +//! Cross platform handling of Ctrl-C signals. +//! +//! [HandlerRoutine]:https://msdn.microsoft.com/en-us/library/windows/desktop/ms683242.aspx +//! +//! [set_handler()](fn.set_handler.html) allows setting a handler closure which is executed on +//! `Ctrl+C`. On Unix, this corresponds to a `SIGINT` signal. On windows, `Ctrl+C` corresponds to +//! [`CTRL_C_EVENT`][HandlerRoutine] or [`CTRL_BREAK_EVENT`][HandlerRoutine]. +//! +//! Setting a handler will start a new dedicated signal handling thread where we +//! execute the handler each time we receive a `Ctrl+C` signal. There can only be +//! one handler, you would typically set one at the start of your program. +//! //! # Example //! ```no_run //! extern crate ctrlc; @@ -17,14 +30,21 @@ //! fn main() { //! let running = Arc::new(AtomicBool::new(true)); //! let r = running.clone(); +//! //! ctrlc::set_handler(move || { //! r.store(false, Ordering::SeqCst); //! }).expect("Error setting Ctrl-C handler"); +//! //! println!("Waiting for Ctrl-C..."); //! while running.load(Ordering::SeqCst) {} //! println!("Got it! Exiting..."); //! } //! ``` +//! +//! # Handling SIGTERM +//! Handling of `SIGTERM` can be enabled with `termination` feature. If this is enabled, +//! the handler specified by `set_handler()` will be executed for both `SIGINT` and `SIGTERM`. +//! use std::fmt; use std::sync::atomic::{AtomicBool, Ordering, ATOMIC_BOOL_INIT}; @@ -32,120 +52,136 @@ use std::thread; static INIT: AtomicBool = ATOMIC_BOOL_INIT; +/// Ctrl-C error. #[derive(Debug)] pub enum Error { - Init(String), - MultipleHandlers(String), - SetHandler, + /// Ctrl-C signal handler already registered. + MultipleHandlers, + /// Unexpected system error. + System(std::io::Error), } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use std::error::Error; - - write!(f, "CtrlC error: {}", self.description()) + write!(f, "Ctrl-C error: {}", self.description()) } } impl std::error::Error for Error { fn description(&self) -> &str { match *self { - Error::Init(ref msg) => &msg, - Error::MultipleHandlers(ref msg) => &msg, - Error::SetHandler => "Error setting handler" + Error::MultipleHandlers => "Ctrl-C signal handler already registered", + Error::System(_) => "Unexpected system error", + } + } + + fn cause(&self) -> Option<&std::error::Error> { + match *self { + Error::System(ref e) => Some(e), + _ => None, } } } #[cfg(unix)] mod platform { - extern crate libc; - - use ::Error; - use self::libc::c_int; - use self::libc::{signal, sighandler_t, SIGINT, SIG_ERR, EINTR}; - - type PipeReadEnd = i32; - type PipeWriteEnd = i32; - pub static mut PIPE_FDS: (PipeReadEnd, PipeWriteEnd) = (-1, -1); - - pub use self::libc::{c_void, fcntl, FD_CLOEXEC, F_SETFD, pipe, read, write}; - - #[cfg(feature = "termination")] - use self::libc::SIGTERM; - - extern "C" { - #[cfg_attr(any(target_os = "macos", - target_os = "ios", - target_os = "freebsd"), - link_name = "__error")] - #[cfg_attr(target_os = "dragonfly", - link_name = "__dfly_error")] - #[cfg_attr(any(target_os = "openbsd", - target_os = "bitrig", - target_os = "android"), - link_name = "__errno")] - #[cfg_attr(target_os = "linux", - link_name = "__errno_location")] - fn errno_location() -> *mut c_int; - } + extern crate nix; - unsafe extern "C" fn os_handler(_: c_int) { - // Assuming this always succeeds. Can't really handle errors in any meaningful way. - write(PIPE_FDS.1, &mut 0u8 as *mut _ as *mut c_void, 1); - } + use super::Error; + use self::nix::unistd; + use std::os::unix::io::RawFd; + use std::io; - #[cfg(feature = "termination")] - #[inline] - unsafe fn set_os_handler(handler: unsafe extern "C" fn(c_int)) -> Result<(), Error> { - if signal(SIGINT, ::std::mem::transmute::<_, sighandler_t>(handler)) == SIG_ERR { - return Err(Error::SetHandler); - } - if signal(SIGTERM, ::std::mem::transmute::<_, sighandler_t>(handler)) == SIG_ERR { - return Err(Error::SetHandler); - } - Ok(()) - } + static mut PIPE: (RawFd, RawFd) = (-1, -1); - #[cfg(not(feature = "termination"))] - #[inline] - unsafe fn set_os_handler(handler: unsafe extern "C" fn(c_int)) -> Result<(), Error> { - if signal(SIGINT, ::std::mem::transmute::<_, sighandler_t>(handler)) == SIG_ERR { - return Err(Error::SetHandler); + extern fn os_handler(_: nix::c_int) { + // Assuming this always succeeds. Can't really handle errors in any meaningful way. + unsafe { + unistd::write(PIPE.1, &[0u8]).is_ok(); } - Ok(()) } - /// Register os signal handler, must be called before calling `block_ctrl_c()`. - /// Should only be called once. + /// Register os signal handler. + /// + /// Must be called before calling [`block_ctrl_c()`](fn.block_ctrl_c.html) + /// and should only be called once. + /// + /// # Errors + /// Will return an error if a system error occurred. + /// #[inline] pub unsafe fn init_os_handler() -> Result<(), Error> { - let mut fds = [0i32, 0]; - let pipe_fds = fds.as_mut_ptr(); - if pipe(pipe_fds) == -1 { - return Err(Error::Init(format!("pipe failed with error {}", *errno_location()))); - } - PIPE_FDS = (*pipe_fds.offset(0), *pipe_fds.offset(1)); - if fcntl(PIPE_FDS.0, F_SETFD, FD_CLOEXEC) == -1 { - return Err(Error::Init(format!("fcntl failed with error {}", *errno_location()))); + use self::nix::fcntl; + use self::nix::sys::signal; + + PIPE = unistd::pipe2(fcntl::O_CLOEXEC).map_err(|e| Error::System(e.into()))?; + + let close_pipe = |e: nix::Error| -> Error { + unistd::close(PIPE.1).is_ok(); + unistd::close(PIPE.0).is_ok(); + Error::System(e.into()) + }; + + // Make sure we never block on write in the os handler. + if let Err(e) = fcntl::fcntl(PIPE.1, fcntl::FcntlArg::F_SETFL(fcntl::O_NONBLOCK)) { + return Err(close_pipe(e)); } - if fcntl(PIPE_FDS.1, F_SETFD, FD_CLOEXEC) == -1 { - return Err(Error::Init(format!("fcntl failed with error {}", *errno_location()))); + + let handler = signal::SigHandler::Handler(os_handler); + let new_action = signal::SigAction::new(handler, + signal::SA_RESTART, + signal::SigSet::empty() + ); + + let _old = match signal::sigaction(signal::Signal::SIGINT, &new_action) { + Ok(old) => old, + Err(e) => return Err(close_pipe(e)), + }; + + #[cfg(feature = "termination")] + match signal::sigaction(signal::Signal::SIGTERM, &new_action) { + Ok(_) => {}, + Err(e) => { + signal::sigaction(signal::Signal::SIGINT, &_old).unwrap(); + return Err(close_pipe(e)); + }, } - set_os_handler(os_handler) + + // TODO: Maybe throw an error if old action is not SigDfl. + + Ok(()) } /// Blocks until a Ctrl-C signal is received. + /// + /// Must be called after calling [`init_os_handler()`](fn.init_os_handler.html). + /// + /// # Errors + /// Will return an error if a system error occurred. + /// #[inline] - pub unsafe fn block_ctrl_c() { - let mut buf = 0u8; + pub unsafe fn block_ctrl_c() -> Result<(), Error> { + let mut buf = [0u8]; + + // TODO: Can we safely convert the pipe fd into a std::io::Read + // with std::os::unix::io::FromRawFd, this would handle EINTR + // and everything for us. loop { - if read(PIPE_FDS.0, &mut buf as *mut _ as *mut c_void, 1) == -1 { - assert_eq!(*errno_location(), EINTR); - } else { - break; + match unistd::read(PIPE.0, &mut buf[..]) { + Ok(1) => break, + Ok(_) => return Err(Error::System(io::ErrorKind::UnexpectedEof.into()).into()), + Err(nix::Error::Sys(nix::Errno::EINTR)) => {}, + Err(e) => return Err(Error::System(e.into())), } } + + Ok(()) + } + + #[cfg(test)] + pub fn raise_ctrl_c() { + signal::raise(signal::Signal::SIGINT).unwrap(); } } @@ -154,65 +190,122 @@ mod platform { extern crate winapi; extern crate kernel32; - use ::Error; - use self::kernel32::{SetConsoleCtrlHandler, CreateSemaphoreA, ReleaseSemaphore, - WaitForSingleObject}; - use self::winapi::{HANDLE, BOOL, DWORD, TRUE, FALSE, INFINITE, WAIT_OBJECT_0, c_long}; - + use super::Error; + use self::winapi::{HANDLE, BOOL, DWORD, TRUE, FALSE, c_long}; use std::ptr; + use std::io; const MAX_SEM_COUNT: c_long = 255; static mut SEMAPHORE: HANDLE = 0 as HANDLE; unsafe extern "system" fn os_handler(_: DWORD) -> BOOL { - // ReleaseSemaphore() should only fail when the semaphore - // counter has reached its maximum value or if the semaphore - // is invalid, we can therefore safely ignore return value. - ReleaseSemaphore(SEMAPHORE, 1, ptr::null_mut()); + // Assuming this always succeeds. Can't really handle errors in any meaningful way. + kernel32::ReleaseSemaphore(SEMAPHORE, 1, ptr::null_mut()); TRUE } - /// Register os signal handler, must be called before calling block_ctrl_c(). - /// Should only be called once. + /// Register os signal handler. + /// + /// Must be called before calling [`block_ctrl_c()`](fn.block_ctrl_c.html) + /// and should only be called once. + /// + /// # Errors + /// Will return an error if a system error occurred. + /// #[inline] pub unsafe fn init_os_handler() -> Result<(), Error> { - SEMAPHORE = CreateSemaphoreA(ptr::null_mut(), 0, MAX_SEM_COUNT, ptr::null()); + SEMAPHORE = kernel32::CreateSemaphoreA(ptr::null_mut(), 0, MAX_SEM_COUNT, ptr::null()); if SEMAPHORE.is_null() { - return Err(Error::Init("CreateSemaphoreA failed".into())); + return Err(Error::System(io::Error::last_os_error())); } - if SetConsoleCtrlHandler(Some(os_handler), TRUE) == FALSE { - return Err(Error::SetHandler); + + if kernel32::SetConsoleCtrlHandler(Some(os_handler), TRUE) == FALSE { + let e = io::Error::last_os_error(); + kernel32::CloseHandle(SEMAPHORE); + SEMAPHORE = 0 as HANDLE; + return Err(Error::System(e)); } + Ok(()) } /// Blocks until a Ctrl-C signal is received. + /// + /// Must be called after calling [`init_os_handler()`](fn.init_os_handler.html). + /// + /// # Errors + /// Will return an error if a system error occurred. + /// #[inline] - pub unsafe fn block_ctrl_c() { - assert_eq!(WaitForSingleObject(SEMAPHORE, INFINITE), WAIT_OBJECT_0); + pub unsafe fn block_ctrl_c() -> Result<(), Error> { + use self::winapi::{INFINITE, WAIT_OBJECT_0, WAIT_FAILED}; + + match kernel32::WaitForSingleObject(SEMAPHORE, INFINITE) { + WAIT_OBJECT_0 => Ok(()), + WAIT_FAILED => Err(Error::System(io::Error::last_os_error())), + ret => Err(Error::System(io::Error::new( + io::ErrorKind::Other, + format!("WaitForSingleObject(), unexpected return value \"{:x}\"", ret), + ))), + } + } + + #[cfg(test)] + pub fn raise_ctrl_c() { + unsafe { + // This will signal the whole process group. + assert!(kernel32::GenerateConsoleCtrlEvent(winapi::CTRL_C_EVENT, 0) != 0); + } } } -/// Sets up the signal handler for Ctrl-C. +/// Register signal handler for Ctrl-C. +/// +/// Starts a new dedicated signal handling thread. Should only be called once, +/// typically at the start of your program. +/// /// # Example -/// ``` +/// ```no_run /// ctrlc::set_handler(|| println!("Hello world!")).expect("Error setting Ctrl-C handler"); /// ``` +/// +/// # Warning +/// On Unix, any existing `SIGINT`, `SIGTERM`(if termination feature is enabled) or `SA_SIGINFO` +/// posix signal handlers will be overwritten. On Windows, multiple handler routines are allowed, +/// but they are called on a last-registered, first-called basis until the signal is handled. +/// +/// On Unix, signal dispositions and signal handlers are inherited by child processes created via +/// `fork(2)` on, but not by child processes created via `execve(2)`. +/// Signal handlers are not inherited on Windows. +/// +/// # Errors +/// Will return an error if another `ctrlc::set_handler()` handler exists or if a +/// system error occurred while setting the handler. +/// +/// # Panics +/// Any panic in the handler will not be caught and will cause the signal handler thread to stop. +/// pub fn set_handler(user_handler: F) -> Result<(), Error> where F: Fn() -> () + 'static + Send { - if INIT.swap(true, Ordering::SeqCst) != false { - return Err(Error::MultipleHandlers("Ctrl-C signal handler already registered".into())); + if INIT.compare_and_swap(false, true, Ordering::SeqCst) { + return Err(Error::MultipleHandlers); } unsafe { - try!(platform::init_os_handler()); + match platform::init_os_handler() { + Ok(_) => {}, + err => { + INIT.store(false, Ordering::SeqCst); + return err; + }, + } } thread::spawn(move || { loop { unsafe { - platform::block_ctrl_c(); + platform::block_ctrl_c().expect("Critical system error while waiting for Ctrl-C"); } user_handler(); } @@ -221,8 +314,19 @@ pub fn set_handler(user_handler: F) -> Result<(), Error> Ok(()) } -#[test] -fn test_multiple_handlers() { - assert!(set_handler(|| {}).is_ok()); - assert!(set_handler(|| {}).is_err()); +#[cfg(test)] +mod tests { + #[test] + fn test_set_handler() { + let (tx, rx) = ::std::sync::mpsc::channel(); + super::set_handler(move || { + tx.send(true).unwrap(); + }).unwrap(); + + super::platform::raise_ctrl_c(); + + rx.recv_timeout(::std::time::Duration::from_secs(1)).unwrap(); + + assert!(super::set_handler(|| {}).is_err()); + } } From 11d3b4f26a2010886d9ff5d93992de0e8a632ffd Mon Sep 17 00:00:00 2001 From: Henning Ottesen Date: Sun, 11 Jun 2017 02:08:40 +0200 Subject: [PATCH 2/2] Custom test harness - Run tests on a secondary console on windows. - Workaround for GenerateConsoleCtrlEvent() signaling the whole process group. - Enforces serialization and deterministic order for tests. - Uses SetStdHandle(). This breaks Rust's stdout for Rust pre 1.18.0. --- Cargo.toml | 5 + src/lib.rs | 30 ------ src/tests.rs | 265 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 270 insertions(+), 30 deletions(-) create mode 100644 src/tests.rs diff --git a/Cargo.toml b/Cargo.toml index e16f468..93838b8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,3 +17,8 @@ kernel32-sys = "0.2" [features] termination = [] + +[[test]] +name = "tests" +path = "src/tests.rs" +harness = false diff --git a/src/lib.rs b/src/lib.rs index 9d1ed16..db6b72e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -178,11 +178,6 @@ mod platform { Ok(()) } - - #[cfg(test)] - pub fn raise_ctrl_c() { - signal::raise(signal::Signal::SIGINT).unwrap(); - } } #[cfg(windows)] @@ -249,14 +244,6 @@ mod platform { ))), } } - - #[cfg(test)] - pub fn raise_ctrl_c() { - unsafe { - // This will signal the whole process group. - assert!(kernel32::GenerateConsoleCtrlEvent(winapi::CTRL_C_EVENT, 0) != 0); - } - } } /// Register signal handler for Ctrl-C. @@ -313,20 +300,3 @@ pub fn set_handler(user_handler: F) -> Result<(), Error> Ok(()) } - -#[cfg(test)] -mod tests { - #[test] - fn test_set_handler() { - let (tx, rx) = ::std::sync::mpsc::channel(); - super::set_handler(move || { - tx.send(true).unwrap(); - }).unwrap(); - - super::platform::raise_ctrl_c(); - - rx.recv_timeout(::std::time::Duration::from_secs(1)).unwrap(); - - assert!(super::set_handler(|| {}).is_err()); - } -} diff --git a/src/tests.rs b/src/tests.rs new file mode 100644 index 0000000..b5a2f0d --- /dev/null +++ b/src/tests.rs @@ -0,0 +1,265 @@ +// Copyright (c) 2015 CtrlC developers +// Licensed under the Apache License, Version 2.0 +// or the MIT +// license , +// at your option. All files in the project carrying such +// notice may not be copied, modified, or distributed except +// according to those terms. + +extern crate ctrlc; + +#[cfg(unix)] +mod platform { + extern crate nix; + + use ::std::io; + + pub unsafe fn setup() -> io::Result<()> { + Ok(()) + } + + pub unsafe fn cleanup() -> io::Result<()> { + Ok(()) + } + + pub unsafe fn raise_ctrl_c() { + self::nix::sys::signal::raise(self::nix::sys::signal::SIGINT).unwrap(); + } + + pub unsafe fn print(fmt: ::std::fmt::Arguments) { + use self::io::Write; + let stdout = ::std::io::stdout(); + stdout.lock().write_fmt(fmt).unwrap(); + } +} + +#[cfg(windows)] +mod platform { + extern crate winapi; + extern crate kernel32; + + use std::io; + use std::ptr; + use self::winapi::winnt::{CHAR, HANDLE}; + use self::winapi::minwindef::DWORD; + + /// Stores a piped stdout handle or a cache that gets flushed when we reattached to the old console. + enum Output { + Pipe(HANDLE), + Cached(Vec), + } + + static mut OLD_OUT: *mut Output = 0 as *mut Output; + + impl io::Write for Output { + fn write(&mut self, buf: &[u8]) -> io::Result { + match *self { + Output::Pipe(handle) => unsafe { + use self::winapi::winnt::VOID; + + let mut n = 0u32; + if self::kernel32::WriteFile( + handle, + buf.as_ptr() as *const VOID, + buf.len() as DWORD, + &mut n as *mut DWORD, + ptr::null_mut() + ) == 0 { + Err(io::Error::last_os_error()) + } else { + Ok(n as usize) + } + }, + Output::Cached(ref mut s) => s.write(buf), + } + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } + } + + impl Output { + /// Stores current piped stdout or creates a new output cache that will + /// be written to stdout at a later time. + fn new() -> io::Result { + use self::winapi::shlobj::INVALID_HANDLE_VALUE; + + unsafe { + let stdout = self::kernel32::GetStdHandle(winapi::STD_OUTPUT_HANDLE); + if stdout.is_null() || stdout == INVALID_HANDLE_VALUE { + return Err(io::Error::last_os_error()); + } + + let mut out = 0u32; + match self::kernel32::GetConsoleMode(stdout, &mut out as *mut DWORD) { + 0 => Ok(Output::Pipe(stdout)), + _ => Ok(Output::Cached(Vec::new())), + } + } + } + + /// Set stdout/stderr and flush cache. + unsafe fn set_as_std(self) -> io::Result<()> { + let stdout = match self { + Output::Pipe(h) => h, + Output::Cached(_) => get_stdout()?, + }; + + if self::kernel32::SetStdHandle(winapi::STD_OUTPUT_HANDLE, stdout) == 0 { + return Err(io::Error::last_os_error()); + } + + if self::kernel32::SetStdHandle(winapi::STD_ERROR_HANDLE, stdout) == 0 { + return Err(io::Error::last_os_error()); + } + + match self { + Output::Pipe(_) => Ok(()), + Output::Cached(ref s) => { + // Write cached output + use self::io::Write; + let out = io::stdout(); + out.lock().write_all(&s[..])?; + Ok(()) + } + } + } + } + + unsafe fn get_stdout() -> io::Result { + use self::winapi::winnt::{GENERIC_READ, GENERIC_WRITE, FILE_SHARE_WRITE}; + use self::winapi::shlobj::INVALID_HANDLE_VALUE; + use self::winapi::fileapi::OPEN_EXISTING; + + let stdout = self::kernel32::CreateFileA( + "CONOUT$\0".as_ptr() as *const CHAR, + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_WRITE, + ptr::null_mut(), + OPEN_EXISTING, + 0, + ptr::null_mut() + ); + + if stdout.is_null() || stdout == INVALID_HANDLE_VALUE { + Err(io::Error::last_os_error()) + } else { + Ok(stdout) + } + } + + /// Detach from the current console and create a new one, + /// We do this because GenerateConsoleCtrlEvent() sends ctrl-c events + /// to all processes on the same console. We want events to be received + /// only by our process. + /// + /// This breaks rust's stdout pre 1.18.0. Rust used to + /// [cache the std handles](https://github.com/rust-lang/rust/pull/40516) + /// + pub unsafe fn setup() -> io::Result<()> { + let old_out = Output::new()?; + + if self::kernel32::FreeConsole() == 0 { + return Err(io::Error::last_os_error()); + } + + if self::kernel32::AllocConsole() == 0 { + return Err(io::Error::last_os_error()); + } + + // AllocConsole will not always set stdout/stderr to the to the console buffer + // of the new terminal. + + let stdout = get_stdout()?; + if self::kernel32::SetStdHandle(winapi::STD_OUTPUT_HANDLE, stdout) == 0 { + return Err(io::Error::last_os_error()); + } + + if self::kernel32::SetStdHandle(winapi::STD_ERROR_HANDLE, stdout) == 0 { + return Err(io::Error::last_os_error()); + } + + OLD_OUT = Box::into_raw(Box::new(old_out)); + + Ok(()) + } + + /// Reattach to the old console. + pub unsafe fn cleanup() -> io::Result<()> { + if self::kernel32::FreeConsole() == 0 { + return Err(io::Error::last_os_error()); + } + + if self::kernel32::AttachConsole(winapi::wincon::ATTACH_PARENT_PROCESS) == 0 { + return Err(io::Error::last_os_error()); + } + + Box::from_raw(OLD_OUT).set_as_std()?; + + Ok(()) + } + + /// This will signal the whole process group. + pub unsafe fn raise_ctrl_c() { + assert!(self::kernel32::GenerateConsoleCtrlEvent(self::winapi::CTRL_C_EVENT, 0) != 0); + } + + /// Print to both consoles, this is not thread safe. + pub unsafe fn print(fmt: ::std::fmt::Arguments) { + use self::io::Write; + { + let stdout = io::stdout(); + stdout.lock().write_fmt(fmt).unwrap(); + } + { + assert!(!OLD_OUT.is_null()); + (*OLD_OUT).write_fmt(fmt).unwrap(); + } + } +} + +fn test_set_handler() { + let (tx, rx) = ::std::sync::mpsc::channel(); + ctrlc::set_handler(move || { + tx.send(true).unwrap(); + }).unwrap(); + + unsafe { platform::raise_ctrl_c(); } + + rx.recv_timeout(::std::time::Duration::from_secs(10)).unwrap(); + + match ctrlc::set_handler(|| {}) { + Err(ctrlc::Error::MultipleHandlers) => {}, + ret => panic!("{:?}", ret), + } +} + +macro_rules! run_tests { + ( $($test_fn:ident),* ) => { + unsafe { + platform::print(format_args!("\n")); + $( + platform::print(format_args!("test tests::{} ... ", stringify!($test_fn))); + $test_fn(); + platform::print(format_args!("ok\n")); + )* + platform::print(format_args!("\n")); + } + } +} + +fn main() { + unsafe { platform::setup().unwrap(); } + + let default = std::panic::take_hook(); + std::panic::set_hook(Box::new(move |info| { + unsafe { platform::cleanup().unwrap(); } + (default)(info); + })); + + run_tests!(test_set_handler); + + unsafe { platform::cleanup().unwrap(); } +} \ No newline at end of file