feature. See also
. The project being documented here (as the example) is the Zig library itself.
Dispatch.spawn
fn spawn(ev: *Evented, options: process.SpawnOptions) process.SpawnError!Spawned
File
Code
fn spawn(ev: *Evented, options: process.SpawnOptions) process.SpawnError!Spawned {
// we must initially set CLOEXEC to avoid a race condition. If another thread
// is racing to spawn a different child process, we don't want it to inherit
// these FDs in any scenario; that would mean that, for instance, calls to
// `poll` from the parent would not report the child's stdout as closing when
// expected, since the other child may retain a reference to the write end of
// the pipe. So, we create the pipes with CLOEXEC initially. After fork, we
// need to do something in the new child to make sure we preserve the reference
// we want. We could use `fcntl` to remove CLOEXEC from the FD, but as it
// turns out, we `dup2` everything anyway, so there's no need!
const pipe_flags: c.O = .{ .CLOEXEC = true };
const stdin_pipe = if (options.stdin == .pipe) try pipe2(pipe_flags) else undefined;
errdefer if (options.stdin == .pipe) {
destroyPipe(stdin_pipe);
};
const stdout_pipe = if (options.stdout == .pipe) try pipe2(pipe_flags) else undefined;
errdefer if (options.stdout == .pipe) {
destroyPipe(stdout_pipe);
};
const stderr_pipe = if (options.stderr == .pipe) try pipe2(pipe_flags) else undefined;
errdefer if (options.stderr == .pipe) {
destroyPipe(stderr_pipe);
};
const any_ignore =
options.stdin == .ignore or options.stdout == .ignore or options.stderr == .ignore;
const dev_null_file = if (any_ignore) dev_null_file: {
ev.open_dev_null.once(ev, &openDevNullFile);
break :dev_null_file try ev.dev_null_file;
} else undefined;
const prog_pipe: [2]c.fd_t = if (options.progress_node.index != .none)
try pipe2(.{ .NONBLOCK = true, .CLOEXEC = true })
else
.{ -1, -1 };
errdefer destroyPipe(prog_pipe);
var arena_allocator = std.heap.ArenaAllocator.init(ev.allocator());
defer arena_allocator.deinit();
const arena = arena_allocator.allocator();
// and this allocator may be a libc allocator.
// I have personally observed the child process deadlocking when it tries
// to call malloc() due to a heap allocation between fork() and execve(),
// in musl v1.1.24.
// Additionally, we want to reduce the number of possible ways things
// can fail between fork() and execve().
// Therefore, we do all the allocation for the execve() before the fork().
// This means we must do the null-termination of argv and env vars here.
const argv_buf = try arena.allocSentinel(?[*:0]const u8, options.argv.len, null);
for (options.argv, 0..) |arg, i| argv_buf[i] = (try arena.dupeSentinel(u8, arg, 0)).ptr;
const env_block = env_block: {
const prog_fd: i32 = if (prog_pipe[1] == -1) -1 else prog_fileno;
if (options.environ_map) |environ_map| break :env_block try environ_map.createPosixBlock(arena, .{
.zig_progress_fd = prog_fd,
});
break :env_block try ev.environ.process_environ.createPosixBlock(arena, .{
.zig_progress_fd = prog_fd,
});
};
// It is closed by the child (via CLOEXEC) without writing if `execvpe` succeeds.
const err_pipe: [2]File = err_pipe: {
const err_pipe = try pipe2(.{ .CLOEXEC = true });
break :err_pipe .{
.{ .handle = err_pipe[0], .flags = .{ .nonblocking = false } },
.{ .handle = err_pipe[1], .flags = .{ .nonblocking = false } },
};
};
errdefer fileClose(ev, &err_pipe);
ev.scan_environ.once(ev, &scanEnviron);
const PATH = ev.environ.string.PATH orelse default_PATH;
const pid_result: c.pid_t = fork: {
const rc = c.fork();
switch (c.errno(rc)) {
.SUCCESS => break :fork @intCast(rc),
.AGAIN => return error.SystemResources,
.NOMEM => return error.SystemResources,
.NOSYS => return error.OperationUnsupported,
else => |err| return unexpectedErrno(err),
}
};
if (pid_result == 0) {
defer comptime unreachable;
const err = ev.setUpChild(.{
.stdin_pipe = stdin_pipe[0],
.stdout_pipe = stdout_pipe[1],
.stderr_pipe = stderr_pipe[1],
.dev_null_fd = dev_null_file.handle,
.prog_pipe = prog_pipe[1],
.argv_buf = argv_buf,
.env_block = env_block,
.PATH = PATH,
.spawn = options,
});
ev.writeAll(err_pipe[1], @ptrCast(&err)) catch {};
c.exit(1);
}
const pid: c.pid_t = @intCast(pid_result);
errdefer comptime unreachable;
fileClose(ev, err_pipe[1..2]);
if (options.stdin == .pipe) closeFd(stdin_pipe[0]);
if (options.stdout == .pipe) closeFd(stdout_pipe[1]);
if (options.stderr == .pipe) closeFd(stderr_pipe[1]);
if (prog_pipe[1] != -1) closeFd(prog_pipe[1]);
options.progress_node.setIpcFile(ev, .{ .handle = prog_pipe[0], .flags = .{ .nonblocking = true } });
return .{
.pid = pid,
.err_pipe = err_pipe[0],
.stdin = switch (options.stdin) {
.pipe => .{ .handle = stdin_pipe[1], .flags = .{ .nonblocking = false } },
else => null,
},
.stdout = switch (options.stdout) {
.pipe => .{ .handle = stdout_pipe[0], .flags = .{ .nonblocking = false } },
else => null,
},
.stderr = switch (options.stderr) {
.pipe => .{ .handle = stderr_pipe[0], .flags = .{ .nonblocking = false } },
else => null,
},
};
}