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}