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

92.9% Lines (78/84) 100.0% List of functions (6/6) 86.0% Branches (74/86)
win_validate_handle.hpp
f(x) Functions (6)
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 15x ntdll_proc(char const* name) noexcept
94 {
95
1/2
✓ Branch 3 → 4 taken 15 times.
✗ Branch 3 → 6 not taken.
15x if (HMODULE h = ::GetModuleHandleW(L"ntdll.dll"))
96 return reinterpret_cast<Fn>(
97 15x reinterpret_cast<void (*)()>(::GetProcAddress(h, name)));
98 ✗ return nullptr;
99 }
100
101 inline query_information_file_fn
102 302x query_information_file() noexcept
103 {
104 static query_information_file_fn const fn =
105
3/4
✓ Branch 2 → 3 taken 11 times.
✓ Branch 2 → 7 taken 291 times.
✓ Branch 4 → 5 taken 11 times.
✗ Branch 4 → 7 not taken.
302x ntdll_proc<query_information_file_fn>("NtQueryInformationFile");
106 302x return fn;
107 }
108
109 inline query_object_fn
110 2828x query_object() noexcept
111 {
112 static query_object_fn const fn =
113
3/4
✓ Branch 2 → 3 taken 4 times.
✓ Branch 2 → 7 taken 2824 times.
✓ Branch 4 → 5 taken 4 times.
✗ Branch 4 → 7 not taken.
2828x ntdll_proc<query_object_fn>("NtQueryObject");
114 2828x return fn;
115 }
116
117 } // namespace win_nt
118
119 /** Validate a handle for adoption by an overlapped type.
120
121 @param h The handle to inspect. Never read, written or waited on.
122 @param kind The adopting type.
123
124 @return `bad_file_descriptor` for a null, invalid or closed
125 handle; `operation_not_supported` for a console, a socket, a
126 synchronous-mode handle, a handle already in
127 skip-completion-port-on-success mode, a directory, a disk
128 handle adopted
129 by `win_stream_handle`, or a pipe adopted by a file type;
130 otherwise an empty code. A missing `ntdll` entry point fails
131 closed with `operation_not_supported`.
132 */
133 inline std::error_code
134 320x validate_overlapped_handle(HANDLE h, handle_kind kind) noexcept
135 {
136 auto const not_supported =
137 320x std::make_error_code(std::errc::operation_not_supported);
138
139
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)
140 13x return std::make_error_code(std::errc::bad_file_descriptor);
141
142 307x ::SetLastError(NO_ERROR);
143 307x DWORD const type = ::GetFileType(h);
144
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)
145 1x return std::make_error_code(std::errc::bad_file_descriptor);
146
147 306x DWORD console_mode = 0;
148
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))
149 4x return not_supported;
150
151 302x auto const query = win_nt::query_information_file();
152
1/2
✗ Branch 23 → 24 not taken.
✓ Branch 23 → 25 taken 302 times.
302x if (!query)
153 ✗ return not_supported;
154 302x win_nt::io_status_block iosb{};
155 302x ULONG mode = 0;
156
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) <
157 0)
158 ✗ return not_supported;
159
2/2
✓ Branch 28 → 29 taken 21 times.
✓ Branch 28 → 30 taken 281 times.
302x if (mode &
160 (win_nt::file_synchronous_io_alert |
161 win_nt::file_synchronous_io_nonalert))
162 21x return not_supported;
163
164 // A handle already in skip-on-success mode queues no packet for a
165 // synchronous success, so the op would never complete. The mode
166 // cannot be cleared. A failed query means the mode was never set.
167 281x ULONG notify = 0;
168 281x if (query(h, &iosb, &notify, sizeof(notify),
169
4/4
✓ Branch 31 → 32 taken 279 times.
✓ Branch 31 → 34 taken 2 times.
✓ Branch 35 → 36 taken 4 times.
✓ Branch 35 → 37 taken 277 times.
560x win_nt::file_io_completion_notification_information) >= 0 &&
170
2/2
✓ Branch 32 → 33 taken 4 times.
✓ Branch 32 → 34 taken 275 times.
279x (notify & win_nt::file_skip_completion_port_on_success))
171 4x return not_supported;
172
173 // A failed query (volumes, some devices) means "not a directory".
174 277x FILE_BASIC_INFO basic{};
175 277x if (::GetFileInformationByHandleEx(
176
4/4
✓ Branch 38 → 39 taken 274 times.
✓ Branch 38 → 41 taken 3 times.
✓ Branch 42 → 43 taken 5 times.
✓ Branch 42 → 44 taken 272 times.
551x h, FileBasicInfo, &basic, sizeof(basic)) &&
177
2/2
✓ Branch 39 → 40 taken 5 times.
✓ Branch 39 → 41 taken 269 times.
274x (basic.FileAttributes & FILE_ATTRIBUTE_DIRECTORY))
178 5x return not_supported;
179
180 // A SOCKET reports FILE_TYPE_PIPE but is no named pipe, and must be
181 // closed with closesocket, not CloseHandle. GetNamedPipeInfo fails
182 // on it with ERROR_INVALID_FUNCTION; on a pipe end opened without
183 // FILE_READ_ATTRIBUTES (a PIPE_ACCESS_OUTBOUND server) it fails
184 // with ERROR_ACCESS_DENIED, which is no reason to reject.
185 318x if (type == FILE_TYPE_PIPE &&
186
6/6
✓ Branch 44 → 45 taken 46 times.
✓ Branch 44 → 50 taken 226 times.
✓ Branch 46 → 47 taken 3 times.
✓ Branch 46 → 50 taken 43 times.
✓ Branch 51 → 52 taken 1 time.
✓ Branch 51 → 53 taken 271 times.
275x !::GetNamedPipeInfo(h, nullptr, nullptr, nullptr, nullptr) &&
187
2/2
✓ Branch 48 → 49 taken 1 time.
✓ Branch 48 → 50 taken 2 times.
3x ::GetLastError() != ERROR_ACCESS_DENIED)
188 1x return not_supported;
189
190
3/4
✓ Branch 53 → 54 taken 32 times.
✓ Branch 53 → 57 taken 216 times.
✓ Branch 53 → 60 taken 23 times.
✗ Branch 53 → 61 not taken.
271x switch (kind)
191 {
192 32x case handle_kind::stream_handle:
193
2/2
✓ Branch 54 → 55 taken 2 times.
✓ Branch 54 → 56 taken 30 times.
32x if (type == FILE_TYPE_DISK)
194 2x return not_supported;
195 30x break;
196 216x case handle_kind::stream_file:
197 case handle_kind::random_access_file:
198
2/2
✓ Branch 57 → 58 taken 4 times.
✓ Branch 57 → 59 taken 212 times.
216x if (type == FILE_TYPE_PIPE)
199 4x return not_supported;
200 212x break;
201 23x case handle_kind::random_access_handle:
202 23x break;
203 }
204 265x return {};
205 }
206
207 /** Validate a handle for adoption by `win_object_handle`.
208
209 @return `bad_file_descriptor` for a null, invalid or closed
210 handle; `operation_not_supported` for a pseudo-handle, an
211 object type other than process, thread, event, semaphore or
212 waitable timer, a handle without `SYNCHRONIZE` access, or a
213 missing `ntdll` entry point; otherwise an empty code.
214 */
215 inline std::error_code
216 2832x validate_object_handle(HANDLE h) noexcept
217 {
218 auto const not_supported =
219 2832x std::make_error_code(std::errc::operation_not_supported);
220
221
4/4
✓ Branch 3 → 4 taken 2830 times.
✓ Branch 3 → 5 taken 2 times.
✓ Branch 4 → 5 taken 1 time.
✓ Branch 4 → 6 taken 2829 times.
2832x if (h == nullptr || h == INVALID_HANDLE_VALUE)
222 3x return std::make_error_code(std::errc::bad_file_descriptor);
223
224 // GetCurrentThread() and the other pseudo-handles (-2 .. -6) resolve
225 // per calling thread. -1 (GetCurrentProcess) is INVALID_HANDLE_VALUE
226 // and already rejected above.
227 2829x auto const v = reinterpret_cast<std::intptr_t>(h);
228
3/4
✓ Branch 6 → 7 taken 1 time.
✓ Branch 6 → 9 taken 2828 times.
✓ Branch 7 → 8 taken 1 time.
✗ Branch 7 → 9 not taken.
2829x if (v <= -2 && v >= -6)
229 1x return not_supported;
230
231 2828x auto const query = win_nt::query_object();
232
1/2
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 12 taken 2828 times.
2828x if (!query)
233 ✗ return not_supported;
234
235 2828x win_nt::object_basic_info basic{};
236 2828x if (query(
237 h, win_nt::object_basic_information, &basic, sizeof(basic),
238
2/2
✓ Branch 13 → 14 taken 1 time.
✓ Branch 13 → 15 taken 2827 times.
2828x nullptr) < 0)
239 1x return std::make_error_code(std::errc::bad_file_descriptor);
240
2/2
✓ Branch 15 → 16 taken 1 time.
✓ Branch 15 → 17 taken 2826 times.
2827x if (!(basic.GrantedAccess & SYNCHRONIZE))
241 1x return not_supported;
242
243 alignas(8) unsigned char buf[1024];
244
1/2
✗ Branch 18 → 19 not taken.
✓ Branch 18 → 20 taken 2826 times.
2826x if (query(h, win_nt::object_type_information, buf, sizeof(buf), nullptr) <
245 0)
246 ✗ return not_supported;
247 win_nt::unicode_string name;
248 2826x std::memcpy(&name, buf, sizeof(name));
249
1/2
✗ Branch 20 → 21 not taken.
✓ Branch 20 → 22 taken 2826 times.
2826x if (!name.Buffer)
250 ✗ return not_supported;
251
252 // Waitable kinds the pool can satisfy without side effects on a
253 // thread the coroutine does not own. Mutant (a mutex) is excluded
254 // for that reason. File is excluded because a file object signals
255 // on any I/O completion; console input and change notifications
256 // are File objects and need their own handling.
257 static constexpr std::wstring_view accepted[] = {
258 L"Process", L"Thread", L"Event", L"Semaphore", L"Timer", L"Job"};
259 std::wstring_view const type_name(
260 2826x name.Buffer, name.Length / sizeof(wchar_t));
261 2826x bool ok = false;
262
2/2
✓ Branch 30 → 24 taken 16956 times.
✓ Branch 30 → 31 taken 2826 times.
19782x for (auto a : accepted)
263
4/4
✓ Branch 24 → 25 taken 8493 times.
✓ Branch 24 → 27 taken 8463 times.
✓ Branch 26 → 27 taken 2822 times.
✓ Branch 26 → 28 taken 5671 times.
16956x ok = ok || type_name == a;
264
2/2
✓ Branch 31 → 32 taken 4 times.
✓ Branch 31 → 33 taken 2822 times.
2826x if (!ok)
265 4x return not_supported;
266
267 2822x return {};
268 }
269
270 } // namespace boost::corosio::detail
271
272 #endif // BOOST_COROSIO_HAS_IOCP
273
274 #endif
275