@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.
- package/README.md +4 -14
- package/lib/browser/cli.js +77 -12
- package/lib/browser/clocks.js +15 -27
- package/lib/browser/filesystem.js +147 -205
- package/lib/browser/index.js +1 -3
- package/lib/{common → browser}/io.js +8 -4
- package/lib/common/assert.js +7 -0
- package/lib/io/calls.js +64 -0
- package/lib/io/worker-http.js +95 -0
- package/lib/io/worker-io.js +322 -0
- package/lib/io/worker-thread.js +569 -0
- package/lib/nodejs/cli.js +45 -58
- package/lib/nodejs/clocks.js +13 -27
- package/lib/nodejs/filesystem.js +540 -427
- package/lib/nodejs/http.js +441 -175
- package/lib/nodejs/index.js +4 -1
- package/lib/nodejs/io.js +1 -0
- package/lib/nodejs/sockets/socket-common.js +116 -0
- package/lib/nodejs/sockets/socketopts-bindings.js +94 -0
- package/lib/nodejs/sockets/tcp-socket-impl.js +794 -0
- package/lib/nodejs/sockets/udp-socket-impl.js +628 -0
- package/lib/nodejs/sockets/wasi-sockets.js +320 -0
- package/lib/nodejs/sockets.js +11 -200
- package/lib/synckit/index.js +4 -2
- package/package.json +1 -5
- package/types/interfaces/wasi-cli-terminal-input.d.ts +4 -0
- package/types/interfaces/wasi-cli-terminal-output.d.ts +4 -0
- package/types/interfaces/wasi-clocks-monotonic-clock.d.ts +19 -6
- package/types/interfaces/wasi-filesystem-types.d.ts +1 -178
- package/types/interfaces/wasi-http-outgoing-handler.d.ts +2 -2
- package/types/interfaces/wasi-http-types.d.ts +412 -82
- package/types/interfaces/wasi-io-error.d.ts +16 -0
- package/types/interfaces/wasi-io-poll.d.ts +19 -8
- package/types/interfaces/wasi-io-streams.d.ts +26 -46
- package/types/interfaces/wasi-sockets-ip-name-lookup.d.ts +9 -21
- package/types/interfaces/wasi-sockets-network.d.ts +4 -0
- package/types/interfaces/wasi-sockets-tcp.d.ts +75 -18
- package/types/interfaces/wasi-sockets-udp-create-socket.d.ts +1 -1
- package/types/interfaces/wasi-sockets-udp.d.ts +282 -193
- package/types/wasi-cli-command.d.ts +28 -28
- package/types/wasi-http-proxy.d.ts +12 -12
- package/lib/common/make-request.js +0 -30
- package/types/interfaces/wasi-clocks-timezone.d.ts +0 -56
package/lib/nodejs/filesystem.js
CHANGED
|
@@ -1,477 +1,590 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
|
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
|
|
18
|
-
if (obj.isFile())
|
|
19
|
-
|
|
20
|
-
else if (obj.
|
|
21
|
-
|
|
22
|
-
else if (obj.
|
|
23
|
-
|
|
24
|
-
else if (obj.
|
|
25
|
-
|
|
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
|
-
* @
|
|
37
|
-
* { hostPreopen: string } |
|
|
38
|
-
* { fullPath: string, fd: number }
|
|
39
|
-
* } DescriptorProps
|
|
61
|
+
* @implements {DescriptorProps}
|
|
40
62
|
*/
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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
|
-
|
|
235
|
-
|
|
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
|
-
|
|
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
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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
|
-
|
|
289
|
-
|
|
290
|
-
|
|
132
|
+
getFlags() {
|
|
133
|
+
if (this.#hostPreopen) throw "invalid";
|
|
134
|
+
return this.#mode;
|
|
135
|
+
}
|
|
291
136
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
137
|
+
getType() {
|
|
138
|
+
if (this.#hostPreopen) return "directory";
|
|
139
|
+
const stats = fstatSync(this.#fd);
|
|
140
|
+
return lookupType(stats);
|
|
141
|
+
}
|
|
295
142
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
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
|
-
|
|
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
|
-
|
|
340
|
-
|
|
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
|
-
|
|
344
|
-
|
|
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
|
-
|
|
348
|
-
|
|
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
|
-
|
|
352
|
-
|
|
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
|
-
|
|
356
|
-
|
|
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
|
-
|
|
360
|
-
|
|
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
|
-
|
|
364
|
-
|
|
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
|
-
|
|
368
|
-
|
|
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
|
-
|
|
372
|
-
|
|
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
|
-
|
|
376
|
-
|
|
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
|
-
|
|
380
|
-
|
|
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
|
-
|
|
384
|
-
|
|
385
|
-
|
|
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
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
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
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
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
|
-
|
|
413
|
-
this
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
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
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
};
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
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
|
-
|
|
478
|
+
let preopenEntries = [];
|
|
432
479
|
|
|
433
|
-
export const
|
|
480
|
+
export const preopens = {
|
|
481
|
+
Descriptor,
|
|
482
|
+
getDirectories() {
|
|
483
|
+
return preopenEntries;
|
|
484
|
+
},
|
|
485
|
+
};
|
|
434
486
|
|
|
435
|
-
|
|
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
|
|
438
|
-
|
|
439
|
-
case
|
|
440
|
-
case
|
|
441
|
-
|
|
442
|
-
case
|
|
443
|
-
|
|
444
|
-
case
|
|
445
|
-
|
|
446
|
-
case
|
|
447
|
-
|
|
448
|
-
case
|
|
449
|
-
|
|
450
|
-
case
|
|
451
|
-
|
|
452
|
-
case
|
|
453
|
-
|
|
454
|
-
case
|
|
455
|
-
|
|
456
|
-
case
|
|
457
|
-
|
|
458
|
-
case
|
|
459
|
-
|
|
460
|
-
case
|
|
461
|
-
|
|
462
|
-
case
|
|
463
|
-
|
|
464
|
-
case
|
|
465
|
-
|
|
466
|
-
case
|
|
467
|
-
|
|
468
|
-
case
|
|
469
|
-
|
|
470
|
-
case
|
|
471
|
-
|
|
472
|
-
case
|
|
473
|
-
|
|
474
|
-
case
|
|
475
|
-
|
|
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
|
+
}
|