@bytecodealliance/preview2-shim 0.14.1 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/nodejs/cli.js CHANGED
@@ -1,21 +1,31 @@
1
1
  import { argv, env, cwd } from "node:process";
2
2
  import {
3
+ ioCall,
3
4
  streams,
4
5
  inputStreamCreate,
5
6
  outputStreamCreate,
6
7
  } from "../io/worker-io.js";
7
- import { STDIN, STDOUT, STDERR } from "../io/calls.js";
8
+ import { INPUT_STREAM_CREATE, STDERR, STDIN, STDOUT } from "../io/calls.js";
8
9
  const { InputStream, OutputStream } = streams;
9
10
 
10
- export const _setEnv = env => void (_env = Object.entries(env));
11
- export const _setArgs = args => void (_args = args);
12
- export const _setCwd = cwd => void (_cwd = cwd);
13
- export const _setStdin = stdin => void (stdinStream = stdin);
14
- export const _setStdout = stdout => void (stdoutStream = stdout);
15
- export const _setStderr = stderr => void (stderrStream = stderr);
16
- export const _setTerminalStdin = terminalStdin => void (terminalStdinInstance = terminalStdin);
17
- export const _setTerminalStdout = terminalStdout => void (terminalStdoutInstance = terminalStdout);
18
- export const _setTerminalStderr = terminalStderr => void (terminalStderrInstance = terminalStderr);
11
+ export const _appendEnv = (env) => {
12
+ void (_env = [
13
+ ..._env.filter(([curKey]) => !(curKey in env)),
14
+ ...Object.entries(env),
15
+ ]);
16
+ }
17
+ export const _setEnv = (env) => void (_env = Object.entries(env));
18
+ export const _setArgs = (args) => void (_args = args);
19
+ export const _setCwd = (cwd) => void (_cwd = cwd);
20
+ export const _setStdin = (stdin) => void (stdinStream = stdin);
21
+ export const _setStdout = (stdout) => void (stdoutStream = stdout);
22
+ export const _setStderr = (stderr) => void (stderrStream = stderr);
23
+ export const _setTerminalStdin = (terminalStdin) =>
24
+ void (terminalStdinInstance = terminalStdin);
25
+ export const _setTerminalStdout = (terminalStdout) =>
26
+ void (terminalStdoutInstance = terminalStdout);
27
+ export const _setTerminalStderr = (terminalStderr) =>
28
+ void (terminalStderrInstance = terminalStderr);
19
29
 
20
30
  let _env = Object.entries(env),
21
31
  _args = argv.slice(1),
@@ -39,13 +49,19 @@ export const exit = {
39
49
  },
40
50
  };
41
51
 
42
- let stdinStream = inputStreamCreate(STDIN, 1);
43
- let stdoutStream = outputStreamCreate(STDOUT, 2);
44
- let stderrStream = outputStreamCreate(STDERR, 3);
52
+ // Stdin is created as a FILE descriptor
53
+ let stdinStream;
54
+ let stdoutStream = outputStreamCreate(STDOUT, 1);
55
+ let stderrStream = outputStreamCreate(STDERR, 2);
45
56
 
46
57
  export const stdin = {
47
58
  InputStream,
48
59
  getStdin() {
60
+ if (!stdinStream)
61
+ stdinStream = inputStreamCreate(
62
+ STDIN,
63
+ ioCall(INPUT_STREAM_CREATE | STDIN, null, null),
64
+ );
49
65
  return stdinStream;
50
66
  },
51
67
  };
@@ -1,20 +1,23 @@
1
- import { ioCall, createPoll, resolvedPoll } from "../io/worker-io.js";
2
- import * as calls from "../io/calls.js";
1
+ import { ioCall, createPoll } from "../io/worker-io.js";
2
+ import {
3
+ CLOCKS_NOW,
4
+ CLOCKS_INSTANT_SUBSCRIBE,
5
+ CLOCKS_DURATION_SUBSCRIBE,
6
+ } from "../io/calls.js";
3
7
 
4
8
  export const monotonicClock = {
5
9
  resolution() {
6
10
  return 1n;
7
11
  },
8
12
  now() {
9
- return ioCall(calls.CLOCKS_NOW);
13
+ return ioCall(CLOCKS_NOW, null, null);
10
14
  },
11
15
  subscribeInstant(instant) {
12
- return createPoll(calls.CLOCKS_INSTANT_SUBSCRIBE, null, instant);
16
+ return createPoll(CLOCKS_INSTANT_SUBSCRIBE, null, instant);
13
17
  },
14
18
  subscribeDuration(duration) {
15
19
  duration = BigInt(duration);
16
- if (duration === 0n) return resolvedPoll();
17
- return createPoll(calls.CLOCKS_DURATION_SUBSCRIBE, null, duration);
20
+ return createPoll(CLOCKS_DURATION_SUBSCRIBE, null, duration);
18
21
  },
19
22
  };
20
23
 
@@ -1,11 +1,12 @@
1
1
  import {
2
- ioCall,
2
+ earlyDispose,
3
3
  inputStreamCreate,
4
+ ioCall,
4
5
  outputStreamCreate,
6
+ registerDispose,
5
7
  } from "../io/worker-io.js";
6
8
  import { INPUT_STREAM_CREATE, OUTPUT_STREAM_CREATE } from "../io/calls.js";
7
9
  import { FILE } from "../io/calls.js";
8
- // import { environment } from "./cli.js";
9
10
  import {
10
11
  closeSync,
11
12
  constants,
@@ -35,6 +36,7 @@ import { platform } from "node:process";
35
36
  const symbolDispose = Symbol.dispose || Symbol.for("dispose");
36
37
 
37
38
  const isWindows = platform === "win32";
39
+ const isMac = platform === "darwin";
38
40
 
39
41
  const nsMagnitude = 1_000_000_000_000n;
40
42
  function nsToDateTime(ns) {
@@ -54,29 +56,29 @@ function lookupType(obj) {
54
56
  return "unknown";
55
57
  }
56
58
 
57
- // Note: This should implement per-segment semantics of openAt, but we cannot currently
58
- // due to the lack of support for openat() in Node.js.
59
+ // Note: This should implement per-segment semantics of openAt, but we cannot
60
+ // currently due to the lack of support for openat() in Node.js.
59
61
  // Tracking issue: https://github.com/libuv/libuv/issues/4167
62
+
60
63
  /**
61
64
  * @implements {DescriptorProps}
62
65
  */
63
- let descriptorCnt = 3;
64
66
  class Descriptor {
65
67
  #hostPreopen;
66
68
  #fd;
69
+ #finalizer;
67
70
  #mode;
68
71
  #fullPath;
69
72
 
70
73
  static _createPreopen(hostPreopen) {
71
74
  const descriptor = new Descriptor();
72
75
  descriptor.#hostPreopen = hostPreopen.endsWith("/")
73
- ? hostPreopen.slice(0, -1) || '/'
76
+ ? hostPreopen.slice(0, -1) || "/"
74
77
  : hostPreopen;
75
78
  // Windows requires UNC paths at minimum
76
79
  if (isWindows) {
77
- descriptor.#hostPreopen = descriptor.#hostPreopen.replace(/\\/g, '/');
78
- if (descriptor.#hostPreopen === '/')
79
- descriptor.#hostPreopen = '//';
80
+ descriptor.#hostPreopen = descriptor.#hostPreopen.replace(/\\/g, "/");
81
+ if (descriptor.#hostPreopen === "/") descriptor.#hostPreopen = "//";
80
82
  }
81
83
  return descriptor;
82
84
  }
@@ -84,15 +86,17 @@ class Descriptor {
84
86
  static _create(fd, mode, fullPath) {
85
87
  const descriptor = new Descriptor();
86
88
  descriptor.#fd = fd;
89
+ descriptor.#finalizer = registerDispose(descriptor, null, fd, closeSync);
87
90
  descriptor.#mode = mode;
88
- if (fullPath.endsWith("/")) throw new Error("bad full path");
89
91
  descriptor.#fullPath = fullPath;
90
92
  return descriptor;
91
93
  }
92
94
 
93
- constructor() {
94
- // this id is purely for debugging purposes
95
- this._id = descriptorCnt++;
95
+ [symbolDispose]() {
96
+ if (this.#finalizer) {
97
+ earlyDispose(this.#finalizer);
98
+ this.#finalizer = null;
99
+ }
96
100
  }
97
101
 
98
102
  readViaStream(offset) {
@@ -118,19 +122,21 @@ class Descriptor {
118
122
  return this.writeViaStream(this.stat().size);
119
123
  }
120
124
 
121
- advise(_offset, _length, _advice) { }
125
+ advise(_offset, _length, _advice) {
126
+ if (this.getType() === "directory") throw "bad-descriptor";
127
+ }
122
128
 
123
129
  syncData() {
124
130
  if (this.#hostPreopen) throw "invalid";
125
131
  try {
126
132
  fdatasyncSync(this.#fd);
127
133
  } catch (e) {
134
+ if (e.code === "EPERM") return;
128
135
  throw convertFsError(e);
129
136
  }
130
137
  }
131
138
 
132
139
  getFlags() {
133
- if (this.#hostPreopen) throw "invalid";
134
140
  return this.#mode;
135
141
  }
136
142
 
@@ -164,7 +170,7 @@ class Descriptor {
164
170
  const mtime = this.#getNewTimestamp(
165
171
  dataModificationTimestamp,
166
172
  dataModificationTimestamp.tag === "no-change" &&
167
- stats.dataModificationTimestamp
173
+ stats.dataModificationTimestamp
168
174
  );
169
175
  try {
170
176
  futimesSync(this.#fd, atime, mtime);
@@ -186,8 +192,14 @@ class Descriptor {
186
192
 
187
193
  read(length, offset) {
188
194
  if (!this.#fullPath) throw "bad-descriptor";
189
- const buf = new Uint8Array(length);
190
- const bytesRead = readSync(this.#fd, buf, Number(offset), length, 0);
195
+ const buf = new Uint8Array(Number(length));
196
+ const bytesRead = readSync(
197
+ this.#fd,
198
+ buf,
199
+ 0,
200
+ Number(length),
201
+ Number(offset)
202
+ );
191
203
  const out = new Uint8Array(buf.buffer, 0, bytesRead);
192
204
  return [out, bytesRead === 0 ? "ended" : "open"];
193
205
  }
@@ -195,7 +207,7 @@ class Descriptor {
195
207
  write(buffer, offset) {
196
208
  if (!this.#fullPath) throw "bad-descriptor";
197
209
  return BigInt(
198
- writeSync(this.#fd, buffer, Number(offset), buffer.byteLength - offset, 0)
210
+ writeSync(this.#fd, buffer, 0, buffer.byteLength, Number(offset))
199
211
  );
200
212
  }
201
213
 
@@ -214,6 +226,7 @@ class Descriptor {
214
226
  try {
215
227
  fsyncSync(this.#fd);
216
228
  } catch (e) {
229
+ if (e.code === "EPERM") return;
217
230
  throw convertFsError(e);
218
231
  }
219
232
  }
@@ -250,7 +263,9 @@ class Descriptor {
250
263
  const fullPath = this.#getFullPath(path, false);
251
264
  let stats;
252
265
  try {
253
- stats = (pathFlags.symlinkFollow ? statSync : lstatSync)(fullPath, { bigint: true });
266
+ stats = (pathFlags.symlinkFollow ? statSync : lstatSync)(fullPath, {
267
+ bigint: true,
268
+ });
254
269
  } catch (e) {
255
270
  throw convertFsError(e);
256
271
  }
@@ -280,7 +295,7 @@ class Descriptor {
280
295
  const mtime = this.#getNewTimestamp(
281
296
  dataModificationTimestamp,
282
297
  dataModificationTimestamp.tag === "no-change" &&
283
- stats.dataModificationTimestamp
298
+ stats.dataModificationTimestamp
284
299
  );
285
300
  try {
286
301
  (pathFlags.symlinkFollow ? utimesSync : lutimesSync)(
@@ -297,8 +312,7 @@ class Descriptor {
297
312
  const oldFullPath = this.#getFullPath(oldPath, oldPathFlags.symlinkFollow);
298
313
  const newFullPath = newDescriptor.#getFullPath(newPath, false);
299
314
  // Windows doesn't automatically fail on trailing slashes
300
- if (isWindows && newFullPath.endsWith('/'))
301
- throw 'no-entry';
315
+ if (isWindows && newFullPath.endsWith("/")) throw "no-entry";
302
316
  try {
303
317
  linkSync(oldFullPath, newFullPath);
304
318
  } catch (e) {
@@ -320,14 +334,51 @@ class Descriptor {
320
334
  else if (descriptorFlags.read) fsOpenFlags |= constants.O_RDONLY;
321
335
  if (descriptorFlags.fileIntegritySync) fsOpenFlags |= constants.O_SYNC;
322
336
  if (descriptorFlags.dataIntegritySync) fsOpenFlags |= constants.O_DSYNC;
323
- // Unsupported:
324
- // if (descriptorFlags.requestedWriteSync)
325
- // if (descriptorFlags.mutateDirectory)
326
-
337
+ if (!pathFlags.symlinkFollow) fsOpenFlags |= constants.O_NOFOLLOW;
338
+ if (descriptorFlags.requestedWriteSync || descriptorFlags.mutateDirectory)
339
+ throw "unsupported";
340
+ // Currently throw to match Wasmtime
341
+ if (descriptorFlags.fileIntegritySync || descriptorFlags.dataIntegritySync)
342
+ throw "unsupported";
343
+ if (isWindows) {
344
+ if (!pathFlags.symlinkFollow && !openFlags.create) {
345
+ let isSymlink = false;
346
+ try {
347
+ isSymlink = lstatSync(fullPath).isSymbolicLink();
348
+ } catch (e) {
349
+ //
350
+ }
351
+ if (isSymlink) throw openFlags.directory ? "not-directory" : "loop";
352
+ }
353
+ if (pathFlags.symlinkFollow && openFlags.directory) {
354
+ let isFile = false;
355
+ try {
356
+ isFile = !statSync(fullPath).isDirectory();
357
+ } catch (e) {
358
+ //
359
+ }
360
+ if (isFile) throw "not-directory";
361
+ }
362
+ }
327
363
  try {
328
364
  const fd = openSync(fullPath, fsOpenFlags);
329
- return descriptorCreate(fd, descriptorFlags, fullPath, preopenEntries);
365
+ const descriptor = descriptorCreate(
366
+ fd,
367
+ descriptorFlags,
368
+ fullPath,
369
+ preopenEntries
370
+ );
371
+ if (fullPath.endsWith("/") && isWindows) {
372
+ // check if its a directory
373
+ if (descriptor.getType() !== "directory") {
374
+ descriptor[symbolDispose]();
375
+ throw "not-directory";
376
+ }
377
+ }
378
+ return descriptor;
330
379
  } catch (e) {
380
+ if (e.code === "ERR_INVALID_ARG_VALUE")
381
+ throw isWindows ? "no-entry" : "invalid";
331
382
  throw convertFsError(e);
332
383
  }
333
384
  }
@@ -346,8 +397,7 @@ class Descriptor {
346
397
  try {
347
398
  rmdirSync(fullPath);
348
399
  } catch (e) {
349
- if (isWindows && e.code === 'ENOENT')
350
- throw 'not-directory';
400
+ if (isWindows && e.code === "ENOENT") throw "not-directory";
351
401
  throw convertFsError(e);
352
402
  }
353
403
  }
@@ -358,19 +408,29 @@ class Descriptor {
358
408
  try {
359
409
  renameSync(oldFullPath, newFullPath);
360
410
  } catch (e) {
361
- if (isWindows && e.code === 'EPERM')
362
- throw 'access';
411
+ if (isWindows && e.code === "EPERM") throw "access";
363
412
  throw convertFsError(e);
364
413
  }
365
414
  }
366
415
 
367
416
  symlinkAt(target, path) {
368
417
  const fullPath = this.#getFullPath(path, false);
418
+ if (target.startsWith("/")) throw "not-permitted";
369
419
  try {
370
420
  symlinkSync(target, fullPath);
371
421
  } catch (e) {
372
- if (isWindows && (e.code === 'EPERM' || e.code === 'EEXIST'))
373
- throw 'no-entry'
422
+ if (fullPath.endsWith("/") && e.code === "EEXIST") {
423
+ let isDir = false;
424
+ try {
425
+ isDir = statSync(fullPath).isDirectory();
426
+ } catch (_) {
427
+ //
428
+ }
429
+ if (!isDir) throw isWindows ? "no-entry" : "not-directory";
430
+ }
431
+ if (isWindows) {
432
+ if (e.code === "EPERM" || e.code === "EEXIST") throw "no-entry";
433
+ }
374
434
  throw convertFsError(e);
375
435
  }
376
436
  }
@@ -378,8 +438,18 @@ class Descriptor {
378
438
  unlinkFileAt(path) {
379
439
  const fullPath = this.#getFullPath(path, false);
380
440
  try {
441
+ if (fullPath.endsWith("/")) {
442
+ let isDir = false;
443
+ try {
444
+ isDir = statSync(fullPath).isDirectory();
445
+ } catch (e) {
446
+ //
447
+ }
448
+ throw isDir ? (isWindows ? "access" : (isMac ? "not-permitted" : "is-directory")) : "not-directory";
449
+ }
381
450
  unlinkSync(fullPath);
382
451
  } catch (e) {
452
+ if (isWindows && e.code === "EPERM") throw "access";
383
453
  throw convertFsError(e);
384
454
  }
385
455
  }
@@ -401,7 +471,9 @@ class Descriptor {
401
471
  metadataHashAt(pathFlags, path) {
402
472
  const fullPath = this.#getFullPath(path, false);
403
473
  try {
404
- const stats = (pathFlags.symlinkFollow ? statSync : lstatSync)(fullPath, { bigint: true });
474
+ const stats = (pathFlags.symlinkFollow ? statSync : lstatSync)(fullPath, {
475
+ bigint: true,
476
+ });
405
477
  return { upper: stats.mtimeNs, lower: stats.ino };
406
478
  } catch (e) {
407
479
  throw convertFsError(e);
@@ -412,34 +484,59 @@ class Descriptor {
412
484
  #getFullPath(subpath, _followSymlinks) {
413
485
  let descriptor = this;
414
486
  if (subpath.indexOf("\\") !== -1) subpath = subpath.replace(/\\/g, "/");
415
- if (subpath[0] === '/') {
416
- let bestPreopenMatch = "";
417
- for (const preopenEntry of preopenEntries) {
487
+ if (subpath.indexOf("//") !== -1) subpath = subpath.replace(/\/\/+/g, "/");
488
+ if (subpath[0] === "/") throw "not-permitted";
489
+
490
+ // segment resolution
491
+ const segments = [];
492
+ let segmentIndex = -1;
493
+ for (let i = 0; i < subpath.length; i++) {
494
+ // busy reading a segment - only terminate on '/'
495
+ if (segmentIndex !== -1) {
496
+ if (subpath[i] === "/") {
497
+ segments.push(subpath.slice(segmentIndex, i + 1));
498
+ segmentIndex = -1;
499
+ }
500
+ continue;
501
+ }
502
+ // new segment - check if it is relative
503
+ else if (subpath[i] === ".") {
504
+ // ../ segment
418
505
  if (
419
- subpath.startsWith(preopenEntry[1]) &&
420
- (!bestPreopenMatch ||
421
- bestPreopenMatch.length < preopenEntry[1].length)
506
+ subpath[i + 1] === "." &&
507
+ (subpath[i + 2] === "/" || i + 2 === subpath.length)
422
508
  ) {
423
- bestPreopenMatch = preopenEntry;
509
+ if (segments.pop() === undefined) throw "not-permitted";
510
+ i += 2;
511
+ continue;
512
+ }
513
+ // ./ segment
514
+ else if (subpath[i + 1] === "/" || i + 1 === subpath.length) {
515
+ i += 1;
516
+ continue;
424
517
  }
425
518
  }
426
- if (!bestPreopenMatch) throw "no-entry";
427
- descriptor = bestPreopenMatch[0];
428
- subpath = subpath.slice(bestPreopenMatch[1]);
429
- if (subpath[0] === "/") subpath = subpath.slice(1);
519
+ // it is the start of a new segment
520
+ while (subpath[i] === "/") i++;
521
+ segmentIndex = i;
430
522
  }
431
- if (subpath.startsWith("."))
432
- subpath = subpath.slice(subpath[1] === "/" ? 2 : 1);
523
+ // finish reading out the last segment
524
+ if (segmentIndex !== -1) segments.push(subpath.slice(segmentIndex));
525
+
526
+ subpath = segments.join("");
527
+
433
528
  if (descriptor.#hostPreopen)
434
529
  return (
435
- descriptor.#hostPreopen + (descriptor.#hostPreopen.endsWith('/') ? '' : (subpath.length > 0 ? "/" : "")) + subpath
530
+ descriptor.#hostPreopen +
531
+ (descriptor.#hostPreopen.endsWith("/")
532
+ ? ""
533
+ : subpath.length > 0
534
+ ? "/"
535
+ : "") +
536
+ subpath
436
537
  );
437
538
  return descriptor.#fullPath + (subpath.length > 0 ? "/" : "") + subpath;
438
539
  }
439
-
440
- [symbolDispose]() {
441
- if (this.#fd) closeSync(this.#fd);
442
- }
443
540
  }
444
541
  const descriptorCreatePreopen = Descriptor._createPreopen;
445
542
  delete Descriptor._createPreopen;
@@ -448,6 +545,7 @@ delete Descriptor._create;
448
545
 
449
546
  class DirectoryEntryStream {
450
547
  #dir;
548
+ #finalizer;
451
549
  readDirectoryEntry() {
452
550
  let entry;
453
551
  try {
@@ -462,15 +560,23 @@ class DirectoryEntryStream {
462
560
  const type = lookupType(entry);
463
561
  return { name, type };
464
562
  }
465
- [symbolDispose]() {
466
- this.#dir.closeSync();
467
- }
468
-
469
563
  static _create(dir) {
470
564
  const dirStream = new DirectoryEntryStream();
565
+ dirStream.#finalizer = registerDispose(
566
+ dirStream,
567
+ null,
568
+ null,
569
+ dir.closeSync.bind(dir)
570
+ );
471
571
  dirStream.#dir = dir;
472
572
  return dirStream;
473
573
  }
574
+ [symbolDispose]() {
575
+ if (this.#finalizer) {
576
+ earlyDispose(this.#finalizer);
577
+ this.#finalizer = null;
578
+ }
579
+ }
474
580
  }
475
581
  const directoryEntryStreamCreate = DirectoryEntryStream._create;
476
582
  delete DirectoryEntryStream._create;
@@ -489,6 +595,9 @@ _addPreopen("/", isWindows ? "//" : "/");
489
595
  export const types = {
490
596
  Descriptor,
491
597
  DirectoryEntryStream,
598
+ filesystemErrorCode(err) {
599
+ return convertFsError(err.payload);
600
+ },
492
601
  };
493
602
 
494
603
  export function _setPreopens(preopens) {
@@ -564,6 +673,10 @@ function convertFsError(e) {
564
673
  return "unsupported";
565
674
  case "ENOTTY":
566
675
  return "no-tty";
676
+ // windows gives this error for badly structured `//` reads
677
+ // this seems like a slightly better error than unknown given
678
+ // that it's a common footgun
679
+ case -4094:
567
680
  case "ENXIO":
568
681
  return "no-such-device";
569
682
  case "EOVERFLOW":