From 1c6a9d75df27564387764ee178d48666156039d4 Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Fri, 9 Oct 2026 22:28:11 -0700 Subject: [PATCH] Threaded.fileStatWindows: fix for character device files Tested with a program that calls `stdin().stat(io)`. With this change, the following all work and return `.kind = .character_device` whereas before they would hit error.Unexpected or panic: stdin-stat.exe stdin-stat.exe < NUL stdin-stat.exe < COM1 stdin-stat.exe < AUX --- lib/std/Io/Threaded.zig | 50 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 69cd17326c34178162b08ac6bbf58bdb331f10bc..c6bcd0a456d94d6e2e7658424e228d1a6f216398 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -4110,6 +4110,30 @@ fn fileStatLinux(userdata: ?*anyopaque, file: File) File.StatError!File.Stat { } } +fn windowsGetFileDevice(file: File) error{ Canceled, AccessDenied, Unexpected }!windows.CTL_CODE.FILE_DEVICE { + var io_status_block: windows.IO_STATUS_BLOCK = undefined; + var device_info: windows.FILE.FS_DEVICE_INFORMATION = undefined; + { + const syscall: Syscall = try .start(); + while (true) switch (windows.ntdll.NtQueryVolumeInformationFile( + file.handle, + &io_status_block, + &device_info, + @sizeOf(windows.FILE.FS_DEVICE_INFORMATION), + .Device, + )) { + .SUCCESS => break syscall.finish(), + .CANCELLED => { + try syscall.checkCancel(); + continue; + }, + .ACCESS_DENIED => return syscall.fail(error.AccessDenied), + else => |s| return syscall.unexpectedNtstatus(s), + }; + } + return device_info.DeviceType.FileDevice; +} + fn fileStatWindows(userdata: ?*anyopaque, file: File) File.StatError!File.Stat { const t: *Threaded = @ptrCast(@alignCast(userdata)); @@ -4134,7 +4158,31 @@ fn fileStatWindows(userdata: ?*anyopaque, file: File) File.StatError!File.Stat { // size provided. This is treated as success because the type of variable-length information that this would be relevant for // (name, volume name, etc) we don't care about. .BUFFER_OVERFLOW => break syscall.finish(), - .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err), + .INVALID_PARAMETER, // SERIAL_PORT devices like AUX, COM1, etc + .INVALID_DEVICE_REQUEST, // CONSOLE, etc devices + .INVALID_INFO_CLASS, // NULL, etc devices + => { + syscall.finish(); + const device = try windowsGetFileDevice(file); + return .{ + .inode = @intCast(@intFromPtr(file.handle)), + .size = 0, + .permissions = .default_file, + .kind = switch (device) { + // These are classified as FILE_TYPE_CHAR by GetFileType + // https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfiletype + // (GetFileType calls NtQueryVolumeInformationFile with FS_DEVICE_INFORMATION) + .CONSOLE, .NULL, .SERIAL_PORT => .character_device, + else => .unknown, + }, + // Match the times that NtQueryInformationFile returns for named pipes. + .atime = windows.fromSysTime(0), + .mtime = windows.fromSysTime(0), + .ctime = windows.fromSysTime(0), + .nlink = 1, + .block_size = block_size, + }; + }, .ACCESS_DENIED => return syscall.fail(error.AccessDenied), .CANCELLED => { try syscall.checkCancel(); -- 2.54.0