diff options
| author | T <t@tjp.lol> | 2026-05-27 08:04:04 -0600 |
|---|---|---|
| committer | T <t@tjp.lol> | 2026-05-27 11:46:52 -0600 |
| commit | 576891dc2ec4d917932a4c396471d4bbbad90c8e (patch) | |
| tree | 0662d629cf15a2e9cbb51353f6d3abe6d2c6edb5 /src/self_exe.zig | |
| parent | b72a405534d6be019573ee0a806014e2713fe55e (diff) | |
Finish the hard parts of the lua makeover
- bundle luarocks source in the panto binary
- bootstrap process (intended for first `panto` run):
- make ~/.local/share/panto/...
- write out luarocks sources into it
- run luarocks to install luv
- new `panto bootstrap` command just runs the bootstrap
- `panto bootstrap --force` removes everything and re-bootstraps
- new `panto lua` command just runs panto's embedded lua
Diffstat (limited to 'src/self_exe.zig')
| -rw-r--r-- | src/self_exe.zig | 90 |
1 files changed, 90 insertions, 0 deletions
diff --git a/src/self_exe.zig b/src/self_exe.zig new file mode 100644 index 0000000..6eda92d --- /dev/null +++ b/src/self_exe.zig @@ -0,0 +1,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; |
