From 9fe0ebe1087ba824dac7a32042b476a9ce8d770e Mon Sep 17 00:00:00 2001 From: Jacob Young Date: Thu, 24 Sep 2026 16:19:32 -0400 Subject: [PATCH] Threaded: implement process spawning for macOS --- lib/std/Io/Threaded.zig | 447 +++++++++++++++++++++++++++++++++++++++- lib/std/c.zig | 2 + lib/std/c/darwin.zig | 4 +- lib/std/process.zig | 12 +- test/src/Runner.zig | 55 +++-- test/tests.zig | 2 + 6 files changed, 492 insertions(+), 30 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 50d9e464483921faa8d33179363c145c9b673bc4..2223d3e0659e50bea6a75595bb781596721c37c4 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -7028,6 +7028,11 @@ fn fileRealPathPosix(userdata: ?*anyopaque, file: File, out_buffer: []u8) File.R } fn realPathPosix(fd: posix.fd_t, out_buffer: []u8) File.RealPathError!usize { + if (fd == posix.AT.FDCWD) return processCurrentPathPosix(out_buffer) catch |err| switch (err) { + else => |e| return e, + error.CurrentDirUnlinked => return error.FileNotFound, + error.PathExceedsLimit => return error.NameTooLong, + }; switch (native_os) { .dragonfly, .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => { var sufficient_buffer: [posix.PATH_MAX]u8 = undefined; @@ -14143,6 +14148,7 @@ fn unlockStderr(userdata: ?*anyopaque) void { fn processCurrentPath(userdata: ?*anyopaque, buffer: []u8) process.CurrentPathError!usize { const t: *Threaded = @ptrCast(@alignCast(userdata)); _ = t; + if (is_windows) { var wtf16le_buf: [windows.PATH_MAX_WIDE:0]u16 = undefined; const n = windows.ntdll.RtlGetCurrentDirectory_U(wtf16le_buf.len * 2 + 2, &wtf16le_buf) / 2; @@ -14158,12 +14164,18 @@ fn processCurrentPath(userdata: ?*anyopaque, buffer: []u8) process.CurrentPathEr end_index += std.unicode.wtf8Encode(codepoint, buffer[end_index..]) catch unreachable; } return end_index; - } else if (native_os == .wasi and !builtin.link_libc) { + } + + if (native_os == .wasi and !builtin.link_libc) { if (buffer.len == 0) return error.NameTooLong; buffer[0] = '.'; return 1; } + return processCurrentPathPosix(buffer); +} + +fn processCurrentPathPosix(buffer: []u8) process.CurrentPathError!usize { const err: posix.E = if (builtin.link_libc) err: { const c_err = if (std.c.getcwd(buffer.ptr, buffer.len)) |_| 0 else std.c._errno().*; break :err @fromBackingInt(@intCast(c_err)); @@ -15131,12 +15143,54 @@ fn processReplace(userdata: ?*anyopaque, options: process.ReplaceOptions) proces }); }; + if (is_darwin) if (t.spawnDarwin( + .replace, + options.exe, + argv_buf.ptr, + options.cwd, + env_block, + options.expand_arg0, + .none, + .inherit, + .inherit, + .inherit, + options.inherit_dirs, + options.inherit_files, + null, + options.start_suspended, + PATH, + )) |_| unreachable else |err| switch (err) { + else => |e| return e, + error.WouldBlock => return error.SystemResources, + error.FileTooBig, + error.NoSpaceLeft, + error.PathAlreadyExists, + error.SymLinkLoop, + error.NetworkNotFound, + error.PipeBusy, + error.AntivirusInterference, + => return error.FileNotFound, + error.DeviceBusy, + error.NoDevice, + error.UnrecognizedVolume, + error.ReadOnlyFileSystem, + => return error.FileSystem, + error.ResourceLimitReached => return error.SystemResources, + error.FileLocksUnsupported => unreachable, // lock not requested + error.InvalidWtf8 => unreachable, // windows only + error.InvalidBatchScriptArg => unreachable, // windows only + error.InvalidUserId => unreachable, // not supported + error.InvalidProcessGroupId => unreachable, // passed null + error.InvalidName => unreachable, // windows only + error.ProcessAlreadyExec => unreachable, // linux only + }; return posixExec(options.exe, argv_buf.ptr, options.expand_arg0, env_block, PATH); } const processSpawn = switch (native_os) { .wasi, .emscripten, .ios, .tvos, .visionos, .watchos => processSpawnUnsupported, .windows => processSpawnWindows, + .driverkit, .maccatalyst, .macos => processSpawnDarwin, else => processSpawnPosix, }; @@ -15743,6 +15797,56 @@ fn destroyPipe(pipe: [2]posix.fd_t) void { if (pipe[0] != pipe[1]) closeFd(pipe[1]); } +fn processSpawnDarwin(userdata: ?*anyopaque, options: process.SpawnOptions) process.SpawnError!process.Child { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + + t.scanEnviron(); // for PATH + const PATH = t.environ.string.PATH orelse default_PATH; + + var arena_allocator = std.heap.ArenaAllocator.init(t.allocator); + defer arena_allocator.deinit(); + const arena = arena_allocator.allocator(); + + const argv_buf = try arena.allocSentinel(?[*:0]const u8, options.argv.len, null); + for (options.argv, 0..) |arg, i| argv_buf[i] = (try arena.dupeSentinel(u8, arg, 0)).ptr; + + const env_block = env_block: { + const prog_fd: i32 = -1; + if (options.environ_map) |environ_map| break :env_block try environ_map.createPosixBlock(arena, .{ + .zig_progress_fd = prog_fd, + }); + break :env_block try t.environ.process_environ.createPosixBlock(arena, .{ + .zig_progress_fd = prog_fd, + }); + }; + + const spawned = try t.spawnDarwin( + .spawn, + options.exe, + argv_buf.ptr, + options.cwd, + env_block, + options.expand_arg0, + options.progress_node, + options.stdin, + options.stdout, + options.stderr, + options.inherit_dirs, + options.inherit_files, + options.pgid, + options.start_suspended, + PATH, + ); + return .{ + .id = spawned.pid, + .thread_handle = {}, + .stdin = spawned.stdin, + .stdout = spawned.stdout, + .stderr = spawned.stderr, + .request_resource_usage_statistics = options.request_resource_usage_statistics, + }; +} + fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) process.SpawnError!process.Child { const t: *Threaded = @ptrCast(@alignCast(userdata)); @@ -17037,6 +17141,7 @@ fn posixExec( .path = .cwd(), } else .search, .search => { + if (Dir.path.isAbsolute(arg0_slice)) continue :exe .{ .path = .cwd() }; // Use of PATH_MAX here is valid as the path_buf will be passed // directly to the operating system in posixExecveat. var path_buf: [posix.PATH_MAX]u8 = undefined; @@ -17047,7 +17152,7 @@ fn posixExec( const path_len = search_path.len + arg0_slice.len + 1; if (path_buf.len < path_len + 1) return error.NameTooLong; @memcpy(path_buf[0..search_path.len], search_path); - path_buf[search_path.len] = '/'; + path_buf[search_path.len] = Dir.path.sep; @memcpy(path_buf[search_path.len + 1 ..][0..arg0_slice.len], arg0_slice); path_buf[path_len] = 0; const full_path = path_buf[0..path_len :0].ptr; @@ -17065,7 +17170,7 @@ fn posixExec( } return err; }, - .path => |path| return posixExecveat(path.handle, argv[0], argv, env_block), + .path => |path| return posixExecveat(path.handle, arg0_slice, argv, env_block), .file => |file| return posixExecveat(file.handle, null, argv, env_block), .explicit => |explicit| { var path_buffer: [posix.PATH_MAX]u8 = undefined; @@ -17100,7 +17205,7 @@ pub fn posixExecveat( .NOSYS => {}, } } - var path_buffer: [posix.PATH_MAX:0]u8 = undefined; + var path_buffer: [posix.PATH_MAX]u8 = undefined; var path_len = realPathPosix(dir, &path_buffer) catch |err| switch (err) { else => |e| return e, error.InputOutput, @@ -17125,7 +17230,9 @@ pub fn posixExecveat( path_len += path_slice.len; } path_buffer[path_len] = 0; - break :err posix.errno(posix.system.execve(&path_buffer, argv, env_block.slice.ptr)); + break :err posix.errno( + posix.system.execve(path_buffer[0..path_len :0], argv, env_block.slice.ptr), + ); }) { .FAULT => |err| return errnoBug(err), // Bad pointer parameter. .@"2BIG" => return error.SystemResources, @@ -17158,6 +17265,334 @@ pub fn posixExecveat( } } +fn spawnDarwin( + t: *Threaded, + mode: enum { spawn, replace }, + exe: process.ReplaceOptions.Exe, + argv: [*:null]?[*:0]const u8, + cwd: process.Child.Cwd, + env_block: process.Environ.PosixBlock, + expand_arg0: process.ArgExpansion, + progress_node: std.Progress.Node, + stdin: process.SpawnOptions.StdIo, + stdout: process.SpawnOptions.StdIo, + stderr: process.SpawnOptions.StdIo, + inherit_dirs: []const Dir, + inherit_files: []const File, + pgid: ?posix.pid_t, + start_suspended: bool, + PATH: []const u8, +) (process.SpawnError || process.ReplaceError)!Spawned { + var fa: std.c.posix_spawn_file_actions_t = undefined; + switch (@as(std.c.E, @fromBackingInt(@intCast(std.c.posix_spawn_file_actions_init(&fa))))) { + .SUCCESS => {}, + .NOMEM => return error.SystemResources, + else => |err| return posix.unexpectedErrno(err), + } + defer switch (@as(std.c.E, @fromBackingInt(@intCast( + std.c.posix_spawn_file_actions_destroy(&fa), + )))) { + .SUCCESS => {}, + .INVAL => recoverableOsBugDetected(), + else => recoverableOsBugDetected(), + }; + + var attr: std.c.posix_spawnattr_t = undefined; + switch (@as(std.c.E, @fromBackingInt(@intCast(std.c.posix_spawnattr_init(&attr))))) { + .SUCCESS => {}, + .NOMEM => return error.SystemResources, + else => |err| return posix.unexpectedErrno(err), + } + defer switch (@as(std.c.E, @fromBackingInt(@intCast(std.c.posix_spawnattr_destroy(&attr))))) { + .SUCCESS => {}, + .INVAL => recoverableOsBugDetected(), + else => recoverableOsBugDetected(), + }; + + // The child process does need to access (one end of) these pipes. However, + // we must initially set CLOEXEC to avoid a race condition. If another thread + // is racing to spawn a different child process, we don't want it to inherit + // these FDs in any scenario; that would mean that, for instance, calls to + // `poll` from the parent would not report the child's stdout as closing when + // expected, since the other child may retain a reference to the write end of + // the pipe. So, we create the pipes with CLOEXEC initially. After fork, we + // need to do something in the new child to make sure we preserve the reference + // we want. We could use `fcntl` to remove CLOEXEC from the FD, but as it + // turns out, we `dup2` everything anyway, so there's no need! + const pipe_flags: posix.O = .{ .CLOEXEC = true }; + + const stdin_pipe = if (stdin == .pipe) try pipe2(pipe_flags) else undefined; + errdefer if (stdin == .pipe) { + destroyPipe(stdin_pipe); + }; + + const stdout_pipe = if (stdout == .pipe) try pipe2(pipe_flags) else undefined; + errdefer if (stdout == .pipe) { + destroyPipe(stdout_pipe); + }; + + const stderr_pipe = if (stderr == .pipe) try pipe2(pipe_flags) else undefined; + errdefer if (stderr == .pipe) { + destroyPipe(stderr_pipe); + }; + + const any_ignore = (stdin == .ignore or stdout == .ignore or stderr == .ignore); + const dev_null_fd = if (any_ignore) try t.getDevNullFd() else undefined; + + const prog_pipe: [2]posix.fd_t = if (progress_node.index != .none) pipe: { + // We use CLOEXEC for the same reason as in `pipe_flags`. + break :pipe try pipe2(.{ .NONBLOCK = true, .CLOEXEC = true }); + } else .{ -1, -1 }; + errdefer destroyPipe(prog_pipe); + + var cwd_buffer: [posix.PATH_MAX]u8 = undefined; + switch (cwd) { + .inherit => {}, + .dir => |dir| switch (@as(std.c.E, @fromBackingInt(@intCast( + std.c.posix_spawn_file_actions_addfchdir_np(&fa, dir.handle), + )))) { + .SUCCESS => {}, + .BADF => |err| return errnoBug(err), + .INVAL => |err| return errnoBug(err), + .NOMEM => return error.SystemResources, + else => |err| return posix.unexpectedErrno(err), + }, + .path => |path| switch (@as(std.c.E, @fromBackingInt(@intCast( + std.c.posix_spawn_file_actions_addchdir_np(&fa, try pathToPosix(path, &cwd_buffer)), + )))) { + .SUCCESS => {}, + .BADF => |err| return errnoBug(err), + .INVAL => |err| return errnoBug(err), + .NAMETOOLONG => return error.NameTooLong, + .NOMEM => return error.SystemResources, + else => |err| return posix.unexpectedErrno(err), + }, + } + + for (inherit_dirs) |inherit_dir| switch (@as(std.c.E, @fromBackingInt(@intCast( + std.c.posix_spawn_file_actions_addinherit_np(&fa, inherit_dir.handle), + )))) { + .SUCCESS => {}, + .BADF => |err| return errnoBug(err), + .INVAL => |err| return errnoBug(err), + .NOMEM => return error.SystemResources, + else => |err| return posix.unexpectedErrno(err), + }; + for (inherit_files) |inherit_file| switch (@as(std.c.E, @fromBackingInt(@intCast( + std.c.posix_spawn_file_actions_addinherit_np(&fa, inherit_file.handle), + )))) { + .SUCCESS => {}, + .BADF => |err| return errnoBug(err), + .INVAL => |err| return errnoBug(err), + .NOMEM => return error.SystemResources, + else => |err| return posix.unexpectedErrno(err), + }; + if (prog_pipe[1] != -1) switch (@as(std.c.E, @fromBackingInt(@intCast( + std.c.posix_spawn_file_actions_addinherit_np(&fa, prog_pipe[1]), + )))) { + .SUCCESS => {}, + .BADF => |err| return errnoBug(err), + .INVAL => |err| return errnoBug(err), + .NOMEM => return error.SystemResources, + else => |err| return posix.unexpectedErrno(err), + }; + for ( + [_]process.SpawnOptions.StdIo{ stdin, stdout, stderr }, + [_]posix.fd_t{ stdin_pipe[0], stdout_pipe[1], stderr_pipe[1] }, + [_]posix.fd_t{ posix.STDIN_FILENO, posix.STDOUT_FILENO, posix.STDERR_FILENO }, + ) |stdio, pipe_fd, std_fd| switch (@as(std.c.E, @fromBackingInt(@intCast(switch (stdio) { + .pipe => std.c.posix_spawn_file_actions_adddup2(&fa, pipe_fd, std_fd), + .close => std.c.posix_spawn_file_actions_addclose(&fa, std_fd), + .inherit => std.c.posix_spawn_file_actions_addinherit_np(&fa, std_fd), + .ignore => std.c.posix_spawn_file_actions_adddup2(&fa, dev_null_fd, std_fd), + .file => |file| std.c.posix_spawn_file_actions_adddup2(&fa, file.handle, std_fd), + })))) { + .SUCCESS => {}, + .BADF => |err| return errnoBug(err), + .INVAL => |err| return errnoBug(err), + .NOMEM => return error.SystemResources, + else => |err| return posix.unexpectedErrno(err), + }; + + if (pgid) |pid| switch (@as(std.c.E, @fromBackingInt(@intCast( + std.c.posix_spawnattr_setpgroup(&attr, pid), + )))) { + .SUCCESS => {}, + .INVAL => return error.InvalidProcessGroupId, + else => |err| return posix.unexpectedErrno(err), + }; + + switch (@as(std.c.E, @fromBackingInt(@intCast(std.c.posix_spawnattr_setflags(&attr, .{ + .SETPGROUP = pgid != null, + .SETEXEC = switch (mode) { + .spawn => false, + .replace => true, + }, + .START_SUSPENDED = start_suspended, + }))))) { + .SUCCESS => {}, + .INVAL => |err| return errnoBug(err), + else => |err| return posix.unexpectedErrno(err), + } + + var pid: posix.pid_t = undefined; + const arg0_slice = std.mem.span(argv[0].?); + var path_buffer: [posix.PATH_MAX]u8 = undefined; + switch (err: { + break :err @as(std.c.E, @fromBackingInt(@intCast(exe: switch (exe) { + .detect => if (std.mem.findScalar(u8, arg0_slice, '/')) |_| + std.c.posix_spawn(&pid, arg0_slice, &fa, &attr, argv, env_block.slice.ptr) + else + continue :exe .search, + .search => if (!Dir.path.isAbsolute(arg0_slice)) { + // Use of PATH_MAX here is valid as the path_buf will be passed + // directly to the operating system in posixExecveat. + var path_buf: [posix.PATH_MAX]u8 = undefined; + var it = std.mem.tokenizeScalar(u8, PATH, ':'); + var err: error{ AccessDenied, FileNotFound, NotDir } = error.FileNotFound; + + while (it.next()) |search_path| { + const path_len = search_path.len + arg0_slice.len + 1; + if (path_buf.len < path_len + 1) return error.NameTooLong; + @memcpy(path_buf[0..search_path.len], search_path); + path_buf[search_path.len] = Dir.path.sep; + @memcpy(path_buf[search_path.len + 1 ..][0..arg0_slice.len], arg0_slice); + path_buf[path_len] = 0; + const full_path = path_buf[0..path_len :0].ptr; + switch (expand_arg0) { + .expand => argv[0] = full_path, + .no_expand => {}, + } + switch (@as(std.c.E, @fromBackingInt(@intCast( + std.c.posix_spawn(&pid, full_path, &fa, &attr, argv, env_block.slice.ptr), + )))) { + else => |e| break :err e, + .ACCES, .NOENT, .NOTDIR => |e| switch (err) { + error.AccessDenied => {}, + error.FileNotFound, error.NotDir => err = switch (e) { + else => unreachable, + .ACCES => error.AccessDenied, + .NOENT => error.FileNotFound, + .NOTDIR => error.NotDir, + }, + }, + } + } + return err; + } else std.c.posix_spawn(&pid, arg0_slice, &fa, &attr, argv, env_block.slice.ptr), + .path => |path| if (path.handle != posix.AT.FDCWD and !Dir.path.isAbsolute(arg0_slice)) { + var path_len = realPathPosix(path.handle, &path_buffer) catch |err| switch (err) { + else => |e| return e, + error.InputOutput => return error.FileNotFound, + }; + path_buffer[path_len] = Dir.path.sep; + path_len += 1; + @memcpy(path_buffer[path_len..][0..arg0_slice.len], arg0_slice); + path_len += arg0_slice.len; + path_buffer[path_len] = 0; + break :exe std.c.posix_spawn( + &pid, + path_buffer[0..path_len :0], + &fa, + &attr, + argv, + env_block.slice.ptr, + ); + } else std.c.posix_spawn(&pid, arg0_slice, &fa, &attr, argv, env_block.slice.ptr), + .file => |file| { + const path_len = realPathPosix(file.handle, &path_buffer) catch |err| switch (err) { + else => |e| return e, + error.InputOutput => return error.FileNotFound, + }; + path_buffer[path_len] = 0; + break :exe std.c.posix_spawn( + &pid, + path_buffer[0..path_len :0], + &fa, + &attr, + argv, + env_block.slice.ptr, + ); + }, + .explicit => |explicit| if (explicit.dir.handle != posix.AT.FDCWD and + !Dir.path.isAbsolute(explicit.path)) + { + var path_len = realPathPosix( + explicit.dir.handle, + &path_buffer, + ) catch |err| switch (err) { + else => |e| return e, + error.InputOutput => return error.FileNotFound, + }; + path_buffer[path_len] = Dir.path.sep; + path_len += 1; + @memcpy(path_buffer[path_len..][0..explicit.path.len], explicit.path); + path_len += explicit.path.len; + path_buffer[path_len] = 0; + break :exe std.c.posix_spawn( + &pid, + path_buffer[0..path_len :0], + &fa, + &attr, + argv, + env_block.slice.ptr, + ); + } else std.c.posix_spawn( + &pid, + try pathToPosix(explicit.path, &path_buffer), + &fa, + &attr, + argv, + env_block.slice.ptr, + ), + }))); + }) { + .SUCCESS => {}, + .AGAIN => return error.SystemResources, + .INVAL => |err| return errnoBug(err), + .@"2BIG" => return error.SystemResources, + .ACCES => return error.AccessDenied, + .FAULT => |err| return errnoBug(err), + .IO => return error.FileSystem, + .LOOP => return error.FileSystem, + .NAMETOOLONG => return error.NameTooLong, + .NOENT => return error.FileNotFound, + .NOEXEC => return error.InvalidExe, + .NOMEM => return error.SystemResources, + .NOTDIR => return error.NotDir, + .TXTBSY => return error.FileBusy, + .BADARCH => return error.InvalidExe, + .BADF => |err| return errnoBug(err), + else => |err| return posix.unexpectedErrno(err), + } + + errdefer comptime unreachable; // The child is spawned; we must not error from now on + + if (stdin == .pipe) closeFd(stdin_pipe[0]); + if (stdout == .pipe) closeFd(stdout_pipe[1]); + if (stderr == .pipe) closeFd(stderr_pipe[1]); + + if (prog_pipe[1] != -1) closeFd(prog_pipe[1]); + progress_node.setIpcFile(t, .{ .handle = prog_pipe[0], .flags = .{ .nonblocking = true } }); + + return .{ + .pid = pid, + .err_fd = -1, + .stdin = switch (stdin) { + .pipe => .{ .handle = stdin_pipe[1], .flags = .{ .nonblocking = false } }, + else => null, + }, + .stdout = switch (stdout) { + .pipe => .{ .handle = stdout_pipe[0], .flags = .{ .nonblocking = false } }, + else => null, + }, + .stderr = switch (stderr) { + .pipe => .{ .handle = stderr_pipe[0], .flags = .{ .nonblocking = false } }, + else => null, + }, + }; +} + pub const CreatePipeOptions = struct { server: End, client: End, @@ -18469,7 +18904,7 @@ const CreateFileMapError = error{ fn createFileMap( file: File, - protection: std.process.MemoryProtection, + protection: process.MemoryProtection, offset: u64, populate: bool, len: usize, diff --git a/lib/std/c.zig b/lib/std/c.zig index 4ec2d6b0ac2db23b2ffa6b846772f2f4e9b18b16..51c6656d4f21dc23717f757faddc76f109049e2a 100644 --- a/lib/std/c.zig +++ b/lib/std/c.zig @@ -11785,8 +11785,10 @@ pub const posix_spawn_file_actions_init = darwin.posix_spawn_file_actions_init; pub const posix_spawn_file_actions_t = darwin.posix_spawn_file_actions_t; pub const posix_spawnattr_destroy = darwin.posix_spawnattr_destroy; pub const posix_spawnattr_getflags = darwin.posix_spawnattr_getflags; +pub const posix_spawnattr_getpgroup = darwin.posix_spawnattr_getpgroup; pub const posix_spawnattr_init = darwin.posix_spawnattr_init; pub const posix_spawnattr_setflags = darwin.posix_spawnattr_setflags; +pub const posix_spawnattr_setpgroup = darwin.posix_spawnattr_setpgroup; pub const posix_spawnattr_t = darwin.posix_spawnattr_t; pub const posix_spawnp = darwin.posix_spawnp; pub const pthread_attr_get_qos_class_np = darwin.pthread_attr_get_qos_class_np; diff --git a/lib/std/c/darwin.zig b/lib/std/c/darwin.zig index 19ff7897d8a4297dedcd565d4f01b5f903f60667..fa8c1901cd74954ece2856487cf4eb6afcd90beb 100644 --- a/lib/std/c/darwin.zig +++ b/lib/std/c/darwin.zig @@ -1253,8 +1253,10 @@ pub const posix_spawnattr_t = *opaque {}; pub const posix_spawn_file_actions_t = *opaque {}; pub extern "c" fn posix_spawnattr_init(attr: *posix_spawnattr_t) c_int; pub extern "c" fn posix_spawnattr_destroy(attr: *posix_spawnattr_t) c_int; -pub extern "c" fn posix_spawnattr_setflags(attr: *posix_spawnattr_t, flags: POSIX_SPAWN) c_int; pub extern "c" fn posix_spawnattr_getflags(attr: *const posix_spawnattr_t, flags: *POSIX_SPAWN) c_int; +pub extern "c" fn posix_spawnattr_getpgroup(attr: *const posix_spawnattr_t, pgroup: *pid_t) c_int; +pub extern "c" fn posix_spawnattr_setflags(attr: *posix_spawnattr_t, flags: POSIX_SPAWN) c_int; +pub extern "c" fn posix_spawnattr_setpgroup(attr: *posix_spawnattr_t, pgroup: pid_t) c_int; pub extern "c" fn posix_spawn_file_actions_init(actions: *posix_spawn_file_actions_t) c_int; pub extern "c" fn posix_spawn_file_actions_destroy(actions: *posix_spawn_file_actions_t) c_int; pub extern "c" fn posix_spawn_file_actions_addclose(actions: *posix_spawn_file_actions_t, filedes: fd_t) c_int; diff --git a/lib/std/process.zig b/lib/std/process.zig index 43ec3611e0c373a72e60dfdf42c0ba405af5ab4a..ed4dceabdde0abee586683e90daddf144d78fff9 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -284,10 +284,20 @@ pub const ReplaceError = error{ pub const ReplaceOptions = struct { exe: Exe = .detect, argv: []const []const u8, - expand_arg0: ArgExpansion = .no_expand, + + /// Set to change the current working directory when spawning the child process. + cwd: Child.Cwd = .inherit, /// Replaces the environment when provided. The PATH value from here is /// never used to resolve `argv[0]`. environ_map: ?*const Environ.Map = null, + expand_arg0: ArgExpansion = .no_expand, + + inherit_dirs: []const Dir = &.{}, + inherit_files: []const File = &.{}, + + /// Start child process in suspended state. + /// For Posix systems it's started as if SIGSTOP was sent. + start_suspended: bool = false, pub const Exe = union(enum) { /// `argv[0]` is the name of the program to execute. If it is not already a diff --git a/test/src/Runner.zig b/test/src/Runner.zig index 158d657ad37ba2b5f73183d9e709eae0be431cbb..c985ff8410fea3e0181a74d41d19be62d36767d0 100644 --- a/test/src/Runner.zig +++ b/test/src/Runner.zig @@ -26,6 +26,7 @@ const Args = struct { enable_wine: bool, enable_wasmtime: bool, enable_darling: bool, + quiet: bool, fn deinit(args: *Args, gpa: std.mem.Allocator) void { args.targets.deinit(gpa); @@ -98,6 +99,8 @@ pub fn runServer(runner: *Runner) ProtocolError { runner.args.enable_wasmtime = true; } else if (std.mem.eql(u8, string, "-fdarling")) { runner.args.enable_darling = true; + } else if (std.mem.eql(u8, string, "--quiet")) { + runner.args.quiet = true; }, .zig => unreachable, .lib => unreachable, @@ -463,10 +466,10 @@ fn testOne(runner: *Runner, test_index: u32) TestError!void { .target_query = target_query, .manifest_sr = .init(&manifest_fr.interface, runner.gpa, &runner.eb_wip, src_path_string: { var path_buffer: [std.Io.Dir.max_path_bytes]u8 = undefined; + const src_path = src_path: { - break :src_path path_buffer[0 .. std.Io.Dir.readLinkAbsolute( + break :src_path path_buffer[0 .. manifest_file.realPath( runner.io, - try runner.arena.allocator().print("/proc/self/fd/{d}", .{manifest_file.handle}), &path_buffer, ) catch |err| switch (err) { error.Canceled => |e| return e, @@ -839,12 +842,9 @@ fn spawnCompiler( .selfhosted => try argv.append(gpa, "-fno-llvm"), .llvm => try argv.append(gpa, "-fllvm"), .cbe => try argv.append(gpa, "-ofmt=c"), - } else try argv.appendSlice(gpa, &.{ "-I", path_buffer[0 .. std.Io.Dir.readLinkAbsolute( - runner.io, - try arena.print("/proc/self/fd/{d}", .{(runner.args.lib_dir orelse - return runner.fail("\"--lib=path/to/lib\" arg required with cbe", .{})).handle}), - &path_buffer, - ) catch |err| switch (err) { + } else try argv.appendSlice(gpa, &.{ "-I", path_buffer[0 .. (runner.args.lib_dir orelse + return runner.fail("\"--lib=path/to/lib\" arg required with cbe", .{})) + .realPath(runner.io, &path_buffer) catch |err| switch (err) { error.Canceled => |e| return e, else => |e| return runner.fail("unable to get lib path: {t}", .{e}), }] }); @@ -1034,8 +1034,8 @@ const Compiler = struct { )) { .bad_dl, .bad_os_or_cpu => { // This binary cannot be executed on this host. - std.log.warn("skipping execution because host '{s}' cannot execute binaries for " ++ - "foreign target '{s}'", .{ + if (!runner.args.quiet) std.log.warn("skipping execution because host {q} cannot " ++ + "execute binaries for foreign target {q}", .{ try runner.host.zigTriple(arena), comp.target.triple, }); return; @@ -1080,12 +1080,12 @@ const Compiler = struct { .progress_node = run_prog_node, }) catch |err| if (executor) |_| { // Chances are the foreign executor isn't available. Skip this evaluation. - std.log.warn( - "skipping execution of '{s}' via executor for foreign target '{s}': {t}", + if (!runner.args.quiet) std.log.warn( + "skipping execution of {q} via executor for foreign target {q}: {t}", .{ bin_path, comp.target.triple, err }, ); return; - } else return runner.fail("unable to run the generated executable '{s}': {t}", .{ + } else return runner.fail("unable to run the generated executable {q}: {t}", .{ bin_path, err, }); defer { @@ -1097,7 +1097,7 @@ const Compiler = struct { .errors, .lldb => unreachable, .stdout => { if (code != 0) return runner.fail( - "generated executable '{s}' failed with code {d}", + "generated executable {q} failed with code {d}", .{ bin_path, code }, ); std.testing.expectEqualStrings( @@ -1125,20 +1125,20 @@ const Compiler = struct { }, }, .signal => |sig| return runner.fail( - "generated executable '{s}' terminated with signal {t}", + "generated executable {q} terminated with signal {t}", .{ bin_path, sig }, ), .stopped => |sig| return runner.fail( - "generated executable '{s}' stopped with signal {t}", + "generated executable {q} stopped with signal {t}", .{ bin_path, sig }, ), .unknown => return runner.fail( - "generated executable '{s}' terminated unexpectedly", + "generated executable {q} terminated unexpectedly", .{bin_path}, ), } if (executor == null and result.stderr.len > 0) { - std.log.err("generated executable '{s}' had unexpected stderr:\n{s}", .{ + std.log.err("generated executable {q} had unexpected stderr:\n{s}", .{ bin_path, result.stderr, }); } @@ -1247,6 +1247,7 @@ const ClientArgs = struct { enable_wine: bool, enable_wasmtime: bool, enable_darling: bool, + quiet: bool, }; pub fn main(init: std.process.Init) (std.mem.Allocator.Error || std.Io.Cancelable)!u8 { const fatal = struct { @@ -1272,6 +1273,7 @@ pub fn main(init: std.process.Init) (std.mem.Allocator.Error || std.Io.Cancelabl .enable_wine = false, .enable_wasmtime = false, .enable_darling = false, + .quiet = false, }; defer args.targets.deinit(init.gpa); @@ -1300,6 +1302,7 @@ pub fn main(init: std.process.Init) (std.mem.Allocator.Error || std.Io.Cancelabl .enable_wine = false, .enable_wasmtime = false, .enable_darling = false, + .quiet = false, }, .host = std.zig.system.resolveTargetQuery(init.io, .{}) catch |err| switch (err) { error.Canceled => |e| return e, @@ -1328,17 +1331,17 @@ pub fn main(init: std.process.Init) (std.mem.Allocator.Error || std.Io.Cancelabl }, } } else if (std.mem.eql(u8, arg, "--zig")) { - zig_path_arg = arg_it.next() orelse fatal("missing arg after '{s}'", .{arg}); + zig_path_arg = arg_it.next() orelse fatal("missing arg after {q}", .{arg}); } else if (std.mem.cutPrefix(u8, arg, "--zig=")) |zig_path| { zig_path_arg = zig_path; } else if (std.mem.eql(u8, arg, "--lib")) { - lib_path_arg = arg_it.next() orelse fatal("missing arg after '{s}'", .{arg}); + lib_path_arg = arg_it.next() orelse fatal("missing arg after {q}", .{arg}); } else if (std.mem.cutPrefix(u8, arg, "--lib=")) |lib_path| { lib_path_arg = lib_path; } else if (std.mem.eql(u8, arg, "--target")) { try args.targets.append( init.gpa, - try arena.dupe(u8, arg_it.next() orelse fatal("missing arg after '{s}'", .{arg})), + try arena.dupe(u8, arg_it.next() orelse fatal("missing arg after {q}", .{arg})), ); } else if (std.mem.cutPrefix(u8, arg, "--target=")) |target| { try args.targets.append(init.gpa, try arena.dupe(u8, target)); @@ -1352,8 +1355,10 @@ pub fn main(init: std.process.Init) (std.mem.Allocator.Error || std.Io.Cancelabl args.enable_wasmtime = true; } else if (std.mem.eql(u8, arg, "-fdarling")) { args.enable_darling = true; + } else if (std.mem.eql(u8, arg, "--quiet")) { + args.quiet = true; } else { - if (test_path_arg) |_| fatal("unknown arg '{s}'", .{arg}); + if (test_path_arg) |_| fatal("unknown arg {q}", .{arg}); test_path_arg = arg; } } @@ -1509,6 +1514,12 @@ fn runClient( try argv.append(gpa, 0); } + if (args.quiet) { + try argv.append(gpa, @backingInt(Arg.string)); + try argv.appendSlice(gpa, "--quiet"); + try argv.append(gpa, 0); + } + try client.serveMessageHeader(.{ .tag = .args, .bytes_len = @intCast(argv.items.len), diff --git a/test/tests.zig b/test/tests.zig index ac6cca3ab78d3325daac58214975254b7ef3da9f..3da8ef96442b31b91217fadc6334d5102dcaa76d 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -3417,6 +3417,8 @@ pub fn addIncrementalTests( run.addThirdPartyEnabledArgWasmtime(.{ .enabled = "-fwasmtime" }); run.addThirdPartyEnabledArgWine(.{ .enabled = "-fwine" }); + run.addArg("--quiet"); // don't fill stderr telling us about skipped tests etc + tests_step.dependOn(&run.step); } -- 2.54.0