@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.
@@ -252,6 +252,11 @@ function versionError(theirs) {
252
252
  }
253
253
 
254
254
  // src/fs/types.ts
255
+ function rangedOps(fs) {
256
+ const { pread, pwrite, truncate } = fs;
257
+ if (!pread || !pwrite || !truncate) return void 0;
258
+ return { pread: pread.bind(fs), pwrite: pwrite.bind(fs), truncate: truncate.bind(fs) };
259
+ }
255
260
  function fsError(code, path) {
256
261
  return Object.assign(new Error(`${code}: ${path}`), { code });
257
262
  }
@@ -3654,7 +3659,8 @@ function withCommandDirs(fs, names) {
3654
3659
  symlink: (target, path) => fs.symlink(target, path),
3655
3660
  readlink: (path) => fs.readlink(path),
3656
3661
  chmod: (path, mode) => fs.chmod(path, mode),
3657
- utimes: (path, atime, mtime) => fs.utimes(path, atime, mtime)
3662
+ utimes: (path, atime, mtime) => fs.utimes(path, atime, mtime),
3663
+ ...rangedOps(fs) ?? {}
3658
3664
  };
3659
3665
  }
3660
3666
 
@@ -3731,6 +3737,11 @@ var FsWatchers = class {
3731
3737
  chmod: after(fs.chmod.bind(fs), (path) => [path]),
3732
3738
  utimes: after(fs.utimes.bind(fs), (path) => [path])
3733
3739
  };
3740
+ const ranged2 = rangedOps(fs);
3741
+ if (ranged2) {
3742
+ overrides.pwrite = after(ranged2.pwrite, (path) => [path]);
3743
+ overrides.truncate = after(ranged2.truncate, (path) => [path]);
3744
+ }
3734
3745
  return new Proxy(fs, {
3735
3746
  get(target, key, receiver) {
3736
3747
  if (Object.hasOwn(overrides, key)) return overrides[key];
@@ -3925,7 +3936,7 @@ var ChildTable = class {
3925
3936
  };
3926
3937
 
3927
3938
  // src/mount/protocol.ts
3928
- var DRIVER_PROTOCOL = [1, 0];
3939
+ var DRIVER_PROTOCOL = [1, 1];
3929
3940
  function driverVersionError(theirs) {
3930
3941
  const major = Array.isArray(theirs) ? theirs[0] : void 0;
3931
3942
  if (major === DRIVER_PROTOCOL[0]) return void 0;
@@ -4084,7 +4095,8 @@ function statOf(attr, path, mount) {
4084
4095
  ino: attr.ino ?? inodeOf(path),
4085
4096
  dev: mount.dev,
4086
4097
  ...readonly(mount) ? { readonly: true } : {},
4087
- ...mount.maxFile > 0 ? { maxFile: mount.maxFile } : {}
4098
+ ...mount.maxFile > 0 ? { maxFile: mount.maxFile } : {},
4099
+ ...mount.caps.ranges ? { ranged: true } : {}
4088
4100
  };
4089
4101
  }
4090
4102
  function concat4(chunks, total) {
@@ -4097,6 +4109,21 @@ function concat4(chunks, total) {
4097
4109
  }
4098
4110
  return out;
4099
4111
  }
4112
+ function resized(bytes, size) {
4113
+ if (size <= bytes.length) return bytes.subarray(0, size);
4114
+ const out = new Uint8Array(size);
4115
+ out.set(bytes);
4116
+ return out;
4117
+ }
4118
+ function handed(bytes, transfer, chunk2) {
4119
+ const whole = bytes.byteOffset === 0 && bytes.byteLength === bytes.buffer.byteLength;
4120
+ return transfer && whole && bytes.length <= chunk2 && bytes.buffer instanceof ArrayBuffer;
4121
+ }
4122
+ function spliced(bytes, offset, part) {
4123
+ const out = resized(bytes, Math.max(bytes.length, offset + part.length));
4124
+ out.set(part, offset);
4125
+ return out;
4126
+ }
4100
4127
  var MountTable = class {
4101
4128
  mounts = /* @__PURE__ */ new Map();
4102
4129
  devices = 0;
@@ -4376,18 +4403,98 @@ var MountFs = class {
4376
4403
  const chunk2 = mount.caps.maxIo ?? DEFAULT_IO;
4377
4404
  try {
4378
4405
  for (let offset = 0; offset < bytes.length; offset += chunk2) {
4379
- await mount.conn.call({
4380
- op: "write",
4406
+ const part = bytes.slice(offset, offset + chunk2);
4407
+ await mount.conn.call({ op: "write", fh, offset, bytes: part }, [part.buffer]);
4408
+ }
4409
+ } finally {
4410
+ await mount.conn.call({ op: "release", fh });
4411
+ this.table.forget(mount, real);
4412
+ }
4413
+ }
4414
+ async pread(path, offset, length2) {
4415
+ const real = await this.follow(path);
4416
+ const found = this.at(real);
4417
+ if (!found) return (await this.base.readFileBuffer(real)).slice(offset, offset + length2);
4418
+ const { mount, rel: rel2 } = found;
4419
+ if ((await this.table.getattr(mount, real, rel2)).kind === "directory") {
4420
+ throw errnoError("EISDIR", real);
4421
+ }
4422
+ const fh = await this.open(mount, rel2, false);
4423
+ const chunk2 = mount.caps.maxIo ?? DEFAULT_IO;
4424
+ const chunks = [];
4425
+ let total = 0;
4426
+ try {
4427
+ while (total < length2) {
4428
+ const bytes = await mount.conn.call({
4429
+ op: "read",
4381
4430
  fh,
4382
- offset,
4383
- bytes: bytes.slice(offset, offset + chunk2)
4431
+ offset: offset + total,
4432
+ size: Math.min(chunk2, length2 - total)
4384
4433
  });
4434
+ if (bytes.length === 0) break;
4435
+ chunks.push(bytes);
4436
+ total += bytes.length;
4437
+ }
4438
+ } finally {
4439
+ await mount.conn.call({ op: "release", fh });
4440
+ }
4441
+ return concat4(chunks, total);
4442
+ }
4443
+ fits(mount, real, end) {
4444
+ if (mount.maxFile > 0 && end > mount.maxFile) {
4445
+ throw errnoError(
4446
+ "EFBIG",
4447
+ `${real} would be larger than this mount's maxfile (${mount.maxFile})`
4448
+ );
4449
+ }
4450
+ }
4451
+ async pwrite(path, offset, bytes, transfer = false) {
4452
+ const real = await this.follow(path, 0, true);
4453
+ const found = this.at(real);
4454
+ if (!found)
4455
+ return this.base.writeFile(
4456
+ real,
4457
+ spliced(await this.base.readFileBuffer(real), offset, bytes)
4458
+ );
4459
+ const { mount, rel: rel2 } = found;
4460
+ this.writable(mount, real);
4461
+ this.fits(mount, real, offset + bytes.length);
4462
+ this.table.forget(mount, real);
4463
+ const fh = await mount.conn.call({
4464
+ op: "open",
4465
+ path: rel2,
4466
+ write: true,
4467
+ create: true,
4468
+ truncate: false,
4469
+ exclusive: false
4470
+ });
4471
+ const chunk2 = mount.caps.maxIo ?? DEFAULT_IO;
4472
+ try {
4473
+ for (let at = 0; at < bytes.length; at += chunk2) {
4474
+ const part = handed(bytes, transfer, chunk2) ? bytes : bytes.slice(at, at + chunk2);
4475
+ await mount.conn.call({ op: "write", fh, offset: offset + at, bytes: part }, [part.buffer]);
4385
4476
  }
4386
4477
  } finally {
4387
4478
  await mount.conn.call({ op: "release", fh });
4388
4479
  this.table.forget(mount, real);
4389
4480
  }
4390
4481
  }
4482
+ async truncate(path, size) {
4483
+ const real = await this.follow(path);
4484
+ const found = this.at(real);
4485
+ if (!found?.mount.caps.ranges) {
4486
+ return this.writeFile(real, resized(await this.readFileBuffer(real), size));
4487
+ }
4488
+ const { mount, rel: rel2 } = found;
4489
+ this.writable(mount, real);
4490
+ this.fits(mount, real, size);
4491
+ this.table.forget(mount, real);
4492
+ try {
4493
+ await mount.conn.call({ op: "setattr", path: rel2, size });
4494
+ } finally {
4495
+ this.table.forget(mount, real);
4496
+ }
4497
+ }
4391
4498
  async exists(path) {
4392
4499
  if (!this.at(path)) return this.base.exists(path);
4393
4500
  try {
@@ -4571,7 +4678,8 @@ function statJson(s) {
4571
4678
  ino: s.ino,
4572
4679
  ...s.dev !== void 0 ? { dev: s.dev } : {},
4573
4680
  ...s.readonly ? { readonly: true } : {},
4574
- ...s.maxFile ? { maxFile: s.maxFile } : {}
4681
+ ...s.maxFile ? { maxFile: s.maxFile } : {},
4682
+ ...s.ranged ? { ranged: true } : {}
4575
4683
  };
4576
4684
  }
4577
4685
  async function readdirStat(fs, dir) {
@@ -4636,10 +4744,24 @@ async function run(entry, path, req) {
4636
4744
  case "utimes":
4637
4745
  await fs.utimes(path, new Date(req.atimeMs ?? 0), new Date(req.mtimeMs ?? 0));
4638
4746
  return done;
4747
+ case "pread":
4748
+ case "pwrite":
4749
+ case "truncate":
4750
+ return ranged(fs, path, req);
4639
4751
  default:
4640
4752
  return { ok: false, errno: "EINVAL", message: `sync-fs: unknown op '${req.op}'` };
4641
4753
  }
4642
4754
  }
4755
+ async function ranged(fs, path, req) {
4756
+ const ops = rangedOps(fs);
4757
+ if (!ops) throw syncError("ENOSYS", path);
4758
+ const offset = req.offset ?? 0;
4759
+ if (req.op === "pread")
4760
+ return { ok: true, kind: "bytes", bytes: await ops.pread(path, offset, req.length ?? 0) };
4761
+ if (req.op === "pwrite") await ops.pwrite(path, offset, req.body ?? new Uint8Array(0), true);
4762
+ else await ops.truncate(path, req.length ?? 0);
4763
+ return done;
4764
+ }
4643
4765
  async function dispatchSyncFs(req) {
4644
4766
  const entry = resolveSyncFsToken(req.token);
4645
4767
  if (!entry) return { ok: false, errno: "EACCES", message: "sync-fs: unknown or revoked token" };
@@ -5645,6 +5767,226 @@ async function accept(proc, fd, nonblock) {
5645
5767
  return { fd: proc.fds.install(new OpenFile(socket), 3), peer: socket.peer };
5646
5768
  }
5647
5769
 
5770
+ // src/fs/pages.ts
5771
+ var PAGE = 64 * 1024;
5772
+ var READAHEAD = 1024 * 1024;
5773
+ var CLEAN_LIMIT = 4 * 1024 * 1024;
5774
+ var WRITEBACK = 8 * 1024 * 1024;
5775
+ var RUN = 1024 * 1024;
5776
+ var Pages = class {
5777
+ length;
5778
+ stored;
5779
+ shrink;
5780
+ pages = /* @__PURE__ */ new Map();
5781
+ dirty = /* @__PURE__ */ new Set();
5782
+ free = [];
5783
+ pinned = false;
5784
+ constructor(size) {
5785
+ this.length = size;
5786
+ this.stored = size;
5787
+ }
5788
+ get dirtyBytes() {
5789
+ return this.dirty.size * PAGE;
5790
+ }
5791
+ get changed() {
5792
+ return this.dirty.size > 0 || this.shrink !== void 0 || this.length !== this.stored;
5793
+ }
5794
+ missing(at, length2) {
5795
+ this.evict();
5796
+ const end = Math.min(this.stored, at + length2, this.length);
5797
+ const runs = [];
5798
+ for (let page = Math.floor(at / PAGE); page * PAGE < end; page++) {
5799
+ if (this.pages.has(page)) continue;
5800
+ const last2 = runs[runs.length - 1];
5801
+ if (last2 && last2[0] + last2[1] === page * PAGE) last2[1] += PAGE;
5802
+ else runs.push([page * PAGE, PAGE]);
5803
+ }
5804
+ const last = runs[runs.length - 1];
5805
+ if (last) last[1] = Math.max(last[1], Math.min(READAHEAD, this.stored - last[0]));
5806
+ return runs.map(([start, n]) => [start, Math.min(n, this.stored - start)]);
5807
+ }
5808
+ partial(at, length2) {
5809
+ if (length2 === 0) return [];
5810
+ this.evict();
5811
+ const edges = /* @__PURE__ */ new Set([Math.floor(at / PAGE), Math.floor((at + length2 - 1) / PAGE)]);
5812
+ const runs = [];
5813
+ for (const page of edges) {
5814
+ const start = page * PAGE;
5815
+ const covered = at <= start && at + length2 >= start + PAGE;
5816
+ if (covered || this.pages.has(page) || start >= this.stored) continue;
5817
+ runs.push([start, Math.min(PAGE, this.stored - start)]);
5818
+ }
5819
+ return runs;
5820
+ }
5821
+ fill(start, bytes) {
5822
+ for (let at = 0; at < bytes.length; at += PAGE) {
5823
+ const page = (start + at) / PAGE;
5824
+ if (this.pages.has(page)) continue;
5825
+ const part = bytes.subarray(at, Math.min(at + PAGE, bytes.length));
5826
+ const fresh = this.take(part.length);
5827
+ fresh.set(part);
5828
+ this.pages.set(page, fresh);
5829
+ }
5830
+ }
5831
+ read(at, length2) {
5832
+ const out = new Uint8Array(Math.max(0, Math.min(this.length, at + length2) - at));
5833
+ this.readInto(at, out);
5834
+ return out;
5835
+ }
5836
+ readInto(at, out) {
5837
+ const end = Math.min(this.length, at + out.length);
5838
+ for (let pos = at; pos < end; ) {
5839
+ const page = Math.floor(pos / PAGE);
5840
+ const from = pos - page * PAGE;
5841
+ const n = Math.min(PAGE - from, end - pos);
5842
+ const bytes = this.pages.get(page);
5843
+ if (bytes) out.set(bytes.subarray(from, from + n), pos - at);
5844
+ else out.fill(0, pos - at, pos - at + n);
5845
+ pos += n;
5846
+ }
5847
+ return Math.max(0, end - at);
5848
+ }
5849
+ write(at, bytes) {
5850
+ for (let done4 = 0; done4 < bytes.length; ) {
5851
+ const pos = at + done4;
5852
+ const page = Math.floor(pos / PAGE);
5853
+ const from = pos - page * PAGE;
5854
+ const n = Math.min(PAGE - from, bytes.length - done4);
5855
+ let target = this.pages.get(page);
5856
+ if (!target) {
5857
+ target = this.take(0);
5858
+ this.pages.set(page, target);
5859
+ }
5860
+ target.set(bytes.subarray(done4, done4 + n), from);
5861
+ this.dirty.add(page);
5862
+ done4 += n;
5863
+ }
5864
+ this.length = Math.max(this.length, at + bytes.length);
5865
+ }
5866
+ truncate(size) {
5867
+ if (size < this.stored) {
5868
+ this.stored = size;
5869
+ this.shrink = Math.min(this.shrink ?? size, size);
5870
+ }
5871
+ if (size < this.length) {
5872
+ const edge = Math.floor(size / PAGE);
5873
+ for (const page of [...this.pages.keys()]) {
5874
+ if (page > edge || page === edge && size === edge * PAGE) this.drop(page);
5875
+ }
5876
+ this.pages.get(edge)?.fill(0, size - edge * PAGE);
5877
+ }
5878
+ this.length = size;
5879
+ }
5880
+ plan() {
5881
+ const runs = [];
5882
+ const sorted = [...this.dirty].sort((a, b) => a - b);
5883
+ for (let i = 0; i < sorted.length; ) {
5884
+ let j = i + 1;
5885
+ const first = sorted[i];
5886
+ while (j < sorted.length && sorted[j] === first + (j - i) && sorted[j] * PAGE % RUN !== 0) {
5887
+ j++;
5888
+ }
5889
+ const start = first * PAGE;
5890
+ const end = Math.min(this.length, (first + (j - i)) * PAGE);
5891
+ const bytes = new Uint8Array(end - start);
5892
+ for (let k = i; k < j; k++) {
5893
+ const page = this.pages.get(sorted[k]);
5894
+ bytes.set(page.subarray(0, Math.min(PAGE, end - (k - i) * PAGE - start)), (k - i) * PAGE);
5895
+ }
5896
+ runs.push({ offset: start, bytes });
5897
+ i = j;
5898
+ }
5899
+ const written = Math.max(this.stored, ...runs.map((r) => r.offset + r.bytes.length));
5900
+ return {
5901
+ ...this.shrink !== void 0 ? { shrink: this.shrink } : {},
5902
+ runs,
5903
+ ...this.length > written ? { extend: this.length } : {}
5904
+ };
5905
+ }
5906
+ flushed() {
5907
+ this.dirty.clear();
5908
+ this.shrink = void 0;
5909
+ this.stored = this.length;
5910
+ this.evict();
5911
+ }
5912
+ pin() {
5913
+ this.pinned = true;
5914
+ }
5915
+ reset(size) {
5916
+ if (this.pinned) return;
5917
+ for (const page of [...this.pages.keys()]) if (!this.dirty.has(page)) this.drop(page);
5918
+ if (this.changed) return;
5919
+ this.length = size;
5920
+ this.stored = size;
5921
+ }
5922
+ evict() {
5923
+ if (this.pinned) return;
5924
+ let clean = (this.pages.size - this.dirty.size) * PAGE;
5925
+ for (const page of this.pages.keys()) {
5926
+ if (clean <= CLEAN_LIMIT) return;
5927
+ if (this.dirty.has(page)) continue;
5928
+ this.drop(page);
5929
+ clean -= PAGE;
5930
+ }
5931
+ }
5932
+ take(used) {
5933
+ const page = this.free.pop();
5934
+ if (!page) return new Uint8Array(PAGE);
5935
+ page.fill(0, used);
5936
+ return page;
5937
+ }
5938
+ drop(page) {
5939
+ const bytes = this.pages.get(page);
5940
+ this.pages.delete(page);
5941
+ this.dirty.delete(page);
5942
+ if (this.free.length < CLEAN_LIMIT / PAGE) this.free.push(bytes);
5943
+ }
5944
+ };
5945
+
5946
+ // src/fs/ranged.ts
5947
+ var AsyncRangedFile = class {
5948
+ pages;
5949
+ io;
5950
+ pinned = false;
5951
+ path;
5952
+ constructor(io, path, size) {
5953
+ this.io = io;
5954
+ this.path = path;
5955
+ this.pages = new Pages(size);
5956
+ }
5957
+ size() {
5958
+ return this.pages.length;
5959
+ }
5960
+ async fetch(runs) {
5961
+ for (const [start, n] of runs) this.pages.fill(start, await this.io.pread(this.path, start, n));
5962
+ }
5963
+ async read(at, length2) {
5964
+ await this.fetch(this.pages.missing(at, length2));
5965
+ return this.pages.read(at, length2);
5966
+ }
5967
+ async write(at, bytes) {
5968
+ await this.fetch(this.pages.partial(at, bytes.length));
5969
+ this.pages.write(at, bytes);
5970
+ if (!this.pinned && this.pages.dirtyBytes >= WRITEBACK) await this.flush();
5971
+ }
5972
+ truncate(size) {
5973
+ this.pages.truncate(size);
5974
+ }
5975
+ async flush() {
5976
+ if (this.pinned || !this.pages.changed) return;
5977
+ const plan = this.pages.plan();
5978
+ if (plan.shrink !== void 0) await this.io.truncate(this.path, plan.shrink);
5979
+ for (const run2 of plan.runs) await this.io.pwrite(this.path, run2.offset, run2.bytes, true);
5980
+ if (plan.extend !== void 0) await this.io.truncate(this.path, plan.extend);
5981
+ this.pages.flushed();
5982
+ }
5983
+ async pin() {
5984
+ this.pinned = true;
5985
+ this.pages.pin();
5986
+ await this.fetch(this.pages.missing(0, this.pages.length));
5987
+ }
5988
+ };
5989
+
5648
5990
  // src/kernel/vfs-file.ts
5649
5991
  var O_ACCMODE = 3;
5650
5992
  var O_WRONLY = 1;
@@ -5664,6 +6006,7 @@ function within3(path, root) {
5664
6006
  }
5665
6007
  var VfsNode = class {
5666
6008
  data;
6009
+ ranged;
5667
6010
  length = 0;
5668
6011
  dirty = false;
5669
6012
  queue = Promise.resolve();
@@ -5694,6 +6037,23 @@ var VfsNode = class {
5694
6037
  clearTimeout(this.writeBack);
5695
6038
  this.writeBack = void 0;
5696
6039
  this.data = void 0;
6040
+ this.ranged = void 0;
6041
+ }
6042
+ async prepare() {
6043
+ if (this.revoked) throw new KernelError("EIO");
6044
+ if (this.ranged || this.data) return this.ranged;
6045
+ const io = rangedOps(this.fs);
6046
+ const st = io && !this.orphaned ? await this.fs.stat?.(this.path).catch(() => void 0) : void 0;
6047
+ if (io && st?.ranged) {
6048
+ this.ranged = new AsyncRangedFile(io, this.path, st.size ?? 0);
6049
+ return this.ranged;
6050
+ }
6051
+ await this.load();
6052
+ return void 0;
6053
+ }
6054
+ async keep() {
6055
+ const ranged2 = await this.prepare();
6056
+ await ranged2?.pin();
5697
6057
  }
5698
6058
  async load() {
5699
6059
  if (this.revoked) throw new KernelError("EIO");
@@ -5710,22 +6070,26 @@ var VfsNode = class {
5710
6070
  return this.data;
5711
6071
  }
5712
6072
  replace(contents2) {
6073
+ this.ranged = void 0;
5713
6074
  this.data = new Uint8Array(contents2);
5714
6075
  this.length = this.data.length;
5715
6076
  this.missing = false;
5716
6077
  this.markDirty();
5717
6078
  }
5718
6079
  async materialize() {
5719
- await this.load();
6080
+ if (await this.prepare()) return;
5720
6081
  if (!this.missing || this.orphaned) return;
5721
6082
  this.missing = false;
5722
6083
  await this.fs.writeFile(this.path, this.data?.slice(0, this.length) ?? new Uint8Array(0));
6084
+ if (!this.dirty) this.data = void 0;
5723
6085
  }
5724
6086
  async size() {
5725
- await this.load();
5726
- return this.length;
6087
+ const ranged2 = await this.prepare();
6088
+ return ranged2 ? ranged2.size() : this.length;
5727
6089
  }
5728
6090
  async pread(max, at) {
6091
+ const ranged2 = await this.prepare();
6092
+ if (ranged2) return ranged2.read(at, max);
5729
6093
  const bytes = await this.load();
5730
6094
  const n = Math.max(0, Math.min(max, this.length - at));
5731
6095
  return bytes.slice(at, at + n);
@@ -5748,12 +6112,18 @@ var VfsNode = class {
5748
6112
  }
5749
6113
  renamedTo(path) {
5750
6114
  this.path = path;
6115
+ if (this.ranged) this.ranged.path = path;
5751
6116
  this.cap = void 0;
5752
6117
  }
5753
6118
  async pwrite(bytes, at) {
5754
6119
  if (bytes.length === 0) return 0;
5755
6120
  await this.fits(at + bytes.length);
5756
- await this.load();
6121
+ const ranged2 = await this.prepare();
6122
+ if (ranged2) {
6123
+ await ranged2.write(at, bytes);
6124
+ this.markDirty();
6125
+ return bytes.length;
6126
+ }
5757
6127
  const buf = this.ensure(at + bytes.length);
5758
6128
  if (at > this.length) buf.fill(0, this.length, at);
5759
6129
  buf.set(bytes, at);
@@ -5763,7 +6133,12 @@ var VfsNode = class {
5763
6133
  }
5764
6134
  async truncate(size) {
5765
6135
  await this.fits(size);
5766
- await this.load();
6136
+ const ranged2 = await this.prepare();
6137
+ if (ranged2) {
6138
+ ranged2.truncate(size);
6139
+ this.markDirty();
6140
+ return;
6141
+ }
5767
6142
  const buf = this.ensure(size);
5768
6143
  if (size > this.length) buf.fill(0, this.length, size);
5769
6144
  this.length = size;
@@ -5788,11 +6163,12 @@ var VfsNode = class {
5788
6163
  }
5789
6164
  async flush() {
5790
6165
  if (this.revoked && this.dirty) throw new KernelError("EIO");
5791
- if (!this.dirty || !this.data || this.orphaned) return;
6166
+ if (!this.dirty || this.orphaned || !(this.data || this.ranged)) return;
5792
6167
  this.dirty = false;
5793
6168
  const started = performance.now();
5794
6169
  try {
5795
- await this.fs.writeFile(this.path, this.data.slice(0, this.length));
6170
+ if (this.ranged) await this.ranged.flush();
6171
+ else await this.fs.writeFile(this.path, this.data.slice(0, this.length));
5796
6172
  } catch (err) {
5797
6173
  this.dirty = true;
5798
6174
  throw err;
@@ -5839,7 +6215,7 @@ var VfsNodes = class {
5839
6215
  }
5840
6216
  async unlinking(path) {
5841
6217
  const node = this.byPath.get(path);
5842
- if (node) await node.serial(async () => void await Promise.all([node.load(), node.limit()]));
6218
+ if (node) await node.serial(async () => void await Promise.all([node.keep(), node.limit()]));
5843
6219
  }
5844
6220
  revoke(prefix) {
5845
6221
  for (const [path, node] of this.byPath) {
@@ -6988,7 +7364,8 @@ async function dispatch(h, req) {
6988
7364
  case "setattr":
6989
7365
  return h.setattr?.(req.path, {
6990
7366
  ...req.mode !== void 0 ? { mode: req.mode } : {},
6991
- ...req.mtime !== void 0 ? { mtime: req.mtime } : {}
7367
+ ...req.mtime !== void 0 ? { mtime: req.mtime } : {},
7368
+ ...req.size !== void 0 ? { size: req.size } : {}
6992
7369
  });
6993
7370
  case "statfs":
6994
7371
  return h.statfs?.() ?? null;
@@ -6996,7 +7373,7 @@ async function dispatch(h, req) {
6996
7373
  throw fsError2("ENOSYS", `unknown driver op ${req.op}`);
6997
7374
  }
6998
7375
  }
6999
- function serveFilesystem(port, handlers, capabilities = {}) {
7376
+ function serveFilesystem(port, handlers, capabilities = {}, options = {}) {
7000
7377
  let open = false;
7001
7378
  const reply = (message2, transfer = []) => port.postMessage(message2, transfer);
7002
7379
  async function hello(message2) {
@@ -7032,7 +7409,8 @@ function serveFilesystem(port, handlers, capabilities = {}) {
7032
7409
  dispatch(handlers, data).then(
7033
7410
  (result) => {
7034
7411
  if (result instanceof Uint8Array) {
7035
- const bytes = result.slice();
7412
+ const whole = result.byteOffset === 0 && result.byteLength === result.buffer.byteLength;
7413
+ const bytes = options.owned && whole ? result : result.slice();
7036
7414
  reply({ id: data.id, result: bytes }, [bytes.buffer]);
7037
7415
  } else reply({ id: data.id, result: result ?? null });
7038
7416
  },
@@ -7698,6 +8076,58 @@ async function openHostfs(spec, hook, fetch2, timing = {}) {
7698
8076
  };
7699
8077
  }
7700
8078
 
8079
+ // src/mount/chunked.ts
8080
+ var CHUNK2 = 1024 * 1024;
8081
+ var ChunkedBytes = class {
8082
+ chunks = [];
8083
+ size = 0;
8084
+ read(offset, length2) {
8085
+ const end = Math.min(this.size, offset + length2);
8086
+ if (end <= offset) return new Uint8Array(0);
8087
+ const out = new Uint8Array(end - offset);
8088
+ for (let at = offset; at < end; ) {
8089
+ const index = Math.floor(at / CHUNK2);
8090
+ const from = at - index * CHUNK2;
8091
+ const n = Math.min(CHUNK2 - from, end - at);
8092
+ const chunk2 = this.chunks[index];
8093
+ if (chunk2) out.set(chunk2.subarray(from, Math.min(from + n, chunk2.length)), at - offset);
8094
+ at += n;
8095
+ }
8096
+ return out;
8097
+ }
8098
+ write(offset, bytes, owned = false) {
8099
+ const whole = owned && bytes.byteOffset === 0 && bytes.byteLength === bytes.buffer.byteLength;
8100
+ for (let done4 = 0; done4 < bytes.length; ) {
8101
+ const at = offset + done4;
8102
+ const index = Math.floor(at / CHUNK2);
8103
+ const from = at - index * CHUNK2;
8104
+ const n = Math.min(CHUNK2 - from, bytes.length - done4);
8105
+ if (whole && n === CHUNK2 && bytes.length === CHUNK2) this.chunks[index] = bytes;
8106
+ else this.room(index, from + n).set(bytes.subarray(done4, done4 + n), from);
8107
+ done4 += n;
8108
+ }
8109
+ this.size = Math.max(this.size, offset + bytes.length);
8110
+ }
8111
+ truncate(size) {
8112
+ if (size < this.size) {
8113
+ const keep = Math.ceil(size / CHUNK2);
8114
+ this.chunks.length = Math.min(this.chunks.length, keep);
8115
+ const tail = this.chunks[keep - 1];
8116
+ const from = size - (keep - 1) * CHUNK2;
8117
+ if (tail && from < tail.length) tail.fill(0, from);
8118
+ }
8119
+ this.size = size;
8120
+ }
8121
+ room(index, need) {
8122
+ const chunk2 = this.chunks[index];
8123
+ if (chunk2 && chunk2.length >= need) return chunk2;
8124
+ const grown = new Uint8Array(Math.min(CHUNK2, Math.max(need, (chunk2?.length ?? 0) * 2)));
8125
+ if (chunk2) grown.set(chunk2);
8126
+ this.chunks[index] = grown;
8127
+ return grown;
8128
+ }
8129
+ };
8130
+
7701
8131
  // src/mount/tmpfs.ts
7702
8132
  function tmpfs() {
7703
8133
  let inodes = 0;
@@ -7706,7 +8136,7 @@ function tmpfs() {
7706
8136
  ino: ++inodes,
7707
8137
  mode,
7708
8138
  mtime: Date.now(),
7709
- data: new Uint8Array(0),
8139
+ data: new ChunkedBytes(),
7710
8140
  target: "",
7711
8141
  children: /* @__PURE__ */ new Map()
7712
8142
  });
@@ -7734,7 +8164,7 @@ function tmpfs() {
7734
8164
  }
7735
8165
  const attr = (node) => ({
7736
8166
  kind: node.kind,
7737
- size: node.kind === "file" ? node.data.length : node.kind === "symlink" ? node.target.length : 4096,
8167
+ size: node.kind === "file" ? node.data.size : node.kind === "symlink" ? node.target.length : 4096,
7738
8168
  mtime: node.mtime,
7739
8169
  mode: node.mode,
7740
8170
  ino: node.ino
@@ -7769,32 +8199,25 @@ function tmpfs() {
7769
8199
  },
7770
8200
  async open(path, flags) {
7771
8201
  const node = opened2(path, flags);
7772
- const data = flags.truncate ? new Uint8Array(0) : node.data.slice();
7773
- handles.set(++nextFh, { node, data, length: data.length, dirty: flags.truncate });
8202
+ if (flags.truncate && node.kind === "file") {
8203
+ node.data.truncate(0);
8204
+ node.mtime = Date.now();
8205
+ }
8206
+ handles.set(++nextFh, { node, write: flags.write });
7774
8207
  return nextFh;
7775
8208
  },
7776
8209
  async read(fh, offset, size) {
7777
- const h = handle(fh);
7778
- return h.data.slice(offset, Math.min(h.length, offset + size));
8210
+ return handle(fh).node.data.read(offset, size);
7779
8211
  },
7780
8212
  async write(fh, offset, bytes) {
7781
8213
  const h = handle(fh);
7782
- const end = offset + bytes.length;
7783
- if (end > h.data.length) {
7784
- const grown = new Uint8Array(Math.max(end, h.data.length * 2));
7785
- grown.set(h.data.subarray(0, h.length));
7786
- h.data = grown;
7787
- }
7788
- h.data.set(bytes, offset);
7789
- h.length = Math.max(h.length, end);
7790
- h.dirty = true;
8214
+ if (!h.write) throw fsError2("EBADF", `handle ${fh} is read-only`);
8215
+ h.node.data.write(offset, bytes, true);
8216
+ h.node.mtime = Date.now();
7791
8217
  },
7792
8218
  async release(fh) {
7793
- const h = handle(fh);
8219
+ handle(fh);
7794
8220
  handles.delete(fh);
7795
- if (!h.dirty) return;
7796
- h.node.data = h.data.slice(0, h.length);
7797
- h.node.mtime = Date.now();
7798
8221
  },
7799
8222
  async mkdir(path) {
7800
8223
  const { dir, name: name2 } = parent(path);
@@ -7847,6 +8270,12 @@ function tmpfs() {
7847
8270
  async setattr(path, change) {
7848
8271
  const node = find(path);
7849
8272
  if (change.mode !== void 0) node.mode = node.mode & ~4095 | change.mode & 4095;
8273
+ if (change.size !== void 0) {
8274
+ if (node.kind !== "file")
8275
+ throw fsError2(node.kind === "directory" ? "EISDIR" : "EINVAL", path);
8276
+ node.data.truncate(change.size);
8277
+ node.mtime = Date.now();
8278
+ }
7850
8279
  if (change.mtime !== void 0) node.mtime = change.mtime;
7851
8280
  },
7852
8281
  async statfs() {
@@ -8437,7 +8866,12 @@ var Launcher = class {
8437
8866
  }
8438
8867
  if (type === "tmpfs") {
8439
8868
  const { port1, port2 } = new MessageChannel();
8440
- serveFilesystem(port2, tmpfs(), { symlinks: true, chmod: true, attrTtl: 0 });
8869
+ serveFilesystem(
8870
+ port2,
8871
+ tmpfs(),
8872
+ { symlinks: true, chmod: true, ranges: true, attrTtl: 0 },
8873
+ { owned: true }
8874
+ );
8441
8875
  return {
8442
8876
  port: port1,
8443
8877
  dispose: () => {