@ai-ecoverse/slicc-kernel 1.22.1 → 1.24.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 +31 -4
- package/dist/driver-worker.js +87 -1
- package/dist/driver-worker.js.map +3 -3
- package/dist/index.js +126 -15
- package/dist/index.js.map +3 -3
- package/dist/kernel-worker.js +2243 -1581
- package/dist/kernel-worker.js.map +4 -4
- package/dist/node-driver-worker.js +87 -1
- package/dist/node-driver-worker.js.map +3 -3
- package/dist/node.js +1308 -511
- package/dist/node.js.map +4 -4
- package/package.json +1 -1
package/dist/kernel-worker.js
CHANGED
|
@@ -177,80 +177,6 @@ function openMeta(name2, factory) {
|
|
|
177
177
|
});
|
|
178
178
|
}
|
|
179
179
|
|
|
180
|
-
// src/mount/media.ts
|
|
181
|
-
var MEDIA_STORE = "handles";
|
|
182
|
-
function memoryMedia() {
|
|
183
|
-
const handles = /* @__PURE__ */ new Map();
|
|
184
|
-
return {
|
|
185
|
-
get: async (id) => handles.get(id),
|
|
186
|
-
put: async (id, handle) => void handles.set(id, handle)
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
function settle2(request2, value) {
|
|
190
|
-
return new Promise((resolve, reject) => {
|
|
191
|
-
request2.onsuccess = () => resolve(value());
|
|
192
|
-
request2.onerror = () => reject(request2.error);
|
|
193
|
-
});
|
|
194
|
-
}
|
|
195
|
-
function openMedia(name2, factory) {
|
|
196
|
-
const request2 = factory.open(name2, 1);
|
|
197
|
-
request2.onupgradeneeded = () => request2.result.createObjectStore(MEDIA_STORE);
|
|
198
|
-
return settle2(request2, () => {
|
|
199
|
-
const db = request2.result;
|
|
200
|
-
const store = (mode) => db.transaction(MEDIA_STORE, mode).objectStore(MEDIA_STORE);
|
|
201
|
-
return {
|
|
202
|
-
get: (id) => {
|
|
203
|
-
const got = store("readonly").get(id);
|
|
204
|
-
return settle2(got, () => got.result);
|
|
205
|
-
},
|
|
206
|
-
put: (id, handle) => settle2(store("readwrite").put(handle, id), () => void 0)
|
|
207
|
-
};
|
|
208
|
-
});
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// src/pin.ts
|
|
212
|
-
function pinScript(url) {
|
|
213
|
-
try {
|
|
214
|
-
const request2 = new XMLHttpRequest();
|
|
215
|
-
request2.open("GET", url.href, false);
|
|
216
|
-
request2.send();
|
|
217
|
-
if (request2.status !== 200) return url.href;
|
|
218
|
-
const source = `${request2.responseText}
|
|
219
|
-
//# sourceURL=${url.href}
|
|
220
|
-
`;
|
|
221
|
-
return URL.createObjectURL(new Blob([source], { type: "text/javascript" }));
|
|
222
|
-
} catch {
|
|
223
|
-
return url.href;
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// src/client/protocol.ts
|
|
228
|
-
var PROTOCOL = [1, 4];
|
|
229
|
-
var FS_METHODS = [
|
|
230
|
-
"readFile",
|
|
231
|
-
"writeFile",
|
|
232
|
-
"stat",
|
|
233
|
-
"lstat",
|
|
234
|
-
"readdir",
|
|
235
|
-
"mkdir",
|
|
236
|
-
"rm",
|
|
237
|
-
"rename",
|
|
238
|
-
"realpath",
|
|
239
|
-
"symlink",
|
|
240
|
-
"readlink",
|
|
241
|
-
"exists"
|
|
242
|
-
];
|
|
243
|
-
function locksOf(scope = globalThis) {
|
|
244
|
-
const locks = scope.navigator?.locks;
|
|
245
|
-
return typeof locks?.request === "function" ? locks : void 0;
|
|
246
|
-
}
|
|
247
|
-
function versionError(theirs) {
|
|
248
|
-
const major = Array.isArray(theirs) ? theirs[0] : void 0;
|
|
249
|
-
if (major === PROTOCOL[0]) return void 0;
|
|
250
|
-
const named = typeof major === "number" ? `${major}.x` : "without a version";
|
|
251
|
-
return `slicc-kernel client protocol ${named} is not supported: this side speaks ${PROTOCOL.join(".")}`;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
180
|
// src/fs/types.ts
|
|
255
181
|
function rangedOps(fs) {
|
|
256
182
|
const { pread, pwrite, truncate } = fs;
|
|
@@ -285,1626 +211,1878 @@ function inodeOf(path) {
|
|
|
285
211
|
return 4294967296 * (2097151 & h2) + (h1 >>> 0) || 1;
|
|
286
212
|
}
|
|
287
213
|
|
|
288
|
-
// src/
|
|
289
|
-
var
|
|
290
|
-
var
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
214
|
+
// src/fs/opfs.ts
|
|
215
|
+
var S_IFDIR = 16384;
|
|
216
|
+
var S_IFREG = 32768;
|
|
217
|
+
var S_IFLNK = 40960;
|
|
218
|
+
var PERM_MASK = 4095;
|
|
219
|
+
var READ_ATTEMPTS = 3;
|
|
220
|
+
var MAX_LINKS = 40;
|
|
221
|
+
var CHECK_MS = 1e3;
|
|
222
|
+
var EXECUTABLE = /\/node_modules\/(?:@[^/]+\/)?[^/]+\/bin\//;
|
|
223
|
+
var ERRNO_BY_DOM_ERROR = {
|
|
224
|
+
NotFoundError: "ENOENT",
|
|
225
|
+
TypeMismatchError: "ENOTDIR",
|
|
226
|
+
InvalidModificationError: "ENOTEMPTY",
|
|
227
|
+
NoModificationAllowedError: "EBUSY",
|
|
228
|
+
TypeError: "EINVAL",
|
|
229
|
+
NotAllowedError: "EACCES"
|
|
296
230
|
};
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
231
|
+
function translate(err, path) {
|
|
232
|
+
if (typeof err?.code === "string") return err;
|
|
233
|
+
const name2 = err?.name;
|
|
234
|
+
return fsError(typeof name2 === "string" && ERRNO_BY_DOM_ERROR[name2] || "EIO", path);
|
|
235
|
+
}
|
|
236
|
+
function missing(err) {
|
|
237
|
+
const code = err?.code;
|
|
238
|
+
return code === "ENOENT" || code === "ENOTDIR";
|
|
239
|
+
}
|
|
240
|
+
function split(path) {
|
|
241
|
+
return path.split("/").filter(Boolean);
|
|
242
|
+
}
|
|
243
|
+
function parentOf2(path) {
|
|
244
|
+
return path.slice(0, path.lastIndexOf("/")) || "/";
|
|
245
|
+
}
|
|
246
|
+
function baseOf(path) {
|
|
247
|
+
return path.slice(path.lastIndexOf("/") + 1);
|
|
248
|
+
}
|
|
249
|
+
function prefixes(path) {
|
|
250
|
+
const parts = split(path);
|
|
251
|
+
return parts.map((_, i) => `/${parts.slice(0, i + 1).join("/")}`);
|
|
252
|
+
}
|
|
253
|
+
function within(path, root) {
|
|
254
|
+
return path === root || path.startsWith(`${root}/`);
|
|
255
|
+
}
|
|
256
|
+
var LOCKED = /* @__PURE__ */ new Set(["NotReadableError", "NoModificationAllowedError", "InvalidStateError"]);
|
|
257
|
+
var SYNC_BACKOFF_MS = [5, 10, 20, 40, 80];
|
|
258
|
+
async function openSync(handle) {
|
|
259
|
+
const sync = handle.createSyncAccessHandle;
|
|
260
|
+
if (!sync) return void 0;
|
|
261
|
+
for (let attempt = 0; ; attempt++) {
|
|
262
|
+
try {
|
|
263
|
+
return await sync.call(handle, { mode: "readwrite-unsafe" });
|
|
264
|
+
} catch (err) {
|
|
265
|
+
const delay = SYNC_BACKOFF_MS[attempt];
|
|
266
|
+
if (!LOCKED.has(err?.name)) throw err;
|
|
267
|
+
if (delay === void 0) return void 0;
|
|
268
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
269
|
+
}
|
|
306
270
|
}
|
|
307
|
-
|
|
308
|
-
|
|
271
|
+
}
|
|
272
|
+
function writeAll(access, at, bytes) {
|
|
273
|
+
for (let done4 = 0; done4 < bytes.length; ) {
|
|
274
|
+
const n = access.write(bytes.subarray(done4), { at: at + done4 });
|
|
275
|
+
if (!(n > 0))
|
|
276
|
+
throw Object.assign(new Error("the file system took no more bytes"), { code: "ENOSPC" });
|
|
277
|
+
done4 += n;
|
|
309
278
|
}
|
|
310
|
-
|
|
311
|
-
|
|
279
|
+
}
|
|
280
|
+
async function positioned(handle, op) {
|
|
281
|
+
const access = await openSync(handle);
|
|
282
|
+
if (access) {
|
|
283
|
+
try {
|
|
284
|
+
op({
|
|
285
|
+
write: (at, bytes) => writeAll(access, at, bytes),
|
|
286
|
+
truncate: (n) => access.truncate(n)
|
|
287
|
+
});
|
|
288
|
+
access.flush();
|
|
289
|
+
} finally {
|
|
290
|
+
access.close();
|
|
291
|
+
}
|
|
292
|
+
return;
|
|
312
293
|
}
|
|
313
|
-
|
|
314
|
-
|
|
294
|
+
const writable = await handle.createWritable({ keepExistingData: true });
|
|
295
|
+
try {
|
|
296
|
+
await op({
|
|
297
|
+
write: (at, bytes) => writable.write({ type: "write", position: at, data: bytes }),
|
|
298
|
+
truncate: (n) => writable.truncate(n)
|
|
299
|
+
});
|
|
300
|
+
} catch (err) {
|
|
301
|
+
await writable.abort();
|
|
302
|
+
throw err;
|
|
315
303
|
}
|
|
316
|
-
|
|
317
|
-
|
|
304
|
+
await writable.close();
|
|
305
|
+
}
|
|
306
|
+
function syncAccessHandles(scope = globalThis) {
|
|
307
|
+
const handle = scope.FileSystemFileHandle;
|
|
308
|
+
return handle !== void 0 && "createSyncAccessHandle" in handle.prototype;
|
|
309
|
+
}
|
|
310
|
+
function isHandle(child) {
|
|
311
|
+
return "kind" in child;
|
|
312
|
+
}
|
|
313
|
+
var OpfsFs = class {
|
|
314
|
+
root;
|
|
315
|
+
meta;
|
|
316
|
+
dirs = /* @__PURE__ */ new Map();
|
|
317
|
+
checked = /* @__PURE__ */ new WeakMap();
|
|
318
|
+
channel;
|
|
319
|
+
ranged;
|
|
320
|
+
constructor(root, meta = new MemoryMeta(), channel, options = {}) {
|
|
321
|
+
this.root = root;
|
|
322
|
+
this.meta = meta;
|
|
323
|
+
this.ranged = options.ranged === true;
|
|
324
|
+
this.channel = channel === void 0 ? void 0 : new BroadcastChannel(channel);
|
|
325
|
+
if (this.channel) {
|
|
326
|
+
this.channel.onmessage = ({ data }) => {
|
|
327
|
+
for (const path of data) this.forget(path);
|
|
328
|
+
};
|
|
329
|
+
this.channel.unref?.();
|
|
330
|
+
}
|
|
318
331
|
}
|
|
319
|
-
|
|
320
|
-
|
|
332
|
+
changed(...paths) {
|
|
333
|
+
for (const path of paths) this.forget(path);
|
|
334
|
+
this.channel?.postMessage(paths);
|
|
321
335
|
}
|
|
322
|
-
|
|
323
|
-
|
|
336
|
+
resolvePath(base, path) {
|
|
337
|
+
return resolvePath(base, path);
|
|
324
338
|
}
|
|
325
|
-
|
|
326
|
-
|
|
339
|
+
async walk(parts, from, start) {
|
|
340
|
+
let dir = start;
|
|
341
|
+
for (let at = from; at < parts.length; at++) {
|
|
342
|
+
dir = await dir.getDirectoryHandle(parts[at]);
|
|
343
|
+
this.dirs.set(`/${parts.slice(0, at + 1).join("/")}`, dir);
|
|
344
|
+
this.checked.set(dir, performance.now());
|
|
345
|
+
}
|
|
346
|
+
return dir;
|
|
327
347
|
}
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
348
|
+
async dir(path) {
|
|
349
|
+
const parts = split(path);
|
|
350
|
+
let from = parts.length;
|
|
351
|
+
let cached;
|
|
352
|
+
for (; from > 0 && !cached; from--)
|
|
353
|
+
cached = this.dirs.get(`/${parts.slice(0, from).join("/")}`);
|
|
354
|
+
if (cached) from++;
|
|
355
|
+
const at = `/${parts.slice(0, from).join("/")}`;
|
|
356
|
+
if (cached && !await this.current(at, cached)) {
|
|
357
|
+
this.forget(at);
|
|
358
|
+
cached = void 0;
|
|
359
|
+
from = 0;
|
|
360
|
+
}
|
|
361
|
+
try {
|
|
362
|
+
return await this.walk(parts, from, cached ?? this.root);
|
|
363
|
+
} catch (err) {
|
|
364
|
+
throw translate(err, path);
|
|
333
365
|
}
|
|
334
|
-
this.wake();
|
|
335
366
|
}
|
|
336
|
-
|
|
337
|
-
if (this.
|
|
338
|
-
this.
|
|
367
|
+
async current(path, dir) {
|
|
368
|
+
if (performance.now() - (this.checked.get(dir) ?? -CHECK_MS) < CHECK_MS) return true;
|
|
369
|
+
const where = await this.root.resolve(dir).catch(() => null);
|
|
370
|
+
if (where === null || `/${where.join("/")}` !== path) return false;
|
|
371
|
+
this.checked.set(dir, performance.now());
|
|
372
|
+
return true;
|
|
339
373
|
}
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
if (this.writers === 0) return new Uint8Array(0);
|
|
343
|
-
await this.changed(signal);
|
|
344
|
-
}
|
|
345
|
-
const out = this.take(Math.min(max, this.size));
|
|
346
|
-
this.wake();
|
|
347
|
-
return out;
|
|
374
|
+
forget(path) {
|
|
375
|
+
for (const key of [...this.dirs.keys()]) if (within(key, path)) this.dirs.delete(key);
|
|
348
376
|
}
|
|
349
|
-
async
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
await
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
const count = Math.min(chunk2.length, out.length - filled);
|
|
359
|
-
out.set(chunk2.subarray(0, count), filled);
|
|
360
|
-
filled += count;
|
|
377
|
+
async inDir(path, op) {
|
|
378
|
+
const dir = await this.dir(path);
|
|
379
|
+
try {
|
|
380
|
+
return await op(dir);
|
|
381
|
+
} catch (err) {
|
|
382
|
+
if (err?.name !== "NotFoundError" || await this.current(path, dir))
|
|
383
|
+
throw err;
|
|
384
|
+
this.forget(path);
|
|
385
|
+
return op(await this.dir(path));
|
|
361
386
|
}
|
|
362
|
-
return out;
|
|
363
387
|
}
|
|
364
|
-
async
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
388
|
+
async child(parent, name2, kind) {
|
|
389
|
+
return kind === "file" ? parent.getFileHandle(name2) : parent.getDirectoryHandle(name2);
|
|
390
|
+
}
|
|
391
|
+
async parent(path) {
|
|
392
|
+
const parts = split(path);
|
|
393
|
+
const name2 = parts.pop();
|
|
394
|
+
if (name2 === void 0) throw fsError("EBUSY", path);
|
|
395
|
+
return [`/${parts.join("/")}`, name2];
|
|
396
|
+
}
|
|
397
|
+
async handle(path) {
|
|
398
|
+
if (split(path).length === 0) return this.root;
|
|
399
|
+
const [dir, name2] = await this.parent(path);
|
|
400
|
+
const known = this.dirs.has(path);
|
|
401
|
+
return this.inDir(dir, async (parent) => {
|
|
402
|
+
const [first, second] = known ? ["directory", "file"] : ["file", "directory"];
|
|
403
|
+
try {
|
|
404
|
+
return await this.child(parent, name2, first);
|
|
405
|
+
} catch (err) {
|
|
406
|
+
if (err?.name !== "TypeMismatchError") throw err;
|
|
377
407
|
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
408
|
+
return this.child(parent, name2, second);
|
|
409
|
+
}).catch((err) => {
|
|
410
|
+
throw translate(err, path);
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
real(path) {
|
|
414
|
+
return this.handle(path).then(
|
|
415
|
+
() => true,
|
|
416
|
+
() => false
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
async linkAlong(path) {
|
|
420
|
+
for (const entry of await this.meta.get(prefixes(path))) {
|
|
421
|
+
if (entry?.link === void 0) continue;
|
|
422
|
+
if (!await this.real(entry.path) && await this.real(parentOf2(entry.path))) return entry;
|
|
383
423
|
}
|
|
384
|
-
return
|
|
424
|
+
return void 0;
|
|
385
425
|
}
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
let
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
426
|
+
async locate(path, follow) {
|
|
427
|
+
let current = path;
|
|
428
|
+
for (let hops = 0; hops <= MAX_LINKS; hops++) {
|
|
429
|
+
try {
|
|
430
|
+
return { path: current, handle: await this.handle(current) };
|
|
431
|
+
} catch (err) {
|
|
432
|
+
if (!missing(err)) throw err;
|
|
433
|
+
const link2 = await this.linkAlong(current);
|
|
434
|
+
if (!link2) throw err;
|
|
435
|
+
if (link2.path === current && !follow) return { path: current, link: link2 };
|
|
436
|
+
const rest = current.slice(link2.path.length);
|
|
437
|
+
current = resolvePath(parentOf2(link2.path), `${link2.link}${rest}`);
|
|
438
|
+
}
|
|
396
439
|
}
|
|
397
|
-
|
|
398
|
-
return out;
|
|
440
|
+
throw fsError("ELOOP", path);
|
|
399
441
|
}
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
return
|
|
403
|
-
const onAbort = () => {
|
|
404
|
-
this.waiters = this.waiters.filter((w) => w !== waiter);
|
|
405
|
-
reject(new PipeError("EINTR"));
|
|
406
|
-
};
|
|
407
|
-
const waiter = () => {
|
|
408
|
-
signal?.removeEventListener("abort", onAbort);
|
|
409
|
-
resolve();
|
|
410
|
-
};
|
|
411
|
-
this.waiters.push(waiter);
|
|
412
|
-
signal?.addEventListener("abort", onAbort, { once: true });
|
|
413
|
-
});
|
|
442
|
+
mode(path, directory, meta) {
|
|
443
|
+
const fallback = directory || EXECUTABLE.test(path) ? 493 : 420;
|
|
444
|
+
return (directory ? S_IFDIR : S_IFREG) | (meta?.mode ?? fallback);
|
|
414
445
|
}
|
|
415
|
-
|
|
416
|
-
const
|
|
417
|
-
|
|
418
|
-
|
|
446
|
+
async statOf(path, handle, meta) {
|
|
447
|
+
const directory = handle.kind === "directory";
|
|
448
|
+
const file = directory ? void 0 : await handle.getFile();
|
|
449
|
+
const modified = file?.lastModified ?? 0;
|
|
450
|
+
const mtime = meta?.mtimeMs !== void 0 && meta.mtimeFor === modified ? meta.mtimeMs : modified;
|
|
451
|
+
return {
|
|
452
|
+
isFile: !directory,
|
|
453
|
+
isDirectory: directory,
|
|
454
|
+
isSymbolicLink: false,
|
|
455
|
+
size: file?.size ?? 0,
|
|
456
|
+
mode: this.mode(path, directory, meta),
|
|
457
|
+
mtime: new Date(mtime),
|
|
458
|
+
atime: new Date(meta?.atimeMs ?? mtime),
|
|
459
|
+
ctime: new Date(Math.max(meta?.ctimeMs ?? mtime, modified)),
|
|
460
|
+
ino: meta?.ino ?? inodeOf(path),
|
|
461
|
+
...this.ranged && !directory ? { ranged: true } : {}
|
|
462
|
+
};
|
|
419
463
|
}
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
464
|
+
linkStat(entry) {
|
|
465
|
+
const time2 = new Date(entry.mtimeMs ?? 0);
|
|
466
|
+
return {
|
|
467
|
+
isFile: false,
|
|
468
|
+
isDirectory: false,
|
|
469
|
+
isSymbolicLink: true,
|
|
470
|
+
size: new TextEncoder().encode(entry.link).length,
|
|
471
|
+
mode: S_IFLNK | 511,
|
|
472
|
+
mtime: time2,
|
|
473
|
+
atime: time2,
|
|
474
|
+
ctime: new Date(entry.ctimeMs ?? 0),
|
|
475
|
+
ino: entry.ino ?? inodeOf(entry.path)
|
|
476
|
+
};
|
|
428
477
|
}
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
var OpenFile = class {
|
|
434
|
-
refs = 1;
|
|
435
|
-
file;
|
|
436
|
-
constructor(file) {
|
|
437
|
-
this.file = file;
|
|
478
|
+
async describe(found) {
|
|
479
|
+
if (found.link) return this.linkStat(found.link);
|
|
480
|
+
const [meta] = await this.meta.get([found.path]);
|
|
481
|
+
return this.statOf(found.path, found.handle, meta);
|
|
438
482
|
}
|
|
439
|
-
|
|
440
|
-
this.
|
|
441
|
-
return this;
|
|
483
|
+
async stat(path) {
|
|
484
|
+
return this.describe(await this.locate(path, true));
|
|
442
485
|
}
|
|
443
|
-
|
|
444
|
-
this.
|
|
445
|
-
if (this.refs === 0) return this.file.close();
|
|
486
|
+
async lstat(path) {
|
|
487
|
+
return this.describe(await this.locate(path, false));
|
|
446
488
|
}
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
poll: () => ({ readable: pipe.readReady, writable: false, hangup: pipe.writersGone }),
|
|
463
|
-
changed: (signal) => pipe.changed(signal)
|
|
464
|
-
}),
|
|
465
|
-
write: new OpenFile({
|
|
466
|
-
write: async (bytes, signal) => {
|
|
467
|
-
try {
|
|
468
|
-
return await pipe.write(bytes, signal);
|
|
469
|
-
} catch (e) {
|
|
470
|
-
throw new KernelError(e.code);
|
|
471
|
-
}
|
|
472
|
-
},
|
|
473
|
-
close: () => pipe.closeWrite(),
|
|
474
|
-
poll: () => ({ readable: false, writable: pipe.writeReady, hangup: pipe.readersGone }),
|
|
475
|
-
changed: (signal) => pipe.changed(signal)
|
|
476
|
-
})
|
|
477
|
-
};
|
|
478
|
-
}
|
|
479
|
-
function bytesSource(data) {
|
|
480
|
-
let offset = 0;
|
|
481
|
-
return new OpenFile({
|
|
482
|
-
read: async (max) => {
|
|
483
|
-
const out = data.subarray(offset, offset + max);
|
|
484
|
-
offset += out.length;
|
|
485
|
-
return out;
|
|
486
|
-
},
|
|
487
|
-
close: () => {
|
|
488
|
-
}
|
|
489
|
-
});
|
|
490
|
-
}
|
|
491
|
-
function sinkFile(onData) {
|
|
492
|
-
return new OpenFile({
|
|
493
|
-
write: async (bytes) => {
|
|
494
|
-
onData(bytes.slice());
|
|
495
|
-
return bytes.length;
|
|
496
|
-
},
|
|
497
|
-
close: () => {
|
|
498
|
-
}
|
|
499
|
-
});
|
|
500
|
-
}
|
|
501
|
-
function nullFile() {
|
|
502
|
-
return new OpenFile({
|
|
503
|
-
read: async () => new Uint8Array(0),
|
|
504
|
-
write: async (bytes) => bytes.length,
|
|
505
|
-
close: () => {
|
|
506
|
-
}
|
|
507
|
-
});
|
|
508
|
-
}
|
|
509
|
-
function deviceFile(device, access) {
|
|
510
|
-
const read = async (max) => {
|
|
511
|
-
if (device === "null") return new Uint8Array(0);
|
|
512
|
-
const out = new Uint8Array(max);
|
|
513
|
-
if (device === "urandom") {
|
|
514
|
-
for (let at = 0; at < max; at += 65536) {
|
|
515
|
-
crypto.getRandomValues(out.subarray(at, Math.min(max, at + 65536)));
|
|
489
|
+
async exists(path) {
|
|
490
|
+
return this.locate(path, true).then(
|
|
491
|
+
() => true,
|
|
492
|
+
() => false
|
|
493
|
+
);
|
|
494
|
+
}
|
|
495
|
+
async retrying(path, op, locks) {
|
|
496
|
+
for (let attempt = 1; ; attempt++) {
|
|
497
|
+
try {
|
|
498
|
+
return await op();
|
|
499
|
+
} catch (err) {
|
|
500
|
+
const name2 = err?.name;
|
|
501
|
+
const again = name2 === "NotReadableError" || locks && LOCKED.has(name2);
|
|
502
|
+
if (!again || attempt >= READ_ATTEMPTS) throw translate(err, path);
|
|
503
|
+
await new Promise((resolve) => setTimeout(resolve, attempt * 10));
|
|
516
504
|
}
|
|
517
505
|
}
|
|
518
|
-
return out;
|
|
519
|
-
};
|
|
520
|
-
return new OpenFile({
|
|
521
|
-
...access !== "write" ? { read } : {},
|
|
522
|
-
...access !== "read" ? { write: async (bytes) => bytes.length } : {},
|
|
523
|
-
seek: async () => 0,
|
|
524
|
-
heldMeta: { device, ...access ? { access } : {} },
|
|
525
|
-
close: () => {
|
|
526
|
-
}
|
|
527
|
-
});
|
|
528
|
-
}
|
|
529
|
-
function heldFile(meta) {
|
|
530
|
-
return new OpenFile({ held: true, ...meta ? { heldMeta: meta } : {}, close: () => {
|
|
531
|
-
} });
|
|
532
|
-
}
|
|
533
|
-
function kernelFdKind(file) {
|
|
534
|
-
if (file.held) return "held";
|
|
535
|
-
if (file.heldMeta && "device" in file.heldMeta) return "device";
|
|
536
|
-
if (file.tty) return "tty";
|
|
537
|
-
return file.seek ? "file" : "stream";
|
|
538
|
-
}
|
|
539
|
-
var FdTable = class _FdTable {
|
|
540
|
-
static MAX_FDS = 1024;
|
|
541
|
-
fds = /* @__PURE__ */ new Map();
|
|
542
|
-
cloexec = /* @__PURE__ */ new Set();
|
|
543
|
-
status = /* @__PURE__ */ new Map();
|
|
544
|
-
get(fd) {
|
|
545
|
-
const file = this.fds.get(fd);
|
|
546
|
-
if (!file) throw new KernelError("EBADF");
|
|
547
|
-
return file;
|
|
548
|
-
}
|
|
549
|
-
stdioTerminal() {
|
|
550
|
-
for (const fd of [0, 1, 2]) {
|
|
551
|
-
const tty = this.fds.get(fd)?.file.tty;
|
|
552
|
-
if (tty) return tty;
|
|
553
|
-
}
|
|
554
|
-
return void 0;
|
|
555
|
-
}
|
|
556
|
-
numbers() {
|
|
557
|
-
return [...this.fds.keys()].sort((a, b) => a - b);
|
|
558
506
|
}
|
|
559
|
-
|
|
560
|
-
|
|
507
|
+
async existing(path) {
|
|
508
|
+
const handle = (await this.locate(path, true)).handle;
|
|
509
|
+
if (handle.kind === "directory") throw fsError("EISDIR", path);
|
|
510
|
+
return handle;
|
|
561
511
|
}
|
|
562
|
-
|
|
563
|
-
this.
|
|
564
|
-
this.
|
|
512
|
+
async readFileBuffer(path) {
|
|
513
|
+
const handle = await this.existing(path);
|
|
514
|
+
return this.retrying(
|
|
515
|
+
path,
|
|
516
|
+
async () => new Uint8Array(await (await handle.getFile()).arrayBuffer()),
|
|
517
|
+
false
|
|
518
|
+
);
|
|
565
519
|
}
|
|
566
|
-
|
|
567
|
-
this.
|
|
568
|
-
this.
|
|
520
|
+
async pread(path, offset, length2) {
|
|
521
|
+
const handle = await this.existing(path);
|
|
522
|
+
return this.retrying(
|
|
523
|
+
path,
|
|
524
|
+
async () => new Uint8Array(await (await handle.getFile()).slice(offset, offset + length2).arrayBuffer()),
|
|
525
|
+
false
|
|
526
|
+
);
|
|
569
527
|
}
|
|
570
|
-
|
|
571
|
-
|
|
528
|
+
async pwrite(path, offset, bytes) {
|
|
529
|
+
const handle = (await this.fileFor(path))[0];
|
|
530
|
+
await this.retrying(path, () => positioned(handle, (io) => io.write(offset, bytes)), true);
|
|
572
531
|
}
|
|
573
|
-
|
|
574
|
-
this.
|
|
575
|
-
this.
|
|
532
|
+
async truncate(path, size) {
|
|
533
|
+
const handle = await this.existing(path);
|
|
534
|
+
await this.retrying(path, () => positioned(handle, (io) => io.truncate(size)), true);
|
|
576
535
|
}
|
|
577
|
-
|
|
578
|
-
return this.
|
|
536
|
+
async readFile(path) {
|
|
537
|
+
return new TextDecoder().decode(await this.readFileBuffer(path));
|
|
579
538
|
}
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
}
|
|
539
|
+
async writeTarget(path) {
|
|
540
|
+
try {
|
|
541
|
+
return [(await this.locate(path, true)).path, true];
|
|
542
|
+
} catch (err) {
|
|
543
|
+
if (!missing(err)) throw err;
|
|
586
544
|
}
|
|
587
|
-
|
|
588
|
-
|
|
545
|
+
const [entry] = await this.meta.get([path]);
|
|
546
|
+
const wanted = entry?.link === void 0 ? path : resolvePath(parentOf2(path), entry.link);
|
|
547
|
+
return [resolvePath((await this.locate(parentOf2(wanted), true)).path, baseOf(wanted)), false];
|
|
589
548
|
}
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
549
|
+
async fileFor(path) {
|
|
550
|
+
const [target, existed] = await this.writeTarget(path);
|
|
551
|
+
const [dir, name2] = await this.parent(target);
|
|
552
|
+
try {
|
|
553
|
+
return [
|
|
554
|
+
await this.inDir(dir, (parent) => parent.getFileHandle(name2, { create: true })),
|
|
555
|
+
existed
|
|
556
|
+
];
|
|
557
|
+
} catch (err) {
|
|
558
|
+
const code = err?.name === "TypeMismatchError" ? "EISDIR" : null;
|
|
559
|
+
throw code ? fsError(code, path) : translate(err, path);
|
|
594
560
|
}
|
|
595
|
-
const previous = this.fds.get(fd);
|
|
596
|
-
this.fds.set(fd, file);
|
|
597
|
-
this.cloexec.delete(fd);
|
|
598
|
-
this.status.delete(fd);
|
|
599
|
-
void Promise.resolve(previous?.release()).catch(() => void 0);
|
|
600
561
|
}
|
|
601
|
-
|
|
602
|
-
|
|
562
|
+
async writeFile(path, content) {
|
|
563
|
+
const [file, existed] = await this.fileFor(path);
|
|
564
|
+
if (!existed && content.length === 0) return;
|
|
565
|
+
const writable = await file.createWritable();
|
|
566
|
+
await writable.write(content);
|
|
567
|
+
await writable.close();
|
|
603
568
|
}
|
|
604
|
-
|
|
605
|
-
const
|
|
606
|
-
|
|
607
|
-
|
|
569
|
+
async listing(path) {
|
|
570
|
+
const found = await this.locate(path, true);
|
|
571
|
+
const handle = found.handle;
|
|
572
|
+
if (handle.kind !== "directory") throw fsError("ENOTDIR", path);
|
|
573
|
+
const children2 = /* @__PURE__ */ new Map();
|
|
574
|
+
for await (const child of handle.values()) children2.set(child.name, child);
|
|
575
|
+
for (const link2 of await this.meta.links(found.path)) {
|
|
576
|
+
const name2 = baseOf(link2.path);
|
|
577
|
+
if (!children2.has(name2)) children2.set(name2, link2);
|
|
578
|
+
}
|
|
579
|
+
return [found.path, [...children2].sort(([a], [b]) => a < b ? -1 : 1)];
|
|
608
580
|
}
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
this.fds.delete(fd);
|
|
612
|
-
this.cloexec.delete(fd);
|
|
613
|
-
this.status.delete(fd);
|
|
614
|
-
return file.release();
|
|
581
|
+
async readdir(path) {
|
|
582
|
+
return (await this.listing(path))[1].map(([name2]) => name2);
|
|
615
583
|
}
|
|
616
|
-
|
|
617
|
-
const
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
584
|
+
async readdirStat(path) {
|
|
585
|
+
const [dir, children2] = await this.listing(path);
|
|
586
|
+
const base = dir === "/" ? "" : dir;
|
|
587
|
+
const metas = await this.meta.get(children2.map(([name2]) => `${base}/${name2}`));
|
|
588
|
+
return Promise.all(
|
|
589
|
+
children2.map(async ([name2, child], i) => {
|
|
590
|
+
if (!isHandle(child)) return [name2, this.linkStat(child)];
|
|
591
|
+
const stat = await this.statOf(`${base}/${name2}`, child, metas[i]).catch(() => null);
|
|
592
|
+
return [name2, stat];
|
|
593
|
+
})
|
|
594
|
+
);
|
|
622
595
|
}
|
|
623
|
-
async
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
596
|
+
async mkdir(path, options = {}) {
|
|
597
|
+
let found;
|
|
598
|
+
try {
|
|
599
|
+
found = await this.locate(path, true);
|
|
600
|
+
} catch (err) {
|
|
601
|
+
if (!missing(err)) throw err;
|
|
602
|
+
}
|
|
603
|
+
if (found) {
|
|
604
|
+
const directory = found.handle?.kind === "directory";
|
|
605
|
+
if (options.recursive && directory) return;
|
|
606
|
+
throw fsError(options.recursive ? "ENOTDIR" : "EEXIST", path);
|
|
607
|
+
}
|
|
608
|
+
const [entry] = await this.meta.get([path]);
|
|
609
|
+
if (entry?.link !== void 0) throw fsError("EEXIST", path);
|
|
610
|
+
if (options.recursive) await this.mkdir(parentOf2(path), options);
|
|
611
|
+
const parent = await this.locate(parentOf2(path), true);
|
|
612
|
+
if (parent.handle?.kind !== "directory") throw fsError("ENOTDIR", path);
|
|
613
|
+
await parent.handle.getDirectoryHandle(baseOf(path), { create: true });
|
|
629
614
|
}
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
async function pipeCall(op) {
|
|
653
|
-
try {
|
|
654
|
-
return await op();
|
|
655
|
-
} catch (e) {
|
|
656
|
-
if (e instanceof PipeError) throw new KernelError(e.code);
|
|
657
|
-
throw e;
|
|
615
|
+
async rm(path, options = {}) {
|
|
616
|
+
let found;
|
|
617
|
+
try {
|
|
618
|
+
found = await this.locate(path, false);
|
|
619
|
+
} catch (err) {
|
|
620
|
+
const error = translate(err, path);
|
|
621
|
+
if (options.force && error.code === "ENOENT") return;
|
|
622
|
+
throw error;
|
|
623
|
+
}
|
|
624
|
+
if (found.handle) {
|
|
625
|
+
const [dir, name2] = await this.parent(found.path);
|
|
626
|
+
try {
|
|
627
|
+
await this.inDir(
|
|
628
|
+
dir,
|
|
629
|
+
(parent) => parent.removeEntry(name2, { recursive: options.recursive === true })
|
|
630
|
+
);
|
|
631
|
+
} catch (err) {
|
|
632
|
+
throw translate(err, path);
|
|
633
|
+
}
|
|
634
|
+
this.changed(found.path);
|
|
635
|
+
}
|
|
636
|
+
await this.meta.remove([found.path]);
|
|
658
637
|
}
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
signal?.removeEventListener("abort", onAbort);
|
|
671
|
-
resolve();
|
|
672
|
-
};
|
|
673
|
-
this.waiters.push(waiter);
|
|
674
|
-
signal?.addEventListener("abort", onAbort, { once: true });
|
|
638
|
+
async rename(from, to) {
|
|
639
|
+
if (from === to) return;
|
|
640
|
+
const source = await this.locate(from, false);
|
|
641
|
+
this.forget(source.path);
|
|
642
|
+
const parent = await this.locate(parentOf2(to), true);
|
|
643
|
+
const destination = resolvePath(parent.path, baseOf(to));
|
|
644
|
+
const sourceStat = await this.describe(source);
|
|
645
|
+
if (sourceStat.isDirectory && within(destination, source.path)) throw fsError("EINVAL", to);
|
|
646
|
+
const target = await this.locate(destination, false).catch((err) => {
|
|
647
|
+
if (missing(err)) return null;
|
|
648
|
+
throw err;
|
|
675
649
|
});
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
for (const resolve of waiters) resolve();
|
|
681
|
-
}
|
|
682
|
-
};
|
|
683
|
-
function link(a, b) {
|
|
684
|
-
const ab = new KernelPipe();
|
|
685
|
-
const ba = new KernelPipe();
|
|
686
|
-
for (const pipe of [ab, ba]) {
|
|
687
|
-
pipe.openRead();
|
|
688
|
-
pipe.openWrite();
|
|
689
|
-
}
|
|
690
|
-
a.attach({ rx: ba, tx: ab });
|
|
691
|
-
b.attach({ rx: ab, tx: ba });
|
|
692
|
-
}
|
|
693
|
-
var KernelSocket = class _KernelSocket {
|
|
694
|
-
net;
|
|
695
|
-
domain;
|
|
696
|
-
state = "open";
|
|
697
|
-
local;
|
|
698
|
-
peer;
|
|
699
|
-
link;
|
|
700
|
-
readShut = false;
|
|
701
|
-
writeShut = false;
|
|
702
|
-
queue = [];
|
|
703
|
-
backlog = 0;
|
|
704
|
-
bound = false;
|
|
705
|
-
changes = new Changes();
|
|
706
|
-
options = /* @__PURE__ */ new Map();
|
|
707
|
-
constructor(net, domain) {
|
|
708
|
-
this.net = net;
|
|
709
|
-
this.domain = domain;
|
|
710
|
-
}
|
|
711
|
-
attach(link2) {
|
|
712
|
-
this.link = link2;
|
|
713
|
-
this.state = "connected";
|
|
714
|
-
this.changes.wake();
|
|
715
|
-
}
|
|
716
|
-
get listening() {
|
|
717
|
-
return this.state === "listening";
|
|
718
|
-
}
|
|
719
|
-
connection() {
|
|
720
|
-
if (!this.link || this.state !== "connected") throw new KernelError("ENOTCONN");
|
|
721
|
-
return this.link;
|
|
722
|
-
}
|
|
723
|
-
async read(max, signal) {
|
|
724
|
-
const { rx } = this.connection();
|
|
725
|
-
if (this.readShut || max <= 0) return new Uint8Array(0);
|
|
726
|
-
return pipeCall(() => rx.read(max, signal));
|
|
727
|
-
}
|
|
728
|
-
async peek(max, signal) {
|
|
729
|
-
const { rx } = this.connection();
|
|
730
|
-
if (this.readShut || max <= 0) return new Uint8Array(0);
|
|
731
|
-
return pipeCall(() => rx.peek(max, signal));
|
|
732
|
-
}
|
|
733
|
-
async write(bytes, signal) {
|
|
734
|
-
const { tx } = this.connection();
|
|
735
|
-
if (this.writeShut) throw new KernelError("EPIPE");
|
|
736
|
-
return pipeCall(() => tx.write(bytes, signal));
|
|
737
|
-
}
|
|
738
|
-
poll() {
|
|
739
|
-
if (this.state === "listening") {
|
|
740
|
-
return { readable: this.queue.length > 0, writable: false, hangup: false };
|
|
650
|
+
const targetStat = target && await this.describe(target);
|
|
651
|
+
if (targetStat?.isDirectory && !sourceStat.isDirectory) throw fsError("EISDIR", to);
|
|
652
|
+
if (targetStat && !targetStat.isDirectory && sourceStat.isDirectory) {
|
|
653
|
+
throw fsError("ENOTDIR", to);
|
|
741
654
|
}
|
|
742
|
-
if (
|
|
743
|
-
|
|
655
|
+
if (targetStat?.isDirectory && (await this.readdir(destination)).length > 0) {
|
|
656
|
+
throw fsError("ENOTEMPTY", to);
|
|
744
657
|
}
|
|
745
|
-
const
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
for (const wait2 of waits) wait2.catch(() => void 0);
|
|
757
|
-
return pipeCall(() => Promise.race(waits));
|
|
758
|
-
}
|
|
759
|
-
bind(addr) {
|
|
760
|
-
if (this.state !== "open" || this.local) throw new KernelError("EINVAL");
|
|
761
|
-
if (addr.family !== this.domain) throw new KernelError("EAFNOSUPPORT");
|
|
762
|
-
this.local = this.net.bind(this, addr);
|
|
763
|
-
this.bound = true;
|
|
764
|
-
}
|
|
765
|
-
listen(backlog) {
|
|
766
|
-
if (this.state === "connected") throw new KernelError("EISCONN");
|
|
767
|
-
if (this.state !== "open" && this.state !== "listening") throw new KernelError("EINVAL");
|
|
768
|
-
if (!this.local) {
|
|
769
|
-
if (this.domain === "unix") throw new KernelError("EINVAL");
|
|
770
|
-
this.bind({ family: "inet", host: "0.0.0.0", port: 0 });
|
|
658
|
+
const now = Date.now();
|
|
659
|
+
const carry = (entry) => ({
|
|
660
|
+
ino: inodeOf(source.path),
|
|
661
|
+
...entry,
|
|
662
|
+
path: destination,
|
|
663
|
+
ctimeMs: now
|
|
664
|
+
});
|
|
665
|
+
if (source.link) {
|
|
666
|
+
if (target?.handle) await this.rm(destination);
|
|
667
|
+
await this.meta.move(source.path, destination, carry);
|
|
668
|
+
return;
|
|
771
669
|
}
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
670
|
+
const dir = parent.handle;
|
|
671
|
+
const name2 = baseOf(destination);
|
|
672
|
+
const swap = targetStat?.isDirectory === true;
|
|
673
|
+
const staging = swap ? `.${name2}.${crypto.randomUUID()}` : name2;
|
|
674
|
+
this.forget(destination);
|
|
675
|
+
const timed = await this.explicitTimes(source.path);
|
|
676
|
+
const moved2 = await this.place(source.handle, dir, staging);
|
|
677
|
+
if (swap) await this.rm(destination, { recursive: true });
|
|
678
|
+
await this.meta.move(source.path, destination, carry);
|
|
679
|
+
if (!moved2) await this.rm(source.path, { recursive: true });
|
|
680
|
+
let copied = !moved2;
|
|
681
|
+
if (swap) {
|
|
682
|
+
const staged = await dir.getDirectoryHandle(staging);
|
|
683
|
+
const placed = await this.place(staged, dir, name2);
|
|
684
|
+
if (!placed) await dir.removeEntry(staging, { recursive: true });
|
|
685
|
+
copied ||= !placed;
|
|
782
686
|
}
|
|
687
|
+
if (copied) await this.restamp(destination, timed);
|
|
688
|
+
this.changed(source.path, destination);
|
|
783
689
|
}
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
this.changes.wake();
|
|
788
|
-
return true;
|
|
690
|
+
async modified(path) {
|
|
691
|
+
const handle = await this.handle(path);
|
|
692
|
+
return handle.kind === "file" ? (await handle.getFile()).lastModified : 0;
|
|
789
693
|
}
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
if (addr.family === "inet") {
|
|
797
|
-
this.local = this.net.bind(this, { family: "inet", host: "127.0.0.1", port: 0 });
|
|
798
|
-
this.bound = true;
|
|
799
|
-
} else this.local = { family: "unix", path: "" };
|
|
800
|
-
}
|
|
801
|
-
const server = new _KernelSocket(this.net, this.domain);
|
|
802
|
-
server.local = addr.family === "inet" ? { ...addr, host: canonicalHost(addr.host) } : addr;
|
|
803
|
-
server.peer = this.local;
|
|
804
|
-
this.peer = server.local;
|
|
805
|
-
link(this, server);
|
|
806
|
-
if (!target.offer(server)) {
|
|
807
|
-
this.unlink();
|
|
808
|
-
server.close();
|
|
809
|
-
throw new KernelError("ECONNREFUSED");
|
|
694
|
+
async explicitTimes(path) {
|
|
695
|
+
const timed = [];
|
|
696
|
+
for (const entry of await this.meta.under(path)) {
|
|
697
|
+
if (entry.mtimeFor === void 0 || entry.link !== void 0) continue;
|
|
698
|
+
const current = await this.modified(entry.path).catch(() => void 0);
|
|
699
|
+
if (current === entry.mtimeFor) timed.push(entry.path.slice(path.length));
|
|
810
700
|
}
|
|
701
|
+
return timed;
|
|
811
702
|
}
|
|
812
|
-
|
|
813
|
-
const
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
link2.tx.closeWrite();
|
|
703
|
+
async restamp(path, timed) {
|
|
704
|
+
for (const rest of timed) {
|
|
705
|
+
const mtimeFor = await this.modified(path + rest);
|
|
706
|
+
await this.meta.update(path + rest, (entry) => entry && { ...entry, mtimeFor });
|
|
817
707
|
}
|
|
818
|
-
this.link = void 0;
|
|
819
|
-
this.peer = void 0;
|
|
820
|
-
this.state = "open";
|
|
821
708
|
}
|
|
822
|
-
|
|
823
|
-
const
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
709
|
+
async place(handle, dir, name2) {
|
|
710
|
+
const moved2 = await handle.move?.(dir, name2).then(
|
|
711
|
+
() => true,
|
|
712
|
+
() => false
|
|
713
|
+
);
|
|
714
|
+
if (moved2) return true;
|
|
715
|
+
try {
|
|
716
|
+
await this.copy(handle, dir, name2);
|
|
717
|
+
} catch (err) {
|
|
718
|
+
if (handle.kind === "directory") await dir.removeEntry(name2, { recursive: true });
|
|
719
|
+
throw translate(err, name2);
|
|
832
720
|
}
|
|
833
|
-
|
|
721
|
+
return false;
|
|
834
722
|
}
|
|
835
|
-
|
|
836
|
-
if (
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
return 0;
|
|
842
|
-
case SO_ACCEPTCONN:
|
|
843
|
-
return this.state === "listening" ? 1 : 0;
|
|
844
|
-
case SO_DOMAIN:
|
|
845
|
-
return this.domain === "inet" ? AF_INET : AF_UNIX;
|
|
846
|
-
case SO_SNDBUF:
|
|
847
|
-
case SO_RCVBUF:
|
|
848
|
-
return this.options.get(`${level}:${name2}`) ?? PIPE_CAPACITY;
|
|
849
|
-
}
|
|
723
|
+
async copy(handle, dir, name2) {
|
|
724
|
+
if (handle.kind === "file") {
|
|
725
|
+
const writable = await (await dir.getFileHandle(name2, { create: true })).createWritable();
|
|
726
|
+
await writable.write(await handle.getFile());
|
|
727
|
+
await writable.close();
|
|
728
|
+
return;
|
|
850
729
|
}
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
setOption(level, name2, value) {
|
|
854
|
-
this.options.set(`${level}:${name2}`, value);
|
|
730
|
+
const target = await dir.getDirectoryHandle(name2, { create: true });
|
|
731
|
+
for await (const child of handle.values()) await this.copy(child, target, child.name);
|
|
855
732
|
}
|
|
856
|
-
|
|
857
|
-
const
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
this.readShut = true;
|
|
866
|
-
this.writeShut = true;
|
|
733
|
+
async symlink(target, path) {
|
|
734
|
+
const parent = await this.locate(parentOf2(path), true);
|
|
735
|
+
if (parent.handle?.kind !== "directory") throw fsError("ENOTDIR", path);
|
|
736
|
+
const at = resolvePath(parent.path, baseOf(path));
|
|
737
|
+
if (await this.locate(at, false).then(
|
|
738
|
+
() => true,
|
|
739
|
+
(err) => !missing(err)
|
|
740
|
+
)) {
|
|
741
|
+
throw fsError("EEXIST", path);
|
|
867
742
|
}
|
|
868
|
-
|
|
869
|
-
this.
|
|
870
|
-
|
|
743
|
+
const now = Date.now();
|
|
744
|
+
await this.meta.update(at, () => ({
|
|
745
|
+
path: at,
|
|
746
|
+
link: target,
|
|
747
|
+
dir: parent.path,
|
|
748
|
+
ino: inodeOf(at),
|
|
749
|
+
mtimeMs: now,
|
|
750
|
+
ctimeMs: now
|
|
751
|
+
}));
|
|
871
752
|
}
|
|
872
|
-
|
|
873
|
-
const
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
a.local = b.local = a.peer = b.peer = name2;
|
|
877
|
-
link(a, b);
|
|
878
|
-
return [a, b];
|
|
753
|
+
async readlink(path) {
|
|
754
|
+
const found = await this.locate(path, false);
|
|
755
|
+
if (found.link) return found.link.link;
|
|
756
|
+
throw fsError("EINVAL", path);
|
|
879
757
|
}
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
activators = /* @__PURE__ */ new Map();
|
|
890
|
-
nextEphemeral = EPHEMERAL_FIRST;
|
|
891
|
-
activate(addr, start) {
|
|
892
|
-
this.activators.set(keyOf(addr), start);
|
|
893
|
-
}
|
|
894
|
-
socket(domain) {
|
|
895
|
-
return new KernelSocket(this, domain);
|
|
896
|
-
}
|
|
897
|
-
listen(addr, backlog = 128) {
|
|
898
|
-
const socket = this.socket(addr.family);
|
|
899
|
-
socket.bind(addr);
|
|
900
|
-
socket.listen(backlog);
|
|
901
|
-
return socket;
|
|
902
|
-
}
|
|
903
|
-
connect(addr) {
|
|
904
|
-
const socket = this.socket(addr.family);
|
|
905
|
-
socket.connect(addr);
|
|
906
|
-
return socket;
|
|
907
|
-
}
|
|
908
|
-
bind(socket, addr) {
|
|
909
|
-
if (addr.family === "unix") {
|
|
910
|
-
if (addr.path === "") throw new KernelError("EINVAL");
|
|
911
|
-
} else if (addr.host !== "0.0.0.0" && !isLoopback(addr.host)) {
|
|
912
|
-
throw new KernelError("EADDRNOTAVAIL");
|
|
913
|
-
}
|
|
914
|
-
const named = addr.family === "inet" && addr.port === 0 ? { ...addr, port: this.ephemeral() } : addr;
|
|
915
|
-
const key = keyOf(named);
|
|
916
|
-
if (this.bound.has(key)) throw new KernelError("EADDRINUSE");
|
|
917
|
-
this.bound.set(key, socket);
|
|
918
|
-
return named;
|
|
919
|
-
}
|
|
920
|
-
release(addr, socket) {
|
|
921
|
-
const key = keyOf(addr);
|
|
922
|
-
if (this.bound.get(key) === socket) this.bound.delete(key);
|
|
758
|
+
async chmod(path, mode) {
|
|
759
|
+
const found = await this.locate(path, true);
|
|
760
|
+
const ctimeMs = Date.now();
|
|
761
|
+
await this.meta.update(found.path, (entry) => ({
|
|
762
|
+
...entry,
|
|
763
|
+
path: found.path,
|
|
764
|
+
mode: mode & PERM_MASK,
|
|
765
|
+
ctimeMs
|
|
766
|
+
}));
|
|
923
767
|
}
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
const
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
768
|
+
async utimes(path, atime, mtime) {
|
|
769
|
+
const found = await this.locate(path, true);
|
|
770
|
+
const handle = found.handle;
|
|
771
|
+
const file = handle.kind === "file" ? await handle.getFile() : void 0;
|
|
772
|
+
const ctimeMs = Date.now();
|
|
773
|
+
await this.meta.update(found.path, (entry) => ({
|
|
774
|
+
...entry,
|
|
775
|
+
path: found.path,
|
|
776
|
+
atimeMs: atime.getTime(),
|
|
777
|
+
mtimeMs: mtime.getTime(),
|
|
778
|
+
mtimeFor: file?.lastModified ?? 0,
|
|
779
|
+
ctimeMs
|
|
780
|
+
}));
|
|
933
781
|
}
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
for (
|
|
937
|
-
const
|
|
938
|
-
|
|
939
|
-
if (!
|
|
782
|
+
async reconcile() {
|
|
783
|
+
let removed = 0;
|
|
784
|
+
for (const entry of await this.meta.all()) {
|
|
785
|
+
const real = await this.real(entry.path);
|
|
786
|
+
const stale = entry.link === void 0 ? !real : real || !await this.real(parentOf2(entry.path));
|
|
787
|
+
if (!stale) continue;
|
|
788
|
+
await this.meta.update(entry.path, () => void 0);
|
|
789
|
+
removed++;
|
|
940
790
|
}
|
|
941
|
-
|
|
791
|
+
return removed;
|
|
942
792
|
}
|
|
943
793
|
};
|
|
944
794
|
|
|
945
|
-
// src/
|
|
946
|
-
var
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
}
|
|
953
|
-
var codeOf = (err) => {
|
|
954
|
-
const code = err?.code;
|
|
955
|
-
return typeof code === "string" ? code : "EIO";
|
|
956
|
-
};
|
|
957
|
-
function serveSocket(socket, port, onClose) {
|
|
958
|
-
let closed = false;
|
|
959
|
-
let writes = Promise.resolve();
|
|
960
|
-
let credit = 0;
|
|
961
|
-
let granted2;
|
|
962
|
-
const send = (message2, transfer) => {
|
|
963
|
-
if (!closed) port.postMessage(message2, transfer);
|
|
964
|
-
};
|
|
965
|
-
const close = (reset = false) => {
|
|
966
|
-
if (closed) return;
|
|
967
|
-
if (reset) send({ error: "ECONNRESET" });
|
|
968
|
-
closed = true;
|
|
969
|
-
granted2?.();
|
|
970
|
-
socket.close();
|
|
971
|
-
port.close();
|
|
972
|
-
onClose?.();
|
|
973
|
-
};
|
|
974
|
-
const write = (data) => {
|
|
975
|
-
writes = writes.then(async () => {
|
|
976
|
-
if (closed) return;
|
|
977
|
-
try {
|
|
978
|
-
await socket.write(data);
|
|
979
|
-
send({ ack: true });
|
|
980
|
-
} catch (err) {
|
|
981
|
-
send({ error: codeOf(err) });
|
|
982
|
-
}
|
|
983
|
-
});
|
|
795
|
+
// src/mount/media.ts
|
|
796
|
+
var MEDIA_STORE = "handles";
|
|
797
|
+
function memoryMedia() {
|
|
798
|
+
const handles = /* @__PURE__ */ new Map();
|
|
799
|
+
return {
|
|
800
|
+
get: async (id) => handles.get(id),
|
|
801
|
+
put: async (id, handle) => void handles.set(id, handle)
|
|
984
802
|
};
|
|
985
|
-
port.addEventListener("message", ({ data }) => {
|
|
986
|
-
if (data instanceof Uint8Array) write(data);
|
|
987
|
-
else if ("more" in data) {
|
|
988
|
-
credit++;
|
|
989
|
-
granted2?.();
|
|
990
|
-
} else if ("end" in data) {
|
|
991
|
-
writes = writes.then(() => {
|
|
992
|
-
if (!closed) socket.shutdown(SHUT_WR);
|
|
993
|
-
});
|
|
994
|
-
} else if ("close" in data) close();
|
|
995
|
-
});
|
|
996
|
-
port.start?.();
|
|
997
|
-
const turn = () => credit > 0 || closed ? Promise.resolve() : new Promise((r) => granted2 = r);
|
|
998
|
-
void (async () => {
|
|
999
|
-
try {
|
|
1000
|
-
for (; ; ) {
|
|
1001
|
-
await turn();
|
|
1002
|
-
if (closed) return;
|
|
1003
|
-
const chunk2 = await socket.read(CHUNK);
|
|
1004
|
-
if (chunk2.length === 0) break;
|
|
1005
|
-
credit--;
|
|
1006
|
-
send(chunk2, [chunk2.buffer]);
|
|
1007
|
-
}
|
|
1008
|
-
send({ end: true });
|
|
1009
|
-
} catch (err) {
|
|
1010
|
-
send({ error: codeOf(err) });
|
|
1011
|
-
}
|
|
1012
|
-
})();
|
|
1013
|
-
return () => close(true);
|
|
1014
|
-
}
|
|
1015
|
-
|
|
1016
|
-
// src/kernel/signals.ts
|
|
1017
|
-
var SIG = {
|
|
1018
|
-
HUP: 1,
|
|
1019
|
-
INT: 2,
|
|
1020
|
-
QUIT: 3,
|
|
1021
|
-
ILL: 4,
|
|
1022
|
-
TRAP: 5,
|
|
1023
|
-
ABRT: 6,
|
|
1024
|
-
BUS: 7,
|
|
1025
|
-
FPE: 8,
|
|
1026
|
-
KILL: 9,
|
|
1027
|
-
USR1: 10,
|
|
1028
|
-
SEGV: 11,
|
|
1029
|
-
USR2: 12,
|
|
1030
|
-
PIPE: 13,
|
|
1031
|
-
ALRM: 14,
|
|
1032
|
-
TERM: 15,
|
|
1033
|
-
STKFLT: 16,
|
|
1034
|
-
CHLD: 17,
|
|
1035
|
-
CONT: 18,
|
|
1036
|
-
STOP: 19,
|
|
1037
|
-
TSTP: 20,
|
|
1038
|
-
TTIN: 21,
|
|
1039
|
-
TTOU: 22,
|
|
1040
|
-
URG: 23,
|
|
1041
|
-
XCPU: 24,
|
|
1042
|
-
XFSZ: 25,
|
|
1043
|
-
VTALRM: 26,
|
|
1044
|
-
PROF: 27,
|
|
1045
|
-
WINCH: 28,
|
|
1046
|
-
POLL: 29,
|
|
1047
|
-
PWR: 30,
|
|
1048
|
-
SYS: 31
|
|
1049
|
-
};
|
|
1050
|
-
var IGNORED_BY_DEFAULT = /* @__PURE__ */ new Set([SIG.CHLD, SIG.URG, SIG.WINCH]);
|
|
1051
|
-
var STOPPING = /* @__PURE__ */ new Set([SIG.STOP, SIG.TSTP, SIG.TTIN, SIG.TTOU]);
|
|
1052
|
-
function defaultAction(sig) {
|
|
1053
|
-
if (sig === SIG.CONT) return "continue";
|
|
1054
|
-
if (STOPPING.has(sig)) return "stop";
|
|
1055
|
-
return IGNORED_BY_DEFAULT.has(sig) ? "ignore" : "terminate";
|
|
1056
803
|
}
|
|
1057
|
-
function
|
|
1058
|
-
return
|
|
804
|
+
function settle2(request2, value) {
|
|
805
|
+
return new Promise((resolve, reject) => {
|
|
806
|
+
request2.onsuccess = () => resolve(value());
|
|
807
|
+
request2.onerror = () => reject(request2.error);
|
|
808
|
+
});
|
|
1059
809
|
}
|
|
1060
|
-
function
|
|
1061
|
-
|
|
810
|
+
function openMedia(name2, factory) {
|
|
811
|
+
const request2 = factory.open(name2, 1);
|
|
812
|
+
request2.onupgradeneeded = () => request2.result.createObjectStore(MEDIA_STORE);
|
|
813
|
+
return settle2(request2, () => {
|
|
814
|
+
const db = request2.result;
|
|
815
|
+
const store = (mode) => db.transaction(MEDIA_STORE, mode).objectStore(MEDIA_STORE);
|
|
816
|
+
return {
|
|
817
|
+
get: (id) => {
|
|
818
|
+
const got = store("readonly").get(id);
|
|
819
|
+
return settle2(got, () => got.result);
|
|
820
|
+
},
|
|
821
|
+
put: (id, handle) => settle2(store("readwrite").put(handle, id), () => void 0)
|
|
822
|
+
};
|
|
823
|
+
});
|
|
1062
824
|
}
|
|
1063
|
-
var SIGNAL_BY_NAME = {
|
|
1064
|
-
SIGINT: SIG.INT,
|
|
1065
|
-
SIGTERM: SIG.TERM,
|
|
1066
|
-
SIGKILL: SIG.KILL,
|
|
1067
|
-
SIGSTOP: SIG.STOP,
|
|
1068
|
-
SIGCONT: SIG.CONT
|
|
1069
|
-
};
|
|
1070
825
|
|
|
1071
|
-
// src/
|
|
1072
|
-
|
|
826
|
+
// src/pin.ts
|
|
827
|
+
function pinScript(url) {
|
|
828
|
+
try {
|
|
829
|
+
const request2 = new XMLHttpRequest();
|
|
830
|
+
request2.open("GET", url.href, false);
|
|
831
|
+
request2.send();
|
|
832
|
+
if (request2.status !== 200) return url.href;
|
|
833
|
+
const source = `${request2.responseText}
|
|
834
|
+
//# sourceURL=${url.href}
|
|
835
|
+
`;
|
|
836
|
+
return URL.createObjectURL(new Blob([source], { type: "text/javascript" }));
|
|
837
|
+
} catch {
|
|
838
|
+
return url.href;
|
|
839
|
+
}
|
|
840
|
+
}
|
|
1073
841
|
|
|
1074
|
-
// src/
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
const abort = new AbortController();
|
|
1091
|
-
const entry = { abort };
|
|
1092
|
-
open.set(call.nid, entry);
|
|
1093
|
-
try {
|
|
1094
|
-
const response = await transport.fetch({
|
|
1095
|
-
url: call.url,
|
|
1096
|
-
method: call.method,
|
|
1097
|
-
headers: call.headers,
|
|
1098
|
-
...call.body ? { body: call.body } : {},
|
|
1099
|
-
signal: abort.signal
|
|
1100
|
-
});
|
|
1101
|
-
if (!open.has(call.nid)) {
|
|
1102
|
-
await response.cancel();
|
|
1103
|
-
return;
|
|
842
|
+
// src/cdp/port.ts
|
|
843
|
+
function portCdpHook(ask, gone) {
|
|
844
|
+
return (request2) => new Promise((resolve, reject) => {
|
|
845
|
+
const { port1, port2 } = new MessageChannel();
|
|
846
|
+
let closed = false;
|
|
847
|
+
const conn = {
|
|
848
|
+
send: (m) => {
|
|
849
|
+
if (!closed) port1.postMessage({ m });
|
|
850
|
+
},
|
|
851
|
+
onmessage: null,
|
|
852
|
+
onclose: null,
|
|
853
|
+
close: () => {
|
|
854
|
+
if (closed) return;
|
|
855
|
+
closed = true;
|
|
856
|
+
port1.postMessage({ close: true });
|
|
857
|
+
port1.close();
|
|
1104
858
|
}
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
const read = async (nid) => {
|
|
1118
|
-
const body = open.get(nid)?.body;
|
|
1119
|
-
if (!body) return;
|
|
1120
|
-
try {
|
|
1121
|
-
const next = await body.next();
|
|
1122
|
-
if (next.done) {
|
|
1123
|
-
open.delete(nid);
|
|
1124
|
-
peer.postMessage({ net: "end", nid });
|
|
1125
|
-
} else {
|
|
1126
|
-
const bytes = next.value.slice();
|
|
1127
|
-
peer.postMessage({ net: "chunk", nid, bytes }, [bytes.buffer]);
|
|
859
|
+
};
|
|
860
|
+
port1.addEventListener("message", ({ data }) => {
|
|
861
|
+
if ("m" in data) conn.onmessage?.(data.m);
|
|
862
|
+
else if ("open" in data) resolve(conn);
|
|
863
|
+
else if ("error" in data) {
|
|
864
|
+
closed = true;
|
|
865
|
+
port1.close();
|
|
866
|
+
reject(new Error(data.error));
|
|
867
|
+
} else if (!closed) {
|
|
868
|
+
closed = true;
|
|
869
|
+
port1.close();
|
|
870
|
+
conn.onclose?.(data.close ?? void 0);
|
|
1128
871
|
}
|
|
1129
|
-
}
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
answer(call) {
|
|
1141
|
-
if (call.net === "fetch") void start(call);
|
|
1142
|
-
else if (call.net === "read") void read(call.nid);
|
|
1143
|
-
else drop(call.nid);
|
|
1144
|
-
},
|
|
1145
|
-
close() {
|
|
1146
|
-
for (const nid of [...open.keys()]) drop(nid);
|
|
1147
|
-
}
|
|
1148
|
-
};
|
|
872
|
+
});
|
|
873
|
+
port1.start();
|
|
874
|
+
void gone?.then(() => {
|
|
875
|
+
if (closed) return;
|
|
876
|
+
closed = true;
|
|
877
|
+
port1.close();
|
|
878
|
+
reject(new Error("the CDP host is gone"));
|
|
879
|
+
conn.onclose?.("the CDP host is gone");
|
|
880
|
+
});
|
|
881
|
+
ask(request2, port2);
|
|
882
|
+
});
|
|
1149
883
|
}
|
|
1150
884
|
|
|
1151
|
-
// src/client/
|
|
1152
|
-
var
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
885
|
+
// src/client/protocol.ts
|
|
886
|
+
var PROTOCOL = [1, 5];
|
|
887
|
+
var FS_METHODS = [
|
|
888
|
+
"readFile",
|
|
889
|
+
"writeFile",
|
|
890
|
+
"stat",
|
|
891
|
+
"lstat",
|
|
892
|
+
"readdir",
|
|
893
|
+
"mkdir",
|
|
894
|
+
"rm",
|
|
895
|
+
"rename",
|
|
896
|
+
"realpath",
|
|
897
|
+
"symlink",
|
|
898
|
+
"readlink",
|
|
899
|
+
"exists"
|
|
900
|
+
];
|
|
901
|
+
function locksOf(scope = globalThis) {
|
|
902
|
+
const locks = scope.navigator?.locks;
|
|
903
|
+
return typeof locks?.request === "function" ? locks : void 0;
|
|
1158
904
|
}
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
905
|
+
function versionError(theirs) {
|
|
906
|
+
const major = Array.isArray(theirs) ? theirs[0] : void 0;
|
|
907
|
+
if (major === PROTOCOL[0]) return void 0;
|
|
908
|
+
const named = typeof major === "number" ? `${major}.x` : "without a version";
|
|
909
|
+
return `slicc-kernel client protocol ${named} is not supported: this side speaks ${PROTOCOL.join(".")}`;
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
// src/kernel/pipe.ts
|
|
913
|
+
var PIPE_CAPACITY = 64 * 1024;
|
|
914
|
+
var PipeError = class extends Error {
|
|
915
|
+
code;
|
|
916
|
+
constructor(code) {
|
|
917
|
+
super(code);
|
|
918
|
+
this.code = code;
|
|
919
|
+
}
|
|
920
|
+
};
|
|
921
|
+
var KernelPipe = class {
|
|
922
|
+
chunks = [];
|
|
923
|
+
size = 0;
|
|
924
|
+
readers = 0;
|
|
925
|
+
writers = 0;
|
|
926
|
+
waiters = [];
|
|
927
|
+
capacity;
|
|
928
|
+
constructor(capacity = PIPE_CAPACITY) {
|
|
929
|
+
this.capacity = capacity;
|
|
930
|
+
}
|
|
931
|
+
get buffered() {
|
|
932
|
+
return this.size;
|
|
933
|
+
}
|
|
934
|
+
get readReady() {
|
|
935
|
+
return this.size > 0 || this.writers === 0;
|
|
936
|
+
}
|
|
937
|
+
get writeReady() {
|
|
938
|
+
return this.size < this.capacity || this.readers === 0;
|
|
939
|
+
}
|
|
940
|
+
get writersGone() {
|
|
941
|
+
return this.writers === 0;
|
|
942
|
+
}
|
|
943
|
+
get readersGone() {
|
|
944
|
+
return this.readers === 0;
|
|
945
|
+
}
|
|
946
|
+
openRead() {
|
|
947
|
+
this.readers += 1;
|
|
948
|
+
}
|
|
949
|
+
openWrite() {
|
|
950
|
+
this.writers += 1;
|
|
951
|
+
}
|
|
952
|
+
closeRead() {
|
|
953
|
+
if (this.readers > 0) this.readers -= 1;
|
|
954
|
+
if (this.readers === 0) {
|
|
955
|
+
this.chunks = [];
|
|
956
|
+
this.size = 0;
|
|
1169
957
|
}
|
|
1170
|
-
|
|
1171
|
-
const target = await fs.readlink(next);
|
|
1172
|
-
const base = target.startsWith("/") ? "/" : resolved || "/";
|
|
1173
|
-
parts = [...fs.resolvePath(base, target).split("/").filter(Boolean), ...rest];
|
|
1174
|
-
resolved = "";
|
|
958
|
+
this.wake();
|
|
1175
959
|
}
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
960
|
+
closeWrite() {
|
|
961
|
+
if (this.writers > 0) this.writers -= 1;
|
|
962
|
+
this.wake();
|
|
963
|
+
}
|
|
964
|
+
async read(max, signal) {
|
|
965
|
+
while (this.size === 0) {
|
|
966
|
+
if (this.writers === 0) return new Uint8Array(0);
|
|
967
|
+
await this.changed(signal);
|
|
1184
968
|
}
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
case "stat":
|
|
1189
|
-
return { result: await fs.stat(path(0)) };
|
|
1190
|
-
case "lstat":
|
|
1191
|
-
return { result: await fs.lstat(path(0)) };
|
|
1192
|
-
case "readdir":
|
|
1193
|
-
return { result: await fs.readdir(path(0)) };
|
|
1194
|
-
case "mkdir":
|
|
1195
|
-
await fs.mkdir(path(0), { recursive: true });
|
|
1196
|
-
return { result: true };
|
|
1197
|
-
case "rm":
|
|
1198
|
-
await fs.rm(path(0), { recursive: true, force: args[1] === true });
|
|
1199
|
-
return { result: true };
|
|
1200
|
-
case "rename":
|
|
1201
|
-
await fs.rename(path(0), path(1));
|
|
1202
|
-
return { result: true };
|
|
1203
|
-
case "realpath":
|
|
1204
|
-
return { result: await realpath(fs, String(args[0])) };
|
|
1205
|
-
case "symlink":
|
|
1206
|
-
await fs.symlink(String(args[0]), path(1));
|
|
1207
|
-
return { result: true };
|
|
1208
|
-
case "readlink":
|
|
1209
|
-
return { result: await fs.readlink(path(0)) };
|
|
1210
|
-
default:
|
|
1211
|
-
return { result: await fs.exists(path(0)) };
|
|
969
|
+
const out = this.take(Math.min(max, this.size));
|
|
970
|
+
this.wake();
|
|
971
|
+
return out;
|
|
1212
972
|
}
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
}
|
|
1228
|
-
function killGroups(host, pgids) {
|
|
1229
|
-
if (pgids.length === 0) return;
|
|
1230
|
-
void host.launcher().then((launcher) => {
|
|
1231
|
-
for (const pgid of pgids) launcher.kill(-pgid, SIG.KILL);
|
|
1232
|
-
});
|
|
1233
|
-
}
|
|
1234
|
-
function joinable(l, pgid, ours) {
|
|
1235
|
-
const sid = l.groupSession(pgid);
|
|
1236
|
-
if (sid === void 0) throw fsError("ESRCH", `no process group ${pgid}`);
|
|
1237
|
-
if (!ours(sid)) throw fsError("EPERM", `process group ${pgid} is in another session`);
|
|
1238
|
-
}
|
|
1239
|
-
function serveClient(port, host) {
|
|
1240
|
-
let state = "new";
|
|
1241
|
-
const terminals = /* @__PURE__ */ new Map();
|
|
1242
|
-
const groups = /* @__PURE__ */ new Set();
|
|
1243
|
-
const sessions = /* @__PURE__ */ new Set();
|
|
1244
|
-
const watches = /* @__PURE__ */ new Map();
|
|
1245
|
-
let net;
|
|
1246
|
-
const ended = Promise.withResolvers();
|
|
1247
|
-
const send = (message2, transfer = []) => {
|
|
1248
|
-
if (state !== "closed") port.postMessage(message2, transfer);
|
|
1249
|
-
};
|
|
1250
|
-
const reply = (id, body, transfer) => send({ id, ...body }, transfer);
|
|
1251
|
-
function detach(kill = false, bye = "the slicc-kernel detached this client") {
|
|
1252
|
-
if (state === "closed") return;
|
|
1253
|
-
send({ bye });
|
|
1254
|
-
state = "closed";
|
|
1255
|
-
net?.close();
|
|
1256
|
-
for (const unwatch of watches.values()) unwatch();
|
|
1257
|
-
watches.clear();
|
|
1258
|
-
for (const session of terminals.values()) session.close();
|
|
1259
|
-
terminals.clear();
|
|
1260
|
-
if (kill) killGroups(host, [...groups]);
|
|
1261
|
-
groups.clear();
|
|
1262
|
-
port.close?.();
|
|
1263
|
-
ended.resolve();
|
|
973
|
+
async peek(max, signal) {
|
|
974
|
+
while (this.size === 0) {
|
|
975
|
+
if (this.writers === 0) return new Uint8Array(0);
|
|
976
|
+
await this.changed(signal);
|
|
977
|
+
}
|
|
978
|
+
const out = new Uint8Array(Math.min(max, this.size));
|
|
979
|
+
let filled = 0;
|
|
980
|
+
for (const chunk2 of this.chunks) {
|
|
981
|
+
if (filled === out.length) break;
|
|
982
|
+
const count = Math.min(chunk2.length, out.length - filled);
|
|
983
|
+
out.set(chunk2.subarray(0, count), filled);
|
|
984
|
+
filled += count;
|
|
985
|
+
}
|
|
986
|
+
return out;
|
|
1264
987
|
}
|
|
1265
|
-
async
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
988
|
+
async write(bytes, signal) {
|
|
989
|
+
let offset = 0;
|
|
990
|
+
while (offset < bytes.length) {
|
|
991
|
+
if (this.readers === 0) throw new PipeError("EPIPE");
|
|
992
|
+
const room = this.capacity - this.size;
|
|
993
|
+
if (room === 0) {
|
|
994
|
+
try {
|
|
995
|
+
await this.changed(signal);
|
|
996
|
+
} catch (e) {
|
|
997
|
+
if (offset > 0) return offset;
|
|
998
|
+
throw e;
|
|
999
|
+
}
|
|
1000
|
+
continue;
|
|
1001
|
+
}
|
|
1002
|
+
const n = Math.min(room, bytes.length - offset);
|
|
1003
|
+
this.chunks.push(bytes.slice(offset, offset + n));
|
|
1004
|
+
this.size += n;
|
|
1005
|
+
offset += n;
|
|
1006
|
+
this.wake();
|
|
1271
1007
|
}
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1008
|
+
return bytes.length;
|
|
1009
|
+
}
|
|
1010
|
+
take(n) {
|
|
1011
|
+
const out = new Uint8Array(n);
|
|
1012
|
+
let filled = 0;
|
|
1013
|
+
while (filled < n) {
|
|
1014
|
+
const head = this.chunks[0];
|
|
1015
|
+
const count = Math.min(head.length, n - filled);
|
|
1016
|
+
out.set(head.subarray(0, count), filled);
|
|
1017
|
+
filled += count;
|
|
1018
|
+
if (count === head.length) this.chunks.shift();
|
|
1019
|
+
else this.chunks[0] = head.subarray(count);
|
|
1020
|
+
}
|
|
1021
|
+
this.size -= n;
|
|
1022
|
+
return out;
|
|
1023
|
+
}
|
|
1024
|
+
changed(signal) {
|
|
1025
|
+
if (signal?.aborted) return Promise.reject(new PipeError("EINTR"));
|
|
1026
|
+
return new Promise((resolve, reject) => {
|
|
1027
|
+
const onAbort = () => {
|
|
1028
|
+
this.waiters = this.waiters.filter((w) => w !== waiter);
|
|
1029
|
+
reject(new PipeError("EINTR"));
|
|
1030
|
+
};
|
|
1031
|
+
const waiter = () => {
|
|
1032
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1033
|
+
resolve();
|
|
1034
|
+
};
|
|
1035
|
+
this.waiters.push(waiter);
|
|
1036
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1037
|
+
});
|
|
1038
|
+
}
|
|
1039
|
+
wake() {
|
|
1040
|
+
const waiters = this.waiters;
|
|
1041
|
+
this.waiters = [];
|
|
1042
|
+
for (const resolve of waiters) resolve();
|
|
1043
|
+
}
|
|
1044
|
+
};
|
|
1045
|
+
|
|
1046
|
+
// src/kernel/fd-table.ts
|
|
1047
|
+
var KernelError = class extends Error {
|
|
1048
|
+
code;
|
|
1049
|
+
constructor(code) {
|
|
1050
|
+
super(code);
|
|
1051
|
+
this.code = code;
|
|
1052
|
+
}
|
|
1053
|
+
};
|
|
1054
|
+
function pollFile(file) {
|
|
1055
|
+
return file.poll?.() ?? { readable: !!file.read, writable: !!file.write, hangup: false };
|
|
1056
|
+
}
|
|
1057
|
+
var OpenFile = class {
|
|
1058
|
+
refs = 1;
|
|
1059
|
+
file;
|
|
1060
|
+
constructor(file) {
|
|
1061
|
+
this.file = file;
|
|
1062
|
+
}
|
|
1063
|
+
retain() {
|
|
1064
|
+
this.refs += 1;
|
|
1065
|
+
return this;
|
|
1066
|
+
}
|
|
1067
|
+
release() {
|
|
1068
|
+
this.refs -= 1;
|
|
1069
|
+
if (this.refs === 0) return this.file.close();
|
|
1070
|
+
}
|
|
1071
|
+
};
|
|
1072
|
+
function openPipe(capacity) {
|
|
1073
|
+
const pipe = new KernelPipe(capacity);
|
|
1074
|
+
pipe.openRead();
|
|
1075
|
+
pipe.openWrite();
|
|
1076
|
+
return {
|
|
1077
|
+
read: new OpenFile({
|
|
1078
|
+
read: async (max, signal) => {
|
|
1079
|
+
try {
|
|
1080
|
+
return await pipe.read(max, signal);
|
|
1081
|
+
} catch (e) {
|
|
1082
|
+
throw new KernelError(e.code);
|
|
1083
|
+
}
|
|
1084
|
+
},
|
|
1085
|
+
close: () => pipe.closeRead(),
|
|
1086
|
+
poll: () => ({ readable: pipe.readReady, writable: false, hangup: pipe.writersGone }),
|
|
1087
|
+
changed: (signal) => pipe.changed(signal)
|
|
1088
|
+
}),
|
|
1089
|
+
write: new OpenFile({
|
|
1090
|
+
write: async (bytes, signal) => {
|
|
1091
|
+
try {
|
|
1092
|
+
return await pipe.write(bytes, signal);
|
|
1093
|
+
} catch (e) {
|
|
1094
|
+
throw new KernelError(e.code);
|
|
1095
|
+
}
|
|
1096
|
+
},
|
|
1097
|
+
close: () => pipe.closeWrite(),
|
|
1098
|
+
poll: () => ({ readable: false, writable: pipe.writeReady, hangup: pipe.readersGone }),
|
|
1099
|
+
changed: (signal) => pipe.changed(signal)
|
|
1100
|
+
})
|
|
1101
|
+
};
|
|
1102
|
+
}
|
|
1103
|
+
function bytesSource(data) {
|
|
1104
|
+
let offset = 0;
|
|
1105
|
+
return new OpenFile({
|
|
1106
|
+
read: async (max) => {
|
|
1107
|
+
const out = data.subarray(offset, offset + max);
|
|
1108
|
+
offset += out.length;
|
|
1109
|
+
return out;
|
|
1110
|
+
},
|
|
1111
|
+
close: () => {
|
|
1112
|
+
}
|
|
1113
|
+
});
|
|
1114
|
+
}
|
|
1115
|
+
function sinkFile(onData) {
|
|
1116
|
+
return new OpenFile({
|
|
1117
|
+
write: async (bytes) => {
|
|
1118
|
+
onData(bytes.slice());
|
|
1119
|
+
return bytes.length;
|
|
1120
|
+
},
|
|
1121
|
+
close: () => {
|
|
1122
|
+
}
|
|
1123
|
+
});
|
|
1124
|
+
}
|
|
1125
|
+
function nullFile() {
|
|
1126
|
+
return new OpenFile({
|
|
1127
|
+
read: async () => new Uint8Array(0),
|
|
1128
|
+
write: async (bytes) => bytes.length,
|
|
1129
|
+
close: () => {
|
|
1130
|
+
}
|
|
1131
|
+
});
|
|
1132
|
+
}
|
|
1133
|
+
function deviceFile(device, access) {
|
|
1134
|
+
const read = async (max) => {
|
|
1135
|
+
if (device === "null") return new Uint8Array(0);
|
|
1136
|
+
const out = new Uint8Array(max);
|
|
1137
|
+
if (device === "urandom") {
|
|
1138
|
+
for (let at = 0; at < max; at += 65536) {
|
|
1139
|
+
crypto.getRandomValues(out.subarray(at, Math.min(max, at + 65536)));
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
return out;
|
|
1143
|
+
};
|
|
1144
|
+
return new OpenFile({
|
|
1145
|
+
...access !== "write" ? { read } : {},
|
|
1146
|
+
...access !== "read" ? { write: async (bytes) => bytes.length } : {},
|
|
1147
|
+
seek: async () => 0,
|
|
1148
|
+
heldMeta: { device, ...access ? { access } : {} },
|
|
1149
|
+
close: () => {
|
|
1150
|
+
}
|
|
1151
|
+
});
|
|
1152
|
+
}
|
|
1153
|
+
function heldFile(meta) {
|
|
1154
|
+
return new OpenFile({ held: true, ...meta ? { heldMeta: meta } : {}, close: () => {
|
|
1155
|
+
} });
|
|
1156
|
+
}
|
|
1157
|
+
function kernelFdKind(file) {
|
|
1158
|
+
if (file.held) return "held";
|
|
1159
|
+
if (file.heldMeta && "device" in file.heldMeta) return "device";
|
|
1160
|
+
if (file.tty) return "tty";
|
|
1161
|
+
return file.seek ? "file" : "stream";
|
|
1162
|
+
}
|
|
1163
|
+
var FdTable = class _FdTable {
|
|
1164
|
+
static MAX_FDS = 1024;
|
|
1165
|
+
fds = /* @__PURE__ */ new Map();
|
|
1166
|
+
cloexec = /* @__PURE__ */ new Set();
|
|
1167
|
+
status = /* @__PURE__ */ new Map();
|
|
1168
|
+
get(fd) {
|
|
1169
|
+
const file = this.fds.get(fd);
|
|
1170
|
+
if (!file) throw new KernelError("EBADF");
|
|
1171
|
+
return file;
|
|
1172
|
+
}
|
|
1173
|
+
stdioTerminal() {
|
|
1174
|
+
for (const fd of [0, 1, 2]) {
|
|
1175
|
+
const tty = this.fds.get(fd)?.file.tty;
|
|
1176
|
+
if (tty) return tty;
|
|
1177
|
+
}
|
|
1178
|
+
return void 0;
|
|
1179
|
+
}
|
|
1180
|
+
numbers() {
|
|
1181
|
+
return [...this.fds.keys()].sort((a, b) => a - b);
|
|
1182
|
+
}
|
|
1183
|
+
has(fd) {
|
|
1184
|
+
return this.fds.has(fd);
|
|
1185
|
+
}
|
|
1186
|
+
setCloseOnExec(fd) {
|
|
1187
|
+
this.get(fd);
|
|
1188
|
+
this.cloexec.add(fd);
|
|
1189
|
+
}
|
|
1190
|
+
clearCloseOnExec(fd) {
|
|
1191
|
+
this.get(fd);
|
|
1192
|
+
this.cloexec.delete(fd);
|
|
1193
|
+
}
|
|
1194
|
+
closesOnExec(fd) {
|
|
1195
|
+
return this.cloexec.has(fd);
|
|
1196
|
+
}
|
|
1197
|
+
setStatusFlags(fd, flags) {
|
|
1198
|
+
this.get(fd);
|
|
1199
|
+
this.status.set(fd, flags);
|
|
1200
|
+
}
|
|
1201
|
+
statusFlags(fd) {
|
|
1202
|
+
return this.status.get(fd);
|
|
1203
|
+
}
|
|
1204
|
+
install(file, min = 0) {
|
|
1205
|
+
for (let fd = min; fd < _FdTable.MAX_FDS; fd++) {
|
|
1206
|
+
if (!this.fds.has(fd)) {
|
|
1207
|
+
this.fds.set(fd, file);
|
|
1208
|
+
return fd;
|
|
1282
1209
|
}
|
|
1210
|
+
}
|
|
1211
|
+
void Promise.resolve(file.release()).catch(() => void 0);
|
|
1212
|
+
throw new KernelError("EMFILE");
|
|
1213
|
+
}
|
|
1214
|
+
installAt(fd, file) {
|
|
1215
|
+
if (fd < 0 || fd >= _FdTable.MAX_FDS) {
|
|
1216
|
+
void Promise.resolve(file.release()).catch(() => void 0);
|
|
1217
|
+
throw new KernelError("EBADF");
|
|
1218
|
+
}
|
|
1219
|
+
const previous = this.fds.get(fd);
|
|
1220
|
+
this.fds.set(fd, file);
|
|
1221
|
+
this.cloexec.delete(fd);
|
|
1222
|
+
this.status.delete(fd);
|
|
1223
|
+
void Promise.resolve(previous?.release()).catch(() => void 0);
|
|
1224
|
+
}
|
|
1225
|
+
dup(fd, min = 0) {
|
|
1226
|
+
return this.install(this.get(fd).retain(), min);
|
|
1227
|
+
}
|
|
1228
|
+
dup2(oldFd, newFd) {
|
|
1229
|
+
const file = this.get(oldFd);
|
|
1230
|
+
if (oldFd !== newFd) this.installAt(newFd, file.retain());
|
|
1231
|
+
return newFd;
|
|
1232
|
+
}
|
|
1233
|
+
close(fd) {
|
|
1234
|
+
const file = this.get(fd);
|
|
1235
|
+
this.fds.delete(fd);
|
|
1236
|
+
this.cloexec.delete(fd);
|
|
1237
|
+
this.status.delete(fd);
|
|
1238
|
+
return file.release();
|
|
1239
|
+
}
|
|
1240
|
+
fork() {
|
|
1241
|
+
const child = new _FdTable();
|
|
1242
|
+
for (const [fd, file] of this.fds) child.fds.set(fd, file.retain());
|
|
1243
|
+
for (const fd of this.cloexec) child.cloexec.add(fd);
|
|
1244
|
+
for (const [fd, flags] of this.status) child.status.set(fd, flags);
|
|
1245
|
+
return child;
|
|
1246
|
+
}
|
|
1247
|
+
async closeAll() {
|
|
1248
|
+
const files = [...this.fds.values()];
|
|
1249
|
+
this.fds.clear();
|
|
1250
|
+
this.cloexec.clear();
|
|
1251
|
+
this.status.clear();
|
|
1252
|
+
await Promise.allSettled(files.map((file) => Promise.resolve().then(() => file.release())));
|
|
1253
|
+
}
|
|
1254
|
+
};
|
|
1255
|
+
|
|
1256
|
+
// src/kernel/socket.ts
|
|
1257
|
+
var SOMAXCONN = 4096;
|
|
1258
|
+
var EPHEMERAL_FIRST = 32768;
|
|
1259
|
+
var EPHEMERAL_LAST = 60999;
|
|
1260
|
+
var SOL_SOCKET = 1;
|
|
1261
|
+
var SO_TYPE = 3;
|
|
1262
|
+
var SO_ERROR = 4;
|
|
1263
|
+
var SO_SNDBUF = 7;
|
|
1264
|
+
var SO_RCVBUF = 8;
|
|
1265
|
+
var SO_ACCEPTCONN = 30;
|
|
1266
|
+
var SO_DOMAIN = 39;
|
|
1267
|
+
var SOCK_STREAM = 1;
|
|
1268
|
+
var AF_UNIX = 1;
|
|
1269
|
+
var AF_INET = 2;
|
|
1270
|
+
var SHUT_RD = 0;
|
|
1271
|
+
var SHUT_WR = 1;
|
|
1272
|
+
var SHUT_RDWR = 2;
|
|
1273
|
+
function isLoopback(host) {
|
|
1274
|
+
return /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(host);
|
|
1275
|
+
}
|
|
1276
|
+
async function pipeCall(op) {
|
|
1277
|
+
try {
|
|
1278
|
+
return await op();
|
|
1279
|
+
} catch (e) {
|
|
1280
|
+
if (e instanceof PipeError) throw new KernelError(e.code);
|
|
1281
|
+
throw e;
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
var Changes = class {
|
|
1285
|
+
waiters = [];
|
|
1286
|
+
wait(signal) {
|
|
1287
|
+
if (signal?.aborted) return Promise.reject(new KernelError("EINTR"));
|
|
1288
|
+
return new Promise((resolve, reject) => {
|
|
1289
|
+
const onAbort = () => {
|
|
1290
|
+
this.waiters = this.waiters.filter((w) => w !== waiter);
|
|
1291
|
+
reject(new KernelError("EINTR"));
|
|
1292
|
+
};
|
|
1293
|
+
const waiter = () => {
|
|
1294
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1295
|
+
resolve();
|
|
1296
|
+
};
|
|
1297
|
+
this.waiters.push(waiter);
|
|
1298
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1283
1299
|
});
|
|
1284
1300
|
}
|
|
1285
|
-
|
|
1286
|
-
const
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1301
|
+
wake() {
|
|
1302
|
+
const waiters = this.waiters;
|
|
1303
|
+
this.waiters = [];
|
|
1304
|
+
for (const resolve of waiters) resolve();
|
|
1305
|
+
}
|
|
1306
|
+
};
|
|
1307
|
+
function link(a, b) {
|
|
1308
|
+
const ab = new KernelPipe();
|
|
1309
|
+
const ba = new KernelPipe();
|
|
1310
|
+
for (const pipe of [ab, ba]) {
|
|
1311
|
+
pipe.openRead();
|
|
1312
|
+
pipe.openWrite();
|
|
1313
|
+
}
|
|
1314
|
+
a.attach({ rx: ba, tx: ab });
|
|
1315
|
+
b.attach({ rx: ab, tx: ba });
|
|
1316
|
+
}
|
|
1317
|
+
var KernelSocket = class _KernelSocket {
|
|
1318
|
+
net;
|
|
1319
|
+
domain;
|
|
1320
|
+
state = "open";
|
|
1321
|
+
local;
|
|
1322
|
+
peer;
|
|
1323
|
+
link;
|
|
1324
|
+
readShut = false;
|
|
1325
|
+
writeShut = false;
|
|
1326
|
+
queue = [];
|
|
1327
|
+
backlog = 0;
|
|
1328
|
+
bound = false;
|
|
1329
|
+
changes = new Changes();
|
|
1330
|
+
options = /* @__PURE__ */ new Map();
|
|
1331
|
+
constructor(net, domain) {
|
|
1332
|
+
this.net = net;
|
|
1333
|
+
this.domain = domain;
|
|
1334
|
+
}
|
|
1335
|
+
attach(link2) {
|
|
1336
|
+
this.link = link2;
|
|
1337
|
+
this.state = "connected";
|
|
1338
|
+
this.changes.wake();
|
|
1339
|
+
}
|
|
1340
|
+
get listening() {
|
|
1341
|
+
return this.state === "listening";
|
|
1342
|
+
}
|
|
1343
|
+
connection() {
|
|
1344
|
+
if (!this.link || this.state !== "connected") throw new KernelError("ENOTCONN");
|
|
1345
|
+
return this.link;
|
|
1346
|
+
}
|
|
1347
|
+
async read(max, signal) {
|
|
1348
|
+
const { rx } = this.connection();
|
|
1349
|
+
if (this.readShut || max <= 0) return new Uint8Array(0);
|
|
1350
|
+
return pipeCall(() => rx.read(max, signal));
|
|
1351
|
+
}
|
|
1352
|
+
async peek(max, signal) {
|
|
1353
|
+
const { rx } = this.connection();
|
|
1354
|
+
if (this.readShut || max <= 0) return new Uint8Array(0);
|
|
1355
|
+
return pipeCall(() => rx.peek(max, signal));
|
|
1356
|
+
}
|
|
1357
|
+
async write(bytes, signal) {
|
|
1358
|
+
const { tx } = this.connection();
|
|
1359
|
+
if (this.writeShut) throw new KernelError("EPIPE");
|
|
1360
|
+
return pipeCall(() => tx.write(bytes, signal));
|
|
1361
|
+
}
|
|
1362
|
+
poll() {
|
|
1363
|
+
if (this.state === "listening") {
|
|
1364
|
+
return { readable: this.queue.length > 0, writable: false, hangup: false };
|
|
1365
|
+
}
|
|
1366
|
+
if (this.state !== "connected" || !this.link) {
|
|
1367
|
+
return { readable: false, writable: false, hangup: true };
|
|
1368
|
+
}
|
|
1369
|
+
const { rx, tx } = this.link;
|
|
1370
|
+
return {
|
|
1371
|
+
readable: this.readShut || rx.readReady,
|
|
1372
|
+
writable: this.writeShut || tx.writeReady,
|
|
1373
|
+
hangup: rx.writersGone && tx.readersGone
|
|
1374
|
+
};
|
|
1375
|
+
}
|
|
1376
|
+
changed(signal) {
|
|
1377
|
+
const own = this.changes.wait(signal);
|
|
1378
|
+
if (this.state !== "connected" || !this.link) return own;
|
|
1379
|
+
const waits = [own, this.link.rx.changed(signal), this.link.tx.changed(signal)];
|
|
1380
|
+
for (const wait2 of waits) wait2.catch(() => void 0);
|
|
1381
|
+
return pipeCall(() => Promise.race(waits));
|
|
1382
|
+
}
|
|
1383
|
+
bind(addr) {
|
|
1384
|
+
if (this.state !== "open" || this.local) throw new KernelError("EINVAL");
|
|
1385
|
+
if (addr.family !== this.domain) throw new KernelError("EAFNOSUPPORT");
|
|
1386
|
+
this.local = this.net.bind(this, addr);
|
|
1387
|
+
this.bound = true;
|
|
1388
|
+
}
|
|
1389
|
+
listen(backlog) {
|
|
1390
|
+
if (this.state === "connected") throw new KernelError("EISCONN");
|
|
1391
|
+
if (this.state !== "open" && this.state !== "listening") throw new KernelError("EINVAL");
|
|
1392
|
+
if (!this.local) {
|
|
1393
|
+
if (this.domain === "unix") throw new KernelError("EINVAL");
|
|
1394
|
+
this.bind({ family: "inet", host: "0.0.0.0", port: 0 });
|
|
1395
|
+
}
|
|
1396
|
+
this.backlog = Math.min(Math.max(backlog, 1), SOMAXCONN);
|
|
1397
|
+
this.state = "listening";
|
|
1398
|
+
}
|
|
1399
|
+
async accept(signal, nonblock = false) {
|
|
1400
|
+
for (; ; ) {
|
|
1401
|
+
if (this.state !== "listening") throw new KernelError("EINVAL");
|
|
1402
|
+
const next = this.queue.shift();
|
|
1403
|
+
if (next) return next;
|
|
1404
|
+
if (nonblock) throw new KernelError("EAGAIN");
|
|
1405
|
+
await this.changes.wait(signal);
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
offer(server) {
|
|
1409
|
+
if (this.state !== "listening" || this.queue.length >= this.backlog) return false;
|
|
1410
|
+
this.queue.push(server);
|
|
1411
|
+
this.changes.wake();
|
|
1412
|
+
return true;
|
|
1413
|
+
}
|
|
1414
|
+
connect(addr) {
|
|
1415
|
+
if (this.state === "connected") throw new KernelError("EISCONN");
|
|
1416
|
+
if (this.state !== "open") throw new KernelError("EINVAL");
|
|
1417
|
+
if (addr.family !== this.domain) throw new KernelError("EAFNOSUPPORT");
|
|
1418
|
+
const target = this.net.target(addr);
|
|
1419
|
+
if (!this.local) {
|
|
1420
|
+
if (addr.family === "inet") {
|
|
1421
|
+
this.local = this.net.bind(this, { family: "inet", host: "127.0.0.1", port: 0 });
|
|
1422
|
+
this.bound = true;
|
|
1423
|
+
} else this.local = { family: "unix", path: "" };
|
|
1296
1424
|
}
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1425
|
+
const server = new _KernelSocket(this.net, this.domain);
|
|
1426
|
+
server.local = addr.family === "inet" ? { ...addr, host: canonicalHost(addr.host) } : addr;
|
|
1427
|
+
server.peer = this.local;
|
|
1428
|
+
this.peer = server.local;
|
|
1429
|
+
link(this, server);
|
|
1430
|
+
if (!target.offer(server)) {
|
|
1431
|
+
this.unlink();
|
|
1432
|
+
server.close();
|
|
1433
|
+
throw new KernelError("ECONNREFUSED");
|
|
1301
1434
|
}
|
|
1302
1435
|
}
|
|
1303
|
-
|
|
1304
|
-
const
|
|
1305
|
-
if (
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
onStarted: (pid) => {
|
|
1313
|
-
leader = pid;
|
|
1314
|
-
if (state === "closed") return;
|
|
1315
|
-
groups.add(req.options.pgid ?? pid);
|
|
1316
|
-
if (req.options.pgid === void 0) sessions.add(pid);
|
|
1317
|
-
reply(req.id, { started: pid });
|
|
1318
|
-
}
|
|
1319
|
-
});
|
|
1320
|
-
if (leader === void 0) throw fsError("ENOENT", `${req.argv[0] ?? ""}: command not found`);
|
|
1321
|
-
if (!l.list().some((p) => p.pgid === leader)) groups.delete(leader);
|
|
1322
|
-
return { result: result.status };
|
|
1436
|
+
unlink() {
|
|
1437
|
+
const link2 = this.link;
|
|
1438
|
+
if (link2) {
|
|
1439
|
+
link2.rx.closeRead();
|
|
1440
|
+
link2.tx.closeWrite();
|
|
1441
|
+
}
|
|
1442
|
+
this.link = void 0;
|
|
1443
|
+
this.peer = void 0;
|
|
1444
|
+
this.state = "open";
|
|
1323
1445
|
}
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1446
|
+
shutdown(how) {
|
|
1447
|
+
const { rx, tx } = this.connection();
|
|
1448
|
+
if (how !== SHUT_RD && how !== SHUT_WR && how !== SHUT_RDWR) throw new KernelError("EINVAL");
|
|
1449
|
+
if (how !== SHUT_WR && !this.readShut) {
|
|
1450
|
+
this.readShut = true;
|
|
1451
|
+
rx.closeRead();
|
|
1327
1452
|
}
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
return spawn(req, l);
|
|
1332
|
-
case "open-terminal":
|
|
1333
|
-
return terminal(req, l);
|
|
1334
|
-
case "terminal": {
|
|
1335
|
-
const session = terminals.get(req.terminal);
|
|
1336
|
-
if (!session) throw new Error(`no terminal ${req.terminal}`);
|
|
1337
|
-
act(session, req, signalOf(host));
|
|
1338
|
-
return { result: true };
|
|
1339
|
-
}
|
|
1340
|
-
case "kill":
|
|
1341
|
-
if (!l.kill(req.pid, signalOf(host)(req.signal))) throw fsError("ESRCH", `pid ${req.pid}`);
|
|
1342
|
-
return { result: true };
|
|
1343
|
-
case "ps":
|
|
1344
|
-
return {
|
|
1345
|
-
result: l.list().map(({ tid: _, ...entry }) => entry)
|
|
1346
|
-
};
|
|
1347
|
-
case "fs":
|
|
1348
|
-
if (!FS_METHODS.includes(req.method)) {
|
|
1349
|
-
throw new Error(`unknown file system call ${req.method}`);
|
|
1350
|
-
}
|
|
1351
|
-
return fsCall(l.fs, req.method, req.args);
|
|
1352
|
-
case "watch": {
|
|
1353
|
-
const unwatch = l.watchers.watch(
|
|
1354
|
-
req.paths,
|
|
1355
|
-
{ recursive: req.recursive },
|
|
1356
|
-
(change) => send({ watch: req.id, change })
|
|
1357
|
-
);
|
|
1358
|
-
watches.set(req.id, unwatch);
|
|
1359
|
-
return { result: req.id };
|
|
1360
|
-
}
|
|
1361
|
-
case "unwatch":
|
|
1362
|
-
watches.get(req.watch)?.();
|
|
1363
|
-
watches.delete(req.watch);
|
|
1364
|
-
return { result: true };
|
|
1365
|
-
case "mount":
|
|
1366
|
-
return { result: await l.mount(req.spec) };
|
|
1367
|
-
case "umount":
|
|
1368
|
-
l.umount(req.target);
|
|
1369
|
-
return { result: true };
|
|
1370
|
-
case "mounts":
|
|
1371
|
-
return { result: l.mounts.list() };
|
|
1372
|
-
case "dial":
|
|
1373
|
-
return dialFor(l, req, watches);
|
|
1374
|
-
case "detach":
|
|
1375
|
-
return { result: true };
|
|
1376
|
-
default:
|
|
1377
|
-
throw new Error(`unknown op ${req.op}`);
|
|
1453
|
+
if (how !== SHUT_RD && !this.writeShut) {
|
|
1454
|
+
this.writeShut = true;
|
|
1455
|
+
tx.closeWrite();
|
|
1378
1456
|
}
|
|
1457
|
+
this.changes.wake();
|
|
1379
1458
|
}
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1459
|
+
getOption(level, name2) {
|
|
1460
|
+
if (level === SOL_SOCKET) {
|
|
1461
|
+
switch (name2) {
|
|
1462
|
+
case SO_TYPE:
|
|
1463
|
+
return SOCK_STREAM;
|
|
1464
|
+
case SO_ERROR:
|
|
1465
|
+
return 0;
|
|
1466
|
+
case SO_ACCEPTCONN:
|
|
1467
|
+
return this.state === "listening" ? 1 : 0;
|
|
1468
|
+
case SO_DOMAIN:
|
|
1469
|
+
return this.domain === "inet" ? AF_INET : AF_UNIX;
|
|
1470
|
+
case SO_SNDBUF:
|
|
1471
|
+
case SO_RCVBUF:
|
|
1472
|
+
return this.options.get(`${level}:${name2}`) ?? PIPE_CAPACITY;
|
|
1389
1473
|
}
|
|
1390
|
-
return;
|
|
1391
1474
|
}
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1475
|
+
return this.options.get(`${level}:${name2}`) ?? 0;
|
|
1476
|
+
}
|
|
1477
|
+
setOption(level, name2, value) {
|
|
1478
|
+
this.options.set(`${level}:${name2}`, value);
|
|
1479
|
+
}
|
|
1480
|
+
close() {
|
|
1481
|
+
const was = this.state;
|
|
1482
|
+
this.state = "closed";
|
|
1483
|
+
if (was === "listening") {
|
|
1484
|
+
for (const pending of this.queue.splice(0)) pending.close();
|
|
1396
1485
|
}
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
(
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
}
|
|
1416
|
-
|
|
1417
|
-
// src/fs/opfs.ts
|
|
1418
|
-
var S_IFDIR = 16384;
|
|
1419
|
-
var S_IFREG = 32768;
|
|
1420
|
-
var S_IFLNK = 40960;
|
|
1421
|
-
var PERM_MASK = 4095;
|
|
1422
|
-
var READ_ATTEMPTS = 3;
|
|
1423
|
-
var MAX_LINKS2 = 40;
|
|
1424
|
-
var CHECK_MS = 1e3;
|
|
1425
|
-
var EXECUTABLE = /\/node_modules\/(?:@[^/]+\/)?[^/]+\/bin\//;
|
|
1426
|
-
var ERRNO_BY_DOM_ERROR = {
|
|
1427
|
-
NotFoundError: "ENOENT",
|
|
1428
|
-
TypeMismatchError: "ENOTDIR",
|
|
1429
|
-
InvalidModificationError: "ENOTEMPTY",
|
|
1430
|
-
NoModificationAllowedError: "EBUSY",
|
|
1431
|
-
TypeError: "EINVAL",
|
|
1432
|
-
NotAllowedError: "EACCES"
|
|
1486
|
+
if (this.link) {
|
|
1487
|
+
if (!this.readShut) this.link.rx.closeRead();
|
|
1488
|
+
if (!this.writeShut) this.link.tx.closeWrite();
|
|
1489
|
+
this.readShut = true;
|
|
1490
|
+
this.writeShut = true;
|
|
1491
|
+
}
|
|
1492
|
+
if (this.bound && this.local) this.net.release(this.local, this);
|
|
1493
|
+
this.bound = false;
|
|
1494
|
+
this.changes.wake();
|
|
1495
|
+
}
|
|
1496
|
+
static pair(net, domain) {
|
|
1497
|
+
const a = new _KernelSocket(net, domain);
|
|
1498
|
+
const b = new _KernelSocket(net, domain);
|
|
1499
|
+
const name2 = domain === "inet" ? { family: "inet", host: "127.0.0.1", port: 0 } : { family: "unix", path: "" };
|
|
1500
|
+
a.local = b.local = a.peer = b.peer = name2;
|
|
1501
|
+
link(a, b);
|
|
1502
|
+
return [a, b];
|
|
1503
|
+
}
|
|
1433
1504
|
};
|
|
1434
|
-
function
|
|
1435
|
-
|
|
1436
|
-
const name2 = err?.name;
|
|
1437
|
-
return fsError(typeof name2 === "string" && ERRNO_BY_DOM_ERROR[name2] || "EIO", path);
|
|
1438
|
-
}
|
|
1439
|
-
function missing(err) {
|
|
1440
|
-
const code = err?.code;
|
|
1441
|
-
return code === "ENOENT" || code === "ENOTDIR";
|
|
1442
|
-
}
|
|
1443
|
-
function split(path) {
|
|
1444
|
-
return path.split("/").filter(Boolean);
|
|
1445
|
-
}
|
|
1446
|
-
function parentOf2(path) {
|
|
1447
|
-
return path.slice(0, path.lastIndexOf("/")) || "/";
|
|
1448
|
-
}
|
|
1449
|
-
function baseOf(path) {
|
|
1450
|
-
return path.slice(path.lastIndexOf("/") + 1);
|
|
1451
|
-
}
|
|
1452
|
-
function prefixes(path) {
|
|
1453
|
-
const parts = split(path);
|
|
1454
|
-
return parts.map((_, i) => `/${parts.slice(0, i + 1).join("/")}`);
|
|
1455
|
-
}
|
|
1456
|
-
function within(path, root) {
|
|
1457
|
-
return path === root || path.startsWith(`${root}/`);
|
|
1458
|
-
}
|
|
1459
|
-
function isHandle(child) {
|
|
1460
|
-
return "kind" in child;
|
|
1505
|
+
function canonicalHost(host) {
|
|
1506
|
+
return host === "0.0.0.0" ? "127.0.0.1" : host;
|
|
1461
1507
|
}
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
this.
|
|
1472
|
-
|
|
1473
|
-
this.channel.onmessage = ({ data }) => {
|
|
1474
|
-
for (const path of data) this.forget(path);
|
|
1475
|
-
};
|
|
1476
|
-
this.channel.unref?.();
|
|
1477
|
-
}
|
|
1508
|
+
function keyOf(addr) {
|
|
1509
|
+
return addr.family === "inet" ? `inet:${addr.port}` : `unix:${addr.path}`;
|
|
1510
|
+
}
|
|
1511
|
+
var LoopbackNet = class {
|
|
1512
|
+
bound = /* @__PURE__ */ new Map();
|
|
1513
|
+
activators = /* @__PURE__ */ new Map();
|
|
1514
|
+
internal = /* @__PURE__ */ new Set();
|
|
1515
|
+
nextEphemeral = EPHEMERAL_FIRST;
|
|
1516
|
+
kernelOnly(port, reserve = false) {
|
|
1517
|
+
if (reserve) this.internal.add(port);
|
|
1518
|
+
return this.internal.has(port);
|
|
1478
1519
|
}
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
this.channel?.postMessage(paths);
|
|
1520
|
+
activate(addr, start) {
|
|
1521
|
+
this.activators.set(keyOf(addr), start);
|
|
1482
1522
|
}
|
|
1483
|
-
|
|
1484
|
-
return
|
|
1523
|
+
socket(domain) {
|
|
1524
|
+
return new KernelSocket(this, domain);
|
|
1485
1525
|
}
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
this.checked.set(dir, performance.now());
|
|
1492
|
-
}
|
|
1493
|
-
return dir;
|
|
1526
|
+
listen(addr, backlog = 128) {
|
|
1527
|
+
const socket = this.socket(addr.family);
|
|
1528
|
+
socket.bind(addr);
|
|
1529
|
+
socket.listen(backlog);
|
|
1530
|
+
return socket;
|
|
1494
1531
|
}
|
|
1495
|
-
|
|
1496
|
-
const
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
for (; from > 0 && !cached; from--)
|
|
1500
|
-
cached = this.dirs.get(`/${parts.slice(0, from).join("/")}`);
|
|
1501
|
-
if (cached) from++;
|
|
1502
|
-
const at = `/${parts.slice(0, from).join("/")}`;
|
|
1503
|
-
if (cached && !await this.current(at, cached)) {
|
|
1504
|
-
this.forget(at);
|
|
1505
|
-
cached = void 0;
|
|
1506
|
-
from = 0;
|
|
1507
|
-
}
|
|
1508
|
-
try {
|
|
1509
|
-
return await this.walk(parts, from, cached ?? this.root);
|
|
1510
|
-
} catch (err) {
|
|
1511
|
-
throw translate(err, path);
|
|
1512
|
-
}
|
|
1532
|
+
connect(addr) {
|
|
1533
|
+
const socket = this.socket(addr.family);
|
|
1534
|
+
socket.connect(addr);
|
|
1535
|
+
return socket;
|
|
1513
1536
|
}
|
|
1514
|
-
|
|
1515
|
-
if (
|
|
1516
|
-
|
|
1517
|
-
if (
|
|
1518
|
-
|
|
1519
|
-
|
|
1537
|
+
bind(socket, addr) {
|
|
1538
|
+
if (addr.family === "unix") {
|
|
1539
|
+
if (addr.path === "") throw new KernelError("EINVAL");
|
|
1540
|
+
} else if (addr.host !== "0.0.0.0" && !isLoopback(addr.host)) {
|
|
1541
|
+
throw new KernelError("EADDRNOTAVAIL");
|
|
1542
|
+
}
|
|
1543
|
+
const named = addr.family === "inet" && addr.port === 0 ? { ...addr, port: this.ephemeral() } : addr;
|
|
1544
|
+
const key = keyOf(named);
|
|
1545
|
+
if (this.bound.has(key)) throw new KernelError("EADDRINUSE");
|
|
1546
|
+
this.bound.set(key, socket);
|
|
1547
|
+
return named;
|
|
1520
1548
|
}
|
|
1521
|
-
|
|
1522
|
-
|
|
1549
|
+
release(addr, socket) {
|
|
1550
|
+
const key = keyOf(addr);
|
|
1551
|
+
if (this.bound.get(key) === socket) this.bound.delete(key);
|
|
1523
1552
|
}
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
return await op(dir);
|
|
1528
|
-
} catch (err) {
|
|
1529
|
-
if (err?.name !== "NotFoundError" || await this.current(path, dir))
|
|
1530
|
-
throw err;
|
|
1531
|
-
this.forget(path);
|
|
1532
|
-
return op(await this.dir(path));
|
|
1553
|
+
target(addr) {
|
|
1554
|
+
if (addr.family === "inet" && !isLoopback(canonicalHost(addr.host))) {
|
|
1555
|
+
throw new KernelError("ENETUNREACH");
|
|
1533
1556
|
}
|
|
1557
|
+
const key = keyOf(addr);
|
|
1558
|
+
if (!this.bound.has(key)) this.activators.get(key)?.();
|
|
1559
|
+
const socket = this.bound.get(key);
|
|
1560
|
+
if (!socket?.listening) throw new KernelError("ECONNREFUSED");
|
|
1561
|
+
return socket;
|
|
1534
1562
|
}
|
|
1535
|
-
|
|
1536
|
-
|
|
1563
|
+
ephemeral() {
|
|
1564
|
+
const span = EPHEMERAL_LAST - EPHEMERAL_FIRST + 1;
|
|
1565
|
+
for (let i = 0; i < span; i++) {
|
|
1566
|
+
const port = this.nextEphemeral;
|
|
1567
|
+
this.nextEphemeral = port === EPHEMERAL_LAST ? EPHEMERAL_FIRST : port + 1;
|
|
1568
|
+
if (!this.bound.has(`inet:${port}`)) return port;
|
|
1569
|
+
}
|
|
1570
|
+
throw new KernelError("EADDRINUSE");
|
|
1537
1571
|
}
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1572
|
+
};
|
|
1573
|
+
|
|
1574
|
+
// src/kernel/dial.ts
|
|
1575
|
+
var CHUNK = 64 * 1024;
|
|
1576
|
+
var LOOPBACK_NAMES = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", "::1", "[::1]"]);
|
|
1577
|
+
function dialSocket(net, port, host = "127.0.0.1") {
|
|
1578
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) throw new KernelError("EINVAL");
|
|
1579
|
+
const target = LOOPBACK_NAMES.has(host.toLowerCase()) ? "127.0.0.1" : host;
|
|
1580
|
+
if (net.kernelOnly(port)) {
|
|
1581
|
+
throw Object.assign(
|
|
1582
|
+
new Error(`ECONNREFUSED: port ${port} only takes connections from inside the kernel`),
|
|
1583
|
+
{
|
|
1584
|
+
code: "ECONNREFUSED"
|
|
1585
|
+
}
|
|
1586
|
+
);
|
|
1543
1587
|
}
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1588
|
+
return net.connect({ family: "inet", host: target, port });
|
|
1589
|
+
}
|
|
1590
|
+
var codeOf = (err) => {
|
|
1591
|
+
const code = err?.code;
|
|
1592
|
+
return typeof code === "string" ? code : "EIO";
|
|
1593
|
+
};
|
|
1594
|
+
function serveSocket(socket, port, onClose) {
|
|
1595
|
+
let closed = false;
|
|
1596
|
+
let writes = Promise.resolve();
|
|
1597
|
+
let credit = 0;
|
|
1598
|
+
let granted2;
|
|
1599
|
+
const send = (message2, transfer) => {
|
|
1600
|
+
if (!closed) port.postMessage(message2, transfer);
|
|
1601
|
+
};
|
|
1602
|
+
const close = (reset = false) => {
|
|
1603
|
+
if (closed) return;
|
|
1604
|
+
if (reset) send({ error: "ECONNRESET" });
|
|
1605
|
+
closed = true;
|
|
1606
|
+
granted2?.();
|
|
1607
|
+
socket.close();
|
|
1608
|
+
port.close();
|
|
1609
|
+
onClose?.();
|
|
1610
|
+
};
|
|
1611
|
+
const write = (data) => {
|
|
1612
|
+
writes = writes.then(async () => {
|
|
1613
|
+
if (closed) return;
|
|
1550
1614
|
try {
|
|
1551
|
-
|
|
1615
|
+
await socket.write(data);
|
|
1616
|
+
send({ ack: true });
|
|
1552
1617
|
} catch (err) {
|
|
1553
|
-
|
|
1618
|
+
send({ error: codeOf(err) });
|
|
1554
1619
|
}
|
|
1555
|
-
return this.child(parent, name2, second);
|
|
1556
|
-
}).catch((err) => {
|
|
1557
|
-
throw translate(err, path);
|
|
1558
1620
|
});
|
|
1559
|
-
}
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
async
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
if (
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1621
|
+
};
|
|
1622
|
+
port.addEventListener("message", ({ data }) => {
|
|
1623
|
+
if (data instanceof Uint8Array) write(data);
|
|
1624
|
+
else if ("more" in data) {
|
|
1625
|
+
credit++;
|
|
1626
|
+
granted2?.();
|
|
1627
|
+
} else if ("end" in data) {
|
|
1628
|
+
writes = writes.then(() => {
|
|
1629
|
+
if (!closed) socket.shutdown(SHUT_WR);
|
|
1630
|
+
});
|
|
1631
|
+
} else if ("close" in data) close();
|
|
1632
|
+
});
|
|
1633
|
+
port.start?.();
|
|
1634
|
+
const turn = () => credit > 0 || closed ? Promise.resolve() : new Promise((r) => granted2 = r);
|
|
1635
|
+
void (async () => {
|
|
1636
|
+
try {
|
|
1637
|
+
for (; ; ) {
|
|
1638
|
+
await turn();
|
|
1639
|
+
if (closed) return;
|
|
1640
|
+
const chunk2 = await socket.read(CHUNK);
|
|
1641
|
+
if (chunk2.length === 0) break;
|
|
1642
|
+
credit--;
|
|
1643
|
+
send(chunk2, [chunk2.buffer]);
|
|
1644
|
+
}
|
|
1645
|
+
send({ end: true });
|
|
1646
|
+
} catch (err) {
|
|
1647
|
+
send({ error: codeOf(err) });
|
|
1648
|
+
}
|
|
1649
|
+
})();
|
|
1650
|
+
return () => close(true);
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
// src/kernel/signals.ts
|
|
1654
|
+
var SIG = {
|
|
1655
|
+
HUP: 1,
|
|
1656
|
+
INT: 2,
|
|
1657
|
+
QUIT: 3,
|
|
1658
|
+
ILL: 4,
|
|
1659
|
+
TRAP: 5,
|
|
1660
|
+
ABRT: 6,
|
|
1661
|
+
BUS: 7,
|
|
1662
|
+
FPE: 8,
|
|
1663
|
+
KILL: 9,
|
|
1664
|
+
USR1: 10,
|
|
1665
|
+
SEGV: 11,
|
|
1666
|
+
USR2: 12,
|
|
1667
|
+
PIPE: 13,
|
|
1668
|
+
ALRM: 14,
|
|
1669
|
+
TERM: 15,
|
|
1670
|
+
STKFLT: 16,
|
|
1671
|
+
CHLD: 17,
|
|
1672
|
+
CONT: 18,
|
|
1673
|
+
STOP: 19,
|
|
1674
|
+
TSTP: 20,
|
|
1675
|
+
TTIN: 21,
|
|
1676
|
+
TTOU: 22,
|
|
1677
|
+
URG: 23,
|
|
1678
|
+
XCPU: 24,
|
|
1679
|
+
XFSZ: 25,
|
|
1680
|
+
VTALRM: 26,
|
|
1681
|
+
PROF: 27,
|
|
1682
|
+
WINCH: 28,
|
|
1683
|
+
POLL: 29,
|
|
1684
|
+
PWR: 30,
|
|
1685
|
+
SYS: 31
|
|
1686
|
+
};
|
|
1687
|
+
var IGNORED_BY_DEFAULT = /* @__PURE__ */ new Set([SIG.CHLD, SIG.URG, SIG.WINCH]);
|
|
1688
|
+
var STOPPING = /* @__PURE__ */ new Set([SIG.STOP, SIG.TSTP, SIG.TTIN, SIG.TTOU]);
|
|
1689
|
+
function defaultAction(sig) {
|
|
1690
|
+
if (sig === SIG.CONT) return "continue";
|
|
1691
|
+
if (STOPPING.has(sig)) return "stop";
|
|
1692
|
+
return IGNORED_BY_DEFAULT.has(sig) ? "ignore" : "terminate";
|
|
1693
|
+
}
|
|
1694
|
+
function isSignal(sig) {
|
|
1695
|
+
return Number.isInteger(sig) && sig >= 1 && sig <= 31;
|
|
1696
|
+
}
|
|
1697
|
+
function sigbit(sig) {
|
|
1698
|
+
return 1 << sig;
|
|
1699
|
+
}
|
|
1700
|
+
var SIGNAL_BY_NAME = {
|
|
1701
|
+
SIGINT: SIG.INT,
|
|
1702
|
+
SIGTERM: SIG.TERM,
|
|
1703
|
+
SIGKILL: SIG.KILL,
|
|
1704
|
+
SIGSTOP: SIG.STOP,
|
|
1705
|
+
SIGCONT: SIG.CONT
|
|
1706
|
+
};
|
|
1707
|
+
|
|
1708
|
+
// src/body-idle.ts
|
|
1709
|
+
var BODY_IDLE_MS = 5 * 60 * 1e3;
|
|
1710
|
+
|
|
1711
|
+
// src/transport.ts
|
|
1712
|
+
var MAX_REQUEST_BODY = 64 * 1024 * 1024;
|
|
1713
|
+
function serveTransport(peer, transport) {
|
|
1714
|
+
const open = /* @__PURE__ */ new Map();
|
|
1715
|
+
const fail = (nid, e) => {
|
|
1716
|
+
open.delete(nid);
|
|
1717
|
+
const { status, code } = e;
|
|
1718
|
+
peer.postMessage({
|
|
1719
|
+
net: "error",
|
|
1720
|
+
nid,
|
|
1721
|
+
message: e instanceof Error ? e.message : String(e),
|
|
1722
|
+
...typeof status === "number" ? { status } : {},
|
|
1723
|
+
...typeof code === "string" ? { code } : {}
|
|
1724
|
+
});
|
|
1725
|
+
};
|
|
1726
|
+
const start = async (call) => {
|
|
1727
|
+
const abort = new AbortController();
|
|
1728
|
+
const entry = { abort };
|
|
1729
|
+
open.set(call.nid, entry);
|
|
1730
|
+
try {
|
|
1731
|
+
const response = await transport.fetch({
|
|
1732
|
+
url: call.url,
|
|
1733
|
+
method: call.method,
|
|
1734
|
+
headers: call.headers,
|
|
1735
|
+
...call.body ? { body: call.body } : {},
|
|
1736
|
+
signal: abort.signal
|
|
1737
|
+
});
|
|
1738
|
+
if (!open.has(call.nid)) {
|
|
1739
|
+
await response.cancel();
|
|
1740
|
+
return;
|
|
1585
1741
|
}
|
|
1742
|
+
open.set(call.nid, { abort, body: response.body[Symbol.asyncIterator](), response });
|
|
1743
|
+
peer.postMessage({
|
|
1744
|
+
net: "head",
|
|
1745
|
+
nid: call.nid,
|
|
1746
|
+
status: response.status,
|
|
1747
|
+
statusText: response.statusText,
|
|
1748
|
+
headers: response.headers
|
|
1749
|
+
});
|
|
1750
|
+
} catch (e) {
|
|
1751
|
+
fail(call.nid, e);
|
|
1586
1752
|
}
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
isFile: !directory,
|
|
1600
|
-
isDirectory: directory,
|
|
1601
|
-
isSymbolicLink: false,
|
|
1602
|
-
size: file?.size ?? 0,
|
|
1603
|
-
mode: this.mode(path, directory, meta),
|
|
1604
|
-
mtime: new Date(mtime),
|
|
1605
|
-
atime: new Date(meta?.atimeMs ?? mtime),
|
|
1606
|
-
ctime: new Date(Math.max(meta?.ctimeMs ?? mtime, modified)),
|
|
1607
|
-
ino: meta?.ino ?? inodeOf(path)
|
|
1608
|
-
};
|
|
1609
|
-
}
|
|
1610
|
-
linkStat(entry) {
|
|
1611
|
-
const time2 = new Date(entry.mtimeMs ?? 0);
|
|
1612
|
-
return {
|
|
1613
|
-
isFile: false,
|
|
1614
|
-
isDirectory: false,
|
|
1615
|
-
isSymbolicLink: true,
|
|
1616
|
-
size: new TextEncoder().encode(entry.link).length,
|
|
1617
|
-
mode: S_IFLNK | 511,
|
|
1618
|
-
mtime: time2,
|
|
1619
|
-
atime: time2,
|
|
1620
|
-
ctime: new Date(entry.ctimeMs ?? 0),
|
|
1621
|
-
ino: entry.ino ?? inodeOf(entry.path)
|
|
1622
|
-
};
|
|
1623
|
-
}
|
|
1624
|
-
async describe(found) {
|
|
1625
|
-
if (found.link) return this.linkStat(found.link);
|
|
1626
|
-
const [meta] = await this.meta.get([found.path]);
|
|
1627
|
-
return this.statOf(found.path, found.handle, meta);
|
|
1628
|
-
}
|
|
1629
|
-
async stat(path) {
|
|
1630
|
-
return this.describe(await this.locate(path, true));
|
|
1631
|
-
}
|
|
1632
|
-
async lstat(path) {
|
|
1633
|
-
return this.describe(await this.locate(path, false));
|
|
1634
|
-
}
|
|
1635
|
-
async exists(path) {
|
|
1636
|
-
return this.locate(path, true).then(
|
|
1637
|
-
() => true,
|
|
1638
|
-
() => false
|
|
1639
|
-
);
|
|
1640
|
-
}
|
|
1641
|
-
async readFileBuffer(path) {
|
|
1642
|
-
const handle = (await this.locate(path, true)).handle;
|
|
1643
|
-
if (handle.kind === "directory") throw fsError("EISDIR", path);
|
|
1644
|
-
for (let attempt = 1; ; attempt++) {
|
|
1645
|
-
try {
|
|
1646
|
-
return new Uint8Array(await (await handle.getFile()).arrayBuffer());
|
|
1647
|
-
} catch (err) {
|
|
1648
|
-
if (err?.name !== "NotReadableError" || attempt >= READ_ATTEMPTS) {
|
|
1649
|
-
throw translate(err, path);
|
|
1650
|
-
}
|
|
1651
|
-
await new Promise((resolve) => setTimeout(resolve, attempt * 10));
|
|
1753
|
+
};
|
|
1754
|
+
const read = async (nid) => {
|
|
1755
|
+
const body = open.get(nid)?.body;
|
|
1756
|
+
if (!body) return;
|
|
1757
|
+
try {
|
|
1758
|
+
const next = await body.next();
|
|
1759
|
+
if (next.done) {
|
|
1760
|
+
open.delete(nid);
|
|
1761
|
+
peer.postMessage({ net: "end", nid });
|
|
1762
|
+
} else {
|
|
1763
|
+
const bytes = next.value.slice();
|
|
1764
|
+
peer.postMessage({ net: "chunk", nid, bytes }, [bytes.buffer]);
|
|
1652
1765
|
}
|
|
1766
|
+
} catch (e) {
|
|
1767
|
+
fail(nid, e);
|
|
1653
1768
|
}
|
|
1654
|
-
}
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1769
|
+
};
|
|
1770
|
+
const drop = (nid) => {
|
|
1771
|
+
const entry = open.get(nid);
|
|
1772
|
+
open.delete(nid);
|
|
1773
|
+
entry?.abort.abort();
|
|
1774
|
+
void entry?.response?.cancel().catch(() => void 0);
|
|
1775
|
+
};
|
|
1776
|
+
return {
|
|
1777
|
+
answer(call) {
|
|
1778
|
+
if (call.net === "fetch") void start(call);
|
|
1779
|
+
else if (call.net === "read") void read(call.nid);
|
|
1780
|
+
else drop(call.nid);
|
|
1781
|
+
},
|
|
1782
|
+
close() {
|
|
1783
|
+
for (const nid of [...open.keys()]) drop(nid);
|
|
1663
1784
|
}
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1785
|
+
};
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
// src/client/serve-client.ts
|
|
1789
|
+
var MAX_LINKS2 = 40;
|
|
1790
|
+
function act(session, req, signal) {
|
|
1791
|
+
if (req.action === "write") session.write(req.bytes);
|
|
1792
|
+
else if (req.action === "resize") session.resize(req.cols, req.rows);
|
|
1793
|
+
else if (req.action === "signal") session.signal(signal(req.signal));
|
|
1794
|
+
else session.close();
|
|
1795
|
+
}
|
|
1796
|
+
async function realpath(fs, path) {
|
|
1797
|
+
let parts = fs.resolvePath("/", path).split("/").filter(Boolean);
|
|
1798
|
+
let resolved = "";
|
|
1799
|
+
for (let hops = 0; parts.length > 0; ) {
|
|
1800
|
+
const [name2, ...rest] = parts;
|
|
1801
|
+
const next = `${resolved}/${name2}`;
|
|
1802
|
+
if (!(await fs.lstat(next)).isSymbolicLink) {
|
|
1803
|
+
resolved = next;
|
|
1804
|
+
parts = rest;
|
|
1805
|
+
continue;
|
|
1806
|
+
}
|
|
1807
|
+
if (++hops > MAX_LINKS2) throw fsError("ELOOP", path);
|
|
1808
|
+
const target = await fs.readlink(next);
|
|
1809
|
+
const base = target.startsWith("/") ? "/" : resolved || "/";
|
|
1810
|
+
parts = [...fs.resolvePath(base, target).split("/").filter(Boolean), ...rest];
|
|
1811
|
+
resolved = "";
|
|
1667
1812
|
}
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
throw code ? fsError(code, path) : translate(err, path);
|
|
1813
|
+
return resolved || "/";
|
|
1814
|
+
}
|
|
1815
|
+
async function fsCall(fs, method, args) {
|
|
1816
|
+
const path = (i) => fs.resolvePath("/", String(args[i]));
|
|
1817
|
+
switch (method) {
|
|
1818
|
+
case "readFile": {
|
|
1819
|
+
const bytes = (await fs.readFileBuffer(path(0))).slice();
|
|
1820
|
+
return { result: bytes, transfer: [bytes.buffer] };
|
|
1677
1821
|
}
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1822
|
+
case "writeFile":
|
|
1823
|
+
await fs.writeFile(path(0), args[1]);
|
|
1824
|
+
return { result: true };
|
|
1825
|
+
case "stat":
|
|
1826
|
+
return { result: await fs.stat(path(0)) };
|
|
1827
|
+
case "lstat":
|
|
1828
|
+
return { result: await fs.lstat(path(0)) };
|
|
1829
|
+
case "readdir":
|
|
1830
|
+
return { result: await fs.readdir(path(0)) };
|
|
1831
|
+
case "mkdir":
|
|
1832
|
+
await fs.mkdir(path(0), { recursive: true });
|
|
1833
|
+
return { result: true };
|
|
1834
|
+
case "rm":
|
|
1835
|
+
await fs.rm(path(0), { recursive: true, force: args[1] === true });
|
|
1836
|
+
return { result: true };
|
|
1837
|
+
case "rename":
|
|
1838
|
+
await fs.rename(path(0), path(1));
|
|
1839
|
+
return { result: true };
|
|
1840
|
+
case "realpath":
|
|
1841
|
+
return { result: await realpath(fs, String(args[0])) };
|
|
1842
|
+
case "symlink":
|
|
1843
|
+
await fs.symlink(String(args[0]), path(1));
|
|
1844
|
+
return { result: true };
|
|
1845
|
+
case "readlink":
|
|
1846
|
+
return { result: await fs.readlink(path(0)) };
|
|
1847
|
+
default:
|
|
1848
|
+
return { result: await fs.exists(path(0)) };
|
|
1682
1849
|
}
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1850
|
+
}
|
|
1851
|
+
function signalOf(host) {
|
|
1852
|
+
return host.signal ?? ((name2) => {
|
|
1853
|
+
throw fsError("EINVAL", `this kernel names no signal ${name2}`);
|
|
1854
|
+
});
|
|
1855
|
+
}
|
|
1856
|
+
function dialFor(l, req, open) {
|
|
1857
|
+
const socket = dialSocket(l.net, req.port, req.host);
|
|
1858
|
+
const { port1, port2 } = new MessageChannel();
|
|
1859
|
+
open.set(
|
|
1860
|
+
req.id,
|
|
1861
|
+
serveSocket(socket, port1, () => open.delete(req.id))
|
|
1862
|
+
);
|
|
1863
|
+
return { result: port2, transfer: [port2] };
|
|
1864
|
+
}
|
|
1865
|
+
function killGroups(host, pgids) {
|
|
1866
|
+
if (pgids.length === 0) return;
|
|
1867
|
+
void host.launcher().then((launcher) => {
|
|
1868
|
+
for (const pgid of pgids) launcher.kill(-pgid, SIG.KILL);
|
|
1869
|
+
});
|
|
1870
|
+
}
|
|
1871
|
+
function joinable(l, pgid, ours) {
|
|
1872
|
+
const sid = l.groupSession(pgid);
|
|
1873
|
+
if (sid === void 0) throw fsError("ESRCH", `no process group ${pgid}`);
|
|
1874
|
+
if (!ours(sid)) throw fsError("EPERM", `process group ${pgid} is in another session`);
|
|
1875
|
+
}
|
|
1876
|
+
function cdpRegistry(send, gone) {
|
|
1877
|
+
const registered = /* @__PURE__ */ new Map();
|
|
1878
|
+
const drop = (id) => {
|
|
1879
|
+
registered.get(id)?.();
|
|
1880
|
+
return registered.delete(id);
|
|
1881
|
+
};
|
|
1882
|
+
return {
|
|
1883
|
+
serve(l, req) {
|
|
1884
|
+
const hook = portCdpHook(
|
|
1885
|
+
(request2, cdp) => send({ cdpOpen: { ...request2, registration: req.id }, port: cdp }, [
|
|
1886
|
+
cdp
|
|
1887
|
+
]),
|
|
1888
|
+
gone
|
|
1889
|
+
);
|
|
1890
|
+
registered.set(req.id, l.cdp.register(hook, req.runtime));
|
|
1891
|
+
return req.id;
|
|
1892
|
+
},
|
|
1893
|
+
drop,
|
|
1894
|
+
close: () => {
|
|
1895
|
+
for (const id of [...registered.keys()]) drop(id);
|
|
1692
1896
|
}
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
const base = dir === "/" ? "" : dir;
|
|
1701
|
-
const metas = await this.meta.get(children2.map(([name2]) => `${base}/${name2}`));
|
|
1702
|
-
return Promise.all(
|
|
1703
|
-
children2.map(async ([name2, child], i) => {
|
|
1704
|
-
if (!isHandle(child)) return [name2, this.linkStat(child)];
|
|
1705
|
-
const stat = await this.statOf(`${base}/${name2}`, child, metas[i]).catch(() => null);
|
|
1706
|
-
return [name2, stat];
|
|
1707
|
-
})
|
|
1708
|
-
);
|
|
1897
|
+
};
|
|
1898
|
+
}
|
|
1899
|
+
function watchOp(l, req, watches, send) {
|
|
1900
|
+
if (req.op === "unwatch") {
|
|
1901
|
+
watches.get(req.watch)?.();
|
|
1902
|
+
watches.delete(req.watch);
|
|
1903
|
+
return { result: true };
|
|
1709
1904
|
}
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1905
|
+
const unwatch = l.watchers.watch(
|
|
1906
|
+
req.paths,
|
|
1907
|
+
{ recursive: req.recursive },
|
|
1908
|
+
(change) => send({ watch: req.id, change })
|
|
1909
|
+
);
|
|
1910
|
+
watches.set(req.id, unwatch);
|
|
1911
|
+
return { result: req.id };
|
|
1912
|
+
}
|
|
1913
|
+
function serveClient(port, host) {
|
|
1914
|
+
let state = "new";
|
|
1915
|
+
const terminals = /* @__PURE__ */ new Map();
|
|
1916
|
+
const groups = /* @__PURE__ */ new Set();
|
|
1917
|
+
const sessions = /* @__PURE__ */ new Set();
|
|
1918
|
+
const watches = /* @__PURE__ */ new Map();
|
|
1919
|
+
let net;
|
|
1920
|
+
const ended = Promise.withResolvers();
|
|
1921
|
+
const send = (message2, transfer = []) => {
|
|
1922
|
+
if (state !== "closed") port.postMessage(message2, transfer);
|
|
1923
|
+
};
|
|
1924
|
+
const reply = (id, body, transfer) => send({ id, ...body }, transfer);
|
|
1925
|
+
const cdps = cdpRegistry(send, ended.promise);
|
|
1926
|
+
function detach(kill = false, bye = "the slicc-kernel detached this client") {
|
|
1927
|
+
if (state === "closed") return;
|
|
1928
|
+
send({ bye });
|
|
1929
|
+
state = "closed";
|
|
1930
|
+
net?.close();
|
|
1931
|
+
for (const unwatch of watches.values()) unwatch();
|
|
1932
|
+
watches.clear();
|
|
1933
|
+
cdps.close();
|
|
1934
|
+
for (const session of terminals.values()) session.close();
|
|
1935
|
+
terminals.clear();
|
|
1936
|
+
if (kill) killGroups(host, [...groups]);
|
|
1937
|
+
groups.clear();
|
|
1938
|
+
port.close?.();
|
|
1939
|
+
ended.resolve();
|
|
1728
1940
|
}
|
|
1729
|
-
async
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
if (options.force && error.code === "ENOENT") return;
|
|
1736
|
-
throw error;
|
|
1941
|
+
async function hello(message2) {
|
|
1942
|
+
const refused = versionError(message2.protocol);
|
|
1943
|
+
if (refused) {
|
|
1944
|
+
send({ hello: { protocol: PROTOCOL, error: refused } });
|
|
1945
|
+
detach();
|
|
1946
|
+
return;
|
|
1737
1947
|
}
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1948
|
+
const launcher = await host.launcher();
|
|
1949
|
+
const transport = host.transport?.() ?? launcher.transport;
|
|
1950
|
+
net = serveTransport({ postMessage: (m, t) => send(m, t) }, transport);
|
|
1951
|
+
state = "open";
|
|
1952
|
+
if (message2.lock && host.locks) void host.locks.request(message2.lock, () => detach());
|
|
1953
|
+
send({
|
|
1954
|
+
hello: {
|
|
1955
|
+
protocol: PROTOCOL,
|
|
1956
|
+
traits: transport.traits,
|
|
1957
|
+
...host.lock ? { lock: host.lock } : {}
|
|
1747
1958
|
}
|
|
1748
|
-
this.changed(found.path);
|
|
1749
|
-
}
|
|
1750
|
-
await this.meta.remove([found.path]);
|
|
1751
|
-
}
|
|
1752
|
-
async rename(from, to) {
|
|
1753
|
-
if (from === to) return;
|
|
1754
|
-
const source = await this.locate(from, false);
|
|
1755
|
-
this.forget(source.path);
|
|
1756
|
-
const parent = await this.locate(parentOf2(to), true);
|
|
1757
|
-
const destination = resolvePath(parent.path, baseOf(to));
|
|
1758
|
-
const sourceStat = await this.describe(source);
|
|
1759
|
-
if (sourceStat.isDirectory && within(destination, source.path)) throw fsError("EINVAL", to);
|
|
1760
|
-
const target = await this.locate(destination, false).catch((err) => {
|
|
1761
|
-
if (missing(err)) return null;
|
|
1762
|
-
throw err;
|
|
1763
1959
|
});
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
if (targetStat?.isDirectory && (await this.readdir(destination)).length > 0) {
|
|
1770
|
-
throw fsError("ENOTEMPTY", to);
|
|
1771
|
-
}
|
|
1772
|
-
const now = Date.now();
|
|
1773
|
-
const carry = (entry) => ({
|
|
1774
|
-
ino: inodeOf(source.path),
|
|
1775
|
-
...entry,
|
|
1776
|
-
path: destination,
|
|
1777
|
-
ctimeMs: now
|
|
1960
|
+
}
|
|
1961
|
+
async function terminal(req, l) {
|
|
1962
|
+
const session = await l.openTerminal(req.argv, {
|
|
1963
|
+
...req.options,
|
|
1964
|
+
onData: (bytes) => reply(req.id, { fd: 1, bytes: bytes.slice() })
|
|
1778
1965
|
});
|
|
1779
|
-
if (
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1966
|
+
if (state === "closed") {
|
|
1967
|
+
session.close();
|
|
1968
|
+
} else {
|
|
1969
|
+
terminals.set(req.id, session);
|
|
1970
|
+
sessions.add(session.pid);
|
|
1971
|
+
reply(req.id, { started: session.pid });
|
|
1783
1972
|
}
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
this.forget(destination);
|
|
1789
|
-
const timed = await this.explicitTimes(source.path);
|
|
1790
|
-
const moved2 = await this.place(source.handle, dir, staging);
|
|
1791
|
-
if (swap) await this.rm(destination, { recursive: true });
|
|
1792
|
-
await this.meta.move(source.path, destination, carry);
|
|
1793
|
-
if (!moved2) await this.rm(source.path, { recursive: true });
|
|
1794
|
-
let copied = !moved2;
|
|
1795
|
-
if (swap) {
|
|
1796
|
-
const staged = await dir.getDirectoryHandle(staging);
|
|
1797
|
-
const placed = await this.place(staged, dir, name2);
|
|
1798
|
-
if (!placed) await dir.removeEntry(staging, { recursive: true });
|
|
1799
|
-
copied ||= !placed;
|
|
1973
|
+
try {
|
|
1974
|
+
return { result: await session.exited };
|
|
1975
|
+
} finally {
|
|
1976
|
+
terminals.delete(req.id);
|
|
1800
1977
|
}
|
|
1801
|
-
if (copied) await this.restamp(destination, timed);
|
|
1802
|
-
this.changed(source.path, destination);
|
|
1803
|
-
}
|
|
1804
|
-
async modified(path) {
|
|
1805
|
-
const handle = await this.handle(path);
|
|
1806
|
-
return handle.kind === "file" ? (await handle.getFile()).lastModified : 0;
|
|
1807
1978
|
}
|
|
1808
|
-
async
|
|
1809
|
-
const
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1979
|
+
async function spawn(req, l) {
|
|
1980
|
+
const out = (fd) => (bytes) => reply(req.id, { fd, bytes: bytes.slice() });
|
|
1981
|
+
if (req.options.pgid !== void 0) joinable(l, req.options.pgid, (sid) => sessions.has(sid));
|
|
1982
|
+
let leader;
|
|
1983
|
+
const result = await l.run(req.argv, {
|
|
1984
|
+
...req.options,
|
|
1985
|
+
collect: false,
|
|
1986
|
+
onStdout: out(1),
|
|
1987
|
+
onStderr: out(2),
|
|
1988
|
+
onStarted: (pid) => {
|
|
1989
|
+
leader = pid;
|
|
1990
|
+
if (state === "closed") return;
|
|
1991
|
+
groups.add(req.options.pgid ?? pid);
|
|
1992
|
+
if (req.options.pgid === void 0) sessions.add(pid);
|
|
1993
|
+
reply(req.id, { started: pid });
|
|
1994
|
+
}
|
|
1995
|
+
});
|
|
1996
|
+
if (leader === void 0) throw fsError("ENOENT", `${req.argv[0] ?? ""}: command not found`);
|
|
1997
|
+
if (!l.list().some((p) => p.pgid === leader)) groups.delete(leader);
|
|
1998
|
+
return { result: result.status };
|
|
1816
1999
|
}
|
|
1817
|
-
async
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
await this.meta.update(path + rest, (entry) => entry && { ...entry, mtimeFor });
|
|
2000
|
+
async function handle(req) {
|
|
2001
|
+
if (host.scope === "transport" && req.op !== "detach") {
|
|
2002
|
+
throw fsError("EPERM", `${req.op}: this client may only use the network transport`);
|
|
1821
2003
|
}
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
2004
|
+
const l = await host.launcher();
|
|
2005
|
+
switch (req.op) {
|
|
2006
|
+
case "spawn":
|
|
2007
|
+
return spawn(req, l);
|
|
2008
|
+
case "open-terminal":
|
|
2009
|
+
return terminal(req, l);
|
|
2010
|
+
case "terminal": {
|
|
2011
|
+
const session = terminals.get(req.terminal);
|
|
2012
|
+
if (!session) throw new Error(`no terminal ${req.terminal}`);
|
|
2013
|
+
act(session, req, signalOf(host));
|
|
2014
|
+
return { result: true };
|
|
2015
|
+
}
|
|
2016
|
+
case "kill":
|
|
2017
|
+
if (!l.kill(req.pid, signalOf(host)(req.signal))) throw fsError("ESRCH", `pid ${req.pid}`);
|
|
2018
|
+
return { result: true };
|
|
2019
|
+
case "ps":
|
|
2020
|
+
return {
|
|
2021
|
+
result: l.list().map(({ tid: _, ...entry }) => entry)
|
|
2022
|
+
};
|
|
2023
|
+
case "fs":
|
|
2024
|
+
if (!FS_METHODS.includes(req.method)) {
|
|
2025
|
+
throw new Error(`unknown file system call ${req.method}`);
|
|
2026
|
+
}
|
|
2027
|
+
return fsCall(l.fs, req.method, req.args);
|
|
2028
|
+
case "watch":
|
|
2029
|
+
case "unwatch":
|
|
2030
|
+
return watchOp(l, req, watches, send);
|
|
2031
|
+
case "mount":
|
|
2032
|
+
return { result: await l.mount(req.spec) };
|
|
2033
|
+
case "umount":
|
|
2034
|
+
l.umount(req.target);
|
|
2035
|
+
return { result: true };
|
|
2036
|
+
case "mounts":
|
|
2037
|
+
return { result: l.mounts.list() };
|
|
2038
|
+
case "dial":
|
|
2039
|
+
return dialFor(l, req, watches);
|
|
2040
|
+
case "serve-cdp":
|
|
2041
|
+
return { result: cdps.serve(l, req) };
|
|
2042
|
+
case "unserve-cdp":
|
|
2043
|
+
return { result: cdps.drop(req.registration) };
|
|
2044
|
+
case "detach":
|
|
2045
|
+
return { result: true };
|
|
2046
|
+
default:
|
|
2047
|
+
throw new Error(`unknown op ${req.op}`);
|
|
1834
2048
|
}
|
|
1835
|
-
return false;
|
|
1836
2049
|
}
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
2050
|
+
port.addEventListener("message", (event) => {
|
|
2051
|
+
const data = event.data;
|
|
2052
|
+
if (state === "closed") return;
|
|
2053
|
+
if (data.hello) {
|
|
2054
|
+
if (state === "new") {
|
|
2055
|
+
hello(data.hello).catch((err) => {
|
|
2056
|
+
send({ hello: { protocol: PROTOCOL, error: String(err?.message ?? err) } });
|
|
2057
|
+
detach();
|
|
2058
|
+
});
|
|
2059
|
+
}
|
|
1842
2060
|
return;
|
|
1843
2061
|
}
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
const parent = await this.locate(parentOf2(path), true);
|
|
1849
|
-
if (parent.handle?.kind !== "directory") throw fsError("ENOTDIR", path);
|
|
1850
|
-
const at = resolvePath(parent.path, baseOf(path));
|
|
1851
|
-
if (await this.locate(at, false).then(
|
|
1852
|
-
() => true,
|
|
1853
|
-
(err) => !missing(err)
|
|
1854
|
-
)) {
|
|
1855
|
-
throw fsError("EEXIST", path);
|
|
1856
|
-
}
|
|
1857
|
-
const now = Date.now();
|
|
1858
|
-
await this.meta.update(at, () => ({
|
|
1859
|
-
path: at,
|
|
1860
|
-
link: target,
|
|
1861
|
-
dir: parent.path,
|
|
1862
|
-
ino: inodeOf(at),
|
|
1863
|
-
mtimeMs: now,
|
|
1864
|
-
ctimeMs: now
|
|
1865
|
-
}));
|
|
1866
|
-
}
|
|
1867
|
-
async readlink(path) {
|
|
1868
|
-
const found = await this.locate(path, false);
|
|
1869
|
-
if (found.link) return found.link.link;
|
|
1870
|
-
throw fsError("EINVAL", path);
|
|
1871
|
-
}
|
|
1872
|
-
async chmod(path, mode) {
|
|
1873
|
-
const found = await this.locate(path, true);
|
|
1874
|
-
const ctimeMs = Date.now();
|
|
1875
|
-
await this.meta.update(found.path, (entry) => ({
|
|
1876
|
-
...entry,
|
|
1877
|
-
path: found.path,
|
|
1878
|
-
mode: mode & PERM_MASK,
|
|
1879
|
-
ctimeMs
|
|
1880
|
-
}));
|
|
1881
|
-
}
|
|
1882
|
-
async utimes(path, atime, mtime) {
|
|
1883
|
-
const found = await this.locate(path, true);
|
|
1884
|
-
const handle = found.handle;
|
|
1885
|
-
const file = handle.kind === "file" ? await handle.getFile() : void 0;
|
|
1886
|
-
const ctimeMs = Date.now();
|
|
1887
|
-
await this.meta.update(found.path, (entry) => ({
|
|
1888
|
-
...entry,
|
|
1889
|
-
path: found.path,
|
|
1890
|
-
atimeMs: atime.getTime(),
|
|
1891
|
-
mtimeMs: mtime.getTime(),
|
|
1892
|
-
mtimeFor: file?.lastModified ?? 0,
|
|
1893
|
-
ctimeMs
|
|
1894
|
-
}));
|
|
1895
|
-
}
|
|
1896
|
-
async reconcile() {
|
|
1897
|
-
let removed = 0;
|
|
1898
|
-
for (const entry of await this.meta.all()) {
|
|
1899
|
-
const real = await this.real(entry.path);
|
|
1900
|
-
const stale = entry.link === void 0 ? !real : real || !await this.real(parentOf2(entry.path));
|
|
1901
|
-
if (!stale) continue;
|
|
1902
|
-
await this.meta.update(entry.path, () => void 0);
|
|
1903
|
-
removed++;
|
|
2062
|
+
if (state !== "open") return;
|
|
2063
|
+
if (data.net !== void 0) {
|
|
2064
|
+
net?.answer(data);
|
|
2065
|
+
return;
|
|
1904
2066
|
}
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
}
|
|
2067
|
+
const req = data;
|
|
2068
|
+
handle(req).then(
|
|
2069
|
+
({ result, transfer }) => {
|
|
2070
|
+
reply(req.id, { result }, transfer);
|
|
2071
|
+
if (req.op === "detach") detach(req.kill === true);
|
|
2072
|
+
},
|
|
2073
|
+
(err) => {
|
|
2074
|
+
const code = err?.code;
|
|
2075
|
+
reply(req.id, {
|
|
2076
|
+
error: err instanceof Error ? err.message : String(err),
|
|
2077
|
+
...typeof code === "string" ? { code } : {}
|
|
2078
|
+
});
|
|
2079
|
+
}
|
|
2080
|
+
);
|
|
2081
|
+
});
|
|
2082
|
+
port.addEventListener("close", () => detach());
|
|
2083
|
+
port.start?.();
|
|
2084
|
+
return { detach, closed: ended.promise };
|
|
2085
|
+
}
|
|
1908
2086
|
|
|
1909
2087
|
// src/kernel/net/loopback-names.ts
|
|
1910
2088
|
var HOST_LOOPBACK = "host.slicc.internal";
|
|
@@ -1934,6 +2112,8 @@ var REASON = {
|
|
|
1934
2112
|
200: "OK",
|
|
1935
2113
|
400: "Bad Request",
|
|
1936
2114
|
403: "Forbidden",
|
|
2115
|
+
404: "Not Found",
|
|
2116
|
+
405: "Method Not Allowed",
|
|
1937
2117
|
408: "Request Timeout",
|
|
1938
2118
|
411: "Length Required",
|
|
1939
2119
|
413: "Content Too Large",
|
|
@@ -1943,6 +2123,7 @@ var REASON = {
|
|
|
1943
2123
|
501: "Not Implemented",
|
|
1944
2124
|
502: "Bad Gateway",
|
|
1945
2125
|
503: "Service Unavailable",
|
|
2126
|
+
504: "Gateway Timeout",
|
|
1946
2127
|
505: "HTTP Version Not Supported"
|
|
1947
2128
|
};
|
|
1948
2129
|
var Incoming = class {
|
|
@@ -3388,6 +3569,465 @@ var RemoteTransport = class {
|
|
|
3388
3569
|
}
|
|
3389
3570
|
};
|
|
3390
3571
|
|
|
3572
|
+
// src/cdp/types.ts
|
|
3573
|
+
var CDP_PORT = 9222;
|
|
3574
|
+
var NO_CDP_HOST = "slicc-kernel: no CDP host is attached (createKernel({ cdp }) or client.serveCdp)";
|
|
3575
|
+
|
|
3576
|
+
// src/cdp/websocket.ts
|
|
3577
|
+
var GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
|
3578
|
+
var MAX_MESSAGE = 256 * 1024 * 1024;
|
|
3579
|
+
var CLOSE = {
|
|
3580
|
+
normal: 1e3,
|
|
3581
|
+
protocol: 1002,
|
|
3582
|
+
unsupported: 1003,
|
|
3583
|
+
invalid: 1007,
|
|
3584
|
+
tooBig: 1009,
|
|
3585
|
+
error: 1011
|
|
3586
|
+
};
|
|
3587
|
+
var OP_TEXT = 1;
|
|
3588
|
+
var OP = { continuation: 0, text: OP_TEXT, binary: 2, close: 8, ping: 9, pong: 10 };
|
|
3589
|
+
var WsError = class extends Error {
|
|
3590
|
+
code;
|
|
3591
|
+
constructor(code, message2) {
|
|
3592
|
+
super(message2);
|
|
3593
|
+
this.code = code;
|
|
3594
|
+
}
|
|
3595
|
+
};
|
|
3596
|
+
function field(headers, name2) {
|
|
3597
|
+
return headers.find(([n]) => n.toLowerCase() === name2)?.[1];
|
|
3598
|
+
}
|
|
3599
|
+
async function acceptKey(headers) {
|
|
3600
|
+
const tokens = (name2) => (field(headers, name2) ?? "").split(",").map((t) => t.trim().toLowerCase());
|
|
3601
|
+
if (!tokens("upgrade").includes("websocket") || !tokens("connection").includes("upgrade")) {
|
|
3602
|
+
throw new HttpError(400, "expected a WebSocket upgrade");
|
|
3603
|
+
}
|
|
3604
|
+
if (field(headers, "sec-websocket-version") !== "13") {
|
|
3605
|
+
throw new HttpError(400, "Sec-WebSocket-Version must be 13");
|
|
3606
|
+
}
|
|
3607
|
+
const key = field(headers, "sec-websocket-key") ?? "";
|
|
3608
|
+
if (!/^[A-Za-z0-9+/]{22}==$/.test(key)) throw new HttpError(400, "bad Sec-WebSocket-Key");
|
|
3609
|
+
const digest = await crypto.subtle.digest("SHA-1", new TextEncoder().encode(key + GUID));
|
|
3610
|
+
return btoa(String.fromCharCode(...new Uint8Array(digest)));
|
|
3611
|
+
}
|
|
3612
|
+
function isUpgrade(headers) {
|
|
3613
|
+
return (field(headers, "upgrade") ?? "").toLowerCase().includes("websocket");
|
|
3614
|
+
}
|
|
3615
|
+
function frame(opcode, payload) {
|
|
3616
|
+
const n = payload.length;
|
|
3617
|
+
const size = n < 126 ? 2 : n < 65536 ? 4 : 10;
|
|
3618
|
+
const out = new Uint8Array(size + n);
|
|
3619
|
+
const view = new DataView(out.buffer);
|
|
3620
|
+
out[0] = 128 | opcode;
|
|
3621
|
+
if (n < 126) out[1] = n;
|
|
3622
|
+
else if (n < 65536) {
|
|
3623
|
+
out[1] = 126;
|
|
3624
|
+
view.setUint16(2, n);
|
|
3625
|
+
} else {
|
|
3626
|
+
out[1] = 127;
|
|
3627
|
+
view.setBigUint64(2, BigInt(n));
|
|
3628
|
+
}
|
|
3629
|
+
out.set(payload, size);
|
|
3630
|
+
return out;
|
|
3631
|
+
}
|
|
3632
|
+
function closeFrame(code, reason = "") {
|
|
3633
|
+
let bytes = new TextEncoder().encode(reason);
|
|
3634
|
+
if (bytes.length > 123) bytes = bytes.subarray(0, 123);
|
|
3635
|
+
const payload = new Uint8Array(2 + bytes.length);
|
|
3636
|
+
new DataView(payload.buffer).setUint16(0, code);
|
|
3637
|
+
payload.set(bytes, 2);
|
|
3638
|
+
return frame(OP.close, payload);
|
|
3639
|
+
}
|
|
3640
|
+
function pongFrame(payload) {
|
|
3641
|
+
return frame(OP.pong, payload);
|
|
3642
|
+
}
|
|
3643
|
+
async function fill(incoming, out, signal) {
|
|
3644
|
+
for (let at = 0; at < out.length; ) {
|
|
3645
|
+
const piece = await incoming.some(out.length - at, signal);
|
|
3646
|
+
if (piece.length === 0) throw new WsError(CLOSE.protocol, "the connection closed mid-frame");
|
|
3647
|
+
out.set(piece, at);
|
|
3648
|
+
at += piece.length;
|
|
3649
|
+
}
|
|
3650
|
+
}
|
|
3651
|
+
var fatal = new TextDecoder("utf-8", { fatal: true });
|
|
3652
|
+
function decode(bytes) {
|
|
3653
|
+
try {
|
|
3654
|
+
return fatal.decode(bytes);
|
|
3655
|
+
} catch {
|
|
3656
|
+
throw new WsError(CLOSE.invalid, "a text message is not UTF-8");
|
|
3657
|
+
}
|
|
3658
|
+
}
|
|
3659
|
+
var WsReader = class {
|
|
3660
|
+
incoming;
|
|
3661
|
+
max;
|
|
3662
|
+
parts = [];
|
|
3663
|
+
size = 0;
|
|
3664
|
+
continuing = false;
|
|
3665
|
+
constructor(incoming, max = MAX_MESSAGE) {
|
|
3666
|
+
this.incoming = incoming;
|
|
3667
|
+
this.max = max;
|
|
3668
|
+
}
|
|
3669
|
+
async next(signal) {
|
|
3670
|
+
for (; ; ) {
|
|
3671
|
+
const first = await this.incoming.some(1, signal);
|
|
3672
|
+
if (first.length === 0) return null;
|
|
3673
|
+
const head = new Uint8Array(2);
|
|
3674
|
+
head[0] = first[0];
|
|
3675
|
+
await fill(this.incoming, head.subarray(1), signal);
|
|
3676
|
+
const fin = (head[0] & 128) !== 0;
|
|
3677
|
+
const opcode = head[0] & 15;
|
|
3678
|
+
if ((head[0] & 112) !== 0) throw new WsError(CLOSE.protocol, "reserved bits are set");
|
|
3679
|
+
if ((head[1] & 128) === 0) throw new WsError(CLOSE.protocol, "client frames must be masked");
|
|
3680
|
+
const length2 = await this.length(head[1] & 127, signal);
|
|
3681
|
+
const control = opcode >= 8;
|
|
3682
|
+
if (control && (length2 > 125 || !fin)) {
|
|
3683
|
+
throw new WsError(CLOSE.protocol, "a control frame is too long or fragmented");
|
|
3684
|
+
}
|
|
3685
|
+
if (!control && this.size + length2 > this.max) {
|
|
3686
|
+
throw new WsError(CLOSE.tooBig, `a message is over ${this.max} bytes`);
|
|
3687
|
+
}
|
|
3688
|
+
const mask = new Uint8Array(4);
|
|
3689
|
+
await fill(this.incoming, mask, signal);
|
|
3690
|
+
const payload = new Uint8Array(length2);
|
|
3691
|
+
await fill(this.incoming, payload, signal);
|
|
3692
|
+
for (let i = 0; i < length2; i++) payload[i] ^= mask[i & 3];
|
|
3693
|
+
const event = this.take(opcode, fin, payload);
|
|
3694
|
+
if (event) return event;
|
|
3695
|
+
}
|
|
3696
|
+
}
|
|
3697
|
+
async length(short, signal) {
|
|
3698
|
+
if (short < 126) return short;
|
|
3699
|
+
const ext = new Uint8Array(short === 126 ? 2 : 8);
|
|
3700
|
+
await fill(this.incoming, ext, signal);
|
|
3701
|
+
const view = new DataView(ext.buffer);
|
|
3702
|
+
if (short === 126) return view.getUint16(0);
|
|
3703
|
+
const long = view.getBigUint64(0);
|
|
3704
|
+
return long > BigInt(this.max) ? this.max + 1 : Number(long);
|
|
3705
|
+
}
|
|
3706
|
+
take(opcode, fin, payload) {
|
|
3707
|
+
switch (opcode) {
|
|
3708
|
+
case OP.ping:
|
|
3709
|
+
return { kind: "ping", payload };
|
|
3710
|
+
case OP.pong:
|
|
3711
|
+
return void 0;
|
|
3712
|
+
case OP.close: {
|
|
3713
|
+
if (payload.length === 1) throw new WsError(CLOSE.protocol, "a close frame is malformed");
|
|
3714
|
+
const code = payload.length >= 2 ? new DataView(payload.buffer).getUint16(0) : 1005;
|
|
3715
|
+
return { kind: "close", code, reason: decode(payload.subarray(2)) };
|
|
3716
|
+
}
|
|
3717
|
+
case OP.text:
|
|
3718
|
+
case OP.continuation: {
|
|
3719
|
+
if (opcode === OP.continuation !== this.continuing) {
|
|
3720
|
+
throw new WsError(CLOSE.protocol, "unexpected continuation frame");
|
|
3721
|
+
}
|
|
3722
|
+
this.parts.push(payload);
|
|
3723
|
+
this.size += payload.length;
|
|
3724
|
+
this.continuing = !fin;
|
|
3725
|
+
if (!fin) return void 0;
|
|
3726
|
+
const whole = this.parts.length === 1 ? payload : concat(this.parts, this.size);
|
|
3727
|
+
this.parts = [];
|
|
3728
|
+
this.size = 0;
|
|
3729
|
+
return { kind: "text", text: decode(whole) };
|
|
3730
|
+
}
|
|
3731
|
+
case OP.binary:
|
|
3732
|
+
throw new WsError(CLOSE.unsupported, "CDP messages are text");
|
|
3733
|
+
default:
|
|
3734
|
+
throw new WsError(CLOSE.protocol, `unknown opcode ${opcode}`);
|
|
3735
|
+
}
|
|
3736
|
+
}
|
|
3737
|
+
};
|
|
3738
|
+
function switchingHead(accept2) {
|
|
3739
|
+
return latin1Bytes(
|
|
3740
|
+
`HTTP/1.1 101 Switching Protocols\r
|
|
3741
|
+
Upgrade: websocket\r
|
|
3742
|
+
Connection: Upgrade\r
|
|
3743
|
+
Sec-WebSocket-Accept: ${accept2}\r
|
|
3744
|
+
\r
|
|
3745
|
+
`
|
|
3746
|
+
);
|
|
3747
|
+
}
|
|
3748
|
+
|
|
3749
|
+
// src/cdp/facade.ts
|
|
3750
|
+
var MAX_HEAD = 64 * 1024;
|
|
3751
|
+
var LIST_TIMEOUT = 1e4;
|
|
3752
|
+
function respond(status, body, type = "text/plain; charset=utf-8") {
|
|
3753
|
+
const bytes = new TextEncoder().encode(body);
|
|
3754
|
+
const head = latin1Bytes(
|
|
3755
|
+
`HTTP/1.1 ${status} ${REASON[status]}\r
|
|
3756
|
+
Content-Type: ${type}\r
|
|
3757
|
+
Content-Length: ${bytes.length}\r
|
|
3758
|
+
Connection: close\r
|
|
3759
|
+
\r
|
|
3760
|
+
`
|
|
3761
|
+
);
|
|
3762
|
+
const out = new Uint8Array(head.length + bytes.length);
|
|
3763
|
+
out.set(head);
|
|
3764
|
+
out.set(bytes, head.length);
|
|
3765
|
+
return out;
|
|
3766
|
+
}
|
|
3767
|
+
function json(value) {
|
|
3768
|
+
return respond(200, `${JSON.stringify(value, null, 2)}
|
|
3769
|
+
`, "application/json; charset=UTF-8");
|
|
3770
|
+
}
|
|
3771
|
+
var CdpFacade = class {
|
|
3772
|
+
options;
|
|
3773
|
+
listener;
|
|
3774
|
+
stop = new AbortController();
|
|
3775
|
+
connections = /* @__PURE__ */ new Set();
|
|
3776
|
+
closed;
|
|
3777
|
+
constructor(options) {
|
|
3778
|
+
this.options = options;
|
|
3779
|
+
this.listener = options.net.listen({
|
|
3780
|
+
family: "inet",
|
|
3781
|
+
host: "127.0.0.1",
|
|
3782
|
+
port: options.port ?? CDP_PORT
|
|
3783
|
+
});
|
|
3784
|
+
this.closed = this.acceptLoop();
|
|
3785
|
+
}
|
|
3786
|
+
close() {
|
|
3787
|
+
if (this.stop.signal.aborted) return;
|
|
3788
|
+
this.stop.abort();
|
|
3789
|
+
this.listener.close();
|
|
3790
|
+
for (const conn of this.connections) conn.close();
|
|
3791
|
+
}
|
|
3792
|
+
async acceptLoop() {
|
|
3793
|
+
const served = /* @__PURE__ */ new Set();
|
|
3794
|
+
for (; ; ) {
|
|
3795
|
+
let conn;
|
|
3796
|
+
try {
|
|
3797
|
+
conn = await this.listener.accept(this.stop.signal);
|
|
3798
|
+
} catch {
|
|
3799
|
+
break;
|
|
3800
|
+
}
|
|
3801
|
+
const done4 = this.serve(conn).finally(() => served.delete(done4));
|
|
3802
|
+
served.add(done4);
|
|
3803
|
+
}
|
|
3804
|
+
await Promise.allSettled([...served]);
|
|
3805
|
+
}
|
|
3806
|
+
async serve(conn) {
|
|
3807
|
+
this.connections.add(conn);
|
|
3808
|
+
const incoming = new Incoming(conn);
|
|
3809
|
+
try {
|
|
3810
|
+
const head = await incoming.head(MAX_HEAD, this.stop.signal);
|
|
3811
|
+
if (head) await this.route(conn, incoming, parseRequestHead(head));
|
|
3812
|
+
} catch (e) {
|
|
3813
|
+
if (e instanceof HttpError) await this.write(conn, respond(e.status, `${e.message}
|
|
3814
|
+
`));
|
|
3815
|
+
} finally {
|
|
3816
|
+
this.connections.delete(conn);
|
|
3817
|
+
conn.close();
|
|
3818
|
+
}
|
|
3819
|
+
}
|
|
3820
|
+
async write(conn, bytes) {
|
|
3821
|
+
try {
|
|
3822
|
+
await conn.write(bytes, this.stop.signal);
|
|
3823
|
+
} catch {
|
|
3824
|
+
}
|
|
3825
|
+
}
|
|
3826
|
+
async route(conn, incoming, req) {
|
|
3827
|
+
const url = new URL(req.target, "http://127.0.0.1");
|
|
3828
|
+
const runtime = url.searchParams.get("runtime") ?? void 0;
|
|
3829
|
+
const host = req.headers.find(([n]) => n.toLowerCase() === "host")?.[1] ?? "127.0.0.1";
|
|
3830
|
+
if (req.method !== "GET") throw new HttpError(405, `${req.method} is not supported here`);
|
|
3831
|
+
const { hosts } = this.options;
|
|
3832
|
+
if (url.pathname === hosts.path && isUpgrade(req.headers)) {
|
|
3833
|
+
return this.upgrade(conn, incoming, req, runtime);
|
|
3834
|
+
}
|
|
3835
|
+
const path = url.pathname.replace(/\/+$/, "");
|
|
3836
|
+
if (path !== "/json/version" && path !== "/json/list" && path !== "/json") {
|
|
3837
|
+
throw new HttpError(404, "not found: try /json/version");
|
|
3838
|
+
}
|
|
3839
|
+
if (!this.options.hosts.available(runtime)) {
|
|
3840
|
+
throw new HttpError(503, NO_CDP_HOST);
|
|
3841
|
+
}
|
|
3842
|
+
if (path === "/json/version") {
|
|
3843
|
+
await this.write(
|
|
3844
|
+
conn,
|
|
3845
|
+
json({
|
|
3846
|
+
Browser: "slicc-kernel",
|
|
3847
|
+
"Protocol-Version": "1.3",
|
|
3848
|
+
webSocketDebuggerUrl: `ws://${host}${hosts.path}${url.search}`
|
|
3849
|
+
})
|
|
3850
|
+
);
|
|
3851
|
+
return;
|
|
3852
|
+
}
|
|
3853
|
+
await this.write(conn, json(await this.list(runtime)));
|
|
3854
|
+
}
|
|
3855
|
+
async list(runtime) {
|
|
3856
|
+
const conn = await this.host(runtime);
|
|
3857
|
+
try {
|
|
3858
|
+
const reply = await new Promise(
|
|
3859
|
+
(resolve, reject) => {
|
|
3860
|
+
const timer = setTimeout(
|
|
3861
|
+
() => reject(new HttpError(504, "the CDP host did not answer Target.getTargets")),
|
|
3862
|
+
this.options.listTimeout ?? LIST_TIMEOUT
|
|
3863
|
+
);
|
|
3864
|
+
conn.onclose = (reason) => {
|
|
3865
|
+
clearTimeout(timer);
|
|
3866
|
+
reject(new HttpError(502, `the CDP host closed: ${reason ?? "no reason"}`));
|
|
3867
|
+
};
|
|
3868
|
+
conn.onmessage = (message2) => {
|
|
3869
|
+
const parsed = JSON.parse(message2);
|
|
3870
|
+
if (parsed.id !== 1) return;
|
|
3871
|
+
clearTimeout(timer);
|
|
3872
|
+
resolve(parsed);
|
|
3873
|
+
};
|
|
3874
|
+
conn.send(JSON.stringify({ id: 1, method: "Target.getTargets" }));
|
|
3875
|
+
}
|
|
3876
|
+
);
|
|
3877
|
+
if (!reply.result) {
|
|
3878
|
+
throw new HttpError(502, `Target.getTargets failed: ${JSON.stringify(reply.error)}`);
|
|
3879
|
+
}
|
|
3880
|
+
return (reply.result.targetInfos ?? []).map((t) => ({
|
|
3881
|
+
description: "",
|
|
3882
|
+
id: t.targetId,
|
|
3883
|
+
title: t.title,
|
|
3884
|
+
type: t.type,
|
|
3885
|
+
url: t.url
|
|
3886
|
+
}));
|
|
3887
|
+
} finally {
|
|
3888
|
+
conn.close();
|
|
3889
|
+
}
|
|
3890
|
+
}
|
|
3891
|
+
host(runtime) {
|
|
3892
|
+
return this.options.hosts.connect(runtime ? { runtime } : {});
|
|
3893
|
+
}
|
|
3894
|
+
async upgrade(conn, incoming, req, runtime) {
|
|
3895
|
+
const accept2 = await acceptKey(req.headers);
|
|
3896
|
+
const host = await this.host(runtime);
|
|
3897
|
+
await new Relay(conn, incoming, host, this.options.maxMessage ?? MAX_MESSAGE, this.stop.signal).start(switchingHead(accept2)).finally(() => host.close());
|
|
3898
|
+
}
|
|
3899
|
+
};
|
|
3900
|
+
var Relay = class {
|
|
3901
|
+
writes = Promise.resolve();
|
|
3902
|
+
ended = false;
|
|
3903
|
+
conn;
|
|
3904
|
+
incoming;
|
|
3905
|
+
host;
|
|
3906
|
+
max;
|
|
3907
|
+
stop;
|
|
3908
|
+
constructor(conn, incoming, host, max, stop) {
|
|
3909
|
+
this.conn = conn;
|
|
3910
|
+
this.incoming = incoming;
|
|
3911
|
+
this.host = host;
|
|
3912
|
+
this.max = max;
|
|
3913
|
+
this.stop = stop;
|
|
3914
|
+
}
|
|
3915
|
+
send(bytes) {
|
|
3916
|
+
this.writes = this.writes.then(() => this.conn.write(bytes, this.stop)).catch(() => void 0);
|
|
3917
|
+
return this.writes;
|
|
3918
|
+
}
|
|
3919
|
+
end(code, reason) {
|
|
3920
|
+
if (this.ended) return this.writes;
|
|
3921
|
+
this.ended = true;
|
|
3922
|
+
return this.send(closeFrame(code, reason));
|
|
3923
|
+
}
|
|
3924
|
+
async start(head) {
|
|
3925
|
+
await this.send(head);
|
|
3926
|
+
const hostGone = new Promise((resolve) => {
|
|
3927
|
+
this.host.onclose = (reason) => {
|
|
3928
|
+
void this.end(reason ? CLOSE.error : CLOSE.normal, reason ?? "").then(() => resolve());
|
|
3929
|
+
};
|
|
3930
|
+
});
|
|
3931
|
+
this.host.onmessage = (message2) => {
|
|
3932
|
+
if (this.ended) return;
|
|
3933
|
+
const bytes = new TextEncoder().encode(message2);
|
|
3934
|
+
if (bytes.length > this.max)
|
|
3935
|
+
void this.end(CLOSE.tooBig, `a message is over ${this.max} bytes`);
|
|
3936
|
+
else void this.send(frame(OP_TEXT, bytes));
|
|
3937
|
+
};
|
|
3938
|
+
await Promise.race([this.pump(), hostGone]);
|
|
3939
|
+
await this.writes;
|
|
3940
|
+
}
|
|
3941
|
+
async pump() {
|
|
3942
|
+
const reader = new WsReader(this.incoming, this.max);
|
|
3943
|
+
try {
|
|
3944
|
+
for (; ; ) {
|
|
3945
|
+
const event = await reader.next(this.stop);
|
|
3946
|
+
if (!event) return;
|
|
3947
|
+
if (event.kind === "text") this.host.send(event.text);
|
|
3948
|
+
else if (event.kind === "ping") void this.send(pongFrame(event.payload));
|
|
3949
|
+
else {
|
|
3950
|
+
await this.end(event.code === 1005 ? CLOSE.normal : event.code, "");
|
|
3951
|
+
return;
|
|
3952
|
+
}
|
|
3953
|
+
}
|
|
3954
|
+
} catch (e) {
|
|
3955
|
+
if (e instanceof WsError) await this.end(e.code, e.message);
|
|
3956
|
+
}
|
|
3957
|
+
}
|
|
3958
|
+
};
|
|
3959
|
+
function enableCdp(net, hosts) {
|
|
3960
|
+
let facade;
|
|
3961
|
+
net.kernelOnly(CDP_PORT, true);
|
|
3962
|
+
net.activate({ family: "inet", host: "127.0.0.1", port: CDP_PORT }, () => {
|
|
3963
|
+
const started = new CdpFacade({ net, hosts });
|
|
3964
|
+
facade = started;
|
|
3965
|
+
void started.closed.then(() => {
|
|
3966
|
+
if (facade === started) facade = void 0;
|
|
3967
|
+
});
|
|
3968
|
+
});
|
|
3969
|
+
return () => facade;
|
|
3970
|
+
}
|
|
3971
|
+
|
|
3972
|
+
// src/cdp/hosts.ts
|
|
3973
|
+
var CdpHosts = class {
|
|
3974
|
+
embedder;
|
|
3975
|
+
clients = [];
|
|
3976
|
+
closed = false;
|
|
3977
|
+
path = `/devtools/browser/${crypto.randomUUID()}`;
|
|
3978
|
+
url = `ws://127.0.0.1:${CDP_PORT}${this.path}`;
|
|
3979
|
+
constructor(hook) {
|
|
3980
|
+
this.embedder = hook ? { hook, runtime: void 0, open: /* @__PURE__ */ new Set() } : void 0;
|
|
3981
|
+
}
|
|
3982
|
+
register(hook, runtime) {
|
|
3983
|
+
const entry = { hook, runtime, open: /* @__PURE__ */ new Set() };
|
|
3984
|
+
this.clients.push(entry);
|
|
3985
|
+
return () => {
|
|
3986
|
+
const at = this.clients.indexOf(entry);
|
|
3987
|
+
if (at >= 0) this.clients.splice(at, 1);
|
|
3988
|
+
closeAll(entry);
|
|
3989
|
+
};
|
|
3990
|
+
}
|
|
3991
|
+
pick(runtime) {
|
|
3992
|
+
if (this.closed) return void 0;
|
|
3993
|
+
const named = runtime ? this.clients.findLast((c) => c.runtime === runtime) : void 0;
|
|
3994
|
+
return named ?? this.embedder ?? this.clients.at(-1);
|
|
3995
|
+
}
|
|
3996
|
+
available(runtime) {
|
|
3997
|
+
return this.pick(runtime) !== void 0;
|
|
3998
|
+
}
|
|
3999
|
+
async connect(request2) {
|
|
4000
|
+
const entry = this.pick(request2.runtime);
|
|
4001
|
+
if (!entry) throw new HttpError(503, NO_CDP_HOST);
|
|
4002
|
+
let conn;
|
|
4003
|
+
try {
|
|
4004
|
+
conn = await entry.hook(request2.runtime ? { runtime: request2.runtime } : {});
|
|
4005
|
+
} catch (e) {
|
|
4006
|
+
throw new HttpError(502, `CDP host: ${e instanceof Error ? e.message : String(e)}`);
|
|
4007
|
+
}
|
|
4008
|
+
if (this.closed || !(entry === this.embedder || this.clients.includes(entry))) {
|
|
4009
|
+
conn.close();
|
|
4010
|
+
throw new HttpError(503, NO_CDP_HOST);
|
|
4011
|
+
}
|
|
4012
|
+
entry.open.add(conn);
|
|
4013
|
+
const close = conn.close.bind(conn);
|
|
4014
|
+
conn.close = () => {
|
|
4015
|
+
if (entry.open.delete(conn)) close();
|
|
4016
|
+
};
|
|
4017
|
+
return conn;
|
|
4018
|
+
}
|
|
4019
|
+
close() {
|
|
4020
|
+
this.closed = true;
|
|
4021
|
+
for (const entry of [this.embedder, ...this.clients]) if (entry) closeAll(entry);
|
|
4022
|
+
}
|
|
4023
|
+
};
|
|
4024
|
+
function closeAll(entry) {
|
|
4025
|
+
for (const conn of [...entry.open]) {
|
|
4026
|
+
conn.close();
|
|
4027
|
+
conn.onclose?.("the CDP host is gone");
|
|
4028
|
+
}
|
|
4029
|
+
}
|
|
4030
|
+
|
|
3391
4031
|
// src/commands.ts
|
|
3392
4032
|
var NAME = /^[A-Za-z0-9._+-]+$/;
|
|
3393
4033
|
var ENV_KEY = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
@@ -4434,6 +5074,7 @@ var MountFs = class {
|
|
|
4434
5074
|
async pread(path, offset, length2) {
|
|
4435
5075
|
const real = await this.follow(path);
|
|
4436
5076
|
const found = this.at(real);
|
|
5077
|
+
if (!found && this.base.pread) return this.base.pread(real, offset, length2);
|
|
4437
5078
|
if (!found) return (await this.base.readFileBuffer(real)).slice(offset, offset + length2);
|
|
4438
5079
|
const { mount, rel: rel2 } = found;
|
|
4439
5080
|
if ((await this.table.getattr(mount, real, rel2)).kind === "directory") {
|
|
@@ -4471,6 +5112,7 @@ var MountFs = class {
|
|
|
4471
5112
|
async pwrite(path, offset, bytes, transfer = false) {
|
|
4472
5113
|
const real = await this.follow(path, 0, true);
|
|
4473
5114
|
const found = this.at(real);
|
|
5115
|
+
if (!found && this.base.pwrite) return this.base.pwrite(real, offset, bytes);
|
|
4474
5116
|
if (!found)
|
|
4475
5117
|
return this.base.writeFile(
|
|
4476
5118
|
real,
|
|
@@ -4502,6 +5144,7 @@ var MountFs = class {
|
|
|
4502
5144
|
async truncate(path, size) {
|
|
4503
5145
|
const real = await this.follow(path);
|
|
4504
5146
|
const found = this.at(real);
|
|
5147
|
+
if (!found && this.base.truncate) return this.base.truncate(real, size);
|
|
4505
5148
|
if (!found?.mount.caps.ranges) {
|
|
4506
5149
|
return this.writeFile(real, resized(await this.readFileBuffer(real), size));
|
|
4507
5150
|
}
|
|
@@ -4711,7 +5354,7 @@ async function storageUsage() {
|
|
|
4711
5354
|
return { quota: estimate.quota, usage: estimate.usage ?? 0 };
|
|
4712
5355
|
}
|
|
4713
5356
|
var done = { ok: true, kind: "void" };
|
|
4714
|
-
function
|
|
5357
|
+
function json2(value) {
|
|
4715
5358
|
return { ok: true, kind: "json", json: value };
|
|
4716
5359
|
}
|
|
4717
5360
|
async function run(entry, path, req) {
|
|
@@ -4723,15 +5366,15 @@ async function run(entry, path, req) {
|
|
|
4723
5366
|
await fs.writeFile(path, req.body ?? new Uint8Array(0));
|
|
4724
5367
|
return done;
|
|
4725
5368
|
case "exists":
|
|
4726
|
-
return
|
|
5369
|
+
return json2(await fs.exists(path));
|
|
4727
5370
|
case "stat":
|
|
4728
|
-
return
|
|
5371
|
+
return json2(statJson(await fs.stat(path)));
|
|
4729
5372
|
case "lstat":
|
|
4730
|
-
return
|
|
5373
|
+
return json2(statJson(await fs.lstat(path)));
|
|
4731
5374
|
case "readdir":
|
|
4732
|
-
return
|
|
5375
|
+
return json2(await fs.readdir(path));
|
|
4733
5376
|
case "readdir-stat":
|
|
4734
|
-
return
|
|
5377
|
+
return json2(await readdirStat(fs, path));
|
|
4735
5378
|
case "mkdir":
|
|
4736
5379
|
await fs.mkdir(path, { recursive: true });
|
|
4737
5380
|
return done;
|
|
@@ -4757,7 +5400,7 @@ async function run(entry, path, req) {
|
|
|
4757
5400
|
await fs.symlink(req.arg2 ?? "", path);
|
|
4758
5401
|
return done;
|
|
4759
5402
|
case "readlink":
|
|
4760
|
-
return
|
|
5403
|
+
return json2(await fs.readlink(path));
|
|
4761
5404
|
case "chmod":
|
|
4762
5405
|
await fs.chmod(path, req.mode ?? 0);
|
|
4763
5406
|
return done;
|
|
@@ -4788,7 +5431,7 @@ async function dispatchSyncFs(req) {
|
|
|
4788
5431
|
const { fs, cwd } = entry;
|
|
4789
5432
|
try {
|
|
4790
5433
|
const path = fs.resolvePath(cwd, req.path);
|
|
4791
|
-
if (req.op === "statfs") return
|
|
5434
|
+
if (req.op === "statfs") return json2(await entry.statfs?.(path) ?? await storageUsage());
|
|
4792
5435
|
if (req.op === "hold") {
|
|
4793
5436
|
entry.hold?.(path, req.mode === 1);
|
|
4794
5437
|
return done;
|
|
@@ -4961,7 +5604,7 @@ var HOST_OPS = [
|
|
|
4961
5604
|
"async-cancel",
|
|
4962
5605
|
"async-close"
|
|
4963
5606
|
];
|
|
4964
|
-
var
|
|
5607
|
+
var json3 = (value) => ({ ok: true, kind: "json", json: value });
|
|
4965
5608
|
var done2 = { ok: true, kind: "void" };
|
|
4966
5609
|
var LockTable = class {
|
|
4967
5610
|
held = /* @__PURE__ */ new Map();
|
|
@@ -5010,7 +5653,7 @@ var AsyncOps = class {
|
|
|
5010
5653
|
}
|
|
5011
5654
|
resolve(value) {
|
|
5012
5655
|
const id = this.hold();
|
|
5013
|
-
this.complete(id,
|
|
5656
|
+
this.complete(id, json3(value));
|
|
5014
5657
|
return id;
|
|
5015
5658
|
}
|
|
5016
5659
|
hold() {
|
|
@@ -5092,15 +5735,15 @@ async function hostSyscall(req, ctx) {
|
|
|
5092
5735
|
return done2;
|
|
5093
5736
|
case "async-submit":
|
|
5094
5737
|
if (!ctx.valid(req.req)) throw new KernelError("EINVAL");
|
|
5095
|
-
return
|
|
5738
|
+
return json3(ctx.ops.submit(req.req));
|
|
5096
5739
|
case "async-wait":
|
|
5097
|
-
return
|
|
5740
|
+
return json3(await ctx.ops.wait(req.timeoutMs, ctx.blocking()));
|
|
5098
5741
|
case "async-take":
|
|
5099
5742
|
return ctx.ops.take(req.id);
|
|
5100
5743
|
case "async-resolve":
|
|
5101
|
-
return
|
|
5744
|
+
return json3(ctx.ops.resolve(req.value));
|
|
5102
5745
|
case "async-hold":
|
|
5103
|
-
return
|
|
5746
|
+
return json3(ctx.ops.hold());
|
|
5104
5747
|
case "async-cancel":
|
|
5105
5748
|
ctx.ops.cancel(req.id);
|
|
5106
5749
|
return done2;
|
|
@@ -5627,23 +6270,23 @@ function terminalOf(ctx, fd) {
|
|
|
5627
6270
|
if (!tty) throw new KernelError("ENOTTY");
|
|
5628
6271
|
return tty;
|
|
5629
6272
|
}
|
|
5630
|
-
var
|
|
6273
|
+
var json4 = (value) => ({ ok: true, kind: "json", json: value });
|
|
5631
6274
|
var done3 = { ok: true, kind: "void" };
|
|
5632
6275
|
function ptySyscall(req, ctx) {
|
|
5633
6276
|
switch (req.op) {
|
|
5634
6277
|
case "pty-open": {
|
|
5635
6278
|
if (!ctx.ptys) throw new KernelError("ENOENT");
|
|
5636
|
-
return
|
|
6279
|
+
return json4(ctx.fds.install(ctx.ptys.open().master, 3));
|
|
5637
6280
|
}
|
|
5638
6281
|
case "pty-slave-open": {
|
|
5639
6282
|
if (!ctx.ptys) throw new KernelError("ENOENT");
|
|
5640
6283
|
const pair = ctx.ptys.get(req.n);
|
|
5641
6284
|
const fd = ctx.fds.install(pair.openSlave(), 3);
|
|
5642
6285
|
if (!req.noctty) ctx.jobs?.acquireTerminal(ctx.pid, pair.slave);
|
|
5643
|
-
return
|
|
6286
|
+
return json4(fd);
|
|
5644
6287
|
}
|
|
5645
6288
|
case "pty-number":
|
|
5646
|
-
return
|
|
6289
|
+
return json4(masterOf(ctx, req.fd).index);
|
|
5647
6290
|
case "pty-lock":
|
|
5648
6291
|
masterOf(ctx, req.fd).locked = req.lock;
|
|
5649
6292
|
return done3;
|
|
@@ -5655,7 +6298,7 @@ function ptySyscall(req, ctx) {
|
|
|
5655
6298
|
return done3;
|
|
5656
6299
|
}
|
|
5657
6300
|
case "pty-list":
|
|
5658
|
-
return
|
|
6301
|
+
return json4(ctx.ptys?.numbers() ?? []);
|
|
5659
6302
|
case "pty-packet":
|
|
5660
6303
|
masterOf(ctx, req.fd).packet = req.on;
|
|
5661
6304
|
return done3;
|
|
@@ -5733,7 +6376,7 @@ function socketAt(fds, fd) {
|
|
|
5733
6376
|
if (!(file instanceof KernelSocket)) throw new KernelError("ENOTSOCK");
|
|
5734
6377
|
return file;
|
|
5735
6378
|
}
|
|
5736
|
-
var ok = (
|
|
6379
|
+
var ok = (json5) => json5 === void 0 ? { ok: true, kind: "void" } : { ok: true, kind: "json", json: json5 };
|
|
5737
6380
|
async function socketSyscall(req, proc) {
|
|
5738
6381
|
const { fds, net } = proc;
|
|
5739
6382
|
switch (req.op) {
|
|
@@ -7697,8 +8340,8 @@ function umountCall(target, flags) {
|
|
|
7697
8340
|
var FSTAB_PATH = "/etc/fstab";
|
|
7698
8341
|
var FSTAB_RETRIES = [1e3, 4e3, 16e3];
|
|
7699
8342
|
var FINAL = /* @__PURE__ */ new Set(["EINVAL", "EBUSY", "EPERM", "EACCES"]);
|
|
7700
|
-
function unescape(
|
|
7701
|
-
return
|
|
8343
|
+
function unescape(field2) {
|
|
8344
|
+
return field2.replace(
|
|
7702
8345
|
/\\([0-7]{3})/g,
|
|
7703
8346
|
(_, octal) => String.fromCharCode(Number.parseInt(octal, 8))
|
|
7704
8347
|
);
|
|
@@ -8346,7 +8989,7 @@ function importedMemory(bytes) {
|
|
|
8346
8989
|
const count = u32();
|
|
8347
8990
|
for (let i = 0; i < count; i++) {
|
|
8348
8991
|
const module = name2();
|
|
8349
|
-
const
|
|
8992
|
+
const field2 = name2();
|
|
8350
8993
|
const kind = bytes[at++];
|
|
8351
8994
|
if (kind === 0) u32();
|
|
8352
8995
|
else if (kind === 1) {
|
|
@@ -8354,7 +8997,7 @@ function importedMemory(bytes) {
|
|
|
8354
8997
|
limits();
|
|
8355
8998
|
} else if (kind === 2) {
|
|
8356
8999
|
const l = limits();
|
|
8357
|
-
return { module, name:
|
|
9000
|
+
return { module, name: field2, ...l };
|
|
8358
9001
|
} else if (kind === 3) at += 2;
|
|
8359
9002
|
else if (kind === 4) {
|
|
8360
9003
|
at++;
|
|
@@ -8445,12 +9088,12 @@ function readImports(r, types) {
|
|
|
8445
9088
|
const foreign = {};
|
|
8446
9089
|
for (let i = r.u32(); i > 0; i--) {
|
|
8447
9090
|
const module = r.name();
|
|
8448
|
-
const
|
|
9091
|
+
const field2 = r.name();
|
|
8449
9092
|
const kind = r.byte();
|
|
8450
9093
|
if (kind !== 0) skipImport(r, kind);
|
|
8451
9094
|
else {
|
|
8452
9095
|
const type = types[r.u32()] ?? "other";
|
|
8453
|
-
if (!RESERVED_NAMESPACES.has(module)) (foreign[module] ??= {})[
|
|
9096
|
+
if (!RESERVED_NAMESPACES.has(module)) (foreign[module] ??= {})[field2] = type;
|
|
8454
9097
|
}
|
|
8455
9098
|
}
|
|
8456
9099
|
return foreign;
|
|
@@ -8557,6 +9200,7 @@ var Launcher = class {
|
|
|
8557
9200
|
locks = new LockTable();
|
|
8558
9201
|
ptys = new PtyTable((tty, sig) => this.jobs.signalOwnedForeground(tty, sig));
|
|
8559
9202
|
net = new LoopbackNet();
|
|
9203
|
+
cdp;
|
|
8560
9204
|
ca;
|
|
8561
9205
|
transport;
|
|
8562
9206
|
watchers = new FsWatchers();
|
|
@@ -8593,7 +9237,8 @@ var Launcher = class {
|
|
|
8593
9237
|
this.processMounts = options.processMounts ?? true;
|
|
8594
9238
|
this.pnpmHome = options.env?.PNPM_HOME ?? PNPM_HOME;
|
|
8595
9239
|
this.modulesDir = options.modules ?? "/node_modules";
|
|
8596
|
-
this.
|
|
9240
|
+
this.cdp = new CdpHosts(options.cdp);
|
|
9241
|
+
this.env = { ...networkEnv(), SLICC_CDP_URL: this.cdp.url, ...options.env };
|
|
8597
9242
|
this.ca = kernelCa(options.caStore ?? memoryCaStore());
|
|
8598
9243
|
this.transport = options.transport ?? missingTransport();
|
|
8599
9244
|
enableNetwork(this.net, {
|
|
@@ -8601,6 +9246,7 @@ var Launcher = class {
|
|
|
8601
9246
|
engine: kernelTlsEngine(packageTlsEngine(options.fs, this.modulesDir)),
|
|
8602
9247
|
ca: this.ca
|
|
8603
9248
|
});
|
|
9249
|
+
enableCdp(this.net, this.cdp);
|
|
8604
9250
|
this.fs = withCommandDirs(this.base, async () => new Set((await this.commands()).keys()));
|
|
8605
9251
|
this.watchers.watch([this.modulesDir, this.pnpmHome], { recursive: true }, () => {
|
|
8606
9252
|
this.catalog = void 0;
|
|
@@ -9126,6 +9772,11 @@ var Launcher = class {
|
|
|
9126
9772
|
};
|
|
9127
9773
|
|
|
9128
9774
|
// src/serve.ts
|
|
9775
|
+
async function inOpfs(deps, root) {
|
|
9776
|
+
if (deps.syncAccess !== true) return false;
|
|
9777
|
+
const opfs = await deps.storage().catch(() => void 0);
|
|
9778
|
+
return await opfs?.resolve(root).catch(() => null) != null;
|
|
9779
|
+
}
|
|
9129
9780
|
function signalNumber(name2) {
|
|
9130
9781
|
const key = name2.slice(3);
|
|
9131
9782
|
if (!name2.startsWith("SIG") || !Object.hasOwn(SIG, key))
|
|
@@ -9159,6 +9810,14 @@ async function stores(deps, name2, media) {
|
|
|
9159
9810
|
...handles === false ? {} : { media: await deps.media?.(handles) }
|
|
9160
9811
|
};
|
|
9161
9812
|
}
|
|
9813
|
+
function cdpOption(req, port) {
|
|
9814
|
+
if (!req.cdp) return {};
|
|
9815
|
+
return {
|
|
9816
|
+
cdp: portCdpHook(
|
|
9817
|
+
(request2, cdp) => port.postMessage({ cdpOpen: request2, port: cdp }, [cdp])
|
|
9818
|
+
)
|
|
9819
|
+
};
|
|
9820
|
+
}
|
|
9162
9821
|
function serveKernel(port, deps) {
|
|
9163
9822
|
let launcher;
|
|
9164
9823
|
let remote;
|
|
@@ -9218,7 +9877,8 @@ function serveKernel(port, deps) {
|
|
|
9218
9877
|
const name2 = req.metadata ?? META_DB;
|
|
9219
9878
|
const { meta, media } = await stores(deps, name2, req.media);
|
|
9220
9879
|
remote = req.transport ? new RemoteTransport(port, req.transport) : void 0;
|
|
9221
|
-
const
|
|
9880
|
+
const ranged2 = await inOpfs(deps, root);
|
|
9881
|
+
const fs = new OpfsFs(root, meta, dirsChannel(name2), { ranged: ranged2 });
|
|
9222
9882
|
await fs.reconcile();
|
|
9223
9883
|
const started = new Launcher({
|
|
9224
9884
|
fs,
|
|
@@ -9230,6 +9890,7 @@ function serveKernel(port, deps) {
|
|
|
9230
9890
|
...media ? { media } : {},
|
|
9231
9891
|
onMountPending: (medium) => port.postMessage({ medium }),
|
|
9232
9892
|
...req.hostfs ? { hostfs: askGrant } : {},
|
|
9893
|
+
...cdpOption(req, port),
|
|
9233
9894
|
...mountPolicy(req.processMounts),
|
|
9234
9895
|
caStore: caStore(deps.metadata ? caDb(req) : false)
|
|
9235
9896
|
});
|
|
@@ -9315,6 +9976,7 @@ var processWorker = pinScript(new URL("./process-worker.js", import.meta.url));
|
|
|
9315
9976
|
var driverWorker = pinScript(new URL("./driver-worker.js", import.meta.url));
|
|
9316
9977
|
serveKernel(globalThis, {
|
|
9317
9978
|
storage: () => navigator.storage.getDirectory(),
|
|
9979
|
+
syncAccess: syncAccessHandles(),
|
|
9318
9980
|
createWorker: () => new Worker(processWorker, { type: "module" }),
|
|
9319
9981
|
createDriverWorker: () => new Worker(driverWorker, { type: "module" }),
|
|
9320
9982
|
metadata: (name2) => openMeta(name2, indexedDB),
|