//! 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;