@ai-ecoverse/slicc-kernel 1.92.0 → 1.93.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
@@ -451,6 +451,9 @@ async function present(fs, path, directory) {
451
451
  }
452
452
  async function finalOf(fs, walked, want, options) {
453
453
  const perm = walked.perm;
454
+ if (options.follow === false && perm.link !== void 0) {
455
+ return [{ ...perm, mode: 511 }, false];
456
+ }
454
457
  if ((want & X_OK) === 0 || options.directory !== void 0) {
455
458
  return [perm, options.directory !== false];
456
459
  }
@@ -622,6 +625,18 @@ async function chmodMode(g, path, mode) {
622
625
  if (!g.dac.enforcing || !perm || g.cred.euid === 0 || inGroup(g.cred, perm.gid)) return mode;
623
626
  return mode & ~S_ISGID2;
624
627
  }
628
+ function heldChmodMode(g, path, st, mode) {
629
+ const perm = permOf(st);
630
+ if (g.cred.euid !== 0 && perm.uid !== g.cred.euid) deny(g, "fchmod", path, "EPERM");
631
+ if (!g.dac.enforcing || g.cred.euid === 0 || inGroup(g.cred, perm.gid)) return mode;
632
+ return mode & ~S_ISGID2;
633
+ }
634
+ function heldRetimable(g, path, st, now) {
635
+ const perm = permOf(st);
636
+ if (g.cred.euid === 0 || perm.uid === g.cred.euid) return;
637
+ if (!now) deny(g, "futimens", path, "EPERM");
638
+ else if (!permits(g.cred, perm, W_OK, false)) deny(g, "futimens", path);
639
+ }
625
640
  async function retimable(g, op, path, now = false) {
626
641
  const follow2 = op !== "lutimes";
627
642
  if (!now) return void await ownerOnly(g, op, path, follow2);
@@ -1073,6 +1088,17 @@ var FdTable = class _FdTable {
1073
1088
  this.status.delete(fd);
1074
1089
  void Promise.resolve(previous?.release()).catch(() => void 0);
1075
1090
  }
1091
+ async replaceAt(fd, file) {
1092
+ if (fd < 0 || fd >= _FdTable.MAX_FDS) {
1093
+ void Promise.resolve(file.release()).catch(() => void 0);
1094
+ throw new KernelError("EBADF");
1095
+ }
1096
+ const previous = this.fds.get(fd);
1097
+ this.fds.set(fd, file);
1098
+ this.cloexec.delete(fd);
1099
+ this.status.delete(fd);
1100
+ await Promise.resolve(previous?.release()).catch(() => void 0);
1101
+ }
1076
1102
  dup(fd, min = 0) {
1077
1103
  return this.install(this.get(fd).retain(), min);
1078
1104
  }
@@ -1081,6 +1107,11 @@ var FdTable = class _FdTable {
1081
1107
  if (oldFd !== newFd) this.installAt(newFd, file.retain());
1082
1108
  return newFd;
1083
1109
  }
1110
+ async dup2Clipboard(oldFd, newFd) {
1111
+ const file = this.get(oldFd);
1112
+ if (oldFd !== newFd) await this.replaceAt(newFd, file.retain());
1113
+ return newFd;
1114
+ }
1084
1115
  close(fd) {
1085
1116
  const file = this.get(fd);
1086
1117
  this.fds.delete(fd);
@@ -2020,17 +2051,21 @@ function openWebSocket(Socket, request, withHeaders) {
2020
2051
  } catch {
2021
2052
  }
2022
2053
  });
2023
- socket.addEventListener("close", (event) => {
2024
- queue.end({ code: event.code, reason: event.reason });
2025
- if (open) return;
2026
- fail3(unopened(url, event.code));
2027
- });
2054
+ socket.addEventListener(
2055
+ "close",
2056
+ (event) => {
2057
+ queue.end({ code: event.code, reason: event.reason, wasClean: event.wasClean === true });
2058
+ if (open) return;
2059
+ fail3(unopened(url, event.code));
2060
+ }
2061
+ );
2028
2062
  });
2029
2063
  }
2030
2064
 
2031
2065
  // src/transport.ts
2032
2066
  var SEND_BUFFER = 1024 * 1024;
2033
2067
  var CLOSE_WAIT_MS = 5e3;
2068
+ var DNS_CODES = /* @__PURE__ */ new Set(["ENOTFOUND", "EAI_AGAIN", "EAI_NONAME", "EAI_FAIL", "ENODATA"]);
2034
2069
  var NO_WEBSOCKET = "WebSocket not supported by this transport (fetchTransport needs { webSocket: true })";
2035
2070
  var MAX_REQUEST_BODY = 64 * 1024 * 1024;
2036
2071
  function joinHeaders(headers) {
@@ -2082,11 +2117,14 @@ function fetchTransport(options = {}) {
2082
2117
  });
2083
2118
  } catch (e) {
2084
2119
  if (request.signal.aborted) throw e;
2120
+ const causeCode = e?.cause?.code;
2121
+ const dns = typeof causeCode === "string" && DNS_CODES.has(causeCode);
2085
2122
  throw Object.assign(
2086
2123
  new Error(
2087
2124
  `fetch ${request.url} failed (unreachable, or not allowed by CORS)${options.hint ? `: ${options.hint}` : ""}`
2088
2125
  ),
2089
- { status: 502 }
2126
+ { status: 502 },
2127
+ dns ? { code: causeCode } : {}
2090
2128
  );
2091
2129
  }
2092
2130
  const headers2 = [];
@@ -2329,7 +2367,7 @@ function homeOf(home) {
2329
2367
  if (typeof home !== "string" || !home.startsWith("/") || /[:\r\n]/.test(home)) {
2330
2368
  throw fsError("EINVAL", `not a home directory: ${String(home)}`);
2331
2369
  }
2332
- return home;
2370
+ return normalizePath(home);
2333
2371
  }
2334
2372
  function freeId(users, groups) {
2335
2373
  const taken = /* @__PURE__ */ new Set([...users.map((r) => r.uid), ...groups.map((r) => r.gid)]);
@@ -3220,12 +3258,36 @@ var MemoryMeta = class {
3220
3258
  };
3221
3259
 
3222
3260
  // src/fs/integrity.ts
3261
+ var SOURCE_SUFFIXES = [
3262
+ ".js",
3263
+ ".mjs",
3264
+ ".cjs",
3265
+ ".json",
3266
+ ".py",
3267
+ ".pyc",
3268
+ ".pth",
3269
+ ".zip",
3270
+ ".so",
3271
+ ".wasm",
3272
+ ".sh"
3273
+ ];
3274
+ var EXEC_OPS = { failed: "exec-integrity", unknown: "exec-unverified" };
3275
+ var READ_OPS = { failed: "read-integrity", unknown: "read-unverified" };
3223
3276
  var KERNEL_CALLER = { pid: 0, uid: 0 };
3224
3277
  function integrityOptions(options) {
3225
3278
  if (options && typeof options.lookup !== "function") {
3226
3279
  throw new TypeError("protect.integrity.lookup must be a function");
3227
3280
  }
3228
3281
  integrityUnknown(options);
3282
+ integritySources(options);
3283
+ }
3284
+ function integritySources(options) {
3285
+ const sources = options?.sources ?? SOURCE_SUFFIXES;
3286
+ const suffix = (s) => typeof s === "string" && /^\.[^/]+$/.test(s);
3287
+ if (!Array.isArray(sources) || !sources.every(suffix)) {
3288
+ throw new TypeError("protect.integrity.sources must be a list of suffixes such as .js");
3289
+ }
3290
+ return [...sources];
3229
3291
  }
3230
3292
  function integrityUnknown(options) {
3231
3293
  const unknown = options?.unknown ?? "allow";
@@ -3247,16 +3309,21 @@ var Integrity = class {
3247
3309
  unknown;
3248
3310
  report;
3249
3311
  unverified = /* @__PURE__ */ new Set();
3312
+ unread = /* @__PURE__ */ new Set();
3313
+ sources;
3314
+ passed = /* @__PURE__ */ new Set();
3315
+ generation = 0;
3250
3316
  constructor(prefixes2, root, options, report) {
3251
3317
  this.prefixes = prefixes2;
3252
3318
  this.root = root;
3253
3319
  this.lookup = options.lookup;
3254
3320
  this.unknown = integrityUnknown(options);
3321
+ this.sources = integritySources(options);
3255
3322
  this.report = report;
3256
3323
  }
3257
- async real(path, asked) {
3324
+ async real(path, asked, known) {
3258
3325
  const at = normalizePath(path);
3259
- const real = await this.root.realpath?.(at, true).catch(() => void 0) ?? at;
3326
+ const real = known ?? await this.root.realpath?.(at, true).catch(() => void 0) ?? at;
3260
3327
  if (isProtected(this.prefixes, real)) return { real, name: real };
3261
3328
  const names = asked === void 0 ? [] : await namesOf(this.root, asked);
3262
3329
  if (real !== at) names.push(...await namesOf(this.root, at));
@@ -3281,6 +3348,51 @@ var Integrity = class {
3281
3348
  const read = name2 !== real ? { bytes: await plain(), version: void 0 } : this.root.readVersioned ? await this.root.readVersioned(real) : { bytes: await this.root.readFileBuffer(real), version: void 0 };
3282
3349
  const { bytes } = read;
3283
3350
  const keyed = read.version === void 0 ? { bytes } : { bytes, key: `${real}\0${read.version}` };
3351
+ await this.verify(name2, bytes, caller, EXEC_OPS);
3352
+ return keyed;
3353
+ }
3354
+ get readsChecked() {
3355
+ return this.sources.length > 0;
3356
+ }
3357
+ async readSource(path, caller, plain) {
3358
+ const found = await this.source(path);
3359
+ return found === void 0 ? plain() : this.checked(found, caller, plain);
3360
+ }
3361
+ async openSource(path, caller, real) {
3362
+ const found = await this.source(path, real);
3363
+ if (found === void 0) return;
3364
+ if (found.name === found.real && this.root.version) {
3365
+ if (this.passed.has(`${found.real}\0${await this.root.version(found.real, true)}`)) return;
3366
+ }
3367
+ await this.checked(found, caller, () => this.root.readFileBuffer(found.real));
3368
+ }
3369
+ async checked(found, caller, plain) {
3370
+ const { real, name: name2 } = found;
3371
+ if (name2 !== real || !this.root.readVersioned) {
3372
+ const bytes2 = await plain();
3373
+ await this.verify(name2, bytes2, caller, READ_OPS);
3374
+ return bytes2;
3375
+ }
3376
+ const { bytes, version } = await this.root.readVersioned(real, true);
3377
+ await this.pass(`${real}\0${version}`, () => this.verify(name2, bytes, caller, READ_OPS));
3378
+ return bytes;
3379
+ }
3380
+ async pass(key, check) {
3381
+ if (this.passed.has(key)) return;
3382
+ const generation = this.generation;
3383
+ await check();
3384
+ if (generation === this.generation) this.passed.add(key);
3385
+ }
3386
+ async source(path, known) {
3387
+ if (this.sources.length === 0) return void 0;
3388
+ const found = await this.real(path, void 0, known);
3389
+ if (found === void 0) return void 0;
3390
+ const suffixed = (p) => this.sources.some((s) => p.endsWith(s));
3391
+ if (suffixed(found.real) || suffixed(found.name) || suffixed(normalizePath(path))) return found;
3392
+ const st = await this.root.stat(found.real).catch(() => void 0);
3393
+ return st?.isFile && ((st.mode ?? 0) & 73) !== 0 ? found : void 0;
3394
+ }
3395
+ async verify(name2, bytes, caller, ops) {
3284
3396
  const actual = await sha256(bytes);
3285
3397
  const denial = { pid: caller.pid, uid: caller.uid, path: name2, actual };
3286
3398
  const tagged = caller.tag !== void 0 ? { tag: caller.tag } : {};
@@ -3288,27 +3400,33 @@ var Integrity = class {
3288
3400
  try {
3289
3401
  expected = await this.expected(name2);
3290
3402
  } catch {
3291
- this.say({ ...denial, ...tagged, op: "exec-integrity", errno: "EACCES" });
3403
+ this.say({ ...denial, ...tagged, op: ops.failed, errno: "EACCES" });
3292
3404
  throw fsError("EACCES", `integrity lookup failed: ${name2}`);
3293
3405
  }
3294
- if (expected === void 0) {
3295
- if (this.unknown === "allow") {
3296
- if (!this.unverified.has(name2)) {
3297
- this.unverified.add(name2);
3298
- this.say({ ...denial, ...tagged, op: "exec-unverified", errno: "ENODATA" });
3299
- }
3300
- return keyed;
3406
+ if (expected === actual) return;
3407
+ if (expected === void 0 && this.unknown === "allow") {
3408
+ const seen = ops === EXEC_OPS ? this.unverified : this.unread;
3409
+ if (!seen.has(name2)) {
3410
+ seen.add(name2);
3411
+ this.say({ ...denial, ...tagged, op: ops.unknown, errno: "ENODATA" });
3301
3412
  }
3302
- } else if (expected === actual) {
3303
- return keyed;
3413
+ return;
3304
3414
  }
3305
3415
  const known = expected !== void 0 ? { expected } : {};
3306
- this.say({ ...denial, ...tagged, ...known, op: "exec-integrity", errno: "EACCES" });
3416
+ this.say({ ...denial, ...tagged, ...known, op: ops.failed, errno: "EACCES" });
3307
3417
  throw fsError("EACCES", `integrity check failed: ${name2}`);
3308
3418
  }
3309
3419
  forget(path) {
3310
- for (const name2 of [...this.unverified]) {
3311
- if (path === void 0 || isProtected([path], name2)) this.unverified.delete(name2);
3420
+ this.generation++;
3421
+ for (const seen of [this.unverified, this.unread]) {
3422
+ for (const name2 of [...seen]) {
3423
+ if (path === void 0 || isProtected([path], name2)) seen.delete(name2);
3424
+ }
3425
+ }
3426
+ for (const key of [...this.passed]) {
3427
+ if (path === void 0 || isProtected([path], key.slice(0, key.indexOf("\0")))) {
3428
+ this.passed.delete(key);
3429
+ }
3312
3430
  }
3313
3431
  }
3314
3432
  async expected(path) {
@@ -3507,15 +3625,15 @@ var ProtectedFs = class {
3507
3625
  }
3508
3626
  } : {}
3509
3627
  };
3510
- const view = {};
3628
+ const view2 = {};
3511
3629
  for (let at = fs; at && at !== Object.prototype; at = Object.getPrototypeOf(at)) {
3512
3630
  for (const name2 of Object.getOwnPropertyNames(at)) {
3513
- if (name2 === "constructor" || Object.hasOwn(view, name2)) continue;
3631
+ if (name2 === "constructor" || Object.hasOwn(view2, name2)) continue;
3514
3632
  const value = fs[name2];
3515
- view[name2] = typeof value === "function" ? value.bind(fs) : value;
3633
+ view2[name2] = typeof value === "function" ? value.bind(fs) : value;
3516
3634
  }
3517
3635
  }
3518
- return Object.assign(view, own);
3636
+ return Object.assign(view2, own);
3519
3637
  }
3520
3638
  };
3521
3639
 
@@ -3634,6 +3752,23 @@ var RemoteWatch = class {
3634
3752
  }
3635
3753
  };
3636
3754
 
3755
+ // src/fs/view.ts
3756
+ function view(target, overrides) {
3757
+ const keys = new Set(Object.keys(overrides));
3758
+ return new Proxy(target, {
3759
+ get(t, prop, receiver) {
3760
+ if (keys.has(prop)) return overrides[prop];
3761
+ return Reflect.get(t, prop, receiver);
3762
+ },
3763
+ set(t, prop, value) {
3764
+ return Reflect.set(t, prop, value);
3765
+ },
3766
+ getPrototypeOf(t) {
3767
+ return Reflect.getPrototypeOf(t);
3768
+ }
3769
+ });
3770
+ }
3771
+
3637
3772
  // src/fs/opfs.ts
3638
3773
  var S_IFDIR = 16384;
3639
3774
  var S_IFREG = 32768;
@@ -3769,11 +3904,16 @@ var OpfsFs = class {
3769
3904
  remote;
3770
3905
  ranged;
3771
3906
  owners = false;
3907
+ rejectLinks = false;
3772
3908
  mounts = {};
3909
+ locks;
3910
+ lockPrefix;
3773
3911
  constructor(root, meta = new MemoryMeta(), channel, options = {}) {
3774
3912
  this.root = root;
3775
3913
  this.meta = sidecar(meta);
3776
3914
  this.ranged = options.ranged === true;
3915
+ this.locks = options.locks === void 0 ? lockManager() : options.locks ?? void 0;
3916
+ this.lockPrefix = `slicc-excl:${channel ?? ""}:`;
3777
3917
  this.channel = channel === void 0 ? void 0 : new BroadcastChannel(channel);
3778
3918
  this.remote = channel === void 0 ? void 0 : new RemoteWatch(`${channel}:watch`);
3779
3919
  if (this.channel) {
@@ -3800,12 +3940,13 @@ var OpfsFs = class {
3800
3940
  return this.remote?.announce(paths) ?? Promise.resolve();
3801
3941
  }
3802
3942
  committed(target) {
3943
+ this.fileTimes.delete(target);
3803
3944
  if (this.remote?.watched()) this.remote.wrote(target);
3804
3945
  }
3805
3946
  counted = [];
3806
- async stamp(path) {
3947
+ async stamp(path, cached = false) {
3807
3948
  const paths = [path, "/"];
3808
- const [entry, root] = this.meta.fresh ? await this.meta.fresh(paths) : await this.meta.get(paths);
3949
+ const [entry, root] = this.meta.fresh && !cached ? await this.meta.fresh(paths) : await this.meta.get(paths);
3809
3950
  return `${entry?.writes ?? 0}:${root?.epoch ?? 0}`;
3810
3951
  }
3811
3952
  async epoch() {
@@ -3904,13 +4045,25 @@ var OpfsFs = class {
3904
4045
  this.counted = versions ? prefixes2 : [];
3905
4046
  }
3906
4047
  unguarded() {
3907
- return Object.create(this, { guarded: { value: [] } });
4048
+ return view(this, { guarded: [] });
4049
+ }
4050
+ noLinks() {
4051
+ return view(this, { rejectLinks: true });
3908
4052
  }
3909
4053
  refuse(path, holding = false) {
3910
4054
  if (isProtected(this.guarded, path) || holding && holdsProtected(this.guarded, path)) {
3911
4055
  throw fsError("EROFS", path);
3912
4056
  }
3913
4057
  }
4058
+ async locked(physicalPath, op) {
4059
+ if (!this.locks) return op();
4060
+ const key = `${this.lockPrefix}${physicalPath}`;
4061
+ let result;
4062
+ await this.locks.request(key, async () => {
4063
+ result = await op();
4064
+ });
4065
+ return result;
4066
+ }
3914
4067
  async chown(path, change) {
3915
4068
  if (!this.owners) return;
3916
4069
  if (change.kind === "new") {
@@ -4042,8 +4195,15 @@ var OpfsFs = class {
4042
4195
  const link2 = await this.linkAlong(current);
4043
4196
  if (!link2) throw err;
4044
4197
  if (link2.path === current && !follow2) return { path: current, link: link2 };
4198
+ if (this.rejectLinks) throw fsError("ELOOP", path);
4045
4199
  const target = `${link2.link}${current.slice(link2.path.length)}`;
4046
- current = target.split("/").includes("..") ? await this.physical(parentOf4(link2.path), target, hops + 1, path) : resolvePath(parentOf4(link2.path), target);
4200
+ if (target.split("/").includes("..")) {
4201
+ const resolved2 = await this.physical(parentOf4(link2.path), target, hops + 1, path);
4202
+ current = resolved2.path;
4203
+ hops = resolved2.hops;
4204
+ } else {
4205
+ current = resolvePath(parentOf4(link2.path), target);
4206
+ }
4047
4207
  this.crossing(path, current);
4048
4208
  }
4049
4209
  }
@@ -4052,7 +4212,8 @@ var OpfsFs = class {
4052
4212
  async physical(base, path, used, of) {
4053
4213
  let done4 = [];
4054
4214
  let todo = (path.startsWith("/") ? path : `${base}/${path}`).split("/").filter((p) => p !== "" && p !== ".");
4055
- for (let hops = used; todo.length > 0; ) {
4215
+ let hops = used;
4216
+ while (todo.length > 0) {
4056
4217
  const part = todo.shift();
4057
4218
  if (part === "..") done4.pop();
4058
4219
  else if (!todo.includes("..")) done4.push(part);
@@ -4062,6 +4223,7 @@ var OpfsFs = class {
4062
4223
  const st = await this.lstat(at);
4063
4224
  if (!st.isSymbolicLink && !st.isDirectory) throw fsError("ENOTDIR", at);
4064
4225
  if (!st.isSymbolicLink) done4.push(part);
4226
+ else if (this.rejectLinks) throw fsError("ELOOP", of);
4065
4227
  else if (++hops > MAX_LINKS5) throw fsError("ELOOP", path);
4066
4228
  else {
4067
4229
  const link2 = await this.readlink(at);
@@ -4070,7 +4232,7 @@ var OpfsFs = class {
4070
4232
  }
4071
4233
  }
4072
4234
  }
4073
- return `/${done4.join("/")}`;
4235
+ return { path: `/${done4.join("/")}`, hops };
4074
4236
  }
4075
4237
  mode(path, directory, meta) {
4076
4238
  const fallback = directory || EXECUTABLE.test(path) ? 493 : 420;
@@ -4114,6 +4276,11 @@ var OpfsFs = class {
4114
4276
  return entry.timesAt >= ms;
4115
4277
  }
4116
4278
  movePending(from, to) {
4279
+ if (to !== void 0) {
4280
+ for (const path of [...this.fileTimes.keys()]) {
4281
+ if (within2(path, to)) this.fileTimes.delete(path);
4282
+ }
4283
+ }
4117
4284
  for (const [path, ms] of [...this.fileTimes]) {
4118
4285
  if (!within2(path, from)) continue;
4119
4286
  this.fileTimes.delete(path);
@@ -4121,8 +4288,7 @@ var OpfsFs = class {
4121
4288
  }
4122
4289
  }
4123
4290
  flushing = {};
4124
- touch(...dirs) {
4125
- const now = Date.now();
4291
+ touch(now, ...dirs) {
4126
4292
  for (const dir of dirs) {
4127
4293
  this.dirTimes.set(dir, now);
4128
4294
  this.dirDirty.add(dir);
@@ -4235,12 +4401,12 @@ var OpfsFs = class {
4235
4401
  false
4236
4402
  );
4237
4403
  }
4238
- async readVersioned(path) {
4404
+ async readVersioned(path, cached = false) {
4239
4405
  const handle = await this.existing(path);
4240
4406
  return this.retrying(
4241
4407
  path,
4242
4408
  async () => {
4243
- const stamp = await this.stamp(path);
4409
+ const stamp = await this.stamp(path, cached);
4244
4410
  const file = await handle.getFile();
4245
4411
  const bytes = new Uint8Array(await file.arrayBuffer());
4246
4412
  return { bytes, version: `${file.lastModified}:${file.size}:${stamp}` };
@@ -4248,12 +4414,12 @@ var OpfsFs = class {
4248
4414
  false
4249
4415
  );
4250
4416
  }
4251
- async version(path) {
4417
+ async version(path, cached = false) {
4252
4418
  const handle = await this.existing(path);
4253
4419
  return this.retrying(
4254
4420
  path,
4255
4421
  async () => {
4256
- const stamp = await this.stamp(path);
4422
+ const stamp = await this.stamp(path, cached);
4257
4423
  const file = await handle.getFile();
4258
4424
  return `${file.lastModified}:${file.size}:${stamp}`;
4259
4425
  },
@@ -4338,7 +4504,7 @@ var OpfsFs = class {
4338
4504
  dir,
4339
4505
  (parent) => parent.getFileHandle(name2, { create: true })
4340
4506
  );
4341
- if (!existed) this.touch(parentOf4(target));
4507
+ if (!existed) this.touch(Date.now(), parentOf4(target));
4342
4508
  return [handle, existed, target];
4343
4509
  } catch (err) {
4344
4510
  const code = err?.name === "TypeMismatchError" ? "EISDIR" : null;
@@ -4393,6 +4559,7 @@ var OpfsFs = class {
4393
4559
  );
4394
4560
  }
4395
4561
  async mkdir(path, options = {}) {
4562
+ if (!options.recursive) return this.mkdirExcl(path, options.owner);
4396
4563
  let found;
4397
4564
  try {
4398
4565
  found = await this.locate(path, true);
@@ -4401,28 +4568,75 @@ var OpfsFs = class {
4401
4568
  }
4402
4569
  if (found) {
4403
4570
  const directory = found.handle?.kind === "directory";
4404
- if (options.recursive && directory) return;
4405
- throw fsError(options.recursive ? "ENOTDIR" : "EEXIST", path);
4571
+ if (directory) return;
4572
+ throw fsError("ENOTDIR", path);
4406
4573
  }
4407
4574
  const [entry] = await this.meta.get([path]);
4408
4575
  if (entry?.link !== void 0) throw fsError("EEXIST", path);
4409
4576
  const { owner } = options;
4410
- if (options.recursive) {
4411
- const mode = owner?.parents;
4412
- const above = owner && {
4413
- uid: owner.uid,
4414
- gid: owner.gid,
4415
- ...mode !== void 0 ? { mode, parents: mode } : {}
4416
- };
4417
- await this.mkdir(parentOf4(path), { recursive: true, ...above ? { owner: above } : {} });
4418
- }
4577
+ const mode = owner?.parents;
4578
+ const above = owner && {
4579
+ uid: owner.uid,
4580
+ gid: owner.gid,
4581
+ ...mode !== void 0 ? { mode, parents: mode } : {}
4582
+ };
4583
+ await this.mkdir(parentOf4(path), { recursive: true, ...above ? { owner: above } : {} });
4419
4584
  const parent = await this.locate(parentOf4(path), true);
4420
4585
  if (parent.handle?.kind !== "directory") throw fsError("ENOTDIR", path);
4421
4586
  this.refuse(resolvePath(parent.path, baseOf(path)));
4422
4587
  await parent.handle.getDirectoryHandle(baseOf(path), { create: true });
4588
+ const now = Date.now();
4423
4589
  const made2 = `${parent.path === "/" ? "" : parent.path}/${baseOf(path)}`;
4424
4590
  await this.stampNew(made2, owner, true);
4425
- this.touch(parent.path, made2);
4591
+ this.touch(now, parent.path, made2);
4592
+ }
4593
+ async mkdirExcl(path, owner) {
4594
+ const parent = await this.locate(parentOf4(path), true);
4595
+ if (parent.handle?.kind !== "directory") throw fsError("ENOTDIR", path);
4596
+ const dir = parent.handle;
4597
+ const name2 = baseOf(path);
4598
+ const physical = resolvePath(parent.path, name2);
4599
+ await this.locked(physical, async () => {
4600
+ if (await this.handleExists(dir, name2)) throw fsError("EEXIST", path);
4601
+ const freshMeta = this.meta.fresh ? await this.meta.fresh([path]) : await this.meta.get([path]);
4602
+ if (freshMeta[0]?.link !== void 0) throw fsError("EEXIST", path);
4603
+ this.forget(physical);
4604
+ this.refuse(physical);
4605
+ await dir.getDirectoryHandle(name2, { create: true });
4606
+ const now = Date.now();
4607
+ const made2 = `${parent.path === "/" ? "" : parent.path}/${name2}`;
4608
+ await this.stampNew(made2, owner, true);
4609
+ this.touch(now, parent.path, made2);
4610
+ });
4611
+ }
4612
+ async createExclusive(path, owner) {
4613
+ const parent = await this.locate(parentOf4(path), true);
4614
+ if (parent.handle?.kind !== "directory") throw fsError("ENOTDIR", path);
4615
+ const dir = parent.handle;
4616
+ const name2 = baseOf(path);
4617
+ const physical = resolvePath(parent.path, name2);
4618
+ await this.locked(physical, async () => {
4619
+ if (await this.handleExists(dir, name2)) throw fsError("EEXIST", path);
4620
+ const freshMeta = this.meta.fresh ? await this.meta.fresh([path]) : await this.meta.get([path]);
4621
+ if (freshMeta[0] !== void 0) throw fsError("EEXIST", path);
4622
+ this.forget(physical);
4623
+ this.refuse(physical);
4624
+ await dir.getFileHandle(name2, { create: true });
4625
+ const now = Date.now();
4626
+ const made2 = `${parent.path === "/" ? "" : parent.path}/${name2}`;
4627
+ await this.stampNew(made2, owner, false);
4628
+ this.touch(now, parent.path, made2);
4629
+ });
4630
+ }
4631
+ async handleExists(dir, name2) {
4632
+ try {
4633
+ await dir.getDirectoryHandle(name2);
4634
+ return true;
4635
+ } catch (err) {
4636
+ if (err?.name === "TypeMismatchError") return true;
4637
+ if (err?.name === "NotFoundError") return false;
4638
+ throw err;
4639
+ }
4426
4640
  }
4427
4641
  async rm(path, options = {}) {
4428
4642
  let found;
@@ -4433,8 +4647,8 @@ var OpfsFs = class {
4433
4647
  if (options.force && error.code === "ENOENT") return;
4434
4648
  throw error;
4435
4649
  }
4436
- this.refuse(found.path, true);
4437
4650
  if (options.file && found.handle?.kind === "directory") throw fsError("EISDIR", path);
4651
+ this.refuse(found.path, true);
4438
4652
  if (found.handle) {
4439
4653
  const [dir, name2] = await this.parent(found.path);
4440
4654
  try {
@@ -4445,22 +4659,23 @@ var OpfsFs = class {
4445
4659
  } catch (err) {
4446
4660
  throw translate(err, path);
4447
4661
  }
4662
+ const now = Date.now();
4448
4663
  const file = found.handle.kind === "file";
4449
4664
  if (!file || this.remote?.watched()) this.changed(found.path);
4450
4665
  else this.forget(found.path);
4451
4666
  this.movePending(found.path);
4452
4667
  if (file && this.meta.absent?.(found.path)) {
4453
- this.touch(parentOf4(found.path));
4668
+ this.touch(now, parentOf4(found.path));
4454
4669
  return;
4455
4670
  }
4456
4671
  if (file && this.meta.removeFile) {
4457
4672
  await this.meta.removeFile(found.path);
4458
- this.touch(parentOf4(found.path));
4673
+ this.touch(now, parentOf4(found.path));
4459
4674
  return;
4460
4675
  }
4461
4676
  }
4462
4677
  await this.meta.remove([found.path]);
4463
- this.touch(parentOf4(found.path));
4678
+ this.touch(Date.now(), parentOf4(found.path));
4464
4679
  }
4465
4680
  links() {
4466
4681
  return false;
@@ -4468,11 +4683,14 @@ var OpfsFs = class {
4468
4683
  async link(from, to, follow2) {
4469
4684
  await (follow2 ? this.stat(from) : this.lstat(from));
4470
4685
  if (!(await this.stat(parentOf4(to))).isDirectory) throw fsError("ENOTDIR", to);
4471
- if (await this.lstat(to).then(
4686
+ const taken = await this.lstat(to).then(
4472
4687
  () => true,
4473
- () => false
4474
- ))
4475
- throw fsError("EEXIST", to);
4688
+ (err) => {
4689
+ if (missing2(err)) return false;
4690
+ throw err;
4691
+ }
4692
+ );
4693
+ if (taken) throw fsError("EEXIST", to);
4476
4694
  throw fsError("EPERM", `${from}: this file system has no hard links`);
4477
4695
  }
4478
4696
  async rename(from, to) {
@@ -4512,7 +4730,7 @@ var OpfsFs = class {
4512
4730
  if (source.link) {
4513
4731
  if (target?.handle) await this.rm(destination);
4514
4732
  await this.meta.move(source.path, destination, carry);
4515
- this.touch(parentOf4(source.path), parent.path);
4733
+ this.touch(Date.now(), parentOf4(source.path), parent.path);
4516
4734
  return;
4517
4735
  }
4518
4736
  const dir = parent.handle;
@@ -4535,7 +4753,7 @@ var OpfsFs = class {
4535
4753
  }
4536
4754
  if (copied) await this.restamp(destination, timed2);
4537
4755
  this.changed(source.path, destination);
4538
- this.touch(parentOf4(source.path), parent.path);
4756
+ this.touch(Date.now(), parentOf4(source.path), parent.path);
4539
4757
  }
4540
4758
  async modified(path) {
4541
4759
  const handle = await this.handle(path);
@@ -4605,7 +4823,7 @@ var OpfsFs = class {
4605
4823
  ...fields
4606
4824
  }))
4607
4825
  );
4608
- this.touch(parent.path);
4826
+ this.touch(Date.now(), parent.path);
4609
4827
  }
4610
4828
  async realpath(path, follow2) {
4611
4829
  return (await this.locate(path, follow2)).path;
@@ -5259,15 +5477,15 @@ function frame(opcode, payload) {
5259
5477
  const n = payload.length;
5260
5478
  const size = n < 126 ? 2 : n < 65536 ? 4 : 10;
5261
5479
  const out = new Uint8Array(size + n);
5262
- const view = new DataView(out.buffer);
5480
+ const view2 = new DataView(out.buffer);
5263
5481
  out[0] = 128 | opcode;
5264
5482
  if (n < 126) out[1] = n;
5265
5483
  else if (n < 65536) {
5266
5484
  out[1] = 126;
5267
- view.setUint16(2, n);
5485
+ view2.setUint16(2, n);
5268
5486
  } else {
5269
5487
  out[1] = 127;
5270
- view.setBigUint64(2, BigInt(n));
5488
+ view2.setBigUint64(2, BigInt(n));
5271
5489
  }
5272
5490
  out.set(payload, size);
5273
5491
  return out;
@@ -5389,9 +5607,9 @@ var WsReader = class {
5389
5607
  if (short < 126) return short;
5390
5608
  const ext = new Uint8Array(short === 126 ? 2 : 8);
5391
5609
  await fill(this.incoming, ext, signal);
5392
- const view = new DataView(ext.buffer);
5393
- if (short === 126) return view.getUint16(0);
5394
- const long = view.getBigUint64(0);
5610
+ const view2 = new DataView(ext.buffer);
5611
+ if (short === 126) return view2.getUint16(0);
5612
+ const long = view2.getBigUint64(0);
5395
5613
  return long > BigInt(this.max) ? this.max + 1 : Number(long);
5396
5614
  }
5397
5615
  take(opcode, fin, payload) {
@@ -7571,6 +7789,7 @@ var ClipboardChannel = class {
7571
7789
  size = 0;
7572
7790
  wrote = false;
7573
7791
  failed = false;
7792
+ aborted = false;
7574
7793
  constructor(broker, access2, stamp) {
7575
7794
  this.broker = broker;
7576
7795
  this.access = access2;
@@ -7625,11 +7844,14 @@ var ClipboardChannel = class {
7625
7844
  poll() {
7626
7845
  return READY;
7627
7846
  }
7847
+ abort() {
7848
+ this.aborted = true;
7849
+ }
7628
7850
  async close() {
7629
7851
  this.snapshot = void 0;
7630
7852
  this.taken = void 0;
7631
7853
  const commits = this.access === "write" || this.access === "both" && this.wrote;
7632
- if (!commits || this.failed) return;
7854
+ if (!commits || this.failed || this.aborted) return;
7633
7855
  const bytes = joined2(this.chunks, this.size);
7634
7856
  this.chunks = [];
7635
7857
  this.size = 0;
@@ -7872,6 +8094,15 @@ function parseSize(text) {
7872
8094
  if (!match) throw errnoError("EINVAL", `not a size: ${text}`);
7873
8095
  return Math.floor(Number(match[1]) * UNITS[match[2].toLowerCase()]);
7874
8096
  }
8097
+ function linkedTo(mount, path) {
8098
+ const own = mount.attrs.get(path)?.value;
8099
+ const ino = own?.kind === "directory" ? void 0 : own?.ino;
8100
+ return (attr2) => {
8101
+ if (!attr2 || attr2.kind === "directory") return false;
8102
+ if (ino !== void 0) return attr2.ino === ino;
8103
+ return own?.kind !== "directory" && (attr2.nlink ?? 1) > 1;
8104
+ };
8105
+ }
7875
8106
  var within5 = (path, target) => path === target || target === "/" || path.startsWith(`${target}/`);
7876
8107
  var HeldSet = class extends Set {
7877
8108
  idle;
@@ -8313,10 +8544,13 @@ var MountTable = class {
8313
8544
  }
8314
8545
  forget(mount, path) {
8315
8546
  mount.epoch++;
8316
- for (const key of [...mount.attrs.keys()]) if (within5(key, path)) mount.attrs.delete(key);
8547
+ const linked2 = mount.caps.links ? linkedTo(mount, path) : () => false;
8548
+ for (const [key, cached] of [...mount.attrs])
8549
+ if (within5(key, path) || linked2(cached.value)) mount.attrs.delete(key);
8317
8550
  mount.lists.delete(path);
8318
8551
  mount.lists.delete(parentOf7(path));
8319
- for (const key of [...mount.lists.keys()]) if (within5(key, path)) mount.lists.delete(key);
8552
+ for (const [key, cached] of [...mount.lists])
8553
+ if (within5(key, path) || cached.value.some((e) => linked2(e.attr))) mount.lists.delete(key);
8320
8554
  }
8321
8555
  async getattr(mount, path, rel2, settled = false) {
8322
8556
  for (let round = 1; ; round++) {
@@ -8365,12 +8599,64 @@ function noteExtent(extents, from, to) {
8365
8599
  last[1] = Math.max(last[1], to);
8366
8600
  } else extents.push([from, to]);
8367
8601
  }
8368
- var MountFs = class {
8602
+ var RELINKED = [
8603
+ ["readFileBuffer", [0]],
8604
+ ["writeFile", [0]],
8605
+ ["pread", [0]],
8606
+ ["pwrite", [0]],
8607
+ ["truncate", [0]],
8608
+ ["lstat", [0]],
8609
+ ["stat", [0]],
8610
+ ["readdir", [0]],
8611
+ ["readdirStat", [0]],
8612
+ ["mkdir", [0]],
8613
+ ["rm", [0]],
8614
+ ["rename", [0, 1]],
8615
+ ["symlink", [1]],
8616
+ ["readlink", [0]],
8617
+ ["chown", [0]],
8618
+ ["chmod", [0]],
8619
+ ["utimes", [0]],
8620
+ ["lutimes", [0]]
8621
+ ];
8622
+ var MountFs = class _MountFs {
8369
8623
  table;
8370
8624
  base;
8371
- constructor(table, base) {
8625
+ rejectLinks;
8626
+ constructor(table, base, rejectLinks = false) {
8372
8627
  this.table = table;
8373
8628
  this.base = base;
8629
+ this.rejectLinks = rejectLinks;
8630
+ }
8631
+ static {
8632
+ const proto = _MountFs.prototype;
8633
+ for (const [name2, at] of RELINKED) {
8634
+ const run2 = proto[name2];
8635
+ proto[name2] = async function(...args) {
8636
+ try {
8637
+ return await run2.apply(this, args);
8638
+ } catch (err) {
8639
+ const moved2 = await this.relinked(err, args, at);
8640
+ if (!moved2) throw err;
8641
+ return this[name2](...moved2);
8642
+ }
8643
+ };
8644
+ }
8645
+ }
8646
+ async relinked(err, args, at) {
8647
+ if (err?.code !== "ENOTDIR") return void 0;
8648
+ const moved2 = [...args];
8649
+ let changed = false;
8650
+ for (const i of at) {
8651
+ const path = args[i];
8652
+ if (typeof path !== "string" || !this.at(path)) continue;
8653
+ const named = await this.named(path).catch(() => void 0);
8654
+ if (named === void 0 || named === normalizePath(path)) continue;
8655
+ if (this.rejectLinks) throw errnoError("ELOOP", path);
8656
+ moved2[i] = named;
8657
+ changed = true;
8658
+ }
8659
+ return changed ? moved2 : void 0;
8374
8660
  }
8375
8661
  at(path) {
8376
8662
  return this.table.locate(normalizePath(path));
@@ -8421,6 +8707,7 @@ var MountFs = class {
8421
8707
  throw err;
8422
8708
  }
8423
8709
  if (attr2.kind !== "symlink") return real;
8710
+ if (this.rejectLinks) throw errnoError("ELOOP", path);
8424
8711
  if (hops >= MAX_LINKS7) throw errnoError("ELOOP", path);
8425
8712
  const target = await found.mount.conn.call({ op: "readlink", path: found.rel });
8426
8713
  return this.follow(this.base.resolvePath(parentOf7(real), target), hops + 1, create);
@@ -8825,6 +9112,7 @@ var MountFs = class {
8825
9112
  }
8826
9113
  const { mount, rel: rel2 } = found;
8827
9114
  const real = normalizePath(path);
9115
+ if (options.file && rel2 === "/") throw errnoError("EISDIR", real);
8828
9116
  if (rel2 === "/" && options.recursive) await this.rmEach(real, mount, rel2, options);
8829
9117
  if (rel2 === "/") throw errnoError("EBUSY", `${real} is a mount point`);
8830
9118
  this.writable(mount, real);
@@ -9060,8 +9348,22 @@ var MountFs = class {
9060
9348
  this.table.forget(found.mount, real);
9061
9349
  await found.mount.conn.call({ op: "setattr", path: found.rel, mtime: mtime.getTime() });
9062
9350
  }
9351
+ noLinks() {
9352
+ const noLinkBase = this.base.noLinks.call(this.base);
9353
+ return view(this, { rejectLinks: true, base: noLinkBase });
9354
+ }
9063
9355
  };
9064
9356
 
9357
+ // src/fs/dev-removal.ts
9358
+ function inDevTree(path) {
9359
+ return path === "/dev" || path.startsWith("/dev/");
9360
+ }
9361
+ function devRemoval(path, cred) {
9362
+ if (!inDevTree(path)) return void 0;
9363
+ if (cred && cred.euid !== 0) return "EACCES";
9364
+ return path === "/dev" ? "EBUSY" : "EPERM";
9365
+ }
9366
+
9065
9367
  // src/realm/sync-fs-token-registry.ts
9066
9368
  var registry = /* @__PURE__ */ new Map();
9067
9369
  function mintSyncFsToken(entry) {
@@ -9174,6 +9476,22 @@ function plainDirMode(entry, mode) {
9174
9476
  const wanted = mode & 4095 & ~(entry.umask?.() ?? 18);
9175
9477
  return wanted === PLAIN_DIR_MODE ? void 0 : wanted;
9176
9478
  }
9479
+ function plainParentsMode(entry) {
9480
+ if (entry.cred) return void 0;
9481
+ const wanted = 511 & ~(entry.umask?.() ?? 18) | 192;
9482
+ return wanted === PLAIN_DIR_MODE ? void 0 : wanted;
9483
+ }
9484
+ async function missingParents(fs, path, mode) {
9485
+ const parts2 = path.split("/").filter(Boolean);
9486
+ const dirs = [];
9487
+ for (let i = 1; i < parts2.length; i++) {
9488
+ const p = `/${parts2.slice(0, i).join("/")}`;
9489
+ if (!await fs.exists(p)) dirs.push(p);
9490
+ }
9491
+ return async () => {
9492
+ for (const p of dirs) await fs.chmod(p, mode);
9493
+ };
9494
+ }
9177
9495
  function mkdirOptions(entry, mode = 511) {
9178
9496
  const owner = ownerFor2(entry, mode);
9179
9497
  if (!owner) return { recursive: true };
@@ -9185,23 +9503,39 @@ async function mkdirExclusive(entry, path, mode) {
9185
9503
  await entry.fs.mkdir(path, owner ? { owner } : {});
9186
9504
  }
9187
9505
  async function chown(entry, path, req) {
9506
+ const real = req.handle !== void 0 ? await keptOr(entry, path, req.handle) : path;
9188
9507
  const cred = entry.cred?.();
9189
9508
  if (!cred) {
9190
- await (req.op === "chown" ? entry.fs.stat(path) : entry.fs.lstat(path));
9509
+ await (req.op === "chown" ? entry.fs.stat(real) : entry.fs.lstat(real));
9191
9510
  return done;
9192
9511
  }
9193
- await chownAs(entry.fs, cred, path, {
9512
+ await chownAs(entry.fs, cred, real, {
9194
9513
  ...req.uid !== void 0 ? { uid: req.uid } : {},
9195
9514
  ...req.gid !== void 0 ? { gid: req.gid } : {},
9196
9515
  follow: req.op === "chown"
9197
9516
  });
9198
9517
  return done;
9199
9518
  }
9519
+ async function runMkdir(entry, path, req) {
9520
+ const { fs } = entry;
9521
+ const plain = plainDirMode(entry, req.mode);
9522
+ const parentsMode = !req.exclusive ? plainParentsMode(entry) : void 0;
9523
+ const fixParents = parentsMode !== void 0 ? await missingParents(fs, path, parentsMode) : void 0;
9524
+ const fresh2 = plain !== void 0 && !await fs.exists(path);
9525
+ await (req.exclusive ? mkdirExclusive(entry, path, req.mode) : fs.mkdir(path, mkdirOptions(entry, req.mode)));
9526
+ await fixParents?.();
9527
+ if (fresh2) await fs.chmod(path, plain);
9528
+ return done;
9529
+ }
9530
+ function readSource(entry, path) {
9531
+ const plain = () => entry.fs.readFileBuffer(path);
9532
+ return entry.source ? entry.source.read(path, plain) : plain();
9533
+ }
9200
9534
  async function run(entry, path, req) {
9201
9535
  const { fs, cwd } = entry;
9202
9536
  switch (req.op) {
9203
9537
  case "read":
9204
- return { ok: true, kind: "bytes", bytes: await fs.readFileBuffer(path) };
9538
+ return { ok: true, kind: "bytes", bytes: await readSource(entry, path) };
9205
9539
  case "write":
9206
9540
  await fs.writeFile(path, req.body ?? new Uint8Array(0), ownerFor2(entry, 438));
9207
9541
  return done;
@@ -9215,13 +9549,8 @@ async function run(entry, path, req) {
9215
9549
  return json2(await fs.readdir(path));
9216
9550
  case "readdir-stat":
9217
9551
  return json2(await readdirStat(fs, path));
9218
- case "mkdir": {
9219
- const plain = plainDirMode(entry, req.mode);
9220
- const fresh2 = plain !== void 0 && !await fs.exists(path);
9221
- await (req.exclusive ? mkdirExclusive(entry, path, req.mode) : fs.mkdir(path, mkdirOptions(entry, req.mode)));
9222
- if (fresh2) await fs.chmod(path, plain);
9223
- return done;
9224
- }
9552
+ case "mkdir":
9553
+ return runMkdir(entry, path, req);
9225
9554
  case "rm":
9226
9555
  await fs.rm(path, { recursive: true });
9227
9556
  return done;
@@ -9247,7 +9576,7 @@ async function run(entry, path, req) {
9247
9576
  return done;
9248
9577
  case "create":
9249
9578
  await createExclusive(fs, path, ownerFor2(entry, req.mode ?? 438));
9250
- entry.created?.add(path);
9579
+ if (req.opening) entry.created?.add(path);
9251
9580
  entry.fresh?.(path);
9252
9581
  return done;
9253
9582
  case "readlink":
@@ -9258,10 +9587,10 @@ async function run(entry, path, req) {
9258
9587
  return done;
9259
9588
  }
9260
9589
  case "utimes":
9261
- await fs.utimes(path, new Date(req.atimeMs ?? 0), new Date(req.mtimeMs ?? 0));
9590
+ await fs.utimes(path, ...await timesOf(entry, path, req));
9262
9591
  return done;
9263
9592
  case "lutimes":
9264
- await lutimesOf(fs, path, new Date(req.atimeMs ?? 0), new Date(req.mtimeMs ?? 0));
9593
+ await lutimesOf(fs, path, ...await timesOf(entry, path, req));
9265
9594
  return done;
9266
9595
  case "chown":
9267
9596
  case "lchown":
@@ -9274,6 +9603,17 @@ async function run(entry, path, req) {
9274
9603
  return { ok: false, errno: "EINVAL", message: `sync-fs: unknown op '${req.op}'` };
9275
9604
  }
9276
9605
  }
9606
+ async function timesOf(entry, path, req) {
9607
+ const given = [new Date(req.atimeMs ?? 0), new Date(req.mtimeMs ?? 0)];
9608
+ const g = req.now === true ? guardOf(entry) : void 0;
9609
+ if (!g || g.cred.euid === 0) return given;
9610
+ const look = req.op === "lutimes" ? entry.fs.lstat : entry.fs.stat;
9611
+ const st = await look.call(entry.fs, path).catch(() => void 0);
9612
+ if (!st || (st.uid ?? 0) === g.cred.euid) return given;
9613
+ const now = /* @__PURE__ */ new Date();
9614
+ const kept = (ms, stored) => ms === stored.getTime() ? stored : now;
9615
+ return [kept(req.atimeMs, st.atime), kept(req.mtimeMs, st.mtime)];
9616
+ }
9277
9617
  function guardOf(entry) {
9278
9618
  const cred = entry.dac && entry.cred?.();
9279
9619
  if (!cred || !entry.dac) return void 0;
@@ -9283,41 +9623,82 @@ function guardOf(entry) {
9283
9623
  var FOLLOWING = /* @__PURE__ */ new Set(["exists", "stat", "chown", "statfs"]);
9284
9624
  var LOOKS = /* @__PURE__ */ new Set(["exists", "stat", "lstat"]);
9285
9625
  var NOT_FOLLOWING = /* @__PURE__ */ new Set(["lstat", "readlink", "lchown"]);
9626
+ async function walkSafe(g, path, follow2) {
9627
+ try {
9628
+ const walked = await walk(g.fs, g.cred, path, follow2, g.links);
9629
+ return walked.path;
9630
+ } catch {
9631
+ return path;
9632
+ }
9633
+ }
9286
9634
  async function guard(g, path, req, cwd) {
9287
9635
  const { op } = req;
9288
- if (FOLLOWING.has(op) || NOT_FOLLOWING.has(op)) return searchable(g, op, path, FOLLOWING.has(op));
9636
+ if (FOLLOWING.has(op) || NOT_FOLLOWING.has(op)) {
9637
+ const walked = await need(g, op, path, 0, { follow: FOLLOWING.has(op) });
9638
+ return { path: walked.path };
9639
+ }
9289
9640
  const other = () => g.fs.resolvePath(cwd, req.arg2 ?? "");
9290
9641
  switch (op) {
9291
9642
  case "readdir":
9292
- case "readdir-stat":
9293
- return listable(g, op, path);
9294
- case "mkdir":
9295
- return makeIn(g, op, path);
9643
+ case "readdir-stat": {
9644
+ await listable(g, op, path);
9645
+ const w = await walkSafe(g, path, true);
9646
+ return { path: w };
9647
+ }
9648
+ case "mkdir": {
9649
+ await makeIn(g, op, path);
9650
+ return { path: await walkSafe(g, path, false) };
9651
+ }
9296
9652
  case "create":
9297
9653
  case "symlink":
9298
- return inParent(g, op, path);
9654
+ await inParent(g, op, path);
9655
+ return { path: await walkSafe(g, path, op === "create") };
9299
9656
  case "unlink":
9300
- case "rmdir":
9301
- return inParent(g, op, path, true);
9302
- case "rm":
9303
- return removable(g, op, path);
9304
- case "rename":
9305
- return renamable(g, path, other());
9306
- case "link":
9307
- return linkable(g, path, other(), req.mode === 1);
9657
+ case "rmdir": {
9658
+ await inParent(g, op, path, true);
9659
+ const w = await walkSafe(g, path, false);
9660
+ return { path: w };
9661
+ }
9662
+ case "rm": {
9663
+ await removable(g, op, path);
9664
+ const w = await walkSafe(g, path, false);
9665
+ return { path: w };
9666
+ }
9667
+ case "rename": {
9668
+ const to = other();
9669
+ await renamable(g, path, to);
9670
+ const fromWalked = await walkSafe(g, path, false);
9671
+ const toWalked = await walkSafe(g, to, false);
9672
+ return { path: fromWalked, arg2: toWalked };
9673
+ }
9674
+ case "link": {
9675
+ const to = other();
9676
+ const follow2 = req.mode === 1;
9677
+ await linkable(g, path, to, follow2);
9678
+ const fromWalked = await walkSafe(g, path, follow2);
9679
+ const toWalked = await walkSafe(g, to, false);
9680
+ return { path: fromWalked, arg2: toWalked };
9681
+ }
9308
9682
  case "utimes":
9309
- case "lutimes":
9310
- return retimable(g, op, path, req.now === true);
9683
+ case "lutimes": {
9684
+ await retimable(g, op, path, req.now === true);
9685
+ const follow2 = op === "utimes";
9686
+ const w = await walkSafe(g, path, follow2);
9687
+ return { path: w };
9688
+ }
9311
9689
  case "read":
9312
9690
  case "pread":
9313
- return req.handle === void 0 ? readable(g, op, path) : void 0;
9691
+ if (req.handle === void 0) await readable(g, op, path);
9692
+ return { path: await walkSafe(g, path, true) };
9314
9693
  case "pwrite":
9315
9694
  case "truncate":
9316
- return req.handle === void 0 ? fileWritable(g, op, path) : void 0;
9695
+ if (req.handle === void 0) await fileWritable(g, op, path);
9696
+ return { path: await walkSafe(g, path, true) };
9317
9697
  case "write":
9318
- return req.handle === void 0 && req.mode !== 1 ? writable(g, op, path) : void 0;
9698
+ if (req.handle === void 0 && req.mode !== 1) await writable(g, op, path);
9699
+ return { path: await walkSafe(g, path, true) };
9319
9700
  default:
9320
- return void 0;
9701
+ return { path: await walkSafe(g, path, true) };
9321
9702
  }
9322
9703
  }
9323
9704
  async function opening(entry, path, mode) {
@@ -9334,6 +9715,7 @@ async function holdOp(entry, path, req) {
9334
9715
  if (on && open) {
9335
9716
  await opening(entry, path, mode);
9336
9717
  const real = await entry.fs.realpath?.(path, true).catch(() => path);
9718
+ await entry.source?.open(path, real);
9337
9719
  return json2(entry.hold?.(path, on, open, real) ?? null);
9338
9720
  }
9339
9721
  entry.hold?.(path, on, open, void 0, req.handle);
@@ -9386,15 +9768,16 @@ async function mayChange(entry, path, req) {
9386
9768
  }
9387
9769
  async function guarded(entry, path, req) {
9388
9770
  const g = guardOf(entry);
9389
- if (!g) return void 0;
9390
- if (LOOKS.has(req.op) && await entry.holdsKept?.(path)) return void 0;
9771
+ if (!g) return {};
9772
+ if (LOOKS.has(req.op) && await entry.holdsKept?.(path)) return {};
9391
9773
  try {
9392
9774
  await mayChange(entry, path, req);
9393
- await guard(g, path, req, entry.cwd);
9394
- return void 0;
9775
+ const paths = await guard(g, path, req, entry.cwd);
9776
+ return { paths };
9395
9777
  } catch (err) {
9396
9778
  const refusal2 = err.code === "EACCES";
9397
- return req.op === "exists" && refusal2 ? json2(false) : toErrno(err);
9779
+ const refused2 = req.op === "exists" && refusal2 ? json2(false) : toErrno(err);
9780
+ return { refused: refused2 };
9398
9781
  }
9399
9782
  }
9400
9783
  async function createExclusive(fs, path, owner) {
@@ -9420,6 +9803,7 @@ async function ranged(fs, path, req) {
9420
9803
  else await ops.truncate(path, req.length ?? 0);
9421
9804
  return done;
9422
9805
  }
9806
+ var CHOWN_OPS = /* @__PURE__ */ new Set(["chown", "lchown"]);
9423
9807
  var UNLINKED_OPS = /* @__PURE__ */ new Set(["read", "pread", "pwrite", "truncate"]);
9424
9808
  var OWN_OPS = /* @__PURE__ */ new Set(["rm", "unlink", "rmdir", "rename", "link"]);
9425
9809
  var TWO_PATHS = /* @__PURE__ */ new Set(["rename", "link"]);
@@ -9484,7 +9868,7 @@ async function rmPermitted(entry, path, req, refused2) {
9484
9868
  const st = await entry.fs.lstat(path).catch(() => void 0);
9485
9869
  if (!st?.isDirectory) return refused2;
9486
9870
  const unlisted = await guarded(entry, path, { ...req, op: "readdir" });
9487
- if (unlisted) return unlisted;
9871
+ if ("refused" in unlisted) return unlisted.refused;
9488
9872
  const names = await entry.fs.readdir(path).catch(() => void 0);
9489
9873
  if (!names) return refused2;
9490
9874
  let first;
@@ -9496,31 +9880,64 @@ async function rmPermitted(entry, path, req, refused2) {
9496
9880
  if (!child.ok) first ??= child;
9497
9881
  }
9498
9882
  if (first) return first;
9499
- const again = await guarded(entry, path, req);
9500
- return again ?? await run(entry, path, req);
9883
+ const g = guardOf(entry);
9884
+ if (g) {
9885
+ try {
9886
+ await inParent(g, "rm", path, true);
9887
+ } catch (err) {
9888
+ return toErrno(err);
9889
+ }
9890
+ }
9891
+ return await run(entry, path, req);
9892
+ }
9893
+ async function byHandle(entry, path, req) {
9894
+ if (req.op === "kept") return json2(entry.keptFor?.(req.handle ?? -1) !== void 0);
9895
+ if (req.op === "stat" && req.handle !== void 0) return keptStat(entry, req);
9896
+ if (CHOWN_OPS.has(req.op) && entry.keptFor?.(req.handle ?? -1)) return chown(entry, path, req);
9897
+ return void 0;
9898
+ }
9899
+ async function guardAndResolve(entry, path, req) {
9900
+ const result = await guarded(entry, path, req);
9901
+ if ("refused" in result) return result.refused;
9902
+ const { noLinks } = entry;
9903
+ const { paths } = result;
9904
+ if (entry.links === void 0 || !noLinks || !paths) return { path, entry };
9905
+ return {
9906
+ path: paths.path,
9907
+ ...paths.arg2 ? { arg2: paths.arg2 } : {},
9908
+ entry: { ...entry, fs: noLinks }
9909
+ };
9501
9910
  }
9911
+ var REMOVALS = /* @__PURE__ */ new Set(["unlink", "rmdir", "rm"]);
9502
9912
  async function dispatchOne(entry, req) {
9503
9913
  const { fs, cwd } = entry;
9504
9914
  try {
9505
9915
  const path = fs.resolvePath(cwd, req.path);
9506
- if (req.op === "kept") return json2(entry.keptFor?.(req.handle ?? -1) !== void 0);
9507
- if (req.op === "stat" && req.handle !== void 0) return await keptStat(entry, req);
9916
+ const device = REMOVALS.has(req.op) ? devRemoval(path, entry.cred?.()) : void 0;
9917
+ if (device) return { ok: false, errno: device, message: `${path}: a device entry stays` };
9918
+ const held = await byHandle(entry, path, req);
9919
+ if (held) return held;
9508
9920
  if (req.op === "access") return await accessOp(entry, path, req);
9509
9921
  if (req.op === "hold") return await holdOp(entry, path, req);
9510
- const refused2 = await guarded(entry, path, req);
9511
- if (refused2 && req.op === "rm" && !refused2.ok && DENIED.has(refused2.errno)) {
9512
- return await rmPermitted(entry, path, req, refused2);
9513
- }
9514
- if (refused2) return refused2;
9515
- if (req.op === "statfs") return json2(await usageAt(entry, path) ?? await storageUsage());
9516
- if (entry.revoked?.(path)) return { ok: false, errno: "EIO", message: `${path} was unmounted` };
9922
+ const resolved2 = await guardAndResolve(entry, path, req);
9923
+ if (!("entry" in resolved2)) {
9924
+ if (req.op === "rm" && !resolved2.ok && DENIED.has(resolved2.errno))
9925
+ return await rmPermitted(entry, path, req, resolved2);
9926
+ return resolved2;
9927
+ }
9928
+ const { path: resolvedPath, arg2: resolvedArg2, entry: opEntry } = resolved2;
9929
+ const opReq = resolvedArg2 ? { ...req, arg2: resolvedArg2 } : req;
9930
+ if (req.op === "statfs")
9931
+ return json2(await usageAt(entry, resolvedPath) ?? await storageUsage());
9932
+ if (entry.revoked?.(resolvedPath))
9933
+ return { ok: false, errno: "EIO", message: `${resolvedPath} was unmounted` };
9517
9934
  if (req.op === "write" && req.mode === 1 || UNLINKED_OPS.has(req.op)) {
9518
- const serve = async () => run(entry, await keptOr(entry, path, req.handle), req);
9519
- return await (entry.gate ? entry.gate(path, serve) : serve());
9935
+ const serve = async () => run(opEntry, await keptOr(opEntry, resolvedPath, req.handle), opReq);
9936
+ return await (entry.gate ? entry.gate(resolvedPath, serve) : serve());
9520
9937
  }
9521
- if (!entry.own || !OWN_OPS.has(req.op)) return await run(entry, path, req);
9522
- const target = TWO_PATHS.has(req.op) ? fs.resolvePath(cwd, req.arg2 ?? "") : path;
9523
- return await owned2(entry, entry.own, req, path, target);
9938
+ if (!entry.own || !OWN_OPS.has(req.op)) return await run(opEntry, resolvedPath, opReq);
9939
+ const target = resolvedArg2 ?? (TWO_PATHS.has(req.op) ? fs.resolvePath(cwd, req.arg2 ?? "") : resolvedPath);
9940
+ return await owned2(opEntry, entry.own, opReq, resolvedPath, target);
9524
9941
  } catch (err) {
9525
9942
  return toErrno(err);
9526
9943
  }
@@ -9548,6 +9965,15 @@ var SAB_I_KILLED = 13;
9548
9965
  var SAB_I_ASYNC = 14;
9549
9966
  var SAB_I_STOP = 15;
9550
9967
  var SAB_I_BLOCKED = 16;
9968
+ var SAB_I_BLOCKED_HI = 17;
9969
+ function loadBlocked(header4) {
9970
+ return [Atomics.load(header4, SAB_I_BLOCKED), Atomics.load(header4, SAB_I_BLOCKED_HI)];
9971
+ }
9972
+ function storeBlocked(header4, [low, high]) {
9973
+ Atomics.store(header4, SAB_I_BLOCKED_HI, high);
9974
+ Atomics.store(header4, SAB_I_BLOCKED, low);
9975
+ }
9976
+ var anyBlocked = (mask) => mask !== void 0 && (mask[0] !== 0 || mask[1] !== 0);
9551
9977
  var U32 = 2 ** 32;
9552
9978
  function storeU53(header4, lo, hi, value) {
9553
9979
  Atomics.store(header4, hi, Math.floor(value / U32));
@@ -10074,7 +10500,7 @@ function mountTable(mounts = []) {
10074
10500
  }
10075
10501
 
10076
10502
  // src/version.ts
10077
- var KERNEL_VERSION = true ? "1.92.0" : "0.0.0-dev";
10503
+ var KERNEL_VERSION = true ? "1.93.1" : "0.0.0-dev";
10078
10504
  var OS_RELEASE = "6.0.0-slicc";
10079
10505
  function procVersion() {
10080
10506
  return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
@@ -10774,21 +11200,28 @@ function bodyOf(incoming, head, method, signal) {
10774
11200
  var KernelLoopback = class {
10775
11201
  net;
10776
11202
  cred;
10777
- constructor(net, cred) {
11203
+ stamp;
11204
+ constructor(net, cred, stamp) {
10778
11205
  this.net = net;
10779
11206
  this.cred = cred;
11207
+ this.stamp = stamp;
10780
11208
  }
10781
11209
  port(url) {
10782
11210
  return kernelLoopbackPort(url);
10783
11211
  }
10784
- connect(url, port, signal) {
11212
+ async connect(url, port, signal) {
10785
11213
  if (url.protocol === "https:" || url.protocol === "wss:") {
10786
11214
  throw errno("ENOTSUP", `TLS to the kernel's loopback is not supported: ${url.origin}`);
10787
11215
  }
10788
11216
  signal.throwIfAborted();
10789
11217
  const socket = this.net.socket("inet");
10790
11218
  try {
10791
- void socket.connect({ family: "inet", host: "127.0.0.1", port }, false, this.cred());
11219
+ void socket.connect(
11220
+ { family: "inet", host: "127.0.0.1", port },
11221
+ false,
11222
+ this.cred(),
11223
+ this.stamp ? await this.stamp() : void 0
11224
+ );
10792
11225
  } catch (err) {
10793
11226
  socket.close();
10794
11227
  const refused2 = err instanceof KernelError && err.code === "ECONNREFUSED";
@@ -10802,7 +11235,7 @@ var KernelLoopback = class {
10802
11235
  const body = req.body ?? (/^(POST|PUT|PATCH)$/i.test(req.method) ? new Uint8Array(0) : void 0);
10803
11236
  const length2 = body ? [["Content-Length", String(body.length)]] : [];
10804
11237
  const head = requestHead(req.method, url, req.headers, [...length2, ["Connection", "close"]]);
10805
- const socket = this.connect(url, port, req.signal);
11238
+ const socket = await this.connect(url, port, req.signal);
10806
11239
  try {
10807
11240
  await writeAll2(socket, body ? concat([head, body]) : head, req.signal);
10808
11241
  const incoming = new Incoming(socket);
@@ -10823,7 +11256,7 @@ var KernelLoopback = class {
10823
11256
  }
10824
11257
  async websocket(req, port) {
10825
11258
  const url = new URL(req.url);
10826
- const socket = this.connect(url, port, req.signal);
11259
+ const socket = await this.connect(url, port, req.signal);
10827
11260
  try {
10828
11261
  const key = btoa(String.fromCharCode(...crypto.getRandomValues(new Uint8Array(16))));
10829
11262
  const extra = [
@@ -10848,6 +11281,9 @@ var KernelLoopback = class {
10848
11281
  if (!upgraded || accept2 !== await acceptFor(key)) {
10849
11282
  throw errno("EPROTO", "the server did not accept the WebSocket key");
10850
11283
  }
11284
+ if (fieldValues(head.headers, "sec-websocket-extensions").length > 0) {
11285
+ throw errno("EPROTO", "the server offered an unsolicited extension");
11286
+ }
10851
11287
  const protocol = fieldValues(head.headers, "sec-websocket-protocol")[0] ?? "";
10852
11288
  return new LoopbackWebSocket(socket, incoming, protocol);
10853
11289
  } catch (err) {
@@ -10947,7 +11383,7 @@ var LoopbackWebSocket = class {
10947
11383
  })();
10948
11384
  }
10949
11385
  async pump(reader, deliver) {
10950
- let close = { code: 1006, reason: "" };
11386
+ let close = { code: 1006, reason: "", wasClean: false };
10951
11387
  try {
10952
11388
  for (; ; ) {
10953
11389
  await this.space();
@@ -10957,14 +11393,14 @@ var LoopbackWebSocket = class {
10957
11393
  else if (event.kind === "binary") deliver(event.bytes);
10958
11394
  else if (event.kind === "ping") this.write(clientFrame(OP_PONG, event.payload));
10959
11395
  else {
10960
- close = { code: event.code, reason: event.reason };
11396
+ close = { code: event.code, reason: event.reason, wasClean: true };
10961
11397
  if (!this.sentClose) this.sendClose(event.code === 1005 ? 1e3 : event.code, "");
10962
11398
  break;
10963
11399
  }
10964
11400
  }
10965
11401
  } catch (err) {
10966
11402
  if (err instanceof WsError) this.sendClose(err.code, err.message);
10967
- close = { code: err instanceof WsError ? err.code : 1006, reason: "" };
11403
+ close = { code: 1006, reason: "", wasClean: false };
10968
11404
  } finally {
10969
11405
  const late = setTimeout(() => this.stop.abort(), CLOSE_WAIT_MS3);
10970
11406
  await this.writes;
@@ -11754,14 +12190,22 @@ var WsHandles = class {
11754
12190
  return typeof next2.value === "string" ? { ok: true, kind: "json", json: { text: next2.value } } : { ok: true, kind: "bytes", bytes: next2.value };
11755
12191
  }
11756
12192
  const closed = await socket.closed;
11757
- return { ok: true, kind: "json", json: { code: closed.code, reason: closed.reason } };
12193
+ return {
12194
+ ok: true,
12195
+ kind: "json",
12196
+ json: { code: closed.code, reason: closed.reason, wasClean: closed.wasClean === true }
12197
+ };
11758
12198
  } catch (e) {
11759
12199
  return fail("ECONNRESET", message(e));
11760
12200
  }
11761
12201
  }
11762
12202
  async closed(socket) {
11763
- const { code, reason } = await socket.closed;
11764
- return { ok: true, kind: "json", json: { code, reason } };
12203
+ const closed = await socket.closed;
12204
+ return {
12205
+ ok: true,
12206
+ kind: "json",
12207
+ json: { code: closed.code, reason: closed.reason, wasClean: closed.wasClean === true }
12208
+ };
11765
12209
  }
11766
12210
  send(socket, req, entry) {
11767
12211
  const data = req.text ?? req.body;
@@ -11801,7 +12245,7 @@ function message2(e) {
11801
12245
  }
11802
12246
  function codeOf2(e) {
11803
12247
  const code = e?.code;
11804
- return typeof code === "string" && /^E[A-Z]+$/.test(code) ? code : "EIO";
12248
+ return typeof code === "string" && /^E[A-Z]+$|^EAI_[A-Z]+$/.test(code) ? code : "EIO";
11805
12249
  }
11806
12250
  async function* once(bytes) {
11807
12251
  yield bytes;
@@ -11884,7 +12328,8 @@ var HttpHandles = class {
11884
12328
  if (port !== void 0) return { ok: false, errno: codeOf2(e), message: text };
11885
12329
  const status = e.status;
11886
12330
  if (text === NO_TRANSPORT || typeof status !== "number" || status === 502) {
11887
- const errno2 = text === NO_TRANSPORT ? "ENETUNREACH" : "ECONNREFUSED";
12331
+ const code = codeOf2(e);
12332
+ const errno2 = text === NO_TRANSPORT ? "ENETUNREACH" : code !== "EIO" ? code : "ECONNREFUSED";
11888
12333
  return { ok: false, errno: errno2, message: text };
11889
12334
  }
11890
12335
  entry.body = once(new TextEncoder().encode(text))[Symbol.asyncIterator]();
@@ -13477,7 +13922,7 @@ function isPtySyscall(req) {
13477
13922
  return req.op.startsWith("pty-");
13478
13923
  }
13479
13924
  function isVfsSyscall(req) {
13480
- return req.op.startsWith("fd-path-") || req.op === "fd-pread" || req.op === "fd-pwrite" || req.op === "fd-resize" || req.op === "fd-vfs-stat";
13925
+ return req.op.startsWith("fd-path-") || req.op === "fd-pread" || req.op === "fd-pwrite" || req.op === "fd-resize" || req.op === "fd-vfs-stat" || req.op === "fd-chmod" || req.op === "fd-chown" || req.op === "fd-utimes";
13481
13926
  }
13482
13927
  function isFdSyscall(req) {
13483
13928
  return req.op.startsWith("fd-");
@@ -13497,6 +13942,9 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
13497
13942
  "fd-pwrite",
13498
13943
  "fd-resize",
13499
13944
  "fd-vfs-stat",
13945
+ "fd-chmod",
13946
+ "fd-chown",
13947
+ "fd-utimes",
13500
13948
  "fd-path-flush",
13501
13949
  "fd-path-unlinking",
13502
13950
  "fd-path-unlinked",
@@ -13527,6 +13975,7 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
13527
13975
  "fd-tty-owner",
13528
13976
  "fd-open-sealed",
13529
13977
  "fd-open-clipboard",
13978
+ "fd-clipboard-abort",
13530
13979
  "tty-get",
13531
13980
  "tty-set",
13532
13981
  "tty-winsz",
@@ -13872,7 +14321,11 @@ var WasmProcess = class {
13872
14321
  if (!stderr?.write || stderr.clipboard) return;
13873
14322
  const line = `${name2}: /dev/clipboard: ${clipboardLine(err.detail)}
13874
14323
  `;
13875
- await stderr.write(new TextEncoder().encode(line), AbortSignal.timeout(1e3)).catch(() => 0);
14324
+ const timeout = new Promise((r) => setTimeout(() => r(0), 1e3));
14325
+ await Promise.race([
14326
+ stderr.write(new TextEncoder().encode(line), AbortSignal.timeout(1e3)).catch(() => 0),
14327
+ timeout
14328
+ ]);
13876
14329
  }
13877
14330
  async write(fd, body, nonblock = false, cancelled) {
13878
14331
  const file = this.fds.get(fd).file;
@@ -13899,10 +14352,13 @@ var WasmProcess = class {
13899
14352
  await refuseReadonly(this.options.fs, req.path, req.flags, req);
13900
14353
  if (this.writesAt(req)) await this.options.fs.mayChange?.(req.path, !req.exclusive);
13901
14354
  await this.mayOpen(req);
14355
+ const marked = await this.markedPath(req);
14356
+ await this.sourceOpen(marked === void 0 ? req : { ...req, path: marked });
14357
+ const fs = marked === void 0 ? this.options.fs : this.options.noLinks;
13902
14358
  const fd = this.fds.install(heldFile(), 3);
13903
14359
  try {
13904
14360
  const nodes = this.nodes;
13905
- const path = req.orphan ? req.path : req.exclusive ? await nodes.entryKey(req.path) : req.create ? await nodes.targetKey(req.path) : await this.keptOr(await nodes.targetKey(req.path), req.handle);
14361
+ const path = await this.openPath(req, marked);
13906
14362
  if (inProc(path)) {
13907
14363
  this.fds.installAt(fd, await this.procFile(req, path));
13908
14364
  return fd;
@@ -13912,23 +14368,23 @@ var WasmProcess = class {
13912
14368
  const plain = this.plainMode(req.mode);
13913
14369
  if (req.exclusive) {
13914
14370
  await nodes.createExclusive(path, this.newOwner(req.mode));
13915
- if (plain !== void 0) await this.options.fs.chmod?.(path, plain);
13916
- this.fds.installAt(fd, this.openVfsFile(open));
14371
+ if (plain !== void 0) await fs.chmod?.(path, plain);
14372
+ this.fds.installAt(fd, this.openVfsFile(open, void 0, fs));
13917
14373
  } else if (req.create) {
13918
14374
  await nodes.onEntry(path, async () => {
13919
14375
  const marks = this.options.owners || plain !== void 0;
13920
- const fresh2 = marks && !await present2(this.options.fs, path, true);
13921
- this.fds.installAt(fd, this.openVfsFile(open, await nodes.identify(path)));
14376
+ const fresh2 = marks && !await present2(fs, path, true);
14377
+ this.fds.installAt(fd, this.openVfsFile(open, await nodes.identify(path), fs));
13922
14378
  await this.fds.get(fd).file.stat?.();
13923
- if (fresh2 && plain !== void 0) await this.options.fs.chmod?.(path, plain);
14379
+ if (fresh2 && plain !== void 0) await fs.chmod?.(path, plain);
13924
14380
  else if (fresh2) {
13925
- await this.options.fs.chown?.(path, { ...this.newOwner(req.mode), kind: "new" });
14381
+ await fs.chown?.(path, { ...this.newOwner(req.mode), kind: "new" });
13926
14382
  }
13927
14383
  });
13928
14384
  } else {
13929
- this.fds.installAt(fd, this.openVfsFile(open, await nodes.identify(path)));
14385
+ this.fds.installAt(fd, this.openVfsFile(open, await nodes.identify(path), fs));
13930
14386
  }
13931
- if (req.existing && !await present2(this.options.fs, path, true)) {
14387
+ if (req.existing && !await present2(fs, path, true)) {
13932
14388
  throw new KernelError("ENOENT");
13933
14389
  }
13934
14390
  } catch (err) {
@@ -13962,6 +14418,27 @@ var WasmProcess = class {
13962
14418
  writesAt(req) {
13963
14419
  return (req.flags & WRITE_ACCESS) !== 0 || req.truncate === true || req.create === true || req.exclusive === true;
13964
14420
  }
14421
+ async sourceOpen(req) {
14422
+ const { source } = this.options;
14423
+ const inherited2 = req.orphan || req.contents !== void 0 || req.handle !== void 0;
14424
+ if (!source || inherited2 || req.pin !== void 0 || req.exclusive) return;
14425
+ await source.open(req.path);
14426
+ }
14427
+ async markedPath(req) {
14428
+ const { guard: guard2, noLinks } = this.options;
14429
+ const inherited2 = req.orphan || req.contents !== void 0 || req.handle !== void 0;
14430
+ if (guard2?.links === void 0 || !noLinks || inherited2 || req.pin !== void 0)
14431
+ return void 0;
14432
+ return (await walk(guard2.fs, this.cred, req.path, !req.exclusive, guard2.links)).path;
14433
+ }
14434
+ async openPath(req, marked) {
14435
+ const nodes = this.nodes;
14436
+ if (req.orphan) return req.path;
14437
+ if (req.exclusive) return nodes.entryKey(marked ?? req.path);
14438
+ if (marked !== void 0) return marked;
14439
+ if (req.create) return nodes.targetKey(req.path);
14440
+ return this.keptOr(await nodes.targetKey(req.path), req.handle);
14441
+ }
13965
14442
  async mayOpen(req) {
13966
14443
  const { guard: guard2 } = this.options;
13967
14444
  const inherited2 = req.orphan || req.contents !== void 0 || req.handle !== void 0;
@@ -14061,9 +14538,9 @@ var WasmProcess = class {
14061
14538
  );
14062
14539
  return memoryFile(tree2.read(path), path, req.position);
14063
14540
  }
14064
- openVfsFile(req, id2) {
14541
+ openVfsFile(req, id2, fs = this.options.fs) {
14065
14542
  return vfsFile(
14066
- this.options.fs,
14543
+ fs,
14067
14544
  {
14068
14545
  path: req.path,
14069
14546
  flags: req.flags,
@@ -14146,7 +14623,7 @@ var WasmProcess = class {
14146
14623
  return { ok: true, kind: "void" };
14147
14624
  case "fd-renumber":
14148
14625
  if (req.from !== req.to) {
14149
- this.fds.dup2(req.from, req.to);
14626
+ await this.fds.dup2Clipboard(req.from, req.to);
14150
14627
  if (!req.keep) await Promise.resolve(this.fds.close(req.from));
14151
14628
  }
14152
14629
  return { ok: true, kind: "void" };
@@ -14158,6 +14635,11 @@ var WasmProcess = class {
14158
14635
  return { ok: true, kind: "json", json: this.openSealed(req.flags) };
14159
14636
  case "fd-open-clipboard":
14160
14637
  return { ok: true, kind: "json", json: await this.openClipboard(req.flags) };
14638
+ case "fd-clipboard-abort": {
14639
+ const file = this.fds.get(req.fd);
14640
+ file.file.clipboard?.abort();
14641
+ return { ok: true, kind: "void" };
14642
+ }
14161
14643
  case "fd-tty-owner":
14162
14644
  return { ok: true, kind: "json", json: this.terminalByName(req.name).owner ?? null };
14163
14645
  case "fd-select": {
@@ -14210,12 +14692,23 @@ var WasmProcess = class {
14210
14692
  case "fd-resize":
14211
14693
  await file.resize(req.size);
14212
14694
  return { ok: true, kind: "void" };
14213
- case "fd-vfs-stat":
14214
- return {
14215
- ok: true,
14216
- kind: "json",
14217
- json: req.identity ? await this.identified(file.stat.bind(file), file.known?.bind(file)) : await file.stat()
14218
- };
14695
+ case "fd-vfs-stat": {
14696
+ if (req.identity) {
14697
+ return {
14698
+ ok: true,
14699
+ kind: "json",
14700
+ json: await this.identified(file.stat.bind(file), file.known?.bind(file))
14701
+ };
14702
+ }
14703
+ const vfs = await file.stat();
14704
+ if (!req.full) return { ok: true, kind: "json", json: vfs };
14705
+ const st = await this.heldStat(vfs).catch(() => void 0);
14706
+ return { ok: true, kind: "json", json: { ...vfs, ...st ? { stat: st } : {} } };
14707
+ }
14708
+ case "fd-chmod":
14709
+ case "fd-chown":
14710
+ case "fd-utimes":
14711
+ return this.fdMetadata(await file.stat(), req);
14219
14712
  }
14220
14713
  }
14221
14714
  keptPath;
@@ -14236,7 +14729,7 @@ var WasmProcess = class {
14236
14729
  async promote(req) {
14237
14730
  if (!this.fds.get(req.fd).file.held) throw new KernelError("EBADF");
14238
14731
  if (req.share !== void 0) {
14239
- this.fds.dup2(req.share, req.fd);
14732
+ await this.fds.dup2Clipboard(req.share, req.fd);
14240
14733
  return;
14241
14734
  }
14242
14735
  if (req.path === void 0) throw new KernelError("EINVAL");
@@ -14265,6 +14758,46 @@ var WasmProcess = class {
14265
14758
  this.fds.installAt(fd, heldFile(meta));
14266
14759
  return fd;
14267
14760
  }
14761
+ heldPath(vfs) {
14762
+ if (!vfs.path) throw new KernelError("EBADF");
14763
+ if (vfs.orphan) throw new KernelError("EOPNOTSUPP");
14764
+ return vfs.path;
14765
+ }
14766
+ heldFs() {
14767
+ return this.options.guard?.fs ?? this.options.fs;
14768
+ }
14769
+ async heldStat(vfs) {
14770
+ const stat = this.heldFs().stat;
14771
+ if (!stat) throw new KernelError("ENOSYS");
14772
+ return stat.call(this.heldFs(), this.heldPath(vfs));
14773
+ }
14774
+ async fdMetadata(vfs, req) {
14775
+ const path = this.heldPath(vfs);
14776
+ const fs = this.heldFs();
14777
+ const g = this.guarded();
14778
+ if (req.op === "fd-chown") {
14779
+ const ids = {
14780
+ ...req.uid !== void 0 ? { uid: req.uid } : {},
14781
+ ...req.gid !== void 0 ? { gid: req.gid } : {}
14782
+ };
14783
+ if (!this.options.owners || !fs.chown || !fs.stat) await this.heldStat(vfs);
14784
+ else await chownAs(fs, this.cred, path, { ...ids, follow: true });
14785
+ return { ok: true, kind: "void" };
14786
+ }
14787
+ const st = await this.heldStat(vfs);
14788
+ if (req.op === "fd-chmod") {
14789
+ if (!fs.chmod) throw new KernelError("ENOSYS");
14790
+ await fs.chmod(path, g ? heldChmodMode(g, path, st, req.mode) : req.mode);
14791
+ return { ok: true, kind: "void" };
14792
+ }
14793
+ if (!fs.utimes) throw new KernelError("ENOSYS");
14794
+ if (g) heldRetimable(g, path, st, req.now === true);
14795
+ const at = (ms, kept) => ms === null ? kept : req.now ? /* @__PURE__ */ new Date() : new Date(ms);
14796
+ const atime = at(req.atimeMs, st.atime);
14797
+ const mtime = at(req.mtimeMs, st.mtime);
14798
+ await fs.utimes(path, atime, mtime);
14799
+ return { ok: true, kind: "void" };
14800
+ }
14268
14801
  fdInfo(fd) {
14269
14802
  const file = this.fds.get(fd).file;
14270
14803
  const flags2 = this.fds.statusFlags(fd);
@@ -14483,7 +15016,10 @@ var WasmProcess = class {
14483
15016
  const { file, argv, env, cwd, cred, caller } = answer.grant;
14484
15017
  const stdio = [{ fd: 0 }, { fd: 1 }, { fd: 2 }];
14485
15018
  return {
14486
- pid: await this.children.spawn({ file, argv, env, cwd }, stdio, [], { cred, caller })
15019
+ pid: await this.children.spawn({ file, argv, env, cwd, pgid: 0 }, stdio, [], {
15020
+ cred,
15021
+ caller
15022
+ })
14487
15023
  };
14488
15024
  }
14489
15025
  async mountSyscall(req, cancelled) {
@@ -14787,7 +15323,9 @@ function holdingToken(opts, holds, caller) {
14787
15323
  ...opts.dac ? { dac: opts.dac, pid: opts.pid, created: caller.created } : {},
14788
15324
  ...opts.links !== void 0 ? { links: opts.links } : {},
14789
15325
  ...opts.fresh ? { fresh: (path) => opts.fresh?.(path, caller.umask()) } : {},
15326
+ ...opts.source ? { source: opts.source } : {},
14790
15327
  fs: opts.fs,
15328
+ noLinks: opts.noLinks,
14791
15329
  cwd: opts.cwd,
14792
15330
  ...opts.statfs ? { statfs: opts.statfs } : {},
14793
15331
  hold: (path, on, open, real, handle) => holds.hold(path, on, open, real, handle),
@@ -14813,6 +15351,8 @@ function processOptions(opts, header4, hooks, created3) {
14813
15351
  ...opts.outputFilter ? { outputFilter: opts.outputFilter } : {},
14814
15352
  ...opts.clipboard ? { clipboard: opts.clipboard, ...clipboardName(opts) } : {},
14815
15353
  ...opts.readonlyAt ? { readonlyAt: opts.readonlyAt } : {},
15354
+ ...opts.source ? { source: opts.source } : {},
15355
+ noLinks: opts.noLinks,
14816
15356
  ...opts.dac ? { guard: { dac: opts.dac, fs: opts.fs, created: created3, ...linksOf(opts) } } : {},
14817
15357
  ...opts.identity ? { identity: opts.identity } : {},
14818
15358
  ...opts.onSyscall ? { onSyscall: opts.onSyscall } : {},
@@ -14868,8 +15408,8 @@ function threadInit(opts, program, sab, cred, thread, main) {
14868
15408
  function spawnWasmProcess(opts) {
14869
15409
  const sab = new SharedArrayBuffer(SAB_HEADER_BYTES + SAB_DEFAULT_WINDOW_BYTES);
14870
15410
  const header4 = new Int32Array(sab, 0, SAB_HEADER_I32);
14871
- if (opts.blocked && opts.program.abi === "wasi")
14872
- Atomics.store(header4, SAB_I_BLOCKED, opts.blocked);
15411
+ const blocked = opts.fork?.wasi?.blocked ?? opts.blocked;
15412
+ if (blocked && anyBlocked(blocked) && opts.program.abi === "wasi") storeBlocked(header4, blocked);
14873
15413
  const threadSignals = /* @__PURE__ */ new Map();
14874
15414
  const created3 = new Created();
14875
15415
  const process2 = new WasmProcess(
@@ -14884,7 +15424,7 @@ function spawnWasmProcess(opts) {
14884
15424
  ...opts.program.abi === "js" ? {
14885
15425
  exitNow: (code) => exitNow(code),
14886
15426
  exitSignal: (sig) => queueMicrotask(() => finish(128 + sig, sig))
14887
- } : {}
15427
+ } : { exitSignal: (sig) => terminate(128 + sig, sig) }
14888
15428
  },
14889
15429
  created3
14890
15430
  )
@@ -14970,7 +15510,7 @@ function spawnWasmProcess(opts) {
14970
15510
  const tsab = new SharedArrayBuffer(SAB_HEADER_BYTES + SAB_DEFAULT_WINDOW_BYTES);
14971
15511
  const tw = opts.createWorker();
14972
15512
  const tsignals = threadSignalsOf(tsab);
14973
- if (thread.blocked) Atomics.store(tsignals.header, SAB_I_BLOCKED, thread.blocked);
15513
+ if (thread.blocked) storeBlocked(tsignals.header, thread.blocked);
14974
15514
  threadSignals.set(thread.tid, tsignals);
14975
15515
  const tdispatch = (req) => dying.serving(
14976
15516
  () => isWasmSyscall(req) ? process2.syscall(req, threadCancel(tsignals)) : dispatchSyncFs(req)
@@ -14998,12 +15538,13 @@ function spawnWasmProcess(opts) {
14998
15538
  processInit(opts, sab, process2.umask, copyCred(process2.cred), lane?.lane),
14999
15539
  transferOf(opts, lane?.lane)
15000
15540
  );
15001
- const signal = (sig) => {
15002
- if (process2.signal(sig) !== "terminate") return;
15003
- const code = sig === SIG.KILL ? 137 : 128 + sig;
15541
+ const terminate = (code, sig) => {
15004
15542
  if (opts.program.abi !== "wasi" || done4) finish(code, sig);
15005
15543
  else dying.start(code, sig, header4, process2, () => finish(code, sig));
15006
15544
  };
15545
+ const signal = (sig) => {
15546
+ if (process2.signal(sig) === "terminate") terminate(sig === SIG.KILL ? 137 : 128 + sig, sig);
15547
+ };
15007
15548
  const kill = (code = 137) => finish(code, code > 128 && code < 160 ? code - 128 : void 0);
15008
15549
  return {
15009
15550
  pid: opts.pid,
@@ -15014,7 +15555,7 @@ function spawnWasmProcess(opts) {
15014
15555
  onState: (listener) => process2.onState(listener),
15015
15556
  memory: () => Atomics.load(header4, SAB_I_MEMORY) * 65536,
15016
15557
  ignoredSignals: (fork) => process2.inheritable(fork),
15017
- blockedSignals: () => opts.program.abi === "wasi" ? Atomics.load(header4, SAB_I_BLOCKED) : 0,
15558
+ blockedSignals: () => opts.program.abi === "wasi" ? loadBlocked(header4) : [0, 0],
15018
15559
  signalMasks: () => process2.signalMasks(),
15019
15560
  umask: () => process2.umask,
15020
15561
  cred: () => copyCred(process2.cred),
@@ -18223,14 +18764,16 @@ var SUDO_COMMAND = {
18223
18764
  var SUDO_SOURCE = [
18224
18765
  "export default async function sudo(ctx) {",
18225
18766
  " const say = (fd, text) => ctx.write(fd, text).catch(() => undefined);",
18767
+ ' const RELAYED = ["SIGTERM", "SIGHUP", "SIGINT", "SIGQUIT", "SIGUSR1", "SIGUSR2", "SIGALRM"];',
18768
+ " const NUMBER = { SIGTERM: 15, SIGHUP: 1, SIGINT: 2, SIGQUIT: 3, SIGUSR1: 10, SIGUSR2: 12, SIGALRM: 14 };",
18226
18769
  " let pid;",
18227
18770
  " let held;",
18228
18771
  " const relay = (sig) => {",
18229
18772
  " if (pid === undefined) held = sig;",
18230
- " else return ctx.kill(pid, sig).catch(() => undefined);",
18773
+ " else return ctx.kill(-pid, sig).catch(() => undefined);",
18231
18774
  " };",
18232
- ' for (const sig of ["SIGTERM", "SIGHUP"]) await ctx.signals.on(sig, relay);',
18233
- " const failed = () => (held === undefined ? 1 : 128 + held);",
18775
+ " for (const sig of RELAYED) await ctx.signals.on(sig, () => relay(sig));",
18776
+ " const failed = () => (held === undefined ? 1 : 128 + NUMBER[held]);",
18234
18777
  " let answer;",
18235
18778
  " try {",
18236
18779
  " answer = await ctx.sudo(ctx.argv.slice(1));",
@@ -18246,18 +18789,41 @@ var SUDO_SOURCE = [
18246
18789
  " await say(1, answer.list);",
18247
18790
  " return 0;",
18248
18791
  " }",
18249
- ' for (const sig of ["SIGINT", "SIGQUIT"]) await ctx.signals.on(sig, () => undefined);',
18250
18792
  " if (answer.run !== undefined) {",
18251
18793
  " try {",
18252
- " pid = (await ctx.spawn({ argv: answer.run })).pid;",
18794
+ " pid = (await ctx.spawn({ argv: answer.run, pgid: 0 })).pid;",
18253
18795
  " } catch (err) {",
18254
18796
  ' const why = err && err.code === "ENOENT" ? "command not found" : String(err && err.code);',
18255
18797
  ' await say(2, "sudo: " + answer.run[0] + ": " + why + "\\n");',
18256
18798
  " return failed();",
18257
18799
  " }",
18258
18800
  " } else pid = answer.pid;",
18801
+ ' const stat = () => new TextDecoder().decode(ctx.sync.fs.readFile("/proc/" + ctx.pid + "/stat"));',
18802
+ ' const mine = Number(stat().replace(/^.*\\) /, "").split(" ")[2]);',
18803
+ " const foreground = () => ctx.tty.foreground(0).catch(() => undefined);",
18804
+ " const owned = (await foreground()) === mine;",
18805
+ " const handOver = async () => {",
18806
+ " if ((await foreground()) === mine) await ctx.tty.setForeground(0, pid).catch(() => undefined);",
18807
+ " };",
18808
+ " const takeBack = async () => {",
18809
+ " if (owned && (await foreground()) === pid) await ctx.tty.setForeground(0, mine).catch(() => undefined);",
18810
+ " };",
18811
+ " if (owned) await handOver();",
18259
18812
  " if (held !== undefined) await relay(held);",
18260
- " return (await ctx.wait(pid)).status;",
18813
+ " for (;;) {",
18814
+ " const ended = await ctx.wait(pid, { untraced: true });",
18815
+ " if (!ended.stopped) {",
18816
+ " await takeBack();",
18817
+ " return ended.status;",
18818
+ " }",
18819
+ " const reading = ended.signal === 21 || ended.signal === 22;",
18820
+ " if (!(reading && (await foreground()) === mine)) {",
18821
+ " await takeBack();",
18822
+ ' ctx.sync.kill(ctx.pid, ended.signal === 19 ? "SIGTSTP" : ended.signal);',
18823
+ " }",
18824
+ " await handOver();",
18825
+ ' await ctx.kill(-pid, "SIGCONT").catch(() => undefined);',
18826
+ " }",
18261
18827
  "}",
18262
18828
  ""
18263
18829
  ].join("\n");
@@ -18273,6 +18839,7 @@ var WASM_MAGIC = [0, 97, 115, 109];
18273
18839
  var SHEBANG_MAX = 256;
18274
18840
  var MAX_INTERPRETERS = 4;
18275
18841
  var NOT_FOUND = 127;
18842
+ var NOT_EXECUTABLE = 126;
18276
18843
  var DECIDE_MS = 100;
18277
18844
  var INIT_PID = 1;
18278
18845
  var SHARED_DIRS = ["/tmp", "/home"];
@@ -18422,6 +18989,9 @@ function addTimes(into, pid, t) {
18422
18989
  var Launcher = class {
18423
18990
  fs;
18424
18991
  processFs;
18992
+ noLinksFs;
18993
+ mounted;
18994
+ protection;
18425
18995
  prefixes;
18426
18996
  writerTags;
18427
18997
  unguardedRoot;
@@ -18477,6 +19047,9 @@ var Launcher = class {
18477
19047
  hostname;
18478
19048
  watchers = new FsWatchers();
18479
19049
  pnpmHome;
19050
+ pnpmChain;
19051
+ pnpmMade;
19052
+ pnpmEpoch = 0;
18480
19053
  mounts;
18481
19054
  createDriverWorker;
18482
19055
  media;
@@ -18527,11 +19100,14 @@ var Launcher = class {
18527
19100
  this.mounts = new MountTable({
18528
19101
  open: (type, spec) => this.driver(type, spec),
18529
19102
  busy: (target) => [...this.openFiles].some((nodes) => nodes.holds(target)) || heldUnder(this.held, target),
18530
- changed: this.watchers.changed.bind(this.watchers),
19103
+ changed: (...paths) => {
19104
+ if (paths.some((path) => this.pnpmChain.has(path))) this.pnpmStale();
19105
+ this.watchers.changed(...paths);
19106
+ },
18531
19107
  owners: this.owners
18532
19108
  });
18533
- const watched = this.watchers.wrap(this.mounts.wrap(options.fs));
18534
- this.base = this.owners ? this.freshModules.wrap(watched) : watched;
19109
+ this.mounted = this.mounts.wrap(options.fs);
19110
+ this.base = this.rooted(this.mounted);
18535
19111
  this.createWorker = options.createWorker;
18536
19112
  this.createDriverWorker = options.createDriverWorker;
18537
19113
  this.media = options.media ?? memoryMedia();
@@ -18540,6 +19116,7 @@ var Launcher = class {
18540
19116
  this.fstabRetries = options.fstabRetries ?? FSTAB_RETRIES;
18541
19117
  this.processMounts = options.processMounts ?? true;
18542
19118
  this.pnpmHome = options.env?.PNPM_HOME ?? SYSTEM_PNPM_HOME;
19119
+ this.pnpmChain = new Set(chain(normalizePath(this.pnpmHome)));
18543
19120
  this.modulesDir = options.modules ?? "/node_modules";
18544
19121
  this.appended = appendedRoots(options.commands);
18545
19122
  this.cdp = new CdpHosts(options.cdp);
@@ -18568,12 +19145,7 @@ var Launcher = class {
18568
19145
  });
18569
19146
  enableCdp(this.net, this.cdp);
18570
19147
  this.facades.enable(this.net);
18571
- const fs = withCommandDirs(
18572
- this.base,
18573
- async () => new Set((await this.commands()).keys()),
18574
- (dir) => this.userCommandDir(dir),
18575
- this.owners
18576
- );
19148
+ const fs = this.commandFs(this.base);
18577
19149
  this.nodes = new VfsNodes(fs, (path) => this.released(path));
18578
19150
  this.nodes.keepsOnDisk = true;
18579
19151
  this.openFiles.add(this.nodes);
@@ -18589,15 +19161,23 @@ var Launcher = class {
18589
19161
  ...this.owners ? { dirOwner: { uid: 0, gid: 0, mode: 448 } } : {}
18590
19162
  }
18591
19163
  );
18592
- this.fs = this.unlinked.wrap(keepingOpen(fs, this.nodes));
19164
+ this.fs = this.processChain(fs, this.nodes, this.unlinked);
18593
19165
  this.writerTags = writerTags(options.protect);
18594
19166
  this.unlinkOwnerOption = options.unlinkOwner;
18595
19167
  if (this.writerTags.size > 0 && options.fs.protect && !options.fs.unguarded) {
18596
19168
  throw new TypeError("protect.writers needs a root file system with an unguarded view");
18597
19169
  }
19170
+ if (!options.fs.noLinks) throw new TypeError("the root file system needs a noLinks view");
18598
19171
  this.unguardedRoot = options.fs.unguarded?.() ?? options.fs;
18599
19172
  this.prefixes = protectedPrefixes(options.protect?.paths ?? []);
18600
- this.processFs = this.prefixes.length > 0 ? new ProtectedFs(this.prefixes).view(this.fs) : this.fs;
19173
+ this.protection = this.prefixes.length > 0 ? new ProtectedFs(this.prefixes) : void 0;
19174
+ this.processFs = this.protection?.view(this.fs) ?? this.fs;
19175
+ const noLinks = this.processChain(
19176
+ this.commandFs(this.rooted(this.noLinksOf(this.mounted))),
19177
+ this.nodes,
19178
+ this.unlinked
19179
+ );
19180
+ this.noLinksFs = this.protection?.view(noLinks) ?? noLinks;
18601
19181
  const integrity = options.protect?.integrity;
18602
19182
  integrityOptions(integrity);
18603
19183
  options.fs.protect?.(this.prefixes, Boolean(integrity));
@@ -19009,10 +19589,15 @@ var Launcher = class {
19009
19589
  }
19010
19590
  async unrunnable(file, cwd, scope) {
19011
19591
  const path = this.fs.resolvePath(cwd, file);
19592
+ if (await this.directory(file, path)) return "EACCES";
19012
19593
  const head = await this.base.readFileBuffer(path).catch(() => null);
19013
19594
  if (!head || head[0] === 35 && head[1] === 33) return "ENOENT";
19014
19595
  return binfmtOf(await this.interpreters(scope), path) ? "ENOENT" : "ENOEXEC";
19015
19596
  }
19597
+ async directory(file, path) {
19598
+ if (!file.includes("/") || COMMAND2.test(file)) return false;
19599
+ return (await this.base.stat(path).catch(() => void 0))?.isDirectory === true;
19600
+ }
19016
19601
  verifiedFs(fs) {
19017
19602
  const read = (path) => this.verified(path, KERNEL_CALLER, fs);
19018
19603
  return Object.assign(Object.create(fs), {
@@ -19138,6 +19723,7 @@ var Launcher = class {
19138
19723
  delete env[TICKET_ENV];
19139
19724
  env.HOSTNAME = this.hostname;
19140
19725
  let program;
19726
+ await this.pnpmReady();
19141
19727
  try {
19142
19728
  const caller = { pid: req.ppid ?? 0, uid: req.cred.euid, tag: req.tag };
19143
19729
  if (this.integrity) await this.checkedSources(planned, caller);
@@ -19152,10 +19738,29 @@ var Launcher = class {
19152
19738
  return this.jobs.sessionOf(pgid);
19153
19739
  }
19154
19740
  loopbackFor(pid) {
19155
- return new KernelLoopback(this.net, () => {
19156
- const cred = this.credOf(pid);
19157
- return { pid, uid: cred.euid, gid: cred.egid };
19741
+ return new KernelLoopback(
19742
+ this.net,
19743
+ () => {
19744
+ const cred = this.credOf(pid);
19745
+ return { pid, uid: cred.euid, gid: cred.egid };
19746
+ },
19747
+ () => this.connectorStamp(pid)
19748
+ );
19749
+ }
19750
+ sourceCheck(pid, writer) {
19751
+ const { integrity } = this;
19752
+ if (!integrity?.readsChecked || writer) return {};
19753
+ const caller = () => ({
19754
+ pid,
19755
+ uid: this.credOf(pid).euid,
19756
+ tag: this.tagOf(pid)
19158
19757
  });
19758
+ return {
19759
+ source: {
19760
+ read: (path, plain) => integrity.readSource(path, caller(), plain),
19761
+ open: (path, real) => integrity.openSource(path, caller(), real)
19762
+ }
19763
+ };
19159
19764
  }
19160
19765
  treeScrubs(req) {
19161
19766
  const inherited2 = req.ppid !== void 0 ? this.scrubs.get(req.ppid) : void 0;
@@ -19192,11 +19797,12 @@ var Launcher = class {
19192
19797
  },
19193
19798
  settling: () => this.settling.isSettling(pid),
19194
19799
  ...req.ignored ? { ignored: req.ignored } : {},
19195
- ...req.blocked ? { blocked: req.blocked } : {},
19800
+ ...anyBlocked(req.blocked) ? { blocked: req.blocked } : {},
19196
19801
  ...req.umask !== void 0 ? { umask: req.umask } : {},
19197
19802
  cred: req.cred,
19198
19803
  ...this.owners && req.program.abi === "wasi" ? { fresh: (path, umask) => this.freshModules.add(path, umask, pid) } : {},
19199
19804
  ...this.owners ? { owners: true } : {},
19805
+ ...this.sourceCheck(pid, stack.writer),
19200
19806
  ...linksOf2(req),
19201
19807
  sealed: this.sealed,
19202
19808
  ...scrubs ? { outputFilter: (file) => scrubbedAll(file, scrubs) } : {},
@@ -19209,6 +19815,7 @@ var Launcher = class {
19209
19815
  cwd: req.cwd,
19210
19816
  fds: req.fds,
19211
19817
  fs: stack.fs,
19818
+ noLinks: stack.noLinks,
19212
19819
  ...this.prefixes.length > 0 && !stack.writer ? { readonlyAt: (path) => isProtected(this.prefixes, path) } : {},
19213
19820
  createWorker: this.createWorker,
19214
19821
  onError: req.report,
@@ -19286,6 +19893,11 @@ var Launcher = class {
19286
19893
  return { shownPid: this.jobs.shown(req.ppid), ...ppid !== void 0 ? { ppid } : {} };
19287
19894
  }
19288
19895
  forget(pid) {
19896
+ const image = this.jobs.list().find((m) => m.pid === pid)?.execParent;
19897
+ const described2 = this.described.get(pid);
19898
+ if (image !== void 0 && described2 && this.described.has(image)) {
19899
+ this.described.set(image, described2);
19900
+ }
19289
19901
  this.jobs.remove(pid);
19290
19902
  this.described.delete(pid);
19291
19903
  const spent = this.spent.get(pid);
@@ -19401,10 +20013,11 @@ var Launcher = class {
19401
20013
  const root = member && rootOf(member).pid;
19402
20014
  return root !== void 0 && this.running(root) ? root : INIT_PID;
19403
20015
  };
20016
+ const superseded = new Set([...members.values()].map((m) => m.execParent));
19404
20017
  const listed = [];
19405
20018
  for (const member of members.values()) {
19406
20019
  const described2 = this.described.get(member.pid);
19407
- if (member.execed || !described2) continue;
20020
+ if (member.execed && superseded.has(member.pid) || !described2) continue;
19408
20021
  const cred = this.credOf(member.pid);
19409
20022
  if (!maySee(viewer, cred)) continue;
19410
20023
  const root = rootOf(member);
@@ -19589,7 +20202,7 @@ var Launcher = class {
19589
20202
  }
19590
20203
  signalMasksOf(pid) {
19591
20204
  const masks = this.processes.get(pid)?.signalMasks?.();
19592
- const blocked = this.blockedBy(pid);
20205
+ const [blocked] = this.blockedBy(pid);
19593
20206
  const sigBlk = blocked ? { sigBlk: blocked } : {};
19594
20207
  return masks ? { sigIgn: masks.ignored, sigCgt: masks.caught, ...sigBlk } : sigBlk;
19595
20208
  }
@@ -19597,14 +20210,14 @@ var Launcher = class {
19597
20210
  return this.settling.isSettling(pid) || (this.ignoredBy(pid) & sigbit(SIG.TTOU)) !== 0;
19598
20211
  }
19599
20212
  blockedBy(pid) {
19600
- return this.processes.get(pid)?.blockedSignals?.() ?? 0;
20213
+ return this.processes.get(pid)?.blockedSignals?.() ?? [0, 0];
19601
20214
  }
19602
20215
  ignoredBy(pid, fork = false) {
19603
20216
  return this.processes.get(pid)?.ignoredSignals(fork) ?? 0;
19604
20217
  }
19605
20218
  kill(pid, sig, sender, from) {
19606
20219
  const session = from === void 0 ? void 0 : this.jobs.sidOf(from);
19607
- const may = (target) => !sender || maySignal(sender, this.credOf(target)) || sig === SIG.CONT && session !== void 0 && this.jobs.sidOf(target) === session || from !== void 0 && this.imageOf(from) === this.imageOf(target) || from !== void 0 && this.granted.get(this.jobs.shown(target)) === from;
20220
+ const may = (target) => !sender || maySignal(sender, this.credOf(target)) || sig === SIG.CONT && session !== void 0 && this.jobs.sidOf(target) === session || from !== void 0 && this.imageOf(from) === this.imageOf(target) || from !== void 0 && this.granted.get(this.jobs.shown(target)) === from || from !== void 0 && this.granted.get(this.jobs.pgidOf(target) ?? 0) === from;
19608
20221
  if (pid === -1) {
19609
20222
  const sender2 = from === void 0 ? void 0 : this.imageOf(from);
19610
20223
  return this.killAll(sig, (member) => this.imageOf(member) !== sender2, may);
@@ -19755,8 +20368,7 @@ var Launcher = class {
19755
20368
  this.fs
19756
20369
  );
19757
20370
  void this.unlinked.sweep(entry.target);
19758
- if (this.pnpmHome.startsWith(`${entry.target.replace(/\/$/, "")}/`))
19759
- await this.provisionPnpmHome();
20371
+ this.pnpmStale();
19760
20372
  return entry;
19761
20373
  }
19762
20374
  async ownTmp() {
@@ -19804,8 +20416,18 @@ var Launcher = class {
19804
20416
  if (path === "/") return path;
19805
20417
  return joinName(await realDir(this.base, parentOf(path)), baseName(path));
19806
20418
  }
19807
- async provisionPnpmHome() {
19808
- await this.base.mkdir(this.pnpmHome, { recursive: true }).catch(() => void 0);
20419
+ pnpmReady() {
20420
+ if (this.pnpmMade !== void 0) return this.pnpmMade;
20421
+ const epoch = this.pnpmEpoch;
20422
+ const made2 = this.base.mkdir(this.pnpmHome, { recursive: true }).then(() => this.owners ? this.stampLayout() : void 0).catch(() => {
20423
+ if (this.pnpmEpoch === epoch) this.pnpmMade = void 0;
20424
+ });
20425
+ this.pnpmMade = made2;
20426
+ return made2;
20427
+ }
20428
+ pnpmStale() {
20429
+ this.pnpmEpoch++;
20430
+ this.pnpmMade = void 0;
19809
20431
  }
19810
20432
  mountedBy(spec, cred) {
19811
20433
  if (!this.owners || cred.euid === 0 && cred.egid === 0) return spec;
@@ -19839,6 +20461,7 @@ var Launcher = class {
19839
20461
  umount(target, detach = false) {
19840
20462
  const at = normalizePath(target);
19841
20463
  this.mounts.umount(at, detach);
20464
+ this.pnpmStale();
19842
20465
  if (!detach) return;
19843
20466
  for (const nodes of this.openFiles) nodes.revoke(at);
19844
20467
  for (const holds of this.held) holds.revoke(at);
@@ -19904,7 +20527,7 @@ var Launcher = class {
19904
20527
  await this.resolvePrefixes();
19905
20528
  for (const dir of SHARED_DIRS) await this.base.mkdir(dir, { recursive: true });
19906
20529
  if (this.owners) await this.ownTmp();
19907
- await this.provisionPnpmHome();
20530
+ await this.pnpmReady();
19908
20531
  await this.users.ensure();
19909
20532
  if (this.owners) await this.stampLayout();
19910
20533
  if (this.owners) await this.sudoer.provision();
@@ -19967,19 +20590,20 @@ var Launcher = class {
19967
20590
  }
19968
20591
  stackFor(tag) {
19969
20592
  if (!this.isWriter(tag)) {
19970
- return { fs: this.processFs, nodes: this.nodes, unlinked: this.unlinked, writer: false };
20593
+ return {
20594
+ fs: this.processFs,
20595
+ noLinks: this.noLinksFs,
20596
+ nodes: this.nodes,
20597
+ unlinked: this.unlinked,
20598
+ writer: false
20599
+ };
19971
20600
  }
19972
20601
  this.writers ??= this.writerStack();
19973
20602
  return this.writers;
19974
20603
  }
19975
20604
  writerStack() {
19976
- const watched = this.watchers.wrap(this.mounts.wrap(this.unguardedRoot));
19977
- const fs = withCommandDirs(
19978
- this.owners ? this.freshModules.wrap(watched) : watched,
19979
- async () => new Set((await this.commands()).keys()),
19980
- (dir) => this.userCommandDir(dir),
19981
- this.owners
19982
- );
20605
+ const mounted = this.mounts.wrap(this.unguardedRoot);
20606
+ const fs = this.commandFs(this.rooted(mounted));
19983
20607
  const nodes = new VfsNodes(fs, (path) => this.released(path), this.nodes.entries);
19984
20608
  nodes.keepsOnDisk = true;
19985
20609
  this.openFiles.add(nodes);
@@ -19994,7 +20618,32 @@ var Launcher = class {
19994
20618
  ...this.owners ? { dirOwner: { uid: 0, gid: 0, mode: 448 } } : {}
19995
20619
  }
19996
20620
  );
19997
- return { fs: unlinked.wrap(keepingOpen(fs, nodes)), nodes, unlinked, writer: true };
20621
+ const noLinks = this.commandFs(this.rooted(this.noLinksOf(mounted)));
20622
+ return {
20623
+ fs: this.processChain(fs, nodes, unlinked),
20624
+ noLinks: this.processChain(noLinks, nodes, unlinked),
20625
+ nodes,
20626
+ unlinked,
20627
+ writer: true
20628
+ };
20629
+ }
20630
+ noLinksOf(mounted) {
20631
+ return mounted.noLinks.call(mounted);
20632
+ }
20633
+ rooted(mounted) {
20634
+ const watched = this.watchers.wrap(mounted);
20635
+ return this.owners ? this.freshModules.wrap(watched) : watched;
20636
+ }
20637
+ commandFs(root) {
20638
+ return withCommandDirs(
20639
+ root,
20640
+ async () => new Set((await this.commands()).keys()),
20641
+ (dir) => this.userCommandDir(dir),
20642
+ this.owners
20643
+ );
20644
+ }
20645
+ processChain(fs, nodes, unlinked) {
20646
+ return unlinked.wrap(keepingOpen(fs, nodes));
19998
20647
  }
19999
20648
  async starting(argv, dir, account, tag) {
20000
20649
  this.resetCatalogs();
@@ -20076,8 +20725,10 @@ var Launcher = class {
20076
20725
  const report = (message3) => stderr(encoder7.encode(`${message3}
20077
20726
  `));
20078
20727
  if (!planned) {
20079
- report(`${file}: command not found`);
20080
- return { status: NOT_FOUND, stdout: concat6(out), stderr: concat6(err) };
20728
+ const directory = await this.directory(file, this.fs.resolvePath(cwd, file));
20729
+ report(`${file}: ${directory ? "Is a directory" : "command not found"}`);
20730
+ const status2 = directory ? NOT_EXECUTABLE : NOT_FOUND;
20731
+ return { status: status2, stdout: concat6(out), stderr: concat6(err) };
20081
20732
  }
20082
20733
  const fds = new FdTable();
20083
20734
  fds.installAt(0, options.stdin ? bytesSource(options.stdin) : deviceFile("null"));
@@ -20100,6 +20751,8 @@ var Launcher = class {
20100
20751
  };
20101
20752
 
20102
20753
  // src/node/loopback.ts
20754
+ import { AsyncLocalStorage } from "node:async_hooks";
20755
+ import { subscribe } from "node:diagnostics_channel";
20103
20756
  import { lookup } from "node:dns/promises";
20104
20757
  import { isIP } from "node:net";
20105
20758
  var DEVTOOLS_PORTS = [9222, 9223, 9224, 9225, 9226, 9227, 9228, 9229];
@@ -20159,6 +20812,11 @@ function refusal(url, address, place) {
20159
20812
  const why = place === "link-local" ? `nodeTransport refuses link-local ${where}` : `nodeTransport refuses the host's loopback ${where} (see nodeTransport({ hostLoopback }))`;
20160
20813
  return Object.assign(new Error(why), { status: 403, code: "EACCES" });
20161
20814
  }
20815
+ function connectRefusal(address, port, place) {
20816
+ const where = `${address}:${port}`;
20817
+ const why = place === "link-local" ? `nodeTransport refuses link-local ${where}` : `nodeTransport refuses the host's loopback ${where} (see nodeTransport({ hostLoopback }))`;
20818
+ return Object.assign(new Error(why), { status: 403, code: "EACCES" });
20819
+ }
20162
20820
  async function addressesOf(host, resolve2) {
20163
20821
  const plain = host.replace(/^\[(.*)\]$/, "$1");
20164
20822
  if (isIP(plain)) return [plain];
@@ -20185,7 +20843,66 @@ function guarded2(check, signal) {
20185
20843
  });
20186
20844
  return Promise.race([check, aborted2]).finally(() => signal.removeEventListener("abort", stop));
20187
20845
  }
20846
+ var loopbackStore = new AsyncLocalStorage();
20847
+ var subscribed = false;
20848
+ function envProxyEnabled() {
20849
+ if (process.env.NODE_USE_ENV_PROXY === "1") return true;
20850
+ const opts = process.env.NODE_OPTIONS ?? "";
20851
+ const args = process.execArgv ?? [];
20852
+ return args.includes("--use-env-proxy") || opts.includes("--use-env-proxy");
20853
+ }
20854
+ function normaliseProxyUrl(url) {
20855
+ try {
20856
+ const parsed2 = new URL(url);
20857
+ const host = parsed2.hostname.replace(/^\[(.*)\]$/, "$1");
20858
+ const port = parsed2.port || (parsed2.protocol === "https:" ? "443" : "80");
20859
+ return `${host}:${port}`;
20860
+ } catch {
20861
+ return void 0;
20862
+ }
20863
+ }
20864
+ function proxyAddresses() {
20865
+ if (!envProxyEnabled()) return /* @__PURE__ */ new Set();
20866
+ const out = /* @__PURE__ */ new Set();
20867
+ for (const key of ["HTTPS_PROXY", "https_proxy", "HTTP_PROXY", "http_proxy"]) {
20868
+ const val = process.env[key];
20869
+ if (val) {
20870
+ const addr = normaliseProxyUrl(val);
20871
+ if (addr) out.add(addr);
20872
+ }
20873
+ }
20874
+ return out;
20875
+ }
20876
+ function ensureSubscribed() {
20877
+ if (subscribed) return;
20878
+ subscribed = true;
20879
+ subscribe("undici:client:connected", (message3) => {
20880
+ const entry = loopbackStore.getStore();
20881
+ if (!entry) return;
20882
+ const { socket, connectParams } = message3;
20883
+ const address = socket.remoteAddress;
20884
+ if (!address) {
20885
+ const err2 = connectRefusal("unknown", 0, "loopback");
20886
+ entry.refused = err2;
20887
+ socket.destroy(err2);
20888
+ return;
20889
+ }
20890
+ const proxies = proxyAddresses();
20891
+ if (proxies.size > 0 && connectParams.hostname) {
20892
+ const cpPort = connectParams.port || (connectParams.protocol === "https:" ? "443" : "80");
20893
+ if (proxies.has(`${connectParams.hostname}:${cpPort}`)) return;
20894
+ }
20895
+ const place = placeOf(address);
20896
+ if (!place) return;
20897
+ const port = socket.remotePort ?? 0;
20898
+ if (place === "loopback" && allows(entry.policy, port)) return;
20899
+ const err = connectRefusal(address, port, place);
20900
+ entry.refused = err;
20901
+ socket.destroy(err);
20902
+ });
20903
+ }
20188
20904
  function guardedLoopback(transport, policy, resolve2) {
20905
+ ensureSubscribed();
20189
20906
  const open = transport.websocket?.bind(transport);
20190
20907
  return {
20191
20908
  get traits() {
@@ -20193,12 +20910,22 @@ function guardedLoopback(transport, policy, resolve2) {
20193
20910
  },
20194
20911
  fetch: async (request, owner) => {
20195
20912
  await guarded2(checkDestination(request.url, policy, resolve2), request.signal);
20196
- return transport.fetch(request, owner);
20913
+ const entry = { policy };
20914
+ try {
20915
+ return await loopbackStore.run(entry, () => transport.fetch(request, owner));
20916
+ } catch (err) {
20917
+ throw entry.refused ?? err;
20918
+ }
20197
20919
  },
20198
20920
  ...open ? {
20199
20921
  websocket: async (request) => {
20200
20922
  await guarded2(checkDestination(request.url, policy, resolve2), request.signal);
20201
- return open(request);
20923
+ const entry = { policy };
20924
+ try {
20925
+ return await loopbackStore.run(entry, () => open(request));
20926
+ } catch (err) {
20927
+ throw entry.refused ?? err;
20928
+ }
20202
20929
  }
20203
20930
  } : {}
20204
20931
  };
@@ -20250,8 +20977,8 @@ var Chunks = class {
20250
20977
  const index = Math.floor((at + done4) / CHUNK4);
20251
20978
  const from = at + done4 - index * CHUNK4;
20252
20979
  const n = Math.min(CHUNK4 - from, bytes.length - done4);
20253
- const view = bytes.subarray(done4, done4 + n);
20254
- const part = view.buffer instanceof ArrayBuffer ? view : view.slice();
20980
+ const view2 = bytes.subarray(done4, done4 + n);
20981
+ const part = view2.buffer instanceof ArrayBuffer ? view2 : view2.slice();
20255
20982
  const old = this.blobs[index];
20256
20983
  if (from === 0 && n >= (old?.size ?? 0)) this.blobs[index] = new Blob([part]);
20257
20984
  else {