authorgravatar for jacobly@ziglang.orgJacob Young <jacobly@ziglang.org> 2026-09-24 16:19:32-04:00
committergravatar for jacobly@ziglang.orgJacob Young <jacobly@ziglang.org> 2026-09-26 19:08:50-04:00
log9fe0ebe1087ba824dac7a32042b476a9ce8d770e
treecfacf1fc49d1e249b95b07a4d3ad1c5ea955818a
parent00b59eb18ae796ab2d5b20e91025811fdaae0b13

Threaded: implement process spawning for macOS


6 files changed, 492 insertions(+), 30 deletions(-)

lib/std/Io/Threaded.zig+441-6
......@@ -7028,6 +7028,11 @@ fn fileRealPathPosix(userdata: ?*anyopaque, file: File, out_buffer: []u8) File.R
70287028}
70297029
70307030fn realPathPosix(fd: posix.fd_t, out_buffer: []u8) File.RealPathError!usize {
7031 if (fd == posix.AT.FDCWD) return processCurrentPathPosix(out_buffer) catch |err| switch (err) {
7032 else => |e| return e,
7033 error.CurrentDirUnlinked => return error.FileNotFound,
7034 error.PathExceedsLimit => return error.NameTooLong,
7035 };
70317036 switch (native_os) {
70327037 .dragonfly, .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => {
70337038 var sufficient_buffer: [posix.PATH_MAX]u8 = undefined;
......@@ -14143,6 +14148,7 @@ fn unlockStderr(userdata: ?*anyopaque) void {
1414314148fn processCurrentPath(userdata: ?*anyopaque, buffer: []u8) process.CurrentPathError!usize {
1414414149 const t: *Threaded = @ptrCast(@alignCast(userdata));
1414514150 _ = t;
14151
1414614152 if (is_windows) {
1414714153 var wtf16le_buf: [windows.PATH_MAX_WIDE:0]u16 = undefined;
1414814154 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
1415814164 end_index += std.unicode.wtf8Encode(codepoint, buffer[end_index..]) catch unreachable;
1415914165 }
1416014166 return end_index;
14161 } else if (native_os == .wasi and !builtin.link_libc) {
14167 }
14168
14169 if (native_os == .wasi and !builtin.link_libc) {
1416214170 if (buffer.len == 0) return error.NameTooLong;
1416314171 buffer[0] = '.';
1416414172 return 1;
1416514173 }
1416614174
14175 return processCurrentPathPosix(buffer);
14176}
14177
14178fn processCurrentPathPosix(buffer: []u8) process.CurrentPathError!usize {
1416714179 const err: posix.E = if (builtin.link_libc) err: {
1416814180 const c_err = if (std.c.getcwd(buffer.ptr, buffer.len)) |_| 0 else std.c._errno().*;
1416914181 break :err @fromBackingInt(@intCast(c_err));
......@@ -15131,12 +15143,54 @@ fn processReplace(userdata: ?*anyopaque, options: process.ReplaceOptions) proces
1513115143 });
1513215144 };
1513315145
15146 if (is_darwin) if (t.spawnDarwin(
15147 .replace,
15148 options.exe,
15149 argv_buf.ptr,
15150 options.cwd,
15151 env_block,
15152 options.expand_arg0,
15153 .none,
15154 .inherit,
15155 .inherit,
15156 .inherit,
15157 options.inherit_dirs,
15158 options.inherit_files,
15159 null,
15160 options.start_suspended,
15161 PATH,
15162 )) |_| unreachable else |err| switch (err) {
15163 else => |e| return e,
15164 error.WouldBlock => return error.SystemResources,
15165 error.FileTooBig,
15166 error.NoSpaceLeft,
15167 error.PathAlreadyExists,
15168 error.SymLinkLoop,
15169 error.NetworkNotFound,
15170 error.PipeBusy,
15171 error.AntivirusInterference,
15172 => return error.FileNotFound,
15173 error.DeviceBusy,
15174 error.NoDevice,
15175 error.UnrecognizedVolume,
15176 error.ReadOnlyFileSystem,
15177 => return error.FileSystem,
15178 error.ResourceLimitReached => return error.SystemResources,
15179 error.FileLocksUnsupported => unreachable, // lock not requested
15180 error.InvalidWtf8 => unreachable, // windows only
15181 error.InvalidBatchScriptArg => unreachable, // windows only
15182 error.InvalidUserId => unreachable, // not supported
15183 error.InvalidProcessGroupId => unreachable, // passed null
15184 error.InvalidName => unreachable, // windows only
15185 error.ProcessAlreadyExec => unreachable, // linux only
15186 };
1513415187 return posixExec(options.exe, argv_buf.ptr, options.expand_arg0, env_block, PATH);
1513515188}
1513615189
1513715190const processSpawn = switch (native_os) {
1513815191 .wasi, .emscripten, .ios, .tvos, .visionos, .watchos => processSpawnUnsupported,
1513915192 .windows => processSpawnWindows,
15193 .driverkit, .maccatalyst, .macos => processSpawnDarwin,
1514015194 else => processSpawnPosix,
1514115195};
1514215196
......@@ -15743,6 +15797,56 @@ fn destroyPipe(pipe: [2]posix.fd_t) void {
1574315797 if (pipe[0] != pipe[1]) closeFd(pipe[1]);
1574415798}
1574515799
15800fn processSpawnDarwin(userdata: ?*anyopaque, options: process.SpawnOptions) process.SpawnError!process.Child {
15801 const t: *Threaded = @ptrCast(@alignCast(userdata));
15802
15803 t.scanEnviron(); // for PATH
15804 const PATH = t.environ.string.PATH orelse default_PATH;
15805
15806 var arena_allocator = std.heap.ArenaAllocator.init(t.allocator);
15807 defer arena_allocator.deinit();
15808 const arena = arena_allocator.allocator();
15809
15810 const argv_buf = try arena.allocSentinel(?[*:0]const u8, options.argv.len, null);
15811 for (options.argv, 0..) |arg, i| argv_buf[i] = (try arena.dupeSentinel(u8, arg, 0)).ptr;
15812
15813 const env_block = env_block: {
15814 const prog_fd: i32 = -1;
15815 if (options.environ_map) |environ_map| break :env_block try environ_map.createPosixBlock(arena, .{
15816 .zig_progress_fd = prog_fd,
15817 });
15818 break :env_block try t.environ.process_environ.createPosixBlock(arena, .{
15819 .zig_progress_fd = prog_fd,
15820 });
15821 };
15822
15823 const spawned = try t.spawnDarwin(
15824 .spawn,
15825 options.exe,
15826 argv_buf.ptr,
15827 options.cwd,
15828 env_block,
15829 options.expand_arg0,
15830 options.progress_node,
15831 options.stdin,
15832 options.stdout,
15833 options.stderr,
15834 options.inherit_dirs,
15835 options.inherit_files,
15836 options.pgid,
15837 options.start_suspended,
15838 PATH,
15839 );
15840 return .{
15841 .id = spawned.pid,
15842 .thread_handle = {},
15843 .stdin = spawned.stdin,
15844 .stdout = spawned.stdout,
15845 .stderr = spawned.stderr,
15846 .request_resource_usage_statistics = options.request_resource_usage_statistics,
15847 };
15848}
15849
1574615850fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) process.SpawnError!process.Child {
1574715851 const t: *Threaded = @ptrCast(@alignCast(userdata));
1574815852
......@@ -17037,6 +17141,7 @@ fn posixExec(
1703717141 .path = .cwd(),
1703817142 } else .search,
1703917143 .search => {
17144 if (Dir.path.isAbsolute(arg0_slice)) continue :exe .{ .path = .cwd() };
1704017145 // Use of PATH_MAX here is valid as the path_buf will be passed
1704117146 // directly to the operating system in posixExecveat.
1704217147 var path_buf: [posix.PATH_MAX]u8 = undefined;
......@@ -17047,7 +17152,7 @@ fn posixExec(
1704717152 const path_len = search_path.len + arg0_slice.len + 1;
1704817153 if (path_buf.len < path_len + 1) return error.NameTooLong;
1704917154 @memcpy(path_buf[0..search_path.len], search_path);
17050 path_buf[search_path.len] = '/';
17155 path_buf[search_path.len] = Dir.path.sep;
1705117156 @memcpy(path_buf[search_path.len + 1 ..][0..arg0_slice.len], arg0_slice);
1705217157 path_buf[path_len] = 0;
1705317158 const full_path = path_buf[0..path_len :0].ptr;
......@@ -17065,7 +17170,7 @@ fn posixExec(
1706517170 }
1706617171 return err;
1706717172 },
17068 .path => |path| return posixExecveat(path.handle, argv[0], argv, env_block),
17173 .path => |path| return posixExecveat(path.handle, arg0_slice, argv, env_block),
1706917174 .file => |file| return posixExecveat(file.handle, null, argv, env_block),
1707017175 .explicit => |explicit| {
1707117176 var path_buffer: [posix.PATH_MAX]u8 = undefined;
......@@ -17100,7 +17205,7 @@ pub fn posixExecveat(
1710017205 .NOSYS => {},
1710117206 }
1710217207 }
17103 var path_buffer: [posix.PATH_MAX:0]u8 = undefined;
17208 var path_buffer: [posix.PATH_MAX]u8 = undefined;
1710417209 var path_len = realPathPosix(dir, &path_buffer) catch |err| switch (err) {
1710517210 else => |e| return e,
1710617211 error.InputOutput,
......@@ -17125,7 +17230,9 @@ pub fn posixExecveat(
1712517230 path_len += path_slice.len;
1712617231 }
1712717232 path_buffer[path_len] = 0;
17128 break :err posix.errno(posix.system.execve(&path_buffer, argv, env_block.slice.ptr));
17233 break :err posix.errno(
17234 posix.system.execve(path_buffer[0..path_len :0], argv, env_block.slice.ptr),
17235 );
1712917236 }) {
1713017237 .FAULT => |err| return errnoBug(err), // Bad pointer parameter.
1713117238 .@"2BIG" => return error.SystemResources,
......@@ -17158,6 +17265,334 @@ pub fn posixExecveat(
1715817265 }
1715917266}
1716017267
17268fn spawnDarwin(
17269 t: *Threaded,
17270 mode: enum { spawn, replace },
17271 exe: process.ReplaceOptions.Exe,
17272 argv: [*:null]?[*:0]const u8,
17273 cwd: process.Child.Cwd,
17274 env_block: process.Environ.PosixBlock,
17275 expand_arg0: process.ArgExpansion,
17276 progress_node: std.Progress.Node,
17277 stdin: process.SpawnOptions.StdIo,
17278 stdout: process.SpawnOptions.StdIo,
17279 stderr: process.SpawnOptions.StdIo,
17280 inherit_dirs: []const Dir,
17281 inherit_files: []const File,
17282 pgid: ?posix.pid_t,
17283 start_suspended: bool,
17284 PATH: []const u8,
17285) (process.SpawnError || process.ReplaceError)!Spawned {
17286 var fa: std.c.posix_spawn_file_actions_t = undefined;
17287 switch (@as(std.c.E, @fromBackingInt(@intCast(std.c.posix_spawn_file_actions_init(&fa))))) {
17288 .SUCCESS => {},
17289 .NOMEM => return error.SystemResources,
17290 else => |err| return posix.unexpectedErrno(err),
17291 }
17292 defer switch (@as(std.c.E, @fromBackingInt(@intCast(
17293 std.c.posix_spawn_file_actions_destroy(&fa),
17294 )))) {
17295 .SUCCESS => {},
17296 .INVAL => recoverableOsBugDetected(),
17297 else => recoverableOsBugDetected(),
17298 };
17299
17300 var attr: std.c.posix_spawnattr_t = undefined;
17301 switch (@as(std.c.E, @fromBackingInt(@intCast(std.c.posix_spawnattr_init(&attr))))) {
17302 .SUCCESS => {},
17303 .NOMEM => return error.SystemResources,
17304 else => |err| return posix.unexpectedErrno(err),
17305 }
17306 defer switch (@as(std.c.E, @fromBackingInt(@intCast(std.c.posix_spawnattr_destroy(&attr))))) {
17307 .SUCCESS => {},
17308 .INVAL => recoverableOsBugDetected(),
17309 else => recoverableOsBugDetected(),
17310 };
17311
17312 // The child process does need to access (one end of) these pipes. However,
17313 // we must initially set CLOEXEC to avoid a race condition. If another thread
17314 // is racing to spawn a different child process, we don't want it to inherit
17315 // these FDs in any scenario; that would mean that, for instance, calls to
17316 // `poll` from the parent would not report the child's stdout as closing when
17317 // expected, since the other child may retain a reference to the write end of
17318 // the pipe. So, we create the pipes with CLOEXEC initially. After fork, we
17319 // need to do something in the new child to make sure we preserve the reference
17320 // we want. We could use `fcntl` to remove CLOEXEC from the FD, but as it
17321 // turns out, we `dup2` everything anyway, so there's no need!
17322 const pipe_flags: posix.O = .{ .CLOEXEC = true };
17323
17324 const stdin_pipe = if (stdin == .pipe) try pipe2(pipe_flags) else undefined;
17325 errdefer if (stdin == .pipe) {
17326 destroyPipe(stdin_pipe);
17327 };
17328
17329 const stdout_pipe = if (stdout == .pipe) try pipe2(pipe_flags) else undefined;
17330 errdefer if (stdout == .pipe) {
17331 destroyPipe(stdout_pipe);
17332 };
17333
17334 const stderr_pipe = if (stderr == .pipe) try pipe2(pipe_flags) else undefined;
17335 errdefer if (stderr == .pipe) {
17336 destroyPipe(stderr_pipe);
17337 };
17338
17339 const any_ignore = (stdin == .ignore or stdout == .ignore or stderr == .ignore);
17340 const dev_null_fd = if (any_ignore) try t.getDevNullFd() else undefined;
17341
17342 const prog_pipe: [2]posix.fd_t = if (progress_node.index != .none) pipe: {
17343 // We use CLOEXEC for the same reason as in `pipe_flags`.
17344 break :pipe try pipe2(.{ .NONBLOCK = true, .CLOEXEC = true });
17345 } else .{ -1, -1 };
17346 errdefer destroyPipe(prog_pipe);
17347
17348 var cwd_buffer: [posix.PATH_MAX]u8 = undefined;
17349 switch (cwd) {
17350 .inherit => {},
17351 .dir => |dir| switch (@as(std.c.E, @fromBackingInt(@intCast(
17352 std.c.posix_spawn_file_actions_addfchdir_np(&fa, dir.handle),
17353 )))) {
17354 .SUCCESS => {},
17355 .BADF => |err| return errnoBug(err),
17356 .INVAL => |err| return errnoBug(err),
17357 .NOMEM => return error.SystemResources,
17358 else => |err| return posix.unexpectedErrno(err),
17359 },
17360 .path => |path| switch (@as(std.c.E, @fromBackingInt(@intCast(
17361 std.c.posix_spawn_file_actions_addchdir_np(&fa, try pathToPosix(path, &cwd_buffer)),
17362 )))) {
17363 .SUCCESS => {},
17364 .BADF => |err| return errnoBug(err),
17365 .INVAL => |err| return errnoBug(err),
17366 .NAMETOOLONG => return error.NameTooLong,
17367 .NOMEM => return error.SystemResources,
17368 else => |err| return posix.unexpectedErrno(err),
17369 },
17370 }
17371
17372 for (inherit_dirs) |inherit_dir| switch (@as(std.c.E, @fromBackingInt(@intCast(
17373 std.c.posix_spawn_file_actions_addinherit_np(&fa, inherit_dir.handle),
17374 )))) {
17375 .SUCCESS => {},
17376 .BADF => |err| return errnoBug(err),
17377 .INVAL => |err| return errnoBug(err),
17378 .NOMEM => return error.SystemResources,
17379 else => |err| return posix.unexpectedErrno(err),
17380 };
17381 for (inherit_files) |inherit_file| switch (@as(std.c.E, @fromBackingInt(@intCast(
17382 std.c.posix_spawn_file_actions_addinherit_np(&fa, inherit_file.handle),
17383 )))) {
17384 .SUCCESS => {},
17385 .BADF => |err| return errnoBug(err),
17386 .INVAL => |err| return errnoBug(err),
17387 .NOMEM => return error.SystemResources,
17388 else => |err| return posix.unexpectedErrno(err),
17389 };
17390 if (prog_pipe[1] != -1) switch (@as(std.c.E, @fromBackingInt(@intCast(
17391 std.c.posix_spawn_file_actions_addinherit_np(&fa, prog_pipe[1]),
17392 )))) {
17393 .SUCCESS => {},
17394 .BADF => |err| return errnoBug(err),
17395 .INVAL => |err| return errnoBug(err),
17396 .NOMEM => return error.SystemResources,
17397 else => |err| return posix.unexpectedErrno(err),
17398 };
17399 for (
17400 [_]process.SpawnOptions.StdIo{ stdin, stdout, stderr },
17401 [_]posix.fd_t{ stdin_pipe[0], stdout_pipe[1], stderr_pipe[1] },
17402 [_]posix.fd_t{ posix.STDIN_FILENO, posix.STDOUT_FILENO, posix.STDERR_FILENO },
17403 ) |stdio, pipe_fd, std_fd| switch (@as(std.c.E, @fromBackingInt(@intCast(switch (stdio) {
17404 .pipe => std.c.posix_spawn_file_actions_adddup2(&fa, pipe_fd, std_fd),
17405 .close => std.c.posix_spawn_file_actions_addclose(&fa, std_fd),
17406 .inherit => std.c.posix_spawn_file_actions_addinherit_np(&fa, std_fd),
17407 .ignore => std.c.posix_spawn_file_actions_adddup2(&fa, dev_null_fd, std_fd),
17408 .file => |file| std.c.posix_spawn_file_actions_adddup2(&fa, file.handle, std_fd),
17409 })))) {
17410 .SUCCESS => {},
17411 .BADF => |err| return errnoBug(err),
17412 .INVAL => |err| return errnoBug(err),
17413 .NOMEM => return error.SystemResources,
17414 else => |err| return posix.unexpectedErrno(err),
17415 };
17416
17417 if (pgid) |pid| switch (@as(std.c.E, @fromBackingInt(@intCast(
17418 std.c.posix_spawnattr_setpgroup(&attr, pid),
17419 )))) {
17420 .SUCCESS => {},
17421 .INVAL => return error.InvalidProcessGroupId,
17422 else => |err| return posix.unexpectedErrno(err),
17423 };
17424
17425 switch (@as(std.c.E, @fromBackingInt(@intCast(std.c.posix_spawnattr_setflags(&attr, .{
17426 .SETPGROUP = pgid != null,
17427 .SETEXEC = switch (mode) {
17428 .spawn => false,
17429 .replace => true,
17430 },
17431 .START_SUSPENDED = start_suspended,
17432 }))))) {
17433 .SUCCESS => {},
17434 .INVAL => |err| return errnoBug(err),
17435 else => |err| return posix.unexpectedErrno(err),
17436 }
17437
17438 var pid: posix.pid_t = undefined;
17439 const arg0_slice = std.mem.span(argv[0].?);
17440 var path_buffer: [posix.PATH_MAX]u8 = undefined;
17441 switch (err: {
17442 break :err @as(std.c.E, @fromBackingInt(@intCast(exe: switch (exe) {
17443 .detect => if (std.mem.findScalar(u8, arg0_slice, '/')) |_|
17444 std.c.posix_spawn(&pid, arg0_slice, &fa, &attr, argv, env_block.slice.ptr)
17445 else
17446 continue :exe .search,
17447 .search => if (!Dir.path.isAbsolute(arg0_slice)) {
17448 // Use of PATH_MAX here is valid as the path_buf will be passed
17449 // directly to the operating system in posixExecveat.
17450 var path_buf: [posix.PATH_MAX]u8 = undefined;
17451 var it = std.mem.tokenizeScalar(u8, PATH, ':');
17452 var err: error{ AccessDenied, FileNotFound, NotDir } = error.FileNotFound;
17453
17454 while (it.next()) |search_path| {
17455 const path_len = search_path.len + arg0_slice.len + 1;
17456 if (path_buf.len < path_len + 1) return error.NameTooLong;
17457 @memcpy(path_buf[0..search_path.len], search_path);
17458 path_buf[search_path.len] = Dir.path.sep;
17459 @memcpy(path_buf[search_path.len + 1 ..][0..arg0_slice.len], arg0_slice);
17460 path_buf[path_len] = 0;
17461 const full_path = path_buf[0..path_len :0].ptr;
17462 switch (expand_arg0) {
17463 .expand => argv[0] = full_path,
17464 .no_expand => {},
17465 }
17466 switch (@as(std.c.E, @fromBackingInt(@intCast(
17467 std.c.posix_spawn(&pid, full_path, &fa, &attr, argv, env_block.slice.ptr),
17468 )))) {
17469 else => |e| break :err e,
17470 .ACCES, .NOENT, .NOTDIR => |e| switch (err) {
17471 error.AccessDenied => {},
17472 error.FileNotFound, error.NotDir => err = switch (e) {
17473 else => unreachable,
17474 .ACCES => error.AccessDenied,
17475 .NOENT => error.FileNotFound,
17476 .NOTDIR => error.NotDir,
17477 },
17478 },
17479 }
17480 }
17481 return err;
17482 } else std.c.posix_spawn(&pid, arg0_slice, &fa, &attr, argv, env_block.slice.ptr),
17483 .path => |path| if (path.handle != posix.AT.FDCWD and !Dir.path.isAbsolute(arg0_slice)) {
17484 var path_len = realPathPosix(path.handle, &path_buffer) catch |err| switch (err) {
17485 else => |e| return e,
17486 error.InputOutput => return error.FileNotFound,
17487 };
17488 path_buffer[path_len] = Dir.path.sep;
17489 path_len += 1;
17490 @memcpy(path_buffer[path_len..][0..arg0_slice.len], arg0_slice);
17491 path_len += arg0_slice.len;
17492 path_buffer[path_len] = 0;
17493 break :exe std.c.posix_spawn(
17494 &pid,
17495 path_buffer[0..path_len :0],
17496 &fa,
17497 &attr,
17498 argv,
17499 env_block.slice.ptr,
17500 );
17501 } else std.c.posix_spawn(&pid, arg0_slice, &fa, &attr, argv, env_block.slice.ptr),
17502 .file => |file| {
17503 const path_len = realPathPosix(file.handle, &path_buffer) catch |err| switch (err) {
17504 else => |e| return e,
17505 error.InputOutput => return error.FileNotFound,
17506 };
17507 path_buffer[path_len] = 0;
17508 break :exe std.c.posix_spawn(
17509 &pid,
17510 path_buffer[0..path_len :0],
17511 &fa,
17512 &attr,
17513 argv,
17514 env_block.slice.ptr,
17515 );
17516 },
17517 .explicit => |explicit| if (explicit.dir.handle != posix.AT.FDCWD and
17518 !Dir.path.isAbsolute(explicit.path))
17519 {
17520 var path_len = realPathPosix(
17521 explicit.dir.handle,
17522 &path_buffer,
17523 ) catch |err| switch (err) {
17524 else => |e| return e,
17525 error.InputOutput => return error.FileNotFound,
17526 };
17527 path_buffer[path_len] = Dir.path.sep;
17528 path_len += 1;
17529 @memcpy(path_buffer[path_len..][0..explicit.path.len], explicit.path);
17530 path_len += explicit.path.len;
17531 path_buffer[path_len] = 0;
17532 break :exe std.c.posix_spawn(
17533 &pid,
17534 path_buffer[0..path_len :0],
17535 &fa,
17536 &attr,
17537 argv,
17538 env_block.slice.ptr,
17539 );
17540 } else std.c.posix_spawn(
17541 &pid,
17542 try pathToPosix(explicit.path, &path_buffer),
17543 &fa,
17544 &attr,
17545 argv,
17546 env_block.slice.ptr,
17547 ),
17548 })));
17549 }) {
17550 .SUCCESS => {},
17551 .AGAIN => return error.SystemResources,
17552 .INVAL => |err| return errnoBug(err),
17553 .@"2BIG" => return error.SystemResources,
17554 .ACCES => return error.AccessDenied,
17555 .FAULT => |err| return errnoBug(err),
17556 .IO => return error.FileSystem,
17557 .LOOP => return error.FileSystem,
17558 .NAMETOOLONG => return error.NameTooLong,
17559 .NOENT => return error.FileNotFound,
17560 .NOEXEC => return error.InvalidExe,
17561 .NOMEM => return error.SystemResources,
17562 .NOTDIR => return error.NotDir,
17563 .TXTBSY => return error.FileBusy,
17564 .BADARCH => return error.InvalidExe,
17565 .BADF => |err| return errnoBug(err),
17566 else => |err| return posix.unexpectedErrno(err),
17567 }
17568
17569 errdefer comptime unreachable; // The child is spawned; we must not error from now on
17570
17571 if (stdin == .pipe) closeFd(stdin_pipe[0]);
17572 if (stdout == .pipe) closeFd(stdout_pipe[1]);
17573 if (stderr == .pipe) closeFd(stderr_pipe[1]);
17574
17575 if (prog_pipe[1] != -1) closeFd(prog_pipe[1]);
17576 progress_node.setIpcFile(t, .{ .handle = prog_pipe[0], .flags = .{ .nonblocking = true } });
17577
17578 return .{
17579 .pid = pid,
17580 .err_fd = -1,
17581 .stdin = switch (stdin) {
17582 .pipe => .{ .handle = stdin_pipe[1], .flags = .{ .nonblocking = false } },
17583 else => null,
17584 },
17585 .stdout = switch (stdout) {
17586 .pipe => .{ .handle = stdout_pipe[0], .flags = .{ .nonblocking = false } },
17587 else => null,
17588 },
17589 .stderr = switch (stderr) {
17590 .pipe => .{ .handle = stderr_pipe[0], .flags = .{ .nonblocking = false } },
17591 else => null,
17592 },
17593 };
17594}
17595
1716117596pub const CreatePipeOptions = struct {
1716217597 server: End,
1716317598 client: End,
......@@ -18469,7 +18904,7 @@ const CreateFileMapError = error{
1846918904
1847018905fn createFileMap(
1847118906 file: File,
18472 protection: std.process.MemoryProtection,
18907 protection: process.MemoryProtection,
1847318908 offset: u64,
1847418909 populate: bool,
1847518910 len: usize,
lib/std/c.zig+2
......@@ -11785,8 +11785,10 @@ pub const posix_spawn_file_actions_init = darwin.posix_spawn_file_actions_init;
1178511785pub const posix_spawn_file_actions_t = darwin.posix_spawn_file_actions_t;
1178611786pub const posix_spawnattr_destroy = darwin.posix_spawnattr_destroy;
1178711787pub const posix_spawnattr_getflags = darwin.posix_spawnattr_getflags;
11788pub const posix_spawnattr_getpgroup = darwin.posix_spawnattr_getpgroup;
1178811789pub const posix_spawnattr_init = darwin.posix_spawnattr_init;
1178911790pub const posix_spawnattr_setflags = darwin.posix_spawnattr_setflags;
11791pub const posix_spawnattr_setpgroup = darwin.posix_spawnattr_setpgroup;
1179011792pub const posix_spawnattr_t = darwin.posix_spawnattr_t;
1179111793pub const posix_spawnp = darwin.posix_spawnp;
1179211794pub const pthread_attr_get_qos_class_np = darwin.pthread_attr_get_qos_class_np;
lib/std/c/darwin.zig+3-1
......@@ -1253,8 +1253,10 @@ pub const posix_spawnattr_t = *opaque {};
12531253pub const posix_spawn_file_actions_t = *opaque {};
12541254pub extern "c" fn posix_spawnattr_init(attr: *posix_spawnattr_t) c_int;
12551255pub extern "c" fn posix_spawnattr_destroy(attr: *posix_spawnattr_t) c_int;
1256pub extern "c" fn posix_spawnattr_setflags(attr: *posix_spawnattr_t, flags: POSIX_SPAWN) c_int;
12571256pub extern "c" fn posix_spawnattr_getflags(attr: *const posix_spawnattr_t, flags: *POSIX_SPAWN) c_int;
1257pub extern "c" fn posix_spawnattr_getpgroup(attr: *const posix_spawnattr_t, pgroup: *pid_t) c_int;
1258pub extern "c" fn posix_spawnattr_setflags(attr: *posix_spawnattr_t, flags: POSIX_SPAWN) c_int;
1259pub extern "c" fn posix_spawnattr_setpgroup(attr: *posix_spawnattr_t, pgroup: pid_t) c_int;
12581260pub extern "c" fn posix_spawn_file_actions_init(actions: *posix_spawn_file_actions_t) c_int;
12591261pub extern "c" fn posix_spawn_file_actions_destroy(actions: *posix_spawn_file_actions_t) c_int;
12601262pub extern "c" fn posix_spawn_file_actions_addclose(actions: *posix_spawn_file_actions_t, filedes: fd_t) c_int;
lib/std/process.zig+11-1
......@@ -284,10 +284,20 @@ pub const ReplaceError = error{
284284pub const ReplaceOptions = struct {
285285 exe: Exe = .detect,
286286 argv: []const []const u8,
287 expand_arg0: ArgExpansion = .no_expand,
287
288 /// Set to change the current working directory when spawning the child process.
289 cwd: Child.Cwd = .inherit,
288290 /// Replaces the environment when provided. The PATH value from here is
289291 /// never used to resolve `argv[0]`.
290292 environ_map: ?*const Environ.Map = null,
293 expand_arg0: ArgExpansion = .no_expand,
294
295 inherit_dirs: []const Dir = &.{},
296 inherit_files: []const File = &.{},
297
298 /// Start child process in suspended state.
299 /// For Posix systems it's started as if SIGSTOP was sent.
300 start_suspended: bool = false,
291301
292302 pub const Exe = union(enum) {
293303 /// `argv[0]` is the name of the program to execute. If it is not already a
test/src/Runner.zig+33-22
......@@ -26,6 +26,7 @@ const Args = struct {
2626 enable_wine: bool,
2727 enable_wasmtime: bool,
2828 enable_darling: bool,
29 quiet: bool,
2930
3031 fn deinit(args: *Args, gpa: std.mem.Allocator) void {
3132 args.targets.deinit(gpa);
......@@ -98,6 +99,8 @@ pub fn runServer(runner: *Runner) ProtocolError {
9899 runner.args.enable_wasmtime = true;
99100 } else if (std.mem.eql(u8, string, "-fdarling")) {
100101 runner.args.enable_darling = true;
102 } else if (std.mem.eql(u8, string, "--quiet")) {
103 runner.args.quiet = true;
101104 },
102105 .zig => unreachable,
103106 .lib => unreachable,
......@@ -463,10 +466,10 @@ fn testOne(runner: *Runner, test_index: u32) TestError!void {
463466 .target_query = target_query,
464467 .manifest_sr = .init(&manifest_fr.interface, runner.gpa, &runner.eb_wip, src_path_string: {
465468 var path_buffer: [std.Io.Dir.max_path_bytes]u8 = undefined;
469
466470 const src_path = src_path: {
467 break :src_path path_buffer[0 .. std.Io.Dir.readLinkAbsolute(
471 break :src_path path_buffer[0 .. manifest_file.realPath(
468472 runner.io,
469 try runner.arena.allocator().print("/proc/self/fd/{d}", .{manifest_file.handle}),
470473 &path_buffer,
471474 ) catch |err| switch (err) {
472475 error.Canceled => |e| return e,
......@@ -839,12 +842,9 @@ fn spawnCompiler(
839842 .selfhosted => try argv.append(gpa, "-fno-llvm"),
840843 .llvm => try argv.append(gpa, "-fllvm"),
841844 .cbe => try argv.append(gpa, "-ofmt=c"),
842 } else try argv.appendSlice(gpa, &.{ "-I", path_buffer[0 .. std.Io.Dir.readLinkAbsolute(
843 runner.io,
844 try arena.print("/proc/self/fd/{d}", .{(runner.args.lib_dir orelse
845 return runner.fail("\"--lib=path/to/lib\" arg required with cbe", .{})).handle}),
846 &path_buffer,
847 ) catch |err| switch (err) {
845 } else try argv.appendSlice(gpa, &.{ "-I", path_buffer[0 .. (runner.args.lib_dir orelse
846 return runner.fail("\"--lib=path/to/lib\" arg required with cbe", .{}))
847 .realPath(runner.io, &path_buffer) catch |err| switch (err) {
848848 error.Canceled => |e| return e,
849849 else => |e| return runner.fail("unable to get lib path: {t}", .{e}),
850850 }] });
......@@ -1034,8 +1034,8 @@ const Compiler = struct {
10341034 )) {
10351035 .bad_dl, .bad_os_or_cpu => {
10361036 // This binary cannot be executed on this host.
1037 std.log.warn("skipping execution because host '{s}' cannot execute binaries for " ++
1038 "foreign target '{s}'", .{
1037 if (!runner.args.quiet) std.log.warn("skipping execution because host {q} cannot " ++
1038 "execute binaries for foreign target {q}", .{
10391039 try runner.host.zigTriple(arena), comp.target.triple,
10401040 });
10411041 return;
......@@ -1080,12 +1080,12 @@ const Compiler = struct {
10801080 .progress_node = run_prog_node,
10811081 }) catch |err| if (executor) |_| {
10821082 // Chances are the foreign executor isn't available. Skip this evaluation.
1083 std.log.warn(
1084 "skipping execution of '{s}' via executor for foreign target '{s}': {t}",
1083 if (!runner.args.quiet) std.log.warn(
1084 "skipping execution of {q} via executor for foreign target {q}: {t}",
10851085 .{ bin_path, comp.target.triple, err },
10861086 );
10871087 return;
1088 } else return runner.fail("unable to run the generated executable '{s}': {t}", .{
1088 } else return runner.fail("unable to run the generated executable {q}: {t}", .{
10891089 bin_path, err,
10901090 });
10911091 defer {
......@@ -1097,7 +1097,7 @@ const Compiler = struct {
10971097 .errors, .lldb => unreachable,
10981098 .stdout => {
10991099 if (code != 0) return runner.fail(
1100 "generated executable '{s}' failed with code {d}",
1100 "generated executable {q} failed with code {d}",
11011101 .{ bin_path, code },
11021102 );
11031103 std.testing.expectEqualStrings(
......@@ -1125,20 +1125,20 @@ const Compiler = struct {
11251125 },
11261126 },
11271127 .signal => |sig| return runner.fail(
1128 "generated executable '{s}' terminated with signal {t}",
1128 "generated executable {q} terminated with signal {t}",
11291129 .{ bin_path, sig },
11301130 ),
11311131 .stopped => |sig| return runner.fail(
1132 "generated executable '{s}' stopped with signal {t}",
1132 "generated executable {q} stopped with signal {t}",
11331133 .{ bin_path, sig },
11341134 ),
11351135 .unknown => return runner.fail(
1136 "generated executable '{s}' terminated unexpectedly",
1136 "generated executable {q} terminated unexpectedly",
11371137 .{bin_path},
11381138 ),
11391139 }
11401140 if (executor == null and result.stderr.len > 0) {
1141 std.log.err("generated executable '{s}' had unexpected stderr:\n{s}", .{
1141 std.log.err("generated executable {q} had unexpected stderr:\n{s}", .{
11421142 bin_path, result.stderr,
11431143 });
11441144 }
......@@ -1247,6 +1247,7 @@ const ClientArgs = struct {
12471247 enable_wine: bool,
12481248 enable_wasmtime: bool,
12491249 enable_darling: bool,
1250 quiet: bool,
12501251};
12511252pub fn main(init: std.process.Init) (std.mem.Allocator.Error || std.Io.Cancelable)!u8 {
12521253 const fatal = struct {
......@@ -1272,6 +1273,7 @@ pub fn main(init: std.process.Init) (std.mem.Allocator.Error || std.Io.Cancelabl
12721273 .enable_wine = false,
12731274 .enable_wasmtime = false,
12741275 .enable_darling = false,
1276 .quiet = false,
12751277 };
12761278 defer args.targets.deinit(init.gpa);
12771279
......@@ -1300,6 +1302,7 @@ pub fn main(init: std.process.Init) (std.mem.Allocator.Error || std.Io.Cancelabl
13001302 .enable_wine = false,
13011303 .enable_wasmtime = false,
13021304 .enable_darling = false,
1305 .quiet = false,
13031306 },
13041307 .host = std.zig.system.resolveTargetQuery(init.io, .{}) catch |err| switch (err) {
13051308 error.Canceled => |e| return e,
......@@ -1328,17 +1331,17 @@ pub fn main(init: std.process.Init) (std.mem.Allocator.Error || std.Io.Cancelabl
13281331 },
13291332 }
13301333 } else if (std.mem.eql(u8, arg, "--zig")) {
1331 zig_path_arg = arg_it.next() orelse fatal("missing arg after '{s}'", .{arg});
1334 zig_path_arg = arg_it.next() orelse fatal("missing arg after {q}", .{arg});
13321335 } else if (std.mem.cutPrefix(u8, arg, "--zig=")) |zig_path| {
13331336 zig_path_arg = zig_path;
13341337 } else if (std.mem.eql(u8, arg, "--lib")) {
1335 lib_path_arg = arg_it.next() orelse fatal("missing arg after '{s}'", .{arg});
1338 lib_path_arg = arg_it.next() orelse fatal("missing arg after {q}", .{arg});
13361339 } else if (std.mem.cutPrefix(u8, arg, "--lib=")) |lib_path| {
13371340 lib_path_arg = lib_path;
13381341 } else if (std.mem.eql(u8, arg, "--target")) {
13391342 try args.targets.append(
13401343 init.gpa,
1341 try arena.dupe(u8, arg_it.next() orelse fatal("missing arg after '{s}'", .{arg})),
1344 try arena.dupe(u8, arg_it.next() orelse fatal("missing arg after {q}", .{arg})),
13421345 );
13431346 } else if (std.mem.cutPrefix(u8, arg, "--target=")) |target| {
13441347 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
13521355 args.enable_wasmtime = true;
13531356 } else if (std.mem.eql(u8, arg, "-fdarling")) {
13541357 args.enable_darling = true;
1358 } else if (std.mem.eql(u8, arg, "--quiet")) {
1359 args.quiet = true;
13551360 } else {
1356 if (test_path_arg) |_| fatal("unknown arg '{s}'", .{arg});
1361 if (test_path_arg) |_| fatal("unknown arg {q}", .{arg});
13571362 test_path_arg = arg;
13581363 }
13591364 }
......@@ -1509,6 +1514,12 @@ fn runClient(
15091514 try argv.append(gpa, 0);
15101515 }
15111516
1517 if (args.quiet) {
1518 try argv.append(gpa, @backingInt(Arg.string));
1519 try argv.appendSlice(gpa, "--quiet");
1520 try argv.append(gpa, 0);
1521 }
1522
15121523 try client.serveMessageHeader(.{
15131524 .tag = .args,
15141525 .bytes_len = @intCast(argv.items.len),
test/tests.zig+2
......@@ -3417,6 +3417,8 @@ pub fn addIncrementalTests(
34173417 run.addThirdPartyEnabledArgWasmtime(.{ .enabled = "-fwasmtime" });
34183418 run.addThirdPartyEnabledArgWine(.{ .enabled = "-fwine" });
34193419
3420 run.addArg("--quiet"); // don't fill stderr telling us about skipped tests etc
3421
34203422 tests_step.dependOn(&run.step);
34213423 }
34223424