# std.os - OS-Specific APIs Reference (Zig 0.16.0) Thin wrappers around OS-specific APIs. Zig 0.16 moves many blocking/nondeterministic operations behind `std.Io`; prefer `std.Io` abstractions for portable code and drop down to `std.posix` / `std.os.windows` only for explicitly platform-specific code. Primary Zig 0.16 release-note source: https://ziglang.org/download/0.16.0/release-notes.html ## Table of Contents - [Module Structure](#module-structure) - [Platform Submodules](#platform-submodules) - [Linux-Specific APIs](#linux-specific-apis) - [Windows-Specific APIs](#windows-specific-apis) - [WASI-Specific APIs](#wasi-specific-apis) - [io_uring (Linux)](#io_uring-linux) - [Common Functions](#common-functions) - [Common Patterns](#common-patterns) ## Module Structure ```zig std.os.linux // Linux syscalls and constants std.os.windows // Windows NT APIs std.os.wasi // WebAssembly System Interface std.os.plan9 // Plan 9 system calls std.os.uefi // UEFI firmware interface std.os.emscripten // Emscripten runtime ``` Those six target modules are the complete public surface of `std.os` in Zig 0.16. For most use cases, prefer `std.Io` (portable, capability-based I/O) or `std.posix` (cross-platform POSIX-like APIs). Process startup arguments and environment data are supplied through `std.process.Init`, not `std.os.argv` or `std.os.environ`. ## Platform Submodules ### When to Use Each Level ```zig // High-level (recommended for most code) const file = try std.Io.Dir.cwd().openFile(io, "data.txt", .{}); defer file.close(io); // POSIX-level (cross-platform low-level) const fd = try std.posix.open("data.txt", .{}, 0); // OS-specific (platform-specific features) const result = std.os.linux.syscall3(.read, @intCast(fd), @intFromPtr(buf.ptr), buf.len); ``` ## Linux-Specific APIs ### Direct Syscalls ```zig const linux = std.os.linux; // Raw syscall interface const result = linux.syscall3(.write, fd, @intFromPtr(buf.ptr), buf.len); if (linux.errno(result) != .SUCCESS) { // handle error } // Common syscalls with typed wrappers _ = linux.dup(old_fd); _ = linux.dup2(old_fd, new_fd); _ = linux.fork(); _ = linux.execve(path, argv, envp); _ = linux.chdir(path); _ = linux.chroot(path); ``` ### Memory Mapping ```zig const linux = std.os.linux; // mmap with typed flags const result = linux.mmap( null, length, linux.PROT.READ | linux.PROT.WRITE, .{ .TYPE = .PRIVATE, .ANONYMOUS = true }, -1, 0, ); const addr: [*]u8 = switch (linux.errno(result)) { .SUCCESS => @ptrFromInt(result), else => |err| return std.posix.unexpectedErrno(err), }; // Remap _ = linux.mremap(old_addr, old_size, new_size, .{ .MAYMOVE = true }, null); // Unmap _ = linux.munmap(addr, length); ``` ### File Operations ```zig const linux = std.os.linux; // Open flags (architecture-specific packed struct) const flags: linux.O = .{ .ACCMODE = .RDWR, .CREAT = true, .TRUNC = true, .CLOEXEC = true, }; // fallocate - preallocate file space _ = linux.fallocate(fd, 0, 0, size); // utimensat - set file timestamps _ = linux.utimensat(dirfd, path, ×, 0); ``` ### Futex (Fast Userspace Mutex) ```zig const linux = std.os.linux; // Wait on futex _ = linux.futex( &futex_word, .{ .cmd = .WAIT, .private = true }, expected_value, .{ .timeout = &timeout }, null, 0, ); // Wake waiters _ = linux.futex( &futex_word, .{ .cmd = .WAKE, .private = true }, num_to_wake, .{ .val2 = 0 }, null, 0, ); ``` ### Signals ```zig const linux = std.os.linux; // Signal handling var act: linux.Sigaction = .{ .handler = .{ .handler = signal_handler }, .mask = linux.empty_sigset, .flags = .{}, }; const result = linux.sigaction(.INT, &act, null); if (linux.errno(result) != .SUCCESS) { // translate or handle the raw errno } // Kill process _ = linux.kill(pid, linux.SIG.TERM); ``` ### Epoll ```zig const linux = std.os.linux; // Create epoll instance const epfd = linux.epoll_create1(.{ .CLOEXEC = true }); // Add file descriptor var event: linux.epoll_event = .{ .events = linux.EPOLL.IN | linux.EPOLL.ET, .data = .{ .fd = client_fd }, }; _ = linux.epoll_ctl(epfd, .ADD, client_fd, &event); // Wait for events var events: [64]linux.epoll_event = undefined; const result = linux.epoll_wait(epfd, &events, @intCast(events.len), -1); const n: usize = switch (linux.errno(result)) { .SUCCESS => result, else => |err| return std.posix.unexpectedErrno(err), }; for (events[0..n]) |ev| { // handle event } ``` ### getauxval ```zig const linux = std.os.linux; // Get auxiliary vector values (set by kernel at process start) const page_size = linux.getauxval(std.elf.AT_PAGESZ); const entry_point = linux.getauxval(std.elf.AT_ENTRY); const platform = linux.getauxval(std.elf.AT_PLATFORM); ``` ## Windows-Specific APIs ### File Operations ```zig // Prefer the portable std.Io layer even in Windows-only programs. const file = try std.Io.Dir.cwd().openFile(io, "data.txt", .{}); defer file.close(io); ``` For Win32 or NT features that `std.Io` does not expose, use declarations actually exported by `std.os.windows` and its `kernel32`/`ntdll` submodules. There is no public `std.os.windows.OpenFile` helper in Zig 0.16. ### Process Information ```zig const windows = std.os.windows; // Current process/thread const process = windows.GetCurrentProcess(); const pid = windows.GetCurrentProcessId(); const thread = windows.GetCurrentThread(); const tid = windows.GetCurrentThreadId(); // Last error const err = windows.GetLastError(); ``` ### Pipes Use the `std.Io` pipe/process APIs for portable pipes. If raw Windows handles are required, call a declaration that is actually exported from the relevant Windows submodule and translate its Win32 error explicitly; Zig 0.16 does not expose a fallible `std.os.windows.CreatePipe` wrapper with the signature shown in older examples. ### Submodules ```zig windows.kernel32 // kernel32.dll functions windows.ntdll // ntdll.dll functions (NT native API) windows.ws2_32 // Winsock 2 networking windows.crypt32 // Cryptographic functions windows.nls // National Language Support ``` ## WASI-Specific APIs ### File Descriptors ```zig const wasi = std.os.wasi; // Read/write var nread: usize = undefined; switch (wasi.fd_read(fd, &iovs, iovs.len, &nread)) { .SUCCESS => {}, .BADF => return error.BadFileDescriptor, else => |e| return unexpectedErrno(e), } // Seek var new_offset: wasi.filesize_t = undefined; _ = wasi.fd_seek(fd, offset, .SET, &new_offset); // Sync _ = wasi.fd_sync(fd); _ = wasi.fd_datasync(fd); ``` ### Path Operations ```zig const wasi = std.os.wasi; // Create directory _ = wasi.path_create_directory(dirfd, path.ptr, path.len); // Open file var result_fd: wasi.fd_t = undefined; _ = wasi.path_open( dirfd, .{ .SYMLINK_FOLLOW = true }, path.ptr, path.len, .{ .CREAT = true }, rights_base, rights_inheriting, .{}, &result_fd, ); // Symlinks _ = wasi.path_symlink(old_path.ptr, old_path.len, dirfd, new_path.ptr, new_path.len); _ = wasi.path_readlink(dirfd, path.ptr, path.len, buf.ptr, buf.len, &bufused); ``` ### Clock ```zig const wasi = std.os.wasi; var timestamp: wasi.timestamp_t = undefined; switch (wasi.clock_time_get(.MONOTONIC, 1, ×tamp)) { .SUCCESS => {}, else => |e| return error.ClockGetFailed, } ``` ### Environment and Arguments ```zig const wasi = std.os.wasi; // Arguments var argc: usize = undefined; var argv_buf_size: usize = undefined; _ = wasi.args_sizes_get(&argc, &argv_buf_size); // Environment var environ_count: usize = undefined; var environ_buf_size: usize = undefined; _ = wasi.environ_sizes_get(&environ_count, &environ_buf_size); ``` ### Random ```zig const wasi = std.os.wasi; var buf: [32]u8 = undefined; switch (wasi.random_get(&buf, buf.len)) { .SUCCESS => {}, else => return error.RandomFailed, } ``` ## io_uring (Linux) High-performance async I/O for Linux 5.4+. ### Basic Setup ```zig const IoUring = std.os.linux.IoUring; // Initialize with 256 entries var ring = try IoUring.init(256, 0); defer ring.deinit(); // With custom parameters var params = std.mem.zeroInit(std.os.linux.io_uring_params, .{ .flags = std.os.linux.IORING_SETUP_SQPOLL, // kernel-side submission .sq_thread_idle = 2000, // ms before SQ thread sleeps }); var ring = try IoUring.init_params(256, ¶ms); ``` ### Submitting Operations ```zig // Get submission queue entry const sqe = try ring.get_sqe(); // Prepare read operation sqe.prep_read(fd, buffer, offset); sqe.user_data = my_context; // identify this request in completion // Or write sqe.prep_write(fd, data, offset); // Submit to kernel const submitted = try ring.submit(); ``` ### Waiting for Completions ```zig // Submit and wait for at least 1 completion _ = try ring.submit_and_wait(1); // Process completions var cqes: [32]std.os.linux.io_uring_cqe = undefined; const ready = try ring.copy_cqes(&cqes, 1); for (cqes[0..ready]) |cqe| { const user_data = cqe.user_data; const result = cqe.res; // bytes transferred or -errno if (result < 0) { const err: std.os.linux.E = @enumFromInt(@as(u16, @intCast(-result))); // handle error } } ``` `copy_cqes` copies and consumes completions as a batch. `copy_cqe` is the corresponding wait-for-one convenience method. Do not additionally call `cqe_seen` or `cq_advance` after either copying API. ### Common Operations ```zig // File I/O sqe.prep_read(fd, buf, offset); sqe.prep_write(fd, data, offset); sqe.prep_readv(fd, iovecs, offset); sqe.prep_writev(fd, iovecs, offset); // Fixed buffers (pre-registered, zero-copy) sqe.prep_read_fixed(fd, registered_iovec, offset, buf_index); sqe.prep_write_fixed(fd, registered_iovec, offset, buf_index); // Network sqe.prep_accept(listen_fd, &client_addr, &addr_len, 0); sqe.prep_connect(fd, &addr, addr_len); sqe.prep_recv(fd, buf, 0); sqe.prep_send(fd, data, 0); // Timeouts sqe.prep_timeout(×pec, 0, 0); sqe.prep_link_timeout(×pec, 0); // timeout linked op // File operations sqe.prep_openat(dirfd, path, flags, mode); sqe.prep_close(fd); sqe.prep_statx(dirfd, path, flags, mask, &statx); // Misc sqe.prep_nop(); // no-op (for benchmarking) sqe.prep_cancel(user_data, 0); // cancel pending request ``` ### Linked Operations ```zig // Chain operations: second runs only if first succeeds const sqe1 = try ring.get_sqe(); sqe1.prep_write(fd, header, 0); sqe1.flags |= std.os.linux.IOSQE_IO_LINK; const sqe2 = try ring.get_sqe(); sqe2.prep_write(fd, body, header.len); _ = try ring.submit(); ``` ### Buffer Registration ```zig // Register buffers for zero-copy I/O var buffers: [16][4096]u8 = undefined; var iovecs: [16]std.posix.iovec = undefined; for (&iovecs, &buffers) |*iov, *buf| { iov.* = .{ .base = buf, .len = buf.len }; } try ring.register_buffers(&iovecs); defer ring.unregister_buffers() catch {}; // Use registered buffer const sqe = try ring.get_sqe(); sqe.prep_read_fixed(fd, &iovecs[0], 0, 0); // buf_index = 0 ``` ### File Descriptor Registration ```zig // Register FDs for faster access var fds = [_]std.posix.fd_t{ fd1, fd2, fd3 }; try ring.register_files(&fds); defer ring.unregister_files() catch {}; // Use with IOSQE_FIXED_FILE flag const sqe = try ring.get_sqe(); sqe.prep_read(0, buf, 0); // fd index, not actual fd sqe.flags |= std.os.linux.IOSQE_FIXED_FILE; ``` ## Common Functions Zig 0.16 deliberately has no cross-platform function layer at the `std.os` root. Older references may mention root functions such as `std.os.getFdPath`, `std.os.accessW`, `std.os.fstatat_wasi`, or `std.os.fstat_wasi`; those are not public Zig 0.16 APIs. Choose the API by intent: - Use `std.Io.Dir` and `std.Io.File` for portable path, access, and metadata operations. - Use `std.posix` for POSIX-like file-descriptor operations. - Use `std.os.windows`, `std.os.wasi`, or another exported target module when the behavior is intentionally ABI-specific. - Preserve the path separately when an application needs to associate a portable path with an open file; a descriptor does not portably retain a recoverable canonical pathname. ## Common Patterns ### Platform-Specific Code ```zig const builtin = @import("builtin"); fn platformSpecific() !void { switch (builtin.os.tag) { .linux => { const linux = std.os.linux; // Linux-specific code }, .windows => { const windows = std.os.windows; // Windows-specific code }, .wasi => { const wasi = std.os.wasi; // WASI-specific code }, else => @compileError("Unsupported OS"), } } ``` ### io_uring Event Loop ```zig fn eventLoop(ring: *std.os.linux.IoUring) !void { while (running) { // Submit pending and wait for completions _ = try ring.submit_and_wait(1); // Copying also advances the completion queue. var cqes: [64]std.os.linux.io_uring_cqe = undefined; const count = try ring.copy_cqes(&cqes, 1); for (cqes[0..count]) |cqe| { const ctx = @as(*Context, @ptrFromInt(cqe.user_data)); try ctx.handle_completion(cqe.res); } } } ``` ### Handling Syscall Errors ```zig const linux = std.os.linux; fn readSyscall(fd: i32, buf: []u8) !usize { const result = linux.syscall3(.read, @intCast(fd), @intFromPtr(buf.ptr), buf.len); switch (linux.errno(result)) { .SUCCESS => return result, .INTR => return error.Interrupted, .AGAIN => return error.WouldBlock, .BADF => return error.BadFileDescriptor, .FAULT => return error.BadAddress, .INVAL => return error.InvalidArgument, .IO => return error.InputOutput, .ISDIR => return error.IsDir, else => |e| return std.posix.unexpectedErrno(e), } } ``` ### Windows Error Handling ```zig const windows = std.os.windows; fn translateLastError() !void { // Call this immediately after a Win32 API reports failure; another Win32 // call may overwrite the thread's last-error value. switch (windows.GetLastError()) { .FILE_NOT_FOUND => return error.FileNotFound, .ACCESS_DENIED => return error.AccessDenied, else => |e| return windows.unexpectedError(e), } } ``` ### Retaining a Portable File Path If later logic needs both a file and its pathname, store an owned copy of the pathname when opening the file. Trying to reconstruct a canonical path from a descriptor is target-specific and can be ambiguous after rename, unlink, mount, or namespace changes.