authorgravatar for squeek502@hotmail.comRyan Liptak <squeek502@hotmail.com> 2026-10-09 22:28:11-07:00
committergravatar for squeek502@hotmail.comRyan Liptak <squeek502@hotmail.com> 2026-10-09 22:32:38-07:00
log1c6a9d75df27564387764ee178d48666156039d4
tree635cade740a872d430b195a6cd932f6f2153b66f
parent61656d0e0e8ec894d72232c21c5933d396ef8501

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

1 files changed, 49 insertions(+), 1 deletions(-)

lib/std/Io/Threaded.zig+49-1
...@@ -4110,6 +4110,30 @@ fn fileStatLinux(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {...@@ -4110,6 +4110,30 @@ fn fileStatLinux(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {
4110 }4110 }
4111}4111}
41124112
4113fn windowsGetFileDevice(file: File) error{ Canceled, AccessDenied, Unexpected }!windows.CTL_CODE.FILE_DEVICE {
4114 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
4115 var device_info: windows.FILE.FS_DEVICE_INFORMATION = undefined;
4116 {
4117 const syscall: Syscall = try .start();
4118 while (true) switch (windows.ntdll.NtQueryVolumeInformationFile(
4119 file.handle,
4120 &io_status_block,
4121 &device_info,
4122 @sizeOf(windows.FILE.FS_DEVICE_INFORMATION),
4123 .Device,
4124 )) {
4125 .SUCCESS => break syscall.finish(),
4126 .CANCELLED => {
4127 try syscall.checkCancel();
4128 continue;
4129 },
4130 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
4131 else => |s| return syscall.unexpectedNtstatus(s),
4132 };
4133 }
4134 return device_info.DeviceType.FileDevice;
4135}
4136
4113fn fileStatWindows(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {4137fn fileStatWindows(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {
4114 const t: *Threaded = @ptrCast(@alignCast(userdata));4138 const t: *Threaded = @ptrCast(@alignCast(userdata));
41154139
...@@ -4134,7 +4158,31 @@ fn fileStatWindows(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {...@@ -4134,7 +4158,31 @@ fn fileStatWindows(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {
4134 // size provided. This is treated as success because the type of variable-length information that this would be relevant for4158 // size provided. This is treated as success because the type of variable-length information that this would be relevant for
4135 // (name, volume name, etc) we don't care about.4159 // (name, volume name, etc) we don't care about.
4136 .BUFFER_OVERFLOW => break syscall.finish(),4160 .BUFFER_OVERFLOW => break syscall.finish(),
4137 .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err),4161 .INVALID_PARAMETER, // SERIAL_PORT devices like AUX, COM1, etc
4162 .INVALID_DEVICE_REQUEST, // CONSOLE, etc devices
4163 .INVALID_INFO_CLASS, // NULL, etc devices
4164 => {
4165 syscall.finish();
4166 const device = try windowsGetFileDevice(file);
4167 return .{
4168 .inode = @intCast(@intFromPtr(file.handle)),
4169 .size = 0,
4170 .permissions = .default_file,
4171 .kind = switch (device) {
4172 // These are classified as FILE_TYPE_CHAR by GetFileType
4173 // https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfiletype
4174 // (GetFileType calls NtQueryVolumeInformationFile with FS_DEVICE_INFORMATION)
4175 .CONSOLE, .NULL, .SERIAL_PORT => .character_device,
4176 else => .unknown,
4177 },
4178 // Match the times that NtQueryInformationFile returns for named pipes.
4179 .atime = windows.fromSysTime(0),
4180 .mtime = windows.fromSysTime(0),
4181 .ctime = windows.fromSysTime(0),
4182 .nlink = 1,
4183 .block_size = block_size,
4184 };
4185 },
4138 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),4186 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
4139 .CANCELLED => {4187 .CANCELLED => {
4140 try syscall.checkCancel();4188 try syscall.checkCancel();