@ai-ecoverse/slicc-kernel 1.61.2 → 1.62.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.
package/dist/node.js CHANGED
@@ -105,13 +105,13 @@ function fsError(code, path) {
105
105
  return Object.assign(new Error(`${code}: ${path}`), { code });
106
106
  }
107
107
  function normalizePath(path) {
108
- const parts = [];
108
+ const parts2 = [];
109
109
  for (const part of path.split("/")) {
110
110
  if (part === "" || part === ".") continue;
111
- if (part === "..") parts.pop();
112
- else parts.push(part);
111
+ if (part === "..") parts2.pop();
112
+ else parts2.push(part);
113
113
  }
114
- return `/${parts.join("/")}`;
114
+ return `/${parts2.join("/")}`;
115
115
  }
116
116
  function resolvePath(base, path) {
117
117
  return normalizePath(path.startsWith("/") ? path : `${base}/${path}`);
@@ -129,6 +129,355 @@ function inodeOf(path) {
129
129
  return 4294967296 * (2097151 & h2) + (h1 >>> 0) || 1;
130
130
  }
131
131
 
132
+ // src/fs/owners.ts
133
+ var ROOT_OWNER = Object.freeze({ uid: 0, gid: 0 });
134
+ var S_ISUID = 2048;
135
+ var S_ISGID = 1024;
136
+ var S_IXGRP = 8;
137
+ function ownOf(entry) {
138
+ if (entry?.uid === void 0 || entry.gid === void 0) return void 0;
139
+ return { uid: entry.uid, gid: entry.gid };
140
+ }
141
+ function handedBy(entry) {
142
+ return entry?.heir ? { uid: entry.heir[0], gid: entry.heir[1] } : ownOf(entry);
143
+ }
144
+ function inherit(entries) {
145
+ for (let i = entries.length - 1; i >= 0; i--) {
146
+ const found = handedBy(entries[i]);
147
+ if (found) return found;
148
+ }
149
+ return { ...ROOT_OWNER };
150
+ }
151
+ function chain(path) {
152
+ const parts2 = path.split("/").filter(Boolean);
153
+ return ["/", ...parts2.map((_, i) => `/${parts2.slice(0, i + 1).join("/")}`)];
154
+ }
155
+ function sameOwner(a, b) {
156
+ return a.uid === b.uid && a.gid === b.gid;
157
+ }
158
+ function ownerOf(cred, mode) {
159
+ return { uid: cred.euid, gid: cred.egid, ...mode !== void 0 ? { mode } : {} };
160
+ }
161
+ function inGroup(cred, gid) {
162
+ return cred.egid === gid || cred.groups.includes(gid);
163
+ }
164
+ function mayChown(cred, current, uid, gid) {
165
+ if (cred.euid === 0) return true;
166
+ const owner = cred.euid === current.uid;
167
+ if (uid !== void 0 && !(owner && uid === current.uid)) return false;
168
+ if (gid !== void 0 && !(owner && (gid === current.gid || inGroup(cred, gid)))) return false;
169
+ return true;
170
+ }
171
+ function clearedSetId(mode) {
172
+ const bits2 = S_ISUID | (mode & S_IXGRP ? S_ISGID : 0);
173
+ return mode & 4095 & ~bits2;
174
+ }
175
+ async function chownAs(fs, cred, path, change) {
176
+ const { uid, gid, follow: follow2 } = change;
177
+ const st = follow2 ? await fs.stat(path) : await fs.lstat(path);
178
+ const current = { uid: st.uid ?? 0, gid: st.gid ?? 0 };
179
+ if (!fs.chown || !mayChown(cred, current, uid, gid)) throw fsError("EPERM", path);
180
+ const clear = st.isFile && (uid !== void 0 || gid !== void 0) ? clearedSetId(st.mode) : void 0;
181
+ await fs.chown(path, {
182
+ ...uid !== void 0 ? { uid } : {},
183
+ ...gid !== void 0 ? { gid } : {},
184
+ follow: follow2,
185
+ ...clear !== void 0 && clear !== (st.mode & 4095) ? { mode: clear } : {}
186
+ });
187
+ }
188
+
189
+ // src/fs/walk.ts
190
+ var MAX_LINKS = 40;
191
+ function parentOf(path) {
192
+ return path.slice(0, path.lastIndexOf("/")) || "/";
193
+ }
194
+ async function mustBeDir(fs, path) {
195
+ if (!fs.lstat) return;
196
+ const st = await fs.lstat(path);
197
+ if (st.isDirectory !== true) throw fsError("ENOTDIR", path);
198
+ }
199
+ async function linkOf(fs, path) {
200
+ return fs.readlink?.(path).catch(() => void 0);
201
+ }
202
+ async function realDir(fs, dir, budget = { hops: 0 }) {
203
+ const queue = dir.split("/").filter(Boolean);
204
+ let at = "/";
205
+ while (queue.length > 0) {
206
+ const part = queue.shift();
207
+ if (part === ".") continue;
208
+ if (part === "..") {
209
+ at = parentOf(at);
210
+ continue;
211
+ }
212
+ const next2 = at === "/" ? `/${part}` : `${at}/${part}`;
213
+ const link2 = await linkOf(fs, next2);
214
+ if (link2 === void 0) {
215
+ if (queue.find((p) => p !== ".") === "..") await mustBeDir(fs, next2);
216
+ at = next2;
217
+ continue;
218
+ }
219
+ if (budget.hops >= MAX_LINKS) throw fsError("ELOOP", dir);
220
+ budget.hops++;
221
+ queue.unshift(...link2.split("/").filter(Boolean));
222
+ if (link2.startsWith("/")) at = "/";
223
+ }
224
+ return at;
225
+ }
226
+ function joinName(dir, name2) {
227
+ return dir === "/" ? `/${name2}` : `${dir}/${name2}`;
228
+ }
229
+ async function realTarget(fs, path) {
230
+ const budget = { hops: 0 };
231
+ let dir = await realDir(fs, parentOf(path), budget);
232
+ let name2 = path.slice(path.lastIndexOf("/") + 1);
233
+ for (; ; ) {
234
+ const link2 = await linkOf(fs, joinName(dir, name2));
235
+ if (link2 === void 0) break;
236
+ if (budget.hops >= MAX_LINKS) throw fsError("ELOOP", path);
237
+ budget.hops++;
238
+ const target = link2.startsWith("/") ? link2 : joinName(dir, link2);
239
+ dir = await realDir(fs, parentOf(target), budget);
240
+ name2 = target.slice(target.lastIndexOf("/") + 1);
241
+ }
242
+ return joinName(dir, name2);
243
+ }
244
+
245
+ // src/fs/access.ts
246
+ var R_OK = 4;
247
+ var W_OK = 2;
248
+ var X_OK = 1;
249
+ var MAX_LINKS2 = 40;
250
+ var S_ISVTX = 512;
251
+ function permOf(st, link2) {
252
+ return {
253
+ uid: st.uid ?? 0,
254
+ gid: st.gid ?? 0,
255
+ mode: st.mode & 4095,
256
+ ...link2 !== void 0 ? { link: link2 } : {}
257
+ };
258
+ }
259
+ function permits(cred, perm, want, directory = true) {
260
+ const mode = perm.mode ?? (directory ? 493 : 420);
261
+ if (cred.euid === 0) return (want & X_OK) === 0 || directory || (mode & 73) !== 0;
262
+ const shift = cred.euid === perm.uid ? 6 : inGroup(cred, perm.gid) ? 3 : 0;
263
+ return (mode >> shift & want) === want;
264
+ }
265
+ function sticky(cred, dir, entry) {
266
+ if (cred.euid === 0 || ((dir.mode ?? 0) & S_ISVTX) === 0) return true;
267
+ return cred.euid === dir.uid || cred.euid === (entry ?? dir).uid;
268
+ }
269
+ async function ancestryOf(fs, path) {
270
+ if (fs.ancestry) return fs.ancestry(path);
271
+ const out = [];
272
+ for (const at of chain(path)) {
273
+ const st = await fs.lstat(at).catch(() => void 0);
274
+ if (!st) break;
275
+ out.push(permOf(st, st.isSymbolicLink ? await fs.readlink(at) : void 0));
276
+ }
277
+ return out;
278
+ }
279
+ var Perms = class {
280
+ known = /* @__PURE__ */ new Map();
281
+ fs;
282
+ constructor(fs) {
283
+ this.fs = fs;
284
+ }
285
+ async learn(path) {
286
+ const names = chain(path);
287
+ const perms = await ancestryOf(this.fs, path);
288
+ for (let i = 0; i < names.length; i++) {
289
+ const perm = perms[i];
290
+ this.known.set(names[i], perm);
291
+ if (perm === void 0 || perm.link !== void 0) break;
292
+ }
293
+ }
294
+ async at(path) {
295
+ if (!this.known.has(path)) await this.learn(path);
296
+ return this.known.get(path);
297
+ }
298
+ };
299
+ function parts(path) {
300
+ return path.split("/").filter((part) => part !== "" && part !== ".");
301
+ }
302
+ async function walk(fs, cred, path, follow2) {
303
+ const perms = new Perms(fs);
304
+ await perms.learn(path);
305
+ const queue = parts(path);
306
+ let at = "/";
307
+ for (let hops = 0; queue.length > 0; ) {
308
+ const part = queue.shift();
309
+ const dir = await perms.at(at);
310
+ if (!dir) return { path: joinName(at, [part, ...queue].join("/")), perm: void 0 };
311
+ if (!permits(cred, dir, X_OK)) return { path, perm: void 0, denied: at };
312
+ if (part === "..") {
313
+ at = parentOf(at);
314
+ continue;
315
+ }
316
+ const next2 = joinName(at, part);
317
+ const perm = await perms.at(next2);
318
+ const last = queue.length === 0;
319
+ if (perm?.link !== void 0 && (!last || follow2)) {
320
+ if (++hops > MAX_LINKS2) return { path: next2, perm: void 0 };
321
+ queue.unshift(...parts(perm.link));
322
+ if (perm.link.startsWith("/")) at = "/";
323
+ continue;
324
+ }
325
+ if (last) return { path: next2, perm };
326
+ at = next2;
327
+ }
328
+ return { path: at, perm: await perms.at(at) };
329
+ }
330
+ async function present(fs, path, directory) {
331
+ const st = await fs.lstat(path).catch(() => void 0);
332
+ return st !== void 0 && (!directory || st.isDirectory);
333
+ }
334
+ async function access(fs, cred, path, want, options = {}) {
335
+ const walked = await walk(fs, cred, path, options.follow !== false);
336
+ if (walked.denied !== void 0) {
337
+ if (await present(fs, walked.denied, true)) return walked;
338
+ return { path, perm: void 0, missing: true };
339
+ }
340
+ const { perm } = walked;
341
+ if (!perm) return { ...walked, missing: true };
342
+ if (want === 0 || permits(cred, perm, want, options.directory !== false)) return walked;
343
+ if (await present(fs, walked.path, false)) return { ...walked, denied: walked.path };
344
+ return { ...walked, missing: true };
345
+ }
346
+ function refused(path, errno = "EACCES") {
347
+ return fsError(errno, path);
348
+ }
349
+
350
+ // src/fs/dac.ts
351
+ var MAX_DENIAL_KEYS = 1e4;
352
+ var S_ISGID2 = 1024;
353
+ var Dac = class {
354
+ mode;
355
+ hook;
356
+ log;
357
+ seen = /* @__PURE__ */ new Set();
358
+ suppressed = false;
359
+ constructor(mode, hook, log = (line) => console.warn(line)) {
360
+ this.mode = mode;
361
+ this.hook = hook;
362
+ this.log = log;
363
+ }
364
+ get enforcing() {
365
+ return this.mode === "enforce";
366
+ }
367
+ denied(denial) {
368
+ try {
369
+ this.hook?.({ ...denial });
370
+ } catch {
371
+ }
372
+ if (this.mode === "report") this.note(denial);
373
+ else throw refused(denial.path, denial.errno);
374
+ }
375
+ note(d) {
376
+ const key = `${d.uid}\0${d.op}\0${d.errno}\0${d.path}`;
377
+ if (this.seen.has(key)) return;
378
+ if (this.seen.size >= MAX_DENIAL_KEYS) {
379
+ if (!this.suppressed) this.log("slicc-kernel users report: more denials suppressed");
380
+ this.suppressed = true;
381
+ return;
382
+ }
383
+ this.seen.add(key);
384
+ this.log(
385
+ `slicc-kernel users report: ${d.errno} ${d.op} ${d.path} (uid ${d.uid}, pid ${d.pid})`
386
+ );
387
+ }
388
+ };
389
+ function deny(g, op, path, errno = "EACCES") {
390
+ g.dac.denied({ pid: g.pid, uid: g.cred.euid, op, path, errno });
391
+ }
392
+ async function need(g, op, path, want, options = {}, shown = path) {
393
+ const walked = await access(g.fs, g.cred, path, want, options);
394
+ if (walked.denied !== void 0) deny(g, op, shown);
395
+ return walked;
396
+ }
397
+ async function stickyEntry(g, op, path, dir) {
398
+ const perm = dir.perm;
399
+ if (!perm || sticky(g.cred, perm, perm)) return;
400
+ const entry = joinName(dir.path, path.slice(path.lastIndexOf("/") + 1));
401
+ if (sticky(g.cred, perm, (await ancestryOf(g.fs, entry)).at(-1))) return;
402
+ if (await present(g.fs, entry, false)) deny(g, op, path, "EPERM");
403
+ }
404
+ async function inParent(g, op, path, old = false) {
405
+ const dir = await need(g, op, parentOf(path), W_OK | X_OK, {}, path);
406
+ if (old && dir.denied === void 0) await stickyEntry(g, op, path, dir);
407
+ }
408
+ async function makeIn(g, op, path) {
409
+ for (let dir = parentOf(path); ; dir = parentOf(dir)) {
410
+ const walked = await access(g.fs, g.cred, dir, W_OK | X_OK);
411
+ if (walked.denied !== void 0) {
412
+ if (!await present(g.fs, path, true)) deny(g, op, path);
413
+ return;
414
+ }
415
+ if (!walked.missing || dir === "/") return;
416
+ }
417
+ }
418
+ async function tree(g, op, dir, perm) {
419
+ if (!permits(g.cred, perm, R_OK | W_OK | X_OK)) return deny(g, op, dir);
420
+ for (const [name2, st] of await g.fs.readdirStat(dir)) {
421
+ if (!st) continue;
422
+ const child = joinName(dir, name2);
423
+ if (!sticky(g.cred, perm, permOf(st))) deny(g, op, child, "EPERM");
424
+ if (st.isDirectory) await tree(g, op, child, permOf(st));
425
+ }
426
+ }
427
+ async function removable(g, op, path) {
428
+ await inParent(g, op, path, true);
429
+ if (g.cred.euid === 0) return;
430
+ const st = await g.fs.lstat(path).catch(() => void 0);
431
+ if (st?.isDirectory) await tree(g, op, path, permOf(st));
432
+ }
433
+ async function renamable(g, from, to) {
434
+ if (from === to) return void await need(g, "rename", from, 0, { follow: false });
435
+ await inParent(g, "rename", from, true);
436
+ await inParent(g, "rename", to, true);
437
+ if (g.cred.euid === 0 || parentOf(from) === parentOf(to)) return;
438
+ const st = await g.fs.lstat(from).catch(() => void 0);
439
+ if (st?.isDirectory && !permits(g.cred, permOf(st), W_OK)) deny(g, "rename", from);
440
+ }
441
+ async function linkable(g, from, to, follow2) {
442
+ const source = await need(g, "link", from, 0, { follow: follow2 });
443
+ const perm = source.perm;
444
+ if (perm && g.cred.euid !== 0 && perm.uid !== g.cred.euid) {
445
+ const safe = permits(g.cred, perm, R_OK | W_OK, false);
446
+ if (!safe && await present(g.fs, source.path, false)) deny(g, "link", from, "EPERM");
447
+ }
448
+ await inParent(g, "link", to);
449
+ }
450
+ async function ownerOnly(g, op, path, follow2) {
451
+ const walked = await need(g, op, path, 0, { follow: follow2 });
452
+ const perm = walked.perm;
453
+ if (!perm || g.cred.euid === 0 || perm.uid === g.cred.euid) return walked;
454
+ if (await present(g.fs, walked.path, false)) deny(g, op, path, "EPERM");
455
+ return walked;
456
+ }
457
+ async function chmodMode(g, path, mode) {
458
+ const perm = (await ownerOnly(g, "chmod", path, true)).perm;
459
+ if (!g.dac.enforcing || !perm || g.cred.euid === 0 || inGroup(g.cred, perm.gid)) return mode;
460
+ return mode & ~S_ISGID2;
461
+ }
462
+ async function retimable(g, op, path) {
463
+ await ownerOnly(g, op, path, op !== "lutimes");
464
+ }
465
+ async function listable(g, op, path) {
466
+ await need(g, op, path, R_OK | X_OK, { directory: true });
467
+ }
468
+ async function searchable(g, op, path, follow2) {
469
+ await need(g, op, path, 0, { follow: follow2 });
470
+ }
471
+ async function readable(g, op, path) {
472
+ await need(g, op, path, R_OK, { directory: false });
473
+ }
474
+ async function writable(g, op, path) {
475
+ const st = await g.fs.stat(path).catch(() => void 0);
476
+ if (st) return void await need(g, op, path, W_OK, { directory: st.isDirectory });
477
+ const target = await need(g, op, path, 0);
478
+ if (target.denied === void 0) await need(g, op, parentOf(target.path), W_OK | X_OK, {}, path);
479
+ }
480
+
132
481
  // src/kernel/pipe.ts
133
482
  var PIPE_CAPACITY = 64 * 1024;
134
483
  var PipeError = class extends Error {
@@ -372,7 +721,7 @@ function written(device, bytes) {
372
721
  if (device === "full" && bytes.length > 0) throw new KernelError("ENOSPC");
373
722
  return bytes.length;
374
723
  }
375
- function deviceFile(device, access) {
724
+ function deviceFile(device, access2) {
376
725
  const read = async (max) => {
377
726
  if (device === "null") return new Uint8Array(0);
378
727
  const out = new Uint8Array(max);
@@ -384,10 +733,10 @@ function deviceFile(device, access) {
384
733
  return out;
385
734
  };
386
735
  return new OpenFile({
387
- ...access !== "write" ? { read } : {},
388
- ...access !== "read" ? { write: async (bytes) => written(device, bytes) } : {},
736
+ ...access2 !== "write" ? { read } : {},
737
+ ...access2 !== "read" ? { write: async (bytes) => written(device, bytes) } : {},
389
738
  seek: async () => 0,
390
- heldMeta: { device, ...access ? { access } : {} },
739
+ heldMeta: { device, ...access2 ? { access: access2 } : {} },
391
740
  close: () => {
392
741
  }
393
742
  });
@@ -1824,9 +2173,9 @@ var UserDb = class {
1824
2173
  if (await this.realDir(pnpm)) await this.fs.chmod(pnpm, scoop ? 488 : 448);
1825
2174
  }
1826
2175
  async realDir(path) {
1827
- const parts = path.split("/").filter(Boolean);
1828
- for (let n = 1; n <= parts.length; n++) {
1829
- const at = `/${parts.slice(0, n).join("/")}`;
2176
+ const parts2 = path.split("/").filter(Boolean);
2177
+ for (let n = 1; n <= parts2.length; n++) {
2178
+ const at = `/${parts2.slice(0, n).join("/")}`;
1830
2179
  const st = await this.fs.lstat(at).catch(() => void 0);
1831
2180
  if (!st?.isDirectory) return false;
1832
2181
  }
@@ -2054,7 +2403,7 @@ function versionError(theirs) {
2054
2403
  }
2055
2404
 
2056
2405
  // src/client/serve-client.ts
2057
- var MAX_LINKS = 40;
2406
+ var MAX_LINKS3 = 40;
2058
2407
  function act(session, req, signal) {
2059
2408
  if (req.action === "write") session.write(req.bytes);
2060
2409
  else if (req.action === "resize") session.resize(req.cols, req.rows);
@@ -2062,26 +2411,50 @@ function act(session, req, signal) {
2062
2411
  else session.close();
2063
2412
  }
2064
2413
  async function realpath(fs, path) {
2065
- let parts = fs.resolvePath("/", path).split("/").filter(Boolean);
2414
+ let parts2 = fs.resolvePath("/", path).split("/").filter(Boolean);
2066
2415
  let resolved = "";
2067
- for (let hops = 0; parts.length > 0; ) {
2068
- const [name2, ...rest2] = parts;
2416
+ for (let hops = 0; parts2.length > 0; ) {
2417
+ const [name2, ...rest2] = parts2;
2069
2418
  const next2 = `${resolved}/${name2}`;
2070
2419
  if (!(await fs.lstat(next2)).isSymbolicLink) {
2071
2420
  resolved = next2;
2072
- parts = rest2;
2421
+ parts2 = rest2;
2073
2422
  continue;
2074
2423
  }
2075
- if (++hops > MAX_LINKS) throw fsError("ELOOP", path);
2424
+ if (++hops > MAX_LINKS3) throw fsError("ELOOP", path);
2076
2425
  const target = await fs.readlink(next2);
2077
2426
  const base = target.startsWith("/") ? "/" : resolved || "/";
2078
- parts = [...fs.resolvePath(base, target).split("/").filter(Boolean), ...rest2];
2427
+ parts2 = [...fs.resolvePath(base, target).split("/").filter(Boolean), ...rest2];
2079
2428
  resolved = "";
2080
2429
  }
2081
2430
  return resolved || "/";
2082
2431
  }
2083
- async function fsCall(fs, method, args, owner) {
2432
+ var CLIENT_CHECKS = {
2433
+ readFile: (g, at) => readable(g, "read", at(0)),
2434
+ writeFile: (g, at) => writable(g, "write", at(0)),
2435
+ stat: (g, at) => searchable(g, "stat", at(0), true),
2436
+ lstat: (g, at) => searchable(g, "lstat", at(0), false),
2437
+ readdir: (g, at) => listable(g, "readdir", at(0)),
2438
+ mkdir: (g, at) => makeIn(g, "mkdir", at(0)),
2439
+ rm: (g, at) => removable(g, "rm", at(0)),
2440
+ rename: (g, at) => renamable(g, at(0), at(1)),
2441
+ realpath: (g, at) => searchable(g, "realpath", at(0), true),
2442
+ symlink: (g, at) => inParent(g, "symlink", at(1)),
2443
+ readlink: (g, at) => searchable(g, "readlink", at(0), false),
2444
+ exists: (g, at) => searchable(g, "exists", at(0), true)
2445
+ };
2446
+ async function fsCall(fs, method, args, owner, g) {
2084
2447
  const path = (i) => fs.resolvePath("/", String(args[i]));
2448
+ if (g) {
2449
+ const denied2 = await Promise.resolve(CLIENT_CHECKS[method](g, path)).then(
2450
+ () => false,
2451
+ (err) => {
2452
+ if (method === "exists") return true;
2453
+ throw err;
2454
+ }
2455
+ );
2456
+ if (denied2) return { result: false };
2457
+ }
2085
2458
  switch (method) {
2086
2459
  case "readFile": {
2087
2460
  const bytes = (await fs.readFileBuffer(path(0))).slice();
@@ -2144,6 +2517,10 @@ function ownerFor(l, self) {
2144
2517
  if (!l.owners || !self) return void 0;
2145
2518
  return { uid: self.uid, gid: self.gid, umask: self.umask ?? 18 };
2146
2519
  }
2520
+ function guardFor(l, self) {
2521
+ if (!l.dac || !self) return void 0;
2522
+ return { fs: l.fs, cred: credOf(self), pid: 0, dac: l.dac };
2523
+ }
2147
2524
  async function accountOf(l, user) {
2148
2525
  const found = await l.users.lookup(user);
2149
2526
  if (!found) throw fsError("ENOENT", `no user ${String(user)}`);
@@ -2212,17 +2589,28 @@ function cdpRegistry(send, gone) {
2212
2589
  }
2213
2590
  };
2214
2591
  }
2215
- function watchOp(l, req, watches, send) {
2592
+ function visibleTo(g, notify) {
2593
+ if (!g?.dac.enforcing) return notify;
2594
+ let queue = Promise.resolve();
2595
+ const seen = async (path) => (await access(g.fs, g.cred, parentOf(path), R_OK | X_OK, { directory: true })).denied === void 0;
2596
+ return (change) => {
2597
+ queue = queue.then(async () => {
2598
+ if (!("paths" in change)) return notify(change);
2599
+ const paths = [];
2600
+ for (const path of change.paths) if (await seen(path)) paths.push(path);
2601
+ if (paths.length > 0) notify({ paths });
2602
+ });
2603
+ };
2604
+ }
2605
+ async function watchOp(l, req, watches, send, g) {
2216
2606
  if (req.op === "unwatch") {
2217
2607
  watches.get(req.watch)?.();
2218
2608
  watches.delete(req.watch);
2219
2609
  return { result: true };
2220
2610
  }
2221
- const unwatch = l.watchers.watch(
2222
- req.paths,
2223
- { recursive: req.recursive },
2224
- (change) => send({ watch: req.id, change })
2225
- );
2611
+ if (g) for (const path of req.paths) await readable(g, "watch", l.fs.resolvePath("/", path));
2612
+ const notify = visibleTo(g, (change) => send({ watch: req.id, change }));
2613
+ const unwatch = l.watchers.watch(req.paths, { recursive: req.recursive }, notify);
2226
2614
  watches.set(req.id, unwatch);
2227
2615
  return { result: req.id };
2228
2616
  }
@@ -2256,9 +2644,9 @@ function serveClient(port, host) {
2256
2644
  ended.resolve();
2257
2645
  }
2258
2646
  async function hello(message3) {
2259
- const refused = versionError(message3.protocol);
2260
- if (refused) {
2261
- send({ hello: { protocol: PROTOCOL, error: refused } });
2647
+ const refused2 = versionError(message3.protocol);
2648
+ if (refused2) {
2649
+ send({ hello: { protocol: PROTOCOL, error: refused2 } });
2262
2650
  detach();
2263
2651
  return;
2264
2652
  }
@@ -2338,10 +2726,10 @@ function serveClient(port, host) {
2338
2726
  if (!FS_METHODS.includes(req.method)) {
2339
2727
  throw new Error(`unknown file system call ${req.method}`);
2340
2728
  }
2341
- return fsCall(l.fs, req.method, req.args, ownerFor(l, self));
2729
+ return fsCall(l.fs, req.method, req.args, ownerFor(l, self), guardFor(l, self));
2342
2730
  case "watch":
2343
2731
  case "unwatch":
2344
- return watchOp(l, req, watches, send);
2732
+ return watchOp(l, req, watches, send, guardFor(l, self));
2345
2733
  case "mount":
2346
2734
  return { result: await l.mount(req.spec) };
2347
2735
  case "umount":
@@ -2399,11 +2787,11 @@ function serveClient(port, host) {
2399
2787
  }
2400
2788
 
2401
2789
  // src/fs/meta.ts
2402
- function parentOf(path) {
2790
+ function parentOf2(path) {
2403
2791
  return path.slice(0, path.lastIndexOf("/")) || "/";
2404
2792
  }
2405
2793
  function moved(entry, path) {
2406
- return entry.link === void 0 ? { ...entry, path } : { ...entry, path, dir: parentOf(path) };
2794
+ return entry.link === void 0 ? { ...entry, path } : { ...entry, path, dir: parentOf2(path) };
2407
2795
  }
2408
2796
  function under(path, root) {
2409
2797
  return path === root || path.startsWith(`${root}/`);
@@ -2446,71 +2834,14 @@ var MemoryMeta = class {
2446
2834
  }
2447
2835
  };
2448
2836
 
2449
- // src/fs/owners.ts
2450
- var ROOT_OWNER = Object.freeze({ uid: 0, gid: 0 });
2451
- var S_ISUID = 2048;
2452
- var S_ISGID = 1024;
2453
- var S_IXGRP = 8;
2454
- function ownOf(entry) {
2455
- if (entry?.uid === void 0 || entry.gid === void 0) return void 0;
2456
- return { uid: entry.uid, gid: entry.gid };
2457
- }
2458
- function handedBy(entry) {
2459
- return entry?.heir ? { uid: entry.heir[0], gid: entry.heir[1] } : ownOf(entry);
2460
- }
2461
- function inherit(entries) {
2462
- for (let i = entries.length - 1; i >= 0; i--) {
2463
- const found = handedBy(entries[i]);
2464
- if (found) return found;
2465
- }
2466
- return { ...ROOT_OWNER };
2467
- }
2468
- function chain(path) {
2469
- const parts = path.split("/").filter(Boolean);
2470
- return ["/", ...parts.map((_, i) => `/${parts.slice(0, i + 1).join("/")}`)];
2471
- }
2472
- function sameOwner(a, b) {
2473
- return a.uid === b.uid && a.gid === b.gid;
2474
- }
2475
- function ownerOf(cred, mode) {
2476
- return { uid: cred.euid, gid: cred.egid, ...mode !== void 0 ? { mode } : {} };
2477
- }
2478
- function inGroup(cred, gid) {
2479
- return cred.egid === gid || cred.groups.includes(gid);
2480
- }
2481
- function mayChown(cred, current, uid, gid) {
2482
- if (cred.euid === 0) return true;
2483
- const owner = cred.euid === current.uid;
2484
- if (uid !== void 0 && !(owner && uid === current.uid)) return false;
2485
- if (gid !== void 0 && !(owner && (gid === current.gid || inGroup(cred, gid)))) return false;
2486
- return true;
2487
- }
2488
- function clearedSetId(mode) {
2489
- const bits2 = S_ISUID | (mode & S_IXGRP ? S_ISGID : 0);
2490
- return mode & 4095 & ~bits2;
2491
- }
2492
- async function chownAs(fs, cred, path, change) {
2493
- const { uid, gid, follow: follow2 } = change;
2494
- const st = follow2 ? await fs.stat(path) : await fs.lstat(path);
2495
- const current = { uid: st.uid ?? 0, gid: st.gid ?? 0 };
2496
- if (!fs.chown || !mayChown(cred, current, uid, gid)) throw fsError("EPERM", path);
2497
- const clear = st.isFile && (uid !== void 0 || gid !== void 0) ? clearedSetId(st.mode) : void 0;
2498
- await fs.chown(path, {
2499
- ...uid !== void 0 ? { uid } : {},
2500
- ...gid !== void 0 ? { gid } : {},
2501
- follow: follow2,
2502
- ...clear !== void 0 && clear !== (st.mode & 4095) ? { mode: clear } : {}
2503
- });
2504
- }
2505
-
2506
2837
  // src/fs/opfs.ts
2507
2838
  var S_IFDIR = 16384;
2508
2839
  var S_IFREG = 32768;
2509
2840
  var S_IFLNK = 40960;
2510
2841
  var PERM_MASK = 4095;
2511
- var S_ISGID2 = 1024;
2842
+ var S_ISGID3 = 1024;
2512
2843
  var READ_ATTEMPTS = 3;
2513
- var MAX_LINKS2 = 40;
2844
+ var MAX_LINKS4 = 40;
2514
2845
  var CHECK_MS = 1e3;
2515
2846
  var EXECUTABLE = /\/node_modules\/(?:@[^/]+\/)?[^/]+\/bin\//;
2516
2847
  var ERRNO_BY_DOM_ERROR = {
@@ -2542,10 +2873,10 @@ function translate(err, path, op = "lookup", mapped = true) {
2542
2873
  var META_OPS = ["get", "update", "move", "remove", "links", "under", "all"];
2543
2874
  function sidecar(meta) {
2544
2875
  const pathOf = ([first]) => [first, "/"].flat().find((p) => typeof p === "string");
2545
- const guarded = (op) => (...args) => meta[op].apply(meta, args).catch((err) => {
2876
+ const guarded2 = (op) => (...args) => meta[op].apply(meta, args).catch((err) => {
2546
2877
  throw translate(err, pathOf(args), "sidecar", false);
2547
2878
  });
2548
- return Object.fromEntries(META_OPS.map((op) => [op, guarded(op)]));
2879
+ return Object.fromEntries(META_OPS.map((op) => [op, guarded2(op)]));
2549
2880
  }
2550
2881
  function missing(err) {
2551
2882
  const code = err?.code;
@@ -2554,15 +2885,15 @@ function missing(err) {
2554
2885
  function split(path) {
2555
2886
  return path.split("/").filter(Boolean);
2556
2887
  }
2557
- function parentOf2(path) {
2888
+ function parentOf3(path) {
2558
2889
  return path.slice(0, path.lastIndexOf("/")) || "/";
2559
2890
  }
2560
2891
  function baseOf(path) {
2561
2892
  return path.slice(path.lastIndexOf("/") + 1);
2562
2893
  }
2563
2894
  function prefixes(path) {
2564
- const parts = split(path);
2565
- return parts.map((_, i) => `/${parts.slice(0, i + 1).join("/")}`);
2895
+ const parts2 = split(path);
2896
+ return parts2.map((_, i) => `/${parts2.slice(0, i + 1).join("/")}`);
2566
2897
  }
2567
2898
  function within(path, root) {
2568
2899
  return path === root || path.startsWith(`${root}/`);
@@ -2583,39 +2914,39 @@ async function openSync(handle) {
2583
2914
  }
2584
2915
  }
2585
2916
  }
2586
- function writeAll(access, at, bytes) {
2917
+ function writeAll(access2, at, bytes) {
2587
2918
  for (let done4 = 0; done4 < bytes.length; ) {
2588
- const n = access.write(bytes.subarray(done4), { at: at + done4 });
2919
+ const n = access2.write(bytes.subarray(done4), { at: at + done4 });
2589
2920
  if (!(n > 0))
2590
2921
  throw Object.assign(new Error("the file system took no more bytes"), { code: "ENOSPC" });
2591
2922
  done4 += n;
2592
2923
  }
2593
2924
  }
2594
2925
  async function positioned(handle, op) {
2595
- const access = await openSync(handle);
2596
- if (access) {
2926
+ const access2 = await openSync(handle);
2927
+ if (access2) {
2597
2928
  try {
2598
2929
  op({
2599
- write: (at, bytes) => writeAll(access, at, bytes),
2600
- truncate: (n) => access.truncate(n)
2930
+ write: (at, bytes) => writeAll(access2, at, bytes),
2931
+ truncate: (n) => access2.truncate(n)
2601
2932
  });
2602
- access.flush();
2933
+ access2.flush();
2603
2934
  } finally {
2604
- access.close();
2935
+ access2.close();
2605
2936
  }
2606
2937
  return;
2607
2938
  }
2608
- const writable = await handle.createWritable({ keepExistingData: true });
2939
+ const writable2 = await handle.createWritable({ keepExistingData: true });
2609
2940
  try {
2610
2941
  await op({
2611
- write: (at, bytes) => writable.write({ type: "write", position: at, data: bytes }),
2612
- truncate: (n) => writable.truncate(n)
2942
+ write: (at, bytes) => writable2.write({ type: "write", position: at, data: bytes }),
2943
+ truncate: (n) => writable2.truncate(n)
2613
2944
  });
2614
2945
  } catch (err) {
2615
- await writable.abort();
2946
+ await writable2.abort();
2616
2947
  throw err;
2617
2948
  }
2618
- await writable.close();
2949
+ await writable2.close();
2619
2950
  }
2620
2951
  function isHandle(child) {
2621
2952
  return "kind" in child;
@@ -2650,13 +2981,21 @@ var OpfsFs = class {
2650
2981
  trackOwners() {
2651
2982
  this.owners = true;
2652
2983
  }
2984
+ async ancestry(path) {
2985
+ const entries = await this.meta.get(chain(path));
2986
+ return entries.map((entry, i) => ({
2987
+ ...ownOf(entry) ?? (i === 0 ? ROOT_OWNER : inherit(entries.slice(0, i))),
2988
+ ...entry?.mode !== void 0 ? { mode: entry.mode } : {},
2989
+ ...entry?.link !== void 0 ? { link: entry.link } : {}
2990
+ }));
2991
+ }
2653
2992
  async handedTo(dir) {
2654
2993
  return inherit(await this.meta.get(chain(dir)));
2655
2994
  }
2656
2995
  async ownerAt(path, entry) {
2657
2996
  const own = ownOf(entry);
2658
2997
  if (own) return own;
2659
- return path === "/" ? { ...ROOT_OWNER } : this.handedTo(parentOf2(path));
2998
+ return path === "/" ? { ...ROOT_OWNER } : this.handedTo(parentOf3(path));
2660
2999
  }
2661
3000
  async owned(stat, path, entry) {
2662
3001
  return this.owners ? { ...stat, ...await this.ownerAt(path, entry) } : stat;
@@ -2666,11 +3005,11 @@ var OpfsFs = class {
2666
3005
  const entries = await this.meta.get(chain(path));
2667
3006
  const self = entries.pop();
2668
3007
  const parent = entries.at(-1);
2669
- const setgid = ((parent?.mode ?? 0) & S_ISGID2) !== 0;
3008
+ const setgid = ((parent?.mode ?? 0) & S_ISGID3) !== 0;
2670
3009
  const parentGid = (ownOf(parent) ?? inherit(entries.slice(0, -1))).gid;
2671
3010
  const want = { uid: owner.uid, gid: setgid ? parentGid : owner.gid };
2672
3011
  let mode = owner.mode === void 0 ? void 0 : owner.mode & PERM_MASK;
2673
- if (directory && setgid) mode = (mode ?? 493) | S_ISGID2;
3012
+ if (directory && setgid) mode = (mode ?? 493) | S_ISGID3;
2674
3013
  const fields = {};
2675
3014
  if (mode !== void 0 && mode !== (this.mode(path, directory, void 0) & PERM_MASK)) {
2676
3015
  fields.mode = mode;
@@ -2686,7 +3025,7 @@ var OpfsFs = class {
2686
3025
  async chown(path, change) {
2687
3026
  if (!this.owners) return;
2688
3027
  if (change.kind === "new") {
2689
- const at = resolvePath((await this.locate(parentOf2(path), true)).path, baseOf(path));
3028
+ const at = resolvePath((await this.locate(parentOf3(path), true)).path, baseOf(path));
2690
3029
  const { uid = 0, gid = 0, mode } = change;
2691
3030
  return this.stampNew(at, { uid, gid, ...mode !== void 0 ? { mode } : {} }, false);
2692
3031
  }
@@ -2714,30 +3053,30 @@ var OpfsFs = class {
2714
3053
  const [stored] = source.link ? [source.link] : await this.meta.get([source.path]);
2715
3054
  return ownOf(stored) ? void 0 : this.ownerAt(source.path, stored);
2716
3055
  }
2717
- async walk(parts, from, start2) {
3056
+ async walk(parts2, from, start2) {
2718
3057
  let dir = start2;
2719
- for (let at = from; at < parts.length; at++) {
2720
- dir = await dir.getDirectoryHandle(parts[at]);
2721
- this.dirs.set(`/${parts.slice(0, at + 1).join("/")}`, dir);
3058
+ for (let at = from; at < parts2.length; at++) {
3059
+ dir = await dir.getDirectoryHandle(parts2[at]);
3060
+ this.dirs.set(`/${parts2.slice(0, at + 1).join("/")}`, dir);
2722
3061
  this.checked.set(dir, performance.now());
2723
3062
  }
2724
3063
  return dir;
2725
3064
  }
2726
3065
  async dir(path) {
2727
- const parts = split(path);
2728
- let from = parts.length;
3066
+ const parts2 = split(path);
3067
+ let from = parts2.length;
2729
3068
  let cached;
2730
3069
  for (; from > 0 && !cached; from--)
2731
- cached = this.dirs.get(`/${parts.slice(0, from).join("/")}`);
3070
+ cached = this.dirs.get(`/${parts2.slice(0, from).join("/")}`);
2732
3071
  if (cached) from++;
2733
- const at = `/${parts.slice(0, from).join("/")}`;
3072
+ const at = `/${parts2.slice(0, from).join("/")}`;
2734
3073
  if (cached && !await this.current(at, cached)) {
2735
3074
  this.forget(at);
2736
3075
  cached = void 0;
2737
3076
  from = 0;
2738
3077
  }
2739
3078
  try {
2740
- return await this.walk(parts, from, cached ?? this.root);
3079
+ return await this.walk(parts2, from, cached ?? this.root);
2741
3080
  } catch (err) {
2742
3081
  throw translate(err, path);
2743
3082
  }
@@ -2767,10 +3106,10 @@ var OpfsFs = class {
2767
3106
  return kind === "file" ? parent.getFileHandle(name2) : parent.getDirectoryHandle(name2);
2768
3107
  }
2769
3108
  async parent(path) {
2770
- const parts = split(path);
2771
- const name2 = parts.pop();
3109
+ const parts2 = split(path);
3110
+ const name2 = parts2.pop();
2772
3111
  if (name2 === void 0) throw fsError("EBUSY", path);
2773
- return [`/${parts.join("/")}`, name2];
3112
+ return [`/${parts2.join("/")}`, name2];
2774
3113
  }
2775
3114
  async handle(path) {
2776
3115
  if (split(path).length === 0) return this.root;
@@ -2797,13 +3136,13 @@ var OpfsFs = class {
2797
3136
  async linkAlong(path) {
2798
3137
  for (const entry of await this.meta.get(prefixes(path))) {
2799
3138
  if (entry?.link === void 0) continue;
2800
- if (!await this.real(entry.path) && await this.real(parentOf2(entry.path))) return entry;
3139
+ if (!await this.real(entry.path) && await this.real(parentOf3(entry.path))) return entry;
2801
3140
  }
2802
3141
  return void 0;
2803
3142
  }
2804
3143
  async locate(path, follow2) {
2805
3144
  let current = path;
2806
- for (let hops = 0; hops <= MAX_LINKS2; hops++) {
3145
+ for (let hops = 0; hops <= MAX_LINKS4; hops++) {
2807
3146
  try {
2808
3147
  return { path: current, handle: await this.handle(current) };
2809
3148
  } catch (err) {
@@ -2812,7 +3151,7 @@ var OpfsFs = class {
2812
3151
  if (!link2) throw err;
2813
3152
  if (link2.path === current && !follow2) return { path: current, link: link2 };
2814
3153
  const target = `${link2.link}${current.slice(link2.path.length)}`;
2815
- current = target.split("/").includes("..") ? await this.physical(parentOf2(link2.path), target, hops + 1) : resolvePath(parentOf2(link2.path), target);
3154
+ current = target.split("/").includes("..") ? await this.physical(parentOf3(link2.path), target, hops + 1) : resolvePath(parentOf3(link2.path), target);
2816
3155
  }
2817
3156
  }
2818
3157
  throw fsError("ELOOP", path);
@@ -2829,7 +3168,7 @@ var OpfsFs = class {
2829
3168
  const st = await this.lstat(at);
2830
3169
  if (!st.isSymbolicLink && !st.isDirectory) throw fsError("ENOTDIR", at);
2831
3170
  if (!st.isSymbolicLink) done4.push(part);
2832
- else if (++hops > MAX_LINKS2) throw fsError("ELOOP", path);
3171
+ else if (++hops > MAX_LINKS4) throw fsError("ELOOP", path);
2833
3172
  else {
2834
3173
  const link2 = await this.readlink(at);
2835
3174
  if (link2.startsWith("/")) done4 = [];
@@ -2972,19 +3311,19 @@ var OpfsFs = class {
2972
3311
  }
2973
3312
  async writeSession(path, truncate) {
2974
3313
  const handle = (await this.fileFor(path))[0];
2975
- const writable = await this.retrying(
3314
+ const writable2 = await this.retrying(
2976
3315
  path,
2977
3316
  () => handle.createWritable({ keepExistingData: !truncate }),
2978
3317
  true
2979
3318
  );
2980
3319
  return {
2981
- write: (at, bytes) => writable.write({ type: "write", position: at, data: bytes }).catch((err) => {
3320
+ write: (at, bytes) => writable2.write({ type: "write", position: at, data: bytes }).catch((err) => {
2982
3321
  throw translate(err, path, "write", false);
2983
3322
  }),
2984
- truncate: (size) => writable.truncate(size).catch((err) => {
3323
+ truncate: (size) => writable2.truncate(size).catch((err) => {
2985
3324
  throw translate(err, path, "write", false);
2986
3325
  }),
2987
- close: () => writable.close().catch((err) => {
3326
+ close: () => writable2.close().catch((err) => {
2988
3327
  throw translate(err, path, "commit", false);
2989
3328
  })
2990
3329
  };
@@ -2999,8 +3338,8 @@ var OpfsFs = class {
2999
3338
  if (!missing(err)) throw err;
3000
3339
  }
3001
3340
  const [entry] = await this.meta.get([path]);
3002
- const wanted = entry?.link === void 0 ? path : resolvePath(parentOf2(path), entry.link);
3003
- return [resolvePath((await this.locate(parentOf2(wanted), true)).path, baseOf(wanted)), false];
3341
+ const wanted = entry?.link === void 0 ? path : resolvePath(parentOf3(path), entry.link);
3342
+ return [resolvePath((await this.locate(parentOf3(wanted), true)).path, baseOf(wanted)), false];
3004
3343
  }
3005
3344
  async fileFor(path) {
3006
3345
  const [target, existed] = await this.writeTarget(path);
@@ -3010,7 +3349,7 @@ var OpfsFs = class {
3010
3349
  dir,
3011
3350
  (parent) => parent.getFileHandle(name2, { create: true })
3012
3351
  );
3013
- if (!existed) this.touch(parentOf2(target));
3352
+ if (!existed) this.touch(parentOf3(target));
3014
3353
  return [handle, existed, target];
3015
3354
  } catch (err) {
3016
3355
  const code = err?.name === "TypeMismatchError" ? "EISDIR" : null;
@@ -3022,9 +3361,9 @@ var OpfsFs = class {
3022
3361
  if (!existed) await this.stampNew(target, owner, false);
3023
3362
  if (!existed && content.length === 0) return;
3024
3363
  try {
3025
- const writable = await file.createWritable();
3026
- await writable.write(content);
3027
- await writable.close();
3364
+ const writable2 = await file.createWritable();
3365
+ await writable2.write(content);
3366
+ await writable2.close();
3028
3367
  } catch (err) {
3029
3368
  throw translate(err, path, "commit", false);
3030
3369
  }
@@ -3080,9 +3419,9 @@ var OpfsFs = class {
3080
3419
  gid: owner.gid,
3081
3420
  ...mode ? { mode, parents: mode } : {}
3082
3421
  };
3083
- await this.mkdir(parentOf2(path), { recursive: true, ...above ? { owner: above } : {} });
3422
+ await this.mkdir(parentOf3(path), { recursive: true, ...above ? { owner: above } : {} });
3084
3423
  }
3085
- const parent = await this.locate(parentOf2(path), true);
3424
+ const parent = await this.locate(parentOf3(path), true);
3086
3425
  if (parent.handle?.kind !== "directory") throw fsError("ENOTDIR", path);
3087
3426
  await parent.handle.getDirectoryHandle(baseOf(path), { create: true });
3088
3427
  const made2 = `${parent.path === "/" ? "" : parent.path}/${baseOf(path)}`;
@@ -3111,7 +3450,7 @@ var OpfsFs = class {
3111
3450
  this.changed(found.path);
3112
3451
  }
3113
3452
  await this.meta.remove([found.path]);
3114
- this.touch(parentOf2(found.path));
3453
+ this.touch(parentOf3(found.path));
3115
3454
  }
3116
3455
  links() {
3117
3456
  return false;
@@ -3123,7 +3462,7 @@ var OpfsFs = class {
3123
3462
  if (from === to) return;
3124
3463
  const source = await this.locate(from, false);
3125
3464
  this.forget(source.path);
3126
- const parent = await this.locate(parentOf2(to), true);
3465
+ const parent = await this.locate(parentOf3(to), true);
3127
3466
  const destination = resolvePath(parent.path, baseOf(to));
3128
3467
  const sourceStat = await this.describe(source);
3129
3468
  if (sourceStat.isDirectory && within(destination, source.path)) throw fsError("EINVAL", to);
@@ -3151,7 +3490,7 @@ var OpfsFs = class {
3151
3490
  if (source.link) {
3152
3491
  if (target?.handle) await this.rm(destination);
3153
3492
  await this.meta.move(source.path, destination, carry);
3154
- this.touch(parentOf2(source.path), parent.path);
3493
+ this.touch(parentOf3(source.path), parent.path);
3155
3494
  return;
3156
3495
  }
3157
3496
  const dir = parent.handle;
@@ -3173,7 +3512,7 @@ var OpfsFs = class {
3173
3512
  }
3174
3513
  if (copied) await this.restamp(destination, timed2);
3175
3514
  this.changed(source.path, destination);
3176
- this.touch(parentOf2(source.path), parent.path);
3515
+ this.touch(parentOf3(source.path), parent.path);
3177
3516
  }
3178
3517
  async modified(path) {
3179
3518
  const handle = await this.handle(path);
@@ -3210,16 +3549,16 @@ var OpfsFs = class {
3210
3549
  }
3211
3550
  async copy(handle, dir, name2) {
3212
3551
  if (handle.kind === "file") {
3213
- const writable = await (await dir.getFileHandle(name2, { create: true })).createWritable();
3214
- await writable.write(await handle.getFile());
3215
- await writable.close();
3552
+ const writable2 = await (await dir.getFileHandle(name2, { create: true })).createWritable();
3553
+ await writable2.write(await handle.getFile());
3554
+ await writable2.close();
3216
3555
  return;
3217
3556
  }
3218
3557
  const target = await dir.getDirectoryHandle(name2, { create: true });
3219
3558
  for await (const child of handle.values()) await this.copy(child, target, child.name);
3220
3559
  }
3221
3560
  async symlink(target, path, owner) {
3222
- const parent = await this.locate(parentOf2(path), true);
3561
+ const parent = await this.locate(parentOf3(path), true);
3223
3562
  if (parent.handle?.kind !== "directory") throw fsError("ENOTDIR", path);
3224
3563
  const at = resolvePath(parent.path, baseOf(path));
3225
3564
  if (await this.locate(at, false).then(
@@ -3295,7 +3634,7 @@ var OpfsFs = class {
3295
3634
  let removed = 0;
3296
3635
  for (const entry of await this.meta.all()) {
3297
3636
  const real = await this.real(entry.path);
3298
- const stale = entry.link === void 0 ? !real : real || !await this.real(parentOf2(entry.path));
3637
+ const stale = entry.link === void 0 ? !real : real || !await this.real(parentOf3(entry.path));
3299
3638
  if (!stale) continue;
3300
3639
  await this.meta.update(entry.path, () => void 0);
3301
3640
  removed++;
@@ -3366,7 +3705,7 @@ function webLocks() {
3366
3705
  return globalThis.navigator?.locks;
3367
3706
  }
3368
3707
  var within2 = (path, target) => path === target || target === "/" || path.startsWith(`${target}/`);
3369
- var parentOf3 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
3708
+ var parentOf4 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
3370
3709
  var lockName = (instance) => `slicc-unlinked:${instance}`;
3371
3710
  var dirOf = (root) => root === "/" ? `/${UNLINKED_DIR}` : `${root}/${UNLINKED_DIR}`;
3372
3711
  var PathGate = class {
@@ -3497,7 +3836,7 @@ var UnlinkedKeeper = class {
3497
3836
  await this.serial(async () => {
3498
3837
  await Promise.allSettled([this.fs.rename(file.hidden, path)]);
3499
3838
  this.unlinkedName(file.hidden);
3500
- await this.tidy(parentOf3(file.hidden));
3839
+ await this.tidy(parentOf4(file.hidden));
3501
3840
  });
3502
3841
  }
3503
3842
  done4();
@@ -3537,7 +3876,7 @@ var UnlinkedKeeper = class {
3537
3876
  }
3538
3877
  await this.fs.rm(hidden).catch(() => void 0);
3539
3878
  this.unlinkedName(hidden);
3540
- await this.tidy(parentOf3(hidden));
3879
+ await this.tidy(parentOf4(hidden));
3541
3880
  }
3542
3881
  async linkedName(hidden) {
3543
3882
  if (!mayLink(this.fs, hidden)) return;
@@ -4547,7 +4886,7 @@ function binfmtOf(binfmts, path) {
4547
4886
  var S_IFDIR2 = 16384;
4548
4887
  var S_IFREG2 = 32768;
4549
4888
  var COMMAND = /^\/(?:usr\/)?bin\/([^/]+)$/;
4550
- var MAX_LINKS3 = 40;
4889
+ var MAX_LINKS5 = 40;
4551
4890
  function missing2(err) {
4552
4891
  return err?.code === "ENOENT";
4553
4892
  }
@@ -4567,7 +4906,7 @@ function synthetic(directory, path) {
4567
4906
  }
4568
4907
  async function followLinks(fs, path) {
4569
4908
  let current = path;
4570
- for (let hops = 0; hops < MAX_LINKS3; hops++) {
4909
+ for (let hops = 0; hops < MAX_LINKS5; hops++) {
4571
4910
  const st = await fs.lstat(current).catch(() => void 0);
4572
4911
  if (!st?.isSymbolicLink) return current;
4573
4912
  const parent = current.slice(0, current.lastIndexOf("/")) || "/";
@@ -4674,6 +5013,7 @@ function withCommandDirs(fs, names, userDir = async () => void 0, owners = false
4674
5013
  readlink: (path) => fs.readlink(path),
4675
5014
  ...fs.realpath ? { realpath: fs.realpath.bind(fs) } : {},
4676
5015
  ...fs.chown ? { chown: fs.chown.bind(fs) } : {},
5016
+ ...fs.ancestry ? { ancestry: fs.ancestry.bind(fs) } : {},
4677
5017
  chmod: (path, mode) => fs.chmod(path, mode),
4678
5018
  utimes: (path, atime, mtime) => fs.utimes(path, atime, mtime),
4679
5019
  lutimes: (path, atime, mtime) => lutimesOf(fs, path, atime, mtime),
@@ -4686,12 +5026,12 @@ var MAX_HOPS = 40;
4686
5026
  function start(path) {
4687
5027
  return { done: [], todo: path.split("/").filter((p) => p !== ""), hops: 0 };
4688
5028
  }
4689
- function next(walk) {
4690
- while (walk.todo.length > 0) {
4691
- const part = walk.todo.shift();
5029
+ function next(walk2) {
5030
+ while (walk2.todo.length > 0) {
5031
+ const part = walk2.todo.shift();
4692
5032
  if (part === ".") continue;
4693
5033
  if (part === "..") {
4694
- walk.done.pop();
5034
+ walk2.done.pop();
4695
5035
  continue;
4696
5036
  }
4697
5037
  return part;
@@ -4699,42 +5039,42 @@ function next(walk) {
4699
5039
  return void 0;
4700
5040
  }
4701
5041
  function lexical(path) {
4702
- const walk = start(path);
4703
- for (let part = next(walk); part !== void 0; part = next(walk)) walk.done.push(part);
4704
- return `/${walk.done.join("/")}`;
5042
+ const walk2 = start(path);
5043
+ for (let part = next(walk2); part !== void 0; part = next(walk2)) walk2.done.push(part);
5044
+ return `/${walk2.done.join("/")}`;
4705
5045
  }
4706
- function rest(walk, part) {
4707
- return lexical(`/${[...walk.done, part, ...walk.todo].join("/")}`);
5046
+ function rest(walk2, part) {
5047
+ return lexical(`/${[...walk2.done, part, ...walk2.todo].join("/")}`);
4708
5048
  }
4709
- function follow(walk, target) {
4710
- if (++walk.hops > MAX_HOPS) return false;
4711
- if (target.startsWith("/")) walk.done.length = 0;
4712
- walk.todo = [...target.split("/").filter((p) => p !== ""), ...walk.todo];
5049
+ function follow(walk2, target) {
5050
+ if (++walk2.hops > MAX_HOPS) return false;
5051
+ if (target.startsWith("/")) walk2.done.length = 0;
5052
+ walk2.todo = [...target.split("/").filter((p) => p !== ""), ...walk2.todo];
4713
5053
  return true;
4714
5054
  }
4715
5055
  async function physicalPathAsync(fs, path) {
4716
- const walk = start(path);
4717
- for (let part = next(walk); part !== void 0; part = next(walk)) {
4718
- const at = `/${[...walk.done, part].join("/")}`;
5056
+ const walk2 = start(path);
5057
+ for (let part = next(walk2); part !== void 0; part = next(walk2)) {
5058
+ const at = `/${[...walk2.done, part].join("/")}`;
4719
5059
  let target;
4720
5060
  try {
4721
5061
  target = (await fs.lstat(at)).isSymbolicLink ? await fs.readlink(at) : void 0;
4722
5062
  } catch {
4723
- return rest(walk, part);
5063
+ return rest(walk2, part);
4724
5064
  }
4725
- if (target === void 0) walk.done.push(part);
4726
- else if (!follow(walk, target)) return lexical(path);
5065
+ if (target === void 0) walk2.done.push(part);
5066
+ else if (!follow(walk2, target)) return lexical(path);
4727
5067
  }
4728
- return `/${walk.done.join("/")}`;
5068
+ return `/${walk2.done.join("/")}`;
4729
5069
  }
4730
5070
 
4731
5071
  // src/fs/watch.ts
4732
5072
  var WATCH_BATCH_LIMIT = 1e3;
4733
5073
  var under2 = (path, dir) => dir === "/" || path.startsWith(`${dir}/`);
4734
- var parentOf4 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
5074
+ var parentOf5 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
4735
5075
  function touches(watcher, changed) {
4736
5076
  return watcher.paths.some(
4737
- (watched) => changed === watched || under2(watched, changed) || (watcher.recursive ? under2(changed, watched) : parentOf4(changed) === watched)
5077
+ (watched) => changed === watched || under2(watched, changed) || (watcher.recursive ? under2(changed, watched) : parentOf5(changed) === watched)
4738
5078
  );
4739
5079
  }
4740
5080
  var FsWatchers = class {
@@ -4783,9 +5123,9 @@ var FsWatchers = class {
4783
5123
  mkdir: async (path, options) => {
4784
5124
  const created3 = [];
4785
5125
  if (options?.recursive) {
4786
- const parts = normalizePath(path).split("/").filter(Boolean);
4787
- for (let at = 1; at < parts.length; at++) {
4788
- const ancestor = `/${parts.slice(0, at).join("/")}`;
5126
+ const parts2 = normalizePath(path).split("/").filter(Boolean);
5127
+ for (let at = 1; at < parts2.length; at++) {
5128
+ const ancestor = `/${parts2.slice(0, at).join("/")}`;
4789
5129
  if (created3.length > 0 || !await fs.exists(ancestor)) created3.push(ancestor);
4790
5130
  }
4791
5131
  }
@@ -5014,62 +5354,6 @@ var ChildTable = class {
5014
5354
  }
5015
5355
  };
5016
5356
 
5017
- // src/fs/walk.ts
5018
- var MAX_LINKS4 = 40;
5019
- function parentOf5(path) {
5020
- return path.slice(0, path.lastIndexOf("/")) || "/";
5021
- }
5022
- async function mustBeDir(fs, path) {
5023
- if (!fs.lstat) return;
5024
- const st = await fs.lstat(path);
5025
- if (st.isDirectory !== true) throw fsError("ENOTDIR", path);
5026
- }
5027
- async function linkOf(fs, path) {
5028
- return fs.readlink?.(path).catch(() => void 0);
5029
- }
5030
- async function realDir(fs, dir, budget = { hops: 0 }) {
5031
- const queue = dir.split("/").filter(Boolean);
5032
- let at = "/";
5033
- while (queue.length > 0) {
5034
- const part = queue.shift();
5035
- if (part === ".") continue;
5036
- if (part === "..") {
5037
- at = parentOf5(at);
5038
- continue;
5039
- }
5040
- const next2 = at === "/" ? `/${part}` : `${at}/${part}`;
5041
- const link2 = await linkOf(fs, next2);
5042
- if (link2 === void 0) {
5043
- if (queue.find((p) => p !== ".") === "..") await mustBeDir(fs, next2);
5044
- at = next2;
5045
- continue;
5046
- }
5047
- if (budget.hops >= MAX_LINKS4) throw fsError("ELOOP", dir);
5048
- budget.hops++;
5049
- queue.unshift(...link2.split("/").filter(Boolean));
5050
- if (link2.startsWith("/")) at = "/";
5051
- }
5052
- return at;
5053
- }
5054
- function joinName(dir, name2) {
5055
- return dir === "/" ? `/${name2}` : `${dir}/${name2}`;
5056
- }
5057
- async function realTarget(fs, path) {
5058
- const budget = { hops: 0 };
5059
- let dir = await realDir(fs, parentOf5(path), budget);
5060
- let name2 = path.slice(path.lastIndexOf("/") + 1);
5061
- for (; ; ) {
5062
- const link2 = await linkOf(fs, joinName(dir, name2));
5063
- if (link2 === void 0) break;
5064
- if (budget.hops >= MAX_LINKS4) throw fsError("ELOOP", path);
5065
- budget.hops++;
5066
- const target = link2.startsWith("/") ? link2 : joinName(dir, link2);
5067
- dir = await realDir(fs, parentOf5(target), budget);
5068
- name2 = target.slice(target.lastIndexOf("/") + 1);
5069
- }
5070
- return joinName(dir, name2);
5071
- }
5072
-
5073
5357
  // src/mount/protocol.ts
5074
5358
  var DRIVER_PROTOCOL = [1, 2];
5075
5359
  function driverVersionError(theirs) {
@@ -5132,8 +5416,8 @@ var DriverConnection = class _DriverConnection {
5132
5416
  } finally {
5133
5417
  clearTimeout(timer);
5134
5418
  }
5135
- const refused = driverVersionError(hello.protocol);
5136
- if (refused) throw errnoError("EPROTO", refused);
5419
+ const refused2 = driverVersionError(hello.protocol);
5420
+ if (refused2) throw errnoError("EPROTO", refused2);
5137
5421
  if (hello.error) throw errnoError(hello.errno ?? "EIO", hello.error);
5138
5422
  return new _DriverConnection(port, hello.capabilities ?? {}, waiting, timeoutMs, options.onFail);
5139
5423
  }
@@ -5173,7 +5457,7 @@ var DriverConnection = class _DriverConnection {
5173
5457
  };
5174
5458
 
5175
5459
  // src/mount/mount-fs.ts
5176
- var S_ISGID3 = 1024;
5460
+ var S_ISGID4 = 1024;
5177
5461
  var ID = /^\d{1,10}$/;
5178
5462
  var MAX_ID2 = 4294967294;
5179
5463
  function idOption(value) {
@@ -5186,7 +5470,7 @@ function checkIds(options) {
5186
5470
  if (!ID.test(value) || Number(value) > MAX_ID2) throw errnoError("EINVAL", `${key}=${value}`);
5187
5471
  }
5188
5472
  }
5189
- var MAX_LINKS5 = 40;
5473
+ var MAX_LINKS6 = 40;
5190
5474
  var STALE_RETRIES = 3;
5191
5475
  var SETTLE_ROUNDS = 4;
5192
5476
  var DEFAULT_IO = 8 * 1024 * 1024;
@@ -5424,12 +5708,12 @@ var MountTable = class {
5424
5708
  }
5425
5709
  list() {
5426
5710
  const mounted = [...this.mounts.values()].map(
5427
- ({ type, source, target, options, state, error, present: present2 }) => ({
5711
+ ({ type, source, target, options, state, error, present: present3 }) => ({
5428
5712
  type,
5429
5713
  source,
5430
5714
  target,
5431
5715
  options: { ...options },
5432
- state: state === "ok" && present2?.() === false ? "nomedium" : state,
5716
+ state: state === "ok" && present3?.() === false ? "nomedium" : state,
5433
5717
  ...error ? { error } : {}
5434
5718
  })
5435
5719
  );
@@ -5704,7 +5988,7 @@ var MountFs = class {
5704
5988
  throw err;
5705
5989
  }
5706
5990
  if (attr2.kind !== "symlink") return real;
5707
- if (hops >= MAX_LINKS5) throw errnoError("ELOOP", path);
5991
+ if (hops >= MAX_LINKS6) throw errnoError("ELOOP", path);
5708
5992
  const target = await found.mount.conn.call({ op: "readlink", path: found.rel });
5709
5993
  return this.follow(this.base.resolvePath(parentOf6(real), target), hops + 1, create);
5710
5994
  }
@@ -5724,9 +6008,9 @@ var MountFs = class {
5724
6008
  if (!owner || !mount.owners || !mount.caps.owners) return void 0;
5725
6009
  const dir = parentOf6(real);
5726
6010
  const above = statOf(await this.table.getattr(mount, dir, parentOf6(rel2)), dir, mount);
5727
- const setgid = (above.mode & S_ISGID3) !== 0;
6011
+ const setgid = (above.mode & S_ISGID4) !== 0;
5728
6012
  let mode = owner.mode;
5729
- if (directory && setgid) mode = (mode ?? 493) | S_ISGID3;
6013
+ if (directory && setgid) mode = (mode ?? 493) | S_ISGID4;
5730
6014
  return {
5731
6015
  uid: owner.uid,
5732
6016
  gid: setgid ? above.gid : owner.gid,
@@ -6001,14 +6285,14 @@ var MountFs = class {
6001
6285
  await mount.conn.call({ op: "mkdir", path: rel2, ...made2 });
6002
6286
  return;
6003
6287
  }
6004
- const parts = rel2.split("/").filter(Boolean);
6005
- for (let n = 1; n <= parts.length; n++) {
6006
- const sub = `/${parts.slice(0, n).join("/")}`;
6288
+ const parts2 = rel2.split("/").filter(Boolean);
6289
+ for (let n = 1; n <= parts2.length; n++) {
6290
+ const sub = `/${parts2.slice(0, n).join("/")}`;
6007
6291
  const full = normalizePath(`${mount.target}${sub}`);
6008
6292
  const exists = await this.table.getattr(mount, full, sub).catch(() => void 0);
6009
6293
  if (exists?.kind === "directory") continue;
6010
6294
  if (exists) throw errnoError("ENOTDIR", full);
6011
- const own = n === parts.length ? owner : owner && { ...owner, mode: owner.parents };
6295
+ const own = n === parts2.length ? owner : owner && { ...owner, mode: owner.parents };
6012
6296
  const made2 = await this.driverOwner({ mount, rel: sub }, full, own, true);
6013
6297
  this.table.forget(mount, full);
6014
6298
  await mount.conn.call({ op: "mkdir", path: sub, ...made2 });
@@ -6101,6 +6385,20 @@ var MountFs = class {
6101
6385
  if (this.at(real)) throw errnoError("ENOSYS", path);
6102
6386
  return real;
6103
6387
  }
6388
+ async ancestry(path) {
6389
+ const names = chain(normalizePath(path));
6390
+ const first = names.findIndex((name2) => this.at(name2));
6391
+ if (first < 0) return ancestryOf(this.base, path);
6392
+ const out = first === 0 ? [] : await ancestryOf(this.base, names[first - 1]);
6393
+ if (out.length < first || out.some((perm) => perm?.link !== void 0)) return out;
6394
+ for (const name2 of names.slice(first)) {
6395
+ const st = await this.lstat(name2).catch(() => void 0);
6396
+ if (!st) break;
6397
+ out.push(permOf(st, st.isSymbolicLink ? await this.readlink(name2) : void 0));
6398
+ if (st.isSymbolicLink) break;
6399
+ }
6400
+ return out;
6401
+ }
6104
6402
  async readlink(path) {
6105
6403
  const found = this.at(path);
6106
6404
  if (!found) return this.base.readlink(path);
@@ -6311,9 +6609,11 @@ async function run(entry, path, req) {
6311
6609
  return done;
6312
6610
  case "readlink":
6313
6611
  return json2(await fs.readlink(path));
6314
- case "chmod":
6315
- await fs.chmod(path, req.mode ?? 0);
6612
+ case "chmod": {
6613
+ const g = guardOf(entry);
6614
+ await fs.chmod(path, g ? await chmodMode(g, path, req.mode ?? 0) : req.mode ?? 0);
6316
6615
  return done;
6616
+ }
6317
6617
  case "utimes":
6318
6618
  await fs.utimes(path, new Date(req.atimeMs ?? 0), new Date(req.mtimeMs ?? 0));
6319
6619
  return done;
@@ -6331,6 +6631,53 @@ async function run(entry, path, req) {
6331
6631
  return { ok: false, errno: "EINVAL", message: `sync-fs: unknown op '${req.op}'` };
6332
6632
  }
6333
6633
  }
6634
+ function guardOf(entry) {
6635
+ const cred = entry.dac && entry.cred?.();
6636
+ if (!cred || !entry.dac) return void 0;
6637
+ return { fs: entry.fs, cred, pid: entry.pid ?? 0, dac: entry.dac };
6638
+ }
6639
+ var FOLLOWING = /* @__PURE__ */ new Set(["exists", "stat", "chown", "statfs"]);
6640
+ var NOT_FOLLOWING = /* @__PURE__ */ new Set(["lstat", "readlink", "lchown"]);
6641
+ async function guard(g, path, req, cwd) {
6642
+ const { op } = req;
6643
+ if (FOLLOWING.has(op) || NOT_FOLLOWING.has(op)) return searchable(g, op, path, FOLLOWING.has(op));
6644
+ const other = () => g.fs.resolvePath(cwd, req.arg2 ?? "");
6645
+ switch (op) {
6646
+ case "readdir":
6647
+ case "readdir-stat":
6648
+ return listable(g, op, path);
6649
+ case "mkdir":
6650
+ return makeIn(g, op, path);
6651
+ case "create":
6652
+ case "symlink":
6653
+ return inParent(g, op, path);
6654
+ case "unlink":
6655
+ case "rmdir":
6656
+ return inParent(g, op, path, true);
6657
+ case "rm":
6658
+ return removable(g, op, path);
6659
+ case "rename":
6660
+ return renamable(g, path, other());
6661
+ case "link":
6662
+ return linkable(g, path, other(), req.mode === 1);
6663
+ case "utimes":
6664
+ case "lutimes":
6665
+ return retimable(g, op, path);
6666
+ default:
6667
+ return void 0;
6668
+ }
6669
+ }
6670
+ async function guarded(entry, path, req) {
6671
+ const g = guardOf(entry);
6672
+ if (!g) return void 0;
6673
+ try {
6674
+ await guard(g, path, req, entry.cwd);
6675
+ return void 0;
6676
+ } catch (err) {
6677
+ const refusal = err.code === "EACCES";
6678
+ return req.op === "exists" && refusal ? json2(false) : toErrno(err);
6679
+ }
6680
+ }
6334
6681
  async function createExclusive(fs, path, owner) {
6335
6682
  if (fs.createExclusive) return fs.createExclusive(path, owner);
6336
6683
  if (await fs.lstat(path).then(
@@ -6383,24 +6730,56 @@ async function owned(entry, own, req, path, target) {
6383
6730
  }
6384
6731
  return own(target, () => run(entry, path, req));
6385
6732
  }
6733
+ async function holdOp(entry, path, req) {
6734
+ const mode = req.mode ?? 0;
6735
+ const [on, open] = [(mode & 1) === 1, (mode & 2) === 2];
6736
+ if (on && open) {
6737
+ const real = await entry.fs.realpath?.(path, true).catch(() => path);
6738
+ return json2(entry.hold?.(path, on, open, real) ?? null);
6739
+ }
6740
+ entry.hold?.(path, on, open, void 0, req.handle);
6741
+ return done;
6742
+ }
6743
+ var IDENTITY_OPS = /* @__PURE__ */ new Set([
6744
+ "write",
6745
+ "pwrite",
6746
+ "truncate",
6747
+ "chmod",
6748
+ "utimes",
6749
+ "chown",
6750
+ "lchown"
6751
+ ]);
6386
6752
  async function dispatchSyncFs(req) {
6387
6753
  const entry = resolveSyncFsToken(req.token);
6388
6754
  if (!entry) return { ok: false, errno: "EACCES", message: "sync-fs: unknown or revoked token" };
6755
+ if (!req.identity || !IDENTITY_OPS.has(req.op)) return dispatchOne(entry, req);
6756
+ const { fs, cwd } = entry;
6757
+ const path = fs.resolvePath(cwd, req.path);
6758
+ const identified = async (held) => {
6759
+ const own = held ? { ...entry, fs: { ...fs, writeFile: held.writeFile } } : entry;
6760
+ const result = await dispatchOne(own, req);
6761
+ if (!result.ok) return result;
6762
+ if (held && !held.direct) return json2(null);
6763
+ const look = req.op === "lchown" ? fs.lstat : fs.stat;
6764
+ const st = await look.call(fs, path).catch(() => void 0);
6765
+ return json2(st ? statJson(st) : null);
6766
+ };
6767
+ try {
6768
+ return await (fs.exclusive ? fs.exclusive(path, identified, req.op !== "lchown") : identified());
6769
+ } catch (err) {
6770
+ return toErrno(err);
6771
+ }
6772
+ }
6773
+ async function dispatchOne(entry, req) {
6389
6774
  const { fs, cwd } = entry;
6390
6775
  try {
6391
6776
  const path = fs.resolvePath(cwd, req.path);
6392
6777
  if (req.op === "kept") return json2(entry.keptFor?.(req.handle ?? -1) !== void 0);
6393
6778
  if (req.op === "stat" && req.handle !== void 0) return await keptStat(entry, req);
6779
+ if (req.op === "hold") return holdOp(entry, path, req);
6780
+ const refused2 = await guarded(entry, path, req);
6781
+ if (refused2) return refused2;
6394
6782
  if (req.op === "statfs") return json2(await entry.statfs?.(path) ?? await storageUsage());
6395
- if (req.op === "hold") {
6396
- const [on, open] = [((req.mode ?? 0) & 1) === 1, ((req.mode ?? 0) & 2) === 2];
6397
- if (on && open) {
6398
- const real = await fs.realpath?.(path, true).catch(() => path);
6399
- return json2(entry.hold?.(path, on, open, real) ?? null);
6400
- }
6401
- entry.hold?.(path, on, open, void 0, req.handle);
6402
- return done;
6403
- }
6404
6783
  if (entry.revoked?.(path)) return { ok: false, errno: "EIO", message: `${path} was unmounted` };
6405
6784
  if (req.op === "write" && req.mode === 1 || UNLINKED_OPS.has(req.op)) {
6406
6785
  const serve = async () => run(entry, await keptOr(entry, path, req.handle), req);
@@ -6601,9 +6980,9 @@ function mountTable(mounts = []) {
6601
6980
  for (const m of mounts) {
6602
6981
  const options = Object.entries(m.options).map(([k, v]) => v === "" ? k : `${k}=${v}`);
6603
6982
  if (m.state !== "ok") options.push(m.state);
6604
- const access = options.includes("ro") ? [] : ["rw"];
6983
+ const access2 = options.includes("ro") ? [] : ["rw"];
6605
6984
  lines2.push(
6606
- `${field2(m.source)} ${field2(m.target)} ${m.type} ${[...access, ...options].join(",")} 0 0`
6985
+ `${field2(m.source)} ${field2(m.target)} ${m.type} ${[...access2, ...options].join(",")} 0 0`
6607
6986
  );
6608
6987
  }
6609
6988
  return `${lines2.join("\n")}
@@ -7039,8 +7418,8 @@ var ProcTree = class {
7039
7418
  alive(pid) {
7040
7419
  return this.listing().processes.some((p) => p.pid === pid);
7041
7420
  }
7042
- node(parts) {
7043
- const [top, leaf, ...rest2] = parts;
7421
+ node(parts2) {
7422
+ const [top, leaf, ...rest2] = parts2;
7044
7423
  if (top === void 0) {
7045
7424
  return {
7046
7425
  kind: "dir",
@@ -7078,9 +7457,9 @@ var ProcTree = class {
7078
7457
  };
7079
7458
  }
7080
7459
  parts(path, follow2) {
7081
- const parts = path.split("/").filter((p) => p !== "").slice(1);
7082
- if (parts[0] === "self" && (follow2 || parts.length > 1)) parts[0] = String(this.pid);
7083
- return parts;
7460
+ const parts2 = path.split("/").filter((p) => p !== "").slice(1);
7461
+ if (parts2[0] === "self" && (follow2 || parts2.length > 1)) parts2[0] = String(this.pid);
7462
+ return parts2;
7084
7463
  }
7085
7464
  find(path, follow2) {
7086
7465
  const node = this.node(this.parts(path, follow2));
@@ -7166,8 +7545,8 @@ function refusalOf(found) {
7166
7545
  return found === false ? { refused: "ENOTDIR" } : found;
7167
7546
  }
7168
7547
  function throughLink(target, done4, todo) {
7169
- const parts = target.split("/").filter((part) => part !== "");
7170
- return [target.startsWith("/") ? [] : done4, [...parts, ...todo]];
7548
+ const parts2 = target.split("/").filter((part) => part !== "");
7549
+ return [target.startsWith("/") ? [] : done4, [...parts2, ...todo]];
7171
7550
  }
7172
7551
  async function procAlias(fs, path, follow2) {
7173
7552
  const { readlink, lstat } = fs;
@@ -7177,14 +7556,14 @@ async function procAlias(fs, path, follow2) {
7177
7556
  () => false
7178
7557
  );
7179
7558
  if (!physical) return void 0;
7180
- const walk = procWalk(path, follow2);
7181
- let step = walk.next();
7559
+ const walk2 = procWalk(path, follow2);
7560
+ let step = walk2.next();
7182
7561
  while (!step.done) {
7183
7562
  const { at, check } = step.value;
7184
7563
  const link2 = await readlink.call(fs, at).catch(() => void 0);
7185
7564
  const isDir = (st) => st.isDirectory === true;
7186
7565
  const target = link2 ?? (check ? await lstat.call(fs, at).then(isDir, failed) : true);
7187
- step = walk.next(target);
7566
+ step = walk2.next(target);
7188
7567
  }
7189
7568
  return step.value;
7190
7569
  }
@@ -7778,8 +8157,8 @@ var RealmProxy = class {
7778
8157
  }
7779
8158
  async connect(conn, incoming, req, idle) {
7780
8159
  const target = tunnelTarget(req);
7781
- const refused = target.port === 443 ? await this.tunnelReady(target) : void 0;
7782
- if (refused) throw refused;
8160
+ const refused2 = target.port === 443 ? await this.tunnelReady(target) : void 0;
8161
+ if (refused2) throw refused2;
7783
8162
  await conn.write(latin1Bytes("HTTP/1.1 200 Connection Established\r\n\r\n"), this.stop.signal);
7784
8163
  try {
7785
8164
  const first = await incoming.peek(this.stop.signal);
@@ -7922,10 +8301,10 @@ var RealmProxy = class {
7922
8301
  };
7923
8302
 
7924
8303
  // src/kernel/net/x509.ts
7925
- function concat4(parts) {
7926
- const out = new Uint8Array(parts.reduce((n, p) => n + p.length, 0));
8304
+ function concat4(parts2) {
8305
+ const out = new Uint8Array(parts2.reduce((n, p) => n + p.length, 0));
7927
8306
  let at = 0;
7928
- for (const p of parts) {
8307
+ for (const p of parts2) {
7929
8308
  out.set(p, at);
7930
8309
  at += p.length;
7931
8310
  }
@@ -7941,8 +8320,8 @@ function tlv(tag, ...contents2) {
7941
8320
  const body = concat4(contents2);
7942
8321
  return concat4([Uint8Array.of(tag), length(body.length), body]);
7943
8322
  }
7944
- var seq = (...parts) => tlv(48, ...parts);
7945
- var set = (...parts) => tlv(49, ...parts);
8323
+ var seq = (...parts2) => tlv(48, ...parts2);
8324
+ var set = (...parts2) => tlv(49, ...parts2);
7946
8325
  var octets = (bytes) => tlv(4, bytes);
7947
8326
  var bits = (bytes, unused = 0) => tlv(3, Uint8Array.of(unused), bytes);
7948
8327
  var TRUE = tlv(1, Uint8Array.of(255));
@@ -8030,8 +8409,8 @@ function randomSerial() {
8030
8409
  function ipv4(host) {
8031
8410
  const m = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(host);
8032
8411
  if (!m) return void 0;
8033
- const parts = m.slice(1).map(Number);
8034
- return parts.every((p) => p <= 255) ? Uint8Array.from(parts) : void 0;
8412
+ const parts2 = m.slice(1).map(Number);
8413
+ return parts2.every((p) => p <= 255) ? Uint8Array.from(parts2) : void 0;
8035
8414
  }
8036
8415
  function ipv6(host) {
8037
8416
  if (!host.includes(":") || !/^[0-9A-Fa-f:.]+$/.test(host)) return void 0;
@@ -8860,9 +9239,9 @@ var HttpHandles = class {
8860
9239
  // src/kernel/net/routes.ts
8861
9240
  var HOST_ADDRESS = HOST_LOOPBACK_ADDRESS.join(".");
8862
9241
  function ipv4Bytes(text) {
8863
- const parts = text.split(".");
8864
- if (parts.length !== 4 || !parts.every((p) => /^\d{1,3}$/.test(p))) return void 0;
8865
- const bytes = parts.map(Number);
9242
+ const parts2 = text.split(".");
9243
+ if (parts2.length !== 4 || !parts2.every((p) => /^\d{1,3}$/.test(p))) return void 0;
9244
+ const bytes = parts2.map(Number);
8866
9245
  return bytes.every((b) => b <= 255) ? bytes : void 0;
8867
9246
  }
8868
9247
  function ipv6Groups(text) {
@@ -8889,9 +9268,9 @@ function addressBits(host) {
8889
9268
  return v6 ? { v6: true, bits: toBits(v6, 16) } : void 0;
8890
9269
  }
8891
9270
  function parsePrefix(text) {
8892
- const parts = text.trim().split("/");
8893
- if (parts.length > 2) return void 0;
8894
- const [address = "", size] = parts;
9271
+ const parts2 = text.trim().split("/");
9272
+ if (parts2.length > 2) return void 0;
9273
+ const [address = "", size] = parts2;
8895
9274
  const parsed2 = addressBits(address);
8896
9275
  if (!parsed2) return void 0;
8897
9276
  const max = parsed2.v6 ? 128 : 32;
@@ -9739,7 +10118,9 @@ var VfsNode = class {
9739
10118
  this.ranged = new AsyncRangedFile(io, this.path, this.pin.size, this.pin.version);
9740
10119
  return this.ranged;
9741
10120
  }
9742
- const st = io && !this.orphaned ? await this.fs.stat?.(this.path).catch(() => void 0) : void 0;
10121
+ const at = this.path;
10122
+ const st = io && !this.orphaned ? await this.fs.stat?.(at).catch(() => void 0) : void 0;
10123
+ this.note(at, st);
9743
10124
  if (io && st?.ranged) {
9744
10125
  this.ranged = new AsyncRangedFile(io, this.path, st.size ?? 0, st.version);
9745
10126
  return this.ranged;
@@ -9799,12 +10180,23 @@ var VfsNode = class {
9799
10180
  const stat = this.fs.stat?.bind(this.fs);
9800
10181
  if (!stat || this.orphaned) return Promise.resolve(void 0);
9801
10182
  const parent = this.path.slice(0, this.path.lastIndexOf("/")) || "/";
9802
- this.cap ??= stat(this.path).catch(() => stat(parent)).then(
10183
+ const at = this.path;
10184
+ this.cap ??= stat(at).then((st) => {
10185
+ this.note(at, st);
10186
+ return st;
10187
+ }).catch(() => stat(parent)).then(
9803
10188
  (st) => st.maxFile,
9804
10189
  () => void 0
9805
10190
  );
9806
10191
  return this.cap;
9807
10192
  }
10193
+ seen;
10194
+ note(path, st) {
10195
+ if (st?.ino !== void 0) this.seen = { path, ino: st.ino };
10196
+ }
10197
+ known() {
10198
+ return this.seen?.path === this.path && !this.orphaned ? this.seen.ino : void 0;
10199
+ }
9808
10200
  async fits(end) {
9809
10201
  const max = await this.limit();
9810
10202
  if (max !== void 0 && end > max) throw new KernelError("EFBIG");
@@ -9852,10 +10244,10 @@ var VfsNode = class {
9852
10244
  this.serial(() => this.flush()).catch(() => void 0);
9853
10245
  }, delay);
9854
10246
  }
9855
- ensure(need) {
10247
+ ensure(need2) {
9856
10248
  const cur = this.data ?? new Uint8Array(0);
9857
- if (cur.length >= need) return cur;
9858
- const grown = new Uint8Array(Math.max(need, cur.length * 2, 256));
10249
+ if (cur.length >= need2) return cur;
10250
+ const grown = new Uint8Array(Math.max(need2, cur.length * 2, 256));
9859
10251
  grown.set(cur.subarray(0, this.length));
9860
10252
  this.data = grown;
9861
10253
  return grown;
@@ -9898,14 +10290,14 @@ var VfsNodes = class {
9898
10290
  return run2;
9899
10291
  }
9900
10292
  async entryKey(path) {
9901
- const parent = parentOf5(path);
10293
+ const parent = parentOf(path);
9902
10294
  const dir = await this.real(parent, true, () => realDir(this.fs, parent));
9903
10295
  return joinName(dir, path.slice(path.lastIndexOf("/") + 1));
9904
10296
  }
9905
10297
  targetKey(path) {
9906
10298
  return this.real(path, true, () => realTarget(this.fs, path));
9907
10299
  }
9908
- async real(path, follow2, walk) {
10300
+ async real(path, follow2, walk2) {
9909
10301
  if (this.fs.realpath) {
9910
10302
  try {
9911
10303
  return await this.fs.realpath(path, follow2);
@@ -9913,13 +10305,13 @@ var VfsNodes = class {
9913
10305
  if (errCode(err) === "ELOOP") throw err;
9914
10306
  }
9915
10307
  }
9916
- return walk();
10308
+ return walk2();
9917
10309
  }
9918
10310
  createExclusive(path, owner) {
9919
10311
  return this.onEntry(path, async () => {
9920
10312
  const node = this.byPath.get(path);
9921
10313
  const held = node ? await node.serial(async () => node.holds()) : false;
9922
- if (held || await present(this.fs, path)) throw fsError("EEXIST", path);
10314
+ if (held || await present2(this.fs, path)) throw fsError("EEXIST", path);
9923
10315
  await this.fs.writeFile(path, new Uint8Array(0), owner);
9924
10316
  });
9925
10317
  }
@@ -9979,13 +10371,13 @@ var VfsNodes = class {
9979
10371
  if (id2 !== void 0) this.know(node, id2);
9980
10372
  return node;
9981
10373
  }
9982
- open(path, pin, writable = false, id2) {
10374
+ open(path, pin, writable2 = false, id2) {
9983
10375
  const node = this.named(path, id2) ?? this.create(path, id2, new VfsNode(this.fs, path, void 0, false, false, pin));
9984
10376
  node.opens++;
9985
- if (writable) node.writers++;
10377
+ if (writable2) node.writers++;
9986
10378
  return node;
9987
10379
  }
9988
- adopt(path, contents2, dirty, writable = false, id2) {
10380
+ adopt(path, contents2, dirty, writable2 = false, id2) {
9989
10381
  let node = this.named(path, id2);
9990
10382
  if (!node) {
9991
10383
  node = this.create(path, id2, new VfsNode(this.fs, path, contents2, false, dirty));
@@ -9993,7 +10385,7 @@ var VfsNodes = class {
9993
10385
  node.replace(contents2);
9994
10386
  }
9995
10387
  node.opens++;
9996
- if (writable) node.writers++;
10388
+ if (writable2) node.writers++;
9997
10389
  return node;
9998
10390
  }
9999
10391
  holds(prefix) {
@@ -10005,11 +10397,11 @@ var VfsNodes = class {
10005
10397
  if (node.writers > 0 && within5(path, prefix)) return true;
10006
10398
  return false;
10007
10399
  }
10008
- closed(node, writable = false) {
10400
+ closed(node, writable2 = false) {
10009
10401
  node.opens--;
10010
- if (writable) node.writers--;
10402
+ if (writable2) node.writers--;
10011
10403
  if (node.opens > 0) {
10012
- if (writable && node.writers === 0) this.onClosed?.(node.path);
10404
+ if (writable2 && node.writers === 0) this.onClosed?.(node.path);
10013
10405
  return;
10014
10406
  }
10015
10407
  this.forget(node);
@@ -10086,7 +10478,7 @@ var VfsNodes = class {
10086
10478
  }
10087
10479
  }
10088
10480
  };
10089
- async function present(fs, path, follow2 = false) {
10481
+ async function present2(fs, path, follow2 = false) {
10090
10482
  const look = (follow2 ? void 0 : fs.lstat) ?? fs.stat;
10091
10483
  try {
10092
10484
  await (look ? look.call(fs, path) : fs.readFileBuffer(path));
@@ -10100,7 +10492,7 @@ function errCode(err) {
10100
10492
  return err?.code;
10101
10493
  }
10102
10494
  async function targetDir(fs, path) {
10103
- return parentOf5(await realTarget(fs, path).catch(() => path));
10495
+ return parentOf(await realTarget(fs, path).catch(() => path));
10104
10496
  }
10105
10497
  async function refuseReadonly(fs, path, flags, opts = {}) {
10106
10498
  const writes = (flags & O_ACCMODE) !== 0 || opts.truncate === true;
@@ -10118,6 +10510,18 @@ async function refuseReadonly(fs, path, flags, opts = {}) {
10118
10510
  function keepingOpen(fs, nodes) {
10119
10511
  return {
10120
10512
  ...fs,
10513
+ async exclusive(path, run2, follow2 = true) {
10514
+ const at = fs.resolvePath("/", path);
10515
+ const key = follow2 ? nodes.targetKey(at) : nodes.entryKey(at);
10516
+ const locked = await key.catch(() => at);
10517
+ return nodes.onEntry(
10518
+ locked,
10519
+ () => run2({
10520
+ writeFile: (p, content, owner) => fs.writeFile(p, content, owner),
10521
+ direct: locked === at
10522
+ })
10523
+ );
10524
+ },
10121
10525
  async rm(path, options) {
10122
10526
  const at = await nodes.entryKey(fs.resolvePath("/", path));
10123
10527
  await nodes.unlinking(at);
@@ -10150,10 +10554,10 @@ function keepingOpen(fs, nodes) {
10150
10554
  };
10151
10555
  }
10152
10556
  function vfsFile(fs, opts, nodes) {
10153
- const access = opts.flags & O_ACCMODE;
10154
- const readable = access !== O_WRONLY;
10155
- const writable = access === O_WRONLY || access === O_RDWR;
10156
- const node = !nodes || opts.orphan ? new VfsNode(fs, opts.path, opts.contents, opts.orphan === true, opts.dirty === true) : opts.contents !== void 0 ? nodes.adopt(opts.path, opts.contents, opts.dirty === true, writable, opts.id) : nodes.open(opts.path, opts.pin, writable, opts.id);
10557
+ const access2 = opts.flags & O_ACCMODE;
10558
+ const readable2 = access2 !== O_WRONLY;
10559
+ const writable2 = access2 === O_WRONLY || access2 === O_RDWR;
10560
+ const node = !nodes || opts.orphan ? new VfsNode(fs, opts.path, opts.contents, opts.orphan === true, opts.dirty === true) : opts.contents !== void 0 ? nodes.adopt(opts.path, opts.contents, opts.dirty === true, writable2, opts.id) : nodes.open(opts.path, opts.pin, writable2, opts.id);
10157
10561
  let openError;
10158
10562
  const atOpen = (op) => {
10159
10563
  void node.serial(op).catch((err) => {
@@ -10174,12 +10578,12 @@ function vfsFile(fs, opts, nodes) {
10174
10578
  return op();
10175
10579
  });
10176
10580
  return new OpenFile({
10177
- read: readable ? (max) => serial(async () => {
10581
+ read: readable2 ? (max) => serial(async () => {
10178
10582
  const out = await node.pread(max, offset);
10179
10583
  offset += out.length;
10180
10584
  return out;
10181
10585
  }) : void 0,
10182
- write: writable ? (bytes) => serial(async () => {
10586
+ write: writable2 ? (bytes) => serial(async () => {
10183
10587
  if (opts.flags & O_APPEND) offset = await node.size();
10184
10588
  offset += await node.pwrite(bytes, offset);
10185
10589
  return bytes.length;
@@ -10194,15 +10598,15 @@ function vfsFile(fs, opts, nodes) {
10194
10598
  return offset;
10195
10599
  }),
10196
10600
  pread: (max, at) => serial(() => {
10197
- if (!readable) throw new KernelError("EBADF");
10601
+ if (!readable2) throw new KernelError("EBADF");
10198
10602
  return node.pread(max, at);
10199
10603
  }),
10200
10604
  pwrite: (bytes, at) => serial(() => {
10201
- if (!writable) throw new KernelError("EBADF");
10605
+ if (!writable2) throw new KernelError("EBADF");
10202
10606
  return node.pwrite(bytes, at);
10203
10607
  }),
10204
10608
  resize: (size) => serial(() => {
10205
- if (!writable) throw new KernelError("EBADF");
10609
+ if (!writable2) throw new KernelError("EBADF");
10206
10610
  return node.truncate(size);
10207
10611
  }),
10208
10612
  stat: () => serial(async () => ({
@@ -10210,13 +10614,14 @@ function vfsFile(fs, opts, nodes) {
10210
10614
  size: await node.size(),
10211
10615
  ...node.orphaned ? { orphan: true } : {}
10212
10616
  })),
10617
+ known: () => node.known(),
10213
10618
  flush: () => serial(() => node.flush()),
10214
10619
  close: () => node.serial(async () => {
10215
10620
  try {
10216
10621
  takeOpenError();
10217
10622
  await node.flush();
10218
10623
  } finally {
10219
- nodes?.closed(node, writable);
10624
+ nodes?.closed(node, writable2);
10220
10625
  }
10221
10626
  })
10222
10627
  });
@@ -10587,7 +10992,7 @@ var WasmProcess = class {
10587
10992
  this.fds.installAt(fd, this.openVfsFile(open));
10588
10993
  } else if (req.create) {
10589
10994
  await nodes.onEntry(path, async () => {
10590
- const fresh = this.options.owners && !await present(this.options.fs, path, true);
10995
+ const fresh = this.options.owners && !await present2(this.options.fs, path, true);
10591
10996
  this.fds.installAt(fd, this.openVfsFile(open, await nodes.identify(path)));
10592
10997
  await this.fds.get(fd).file.stat?.();
10593
10998
  if (fresh) await this.options.fs.chown?.(path, { ...this.newOwner(), kind: "new" });
@@ -10595,7 +11000,7 @@ var WasmProcess = class {
10595
11000
  } else {
10596
11001
  this.fds.installAt(fd, this.openVfsFile(open, await nodes.identify(path)));
10597
11002
  }
10598
- if (req.existing && !await present(this.options.fs, path, true)) {
11003
+ if (req.existing && !await present2(this.options.fs, path, true)) {
10599
11004
  throw new KernelError("ENOENT");
10600
11005
  }
10601
11006
  } catch (err) {
@@ -10604,6 +11009,20 @@ var WasmProcess = class {
10604
11009
  }
10605
11010
  return fd;
10606
11011
  }
11012
+ async identified(stat, known) {
11013
+ const first = await stat();
11014
+ if (first.orphan) return first;
11015
+ const ino = known?.();
11016
+ if (ino !== void 0) return { ...first, ino };
11017
+ const { fs } = this.options;
11018
+ const look = async () => {
11019
+ const now = await stat();
11020
+ if (now.path !== first.path || now.orphan) return now;
11021
+ const st = await fs.stat?.(now.path).catch(() => void 0);
11022
+ return st?.ino === void 0 ? now : { ...now, ino: st.ino };
11023
+ };
11024
+ return fs.exclusive ? fs.exclusive(first.path, look) : look();
11025
+ }
10607
11026
  newOwner() {
10608
11027
  if (!this.options.owners) return void 0;
10609
11028
  return { uid: this.cred.euid, gid: this.cred.egid, mode: 438 & ~this.umask };
@@ -10615,14 +11034,14 @@ var WasmProcess = class {
10615
11034
  if (req.flags & WRITE_ACCESS || req.create || req.truncate || req.exclusive) {
10616
11035
  throw new KernelError("EROFS");
10617
11036
  }
10618
- const tree = new ProcTree(
11037
+ const tree2 = new ProcTree(
10619
11038
  {
10620
11039
  procList: () => this.options.processes?.() ?? { boot: 0, processes: [] },
10621
11040
  mountList: () => this.options.mounts?.() ?? []
10622
11041
  },
10623
11042
  (await this.options.identity?.())?.pid ?? this.pid
10624
11043
  );
10625
- return memoryFile(tree.read(path), path, req.position);
11044
+ return memoryFile(tree2.read(path), path, req.position);
10626
11045
  }
10627
11046
  openVfsFile(req, id2) {
10628
11047
  return vfsFile(
@@ -10765,7 +11184,11 @@ var WasmProcess = class {
10765
11184
  await file.resize(req.size);
10766
11185
  return { ok: true, kind: "void" };
10767
11186
  case "fd-vfs-stat":
10768
- return { ok: true, kind: "json", json: await file.stat() };
11187
+ return {
11188
+ ok: true,
11189
+ kind: "json",
11190
+ json: req.identity ? await this.identified(file.stat.bind(file), file.known?.bind(file)) : await file.stat()
11191
+ };
10769
11192
  }
10770
11193
  }
10771
11194
  keptPath;
@@ -10774,7 +11197,7 @@ var WasmProcess = class {
10774
11197
  const held = handle === void 0 ? void 0 : this.keptHandle?.(handle);
10775
11198
  if (held !== void 0) return held;
10776
11199
  const kept = this.keptPath?.(path);
10777
- return kept !== void 0 && !await present(this.options.fs, path, true) ? kept : path;
11200
+ return kept !== void 0 && !await present2(this.options.fs, path, true) ? kept : path;
10778
11201
  }
10779
11202
  async promote(req) {
10780
11203
  if (!this.fds.get(req.fd).file.held) throw new KernelError("EBADF");
@@ -11284,6 +11707,7 @@ function inherited(opts) {
11284
11707
  function holdingToken(opts, holds, caller) {
11285
11708
  return mintSyncFsToken({
11286
11709
  ...opts.owners ? caller : {},
11710
+ ...opts.dac ? { dac: opts.dac, pid: opts.pid } : {},
11287
11711
  fs: opts.fs,
11288
11712
  cwd: opts.cwd,
11289
11713
  ...opts.statfs ? { statfs: opts.statfs } : {},
@@ -11913,9 +12337,9 @@ function serveFilesystem(port, handlers, capabilities = {}, options = {}) {
11913
12337
  let open = false;
11914
12338
  const reply = (message3, transfer = []) => port.postMessage(message3, transfer);
11915
12339
  async function hello(message3) {
11916
- const refused = driverVersionError(message3.protocol);
11917
- if (refused) {
11918
- reply({ hello: { protocol: DRIVER_PROTOCOL, error: refused, errno: "EPROTO" } });
12340
+ const refused2 = driverVersionError(message3.protocol);
12341
+ if (refused2) {
12342
+ reply({ hello: { protocol: DRIVER_PROTOCOL, error: refused2, errno: "EPROTO" } });
11919
12343
  return;
11920
12344
  }
11921
12345
  try {
@@ -12457,7 +12881,7 @@ function hostfsHandlers(current, againMs = RESTORE_AGAIN_MS) {
12457
12881
  }, againMs);
12458
12882
  timer.unref?.();
12459
12883
  });
12460
- async function writable(client, path, flags) {
12884
+ async function writable2(client, path, flags) {
12461
12885
  const { ifMatch: _, ...asked } = flags;
12462
12886
  const opening = () => client.call("open", { path: rel(path), ...asked });
12463
12887
  try {
@@ -12488,7 +12912,7 @@ function hostfsHandlers(current, againMs = RESTORE_AGAIN_MS) {
12488
12912
  async open(path, flags) {
12489
12913
  const client = current();
12490
12914
  const token = client.token;
12491
- const { opened: opened2, restore } = await writable(client, path, flags);
12915
+ const { opened: opened2, restore } = await writable2(client, path, flags);
12492
12916
  if (flags.ifMatch !== void 0 && opened2.attr?.etag !== flags.ifMatch) {
12493
12917
  await client.call("release", { fh: opened2.fh }, true);
12494
12918
  throw fsError2("ESTALE", `${path} changed on the host while it was being read`);
@@ -12661,7 +13085,7 @@ var PORT_DEAD_MS = 1e4;
12661
13085
  var PORT_LINGER_MS = 1e3;
12662
13086
  function timed(transport, ms) {
12663
13087
  const inflight = /* @__PURE__ */ new Set();
12664
- const guard = (name2, run2, late, expire) => new Promise((resolve, reject) => {
13088
+ const guard2 = (name2, run2, late, expire) => new Promise((resolve, reject) => {
12665
13089
  let expired = false;
12666
13090
  const running = run2();
12667
13091
  inflight.add(running);
@@ -12710,15 +13134,15 @@ function timed(transport, ms) {
12710
13134
  },
12711
13135
  retire: (why) => transport.retire?.(why),
12712
13136
  ...transport.cleanup ? { cleanup } : {},
12713
- call: (op, body, handle) => guard(
13137
+ call: (op, body, handle) => guard2(
12714
13138
  op,
12715
13139
  () => transport.call(op, body, handle),
12716
13140
  op === "open" ? release : void 0,
12717
13141
  op === "release" ? (running) => void cleanup("release", body ?? {}, running).catch(() => {
12718
13142
  }) : void 0
12719
13143
  ),
12720
- read: (fh, offset, size, ifMatch) => guard("read", () => transport.read(fh, offset, size, ifMatch)),
12721
- write: (fh, offset, bytes) => guard("write", () => transport.write(fh, offset, bytes)),
13144
+ read: (fh, offset, size, ifMatch) => guard2("read", () => transport.read(fh, offset, size, ifMatch)),
13145
+ write: (fh, offset, bytes) => guard2("write", () => transport.write(fh, offset, bytes)),
12722
13146
  watch: (signal, onEvent, onOpen) => transport.watch(signal, onEvent, onOpen)
12723
13147
  };
12724
13148
  }
@@ -13099,10 +13523,10 @@ var ChunkedBytes = class {
13099
13523
  }
13100
13524
  this.size = size;
13101
13525
  }
13102
- room(index, need) {
13526
+ room(index, need2) {
13103
13527
  const chunk2 = this.chunks[index];
13104
- if (chunk2 && chunk2.length >= need) return chunk2;
13105
- const grown = new Uint8Array(Math.min(CHUNK3, Math.max(need, (chunk2?.length ?? 0) * 2)));
13528
+ if (chunk2 && chunk2.length >= need2) return chunk2;
13529
+ const grown = new Uint8Array(Math.min(CHUNK3, Math.max(need2, (chunk2?.length ?? 0) * 2)));
13106
13530
  if (chunk2) grown.set(chunk2);
13107
13531
  this.chunks[index] = grown;
13108
13532
  return grown;
@@ -13149,14 +13573,14 @@ function tmpfs() {
13149
13573
  const root = make("directory", 16877);
13150
13574
  const handles = /* @__PURE__ */ new Map();
13151
13575
  let nextFh = 0;
13152
- const parts = (path) => path.split("/").filter(Boolean);
13576
+ const parts2 = (path) => path.split("/").filter(Boolean);
13153
13577
  const place = (dir, name2, node) => {
13154
13578
  dir.children.set(name2, node);
13155
13579
  dir.mtime = Date.now();
13156
13580
  };
13157
13581
  function find(path) {
13158
13582
  let node = root;
13159
- for (const name2 of parts(path)) {
13583
+ for (const name2 of parts2(path)) {
13160
13584
  if (node.kind !== "directory") throw fsError2("ENOTDIR", path);
13161
13585
  const next2 = node.children.get(name2);
13162
13586
  if (!next2) throw fsError2("ENOENT", path);
@@ -13165,7 +13589,7 @@ function tmpfs() {
13165
13589
  return node;
13166
13590
  }
13167
13591
  function parent(path) {
13168
- const names = parts(path);
13592
+ const names = parts2(path);
13169
13593
  const name2 = names.pop();
13170
13594
  if (name2 === void 0) throw fsError2("EBUSY", "the root of a mount");
13171
13595
  const dir = find(`/${names.join("/")}`);
@@ -13250,8 +13674,8 @@ function tmpfs() {
13250
13674
  dir.mtime = Date.now();
13251
13675
  },
13252
13676
  async rename(from, to) {
13253
- const a = `/${parts(from).join("/")}`;
13254
- const b = `/${parts(to).join("/")}`;
13677
+ const a = `/${parts2(from).join("/")}`;
13678
+ const b = `/${parts2(to).join("/")}`;
13255
13679
  if (b !== a && b.startsWith(`${a}/`)) throw fsError2("EINVAL", `${to} is inside ${from}`);
13256
13680
  const source = parent(from);
13257
13681
  const node = source.dir.children.get(source.name);
@@ -13610,11 +14034,13 @@ var Launcher = class {
13610
14034
  booting = new AbortController();
13611
14035
  fstab = Promise.resolve([]);
13612
14036
  usersMode;
14037
+ dac;
13613
14038
  nextPid = 1e3;
13614
14039
  boot = Date.now();
13615
14040
  terminals = 0;
13616
14041
  constructor(options) {
13617
14042
  this.usersMode = usersMode(options.users);
14043
+ this.dac = this.usersMode === "off" ? void 0 : new Dac(this.usersMode, options.onDenial);
13618
14044
  if (this.owners) options.fs.trackOwners?.();
13619
14045
  this.mounts = new MountTable({
13620
14046
  open: (type, spec) => this.driver(type, spec),
@@ -13945,6 +14371,7 @@ var Launcher = class {
13945
14371
  ...req.umask !== void 0 ? { umask: req.umask } : {},
13946
14372
  cred: req.cred,
13947
14373
  ...this.owners ? { owners: true } : {},
14374
+ ...this.dac ? { dac: this.dac } : {},
13948
14375
  program: req.program,
13949
14376
  argv0: req.argv0,
13950
14377
  args: req.args,
@@ -14908,7 +15335,7 @@ function dialStream(port, onClose) {
14908
15335
  let closed = false;
14909
15336
  let reader;
14910
15337
  const acks = [];
14911
- const readable = new ReadableStream({
15338
+ const readable2 = new ReadableStream({
14912
15339
  start(controller) {
14913
15340
  reader = controller;
14914
15341
  },
@@ -14949,7 +15376,7 @@ function dialStream(port, onClose) {
14949
15376
  else finish(errorWithCode(data.error));
14950
15377
  });
14951
15378
  port.start?.();
14952
- const writable = new WritableStream({
15379
+ const writable2 = new WritableStream({
14953
15380
  write(chunk2) {
14954
15381
  if (closed) throw errorWithCode("EPIPE: the connection is closed");
14955
15382
  const copy = chunk2.slice();
@@ -14968,7 +15395,7 @@ function dialStream(port, onClose) {
14968
15395
  shut();
14969
15396
  finish(error);
14970
15397
  };
14971
- return { readable, writable, close, fail: fail3 };
15398
+ return { readable: readable2, writable: writable2, close, fail: fail3 };
14972
15399
  }
14973
15400
 
14974
15401
  // src/client/loopback-fetch.ts
@@ -15452,9 +15879,9 @@ async function greet(channel, lock, timeoutMs) {
15452
15879
  } finally {
15453
15880
  clearTimeout(timer);
15454
15881
  }
15455
- const refused = hello.error ?? versionError(hello.protocol);
15456
- if (refused) {
15457
- const error = new Error(refused);
15882
+ const refused2 = hello.error ?? versionError(hello.protocol);
15883
+ if (refused2) {
15884
+ const error = new Error(refused2);
15458
15885
  channel.end(error);
15459
15886
  throw error;
15460
15887
  }
@@ -15604,10 +16031,11 @@ async function createNodeKernel(options = {}) {
15604
16031
  ...hostfsFetchOf(options),
15605
16032
  ...options.cdp ? { cdp: options.cdp } : {},
15606
16033
  ...options.hostname !== void 0 ? { hostname: options.hostname } : {},
15607
- ...options.users !== void 0 ? { users: options.users } : {}
16034
+ ...options.users !== void 0 ? { users: options.users } : {},
16035
+ ...options.onDenial ? { onDenial: options.onDenial } : {}
15608
16036
  });
15609
16037
  await launcher.prepare();
15610
- const guard = (work) => new Promise((resolve, reject) => {
16038
+ const guard2 = (work) => new Promise((resolve, reject) => {
15611
16039
  if (terminated) return reject(new Error(TERMINATED));
15612
16040
  pending.add(reject);
15613
16041
  work().then(resolve, reject).finally(() => pending.delete(reject));
@@ -15619,7 +16047,7 @@ async function createNodeKernel(options = {}) {
15619
16047
  const { stdin, onStdout, onStderr, ...rest2 } = opts;
15620
16048
  const onOut = textOf(onStdout);
15621
16049
  const onErr = textOf(onStderr);
15622
- const result = await guard(
16050
+ const result = await guard2(
15623
16051
  () => launcher.run(argv, {
15624
16052
  ...rest2,
15625
16053
  ...stdin !== void 0 ? { stdin: typeof stdin === "string" ? new TextEncoder().encode(stdin) : stdin } : {},
@@ -15637,7 +16065,7 @@ async function createNodeKernel(options = {}) {
15637
16065
  const { onData, ...rest2 } = opts;
15638
16066
  let listener = onData ?? null;
15639
16067
  const backlog = [];
15640
- const session = await guard(
16068
+ const session = await guard2(
15641
16069
  () => launcher.openTerminal(argv, {
15642
16070
  ...rest2,
15643
16071
  onData: (bytes) => listener ? listener(bytes) : backlog.push(bytes)
@@ -15645,7 +16073,7 @@ async function createNodeKernel(options = {}) {
15645
16073
  );
15646
16074
  return {
15647
16075
  pid: session.pid,
15648
- exited: guard(() => session.exited),
16076
+ exited: guard2(() => session.exited),
15649
16077
  get onData() {
15650
16078
  return listener;
15651
16079
  },
@@ -15665,20 +16093,20 @@ async function createNodeKernel(options = {}) {
15665
16093
  await launcher.fs.writeFile(path, data);
15666
16094
  },
15667
16095
  readFile: async (path) => launcher.fs.readFileBuffer(path),
15668
- mount: (spec) => guard(() => launcher.mount(spec)),
15669
- umount: (target) => guard(async () => launcher.umount(target)),
16096
+ mount: (spec) => guard2(() => launcher.mount(spec)),
16097
+ umount: (target) => guard2(async () => launcher.umount(target)),
15670
16098
  mounts: async () => launcher.mounts.list(),
15671
- setRoutes: (routes) => guard(async () => launcher.setRoutes(routes)),
16099
+ setRoutes: (routes) => guard2(async () => launcher.setRoutes(routes)),
15672
16100
  users: {
15673
16101
  mode: launcher.usersMode,
15674
- add: (user) => guard(() => launcher.users.add(user)),
15675
- remove: (name2, options2) => guard(() => launcher.users.remove(name2, options2)),
15676
- list: () => guard(() => launcher.users.list())
16102
+ add: (user) => guard2(() => launcher.users.add(user)),
16103
+ remove: (name2, options2) => guard2(() => launcher.users.remove(name2, options2)),
16104
+ list: () => guard2(() => launcher.users.list())
15677
16105
  },
15678
16106
  groups: {
15679
- list: () => guard(() => launcher.users.groups()),
15680
- addMember: (group, user) => guard(() => launcher.users.addMember(group, user)),
15681
- removeMember: (group, user) => guard(() => launcher.users.removeMember(group, user))
16107
+ list: () => guard2(() => launcher.users.groups()),
16108
+ addMember: (group, user) => guard2(() => launcher.users.addMember(group, user)),
16109
+ removeMember: (group, user) => guard2(() => launcher.users.removeMember(group, user))
15682
16110
  },
15683
16111
  async connect({ user } = {}) {
15684
16112
  if (terminated) throw new Error(TERMINATED);