@bytecodealliance/preview2-shim 0.0.21 → 0.14.1

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