# 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 std.os.freebsd // FreeBSD-specific definitions std.os.environ // Environment variables (populated at startup) std.os.argv // Command line arguments (POSIX only) ``` **Note**: For most use cases, prefer `std.posix` (cross-platform POSIX-like APIs) or `std.fs`/`std.process` (high-level abstractions). Use `std.os` when you need direct OS-specific functionality. ## Platform Submodules ### When to Use Each Level ```zig // High-level (recommended for most code) const file = try std.fs.cwd().openFile("data.txt", .{}); // 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, fd, 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.E.init(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 addr = linux.mmap( null, length, linux.PROT.READ | linux.PROT.WRITE, .{ .TYPE = .PRIVATE, .ANONYMOUS = true }, -1, 0, ); if (addr == linux.MAP_FAILED) { // handle error } // 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, .{ .op = .WAIT, .PRIVATE = true }, expected_value, .{ .timeout = &timeout }, null, 0, ); // Wake waiters _ = linux.futex( &futex_word, .{ .op = .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 = .{}, }; _ = linux.sigaction(linux.SIG.INT, &act, null); // 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 n = linux.epoll_wait(epfd, &events, -1); 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 const windows = std.os.windows; // Open file with NT API const handle = try windows.OpenFile(path_utf16, .{ .access_mask = windows.GENERIC_READ | windows.GENERIC_WRITE, .creation = windows.FILE_OPEN, .share_access = windows.FILE_SHARE_READ, .filter = .file_only, .follow_symlinks = true, }); defer windows.CloseHandle(handle); ``` ### 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 ```zig const windows = std.os.windows; var read_handle: windows.HANDLE = undefined; var write_handle: windows.HANDLE = undefined; var sa: windows.SECURITY_ATTRIBUTES = .{ .nLength = @sizeOf(windows.SECURITY_ATTRIBUTES), .lpSecurityDescriptor = null, .bInheritHandle = windows.TRUE, }; try windows.CreatePipe(&read_handle, &write_handle, &sa); ``` ### Submodules ```zig windows.kernel32 // kernel32.dll functions windows.ntdll // ntdll.dll functions (NT native API) windows.advapi32 // advapi32.dll (security, registry) 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 while (ring.cq_ready() > 0) { const cqe = ring.peek_cqe() orelse break; const user_data = cqe.user_data; const result = cqe.res; // bytes transferred or -errno if (result < 0) { const err = std.os.linux.E.init(@intCast(-result)); // handle error } ring.cq_advance(1); // mark CQE as consumed } ``` ### 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, buf, offset, buf_index); sqe.prep_write_fixed(fd, data, 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, &buffers[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 ### getFdPath Get canonical path from file descriptor (not all platforms). ```zig var buf: [std.fs.max_path_bytes]u8 = undefined; const path = try std.os.getFdPath(fd, &buf); std.debug.print("Path: {s}\n", .{path}); // Check if supported at comptime if (comptime std.os.isGetFdPathSupportedOnTarget(builtin.os)) { // safe to call } ``` **Supported**: Linux, macOS, FreeBSD, Windows, Solaris/illumos, DragonFly (6.0+), NetBSD (10.0+) ### accessW (Windows) Check file accessibility with WTF-16LE path. ```zig const path_w = std.unicode.utf8ToUtf16LeStringLiteral("C:\\file.txt"); std.os.accessW(path_w) catch |err| switch (err) { error.FileNotFound => {}, error.AccessDenied => {}, else => return err, }; ``` ### WASI stat functions ```zig // stat by path const stat = try std.os.fstatat_wasi(dirfd, path, .{ .SYMLINK_FOLLOW = true }); // stat by fd const stat = try std.os.fstat_wasi(fd); stat.size; // file size stat.filetype; // .REGULAR_FILE, .DIRECTORY, .SYMBOLIC_LINK, etc. stat.atim; // access time (nanoseconds) stat.mtim; // modification time stat.ctim; // status change time ``` ## 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); // Process all available completions while (ring.cq_ready() > 0) { const cqe = ring.peek_cqe() orelse break; defer ring.cq_advance(1); 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.E.init(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 windowsOperation() !void { const result = windows.kernel32.SomeFunction(...); if (result == windows.FALSE) { switch (windows.GetLastError()) { .ERROR_FILE_NOT_FOUND => return error.FileNotFound, .ERROR_ACCESS_DENIED => return error.AccessDenied, else => |e| return windows.unexpectedError(e), } } } ``` ### Cross-Platform File Descriptor Path ```zig fn getFilePath(fd: std.posix.fd_t, allocator: Allocator) ![]u8 { if (comptime !std.os.isGetFdPathSupportedOnTarget(builtin.os)) { return error.Unsupported; } var buf: [std.fs.max_path_bytes]u8 = undefined; const path = try std.os.getFdPath(fd, &buf); return try allocator.dupe(u8, path); } ```