Path: blob/main/core/posix-node/src/fork_exec.zig
1396 views
const c = @import("c.zig");1const node = @import("node.zig");2const std = @import("std");3const clib = @cImport({4@cDefine("struct__OSUnalignedU16", "uint16_t");5@cDefine("struct__OSUnalignedU32", "uint32_t");6@cDefine("struct__OSUnalignedU64", "uint64_t");7@cInclude("fcntl.h");8@cInclude("sys/wait.h");9@cInclude("unistd.h");10@cInclude("stdlib.h");11});12const util = @import("util.zig");1314pub fn register(env: c.napi_env, exports: c.napi_value) !void {15try node.registerFunction(env, exports, "fork_exec", forkExec);16try node.registerFunction(env, exports, "set_inheritable", set_inheritable_impl);17try node.registerFunction(env, exports, "is_inheritable", is_inheritable_impl);18}1920const Errors = error{ CloseError, CWDError, DupError, Dup2Error, ForkError, ExecError, SetInheritableReadFlags, SetInheritableSETFD, SetWasiFdError };2122// {23// exec_array: string[];24// argv: string[];25// envp: string[];26// cwd: string;27// p2cread: number;28// p2cwrite: number;29// c2pread: number;30// c2pwrite: number;31// errread: number;32// errwrite: number;33// errpipe_read: number;34// errpipe_write: number;35// err_map: { [native: number]: number };36// }3738fn forkExec(env: c.napi_env, info: c.napi_callback_info) callconv(.C) c.napi_value {39const args = node.getArgv(env, info, 1) catch return null;40const opts = args[0];41return forkExec1(env, opts) catch return null;42}4344fn forkExec1(env: c.napi_env, opts: c.napi_value) !c.napi_value {45const exec_array = try node.getNamedProperty(env, opts, "exec_array", "exec_array field (a string[])");46var exec_array_c = try node.valueToArrayOfStrings(env, exec_array, "exec_array");47defer util.freeArrayOfStrings(exec_array_c);4849const argv = try node.getNamedProperty(env, opts, "argv", "argv field (a string[])");50var argv_c = try node.valueToArrayOfStrings(env, argv, "argv");51defer util.freeArrayOfStrings(argv_c);5253const envp = try node.getNamedProperty(env, opts, "envp", "envp field (a string[])");54var envp_c = try node.valueToArrayOfStrings(env, envp, "envp");55defer util.freeArrayOfStrings(envp_c);5657// TODO: what about utf-8 and unicode?58const cwd = try node.getNamedProperty(env, opts, "cwd", "cwd field (a string)");59var cwd_c = try node.valueToString(env, cwd, "current working directory");60defer std.c.free(cwd_c);6162const WASI_FD_INFO = try node.getNamedProperty(env, opts, "WASI_FD_INFO", "WASI_FD_INFO field (a string)");63var WASI_FD_INFO_c = try node.valueToString(env, WASI_FD_INFO, "WASI_FD_INFO field");64defer std.c.free(WASI_FD_INFO_c);6566// stdin (=p2c = python to c), stdout (=c2p = c to python), stderr = (err):67const p2cread = try i32Prop(env, opts, "p2cread");68const p2cwrite = try i32Prop(env, opts, "p2cwrite");69const c2pread = try i32Prop(env, opts, "c2pread");70const c2pwrite = try i32Prop(env, opts, "c2pwrite");71const errread = try i32Prop(env, opts, "errread");72const errwrite = try i32Prop(env, opts, "errwrite");73// and also the special errpipe:74const errpipe_read = try i32Prop(env, opts, "errpipe_read");75const errpipe_write = try i32Prop(env, opts, "errpipe_write");7677// whether or not to close file descriptors78const close_fds = try i32Prop(env, opts, "close_fds");79// fd's to not close80const fds_to_keep = try node.getNamedProperty(env, opts, "fds_to_keep", "file descriptors to keep");81var fds_to_keep_len: u32 = undefined;82const fds_to_keep_c = try node.valueToArrayOfI32(env, fds_to_keep, "fds_to_keep", &fds_to_keep_len);83defer std.c.free(fds_to_keep_c);8485// err_map86const err_map = try node.getNamedProperty(env, opts, "err_map", "native to wasm error mapping");87var err_map_len: u32 = undefined;88const err_map_c = try node.valueToArrayOfI32(env, err_map, "err_map", &err_map_len);89defer std.c.free(err_map_c);9091///////////////////// ///////////////////// /////////////////////92// IMPORTANT! Do NOT use anything from the nodejs runtime below this point!93///////////////////// ///////////////////// /////////////////////9495const pid = clib.fork();96if (pid == -1) {97// TODO: write to the error pipe.98node.throwErrno(env, "fork system call failed");99return null;100}101if (pid != 0) {102// parent -- we're done with everything we need to do here.103// NOTE: the way python uses fork_exec is that even if all the104// forks fail, we do NOT report an error directly. Instead,105// an error message is sent via a pipe.106return try node.create_i32(env, pid, "pid");107}108// We're the child.109110// Get rid of all the other node async io and threads by closing111// the lib-uv event loop, which would otherwise cause random hangs.112try node.closeEventLoop(env);113doExec(exec_array_c, argv_c, envp_c, cwd_c, p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite, errpipe_read, errpipe_write, close_fds, fds_to_keep_c, fds_to_keep_len, WASI_FD_INFO_c) catch |err| {114std.debug.print("Error in doExec: {}\n", .{err});115// every exec failed, so we report this error over the pipe:116117var buffer: [128]u8 = undefined;118var fba = std.heap.FixedBufferAllocator.init(&buffer);119const allocator = fba.allocator();120const errno = util.getErrno();121const errnoWasm = if (errno >= 0 and errno < err_map_len) err_map_c[@intCast(u32, errno)] else -1;122123// Data format for errpipe_write: "exception name:hex errno:description"124// TODO: subtle bug still -- we need to somehow translate the error number,125// since the errno here is for node native and we need the wasi one...126const mesg = try std.fmt.allocPrint(127allocator,128"OSError:{x}:{s}",129.{ errnoWasm, if (err == Errors.ExecError) "exec" else "noexec" },130);131defer allocator.free(mesg);132if (clib.write(errpipe_write, @ptrCast([*]u8, mesg), mesg.len) == -1) {133// this is very unlikely to be visible and there isn't anything we can do.134std.debug.print("Error writing to pipe.\n", .{});135}136137_ = clib.exit(0);138};139// impossible to get here...140unreachable;141}142143fn i32Prop(env: c.napi_env, opts: c.napi_value, comptime prop: [:0]const u8) !i32 {144const x = try node.getNamedProperty(env, opts, prop, prop);145return try node.i32FromValue(env, x, prop);146}147148fn setInheritable(fd: i32, inheritable: bool) !void {149var flags = clib.fcntl(fd, clib.F_GETFD, @intCast(c_int, 0));150if (flags < 0) {151return Errors.SetInheritableReadFlags;152}153if (inheritable) {154flags &= ~clib.FD_CLOEXEC; // clear FD_CLOEXEC bit155} else {156flags |= clib.FD_CLOEXEC; // set FD_CLOEXEC bit157}158if (clib.fcntl(fd, clib.F_SETFD, flags) == -1) {159return Errors.SetInheritableSETFD;160}161}162163fn set_inheritable_impl(env: c.napi_env, info: c.napi_callback_info) callconv(.C) c.napi_value {164const args = node.getArgv(env, info, 2) catch return null;165const fd = node.i32FromValue(env, args[0], "fd") catch return null;166const inheritable = node.valueToBool(env, args[1], "inheritable") catch return null;167168setInheritable(fd, inheritable) catch {169node.throwErrno(env, "set_inheritable call failed");170};171return null;172}173174fn isInheritable(fd: i32) !bool {175var flags = clib.fcntl(fd, clib.F_GETFD, @intCast(c_int, 0));176if (flags < 0) {177return Errors.SetInheritableReadFlags;178}179// inheritable means the FD_CLOEXEC bit is NOT set in flags, i.e.,180// do not181return flags & clib.FD_CLOEXEC == 0;182}183184fn is_inheritable_impl(env: c.napi_env, info: c.napi_callback_info) callconv(.C) c.napi_value {185const args = node.getArgv(env, info, 1) catch return null;186const fd = node.i32FromValue(env, args[0], "fd") catch return null;187const status = isInheritable(fd) catch {188node.throwErrno(env, "is_inheritable call failed");189return null;190};191return node.create_bool(env, status, "inheritable status") catch return null;192}193194fn close(fd: i32) !void {195if (clib.close(fd) != 0) {196return Errors.CloseError;197}198}199200fn dup(fd: i32) !i32 {201const new_fd = clib.dup(fd);202if (new_fd == -1) {203return Errors.DupError;204}205return new_fd;206}207208fn dup2(fd: i32, new_fd: i32) !void {209if (clib.dup2(fd, new_fd) == -1) {210return Errors.Dup2Error;211}212}213214fn doExec(exec_array: [*](?[*:0]u8), argv: [*](?[*:0]u8), envp: [*](?[*:0]u8), cwd: [*:0]u8, p2cread: i32, p2cwrite: i32, c2pread: i32, _c2pwrite: i32, errread: i32, _errwrite: i32, errpipe_read: i32, errpipe_write: i32, close_fds: i32, fds_to_keep: [*]i32, fds_to_keep_len: u32, WASI_FD_INFO: [*:0]u8) !void {215216// TODO: bunch of stuff here regarding pipes and uid/gid. This is a direct port217// of child_exec from cpython's Modules/_posixsubprocess.c, with some218// comments copied to keep things anchored.219220if (envp[0] == null) {221// set WASI_FD_INFO to WASI_FD_INFO_c so child has access to this information222// and wasi-js can reconstruct correct file descriptor table.223// In case envp_c is nontrivial, this should have already been done.224if (clib.setenv("WASI_FD_INFO", WASI_FD_INFO, 1) == -1) {225return Errors.SetWasiFdError;226}227}228229if (fds_to_keep_len > 0) {230var i: usize = 0;231while (i < fds_to_keep_len) : (i += 1) {232if (fds_to_keep[i] != errpipe_write) {233try setInheritable(fds_to_keep[i], true);234}235}236}237238// Close parent's pipe ends:239if (p2cwrite != -1) {240try close(p2cwrite);241}242if (c2pread != -1) {243try close(c2pread);244}245if (errread != -1) {246try close(errread);247}248try close(errpipe_read);249250// When duping fds, if there arises a situation where one of the fds is251// either 0, 1 or 2, it is possible that it is overwritten (#12607).252var c2pwrite = _c2pwrite;253if (c2pwrite == 0) {254c2pwrite = try dup(c2pwrite);255try setInheritable(c2pwrite, false);256}257var errwrite = _errwrite;258while (errwrite == 0 or errwrite == 1) {259errwrite = try dup(errwrite);260try setInheritable(errwrite, false);261}262263// Dup fds for child.264// dup2() removes the CLOEXEC flag but we must do it ourselves if dup2()265// would be a no-op (issue #10806).266if (p2cread == 0) {267try setInheritable(p2cread, true);268} else if (p2cread != -1) {269try dup2(p2cread, 0); // stdin270}271272if (c2pwrite == 1) {273try setInheritable(c2pwrite, true);274} else if (c2pwrite != -1) {275try dup2(c2pwrite, 1); // stdout276}277278if (errwrite == 2) {279try setInheritable(errwrite, true);280} else if (errwrite != -1) {281try dup2(errwrite, 2); // stderr282}283284if (node.strlen(cwd) > 0) {285if (clib.chdir(cwd) != 0) {286return Errors.CWDError;287}288}289290// TODO: child_umask291// TODO: call_setsid292// TODO: call_setgroups293// TODO: call_setgid294// TODO: call_setuid295296if (close_fds != 0) {297closeOpenFds(3, errpipe_write, fds_to_keep, fds_to_keep_len);298}299300// Make it so errpipe_write is closed when an exec succeeds301// This is the default, so this line is not necessary. But I'm302// keeping it to make things very clear for now.303try setInheritable(errpipe_write, false);304305// Try each executable in turn until one of them works. In practice this306// is trying every possible location in the PATH.307var i: usize = 0;308while (exec_array[i] != null) : (i += 1) {309if (envp[0] != null) {310_ = clib.execve(exec_array[i], argv, envp);311} else {312_ = clib.execv(exec_array[i], argv);313}314// If we're here, it didn't work. Will try next one if possible...315}316return Errors.ExecError;317}318319// stupidly inefficient since len is tiny in practice (todo?).320fn isInList(fd: i32, v: [*]i32, len: u32) bool {321var j: u32 = 0;322while (j < len) : (j += 1) {323if (v[j] == fd) {324return true;325}326}327return false;328}329330fn closeOpenFds(start_fd: i32, errpipe_write: i32, fds_to_keep: [*]i32, fds_to_keep_len: u32) void {331var fd = start_fd;332while (fd < 256) : (fd += 1) {333if (fd == errpipe_write) continue;334if (!isInList(fd, fds_to_keep, fds_to_keep_len)) {335// ignore errors336_ = clib.close(fd);337}338}339}340341342