summaryrefslogtreecommitdiff
path: root/src/self_exe.zig
diff options
context:
space:
mode:
authorT <t@tjp.lol>2026-05-27 08:04:04 -0600
committerT <t@tjp.lol>2026-05-27 11:46:52 -0600
commit576891dc2ec4d917932a4c396471d4bbbad90c8e (patch)
tree0662d629cf15a2e9cbb51353f6d3abe6d2c6edb5 /src/self_exe.zig
parentb72a405534d6be019573ee0a806014e2713fe55e (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.zig90
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;