@openclaw/fs-safe 0.11.0 → 0.13.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 +119 -0
- package/README.md +15 -8
- package/dist/absolute-path.d.ts.map +1 -1
- package/dist/absolute-path.js +32 -11
- package/dist/advanced.d.ts +3 -1
- package/dist/advanced.d.ts.map +1 -1
- package/dist/advanced.js +3 -1
- package/dist/archive-entry.d.ts.map +1 -1
- package/dist/archive-entry.js +4 -3
- package/dist/archive-gzip-tail.d.ts.map +1 -1
- package/dist/archive-gzip-tail.js +13 -9
- package/dist/archive-input.d.ts.map +1 -1
- package/dist/archive-input.js +6 -2
- package/dist/archive-merge.d.ts.map +1 -1
- package/dist/archive-merge.js +15 -2
- package/dist/archive-native.d.ts.map +1 -1
- package/dist/archive-native.js +7 -19
- package/dist/archive-plan.js +1 -1
- package/dist/archive-read.d.ts.map +1 -1
- package/dist/archive-read.js +55 -46
- package/dist/archive-staging.d.ts.map +1 -1
- package/dist/archive-staging.js +43 -10
- package/dist/archive-tar-inspect.d.ts.map +1 -1
- package/dist/archive-tar-inspect.js +4 -1
- package/dist/archive-zip-admission.d.ts +1 -1
- package/dist/archive-zip-admission.d.ts.map +1 -1
- package/dist/archive-zip-admission.js +2 -2
- package/dist/archive-zip-directory.d.ts +3 -0
- package/dist/archive-zip-directory.d.ts.map +1 -1
- package/dist/archive-zip-directory.js +13 -2
- package/dist/archive-zip-loader.d.ts +3 -2
- package/dist/archive-zip-loader.d.ts.map +1 -1
- package/dist/archive-zip-loader.js +30 -4
- package/dist/archive-zip-manifest.d.ts +5 -0
- package/dist/archive-zip-manifest.d.ts.map +1 -0
- package/dist/archive-zip-manifest.js +22 -0
- package/dist/archive-zip-names.d.ts +6 -1
- package/dist/archive-zip-names.d.ts.map +1 -1
- package/dist/archive-zip-names.js +50 -17
- package/dist/archive-zip-preflight.d.ts.map +1 -1
- package/dist/archive-zip-preflight.js +3 -2
- package/dist/archive.d.ts.map +1 -1
- package/dist/archive.js +9 -4
- package/dist/bounded-read.js +2 -2
- package/dist/darwin-acl.d.ts +4 -0
- package/dist/darwin-acl.d.ts.map +1 -0
- package/dist/darwin-acl.js +24 -0
- package/dist/deny-mutations.d.ts.map +1 -1
- package/dist/deny-mutations.js +8 -2
- package/dist/device-path.d.ts.map +1 -1
- package/dist/device-path.js +5 -3
- package/dist/directory-durability.d.ts.map +1 -1
- package/dist/directory-durability.js +67 -23
- package/dist/directory-entry-path.d.ts +3 -0
- package/dist/directory-entry-path.d.ts.map +1 -0
- package/dist/directory-entry-path.js +21 -0
- package/dist/directory-guard.d.ts +17 -1
- package/dist/directory-guard.d.ts.map +1 -1
- package/dist/directory-guard.js +134 -48
- package/dist/directory-mode-node.d.ts +12 -0
- package/dist/directory-mode-node.d.ts.map +1 -1
- package/dist/directory-mode-node.js +102 -4
- package/dist/effective-uid.d.ts +2 -0
- package/dist/effective-uid.d.ts.map +1 -0
- package/dist/effective-uid.js +25 -0
- package/dist/file-handle-transfer.d.ts.map +1 -1
- package/dist/file-handle-transfer.js +98 -27
- package/dist/file-hash.d.ts.map +1 -1
- package/dist/file-hash.js +12 -2
- package/dist/file-lock-sync.d.ts.map +1 -1
- package/dist/file-lock-sync.js +36 -6
- package/dist/file-lock.d.ts.map +1 -1
- package/dist/file-lock.js +29 -9
- package/dist/file-observation.d.ts +1 -1
- package/dist/file-observation.d.ts.map +1 -1
- package/dist/file-store-boundary.d.ts +6 -2
- package/dist/file-store-boundary.d.ts.map +1 -1
- package/dist/file-store-boundary.js +20 -65
- package/dist/file-store-copy-source.d.ts +5 -0
- package/dist/file-store-copy-source.d.ts.map +1 -0
- package/dist/file-store-copy-source.js +31 -0
- package/dist/file-store-path.d.ts.map +1 -1
- package/dist/file-store-path.js +4 -1
- package/dist/file-store-prune.d.ts.map +1 -1
- package/dist/file-store-prune.js +9 -1
- package/dist/file-store-sync-directory.d.ts +16 -0
- package/dist/file-store-sync-directory.d.ts.map +1 -0
- package/dist/file-store-sync-directory.js +349 -0
- package/dist/file-store.d.ts.map +1 -1
- package/dist/file-store.js +11 -28
- package/dist/filename.d.ts +1 -0
- package/dist/filename.d.ts.map +1 -1
- package/dist/filename.js +85 -23
- package/dist/fs.d.ts.map +1 -1
- package/dist/fs.js +3 -2
- package/dist/guarded-mkdir.d.ts +10 -0
- package/dist/guarded-mkdir.d.ts.map +1 -1
- package/dist/guarded-mkdir.js +217 -33
- package/dist/guest.d.ts.map +1 -1
- package/dist/guest.js +16 -5
- package/dist/home-dir.d.ts.map +1 -1
- package/dist/home-dir.js +73 -10
- package/dist/install-path.d.ts +6 -0
- package/dist/install-path.d.ts.map +1 -1
- package/dist/install-path.js +67 -19
- package/dist/json-durable-queue-ownership.d.ts +8 -0
- package/dist/json-durable-queue-ownership.d.ts.map +1 -1
- package/dist/json-durable-queue-ownership.js +134 -43
- package/dist/json-durable-queue-paths.d.ts +11 -0
- package/dist/json-durable-queue-paths.d.ts.map +1 -0
- package/dist/json-durable-queue-paths.js +42 -0
- package/dist/json-durable-queue-read.d.ts +6 -0
- package/dist/json-durable-queue-read.d.ts.map +1 -0
- package/dist/json-durable-queue-read.js +61 -0
- package/dist/json-durable-queue.d.ts +1 -1
- package/dist/json-durable-queue.d.ts.map +1 -1
- package/dist/json-durable-queue.js +83 -134
- package/dist/json-store.d.ts.map +1 -1
- package/dist/json-store.js +5 -1
- package/dist/json.d.ts.map +1 -1
- package/dist/json.js +54 -22
- package/dist/local-file-access.d.ts.map +1 -1
- package/dist/local-file-access.js +4 -0
- package/dist/local-file-descriptor.d.ts +17 -0
- package/dist/local-file-descriptor.d.ts.map +1 -0
- package/dist/local-file-descriptor.js +84 -0
- package/dist/local-roots.d.ts.map +1 -1
- package/dist/local-roots.js +35 -7
- package/dist/move-path-cleanup.d.ts +2 -0
- package/dist/move-path-cleanup.d.ts.map +1 -1
- package/dist/move-path-cleanup.js +44 -18
- package/dist/move-path.d.ts.map +1 -1
- package/dist/move-path.js +31 -6
- package/dist/native-binding.d.ts +21 -0
- package/dist/native-binding.d.ts.map +1 -1
- package/dist/native-directory-observation.d.ts +17 -0
- package/dist/native-directory-observation.d.ts.map +1 -0
- package/dist/native-directory-observation.js +37 -0
- package/dist/native-parent-admission.d.ts +31 -0
- package/dist/native-parent-admission.d.ts.map +1 -0
- package/dist/native-parent-admission.js +124 -0
- package/dist/native-pinned-write-windows.d.ts +0 -1
- package/dist/native-pinned-write-windows.d.ts.map +1 -1
- package/dist/native-pinned-write-windows.js +0 -3
- package/dist/native-pinned-write.d.ts.map +1 -1
- package/dist/native-pinned-write.js +309 -40
- package/dist/output.d.ts.map +1 -1
- package/dist/output.js +15 -5
- package/dist/overwrite-file-handle.d.ts.map +1 -1
- package/dist/overwrite-file-handle.js +5 -1
- package/dist/owner-dacl.d.ts.map +1 -1
- package/dist/owner-dacl.js +2 -0
- package/dist/path-policy.d.ts.map +1 -1
- package/dist/path-policy.js +7 -2
- package/dist/path-prefix.d.ts +7 -0
- package/dist/path-prefix.d.ts.map +1 -0
- package/dist/path-prefix.js +82 -0
- package/dist/path-scope-lexical.d.ts +14 -0
- package/dist/path-scope-lexical.d.ts.map +1 -0
- package/dist/path-scope-lexical.js +37 -0
- package/dist/path-segment-route.d.ts +7 -0
- package/dist/path-segment-route.d.ts.map +1 -0
- package/dist/path-segment-route.js +24 -0
- package/dist/path-suffix-aliases.d.ts +10 -0
- package/dist/path-suffix-aliases.d.ts.map +1 -0
- package/dist/path-suffix-aliases.js +386 -0
- package/dist/path.d.ts.map +1 -1
- package/dist/path.js +35 -6
- package/dist/permissions-windows.d.ts.map +1 -1
- package/dist/permissions-windows.js +14 -3
- package/dist/permissions.d.ts.map +1 -1
- package/dist/permissions.js +37 -8
- package/dist/pinned-mutation-admission.d.ts +25 -0
- package/dist/pinned-mutation-admission.d.ts.map +1 -0
- package/dist/pinned-mutation-admission.js +425 -0
- package/dist/pinned-mutation-observation.d.ts +34 -0
- package/dist/pinned-mutation-observation.d.ts.map +1 -0
- package/dist/pinned-mutation-observation.js +142 -0
- package/dist/pinned-mutation-shared-route.d.ts +24 -0
- package/dist/pinned-mutation-shared-route.d.ts.map +1 -0
- package/dist/pinned-mutation-shared-route.js +70 -0
- package/dist/pinned-open.d.ts +6 -0
- package/dist/pinned-open.d.ts.map +1 -1
- package/dist/pinned-open.js +28 -10
- package/dist/pinned-write-types.d.ts +75 -0
- package/dist/pinned-write-types.d.ts.map +1 -0
- package/dist/pinned-write-types.js +1 -0
- package/dist/pinned-write.d.ts +7 -33
- package/dist/pinned-write.d.ts.map +1 -1
- package/dist/pinned-write.js +152 -17
- package/dist/private-directory.d.ts.map +1 -1
- package/dist/private-directory.js +2 -0
- package/dist/private-producer-handoff.d.ts +16 -0
- package/dist/private-producer-handoff.d.ts.map +1 -0
- package/dist/private-producer-handoff.js +272 -0
- package/dist/private-temp-workspace.d.ts +2 -39
- package/dist/private-temp-workspace.d.ts.map +1 -1
- package/dist/private-temp-workspace.js +183 -77
- package/dist/publish-file.d.ts.map +1 -1
- package/dist/publish-file.js +33 -29
- package/dist/regular-file.d.ts.map +1 -1
- package/dist/regular-file.js +56 -45
- package/dist/replace-directory.d.ts.map +1 -1
- package/dist/replace-directory.js +12 -5
- package/dist/replace-file-temp-owner.d.ts +1 -0
- package/dist/replace-file-temp-owner.d.ts.map +1 -1
- package/dist/replace-file-temp-owner.js +12 -1
- package/dist/replace-file.d.ts.map +1 -1
- package/dist/replace-file.js +24 -24
- package/dist/root-boundary.d.ts +29 -0
- package/dist/root-boundary.d.ts.map +1 -0
- package/dist/root-boundary.js +182 -0
- package/dist/root-context.d.ts +12 -1
- package/dist/root-context.d.ts.map +1 -1
- package/dist/root-context.js +89 -16
- package/dist/root-directory-creation.d.ts +13 -0
- package/dist/root-directory-creation.d.ts.map +1 -0
- package/dist/root-directory-creation.js +212 -0
- package/dist/root-directory-list.d.ts +16 -4
- package/dist/root-directory-list.d.ts.map +1 -1
- package/dist/root-directory-list.js +182 -33
- package/dist/root-directory.d.ts +23 -0
- package/dist/root-directory.d.ts.map +1 -0
- package/dist/root-directory.js +141 -0
- package/dist/root-errors.d.ts.map +1 -1
- package/dist/root-errors.js +12 -1
- package/dist/root-file-final-admission.d.ts +21 -0
- package/dist/root-file-final-admission.d.ts.map +1 -0
- package/dist/root-file-final-admission.js +83 -0
- package/dist/root-file.d.ts.map +1 -1
- package/dist/root-file.js +103 -24
- package/dist/root-impl.d.ts +1 -0
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +316 -325
- package/dist/root-move-noreplace.d.ts +14 -0
- package/dist/root-move-noreplace.d.ts.map +1 -0
- package/dist/root-move-noreplace.js +202 -0
- package/dist/root-move-preflight.d.ts +8 -0
- package/dist/root-move-preflight.d.ts.map +1 -0
- package/dist/root-move-preflight.js +16 -0
- package/dist/root-observed-path.d.ts +9 -0
- package/dist/root-observed-path.d.ts.map +1 -0
- package/dist/root-observed-path.js +95 -0
- package/dist/root-path-errors.d.ts +12 -0
- package/dist/root-path-errors.d.ts.map +1 -0
- package/dist/root-path-errors.js +13 -0
- package/dist/root-path-existing.d.ts +2 -0
- package/dist/root-path-existing.d.ts.map +1 -1
- package/dist/root-path-existing.js +63 -22
- package/dist/root-path-observation.d.ts +63 -0
- package/dist/root-path-observation.d.ts.map +1 -0
- package/dist/root-path-observation.js +180 -0
- package/dist/root-path-stat.d.ts +5 -0
- package/dist/root-path-stat.d.ts.map +1 -0
- package/dist/root-path-stat.js +101 -0
- package/dist/root-path-symlink.d.ts.map +1 -1
- package/dist/root-path-symlink.js +23 -4
- package/dist/root-path.d.ts +13 -0
- package/dist/root-path.d.ts.map +1 -1
- package/dist/root-path.js +263 -73
- package/dist/root-paths-lexical.d.ts +9 -0
- package/dist/root-paths-lexical.d.ts.map +1 -0
- package/dist/root-paths-lexical.js +22 -0
- package/dist/root-paths.d.ts +3 -29
- package/dist/root-paths.d.ts.map +1 -1
- package/dist/root-paths.js +89 -178
- package/dist/root-read-admission.d.ts +26 -0
- package/dist/root-read-admission.d.ts.map +1 -0
- package/dist/root-read-admission.js +94 -0
- package/dist/root-remove-identity.d.ts +15 -0
- package/dist/root-remove-identity.d.ts.map +1 -0
- package/dist/root-remove-identity.js +89 -0
- package/dist/root-remove-receipt.d.ts +17 -0
- package/dist/root-remove-receipt.d.ts.map +1 -0
- package/dist/root-remove-receipt.js +37 -0
- package/dist/root-remove.d.ts +2 -1
- package/dist/root-remove.d.ts.map +1 -1
- package/dist/root-remove.js +172 -10
- package/dist/root-walk.d.ts.map +1 -1
- package/dist/root-walk.js +2 -1
- package/dist/root-write-admission.d.ts +68 -0
- package/dist/root-write-admission.d.ts.map +1 -0
- package/dist/root-write-admission.js +322 -0
- package/dist/root-write-compatibility.d.ts +13 -0
- package/dist/root-write-compatibility.d.ts.map +1 -0
- package/dist/root-write-compatibility.js +74 -0
- package/dist/root-write-complete-parent.d.ts +42 -0
- package/dist/root-write-complete-parent.d.ts.map +1 -0
- package/dist/root-write-complete-parent.js +195 -0
- package/dist/root-write-mode.d.ts +2 -0
- package/dist/root-write-mode.d.ts.map +1 -1
- package/dist/root-write-mode.js +20 -7
- package/dist/root-write-publication.d.ts +24 -0
- package/dist/root-write-publication.d.ts.map +1 -0
- package/dist/root-write-publication.js +85 -0
- package/dist/root-write-verification.d.ts.map +1 -1
- package/dist/root-write-verification.js +18 -7
- package/dist/safe-path-segment.d.ts.map +1 -1
- package/dist/safe-path-segment.js +3 -1
- package/dist/secret-file.d.ts.map +1 -1
- package/dist/secret-file.js +62 -24
- package/dist/secret-read-async.d.ts.map +1 -1
- package/dist/secret-read-async.js +18 -13
- package/dist/secret-read-policy.d.ts.map +1 -1
- package/dist/secret-read-policy.js +5 -1
- package/dist/secure-file-windows.d.ts +10 -0
- package/dist/secure-file-windows.d.ts.map +1 -0
- package/dist/secure-file-windows.js +186 -0
- package/dist/secure-file.d.ts.map +1 -1
- package/dist/secure-file.js +98 -10
- package/dist/secure-temp-dir.d.ts +6 -3
- package/dist/secure-temp-dir.d.ts.map +1 -1
- package/dist/secure-temp-dir.js +121 -101
- package/dist/secure-temp-repair.d.ts +35 -0
- package/dist/secure-temp-repair.d.ts.map +1 -0
- package/dist/secure-temp-repair.js +106 -0
- package/dist/sibling-staged-file.d.ts +3 -1
- package/dist/sibling-staged-file.d.ts.map +1 -1
- package/dist/sibling-staged-file.js +181 -50
- package/dist/sibling-temp.d.ts.map +1 -1
- package/dist/sibling-temp.js +27 -12
- package/dist/sidecar-lock-acquire.d.ts.map +1 -1
- package/dist/sidecar-lock-acquire.js +45 -15
- package/dist/sidecar-lock-root.d.ts.map +1 -1
- package/dist/sidecar-lock-root.js +4 -2
- package/dist/sidecar-lock.d.ts.map +1 -1
- package/dist/sidecar-lock.js +2 -3
- package/dist/staged-directory.d.ts +14 -5
- package/dist/staged-directory.d.ts.map +1 -1
- package/dist/staged-directory.js +54 -3
- package/dist/standalone-publication-path.d.ts +2 -0
- package/dist/standalone-publication-path.d.ts.map +1 -0
- package/dist/standalone-publication-path.js +6 -0
- package/dist/stat-observation.d.ts +11 -0
- package/dist/stat-observation.d.ts.map +1 -0
- package/dist/stat-observation.js +64 -0
- package/dist/strict-file-identity.d.ts.map +1 -1
- package/dist/strict-file-identity.js +39 -2
- package/dist/symlink-parents.d.ts.map +1 -1
- package/dist/symlink-parents.js +7 -2
- package/dist/temp-target.d.ts +2 -0
- package/dist/temp-target.d.ts.map +1 -1
- package/dist/temp-target.js +133 -4
- package/dist/temp-workspace-admission.d.ts +22 -0
- package/dist/temp-workspace-admission.d.ts.map +1 -0
- package/dist/temp-workspace-admission.js +374 -0
- package/dist/temp-workspace-child-admission.d.ts +13 -0
- package/dist/temp-workspace-child-admission.d.ts.map +1 -0
- package/dist/temp-workspace-child-admission.js +95 -0
- package/dist/temp-workspace-descriptor.d.ts +48 -0
- package/dist/temp-workspace-descriptor.d.ts.map +1 -0
- package/dist/temp-workspace-descriptor.js +363 -0
- package/dist/temp-workspace-identity.d.ts +15 -0
- package/dist/temp-workspace-identity.d.ts.map +1 -0
- package/dist/temp-workspace-identity.js +41 -0
- package/dist/temp-workspace-owner.d.ts +7 -6
- package/dist/temp-workspace-owner.d.ts.map +1 -1
- package/dist/temp-workspace-owner.js +121 -61
- package/dist/temp-workspace-permissions.d.ts +4 -0
- package/dist/temp-workspace-permissions.d.ts.map +1 -0
- package/dist/temp-workspace-permissions.js +32 -0
- package/dist/temp-workspace-types.d.ts +40 -0
- package/dist/temp-workspace-types.d.ts.map +1 -0
- package/dist/temp-workspace-types.js +1 -0
- package/dist/temp.d.ts +1 -1
- package/dist/temp.d.ts.map +1 -1
- package/dist/temp.js +1 -1
- package/dist/test-hooks.d.ts +7 -0
- package/dist/test-hooks.d.ts.map +1 -1
- package/dist/text-atomic.d.ts.map +1 -1
- package/dist/text-atomic.js +3 -1
- package/dist/trash.d.ts.map +1 -1
- package/dist/trash.js +54 -8
- package/dist/walk.d.ts.map +1 -1
- package/dist/walk.js +20 -16
- package/dist/windows-owner.d.ts.map +1 -1
- package/dist/windows-owner.js +5 -0
- package/dist/windows-path-alias.d.ts +39 -0
- package/dist/windows-path-alias.d.ts.map +1 -0
- package/dist/windows-path-alias.js +153 -0
- package/docs/advanced.md +28 -1
- package/docs/archive.md +29 -2
- package/docs/atomic.md +27 -3
- package/docs/contributing.md +4 -1
- package/docs/copy.md +4 -1
- package/docs/durability.md +18 -2
- package/docs/errors.md +14 -6
- package/docs/file-store.md +30 -3
- package/docs/filename.md +21 -7
- package/docs/guest.md +5 -0
- package/docs/install-path.md +61 -15
- package/docs/install.md +12 -8
- package/docs/json-store.md +5 -1
- package/docs/json.md +11 -0
- package/docs/mutation-policy-proof.md +65 -0
- package/docs/native-helper.md +28 -9
- package/docs/native.md +46 -10
- package/docs/output.md +33 -11
- package/docs/path-prefix.md +64 -0
- package/docs/path-suffix-aliases.md +159 -0
- package/docs/path.md +12 -1
- package/docs/permissions.md +26 -1
- package/docs/private-file-store.md +14 -0
- package/docs/public-api.md +14 -0
- package/docs/quickstart.md +1 -1
- package/docs/reading.md +8 -1
- package/docs/root.md +21 -4
- package/docs/secret-file.md +3 -0
- package/docs/secure-file.md +21 -16
- package/docs/security-model.md +76 -9
- package/docs/sidecar-lock.md +28 -1
- package/docs/store.md +27 -2
- package/docs/temp.md +262 -34
- package/docs/test-hooks.md +14 -0
- package/docs/writing.md +38 -8
- package/package.json +10 -10
|
@@ -4,13 +4,43 @@ import fs, {} from "node:fs/promises";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { assertAsyncDirectoryGuard, assertDirectoryIdentitySync, createAsyncDirectoryGuard, } from "./directory-guard.js";
|
|
6
6
|
import { syncDirectoryBestEffort } from "./directory-durability.js";
|
|
7
|
+
import { isWindowsReservedDeviceName } from "./device-path.js";
|
|
7
8
|
import { FsSafeError } from "./errors.js";
|
|
9
|
+
import { getNativeBinding } from "./native.js";
|
|
10
|
+
import { handoffPrivateProducerFile, } from "./private-producer-handoff.js";
|
|
8
11
|
import { resolveReadOpenFlags } from "./read-open-flags.js";
|
|
9
|
-
import {
|
|
12
|
+
import { rootFromDirectoryGuard } from "./root-impl.js";
|
|
10
13
|
import { inspectFileIdentity } from "./strict-file-identity.js";
|
|
11
14
|
import { registerTempPathForExit } from "./temp-cleanup.js";
|
|
12
15
|
import { createOwnedTempFile } from "./temp-target.js";
|
|
13
16
|
import { serializePathWrite } from "./write-queue.js";
|
|
17
|
+
import { assertNoWindowsPathAlias, resolvePathPreservingWindowsRoot, } from "./windows-path-alias.js";
|
|
18
|
+
const INVALID_CALLBACK_COMPONENT_CHARACTERS = /[\u0000-\u001f\u007f-\u009f<>:"/\\|?*]/u;
|
|
19
|
+
function resolveCallbackTempDirectory(workspaceDir) {
|
|
20
|
+
assertNoWindowsPathAlias(workspaceDir, "filesystem", "sibling temp parent uses a Windows filesystem namespace alias");
|
|
21
|
+
const dir = resolvePathPreservingWindowsRoot(workspaceDir);
|
|
22
|
+
assertNoWindowsPathAlias(dir, "filesystem", "sibling temp parent uses a Windows filesystem namespace alias");
|
|
23
|
+
return dir;
|
|
24
|
+
}
|
|
25
|
+
export function resolveCallbackTempPath(workspaceDir, component) {
|
|
26
|
+
const dir = resolveCallbackTempDirectory(workspaceDir);
|
|
27
|
+
if (typeof component !== "string" ||
|
|
28
|
+
component === "" ||
|
|
29
|
+
component === "." ||
|
|
30
|
+
component === ".." ||
|
|
31
|
+
INVALID_CALLBACK_COMPONENT_CHARACTERS.test(component) ||
|
|
32
|
+
component.endsWith(".") ||
|
|
33
|
+
component.endsWith(" ") ||
|
|
34
|
+
isWindowsReservedDeviceName(component)) {
|
|
35
|
+
throw new FsSafeError("invalid-path", "callback temp name must be one path component");
|
|
36
|
+
}
|
|
37
|
+
const joined = path.join(dir, component);
|
|
38
|
+
assertNoWindowsPathAlias(joined, "filesystem", "sibling temp path uses a Windows filesystem namespace alias");
|
|
39
|
+
if (path.dirname(joined) !== dir) {
|
|
40
|
+
throw new FsSafeError("invalid-path", "callback temp path must be a direct workspace child");
|
|
41
|
+
}
|
|
42
|
+
return joined;
|
|
43
|
+
}
|
|
14
44
|
function assertRegularFile(stat) {
|
|
15
45
|
if (stat.isSymbolicLink()) {
|
|
16
46
|
throw new FsSafeError("symlink", "symlink sibling temp not allowed");
|
|
@@ -29,42 +59,102 @@ async function inspectStage(inspect, expected) {
|
|
|
29
59
|
return stat;
|
|
30
60
|
}, expected);
|
|
31
61
|
}
|
|
62
|
+
function aggregateErrors(errors, message) {
|
|
63
|
+
if (errors.length === 1)
|
|
64
|
+
return errors[0];
|
|
65
|
+
return new AggregateError(errors, message);
|
|
66
|
+
}
|
|
32
67
|
// Own the workspace before the producer can leave partial output. The finished
|
|
33
68
|
// file still enters the ordinary sibling admission and publication lifecycle.
|
|
34
69
|
async function writeIsolatedProducer(params) {
|
|
35
|
-
const
|
|
70
|
+
const tempPath = params.tempPath;
|
|
71
|
+
const write = params.write;
|
|
72
|
+
const writeReceiver = params.writeReceiver;
|
|
73
|
+
const parentGuard = params.parentGuard;
|
|
74
|
+
const assertParent = params.assertParent;
|
|
75
|
+
const syncTempFile = params.syncTempFile;
|
|
76
|
+
assertParent();
|
|
77
|
+
const targetRoot = rootFromDirectoryGuard(parentGuard);
|
|
78
|
+
// Select the handoff before invoking the producer. Missing required native
|
|
79
|
+
// support must not leave producer output behind as a discovery side effect.
|
|
80
|
+
const native = getNativeBinding();
|
|
81
|
+
const cleanupErrors = [];
|
|
36
82
|
const { target, identity } = await createOwnedTempFile({
|
|
37
83
|
rootDir: targetRoot.rootReal,
|
|
38
84
|
prefix: "fs-safe-output",
|
|
39
|
-
fileName: path.basename(
|
|
85
|
+
fileName: path.basename(tempPath),
|
|
86
|
+
onCleanupError: (error) => cleanupErrors.push(error),
|
|
40
87
|
});
|
|
41
|
-
|
|
42
|
-
|
|
88
|
+
let cleanupStarted = false;
|
|
89
|
+
const cleanupWorkspace = async () => {
|
|
90
|
+
if (cleanupStarted)
|
|
91
|
+
return;
|
|
92
|
+
cleanupStarted = true;
|
|
93
|
+
try {
|
|
94
|
+
await target.cleanup();
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
cleanupErrors.push(error);
|
|
98
|
+
}
|
|
99
|
+
if (cleanupErrors.length > 0) {
|
|
100
|
+
throw aggregateErrors(cleanupErrors, "isolated producer workspace cleanup failed");
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
const assertWorkspace = () => {
|
|
43
104
|
assertDirectoryIdentitySync(target.dir, { ...identity, realPath: target.dir });
|
|
44
105
|
};
|
|
106
|
+
const assertCurrent = () => {
|
|
107
|
+
assertParent();
|
|
108
|
+
assertWorkspace();
|
|
109
|
+
};
|
|
45
110
|
try {
|
|
111
|
+
const producerPath = resolveCallbackTempPath(target.dir, path.basename(target.path));
|
|
46
112
|
assertCurrent();
|
|
47
|
-
const result = await
|
|
113
|
+
const result = await Reflect.apply(write, writeReceiver, [producerPath]);
|
|
48
114
|
assertCurrent();
|
|
49
|
-
|
|
50
|
-
assertBeforeMutation: assertCurrent
|
|
115
|
+
if (native) {
|
|
116
|
+
await targetRoot.move(path.relative(targetRoot.rootReal, producerPath), path.basename(tempPath), { assertBeforeMutation: assertCurrent });
|
|
117
|
+
return { cleanupWorkspace, result };
|
|
118
|
+
}
|
|
119
|
+
const handoff = await handoffPrivateProducerFile({
|
|
120
|
+
sourcePath: producerPath,
|
|
121
|
+
targetPath: tempPath,
|
|
122
|
+
assertSourceParent: assertWorkspace,
|
|
123
|
+
assertTargetParent: assertParent,
|
|
124
|
+
readWrite: syncTempFile,
|
|
51
125
|
});
|
|
52
|
-
return result;
|
|
126
|
+
return { cleanupWorkspace, handoff, result };
|
|
53
127
|
}
|
|
54
|
-
|
|
55
|
-
|
|
128
|
+
catch (error) {
|
|
129
|
+
try {
|
|
130
|
+
await cleanupWorkspace();
|
|
131
|
+
}
|
|
132
|
+
catch (cleanupError) {
|
|
133
|
+
throw new AggregateError([error, cleanupError], "isolated producer operation and workspace cleanup failed");
|
|
134
|
+
}
|
|
135
|
+
throw error;
|
|
56
136
|
}
|
|
57
137
|
}
|
|
58
138
|
// Callback paths are not owned until all three admission observations agree.
|
|
59
139
|
// Keep one descriptor and one exact identity through mode, sync, rename and cleanup.
|
|
60
140
|
// Read/write access is needed only when the caller requests file synchronization.
|
|
61
141
|
export async function writeCallbackSibling(params) {
|
|
62
|
-
const parent =
|
|
142
|
+
const parent = resolveCallbackTempDirectory(params.tempDir);
|
|
143
|
+
const tempPath = resolveCallbackTempPath(parent, params.tempName);
|
|
144
|
+
const write = params.write;
|
|
145
|
+
const producerIsolation = params.producerIsolation;
|
|
146
|
+
const resolveFinalPath = params.resolveFinalPath;
|
|
147
|
+
const mode = params.mode;
|
|
148
|
+
const ignoreModeError = params.ignoreModeError;
|
|
149
|
+
const maxBytes = params.maxBytes;
|
|
150
|
+
const syncTempFile = params.syncTempFile;
|
|
151
|
+
const syncParentDir = params.syncParentDir;
|
|
63
152
|
const guard = await createAsyncDirectoryGuard(parent, { bigint: true });
|
|
64
153
|
const assertParent = () => assertAsyncDirectoryGuard(guard);
|
|
65
154
|
let handle;
|
|
66
155
|
let identity;
|
|
67
156
|
let unregister;
|
|
157
|
+
let cleanupWorkspace;
|
|
68
158
|
let renamed = false;
|
|
69
159
|
let failure;
|
|
70
160
|
const inspectPath = (pathname, expected) => inspectStage(() => fsSync.lstatSync(pathname, { bigint: true }), expected);
|
|
@@ -72,70 +162,98 @@ export async function writeCallbackSibling(params) {
|
|
|
72
162
|
await assertParent();
|
|
73
163
|
const opened = await inspectStage(() => fsSync.fstatSync(handle.fd, { bigint: true }), identity);
|
|
74
164
|
const current = await inspectPath(pathname, opened);
|
|
75
|
-
if (
|
|
76
|
-
(opened.size >
|
|
77
|
-
throw new FsSafeError("too-large", `sibling temp exceeds maxBytes (${
|
|
165
|
+
if (maxBytes !== undefined &&
|
|
166
|
+
(opened.size > maxBytes || current.size > maxBytes)) {
|
|
167
|
+
throw new FsSafeError("too-large", `sibling temp exceeds maxBytes (${maxBytes})`);
|
|
78
168
|
}
|
|
79
169
|
};
|
|
80
170
|
try {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
171
|
+
let result;
|
|
172
|
+
if (producerIsolation === "private-directory") {
|
|
173
|
+
const isolated = await writeIsolatedProducer({
|
|
174
|
+
tempPath: tempPath,
|
|
175
|
+
write,
|
|
176
|
+
writeReceiver: params,
|
|
177
|
+
parentGuard: guard,
|
|
178
|
+
syncTempFile: syncTempFile,
|
|
85
179
|
assertParent: () => assertDirectoryIdentitySync(parent, {
|
|
86
180
|
dev: guard.stat.dev,
|
|
87
181
|
ino: guard.stat.ino,
|
|
88
182
|
realPath: guard.realPath,
|
|
89
183
|
}),
|
|
90
|
-
})
|
|
91
|
-
|
|
184
|
+
});
|
|
185
|
+
result = isolated.result;
|
|
186
|
+
cleanupWorkspace = isolated.cleanupWorkspace;
|
|
187
|
+
if (isolated.handoff) {
|
|
188
|
+
handle = isolated.handoff.handle;
|
|
189
|
+
identity = isolated.handoff.identity;
|
|
190
|
+
unregister = isolated.handoff.unregister;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
result = await Reflect.apply(write, params, [tempPath]);
|
|
195
|
+
}
|
|
92
196
|
await assertParent();
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const access = params.syncTempFile ? fsSync.constants.O_RDWR : fsSync.constants.O_RDONLY;
|
|
97
|
-
handle = await fs.open(params.tempPath, access | resolveReadOpenFlags());
|
|
197
|
+
if (handle) {
|
|
198
|
+
const opened = await inspectStage(() => fsSync.fstatSync(handle.fd, { bigint: true }), identity);
|
|
199
|
+
await inspectPath(tempPath, opened);
|
|
98
200
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
201
|
+
else {
|
|
202
|
+
const before = await inspectPath(tempPath);
|
|
203
|
+
try {
|
|
204
|
+
// No create/truncate flags; O_NONBLOCK also bounds a FIFO swap during open.
|
|
205
|
+
const access = syncTempFile ? fsSync.constants.O_RDWR : fsSync.constants.O_RDONLY;
|
|
206
|
+
handle = await fs.open(tempPath, access | resolveReadOpenFlags());
|
|
207
|
+
}
|
|
208
|
+
catch (error) {
|
|
209
|
+
if (error?.code === "ELOOP") {
|
|
210
|
+
throw new FsSafeError("symlink", "symlink sibling temp not allowed", { cause: error });
|
|
211
|
+
}
|
|
212
|
+
throw error;
|
|
102
213
|
}
|
|
103
|
-
|
|
214
|
+
const opened = await inspectStage(() => fsSync.fstatSync(handle.fd, { bigint: true }), before);
|
|
215
|
+
await inspectPath(tempPath, opened);
|
|
216
|
+
identity = opened;
|
|
217
|
+
unregister = registerTempPathForExit(tempPath, { identity, singleLinkFile: true });
|
|
104
218
|
}
|
|
105
|
-
const opened = await inspectStage(() => fsSync.fstatSync(handle.fd, { bigint: true }), before);
|
|
106
|
-
await inspectPath(params.tempPath, opened);
|
|
107
219
|
await assertParent();
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
220
|
+
if (cleanupWorkspace) {
|
|
221
|
+
const cleanup = cleanupWorkspace;
|
|
222
|
+
cleanupWorkspace = undefined;
|
|
223
|
+
await cleanup();
|
|
224
|
+
}
|
|
225
|
+
const rawFilePath = Reflect.apply(resolveFinalPath, params, [result]);
|
|
226
|
+
assertNoWindowsPathAlias(rawFilePath, "filesystem", "final path uses a Windows filesystem namespace alias");
|
|
227
|
+
const filePath = path.resolve(rawFilePath);
|
|
228
|
+
assertNoWindowsPathAlias(filePath, "filesystem", "final path uses a Windows filesystem namespace alias");
|
|
111
229
|
if (path.dirname(filePath) !== parent) {
|
|
112
230
|
throw new Error("Final path must be in the sibling temp directory.");
|
|
113
231
|
}
|
|
114
|
-
if (filePath ===
|
|
232
|
+
if (filePath === tempPath) {
|
|
115
233
|
throw new FsSafeError("invalid-path", "final path must differ from the sibling temp path");
|
|
116
234
|
}
|
|
117
235
|
await serializePathWrite(filePath, async () => {
|
|
118
|
-
await assertCurrent(
|
|
119
|
-
if (
|
|
236
|
+
await assertCurrent(tempPath);
|
|
237
|
+
if (mode !== undefined) {
|
|
120
238
|
try {
|
|
121
|
-
await handle.chmod(
|
|
239
|
+
await handle.chmod(mode);
|
|
122
240
|
}
|
|
123
241
|
catch (error) {
|
|
124
|
-
if (!
|
|
242
|
+
if (!ignoreModeError)
|
|
125
243
|
throw error;
|
|
126
244
|
}
|
|
127
245
|
}
|
|
128
|
-
if (
|
|
129
|
-
await assertCurrent(
|
|
246
|
+
if (syncTempFile) {
|
|
247
|
+
await assertCurrent(tempPath);
|
|
130
248
|
await syncFileBestEffort(handle);
|
|
131
249
|
}
|
|
132
|
-
await assertCurrent(
|
|
133
|
-
await fs.rename(
|
|
250
|
+
await assertCurrent(tempPath);
|
|
251
|
+
await fs.rename(tempPath, filePath);
|
|
134
252
|
// A later verification failure never authorizes rollback of the final name.
|
|
135
253
|
renamed = true;
|
|
136
254
|
unregister();
|
|
137
255
|
await assertCurrent(filePath);
|
|
138
|
-
if (
|
|
256
|
+
if (syncParentDir)
|
|
139
257
|
await syncDirectoryBestEffort(parent);
|
|
140
258
|
await assertCurrent(filePath);
|
|
141
259
|
});
|
|
@@ -146,13 +264,22 @@ export async function writeCallbackSibling(params) {
|
|
|
146
264
|
throw error;
|
|
147
265
|
}
|
|
148
266
|
finally {
|
|
267
|
+
const settlementErrors = [];
|
|
268
|
+
if (cleanupWorkspace) {
|
|
269
|
+
try {
|
|
270
|
+
await cleanupWorkspace();
|
|
271
|
+
}
|
|
272
|
+
catch (error) {
|
|
273
|
+
settlementErrors.push(error);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
149
276
|
try {
|
|
150
277
|
if (!renamed && identity) {
|
|
151
278
|
try {
|
|
152
279
|
await assertParent();
|
|
153
280
|
await inspectStage(() => fsSync.fstatSync(handle.fd, { bigint: true }), identity);
|
|
154
|
-
await inspectPath(
|
|
155
|
-
await fs.unlink(
|
|
281
|
+
await inspectPath(tempPath, identity);
|
|
282
|
+
await fs.unlink(tempPath);
|
|
156
283
|
unregister?.();
|
|
157
284
|
}
|
|
158
285
|
catch (error) {
|
|
@@ -168,9 +295,13 @@ export async function writeCallbackSibling(params) {
|
|
|
168
295
|
await handle?.close();
|
|
169
296
|
}
|
|
170
297
|
catch (error) {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
298
|
+
settlementErrors.push(error);
|
|
299
|
+
}
|
|
300
|
+
if (settlementErrors.length > 0) {
|
|
301
|
+
if (failure) {
|
|
302
|
+
throw new AggregateError([failure.error, ...settlementErrors], "sibling publication and settlement failed");
|
|
303
|
+
}
|
|
304
|
+
throw aggregateErrors(settlementErrors, "sibling publication settlement failed");
|
|
174
305
|
}
|
|
175
306
|
}
|
|
176
307
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sibling-temp.d.ts","sourceRoot":"","sources":["../src/sibling-temp.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"sibling-temp.d.ts","sourceRoot":"","sources":["../src/sibling-temp.ts"],"names":[],"mappings":"AAmBA,MAAM,MAAM,2BAA2B,CAAC,CAAC,IAAI;IAC3C,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IAC5C,oEAAoE;IACpE,iBAAiB,CAAC,EAAE,mBAAmB,CAAC;IACxC,gBAAgB,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,MAAM,CAAC;IACxC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uGAAuG;IACvG,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mEAAmE;IACnE,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,yEAAyE;IACzE,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,0BAA0B,CAAC,CAAC,IAAI;IAC1C,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,CAAC,CAAC;CACX,CAAC;AASF,wBAAsB,oBAAoB,CAAC,CAAC,EAC1C,OAAO,EAAE,2BAA2B,CAAC,CAAC,CAAC,GACtC,OAAO,CAAC,0BAA0B,CAAC,CAAC,CAAC,CAAC,CA0BxC;AAwBD,wBAAsB,uBAAuB,CAAC,MAAM,EAAE;IACpD,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,GAAG,OAAO,CAAC,IAAI,CAAC,CAsDhB"}
|
package/dist/sibling-temp.js
CHANGED
|
@@ -9,17 +9,22 @@ import { recursiveMkdirPath } from "./recursive-mkdir-path.js";
|
|
|
9
9
|
import { root } from "./root.js";
|
|
10
10
|
import { assertSafePathPrefix } from "./safe-path-segment.js";
|
|
11
11
|
import { resolveSecureTempRoot } from "./secure-temp-dir.js";
|
|
12
|
-
import { writeCallbackSibling } from "./sibling-staged-file.js";
|
|
12
|
+
import { resolveCallbackTempPath, writeCallbackSibling } from "./sibling-staged-file.js";
|
|
13
13
|
import { tempFile } from "./temp-target.js";
|
|
14
14
|
import { getFsSafeTestHooks } from "./test-hooks.js";
|
|
15
|
-
|
|
15
|
+
import { assertNoWindowsPathAlias, resolvePathPreservingWindowsRoot, } from "./windows-path-alias.js";
|
|
16
|
+
function buildTempName(tempPrefix) {
|
|
16
17
|
const safePrefix = assertSafePathPrefix(tempPrefix ?? ".fs-safe-stream", {
|
|
17
18
|
label: "sibling temp prefix",
|
|
18
19
|
});
|
|
19
|
-
return
|
|
20
|
+
return `${safePrefix}.${process.pid}.${randomUUID()}.tmp`;
|
|
20
21
|
}
|
|
21
22
|
export async function writeSiblingTempFile(options) {
|
|
22
|
-
const
|
|
23
|
+
const dirInput = options.dir;
|
|
24
|
+
assertNoWindowsPathAlias(dirInput, "filesystem", "sibling temp directory uses a Windows filesystem namespace alias");
|
|
25
|
+
const dir = resolvePathPreservingWindowsRoot(dirInput);
|
|
26
|
+
assertNoWindowsPathAlias(dir, "filesystem", "sibling temp directory uses a Windows filesystem namespace alias");
|
|
27
|
+
const producerIsolation = options.producerIsolation;
|
|
23
28
|
await fs.mkdir(recursiveMkdirPath(dir), { recursive: true, mode: options.dirMode ?? 0o700 });
|
|
24
29
|
if (options.chmodDir !== false) {
|
|
25
30
|
await applyDirectoryMode({
|
|
@@ -30,9 +35,10 @@ export async function writeSiblingTempFile(options) {
|
|
|
30
35
|
});
|
|
31
36
|
}
|
|
32
37
|
return await writeCallbackSibling({
|
|
33
|
-
|
|
38
|
+
tempDir: dir,
|
|
39
|
+
tempName: buildTempName(options.tempPrefix),
|
|
34
40
|
write: options.writeTemp,
|
|
35
|
-
producerIsolation
|
|
41
|
+
producerIsolation,
|
|
36
42
|
resolveFinalPath: options.resolveFinalPath,
|
|
37
43
|
mode: options.mode,
|
|
38
44
|
ignoreModeError: true,
|
|
@@ -40,7 +46,7 @@ export async function writeSiblingTempFile(options) {
|
|
|
40
46
|
syncParentDir: options.syncParentDir === true,
|
|
41
47
|
});
|
|
42
48
|
}
|
|
43
|
-
function
|
|
49
|
+
function buildSiblingTempName(params) {
|
|
44
50
|
const id = crypto.randomUUID();
|
|
45
51
|
const safePrefix = assertSafePathPrefix(params.tempPrefix, {
|
|
46
52
|
label: "sibling temp prefix",
|
|
@@ -52,17 +58,23 @@ function buildSiblingTempPath(params) {
|
|
|
52
58
|
fileName: sanitizeUntrustedFileName(path.basename(params.targetPath), params.fallbackFileName),
|
|
53
59
|
suffix,
|
|
54
60
|
});
|
|
55
|
-
return
|
|
61
|
+
return `${prefix}${safeTail}${suffix}`;
|
|
56
62
|
}
|
|
57
63
|
export async function writeViaSiblingTempPath(params) {
|
|
64
|
+
const rootDirInput = params.rootDir;
|
|
65
|
+
const targetPathInput = params.targetPath;
|
|
66
|
+
assertNoWindowsPathAlias(rootDirInput, "filesystem", "sibling temp root uses a Windows filesystem namespace alias");
|
|
67
|
+
assertNoWindowsPathAlias(targetPathInput, "filesystem", "sibling temp target uses a Windows filesystem namespace alias");
|
|
58
68
|
let rootDir;
|
|
59
69
|
try {
|
|
60
|
-
rootDir = realpathSync.native(
|
|
70
|
+
rootDir = realpathSync.native(resolvePathPreservingWindowsRoot(rootDirInput));
|
|
61
71
|
}
|
|
62
72
|
catch {
|
|
63
|
-
rootDir =
|
|
73
|
+
rootDir = resolvePathPreservingWindowsRoot(rootDirInput);
|
|
64
74
|
}
|
|
65
|
-
|
|
75
|
+
assertNoWindowsPathAlias(rootDir, "filesystem", "sibling temp root uses a Windows filesystem namespace alias");
|
|
76
|
+
const requestedTargetPath = path.resolve(targetPathInput);
|
|
77
|
+
assertNoWindowsPathAlias(requestedTargetPath, "filesystem", "sibling temp target uses a Windows filesystem namespace alias");
|
|
66
78
|
let targetPath;
|
|
67
79
|
try {
|
|
68
80
|
const realDir = realpathSync.native(path.dirname(requestedTargetPath));
|
|
@@ -71,6 +83,7 @@ export async function writeViaSiblingTempPath(params) {
|
|
|
71
83
|
catch {
|
|
72
84
|
targetPath = requestedTargetPath;
|
|
73
85
|
}
|
|
86
|
+
assertNoWindowsPathAlias(targetPath, "filesystem", "sibling temp target uses a Windows filesystem namespace alias");
|
|
74
87
|
const relativeTargetPath = path.relative(rootDir, targetPath);
|
|
75
88
|
if (!relativeTargetPath ||
|
|
76
89
|
relativeTargetPath === ".." ||
|
|
@@ -87,12 +100,14 @@ export async function writeViaSiblingTempPath(params) {
|
|
|
87
100
|
}),
|
|
88
101
|
prefix: "fs-safe-output",
|
|
89
102
|
});
|
|
103
|
+
assertNoWindowsPathAlias(workspace.dir, "filesystem", "sibling temp workspace uses a Windows filesystem namespace alias");
|
|
90
104
|
try {
|
|
91
|
-
const
|
|
105
|
+
const tempName = buildSiblingTempName({
|
|
92
106
|
targetPath: path.join(workspace.dir, path.basename(targetPath)),
|
|
93
107
|
fallbackFileName: params.fallbackFileName ?? "output.bin",
|
|
94
108
|
tempPrefix: params.tempPrefix ?? ".fs-safe-output-",
|
|
95
109
|
});
|
|
110
|
+
const tempPath = resolveCallbackTempPath(workspace.dir, tempName);
|
|
96
111
|
await getFsSafeTestHooks()?.beforeSiblingTempWrite?.(tempPath);
|
|
97
112
|
await params.writeTemp(tempPath);
|
|
98
113
|
await assertAsyncDirectoryGuard(rootGuard);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sidecar-lock-acquire.d.ts","sourceRoot":"","sources":["../src/sidecar-lock-acquire.ts"],"names":[],"mappings":"AAUA,OAAO,EAA6B,KAAK,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1F,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAW3C,OAAO,EAWL,KAAK,mBAAmB,EACzB,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,EAAE,yBAAyB,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"sidecar-lock-acquire.d.ts","sourceRoot":"","sources":["../src/sidecar-lock-acquire.ts"],"names":[],"mappings":"AAUA,OAAO,EAA6B,KAAK,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1F,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAW3C,OAAO,EAWL,KAAK,mBAAmB,EACzB,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,EAAE,yBAAyB,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAK5F,KAAK,iBAAiB,GAAG,IAAI,CAAC,gBAAgB,EAAE,IAAI,GAAG,OAAO,GAAG,MAAM,GAAG,WAAW,CAAC,CAAC;AAEvF,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,iBAAiB,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,cAAc,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,QAAQ,CAAC,EAAE,IAAI,CAAC;IAChB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IACxC,eAAe,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC;CAClC,CAAC;AAEF,KAAK,6BAA6B,GAAG;IACnC,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IACnC,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC3B,2BAA2B,IAAI,IAAI,CAAC;IACpC,iBAAiB,CAAC,oBAAoB,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,GAAG,iBAAiB,CAAC;IAC1F,eAAe,CACb,oBAAoB,EAAE,MAAM,EAC5B,IAAI,EAAE,eAAe,EACrB,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAC5B,OAAO,CAAC,OAAO,CAAC,CAAC;CACrB,CAAC;AAoCF,wBAAsB,kBAAkB,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/E,OAAO,EAAE,yBAAyB,CAAC,QAAQ,CAAC,EAC5C,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,iBAAiB,CAAC,CA2U5B"}
|
|
@@ -13,8 +13,16 @@ import { computeSidecarLockDelayMs, defaultSidecarLockShouldReclaim, isTransient
|
|
|
13
13
|
import { readSidecarLockRawSnapshot, parseSidecarLockSnapshot, relativeSidecarLockPath, releaseSidecarReclaimGuard, removeSidecarLockIfUnchanged, removeStaleSidecarLockIfAllowed, serializeSidecarLockPayload, sidecarLockSnapshotStillPresent, sidecarReclaimGuardExists, tryAcquireSidecarReclaimGuard, } from "./sidecar-lock-reclaim.js";
|
|
14
14
|
import { createSuppressedError } from "./suppressed-error.js";
|
|
15
15
|
import { sleep } from "./timing.js";
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
import { anchorWindowsDriveRelativePath, assertNoWindowsPathAlias } from "./windows-path-alias.js";
|
|
17
|
+
function isCwdIndependentAbsolutePath(filePath) {
|
|
18
|
+
if (!path.isAbsolute(filePath))
|
|
19
|
+
return false;
|
|
20
|
+
// A leading separator on Windows is rooted on the process's current drive.
|
|
21
|
+
// Drive-qualified, UNC, and namespace roots are longer and need no cwd state.
|
|
22
|
+
return process.platform !== "win32" || path.parse(filePath).root.length > 1;
|
|
23
|
+
}
|
|
24
|
+
async function resolveNormalizedTargetPath(resolved, lockRoot) {
|
|
25
|
+
assertNoWindowsPathAlias(resolved);
|
|
18
26
|
const dir = path.dirname(resolved);
|
|
19
27
|
if (lockRoot) {
|
|
20
28
|
// The target is an arbitration key, not necessarily inside the lock Root.
|
|
@@ -22,15 +30,23 @@ async function resolveNormalizedTargetPath(targetPath, lockRoot) {
|
|
|
22
30
|
await lockRoot.resolve(".");
|
|
23
31
|
const parent = await canonicalPathFromExistingAncestor(dir);
|
|
24
32
|
await lockRoot.resolve(".");
|
|
25
|
-
|
|
33
|
+
assertNoWindowsPathAlias(parent);
|
|
34
|
+
const normalized = path.join(parent, path.basename(resolved));
|
|
35
|
+
assertNoWindowsPathAlias(normalized);
|
|
36
|
+
return normalized;
|
|
26
37
|
}
|
|
27
38
|
await fs.mkdir(recursiveMkdirPath(dir), { recursive: true });
|
|
39
|
+
let parent;
|
|
28
40
|
try {
|
|
29
|
-
|
|
41
|
+
parent = realpathSync.native(dir);
|
|
30
42
|
}
|
|
31
43
|
catch {
|
|
32
44
|
return resolved;
|
|
33
45
|
}
|
|
46
|
+
assertNoWindowsPathAlias(parent);
|
|
47
|
+
const normalized = path.join(parent, path.basename(resolved));
|
|
48
|
+
assertNoWindowsPathAlias(normalized);
|
|
49
|
+
return normalized;
|
|
34
50
|
}
|
|
35
51
|
export async function acquireSidecarLock(options, context) {
|
|
36
52
|
const retry = options.retry ?? {};
|
|
@@ -38,9 +54,24 @@ export async function acquireSidecarLock(options, context) {
|
|
|
38
54
|
validateSidecarLockTimeoutMs(options.timeoutMs);
|
|
39
55
|
validateSidecarLockStaleMs(options.staleMs);
|
|
40
56
|
validateSidecarLockCompromiseCheckIntervalMs(options.compromiseCheckIntervalMs);
|
|
57
|
+
const targetPath = options.targetPath;
|
|
58
|
+
const explicitLockPath = options.lockPath;
|
|
59
|
+
const anchoredTargetPath = anchorWindowsDriveRelativePath(targetPath);
|
|
60
|
+
assertNoWindowsPathAlias(anchoredTargetPath);
|
|
61
|
+
const resolvedTargetPath = path.resolve(anchoredTargetPath);
|
|
62
|
+
let resolvedLockPath;
|
|
63
|
+
if (explicitLockPath !== undefined) {
|
|
64
|
+
const anchoredLockPath = anchorWindowsDriveRelativePath(explicitLockPath);
|
|
65
|
+
assertNoWindowsPathAlias(anchoredLockPath);
|
|
66
|
+
resolvedLockPath = isCwdIndependentAbsolutePath(explicitLockPath)
|
|
67
|
+
? explicitLockPath
|
|
68
|
+
: path.resolve(anchoredLockPath);
|
|
69
|
+
}
|
|
41
70
|
context.ensureExitCleanupRegistered();
|
|
42
|
-
const
|
|
43
|
-
const
|
|
71
|
+
const lockRoot = options.lockRoot;
|
|
72
|
+
const normalizedTargetPath = await resolveNormalizedTargetPath(resolvedTargetPath, lockRoot);
|
|
73
|
+
const lockPath = resolvedLockPath ?? `${normalizedTargetPath}.lock`;
|
|
74
|
+
assertNoWindowsPathAlias(lockPath);
|
|
44
75
|
let held = context.held.get(normalizedTargetPath);
|
|
45
76
|
if (held &&
|
|
46
77
|
options.reentrantOwner !== undefined &&
|
|
@@ -58,7 +89,7 @@ export async function acquireSidecarLock(options, context) {
|
|
|
58
89
|
options.reentrantOwner !== undefined &&
|
|
59
90
|
held.reentrantOwner !== undefined &&
|
|
60
91
|
options.reentrantOwner === held.reentrantOwner) {
|
|
61
|
-
held.refCount
|
|
92
|
+
held.refCount = (held.refCount ?? 1) + 1;
|
|
62
93
|
// Retention is monotonic: any same-owner request to keep the sidecar on
|
|
63
94
|
// exit upgrades the held lock; a later default acquisition never revokes it.
|
|
64
95
|
if (options.retainOnExit === true) {
|
|
@@ -118,8 +149,7 @@ export async function acquireSidecarLock(options, context) {
|
|
|
118
149
|
const payload = await options.payload();
|
|
119
150
|
const { raw, ownershipToken } = serializeSidecarLockPayload(payload);
|
|
120
151
|
try {
|
|
121
|
-
if (
|
|
122
|
-
const lockRoot = options.lockRoot;
|
|
152
|
+
if (lockRoot) {
|
|
123
153
|
const relativeLockPath = relativeSidecarLockPath(lockRoot, lockPath);
|
|
124
154
|
const observation = fileObservation();
|
|
125
155
|
try {
|
|
@@ -180,7 +210,7 @@ export async function acquireSidecarLock(options, context) {
|
|
|
180
210
|
snapshot,
|
|
181
211
|
acquiredAt: Date.now(),
|
|
182
212
|
metadata: options.metadata ?? {},
|
|
183
|
-
lockRoot
|
|
213
|
+
lockRoot,
|
|
184
214
|
retainOnExit: options.retainOnExit,
|
|
185
215
|
parsePayload: options.parsePayload,
|
|
186
216
|
};
|
|
@@ -239,13 +269,13 @@ export async function acquireSidecarLock(options, context) {
|
|
|
239
269
|
// Root-created records retain the creator's byte/token receipt;
|
|
240
270
|
// partial direct writes use the exclusive descriptor's identity.
|
|
241
271
|
await removeSidecarLockIfUnchanged(lockPath, failedSnapshot, {
|
|
242
|
-
lockRoot
|
|
272
|
+
lockRoot,
|
|
243
273
|
parsePayload: options.parsePayload,
|
|
244
274
|
});
|
|
245
275
|
}
|
|
246
276
|
else if (createdSnapshot) {
|
|
247
277
|
await removeSidecarLockIfUnchanged(lockPath, createdSnapshot, {
|
|
248
|
-
lockRoot
|
|
278
|
+
lockRoot,
|
|
249
279
|
parsePayload: options.parsePayload,
|
|
250
280
|
});
|
|
251
281
|
}
|
|
@@ -271,7 +301,7 @@ export async function acquireSidecarLock(options, context) {
|
|
|
271
301
|
let lockFileOpenDenied = false;
|
|
272
302
|
try {
|
|
273
303
|
rawSnapshot = await readSidecarLockRawSnapshot(lockPath, {
|
|
274
|
-
lockRoot
|
|
304
|
+
lockRoot,
|
|
275
305
|
rejectNonFile: true,
|
|
276
306
|
discardObservation: "changed",
|
|
277
307
|
onOpenFailure: (error) => { lockFileOpenDenied = isTransientLockFileDenial(error, lockPath); },
|
|
@@ -302,7 +332,7 @@ export async function acquireSidecarLock(options, context) {
|
|
|
302
332
|
heldByThisProcess: context.held.has(normalizedTargetPath),
|
|
303
333
|
})) {
|
|
304
334
|
if (!(await sidecarLockSnapshotStillPresent(lockPath, snapshot, {
|
|
305
|
-
lockRoot
|
|
335
|
+
lockRoot,
|
|
306
336
|
parsePayload: options.parsePayload,
|
|
307
337
|
}))) {
|
|
308
338
|
await waitForRetry();
|
|
@@ -320,7 +350,7 @@ export async function acquireSidecarLock(options, context) {
|
|
|
320
350
|
normalizedTargetPath,
|
|
321
351
|
snapshot,
|
|
322
352
|
shouldRemoveStaleLock: options.shouldRemoveStaleLock,
|
|
323
|
-
lockRoot
|
|
353
|
+
lockRoot,
|
|
324
354
|
parsePayload: options.parsePayload,
|
|
325
355
|
});
|
|
326
356
|
if (removal === "removed" || removal === "changed") {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sidecar-lock-root.d.ts","sourceRoot":"","sources":["../src/sidecar-lock-root.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAGvD,wBAAsB,eAAe,CACnC,QAAQ,EAAE,IAAI,EACd,QAAQ,EAAE,MAAM,EAChB,kBAAkB,CAAC,EAAE,UAAU,GAAG,SAAS,EAC3C,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,GACvC,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"sidecar-lock-root.d.ts","sourceRoot":"","sources":["../src/sidecar-lock-root.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAGvD,wBAAsB,eAAe,CACnC,QAAQ,EAAE,IAAI,EACd,QAAQ,EAAE,MAAM,EAChB,kBAAkB,CAAC,EAAE,UAAU,GAAG,SAAS,EAC3C,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,GACvC,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAyD5B"}
|
|
@@ -53,13 +53,15 @@ export async function openSidecarRoot(lockRoot, relative, discardObservation, on
|
|
|
53
53
|
if (!discardObservation || (!missing && (!completeParents || !discardable)))
|
|
54
54
|
throw error;
|
|
55
55
|
try {
|
|
56
|
+
const probe = fileObservation();
|
|
56
57
|
try {
|
|
57
|
-
const current = await lockRoot.stat(relative);
|
|
58
|
+
const current = await probe.run(() => lockRoot.stat(relative));
|
|
58
59
|
if (!current.isFile || current.isSymbolicLink || current.nlink !== 1)
|
|
59
60
|
throw error;
|
|
60
61
|
}
|
|
61
62
|
catch (probeError) {
|
|
62
|
-
if (!(probeError instanceof FsSafeError && probeError.code === "not-found")
|
|
63
|
+
if (!(probeError instanceof FsSafeError && probeError.code === "not-found") &&
|
|
64
|
+
!probe.has(probeError, `stat-leaf-missing:${expectedRealPath}`))
|
|
63
65
|
throw probeError;
|
|
64
66
|
}
|
|
65
67
|
for (const { dir, stat } of parents)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sidecar-lock.d.ts","sourceRoot":"","sources":["../src/sidecar-lock.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EACV,yBAAyB,EACzB,iBAAiB,EACjB,oBAAoB,EACpB,sBAAsB,EACvB,MAAM,yBAAyB,CAAC;AACjC,YAAY,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AAC1E,YAAY,EACV,yBAAyB,EACzB,0BAA0B,EAC1B,iBAAiB,EACjB,oBAAoB,EACpB,uBAAuB,EACvB,wBAAwB,EACxB,sBAAsB,GACvB,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"sidecar-lock.d.ts","sourceRoot":"","sources":["../src/sidecar-lock.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EACV,yBAAyB,EACzB,iBAAiB,EACjB,oBAAoB,EACpB,sBAAsB,EACvB,MAAM,yBAAyB,CAAC;AACjC,YAAY,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AAC1E,YAAY,EACV,yBAAyB,EACzB,0BAA0B,EAC1B,iBAAiB,EACjB,oBAAoB,EACpB,uBAAuB,EACvB,wBAAwB,EACxB,sBAAsB,GACvB,MAAM,yBAAyB,CAAC;AAmIjC,0FAA0F;AAC1F,wBAAgB,uBAAuB,IAAI,OAAO,CAMjD;AAyFD,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM;cAU3B,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,WACpD,yBAAyB,CAAC,QAAQ,CAAC,KAC3C,OAAO,CAAC,iBAAiB,CAAC;eAkBL,CAAC,EAAE,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,WACxD,yBAAyB,CAAC,QAAQ,CAAC,MACxC,MAAM,OAAO,CAAC,CAAC,CAAC,KACnB,OAAO,CAAC,CAAC,CAAC;iBAqBW,OAAO,CAAC,IAAI,CAAC;iBAQnB,IAAI;uBAIE,oBAAoB,EAAE;EAW/C;AAED,wBAAsB,eAAe,CAAC,CAAC,EAAE,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/E,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,sBAAsB,CAAC,QAAQ,CAAC,EACzC,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACnB,OAAO,CAAC,CAAC,CAAC,CAMZ"}
|
package/dist/sidecar-lock.js
CHANGED
|
@@ -37,9 +37,6 @@ function resolveManagerState(key) {
|
|
|
37
37
|
// Backfill state created by fs-safe versions that predate reclaim guards.
|
|
38
38
|
state.reclaimCleanupRegistered ??= false;
|
|
39
39
|
state.reclaimGuards ??= new Set();
|
|
40
|
-
for (const held of state.held.values()) {
|
|
41
|
-
held.refCount ??= 1;
|
|
42
|
-
}
|
|
43
40
|
}
|
|
44
41
|
return state;
|
|
45
42
|
}
|
|
@@ -163,6 +160,8 @@ async function releaseHeldLock(state, normalizedTargetPath, held, options = {})
|
|
|
163
160
|
await held.releasePromise;
|
|
164
161
|
return true;
|
|
165
162
|
}
|
|
163
|
+
// Older package copies can add holders after this manager was constructed.
|
|
164
|
+
held.refCount ??= 1;
|
|
166
165
|
if (options.force) {
|
|
167
166
|
held.refCount = 0;
|
|
168
167
|
}
|