@ai-ecoverse/slicc-kernel 1.160.3 → 1.162.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.
@@ -1000,6 +1000,14 @@ var RemoteWatch = class {
1000
1000
  mourned = /* @__PURE__ */ new Set();
1001
1001
  others = /* @__PURE__ */ new Map();
1002
1002
  union = /* @__PURE__ */ new Set();
1003
+ myHolds = /* @__PURE__ */ new Map();
1004
+ peerHolds = /* @__PURE__ */ new Map();
1005
+ holdsSent = 0;
1006
+ silent = /* @__PURE__ */ new Set();
1007
+ holders = /* @__PURE__ */ new Set();
1008
+ instances = /* @__PURE__ */ new Map();
1009
+ instance;
1010
+ keeps;
1003
1011
  listeners = /* @__PURE__ */ new Set();
1004
1012
  waiting = /* @__PURE__ */ new Map();
1005
1013
  locks;
@@ -1043,7 +1051,7 @@ var RemoteWatch = class {
1043
1051
  this.settled = true;
1044
1052
  this.recount();
1045
1053
  });
1046
- this.channel.postMessage({ hello: this.tab, renames: true });
1054
+ this.channel.postMessage({ hello: this.tab, renames: true, keeps: true });
1047
1055
  }
1048
1056
  async join() {
1049
1057
  if (!this.locks) return;
@@ -1103,8 +1111,12 @@ var RemoteWatch = class {
1103
1111
  return `${this.name}:${tab}`;
1104
1112
  }
1105
1113
  receive(data) {
1114
+ const spoke = data.hello ?? data.here ?? data.tab ?? data.from;
1115
+ if (spoke !== void 0) this.silent.delete(spoke);
1106
1116
  const sender = data.hello ?? data.here ?? (data.paths !== void 0 ? data.tab : void 0);
1107
1117
  if (sender !== void 0 && data.renames) this.ackers.add(sender);
1118
+ const keeper = data.hello ?? data.here;
1119
+ if (keeper !== void 0 && data.keeps) this.holders.add(keeper);
1108
1120
  if (data.hello !== void 0) this.greeted(data.hello);
1109
1121
  if (data.renaming !== void 0 && data.tab !== void 0) this.heard(data);
1110
1122
  if (data.renamed !== void 0) this.renamed?.(data.renamed);
@@ -1115,7 +1127,9 @@ var RemoteWatch = class {
1115
1127
  this.met?.();
1116
1128
  }
1117
1129
  if (data.tab !== void 0 && data.paths !== void 0) this.announced(data);
1130
+ this.holdsHeard(data);
1118
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);
1119
1133
  this.acked(data.ack, data.from);
1120
1134
  }
1121
1135
  if (data.changed !== void 0) this.emit({ paths: data.changed, trees: [], content: true });
@@ -1141,8 +1155,11 @@ var RemoteWatch = class {
1141
1155
  void this.locks.request(name2, async () => {
1142
1156
  this.mourned.delete(peer);
1143
1157
  this.others.delete(peer);
1158
+ this.peerHolds.delete(peer);
1159
+ this.keeps?.left(peer);
1144
1160
  this.peers.delete(peer);
1145
1161
  this.ackers.delete(peer);
1162
+ this.holders.delete(peer);
1146
1163
  this.flushedAt(peer);
1147
1164
  this.rebuild();
1148
1165
  this.recount();
@@ -1155,8 +1172,9 @@ var RemoteWatch = class {
1155
1172
  greeted(peer) {
1156
1173
  this.arrived(peer);
1157
1174
  const answer2 = () => {
1158
- this.channel.postMessage({ here: this.tab, to: peer, renames: true });
1175
+ this.channel.postMessage({ here: this.tab, to: peer, renames: true, keeps: true });
1159
1176
  if (this.mine.length > 0) void this.announce(this.mine);
1177
+ if (this.myHolds.size > 0) this.sendHolds([...this.myHolds], []);
1160
1178
  };
1161
1179
  if (!this.quiet) {
1162
1180
  answer2();
@@ -1179,6 +1197,127 @@ var RemoteWatch = class {
1179
1197
  this.rebuild();
1180
1198
  this.channel.postMessage({ ack: seq2, from: this.tab, to: from });
1181
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
+ }
1228
+ heldBy({ tab, holds: holds2, seq: seq2 }) {
1229
+ const from = tab;
1230
+ const { add, drop } = holds2;
1231
+ if (holds2?.instance !== void 0) this.instances.set(from, holds2.instance);
1232
+ const held = this.peerHolds.get(from) ?? /* @__PURE__ */ new Map();
1233
+ for (const path of drop) held.delete(path);
1234
+ for (const [path, fid] of add) held.set(path, fid);
1235
+ if (held.size > 0) this.peerHolds.set(from, held);
1236
+ else this.peerHolds.delete(from);
1237
+ this.channel.postMessage({ ack: seq2, from: this.tab, to: from });
1238
+ }
1239
+ hold(add, drop, wait2) {
1240
+ const fresh3 = add.filter(
1241
+ ([path, fid]) => !this.myHolds.has(path) || this.myHolds.get(path) !== fid
1242
+ );
1243
+ const gone = drop.filter((path) => this.myHolds.has(path));
1244
+ for (const path of gone) this.myHolds.delete(path);
1245
+ for (const [path, fid] of fresh3) this.myHolds.set(path, fid);
1246
+ if (fresh3.length === 0 && gone.length === 0 || this.peers.size === 0)
1247
+ return Promise.resolve();
1248
+ const seq2 = this.sendHolds(fresh3, gone);
1249
+ if (!wait2) return Promise.resolve();
1250
+ const heard = new Set(
1251
+ [...this.peers].filter((peer) => this.holders.has(peer) && !this.silent.has(peer))
1252
+ );
1253
+ return this.acks(seq2, heard, (left) => {
1254
+ for (const peer of left) this.silent.add(peer);
1255
+ });
1256
+ }
1257
+ rehold(from, to) {
1258
+ const add = [];
1259
+ const drop = [];
1260
+ for (const [path, fid] of [...this.myHolds]) {
1261
+ if (path !== from && !path.startsWith(`${from}/`)) continue;
1262
+ const moved2 = to + path.slice(from.length);
1263
+ this.myHolds.delete(path);
1264
+ this.myHolds.set(moved2, fid);
1265
+ drop.push(path);
1266
+ add.push([moved2, fid]);
1267
+ }
1268
+ if (drop.length > 0 && this.peers.size > 0) this.sendHolds(add, drop);
1269
+ }
1270
+ sendHolds(add, drop) {
1271
+ const seq2 = ++this.seq;
1272
+ this.holdsSent++;
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
+ });
1279
+ return seq2;
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
+ }
1305
+ peerHeld(path) {
1306
+ const out = [];
1307
+ for (const [tab, held] of this.peerHolds)
1308
+ if (held.has(path)) out.push({ tab, fid: held.get(path) });
1309
+ return out;
1310
+ }
1311
+ peerHeldPaths() {
1312
+ const out = /* @__PURE__ */ new Set();
1313
+ for (const held of this.peerHolds.values()) for (const path of held.keys()) out.add(path);
1314
+ return [...out].sort();
1315
+ }
1316
+ holdCounts() {
1317
+ let peers = 0;
1318
+ for (const held of this.peerHolds.values()) peers += held.size;
1319
+ return { mine: this.myHolds.size, peers, sent: this.holdsSent };
1320
+ }
1182
1321
  acked(seq2, from) {
1183
1322
  const wait2 = this.waiting.get(seq2);
1184
1323
  if (!wait2) return;
@@ -1193,11 +1332,14 @@ var RemoteWatch = class {
1193
1332
  if (!this.locks) return;
1194
1333
  const held = new Set(((await this.locks.query()).held ?? []).map((lock) => lock.name));
1195
1334
  let left = false;
1196
- for (const tab of /* @__PURE__ */ new Set([...this.others.keys(), ...this.peers])) {
1335
+ for (const tab of /* @__PURE__ */ new Set([...this.others.keys(), ...this.peers, ...this.peerHolds.keys()])) {
1197
1336
  if (!held.has(this.lockOf(tab))) {
1198
1337
  if (this.peers.delete(tab)) left = true;
1199
1338
  this.others.delete(tab);
1339
+ this.peerHolds.delete(tab);
1340
+ this.keeps?.left(tab);
1200
1341
  this.ackers.delete(tab);
1342
+ this.holders.delete(tab);
1201
1343
  }
1202
1344
  }
1203
1345
  this.rebuild();
@@ -1245,8 +1387,9 @@ var RemoteWatch = class {
1245
1387
  renamedTree(lock) {
1246
1388
  this.channel.postMessage({ renamed: lock });
1247
1389
  }
1248
- acks(seq2, from) {
1390
+ acks(seq2, from, missed) {
1249
1391
  if (from.size === 0) return Promise.resolve();
1392
+ const left = new Set(from);
1250
1393
  return new Promise((resolve2) => {
1251
1394
  const done5 = () => {
1252
1395
  clearTimeout(timer);
@@ -1254,10 +1397,11 @@ var RemoteWatch = class {
1254
1397
  resolve2();
1255
1398
  };
1256
1399
  const timer = setTimeout(() => {
1400
+ missed?.(left);
1257
1401
  done5();
1258
1402
  void this.prune();
1259
1403
  }, ANNOUNCE_WAIT_MS);
1260
- this.waiting.set(seq2, { left: new Set(from), done: done5 });
1404
+ this.waiting.set(seq2, { left, done: done5 });
1261
1405
  });
1262
1406
  }
1263
1407
  emit(change) {
@@ -1360,6 +1504,7 @@ function translate(err, path, op = "lookup", mapped = true) {
1360
1504
  return error;
1361
1505
  }
1362
1506
  var META_OPS = ["get", "update", "move", "remove", "links", "under", "all"];
1507
+ var FILE_ID_TRIES = 3;
1363
1508
  function sidecar(meta) {
1364
1509
  const pathOf = ([first]) => [first, "/"].flat().find((p) => typeof p === "string");
1365
1510
  const guarded2 = (op) => (...args) => meta[op].apply(meta, args).catch((err) => {
@@ -1603,18 +1748,48 @@ var OpfsFs = class {
1603
1748
  this.channel?.postMessage(paths);
1604
1749
  }
1605
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) {
1606
1761
  const found = await this.locate(path, true);
1607
1762
  const [entry] = await (this.meta.fresh ?? this.meta.get).call(this.meta, [found.path]);
1608
1763
  if (entry?.fid !== void 0) return entry.fid;
1609
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;
1610
1775
  this.idWrites++;
1611
- await this.meta.update(found.path, (now) => ({
1612
- ...occupied(now),
1613
- path: found.path,
1614
- fid: now?.fid ?? fid
1615
- }));
1616
- const [stored] = await this.meta.get([found.path]);
1617
- 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;
1618
1793
  }
1619
1794
  heldUnder = () => [];
1620
1795
  heldBy(held) {
@@ -1633,6 +1808,30 @@ var OpfsFs = class {
1633
1808
  return this.remote?.listen(listener) ?? (() => {
1634
1809
  });
1635
1810
  }
1811
+ announceHolds(add, drop, wait2) {
1812
+ return this.remote?.hold(add, drop, wait2) ?? Promise.resolve();
1813
+ }
1814
+ holdCounts() {
1815
+ const remote = this.remote;
1816
+ return remote && { ...remote.holdCounts(), held: remote.peerHeldPaths() };
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
+ }
1832
+ rehold(from, to) {
1833
+ this.remote?.rehold(from, to);
1834
+ }
1636
1835
  announceWatch(paths) {
1637
1836
  return this.remote?.announce(paths) ?? Promise.resolve();
1638
1837
  }
@@ -2577,7 +2776,7 @@ var OpfsFs = class {
2577
2776
  }
2578
2777
  }
2579
2778
  async writeFile(path, content, owner) {
2580
- const [file, existed, target] = await this.fileFor(path);
2779
+ const [file, existed, target] = await this.fileFor(path, owner?.existing !== true);
2581
2780
  if (!existed) await this.stampNew(target, owner, false);
2582
2781
  if (!existed && content.length === 0) return;
2583
2782
  let writable2;
@@ -8236,7 +8435,7 @@ function satisfies(version, range2) {
8236
8435
  }
8237
8436
 
8238
8437
  // src/version.ts
8239
- var KERNEL_VERSION = true ? "1.160.3" : "0.0.0-dev";
8438
+ var KERNEL_VERSION = true ? "1.162.0" : "0.0.0-dev";
8240
8439
  var OS_RELEASE = "6.0.0-slicc";
8241
8440
  function procVersion() {
8242
8441
  return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
@@ -9186,6 +9385,9 @@ var LOOKUP_OPS = [
9186
9385
  var within4 = (path, target) => path === target || target === "/" || path.startsWith(`${target}/`);
9187
9386
  var TOUCHED_MAX = 4096;
9188
9387
  var parentOf5 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
9388
+ var PEER_RM_MS = 1e3;
9389
+ var PEER_RENAME_MS = 1e4;
9390
+ var PEER_GATE_MS = PEER_RENAME_MS + 5e3;
9189
9391
  var lockName = (instance) => `slicc-unlinked:${instance}`;
9190
9392
  var dirOf = (root) => root === "/" ? `/${UNLINKED_DIR}` : `${root}/${UNLINKED_DIR}`;
9191
9393
  var PathGate = class {
@@ -9228,7 +9430,9 @@ var UnlinkedKeeper = class {
9228
9430
  links = /* @__PURE__ */ new Map();
9229
9431
  linked = /* @__PURE__ */ new Map();
9230
9432
  dirOwner;
9433
+ peers;
9231
9434
  constructor(fs, holders, rootOf, options = {}) {
9435
+ this.peers = options.peers;
9232
9436
  this.marks = options.marks ?? new PathMarks();
9233
9437
  const { locks, owner } = options;
9234
9438
  this.dirOwner = options.dirOwner;
@@ -9304,22 +9508,29 @@ var UnlinkedKeeper = class {
9304
9508
  lstat: (path) => unreplaced(path, async () => counted2(await fs.lstat(path))),
9305
9509
  async rm(path, options) {
9306
9510
  const target = fs.resolvePath("/", path);
9307
- await replace(target, async (gated, sealed) => {
9308
- const pinned = await keeper.pin(target, gated, sealed);
9309
- if (pinned.moved.has(target)) pinned.commit();
9310
- else await keeper.settle(pinned, () => fs.rm(path, options));
9311
- });
9511
+ const kept = await keeper.peerKeep(target, false, () => keeper.removable(target, options));
9512
+ if (kept.whole) return;
9513
+ await kept.undoing(
9514
+ () => replace(target, async (gated, sealed) => {
9515
+ const pinned = await keeper.pin(target, gated, sealed);
9516
+ if (pinned.moved.has(target)) pinned.commit();
9517
+ else await keeper.settle(pinned, () => fs.rm(path, options));
9518
+ })
9519
+ );
9312
9520
  },
9313
9521
  async rename(from, to) {
9314
9522
  const target = fs.resolvePath("/", to);
9315
9523
  const source = fs.resolvePath("/", from);
9316
9524
  if (target === source) return fs.rename(from, to);
9317
- await keeper.marks.around(
9318
- [source],
9319
- () => replace(target, async (gated, sealed) => {
9320
- const pinned = await keeper.pin(target, gated, sealed);
9321
- await keeper.settle(pinned, () => fs.rename(from, to));
9322
- })
9525
+ const kept = await keeper.peerKeep(target, true, () => keeper.renamable(source, target));
9526
+ await kept.undoing(
9527
+ () => keeper.marks.around(
9528
+ [source],
9529
+ () => replace(target, async (gated, sealed) => {
9530
+ const pinned = await keeper.pin(target, gated, sealed);
9531
+ await keeper.settle(pinned, () => fs.rename(from, to));
9532
+ })
9533
+ )
9323
9534
  );
9324
9535
  },
9325
9536
  symlink: (target, path, owner) => keeper.marks.around([fs.resolvePath("/", path)], () => fs.symlink(target, path, owner)),
@@ -9464,6 +9675,114 @@ var UnlinkedKeeper = class {
9464
9675
  (err) => err?.code !== "ENOENT" && err?.code !== "ENOTDIR"
9465
9676
  );
9466
9677
  }
9678
+ get id() {
9679
+ return this.instance;
9680
+ }
9681
+ adopt(hidden, refs) {
9682
+ return this.keptFile(hidden, { refs });
9683
+ }
9684
+ async peerKeep(target, over, valid = async () => true) {
9685
+ const none = { whole: false, undoing: (op) => op() };
9686
+ const peers = this.peers;
9687
+ if (!peers?.shared() || this.rootOf(target) !== "/") return none;
9688
+ const held = peers.heldWithin(target).filter((holder) => holder.instance !== void 0);
9689
+ if (held.length === 0 || !await valid()) return none;
9690
+ const tabs = /* @__PURE__ */ new Map();
9691
+ for (const holder of held) tabs.set(holder.tab, [...tabs.get(holder.tab) ?? [], holder]);
9692
+ const asked = [...tabs].map(([tab, holders]) => {
9693
+ const paths = holders.map((holder) => holder.path);
9694
+ return { tab, holders, paths };
9695
+ });
9696
+ const heard = await Promise.all(
9697
+ asked.map(
9698
+ ({ tab, paths }) => peers.ask(tab, { keeping: { paths } }, over ? PEER_RENAME_MS : PEER_RM_MS)
9699
+ )
9700
+ );
9701
+ if (over && heard.includes(false)) {
9702
+ for (const { tab, paths } of asked)
9703
+ void peers.ask(tab, { kept: { items: [], paths } }, PEER_RM_MS);
9704
+ throw fsError("EBUSY", `${target}: open in another tab that did not answer`);
9705
+ }
9706
+ const { whole, moved: moved2 } = await this.setAside(peers, target, asked, heard);
9707
+ return { whole, undoing: (op) => this.undoing(op, moved2) };
9708
+ }
9709
+ async setAside(peers, target, asked, heard) {
9710
+ const mine = this.heldWithin(target);
9711
+ const taken = /* @__PURE__ */ new Set();
9712
+ let whole = false;
9713
+ const told = [];
9714
+ const moved2 = [];
9715
+ for (const [i, { tab, holders, paths }] of asked.entries()) {
9716
+ const items = [];
9717
+ for (const holder of holders) {
9718
+ const free = !mine.has(holder.path) && !taken.has(holder.path);
9719
+ const hidden = free ? await this.asideFor(holder.path, holder.instance) : void 0;
9720
+ if (hidden !== void 0) {
9721
+ taken.add(holder.path);
9722
+ moved2.push({ tab, path: holder.path, hidden, fid: holder.fid });
9723
+ if (holder.path === target) whole = true;
9724
+ }
9725
+ items.push({ path: holder.path, hidden, fid: holder.fid });
9726
+ }
9727
+ const answer2 = peers.ask(tab, { kept: { items, paths } }, PEER_RM_MS);
9728
+ if (heard[i]) told.push(answer2);
9729
+ }
9730
+ await Promise.all(told);
9731
+ return { whole, moved: moved2 };
9732
+ }
9733
+ async undoing(op, moved2) {
9734
+ try {
9735
+ return await op();
9736
+ } catch (err) {
9737
+ for (const { tab, path, hidden, fid } of moved2) {
9738
+ const back = await this.serial(async () => {
9739
+ if (await this.present(path)) return false;
9740
+ return this.fs.rename(hidden, path).then(
9741
+ () => true,
9742
+ () => false
9743
+ );
9744
+ });
9745
+ if (!back) continue;
9746
+ void this.peers?.ask(
9747
+ tab,
9748
+ { kept: { items: [{ path, hidden: void 0, fid, undo: hidden }], paths: [] } },
9749
+ PEER_RM_MS
9750
+ );
9751
+ }
9752
+ throw err;
9753
+ }
9754
+ }
9755
+ async removable(target, options) {
9756
+ const st = await this.fs.lstat(target).catch(() => void 0);
9757
+ return st !== void 0 && (!st.isDirectory || options?.recursive === true);
9758
+ }
9759
+ async renamable(source, target) {
9760
+ const from = await this.fs.lstat(source);
9761
+ const to = await this.fs.lstat(target).catch(() => void 0);
9762
+ if (!to) return true;
9763
+ if (to.isDirectory && !from.isDirectory) throw fsError("EISDIR", target);
9764
+ if (!to.isDirectory && from.isDirectory) throw fsError("ENOTDIR", target);
9765
+ if (to.isDirectory && (await this.fs.readdir(target)).length > 0)
9766
+ throw fsError("ENOTEMPTY", target);
9767
+ return true;
9768
+ }
9769
+ async asideFor(path, instance) {
9770
+ return await this.hideFor(path, instance) ?? this.hideFor(path, instance);
9771
+ }
9772
+ hideFor(path, instance) {
9773
+ return this.serial(async () => {
9774
+ const dir = dirOf("/");
9775
+ const hidden = `${dir}/${instance}.p${crypto.randomUUID().replaceAll("-", "")}`;
9776
+ try {
9777
+ const owner = this.dirOwner;
9778
+ await this.fs.mkdir(dir, { recursive: true, ...owner ? { owner } : {} });
9779
+ await this.fs.rename(path, hidden);
9780
+ return hidden;
9781
+ } catch {
9782
+ return void 0;
9783
+ }
9784
+ });
9785
+ }
9467
9786
  keptFile(hidden, entry) {
9468
9787
  return {
9469
9788
  hidden,
@@ -9518,7 +9837,7 @@ var UnlinkedKeeper = class {
9518
9837
  }
9519
9838
  async tidy(dir) {
9520
9839
  const left = await this.fs.readdir(dir).catch(() => [UNLINKED_DIR]);
9521
- if (left.length === 0) await this.fs.rm(dir, { recursive: true }).catch(() => void 0);
9840
+ if (left.length === 0) await this.fs.rm(dir).catch(() => void 0);
9522
9841
  }
9523
9842
  sweep(root) {
9524
9843
  const query = this.locks?.query?.bind(this.locks);
@@ -10763,6 +11082,11 @@ async function shownTarget(fs, target) {
10763
11082
  const st = await quiet(fs.stat(target));
10764
11083
  return st?.isFile && await readsAs2(fs, NOBODY, target) ? st : void 0;
10765
11084
  }
11085
+ async function madeFor(owner, virtual) {
11086
+ if (!owner?.existing || await virtual() === void 0) return owner;
11087
+ const { existing: _, ...made2 } = owner;
11088
+ return made2;
11089
+ }
10766
11090
  function withCommandDirs(fs, names2, userDir = async () => void 0, owners = false, targetOf2 = async () => void 0) {
10767
11091
  async function nearest(path) {
10768
11092
  const st = await fs.stat(path).catch(() => void 0);
@@ -10860,7 +11184,7 @@ function withCommandDirs(fs, names2, userDir = async () => void 0, owners = fals
10860
11184
  }),
10861
11185
  readFileBuffer: (path) => fs.readFileBuffer(path).catch((err) => content(path, err)),
10862
11186
  commandTarget: async (path) => (await entryTarget(path))?.path,
10863
- writeFile: (path, content2, owner) => fs.writeFile(path, content2, owner),
11187
+ writeFile: async (path, content2, owner) => fs.writeFile(path, content2, await madeFor(owner, () => entryTarget(path))),
10864
11188
  link: (from, to, follow2) => fs.link(from, to, follow2),
10865
11189
  links: (path) => mayLink(fs, path),
10866
11190
  async exists(path) {
@@ -11025,6 +11349,10 @@ function withEtcFiles(fs, files) {
11025
11349
  }
11026
11350
  } : {},
11027
11351
  readFile: (path) => fs.readFile(path).catch(fallback(path, (b) => new TextDecoder().decode(b))),
11352
+ writeFile: async (path, content, owner) => {
11353
+ if (owner?.existing && files.has(path)) await materialize(path);
11354
+ return fs.writeFile(path, content, owner);
11355
+ },
11028
11356
  ...fs.append ? {
11029
11357
  append: async (path, bytes, options) => {
11030
11358
  if (files.has(path)) await materialize(path);
@@ -12880,12 +13208,15 @@ function linkedTo(mount, path) {
12880
13208
  var within6 = (path, target) => path === target || target === "/" || path.startsWith(`${target}/`);
12881
13209
  var HeldSet = class extends Set {
12882
13210
  idle;
12883
- constructor(idle2) {
13211
+ moved;
13212
+ constructor(idle2, moved2) {
12884
13213
  super();
12885
13214
  this.idle = idle2;
13215
+ this.moved = moved2;
12886
13216
  }
12887
13217
  add(holds2) {
12888
13218
  holds2.onIdle = this.idle;
13219
+ holds2.onMoved = this.moved;
12889
13220
  return super.add(holds2);
12890
13221
  }
12891
13222
  doubt(paths, over) {
@@ -12922,6 +13253,7 @@ var HeldPaths = class extends Map {
12922
13253
  doubted = /* @__PURE__ */ new Map();
12923
13254
  doubts = 0;
12924
13255
  onIdle;
13256
+ onMoved;
12925
13257
  keyOf(path) {
12926
13258
  return this.aliases.get(path) ?? path;
12927
13259
  }
@@ -12979,12 +13311,25 @@ var HeldPaths = class extends Map {
12979
13311
  for (const file of this.unlinked.values()) if (file.hidden === hidden) return true;
12980
13312
  return false;
12981
13313
  }
13314
+ boundAt(path, fid) {
13315
+ return this.handlesAt(path).filter(
13316
+ (id2) => !this.keptHandles.has(id2) && this.bound.get(id2)?.fid === fid
13317
+ );
13318
+ }
13319
+ keepBound(ids, path, file) {
13320
+ for (const id2 of ids) this.keptHandles.set(id2, file);
13321
+ this.uses.set(file, ids.length);
13322
+ this.unlinked.set(path, file);
13323
+ }
12982
13324
  keep(path, file) {
12983
13325
  const fresh3 = this.handlesAt(path).filter((id2) => !this.keptHandles.has(id2));
12984
13326
  for (const id2 of fresh3) this.keptHandles.set(id2, file);
12985
13327
  this.uses.set(file, Math.max(fresh3.length, 1));
12986
13328
  this.unlinked.set(path, file);
12987
13329
  }
13330
+ unkeepAt(path, hidden) {
13331
+ for (const file of [...this.uses.keys()]) if (file.hidden === hidden) this.unkeep(path, file);
13332
+ }
12988
13333
  unkeep(path, file) {
12989
13334
  for (const [id2, kept] of this.keptHandles) if (kept === file) this.keptHandles.delete(id2);
12990
13335
  this.uses.delete(file);
@@ -13082,6 +13427,7 @@ var HeldPaths = class extends Map {
13082
13427
  for (const [id2, key] of this.handles) {
13083
13428
  if (within6(key, from)) this.handles.set(id2, to + key.slice(from.length));
13084
13429
  }
13430
+ this.onMoved?.(from, to);
13085
13431
  }
13086
13432
  isRevoked(path) {
13087
13433
  return this.revoked.has(path);
@@ -13552,12 +13898,12 @@ var MountFs = class _MountFs {
13552
13898
  const target = await found.mount.conn.call({ op: "readlink", path: found.rel });
13553
13899
  return this.follow(this.base.resolvePath(parentOf8(real), target), hops + 1, create);
13554
13900
  }
13555
- async open(mount, rel2, write, owner) {
13901
+ async open(mount, rel2, write, owner, create = write) {
13556
13902
  return await mount.conn.call({
13557
13903
  op: "open",
13558
13904
  path: rel2,
13559
13905
  write,
13560
- create: write,
13906
+ create,
13561
13907
  truncate: write,
13562
13908
  exclusive: false,
13563
13909
  ...owner
@@ -13654,10 +14000,10 @@ var MountFs = class _MountFs {
13654
14000
  `${real} would be larger than this mount's maxfile (${mount.maxFile})`
13655
14001
  );
13656
14002
  }
13657
- const made2 = await this.driverOwner(found, real, owner, false);
14003
+ const made2 = owner?.existing ? void 0 : await this.driverOwner(found, real, owner, false);
13658
14004
  for (let attempt = 1; ; attempt++) {
13659
14005
  try {
13660
- return await this.writeWhole(mount, real, rel2, bytes, made2);
14006
+ return await this.writeWhole(mount, real, rel2, bytes, made2, owner?.existing !== true);
13661
14007
  } catch (err) {
13662
14008
  if (err.code !== "ESTALE" || attempt >= STALE_RETRIES) throw err;
13663
14009
  }
@@ -13675,10 +14021,10 @@ var MountFs = class _MountFs {
13675
14021
  this.table.forget(mount, real);
13676
14022
  }
13677
14023
  }
13678
- async writeWhole(mount, real, rel2, bytes, owner) {
14024
+ async writeWhole(mount, real, rel2, bytes, owner, create = true) {
13679
14025
  await this.table.commit(real);
13680
14026
  this.table.forget(mount, real);
13681
- const fh = await this.open(mount, rel2, true, owner);
14027
+ const fh = await this.open(mount, rel2, true, owner, create);
13682
14028
  const chunk2 = mount.caps.maxIo ?? DEFAULT_IO;
13683
14029
  try {
13684
14030
  for (let offset = 0; offset < bytes.length; offset += chunk2) {
@@ -14415,6 +14761,11 @@ function ownerFor2(entry, mode) {
14415
14761
  if (!cred) return void 0;
14416
14762
  return ownerOf(cred, mode === void 0 ? void 0 : mode & ~(entry.umask?.() ?? 18));
14417
14763
  }
14764
+ function writeOwner(entry, req) {
14765
+ const owner = ownerFor2(entry, 438);
14766
+ if (req.handle === void 0 || req.mode !== 1) return owner;
14767
+ return { ...owner ?? {}, existing: true };
14768
+ }
14418
14769
  function plainDirMode(entry, mode) {
14419
14770
  if (entry.cred || mode === void 0) return void 0;
14420
14771
  const wanted = mode & 4095 & ~(entry.umask?.() ?? 18);
@@ -14485,7 +14836,7 @@ async function run(entry, path, req) {
14485
14836
  case "read":
14486
14837
  return { ok: true, kind: "bytes", bytes: await readSource(entry, path) };
14487
14838
  case "write":
14488
- await fs.writeFile(path, req.body ?? new Uint8Array(0), ownerFor2(entry, 438));
14839
+ await fs.writeFile(path, req.body ?? new Uint8Array(0), writeOwner(entry, req));
14489
14840
  return done2;
14490
14841
  case "exists":
14491
14842
  return json2(await fs.exists(path));
@@ -14709,12 +15060,18 @@ async function holdOp(entry, path, req) {
14709
15060
  if (req.nofollow && !await ownEntry(entry, path, real ?? path)) continue;
14710
15061
  const fid = req.ino === void 0 ? void 0 : await idOf(entry, real ?? path);
14711
15062
  if (!r || !r.missed(path) && !r.changedSince(path, gen) && !r.inFlight?.(path))
14712
- return json2(entry.hold?.(path, on, open2, real, void 0, req.ino, fid) ?? null);
15063
+ return json2(await heldOpen(entry, path, real, req, fid));
14713
15064
  }
14714
15065
  }
14715
15066
  entry.hold?.(path, on, open2, void 0, req.handle);
14716
15067
  return done2;
14717
15068
  }
15069
+ async function heldOpen(entry, path, real, req, fid) {
15070
+ const handle = entry.hold?.(path, true, true, real, void 0, req.ino, fid);
15071
+ if (handle === void 0) return null;
15072
+ await entry.announceHold?.(real ?? path, fid, ((req.mode ?? 0) & HOLD_CREATED) !== 0);
15073
+ return handle;
15074
+ }
14718
15075
  async function idOf(entry, path) {
14719
15076
  return await entry.fileId?.(path)?.catch(() => void 0) ?? void 0;
14720
15077
  }
@@ -15732,6 +16089,10 @@ var SYS_TREE = {
15732
16089
  `,
15733
16090
  slicc_file_ids: (listing) => `${listing.fileIds ?? 0}
15734
16091
  `,
16092
+ slicc_peer_holds: ({ holds: holds2 }) => `${holds2?.mine ?? 0} ${holds2?.peers ?? 0} ${holds2?.sent ?? 0}
16093
+ `,
16094
+ slicc_peer_held: ({ holds: holds2 }) => (holds2?.held ?? []).map((path) => `${path}
16095
+ `).join(""),
15735
16096
  slicc_rename_acks: (listing) => {
15736
16097
  const samples = [...listing.renameAcks?.ackMs ?? []].sort((a, b) => a - b);
15737
16098
  const median = samples[Math.floor(samples.length / 2)] ?? 0;
@@ -26974,6 +27335,7 @@ function holdingToken(opts, holds2, caller) {
26974
27335
  ...opts.statfs ? { statfs: opts.statfs } : {},
26975
27336
  ...opts.renames ? { renames: opts.renames } : {},
26976
27337
  ...opts.fileId ? { fileId: opts.fileId } : {},
27338
+ ...opts.announceHold ? { announceHold: opts.announceHold } : {},
26977
27339
  ...opts.renameSeq ? { renameSeq: opts.renameSeq } : {},
26978
27340
  hold: (path, on, open2, real, handle, ino, fid) => holds2.hold(path, on, open2, real, handle, ino, fid),
26979
27341
  boundIno: holds2.boundIno.bind(holds2),
@@ -30927,7 +31289,12 @@ var Launcher = class {
30927
31289
  identities = { asked: 0, timedOut: 0 };
30928
31290
  nodes;
30929
31291
  unlinked;
30930
- held = new HeldSet((path) => this.released(path));
31292
+ held = new HeldSet(
31293
+ (path) => this.released(path),
31294
+ (from, to) => {
31295
+ if (this.mounts.locate(to) === void 0) this.rootFs.rehold?.(from, to);
31296
+ }
31297
+ );
30931
31298
  processMounts;
30932
31299
  booting = new AbortController();
30933
31300
  fstab = Promise.resolve([]);
@@ -31065,7 +31432,7 @@ var Launcher = class {
31065
31432
  busy: (path) => [...this.openFiles].some((nodes) => nodes.holds(path)),
31066
31433
  owner: options.unlinkOwner,
31067
31434
  marks: this.pathMarks,
31068
- ...this.owners ? { dirOwner: { uid: 0, gid: 0, mode: 448 } } : {}
31435
+ ...this.peerOptions(options.fs)
31069
31436
  }
31070
31437
  );
31071
31438
  this.fs = this.processChain(fs, this.nodes, this.unlinked);
@@ -31899,7 +32266,7 @@ var Launcher = class {
31899
32266
  const assign = this.mounts.locate(target.wasm) === void 0 ? this.rootFs.fileId : void 0;
31900
32267
  return this.base.stat(target.wasm).then(
31901
32268
  async (st) => {
31902
- const fid = st.fid ?? await assign?.call(this.rootFs, target.wasm);
32269
+ const fid = st.fid ?? await assign?.call(this.rootFs, target.wasm)?.catch(() => void 0);
31903
32270
  if (fid === void 0) return exeIdOf(fileIdentity(st.ino, st.mtime.getTime()));
31904
32271
  return { exeId: `fid:${fid}:${st.mtime.getTime()}` };
31905
32272
  },
@@ -32145,7 +32512,8 @@ var Launcher = class {
32145
32512
  cwdChecks: this.cwds.checks,
32146
32513
  renameAcks: this.rootFs.renameWaits?.(),
32147
32514
  staleFds: staleFdCount(),
32148
- fileIds: this.rootFs.fileIdWrites?.() ?? 0
32515
+ fileIds: this.rootFs.fileIdWrites?.() ?? 0,
32516
+ holds: this.rootFs.holdCounts?.()
32149
32517
  }),
32150
32518
  sudo: (call, signal) => this.sudo(pid, call, signal),
32151
32519
  crontab: (call) => this.crontab(pid, call),
@@ -32157,6 +32525,7 @@ var Launcher = class {
32157
32525
  statfs: (path) => this.mounts.statfs(path),
32158
32526
  renames: (since, held) => this.renameLog.since(since, held),
32159
32527
  fileId: (path) => this.locks.shared?.() === true ? this.rootFileId(path) : void 0,
32528
+ announceHold: (path, fid, created4) => this.locks.shared?.() === true && this.mounts.locate(path) === void 0 ? this.rootFs.announceHolds?.([[path, fid]], [], !created4) : void 0,
32160
32529
  renameSeq: (from, to) => this.renameLog.seqOf(from, to),
32161
32530
  mounts: () => this.mounts.list(),
32162
32531
  mount: (call, signal) => this.processMount(pid, call, signal),
@@ -32598,6 +32967,8 @@ var Launcher = class {
32598
32967
  const at = normalizePath(path);
32599
32968
  this.unlinked.closed(at);
32600
32969
  this.writers?.unlinked.closed(at);
32970
+ if (![...this.held].some((holds2) => holds2.holding(at)))
32971
+ void this.rootFs.announceHolds?.([], [at], false);
32601
32972
  const open2 = [...this.openFiles].some((nodes) => nodes.writes(at)) || heldUnder(this.held, at);
32602
32973
  if (!open2) void this.mounts.commit(at).catch(() => void 0);
32603
32974
  }
@@ -33057,8 +33428,82 @@ var Launcher = class {
33057
33428
  this.writers ??= this.writerStack();
33058
33429
  return this.writers;
33059
33430
  }
33431
+ peerGates = /* @__PURE__ */ new Map();
33432
+ peerOptions(fs) {
33433
+ const peers = fs.peerKeeps?.();
33434
+ queueMicrotask(() => fs.servePeerKeeps?.(this.unlinked.id, this.peerKept()));
33435
+ return {
33436
+ ...peers ? { peers } : {},
33437
+ ...this.owners ? { dirOwner: { uid: 0, gid: 0, mode: 448 } } : {}
33438
+ };
33439
+ }
33440
+ peerKept() {
33441
+ const gates = this.peerGates;
33442
+ const gone = /* @__PURE__ */ new Set();
33443
+ const instance = this.unlinked.id;
33444
+ const ours = (hidden) => hidden.startsWith(`/${UNLINKED_DIR}/${instance}.p`) && /^[0-9a-f]+$/.test(hidden.slice(`/${UNLINKED_DIR}/${instance}.p`.length));
33445
+ const open2 = (tab, paths) => {
33446
+ for (const [key, release] of [...gates]) {
33447
+ const [owner, path] = key.split("\0");
33448
+ if (owner !== tab || paths && !paths.includes(path)) continue;
33449
+ gates.delete(key);
33450
+ release();
33451
+ }
33452
+ };
33453
+ return {
33454
+ keeping: async (tab, paths) => {
33455
+ for (const path of paths) {
33456
+ if (gone.has(tab)) return;
33457
+ const release = await this.unlinked.gate.acquire(path);
33458
+ const key = `${tab}\0${path}`;
33459
+ let done5 = false;
33460
+ const once2 = () => {
33461
+ if (done5) return;
33462
+ done5 = true;
33463
+ clearTimeout(timer);
33464
+ release();
33465
+ };
33466
+ const timer = setTimeout(() => {
33467
+ if (gates.get(key) === once2) gates.delete(key);
33468
+ once2();
33469
+ }, PEER_GATE_MS);
33470
+ if (gone.has(tab)) once2();
33471
+ else gates.set(key, once2);
33472
+ }
33473
+ },
33474
+ kept: async (tab, items, paths) => {
33475
+ for (const { path, hidden, fid, undo } of items) {
33476
+ if (undo !== void 0) this.unadopt(path, undo, fid);
33477
+ else this.adoptKept(path, hidden, fid, ours);
33478
+ }
33479
+ open2(tab, paths);
33480
+ },
33481
+ left: (tab) => {
33482
+ gone.add(tab);
33483
+ open2(tab);
33484
+ }
33485
+ };
33486
+ }
33487
+ unadopt(path, undo, fid) {
33488
+ for (const holds2 of this.held) holds2.unkeepAt(path, undo);
33489
+ void this.rootFs.announceHolds?.([[path, fid]], [], false);
33490
+ }
33491
+ adoptKept(path, hidden, fid, ours) {
33492
+ if (hidden === void 0 || !ours(hidden)) {
33493
+ this.held.doubt([path]);
33494
+ return;
33495
+ }
33496
+ const bound3 = [...this.held].map(
33497
+ (holds2) => [holds2, [...holds2.boundAt(path, fid), ...holds2.boundAt(hidden, fid)]]
33498
+ );
33499
+ const keeping = bound3.filter(([, ids]) => ids.length > 0);
33500
+ const file = this.unlinked.adopt(hidden, Math.max(keeping.length, 1));
33501
+ if (keeping.length === 0) file.release();
33502
+ for (const [holds2, ids] of keeping) holds2.keepBound(ids, path, file);
33503
+ void this.rootFs.announceHolds?.([], [path, hidden], false);
33504
+ }
33060
33505
  rootFileId(path) {
33061
- return this.mounts.locate(path) === void 0 ? this.rootFs.fileId?.(path) : void 0;
33506
+ return this.mounts.locate(path) === void 0 ? this.rootFs.bindId?.(path) : void 0;
33062
33507
  }
33063
33508
  flushingPeers(fs) {
33064
33509
  const peers = fs.peers?.();
@@ -33104,6 +33549,7 @@ var Launcher = class {
33104
33549
  busy: (path) => [...this.openFiles].some((open2) => open2.holds(path)),
33105
33550
  owner: this.unlinkOwnerOption,
33106
33551
  marks: this.pathMarks,
33552
+ peers: this.rootFs.peerKeeps?.(),
33107
33553
  ...this.owners ? { dirOwner: { uid: 0, gid: 0, mode: 448 } } : {}
33108
33554
  }
33109
33555
  );