@ai-ecoverse/slicc-kernel 1.21.1 → 1.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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,6 +2538,21 @@ 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;
@@ -2805,18 +2832,98 @@ var MountFs = class {
2805
2832
  const chunk2 = mount.caps.maxIo ?? DEFAULT_IO;
2806
2833
  try {
2807
2834
  for (let offset = 0; offset < bytes.length; offset += chunk2) {
2808
- await mount.conn.call({
2809
- op: "write",
2835
+ const part = bytes.slice(offset, offset + chunk2);
2836
+ await mount.conn.call({ op: "write", fh, offset, bytes: part }, [part.buffer]);
2837
+ }
2838
+ } finally {
2839
+ await mount.conn.call({ op: "release", fh });
2840
+ this.table.forget(mount, real);
2841
+ }
2842
+ }
2843
+ async pread(path, offset, length2) {
2844
+ const real = await this.follow(path);
2845
+ const found = this.at(real);
2846
+ if (!found) return (await this.base.readFileBuffer(real)).slice(offset, offset + length2);
2847
+ const { mount, rel: rel2 } = found;
2848
+ if ((await this.table.getattr(mount, real, rel2)).kind === "directory") {
2849
+ throw errnoError("EISDIR", real);
2850
+ }
2851
+ const fh = await this.open(mount, rel2, false);
2852
+ const chunk2 = mount.caps.maxIo ?? DEFAULT_IO;
2853
+ const chunks2 = [];
2854
+ let total = 0;
2855
+ try {
2856
+ while (total < length2) {
2857
+ const bytes = await mount.conn.call({
2858
+ op: "read",
2810
2859
  fh,
2811
- offset,
2812
- bytes: bytes.slice(offset, offset + chunk2)
2860
+ offset: offset + total,
2861
+ size: Math.min(chunk2, length2 - total)
2813
2862
  });
2863
+ if (bytes.length === 0) break;
2864
+ chunks2.push(bytes);
2865
+ total += bytes.length;
2866
+ }
2867
+ } finally {
2868
+ await mount.conn.call({ op: "release", fh });
2869
+ }
2870
+ return concat2(chunks2, total);
2871
+ }
2872
+ fits(mount, real, end) {
2873
+ if (mount.maxFile > 0 && end > mount.maxFile) {
2874
+ throw errnoError(
2875
+ "EFBIG",
2876
+ `${real} would be larger than this mount's maxfile (${mount.maxFile})`
2877
+ );
2878
+ }
2879
+ }
2880
+ async pwrite(path, offset, bytes, transfer = false) {
2881
+ const real = await this.follow(path, 0, true);
2882
+ const found = this.at(real);
2883
+ if (!found)
2884
+ return this.base.writeFile(
2885
+ real,
2886
+ spliced(await this.base.readFileBuffer(real), offset, bytes)
2887
+ );
2888
+ const { mount, rel: rel2 } = found;
2889
+ this.writable(mount, real);
2890
+ this.fits(mount, real, offset + bytes.length);
2891
+ this.table.forget(mount, real);
2892
+ const fh = await mount.conn.call({
2893
+ op: "open",
2894
+ path: rel2,
2895
+ write: true,
2896
+ create: true,
2897
+ truncate: false,
2898
+ exclusive: false
2899
+ });
2900
+ const chunk2 = mount.caps.maxIo ?? DEFAULT_IO;
2901
+ try {
2902
+ for (let at = 0; at < bytes.length; at += chunk2) {
2903
+ const part = handed(bytes, transfer, chunk2) ? bytes : bytes.slice(at, at + chunk2);
2904
+ await mount.conn.call({ op: "write", fh, offset: offset + at, bytes: part }, [part.buffer]);
2814
2905
  }
2815
2906
  } finally {
2816
2907
  await mount.conn.call({ op: "release", fh });
2817
2908
  this.table.forget(mount, real);
2818
2909
  }
2819
2910
  }
2911
+ async truncate(path, size) {
2912
+ const real = await this.follow(path);
2913
+ const found = this.at(real);
2914
+ if (!found?.mount.caps.ranges) {
2915
+ return this.writeFile(real, resized(await this.readFileBuffer(real), size));
2916
+ }
2917
+ const { mount, rel: rel2 } = found;
2918
+ this.writable(mount, real);
2919
+ this.fits(mount, real, size);
2920
+ this.table.forget(mount, real);
2921
+ try {
2922
+ await mount.conn.call({ op: "setattr", path: rel2, size });
2923
+ } finally {
2924
+ this.table.forget(mount, real);
2925
+ }
2926
+ }
2820
2927
  async exists(path) {
2821
2928
  if (!this.at(path)) return this.base.exists(path);
2822
2929
  try {
@@ -3000,7 +3107,8 @@ function statJson(s) {
3000
3107
  ino: s.ino,
3001
3108
  ...s.dev !== void 0 ? { dev: s.dev } : {},
3002
3109
  ...s.readonly ? { readonly: true } : {},
3003
- ...s.maxFile ? { maxFile: s.maxFile } : {}
3110
+ ...s.maxFile ? { maxFile: s.maxFile } : {},
3111
+ ...s.ranged ? { ranged: true } : {}
3004
3112
  };
3005
3113
  }
3006
3114
  async function readdirStat(fs, dir) {
@@ -3065,10 +3173,24 @@ async function run(entry, path, req) {
3065
3173
  case "utimes":
3066
3174
  await fs.utimes(path, new Date(req.atimeMs ?? 0), new Date(req.mtimeMs ?? 0));
3067
3175
  return done;
3176
+ case "pread":
3177
+ case "pwrite":
3178
+ case "truncate":
3179
+ return ranged(fs, path, req);
3068
3180
  default:
3069
3181
  return { ok: false, errno: "EINVAL", message: `sync-fs: unknown op '${req.op}'` };
3070
3182
  }
3071
3183
  }
3184
+ async function ranged(fs, path, req) {
3185
+ const ops = rangedOps(fs);
3186
+ if (!ops) throw syncError("ENOSYS", path);
3187
+ const offset = req.offset ?? 0;
3188
+ if (req.op === "pread")
3189
+ return { ok: true, kind: "bytes", bytes: await ops.pread(path, offset, req.length ?? 0) };
3190
+ if (req.op === "pwrite") await ops.pwrite(path, offset, req.body ?? new Uint8Array(0), true);
3191
+ else await ops.truncate(path, req.length ?? 0);
3192
+ return done;
3193
+ }
3072
3194
  async function dispatchSyncFs(req) {
3073
3195
  const entry = resolveSyncFsToken(req.token);
3074
3196
  if (!entry) return { ok: false, errno: "EACCES", message: "sync-fs: unknown or revoked token" };
@@ -5396,6 +5518,226 @@ async function accept(proc, fd, nonblock) {
5396
5518
  return { fd: proc.fds.install(new OpenFile(socket), 3), peer: socket.peer };
5397
5519
  }
5398
5520
 
5521
+ // src/fs/pages.ts
5522
+ var PAGE = 64 * 1024;
5523
+ var READAHEAD = 1024 * 1024;
5524
+ var CLEAN_LIMIT = 4 * 1024 * 1024;
5525
+ var WRITEBACK = 8 * 1024 * 1024;
5526
+ var RUN = 1024 * 1024;
5527
+ var Pages = class {
5528
+ length;
5529
+ stored;
5530
+ shrink;
5531
+ pages = /* @__PURE__ */ new Map();
5532
+ dirty = /* @__PURE__ */ new Set();
5533
+ free = [];
5534
+ pinned = false;
5535
+ constructor(size) {
5536
+ this.length = size;
5537
+ this.stored = size;
5538
+ }
5539
+ get dirtyBytes() {
5540
+ return this.dirty.size * PAGE;
5541
+ }
5542
+ get changed() {
5543
+ return this.dirty.size > 0 || this.shrink !== void 0 || this.length !== this.stored;
5544
+ }
5545
+ missing(at, length2) {
5546
+ this.evict();
5547
+ const end = Math.min(this.stored, at + length2, this.length);
5548
+ const runs = [];
5549
+ for (let page = Math.floor(at / PAGE); page * PAGE < end; page++) {
5550
+ if (this.pages.has(page)) continue;
5551
+ const last2 = runs[runs.length - 1];
5552
+ if (last2 && last2[0] + last2[1] === page * PAGE) last2[1] += PAGE;
5553
+ else runs.push([page * PAGE, PAGE]);
5554
+ }
5555
+ const last = runs[runs.length - 1];
5556
+ if (last) last[1] = Math.max(last[1], Math.min(READAHEAD, this.stored - last[0]));
5557
+ return runs.map(([start, n]) => [start, Math.min(n, this.stored - start)]);
5558
+ }
5559
+ partial(at, length2) {
5560
+ if (length2 === 0) return [];
5561
+ this.evict();
5562
+ const edges = /* @__PURE__ */ new Set([Math.floor(at / PAGE), Math.floor((at + length2 - 1) / PAGE)]);
5563
+ const runs = [];
5564
+ for (const page of edges) {
5565
+ const start = page * PAGE;
5566
+ const covered = at <= start && at + length2 >= start + PAGE;
5567
+ if (covered || this.pages.has(page) || start >= this.stored) continue;
5568
+ runs.push([start, Math.min(PAGE, this.stored - start)]);
5569
+ }
5570
+ return runs;
5571
+ }
5572
+ fill(start, bytes) {
5573
+ for (let at = 0; at < bytes.length; at += PAGE) {
5574
+ const page = (start + at) / PAGE;
5575
+ if (this.pages.has(page)) continue;
5576
+ const part = bytes.subarray(at, Math.min(at + PAGE, bytes.length));
5577
+ const fresh = this.take(part.length);
5578
+ fresh.set(part);
5579
+ this.pages.set(page, fresh);
5580
+ }
5581
+ }
5582
+ read(at, length2) {
5583
+ const out = new Uint8Array(Math.max(0, Math.min(this.length, at + length2) - at));
5584
+ this.readInto(at, out);
5585
+ return out;
5586
+ }
5587
+ readInto(at, out) {
5588
+ const end = Math.min(this.length, at + out.length);
5589
+ for (let pos = at; pos < end; ) {
5590
+ const page = Math.floor(pos / PAGE);
5591
+ const from = pos - page * PAGE;
5592
+ const n = Math.min(PAGE - from, end - pos);
5593
+ const bytes = this.pages.get(page);
5594
+ if (bytes) out.set(bytes.subarray(from, from + n), pos - at);
5595
+ else out.fill(0, pos - at, pos - at + n);
5596
+ pos += n;
5597
+ }
5598
+ return Math.max(0, end - at);
5599
+ }
5600
+ write(at, bytes) {
5601
+ for (let done4 = 0; done4 < bytes.length; ) {
5602
+ const pos = at + done4;
5603
+ const page = Math.floor(pos / PAGE);
5604
+ const from = pos - page * PAGE;
5605
+ const n = Math.min(PAGE - from, bytes.length - done4);
5606
+ let target = this.pages.get(page);
5607
+ if (!target) {
5608
+ target = this.take(0);
5609
+ this.pages.set(page, target);
5610
+ }
5611
+ target.set(bytes.subarray(done4, done4 + n), from);
5612
+ this.dirty.add(page);
5613
+ done4 += n;
5614
+ }
5615
+ this.length = Math.max(this.length, at + bytes.length);
5616
+ }
5617
+ truncate(size) {
5618
+ if (size < this.stored) {
5619
+ this.stored = size;
5620
+ this.shrink = Math.min(this.shrink ?? size, size);
5621
+ }
5622
+ if (size < this.length) {
5623
+ const edge = Math.floor(size / PAGE);
5624
+ for (const page of [...this.pages.keys()]) {
5625
+ if (page > edge || page === edge && size === edge * PAGE) this.drop(page);
5626
+ }
5627
+ this.pages.get(edge)?.fill(0, size - edge * PAGE);
5628
+ }
5629
+ this.length = size;
5630
+ }
5631
+ plan() {
5632
+ const runs = [];
5633
+ const sorted = [...this.dirty].sort((a, b) => a - b);
5634
+ for (let i = 0; i < sorted.length; ) {
5635
+ let j = i + 1;
5636
+ const first = sorted[i];
5637
+ while (j < sorted.length && sorted[j] === first + (j - i) && sorted[j] * PAGE % RUN !== 0) {
5638
+ j++;
5639
+ }
5640
+ const start = first * PAGE;
5641
+ const end = Math.min(this.length, (first + (j - i)) * PAGE);
5642
+ const bytes = new Uint8Array(end - start);
5643
+ for (let k = i; k < j; k++) {
5644
+ const page = this.pages.get(sorted[k]);
5645
+ bytes.set(page.subarray(0, Math.min(PAGE, end - (k - i) * PAGE - start)), (k - i) * PAGE);
5646
+ }
5647
+ runs.push({ offset: start, bytes });
5648
+ i = j;
5649
+ }
5650
+ const written = Math.max(this.stored, ...runs.map((r) => r.offset + r.bytes.length));
5651
+ return {
5652
+ ...this.shrink !== void 0 ? { shrink: this.shrink } : {},
5653
+ runs,
5654
+ ...this.length > written ? { extend: this.length } : {}
5655
+ };
5656
+ }
5657
+ flushed() {
5658
+ this.dirty.clear();
5659
+ this.shrink = void 0;
5660
+ this.stored = this.length;
5661
+ this.evict();
5662
+ }
5663
+ pin() {
5664
+ this.pinned = true;
5665
+ }
5666
+ reset(size) {
5667
+ if (this.pinned) return;
5668
+ for (const page of [...this.pages.keys()]) if (!this.dirty.has(page)) this.drop(page);
5669
+ if (this.changed) return;
5670
+ this.length = size;
5671
+ this.stored = size;
5672
+ }
5673
+ evict() {
5674
+ if (this.pinned) return;
5675
+ let clean = (this.pages.size - this.dirty.size) * PAGE;
5676
+ for (const page of this.pages.keys()) {
5677
+ if (clean <= CLEAN_LIMIT) return;
5678
+ if (this.dirty.has(page)) continue;
5679
+ this.drop(page);
5680
+ clean -= PAGE;
5681
+ }
5682
+ }
5683
+ take(used) {
5684
+ const page = this.free.pop();
5685
+ if (!page) return new Uint8Array(PAGE);
5686
+ page.fill(0, used);
5687
+ return page;
5688
+ }
5689
+ drop(page) {
5690
+ const bytes = this.pages.get(page);
5691
+ this.pages.delete(page);
5692
+ this.dirty.delete(page);
5693
+ if (this.free.length < CLEAN_LIMIT / PAGE) this.free.push(bytes);
5694
+ }
5695
+ };
5696
+
5697
+ // src/fs/ranged.ts
5698
+ var AsyncRangedFile = class {
5699
+ pages;
5700
+ io;
5701
+ pinned = false;
5702
+ path;
5703
+ constructor(io, path, size) {
5704
+ this.io = io;
5705
+ this.path = path;
5706
+ this.pages = new Pages(size);
5707
+ }
5708
+ size() {
5709
+ return this.pages.length;
5710
+ }
5711
+ async fetch(runs) {
5712
+ for (const [start, n] of runs) this.pages.fill(start, await this.io.pread(this.path, start, n));
5713
+ }
5714
+ async read(at, length2) {
5715
+ await this.fetch(this.pages.missing(at, length2));
5716
+ return this.pages.read(at, length2);
5717
+ }
5718
+ async write(at, bytes) {
5719
+ await this.fetch(this.pages.partial(at, bytes.length));
5720
+ this.pages.write(at, bytes);
5721
+ if (!this.pinned && this.pages.dirtyBytes >= WRITEBACK) await this.flush();
5722
+ }
5723
+ truncate(size) {
5724
+ this.pages.truncate(size);
5725
+ }
5726
+ async flush() {
5727
+ if (this.pinned || !this.pages.changed) return;
5728
+ const plan = this.pages.plan();
5729
+ if (plan.shrink !== void 0) await this.io.truncate(this.path, plan.shrink);
5730
+ for (const run2 of plan.runs) await this.io.pwrite(this.path, run2.offset, run2.bytes, true);
5731
+ if (plan.extend !== void 0) await this.io.truncate(this.path, plan.extend);
5732
+ this.pages.flushed();
5733
+ }
5734
+ async pin() {
5735
+ this.pinned = true;
5736
+ this.pages.pin();
5737
+ await this.fetch(this.pages.missing(0, this.pages.length));
5738
+ }
5739
+ };
5740
+
5399
5741
  // src/kernel/vfs-file.ts
5400
5742
  var O_ACCMODE = 3;
5401
5743
  var O_WRONLY = 1;
@@ -5415,6 +5757,7 @@ function within3(path, root) {
5415
5757
  }
5416
5758
  var VfsNode = class {
5417
5759
  data;
5760
+ ranged;
5418
5761
  length = 0;
5419
5762
  dirty = false;
5420
5763
  queue = Promise.resolve();
@@ -5445,6 +5788,23 @@ var VfsNode = class {
5445
5788
  clearTimeout(this.writeBack);
5446
5789
  this.writeBack = void 0;
5447
5790
  this.data = void 0;
5791
+ this.ranged = void 0;
5792
+ }
5793
+ async prepare() {
5794
+ if (this.revoked) throw new KernelError("EIO");
5795
+ if (this.ranged || this.data) return this.ranged;
5796
+ const io = rangedOps(this.fs);
5797
+ const st = io && !this.orphaned ? await this.fs.stat?.(this.path).catch(() => void 0) : void 0;
5798
+ if (io && st?.ranged) {
5799
+ this.ranged = new AsyncRangedFile(io, this.path, st.size ?? 0);
5800
+ return this.ranged;
5801
+ }
5802
+ await this.load();
5803
+ return void 0;
5804
+ }
5805
+ async keep() {
5806
+ const ranged2 = await this.prepare();
5807
+ await ranged2?.pin();
5448
5808
  }
5449
5809
  async load() {
5450
5810
  if (this.revoked) throw new KernelError("EIO");
@@ -5461,22 +5821,26 @@ var VfsNode = class {
5461
5821
  return this.data;
5462
5822
  }
5463
5823
  replace(contents2) {
5824
+ this.ranged = void 0;
5464
5825
  this.data = new Uint8Array(contents2);
5465
5826
  this.length = this.data.length;
5466
5827
  this.missing = false;
5467
5828
  this.markDirty();
5468
5829
  }
5469
5830
  async materialize() {
5470
- await this.load();
5831
+ if (await this.prepare()) return;
5471
5832
  if (!this.missing || this.orphaned) return;
5472
5833
  this.missing = false;
5473
5834
  await this.fs.writeFile(this.path, this.data?.slice(0, this.length) ?? new Uint8Array(0));
5835
+ if (!this.dirty) this.data = void 0;
5474
5836
  }
5475
5837
  async size() {
5476
- await this.load();
5477
- return this.length;
5838
+ const ranged2 = await this.prepare();
5839
+ return ranged2 ? ranged2.size() : this.length;
5478
5840
  }
5479
5841
  async pread(max, at) {
5842
+ const ranged2 = await this.prepare();
5843
+ if (ranged2) return ranged2.read(at, max);
5480
5844
  const bytes = await this.load();
5481
5845
  const n = Math.max(0, Math.min(max, this.length - at));
5482
5846
  return bytes.slice(at, at + n);
@@ -5499,12 +5863,18 @@ var VfsNode = class {
5499
5863
  }
5500
5864
  renamedTo(path) {
5501
5865
  this.path = path;
5866
+ if (this.ranged) this.ranged.path = path;
5502
5867
  this.cap = void 0;
5503
5868
  }
5504
5869
  async pwrite(bytes, at) {
5505
5870
  if (bytes.length === 0) return 0;
5506
5871
  await this.fits(at + bytes.length);
5507
- await this.load();
5872
+ const ranged2 = await this.prepare();
5873
+ if (ranged2) {
5874
+ await ranged2.write(at, bytes);
5875
+ this.markDirty();
5876
+ return bytes.length;
5877
+ }
5508
5878
  const buf = this.ensure(at + bytes.length);
5509
5879
  if (at > this.length) buf.fill(0, this.length, at);
5510
5880
  buf.set(bytes, at);
@@ -5514,7 +5884,12 @@ var VfsNode = class {
5514
5884
  }
5515
5885
  async truncate(size) {
5516
5886
  await this.fits(size);
5517
- await this.load();
5887
+ const ranged2 = await this.prepare();
5888
+ if (ranged2) {
5889
+ ranged2.truncate(size);
5890
+ this.markDirty();
5891
+ return;
5892
+ }
5518
5893
  const buf = this.ensure(size);
5519
5894
  if (size > this.length) buf.fill(0, this.length, size);
5520
5895
  this.length = size;
@@ -5539,11 +5914,12 @@ var VfsNode = class {
5539
5914
  }
5540
5915
  async flush() {
5541
5916
  if (this.revoked && this.dirty) throw new KernelError("EIO");
5542
- if (!this.dirty || !this.data || this.orphaned) return;
5917
+ if (!this.dirty || this.orphaned || !(this.data || this.ranged)) return;
5543
5918
  this.dirty = false;
5544
5919
  const started = performance.now();
5545
5920
  try {
5546
- await this.fs.writeFile(this.path, this.data.slice(0, this.length));
5921
+ if (this.ranged) await this.ranged.flush();
5922
+ else await this.fs.writeFile(this.path, this.data.slice(0, this.length));
5547
5923
  } catch (err) {
5548
5924
  this.dirty = true;
5549
5925
  throw err;
@@ -5590,7 +5966,7 @@ var VfsNodes = class {
5590
5966
  }
5591
5967
  async unlinking(path) {
5592
5968
  const node = this.byPath.get(path);
5593
- if (node) await node.serial(async () => void await Promise.all([node.load(), node.limit()]));
5969
+ if (node) await node.serial(async () => void await Promise.all([node.keep(), node.limit()]));
5594
5970
  }
5595
5971
  revoke(prefix) {
5596
5972
  for (const [path, node] of this.byPath) {
@@ -6739,7 +7115,8 @@ async function dispatch(h, req) {
6739
7115
  case "setattr":
6740
7116
  return h.setattr?.(req.path, {
6741
7117
  ...req.mode !== void 0 ? { mode: req.mode } : {},
6742
- ...req.mtime !== void 0 ? { mtime: req.mtime } : {}
7118
+ ...req.mtime !== void 0 ? { mtime: req.mtime } : {},
7119
+ ...req.size !== void 0 ? { size: req.size } : {}
6743
7120
  });
6744
7121
  case "statfs":
6745
7122
  return h.statfs?.() ?? null;
@@ -6747,7 +7124,7 @@ async function dispatch(h, req) {
6747
7124
  throw fsError2("ENOSYS", `unknown driver op ${req.op}`);
6748
7125
  }
6749
7126
  }
6750
- function serveFilesystem(port, handlers, capabilities = {}) {
7127
+ function serveFilesystem(port, handlers, capabilities = {}, options = {}) {
6751
7128
  let open = false;
6752
7129
  const reply = (message2, transfer = []) => port.postMessage(message2, transfer);
6753
7130
  async function hello(message2) {
@@ -6783,7 +7160,8 @@ function serveFilesystem(port, handlers, capabilities = {}) {
6783
7160
  dispatch(handlers, data).then(
6784
7161
  (result) => {
6785
7162
  if (result instanceof Uint8Array) {
6786
- const bytes = result.slice();
7163
+ const whole = result.byteOffset === 0 && result.byteLength === result.buffer.byteLength;
7164
+ const bytes = options.owned && whole ? result : result.slice();
6787
7165
  reply({ id: data.id, result: bytes }, [bytes.buffer]);
6788
7166
  } else reply({ id: data.id, result: result ?? null });
6789
7167
  },
@@ -7458,6 +7836,58 @@ function memoryMedia() {
7458
7836
  };
7459
7837
  }
7460
7838
 
7839
+ // src/mount/chunked.ts
7840
+ var CHUNK2 = 1024 * 1024;
7841
+ var ChunkedBytes = class {
7842
+ chunks = [];
7843
+ size = 0;
7844
+ read(offset, length2) {
7845
+ const end = Math.min(this.size, offset + length2);
7846
+ if (end <= offset) return new Uint8Array(0);
7847
+ const out = new Uint8Array(end - offset);
7848
+ for (let at = offset; at < end; ) {
7849
+ const index = Math.floor(at / CHUNK2);
7850
+ const from = at - index * CHUNK2;
7851
+ const n = Math.min(CHUNK2 - from, end - at);
7852
+ const chunk2 = this.chunks[index];
7853
+ if (chunk2) out.set(chunk2.subarray(from, Math.min(from + n, chunk2.length)), at - offset);
7854
+ at += n;
7855
+ }
7856
+ return out;
7857
+ }
7858
+ write(offset, bytes, owned = false) {
7859
+ const whole = owned && bytes.byteOffset === 0 && bytes.byteLength === bytes.buffer.byteLength;
7860
+ for (let done4 = 0; done4 < bytes.length; ) {
7861
+ const at = offset + done4;
7862
+ const index = Math.floor(at / CHUNK2);
7863
+ const from = at - index * CHUNK2;
7864
+ const n = Math.min(CHUNK2 - from, bytes.length - done4);
7865
+ if (whole && n === CHUNK2 && bytes.length === CHUNK2) this.chunks[index] = bytes;
7866
+ else this.room(index, from + n).set(bytes.subarray(done4, done4 + n), from);
7867
+ done4 += n;
7868
+ }
7869
+ this.size = Math.max(this.size, offset + bytes.length);
7870
+ }
7871
+ truncate(size) {
7872
+ if (size < this.size) {
7873
+ const keep = Math.ceil(size / CHUNK2);
7874
+ this.chunks.length = Math.min(this.chunks.length, keep);
7875
+ const tail = this.chunks[keep - 1];
7876
+ const from = size - (keep - 1) * CHUNK2;
7877
+ if (tail && from < tail.length) tail.fill(0, from);
7878
+ }
7879
+ this.size = size;
7880
+ }
7881
+ room(index, need) {
7882
+ const chunk2 = this.chunks[index];
7883
+ if (chunk2 && chunk2.length >= need) return chunk2;
7884
+ const grown = new Uint8Array(Math.min(CHUNK2, Math.max(need, (chunk2?.length ?? 0) * 2)));
7885
+ if (chunk2) grown.set(chunk2);
7886
+ this.chunks[index] = grown;
7887
+ return grown;
7888
+ }
7889
+ };
7890
+
7461
7891
  // src/mount/tmpfs.ts
7462
7892
  function tmpfs() {
7463
7893
  let inodes = 0;
@@ -7466,7 +7896,7 @@ function tmpfs() {
7466
7896
  ino: ++inodes,
7467
7897
  mode,
7468
7898
  mtime: Date.now(),
7469
- data: new Uint8Array(0),
7899
+ data: new ChunkedBytes(),
7470
7900
  target: "",
7471
7901
  children: /* @__PURE__ */ new Map()
7472
7902
  });
@@ -7494,7 +7924,7 @@ function tmpfs() {
7494
7924
  }
7495
7925
  const attr = (node) => ({
7496
7926
  kind: node.kind,
7497
- size: node.kind === "file" ? node.data.length : node.kind === "symlink" ? node.target.length : 4096,
7927
+ size: node.kind === "file" ? node.data.size : node.kind === "symlink" ? node.target.length : 4096,
7498
7928
  mtime: node.mtime,
7499
7929
  mode: node.mode,
7500
7930
  ino: node.ino
@@ -7529,32 +7959,25 @@ function tmpfs() {
7529
7959
  },
7530
7960
  async open(path, flags) {
7531
7961
  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 });
7962
+ if (flags.truncate && node.kind === "file") {
7963
+ node.data.truncate(0);
7964
+ node.mtime = Date.now();
7965
+ }
7966
+ handles.set(++nextFh, { node, write: flags.write });
7534
7967
  return nextFh;
7535
7968
  },
7536
7969
  async read(fh, offset, size) {
7537
- const h = handle(fh);
7538
- return h.data.slice(offset, Math.min(h.length, offset + size));
7970
+ return handle(fh).node.data.read(offset, size);
7539
7971
  },
7540
7972
  async write(fh, offset, bytes) {
7541
7973
  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;
7974
+ if (!h.write) throw fsError2("EBADF", `handle ${fh} is read-only`);
7975
+ h.node.data.write(offset, bytes, true);
7976
+ h.node.mtime = Date.now();
7551
7977
  },
7552
7978
  async release(fh) {
7553
- const h = handle(fh);
7979
+ handle(fh);
7554
7980
  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
7981
  },
7559
7982
  async mkdir(path) {
7560
7983
  const { dir, name: name2 } = parent(path);
@@ -7607,6 +8030,12 @@ function tmpfs() {
7607
8030
  async setattr(path, change) {
7608
8031
  const node = find(path);
7609
8032
  if (change.mode !== void 0) node.mode = node.mode & ~4095 | change.mode & 4095;
8033
+ if (change.size !== void 0) {
8034
+ if (node.kind !== "file")
8035
+ throw fsError2(node.kind === "directory" ? "EISDIR" : "EINVAL", path);
8036
+ node.data.truncate(change.size);
8037
+ node.mtime = Date.now();
8038
+ }
7610
8039
  if (change.mtime !== void 0) node.mtime = change.mtime;
7611
8040
  },
7612
8041
  async statfs() {
@@ -8197,7 +8626,12 @@ var Launcher = class {
8197
8626
  }
8198
8627
  if (type === "tmpfs") {
8199
8628
  const { port1, port2 } = new MessageChannel();
8200
- serveFilesystem(port2, tmpfs(), { symlinks: true, chmod: true, attrTtl: 0 });
8629
+ serveFilesystem(
8630
+ port2,
8631
+ tmpfs(),
8632
+ { symlinks: true, chmod: true, ranges: true, attrTtl: 0 },
8633
+ { owned: true }
8634
+ );
8201
8635
  return {
8202
8636
  port: port1,
8203
8637
  dispose: () => {