@ai-ecoverse/slicc-kernel 1.15.1 → 1.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -76,9 +76,9 @@ var IndexedDbMeta = class {
76
76
  if (trees.some((tree) => under(key, tree))) this.cache.delete(key);
77
77
  }
78
78
  }
79
- changed(invalidation) {
80
- this.drop(invalidation);
81
- this.channel.postMessage(invalidation);
79
+ changed(invalidation2) {
80
+ this.drop(invalidation2);
81
+ this.channel.postMessage(invalidation2);
82
82
  }
83
83
  transaction(mode) {
84
84
  const transaction = this.db.transaction(META_STORE, mode);
@@ -2952,9 +2952,9 @@ var RemoteTransport = class {
2952
2952
  var NAME = /^[A-Za-z0-9._+-]+$/;
2953
2953
  var ENV_KEY = /^[A-Za-z_][A-Za-z0-9_]*$/;
2954
2954
  var PACKAGE = /\$\{package\}/g;
2955
- function inside(pkg, rel) {
2956
- if (typeof rel !== "string" || rel === "") return void 0;
2957
- const clean = rel.replace(/^\.\//, "");
2955
+ function inside(pkg, rel2) {
2956
+ if (typeof rel2 !== "string" || rel2 === "") return void 0;
2957
+ const clean = rel2.replace(/^\.\//, "");
2958
2958
  if (clean.startsWith("/") || clean.split("/").includes("..")) return void 0;
2959
2959
  return `${pkg}/${clean}`;
2960
2960
  }
@@ -3529,7 +3529,8 @@ function statJson(s) {
3529
3529
  atimeMs: s.atime.getTime(),
3530
3530
  ctimeMs: s.ctime.getTime(),
3531
3531
  ino: s.ino,
3532
- ...s.dev !== void 0 ? { dev: s.dev } : {}
3532
+ ...s.dev !== void 0 ? { dev: s.dev } : {},
3533
+ ...s.readonly ? { readonly: true } : {}
3533
3534
  };
3534
3535
  }
3535
3536
  async function readdirStat(fs, dir) {
@@ -5091,6 +5092,20 @@ var VfsNodes = class {
5091
5092
  }
5092
5093
  }
5093
5094
  };
5095
+ async function refuseReadonly(fs, path, flags, opts = {}) {
5096
+ const writes = (flags & O_ACCMODE) !== 0 || opts.truncate === true;
5097
+ if (!writes && !opts.create || !fs.stat) return;
5098
+ const stat = (p) => fs.stat?.(p).catch((err) => {
5099
+ const code = err?.code;
5100
+ if (isMissing(err) || typeof code !== "string") return void 0;
5101
+ throw err;
5102
+ });
5103
+ const parent = path.slice(0, path.lastIndexOf("/")) || "/";
5104
+ const own = await stat(path);
5105
+ if (own && !writes) return;
5106
+ const st = own ?? await stat(parent);
5107
+ if (st?.readonly) throw new KernelError("EROFS");
5108
+ }
5094
5109
  function vfsFile(fs, opts, nodes) {
5095
5110
  const access = opts.flags & O_ACCMODE;
5096
5111
  const readable = access !== O_WRONLY;
@@ -5361,6 +5376,10 @@ var WasmProcess = class {
5361
5376
  if (e instanceof KernelError || e instanceof SpawnError) {
5362
5377
  return { ok: false, errno: e.code, message: e.code };
5363
5378
  }
5379
+ const code = e?.code;
5380
+ if (typeof code === "string" && /^E[A-Z]+$/.test(code)) {
5381
+ return { ok: false, errno: code, message: code };
5382
+ }
5364
5383
  throw e;
5365
5384
  }
5366
5385
  }
@@ -5433,6 +5452,7 @@ var WasmProcess = class {
5433
5452
  case "fd-poll":
5434
5453
  return { ok: true, kind: "json", json: pollFile(this.fds.get(req.fd).file) };
5435
5454
  case "fd-open-vfs":
5455
+ await refuseReadonly(this.options.fs, req.path, req.flags, req);
5436
5456
  return { ok: true, kind: "json", json: this.fds.install(this.openVfsFile(req), 3) };
5437
5457
  case "fd-info":
5438
5458
  return { ok: true, kind: "json", json: this.fdInfo(req.fd) };
@@ -6159,6 +6179,57 @@ function serveFilesystem(port, handlers, capabilities = {}) {
6159
6179
  return { invalidate: (paths) => reply({ invalidate: paths }) };
6160
6180
  }
6161
6181
 
6182
+ // src/mount/medium.ts
6183
+ var EMPTY_ROOT = { kind: "directory", size: 0, mtime: 0, mode: 16877 };
6184
+ var absent = () => fsError2("ENOMEDIUM", "No medium found");
6185
+ function mediumSlot(lost, onLost) {
6186
+ let current;
6187
+ function wrap(pick) {
6188
+ return async (...args) => {
6189
+ const medium = current;
6190
+ if (!medium) throw absent();
6191
+ try {
6192
+ return await pick(medium.handlers)(...args);
6193
+ } catch (err) {
6194
+ if (!await lost(err, medium.key)) throw err;
6195
+ if (current === medium) {
6196
+ current = void 0;
6197
+ onLost(medium.key);
6198
+ }
6199
+ throw absent();
6200
+ }
6201
+ };
6202
+ }
6203
+ const getattr = wrap((h) => h.getattr);
6204
+ const readdir = wrap((h) => h.readdir);
6205
+ const statfs = wrap((h) => async () => await h.statfs?.() ?? null);
6206
+ return {
6207
+ handlers: {
6208
+ getattr: (path) => current || path !== "/" ? getattr(path) : Promise.resolve(EMPTY_ROOT),
6209
+ readdir: (path) => current || path !== "/" ? readdir(path) : Promise.resolve([]),
6210
+ open: wrap((h) => h.open),
6211
+ read: wrap((h) => h.read),
6212
+ write: wrap((h) => h.write),
6213
+ release: wrap((h) => h.release),
6214
+ mkdir: wrap((h) => h.mkdir),
6215
+ rmdir: wrap((h) => h.rmdir),
6216
+ unlink: wrap((h) => h.unlink),
6217
+ rename: wrap((h) => h.rename),
6218
+ symlink: wrap((h) => h.symlink),
6219
+ readlink: wrap((h) => h.readlink),
6220
+ setattr: wrap((h) => h.setattr),
6221
+ statfs: () => current ? statfs() : Promise.resolve(null)
6222
+ },
6223
+ present: () => current !== void 0,
6224
+ insert(key, handlers) {
6225
+ current = { key, handlers };
6226
+ },
6227
+ eject() {
6228
+ current = void 0;
6229
+ }
6230
+ };
6231
+ }
6232
+
6162
6233
  // src/mount/fsa.ts
6163
6234
  var FSA_CAPABILITIES = { listingStats: true };
6164
6235
  var codeOf = (err) => err?.code;
@@ -6246,7 +6317,6 @@ function kernelFsHandlers(fs) {
6246
6317
  }
6247
6318
  };
6248
6319
  }
6249
- var EMPTY_ROOT = { kind: "directory", size: 0, mtime: 0, mode: 16877 };
6250
6320
  function granted(handle) {
6251
6321
  if (!handle.queryPermission) return Promise.resolve(true);
6252
6322
  return handle.queryPermission({ mode: "readwrite" }).then(
@@ -6255,48 +6325,345 @@ function granted(handle) {
6255
6325
  );
6256
6326
  }
6257
6327
  function removableMedium(onLost, open = (handle) => new OpfsFs(handle)) {
6258
- let current;
6259
- const absent = () => fsError2("ENOMEDIUM", "No medium found");
6260
- function wrap(pick) {
6261
- return async (...args) => {
6262
- const medium = current;
6263
- if (!medium) throw absent();
6264
- try {
6265
- return await pick(medium.handlers)(...args);
6266
- } catch (err) {
6267
- if (!denied(err) || await granted(medium.handle)) throw err;
6268
- if (current === medium) {
6269
- current = void 0;
6270
- onLost(medium.handle);
6328
+ const slot = mediumSlot(
6329
+ async (err, handle) => denied(err) && !await granted(handle),
6330
+ onLost
6331
+ );
6332
+ return {
6333
+ handlers: slot.handlers,
6334
+ present: slot.present,
6335
+ insert: (handle) => slot.insert(handle, kernelFsHandlers(open(handle))),
6336
+ eject: slot.eject
6337
+ };
6338
+ }
6339
+
6340
+ // src/mount/hostfs.ts
6341
+ var HOSTFS_IO = 16 * 1024 * 1024;
6342
+ var codeOf2 = (err) => err?.code;
6343
+ var unreachable = (err) => codeOf2(err) === "ENOTCONN";
6344
+ function rel(path) {
6345
+ return path.replace(/^\/+/, "");
6346
+ }
6347
+ var HostfsClient = class {
6348
+ grant;
6349
+ regrant;
6350
+ fetch;
6351
+ constructor(grant, regrant, fetch2) {
6352
+ this.grant = grant;
6353
+ this.regrant = regrant;
6354
+ this.fetch = fetch2;
6355
+ }
6356
+ get capabilities() {
6357
+ return this.grant.capabilities ?? {};
6358
+ }
6359
+ get token() {
6360
+ return this.grant.token;
6361
+ }
6362
+ get maxIo() {
6363
+ return this.capabilities.maxIo ?? HOSTFS_IO;
6364
+ }
6365
+ async send(path, init, handle = false, retried = false) {
6366
+ let response;
6367
+ try {
6368
+ response = await this.fetch(`${this.grant.url}${path}`, {
6369
+ ...init,
6370
+ credentials: "omit",
6371
+ headers: {
6372
+ ...init.headers,
6373
+ "X-Hostfs-Token": this.grant.token
6271
6374
  }
6272
- throw absent();
6375
+ });
6376
+ } catch (err) {
6377
+ throw fsError2("ENOTCONN", `the host proxy is unreachable: ${err.message}`);
6378
+ }
6379
+ if (response.ok) return response;
6380
+ if (response.headers.get("X-Proxy-Error")) {
6381
+ await response.body?.cancel();
6382
+ if (retried || response.status !== 403) {
6383
+ throw fsError2("EACCES", `the host proxy refused this folder (${response.status})`);
6273
6384
  }
6274
- };
6385
+ this.grant = await this.regrant();
6386
+ if (handle)
6387
+ throw fsError2("ESTALE", "the folder was granted again, so its open files are gone");
6388
+ return this.send(path, init, false, true);
6389
+ }
6390
+ const errno = response.headers.get("X-Hostfs-Errno") ?? "EIO";
6391
+ const body = await response.json().catch(() => ({}));
6392
+ throw fsError2(errno, typeof body.message === "string" ? body.message : errno);
6393
+ }
6394
+ async call(op, body = {}, handle = false) {
6395
+ const response = await this.send(
6396
+ "/api/hostfs",
6397
+ {
6398
+ method: "POST",
6399
+ headers: { "Content-Type": "application/json" },
6400
+ body: JSON.stringify({ op, ...body })
6401
+ },
6402
+ handle
6403
+ );
6404
+ return await response.json();
6405
+ }
6406
+ async read(fh, offset, size, ifMatch) {
6407
+ const response = await this.send(
6408
+ "/api/hostfs",
6409
+ {
6410
+ method: "POST",
6411
+ headers: { "Content-Type": "application/json" },
6412
+ body: JSON.stringify({ op: "read", fh, offset, size, ...ifMatch ? { ifMatch } : {} })
6413
+ },
6414
+ true
6415
+ );
6416
+ return new Uint8Array(await response.arrayBuffer());
6417
+ }
6418
+ async write(fh, offset, bytes) {
6419
+ const response = await this.send(
6420
+ "/api/hostfs/write",
6421
+ {
6422
+ method: "PUT",
6423
+ headers: {
6424
+ "Content-Type": "application/octet-stream",
6425
+ "X-Hostfs-Request": JSON.stringify({ fh, offset })
6426
+ },
6427
+ body: bytes
6428
+ },
6429
+ true
6430
+ );
6431
+ await response.body?.cancel();
6432
+ }
6433
+ async watch(signal, onEvent, onOpen = () => void 0) {
6434
+ const response = await this.send("/api/hostfs/watch", { method: "POST", signal });
6435
+ onOpen();
6436
+ const reader = response.body.getReader();
6437
+ const decoder2 = new TextDecoder();
6438
+ let buffered = "";
6439
+ try {
6440
+ for (; ; ) {
6441
+ const { done: done4, value } = await reader.read();
6442
+ if (done4) return;
6443
+ buffered += decoder2.decode(value, { stream: true });
6444
+ let end = buffered.indexOf("\n");
6445
+ while (end >= 0) {
6446
+ const line = buffered.slice(0, end).trim();
6447
+ buffered = buffered.slice(end + 1);
6448
+ if (line) onEvent(JSON.parse(line));
6449
+ end = buffered.indexOf("\n");
6450
+ }
6451
+ }
6452
+ } catch (err) {
6453
+ if (signal.aborted) return;
6454
+ throw fsError2("ENOTCONN", `the host proxy's watch broke: ${err.message}`);
6455
+ } finally {
6456
+ reader.releaseLock();
6457
+ }
6458
+ }
6459
+ };
6460
+ function hostfsHandlers(current) {
6461
+ const handles = /* @__PURE__ */ new Map();
6462
+ let nextFh = 0;
6463
+ const fresh = (remote) => {
6464
+ if (remote.client.token === remote.token) return remote;
6465
+ throw fsError2("ESTALE", "the folder was granted again, so this open file is gone");
6466
+ };
6467
+ const live = (fh) => {
6468
+ const remote = handles.get(fh);
6469
+ if (!remote) throw fsError2("EBADF", `handle ${fh}`);
6470
+ return fresh(remote);
6471
+ };
6472
+ const call = (op, body = {}) => current().call(op, body);
6473
+ async function writable(client, path, flags) {
6474
+ const opening = () => client.call("open", { path: rel(path), ...flags });
6475
+ try {
6476
+ return { opened: await opening(), restore: void 0 };
6477
+ } catch (err) {
6478
+ if (!flags.write || codeOf2(err) !== "EACCES") throw err;
6479
+ const mode = (await client.call("stat", { path: rel(path) })).mode ?? 0;
6480
+ if (mode & 128) throw err;
6481
+ const restore = { path: rel(path), mode };
6482
+ await client.call("setattr", { path: rel(path), mode: mode | 128 });
6483
+ const opened2 = await opening().catch(async (again) => {
6484
+ await client.call("setattr", restore);
6485
+ throw again;
6486
+ });
6487
+ return { opened: opened2, restore };
6488
+ }
6275
6489
  }
6276
- const getattr = wrap((h) => h.getattr);
6277
- const readdir = wrap((h) => h.readdir);
6278
6490
  return {
6279
- handlers: {
6280
- getattr: (path) => current || path !== "/" ? getattr(path) : Promise.resolve(EMPTY_ROOT),
6281
- readdir: (path) => current || path !== "/" ? readdir(path) : Promise.resolve([]),
6282
- open: wrap((h) => h.open),
6283
- read: wrap((h) => h.read),
6284
- write: wrap((h) => h.write),
6285
- release: wrap((h) => h.release),
6286
- mkdir: wrap((h) => h.mkdir),
6287
- rmdir: wrap((h) => h.rmdir),
6288
- unlink: wrap((h) => h.unlink),
6289
- rename: wrap((h) => h.rename),
6290
- symlink: wrap((h) => h.symlink),
6291
- readlink: wrap((h) => h.readlink),
6292
- setattr: wrap((h) => h.setattr)
6491
+ getattr: async (path) => await call("stat", { path: rel(path) }),
6492
+ async readdir(path) {
6493
+ const { entries } = await call(
6494
+ "list",
6495
+ { path: rel(path) }
6496
+ );
6497
+ return entries.map(({ name: name2, attr }) => ({ name: name2, kind: attr.kind, attr }));
6293
6498
  },
6294
- present: () => current !== void 0,
6295
- insert(handle) {
6296
- current = { handle, handlers: kernelFsHandlers(open(handle)) };
6499
+ async open(path, flags) {
6500
+ const client = current();
6501
+ const token = client.token;
6502
+ const { opened: opened2, restore } = await writable(client, path, flags);
6503
+ handles.set(++nextFh, {
6504
+ client,
6505
+ token,
6506
+ fh: opened2.fh,
6507
+ etag: flags.write ? void 0 : opened2.attr?.etag,
6508
+ restore
6509
+ });
6510
+ return nextFh;
6297
6511
  },
6298
- eject() {
6299
- current = void 0;
6512
+ async read(fh, offset, size) {
6513
+ const remote = live(fh);
6514
+ return remote.client.read(
6515
+ remote.fh,
6516
+ offset,
6517
+ Math.min(size, remote.client.maxIo),
6518
+ remote.etag
6519
+ );
6520
+ },
6521
+ async write(fh, offset, bytes) {
6522
+ const remote = live(fh);
6523
+ const most = remote.client.maxIo;
6524
+ for (let at = 0; at < bytes.length; at += most) {
6525
+ await remote.client.write(remote.fh, offset + at, bytes.subarray(at, at + most));
6526
+ }
6527
+ },
6528
+ async release(fh) {
6529
+ const remote = handles.get(fh);
6530
+ if (!remote) throw fsError2("EBADF", `handle ${fh}`);
6531
+ handles.delete(fh);
6532
+ try {
6533
+ await fresh(remote).client.call("release", { fh: remote.fh }, true);
6534
+ } finally {
6535
+ if (remote.restore) await current().call("setattr", remote.restore);
6536
+ }
6537
+ },
6538
+ mkdir: async (path) => void await call("mkdir", { path: rel(path) }),
6539
+ rmdir: async (path) => void await call("rmdir", { path: rel(path) }),
6540
+ unlink: async (path) => void await call("unlink", { path: rel(path) }),
6541
+ rename: async (from, to) => void await call("rename", { from: rel(from), to: rel(to) }),
6542
+ symlink: async (target, path) => void await call("symlink", { target, path: rel(path) }),
6543
+ readlink: async (path) => (await call("readlink", { path: rel(path) })).target,
6544
+ setattr: async (path, change) => void await call("setattr", { path: rel(path), ...change }),
6545
+ statfs: () => call("statfs")
6546
+ };
6547
+ }
6548
+ function sleep(ms, signal) {
6549
+ return new Promise((resolve) => {
6550
+ const done4 = () => {
6551
+ clearTimeout(timer);
6552
+ signal.removeEventListener("abort", done4);
6553
+ resolve();
6554
+ };
6555
+ const timer = setTimeout(done4, ms);
6556
+ signal.addEventListener("abort", done4);
6557
+ });
6558
+ }
6559
+ function keepWatching(loop, signal) {
6560
+ const silenceMs = loop.silenceMs ?? 45e3;
6561
+ const [first, most] = loop.backoffMs ?? [1e3, 3e4];
6562
+ const ready2 = Promise.withResolvers();
6563
+ let wait = first;
6564
+ let up = false;
6565
+ let current;
6566
+ async function session() {
6567
+ const session2 = new AbortController();
6568
+ current = session2;
6569
+ const stop = () => session2.abort();
6570
+ signal.addEventListener("abort", stop);
6571
+ let timer;
6572
+ const quiet = () => {
6573
+ clearTimeout(timer);
6574
+ timer = setTimeout(stop, silenceMs);
6575
+ };
6576
+ let opened2 = false;
6577
+ try {
6578
+ const client = await loop.connect();
6579
+ await client.watch(
6580
+ session2.signal,
6581
+ (event) => {
6582
+ quiet();
6583
+ if (!event.ping) loop.event(event);
6584
+ },
6585
+ () => {
6586
+ quiet();
6587
+ opened2 = true;
6588
+ up = true;
6589
+ loop.up(client);
6590
+ ready2.resolve();
6591
+ }
6592
+ );
6593
+ return opened2;
6594
+ } catch {
6595
+ return opened2;
6596
+ } finally {
6597
+ clearTimeout(timer);
6598
+ signal.removeEventListener("abort", stop);
6599
+ }
6600
+ }
6601
+ void (async () => {
6602
+ while (!signal.aborted) {
6603
+ const began = Date.now();
6604
+ const opened2 = await session();
6605
+ if (signal.aborted) return;
6606
+ if (opened2 && Date.now() - began >= first) {
6607
+ wait = first;
6608
+ continue;
6609
+ }
6610
+ if (!opened2 && up) {
6611
+ up = false;
6612
+ loop.down();
6613
+ }
6614
+ ready2.resolve();
6615
+ await sleep(wait, signal);
6616
+ wait = Math.min(wait * 2, most);
6617
+ }
6618
+ })();
6619
+ return { ready: ready2.promise, kick: () => current?.abort() };
6620
+ }
6621
+ function invalidation(event) {
6622
+ return event.paths ?? true;
6623
+ }
6624
+ async function openHostfs(spec, hook, fetch2, timing = {}) {
6625
+ const readonly2 = spec.options?.ro !== void 0;
6626
+ const { port1, port2 } = new MessageChannel();
6627
+ const slot = mediumSlot(
6628
+ async (err) => unreachable(err),
6629
+ () => watching.kick()
6630
+ );
6631
+ const stop = new AbortController();
6632
+ const grant = () => hook(spec.source, { readonly: readonly2 });
6633
+ let active;
6634
+ const handlers = hostfsHandlers(() => active);
6635
+ const served = serveFilesystem(port2, slot.handlers, {
6636
+ symlinks: true,
6637
+ chmod: true,
6638
+ listingStats: true,
6639
+ maxIo: HOSTFS_IO,
6640
+ ...readonly2 ? { readonly: true } : {}
6641
+ });
6642
+ const watching = keepWatching(
6643
+ {
6644
+ connect: async () => new HostfsClient(await grant(), grant, fetch2),
6645
+ up(client) {
6646
+ active = client;
6647
+ slot.insert(client, handlers);
6648
+ served.invalidate(true);
6649
+ },
6650
+ event: (event) => served.invalidate(invalidation(event)),
6651
+ down() {
6652
+ slot.eject();
6653
+ served.invalidate(true);
6654
+ },
6655
+ ...timing
6656
+ },
6657
+ stop.signal
6658
+ );
6659
+ await watching.ready;
6660
+ return {
6661
+ port: port1,
6662
+ present: slot.present,
6663
+ dispose() {
6664
+ stop.abort();
6665
+ port1.close();
6666
+ port2.close();
6300
6667
  }
6301
6668
  };
6302
6669
  }
@@ -6396,6 +6763,7 @@ var DriverConnection = class _DriverConnection {
6396
6763
 
6397
6764
  // src/mount/mount-fs.ts
6398
6765
  var MAX_LINKS4 = 40;
6766
+ var STALE_RETRIES = 3;
6399
6767
  var DEFAULT_IO = 8 * 1024 * 1024;
6400
6768
  var UNITS = { "": 1, k: 1024, m: 1024 ** 2, g: 1024 ** 3, t: 1024 ** 4 };
6401
6769
  function parseSize(text) {
@@ -6409,7 +6777,8 @@ function heldUnder(held, target) {
6409
6777
  return false;
6410
6778
  }
6411
6779
  var parentOf4 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
6412
- function statOf(attr, path, dev) {
6780
+ var readonly = (mount) => mount.caps.readonly === true;
6781
+ function statOf(attr, path, mount) {
6413
6782
  const mtime = new Date(attr.mtime);
6414
6783
  const fallback = attr.kind === "directory" ? 16877 : attr.kind === "symlink" ? 41471 : 33188;
6415
6784
  return {
@@ -6422,7 +6791,8 @@ function statOf(attr, path, dev) {
6422
6791
  atime: mtime,
6423
6792
  ctime: mtime,
6424
6793
  ino: attr.ino ?? inodeOf(path),
6425
- dev
6794
+ dev: mount.dev,
6795
+ ...readonly(mount) ? { readonly: true } : {}
6426
6796
  };
6427
6797
  }
6428
6798
  function concat4(chunks, total) {
@@ -6462,8 +6832,8 @@ var MountTable = class {
6462
6832
  best = mount;
6463
6833
  }
6464
6834
  if (!best) return void 0;
6465
- const rel = best.target === "/" ? path : path.slice(best.target.length);
6466
- return { mount: best, rel: rel || "/" };
6835
+ const rel2 = best.target === "/" ? path : path.slice(best.target.length);
6836
+ return { mount: best, rel: rel2 || "/" };
6467
6837
  }
6468
6838
  async mount(spec, fs) {
6469
6839
  const target = normalizePath(spec.target);
@@ -6568,20 +6938,20 @@ var MountTable = class {
6568
6938
  mount.lists.delete(parentOf4(path));
6569
6939
  for (const key of [...mount.lists.keys()]) if (within3(key, path)) mount.lists.delete(key);
6570
6940
  }
6571
- async getattr(mount, path, rel) {
6941
+ async getattr(mount, path, rel2) {
6572
6942
  if (mount.conn.failure) throw mount.conn.failure;
6573
6943
  const cached = mount.attrs.get(path);
6574
6944
  if (cached && cached.expires > Date.now()) return cached.value;
6575
- const attr = await mount.conn.call({ op: "getattr", path: rel });
6945
+ const attr = await mount.conn.call({ op: "getattr", path: rel2 });
6576
6946
  const ttl = mount.caps.attrTtl ?? 1e3;
6577
6947
  if (ttl > 0) mount.attrs.set(path, { value: attr, expires: Date.now() + ttl });
6578
6948
  return attr;
6579
6949
  }
6580
- async readdir(mount, path, rel) {
6950
+ async readdir(mount, path, rel2) {
6581
6951
  if (mount.conn.failure) throw mount.conn.failure;
6582
6952
  const cached = mount.lists.get(path);
6583
6953
  if (cached && cached.expires > Date.now()) return cached.value;
6584
- const entries = await mount.conn.call({ op: "readdir", path: rel });
6954
+ const entries = await mount.conn.call({ op: "readdir", path: rel2 });
6585
6955
  const ttl = mount.caps.entryTtl ?? mount.caps.attrTtl ?? 1e3;
6586
6956
  if (ttl > 0) mount.lists.set(path, { value: entries, expires: Date.now() + ttl });
6587
6957
  return entries;
@@ -6608,7 +6978,7 @@ var MountFs = class {
6608
6978
  return this.table.locate(normalizePath(path));
6609
6979
  }
6610
6980
  writable(mount, path) {
6611
- if (mount.caps.readonly) throw errnoError("EROFS", path);
6981
+ if (readonly(mount)) throw errnoError("EROFS", path);
6612
6982
  }
6613
6983
  async follow(path, hops = 0, create = false) {
6614
6984
  const found = this.at(path);
@@ -6626,10 +6996,10 @@ var MountFs = class {
6626
6996
  const target = await found.mount.conn.call({ op: "readlink", path: found.rel });
6627
6997
  return this.follow(this.base.resolvePath(parentOf4(real), target), hops + 1, create);
6628
6998
  }
6629
- async open(mount, rel, write) {
6999
+ async open(mount, rel2, write) {
6630
7000
  return await mount.conn.call({
6631
7001
  op: "open",
6632
- path: rel,
7002
+ path: rel2,
6633
7003
  write,
6634
7004
  create: write,
6635
7005
  truncate: write,
@@ -6646,13 +7016,22 @@ var MountFs = class {
6646
7016
  const real = await this.follow(path);
6647
7017
  const found = this.at(real);
6648
7018
  if (!found) return this.base.readFileBuffer(real);
6649
- const { mount, rel } = found;
6650
- const attr = await this.table.getattr(mount, real, rel);
7019
+ for (let attempt = 1; ; attempt++) {
7020
+ try {
7021
+ return await this.readWhole(found.mount, real, found.rel);
7022
+ } catch (err) {
7023
+ if (err.code !== "ESTALE" || attempt >= STALE_RETRIES) throw err;
7024
+ this.table.forget(found.mount, real);
7025
+ }
7026
+ }
7027
+ }
7028
+ async readWhole(mount, real, rel2) {
7029
+ const attr = await this.table.getattr(mount, real, rel2);
6651
7030
  if (attr.kind === "directory") throw errnoError("EISDIR", real);
6652
7031
  if (mount.maxFile > 0 && attr.size > mount.maxFile) {
6653
7032
  throw errnoError("EFBIG", `${real} is larger than this mount's maxfile (${mount.maxFile})`);
6654
7033
  }
6655
- const fh = await this.open(mount, rel, false);
7034
+ const fh = await this.open(mount, rel2, false);
6656
7035
  const chunk2 = mount.caps.maxIo ?? DEFAULT_IO;
6657
7036
  const chunks = [];
6658
7037
  let total = 0;
@@ -6678,7 +7057,7 @@ var MountFs = class {
6678
7057
  const real = await this.follow(path, 0, true);
6679
7058
  const found = this.at(real);
6680
7059
  if (!found) return this.base.writeFile(real, content);
6681
- const { mount, rel } = found;
7060
+ const { mount, rel: rel2 } = found;
6682
7061
  this.writable(mount, real);
6683
7062
  const bytes = typeof content === "string" ? new TextEncoder().encode(content) : content;
6684
7063
  if (mount.maxFile > 0 && bytes.length > mount.maxFile) {
@@ -6687,8 +7066,17 @@ var MountFs = class {
6687
7066
  `${real} would be larger than this mount's maxfile (${mount.maxFile})`
6688
7067
  );
6689
7068
  }
7069
+ for (let attempt = 1; ; attempt++) {
7070
+ try {
7071
+ return await this.writeWhole(mount, real, rel2, bytes);
7072
+ } catch (err) {
7073
+ if (err.code !== "ESTALE" || attempt >= STALE_RETRIES) throw err;
7074
+ }
7075
+ }
7076
+ }
7077
+ async writeWhole(mount, real, rel2, bytes) {
6690
7078
  this.table.forget(mount, real);
6691
- const fh = await this.open(mount, rel, true);
7079
+ const fh = await this.open(mount, rel2, true);
6692
7080
  const chunk2 = mount.caps.maxIo ?? DEFAULT_IO;
6693
7081
  try {
6694
7082
  for (let offset = 0; offset < bytes.length; offset += chunk2) {
@@ -6719,7 +7107,7 @@ var MountFs = class {
6719
7107
  const found = this.at(path);
6720
7108
  if (!found) return this.base.lstat(path);
6721
7109
  const real = normalizePath(path);
6722
- return statOf(await this.table.getattr(found.mount, real, found.rel), real, found.mount.dev);
7110
+ return statOf(await this.table.getattr(found.mount, real, found.rel), real, found.mount);
6723
7111
  }
6724
7112
  async stat(path) {
6725
7113
  const real = await this.follow(path);
@@ -6746,7 +7134,7 @@ var MountFs = class {
6746
7134
  list.map(async (entry) => {
6747
7135
  const child = normalizePath(`${real}/${entry.name}`);
6748
7136
  if (entry.attr && mount.caps.listingStats) {
6749
- return [entry.name, statOf(entry.attr, child, mount.dev)];
7137
+ return [entry.name, statOf(entry.attr, child, mount)];
6750
7138
  }
6751
7139
  return [entry.name, await this.lstat(child).catch(() => null)];
6752
7140
  })
@@ -6755,16 +7143,16 @@ var MountFs = class {
6755
7143
  async mkdir(path, options = {}) {
6756
7144
  const found = this.at(path);
6757
7145
  if (!found) return this.base.mkdir(path, options);
6758
- const { mount, rel } = found;
7146
+ const { mount, rel: rel2 } = found;
6759
7147
  const real = normalizePath(path);
6760
7148
  this.writable(mount, real);
6761
7149
  if (!options.recursive) {
6762
- if (rel === "/") throw errnoError("EEXIST", real);
7150
+ if (rel2 === "/") throw errnoError("EEXIST", real);
6763
7151
  this.table.forget(mount, real);
6764
- await mount.conn.call({ op: "mkdir", path: rel });
7152
+ await mount.conn.call({ op: "mkdir", path: rel2 });
6765
7153
  return;
6766
7154
  }
6767
- const parts = rel.split("/").filter(Boolean);
7155
+ const parts = rel2.split("/").filter(Boolean);
6768
7156
  for (let n = 1; n <= parts.length; n++) {
6769
7157
  const sub = `/${parts.slice(0, n).join("/")}`;
6770
7158
  const full = normalizePath(`${mount.target}${sub}`);
@@ -6778,25 +7166,25 @@ var MountFs = class {
6778
7166
  async rm(path, options = {}) {
6779
7167
  const found = this.at(path);
6780
7168
  if (!found) return this.base.rm(path, options);
6781
- const { mount, rel } = found;
7169
+ const { mount, rel: rel2 } = found;
6782
7170
  const real = normalizePath(path);
6783
- if (rel === "/") throw errnoError("EBUSY", `${real} is a mount point`);
7171
+ if (rel2 === "/") throw errnoError("EBUSY", `${real} is a mount point`);
6784
7172
  this.writable(mount, real);
6785
7173
  let attr;
6786
7174
  try {
6787
- attr = await this.table.getattr(mount, real, rel);
7175
+ attr = await this.table.getattr(mount, real, rel2);
6788
7176
  } catch (err) {
6789
7177
  if (options.force && err.code === "ENOENT") return;
6790
7178
  throw err;
6791
7179
  }
6792
7180
  this.table.forget(mount, real);
6793
7181
  if (attr.kind === "directory" && options.recursive) {
6794
- for (const entry of await this.table.readdir(mount, real, rel)) {
7182
+ for (const entry of await this.table.readdir(mount, real, rel2)) {
6795
7183
  await this.rm(`${real}/${entry.name}`, { recursive: true, force: options.force ?? false });
6796
7184
  }
6797
7185
  this.table.forget(mount, real);
6798
7186
  }
6799
- await mount.conn.call({ op: attr.kind === "directory" ? "rmdir" : "unlink", path: rel });
7187
+ await mount.conn.call({ op: attr.kind === "directory" ? "rmdir" : "unlink", path: rel2 });
6800
7188
  this.table.forget(mount, real);
6801
7189
  }
6802
7190
  async rename(from, to) {
@@ -7258,6 +7646,7 @@ var Launcher = class {
7258
7646
  media;
7259
7647
  onMountPending;
7260
7648
  removable = /* @__PURE__ */ new Map();
7649
+ hostfs;
7261
7650
  inserted = /* @__PURE__ */ new Map();
7262
7651
  openFiles = /* @__PURE__ */ new Set();
7263
7652
  held = /* @__PURE__ */ new Set();
@@ -7275,6 +7664,7 @@ var Launcher = class {
7275
7664
  this.createDriverWorker = options.createDriverWorker;
7276
7665
  this.media = options.media ?? memoryMedia();
7277
7666
  this.onMountPending = options.onMountPending;
7667
+ this.hostfs = options;
7278
7668
  this.pnpmHome = options.env?.PNPM_HOME ?? PNPM_HOME;
7279
7669
  this.modulesDir = options.modules ?? "/node_modules";
7280
7670
  this.env = { ...networkEnv(), ...options.env };
@@ -7540,6 +7930,17 @@ var Launcher = class {
7540
7930
  }
7541
7931
  async driver(type, spec) {
7542
7932
  if (type === "fsa") return this.fsaDriver(spec);
7933
+ if (type === "hostfs") {
7934
+ const { hostfs, hostfsFetch, hostfsTiming } = this.hostfs;
7935
+ if (!hostfs)
7936
+ throw fsError("ENODEV", "this kernel has no hostfs hook (createKernel({ hostfs }))");
7937
+ return openHostfs(
7938
+ spec,
7939
+ hostfs,
7940
+ hostfsFetch ?? ((url, init) => fetch(url, init)),
7941
+ hostfsTiming
7942
+ );
7943
+ }
7543
7944
  if (type === "tmpfs") {
7544
7945
  const { port1, port2 } = new MessageChannel();
7545
7946
  serveFilesystem(port2, tmpfs(), { symlinks: true, chmod: true, attrTtl: 0 });
@@ -7796,6 +8197,7 @@ function serveKernel(port, deps) {
7796
8197
  ...remote ? { transport: remote } : {},
7797
8198
  ...media ? { media } : {},
7798
8199
  onMountPending: (medium) => port.postMessage({ medium }),
8200
+ ...req.hostfs ? { hostfs: askGrant } : {},
7799
8201
  caStore: caStore(deps.metadata ? name2 : false)
7800
8202
  });
7801
8203
  await started.prepare();
@@ -7829,7 +8231,25 @@ function serveKernel(port, deps) {
7829
8231
  onStderr: (bytes) => reply(req.id, { fd: 2, bytes })
7830
8232
  });
7831
8233
  }
8234
+ const grants = /* @__PURE__ */ new Map();
8235
+ let nextGrant = 0;
8236
+ const askGrant = (source, options) => {
8237
+ const id = ++nextGrant;
8238
+ const waiting = Promise.withResolvers();
8239
+ grants.set(id, waiting);
8240
+ port.postMessage({ hostfs: { id, source, ...options } });
8241
+ return waiting.promise;
8242
+ };
8243
+ function granted2({ id, grant, error }) {
8244
+ const waiting = grants.get(id);
8245
+ if (!waiting) return;
8246
+ grants.delete(id);
8247
+ if (grant) waiting.resolve(grant);
8248
+ else waiting.reject(Object.assign(new Error(error ?? "no grant"), { code: "EACCES" }));
8249
+ }
7832
8250
  port.addEventListener("message", (event) => {
8251
+ const grant = event.data.hostfsGrant;
8252
+ if (grant) return granted2(grant);
7833
8253
  if (event.data.net !== void 0) {
7834
8254
  remote?.receive(event.data);
7835
8255
  return;