From ea7512084bc6f0f18b9dc3d1ecd23599640b3363 Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Wed, 10 Dec 2025 18:56:14 -0800 Subject: [PATCH 1/2] Make windows.DeviceIoControl return NTSTATUS instead of a Zig error The number of possible errors in the theoretical error set of this function is very large, while each individual call to the function is only concerned with a (potentially small) subset of those errors that are specific to the control code being used. This commit makes the callers determine which statuses they are interested in to avoid an ever-ballooning error set and ever-growing switch cases at each call site that throw away most of those errors. --- lib/std/os/windows.zig | 96 +++++++++++++----------------------------- 1 file changed, 29 insertions(+), 67 deletions(-) diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index 70219c35101e232ff53ea9228de2e39e64262c17..bb908143c10b1bb2fa03e1d6c764fac8b540e93e 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -2574,23 +2574,6 @@ pub fn CreatePipe(rd: *HANDLE, wr: *HANDLE, sattr: *const SECURITY_ATTRIBUTES) C wr.* = write; } -pub const DeviceIoControlError = error{ - AccessDenied, - /// The volume does not contain a recognized file system. File system - /// drivers might not be loaded, or the volume may be corrupt. - UnrecognizedVolume, - Pending, - /// Attempted to connect a named pipe in the "closing" state, meaning a previous client has - /// has closed their handle but we have not yet disconnected the pipe. - PipeClosing, - /// Attempted to connect a named pipe in the "connected" state, meaning a client has already - /// opened the pipe; there is a good connection between client and server. - PipeAlreadyConnected, - /// Attempted to connect a non-blocking named pipe which is already listening for connections. - PipeAlreadyListening, - Unexpected, -}; - /// A Zig wrapper around `NtDeviceIoControlFile` and `NtFsControlFile` syscalls. /// It implements similar behavior to `DeviceIoControl` and is meant to serve /// as a direct substitute for that call. @@ -2606,9 +2589,9 @@ pub fn DeviceIoControl( in: []const u8 = &.{}, out: []u8 = &.{}, }, -) DeviceIoControlError!void { +) NTSTATUS { var io_status_block: IO_STATUS_BLOCK = undefined; - const rc = switch (io_control_code.DeviceType) { + return switch (io_control_code.DeviceType) { .FILE_SYSTEM, .NAMED_PIPE => ntdll.NtFsControlFile( device, opts.event, @@ -2634,19 +2617,6 @@ pub fn DeviceIoControl( @intCast(opts.out.len), ), }; - switch (rc) { - .SUCCESS => {}, - .PIPE_CLOSING => return error.PipeClosing, - .PIPE_CONNECTED => return error.PipeAlreadyConnected, - .PIPE_LISTENING => return error.PipeAlreadyListening, - .PRIVILEGE_NOT_HELD => return error.AccessDenied, - .ACCESS_DENIED => return error.AccessDenied, - .INVALID_DEVICE_REQUEST => return error.AccessDenied, // Not supported by the underlying filesystem - .INVALID_PARAMETER => unreachable, - .UNRECOGNIZED_VOLUME => return error.UnrecognizedVolume, - .PENDING => return error.Pending, - else => return unexpectedStatus(rc), - } } pub fn GetOverlappedResult(h: HANDLE, overlapped: *OVERLAPPED, wait: bool) !DWORD { @@ -3037,9 +3007,6 @@ pub const CreateSymbolicLinkError = error{ NoDevice, NetworkNotFound, BadPathName, - /// The volume does not contain a recognized file system. File system - /// drivers might not be loaded, or the volume may be corrupt. - UnrecognizedVolume, Unexpected, }; @@ -3139,13 +3106,14 @@ pub fn CreateSymbolicLink( @memcpy(buffer[@sizeOf(SYMLINK_DATA)..][0 .. final_target_path.len * 2], @as([*]const u8, @ptrCast(final_target_path))); const paths_start = @sizeOf(SYMLINK_DATA) + final_target_path.len * 2; @memcpy(buffer[paths_start..][0 .. final_target_path.len * 2], @as([*]const u8, @ptrCast(final_target_path))); - _ = DeviceIoControl(symlink_handle, FSCTL.SET_REPARSE_POINT, .{ .in = buffer[0..buf_len] }) catch |err| switch (err) { - error.PipeClosing => unreachable, - error.PipeAlreadyConnected => unreachable, - error.PipeAlreadyListening => unreachable, - error.Pending => unreachable, - else => |e| return e, - }; + const rc = DeviceIoControl(symlink_handle, FSCTL.SET_REPARSE_POINT, .{ .in = buffer[0..buf_len] }); + switch (rc) { + .SUCCESS => {}, + .PRIVILEGE_NOT_HELD => return error.AccessDenied, + .ACCESS_DENIED => return error.AccessDenied, + .INVALID_DEVICE_REQUEST => return error.AccessDenied, // Not supported by the underlying filesystem + else => return unexpectedStatus(rc), + } } pub const ReadLinkError = error{ @@ -3184,15 +3152,11 @@ pub fn ReadLink(dir: ?HANDLE, sub_path_w: []const u16, out_buffer: []u16) ReadLi defer CloseHandle(result_handle); var reparse_buf: [MAXIMUM_REPARSE_DATA_BUFFER_SIZE]u8 align(@alignOf(REPARSE_DATA_BUFFER)) = undefined; - _ = DeviceIoControl(result_handle, FSCTL.GET_REPARSE_POINT, .{ .out = reparse_buf[0..] }) catch |err| switch (err) { - error.PipeClosing => unreachable, - error.PipeAlreadyConnected => unreachable, - error.PipeAlreadyListening => unreachable, - error.AccessDenied => return error.Unexpected, - error.UnrecognizedVolume => return error.Unexpected, - error.Pending => unreachable, - else => |e| return e, - }; + const rc = DeviceIoControl(result_handle, FSCTL.GET_REPARSE_POINT, .{ .out = reparse_buf[0..] }); + switch (rc) { + .SUCCESS => {}, + else => return unexpectedStatus(rc), + } const reparse_struct: *const REPARSE_DATA_BUFFER = @ptrCast(@alignCast(&reparse_buf[0])); const IoReparseTagInt = @typeInfo(IO_REPARSE_TAG).@"struct".backing_integer.?; @@ -3744,14 +3708,14 @@ pub fn GetFinalPathNameByHandle( input_struct.DeviceNameLength = @intCast(volume_name_u16.len * 2); @memcpy(input_buf[@sizeOf(MOUNTMGR_MOUNT_POINT)..][0 .. volume_name_u16.len * 2], @as([*]const u8, @ptrCast(volume_name_u16.ptr))); - DeviceIoControl(mgmt_handle, IOCTL.MOUNTMGR.QUERY_POINTS, .{ .in = &input_buf, .out = &output_buf }) catch |err| switch (err) { - error.PipeClosing => unreachable, - error.PipeAlreadyConnected => unreachable, - error.PipeAlreadyListening => unreachable, - error.AccessDenied => return error.Unexpected, - error.Pending => unreachable, - else => |e| return e, - }; + { + const rc = DeviceIoControl(mgmt_handle, IOCTL.MOUNTMGR.QUERY_POINTS, .{ .in = &input_buf, .out = &output_buf }); + switch (rc) { + .SUCCESS => {}, + .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, + else => return unexpectedStatus(rc), + } + } const mount_points_struct: *const MOUNTMGR_MOUNT_POINTS = @ptrCast(&output_buf[0]); const mount_points = @as( @@ -3803,14 +3767,12 @@ pub fn GetFinalPathNameByHandle( vol_input_struct.DeviceNameLength = @intCast(symlink.len * 2); @memcpy(@as([*]WCHAR, &vol_input_struct.DeviceName)[0..symlink.len], symlink); - DeviceIoControl(mgmt_handle, IOCTL.MOUNTMGR.QUERY_DOS_VOLUME_PATH, .{ .in = &vol_input_buf, .out = &vol_output_buf }) catch |err| switch (err) { - error.PipeClosing => unreachable, - error.PipeAlreadyConnected => unreachable, - error.PipeAlreadyListening => unreachable, - error.AccessDenied => return error.Unexpected, - error.Pending => unreachable, - else => |e| return e, - }; + const rc = DeviceIoControl(mgmt_handle, IOCTL.MOUNTMGR.QUERY_DOS_VOLUME_PATH, .{ .in = &vol_input_buf, .out = &vol_output_buf }); + switch (rc) { + .SUCCESS => {}, + .UNRECOGNIZED_VOLUME => return error.UnrecognizedVolume, + else => return unexpectedStatus(rc), + } const volume_paths_struct: *const MOUNTMGR_VOLUME_PATHS = @ptrCast(&vol_output_buf[0]); const volume_path = std.mem.sliceTo(@as( [*]const u16, -- 2.54.0 From 36cb5ea5f4c9f1a96953f30812ebaf79f8a546d6 Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Wed, 10 Dec 2025 18:59:15 -0800 Subject: [PATCH 2/2] windows.ReadLink: handle NOT_A_REPARSE_POINT and add test --- lib/std/fs/test.zig | 26 ++++++++++++++++++++++++++ lib/std/os/windows.zig | 2 ++ 2 files changed, 28 insertions(+) diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index 6f6489e80f7fc3a63c25ea971f633c1f1186545b..13438412ce4a01ab5d97848dd2f884126001a269 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -218,6 +218,32 @@ test "Dir.readLink" { }.impl); } +test "Dir.readLink on non-symlinks" { + try testWithAllSupportedPathTypes(struct { + fn impl(ctx: *TestContext) !void { + const file_path = try ctx.transformPath("file.txt"); + try ctx.dir.writeFile(.{ .sub_path = file_path, .data = "nonsense" }); + const dir_path = try ctx.transformPath("subdir"); + try ctx.dir.makeDir(dir_path); + + // file + var buffer: [fs.max_path_bytes]u8 = undefined; + try std.testing.expectError(error.NotLink, ctx.dir.readLink(file_path, &buffer)); + if (builtin.os.tag == .windows) { + var file_path_w = try std.os.windows.sliceToPrefixedFileW(ctx.dir.fd, file_path); + try std.testing.expectError(error.NotLink, ctx.dir.readLinkW(file_path_w.span(), &file_path_w.data)); + } + + // dir + try std.testing.expectError(error.NotLink, ctx.dir.readLink(dir_path, &buffer)); + if (builtin.os.tag == .windows) { + var dir_path_w = try std.os.windows.sliceToPrefixedFileW(ctx.dir.fd, dir_path); + try std.testing.expectError(error.NotLink, ctx.dir.readLinkW(dir_path_w.span(), &dir_path_w.data)); + } + } + }.impl); +} + fn testReadLink(dir: Dir, target_path: []const u8, symlink_path: []const u8) !void { var buffer: [fs.max_path_bytes]u8 = undefined; const actual = try dir.readLink(symlink_path, buffer[0..]); diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index bb908143c10b1bb2fa03e1d6c764fac8b540e93e..5f41a6a1fdcdcfea2cd32b4f8df3840b53ca4b05 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -3125,6 +3125,7 @@ pub const ReadLinkError = error{ BadPathName, AntivirusInterference, UnsupportedReparsePointType, + NotLink, }; /// `sub_path_w` will never be accessed after `out_buffer` has been written to, so it @@ -3155,6 +3156,7 @@ pub fn ReadLink(dir: ?HANDLE, sub_path_w: []const u16, out_buffer: []u16) ReadLi const rc = DeviceIoControl(result_handle, FSCTL.GET_REPARSE_POINT, .{ .out = reparse_buf[0..] }); switch (rc) { .SUCCESS => {}, + .NOT_A_REPARSE_POINT => return error.NotLink, else => return unexpectedStatus(rc), } -- 2.54.0