@ai-ecoverse/slicc-kernel 1.21.1 → 1.22.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
@@ -2,6 +2,11 @@
2
2
  import { MessageChannel as MessageChannel2, Worker } from "node:worker_threads";
3
3
 
4
4
  // src/fs/types.ts
5
+ function rangedOps(fs) {
6
+ const { pread, pwrite, truncate } = fs;
7
+ if (!pread || !pwrite || !truncate) return void 0;
8
+ return { pread: pread.bind(fs), pwrite: pwrite.bind(fs), truncate: truncate.bind(fs) };
9
+ }
5
10
  function fsError(code, path) {
6
11
  return Object.assign(new Error(`${code}: ${path}`), { code });
7
12
  }
@@ -2083,7 +2088,8 @@ function withCommandDirs(fs, names) {
2083
2088
  symlink: (target, path) => fs.symlink(target, path),
2084
2089
  readlink: (path) => fs.readlink(path),
2085
2090
  chmod: (path, mode) => fs.chmod(path, mode),
2086
- utimes: (path, atime, mtime) => fs.utimes(path, atime, mtime)
2091
+ utimes: (path, atime, mtime) => fs.utimes(path, atime, mtime),
2092
+ ...rangedOps(fs) ?? {}
2087
2093
  };
2088
2094
  }
2089
2095
 
@@ -2160,6 +2166,11 @@ var FsWatchers = class {
2160
2166
  chmod: after(fs.chmod.bind(fs), (path) => [path]),
2161
2167
  utimes: after(fs.utimes.bind(fs), (path) => [path])
2162
2168
  };
2169
+ const ranged2 = rangedOps(fs);
2170
+ if (ranged2) {
2171
+ overrides.pwrite = after(ranged2.pwrite, (path) => [path]);
2172
+ overrides.truncate = after(ranged2.truncate, (path) => [path]);
2173
+ }
2163
2174
  return new Proxy(fs, {
2164
2175
  get(target, key, receiver) {
2165
2176
  if (Object.hasOwn(overrides, key)) return overrides[key];
@@ -2354,7 +2365,7 @@ var ChildTable = class {
2354
2365
  };
2355
2366
 
2356
2367
  // src/mount/protocol.ts
2357
- var DRIVER_PROTOCOL = [1, 0];
2368
+ var DRIVER_PROTOCOL = [1, 1];
2358
2369
  function driverVersionError(theirs) {
2359
2370
  const major = Array.isArray(theirs) ? theirs[0] : void 0;
2360
2371
  if (major === DRIVER_PROTOCOL[0]) return void 0;
@@ -2513,7 +2524,8 @@ function statOf(attr, path, mount) {
2513
2524
  ino: attr.ino ?? inodeOf(path),
2514
2525
  dev: mount.dev,
2515
2526
  ...readonly(mount) ? { readonly: true } : {},
2516
- ...mount.maxFile > 0 ? { maxFile: mount.maxFile } : {}
2527
+ ...mount.maxFile > 0 ? { maxFile: mount.maxFile } : {},
2528
+ ...mount.caps.ranges ? { ranged: true } : {}
2517
2529
  };
2518
2530
  }
2519
2531
  function concat2(chunks2, total) {
@@ -2526,10 +2538,26 @@ function concat2(chunks2, total) {
2526
2538
  }
2527
2539
  return out;
2528
2540
  }
2541
+ function resized(bytes, size) {
2542
+ if (size <= bytes.length) return bytes.subarray(0, size);
2543
+ const out = new Uint8Array(size);
2544
+ out.set(bytes);
2545
+ return out;
2546
+ }
2547
+ function handed(bytes, transfer, chunk2) {
2548
+ const whole = bytes.byteOffset === 0 && bytes.byteLength === bytes.buffer.byteLength;
2549
+ return transfer && whole && bytes.length <= chunk2 && bytes.buffer instanceof ArrayBuffer;
2550
+ }
2551
+ function spliced(bytes, offset, part) {
2552
+ const out = resized(bytes, Math.max(bytes.length, offset + part.length));
2553
+ out.set(part, offset);
2554
+ return out;
2555
+ }
2529
2556
  var MountTable = class {
2530
2557
  mounts = /* @__PURE__ */ new Map();
2531
2558
  devices = 0;
2532
2559
  pending = /* @__PURE__ */ new Set();
2560
+ notes = /* @__PURE__ */ new Map();
2533
2561
  deps;
2534
2562
  constructor(deps) {
2535
2563
  this.deps = deps;
@@ -2538,7 +2566,7 @@ var MountTable = class {
2538
2566
  return this.mounts.get(target);
2539
2567
  }
2540
2568
  list() {
2541
- return [...this.mounts.values()].map(
2569
+ const mounted = [...this.mounts.values()].map(
2542
2570
  ({ type, source, target, options, state, error, present }) => ({
2543
2571
  type,
2544
2572
  source,
@@ -2548,6 +2576,19 @@ var MountTable = class {
2548
2576
  ...error ? { error } : {}
2549
2577
  })
2550
2578
  );
2579
+ const noted = [...this.notes.values()].filter((note) => !this.mounts.has(note.target));
2580
+ return [...mounted, ...noted.map((note) => ({ ...note, options: { ...note.options } }))];
2581
+ }
2582
+ note(spec, state, error) {
2583
+ const target = normalizePath(spec.target);
2584
+ this.notes.set(target, {
2585
+ type: spec.type,
2586
+ source: spec.source,
2587
+ target,
2588
+ options: { ...spec.options },
2589
+ state,
2590
+ ...error ? { error } : {}
2591
+ });
2551
2592
  }
2552
2593
  locate(path) {
2553
2594
  let best;
@@ -2624,13 +2665,19 @@ var MountTable = class {
2624
2665
  };
2625
2666
  mounted = mount;
2626
2667
  this.mounts.set(target, mount);
2668
+ this.notes.delete(target);
2627
2669
  return this.list().find((m) => m.target === target);
2628
2670
  }
2671
+ unnote(target) {
2672
+ this.notes.delete(normalizePath(target));
2673
+ }
2629
2674
  umount(target, detach = false) {
2630
2675
  const at = normalizePath(target);
2631
2676
  const mount = this.mounts.get(at);
2677
+ if (!mount && this.notes.delete(at)) return;
2632
2678
  if (!mount) throw errnoError("EINVAL", `${at} is not mounted`);
2633
2679
  if (!detach && this.deps.busy(at)) throw errnoError("EBUSY", `${at} has open files`);
2680
+ this.notes.delete(at);
2634
2681
  this.mounts.delete(at);
2635
2682
  mount.conn.close();
2636
2683
  mount.dispose();
@@ -2805,15 +2852,95 @@ var MountFs = class {
2805
2852
  const chunk2 = mount.caps.maxIo ?? DEFAULT_IO;
2806
2853
  try {
2807
2854
  for (let offset = 0; offset < bytes.length; offset += chunk2) {
2808
- await mount.conn.call({
2809
- op: "write",
2855
+ const part = bytes.slice(offset, offset + chunk2);
2856
+ await mount.conn.call({ op: "write", fh, offset, bytes: part }, [part.buffer]);
2857
+ }
2858
+ } finally {
2859
+ await mount.conn.call({ op: "release", fh });
2860
+ this.table.forget(mount, real);
2861
+ }
2862
+ }
2863
+ async pread(path, offset, length2) {
2864
+ const real = await this.follow(path);
2865
+ const found = this.at(real);
2866
+ if (!found) return (await this.base.readFileBuffer(real)).slice(offset, offset + length2);
2867
+ const { mount, rel: rel2 } = found;
2868
+ if ((await this.table.getattr(mount, real, rel2)).kind === "directory") {
2869
+ throw errnoError("EISDIR", real);
2870
+ }
2871
+ const fh = await this.open(mount, rel2, false);
2872
+ const chunk2 = mount.caps.maxIo ?? DEFAULT_IO;
2873
+ const chunks2 = [];
2874
+ let total = 0;
2875
+ try {
2876
+ while (total < length2) {
2877
+ const bytes = await mount.conn.call({
2878
+ op: "read",
2810
2879
  fh,
2811
- offset,
2812
- bytes: bytes.slice(offset, offset + chunk2)
2880
+ offset: offset + total,
2881
+ size: Math.min(chunk2, length2 - total)
2813
2882
  });
2883
+ if (bytes.length === 0) break;
2884
+ chunks2.push(bytes);
2885
+ total += bytes.length;
2814
2886
  }
2815
2887
  } finally {
2816
2888
  await mount.conn.call({ op: "release", fh });
2889
+ }
2890
+ return concat2(chunks2, total);
2891
+ }
2892
+ fits(mount, real, end) {
2893
+ if (mount.maxFile > 0 && end > mount.maxFile) {
2894
+ throw errnoError(
2895
+ "EFBIG",
2896
+ `${real} would be larger than this mount's maxfile (${mount.maxFile})`
2897
+ );
2898
+ }
2899
+ }
2900
+ async pwrite(path, offset, bytes, transfer = false) {
2901
+ const real = await this.follow(path, 0, true);
2902
+ const found = this.at(real);
2903
+ if (!found)
2904
+ return this.base.writeFile(
2905
+ real,
2906
+ spliced(await this.base.readFileBuffer(real), offset, bytes)
2907
+ );
2908
+ const { mount, rel: rel2 } = found;
2909
+ this.writable(mount, real);
2910
+ this.fits(mount, real, offset + bytes.length);
2911
+ this.table.forget(mount, real);
2912
+ const fh = await mount.conn.call({
2913
+ op: "open",
2914
+ path: rel2,
2915
+ write: true,
2916
+ create: true,
2917
+ truncate: false,
2918
+ exclusive: false
2919
+ });
2920
+ const chunk2 = mount.caps.maxIo ?? DEFAULT_IO;
2921
+ try {
2922
+ for (let at = 0; at < bytes.length; at += chunk2) {
2923
+ const part = handed(bytes, transfer, chunk2) ? bytes : bytes.slice(at, at + chunk2);
2924
+ await mount.conn.call({ op: "write", fh, offset: offset + at, bytes: part }, [part.buffer]);
2925
+ }
2926
+ } finally {
2927
+ await mount.conn.call({ op: "release", fh });
2928
+ this.table.forget(mount, real);
2929
+ }
2930
+ }
2931
+ async truncate(path, size) {
2932
+ const real = await this.follow(path);
2933
+ const found = this.at(real);
2934
+ if (!found?.mount.caps.ranges) {
2935
+ return this.writeFile(real, resized(await this.readFileBuffer(real), size));
2936
+ }
2937
+ const { mount, rel: rel2 } = found;
2938
+ this.writable(mount, real);
2939
+ this.fits(mount, real, size);
2940
+ this.table.forget(mount, real);
2941
+ try {
2942
+ await mount.conn.call({ op: "setattr", path: rel2, size });
2943
+ } finally {
2817
2944
  this.table.forget(mount, real);
2818
2945
  }
2819
2946
  }
@@ -3000,7 +3127,8 @@ function statJson(s) {
3000
3127
  ino: s.ino,
3001
3128
  ...s.dev !== void 0 ? { dev: s.dev } : {},
3002
3129
  ...s.readonly ? { readonly: true } : {},
3003
- ...s.maxFile ? { maxFile: s.maxFile } : {}
3130
+ ...s.maxFile ? { maxFile: s.maxFile } : {},
3131
+ ...s.ranged ? { ranged: true } : {}
3004
3132
  };
3005
3133
  }
3006
3134
  async function readdirStat(fs, dir) {
@@ -3065,10 +3193,24 @@ async function run(entry, path, req) {
3065
3193
  case "utimes":
3066
3194
  await fs.utimes(path, new Date(req.atimeMs ?? 0), new Date(req.mtimeMs ?? 0));
3067
3195
  return done;
3196
+ case "pread":
3197
+ case "pwrite":
3198
+ case "truncate":
3199
+ return ranged(fs, path, req);
3068
3200
  default:
3069
3201
  return { ok: false, errno: "EINVAL", message: `sync-fs: unknown op '${req.op}'` };
3070
3202
  }
3071
3203
  }
3204
+ async function ranged(fs, path, req) {
3205
+ const ops = rangedOps(fs);
3206
+ if (!ops) throw syncError("ENOSYS", path);
3207
+ const offset = req.offset ?? 0;
3208
+ if (req.op === "pread")
3209
+ return { ok: true, kind: "bytes", bytes: await ops.pread(path, offset, req.length ?? 0) };
3210
+ if (req.op === "pwrite") await ops.pwrite(path, offset, req.body ?? new Uint8Array(0), true);
3211
+ else await ops.truncate(path, req.length ?? 0);
3212
+ return done;
3213
+ }
3072
3214
  async function dispatchSyncFs(req) {
3073
3215
  const entry = resolveSyncFsToken(req.token);
3074
3216
  if (!entry) return { ok: false, errno: "EACCES", message: "sync-fs: unknown or revoked token" };
@@ -5396,6 +5538,226 @@ async function accept(proc, fd, nonblock) {
5396
5538
  return { fd: proc.fds.install(new OpenFile(socket), 3), peer: socket.peer };
5397
5539
  }
5398
5540
 
5541
+ // src/fs/pages.ts
5542
+ var PAGE = 64 * 1024;
5543
+ var READAHEAD = 1024 * 1024;
5544
+ var CLEAN_LIMIT = 4 * 1024 * 1024;
5545
+ var WRITEBACK = 8 * 1024 * 1024;
5546
+ var RUN = 1024 * 1024;
5547
+ var Pages = class {
5548
+ length;
5549
+ stored;
5550
+ shrink;
5551
+ pages = /* @__PURE__ */ new Map();
5552
+ dirty = /* @__PURE__ */ new Set();
5553
+ free = [];
5554
+ pinned = false;
5555
+ constructor(size) {
5556
+ this.length = size;
5557
+ this.stored = size;
5558
+ }
5559
+ get dirtyBytes() {
5560
+ return this.dirty.size * PAGE;
5561
+ }
5562
+ get changed() {
5563
+ return this.dirty.size > 0 || this.shrink !== void 0 || this.length !== this.stored;
5564
+ }
5565
+ missing(at, length2) {
5566
+ this.evict();
5567
+ const end = Math.min(this.stored, at + length2, this.length);
5568
+ const runs = [];
5569
+ for (let page = Math.floor(at / PAGE); page * PAGE < end; page++) {
5570
+ if (this.pages.has(page)) continue;
5571
+ const last2 = runs[runs.length - 1];
5572
+ if (last2 && last2[0] + last2[1] === page * PAGE) last2[1] += PAGE;
5573
+ else runs.push([page * PAGE, PAGE]);
5574
+ }
5575
+ const last = runs[runs.length - 1];
5576
+ if (last) last[1] = Math.max(last[1], Math.min(READAHEAD, this.stored - last[0]));
5577
+ return runs.map(([start, n]) => [start, Math.min(n, this.stored - start)]);
5578
+ }
5579
+ partial(at, length2) {
5580
+ if (length2 === 0) return [];
5581
+ this.evict();
5582
+ const edges = /* @__PURE__ */ new Set([Math.floor(at / PAGE), Math.floor((at + length2 - 1) / PAGE)]);
5583
+ const runs = [];
5584
+ for (const page of edges) {
5585
+ const start = page * PAGE;
5586
+ const covered = at <= start && at + length2 >= start + PAGE;
5587
+ if (covered || this.pages.has(page) || start >= this.stored) continue;
5588
+ runs.push([start, Math.min(PAGE, this.stored - start)]);
5589
+ }
5590
+ return runs;
5591
+ }
5592
+ fill(start, bytes) {
5593
+ for (let at = 0; at < bytes.length; at += PAGE) {
5594
+ const page = (start + at) / PAGE;
5595
+ if (this.pages.has(page)) continue;
5596
+ const part = bytes.subarray(at, Math.min(at + PAGE, bytes.length));
5597
+ const fresh = this.take(part.length);
5598
+ fresh.set(part);
5599
+ this.pages.set(page, fresh);
5600
+ }
5601
+ }
5602
+ read(at, length2) {
5603
+ const out = new Uint8Array(Math.max(0, Math.min(this.length, at + length2) - at));
5604
+ this.readInto(at, out);
5605
+ return out;
5606
+ }
5607
+ readInto(at, out) {
5608
+ const end = Math.min(this.length, at + out.length);
5609
+ for (let pos = at; pos < end; ) {
5610
+ const page = Math.floor(pos / PAGE);
5611
+ const from = pos - page * PAGE;
5612
+ const n = Math.min(PAGE - from, end - pos);
5613
+ const bytes = this.pages.get(page);
5614
+ if (bytes) out.set(bytes.subarray(from, from + n), pos - at);
5615
+ else out.fill(0, pos - at, pos - at + n);
5616
+ pos += n;
5617
+ }
5618
+ return Math.max(0, end - at);
5619
+ }
5620
+ write(at, bytes) {
5621
+ for (let done4 = 0; done4 < bytes.length; ) {
5622
+ const pos = at + done4;
5623
+ const page = Math.floor(pos / PAGE);
5624
+ const from = pos - page * PAGE;
5625
+ const n = Math.min(PAGE - from, bytes.length - done4);
5626
+ let target = this.pages.get(page);
5627
+ if (!target) {
5628
+ target = this.take(0);
5629
+ this.pages.set(page, target);
5630
+ }
5631
+ target.set(bytes.subarray(done4, done4 + n), from);
5632
+ this.dirty.add(page);
5633
+ done4 += n;
5634
+ }
5635
+ this.length = Math.max(this.length, at + bytes.length);
5636
+ }
5637
+ truncate(size) {
5638
+ if (size < this.stored) {
5639
+ this.stored = size;
5640
+ this.shrink = Math.min(this.shrink ?? size, size);
5641
+ }
5642
+ if (size < this.length) {
5643
+ const edge = Math.floor(size / PAGE);
5644
+ for (const page of [...this.pages.keys()]) {
5645
+ if (page > edge || page === edge && size === edge * PAGE) this.drop(page);
5646
+ }
5647
+ this.pages.get(edge)?.fill(0, size - edge * PAGE);
5648
+ }
5649
+ this.length = size;
5650
+ }
5651
+ plan() {
5652
+ const runs = [];
5653
+ const sorted = [...this.dirty].sort((a, b) => a - b);
5654
+ for (let i = 0; i < sorted.length; ) {
5655
+ let j = i + 1;
5656
+ const first = sorted[i];
5657
+ while (j < sorted.length && sorted[j] === first + (j - i) && sorted[j] * PAGE % RUN !== 0) {
5658
+ j++;
5659
+ }
5660
+ const start = first * PAGE;
5661
+ const end = Math.min(this.length, (first + (j - i)) * PAGE);
5662
+ const bytes = new Uint8Array(end - start);
5663
+ for (let k = i; k < j; k++) {
5664
+ const page = this.pages.get(sorted[k]);
5665
+ bytes.set(page.subarray(0, Math.min(PAGE, end - (k - i) * PAGE - start)), (k - i) * PAGE);
5666
+ }
5667
+ runs.push({ offset: start, bytes });
5668
+ i = j;
5669
+ }
5670
+ const written = Math.max(this.stored, ...runs.map((r) => r.offset + r.bytes.length));
5671
+ return {
5672
+ ...this.shrink !== void 0 ? { shrink: this.shrink } : {},
5673
+ runs,
5674
+ ...this.length > written ? { extend: this.length } : {}
5675
+ };
5676
+ }
5677
+ flushed() {
5678
+ this.dirty.clear();
5679
+ this.shrink = void 0;
5680
+ this.stored = this.length;
5681
+ this.evict();
5682
+ }
5683
+ pin() {
5684
+ this.pinned = true;
5685
+ }
5686
+ reset(size) {
5687
+ if (this.pinned) return;
5688
+ for (const page of [...this.pages.keys()]) if (!this.dirty.has(page)) this.drop(page);
5689
+ if (this.changed) return;
5690
+ this.length = size;
5691
+ this.stored = size;
5692
+ }
5693
+ evict() {
5694
+ if (this.pinned) return;
5695
+ let clean = (this.pages.size - this.dirty.size) * PAGE;
5696
+ for (const page of this.pages.keys()) {
5697
+ if (clean <= CLEAN_LIMIT) return;
5698
+ if (this.dirty.has(page)) continue;
5699
+ this.drop(page);
5700
+ clean -= PAGE;
5701
+ }
5702
+ }
5703
+ take(used) {
5704
+ const page = this.free.pop();
5705
+ if (!page) return new Uint8Array(PAGE);
5706
+ page.fill(0, used);
5707
+ return page;
5708
+ }
5709
+ drop(page) {
5710
+ const bytes = this.pages.get(page);
5711
+ this.pages.delete(page);
5712
+ this.dirty.delete(page);
5713
+ if (this.free.length < CLEAN_LIMIT / PAGE) this.free.push(bytes);
5714
+ }
5715
+ };
5716
+
5717
+ // src/fs/ranged.ts
5718
+ var AsyncRangedFile = class {
5719
+ pages;
5720
+ io;
5721
+ pinned = false;
5722
+ path;
5723
+ constructor(io, path, size) {
5724
+ this.io = io;
5725
+ this.path = path;
5726
+ this.pages = new Pages(size);
5727
+ }
5728
+ size() {
5729
+ return this.pages.length;
5730
+ }
5731
+ async fetch(runs) {
5732
+ for (const [start, n] of runs) this.pages.fill(start, await this.io.pread(this.path, start, n));
5733
+ }
5734
+ async read(at, length2) {
5735
+ await this.fetch(this.pages.missing(at, length2));
5736
+ return this.pages.read(at, length2);
5737
+ }
5738
+ async write(at, bytes) {
5739
+ await this.fetch(this.pages.partial(at, bytes.length));
5740
+ this.pages.write(at, bytes);
5741
+ if (!this.pinned && this.pages.dirtyBytes >= WRITEBACK) await this.flush();
5742
+ }
5743
+ truncate(size) {
5744
+ this.pages.truncate(size);
5745
+ }
5746
+ async flush() {
5747
+ if (this.pinned || !this.pages.changed) return;
5748
+ const plan = this.pages.plan();
5749
+ if (plan.shrink !== void 0) await this.io.truncate(this.path, plan.shrink);
5750
+ for (const run2 of plan.runs) await this.io.pwrite(this.path, run2.offset, run2.bytes, true);
5751
+ if (plan.extend !== void 0) await this.io.truncate(this.path, plan.extend);
5752
+ this.pages.flushed();
5753
+ }
5754
+ async pin() {
5755
+ this.pinned = true;
5756
+ this.pages.pin();
5757
+ await this.fetch(this.pages.missing(0, this.pages.length));
5758
+ }
5759
+ };
5760
+
5399
5761
  // src/kernel/vfs-file.ts
5400
5762
  var O_ACCMODE = 3;
5401
5763
  var O_WRONLY = 1;
@@ -5415,6 +5777,7 @@ function within3(path, root) {
5415
5777
  }
5416
5778
  var VfsNode = class {
5417
5779
  data;
5780
+ ranged;
5418
5781
  length = 0;
5419
5782
  dirty = false;
5420
5783
  queue = Promise.resolve();
@@ -5445,6 +5808,23 @@ var VfsNode = class {
5445
5808
  clearTimeout(this.writeBack);
5446
5809
  this.writeBack = void 0;
5447
5810
  this.data = void 0;
5811
+ this.ranged = void 0;
5812
+ }
5813
+ async prepare() {
5814
+ if (this.revoked) throw new KernelError("EIO");
5815
+ if (this.ranged || this.data) return this.ranged;
5816
+ const io = rangedOps(this.fs);
5817
+ const st = io && !this.orphaned ? await this.fs.stat?.(this.path).catch(() => void 0) : void 0;
5818
+ if (io && st?.ranged) {
5819
+ this.ranged = new AsyncRangedFile(io, this.path, st.size ?? 0);
5820
+ return this.ranged;
5821
+ }
5822
+ await this.load();
5823
+ return void 0;
5824
+ }
5825
+ async keep() {
5826
+ const ranged2 = await this.prepare();
5827
+ await ranged2?.pin();
5448
5828
  }
5449
5829
  async load() {
5450
5830
  if (this.revoked) throw new KernelError("EIO");
@@ -5461,22 +5841,26 @@ var VfsNode = class {
5461
5841
  return this.data;
5462
5842
  }
5463
5843
  replace(contents2) {
5844
+ this.ranged = void 0;
5464
5845
  this.data = new Uint8Array(contents2);
5465
5846
  this.length = this.data.length;
5466
5847
  this.missing = false;
5467
5848
  this.markDirty();
5468
5849
  }
5469
5850
  async materialize() {
5470
- await this.load();
5851
+ if (await this.prepare()) return;
5471
5852
  if (!this.missing || this.orphaned) return;
5472
5853
  this.missing = false;
5473
5854
  await this.fs.writeFile(this.path, this.data?.slice(0, this.length) ?? new Uint8Array(0));
5855
+ if (!this.dirty) this.data = void 0;
5474
5856
  }
5475
5857
  async size() {
5476
- await this.load();
5477
- return this.length;
5858
+ const ranged2 = await this.prepare();
5859
+ return ranged2 ? ranged2.size() : this.length;
5478
5860
  }
5479
5861
  async pread(max, at) {
5862
+ const ranged2 = await this.prepare();
5863
+ if (ranged2) return ranged2.read(at, max);
5480
5864
  const bytes = await this.load();
5481
5865
  const n = Math.max(0, Math.min(max, this.length - at));
5482
5866
  return bytes.slice(at, at + n);
@@ -5499,12 +5883,18 @@ var VfsNode = class {
5499
5883
  }
5500
5884
  renamedTo(path) {
5501
5885
  this.path = path;
5886
+ if (this.ranged) this.ranged.path = path;
5502
5887
  this.cap = void 0;
5503
5888
  }
5504
5889
  async pwrite(bytes, at) {
5505
5890
  if (bytes.length === 0) return 0;
5506
5891
  await this.fits(at + bytes.length);
5507
- await this.load();
5892
+ const ranged2 = await this.prepare();
5893
+ if (ranged2) {
5894
+ await ranged2.write(at, bytes);
5895
+ this.markDirty();
5896
+ return bytes.length;
5897
+ }
5508
5898
  const buf = this.ensure(at + bytes.length);
5509
5899
  if (at > this.length) buf.fill(0, this.length, at);
5510
5900
  buf.set(bytes, at);
@@ -5514,7 +5904,12 @@ var VfsNode = class {
5514
5904
  }
5515
5905
  async truncate(size) {
5516
5906
  await this.fits(size);
5517
- await this.load();
5907
+ const ranged2 = await this.prepare();
5908
+ if (ranged2) {
5909
+ ranged2.truncate(size);
5910
+ this.markDirty();
5911
+ return;
5912
+ }
5518
5913
  const buf = this.ensure(size);
5519
5914
  if (size > this.length) buf.fill(0, this.length, size);
5520
5915
  this.length = size;
@@ -5539,11 +5934,12 @@ var VfsNode = class {
5539
5934
  }
5540
5935
  async flush() {
5541
5936
  if (this.revoked && this.dirty) throw new KernelError("EIO");
5542
- if (!this.dirty || !this.data || this.orphaned) return;
5937
+ if (!this.dirty || this.orphaned || !(this.data || this.ranged)) return;
5543
5938
  this.dirty = false;
5544
5939
  const started = performance.now();
5545
5940
  try {
5546
- await this.fs.writeFile(this.path, this.data.slice(0, this.length));
5941
+ if (this.ranged) await this.ranged.flush();
5942
+ else await this.fs.writeFile(this.path, this.data.slice(0, this.length));
5547
5943
  } catch (err) {
5548
5944
  this.dirty = true;
5549
5945
  throw err;
@@ -5590,7 +5986,7 @@ var VfsNodes = class {
5590
5986
  }
5591
5987
  async unlinking(path) {
5592
5988
  const node = this.byPath.get(path);
5593
- if (node) await node.serial(async () => void await Promise.all([node.load(), node.limit()]));
5989
+ if (node) await node.serial(async () => void await Promise.all([node.keep(), node.limit()]));
5594
5990
  }
5595
5991
  revoke(prefix) {
5596
5992
  for (const [path, node] of this.byPath) {
@@ -6739,7 +7135,8 @@ async function dispatch(h, req) {
6739
7135
  case "setattr":
6740
7136
  return h.setattr?.(req.path, {
6741
7137
  ...req.mode !== void 0 ? { mode: req.mode } : {},
6742
- ...req.mtime !== void 0 ? { mtime: req.mtime } : {}
7138
+ ...req.mtime !== void 0 ? { mtime: req.mtime } : {},
7139
+ ...req.size !== void 0 ? { size: req.size } : {}
6743
7140
  });
6744
7141
  case "statfs":
6745
7142
  return h.statfs?.() ?? null;
@@ -6747,7 +7144,7 @@ async function dispatch(h, req) {
6747
7144
  throw fsError2("ENOSYS", `unknown driver op ${req.op}`);
6748
7145
  }
6749
7146
  }
6750
- function serveFilesystem(port, handlers, capabilities = {}) {
7147
+ function serveFilesystem(port, handlers, capabilities = {}, options = {}) {
6751
7148
  let open = false;
6752
7149
  const reply = (message2, transfer = []) => port.postMessage(message2, transfer);
6753
7150
  async function hello(message2) {
@@ -6783,7 +7180,8 @@ function serveFilesystem(port, handlers, capabilities = {}) {
6783
7180
  dispatch(handlers, data).then(
6784
7181
  (result) => {
6785
7182
  if (result instanceof Uint8Array) {
6786
- const bytes = result.slice();
7183
+ const whole = result.byteOffset === 0 && result.byteLength === result.buffer.byteLength;
7184
+ const bytes = options.owned && whole ? result : result.slice();
6787
7185
  reply({ id: data.id, result: bytes }, [bytes.buffer]);
6788
7186
  } else reply({ id: data.id, result: result ?? null });
6789
7187
  },
@@ -7090,28 +7488,34 @@ function wait(ms, signal) {
7090
7488
  });
7091
7489
  }
7092
7490
  async function mountFstab(lines, mount, signal, retries = FSTAB_RETRIES, discard = () => {
7491
+ }, settled = () => {
7093
7492
  }) {
7094
7493
  return Promise.all(
7095
- lines.map(async ({ line, spec, invalid }) => {
7096
- let failed = invalid;
7097
- for (let attempt = 0; !invalid && attempt <= retries.length && !signal.aborted; attempt++) {
7098
- try {
7099
- const entry = await mount(spec);
7100
- if (!signal.aborted) return { line, target: spec.target, entry };
7101
- discard(entry);
7102
- failed = Object.assign(new Error("the kernel stopped"), { code: "ECANCELED" });
7103
- break;
7104
- } catch (err) {
7105
- failed = err;
7106
- if (FINAL.has(codeOf3(err)) || attempt === retries.length) break;
7107
- await wait(retries[attempt], signal);
7108
- }
7109
- }
7110
- const code = codeOf3(failed);
7111
- return { line, target: spec.target, code, error: String(failed?.message ?? code) };
7494
+ lines.map(async (each) => {
7495
+ const result = await mountLine(each, mount, signal, retries, discard);
7496
+ settled(each, result);
7497
+ return result;
7112
7498
  })
7113
7499
  );
7114
7500
  }
7501
+ async function mountLine({ line, spec, invalid }, mount, signal, retries, discard) {
7502
+ let failed = invalid;
7503
+ for (let attempt = 0; !invalid && attempt <= retries.length && !signal.aborted; attempt++) {
7504
+ try {
7505
+ const entry = await mount(spec);
7506
+ if (!signal.aborted) return { line, target: spec.target, entry };
7507
+ discard(entry);
7508
+ failed = Object.assign(new Error("the kernel stopped"), { code: "ECANCELED" });
7509
+ break;
7510
+ } catch (err) {
7511
+ failed = err;
7512
+ if (FINAL.has(codeOf3(err)) || attempt === retries.length) break;
7513
+ await wait(retries[attempt], signal);
7514
+ }
7515
+ }
7516
+ const code = codeOf3(failed);
7517
+ return { line, target: spec.target, code, error: String(failed?.message ?? code) };
7518
+ }
7115
7519
 
7116
7520
  // src/mount/hostfs.ts
7117
7521
  var HOSTFS_IO = 16 * 1024 * 1024;
@@ -7458,6 +7862,58 @@ function memoryMedia() {
7458
7862
  };
7459
7863
  }
7460
7864
 
7865
+ // src/mount/chunked.ts
7866
+ var CHUNK2 = 1024 * 1024;
7867
+ var ChunkedBytes = class {
7868
+ chunks = [];
7869
+ size = 0;
7870
+ read(offset, length2) {
7871
+ const end = Math.min(this.size, offset + length2);
7872
+ if (end <= offset) return new Uint8Array(0);
7873
+ const out = new Uint8Array(end - offset);
7874
+ for (let at = offset; at < end; ) {
7875
+ const index = Math.floor(at / CHUNK2);
7876
+ const from = at - index * CHUNK2;
7877
+ const n = Math.min(CHUNK2 - from, end - at);
7878
+ const chunk2 = this.chunks[index];
7879
+ if (chunk2) out.set(chunk2.subarray(from, Math.min(from + n, chunk2.length)), at - offset);
7880
+ at += n;
7881
+ }
7882
+ return out;
7883
+ }
7884
+ write(offset, bytes, owned = false) {
7885
+ const whole = owned && bytes.byteOffset === 0 && bytes.byteLength === bytes.buffer.byteLength;
7886
+ for (let done4 = 0; done4 < bytes.length; ) {
7887
+ const at = offset + done4;
7888
+ const index = Math.floor(at / CHUNK2);
7889
+ const from = at - index * CHUNK2;
7890
+ const n = Math.min(CHUNK2 - from, bytes.length - done4);
7891
+ if (whole && n === CHUNK2 && bytes.length === CHUNK2) this.chunks[index] = bytes;
7892
+ else this.room(index, from + n).set(bytes.subarray(done4, done4 + n), from);
7893
+ done4 += n;
7894
+ }
7895
+ this.size = Math.max(this.size, offset + bytes.length);
7896
+ }
7897
+ truncate(size) {
7898
+ if (size < this.size) {
7899
+ const keep = Math.ceil(size / CHUNK2);
7900
+ this.chunks.length = Math.min(this.chunks.length, keep);
7901
+ const tail = this.chunks[keep - 1];
7902
+ const from = size - (keep - 1) * CHUNK2;
7903
+ if (tail && from < tail.length) tail.fill(0, from);
7904
+ }
7905
+ this.size = size;
7906
+ }
7907
+ room(index, need) {
7908
+ const chunk2 = this.chunks[index];
7909
+ if (chunk2 && chunk2.length >= need) return chunk2;
7910
+ const grown = new Uint8Array(Math.min(CHUNK2, Math.max(need, (chunk2?.length ?? 0) * 2)));
7911
+ if (chunk2) grown.set(chunk2);
7912
+ this.chunks[index] = grown;
7913
+ return grown;
7914
+ }
7915
+ };
7916
+
7461
7917
  // src/mount/tmpfs.ts
7462
7918
  function tmpfs() {
7463
7919
  let inodes = 0;
@@ -7466,7 +7922,7 @@ function tmpfs() {
7466
7922
  ino: ++inodes,
7467
7923
  mode,
7468
7924
  mtime: Date.now(),
7469
- data: new Uint8Array(0),
7925
+ data: new ChunkedBytes(),
7470
7926
  target: "",
7471
7927
  children: /* @__PURE__ */ new Map()
7472
7928
  });
@@ -7494,7 +7950,7 @@ function tmpfs() {
7494
7950
  }
7495
7951
  const attr = (node) => ({
7496
7952
  kind: node.kind,
7497
- size: node.kind === "file" ? node.data.length : node.kind === "symlink" ? node.target.length : 4096,
7953
+ size: node.kind === "file" ? node.data.size : node.kind === "symlink" ? node.target.length : 4096,
7498
7954
  mtime: node.mtime,
7499
7955
  mode: node.mode,
7500
7956
  ino: node.ino
@@ -7529,32 +7985,25 @@ function tmpfs() {
7529
7985
  },
7530
7986
  async open(path, flags) {
7531
7987
  const node = opened2(path, flags);
7532
- const data = flags.truncate ? new Uint8Array(0) : node.data.slice();
7533
- handles.set(++nextFh, { node, data, length: data.length, dirty: flags.truncate });
7988
+ if (flags.truncate && node.kind === "file") {
7989
+ node.data.truncate(0);
7990
+ node.mtime = Date.now();
7991
+ }
7992
+ handles.set(++nextFh, { node, write: flags.write });
7534
7993
  return nextFh;
7535
7994
  },
7536
7995
  async read(fh, offset, size) {
7537
- const h = handle(fh);
7538
- return h.data.slice(offset, Math.min(h.length, offset + size));
7996
+ return handle(fh).node.data.read(offset, size);
7539
7997
  },
7540
7998
  async write(fh, offset, bytes) {
7541
7999
  const h = handle(fh);
7542
- const end = offset + bytes.length;
7543
- if (end > h.data.length) {
7544
- const grown = new Uint8Array(Math.max(end, h.data.length * 2));
7545
- grown.set(h.data.subarray(0, h.length));
7546
- h.data = grown;
7547
- }
7548
- h.data.set(bytes, offset);
7549
- h.length = Math.max(h.length, end);
7550
- h.dirty = true;
8000
+ if (!h.write) throw fsError2("EBADF", `handle ${fh} is read-only`);
8001
+ h.node.data.write(offset, bytes, true);
8002
+ h.node.mtime = Date.now();
7551
8003
  },
7552
8004
  async release(fh) {
7553
- const h = handle(fh);
8005
+ handle(fh);
7554
8006
  handles.delete(fh);
7555
- if (!h.dirty) return;
7556
- h.node.data = h.data.slice(0, h.length);
7557
- h.node.mtime = Date.now();
7558
8007
  },
7559
8008
  async mkdir(path) {
7560
8009
  const { dir, name: name2 } = parent(path);
@@ -7607,6 +8056,12 @@ function tmpfs() {
7607
8056
  async setattr(path, change) {
7608
8057
  const node = find(path);
7609
8058
  if (change.mode !== void 0) node.mode = node.mode & ~4095 | change.mode & 4095;
8059
+ if (change.size !== void 0) {
8060
+ if (node.kind !== "file")
8061
+ throw fsError2(node.kind === "directory" ? "EISDIR" : "EINVAL", path);
8062
+ node.data.truncate(change.size);
8063
+ node.mtime = Date.now();
8064
+ }
7610
8065
  if (change.mtime !== void 0) node.mtime = change.mtime;
7611
8066
  },
7612
8067
  async statfs() {
@@ -7872,6 +8327,7 @@ var Launcher = class {
7872
8327
  onMountPending;
7873
8328
  removable = /* @__PURE__ */ new Map();
7874
8329
  hostfs;
8330
+ fstabRetries;
7875
8331
  inserted = /* @__PURE__ */ new Map();
7876
8332
  openFiles = /* @__PURE__ */ new Set();
7877
8333
  held = /* @__PURE__ */ new Set();
@@ -7893,6 +8349,7 @@ var Launcher = class {
7893
8349
  this.media = options.media ?? memoryMedia();
7894
8350
  this.onMountPending = options.onMountPending;
7895
8351
  this.hostfs = options;
8352
+ this.fstabRetries = options.fstabRetries ?? FSTAB_RETRIES;
7896
8353
  this.processMounts = options.processMounts ?? true;
7897
8354
  this.pnpmHome = options.env?.PNPM_HOME ?? PNPM_HOME;
7898
8355
  this.modulesDir = options.modules ?? "/node_modules";
@@ -8197,7 +8654,12 @@ var Launcher = class {
8197
8654
  }
8198
8655
  if (type === "tmpfs") {
8199
8656
  const { port1, port2 } = new MessageChannel();
8200
- serveFilesystem(port2, tmpfs(), { symlinks: true, chmod: true, attrTtl: 0 });
8657
+ serveFilesystem(
8658
+ port2,
8659
+ tmpfs(),
8660
+ { symlinks: true, chmod: true, ranges: true, attrTtl: 0 },
8661
+ { owned: true }
8662
+ );
8201
8663
  return {
8202
8664
  port: port1,
8203
8665
  dispose: () => {
@@ -8321,12 +8783,22 @@ var Launcher = class {
8321
8783
  }
8322
8784
  async mountFstab() {
8323
8785
  const text = await this.base.readFile(FSTAB_PATH).catch(() => "");
8786
+ const lines = parseFstab(text);
8787
+ for (const { spec } of lines) this.mounts.note(spec, "pending");
8324
8788
  return mountFstab(
8325
- parseFstab(text),
8326
- (spec) => this.mount(spec),
8789
+ lines,
8790
+ (spec) => this.mount(spec).catch((err) => {
8791
+ this.mounts.note(spec, "pending", err?.message ?? String(err));
8792
+ throw err;
8793
+ }),
8327
8794
  this.booting.signal,
8328
- FSTAB_RETRIES,
8329
- (entry) => this.umount(entry.target, true)
8795
+ this.fstabRetries,
8796
+ (entry) => this.umount(entry.target, true),
8797
+ ({ spec }, result) => {
8798
+ if (!result.code || result.code === "ECANCELED" || this.booting.signal.aborted) {
8799
+ this.mounts.unnote(spec.target);
8800
+ } else this.mounts.note(spec, "failed", result.error);
8801
+ }
8330
8802
  );
8331
8803
  }
8332
8804
  environment(cwd, extra) {
@@ -9297,7 +9769,8 @@ async function createNodeKernel(options = {}) {
9297
9769
  ...options.modules ? { modules: options.modules } : {},
9298
9770
  ...options.env ? { env: options.env } : {},
9299
9771
  ...transport ? { transport } : {},
9300
- ...options.processMounts !== void 0 ? { processMounts: options.processMounts } : {}
9772
+ ...options.processMounts !== void 0 ? { processMounts: options.processMounts } : {},
9773
+ ...options.fstabRetries ? { fstabRetries: options.fstabRetries } : {}
9301
9774
  });
9302
9775
  await launcher.prepare();
9303
9776
  const guard = (work) => new Promise((resolve, reject) => {