@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.
package/dist/node.js CHANGED
@@ -3675,7 +3675,7 @@ function satisfies(version, range2) {
3675
3675
  }
3676
3676
 
3677
3677
  // src/version.ts
3678
- var KERNEL_VERSION = true ? "1.160.3" : "0.0.0-dev";
3678
+ var KERNEL_VERSION = true ? "1.162.0" : "0.0.0-dev";
3679
3679
  var OS_RELEASE = "6.0.0-slicc";
3680
3680
  function procVersion() {
3681
3681
  return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
@@ -4638,6 +4638,9 @@ var LOOKUP_OPS = [
4638
4638
  var within2 = (path, target) => path === target || target === "/" || path.startsWith(`${target}/`);
4639
4639
  var TOUCHED_MAX = 4096;
4640
4640
  var parentOf2 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
4641
+ var PEER_RM_MS = 1e3;
4642
+ var PEER_RENAME_MS = 1e4;
4643
+ var PEER_GATE_MS = PEER_RENAME_MS + 5e3;
4641
4644
  var lockName = (instance) => `slicc-unlinked:${instance}`;
4642
4645
  var dirOf = (root) => root === "/" ? `/${UNLINKED_DIR}` : `${root}/${UNLINKED_DIR}`;
4643
4646
  var PathGate = class {
@@ -4680,7 +4683,9 @@ var UnlinkedKeeper = class {
4680
4683
  links = /* @__PURE__ */ new Map();
4681
4684
  linked = /* @__PURE__ */ new Map();
4682
4685
  dirOwner;
4686
+ peers;
4683
4687
  constructor(fs, holders, rootOf, options = {}) {
4688
+ this.peers = options.peers;
4684
4689
  this.marks = options.marks ?? new PathMarks();
4685
4690
  const { locks, owner } = options;
4686
4691
  this.dirOwner = options.dirOwner;
@@ -4756,22 +4761,29 @@ var UnlinkedKeeper = class {
4756
4761
  lstat: (path) => unreplaced(path, async () => counted3(await fs.lstat(path))),
4757
4762
  async rm(path, options) {
4758
4763
  const target = fs.resolvePath("/", path);
4759
- await replace(target, async (gated, sealed) => {
4760
- const pinned = await keeper.pin(target, gated, sealed);
4761
- if (pinned.moved.has(target)) pinned.commit();
4762
- else await keeper.settle(pinned, () => fs.rm(path, options));
4763
- });
4764
+ const kept = await keeper.peerKeep(target, false, () => keeper.removable(target, options));
4765
+ if (kept.whole) return;
4766
+ await kept.undoing(
4767
+ () => replace(target, async (gated, sealed) => {
4768
+ const pinned = await keeper.pin(target, gated, sealed);
4769
+ if (pinned.moved.has(target)) pinned.commit();
4770
+ else await keeper.settle(pinned, () => fs.rm(path, options));
4771
+ })
4772
+ );
4764
4773
  },
4765
4774
  async rename(from, to) {
4766
4775
  const target = fs.resolvePath("/", to);
4767
4776
  const source = fs.resolvePath("/", from);
4768
4777
  if (target === source) return fs.rename(from, to);
4769
- await keeper.marks.around(
4770
- [source],
4771
- () => replace(target, async (gated, sealed) => {
4772
- const pinned = await keeper.pin(target, gated, sealed);
4773
- await keeper.settle(pinned, () => fs.rename(from, to));
4774
- })
4778
+ const kept = await keeper.peerKeep(target, true, () => keeper.renamable(source, target));
4779
+ await kept.undoing(
4780
+ () => keeper.marks.around(
4781
+ [source],
4782
+ () => replace(target, async (gated, sealed) => {
4783
+ const pinned = await keeper.pin(target, gated, sealed);
4784
+ await keeper.settle(pinned, () => fs.rename(from, to));
4785
+ })
4786
+ )
4775
4787
  );
4776
4788
  },
4777
4789
  symlink: (target, path, owner) => keeper.marks.around([fs.resolvePath("/", path)], () => fs.symlink(target, path, owner)),
@@ -4916,6 +4928,114 @@ var UnlinkedKeeper = class {
4916
4928
  (err) => err?.code !== "ENOENT" && err?.code !== "ENOTDIR"
4917
4929
  );
4918
4930
  }
4931
+ get id() {
4932
+ return this.instance;
4933
+ }
4934
+ adopt(hidden, refs) {
4935
+ return this.keptFile(hidden, { refs });
4936
+ }
4937
+ async peerKeep(target, over, valid2 = async () => true) {
4938
+ const none = { whole: false, undoing: (op) => op() };
4939
+ const peers = this.peers;
4940
+ if (!peers?.shared() || this.rootOf(target) !== "/") return none;
4941
+ const held = peers.heldWithin(target).filter((holder) => holder.instance !== void 0);
4942
+ if (held.length === 0 || !await valid2()) return none;
4943
+ const tabs = /* @__PURE__ */ new Map();
4944
+ for (const holder of held) tabs.set(holder.tab, [...tabs.get(holder.tab) ?? [], holder]);
4945
+ const asked = [...tabs].map(([tab, holders]) => {
4946
+ const paths = holders.map((holder) => holder.path);
4947
+ return { tab, holders, paths };
4948
+ });
4949
+ const heard = await Promise.all(
4950
+ asked.map(
4951
+ ({ tab, paths }) => peers.ask(tab, { keeping: { paths } }, over ? PEER_RENAME_MS : PEER_RM_MS)
4952
+ )
4953
+ );
4954
+ if (over && heard.includes(false)) {
4955
+ for (const { tab, paths } of asked)
4956
+ void peers.ask(tab, { kept: { items: [], paths } }, PEER_RM_MS);
4957
+ throw fsError("EBUSY", `${target}: open in another tab that did not answer`);
4958
+ }
4959
+ const { whole, moved: moved2 } = await this.setAside(peers, target, asked, heard);
4960
+ return { whole, undoing: (op) => this.undoing(op, moved2) };
4961
+ }
4962
+ async setAside(peers, target, asked, heard) {
4963
+ const mine = this.heldWithin(target);
4964
+ const taken = /* @__PURE__ */ new Set();
4965
+ let whole = false;
4966
+ const told = [];
4967
+ const moved2 = [];
4968
+ for (const [i, { tab, holders, paths }] of asked.entries()) {
4969
+ const items = [];
4970
+ for (const holder of holders) {
4971
+ const free = !mine.has(holder.path) && !taken.has(holder.path);
4972
+ const hidden = free ? await this.asideFor(holder.path, holder.instance) : void 0;
4973
+ if (hidden !== void 0) {
4974
+ taken.add(holder.path);
4975
+ moved2.push({ tab, path: holder.path, hidden, fid: holder.fid });
4976
+ if (holder.path === target) whole = true;
4977
+ }
4978
+ items.push({ path: holder.path, hidden, fid: holder.fid });
4979
+ }
4980
+ const answer2 = peers.ask(tab, { kept: { items, paths } }, PEER_RM_MS);
4981
+ if (heard[i]) told.push(answer2);
4982
+ }
4983
+ await Promise.all(told);
4984
+ return { whole, moved: moved2 };
4985
+ }
4986
+ async undoing(op, moved2) {
4987
+ try {
4988
+ return await op();
4989
+ } catch (err) {
4990
+ for (const { tab, path, hidden, fid } of moved2) {
4991
+ const back = await this.serial(async () => {
4992
+ if (await this.present(path)) return false;
4993
+ return this.fs.rename(hidden, path).then(
4994
+ () => true,
4995
+ () => false
4996
+ );
4997
+ });
4998
+ if (!back) continue;
4999
+ void this.peers?.ask(
5000
+ tab,
5001
+ { kept: { items: [{ path, hidden: void 0, fid, undo: hidden }], paths: [] } },
5002
+ PEER_RM_MS
5003
+ );
5004
+ }
5005
+ throw err;
5006
+ }
5007
+ }
5008
+ async removable(target, options) {
5009
+ const st = await this.fs.lstat(target).catch(() => void 0);
5010
+ return st !== void 0 && (!st.isDirectory || options?.recursive === true);
5011
+ }
5012
+ async renamable(source, target) {
5013
+ const from = await this.fs.lstat(source);
5014
+ const to = await this.fs.lstat(target).catch(() => void 0);
5015
+ if (!to) return true;
5016
+ if (to.isDirectory && !from.isDirectory) throw fsError("EISDIR", target);
5017
+ if (!to.isDirectory && from.isDirectory) throw fsError("ENOTDIR", target);
5018
+ if (to.isDirectory && (await this.fs.readdir(target)).length > 0)
5019
+ throw fsError("ENOTEMPTY", target);
5020
+ return true;
5021
+ }
5022
+ async asideFor(path, instance) {
5023
+ return await this.hideFor(path, instance) ?? this.hideFor(path, instance);
5024
+ }
5025
+ hideFor(path, instance) {
5026
+ return this.serial(async () => {
5027
+ const dir = dirOf("/");
5028
+ const hidden = `${dir}/${instance}.p${crypto.randomUUID().replaceAll("-", "")}`;
5029
+ try {
5030
+ const owner = this.dirOwner;
5031
+ await this.fs.mkdir(dir, { recursive: true, ...owner ? { owner } : {} });
5032
+ await this.fs.rename(path, hidden);
5033
+ return hidden;
5034
+ } catch {
5035
+ return void 0;
5036
+ }
5037
+ });
5038
+ }
4919
5039
  keptFile(hidden, entry) {
4920
5040
  return {
4921
5041
  hidden,
@@ -4970,7 +5090,7 @@ var UnlinkedKeeper = class {
4970
5090
  }
4971
5091
  async tidy(dir) {
4972
5092
  const left = await this.fs.readdir(dir).catch(() => [UNLINKED_DIR]);
4973
- if (left.length === 0) await this.fs.rm(dir, { recursive: true }).catch(() => void 0);
5093
+ if (left.length === 0) await this.fs.rm(dir).catch(() => void 0);
4974
5094
  }
4975
5095
  sweep(root) {
4976
5096
  const query = this.locks?.query?.bind(this.locks);
@@ -6380,6 +6500,11 @@ async function shownTarget(fs, target) {
6380
6500
  const st = await quiet2(fs.stat(target));
6381
6501
  return st?.isFile && await readsAs2(fs, NOBODY, target) ? st : void 0;
6382
6502
  }
6503
+ async function madeFor(owner, virtual) {
6504
+ if (!owner?.existing || await virtual() === void 0) return owner;
6505
+ const { existing: _, ...made2 } = owner;
6506
+ return made2;
6507
+ }
6383
6508
  function withCommandDirs(fs, names2, userDir = async () => void 0, owners = false, targetOf2 = async () => void 0) {
6384
6509
  async function nearest(path) {
6385
6510
  const st = await fs.stat(path).catch(() => void 0);
@@ -6477,7 +6602,7 @@ function withCommandDirs(fs, names2, userDir = async () => void 0, owners = fals
6477
6602
  }),
6478
6603
  readFileBuffer: (path) => fs.readFileBuffer(path).catch((err) => content(path, err)),
6479
6604
  commandTarget: async (path) => (await entryTarget(path))?.path,
6480
- writeFile: (path, content2, owner) => fs.writeFile(path, content2, owner),
6605
+ writeFile: async (path, content2, owner) => fs.writeFile(path, content2, await madeFor(owner, () => entryTarget(path))),
6481
6606
  link: (from, to, follow2) => fs.link(from, to, follow2),
6482
6607
  links: (path) => mayLink(fs, path),
6483
6608
  async exists(path) {
@@ -6642,6 +6767,10 @@ function withEtcFiles(fs, files) {
6642
6767
  }
6643
6768
  } : {},
6644
6769
  readFile: (path) => fs.readFile(path).catch(fallback(path, (b) => new TextDecoder().decode(b))),
6770
+ writeFile: async (path, content, owner) => {
6771
+ if (owner?.existing && files.has(path)) await materialize(path);
6772
+ return fs.writeFile(path, content, owner);
6773
+ },
6645
6774
  ...fs.append ? {
6646
6775
  append: async (path, bytes, options) => {
6647
6776
  if (files.has(path)) await materialize(path);
@@ -8919,12 +9048,15 @@ function linkedTo(mount, path) {
8919
9048
  var within5 = (path, target) => path === target || target === "/" || path.startsWith(`${target}/`);
8920
9049
  var HeldSet = class extends Set {
8921
9050
  idle;
8922
- constructor(idle2) {
9051
+ moved;
9052
+ constructor(idle2, moved2) {
8923
9053
  super();
8924
9054
  this.idle = idle2;
9055
+ this.moved = moved2;
8925
9056
  }
8926
9057
  add(holds2) {
8927
9058
  holds2.onIdle = this.idle;
9059
+ holds2.onMoved = this.moved;
8928
9060
  return super.add(holds2);
8929
9061
  }
8930
9062
  doubt(paths, over) {
@@ -8961,6 +9093,7 @@ var HeldPaths = class extends Map {
8961
9093
  doubted = /* @__PURE__ */ new Map();
8962
9094
  doubts = 0;
8963
9095
  onIdle;
9096
+ onMoved;
8964
9097
  keyOf(path) {
8965
9098
  return this.aliases.get(path) ?? path;
8966
9099
  }
@@ -9018,12 +9151,25 @@ var HeldPaths = class extends Map {
9018
9151
  for (const file of this.unlinked.values()) if (file.hidden === hidden) return true;
9019
9152
  return false;
9020
9153
  }
9154
+ boundAt(path, fid) {
9155
+ return this.handlesAt(path).filter(
9156
+ (id2) => !this.keptHandles.has(id2) && this.bound.get(id2)?.fid === fid
9157
+ );
9158
+ }
9159
+ keepBound(ids, path, file) {
9160
+ for (const id2 of ids) this.keptHandles.set(id2, file);
9161
+ this.uses.set(file, ids.length);
9162
+ this.unlinked.set(path, file);
9163
+ }
9021
9164
  keep(path, file) {
9022
9165
  const fresh3 = this.handlesAt(path).filter((id2) => !this.keptHandles.has(id2));
9023
9166
  for (const id2 of fresh3) this.keptHandles.set(id2, file);
9024
9167
  this.uses.set(file, Math.max(fresh3.length, 1));
9025
9168
  this.unlinked.set(path, file);
9026
9169
  }
9170
+ unkeepAt(path, hidden) {
9171
+ for (const file of [...this.uses.keys()]) if (file.hidden === hidden) this.unkeep(path, file);
9172
+ }
9027
9173
  unkeep(path, file) {
9028
9174
  for (const [id2, kept] of this.keptHandles) if (kept === file) this.keptHandles.delete(id2);
9029
9175
  this.uses.delete(file);
@@ -9121,6 +9267,7 @@ var HeldPaths = class extends Map {
9121
9267
  for (const [id2, key] of this.handles) {
9122
9268
  if (within5(key, from)) this.handles.set(id2, to + key.slice(from.length));
9123
9269
  }
9270
+ this.onMoved?.(from, to);
9124
9271
  }
9125
9272
  isRevoked(path) {
9126
9273
  return this.revoked.has(path);
@@ -9591,12 +9738,12 @@ var MountFs = class _MountFs {
9591
9738
  const target = await found.mount.conn.call({ op: "readlink", path: found.rel });
9592
9739
  return this.follow(this.base.resolvePath(parentOf5(real), target), hops + 1, create);
9593
9740
  }
9594
- async open(mount, rel2, write, owner) {
9741
+ async open(mount, rel2, write, owner, create = write) {
9595
9742
  return await mount.conn.call({
9596
9743
  op: "open",
9597
9744
  path: rel2,
9598
9745
  write,
9599
- create: write,
9746
+ create,
9600
9747
  truncate: write,
9601
9748
  exclusive: false,
9602
9749
  ...owner
@@ -9693,10 +9840,10 @@ var MountFs = class _MountFs {
9693
9840
  `${real} would be larger than this mount's maxfile (${mount.maxFile})`
9694
9841
  );
9695
9842
  }
9696
- const made2 = await this.driverOwner(found, real, owner, false);
9843
+ const made2 = owner?.existing ? void 0 : await this.driverOwner(found, real, owner, false);
9697
9844
  for (let attempt = 1; ; attempt++) {
9698
9845
  try {
9699
- return await this.writeWhole(mount, real, rel2, bytes, made2);
9846
+ return await this.writeWhole(mount, real, rel2, bytes, made2, owner?.existing !== true);
9700
9847
  } catch (err) {
9701
9848
  if (err.code !== "ESTALE" || attempt >= STALE_RETRIES) throw err;
9702
9849
  }
@@ -9714,10 +9861,10 @@ var MountFs = class _MountFs {
9714
9861
  this.table.forget(mount, real);
9715
9862
  }
9716
9863
  }
9717
- async writeWhole(mount, real, rel2, bytes, owner) {
9864
+ async writeWhole(mount, real, rel2, bytes, owner, create = true) {
9718
9865
  await this.table.commit(real);
9719
9866
  this.table.forget(mount, real);
9720
- const fh = await this.open(mount, rel2, true, owner);
9867
+ const fh = await this.open(mount, rel2, true, owner, create);
9721
9868
  const chunk2 = mount.caps.maxIo ?? DEFAULT_IO;
9722
9869
  try {
9723
9870
  for (let offset = 0; offset < bytes.length; offset += chunk2) {
@@ -10454,6 +10601,11 @@ function ownerFor(entry, mode) {
10454
10601
  if (!cred) return void 0;
10455
10602
  return ownerOf(cred, mode === void 0 ? void 0 : mode & ~(entry.umask?.() ?? 18));
10456
10603
  }
10604
+ function writeOwner(entry, req) {
10605
+ const owner = ownerFor(entry, 438);
10606
+ if (req.handle === void 0 || req.mode !== 1) return owner;
10607
+ return { ...owner ?? {}, existing: true };
10608
+ }
10457
10609
  function plainDirMode(entry, mode) {
10458
10610
  if (entry.cred || mode === void 0) return void 0;
10459
10611
  const wanted = mode & 4095 & ~(entry.umask?.() ?? 18);
@@ -10524,7 +10676,7 @@ async function run(entry, path, req) {
10524
10676
  case "read":
10525
10677
  return { ok: true, kind: "bytes", bytes: await readSource(entry, path) };
10526
10678
  case "write":
10527
- await fs.writeFile(path, req.body ?? new Uint8Array(0), ownerFor(entry, 438));
10679
+ await fs.writeFile(path, req.body ?? new Uint8Array(0), writeOwner(entry, req));
10528
10680
  return done;
10529
10681
  case "exists":
10530
10682
  return json2(await fs.exists(path));
@@ -10748,12 +10900,18 @@ async function holdOp(entry, path, req) {
10748
10900
  if (req.nofollow && !await ownEntry(entry, path, real ?? path)) continue;
10749
10901
  const fid = req.ino === void 0 ? void 0 : await idOf(entry, real ?? path);
10750
10902
  if (!r || !r.missed(path) && !r.changedSince(path, gen) && !r.inFlight?.(path))
10751
- return json2(entry.hold?.(path, on, open, real, void 0, req.ino, fid) ?? null);
10903
+ return json2(await heldOpen(entry, path, real, req, fid));
10752
10904
  }
10753
10905
  }
10754
10906
  entry.hold?.(path, on, open, void 0, req.handle);
10755
10907
  return done;
10756
10908
  }
10909
+ async function heldOpen(entry, path, real, req, fid) {
10910
+ const handle = entry.hold?.(path, true, true, real, void 0, req.ino, fid);
10911
+ if (handle === void 0) return null;
10912
+ await entry.announceHold?.(real ?? path, fid, ((req.mode ?? 0) & HOLD_CREATED) !== 0);
10913
+ return handle;
10914
+ }
10757
10915
  async function idOf(entry, path) {
10758
10916
  return await entry.fileId?.(path)?.catch(() => void 0) ?? void 0;
10759
10917
  }
@@ -11771,6 +11929,10 @@ var SYS_TREE = {
11771
11929
  `,
11772
11930
  slicc_file_ids: (listing) => `${listing.fileIds ?? 0}
11773
11931
  `,
11932
+ slicc_peer_holds: ({ holds: holds2 }) => `${holds2?.mine ?? 0} ${holds2?.peers ?? 0} ${holds2?.sent ?? 0}
11933
+ `,
11934
+ slicc_peer_held: ({ holds: holds2 }) => (holds2?.held ?? []).map((path) => `${path}
11935
+ `).join(""),
11774
11936
  slicc_rename_acks: (listing) => {
11775
11937
  const samples = [...listing.renameAcks?.ackMs ?? []].sort((a, b) => a - b);
11776
11938
  const median = samples[Math.floor(samples.length / 2)] ?? 0;
@@ -22853,6 +23015,7 @@ function holdingToken(opts, holds2, caller) {
22853
23015
  ...opts.statfs ? { statfs: opts.statfs } : {},
22854
23016
  ...opts.renames ? { renames: opts.renames } : {},
22855
23017
  ...opts.fileId ? { fileId: opts.fileId } : {},
23018
+ ...opts.announceHold ? { announceHold: opts.announceHold } : {},
22856
23019
  ...opts.renameSeq ? { renameSeq: opts.renameSeq } : {},
22857
23020
  hold: (path, on, open, real, handle, ino, fid) => holds2.hold(path, on, open, real, handle, ino, fid),
22858
23021
  boundIno: holds2.boundIno.bind(holds2),
@@ -23404,6 +23567,14 @@ var RemoteWatch = class {
23404
23567
  mourned = /* @__PURE__ */ new Set();
23405
23568
  others = /* @__PURE__ */ new Map();
23406
23569
  union = /* @__PURE__ */ new Set();
23570
+ myHolds = /* @__PURE__ */ new Map();
23571
+ peerHolds = /* @__PURE__ */ new Map();
23572
+ holdsSent = 0;
23573
+ silent = /* @__PURE__ */ new Set();
23574
+ holders = /* @__PURE__ */ new Set();
23575
+ instances = /* @__PURE__ */ new Map();
23576
+ instance;
23577
+ keeps;
23407
23578
  listeners = /* @__PURE__ */ new Set();
23408
23579
  waiting = /* @__PURE__ */ new Map();
23409
23580
  locks;
@@ -23447,7 +23618,7 @@ var RemoteWatch = class {
23447
23618
  this.settled = true;
23448
23619
  this.recount();
23449
23620
  });
23450
- this.channel.postMessage({ hello: this.tab, renames: true });
23621
+ this.channel.postMessage({ hello: this.tab, renames: true, keeps: true });
23451
23622
  }
23452
23623
  async join() {
23453
23624
  if (!this.locks) return;
@@ -23507,8 +23678,12 @@ var RemoteWatch = class {
23507
23678
  return `${this.name}:${tab}`;
23508
23679
  }
23509
23680
  receive(data) {
23681
+ const spoke = data.hello ?? data.here ?? data.tab ?? data.from;
23682
+ if (spoke !== void 0) this.silent.delete(spoke);
23510
23683
  const sender = data.hello ?? data.here ?? (data.paths !== void 0 ? data.tab : void 0);
23511
23684
  if (sender !== void 0 && data.renames) this.ackers.add(sender);
23685
+ const keeper = data.hello ?? data.here;
23686
+ if (keeper !== void 0 && data.keeps) this.holders.add(keeper);
23512
23687
  if (data.hello !== void 0) this.greeted(data.hello);
23513
23688
  if (data.renaming !== void 0 && data.tab !== void 0) this.heard(data);
23514
23689
  if (data.renamed !== void 0) this.renamed?.(data.renamed);
@@ -23519,7 +23694,9 @@ var RemoteWatch = class {
23519
23694
  this.met?.();
23520
23695
  }
23521
23696
  if (data.tab !== void 0 && data.paths !== void 0) this.announced(data);
23697
+ this.holdsHeard(data);
23522
23698
  if (data.ack !== void 0 && data.to === this.tab && data.from !== void 0) {
23699
+ if (data.instance !== void 0) this.instances.set(data.from, data.instance);
23523
23700
  this.acked(data.ack, data.from);
23524
23701
  }
23525
23702
  if (data.changed !== void 0) this.emit({ paths: data.changed, trees: [], content: true });
@@ -23545,8 +23722,11 @@ var RemoteWatch = class {
23545
23722
  void this.locks.request(name2, async () => {
23546
23723
  this.mourned.delete(peer);
23547
23724
  this.others.delete(peer);
23725
+ this.peerHolds.delete(peer);
23726
+ this.keeps?.left(peer);
23548
23727
  this.peers.delete(peer);
23549
23728
  this.ackers.delete(peer);
23729
+ this.holders.delete(peer);
23550
23730
  this.flushedAt(peer);
23551
23731
  this.rebuild();
23552
23732
  this.recount();
@@ -23559,8 +23739,9 @@ var RemoteWatch = class {
23559
23739
  greeted(peer) {
23560
23740
  this.arrived(peer);
23561
23741
  const answer2 = () => {
23562
- this.channel.postMessage({ here: this.tab, to: peer, renames: true });
23742
+ this.channel.postMessage({ here: this.tab, to: peer, renames: true, keeps: true });
23563
23743
  if (this.mine.length > 0) void this.announce(this.mine);
23744
+ if (this.myHolds.size > 0) this.sendHolds([...this.myHolds], []);
23564
23745
  };
23565
23746
  if (!this.quiet) {
23566
23747
  answer2();
@@ -23583,6 +23764,127 @@ var RemoteWatch = class {
23583
23764
  this.rebuild();
23584
23765
  this.channel.postMessage({ ack: seq2, from: this.tab, to: from });
23585
23766
  }
23767
+ holdsHeard(data) {
23768
+ if (data.tab === void 0) return;
23769
+ if (data.holds !== void 0) this.heldBy(data);
23770
+ if (data.to === this.tab && (data.keeping || data.kept)) void this.keepAsked(data);
23771
+ }
23772
+ keepChains = /* @__PURE__ */ new Map();
23773
+ keepAsked(data) {
23774
+ const from = data.tab;
23775
+ const next3 = (this.keepChains.get(from) ?? Promise.resolve()).then(() => this.keepOne(data));
23776
+ this.keepChains.set(from, next3);
23777
+ return next3;
23778
+ }
23779
+ async keepOne({ tab, seq: seq2, keeping, kept }) {
23780
+ const from = tab;
23781
+ const keeps = this.keeps;
23782
+ try {
23783
+ if (keeping) await keeps?.keeping(from, keeping.paths);
23784
+ if (kept)
23785
+ await keeps?.kept(from, kept.items, kept.paths ?? kept.items.map((item) => item.path));
23786
+ } finally {
23787
+ this.channel.postMessage({
23788
+ ack: seq2,
23789
+ from: this.tab,
23790
+ to: from,
23791
+ ...this.instance === void 0 ? {} : { instance: this.instance }
23792
+ });
23793
+ }
23794
+ }
23795
+ heldBy({ tab, holds: holds2, seq: seq2 }) {
23796
+ const from = tab;
23797
+ const { add, drop } = holds2;
23798
+ if (holds2?.instance !== void 0) this.instances.set(from, holds2.instance);
23799
+ const held = this.peerHolds.get(from) ?? /* @__PURE__ */ new Map();
23800
+ for (const path of drop) held.delete(path);
23801
+ for (const [path, fid] of add) held.set(path, fid);
23802
+ if (held.size > 0) this.peerHolds.set(from, held);
23803
+ else this.peerHolds.delete(from);
23804
+ this.channel.postMessage({ ack: seq2, from: this.tab, to: from });
23805
+ }
23806
+ hold(add, drop, wait2) {
23807
+ const fresh3 = add.filter(
23808
+ ([path, fid]) => !this.myHolds.has(path) || this.myHolds.get(path) !== fid
23809
+ );
23810
+ const gone = drop.filter((path) => this.myHolds.has(path));
23811
+ for (const path of gone) this.myHolds.delete(path);
23812
+ for (const [path, fid] of fresh3) this.myHolds.set(path, fid);
23813
+ if (fresh3.length === 0 && gone.length === 0 || this.peers.size === 0)
23814
+ return Promise.resolve();
23815
+ const seq2 = this.sendHolds(fresh3, gone);
23816
+ if (!wait2) return Promise.resolve();
23817
+ const heard = new Set(
23818
+ [...this.peers].filter((peer) => this.holders.has(peer) && !this.silent.has(peer))
23819
+ );
23820
+ return this.acks(seq2, heard, (left) => {
23821
+ for (const peer of left) this.silent.add(peer);
23822
+ });
23823
+ }
23824
+ rehold(from, to) {
23825
+ const add = [];
23826
+ const drop = [];
23827
+ for (const [path, fid] of [...this.myHolds]) {
23828
+ if (path !== from && !path.startsWith(`${from}/`)) continue;
23829
+ const moved2 = to + path.slice(from.length);
23830
+ this.myHolds.delete(path);
23831
+ this.myHolds.set(moved2, fid);
23832
+ drop.push(path);
23833
+ add.push([moved2, fid]);
23834
+ }
23835
+ if (drop.length > 0 && this.peers.size > 0) this.sendHolds(add, drop);
23836
+ }
23837
+ sendHolds(add, drop) {
23838
+ const seq2 = ++this.seq;
23839
+ this.holdsSent++;
23840
+ const instance = this.instance === void 0 ? {} : { instance: this.instance };
23841
+ this.channel.postMessage({
23842
+ tab: this.tab,
23843
+ holds: { add: [...add], drop: [...drop], ...instance },
23844
+ seq: seq2
23845
+ });
23846
+ return seq2;
23847
+ }
23848
+ heldWithin(target) {
23849
+ const out = [];
23850
+ for (const [tab, held] of this.peerHolds) {
23851
+ for (const [path, fid] of held) {
23852
+ if (path !== target && !path.startsWith(`${target}/`)) continue;
23853
+ out.push({ tab, path, fid, instance: this.instances.get(tab) });
23854
+ }
23855
+ }
23856
+ return out;
23857
+ }
23858
+ ask(to, body, cap) {
23859
+ const seq2 = ++this.seq;
23860
+ this.channel.postMessage({ tab: this.tab, to, seq: seq2, ...body });
23861
+ return new Promise((resolve2) => {
23862
+ const done4 = () => {
23863
+ clearTimeout(timer);
23864
+ this.waiting.delete(seq2);
23865
+ resolve2(left.size === 0);
23866
+ };
23867
+ const left = /* @__PURE__ */ new Set([to]);
23868
+ const timer = setTimeout(done4, cap);
23869
+ this.waiting.set(seq2, { left, done: done4 });
23870
+ });
23871
+ }
23872
+ peerHeld(path) {
23873
+ const out = [];
23874
+ for (const [tab, held] of this.peerHolds)
23875
+ if (held.has(path)) out.push({ tab, fid: held.get(path) });
23876
+ return out;
23877
+ }
23878
+ peerHeldPaths() {
23879
+ const out = /* @__PURE__ */ new Set();
23880
+ for (const held of this.peerHolds.values()) for (const path of held.keys()) out.add(path);
23881
+ return [...out].sort();
23882
+ }
23883
+ holdCounts() {
23884
+ let peers = 0;
23885
+ for (const held of this.peerHolds.values()) peers += held.size;
23886
+ return { mine: this.myHolds.size, peers, sent: this.holdsSent };
23887
+ }
23586
23888
  acked(seq2, from) {
23587
23889
  const wait2 = this.waiting.get(seq2);
23588
23890
  if (!wait2) return;
@@ -23597,11 +23899,14 @@ var RemoteWatch = class {
23597
23899
  if (!this.locks) return;
23598
23900
  const held = new Set(((await this.locks.query()).held ?? []).map((lock) => lock.name));
23599
23901
  let left = false;
23600
- for (const tab of /* @__PURE__ */ new Set([...this.others.keys(), ...this.peers])) {
23902
+ for (const tab of /* @__PURE__ */ new Set([...this.others.keys(), ...this.peers, ...this.peerHolds.keys()])) {
23601
23903
  if (!held.has(this.lockOf(tab))) {
23602
23904
  if (this.peers.delete(tab)) left = true;
23603
23905
  this.others.delete(tab);
23906
+ this.peerHolds.delete(tab);
23907
+ this.keeps?.left(tab);
23604
23908
  this.ackers.delete(tab);
23909
+ this.holders.delete(tab);
23605
23910
  }
23606
23911
  }
23607
23912
  this.rebuild();
@@ -23649,8 +23954,9 @@ var RemoteWatch = class {
23649
23954
  renamedTree(lock) {
23650
23955
  this.channel.postMessage({ renamed: lock });
23651
23956
  }
23652
- acks(seq2, from) {
23957
+ acks(seq2, from, missed) {
23653
23958
  if (from.size === 0) return Promise.resolve();
23959
+ const left = new Set(from);
23654
23960
  return new Promise((resolve2) => {
23655
23961
  const done4 = () => {
23656
23962
  clearTimeout(timer);
@@ -23658,10 +23964,11 @@ var RemoteWatch = class {
23658
23964
  resolve2();
23659
23965
  };
23660
23966
  const timer = setTimeout(() => {
23967
+ missed?.(left);
23661
23968
  done4();
23662
23969
  void this.prune();
23663
23970
  }, ANNOUNCE_WAIT_MS);
23664
- this.waiting.set(seq2, { left: new Set(from), done: done4 });
23971
+ this.waiting.set(seq2, { left, done: done4 });
23665
23972
  });
23666
23973
  }
23667
23974
  emit(change) {
@@ -25073,6 +25380,7 @@ function translate(err, path, op = "lookup", mapped = true) {
25073
25380
  return error;
25074
25381
  }
25075
25382
  var META_OPS = ["get", "update", "move", "remove", "links", "under", "all"];
25383
+ var FILE_ID_TRIES = 3;
25076
25384
  function sidecar(meta) {
25077
25385
  const pathOf = ([first]) => [first, "/"].flat().find((p) => typeof p === "string");
25078
25386
  const guarded3 = (op) => (...args) => meta[op].apply(meta, args).catch((err) => {
@@ -25312,18 +25620,48 @@ var OpfsFs = class {
25312
25620
  this.channel?.postMessage(paths);
25313
25621
  }
25314
25622
  async fileId(path) {
25623
+ for (let tries = 0; tries < FILE_ID_TRIES; tries++) {
25624
+ const fid = await this.assignId(path);
25625
+ if (fid !== void 0) return fid;
25626
+ }
25627
+ throw fsError("EAGAIN", `${path}: moved while its file id was assigned`);
25628
+ }
25629
+ bindId(path) {
25630
+ return this.assignId(path);
25631
+ }
25632
+ async assignId(path) {
25315
25633
  const found = await this.locate(path, true);
25316
25634
  const [entry] = await (this.meta.fresh ?? this.meta.get).call(this.meta, [found.path]);
25317
25635
  if (entry?.fid !== void 0) return entry.fid;
25318
25636
  const fid = crypto.randomUUID();
25637
+ let moved2 = false;
25638
+ await this.meta.update(found.path, (now) => {
25639
+ const vacatedSince = now?.vacated && (!entry?.vacated || now.ino !== entry.ino);
25640
+ if (vacatedSince || entry !== void 0 && now === void 0) {
25641
+ moved2 = true;
25642
+ return now;
25643
+ }
25644
+ return { ...occupied(now), path: found.path, fid: now?.fid ?? fid };
25645
+ });
25646
+ if (moved2) return void 0;
25319
25647
  this.idWrites++;
25320
- await this.meta.update(found.path, (now) => ({
25321
- ...occupied(now),
25322
- path: found.path,
25323
- fid: now?.fid ?? fid
25324
- }));
25325
- const [stored] = await this.meta.get([found.path]);
25326
- return stored?.fid ?? fid;
25648
+ const there = await this.locate(path, true).then(
25649
+ (at) => at.path === found.path,
25650
+ () => false
25651
+ );
25652
+ if (there) return (await this.meta.get([found.path]))[0]?.fid ?? fid;
25653
+ const gone = await this.locate(found.path, true).then(
25654
+ () => false,
25655
+ (err) => missing4(err)
25656
+ );
25657
+ if (gone) {
25658
+ await this.meta.update(found.path, (now) => {
25659
+ if (now?.fid !== fid || now.vacated) return now;
25660
+ const { fid: _, ...rest2 } = now;
25661
+ return rest2;
25662
+ });
25663
+ }
25664
+ return void 0;
25327
25665
  }
25328
25666
  heldUnder = () => [];
25329
25667
  heldBy(held) {
@@ -25342,6 +25680,30 @@ var OpfsFs = class {
25342
25680
  return this.remote?.listen(listener) ?? (() => {
25343
25681
  });
25344
25682
  }
25683
+ announceHolds(add, drop, wait2) {
25684
+ return this.remote?.hold(add, drop, wait2) ?? Promise.resolve();
25685
+ }
25686
+ holdCounts() {
25687
+ const remote = this.remote;
25688
+ return remote && { ...remote.holdCounts(), held: remote.peerHeldPaths() };
25689
+ }
25690
+ peerKeeps() {
25691
+ const remote = this.remote;
25692
+ if (!remote) return void 0;
25693
+ return {
25694
+ shared: () => this.shared(),
25695
+ heldWithin: (path) => remote.heldWithin(path),
25696
+ ask: (to, body, cap) => remote.ask(to, body, cap)
25697
+ };
25698
+ }
25699
+ servePeerKeeps(instance, handlers) {
25700
+ if (!this.remote) return;
25701
+ this.remote.instance = instance;
25702
+ this.remote.keeps = handlers;
25703
+ }
25704
+ rehold(from, to) {
25705
+ this.remote?.rehold(from, to);
25706
+ }
25345
25707
  announceWatch(paths) {
25346
25708
  return this.remote?.announce(paths) ?? Promise.resolve();
25347
25709
  }
@@ -26286,7 +26648,7 @@ var OpfsFs = class {
26286
26648
  }
26287
26649
  }
26288
26650
  async writeFile(path, content, owner) {
26289
- const [file, existed, target] = await this.fileFor(path);
26651
+ const [file, existed, target] = await this.fileFor(path, owner?.existing !== true);
26290
26652
  if (!existed) await this.stampNew(target, owner, false);
26291
26653
  if (!existed && content.length === 0) return;
26292
26654
  let writable2;
@@ -30034,7 +30396,12 @@ var Launcher = class {
30034
30396
  identities = { asked: 0, timedOut: 0 };
30035
30397
  nodes;
30036
30398
  unlinked;
30037
- held = new HeldSet((path) => this.released(path));
30399
+ held = new HeldSet(
30400
+ (path) => this.released(path),
30401
+ (from, to) => {
30402
+ if (this.mounts.locate(to) === void 0) this.rootFs.rehold?.(from, to);
30403
+ }
30404
+ );
30038
30405
  processMounts;
30039
30406
  booting = new AbortController();
30040
30407
  fstab = Promise.resolve([]);
@@ -30172,7 +30539,7 @@ var Launcher = class {
30172
30539
  busy: (path) => [...this.openFiles].some((nodes) => nodes.holds(path)),
30173
30540
  owner: options.unlinkOwner,
30174
30541
  marks: this.pathMarks,
30175
- ...this.owners ? { dirOwner: { uid: 0, gid: 0, mode: 448 } } : {}
30542
+ ...this.peerOptions(options.fs)
30176
30543
  }
30177
30544
  );
30178
30545
  this.fs = this.processChain(fs, this.nodes, this.unlinked);
@@ -31006,7 +31373,7 @@ var Launcher = class {
31006
31373
  const assign = this.mounts.locate(target.wasm) === void 0 ? this.rootFs.fileId : void 0;
31007
31374
  return this.base.stat(target.wasm).then(
31008
31375
  async (st) => {
31009
- const fid = st.fid ?? await assign?.call(this.rootFs, target.wasm);
31376
+ const fid = st.fid ?? await assign?.call(this.rootFs, target.wasm)?.catch(() => void 0);
31010
31377
  if (fid === void 0) return exeIdOf(fileIdentity(st.ino, st.mtime.getTime()));
31011
31378
  return { exeId: `fid:${fid}:${st.mtime.getTime()}` };
31012
31379
  },
@@ -31252,7 +31619,8 @@ var Launcher = class {
31252
31619
  cwdChecks: this.cwds.checks,
31253
31620
  renameAcks: this.rootFs.renameWaits?.(),
31254
31621
  staleFds: staleFdCount(),
31255
- fileIds: this.rootFs.fileIdWrites?.() ?? 0
31622
+ fileIds: this.rootFs.fileIdWrites?.() ?? 0,
31623
+ holds: this.rootFs.holdCounts?.()
31256
31624
  }),
31257
31625
  sudo: (call, signal) => this.sudo(pid, call, signal),
31258
31626
  crontab: (call) => this.crontab(pid, call),
@@ -31264,6 +31632,7 @@ var Launcher = class {
31264
31632
  statfs: (path) => this.mounts.statfs(path),
31265
31633
  renames: (since2, held) => this.renameLog.since(since2, held),
31266
31634
  fileId: (path) => this.locks.shared?.() === true ? this.rootFileId(path) : void 0,
31635
+ announceHold: (path, fid, created4) => this.locks.shared?.() === true && this.mounts.locate(path) === void 0 ? this.rootFs.announceHolds?.([[path, fid]], [], !created4) : void 0,
31267
31636
  renameSeq: (from, to) => this.renameLog.seqOf(from, to),
31268
31637
  mounts: () => this.mounts.list(),
31269
31638
  mount: (call, signal) => this.processMount(pid, call, signal),
@@ -31705,6 +32074,8 @@ var Launcher = class {
31705
32074
  const at = normalizePath(path);
31706
32075
  this.unlinked.closed(at);
31707
32076
  this.writers?.unlinked.closed(at);
32077
+ if (![...this.held].some((holds2) => holds2.holding(at)))
32078
+ void this.rootFs.announceHolds?.([], [at], false);
31708
32079
  const open = [...this.openFiles].some((nodes) => nodes.writes(at)) || heldUnder(this.held, at);
31709
32080
  if (!open) void this.mounts.commit(at).catch(() => void 0);
31710
32081
  }
@@ -32164,8 +32535,82 @@ var Launcher = class {
32164
32535
  this.writers ??= this.writerStack();
32165
32536
  return this.writers;
32166
32537
  }
32538
+ peerGates = /* @__PURE__ */ new Map();
32539
+ peerOptions(fs) {
32540
+ const peers = fs.peerKeeps?.();
32541
+ queueMicrotask(() => fs.servePeerKeeps?.(this.unlinked.id, this.peerKept()));
32542
+ return {
32543
+ ...peers ? { peers } : {},
32544
+ ...this.owners ? { dirOwner: { uid: 0, gid: 0, mode: 448 } } : {}
32545
+ };
32546
+ }
32547
+ peerKept() {
32548
+ const gates = this.peerGates;
32549
+ const gone = /* @__PURE__ */ new Set();
32550
+ const instance = this.unlinked.id;
32551
+ const ours = (hidden) => hidden.startsWith(`/${UNLINKED_DIR}/${instance}.p`) && /^[0-9a-f]+$/.test(hidden.slice(`/${UNLINKED_DIR}/${instance}.p`.length));
32552
+ const open = (tab, paths) => {
32553
+ for (const [key, release] of [...gates]) {
32554
+ const [owner, path] = key.split("\0");
32555
+ if (owner !== tab || paths && !paths.includes(path)) continue;
32556
+ gates.delete(key);
32557
+ release();
32558
+ }
32559
+ };
32560
+ return {
32561
+ keeping: async (tab, paths) => {
32562
+ for (const path of paths) {
32563
+ if (gone.has(tab)) return;
32564
+ const release = await this.unlinked.gate.acquire(path);
32565
+ const key = `${tab}\0${path}`;
32566
+ let done4 = false;
32567
+ const once2 = () => {
32568
+ if (done4) return;
32569
+ done4 = true;
32570
+ clearTimeout(timer);
32571
+ release();
32572
+ };
32573
+ const timer = setTimeout(() => {
32574
+ if (gates.get(key) === once2) gates.delete(key);
32575
+ once2();
32576
+ }, PEER_GATE_MS);
32577
+ if (gone.has(tab)) once2();
32578
+ else gates.set(key, once2);
32579
+ }
32580
+ },
32581
+ kept: async (tab, items, paths) => {
32582
+ for (const { path, hidden, fid, undo } of items) {
32583
+ if (undo !== void 0) this.unadopt(path, undo, fid);
32584
+ else this.adoptKept(path, hidden, fid, ours);
32585
+ }
32586
+ open(tab, paths);
32587
+ },
32588
+ left: (tab) => {
32589
+ gone.add(tab);
32590
+ open(tab);
32591
+ }
32592
+ };
32593
+ }
32594
+ unadopt(path, undo, fid) {
32595
+ for (const holds2 of this.held) holds2.unkeepAt(path, undo);
32596
+ void this.rootFs.announceHolds?.([[path, fid]], [], false);
32597
+ }
32598
+ adoptKept(path, hidden, fid, ours) {
32599
+ if (hidden === void 0 || !ours(hidden)) {
32600
+ this.held.doubt([path]);
32601
+ return;
32602
+ }
32603
+ const bound3 = [...this.held].map(
32604
+ (holds2) => [holds2, [...holds2.boundAt(path, fid), ...holds2.boundAt(hidden, fid)]]
32605
+ );
32606
+ const keeping = bound3.filter(([, ids]) => ids.length > 0);
32607
+ const file = this.unlinked.adopt(hidden, Math.max(keeping.length, 1));
32608
+ if (keeping.length === 0) file.release();
32609
+ for (const [holds2, ids] of keeping) holds2.keepBound(ids, path, file);
32610
+ void this.rootFs.announceHolds?.([], [path, hidden], false);
32611
+ }
32167
32612
  rootFileId(path) {
32168
- return this.mounts.locate(path) === void 0 ? this.rootFs.fileId?.(path) : void 0;
32613
+ return this.mounts.locate(path) === void 0 ? this.rootFs.bindId?.(path) : void 0;
32169
32614
  }
32170
32615
  flushingPeers(fs) {
32171
32616
  const peers = fs.peers?.();
@@ -32211,6 +32656,7 @@ var Launcher = class {
32211
32656
  busy: (path) => [...this.openFiles].some((open) => open.holds(path)),
32212
32657
  owner: this.unlinkOwnerOption,
32213
32658
  marks: this.pathMarks,
32659
+ peers: this.rootFs.peerKeeps?.(),
32214
32660
  ...this.owners ? { dirOwner: { uid: 0, gid: 0, mode: 448 } } : {}
32215
32661
  }
32216
32662
  );