summaryrefslogtreecommitdiff
path: root/src/self_exe.zig
blob: 6eda92d4bab39aabd6e57d059e831c939f67585e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
//! Resolve the absolute path of the currently running executable.
//!
//! Used to find the panto binary so we can launch ourselves as `panto
//! lua` from inside the bootstrap (the embedded luarocks shells out to
//! its configured `LUA` interpreter, which we point at a wrapper script
//! that exec's panto's `lua` subcommand).
//!
//! Zig's standard library doesn't expose a portable `selfExePath` in
//! the current API, so this module wraps the per-OS syscall.
//!
//! Supported:
//!   - macOS / iOS / tvOS / watchOS: `_NSGetExecutablePath` + realpath
//!   - Linux: `readlink("/proc/self/exe")`
//!   - FreeBSD / NetBSD / DragonFly: `readlink("/proc/curproc/file")`
//!
//! Other targets currently return `error.SelfExePathUnavailable`.

const std = @import("std");
const builtin = @import("builtin");

const Allocator = std.mem.Allocator;

pub const ResolveError = error{
    SelfExePathUnavailable,
    SelfExePathTooLong,
    SelfExePathReadFailed,
    OutOfMemory,
};

/// Return the absolute, symlink-resolved path of the current process's
/// executable. Caller owns the returned slice.
pub fn selfExePathAlloc(allocator: Allocator) ResolveError![]u8 {
    var buf: [std.fs.max_path_bytes]u8 = undefined;
    const path = try selfExePath(&buf);
    return allocator.dupe(u8, path);
}

/// Fill `buf` with the absolute path. Returns the slice that was used.
pub fn selfExePath(buf: []u8) ResolveError![]u8 {
    return switch (builtin.os.tag) {
        .macos, .ios, .tvos, .watchos => try macosSelfExePath(buf),
        .linux => try readLinkSelfExe(buf, "/proc/self/exe"),
        .freebsd, .netbsd, .dragonfly => try readLinkSelfExe(buf, "/proc/curproc/file"),
        else => return ResolveError.SelfExePathUnavailable,
    };
}

fn readLinkSelfExe(buf: []u8, link_path: []const u8) ResolveError![]u8 {
    const link_z = std.posix.toPosixPath(link_path) catch return ResolveError.SelfExePathTooLong;
    const n = posixReadlink(&link_z, buf) catch return ResolveError.SelfExePathReadFailed;
    return buf[0..n];
}

/// Thin wrapper around the POSIX `readlink` syscall using libc directly,
/// since std.posix's surface here has been in flux. libc is linked into
/// panto already (Lua needs it), so this stays cheap.
extern "c" fn readlink(path: [*:0]const u8, buf: [*]u8, bufsize: usize) isize;

fn posixReadlink(path: [*:0]const u8, buf: []u8) !usize {
    const n = readlink(path, buf.ptr, buf.len);
    if (n < 0) return error.SelfExePathReadFailed;
    return @intCast(n);
}

extern "c" fn _NSGetExecutablePath(buf: [*]u8, bufsize: *u32) c_int;

fn macosSelfExePath(buf: []u8) ResolveError![]u8 {
    var stage: [std.fs.max_path_bytes]u8 = undefined;
    var size: u32 = @intCast(stage.len);
    if (_NSGetExecutablePath(&stage, &size) != 0) return ResolveError.SelfExePathTooLong;
    // _NSGetExecutablePath populates `size` on overflow; on success
    // we need to find the NUL ourselves.
    const len = std.mem.indexOfScalar(u8, &stage, 0) orelse return ResolveError.SelfExePathTooLong;

    // The returned path may include `..` and symlinks; canonicalize so
    // downstream consumers don't have to. realpath(3) on POSIX, both
    // libc-resident on macOS and Linux.
    const path_z = std.posix.toPosixPath(stage[0..len]) catch return ResolveError.SelfExePathTooLong;

    // libc realpath: NULL second arg => malloc; we want stack buffer.
    var real_buf: [std.fs.max_path_bytes]u8 = undefined;
    if (libc_realpath(&path_z, &real_buf) == null) return ResolveError.SelfExePathReadFailed;
    const real_len = std.mem.indexOfScalar(u8, &real_buf, 0) orelse return ResolveError.SelfExePathTooLong;
    if (real_len > buf.len) return ResolveError.SelfExePathTooLong;
    @memcpy(buf[0..real_len], real_buf[0..real_len]);
    return buf[0..real_len];
}

extern "c" fn realpath(path: [*:0]const u8, resolved: [*]u8) ?[*]u8;
const libc_realpath = realpath;