authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-10-11 19:43:20+02:00
committergravatar for alex@alexrp.comAlex Rønne Petersen <alex@alexrp.com> 2026-10-12 10:33:02+02:00
log12816808f6853cdda1acf43b19f9aac9e339cb20
tree52706ae6c300357124fe05323625bdf5ff769447
parent8149fffb1b67da3d20089144d71787e73493e38b
signature Signed by SSH key SHA256:7B/LJ7bpR1eX8aCXSr4mtd5M45VMPKcx9zY8e95b5QM

Merge pull request 'Io.Threaded: a few Windows stat fixes' (#37174) from squeek502/zig:misc-windows-fixes into master

Reviewed-on: https://codeberg.org/ziglang/zig/pulls/37174 Reviewed-by: Andrew Kelley <andrew@ziglang.org>

2 files changed, 92 insertions(+), 3 deletions(-)

lib/std/Io/Threaded.zig+55-3
...@@ -4088,6 +4088,30 @@ fn fileStatLinux(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {...@@ -4088,6 +4088,30 @@ fn fileStatLinux(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {
4088 }4088 }
4089}4089}
40904090
4091fn windowsGetFileDevice(file: File) error{ Canceled, AccessDenied, Unexpected }!windows.CTL_CODE.FILE_DEVICE {
4092 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
4093 var device_info: windows.FILE.FS_DEVICE_INFORMATION = undefined;
4094 {
4095 const syscall: Syscall = try .start();
4096 while (true) switch (windows.ntdll.NtQueryVolumeInformationFile(
4097 file.handle,
4098 &io_status_block,
4099 &device_info,
4100 @sizeOf(windows.FILE.FS_DEVICE_INFORMATION),
4101 .Device,
4102 )) {
4103 .SUCCESS => break syscall.finish(),
4104 .CANCELLED => {
4105 try syscall.checkCancel();
4106 continue;
4107 },
4108 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
4109 else => |s| return syscall.unexpectedNtstatus(s),
4110 };
4111 }
4112 return device_info.DeviceType.FileDevice;
4113}
4114
4091fn fileStatWindows(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {4115fn fileStatWindows(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {
4092 const t: *Threaded = @ptrCast(@alignCast(userdata));4116 const t: *Threaded = @ptrCast(@alignCast(userdata));
40934117
...@@ -4112,7 +4136,31 @@ fn fileStatWindows(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {...@@ -4112,7 +4136,31 @@ fn fileStatWindows(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {
4112 // size provided. This is treated as success because the type of variable-length information that this would be relevant for4136 // size provided. This is treated as success because the type of variable-length information that this would be relevant for
4113 // (name, volume name, etc) we don't care about.4137 // (name, volume name, etc) we don't care about.
4114 .BUFFER_OVERFLOW => break syscall.finish(),4138 .BUFFER_OVERFLOW => break syscall.finish(),
4115 .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err),4139 .INVALID_PARAMETER, // SERIAL_PORT devices like AUX, COM1, etc
4140 .INVALID_DEVICE_REQUEST, // CONSOLE, etc devices
4141 .INVALID_INFO_CLASS, // NULL, etc devices
4142 => {
4143 syscall.finish();
4144 const device = try windowsGetFileDevice(file);
4145 return .{
4146 .inode = @intCast(@intFromPtr(file.handle)),
4147 .size = 0,
4148 .permissions = .default_file,
4149 .kind = switch (device) {
4150 // These are classified as FILE_TYPE_CHAR by GetFileType
4151 // https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfiletype
4152 // (GetFileType calls NtQueryVolumeInformationFile with FS_DEVICE_INFORMATION)
4153 .CONSOLE, .NULL, .SERIAL_PORT => .character_device,
4154 else => .unknown,
4155 },
4156 // Match the times that NtQueryInformationFile returns for named pipes.
4157 .atime = windows.fromSysTime(0),
4158 .mtime = windows.fromSysTime(0),
4159 .ctime = windows.fromSysTime(0),
4160 .nlink = 1,
4161 .block_size = block_size,
4162 };
4163 },
4116 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),4164 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
4117 .CANCELLED => {4165 .CANCELLED => {
4118 try syscall.checkCancel();4166 try syscall.checkCancel();
...@@ -4151,8 +4199,10 @@ fn fileStatWindows(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {...@@ -4151,8 +4199,10 @@ fn fileStatWindows(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {
4151 break :reparse_point .unknown;4199 break :reparse_point .unknown;
4152 } else if (info.BasicInformation.FileAttributes.DIRECTORY)4200 } else if (info.BasicInformation.FileAttributes.DIRECTORY)
4153 .directory4201 .directory
4154 else4202 else switch (try windowsGetFileDevice(file)) {
4155 .file,4203 .NAMED_PIPE => .named_pipe,
4204 else => .file,
4205 },
4156 .atime = windows.fromSysTime(info.BasicInformation.LastAccessTime),4206 .atime = windows.fromSysTime(info.BasicInformation.LastAccessTime),
4157 .mtime = windows.fromSysTime(info.BasicInformation.LastWriteTime),4207 .mtime = windows.fromSysTime(info.BasicInformation.LastWriteTime),
4158 .ctime = windows.fromSysTime(info.BasicInformation.ChangeTime),4208 .ctime = windows.fromSysTime(info.BasicInformation.ChangeTime),
...@@ -17794,6 +17844,7 @@ pub fn windowsCreatePipe(t: *Threaded, options: CreatePipeOptions) ![2]windows.H...@@ -17794,6 +17844,7 @@ pub fn windowsCreatePipe(t: *Threaded, options: CreatePipeOptions) ![2]windows.H
17794 .READ_DATA = options.inbound,17844 .READ_DATA = options.inbound,
17795 .WRITE_DATA = options.outbound,17845 .WRITE_DATA = options.outbound,
17796 .WRITE_ATTRIBUTES = true,17846 .WRITE_ATTRIBUTES = true,
17847 .READ_ATTRIBUTES = true,
17797 } },17848 } },
17798 .STANDARD = .{ .SYNCHRONIZE = true },17849 .STANDARD = .{ .SYNCHRONIZE = true },
17799 },17850 },
...@@ -17836,6 +17887,7 @@ pub fn windowsCreatePipe(t: *Threaded, options: CreatePipeOptions) ![2]windows.H...@@ -17836,6 +17887,7 @@ pub fn windowsCreatePipe(t: *Threaded, options: CreatePipeOptions) ![2]windows.H
17836 .READ_DATA = options.outbound,17887 .READ_DATA = options.outbound,
17837 .WRITE_DATA = options.inbound,17888 .WRITE_DATA = options.inbound,
17838 .WRITE_ATTRIBUTES = true,17889 .WRITE_ATTRIBUTES = true,
17890 .READ_ATTRIBUTES = true,
17839 } },17891 } },
17840 .STANDARD = .{ .SYNCHRONIZE = true },17892 .STANDARD = .{ .SYNCHRONIZE = true },
17841 },17893 },
lib/std/fs/test.zig+37
...@@ -298,6 +298,43 @@ test "File.stat on a File that is a symlink returns Kind.sym_link" {...@@ -298,6 +298,43 @@ test "File.stat on a File that is a symlink returns Kind.sym_link" {
298 }.impl);298 }.impl);
299}299}
300300
301test "File.stat on a File that is a named pipe returns Kind.named_pipe" {
302 // Creating pipes is Io.Threaded-specific
303 var threaded: std.Io.Threaded = .init(std.testing.allocator, .{
304 .argv0 = .empty,
305 .environ = .empty,
306 });
307 defer threaded.deinit();
308 const io = threaded.io();
309
310 var read_end: Io.File = undefined;
311 var write_end: Io.File = undefined;
312 switch (builtin.target.os.tag) {
313 .wasi => return error.SkipZigTest,
314 .windows => {
315 const pipe = try threaded.windowsCreatePipe(.{
316 .server = .{ .mode = .{ .IO = .SYNCHRONOUS_NONALERT } },
317 .client = .{ .mode = .{ .IO = .SYNCHRONOUS_NONALERT } },
318 .inbound = true,
319 });
320 read_end = .{ .handle = pipe[0], .flags = .{ .nonblocking = false } };
321 write_end = .{ .handle = pipe[1], .flags = .{ .nonblocking = false } };
322 },
323 else => {
324 const pipe = try std.Io.Threaded.pipe2(.{ .CLOEXEC = true });
325 read_end = .{ .handle = pipe[0], .flags = .{ .nonblocking = false } };
326 write_end = .{ .handle = pipe[1], .flags = .{ .nonblocking = false } };
327 },
328 }
329 defer {
330 read_end.close(io);
331 write_end.close(io);
332 }
333
334 try expectEqual(.named_pipe, (try read_end.stat(io)).kind);
335 try expectEqual(.named_pipe, (try write_end.stat(io)).kind);
336}
337
301test "Dir.statFile on a symlink" {338test "Dir.statFile on a symlink" {
302 const io = testing.io;339 const io = testing.io;
303340