@openclaw/fs-safe 0.14.0 → 0.16.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 +88 -0
- package/README.md +38 -10
- package/dist/advanced.d.ts +2 -1
- package/dist/advanced.d.ts.map +1 -1
- package/dist/advanced.js +2 -1
- package/dist/archive-kind.d.ts +0 -1
- package/dist/archive-kind.d.ts.map +1 -1
- package/dist/archive-kind.js +5 -17
- package/dist/archive-merge.d.ts.map +1 -1
- package/dist/archive-merge.js +113 -46
- package/dist/archive-parser.wasm +0 -0
- package/dist/archive-read.d.ts.map +1 -1
- package/dist/archive-read.js +10 -11
- package/dist/archive-tar-stream.d.ts +3 -0
- package/dist/archive-tar-stream.d.ts.map +1 -1
- package/dist/archive-tar-stream.js +56 -37
- package/dist/archive-tar-wasm.d.ts +16 -4
- package/dist/archive-tar-wasm.d.ts.map +1 -1
- package/dist/archive-tar-wasm.js +134 -34
- 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.d.ts.map +1 -1
- package/dist/archive.js +11 -11
- 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/clone-metadata.d.ts +1 -0
- package/dist/clone-metadata.d.ts.map +1 -1
- package/dist/clone-metadata.js +6 -2
- 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/create-directory.d.ts +20 -0
- package/dist/create-directory.d.ts.map +1 -0
- package/dist/create-directory.js +130 -0
- package/dist/create-file-async.d.ts +7 -0
- package/dist/create-file-async.d.ts.map +1 -0
- package/dist/create-file-async.js +121 -0
- package/dist/create-file.d.ts +8 -0
- package/dist/create-file.d.ts.map +1 -0
- package/dist/create-file.js +190 -0
- package/dist/create-owned-file.d.ts +8 -0
- package/dist/create-owned-file.d.ts.map +1 -0
- package/dist/create-owned-file.js +16 -0
- package/dist/create.d.ts +4 -0
- package/dist/create.d.ts.map +1 -0
- package/dist/create.js +2 -0
- package/dist/creation-darwin.d.ts +7 -0
- package/dist/creation-darwin.d.ts.map +1 -0
- package/dist/creation-darwin.js +79 -0
- package/dist/creation-file-state.d.ts +19 -0
- package/dist/creation-file-state.d.ts.map +1 -0
- package/dist/creation-file-state.js +118 -0
- package/dist/creation-path.d.ts +21 -0
- package/dist/creation-path.d.ts.map +1 -0
- package/dist/creation-path.js +71 -0
- package/dist/creation-permissions.d.ts +19 -0
- package/dist/creation-permissions.d.ts.map +1 -0
- package/dist/creation-permissions.js +125 -0
- package/dist/directory-durability.d.ts +1 -1
- package/dist/directory-durability.d.ts.map +1 -1
- package/dist/directory-durability.js +22 -80
- package/dist/directory-guard.d.ts +3 -0
- package/dist/directory-guard.d.ts.map +1 -1
- package/dist/directory-mode-node.d.ts +2 -0
- package/dist/directory-mode-node.d.ts.map +1 -1
- package/dist/directory-mode-node.js +8 -0
- package/dist/directory-mode-owner.js +5 -5
- package/dist/directory-receipt.d.ts +24 -0
- package/dist/directory-receipt.d.ts.map +1 -0
- package/dist/directory-receipt.js +127 -0
- package/dist/file-cleanup.d.ts +19 -0
- package/dist/file-cleanup.d.ts.map +1 -0
- package/dist/file-cleanup.js +78 -0
- 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-observation.d.ts +1 -1
- package/dist/file-observation.d.ts.map +1 -1
- package/dist/file-store-boundary.d.ts +2 -6
- package/dist/file-store-boundary.d.ts.map +1 -1
- package/dist/file-store-boundary.js +3 -9
- package/dist/file-store-prune.d.ts.map +1 -1
- package/dist/file-store-prune.js +5 -1
- package/dist/file-store-sync-write.d.ts.map +1 -1
- package/dist/file-store-sync-write.js +5 -8
- package/dist/file-store.d.ts.map +1 -1
- package/dist/file-store.js +47 -12
- package/dist/guarded-mkdir.d.ts +1 -0
- package/dist/guarded-mkdir.d.ts.map +1 -1
- package/dist/guarded-mkdir.js +36 -7
- 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/move-path.js +1 -1
- package/dist/native-binding.d.ts +13 -1
- package/dist/native-binding.d.ts.map +1 -1
- package/dist/native-fallback-warning.d.ts +4 -0
- package/dist/native-fallback-warning.d.ts.map +1 -0
- package/dist/native-fallback-warning.js +11 -0
- package/dist/native-operations.d.ts +0 -2
- package/dist/native-operations.d.ts.map +1 -1
- package/dist/native-operations.js +0 -24
- package/dist/native-parent-admission.d.ts +5 -2
- package/dist/native-parent-admission.d.ts.map +1 -1
- package/dist/native-parent-admission.js +27 -7
- package/dist/native-pinned-write-windows.d.ts +1 -1
- package/dist/native-pinned-write-windows.d.ts.map +1 -1
- package/dist/native-pinned-write-windows.js +174 -29
- package/dist/native-pinned-write.d.ts.map +1 -1
- package/dist/native-pinned-write.js +26 -14
- 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 +209 -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 +3 -2
- package/dist/native-staged-file.d.ts.map +1 -1
- package/dist/native-staged-file.js +121 -72
- package/dist/output.d.ts.map +1 -1
- package/dist/output.js +12 -8
- package/dist/owner-dacl.d.ts.map +1 -1
- package/dist/owner-dacl.js +10 -4
- 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 +10 -5
- package/dist/pinned-mutation-observation.d.ts +0 -1
- package/dist/pinned-mutation-observation.d.ts.map +1 -1
- package/dist/pinned-mutation-observation.js +0 -19
- 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-input.d.ts +4 -0
- package/dist/pinned-write-input.d.ts.map +1 -0
- package/dist/pinned-write-input.js +25 -0
- package/dist/pinned-write-mode.d.ts +5 -0
- package/dist/pinned-write-mode.d.ts.map +1 -0
- package/dist/pinned-write-mode.js +24 -0
- package/dist/pinned-write-staged.d.ts +6 -0
- package/dist/pinned-write-staged.d.ts.map +1 -0
- package/dist/pinned-write-staged.js +187 -0
- package/dist/pinned-write-types.d.ts +5 -0
- package/dist/pinned-write-types.d.ts.map +1 -1
- package/dist/pinned-write.d.ts.map +1 -1
- package/dist/pinned-write.js +35 -145
- package/dist/private-directory.d.ts.map +1 -1
- package/dist/private-directory.js +18 -4
- package/dist/private-producer-handoff-sync.d.ts +14 -0
- package/dist/private-producer-handoff-sync.d.ts.map +1 -0
- package/dist/private-producer-handoff-sync.js +114 -0
- package/dist/private-producer-handoff.d.ts +22 -4
- package/dist/private-producer-handoff.d.ts.map +1 -1
- package/dist/private-producer-handoff.js +140 -77
- package/dist/private-temp-workspace.d.ts.map +1 -1
- package/dist/private-temp-workspace.js +75 -121
- package/dist/publish-copy-stage.d.ts +2 -1
- package/dist/publish-copy-stage.d.ts.map +1 -1
- package/dist/publish-copy-stage.js +16 -7
- package/dist/publish-file.d.ts.map +1 -1
- package/dist/publish-file.js +2 -2
- 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 -9
- package/dist/replace-file-temp-owner.d.ts.map +1 -1
- package/dist/replace-file-temp-owner.js +56 -72
- 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 +8 -1
- package/dist/root-create-input.d.ts.map +1 -1
- package/dist/root-create-input.js +17 -4
- package/dist/root-directory-creation.d.ts +3 -3
- package/dist/root-directory-creation.d.ts.map +1 -1
- package/dist/root-directory-creation.js +15 -3
- 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 +46 -17
- package/dist/root-move-noreplace.d.ts.map +1 -1
- package/dist/root-move-noreplace.js +24 -15
- package/dist/root-options.d.ts +12 -4
- package/dist/root-options.d.ts.map +1 -1
- 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 +11 -35
- package/dist/root-path-stat.d.ts.map +1 -1
- package/dist/root-path-stat.js +59 -7
- 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-complete-parent.d.ts.map +1 -1
- package/dist/root-write-complete-parent.js +7 -23
- package/dist/root-write-publication.js +1 -1
- 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 +3 -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/secure-file-windows.d.ts +6 -0
- package/dist/secure-file-windows.d.ts.map +1 -1
- package/dist/secure-file-windows.js +34 -117
- package/dist/secure-file.js +2 -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-root.d.ts.map +1 -1
- package/dist/sidecar-lock-root.js +2 -1
- 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.map +1 -1
- package/dist/staged-directory.js +6 -6
- package/dist/staged-file-settlement.d.ts +17 -0
- package/dist/staged-file-settlement.d.ts.map +1 -0
- package/dist/staged-file-settlement.js +57 -0
- 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 +4 -3
- package/dist/windows-security-bridge.cs +336 -0
- package/dist/windows-security-bridge.ps1 +15 -0
- package/dist/windows-security-command.d.ts +26 -0
- package/dist/windows-security-command.d.ts.map +1 -0
- package/dist/windows-security-command.js +363 -0
- package/dist/windows-security-facts.d.ts +6 -0
- package/dist/windows-security-facts.d.ts.map +1 -0
- package/dist/windows-security-facts.js +108 -0
- package/docs/advanced.md +4 -2
- package/docs/archive.md +97 -46
- package/docs/atomic.md +85 -8
- package/docs/config.md +6 -2
- package/docs/contributing.md +44 -4
- package/docs/copy.md +37 -0
- package/docs/creation.md +128 -0
- package/docs/durability.md +24 -0
- package/docs/file-store.md +19 -0
- package/docs/install.md +31 -7
- package/docs/json-store.md +5 -0
- package/docs/migrating-to-0.5.md +15 -6
- package/docs/migrating-to-0.6.md +9 -4
- package/docs/native-helper.md +32 -12
- package/docs/native.md +38 -7
- 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 +50 -14
- package/docs/public-api.md +3 -2
- package/docs/root.md +56 -3
- package/docs/secret-file.md +11 -2
- package/docs/secure-file.md +9 -4
- package/docs/sidecar-lock.md +114 -8
- package/docs/staged-file.md +12 -3
- package/docs/temp.md +20 -3
- package/docs/walk.md +67 -1
- package/docs/writing.md +76 -6
- package/package.json +19 -16
- package/dist/darwin-acl.d.ts +0 -4
- package/dist/darwin-acl.d.ts.map +0 -1
- package/dist/darwin-acl.js +0 -24
|
@@ -1,12 +1,94 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
+
import fsSync from "node:fs";
|
|
2
3
|
import fs from "node:fs/promises";
|
|
3
4
|
import path from "node:path";
|
|
4
|
-
import {
|
|
5
|
+
import { assertSyncDirectoryGuard, createAsyncDirectoryGuard, inspectDirectoryIdentitySync, } from "./directory-guard.js";
|
|
6
|
+
import { FsSafeError } from "./errors.js";
|
|
7
|
+
import { classifyNativeRenameFailure, NATIVE_RENAME_SOURCE_IDENTITY_MISMATCH, } from "./native-rename-outcome.js";
|
|
5
8
|
import { recursiveMkdirPath } from "./recursive-mkdir-path.js";
|
|
9
|
+
import { RetainedDirectoryReplacement } from "./retained-directory-replacement.js";
|
|
6
10
|
import { assertSafePathPrefix } from "./safe-path-segment.js";
|
|
7
11
|
import { admitStandalonePublicationPath } from "./standalone-publication-path.js";
|
|
8
12
|
import { serializePathWrite } from "./write-queue.js";
|
|
9
13
|
import { assertNoWindowsPathAlias } from "./windows-path-alias.js";
|
|
14
|
+
function distinctGuards(source, target) {
|
|
15
|
+
return source === target ? [source] : [source, target];
|
|
16
|
+
}
|
|
17
|
+
function assertGuardsCurrent(guards) {
|
|
18
|
+
for (const guard of guards)
|
|
19
|
+
assertSyncDirectoryGuard(guard);
|
|
20
|
+
}
|
|
21
|
+
function copyOperationalCode(target, source) {
|
|
22
|
+
const code = source?.code;
|
|
23
|
+
if (typeof code === "string") {
|
|
24
|
+
Object.defineProperty(target, "code", { configurable: true, value: code });
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function replacementFailure(error, message, details) {
|
|
28
|
+
if (error instanceof FsSafeError) {
|
|
29
|
+
return new FsSafeError(error.code, message, { cause: error, details });
|
|
30
|
+
}
|
|
31
|
+
const failure = new Error(message, { cause: error });
|
|
32
|
+
copyOperationalCode(failure, error);
|
|
33
|
+
Object.defineProperty(failure, "details", { configurable: true, value: details });
|
|
34
|
+
return failure;
|
|
35
|
+
}
|
|
36
|
+
function replacementAggregate(error, recoveryError, message, details) {
|
|
37
|
+
const combined = new AggregateError([error, recoveryError], "directory replacement operation and recovery both failed");
|
|
38
|
+
if (error instanceof FsSafeError) {
|
|
39
|
+
return new FsSafeError(error.code, message, { cause: combined, details });
|
|
40
|
+
}
|
|
41
|
+
const failure = new Error(message, { cause: combined });
|
|
42
|
+
copyOperationalCode(failure, error);
|
|
43
|
+
Object.defineProperty(failure, "details", { configurable: true, value: details });
|
|
44
|
+
return failure;
|
|
45
|
+
}
|
|
46
|
+
function isMissingDirectoryEntry(error) {
|
|
47
|
+
return error?.code === "ENOENT";
|
|
48
|
+
}
|
|
49
|
+
function observeOptionalDirectory(pathname) {
|
|
50
|
+
let initial;
|
|
51
|
+
try {
|
|
52
|
+
initial = fsSync.lstatSync(pathname, { bigint: true });
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
if (isMissingDirectoryEntry(error))
|
|
56
|
+
return undefined;
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
return inspectDirectoryIdentitySync(pathname, undefined, initial);
|
|
60
|
+
}
|
|
61
|
+
function normalizeNativeRenameFailure(error) {
|
|
62
|
+
const code = error?.code;
|
|
63
|
+
if (code === NATIVE_RENAME_SOURCE_IDENTITY_MISMATCH) {
|
|
64
|
+
return new FsSafeError("path-mismatch", "directory replacement source identity changed before native rename", { cause: error });
|
|
65
|
+
}
|
|
66
|
+
if (code === "EINVAL" || code === "ENOSYS" || code === "ENOTSUP" ||
|
|
67
|
+
code === "EOPNOTSUPP") {
|
|
68
|
+
return new FsSafeError("helper-unavailable", "native no-replace directory rename is unavailable on this filesystem", { cause: error });
|
|
69
|
+
}
|
|
70
|
+
return error;
|
|
71
|
+
}
|
|
72
|
+
function renameRetainedDirectory(params) {
|
|
73
|
+
const guards = distinctGuards(params.sourceParent, params.targetParent);
|
|
74
|
+
assertGuardsCurrent(guards);
|
|
75
|
+
const initial = fsSync.lstatSync(params.from, { bigint: true });
|
|
76
|
+
const identity = inspectDirectoryIdentitySync(params.from, params.expectedSource, initial);
|
|
77
|
+
try {
|
|
78
|
+
params.renameNoReplace(identity);
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
if (classifyNativeRenameFailure(error) === "indeterminate") {
|
|
82
|
+
params.onIndeterminate();
|
|
83
|
+
}
|
|
84
|
+
throw normalizeNativeRenameFailure(error);
|
|
85
|
+
}
|
|
86
|
+
// Record the transition synchronously before any post-rename observation.
|
|
87
|
+
params.onRenamed?.(identity);
|
|
88
|
+
inspectDirectoryIdentitySync(params.to, identity);
|
|
89
|
+
assertGuardsCurrent(guards);
|
|
90
|
+
return identity;
|
|
91
|
+
}
|
|
10
92
|
export async function replaceDirectoryAtomic(options) {
|
|
11
93
|
const stagedDirInput = admitStandalonePublicationPath(options.stagedDir, "staged directory uses a Windows filesystem namespace alias");
|
|
12
94
|
const targetDirInput = admitStandalonePublicationPath(options.targetDir, "target directory uses a Windows filesystem namespace alias");
|
|
@@ -23,28 +105,184 @@ async function replaceDirectoryAtomicUnserialized(stagedDir, targetDir, backupPr
|
|
|
23
105
|
const parentDir = path.dirname(targetDir);
|
|
24
106
|
const backupPrefix = assertSafePathPrefix(backupPrefixInput ?? ".fs-safe-dir-backup-", { label: "atomic directory backup prefix" });
|
|
25
107
|
const backupDir = path.join(parentDir, `${backupPrefix}${process.pid}-${randomUUID()}`);
|
|
26
|
-
|
|
108
|
+
const backup = {
|
|
109
|
+
identity: undefined,
|
|
110
|
+
transition: "not-renamed",
|
|
111
|
+
};
|
|
112
|
+
const publication = {
|
|
113
|
+
status: "not-published",
|
|
114
|
+
};
|
|
115
|
+
let operationFailed = false;
|
|
116
|
+
let operationError;
|
|
117
|
+
let operationDetails = {
|
|
118
|
+
phase: "backup",
|
|
119
|
+
publication: publication.status,
|
|
120
|
+
recovery: "none",
|
|
121
|
+
};
|
|
122
|
+
const retainedBinding = RetainedDirectoryReplacement.requireBinding();
|
|
27
123
|
await fs.mkdir(recursiveMkdirPath(parentDir), { recursive: true });
|
|
124
|
+
const targetParent = await createAsyncDirectoryGuard(parentDir, { bigint: true });
|
|
125
|
+
const stagedParentDir = path.dirname(stagedDir);
|
|
126
|
+
const stagedParent = stagedParentDir === parentDir
|
|
127
|
+
? targetParent
|
|
128
|
+
: await createAsyncDirectoryGuard(stagedParentDir, { bigint: true });
|
|
129
|
+
const stagedIdentity = inspectDirectoryIdentitySync(stagedDir);
|
|
130
|
+
const targetIdentity = observeOptionalDirectory(targetDir);
|
|
131
|
+
const retained = RetainedDirectoryReplacement.retain({
|
|
132
|
+
targetParent: {
|
|
133
|
+
path: parentDir,
|
|
134
|
+
realPath: targetParent.realPath,
|
|
135
|
+
identity: targetParent.stat,
|
|
136
|
+
},
|
|
137
|
+
stagedParent: {
|
|
138
|
+
path: stagedParentDir,
|
|
139
|
+
realPath: stagedParent.realPath,
|
|
140
|
+
identity: stagedParent.stat,
|
|
141
|
+
},
|
|
142
|
+
...(targetIdentity ? {
|
|
143
|
+
originalTarget: { path: targetDir, identity: targetIdentity },
|
|
144
|
+
} : {}),
|
|
145
|
+
}, retainedBinding);
|
|
146
|
+
const targetBasename = path.basename(targetDir);
|
|
147
|
+
const stagedBasename = path.basename(stagedDir);
|
|
148
|
+
const backupBasename = path.basename(backupDir);
|
|
149
|
+
const details = (phase, recovery) => {
|
|
150
|
+
operationDetails = {
|
|
151
|
+
phase,
|
|
152
|
+
publication: publication.status,
|
|
153
|
+
recovery,
|
|
154
|
+
...(backup.transition === "not-renamed" ? {} : { backupPath: backupDir }),
|
|
155
|
+
};
|
|
156
|
+
return operationDetails;
|
|
157
|
+
};
|
|
158
|
+
const rollback = (error, failedPhase, identity) => {
|
|
159
|
+
const rollbackState = {
|
|
160
|
+
transition: "not-renamed",
|
|
161
|
+
};
|
|
162
|
+
try {
|
|
163
|
+
renameRetainedDirectory({
|
|
164
|
+
from: backupDir,
|
|
165
|
+
to: targetDir,
|
|
166
|
+
sourceParent: targetParent,
|
|
167
|
+
targetParent,
|
|
168
|
+
expectedSource: identity,
|
|
169
|
+
renameNoReplace: expectedSource => {
|
|
170
|
+
retained.renameNoReplace("target", backupBasename, targetBasename, expectedSource);
|
|
171
|
+
},
|
|
172
|
+
onIndeterminate() {
|
|
173
|
+
rollbackState.transition = "indeterminate";
|
|
174
|
+
backup.transition = "indeterminate";
|
|
175
|
+
},
|
|
176
|
+
onRenamed() {
|
|
177
|
+
rollbackState.transition = "renamed";
|
|
178
|
+
backup.transition = "not-renamed";
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
catch (rollbackError) {
|
|
183
|
+
const recovery = rollbackState.transition === "renamed"
|
|
184
|
+
? "restored-unverified"
|
|
185
|
+
: rollbackState.transition === "indeterminate"
|
|
186
|
+
? "indeterminate"
|
|
187
|
+
: "backup-path-preserved";
|
|
188
|
+
throw replacementAggregate(error, rollbackError, `directory ${failedPhase} and rollback failed`, details("rollback", recovery));
|
|
189
|
+
}
|
|
190
|
+
throw replacementFailure(error, `directory ${failedPhase} failed after restoring the original target`, details(failedPhase, "restored"));
|
|
191
|
+
};
|
|
28
192
|
try {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
193
|
+
if (targetIdentity) {
|
|
194
|
+
try {
|
|
195
|
+
backup.identity = renameRetainedDirectory({
|
|
196
|
+
from: targetDir,
|
|
197
|
+
to: backupDir,
|
|
198
|
+
sourceParent: targetParent,
|
|
199
|
+
targetParent,
|
|
200
|
+
expectedSource: targetIdentity,
|
|
201
|
+
renameNoReplace: expectedSource => {
|
|
202
|
+
retained.renameNoReplace("target", targetBasename, backupBasename, expectedSource);
|
|
203
|
+
},
|
|
204
|
+
onIndeterminate() {
|
|
205
|
+
backup.transition = "indeterminate";
|
|
206
|
+
},
|
|
207
|
+
onRenamed(identity) {
|
|
208
|
+
backup.identity = identity;
|
|
209
|
+
backup.transition = "renamed";
|
|
210
|
+
},
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
catch (err) {
|
|
214
|
+
if (backup.transition === "renamed" && backup.identity) {
|
|
215
|
+
rollback(err, "backup", backup.identity);
|
|
216
|
+
}
|
|
217
|
+
const recovery = backup.transition === "indeterminate" ? "indeterminate" : "none";
|
|
218
|
+
throw replacementFailure(err, "directory backup failed before publication", details("backup", recovery));
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
try {
|
|
222
|
+
renameRetainedDirectory({
|
|
223
|
+
from: stagedDir,
|
|
224
|
+
to: targetDir,
|
|
225
|
+
sourceParent: stagedParent,
|
|
226
|
+
targetParent,
|
|
227
|
+
expectedSource: stagedIdentity,
|
|
228
|
+
renameNoReplace: expectedSource => {
|
|
229
|
+
retained.renameNoReplace("staged", stagedBasename, targetBasename, expectedSource);
|
|
230
|
+
},
|
|
231
|
+
onIndeterminate() {
|
|
232
|
+
publication.status = "indeterminate";
|
|
233
|
+
},
|
|
234
|
+
onRenamed() {
|
|
235
|
+
publication.status = "published";
|
|
236
|
+
},
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
catch (err) {
|
|
240
|
+
if (publication.status === "published") {
|
|
241
|
+
throw replacementFailure(err, "published directory failed retained identity verification", details("publish", backup.transition === "renamed" ? "backup-path-preserved" : "none"));
|
|
242
|
+
}
|
|
243
|
+
if (publication.status === "indeterminate") {
|
|
244
|
+
throw replacementFailure(err, "directory publication outcome is indeterminate", details("publish", backup.transition === "renamed" ? "backup-path-preserved" : "none"));
|
|
245
|
+
}
|
|
246
|
+
if (backup.transition === "renamed" && backup.identity) {
|
|
247
|
+
rollback(err, "publish", backup.identity);
|
|
248
|
+
}
|
|
249
|
+
throw replacementFailure(err, "directory publication failed before commit", details("publish", "none"));
|
|
250
|
+
}
|
|
251
|
+
details("publish", backup.transition === "renamed" ? "backup-path-preserved" : "none");
|
|
252
|
+
if (backup.transition === "renamed" && backup.identity) {
|
|
253
|
+
let cleanupAttempted = false;
|
|
254
|
+
try {
|
|
255
|
+
assertSyncDirectoryGuard(targetParent);
|
|
256
|
+
inspectDirectoryIdentitySync(backupDir, backup.identity);
|
|
257
|
+
cleanupAttempted = true;
|
|
258
|
+
const outcome = await retained.removeOriginal(backupBasename);
|
|
259
|
+
if (outcome !== "removed") {
|
|
260
|
+
throw new FsSafeError("path-mismatch", "retained backup changed before bounded cleanup");
|
|
261
|
+
}
|
|
262
|
+
backup.transition = "not-renamed";
|
|
263
|
+
details("cleanup", "none");
|
|
264
|
+
}
|
|
265
|
+
catch (error) {
|
|
266
|
+
throw replacementFailure(error, "published directory but retained backup cleanup failed", {
|
|
267
|
+
...details("cleanup", cleanupAttempted ? "cleanup-incomplete" : "backup-path-preserved"),
|
|
268
|
+
});
|
|
269
|
+
}
|
|
35
270
|
}
|
|
36
271
|
}
|
|
37
|
-
|
|
38
|
-
|
|
272
|
+
catch (error) {
|
|
273
|
+
operationFailed = true;
|
|
274
|
+
operationError = error;
|
|
275
|
+
throw error;
|
|
39
276
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
277
|
+
finally {
|
|
278
|
+
try {
|
|
279
|
+
retained.close();
|
|
280
|
+
}
|
|
281
|
+
catch (closeError) {
|
|
282
|
+
if (operationFailed) {
|
|
283
|
+
throw replacementAggregate(operationError, closeError, "directory replacement and retained descriptor close failed", operationDetails);
|
|
284
|
+
}
|
|
285
|
+
throw replacementFailure(closeError, "directory replacement retained descriptor close failed", operationDetails);
|
|
44
286
|
}
|
|
45
|
-
throw err;
|
|
46
|
-
}
|
|
47
|
-
if (backupCreated) {
|
|
48
|
-
await guardedRm({ target: backupDir, recursive: true, force: true, verifyAfter: false });
|
|
49
287
|
}
|
|
50
288
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"replace-file-copy-fallback.d.ts","sourceRoot":"","sources":["../src/replace-file-copy-fallback.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,EAAE,EAAE,KAAK,WAAW,EAAc,MAAM,SAAS,CAAC;AAQ/D,MAAM,MAAM,oCAAoC,GAAG,QAAQ,CAAC;AAC5D,MAAM,MAAM,oCAAoC,GAAG,kBAAkB,GAAG,MAAM,CAAC;AAC/E,MAAM,MAAM,+BAA+B,GAAG,UAAU,GAAG,gBAAgB,CAAC;AAC5E,MAAM,MAAM,sCAAsC,GAAG;IACnD,OAAO,EAAE,+BAA+B,CAAC;CAC1C,CAAC;AAEF,KAAK,eAAe,GAAG;IACrB,KAAK,EAAE,cAAc,kBAAkB,EAAE,KAAK,CAAC;IAC/C,IAAI,EAAE,cAAc,kBAAkB,EAAE,IAAI,CAAC;IAC7C,EAAE,EAAE,cAAc,kBAAkB,EAAE,EAAE,CAAC;IACzC,MAAM,EAAE,cAAc,kBAAkB,EAAE,MAAM,CAAC;CAClD,CAAC;AAEF,KAAK,cAAc,GAAG,IAAI,CACxB,OAAO,MAAM,EACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,eAAe,GACf,WAAW,GACX,UAAU,GACV,UAAU,GACV,QAAQ,GACR,YAAY,GACZ,WAAW,CACd,CAAC;
|
|
1
|
+
{"version":3,"file":"replace-file-copy-fallback.d.ts","sourceRoot":"","sources":["../src/replace-file-copy-fallback.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,EAAE,EAAE,KAAK,WAAW,EAAc,MAAM,SAAS,CAAC;AAQ/D,MAAM,MAAM,oCAAoC,GAAG,QAAQ,CAAC;AAC5D,MAAM,MAAM,oCAAoC,GAAG,kBAAkB,GAAG,MAAM,CAAC;AAC/E,MAAM,MAAM,+BAA+B,GAAG,UAAU,GAAG,gBAAgB,CAAC;AAC5E,MAAM,MAAM,sCAAsC,GAAG;IACnD,OAAO,EAAE,+BAA+B,CAAC;CAC1C,CAAC;AAEF,KAAK,eAAe,GAAG;IACrB,KAAK,EAAE,cAAc,kBAAkB,EAAE,KAAK,CAAC;IAC/C,IAAI,EAAE,cAAc,kBAAkB,EAAE,IAAI,CAAC;IAC7C,EAAE,EAAE,cAAc,kBAAkB,EAAE,EAAE,CAAC;IACzC,MAAM,EAAE,cAAc,kBAAkB,EAAE,MAAM,CAAC;CAClD,CAAC;AAEF,KAAK,cAAc,GAAG,IAAI,CACxB,OAAO,MAAM,EACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,eAAe,GACf,WAAW,GACX,UAAU,GACV,UAAU,GACV,QAAQ,GACR,YAAY,GACZ,WAAW,CACd,CAAC;AAqHF,wBAAsB,+BAA+B,CACnD,QAAQ,EAAE,eAAe,EACzB,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,oCAAoC,GAC5C,OAAO,CAAC,IAAI,CAAC,CAIf;AAED,wBAAgB,mCAAmC,CACjD,QAAQ,EAAE,cAAc,EACxB,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,oCAAoC,GAC5C,IAAI,CAIN;AA+HD,wBAAsB,mBAAmB,CAAC,MAAM,EAAE;IAChD,QAAQ,EAAE,eAAe,CAAC;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,oBAAoB,CAAC,EAAE,oCAAoC,CAAC;IAC5D,OAAO,EAAE,oCAAoC,CAAC;IAC9C,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,sBAAsB,CAAC,EAAE,WAAW,CAAC;IACrC,IAAI,EAAE,OAAO,CAAC;CACf,GAAG,OAAO,CAAC,IAAI,CAAC,CAuEhB;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE;IAC9C,QAAQ,EAAE,cAAc,CAAC;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,oBAAoB,CAAC,EAAE,oCAAoC,CAAC;IAC5D,OAAO,EAAE,oCAAoC,CAAC;IAC9C,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,sBAAsB,CAAC,EAAE,WAAW,CAAC;IACrC,UAAU,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD,IAAI,EAAE,OAAO,CAAC;CACf,GAAG,IAAI,CAuEP"}
|
|
@@ -10,10 +10,28 @@ const NOFOLLOW = SUPPORTS_NOFOLLOW ? syncFs.constants.O_NOFOLLOW : 0;
|
|
|
10
10
|
const OPEN_READ_FLAGS = resolveReadOpenFlags();
|
|
11
11
|
const OPEN_READ_WRITE_FLAGS = syncFs.constants.O_RDWR | NOFOLLOW;
|
|
12
12
|
const OPEN_WRITE_EXCLUSIVE_FLAGS = syncFs.constants.O_WRONLY | syncFs.constants.O_CREAT | syncFs.constants.O_EXCL | NOFOLLOW;
|
|
13
|
+
function closeSyncAfterAdmissionFailure(fsModule, fd, error) {
|
|
14
|
+
try {
|
|
15
|
+
fsModule.closeSync(fd);
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
// Preserve the already-selected admission failure.
|
|
19
|
+
}
|
|
20
|
+
throw error;
|
|
21
|
+
}
|
|
13
22
|
function notFound(error) {
|
|
14
23
|
return error.code === "ENOENT";
|
|
15
24
|
}
|
|
16
|
-
function assertPinnedDestination(pathname, opened, dest, hardlinks) {
|
|
25
|
+
function assertPinnedDestination(pathname, opened, dest, hardlinks, admission) {
|
|
26
|
+
if (admission === "hardlinks") {
|
|
27
|
+
if (pathname.isSymbolicLink() || !pathname.isFile() || !sameFileIdentity(pathname, opened)) {
|
|
28
|
+
throw new FsSafeError("path-mismatch", `Atomic replace destination changed while opening: ${dest}`);
|
|
29
|
+
}
|
|
30
|
+
if (opened.nlink > 1) {
|
|
31
|
+
throw new FsSafeError("hardlink", `Hardlinked atomic replace destination not allowed: ${dest}`);
|
|
32
|
+
}
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
17
35
|
if (pathname.isSymbolicLink()) {
|
|
18
36
|
throw new FsSafeError("symlink", `Refusing copy fallback through symlink destination: ${dest}`);
|
|
19
37
|
}
|
|
@@ -27,7 +45,7 @@ function assertPinnedDestination(pathname, opened, dest, hardlinks) {
|
|
|
27
45
|
throw new FsSafeError("hardlink", `Hardlinked copy fallback destination not allowed: ${dest}`);
|
|
28
46
|
}
|
|
29
47
|
}
|
|
30
|
-
async function openPinnedDestination(fsModule, dest, hardlinks) {
|
|
48
|
+
async function openPinnedDestination(fsModule, dest, admission, hardlinks) {
|
|
31
49
|
let preview;
|
|
32
50
|
try {
|
|
33
51
|
preview = fsModule === fs ? syncFs.lstatSync(dest) : await fsModule.lstat(dest);
|
|
@@ -39,22 +57,24 @@ async function openPinnedDestination(fsModule, dest, hardlinks) {
|
|
|
39
57
|
}
|
|
40
58
|
if (!preview)
|
|
41
59
|
return null;
|
|
42
|
-
if (preview.isSymbolicLink())
|
|
60
|
+
if (admission === "hardlinks" && (preview.isSymbolicLink() || !preview.isFile()))
|
|
61
|
+
return null;
|
|
62
|
+
if (admission === "restore" && preview.isSymbolicLink()) {
|
|
43
63
|
throw new FsSafeError("symlink", `Refusing copy fallback through symlink destination: ${dest}`);
|
|
44
64
|
}
|
|
45
|
-
const handle = await fsModule.open(dest, OPEN_READ_WRITE_FLAGS);
|
|
65
|
+
const handle = await fsModule.open(dest, admission === "restore" ? OPEN_READ_WRITE_FLAGS : OPEN_READ_FLAGS);
|
|
46
66
|
try {
|
|
47
67
|
const opened = fsModule === fs ? syncFs.fstatSync(handle.fd) : await handle.stat();
|
|
48
68
|
const current = fsModule === fs ? syncFs.lstatSync(dest) : await fsModule.lstat(dest);
|
|
49
|
-
assertPinnedDestination(current, opened, dest, hardlinks);
|
|
50
|
-
return
|
|
69
|
+
assertPinnedDestination(current, opened, dest, hardlinks, admission);
|
|
70
|
+
return handle;
|
|
51
71
|
}
|
|
52
72
|
catch (error) {
|
|
53
73
|
await handle.close().catch(() => undefined);
|
|
54
74
|
throw error;
|
|
55
75
|
}
|
|
56
76
|
}
|
|
57
|
-
function openPinnedDestinationSync(fsModule, dest, hardlinks) {
|
|
77
|
+
function openPinnedDestinationSync(fsModule, dest, admission, hardlinks) {
|
|
58
78
|
let preview;
|
|
59
79
|
try {
|
|
60
80
|
preview = fsModule.lstatSync(dest);
|
|
@@ -64,77 +84,34 @@ function openPinnedDestinationSync(fsModule, dest, hardlinks) {
|
|
|
64
84
|
return null;
|
|
65
85
|
throw error;
|
|
66
86
|
}
|
|
67
|
-
if (preview.isSymbolicLink())
|
|
87
|
+
if (admission === "hardlinks" && (preview.isSymbolicLink() || !preview.isFile()))
|
|
88
|
+
return null;
|
|
89
|
+
if (admission === "restore" && preview.isSymbolicLink()) {
|
|
68
90
|
throw new FsSafeError("symlink", `Refusing copy fallback through symlink destination: ${dest}`);
|
|
69
91
|
}
|
|
70
|
-
const fd = fsModule.openSync(dest, OPEN_READ_WRITE_FLAGS);
|
|
92
|
+
const fd = fsModule.openSync(dest, admission === "restore" ? OPEN_READ_WRITE_FLAGS : OPEN_READ_FLAGS);
|
|
71
93
|
try {
|
|
72
94
|
const opened = fsModule.fstatSync(fd);
|
|
73
|
-
assertPinnedDestination(fsModule.lstatSync(dest), opened, dest, hardlinks);
|
|
74
|
-
return
|
|
95
|
+
assertPinnedDestination(fsModule.lstatSync(dest), opened, dest, hardlinks, admission);
|
|
96
|
+
return fd;
|
|
75
97
|
}
|
|
76
98
|
catch (error) {
|
|
77
|
-
fsModule
|
|
78
|
-
throw error;
|
|
99
|
+
closeSyncAfterAdmissionFailure(fsModule, fd, error);
|
|
79
100
|
}
|
|
80
101
|
}
|
|
81
102
|
export async function assertDestinationHardlinkPolicy(fsModule, dest, policy) {
|
|
82
103
|
if (policy !== "reject")
|
|
83
104
|
return;
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
preview = fsModule === fs ? syncFs.lstatSync(dest) : await fsModule.lstat(dest);
|
|
87
|
-
}
|
|
88
|
-
catch (error) {
|
|
89
|
-
if (notFound(error))
|
|
90
|
-
return;
|
|
91
|
-
throw error;
|
|
92
|
-
}
|
|
93
|
-
if (!preview || preview.isSymbolicLink() || !preview.isFile())
|
|
94
|
-
return;
|
|
95
|
-
const handle = await fsModule.open(dest, OPEN_READ_FLAGS);
|
|
96
|
-
try {
|
|
97
|
-
const opened = fsModule === fs ? syncFs.fstatSync(handle.fd) : await handle.stat();
|
|
98
|
-
const current = fsModule === fs ? syncFs.lstatSync(dest) : await fsModule.lstat(dest);
|
|
99
|
-
if (current.isSymbolicLink() || !current.isFile() || !sameFileIdentity(current, opened)) {
|
|
100
|
-
throw new FsSafeError("path-mismatch", `Atomic replace destination changed while opening: ${dest}`);
|
|
101
|
-
}
|
|
102
|
-
if (opened.nlink > 1) {
|
|
103
|
-
throw new FsSafeError("hardlink", `Hardlinked atomic replace destination not allowed: ${dest}`);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
finally {
|
|
105
|
+
const handle = await openPinnedDestination(fsModule, dest, "hardlinks");
|
|
106
|
+
if (handle)
|
|
107
107
|
await handle.close().catch(() => undefined);
|
|
108
|
-
}
|
|
109
108
|
}
|
|
110
109
|
export function assertDestinationHardlinkPolicySync(fsModule, dest, policy) {
|
|
111
110
|
if (policy !== "reject")
|
|
112
111
|
return;
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
preview = fsModule.lstatSync(dest);
|
|
116
|
-
}
|
|
117
|
-
catch (error) {
|
|
118
|
-
if (notFound(error))
|
|
119
|
-
return;
|
|
120
|
-
throw error;
|
|
121
|
-
}
|
|
122
|
-
if (preview.isSymbolicLink() || !preview.isFile())
|
|
123
|
-
return;
|
|
124
|
-
const fd = fsModule.openSync(dest, OPEN_READ_FLAGS);
|
|
125
|
-
try {
|
|
126
|
-
const opened = fsModule.fstatSync(fd);
|
|
127
|
-
const current = fsModule.lstatSync(dest);
|
|
128
|
-
if (current.isSymbolicLink() || !current.isFile() || !sameFileIdentity(current, opened)) {
|
|
129
|
-
throw new FsSafeError("path-mismatch", `Atomic replace destination changed while opening: ${dest}`);
|
|
130
|
-
}
|
|
131
|
-
if (opened.nlink > 1) {
|
|
132
|
-
throw new FsSafeError("hardlink", `Hardlinked atomic replace destination not allowed: ${dest}`);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
finally {
|
|
112
|
+
const fd = openPinnedDestinationSync(fsModule, dest, "hardlinks");
|
|
113
|
+
if (fd !== null)
|
|
136
114
|
fsModule.closeSync(fd);
|
|
137
|
-
}
|
|
138
115
|
}
|
|
139
116
|
function restoreReadOptions(stat, maxBytes) {
|
|
140
117
|
return {
|
|
@@ -183,7 +160,7 @@ function writeAllSync(fsModule, fd, data) {
|
|
|
183
160
|
function restoreFailure(writeError, cleanup, restoreError) {
|
|
184
161
|
const primary = writeError instanceof Error ? writeError : new Error(String(writeError));
|
|
185
162
|
const details = { cleanup };
|
|
186
|
-
const cause =
|
|
163
|
+
const cause = cleanup === "restore-failed"
|
|
187
164
|
? new AggregateError([primary, restoreError], "copy fallback and original restoration both failed")
|
|
188
165
|
: primary;
|
|
189
166
|
return new FsSafeError("helper-failed", cleanup === "restored"
|
|
@@ -246,11 +223,12 @@ export async function copyFallbackReplace(params) {
|
|
|
246
223
|
});
|
|
247
224
|
const { replacement } = source;
|
|
248
225
|
let destHandle = null;
|
|
226
|
+
let closeRequiredForSuccess = false;
|
|
249
227
|
try {
|
|
250
228
|
if (params.restore === "restore-original") {
|
|
251
|
-
const pinned = await openPinnedDestination(params.fsModule, params.dest, params.destinationHardlinks);
|
|
229
|
+
const pinned = await openPinnedDestination(params.fsModule, params.dest, "restore", params.destinationHardlinks);
|
|
252
230
|
if (pinned) {
|
|
253
|
-
destHandle = pinned
|
|
231
|
+
destHandle = pinned;
|
|
254
232
|
await replacePinnedWithRestore(params.fsModule, destHandle, replacement, params.maxRestoreBytes, source.mode);
|
|
255
233
|
}
|
|
256
234
|
}
|
|
@@ -277,10 +255,20 @@ export async function copyFallbackReplace(params) {
|
|
|
277
255
|
if (params.sync) {
|
|
278
256
|
await destHandle.sync();
|
|
279
257
|
}
|
|
258
|
+
closeRequiredForSuccess = !params.sync;
|
|
280
259
|
}
|
|
281
260
|
}
|
|
282
261
|
finally {
|
|
283
|
-
|
|
262
|
+
if (destHandle) {
|
|
263
|
+
try {
|
|
264
|
+
await destHandle.close();
|
|
265
|
+
}
|
|
266
|
+
catch (closeError) {
|
|
267
|
+
if (closeRequiredForSuccess) {
|
|
268
|
+
throw closeError;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
284
272
|
}
|
|
285
273
|
}
|
|
286
274
|
export function copyFallbackReplaceSync(params) {
|
|
@@ -291,11 +279,12 @@ export function copyFallbackReplaceSync(params) {
|
|
|
291
279
|
});
|
|
292
280
|
const { replacement } = source;
|
|
293
281
|
let destFd;
|
|
282
|
+
let closeRequiredForSuccess = false;
|
|
294
283
|
try {
|
|
295
284
|
if (params.restore === "restore-original") {
|
|
296
|
-
const pinned = openPinnedDestinationSync(params.fsModule, params.dest, params.destinationHardlinks);
|
|
297
|
-
if (pinned) {
|
|
298
|
-
destFd = pinned
|
|
285
|
+
const pinned = openPinnedDestinationSync(params.fsModule, params.dest, "restore", params.destinationHardlinks);
|
|
286
|
+
if (pinned !== null) {
|
|
287
|
+
destFd = pinned;
|
|
299
288
|
replacePinnedWithRestoreSync(params.fsModule, destFd, replacement, params.maxRestoreBytes, source.mode, params.fchmodSync);
|
|
300
289
|
}
|
|
301
290
|
}
|
|
@@ -321,6 +310,7 @@ export function copyFallbackReplaceSync(params) {
|
|
|
321
310
|
if (params.sync) {
|
|
322
311
|
params.fsModule.fsyncSync(destFd);
|
|
323
312
|
}
|
|
313
|
+
closeRequiredForSuccess = !params.sync;
|
|
324
314
|
}
|
|
325
315
|
}
|
|
326
316
|
finally {
|
|
@@ -328,8 +318,10 @@ export function copyFallbackReplaceSync(params) {
|
|
|
328
318
|
try {
|
|
329
319
|
params.fsModule.closeSync(destFd);
|
|
330
320
|
}
|
|
331
|
-
catch {
|
|
332
|
-
|
|
321
|
+
catch (closeError) {
|
|
322
|
+
if (closeRequiredForSuccess) {
|
|
323
|
+
throw closeError;
|
|
324
|
+
}
|
|
333
325
|
}
|
|
334
326
|
}
|
|
335
327
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"replace-file-copy-source.d.ts","sourceRoot":"","sources":["../src/replace-file-copy-source.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,EAAE,EAAE,KAAK,WAAW,
|
|
1
|
+
{"version":3,"file":"replace-file-copy-source.d.ts","sourceRoot":"","sources":["../src/replace-file-copy-source.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,EAAE,EAAE,KAAK,WAAW,EAAc,MAAM,SAAS,CAAC;AAQ/D,KAAK,qBAAqB,GAAG,IAAI,CAAC,cAAc,kBAAkB,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC,CAAC;AACvF,KAAK,oBAAoB,GAAG,IAAI,CAC9B,OAAO,MAAM,EACb,WAAW,GAAG,WAAW,GAAG,WAAW,GAAG,UAAU,GAAG,UAAU,CAClE,CAAC;AA8DF,wBAAsB,mBAAmB,CAAC,MAAM,EAAE;IAChD,QAAQ,EAAE,qBAAqB,CAAC;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,gBAAgB,CAAC,EAAE,WAAW,CAAC;CAChC,GAAG,OAAO,CAAC;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAyBjD;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE;IAC9C,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,gBAAgB,CAAC,EAAE,WAAW,CAAC;CAChC,GAAG;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAyBxC"}
|
|
@@ -17,6 +17,14 @@ function assertSourcePreview(source, src) {
|
|
|
17
17
|
throw new FsSafeError("hardlink", `Hardlinked copy fallback source not allowed: ${src}`);
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
|
+
function assertOpenedSource(opened, current, src) {
|
|
21
|
+
if (!opened.isFile() || current.isSymbolicLink() || !sameFileIdentity(opened, current)) {
|
|
22
|
+
throw new FsSafeError("path-mismatch", `Copy fallback source changed while opening: ${src}`);
|
|
23
|
+
}
|
|
24
|
+
if (opened.nlink !== 1) {
|
|
25
|
+
throw new FsSafeError("hardlink", `Hardlinked copy fallback source not allowed: ${src}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
20
28
|
function sourceSymlinkError(src, cause) {
|
|
21
29
|
return new FsSafeError("symlink", `Refusing copy fallback from non-file source: ${src}`, {
|
|
22
30
|
cause,
|
|
@@ -66,12 +74,7 @@ export async function readOwnedCopySource(params) {
|
|
|
66
74
|
const opened = params.fsModule === fs ? syncFs.fstatSync(handle.fd) : await handle.stat();
|
|
67
75
|
const current = params.fsModule === fs
|
|
68
76
|
? syncFs.lstatSync(params.src) : await params.fsModule.lstat(params.src);
|
|
69
|
-
|
|
70
|
-
throw new FsSafeError("path-mismatch", `Copy fallback source changed while opening: ${params.src}`);
|
|
71
|
-
}
|
|
72
|
-
if (opened.nlink !== 1) {
|
|
73
|
-
throw new FsSafeError("hardlink", `Hardlinked copy fallback source not allowed: ${params.src}`);
|
|
74
|
-
}
|
|
77
|
+
assertOpenedSource(opened, current, params.src);
|
|
75
78
|
return { replacement: await handle.readFile(), mode: opened.mode };
|
|
76
79
|
}
|
|
77
80
|
finally {
|
|
@@ -88,12 +91,7 @@ export function readOwnedCopySourceSync(params) {
|
|
|
88
91
|
}
|
|
89
92
|
const opened = params.fsModule.fstatSync(fd);
|
|
90
93
|
const current = params.fsModule.lstatSync(params.src);
|
|
91
|
-
|
|
92
|
-
throw new FsSafeError("path-mismatch", `Copy fallback source changed while opening: ${params.src}`);
|
|
93
|
-
}
|
|
94
|
-
if (opened.nlink !== 1) {
|
|
95
|
-
throw new FsSafeError("hardlink", `Hardlinked copy fallback source not allowed: ${params.src}`);
|
|
96
|
-
}
|
|
94
|
+
assertOpenedSource(opened, current, params.src);
|
|
97
95
|
return { replacement: readAllSync(params.fsModule, fd, opened.size), mode: opened.mode };
|
|
98
96
|
}
|
|
99
97
|
finally {
|
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
import syncFs, { type BigIntStats } from "node:fs";
|
|
2
2
|
import fs, { type FileHandle } from "node:fs/promises";
|
|
3
|
-
import { type AnyAsyncDirectoryGuard } from "./directory-guard.js";
|
|
4
3
|
type AsyncOwnerFileSystem = Pick<typeof fs, "lstat" | "open" | "unlink">;
|
|
5
4
|
type SyncOwnerFileSystem = Pick<typeof syncFs, "closeSync" | "fstatSync" | "lstatSync" | "openSync" | "readFileSync" | "unlinkSync">;
|
|
5
|
+
export type AtomicTempFailure = Readonly<{
|
|
6
|
+
error: unknown;
|
|
7
|
+
}>;
|
|
6
8
|
export declare function removePathIfIdentityUnchanged(targetPath: string, identity: Pick<BigIntStats, "dev" | "ino">): Promise<void>;
|
|
7
|
-
export declare function cleanupPinnedFilePath(params: {
|
|
8
|
-
pathname: string;
|
|
9
|
-
handle: FileHandle;
|
|
10
|
-
identity?: BigIntStats;
|
|
11
|
-
parentGuard: AnyAsyncDirectoryGuard;
|
|
12
|
-
}): Promise<void>;
|
|
13
9
|
export declare class AsyncAtomicTempOwner {
|
|
14
10
|
#private;
|
|
15
11
|
readonly pathname: string;
|
|
@@ -26,7 +22,7 @@ export declare class AsyncAtomicTempOwner {
|
|
|
26
22
|
markRenamed(): void;
|
|
27
23
|
finish(params: {
|
|
28
24
|
fsModule: AsyncOwnerFileSystem;
|
|
29
|
-
|
|
25
|
+
originalFailure?: AtomicTempFailure;
|
|
30
26
|
throwOnCleanupError: boolean;
|
|
31
27
|
}): Promise<void>;
|
|
32
28
|
}
|
|
@@ -46,7 +42,7 @@ export declare class SyncAtomicTempOwner {
|
|
|
46
42
|
markRenamed(): void;
|
|
47
43
|
finish(params: {
|
|
48
44
|
fsModule: SyncOwnerFileSystem;
|
|
49
|
-
|
|
45
|
+
originalFailure?: AtomicTempFailure;
|
|
50
46
|
throwOnCleanupError: boolean;
|
|
51
47
|
}): void;
|
|
52
48
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"replace-file-temp-owner.d.ts","sourceRoot":"","sources":["../src/replace-file-temp-owner.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,EAAE,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,EAAE,EAAE,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"replace-file-temp-owner.d.ts","sourceRoot":"","sources":["../src/replace-file-temp-owner.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,EAAE,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,EAAE,EAAE,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAWvD,KAAK,oBAAoB,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAC,CAAC;AACzE,KAAK,mBAAmB,GAAG,IAAI,CAC7B,OAAO,MAAM,EACb,WAAW,GAAG,WAAW,GAAG,WAAW,GAAG,UAAU,GAAG,cAAc,GAAG,YAAY,CACrF,CAAC;AAIF,MAAM,MAAM,iBAAiB,GAAG,QAAQ,CAAC;IAAE,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC,CAAC;AAmB7D,wBAAsB,6BAA6B,CACjD,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,IAAI,CAAC,WAAW,EAAE,KAAK,GAAG,KAAK,CAAC,GACzC,OAAO,CAAC,IAAI,CAAC,CAOf;AAkFD,qBAAa,oBAAoB;;IAC/B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAM1B,YAAY,QAAQ,EAAE,MAAM,EAG3B;IAED,KAAK,IAAI,IAAI,CAEZ;IAED,QAAQ,CAAC,UAAU,aAAc,WAAW,KAAG,IAAI,CAGjD;IAEF,KAAK,CAAC,IAAI,EAAE;QAAE,MAAM,EAAE,UAAU,CAAC;QAAC,QAAQ,EAAE,WAAW,CAAA;KAAE,GAAG,IAAI,CAG/D;IAED,IAAI,QAAQ,IAAI,WAAW,CAG1B;IAEK,aAAa,CAAC,QAAQ,EAAE,oBAAoB,EAAE,QAAQ,SAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAoB3F;IAEK,eAAe,CACnB,QAAQ,EAAE,oBAAoB,EAC9B,QAAQ,EAAE,MAAM,EAChB,YAAY,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC,IAAI,CAAC,CA8Cf;IAED,WAAW,IAAI,IAAI,CAGlB;IAEK,MAAM,CAAC,MAAM,EAAE;QACnB,QAAQ,EAAE,oBAAoB,CAAC;QAC/B,eAAe,CAAC,EAAE,iBAAiB,CAAC;QACpC,mBAAmB,EAAE,OAAO,CAAC;KAC9B,GAAG,OAAO,CAAC,IAAI,CAAC,CAqChB;CACF;AAED,qBAAa,mBAAmB;;IAC9B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAM1B,YAAY,QAAQ,EAAE,MAAM,EAG3B;IAED,KAAK,IAAI,IAAI,CAEZ;IAED,QAAQ,CAAC,UAAU,aAAc,WAAW,KAAG,IAAI,CAGjD;IAEF,KAAK,CAAC,IAAI,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,WAAW,CAAA;KAAE,GAAG,IAAI,CAGvD;IAED,IAAI,QAAQ,IAAI,WAAW,CAG1B;IAED,aAAa,CAAC,QAAQ,EAAE,mBAAmB,EAAE,QAAQ,SAAgB,GAAG,IAAI,CAkB3E;IAED,eAAe,CACb,QAAQ,EAAE,mBAAmB,EAC7B,QAAQ,EAAE,MAAM,EAChB,YAAY,CAAC,EAAE,MAAM,GACpB,IAAI,CAkDN;IAED,WAAW,IAAI,IAAI,CAGlB;IAED,MAAM,CAAC,MAAM,EAAE;QACb,QAAQ,EAAE,mBAAmB,CAAC;QAC9B,eAAe,CAAC,EAAE,iBAAiB,CAAC;QACpC,mBAAmB,EAAE,OAAO,CAAC;KAC9B,GAAG,IAAI,CAuCP;CACF"}
|