@bytecodealliance/preview2-shim 0.0.20 → 0.14.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.
Files changed (43) hide show
  1. package/README.md +4 -14
  2. package/lib/browser/cli.js +77 -12
  3. package/lib/browser/clocks.js +15 -27
  4. package/lib/browser/filesystem.js +147 -205
  5. package/lib/browser/index.js +1 -3
  6. package/lib/{common → browser}/io.js +8 -4
  7. package/lib/common/assert.js +7 -0
  8. package/lib/io/calls.js +64 -0
  9. package/lib/io/worker-http.js +95 -0
  10. package/lib/io/worker-io.js +322 -0
  11. package/lib/io/worker-thread.js +569 -0
  12. package/lib/nodejs/cli.js +45 -58
  13. package/lib/nodejs/clocks.js +13 -27
  14. package/lib/nodejs/filesystem.js +540 -427
  15. package/lib/nodejs/http.js +441 -175
  16. package/lib/nodejs/index.js +4 -1
  17. package/lib/nodejs/io.js +1 -0
  18. package/lib/nodejs/sockets/socket-common.js +116 -0
  19. package/lib/nodejs/sockets/socketopts-bindings.js +94 -0
  20. package/lib/nodejs/sockets/tcp-socket-impl.js +794 -0
  21. package/lib/nodejs/sockets/udp-socket-impl.js +628 -0
  22. package/lib/nodejs/sockets/wasi-sockets.js +320 -0
  23. package/lib/nodejs/sockets.js +11 -200
  24. package/lib/synckit/index.js +4 -2
  25. package/package.json +1 -5
  26. package/types/interfaces/wasi-cli-terminal-input.d.ts +4 -0
  27. package/types/interfaces/wasi-cli-terminal-output.d.ts +4 -0
  28. package/types/interfaces/wasi-clocks-monotonic-clock.d.ts +19 -6
  29. package/types/interfaces/wasi-filesystem-types.d.ts +1 -178
  30. package/types/interfaces/wasi-http-outgoing-handler.d.ts +2 -2
  31. package/types/interfaces/wasi-http-types.d.ts +412 -82
  32. package/types/interfaces/wasi-io-error.d.ts +16 -0
  33. package/types/interfaces/wasi-io-poll.d.ts +19 -8
  34. package/types/interfaces/wasi-io-streams.d.ts +26 -46
  35. package/types/interfaces/wasi-sockets-ip-name-lookup.d.ts +9 -21
  36. package/types/interfaces/wasi-sockets-network.d.ts +4 -0
  37. package/types/interfaces/wasi-sockets-tcp.d.ts +75 -18
  38. package/types/interfaces/wasi-sockets-udp-create-socket.d.ts +1 -1
  39. package/types/interfaces/wasi-sockets-udp.d.ts +282 -193
  40. package/types/wasi-cli-command.d.ts +28 -28
  41. package/types/wasi-http-proxy.d.ts +12 -12
  42. package/lib/common/make-request.js +0 -30
  43. package/types/interfaces/wasi-clocks-timezone.d.ts +0 -56
@@ -1,477 +1,590 @@
1
- import { streams } from '../common/io.js';
2
- import { environment } from './cli.js';
3
- import { constants, readSync, openSync, opendirSync, closeSync, fstatSync, lstatSync, statSync, writeSync, mkdirSync } from 'node:fs';
4
- import { platform } from 'node:process';
5
-
6
- const { InputStream, OutputStream, Error: StreamError } = streams;
7
-
8
- const isWindows = platform === 'win32';
1
+ import {
2
+ ioCall,
3
+ inputStreamCreate,
4
+ outputStreamCreate,
5
+ } from "../io/worker-io.js";
6
+ import { INPUT_STREAM_CREATE, OUTPUT_STREAM_CREATE } from "../io/calls.js";
7
+ import { FILE } from "../io/calls.js";
8
+ // import { environment } from "./cli.js";
9
+ import {
10
+ closeSync,
11
+ constants,
12
+ fdatasyncSync,
13
+ fstatSync,
14
+ fsyncSync,
15
+ ftruncateSync,
16
+ futimesSync,
17
+ linkSync,
18
+ lstatSync,
19
+ lutimesSync,
20
+ mkdirSync,
21
+ opendirSync,
22
+ openSync,
23
+ readlinkSync,
24
+ readSync,
25
+ renameSync,
26
+ rmdirSync,
27
+ statSync,
28
+ symlinkSync,
29
+ unlinkSync,
30
+ utimesSync,
31
+ writeSync,
32
+ } from "node:fs";
33
+ import { platform } from "node:process";
34
+
35
+ const symbolDispose = Symbol.dispose || Symbol.for("dispose");
36
+
37
+ const isWindows = platform === "win32";
9
38
 
10
39
  const nsMagnitude = 1_000_000_000_000n;
11
- function nsToDateTime (ns) {
40
+ function nsToDateTime(ns) {
12
41
  const seconds = ns / nsMagnitude;
13
42
  const nanoseconds = Number(ns % seconds);
14
43
  return { seconds, nanoseconds };
15
44
  }
16
45
 
17
- function lookupType (obj) {
18
- if (obj.isFile())
19
- return 'regular-file';
20
- else if (obj.isSocket())
21
- return 'socket';
22
- else if (obj.isSymbolicLink())
23
- return 'symbolic-link';
24
- else if (obj.isFIFO())
25
- return 'fifo';
26
- else if (obj.isDirectory())
27
- return 'directory';
28
- else if (obj.isCharacterDevice())
29
- return 'character-device';
30
- else if (obj.isBlockDevice())
31
- return 'block-device';
32
- return 'unknown';
46
+ function lookupType(obj) {
47
+ if (obj.isFile()) return "regular-file";
48
+ else if (obj.isSocket()) return "socket";
49
+ else if (obj.isSymbolicLink()) return "symbolic-link";
50
+ else if (obj.isFIFO()) return "fifo";
51
+ else if (obj.isDirectory()) return "directory";
52
+ else if (obj.isCharacterDevice()) return "character-device";
53
+ else if (obj.isBlockDevice()) return "block-device";
54
+ return "unknown";
33
55
  }
34
56
 
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
+ // Tracking issue: https://github.com/libuv/libuv/issues/4167
35
60
  /**
36
- * @typedef {
37
- * { hostPreopen: string } |
38
- * { fullPath: string, fd: number }
39
- * } DescriptorProps
61
+ * @implements {DescriptorProps}
40
62
  */
41
- export class FileSystem {
42
- // Note: This should implement per-segment semantics of openAt, but we cannot currently
43
- // due to the lack of support for openat() in Node.js.
44
- // Tracking issue: https://github.com/libuv/libuv/issues/4167
45
-
46
- // TODO: support followSymlinks
47
- getFullPath (descriptor, subpath, _followSymlinks) {
48
- if (subpath.indexOf('\\') !== -1)
49
- subpath = subpath.replace(/\\/g, '/');
50
- if (subpath[0] === '/') {
51
- let bestPreopenMatch = '';
52
- for (const preopenEntry of this.preopenEntries) {
53
- if (subpath.startsWith(preopenEntry[1]) && (!bestPreopenMatch || bestPreopenMatch.length < preopenEntry[1].length)) {
54
- bestPreopenMatch = preopenEntry;
55
- }
56
- }
57
- if (!bestPreopenMatch)
58
- throw 'no-entry';
59
- descriptor = bestPreopenMatch[0];
60
- subpath = subpath.slice(bestPreopenMatch[1]);
61
- if (subpath[0] === '/')
62
- subpath = subpath.slice(1);
63
- }
64
- if (subpath.startsWith('.'))
65
- subpath = subpath.slice(subpath[1] === '/' ? 2 : 1);
66
- if (descriptor.hostPreopen)
67
- return descriptor.hostPreopen + (descriptor.hostPreopen.endsWith('/') ? '' : '/') + subpath;
68
- return descriptor.fullPath + '/' + subpath;
69
- }
70
-
71
- /**
72
- *
73
- * @param {[string, string][]} preopens
74
- * @param {import('./cli.js').environment} environment
75
- * @returns
76
- */
77
- constructor (preopens, environment) {
78
- const fs = this;
79
- this.cwd = environment.initialCwd();
80
-
81
- class FileInputStream extends InputStream {
82
- constructor (hostFd, position) {
83
- super({
84
- blockingRead (len) {
85
- const buf = new Uint8Array(Number(len));
86
- try {
87
- var bytesRead = readSync(this.hostFd, buf, 0, buf.byteLength, this.position);
88
- } catch (e) {
89
- throw { tag: 'last-operation-failed', val: new StreamError(e.message) };
90
- }
91
- this.position += bytesRead;
92
- if (bytesRead < buf.byteLength) {
93
- if (bytesRead === 0)
94
- throw { tag: 'closed' };
95
- return new Uint8Array(buf.buffer, 0, bytesRead);
96
- }
97
- return buf;
98
- },
99
- subscribe () {
100
- // TODO
101
- },
102
- drop () {
103
- // TODO
104
- }
105
- });
106
- this.hostFd = hostFd;
107
- this.position = Number(position);
108
- }
109
- }
110
-
111
- class FileOutputStream extends OutputStream {
112
- constructor (hostFd, position) {
113
- super({
114
- write (contents) {
115
- let totalWritten = 0;
116
- while (totalWritten !== contents.byteLength) {
117
- const bytesWritten = writeSync(this.hostFd, contents, null, null, this.position);
118
- totalWritten += bytesWritten;
119
- contents = new Uint8Array(contents.buffer, bytesWritten);
120
- }
121
- this.position += contents.byteLength;
122
- },
123
- blockingFlush () {
124
-
125
- },
126
- drop () {
127
-
128
- }
129
- });
130
- this.hostFd = hostFd;
131
- this.position = Number(position);
132
- }
133
- }
134
-
135
- class DirectoryEntryStream {
136
- constructor (dir) {
137
- this.dir = dir;
138
- }
139
- readDirectoryEntry () {
140
- let entry;
141
- try {
142
- entry = this.dir.readSync();
143
- } catch (e) {
144
- throw convertFsError(e);
145
- }
146
- if (entry === null) {
147
- return null;
148
- }
149
- const name = entry.name;
150
- const type = lookupType(entry);
151
- return { name, type };
152
- }
153
- drop () {
154
- this.dir.closeSync();
155
- }
63
+ let descriptorCnt = 3;
64
+ class Descriptor {
65
+ #hostPreopen;
66
+ #fd;
67
+ #mode;
68
+ #fullPath;
69
+
70
+ static _createPreopen(hostPreopen) {
71
+ const descriptor = new Descriptor();
72
+ descriptor.#hostPreopen = hostPreopen.endsWith("/")
73
+ ? hostPreopen.slice(0, -1) || '/'
74
+ : hostPreopen;
75
+ // Windows requires UNC paths at minimum
76
+ if (isWindows) {
77
+ descriptor.#hostPreopen = descriptor.#hostPreopen.replace(/\\/g, '/');
78
+ if (descriptor.#hostPreopen === '/')
79
+ descriptor.#hostPreopen = '//';
156
80
  }
81
+ return descriptor;
82
+ }
157
83
 
158
- /**
159
- * @implements {DescriptorProps}
160
- */
161
- class Descriptor {
162
- constructor () {
163
- this.id = fs.descriptorCnt++;
164
- }
165
- readViaStream(offset) {
166
- if (this.hostPreopen)
167
- throw { tag: 'last-operation-failed', val: new StreamError };
168
- return new FileInputStream(this.fd, offset);
169
- }
170
- writeViaStream(offset) {
171
- if (this.hostPreopen)
172
- throw 'is-directory';
173
- return new FileOutputStream(this.fd, offset);
174
- }
175
-
176
- appendViaStream() {
177
- console.log(`[filesystem] APPEND STREAM ${this.id}`);
178
- }
179
-
180
- advise(offset, length, advice) {
181
- console.log(`[filesystem] ADVISE`, this.id, offset, length, advice);
182
- }
183
-
184
- syncData() {
185
- console.log(`[filesystem] SYNC DATA ${this.id}`);
186
- }
187
-
188
- getFlags() {
189
- console.log(`[filesystem] FLAGS FOR ${this.id}`);
190
- }
191
-
192
- getType() {
193
- if (this.hostPreopen) return 'directory';
194
- const stats = fstatSync(this.fd);
195
- return lookupType(stats);
196
- }
197
-
198
- setFlags(flags) {
199
- console.log(`[filesystem] SET FLAGS ${this.id} ${JSON.stringify(flags)}`);
200
- }
201
-
202
- setSize(size) {
203
- console.log(`[filesystem] SET SIZE`, this.id, size);
204
- }
205
-
206
- setTimes(dataAccessTimestamp, dataModificationTimestamp) {
207
- console.log(`[filesystem] SET TIMES`, this.id, dataAccessTimestamp, dataModificationTimestamp);
208
- }
84
+ static _create(fd, mode, fullPath) {
85
+ const descriptor = new Descriptor();
86
+ descriptor.#fd = fd;
87
+ descriptor.#mode = mode;
88
+ if (fullPath.endsWith("/")) throw new Error("bad full path");
89
+ descriptor.#fullPath = fullPath;
90
+ return descriptor;
91
+ }
209
92
 
210
- read(length, offset) {
211
- if (!this.fullPath) throw 'bad-descriptor';
212
- const buf = new Uint8Array(length);
213
- const bytesRead = readSync(this.fd, buf, Number(offset), length, 0);
214
- const out = new Uint8Array(buf.buffer, 0, bytesRead);
215
- return [out, bytesRead === 0 ? 'ended' : 'open'];
216
- }
93
+ constructor() {
94
+ // this id is purely for debugging purposes
95
+ this._id = descriptorCnt++;
96
+ }
217
97
 
218
- write(buffer, offset) {
219
- if (!this.fullPath) throw 'bad-descriptor';
220
- return BigInt(writeSync(this.fd, buffer, Number(offset), buffer.byteLength - offset, 0));
221
- }
98
+ readViaStream(offset) {
99
+ if (this.#hostPreopen) throw "is-directory";
100
+ return inputStreamCreate(
101
+ FILE,
102
+ ioCall(INPUT_STREAM_CREATE | FILE, null, {
103
+ fd: this.#fd,
104
+ offset,
105
+ })
106
+ );
107
+ }
222
108
 
223
- readDirectory() {
224
- if (!this.fullPath) throw 'bad-descriptor';
225
- try {
226
- const dir = opendirSync(isWindows ? this.fullPath.slice(1) : this.fullPath);
227
- return new DirectoryEntryStream(dir);
228
- }
229
- catch (e) {
230
- throw convertFsError(e);
231
- }
232
- }
109
+ writeViaStream(offset) {
110
+ if (this.#hostPreopen) throw "is-directory";
111
+ return outputStreamCreate(
112
+ FILE,
113
+ ioCall(OUTPUT_STREAM_CREATE | FILE, null, { fd: this.#fd, offset })
114
+ );
115
+ }
233
116
 
234
- sync() {
235
- console.log(`[filesystem] SYNC`, this.id);
236
- }
237
-
238
- createDirectoryAt(path) {
239
- const fullPath = fs.getFullPath(this, path);
240
- try {
241
- mkdirSync(fullPath);
242
- }
243
- catch (e) {
244
- throw convertFsError(e);
245
- }
246
- }
117
+ appendViaStream() {
118
+ return this.writeViaStream(this.stat().size);
119
+ }
247
120
 
248
- stat() {
249
- if (this.hostPreopen) throw 'invalid';
250
- let stats;
251
- try {
252
- stats = fstatSync(this.fd, { bigint: true });
253
- }
254
- catch (e) {
255
- convertFsError(e);
256
- }
257
- const type = lookupType(stats);
258
- return {
259
- type,
260
- linkCount: stats.nlink,
261
- size: stats.size,
262
- dataAccessTimestamp: nsToDateTime(stats.atimeNs),
263
- dataModificationTimestamp: nsToDateTime(stats.mtimeNs),
264
- statusChangeTimestamp: nsToDateTime(stats.ctimeNs),
265
- };
266
- }
121
+ advise(_offset, _length, _advice) { }
267
122
 
268
- statAt(pathFlags, path) {
269
- const fullPath = fs.getFullPath(this, path, false);
270
- let stats;
271
- try {
272
- stats = (pathFlags.symlinkFollow ? statSync : lstatSync)(isWindows ? fullPath.slice(1) : fullPath, { bigint: true });
273
- }
274
- catch (e) {
275
- convertFsError(e);
276
- }
277
- const type = lookupType(stats);
278
- return {
279
- type,
280
- linkCount: stats.nlink,
281
- size: stats.size,
282
- dataAccessTimestamp: nsToDateTime(stats.atimeNs),
283
- dataModificationTimestamp: nsToDateTime(stats.mtimeNs),
284
- statusChangeTimestamp: nsToDateTime(stats.ctimeNs),
285
- };
286
- }
123
+ syncData() {
124
+ if (this.#hostPreopen) throw "invalid";
125
+ try {
126
+ fdatasyncSync(this.#fd);
127
+ } catch (e) {
128
+ throw convertFsError(e);
129
+ }
130
+ }
287
131
 
288
- setTimesAt() {
289
- console.log(`[filesystem] SET TIMES AT`, this.id);
290
- }
132
+ getFlags() {
133
+ if (this.#hostPreopen) throw "invalid";
134
+ return this.#mode;
135
+ }
291
136
 
292
- linkAt() {
293
- console.log(`[filesystem] LINK AT`, this.id);
294
- }
137
+ getType() {
138
+ if (this.#hostPreopen) return "directory";
139
+ const stats = fstatSync(this.#fd);
140
+ return lookupType(stats);
141
+ }
295
142
 
296
- openAt(pathFlags, path, openFlags, descriptorFlags, modes) {
297
- const fullPath = fs.getFullPath(this, path, pathFlags.symlinkFollow);
298
- let fsOpenFlags = 0x0;
299
- if (openFlags.create)
300
- fsOpenFlags |= constants.O_CREAT;
301
- if (openFlags.directory)
302
- fsOpenFlags |= constants.O_DIRECTORY;
303
- if (openFlags.exclusive)
304
- fsOpenFlags |= constants.O_EXCL;
305
- if (openFlags.truncate)
306
- fsOpenFlags |= constants.O_TRUNC;
307
-
308
- if (descriptorFlags.read && descriptorFlags.write)
309
- fsOpenFlags |= constants.O_RDWR;
310
- else if (descriptorFlags.write)
311
- fsOpenFlags |= constants.O_WRONLY;
312
- // TODO:
313
- // if (descriptorFlags.fileIntegritySync)
314
- // if (descriptorFlags.dataIntegritySync)
315
- // if (descriptorFlags.requestedWriteSync)
316
- // if (descriptorFlags.mutateDirectory)
317
-
318
- let fsMode = 0x0;
319
- if (modes.readable)
320
- fsMode |= 0o444;
321
- if (modes.writeable)
322
- fsMode |= 0o222;
323
- if (modes.executable)
324
- fsMode |= 0o111;
325
-
326
- try {
327
- const fd = openSync(isWindows ? fullPath.slice(1) : fullPath, fsOpenFlags, fsMode);
328
- return Object.assign(new Descriptor(), { fullPath, fd });
329
- }
330
- catch (e) {
331
- throw convertFsError(e);
332
- }
333
- }
143
+ setSize(size) {
144
+ if (this.#hostPreopen) throw "is-directory";
145
+ try {
146
+ ftruncateSync(this.#fd, Number(size));
147
+ } catch (e) {
148
+ throw convertFsError(e);
149
+ }
150
+ }
334
151
 
335
- readlinkAt() {
336
- console.log(`[filesystem] READLINK AT`, this.id);
337
- }
152
+ setTimes(dataAccessTimestamp, dataModificationTimestamp) {
153
+ if (this.#hostPreopen) throw "invalid";
154
+ let stats;
155
+ if (
156
+ dataAccessTimestamp.tag === "no-change" ||
157
+ dataModificationTimestamp.tag === "no-change"
158
+ )
159
+ stats = this.stat();
160
+ const atime = this.#getNewTimestamp(
161
+ dataAccessTimestamp,
162
+ dataAccessTimestamp.tag === "no-change" && stats.dataAccessTimestamp
163
+ );
164
+ const mtime = this.#getNewTimestamp(
165
+ dataModificationTimestamp,
166
+ dataModificationTimestamp.tag === "no-change" &&
167
+ stats.dataModificationTimestamp
168
+ );
169
+ try {
170
+ futimesSync(this.#fd, atime, mtime);
171
+ } catch (e) {
172
+ throw convertFsError(e);
173
+ }
174
+ }
338
175
 
339
- removeDirectoryAt() {
340
- console.log(`[filesystem] REMOVE DIR AT`, this.id);
341
- }
176
+ #getNewTimestamp(newTimestamp, maybeNow) {
177
+ switch (newTimestamp.tag) {
178
+ case "no-change":
179
+ return timestampToMs(maybeNow);
180
+ case "now":
181
+ return Math.floor(Date.now() / 1e3);
182
+ case "timestamp":
183
+ return timestampToMs(newTimestamp.val);
184
+ }
185
+ }
342
186
 
343
- renameAt() {
344
- console.log(`[filesystem] RENAME AT`, this.id);
345
- }
187
+ read(length, offset) {
188
+ if (!this.#fullPath) throw "bad-descriptor";
189
+ const buf = new Uint8Array(length);
190
+ const bytesRead = readSync(this.#fd, buf, Number(offset), length, 0);
191
+ const out = new Uint8Array(buf.buffer, 0, bytesRead);
192
+ return [out, bytesRead === 0 ? "ended" : "open"];
193
+ }
346
194
 
347
- symlinkAt() {
348
- console.log(`[filesystem] SYMLINK AT`, this.id);
349
- }
195
+ write(buffer, offset) {
196
+ if (!this.#fullPath) throw "bad-descriptor";
197
+ return BigInt(
198
+ writeSync(this.#fd, buffer, Number(offset), buffer.byteLength - offset, 0)
199
+ );
200
+ }
350
201
 
351
- unlinkFileAt() {
352
- console.log(`[filesystem] UNLINK FILE AT`, this.id);
353
- }
202
+ readDirectory() {
203
+ if (!this.#fullPath) throw "bad-descriptor";
204
+ try {
205
+ const dir = opendirSync(this.#fullPath);
206
+ return directoryEntryStreamCreate(dir);
207
+ } catch (e) {
208
+ throw convertFsError(e);
209
+ }
210
+ }
354
211
 
355
- changeFilePermissionsAt() {
356
- console.log(`[filesystem] CHANGE FILE PERMISSIONS AT`, this.id);
357
- }
212
+ sync() {
213
+ if (this.#hostPreopen) throw "invalid";
214
+ try {
215
+ fsyncSync(this.#fd);
216
+ } catch (e) {
217
+ throw convertFsError(e);
218
+ }
219
+ }
358
220
 
359
- changeDirectoryPermissionsAt() {
360
- console.log(`[filesystem] CHANGE DIR PERMISSIONS AT`, this.id);
361
- }
221
+ createDirectoryAt(path) {
222
+ const fullPath = this.#getFullPath(path);
223
+ try {
224
+ mkdirSync(fullPath);
225
+ } catch (e) {
226
+ throw convertFsError(e);
227
+ }
228
+ }
362
229
 
363
- lockShared() {
364
- console.log(`[filesystem] LOCK SHARED`, this.id);
365
- }
230
+ stat() {
231
+ if (this.#hostPreopen) throw "invalid";
232
+ let stats;
233
+ try {
234
+ stats = fstatSync(this.#fd, { bigint: true });
235
+ } catch (e) {
236
+ throw convertFsError(e);
237
+ }
238
+ const type = lookupType(stats);
239
+ return {
240
+ type,
241
+ linkCount: stats.nlink,
242
+ size: stats.size,
243
+ dataAccessTimestamp: nsToDateTime(stats.atimeNs),
244
+ dataModificationTimestamp: nsToDateTime(stats.mtimeNs),
245
+ statusChangeTimestamp: nsToDateTime(stats.ctimeNs),
246
+ };
247
+ }
366
248
 
367
- lockExclusive() {
368
- console.log(`[filesystem] LOCK EXCLUSIVE`, this.id);
369
- }
249
+ statAt(pathFlags, path) {
250
+ const fullPath = this.#getFullPath(path, false);
251
+ let stats;
252
+ try {
253
+ stats = (pathFlags.symlinkFollow ? statSync : lstatSync)(fullPath, { bigint: true });
254
+ } catch (e) {
255
+ throw convertFsError(e);
256
+ }
257
+ const type = lookupType(stats);
258
+ return {
259
+ type,
260
+ linkCount: stats.nlink,
261
+ size: stats.size,
262
+ dataAccessTimestamp: nsToDateTime(stats.atimeNs),
263
+ dataModificationTimestamp: nsToDateTime(stats.mtimeNs),
264
+ statusChangeTimestamp: nsToDateTime(stats.ctimeNs),
265
+ };
266
+ }
370
267
 
371
- tryLockShared() {
372
- console.log(`[filesystem] TRY LOCK SHARED`, this.id);
373
- }
268
+ setTimesAt(pathFlags, path, dataAccessTimestamp, dataModificationTimestamp) {
269
+ const fullPath = this.#getFullPath(path, false);
270
+ let stats;
271
+ if (
272
+ dataAccessTimestamp.tag === "no-change" ||
273
+ dataModificationTimestamp.tag === "no-change"
274
+ )
275
+ stats = this.stat();
276
+ const atime = this.#getNewTimestamp(
277
+ dataAccessTimestamp,
278
+ dataAccessTimestamp.tag === "no-change" && stats.dataAccessTimestamp
279
+ );
280
+ const mtime = this.#getNewTimestamp(
281
+ dataModificationTimestamp,
282
+ dataModificationTimestamp.tag === "no-change" &&
283
+ stats.dataModificationTimestamp
284
+ );
285
+ try {
286
+ (pathFlags.symlinkFollow ? utimesSync : lutimesSync)(
287
+ fullPath,
288
+ atime,
289
+ mtime
290
+ );
291
+ } catch (e) {
292
+ throw convertFsError(e);
293
+ }
294
+ }
374
295
 
375
- tryLockExclusive() {
376
- console.log(`[filesystem] TRY LOCK EXCLUSIVE`, this.id);
377
- }
296
+ linkAt(oldPathFlags, oldPath, newDescriptor, newPath) {
297
+ const oldFullPath = this.#getFullPath(oldPath, oldPathFlags.symlinkFollow);
298
+ const newFullPath = newDescriptor.#getFullPath(newPath, false);
299
+ // Windows doesn't automatically fail on trailing slashes
300
+ if (isWindows && newFullPath.endsWith('/'))
301
+ throw 'no-entry';
302
+ try {
303
+ linkSync(oldFullPath, newFullPath);
304
+ } catch (e) {
305
+ throw convertFsError(e);
306
+ }
307
+ }
378
308
 
379
- unlock() {
380
- console.log(`[filesystem] UNLOCK`, this.id);
381
- }
309
+ openAt(pathFlags, path, openFlags, descriptorFlags) {
310
+ const fullPath = this.#getFullPath(path, pathFlags.symlinkFollow);
311
+ let fsOpenFlags = 0x0;
312
+ if (openFlags.create) fsOpenFlags |= constants.O_CREAT;
313
+ if (openFlags.directory) fsOpenFlags |= constants.O_DIRECTORY;
314
+ if (openFlags.exclusive) fsOpenFlags |= constants.O_EXCL;
315
+ if (openFlags.truncate) fsOpenFlags |= constants.O_TRUNC;
316
+
317
+ if (descriptorFlags.read && descriptorFlags.write)
318
+ fsOpenFlags |= constants.O_RDWR;
319
+ else if (descriptorFlags.write) fsOpenFlags |= constants.O_WRONLY;
320
+ else if (descriptorFlags.read) fsOpenFlags |= constants.O_RDONLY;
321
+ if (descriptorFlags.fileIntegritySync) fsOpenFlags |= constants.O_SYNC;
322
+ if (descriptorFlags.dataIntegritySync) fsOpenFlags |= constants.O_DSYNC;
323
+ // Unsupported:
324
+ // if (descriptorFlags.requestedWriteSync)
325
+ // if (descriptorFlags.mutateDirectory)
326
+
327
+ try {
328
+ const fd = openSync(fullPath, fsOpenFlags);
329
+ return descriptorCreate(fd, descriptorFlags, fullPath, preopenEntries);
330
+ } catch (e) {
331
+ throw convertFsError(e);
332
+ }
333
+ }
382
334
 
383
- drop() {
384
- if (this.fd)
385
- closeSync(this.fd);
386
- }
335
+ readlinkAt(path) {
336
+ const fullPath = this.#getFullPath(path, false);
337
+ try {
338
+ return readlinkSync(fullPath);
339
+ } catch (e) {
340
+ throw convertFsError(e);
341
+ }
342
+ }
387
343
 
388
- metadataHash() {
389
- if (this.hostPreopen)
390
- return { upper: 0n, lower: BigInt(this.id) };
391
- try {
392
- const stats = fstatSync(this.fd, { bigint: true });
393
- return { upper: stats.mtimeNs, lower: stats.ino };
394
- }
395
- catch (e) {
396
- convertFsError(e);
397
- }
398
- }
344
+ removeDirectoryAt(path) {
345
+ const fullPath = this.#getFullPath(path, false);
346
+ try {
347
+ rmdirSync(fullPath);
348
+ } catch (e) {
349
+ if (isWindows && e.code === 'ENOENT')
350
+ throw 'not-directory';
351
+ throw convertFsError(e);
352
+ }
353
+ }
399
354
 
400
- metadataHashAt(pathFlags, path) {
401
- const fullPath = fs.getFullPath(this, path, false);
402
- try {
403
- const stats = (pathFlags.symlinkFollow ? statSync : lstatSync)(isWindows ? fullPath.slice(1) : fullPath, { bigint: true });
404
- return { upper: stats.mtimeNs, lower: stats.ino };
405
- }
406
- catch (e) {
407
- convertFsError(e);
355
+ renameAt(oldPath, newDescriptor, newPath) {
356
+ const oldFullPath = this.#getFullPath(oldPath, false);
357
+ const newFullPath = newDescriptor.#getFullPath(newPath, false);
358
+ try {
359
+ renameSync(oldFullPath, newFullPath);
360
+ } catch (e) {
361
+ if (isWindows && e.code === 'EPERM')
362
+ throw 'access';
363
+ throw convertFsError(e);
364
+ }
365
+ }
366
+
367
+ symlinkAt(target, path) {
368
+ const fullPath = this.#getFullPath(path, false);
369
+ try {
370
+ symlinkSync(target, fullPath);
371
+ } catch (e) {
372
+ if (isWindows && (e.code === 'EPERM' || e.code === 'EEXIST'))
373
+ throw 'no-entry'
374
+ throw convertFsError(e);
375
+ }
376
+ }
377
+
378
+ unlinkFileAt(path) {
379
+ const fullPath = this.#getFullPath(path, false);
380
+ try {
381
+ unlinkSync(fullPath);
382
+ } catch (e) {
383
+ throw convertFsError(e);
384
+ }
385
+ }
386
+
387
+ isSameObject(other) {
388
+ return other === this;
389
+ }
390
+
391
+ metadataHash() {
392
+ if (this.#hostPreopen) return { upper: 0n, lower: BigInt(this._id) };
393
+ try {
394
+ const stats = fstatSync(this.#fd, { bigint: true });
395
+ return { upper: stats.mtimeNs, lower: stats.ino };
396
+ } catch (e) {
397
+ throw convertFsError(e);
398
+ }
399
+ }
400
+
401
+ metadataHashAt(pathFlags, path) {
402
+ const fullPath = this.#getFullPath(path, false);
403
+ try {
404
+ const stats = (pathFlags.symlinkFollow ? statSync : lstatSync)(fullPath, { bigint: true });
405
+ return { upper: stats.mtimeNs, lower: stats.ino };
406
+ } catch (e) {
407
+ throw convertFsError(e);
408
+ }
409
+ }
410
+
411
+ // TODO: support followSymlinks
412
+ #getFullPath(subpath, _followSymlinks) {
413
+ let descriptor = this;
414
+ if (subpath.indexOf("\\") !== -1) subpath = subpath.replace(/\\/g, "/");
415
+ if (subpath[0] === '/') {
416
+ let bestPreopenMatch = "";
417
+ for (const preopenEntry of preopenEntries) {
418
+ if (
419
+ subpath.startsWith(preopenEntry[1]) &&
420
+ (!bestPreopenMatch ||
421
+ bestPreopenMatch.length < preopenEntry[1].length)
422
+ ) {
423
+ bestPreopenMatch = preopenEntry;
408
424
  }
409
425
  }
426
+ if (!bestPreopenMatch) throw "no-entry";
427
+ descriptor = bestPreopenMatch[0];
428
+ subpath = subpath.slice(bestPreopenMatch[1]);
429
+ if (subpath[0] === "/") subpath = subpath.slice(1);
410
430
  }
431
+ if (subpath.startsWith("."))
432
+ subpath = subpath.slice(subpath[1] === "/" ? 2 : 1);
433
+ if (descriptor.#hostPreopen)
434
+ return (
435
+ descriptor.#hostPreopen + (descriptor.#hostPreopen.endsWith('/') ? '' : (subpath.length > 0 ? "/" : "")) + subpath
436
+ );
437
+ return descriptor.#fullPath + (subpath.length > 0 ? "/" : "") + subpath;
438
+ }
411
439
 
412
- this.descriptorCnt = 3;
413
- this.preopenEntries = [];
414
- for (const [virtualPath, hostPreopen] of Object.entries(preopens)) {
415
- const preopenEntry = [Object.assign(new Descriptor(), { hostPreopen }), virtualPath];
416
- this.preopenEntries.push(preopenEntry);
440
+ [symbolDispose]() {
441
+ if (this.#fd) closeSync(this.#fd);
442
+ }
443
+ }
444
+ const descriptorCreatePreopen = Descriptor._createPreopen;
445
+ delete Descriptor._createPreopen;
446
+ const descriptorCreate = Descriptor._create;
447
+ delete Descriptor._create;
448
+
449
+ class DirectoryEntryStream {
450
+ #dir;
451
+ readDirectoryEntry() {
452
+ let entry;
453
+ try {
454
+ entry = this.#dir.readSync();
455
+ } catch (e) {
456
+ throw convertFsError(e);
417
457
  }
418
- this.preopens = {
419
- Descriptor,
420
- getDirectories () {
421
- return fs.preopenEntries;
422
- }
423
- };
424
- this.types = {
425
- Descriptor,
426
- DirectoryEntryStream,
427
- };
458
+ if (entry === null) {
459
+ return null;
460
+ }
461
+ const name = entry.name;
462
+ const type = lookupType(entry);
463
+ return { name, type };
464
+ }
465
+ [symbolDispose]() {
466
+ this.#dir.closeSync();
467
+ }
468
+
469
+ static _create(dir) {
470
+ const dirStream = new DirectoryEntryStream();
471
+ dirStream.#dir = dir;
472
+ return dirStream;
428
473
  }
429
474
  }
475
+ const directoryEntryStreamCreate = DirectoryEntryStream._create;
476
+ delete DirectoryEntryStream._create;
430
477
 
431
- const _fs = new FileSystem({ '/': '/' }, environment);
478
+ let preopenEntries = [];
432
479
 
433
- export const { preopens, types } = _fs;
480
+ export const preopens = {
481
+ Descriptor,
482
+ getDirectories() {
483
+ return preopenEntries;
484
+ },
485
+ };
434
486
 
435
- function convertFsError (e) {
487
+ _addPreopen("/", isWindows ? "//" : "/");
488
+
489
+ export const types = {
490
+ Descriptor,
491
+ DirectoryEntryStream,
492
+ };
493
+
494
+ export function _setPreopens(preopens) {
495
+ preopenEntries = [];
496
+ for (const [virtualPath, hostPreopen] of Object.entries(preopens)) {
497
+ _addPreopen(virtualPath, hostPreopen);
498
+ }
499
+ }
500
+
501
+ export function _addPreopen(virtualPath, hostPreopen) {
502
+ const preopenEntry = [descriptorCreatePreopen(hostPreopen), virtualPath];
503
+ preopenEntries.push(preopenEntry);
504
+ }
505
+
506
+ function convertFsError(e) {
436
507
  switch (e.code) {
437
- case 'EACCES': throw 'access';
438
- case 'EAGAIN':
439
- case 'EWOULDBLOCK': throw 'would-block';
440
- case 'EALREADY': throw 'already';
441
- case 'EBADF': throw 'bad-descriptor';
442
- case 'EBUSY': throw 'busy';
443
- case 'EDEADLK': throw 'deadlock';
444
- case 'EDQUOT': throw 'quota';
445
- case 'EEXIST': throw 'exist';
446
- case 'EFBIG': throw 'file-too-large';
447
- case 'EILSEQ': throw 'illegal-byte-sequence';
448
- case 'EINPROGRESS': throw 'in-progress';
449
- case 'EINTR': throw 'interrupted';
450
- case 'EINVAL': throw 'invalid';
451
- case 'EIO': throw 'io';
452
- case 'EISDIR': throw 'is-directory';
453
- case 'ELOOP': throw 'loop';
454
- case 'EMLINK': throw 'too-many-links';
455
- case 'EMSGSIZE': throw 'message-size';
456
- case 'ENAMETOOLONG': throw 'name-too-long'
457
- case 'ENODEV': throw 'no-device';
458
- case 'ENOENT': throw 'no-entry';
459
- case 'ENOLCK': throw 'no-lock';
460
- case 'ENOMEM': throw 'insufficient-memory';
461
- case 'ENOSPC': throw 'insufficient-space';
462
- case 'ENOTDIR': throw 'not-directory';
463
- case 'ENOTEMPTY': throw 'not-empty';
464
- case 'ENOTRECOVERABLE': throw 'not-recoverable';
465
- case 'ENOTSUP': throw 'unsupported';
466
- case 'ENOTTY': throw 'no-tty';
467
- case 'ENXIO': throw 'no-such-device';
468
- case 'EOVERFLOW': throw 'overflow';
469
- case 'EPERM': throw 'not-permitted';
470
- case 'EPIPE': throw 'pipe';
471
- case 'EROFS': throw 'read-only';
472
- case 'ESPIPE': throw 'invalid-seek';
473
- case 'ETXTBSY': throw 'text-file-busy';
474
- case 'EXDEV': throw 'cross-device';
475
- default: throw e;
508
+ case "EACCES":
509
+ return "access";
510
+ case "EAGAIN":
511
+ case "EWOULDBLOCK":
512
+ return "would-block";
513
+ case "EALREADY":
514
+ return "already";
515
+ case "EBADF":
516
+ return "bad-descriptor";
517
+ case "EBUSY":
518
+ return "busy";
519
+ case "EDEADLK":
520
+ return "deadlock";
521
+ case "EDQUOT":
522
+ return "quota";
523
+ case "EEXIST":
524
+ return "exist";
525
+ case "EFBIG":
526
+ return "file-too-large";
527
+ case "EILSEQ":
528
+ return "illegal-byte-sequence";
529
+ case "EINPROGRESS":
530
+ return "in-progress";
531
+ case "EINTR":
532
+ return "interrupted";
533
+ case "EINVAL":
534
+ return "invalid";
535
+ case "EIO":
536
+ return "io";
537
+ case "EISDIR":
538
+ return "is-directory";
539
+ case "ELOOP":
540
+ return "loop";
541
+ case "EMLINK":
542
+ return "too-many-links";
543
+ case "EMSGSIZE":
544
+ return "message-size";
545
+ case "ENAMETOOLONG":
546
+ return "name-too-long";
547
+ case "ENODEV":
548
+ return "no-device";
549
+ case "ENOENT":
550
+ return "no-entry";
551
+ case "ENOLCK":
552
+ return "no-lock";
553
+ case "ENOMEM":
554
+ return "insufficient-memory";
555
+ case "ENOSPC":
556
+ return "insufficient-space";
557
+ case "ENOTDIR":
558
+ return "not-directory";
559
+ case "ENOTEMPTY":
560
+ return "not-empty";
561
+ case "ENOTRECOVERABLE":
562
+ return "not-recoverable";
563
+ case "ENOTSUP":
564
+ return "unsupported";
565
+ case "ENOTTY":
566
+ return "no-tty";
567
+ case "ENXIO":
568
+ return "no-such-device";
569
+ case "EOVERFLOW":
570
+ return "overflow";
571
+ case "EPERM":
572
+ return "not-permitted";
573
+ case "EPIPE":
574
+ return "pipe";
575
+ case "EROFS":
576
+ return "read-only";
577
+ case "ESPIPE":
578
+ return "invalid-seek";
579
+ case "ETXTBSY":
580
+ return "text-file-busy";
581
+ case "EXDEV":
582
+ return "cross-device";
583
+ default:
584
+ throw e;
476
585
  }
477
586
  }
587
+
588
+ function timestampToMs(timestamp) {
589
+ return Number(timestamp.seconds) * 1000 + timestamp.nanoseconds / 1e9;
590
+ }