Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemathinc
GitHub Repository: sagemathinc/wapython
Path: blob/main/core/wasi-js/src/wasi.ts
1067 views
1
/* MIT licensed. See README.md for copyright and history information.
2
3
For API docs about what these functions below are supposed to be, see
4
5
https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md
6
7
and a TODO is copy/paste most of that as comments below.
8
*/
9
10
11
12
import debug from "debug";
13
const log = debug("wasi");
14
const logOpen = debug("wasi:open"); // just log opening files, which is useful
15
16
// See the comment in packages/cpython/src/pyconfig.h
17
// In particular, until we patch cpython itself, it's really
18
// only safe to set this to 256. TODO: we plan to patch
19
// everything in cpython that falls back to 256 to instead
20
// use the value 32768.
21
const SC_OPEN_MAX = 32768;
22
23
import type {
24
WASIBindings,
25
WASIArgs,
26
WASIEnv,
27
WASIPreopenedDirs,
28
WASIConfig,
29
File,
30
} from "./types";
31
32
import { WASIError } from "./types";
33
34
import toBuffer from "typedarray-to-buffer";
35
36
import {
37
WASI_ESUCCESS,
38
WASI_EBADF,
39
WASI_EINVAL,
40
WASI_ENOSYS,
41
WASI_EPERM,
42
//WASI_ENOTCAPABLE,
43
WASI_FILETYPE_UNKNOWN,
44
WASI_FILETYPE_BLOCK_DEVICE,
45
WASI_FILETYPE_CHARACTER_DEVICE,
46
WASI_FILETYPE_DIRECTORY,
47
WASI_FILETYPE_REGULAR_FILE,
48
WASI_FILETYPE_SOCKET_STREAM,
49
WASI_FILETYPE_SYMBOLIC_LINK,
50
WASI_FILETYPE,
51
WASI_FDFLAG_APPEND,
52
WASI_FDFLAG_DSYNC,
53
WASI_FDFLAG_NONBLOCK,
54
WASI_FDFLAG_RSYNC,
55
WASI_FDFLAG_SYNC,
56
WASI_RIGHT_FD_DATASYNC,
57
WASI_RIGHT_FD_READ,
58
WASI_RIGHT_FD_SEEK,
59
WASI_RIGHT_FD_FDSTAT_SET_FLAGS,
60
WASI_RIGHT_FD_SYNC,
61
WASI_RIGHT_FD_TELL,
62
WASI_RIGHT_FD_WRITE,
63
WASI_RIGHT_FD_ADVISE,
64
WASI_RIGHT_FD_ALLOCATE,
65
WASI_RIGHT_PATH_CREATE_DIRECTORY,
66
WASI_RIGHT_PATH_CREATE_FILE,
67
WASI_RIGHT_PATH_LINK_SOURCE,
68
WASI_RIGHT_PATH_LINK_TARGET,
69
WASI_RIGHT_PATH_OPEN,
70
WASI_RIGHT_FD_READDIR,
71
WASI_RIGHT_PATH_READLINK,
72
WASI_RIGHT_PATH_RENAME_SOURCE,
73
WASI_RIGHT_PATH_RENAME_TARGET,
74
WASI_RIGHT_PATH_FILESTAT_GET,
75
WASI_RIGHT_PATH_FILESTAT_SET_SIZE,
76
WASI_RIGHT_PATH_FILESTAT_SET_TIMES,
77
WASI_RIGHT_FD_FILESTAT_GET,
78
WASI_RIGHT_FD_FILESTAT_SET_SIZE,
79
WASI_RIGHT_FD_FILESTAT_SET_TIMES,
80
WASI_RIGHT_PATH_SYMLINK,
81
WASI_RIGHT_PATH_REMOVE_DIRECTORY,
82
WASI_RIGHT_POLL_FD_READWRITE,
83
WASI_RIGHT_PATH_UNLINK_FILE,
84
RIGHTS_BLOCK_DEVICE_BASE,
85
RIGHTS_BLOCK_DEVICE_INHERITING,
86
RIGHTS_CHARACTER_DEVICE_BASE,
87
RIGHTS_CHARACTER_DEVICE_INHERITING,
88
RIGHTS_REGULAR_FILE_BASE,
89
RIGHTS_REGULAR_FILE_INHERITING,
90
RIGHTS_DIRECTORY_BASE,
91
RIGHTS_DIRECTORY_INHERITING,
92
RIGHTS_SOCKET_BASE,
93
RIGHTS_SOCKET_INHERITING,
94
RIGHTS_TTY_BASE,
95
RIGHTS_TTY_INHERITING,
96
WASI_CLOCK_MONOTONIC,
97
WASI_CLOCK_PROCESS_CPUTIME_ID,
98
WASI_CLOCK_REALTIME,
99
WASI_CLOCK_THREAD_CPUTIME_ID,
100
WASI_EVENTTYPE_CLOCK,
101
WASI_EVENTTYPE_FD_READ,
102
WASI_EVENTTYPE_FD_WRITE,
103
WASI_FILESTAT_SET_ATIM,
104
WASI_FILESTAT_SET_ATIM_NOW,
105
WASI_FILESTAT_SET_MTIM,
106
WASI_FILESTAT_SET_MTIM_NOW,
107
WASI_O_CREAT,
108
WASI_O_DIRECTORY,
109
WASI_O_EXCL,
110
WASI_O_TRUNC,
111
WASI_PREOPENTYPE_DIR,
112
WASI_STDIN_FILENO,
113
WASI_STDOUT_FILENO,
114
WASI_STDERR_FILENO,
115
ERROR_MAP,
116
SIGNAL_MAP,
117
WASI_WHENCE_CUR,
118
WASI_WHENCE_END,
119
WASI_WHENCE_SET,
120
} from "./constants";
121
122
const STDIN_DEFAULT_RIGHTS =
123
WASI_RIGHT_FD_DATASYNC |
124
WASI_RIGHT_FD_READ |
125
WASI_RIGHT_FD_SYNC |
126
WASI_RIGHT_FD_ADVISE |
127
WASI_RIGHT_FD_FILESTAT_GET |
128
WASI_RIGHT_POLL_FD_READWRITE;
129
const STDOUT_DEFAULT_RIGHTS =
130
WASI_RIGHT_FD_DATASYNC |
131
WASI_RIGHT_FD_WRITE |
132
WASI_RIGHT_FD_SYNC |
133
WASI_RIGHT_FD_ADVISE |
134
WASI_RIGHT_FD_FILESTAT_GET |
135
WASI_RIGHT_POLL_FD_READWRITE;
136
const STDERR_DEFAULT_RIGHTS = STDOUT_DEFAULT_RIGHTS;
137
138
// I don't know what this *should* be, but I'm
139
// adding things as they are expected/implemented.
140
export const SOCKET_DEFAULT_RIGHTS =
141
WASI_RIGHT_FD_DATASYNC |
142
WASI_RIGHT_FD_READ |
143
WASI_RIGHT_FD_WRITE |
144
WASI_RIGHT_FD_ADVISE |
145
WASI_RIGHT_FD_FILESTAT_GET |
146
WASI_RIGHT_POLL_FD_READWRITE |
147
WASI_RIGHT_FD_FDSTAT_SET_FLAGS;
148
149
const msToNs = (ms: number) => {
150
const msInt = Math.trunc(ms);
151
const decimal = BigInt(Math.round((ms - msInt) * 1000000));
152
const ns = BigInt(msInt) * BigInt(1000000);
153
return ns + decimal;
154
};
155
156
const nsToMs = (ns: number | bigint) => {
157
if (typeof ns === "number") {
158
ns = Math.trunc(ns);
159
}
160
const nsInt = BigInt(ns);
161
return Number(nsInt / BigInt(1000000));
162
};
163
164
const wrap =
165
<T extends Function>(f: T) =>
166
(...args: any[]) => {
167
try {
168
return f(...args);
169
} catch (err) {
170
// log("WASI error", err);
171
// console.trace(err);
172
let e: any = err;
173
174
// This is to support unionfs, e.g., in fd_write if a pipe
175
// breaks, then unionfs raises "Error: EBADF: bad file descriptor, write",
176
// but the relevant error is "prev: Error: EPIPE: broken pipe, write", which it saves.
177
while (e.prev != null) {
178
e = e.prev;
179
}
180
// If it's an error from the fs
181
if (e?.code && typeof e?.code === "string") {
182
return ERROR_MAP[e.code] || WASI_EINVAL;
183
}
184
// If it's a WASI error, we return it directly
185
if (e instanceof WASIError) {
186
return e.errno;
187
}
188
// Otherwise we let the error bubble up
189
throw e;
190
}
191
};
192
193
const stat = (wasi: WASI, fd: number): File => {
194
const entry = wasi.FD_MAP.get(fd);
195
// console.log("stat", { fd, entry, FD_MAP: wasi.FD_MAP });
196
// log("stat", { fd, entry, FD_MAP: wasi.FD_MAP });
197
if (!entry) {
198
throw new WASIError(WASI_EBADF);
199
}
200
if (entry.filetype === undefined) {
201
const stats = wasi.fstatSync(entry.real);
202
const { filetype, rightsBase, rightsInheriting } = translateFileAttributes(
203
wasi,
204
fd,
205
stats
206
);
207
entry.filetype = filetype as WASI_FILETYPE;
208
if (!entry.rights) {
209
entry.rights = {
210
base: rightsBase,
211
inheriting: rightsInheriting,
212
};
213
}
214
}
215
return entry;
216
};
217
218
const translateFileAttributes = (
219
wasi: WASI,
220
fd: number | undefined,
221
stats: any
222
) => {
223
switch (true) {
224
case stats.isBlockDevice():
225
return {
226
filetype: WASI_FILETYPE_BLOCK_DEVICE,
227
rightsBase: RIGHTS_BLOCK_DEVICE_BASE,
228
rightsInheriting: RIGHTS_BLOCK_DEVICE_INHERITING,
229
};
230
case stats.isCharacterDevice(): {
231
const filetype = WASI_FILETYPE_CHARACTER_DEVICE;
232
if (fd !== undefined && wasi.bindings.isTTY(fd)) {
233
return {
234
filetype,
235
rightsBase: RIGHTS_TTY_BASE,
236
rightsInheriting: RIGHTS_TTY_INHERITING,
237
};
238
}
239
return {
240
filetype,
241
rightsBase: RIGHTS_CHARACTER_DEVICE_BASE,
242
rightsInheriting: RIGHTS_CHARACTER_DEVICE_INHERITING,
243
};
244
}
245
case stats.isDirectory():
246
return {
247
filetype: WASI_FILETYPE_DIRECTORY,
248
rightsBase: RIGHTS_DIRECTORY_BASE,
249
rightsInheriting: RIGHTS_DIRECTORY_INHERITING,
250
};
251
case stats.isFIFO():
252
return {
253
filetype: WASI_FILETYPE_SOCKET_STREAM,
254
rightsBase: RIGHTS_SOCKET_BASE,
255
rightsInheriting: RIGHTS_SOCKET_INHERITING,
256
};
257
case stats.isFile():
258
return {
259
filetype: WASI_FILETYPE_REGULAR_FILE,
260
rightsBase: RIGHTS_REGULAR_FILE_BASE,
261
rightsInheriting: RIGHTS_REGULAR_FILE_INHERITING,
262
};
263
case stats.isSocket():
264
return {
265
filetype: WASI_FILETYPE_SOCKET_STREAM,
266
rightsBase: RIGHTS_SOCKET_BASE,
267
rightsInheriting: RIGHTS_SOCKET_INHERITING,
268
};
269
case stats.isSymbolicLink():
270
return {
271
filetype: WASI_FILETYPE_SYMBOLIC_LINK,
272
rightsBase: BigInt(0),
273
rightsInheriting: BigInt(0),
274
};
275
default:
276
return {
277
filetype: WASI_FILETYPE_UNKNOWN,
278
rightsBase: BigInt(0),
279
rightsInheriting: BigInt(0),
280
};
281
}
282
};
283
284
type Exports = {
285
[key: string]: any;
286
};
287
288
// const logToFile = (...args) => {
289
// require("fs").appendFileSync(
290
// "/tmp/wasi.log",
291
// args.map((x) => `${x}`).join(" ") + "\n"
292
// );
293
// };
294
295
let warnedAboutSleep = false;
296
297
// The js side of the wasi state.
298
interface State {
299
env: WASIEnv;
300
FD_MAP: Map<number, File>;
301
bindings: WASIBindings;
302
}
303
304
export default class WASI {
305
memory: WebAssembly.Memory;
306
view: DataView;
307
FD_MAP: Map<number, File>;
308
wasiImport: Exports;
309
bindings: WASIBindings;
310
// This sleep is in milliseconds; it's NOT the libc sleep!
311
sleep?: (milliseconds: number) => void;
312
lastStdin: number = 0;
313
getStdin?: (milliseconds?: number) => Buffer; // timeout milliseconds is never used
314
stdinBuffer?: Buffer;
315
sendStdout?: (Buffer) => void;
316
sendStderr?: (Buffer) => void;
317
env: WASIEnv = {};
318
319
getState(): State {
320
return { env: this.env, FD_MAP: this.FD_MAP, bindings: this.bindings };
321
}
322
323
setState(state: State) {
324
this.env = state.env;
325
this.FD_MAP = state.FD_MAP;
326
this.bindings = state.bindings;
327
}
328
329
fstatSync(real_fd: number) {
330
if (real_fd <= 2) {
331
try {
332
return this.bindings.fs.fstatSync(real_fd);
333
} catch (_) {
334
// In special case of stdin/stdout/stderr in some environments
335
// (e.g., windows under electron) some of the actual file descriptors
336
// aren't defined in the node process. We thus fake it, since we
337
// are virtualizing these in our code anyways.
338
const now = new Date();
339
return {
340
dev: 0,
341
mode: 8592,
342
nlink: 1,
343
uid: 0,
344
gid: 0,
345
rdev: 0,
346
blksize: 65536,
347
ino: 0,
348
size: 0,
349
blocks: 0,
350
atimeMs: now.valueOf(),
351
mtimeMs: now.valueOf(),
352
ctimeMs: now.valueOf(),
353
birthtimeMs: 0,
354
atime: new Date(),
355
mtime: new Date(),
356
ctime: new Date(),
357
birthtime: new Date(0),
358
};
359
}
360
}
361
// general case
362
return this.bindings.fs.fstatSync(real_fd);
363
}
364
365
constructor(wasiConfig: WASIConfig) {
366
this.sleep = wasiConfig.sleep;
367
this.getStdin = wasiConfig.getStdin;
368
this.sendStdout = wasiConfig.sendStdout;
369
this.sendStderr = wasiConfig.sendStderr;
370
// Destructure our wasiConfig
371
let preopens: WASIPreopenedDirs = {};
372
if (wasiConfig.preopens) {
373
preopens = wasiConfig.preopens;
374
}
375
376
if (wasiConfig && wasiConfig.env) {
377
this.env = wasiConfig.env;
378
}
379
let args: WASIArgs = [];
380
if (wasiConfig && wasiConfig.args) {
381
args = wasiConfig.args;
382
}
383
// @ts-ignore
384
this.memory = undefined;
385
386
// @ts-ignore
387
this.view = undefined;
388
this.bindings = wasiConfig.bindings;
389
const fs = this.bindings.fs;
390
this.FD_MAP = new Map([
391
[
392
WASI_STDIN_FILENO,
393
{
394
real: 0,
395
filetype: WASI_FILETYPE_CHARACTER_DEVICE,
396
// offset: BigInt(0),
397
rights: {
398
base: STDIN_DEFAULT_RIGHTS,
399
inheriting: BigInt(0),
400
},
401
path: "/dev/stdin",
402
},
403
],
404
[
405
WASI_STDOUT_FILENO,
406
{
407
real: 1,
408
filetype: WASI_FILETYPE_CHARACTER_DEVICE,
409
// offset: BigInt(0),
410
rights: {
411
base: STDOUT_DEFAULT_RIGHTS,
412
inheriting: BigInt(0),
413
},
414
path: "/dev/stdout",
415
},
416
],
417
[
418
WASI_STDERR_FILENO,
419
{
420
real: 2,
421
filetype: WASI_FILETYPE_CHARACTER_DEVICE,
422
// offset: BigInt(0),
423
rights: {
424
base: STDERR_DEFAULT_RIGHTS,
425
inheriting: BigInt(0),
426
},
427
path: "/dev/stderr",
428
},
429
],
430
]);
431
432
const path = this.bindings.path;
433
434
for (const [k, v] of Object.entries(preopens)) {
435
const real = fs.openSync(v, fs.constants.O_RDONLY);
436
const newfd = this.getUnusedFileDescriptor();
437
this.FD_MAP.set(newfd, {
438
real,
439
filetype: WASI_FILETYPE_DIRECTORY,
440
// offset: BigInt(0),
441
rights: {
442
base: RIGHTS_DIRECTORY_BASE,
443
inheriting: RIGHTS_DIRECTORY_INHERITING,
444
},
445
fakePath: k,
446
path: v,
447
});
448
}
449
450
const getiovs = (iovs: number, iovsLen: number) => {
451
// iovs* -> [iov, iov, ...]
452
// __wasi_ciovec_t {
453
// void* buf,
454
// size_t buf_len,
455
// }
456
457
this.refreshMemory();
458
459
const buffers = Array.from({ length: iovsLen }, (_, i) => {
460
const ptr = iovs + i * 8;
461
const buf = this.view.getUint32(ptr, true);
462
let bufLen = this.view.getUint32(ptr + 4, true);
463
// the mmap stuff in wasi tries to make this overwrite all
464
// allocated memory, so we cap it or things crash.
465
// TODO: maybe we need to allocate more memory? I don't know!!
466
if (bufLen > this.memory.buffer.byteLength - buf) {
467
// console.log({
468
// buf,
469
// bufLen,
470
// total_memory: this.memory.buffer.byteLength,
471
// });
472
log("getiovs: warning -- truncating buffer to fit in memory");
473
bufLen = Math.min(
474
bufLen,
475
Math.max(0, this.memory.buffer.byteLength - buf)
476
);
477
}
478
try {
479
const buffer = new Uint8Array(this.memory.buffer, buf, bufLen);
480
return toBuffer(buffer);
481
} catch (err) {
482
// don't hide this
483
console.warn("WASI.getiovs -- invalid buffer", err);
484
// but at least make it so we don't totally kill WASM, so we
485
// get a traceback in the calling program (say python).
486
// TODO: Right now this sort of thing happens with aggressive use of mmap,
487
// but I plan to replace how mmap works with something that is viable.
488
throw new WASIError(WASI_EINVAL);
489
}
490
});
491
492
return buffers;
493
};
494
495
const CHECK_FD = (fd: number, rights: bigint) => {
496
// log("CHECK_FD", { fd, rights });
497
const stats = stat(this, fd);
498
// log("CHECK_FD", { stats });
499
if (rights !== BigInt(0) && (stats.rights.base & rights) === BigInt(0)) {
500
throw new WASIError(WASI_EPERM);
501
}
502
return stats;
503
};
504
const CPUTIME_START = this.bindings.hrtime();
505
506
const now = (clockId?: number) => {
507
switch (clockId) {
508
case WASI_CLOCK_MONOTONIC:
509
return this.bindings.hrtime();
510
case WASI_CLOCK_REALTIME:
511
return msToNs(Date.now());
512
case WASI_CLOCK_PROCESS_CPUTIME_ID:
513
case WASI_CLOCK_THREAD_CPUTIME_ID: // TODO -- this assumes 1 thread
514
return this.bindings.hrtime() - CPUTIME_START;
515
default:
516
return null;
517
}
518
};
519
520
this.wasiImport = {
521
args_get: (argv: number, argvBuf: number) => {
522
this.refreshMemory();
523
let coffset = argv;
524
let offset = argvBuf;
525
args.forEach((a) => {
526
this.view.setUint32(coffset, offset, true);
527
coffset += 4;
528
offset += Buffer.from(this.memory.buffer).write(`${a}\0`, offset);
529
});
530
return WASI_ESUCCESS;
531
},
532
533
args_sizes_get: (argc: number, argvBufSize: number) => {
534
this.refreshMemory();
535
this.view.setUint32(argc, args.length, true);
536
const size = args.reduce((acc, a) => acc + Buffer.byteLength(a) + 1, 0);
537
this.view.setUint32(argvBufSize, size, true);
538
return WASI_ESUCCESS;
539
},
540
541
environ_get: (environ: number, environBuf: number) => {
542
this.refreshMemory();
543
let coffset = environ;
544
let offset = environBuf;
545
Object.entries(this.env).forEach(([key, value]) => {
546
this.view.setUint32(coffset, offset, true);
547
coffset += 4;
548
offset += Buffer.from(this.memory.buffer).write(
549
`${key}=${value}\0`,
550
offset
551
);
552
});
553
return WASI_ESUCCESS;
554
},
555
556
environ_sizes_get: (environCount: number, environBufSize: number) => {
557
this.refreshMemory();
558
const envProcessed = Object.entries(this.env).map(
559
([key, value]) => `${key}=${value}\0`
560
);
561
const size = envProcessed.reduce(
562
(acc, e) => acc + Buffer.byteLength(e),
563
0
564
);
565
this.view.setUint32(environCount, envProcessed.length, true);
566
this.view.setUint32(environBufSize, size, true);
567
return WASI_ESUCCESS;
568
},
569
570
clock_res_get: (clockId: number, resolution: number) => {
571
let res;
572
switch (clockId) {
573
case WASI_CLOCK_MONOTONIC:
574
case WASI_CLOCK_PROCESS_CPUTIME_ID:
575
case WASI_CLOCK_THREAD_CPUTIME_ID: {
576
res = BigInt(1);
577
break;
578
}
579
case WASI_CLOCK_REALTIME: {
580
res = BigInt(1000);
581
break;
582
}
583
}
584
if (!res) {
585
throw Error("invalid clockId");
586
}
587
this.view.setBigUint64(resolution, res);
588
return WASI_ESUCCESS;
589
},
590
591
clock_time_get: (clockId: number, _precision: number, time: number) => {
592
this.refreshMemory();
593
const n = now(clockId);
594
if (n === null) {
595
return WASI_EINVAL;
596
}
597
this.view.setBigUint64(time, BigInt(n), true);
598
return WASI_ESUCCESS;
599
},
600
601
fd_advise: wrap(
602
(fd: number, _offset: number, _len: number, _advice: number) => {
603
CHECK_FD(fd, WASI_RIGHT_FD_ADVISE);
604
return WASI_ENOSYS;
605
}
606
),
607
608
fd_allocate: wrap((fd: number, _offset: number, _len: number) => {
609
CHECK_FD(fd, WASI_RIGHT_FD_ALLOCATE);
610
return WASI_ENOSYS;
611
}),
612
613
fd_close: wrap((fd: number) => {
614
const stats = CHECK_FD(fd, BigInt(0));
615
fs.closeSync(stats.real);
616
this.FD_MAP.delete(fd);
617
return WASI_ESUCCESS;
618
}),
619
620
fd_datasync: wrap((fd: number) => {
621
const stats = CHECK_FD(fd, WASI_RIGHT_FD_DATASYNC);
622
fs.fdatasyncSync(stats.real);
623
return WASI_ESUCCESS;
624
}),
625
626
fd_fdstat_get: wrap((fd: number, bufPtr: number) => {
627
const stats = CHECK_FD(fd, BigInt(0));
628
// console.log("fd_fdstat_get", fd, stats);
629
this.refreshMemory();
630
if (stats.filetype == null) {
631
throw Error("stats.filetype must be set");
632
}
633
this.view.setUint8(bufPtr, stats.filetype); // FILETYPE u8
634
this.view.setUint16(bufPtr + 2, 0, true); // FDFLAG u16
635
this.view.setUint16(bufPtr + 4, 0, true); // FDFLAG u16
636
this.view.setBigUint64(bufPtr + 8, BigInt(stats.rights.base), true); // u64
637
this.view.setBigUint64(
638
bufPtr + 8 + 8,
639
BigInt(stats.rights.inheriting),
640
true
641
); // u64
642
return WASI_ESUCCESS;
643
}),
644
645
/*
646
fd_fdstat_set_flags
647
648
Docs From upstream:
649
Adjust the flags associated with a file descriptor.
650
Note: This is similar to `fcntl(fd, F_SETFL, flags)` in POSIX.
651
652
This could be supported via posix-node in general (when available)
653
for sockets and stdin/stdout/stderr and genuine files (but not
654
for memfs, obviously). It's typically used by C programs for
655
locking files, but most importantly for us, for setting whether
656
reading from a fd is nonblocking (very important for stdin)
657
or should time out after a certain amount of time (e.g., very
658
important for a network socket).
659
660
For now we implement this in a very small number of cases
661
and return "Function not implemented" otherwise.
662
*/
663
fd_fdstat_set_flags: wrap((fd: number, flags: number) => {
664
// Are we allowed to set flags. This more means: "is it implemented?".
665
// Right now we only set this flag for sockets (that's done in the
666
// external kernel module in src/wasm/posix/socket.ts).
667
CHECK_FD(fd, WASI_RIGHT_FD_FDSTAT_SET_FLAGS);
668
if (this.wasiImport.sock_fcntlSetFlags(fd, flags) == 0) {
669
return WASI_ESUCCESS;
670
}
671
return WASI_ENOSYS;
672
}),
673
674
fd_fdstat_set_rights: wrap(
675
(fd: number, fsRightsBase: bigint, fsRightsInheriting: bigint) => {
676
const stats = CHECK_FD(fd, BigInt(0));
677
const nrb = stats.rights.base | fsRightsBase;
678
if (nrb > stats.rights.base) {
679
return WASI_EPERM;
680
}
681
const nri = stats.rights.inheriting | fsRightsInheriting;
682
if (nri > stats.rights.inheriting) {
683
return WASI_EPERM;
684
}
685
stats.rights.base = fsRightsBase;
686
stats.rights.inheriting = fsRightsInheriting;
687
return WASI_ESUCCESS;
688
}
689
),
690
691
fd_filestat_get: wrap((fd: number, bufPtr: number) => {
692
const stats = CHECK_FD(fd, WASI_RIGHT_FD_FILESTAT_GET);
693
const rstats = this.fstatSync(stats.real);
694
this.refreshMemory();
695
this.view.setBigUint64(bufPtr, BigInt(rstats.dev), true);
696
bufPtr += 8;
697
this.view.setBigUint64(bufPtr, BigInt(rstats.ino), true);
698
bufPtr += 8;
699
if (stats.filetype == null) {
700
throw Error("stats.filetype must be set");
701
}
702
this.view.setUint8(bufPtr, stats.filetype);
703
bufPtr += 8;
704
this.view.setBigUint64(bufPtr, BigInt(rstats.nlink), true);
705
bufPtr += 8;
706
this.view.setBigUint64(bufPtr, BigInt(rstats.size), true);
707
bufPtr += 8;
708
this.view.setBigUint64(bufPtr, msToNs(rstats.atimeMs), true);
709
bufPtr += 8;
710
this.view.setBigUint64(bufPtr, msToNs(rstats.mtimeMs), true);
711
bufPtr += 8;
712
this.view.setBigUint64(bufPtr, msToNs(rstats.ctimeMs), true);
713
return WASI_ESUCCESS;
714
}),
715
716
fd_filestat_set_size: wrap((fd: number, stSize: number) => {
717
const stats = CHECK_FD(fd, WASI_RIGHT_FD_FILESTAT_SET_SIZE);
718
fs.ftruncateSync(stats.real, Number(stSize));
719
return WASI_ESUCCESS;
720
}),
721
722
fd_filestat_set_times: wrap(
723
(fd: number, stAtim: number, stMtim: number, fstflags: number) => {
724
const stats = CHECK_FD(fd, WASI_RIGHT_FD_FILESTAT_SET_TIMES);
725
const rstats = this.fstatSync(stats.real);
726
let atim = rstats.atime;
727
let mtim = rstats.mtime;
728
const n = nsToMs(now(WASI_CLOCK_REALTIME)!);
729
const atimflags = WASI_FILESTAT_SET_ATIM | WASI_FILESTAT_SET_ATIM_NOW;
730
if ((fstflags & atimflags) === atimflags) {
731
return WASI_EINVAL;
732
}
733
const mtimflags = WASI_FILESTAT_SET_MTIM | WASI_FILESTAT_SET_MTIM_NOW;
734
if ((fstflags & mtimflags) === mtimflags) {
735
return WASI_EINVAL;
736
}
737
if ((fstflags & WASI_FILESTAT_SET_ATIM) === WASI_FILESTAT_SET_ATIM) {
738
atim = nsToMs(stAtim);
739
} else if (
740
(fstflags & WASI_FILESTAT_SET_ATIM_NOW) ===
741
WASI_FILESTAT_SET_ATIM_NOW
742
) {
743
atim = n;
744
}
745
if ((fstflags & WASI_FILESTAT_SET_MTIM) === WASI_FILESTAT_SET_MTIM) {
746
mtim = nsToMs(stMtim);
747
} else if (
748
(fstflags & WASI_FILESTAT_SET_MTIM_NOW) ===
749
WASI_FILESTAT_SET_MTIM_NOW
750
) {
751
mtim = n;
752
}
753
fs.futimesSync(stats.real, new Date(atim), new Date(mtim));
754
return WASI_ESUCCESS;
755
}
756
),
757
758
fd_prestat_get: wrap((fd: number, bufPtr: number) => {
759
const stats = CHECK_FD(fd, BigInt(0));
760
// log("fd_prestat_get", { fd, stats });
761
this.refreshMemory();
762
this.view.setUint8(bufPtr, WASI_PREOPENTYPE_DIR);
763
this.view.setUint32(
764
bufPtr + 4,
765
// TODO: this is definitely completely wrong unless preopens=/.
766
// NOTE: when both paths are blank, we return "". This is used by
767
// cPython on sockets. It used to raise an error here.
768
Buffer.byteLength(stats.fakePath ?? stats.path ?? ""),
769
true
770
);
771
return WASI_ESUCCESS;
772
}),
773
774
fd_prestat_dir_name: wrap(
775
(fd: number, pathPtr: number, pathLen: number) => {
776
const stats = CHECK_FD(fd, BigInt(0));
777
this.refreshMemory();
778
// NOTE: when both paths are blank, we return "". This is used by
779
// cPython on sockets. It used to raise an error here.
780
Buffer.from(this.memory.buffer).write(
781
stats.fakePath ?? stats.path ?? "" /* TODO: wrong in general!? */,
782
pathPtr,
783
pathLen,
784
"utf8"
785
);
786
return WASI_ESUCCESS;
787
}
788
),
789
790
fd_pwrite: wrap(
791
(
792
fd: number,
793
iovs: number,
794
iovsLen: number,
795
offset: number,
796
nwritten: number
797
) => {
798
const stats = CHECK_FD(fd, WASI_RIGHT_FD_WRITE | WASI_RIGHT_FD_SEEK);
799
let written = 0;
800
getiovs(iovs, iovsLen).forEach((iov) => {
801
let w = 0;
802
while (w < iov.byteLength) {
803
w += fs.writeSync(
804
stats.real,
805
iov,
806
w,
807
iov.byteLength - w,
808
Number(offset) + written + w
809
);
810
}
811
written += w;
812
});
813
this.view.setUint32(nwritten, written, true);
814
return WASI_ESUCCESS;
815
}
816
),
817
818
fd_write: wrap(
819
(fd: number, iovs: number, iovsLen: number, nwritten: number) => {
820
const stats = CHECK_FD(fd, WASI_RIGHT_FD_WRITE);
821
const IS_STDOUT = fd == WASI_STDOUT_FILENO;
822
const IS_STDERR = fd == WASI_STDERR_FILENO;
823
let written = 0;
824
getiovs(iovs, iovsLen).forEach((iov) => {
825
//console.log("fd_write", `"${new TextDecoder().decode(iov)}"`);
826
if (iov.byteLength == 0) return;
827
// log(
828
// `writing to fd=${fd}: `,
829
// JSON.stringify(new TextDecoder().decode(iov)),
830
// JSON.stringify(iov)
831
// );
832
if (IS_STDOUT && this.sendStdout != null) {
833
this.sendStdout(iov);
834
written += iov.byteLength;
835
} else if (IS_STDERR && this.sendStderr != null) {
836
this.sendStderr(iov);
837
written += iov.byteLength;
838
} else {
839
// useful to be absolutely sure if wasi is writing something:
840
// log(`write "${new TextDecoder().decode(iov)}" to ${fd})`);
841
let w = 0;
842
while (w < iov.byteLength) {
843
// log(`write ${iov.byteLength} bytes to fd=${stats.real}`);
844
const i = fs.writeSync(
845
stats.real,
846
iov,
847
w,
848
iov.byteLength - w,
849
stats.offset ? Number(stats.offset) : null
850
);
851
// log(`just wrote i=${i} bytes`);
852
if (stats.offset) stats.offset += BigInt(i);
853
w += i;
854
}
855
//console.log("fd_write", fd, " wrote ", w);
856
written += w;
857
}
858
});
859
this.view.setUint32(nwritten, written, true);
860
return WASI_ESUCCESS;
861
}
862
),
863
864
fd_pread: wrap(
865
(
866
fd: number,
867
iovs: number,
868
iovsLen: number,
869
offset: number,
870
nread: number
871
) => {
872
const stats = CHECK_FD(fd, WASI_RIGHT_FD_READ | WASI_RIGHT_FD_SEEK);
873
let read = 0;
874
outer: for (const iov of getiovs(iovs, iovsLen)) {
875
let r = 0;
876
while (r < iov.byteLength) {
877
const length = iov.byteLength - r;
878
const rr = fs.readSync(
879
stats.real,
880
iov,
881
r,
882
iov.byteLength - r,
883
Number(offset) + read + r
884
);
885
r += rr;
886
read += rr;
887
// If we don't read anything, or we receive less than requested
888
if (rr === 0 || rr < length) {
889
break outer;
890
}
891
}
892
read += r;
893
}
894
this.view.setUint32(nread, read, true);
895
return WASI_ESUCCESS;
896
}
897
),
898
899
fd_read: wrap(
900
(fd: number, iovs: number, iovsLen: number, nread: number) => {
901
const stats = CHECK_FD(fd, WASI_RIGHT_FD_READ);
902
const IS_STDIN = fd == WASI_STDIN_FILENO;
903
let read = 0;
904
// logToFile(
905
// `fd_read: ${IS_STDIN}, ${JSON.stringify(stats, (_, value) =>
906
// typeof value === "bigint" ? value.toString() : value
907
// )}, ${this.stdinBuffer?.length} ${this.stdinBuffer?.toString()}`
908
// );
909
// console.log("fd_read", fd, stats, IS_STDIN, this.getStdin != null);
910
outer: for (const iov of getiovs(iovs, iovsLen)) {
911
let r = 0;
912
while (r < iov.byteLength) {
913
let length = iov.byteLength - r;
914
let position =
915
IS_STDIN || stats.offset === undefined
916
? null
917
: Number(stats.offset);
918
let rr = 0;
919
if (IS_STDIN) {
920
if (this.getStdin != null) {
921
if (this.stdinBuffer == null) {
922
this.stdinBuffer = this.getStdin();
923
}
924
if (this.stdinBuffer != null) {
925
// just got stdin after waiting for it in poll_oneoff
926
// TODO: Do we need to limit length or iov will overflow?
927
// Or will the below just work fine? It might.
928
// Second remark -- we do not do anything special here to try to
929
// handle seeing EOF (ctrl+d) in the stream. No matter what I try,
930
// doing something here (e.g., returning 0 bytes read) doesn't
931
// properly work with libedit. So we leave it alone and let
932
// our slightly patched libedit handle control+d.
933
// In particular note to self -- **handling of control+d is done in libedit!**
934
rr = this.stdinBuffer.copy(iov);
935
if (rr == this.stdinBuffer.length) {
936
this.stdinBuffer = undefined;
937
} else {
938
this.stdinBuffer = this.stdinBuffer.slice(rr);
939
}
940
if (rr > 0) {
941
// we read from stdin.
942
this.lastStdin = new Date().valueOf();
943
}
944
}
945
} else {
946
// WARNING: might have to do something that burns 100% cpu... :-(
947
// though this is useful for debugging situations.
948
if (this.sleep == null && !warnedAboutSleep) {
949
warnedAboutSleep = true;
950
console.log(
951
"(cpu waiting for stdin: please define a way to sleep!) "
952
);
953
}
954
//while (rr == 0) {
955
try {
956
rr = fs.readSync(
957
stats.real, // fd
958
iov, // buffer
959
r, // offset
960
length, // length
961
position // position
962
);
963
} catch (_err) {}
964
if (rr == 0) {
965
this.shortPause();
966
} else {
967
this.lastStdin = new Date().valueOf();
968
}
969
//}
970
}
971
} else {
972
rr = fs.readSync(
973
stats.real, // fd
974
iov, // buffer
975
r, // offset
976
length, // length
977
position // position
978
);
979
}
980
// TODO: I'm not sure which type of files should have an offset yet.
981
// E.g., obviously a regular file should and obviously stdin (a character
982
// device) and a pipe (which has type WASI_FILETYPE_SOCKET_STREAM) does not.
983
if (stats.filetype == WASI_FILETYPE_REGULAR_FILE) {
984
stats.offset =
985
(stats.offset ? stats.offset : BigInt(0)) + BigInt(rr);
986
}
987
r += rr;
988
read += rr;
989
990
// If we don't read anything, or we receive less than requested
991
if (rr === 0 || rr < length) {
992
break outer;
993
}
994
}
995
}
996
// console.log("fd_read: nread=", read);
997
this.view.setUint32(nread, read, true);
998
return WASI_ESUCCESS;
999
}
1000
),
1001
1002
fd_readdir: wrap(
1003
(
1004
fd: number,
1005
bufPtr: number,
1006
bufLen: number,
1007
cookie: number,
1008
bufusedPtr: number
1009
) => {
1010
const stats = CHECK_FD(fd, WASI_RIGHT_FD_READDIR);
1011
// log("fd_readdir got stats = ", stats);
1012
this.refreshMemory();
1013
const entries = fs.readdirSync(stats.path, { withFileTypes: true });
1014
const startPtr = bufPtr;
1015
for (let i = Number(cookie); i < entries.length; i += 1) {
1016
const entry = entries[i];
1017
let nameLength = Buffer.byteLength(entry.name);
1018
if (bufPtr - startPtr > bufLen) {
1019
break;
1020
}
1021
this.view.setBigUint64(bufPtr, BigInt(i + 1), true);
1022
bufPtr += 8;
1023
if (bufPtr - startPtr > bufLen) {
1024
break;
1025
}
1026
// We use lstat instead of stat, since stat fails on broken links.
1027
// Also, stat resolves the link giving the wrong inode! On the other
1028
// hand, lstat works fine on non-links. This is wrong in upstream,
1029
// which breaks testing test_compileall.py in the python test suite,
1030
// due to doing os.scandir on a directory that contains a broken link.
1031
const rstats = fs.lstatSync(path.resolve(stats.path, entry.name));
1032
this.view.setBigUint64(bufPtr, BigInt(rstats.ino), true);
1033
bufPtr += 8;
1034
if (bufPtr - startPtr > bufLen) {
1035
break;
1036
}
1037
this.view.setUint32(bufPtr, nameLength, true);
1038
bufPtr += 4;
1039
if (bufPtr - startPtr > bufLen) {
1040
break;
1041
}
1042
let filetype;
1043
switch (true) {
1044
case rstats.isBlockDevice():
1045
filetype = WASI_FILETYPE_BLOCK_DEVICE;
1046
break;
1047
case rstats.isCharacterDevice():
1048
filetype = WASI_FILETYPE_CHARACTER_DEVICE;
1049
break;
1050
case rstats.isDirectory():
1051
filetype = WASI_FILETYPE_DIRECTORY;
1052
break;
1053
case rstats.isFIFO():
1054
filetype = WASI_FILETYPE_SOCKET_STREAM;
1055
break;
1056
case rstats.isFile():
1057
filetype = WASI_FILETYPE_REGULAR_FILE;
1058
break;
1059
case rstats.isSocket():
1060
filetype = WASI_FILETYPE_SOCKET_STREAM;
1061
break;
1062
case rstats.isSymbolicLink():
1063
filetype = WASI_FILETYPE_SYMBOLIC_LINK;
1064
break;
1065
default:
1066
filetype = WASI_FILETYPE_UNKNOWN;
1067
break;
1068
}
1069
this.view.setUint8(bufPtr, filetype);
1070
bufPtr += 1;
1071
bufPtr += 3; // padding
1072
if (bufPtr + nameLength >= startPtr + bufLen) {
1073
// It doesn't fit in the buffer
1074
break;
1075
}
1076
let memory_buffer = Buffer.from(this.memory.buffer);
1077
memory_buffer.write(entry.name, bufPtr);
1078
bufPtr += nameLength;
1079
}
1080
const bufused = bufPtr - startPtr;
1081
this.view.setUint32(bufusedPtr, Math.min(bufused, bufLen), true);
1082
return WASI_ESUCCESS;
1083
}
1084
),
1085
1086
fd_renumber: wrap((from: number, to: number) => {
1087
CHECK_FD(from, BigInt(0));
1088
CHECK_FD(to, BigInt(0));
1089
fs.closeSync((this.FD_MAP.get(from) as File).real);
1090
this.FD_MAP.set(from, this.FD_MAP.get(to) as File);
1091
this.FD_MAP.delete(to);
1092
return WASI_ESUCCESS;
1093
}),
1094
1095
fd_seek: wrap(
1096
(
1097
fd: number,
1098
offset: number | bigint,
1099
whence: number,
1100
newOffsetPtr: number
1101
) => {
1102
const stats = CHECK_FD(fd, WASI_RIGHT_FD_SEEK);
1103
this.refreshMemory();
1104
switch (whence) {
1105
case WASI_WHENCE_CUR:
1106
stats.offset =
1107
(stats.offset ? stats.offset : BigInt(0)) + BigInt(offset);
1108
break;
1109
case WASI_WHENCE_END:
1110
const { size } = this.fstatSync(stats.real);
1111
stats.offset = BigInt(size) + BigInt(offset);
1112
break;
1113
case WASI_WHENCE_SET:
1114
stats.offset = BigInt(offset);
1115
break;
1116
}
1117
if (stats.offset == null) {
1118
throw Error("stats.offset must be defined");
1119
}
1120
this.view.setBigUint64(newOffsetPtr, stats.offset, true);
1121
return WASI_ESUCCESS;
1122
}
1123
),
1124
1125
fd_tell: wrap((fd: number, offsetPtr: number) => {
1126
const stats = CHECK_FD(fd, WASI_RIGHT_FD_TELL);
1127
this.refreshMemory();
1128
if (!stats.offset) {
1129
stats.offset = BigInt(0);
1130
}
1131
this.view.setBigUint64(offsetPtr, stats.offset, true);
1132
return WASI_ESUCCESS;
1133
}),
1134
1135
fd_sync: wrap((fd: number) => {
1136
const stats = CHECK_FD(fd, WASI_RIGHT_FD_SYNC);
1137
fs.fsyncSync(stats.real);
1138
return WASI_ESUCCESS;
1139
}),
1140
1141
path_create_directory: wrap(
1142
(fd: number, pathPtr: number, pathLen: number) => {
1143
const stats = CHECK_FD(fd, WASI_RIGHT_PATH_CREATE_DIRECTORY);
1144
if (!stats.path) {
1145
return WASI_EINVAL;
1146
}
1147
this.refreshMemory();
1148
const p = Buffer.from(
1149
this.memory.buffer,
1150
pathPtr,
1151
pathLen
1152
).toString();
1153
fs.mkdirSync(path.resolve(stats.path, p));
1154
return WASI_ESUCCESS;
1155
}
1156
),
1157
1158
path_filestat_get: wrap(
1159
(
1160
fd: number,
1161
flags: number,
1162
pathPtr: number,
1163
pathLen: number,
1164
bufPtr: number
1165
) => {
1166
const stats = CHECK_FD(fd, WASI_RIGHT_PATH_FILESTAT_GET);
1167
if (!stats.path) {
1168
return WASI_EINVAL;
1169
}
1170
this.refreshMemory();
1171
const p = Buffer.from(
1172
this.memory.buffer,
1173
pathPtr,
1174
pathLen
1175
).toString();
1176
//console.log("path_filestat_get", p);
1177
let rstats;
1178
if (flags) {
1179
rstats = fs.statSync(path.resolve(stats.path, p));
1180
} else {
1181
// there is exactly one flag implemented called "__WASI_LOOKUPFLAGS_SYMLINK_FOLLOW";
1182
// it's 1 and is used to follow links, i.e.,
1183
// implement lstat -- this is ignored in upstream.
1184
// See zig/lib/libc/wasi/libc-bottom-half/cloudlibc/src/libc/sys/stat/fstatat.c
1185
rstats = fs.lstatSync(path.resolve(stats.path, p));
1186
}
1187
//console.log("path_filestat_get got", rstats)
1188
// NOTE: the output is the filestat struct as documented here
1189
// https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#-filestat-record
1190
// This does NOT even have a field for that. This is considered an open bug in WASI:
1191
// https://github.com/WebAssembly/wasi-filesystem/issues/34
1192
// That said, wasi does end up setting enough of st_mode so isdir works.
1193
this.view.setBigUint64(bufPtr, BigInt(rstats.dev), true);
1194
bufPtr += 8;
1195
this.view.setBigUint64(bufPtr, BigInt(rstats.ino), true);
1196
bufPtr += 8;
1197
this.view.setUint8(
1198
bufPtr,
1199
translateFileAttributes(this, undefined, rstats).filetype
1200
);
1201
bufPtr += 8;
1202
this.view.setBigUint64(bufPtr, BigInt(rstats.nlink), true);
1203
bufPtr += 8;
1204
this.view.setBigUint64(bufPtr, BigInt(rstats.size), true);
1205
bufPtr += 8;
1206
this.view.setBigUint64(bufPtr, msToNs(rstats.atimeMs), true);
1207
bufPtr += 8;
1208
this.view.setBigUint64(bufPtr, msToNs(rstats.mtimeMs), true);
1209
bufPtr += 8;
1210
this.view.setBigUint64(bufPtr, msToNs(rstats.ctimeMs), true);
1211
return WASI_ESUCCESS;
1212
}
1213
),
1214
1215
path_filestat_set_times: wrap(
1216
(
1217
fd: number,
1218
_dirflags: number,
1219
pathPtr: number,
1220
pathLen: number,
1221
stAtim: number,
1222
stMtim: number,
1223
fstflags: number
1224
) => {
1225
const stats = CHECK_FD(fd, WASI_RIGHT_PATH_FILESTAT_SET_TIMES);
1226
if (!stats.path) {
1227
return WASI_EINVAL;
1228
}
1229
this.refreshMemory();
1230
const rstats = this.fstatSync(stats.real);
1231
let atim = rstats.atime;
1232
let mtim = rstats.mtime;
1233
const n = nsToMs(now(WASI_CLOCK_REALTIME)!);
1234
const atimflags = WASI_FILESTAT_SET_ATIM | WASI_FILESTAT_SET_ATIM_NOW;
1235
if ((fstflags & atimflags) === atimflags) {
1236
return WASI_EINVAL;
1237
}
1238
const mtimflags = WASI_FILESTAT_SET_MTIM | WASI_FILESTAT_SET_MTIM_NOW;
1239
if ((fstflags & mtimflags) === mtimflags) {
1240
return WASI_EINVAL;
1241
}
1242
if ((fstflags & WASI_FILESTAT_SET_ATIM) === WASI_FILESTAT_SET_ATIM) {
1243
atim = nsToMs(stAtim);
1244
} else if (
1245
(fstflags & WASI_FILESTAT_SET_ATIM_NOW) ===
1246
WASI_FILESTAT_SET_ATIM_NOW
1247
) {
1248
atim = n;
1249
}
1250
if ((fstflags & WASI_FILESTAT_SET_MTIM) === WASI_FILESTAT_SET_MTIM) {
1251
mtim = nsToMs(stMtim);
1252
} else if (
1253
(fstflags & WASI_FILESTAT_SET_MTIM_NOW) ===
1254
WASI_FILESTAT_SET_MTIM_NOW
1255
) {
1256
mtim = n;
1257
}
1258
const p = Buffer.from(
1259
this.memory.buffer,
1260
pathPtr,
1261
pathLen
1262
).toString();
1263
fs.utimesSync(
1264
path.resolve(stats.path, p),
1265
new Date(atim),
1266
new Date(mtim)
1267
);
1268
return WASI_ESUCCESS;
1269
}
1270
),
1271
1272
path_link: wrap(
1273
(
1274
oldFd: number,
1275
_oldFlags: number,
1276
oldPath: number,
1277
oldPathLen: number,
1278
newFd: number,
1279
newPath: number,
1280
newPathLen: number
1281
) => {
1282
const ostats = CHECK_FD(oldFd, WASI_RIGHT_PATH_LINK_SOURCE);
1283
const nstats = CHECK_FD(newFd, WASI_RIGHT_PATH_LINK_TARGET);
1284
if (!ostats.path || !nstats.path) {
1285
return WASI_EINVAL;
1286
}
1287
this.refreshMemory();
1288
const op = Buffer.from(
1289
this.memory.buffer,
1290
oldPath,
1291
oldPathLen
1292
).toString();
1293
const np = Buffer.from(
1294
this.memory.buffer,
1295
newPath,
1296
newPathLen
1297
).toString();
1298
fs.linkSync(
1299
path.resolve(ostats.path, op),
1300
path.resolve(nstats.path, np)
1301
);
1302
return WASI_ESUCCESS;
1303
}
1304
),
1305
1306
path_open: wrap(
1307
(
1308
dirfd: number,
1309
_dirflags: number,
1310
pathPtr: number,
1311
pathLen: number,
1312
oflags: number,
1313
fsRightsBase: bigint | number,
1314
fsRightsInheriting: bigint | number,
1315
fsFlags: number,
1316
fdPtr: number
1317
) => {
1318
const stats = CHECK_FD(dirfd, WASI_RIGHT_PATH_OPEN);
1319
fsRightsBase = BigInt(fsRightsBase);
1320
fsRightsInheriting = BigInt(fsRightsInheriting);
1321
1322
const read =
1323
(fsRightsBase & (WASI_RIGHT_FD_READ | WASI_RIGHT_FD_READDIR)) !==
1324
BigInt(0);
1325
const write =
1326
(fsRightsBase &
1327
(WASI_RIGHT_FD_DATASYNC |
1328
WASI_RIGHT_FD_WRITE |
1329
WASI_RIGHT_FD_ALLOCATE |
1330
WASI_RIGHT_FD_FILESTAT_SET_SIZE)) !==
1331
BigInt(0);
1332
1333
let noflags;
1334
if (write && read) {
1335
noflags = fs.constants.O_RDWR;
1336
} else if (read) {
1337
noflags = fs.constants.O_RDONLY;
1338
} else if (write) {
1339
noflags = fs.constants.O_WRONLY;
1340
}
1341
1342
// fsRightsBase is needed here but perhaps we should do it in neededInheriting
1343
let neededBase = fsRightsBase | WASI_RIGHT_PATH_OPEN;
1344
let neededInheriting = fsRightsBase | fsRightsInheriting;
1345
1346
if ((oflags & WASI_O_CREAT) !== 0) {
1347
noflags |= fs.constants.O_CREAT;
1348
neededBase |= WASI_RIGHT_PATH_CREATE_FILE;
1349
}
1350
if ((oflags & WASI_O_DIRECTORY) !== 0) {
1351
noflags |= fs.constants.O_DIRECTORY;
1352
}
1353
if ((oflags & WASI_O_EXCL) !== 0) {
1354
noflags |= fs.constants.O_EXCL;
1355
}
1356
if ((oflags & WASI_O_TRUNC) !== 0) {
1357
noflags |= fs.constants.O_TRUNC;
1358
neededBase |= WASI_RIGHT_PATH_FILESTAT_SET_SIZE;
1359
}
1360
1361
// Convert file descriptor flags.
1362
if ((fsFlags & WASI_FDFLAG_APPEND) !== 0) {
1363
noflags |= fs.constants.O_APPEND;
1364
}
1365
if ((fsFlags & WASI_FDFLAG_DSYNC) !== 0) {
1366
if (fs.constants.O_DSYNC) {
1367
noflags |= fs.constants.O_DSYNC;
1368
} else {
1369
noflags |= fs.constants.O_SYNC;
1370
}
1371
neededInheriting |= WASI_RIGHT_FD_DATASYNC;
1372
}
1373
if ((fsFlags & WASI_FDFLAG_NONBLOCK) !== 0) {
1374
noflags |= fs.constants.O_NONBLOCK;
1375
}
1376
if ((fsFlags & WASI_FDFLAG_RSYNC) !== 0) {
1377
if (fs.constants.O_RSYNC) {
1378
noflags |= fs.constants.O_RSYNC;
1379
} else {
1380
noflags |= fs.constants.O_SYNC;
1381
}
1382
neededInheriting |= WASI_RIGHT_FD_SYNC;
1383
}
1384
if ((fsFlags & WASI_FDFLAG_SYNC) !== 0) {
1385
noflags |= fs.constants.O_SYNC;
1386
neededInheriting |= WASI_RIGHT_FD_SYNC;
1387
}
1388
if (
1389
write &&
1390
(noflags & (fs.constants.O_APPEND | fs.constants.O_TRUNC)) === 0
1391
) {
1392
neededInheriting |= WASI_RIGHT_FD_SEEK;
1393
}
1394
1395
this.refreshMemory();
1396
const p = Buffer.from(
1397
this.memory.buffer,
1398
pathPtr,
1399
pathLen
1400
).toString();
1401
if (p == "dev/tty") {
1402
// special case: "the terminal".
1403
// This is used, e.g., in the "less" program in open_tty in ttyin.c
1404
// It will work to make a new tty if using the native os, but when
1405
// using a worker thread or in browser, it's much simpler to just
1406
// return stdin, which works fine (I think).
1407
this.view.setUint32(fdPtr, WASI_STDIN_FILENO, true);
1408
return WASI_ESUCCESS;
1409
}
1410
logOpen("path_open", p);
1411
if (p.startsWith("proc/")) {
1412
// Immediate error -- otherwise stuff will try to read from this,
1413
// which just isn't implemented, and will hang forever.
1414
// E.g., cython does.
1415
throw new WASIError(WASI_EBADF);
1416
}
1417
const fullUnresolved = path.resolve(stats.path, p);
1418
// I don't know why the original code blocked .., but that breaks
1419
// applications (e.g., tar), and this seems like the wrong layer at which to
1420
// be imposing security?
1421
// if (path.relative(stats.path, fullUnresolved).startsWith("..")) {
1422
// return WASI_ENOTCAPABLE;
1423
// }
1424
let full;
1425
try {
1426
full = fs.realpathSync(fullUnresolved);
1427
// if (path.relative(stats.path, full).startsWith("..")) {
1428
// return WASI_ENOTCAPABLE;
1429
// }
1430
} catch (e) {
1431
if ((e as any)?.code === "ENOENT") {
1432
full = fullUnresolved;
1433
} else {
1434
// log("** openpath FAIL: p = ", p, e);
1435
throw e;
1436
}
1437
}
1438
/* check if the file is a directory (unless opening for write,
1439
* in which case the file may not exist and should be created) */
1440
let isDirectory;
1441
if (write) {
1442
try {
1443
isDirectory = fs.statSync(full).isDirectory();
1444
} catch (_err) {
1445
//console.log(_err)
1446
}
1447
}
1448
let realfd;
1449
if (!write && isDirectory) {
1450
realfd = fs.openSync(full, fs.constants.O_RDONLY);
1451
} else {
1452
// console.log(`fs.openSync("${full}", ${noflags})`);
1453
realfd = fs.openSync(full, noflags);
1454
}
1455
const newfd = this.getUnusedFileDescriptor();
1456
// log(`** openpath got fd: p='${p}', fd=${newfd}`);
1457
this.FD_MAP.set(newfd, {
1458
real: realfd,
1459
filetype: undefined,
1460
// offset: BigInt(0),
1461
rights: {
1462
base: neededBase,
1463
inheriting: neededInheriting,
1464
},
1465
path: full,
1466
});
1467
// calling state here does some consistency checks
1468
// and set the filetype entry in the record created above.
1469
stat(this, newfd);
1470
this.view.setUint32(fdPtr, newfd, true);
1471
return WASI_ESUCCESS;
1472
}
1473
),
1474
1475
path_readlink: wrap(
1476
(
1477
fd: number,
1478
pathPtr: number,
1479
pathLen: number,
1480
buf: number,
1481
bufLen: number,
1482
bufused: number
1483
) => {
1484
const stats = CHECK_FD(fd, WASI_RIGHT_PATH_READLINK);
1485
if (!stats.path) {
1486
return WASI_EINVAL;
1487
}
1488
this.refreshMemory();
1489
const p = Buffer.from(
1490
this.memory.buffer,
1491
pathPtr,
1492
pathLen
1493
).toString();
1494
const full = path.resolve(stats.path, p);
1495
const r = fs.readlinkSync(full);
1496
const used = Buffer.from(this.memory.buffer).write(r, buf, bufLen);
1497
this.view.setUint32(bufused, used, true);
1498
return WASI_ESUCCESS;
1499
}
1500
),
1501
1502
path_remove_directory: wrap(
1503
(fd: number, pathPtr: number, pathLen: number) => {
1504
const stats = CHECK_FD(fd, WASI_RIGHT_PATH_REMOVE_DIRECTORY);
1505
if (!stats.path) {
1506
return WASI_EINVAL;
1507
}
1508
this.refreshMemory();
1509
const p = Buffer.from(
1510
this.memory.buffer,
1511
pathPtr,
1512
pathLen
1513
).toString();
1514
fs.rmdirSync(path.resolve(stats.path, p));
1515
return WASI_ESUCCESS;
1516
}
1517
),
1518
1519
path_rename: wrap(
1520
(
1521
oldFd: number,
1522
oldPath: number,
1523
oldPathLen: number,
1524
newFd: number,
1525
newPath: number,
1526
newPathLen: number
1527
) => {
1528
const ostats = CHECK_FD(oldFd, WASI_RIGHT_PATH_RENAME_SOURCE);
1529
const nstats = CHECK_FD(newFd, WASI_RIGHT_PATH_RENAME_TARGET);
1530
if (!ostats.path || !nstats.path) {
1531
return WASI_EINVAL;
1532
}
1533
this.refreshMemory();
1534
const op = Buffer.from(
1535
this.memory.buffer,
1536
oldPath,
1537
oldPathLen
1538
).toString();
1539
const np = Buffer.from(
1540
this.memory.buffer,
1541
newPath,
1542
newPathLen
1543
).toString();
1544
fs.renameSync(
1545
path.resolve(ostats.path, op),
1546
path.resolve(nstats.path, np)
1547
);
1548
return WASI_ESUCCESS;
1549
}
1550
),
1551
1552
path_symlink: wrap(
1553
(
1554
oldPath: number,
1555
oldPathLen: number,
1556
fd: number,
1557
newPath: number,
1558
newPathLen: number
1559
) => {
1560
const stats = CHECK_FD(fd, WASI_RIGHT_PATH_SYMLINK);
1561
if (!stats.path) {
1562
return WASI_EINVAL;
1563
}
1564
this.refreshMemory();
1565
const op = Buffer.from(
1566
this.memory.buffer,
1567
oldPath,
1568
oldPathLen
1569
).toString();
1570
const np = Buffer.from(
1571
this.memory.buffer,
1572
newPath,
1573
newPathLen
1574
).toString();
1575
fs.symlinkSync(op, path.resolve(stats.path, np));
1576
return WASI_ESUCCESS;
1577
}
1578
),
1579
1580
path_unlink_file: wrap((fd: number, pathPtr: number, pathLen: number) => {
1581
const stats = CHECK_FD(fd, WASI_RIGHT_PATH_UNLINK_FILE);
1582
if (!stats.path) {
1583
return WASI_EINVAL;
1584
}
1585
this.refreshMemory();
1586
const p = Buffer.from(this.memory.buffer, pathPtr, pathLen).toString();
1587
fs.unlinkSync(path.resolve(stats.path, p));
1588
return WASI_ESUCCESS;
1589
}),
1590
1591
// poll_oneoff: Concurrently poll for the occurrence of a set of events.
1592
//
1593
// TODO: this is NOT implemented properly yet in general.
1594
// It does read all the data from sin, etc.
1595
// correctly now, but it doesn't actually work correctly
1596
// when there are multiple subscriptions.
1597
// It works for:
1598
// - one timer
1599
// - one file descriptor corresponding to a socket and one timer,
1600
// which is what poll with 1 fd and a timeout create.
1601
poll_oneoff: (
1602
sin: number,
1603
sout: number,
1604
nsubscriptions: number,
1605
neventsPtr: number
1606
) => {
1607
let nevents = 0;
1608
let name = "";
1609
1610
// May have to wait this long (this gets computed below in the WASI_EVENTTYPE_CLOCK case).
1611
1612
let waitTimeNs = BigInt(0);
1613
1614
let fd = -1;
1615
let fd_type: "read" | "write" = "read";
1616
let fd_timeout_ms = 0;
1617
1618
const startNs = BigInt(this.bindings.hrtime());
1619
this.refreshMemory();
1620
let last_sin = sin;
1621
for (let i = 0; i < nsubscriptions; i += 1) {
1622
const userdata = this.view.getBigUint64(sin, true);
1623
sin += 8;
1624
const type = this.view.getUint8(sin);
1625
sin += 1;
1626
sin += 7; // padding
1627
if (log.enabled) {
1628
if (type == WASI_EVENTTYPE_CLOCK) {
1629
name = "poll_oneoff (type=WASI_EVENTTYPE_CLOCK): ";
1630
} else if (type == WASI_EVENTTYPE_FD_READ) {
1631
name = "poll_oneoff (type=WASI_EVENTTYPE_FD_READ): ";
1632
} else {
1633
name = "poll_oneoff (type=WASI_EVENTTYPE_FD_WRITE): ";
1634
}
1635
log(name);
1636
}
1637
switch (type) {
1638
case WASI_EVENTTYPE_CLOCK: {
1639
// see packages/zig/dist/lib/libc/include/wasm-wasi-musl/wasi/api.h
1640
// for exactly how these values are encoded. I carefully looked
1641
// at that header and **this is definitely right**. Same with the fd
1642
// in the other case below.
1643
const clockid = this.view.getUint32(sin, true);
1644
sin += 4;
1645
sin += 4; // padding
1646
const timeout = this.view.getBigUint64(sin, true);
1647
sin += 8;
1648
//const precision = this.view.getBigUint64(sin, true);
1649
sin += 8;
1650
const subclockflags = this.view.getUint16(sin, true);
1651
sin += 2;
1652
sin += 6; // padding
1653
1654
const absolute = subclockflags === 1;
1655
if (log.enabled) {
1656
log(name, { clockid, timeout, absolute });
1657
}
1658
if (!absolute) {
1659
fd_timeout_ms = Number(timeout / BigInt(1000000));
1660
}
1661
1662
let e = WASI_ESUCCESS;
1663
const t = now(clockid);
1664
// logToFile(t, clockid, timeout, subclockflags, absolute);
1665
if (t == null) {
1666
e = WASI_EINVAL;
1667
} else {
1668
const end = absolute ? timeout : t + timeout;
1669
const waitNs = end - t;
1670
if (waitNs > waitTimeNs) {
1671
waitTimeNs = waitNs;
1672
}
1673
}
1674
1675
this.view.setBigUint64(sout, userdata, true);
1676
sout += 8;
1677
this.view.setUint16(sout, e, true); // error
1678
sout += 2; // pad offset 2
1679
this.view.setUint8(sout, WASI_EVENTTYPE_CLOCK);
1680
sout += 1; // pad offset 1
1681
sout += 5; // padding to 8
1682
1683
nevents += 1;
1684
1685
break;
1686
}
1687
case WASI_EVENTTYPE_FD_READ:
1688
case WASI_EVENTTYPE_FD_WRITE: {
1689
/*
1690
Look at
1691
lib/libc/wasi/libc-bottom-half/cloudlibc/src/libc/sys/select/pselect.c
1692
to see how poll_oneoff is actually used by wasi to implement pselect.
1693
It's also used in
1694
lib/libc/wasi/libc-bottom-half/cloudlibc/src/libc/poll/poll.c
1695
1696
"If none of the selected descriptors are ready for the
1697
requested operation, the pselect() or select() function shall
1698
block until at least one of the requested operations becomes
1699
ready, until the timeout occurs, or until interrupted by a signal."
1700
Thus what is supposed to happen below is supposed
1701
to block until the fd is ready to read from or write
1702
to, etc.
1703
1704
For now at least if reading from stdin then we block for a short amount
1705
of time if getStdin defined; otherwise, we at least *pause* for a moment
1706
(to avoid cpu burn) if this.sleep is available.
1707
*/
1708
fd = this.view.getUint32(sin, true);
1709
fd_type = type == WASI_EVENTTYPE_FD_READ ? "read" : "write";
1710
sin += 4;
1711
log(name, "fd =", fd);
1712
sin += 28;
1713
1714
this.view.setBigUint64(sout, userdata, true);
1715
sout += 8;
1716
this.view.setUint16(sout, WASI_ENOSYS, true); // error
1717
sout += 2; // pad offset 2
1718
this.view.setUint8(sout, type);
1719
sout += 1; // pad offset 3
1720
sout += 5; // padding to 8
1721
1722
nevents += 1;
1723
/*
1724
TODO: for now for stdin we are just doing a dumb hack.
1725
1726
We just do something really naive, which is "pause for a little while".
1727
It seems to work for every application I have so far, from Python to
1728
to ncurses, etc. This also makes it easy to have non-blocking sleep
1729
in node.js at the terminal without a worker thread, which is very nice!
1730
1731
Before I had it block here via getStdin when available, but that does not work
1732
in general; in particular, it breaks ncurses completely. In
1733
ncurses/tty/tty_update.c
1734
the following call is assumed not to block, and if it does, then ncurses
1735
interaction becomes totally broken:
1736
1737
select(SP_PARM->_checkfd + 1, &fdset, NULL, NULL, &ktimeout)
1738
1739
*/
1740
if (fd == WASI_STDIN_FILENO && WASI_EVENTTYPE_FD_READ == type) {
1741
this.shortPause();
1742
}
1743
1744
break;
1745
}
1746
default:
1747
return WASI_EINVAL;
1748
}
1749
1750
// Consistency check that we consumed exactly the right amount
1751
// of the __wasi_subscription_t. See zig/lib/libc/include/wasm-wasi-musl/wasi/api.h
1752
if (sin - last_sin != 48) {
1753
console.warn("*** BUG in wasi-js in poll_oneoff ", {
1754
i,
1755
sin,
1756
last_sin,
1757
diff: sin - last_sin,
1758
});
1759
}
1760
last_sin = sin;
1761
}
1762
1763
this.view.setUint32(neventsPtr, nevents, true);
1764
1765
if (nevents == 2 && fd >= 0) {
1766
const r = this.wasiImport.sock_pollSocket(fd, fd_type, fd_timeout_ms);
1767
if (r != WASI_ENOSYS) {
1768
// special implementation from outside
1769
return r;
1770
}
1771
// fall back to below
1772
}
1773
1774
// Account for the time it took to do everything above, which
1775
// can be arbitrarily long:
1776
if (waitTimeNs > 0) {
1777
waitTimeNs -= BigInt(this.bindings.hrtime()) - startNs;
1778
// logToFile("waitTimeNs", waitTimeNs);
1779
if (waitTimeNs >= 1000000) {
1780
if (this.sleep == null && !warnedAboutSleep) {
1781
warnedAboutSleep = true;
1782
console.log(
1783
"(100% cpu burning waiting for stdin: please define a way to sleep!) "
1784
);
1785
}
1786
if (this.sleep != null) {
1787
// We are running in a worker thread, and have *some way*
1788
// to synchronously pause execution of this thread. Yeah!
1789
const ms = nsToMs(waitTimeNs);
1790
this.sleep(ms);
1791
} else {
1792
// Use **horrible** 100% block and 100% cpu
1793
// wait, which might sort of work, but is obviously
1794
// a wrong nightmare. Unfortunately, this is the
1795
// only possible thing to do when not running in
1796
// a work thread.
1797
const end = BigInt(this.bindings.hrtime()) + waitTimeNs;
1798
while (BigInt(this.bindings.hrtime()) < end) {
1799
// burn your CPU!
1800
}
1801
}
1802
}
1803
}
1804
1805
return WASI_ESUCCESS;
1806
},
1807
1808
proc_exit: (rval: number) => {
1809
this.bindings.exit(rval);
1810
return WASI_ESUCCESS;
1811
},
1812
1813
proc_raise: (sig: number) => {
1814
if (!(sig in SIGNAL_MAP)) {
1815
return WASI_EINVAL;
1816
}
1817
this.bindings.kill(SIGNAL_MAP[sig]);
1818
return WASI_ESUCCESS;
1819
},
1820
1821
random_get: (bufPtr: number, bufLen: number) => {
1822
this.refreshMemory();
1823
this.bindings.randomFillSync(
1824
new Uint8Array(this.memory.buffer),
1825
bufPtr,
1826
bufLen
1827
);
1828
return WASI_ESUCCESS;
1829
// NOTE: upstream had "return WASI_ESUCCESS;" here, which I thought was
1830
// a major bug, since getrandom returns the *number of random bytes*.
1831
// However, I think instead this was a bug in musl or libc or zig or something,
1832
// which got fixed in version 0.10.0-dev.4161+dab5bb924, since with that
1833
// release returning anything instead of success (=0) here actually
1834
// (Before returning 0 made it so Python hung mysteriously on startup, which tooks
1835
// me days of suffering to figure out. In particular, Python startup
1836
// hangs at py_getrandom in bootstrap_hash.c.)
1837
// return bufLen;
1838
},
1839
1840
sched_yield() {
1841
// Single threaded environment
1842
// This is a no-op in JS
1843
return WASI_ESUCCESS;
1844
},
1845
1846
// The client could overwrite these sock_*; that's what
1847
// CoWasm does in injectFunctions in
1848
// packages/kernel/src/wasm/worker/posix-context.ts
1849
sock_recv() {
1850
return WASI_ENOSYS;
1851
},
1852
1853
sock_send() {
1854
return WASI_ENOSYS;
1855
},
1856
1857
sock_shutdown() {
1858
return WASI_ENOSYS;
1859
},
1860
1861
sock_fcntlSetFlags(_fd: number, _flags: number) {
1862
return WASI_ENOSYS;
1863
},
1864
1865
sock_pollSocket(
1866
_fd: number,
1867
_eventtype: "read" | "write",
1868
_timeout_ms: number
1869
) {
1870
return WASI_ENOSYS;
1871
},
1872
};
1873
1874
if (log.enabled) {
1875
// Wrap each of the imports to show the calls via the debug logger.
1876
// We ONLY do this if the logger is enabled, since it might
1877
// be expensive.
1878
Object.keys(this.wasiImport).forEach((key: string) => {
1879
const prevImport = this.wasiImport[key];
1880
this.wasiImport[key] = function (...args: any[]) {
1881
log(key, args);
1882
try {
1883
let result = prevImport(...args);
1884
log("result", result);
1885
return result;
1886
} catch (e) {
1887
log("error: ", e);
1888
throw e;
1889
}
1890
};
1891
});
1892
}
1893
}
1894
1895
shortPause() {
1896
if (this.sleep == null) return;
1897
const now = new Date().valueOf();
1898
if (now - this.lastStdin > 2000) {
1899
// We have *some way* to synchronously pause execution of
1900
// this thread, so we sleep a little to avoid burning
1901
// 100% cpu. But not right after reading input, since
1902
// otherwise typing feels laggy.
1903
// We can probably get rid of this entirely with a proper
1904
// wgetchar...
1905
this.sleep(50);
1906
}
1907
}
1908
1909
// return an unused file descriptor. It *will* be the smallest
1910
// available file descriptor, except we don't use 0,1,2
1911
getUnusedFileDescriptor(start = 3) {
1912
let fd = start;
1913
while (this.FD_MAP.has(fd)) {
1914
fd += 1;
1915
}
1916
if (fd > SC_OPEN_MAX) {
1917
throw Error("no available file descriptors");
1918
}
1919
return fd;
1920
}
1921
1922
refreshMemory() {
1923
// @ts-ignore
1924
if (!this.view || this.view.buffer.byteLength === 0) {
1925
this.view = new DataView(this.memory.buffer);
1926
}
1927
}
1928
1929
setMemory(memory: WebAssembly.Memory) {
1930
this.memory = memory;
1931
}
1932
1933
start(instance: WebAssembly.Instance, memory?: WebAssembly.Memory) {
1934
const exports = instance.exports;
1935
if (exports === null || typeof exports !== "object") {
1936
throw new Error(
1937
`instance.exports must be an Object. Received ${exports}.`
1938
);
1939
}
1940
if (memory == null) {
1941
memory = exports.memory as any;
1942
if (!(memory instanceof WebAssembly.Memory)) {
1943
throw new Error(
1944
`instance.exports.memory must be a WebAssembly.Memory. Recceived ${memory}.`
1945
);
1946
}
1947
}
1948
1949
this.setMemory(memory);
1950
if (exports._start) {
1951
(exports as any)._start();
1952
}
1953
}
1954
1955
private getImportNamespace(module: WebAssembly.Module): string {
1956
let namespace: string | null = null;
1957
for (let imp of WebAssembly.Module.imports(module)) {
1958
// We only check for the functions
1959
if (imp.kind !== "function") {
1960
continue;
1961
}
1962
// We allow functions in other namespaces other than wasi
1963
if (!imp.module.startsWith("wasi_")) {
1964
continue;
1965
}
1966
if (!namespace) {
1967
namespace = imp.module;
1968
} else {
1969
if (namespace !== imp.module) {
1970
throw new Error("Multiple namespaces detected.");
1971
}
1972
}
1973
}
1974
return namespace!;
1975
}
1976
1977
getImports(
1978
module: WebAssembly.Module
1979
): Record<string, Record<string, Function>> {
1980
let namespace = this.getImportNamespace(module);
1981
switch (namespace) {
1982
case "wasi_unstable":
1983
return {
1984
wasi_unstable: this.wasiImport,
1985
};
1986
case "wasi_snapshot_preview1":
1987
return {
1988
wasi_snapshot_preview1: this.wasiImport,
1989
};
1990
default:
1991
throw new Error(
1992
"Can't detect a WASI namespace for the WebAssembly Module"
1993
);
1994
}
1995
}
1996
1997
initWasiFdInfo() {
1998
// TODO: this is NOT used yet. It currently crashes.
1999
if (this.env["WASI_FD_INFO"] != null) {
2000
// If the environment variable WASI_FD_INFO is set to the
2001
// JSON version of a map from wasi fd's to real fd's, then
2002
// we also initialize FD_MAP with that, assuming these
2003
// are all inheritable file descriptors for ends of pipes.
2004
// This is something added for
2005
// python-wasm fork/exec support.
2006
const fdInfo = JSON.parse(this.env["WASI_FD_INFO"]);
2007
for (const wasi_fd in fdInfo) {
2008
console.log(wasi_fd);
2009
const fd = parseInt(wasi_fd);
2010
if (this.FD_MAP.has(fd)) {
2011
continue;
2012
}
2013
const real = fdInfo[wasi_fd];
2014
try {
2015
// check the fd really exists
2016
this.fstatSync(real);
2017
} catch (_err) {
2018
console.log("discarding ", { wasi_fd, real });
2019
continue;
2020
}
2021
const file = {
2022
real,
2023
filetype: WASI_FILETYPE_SOCKET_STREAM,
2024
rights: {
2025
base: STDIN_DEFAULT_RIGHTS, // TODO
2026
inheriting: BigInt(0),
2027
},
2028
} as File;
2029
this.FD_MAP.set(fd, file);
2030
}
2031
console.log("after initWasiFdInfo: ", this.FD_MAP);
2032
console.log("fdInfo = ", fdInfo);
2033
} else {
2034
console.log("no WASI_FD_INFO");
2035
}
2036
}
2037
}
2038
2039