• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 use std::ffi::c_void;
2 use std::fmt;
3 use std::fs::File;
4 use std::io;
5 use std::mem::size_of;
6 use std::os::windows::io::AsRawHandle;
7 
8 use windows_sys::Win32::Foundation::{
9     RtlNtStatusToDosError, HANDLE, NTSTATUS, STATUS_NOT_FOUND, STATUS_PENDING, STATUS_SUCCESS,
10 };
11 use windows_sys::Win32::System::WindowsProgramming::{
12     NtDeviceIoControlFile, IO_STATUS_BLOCK, IO_STATUS_BLOCK_0,
13 };
14 
15 const IOCTL_AFD_POLL: u32 = 0x00012024;
16 
17 #[link(name = "ntdll")]
18 extern "system" {
19     /// See <https://processhacker.sourceforge.io/doc/ntioapi_8h.html#a0d4d550cad4d62d75b76961e25f6550c>
20     ///
21     /// This is an undocumented API and as such not part of <https://github.com/microsoft/win32metadata>
22     /// from which `windows-sys` is generated, and also unlikely to be added, so
23     /// we manually declare it here
NtCancelIoFileEx( FileHandle: HANDLE, IoRequestToCancel: *mut IO_STATUS_BLOCK, IoStatusBlock: *mut IO_STATUS_BLOCK, ) -> NTSTATUS24     fn NtCancelIoFileEx(
25         FileHandle: HANDLE,
26         IoRequestToCancel: *mut IO_STATUS_BLOCK,
27         IoStatusBlock: *mut IO_STATUS_BLOCK,
28     ) -> NTSTATUS;
29 }
30 /// Winsock2 AFD driver instance.
31 ///
32 /// All operations are unsafe due to IO_STATUS_BLOCK parameter are being used by Afd driver during STATUS_PENDING before I/O Completion Port returns its result.
33 #[derive(Debug)]
34 pub struct Afd {
35     fd: File,
36 }
37 
38 #[repr(C)]
39 #[derive(Debug)]
40 pub struct AfdPollHandleInfo {
41     pub handle: HANDLE,
42     pub events: u32,
43     pub status: NTSTATUS,
44 }
45 
46 unsafe impl Send for AfdPollHandleInfo {}
47 
48 #[repr(C)]
49 pub struct AfdPollInfo {
50     pub timeout: i64,
51     // Can have only value 1.
52     pub number_of_handles: u32,
53     pub exclusive: u32,
54     pub handles: [AfdPollHandleInfo; 1],
55 }
56 
57 impl fmt::Debug for AfdPollInfo {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result58     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59         f.debug_struct("AfdPollInfo").finish()
60     }
61 }
62 
63 impl Afd {
64     /// Poll `Afd` instance with `AfdPollInfo`.
65     ///
66     /// # Unsafety
67     ///
68     /// This function is unsafe due to memory of `IO_STATUS_BLOCK` still being used by `Afd` instance while `Ok(false)` (`STATUS_PENDING`).
69     /// `iosb` needs to be untouched after the call while operation is in effective at ALL TIME except for `cancel` method.
70     /// So be careful not to `poll` twice while polling.
71     /// User should deallocate there overlapped value when error to prevent memory leak.
poll( &self, info: &mut AfdPollInfo, iosb: *mut IO_STATUS_BLOCK, overlapped: *mut c_void, ) -> io::Result<bool>72     pub unsafe fn poll(
73         &self,
74         info: &mut AfdPollInfo,
75         iosb: *mut IO_STATUS_BLOCK,
76         overlapped: *mut c_void,
77     ) -> io::Result<bool> {
78         let info_ptr = info as *mut _ as *mut c_void;
79         (*iosb).Anonymous.Status = STATUS_PENDING;
80         let status = NtDeviceIoControlFile(
81             self.fd.as_raw_handle() as HANDLE,
82             0,
83             None,
84             overlapped,
85             iosb,
86             IOCTL_AFD_POLL,
87             info_ptr,
88             size_of::<AfdPollInfo>() as u32,
89             info_ptr,
90             size_of::<AfdPollInfo>() as u32,
91         );
92         match status {
93             STATUS_SUCCESS => Ok(true),
94             STATUS_PENDING => Ok(false),
95             _ => Err(io::Error::from_raw_os_error(
96                 RtlNtStatusToDosError(status) as i32
97             )),
98         }
99     }
100 
101     /// Cancel previous polled request of `Afd`.
102     ///
103     /// iosb needs to be used by `poll` first for valid `cancel`.
104     ///
105     /// # Unsafety
106     ///
107     /// This function is unsafe due to memory of `IO_STATUS_BLOCK` still being used by `Afd` instance while `Ok(false)` (`STATUS_PENDING`).
108     /// Use it only with request is still being polled so that you have valid `IO_STATUS_BLOCK` to use.
109     /// User should NOT deallocate there overlapped value after the `cancel` to prevent double free.
cancel(&self, iosb: *mut IO_STATUS_BLOCK) -> io::Result<()>110     pub unsafe fn cancel(&self, iosb: *mut IO_STATUS_BLOCK) -> io::Result<()> {
111         if (*iosb).Anonymous.Status != STATUS_PENDING {
112             return Ok(());
113         }
114 
115         let mut cancel_iosb = IO_STATUS_BLOCK {
116             Anonymous: IO_STATUS_BLOCK_0 { Status: 0 },
117             Information: 0,
118         };
119         let status = NtCancelIoFileEx(self.fd.as_raw_handle() as HANDLE, iosb, &mut cancel_iosb);
120         if status == STATUS_SUCCESS || status == STATUS_NOT_FOUND {
121             return Ok(());
122         }
123         Err(io::Error::from_raw_os_error(
124             RtlNtStatusToDosError(status) as i32
125         ))
126     }
127 }
128 
129 cfg_io_source! {
130     use std::mem::zeroed;
131     use std::os::windows::io::{FromRawHandle, RawHandle};
132     use std::ptr::null_mut;
133     use std::sync::atomic::{AtomicUsize, Ordering};
134 
135     use super::iocp::CompletionPort;
136     use windows_sys::Win32::{
137         Foundation::{UNICODE_STRING, INVALID_HANDLE_VALUE},
138         System::WindowsProgramming::{
139             OBJECT_ATTRIBUTES, FILE_SKIP_SET_EVENT_ON_HANDLE,
140         },
141         Storage::FileSystem::{FILE_OPEN, NtCreateFile, SetFileCompletionNotificationModes, SYNCHRONIZE, FILE_SHARE_READ, FILE_SHARE_WRITE},
142     };
143 
144     const AFD_HELPER_ATTRIBUTES: OBJECT_ATTRIBUTES = OBJECT_ATTRIBUTES {
145         Length: size_of::<OBJECT_ATTRIBUTES>() as u32,
146         RootDirectory: 0,
147         ObjectName: &AFD_OBJ_NAME as *const _ as *mut _,
148         Attributes: 0,
149         SecurityDescriptor: null_mut(),
150         SecurityQualityOfService: null_mut(),
151     };
152 
153     const AFD_OBJ_NAME: UNICODE_STRING = UNICODE_STRING {
154         Length: (AFD_HELPER_NAME.len() * size_of::<u16>()) as u16,
155         MaximumLength: (AFD_HELPER_NAME.len() * size_of::<u16>()) as u16,
156         Buffer: AFD_HELPER_NAME.as_ptr() as *mut _,
157     };
158 
159     const AFD_HELPER_NAME: &[u16] = &[
160         '\\' as _,
161         'D' as _,
162         'e' as _,
163         'v' as _,
164         'i' as _,
165         'c' as _,
166         'e' as _,
167         '\\' as _,
168         'A' as _,
169         'f' as _,
170         'd' as _,
171         '\\' as _,
172         'M' as _,
173         'i' as _,
174         'o' as _
175     ];
176 
177     static NEXT_TOKEN: AtomicUsize = AtomicUsize::new(0);
178 
179     impl AfdPollInfo {
180         pub fn zeroed() -> AfdPollInfo {
181             unsafe { zeroed() }
182         }
183     }
184 
185     impl Afd {
186         /// Create new Afd instance.
187         pub(crate) fn new(cp: &CompletionPort) -> io::Result<Afd> {
188             let mut afd_helper_handle: HANDLE = INVALID_HANDLE_VALUE;
189             let mut iosb = IO_STATUS_BLOCK {
190                 Anonymous: IO_STATUS_BLOCK_0 { Status: 0 },
191                 Information: 0,
192             };
193 
194             unsafe {
195                 let status = NtCreateFile(
196                     &mut afd_helper_handle as *mut _,
197                     SYNCHRONIZE,
198                     &AFD_HELPER_ATTRIBUTES as *const _ as *mut _,
199                     &mut iosb,
200                     null_mut(),
201                     0,
202                     FILE_SHARE_READ | FILE_SHARE_WRITE,
203                     FILE_OPEN,
204                     0,
205                     null_mut(),
206                     0,
207                 );
208                 if status != STATUS_SUCCESS {
209                     let raw_err = io::Error::from_raw_os_error(
210                         RtlNtStatusToDosError(status) as i32
211                     );
212                     let msg = format!("Failed to open \\Device\\Afd\\Mio: {}", raw_err);
213                     return Err(io::Error::new(raw_err.kind(), msg));
214                 }
215                 let fd = File::from_raw_handle(afd_helper_handle as RawHandle);
216                 // Increment by 2 to reserve space for other types of handles.
217                 // Non-AFD types (currently only NamedPipe), use odd numbered
218                 // tokens. This allows the selector to differentate between them
219                 // and dispatch events accordingly.
220                 let token = NEXT_TOKEN.fetch_add(2, Ordering::Relaxed) + 2;
221                 let afd = Afd { fd };
222                 cp.add_handle(token, &afd.fd)?;
223                 match SetFileCompletionNotificationModes(
224                     afd_helper_handle,
225                     FILE_SKIP_SET_EVENT_ON_HANDLE as u8 // This is just 2, so fits in u8
226                 ) {
227                     0 => Err(io::Error::last_os_error()),
228                     _ => Ok(afd),
229                 }
230             }
231         }
232     }
233 }
234 
235 pub const POLL_RECEIVE: u32 = 0b0_0000_0001;
236 pub const POLL_RECEIVE_EXPEDITED: u32 = 0b0_0000_0010;
237 pub const POLL_SEND: u32 = 0b0_0000_0100;
238 pub const POLL_DISCONNECT: u32 = 0b0_0000_1000;
239 pub const POLL_ABORT: u32 = 0b0_0001_0000;
240 pub const POLL_LOCAL_CLOSE: u32 = 0b0_0010_0000;
241 // Not used as it indicated in each event where a connection is connected, not
242 // just the first time a connection is established.
243 // Also see https://github.com/piscisaureus/wepoll/commit/8b7b340610f88af3d83f40fb728e7b850b090ece.
244 pub const POLL_CONNECT: u32 = 0b0_0100_0000;
245 pub const POLL_ACCEPT: u32 = 0b0_1000_0000;
246 pub const POLL_CONNECT_FAIL: u32 = 0b1_0000_0000;
247 
248 pub const KNOWN_EVENTS: u32 = POLL_RECEIVE
249     | POLL_RECEIVE_EXPEDITED
250     | POLL_SEND
251     | POLL_DISCONNECT
252     | POLL_ABORT
253     | POLL_LOCAL_CLOSE
254     | POLL_ACCEPT
255     | POLL_CONNECT_FAIL;
256