@ai-ecoverse/slicc-kernel 1.116.2 → 1.117.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/README.md +18 -1
- 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 +1 -1
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/`:
|
|
@@ -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.0" : "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) => {
|