fix process and global error handling

Restore the error handling that the removed std.posix fork and waitpid
wrappers previously provided. Raw fork failures now propagate, waitpid
retries interruptions before reading status, and edit-config constructs the
sentinel-terminated argv required by execve.

Let global initialization own cleanup through its existing errdefer so
temporary paths are freed once. Report initialization failures with the
static synchronous I/O provider because global I/O has already been torn
down by that point.
This commit is contained in:
Mitchell Hashimoto
2026-07-22 06:32:37 -07:00
parent 7121ab6c3f
commit a77c706a18
4 changed files with 49 additions and 13 deletions

View File

@@ -189,7 +189,7 @@ fn startPosix(self: *Command, arena: Allocator) !void {
@compileError("missing env vars");
// Fork.
const pid = posix.system.fork();
const pid = try fork();
if (pid != 0) {
// Parent, return immediately.
@@ -291,6 +291,17 @@ fn startPosix(self: *Command, arena: Allocator) !void {
return error.ExecFailedInChild;
}
/// Wrapper for the raw fork syscall. This preserves the error handling from
/// the std.posix wrapper that was removed in Zig 0.16.
fn fork() !posix.pid_t {
const rc = posix.system.fork();
switch (posix.errno(rc)) {
.SUCCESS => return @intCast(rc),
.AGAIN, .NOMEM => return error.SystemResources,
else => |err| return posix.unexpectedErrno(err),
}
}
fn startWindows(self: *Command, arena: Allocator) !void {
const cwd_w = if (self.cwd) |cwd| try std.unicode.utf8ToUtf16LeAllocZ(arena, cwd) else null;
@@ -494,10 +505,10 @@ pub fn wait(self: Command, block: bool) !Exit {
return .{ .Exited = exit_code };
}
const status = if (block) wait_block: {
var status: c_int = undefined;
_ = posix.system.waitpid(self.pid.?, &status, 0);
break :wait_block status;
const status: u32 = if (block) wait_block: {
var status: if (builtin.link_libc) c_int else u32 = undefined;
_ = try waitPid(self.pid.?, &status, 0);
break :wait_block @bitCast(status);
} else wait_nohang: {
// We specify NOHANG because its not our fault if the process we launch
// for the tty doesn't properly waitpid its children. We don't want
@@ -507,13 +518,32 @@ pub fn wait(self: Command, block: bool) !Exit {
// wait call has not been performed, so we need to keep trying until we get
// a non-zero pid back, otherwise we end up with zombie processes.
while (true) {
var status: c_int = undefined;
const pid = posix.system.waitpid(self.pid.?, &status, posix.system.W.NOHANG);
if (pid != 0) break :wait_nohang status;
var status: if (builtin.link_libc) c_int else u32 = undefined;
const pid = try waitPid(self.pid.?, &status, posix.system.W.NOHANG);
if (pid != 0) break :wait_nohang @bitCast(status);
}
};
return .init(@bitCast(status));
return .init(status);
}
/// Wrapper for the raw waitpid syscall. Status is only initialized on success;
/// interrupted waits are retried and all other errors are propagated.
fn waitPid(
pid: posix.pid_t,
status: *if (builtin.link_libc) c_int else u32,
flags: u32,
) !posix.pid_t {
while (true) {
const rc = posix.system.waitpid(pid, status, @intCast(flags));
switch (posix.errno(rc)) {
.SUCCESS => return @intCast(rc),
.INTR => continue,
.CHILD => return error.NoChildProcess,
.INVAL => return error.InvalidWaitOptions,
else => |err| return posix.unexpectedErrno(err),
}
}
}
/// Sets command->data to data.

View File

@@ -158,9 +158,13 @@ fn runInner(alloc: Allocator, stderr: *std.Io.Writer) !u8 {
defer alloc.free(command);
// Run/replace process (using execve)
const argv = &.{ "/bin/sh", "-c", command };
const argv = [_:null]?[*:0]const u8{
"/bin/sh",
"-c",
command.ptr,
};
const envp = std.c.environ;
const err = std.posix.errno(std.posix.system.execve(argv[0], @ptrCast(argv), envp));
const err = std.posix.errno(std.posix.system.execve(argv[0].?, &argv, envp));
// If we reached this point then exec failed.
try stderr.print(

View File

@@ -121,7 +121,6 @@ pub fn init(opts: InitOpts) !void {
// Discover and save the temporary directory path
self.tmp_dir_path = try allocTmpDir(self.alloc, self.environ);
errdefer freeTmpDir(self.alloc, self.tmp_dir_path.?);
// We first try to parse any action that we may be executing.
// Tool binaries (ghostty-bench, ghostty-gen) have their own action

View File

@@ -30,7 +30,10 @@ pub fn main(minimal: std.process.Init.Minimal) !MainReturn {
// no other Zig code should EVER access the global state.
global.init(.{ .main = minimal }) catch |err| {
var buffer: [1024]u8 = undefined;
var stderr_writer = std.Io.File.stderr().writer(global.io(), &buffer);
var stderr_writer = std.Io.File.stderr().writer(
std.Io.Threaded.global_single_threaded.io(),
&buffer,
);
const stderr = &stderr_writer.interface;
defer std.process.exit(1);
const ErrSet = @TypeOf(err) || error{Unknown};