@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.
@@ -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/kernel/pipe.ts
289
- var PIPE_CAPACITY = 64 * 1024;
290
- var PipeError = class extends Error {
291
- code;
292
- constructor(code) {
293
- super(code);
294
- this.code = code;
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
- var KernelPipe = class {
298
- chunks = [];
299
- size = 0;
300
- readers = 0;
301
- writers = 0;
302
- waiters = [];
303
- capacity;
304
- constructor(capacity = PIPE_CAPACITY) {
305
- this.capacity = capacity;
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
- get buffered() {
308
- return this.size;
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
- get readReady() {
311
- return this.size > 0 || this.writers === 0;
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
- get writeReady() {
314
- return this.size < this.capacity || this.readers === 0;
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
- get writersGone() {
317
- return this.writers === 0;
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
- get readersGone() {
320
- return this.readers === 0;
332
+ changed(...paths) {
333
+ for (const path of paths) this.forget(path);
334
+ this.channel?.postMessage(paths);
321
335
  }
322
- openRead() {
323
- this.readers += 1;
336
+ resolvePath(base, path) {
337
+ return resolvePath(base, path);
324
338
  }
325
- openWrite() {
326
- this.writers += 1;
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
- closeRead() {
329
- if (this.readers > 0) this.readers -= 1;
330
- if (this.readers === 0) {
331
- this.chunks = [];
332
- this.size = 0;
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
- closeWrite() {
337
- if (this.writers > 0) this.writers -= 1;
338
- this.wake();
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
- async read(max, signal) {
341
- while (this.size === 0) {
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 peek(max, signal) {
350
- while (this.size === 0) {
351
- if (this.writers === 0) return new Uint8Array(0);
352
- await this.changed(signal);
353
- }
354
- const out = new Uint8Array(Math.min(max, this.size));
355
- let filled = 0;
356
- for (const chunk2 of this.chunks) {
357
- if (filled === out.length) break;
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 write(bytes, signal) {
365
- let offset = 0;
366
- while (offset < bytes.length) {
367
- if (this.readers === 0) throw new PipeError("EPIPE");
368
- const room = this.capacity - this.size;
369
- if (room === 0) {
370
- try {
371
- await this.changed(signal);
372
- } catch (e) {
373
- if (offset > 0) return offset;
374
- throw e;
375
- }
376
- continue;
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
- const n = Math.min(room, bytes.length - offset);
379
- this.chunks.push(bytes.slice(offset, offset + n));
380
- this.size += n;
381
- offset += n;
382
- this.wake();
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 bytes.length;
424
+ return void 0;
385
425
  }
386
- take(n) {
387
- const out = new Uint8Array(n);
388
- let filled = 0;
389
- while (filled < n) {
390
- const head = this.chunks[0];
391
- const count = Math.min(head.length, n - filled);
392
- out.set(head.subarray(0, count), filled);
393
- filled += count;
394
- if (count === head.length) this.chunks.shift();
395
- else this.chunks[0] = head.subarray(count);
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
- this.size -= n;
398
- return out;
440
+ throw fsError("ELOOP", path);
399
441
  }
400
- changed(signal) {
401
- if (signal?.aborted) return Promise.reject(new PipeError("EINTR"));
402
- return new Promise((resolve, reject) => {
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
- wake() {
416
- const waiters = this.waiters;
417
- this.waiters = [];
418
- for (const resolve of waiters) resolve();
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
- // src/kernel/fd-table.ts
423
- var KernelError = class extends Error {
424
- code;
425
- constructor(code) {
426
- super(code);
427
- this.code = code;
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
- function pollFile(file) {
431
- return file.poll?.() ?? { readable: !!file.read, writable: !!file.write, hangup: false };
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
- retain() {
440
- this.refs += 1;
441
- return this;
483
+ async stat(path) {
484
+ return this.describe(await this.locate(path, true));
442
485
  }
443
- release() {
444
- this.refs -= 1;
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
- function openPipe(capacity) {
449
- const pipe = new KernelPipe(capacity);
450
- pipe.openRead();
451
- pipe.openWrite();
452
- return {
453
- read: new OpenFile({
454
- read: async (max, signal) => {
455
- try {
456
- return await pipe.read(max, signal);
457
- } catch (e) {
458
- throw new KernelError(e.code);
459
- }
460
- },
461
- close: () => pipe.closeRead(),
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
- has(fd) {
560
- return this.fds.has(fd);
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
- setCloseOnExec(fd) {
563
- this.get(fd);
564
- this.cloexec.add(fd);
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
- clearCloseOnExec(fd) {
567
- this.get(fd);
568
- this.cloexec.delete(fd);
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
- closesOnExec(fd) {
571
- return this.cloexec.has(fd);
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
- setStatusFlags(fd, flags) {
574
- this.get(fd);
575
- this.status.set(fd, flags);
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
- statusFlags(fd) {
578
- return this.status.get(fd);
536
+ async readFile(path) {
537
+ return new TextDecoder().decode(await this.readFileBuffer(path));
579
538
  }
580
- install(file, min = 0) {
581
- for (let fd = min; fd < _FdTable.MAX_FDS; fd++) {
582
- if (!this.fds.has(fd)) {
583
- this.fds.set(fd, file);
584
- return fd;
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
- void Promise.resolve(file.release()).catch(() => void 0);
588
- throw new KernelError("EMFILE");
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
- installAt(fd, file) {
591
- if (fd < 0 || fd >= _FdTable.MAX_FDS) {
592
- void Promise.resolve(file.release()).catch(() => void 0);
593
- throw new KernelError("EBADF");
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
- dup(fd, min = 0) {
602
- return this.install(this.get(fd).retain(), min);
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
- dup2(oldFd, newFd) {
605
- const file = this.get(oldFd);
606
- if (oldFd !== newFd) this.installAt(newFd, file.retain());
607
- return newFd;
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
- close(fd) {
610
- const file = this.get(fd);
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
- fork() {
617
- const child = new _FdTable();
618
- for (const [fd, file] of this.fds) child.fds.set(fd, file.retain());
619
- for (const fd of this.cloexec) child.cloexec.add(fd);
620
- for (const [fd, flags] of this.status) child.status.set(fd, flags);
621
- return child;
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 closeAll() {
624
- const files = [...this.fds.values()];
625
- this.fds.clear();
626
- this.cloexec.clear();
627
- this.status.clear();
628
- await Promise.allSettled(files.map((file) => Promise.resolve().then(() => file.release())));
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
- // src/kernel/socket.ts
633
- var SOMAXCONN = 4096;
634
- var EPHEMERAL_FIRST = 32768;
635
- var EPHEMERAL_LAST = 60999;
636
- var SOL_SOCKET = 1;
637
- var SO_TYPE = 3;
638
- var SO_ERROR = 4;
639
- var SO_SNDBUF = 7;
640
- var SO_RCVBUF = 8;
641
- var SO_ACCEPTCONN = 30;
642
- var SO_DOMAIN = 39;
643
- var SOCK_STREAM = 1;
644
- var AF_UNIX = 1;
645
- var AF_INET = 2;
646
- var SHUT_RD = 0;
647
- var SHUT_WR = 1;
648
- var SHUT_RDWR = 2;
649
- function isLoopback(host) {
650
- return /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(host);
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
- var Changes = class {
661
- waiters = [];
662
- wait(signal) {
663
- if (signal?.aborted) return Promise.reject(new KernelError("EINTR"));
664
- return new Promise((resolve, reject) => {
665
- const onAbort = () => {
666
- this.waiters = this.waiters.filter((w) => w !== waiter);
667
- reject(new KernelError("EINTR"));
668
- };
669
- const waiter = () => {
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
- wake() {
678
- const waiters = this.waiters;
679
- this.waiters = [];
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 (this.state !== "connected" || !this.link) {
743
- return { readable: false, writable: false, hangup: true };
655
+ if (targetStat?.isDirectory && (await this.readdir(destination)).length > 0) {
656
+ throw fsError("ENOTEMPTY", to);
744
657
  }
745
- const { rx, tx } = this.link;
746
- return {
747
- readable: this.readShut || rx.readReady,
748
- writable: this.writeShut || tx.writeReady,
749
- hangup: rx.writersGone && tx.readersGone
750
- };
751
- }
752
- changed(signal) {
753
- const own = this.changes.wait(signal);
754
- if (this.state !== "connected" || !this.link) return own;
755
- const waits = [own, this.link.rx.changed(signal), this.link.tx.changed(signal)];
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
- this.backlog = Math.min(Math.max(backlog, 1), SOMAXCONN);
773
- this.state = "listening";
774
- }
775
- async accept(signal, nonblock = false) {
776
- for (; ; ) {
777
- if (this.state !== "listening") throw new KernelError("EINVAL");
778
- const next = this.queue.shift();
779
- if (next) return next;
780
- if (nonblock) throw new KernelError("EAGAIN");
781
- await this.changes.wait(signal);
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
- offer(server) {
785
- if (this.state !== "listening" || this.queue.length >= this.backlog) return false;
786
- this.queue.push(server);
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
- connect(addr) {
791
- if (this.state === "connected") throw new KernelError("EISCONN");
792
- if (this.state !== "open") throw new KernelError("EINVAL");
793
- if (addr.family !== this.domain) throw new KernelError("EAFNOSUPPORT");
794
- const target = this.net.target(addr);
795
- if (!this.local) {
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
- unlink() {
813
- const link2 = this.link;
814
- if (link2) {
815
- link2.rx.closeRead();
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
- shutdown(how) {
823
- const { rx, tx } = this.connection();
824
- if (how !== SHUT_RD && how !== SHUT_WR && how !== SHUT_RDWR) throw new KernelError("EINVAL");
825
- if (how !== SHUT_WR && !this.readShut) {
826
- this.readShut = true;
827
- rx.closeRead();
828
- }
829
- if (how !== SHUT_RD && !this.writeShut) {
830
- this.writeShut = true;
831
- tx.closeWrite();
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
- this.changes.wake();
721
+ return false;
834
722
  }
835
- getOption(level, name2) {
836
- if (level === SOL_SOCKET) {
837
- switch (name2) {
838
- case SO_TYPE:
839
- return SOCK_STREAM;
840
- case SO_ERROR:
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
- return this.options.get(`${level}:${name2}`) ?? 0;
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
- close() {
857
- const was = this.state;
858
- this.state = "closed";
859
- if (was === "listening") {
860
- for (const pending of this.queue.splice(0)) pending.close();
861
- }
862
- if (this.link) {
863
- if (!this.readShut) this.link.rx.closeRead();
864
- if (!this.writeShut) this.link.tx.closeWrite();
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
- if (this.bound && this.local) this.net.release(this.local, this);
869
- this.bound = false;
870
- this.changes.wake();
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
- static pair(net, domain) {
873
- const a = new _KernelSocket(net, domain);
874
- const b = new _KernelSocket(net, domain);
875
- const name2 = domain === "inet" ? { family: "inet", host: "127.0.0.1", port: 0 } : { family: "unix", path: "" };
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
- function canonicalHost(host) {
882
- return host === "0.0.0.0" ? "127.0.0.1" : host;
883
- }
884
- function keyOf(addr) {
885
- return addr.family === "inet" ? `inet:${addr.port}` : `unix:${addr.path}`;
886
- }
887
- var LoopbackNet = class {
888
- bound = /* @__PURE__ */ new Map();
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
- target(addr) {
925
- if (addr.family === "inet" && !isLoopback(canonicalHost(addr.host))) {
926
- throw new KernelError("ENETUNREACH");
927
- }
928
- const key = keyOf(addr);
929
- if (!this.bound.has(key)) this.activators.get(key)?.();
930
- const socket = this.bound.get(key);
931
- if (!socket?.listening) throw new KernelError("ECONNREFUSED");
932
- return socket;
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
- ephemeral() {
935
- const span = EPHEMERAL_LAST - EPHEMERAL_FIRST + 1;
936
- for (let i = 0; i < span; i++) {
937
- const port = this.nextEphemeral;
938
- this.nextEphemeral = port === EPHEMERAL_LAST ? EPHEMERAL_FIRST : port + 1;
939
- if (!this.bound.has(`inet:${port}`)) return port;
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
- throw new KernelError("EADDRINUSE");
791
+ return removed;
942
792
  }
943
793
  };
944
794
 
945
- // src/kernel/dial.ts
946
- var CHUNK = 64 * 1024;
947
- var LOOPBACK_NAMES = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", "::1", "[::1]"]);
948
- function dialSocket(net, port, host = "127.0.0.1") {
949
- if (!Number.isInteger(port) || port < 1 || port > 65535) throw new KernelError("EINVAL");
950
- const target = LOOPBACK_NAMES.has(host.toLowerCase()) ? "127.0.0.1" : host;
951
- return net.connect({ family: "inet", host: target, port });
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 isSignal(sig) {
1058
- return Number.isInteger(sig) && sig >= 1 && sig <= 31;
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 sigbit(sig) {
1061
- return 1 << sig;
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/body-idle.ts
1072
- var BODY_IDLE_MS = 5 * 60 * 1e3;
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/transport.ts
1075
- var MAX_REQUEST_BODY = 64 * 1024 * 1024;
1076
- function serveTransport(peer, transport) {
1077
- const open = /* @__PURE__ */ new Map();
1078
- const fail = (nid, e) => {
1079
- open.delete(nid);
1080
- const { status, code } = e;
1081
- peer.postMessage({
1082
- net: "error",
1083
- nid,
1084
- message: e instanceof Error ? e.message : String(e),
1085
- ...typeof status === "number" ? { status } : {},
1086
- ...typeof code === "string" ? { code } : {}
1087
- });
1088
- };
1089
- const start = async (call) => {
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
- open.set(call.nid, { abort, body: response.body[Symbol.asyncIterator](), response });
1106
- peer.postMessage({
1107
- net: "head",
1108
- nid: call.nid,
1109
- status: response.status,
1110
- statusText: response.statusText,
1111
- headers: response.headers
1112
- });
1113
- } catch (e) {
1114
- fail(call.nid, e);
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
- } catch (e) {
1130
- fail(nid, e);
1131
- }
1132
- };
1133
- const drop = (nid) => {
1134
- const entry = open.get(nid);
1135
- open.delete(nid);
1136
- entry?.abort.abort();
1137
- void entry?.response?.cancel().catch(() => void 0);
1138
- };
1139
- return {
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/serve-client.ts
1152
- var MAX_LINKS = 40;
1153
- function act(session, req, signal) {
1154
- if (req.action === "write") session.write(req.bytes);
1155
- else if (req.action === "resize") session.resize(req.cols, req.rows);
1156
- else if (req.action === "signal") session.signal(signal(req.signal));
1157
- else session.close();
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
- async function realpath(fs, path) {
1160
- let parts = fs.resolvePath("/", path).split("/").filter(Boolean);
1161
- let resolved = "";
1162
- for (let hops = 0; parts.length > 0; ) {
1163
- const [name2, ...rest] = parts;
1164
- const next = `${resolved}/${name2}`;
1165
- if (!(await fs.lstat(next)).isSymbolicLink) {
1166
- resolved = next;
1167
- parts = rest;
1168
- continue;
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
- if (++hops > MAX_LINKS) throw fsError("ELOOP", path);
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
- return resolved || "/";
1177
- }
1178
- async function fsCall(fs, method, args) {
1179
- const path = (i) => fs.resolvePath("/", String(args[i]));
1180
- switch (method) {
1181
- case "readFile": {
1182
- const bytes = (await fs.readFileBuffer(path(0))).slice();
1183
- return { result: bytes, transfer: [bytes.buffer] };
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
- case "writeFile":
1186
- await fs.writeFile(path(0), args[1]);
1187
- return { result: true };
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
- function signalOf(host) {
1215
- return host.signal ?? ((name2) => {
1216
- throw fsError("EINVAL", `this kernel names no signal ${name2}`);
1217
- });
1218
- }
1219
- function dialFor(l, req, open) {
1220
- const socket = dialSocket(l.net, req.port, req.host);
1221
- const { port1, port2 } = new MessageChannel();
1222
- open.set(
1223
- req.id,
1224
- serveSocket(socket, port1, () => open.delete(req.id))
1225
- );
1226
- return { result: port2, transfer: [port2] };
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 function hello(message2) {
1266
- const refused = versionError(message2.protocol);
1267
- if (refused) {
1268
- send({ hello: { protocol: PROTOCOL, error: refused } });
1269
- detach();
1270
- return;
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
- const launcher = await host.launcher();
1273
- const transport = host.transport?.() ?? launcher.transport;
1274
- net = serveTransport({ postMessage: (m, t) => send(m, t) }, transport);
1275
- state = "open";
1276
- if (message2.lock && host.locks) void host.locks.request(message2.lock, () => detach());
1277
- send({
1278
- hello: {
1279
- protocol: PROTOCOL,
1280
- traits: transport.traits,
1281
- ...host.lock ? { lock: host.lock } : {}
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
- async function terminal(req, l) {
1286
- const session = await l.openTerminal(req.argv, {
1287
- ...req.options,
1288
- onData: (bytes) => reply(req.id, { fd: 1, bytes: bytes.slice() })
1289
- });
1290
- if (state === "closed") {
1291
- session.close();
1292
- } else {
1293
- terminals.set(req.id, session);
1294
- sessions.add(session.pid);
1295
- reply(req.id, { started: session.pid });
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
- try {
1298
- return { result: await session.exited };
1299
- } finally {
1300
- terminals.delete(req.id);
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
- async function spawn(req, l) {
1304
- const out = (fd) => (bytes) => reply(req.id, { fd, bytes: bytes.slice() });
1305
- if (req.options.pgid !== void 0) joinable(l, req.options.pgid, (sid) => sessions.has(sid));
1306
- let leader;
1307
- const result = await l.run(req.argv, {
1308
- ...req.options,
1309
- collect: false,
1310
- onStdout: out(1),
1311
- onStderr: out(2),
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
- async function handle(req) {
1325
- if (host.scope === "transport" && req.op !== "detach") {
1326
- throw fsError("EPERM", `${req.op}: this client may only use the network transport`);
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
- const l = await host.launcher();
1329
- switch (req.op) {
1330
- case "spawn":
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
- port.addEventListener("message", (event) => {
1381
- const data = event.data;
1382
- if (state === "closed") return;
1383
- if (data.hello) {
1384
- if (state === "new") {
1385
- hello(data.hello).catch((err) => {
1386
- send({ hello: { protocol: PROTOCOL, error: String(err?.message ?? err) } });
1387
- detach();
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
- if (state !== "open") return;
1393
- if (data.net !== void 0) {
1394
- net?.answer(data);
1395
- return;
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
- const req = data;
1398
- handle(req).then(
1399
- ({ result, transfer }) => {
1400
- reply(req.id, { result }, transfer);
1401
- if (req.op === "detach") detach(req.kill === true);
1402
- },
1403
- (err) => {
1404
- const code = err?.code;
1405
- reply(req.id, {
1406
- error: err instanceof Error ? err.message : String(err),
1407
- ...typeof code === "string" ? { code } : {}
1408
- });
1409
- }
1410
- );
1411
- });
1412
- port.addEventListener("close", () => detach());
1413
- port.start?.();
1414
- return { detach, closed: ended.promise };
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 translate(err, path) {
1435
- if (typeof err?.code === "string") return err;
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
- var OpfsFs = class {
1463
- root;
1464
- meta;
1465
- dirs = /* @__PURE__ */ new Map();
1466
- checked = /* @__PURE__ */ new WeakMap();
1467
- channel;
1468
- constructor(root, meta = new MemoryMeta(), channel) {
1469
- this.root = root;
1470
- this.meta = meta;
1471
- this.channel = channel === void 0 ? void 0 : new BroadcastChannel(channel);
1472
- if (this.channel) {
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
- changed(...paths) {
1480
- for (const path of paths) this.forget(path);
1481
- this.channel?.postMessage(paths);
1520
+ activate(addr, start) {
1521
+ this.activators.set(keyOf(addr), start);
1482
1522
  }
1483
- resolvePath(base, path) {
1484
- return resolvePath(base, path);
1523
+ socket(domain) {
1524
+ return new KernelSocket(this, domain);
1485
1525
  }
1486
- async walk(parts, from, start) {
1487
- let dir = start;
1488
- for (let at = from; at < parts.length; at++) {
1489
- dir = await dir.getDirectoryHandle(parts[at]);
1490
- this.dirs.set(`/${parts.slice(0, at + 1).join("/")}`, dir);
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
- async dir(path) {
1496
- const parts = split(path);
1497
- let from = parts.length;
1498
- let cached;
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
- async current(path, dir) {
1515
- if (performance.now() - (this.checked.get(dir) ?? -CHECK_MS) < CHECK_MS) return true;
1516
- const where = await this.root.resolve(dir).catch(() => null);
1517
- if (where === null || `/${where.join("/")}` !== path) return false;
1518
- this.checked.set(dir, performance.now());
1519
- return true;
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
- forget(path) {
1522
- for (const key of [...this.dirs.keys()]) if (within(key, path)) this.dirs.delete(key);
1549
+ release(addr, socket) {
1550
+ const key = keyOf(addr);
1551
+ if (this.bound.get(key) === socket) this.bound.delete(key);
1523
1552
  }
1524
- async inDir(path, op) {
1525
- const dir = await this.dir(path);
1526
- try {
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
- async child(parent, name2, kind) {
1536
- return kind === "file" ? parent.getFileHandle(name2) : parent.getDirectoryHandle(name2);
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
- async parent(path) {
1539
- const parts = split(path);
1540
- const name2 = parts.pop();
1541
- if (name2 === void 0) throw fsError("EBUSY", path);
1542
- return [`/${parts.join("/")}`, name2];
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
- async handle(path) {
1545
- if (split(path).length === 0) return this.root;
1546
- const [dir, name2] = await this.parent(path);
1547
- const known = this.dirs.has(path);
1548
- return this.inDir(dir, async (parent) => {
1549
- const [first, second] = known ? ["directory", "file"] : ["file", "directory"];
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
- return await this.child(parent, name2, first);
1615
+ await socket.write(data);
1616
+ send({ ack: true });
1552
1617
  } catch (err) {
1553
- if (err?.name !== "TypeMismatchError") throw err;
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
- real(path) {
1561
- return this.handle(path).then(
1562
- () => true,
1563
- () => false
1564
- );
1565
- }
1566
- async linkAlong(path) {
1567
- for (const entry of await this.meta.get(prefixes(path))) {
1568
- if (entry?.link === void 0) continue;
1569
- if (!await this.real(entry.path) && await this.real(parentOf2(entry.path))) return entry;
1570
- }
1571
- return void 0;
1572
- }
1573
- async locate(path, follow) {
1574
- let current = path;
1575
- for (let hops = 0; hops <= MAX_LINKS2; hops++) {
1576
- try {
1577
- return { path: current, handle: await this.handle(current) };
1578
- } catch (err) {
1579
- if (!missing(err)) throw err;
1580
- const link2 = await this.linkAlong(current);
1581
- if (!link2) throw err;
1582
- if (link2.path === current && !follow) return { path: current, link: link2 };
1583
- const rest = current.slice(link2.path.length);
1584
- current = resolvePath(parentOf2(link2.path), `${link2.link}${rest}`);
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
- throw fsError("ELOOP", path);
1588
- }
1589
- mode(path, directory, meta) {
1590
- const fallback = directory || EXECUTABLE.test(path) ? 493 : 420;
1591
- return (directory ? S_IFDIR : S_IFREG) | (meta?.mode ?? fallback);
1592
- }
1593
- async statOf(path, handle, meta) {
1594
- const directory = handle.kind === "directory";
1595
- const file = directory ? void 0 : await handle.getFile();
1596
- const modified = file?.lastModified ?? 0;
1597
- const mtime = meta?.mtimeMs !== void 0 && meta.mtimeFor === modified ? meta.mtimeMs : modified;
1598
- return {
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
- async readFile(path) {
1656
- return new TextDecoder().decode(await this.readFileBuffer(path));
1657
- }
1658
- async writeTarget(path) {
1659
- try {
1660
- return [(await this.locate(path, true)).path, true];
1661
- } catch (err) {
1662
- if (!missing(err)) throw err;
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
- const [entry] = await this.meta.get([path]);
1665
- const wanted = entry?.link === void 0 ? path : resolvePath(parentOf2(path), entry.link);
1666
- return [resolvePath((await this.locate(parentOf2(wanted), true)).path, baseOf(wanted)), false];
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
- async writeFile(path, content) {
1669
- const [target, existed] = await this.writeTarget(path);
1670
- const [dir, name2] = await this.parent(target);
1671
- let file;
1672
- try {
1673
- file = await this.inDir(dir, (parent) => parent.getFileHandle(name2, { create: true }));
1674
- } catch (err) {
1675
- const code = err?.name === "TypeMismatchError" ? "EISDIR" : null;
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
- if (!existed && content.length === 0) return;
1679
- const writable = await file.createWritable();
1680
- await writable.write(content);
1681
- await writable.close();
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
- async listing(path) {
1684
- const found = await this.locate(path, true);
1685
- const handle = found.handle;
1686
- if (handle.kind !== "directory") throw fsError("ENOTDIR", path);
1687
- const children2 = /* @__PURE__ */ new Map();
1688
- for await (const child of handle.values()) children2.set(child.name, child);
1689
- for (const link2 of await this.meta.links(found.path)) {
1690
- const name2 = baseOf(link2.path);
1691
- if (!children2.has(name2)) children2.set(name2, link2);
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
- return [found.path, [...children2].sort(([a], [b]) => a < b ? -1 : 1)];
1694
- }
1695
- async readdir(path) {
1696
- return (await this.listing(path))[1].map(([name2]) => name2);
1697
- }
1698
- async readdirStat(path) {
1699
- const [dir, children2] = await this.listing(path);
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
- async mkdir(path, options = {}) {
1711
- let found;
1712
- try {
1713
- found = await this.locate(path, true);
1714
- } catch (err) {
1715
- if (!missing(err)) throw err;
1716
- }
1717
- if (found) {
1718
- const directory = found.handle?.kind === "directory";
1719
- if (options.recursive && directory) return;
1720
- throw fsError(options.recursive ? "ENOTDIR" : "EEXIST", path);
1721
- }
1722
- const [entry] = await this.meta.get([path]);
1723
- if (entry?.link !== void 0) throw fsError("EEXIST", path);
1724
- if (options.recursive) await this.mkdir(parentOf2(path), options);
1725
- const parent = await this.locate(parentOf2(path), true);
1726
- if (parent.handle?.kind !== "directory") throw fsError("ENOTDIR", path);
1727
- await parent.handle.getDirectoryHandle(baseOf(path), { create: true });
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 rm(path, options = {}) {
1730
- let found;
1731
- try {
1732
- found = await this.locate(path, false);
1733
- } catch (err) {
1734
- const error = translate(err, path);
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
- if (found.handle) {
1739
- const [dir, name2] = await this.parent(found.path);
1740
- try {
1741
- await this.inDir(
1742
- dir,
1743
- (parent) => parent.removeEntry(name2, { recursive: options.recursive === true })
1744
- );
1745
- } catch (err) {
1746
- throw translate(err, path);
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
- const targetStat = target && await this.describe(target);
1765
- if (targetStat?.isDirectory && !sourceStat.isDirectory) throw fsError("EISDIR", to);
1766
- if (targetStat && !targetStat.isDirectory && sourceStat.isDirectory) {
1767
- throw fsError("ENOTDIR", to);
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 (source.link) {
1780
- if (target?.handle) await this.rm(destination);
1781
- await this.meta.move(source.path, destination, carry);
1782
- return;
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
- const dir = parent.handle;
1785
- const name2 = baseOf(destination);
1786
- const swap = targetStat?.isDirectory === true;
1787
- const staging = swap ? `.${name2}.${crypto.randomUUID()}` : name2;
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 explicitTimes(path) {
1809
- const timed = [];
1810
- for (const entry of await this.meta.under(path)) {
1811
- if (entry.mtimeFor === void 0 || entry.link !== void 0) continue;
1812
- const current = await this.modified(entry.path).catch(() => void 0);
1813
- if (current === entry.mtimeFor) timed.push(entry.path.slice(path.length));
1814
- }
1815
- return timed;
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 restamp(path, timed) {
1818
- for (const rest of timed) {
1819
- const mtimeFor = await this.modified(path + rest);
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
- async place(handle, dir, name2) {
1824
- const moved2 = await handle.move?.(dir, name2).then(
1825
- () => true,
1826
- () => false
1827
- );
1828
- if (moved2) return true;
1829
- try {
1830
- await this.copy(handle, dir, name2);
1831
- } catch (err) {
1832
- if (handle.kind === "directory") await dir.removeEntry(name2, { recursive: true });
1833
- throw translate(err, name2);
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
- async copy(handle, dir, name2) {
1838
- if (handle.kind === "file") {
1839
- const writable = await (await dir.getFileHandle(name2, { create: true })).createWritable();
1840
- await writable.write(await handle.getFile());
1841
- await writable.close();
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
- const target = await dir.getDirectoryHandle(name2, { create: true });
1845
- for await (const child of handle.values()) await this.copy(child, target, child.name);
1846
- }
1847
- async symlink(target, path) {
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
- return removed;
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 json(value) {
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 json(await fs.exists(path));
5369
+ return json2(await fs.exists(path));
4727
5370
  case "stat":
4728
- return json(statJson(await fs.stat(path)));
5371
+ return json2(statJson(await fs.stat(path)));
4729
5372
  case "lstat":
4730
- return json(statJson(await fs.lstat(path)));
5373
+ return json2(statJson(await fs.lstat(path)));
4731
5374
  case "readdir":
4732
- return json(await fs.readdir(path));
5375
+ return json2(await fs.readdir(path));
4733
5376
  case "readdir-stat":
4734
- return json(await readdirStat(fs, path));
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 json(await fs.readlink(path));
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 json(await entry.statfs?.(path) ?? await storageUsage());
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 json2 = (value) => ({ ok: true, kind: "json", json: value });
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, json2(value));
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 json2(ctx.ops.submit(req.req));
5738
+ return json3(ctx.ops.submit(req.req));
5096
5739
  case "async-wait":
5097
- return json2(await ctx.ops.wait(req.timeoutMs, ctx.blocking()));
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 json2(ctx.ops.resolve(req.value));
5744
+ return json3(ctx.ops.resolve(req.value));
5102
5745
  case "async-hold":
5103
- return json2(ctx.ops.hold());
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 json3 = (value) => ({ ok: true, kind: "json", json: value });
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 json3(ctx.fds.install(ctx.ptys.open().master, 3));
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 json3(fd);
6286
+ return json4(fd);
5644
6287
  }
5645
6288
  case "pty-number":
5646
- return json3(masterOf(ctx, req.fd).index);
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 json3(ctx.ptys?.numbers() ?? []);
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 = (json4) => json4 === void 0 ? { ok: true, kind: "void" } : { ok: true, kind: "json", json: json4 };
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(field) {
7701
- return field.replace(
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 field = name2();
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: field, ...l };
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 field = r.name();
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] ??= {})[field] = type;
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.env = { ...networkEnv(), ...options.env };
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 fs = new OpfsFs(root, meta, dirsChannel(name2));
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),