Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemathinc
GitHub Repository: sagemathinc/wapython
Path: blob/main/core/posix-node/src/fork_exec.zig
1067 views
1
const c = @import("c.zig");
2
const node = @import("node.zig");
3
const std = @import("std");
4
const clib = @cImport({
5
@cDefine("struct__OSUnalignedU16", "uint16_t");
6
@cDefine("struct__OSUnalignedU32", "uint32_t");
7
@cDefine("struct__OSUnalignedU64", "uint64_t");
8
@cInclude("fcntl.h");
9
@cInclude("sys/wait.h");
10
@cInclude("unistd.h");
11
@cInclude("stdlib.h");
12
});
13
const util = @import("util.zig");
14
15
pub fn register(env: c.napi_env, exports: c.napi_value) !void {
16
try node.registerFunction(env, exports, "fork_exec", forkExec);
17
try node.registerFunction(env, exports, "set_inheritable", set_inheritable_impl);
18
try node.registerFunction(env, exports, "is_inheritable", is_inheritable_impl);
19
}
20
21
const Errors = error{ CloseError, CWDError, DupError, Dup2Error, ForkError, ExecError, SetInheritableReadFlags, SetInheritableSETFD, SetWasiFdError };
22
23
// {
24
// exec_array: string[];
25
// argv: string[];
26
// envp: string[];
27
// cwd: string;
28
// p2cread: number;
29
// p2cwrite: number;
30
// c2pread: number;
31
// c2pwrite: number;
32
// errread: number;
33
// errwrite: number;
34
// errpipe_read: number;
35
// errpipe_write: number;
36
// err_map: { [native: number]: number };
37
// }
38
39
fn forkExec(env: c.napi_env, info: c.napi_callback_info) callconv(.C) c.napi_value {
40
const args = node.getArgv(env, info, 1) catch return null;
41
const opts = args[0];
42
return forkExec1(env, opts) catch return null;
43
}
44
45
fn forkExec1(env: c.napi_env, opts: c.napi_value) !c.napi_value {
46
const exec_array = try node.getNamedProperty(env, opts, "exec_array", "exec_array field (a string[])");
47
var exec_array_c = try node.valueToArrayOfStrings(env, exec_array, "exec_array");
48
defer util.freeArrayOfStrings(exec_array_c);
49
50
const argv = try node.getNamedProperty(env, opts, "argv", "argv field (a string[])");
51
var argv_c = try node.valueToArrayOfStrings(env, argv, "argv");
52
defer util.freeArrayOfStrings(argv_c);
53
54
const envp = try node.getNamedProperty(env, opts, "envp", "envp field (a string[])");
55
var envp_c = try node.valueToArrayOfStrings(env, envp, "envp");
56
defer util.freeArrayOfStrings(envp_c);
57
58
// TODO: what about utf-8 and unicode?
59
const cwd = try node.getNamedProperty(env, opts, "cwd", "cwd field (a string)");
60
var cwd_c = try node.valueToString(env, cwd, "current working directory");
61
defer std.c.free(cwd_c);
62
63
const WASI_FD_INFO = try node.getNamedProperty(env, opts, "WASI_FD_INFO", "WASI_FD_INFO field (a string)");
64
var WASI_FD_INFO_c = try node.valueToString(env, WASI_FD_INFO, "WASI_FD_INFO field");
65
defer std.c.free(WASI_FD_INFO_c);
66
67
// stdin (=p2c = python to c), stdout (=c2p = c to python), stderr = (err):
68
const p2cread = try i32Prop(env, opts, "p2cread");
69
const p2cwrite = try i32Prop(env, opts, "p2cwrite");
70
const c2pread = try i32Prop(env, opts, "c2pread");
71
const c2pwrite = try i32Prop(env, opts, "c2pwrite");
72
const errread = try i32Prop(env, opts, "errread");
73
const errwrite = try i32Prop(env, opts, "errwrite");
74
// and also the special errpipe:
75
const errpipe_read = try i32Prop(env, opts, "errpipe_read");
76
const errpipe_write = try i32Prop(env, opts, "errpipe_write");
77
78
// whether or not to close file descriptors
79
const close_fds = try i32Prop(env, opts, "close_fds");
80
// fd's to not close
81
const fds_to_keep = try node.getNamedProperty(env, opts, "fds_to_keep", "file descriptors to keep");
82
var fds_to_keep_len: u32 = undefined;
83
const fds_to_keep_c = try node.valueToArrayOfI32(env, fds_to_keep, "fds_to_keep", &fds_to_keep_len);
84
defer std.c.free(fds_to_keep_c);
85
86
// err_map
87
const err_map = try node.getNamedProperty(env, opts, "err_map", "native to wasm error mapping");
88
var err_map_len: u32 = undefined;
89
const err_map_c = try node.valueToArrayOfI32(env, err_map, "err_map", &err_map_len);
90
defer std.c.free(err_map_c);
91
92
///////////////////// ///////////////////// /////////////////////
93
// IMPORTANT! Do NOT use anything from the nodejs runtime below this point!
94
///////////////////// ///////////////////// /////////////////////
95
96
const pid = clib.fork();
97
if (pid == -1) {
98
// TODO: write to the error pipe.
99
node.throwErrno(env, "fork system call failed");
100
return null;
101
}
102
if (pid != 0) {
103
// parent -- we're done with everything we need to do here.
104
// NOTE: the way python uses fork_exec is that even if all the
105
// forks fail, we do NOT report an error directly. Instead,
106
// an error message is sent via a pipe.
107
return try node.create_i32(env, pid, "pid");
108
}
109
// We're the child.
110
111
// Get rid of all the other node async io and threads by closing
112
// the lib-uv event loop, which would otherwise cause random hangs.
113
try node.closeEventLoop(env);
114
doExec(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| {
115
std.debug.print("Error in doExec: {}\n", .{err});
116
// every exec failed, so we report this error over the pipe:
117
118
var buffer: [128]u8 = undefined;
119
var fba = std.heap.FixedBufferAllocator.init(&buffer);
120
const allocator = fba.allocator();
121
const errno = util.getErrno();
122
const errnoWasm = if (errno >= 0 and errno < err_map_len) err_map_c[@intCast(u32, errno)] else -1;
123
124
// Data format for errpipe_write: "exception name:hex errno:description"
125
// TODO: subtle bug still -- we need to somehow translate the error number,
126
// since the errno here is for node native and we need the wasi one...
127
const mesg = try std.fmt.allocPrint(
128
allocator,
129
"OSError:{x}:{s}",
130
.{ errnoWasm, if (err == Errors.ExecError) "exec" else "noexec" },
131
);
132
defer allocator.free(mesg);
133
if (clib.write(errpipe_write, @ptrCast([*]u8, mesg), mesg.len) == -1) {
134
// this is very unlikely to be visible and there isn't anything we can do.
135
std.debug.print("Error writing to pipe.\n", .{});
136
}
137
138
_ = clib.exit(0);
139
};
140
// impossible to get here...
141
unreachable;
142
}
143
144
fn i32Prop(env: c.napi_env, opts: c.napi_value, comptime prop: [:0]const u8) !i32 {
145
const x = try node.getNamedProperty(env, opts, prop, prop);
146
return try node.i32FromValue(env, x, prop);
147
}
148
149
fn setInheritable(fd: i32, inheritable: bool) !void {
150
var flags = clib.fcntl(fd, clib.F_GETFD, @intCast(c_int, 0));
151
if (flags < 0) {
152
return Errors.SetInheritableReadFlags;
153
}
154
if (inheritable) {
155
flags &= ~clib.FD_CLOEXEC; // clear FD_CLOEXEC bit
156
} else {
157
flags |= clib.FD_CLOEXEC; // set FD_CLOEXEC bit
158
}
159
if (clib.fcntl(fd, clib.F_SETFD, flags) == -1) {
160
return Errors.SetInheritableSETFD;
161
}
162
}
163
164
fn set_inheritable_impl(env: c.napi_env, info: c.napi_callback_info) callconv(.C) c.napi_value {
165
const args = node.getArgv(env, info, 2) catch return null;
166
const fd = node.i32FromValue(env, args[0], "fd") catch return null;
167
const inheritable = node.valueToBool(env, args[1], "inheritable") catch return null;
168
169
setInheritable(fd, inheritable) catch {
170
node.throwErrno(env, "set_inheritable call failed");
171
};
172
return null;
173
}
174
175
fn isInheritable(fd: i32) !bool {
176
var flags = clib.fcntl(fd, clib.F_GETFD, @intCast(c_int, 0));
177
if (flags < 0) {
178
return Errors.SetInheritableReadFlags;
179
}
180
// inheritable means the FD_CLOEXEC bit is NOT set in flags, i.e.,
181
// do not
182
return flags & clib.FD_CLOEXEC == 0;
183
}
184
185
fn is_inheritable_impl(env: c.napi_env, info: c.napi_callback_info) callconv(.C) c.napi_value {
186
const args = node.getArgv(env, info, 1) catch return null;
187
const fd = node.i32FromValue(env, args[0], "fd") catch return null;
188
const status = isInheritable(fd) catch {
189
node.throwErrno(env, "is_inheritable call failed");
190
return null;
191
};
192
return node.create_bool(env, status, "inheritable status") catch return null;
193
}
194
195
fn close(fd: i32) !void {
196
if (clib.close(fd) != 0) {
197
return Errors.CloseError;
198
}
199
}
200
201
fn dup(fd: i32) !i32 {
202
const new_fd = clib.dup(fd);
203
if (new_fd == -1) {
204
return Errors.DupError;
205
}
206
return new_fd;
207
}
208
209
fn dup2(fd: i32, new_fd: i32) !void {
210
if (clib.dup2(fd, new_fd) == -1) {
211
return Errors.Dup2Error;
212
}
213
}
214
215
fn 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 {
216
217
// TODO: bunch of stuff here regarding pipes and uid/gid. This is a direct port
218
// of child_exec from cpython's Modules/_posixsubprocess.c, with some
219
// comments copied to keep things anchored.
220
221
if (envp[0] == null) {
222
// set WASI_FD_INFO to WASI_FD_INFO_c so child has access to this information
223
// and wasi-js can reconstruct correct file descriptor table.
224
// In case envp_c is nontrivial, this should have already been done.
225
if (clib.setenv("WASI_FD_INFO", WASI_FD_INFO, 1) == -1) {
226
return Errors.SetWasiFdError;
227
}
228
}
229
230
if (fds_to_keep_len > 0) {
231
var i: usize = 0;
232
while (i < fds_to_keep_len) : (i += 1) {
233
if (fds_to_keep[i] != errpipe_write) {
234
try setInheritable(fds_to_keep[i], true);
235
}
236
}
237
}
238
239
// Close parent's pipe ends:
240
if (p2cwrite != -1) {
241
try close(p2cwrite);
242
}
243
if (c2pread != -1) {
244
try close(c2pread);
245
}
246
if (errread != -1) {
247
try close(errread);
248
}
249
try close(errpipe_read);
250
251
// When duping fds, if there arises a situation where one of the fds is
252
// either 0, 1 or 2, it is possible that it is overwritten (#12607).
253
var c2pwrite = _c2pwrite;
254
if (c2pwrite == 0) {
255
c2pwrite = try dup(c2pwrite);
256
try setInheritable(c2pwrite, false);
257
}
258
var errwrite = _errwrite;
259
while (errwrite == 0 or errwrite == 1) {
260
errwrite = try dup(errwrite);
261
try setInheritable(errwrite, false);
262
}
263
264
// Dup fds for child.
265
// dup2() removes the CLOEXEC flag but we must do it ourselves if dup2()
266
// would be a no-op (issue #10806).
267
if (p2cread == 0) {
268
try setInheritable(p2cread, true);
269
} else if (p2cread != -1) {
270
try dup2(p2cread, 0); // stdin
271
}
272
273
if (c2pwrite == 1) {
274
try setInheritable(c2pwrite, true);
275
} else if (c2pwrite != -1) {
276
try dup2(c2pwrite, 1); // stdout
277
}
278
279
if (errwrite == 2) {
280
try setInheritable(errwrite, true);
281
} else if (errwrite != -1) {
282
try dup2(errwrite, 2); // stderr
283
}
284
285
if (node.strlen(cwd) > 0) {
286
if (clib.chdir(cwd) != 0) {
287
return Errors.CWDError;
288
}
289
}
290
291
// TODO: child_umask
292
// TODO: call_setsid
293
// TODO: call_setgroups
294
// TODO: call_setgid
295
// TODO: call_setuid
296
297
if (close_fds != 0) {
298
closeOpenFds(3, errpipe_write, fds_to_keep, fds_to_keep_len);
299
}
300
301
// Make it so errpipe_write is closed when an exec succeeds
302
// This is the default, so this line is not necessary. But I'm
303
// keeping it to make things very clear for now.
304
try setInheritable(errpipe_write, false);
305
306
// Try each executable in turn until one of them works. In practice this
307
// is trying every possible location in the PATH.
308
var i: usize = 0;
309
while (exec_array[i] != null) : (i += 1) {
310
if (envp[0] != null) {
311
_ = clib.execve(exec_array[i], argv, envp);
312
} else {
313
_ = clib.execv(exec_array[i], argv);
314
}
315
// If we're here, it didn't work. Will try next one if possible...
316
}
317
return Errors.ExecError;
318
}
319
320
// stupidly inefficient since len is tiny in practice (todo?).
321
fn isInList(fd: i32, v: [*]i32, len: u32) bool {
322
var j: u32 = 0;
323
while (j < len) : (j += 1) {
324
if (v[j] == fd) {
325
return true;
326
}
327
}
328
return false;
329
}
330
331
fn closeOpenFds(start_fd: i32, errpipe_write: i32, fds_to_keep: [*]i32, fds_to_keep_len: u32) void {
332
var fd = start_fd;
333
while (fd < 256) : (fd += 1) {
334
if (fd == errpipe_write) continue;
335
if (!isInList(fd, fds_to_keep, fds_to_keep_len)) {
336
// ignore errors
337
_ = clib.close(fd);
338
}
339
}
340
}
341
342