@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.
package/dist/node.js CHANGED
@@ -1067,9 +1067,9 @@ var OpfsFs = class {
1067
1067
  var NAME = /^[A-Za-z0-9._+-]+$/;
1068
1068
  var ENV_KEY = /^[A-Za-z_][A-Za-z0-9_]*$/;
1069
1069
  var PACKAGE = /\$\{package\}/g;
1070
- function inside(pkg, rel) {
1071
- if (typeof rel !== "string" || rel === "") return void 0;
1072
- const clean = rel.replace(/^\.\//, "");
1070
+ function inside(pkg, rel2) {
1071
+ if (typeof rel2 !== "string" || rel2 === "") return void 0;
1072
+ const clean = rel2.replace(/^\.\//, "");
1073
1073
  if (clean.startsWith("/") || clean.split("/").includes("..")) return void 0;
1074
1074
  return `${pkg}/${clean}`;
1075
1075
  }
@@ -1988,7 +1988,8 @@ function statJson(s) {
1988
1988
  atimeMs: s.atime.getTime(),
1989
1989
  ctimeMs: s.ctime.getTime(),
1990
1990
  ino: s.ino,
1991
- ...s.dev !== void 0 ? { dev: s.dev } : {}
1991
+ ...s.dev !== void 0 ? { dev: s.dev } : {},
1992
+ ...s.readonly ? { readonly: true } : {}
1992
1993
  };
1993
1994
  }
1994
1995
  async function readdirStat(fs, dir) {
@@ -4843,6 +4844,20 @@ var VfsNodes = class {
4843
4844
  }
4844
4845
  }
4845
4846
  };
4847
+ async function refuseReadonly(fs, path, flags, opts = {}) {
4848
+ const writes = (flags & O_ACCMODE) !== 0 || opts.truncate === true;
4849
+ if (!writes && !opts.create || !fs.stat) return;
4850
+ const stat = (p) => fs.stat?.(p).catch((err) => {
4851
+ const code = err?.code;
4852
+ if (isMissing(err) || typeof code !== "string") return void 0;
4853
+ throw err;
4854
+ });
4855
+ const parent = path.slice(0, path.lastIndexOf("/")) || "/";
4856
+ const own = await stat(path);
4857
+ if (own && !writes) return;
4858
+ const st = own ?? await stat(parent);
4859
+ if (st?.readonly) throw new KernelError("EROFS");
4860
+ }
4846
4861
  function vfsFile(fs, opts, nodes) {
4847
4862
  const access = opts.flags & O_ACCMODE;
4848
4863
  const readable = access !== O_WRONLY;
@@ -5113,6 +5128,10 @@ var WasmProcess = class {
5113
5128
  if (e instanceof KernelError || e instanceof SpawnError) {
5114
5129
  return { ok: false, errno: e.code, message: e.code };
5115
5130
  }
5131
+ const code = e?.code;
5132
+ if (typeof code === "string" && /^E[A-Z]+$/.test(code)) {
5133
+ return { ok: false, errno: code, message: code };
5134
+ }
5116
5135
  throw e;
5117
5136
  }
5118
5137
  }
@@ -5185,6 +5204,7 @@ var WasmProcess = class {
5185
5204
  case "fd-poll":
5186
5205
  return { ok: true, kind: "json", json: pollFile(this.fds.get(req.fd).file) };
5187
5206
  case "fd-open-vfs":
5207
+ await refuseReadonly(this.options.fs, req.path, req.flags, req);
5188
5208
  return { ok: true, kind: "json", json: this.fds.install(this.openVfsFile(req), 3) };
5189
5209
  case "fd-info":
5190
5210
  return { ok: true, kind: "json", json: this.fdInfo(req.fd) };
@@ -5911,6 +5931,57 @@ function serveFilesystem(port, handlers, capabilities = {}) {
5911
5931
  return { invalidate: (paths) => reply({ invalidate: paths }) };
5912
5932
  }
5913
5933
 
5934
+ // src/mount/medium.ts
5935
+ var EMPTY_ROOT = { kind: "directory", size: 0, mtime: 0, mode: 16877 };
5936
+ var absent = () => fsError2("ENOMEDIUM", "No medium found");
5937
+ function mediumSlot(lost, onLost) {
5938
+ let current;
5939
+ function wrap(pick) {
5940
+ return async (...args) => {
5941
+ const medium = current;
5942
+ if (!medium) throw absent();
5943
+ try {
5944
+ return await pick(medium.handlers)(...args);
5945
+ } catch (err) {
5946
+ if (!await lost(err, medium.key)) throw err;
5947
+ if (current === medium) {
5948
+ current = void 0;
5949
+ onLost(medium.key);
5950
+ }
5951
+ throw absent();
5952
+ }
5953
+ };
5954
+ }
5955
+ const getattr = wrap((h) => h.getattr);
5956
+ const readdir = wrap((h) => h.readdir);
5957
+ const statfs = wrap((h) => async () => await h.statfs?.() ?? null);
5958
+ return {
5959
+ handlers: {
5960
+ getattr: (path) => current || path !== "/" ? getattr(path) : Promise.resolve(EMPTY_ROOT),
5961
+ readdir: (path) => current || path !== "/" ? readdir(path) : Promise.resolve([]),
5962
+ open: wrap((h) => h.open),
5963
+ read: wrap((h) => h.read),
5964
+ write: wrap((h) => h.write),
5965
+ release: wrap((h) => h.release),
5966
+ mkdir: wrap((h) => h.mkdir),
5967
+ rmdir: wrap((h) => h.rmdir),
5968
+ unlink: wrap((h) => h.unlink),
5969
+ rename: wrap((h) => h.rename),
5970
+ symlink: wrap((h) => h.symlink),
5971
+ readlink: wrap((h) => h.readlink),
5972
+ setattr: wrap((h) => h.setattr),
5973
+ statfs: () => current ? statfs() : Promise.resolve(null)
5974
+ },
5975
+ present: () => current !== void 0,
5976
+ insert(key, handlers) {
5977
+ current = { key, handlers };
5978
+ },
5979
+ eject() {
5980
+ current = void 0;
5981
+ }
5982
+ };
5983
+ }
5984
+
5914
5985
  // src/mount/fsa.ts
5915
5986
  var FSA_CAPABILITIES = { listingStats: true };
5916
5987
  var codeOf = (err) => err?.code;
@@ -5998,7 +6069,6 @@ function kernelFsHandlers(fs) {
5998
6069
  }
5999
6070
  };
6000
6071
  }
6001
- var EMPTY_ROOT = { kind: "directory", size: 0, mtime: 0, mode: 16877 };
6002
6072
  function granted(handle) {
6003
6073
  if (!handle.queryPermission) return Promise.resolve(true);
6004
6074
  return handle.queryPermission({ mode: "readwrite" }).then(
@@ -6007,48 +6077,345 @@ function granted(handle) {
6007
6077
  );
6008
6078
  }
6009
6079
  function removableMedium(onLost, open = (handle) => new OpfsFs(handle)) {
6010
- let current;
6011
- const absent = () => fsError2("ENOMEDIUM", "No medium found");
6012
- function wrap(pick) {
6013
- return async (...args) => {
6014
- const medium = current;
6015
- if (!medium) throw absent();
6016
- try {
6017
- return await pick(medium.handlers)(...args);
6018
- } catch (err) {
6019
- if (!denied(err) || await granted(medium.handle)) throw err;
6020
- if (current === medium) {
6021
- current = void 0;
6022
- onLost(medium.handle);
6080
+ const slot = mediumSlot(
6081
+ async (err, handle) => denied(err) && !await granted(handle),
6082
+ onLost
6083
+ );
6084
+ return {
6085
+ handlers: slot.handlers,
6086
+ present: slot.present,
6087
+ insert: (handle) => slot.insert(handle, kernelFsHandlers(open(handle))),
6088
+ eject: slot.eject
6089
+ };
6090
+ }
6091
+
6092
+ // src/mount/hostfs.ts
6093
+ var HOSTFS_IO = 16 * 1024 * 1024;
6094
+ var codeOf2 = (err) => err?.code;
6095
+ var unreachable = (err) => codeOf2(err) === "ENOTCONN";
6096
+ function rel(path) {
6097
+ return path.replace(/^\/+/, "");
6098
+ }
6099
+ var HostfsClient = class {
6100
+ grant;
6101
+ regrant;
6102
+ fetch;
6103
+ constructor(grant, regrant, fetch2) {
6104
+ this.grant = grant;
6105
+ this.regrant = regrant;
6106
+ this.fetch = fetch2;
6107
+ }
6108
+ get capabilities() {
6109
+ return this.grant.capabilities ?? {};
6110
+ }
6111
+ get token() {
6112
+ return this.grant.token;
6113
+ }
6114
+ get maxIo() {
6115
+ return this.capabilities.maxIo ?? HOSTFS_IO;
6116
+ }
6117
+ async send(path, init, handle = false, retried = false) {
6118
+ let response;
6119
+ try {
6120
+ response = await this.fetch(`${this.grant.url}${path}`, {
6121
+ ...init,
6122
+ credentials: "omit",
6123
+ headers: {
6124
+ ...init.headers,
6125
+ "X-Hostfs-Token": this.grant.token
6023
6126
  }
6024
- throw absent();
6127
+ });
6128
+ } catch (err) {
6129
+ throw fsError2("ENOTCONN", `the host proxy is unreachable: ${err.message}`);
6130
+ }
6131
+ if (response.ok) return response;
6132
+ if (response.headers.get("X-Proxy-Error")) {
6133
+ await response.body?.cancel();
6134
+ if (retried || response.status !== 403) {
6135
+ throw fsError2("EACCES", `the host proxy refused this folder (${response.status})`);
6025
6136
  }
6026
- };
6137
+ this.grant = await this.regrant();
6138
+ if (handle)
6139
+ throw fsError2("ESTALE", "the folder was granted again, so its open files are gone");
6140
+ return this.send(path, init, false, true);
6141
+ }
6142
+ const errno = response.headers.get("X-Hostfs-Errno") ?? "EIO";
6143
+ const body = await response.json().catch(() => ({}));
6144
+ throw fsError2(errno, typeof body.message === "string" ? body.message : errno);
6145
+ }
6146
+ async call(op, body = {}, handle = false) {
6147
+ const response = await this.send(
6148
+ "/api/hostfs",
6149
+ {
6150
+ method: "POST",
6151
+ headers: { "Content-Type": "application/json" },
6152
+ body: JSON.stringify({ op, ...body })
6153
+ },
6154
+ handle
6155
+ );
6156
+ return await response.json();
6157
+ }
6158
+ async read(fh, offset, size, ifMatch) {
6159
+ const response = await this.send(
6160
+ "/api/hostfs",
6161
+ {
6162
+ method: "POST",
6163
+ headers: { "Content-Type": "application/json" },
6164
+ body: JSON.stringify({ op: "read", fh, offset, size, ...ifMatch ? { ifMatch } : {} })
6165
+ },
6166
+ true
6167
+ );
6168
+ return new Uint8Array(await response.arrayBuffer());
6169
+ }
6170
+ async write(fh, offset, bytes) {
6171
+ const response = await this.send(
6172
+ "/api/hostfs/write",
6173
+ {
6174
+ method: "PUT",
6175
+ headers: {
6176
+ "Content-Type": "application/octet-stream",
6177
+ "X-Hostfs-Request": JSON.stringify({ fh, offset })
6178
+ },
6179
+ body: bytes
6180
+ },
6181
+ true
6182
+ );
6183
+ await response.body?.cancel();
6184
+ }
6185
+ async watch(signal, onEvent, onOpen = () => void 0) {
6186
+ const response = await this.send("/api/hostfs/watch", { method: "POST", signal });
6187
+ onOpen();
6188
+ const reader = response.body.getReader();
6189
+ const decoder2 = new TextDecoder();
6190
+ let buffered = "";
6191
+ try {
6192
+ for (; ; ) {
6193
+ const { done: done4, value } = await reader.read();
6194
+ if (done4) return;
6195
+ buffered += decoder2.decode(value, { stream: true });
6196
+ let end = buffered.indexOf("\n");
6197
+ while (end >= 0) {
6198
+ const line = buffered.slice(0, end).trim();
6199
+ buffered = buffered.slice(end + 1);
6200
+ if (line) onEvent(JSON.parse(line));
6201
+ end = buffered.indexOf("\n");
6202
+ }
6203
+ }
6204
+ } catch (err) {
6205
+ if (signal.aborted) return;
6206
+ throw fsError2("ENOTCONN", `the host proxy's watch broke: ${err.message}`);
6207
+ } finally {
6208
+ reader.releaseLock();
6209
+ }
6210
+ }
6211
+ };
6212
+ function hostfsHandlers(current) {
6213
+ const handles = /* @__PURE__ */ new Map();
6214
+ let nextFh = 0;
6215
+ const fresh = (remote) => {
6216
+ if (remote.client.token === remote.token) return remote;
6217
+ throw fsError2("ESTALE", "the folder was granted again, so this open file is gone");
6218
+ };
6219
+ const live = (fh) => {
6220
+ const remote = handles.get(fh);
6221
+ if (!remote) throw fsError2("EBADF", `handle ${fh}`);
6222
+ return fresh(remote);
6223
+ };
6224
+ const call = (op, body = {}) => current().call(op, body);
6225
+ async function writable(client, path, flags) {
6226
+ const opening = () => client.call("open", { path: rel(path), ...flags });
6227
+ try {
6228
+ return { opened: await opening(), restore: void 0 };
6229
+ } catch (err) {
6230
+ if (!flags.write || codeOf2(err) !== "EACCES") throw err;
6231
+ const mode = (await client.call("stat", { path: rel(path) })).mode ?? 0;
6232
+ if (mode & 128) throw err;
6233
+ const restore = { path: rel(path), mode };
6234
+ await client.call("setattr", { path: rel(path), mode: mode | 128 });
6235
+ const opened2 = await opening().catch(async (again) => {
6236
+ await client.call("setattr", restore);
6237
+ throw again;
6238
+ });
6239
+ return { opened: opened2, restore };
6240
+ }
6027
6241
  }
6028
- const getattr = wrap((h) => h.getattr);
6029
- const readdir = wrap((h) => h.readdir);
6030
6242
  return {
6031
- handlers: {
6032
- getattr: (path) => current || path !== "/" ? getattr(path) : Promise.resolve(EMPTY_ROOT),
6033
- readdir: (path) => current || path !== "/" ? readdir(path) : Promise.resolve([]),
6034
- open: wrap((h) => h.open),
6035
- read: wrap((h) => h.read),
6036
- write: wrap((h) => h.write),
6037
- release: wrap((h) => h.release),
6038
- mkdir: wrap((h) => h.mkdir),
6039
- rmdir: wrap((h) => h.rmdir),
6040
- unlink: wrap((h) => h.unlink),
6041
- rename: wrap((h) => h.rename),
6042
- symlink: wrap((h) => h.symlink),
6043
- readlink: wrap((h) => h.readlink),
6044
- setattr: wrap((h) => h.setattr)
6243
+ getattr: async (path) => await call("stat", { path: rel(path) }),
6244
+ async readdir(path) {
6245
+ const { entries } = await call(
6246
+ "list",
6247
+ { path: rel(path) }
6248
+ );
6249
+ return entries.map(({ name: name2, attr }) => ({ name: name2, kind: attr.kind, attr }));
6045
6250
  },
6046
- present: () => current !== void 0,
6047
- insert(handle) {
6048
- current = { handle, handlers: kernelFsHandlers(open(handle)) };
6251
+ async open(path, flags) {
6252
+ const client = current();
6253
+ const token = client.token;
6254
+ const { opened: opened2, restore } = await writable(client, path, flags);
6255
+ handles.set(++nextFh, {
6256
+ client,
6257
+ token,
6258
+ fh: opened2.fh,
6259
+ etag: flags.write ? void 0 : opened2.attr?.etag,
6260
+ restore
6261
+ });
6262
+ return nextFh;
6049
6263
  },
6050
- eject() {
6051
- current = void 0;
6264
+ async read(fh, offset, size) {
6265
+ const remote = live(fh);
6266
+ return remote.client.read(
6267
+ remote.fh,
6268
+ offset,
6269
+ Math.min(size, remote.client.maxIo),
6270
+ remote.etag
6271
+ );
6272
+ },
6273
+ async write(fh, offset, bytes) {
6274
+ const remote = live(fh);
6275
+ const most = remote.client.maxIo;
6276
+ for (let at = 0; at < bytes.length; at += most) {
6277
+ await remote.client.write(remote.fh, offset + at, bytes.subarray(at, at + most));
6278
+ }
6279
+ },
6280
+ async release(fh) {
6281
+ const remote = handles.get(fh);
6282
+ if (!remote) throw fsError2("EBADF", `handle ${fh}`);
6283
+ handles.delete(fh);
6284
+ try {
6285
+ await fresh(remote).client.call("release", { fh: remote.fh }, true);
6286
+ } finally {
6287
+ if (remote.restore) await current().call("setattr", remote.restore);
6288
+ }
6289
+ },
6290
+ mkdir: async (path) => void await call("mkdir", { path: rel(path) }),
6291
+ rmdir: async (path) => void await call("rmdir", { path: rel(path) }),
6292
+ unlink: async (path) => void await call("unlink", { path: rel(path) }),
6293
+ rename: async (from, to) => void await call("rename", { from: rel(from), to: rel(to) }),
6294
+ symlink: async (target, path) => void await call("symlink", { target, path: rel(path) }),
6295
+ readlink: async (path) => (await call("readlink", { path: rel(path) })).target,
6296
+ setattr: async (path, change) => void await call("setattr", { path: rel(path), ...change }),
6297
+ statfs: () => call("statfs")
6298
+ };
6299
+ }
6300
+ function sleep(ms, signal) {
6301
+ return new Promise((resolve) => {
6302
+ const done4 = () => {
6303
+ clearTimeout(timer);
6304
+ signal.removeEventListener("abort", done4);
6305
+ resolve();
6306
+ };
6307
+ const timer = setTimeout(done4, ms);
6308
+ signal.addEventListener("abort", done4);
6309
+ });
6310
+ }
6311
+ function keepWatching(loop, signal) {
6312
+ const silenceMs = loop.silenceMs ?? 45e3;
6313
+ const [first, most] = loop.backoffMs ?? [1e3, 3e4];
6314
+ const ready2 = Promise.withResolvers();
6315
+ let wait = first;
6316
+ let up = false;
6317
+ let current;
6318
+ async function session() {
6319
+ const session2 = new AbortController();
6320
+ current = session2;
6321
+ const stop = () => session2.abort();
6322
+ signal.addEventListener("abort", stop);
6323
+ let timer;
6324
+ const quiet = () => {
6325
+ clearTimeout(timer);
6326
+ timer = setTimeout(stop, silenceMs);
6327
+ };
6328
+ let opened2 = false;
6329
+ try {
6330
+ const client = await loop.connect();
6331
+ await client.watch(
6332
+ session2.signal,
6333
+ (event) => {
6334
+ quiet();
6335
+ if (!event.ping) loop.event(event);
6336
+ },
6337
+ () => {
6338
+ quiet();
6339
+ opened2 = true;
6340
+ up = true;
6341
+ loop.up(client);
6342
+ ready2.resolve();
6343
+ }
6344
+ );
6345
+ return opened2;
6346
+ } catch {
6347
+ return opened2;
6348
+ } finally {
6349
+ clearTimeout(timer);
6350
+ signal.removeEventListener("abort", stop);
6351
+ }
6352
+ }
6353
+ void (async () => {
6354
+ while (!signal.aborted) {
6355
+ const began = Date.now();
6356
+ const opened2 = await session();
6357
+ if (signal.aborted) return;
6358
+ if (opened2 && Date.now() - began >= first) {
6359
+ wait = first;
6360
+ continue;
6361
+ }
6362
+ if (!opened2 && up) {
6363
+ up = false;
6364
+ loop.down();
6365
+ }
6366
+ ready2.resolve();
6367
+ await sleep(wait, signal);
6368
+ wait = Math.min(wait * 2, most);
6369
+ }
6370
+ })();
6371
+ return { ready: ready2.promise, kick: () => current?.abort() };
6372
+ }
6373
+ function invalidation(event) {
6374
+ return event.paths ?? true;
6375
+ }
6376
+ async function openHostfs(spec, hook, fetch2, timing = {}) {
6377
+ const readonly2 = spec.options?.ro !== void 0;
6378
+ const { port1, port2 } = new MessageChannel();
6379
+ const slot = mediumSlot(
6380
+ async (err) => unreachable(err),
6381
+ () => watching.kick()
6382
+ );
6383
+ const stop = new AbortController();
6384
+ const grant = () => hook(spec.source, { readonly: readonly2 });
6385
+ let active;
6386
+ const handlers = hostfsHandlers(() => active);
6387
+ const served = serveFilesystem(port2, slot.handlers, {
6388
+ symlinks: true,
6389
+ chmod: true,
6390
+ listingStats: true,
6391
+ maxIo: HOSTFS_IO,
6392
+ ...readonly2 ? { readonly: true } : {}
6393
+ });
6394
+ const watching = keepWatching(
6395
+ {
6396
+ connect: async () => new HostfsClient(await grant(), grant, fetch2),
6397
+ up(client) {
6398
+ active = client;
6399
+ slot.insert(client, handlers);
6400
+ served.invalidate(true);
6401
+ },
6402
+ event: (event) => served.invalidate(invalidation(event)),
6403
+ down() {
6404
+ slot.eject();
6405
+ served.invalidate(true);
6406
+ },
6407
+ ...timing
6408
+ },
6409
+ stop.signal
6410
+ );
6411
+ await watching.ready;
6412
+ return {
6413
+ port: port1,
6414
+ present: slot.present,
6415
+ dispose() {
6416
+ stop.abort();
6417
+ port1.close();
6418
+ port2.close();
6052
6419
  }
6053
6420
  };
6054
6421
  }
@@ -6157,6 +6524,7 @@ var DriverConnection = class _DriverConnection {
6157
6524
 
6158
6525
  // src/mount/mount-fs.ts
6159
6526
  var MAX_LINKS4 = 40;
6527
+ var STALE_RETRIES = 3;
6160
6528
  var DEFAULT_IO = 8 * 1024 * 1024;
6161
6529
  var UNITS = { "": 1, k: 1024, m: 1024 ** 2, g: 1024 ** 3, t: 1024 ** 4 };
6162
6530
  function parseSize(text) {
@@ -6170,7 +6538,8 @@ function heldUnder(held, target) {
6170
6538
  return false;
6171
6539
  }
6172
6540
  var parentOf4 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
6173
- function statOf(attr, path, dev) {
6541
+ var readonly = (mount) => mount.caps.readonly === true;
6542
+ function statOf(attr, path, mount) {
6174
6543
  const mtime = new Date(attr.mtime);
6175
6544
  const fallback = attr.kind === "directory" ? 16877 : attr.kind === "symlink" ? 41471 : 33188;
6176
6545
  return {
@@ -6183,7 +6552,8 @@ function statOf(attr, path, dev) {
6183
6552
  atime: mtime,
6184
6553
  ctime: mtime,
6185
6554
  ino: attr.ino ?? inodeOf(path),
6186
- dev
6555
+ dev: mount.dev,
6556
+ ...readonly(mount) ? { readonly: true } : {}
6187
6557
  };
6188
6558
  }
6189
6559
  function concat4(chunks2, total) {
@@ -6223,8 +6593,8 @@ var MountTable = class {
6223
6593
  best = mount;
6224
6594
  }
6225
6595
  if (!best) return void 0;
6226
- const rel = best.target === "/" ? path : path.slice(best.target.length);
6227
- return { mount: best, rel: rel || "/" };
6596
+ const rel2 = best.target === "/" ? path : path.slice(best.target.length);
6597
+ return { mount: best, rel: rel2 || "/" };
6228
6598
  }
6229
6599
  async mount(spec, fs) {
6230
6600
  const target = normalizePath(spec.target);
@@ -6329,20 +6699,20 @@ var MountTable = class {
6329
6699
  mount.lists.delete(parentOf4(path));
6330
6700
  for (const key of [...mount.lists.keys()]) if (within3(key, path)) mount.lists.delete(key);
6331
6701
  }
6332
- async getattr(mount, path, rel) {
6702
+ async getattr(mount, path, rel2) {
6333
6703
  if (mount.conn.failure) throw mount.conn.failure;
6334
6704
  const cached = mount.attrs.get(path);
6335
6705
  if (cached && cached.expires > Date.now()) return cached.value;
6336
- const attr = await mount.conn.call({ op: "getattr", path: rel });
6706
+ const attr = await mount.conn.call({ op: "getattr", path: rel2 });
6337
6707
  const ttl = mount.caps.attrTtl ?? 1e3;
6338
6708
  if (ttl > 0) mount.attrs.set(path, { value: attr, expires: Date.now() + ttl });
6339
6709
  return attr;
6340
6710
  }
6341
- async readdir(mount, path, rel) {
6711
+ async readdir(mount, path, rel2) {
6342
6712
  if (mount.conn.failure) throw mount.conn.failure;
6343
6713
  const cached = mount.lists.get(path);
6344
6714
  if (cached && cached.expires > Date.now()) return cached.value;
6345
- const entries = await mount.conn.call({ op: "readdir", path: rel });
6715
+ const entries = await mount.conn.call({ op: "readdir", path: rel2 });
6346
6716
  const ttl = mount.caps.entryTtl ?? mount.caps.attrTtl ?? 1e3;
6347
6717
  if (ttl > 0) mount.lists.set(path, { value: entries, expires: Date.now() + ttl });
6348
6718
  return entries;
@@ -6369,7 +6739,7 @@ var MountFs = class {
6369
6739
  return this.table.locate(normalizePath(path));
6370
6740
  }
6371
6741
  writable(mount, path) {
6372
- if (mount.caps.readonly) throw errnoError("EROFS", path);
6742
+ if (readonly(mount)) throw errnoError("EROFS", path);
6373
6743
  }
6374
6744
  async follow(path, hops = 0, create = false) {
6375
6745
  const found = this.at(path);
@@ -6387,10 +6757,10 @@ var MountFs = class {
6387
6757
  const target = await found.mount.conn.call({ op: "readlink", path: found.rel });
6388
6758
  return this.follow(this.base.resolvePath(parentOf4(real), target), hops + 1, create);
6389
6759
  }
6390
- async open(mount, rel, write) {
6760
+ async open(mount, rel2, write) {
6391
6761
  return await mount.conn.call({
6392
6762
  op: "open",
6393
- path: rel,
6763
+ path: rel2,
6394
6764
  write,
6395
6765
  create: write,
6396
6766
  truncate: write,
@@ -6407,13 +6777,22 @@ var MountFs = class {
6407
6777
  const real = await this.follow(path);
6408
6778
  const found = this.at(real);
6409
6779
  if (!found) return this.base.readFileBuffer(real);
6410
- const { mount, rel } = found;
6411
- const attr = await this.table.getattr(mount, real, rel);
6780
+ for (let attempt = 1; ; attempt++) {
6781
+ try {
6782
+ return await this.readWhole(found.mount, real, found.rel);
6783
+ } catch (err) {
6784
+ if (err.code !== "ESTALE" || attempt >= STALE_RETRIES) throw err;
6785
+ this.table.forget(found.mount, real);
6786
+ }
6787
+ }
6788
+ }
6789
+ async readWhole(mount, real, rel2) {
6790
+ const attr = await this.table.getattr(mount, real, rel2);
6412
6791
  if (attr.kind === "directory") throw errnoError("EISDIR", real);
6413
6792
  if (mount.maxFile > 0 && attr.size > mount.maxFile) {
6414
6793
  throw errnoError("EFBIG", `${real} is larger than this mount's maxfile (${mount.maxFile})`);
6415
6794
  }
6416
- const fh = await this.open(mount, rel, false);
6795
+ const fh = await this.open(mount, rel2, false);
6417
6796
  const chunk2 = mount.caps.maxIo ?? DEFAULT_IO;
6418
6797
  const chunks2 = [];
6419
6798
  let total = 0;
@@ -6439,7 +6818,7 @@ var MountFs = class {
6439
6818
  const real = await this.follow(path, 0, true);
6440
6819
  const found = this.at(real);
6441
6820
  if (!found) return this.base.writeFile(real, content);
6442
- const { mount, rel } = found;
6821
+ const { mount, rel: rel2 } = found;
6443
6822
  this.writable(mount, real);
6444
6823
  const bytes = typeof content === "string" ? new TextEncoder().encode(content) : content;
6445
6824
  if (mount.maxFile > 0 && bytes.length > mount.maxFile) {
@@ -6448,8 +6827,17 @@ var MountFs = class {
6448
6827
  `${real} would be larger than this mount's maxfile (${mount.maxFile})`
6449
6828
  );
6450
6829
  }
6830
+ for (let attempt = 1; ; attempt++) {
6831
+ try {
6832
+ return await this.writeWhole(mount, real, rel2, bytes);
6833
+ } catch (err) {
6834
+ if (err.code !== "ESTALE" || attempt >= STALE_RETRIES) throw err;
6835
+ }
6836
+ }
6837
+ }
6838
+ async writeWhole(mount, real, rel2, bytes) {
6451
6839
  this.table.forget(mount, real);
6452
- const fh = await this.open(mount, rel, true);
6840
+ const fh = await this.open(mount, rel2, true);
6453
6841
  const chunk2 = mount.caps.maxIo ?? DEFAULT_IO;
6454
6842
  try {
6455
6843
  for (let offset = 0; offset < bytes.length; offset += chunk2) {
@@ -6480,7 +6868,7 @@ var MountFs = class {
6480
6868
  const found = this.at(path);
6481
6869
  if (!found) return this.base.lstat(path);
6482
6870
  const real = normalizePath(path);
6483
- return statOf(await this.table.getattr(found.mount, real, found.rel), real, found.mount.dev);
6871
+ return statOf(await this.table.getattr(found.mount, real, found.rel), real, found.mount);
6484
6872
  }
6485
6873
  async stat(path) {
6486
6874
  const real = await this.follow(path);
@@ -6507,7 +6895,7 @@ var MountFs = class {
6507
6895
  list.map(async (entry) => {
6508
6896
  const child = normalizePath(`${real}/${entry.name}`);
6509
6897
  if (entry.attr && mount.caps.listingStats) {
6510
- return [entry.name, statOf(entry.attr, child, mount.dev)];
6898
+ return [entry.name, statOf(entry.attr, child, mount)];
6511
6899
  }
6512
6900
  return [entry.name, await this.lstat(child).catch(() => null)];
6513
6901
  })
@@ -6516,16 +6904,16 @@ var MountFs = class {
6516
6904
  async mkdir(path, options = {}) {
6517
6905
  const found = this.at(path);
6518
6906
  if (!found) return this.base.mkdir(path, options);
6519
- const { mount, rel } = found;
6907
+ const { mount, rel: rel2 } = found;
6520
6908
  const real = normalizePath(path);
6521
6909
  this.writable(mount, real);
6522
6910
  if (!options.recursive) {
6523
- if (rel === "/") throw errnoError("EEXIST", real);
6911
+ if (rel2 === "/") throw errnoError("EEXIST", real);
6524
6912
  this.table.forget(mount, real);
6525
- await mount.conn.call({ op: "mkdir", path: rel });
6913
+ await mount.conn.call({ op: "mkdir", path: rel2 });
6526
6914
  return;
6527
6915
  }
6528
- const parts = rel.split("/").filter(Boolean);
6916
+ const parts = rel2.split("/").filter(Boolean);
6529
6917
  for (let n = 1; n <= parts.length; n++) {
6530
6918
  const sub = `/${parts.slice(0, n).join("/")}`;
6531
6919
  const full = normalizePath(`${mount.target}${sub}`);
@@ -6539,25 +6927,25 @@ var MountFs = class {
6539
6927
  async rm(path, options = {}) {
6540
6928
  const found = this.at(path);
6541
6929
  if (!found) return this.base.rm(path, options);
6542
- const { mount, rel } = found;
6930
+ const { mount, rel: rel2 } = found;
6543
6931
  const real = normalizePath(path);
6544
- if (rel === "/") throw errnoError("EBUSY", `${real} is a mount point`);
6932
+ if (rel2 === "/") throw errnoError("EBUSY", `${real} is a mount point`);
6545
6933
  this.writable(mount, real);
6546
6934
  let attr;
6547
6935
  try {
6548
- attr = await this.table.getattr(mount, real, rel);
6936
+ attr = await this.table.getattr(mount, real, rel2);
6549
6937
  } catch (err) {
6550
6938
  if (options.force && err.code === "ENOENT") return;
6551
6939
  throw err;
6552
6940
  }
6553
6941
  this.table.forget(mount, real);
6554
6942
  if (attr.kind === "directory" && options.recursive) {
6555
- for (const entry of await this.table.readdir(mount, real, rel)) {
6943
+ for (const entry of await this.table.readdir(mount, real, rel2)) {
6556
6944
  await this.rm(`${real}/${entry.name}`, { recursive: true, force: options.force ?? false });
6557
6945
  }
6558
6946
  this.table.forget(mount, real);
6559
6947
  }
6560
- await mount.conn.call({ op: attr.kind === "directory" ? "rmdir" : "unlink", path: rel });
6948
+ await mount.conn.call({ op: attr.kind === "directory" ? "rmdir" : "unlink", path: rel2 });
6561
6949
  this.table.forget(mount, real);
6562
6950
  }
6563
6951
  async rename(from, to) {
@@ -7019,6 +7407,7 @@ var Launcher = class {
7019
7407
  media;
7020
7408
  onMountPending;
7021
7409
  removable = /* @__PURE__ */ new Map();
7410
+ hostfs;
7022
7411
  inserted = /* @__PURE__ */ new Map();
7023
7412
  openFiles = /* @__PURE__ */ new Set();
7024
7413
  held = /* @__PURE__ */ new Set();
@@ -7036,6 +7425,7 @@ var Launcher = class {
7036
7425
  this.createDriverWorker = options.createDriverWorker;
7037
7426
  this.media = options.media ?? memoryMedia();
7038
7427
  this.onMountPending = options.onMountPending;
7428
+ this.hostfs = options;
7039
7429
  this.pnpmHome = options.env?.PNPM_HOME ?? PNPM_HOME;
7040
7430
  this.modulesDir = options.modules ?? "/node_modules";
7041
7431
  this.env = { ...networkEnv(), ...options.env };
@@ -7301,6 +7691,17 @@ var Launcher = class {
7301
7691
  }
7302
7692
  async driver(type, spec) {
7303
7693
  if (type === "fsa") return this.fsaDriver(spec);
7694
+ if (type === "hostfs") {
7695
+ const { hostfs, hostfsFetch, hostfsTiming } = this.hostfs;
7696
+ if (!hostfs)
7697
+ throw fsError("ENODEV", "this kernel has no hostfs hook (createKernel({ hostfs }))");
7698
+ return openHostfs(
7699
+ spec,
7700
+ hostfs,
7701
+ hostfsFetch ?? ((url, init) => fetch(url, init)),
7702
+ hostfsTiming
7703
+ );
7704
+ }
7304
7705
  if (type === "tmpfs") {
7305
7706
  const { port1, port2 } = new MessageChannel();
7306
7707
  serveFilesystem(port2, tmpfs(), { symlinks: true, chmod: true, attrTtl: 0 });