diff --git a/CMakeLists.txt b/CMakeLists.txt index 457c5297cb..41ee4a8f79 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -467,6 +467,7 @@ set(ZIG_STAGE2_SOURCES "${CMAKE_SOURCE_DIR}/lib/std/os/linux.zig" "${CMAKE_SOURCE_DIR}/lib/std/os/linux/io_uring.zig" "${CMAKE_SOURCE_DIR}/lib/std/os/linux/x86_64.zig" + "${CMAKE_SOURCE_DIR}/lib/std/os/posix_spawn.zig" "${CMAKE_SOURCE_DIR}/lib/std/os/windows.zig" "${CMAKE_SOURCE_DIR}/lib/std/os/windows/ntstatus.zig" "${CMAKE_SOURCE_DIR}/lib/std/os/windows/win32error.zig" diff --git a/lib/std/build.zig b/lib/std/build.zig index 5dcd0e513f..4df9e7bf6e 100644 --- a/lib/std/build.zig +++ b/lib/std/build.zig @@ -1321,6 +1321,7 @@ pub const Builder = struct { error.FileNotFound => error.PkgConfigNotInstalled, error.InvalidName => error.PkgConfigNotInstalled, error.PkgConfigInvalidOutput => error.PkgConfigInvalidOutput, + error.ChildExecFailed => error.PkgConfigFailed, else => return err, }; self.pkg_config_pkg_list = result; @@ -1963,6 +1964,7 @@ pub const LibExeObjStep = struct { error.ExecNotSupported => return error.PkgConfigFailed, error.ExitCodeFailure => return error.PkgConfigFailed, error.FileNotFound => return error.PkgConfigNotInstalled, + error.ChildExecFailed => return error.PkgConfigFailed, else => return err, }; var it = mem.tokenize(u8, stdout, " \r\n\t"); diff --git a/lib/std/c/darwin.zig b/lib/std/c/darwin.zig index a986f5337e..3c133caadb 100644 --- a/lib/std/c/darwin.zig +++ b/lib/std/c/darwin.zig @@ -78,15 +78,40 @@ 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) void; pub extern "c" fn posix_spawnattr_setflags(attr: *posix_spawnattr_t, flags: c_short) c_int; +pub extern "c" fn posix_spawnattr_getflags(attr: *const posix_spawnattr_t, flags: *c_short) 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) void; +pub extern "c" fn posix_spawn_file_actions_addclose(actions: *posix_spawn_file_actions_t, filedes: fd_t) c_int; +pub extern "c" fn posix_spawn_file_actions_addopen( + actions: *posix_spawn_file_actions_t, + filedes: fd_t, + path: [*:0]const u8, + oflag: c_int, + mode: mode_t, +) c_int; +pub extern "c" fn posix_spawn_file_actions_adddup2( + actions: *posix_spawn_file_actions_t, + filedes: fd_t, + newfiledes: fd_t, +) c_int; +pub extern "c" fn posix_spawn_file_actions_addinherit_np(actions: *posix_spawn_file_actions_t, filedes: fd_t) c_int; +pub extern "c" fn posix_spawn_file_actions_addchdir_np(actions: *posix_spawn_file_actions_t, path: [*:0]const u8) c_int; +pub extern "c" fn posix_spawn_file_actions_addfchdir_np(actions: *posix_spawn_file_actions_t, filedes: fd_t) c_int; +pub extern "c" fn posix_spawn( + pid: *pid_t, + path: [*:0]const u8, + actions: ?*const posix_spawn_file_actions_t, + attr: ?*const posix_spawnattr_t, + argv: [*:null]?[*:0]const u8, + env: [*:null]?[*:0]const u8, +) c_int; pub extern "c" fn posix_spawnp( pid: *pid_t, path: [*:0]const u8, actions: ?*const posix_spawn_file_actions_t, - attr: *const posix_spawnattr_t, - argv: [*][*:0]const u8, - env: [*][*:0]const u8, + attr: ?*const posix_spawnattr_t, + argv: [*:null]?[*:0]const u8, + env: [*:null]?[*:0]const u8, ) c_int; pub extern "c" fn kevent64( @@ -2563,16 +2588,16 @@ pub const CPUFAMILY = enum(u32) { _, }; -pub const POSIX_SPAWN_RESETIDS: c_int = 0x0001; -pub const POSIX_SPAWN_SETPGROUP: c_int = 0x0002; -pub const POSIX_SPAWN_SETSIGDEF: c_int = 0x0004; -pub const POSIX_SPAWN_SETSIGMASK: c_int = 0x0008; -pub const POSIX_SPAWN_SETEXEC: c_int = 0x0040; -pub const POSIX_SPAWN_START_SUSPENDED: c_int = 0x0080; -pub const _POSIX_SPAWN_DISABLE_ASLR: c_int = 0x0100; -pub const POSIX_SPAWN_SETSID: c_int = 0x0400; -pub const _POSIX_SPAWN_RESLIDE: c_int = 0x0800; -pub const POSIX_SPAWN_CLOEXEC_DEFAULT: c_int = 0x4000; +pub const POSIX_SPAWN_RESETIDS = 0x0001; +pub const POSIX_SPAWN_SETPGROUP = 0x0002; +pub const POSIX_SPAWN_SETSIGDEF = 0x0004; +pub const POSIX_SPAWN_SETSIGMASK = 0x0008; +pub const POSIX_SPAWN_SETEXEC = 0x0040; +pub const POSIX_SPAWN_START_SUSPENDED = 0x0080; +pub const _POSIX_SPAWN_DISABLE_ASLR = 0x0100; +pub const POSIX_SPAWN_SETSID = 0x0400; +pub const _POSIX_SPAWN_RESLIDE = 0x0800; +pub const POSIX_SPAWN_CLOEXEC_DEFAULT = 0x4000; pub const PT_TRACE_ME = 0; pub const PT_CONTINUE = 7; diff --git a/lib/std/child_process.zig b/lib/std/child_process.zig index 2204b6526f..cc211a69a1 100644 --- a/lib/std/child_process.zig +++ b/lib/std/child_process.zig @@ -53,10 +53,13 @@ pub const ChildProcess = struct { /// Once that is done, `cwd` will be deprecated in favor of this field. cwd_dir: ?fs.Dir = null, - err_pipe: if (builtin.os.tag == .windows) void else [2]os.fd_t, + err_pipe: ?if (builtin.os.tag == .windows) void else [2]os.fd_t, expand_arg0: Arg0Expand, + /// Darwin-only. Disable ASLR for the child process. + disable_aslr: bool = false, + pub const Arg0Expand = os.Arg0Expand; pub const SpawnError = error{ @@ -72,7 +75,13 @@ pub const ChildProcess = struct { /// Windows-only. `cwd` was provided, but the path did not exist when spawning the child process. CurrentWorkingDirectoryUnlinked, - } || os.ExecveError || os.SetIdError || os.ChangeCurDirError || windows.CreateProcessError || windows.WaitForSingleObjectError; + } || + os.ExecveError || + os.SetIdError || + os.ChangeCurDirError || + windows.CreateProcessError || + windows.WaitForSingleObjectError || + os.posix_spawn.Error; pub const Term = union(enum) { Exited: u8, @@ -98,7 +107,7 @@ pub const ChildProcess = struct { .pid = undefined, .handle = undefined, .thread_handle = undefined, - .err_pipe = undefined, + .err_pipe = null, .term = null, .env_map = null, .cwd = null, @@ -128,6 +137,10 @@ pub const ChildProcess = struct { @compileError("the target operating system cannot spawn processes"); } + if (comptime builtin.target.isDarwin()) { + return self.spawnMacos(); + } + if (builtin.os.tag == .windows) { return self.spawnWindows(); } else { @@ -166,7 +179,7 @@ pub const ChildProcess = struct { return term; } try os.kill(self.pid, os.SIG.TERM); - self.waitUnwrapped(); + try self.waitUnwrapped(); return self.term.?; } @@ -435,7 +448,7 @@ pub const ChildProcess = struct { return term; } - self.waitUnwrapped(); + try self.waitUnwrapped(); return self.term.?; } @@ -461,8 +474,12 @@ pub const ChildProcess = struct { return result; } - fn waitUnwrapped(self: *ChildProcess) void { - const status = os.waitpid(self.pid, 0).status; + fn waitUnwrapped(self: *ChildProcess) !void { + const res: os.WaitPidResult = if (comptime builtin.target.isDarwin()) + try os.posix_spawn.waitpid(self.pid, 0) + else + os.waitpid(self.pid, 0); + const status = res.status; self.cleanupStreams(); self.handleWaitResult(status); } @@ -487,37 +504,39 @@ pub const ChildProcess = struct { } fn cleanupAfterWait(self: *ChildProcess, status: u32) !Term { - defer destroyPipe(self.err_pipe); + if (self.err_pipe) |err_pipe| { + defer destroyPipe(err_pipe); - if (builtin.os.tag == .linux) { - var fd = [1]std.os.pollfd{std.os.pollfd{ - .fd = self.err_pipe[0], - .events = std.os.POLL.IN, - .revents = undefined, - }}; + if (builtin.os.tag == .linux) { + var fd = [1]std.os.pollfd{std.os.pollfd{ + .fd = err_pipe[0], + .events = std.os.POLL.IN, + .revents = undefined, + }}; - // Check if the eventfd buffer stores a non-zero value by polling - // it, that's the error code returned by the child process. - _ = std.os.poll(&fd, 0) catch unreachable; + // Check if the eventfd buffer stores a non-zero value by polling + // it, that's the error code returned by the child process. + _ = std.os.poll(&fd, 0) catch unreachable; - // According to eventfd(2) the descriptro is readable if the counter - // has a value greater than 0 - if ((fd[0].revents & std.os.POLL.IN) != 0) { - const err_int = try readIntFd(self.err_pipe[0]); - return @errSetCast(SpawnError, @intToError(err_int)); - } - } else { - // Write maxInt(ErrInt) to the write end of the err_pipe. This is after - // waitpid, so this write is guaranteed to be after the child - // pid potentially wrote an error. This way we can do a blocking - // read on the error pipe and either get maxInt(ErrInt) (no error) or - // an error code. - try writeIntFd(self.err_pipe[1], maxInt(ErrInt)); - const err_int = try readIntFd(self.err_pipe[0]); - // Here we potentially return the fork child's error from the parent - // pid. - if (err_int != maxInt(ErrInt)) { - return @errSetCast(SpawnError, @intToError(err_int)); + // According to eventfd(2) the descriptro is readable if the counter + // has a value greater than 0 + if ((fd[0].revents & std.os.POLL.IN) != 0) { + const err_int = try readIntFd(err_pipe[0]); + return @errSetCast(SpawnError, @intToError(err_int)); + } + } else { + // Write maxInt(ErrInt) to the write end of the err_pipe. This is after + // waitpid, so this write is guaranteed to be after the child + // pid potentially wrote an error. This way we can do a blocking + // read on the error pipe and either get maxInt(ErrInt) (no error) or + // an error code. + try writeIntFd(err_pipe[1], maxInt(ErrInt)); + const err_int = try readIntFd(err_pipe[0]); + // Here we potentially return the fork child's error from the parent + // pid. + if (err_int != maxInt(ErrInt)) { + return @errSetCast(SpawnError, @intToError(err_int)); + } } } @@ -535,6 +554,114 @@ pub const ChildProcess = struct { Term{ .Unknown = status }; } + fn spawnMacos(self: *ChildProcess) SpawnError!void { + const pipe_flags = if (io.is_async) os.O.NONBLOCK else 0; + const stdin_pipe = if (self.stdin_behavior == StdIo.Pipe) try os.pipe2(pipe_flags) else undefined; + errdefer if (self.stdin_behavior == StdIo.Pipe) destroyPipe(stdin_pipe); + + const stdout_pipe = if (self.stdout_behavior == StdIo.Pipe) try os.pipe2(pipe_flags) else undefined; + errdefer if (self.stdout_behavior == StdIo.Pipe) destroyPipe(stdout_pipe); + + const stderr_pipe = if (self.stderr_behavior == StdIo.Pipe) try os.pipe2(pipe_flags) else undefined; + errdefer if (self.stderr_behavior == StdIo.Pipe) destroyPipe(stderr_pipe); + + const any_ignore = (self.stdin_behavior == StdIo.Ignore or self.stdout_behavior == StdIo.Ignore or self.stderr_behavior == StdIo.Ignore); + const dev_null_fd = if (any_ignore) + os.openZ("/dev/null", os.O.RDWR, 0) catch |err| switch (err) { + error.PathAlreadyExists => unreachable, + error.NoSpaceLeft => unreachable, + error.FileTooBig => unreachable, + error.DeviceBusy => unreachable, + error.FileLocksNotSupported => unreachable, + error.BadPathName => unreachable, // Windows-only + error.InvalidHandle => unreachable, // WASI-only + error.WouldBlock => unreachable, + else => |e| return e, + } + else + undefined; + defer if (any_ignore) os.close(dev_null_fd); + + var attr = try os.posix_spawn.Attr.init(); + defer attr.deinit(); + var flags: u16 = os.darwin.POSIX_SPAWN_SETSIGDEF | os.darwin.POSIX_SPAWN_SETSIGMASK; + if (self.disable_aslr) { + flags |= os.darwin._POSIX_SPAWN_DISABLE_ASLR; + } + try attr.set(flags); + + var actions = try os.posix_spawn.Actions.init(); + defer actions.deinit(); + + try setUpChildIoPosixSpawn(self.stdin_behavior, &actions, stdin_pipe[0], os.STDIN_FILENO, dev_null_fd); + try setUpChildIoPosixSpawn(self.stdout_behavior, &actions, stdout_pipe[1], os.STDOUT_FILENO, dev_null_fd); + try setUpChildIoPosixSpawn(self.stderr_behavior, &actions, stderr_pipe[1], os.STDERR_FILENO, dev_null_fd); + + if (self.cwd_dir) |cwd| { + try actions.fchdir(cwd.fd); + } else if (self.cwd) |cwd| { + try actions.chdir(cwd); + } + + var arena_allocator = std.heap.ArenaAllocator.init(self.allocator); + defer arena_allocator.deinit(); + const arena = arena_allocator.allocator(); + + const argv_buf = try arena.allocSentinel(?[*:0]u8, self.argv.len, null); + for (self.argv) |arg, i| argv_buf[i] = (try arena.dupeZ(u8, arg)).ptr; + + const envp = if (self.env_map) |env_map| m: { + const envp_buf = try createNullDelimitedEnvMap(arena, env_map); + break :m envp_buf.ptr; + } else std.c.environ; + + const pid = try os.posix_spawn.spawnp(self.argv[0], actions, attr, argv_buf, envp); + + if (self.stdin_behavior == StdIo.Pipe) { + self.stdin = File{ .handle = stdin_pipe[1] }; + } else { + self.stdin = null; + } + if (self.stdout_behavior == StdIo.Pipe) { + self.stdout = File{ .handle = stdout_pipe[0] }; + } else { + self.stdout = null; + } + if (self.stderr_behavior == StdIo.Pipe) { + self.stderr = File{ .handle = stderr_pipe[0] }; + } else { + self.stderr = null; + } + + self.pid = pid; + self.term = null; + + if (self.stdin_behavior == StdIo.Pipe) { + os.close(stdin_pipe[0]); + } + if (self.stdout_behavior == StdIo.Pipe) { + os.close(stdout_pipe[1]); + } + if (self.stderr_behavior == StdIo.Pipe) { + os.close(stderr_pipe[1]); + } + } + + fn setUpChildIoPosixSpawn( + stdio: StdIo, + actions: *os.posix_spawn.Actions, + pipe_fd: i32, + std_fileno: i32, + dev_null_fd: i32, + ) !void { + switch (stdio) { + .Pipe => try actions.dup2(pipe_fd, std_fileno), + .Close => try actions.close(std_fileno), + .Inherit => {}, + .Ignore => try actions.dup2(dev_null_fd, std_fileno), + } + } + fn spawnPosix(self: *ChildProcess) SpawnError!void { const pipe_flags = if (io.is_async) os.O.NONBLOCK else 0; const stdin_pipe = if (self.stdin_behavior == StdIo.Pipe) try os.pipe2(pipe_flags) else undefined; diff --git a/lib/std/os.zig b/lib/std/os.zig index ae3128654d..0fabc9bff5 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -41,6 +41,7 @@ pub const plan9 = @import("os/plan9.zig"); pub const uefi = @import("os/uefi.zig"); pub const wasi = @import("os/wasi.zig"); pub const windows = @import("os/windows.zig"); +pub const posix_spawn = @import("os/posix_spawn.zig"); comptime { assert(@import("std") == std); // std lib tests require --zig-lib-dir @@ -52,6 +53,7 @@ test { _ = uefi; _ = wasi; _ = windows; + _ = posix_spawn; _ = @import("os/test.zig"); } @@ -4037,6 +4039,9 @@ pub const WaitPidResult = struct { status: u32, }; +/// Use this version of the `waitpid` wrapper if you spawned your child process using explicit +/// `fork` and `execve` method. If you spawned your child process using `posix_spawn` method, +/// use `std.os.posix_spawn.waitpid` instead. pub fn waitpid(pid: pid_t, flags: u32) WaitPidResult { const Status = if (builtin.link_libc) c_int else u32; var status: Status = undefined; diff --git a/lib/std/os/posix_spawn.zig b/lib/std/os/posix_spawn.zig new file mode 100644 index 0000000000..d36475df7f --- /dev/null +++ b/lib/std/os/posix_spawn.zig @@ -0,0 +1,282 @@ +const std = @import("std"); +const builtin = @import("builtin"); + +const os = @import("../os.zig"); +const system = os.system; +const errno = system.getErrno; +const fd_t = system.fd_t; +const mode_t = system.mode_t; +const pid_t = system.pid_t; +const unexpectedErrno = os.unexpectedErrno; +const UnexpectedError = os.UnexpectedError; +const toPosixPath = os.toPosixPath; +const WaitPidResult = os.WaitPidResult; + +pub usingnamespace posix_spawn; + +pub const Error = error{ + SystemResources, + InvalidFileDescriptor, + NameTooLong, + TooBig, + PermissionDenied, + InputOutput, + FileSystem, + FileNotFound, + InvalidExe, + NotDir, + FileBusy, + + /// Returned when the child fails to execute either in the pre-exec() initialization step, or + /// when exec(3) is invoked. + ChildExecFailed, +} || UnexpectedError; + +const posix_spawn = if (builtin.target.isDarwin()) struct { + pub const Attr = struct { + attr: system.posix_spawnattr_t, + + pub fn init() Error!Attr { + var attr: system.posix_spawnattr_t = undefined; + switch (errno(system.posix_spawnattr_init(&attr))) { + .SUCCESS => return Attr{ .attr = attr }, + .NOMEM => return error.SystemResources, + .INVAL => unreachable, + else => |err| return unexpectedErrno(err), + } + } + + pub fn deinit(self: *Attr) void { + system.posix_spawnattr_destroy(&self.attr); + self.* = undefined; + } + + pub fn get(self: Attr) Error!u16 { + var flags: c_short = undefined; + switch (errno(system.posix_spawnattr_getflags(&self.attr, &flags))) { + .SUCCESS => return @bitCast(u16, flags), + .INVAL => unreachable, + else => |err| return unexpectedErrno(err), + } + } + + pub fn set(self: *Attr, flags: u16) Error!void { + switch (errno(system.posix_spawnattr_setflags(&self.attr, @bitCast(c_short, flags)))) { + .SUCCESS => return, + .INVAL => unreachable, + else => |err| return unexpectedErrno(err), + } + } + }; + + pub const Actions = struct { + actions: system.posix_spawn_file_actions_t, + + pub fn init() Error!Actions { + var actions: system.posix_spawn_file_actions_t = undefined; + switch (errno(system.posix_spawn_file_actions_init(&actions))) { + .SUCCESS => return Actions{ .actions = actions }, + .NOMEM => return error.SystemResources, + .INVAL => unreachable, + else => |err| return unexpectedErrno(err), + } + } + + pub fn deinit(self: *Actions) void { + system.posix_spawn_file_actions_destroy(&self.actions); + self.* = undefined; + } + + pub fn open(self: *Actions, fd: fd_t, path: []const u8, flags: u32, mode: mode_t) Error!void { + const posix_path = try toPosixPath(path); + return self.openZ(fd, &posix_path, flags, mode); + } + + pub fn openZ(self: *Actions, fd: fd_t, path: [*:0]const u8, flags: u32, mode: mode_t) Error!void { + switch (errno(system.posix_spawn_file_actions_addopen(&self.actions, fd, path, @bitCast(c_int, flags), mode))) { + .SUCCESS => return, + .BADF => return error.InvalidFileDescriptor, + .NOMEM => return error.SystemResources, + .NAMETOOLONG => return error.NameTooLong, + .INVAL => unreachable, // the value of file actions is invalid + else => |err| return unexpectedErrno(err), + } + } + + pub fn close(self: *Actions, fd: fd_t) Error!void { + switch (errno(system.posix_spawn_file_actions_addclose(&self.actions, fd))) { + .SUCCESS => return, + .BADF => return error.InvalidFileDescriptor, + .NOMEM => return error.SystemResources, + .INVAL => unreachable, // the value of file actions is invalid + .NAMETOOLONG => unreachable, + else => |err| return unexpectedErrno(err), + } + } + + pub fn dup2(self: *Actions, fd: fd_t, newfd: fd_t) Error!void { + switch (errno(system.posix_spawn_file_actions_adddup2(&self.actions, fd, newfd))) { + .SUCCESS => return, + .BADF => return error.InvalidFileDescriptor, + .NOMEM => return error.SystemResources, + .INVAL => unreachable, // the value of file actions is invalid + .NAMETOOLONG => unreachable, + else => |err| return unexpectedErrno(err), + } + } + + pub fn inherit(self: *Actions, fd: fd_t) Error!void { + switch (errno(system.posix_spawn_file_actions_addinherit_np(&self.actions, fd))) { + .SUCCESS => return, + .BADF => return error.InvalidFileDescriptor, + .NOMEM => return error.SystemResources, + .INVAL => unreachable, // the value of file actions is invalid + .NAMETOOLONG => unreachable, + else => |err| return unexpectedErrno(err), + } + } + + pub fn chdir(self: *Actions, path: []const u8) Error!void { + const posix_path = try toPosixPath(path); + return self.chdirZ(&posix_path); + } + + pub fn chdirZ(self: *Actions, path: [*:0]const u8) Error!void { + switch (errno(system.posix_spawn_file_actions_addchdir_np(&self.actions, path))) { + .SUCCESS => return, + .NOMEM => return error.SystemResources, + .NAMETOOLONG => return error.NameTooLong, + .BADF => unreachable, + .INVAL => unreachable, // the value of file actions is invalid + else => |err| return unexpectedErrno(err), + } + } + + pub fn fchdir(self: *Actions, fd: fd_t) Error!void { + switch (errno(system.posix_spawn_file_actions_addfchdir_np(&self.actions, fd))) { + .SUCCESS => return, + .BADF => return error.InvalidFileDescriptor, + .NOMEM => return error.SystemResources, + .INVAL => unreachable, // the value of file actions is invalid + .NAMETOOLONG => unreachable, + else => |err| return unexpectedErrno(err), + } + } + }; + + pub fn spawn( + path: []const u8, + actions: ?Actions, + attr: ?Attr, + argv: [*:null]?[*:0]const u8, + envp: [*:null]?[*:0]const u8, + ) Error!pid_t { + const posix_path = try toPosixPath(path); + return spawnZ(&posix_path, actions, attr, argv, envp); + } + + pub fn spawnZ( + path: [*:0]const u8, + actions: ?Actions, + attr: ?Attr, + argv: [*:null]?[*:0]const u8, + envp: [*:null]?[*:0]const u8, + ) Error!pid_t { + var pid: pid_t = undefined; + switch (errno(system.posix_spawn( + &pid, + path, + if (actions) |a| &a.actions else null, + if (attr) |a| &a.attr else null, + argv, + envp, + ))) { + .SUCCESS => return pid, + .@"2BIG" => return error.TooBig, + .NOMEM => return error.SystemResources, + .BADF => return error.InvalidFileDescriptor, + .ACCES => return error.PermissionDenied, + .IO => return error.InputOutput, + .LOOP => return error.FileSystem, + .NAMETOOLONG => return error.NameTooLong, + .NOENT => return error.FileNotFound, + .NOEXEC => return error.InvalidExe, + .NOTDIR => return error.NotDir, + .TXTBSY => return error.FileBusy, + .BADARCH => return error.InvalidExe, + .BADEXEC => return error.InvalidExe, + .FAULT => unreachable, + .INVAL => unreachable, + else => |err| return unexpectedErrno(err), + } + } + + pub fn spawnp( + file: []const u8, + actions: ?Actions, + attr: ?Attr, + argv: [*:null]?[*:0]const u8, + envp: [*:null]?[*:0]const u8, + ) Error!pid_t { + const posix_file = try toPosixPath(file); + return spawnpZ(&posix_file, actions, attr, argv, envp); + } + + pub fn spawnpZ( + file: [*:0]const u8, + actions: ?Actions, + attr: ?Attr, + argv: [*:null]?[*:0]const u8, + envp: [*:null]?[*:0]const u8, + ) Error!pid_t { + var pid: pid_t = undefined; + switch (errno(system.posix_spawnp( + &pid, + file, + if (actions) |a| &a.actions else null, + if (attr) |a| &a.attr else null, + argv, + envp, + ))) { + .SUCCESS => return pid, + .@"2BIG" => return error.TooBig, + .NOMEM => return error.SystemResources, + .BADF => return error.InvalidFileDescriptor, + .ACCES => return error.PermissionDenied, + .IO => return error.InputOutput, + .LOOP => return error.FileSystem, + .NAMETOOLONG => return error.NameTooLong, + .NOENT => return error.FileNotFound, + .NOEXEC => return error.InvalidExe, + .NOTDIR => return error.NotDir, + .TXTBSY => return error.FileBusy, + .BADARCH => return error.InvalidExe, + .BADEXEC => return error.InvalidExe, + .FAULT => unreachable, + .INVAL => unreachable, + else => |err| return unexpectedErrno(err), + } + } + + /// Use this version of the `waitpid` wrapper if you spawned your child process using `posix_spawn` + /// or `posix_spawnp` syscalls. + /// See also `std.os.waitpid` for an alternative if your child process was spawned via `fork` and + /// `execve` method. + pub fn waitpid(pid: pid_t, flags: u32) Error!WaitPidResult { + const Status = if (builtin.link_libc) c_int else u32; + var status: Status = undefined; + while (true) { + const rc = system.waitpid(pid, &status, if (builtin.link_libc) @intCast(c_int, flags) else flags); + switch (errno(rc)) { + .SUCCESS => return WaitPidResult{ + .pid = @intCast(pid_t, rc), + .status = @bitCast(u32, status), + }, + .INTR => continue, + .CHILD => return error.ChildExecFailed, + .INVAL => unreachable, // Invalid flags. + else => unreachable, + } + } + } +} else struct {};