mio, the I/O layer under Tokio, waits for sockets by opening \Device\Afd
with NtCreateFile and issuing IOCTL_AFD_POLL through NtDeviceIoControlFile,
with the completions delivered to an I/O completion port; wepoll and libuv
do the same. Every Tokio program that touches the network stopped there:
xh, Deno's fetch, Bun.
An AFD endpoint is now a handle that can join a completion port. A poll
reads AFD_POLL_INFO (the timeout, the sockets and the events wanted), waits
in the reactor on the sockets' host descriptors, and finishes as overlapped
I/O does: the output records with the events that occurred (receive,
expedited, send, disconnect, abort, local close, accept, connect failure),
the IO_STATUS_BLOCK, the event, and a packet carrying the APC context. The
native cancels end pending polls as cancelled. WSAIoctl answers
SIO_BASE_HANDLE and the SIO_BSP_HANDLE codes with the socket itself, since
no layered provider sits on it. The structures and codes are the ones those
open-source projects define; Microsoft does not document this interface.
Assisted-by: Claude Code:claude-opus-5-5
AI-Scope: the AFD endpoint, its poll and cancellation, and the base-handle codes
(cherry picked from commit 660a20304054e00ab94afef0322f9dbd0a8cbb8f)
uv makes directory junctions with FSCTL_SET_REPARSE_POINT for its Python
version links, and lsd asks every file for its reparse data with
FSCTL_GET_REPARSE_POINT; both got ERROR_INVALID_FUNCTION.
Reparse points are the host's symbolic links: FSCTL_SET_REPARSE_POINT makes
an empty directory a mount point, or an empty file or directory a symbolic
link, to the target its REPARSE_DATA_BUFFER names; FSCTL_GET_REPARSE_POINT
describes a link (as a mount point when it holds an absolute path to a
directory); FSCTL_DELETE_REPARSE_POINT leaves an empty directory or file.
A file that is no reparse point answers ERROR_NOT_A_REPARSE_POINT. A test
makes a junction, reads a file through it, reads it back and removes it.
Assisted-by: Claude Code:claude-opus-5-5
AI-Scope: the control codes and the test
Windows closes the source handle of DuplicateHandle with
DUPLICATE_CLOSE_SOURCE regardless of the outcome. Go starts every child
process that way: it closes the inheritable copies it made with
DuplicateHandle(GetCurrentProcess(), handle, NULL, NULL, 0, FALSE,
DUPLICATE_CLOSE_SOURCE), with no target process at all. Raven refused the
call and kept the handles, so the write ends of the child's output pipes
stayed open in the parent, its readers never saw the end of the output, and
the parent waited forever: go build hung after its first compile, curlie
after curl.
A handle of the calling process is now closed on every return, success or
failure.
Assisted-by: Claude Code:claude-opus-5-5
AI-Scope: the change
(cherry picked from commit 017a3d84a400223f845101adf7864163e80e9981)
GetLongPathNameW answered the fully normalised DOS path of the file. Windows
keeps the path as given (relative or absolute, its separators as written)
and replaces each component with its long name. nginx compares the answer
with the path it opened, prefix\conf/nginx.conf by default, and refused its
own configuration file when they differed.
The folders keep no 8.3 names, so each component's long form is the name of
its directory entry, which may differ from the caller's in case.
Assisted-by: Claude Code:claude-opus-5-5
AI-Scope: the change
(cherry picked from commit b1e588fe2e0fbc89f2e5918b1750068b315e335f)
Go's os.ReadDir reads directories with GetFileInformationByHandleEx and
FileIdBothDirectoryInfo or FileFullDirectoryInfo, which failed with
ERROR_INVALID_PARAMETER: Hugo, OpenTofu, rclone, restic, gdu and the go
command itself could not read a directory.
The directory classes (FileIdBothDirectoryInfo, FileFullDirectoryInfo,
FileIdExtdDirectoryInfo and their restarting forms) now read entries as
NtQueryDirectoryFile does, which gains the native classes that carry the
file's identifier (FileIdBothDirectoryInformation,
FileIdFullDirectoryInformation, FileIdExtdDirectoryInformation).
RtlNtStatusToDosError learns the statuses directory reads end with, such as
STATUS_NO_MORE_FILES.
Assisted-by: Claude Code:claude-opus-5-5
AI-Scope: the directory classes and the status translations
Rust's standard library, uv, Zig and the event loops built on mio call
NtCreateFile and NtOpenFile directly, which Raven did not answer.
The object name becomes a Win32 path (\??\ and \DosDevices\ names, UNC
names, and names relative to an open directory), and the disposition and
create options become CreateFileW's: FILE_DIRECTORY_FILE and
FILE_NON_DIRECTORY_FILE check what was opened, a directory is created first
for FILE_CREATE or FILE_OPEN_IF, synchronous options decide whether the
handle is overlapped, and the write-through, sequential, random-access,
no-buffering, delete-on-close and reparse-point options become the matching
flags. The IO_STATUS_BLOCK reports FILE_OPENED, FILE_CREATED,
FILE_OVERWRITTEN or FILE_SUPERSEDED, and errors come back as the NTSTATUS
values the Win32 errors stand for.
Assisted-by: Claude Code:claude-opus-5-5
AI-Scope: the handlers, from the NtCreateFile and NtOpenFile documentation
(cherry picked from commit 1482289fad28953e3df44449973cd9ea168f7d5e)
SetFileTime required a handle opened for writing. Windows asks for
FILE_WRITE_ATTRIBUTES, which Go's os.Chtimes opens with, on directories
too; restic could not open its cache.
Assisted-by: Claude Code:claude-opus-5-5
AI-Scope: the access check
(cherry picked from commit 85edfabfd7522823d6612700a9ca3f90bafc2c9b)
* feat: give console programs the terminal as their console
A standard handle that is a terminal, and CONIN$, CONOUT$ and CON, now act as a console: modes map to terminal settings,
reads and writes convert between UTF-16 and the terminal's UTF-8, terminal input becomes key events, and Ctrl+C runs the
control handlers. Files and pipes still fail the console functions, as on Windows.
Assisted-by: Claude Code:claude-opus-5-5
AI-Scope: Wrote the console functions over the terminal, their pty test and the guest test program from the public console API documentation.
* test: let the console client run by hand, saying which step failed
Assisted-by: Claude Code:claude-opus-5-5
AI-Scope: Made the exact size and cursor checks optional and gave each step an instruction and a failure number.
* feat: follow the console cursor through the output instead of asking the terminal
Programs that redraw their command line read and move the cursor on every key; asking the terminal each time was slow, and
wrong on terminals that never answer. The terminal is now asked only when the position may be stale.
Assisted-by: Claude Code:claude-opus-5-5
AI-Scope: Wrote the cursor tracker, WriteConsoleOutputCharacter, ScrollConsoleScreenBuffer, the resize events and their tests.