authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2022-03-14 20:41:28+01:00
committergravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2022-03-16 19:40:44+01:00
logdd55b7294946cf1982815518422a007b19438d71
tree1f5977cb9f3a9a66a9943791821f4247b742f663
parent49c0bb1f33044658909cae4351d931af497ee4e5

std: introduce posix_spawn as an alt to fork-exec

Currently, the new API will only be available on macOS with the intention of adding more POSIX systems to it incrementally (such as Linux, etc.). Changes: * add `posix_spawn` wrappers in a separate container in `os/posix_spawn.zig` * rewrite `ChildProcess.spawnPosix` using `posix_spawn` targeting macOS as `ChildProcess.spawnMacos` * introduce a `posix_spawn` specific `std.c.waitpid` wrapper which does return an error in case the child process failed to exec - this is required for any process that was spawned using `posix_spawn` mechanism as, by definition, the errors returned by `posix_spawn` routine cover only the `fork`-equivalent; `pre-exec()` and `exec()` steps are covered by a catch-all error `ECHILD` returned by `waitpid` on unsuccessful execution, e.g., no such file error, etc.

6 files changed, 493 insertions(+), 51 deletions(-)

CMakeLists.txt+1
......@@ -467,6 +467,7 @@ set(ZIG_STAGE2_SOURCES
467467 "${CMAKE_SOURCE_DIR}/lib/std/os/linux.zig"
468468 "${CMAKE_SOURCE_DIR}/lib/std/os/linux/io_uring.zig"
469469 "${CMAKE_SOURCE_DIR}/lib/std/os/linux/x86_64.zig"
470 "${CMAKE_SOURCE_DIR}/lib/std/os/posix_spawn.zig"
470471 "${CMAKE_SOURCE_DIR}/lib/std/os/windows.zig"
471472 "${CMAKE_SOURCE_DIR}/lib/std/os/windows/ntstatus.zig"
472473 "${CMAKE_SOURCE_DIR}/lib/std/os/windows/win32error.zig"
lib/std/build.zig+2
......@@ -1321,6 +1321,7 @@ pub const Builder = struct {
13211321 error.FileNotFound => error.PkgConfigNotInstalled,
13221322 error.InvalidName => error.PkgConfigNotInstalled,
13231323 error.PkgConfigInvalidOutput => error.PkgConfigInvalidOutput,
1324 error.ChildExecFailed => error.PkgConfigFailed,
13241325 else => return err,
13251326 };
13261327 self.pkg_config_pkg_list = result;
......@@ -1963,6 +1964,7 @@ pub const LibExeObjStep = struct {
19631964 error.ExecNotSupported => return error.PkgConfigFailed,
19641965 error.ExitCodeFailure => return error.PkgConfigFailed,
19651966 error.FileNotFound => return error.PkgConfigNotInstalled,
1967 error.ChildExecFailed => return error.PkgConfigFailed,
19661968 else => return err,
19671969 };
19681970 var it = mem.tokenize(u8, stdout, " \r\n\t");
lib/std/c/darwin.zig+38-13
......@@ -78,15 +78,40 @@ pub const posix_spawn_file_actions_t = *opaque {};
7878pub extern "c" fn posix_spawnattr_init(attr: *posix_spawnattr_t) c_int;
7979pub extern "c" fn posix_spawnattr_destroy(attr: *posix_spawnattr_t) void;
8080pub extern "c" fn posix_spawnattr_setflags(attr: *posix_spawnattr_t, flags: c_short) c_int;
81pub extern "c" fn posix_spawnattr_getflags(attr: *const posix_spawnattr_t, flags: *c_short) c_int;
8182pub extern "c" fn posix_spawn_file_actions_init(actions: *posix_spawn_file_actions_t) c_int;
8283pub extern "c" fn posix_spawn_file_actions_destroy(actions: *posix_spawn_file_actions_t) void;
84pub extern "c" fn posix_spawn_file_actions_addclose(actions: *posix_spawn_file_actions_t, filedes: fd_t) c_int;
85pub extern "c" fn posix_spawn_file_actions_addopen(
86 actions: *posix_spawn_file_actions_t,
87 filedes: fd_t,
88 path: [*:0]const u8,
89 oflag: c_int,
90 mode: mode_t,
91) c_int;
92pub extern "c" fn posix_spawn_file_actions_adddup2(
93 actions: *posix_spawn_file_actions_t,
94 filedes: fd_t,
95 newfiledes: fd_t,
96) c_int;
97pub extern "c" fn posix_spawn_file_actions_addinherit_np(actions: *posix_spawn_file_actions_t, filedes: fd_t) c_int;
98pub extern "c" fn posix_spawn_file_actions_addchdir_np(actions: *posix_spawn_file_actions_t, path: [*:0]const u8) c_int;
99pub extern "c" fn posix_spawn_file_actions_addfchdir_np(actions: *posix_spawn_file_actions_t, filedes: fd_t) c_int;
100pub extern "c" fn posix_spawn(
101 pid: *pid_t,
102 path: [*:0]const u8,
103 actions: ?*const posix_spawn_file_actions_t,
104 attr: ?*const posix_spawnattr_t,
105 argv: [*:null]?[*:0]const u8,
106 env: [*:null]?[*:0]const u8,
107) c_int;
83108pub extern "c" fn posix_spawnp(
84109 pid: *pid_t,
85110 path: [*:0]const u8,
86111 actions: ?*const posix_spawn_file_actions_t,
87 attr: *const posix_spawnattr_t,
88 argv: [*][*:0]const u8,
89 env: [*][*:0]const u8,
112 attr: ?*const posix_spawnattr_t,
113 argv: [*:null]?[*:0]const u8,
114 env: [*:null]?[*:0]const u8,
90115) c_int;
91116
92117pub extern "c" fn kevent64(
......@@ -2563,16 +2588,16 @@ pub const CPUFAMILY = enum(u32) {
25632588 _,
25642589};
25652590
2566pub const POSIX_SPAWN_RESETIDS: c_int = 0x0001;
2567pub const POSIX_SPAWN_SETPGROUP: c_int = 0x0002;
2568pub const POSIX_SPAWN_SETSIGDEF: c_int = 0x0004;
2569pub const POSIX_SPAWN_SETSIGMASK: c_int = 0x0008;
2570pub const POSIX_SPAWN_SETEXEC: c_int = 0x0040;
2571pub const POSIX_SPAWN_START_SUSPENDED: c_int = 0x0080;
2572pub const _POSIX_SPAWN_DISABLE_ASLR: c_int = 0x0100;
2573pub const POSIX_SPAWN_SETSID: c_int = 0x0400;
2574pub const _POSIX_SPAWN_RESLIDE: c_int = 0x0800;
2575pub const POSIX_SPAWN_CLOEXEC_DEFAULT: c_int = 0x4000;
2591pub const POSIX_SPAWN_RESETIDS = 0x0001;
2592pub const POSIX_SPAWN_SETPGROUP = 0x0002;
2593pub const POSIX_SPAWN_SETSIGDEF = 0x0004;
2594pub const POSIX_SPAWN_SETSIGMASK = 0x0008;
2595pub const POSIX_SPAWN_SETEXEC = 0x0040;
2596pub const POSIX_SPAWN_START_SUSPENDED = 0x0080;
2597pub const _POSIX_SPAWN_DISABLE_ASLR = 0x0100;
2598pub const POSIX_SPAWN_SETSID = 0x0400;
2599pub const _POSIX_SPAWN_RESLIDE = 0x0800;
2600pub const POSIX_SPAWN_CLOEXEC_DEFAULT = 0x4000;
25762601
25772602pub const PT_TRACE_ME = 0;
25782603pub const PT_CONTINUE = 7;
lib/std/child_process.zig+165-38
......@@ -53,10 +53,13 @@ pub const ChildProcess = struct {
5353 /// Once that is done, `cwd` will be deprecated in favor of this field.
5454 cwd_dir: ?fs.Dir = null,
5555
56 err_pipe: if (builtin.os.tag == .windows) void else [2]os.fd_t,
56 err_pipe: ?if (builtin.os.tag == .windows) void else [2]os.fd_t,
5757
5858 expand_arg0: Arg0Expand,
5959
60 /// Darwin-only. Disable ASLR for the child process.
61 disable_aslr: bool = false,
62
6063 pub const Arg0Expand = os.Arg0Expand;
6164
6265 pub const SpawnError = error{
......@@ -72,7 +75,13 @@ pub const ChildProcess = struct {
7275
7376 /// Windows-only. `cwd` was provided, but the path did not exist when spawning the child process.
7477 CurrentWorkingDirectoryUnlinked,
75 } || os.ExecveError || os.SetIdError || os.ChangeCurDirError || windows.CreateProcessError || windows.WaitForSingleObjectError;
78 } ||
79 os.ExecveError ||
80 os.SetIdError ||
81 os.ChangeCurDirError ||
82 windows.CreateProcessError ||
83 windows.WaitForSingleObjectError ||
84 os.posix_spawn.Error;
7685
7786 pub const Term = union(enum) {
7887 Exited: u8,
......@@ -98,7 +107,7 @@ pub const ChildProcess = struct {
98107 .pid = undefined,
99108 .handle = undefined,
100109 .thread_handle = undefined,
101 .err_pipe = undefined,
110 .err_pipe = null,
102111 .term = null,
103112 .env_map = null,
104113 .cwd = null,
......@@ -128,6 +137,10 @@ pub const ChildProcess = struct {
128137 @compileError("the target operating system cannot spawn processes");
129138 }
130139
140 if (comptime builtin.target.isDarwin()) {
141 return self.spawnMacos();
142 }
143
131144 if (builtin.os.tag == .windows) {
132145 return self.spawnWindows();
133146 } else {
......@@ -166,7 +179,7 @@ pub const ChildProcess = struct {
166179 return term;
167180 }
168181 try os.kill(self.pid, os.SIG.TERM);
169 self.waitUnwrapped();
182 try self.waitUnwrapped();
170183 return self.term.?;
171184 }
172185
......@@ -435,7 +448,7 @@ pub const ChildProcess = struct {
435448 return term;
436449 }
437450
438 self.waitUnwrapped();
451 try self.waitUnwrapped();
439452 return self.term.?;
440453 }
441454
......@@ -461,8 +474,12 @@ pub const ChildProcess = struct {
461474 return result;
462475 }
463476
464 fn waitUnwrapped(self: *ChildProcess) void {
465 const status = os.waitpid(self.pid, 0).status;
477 fn waitUnwrapped(self: *ChildProcess) !void {
478 const res: os.WaitPidResult = if (comptime builtin.target.isDarwin())
479 try os.posix_spawn.waitpid(self.pid, 0)
480 else
481 os.waitpid(self.pid, 0);
482 const status = res.status;
466483 self.cleanupStreams();
467484 self.handleWaitResult(status);
468485 }
......@@ -487,37 +504,39 @@ pub const ChildProcess = struct {
487504 }
488505
489506 fn cleanupAfterWait(self: *ChildProcess, status: u32) !Term {
490 defer destroyPipe(self.err_pipe);
491
492 if (builtin.os.tag == .linux) {
493 var fd = [1]std.os.pollfd{std.os.pollfd{
494 .fd = self.err_pipe[0],
495 .events = std.os.POLL.IN,
496 .revents = undefined,
497 }};
498
499 // Check if the eventfd buffer stores a non-zero value by polling
500 // it, that's the error code returned by the child process.
501 _ = std.os.poll(&fd, 0) catch unreachable;
502
503 // According to eventfd(2) the descriptro is readable if the counter
504 // has a value greater than 0
505 if ((fd[0].revents & std.os.POLL.IN) != 0) {
506 const err_int = try readIntFd(self.err_pipe[0]);
507 return @errSetCast(SpawnError, @intToError(err_int));
508 }
509 } else {
510 // Write maxInt(ErrInt) to the write end of the err_pipe. This is after
511 // waitpid, so this write is guaranteed to be after the child
512 // pid potentially wrote an error. This way we can do a blocking
513 // read on the error pipe and either get maxInt(ErrInt) (no error) or
514 // an error code.
515 try writeIntFd(self.err_pipe[1], maxInt(ErrInt));
516 const err_int = try readIntFd(self.err_pipe[0]);
517 // Here we potentially return the fork child's error from the parent
518 // pid.
519 if (err_int != maxInt(ErrInt)) {
520 return @errSetCast(SpawnError, @intToError(err_int));
507 if (self.err_pipe) |err_pipe| {
508 defer destroyPipe(err_pipe);
509
510 if (builtin.os.tag == .linux) {
511 var fd = [1]std.os.pollfd{std.os.pollfd{
512 .fd = err_pipe[0],
513 .events = std.os.POLL.IN,
514 .revents = undefined,
515 }};
516
517 // Check if the eventfd buffer stores a non-zero value by polling
518 // it, that's the error code returned by the child process.
519 _ = std.os.poll(&fd, 0) catch unreachable;
520
521 // According to eventfd(2) the descriptro is readable if the counter
522 // has a value greater than 0
523 if ((fd[0].revents & std.os.POLL.IN) != 0) {
524 const err_int = try readIntFd(err_pipe[0]);
525 return @errSetCast(SpawnError, @intToError(err_int));
526 }
527 } else {
528 // Write maxInt(ErrInt) to the write end of the err_pipe. This is after
529 // waitpid, so this write is guaranteed to be after the child
530 // pid potentially wrote an error. This way we can do a blocking
531 // read on the error pipe and either get maxInt(ErrInt) (no error) or
532 // an error code.
533 try writeIntFd(err_pipe[1], maxInt(ErrInt));
534 const err_int = try readIntFd(err_pipe[0]);
535 // Here we potentially return the fork child's error from the parent
536 // pid.
537 if (err_int != maxInt(ErrInt)) {
538 return @errSetCast(SpawnError, @intToError(err_int));
539 }
521540 }
522541 }
523542
......@@ -535,6 +554,114 @@ pub const ChildProcess = struct {
535554 Term{ .Unknown = status };
536555 }
537556
557 fn spawnMacos(self: *ChildProcess) SpawnError!void {
558 const pipe_flags = if (io.is_async) os.O.NONBLOCK else 0;
559 const stdin_pipe = if (self.stdin_behavior == StdIo.Pipe) try os.pipe2(pipe_flags) else undefined;
560 errdefer if (self.stdin_behavior == StdIo.Pipe) destroyPipe(stdin_pipe);
561
562 const stdout_pipe = if (self.stdout_behavior == StdIo.Pipe) try os.pipe2(pipe_flags) else undefined;
563 errdefer if (self.stdout_behavior == StdIo.Pipe) destroyPipe(stdout_pipe);
564
565 const stderr_pipe = if (self.stderr_behavior == StdIo.Pipe) try os.pipe2(pipe_flags) else undefined;
566 errdefer if (self.stderr_behavior == StdIo.Pipe) destroyPipe(stderr_pipe);
567
568 const any_ignore = (self.stdin_behavior == StdIo.Ignore or self.stdout_behavior == StdIo.Ignore or self.stderr_behavior == StdIo.Ignore);
569 const dev_null_fd = if (any_ignore)
570 os.openZ("/dev/null", os.O.RDWR, 0) catch |err| switch (err) {
571 error.PathAlreadyExists => unreachable,
572 error.NoSpaceLeft => unreachable,
573 error.FileTooBig => unreachable,
574 error.DeviceBusy => unreachable,
575 error.FileLocksNotSupported => unreachable,
576 error.BadPathName => unreachable, // Windows-only
577 error.InvalidHandle => unreachable, // WASI-only
578 error.WouldBlock => unreachable,
579 else => |e| return e,
580 }
581 else
582 undefined;
583 defer if (any_ignore) os.close(dev_null_fd);
584
585 var attr = try os.posix_spawn.Attr.init();
586 defer attr.deinit();
587 var flags: u16 = os.darwin.POSIX_SPAWN_SETSIGDEF | os.darwin.POSIX_SPAWN_SETSIGMASK;
588 if (self.disable_aslr) {
589 flags |= os.darwin._POSIX_SPAWN_DISABLE_ASLR;
590 }
591 try attr.set(flags);
592
593 var actions = try os.posix_spawn.Actions.init();
594 defer actions.deinit();
595
596 try setUpChildIoPosixSpawn(self.stdin_behavior, &actions, stdin_pipe[0], os.STDIN_FILENO, dev_null_fd);
597 try setUpChildIoPosixSpawn(self.stdout_behavior, &actions, stdout_pipe[1], os.STDOUT_FILENO, dev_null_fd);
598 try setUpChildIoPosixSpawn(self.stderr_behavior, &actions, stderr_pipe[1], os.STDERR_FILENO, dev_null_fd);
599
600 if (self.cwd_dir) |cwd| {
601 try actions.fchdir(cwd.fd);
602 } else if (self.cwd) |cwd| {
603 try actions.chdir(cwd);
604 }
605
606 var arena_allocator = std.heap.ArenaAllocator.init(self.allocator);
607 defer arena_allocator.deinit();
608 const arena = arena_allocator.allocator();
609
610 const argv_buf = try arena.allocSentinel(?[*:0]u8, self.argv.len, null);
611 for (self.argv) |arg, i| argv_buf[i] = (try arena.dupeZ(u8, arg)).ptr;
612
613 const envp = if (self.env_map) |env_map| m: {
614 const envp_buf = try createNullDelimitedEnvMap(arena, env_map);
615 break :m envp_buf.ptr;
616 } else std.c.environ;
617
618 const pid = try os.posix_spawn.spawnp(self.argv[0], actions, attr, argv_buf, envp);
619
620 if (self.stdin_behavior == StdIo.Pipe) {
621 self.stdin = File{ .handle = stdin_pipe[1] };
622 } else {
623 self.stdin = null;
624 }
625 if (self.stdout_behavior == StdIo.Pipe) {
626 self.stdout = File{ .handle = stdout_pipe[0] };
627 } else {
628 self.stdout = null;
629 }
630 if (self.stderr_behavior == StdIo.Pipe) {
631 self.stderr = File{ .handle = stderr_pipe[0] };
632 } else {
633 self.stderr = null;
634 }
635
636 self.pid = pid;
637 self.term = null;
638
639 if (self.stdin_behavior == StdIo.Pipe) {
640 os.close(stdin_pipe[0]);
641 }
642 if (self.stdout_behavior == StdIo.Pipe) {
643 os.close(stdout_pipe[1]);
644 }
645 if (self.stderr_behavior == StdIo.Pipe) {
646 os.close(stderr_pipe[1]);
647 }
648 }
649
650 fn setUpChildIoPosixSpawn(
651 stdio: StdIo,
652 actions: *os.posix_spawn.Actions,
653 pipe_fd: i32,
654 std_fileno: i32,
655 dev_null_fd: i32,
656 ) !void {
657 switch (stdio) {
658 .Pipe => try actions.dup2(pipe_fd, std_fileno),
659 .Close => try actions.close(std_fileno),
660 .Inherit => {},
661 .Ignore => try actions.dup2(dev_null_fd, std_fileno),
662 }
663 }
664
538665 fn spawnPosix(self: *ChildProcess) SpawnError!void {
539666 const pipe_flags = if (io.is_async) os.O.NONBLOCK else 0;
540667 const stdin_pipe = if (self.stdin_behavior == StdIo.Pipe) try os.pipe2(pipe_flags) else undefined;
lib/std/os.zig+5
......@@ -41,6 +41,7 @@ pub const plan9 = @import("os/plan9.zig");
4141pub const uefi = @import("os/uefi.zig");
4242pub const wasi = @import("os/wasi.zig");
4343pub const windows = @import("os/windows.zig");
44pub const posix_spawn = @import("os/posix_spawn.zig");
4445
4546comptime {
4647 assert(@import("std") == std); // std lib tests require --zig-lib-dir
......@@ -52,6 +53,7 @@ test {
5253 _ = uefi;
5354 _ = wasi;
5455 _ = windows;
56 _ = posix_spawn;
5557
5658 _ = @import("os/test.zig");
5759}
......@@ -4037,6 +4039,9 @@ pub const WaitPidResult = struct {
40374039 status: u32,
40384040};
40394041
4042/// Use this version of the `waitpid` wrapper if you spawned your child process using explicit
4043/// `fork` and `execve` method. If you spawned your child process using `posix_spawn` method,
4044/// use `std.os.posix_spawn.waitpid` instead.
40404045pub fn waitpid(pid: pid_t, flags: u32) WaitPidResult {
40414046 const Status = if (builtin.link_libc) c_int else u32;
40424047 var status: Status = undefined;
lib/std/os/posix_spawn.zig created+282
......@@ -0,0 +1,282 @@
1const std = @import("std");
2const builtin = @import("builtin");
3
4const os = @import("../os.zig");
5const system = os.system;
6const errno = system.getErrno;
7const fd_t = system.fd_t;
8const mode_t = system.mode_t;
9const pid_t = system.pid_t;
10const unexpectedErrno = os.unexpectedErrno;
11const UnexpectedError = os.UnexpectedError;
12const toPosixPath = os.toPosixPath;
13const WaitPidResult = os.WaitPidResult;
14
15pub usingnamespace posix_spawn;
16
17pub const Error = error{
18 SystemResources,
19 InvalidFileDescriptor,
20 NameTooLong,
21 TooBig,
22 PermissionDenied,
23 InputOutput,
24 FileSystem,
25 FileNotFound,
26 InvalidExe,
27 NotDir,
28 FileBusy,
29
30 /// Returned when the child fails to execute either in the pre-exec() initialization step, or
31 /// when exec(3) is invoked.
32 ChildExecFailed,
33} || UnexpectedError;
34
35const posix_spawn = if (builtin.target.isDarwin()) struct {
36 pub const Attr = struct {
37 attr: system.posix_spawnattr_t,
38
39 pub fn init() Error!Attr {
40 var attr: system.posix_spawnattr_t = undefined;
41 switch (errno(system.posix_spawnattr_init(&attr))) {
42 .SUCCESS => return Attr{ .attr = attr },
43 .NOMEM => return error.SystemResources,
44 .INVAL => unreachable,
45 else => |err| return unexpectedErrno(err),
46 }
47 }
48
49 pub fn deinit(self: *Attr) void {
50 system.posix_spawnattr_destroy(&self.attr);
51 self.* = undefined;
52 }
53
54 pub fn get(self: Attr) Error!u16 {
55 var flags: c_short = undefined;
56 switch (errno(system.posix_spawnattr_getflags(&self.attr, &flags))) {
57 .SUCCESS => return @bitCast(u16, flags),
58 .INVAL => unreachable,
59 else => |err| return unexpectedErrno(err),
60 }
61 }
62
63 pub fn set(self: *Attr, flags: u16) Error!void {
64 switch (errno(system.posix_spawnattr_setflags(&self.attr, @bitCast(c_short, flags)))) {
65 .SUCCESS => return,
66 .INVAL => unreachable,
67 else => |err| return unexpectedErrno(err),
68 }
69 }
70 };
71
72 pub const Actions = struct {
73 actions: system.posix_spawn_file_actions_t,
74
75 pub fn init() Error!Actions {
76 var actions: system.posix_spawn_file_actions_t = undefined;
77 switch (errno(system.posix_spawn_file_actions_init(&actions))) {
78 .SUCCESS => return Actions{ .actions = actions },
79 .NOMEM => return error.SystemResources,
80 .INVAL => unreachable,
81 else => |err| return unexpectedErrno(err),
82 }
83 }
84
85 pub fn deinit(self: *Actions) void {
86 system.posix_spawn_file_actions_destroy(&self.actions);
87 self.* = undefined;
88 }
89
90 pub fn open(self: *Actions, fd: fd_t, path: []const u8, flags: u32, mode: mode_t) Error!void {
91 const posix_path = try toPosixPath(path);
92 return self.openZ(fd, &posix_path, flags, mode);
93 }
94
95 pub fn openZ(self: *Actions, fd: fd_t, path: [*:0]const u8, flags: u32, mode: mode_t) Error!void {
96 switch (errno(system.posix_spawn_file_actions_addopen(&self.actions, fd, path, @bitCast(c_int, flags), mode))) {
97 .SUCCESS => return,
98 .BADF => return error.InvalidFileDescriptor,
99 .NOMEM => return error.SystemResources,
100 .NAMETOOLONG => return error.NameTooLong,
101 .INVAL => unreachable, // the value of file actions is invalid
102 else => |err| return unexpectedErrno(err),
103 }
104 }
105
106 pub fn close(self: *Actions, fd: fd_t) Error!void {
107 switch (errno(system.posix_spawn_file_actions_addclose(&self.actions, fd))) {
108 .SUCCESS => return,
109 .BADF => return error.InvalidFileDescriptor,
110 .NOMEM => return error.SystemResources,
111 .INVAL => unreachable, // the value of file actions is invalid
112 .NAMETOOLONG => unreachable,
113 else => |err| return unexpectedErrno(err),
114 }
115 }
116
117 pub fn dup2(self: *Actions, fd: fd_t, newfd: fd_t) Error!void {
118 switch (errno(system.posix_spawn_file_actions_adddup2(&self.actions, fd, newfd))) {
119 .SUCCESS => return,
120 .BADF => return error.InvalidFileDescriptor,
121 .NOMEM => return error.SystemResources,
122 .INVAL => unreachable, // the value of file actions is invalid
123 .NAMETOOLONG => unreachable,
124 else => |err| return unexpectedErrno(err),
125 }
126 }
127
128 pub fn inherit(self: *Actions, fd: fd_t) Error!void {
129 switch (errno(system.posix_spawn_file_actions_addinherit_np(&self.actions, fd))) {
130 .SUCCESS => return,
131 .BADF => return error.InvalidFileDescriptor,
132 .NOMEM => return error.SystemResources,
133 .INVAL => unreachable, // the value of file actions is invalid
134 .NAMETOOLONG => unreachable,
135 else => |err| return unexpectedErrno(err),
136 }
137 }
138
139 pub fn chdir(self: *Actions, path: []const u8) Error!void {
140 const posix_path = try toPosixPath(path);
141 return self.chdirZ(&posix_path);
142 }
143
144 pub fn chdirZ(self: *Actions, path: [*:0]const u8) Error!void {
145 switch (errno(system.posix_spawn_file_actions_addchdir_np(&self.actions, path))) {
146 .SUCCESS => return,
147 .NOMEM => return error.SystemResources,
148 .NAMETOOLONG => return error.NameTooLong,
149 .BADF => unreachable,
150 .INVAL => unreachable, // the value of file actions is invalid
151 else => |err| return unexpectedErrno(err),
152 }
153 }
154
155 pub fn fchdir(self: *Actions, fd: fd_t) Error!void {
156 switch (errno(system.posix_spawn_file_actions_addfchdir_np(&self.actions, fd))) {
157 .SUCCESS => return,
158 .BADF => return error.InvalidFileDescriptor,
159 .NOMEM => return error.SystemResources,
160 .INVAL => unreachable, // the value of file actions is invalid
161 .NAMETOOLONG => unreachable,
162 else => |err| return unexpectedErrno(err),
163 }
164 }
165 };
166
167 pub fn spawn(
168 path: []const u8,
169 actions: ?Actions,
170 attr: ?Attr,
171 argv: [*:null]?[*:0]const u8,
172 envp: [*:null]?[*:0]const u8,
173 ) Error!pid_t {
174 const posix_path = try toPosixPath(path);
175 return spawnZ(&posix_path, actions, attr, argv, envp);
176 }
177
178 pub fn spawnZ(
179 path: [*:0]const u8,
180 actions: ?Actions,
181 attr: ?Attr,
182 argv: [*:null]?[*:0]const u8,
183 envp: [*:null]?[*:0]const u8,
184 ) Error!pid_t {
185 var pid: pid_t = undefined;
186 switch (errno(system.posix_spawn(
187 &pid,
188 path,
189 if (actions) |a| &a.actions else null,
190 if (attr) |a| &a.attr else null,
191 argv,
192 envp,
193 ))) {
194 .SUCCESS => return pid,
195 .@"2BIG" => return error.TooBig,
196 .NOMEM => return error.SystemResources,
197 .BADF => return error.InvalidFileDescriptor,
198 .ACCES => return error.PermissionDenied,
199 .IO => return error.InputOutput,
200 .LOOP => return error.FileSystem,
201 .NAMETOOLONG => return error.NameTooLong,
202 .NOENT => return error.FileNotFound,
203 .NOEXEC => return error.InvalidExe,
204 .NOTDIR => return error.NotDir,
205 .TXTBSY => return error.FileBusy,
206 .BADARCH => return error.InvalidExe,
207 .BADEXEC => return error.InvalidExe,
208 .FAULT => unreachable,
209 .INVAL => unreachable,
210 else => |err| return unexpectedErrno(err),
211 }
212 }
213
214 pub fn spawnp(
215 file: []const u8,
216 actions: ?Actions,
217 attr: ?Attr,
218 argv: [*:null]?[*:0]const u8,
219 envp: [*:null]?[*:0]const u8,
220 ) Error!pid_t {
221 const posix_file = try toPosixPath(file);
222 return spawnpZ(&posix_file, actions, attr, argv, envp);
223 }
224
225 pub fn spawnpZ(
226 file: [*:0]const u8,
227 actions: ?Actions,
228 attr: ?Attr,
229 argv: [*:null]?[*:0]const u8,
230 envp: [*:null]?[*:0]const u8,
231 ) Error!pid_t {
232 var pid: pid_t = undefined;
233 switch (errno(system.posix_spawnp(
234 &pid,
235 file,
236 if (actions) |a| &a.actions else null,
237 if (attr) |a| &a.attr else null,
238 argv,
239 envp,
240 ))) {
241 .SUCCESS => return pid,
242 .@"2BIG" => return error.TooBig,
243 .NOMEM => return error.SystemResources,
244 .BADF => return error.InvalidFileDescriptor,
245 .ACCES => return error.PermissionDenied,
246 .IO => return error.InputOutput,
247 .LOOP => return error.FileSystem,
248 .NAMETOOLONG => return error.NameTooLong,
249 .NOENT => return error.FileNotFound,
250 .NOEXEC => return error.InvalidExe,
251 .NOTDIR => return error.NotDir,
252 .TXTBSY => return error.FileBusy,
253 .BADARCH => return error.InvalidExe,
254 .BADEXEC => return error.InvalidExe,
255 .FAULT => unreachable,
256 .INVAL => unreachable,
257 else => |err| return unexpectedErrno(err),
258 }
259 }
260
261 /// Use this version of the `waitpid` wrapper if you spawned your child process using `posix_spawn`
262 /// or `posix_spawnp` syscalls.
263 /// See also `std.os.waitpid` for an alternative if your child process was spawned via `fork` and
264 /// `execve` method.
265 pub fn waitpid(pid: pid_t, flags: u32) Error!WaitPidResult {
266 const Status = if (builtin.link_libc) c_int else u32;
267 var status: Status = undefined;
268 while (true) {
269 const rc = system.waitpid(pid, &status, if (builtin.link_libc) @intCast(c_int, flags) else flags);
270 switch (errno(rc)) {
271 .SUCCESS => return WaitPidResult{
272 .pid = @intCast(pid_t, rc),
273 .status = @bitCast(u32, status),
274 },
275 .INTR => continue,
276 .CHILD => return error.ChildExecFailed,
277 .INVAL => unreachable, // Invalid flags.
278 else => unreachable,
279 }
280 }
281 }
282} else struct {};