@ai-ecoverse/slicc-kernel 1.116.2 → 1.117.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 +19 -2
- package/dist/kernel-worker.js +128 -23
- package/dist/kernel-worker.js.map +3 -3
- package/dist/node-process-worker.js +23 -8
- package/dist/node-process-worker.js.map +3 -3
- package/dist/node.js +128 -23
- package/dist/node.js.map +3 -3
- package/dist/process-worker.js +23 -8
- package/dist/process-worker.js.map +3 -3
- package/dist/types/kernel/fd-table.d.ts +1 -0
- package/dist/types/kernel/host-ops.d.ts +10 -1
- package/dist/types/kernel/locks.d.ts +11 -0
- package/dist/types/kernel/process.d.ts +2 -0
- package/dist/types/process/wasi/slicc-lock.d.ts +3 -0
- package/dist/types/process/wasi/wasi-fds.d.ts +3 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -618,6 +618,13 @@ Directories are renamed with `FileSystemHandle.move()` where available, else by
|
|
|
618
618
|
- **F_GETLK:** with a conflicting lock, it writes that lock back (`l_whence` 0, its absolute start, its length, the holder's pid). Without one, it sets only `l_type` to `2`.
|
|
619
619
|
- **Errors (WASI numbers):** `EAGAIN` for a conflict on `F_SETLK` (never `EACCES`), `EINTR` when a signal with a handler ends an `F_SETLKW` wait, `EDEADLK` for a wait that would close a cycle, `EBADF` for a bad descriptor or a lock type the descriptor's access mode doesn't allow, and `EINVAL` for a bad `l_type` or `l_whence` or a start before 0.
|
|
620
620
|
- **Semantics:** the same as Emscripten's, above. A libc that finds no import on an older kernel gets `ENOSYS` and keeps its local answer.
|
|
621
|
+
- **Any kind of descriptor** ([#893](https://github.com/ai-ecoverse/slicc-kernel/issues/893)): a pipe, a socket or a terminal takes record locks too, as on Linux, keyed by the object (both ends of a pipe are one object, each socket is its own, a terminal is one object however often it's opened). `l_whence` `CUR` and `END` count from 0 there.
|
|
622
|
+
- **`slicc.flock(fd, op) -> errno`** ([#893](https://github.com/ai-ecoverse/slicc-kernel/issues/893)): BSD `flock(2)` locks, with Linux's semantics, for a WASIX program on any descriptor: a regular file, a directory, a pipe, a socket, a terminal or a `/dev` device.
|
|
623
|
+
- **op:** Linux's bits, `LOCK_SH` `1`, `LOCK_EX` `2` or `LOCK_UN` `8`, optionally with `LOCK_NB` `4`. Anything else is `EINVAL`.
|
|
624
|
+
- **Owner:** the open file description, not the process. A `dup`'d descriptor and one a forked child or a spawned program inherits share it, so the child's `LOCK_EX|LOCK_NB` is granted and its `LOCK_UN` releases the lock for the parent too. So children that `flock(STDOUT)` an inherited STDOUT don't exclude each other, on Linux either: for that, each child opens a lock file itself. A second open of the same object is another description and conflicts: the file opened again (by any name of it), `/dev/tty` opened again, the other end of a pipe.
|
|
625
|
+
- **Rules:** shared locks coexist, an exclusive one excludes every other description, and any access mode takes either kind, so `LOCK_EX` on a read-only open excludes (homescoop#758). A conversion drops the old lock before it takes the new one, as on Linux, so a refused `LOCK_NB` conversion leaves no lock. The lock goes with `LOCK_UN` or when the description's last descriptor closes, at exit or kill too. flock locks and `fcntl` record locks are separate and don't block each other.
|
|
626
|
+
- **Errors (WASI numbers):** `EAGAIN` for a conflict with `LOCK_NB`, `EINTR` when a signal with a handler ends a wait (as for `F_SETLKW`: a libc that wants `SA_RESTART` asks again), `EBADF` for a bad descriptor, `EINVAL` for a bad op. There's no deadlock detection, as on Linux.
|
|
627
|
+
- **Limits:** locks are per kernel ([#675](https://github.com/ai-ecoverse/slicc-kernel/issues/675)). A file the program buffers itself is handed to the kernel at its first `flock`, with every other descriptor the program has open on it. A libc that finds no import on an older kernel gets `ENOSYS`; wasix-libc's `flock()` then keeps its old answer. Emscripten programs and the imports module's `tryLock` are unchanged.
|
|
621
628
|
- **Rules on a mount:**
|
|
622
629
|
- a rename across mounts is `EXDEV`, so `mv` copies;
|
|
623
630
|
- a symlink on the root file system that leads onto a mount, such as `~/proj` → `/mnt/drive/proj`, or a chain from a mount to the root and back, is followed onto the mount by every operation, in every runtime: reads, writes, `stat`, listings, `mkdir`, removals, renames, `chmod`, `chown` and times. Removing the link itself removes the link. Before [#547](https://github.com/ai-ecoverse/slicc-kernel/issues/547) was fixed, the kernel resolved such a link against the directory under the mount point for JS and WASI programs, the embedder's `fs` and attached clients (Emscripten programs weren't affected): reads failed with `ENOENT`, and writes went into that hidden directory, which shows again once the mount is gone;
|
|
@@ -970,7 +977,7 @@ Releases are cut by semantic-release on every push to `main`, in two jobs, and o
|
|
|
970
977
|
Every change is certified in its pull request, before it merges (`cert-review-pr.yml`, next section), and `cert-review` is a required check of the merge queue, so every commit on `main`, and every release built from it, was certified first. A release is then **verified on npm** (`release / verify on npm` in `release.yml`, `tools/cert-review/verify.mjs`), in code, for every tag the run made:
|
|
971
978
|
|
|
972
979
|
- It waits up to an hour for npm to serve the version (an accepted publish can take a while to appear), then downloads the tarball and checks `dist.integrity` (sha512), the SLSA v1 provenance (the tarball's sha512 is its subject; `release.yml` on `refs/heads/main`, a GitHub-hosted builder; `gitCommit` = `gitHead` = the tag, on `main`), and `npm audit signatures`. A version that `release-recover.mjs` skipped because npm's `latest` is newer (#837) has nothing to verify.
|
|
973
|
-
- It then writes `certified @ai-ecoverse/slicc-kernel@<v> sha256=<64 hex> (covers <first>–<v>; ci: <run>; pr-certified: <each merged pull request's cert-review>; known: <open known-issue issues>)` into the GitHub release's notes, between `<!-- cert-verify -->` markers. homescoop's ci reads exactly that prefix and re-hashes the npm tarball against it. `covers` starts after the last version npm serves, so a skipped version is covered by the next. Notes that already carry the identical line stay unchanged.
|
|
980
|
+
- It then writes `certified @ai-ecoverse/slicc-kernel@<v> sha256=<64 hex> (covers <first>–<v>; ci: <run>; pr-certified: <each merged pull request's cert-review at its merge>; known: <open known-issue issues>)` into the GitHub release's notes, between `<!-- cert-verify -->` markers. homescoop's ci reads exactly that prefix and re-hashes the npm tarball against it. `covers` starts after the last version npm serves, so a skipped version is covered by the next. Notes that already carry the identical line stay unchanged. A pull request's `cert-review` is the newest finished one (success, failure or error) on its head at or before its merge, or the newest pending one if none had finished. A re-review that a later `merge-ok` re-label starts on the same head, still pending at the merge or ending after it, doesn't change what the merge queue saw, and a status posted after the merge can't change a release's line.
|
|
974
981
|
- A mismatch writes no line. The job is red with a `supply-chain alert`, and so is a line already in the notes with another sha256.
|
|
975
982
|
|
|
976
983
|
The tools in `tools/cert-review/`:
|
|
@@ -1000,7 +1007,7 @@ The tools in `tools/cert-review/`:
|
|
|
1000
1007
|
|
|
1001
1008
|
`.github/workflows/cert-review-pr.yml` certifies a pull request before it merges, so `main`, and with it npm's `latest`, only gets certified changes. It reports as the `cert-review` commit status, a required check of `main`'s merge queue. Every same-repo pull request gets one (`not behaviour`, `waits for merge-ok`, the review's verdict, or the self-guard's hold). **A pull request from a fork gets none**, because fork code never reaches the pools or the key, so it can't enter the queue: only Lars merges one, after reading it.
|
|
1002
1009
|
|
|
1003
|
-
- **Which pull requests:** those that change shipped behaviour, by a conservative path filter (`behaviourFiles` in `lib.mjs`): `src/`, `build.mjs`, `package.json` and its lock, `tsconfig*.json`, `.releaserc*`, `tools/release-recover.mjs`, the workflows that build or publish (`release.yml`, `ci.yml`), and the reviewer's own code (`tools/cert-review/`, `cert-review*.yml`, `.github/runner.mjs`). **Main's code decides:** every job checks out the pull request's base and fetches only the trees under test, so the path filter, pack, suite, findings, the instructions and record all run from `main`. A pull request that changes the reviewer is reviewed by `main`'s reviewer. The workflow file of a `pull_request` run still comes from the pull request, so a change to the reviewer is held by the **certification self-guard**, the shared composite action `slicc-shared-web/.github/actions/cert-self-guard` at a pinned sha. Its light `self-guard` job runs on every pull request event and every merge group. When the diff touches the reviewer (`.github/workflows/cert*.yml`, `tools/cert-review/**`, `.github/runner.mjs` and the guard's own paths), `cert-review` is red with `reviewer change: needs coordinator review (cert-review-approved on this head)` and no review runs. The label is the Kernel Coordinator's alone: they add `cert-review-approved` after reading the change, and the review then starts with `merge-ok`. The approval is bound to the head's arrival on GitHub, and a push removes the label. A merge group that holds an unapproved reviewer change is red too. If the guard doesn't finish, the status is an error that asks to re-run the failed jobs. **The residual:** the pull request's YAML still decides what runs, so the guard catches an accidental edit, not a deliberate rewrite of this workflow. The pull request that adds this workflow can't pass its own check, because its base has no tools yet. Any other pull request gets a green status that says `not behaviour: <files>`, so the skip is recorded.
|
|
1010
|
+
- **Which pull requests:** those that change shipped behaviour, by a conservative path filter (`behaviourFiles` in `lib.mjs`): `src/`, `build.mjs`, `package.json` and its lock, `tsconfig*.json`, `.releaserc*`, `tools/release-recover.mjs`, the workflows that build or publish (`release.yml`, `ci.yml`), and the reviewer's own code (`tools/cert-review/`, `cert-review*.yml`, `.github/runner.mjs`). **Main's code decides:** every job checks out the pull request's base and fetches only the trees under test, so the path filter, pack, suite, findings, the instructions and record all run from `main`. The merge group's lookup loads its tools from the protected branch itself (`merge_group.base_ref`), not from the group's `base_sha`, which can be the speculative commit of the group ahead and carry the reviewer changes of the pull requests queued before it; `base_sha` is only the lookup's input. A pull request that changes the reviewer is reviewed by `main`'s reviewer. The workflow file of a `pull_request` run still comes from the pull request, so a change to the reviewer is held by the **certification self-guard**, the shared composite action `slicc-shared-web/.github/actions/cert-self-guard` at a pinned sha. Its light `self-guard` job runs on every pull request event and every merge group. When the diff touches the reviewer (`.github/workflows/cert*.yml`, `tools/cert-review/**`, `.github/runner.mjs` and the guard's own paths), `cert-review` is red with `reviewer change: needs coordinator review (cert-review-approved on this head)` and no review runs. The label is the Kernel Coordinator's alone: they add `cert-review-approved` after reading the change, and the review then starts with `merge-ok`. The approval is bound to the head's arrival on GitHub, and a push removes the label. A merge group that holds an unapproved reviewer change is red too. If the guard doesn't finish, the status is an error that asks to re-run the failed jobs. **The residual:** the pull request's YAML still decides what runs, so the guard catches an accidental edit, not a deliberate rewrite of this workflow. The pull request that adds this workflow can't pass its own check, because its base has no tools yet. Any other pull request gets a green status that says `not behaviour: <files>`, so the skip is recorded.
|
|
1004
1011
|
- **Decide by what ships:** when a pull request's behaviour files are only `package.json` and/or `package-lock.json` (a Renovate devDependency bump, say), both sides are packed as usual, and then the `same dist` job (`same.mjs`, in code) compares the two tarballs before anything else runs. It checks the same paths, entry types (a symlink isn't a file) and modes. `package.json` must be equal once `devDependencies`, `version` and `gitHead` are removed, so `scripts` (they ship, a consumer's install runs `postinstall`, and `prepack`/`prepublishOnly` can make the published build differ from the packed one), runtime dependencies, `exports`, `bin`, `files` and `engines` must all match. Every other file must be byte-identical once each side's **exact** version string (build.mjs bakes it into `dist`) is replaced by one placeholder. A version string in one more place, or any looser match, is a difference. Source maps are compared as JSON without their `mappings`, whose columns the longer or shorter version string shifts; the code itself is compared byte for byte. If all of that matches, `cert-review` is green with `not behaviour (identical dist): <candidate sha256> = <base sha256> after normalising <v1>/<v2>`, and the suites, kernel-compat, the review and the record are skipped. `same.json` (uploaded as an artifact) names the rule and both tarballs' full sha256. Any difference, or a `same dist` job that doesn't finish, means the full review. The kernel has no runtime `dependencies`, since esbuild bundles everything into `dist`, so a devDependency that changes the product shows up as a byte difference. A test-only devDependency is checked by `ci / test`, on the pull request and on the merge group.
|
|
1005
1012
|
- **When:** only for a same-repo pull request with `merge-ok`, when the label is added and on every push while it is set. Each pull request has its own concurrency group, and a newer push cancels the older review. A behaviour change without `merge-ok` gets a red status, `waits for merge-ok to be reviewed`. Other labels don't start or cancel anything. Fork code never reaches the pools or the key.
|
|
1006
1013
|
- **What:** the checkout is the event's default `refs/pull/N/merge`, the pull request merged onto `main` at review time, so a review is of what would land, without pushing to the branch. Both sides are packed in the same run, on a GitHub-hosted runner, with no secrets: the candidate (the merge ref, `x.y.z-pr.N.<run>`) and the base (its first parent, `main`). Then:
|
|
@@ -1241,6 +1248,16 @@ A WASIX C program for `test/integration/wasix-locks.test.mjs` (#722). It calls `
|
|
|
1241
1248
|
|
|
1242
1249
|
**Build:** `INSTALL_WASIXCC=<homescoop>/scripts/install-wasixcc.sh WASIX_SYSROOT_28=<unpacked @ai-ecoverse/wasix-sysroot 2025.9.30-28> ./build.sh`, with homescoop's pinned wasixcc 0.4.7 (LLVM 21.1.206, binaryen 133) and Homebrew's binaryen `wasm-opt` 132 on `PATH` for the Asyncify pass (`proc_fork`). Two builds give the same bytes: `bin/wlocktest.wasm` is 56,958 bytes, sha256 `31308a54e13e7dcac497ad9d8a05799008c8beaa4ae3a36c76e218af7235ec23`.
|
|
1243
1250
|
|
|
1251
|
+
### `flocktest`
|
|
1252
|
+
|
|
1253
|
+
A WASIX C program for `test/integration/flock.test.mjs` (#893). It calls `slicc.flock` and `slicc.fcntl_lock` directly (`import_module("slicc")`). Its arguments are a sequence of steps, run in order:
|
|
1254
|
+
- `open SLOT r|w|rw PATH`, `pipe SLOT SLOT`, `pair SLOT SLOT` (a `socketpair`), `dup FROM TO`, `fd SLOT N` (names an inherited descriptor), `close SLOT`;
|
|
1255
|
+
- `flock SLOT OP`, where OP is made of `sh`, `ex`, `un` and `nb` (`exnb` is `LOCK_EX|LOCK_NB`) or is a number; `badfd`; `fcntl SLOT r|w|u` (`F_SETLK` on the whole file);
|
|
1256
|
+
- `fork … end` (the child runs the steps up to the matching `end`, its lines start with `child `, and the parent waits for it before it goes on after `end`);
|
|
1257
|
+
- `touch PATH`, `await PATH`, `sleep MS`, `say TEXT`, `trap` (a `SIGUSR1` handler).
|
|
1258
|
+
|
|
1259
|
+
**Build:** as `wlocktest`. Two builds give the same bytes: `bin/flocktest.wasm` is 58,442 bytes, sha256 `687ecf21c836adda4ef2e328b6032eed28ca7d37efc8e88d3aaefb8fc117115a`.
|
|
1260
|
+
|
|
1244
1261
|
## License
|
|
1245
1262
|
|
|
1246
1263
|
Apache-2.0
|
package/dist/kernel-worker.js
CHANGED
|
@@ -3797,7 +3797,8 @@ function openPipe(capacity) {
|
|
|
3797
3797
|
},
|
|
3798
3798
|
close: () => pipe.closeRead(),
|
|
3799
3799
|
poll: () => ({ readable: pipe.readReady, writable: false, hangup: pipe.writersGone }),
|
|
3800
|
-
changed: (signal) => pipe.changed(signal)
|
|
3800
|
+
changed: (signal) => pipe.changed(signal),
|
|
3801
|
+
identity: pipe
|
|
3801
3802
|
}),
|
|
3802
3803
|
write: new OpenFile({
|
|
3803
3804
|
write: async (bytes, signal) => {
|
|
@@ -3809,7 +3810,8 @@ function openPipe(capacity) {
|
|
|
3809
3810
|
},
|
|
3810
3811
|
close: () => pipe.closeWrite(),
|
|
3811
3812
|
poll: () => ({ readable: false, writable: pipe.writeReady, hangup: pipe.readersGone }),
|
|
3812
|
-
changed: (signal) => pipe.changed(signal)
|
|
3813
|
+
changed: (signal) => pipe.changed(signal),
|
|
3814
|
+
identity: pipe
|
|
3813
3815
|
})
|
|
3814
3816
|
};
|
|
3815
3817
|
}
|
|
@@ -6258,7 +6260,7 @@ function satisfies(version, range2) {
|
|
|
6258
6260
|
}
|
|
6259
6261
|
|
|
6260
6262
|
// src/version.ts
|
|
6261
|
-
var KERNEL_VERSION = true ? "1.
|
|
6263
|
+
var KERNEL_VERSION = true ? "1.117.1" : "0.0.0-dev";
|
|
6262
6264
|
var OS_RELEASE = "6.0.0-slicc";
|
|
6263
6265
|
function procVersion() {
|
|
6264
6266
|
return `Linux version ${OS_RELEASE} (slicc-kernel ${KERNEL_VERSION}) #1 SMP PREEMPT_DYNAMIC
|
|
@@ -9624,6 +9626,7 @@ var FifoTable = class {
|
|
|
9624
9626
|
let open = true;
|
|
9625
9627
|
return new OpenFile({
|
|
9626
9628
|
fifo: path,
|
|
9629
|
+
identity: fifo,
|
|
9627
9630
|
...reads ? {
|
|
9628
9631
|
read: async (max, signal) => {
|
|
9629
9632
|
try {
|
|
@@ -13057,7 +13060,71 @@ function lockRange(start2, len) {
|
|
|
13057
13060
|
if (from < 0) throw new KernelError("EINVAL");
|
|
13058
13061
|
return { start: from, end };
|
|
13059
13062
|
}
|
|
13063
|
+
function changedAt(wakers, key, signal) {
|
|
13064
|
+
return new Promise((resolve2, reject) => {
|
|
13065
|
+
const set3 = wakers.get(key) ?? /* @__PURE__ */ new Set();
|
|
13066
|
+
wakers.set(key, set3);
|
|
13067
|
+
const done4 = () => {
|
|
13068
|
+
set3.delete(wake);
|
|
13069
|
+
if (set3.size === 0 && wakers.get(key) === set3) wakers.delete(key);
|
|
13070
|
+
signal.removeEventListener("abort", abort);
|
|
13071
|
+
};
|
|
13072
|
+
const wake = () => {
|
|
13073
|
+
done4();
|
|
13074
|
+
resolve2();
|
|
13075
|
+
};
|
|
13076
|
+
const abort = () => {
|
|
13077
|
+
done4();
|
|
13078
|
+
reject(new KernelError("EINTR"));
|
|
13079
|
+
};
|
|
13080
|
+
set3.add(wake);
|
|
13081
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
13082
|
+
});
|
|
13083
|
+
}
|
|
13084
|
+
function wakeAt(wakers, key) {
|
|
13085
|
+
for (const wake of [...wakers.get(key) ?? []]) wake();
|
|
13086
|
+
}
|
|
13087
|
+
var FlockTable = class {
|
|
13088
|
+
held = /* @__PURE__ */ new Map();
|
|
13089
|
+
keys = /* @__PURE__ */ new WeakMap();
|
|
13090
|
+
wakers = /* @__PURE__ */ new Map();
|
|
13091
|
+
free(key, owner, kind) {
|
|
13092
|
+
for (const [other, has] of this.held.get(key) ?? [])
|
|
13093
|
+
if (other !== owner && (kind === "exclusive" || has === "exclusive")) return false;
|
|
13094
|
+
return true;
|
|
13095
|
+
}
|
|
13096
|
+
set(key, owner, kind) {
|
|
13097
|
+
const has = this.held.get(key)?.get(owner);
|
|
13098
|
+
if (has === kind) return true;
|
|
13099
|
+
if (has !== void 0) this.drop(owner);
|
|
13100
|
+
if (kind === "unlock") return true;
|
|
13101
|
+
if (!this.free(key, owner, kind)) return false;
|
|
13102
|
+
const holders = this.held.get(key) ?? /* @__PURE__ */ new Map();
|
|
13103
|
+
holders.set(owner, kind);
|
|
13104
|
+
this.held.set(key, holders);
|
|
13105
|
+
this.keys.set(owner, key);
|
|
13106
|
+
return true;
|
|
13107
|
+
}
|
|
13108
|
+
async wait(key, owner, kind, blocking) {
|
|
13109
|
+
if (this.set(key, owner, kind)) return;
|
|
13110
|
+
const signal = blocking();
|
|
13111
|
+
while (!this.set(key, owner, kind)) {
|
|
13112
|
+
if (signal.aborted) throw new KernelError("EINTR");
|
|
13113
|
+
await changedAt(this.wakers, key, signal);
|
|
13114
|
+
}
|
|
13115
|
+
}
|
|
13116
|
+
drop(owner) {
|
|
13117
|
+
const key = this.keys.get(owner);
|
|
13118
|
+
if (key === void 0) return;
|
|
13119
|
+
this.keys.delete(owner);
|
|
13120
|
+
const holders = this.held.get(key);
|
|
13121
|
+
holders?.delete(owner);
|
|
13122
|
+
if (holders?.size === 0) this.held.delete(key);
|
|
13123
|
+
wakeAt(this.wakers, key);
|
|
13124
|
+
}
|
|
13125
|
+
};
|
|
13060
13126
|
var LockTable = class {
|
|
13127
|
+
flocks = new FlockTable();
|
|
13061
13128
|
records = /* @__PURE__ */ new Map();
|
|
13062
13129
|
blocked = /* @__PURE__ */ new Map();
|
|
13063
13130
|
wakers = /* @__PURE__ */ new Map();
|
|
@@ -13115,28 +13182,10 @@ var LockTable = class {
|
|
|
13115
13182
|
return false;
|
|
13116
13183
|
}
|
|
13117
13184
|
changed(key, signal) {
|
|
13118
|
-
return
|
|
13119
|
-
const set3 = this.wakers.get(key) ?? /* @__PURE__ */ new Set();
|
|
13120
|
-
this.wakers.set(key, set3);
|
|
13121
|
-
const done4 = () => {
|
|
13122
|
-
set3.delete(wake);
|
|
13123
|
-
if (set3.size === 0 && this.wakers.get(key) === set3) this.wakers.delete(key);
|
|
13124
|
-
signal.removeEventListener("abort", abort);
|
|
13125
|
-
};
|
|
13126
|
-
const wake = () => {
|
|
13127
|
-
done4();
|
|
13128
|
-
resolve2();
|
|
13129
|
-
};
|
|
13130
|
-
const abort = () => {
|
|
13131
|
-
done4();
|
|
13132
|
-
reject(new KernelError("EINTR"));
|
|
13133
|
-
};
|
|
13134
|
-
set3.add(wake);
|
|
13135
|
-
signal.addEventListener("abort", abort, { once: true });
|
|
13136
|
-
});
|
|
13185
|
+
return changedAt(this.wakers, key, signal);
|
|
13137
13186
|
}
|
|
13138
13187
|
wake(key) {
|
|
13139
|
-
|
|
13188
|
+
wakeAt(this.wakers, key);
|
|
13140
13189
|
}
|
|
13141
13190
|
store(key, locks) {
|
|
13142
13191
|
if (locks.length === 0) this.records.delete(key);
|
|
@@ -13201,6 +13250,7 @@ var HOST_OPS = [
|
|
|
13201
13250
|
"lk-set",
|
|
13202
13251
|
"lk-get",
|
|
13203
13252
|
"lk-close",
|
|
13253
|
+
"flock",
|
|
13204
13254
|
"async-submit",
|
|
13205
13255
|
"async-wait",
|
|
13206
13256
|
"async-take",
|
|
@@ -13353,6 +13403,8 @@ async function hostSyscall(req, ctx) {
|
|
|
13353
13403
|
case "lk-get":
|
|
13354
13404
|
case "lk-close":
|
|
13355
13405
|
return recordLock2(req, ctx);
|
|
13406
|
+
case "flock":
|
|
13407
|
+
return flock(req, ctx);
|
|
13356
13408
|
case "async-submit":
|
|
13357
13409
|
if (!ctx.valid(req.req)) throw new KernelError("EINVAL");
|
|
13358
13410
|
if (req.ack) ctx.ops.ack(req.ack);
|
|
@@ -13375,6 +13427,17 @@ async function hostSyscall(req, ctx) {
|
|
|
13375
13427
|
return done2;
|
|
13376
13428
|
}
|
|
13377
13429
|
}
|
|
13430
|
+
var FLOCK_KINDS = /* @__PURE__ */ new Set(["shared", "exclusive", "unlock"]);
|
|
13431
|
+
async function flock(req, ctx) {
|
|
13432
|
+
const { locks, flockOf } = ctx;
|
|
13433
|
+
if (!locks || !flockOf) throw new KernelError("ENOLCK");
|
|
13434
|
+
if (!FLOCK_KINDS.has(req.kind)) throw new KernelError("EINVAL");
|
|
13435
|
+
const { key, owner } = await flockOf(req.fd);
|
|
13436
|
+
if (req.kind !== "unlock" && req.wait)
|
|
13437
|
+
await locks.flocks.wait(key, owner, req.kind, ctx.blocking);
|
|
13438
|
+
else if (!locks.flocks.set(key, owner, req.kind)) throw new KernelError("EAGAIN");
|
|
13439
|
+
return done2;
|
|
13440
|
+
}
|
|
13378
13441
|
async function recordLock2(req, ctx) {
|
|
13379
13442
|
const { locks, fileKey } = ctx;
|
|
13380
13443
|
if (!locks || !fileKey) throw new KernelError("ENOLCK");
|
|
@@ -17503,6 +17566,18 @@ var fileKeys = /* @__PURE__ */ new WeakMap();
|
|
|
17503
17566
|
function identityKey(st, path) {
|
|
17504
17567
|
return st.ino === void 0 ? path : `${st.dev ?? 0}:${st.ino}`;
|
|
17505
17568
|
}
|
|
17569
|
+
var objectIds = /* @__PURE__ */ new WeakMap();
|
|
17570
|
+
var nextObjectId = 1;
|
|
17571
|
+
function objectKey(file) {
|
|
17572
|
+
const at = file.tty ?? file.identity ?? file.pty ?? file;
|
|
17573
|
+
let id2 = objectIds.get(at);
|
|
17574
|
+
if (id2 === void 0) {
|
|
17575
|
+
id2 = nextObjectId++;
|
|
17576
|
+
objectIds.set(at, id2);
|
|
17577
|
+
}
|
|
17578
|
+
return `object:${id2}`;
|
|
17579
|
+
}
|
|
17580
|
+
var flockHooked = /* @__PURE__ */ new WeakSet();
|
|
17506
17581
|
var DYING = { ok: false, errno: "EINTR", message: "the process is being killed" };
|
|
17507
17582
|
var HOSTS_ONLY = new Resolver({ routes: new Routes() });
|
|
17508
17583
|
function timeLeft(req, started) {
|
|
@@ -17803,6 +17878,7 @@ var WasmProcess = class {
|
|
|
17803
17878
|
pid: this.pid,
|
|
17804
17879
|
owner: () => this.lockOwner(),
|
|
17805
17880
|
fileKey: (target, kind, setting) => this.lockKey(target, kind, setting),
|
|
17881
|
+
flockOf: (fd) => this.flockOf(fd),
|
|
17806
17882
|
locks: this.options.locks,
|
|
17807
17883
|
ops: this.asyncOps,
|
|
17808
17884
|
blocking: () => this.blockingSignal(cancelled),
|
|
@@ -17871,6 +17947,7 @@ var WasmProcess = class {
|
|
|
17871
17947
|
throw new KernelError("EBADF");
|
|
17872
17948
|
const known = file ? fileKeys.get(file) : this.pathKeys.get(target.path ?? "");
|
|
17873
17949
|
if (kind === "close" && !file && target.path !== void 0) this.pathKeys.delete(target.path);
|
|
17950
|
+
if (access2 && !access2.stat) return objectKey(access2);
|
|
17874
17951
|
const want = kind === "read" ? R_OK : kind === "write" ? W_OK : 0;
|
|
17875
17952
|
const path = await this.lockPath(target, want);
|
|
17876
17953
|
if (known !== void 0) return known;
|
|
@@ -17885,6 +17962,33 @@ var WasmProcess = class {
|
|
|
17885
17962
|
else if (this.pathKeys.size < 4096) this.pathKeys.set(path, key);
|
|
17886
17963
|
return key;
|
|
17887
17964
|
}
|
|
17965
|
+
async flockOf(fd) {
|
|
17966
|
+
const open = this.fds.get(fd);
|
|
17967
|
+
const { locks } = this.options;
|
|
17968
|
+
if (locks && !flockHooked.has(open)) {
|
|
17969
|
+
flockHooked.add(open);
|
|
17970
|
+
open.onClose(() => locks.flocks.drop(open));
|
|
17971
|
+
}
|
|
17972
|
+
return { key: await this.objectKeyOf(open), owner: open };
|
|
17973
|
+
}
|
|
17974
|
+
async objectKeyOf(open) {
|
|
17975
|
+
const { file } = open;
|
|
17976
|
+
const meta = file.heldMeta;
|
|
17977
|
+
if (meta && "device" in meta) return `device:${meta.device}`;
|
|
17978
|
+
if (meta && "dir" in meta) {
|
|
17979
|
+
const st2 = await this.options.fs.stat?.(meta.dir).catch(() => void 0);
|
|
17980
|
+
return st2 ? identityKey(st2, meta.dir) : objectKey(file);
|
|
17981
|
+
}
|
|
17982
|
+
const known = fileKeys.get(open);
|
|
17983
|
+
if (known !== void 0) return known;
|
|
17984
|
+
const vfs = await file.stat?.().catch(() => void 0);
|
|
17985
|
+
if (!vfs || vfs.orphan) return objectKey(file);
|
|
17986
|
+
const st = await this.options.fs.stat?.(vfs.path).catch(() => void 0);
|
|
17987
|
+
if (!st) return objectKey(file);
|
|
17988
|
+
const key = identityKey(st, vfs.path);
|
|
17989
|
+
fileKeys.set(open, key);
|
|
17990
|
+
return key;
|
|
17991
|
+
}
|
|
17888
17992
|
async dropRecordLocks(fd) {
|
|
17889
17993
|
const { locks } = this.options;
|
|
17890
17994
|
const owner = this.options.jobs?.shown(this.pid) ?? this.pid;
|
|
@@ -20165,6 +20269,7 @@ function scrubbed(inner, scrubber, idleMs = SCRUB_IDLE_MS) {
|
|
|
20165
20269
|
},
|
|
20166
20270
|
changed,
|
|
20167
20271
|
...file.tty ? { tty: file.tty } : {},
|
|
20272
|
+
identity: file.identity ?? file,
|
|
20168
20273
|
...file.clipboard ? { clipboard: file.clipboard } : {},
|
|
20169
20274
|
...file.write ? {
|
|
20170
20275
|
write: async (bytes, signal) => {
|