authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-02-01 01:08:01-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-02-01 01:08:01-08:00
log59073484baf89072aa02f23fe9a09662e5def100
tree80091d7af70172ec2c656dce9b407bc21119ae78
parente5454ff780ae4571cfa71a2edb6f4287eb8cf4de

std.Io: add ioctl / DeviceIoControlFile API


5 files changed, 211 insertions(+), 58 deletions(-)

lib/std/Io.zig+28-1
......@@ -257,6 +257,9 @@ pub const VTable = struct {
257257pub const Operation = union(enum) {
258258 file_read_streaming: FileReadStreaming,
259259 file_write_streaming: FileWriteStreaming,
260 /// On Windows this is NtDeviceIoControlFile. On POSIX this is ioctl. On
261 /// other systems this tag is unreachable.
262 device_io_control: DeviceIoControl,
260263
261264 pub const Tag = @typeInfo(Operation).@"union".tag_type.?;
262265
......@@ -324,13 +327,37 @@ pub const Operation = union(enum) {
324327 pub const Result = Error!usize;
325328 };
326329
330 pub const DeviceIoControl = switch (builtin.os.tag) {
331 .wasi => noreturn,
332 .windows => struct {
333 file: File,
334 IoControlCode: std.os.windows.CTL_CODE,
335 InputBuffer: ?*const anyopaque,
336 InputBufferLength: u32,
337 OutputBuffer: ?*anyopaque,
338 OutputBufferLength: u32,
339
340 pub const Result = std.os.windows.IO_STATUS_BLOCK;
341 },
342 else => struct {
343 file: File,
344 /// Device-dependent operation code.
345 code: u32,
346 arg: ?*anyopaque,
347
348 /// Device and operation dependent result. Negative values are
349 /// negative errno.
350 pub const Result = i32;
351 },
352 };
353
327354 pub const Result = Result: {
328355 const operation_fields = @typeInfo(Operation).@"union".fields;
329356 var field_names: [operation_fields.len][]const u8 = undefined;
330357 var field_types: [operation_fields.len]type = undefined;
331358 for (operation_fields, &field_names, &field_types) |field, *field_name, *field_type| {
332359 field_name.* = field.name;
333 field_type.* = field.type.Result;
360 field_type.* = if (field.type == noreturn) noreturn else field.type.Result;
334361 }
335362 break :Result @Union(.auto, Tag, &field_names, &field_types, &@splat(.{}));
336363 };
lib/std/Io/Threaded.zig+163-25
......@@ -2500,6 +2500,9 @@ fn operate(userdata: ?*anyopaque, operation: Io.Operation) Io.Cancelable!Io.Oper
25002500 else => |e| e,
25012501 },
25022502 },
2503 .device_io_control => |*o| return .{
2504 .device_io_control = try deviceIoControl(t, o),
2505 },
25032506 }
25042507}
25052508
......@@ -2531,6 +2534,14 @@ fn batchAwaitAsync(userdata: ?*anyopaque, b: *Io.Batch) Io.Cancelable!void {
25312534 poll_buffer[poll_len] = .{ .fd = o.file.handle, .events = posix.POLL.OUT, .revents = 0 };
25322535 poll_len += 1;
25332536 },
2537 .device_io_control => |o| {
2538 poll_buffer[poll_len] = .{
2539 .fd = o.file.handle,
2540 .events = posix.POLL.OUT | posix.POLL.IN | posix.POLL.ERR,
2541 .revents = 0,
2542 };
2543 poll_len += 1;
2544 },
25342545 }
25352546 index = submission.node.next;
25362547 }
......@@ -2696,6 +2707,7 @@ fn batchAwaitConcurrent(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout
26962707 switch (submission.operation) {
26972708 .file_read_streaming => |o| try poll_storage.add(o.file, posix.POLL.IN),
26982709 .file_write_streaming => |o| try poll_storage.add(o.file, posix.POLL.OUT),
2710 .device_io_control => |o| try poll_storage.add(o.file, posix.POLL.IN | posix.POLL.OUT | posix.POLL.ERR),
26992711 }
27002712 index = submission.node.next;
27012713 }
......@@ -2874,6 +2886,7 @@ fn batchApc(apc_context: ?*anyopaque, iosb: *windows.IO_STATUS_BLOCK, _: windows
28742886 const result: Io.Operation.Result = switch (pending.tag) {
28752887 .file_read_streaming => .{ .file_read_streaming = ntReadFileResult(iosb) },
28762888 .file_write_streaming => .{ .file_write_streaming = ntWriteFileResult(iosb) },
2889 .device_io_control => .{ .device_io_control = iosb.* },
28772890 };
28782891 storage.* = .{ .completion = .{ .node = .{ .next = .none }, .result = result } };
28792892 },
......@@ -3020,6 +3033,59 @@ fn batchAwaitWindows(b: *Io.Batch, concurrency: bool) error{ Canceled, Concurren
30203033 else => |status| {
30213034 syscall.finish();
30223035
3036 context.iosb.u.Status = status;
3037 batchApc(b, &context.iosb, 0);
3038 break;
3039 },
3040 };
3041 }
3042 },
3043 .device_io_control => |o| {
3044 if (o.file.flags.nonblocking) {
3045 context.file = o.file.handle;
3046 switch (windows.ntdll.NtDeviceIoControlFile(
3047 o.file.handle,
3048 null, // event
3049 &batchApc,
3050 b,
3051 &context.iosb,
3052 o.IoControlCode,
3053 o.InputBuffer,
3054 o.InputBufferLength,
3055 o.OutputBuffer,
3056 o.OutputBufferLength,
3057 )) {
3058 .PENDING, .SUCCESS => {},
3059 .CANCELLED => unreachable,
3060 else => |status| {
3061 context.iosb.u.Status = status;
3062 batchApc(b, &context.iosb, 0);
3063 },
3064 }
3065 } else {
3066 if (concurrency) return error.ConcurrencyUnavailable;
3067
3068 const syscall: Syscall = try .start();
3069 while (true) switch (windows.ntdll.NtDeviceIoControlFile(
3070 o.file.handle,
3071 null, // event
3072 null, // APC routine
3073 null, // APC context
3074 &context.iosb,
3075 o.IoControlCode,
3076 o.InputBuffer,
3077 o.InputBufferLength,
3078 o.OutputBuffer,
3079 o.OutputBufferLength,
3080 )) {
3081 .PENDING => unreachable, // unrecoverable: wrong File nonblocking flag
3082 .CANCELLED => {
3083 try syscall.checkCancel();
3084 continue;
3085 },
3086 else => |status| {
3087 syscall.finish();
3088
30233089 context.iosb.u.Status = status;
30243090 batchApc(b, &context.iosb, 0);
30253091 break;
......@@ -12986,31 +13052,18 @@ fn netInterfaceNameResolve(
1298613052 };
1298713053
1298813054 const syscall: Syscall = try .start();
12989 while (true) {
12990 switch (posix.errno(posix.system.ioctl(sock_fd, posix.SIOCGIFINDEX, @intFromPtr(&ifr)))) {
12991 .SUCCESS => {
12992 syscall.finish();
12993 return .{ .index = @bitCast(ifr.ifru.ivalue) };
12994 },
12995 .INTR => {
12996 try syscall.checkCancel();
12997 continue;
12998 },
12999 else => |e| {
13000 syscall.finish();
13001 switch (e) {
13002 .INVAL => |err| return errnoBug(err), // Bad parameters.
13003 .NOTTY => |err| return errnoBug(err),
13004 .NXIO => |err| return errnoBug(err),
13005 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
13006 .FAULT => |err| return errnoBug(err), // Bad pointer parameter.
13007 .IO => |err| return errnoBug(err), // sock_fd is not a file descriptor
13008 .NODEV => return error.InterfaceNotFound,
13009 else => |err| return posix.unexpectedErrno(err),
13010 }
13011 },
13012 }
13013 }
13055 while (true) switch (posix.errno(posix.system.ioctl(sock_fd, posix.SIOCGIFINDEX, @intFromPtr(&ifr)))) {
13056 .SUCCESS => {
13057 syscall.finish();
13058 return .{ .index = @bitCast(ifr.ifru.ivalue) };
13059 },
13060 .INTR => {
13061 try syscall.checkCancel();
13062 continue;
13063 },
13064 .NODEV => return syscall.fail(error.InterfaceNotFound),
13065 else => |err| return syscall.unexpectedErrno(err),
13066 };
1301413067 }
1301513068
1301613069 if (is_windows) {
......@@ -17849,3 +17902,88 @@ fn mmSyncWrite(file: File, memory: []u8, offset: u64) File.WritePositionalError!
1784917902 }
1785017903 }
1785117904}
17905
17906fn deviceIoControl(t: *Threaded, o: *const Io.Operation.DeviceIoControl) Io.Cancelable!Io.Operation.DeviceIoControl.Result {
17907 _ = t;
17908 if (is_windows) {
17909 var iosb: windows.IO_STATUS_BLOCK = undefined;
17910 if (o.file.flags.nonblocking) {
17911 var done: bool = false;
17912 switch (windows.ntdll.NtDeviceIoControlFile(
17913 o.file.handle,
17914 null, // event
17915 flagApc,
17916 &done, // APC context
17917 &iosb,
17918 o.IoControlCode,
17919 o.InputBuffer,
17920 o.InputBufferLength,
17921 o.OutputBuffer,
17922 o.OutputBufferLength,
17923 )) {
17924 // We must wait for the APC routine.
17925 .PENDING, .SUCCESS => while (!done) {
17926 // Once we get here we must not return from the function until the
17927 // operation completes, thereby releasing reference to io_status_block.
17928 const alertable_syscall = AlertableSyscall.start() catch |err| switch (err) {
17929 error.Canceled => |e| {
17930 var cancel_iosb: windows.IO_STATUS_BLOCK = undefined;
17931 _ = windows.ntdll.NtCancelIoFileEx(o.file.handle, &iosb, &cancel_iosb);
17932 while (!done) waitForApcOrAlert();
17933 return e;
17934 },
17935 };
17936 waitForApcOrAlert();
17937 alertable_syscall.finish();
17938 },
17939 else => |status| iosb.u.Status = status,
17940 }
17941 } else {
17942 const syscall: Syscall = try .start();
17943 while (true) switch (windows.ntdll.NtDeviceIoControlFile(
17944 o.file.handle,
17945 null, // event
17946 null, // APC routine
17947 null, // APC context
17948 &iosb,
17949 o.IoControlCode,
17950 o.InputBuffer,
17951 o.InputBufferLength,
17952 o.OutputBuffer,
17953 o.OutputBufferLength,
17954 )) {
17955 .PENDING => unreachable, // unrecoverable: wrong asynchronous flag
17956 .CANCELLED => {
17957 try syscall.checkCancel();
17958 continue;
17959 },
17960 else => |status| {
17961 syscall.finish();
17962 iosb.u.Status = status;
17963 break;
17964 },
17965 };
17966 }
17967 return iosb;
17968 } else {
17969 const syscall: Syscall = try .start();
17970 while (true) {
17971 const rc = posix.system.ioctl(o.file.handle, @bitCast(o.code), @intFromPtr(o.arg));
17972 switch (posix.errno(rc)) {
17973 .SUCCESS => {
17974 syscall.finish();
17975 if (@TypeOf(rc) == usize) return @bitCast(@as(u32, @truncate(rc)));
17976 return rc;
17977 },
17978 .INTR => {
17979 try syscall.checkCancel();
17980 continue;
17981 },
17982 else => |err| {
17983 syscall.finish();
17984 return -@as(i32, @intFromEnum(err));
17985 },
17986 }
17987 }
17988 }
17989}
lib/std/Progress.zig+14-9
......@@ -573,7 +573,7 @@ fn updateTask(io: Io) void {
573573 {
574574 const resize_flag = wait(io, global_progress.initial_delay_ns);
575575 if (@atomicLoad(bool, &global_progress.done, .monotonic)) return;
576 maybeUpdateSize(resize_flag);
576 maybeUpdateSize(io, resize_flag) catch return;
577577
578578 const buffer, _ = computeRedraw(&serialized_buffer);
579579 if (io.vtable.tryLockStderr(io.userdata, null) catch return) |locked_stderr| {
......@@ -592,7 +592,7 @@ fn updateTask(io: Io) void {
592592 return clearWrittenWithEscapeCodes(stderr.file_writer) catch {};
593593 }
594594
595 maybeUpdateSize(resize_flag);
595 maybeUpdateSize(io, resize_flag) catch return;
596596
597597 const buffer, _ = computeRedraw(&serialized_buffer);
598598 if (io.vtable.tryLockStderr(io.userdata, null) catch return) |locked_stderr| {
......@@ -622,7 +622,7 @@ fn windowsApiUpdateTask(io: Io) void {
622622 {
623623 const resize_flag = wait(io, global_progress.initial_delay_ns);
624624 if (@atomicLoad(bool, &global_progress.done, .monotonic)) return;
625 maybeUpdateSize(resize_flag);
625 maybeUpdateSize(io, resize_flag) catch return;
626626
627627 const buffer, const nl_n = computeRedraw(&serialized_buffer);
628628 if (io.vtable.tryLockStderr(io.userdata, null) catch return) |locked_stderr| {
......@@ -643,7 +643,7 @@ fn windowsApiUpdateTask(io: Io) void {
643643 return clearWrittenWindowsApi() catch {};
644644 }
645645
646 maybeUpdateSize(resize_flag);
646 maybeUpdateSize(io, resize_flag) catch return;
647647
648648 const buffer, const nl_n = computeRedraw(&serialized_buffer);
649649 if (io.vtable.tryLockStderr(io.userdata, null) catch return) |locked_stderr| {
......@@ -1484,15 +1484,15 @@ fn writevNonblock(io: Io, file: Io.File, iov: [][]const u8) Io.File.Writer.Error
14841484 }
14851485}
14861486
1487fn maybeUpdateSize(resize_flag: bool) void {
1487fn maybeUpdateSize(io: Io, resize_flag: bool) !void {
14881488 if (!resize_flag) return;
14891489
1490 const fd = global_progress.terminal.handle;
1490 const file = global_progress.terminal;
14911491
14921492 if (is_windows) {
14931493 var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined;
14941494
1495 if (windows.kernel32.GetConsoleScreenBufferInfo(fd, &info) != windows.FALSE) {
1495 if (windows.kernel32.GetConsoleScreenBufferInfo(file.handle, &info) != windows.FALSE) {
14961496 // In the old Windows console, dwSize.Y is the line count of the
14971497 // entire scrollback buffer, so we use this instead so that we
14981498 // always get the size of the screen.
......@@ -1512,8 +1512,13 @@ fn maybeUpdateSize(resize_flag: bool) void {
15121512 .ypixel = 0,
15131513 };
15141514
1515 const err = posix.system.ioctl(fd, posix.T.IOCGWINSZ, @intFromPtr(&winsize));
1516 if (posix.errno(err) == .SUCCESS) {
1515 const err = (try io.operate(.{ .device_io_control = .{
1516 .file = file,
1517 .code = posix.T.IOCGWINSZ,
1518 .arg = &winsize,
1519 } })).device_io_control;
1520
1521 if (err >= 0) {
15171522 global_progress.rows = winsize.row;
15181523 global_progress.cols = winsize.col;
15191524 } else {
lib/std/c.zig+6-1
......@@ -10731,7 +10731,6 @@ pub extern "c" fn sysctlnametomib(name: [*:0]const u8, mibp: ?*c_int, sizep: ?*u
1073110731pub extern "c" fn tcgetattr(fd: fd_t, termios_p: *termios) c_int;
1073210732pub extern "c" fn tcsetattr(fd: fd_t, optional_action: TCSA, termios_p: *const termios) c_int;
1073310733pub extern "c" fn fcntl(fd: fd_t, cmd: c_int, ...) c_int;
10734pub extern "c" fn ioctl(fd: fd_t, request: c_int, ...) c_int;
1073510734pub extern "c" fn uname(buf: *utsname) c_int;
1073610735
1073710736pub extern "c" fn gethostname(name: [*]u8, len: usize) c_int;
......@@ -11108,6 +11107,11 @@ pub const clock_nanosleep = switch (native_os) {
1110811107 else => {},
1110911108};
1111011109
11110pub const ioctl = switch (native_os) {
11111 .windows, .wasi => {},
11112 else => private.ioctl,
11113};
11114
1111111115// OS-specific bits. These are protected from being used on the wrong OS by
1111211116// comptime assertions inside each OS-specific file.
1111311117
......@@ -11495,6 +11499,7 @@ const private = struct {
1149511499 };
1149611500 extern "c" fn getrusage(who: c_int, usage: *rusage) c_int;
1149711501 extern "c" fn gettimeofday(noalias tv: ?*timeval, noalias tz: ?*timezone) c_int;
11502 extern "c" fn ioctl(fd: fd_t, request: c_int, ...) c_int;
1149811503 extern "c" fn msync(addr: *align(page_size) const anyopaque, len: usize, flags: c_int) c_int;
1149911504 extern "c" fn nanosleep(rqtp: *const timespec, rmtp: ?*timespec) c_int;
1150011505 extern "c" fn clock_nanosleep(clockid: clockid_t, flags: TIMER, t: *const timespec, remain: ?*timespec) c_int;
lib/std/posix.zig-22
......@@ -1800,28 +1800,6 @@ pub fn name_to_handle_atZ(
18001800 }
18011801}
18021802
1803pub const IoCtl_SIOCGIFINDEX_Error = error{
1804 FileSystem,
1805 InterfaceNotFound,
1806} || UnexpectedError;
1807
1808pub fn ioctl_SIOCGIFINDEX(fd: fd_t, ifr: *ifreq) IoCtl_SIOCGIFINDEX_Error!void {
1809 while (true) {
1810 switch (errno(system.ioctl(fd, SIOCGIFINDEX, @intFromPtr(ifr)))) {
1811 .SUCCESS => return,
1812 .INVAL => unreachable, // Bad parameters.
1813 .NOTTY => unreachable,
1814 .NXIO => unreachable,
1815 .BADF => unreachable, // Always a race condition.
1816 .FAULT => unreachable, // Bad pointer parameter.
1817 .INTR => continue,
1818 .IO => return error.FileSystem,
1819 .NODEV => return error.InterfaceNotFound,
1820 else => |err| return unexpectedErrno(err),
1821 }
1822 }
1823}
1824
18251803pub const lfs64_abi = native_os == .linux and builtin.link_libc and (builtin.abi.isGnu() or builtin.abi.isAndroid());
18261804
18271805/// Whether or not `error.Unexpected` will print its value and a stack trace.