@openclaw/fs-safe 0.12.0 → 0.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +73 -0
- package/README.md +12 -7
- package/dist/absolute-path.d.ts.map +1 -1
- package/dist/absolute-path.js +32 -11
- package/dist/advanced.d.ts +2 -0
- package/dist/advanced.d.ts.map +1 -1
- package/dist/advanced.js +2 -0
- 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 +18 -13
- 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 +35 -14
- 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/copy-file-input.d.ts +1 -1
- package/dist/copy-file-input.d.ts.map +1 -1
- package/dist/copy-file-input.js +2 -0
- 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/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 +3 -0
- 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-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.map +1 -1
- package/dist/filename.js +65 -20
- package/dist/fs.d.ts.map +1 -1
- package/dist/fs.js +3 -2
- package/dist/guarded-mkdir.d.ts +8 -0
- package/dist/guarded-mkdir.d.ts.map +1 -1
- package/dist/guarded-mkdir.js +176 -27
- package/dist/guest.d.ts.map +1 -1
- package/dist/guest.js +4 -1
- package/dist/home-dir.d.ts.map +1 -1
- package/dist/home-dir.js +73 -10
- package/dist/install-path.d.ts.map +1 -1
- package/dist/install-path.js +54 -19
- package/dist/json-durable-queue-ownership.d.ts +6 -0
- package/dist/json-durable-queue-ownership.d.ts.map +1 -1
- package/dist/json-durable-queue-ownership.js +89 -42
- 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.map +1 -1
- package/dist/json-durable-queue-read.js +2 -0
- package/dist/json-durable-queue.d.ts.map +1 -1
- package/dist/json-durable-queue.js +50 -58
- 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 +17 -0
- package/dist/native-binding.d.ts.map +1 -1
- package/dist/native-binding.js +8 -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-operations.d.ts.map +1 -1
- package/dist/native-operations.js +6 -4
- package/dist/native-parent-admission.d.ts +32 -0
- package/dist/native-parent-admission.d.ts.map +1 -0
- package/dist/native-parent-admission.js +127 -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 +8 -9
- package/dist/native-pinned-write.d.ts.map +1 -1
- package/dist/native-pinned-write.js +314 -42
- package/dist/native-staged-file.d.ts +4 -4
- package/dist/native-staged-file.d.ts.map +1 -1
- package/dist/native-staged-file.js +20 -10
- package/dist/native.d.ts +1 -1
- package/dist/native.d.ts.map +1 -1
- package/dist/native.js +7 -2
- 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.map +1 -1
- package/dist/path-scope-lexical.js +18 -8
- 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 +16 -5
- 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 +151 -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 +38 -32
- 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 +4 -0
- package/dist/root-boundary.d.ts.map +1 -1
- package/dist/root-boundary.js +6 -1
- package/dist/root-context.d.ts +12 -1
- package/dist/root-context.d.ts.map +1 -1
- package/dist/root-context.js +57 -11
- 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 +180 -39
- 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 +289 -317
- 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-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.map +1 -1
- package/dist/root-path-existing.js +53 -20
- 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 +11 -0
- package/dist/root-path.d.ts.map +1 -1
- package/dist/root-path.js +215 -53
- 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 -25
- package/dist/root-paths.d.ts.map +1 -1
- package/dist/root-paths.js +77 -154
- 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-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-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 +6 -4
- 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.d.ts.map +1 -1
- package/dist/secure-file.js +92 -7
- 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 -2
- package/dist/sibling-staged-file.d.ts.map +1 -1
- package/dist/sibling-staged-file.js +179 -55
- package/dist/sibling-temp.d.ts.map +1 -1
- package/dist/sibling-temp.js +27 -13
- package/dist/sidecar-lock-acquire.d.ts.map +1 -1
- package/dist/sidecar-lock-acquire.js +44 -14
- package/dist/sidecar-lock-root.d.ts.map +1 -1
- package/dist/sidecar-lock-root.js +4 -2
- 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 +130 -3
- 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 +9 -6
- 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 +25 -0
- package/docs/archive.md +23 -2
- package/docs/atomic.md +27 -3
- package/docs/copy.md +3 -0
- package/docs/durability.md +14 -0
- package/docs/errors.md +14 -6
- package/docs/file-store.md +24 -3
- package/docs/filename.md +14 -7
- package/docs/install-path.md +2 -2
- package/docs/install.md +8 -7
- 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 +41 -11
- package/docs/native.md +32 -8
- 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 +11 -0
- package/docs/private-file-store.md +14 -0
- package/docs/public-api.md +9 -0
- package/docs/quickstart.md +1 -1
- package/docs/reading.md +8 -1
- package/docs/root.md +18 -3
- package/docs/secret-file.md +3 -0
- package/docs/secure-file.md +6 -2
- package/docs/security-model.md +75 -8
- package/docs/sidecar-lock.md +28 -1
- package/docs/store.md +5 -1
- package/docs/temp.md +260 -36
- package/docs/test-hooks.md +14 -0
- package/docs/writing.md +38 -8
- package/package.json +10 -10
|
@@ -4,22 +4,21 @@ import fsSync, {} from "node:fs";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { FsSafeError } from "./errors.js";
|
|
6
6
|
import { nativeOpenFlags, removeNativeCreatedFileIfStillPinned, writeNativeInput, } from "./native-operations.js";
|
|
7
|
+
import { captureNativeFdClose } from "./native-binding.js";
|
|
7
8
|
import { inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
8
9
|
import { assertFinalSymlinkRejected } from "./root-symlink-policy.js";
|
|
9
|
-
|
|
10
|
-
return left.dev === right.dev && left.ino === right.ino;
|
|
11
|
-
}
|
|
12
|
-
function closeWriteFd(fd) {
|
|
10
|
+
function closeWriteFd(closeFd, fd) {
|
|
13
11
|
if (fd === undefined)
|
|
14
12
|
return;
|
|
15
13
|
try {
|
|
16
|
-
|
|
14
|
+
closeFd(fd);
|
|
17
15
|
}
|
|
18
16
|
catch (error) {
|
|
19
17
|
return error;
|
|
20
18
|
}
|
|
21
19
|
}
|
|
22
20
|
export async function runPinnedWriteWindows(binding, params, root, parentFd, parentGuard) {
|
|
21
|
+
const closeFd = captureNativeFdClose(binding);
|
|
23
22
|
const parentPath = parentGuard.realPath;
|
|
24
23
|
let tempFd;
|
|
25
24
|
let targetFd;
|
|
@@ -67,7 +66,7 @@ export async function runPinnedWriteWindows(binding, params, root, parentFd, par
|
|
|
67
66
|
syncFileBestEffortSync(targetFd);
|
|
68
67
|
}
|
|
69
68
|
catch (error) {
|
|
70
|
-
closeWriteFd(targetFd);
|
|
69
|
+
closeWriteFd(closeFd, targetFd);
|
|
71
70
|
targetFd = undefined;
|
|
72
71
|
// Staged copies and streamed creates preserve publication for caller recovery.
|
|
73
72
|
if (params.input.kind === "buffer" ||
|
|
@@ -89,8 +88,8 @@ export async function runPinnedWriteWindows(binding, params, root, parentFd, par
|
|
|
89
88
|
return { dev: targetIdentity.dev, ino: targetIdentity.ino };
|
|
90
89
|
}
|
|
91
90
|
finally {
|
|
92
|
-
const targetCloseError = closeWriteFd(targetFd);
|
|
93
|
-
const tempCloseError = closeWriteFd(tempFd);
|
|
91
|
+
const targetCloseError = closeWriteFd(closeFd, targetFd);
|
|
92
|
+
const tempCloseError = closeWriteFd(closeFd, tempFd);
|
|
94
93
|
if (!renamed) {
|
|
95
94
|
removeNativeCreatedFileIfStillPinned({
|
|
96
95
|
parentPath,
|
|
@@ -99,7 +98,7 @@ export async function runPinnedWriteWindows(binding, params, root, parentFd, par
|
|
|
99
98
|
created: tempIdentity,
|
|
100
99
|
});
|
|
101
100
|
}
|
|
102
|
-
const parentCloseError = closeWriteFd(parentFd);
|
|
101
|
+
const parentCloseError = closeWriteFd(closeFd, parentFd);
|
|
103
102
|
await root.close().catch(() => undefined);
|
|
104
103
|
const closeError = targetCloseError ?? tempCloseError ?? parentCloseError;
|
|
105
104
|
if (completed && closeError !== undefined)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"native-pinned-write.d.ts","sourceRoot":"","sources":["../src/native-pinned-write.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"native-pinned-write.d.ts","sourceRoot":"","sources":["../src/native-pinned-write.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAI3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD,OAAO,KAAK,EAIV,iBAAiB,EAClB,MAAM,mBAAmB,CAAC;AAqV3B,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAsGvH"}
|
|
@@ -50,25 +50,295 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
|
|
|
50
50
|
var e = new Error(message);
|
|
51
51
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
52
52
|
});
|
|
53
|
-
import fsSync from "node:fs";
|
|
54
|
-
import fs from "node:fs/promises";
|
|
53
|
+
import fsSync, {} from "node:fs";
|
|
55
54
|
import path from "node:path";
|
|
56
55
|
import { inspectDirectoryIdentity } from "./directory-guard.js";
|
|
57
56
|
import { FsSafeError } from "./errors.js";
|
|
58
|
-
import { runPinnedWriteWindows
|
|
57
|
+
import { runPinnedWriteWindows } from "./native-pinned-write-windows.js";
|
|
58
|
+
import { openNativeParentAdmission, openNativeRootAdmission } from "./native-parent-admission.js";
|
|
59
59
|
import { assertNativeStaging, writeNativeStage } from "./native-staged-file.js";
|
|
60
|
-
import {
|
|
60
|
+
import { captureNativeFdClose } from "./native-binding.js";
|
|
61
|
+
import { assertPolicyStagedDirectoryCurrent, assertStagedDirectoryCurrent, describePolicyStagedDirectory, describeStagedDirectory, refreshPolicyStagedDirectoryObservation, } from "./staged-directory.js";
|
|
61
62
|
import { inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
62
63
|
import { realpathSync } from "./realpath.js";
|
|
64
|
+
import { isNotFoundPathError, isSymlinkOpenError } from "./path.js";
|
|
65
|
+
import { checkedMutationDirectory, } from "./pinned-mutation-observation.js";
|
|
66
|
+
import { createPathSegmentRoute, joinPathSegmentRoute } from "./path-segment-route.js";
|
|
67
|
+
function relativeParentSegments(relativeParentPath) {
|
|
68
|
+
return relativeParentPath.split("/").filter(Boolean);
|
|
69
|
+
}
|
|
70
|
+
function prospectiveTargetPath(parentPath, parentRoute, offset, basename) {
|
|
71
|
+
return joinPathSegmentRoute(parentPath, parentRoute, offset, basename);
|
|
72
|
+
}
|
|
73
|
+
function sameAbsolutePath(left, right) {
|
|
74
|
+
return path.relative(path.resolve(left), path.resolve(right)) === "";
|
|
75
|
+
}
|
|
76
|
+
function assertDirectChildBasename(basename) {
|
|
77
|
+
if (!basename || basename === "." || basename === ".." || basename.includes("/") ||
|
|
78
|
+
basename.includes("\0") || (process.platform === "win32" && basename.includes("\\"))) {
|
|
79
|
+
throw new FsSafeError("invalid-path", "native parent creation requires one direct-child basename");
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function mkdirPolicyChild(binding, parentFd, basename, mode) {
|
|
83
|
+
assertDirectChildBasename(basename);
|
|
84
|
+
const mkdirChild = binding.mkdirChildBeneath;
|
|
85
|
+
if (typeof mkdirChild === "function") {
|
|
86
|
+
const created = mkdirChild.call(binding, parentFd, basename, mode);
|
|
87
|
+
if (typeof created === "boolean")
|
|
88
|
+
return created;
|
|
89
|
+
}
|
|
90
|
+
// Old or malformed optional helpers still execute through the established
|
|
91
|
+
// recursive primitive, but cannot provide exclusive-creation provenance.
|
|
92
|
+
binding.mkdirBeneath(parentFd, basename, mode);
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
async function authorizePinnedMutation(params, request) {
|
|
96
|
+
return await params.mutationAdmission?.authorize(Object.freeze(request));
|
|
97
|
+
}
|
|
98
|
+
function normalizePolicyParentOpenError(error, params) {
|
|
99
|
+
if (!isSymlinkOpenError(error))
|
|
100
|
+
return error;
|
|
101
|
+
return params.mutationAdmission?.rejectParentSymlinks
|
|
102
|
+
? new FsSafeError("symlink", "symlink path component not allowed", {
|
|
103
|
+
cause: error instanceof Error ? error : undefined,
|
|
104
|
+
})
|
|
105
|
+
: new FsSafeError("path-mismatch", "native write parent changed during policy admission", {
|
|
106
|
+
cause: error instanceof Error ? error : undefined,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
async function describePosixParent(parentFd, pathname) {
|
|
110
|
+
const parentPath = realpathSync.native(pathname);
|
|
111
|
+
const directory = describeStagedDirectory(parentFd, parentPath);
|
|
112
|
+
const parentPathStat = await inspectDirectoryIdentity(parentPath, inspectFileIdentitySync(() => fsSync.fstatSync(parentFd, { bigint: true })));
|
|
113
|
+
return {
|
|
114
|
+
parentFd,
|
|
115
|
+
parentPath,
|
|
116
|
+
directory,
|
|
117
|
+
parentPathStat,
|
|
118
|
+
observation: checkedMutationDirectory(parentPath, directory.realPath, parentPathStat),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
function describePolicyPosixParent(parentFd, pathname) {
|
|
122
|
+
const policyDirectory = describePolicyStagedDirectory(parentFd, pathname);
|
|
123
|
+
return {
|
|
124
|
+
parentFd,
|
|
125
|
+
parentPath: policyDirectory.directory.realPath,
|
|
126
|
+
directory: policyDirectory.directory,
|
|
127
|
+
parentPathStat: policyDirectory.stat,
|
|
128
|
+
observation: policyDirectory.observation,
|
|
129
|
+
policyDirectory,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function policyParentCaptureDeopt(error) {
|
|
133
|
+
return error instanceof FsSafeError &&
|
|
134
|
+
(error.code === "path-mismatch" || error.code === "not-file");
|
|
135
|
+
}
|
|
136
|
+
function tryDescribePolicyPosixParent(parentFd, pathname) {
|
|
137
|
+
try {
|
|
138
|
+
return describePolicyPosixParent(parentFd, pathname);
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
if (!policyParentCaptureDeopt(error))
|
|
142
|
+
throw error;
|
|
143
|
+
return undefined;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
function assertPosixParentCurrent(parent) {
|
|
147
|
+
return parent.policyDirectory
|
|
148
|
+
? assertPolicyStagedDirectoryCurrent(parent.policyDirectory)
|
|
149
|
+
: assertStagedDirectoryCurrent(parent.directory);
|
|
150
|
+
}
|
|
151
|
+
async function capturePolicyAwarePosixParent(binding, params, rootFd, directoryFlags) {
|
|
152
|
+
const closeFd = captureNativeFdClose(binding);
|
|
153
|
+
const segments = relativeParentSegments(params.relativeParentPath);
|
|
154
|
+
const parentSpelling = segments.length
|
|
155
|
+
? path.join(params.rootPath, ...segments)
|
|
156
|
+
: params.rootPath;
|
|
157
|
+
const segmentRoute = createPathSegmentRoute(segments);
|
|
158
|
+
const initialTarget = prospectiveTargetPath(params.rootPath, segmentRoute, 0, params.basename);
|
|
159
|
+
let retainedTargetPath = params.mutationAdmission?.beginParentWalk?.();
|
|
160
|
+
if (retainedTargetPath && !sameAbsolutePath(retainedTargetPath, initialTarget)) {
|
|
161
|
+
retainedTargetPath = undefined;
|
|
162
|
+
}
|
|
163
|
+
// Preserve the one-open hot path when the complete parent still exists.
|
|
164
|
+
// Policy is attached only after the opened descriptor is associated with its
|
|
165
|
+
// current canonical pathname, so a contained redirect cannot retain the old
|
|
166
|
+
// preflight authorization.
|
|
167
|
+
let completeParentFd;
|
|
168
|
+
try {
|
|
169
|
+
completeParentFd = binding.openBeneath(rootFd, params.relativeParentPath, directoryFlags).fd;
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
if (error?.code !== "ENOENT" || !params.mkdir)
|
|
173
|
+
throw error;
|
|
174
|
+
}
|
|
175
|
+
if (completeParentFd !== undefined) {
|
|
176
|
+
const parentFd = completeParentFd;
|
|
177
|
+
try {
|
|
178
|
+
let admitted = retainedTargetPath
|
|
179
|
+
? tryDescribePolicyPosixParent(parentFd, parentSpelling)
|
|
180
|
+
: undefined;
|
|
181
|
+
if (!admitted) {
|
|
182
|
+
retainedTargetPath = undefined;
|
|
183
|
+
admitted = await describePosixParent(parentFd, parentSpelling);
|
|
184
|
+
}
|
|
185
|
+
const targetPath = path.join(admitted.parentPath, params.basename);
|
|
186
|
+
await authorizePinnedMutation(params, {
|
|
187
|
+
targetPath,
|
|
188
|
+
mutationPath: targetPath,
|
|
189
|
+
phase: "parent",
|
|
190
|
+
});
|
|
191
|
+
assertPosixParentCurrent(admitted);
|
|
192
|
+
return admitted;
|
|
193
|
+
}
|
|
194
|
+
catch (error) {
|
|
195
|
+
closeFd(parentFd);
|
|
196
|
+
throw error;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
// The canonical preflight spelling has no intentional symlinks in its
|
|
200
|
+
// existing prefix. Walk missing-parent cases one direct child at a time so
|
|
201
|
+
// every existing/opened object is authorized and every mkdir is authorized
|
|
202
|
+
// before dispatch. O_NOFOLLOW makes a concurrently introduced link fail
|
|
203
|
+
// before the next component can be created through it.
|
|
204
|
+
const secureDirectoryFlags = directoryFlags | (fsSync.constants.O_NOFOLLOW ?? 0);
|
|
205
|
+
let currentFd = rootFd;
|
|
206
|
+
let currentOwnedFd;
|
|
207
|
+
let currentPath = params.rootPath;
|
|
208
|
+
let current = retainedTargetPath
|
|
209
|
+
? tryDescribePolicyPosixParent(rootFd, currentPath)
|
|
210
|
+
: undefined;
|
|
211
|
+
if (!current) {
|
|
212
|
+
retainedTargetPath = undefined;
|
|
213
|
+
current = await describePosixParent(rootFd, currentPath);
|
|
214
|
+
}
|
|
215
|
+
try {
|
|
216
|
+
await authorizePinnedMutation(params, {
|
|
217
|
+
targetPath: retainedTargetPath ?? initialTarget,
|
|
218
|
+
mutationPath: retainedTargetPath ?? initialTarget,
|
|
219
|
+
phase: "parent",
|
|
220
|
+
});
|
|
221
|
+
assertPosixParentCurrent(current);
|
|
222
|
+
for (let index = 0; index < segments.length; index += 1) {
|
|
223
|
+
const segment = segments[index];
|
|
224
|
+
const childPath = path.join(currentPath, segment);
|
|
225
|
+
let childFd;
|
|
226
|
+
let createdByMkdir = false;
|
|
227
|
+
let createReceipt;
|
|
228
|
+
try {
|
|
229
|
+
childFd = binding.openBeneath(currentFd, segment, secureDirectoryFlags).fd;
|
|
230
|
+
}
|
|
231
|
+
catch (openError) {
|
|
232
|
+
if (!isNotFoundPathError(openError)) {
|
|
233
|
+
throw normalizePolicyParentOpenError(openError, params);
|
|
234
|
+
}
|
|
235
|
+
const targetPath = retainedTargetPath ?? prospectiveTargetPath(currentPath, segmentRoute, index, params.basename);
|
|
236
|
+
const request = Object.freeze({
|
|
237
|
+
targetPath,
|
|
238
|
+
mutationPath: childPath,
|
|
239
|
+
phase: "parent-create",
|
|
240
|
+
});
|
|
241
|
+
createReceipt = params.mutationAdmission?.tryAuthorizeAtParent?.(request, current.observation);
|
|
242
|
+
if (!createReceipt) {
|
|
243
|
+
createReceipt = await authorizePinnedMutation(params, request);
|
|
244
|
+
assertPosixParentCurrent(current);
|
|
245
|
+
}
|
|
246
|
+
params.assertBeforeMutation?.();
|
|
247
|
+
// Do not carry pathname freshness across the live authority callback.
|
|
248
|
+
assertPosixParentCurrent(current);
|
|
249
|
+
createdByMkdir = mkdirPolicyChild(binding, currentFd, segment, 0o777);
|
|
250
|
+
try {
|
|
251
|
+
childFd = binding.openBeneath(currentFd, segment, secureDirectoryFlags).fd;
|
|
252
|
+
}
|
|
253
|
+
catch (createdOpenError) {
|
|
254
|
+
throw normalizePolicyParentOpenError(createdOpenError, params);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
let child;
|
|
258
|
+
try {
|
|
259
|
+
const acceleratedChild = retainedTargetPath
|
|
260
|
+
? tryDescribePolicyPosixParent(childFd, childPath)
|
|
261
|
+
: undefined;
|
|
262
|
+
if (acceleratedChild) {
|
|
263
|
+
child = acceleratedChild;
|
|
264
|
+
}
|
|
265
|
+
else {
|
|
266
|
+
retainedTargetPath = undefined;
|
|
267
|
+
child = await describePosixParent(childFd, childPath);
|
|
268
|
+
}
|
|
269
|
+
if (!sameAbsolutePath(child.parentPath, childPath))
|
|
270
|
+
retainedTargetPath = undefined;
|
|
271
|
+
let childAuthorization;
|
|
272
|
+
if (createdByMkdir && createReceipt && params.mutationAdmission?.advanceCreatedDirectory) {
|
|
273
|
+
let evidence;
|
|
274
|
+
try {
|
|
275
|
+
const parent = current.policyDirectory
|
|
276
|
+
? refreshPolicyStagedDirectoryObservation(current.policyDirectory)
|
|
277
|
+
: checkedMutationDirectory(currentPath, current.directory.realPath, assertStagedDirectoryCurrent(current.directory));
|
|
278
|
+
evidence = Object.freeze({
|
|
279
|
+
admission: createReceipt,
|
|
280
|
+
parent,
|
|
281
|
+
child: child.observation,
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
catch {
|
|
285
|
+
// Leave failed evidence to the full ordered admission below.
|
|
286
|
+
}
|
|
287
|
+
if (evidence) {
|
|
288
|
+
childAuthorization = params.mutationAdmission.advanceCreatedDirectory(evidence);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
const targetPath = retainedTargetPath ?? prospectiveTargetPath(child.parentPath, segmentRoute, index + 1, params.basename);
|
|
292
|
+
const request = Object.freeze({
|
|
293
|
+
targetPath,
|
|
294
|
+
mutationPath: targetPath,
|
|
295
|
+
phase: "parent",
|
|
296
|
+
});
|
|
297
|
+
childAuthorization ??= params.mutationAdmission?.tryAuthorizeAtParent?.(request, child.observation);
|
|
298
|
+
if (!childAuthorization) {
|
|
299
|
+
await authorizePinnedMutation(params, request);
|
|
300
|
+
assertPosixParentCurrent(child);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
catch (error) {
|
|
304
|
+
closeFd(childFd);
|
|
305
|
+
throw error;
|
|
306
|
+
}
|
|
307
|
+
const previousOwnedFd = currentOwnedFd;
|
|
308
|
+
currentOwnedFd = child.parentFd;
|
|
309
|
+
currentFd = child.parentFd;
|
|
310
|
+
currentPath = child.parentPath;
|
|
311
|
+
current = child;
|
|
312
|
+
if (previousOwnedFd !== undefined)
|
|
313
|
+
closeFd(previousOwnedFd);
|
|
314
|
+
if (index === segments.length - 1) {
|
|
315
|
+
currentOwnedFd = undefined;
|
|
316
|
+
return child;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
// Empty relative parents are handled by the complete-parent fast path.
|
|
320
|
+
throw new FsSafeError("path-mismatch", "native write parent admission did not complete");
|
|
321
|
+
}
|
|
322
|
+
finally {
|
|
323
|
+
if (currentOwnedFd !== undefined)
|
|
324
|
+
closeFd(currentOwnedFd);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
63
327
|
export async function runPinnedWriteNative(binding, params) {
|
|
64
328
|
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
65
329
|
try {
|
|
330
|
+
const closeFd = captureNativeFdClose(binding);
|
|
66
331
|
const windows = process.platform === "win32";
|
|
67
332
|
if (!windows) {
|
|
68
333
|
assertNativeStaging(binding);
|
|
69
334
|
}
|
|
70
335
|
const directoryFlags = fsSync.constants.O_RDONLY | (fsSync.constants.O_DIRECTORY ?? 0);
|
|
71
|
-
const
|
|
336
|
+
const rootAdmission = await openNativeRootAdmission(binding, {
|
|
337
|
+
rootPath: params.rootPath,
|
|
338
|
+
rootIdentity: params.rootIdentity,
|
|
339
|
+
operation: "native write",
|
|
340
|
+
});
|
|
341
|
+
const root = rootAdmission.root;
|
|
72
342
|
const posixRoot = __addDisposableResource(env_1, windows ? undefined : root, true);
|
|
73
343
|
let parentFd;
|
|
74
344
|
let windowsOwnsDirectories = false;
|
|
@@ -77,49 +347,42 @@ export async function runPinnedWriteNative(binding, params) {
|
|
|
77
347
|
const parentGuard = __addDisposableResource(env_1, {
|
|
78
348
|
[Symbol.dispose]() {
|
|
79
349
|
if (!windows && parentFd !== undefined) {
|
|
80
|
-
|
|
350
|
+
closeFd(parentFd);
|
|
81
351
|
}
|
|
82
352
|
},
|
|
83
353
|
}, false);
|
|
84
354
|
try {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
let
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
355
|
+
let parentPath;
|
|
356
|
+
let directory;
|
|
357
|
+
let parentPathStat;
|
|
358
|
+
let policyParentAdmitted = false;
|
|
359
|
+
if (!windows && params.mutationAdmission) {
|
|
360
|
+
const admitted = await capturePolicyAwarePosixParent(binding, params, root.fd, directoryFlags);
|
|
361
|
+
parentFd = admitted.parentFd;
|
|
362
|
+
parentPath = admitted.parentPath;
|
|
363
|
+
directory = admitted.directory;
|
|
364
|
+
parentPathStat = admitted.parentPathStat;
|
|
365
|
+
policyParentAdmitted = true;
|
|
95
366
|
}
|
|
96
367
|
else {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
if (!rootMatches) {
|
|
101
|
-
throw new FsSafeError("path-mismatch", "root path changed during native write");
|
|
102
|
-
}
|
|
103
|
-
if (params.mkdir) {
|
|
104
|
-
params.assertBeforeMutation?.();
|
|
105
|
-
binding.mkdirBeneath(root.fd, params.relativeParentPath, 0o777);
|
|
106
|
-
}
|
|
107
|
-
parentFd = binding.openBeneath(root.fd, params.relativeParentPath, directoryFlags).fd;
|
|
108
|
-
const parentPath = realpathSync.native(params.relativeParentPath
|
|
109
|
-
? path.join(params.rootPath, ...params.relativeParentPath.split("/"))
|
|
110
|
-
: params.rootPath);
|
|
111
|
-
const directory = windows ? undefined : describeStagedDirectory(parentFd, parentPath);
|
|
112
|
-
const parentPathStat = exactRoot
|
|
113
|
-
? await inspectDirectoryIdentity(parentPath, inspectFileIdentitySync(() => fsSync.fstatSync(parentFd, { bigint: true })))
|
|
114
|
-
: fsSync.lstatSync(parentPath);
|
|
115
|
-
if (windows && !exactRoot) {
|
|
116
|
-
const parentIdentity = binding.fstatIdentity(parentFd);
|
|
117
|
-
if (parentPathStat.isSymbolicLink() || !sameNativeIdentity(parentPathStat, parentIdentity)) {
|
|
118
|
-
throw new FsSafeError("path-mismatch", "native write parent changed during resolution");
|
|
368
|
+
if (params.mkdir) {
|
|
369
|
+
params.assertBeforeMutation?.();
|
|
370
|
+
binding.mkdirBeneath(root.fd, params.relativeParentPath, 0o777);
|
|
119
371
|
}
|
|
372
|
+
const admitted = await openNativeParentAdmission(binding, rootAdmission, params.relativeParentPath);
|
|
373
|
+
parentFd = admitted.fd;
|
|
374
|
+
parentPath = admitted.guard.realPath;
|
|
375
|
+
directory = admitted.stagedDirectory;
|
|
376
|
+
parentPathStat = admitted.guard.stat;
|
|
120
377
|
}
|
|
121
|
-
|
|
122
|
-
|
|
378
|
+
if (!policyParentAdmitted && params.mutationAdmission) {
|
|
379
|
+
const targetPath = path.join(parentPath, params.basename);
|
|
380
|
+
await authorizePinnedMutation(params, {
|
|
381
|
+
targetPath,
|
|
382
|
+
mutationPath: targetPath,
|
|
383
|
+
phase: "parent",
|
|
384
|
+
});
|
|
385
|
+
await inspectDirectoryIdentity(parentPath, inspectFileIdentitySync(() => fsSync.fstatSync(parentFd, { bigint: true })));
|
|
123
386
|
}
|
|
124
387
|
const verificationGuard = { dir: parentPath, realPath: parentPath, stat: parentPathStat };
|
|
125
388
|
if (params.overwrite === false) {
|
|
@@ -140,12 +403,21 @@ export async function runPinnedWriteNative(binding, params) {
|
|
|
140
403
|
}
|
|
141
404
|
const ownedParent = parentFd;
|
|
142
405
|
parentFd = undefined;
|
|
143
|
-
return await writeNativeStage(binding, ownedParent, directory, params, verificationGuard);
|
|
406
|
+
return await writeNativeStage(binding, ownedParent, closeFd, directory, params, verificationGuard);
|
|
144
407
|
}
|
|
145
408
|
finally {
|
|
146
409
|
if (windows && !windowsOwnsDirectories) {
|
|
147
410
|
if (parentFd !== undefined) {
|
|
148
|
-
|
|
411
|
+
// Match the Windows leaf cleanup contract: an admission or identity
|
|
412
|
+
// failure remains primary, while every owned directory is still given
|
|
413
|
+
// a close attempt. In particular, a parent close failure must not skip
|
|
414
|
+
// the root FileHandle close.
|
|
415
|
+
try {
|
|
416
|
+
closeFd(parentFd);
|
|
417
|
+
}
|
|
418
|
+
catch {
|
|
419
|
+
// Best effort while propagating the operation failure.
|
|
420
|
+
}
|
|
149
421
|
}
|
|
150
422
|
await root.close().catch(() => undefined);
|
|
151
423
|
}
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import type { DirectoryReceipt } from "./directory-durability.js";
|
|
2
2
|
import type { AnyAsyncDirectoryGuard } from "./directory-guard.js";
|
|
3
3
|
import type { FileIdentityStat } from "./file-identity.js";
|
|
4
|
-
import type
|
|
4
|
+
import { type NativeBinding } from "./native-binding.js";
|
|
5
5
|
import type { PinnedWriteInput, PinnedWriteParams } from "./pinned-write.js";
|
|
6
6
|
import type { PublishedFileReceipt, StagedFile, StagedFileCleanupReceipt, StagedFileReceipt } from "./staged-file-types.js";
|
|
7
7
|
export type NativeStagingBinding = NativeBinding & Required<Pick<NativeBinding, "createStagedFile" | "stagedFileMatches" | "removeStagedFile">>;
|
|
8
8
|
export declare function assertNativeStaging(binding: NativeBinding): asserts binding is NativeStagingBinding;
|
|
9
9
|
declare class NativeStagedFile implements StagedFile {
|
|
10
10
|
#private;
|
|
11
|
-
constructor(binding: NativeStagingBinding, parentFd: number, directory: StagedFileReceipt["directory"], portableNames: boolean, publishedMode: number, sync: boolean, assertBeforeMutation?: () => void);
|
|
12
|
-
static create(binding: NativeStagingBinding, parentFd: number, directory: StagedFileReceipt["directory"], input: PinnedWriteInput, mode: number, maxBytes?: number, portableNames?: boolean, sync?: boolean, assertBeforeMutation?: () => void): Promise<NativeStagedFile>;
|
|
13
|
-
static write(binding: NativeStagingBinding, parentFd: number, directory: StagedFileReceipt["directory"], params: PinnedWriteParams, parentGuard: AnyAsyncDirectoryGuard): Promise<FileIdentityStat>;
|
|
11
|
+
constructor(binding: NativeStagingBinding, parentFd: number, closeParentFd: (fd: number) => void, directory: StagedFileReceipt["directory"], portableNames: boolean, publishedMode: number, sync: boolean, assertBeforeMutation?: () => void);
|
|
12
|
+
static create(binding: NativeStagingBinding, parentFd: number, closeParentFd: (fd: number) => void, directory: StagedFileReceipt["directory"], input: PinnedWriteInput, mode: number, maxBytes?: number, portableNames?: boolean, sync?: boolean, assertBeforeMutation?: () => void): Promise<NativeStagedFile>;
|
|
13
|
+
static write(binding: NativeStagingBinding, parentFd: number, closeParentFd: (fd: number) => void, directory: StagedFileReceipt["directory"], params: PinnedWriteParams, parentGuard: AnyAsyncDirectoryGuard): Promise<FileIdentityStat>;
|
|
14
14
|
get receipt(): StagedFileReceipt;
|
|
15
15
|
assertCurrent(): Promise<void>;
|
|
16
16
|
publish(basename: string, options: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"native-staged-file.d.ts","sourceRoot":"","sources":["../src/native-staged-file.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAMlE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAGnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"native-staged-file.d.ts","sourceRoot":"","sources":["../src/native-staged-file.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAMlE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAGnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,EAAwB,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAG/E,OAAO,KAAK,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAG7E,OAAO,KAAK,EACV,oBAAoB,EACpB,UAAU,EACV,wBAAwB,EAGxB,iBAAiB,EAClB,MAAM,wBAAwB,CAAC;AAEhC,MAAM,MAAM,oBAAoB,GAAG,aAAa,GAAG,QAAQ,CAAC,IAAI,CAC9D,aAAa,EACb,kBAAkB,GAAG,mBAAmB,GAAG,kBAAkB,CAC9D,CAAC,CAAC;AAEH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,OAAO,IAAI,oBAAoB,CAWnG;AA6BD,cAAM,gBAAiB,YAAW,UAAU;;IAe1C,YACE,OAAO,EAAE,oBAAoB,EAC7B,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,EACnC,SAAS,EAAE,iBAAiB,CAAC,WAAW,CAAC,EACzC,aAAa,EAAE,OAAO,EACtB,aAAa,EAAE,MAAM,EACrB,IAAI,EAAE,OAAO,EACb,oBAAoB,CAAC,EAAE,MAAM,IAAI,EAWlC;IAID,OAAa,MAAM,CACjB,OAAO,EAAE,oBAAoB,EAC7B,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,EACnC,SAAS,EAAE,iBAAiB,CAAC,WAAW,CAAC,EACzC,KAAK,EAAE,gBAAgB,EACvB,IAAI,EAAE,MAAM,EACZ,QAAQ,CAAC,EAAE,MAAM,EAEjB,aAAa,UAAO,EACpB,IAAI,UAAO,EACX,oBAAoB,CAAC,EAAE,MAAM,IAAI,GAChC,OAAO,CAAC,gBAAgB,CAAC,CAc3B;IAED,OAAa,KAAK,CAChB,OAAO,EAAE,oBAAoB,EAC7B,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,EACnC,SAAS,EAAE,iBAAiB,CAAC,WAAW,CAAC,EACzC,MAAM,EAAE,iBAAiB,EACzB,WAAW,EAAE,sBAAsB,GAClC,OAAO,CAAC,gBAAgB,CAAC,CAc3B;IAED,IAAI,OAAO,IAAI,iBAAiB,CAK/B;IA8FK,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAEnC;IAEK,OAAO,CACX,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE;QAAE,SAAS,EAAE,OAAO,CAAA;KAAE,EAC/B,WAAW,CAAC,EAAE,iBAAiB,CAAC,aAAa,CAAC,GAC7C,OAAO,CAAC,oBAAoB,CAAC,CAyD/B;IAgDK,OAAO,IAAI,OAAO,CAAC,wBAAwB,CAAC,CAMjD;IAEK,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAO3C;CACF;AAED,eAAO,MAAM,iBAAiB,EAAE,CAAC,GAAG,IAAI,EAAE,UAAU,CAAC,OAAO,gBAAgB,CAAC,MAAM,CAAC,KAAK,OAAO,CAAC,UAAU,CAClF,CAAC;AAC1B,eAAO,MAAM,gBAAgB,+BAAyB,CAAC;AAEvD,YAAY,EACV,oBAAoB,EAAE,UAAU,EAAE,wBAAwB,EAAE,wBAAwB,EACpF,qBAAqB,EAAE,iBAAiB,GACzC,MAAM,wBAAwB,CAAC;AAEhC,wBAAsB,oBAAoB,CAAC,OAAO,EAAE;IAClD,SAAS,EAAE,MAAM,GAAG,gBAAgB,CAAC;IACrC,OAAO,EAAE,MAAM,GAAG,UAAU,CAAC;IAC7B,6EAA6E;IAC7E,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,GAAG,OAAO,CAAC,UAAU,CAAC,CAUtB"}
|
|
@@ -57,12 +57,14 @@ import fs from "node:fs";
|
|
|
57
57
|
import path from "node:path";
|
|
58
58
|
import { FsSafeError } from "./errors.js";
|
|
59
59
|
import { MutationAuthorityError } from "./mutation-authority.js";
|
|
60
|
+
import { captureNativeFdClose } from "./native-binding.js";
|
|
60
61
|
import { writeNativeInput } from "./native-operations.js";
|
|
61
62
|
import { assertNativeCopyCompleted, createNativeCopyFile } from "./copy-file-input.js";
|
|
62
63
|
import { assertStagedDirectoryCurrent, openStagedDirectory } from "./staged-directory.js";
|
|
63
64
|
import { assertFinalSymlinkRejected } from "./root-symlink-policy.js";
|
|
64
65
|
export function assertNativeStaging(binding) {
|
|
65
66
|
if ([
|
|
67
|
+
binding.closeOwnedFd,
|
|
66
68
|
binding.createStagedFile,
|
|
67
69
|
binding.stagedFileMatches,
|
|
68
70
|
binding.removeStagedFile,
|
|
@@ -92,7 +94,9 @@ function failure(error, details) {
|
|
|
92
94
|
}
|
|
93
95
|
class NativeStagedFile {
|
|
94
96
|
#binding;
|
|
97
|
+
#closeFd;
|
|
95
98
|
#parentFd;
|
|
99
|
+
#closeParentFd;
|
|
96
100
|
#directory;
|
|
97
101
|
#portableNames;
|
|
98
102
|
#publishedMode;
|
|
@@ -102,26 +106,29 @@ class NativeStagedFile {
|
|
|
102
106
|
#state = { status: "open", publication: NOT_PUBLISHED };
|
|
103
107
|
#receipt;
|
|
104
108
|
#rejectFinalSymlink = false;
|
|
105
|
-
constructor(binding, parentFd, directory, portableNames, publishedMode, sync, assertBeforeMutation) {
|
|
109
|
+
constructor(binding, parentFd, closeParentFd, directory, portableNames, publishedMode, sync, assertBeforeMutation) {
|
|
110
|
+
this.#closeFd = captureNativeFdClose(binding);
|
|
106
111
|
this.#binding = binding;
|
|
107
112
|
this.#parentFd = parentFd;
|
|
113
|
+
this.#closeParentFd = closeParentFd;
|
|
108
114
|
this.#directory = directory;
|
|
109
115
|
this.#portableNames = portableNames;
|
|
110
116
|
this.#publishedMode = publishedMode;
|
|
111
117
|
this.#sync = sync;
|
|
112
118
|
this.#assertBeforeMutation = assertBeforeMutation;
|
|
113
119
|
}
|
|
114
|
-
//
|
|
115
|
-
|
|
120
|
+
// Public staging supplies a Node parent; pinned writes supply a native parent.
|
|
121
|
+
// The supplied closer owns parentFd even on construction or preparation failure.
|
|
122
|
+
static async create(binding, parentFd, closeParentFd, directory, input, mode, maxBytes,
|
|
116
123
|
// Public staging uses portable names; existing POSIX writes accept literal names.
|
|
117
124
|
portableNames = true, sync = true, assertBeforeMutation) {
|
|
118
125
|
let staged;
|
|
119
126
|
try {
|
|
120
|
-
staged = new NativeStagedFile(binding, parentFd, directory, portableNames, mode, sync, assertBeforeMutation);
|
|
127
|
+
staged = new NativeStagedFile(binding, parentFd, closeParentFd, directory, portableNames, mode, sync, assertBeforeMutation);
|
|
121
128
|
}
|
|
122
129
|
catch (error) {
|
|
123
130
|
try {
|
|
124
|
-
|
|
131
|
+
closeParentFd(parentFd);
|
|
125
132
|
}
|
|
126
133
|
catch (closeError) {
|
|
127
134
|
throw new AggregateError([error, closeError], "staged owner construction and close failed");
|
|
@@ -131,12 +138,12 @@ class NativeStagedFile {
|
|
|
131
138
|
await staged.#prepare(input, maxBytes);
|
|
132
139
|
return staged;
|
|
133
140
|
}
|
|
134
|
-
static async write(binding, parentFd, directory, params, parentGuard) {
|
|
141
|
+
static async write(binding, parentFd, closeParentFd, directory, params, parentGuard) {
|
|
135
142
|
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
136
143
|
try {
|
|
137
144
|
// This owner never escapes. Only the internal verifier borrows its fd;
|
|
138
145
|
// public descriptor methods remain await-free and cannot race disposal.
|
|
139
|
-
const staged = __addDisposableResource(env_1, await NativeStagedFile.create(binding, parentFd, directory, params.input, params.mode, params.maxBytes, false, params.sync, params.assertBeforeMutation), true);
|
|
146
|
+
const staged = __addDisposableResource(env_1, await NativeStagedFile.create(binding, parentFd, closeParentFd, directory, params.input, params.mode, params.maxBytes, false, params.sync, params.assertBeforeMutation), true);
|
|
140
147
|
staged.#rejectFinalSymlink = params.rejectFinalSymlink === true;
|
|
141
148
|
if (params.input.kind === "file")
|
|
142
149
|
await params.input.verifySource();
|
|
@@ -336,12 +343,15 @@ class NativeStagedFile {
|
|
|
336
343
|
}
|
|
337
344
|
}
|
|
338
345
|
let resources = "closed";
|
|
339
|
-
for (const fd of [
|
|
346
|
+
for (const [fd, closeFd] of [
|
|
347
|
+
[state.fileFd, this.#closeFd],
|
|
348
|
+
[this.#parentFd, this.#closeParentFd],
|
|
349
|
+
]) {
|
|
340
350
|
if (fd === undefined) {
|
|
341
351
|
continue;
|
|
342
352
|
}
|
|
343
353
|
try {
|
|
344
|
-
|
|
354
|
+
closeFd(fd);
|
|
345
355
|
}
|
|
346
356
|
catch (error) {
|
|
347
357
|
resources = "close-failed";
|
|
@@ -385,5 +395,5 @@ export async function stageFileInDirectory(options) {
|
|
|
385
395
|
const input = { kind: "buffer", data: Buffer.from(options.content) };
|
|
386
396
|
const mode = options.mode ?? 0o600;
|
|
387
397
|
const parent = openStagedDirectory(options.directory);
|
|
388
|
-
return await createNativeStage(binding, parent.fd, parent.receipt, input, mode);
|
|
398
|
+
return await createNativeStage(binding, parent.fd, fs.closeSync, parent.receipt, input, mode);
|
|
389
399
|
}
|
package/dist/native.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type NativeBinding } from "./native-binding.js";
|
|
2
2
|
export type { NativeBinding } from "./native-binding.js";
|
|
3
3
|
export declare function __loadBundledNativeForTest(): NativeBinding;
|
|
4
4
|
export declare function __nativeLoaderDetectorsForTest(): {
|
package/dist/native.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"native.d.ts","sourceRoot":"","sources":["../src/native.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"native.d.ts","sourceRoot":"","sources":["../src/native.ts"],"names":[],"mappings":"AAQA,OAAO,EAAwB,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAG/E,YAAY,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAwKzD,wBAAgB,0BAA0B,IAAI,aAAa,CAE1D;AAED,wBAAgB,8BAA8B,IAAI;IAChD,MAAM,EAAE,OAAO,GAAG,SAAS,CAAC;IAC5B,UAAU,EAAE,OAAO,GAAG,SAAS,CAAC;IAChC,cAAc,EAAE,OAAO,GAAG,SAAS,CAAC;CACrC,CAMA;AAED,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,CAAC,QAAQ,EACzB,IAAI,EAAE,MAAM,EACZ,IAAI,UAAQ,GACX,MAAM,GAAG,SAAS,CAEpB;AAED,wBAAgB,gBAAgB,IAAI,aAAa,GAAG,SAAS,CAoB5D;AAED,wBAAgB,oBAAoB,IAAI,aAAa,CAQpD;AAED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,MAAM,aAAa,GAAG,IAAI,CAK1E;AAED,wBAAgB,0BAA0B,IAAI,IAAI,CAKjD"}
|
package/dist/native.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { closeSync, openSync, readSync, readdirSync, } from "node:fs";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import { FsSafeError } from "./errors.js";
|
|
4
|
+
import { captureNativeFdClose } from "./native-binding.js";
|
|
4
5
|
import { getFsSafeNativeConfig } from "./native-config.js";
|
|
5
6
|
const require = createRequire(import.meta.url);
|
|
6
7
|
let binding;
|
|
@@ -144,7 +145,9 @@ function loadBundledBinding() {
|
|
|
144
145
|
if (!target) {
|
|
145
146
|
throw new Error(`Unsupported OS or architecture: ${process.platform}-${process.arch}`);
|
|
146
147
|
}
|
|
147
|
-
|
|
148
|
+
const loaded = require(nativePackageForTarget(target));
|
|
149
|
+
captureNativeFdClose(loaded);
|
|
150
|
+
return loaded;
|
|
148
151
|
}
|
|
149
152
|
export function __loadBundledNativeForTest() {
|
|
150
153
|
return loadBundledBinding();
|
|
@@ -166,7 +169,9 @@ export function getNativeBinding() {
|
|
|
166
169
|
if (!attempted) {
|
|
167
170
|
attempted = true;
|
|
168
171
|
try {
|
|
169
|
-
|
|
172
|
+
const loaded = loadBinding();
|
|
173
|
+
captureNativeFdClose(loaded);
|
|
174
|
+
binding = loaded;
|
|
170
175
|
}
|
|
171
176
|
catch (error) {
|
|
172
177
|
loadError = error;
|
package/dist/output.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"output.d.ts","sourceRoot":"","sources":["../src/output.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"output.d.ts","sourceRoot":"","sources":["../src/output.ts"],"names":[],"mappings":"AAWA,MAAM,MAAM,wBAAwB,CAAC,CAAC,GAAG,IAAI,IAAI;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IACxC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;IAClC,uEAAuE;IACvE,iBAAiB,CAAC,EAAE,mBAAmB,CAAC;IACxC,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,uBAAuB,CAAC,CAAC,GAAG,IAAI,IAAI;IAC9C,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,CAAC,CAAC;CACX,CAAC;AAyDF,wBAAsB,2BAA2B,CAAC,CAAC,GAAG,IAAI,EACxD,OAAO,EAAE,wBAAwB,CAAC,CAAC,CAAC,GACnC,OAAO,CAAC,uBAAuB,CAAC,CAAC,CAAC,CAAC,CA2DrC"}
|