authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-10-11 19:43:20+02:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-10-11 19:43:20+02:00
logeee0e6d70e8b4987a608fc1ea9451fcaf9dd6d16
treec5f5e8382e2538f71f4f58cfa9f512b36e5e171e
parentc6a67f1945f4ebba677b71ea8be141fe6f0fe380
parent55d429e0ba37c0609f8c713952113078a11bb593

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
...@@ -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();
...@@ -4173,8 +4221,10 @@ fn fileStatWindows(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {...@@ -4173,8 +4221,10 @@ fn fileStatWindows(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {
4173 break :reparse_point .unknown;4221 break :reparse_point .unknown;
4174 } else if (info.BasicInformation.FileAttributes.DIRECTORY)4222 } else if (info.BasicInformation.FileAttributes.DIRECTORY)
4175 .directory4223 .directory
4176 else4224 else switch (try windowsGetFileDevice(file)) {
4177 .file,4225 .NAMED_PIPE => .named_pipe,
4226 else => .file,
4227 },
4178 .atime = windows.fromSysTime(info.BasicInformation.LastAccessTime),4228 .atime = windows.fromSysTime(info.BasicInformation.LastAccessTime),
4179 .mtime = windows.fromSysTime(info.BasicInformation.LastWriteTime),4229 .mtime = windows.fromSysTime(info.BasicInformation.LastWriteTime),
4180 .ctime = windows.fromSysTime(info.BasicInformation.ChangeTime),4230 .ctime = windows.fromSysTime(info.BasicInformation.ChangeTime),
...@@ -17818,6 +17868,7 @@ pub fn windowsCreatePipe(t: *Threaded, options: CreatePipeOptions) ![2]windows.H...@@ -17818,6 +17868,7 @@ pub fn windowsCreatePipe(t: *Threaded, options: CreatePipeOptions) ![2]windows.H
17818 .READ_DATA = options.inbound,17868 .READ_DATA = options.inbound,
17819 .WRITE_DATA = options.outbound,17869 .WRITE_DATA = options.outbound,
17820 .WRITE_ATTRIBUTES = true,17870 .WRITE_ATTRIBUTES = true,
17871 .READ_ATTRIBUTES = true,
17821 } },17872 } },
17822 .STANDARD = .{ .SYNCHRONIZE = true },17873 .STANDARD = .{ .SYNCHRONIZE = true },
17823 },17874 },
...@@ -17860,6 +17911,7 @@ pub fn windowsCreatePipe(t: *Threaded, options: CreatePipeOptions) ![2]windows.H...@@ -17860,6 +17911,7 @@ pub fn windowsCreatePipe(t: *Threaded, options: CreatePipeOptions) ![2]windows.H
17860 .READ_DATA = options.outbound,17911 .READ_DATA = options.outbound,
17861 .WRITE_DATA = options.inbound,17912 .WRITE_DATA = options.inbound,
17862 .WRITE_ATTRIBUTES = true,17913 .WRITE_ATTRIBUTES = true,
17914 .READ_ATTRIBUTES = true,
17863 } },17915 } },
17864 .STANDARD = .{ .SYNCHRONIZE = true },17916 .STANDARD = .{ .SYNCHRONIZE = true },
17865 },17917 },
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