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.
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
282
lib/std/os/posix_spawn.zig
Normal file
282
lib/std/os/posix_spawn.zig
Normal file
@@ -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 {};
|
||||
Reference in New Issue
Block a user