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 extern crate nix;
11 
12 use self::nix::unistd;
13 use error::Error as CtrlcError;
14 use std::os::unix::io::RawFd;
15 
16 static mut PIPE: (RawFd, RawFd) = (-1, -1);
17 
18 /// Platform specific error type
19 pub type Error = nix::Error;
20 
21 /// Platform specific signal type
22 pub type Signal = nix::sys::signal::Signal;
23 
os_handler(_: nix::libc::c_int)24 extern "C" fn os_handler(_: nix::libc::c_int) {
25     // Assuming this always succeeds. Can't really handle errors in any meaningful way.
26     unsafe {
27         let _ = unistd::write(PIPE.1, &[0u8]);
28     }
29 }
30 
31 /// Register os signal handler.
32 ///
33 /// Must be called before calling [`block_ctrl_c()`](fn.block_ctrl_c.html)
34 /// and should only be called once.
35 ///
36 /// # Errors
37 /// Will return an error if a system error occurred.
38 ///
39 #[inline]
init_os_handler() -> Result<(), Error>40 pub unsafe fn init_os_handler() -> Result<(), Error> {
41     use self::nix::fcntl;
42     use self::nix::sys::signal;
43 
44     PIPE = unistd::pipe2(fcntl::OFlag::O_CLOEXEC)?;
45 
46     let close_pipe = |e: nix::Error| -> Error {
47         // Try to close the pipes. close() should not fail,
48         // but if it does, there isn't much we can do
49         let _ = unistd::close(PIPE.1);
50         let _ = unistd::close(PIPE.0);
51         e
52     };
53 
54     // Make sure we never block on write in the os handler.
55     if let Err(e) = fcntl::fcntl(PIPE.1, fcntl::FcntlArg::F_SETFL(fcntl::OFlag::O_NONBLOCK)) {
56         return Err(close_pipe(e));
57     }
58 
59     let handler = signal::SigHandler::Handler(os_handler);
60     let new_action = signal::SigAction::new(
61         handler,
62         signal::SaFlags::SA_RESTART,
63         signal::SigSet::empty(),
64     );
65 
66     let _old = match signal::sigaction(signal::Signal::SIGINT, &new_action) {
67         Ok(old) => old,
68         Err(e) => return Err(close_pipe(e)),
69     };
70 
71     #[cfg(feature = "termination")]
72     match signal::sigaction(signal::Signal::SIGTERM, &new_action) {
73         Ok(_) => {}
74         Err(e) => {
75             signal::sigaction(signal::Signal::SIGINT, &_old).unwrap();
76             return Err(close_pipe(e));
77         }
78     }
79 
80     // TODO: Maybe throw an error if old action is not SigDfl.
81 
82     Ok(())
83 }
84 
85 /// Blocks until a Ctrl-C signal is received.
86 ///
87 /// Must be called after calling [`init_os_handler()`](fn.init_os_handler.html).
88 ///
89 /// # Errors
90 /// Will return an error if a system error occurred.
91 ///
92 #[inline]
block_ctrl_c() -> Result<(), CtrlcError>93 pub unsafe fn block_ctrl_c() -> Result<(), CtrlcError> {
94     use std::io;
95     let mut buf = [0u8];
96 
97     // TODO: Can we safely convert the pipe fd into a std::io::Read
98     // with std::os::unix::io::FromRawFd, this would handle EINTR
99     // and everything for us.
100     loop {
101         match unistd::read(PIPE.0, &mut buf[..]) {
102             Ok(1) => break,
103             Ok(_) => return Err(CtrlcError::System(io::ErrorKind::UnexpectedEof.into())),
104             Err(nix::Error::Sys(nix::errno::Errno::EINTR)) => {}
105             Err(e) => return Err(e.into()),
106         }
107     }
108 
109     Ok(())
110 }
111