Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemathinc
GitHub Repository: sagemathinc/wapython
Path: blob/main/core/kernel/src/wasm/posix/index.ts
1068 views
1
/*
2
3
NOTES:
4
- emscripten/src/library_syscall.js is useful inspiration in some cases!
5
*/
6
7
import forkExec from "./fork-exec";
8
import epoll from "./epoll";
9
import netdb from "./netdb";
10
import netif from "./netif";
11
import other from "./other";
12
import sched from "./sched";
13
import signal from "./signal";
14
import socket from "./socket";
15
import spawn from "./spawn";
16
import stdlib from "./stdlib";
17
import stdio from "./stdio";
18
import stat from "./stat";
19
import termios from "./termios";
20
import time from "./time";
21
import unistd from "./unistd";
22
import wait from "./wait";
23
import WASI from "wasi-js";
24
import { initConstants } from "./constants";
25
import SendToWasm from "../worker/send-to-wasm";
26
import RecvFromWasm from "../worker/recv-from-wasm";
27
import constants from "./constants";
28
import debug from "debug";
29
30
const logNotImplemented = debug("posix:not-implemented");
31
const logCall = debug("posix:call");
32
const logReturn = debug("posix:return");
33
const logError = debug("posix:error");
34
35
// For some reason this code
36
// import os; print(os.popen('ls').read())
37
// hangs when run in **linux only** under python-wasm, but not python-wasm-debug,
38
// except if I set any random env variable here... and then it doesn't hang.
39
// This is weird.
40
process.env.__STUPID_HACK__ = "";
41
42
export interface Context {
43
// IMPORTANT: All of the functionality that implements this and has persistent state,
44
// *must* store its state in this state object in such a way that the state object can
45
// be swapped out between calls. There's lot of code that does this by always referring
46
// to context.state and not capturing state itself directly. Messing this up
47
// can lead to segfaults when running webassembly subprocesses. You *can* initialize
48
// this state once at the top of the implementation though, since it is deep copied
49
// before exec, rather than reset (in posix-context.ts).
50
state: { [name: string]: any };
51
fs: FileSystem;
52
send: SendToWasm;
53
recv: RecvFromWasm;
54
wasi: WASI;
55
run: (args: string[]) => number;
56
process: {
57
getpid?: () => number;
58
getuid?: () => number;
59
pid?: number;
60
cwd?: () => string;
61
};
62
os: {
63
loadavg?: () => [number, number, number];
64
getPriority?: (pid?: number) => number;
65
setPriority?: (pid: number, priority?: number) => void;
66
platform?: () => // we care about darwin/linux/win32 for our runtime.
67
"darwin" | "linux" | "win32" | "aix" | "freebsd" | "openbsd" | "sunos";
68
};
69
child_process: {
70
spawnSync?: (command: string) => number;
71
};
72
// The WASM memory (so we can make sense of pointers efficiently).
73
memory: WebAssembly.Memory;
74
// Optional module that gets installed on Mac/Linux, but obviously not windows
75
// for which posix doesn't make sense.
76
posix: {
77
getpgid?: () => number;
78
constants?: { [code: string]: number };
79
chdir?: (string) => void;
80
// TODO...
81
};
82
free: (ptr: number) => void;
83
callFunction: (name: string, ...args) => number | undefined;
84
callWithString: (
85
func: string | { name: string; dll: string } | Function,
86
str?: string | string[],
87
...args
88
) => number | undefined;
89
getcwd: () => string;
90
sleep?: (milliseconds: number) => void;
91
noStdio: boolean;
92
}
93
94
// It might in theory be better if we used typescript to say exactly which functions
95
// are defined. That said, it's not like the WASM side cares about typescript.
96
export type PosixEnv = { [name: string]: Function };
97
98
export default function posix(context: Context): PosixEnv {
99
const P = {
100
...epoll(context),
101
...forkExec(context),
102
...netdb(context),
103
...netif(context),
104
...other(context),
105
...sched(context),
106
...signal(context),
107
...socket(context),
108
...spawn(context),
109
...stat(context),
110
...stdlib(context),
111
...stdio(context),
112
...time(context),
113
...termios(context),
114
...unistd(context),
115
...wait(context),
116
};
117
const Q: any = {};
118
119
let nativeErrnoToSymbol: { [code: number]: string } = {};
120
if (context.posix.constants != null) {
121
for (const symbol in context.posix.constants) {
122
nativeErrnoToSymbol[context.posix.constants[symbol]] = symbol;
123
}
124
}
125
function setErrnoFromNative(nativeErrno: number, name: string, args): void {
126
if (nativeErrno == 0 || isNaN(nativeErrno)) {
127
// TODO: could put a log or something in that name raised error with no code.
128
context.callFunction("setErrno", nativeErrno);
129
return;
130
}
131
// The error code comes from native posix, so we translate it to WASI first
132
const symbol = nativeErrnoToSymbol[nativeErrno];
133
if (symbol != null) {
134
const wasiErrno = constants[symbol];
135
if (wasiErrno != null) {
136
if (logError.enabled) {
137
logError({ name, nativeErrno, wasiErrno, symbol, args });
138
}
139
context.callFunction("setErrno", wasiErrno);
140
return;
141
}
142
}
143
144
const mesg =
145
symbol != null
146
? `WARNING in posix '${name}': Unable to map nativeErrno ${nativeErrno}: add ${symbol} to WASM posix constants in @cowasm/kernel`
147
: `WARNING in posix '${name}': Unable to map nativeErrno ${nativeErrno}: add native symbol corresponding to errno=${nativeErrno} to the posix-node package`;
148
console.warn(mesg);
149
logNotImplemented(mesg);
150
}
151
152
// It's critical to ensure the directories of the host env is the same as
153
// the WASM env, if meaningful or possible. This only matters right now
154
// under node.js, but is really critical there. Thus we wrap *all* posix calls
155
// in this syncdir below.
156
// TODO: optimize. This seems dangerously expensive.
157
let syncdir;
158
if (context.posix.chdir != null) {
159
syncdir = () => {
160
// TODO: it is expected that this may fail, e.g., if we are using a sandbox filesystem
161
// deal with this in a better way.
162
try {
163
context.posix.chdir?.(context.getcwd());
164
} catch (_err) {}
165
};
166
} else {
167
syncdir = () => {};
168
}
169
170
for (const name in P) {
171
Q[name] = (...args) => {
172
syncdir();
173
try {
174
logCall(name, args);
175
const ret = P[name](...args);
176
logReturn(name, ret);
177
return ret;
178
} catch (err) {
179
logError(name, err);
180
if (err.wasiErrno != null) {
181
context.callFunction("setErrno", err.wasiErrno);
182
} else if (err.code != null) {
183
setErrnoFromNative(parseInt(err.code), name, args);
184
} else {
185
// err.code not yet set (TODO), so we log and try heuristic.
186
// On error, for now -1 is returned, and errno should get set to some sort of error indicator
187
// TODO: how should we set errno?
188
// @ts-ignore -- this is just temporary while we sort out setting errno...
189
if (err.name == "NotImplementedError") {
190
// ENOSYS means "Function not implemented (POSIX.1-2001)."
191
context.callFunction("setErrno", constants.ENOSYS);
192
} else {
193
console.trace(
194
`WARNING: Posix library call to ${name} raised exception without error code. The raised error is '${err}'`
195
);
196
logNotImplemented(
197
`Posix call to ${name} raised exception without error code`,
198
err
199
);
200
}
201
}
202
return err.ret ?? -1;
203
}
204
};
205
}
206
Q.init = () => {
207
initConstants(context);
208
};
209
return Q;
210
}
211
212