1 use std::io;
2 use std::sync::Once;
3 
4 use crate::signal::registry::{globals, EventId, EventInfo, Init, Storage};
5 use crate::signal::RxFuture;
6 
7 use windows_sys::Win32::Foundation::BOOL;
8 use windows_sys::Win32::System::Console as console;
9 
ctrl_break() -> io::Result<RxFuture>10 pub(super) fn ctrl_break() -> io::Result<RxFuture> {
11     new(console::CTRL_BREAK_EVENT)
12 }
13 
ctrl_close() -> io::Result<RxFuture>14 pub(super) fn ctrl_close() -> io::Result<RxFuture> {
15     new(console::CTRL_CLOSE_EVENT)
16 }
17 
ctrl_c() -> io::Result<RxFuture>18 pub(super) fn ctrl_c() -> io::Result<RxFuture> {
19     new(console::CTRL_C_EVENT)
20 }
21 
ctrl_logoff() -> io::Result<RxFuture>22 pub(super) fn ctrl_logoff() -> io::Result<RxFuture> {
23     new(console::CTRL_LOGOFF_EVENT)
24 }
25 
ctrl_shutdown() -> io::Result<RxFuture>26 pub(super) fn ctrl_shutdown() -> io::Result<RxFuture> {
27     new(console::CTRL_SHUTDOWN_EVENT)
28 }
29 
new(signum: u32) -> io::Result<RxFuture>30 fn new(signum: u32) -> io::Result<RxFuture> {
31     global_init()?;
32     let rx = globals().register_listener(signum as EventId);
33     Ok(RxFuture::new(rx))
34 }
35 
36 #[derive(Debug)]
37 pub(crate) struct OsStorage {
38     ctrl_break: EventInfo,
39     ctrl_close: EventInfo,
40     ctrl_c: EventInfo,
41     ctrl_logoff: EventInfo,
42     ctrl_shutdown: EventInfo,
43 }
44 
45 impl Init for OsStorage {
init() -> Self46     fn init() -> Self {
47         Self {
48             ctrl_break: Default::default(),
49             ctrl_close: Default::default(),
50             ctrl_c: Default::default(),
51             ctrl_logoff: Default::default(),
52             ctrl_shutdown: Default::default(),
53         }
54     }
55 }
56 
57 impl Storage for OsStorage {
event_info(&self, id: EventId) -> Option<&EventInfo>58     fn event_info(&self, id: EventId) -> Option<&EventInfo> {
59         match u32::try_from(id) {
60             Ok(console::CTRL_BREAK_EVENT) => Some(&self.ctrl_break),
61             Ok(console::CTRL_CLOSE_EVENT) => Some(&self.ctrl_close),
62             Ok(console::CTRL_C_EVENT) => Some(&self.ctrl_c),
63             Ok(console::CTRL_LOGOFF_EVENT) => Some(&self.ctrl_logoff),
64             Ok(console::CTRL_SHUTDOWN_EVENT) => Some(&self.ctrl_shutdown),
65             _ => None,
66         }
67     }
68 
for_each<'a, F>(&'a self, mut f: F) where F: FnMut(&'a EventInfo),69     fn for_each<'a, F>(&'a self, mut f: F)
70     where
71         F: FnMut(&'a EventInfo),
72     {
73         f(&self.ctrl_break);
74         f(&self.ctrl_close);
75         f(&self.ctrl_c);
76         f(&self.ctrl_logoff);
77         f(&self.ctrl_shutdown);
78     }
79 }
80 
81 #[derive(Debug)]
82 pub(crate) struct OsExtraData {}
83 
84 impl Init for OsExtraData {
init() -> Self85     fn init() -> Self {
86         Self {}
87     }
88 }
89 
global_init() -> io::Result<()>90 fn global_init() -> io::Result<()> {
91     static INIT: Once = Once::new();
92 
93     let mut init = None;
94 
95     INIT.call_once(|| unsafe {
96         let rc = console::SetConsoleCtrlHandler(Some(handler), 1);
97         let ret = if rc == 0 {
98             Err(io::Error::last_os_error())
99         } else {
100             Ok(())
101         };
102 
103         init = Some(ret);
104     });
105 
106     init.unwrap_or_else(|| Ok(()))
107 }
108 
handler(ty: u32) -> BOOL109 unsafe extern "system" fn handler(ty: u32) -> BOOL {
110     let globals = globals();
111     globals.record_event(ty as EventId);
112 
113     // According to https://docs.microsoft.com/en-us/windows/console/handlerroutine
114     // the handler routine is always invoked in a new thread, thus we don't
115     // have the same restrictions as in Unix signal handlers, meaning we can
116     // go ahead and perform the broadcast here.
117     if globals.broadcast() {
118         1
119     } else {
120         // No one is listening for this notification any more
121         // let the OS fire the next (possibly the default) handler.
122         0
123     }
124 }
125 
126 #[cfg(all(test, not(loom)))]
127 mod tests {
128     use super::*;
129     use crate::runtime::Runtime;
130 
131     use tokio_test::{assert_ok, assert_pending, assert_ready_ok, task};
132 
133     #[test]
ctrl_c()134     fn ctrl_c() {
135         let rt = rt();
136         let _enter = rt.enter();
137 
138         let mut ctrl_c = task::spawn(crate::signal::ctrl_c());
139 
140         assert_pending!(ctrl_c.poll());
141 
142         // Windows doesn't have a good programmatic way of sending events
143         // like sending signals on Unix, so we'll stub out the actual OS
144         // integration and test that our handling works.
145         unsafe {
146             super::handler(console::CTRL_C_EVENT);
147         }
148 
149         assert_ready_ok!(ctrl_c.poll());
150     }
151 
152     #[test]
ctrl_break()153     fn ctrl_break() {
154         let rt = rt();
155 
156         rt.block_on(async {
157             let mut ctrl_break = assert_ok!(crate::signal::windows::ctrl_break());
158 
159             // Windows doesn't have a good programmatic way of sending events
160             // like sending signals on Unix, so we'll stub out the actual OS
161             // integration and test that our handling works.
162             unsafe {
163                 super::handler(console::CTRL_BREAK_EVENT);
164             }
165 
166             ctrl_break.recv().await.unwrap();
167         });
168     }
169 
170     #[test]
ctrl_close()171     fn ctrl_close() {
172         let rt = rt();
173 
174         rt.block_on(async {
175             let mut ctrl_close = assert_ok!(crate::signal::windows::ctrl_close());
176 
177             // Windows doesn't have a good programmatic way of sending events
178             // like sending signals on Unix, so we'll stub out the actual OS
179             // integration and test that our handling works.
180             unsafe {
181                 super::handler(console::CTRL_CLOSE_EVENT);
182             }
183 
184             ctrl_close.recv().await.unwrap();
185         });
186     }
187 
188     #[test]
ctrl_shutdown()189     fn ctrl_shutdown() {
190         let rt = rt();
191 
192         rt.block_on(async {
193             let mut ctrl_shutdown = assert_ok!(crate::signal::windows::ctrl_shutdown());
194 
195             // Windows doesn't have a good programmatic way of sending events
196             // like sending signals on Unix, so we'll stub out the actual OS
197             // integration and test that our handling works.
198             unsafe {
199                 super::handler(console::CTRL_SHUTDOWN_EVENT);
200             }
201 
202             ctrl_shutdown.recv().await.unwrap();
203         });
204     }
205 
206     #[test]
ctrl_logoff()207     fn ctrl_logoff() {
208         let rt = rt();
209 
210         rt.block_on(async {
211             let mut ctrl_logoff = assert_ok!(crate::signal::windows::ctrl_logoff());
212 
213             // Windows doesn't have a good programmatic way of sending events
214             // like sending signals on Unix, so we'll stub out the actual OS
215             // integration and test that our handling works.
216             unsafe {
217                 super::handler(console::CTRL_LOGOFF_EVENT);
218             }
219 
220             ctrl_logoff.recv().await.unwrap();
221         });
222     }
223 
rt() -> Runtime224     fn rt() -> Runtime {
225         crate::runtime::Builder::new_current_thread()
226             .build()
227             .unwrap()
228     }
229 }
230