@ai-ecoverse/slicc-kernel 1.146.3 → 1.146.5
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 +4 -0
- package/dist/kernel-worker.js +123 -18
- package/dist/kernel-worker.js.map +3 -3
- package/dist/node-process-worker.js +78 -15
- package/dist/node-process-worker.js.map +3 -3
- package/dist/node.js +123 -18
- package/dist/node.js.map +3 -3
- package/dist/process-worker.js +78 -15
- package/dist/process-worker.js.map +3 -3
- package/dist/types/fs/ranged.d.ts +1 -0
- package/dist/types/kernel/proc-info.d.ts +1 -0
- package/dist/types/mount/mount-fs.d.ts +11 -1
- package/dist/types/process/wasi/wasi-fds.d.ts +1 -0
- package/dist/types/process/wasi/wasi-files.d.ts +2 -0
- package/dist/types/realm/live-vfs-fs.d.ts +1 -0
- package/dist/types/realm/sync-fs-dispatch.d.ts +1 -0
- package/dist/types/realm/sync-fs-token-registry.d.ts +6 -1
- package/dist/types/realm/sync-fs-wire.d.ts +2 -0
- package/package.json +3 -4
package/README.md
CHANGED
|
@@ -754,6 +754,10 @@ The proxy answers `200` with `Content-Type: application/vnd.slicc.raw-fetch`: a
|
|
|
754
754
|
|
|
755
755
|
**Renaming a directory on the OPFS root** ([#1122](https://github.com/ai-ecoverse/slicc-kernel/issues/1122)): Chrome's OPFS can move a file but not a directory. So the kernel renames a directory by creating the new tree and moving each file into it (copying a file only if it can't move, e.g. while it's open elsewhere), then removing the old tree. Within the kernel the rename is atomic to readers: a lookup or listing under the old or the new name, or of either parent, waits until the whole tree has moved. So a stage-then-rename updater (pnpm's, an atomic config write) is never seen half there. Another tab sharing the root sees it all at once too (see below). A root whose directories can move (the Node memory root) renames them in one step. **If the page reloads or crashes mid-rename**, some files are already in the new tree and the rest are still in the old one. So before the first move, the kernel writes the rename's intent (`{ from, to }`) to its metadata store, and clears it after the last. At the next boot, when no other tab shares the root (the same rule as for the boot's other repair of stale metadata), the kernel finishes every recorded rename: it moves what's left into the new tree, removes the old name, and clears the record. The moved directory and its files keep their owner, mode and inode, as after a rename that wasn't stopped. While another tab shares the root, a kernel finishes only renames whose kernel is gone ([#1141](https://github.com/ai-ecoverse/slicc-kernel/issues/1141)): the renaming kernel holds a Web Lock (`slicc-rename:<id>`, named in the record) for the whole rename, and the browser releases it when that tab closes or its worker ends. A kernel that boots beside a peer, and every kernel that sees a peer go, finishes each recorded rename whose lock is free, taking that lock while it does, so two survivors never replay the same rename and a live kernel's rename is never touched. A record without a lock (written by an older kernel, or where Web Locks are missing) waits for a boot that's alone, as before. While another tab finishes a rename, this kernel's readers wait as for its own, and so do the other tabs'. A kernel whose wait for another tab's rename ends with the lock free and the record still there finishes the rename itself, under that lock, and a boot that's alone takes a record's lock too, so a third tab that boots meanwhile waits for it.
|
|
756
756
|
|
|
757
|
+
**An open file stays bound to the file it opened** ([#1394](https://github.com/ai-ecoverse/slicc-kernel/issues/1394)): as on Linux, a program's open file keeps reaching the file it opened when its name is later renamed over, by a file or a symlink, by the program itself or by another process.
|
|
758
|
+
- **The kernel keeps it:** the program keeps the file's contents before its own rename over the name, as it already did before its own unlink, and a rename by another process sets the held file aside, as an unlink does. Reads and writes through the descriptor reach the old file, and `fstat` keeps its inode.
|
|
759
|
+
- **Another tab:** a tab doesn't know which files another tab holds open, so a rename over the name there can't keep the file. The descriptor then fails with `ESTALE`; it never reads or writes the file the name now names. `/proc/sys/kernel/slicc_stale_fds` counts these.
|
|
760
|
+
|
|
757
761
|
**A rename by another process** ([#1257](https://github.com/ai-ecoverse/slicc-kernel/issues/1257)): as on Linux, where a descriptor names the file and not its path, a program's open file follows a rename of that file, or of a directory above it, by another process.
|
|
758
762
|
- **The journal:** the kernel keeps the last 4096 renames it applied, each from and to a path, and counts them in the change buffer it shares with its processes. A rename in another tab on the same root reaches it through the sidecar's move notice.
|
|
759
763
|
- **Catching up:** before a WASI or Emscripten program uses an open file's path (a read, a write, a seek, an `fsync`, a close, an open), it reads that count once. If another process renamed something since, and the program holds files open, it asks the kernel once for the renames of the paths it holds and moves its open files to their new names. The kernel answers only about the paths the program sent, and, under `users: 'enforce'`, drops every rename whose old or new name the program couldn't look up (search permission on each directory above it), so no process learns names in a directory it can't search. A program's own rename moves the count without asking, when nothing else came in between.
|
package/dist/kernel-worker.js
CHANGED
|
@@ -1376,6 +1376,28 @@ function prefixes(path) {
|
|
|
1376
1376
|
function within2(path, root) {
|
|
1377
1377
|
return path === root || path.startsWith(`${root}/`);
|
|
1378
1378
|
}
|
|
1379
|
+
var unstored = () => Object.assign(new Error("the file system kept fewer bytes than were written"), {
|
|
1380
|
+
code: "ENOSPC"
|
|
1381
|
+
});
|
|
1382
|
+
var CHECKED_BYTES = 16 * 1024 * 1024;
|
|
1383
|
+
var Extent = class {
|
|
1384
|
+
floor = 0;
|
|
1385
|
+
exact;
|
|
1386
|
+
wrote(end) {
|
|
1387
|
+
this.floor = Math.max(this.floor, end);
|
|
1388
|
+
if (this.exact !== void 0) this.exact = Math.max(this.exact, end);
|
|
1389
|
+
}
|
|
1390
|
+
cut(size) {
|
|
1391
|
+
this.exact = size;
|
|
1392
|
+
this.floor = Math.min(this.floor, size);
|
|
1393
|
+
}
|
|
1394
|
+
big() {
|
|
1395
|
+
return Math.max(this.floor, this.exact ?? 0) >= CHECKED_BYTES;
|
|
1396
|
+
}
|
|
1397
|
+
check(size) {
|
|
1398
|
+
if (size < this.floor || this.exact !== void 0 && size < this.exact) throw unstored();
|
|
1399
|
+
}
|
|
1400
|
+
};
|
|
1379
1401
|
var LOCKED = /* @__PURE__ */ new Set(["NotReadableError", "NoModificationAllowedError", "InvalidStateError"]);
|
|
1380
1402
|
var SYNC_BACKOFF_MS = [5, 10, 20, 40, 80];
|
|
1381
1403
|
async function openSync(handle) {
|
|
@@ -1401,14 +1423,22 @@ function writeAll(access2, at, bytes) {
|
|
|
1401
1423
|
}
|
|
1402
1424
|
}
|
|
1403
1425
|
async function positioned(handle, op) {
|
|
1426
|
+
const extent = new Extent();
|
|
1404
1427
|
const access2 = await openSync(handle);
|
|
1405
1428
|
if (access2) {
|
|
1406
1429
|
try {
|
|
1407
1430
|
op({
|
|
1408
|
-
write: (at, bytes) =>
|
|
1409
|
-
|
|
1431
|
+
write: (at, bytes) => {
|
|
1432
|
+
writeAll(access2, at, bytes);
|
|
1433
|
+
extent.wrote(at + bytes.length);
|
|
1434
|
+
},
|
|
1435
|
+
truncate: (n) => {
|
|
1436
|
+
access2.truncate(n);
|
|
1437
|
+
extent.cut(n);
|
|
1438
|
+
}
|
|
1410
1439
|
});
|
|
1411
1440
|
access2.flush();
|
|
1441
|
+
if (access2.getSize) extent.check(access2.getSize());
|
|
1412
1442
|
} finally {
|
|
1413
1443
|
access2.close();
|
|
1414
1444
|
}
|
|
@@ -1417,14 +1447,25 @@ async function positioned(handle, op) {
|
|
|
1417
1447
|
const writable2 = await handle.createWritable({ keepExistingData: true });
|
|
1418
1448
|
try {
|
|
1419
1449
|
await op({
|
|
1420
|
-
write: (at, bytes) =>
|
|
1421
|
-
|
|
1450
|
+
write: async (at, bytes) => {
|
|
1451
|
+
await writable2.write({
|
|
1452
|
+
type: "write",
|
|
1453
|
+
position: at,
|
|
1454
|
+
data: bytes
|
|
1455
|
+
});
|
|
1456
|
+
extent.wrote(at + bytes.length);
|
|
1457
|
+
},
|
|
1458
|
+
truncate: async (n) => {
|
|
1459
|
+
await writable2.truncate(n);
|
|
1460
|
+
extent.cut(n);
|
|
1461
|
+
}
|
|
1422
1462
|
});
|
|
1423
1463
|
} catch (err) {
|
|
1424
1464
|
await writable2.abort();
|
|
1425
1465
|
throw err;
|
|
1426
1466
|
}
|
|
1427
1467
|
await writable2.close();
|
|
1468
|
+
if (extent.big()) extent.check((await handle.getFile()).size);
|
|
1428
1469
|
}
|
|
1429
1470
|
function syncAccessHandles(scope = globalThis) {
|
|
1430
1471
|
const handle = scope.FileSystemFileHandle;
|
|
@@ -2403,6 +2444,11 @@ var OpfsFs = class {
|
|
|
2403
2444
|
writable2 = await this.io(file.createWritable());
|
|
2404
2445
|
await writable2.write(content);
|
|
2405
2446
|
await writable2.close();
|
|
2447
|
+
if (typeof content !== "string" && content.length >= CHECKED_BYTES) {
|
|
2448
|
+
const extent = new Extent();
|
|
2449
|
+
extent.cut(content.length);
|
|
2450
|
+
extent.check((await file.getFile()).size);
|
|
2451
|
+
}
|
|
2406
2452
|
} catch (err) {
|
|
2407
2453
|
await writable2?.abort().catch(() => void 0);
|
|
2408
2454
|
throw translate(err, path, "commit", false);
|
|
@@ -7728,7 +7774,7 @@ function satisfies(version, range2) {
|
|
|
7728
7774
|
}
|
|
7729
7775
|
|
|
7730
7776
|
// src/version.ts
|
|
7731
|
-
var KERNEL_VERSION = true ? "1.146.
|
|
7777
|
+
var KERNEL_VERSION = true ? "1.146.5" : "0.0.0-dev";
|
|
7732
7778
|
var OS_RELEASE = "6.0.0-slicc";
|
|
7733
7779
|
function procVersion() {
|
|
7734
7780
|
return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
|
|
@@ -10631,16 +10677,16 @@ var FreshModules = class {
|
|
|
10631
10677
|
if (size < MAGIC.length && this.fresh.has(normalizePath(path))) await decide(path);
|
|
10632
10678
|
};
|
|
10633
10679
|
}
|
|
10634
|
-
const
|
|
10680
|
+
const bound3 = /* @__PURE__ */ new WeakMap();
|
|
10635
10681
|
return new Proxy(fs, {
|
|
10636
10682
|
get(target, key, receiver) {
|
|
10637
10683
|
if (Object.hasOwn(overrides, key)) return overrides[key];
|
|
10638
10684
|
const value2 = Reflect.get(target, key, receiver);
|
|
10639
10685
|
if (typeof value2 !== "function") return value2;
|
|
10640
|
-
let method =
|
|
10686
|
+
let method = bound3.get(value2);
|
|
10641
10687
|
if (method === void 0) {
|
|
10642
10688
|
method = value2.bind(target);
|
|
10643
|
-
|
|
10689
|
+
bound3.set(value2, method);
|
|
10644
10690
|
}
|
|
10645
10691
|
return method;
|
|
10646
10692
|
}
|
|
@@ -11880,6 +11926,12 @@ var HeldSet = class extends Set {
|
|
|
11880
11926
|
holds2.onIdle = this.idle;
|
|
11881
11927
|
return super.add(holds2);
|
|
11882
11928
|
}
|
|
11929
|
+
doubt(paths, over) {
|
|
11930
|
+
for (const holds2 of this) {
|
|
11931
|
+
for (const path of paths) holds2.doubt(path, true);
|
|
11932
|
+
if (over !== void 0) holds2.doubt(over);
|
|
11933
|
+
}
|
|
11934
|
+
}
|
|
11883
11935
|
delete(holds2) {
|
|
11884
11936
|
const removed = super.delete(holds2);
|
|
11885
11937
|
for (const path of /* @__PURE__ */ new Set([...holds2.keys(), ...holds2.opens.keys()])) this.idle(path);
|
|
@@ -11901,6 +11953,9 @@ var HeldPaths = class extends Map {
|
|
|
11901
11953
|
uses = /* @__PURE__ */ new Map();
|
|
11902
11954
|
lastHandle = 0;
|
|
11903
11955
|
owning;
|
|
11956
|
+
bound = /* @__PURE__ */ new Map();
|
|
11957
|
+
doubted = /* @__PURE__ */ new Map();
|
|
11958
|
+
doubts = 0;
|
|
11904
11959
|
onIdle;
|
|
11905
11960
|
keyOf(path) {
|
|
11906
11961
|
return this.aliases.get(path) ?? path;
|
|
@@ -11921,6 +11976,21 @@ var HeldPaths = class extends Map {
|
|
|
11921
11976
|
handlesAt(path) {
|
|
11922
11977
|
return [...this.handles].filter(([, key]) => key === path).map(([id2]) => id2);
|
|
11923
11978
|
}
|
|
11979
|
+
doubt(over, exact = false) {
|
|
11980
|
+
if (this.bound.size === 0) return;
|
|
11981
|
+
for (const [handle, key] of this.handles) {
|
|
11982
|
+
if (!this.bound.has(handle) || this.keptHandles.has(handle)) continue;
|
|
11983
|
+
if (exact ? key === over : within6(key, over)) this.doubted.set(handle, ++this.doubts);
|
|
11984
|
+
}
|
|
11985
|
+
}
|
|
11986
|
+
boundIno(handle) {
|
|
11987
|
+
const doubt = this.doubted.get(handle);
|
|
11988
|
+
const ino = this.bound.get(handle);
|
|
11989
|
+
return doubt === void 0 || ino === void 0 ? void 0 : { ino, doubt };
|
|
11990
|
+
}
|
|
11991
|
+
trust(handle, doubt) {
|
|
11992
|
+
if (this.doubted.get(handle) === doubt) this.doubted.delete(handle);
|
|
11993
|
+
}
|
|
11924
11994
|
keptFor(handle) {
|
|
11925
11995
|
return this.keptHandles.get(handle);
|
|
11926
11996
|
}
|
|
@@ -11957,8 +12027,16 @@ var HeldPaths = class extends Map {
|
|
|
11957
12027
|
for (const file of [...this.uses.keys()]) this.letGo(file, true);
|
|
11958
12028
|
this.keptHandles.clear();
|
|
11959
12029
|
}
|
|
11960
|
-
hold(path, on, open2 = false, real, handle) {
|
|
11961
|
-
if (open2)
|
|
12030
|
+
hold(path, on, open2 = false, real, handle, ino) {
|
|
12031
|
+
if (open2) {
|
|
12032
|
+
const made2 = this.holdOpen(path, on, real, handle);
|
|
12033
|
+
if (handle !== void 0) {
|
|
12034
|
+
this.bound.delete(handle);
|
|
12035
|
+
this.doubted.delete(handle);
|
|
12036
|
+
}
|
|
12037
|
+
if (made2 !== void 0 && ino !== void 0) this.bound.set(made2, ino);
|
|
12038
|
+
return made2;
|
|
12039
|
+
}
|
|
11962
12040
|
this.holdWrite(path, on);
|
|
11963
12041
|
return void 0;
|
|
11964
12042
|
}
|
|
@@ -13637,7 +13715,7 @@ async function holdOp(entry, path, req) {
|
|
|
13637
13715
|
await stillThere(entry, path, req);
|
|
13638
13716
|
if (req.nofollow && !await ownEntry(entry, path, real ?? path)) continue;
|
|
13639
13717
|
if (!r || !r.missed(path) && !r.changedSince(path, gen) && !r.inFlight?.(path))
|
|
13640
|
-
return json2(entry.hold?.(path, on, open2, real) ?? null);
|
|
13718
|
+
return json2(entry.hold?.(path, on, open2, real, void 0, req.ino) ?? null);
|
|
13641
13719
|
}
|
|
13642
13720
|
}
|
|
13643
13721
|
entry.hold?.(path, on, open2, void 0, req.handle);
|
|
@@ -13787,10 +13865,26 @@ async function linkedElsewhere(fs, path, from) {
|
|
|
13787
13865
|
return !(source && source.ino === st.ino && source.dev === st.dev);
|
|
13788
13866
|
}
|
|
13789
13867
|
async function keptOr(entry, path, handle) {
|
|
13790
|
-
if (handle !== void 0)
|
|
13868
|
+
if (handle !== void 0) {
|
|
13869
|
+
const hidden = entry.keptFor?.(handle)?.hidden;
|
|
13870
|
+
if (hidden !== void 0) return hidden;
|
|
13871
|
+
await bound(entry, path, handle);
|
|
13872
|
+
return path;
|
|
13873
|
+
}
|
|
13791
13874
|
const kept = entry.unlinked?.(path);
|
|
13792
13875
|
return kept && !await entry.fs.exists(path) ? kept.hidden : path;
|
|
13793
13876
|
}
|
|
13877
|
+
async function bound(entry, path, handle) {
|
|
13878
|
+
const bound3 = entry.boundIno?.(handle);
|
|
13879
|
+
if (bound3 === void 0) return;
|
|
13880
|
+
const now = await entry.fs.stat(path).catch(() => void 0);
|
|
13881
|
+
if (now?.ino === bound3.ino) {
|
|
13882
|
+
entry.trust?.(handle, bound3.doubt);
|
|
13883
|
+
return;
|
|
13884
|
+
}
|
|
13885
|
+
staleFds++;
|
|
13886
|
+
throw syncError("ESTALE", path);
|
|
13887
|
+
}
|
|
13794
13888
|
async function keptStat(entry, req) {
|
|
13795
13889
|
const hidden = entry.keptFor?.(req.handle ?? -1)?.hidden;
|
|
13796
13890
|
if (hidden === void 0)
|
|
@@ -13817,6 +13911,10 @@ var served = 0;
|
|
|
13817
13911
|
function servedFsCalls() {
|
|
13818
13912
|
return served;
|
|
13819
13913
|
}
|
|
13914
|
+
var staleFds = 0;
|
|
13915
|
+
function staleFdCount() {
|
|
13916
|
+
return staleFds;
|
|
13917
|
+
}
|
|
13820
13918
|
async function dispatchSyncFs(req) {
|
|
13821
13919
|
served++;
|
|
13822
13920
|
const entry = resolveSyncFsToken(req.token);
|
|
@@ -14592,6 +14690,8 @@ var SYS_TREE = {
|
|
|
14592
14690
|
slicc_cwd_checks: (listing) => `${listing.cwdChecks ?? 0}
|
|
14593
14691
|
`,
|
|
14594
14692
|
slicc_rename_ack_busy: (listing) => `${listing.renameAcks?.busy ?? 0}
|
|
14693
|
+
`,
|
|
14694
|
+
slicc_stale_fds: (listing) => `${listing.staleFds ?? 0}
|
|
14595
14695
|
`,
|
|
14596
14696
|
slicc_rename_acks: (listing) => {
|
|
14597
14697
|
const samples = [...listing.renameAcks?.ackMs ?? []].sort((a, b) => a - b);
|
|
@@ -19264,7 +19364,7 @@ async function writeAll3(file, bytes) {
|
|
|
19264
19364
|
const write = file.write;
|
|
19265
19365
|
for (let at = 0; at < bytes.length; ) at += await write.call(file, bytes.subarray(at));
|
|
19266
19366
|
}
|
|
19267
|
-
function
|
|
19367
|
+
function bound2(file, key) {
|
|
19268
19368
|
const method = file[key];
|
|
19269
19369
|
return typeof method === "function" ? { [key]: method.bind(file) } : {};
|
|
19270
19370
|
}
|
|
@@ -19356,9 +19456,9 @@ function scrubbed(inner, scrubber, idleMs = SCRUB_IDLE_MS) {
|
|
|
19356
19456
|
later(stream.idle());
|
|
19357
19457
|
};
|
|
19358
19458
|
return new OpenFile({
|
|
19359
|
-
...
|
|
19360
|
-
...
|
|
19361
|
-
...
|
|
19459
|
+
...bound2(file, "read"),
|
|
19460
|
+
...bound2(file, "peek"),
|
|
19461
|
+
...bound2(file, "stat"),
|
|
19362
19462
|
poll: () => {
|
|
19363
19463
|
const state = pollFile(file);
|
|
19364
19464
|
return quiet2() ? state : { ...state, writable: false };
|
|
@@ -25077,7 +25177,9 @@ function holdingToken(opts, holds2, caller) {
|
|
|
25077
25177
|
...opts.statfs ? { statfs: opts.statfs } : {},
|
|
25078
25178
|
...opts.renames ? { renames: opts.renames } : {},
|
|
25079
25179
|
...opts.renameSeq ? { renameSeq: opts.renameSeq } : {},
|
|
25080
|
-
hold: (path, on, open2, real, handle) => holds2.hold(path, on, open2, real, handle),
|
|
25180
|
+
hold: (path, on, open2, real, handle, ino) => holds2.hold(path, on, open2, real, handle, ino),
|
|
25181
|
+
boundIno: holds2.boundIno.bind(holds2),
|
|
25182
|
+
trust: holds2.trust.bind(holds2),
|
|
25081
25183
|
keptFor: (handle) => holds2.keptFor(handle),
|
|
25082
25184
|
unlinked: (path) => holds2.keptAt(path),
|
|
25083
25185
|
gate: (path, op) => opts.unlinkGate && holds2.holding(path) ? opts.unlinkGate.run(holds2.keyOf(path), op) : op(),
|
|
@@ -29128,6 +29230,7 @@ var Launcher = class {
|
|
|
29128
29230
|
this.openFiles.add(this.nodes);
|
|
29129
29231
|
options.fs.onPeer?.(() => this.flushOpen());
|
|
29130
29232
|
options.fs.remoteChanges?.((change) => {
|
|
29233
|
+
this.held.doubt(change.paths, change.moved?.to);
|
|
29131
29234
|
if (change.moved) this.moved(change.moved.from, change.moved.to);
|
|
29132
29235
|
this.changes.flush();
|
|
29133
29236
|
this.cwds.touched(change.paths, change.trees);
|
|
@@ -30140,7 +30243,8 @@ var Launcher = class {
|
|
|
30140
30243
|
opfsCalls: this.rootFs.opfsCalls?.() ?? 0,
|
|
30141
30244
|
opfsOps: this.rootFs.opfsOps?.() ?? 0,
|
|
30142
30245
|
cwdChecks: this.cwds.checks,
|
|
30143
|
-
renameAcks: this.rootFs.renameWaits?.()
|
|
30246
|
+
renameAcks: this.rootFs.renameWaits?.(),
|
|
30247
|
+
staleFds: staleFdCount()
|
|
30144
30248
|
}),
|
|
30145
30249
|
sudo: (call, signal) => this.sudo(pid, call, signal),
|
|
30146
30250
|
crontab: (call) => this.crontab(pid, call),
|
|
@@ -31118,6 +31222,7 @@ var Launcher = class {
|
|
|
31118
31222
|
this.renameLog.add(from, to);
|
|
31119
31223
|
if (source) return;
|
|
31120
31224
|
for (const nodes of this.openFiles) nodes.renamed(from, to);
|
|
31225
|
+
for (const holds2 of this.held) holds2.renamed(from, to);
|
|
31121
31226
|
this.watchers.moved(from, to);
|
|
31122
31227
|
}
|
|
31123
31228
|
processChain(fs, nodes, unlinked) {
|