From c6b75b61b7dc914c46de86aa764958f6954d28a4 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 2 Jan 2026 20:19:32 -0800 Subject: [PATCH] std: fix child processes on riscv32-linux --- lib/std/Io/Threaded.zig | 92 +++++++++++++++++++++++++++++++++-------- lib/std/os/linux.zig | 21 +++++++++- lib/std/posix.zig | 41 ------------------ 3 files changed, 94 insertions(+), 60 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index e9190f590b0ba1d514b1a9fa9c768a0efb0dffba..a515766e6ddf93846acc538aebe25709c3624494 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -1638,8 +1638,14 @@ const have_fchmod = switch (native_os) { else => true, }; +const have_waitid = switch (native_os) { + .linux => @hasField(std.os.linux.SYS, "waitid"), + else => false, +}; + const have_wait4 = switch (native_os) { - .dragonfly, .freebsd, .netbsd, .openbsd, .illumos, .linux, .serenity, .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => true, + .linux => @hasField(std.os.linux.SYS, "wait4"), + .dragonfly, .freebsd, .netbsd, .openbsd, .illumos, .serenity, .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => true, else => false, }; @@ -13019,7 +13025,7 @@ fn childWait(userdata: ?*anyopaque, child: *std.process.Child) process.Child.Wai const t: *Threaded = @ptrCast(@alignCast(userdata)); switch (native_os) { .windows => return childWaitWindows(t, child), - else => return childWaitPosix(t, child), + else => return childWaitPosix(Thread.getCurrent(t), child), } } @@ -13029,7 +13035,7 @@ fn childKill(userdata: ?*anyopaque, child: *std.process.Child) void { if (is_windows) { childKillWindows(t, child, 1) catch childCleanupWindows(child); } else { - childKillPosix(t, child) catch childCleanupPosix(child); + childKillPosix(Thread.getCurrent(t), child) catch childCleanupPosix(child); } } @@ -13109,20 +13115,71 @@ fn childCleanupWindows(child: *process.Child) void { } } -fn childWaitPosix(t: *Threaded, child: *process.Child) process.Child.WaitError!process.Child.Term { - _ = t; // TODO cancelation +fn childWaitPosix(current_thread: *Thread, child: *process.Child) process.Child.WaitError!process.Child.Term { + defer childCleanupPosix(child); + const pid = child.id.?; - const res: posix.WaitPidResult = res: { - if (child.request_resource_usage_statistics and have_wait4) { - var ru: posix.rusage = undefined; - const res = posix.wait4(pid, 0, &ru); - child.resource_usage_statistics.rusage = ru; - break :res res; - } - break :res posix.waitpid(pid, 0); + + var ru: posix.rusage = undefined; + const ru_ptr = if (child.request_resource_usage_statistics) &ru else null; + + if (have_wait4) { + var status: if (builtin.link_libc) c_int else u32 = undefined; + try current_thread.beginSyscall(); + while (true) switch (posix.errno(posix.system.wait4(pid, &status, 0, ru_ptr))) { + .SUCCESS => { + current_thread.endSyscall(); + if (ru_ptr) |p| child.resource_usage_statistics.rusage = p.*; + return statusToTerm(@bitCast(status)); + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + .CHILD => |err| return current_thread.endSyscallErrnoBug(err), // Double-free. + else => |err| return current_thread.endSyscallUnexpectedErrno(err), + }; + } + + if (have_waitid) { + const linux = std.os.linux; // Bypass libc which has the wrong signature. + var info: linux.siginfo_t = undefined; + try current_thread.beginSyscall(); + while (true) switch (linux.errno(linux.waitid(.PID, pid, &info, linux.W.EXITED, ru_ptr))) { + .SUCCESS => { + current_thread.endSyscall(); + if (ru_ptr) |p| child.resource_usage_statistics.rusage = p.*; + const status: u32 = @bitCast(info.fields.common.second.sigchld.status); + const code: linux.CLD = @enumFromInt(info.code); + return switch (code) { + .EXITED => .{ .exited = @truncate(status) }, + .KILLED, .DUMPED => .{ .signal = @enumFromInt(status) }, + .TRAPPED, .STOPPED => .{ .stopped = status }, + _, .CONTINUED => .{ .unknown = status }, + }; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + .CHILD => |err| return current_thread.endSyscallErrnoBug(err), // Double-free. + else => |err| return current_thread.endSyscallUnexpectedErrno(err), + }; + } + + var status: if (builtin.link_libc) c_int else u32 = undefined; + while (true) switch (posix.errno(posix.system.waitpid(pid, &status, 0))) { + .SUCCESS => { + current_thread.endSyscall(); + return statusToTerm(@bitCast(status)); + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + .CHILD => |err| return current_thread.endSyscallErrnoBug(err), // Double-free. + else => |err| return current_thread.endSyscallUnexpectedErrno(err), }; - childCleanupPosix(child); - return statusToTerm(res.status); } fn statusToTerm(status: u32) process.Child.Term { @@ -13136,7 +13193,8 @@ fn statusToTerm(status: u32) process.Child.Term { .{ .unknown = status }; } -fn childKillPosix(t: *Threaded, child: *process.Child) !void { +fn childKillPosix(current_thread: *Thread, child: *process.Child) !void { + // Intentionally uncancelable. while (true) switch (posix.errno(posix.system.kill(child.id.?, .TERM))) { .SUCCESS => break, .INTR => continue, @@ -13145,7 +13203,7 @@ fn childKillPosix(t: *Threaded, child: *process.Child) !void { .SRCH => |err| return errnoBug(err), else => |err| return posix.unexpectedErrno(err), }; - _ = try childWaitPosix(t, child); + _ = try childWaitPosix(current_thread, child); } fn childCleanupPosix(child: *process.Child) void { diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index 68baa9c626e638fbe4c214a1bfaf3b700e2a259e..a1626113ace96ba6df88f0f861e731371d041822 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -1598,8 +1598,15 @@ pub fn wait4(pid: pid_t, status: *u32, flags: u32, usage: ?*rusage) usize { ); } -pub fn waitid(id_type: P, id: i32, infop: *siginfo_t, flags: u32) usize { - return syscall5(.waitid, @intFromEnum(id_type), @as(usize, @bitCast(@as(isize, id))), @intFromPtr(infop), flags, 0); +pub fn waitid(id_type: P, id: i32, infop: *siginfo_t, flags: u32, usage: ?*rusage) usize { + return syscall5( + .waitid, + @intFromEnum(id_type), + @as(usize, @bitCast(@as(isize, id))), + @intFromPtr(infop), + flags, + @intFromPtr(usage), + ); } pub const F = struct { @@ -6205,6 +6212,16 @@ const siginfo_fields_union = extern union { }, }; +pub const CLD = enum(i32) { + EXITED = 1, + KILLED = 2, + DUMPED = 3, + TRAPPED = 4, + STOPPED = 5, + CONTINUED = 6, + _, +}; + pub const siginfo_t = if (is_mips) extern struct { signo: SIG, diff --git a/lib/std/posix.zig b/lib/std/posix.zig index 72ddd276763ae3d81ccf0d10ce0542465d831a49..c703897d98d24a57222713892eca66defc2a47a7 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -1690,47 +1690,6 @@ pub fn getsockoptError(sockfd: fd_t) ConnectError!void { } } -pub const WaitPidResult = struct { - pid: pid_t, - status: u32, -}; - -/// Use this version of the `waitpid` wrapper if you spawned your child process using explicit -/// `fork` and `execve` method. -pub fn waitpid(pid: pid_t, flags: u32) WaitPidResult { - var status: if (builtin.link_libc) c_int else u32 = undefined; - while (true) { - const rc = system.waitpid(pid, &status, @intCast(flags)); - switch (errno(rc)) { - .SUCCESS => return .{ - .pid = @intCast(rc), - .status = @bitCast(status), - }, - .INTR => continue, - .CHILD => unreachable, // The process specified does not exist. It would be a race condition to handle this error. - .INVAL => unreachable, // Invalid flags. - else => unreachable, - } - } -} - -pub fn wait4(pid: pid_t, flags: u32, ru: ?*rusage) WaitPidResult { - var status: if (builtin.link_libc) c_int else u32 = undefined; - while (true) { - const rc = system.wait4(pid, &status, @intCast(flags), ru); - switch (errno(rc)) { - .SUCCESS => return .{ - .pid = @intCast(rc), - .status = @bitCast(status), - }, - .INTR => continue, - .CHILD => unreachable, // The process specified does not exist. It would be a race condition to handle this error. - .INVAL => unreachable, // Invalid flags. - else => unreachable, - } - } -} - pub const FStatError = std.Io.File.StatError; /// Return information about a file descriptor. -- 2.54.0