summaryrefslogtreecommitdiff
path: root/src/luarocks_runtime.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/luarocks_runtime.zig')
-rw-r--r--src/luarocks_runtime.zig103
1 files changed, 98 insertions, 5 deletions
diff --git a/src/luarocks_runtime.zig b/src/luarocks_runtime.zig
index f76a60e..7f2e161 100644
--- a/src/luarocks_runtime.zig
+++ b/src/luarocks_runtime.zig
@@ -29,6 +29,7 @@
//! `<panto-binary> lua` (with the absolute path of the running `panto`
//! binary) into the luarocks config as `variables.LUA`.
+const builtin = @import("builtin");
const std = @import("std");
const Allocator = std.mem.Allocator;
const Io = std.Io;
@@ -70,6 +71,7 @@ pub const BootstrapError = error{
LuarocksInjectionFailed,
LuarocksInstallFailed,
LuarocksSearcherInstallFailed,
+ PantoModuleSigningFailed,
PathConfigFailed,
PantoExecutablePathUnknown,
} || Allocator.Error;
@@ -229,6 +231,98 @@ fn stagePantoModule(
var dir = try Io.Dir.cwd().openDir(io, layout.lib_lua_dir, .{});
defer dir.close(io);
try writeIfDifferent(allocator, io, dir, "panto.so", embedded_panto_so.bytes);
+ try ensurePantoModuleSignature(allocator, layout);
+}
+
+/// Ad-hoc re-sign the staged `panto.so` so macOS will `dlopen` it.
+///
+/// We deliberately spawn `codesign` via `posix_spawn` rather than
+/// `std.process.run`. `std.process.run` (via `Io.Threaded`) implements
+/// spawning as a raw `fork()` + `execve()`. On macOS a `fork()` from a
+/// process that has *any* live secondary thread runs the registered
+/// `pthread_atfork` child handlers in the forked child; system-framework
+/// handlers can dereference per-thread state that no longer exists in the
+/// child and fault (`EXC_BAD_ACCESS`). In a normal `panto` run bootstrap
+/// is still single-threaded here so the fork is safe, but under the
+/// multithreaded Zig test runner (whose pool already has worker threads,
+/// and which installs a debug `SIGSEGV` handler that turns that fault into
+/// an `abort()`) the spawned child dies with `SIGABRT` before `codesign`
+/// ever execs. `posix_spawn` is a single Darwin primitive that does *not*
+/// run userspace `pthread_atfork` handlers — Apple's recommended way to
+/// spawn from a multithreaded process — so it sidesteps the hazard
+/// entirely while behaving identically to the caller. The child's stdio is
+/// redirected to /dev/null (see below); failures surface via the exit
+/// status, which we translate into `error.PantoModuleSigningFailed`.
+fn ensurePantoModuleSignature(
+ allocator: Allocator,
+ layout: panto_home.Layout,
+) !void {
+ if (builtin.os.tag != .macos) return;
+
+ const module_path = try std.fs.path.joinZ(allocator, &.{ layout.lib_lua_dir, "panto.so" });
+ defer allocator.free(module_path);
+
+ const argv = [_:null]?[*:0]const u8{
+ "/usr/bin/codesign",
+ "--force",
+ "--sign",
+ "-",
+ module_path.ptr,
+ null,
+ };
+
+ // Redirect the child's stdio to /dev/null: `codesign` is silent on
+ // success and writes progress/errors to stderr, which we don't want
+ // leaking into panto's own output (and, under the test runner, racing
+ // with the build-server IPC). Failures are surfaced via the exit
+ // status and our own `std.log.err` below.
+ var actions: std.c.posix_spawn_file_actions_t = undefined;
+ if (std.c.posix_spawn_file_actions_init(&actions) != 0) {
+ return error.PantoModuleSigningFailed;
+ }
+ defer _ = std.c.posix_spawn_file_actions_destroy(&actions);
+ const rdonly: c_int = @bitCast(std.c.O{ .ACCMODE = .RDONLY });
+ const wronly: c_int = @bitCast(std.c.O{ .ACCMODE = .WRONLY });
+ _ = std.c.posix_spawn_file_actions_addopen(&actions, std.c.STDIN_FILENO, "/dev/null", rdonly, 0);
+ _ = std.c.posix_spawn_file_actions_addopen(&actions, std.c.STDOUT_FILENO, "/dev/null", wronly, 0);
+ _ = std.c.posix_spawn_file_actions_addopen(&actions, std.c.STDERR_FILENO, "/dev/null", wronly, 0);
+
+ var pid: std.c.pid_t = undefined;
+ const spawn_rc = std.c.posix_spawn(
+ &pid,
+ "/usr/bin/codesign",
+ &actions,
+ null,
+ &argv,
+ @ptrCast(std.c.environ),
+ );
+ if (spawn_rc != 0) {
+ std.log.err(
+ "failed to spawn codesign for staged panto module at {s}: posix_spawn errno={d}",
+ .{ module_path, spawn_rc },
+ );
+ return error.PantoModuleSigningFailed;
+ }
+
+ const status = blk: while (true) {
+ var status: c_int = undefined;
+ const rc = std.c.waitpid(pid, &status, 0);
+ if (rc == -1) {
+ const err = std.posix.errno(rc);
+ if (err == .INTR) continue;
+ std.log.err("failed to wait for codesign: errno={t}", .{err});
+ return error.PantoModuleSigningFailed;
+ }
+ break :blk @as(u32, @bitCast(status));
+ };
+
+ if (std.c.W.IFEXITED(status) and std.c.W.EXITSTATUS(status) == 0) return;
+
+ std.log.err(
+ "codesign failed for staged panto module at {s}: status=0x{x}",
+ .{ module_path, status },
+ );
+ return error.PantoModuleSigningFailed;
}
// ---------------------------------------------------------------------------
@@ -338,14 +432,13 @@ const default_base_config =
\\# exchange_expires_path = "expires_at"
\\# exchange_base_url_path = "endpoints.api"
\\
- \\# OpenAI Codex via a ChatGPT subscription (device login). Codex speaks the
- \\# OpenAI Responses API, not Chat Completions, so style = "openai_responses".
- \\# NOTE: this path is implemented but not yet verified against a live
- \\# ChatGPT plan — see docs/oauth-provider-auth.md.
+ \\# OpenAI Codex via a ChatGPT subscription (device login). Codex uses the
+ \\# Responses protocol with a stricter ChatGPT-subscription dialect.
\\#
\\# [providers.codex]
\\# style = "openai_responses"
- \\# base_url = "https://chatgpt.com/backend-api/codex"
+ \\# dialect = "codex"
+ \\# base_url = "https://chatgpt.com/backend-api"
\\# auth = "openai_codex"
\\#
\\# [providers.codex.extra_headers]