@ai-ecoverse/slicc-kernel 1.91.0 → 1.93.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/node.js CHANGED
@@ -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) {
@@ -9159,6 +9461,7 @@ async function storageUsage() {
9159
9461
  if (typeof estimate?.quota !== "number") return null;
9160
9462
  return { quota: estimate.quota, usage: estimate.usage ?? 0 };
9161
9463
  }
9464
+ var PLAIN_DIR_MODE = 493;
9162
9465
  var done = { ok: true, kind: "void" };
9163
9466
  function json2(value) {
9164
9467
  return { ok: true, kind: "json", json: value };
@@ -9168,6 +9471,27 @@ function ownerFor2(entry, mode) {
9168
9471
  if (!cred) return void 0;
9169
9472
  return ownerOf(cred, mode === void 0 ? void 0 : mode & ~(entry.umask?.() ?? 18));
9170
9473
  }
9474
+ function plainDirMode(entry, mode) {
9475
+ if (entry.cred || mode === void 0) return void 0;
9476
+ const wanted = mode & 4095 & ~(entry.umask?.() ?? 18);
9477
+ return wanted === PLAIN_DIR_MODE ? void 0 : wanted;
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
+ }
9171
9495
  function mkdirOptions(entry, mode = 511) {
9172
9496
  const owner = ownerFor2(entry, mode);
9173
9497
  if (!owner) return { recursive: true };
@@ -9179,23 +9503,39 @@ async function mkdirExclusive(entry, path, mode) {
9179
9503
  await entry.fs.mkdir(path, owner ? { owner } : {});
9180
9504
  }
9181
9505
  async function chown(entry, path, req) {
9506
+ const real = req.handle !== void 0 ? await keptOr(entry, path, req.handle) : path;
9182
9507
  const cred = entry.cred?.();
9183
9508
  if (!cred) {
9184
- 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));
9185
9510
  return done;
9186
9511
  }
9187
- await chownAs(entry.fs, cred, path, {
9512
+ await chownAs(entry.fs, cred, real, {
9188
9513
  ...req.uid !== void 0 ? { uid: req.uid } : {},
9189
9514
  ...req.gid !== void 0 ? { gid: req.gid } : {},
9190
9515
  follow: req.op === "chown"
9191
9516
  });
9192
9517
  return done;
9193
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
+ }
9194
9534
  async function run(entry, path, req) {
9195
9535
  const { fs, cwd } = entry;
9196
9536
  switch (req.op) {
9197
9537
  case "read":
9198
- return { ok: true, kind: "bytes", bytes: await fs.readFileBuffer(path) };
9538
+ return { ok: true, kind: "bytes", bytes: await readSource(entry, path) };
9199
9539
  case "write":
9200
9540
  await fs.writeFile(path, req.body ?? new Uint8Array(0), ownerFor2(entry, 438));
9201
9541
  return done;
@@ -9210,8 +9550,7 @@ async function run(entry, path, req) {
9210
9550
  case "readdir-stat":
9211
9551
  return json2(await readdirStat(fs, path));
9212
9552
  case "mkdir":
9213
- await (req.exclusive ? mkdirExclusive(entry, path, req.mode) : fs.mkdir(path, mkdirOptions(entry, req.mode)));
9214
- return done;
9553
+ return runMkdir(entry, path, req);
9215
9554
  case "rm":
9216
9555
  await fs.rm(path, { recursive: true });
9217
9556
  return done;
@@ -9237,7 +9576,7 @@ async function run(entry, path, req) {
9237
9576
  return done;
9238
9577
  case "create":
9239
9578
  await createExclusive(fs, path, ownerFor2(entry, req.mode ?? 438));
9240
- entry.created?.add(path);
9579
+ if (req.opening) entry.created?.add(path);
9241
9580
  entry.fresh?.(path);
9242
9581
  return done;
9243
9582
  case "readlink":
@@ -9248,10 +9587,10 @@ async function run(entry, path, req) {
9248
9587
  return done;
9249
9588
  }
9250
9589
  case "utimes":
9251
- await fs.utimes(path, new Date(req.atimeMs ?? 0), new Date(req.mtimeMs ?? 0));
9590
+ await fs.utimes(path, ...await timesOf(entry, path, req));
9252
9591
  return done;
9253
9592
  case "lutimes":
9254
- await lutimesOf(fs, path, new Date(req.atimeMs ?? 0), new Date(req.mtimeMs ?? 0));
9593
+ await lutimesOf(fs, path, ...await timesOf(entry, path, req));
9255
9594
  return done;
9256
9595
  case "chown":
9257
9596
  case "lchown":
@@ -9264,6 +9603,17 @@ async function run(entry, path, req) {
9264
9603
  return { ok: false, errno: "EINVAL", message: `sync-fs: unknown op '${req.op}'` };
9265
9604
  }
9266
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
+ }
9267
9617
  function guardOf(entry) {
9268
9618
  const cred = entry.dac && entry.cred?.();
9269
9619
  if (!cred || !entry.dac) return void 0;
@@ -9273,41 +9623,82 @@ function guardOf(entry) {
9273
9623
  var FOLLOWING = /* @__PURE__ */ new Set(["exists", "stat", "chown", "statfs"]);
9274
9624
  var LOOKS = /* @__PURE__ */ new Set(["exists", "stat", "lstat"]);
9275
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
+ }
9276
9634
  async function guard(g, path, req, cwd) {
9277
9635
  const { op } = req;
9278
- 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
+ }
9279
9640
  const other = () => g.fs.resolvePath(cwd, req.arg2 ?? "");
9280
9641
  switch (op) {
9281
9642
  case "readdir":
9282
- case "readdir-stat":
9283
- return listable(g, op, path);
9284
- case "mkdir":
9285
- 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
+ }
9286
9652
  case "create":
9287
9653
  case "symlink":
9288
- return inParent(g, op, path);
9654
+ await inParent(g, op, path);
9655
+ return { path: await walkSafe(g, path, op === "create") };
9289
9656
  case "unlink":
9290
- case "rmdir":
9291
- return inParent(g, op, path, true);
9292
- case "rm":
9293
- return removable(g, op, path);
9294
- case "rename":
9295
- return renamable(g, path, other());
9296
- case "link":
9297
- 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
+ }
9298
9682
  case "utimes":
9299
- case "lutimes":
9300
- 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
+ }
9301
9689
  case "read":
9302
9690
  case "pread":
9303
- 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) };
9304
9693
  case "pwrite":
9305
9694
  case "truncate":
9306
- 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) };
9307
9697
  case "write":
9308
- 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) };
9309
9700
  default:
9310
- return void 0;
9701
+ return { path: await walkSafe(g, path, true) };
9311
9702
  }
9312
9703
  }
9313
9704
  async function opening(entry, path, mode) {
@@ -9324,6 +9715,7 @@ async function holdOp(entry, path, req) {
9324
9715
  if (on && open) {
9325
9716
  await opening(entry, path, mode);
9326
9717
  const real = await entry.fs.realpath?.(path, true).catch(() => path);
9718
+ await entry.source?.open(path, real);
9327
9719
  return json2(entry.hold?.(path, on, open, real) ?? null);
9328
9720
  }
9329
9721
  entry.hold?.(path, on, open, void 0, req.handle);
@@ -9376,15 +9768,16 @@ async function mayChange(entry, path, req) {
9376
9768
  }
9377
9769
  async function guarded(entry, path, req) {
9378
9770
  const g = guardOf(entry);
9379
- if (!g) return void 0;
9380
- 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 {};
9381
9773
  try {
9382
9774
  await mayChange(entry, path, req);
9383
- await guard(g, path, req, entry.cwd);
9384
- return void 0;
9775
+ const paths = await guard(g, path, req, entry.cwd);
9776
+ return { paths };
9385
9777
  } catch (err) {
9386
9778
  const refusal2 = err.code === "EACCES";
9387
- return req.op === "exists" && refusal2 ? json2(false) : toErrno(err);
9779
+ const refused2 = req.op === "exists" && refusal2 ? json2(false) : toErrno(err);
9780
+ return { refused: refused2 };
9388
9781
  }
9389
9782
  }
9390
9783
  async function createExclusive(fs, path, owner) {
@@ -9410,6 +9803,7 @@ async function ranged(fs, path, req) {
9410
9803
  else await ops.truncate(path, req.length ?? 0);
9411
9804
  return done;
9412
9805
  }
9806
+ var CHOWN_OPS = /* @__PURE__ */ new Set(["chown", "lchown"]);
9413
9807
  var UNLINKED_OPS = /* @__PURE__ */ new Set(["read", "pread", "pwrite", "truncate"]);
9414
9808
  var OWN_OPS = /* @__PURE__ */ new Set(["rm", "unlink", "rmdir", "rename", "link"]);
9415
9809
  var TWO_PATHS = /* @__PURE__ */ new Set(["rename", "link"]);
@@ -9474,7 +9868,7 @@ async function rmPermitted(entry, path, req, refused2) {
9474
9868
  const st = await entry.fs.lstat(path).catch(() => void 0);
9475
9869
  if (!st?.isDirectory) return refused2;
9476
9870
  const unlisted = await guarded(entry, path, { ...req, op: "readdir" });
9477
- if (unlisted) return unlisted;
9871
+ if ("refused" in unlisted) return unlisted.refused;
9478
9872
  const names = await entry.fs.readdir(path).catch(() => void 0);
9479
9873
  if (!names) return refused2;
9480
9874
  let first;
@@ -9486,31 +9880,64 @@ async function rmPermitted(entry, path, req, refused2) {
9486
9880
  if (!child.ok) first ??= child;
9487
9881
  }
9488
9882
  if (first) return first;
9489
- const again = await guarded(entry, path, req);
9490
- 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;
9491
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
+ };
9910
+ }
9911
+ var REMOVALS = /* @__PURE__ */ new Set(["unlink", "rmdir", "rm"]);
9492
9912
  async function dispatchOne(entry, req) {
9493
9913
  const { fs, cwd } = entry;
9494
9914
  try {
9495
9915
  const path = fs.resolvePath(cwd, req.path);
9496
- if (req.op === "kept") return json2(entry.keptFor?.(req.handle ?? -1) !== void 0);
9497
- 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;
9498
9920
  if (req.op === "access") return await accessOp(entry, path, req);
9499
9921
  if (req.op === "hold") return await holdOp(entry, path, req);
9500
- const refused2 = await guarded(entry, path, req);
9501
- if (refused2 && req.op === "rm" && !refused2.ok && DENIED.has(refused2.errno)) {
9502
- return await rmPermitted(entry, path, req, refused2);
9503
- }
9504
- if (refused2) return refused2;
9505
- if (req.op === "statfs") return json2(await usageAt(entry, path) ?? await storageUsage());
9506
- 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` };
9507
9934
  if (req.op === "write" && req.mode === 1 || UNLINKED_OPS.has(req.op)) {
9508
- const serve = async () => run(entry, await keptOr(entry, path, req.handle), req);
9509
- 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());
9510
9937
  }
9511
- if (!entry.own || !OWN_OPS.has(req.op)) return await run(entry, path, req);
9512
- const target = TWO_PATHS.has(req.op) ? fs.resolvePath(cwd, req.arg2 ?? "") : path;
9513
- 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);
9514
9941
  } catch (err) {
9515
9942
  return toErrno(err);
9516
9943
  }
@@ -9538,6 +9965,15 @@ var SAB_I_KILLED = 13;
9538
9965
  var SAB_I_ASYNC = 14;
9539
9966
  var SAB_I_STOP = 15;
9540
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);
9541
9977
  var U32 = 2 ** 32;
9542
9978
  function storeU53(header4, lo, hi, value) {
9543
9979
  Atomics.store(header4, hi, Math.floor(value / U32));
@@ -10064,7 +10500,7 @@ function mountTable(mounts = []) {
10064
10500
  }
10065
10501
 
10066
10502
  // src/version.ts
10067
- var KERNEL_VERSION = true ? "1.91.0" : "0.0.0-dev";
10503
+ var KERNEL_VERSION = true ? "1.93.0" : "0.0.0-dev";
10068
10504
  var OS_RELEASE = "6.0.0-slicc";
10069
10505
  function procVersion() {
10070
10506
  return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
@@ -10764,21 +11200,28 @@ function bodyOf(incoming, head, method, signal) {
10764
11200
  var KernelLoopback = class {
10765
11201
  net;
10766
11202
  cred;
10767
- constructor(net, cred) {
11203
+ stamp;
11204
+ constructor(net, cred, stamp) {
10768
11205
  this.net = net;
10769
11206
  this.cred = cred;
11207
+ this.stamp = stamp;
10770
11208
  }
10771
11209
  port(url) {
10772
11210
  return kernelLoopbackPort(url);
10773
11211
  }
10774
- connect(url, port, signal) {
11212
+ async connect(url, port, signal) {
10775
11213
  if (url.protocol === "https:" || url.protocol === "wss:") {
10776
11214
  throw errno("ENOTSUP", `TLS to the kernel's loopback is not supported: ${url.origin}`);
10777
11215
  }
10778
11216
  signal.throwIfAborted();
10779
11217
  const socket = this.net.socket("inet");
10780
11218
  try {
10781
- 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
+ );
10782
11225
  } catch (err) {
10783
11226
  socket.close();
10784
11227
  const refused2 = err instanceof KernelError && err.code === "ECONNREFUSED";
@@ -10792,7 +11235,7 @@ var KernelLoopback = class {
10792
11235
  const body = req.body ?? (/^(POST|PUT|PATCH)$/i.test(req.method) ? new Uint8Array(0) : void 0);
10793
11236
  const length2 = body ? [["Content-Length", String(body.length)]] : [];
10794
11237
  const head = requestHead(req.method, url, req.headers, [...length2, ["Connection", "close"]]);
10795
- const socket = this.connect(url, port, req.signal);
11238
+ const socket = await this.connect(url, port, req.signal);
10796
11239
  try {
10797
11240
  await writeAll2(socket, body ? concat([head, body]) : head, req.signal);
10798
11241
  const incoming = new Incoming(socket);
@@ -10813,7 +11256,7 @@ var KernelLoopback = class {
10813
11256
  }
10814
11257
  async websocket(req, port) {
10815
11258
  const url = new URL(req.url);
10816
- const socket = this.connect(url, port, req.signal);
11259
+ const socket = await this.connect(url, port, req.signal);
10817
11260
  try {
10818
11261
  const key = btoa(String.fromCharCode(...crypto.getRandomValues(new Uint8Array(16))));
10819
11262
  const extra = [
@@ -10838,6 +11281,9 @@ var KernelLoopback = class {
10838
11281
  if (!upgraded || accept2 !== await acceptFor(key)) {
10839
11282
  throw errno("EPROTO", "the server did not accept the WebSocket key");
10840
11283
  }
11284
+ if (fieldValues(head.headers, "sec-websocket-extensions").length > 0) {
11285
+ throw errno("EPROTO", "the server offered an unsolicited extension");
11286
+ }
10841
11287
  const protocol = fieldValues(head.headers, "sec-websocket-protocol")[0] ?? "";
10842
11288
  return new LoopbackWebSocket(socket, incoming, protocol);
10843
11289
  } catch (err) {
@@ -10937,7 +11383,7 @@ var LoopbackWebSocket = class {
10937
11383
  })();
10938
11384
  }
10939
11385
  async pump(reader, deliver) {
10940
- let close = { code: 1006, reason: "" };
11386
+ let close = { code: 1006, reason: "", wasClean: false };
10941
11387
  try {
10942
11388
  for (; ; ) {
10943
11389
  await this.space();
@@ -10947,14 +11393,14 @@ var LoopbackWebSocket = class {
10947
11393
  else if (event.kind === "binary") deliver(event.bytes);
10948
11394
  else if (event.kind === "ping") this.write(clientFrame(OP_PONG, event.payload));
10949
11395
  else {
10950
- close = { code: event.code, reason: event.reason };
11396
+ close = { code: event.code, reason: event.reason, wasClean: true };
10951
11397
  if (!this.sentClose) this.sendClose(event.code === 1005 ? 1e3 : event.code, "");
10952
11398
  break;
10953
11399
  }
10954
11400
  }
10955
11401
  } catch (err) {
10956
11402
  if (err instanceof WsError) this.sendClose(err.code, err.message);
10957
- close = { code: err instanceof WsError ? err.code : 1006, reason: "" };
11403
+ close = { code: 1006, reason: "", wasClean: false };
10958
11404
  } finally {
10959
11405
  const late = setTimeout(() => this.stop.abort(), CLOSE_WAIT_MS3);
10960
11406
  await this.writes;
@@ -11744,14 +12190,22 @@ var WsHandles = class {
11744
12190
  return typeof next2.value === "string" ? { ok: true, kind: "json", json: { text: next2.value } } : { ok: true, kind: "bytes", bytes: next2.value };
11745
12191
  }
11746
12192
  const closed = await socket.closed;
11747
- 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
+ };
11748
12198
  } catch (e) {
11749
12199
  return fail("ECONNRESET", message(e));
11750
12200
  }
11751
12201
  }
11752
12202
  async closed(socket) {
11753
- const { code, reason } = await socket.closed;
11754
- 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
+ };
11755
12209
  }
11756
12210
  send(socket, req, entry) {
11757
12211
  const data = req.text ?? req.body;
@@ -11791,7 +12245,7 @@ function message2(e) {
11791
12245
  }
11792
12246
  function codeOf2(e) {
11793
12247
  const code = e?.code;
11794
- 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";
11795
12249
  }
11796
12250
  async function* once(bytes) {
11797
12251
  yield bytes;
@@ -11874,7 +12328,8 @@ var HttpHandles = class {
11874
12328
  if (port !== void 0) return { ok: false, errno: codeOf2(e), message: text };
11875
12329
  const status = e.status;
11876
12330
  if (text === NO_TRANSPORT || typeof status !== "number" || status === 502) {
11877
- const errno2 = text === NO_TRANSPORT ? "ENETUNREACH" : "ECONNREFUSED";
12331
+ const code = codeOf2(e);
12332
+ const errno2 = text === NO_TRANSPORT ? "ENETUNREACH" : code !== "EIO" ? code : "ECONNREFUSED";
11878
12333
  return { ok: false, errno: errno2, message: text };
11879
12334
  }
11880
12335
  entry.body = once(new TextEncoder().encode(text))[Symbol.asyncIterator]();
@@ -13444,6 +13899,7 @@ function aborted(signal) {
13444
13899
  signal.addEventListener("abort", () => reject(new KernelError("EINTR")), { once: true });
13445
13900
  });
13446
13901
  }
13902
+ var PLAIN_FILE_MODE = 420;
13447
13903
  var JOB_OPS = /* @__PURE__ */ new Set([
13448
13904
  "proc-setpgid",
13449
13905
  "proc-getpgid",
@@ -13466,7 +13922,7 @@ function isPtySyscall(req) {
13466
13922
  return req.op.startsWith("pty-");
13467
13923
  }
13468
13924
  function isVfsSyscall(req) {
13469
- 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";
13470
13926
  }
13471
13927
  function isFdSyscall(req) {
13472
13928
  return req.op.startsWith("fd-");
@@ -13486,6 +13942,9 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
13486
13942
  "fd-pwrite",
13487
13943
  "fd-resize",
13488
13944
  "fd-vfs-stat",
13945
+ "fd-chmod",
13946
+ "fd-chown",
13947
+ "fd-utimes",
13489
13948
  "fd-path-flush",
13490
13949
  "fd-path-unlinking",
13491
13950
  "fd-path-unlinked",
@@ -13516,6 +13975,7 @@ var SYSCALL_OPS = /* @__PURE__ */ new Set([
13516
13975
  "fd-tty-owner",
13517
13976
  "fd-open-sealed",
13518
13977
  "fd-open-clipboard",
13978
+ "fd-clipboard-abort",
13519
13979
  "tty-get",
13520
13980
  "tty-set",
13521
13981
  "tty-winsz",
@@ -13861,7 +14321,11 @@ var WasmProcess = class {
13861
14321
  if (!stderr?.write || stderr.clipboard) return;
13862
14322
  const line = `${name2}: /dev/clipboard: ${clipboardLine(err.detail)}
13863
14323
  `;
13864
- 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
+ ]);
13865
14329
  }
13866
14330
  async write(fd, body, nonblock = false, cancelled) {
13867
14331
  const file = this.fds.get(fd).file;
@@ -13888,30 +14352,39 @@ var WasmProcess = class {
13888
14352
  await refuseReadonly(this.options.fs, req.path, req.flags, req);
13889
14353
  if (this.writesAt(req)) await this.options.fs.mayChange?.(req.path, !req.exclusive);
13890
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;
13891
14358
  const fd = this.fds.install(heldFile(), 3);
13892
14359
  try {
13893
14360
  const nodes = this.nodes;
13894
- 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);
13895
14362
  if (inProc(path)) {
13896
14363
  this.fds.installAt(fd, await this.procFile(req, path));
13897
14364
  return fd;
13898
14365
  }
13899
14366
  if (this.writesAt(req) && this.options.readonlyAt?.(path)) throw new KernelError("EROFS");
13900
14367
  const open = { ...req, path };
14368
+ const plain = this.plainMode(req.mode);
13901
14369
  if (req.exclusive) {
13902
- await nodes.createExclusive(path, this.newOwner());
13903
- this.fds.installAt(fd, this.openVfsFile(open));
14370
+ await nodes.createExclusive(path, this.newOwner(req.mode));
14371
+ if (plain !== void 0) await fs.chmod?.(path, plain);
14372
+ this.fds.installAt(fd, this.openVfsFile(open, void 0, fs));
13904
14373
  } else if (req.create) {
13905
14374
  await nodes.onEntry(path, async () => {
13906
- const fresh2 = this.options.owners && !await present2(this.options.fs, path, true);
13907
- this.fds.installAt(fd, this.openVfsFile(open, await nodes.identify(path)));
14375
+ const marks = this.options.owners || plain !== void 0;
14376
+ const fresh2 = marks && !await present2(fs, path, true);
14377
+ this.fds.installAt(fd, this.openVfsFile(open, await nodes.identify(path), fs));
13908
14378
  await this.fds.get(fd).file.stat?.();
13909
- if (fresh2) await this.options.fs.chown?.(path, { ...this.newOwner(), kind: "new" });
14379
+ if (fresh2 && plain !== void 0) await fs.chmod?.(path, plain);
14380
+ else if (fresh2) {
14381
+ await fs.chown?.(path, { ...this.newOwner(req.mode), kind: "new" });
14382
+ }
13910
14383
  });
13911
14384
  } else {
13912
- this.fds.installAt(fd, this.openVfsFile(open, await nodes.identify(path)));
14385
+ this.fds.installAt(fd, this.openVfsFile(open, await nodes.identify(path), fs));
13913
14386
  }
13914
- if (req.existing && !await present2(this.options.fs, path, true)) {
14387
+ if (req.existing && !await present2(fs, path, true)) {
13915
14388
  throw new KernelError("ENOENT");
13916
14389
  }
13917
14390
  } catch (err) {
@@ -13945,6 +14418,27 @@ var WasmProcess = class {
13945
14418
  writesAt(req) {
13946
14419
  return (req.flags & WRITE_ACCESS) !== 0 || req.truncate === true || req.create === true || req.exclusive === true;
13947
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
+ }
13948
14442
  async mayOpen(req) {
13949
14443
  const { guard: guard2 } = this.options;
13950
14444
  const inherited2 = req.orphan || req.contents !== void 0 || req.handle !== void 0;
@@ -14019,9 +14513,14 @@ var WasmProcess = class {
14019
14513
  if (!tty) throw new KernelError("ENOENT");
14020
14514
  return tty;
14021
14515
  }
14022
- newOwner() {
14516
+ plainMode(mode) {
14517
+ if (this.options.owners || mode === void 0) return void 0;
14518
+ const wanted = mode & 4095 & ~this.umask;
14519
+ return wanted === PLAIN_FILE_MODE ? void 0 : wanted;
14520
+ }
14521
+ newOwner(mode = 438) {
14023
14522
  if (!this.options.owners) return void 0;
14024
- return { uid: this.cred.euid, gid: this.cred.egid, mode: 438 & ~this.umask };
14523
+ return { uid: this.cred.euid, gid: this.cred.egid, mode: mode & 4095 & ~this.umask };
14025
14524
  }
14026
14525
  async openProc(req, path) {
14027
14526
  return this.fds.install(await this.procFile(req, path), 3);
@@ -14039,9 +14538,9 @@ var WasmProcess = class {
14039
14538
  );
14040
14539
  return memoryFile(tree2.read(path), path, req.position);
14041
14540
  }
14042
- openVfsFile(req, id2) {
14541
+ openVfsFile(req, id2, fs = this.options.fs) {
14043
14542
  return vfsFile(
14044
- this.options.fs,
14543
+ fs,
14045
14544
  {
14046
14545
  path: req.path,
14047
14546
  flags: req.flags,
@@ -14124,7 +14623,7 @@ var WasmProcess = class {
14124
14623
  return { ok: true, kind: "void" };
14125
14624
  case "fd-renumber":
14126
14625
  if (req.from !== req.to) {
14127
- this.fds.dup2(req.from, req.to);
14626
+ await this.fds.dup2Clipboard(req.from, req.to);
14128
14627
  if (!req.keep) await Promise.resolve(this.fds.close(req.from));
14129
14628
  }
14130
14629
  return { ok: true, kind: "void" };
@@ -14136,6 +14635,11 @@ var WasmProcess = class {
14136
14635
  return { ok: true, kind: "json", json: this.openSealed(req.flags) };
14137
14636
  case "fd-open-clipboard":
14138
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
+ }
14139
14643
  case "fd-tty-owner":
14140
14644
  return { ok: true, kind: "json", json: this.terminalByName(req.name).owner ?? null };
14141
14645
  case "fd-select": {
@@ -14188,12 +14692,23 @@ var WasmProcess = class {
14188
14692
  case "fd-resize":
14189
14693
  await file.resize(req.size);
14190
14694
  return { ok: true, kind: "void" };
14191
- case "fd-vfs-stat":
14192
- return {
14193
- ok: true,
14194
- kind: "json",
14195
- json: req.identity ? await this.identified(file.stat.bind(file), file.known?.bind(file)) : await file.stat()
14196
- };
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);
14197
14712
  }
14198
14713
  }
14199
14714
  keptPath;
@@ -14214,7 +14729,7 @@ var WasmProcess = class {
14214
14729
  async promote(req) {
14215
14730
  if (!this.fds.get(req.fd).file.held) throw new KernelError("EBADF");
14216
14731
  if (req.share !== void 0) {
14217
- this.fds.dup2(req.share, req.fd);
14732
+ await this.fds.dup2Clipboard(req.share, req.fd);
14218
14733
  return;
14219
14734
  }
14220
14735
  if (req.path === void 0) throw new KernelError("EINVAL");
@@ -14243,6 +14758,46 @@ var WasmProcess = class {
14243
14758
  this.fds.installAt(fd, heldFile(meta));
14244
14759
  return fd;
14245
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
+ }
14246
14801
  fdInfo(fd) {
14247
14802
  const file = this.fds.get(fd).file;
14248
14803
  const flags2 = this.fds.statusFlags(fd);
@@ -14461,7 +15016,10 @@ var WasmProcess = class {
14461
15016
  const { file, argv, env, cwd, cred, caller } = answer.grant;
14462
15017
  const stdio = [{ fd: 0 }, { fd: 1 }, { fd: 2 }];
14463
15018
  return {
14464
- 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
+ })
14465
15023
  };
14466
15024
  }
14467
15025
  async mountSyscall(req, cancelled) {
@@ -14761,11 +15319,13 @@ function inherited(opts) {
14761
15319
  }
14762
15320
  function holdingToken(opts, holds, caller) {
14763
15321
  return mintSyncFsToken({
14764
- ...opts.owners ? caller : {},
15322
+ ...opts.owners ? caller : { umask: caller.umask },
14765
15323
  ...opts.dac ? { dac: opts.dac, pid: opts.pid, created: caller.created } : {},
14766
15324
  ...opts.links !== void 0 ? { links: opts.links } : {},
14767
15325
  ...opts.fresh ? { fresh: (path) => opts.fresh?.(path, caller.umask()) } : {},
15326
+ ...opts.source ? { source: opts.source } : {},
14768
15327
  fs: opts.fs,
15328
+ noLinks: opts.noLinks,
14769
15329
  cwd: opts.cwd,
14770
15330
  ...opts.statfs ? { statfs: opts.statfs } : {},
14771
15331
  hold: (path, on, open, real, handle) => holds.hold(path, on, open, real, handle),
@@ -14791,6 +15351,8 @@ function processOptions(opts, header4, hooks, created3) {
14791
15351
  ...opts.outputFilter ? { outputFilter: opts.outputFilter } : {},
14792
15352
  ...opts.clipboard ? { clipboard: opts.clipboard, ...clipboardName(opts) } : {},
14793
15353
  ...opts.readonlyAt ? { readonlyAt: opts.readonlyAt } : {},
15354
+ ...opts.source ? { source: opts.source } : {},
15355
+ noLinks: opts.noLinks,
14794
15356
  ...opts.dac ? { guard: { dac: opts.dac, fs: opts.fs, created: created3, ...linksOf(opts) } } : {},
14795
15357
  ...opts.identity ? { identity: opts.identity } : {},
14796
15358
  ...opts.onSyscall ? { onSyscall: opts.onSyscall } : {},
@@ -14846,8 +15408,8 @@ function threadInit(opts, program, sab, cred, thread, main) {
14846
15408
  function spawnWasmProcess(opts) {
14847
15409
  const sab = new SharedArrayBuffer(SAB_HEADER_BYTES + SAB_DEFAULT_WINDOW_BYTES);
14848
15410
  const header4 = new Int32Array(sab, 0, SAB_HEADER_I32);
14849
- if (opts.blocked && opts.program.abi === "wasi")
14850
- 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);
14851
15413
  const threadSignals = /* @__PURE__ */ new Map();
14852
15414
  const created3 = new Created();
14853
15415
  const process2 = new WasmProcess(
@@ -14862,7 +15424,7 @@ function spawnWasmProcess(opts) {
14862
15424
  ...opts.program.abi === "js" ? {
14863
15425
  exitNow: (code) => exitNow(code),
14864
15426
  exitSignal: (sig) => queueMicrotask(() => finish(128 + sig, sig))
14865
- } : {}
15427
+ } : { exitSignal: (sig) => terminate(128 + sig, sig) }
14866
15428
  },
14867
15429
  created3
14868
15430
  )
@@ -14948,7 +15510,7 @@ function spawnWasmProcess(opts) {
14948
15510
  const tsab = new SharedArrayBuffer(SAB_HEADER_BYTES + SAB_DEFAULT_WINDOW_BYTES);
14949
15511
  const tw = opts.createWorker();
14950
15512
  const tsignals = threadSignalsOf(tsab);
14951
- if (thread.blocked) Atomics.store(tsignals.header, SAB_I_BLOCKED, thread.blocked);
15513
+ if (thread.blocked) storeBlocked(tsignals.header, thread.blocked);
14952
15514
  threadSignals.set(thread.tid, tsignals);
14953
15515
  const tdispatch = (req) => dying.serving(
14954
15516
  () => isWasmSyscall(req) ? process2.syscall(req, threadCancel(tsignals)) : dispatchSyncFs(req)
@@ -14976,12 +15538,13 @@ function spawnWasmProcess(opts) {
14976
15538
  processInit(opts, sab, process2.umask, copyCred(process2.cred), lane?.lane),
14977
15539
  transferOf(opts, lane?.lane)
14978
15540
  );
14979
- const signal = (sig) => {
14980
- if (process2.signal(sig) !== "terminate") return;
14981
- const code = sig === SIG.KILL ? 137 : 128 + sig;
15541
+ const terminate = (code, sig) => {
14982
15542
  if (opts.program.abi !== "wasi" || done4) finish(code, sig);
14983
15543
  else dying.start(code, sig, header4, process2, () => finish(code, sig));
14984
15544
  };
15545
+ const signal = (sig) => {
15546
+ if (process2.signal(sig) === "terminate") terminate(sig === SIG.KILL ? 137 : 128 + sig, sig);
15547
+ };
14985
15548
  const kill = (code = 137) => finish(code, code > 128 && code < 160 ? code - 128 : void 0);
14986
15549
  return {
14987
15550
  pid: opts.pid,
@@ -14992,7 +15555,7 @@ function spawnWasmProcess(opts) {
14992
15555
  onState: (listener) => process2.onState(listener),
14993
15556
  memory: () => Atomics.load(header4, SAB_I_MEMORY) * 65536,
14994
15557
  ignoredSignals: (fork) => process2.inheritable(fork),
14995
- blockedSignals: () => opts.program.abi === "wasi" ? Atomics.load(header4, SAB_I_BLOCKED) : 0,
15558
+ blockedSignals: () => opts.program.abi === "wasi" ? loadBlocked(header4) : [0, 0],
14996
15559
  signalMasks: () => process2.signalMasks(),
14997
15560
  umask: () => process2.umask,
14998
15561
  cred: () => copyCred(process2.cred),
@@ -18201,14 +18764,16 @@ var SUDO_COMMAND = {
18201
18764
  var SUDO_SOURCE = [
18202
18765
  "export default async function sudo(ctx) {",
18203
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 };",
18204
18769
  " let pid;",
18205
18770
  " let held;",
18206
18771
  " const relay = (sig) => {",
18207
18772
  " if (pid === undefined) held = sig;",
18208
- " else return ctx.kill(pid, sig).catch(() => undefined);",
18773
+ " else return ctx.kill(-pid, sig).catch(() => undefined);",
18209
18774
  " };",
18210
- ' for (const sig of ["SIGTERM", "SIGHUP"]) await ctx.signals.on(sig, relay);',
18211
- " 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]);",
18212
18777
  " let answer;",
18213
18778
  " try {",
18214
18779
  " answer = await ctx.sudo(ctx.argv.slice(1));",
@@ -18224,18 +18789,41 @@ var SUDO_SOURCE = [
18224
18789
  " await say(1, answer.list);",
18225
18790
  " return 0;",
18226
18791
  " }",
18227
- ' for (const sig of ["SIGINT", "SIGQUIT"]) await ctx.signals.on(sig, () => undefined);',
18228
18792
  " if (answer.run !== undefined) {",
18229
18793
  " try {",
18230
- " pid = (await ctx.spawn({ argv: answer.run })).pid;",
18794
+ " pid = (await ctx.spawn({ argv: answer.run, pgid: 0 })).pid;",
18231
18795
  " } catch (err) {",
18232
18796
  ' const why = err && err.code === "ENOENT" ? "command not found" : String(err && err.code);',
18233
18797
  ' await say(2, "sudo: " + answer.run[0] + ": " + why + "\\n");',
18234
18798
  " return failed();",
18235
18799
  " }",
18236
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();",
18237
18812
  " if (held !== undefined) await relay(held);",
18238
- " 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
+ " }",
18239
18827
  "}",
18240
18828
  ""
18241
18829
  ].join("\n");
@@ -18251,6 +18839,7 @@ var WASM_MAGIC = [0, 97, 115, 109];
18251
18839
  var SHEBANG_MAX = 256;
18252
18840
  var MAX_INTERPRETERS = 4;
18253
18841
  var NOT_FOUND = 127;
18842
+ var NOT_EXECUTABLE = 126;
18254
18843
  var DECIDE_MS = 100;
18255
18844
  var INIT_PID = 1;
18256
18845
  var SHARED_DIRS = ["/tmp", "/home"];
@@ -18400,6 +18989,9 @@ function addTimes(into, pid, t) {
18400
18989
  var Launcher = class {
18401
18990
  fs;
18402
18991
  processFs;
18992
+ noLinksFs;
18993
+ mounted;
18994
+ protection;
18403
18995
  prefixes;
18404
18996
  writerTags;
18405
18997
  unguardedRoot;
@@ -18508,8 +19100,8 @@ var Launcher = class {
18508
19100
  changed: this.watchers.changed.bind(this.watchers),
18509
19101
  owners: this.owners
18510
19102
  });
18511
- const watched = this.watchers.wrap(this.mounts.wrap(options.fs));
18512
- this.base = this.owners ? this.freshModules.wrap(watched) : watched;
19103
+ this.mounted = this.mounts.wrap(options.fs);
19104
+ this.base = this.rooted(this.mounted);
18513
19105
  this.createWorker = options.createWorker;
18514
19106
  this.createDriverWorker = options.createDriverWorker;
18515
19107
  this.media = options.media ?? memoryMedia();
@@ -18546,12 +19138,7 @@ var Launcher = class {
18546
19138
  });
18547
19139
  enableCdp(this.net, this.cdp);
18548
19140
  this.facades.enable(this.net);
18549
- const fs = withCommandDirs(
18550
- this.base,
18551
- async () => new Set((await this.commands()).keys()),
18552
- (dir) => this.userCommandDir(dir),
18553
- this.owners
18554
- );
19141
+ const fs = this.commandFs(this.base);
18555
19142
  this.nodes = new VfsNodes(fs, (path) => this.released(path));
18556
19143
  this.nodes.keepsOnDisk = true;
18557
19144
  this.openFiles.add(this.nodes);
@@ -18567,15 +19154,23 @@ var Launcher = class {
18567
19154
  ...this.owners ? { dirOwner: { uid: 0, gid: 0, mode: 448 } } : {}
18568
19155
  }
18569
19156
  );
18570
- this.fs = this.unlinked.wrap(keepingOpen(fs, this.nodes));
19157
+ this.fs = this.processChain(fs, this.nodes, this.unlinked);
18571
19158
  this.writerTags = writerTags(options.protect);
18572
19159
  this.unlinkOwnerOption = options.unlinkOwner;
18573
19160
  if (this.writerTags.size > 0 && options.fs.protect && !options.fs.unguarded) {
18574
19161
  throw new TypeError("protect.writers needs a root file system with an unguarded view");
18575
19162
  }
19163
+ if (!options.fs.noLinks) throw new TypeError("the root file system needs a noLinks view");
18576
19164
  this.unguardedRoot = options.fs.unguarded?.() ?? options.fs;
18577
19165
  this.prefixes = protectedPrefixes(options.protect?.paths ?? []);
18578
- this.processFs = this.prefixes.length > 0 ? new ProtectedFs(this.prefixes).view(this.fs) : this.fs;
19166
+ this.protection = this.prefixes.length > 0 ? new ProtectedFs(this.prefixes) : void 0;
19167
+ this.processFs = this.protection?.view(this.fs) ?? this.fs;
19168
+ const noLinks = this.processChain(
19169
+ this.commandFs(this.rooted(this.noLinksOf(this.mounted))),
19170
+ this.nodes,
19171
+ this.unlinked
19172
+ );
19173
+ this.noLinksFs = this.protection?.view(noLinks) ?? noLinks;
18579
19174
  const integrity = options.protect?.integrity;
18580
19175
  integrityOptions(integrity);
18581
19176
  options.fs.protect?.(this.prefixes, Boolean(integrity));
@@ -18987,10 +19582,15 @@ var Launcher = class {
18987
19582
  }
18988
19583
  async unrunnable(file, cwd, scope) {
18989
19584
  const path = this.fs.resolvePath(cwd, file);
19585
+ if (await this.directory(file, path)) return "EACCES";
18990
19586
  const head = await this.base.readFileBuffer(path).catch(() => null);
18991
19587
  if (!head || head[0] === 35 && head[1] === 33) return "ENOENT";
18992
19588
  return binfmtOf(await this.interpreters(scope), path) ? "ENOENT" : "ENOEXEC";
18993
19589
  }
19590
+ async directory(file, path) {
19591
+ if (!file.includes("/") || COMMAND2.test(file)) return false;
19592
+ return (await this.base.stat(path).catch(() => void 0))?.isDirectory === true;
19593
+ }
18994
19594
  verifiedFs(fs) {
18995
19595
  const read = (path) => this.verified(path, KERNEL_CALLER, fs);
18996
19596
  return Object.assign(Object.create(fs), {
@@ -19130,10 +19730,29 @@ var Launcher = class {
19130
19730
  return this.jobs.sessionOf(pgid);
19131
19731
  }
19132
19732
  loopbackFor(pid) {
19133
- return new KernelLoopback(this.net, () => {
19134
- const cred = this.credOf(pid);
19135
- return { pid, uid: cred.euid, gid: cred.egid };
19733
+ return new KernelLoopback(
19734
+ this.net,
19735
+ () => {
19736
+ const cred = this.credOf(pid);
19737
+ return { pid, uid: cred.euid, gid: cred.egid };
19738
+ },
19739
+ () => this.connectorStamp(pid)
19740
+ );
19741
+ }
19742
+ sourceCheck(pid, writer) {
19743
+ const { integrity } = this;
19744
+ if (!integrity?.readsChecked || writer) return {};
19745
+ const caller = () => ({
19746
+ pid,
19747
+ uid: this.credOf(pid).euid,
19748
+ tag: this.tagOf(pid)
19136
19749
  });
19750
+ return {
19751
+ source: {
19752
+ read: (path, plain) => integrity.readSource(path, caller(), plain),
19753
+ open: (path, real) => integrity.openSource(path, caller(), real)
19754
+ }
19755
+ };
19137
19756
  }
19138
19757
  treeScrubs(req) {
19139
19758
  const inherited2 = req.ppid !== void 0 ? this.scrubs.get(req.ppid) : void 0;
@@ -19170,11 +19789,12 @@ var Launcher = class {
19170
19789
  },
19171
19790
  settling: () => this.settling.isSettling(pid),
19172
19791
  ...req.ignored ? { ignored: req.ignored } : {},
19173
- ...req.blocked ? { blocked: req.blocked } : {},
19792
+ ...anyBlocked(req.blocked) ? { blocked: req.blocked } : {},
19174
19793
  ...req.umask !== void 0 ? { umask: req.umask } : {},
19175
19794
  cred: req.cred,
19176
19795
  ...this.owners && req.program.abi === "wasi" ? { fresh: (path, umask) => this.freshModules.add(path, umask, pid) } : {},
19177
19796
  ...this.owners ? { owners: true } : {},
19797
+ ...this.sourceCheck(pid, stack.writer),
19178
19798
  ...linksOf2(req),
19179
19799
  sealed: this.sealed,
19180
19800
  ...scrubs ? { outputFilter: (file) => scrubbedAll(file, scrubs) } : {},
@@ -19187,6 +19807,7 @@ var Launcher = class {
19187
19807
  cwd: req.cwd,
19188
19808
  fds: req.fds,
19189
19809
  fs: stack.fs,
19810
+ noLinks: stack.noLinks,
19190
19811
  ...this.prefixes.length > 0 && !stack.writer ? { readonlyAt: (path) => isProtected(this.prefixes, path) } : {},
19191
19812
  createWorker: this.createWorker,
19192
19813
  onError: req.report,
@@ -19264,6 +19885,11 @@ var Launcher = class {
19264
19885
  return { shownPid: this.jobs.shown(req.ppid), ...ppid !== void 0 ? { ppid } : {} };
19265
19886
  }
19266
19887
  forget(pid) {
19888
+ const image = this.jobs.list().find((m) => m.pid === pid)?.execParent;
19889
+ const described2 = this.described.get(pid);
19890
+ if (image !== void 0 && described2 && this.described.has(image)) {
19891
+ this.described.set(image, described2);
19892
+ }
19267
19893
  this.jobs.remove(pid);
19268
19894
  this.described.delete(pid);
19269
19895
  const spent = this.spent.get(pid);
@@ -19379,10 +20005,11 @@ var Launcher = class {
19379
20005
  const root = member && rootOf(member).pid;
19380
20006
  return root !== void 0 && this.running(root) ? root : INIT_PID;
19381
20007
  };
20008
+ const superseded = new Set([...members.values()].map((m) => m.execParent));
19382
20009
  const listed = [];
19383
20010
  for (const member of members.values()) {
19384
20011
  const described2 = this.described.get(member.pid);
19385
- if (member.execed || !described2) continue;
20012
+ if (member.execed && superseded.has(member.pid) || !described2) continue;
19386
20013
  const cred = this.credOf(member.pid);
19387
20014
  if (!maySee(viewer, cred)) continue;
19388
20015
  const root = rootOf(member);
@@ -19567,7 +20194,7 @@ var Launcher = class {
19567
20194
  }
19568
20195
  signalMasksOf(pid) {
19569
20196
  const masks = this.processes.get(pid)?.signalMasks?.();
19570
- const blocked = this.blockedBy(pid);
20197
+ const [blocked] = this.blockedBy(pid);
19571
20198
  const sigBlk = blocked ? { sigBlk: blocked } : {};
19572
20199
  return masks ? { sigIgn: masks.ignored, sigCgt: masks.caught, ...sigBlk } : sigBlk;
19573
20200
  }
@@ -19575,14 +20202,14 @@ var Launcher = class {
19575
20202
  return this.settling.isSettling(pid) || (this.ignoredBy(pid) & sigbit(SIG.TTOU)) !== 0;
19576
20203
  }
19577
20204
  blockedBy(pid) {
19578
- return this.processes.get(pid)?.blockedSignals?.() ?? 0;
20205
+ return this.processes.get(pid)?.blockedSignals?.() ?? [0, 0];
19579
20206
  }
19580
20207
  ignoredBy(pid, fork = false) {
19581
20208
  return this.processes.get(pid)?.ignoredSignals(fork) ?? 0;
19582
20209
  }
19583
20210
  kill(pid, sig, sender, from) {
19584
20211
  const session = from === void 0 ? void 0 : this.jobs.sidOf(from);
19585
- 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;
20212
+ 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;
19586
20213
  if (pid === -1) {
19587
20214
  const sender2 = from === void 0 ? void 0 : this.imageOf(from);
19588
20215
  return this.killAll(sig, (member) => this.imageOf(member) !== sender2, may);
@@ -19945,19 +20572,20 @@ var Launcher = class {
19945
20572
  }
19946
20573
  stackFor(tag) {
19947
20574
  if (!this.isWriter(tag)) {
19948
- return { fs: this.processFs, nodes: this.nodes, unlinked: this.unlinked, writer: false };
20575
+ return {
20576
+ fs: this.processFs,
20577
+ noLinks: this.noLinksFs,
20578
+ nodes: this.nodes,
20579
+ unlinked: this.unlinked,
20580
+ writer: false
20581
+ };
19949
20582
  }
19950
20583
  this.writers ??= this.writerStack();
19951
20584
  return this.writers;
19952
20585
  }
19953
20586
  writerStack() {
19954
- const watched = this.watchers.wrap(this.mounts.wrap(this.unguardedRoot));
19955
- const fs = withCommandDirs(
19956
- this.owners ? this.freshModules.wrap(watched) : watched,
19957
- async () => new Set((await this.commands()).keys()),
19958
- (dir) => this.userCommandDir(dir),
19959
- this.owners
19960
- );
20587
+ const mounted = this.mounts.wrap(this.unguardedRoot);
20588
+ const fs = this.commandFs(this.rooted(mounted));
19961
20589
  const nodes = new VfsNodes(fs, (path) => this.released(path), this.nodes.entries);
19962
20590
  nodes.keepsOnDisk = true;
19963
20591
  this.openFiles.add(nodes);
@@ -19972,7 +20600,32 @@ var Launcher = class {
19972
20600
  ...this.owners ? { dirOwner: { uid: 0, gid: 0, mode: 448 } } : {}
19973
20601
  }
19974
20602
  );
19975
- return { fs: unlinked.wrap(keepingOpen(fs, nodes)), nodes, unlinked, writer: true };
20603
+ const noLinks = this.commandFs(this.rooted(this.noLinksOf(mounted)));
20604
+ return {
20605
+ fs: this.processChain(fs, nodes, unlinked),
20606
+ noLinks: this.processChain(noLinks, nodes, unlinked),
20607
+ nodes,
20608
+ unlinked,
20609
+ writer: true
20610
+ };
20611
+ }
20612
+ noLinksOf(mounted) {
20613
+ return mounted.noLinks.call(mounted);
20614
+ }
20615
+ rooted(mounted) {
20616
+ const watched = this.watchers.wrap(mounted);
20617
+ return this.owners ? this.freshModules.wrap(watched) : watched;
20618
+ }
20619
+ commandFs(root) {
20620
+ return withCommandDirs(
20621
+ root,
20622
+ async () => new Set((await this.commands()).keys()),
20623
+ (dir) => this.userCommandDir(dir),
20624
+ this.owners
20625
+ );
20626
+ }
20627
+ processChain(fs, nodes, unlinked) {
20628
+ return unlinked.wrap(keepingOpen(fs, nodes));
19976
20629
  }
19977
20630
  async starting(argv, dir, account, tag) {
19978
20631
  this.resetCatalogs();
@@ -20054,8 +20707,10 @@ var Launcher = class {
20054
20707
  const report = (message3) => stderr(encoder7.encode(`${message3}
20055
20708
  `));
20056
20709
  if (!planned) {
20057
- report(`${file}: command not found`);
20058
- return { status: NOT_FOUND, stdout: concat6(out), stderr: concat6(err) };
20710
+ const directory = await this.directory(file, this.fs.resolvePath(cwd, file));
20711
+ report(`${file}: ${directory ? "Is a directory" : "command not found"}`);
20712
+ const status2 = directory ? NOT_EXECUTABLE : NOT_FOUND;
20713
+ return { status: status2, stdout: concat6(out), stderr: concat6(err) };
20059
20714
  }
20060
20715
  const fds = new FdTable();
20061
20716
  fds.installAt(0, options.stdin ? bytesSource(options.stdin) : deviceFile("null"));
@@ -20078,6 +20733,8 @@ var Launcher = class {
20078
20733
  };
20079
20734
 
20080
20735
  // src/node/loopback.ts
20736
+ import { AsyncLocalStorage } from "node:async_hooks";
20737
+ import { subscribe } from "node:diagnostics_channel";
20081
20738
  import { lookup } from "node:dns/promises";
20082
20739
  import { isIP } from "node:net";
20083
20740
  var DEVTOOLS_PORTS = [9222, 9223, 9224, 9225, 9226, 9227, 9228, 9229];
@@ -20137,6 +20794,11 @@ function refusal(url, address, place) {
20137
20794
  const why = place === "link-local" ? `nodeTransport refuses link-local ${where}` : `nodeTransport refuses the host's loopback ${where} (see nodeTransport({ hostLoopback }))`;
20138
20795
  return Object.assign(new Error(why), { status: 403, code: "EACCES" });
20139
20796
  }
20797
+ function connectRefusal(address, port, place) {
20798
+ const where = `${address}:${port}`;
20799
+ const why = place === "link-local" ? `nodeTransport refuses link-local ${where}` : `nodeTransport refuses the host's loopback ${where} (see nodeTransport({ hostLoopback }))`;
20800
+ return Object.assign(new Error(why), { status: 403, code: "EACCES" });
20801
+ }
20140
20802
  async function addressesOf(host, resolve2) {
20141
20803
  const plain = host.replace(/^\[(.*)\]$/, "$1");
20142
20804
  if (isIP(plain)) return [plain];
@@ -20163,7 +20825,66 @@ function guarded2(check, signal) {
20163
20825
  });
20164
20826
  return Promise.race([check, aborted2]).finally(() => signal.removeEventListener("abort", stop));
20165
20827
  }
20828
+ var loopbackStore = new AsyncLocalStorage();
20829
+ var subscribed = false;
20830
+ function envProxyEnabled() {
20831
+ if (process.env.NODE_USE_ENV_PROXY === "1") return true;
20832
+ const opts = process.env.NODE_OPTIONS ?? "";
20833
+ const args = process.execArgv ?? [];
20834
+ return args.includes("--use-env-proxy") || opts.includes("--use-env-proxy");
20835
+ }
20836
+ function normaliseProxyUrl(url) {
20837
+ try {
20838
+ const parsed2 = new URL(url);
20839
+ const host = parsed2.hostname.replace(/^\[(.*)\]$/, "$1");
20840
+ const port = parsed2.port || (parsed2.protocol === "https:" ? "443" : "80");
20841
+ return `${host}:${port}`;
20842
+ } catch {
20843
+ return void 0;
20844
+ }
20845
+ }
20846
+ function proxyAddresses() {
20847
+ if (!envProxyEnabled()) return /* @__PURE__ */ new Set();
20848
+ const out = /* @__PURE__ */ new Set();
20849
+ for (const key of ["HTTPS_PROXY", "https_proxy", "HTTP_PROXY", "http_proxy"]) {
20850
+ const val = process.env[key];
20851
+ if (val) {
20852
+ const addr = normaliseProxyUrl(val);
20853
+ if (addr) out.add(addr);
20854
+ }
20855
+ }
20856
+ return out;
20857
+ }
20858
+ function ensureSubscribed() {
20859
+ if (subscribed) return;
20860
+ subscribed = true;
20861
+ subscribe("undici:client:connected", (message3) => {
20862
+ const entry = loopbackStore.getStore();
20863
+ if (!entry) return;
20864
+ const { socket, connectParams } = message3;
20865
+ const address = socket.remoteAddress;
20866
+ if (!address) {
20867
+ const err2 = connectRefusal("unknown", 0, "loopback");
20868
+ entry.refused = err2;
20869
+ socket.destroy(err2);
20870
+ return;
20871
+ }
20872
+ const proxies = proxyAddresses();
20873
+ if (proxies.size > 0 && connectParams.hostname) {
20874
+ const cpPort = connectParams.port || (connectParams.protocol === "https:" ? "443" : "80");
20875
+ if (proxies.has(`${connectParams.hostname}:${cpPort}`)) return;
20876
+ }
20877
+ const place = placeOf(address);
20878
+ if (!place) return;
20879
+ const port = socket.remotePort ?? 0;
20880
+ if (place === "loopback" && allows(entry.policy, port)) return;
20881
+ const err = connectRefusal(address, port, place);
20882
+ entry.refused = err;
20883
+ socket.destroy(err);
20884
+ });
20885
+ }
20166
20886
  function guardedLoopback(transport, policy, resolve2) {
20887
+ ensureSubscribed();
20167
20888
  const open = transport.websocket?.bind(transport);
20168
20889
  return {
20169
20890
  get traits() {
@@ -20171,12 +20892,22 @@ function guardedLoopback(transport, policy, resolve2) {
20171
20892
  },
20172
20893
  fetch: async (request, owner) => {
20173
20894
  await guarded2(checkDestination(request.url, policy, resolve2), request.signal);
20174
- return transport.fetch(request, owner);
20895
+ const entry = { policy };
20896
+ try {
20897
+ return await loopbackStore.run(entry, () => transport.fetch(request, owner));
20898
+ } catch (err) {
20899
+ throw entry.refused ?? err;
20900
+ }
20175
20901
  },
20176
20902
  ...open ? {
20177
20903
  websocket: async (request) => {
20178
20904
  await guarded2(checkDestination(request.url, policy, resolve2), request.signal);
20179
- return open(request);
20905
+ const entry = { policy };
20906
+ try {
20907
+ return await loopbackStore.run(entry, () => open(request));
20908
+ } catch (err) {
20909
+ throw entry.refused ?? err;
20910
+ }
20180
20911
  }
20181
20912
  } : {}
20182
20913
  };
@@ -20228,8 +20959,8 @@ var Chunks = class {
20228
20959
  const index = Math.floor((at + done4) / CHUNK4);
20229
20960
  const from = at + done4 - index * CHUNK4;
20230
20961
  const n = Math.min(CHUNK4 - from, bytes.length - done4);
20231
- const view = bytes.subarray(done4, done4 + n);
20232
- const part = view.buffer instanceof ArrayBuffer ? view : view.slice();
20962
+ const view2 = bytes.subarray(done4, done4 + n);
20963
+ const part = view2.buffer instanceof ArrayBuffer ? view2 : view2.slice();
20233
20964
  const old = this.blobs[index];
20234
20965
  if (from === 0 && n >= (old?.size ?? 0)) this.blobs[index] = new Blob([part]);
20235
20966
  else {