Files
bun.sh/src/bun.js/api/bun/spawn.zig
2025-06-27 23:17:09 -07:00

458 lines
16 KiB
Zig

const JSC = bun.JSC;
const bun = @import("bun");
const std = @import("std");
const Environment = bun.Environment;
const system = std.posix.system;
const Maybe = JSC.Maybe;
const fd_t = std.posix.fd_t;
const pid_t = std.posix.pid_t;
const toPosixPath = std.posix.toPosixPath;
const errno = std.posix.errno;
const mode_t = std.posix.mode_t;
const unexpectedErrno = std.posix.unexpectedErrno;
pub const BunSpawn = struct {
pub const Action = extern struct {
pub const FileActionType = enum(u8) {
none = 0,
close = 1,
dup2 = 2,
open = 3,
};
kind: FileActionType = .none,
path: ?[*:0]const u8 = null,
fds: [2]fd_t,
flags: c_int = 0,
mode: c_int = 0,
pub fn init() !Action {
return .{};
}
pub fn deinit(self: *Action, allocator: std.mem.Allocator) void {
if (self.kind == .open) {
if (self.path) |path| {
allocator.free(bun.span(path));
}
}
}
};
pub const Actions = struct {
chdir_buf: ?[*:0]u8 = null,
actions: std.ArrayListUnmanaged(Action) = .{},
detached: bool = false,
pub fn init() !Actions {
return .{};
}
pub fn deinit(self: *Actions) void {
if (self.chdir_buf) |buf| {
bun.default_allocator.free(bun.span(buf));
}
for (self.actions.items) |*action| {
action.deinit(bun.default_allocator);
}
self.actions.deinit(bun.default_allocator);
}
pub fn open(self: *Actions, fd: bun.FileDescriptor, path: []const u8, flags: u32, mode: i32) !void {
const posix_path = try toPosixPath(path);
return self.openZ(fd, &posix_path, flags, mode);
}
pub fn openZ(self: *Actions, fd: bun.FileDescriptor, path: [*:0]const u8, flags: u32, mode: i32) !void {
try self.actions.append(bun.default_allocator, .{
.kind = .open,
.path = (try bun.default_allocator.dupeZ(u8, bun.span(path))).ptr,
.flags = @intCast(flags),
.mode = @intCast(mode),
.fds = .{ fd.native(), 0 },
});
}
pub fn close(self: *Actions, fd: bun.FileDescriptor) !void {
try self.actions.append(bun.default_allocator, .{
.kind = .close,
.fds = .{ fd.native(), 0 },
});
}
pub fn dup2(self: *Actions, fd: bun.FileDescriptor, newfd: bun.FileDescriptor) !void {
try self.actions.append(bun.default_allocator, .{
.kind = .dup2,
.fds = .{ fd.native(), newfd.native() },
});
}
pub fn inherit(self: *Actions, fd: bun.FileDescriptor) !void {
try self.dup2(fd, fd);
}
pub fn chdir(self: *Actions, path: []const u8) !void {
if (self.chdir_buf) |buf| {
bun.default_allocator.free(bun.span(buf));
}
self.chdir_buf = (try bun.default_allocator.dupeZ(u8, path)).ptr;
}
};
pub const Attr = struct {
detached: bool = false,
uid: if (Environment.isPosix) std.posix.uid_t else void = if (Environment.isPosix) std.math.maxInt(u32) else {},
gid: if (Environment.isPosix) std.posix.gid_t else void = if (Environment.isPosix) std.math.maxInt(u32) else {},
pub fn init() !Attr {
return Attr{};
}
pub fn deinit(_: *Attr) void {}
pub fn get(self: Attr) !u16 {
var flags: c_int = 0;
if (self.detached) {
flags |= bun.C.POSIX_SPAWN_SETSID;
}
return @intCast(flags);
}
pub fn set(self: *Attr, flags: u16) !void {
self.detached = (flags & bun.c.POSIX_SPAWN_SETSID) != 0;
}
pub fn resetSignals(this: *Attr) !void {
_ = this;
}
};
};
// mostly taken from zig's posix_spawn.zig
pub const PosixSpawn = struct {
pub const WaitPidResult = struct {
pid: pid_t,
status: u32,
};
pub const PosixSpawnAttr = struct {
attr: system.posix_spawnattr_t,
pub fn init() !PosixSpawnAttr {
var attr: system.posix_spawnattr_t = undefined;
switch (errno(system.posix_spawnattr_init(&attr))) {
.SUCCESS => return PosixSpawnAttr{ .attr = attr },
.NOMEM => return error.SystemResources,
.INVAL => unreachable,
else => |err| return unexpectedErrno(err),
}
}
pub fn deinit(self: *PosixSpawnAttr) void {
_ = system.posix_spawnattr_destroy(&self.attr);
}
pub fn get(self: PosixSpawnAttr) !u16 {
var flags: c_short = undefined;
switch (errno(system.posix_spawnattr_getflags(&self.attr, &flags))) {
.SUCCESS => return @as(u16, @bitCast(flags)),
.INVAL => unreachable,
else => |err| return unexpectedErrno(err),
}
}
pub fn set(self: *PosixSpawnAttr, flags: u16) !void {
switch (errno(system.posix_spawnattr_setflags(&self.attr, @as(c_short, @bitCast(flags))))) {
.SUCCESS => return,
.INVAL => unreachable,
else => |err| return unexpectedErrno(err),
}
}
pub fn resetSignals(this: *PosixSpawnAttr) !void {
if (posix_spawnattr_reset_signals(&this.attr) != 0) {
return error.SystemResources;
}
}
extern fn posix_spawnattr_reset_signals(attr: *system.posix_spawnattr_t) c_int;
pub fn setCredentials(self: *PosixSpawnAttr, uid: ?system.uid_t, gid: ?system.gid_t) !void {
// macOS doesn't support setting uid/gid through posix_spawn attributes
// This is only supported on Linux through our custom posix_spawn_bun implementation
_ = self;
_ = uid;
_ = gid;
}
};
pub const PosixSpawnActions = struct {
actions: system.posix_spawn_file_actions_t,
pub fn init() !PosixSpawnActions {
var actions: system.posix_spawn_file_actions_t = undefined;
switch (errno(system.posix_spawn_file_actions_init(&actions))) {
.SUCCESS => return PosixSpawnActions{ .actions = actions },
.NOMEM => return error.SystemResources,
.INVAL => unreachable,
else => |err| return unexpectedErrno(err),
}
}
pub fn deinit(self: *PosixSpawnActions) void {
_ = system.posix_spawn_file_actions_destroy(&self.actions);
self.* = undefined;
}
pub fn open(self: *PosixSpawnActions, fd: bun.FileDescriptor, path: []const u8, flags: u32, mode: mode_t) !void {
const posix_path = try toPosixPath(path);
return self.openZ(fd, &posix_path, flags, mode);
}
pub fn openZ(self: *PosixSpawnActions, fd: bun.FileDescriptor, path: [*:0]const u8, flags: u32, mode: mode_t) !void {
switch (errno(system.posix_spawn_file_actions_addopen(&self.actions, fd.cast(), path, @as(c_int, @bitCast(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: *PosixSpawnActions, fd: bun.FileDescriptor) !void {
switch (errno(system.posix_spawn_file_actions_addclose(&self.actions, fd.cast()))) {
.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: *PosixSpawnActions, fd: bun.FileDescriptor, newfd: bun.FileDescriptor) !void {
if (fd == newfd) {
return self.inherit(fd);
}
switch (errno(system.posix_spawn_file_actions_adddup2(&self.actions, fd.cast(), newfd.cast()))) {
.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: *PosixSpawnActions, fd: bun.FileDescriptor) !void {
switch (errno(system.posix_spawn_file_actions_addinherit_np(&self.actions, fd.cast()))) {
.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: *PosixSpawnActions, path: []const u8) !void {
const posix_path = try toPosixPath(path);
return self.chdirZ(&posix_path);
}
// deliberately not pub
fn chdirZ(self: *PosixSpawnActions, path: [*:0]const u8) !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 const Actions = if (Environment.isLinux) BunSpawn.Actions else PosixSpawnActions;
pub const Attr = if (Environment.isLinux) BunSpawn.Attr else PosixSpawnAttr;
const BunSpawnRequest = extern struct {
chdir_buf: ?[*:0]u8 = null,
detached: bool = false,
actions: ActionsList = .{},
uid: std.posix.uid_t,
gid: std.posix.gid_t,
const ActionsList = extern struct {
ptr: ?[*]const BunSpawn.Action = null,
len: usize = 0,
};
extern fn posix_spawn_bun(
pid: *c_int,
path: [*:0]const u8,
request: *const BunSpawnRequest,
argv: [*:null]?[*:0]const u8,
envp: [*:null]?[*:0]const u8,
) isize;
pub fn spawn(
path: [*:0]const u8,
req_: BunSpawnRequest,
argv: [*:null]?[*:0]const u8,
envp: [*:null]?[*:0]const u8,
) Maybe(pid_t) {
var req = req_;
var pid: c_int = 0;
const rc = posix_spawn_bun(&pid, path, &req, argv, envp);
if (comptime bun.Environment.allow_assert)
bun.sys.syslog("posix_spawn_bun({s}) = {d} ({d})", .{
bun.span(argv[0] orelse ""),
rc,
pid,
});
if (rc == 0) {
return Maybe(pid_t){ .result = @intCast(pid) };
}
return Maybe(pid_t){
.err = .{
.errno = @as(bun.sys.Error.Int, @truncate(@intFromEnum(@as(std.c.E, @enumFromInt(rc))))),
.syscall = .posix_spawn,
.path = bun.span(argv[0] orelse ""),
},
};
}
};
pub fn spawnZ(
path: [*:0]const u8,
actions: ?Actions,
attr: ?Attr,
argv: [*:null]?[*:0]const u8,
envp: [*:null]?[*:0]const u8,
) Maybe(pid_t) {
if (comptime Environment.isLinux) {
return BunSpawnRequest.spawn(
path,
.{
.actions = if (actions) |act| .{
.ptr = act.actions.items.ptr,
.len = act.actions.items.len,
} else .{
.ptr = null,
.len = 0,
},
.chdir_buf = if (actions) |a| a.chdir_buf else null,
.detached = if (attr) |a| a.detached else false,
.uid = if (attr) |a| a.uid else std.math.maxInt(u32),
.gid = if (attr) |a| a.gid else std.math.maxInt(u32),
},
argv,
envp,
);
}
var pid: pid_t = undefined;
const rc = system.posix_spawn(
&pid,
path,
if (actions) |a| &a.actions else null,
if (attr) |a| &a.attr else null,
argv,
envp,
);
if (comptime bun.Environment.allow_assert)
bun.sys.syslog("posix_spawn({s}) = {d} ({d})", .{
path,
rc,
pid,
});
// Unlike most syscalls, posix_spawn returns 0 on success and an errno on failure.
// That is why bun.sys.getErrno() is not used here, since that checks for -1.
if (rc == 0) {
return Maybe(pid_t){ .result = pid };
}
return Maybe(pid_t){
.err = .{
.errno = @as(bun.sys.Error.Int, @truncate(@intFromEnum(@as(std.c.E, @enumFromInt(rc))))),
.syscall = .posix_spawn,
.path = bun.asByteSlice(path),
},
};
}
/// Use this version of the `waitpid` wrapper if you spawned your child process using `posix_spawn`
/// or `posix_spawnp` syscalls.
/// See also `std.posix.waitpid` for an alternative if your child process was spawned via `fork` and
/// `execve` method.
pub fn waitpid(pid: pid_t, flags: u32) Maybe(WaitPidResult) {
const PidStatus = c_int;
var status: PidStatus = 0;
while (true) {
const rc = system.waitpid(pid, &status, @as(c_int, @intCast(flags)));
switch (errno(rc)) {
.SUCCESS => return Maybe(WaitPidResult){
.result = .{
.pid = @as(pid_t, @intCast(rc)),
.status = @as(u32, @bitCast(status)),
},
},
.INTR => continue,
else => return JSC.Maybe(WaitPidResult).errnoSys(rc, .waitpid).?,
}
}
}
/// Same as waitpid, but also returns resource usage information.
pub fn wait4(pid: pid_t, flags: u32, usage: ?*std.posix.rusage) Maybe(WaitPidResult) {
const PidStatus = c_int;
var status: PidStatus = 0;
while (true) {
const rc = system.wait4(pid, &status, @as(c_int, @intCast(flags)), usage);
switch (errno(rc)) {
.SUCCESS => return Maybe(WaitPidResult){
.result = .{
.pid = @as(pid_t, @intCast(rc)),
.status = @as(u32, @bitCast(status)),
},
},
.INTR => continue,
else => return JSC.Maybe(WaitPidResult).errnoSys(rc, .waitpid).?,
}
}
}
pub const process = @import("process.zig");
pub const Process = process.Process;
pub const SpawnOptions = process.SpawnOptions;
pub const Status = process.Status;
pub const sync = process.sync;
pub const spawnProcess = process.spawnProcess;
pub const WindowsSpawnResult = process.WindowsSpawnResult;
pub const PosixSpawnResult = process.PosixSpawnResult;
pub const SpawnProcessResult = process.SpawnProcessResult;
pub const WindowsSpawnOptions = process.WindowsSpawnOptions;
pub const Rusage = process.Rusage;
pub const Stdio = @import("spawn/stdio.zig").Stdio;
};