| author | |
| committer | |
| log | dd55b7294946cf1982815518422a007b19438d71 |
| tree | 1f5977cb9f3a9a66a9943791821f4247b742f663 |
| parent | 49c0bb1f33044658909cae4351d931af497ee4e5 |
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 |
| 467 | 467 | "${CMAKE_SOURCE_DIR}/lib/std/os/linux.zig" |
| 468 | 468 | "${CMAKE_SOURCE_DIR}/lib/std/os/linux/io_uring.zig" |
| 469 | 469 | "${CMAKE_SOURCE_DIR}/lib/std/os/linux/x86_64.zig" |
| 470 | "${CMAKE_SOURCE_DIR}/lib/std/os/posix_spawn.zig" | |
| 470 | 471 | "${CMAKE_SOURCE_DIR}/lib/std/os/windows.zig" |
| 471 | 472 | "${CMAKE_SOURCE_DIR}/lib/std/os/windows/ntstatus.zig" |
| 472 | 473 | "${CMAKE_SOURCE_DIR}/lib/std/os/windows/win32error.zig" |
lib/std/build.zig+2| ... | ... | @@ -1321,6 +1321,7 @@ pub const Builder = struct { |
| 1321 | 1321 | error.FileNotFound => error.PkgConfigNotInstalled, |
| 1322 | 1322 | error.InvalidName => error.PkgConfigNotInstalled, |
| 1323 | 1323 | error.PkgConfigInvalidOutput => error.PkgConfigInvalidOutput, |
| 1324 | error.ChildExecFailed => error.PkgConfigFailed, | |
| 1324 | 1325 | else => return err, |
| 1325 | 1326 | }; |
| 1326 | 1327 | self.pkg_config_pkg_list = result; |
| ... | ... | @@ -1963,6 +1964,7 @@ pub const LibExeObjStep = struct { |
| 1963 | 1964 | error.ExecNotSupported => return error.PkgConfigFailed, |
| 1964 | 1965 | error.ExitCodeFailure => return error.PkgConfigFailed, |
| 1965 | 1966 | error.FileNotFound => return error.PkgConfigNotInstalled, |
| 1967 | error.ChildExecFailed => return error.PkgConfigFailed, | |
| 1966 | 1968 | else => return err, |
| 1967 | 1969 | }; |
| 1968 | 1970 | 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 {}; |
| 78 | 78 | pub extern "c" fn posix_spawnattr_init(attr: *posix_spawnattr_t) c_int; |
| 79 | 79 | pub extern "c" fn posix_spawnattr_destroy(attr: *posix_spawnattr_t) void; |
| 80 | 80 | pub extern "c" fn posix_spawnattr_setflags(attr: *posix_spawnattr_t, flags: c_short) c_int; |
| 81 | pub extern "c" fn posix_spawnattr_getflags(attr: *const posix_spawnattr_t, flags: *c_short) c_int; | |
| 81 | 82 | pub extern "c" fn posix_spawn_file_actions_init(actions: *posix_spawn_file_actions_t) c_int; |
| 82 | 83 | pub extern "c" fn posix_spawn_file_actions_destroy(actions: *posix_spawn_file_actions_t) void; |
| 84 | pub extern "c" fn posix_spawn_file_actions_addclose(actions: *posix_spawn_file_actions_t, filedes: fd_t) c_int; | |
| 85 | pub 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; | |
| 92 | pub 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; | |
| 97 | pub extern "c" fn posix_spawn_file_actions_addinherit_np(actions: *posix_spawn_file_actions_t, filedes: fd_t) c_int; | |
| 98 | pub extern "c" fn posix_spawn_file_actions_addchdir_np(actions: *posix_spawn_file_actions_t, path: [*:0]const u8) c_int; | |
| 99 | pub extern "c" fn posix_spawn_file_actions_addfchdir_np(actions: *posix_spawn_file_actions_t, filedes: fd_t) c_int; | |
| 100 | pub 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; | |
| 83 | 108 | pub extern "c" fn posix_spawnp( |
| 84 | 109 | pid: *pid_t, |
| 85 | 110 | path: [*:0]const u8, |
| 86 | 111 | 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, | |
| 90 | 115 | ) c_int; |
| 91 | 116 | |
| 92 | 117 | pub extern "c" fn kevent64( |
| ... | ... | @@ -2563,16 +2588,16 @@ pub const CPUFAMILY = enum(u32) { |
| 2563 | 2588 | _, |
| 2564 | 2589 | }; |
| 2565 | 2590 | |
| 2566 | pub const POSIX_SPAWN_RESETIDS: c_int = 0x0001; | |
| 2567 | pub const POSIX_SPAWN_SETPGROUP: c_int = 0x0002; | |
| 2568 | pub const POSIX_SPAWN_SETSIGDEF: c_int = 0x0004; | |
| 2569 | pub const POSIX_SPAWN_SETSIGMASK: c_int = 0x0008; | |
| 2570 | pub const POSIX_SPAWN_SETEXEC: c_int = 0x0040; | |
| 2571 | pub const POSIX_SPAWN_START_SUSPENDED: c_int = 0x0080; | |
| 2572 | pub const _POSIX_SPAWN_DISABLE_ASLR: c_int = 0x0100; | |
| 2573 | pub const POSIX_SPAWN_SETSID: c_int = 0x0400; | |
| 2574 | pub const _POSIX_SPAWN_RESLIDE: c_int = 0x0800; | |
| 2575 | pub const POSIX_SPAWN_CLOEXEC_DEFAULT: c_int = 0x4000; | |
| 2591 | pub const POSIX_SPAWN_RESETIDS = 0x0001; | |
| 2592 | pub const POSIX_SPAWN_SETPGROUP = 0x0002; | |
| 2593 | pub const POSIX_SPAWN_SETSIGDEF = 0x0004; | |
| 2594 | pub const POSIX_SPAWN_SETSIGMASK = 0x0008; | |
| 2595 | pub const POSIX_SPAWN_SETEXEC = 0x0040; | |
| 2596 | pub const POSIX_SPAWN_START_SUSPENDED = 0x0080; | |
| 2597 | pub const _POSIX_SPAWN_DISABLE_ASLR = 0x0100; | |
| 2598 | pub const POSIX_SPAWN_SETSID = 0x0400; | |
| 2599 | pub const _POSIX_SPAWN_RESLIDE = 0x0800; | |
| 2600 | pub const POSIX_SPAWN_CLOEXEC_DEFAULT = 0x4000; | |
| 2576 | 2601 | |
| 2577 | 2602 | pub const PT_TRACE_ME = 0; |
| 2578 | 2603 | pub const PT_CONTINUE = 7; |
lib/std/child_process.zig+165-38| ... | ... | @@ -53,10 +53,13 @@ pub const ChildProcess = struct { |
| 53 | 53 | /// Once that is done, `cwd` will be deprecated in favor of this field. |
| 54 | 54 | cwd_dir: ?fs.Dir = null, |
| 55 | 55 | |
| 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, | |
| 57 | 57 | |
| 58 | 58 | expand_arg0: Arg0Expand, |
| 59 | 59 | |
| 60 | /// Darwin-only. Disable ASLR for the child process. | |
| 61 | disable_aslr: bool = false, | |
| 62 | ||
| 60 | 63 | pub const Arg0Expand = os.Arg0Expand; |
| 61 | 64 | |
| 62 | 65 | pub const SpawnError = error{ |
| ... | ... | @@ -72,7 +75,13 @@ pub const ChildProcess = struct { |
| 72 | 75 | |
| 73 | 76 | /// Windows-only. `cwd` was provided, but the path did not exist when spawning the child process. |
| 74 | 77 | 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; | |
| 76 | 85 | |
| 77 | 86 | pub const Term = union(enum) { |
| 78 | 87 | Exited: u8, |
| ... | ... | @@ -98,7 +107,7 @@ pub const ChildProcess = struct { |
| 98 | 107 | .pid = undefined, |
| 99 | 108 | .handle = undefined, |
| 100 | 109 | .thread_handle = undefined, |
| 101 | .err_pipe = undefined, | |
| 110 | .err_pipe = null, | |
| 102 | 111 | .term = null, |
| 103 | 112 | .env_map = null, |
| 104 | 113 | .cwd = null, |
| ... | ... | @@ -128,6 +137,10 @@ pub const ChildProcess = struct { |
| 128 | 137 | @compileError("the target operating system cannot spawn processes"); |
| 129 | 138 | } |
| 130 | 139 | |
| 140 | if (comptime builtin.target.isDarwin()) { | |
| 141 | return self.spawnMacos(); | |
| 142 | } | |
| 143 | ||
| 131 | 144 | if (builtin.os.tag == .windows) { |
| 132 | 145 | return self.spawnWindows(); |
| 133 | 146 | } else { |
| ... | ... | @@ -166,7 +179,7 @@ pub const ChildProcess = struct { |
| 166 | 179 | return term; |
| 167 | 180 | } |
| 168 | 181 | try os.kill(self.pid, os.SIG.TERM); |
| 169 | self.waitUnwrapped(); | |
| 182 | try self.waitUnwrapped(); | |
| 170 | 183 | return self.term.?; |
| 171 | 184 | } |
| 172 | 185 | |
| ... | ... | @@ -435,7 +448,7 @@ pub const ChildProcess = struct { |
| 435 | 448 | return term; |
| 436 | 449 | } |
| 437 | 450 | |
| 438 | self.waitUnwrapped(); | |
| 451 | try self.waitUnwrapped(); | |
| 439 | 452 | return self.term.?; |
| 440 | 453 | } |
| 441 | 454 | |
| ... | ... | @@ -461,8 +474,12 @@ pub const ChildProcess = struct { |
| 461 | 474 | return result; |
| 462 | 475 | } |
| 463 | 476 | |
| 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; | |
| 466 | 483 | self.cleanupStreams(); |
| 467 | 484 | self.handleWaitResult(status); |
| 468 | 485 | } |
| ... | ... | @@ -487,37 +504,39 @@ pub const ChildProcess = struct { |
| 487 | 504 | } |
| 488 | 505 | |
| 489 | 506 | 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 | } | |
| 521 | 540 | } |
| 522 | 541 | } |
| 523 | 542 | |
| ... | ... | @@ -535,6 +554,114 @@ pub const ChildProcess = struct { |
| 535 | 554 | Term{ .Unknown = status }; |
| 536 | 555 | } |
| 537 | 556 | |
| 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 | ||
| 538 | 665 | fn spawnPosix(self: *ChildProcess) SpawnError!void { |
| 539 | 666 | const pipe_flags = if (io.is_async) os.O.NONBLOCK else 0; |
| 540 | 667 | 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"); |
| 41 | 41 | pub const uefi = @import("os/uefi.zig"); |
| 42 | 42 | pub const wasi = @import("os/wasi.zig"); |
| 43 | 43 | pub const windows = @import("os/windows.zig"); |
| 44 | pub const posix_spawn = @import("os/posix_spawn.zig"); | |
| 44 | 45 | |
| 45 | 46 | comptime { |
| 46 | 47 | assert(@import("std") == std); // std lib tests require --zig-lib-dir |
| ... | ... | @@ -52,6 +53,7 @@ test { |
| 52 | 53 | _ = uefi; |
| 53 | 54 | _ = wasi; |
| 54 | 55 | _ = windows; |
| 56 | _ = posix_spawn; | |
| 55 | 57 | |
| 56 | 58 | _ = @import("os/test.zig"); |
| 57 | 59 | } |
| ... | ... | @@ -4037,6 +4039,9 @@ pub const WaitPidResult = struct { |
| 4037 | 4039 | status: u32, |
| 4038 | 4040 | }; |
| 4039 | 4041 | |
| 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. | |
| 4040 | 4045 | pub fn waitpid(pid: pid_t, flags: u32) WaitPidResult { |
| 4041 | 4046 | const Status = if (builtin.link_libc) c_int else u32; |
| 4042 | 4047 | var status: Status = undefined; |
lib/std/os/posix_spawn.zig created+282| ... | ... | @@ -0,0 +1,282 @@ |
| 1 | const std = @import("std"); | |
| 2 | const builtin = @import("builtin"); | |
| 3 | ||
| 4 | const os = @import("../os.zig"); | |
| 5 | const system = os.system; | |
| 6 | const errno = system.getErrno; | |
| 7 | const fd_t = system.fd_t; | |
| 8 | const mode_t = system.mode_t; | |
| 9 | const pid_t = system.pid_t; | |
| 10 | const unexpectedErrno = os.unexpectedErrno; | |
| 11 | const UnexpectedError = os.UnexpectedError; | |
| 12 | const toPosixPath = os.toPosixPath; | |
| 13 | const WaitPidResult = os.WaitPidResult; | |
| 14 | ||
| 15 | pub usingnamespace posix_spawn; | |
| 16 | ||
| 17 | pub 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 | ||
| 35 | const 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 {}; |