include/boost/corosio/native/detail/iocp/win_validate_handle.hpp

92.2% Lines (83 / 90) 85.2% Branches (75 / 88) 100.0% Functions (7 / 7)
win_validate_handle.hpp
f(x) Functions (7)
Line Branch TLA Hits Source Code
1 //
2 // Copyright (c) 2026 Michael Vandeberg
3 //
4 // Distributed under the Boost Software License, Version 1.0. (See accompanying
5 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6 //
7 // Official repository: https://github.com/cppalliance/corosio
8 //
9
10 #ifndef BOOST_COROSIO_NATIVE_DETAIL_IOCP_WIN_VALIDATE_HANDLE_HPP
11 #define BOOST_COROSIO_NATIVE_DETAIL_IOCP_WIN_VALIDATE_HANDLE_HPP
12
13 #include <boost/corosio/detail/platform.hpp>
14
15 #if BOOST_COROSIO_HAS_IOCP
16
17 #include <boost/corosio/native/detail/iocp/win_windows.hpp>
18
19 #include <cstdint>
20 #include <cstring>
21 #include <string_view>
22 #include <system_error>
23
24 /* The adopt-time gates for Windows handles -- the counterpart of
25 validate_fd.hpp. A gate only inspects the handle. None performs I/O
26 or waits on it, so an auto-reset event or a semaphore keeps its
27 signal across a gate, accepted or rejected.
28
29 Synchronous-mode handles are rejected by querying the file mode
30 rather than by letting CreateIoCompletionPort fail: whether it
31 refuses such a handle is undocumented, and an overlapped ReadFile on
32 one silently runs to completion inside the call.
33 */
34
35 namespace boost::corosio::detail {
36
37 /// The adopting type; the gates differ per type.
38 enum class handle_kind
39 {
40 stream_file,
41 random_access_file,
42 stream_handle,
43 random_access_handle
44 };
45
46 namespace win_nt {
47
48 using ntstatus = LONG;
49
50 struct io_status_block
51 {
52 union
53 {
54 ntstatus Status;
55 void* Pointer;
56 };
57 ULONG_PTR Information;
58 };
59
60 using query_information_file_fn =
61 ntstatus(NTAPI*)(HANDLE, io_status_block*, void*, ULONG, int);
62 using query_object_fn = ntstatus(NTAPI*)(HANDLE, int, void*, ULONG, ULONG*);
63
64 inline constexpr int file_mode_information = 16;
65 inline constexpr int file_io_completion_notification_information = 41;
66 inline constexpr int object_basic_information = 0;
67 inline constexpr int object_type_information = 2;
68
69 inline constexpr ULONG file_synchronous_io_alert = 0x10;
70 inline constexpr ULONG file_synchronous_io_nonalert = 0x20;
71 inline constexpr ULONG file_skip_completion_port_on_success = 0x1;
72
73 struct object_basic_info
74 {
75 ULONG Attributes;
76 ACCESS_MASK GrantedAccess;
77 ULONG HandleCount;
78 ULONG PointerCount;
79 ULONG Reserved[10];
80 };
81
82 struct unicode_string
83 {
84 USHORT Length;
85 USHORT MaximumLength;
86 PWSTR Buffer;
87 };
88
89 // The two-step cast through void(*)() is the sanctioned FARPROC
90 // conversion; a direct cast trips -Wcast-function-type.
91 template<class Fn>
92 Fn
93 24x ntdll_proc(char const* name) noexcept
94 {
95
1/2
✓ Branch 3 → 4 taken 24 times.
✗ Branch 3 → 6 not taken.
24x if (HMODULE h = ::GetModuleHandleW(L"ntdll.dll"))
96 return reinterpret_cast<Fn>(
97 24x reinterpret_cast<void (*)()>(::GetProcAddress(h, name)));
98 ✗ return nullptr;
99 }
100
101 inline query_information_file_fn
102 702x query_information_file() noexcept
103 {
104 static query_information_file_fn const fn =
105
3/4
✓ Branch 2 → 3 taken 20 times.
✓ Branch 2 → 7 taken 682 times.
✓ Branch 4 → 5 taken 20 times.
✗ Branch 4 → 7 not taken.
702x ntdll_proc<query_information_file_fn>("NtQueryInformationFile");
106 702x return fn;
107 }
108
109 inline query_object_fn
110 2829x query_object() noexcept
111 {
112 static query_object_fn const fn =
113
3/4
✓ Branch 2 → 3 taken 4 times.
✓ Branch 2 → 7 taken 2825 times.
✓ Branch 4 → 5 taken 4 times.
✗ Branch 4 → 7 not taken.
2829x ntdll_proc<query_object_fn>("NtQueryObject");
114 2829x return fn;
115 }
116
117 } // namespace win_nt
118
119 /** Check whether a handle is in skip-completion-port-on-success mode.
120
121 Such a handle queues no packet for an operation that succeeds
122 synchronously, so an operation issued through a completion port
123 would never complete. The mode cannot be cleared.
124
125 @param h The handle to inspect. Never read, written or waited on.
126
127 @return `true` if the mode is set. A failed query means it never
128 was.
129 */
130 inline bool
131 400x has_skip_on_success(HANDLE h) noexcept
132 {
133 400x auto const query = win_nt::query_information_file();
134
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 400 times.
400x if (!query)
135 ✗ return false;
136 400x win_nt::io_status_block iosb{};
137 400x ULONG notify = 0;
138 400x return query(h, &iosb, &notify, sizeof(notify),
139
2/2
✓ Branch 6 → 7 taken 398 times.
✓ Branch 6 → 9 taken 2 times.
798x win_nt::file_io_completion_notification_information) >= 0 &&
140
2/2
✓ Branch 7 → 8 taken 9 times.
✓ Branch 7 → 9 taken 389 times.
798x (notify & win_nt::file_skip_completion_port_on_success);
141 }
142
143 /** Validate a handle for adoption by an overlapped type.
144
145 @param h The handle to inspect. Never read, written or waited on.
146 @param kind The adopting type.
147
148 @return `bad_file_descriptor` for a null, invalid or closed
149 handle; `operation_not_supported` for a console, a socket, a
150 synchronous-mode handle, a handle already in
151 skip-completion-port-on-success mode, a directory, a disk
152 handle adopted
153 by `win_stream_handle`, or a pipe adopted by a file type;
154 otherwise an empty code. A missing `ntdll` entry point fails
155 closed with `operation_not_supported`.
156 */
157 inline std::error_code
158 320x validate_overlapped_handle(HANDLE h, handle_kind kind) noexcept
159 {
160 auto const not_supported =
161 320x std::make_error_code(std::errc::operation_not_supported);
162
163
4/4
✓ Branch 3 → 4 taken 315 times.
✓ Branch 3 → 5 taken 5 times.
✓ Branch 4 → 5 taken 8 times.
✓ Branch 4 → 6 taken 307 times.
320x if (h == nullptr || h == INVALID_HANDLE_VALUE)
164 13x return std::make_error_code(std::errc::bad_file_descriptor);
165
166 307x ::SetLastError(NO_ERROR);
167 307x DWORD const type = ::GetFileType(h);
168
5/6
✓ Branch 8 → 9 taken 1 time.
✓ Branch 8 → 12 taken 306 times.
✓ Branch 10 → 11 taken 1 time.
✗ Branch 10 → 12 not taken.
✓ Branch 13 → 14 taken 1 time.
✓ Branch 13 → 15 taken 306 times.
307x if (type == FILE_TYPE_UNKNOWN && ::GetLastError() != NO_ERROR)
169 1x return std::make_error_code(std::errc::bad_file_descriptor);
170
171 306x DWORD console_mode = 0;
172
5/6
✓ Branch 15 → 16 taken 4 times.
✓ Branch 15 → 19 taken 302 times.
✓ Branch 17 → 18 taken 4 times.
✗ Branch 17 → 19 not taken.
✓ Branch 20 → 21 taken 4 times.
✓ Branch 20 → 22 taken 302 times.
306x if (type == FILE_TYPE_CHAR && ::GetConsoleMode(h, &console_mode))
173 4x return not_supported;
174
175 302x auto const query = win_nt::query_information_file();
176
1/2
✗ Branch 23 → 24 not taken.
✓ Branch 23 → 25 taken 302 times.
302x if (!query)
177 ✗ return not_supported;
178 302x win_nt::io_status_block iosb{};
179 302x ULONG mode = 0;
180
1/2
✗ Branch 26 → 27 not taken.
✓ Branch 26 → 28 taken 302 times.
302x if (query(h, &iosb, &mode, sizeof(mode), win_nt::file_mode_information) <
181 0)
182 ✗ return not_supported;
183
2/2
✓ Branch 28 → 29 taken 21 times.
✓ Branch 28 → 30 taken 281 times.
302x if (mode &
184 (win_nt::file_synchronous_io_alert |
185 win_nt::file_synchronous_io_nonalert))
186 21x return not_supported;
187
188
2/2
✓ Branch 31 → 32 taken 4 times.
✓ Branch 31 → 33 taken 277 times.
281x if (has_skip_on_success(h))
189 4x return not_supported;
190
191 // A failed query (volumes, some devices) means "not a directory".
192 277x FILE_BASIC_INFO basic{};
193 277x if (::GetFileInformationByHandleEx(
194
4/4
✓ Branch 34 → 35 taken 274 times.
✓ Branch 34 → 37 taken 3 times.
✓ Branch 38 → 39 taken 5 times.
✓ Branch 38 → 40 taken 272 times.
551x h, FileBasicInfo, &basic, sizeof(basic)) &&
195
2/2
✓ Branch 35 → 36 taken 5 times.
✓ Branch 35 → 37 taken 269 times.
274x (basic.FileAttributes & FILE_ATTRIBUTE_DIRECTORY))
196 5x return not_supported;
197
198 // A SOCKET reports FILE_TYPE_PIPE but is no named pipe, and must be
199 // closed with closesocket, not CloseHandle. GetNamedPipeInfo fails
200 // on it with ERROR_INVALID_FUNCTION; on a pipe end opened without
201 // FILE_READ_ATTRIBUTES (a PIPE_ACCESS_OUTBOUND server) it fails
202 // with ERROR_ACCESS_DENIED, which is no reason to reject.
203 318x if (type == FILE_TYPE_PIPE &&
204
6/6
✓ Branch 40 → 41 taken 46 times.
✓ Branch 40 → 46 taken 226 times.
✓ Branch 42 → 43 taken 3 times.
✓ Branch 42 → 46 taken 43 times.
✓ Branch 47 → 48 taken 1 time.
✓ Branch 47 → 49 taken 271 times.
275x !::GetNamedPipeInfo(h, nullptr, nullptr, nullptr, nullptr) &&
205
2/2
✓ Branch 44 → 45 taken 1 time.
✓ Branch 44 → 46 taken 2 times.
3x ::GetLastError() != ERROR_ACCESS_DENIED)
206 1x return not_supported;
207
208
3/4
✓ Branch 49 → 50 taken 32 times.
✓ Branch 49 → 53 taken 216 times.
✓ Branch 49 → 56 taken 23 times.
✗ Branch 49 → 57 not taken.
271x switch (kind)
209 {
210 32x case handle_kind::stream_handle:
211
2/2
✓ Branch 50 → 51 taken 2 times.
✓ Branch 50 → 52 taken 30 times.
32x if (type == FILE_TYPE_DISK)
212 2x return not_supported;
213 30x break;
214 216x case handle_kind::stream_file:
215 case handle_kind::random_access_file:
216
2/2
✓ Branch 53 → 54 taken 4 times.
✓ Branch 53 → 55 taken 212 times.
216x if (type == FILE_TYPE_PIPE)
217 4x return not_supported;
218 212x break;
219 23x case handle_kind::random_access_handle:
220 23x break;
221 }
222 265x return {};
223 }
224
225 /** Validate a handle for adoption by `win_object_handle`.
226
227 @return `bad_file_descriptor` for a null, invalid or closed
228 handle; `operation_not_supported` for a pseudo-handle, an
229 object type other than process, thread, event, semaphore or
230 waitable timer, a handle without `SYNCHRONIZE` access, or a
231 missing `ntdll` entry point; otherwise an empty code.
232 */
233 inline std::error_code
234 2833x validate_object_handle(HANDLE h) noexcept
235 {
236 auto const not_supported =
237 2833x std::make_error_code(std::errc::operation_not_supported);
238
239
4/4
✓ Branch 3 → 4 taken 2831 times.
✓ Branch 3 → 5 taken 2 times.
✓ Branch 4 → 5 taken 1 time.
✓ Branch 4 → 6 taken 2830 times.
2833x if (h == nullptr || h == INVALID_HANDLE_VALUE)
240 3x return std::make_error_code(std::errc::bad_file_descriptor);
241
242 // GetCurrentThread() and the other pseudo-handles (-2 .. -6) resolve
243 // per calling thread. -1 (GetCurrentProcess) is INVALID_HANDLE_VALUE
244 // and already rejected above.
245 2830x auto const v = reinterpret_cast<std::intptr_t>(h);
246
3/4
✓ Branch 6 → 7 taken 1 time.
✓ Branch 6 → 9 taken 2829 times.
✓ Branch 7 → 8 taken 1 time.
✗ Branch 7 → 9 not taken.
2830x if (v <= -2 && v >= -6)
247 1x return not_supported;
248
249 2829x auto const query = win_nt::query_object();
250
1/2
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 12 taken 2829 times.
2829x if (!query)
251 ✗ return not_supported;
252
253 2829x win_nt::object_basic_info basic{};
254 2829x if (query(
255 h, win_nt::object_basic_information, &basic, sizeof(basic),
256
2/2
✓ Branch 13 → 14 taken 1 time.
✓ Branch 13 → 15 taken 2828 times.
2829x nullptr) < 0)
257 1x return std::make_error_code(std::errc::bad_file_descriptor);
258
2/2
✓ Branch 15 → 16 taken 1 time.
✓ Branch 15 → 17 taken 2827 times.
2828x if (!(basic.GrantedAccess & SYNCHRONIZE))
259 1x return not_supported;
260
261 alignas(8) unsigned char buf[1024];
262
1/2
✗ Branch 18 → 19 not taken.
✓ Branch 18 → 20 taken 2827 times.
2827x if (query(h, win_nt::object_type_information, buf, sizeof(buf), nullptr) <
263 0)
264 ✗ return not_supported;
265 win_nt::unicode_string name;
266 2827x std::memcpy(&name, buf, sizeof(name));
267
1/2
✗ Branch 20 → 21 not taken.
✓ Branch 20 → 22 taken 2827 times.
2827x if (!name.Buffer)
268 ✗ return not_supported;
269
270 // Waitable kinds the pool can satisfy without side effects on a
271 // thread the coroutine does not own. Mutant (a mutex) is excluded
272 // for that reason. File is excluded because a file object signals
273 // on any I/O completion; console input and change notifications
274 // are File objects and need their own handling.
275 static constexpr std::wstring_view accepted[] = {
276 L"Process", L"Thread", L"Event", L"Semaphore", L"Timer", L"Job"};
277 std::wstring_view const type_name(
278 2827x name.Buffer, name.Length / sizeof(wchar_t));
279 2827x bool ok = false;
280
2/2
✓ Branch 30 → 24 taken 16962 times.
✓ Branch 30 → 31 taken 2827 times.
19789x for (auto a : accepted)
281
4/4
✓ Branch 24 → 25 taken 8496 times.
✓ Branch 24 → 27 taken 8466 times.
✓ Branch 26 → 27 taken 2823 times.
✓ Branch 26 → 28 taken 5673 times.
16962x ok = ok || type_name == a;
282
2/2
✓ Branch 31 → 32 taken 4 times.
✓ Branch 31 → 33 taken 2823 times.
2827x if (!ok)
283 4x return not_supported;
284
285 2823x return {};
286 }
287
288 } // namespace boost::corosio::detail
289
290 #endif // BOOST_COROSIO_HAS_IOCP
291
292 #endif
293