Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemathinc
GitHub Repository: sagemathinc/wapython
Path: blob/main/core/posix-node/src/index.ts
1067 views
1
import { log } from "./logging";
2
3
// Map from nodejs to zig descriptions:
4
const nodeToZig = {
5
arm64: "aarch64",
6
x64: "x86_64",
7
linux: "linux-gnu",
8
darwin: "macos",
9
};
10
11
const name = `${nodeToZig[process.arch]}-${nodeToZig[process.platform]}`;
12
13
const LINUX_ONLY = [
14
"getresuid",
15
"getresgid",
16
"setresgid",
17
"setresuid",
18
"fexecve",
19
"_fexecve",
20
];
21
22
export interface Hostent {
23
h_name: string;
24
h_length: number;
25
h_addrtype: number;
26
h_addr_list: string[];
27
h_aliases: string[];
28
}
29
30
export interface Sockaddr {
31
sa_len: number;
32
sa_family: number;
33
sa_data: Buffer;
34
}
35
36
export interface Addrinfo extends Sockaddr {
37
ai_flags: number;
38
ai_family: number;
39
ai_socktype: number;
40
ai_protocol: number;
41
ai_addrlen: number;
42
ai_canonname?: string;
43
}
44
45
interface StatsVFS {
46
f_bsize: number;
47
f_frsize: number;
48
f_blocks: number;
49
f_bfree: number;
50
f_bavail: number;
51
f_files: number;
52
f_ffree: number;
53
f_favail: number;
54
f_fsid: number;
55
f_flag: number;
56
f_namemax: number;
57
}
58
59
interface Termios {
60
c_iflag: number;
61
c_oflag: number;
62
c_cflag: number;
63
c_lflag: number;
64
}
65
66
// Actual possibilities:
67
// | ["addclose", number]
68
// | ["addopen", number, number, number, number]
69
// | ["adddup2", number, number];
70
type PosixSpawnFileActions = any[];
71
72
interface PosixSpawnAttributes {
73
sched_priority?: number;
74
schedpolicy?: number;
75
flags?: number;
76
pgroup?: number;
77
sigmask?: Set<number> | number[];
78
sigdefault?: Set<number> | number[];
79
}
80
81
interface PosixFunctions {
82
// wrappers around some nodejs posix compat functions
83
getpid: () => number;
84
85
// constants
86
constants: { [name: string]: number };
87
88
// unistd:
89
alarm: (seconds: number) => number;
90
chroot: (path: string) => void;
91
getegid: () => number;
92
geteuid: () => number;
93
gethostname: () => string;
94
getpgid: (number) => number;
95
getpgrp: () => number;
96
getppid: () => number;
97
setpgid: (pid: number, pgid: number) => void;
98
setregid: (rgid: number, egid: number) => void;
99
setreuid: (ruid: number, euid: number) => void;
100
setsid: () => number;
101
setegid: (gid: number) => void;
102
seteuid: (uid: number) => void;
103
sethostname: (name: string) => void;
104
// Blocking sleep. This is sleep in *INTEGER* seconds not milliseconds.
105
sleep: (seconds: number) => number;
106
// Blocing sleep for up to 1 seconds, in microseconds; do NOT give input >= 1000000.
107
usleep: (microseconds: number) => number;
108
109
ttyname: (fd: number) => string;
110
dup: (oldfd: number) => number;
111
dup2: (oldfd: number, newfd: number) => number;
112
113
// Change the current directory at the C library / process level; this is not the same as process.chdir(...)!
114
chdir: (path: string) => void;
115
// Get the C library / process level cwd; in general not the same as process.cwd()!
116
getcwd: () => string;
117
118
fork: () => number;
119
120
// Completely stopping the libuv event loop might be useful in conjunction
121
// with fork/exec*.
122
close_event_loop: () => void;
123
124
pipe: () => { readfd: number; writefd: number };
125
pipe2: (flags: number) => { readfd: number; writefd: number };
126
127
getresuid: () => { ruid: number; euid: number; suid: number }; // linux only
128
getresgid: () => { rgid: number; egid: number; sgid: number }; // linux only
129
setresgid: (rgid: number, egid: number, sgid: number) => void; // linux only
130
setresuid: (ruid: number, euid: number, suid: number) => void; // linux only
131
132
// The following are useful, e.eg., for setting a fd to be nonblocking; this
133
// might be done with stdin or a socket. We use the first to implement the WASI
134
// function fd_fdstat_set_flags:
135
136
// Special case fcntl(fd, F_GETFL, int flags)
137
fcntlSetFlags: (fd: number, flags: number) => void;
138
// Special case fcntl(fd, F_SETFL, int flags)
139
fcntlGetFlags: (fd: number) => number;
140
141
// I might implement this at some point; a good way to test
142
// it would be via building and using the fnctlmodule.c Module
143
// of cpython:
144
// fnctl: (fd: number, cmd: number, buf: Buffer) => number;
145
146
execv: (pathname: string, argv: string[]) => number;
147
execvp: (file: string, argv: string[]) => number;
148
149
execve: (
150
pathname: string,
151
argv: string[],
152
env: { [key: string]: string } // more reasonable format to use from node.
153
) => number;
154
_execve: (
155
pathname: string,
156
argv: string[],
157
envp: string[] // same format at system call
158
) => number;
159
fexecve: (
160
// linux only
161
fd: number,
162
argv: string[],
163
env: { [key: string]: string }
164
) => number;
165
_fexecve: (fd: number, argv: string[], envp: string[]) => number; // linux only
166
167
// A fork_exec implementation that is as close as possible to the one in Modules/_posixsubprocess.c in the CPython.
168
// We need this for python-wasm. It forks, then execs each element of exec_array until one works. It also uses
169
// the file descriptor errpipe_write to communicate failure at doing all the stuff leading up to execv, using
170
// the protocol that cPython defines for this.
171
fork_exec: (args: {
172
exec_array: string[];
173
argv: string[];
174
envp: string[];
175
cwd: string;
176
p2cread: number;
177
p2cwrite: number;
178
c2pread: number;
179
c2pwrite: number;
180
errread: number;
181
errwrite: number;
182
errpipe_read: number;
183
errpipe_write: number;
184
fds_to_keep: number[];
185
err_map: number[]; // err_map[native errno] = wasm errno; needed to write correct hex code to errpipe_write.
186
WASI_FD_INFO: string; // this env variable is set after fork and before exec (ignored if envp is set); it is used by wasi-js.
187
}) => number;
188
// This is useful before using fork_exec. It calls fcntl twice.
189
set_inheritable: (fd: number, inheritable: boolean) => void;
190
is_inheritable: (fd: number) => boolean;
191
192
lockf: (fd: number, cmd: number, size: BigInt) => void;
193
194
// This posix call is interesting -- it lets you suspend a node.js
195
// process temporarily, which isn't a normal nodejs feature.
196
pause: () => number;
197
198
// NOTE: node.js has require('os').networkInterfaces(), but it is not
199
// equivalent to the system calls in net/if.h, e.g., because it only returns
200
// info about network interfaces that have been assigned an address.
201
if_indextoname: (ifindex: number) => string;
202
if_nametoindex: (ifname: string) => number;
203
// output is array of pairs (index, 'name')
204
if_nameindex: () => [number, string][];
205
206
// spawn
207
_posix_spawn: (
208
path: string,
209
fileActions,
210
attrs,
211
argv: string[],
212
envp: string[], // same format at system call
213
p_version: boolean
214
) => number;
215
216
posix_spawn: (
217
path: string,
218
fileActions: PosixSpawnFileActions | undefined | null,
219
attrs: PosixSpawnAttributes | undefined | null,
220
argv: string[],
221
envp: { [key: string]: string } | string[]
222
) => number;
223
224
posix_spawnp: (
225
path: string,
226
fileActions: PosixSpawnFileActions | undefined | null,
227
attrs: PosixSpawnAttributes | undefined | null,
228
argv: string[],
229
envp: { [key: string]: string } | string[]
230
) => number;
231
232
// wait
233
234
wait: () => {
235
wstatus: number;
236
ret: number;
237
};
238
wait3: (options: number) => {
239
wstatus: number;
240
ret: number; // pid of child if all goes well
241
// returning rusage data is NOT implemented.
242
};
243
244
// options is an or of these: constants.WNOHANG, constants.WUNTRACED, constants.WCONTINUED
245
waitpid: (
246
pid: number,
247
options: number
248
) => {
249
wstatus: number;
250
ret: number;
251
};
252
253
// other
254
login_tty: (fd: number) => void;
255
statvfs: (path: string) => StatsVFS;
256
fstatvfs: (fd: number) => StatsVFS;
257
ctermid: () => string;
258
259
// netdb:
260
gai_strerror: (errcode: number) => string;
261
hstrerror: (errcode: number) => string;
262
gethostbyname: (name: string) => Hostent;
263
gethostbyaddr: (addr: string) => Hostent; // addr is ipv4 or ipv6 (both are supported)
264
getaddrinfo: (
265
node: string,
266
service: string,
267
hints?: {
268
flags?: number;
269
family?: number;
270
socktype?: number;
271
protocol?: number;
272
}
273
) => Addrinfo[];
274
275
// Synchronous Posix network stack. This is very useful since it isn't available in
276
// node.js and is impossible to efficiently due without writing an extension module.
277
// Bind a socket to an address. Compare with https://github.com/JacobFischer/netlinkwrapper
278
accept: (socket: number) => { fd: number; sockaddr: Sockaddr };
279
bind: (socket: number, sockaddr: Sockaddr) => void;
280
connect: (socket: number, sockaddr: Sockaddr) => void;
281
getsockname: (socket: number) => Sockaddr;
282
getpeername: (socket: number) => Sockaddr;
283
listen: (socket: number, backlog: number) => void;
284
// Receives from network into the buffer and returns how many
285
// bytes were read. This mutates the buffer.
286
recv: (socket: number, buffer: Buffer, flags: number) => number;
287
// Writes to the network everything that is in the buffer.
288
send: (socket: number, buffer: Buffer, flags: number) => number;
289
// how is constants.SHUT_RD, constants.SHUT_WR, or constants.SHUT_RDWR
290
shutdown: (socket: number, how: number) => void;
291
// Create a socket. Returns the file descriptor
292
socket: (family: number, socktype: number, protocol: number) => number;
293
getsockopt: (
294
socket: number,
295
level: number,
296
option_name: number,
297
max_len: number
298
) => Buffer;
299
setsockopt: (
300
socket: number,
301
level: number,
302
option_name: number,
303
option_value: Buffer
304
) => void;
305
306
// termios sort of things; this is NOT done in a general way wrapping the api,
307
// but instead implements things that node doesn't provide.
308
// Blocking read of a single (wide!) character from stdin. This is something
309
// you call from a script, not from the node.js REPL, where it will immediately EOF.
310
// The point is this is a useful building block for creating your own terminal.
311
// See demo/terminal.js
312
getChar: () => string;
313
// Call this and then you can do this in node, even at the interactive node.js prompt
314
// INPUT: "b = Buffer.alloc(10); a = require('fs').readSync(0, b)"
315
// YOU: type a *single character*, and then its put at the beginning of the buffer b!
316
// Of course this changes the defaults for stdin in node.js, which could cause problems.
317
enableRawInput: () => void;
318
setEcho: (enabled: boolean) => void;
319
// Similar to enableRawInput but it only makes stdin non-blocking and does nothing else.
320
// This is precisely what you need when stdin is not interactive, e.g., when running
321
// a script, e.g., "python-wasm a.py", which makes it so input('...') in python works
322
// perfectly. (Right now, you have to use python-wasm --worker for interactive input in a script.)
323
makeStdinBlocking: () => void;
324
tcgetattr: (fd: number) => Termios;
325
tcsetattr: (fd: number, optional_actions: number, tio: Termios) => void;
326
327
// Call watchForSignal once to start watching for the given signal.
328
// Call getSignalState to find out whether that signal was triggered (and clear the state).
329
// ONLY SIGINT is currently implemented!
330
// This is useful because node's "process.on('SIGINT'" doesn't work when the main
331
// event loop is blocked by blocking WebAssembly code.
332
watchForSignal: (signal: number) => void;
333
getSignalState: (signal: number) => boolean;
334
335
pollSocket: (fd: number, events: number, timeout_ms: number) => void;
336
}
337
338
export type Posix = Partial<PosixFunctions>;
339
340
let mod: Posix = {};
341
let mod1: Posix = {};
342
try {
343
mod = require(`./${name}.node`);
344
345
if (process.platform != "linux") {
346
for (const name of LINUX_ONLY) {
347
delete mod[name];
348
}
349
}
350
351
mod.getpid = () => process.pid;
352
353
// provide some better public interfaces:
354
mod["getaddrinfo"] = (node, service, hints) => {
355
const f = mod["_getaddrinfo"];
356
if (f == null) throw Error("getaddrinfo is not implemented");
357
return f(
358
node,
359
service,
360
hints?.flags ?? 0,
361
hints?.family ?? 0,
362
hints?.socktype ?? 0,
363
hints?.protocol ?? 0
364
);
365
};
366
// I could do the JSON in the extension module, but is that really better?
367
mod["statvfs"] = (...args) => JSON.parse(mod["_statvfs"]?.(...args));
368
mod["fstatvfs"] = (...args) => JSON.parse(mod["_fstatvfs"]?.(...args));
369
370
mod["bind"] = (socket: number, sockaddr: Sockaddr) => {
371
return mod["_bind"](
372
socket,
373
sockaddr.sa_len,
374
sockaddr.sa_family,
375
sockaddr.sa_data
376
);
377
};
378
379
mod["connect"] = (socket: number, sockaddr: Sockaddr) => {
380
return mod["_connect"](
381
socket,
382
sockaddr.sa_len,
383
sockaddr.sa_family,
384
sockaddr.sa_data
385
);
386
};
387
388
for (const name of ["execve", "fexecve"]) {
389
const f = mod["_" + name];
390
if (f != null) {
391
mod[name] = (pathname, argv, env) => {
392
return f(pathname, argv, mapToStrings(env));
393
};
394
}
395
}
396
397
const { _posix_spawn } = mod;
398
if (_posix_spawn != null) {
399
for (const name of ["posix_spawn", "posix_spawnp"]) {
400
mod[name] = (
401
path,
402
fileActions,
403
attrs: PosixSpawnAttributes,
404
argv,
405
env
406
) => {
407
if (attrs == null) {
408
attrs = {};
409
} else {
410
// from Set([...]) to [...], which is easier to work with from Zig via node api.
411
if (attrs.sigmask != null) {
412
attrs.sigmask = Array.from(attrs.sigmask);
413
}
414
if (attrs.sigdefault != null) {
415
attrs.sigdefault = Array.from(attrs.sigdefault);
416
}
417
}
418
return _posix_spawn(
419
path,
420
fileActions ?? [],
421
attrs ?? {},
422
argv,
423
mapToStrings(env),
424
name.endsWith("spawnp")
425
);
426
};
427
}
428
}
429
430
for (const name in mod) {
431
exports[name] = mod1[name] = (...args) => {
432
if (name != "chdir") log(name, args);
433
const res = mod[name](...args);
434
if (name != "chdir") log(name, "returned", res);
435
return res;
436
};
437
}
438
exports["constants"] = mod1.constants = mod["getConstants"]?.();
439
} catch (_err) {}
440
441
export default mod1;
442
443
function is_array(obj: any): boolean {
444
return Object.prototype.toString.call(obj) === "[object Array]";
445
}
446
447
function mapToStrings(obj: object | string[]): string[] {
448
if (is_array(obj)) {
449
// already array of strings...
450
return obj as unknown as string[];
451
}
452
const v: string[] = [];
453
for (const key in obj) {
454
v.push(`${key}=${obj[key]}`);
455
}
456
return v;
457
}
458
459