@ai-ecoverse/slicc-kernel 1.37.0 → 1.37.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/kernel-worker.js +323 -47
- package/dist/kernel-worker.js.map +4 -4
- package/dist/node-process-worker.js +29 -12
- package/dist/node-process-worker.js.map +2 -2
- package/dist/node.js +326 -50
- package/dist/node.js.map +4 -4
- package/dist/process-worker.js +29 -12
- package/dist/process-worker.js.map +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -418,7 +418,7 @@ Programs mount and unmount with `mount(2)` and `umount2(2)`, so a `mount`/`umoun
|
|
|
418
418
|
|
|
419
419
|
### Exec
|
|
420
420
|
|
|
421
|
-
An exec'd program takes over the pid of the process it replaces, as on Linux: `getpid()` and `$$` in the new image, `/proc`, `ps`, `kill` and the parent's `waitpid` all agree on that pid, and the image's parent pid is the replaced process's. Its own children see that pid as their parent. A file removed or renamed over while programs hold it open keeps its bytes for them, as on Linux: the kernel keeps one copy for all holders, shared by their descriptors (writes through one are seen through the others), and frees it when the last one closes. Signals a program ignores stay ignored in the image it execs and in the children it spawns or forks, as POSIX has it, so `nohup cmd` and `trap '' TERM; exec cmd` keep `cmd` safe from them; caught signals go back to their default. The kernel holds what was handed down, so a new image that sets one of those signals back to the default with `signal(SIG_DFL)` still ignores it, until it installs a handler.
|
|
421
|
+
An exec'd program takes over the pid of the process it replaces, as on Linux: `getpid()` and `$$` in the new image, `/proc`, `ps`, `kill` and the parent's `waitpid` all agree on that pid, and the image's parent pid is the replaced process's. Its own children see that pid as their parent. A file removed or renamed over while programs hold it open keeps its bytes for them, as on Linux: the kernel keeps one copy for all holders, shared by their descriptors (writes through one are seen through the others), and frees it when the last one closes. That holds for files a program opened in its own worker too: every open, read-only ones included, is known to the kernel. When another process removes such a file, or renames another file over it, the kernel does not copy it: as NFS does with its "silly rename", it moves the file into a hidden directory, `.slicc-unlinked/`, at the root of the mount it lives on (or of the root file system), and serves that holder's reads and writes of the old path from there while the path is gone. No file data is held in memory for it. The file is deleted when the last holder closes it or exits, and the directory goes as soon as it is empty. A file created at the path afterwards is the new file for everyone, also for the holder; a holder whose file another process renames (rather than removes) still uses the old path. Listings in the kernel leave `.slicc-unlinked` out, but on an `fsa` or `hostfs` mount it is visible on the real machine while a removed file is held (add `.slicc-unlinked/` to a `.gitignore` there). Each kernel names its entries after itself and holds a Web Lock for that name while it runs; when a mount is set up (and the root at start), the kernel removes the entries whose owner holds no lock, so a tab that died while holding a file leaves nothing behind for long, and entries of other live tabs on the same origin are left alone. Where Web Locks are missing (the Node entry), nothing is swept. Signals a program ignores stay ignored in the image it execs and in the children it spawns or forks, as POSIX has it, so `nohup cmd` and `trap '' TERM; exec cmd` keep `cmd` safe from them; caught signals go back to their default. The kernel holds what was handed down, so a new image that sets one of those signals back to the default with `signal(SIG_DFL)` still ignores it, until it installs a handler.
|
|
422
422
|
|
|
423
423
|
- **Emscripten:** `Module.sliccKernel.execve(file, argv, env, cwd)` runs `file` as this process's new image on its fds 0, 1 and 2, waits for it, and returns its wait status, or a negative errno when it cannot start. `env` and `cwd` may be `null` for this process's own. An exec shim detects it with `typeof Module.sliccKernel.execve === 'function'` and exits with the status it returns, as `execve` never returns on success. Older shims spawn the program and then call `Module.sliccKernel.execWait(pid)`; that works the same way: a new program asks the kernel for its pid before `main`, and the kernel answers once its parent has made its next call, so an image an older shim execs also reports the pid it replaced (the wait starts once the program is loaded; should the parent make no call within 100 ms after that, the kernel answers anyway, and the image may then report its own pid; `launcher.identities` counts the programs that asked and those answered by that timeout).
|
|
424
424
|
- **WASIX:** `proc_exec` does this by itself.
|
package/dist/kernel-worker.js
CHANGED
|
@@ -4878,13 +4878,193 @@ function withCommandDirs(fs, names) {
|
|
|
4878
4878
|
};
|
|
4879
4879
|
}
|
|
4880
4880
|
|
|
4881
|
+
// src/fs/unlinked.ts
|
|
4882
|
+
var UNLINKED_DIR = ".slicc-unlinked";
|
|
4883
|
+
function webLocks() {
|
|
4884
|
+
return globalThis.navigator?.locks;
|
|
4885
|
+
}
|
|
4886
|
+
var within3 = (path, target) => path === target || target === "/" || path.startsWith(`${target}/`);
|
|
4887
|
+
var parentOf3 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
|
|
4888
|
+
var lockName = (instance) => `slicc-unlinked:${instance}`;
|
|
4889
|
+
var dirOf = (root) => root === "/" ? `/${UNLINKED_DIR}` : `${root}/${UNLINKED_DIR}`;
|
|
4890
|
+
var PathGate = class {
|
|
4891
|
+
tails = /* @__PURE__ */ new Map();
|
|
4892
|
+
async acquire(path) {
|
|
4893
|
+
const prev = this.tails.get(path) ?? Promise.resolve();
|
|
4894
|
+
const { promise, resolve } = Promise.withResolvers();
|
|
4895
|
+
const tail = prev.then(() => promise);
|
|
4896
|
+
this.tails.set(path, tail);
|
|
4897
|
+
await prev;
|
|
4898
|
+
return () => {
|
|
4899
|
+
resolve();
|
|
4900
|
+
if (this.tails.get(path) === tail) this.tails.delete(path);
|
|
4901
|
+
};
|
|
4902
|
+
}
|
|
4903
|
+
async run(path, op) {
|
|
4904
|
+
const release = await this.acquire(path);
|
|
4905
|
+
try {
|
|
4906
|
+
return await op();
|
|
4907
|
+
} finally {
|
|
4908
|
+
release();
|
|
4909
|
+
}
|
|
4910
|
+
}
|
|
4911
|
+
};
|
|
4912
|
+
var UnlinkedKeeper = class {
|
|
4913
|
+
fs;
|
|
4914
|
+
holders;
|
|
4915
|
+
rootOf;
|
|
4916
|
+
locks;
|
|
4917
|
+
busy;
|
|
4918
|
+
waiting = /* @__PURE__ */ new Set();
|
|
4919
|
+
instance = crypto.randomUUID();
|
|
4920
|
+
gate = new PathGate();
|
|
4921
|
+
queue = Promise.resolve();
|
|
4922
|
+
count = 0;
|
|
4923
|
+
constructor(fs, holders, rootOf, locks, busy = () => false) {
|
|
4924
|
+
this.fs = fs;
|
|
4925
|
+
this.holders = holders;
|
|
4926
|
+
this.rootOf = rootOf;
|
|
4927
|
+
this.locks = locks;
|
|
4928
|
+
this.busy = busy;
|
|
4929
|
+
void locks?.request(lockName(this.instance), () => new Promise(() => void 0));
|
|
4930
|
+
}
|
|
4931
|
+
serial(op) {
|
|
4932
|
+
const next = this.queue.then(op);
|
|
4933
|
+
this.queue = next.catch(() => void 0);
|
|
4934
|
+
return next;
|
|
4935
|
+
}
|
|
4936
|
+
wrap() {
|
|
4937
|
+
const fs = this.fs;
|
|
4938
|
+
const keeper = this;
|
|
4939
|
+
const visible = (names) => names.filter((name2) => name2 !== UNLINKED_DIR);
|
|
4940
|
+
return {
|
|
4941
|
+
...fs,
|
|
4942
|
+
async rm(path, options) {
|
|
4943
|
+
const target = fs.resolvePath("/", path);
|
|
4944
|
+
const pinned = await keeper.pin(target);
|
|
4945
|
+
if (pinned.moved.has(target)) pinned.commit();
|
|
4946
|
+
else await keeper.settle(pinned, () => fs.rm(path, options));
|
|
4947
|
+
},
|
|
4948
|
+
async rename(from, to) {
|
|
4949
|
+
const pinned = await keeper.pin(fs.resolvePath("/", to));
|
|
4950
|
+
await keeper.settle(pinned, () => fs.rename(from, to));
|
|
4951
|
+
},
|
|
4952
|
+
readdir: async (path) => visible(await fs.readdir(path)),
|
|
4953
|
+
readdirStat: async (path) => (await fs.readdirStat(path)).filter(([name2]) => name2 !== UNLINKED_DIR)
|
|
4954
|
+
};
|
|
4955
|
+
}
|
|
4956
|
+
async settle(pinned, op) {
|
|
4957
|
+
try {
|
|
4958
|
+
await op();
|
|
4959
|
+
} catch (err) {
|
|
4960
|
+
await pinned.rollback();
|
|
4961
|
+
throw err;
|
|
4962
|
+
}
|
|
4963
|
+
pinned.commit();
|
|
4964
|
+
}
|
|
4965
|
+
async pin(target) {
|
|
4966
|
+
const held = /* @__PURE__ */ new Map();
|
|
4967
|
+
for (const holder of this.holders) {
|
|
4968
|
+
if (holder.owns(target)) continue;
|
|
4969
|
+
for (const path of holder.opens.keys()) {
|
|
4970
|
+
if (!within3(path, target) || holder.unlinked.has(path)) continue;
|
|
4971
|
+
held.set(path, [...held.get(path) ?? [], holder]);
|
|
4972
|
+
}
|
|
4973
|
+
}
|
|
4974
|
+
const paths = [...held.keys()].sort();
|
|
4975
|
+
const releases = [];
|
|
4976
|
+
for (const path of paths) releases.push(await this.gate.acquire(path));
|
|
4977
|
+
const hidden = /* @__PURE__ */ new Map();
|
|
4978
|
+
for (const path of paths) {
|
|
4979
|
+
const at = await this.hide(path);
|
|
4980
|
+
if (at !== void 0) hidden.set(path, at);
|
|
4981
|
+
}
|
|
4982
|
+
const done4 = () => {
|
|
4983
|
+
for (const release of releases) release();
|
|
4984
|
+
};
|
|
4985
|
+
return {
|
|
4986
|
+
moved: new Set(hidden.keys()),
|
|
4987
|
+
commit: () => {
|
|
4988
|
+
for (const [path, at] of hidden) {
|
|
4989
|
+
const holders = held.get(path);
|
|
4990
|
+
const file = this.keptFile(at, { refs: holders.length });
|
|
4991
|
+
for (const holder of holders) holder.unlinked.set(path, file);
|
|
4992
|
+
}
|
|
4993
|
+
done4();
|
|
4994
|
+
},
|
|
4995
|
+
rollback: async () => {
|
|
4996
|
+
for (const [path, at] of hidden) {
|
|
4997
|
+
await this.serial(async () => {
|
|
4998
|
+
await Promise.allSettled([this.fs.rename(at, path)]);
|
|
4999
|
+
await this.tidy(parentOf3(at));
|
|
5000
|
+
});
|
|
5001
|
+
}
|
|
5002
|
+
done4();
|
|
5003
|
+
}
|
|
5004
|
+
};
|
|
5005
|
+
}
|
|
5006
|
+
keptFile(hidden, entry) {
|
|
5007
|
+
return {
|
|
5008
|
+
hidden,
|
|
5009
|
+
release: () => {
|
|
5010
|
+
if (--entry.refs === 0) void this.serial(() => this.drop(hidden));
|
|
5011
|
+
}
|
|
5012
|
+
};
|
|
5013
|
+
}
|
|
5014
|
+
hide(path) {
|
|
5015
|
+
return this.serial(async () => {
|
|
5016
|
+
const dir = dirOf(this.rootOf(path));
|
|
5017
|
+
const hidden = `${dir}/${this.instance}.${++this.count}`;
|
|
5018
|
+
try {
|
|
5019
|
+
await this.fs.mkdir(dir, { recursive: true });
|
|
5020
|
+
await this.fs.rename(path, hidden);
|
|
5021
|
+
return hidden;
|
|
5022
|
+
} catch {
|
|
5023
|
+
await this.tidy(dir);
|
|
5024
|
+
return void 0;
|
|
5025
|
+
}
|
|
5026
|
+
});
|
|
5027
|
+
}
|
|
5028
|
+
closed(path) {
|
|
5029
|
+
if (this.waiting.delete(path)) void this.serial(() => this.drop(path));
|
|
5030
|
+
}
|
|
5031
|
+
async drop(hidden) {
|
|
5032
|
+
if (this.busy(hidden)) {
|
|
5033
|
+
this.waiting.add(hidden);
|
|
5034
|
+
return;
|
|
5035
|
+
}
|
|
5036
|
+
await this.fs.rm(hidden).catch(() => void 0);
|
|
5037
|
+
await this.tidy(parentOf3(hidden));
|
|
5038
|
+
}
|
|
5039
|
+
async tidy(dir) {
|
|
5040
|
+
const left = await this.fs.readdir(dir).catch(() => [UNLINKED_DIR]);
|
|
5041
|
+
if (left.length === 0) await this.fs.rm(dir, { recursive: true }).catch(() => void 0);
|
|
5042
|
+
}
|
|
5043
|
+
sweep(root) {
|
|
5044
|
+
const query = this.locks?.query?.bind(this.locks);
|
|
5045
|
+
if (!query) return Promise.resolve();
|
|
5046
|
+
return this.serial(async () => {
|
|
5047
|
+
const dir = dirOf(root);
|
|
5048
|
+
const names = await this.fs.readdir(dir).catch(() => void 0);
|
|
5049
|
+
if (!names) return;
|
|
5050
|
+
const held = new Set(((await query()).held ?? []).map((lock) => lock.name));
|
|
5051
|
+
for (const name2 of names) {
|
|
5052
|
+
const owner = name2.slice(0, name2.lastIndexOf("."));
|
|
5053
|
+
if (owner === this.instance || held.has(lockName(owner))) continue;
|
|
5054
|
+
await this.fs.rm(`${dir}/${name2}`, { recursive: true }).catch(() => void 0);
|
|
5055
|
+
}
|
|
5056
|
+
await this.tidy(dir);
|
|
5057
|
+
});
|
|
5058
|
+
}
|
|
5059
|
+
};
|
|
5060
|
+
|
|
4881
5061
|
// src/fs/watch.ts
|
|
4882
5062
|
var WATCH_BATCH_LIMIT = 1e3;
|
|
4883
5063
|
var under2 = (path, dir) => dir === "/" || path.startsWith(`${dir}/`);
|
|
4884
|
-
var
|
|
5064
|
+
var parentOf4 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
|
|
4885
5065
|
function touches(watcher, changed) {
|
|
4886
5066
|
return watcher.paths.some(
|
|
4887
|
-
(watched) => changed === watched || under2(watched, changed) || (watcher.recursive ? under2(changed, watched) :
|
|
5067
|
+
(watched) => changed === watched || under2(watched, changed) || (watcher.recursive ? under2(changed, watched) : parentOf4(changed) === watched)
|
|
4888
5068
|
);
|
|
4889
5069
|
}
|
|
4890
5070
|
var FsWatchers = class {
|
|
@@ -5266,7 +5446,7 @@ function parseSize(text) {
|
|
|
5266
5446
|
if (!match) throw errnoError("EINVAL", `not a size: ${text}`);
|
|
5267
5447
|
return Math.floor(Number(match[1]) * UNITS[match[2].toLowerCase()]);
|
|
5268
5448
|
}
|
|
5269
|
-
var
|
|
5449
|
+
var within4 = (path, target) => path === target || target === "/" || path.startsWith(`${target}/`);
|
|
5270
5450
|
var HeldSet = class extends Set {
|
|
5271
5451
|
idle;
|
|
5272
5452
|
constructor(idle) {
|
|
@@ -5279,18 +5459,47 @@ var HeldSet = class extends Set {
|
|
|
5279
5459
|
}
|
|
5280
5460
|
delete(holds) {
|
|
5281
5461
|
const removed = super.delete(holds);
|
|
5282
|
-
for (const path of holds.keys()) this.idle(path);
|
|
5462
|
+
for (const path of /* @__PURE__ */ new Set([...holds.keys(), ...holds.opens.keys()])) this.idle(path);
|
|
5463
|
+
for (const kept of holds.unlinked.values()) kept.release();
|
|
5464
|
+
holds.unlinked.clear();
|
|
5283
5465
|
return removed;
|
|
5284
5466
|
}
|
|
5285
5467
|
};
|
|
5286
5468
|
function heldUnder(held, target) {
|
|
5287
|
-
for (const paths of held) for (const path of paths.keys()) if (
|
|
5469
|
+
for (const paths of held) for (const path of paths.keys()) if (within4(path, target)) return true;
|
|
5288
5470
|
return false;
|
|
5289
5471
|
}
|
|
5290
5472
|
var HeldPaths = class extends Map {
|
|
5291
5473
|
revoked = /* @__PURE__ */ new Set();
|
|
5474
|
+
opens = /* @__PURE__ */ new Map();
|
|
5475
|
+
unlinked = /* @__PURE__ */ new Map();
|
|
5476
|
+
aliases = /* @__PURE__ */ new Map();
|
|
5477
|
+
owning;
|
|
5292
5478
|
onIdle;
|
|
5293
|
-
|
|
5479
|
+
keyOf(path) {
|
|
5480
|
+
return this.aliases.get(path) ?? path;
|
|
5481
|
+
}
|
|
5482
|
+
keptAt(path) {
|
|
5483
|
+
return this.unlinked.get(this.keyOf(path));
|
|
5484
|
+
}
|
|
5485
|
+
holding(path) {
|
|
5486
|
+
return this.opens.has(this.keyOf(path));
|
|
5487
|
+
}
|
|
5488
|
+
hold(path, on, open = false, real) {
|
|
5489
|
+
if (open) {
|
|
5490
|
+
const key = real ?? this.keyOf(path);
|
|
5491
|
+
if (key !== path) this.aliases.set(path, key);
|
|
5492
|
+
const count2 = (this.opens.get(key) ?? 0) + (on ? 1 : -1);
|
|
5493
|
+
if (count2 > 0) this.opens.set(key, count2);
|
|
5494
|
+
else {
|
|
5495
|
+
this.opens.delete(key);
|
|
5496
|
+
this.unlinked.get(key)?.release();
|
|
5497
|
+
this.unlinked.delete(key);
|
|
5498
|
+
for (const [alias, target] of this.aliases) if (target === key) this.aliases.delete(alias);
|
|
5499
|
+
this.onIdle?.(key);
|
|
5500
|
+
}
|
|
5501
|
+
return;
|
|
5502
|
+
}
|
|
5294
5503
|
const count = (this.get(path) ?? 0) + (on ? 1 : -1);
|
|
5295
5504
|
if (count > 0) this.set(path, count);
|
|
5296
5505
|
else {
|
|
@@ -5299,23 +5508,40 @@ var HeldPaths = class extends Map {
|
|
|
5299
5508
|
this.onIdle?.(path);
|
|
5300
5509
|
}
|
|
5301
5510
|
}
|
|
5511
|
+
async own(path, op) {
|
|
5512
|
+
this.owning = path;
|
|
5513
|
+
try {
|
|
5514
|
+
return await op();
|
|
5515
|
+
} finally {
|
|
5516
|
+
this.owning = void 0;
|
|
5517
|
+
}
|
|
5518
|
+
}
|
|
5519
|
+
owns(path) {
|
|
5520
|
+
return this.owning === path;
|
|
5521
|
+
}
|
|
5302
5522
|
revoke(prefix) {
|
|
5303
|
-
for (const path of this.keys()) if (
|
|
5523
|
+
for (const path of this.keys()) if (within4(path, prefix)) this.revoked.add(path);
|
|
5304
5524
|
}
|
|
5305
5525
|
renamed(from, to) {
|
|
5306
5526
|
for (const [path, count] of [...this]) {
|
|
5307
|
-
if (!
|
|
5527
|
+
if (!within4(path, from)) continue;
|
|
5308
5528
|
const moved2 = to + path.slice(from.length);
|
|
5309
5529
|
this.delete(path);
|
|
5310
5530
|
this.set(moved2, (this.get(moved2) ?? 0) + count);
|
|
5311
5531
|
if (this.revoked.delete(path)) this.revoked.add(moved2);
|
|
5312
5532
|
}
|
|
5533
|
+
for (const [path, count] of [...this.opens]) {
|
|
5534
|
+
if (!within4(path, from)) continue;
|
|
5535
|
+
const moved2 = to + path.slice(from.length);
|
|
5536
|
+
this.opens.delete(path);
|
|
5537
|
+
this.opens.set(moved2, (this.opens.get(moved2) ?? 0) + count);
|
|
5538
|
+
}
|
|
5313
5539
|
}
|
|
5314
5540
|
isRevoked(path) {
|
|
5315
5541
|
return this.revoked.has(path);
|
|
5316
5542
|
}
|
|
5317
5543
|
};
|
|
5318
|
-
var
|
|
5544
|
+
var parentOf5 = (path) => path.slice(0, path.lastIndexOf("/")) || "/";
|
|
5319
5545
|
var readonly = (mount) => mount.caps.readonly === true || mount.options.ro !== void 0;
|
|
5320
5546
|
function statOf(attr, path, mount) {
|
|
5321
5547
|
const mtime = new Date(attr.mtime);
|
|
@@ -5404,7 +5630,7 @@ var MountTable = class {
|
|
|
5404
5630
|
locate(path) {
|
|
5405
5631
|
let best;
|
|
5406
5632
|
for (const mount of this.mounts.values()) {
|
|
5407
|
-
if (
|
|
5633
|
+
if (within4(path, mount.target) && (!best || mount.target.length > best.target.length))
|
|
5408
5634
|
best = mount;
|
|
5409
5635
|
}
|
|
5410
5636
|
if (!best) return void 0;
|
|
@@ -5543,7 +5769,7 @@ var MountTable = class {
|
|
|
5543
5769
|
async commitUnder(prefix) {
|
|
5544
5770
|
const at = normalizePath(prefix);
|
|
5545
5771
|
await Promise.all(
|
|
5546
|
-
[...this.sessions.keys()].filter((p) =>
|
|
5772
|
+
[...this.sessions.keys()].filter((p) => within4(p, at)).map((p) => this.commit(p))
|
|
5547
5773
|
);
|
|
5548
5774
|
}
|
|
5549
5775
|
async commitOverlapping(real, offset, length2) {
|
|
@@ -5577,10 +5803,10 @@ var MountTable = class {
|
|
|
5577
5803
|
this.deps.changed?.(...full);
|
|
5578
5804
|
}
|
|
5579
5805
|
forget(mount, path) {
|
|
5580
|
-
for (const key of [...mount.attrs.keys()]) if (
|
|
5806
|
+
for (const key of [...mount.attrs.keys()]) if (within4(key, path)) mount.attrs.delete(key);
|
|
5581
5807
|
mount.lists.delete(path);
|
|
5582
|
-
mount.lists.delete(
|
|
5583
|
-
for (const key of [...mount.lists.keys()]) if (
|
|
5808
|
+
mount.lists.delete(parentOf5(path));
|
|
5809
|
+
for (const key of [...mount.lists.keys()]) if (within4(key, path)) mount.lists.delete(key);
|
|
5584
5810
|
}
|
|
5585
5811
|
async getattr(mount, path, rel2) {
|
|
5586
5812
|
if (mount.conn.failure) throw mount.conn.failure;
|
|
@@ -5645,7 +5871,7 @@ var MountFs = class {
|
|
|
5645
5871
|
if (attr.kind !== "symlink") return real;
|
|
5646
5872
|
if (hops >= MAX_LINKS4) throw errnoError("ELOOP", path);
|
|
5647
5873
|
const target = await found.mount.conn.call({ op: "readlink", path: found.rel });
|
|
5648
|
-
return this.follow(this.base.resolvePath(
|
|
5874
|
+
return this.follow(this.base.resolvePath(parentOf5(real), target), hops + 1, create);
|
|
5649
5875
|
}
|
|
5650
5876
|
async open(mount, rel2, write) {
|
|
5651
5877
|
return await mount.conn.call({
|
|
@@ -6159,6 +6385,8 @@ async function ranged(fs, path, req) {
|
|
|
6159
6385
|
else await ops.truncate(path, req.length ?? 0);
|
|
6160
6386
|
return done;
|
|
6161
6387
|
}
|
|
6388
|
+
var UNLINKED_OPS = /* @__PURE__ */ new Set(["read", "pread", "pwrite", "truncate"]);
|
|
6389
|
+
var OWN_OPS = /* @__PURE__ */ new Set(["rm", "unlink", "rmdir", "rename"]);
|
|
6162
6390
|
async function dispatchSyncFs(req) {
|
|
6163
6391
|
const entry = resolveSyncFsToken(req.token);
|
|
6164
6392
|
if (!entry) return { ok: false, errno: "EACCES", message: "sync-fs: unknown or revoked token" };
|
|
@@ -6167,11 +6395,22 @@ async function dispatchSyncFs(req) {
|
|
|
6167
6395
|
const path = fs.resolvePath(cwd, req.path);
|
|
6168
6396
|
if (req.op === "statfs") return json2(await entry.statfs?.(path) ?? await storageUsage());
|
|
6169
6397
|
if (req.op === "hold") {
|
|
6170
|
-
|
|
6398
|
+
const [on, open] = [((req.mode ?? 0) & 1) === 1, ((req.mode ?? 0) & 2) === 2];
|
|
6399
|
+
const real = on && open ? await fs.realpath?.(path, true).catch(() => path) : void 0;
|
|
6400
|
+
entry.hold?.(path, on, open, real);
|
|
6171
6401
|
return done;
|
|
6172
6402
|
}
|
|
6173
6403
|
if (entry.revoked?.(path)) return { ok: false, errno: "EIO", message: `${path} was unmounted` };
|
|
6174
|
-
|
|
6404
|
+
if (req.op === "write" && req.mode === 1 || UNLINKED_OPS.has(req.op)) {
|
|
6405
|
+
const serve = async () => {
|
|
6406
|
+
const kept = entry.unlinked?.(path);
|
|
6407
|
+
return run(entry, kept && !await fs.exists(path) ? kept.hidden : path, req);
|
|
6408
|
+
};
|
|
6409
|
+
return await (entry.gate ? entry.gate(path, serve) : serve());
|
|
6410
|
+
}
|
|
6411
|
+
if (!entry.own || !OWN_OPS.has(req.op)) return await run(entry, path, req);
|
|
6412
|
+
const target = req.op === "rename" ? fs.resolvePath(cwd, req.arg2 ?? "") : path;
|
|
6413
|
+
return await entry.own(target, () => run(entry, path, req));
|
|
6175
6414
|
} catch (err) {
|
|
6176
6415
|
return toErrno(err);
|
|
6177
6416
|
}
|
|
@@ -7559,7 +7798,7 @@ function isMissing(err) {
|
|
|
7559
7798
|
if (err?.code === "ENOENT") return true;
|
|
7560
7799
|
return err instanceof Error && err.message.startsWith("ENOENT");
|
|
7561
7800
|
}
|
|
7562
|
-
function
|
|
7801
|
+
function within5(path, root) {
|
|
7563
7802
|
return path === root || path.startsWith(root.endsWith("/") ? root : `${root}/`);
|
|
7564
7803
|
}
|
|
7565
7804
|
var VfsNode = class {
|
|
@@ -7764,7 +8003,7 @@ var VfsNodes = class {
|
|
|
7764
8003
|
return run2;
|
|
7765
8004
|
}
|
|
7766
8005
|
async entryKey(path) {
|
|
7767
|
-
const parent =
|
|
8006
|
+
const parent = parentOf6(path);
|
|
7768
8007
|
const dir = await this.real(parent, true, () => realDir(this.fs, parent));
|
|
7769
8008
|
return joinName(dir, path.slice(path.lastIndexOf("/") + 1));
|
|
7770
8009
|
}
|
|
@@ -7812,12 +8051,12 @@ var VfsNodes = class {
|
|
|
7812
8051
|
return node;
|
|
7813
8052
|
}
|
|
7814
8053
|
holds(prefix) {
|
|
7815
|
-
for (const path of this.byPath.keys()) if (
|
|
8054
|
+
for (const path of this.byPath.keys()) if (within5(path, prefix)) return true;
|
|
7816
8055
|
return false;
|
|
7817
8056
|
}
|
|
7818
8057
|
writes(prefix) {
|
|
7819
8058
|
for (const [path, node] of this.byPath)
|
|
7820
|
-
if (node.writers > 0 &&
|
|
8059
|
+
if (node.writers > 0 && within5(path, prefix)) return true;
|
|
7821
8060
|
return false;
|
|
7822
8061
|
}
|
|
7823
8062
|
closed(node, writable = false) {
|
|
@@ -7831,24 +8070,24 @@ var VfsNodes = class {
|
|
|
7831
8070
|
this.onClosed?.(node.path);
|
|
7832
8071
|
}
|
|
7833
8072
|
async flush(path) {
|
|
7834
|
-
for (const [p, node] of this.byPath) if (
|
|
8073
|
+
for (const [p, node] of this.byPath) if (within5(p, path)) await node.serial(() => node.flush());
|
|
7835
8074
|
}
|
|
7836
8075
|
async unlinking(path) {
|
|
7837
8076
|
for (const [held, node] of [...this.byPath]) {
|
|
7838
|
-
if (!
|
|
8077
|
+
if (!within5(held, path)) continue;
|
|
7839
8078
|
await node.serial(async () => void await Promise.all([node.keep(), node.limit()]));
|
|
7840
8079
|
}
|
|
7841
8080
|
}
|
|
7842
8081
|
revoke(prefix) {
|
|
7843
8082
|
for (const [path, node] of this.byPath) {
|
|
7844
|
-
if (!
|
|
8083
|
+
if (!within5(path, prefix)) continue;
|
|
7845
8084
|
node.revoke();
|
|
7846
8085
|
this.byPath.delete(path);
|
|
7847
8086
|
}
|
|
7848
8087
|
}
|
|
7849
8088
|
unlinked(path) {
|
|
7850
8089
|
for (const [held, node] of [...this.byPath]) {
|
|
7851
|
-
if (!
|
|
8090
|
+
if (!within5(held, path)) continue;
|
|
7852
8091
|
node.orphaned = true;
|
|
7853
8092
|
this.byPath.delete(held);
|
|
7854
8093
|
}
|
|
@@ -7857,8 +8096,8 @@ var VfsNodes = class {
|
|
|
7857
8096
|
if (from === to) return;
|
|
7858
8097
|
const moved2 = [];
|
|
7859
8098
|
for (const [p, node] of this.byPath) {
|
|
7860
|
-
if (
|
|
7861
|
-
else if (
|
|
8099
|
+
if (within5(p, from)) moved2.push(node);
|
|
8100
|
+
else if (within5(p, to)) {
|
|
7862
8101
|
node.orphaned = true;
|
|
7863
8102
|
this.byPath.delete(p);
|
|
7864
8103
|
}
|
|
@@ -7881,7 +8120,7 @@ async function present(fs, path, follow = false) {
|
|
|
7881
8120
|
}
|
|
7882
8121
|
}
|
|
7883
8122
|
var MAX_LINKS5 = 40;
|
|
7884
|
-
function
|
|
8123
|
+
function parentOf6(path) {
|
|
7885
8124
|
return path.slice(0, path.lastIndexOf("/")) || "/";
|
|
7886
8125
|
}
|
|
7887
8126
|
async function linkOf(fs, path) {
|
|
@@ -7894,7 +8133,7 @@ async function realDir(fs, dir, budget = { hops: 0 }) {
|
|
|
7894
8133
|
const part = queue.shift();
|
|
7895
8134
|
if (part === ".") continue;
|
|
7896
8135
|
if (part === "..") {
|
|
7897
|
-
at =
|
|
8136
|
+
at = parentOf6(at);
|
|
7898
8137
|
continue;
|
|
7899
8138
|
}
|
|
7900
8139
|
const next = at === "/" ? `/${part}` : `${at}/${part}`;
|
|
@@ -7918,7 +8157,7 @@ function joinName(dir, name2) {
|
|
|
7918
8157
|
}
|
|
7919
8158
|
async function realTarget(fs, path) {
|
|
7920
8159
|
const budget = { hops: 0 };
|
|
7921
|
-
let dir = await realDir(fs,
|
|
8160
|
+
let dir = await realDir(fs, parentOf6(path), budget);
|
|
7922
8161
|
let name2 = path.slice(path.lastIndexOf("/") + 1);
|
|
7923
8162
|
for (; ; ) {
|
|
7924
8163
|
const link2 = await linkOf(fs, joinName(dir, name2));
|
|
@@ -7926,13 +8165,13 @@ async function realTarget(fs, path) {
|
|
|
7926
8165
|
if (budget.hops >= MAX_LINKS5) throw fsError("ELOOP", path);
|
|
7927
8166
|
budget.hops++;
|
|
7928
8167
|
const target = link2.startsWith("/") ? link2 : joinName(dir, link2);
|
|
7929
|
-
dir = await realDir(fs,
|
|
8168
|
+
dir = await realDir(fs, parentOf6(target), budget);
|
|
7930
8169
|
name2 = target.slice(target.lastIndexOf("/") + 1);
|
|
7931
8170
|
}
|
|
7932
8171
|
return joinName(dir, name2);
|
|
7933
8172
|
}
|
|
7934
8173
|
async function targetDir(fs, path) {
|
|
7935
|
-
return
|
|
8174
|
+
return parentOf6(await realTarget(fs, path).catch(() => path));
|
|
7936
8175
|
}
|
|
7937
8176
|
async function refuseReadonly(fs, path, flags, opts = {}) {
|
|
7938
8177
|
const writes = (flags & O_ACCMODE) !== 0 || opts.truncate === true;
|
|
@@ -8321,7 +8560,7 @@ var WasmProcess = class {
|
|
|
8321
8560
|
const fd = this.fds.install(heldFile(), 3);
|
|
8322
8561
|
try {
|
|
8323
8562
|
const nodes = this.nodes;
|
|
8324
|
-
const path = req.orphan ? req.path : req.exclusive ? await nodes.entryKey(req.path) : await nodes.targetKey(req.path);
|
|
8563
|
+
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));
|
|
8325
8564
|
const open = { ...req, path };
|
|
8326
8565
|
if (req.exclusive) {
|
|
8327
8566
|
await nodes.createExclusive(path);
|
|
@@ -8477,6 +8716,11 @@ var WasmProcess = class {
|
|
|
8477
8716
|
return { ok: true, kind: "json", json: await file.stat() };
|
|
8478
8717
|
}
|
|
8479
8718
|
}
|
|
8719
|
+
keptPath;
|
|
8720
|
+
async keptOr(path) {
|
|
8721
|
+
const kept = this.keptPath?.(path);
|
|
8722
|
+
return kept !== void 0 && !await present(this.options.fs, path, true) ? kept : path;
|
|
8723
|
+
}
|
|
8480
8724
|
async promote(req) {
|
|
8481
8725
|
if (!this.fds.get(req.fd).file.held) throw new KernelError("EBADF");
|
|
8482
8726
|
if (req.share !== void 0) {
|
|
@@ -8484,7 +8728,7 @@ var WasmProcess = class {
|
|
|
8484
8728
|
return;
|
|
8485
8729
|
}
|
|
8486
8730
|
if (req.path === void 0) throw new KernelError("EINVAL");
|
|
8487
|
-
const path = req.orphan ? req.path : await this.nodes.targetKey(req.path);
|
|
8731
|
+
const path = req.orphan ? req.path : await this.keptOr(await this.nodes.targetKey(req.path));
|
|
8488
8732
|
if (!this.fds.get(req.fd).file.held) throw new KernelError("EBADF");
|
|
8489
8733
|
const file = vfsFile(
|
|
8490
8734
|
this.options.fs,
|
|
@@ -8843,6 +9087,19 @@ function inherited(opts) {
|
|
|
8843
9087
|
...opts.umask !== void 0 ? { umask: opts.umask } : {}
|
|
8844
9088
|
};
|
|
8845
9089
|
}
|
|
9090
|
+
function holdingToken(opts, holds) {
|
|
9091
|
+
return mintSyncFsToken({
|
|
9092
|
+
fs: opts.fs,
|
|
9093
|
+
cwd: opts.cwd,
|
|
9094
|
+
...opts.statfs ? { statfs: opts.statfs } : {},
|
|
9095
|
+
hold: (path, on, open, real) => holds.hold(path, on, open, real),
|
|
9096
|
+
unlinked: (path) => holds.keptAt(path),
|
|
9097
|
+
gate: (path, op) => opts.unlinkGate && holds.holding(path) ? opts.unlinkGate.run(holds.keyOf(path), op) : op(),
|
|
9098
|
+
own: (path, op) => holds.own(path, op),
|
|
9099
|
+
revoked: (path) => holds.isRevoked(path),
|
|
9100
|
+
renamed: (from, to) => holds.renamed(from, to)
|
|
9101
|
+
});
|
|
9102
|
+
}
|
|
8846
9103
|
function spawnWasmProcess(opts) {
|
|
8847
9104
|
const sab = new SharedArrayBuffer(SAB_HEADER_BYTES + SAB_DEFAULT_WINDOW_BYTES);
|
|
8848
9105
|
const header2 = new Int32Array(sab, 0, SAB_HEADER_I32);
|
|
@@ -8887,14 +9144,8 @@ function spawnWasmProcess(opts) {
|
|
|
8887
9144
|
});
|
|
8888
9145
|
const holds = new HeldPaths();
|
|
8889
9146
|
opts.held?.add(holds);
|
|
8890
|
-
const token =
|
|
8891
|
-
|
|
8892
|
-
cwd: opts.cwd,
|
|
8893
|
-
...opts.statfs ? { statfs: opts.statfs } : {},
|
|
8894
|
-
hold: (path, on) => holds.hold(path, on),
|
|
8895
|
-
revoked: (path) => holds.isRevoked(path),
|
|
8896
|
-
renamed: (from, to) => holds.renamed(from, to)
|
|
8897
|
-
});
|
|
9147
|
+
const token = holdingToken(opts, holds);
|
|
9148
|
+
process.keptPath = (path) => holds.keptAt(path)?.hidden;
|
|
8898
9149
|
const worker = opts.createWorker();
|
|
8899
9150
|
const dying = new Dying();
|
|
8900
9151
|
const dispatch2 = (req) => dying.serving(() => isWasmSyscall(req) ? process.syscall(req) : dispatchSyncFs(req));
|
|
@@ -10534,6 +10785,7 @@ var Launcher = class {
|
|
|
10534
10785
|
deciding = /* @__PURE__ */ new Map();
|
|
10535
10786
|
identities = { asked: 0, timedOut: 0 };
|
|
10536
10787
|
nodes;
|
|
10788
|
+
unlinked;
|
|
10537
10789
|
held = new HeldSet((path) => this.released(path));
|
|
10538
10790
|
processMounts;
|
|
10539
10791
|
booting = new AbortController();
|
|
@@ -10578,7 +10830,15 @@ var Launcher = class {
|
|
|
10578
10830
|
const fs = withCommandDirs(this.base, async () => new Set((await this.commands()).keys()));
|
|
10579
10831
|
this.nodes = new VfsNodes(fs, (path) => this.released(path));
|
|
10580
10832
|
this.openFiles.add(this.nodes);
|
|
10581
|
-
this.
|
|
10833
|
+
this.unlinked = new UnlinkedKeeper(
|
|
10834
|
+
fs,
|
|
10835
|
+
this.held,
|
|
10836
|
+
(path) => this.mountRoot(path),
|
|
10837
|
+
webLocks(),
|
|
10838
|
+
(path) => [...this.openFiles].some((nodes) => nodes.holds(path))
|
|
10839
|
+
);
|
|
10840
|
+
this.fs = keepingOpen(this.unlinked.wrap(), this.nodes);
|
|
10841
|
+
void this.unlinked.sweep("/");
|
|
10582
10842
|
this.watchers.watch([this.modulesDir, this.pnpmHome], { recursive: true }, () => {
|
|
10583
10843
|
this.catalog = void 0;
|
|
10584
10844
|
this.binfmts = void 0;
|
|
@@ -10788,6 +11048,7 @@ var Launcher = class {
|
|
|
10788
11048
|
openFiles: this.openFiles,
|
|
10789
11049
|
nodes: this.nodes,
|
|
10790
11050
|
held: this.held,
|
|
11051
|
+
unlinkGate: this.unlinked.gate,
|
|
10791
11052
|
statfs: (path) => this.mounts.statfs(path),
|
|
10792
11053
|
mounts: () => this.mounts.list(),
|
|
10793
11054
|
mount: (call, signal) => this.processMount(pid, call, signal),
|
|
@@ -10945,6 +11206,7 @@ var Launcher = class {
|
|
|
10945
11206
|
}
|
|
10946
11207
|
released(path) {
|
|
10947
11208
|
const at = normalizePath(path);
|
|
11209
|
+
this.unlinked.closed(at);
|
|
10948
11210
|
const open = [...this.openFiles].some((nodes) => nodes.writes(at)) || heldUnder(this.held, at);
|
|
10949
11211
|
if (!open) void this.mounts.commit(at).catch(() => void 0);
|
|
10950
11212
|
}
|
|
@@ -11040,10 +11302,23 @@ var Launcher = class {
|
|
|
11040
11302
|
}
|
|
11041
11303
|
};
|
|
11042
11304
|
}
|
|
11043
|
-
mount(spec) {
|
|
11044
|
-
|
|
11045
|
-
|
|
11046
|
-
|
|
11305
|
+
async mount(spec) {
|
|
11306
|
+
const fsa = spec.type === "fsa" && !spec.source.startsWith("fsa:");
|
|
11307
|
+
const entry = await this.mounts.mount(
|
|
11308
|
+
fsa ? { ...spec, source: `fsa:${crypto.randomUUID()}` } : spec,
|
|
11309
|
+
this.fs
|
|
11310
|
+
);
|
|
11311
|
+
void this.unlinked.sweep(entry.target);
|
|
11312
|
+
return entry;
|
|
11313
|
+
}
|
|
11314
|
+
mountRoot(path) {
|
|
11315
|
+
let root = "/";
|
|
11316
|
+
for (const { target } of this.mounts.list()) {
|
|
11317
|
+
if (target.length > root.length && (path === target || path.startsWith(`${target}/`))) {
|
|
11318
|
+
root = target;
|
|
11319
|
+
}
|
|
11320
|
+
}
|
|
11321
|
+
return root;
|
|
11047
11322
|
}
|
|
11048
11323
|
async insert(target, handle, source) {
|
|
11049
11324
|
const at = normalizePath(target);
|
|
@@ -11057,6 +11332,7 @@ var Launcher = class {
|
|
|
11057
11332
|
await this.media.put(slot.id, handle);
|
|
11058
11333
|
slot.medium.insert(handle);
|
|
11059
11334
|
slot.served.invalidate(true);
|
|
11335
|
+
void this.unlinked.sweep(at);
|
|
11060
11336
|
}
|
|
11061
11337
|
umount(target, detach = false) {
|
|
11062
11338
|
const at = normalizePath(target);
|