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" {
218218 }.impl);
219219}
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
221247fn testReadLink(dir: Dir, target_path: []const u8, symlink_path: []const u8) !void {
222248 var buffer: [fs.max_path_bytes]u8 = undefined;
223249 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
25742574 wr.* = write;
25752575}
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
25942577/// A Zig wrapper around `NtDeviceIoControlFile` and `NtFsControlFile` syscalls.
25952578/// It implements similar behavior to `DeviceIoControl` and is meant to serve
25962579/// as a direct substitute for that call.
......@@ -2606,9 +2589,9 @@ pub fn DeviceIoControl(
26062589 in: []const u8 = &.{},
26072590 out: []u8 = &.{},
26082591 },
2609) DeviceIoControlError!void {
2592) NTSTATUS {
26102593 var io_status_block: IO_STATUS_BLOCK = undefined;
2611 const rc = switch (io_control_code.DeviceType) {
2594 return switch (io_control_code.DeviceType) {
26122595 .FILE_SYSTEM, .NAMED_PIPE => ntdll.NtFsControlFile(
26132596 device,
26142597 opts.event,
......@@ -2634,19 +2617,6 @@ pub fn DeviceIoControl(
26342617 @intCast(opts.out.len),
26352618 ),
26362619 };
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 }
26502620}
26512621
26522622pub fn GetOverlappedResult(h: HANDLE, overlapped: *OVERLAPPED, wait: bool) !DWORD {
......@@ -3037,9 +3007,6 @@ pub const CreateSymbolicLinkError = error{
30373007 NoDevice,
30383008 NetworkNotFound,
30393009 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,
30433010 Unexpected,
30443011};
30453012
......@@ -3139,13 +3106,14 @@ pub fn CreateSymbolicLink(
31393106 @memcpy(buffer[@sizeOf(SYMLINK_DATA)..][0 .. final_target_path.len * 2], @as([*]const u8, @ptrCast(final_target_path)));
31403107 const paths_start = @sizeOf(SYMLINK_DATA) + final_target_path.len * 2;
31413108 @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) {
3143 error.PipeClosing => unreachable,
3144 error.PipeAlreadyConnected => unreachable,
3145 error.PipeAlreadyListening => unreachable,
3146 error.Pending => unreachable,
3147 else => |e| return e,
3148 };
3109 const rc = DeviceIoControl(symlink_handle, FSCTL.SET_REPARSE_POINT, .{ .in = buffer[0..buf_len] });
3110 switch (rc) {
3111 .SUCCESS => {},
3112 .PRIVILEGE_NOT_HELD => return error.AccessDenied,
3113 .ACCESS_DENIED => return error.AccessDenied,
3114 .INVALID_DEVICE_REQUEST => return error.AccessDenied, // Not supported by the underlying filesystem
3115 else => return unexpectedStatus(rc),
3116 }
31493117}
31503118
31513119pub const ReadLinkError = error{
......@@ -3157,6 +3125,7 @@ pub const ReadLinkError = error{
31573125 BadPathName,
31583126 AntivirusInterference,
31593127 UnsupportedReparsePointType,
3128 NotLink,
31603129};
31613130
31623131/// `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
31843153 defer CloseHandle(result_handle);
31853154
31863155 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) {
3188 error.PipeClosing => unreachable,
3189 error.PipeAlreadyConnected => unreachable,
3190 error.PipeAlreadyListening => unreachable,
3191 error.AccessDenied => return error.Unexpected,
3192 error.UnrecognizedVolume => return error.Unexpected,
3193 error.Pending => unreachable,
3194 else => |e| return e,
3195 };
3156 const rc = DeviceIoControl(result_handle, FSCTL.GET_REPARSE_POINT, .{ .out = reparse_buf[0..] });
3157 switch (rc) {
3158 .SUCCESS => {},
3159 .NOT_A_REPARSE_POINT => return error.NotLink,
3160 else => return unexpectedStatus(rc),
3161 }
31963162
31973163 const reparse_struct: *const REPARSE_DATA_BUFFER = @ptrCast(@alignCast(&reparse_buf[0]));
31983164 const IoReparseTagInt = @typeInfo(IO_REPARSE_TAG).@"struct".backing_integer.?;
......@@ -3744,14 +3710,14 @@ pub fn GetFinalPathNameByHandle(
37443710 input_struct.DeviceNameLength = @intCast(volume_name_u16.len * 2);
37453711 @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) {
3748 error.PipeClosing => unreachable,
3749 error.PipeAlreadyConnected => unreachable,
3750 error.PipeAlreadyListening => unreachable,
3751 error.AccessDenied => return error.Unexpected,
3752 error.Pending => unreachable,
3753 else => |e| return e,
3754 };
3713 {
3714 const rc = DeviceIoControl(mgmt_handle, IOCTL.MOUNTMGR.QUERY_POINTS, .{ .in = &input_buf, .out = &output_buf });
3715 switch (rc) {
3716 .SUCCESS => {},
3717 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
3718 else => return unexpectedStatus(rc),
3719 }
3720 }
37553721 const mount_points_struct: *const MOUNTMGR_MOUNT_POINTS = @ptrCast(&output_buf[0]);
37563722
37573723 const mount_points = @as(
......@@ -3803,14 +3769,12 @@ pub fn GetFinalPathNameByHandle(
38033769 vol_input_struct.DeviceNameLength = @intCast(symlink.len * 2);
38043770 @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) {
3807 error.PipeClosing => unreachable,
3808 error.PipeAlreadyConnected => unreachable,
3809 error.PipeAlreadyListening => unreachable,
3810 error.AccessDenied => return error.Unexpected,
3811 error.Pending => unreachable,
3812 else => |e| return e,
3813 };
3772 const rc = DeviceIoControl(mgmt_handle, IOCTL.MOUNTMGR.QUERY_DOS_VOLUME_PATH, .{ .in = &vol_input_buf, .out = &vol_output_buf });
3773 switch (rc) {
3774 .SUCCESS => {},
3775 .UNRECOGNIZED_VOLUME => return error.UnrecognizedVolume,
3776 else => return unexpectedStatus(rc),
3777 }
38143778 const volume_paths_struct: *const MOUNTMGR_VOLUME_PATHS = @ptrCast(&vol_output_buf[0]);
38153779 const volume_path = std.mem.sliceTo(@as(
38163780 [*]const u16,