@openclaw/fs-safe 0.13.1 → 0.15.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/CHANGELOG.md +64 -1
- package/README.md +10 -4
- package/dist/advanced.d.ts +1 -1
- package/dist/advanced.d.ts.map +1 -1
- package/dist/advanced.js +1 -1
- package/dist/archive-merge.d.ts.map +1 -1
- package/dist/archive-merge.js +113 -46
- package/dist/archive-read.d.ts.map +1 -1
- package/dist/archive-read.js +4 -4
- package/dist/archive-zip-directory.d.ts +4 -0
- package/dist/archive-zip-directory.d.ts.map +1 -1
- package/dist/archive-zip-directory.js +2 -0
- package/dist/archive-zip-entry.d.ts +6 -2
- package/dist/archive-zip-entry.d.ts.map +1 -1
- package/dist/archive-zip-entry.js +23 -8
- package/dist/archive-zip-integrity.d.ts.map +1 -1
- package/dist/archive-zip-integrity.js +3 -4
- package/dist/archive-zip-loader.d.ts.map +1 -1
- package/dist/archive-zip-loader.js +107 -31
- package/dist/archive-zip-names.d.ts +1 -0
- package/dist/archive-zip-names.d.ts.map +1 -1
- package/dist/archive-zip-names.js +6 -0
- package/dist/archive.js +6 -5
- package/dist/bounded-read-stream.d.ts +0 -1
- package/dist/bounded-read-stream.d.ts.map +1 -1
- package/dist/bounded-read-stream.js +0 -6
- package/dist/copy-publication.d.ts +6 -0
- package/dist/copy-publication.d.ts.map +1 -1
- package/dist/copy-publication.js +3 -0
- package/dist/copy-tree-portable.d.ts.map +1 -1
- package/dist/copy-tree-portable.js +44 -24
- package/dist/copy.d.ts.map +1 -1
- package/dist/copy.js +29 -11
- package/dist/directory-mode-owner.js +5 -5
- package/dist/file-handle-transfer.d.ts +2 -0
- package/dist/file-handle-transfer.d.ts.map +1 -1
- package/dist/file-handle-transfer.js +57 -2
- package/dist/file-identity.d.ts.map +1 -1
- package/dist/file-identity.js +18 -4
- package/dist/file-lock-sync-admission.d.ts +19 -0
- package/dist/file-lock-sync-admission.d.ts.map +1 -0
- package/dist/file-lock-sync-admission.js +93 -0
- package/dist/file-lock-sync-root-acquire.d.ts +4 -0
- package/dist/file-lock-sync-root-acquire.d.ts.map +1 -0
- package/dist/file-lock-sync-root-acquire.js +370 -0
- package/dist/file-lock-sync-root-arbitration.d.ts +18 -0
- package/dist/file-lock-sync-root-arbitration.d.ts.map +1 -0
- package/dist/file-lock-sync-root-arbitration.js +66 -0
- package/dist/file-lock-sync-root-held.d.ts +34 -0
- package/dist/file-lock-sync-root-held.d.ts.map +1 -0
- package/dist/file-lock-sync-root-held.js +393 -0
- package/dist/file-lock-sync-root-io.d.ts +44 -0
- package/dist/file-lock-sync-root-io.d.ts.map +1 -0
- package/dist/file-lock-sync-root-io.js +209 -0
- package/dist/file-lock-sync-root-mutation.d.ts +17 -0
- package/dist/file-lock-sync-root-mutation.d.ts.map +1 -0
- package/dist/file-lock-sync-root-mutation.js +277 -0
- package/dist/file-lock-sync-root-options.d.ts +20 -0
- package/dist/file-lock-sync-root-options.d.ts.map +1 -0
- package/dist/file-lock-sync-root-options.js +58 -0
- package/dist/file-lock-sync-root-registration.d.ts +2 -0
- package/dist/file-lock-sync-root-registration.d.ts.map +1 -0
- package/dist/file-lock-sync-root-registration.js +90 -0
- package/dist/file-lock-sync-root.d.ts +36 -0
- package/dist/file-lock-sync-root.d.ts.map +1 -0
- package/dist/file-lock-sync-root.js +361 -0
- package/dist/file-lock-sync-stale-admission.d.ts +24 -0
- package/dist/file-lock-sync-stale-admission.d.ts.map +1 -0
- package/dist/file-lock-sync-stale-admission.js +205 -0
- package/dist/file-lock-sync.d.ts.map +1 -1
- package/dist/file-lock-sync.js +245 -205
- package/dist/file-store-prune.d.ts.map +1 -1
- package/dist/file-store-prune.js +5 -1
- package/dist/file-store-sync-directory.d.ts.map +1 -1
- package/dist/file-store-sync-directory.js +14 -7
- package/dist/file-store-sync-write.js +3 -3
- package/dist/file-store.d.ts.map +1 -1
- package/dist/file-store.js +47 -12
- package/dist/guest-dispatch-python.js +1 -1
- package/dist/json-document-store.d.ts.map +1 -1
- package/dist/json-document-store.js +22 -15
- package/dist/json-durable-queue-ownership.d.ts +0 -1
- package/dist/json-durable-queue-ownership.d.ts.map +1 -1
- package/dist/json-durable-queue-ownership.js +0 -6
- package/dist/native-binding.d.ts +8 -0
- package/dist/native-binding.d.ts.map +1 -1
- package/dist/native-parent-admission.d.ts +3 -2
- package/dist/native-parent-admission.d.ts.map +1 -1
- package/dist/native-parent-admission.js +24 -5
- package/dist/native-pinned-write-windows.d.ts.map +1 -1
- package/dist/native-pinned-write-windows.js +7 -7
- package/dist/native-pinned-write.d.ts.map +1 -1
- package/dist/native-pinned-write.js +202 -172
- package/dist/native-policy-directory-observation.d.ts +12 -0
- package/dist/native-policy-directory-observation.d.ts.map +1 -0
- package/dist/native-policy-directory-observation.js +61 -0
- package/dist/native-policy-parent-windows.d.ts +14 -0
- package/dist/native-policy-parent-windows.d.ts.map +1 -0
- package/dist/native-policy-parent-windows.js +200 -0
- package/dist/native-rename-outcome.d.ts +4 -0
- package/dist/native-rename-outcome.d.ts.map +1 -0
- package/dist/native-rename-outcome.js +8 -0
- package/dist/native-staged-file.d.ts +2 -2
- package/dist/native-staged-file.d.ts.map +1 -1
- package/dist/native-staged-file.js +42 -40
- package/dist/output.d.ts.map +1 -1
- package/dist/output.js +12 -8
- package/dist/path-prefix.d.ts.map +1 -1
- package/dist/path-prefix.js +30 -8
- package/dist/path-suffix-aliases.d.ts +2 -0
- package/dist/path-suffix-aliases.d.ts.map +1 -1
- package/dist/path-suffix-aliases.js +25 -17
- package/dist/permission-exec.d.ts +2 -0
- package/dist/permission-exec.d.ts.map +1 -1
- package/dist/permission-exec.js +150 -21
- package/dist/permissions-windows.js +1 -1
- package/dist/pinned-mutation-admission.d.ts.map +1 -1
- package/dist/pinned-mutation-admission.js +23 -29
- package/dist/pinned-mutation-observation.d.ts +7 -2
- package/dist/pinned-mutation-observation.d.ts.map +1 -1
- package/dist/pinned-mutation-observation.js +81 -18
- package/dist/pinned-mutation-shared-route.d.ts +1 -0
- package/dist/pinned-mutation-shared-route.d.ts.map +1 -1
- package/dist/pinned-mutation-shared-route.js +1 -1
- package/dist/pinned-write-types.d.ts +2 -0
- package/dist/pinned-write-types.d.ts.map +1 -1
- package/dist/private-temp-workspace.d.ts.map +1 -1
- package/dist/private-temp-workspace.js +75 -121
- package/dist/regular-file.d.ts.map +1 -1
- package/dist/regular-file.js +1 -15
- package/dist/replace-directory.d.ts.map +1 -1
- package/dist/replace-directory.js +256 -18
- package/dist/replace-file-copy-fallback.d.ts.map +1 -1
- package/dist/replace-file-copy-fallback.js +62 -70
- package/dist/replace-file-copy-source.d.ts.map +1 -1
- package/dist/replace-file-copy-source.js +10 -12
- package/dist/replace-file-temp-owner.d.ts +5 -2
- package/dist/replace-file-temp-owner.d.ts.map +1 -1
- package/dist/replace-file-temp-owner.js +65 -30
- package/dist/replace-file.js +6 -6
- package/dist/retained-directory-replacement.d.ts +26 -0
- package/dist/retained-directory-replacement.d.ts.map +1 -0
- package/dist/retained-directory-replacement.js +193 -0
- package/dist/root-boundary.d.ts +1 -0
- package/dist/root-boundary.d.ts.map +1 -1
- package/dist/root-boundary.js +4 -0
- package/dist/root-context.d.ts +0 -8
- package/dist/root-context.d.ts.map +1 -1
- package/dist/root-context.js +0 -3
- package/dist/root-create-input.d.ts +6 -0
- package/dist/root-create-input.d.ts.map +1 -1
- package/dist/root-create-input.js +5 -1
- package/dist/root-directory-creation.d.ts.map +1 -1
- package/dist/root-directory-creation.js +5 -4
- package/dist/root-directory-list.d.ts +1 -0
- package/dist/root-directory-list.d.ts.map +1 -1
- package/dist/root-directory-list.js +1 -0
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +20 -11
- package/dist/root-move-noreplace.d.ts.map +1 -1
- package/dist/root-move-noreplace.js +2 -2
- package/dist/root-path-errors.d.ts +1 -0
- package/dist/root-path-errors.d.ts.map +1 -1
- package/dist/root-path-errors.js +11 -2
- package/dist/root-path-existing.d.ts.map +1 -1
- package/dist/root-path-existing.js +12 -37
- package/dist/root-path.js +1 -13
- package/dist/root-remove.d.ts +1 -0
- package/dist/root-remove.d.ts.map +1 -1
- package/dist/root-remove.js +4 -0
- package/dist/root-walk.d.ts +1 -1
- package/dist/root-walk.d.ts.map +1 -1
- package/dist/root-walk.js +17 -2
- package/dist/root-write-admission.d.ts +0 -2
- package/dist/root-write-admission.d.ts.map +1 -1
- package/dist/root-write-admission.js +1 -15
- package/dist/root-write-compatibility.d.ts +1 -2
- package/dist/root-write-compatibility.d.ts.map +1 -1
- package/dist/root-write-compatibility.js +17 -68
- package/dist/root-write-complete-parent.d.ts.map +1 -1
- package/dist/root-write-complete-parent.js +10 -24
- package/dist/root-write-lock-binding.d.ts +15 -0
- package/dist/root-write-lock-binding.d.ts.map +1 -0
- package/dist/root-write-lock-binding.js +162 -0
- package/dist/root-write-verification.d.ts.map +1 -1
- package/dist/root-write-verification.js +29 -42
- package/dist/secret-file.d.ts.map +1 -1
- package/dist/secret-file.js +2 -24
- package/dist/secret-read-async.d.ts.map +1 -1
- package/dist/secret-read-async.js +3 -24
- package/dist/secret-read-policy.d.ts +6 -2
- package/dist/secret-read-policy.d.ts.map +1 -1
- package/dist/secret-read-policy.js +26 -2
- package/dist/sibling-temp.d.ts.map +1 -1
- package/dist/sibling-temp.js +23 -12
- package/dist/sidecar-lock-acquire.d.ts +2 -28
- package/dist/sidecar-lock-acquire.d.ts.map +1 -1
- package/dist/sidecar-lock-acquire.js +288 -199
- package/dist/sidecar-lock-admission-context.d.ts +19 -0
- package/dist/sidecar-lock-admission-context.d.ts.map +1 -0
- package/dist/sidecar-lock-admission-context.js +60 -0
- package/dist/sidecar-lock-admission-parser.d.ts +43 -0
- package/dist/sidecar-lock-admission-parser.d.ts.map +1 -0
- package/dist/sidecar-lock-admission-parser.js +113 -0
- package/dist/sidecar-lock-admission.d.ts +35 -0
- package/dist/sidecar-lock-admission.d.ts.map +1 -0
- package/dist/sidecar-lock-admission.js +7 -0
- package/dist/sidecar-lock-reclaim.d.ts +9 -4
- package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
- package/dist/sidecar-lock-reclaim.js +80 -25
- package/dist/sidecar-lock-stale-admission.d.ts +39 -0
- package/dist/sidecar-lock-stale-admission.d.ts.map +1 -0
- package/dist/sidecar-lock-stale-admission.js +232 -0
- package/dist/sidecar-lock-target.d.ts +8 -0
- package/dist/sidecar-lock-target.d.ts.map +1 -0
- package/dist/sidecar-lock-target.js +55 -0
- package/dist/sidecar-lock.d.ts.map +1 -1
- package/dist/sidecar-lock.js +100 -16
- package/dist/staged-directory.d.ts +5 -2
- package/dist/staged-directory.d.ts.map +1 -1
- package/dist/staged-directory.js +37 -1
- package/dist/temp-workspace-admission.d.ts.map +1 -1
- package/dist/temp-workspace-admission.js +28 -4
- package/dist/temp-workspace-descriptor.d.ts.map +1 -1
- package/dist/temp-workspace-descriptor.js +9 -27
- package/dist/temp-workspace-owner.d.ts.map +1 -1
- package/dist/temp-workspace-owner.js +8 -8
- package/dist/walk.d.ts +5 -1
- package/dist/walk.d.ts.map +1 -1
- package/dist/walk.js +19 -6
- package/dist/windows-owner.d.ts.map +1 -1
- package/dist/windows-owner.js +2 -2
- package/docs/advanced.md +1 -1
- package/docs/archive.md +36 -9
- package/docs/atomic.md +85 -8
- package/docs/copy.md +35 -0
- package/docs/file-store.md +19 -0
- package/docs/guest.md +1 -1
- package/docs/json-store.md +5 -0
- package/docs/native-helper.md +10 -3
- package/docs/native.md +8 -0
- package/docs/output.md +6 -0
- package/docs/path-prefix.md +10 -0
- package/docs/path-suffix-aliases.md +51 -6
- package/docs/permissions.md +13 -0
- package/docs/public-api.md +3 -2
- package/docs/root.md +22 -3
- package/docs/sidecar-lock.md +109 -4
- package/docs/staged-file.md +7 -3
- package/docs/temp.md +20 -3
- package/docs/walk.md +67 -1
- package/docs/writing.md +11 -1
- package/package.json +10 -10
- package/dist/darwin-acl.d.ts +0 -4
- package/dist/darwin-acl.d.ts.map +0 -1
- package/dist/darwin-acl.js +0 -24
package/dist/file-lock-sync.js
CHANGED
|
@@ -1,24 +1,17 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
|
+
import { acquireFileLockSyncWithRoot } from "./file-lock-sync-root-acquire.js";
|
|
3
|
+
import { isRootSyncHeldLockHandle, withRootSyncHeldLockHandle } from "./file-lock-sync-root-held.js";
|
|
2
4
|
import path from "node:path";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { computeSidecarLockDelayMs, isTransientLockFileDenial, maxTransientLockDenials, sidecarLockPayloadCreatedAtMs, validateSidecarLockStaleMs, validateSidecarLockCompromiseCheckIntervalMs, validateSidecarLockRetryOptions, validateSidecarLockTimeoutMs, } from "./sidecar-lock-policy.js";
|
|
5
|
+
import { readSidecarLockSnapshotSync, removeSidecarLockIfUnchangedSync, serializeSidecarLockPayload, sidecarLockSnapshotMatches, } from "./sidecar-lock-reclaim.js";
|
|
6
|
+
import { computeSidecarLockDelayMs, isTransientLockFileDenial, maxTransientLockDenials, validateSidecarLockStaleMs, validateSidecarLockCompromiseCheckIntervalMs, validateSidecarLockRetryOptions, validateSidecarLockTimeoutMs, } from "./sidecar-lock-policy.js";
|
|
6
7
|
import { getFsSafeLockConfig } from "./lock-config.js";
|
|
7
8
|
import { sleepSync } from "./timing.js";
|
|
8
9
|
import { assertNoWindowsPathAlias } from "./windows-path-alias.js";
|
|
9
10
|
import { realpathSync } from "./realpath.js";
|
|
10
11
|
import { recursiveMkdirPath } from "./recursive-mkdir-path.js";
|
|
11
12
|
import { createSuppressedError } from "./suppressed-error.js";
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const SYNC_CLEANUP_HANDLER_KEY = Symbol.for("fsSafe.syncSidecarLockCleanupHandler");
|
|
15
|
-
function getSyncHeldLocks() {
|
|
16
|
-
const globalWithState = globalThis;
|
|
17
|
-
if (!globalWithState[SYNC_HELD_LOCKS_KEY]) {
|
|
18
|
-
globalWithState[SYNC_HELD_LOCKS_KEY] = new Map();
|
|
19
|
-
}
|
|
20
|
-
return globalWithState[SYNC_HELD_LOCKS_KEY];
|
|
21
|
-
}
|
|
13
|
+
import { ensureSyncLockExitCleanupRegistered, getSyncHeldLocks, foreignSyncHeldLock, getSyncLockAdmissions, syncReclaimGuardExists, syncLockTimeout, } from "./file-lock-sync-admission.js";
|
|
14
|
+
import { handleSyncStaleAdmission, } from "./file-lock-sync-stale-admission.js";
|
|
22
15
|
function releaseAllSyncHeldLocks() {
|
|
23
16
|
const heldLocks = getSyncHeldLocks();
|
|
24
17
|
for (const [normalizedTargetPath, held] of heldLocks) {
|
|
@@ -41,14 +34,7 @@ function releaseAllSyncHeldLocks() {
|
|
|
41
34
|
}
|
|
42
35
|
heldLocks.delete(normalizedTargetPath);
|
|
43
36
|
}
|
|
44
|
-
|
|
45
|
-
function ensureSyncExitCleanupRegistered() {
|
|
46
|
-
const globalWithCleanup = globalThis;
|
|
47
|
-
if (globalWithCleanup[SYNC_CLEANUP_REGISTERED_KEY])
|
|
48
|
-
return;
|
|
49
|
-
globalWithCleanup[SYNC_CLEANUP_REGISTERED_KEY] = true;
|
|
50
|
-
globalWithCleanup[SYNC_CLEANUP_HANDLER_KEY] = releaseAllSyncHeldLocks;
|
|
51
|
-
process.on("exit", releaseAllSyncHeldLocks);
|
|
37
|
+
getSyncLockAdmissions().clear();
|
|
52
38
|
}
|
|
53
39
|
function verifySyncHeldLock(held) {
|
|
54
40
|
const current = readSidecarLockSnapshotSync(held.lockPath, held.parsePayload);
|
|
@@ -113,42 +99,10 @@ function normalizeTargetPath(targetPath) {
|
|
|
113
99
|
assertNoWindowsPathAlias(normalized);
|
|
114
100
|
return normalized;
|
|
115
101
|
}
|
|
116
|
-
function boundedLockPath(lockPath, lockRoot) {
|
|
117
|
-
const resolved = path.resolve(lockPath);
|
|
118
|
-
assertNoWindowsPathAlias(resolved);
|
|
119
|
-
if (!lockRoot)
|
|
120
|
-
return resolved;
|
|
121
|
-
relativeSidecarLockPath(lockRoot, resolved);
|
|
122
|
-
const parent = path.dirname(resolved);
|
|
123
|
-
const parentReal = canonicalLockParentSync(parent);
|
|
124
|
-
assertNoWindowsPathAlias(parentReal);
|
|
125
|
-
const parentRelative = path.relative(lockRoot.rootReal, parentReal);
|
|
126
|
-
if (parentRelative === ".." || parentRelative.startsWith(`..${path.sep}`) || path.isAbsolute(parentRelative)) {
|
|
127
|
-
throw new FsSafeError("outside-workspace", "sidecar lock parent is outside lockRoot");
|
|
128
|
-
}
|
|
129
|
-
const bounded = path.join(parentReal, path.basename(resolved));
|
|
130
|
-
assertNoWindowsPathAlias(bounded);
|
|
131
|
-
return bounded;
|
|
132
|
-
}
|
|
133
|
-
function defaultShouldReclaim(snapshot, staleMs, nowMs) {
|
|
134
|
-
const createdAtMs = sidecarLockPayloadCreatedAtMs(snapshot.payload);
|
|
135
|
-
if (createdAtMs !== null)
|
|
136
|
-
return nowMs - createdAtMs > staleMs;
|
|
137
|
-
// A cooperating holder can unlink after the snapshot; use its observed age.
|
|
138
|
-
return !snapshot.stat || nowMs - snapshot.stat.mtimeMs > staleMs;
|
|
139
|
-
}
|
|
140
|
-
function reclaimGuardExists(reclaimGuardPath) {
|
|
141
|
-
try {
|
|
142
|
-
fs.lstatSync(reclaimGuardPath);
|
|
143
|
-
return true;
|
|
144
|
-
}
|
|
145
|
-
catch (error) {
|
|
146
|
-
if (error.code === "ENOENT")
|
|
147
|
-
return false;
|
|
148
|
-
throw error;
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
102
|
export function acquireFileLockSync(targetPath, options) {
|
|
103
|
+
const lockRoot = options.lockRoot;
|
|
104
|
+
if (lockRoot)
|
|
105
|
+
return acquireFileLockSyncWithRoot(targetPath, options, lockRoot);
|
|
152
106
|
const defaults = getFsSafeLockConfig();
|
|
153
107
|
const retry = options.retry ?? defaults.retry ?? {};
|
|
154
108
|
const timeoutMs = options.timeoutMs ?? defaults.timeoutMs;
|
|
@@ -156,38 +110,54 @@ export function acquireFileLockSync(targetPath, options) {
|
|
|
156
110
|
validateSidecarLockTimeoutMs(timeoutMs);
|
|
157
111
|
const staleMs = options.staleMs ?? defaults.staleMs ?? 30_000;
|
|
158
112
|
validateSidecarLockStaleMs(staleMs);
|
|
159
|
-
|
|
113
|
+
const compromiseCheckIntervalMs = options.compromiseCheckIntervalMs;
|
|
114
|
+
validateSidecarLockCompromiseCheckIntervalMs(compromiseCheckIntervalMs);
|
|
160
115
|
const explicitLockPath = options.lockPath;
|
|
161
116
|
assertNoWindowsPathAlias(targetPath);
|
|
162
117
|
if (explicitLockPath !== undefined)
|
|
163
118
|
assertNoWindowsPathAlias(explicitLockPath);
|
|
164
119
|
const normalizedTargetPath = normalizeTargetPath(targetPath);
|
|
165
|
-
const lockPath =
|
|
120
|
+
const lockPath = path.resolve(explicitLockPath ?? `${normalizedTargetPath}.lock`);
|
|
121
|
+
assertNoWindowsPathAlias(lockPath);
|
|
122
|
+
const requestedReentrantOwner = options.reentrantOwner;
|
|
166
123
|
const heldLocks = getSyncHeldLocks();
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
124
|
+
const currentTargetHolder = () => heldLocks.get(normalizedTargetPath) ??
|
|
125
|
+
foreignSyncHeldLock("raw", normalizedTargetPath);
|
|
126
|
+
const initiallyHeld = heldLocks.get(normalizedTargetPath);
|
|
127
|
+
if (initiallyHeld && !foreignSyncHeldLock("raw", normalizedTargetPath) &&
|
|
128
|
+
requestedReentrantOwner !== undefined &&
|
|
129
|
+
initiallyHeld.reentrantOwner !== undefined &&
|
|
130
|
+
requestedReentrantOwner === initiallyHeld.reentrantOwner) {
|
|
131
|
+
initiallyHeld.refCount += 1;
|
|
132
|
+
return createSyncHeldLockHandle(initiallyHeld);
|
|
174
133
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
134
|
+
const admissions = getSyncLockAdmissions();
|
|
135
|
+
const admissionToken = {};
|
|
136
|
+
let ownsAdmission = false;
|
|
137
|
+
const hasAdmissionToken = () => admissions.get(normalizedTargetPath) === admissionToken;
|
|
138
|
+
const releaseAdmission = () => {
|
|
139
|
+
if (ownsAdmission && hasAdmissionToken())
|
|
140
|
+
admissions.delete(normalizedTargetPath);
|
|
141
|
+
ownsAdmission = false;
|
|
142
|
+
};
|
|
143
|
+
let payloadCallback;
|
|
144
|
+
let onCompromised = undefined;
|
|
145
|
+
let onCompromisedObserved = false;
|
|
146
|
+
let parsePayload = undefined;
|
|
147
|
+
let parsePayloadObserved = false;
|
|
148
|
+
let staleRecovery = undefined;
|
|
149
|
+
let staleRecoveryObserved = false;
|
|
150
|
+
const staleOptions = {};
|
|
178
151
|
const startedAt = Date.now();
|
|
179
152
|
let attempt = 0;
|
|
180
153
|
let transientDenials = 0;
|
|
181
154
|
const reclaimGuardPath = `${lockPath}.reclaim`;
|
|
182
155
|
const waitForRetry = () => {
|
|
156
|
+
releaseAdmission();
|
|
183
157
|
const elapsed = Date.now() - startedAt;
|
|
184
158
|
const timedOut = timeoutMs !== undefined && elapsed >= timeoutMs;
|
|
185
159
|
if (timedOut || (retry.retries !== undefined && attempt >= retry.retries)) {
|
|
186
|
-
throw
|
|
187
|
-
code: "file_lock_timeout",
|
|
188
|
-
lockPath,
|
|
189
|
-
normalizedTargetPath,
|
|
190
|
-
});
|
|
160
|
+
throw syncLockTimeout(lockPath, normalizedTargetPath);
|
|
191
161
|
}
|
|
192
162
|
const remaining = timeoutMs === undefined || timeoutMs === Number.POSITIVE_INFINITY
|
|
193
163
|
? Number.POSITIVE_INFINITY
|
|
@@ -210,170 +180,240 @@ export function acquireFileLockSync(targetPath, options) {
|
|
|
210
180
|
}
|
|
211
181
|
return true;
|
|
212
182
|
};
|
|
213
|
-
|
|
214
|
-
if (
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
const payload = options.payload();
|
|
220
|
-
const { raw, ownershipToken } = serializeSidecarLockPayload(payload);
|
|
221
|
-
let lockFileCreateDenied = false;
|
|
183
|
+
const assertAdmissionToken = () => {
|
|
184
|
+
if (!hasAdmissionToken())
|
|
185
|
+
throw syncLockTimeout(lockPath, normalizedTargetPath);
|
|
186
|
+
};
|
|
187
|
+
const runAdmissionBoundary = (callback) => {
|
|
188
|
+
assertAdmissionToken();
|
|
222
189
|
try {
|
|
223
|
-
|
|
224
|
-
? fs.constants.O_NOFOLLOW
|
|
225
|
-
: 0;
|
|
226
|
-
try {
|
|
227
|
-
fd = fs.openSync(lockPath, fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL | noFollow, 0o600);
|
|
228
|
-
}
|
|
229
|
-
catch (error) {
|
|
230
|
-
lockFileCreateDenied = isTransientLockFileDenial(error, lockPath);
|
|
231
|
-
throw error;
|
|
232
|
-
}
|
|
233
|
-
fs.writeFileSync(fd, raw, "utf8");
|
|
234
|
-
fs.fsyncSync(fd);
|
|
235
|
-
const snapshot = {
|
|
236
|
-
raw,
|
|
237
|
-
payload,
|
|
238
|
-
stat: fs.fstatSync(fd),
|
|
239
|
-
ownershipToken,
|
|
240
|
-
};
|
|
241
|
-
const createdHeld = {
|
|
242
|
-
fd,
|
|
243
|
-
lockPath,
|
|
244
|
-
normalizedTargetPath,
|
|
245
|
-
parsePayload: options.parsePayload,
|
|
246
|
-
refCount: 1,
|
|
247
|
-
reentrantOwner: options.reentrantOwner,
|
|
248
|
-
snapshot,
|
|
249
|
-
};
|
|
250
|
-
heldLocks.set(normalizedTargetPath, createdHeld);
|
|
251
|
-
ensureSyncExitCleanupRegistered();
|
|
252
|
-
const returnedHandle = createSyncHeldLockHandle(createdHeld);
|
|
253
|
-
if (options.onCompromised && (options.compromiseCheckIntervalMs ?? 0) > 0) {
|
|
254
|
-
createdHeld.timer = setInterval(() => {
|
|
255
|
-
let stillHeld;
|
|
256
|
-
try {
|
|
257
|
-
stillHeld = returnedHandle.verifyStillHeld();
|
|
258
|
-
}
|
|
259
|
-
catch {
|
|
260
|
-
stillHeld = false;
|
|
261
|
-
}
|
|
262
|
-
if (!stillHeld && createdHeld.timer) {
|
|
263
|
-
clearInterval(createdHeld.timer);
|
|
264
|
-
createdHeld.timer = undefined;
|
|
265
|
-
options.onCompromised?.({ lockPath, normalizedTargetPath });
|
|
266
|
-
}
|
|
267
|
-
}, options.compromiseCheckIntervalMs);
|
|
268
|
-
createdHeld.timer.unref();
|
|
269
|
-
}
|
|
270
|
-
fd = undefined;
|
|
271
|
-
return returnedHandle;
|
|
190
|
+
return callback();
|
|
272
191
|
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
192
|
+
finally {
|
|
193
|
+
assertAdmissionToken();
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
try {
|
|
197
|
+
while (true) {
|
|
198
|
+
if (!ownsAdmission) {
|
|
199
|
+
const held = heldLocks.get(normalizedTargetPath);
|
|
200
|
+
if (held && !foreignSyncHeldLock("raw", normalizedTargetPath) &&
|
|
201
|
+
requestedReentrantOwner !== undefined &&
|
|
202
|
+
held.reentrantOwner !== undefined &&
|
|
203
|
+
requestedReentrantOwner === held.reentrantOwner) {
|
|
204
|
+
held.refCount += 1;
|
|
205
|
+
return createSyncHeldLockHandle(held);
|
|
278
206
|
}
|
|
279
|
-
|
|
280
|
-
//
|
|
207
|
+
if (admissions.has(normalizedTargetPath)) {
|
|
208
|
+
// The owner can only finish after this synchronous callback unwinds.
|
|
209
|
+
throw syncLockTimeout(lockPath, normalizedTargetPath);
|
|
281
210
|
}
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
211
|
+
admissions.set(normalizedTargetPath, admissionToken);
|
|
212
|
+
ownsAdmission = true;
|
|
213
|
+
}
|
|
214
|
+
const attemptHeld = currentTargetHolder();
|
|
215
|
+
const holderWasReplaced = () => {
|
|
216
|
+
const current = currentTargetHolder();
|
|
217
|
+
return current !== undefined && current !== attemptHeld;
|
|
218
|
+
};
|
|
219
|
+
if (!staleRecoveryObserved) {
|
|
220
|
+
staleRecovery = runAdmissionBoundary(() => options.staleRecovery ?? defaults.staleRecovery);
|
|
221
|
+
staleRecoveryObserved = true;
|
|
222
|
+
if (holderWasReplaced()) {
|
|
223
|
+
waitForRetry();
|
|
224
|
+
continue;
|
|
292
225
|
}
|
|
293
226
|
}
|
|
294
|
-
|
|
227
|
+
const reclaimGuardExists = syncReclaimGuardExists(reclaimGuardPath);
|
|
228
|
+
if (!hasAdmissionToken())
|
|
229
|
+
throw syncLockTimeout(lockPath, normalizedTargetPath);
|
|
230
|
+
if (holderWasReplaced()) {
|
|
231
|
+
waitForRetry();
|
|
295
232
|
continue;
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
if (heldLocks.has(normalizedTargetPath)) {
|
|
233
|
+
}
|
|
234
|
+
if (reclaimGuardExists) {
|
|
299
235
|
waitForRetry();
|
|
300
236
|
continue;
|
|
301
237
|
}
|
|
302
|
-
let
|
|
303
|
-
let
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
238
|
+
let fd;
|
|
239
|
+
let createdHeld;
|
|
240
|
+
let createdSnapshot;
|
|
241
|
+
if (!payloadCallback)
|
|
242
|
+
payloadCallback = runAdmissionBoundary(() => options.payload);
|
|
243
|
+
if (holderWasReplaced()) {
|
|
244
|
+
waitForRetry();
|
|
245
|
+
continue;
|
|
309
246
|
}
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
247
|
+
const payload = runAdmissionBoundary(() => Reflect.apply(payloadCallback, options, []));
|
|
248
|
+
if (holderWasReplaced()) {
|
|
249
|
+
waitForRetry();
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
const { raw, ownershipToken } = runAdmissionBoundary(() => serializeSidecarLockPayload(payload));
|
|
253
|
+
if (holderWasReplaced()) {
|
|
254
|
+
waitForRetry();
|
|
255
|
+
continue;
|
|
314
256
|
}
|
|
315
|
-
|
|
316
|
-
if (!snapshot) {
|
|
257
|
+
if (currentTargetHolder() !== undefined) {
|
|
317
258
|
waitForRetry();
|
|
318
259
|
continue;
|
|
319
260
|
}
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
261
|
+
if (!onCompromisedObserved) {
|
|
262
|
+
onCompromised = runAdmissionBoundary(() => options.onCompromised);
|
|
263
|
+
onCompromisedObserved = true;
|
|
264
|
+
if (currentTargetHolder() !== undefined) {
|
|
265
|
+
waitForRetry();
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
if (!parsePayloadObserved) {
|
|
270
|
+
parsePayload = runAdmissionBoundary(() => options.parsePayload);
|
|
271
|
+
parsePayloadObserved = true;
|
|
272
|
+
if (currentTargetHolder() !== undefined) {
|
|
273
|
+
waitForRetry();
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
let lockFileCreateDenied = false;
|
|
278
|
+
let exclusiveCreateConflict = false;
|
|
279
|
+
try {
|
|
280
|
+
const noFollow = process.platform !== "win32" && typeof fs.constants.O_NOFOLLOW === "number"
|
|
281
|
+
? fs.constants.O_NOFOLLOW
|
|
282
|
+
: 0;
|
|
283
|
+
try {
|
|
284
|
+
fd = fs.openSync(lockPath, fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL | noFollow, 0o600);
|
|
285
|
+
}
|
|
286
|
+
catch (error) {
|
|
287
|
+
lockFileCreateDenied = isTransientLockFileDenial(error, lockPath);
|
|
288
|
+
exclusiveCreateConflict = error.code === "EEXIST";
|
|
289
|
+
throw error;
|
|
290
|
+
}
|
|
291
|
+
fs.writeFileSync(fd, raw, "utf8");
|
|
292
|
+
fs.fsyncSync(fd);
|
|
293
|
+
createdSnapshot = {
|
|
294
|
+
raw,
|
|
295
|
+
payload,
|
|
296
|
+
stat: fs.fstatSync(fd),
|
|
297
|
+
ownershipToken,
|
|
298
|
+
};
|
|
299
|
+
createdHeld = {
|
|
300
|
+
fd,
|
|
323
301
|
lockPath,
|
|
324
302
|
normalizedTargetPath,
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
if (
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
303
|
+
parsePayload,
|
|
304
|
+
refCount: 1,
|
|
305
|
+
reentrantOwner: requestedReentrantOwner,
|
|
306
|
+
snapshot: createdSnapshot,
|
|
307
|
+
};
|
|
308
|
+
const candidateHeld = createdHeld;
|
|
309
|
+
ensureSyncLockExitCleanupRegistered(releaseAllSyncHeldLocks, lockPath, normalizedTargetPath);
|
|
310
|
+
if (!hasAdmissionToken() || currentTargetHolder() !== undefined) {
|
|
311
|
+
throw syncLockTimeout(lockPath, normalizedTargetPath);
|
|
312
|
+
}
|
|
313
|
+
const returnedHandle = createSyncHeldLockHandle(candidateHeld);
|
|
314
|
+
if (onCompromised && (compromiseCheckIntervalMs ?? 0) > 0) {
|
|
315
|
+
const compromisedCallback = onCompromised;
|
|
316
|
+
candidateHeld.timer = setInterval(() => {
|
|
317
|
+
let stillHeld;
|
|
318
|
+
try {
|
|
319
|
+
stillHeld = returnedHandle.verifyStillHeld();
|
|
320
|
+
}
|
|
321
|
+
catch {
|
|
322
|
+
stillHeld = false;
|
|
323
|
+
}
|
|
324
|
+
if (!stillHeld && candidateHeld.timer) {
|
|
325
|
+
clearInterval(candidateHeld.timer);
|
|
326
|
+
candidateHeld.timer = undefined;
|
|
327
|
+
Reflect.apply(compromisedCallback, options, [{ lockPath, normalizedTargetPath }]);
|
|
328
|
+
}
|
|
329
|
+
}, compromiseCheckIntervalMs);
|
|
330
|
+
candidateHeld.timer.unref();
|
|
331
|
+
}
|
|
332
|
+
if (!hasAdmissionToken() ||
|
|
333
|
+
currentTargetHolder() !== undefined) {
|
|
334
|
+
throw syncLockTimeout(lockPath, normalizedTargetPath);
|
|
335
|
+
}
|
|
336
|
+
heldLocks.set(normalizedTargetPath, candidateHeld);
|
|
337
|
+
releaseAdmission();
|
|
338
|
+
fd = undefined;
|
|
339
|
+
return returnedHandle;
|
|
340
|
+
}
|
|
341
|
+
catch (error) {
|
|
342
|
+
if (createdHeld?.timer) {
|
|
343
|
+
clearInterval(createdHeld.timer);
|
|
344
|
+
createdHeld.timer = undefined;
|
|
345
|
+
}
|
|
346
|
+
if (createdHeld && heldLocks.get(normalizedTargetPath) === createdHeld) {
|
|
347
|
+
heldLocks.delete(normalizedTargetPath);
|
|
348
|
+
}
|
|
349
|
+
if (fd !== undefined) {
|
|
350
|
+
const failed = createdSnapshot ?? { payload: null };
|
|
351
|
+
if (!failed.stat) {
|
|
352
|
+
try {
|
|
353
|
+
failed.stat = fs.fstatSync(fd);
|
|
354
|
+
}
|
|
355
|
+
catch {
|
|
356
|
+
// Missing identity leaves the sidecar in place, but must not skip close.
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
const failedFd = fd;
|
|
360
|
+
fd = undefined;
|
|
361
|
+
if (createdHeld)
|
|
362
|
+
createdHeld.fd = undefined;
|
|
341
363
|
try {
|
|
342
|
-
fs.
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
continue;
|
|
364
|
+
fs.closeSync(failedFd);
|
|
365
|
+
if (failed.stat)
|
|
366
|
+
removeSidecarLockIfUnchangedSync(lockPath, failed);
|
|
346
367
|
}
|
|
347
|
-
catch (
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
368
|
+
catch (cleanupError) {
|
|
369
|
+
throw createSuppressedError(error, cleanupError, "file lock acquisition and cleanup both failed");
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
if (lockFileCreateDenied) {
|
|
373
|
+
if (!hasAdmissionToken())
|
|
374
|
+
throw syncLockTimeout(lockPath, normalizedTargetPath);
|
|
375
|
+
if (currentTargetHolder() !== undefined) {
|
|
351
376
|
waitForRetry();
|
|
352
377
|
continue;
|
|
353
378
|
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
379
|
+
if (retryLockFileDenial(error))
|
|
380
|
+
continue;
|
|
381
|
+
throw error;
|
|
382
|
+
}
|
|
383
|
+
if (!exclusiveCreateConflict)
|
|
384
|
+
throw error;
|
|
385
|
+
if (!hasAdmissionToken())
|
|
386
|
+
throw syncLockTimeout(lockPath, normalizedTargetPath);
|
|
387
|
+
if (currentTargetHolder() !== undefined) {
|
|
388
|
+
waitForRetry();
|
|
389
|
+
continue;
|
|
364
390
|
}
|
|
365
|
-
|
|
366
|
-
|
|
391
|
+
handleSyncStaleAdmission({
|
|
392
|
+
hasToken: hasAdmissionToken,
|
|
393
|
+
currentHeld: currentTargetHolder,
|
|
367
394
|
lockPath,
|
|
368
395
|
normalizedTargetPath,
|
|
396
|
+
options,
|
|
397
|
+
parsePayload,
|
|
398
|
+
reclaimGuardPath,
|
|
399
|
+
retryLockFileDenial,
|
|
400
|
+
staleMs,
|
|
401
|
+
staleOptions,
|
|
402
|
+
staleRecovery,
|
|
403
|
+
waitForRetry,
|
|
369
404
|
});
|
|
405
|
+
continue;
|
|
370
406
|
}
|
|
371
|
-
waitForRetry();
|
|
372
407
|
}
|
|
373
408
|
}
|
|
409
|
+
finally {
|
|
410
|
+
releaseAdmission();
|
|
411
|
+
}
|
|
374
412
|
}
|
|
375
413
|
export function withFileLockSync(targetPath, options, fn) {
|
|
376
414
|
const lock = acquireFileLockSync(targetPath, options);
|
|
415
|
+
if (isRootSyncHeldLockHandle(lock))
|
|
416
|
+
return withRootSyncHeldLockHandle(lock, fn);
|
|
377
417
|
try {
|
|
378
418
|
return fn();
|
|
379
419
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-store-prune.d.ts","sourceRoot":"","sources":["../src/file-store-prune.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"file-store-prune.d.ts","sourceRoot":"","sources":["../src/file-store-prune.ts"],"names":[],"mappings":"AAYA,MAAM,MAAM,qBAAqB,GAAG;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B,CAAC;AAQF,wBAAsB,wBAAwB,CAAC,MAAM,EAAE;IACrD,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,qBAAqB,CAAC;CAChC,GAAG,OAAO,CAAC,IAAI,CAAC,CA8FhB"}
|
package/dist/file-store-prune.js
CHANGED
|
@@ -6,7 +6,11 @@ import { isPathInside } from "./path.js";
|
|
|
6
6
|
import { realpathSync } from "./realpath.js";
|
|
7
7
|
import { recursiveMkdirPath } from "./recursive-mkdir-path.js";
|
|
8
8
|
import { root } from "./root.js";
|
|
9
|
+
import { nonrecursiveRemovalKind } from "./root-remove.js";
|
|
9
10
|
import { getFsSafeTestHooks } from "./test-hooks.js";
|
|
11
|
+
const REMOVE_EMPTY_DIRECTORY_OPTIONS = Object.freeze({
|
|
12
|
+
[nonrecursiveRemovalKind]: "directory",
|
|
13
|
+
});
|
|
10
14
|
export async function pruneExpiredStoreEntries(params) {
|
|
11
15
|
const now = Date.now();
|
|
12
16
|
const recursive = params.options.recursive ?? false;
|
|
@@ -70,7 +74,7 @@ export async function pruneExpiredStoreEntries(params) {
|
|
|
70
74
|
await assertRootGuard();
|
|
71
75
|
// Keep empty-dir pruning on the same root-bounded remove path as files;
|
|
72
76
|
// the Root fallback handles empty directories without recursive delete.
|
|
73
|
-
await scopedRoot.remove(relativePath).catch(() => undefined);
|
|
77
|
+
await scopedRoot.remove(relativePath, REMOVE_EMPTY_DIRECTORY_OPTIONS).catch(() => undefined);
|
|
74
78
|
}
|
|
75
79
|
continue;
|
|
76
80
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-store-sync-directory.d.ts","sourceRoot":"","sources":["../src/file-store-sync-directory.ts"],"names":[],"mappings":"AAAA,OAAW,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAc/C,KAAK,kBAAkB,GAAG,eAAe,GAAG,OAAO,CAAC;AAOpD,MAAM,MAAM,yBAAyB,GAAG,QAAQ,CAAC;IAC/C,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,WAAW,CAAC;CACxB,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"file-store-sync-directory.d.ts","sourceRoot":"","sources":["../src/file-store-sync-directory.ts"],"names":[],"mappings":"AAAA,OAAW,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAc/C,KAAK,kBAAkB,GAAG,eAAe,GAAG,OAAO,CAAC;AAOpD,MAAM,MAAM,yBAAyB,GAAG,QAAQ,CAAC;IAC/C,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,WAAW,CAAC;CACxB,CAAC,CAAC;AA0VH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,kBAAkB,CAAC;CACnC,GAAG,yBAAyB,CA4D5B;AAED,wBAAgB,+BAA+B,CAC7C,OAAO,EAAE,yBAAyB,GACjC,IAAI,CAsBN"}
|
|
@@ -11,7 +11,9 @@ function changedStoreDirectory(messagePrefix, cause) {
|
|
|
11
11
|
return new FsSafeError("outside-workspace", `${messagePrefix} directory escapes root`, { cause });
|
|
12
12
|
}
|
|
13
13
|
function storeDirectoryRealPath(dir) {
|
|
14
|
-
const
|
|
14
|
+
const operationPath = pathForWindowsFilesystem(dir);
|
|
15
|
+
const realPath = process.platform === "win32"
|
|
16
|
+
? realpathSync(operationPath) : realpathSync.native(operationPath);
|
|
15
17
|
assertNoWindowsPathAlias(realPath, "filesystem");
|
|
16
18
|
return realPath;
|
|
17
19
|
}
|
|
@@ -72,7 +74,9 @@ function assertAdmittedEdgeCurrent(receipts, rootReal, messagePrefix) {
|
|
|
72
74
|
if (!rootReceipt || !targetReceipt) {
|
|
73
75
|
throw new FsSafeError("helper-failed", "store directory receipt is missing");
|
|
74
76
|
}
|
|
75
|
-
const rootStat =
|
|
77
|
+
const rootStat = rootReal === undefined
|
|
78
|
+
? inspectReceiptCurrent(rootReceipt, messagePrefix, true)
|
|
79
|
+
: assertRootCurrent(rootReceipt, rootReal, messagePrefix);
|
|
76
80
|
if (targetIndex === 0)
|
|
77
81
|
return rootStat;
|
|
78
82
|
const parent = receipts[targetIndex - 1];
|
|
@@ -87,10 +91,13 @@ function assertAdmittedEdgeCurrent(receipts, rootReal, messagePrefix) {
|
|
|
87
91
|
return targetStat;
|
|
88
92
|
}
|
|
89
93
|
function assertModeTargetCurrent(receipts, rootReal, messagePrefix) {
|
|
94
|
+
const root = receipts[0];
|
|
90
95
|
const target = receipts.at(-1);
|
|
91
|
-
if (!target)
|
|
96
|
+
if (!root || !target)
|
|
92
97
|
throw new FsSafeError("helper-failed", "store directory receipt is missing");
|
|
93
|
-
|
|
98
|
+
// The target's canonical observation shares the exact identity fence. Keep
|
|
99
|
+
// the trailing root canonical check: an ancestor relocation can retain IDs.
|
|
100
|
+
assertAdmittedEdgeCurrent(receipts, undefined, messagePrefix);
|
|
94
101
|
let targetReal;
|
|
95
102
|
try {
|
|
96
103
|
targetReal = storeDirectoryRealPath(target.dir);
|
|
@@ -98,10 +105,10 @@ function assertModeTargetCurrent(receipts, rootReal, messagePrefix) {
|
|
|
98
105
|
catch (error) {
|
|
99
106
|
throw changedStoreDirectory(messagePrefix, error);
|
|
100
107
|
}
|
|
101
|
-
if (!isPathInside(rootReal, targetReal))
|
|
108
|
+
if (target === root ? targetReal !== rootReal : !isPathInside(rootReal, targetReal)) {
|
|
102
109
|
throw changedStoreDirectory(messagePrefix);
|
|
103
|
-
|
|
104
|
-
return
|
|
110
|
+
}
|
|
111
|
+
return assertAdmittedEdgeCurrent(receipts, rootReal, messagePrefix);
|
|
105
112
|
}
|
|
106
113
|
function assertReceiptChain(receipts, rootReal, messagePrefix) {
|
|
107
114
|
const rootReceipt = receipts[0];
|
|
@@ -76,7 +76,7 @@ export function writeFileSyncAtomic(params) {
|
|
|
76
76
|
}
|
|
77
77
|
const tempPath = path.join(parentGuard?.dir ?? path.dirname(filePath), `.fs-safe-${process.pid}-${randomUUID()}.tmp`);
|
|
78
78
|
const owner = new SyncAtomicTempOwner(tempPath);
|
|
79
|
-
let
|
|
79
|
+
let originalFailure;
|
|
80
80
|
try {
|
|
81
81
|
getFsSafeTestHooks()?.beforeFileStoreSyncPrivateWrite?.(filePath);
|
|
82
82
|
if (parentGuard) {
|
|
@@ -133,10 +133,10 @@ export function writeFileSyncAtomic(params) {
|
|
|
133
133
|
return filePath;
|
|
134
134
|
}
|
|
135
135
|
catch (error) {
|
|
136
|
-
|
|
136
|
+
originalFailure = { error };
|
|
137
137
|
throw error;
|
|
138
138
|
}
|
|
139
139
|
finally {
|
|
140
|
-
owner.finish({ fsModule: fs,
|
|
140
|
+
owner.finish({ fsModule: fs, originalFailure, throwOnCleanupError: false });
|
|
141
141
|
}
|
|
142
142
|
}
|