authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-12-19 18:50:27+01:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-12-19 18:50:27+01:00
log5fa7f13082145c10d0b536cc894f60323ffacd61
tree800f09325ab033898ccbb4cef0cf98e0ada06b15
parentf9419c81cffa95a4256ce0b784b3b016abe5bf49
parent36cb5ea5f4c9f1a96953f30812ebaf79f8a546d6

Merge pull request 'Windows: Avoid ever-expanding DeviceIoControl error set, handle NOT_A_REPARSE_POINT in ReadLink' (#30186) from squeek502/zig:win-deviceiocontrol into master

Reviewed-on: https://codeberg.org/ziglang/zig/pulls/30186

2 files changed, 57 insertions(+), 67 deletions(-)

lib/std/fs/test.zig+26
...@@ -218,6 +218,32 @@ test "Dir.readLink" {...@@ -218,6 +218,32 @@ test "Dir.readLink" {
218 }.impl);218 }.impl);
219}219}
220220
221test "Dir.readLink on non-symlinks" {
222 try testWithAllSupportedPathTypes(struct {
223 fn impl(ctx: *TestContext) !void {
224 const file_path = try ctx.transformPath("file.txt");
225 try ctx.dir.writeFile(.{ .sub_path = file_path, .data = "nonsense" });
226 const dir_path = try ctx.transformPath("subdir");
227 try ctx.dir.makeDir(dir_path);
228
229 // file
230 var buffer: [fs.max_path_bytes]u8 = undefined;
231 try std.testing.expectError(error.NotLink, ctx.dir.readLink(file_path, &buffer));
232 if (builtin.os.tag == .windows) {
233 var file_path_w = try std.os.windows.sliceToPrefixedFileW(ctx.dir.fd, file_path);
234 try std.testing.expectError(error.NotLink, ctx.dir.readLinkW(file_path_w.span(), &file_path_w.data));
235 }
236
237 // dir
238 try std.testing.expectError(error.NotLink, ctx.dir.readLink(dir_path, &buffer));
239 if (builtin.os.tag == .windows) {
240 var dir_path_w = try std.os.windows.sliceToPrefixedFileW(ctx.dir.fd, dir_path);
241 try std.testing.expectError(error.NotLink, ctx.dir.readLinkW(dir_path_w.span(), &dir_path_w.data));
242 }
243 }
244 }.impl);
245}
246
221fn testReadLink(dir: Dir, target_path: []const u8, symlink_path: []const u8) !void {247fn testReadLink(dir: Dir, target_path: []const u8, symlink_path: []const u8) !void {
222 var buffer: [fs.max_path_bytes]u8 = undefined;248 var buffer: [fs.max_path_bytes]u8 = undefined;
223 const actual = try dir.readLink(symlink_path, buffer[0..]);249 const actual = try dir.readLink(symlink_path, buffer[0..]);
lib/std/os/windows.zig+31-67
...@@ -2574,23 +2574,6 @@ pub fn CreatePipe(rd: *HANDLE, wr: *HANDLE, sattr: *const SECURITY_ATTRIBUTES) C...@@ -2574,23 +2574,6 @@ pub fn CreatePipe(rd: *HANDLE, wr: *HANDLE, sattr: *const SECURITY_ATTRIBUTES) C
2574 wr.* = write;2574 wr.* = write;
2575}2575}
25762576
2577pub const DeviceIoControlError = error{
2578 AccessDenied,
2579 /// The volume does not contain a recognized file system. File system
2580 /// drivers might not be loaded, or the volume may be corrupt.
2581 UnrecognizedVolume,
2582 Pending,
2583 /// Attempted to connect a named pipe in the "closing" state, meaning a previous client has
2584 /// has closed their handle but we have not yet disconnected the pipe.
2585 PipeClosing,
2586 /// Attempted to connect a named pipe in the "connected" state, meaning a client has already
2587 /// opened the pipe; there is a good connection between client and server.
2588 PipeAlreadyConnected,
2589 /// Attempted to connect a non-blocking named pipe which is already listening for connections.
2590 PipeAlreadyListening,
2591 Unexpected,
2592};
2593
2594/// A Zig wrapper around `NtDeviceIoControlFile` and `NtFsControlFile` syscalls.2577/// A Zig wrapper around `NtDeviceIoControlFile` and `NtFsControlFile` syscalls.
2595/// It implements similar behavior to `DeviceIoControl` and is meant to serve2578/// It implements similar behavior to `DeviceIoControl` and is meant to serve
2596/// as a direct substitute for that call.2579/// as a direct substitute for that call.
...@@ -2606,9 +2589,9 @@ pub fn DeviceIoControl(...@@ -2606,9 +2589,9 @@ pub fn DeviceIoControl(
2606 in: []const u8 = &.{},2589 in: []const u8 = &.{},
2607 out: []u8 = &.{},2590 out: []u8 = &.{},
2608 },2591 },
2609) DeviceIoControlError!void {2592) NTSTATUS {
2610 var io_status_block: IO_STATUS_BLOCK = undefined;2593 var io_status_block: IO_STATUS_BLOCK = undefined;
2611 const rc = switch (io_control_code.DeviceType) {2594 return switch (io_control_code.DeviceType) {
2612 .FILE_SYSTEM, .NAMED_PIPE => ntdll.NtFsControlFile(2595 .FILE_SYSTEM, .NAMED_PIPE => ntdll.NtFsControlFile(
2613 device,2596 device,
2614 opts.event,2597 opts.event,
...@@ -2634,19 +2617,6 @@ pub fn DeviceIoControl(...@@ -2634,19 +2617,6 @@ pub fn DeviceIoControl(
2634 @intCast(opts.out.len),2617 @intCast(opts.out.len),
2635 ),2618 ),
2636 };2619 };
2637 switch (rc) {
2638 .SUCCESS => {},
2639 .PIPE_CLOSING => return error.PipeClosing,
2640 .PIPE_CONNECTED => return error.PipeAlreadyConnected,
2641 .PIPE_LISTENING => return error.PipeAlreadyListening,
2642 .PRIVILEGE_NOT_HELD => return error.AccessDenied,
2643 .ACCESS_DENIED => return error.AccessDenied,
2644 .INVALID_DEVICE_REQUEST => return error.AccessDenied, // Not supported by the underlying filesystem
2645 .INVALID_PARAMETER => unreachable,
2646 .UNRECOGNIZED_VOLUME => return error.UnrecognizedVolume,
2647 .PENDING => return error.Pending,
2648 else => return unexpectedStatus(rc),
2649 }
2650}2620}
26512621
2652pub fn GetOverlappedResult(h: HANDLE, overlapped: *OVERLAPPED, wait: bool) !DWORD {2622pub fn GetOverlappedResult(h: HANDLE, overlapped: *OVERLAPPED, wait: bool) !DWORD {
...@@ -3037,9 +3007,6 @@ pub const CreateSymbolicLinkError = error{...@@ -3037,9 +3007,6 @@ pub const CreateSymbolicLinkError = error{
3037 NoDevice,3007 NoDevice,
3038 NetworkNotFound,3008 NetworkNotFound,
3039 BadPathName,3009 BadPathName,
3040 /// The volume does not contain a recognized file system. File system
3041 /// drivers might not be loaded, or the volume may be corrupt.
3042 UnrecognizedVolume,
3043 Unexpected,3010 Unexpected,
3044};3011};
30453012
...@@ -3139,13 +3106,14 @@ pub fn CreateSymbolicLink(...@@ -3139,13 +3106,14 @@ pub fn CreateSymbolicLink(
3139 @memcpy(buffer[@sizeOf(SYMLINK_DATA)..][0 .. final_target_path.len * 2], @as([*]const u8, @ptrCast(final_target_path)));3106 @memcpy(buffer[@sizeOf(SYMLINK_DATA)..][0 .. final_target_path.len * 2], @as([*]const u8, @ptrCast(final_target_path)));
3140 const paths_start = @sizeOf(SYMLINK_DATA) + final_target_path.len * 2;3107 const paths_start = @sizeOf(SYMLINK_DATA) + final_target_path.len * 2;
3141 @memcpy(buffer[paths_start..][0 .. final_target_path.len * 2], @as([*]const u8, @ptrCast(final_target_path)));3108 @memcpy(buffer[paths_start..][0 .. final_target_path.len * 2], @as([*]const u8, @ptrCast(final_target_path)));
3142 _ = DeviceIoControl(symlink_handle, FSCTL.SET_REPARSE_POINT, .{ .in = buffer[0..buf_len] }) catch |err| switch (err) {3109 const rc = DeviceIoControl(symlink_handle, FSCTL.SET_REPARSE_POINT, .{ .in = buffer[0..buf_len] });
3143 error.PipeClosing => unreachable,3110 switch (rc) {
3144 error.PipeAlreadyConnected => unreachable,3111 .SUCCESS => {},
3145 error.PipeAlreadyListening => unreachable,3112 .PRIVILEGE_NOT_HELD => return error.AccessDenied,
3146 error.Pending => unreachable,3113 .ACCESS_DENIED => return error.AccessDenied,
3147 else => |e| return e,3114 .INVALID_DEVICE_REQUEST => return error.AccessDenied, // Not supported by the underlying filesystem
3148 };3115 else => return unexpectedStatus(rc),
3116 }
3149}3117}
31503118
3151pub const ReadLinkError = error{3119pub const ReadLinkError = error{
...@@ -3157,6 +3125,7 @@ pub const ReadLinkError = error{...@@ -3157,6 +3125,7 @@ pub const ReadLinkError = error{
3157 BadPathName,3125 BadPathName,
3158 AntivirusInterference,3126 AntivirusInterference,
3159 UnsupportedReparsePointType,3127 UnsupportedReparsePointType,
3128 NotLink,
3160};3129};
31613130
3162/// `sub_path_w` will never be accessed after `out_buffer` has been written to, so it3131/// `sub_path_w` will never be accessed after `out_buffer` has been written to, so it
...@@ -3184,15 +3153,12 @@ pub fn ReadLink(dir: ?HANDLE, sub_path_w: []const u16, out_buffer: []u16) ReadLi...@@ -3184,15 +3153,12 @@ pub fn ReadLink(dir: ?HANDLE, sub_path_w: []const u16, out_buffer: []u16) ReadLi
3184 defer CloseHandle(result_handle);3153 defer CloseHandle(result_handle);
31853154
3186 var reparse_buf: [MAXIMUM_REPARSE_DATA_BUFFER_SIZE]u8 align(@alignOf(REPARSE_DATA_BUFFER)) = undefined;3155 var reparse_buf: [MAXIMUM_REPARSE_DATA_BUFFER_SIZE]u8 align(@alignOf(REPARSE_DATA_BUFFER)) = undefined;
3187 _ = DeviceIoControl(result_handle, FSCTL.GET_REPARSE_POINT, .{ .out = reparse_buf[0..] }) catch |err| switch (err) {3156 const rc = DeviceIoControl(result_handle, FSCTL.GET_REPARSE_POINT, .{ .out = reparse_buf[0..] });
3188 error.PipeClosing => unreachable,3157 switch (rc) {
3189 error.PipeAlreadyConnected => unreachable,3158 .SUCCESS => {},
3190 error.PipeAlreadyListening => unreachable,3159 .NOT_A_REPARSE_POINT => return error.NotLink,
3191 error.AccessDenied => return error.Unexpected,3160 else => return unexpectedStatus(rc),
3192 error.UnrecognizedVolume => return error.Unexpected,3161 }
3193 error.Pending => unreachable,
3194 else => |e| return e,
3195 };
31963162
3197 const reparse_struct: *const REPARSE_DATA_BUFFER = @ptrCast(@alignCast(&reparse_buf[0]));3163 const reparse_struct: *const REPARSE_DATA_BUFFER = @ptrCast(@alignCast(&reparse_buf[0]));
3198 const IoReparseTagInt = @typeInfo(IO_REPARSE_TAG).@"struct".backing_integer.?;3164 const IoReparseTagInt = @typeInfo(IO_REPARSE_TAG).@"struct".backing_integer.?;
...@@ -3744,14 +3710,14 @@ pub fn GetFinalPathNameByHandle(...@@ -3744,14 +3710,14 @@ pub fn GetFinalPathNameByHandle(
3744 input_struct.DeviceNameLength = @intCast(volume_name_u16.len * 2);3710 input_struct.DeviceNameLength = @intCast(volume_name_u16.len * 2);
3745 @memcpy(input_buf[@sizeOf(MOUNTMGR_MOUNT_POINT)..][0 .. volume_name_u16.len * 2], @as([*]const u8, @ptrCast(volume_name_u16.ptr)));3711 @memcpy(input_buf[@sizeOf(MOUNTMGR_MOUNT_POINT)..][0 .. volume_name_u16.len * 2], @as([*]const u8, @ptrCast(volume_name_u16.ptr)));
37463712
3747 DeviceIoControl(mgmt_handle, IOCTL.MOUNTMGR.QUERY_POINTS, .{ .in = &input_buf, .out = &output_buf }) catch |err| switch (err) {3713 {
3748 error.PipeClosing => unreachable,3714 const rc = DeviceIoControl(mgmt_handle, IOCTL.MOUNTMGR.QUERY_POINTS, .{ .in = &input_buf, .out = &output_buf });
3749 error.PipeAlreadyConnected => unreachable,3715 switch (rc) {
3750 error.PipeAlreadyListening => unreachable,3716 .SUCCESS => {},
3751 error.AccessDenied => return error.Unexpected,3717 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
3752 error.Pending => unreachable,3718 else => return unexpectedStatus(rc),
3753 else => |e| return e,3719 }
3754 };3720 }
3755 const mount_points_struct: *const MOUNTMGR_MOUNT_POINTS = @ptrCast(&output_buf[0]);3721 const mount_points_struct: *const MOUNTMGR_MOUNT_POINTS = @ptrCast(&output_buf[0]);
37563722
3757 const mount_points = @as(3723 const mount_points = @as(
...@@ -3803,14 +3769,12 @@ pub fn GetFinalPathNameByHandle(...@@ -3803,14 +3769,12 @@ pub fn GetFinalPathNameByHandle(
3803 vol_input_struct.DeviceNameLength = @intCast(symlink.len * 2);3769 vol_input_struct.DeviceNameLength = @intCast(symlink.len * 2);
3804 @memcpy(@as([*]WCHAR, &vol_input_struct.DeviceName)[0..symlink.len], symlink);3770 @memcpy(@as([*]WCHAR, &vol_input_struct.DeviceName)[0..symlink.len], symlink);
38053771
3806 DeviceIoControl(mgmt_handle, IOCTL.MOUNTMGR.QUERY_DOS_VOLUME_PATH, .{ .in = &vol_input_buf, .out = &vol_output_buf }) catch |err| switch (err) {3772 const rc = DeviceIoControl(mgmt_handle, IOCTL.MOUNTMGR.QUERY_DOS_VOLUME_PATH, .{ .in = &vol_input_buf, .out = &vol_output_buf });
3807 error.PipeClosing => unreachable,3773 switch (rc) {
3808 error.PipeAlreadyConnected => unreachable,3774 .SUCCESS => {},
3809 error.PipeAlreadyListening => unreachable,3775 .UNRECOGNIZED_VOLUME => return error.UnrecognizedVolume,
3810 error.AccessDenied => return error.Unexpected,3776 else => return unexpectedStatus(rc),
3811 error.Pending => unreachable,3777 }
3812 else => |e| return e,
3813 };
3814 const volume_paths_struct: *const MOUNTMGR_VOLUME_PATHS = @ptrCast(&vol_output_buf[0]);3778 const volume_paths_struct: *const MOUNTMGR_VOLUME_PATHS = @ptrCast(&vol_output_buf[0]);
3815 const volume_path = std.mem.sliceTo(@as(3779 const volume_path = std.mem.sliceTo(@as(
3816 [*]const u16,3780 [*]const u16,