@openclaw/fs-safe 0.7.0 → 0.7.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/CHANGELOG.md +29 -0
- package/dist/archive.d.ts.map +1 -1
- package/dist/archive.js +0 -1
- package/dist/directory-guard.d.ts +2 -1
- package/dist/directory-guard.d.ts.map +1 -1
- package/dist/directory-guard.js +10 -0
- package/dist/file-lock-sync.d.ts.map +1 -1
- package/dist/file-lock-sync.js +72 -26
- package/dist/file-observation.d.ts +9 -0
- package/dist/file-observation.d.ts.map +1 -0
- package/dist/file-observation.js +22 -0
- package/dist/json.js +6 -6
- package/dist/native-pinned-write-windows.js +1 -1
- package/dist/opened-file-failure.d.ts +7 -0
- package/dist/opened-file-failure.d.ts.map +1 -0
- package/dist/opened-file-failure.js +41 -0
- package/dist/opened-realpath.d.ts.map +1 -1
- package/dist/opened-realpath.js +8 -2
- package/dist/pinned-write.js +2 -1
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +75 -58
- package/dist/sidecar-lock-acquire.d.ts.map +1 -1
- package/dist/sidecar-lock-acquire.js +46 -13
- package/dist/sidecar-lock-reclaim.d.ts +16 -5
- package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
- package/dist/sidecar-lock-reclaim.js +47 -37
- package/dist/sidecar-lock-root.d.ts +3 -0
- package/dist/sidecar-lock-root.d.ts.map +1 -0
- package/dist/sidecar-lock-root.js +76 -0
- package/dist/strict-file-identity.d.ts.map +1 -1
- package/dist/strict-file-identity.js +4 -1
- package/docs/archive.md +12 -4
- package/docs/json.md +3 -0
- package/docs/output.md +6 -3
- package/docs/sidecar-lock.md +56 -4
- package/docs/writing.md +6 -0
- package/package.json +8 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.7.1 - 2026-09-01
|
|
4
|
+
|
|
5
|
+
### Highlights
|
|
6
|
+
|
|
7
|
+
- **Make lock handoffs reliable:** recover from disappearing ownership records within retry budgets, preserve callback errors, and safely retry failed synchronous release cleanup.
|
|
8
|
+
- **Reject corrupt native ZIP reads:** verify decoded entry length against the declared size before returning bytes, while retaining byte caps and CRC checks.
|
|
9
|
+
- **Preserve long destination filenames:** use independent private staging names for Root writes and copies, ZIP extraction, and synchronous JSON writes instead of lengthening the destination basename.
|
|
10
|
+
|
|
11
|
+
### File locks
|
|
12
|
+
|
|
13
|
+
- Recover Root-backed asynchronous handoffs from unlinked opened records with exact descriptor identity and unlink evidence, including Windows resolver `EPERM`/`EBADF` failures. Recheck canonical ancestors and charge discarded observations to retry/deadline budgets without granting release or reclaim authority. ([#189](https://github.com/openclaw/fs-safe/pull/189))
|
|
14
|
+
- Retry Root lock contention when a holder changes between pre-open inspection and opening the file. Discard only the verified stale observation, recheck canonical ancestors, and require fresh exclusive creation; generic reads, creator admission, and release/reclaim authority remain unchanged.
|
|
15
|
+
- Verify reopened Root-created sidecars against the creator's exact serialized bytes and ownership token before admission. Reject replacements and unlinked descriptors, and retain the original creator receipt for failed-acquisition cleanup. ([#189](https://github.com/openclaw/fs-safe/pull/189))
|
|
16
|
+
- Align Windows synchronous lock-parent canonicalization with Root, including short-name expansion, and bound lock-file create/open denials and missing or changed snapshot retries. Preserve the original `EPERM` when a retry budget is exhausted. ([#189](https://github.com/openclaw/fs-safe/pull/189))
|
|
17
|
+
- Determine synchronous lock staleness from the captured payload timestamp or snapshot mtime, avoiding false stale decisions after unlink or replacement. ([#189](https://github.com/openclaw/fs-safe/pull/189))
|
|
18
|
+
- Retain failed synchronous release cleanup for safe retries without double-decrementing references, re-closing a consumed or reused descriptor, or deleting a replacement sidecar. ([#189](https://github.com/openclaw/fs-safe/pull/189))
|
|
19
|
+
- Propagate payload, JSON serialization, and parsing failures unchanged without retrying the failing callback. Scope Root failure evidence to each observation so historical errors cannot trigger retries in later, nested, or concurrent acquisitions. ([#190](https://github.com/openclaw/fs-safe/pull/190))
|
|
20
|
+
|
|
21
|
+
### File writes and archives
|
|
22
|
+
|
|
23
|
+
- Keep private staging names independent of destination basenames across guarded Root writes and copies, native Windows writes, ZIP extraction, and `writeJsonSync()`. Preserve public producer filenames, custom `tempPrefix`, and literal JSON parent-path and drive-relative semantics. ([#191](https://github.com/openclaw/fs-safe/pull/191))
|
|
24
|
+
- Avoid opening an existing target merely to inherit its mode during create-only Root writes, while preserving boundary, alias, hardlink, and type checks. ([#189](https://github.com/openclaw/fs-safe/pull/189))
|
|
25
|
+
- Reject native `readArchiveEntry()` ZIP payloads whose decoded length differs from the declared uncompressed size with `ArchiveFormatError("archive-header-invalid")`, matching JavaScript behavior while preserving `maxBytes` and CRC checks. ([#189](https://github.com/openclaw/fs-safe/pull/189))
|
|
26
|
+
|
|
27
|
+
### Docs and validation
|
|
28
|
+
|
|
29
|
+
- Clarify that invalid UTF-8 or nonzero bytes after NUL terminators in fixed TAR name, linkname, and USTAR prefix fields reject with `ArchiveSecurityError("entry-path")`; this documents existing behavior. ([#189](https://github.com/openclaw/fs-safe/pull/189))
|
|
30
|
+
- Add cross-process contention and release-retry proofs, plus focused lock-admission and ZIP-integrity coverage in JavaScript-fallback and required-native CI paths. Refresh the release checklist for eight-package trusted publishing.
|
|
31
|
+
|
|
3
32
|
## 0.7.0 - 2026-08-31
|
|
4
33
|
|
|
5
34
|
### Highlights
|
package/dist/archive.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"archive.d.ts","sourceRoot":"","sources":["../src/archive.ts"],"names":[],"mappings":"AA+DA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAElE,YAAY,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AACjF,YAAY,EACV,kBAAkB,EAClB,gBAAgB,EAChB,sBAAsB,EACtB,0BAA0B,GAC3B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,kBAAkB,EAClB,yBAAyB,EACzB,wBAAwB,EACxB,gBAAgB,EAChB,wBAAwB,GACzB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC/F,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EACL,wBAAwB,EACxB,iBAAiB,EACjB,6BAA6B,EAC7B,mBAAmB,EACnB,2BAA2B,EAC3B,uBAAuB,EACvB,4BAA4B,EAC5B,iCAAiC,EACjC,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,GAC3B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,kBAAkB,EAAE,KAAK,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AACtF,OAAO,EAAE,oBAAoB,EAAE,KAAK,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AAC3F,OAAO,EACL,kCAAkC,EAClC,iCAAiC,EACjC,4BAA4B,EAC5B,wBAAwB,EACxB,4BAA4B,GAC7B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,8BAA8B,EAAE,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrF,OAAO,EACL,2BAA2B,EAC3B,iCAAiC,EACjC,KAAK,mBAAmB,GACzB,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"archive.d.ts","sourceRoot":"","sources":["../src/archive.ts"],"names":[],"mappings":"AA+DA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAElE,YAAY,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AACjF,YAAY,EACV,kBAAkB,EAClB,gBAAgB,EAChB,sBAAsB,EACtB,0BAA0B,GAC3B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,kBAAkB,EAClB,yBAAyB,EACzB,wBAAwB,EACxB,gBAAgB,EAChB,wBAAwB,GACzB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC/F,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EACL,wBAAwB,EACxB,iBAAiB,EACjB,6BAA6B,EAC7B,mBAAmB,EACnB,2BAA2B,EAC3B,uBAAuB,EACvB,4BAA4B,EAC5B,iCAAiC,EACjC,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,GAC3B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,kBAAkB,EAAE,KAAK,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AACtF,OAAO,EAAE,oBAAoB,EAAE,KAAK,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AAC3F,OAAO,EACL,kCAAkC,EAClC,iCAAiC,EACjC,4BAA4B,EAC5B,wBAAwB,EACxB,4BAA4B,GAC7B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,8BAA8B,EAAE,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrF,OAAO,EACL,2BAA2B,EAC3B,iCAAiC,EACjC,KAAK,mBAAmB,GACzB,MAAM,4BAA4B,CAAC;AAyNpC,wBAAsB,cAAc,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CA6KjF"}
|
package/dist/archive.js
CHANGED
|
@@ -77,7 +77,6 @@ async function writeZipFileEntry(params) {
|
|
|
77
77
|
try {
|
|
78
78
|
await writeSiblingTempFile({
|
|
79
79
|
dir: path.dirname(destinationPath),
|
|
80
|
-
tempPrefix: `.${path.basename(destinationPath)}.fs-safe-archive`,
|
|
81
80
|
chmodDir: false,
|
|
82
81
|
mode: params.mode,
|
|
83
82
|
writeTemp: async (tempPath) => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Stats } from "node:fs";
|
|
1
|
+
import type { BigIntStats, Stats } from "node:fs";
|
|
2
2
|
export type AsyncDirectoryGuard = {
|
|
3
3
|
dir: string;
|
|
4
4
|
realPath: string;
|
|
@@ -15,4 +15,5 @@ export declare function createSyncDirectoryGuard(dir: string): SyncDirectoryGuar
|
|
|
15
15
|
export declare function assertSyncDirectoryGuard(guard: SyncDirectoryGuard): void;
|
|
16
16
|
export declare function createNearestExistingDirectoryGuard(rootReal: string, targetPath: string): Promise<AsyncDirectoryGuard>;
|
|
17
17
|
export declare function createNearestExistingSyncDirectoryGuard(rootReal: string, targetPath: string): SyncDirectoryGuard;
|
|
18
|
+
export declare function inspectDirectoryIdentity(dir: string, expected?: BigIntStats): Promise<BigIntStats>;
|
|
18
19
|
//# sourceMappingURL=directory-guard.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"directory-guard.d.ts","sourceRoot":"","sources":["../src/directory-guard.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"directory-guard.d.ts","sourceRoot":"","sources":["../src/directory-guard.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAUlD,MAAM,MAAM,mBAAmB,GAAG;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,KAAK,CAAC;CACb,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,KAAK,CAAC;CACb,CAAC;AAEF,wBAAsB,yBAAyB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAMzF;AAED,wBAAsB,yBAAyB,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAQzF;AAED,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,GAAG,kBAAkB,CAMxE;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,kBAAkB,GAAG,IAAI,CAQxE;AAED,wBAAsB,mCAAmC,CACvD,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,mBAAmB,CAAC,CAc9B;AAED,wBAAgB,uCAAuC,CACrD,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,GACjB,kBAAkB,CAcpB;AAGD,wBAAsB,wBAAwB,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAMxG"}
|
package/dist/directory-guard.js
CHANGED
|
@@ -3,6 +3,7 @@ import fs from "node:fs/promises";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { FsSafeError } from "./errors.js";
|
|
5
5
|
import { sameFileIdentity } from "./file-identity.js";
|
|
6
|
+
import { inspectFileIdentity } from "./strict-file-identity.js";
|
|
6
7
|
import { isNotFoundPathError } from "./path.js";
|
|
7
8
|
import { directoryComponentNotDirectoryError } from "./root-errors.js";
|
|
8
9
|
export async function createAsyncDirectoryGuard(dir) {
|
|
@@ -69,3 +70,12 @@ export function createNearestExistingSyncDirectoryGuard(rootReal, targetPath) {
|
|
|
69
70
|
}
|
|
70
71
|
return createSyncDirectoryGuard(root);
|
|
71
72
|
}
|
|
73
|
+
// Recovery receipts must retain every identity bit, including on Windows.
|
|
74
|
+
export async function inspectDirectoryIdentity(dir, expected) {
|
|
75
|
+
return await inspectFileIdentity(async () => {
|
|
76
|
+
const stat = await fs.lstat(dir, { bigint: true });
|
|
77
|
+
if (stat.isSymbolicLink() || !stat.isDirectory())
|
|
78
|
+
throw directoryComponentNotDirectoryError();
|
|
79
|
+
return stat;
|
|
80
|
+
}, expected);
|
|
81
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-lock-sync.d.ts","sourceRoot":"","sources":["../src/file-lock-sync.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,
|
|
1
|
+
{"version":3,"file":"file-lock-sync.d.ts","sourceRoot":"","sources":["../src/file-lock-sync.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EASL,KAAK,wBAAwB,EAC9B,MAAM,2BAA2B,CAAC;AASnC,OAAO,KAAK,EACV,0BAA0B,EAC1B,uBAAuB,EACvB,wBAAwB,EACzB,MAAM,yBAAyB,CAAC;AAIjC,MAAM,MAAM,0BAA0B,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI;IACjF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,uBAAuB,CAAC;IAChC,aAAa,CAAC,EAAE,wBAAwB,CAAC;IACzC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,QAAQ,CAAC;IACxB,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE;QACvB,QAAQ,EAAE,MAAM,CAAC;QACjB,oBAAoB,EAAE,MAAM,CAAC;QAC7B,OAAO,EAAE,OAAO,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;QAChB,KAAK,EAAE,MAAM,CAAC;QACd,iBAAiB,EAAE,KAAK,CAAC;KAC1B,KAAK,OAAO,CAAC;IACd,qBAAqB,CAAC,EAAE,CAAC,QAAQ,EAAE,wBAAwB,KAAK,OAAO,CAAC;IACxE,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IACxC,QAAQ,CAAC,EAAE,IAAI,CAAC;IAChB,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,0BAA0B,KAAK,IAAI,CAAC;IAC3D,yBAAyB,CAAC,EAAE,MAAM,CAAC;CACpC,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,eAAe,IAAI,OAAO,CAAC;IAC3B,OAAO,IAAI,IAAI,CAAC;IAChB,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC;CAC1B,CAAC;AAoJF,wBAAgB,mBAAmB,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC1E,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,0BAA0B,CAAC,QAAQ,CAAC,GAC5C,kBAAkB,CAwMpB;AAED,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC1E,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,0BAA0B,CAAC,QAAQ,CAAC,EAC7C,EAAE,EAAE,MAAM,CAAC,GACV,CAAC,CAOH"}
|
package/dist/file-lock-sync.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { FsSafeError } from "./errors.js";
|
|
4
|
-
import { readSidecarLockSnapshotSync, relativeSidecarLockPath, removeSidecarLockIfUnchangedSync, serializeSidecarLockPayload, sidecarLockSnapshotMatches, } from "./sidecar-lock-reclaim.js";
|
|
5
|
-
import { computeSidecarLockDelayMs, sidecarLockPayloadCreatedAtMs, validateSidecarLockRetryOptions, validateSidecarLockTimeoutMs, } from "./sidecar-lock-policy.js";
|
|
4
|
+
import { readSidecarLockSnapshotSync, readSidecarLockRawSnapshotSync, parseSidecarLockSnapshot, relativeSidecarLockPath, removeSidecarLockIfUnchangedSync, serializeSidecarLockPayload, sidecarLockSnapshotMatches, } from "./sidecar-lock-reclaim.js";
|
|
5
|
+
import { computeSidecarLockDelayMs, isTransientLockFileDenial, maxTransientLockDenials, sidecarLockPayloadCreatedAtMs, validateSidecarLockRetryOptions, validateSidecarLockTimeoutMs, } from "./sidecar-lock-policy.js";
|
|
6
6
|
import { getFsSafeLockConfig } from "./lock-config.js";
|
|
7
7
|
import { sleepSync } from "./timing.js";
|
|
8
8
|
const SYNC_HELD_LOCKS_KEY = Symbol.for("fsSafe.syncSidecarLocks");
|
|
@@ -23,7 +23,8 @@ function releaseAllSyncHeldLocks() {
|
|
|
23
23
|
held.timer = undefined;
|
|
24
24
|
}
|
|
25
25
|
try {
|
|
26
|
-
|
|
26
|
+
if (held.fd !== undefined)
|
|
27
|
+
fs.closeSync(held.fd);
|
|
27
28
|
}
|
|
28
29
|
catch {
|
|
29
30
|
// Best-effort process-exit cleanup.
|
|
@@ -53,16 +54,23 @@ function releaseSyncHeldLock(held) {
|
|
|
53
54
|
const heldLocks = getSyncHeldLocks();
|
|
54
55
|
if (heldLocks.get(held.normalizedTargetPath) !== held)
|
|
55
56
|
return false;
|
|
56
|
-
held.refCount
|
|
57
|
-
|
|
57
|
+
if (held.refCount > 1) {
|
|
58
|
+
held.refCount -= 1;
|
|
58
59
|
return false;
|
|
59
|
-
|
|
60
|
+
}
|
|
61
|
+
// Keep the final reference and cleanup receipt until deletion succeeds.
|
|
60
62
|
if (held.timer) {
|
|
61
63
|
clearInterval(held.timer);
|
|
62
64
|
held.timer = undefined;
|
|
63
65
|
}
|
|
64
|
-
|
|
66
|
+
if (held.fd !== undefined) {
|
|
67
|
+
const fd = held.fd;
|
|
68
|
+
// A close error may still free the number; consume ownership before closing.
|
|
69
|
+
held.fd = undefined;
|
|
70
|
+
fs.closeSync(fd);
|
|
71
|
+
}
|
|
65
72
|
removeSidecarLockIfUnchangedSync(held.lockPath, held.snapshot);
|
|
73
|
+
heldLocks.delete(held.normalizedTargetPath);
|
|
66
74
|
return true;
|
|
67
75
|
}
|
|
68
76
|
function createSyncHeldLockHandle(held) {
|
|
@@ -70,8 +78,8 @@ function createSyncHeldLockHandle(held) {
|
|
|
70
78
|
const release = () => {
|
|
71
79
|
if (released)
|
|
72
80
|
return;
|
|
73
|
-
released = true;
|
|
74
81
|
releaseSyncHeldLock(held);
|
|
82
|
+
released = true;
|
|
75
83
|
};
|
|
76
84
|
return {
|
|
77
85
|
lockPath: held.lockPath,
|
|
@@ -81,11 +89,15 @@ function createSyncHeldLockHandle(held) {
|
|
|
81
89
|
[Symbol.dispose]: release,
|
|
82
90
|
};
|
|
83
91
|
}
|
|
92
|
+
function canonicalLockParentSync(parent) {
|
|
93
|
+
// Match Root's async realpath, including Windows short-name expansion.
|
|
94
|
+
return process.platform === "win32" ? fs.realpathSync.native(parent) : fs.realpathSync(parent);
|
|
95
|
+
}
|
|
84
96
|
function normalizeTargetPath(targetPath) {
|
|
85
97
|
const resolved = path.resolve(targetPath);
|
|
86
98
|
fs.mkdirSync(path.dirname(resolved), { recursive: true });
|
|
87
99
|
try {
|
|
88
|
-
return path.join(
|
|
100
|
+
return path.join(canonicalLockParentSync(path.dirname(resolved)), path.basename(resolved));
|
|
89
101
|
}
|
|
90
102
|
catch {
|
|
91
103
|
return resolved;
|
|
@@ -97,23 +109,19 @@ function boundedLockPath(lockPath, lockRoot) {
|
|
|
97
109
|
return resolved;
|
|
98
110
|
relativeSidecarLockPath(lockRoot, resolved);
|
|
99
111
|
const parent = path.dirname(resolved);
|
|
100
|
-
const parentReal =
|
|
112
|
+
const parentReal = canonicalLockParentSync(parent);
|
|
101
113
|
const parentRelative = path.relative(lockRoot.rootReal, parentReal);
|
|
102
114
|
if (parentRelative === ".." || parentRelative.startsWith(`..${path.sep}`) || path.isAbsolute(parentRelative)) {
|
|
103
115
|
throw new FsSafeError("outside-workspace", "sidecar lock parent is outside lockRoot");
|
|
104
116
|
}
|
|
105
117
|
return path.join(parentReal, path.basename(resolved));
|
|
106
118
|
}
|
|
107
|
-
function defaultShouldReclaim(
|
|
108
|
-
const createdAtMs = sidecarLockPayloadCreatedAtMs(payload);
|
|
119
|
+
function defaultShouldReclaim(snapshot, staleMs, nowMs) {
|
|
120
|
+
const createdAtMs = sidecarLockPayloadCreatedAtMs(snapshot.payload);
|
|
109
121
|
if (createdAtMs !== null)
|
|
110
122
|
return nowMs - createdAtMs > staleMs;
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
catch {
|
|
115
|
-
return true;
|
|
116
|
-
}
|
|
123
|
+
// A cooperating holder can unlink after the snapshot; use its observed age.
|
|
124
|
+
return !snapshot.stat || nowMs - snapshot.stat.mtimeMs > staleMs;
|
|
117
125
|
}
|
|
118
126
|
function reclaimGuardExists(reclaimGuardPath) {
|
|
119
127
|
try {
|
|
@@ -149,6 +157,7 @@ export function acquireFileLockSync(targetPath, options) {
|
|
|
149
157
|
const staleRecovery = options.staleRecovery ?? defaults.staleRecovery;
|
|
150
158
|
const startedAt = Date.now();
|
|
151
159
|
let attempt = 0;
|
|
160
|
+
let transientDenials = 0;
|
|
152
161
|
const reclaimGuardPath = `${lockPath}.reclaim`;
|
|
153
162
|
const waitForRetry = () => {
|
|
154
163
|
const elapsed = Date.now() - startedAt;
|
|
@@ -166,19 +175,41 @@ export function acquireFileLockSync(targetPath, options) {
|
|
|
166
175
|
sleepSync(Math.min(computeSidecarLockDelayMs(retry, attempt), remaining));
|
|
167
176
|
attempt += 1;
|
|
168
177
|
};
|
|
178
|
+
const retryLockFileDenial = (error) => {
|
|
179
|
+
if (!isTransientLockFileDenial(error, lockPath) ||
|
|
180
|
+
++transientDenials > maxTransientLockDenials)
|
|
181
|
+
return false;
|
|
182
|
+
try {
|
|
183
|
+
waitForRetry();
|
|
184
|
+
}
|
|
185
|
+
catch (waitError) {
|
|
186
|
+
// Preserve the filesystem diagnosis when the caller's budget runs out.
|
|
187
|
+
if (waitError.code === "file_lock_timeout")
|
|
188
|
+
throw error;
|
|
189
|
+
throw waitError;
|
|
190
|
+
}
|
|
191
|
+
return true;
|
|
192
|
+
};
|
|
169
193
|
while (true) {
|
|
170
194
|
if (reclaimGuardExists(reclaimGuardPath)) {
|
|
171
195
|
waitForRetry();
|
|
172
196
|
continue;
|
|
173
197
|
}
|
|
174
198
|
let fd;
|
|
199
|
+
const payload = options.payload();
|
|
200
|
+
const { raw, ownershipToken } = serializeSidecarLockPayload(payload);
|
|
201
|
+
let lockFileCreateDenied = false;
|
|
175
202
|
try {
|
|
176
|
-
const payload = options.payload();
|
|
177
|
-
const { raw, ownershipToken } = serializeSidecarLockPayload(payload);
|
|
178
203
|
const noFollow = process.platform !== "win32" && typeof fs.constants.O_NOFOLLOW === "number"
|
|
179
204
|
? fs.constants.O_NOFOLLOW
|
|
180
205
|
: 0;
|
|
181
|
-
|
|
206
|
+
try {
|
|
207
|
+
fd = fs.openSync(lockPath, fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL | noFollow, 0o600);
|
|
208
|
+
}
|
|
209
|
+
catch (error) {
|
|
210
|
+
lockFileCreateDenied = isTransientLockFileDenial(error, lockPath);
|
|
211
|
+
throw error;
|
|
212
|
+
}
|
|
182
213
|
fs.writeFileSync(fd, raw, "utf8");
|
|
183
214
|
fs.fsyncSync(fd);
|
|
184
215
|
const snapshot = {
|
|
@@ -226,17 +257,32 @@ export function acquireFileLockSync(targetPath, options) {
|
|
|
226
257
|
fd = undefined;
|
|
227
258
|
removeSidecarLockIfUnchangedSync(lockPath, failed);
|
|
228
259
|
}
|
|
260
|
+
if (lockFileCreateDenied && retryLockFileDenial(error))
|
|
261
|
+
continue;
|
|
229
262
|
if (error.code !== "EEXIST")
|
|
230
263
|
throw error;
|
|
231
264
|
if (heldLocks.has(normalizedTargetPath)) {
|
|
232
265
|
waitForRetry();
|
|
233
266
|
continue;
|
|
234
267
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
268
|
+
let rawSnapshot;
|
|
269
|
+
let lockFileOpenDenied = false;
|
|
270
|
+
try {
|
|
271
|
+
rawSnapshot = readSidecarLockRawSnapshotSync(lockPath, {
|
|
272
|
+
rejectNonFile: true,
|
|
273
|
+
onOpenFailure: (error) => { lockFileOpenDenied = isTransientLockFileDenial(error, lockPath); },
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
catch (readError) {
|
|
277
|
+
if (lockFileOpenDenied && retryLockFileDenial(readError))
|
|
278
|
+
continue;
|
|
279
|
+
throw readError;
|
|
280
|
+
}
|
|
281
|
+
const snapshot = parseSidecarLockSnapshot(rawSnapshot, options.parsePayload);
|
|
282
|
+
if (!snapshot) {
|
|
283
|
+
waitForRetry();
|
|
239
284
|
continue;
|
|
285
|
+
}
|
|
240
286
|
const nowMs = Date.now();
|
|
241
287
|
const reclaim = options.shouldReclaim
|
|
242
288
|
? options.shouldReclaim({
|
|
@@ -247,7 +293,7 @@ export function acquireFileLockSync(targetPath, options) {
|
|
|
247
293
|
nowMs,
|
|
248
294
|
heldByThisProcess: false,
|
|
249
295
|
})
|
|
250
|
-
: defaultShouldReclaim(snapshot
|
|
296
|
+
: defaultShouldReclaim(snapshot, staleMs, nowMs);
|
|
251
297
|
if (reclaim) {
|
|
252
298
|
if (staleRecovery === "remove-if-unchanged" &&
|
|
253
299
|
snapshot.raw !== undefined &&
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
type FailureKind = "identity" | "resolution" | `open:${string}` | `unlinked:${string}` | `changed:${string}`;
|
|
2
|
+
export declare function recordFileObservationFailure(error: unknown, kind: FailureKind): void;
|
|
3
|
+
export declare function isFileObservationFailure(error: unknown, kind: FailureKind): boolean;
|
|
4
|
+
export declare function fileObservation(): {
|
|
5
|
+
run<T>(operation: () => T): T;
|
|
6
|
+
has(error: unknown, kind: FailureKind): boolean;
|
|
7
|
+
};
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=file-observation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-observation.d.ts","sourceRoot":"","sources":["../src/file-observation.ts"],"names":[],"mappings":"AAEA,KAAK,WAAW,GAAG,UAAU,GAAG,YAAY,GAAG,QAAQ,MAAM,EAAE,GAAG,YAAY,MAAM,EAAE,GAAG,WAAW,MAAM,EAAE,CAAC;AAG7G,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CAMpF;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,OAAO,CAEnF;AAGD,wBAAgB,eAAe;IAG3B,GAAG,CAAC,CAAC,aAAa,MAAM,CAAC,GAAG,CAAC;IAC7B,GAAG,QAAQ,OAAO,QAAQ,WAAW,GAAG,OAAO;EAElD"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
const active = new AsyncLocalStorage();
|
|
3
|
+
export function recordFileObservationFailure(error, kind) {
|
|
4
|
+
const failures = active.getStore();
|
|
5
|
+
if (!failures)
|
|
6
|
+
return;
|
|
7
|
+
let kinds = failures.get(error);
|
|
8
|
+
if (!kinds)
|
|
9
|
+
failures.set(error, kinds = new Set());
|
|
10
|
+
kinds.add(kind);
|
|
11
|
+
}
|
|
12
|
+
export function isFileObservationFailure(error, kind) {
|
|
13
|
+
return active.getStore()?.get(error)?.has(kind) === true;
|
|
14
|
+
}
|
|
15
|
+
// Each Root observation owns its receipts, including nested and concurrent reads.
|
|
16
|
+
export function fileObservation() {
|
|
17
|
+
const failures = new Map();
|
|
18
|
+
return {
|
|
19
|
+
run(operation) { return active.run(failures, operation); },
|
|
20
|
+
has(error, kind) { return failures.get(error)?.has(kind) === true; },
|
|
21
|
+
};
|
|
22
|
+
}
|
package/dist/json.js
CHANGED
|
@@ -166,16 +166,16 @@ export function tryReadJsonSync(pathname, options = {}) {
|
|
|
166
166
|
}
|
|
167
167
|
}
|
|
168
168
|
export function writeJsonSync(pathname, data) {
|
|
169
|
-
|
|
170
|
-
const tmpPath =
|
|
169
|
+
// Keep literal parent segments so staging follows the same symlinks as the target.
|
|
170
|
+
const tmpPath = path.format({ ...path.parse(pathname), base: `.fs-safe-${randomUUID()}.tmp` });
|
|
171
171
|
const payload = `${stringifyJsonDocument(data, null, 2)}\n`;
|
|
172
|
-
fsSync.mkdirSync(path.dirname(
|
|
172
|
+
fsSync.mkdirSync(path.dirname(pathname), { recursive: true, mode: JSON_DIR_MODE });
|
|
173
173
|
try {
|
|
174
174
|
const tempIdentity = writeTempJsonFile(tmpPath, payload);
|
|
175
175
|
trySetSecureMode(tmpPath, tempIdentity);
|
|
176
|
-
renameJsonFileWithFallback(tmpPath,
|
|
177
|
-
trySetSecureMode(
|
|
178
|
-
trySyncDirectory(
|
|
176
|
+
renameJsonFileWithFallback(tmpPath, pathname);
|
|
177
|
+
trySetSecureMode(pathname, tempIdentity);
|
|
178
|
+
trySyncDirectory(pathname);
|
|
179
179
|
}
|
|
180
180
|
finally {
|
|
181
181
|
try {
|
|
@@ -24,7 +24,7 @@ export async function runPinnedWriteWindows(binding, params, root, parentFd, par
|
|
|
24
24
|
let renamed = false;
|
|
25
25
|
let completed = false;
|
|
26
26
|
try {
|
|
27
|
-
tempName =
|
|
27
|
+
tempName = `.fs-safe-${randomUUID()}.tmp`;
|
|
28
28
|
tempFd = binding.openBeneath(parentFd, tempName, nativeOpenFlags(fsSync.constants.O_WRONLY | fsSync.constants.O_CREAT | fsSync.constants.O_EXCL)).fd;
|
|
29
29
|
tempIdentity = fsSync.fstatSync(tempFd);
|
|
30
30
|
const verificationIdentity = fsSync.fstatSync(tempFd, { bigint: true });
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { BigIntStats } from "node:fs";
|
|
2
|
+
import type { FileHandle } from "node:fs/promises";
|
|
3
|
+
export declare function recordFileOpenFailure(error: unknown, filePath: string): never;
|
|
4
|
+
export declare function openedPathResolutionError(error?: Error): Error;
|
|
5
|
+
export declare function recordPreOpenFileChange(error: unknown, handle: FileHandle, filePath: string, before: BigIntStats | undefined, opened: BigIntStats | undefined): Promise<void>;
|
|
6
|
+
export declare function recordOpenedFileFailure(error: unknown, handle: FileHandle, filePath: string, identity: BigIntStats): Promise<void>;
|
|
7
|
+
//# sourceMappingURL=opened-file-failure.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"opened-file-failure.d.ts","sourceRoot":"","sources":["../src/opened-file-failure.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAC3C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAKnD,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,KAAK,CAG7E;AAED,wBAAgB,yBAAyB,CACvC,KAAK,GAAE,KAA8E,GACpF,KAAK,CAGP;AAED,wBAAsB,uBAAuB,CAC3C,KAAK,EAAE,OAAO,EACd,MAAM,EAAE,UAAU,EAClB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,WAAW,GAAG,SAAS,EAC/B,MAAM,EAAE,WAAW,GAAG,SAAS,GAC9B,OAAO,CAAC,IAAI,CAAC,CAcf;AAED,wBAAsB,uBAAuB,CAC3C,KAAK,EAAE,OAAO,EACd,MAAM,EAAE,UAAU,EAClB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,WAAW,GACpB,OAAO,CAAC,IAAI,CAAC,CASf"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { FsSafeError } from "./errors.js";
|
|
2
|
+
import { inspectFileIdentity } from "./strict-file-identity.js";
|
|
3
|
+
import { isFileObservationFailure, recordFileObservationFailure } from "./file-observation.js";
|
|
4
|
+
export function recordFileOpenFailure(error, filePath) {
|
|
5
|
+
recordFileObservationFailure(error, `open:${filePath}`);
|
|
6
|
+
throw error;
|
|
7
|
+
}
|
|
8
|
+
export function openedPathResolutionError(error = new FsSafeError("path-mismatch", "unable to resolve opened file path")) {
|
|
9
|
+
recordFileObservationFailure(error, "resolution");
|
|
10
|
+
return error;
|
|
11
|
+
}
|
|
12
|
+
export async function recordPreOpenFileChange(error, handle, filePath, before, opened) {
|
|
13
|
+
if (!isFileObservationFailure(error, "identity") || !before?.isFile() || !opened?.isFile() ||
|
|
14
|
+
![0n, 1n].includes(before.nlink) || ![0n, 1n].includes(opened.nlink))
|
|
15
|
+
return;
|
|
16
|
+
try {
|
|
17
|
+
await inspectFileIdentity(async () => before);
|
|
18
|
+
const current = await inspectFileIdentity(() => handle.stat({ bigint: true }), opened);
|
|
19
|
+
if (current.isFile() && [0n, 1n].includes(current.nlink) &&
|
|
20
|
+
(before.dev !== current.dev || before.ino !== current.ino)) {
|
|
21
|
+
// A stale pathname sample is not proof that its former inode was unlinked.
|
|
22
|
+
recordFileObservationFailure(error, `changed:${filePath}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
// Unknown, changed, or closed descriptor evidence never permits a discard.
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export async function recordOpenedFileFailure(error, handle, filePath, identity) {
|
|
30
|
+
if ((!isFileObservationFailure(error, "resolution") && !isFileObservationFailure(error, "identity")) ||
|
|
31
|
+
!identity.isFile() || identity.nlink > 1n)
|
|
32
|
+
return;
|
|
33
|
+
try {
|
|
34
|
+
const current = await inspectFileIdentity(() => handle.stat({ bigint: true }), identity);
|
|
35
|
+
if (current.isFile() && current.nlink === 0n)
|
|
36
|
+
recordFileObservationFailure(error, `unlinked:${filePath}`);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
// A closed, changed, or unknown descriptor provides no retry evidence.
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"opened-realpath.d.ts","sourceRoot":"","sources":["../src/opened-realpath.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAGnD,OAAO,EAAoB,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAG7E,wBAAsB,kCAAkC,CACtD,MAAM,EAAE,UAAU,EAClB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,CAAC,CAGjB;AAED,wBAAsB,8BAA8B,CAClD,EAAE,EAAE,MAAM,EACV,UAAU,EAAE,gBAAgB,EAC5B,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,CAAC,
|
|
1
|
+
{"version":3,"file":"opened-realpath.d.ts","sourceRoot":"","sources":["../src/opened-realpath.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAGnD,OAAO,EAAoB,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAG7E,wBAAsB,kCAAkC,CACtD,MAAM,EAAE,UAAU,EAClB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,CAAC,CAGjB;AAED,wBAAsB,8BAA8B,CAClD,EAAE,EAAE,MAAM,EACV,UAAU,EAAE,gBAAgB,EAC5B,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,CAAC,CA2CjB"}
|
package/dist/opened-realpath.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import { openedPathResolutionError } from "./opened-file-failure.js";
|
|
4
4
|
import { sameFileIdentity } from "./file-identity.js";
|
|
5
5
|
import { isNotFoundPathError } from "./path.js";
|
|
6
6
|
export async function resolveOpenedFileRealPathForHandle(handle, ioPath) {
|
|
@@ -36,6 +36,12 @@ export async function resolveOpenedFileRealPathForFd(fd, handleStat, ioPath) {
|
|
|
36
36
|
}
|
|
37
37
|
catch (err) {
|
|
38
38
|
if (!isNotFoundPathError(err)) {
|
|
39
|
+
// Windows can fail here on a deleted-but-open file. Brand only this
|
|
40
|
+
// resolver operation; the caller must still prove unlink on the same fd.
|
|
41
|
+
if (process.platform === "win32" && err instanceof Error &&
|
|
42
|
+
["EPERM", "EBADF"].includes(err.code ?? "")) {
|
|
43
|
+
throw openedPathResolutionError(err);
|
|
44
|
+
}
|
|
39
45
|
throw err;
|
|
40
46
|
}
|
|
41
47
|
}
|
|
@@ -43,7 +49,7 @@ export async function resolveOpenedFileRealPathForFd(fd, handleStat, ioPath) {
|
|
|
43
49
|
if (parentResolved) {
|
|
44
50
|
return parentResolved;
|
|
45
51
|
}
|
|
46
|
-
throw
|
|
52
|
+
throw openedPathResolutionError();
|
|
47
53
|
}
|
|
48
54
|
async function resolveOpenedFileRealPathFromParent(handleStat, ioPath, statOptions) {
|
|
49
55
|
let parentReal;
|
package/dist/pinned-write.js
CHANGED
|
@@ -156,7 +156,8 @@ async function runPinnedWriteFallback(params) {
|
|
|
156
156
|
}
|
|
157
157
|
}
|
|
158
158
|
}
|
|
159
|
-
|
|
159
|
+
// Private staging must not consume the destination basename's filename budget.
|
|
160
|
+
const tempPath = path.join(parentPath, `.fs-safe-${randomUUID()}.tmp`);
|
|
160
161
|
const tempFlags = fsSync.constants.O_WRONLY |
|
|
161
162
|
fsSync.constants.O_CREAT |
|
|
162
163
|
fsSync.constants.O_EXCL |
|
package/dist/root-impl.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"root-impl.d.ts","sourceRoot":"","sources":["../src/root-impl.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAe,KAAK,EAAE,MAAM,SAAS,CAAC;AAElD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAInD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAW7D,OAAO,EAGL,KAAK,kBAAkB,EACxB,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"root-impl.d.ts","sourceRoot":"","sources":["../src/root-impl.ts"],"names":[],"mappings":"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"}
|