zig-skills/references/std-os.md

566 lines
14 KiB
Markdown

# 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, &times, 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, &timestamp)) {
.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, &params);
```
### 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(&timespec, 0, 0);
sqe.prep_link_timeout(&timespec, 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.