Use "overlapped I/O" for Windows named pipe communication
Marco Ricci

Marco Ricci commited on 2025-12-28 18:53:33
Zeige 1 geänderte Dateien mit 118 Einfügungen und 12 Löschungen.

... ...
@@ -28,7 +28,14 @@ if TYPE_CHECKING:
28 28
     from collections.abc import Callable
29 29
     from typing import ClassVar
30 30
 
31
-    from typing_extensions import Any, Buffer, Literal, Self, TypeVar
31
+    from typing_extensions import (
32
+        Any,
33
+        Buffer,
34
+        Literal,
35
+        Self,
36
+        TypeAlias,
37
+        TypeVar,
38
+    )
32 39
 
33 40
     from derivepassphrase import _types
34 41
 
... ...
@@ -53,9 +60,43 @@ if TYPE_CHECKING:
53 60
         ],
54 61
         HANDLE,
55 62
     ]
56
-    ReadFile: Callable[[HANDLE, LPCVOID, DWORD, LPDWORD, None], BOOL]
57
-    WriteFile: Callable[[HANDLE, LPCVOID, DWORD, LPDWORD, None], BOOL]
63
+    ReadFile: Callable[[HANDLE, LPCVOID, DWORD, LPDWORD, LPOVERLAPPED], BOOL]
64
+    WriteFile: Callable[[HANDLE, LPCVOID, DWORD, LPDWORD, LPOVERLAPPED], BOOL]
58 65
     CloseHandle: Callable[[HANDLE], BOOL]
66
+    GetOverlappedResult: Callable[[HANDLE, LPOVERLAPPED, LPDWORD, BOOL], BOOL]
67
+    CreateEvent: Callable[[None, BOOL, BOOL, None], HANDLE]
68
+
69
+
70
+class _OverlappedDummyStruct(ctypes.Structure):
71
+    """The DUMMYSTRUCTNAME structure in the definition of OVERLAPPED."""
72
+
73
+    _fields_ = (("Offset", DWORD), ("OffsetHigh", DWORD))
74
+
75
+
76
+class _OverlappedDummyUnion(ctypes.Union):
77
+    """The DUMMYUNIONNAME union in the definition of OVERLAPPED."""
78
+
79
+    _fields_ = (
80
+        ("DUMMYSTRUCTNAME", _OverlappedDummyStruct),
81
+        ("Pointer", ctypes.c_void_p),
82
+    )
83
+
84
+
85
+class OVERLAPPED(ctypes.Structure):
86
+    """The data structure for overlapped (async) I/O on The Annoying OS."""
87
+
88
+    _fields_ = (
89
+        ("Internal", ctypes.POINTER(ctypes.c_ulong)),
90
+        ("InternalHigh", ctypes.POINTER(ctypes.c_ulong)),
91
+        ("DUMMYUNIONNAME", _OverlappedDummyUnion),
92
+        ("hEvent", HANDLE),
93
+    )
94
+
95
+
96
+if TYPE_CHECKING:
97
+    LPOVERLAPPED: TypeAlias = ctypes._Pointer[OVERLAPPED]  # noqa: SLF001
98
+else:
99
+    LPOVERLAPPED = ctypes.POINTER(OVERLAPPED)
59 100
 
60 101
 __all__ = ("SocketProvider",)
61 102
 
... ...
@@ -90,6 +131,7 @@ GENERIC_WRITE = 0x40000000
90 131
 
91 132
 FILE_SHARE_READ = 0x00000001
92 133
 FILE_SHARE_WRITE = 0x00000002
134
+FILE_FLAG_OVERLAPPED = 0x40000000
93 135
 
94 136
 OPEN_EXISTING = 0x3
95 137
 
... ...
@@ -101,6 +143,7 @@ CRYPTPROTECTMEMORY_CROSS_PROCESS = 0x1
101 143
 PIPE_PREFIX = "//./pipe/".replace("/", "\\")
102 144
 
103 145
 ERROR_PIPE_BUSY = 231
146
+ERROR_IO_PENDING = 997
104 147
 
105 148
 
106 149
 # The type checker and the standard library use different
... ...
@@ -172,6 +215,8 @@ try:  # pragma: unless the-annoying-os no cover
172 215
     ReadFile = kernel32.ReadFile  # type: ignore[attr-defined]
173 216
     WriteFile = kernel32.WriteFile  # type: ignore[attr-defined]
174 217
     CloseHandle = kernel32.CloseHandle  # type: ignore[attr-defined]
218
+    GetOverlappedResult = kernel32.GetOverlappedResult  # type: ignore[attr-defined]
219
+    CreateEvent = kernel32.CreateEventW  # type: ignore[attr-defined]
175 220
 except (
176 221
     AttributeError,
177 222
     FileNotFoundError,
... ...
@@ -203,7 +248,7 @@ except (
203 248
         lpBuffer: LPVOID,  # noqa: N803
204 249
         nNumberOfBytesToRead: DWORD,  # noqa: N803
205 250
         lpNumberOfBytesRead: LPDWORD,  # noqa: N803
206
-        lpOverlapped: None,  # noqa: N803
251
+        lpOverlapped: LPOVERLAPPED,  # noqa: N803
207 252
     ) -> BOOL:  # pragma: no cover [external]
208 253
         del (
209 254
             hFile,
... ...
@@ -219,7 +264,7 @@ except (
219 264
         lpBuffer: LPVOID,  # noqa: N803
220 265
         nNumberOfBytesToWrite: DWORD,  # noqa: N803
221 266
         lpNumberOfBytesWritten: LPDWORD,  # noqa: N803
222
-        lpOverlapped: None,  # noqa: N803
267
+        lpOverlapped: LPOVERLAPPED,  # noqa: N803
223 268
     ) -> BOOL:  # pragma: no cover [external]
224 269
         del (
225 270
             hFile,
... ...
@@ -236,6 +281,24 @@ except (
236 281
         del hHandle
237 282
         raise OSError(errno.ENOTSUP, os.strerror(errno.ENOTSUP))
238 283
 
284
+    def GetOverlappedResult(  # noqa: N802
285
+        hFile: HANDLE,  # noqa: N803
286
+        lpOverlapped: LPOVERLAPPED,  # noqa: N803
287
+        lpNumberOfBytesTransferred: LPDWORD,  # noqa: N803
288
+        bWait: BOOL,  # noqa: N803
289
+    ) -> BOOL:  # pragma: no cover [external]
290
+        del hFile, lpOverlapped, lpNumberOfBytesTransferred, bWait
291
+        raise OSError(errno.ENOTSUP, os.strerror(errno.ENOTSUP))
292
+
293
+    def CreateEvent(  # noqa: N802
294
+        lpEventAttributes: None,  # noqa: N803
295
+        bManualReset: BOOL,  # noqa: N803
296
+        bInitialState: BOOL,  # noqa: N803
297
+        lpName: None,  # noqa: N803
298
+    ) -> HANDLE:
299
+        del lpEventAttributes, bManualReset, bInitialState, lpName
300
+        raise OSError(errno.ENOTSUP, os.strerror(errno.ENOTSUP))
301
+
239 302
 
240 303
 else:  # pragma: unless the-annoying-os no cover
241 304
     CreateFile.argtypes = [  # type: ignore[attr-defined]
... ...
@@ -250,28 +313,45 @@ else:  # pragma: unless the-annoying-os no cover
250 313
         # We always pass None/NULL.
251 314
         HANDLE,
252 315
     ]
316
+    CreateEvent.argtypes = [
317
+        # Actually, LPSECURITY_ATTRIBUTES, but we always pass
318
+        # None/NULL.
319
+        ctypes.c_void_p,
320
+        BOOL,
321
+        BOOL,
322
+        # We always pass None/NULL.
323
+        LPCWSTR,
324
+    ]
253 325
     CreateFile.restype = HANDLE  # type: ignore[attr-defined]
326
+    CreateEvent.restype = HANDLE  # type: ignore[attr-defined]
254 327
     CreateFile.errcheck = _errcheck  # type: ignore[assignment, attr-defined]
328
+    CreateEvent.errcheck = _errcheck  # type: ignore[assignment, attr-defined]
255 329
     ReadFile.argtypes = [  # type: ignore[attr-defined]
256 330
         HANDLE,
257 331
         LPVOID,
258 332
         DWORD,
259 333
         LPDWORD,
260
-        # Actually, LPOVERLAPPED, but we always pass None/NULL.
261
-        ctypes.c_void_p,
334
+        LPOVERLAPPED,
262 335
     ]
263 336
     WriteFile.argtypes = [  # type: ignore[attr-defined]
264 337
         HANDLE,
265 338
         LPVOID,
266 339
         DWORD,
267 340
         LPDWORD,
268
-        # Actually, LPOVERLAPPED, but we always pass None/NULL.
269
-        ctypes.c_void_p,
341
+        LPOVERLAPPED,
270 342
     ]
271 343
     CloseHandle.argtypes = [HANDLE]  # type: ignore[attr-defined]
344
+    GetOverlappedResult.argtypes = [
345
+        HANDLE,
346
+        LPOVERLAPPED,
347
+        LPDWORD,
348
+        # We always pass True.
349
+        BOOL,
350
+    ]
272 351
     ReadFile.restype = BOOL  # type: ignore[attr-defined]
273 352
     WriteFile.restype = BOOL  # type: ignore[attr-defined]
274 353
     CloseHandle.restype = BOOL  # type: ignore[attr-defined]
354
+    GetOverlappedResult.restype = BOOL  # type: ignore[attr-defined]
275 355
 
276 356
 
277 357
 class WindowsNamedPipeHandle:
... ...
@@ -314,7 +394,7 @@ class WindowsNamedPipeHandle:
314 394
                     ctypes.c_ulong(FILE_SHARE_READ | FILE_SHARE_WRITE),
315 395
                     None,
316 396
                     ctypes.c_ulong(OPEN_EXISTING),
317
-                    ctypes.c_ulong(0),
397
+                    ctypes.c_ulong(FILE_FLAG_OVERLAPPED),
318 398
                     None,
319 399
                 )
320 400
             except BlockingIOError:  # pragma: no cover [external]
... ...
@@ -344,17 +424,31 @@ class WindowsNamedPipeHandle:
344 424
         buffer = (ctypes.c_char * 65536)()
345 425
         while data > 0:
346 426
             block_size = min(max(0, data), 65536)
427
+            wait_event = CreateEvent(None, BOOL(True), BOOL(False), None)  # noqa: FBT003
428
+            try:
429
+                overlapped_struct = OVERLAPPED(hEvent=wait_event)
347 430
                 success = ReadFile(
348 431
                     self.handle,
349 432
                     ctypes.cast(ctypes.byref(buffer), ctypes.c_void_p),
350 433
                     DWORD(block_size),
351 434
                     ctypes.cast(ctypes.byref(read_count), LPDWORD),
352
-                None,
435
+                    ctypes.cast(ctypes.byref(overlapped_struct), LPOVERLAPPED),
436
+                )
437
+                if not success and ctypes.GetLastError() == ERROR_IO_PENDING:
438
+                    success = GetOverlappedResult(
439
+                        self.handle,
440
+                        ctypes.cast(
441
+                            ctypes.byref(overlapped_struct), LPOVERLAPPED
442
+                        ),
443
+                        ctypes.cast(ctypes.byref(read_count), LPDWORD),
444
+                        BOOL(True),  # noqa: FBT003
353 445
                     )
354 446
                 if (
355 447
                     not success or read_count.value == 0
356 448
                 ):  # pragma: no cover [external]
357 449
                     raise ctypes.WinError()  # type: ignore[attr-defined]
450
+            finally:
451
+                CloseHandle(wait_event)
358 452
             result.extend(buffer.raw[:block_size])
359 453
             data -= read_count.value
360 454
             read_count.value = 0
... ...
@@ -369,17 +463,29 @@ class WindowsNamedPipeHandle:
369 463
         for i, x in enumerate(data):
370 464
             databuf[i] = ctypes.c_char(x)
371 465
         write_count = DWORD(0)
466
+        wait_event = CreateEvent(None, BOOL(True), BOOL(False), None)  # noqa: FBT003
467
+        try:
468
+            overlapped_struct = OVERLAPPED(hEvent=wait_event)
372 469
             success = WriteFile(
373 470
                 self.handle,
374 471
                 ctypes.cast(ctypes.byref(databuf), ctypes.c_void_p),
375 472
                 DWORD(len(data)),
376 473
                 ctypes.cast(ctypes.byref(write_count), LPDWORD),
377
-            None,
474
+                ctypes.cast(ctypes.byref(overlapped_struct), LPOVERLAPPED),
475
+            )
476
+            if not success and ctypes.GetLastError() == ERROR_IO_PENDING:
477
+                success = GetOverlappedResult(
478
+                    self.handle,
479
+                    ctypes.cast(ctypes.byref(overlapped_struct), LPOVERLAPPED),
480
+                    ctypes.cast(ctypes.byref(write_count), LPDWORD),
481
+                    BOOL(True),  # noqa: FBT003
378 482
                 )
379 483
             if (
380 484
                 not success or write_count.value == 0
381 485
             ):  # pragma: no cover [external]
382 486
                 raise ctypes.WinError()  # type: ignore[attr-defined]
487
+        finally:
488
+            CloseHandle(wait_event)
383 489
 
384 490
     @classmethod
385 491
     def for_openssh(cls) -> Self:
386 492