zig/lib/std/os/posix_spawn.zig
Jakub Konka dd55b72949 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.
2022-03-16 19:40:44 +01:00

283 lines
10 KiB
Zig

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 {};