@ai-ecoverse/slicc-kernel 1.161.0 → 1.162.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.
@@ -1005,6 +1005,9 @@ var RemoteWatch = class {
1005
1005
  holdsSent = 0;
1006
1006
  silent = /* @__PURE__ */ new Set();
1007
1007
  holders = /* @__PURE__ */ new Set();
1008
+ instances = /* @__PURE__ */ new Map();
1009
+ instance;
1010
+ keeps;
1008
1011
  listeners = /* @__PURE__ */ new Set();
1009
1012
  waiting = /* @__PURE__ */ new Map();
1010
1013
  locks;
@@ -1124,8 +1127,9 @@ var RemoteWatch = class {
1124
1127
  this.met?.();
1125
1128
  }
1126
1129
  if (data.tab !== void 0 && data.paths !== void 0) this.announced(data);
1127
- if (data.tab !== void 0 && data.holds !== void 0) this.heldBy(data);
1130
+ this.holdsHeard(data);
1128
1131
  if (data.ack !== void 0 && data.to === this.tab && data.from !== void 0) {
1132
+ if (data.instance !== void 0) this.instances.set(data.from, data.instance);
1129
1133
  this.acked(data.ack, data.from);
1130
1134
  }
1131
1135
  if (data.changed !== void 0) this.emit({ paths: data.changed, trees: [], content: true });
@@ -1152,6 +1156,7 @@ var RemoteWatch = class {
1152
1156
  this.mourned.delete(peer);
1153
1157
  this.others.delete(peer);
1154
1158
  this.peerHolds.delete(peer);
1159
+ this.keeps?.left(peer);
1155
1160
  this.peers.delete(peer);
1156
1161
  this.ackers.delete(peer);
1157
1162
  this.holders.delete(peer);
@@ -1192,9 +1197,38 @@ var RemoteWatch = class {
1192
1197
  this.rebuild();
1193
1198
  this.channel.postMessage({ ack: seq2, from: this.tab, to: from });
1194
1199
  }
1200
+ holdsHeard(data) {
1201
+ if (data.tab === void 0) return;
1202
+ if (data.holds !== void 0) this.heldBy(data);
1203
+ if (data.to === this.tab && (data.keeping || data.kept)) void this.keepAsked(data);
1204
+ }
1205
+ keepChains = /* @__PURE__ */ new Map();
1206
+ keepAsked(data) {
1207
+ const from = data.tab;
1208
+ const next3 = (this.keepChains.get(from) ?? Promise.resolve()).then(() => this.keepOne(data));
1209
+ this.keepChains.set(from, next3);
1210
+ return next3;
1211
+ }
1212
+ async keepOne({ tab, seq: seq2, keeping, kept }) {
1213
+ const from = tab;
1214
+ const keeps = this.keeps;
1215
+ try {
1216
+ if (keeping) await keeps?.keeping(from, keeping.paths);
1217
+ if (kept)
1218
+ await keeps?.kept(from, kept.items, kept.paths ?? kept.items.map((item) => item.path));
1219
+ } finally {
1220
+ this.channel.postMessage({
1221
+ ack: seq2,
1222
+ from: this.tab,
1223
+ to: from,
1224
+ ...this.instance === void 0 ? {} : { instance: this.instance }
1225
+ });
1226
+ }
1227
+ }
1195
1228
  heldBy({ tab, holds: holds2, seq: seq2 }) {
1196
1229
  const from = tab;
1197
1230
  const { add, drop } = holds2;
1231
+ if (holds2?.instance !== void 0) this.instances.set(from, holds2.instance);
1198
1232
  const held = this.peerHolds.get(from) ?? /* @__PURE__ */ new Map();
1199
1233
  for (const path of drop) held.delete(path);
1200
1234
  for (const [path, fid] of add) held.set(path, fid);
@@ -1236,9 +1270,38 @@ var RemoteWatch = class {
1236
1270
  sendHolds(add, drop) {
1237
1271
  const seq2 = ++this.seq;
1238
1272
  this.holdsSent++;
1239
- this.channel.postMessage({ tab: this.tab, holds: { add: [...add], drop: [...drop] }, seq: seq2 });
1273
+ const instance = this.instance === void 0 ? {} : { instance: this.instance };
1274
+ this.channel.postMessage({
1275
+ tab: this.tab,
1276
+ holds: { add: [...add], drop: [...drop], ...instance },
1277
+ seq: seq2
1278
+ });
1240
1279
  return seq2;
1241
1280
  }
1281
+ heldWithin(target) {
1282
+ const out = [];
1283
+ for (const [tab, held] of this.peerHolds) {
1284
+ for (const [path, fid] of held) {
1285
+ if (path !== target && !path.startsWith(`${target}/`)) continue;
1286
+ out.push({ tab, path, fid, instance: this.instances.get(tab) });
1287
+ }
1288
+ }
1289
+ return out;
1290
+ }
1291
+ ask(to, body, cap) {
1292
+ const seq2 = ++this.seq;
1293
+ this.channel.postMessage({ tab: this.tab, to, seq: seq2, ...body });
1294
+ return new Promise((resolve2) => {
1295
+ const done5 = () => {
1296
+ clearTimeout(timer);
1297
+ this.waiting.delete(seq2);
1298
+ resolve2(left.size === 0);
1299
+ };
1300
+ const left = /* @__PURE__ */ new Set([to]);
1301
+ const timer = setTimeout(done5, cap);
1302
+ this.waiting.set(seq2, { left, done: done5 });
1303
+ });
1304
+ }
1242
1305
  peerHeld(path) {
1243
1306
  const out = [];
1244
1307
  for (const [tab, held] of this.peerHolds)
@@ -1274,6 +1337,7 @@ var RemoteWatch = class {
1274
1337
  if (this.peers.delete(tab)) left = true;
1275
1338
  this.others.delete(tab);
1276
1339
  this.peerHolds.delete(tab);
1340
+ this.keeps?.left(tab);
1277
1341
  this.ackers.delete(tab);
1278
1342
  this.holders.delete(tab);
1279
1343
  }
@@ -1440,6 +1504,7 @@ function translate(err, path, op = "lookup", mapped = true) {
1440
1504
  return error;
1441
1505
  }
1442
1506
  var META_OPS = ["get", "update", "move", "remove", "links", "under", "all"];
1507
+ var FILE_ID_TRIES = 3;
1443
1508
  function sidecar(meta) {
1444
1509
  const pathOf = ([first]) => [first, "/"].flat().find((p) => typeof p === "string");
1445
1510
  const guarded2 = (op) => (...args) => meta[op].apply(meta, args).catch((err) => {
@@ -1683,18 +1748,48 @@ var OpfsFs = class {
1683
1748
  this.channel?.postMessage(paths);
1684
1749
  }
1685
1750
  async fileId(path) {
1751
+ for (let tries = 0; tries < FILE_ID_TRIES; tries++) {
1752
+ const fid = await this.assignId(path);
1753
+ if (fid !== void 0) return fid;
1754
+ }
1755
+ throw fsError("EAGAIN", `${path}: moved while its file id was assigned`);
1756
+ }
1757
+ bindId(path) {
1758
+ return this.assignId(path);
1759
+ }
1760
+ async assignId(path) {
1686
1761
  const found = await this.locate(path, true);
1687
1762
  const [entry] = await (this.meta.fresh ?? this.meta.get).call(this.meta, [found.path]);
1688
1763
  if (entry?.fid !== void 0) return entry.fid;
1689
1764
  const fid = crypto.randomUUID();
1765
+ let moved2 = false;
1766
+ await this.meta.update(found.path, (now) => {
1767
+ const vacatedSince = now?.vacated && (!entry?.vacated || now.ino !== entry.ino);
1768
+ if (vacatedSince || entry !== void 0 && now === void 0) {
1769
+ moved2 = true;
1770
+ return now;
1771
+ }
1772
+ return { ...occupied(now), path: found.path, fid: now?.fid ?? fid };
1773
+ });
1774
+ if (moved2) return void 0;
1690
1775
  this.idWrites++;
1691
- await this.meta.update(found.path, (now) => ({
1692
- ...occupied(now),
1693
- path: found.path,
1694
- fid: now?.fid ?? fid
1695
- }));
1696
- const [stored] = await this.meta.get([found.path]);
1697
- return stored?.fid ?? fid;
1776
+ const there = await this.locate(path, true).then(
1777
+ (at) => at.path === found.path,
1778
+ () => false
1779
+ );
1780
+ if (there) return (await this.meta.get([found.path]))[0]?.fid ?? fid;
1781
+ const gone = await this.locate(found.path, true).then(
1782
+ () => false,
1783
+ (err) => missing2(err)
1784
+ );
1785
+ if (gone) {
1786
+ await this.meta.update(found.path, (now) => {
1787
+ if (now?.fid !== fid || now.vacated) return now;
1788
+ const { fid: _, ...rest2 } = now;
1789
+ return rest2;
1790
+ });
1791
+ }
1792
+ return void 0;
1698
1793
  }
1699
1794
  heldUnder = () => [];
1700
1795
  heldBy(held) {
@@ -1720,6 +1815,20 @@ var OpfsFs = class {
1720
1815
  const remote = this.remote;
1721
1816
  return remote && { ...remote.holdCounts(), held: remote.peerHeldPaths() };
1722
1817
  }
1818
+ peerKeeps() {
1819
+ const remote = this.remote;
1820
+ if (!remote) return void 0;
1821
+ return {
1822
+ shared: () => this.shared(),
1823
+ heldWithin: (path) => remote.heldWithin(path),
1824
+ ask: (to, body, cap) => remote.ask(to, body, cap)
1825
+ };
1826
+ }
1827
+ servePeerKeeps(instance, handlers) {
1828
+ if (!this.remote) return;
1829
+ this.remote.instance = instance;
1830
+ this.remote.keeps = handlers;
1831
+ }
1723
1832
  rehold(from, to) {
1724
1833
  this.remote?.rehold(from, to);
1725
1834
  }
@@ -2667,7 +2776,7 @@ var OpfsFs = class {
2667
2776
  }
2668
2777
  }
2669
2778
  async writeFile(path, content, owner) {
2670
- const [file, existed, target] = await this.fileFor(path);
2779
+ const [file, existed, target] = await this.fileFor(path, owner?.existing !== true);
2671
2780
  if (!existed) await this.stampNew(target, owner, false);
2672
2781
  if (!existed && content.length === 0) return;
2673
2782
  let writable2;
@@ -6541,7 +6650,9 @@ var KernelSocket = class _KernelSocket {
6541
6650
  throw new KernelError("ECONNRESET", "read ECONNRESET");
6542
6651
  return bytes.length > 0 ? bytes : null;
6543
6652
  },
6544
- write: (bytes) => raw2.tx.write(bytes),
6653
+ write: (bytes) => raw2.tx.write(bytes).catch((err) => {
6654
+ throw this.refused ? new KernelError("ECONNRESET", "read ECONNRESET") : err;
6655
+ }),
6545
6656
  closeWrite: () => raw2.tx.closeWrite(),
6546
6657
  close: () => {
6547
6658
  raw2.rx.closeRead();
@@ -8326,7 +8437,7 @@ function satisfies(version, range2) {
8326
8437
  }
8327
8438
 
8328
8439
  // src/version.ts
8329
- var KERNEL_VERSION = true ? "1.161.0" : "0.0.0-dev";
8440
+ var KERNEL_VERSION = true ? "1.162.1" : "0.0.0-dev";
8330
8441
  var OS_RELEASE = "6.0.0-slicc";
8331
8442
  function procVersion() {
8332
8443
  return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
@@ -9276,6 +9387,9 @@ var LOOKUP_OPS = [
9276
9387
  var within4 = (path, target) => path === target || target === "/" || path.startsWith(`${target}/`);
9277
9388
  var TOUCHED_MAX = 4096;
9278
9389
  var parentOf5 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
9390
+ var PEER_RM_MS = 1e3;
9391
+ var PEER_RENAME_MS = 1e4;
9392
+ var PEER_GATE_MS = PEER_RENAME_MS + 5e3;
9279
9393
  var lockName = (instance) => `slicc-unlinked:${instance}`;
9280
9394
  var dirOf = (root) => root === "/" ? `/${UNLINKED_DIR}` : `${root}/${UNLINKED_DIR}`;
9281
9395
  var PathGate = class {
@@ -9318,7 +9432,9 @@ var UnlinkedKeeper = class {
9318
9432
  links = /* @__PURE__ */ new Map();
9319
9433
  linked = /* @__PURE__ */ new Map();
9320
9434
  dirOwner;
9435
+ peers;
9321
9436
  constructor(fs, holders, rootOf, options = {}) {
9437
+ this.peers = options.peers;
9322
9438
  this.marks = options.marks ?? new PathMarks();
9323
9439
  const { locks, owner } = options;
9324
9440
  this.dirOwner = options.dirOwner;
@@ -9394,22 +9510,29 @@ var UnlinkedKeeper = class {
9394
9510
  lstat: (path) => unreplaced(path, async () => counted2(await fs.lstat(path))),
9395
9511
  async rm(path, options) {
9396
9512
  const target = fs.resolvePath("/", path);
9397
- await replace(target, async (gated, sealed) => {
9398
- const pinned = await keeper.pin(target, gated, sealed);
9399
- if (pinned.moved.has(target)) pinned.commit();
9400
- else await keeper.settle(pinned, () => fs.rm(path, options));
9401
- });
9513
+ const kept = await keeper.peerKeep(target, false, () => keeper.removable(target, options));
9514
+ if (kept.whole) return;
9515
+ await kept.undoing(
9516
+ () => replace(target, async (gated, sealed) => {
9517
+ const pinned = await keeper.pin(target, gated, sealed);
9518
+ if (pinned.moved.has(target)) pinned.commit();
9519
+ else await keeper.settle(pinned, () => fs.rm(path, options));
9520
+ })
9521
+ );
9402
9522
  },
9403
9523
  async rename(from, to) {
9404
9524
  const target = fs.resolvePath("/", to);
9405
9525
  const source = fs.resolvePath("/", from);
9406
9526
  if (target === source) return fs.rename(from, to);
9407
- await keeper.marks.around(
9408
- [source],
9409
- () => replace(target, async (gated, sealed) => {
9410
- const pinned = await keeper.pin(target, gated, sealed);
9411
- await keeper.settle(pinned, () => fs.rename(from, to));
9412
- })
9527
+ const kept = await keeper.peerKeep(target, true, () => keeper.renamable(source, target));
9528
+ await kept.undoing(
9529
+ () => keeper.marks.around(
9530
+ [source],
9531
+ () => replace(target, async (gated, sealed) => {
9532
+ const pinned = await keeper.pin(target, gated, sealed);
9533
+ await keeper.settle(pinned, () => fs.rename(from, to));
9534
+ })
9535
+ )
9413
9536
  );
9414
9537
  },
9415
9538
  symlink: (target, path, owner) => keeper.marks.around([fs.resolvePath("/", path)], () => fs.symlink(target, path, owner)),
@@ -9554,6 +9677,114 @@ var UnlinkedKeeper = class {
9554
9677
  (err) => err?.code !== "ENOENT" && err?.code !== "ENOTDIR"
9555
9678
  );
9556
9679
  }
9680
+ get id() {
9681
+ return this.instance;
9682
+ }
9683
+ adopt(hidden, refs) {
9684
+ return this.keptFile(hidden, { refs });
9685
+ }
9686
+ async peerKeep(target, over, valid = async () => true) {
9687
+ const none = { whole: false, undoing: (op) => op() };
9688
+ const peers = this.peers;
9689
+ if (!peers?.shared() || this.rootOf(target) !== "/") return none;
9690
+ const held = peers.heldWithin(target).filter((holder) => holder.instance !== void 0);
9691
+ if (held.length === 0 || !await valid()) return none;
9692
+ const tabs = /* @__PURE__ */ new Map();
9693
+ for (const holder of held) tabs.set(holder.tab, [...tabs.get(holder.tab) ?? [], holder]);
9694
+ const asked = [...tabs].map(([tab, holders]) => {
9695
+ const paths = holders.map((holder) => holder.path);
9696
+ return { tab, holders, paths };
9697
+ });
9698
+ const heard = await Promise.all(
9699
+ asked.map(
9700
+ ({ tab, paths }) => peers.ask(tab, { keeping: { paths } }, over ? PEER_RENAME_MS : PEER_RM_MS)
9701
+ )
9702
+ );
9703
+ if (over && heard.includes(false)) {
9704
+ for (const { tab, paths } of asked)
9705
+ void peers.ask(tab, { kept: { items: [], paths } }, PEER_RM_MS);
9706
+ throw fsError("EBUSY", `${target}: open in another tab that did not answer`);
9707
+ }
9708
+ const { whole, moved: moved2 } = await this.setAside(peers, target, asked, heard);
9709
+ return { whole, undoing: (op) => this.undoing(op, moved2) };
9710
+ }
9711
+ async setAside(peers, target, asked, heard) {
9712
+ const mine = this.heldWithin(target);
9713
+ const taken = /* @__PURE__ */ new Set();
9714
+ let whole = false;
9715
+ const told = [];
9716
+ const moved2 = [];
9717
+ for (const [i, { tab, holders, paths }] of asked.entries()) {
9718
+ const items = [];
9719
+ for (const holder of holders) {
9720
+ const free = !mine.has(holder.path) && !taken.has(holder.path);
9721
+ const hidden = free ? await this.asideFor(holder.path, holder.instance) : void 0;
9722
+ if (hidden !== void 0) {
9723
+ taken.add(holder.path);
9724
+ moved2.push({ tab, path: holder.path, hidden, fid: holder.fid });
9725
+ if (holder.path === target) whole = true;
9726
+ }
9727
+ items.push({ path: holder.path, hidden, fid: holder.fid });
9728
+ }
9729
+ const answer2 = peers.ask(tab, { kept: { items, paths } }, PEER_RM_MS);
9730
+ if (heard[i]) told.push(answer2);
9731
+ }
9732
+ await Promise.all(told);
9733
+ return { whole, moved: moved2 };
9734
+ }
9735
+ async undoing(op, moved2) {
9736
+ try {
9737
+ return await op();
9738
+ } catch (err) {
9739
+ for (const { tab, path, hidden, fid } of moved2) {
9740
+ const back = await this.serial(async () => {
9741
+ if (await this.present(path)) return false;
9742
+ return this.fs.rename(hidden, path).then(
9743
+ () => true,
9744
+ () => false
9745
+ );
9746
+ });
9747
+ if (!back) continue;
9748
+ void this.peers?.ask(
9749
+ tab,
9750
+ { kept: { items: [{ path, hidden: void 0, fid, undo: hidden }], paths: [] } },
9751
+ PEER_RM_MS
9752
+ );
9753
+ }
9754
+ throw err;
9755
+ }
9756
+ }
9757
+ async removable(target, options) {
9758
+ const st = await this.fs.lstat(target).catch(() => void 0);
9759
+ return st !== void 0 && (!st.isDirectory || options?.recursive === true);
9760
+ }
9761
+ async renamable(source, target) {
9762
+ const from = await this.fs.lstat(source);
9763
+ const to = await this.fs.lstat(target).catch(() => void 0);
9764
+ if (!to) return true;
9765
+ if (to.isDirectory && !from.isDirectory) throw fsError("EISDIR", target);
9766
+ if (!to.isDirectory && from.isDirectory) throw fsError("ENOTDIR", target);
9767
+ if (to.isDirectory && (await this.fs.readdir(target)).length > 0)
9768
+ throw fsError("ENOTEMPTY", target);
9769
+ return true;
9770
+ }
9771
+ async asideFor(path, instance) {
9772
+ return await this.hideFor(path, instance) ?? this.hideFor(path, instance);
9773
+ }
9774
+ hideFor(path, instance) {
9775
+ return this.serial(async () => {
9776
+ const dir = dirOf("/");
9777
+ const hidden = `${dir}/${instance}.p${crypto.randomUUID().replaceAll("-", "")}`;
9778
+ try {
9779
+ const owner = this.dirOwner;
9780
+ await this.fs.mkdir(dir, { recursive: true, ...owner ? { owner } : {} });
9781
+ await this.fs.rename(path, hidden);
9782
+ return hidden;
9783
+ } catch {
9784
+ return void 0;
9785
+ }
9786
+ });
9787
+ }
9557
9788
  keptFile(hidden, entry) {
9558
9789
  return {
9559
9790
  hidden,
@@ -9608,7 +9839,7 @@ var UnlinkedKeeper = class {
9608
9839
  }
9609
9840
  async tidy(dir) {
9610
9841
  const left = await this.fs.readdir(dir).catch(() => [UNLINKED_DIR]);
9611
- if (left.length === 0) await this.fs.rm(dir, { recursive: true }).catch(() => void 0);
9842
+ if (left.length === 0) await this.fs.rm(dir).catch(() => void 0);
9612
9843
  }
9613
9844
  sweep(root) {
9614
9845
  const query = this.locks?.query?.bind(this.locks);
@@ -10853,6 +11084,11 @@ async function shownTarget(fs, target) {
10853
11084
  const st = await quiet(fs.stat(target));
10854
11085
  return st?.isFile && await readsAs2(fs, NOBODY, target) ? st : void 0;
10855
11086
  }
11087
+ async function madeFor(owner, virtual) {
11088
+ if (!owner?.existing || await virtual() === void 0) return owner;
11089
+ const { existing: _, ...made2 } = owner;
11090
+ return made2;
11091
+ }
10856
11092
  function withCommandDirs(fs, names2, userDir = async () => void 0, owners = false, targetOf2 = async () => void 0) {
10857
11093
  async function nearest(path) {
10858
11094
  const st = await fs.stat(path).catch(() => void 0);
@@ -10950,7 +11186,7 @@ function withCommandDirs(fs, names2, userDir = async () => void 0, owners = fals
10950
11186
  }),
10951
11187
  readFileBuffer: (path) => fs.readFileBuffer(path).catch((err) => content(path, err)),
10952
11188
  commandTarget: async (path) => (await entryTarget(path))?.path,
10953
- writeFile: (path, content2, owner) => fs.writeFile(path, content2, owner),
11189
+ writeFile: async (path, content2, owner) => fs.writeFile(path, content2, await madeFor(owner, () => entryTarget(path))),
10954
11190
  link: (from, to, follow2) => fs.link(from, to, follow2),
10955
11191
  links: (path) => mayLink(fs, path),
10956
11192
  async exists(path) {
@@ -11115,6 +11351,10 @@ function withEtcFiles(fs, files) {
11115
11351
  }
11116
11352
  } : {},
11117
11353
  readFile: (path) => fs.readFile(path).catch(fallback(path, (b) => new TextDecoder().decode(b))),
11354
+ writeFile: async (path, content, owner) => {
11355
+ if (owner?.existing && files.has(path)) await materialize(path);
11356
+ return fs.writeFile(path, content, owner);
11357
+ },
11118
11358
  ...fs.append ? {
11119
11359
  append: async (path, bytes, options) => {
11120
11360
  if (files.has(path)) await materialize(path);
@@ -13073,12 +13313,25 @@ var HeldPaths = class extends Map {
13073
13313
  for (const file of this.unlinked.values()) if (file.hidden === hidden) return true;
13074
13314
  return false;
13075
13315
  }
13316
+ boundAt(path, fid) {
13317
+ return this.handlesAt(path).filter(
13318
+ (id2) => !this.keptHandles.has(id2) && this.bound.get(id2)?.fid === fid
13319
+ );
13320
+ }
13321
+ keepBound(ids, path, file) {
13322
+ for (const id2 of ids) this.keptHandles.set(id2, file);
13323
+ this.uses.set(file, ids.length);
13324
+ this.unlinked.set(path, file);
13325
+ }
13076
13326
  keep(path, file) {
13077
13327
  const fresh3 = this.handlesAt(path).filter((id2) => !this.keptHandles.has(id2));
13078
13328
  for (const id2 of fresh3) this.keptHandles.set(id2, file);
13079
13329
  this.uses.set(file, Math.max(fresh3.length, 1));
13080
13330
  this.unlinked.set(path, file);
13081
13331
  }
13332
+ unkeepAt(path, hidden) {
13333
+ for (const file of [...this.uses.keys()]) if (file.hidden === hidden) this.unkeep(path, file);
13334
+ }
13082
13335
  unkeep(path, file) {
13083
13336
  for (const [id2, kept] of this.keptHandles) if (kept === file) this.keptHandles.delete(id2);
13084
13337
  this.uses.delete(file);
@@ -13647,12 +13900,12 @@ var MountFs = class _MountFs {
13647
13900
  const target = await found.mount.conn.call({ op: "readlink", path: found.rel });
13648
13901
  return this.follow(this.base.resolvePath(parentOf8(real), target), hops + 1, create);
13649
13902
  }
13650
- async open(mount, rel2, write, owner) {
13903
+ async open(mount, rel2, write, owner, create = write) {
13651
13904
  return await mount.conn.call({
13652
13905
  op: "open",
13653
13906
  path: rel2,
13654
13907
  write,
13655
- create: write,
13908
+ create,
13656
13909
  truncate: write,
13657
13910
  exclusive: false,
13658
13911
  ...owner
@@ -13749,10 +14002,10 @@ var MountFs = class _MountFs {
13749
14002
  `${real} would be larger than this mount's maxfile (${mount.maxFile})`
13750
14003
  );
13751
14004
  }
13752
- const made2 = await this.driverOwner(found, real, owner, false);
14005
+ const made2 = owner?.existing ? void 0 : await this.driverOwner(found, real, owner, false);
13753
14006
  for (let attempt = 1; ; attempt++) {
13754
14007
  try {
13755
- return await this.writeWhole(mount, real, rel2, bytes, made2);
14008
+ return await this.writeWhole(mount, real, rel2, bytes, made2, owner?.existing !== true);
13756
14009
  } catch (err) {
13757
14010
  if (err.code !== "ESTALE" || attempt >= STALE_RETRIES) throw err;
13758
14011
  }
@@ -13770,10 +14023,10 @@ var MountFs = class _MountFs {
13770
14023
  this.table.forget(mount, real);
13771
14024
  }
13772
14025
  }
13773
- async writeWhole(mount, real, rel2, bytes, owner) {
14026
+ async writeWhole(mount, real, rel2, bytes, owner, create = true) {
13774
14027
  await this.table.commit(real);
13775
14028
  this.table.forget(mount, real);
13776
- const fh = await this.open(mount, rel2, true, owner);
14029
+ const fh = await this.open(mount, rel2, true, owner, create);
13777
14030
  const chunk2 = mount.caps.maxIo ?? DEFAULT_IO;
13778
14031
  try {
13779
14032
  for (let offset = 0; offset < bytes.length; offset += chunk2) {
@@ -14510,6 +14763,11 @@ function ownerFor2(entry, mode) {
14510
14763
  if (!cred) return void 0;
14511
14764
  return ownerOf(cred, mode === void 0 ? void 0 : mode & ~(entry.umask?.() ?? 18));
14512
14765
  }
14766
+ function writeOwner(entry, req) {
14767
+ const owner = ownerFor2(entry, 438);
14768
+ if (req.handle === void 0 || req.mode !== 1) return owner;
14769
+ return { ...owner ?? {}, existing: true };
14770
+ }
14513
14771
  function plainDirMode(entry, mode) {
14514
14772
  if (entry.cred || mode === void 0) return void 0;
14515
14773
  const wanted = mode & 4095 & ~(entry.umask?.() ?? 18);
@@ -14580,7 +14838,7 @@ async function run(entry, path, req) {
14580
14838
  case "read":
14581
14839
  return { ok: true, kind: "bytes", bytes: await readSource(entry, path) };
14582
14840
  case "write":
14583
- await fs.writeFile(path, req.body ?? new Uint8Array(0), ownerFor2(entry, 438));
14841
+ await fs.writeFile(path, req.body ?? new Uint8Array(0), writeOwner(entry, req));
14584
14842
  return done2;
14585
14843
  case "exists":
14586
14844
  return json2(await fs.exists(path));
@@ -31176,7 +31434,7 @@ var Launcher = class {
31176
31434
  busy: (path) => [...this.openFiles].some((nodes) => nodes.holds(path)),
31177
31435
  owner: options.unlinkOwner,
31178
31436
  marks: this.pathMarks,
31179
- ...this.owners ? { dirOwner: { uid: 0, gid: 0, mode: 448 } } : {}
31437
+ ...this.peerOptions(options.fs)
31180
31438
  }
31181
31439
  );
31182
31440
  this.fs = this.processChain(fs, this.nodes, this.unlinked);
@@ -32010,7 +32268,7 @@ var Launcher = class {
32010
32268
  const assign = this.mounts.locate(target.wasm) === void 0 ? this.rootFs.fileId : void 0;
32011
32269
  return this.base.stat(target.wasm).then(
32012
32270
  async (st) => {
32013
- const fid = st.fid ?? await assign?.call(this.rootFs, target.wasm);
32271
+ const fid = st.fid ?? await assign?.call(this.rootFs, target.wasm)?.catch(() => void 0);
32014
32272
  if (fid === void 0) return exeIdOf(fileIdentity(st.ino, st.mtime.getTime()));
32015
32273
  return { exeId: `fid:${fid}:${st.mtime.getTime()}` };
32016
32274
  },
@@ -33172,8 +33430,82 @@ var Launcher = class {
33172
33430
  this.writers ??= this.writerStack();
33173
33431
  return this.writers;
33174
33432
  }
33433
+ peerGates = /* @__PURE__ */ new Map();
33434
+ peerOptions(fs) {
33435
+ const peers = fs.peerKeeps?.();
33436
+ queueMicrotask(() => fs.servePeerKeeps?.(this.unlinked.id, this.peerKept()));
33437
+ return {
33438
+ ...peers ? { peers } : {},
33439
+ ...this.owners ? { dirOwner: { uid: 0, gid: 0, mode: 448 } } : {}
33440
+ };
33441
+ }
33442
+ peerKept() {
33443
+ const gates = this.peerGates;
33444
+ const gone = /* @__PURE__ */ new Set();
33445
+ const instance = this.unlinked.id;
33446
+ const ours = (hidden) => hidden.startsWith(`/${UNLINKED_DIR}/${instance}.p`) && /^[0-9a-f]+$/.test(hidden.slice(`/${UNLINKED_DIR}/${instance}.p`.length));
33447
+ const open2 = (tab, paths) => {
33448
+ for (const [key, release] of [...gates]) {
33449
+ const [owner, path] = key.split("\0");
33450
+ if (owner !== tab || paths && !paths.includes(path)) continue;
33451
+ gates.delete(key);
33452
+ release();
33453
+ }
33454
+ };
33455
+ return {
33456
+ keeping: async (tab, paths) => {
33457
+ for (const path of paths) {
33458
+ if (gone.has(tab)) return;
33459
+ const release = await this.unlinked.gate.acquire(path);
33460
+ const key = `${tab}\0${path}`;
33461
+ let done5 = false;
33462
+ const once2 = () => {
33463
+ if (done5) return;
33464
+ done5 = true;
33465
+ clearTimeout(timer);
33466
+ release();
33467
+ };
33468
+ const timer = setTimeout(() => {
33469
+ if (gates.get(key) === once2) gates.delete(key);
33470
+ once2();
33471
+ }, PEER_GATE_MS);
33472
+ if (gone.has(tab)) once2();
33473
+ else gates.set(key, once2);
33474
+ }
33475
+ },
33476
+ kept: async (tab, items, paths) => {
33477
+ for (const { path, hidden, fid, undo } of items) {
33478
+ if (undo !== void 0) this.unadopt(path, undo, fid);
33479
+ else this.adoptKept(path, hidden, fid, ours);
33480
+ }
33481
+ open2(tab, paths);
33482
+ },
33483
+ left: (tab) => {
33484
+ gone.add(tab);
33485
+ open2(tab);
33486
+ }
33487
+ };
33488
+ }
33489
+ unadopt(path, undo, fid) {
33490
+ for (const holds2 of this.held) holds2.unkeepAt(path, undo);
33491
+ void this.rootFs.announceHolds?.([[path, fid]], [], false);
33492
+ }
33493
+ adoptKept(path, hidden, fid, ours) {
33494
+ if (hidden === void 0 || !ours(hidden)) {
33495
+ this.held.doubt([path]);
33496
+ return;
33497
+ }
33498
+ const bound3 = [...this.held].map(
33499
+ (holds2) => [holds2, [...holds2.boundAt(path, fid), ...holds2.boundAt(hidden, fid)]]
33500
+ );
33501
+ const keeping = bound3.filter(([, ids]) => ids.length > 0);
33502
+ const file = this.unlinked.adopt(hidden, Math.max(keeping.length, 1));
33503
+ if (keeping.length === 0) file.release();
33504
+ for (const [holds2, ids] of keeping) holds2.keepBound(ids, path, file);
33505
+ void this.rootFs.announceHolds?.([], [path, hidden], false);
33506
+ }
33175
33507
  rootFileId(path) {
33176
- return this.mounts.locate(path) === void 0 ? this.rootFs.fileId?.(path) : void 0;
33508
+ return this.mounts.locate(path) === void 0 ? this.rootFs.bindId?.(path) : void 0;
33177
33509
  }
33178
33510
  flushingPeers(fs) {
33179
33511
  const peers = fs.peers?.();
@@ -33219,6 +33551,7 @@ var Launcher = class {
33219
33551
  busy: (path) => [...this.openFiles].some((open2) => open2.holds(path)),
33220
33552
  owner: this.unlinkOwnerOption,
33221
33553
  marks: this.pathMarks,
33554
+ peers: this.rootFs.peerKeeps?.(),
33222
33555
  ...this.owners ? { dirOwner: { uid: 0, gid: 0, mode: 448 } } : {}
33223
33556
  }
33224
33557
  );