master
1//! This file contains thin wrappers around OS-specific APIs, with these
2//! specific goals in mind:
3//! * Convert "errno"-style error codes into Zig errors.
4//! * When null-terminated byte buffers are required, provide APIs which accept
5//! slices as well as APIs which accept null-terminated byte buffers. Same goes
6//! for WTF-16LE encoding.
7//! * Where operating systems share APIs, e.g. POSIX, these thin wrappers provide
8//! cross platform abstracting.
9//! * When there exists a corresponding libc function and linking libc, the libc
10//! implementation is used. Exceptions are made for known buggy areas of libc.
11//! On Linux libc can be side-stepped by using `std.os.linux` directly.
12//! * For Windows, this file represents the API that libc would provide for
13//! Windows. For thin wrappers around Windows-specific APIs, see `std.os.windows`.
14
15const root = @import("root");
16const std = @import("std.zig");
17const builtin = @import("builtin");
18const assert = std.debug.assert;
19const math = std.math;
20const mem = std.mem;
21const elf = std.elf;
22const fs = std.fs;
23const dl = @import("dynamic_library.zig");
24const max_path_bytes = std.fs.max_path_bytes;
25const posix = std.posix;
26const native_os = builtin.os.tag;
27
28pub const linux = @import("os/linux.zig");
29pub const plan9 = @import("os/plan9.zig");
30pub const uefi = @import("os/uefi.zig");
31pub const wasi = @import("os/wasi.zig");
32pub const emscripten = @import("os/emscripten.zig");
33pub const windows = @import("os/windows.zig");
34pub const freebsd = @import("os/freebsd.zig");
35
36test {
37 _ = linux;
38 if (native_os == .uefi) {
39 _ = uefi;
40 }
41 _ = wasi;
42 _ = windows;
43}
44
45/// See also `getenv`. Populated by startup code before main().
46/// TODO this is a footgun because the value will be undefined when using `zig build-lib`.
47/// https://github.com/ziglang/zig/issues/4524
48pub var environ: [][*:0]u8 = undefined;
49
50/// Populated by startup code before main().
51/// Not available on WASI or Windows without libc. See `std.process.argsAlloc`
52/// or `std.process.argsWithAllocator` for a cross-platform alternative.
53pub var argv: [][*:0]u8 = if (builtin.link_libc) undefined else switch (native_os) {
54 .windows => @compileError("argv isn't supported on Windows: use std.process.argsAlloc instead"),
55 .wasi => @compileError("argv isn't supported on WASI: use std.process.argsAlloc instead"),
56 else => undefined,
57};
58
59pub fn isGetFdPathSupportedOnTarget(os: std.Target.Os) bool {
60 return switch (os.tag) {
61 .windows,
62 .driverkit,
63 .ios,
64 .maccatalyst,
65 .macos,
66 .tvos,
67 .visionos,
68 .watchos,
69 .linux,
70 .illumos,
71 .freebsd,
72 .serenity,
73 => true,
74
75 .dragonfly => os.version_range.semver.max.order(.{ .major = 6, .minor = 0, .patch = 0 }) != .lt,
76 .netbsd => os.version_range.semver.max.order(.{ .major = 10, .minor = 0, .patch = 0 }) != .lt,
77 else => false,
78 };
79}
80
81/// Return canonical path of handle `fd`.
82///
83/// This function is very host-specific and is not universally supported by all hosts.
84/// For example, while it generally works on Linux, macOS, FreeBSD or Windows, it is
85/// unsupported on WASI.
86///
87/// * On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
88/// * On other platforms, the result is an opaque sequence of bytes with no particular encoding.
89///
90/// Calling this function is usually a bug.
91pub fn getFdPath(fd: std.posix.fd_t, out_buffer: *[max_path_bytes]u8) std.posix.RealPathError![]u8 {
92 if (!comptime isGetFdPathSupportedOnTarget(builtin.os)) {
93 @compileError("querying for canonical path of a handle is unsupported on this host");
94 }
95 switch (native_os) {
96 .windows => {
97 var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined;
98 const wide_slice = try windows.GetFinalPathNameByHandle(fd, .{}, wide_buf[0..]);
99
100 const end_index = std.unicode.wtf16LeToWtf8(out_buffer, wide_slice);
101 return out_buffer[0..end_index];
102 },
103 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => {
104 // On macOS, we can use F.GETPATH fcntl command to query the OS for
105 // the path to the file descriptor.
106 @memset(out_buffer[0..max_path_bytes], 0);
107 switch (posix.errno(posix.system.fcntl(fd, posix.F.GETPATH, out_buffer))) {
108 .SUCCESS => {},
109 .BADF => return error.FileNotFound,
110 .NOSPC => return error.NameTooLong,
111 .NOENT => return error.FileNotFound,
112 // TODO man pages for fcntl on macOS don't really tell you what
113 // errno values to expect when command is F.GETPATH...
114 else => |err| return posix.unexpectedErrno(err),
115 }
116 const len = mem.indexOfScalar(u8, out_buffer[0..], 0) orelse max_path_bytes;
117 return out_buffer[0..len];
118 },
119 .linux, .serenity => {
120 var procfs_buf: ["/proc/self/fd/-2147483648\x00".len]u8 = undefined;
121 const proc_path = std.fmt.bufPrintSentinel(procfs_buf[0..], "/proc/self/fd/{d}", .{fd}, 0) catch unreachable;
122
123 const target = posix.readlinkZ(proc_path, out_buffer) catch |err| {
124 switch (err) {
125 error.NotLink => unreachable,
126 error.BadPathName => unreachable,
127 error.UnsupportedReparsePointType => unreachable, // Windows-only
128 error.NetworkNotFound => unreachable, // Windows-only
129 else => |e| return e,
130 }
131 };
132 return target;
133 },
134 .illumos => {
135 var procfs_buf: ["/proc/self/path/-2147483648\x00".len]u8 = undefined;
136 const proc_path = std.fmt.bufPrintSentinel(procfs_buf[0..], "/proc/self/path/{d}", .{fd}, 0) catch unreachable;
137
138 const target = posix.readlinkZ(proc_path, out_buffer) catch |err| switch (err) {
139 error.UnsupportedReparsePointType => unreachable,
140 error.NotLink => unreachable,
141 else => |e| return e,
142 };
143 return target;
144 },
145 .freebsd => {
146 var kfile: std.c.kinfo_file = undefined;
147 kfile.structsize = std.c.KINFO_FILE_SIZE;
148 switch (posix.errno(std.c.fcntl(fd, std.c.F.KINFO, @intFromPtr(&kfile)))) {
149 .SUCCESS => {},
150 .BADF => return error.FileNotFound,
151 else => |err| return posix.unexpectedErrno(err),
152 }
153 const len = mem.indexOfScalar(u8, &kfile.path, 0) orelse max_path_bytes;
154 if (len == 0) return error.NameTooLong;
155 const result = out_buffer[0..len];
156 @memcpy(result, kfile.path[0..len]);
157 return result;
158 },
159 .dragonfly => {
160 @memset(out_buffer[0..max_path_bytes], 0);
161 switch (posix.errno(std.c.fcntl(fd, posix.F.GETPATH, out_buffer))) {
162 .SUCCESS => {},
163 .BADF => return error.FileNotFound,
164 .RANGE => return error.NameTooLong,
165 else => |err| return posix.unexpectedErrno(err),
166 }
167 const len = mem.indexOfScalar(u8, out_buffer[0..], 0) orelse max_path_bytes;
168 return out_buffer[0..len];
169 },
170 .netbsd => {
171 @memset(out_buffer[0..max_path_bytes], 0);
172 switch (posix.errno(std.c.fcntl(fd, posix.F.GETPATH, out_buffer))) {
173 .SUCCESS => {},
174 .ACCES => return error.AccessDenied,
175 .BADF => return error.FileNotFound,
176 .NOENT => return error.FileNotFound,
177 .NOMEM => return error.SystemResources,
178 .RANGE => return error.NameTooLong,
179 else => |err| return posix.unexpectedErrno(err),
180 }
181 const len = mem.indexOfScalar(u8, out_buffer[0..], 0) orelse max_path_bytes;
182 return out_buffer[0..len];
183 },
184 else => unreachable, // made unreachable by isGetFdPathSupportedOnTarget above
185 }
186}
187
188pub const FstatError = error{
189 SystemResources,
190 AccessDenied,
191 Unexpected,
192};
193
194pub fn fstat_wasi(fd: posix.fd_t) FstatError!wasi.filestat_t {
195 var stat: wasi.filestat_t = undefined;
196 switch (wasi.fd_filestat_get(fd, &stat)) {
197 .SUCCESS => return stat,
198 .INVAL => unreachable,
199 .BADF => unreachable, // Always a race condition.
200 .NOMEM => return error.SystemResources,
201 .ACCES => return error.AccessDenied,
202 .NOTCAPABLE => return error.AccessDenied,
203 else => |err| return posix.unexpectedErrno(err),
204 }
205}