@openclaw/fs-safe 0.14.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +88 -0
- package/README.md +38 -10
- package/dist/advanced.d.ts +2 -1
- package/dist/advanced.d.ts.map +1 -1
- package/dist/advanced.js +2 -1
- package/dist/archive-kind.d.ts +0 -1
- package/dist/archive-kind.d.ts.map +1 -1
- package/dist/archive-kind.js +5 -17
- package/dist/archive-merge.d.ts.map +1 -1
- package/dist/archive-merge.js +113 -46
- package/dist/archive-parser.wasm +0 -0
- package/dist/archive-read.d.ts.map +1 -1
- package/dist/archive-read.js +10 -11
- package/dist/archive-tar-stream.d.ts +3 -0
- package/dist/archive-tar-stream.d.ts.map +1 -1
- package/dist/archive-tar-stream.js +56 -37
- package/dist/archive-tar-wasm.d.ts +16 -4
- package/dist/archive-tar-wasm.d.ts.map +1 -1
- package/dist/archive-tar-wasm.js +134 -34
- package/dist/archive-zip-directory.d.ts +4 -0
- package/dist/archive-zip-directory.d.ts.map +1 -1
- package/dist/archive-zip-directory.js +2 -0
- package/dist/archive-zip-entry.d.ts +6 -2
- package/dist/archive-zip-entry.d.ts.map +1 -1
- package/dist/archive-zip-entry.js +23 -8
- package/dist/archive-zip-integrity.d.ts.map +1 -1
- package/dist/archive-zip-integrity.js +3 -4
- package/dist/archive-zip-loader.d.ts.map +1 -1
- package/dist/archive-zip-loader.js +107 -31
- package/dist/archive-zip-names.d.ts +1 -0
- package/dist/archive-zip-names.d.ts.map +1 -1
- package/dist/archive-zip-names.js +6 -0
- package/dist/archive.d.ts.map +1 -1
- package/dist/archive.js +11 -11
- package/dist/bounded-read-stream.d.ts +0 -1
- package/dist/bounded-read-stream.d.ts.map +1 -1
- package/dist/bounded-read-stream.js +0 -6
- package/dist/clone-metadata.d.ts +1 -0
- package/dist/clone-metadata.d.ts.map +1 -1
- package/dist/clone-metadata.js +6 -2
- package/dist/copy-publication.d.ts +6 -0
- package/dist/copy-publication.d.ts.map +1 -1
- package/dist/copy-publication.js +3 -0
- package/dist/copy-tree-portable.d.ts.map +1 -1
- package/dist/copy-tree-portable.js +44 -24
- package/dist/copy.d.ts.map +1 -1
- package/dist/copy.js +29 -11
- package/dist/create-directory.d.ts +20 -0
- package/dist/create-directory.d.ts.map +1 -0
- package/dist/create-directory.js +130 -0
- package/dist/create-file-async.d.ts +7 -0
- package/dist/create-file-async.d.ts.map +1 -0
- package/dist/create-file-async.js +121 -0
- package/dist/create-file.d.ts +8 -0
- package/dist/create-file.d.ts.map +1 -0
- package/dist/create-file.js +190 -0
- package/dist/create-owned-file.d.ts +8 -0
- package/dist/create-owned-file.d.ts.map +1 -0
- package/dist/create-owned-file.js +16 -0
- package/dist/create.d.ts +4 -0
- package/dist/create.d.ts.map +1 -0
- package/dist/create.js +2 -0
- package/dist/creation-darwin.d.ts +7 -0
- package/dist/creation-darwin.d.ts.map +1 -0
- package/dist/creation-darwin.js +79 -0
- package/dist/creation-file-state.d.ts +19 -0
- package/dist/creation-file-state.d.ts.map +1 -0
- package/dist/creation-file-state.js +118 -0
- package/dist/creation-path.d.ts +21 -0
- package/dist/creation-path.d.ts.map +1 -0
- package/dist/creation-path.js +71 -0
- package/dist/creation-permissions.d.ts +19 -0
- package/dist/creation-permissions.d.ts.map +1 -0
- package/dist/creation-permissions.js +125 -0
- package/dist/directory-durability.d.ts +1 -1
- package/dist/directory-durability.d.ts.map +1 -1
- package/dist/directory-durability.js +22 -80
- package/dist/directory-guard.d.ts +3 -0
- package/dist/directory-guard.d.ts.map +1 -1
- package/dist/directory-mode-node.d.ts +2 -0
- package/dist/directory-mode-node.d.ts.map +1 -1
- package/dist/directory-mode-node.js +8 -0
- package/dist/directory-mode-owner.js +5 -5
- package/dist/directory-receipt.d.ts +24 -0
- package/dist/directory-receipt.d.ts.map +1 -0
- package/dist/directory-receipt.js +127 -0
- package/dist/file-cleanup.d.ts +19 -0
- package/dist/file-cleanup.d.ts.map +1 -0
- package/dist/file-cleanup.js +78 -0
- package/dist/file-handle-transfer.d.ts +2 -0
- package/dist/file-handle-transfer.d.ts.map +1 -1
- package/dist/file-handle-transfer.js +57 -2
- package/dist/file-identity.d.ts.map +1 -1
- package/dist/file-identity.js +18 -4
- package/dist/file-lock-sync-admission.d.ts +19 -0
- package/dist/file-lock-sync-admission.d.ts.map +1 -0
- package/dist/file-lock-sync-admission.js +93 -0
- package/dist/file-lock-sync-root-acquire.d.ts +4 -0
- package/dist/file-lock-sync-root-acquire.d.ts.map +1 -0
- package/dist/file-lock-sync-root-acquire.js +370 -0
- package/dist/file-lock-sync-root-arbitration.d.ts +18 -0
- package/dist/file-lock-sync-root-arbitration.d.ts.map +1 -0
- package/dist/file-lock-sync-root-arbitration.js +66 -0
- package/dist/file-lock-sync-root-held.d.ts +34 -0
- package/dist/file-lock-sync-root-held.d.ts.map +1 -0
- package/dist/file-lock-sync-root-held.js +393 -0
- package/dist/file-lock-sync-root-io.d.ts +44 -0
- package/dist/file-lock-sync-root-io.d.ts.map +1 -0
- package/dist/file-lock-sync-root-io.js +209 -0
- package/dist/file-lock-sync-root-mutation.d.ts +17 -0
- package/dist/file-lock-sync-root-mutation.d.ts.map +1 -0
- package/dist/file-lock-sync-root-mutation.js +277 -0
- package/dist/file-lock-sync-root-options.d.ts +20 -0
- package/dist/file-lock-sync-root-options.d.ts.map +1 -0
- package/dist/file-lock-sync-root-options.js +58 -0
- package/dist/file-lock-sync-root-registration.d.ts +2 -0
- package/dist/file-lock-sync-root-registration.d.ts.map +1 -0
- package/dist/file-lock-sync-root-registration.js +90 -0
- package/dist/file-lock-sync-root.d.ts +36 -0
- package/dist/file-lock-sync-root.d.ts.map +1 -0
- package/dist/file-lock-sync-root.js +361 -0
- package/dist/file-lock-sync-stale-admission.d.ts +24 -0
- package/dist/file-lock-sync-stale-admission.d.ts.map +1 -0
- package/dist/file-lock-sync-stale-admission.js +205 -0
- package/dist/file-lock-sync.d.ts.map +1 -1
- package/dist/file-lock-sync.js +245 -205
- package/dist/file-observation.d.ts +1 -1
- package/dist/file-observation.d.ts.map +1 -1
- package/dist/file-store-boundary.d.ts +2 -6
- package/dist/file-store-boundary.d.ts.map +1 -1
- package/dist/file-store-boundary.js +3 -9
- package/dist/file-store-prune.d.ts.map +1 -1
- package/dist/file-store-prune.js +5 -1
- package/dist/file-store-sync-write.d.ts.map +1 -1
- package/dist/file-store-sync-write.js +5 -8
- package/dist/file-store.d.ts.map +1 -1
- package/dist/file-store.js +47 -12
- package/dist/guarded-mkdir.d.ts +1 -0
- package/dist/guarded-mkdir.d.ts.map +1 -1
- package/dist/guarded-mkdir.js +36 -7
- package/dist/json-document-store.d.ts.map +1 -1
- package/dist/json-document-store.js +22 -15
- package/dist/json-durable-queue-ownership.d.ts +0 -1
- package/dist/json-durable-queue-ownership.d.ts.map +1 -1
- package/dist/json-durable-queue-ownership.js +0 -6
- package/dist/move-path.js +1 -1
- package/dist/native-binding.d.ts +13 -1
- package/dist/native-binding.d.ts.map +1 -1
- package/dist/native-fallback-warning.d.ts +4 -0
- package/dist/native-fallback-warning.d.ts.map +1 -0
- package/dist/native-fallback-warning.js +11 -0
- package/dist/native-operations.d.ts +0 -2
- package/dist/native-operations.d.ts.map +1 -1
- package/dist/native-operations.js +0 -24
- package/dist/native-parent-admission.d.ts +5 -2
- package/dist/native-parent-admission.d.ts.map +1 -1
- package/dist/native-parent-admission.js +27 -7
- package/dist/native-pinned-write-windows.d.ts +1 -1
- package/dist/native-pinned-write-windows.d.ts.map +1 -1
- package/dist/native-pinned-write-windows.js +174 -29
- package/dist/native-pinned-write.d.ts.map +1 -1
- package/dist/native-pinned-write.js +26 -14
- package/dist/native-policy-parent-windows.d.ts +14 -0
- package/dist/native-policy-parent-windows.d.ts.map +1 -0
- package/dist/native-policy-parent-windows.js +209 -0
- package/dist/native-rename-outcome.d.ts +4 -0
- package/dist/native-rename-outcome.d.ts.map +1 -0
- package/dist/native-rename-outcome.js +8 -0
- package/dist/native-staged-file.d.ts +3 -2
- package/dist/native-staged-file.d.ts.map +1 -1
- package/dist/native-staged-file.js +121 -72
- package/dist/output.d.ts.map +1 -1
- package/dist/output.js +12 -8
- package/dist/owner-dacl.d.ts.map +1 -1
- package/dist/owner-dacl.js +10 -4
- package/dist/path-prefix.d.ts.map +1 -1
- package/dist/path-prefix.js +30 -8
- package/dist/path-suffix-aliases.d.ts +2 -0
- package/dist/path-suffix-aliases.d.ts.map +1 -1
- package/dist/path-suffix-aliases.js +25 -17
- package/dist/permission-exec.d.ts +2 -0
- package/dist/permission-exec.d.ts.map +1 -1
- package/dist/permission-exec.js +150 -21
- package/dist/permissions-windows.js +1 -1
- package/dist/pinned-mutation-admission.d.ts.map +1 -1
- package/dist/pinned-mutation-admission.js +10 -5
- package/dist/pinned-mutation-observation.d.ts +0 -1
- package/dist/pinned-mutation-observation.d.ts.map +1 -1
- package/dist/pinned-mutation-observation.js +0 -19
- package/dist/pinned-mutation-shared-route.d.ts +1 -0
- package/dist/pinned-mutation-shared-route.d.ts.map +1 -1
- package/dist/pinned-mutation-shared-route.js +1 -1
- package/dist/pinned-write-input.d.ts +4 -0
- package/dist/pinned-write-input.d.ts.map +1 -0
- package/dist/pinned-write-input.js +25 -0
- package/dist/pinned-write-mode.d.ts +5 -0
- package/dist/pinned-write-mode.d.ts.map +1 -0
- package/dist/pinned-write-mode.js +24 -0
- package/dist/pinned-write-staged.d.ts +6 -0
- package/dist/pinned-write-staged.d.ts.map +1 -0
- package/dist/pinned-write-staged.js +187 -0
- package/dist/pinned-write-types.d.ts +5 -0
- package/dist/pinned-write-types.d.ts.map +1 -1
- package/dist/pinned-write.d.ts.map +1 -1
- package/dist/pinned-write.js +35 -145
- package/dist/private-directory.d.ts.map +1 -1
- package/dist/private-directory.js +18 -4
- package/dist/private-producer-handoff-sync.d.ts +14 -0
- package/dist/private-producer-handoff-sync.d.ts.map +1 -0
- package/dist/private-producer-handoff-sync.js +114 -0
- package/dist/private-producer-handoff.d.ts +22 -4
- package/dist/private-producer-handoff.d.ts.map +1 -1
- package/dist/private-producer-handoff.js +140 -77
- package/dist/private-temp-workspace.d.ts.map +1 -1
- package/dist/private-temp-workspace.js +75 -121
- package/dist/publish-copy-stage.d.ts +2 -1
- package/dist/publish-copy-stage.d.ts.map +1 -1
- package/dist/publish-copy-stage.js +16 -7
- package/dist/publish-file.d.ts.map +1 -1
- package/dist/publish-file.js +2 -2
- package/dist/regular-file.d.ts.map +1 -1
- package/dist/regular-file.js +1 -15
- package/dist/replace-directory.d.ts.map +1 -1
- package/dist/replace-directory.js +256 -18
- package/dist/replace-file-copy-fallback.d.ts.map +1 -1
- package/dist/replace-file-copy-fallback.js +62 -70
- package/dist/replace-file-copy-source.d.ts.map +1 -1
- package/dist/replace-file-copy-source.js +10 -12
- package/dist/replace-file-temp-owner.d.ts +5 -9
- package/dist/replace-file-temp-owner.d.ts.map +1 -1
- package/dist/replace-file-temp-owner.js +56 -72
- package/dist/replace-file.js +6 -6
- package/dist/retained-directory-replacement.d.ts +26 -0
- package/dist/retained-directory-replacement.d.ts.map +1 -0
- package/dist/retained-directory-replacement.js +193 -0
- package/dist/root-boundary.d.ts +1 -0
- package/dist/root-boundary.d.ts.map +1 -1
- package/dist/root-boundary.js +4 -0
- package/dist/root-context.d.ts +0 -8
- package/dist/root-context.d.ts.map +1 -1
- package/dist/root-context.js +0 -3
- package/dist/root-create-input.d.ts +8 -1
- package/dist/root-create-input.d.ts.map +1 -1
- package/dist/root-create-input.js +17 -4
- package/dist/root-directory-creation.d.ts +3 -3
- package/dist/root-directory-creation.d.ts.map +1 -1
- package/dist/root-directory-creation.js +15 -3
- package/dist/root-directory-list.d.ts +1 -0
- package/dist/root-directory-list.d.ts.map +1 -1
- package/dist/root-directory-list.js +1 -0
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +46 -17
- package/dist/root-move-noreplace.d.ts.map +1 -1
- package/dist/root-move-noreplace.js +24 -15
- package/dist/root-options.d.ts +12 -4
- package/dist/root-options.d.ts.map +1 -1
- package/dist/root-path-errors.d.ts +1 -0
- package/dist/root-path-errors.d.ts.map +1 -1
- package/dist/root-path-errors.js +11 -2
- package/dist/root-path-existing.d.ts.map +1 -1
- package/dist/root-path-existing.js +11 -35
- package/dist/root-path-stat.d.ts.map +1 -1
- package/dist/root-path-stat.js +59 -7
- package/dist/root-path.js +1 -13
- package/dist/root-remove.d.ts +1 -0
- package/dist/root-remove.d.ts.map +1 -1
- package/dist/root-remove.js +4 -0
- package/dist/root-walk.d.ts +1 -1
- package/dist/root-walk.d.ts.map +1 -1
- package/dist/root-walk.js +17 -2
- package/dist/root-write-admission.d.ts +0 -2
- package/dist/root-write-admission.d.ts.map +1 -1
- package/dist/root-write-admission.js +1 -15
- package/dist/root-write-complete-parent.d.ts.map +1 -1
- package/dist/root-write-complete-parent.js +7 -23
- package/dist/root-write-publication.js +1 -1
- package/dist/root-write-verification.d.ts.map +1 -1
- package/dist/root-write-verification.js +29 -42
- package/dist/secret-file.d.ts.map +1 -1
- package/dist/secret-file.js +3 -24
- package/dist/secret-read-async.d.ts.map +1 -1
- package/dist/secret-read-async.js +3 -24
- package/dist/secret-read-policy.d.ts +6 -2
- package/dist/secret-read-policy.d.ts.map +1 -1
- package/dist/secret-read-policy.js +26 -2
- package/dist/secure-file-windows.d.ts +6 -0
- package/dist/secure-file-windows.d.ts.map +1 -1
- package/dist/secure-file-windows.js +34 -117
- package/dist/secure-file.js +2 -2
- package/dist/sibling-temp.d.ts.map +1 -1
- package/dist/sibling-temp.js +23 -12
- package/dist/sidecar-lock-acquire.d.ts +2 -28
- package/dist/sidecar-lock-acquire.d.ts.map +1 -1
- package/dist/sidecar-lock-acquire.js +288 -199
- package/dist/sidecar-lock-admission-context.d.ts +19 -0
- package/dist/sidecar-lock-admission-context.d.ts.map +1 -0
- package/dist/sidecar-lock-admission-context.js +60 -0
- package/dist/sidecar-lock-admission-parser.d.ts +43 -0
- package/dist/sidecar-lock-admission-parser.d.ts.map +1 -0
- package/dist/sidecar-lock-admission-parser.js +113 -0
- package/dist/sidecar-lock-admission.d.ts +35 -0
- package/dist/sidecar-lock-admission.d.ts.map +1 -0
- package/dist/sidecar-lock-admission.js +7 -0
- package/dist/sidecar-lock-reclaim.d.ts +9 -4
- package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
- package/dist/sidecar-lock-reclaim.js +80 -25
- package/dist/sidecar-lock-root.d.ts.map +1 -1
- package/dist/sidecar-lock-root.js +2 -1
- package/dist/sidecar-lock-stale-admission.d.ts +39 -0
- package/dist/sidecar-lock-stale-admission.d.ts.map +1 -0
- package/dist/sidecar-lock-stale-admission.js +232 -0
- package/dist/sidecar-lock-target.d.ts +8 -0
- package/dist/sidecar-lock-target.d.ts.map +1 -0
- package/dist/sidecar-lock-target.js +55 -0
- package/dist/sidecar-lock.d.ts.map +1 -1
- package/dist/sidecar-lock.js +100 -16
- package/dist/staged-directory.d.ts.map +1 -1
- package/dist/staged-directory.js +6 -6
- package/dist/staged-file-settlement.d.ts +17 -0
- package/dist/staged-file-settlement.d.ts.map +1 -0
- package/dist/staged-file-settlement.js +57 -0
- package/dist/temp-workspace-descriptor.d.ts.map +1 -1
- package/dist/temp-workspace-descriptor.js +9 -27
- package/dist/temp-workspace-owner.d.ts.map +1 -1
- package/dist/temp-workspace-owner.js +8 -8
- package/dist/walk.d.ts +5 -1
- package/dist/walk.d.ts.map +1 -1
- package/dist/walk.js +19 -6
- package/dist/windows-owner.d.ts.map +1 -1
- package/dist/windows-owner.js +4 -3
- package/dist/windows-security-bridge.cs +336 -0
- package/dist/windows-security-bridge.ps1 +15 -0
- package/dist/windows-security-command.d.ts +26 -0
- package/dist/windows-security-command.d.ts.map +1 -0
- package/dist/windows-security-command.js +363 -0
- package/dist/windows-security-facts.d.ts +6 -0
- package/dist/windows-security-facts.d.ts.map +1 -0
- package/dist/windows-security-facts.js +108 -0
- package/docs/advanced.md +4 -2
- package/docs/archive.md +97 -46
- package/docs/atomic.md +85 -8
- package/docs/config.md +6 -2
- package/docs/contributing.md +44 -4
- package/docs/copy.md +37 -0
- package/docs/creation.md +128 -0
- package/docs/durability.md +24 -0
- package/docs/file-store.md +19 -0
- package/docs/install.md +31 -7
- package/docs/json-store.md +5 -0
- package/docs/migrating-to-0.5.md +15 -6
- package/docs/migrating-to-0.6.md +9 -4
- package/docs/native-helper.md +32 -12
- package/docs/native.md +38 -7
- package/docs/output.md +6 -0
- package/docs/path-prefix.md +10 -0
- package/docs/path-suffix-aliases.md +51 -6
- package/docs/permissions.md +50 -14
- package/docs/public-api.md +3 -2
- package/docs/root.md +56 -3
- package/docs/secret-file.md +11 -2
- package/docs/secure-file.md +9 -4
- package/docs/sidecar-lock.md +114 -8
- package/docs/staged-file.md +12 -3
- package/docs/temp.md +20 -3
- package/docs/walk.md +67 -1
- package/docs/writing.md +76 -6
- package/package.json +19 -16
- package/dist/darwin-acl.d.ts +0 -4
- package/dist/darwin-acl.d.ts.map +0 -1
- package/dist/darwin-acl.js +0 -24
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import fsSync, {} from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { assertDirectoryIdentitySync, inspectDirectoryIdentitySync, } from "./directory-guard.js";
|
|
4
|
+
import { FsSafeError } from "./errors.js";
|
|
5
|
+
import { openNativeParentAdmission } from "./native-parent-admission.js";
|
|
6
|
+
import { captureNativeFdClose } from "./native-binding.js";
|
|
7
|
+
import { inspectNativeDirectoryObservation } from "./native-directory-observation.js";
|
|
8
|
+
import { createPathSegmentRoute, joinPathSegmentRoute } from "./path-segment-route.js";
|
|
9
|
+
import { isSymlinkOpenError } from "./path.js";
|
|
10
|
+
import { checkedMutationDirectory } from "./pinned-mutation-observation.js";
|
|
11
|
+
import { canReuseParentWithMutationAssertion } from "./root-write-lock-binding.js";
|
|
12
|
+
import { inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
13
|
+
import { createSuppressedError } from "./suppressed-error.js";
|
|
14
|
+
function assertParentCurrent(parent) {
|
|
15
|
+
const current = inspectFileIdentitySync(() => fsSync.fstatSync(parent.fd, { bigint: true }), parent.guard.stat);
|
|
16
|
+
assertDirectoryIdentitySync(parent.guard.dir, {
|
|
17
|
+
dev: parent.guard.stat.dev,
|
|
18
|
+
ino: parent.guard.stat.ino,
|
|
19
|
+
realPath: parent.guard.realPath,
|
|
20
|
+
});
|
|
21
|
+
return current;
|
|
22
|
+
}
|
|
23
|
+
function closeAfterFailure(closeFd, fd, failure, report = false) {
|
|
24
|
+
if (fd === undefined)
|
|
25
|
+
return;
|
|
26
|
+
try {
|
|
27
|
+
closeFd(fd);
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
if (report)
|
|
31
|
+
throw failure
|
|
32
|
+
? createSuppressedError(error, failure.error, "native parent admission and close failed")
|
|
33
|
+
: error;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export async function capturePolicyAwareWindowsParent(binding, params, rootAdmission) {
|
|
37
|
+
const closeFd = captureNativeFdClose(binding);
|
|
38
|
+
let session;
|
|
39
|
+
function parentObservation(fd, guard) {
|
|
40
|
+
return checkedMutationDirectory(guard.dir, guard.realPath, guard.stat, typeof binding.observeDirectory === "function" ? () => {
|
|
41
|
+
const stat = inspectFileIdentitySync(() => fsSync.fstatSync(fd, { bigint: true }), guard.stat);
|
|
42
|
+
let observed;
|
|
43
|
+
try {
|
|
44
|
+
observed = inspectNativeDirectoryObservation(binding, guard.dir, stat);
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
if (error?.code === "OBSERVATION_UNAVAILABLE")
|
|
48
|
+
return undefined;
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
return { canonicalPath: observed.realPath,
|
|
52
|
+
identity: { dev: stat.dev, ino: stat.ino, mode: stat.mode, nlink: stat.nlink } };
|
|
53
|
+
} : undefined);
|
|
54
|
+
}
|
|
55
|
+
async function openParent(fd, parentPath, relativePath) {
|
|
56
|
+
try {
|
|
57
|
+
const admitted = await openNativeParentAdmission(binding, {
|
|
58
|
+
...rootAdmission,
|
|
59
|
+
root: { fd },
|
|
60
|
+
rootPath: parentPath,
|
|
61
|
+
// Policy fences retain every identity bit even with a legacy numeric root.
|
|
62
|
+
exactRoot: true,
|
|
63
|
+
}, relativePath, "native-directory");
|
|
64
|
+
const guard = { ...admitted.guard, stat: admitted.guard.stat };
|
|
65
|
+
return { fd: admitted.fd, guard, observation: parentObservation(admitted.fd, guard) };
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
if (!isSymlinkOpenError(error))
|
|
69
|
+
throw error;
|
|
70
|
+
throw new FsSafeError(params.mutationAdmission.rejectParentSymlinks ? "symlink" : "path-mismatch", "native write parent changed during policy admission", { cause: error instanceof Error ? error : undefined });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
async function authorize(targetPath, mutationPath = targetPath, phase = "parent") {
|
|
74
|
+
const request = Object.freeze({ targetPath, mutationPath, phase });
|
|
75
|
+
return await (session ? session.authorize(request) : params.mutationAdmission.authorize(request));
|
|
76
|
+
}
|
|
77
|
+
// Existing complete parents need one open and one target admission. Exact
|
|
78
|
+
// parent denies apply only to creation, never to merely using a directory.
|
|
79
|
+
let complete;
|
|
80
|
+
try {
|
|
81
|
+
complete = await openParent(rootAdmission.root.fd, params.rootPath, params.relativeParentPath);
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
if (error?.code !== "ENOENT" || !params.mkdir)
|
|
85
|
+
throw error;
|
|
86
|
+
}
|
|
87
|
+
if (complete) {
|
|
88
|
+
try {
|
|
89
|
+
await authorize(path.join(complete.guard.realPath, params.basename));
|
|
90
|
+
assertParentCurrent(complete);
|
|
91
|
+
return complete;
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
closeAfterFailure(closeFd, complete.fd, { error }, rootAdmission.reportCloseErrors);
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
const segments = params.relativeParentPath.split("/").filter(Boolean);
|
|
99
|
+
const route = createPathSegmentRoute(segments);
|
|
100
|
+
const rootStat = inspectDirectoryIdentitySync(params.rootPath, inspectFileIdentitySync(() => fsSync.fstatSync(rootAdmission.root.fd, { bigint: true })));
|
|
101
|
+
let current = {
|
|
102
|
+
fd: rootAdmission.root.fd,
|
|
103
|
+
guard: { dir: params.rootPath, realPath: params.rootPath, stat: rootStat },
|
|
104
|
+
observation: parentObservation(rootAdmission.root.fd, {
|
|
105
|
+
dir: params.rootPath, realPath: params.rootPath, stat: rootStat,
|
|
106
|
+
}),
|
|
107
|
+
};
|
|
108
|
+
let ownedFd;
|
|
109
|
+
let failure;
|
|
110
|
+
const initialTarget = joinPathSegmentRoute(params.rootPath, route, 0, params.basename);
|
|
111
|
+
if (canReuseParentWithMutationAssertion(params.assertBeforeMutation, params.rootPath, initialTarget)) {
|
|
112
|
+
session = params.mutationAdmission?.beginNativeParentWalk?.() ??
|
|
113
|
+
params.mutationAdmission?.beginSharedParentWalk?.();
|
|
114
|
+
if (session && session.retainedTargetPath !== initialTarget) {
|
|
115
|
+
session.dispose();
|
|
116
|
+
session = undefined;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
try {
|
|
120
|
+
for (let index = 0; index < segments.length; index += 1) {
|
|
121
|
+
const segment = segments[index];
|
|
122
|
+
const parentPath = current.guard.realPath;
|
|
123
|
+
let child;
|
|
124
|
+
let createdByMkdir = false;
|
|
125
|
+
let createReceipt;
|
|
126
|
+
try {
|
|
127
|
+
child = await openParent(current.fd, parentPath, segment);
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
if (error?.code !== "ENOENT")
|
|
131
|
+
throw error;
|
|
132
|
+
// The request admits both the prospective leaf and this missing direct
|
|
133
|
+
// child. A deeper denial can leave already admitted ancestors intact.
|
|
134
|
+
const targetPath = session?.retainedTargetPath ??
|
|
135
|
+
joinPathSegmentRoute(parentPath, route, index, params.basename);
|
|
136
|
+
const mutationPath = path.join(parentPath, segment);
|
|
137
|
+
const request = Object.freeze({ targetPath, mutationPath, phase: "parent-create" });
|
|
138
|
+
createReceipt = session?.tryAuthorizeAtParent(request, current.observation);
|
|
139
|
+
if (!createReceipt) {
|
|
140
|
+
createReceipt = await authorize(targetPath, mutationPath, "parent-create");
|
|
141
|
+
assertParentCurrent(current);
|
|
142
|
+
}
|
|
143
|
+
if (params.assertBeforeMutation) {
|
|
144
|
+
// Opaque callbacks retain their post-callback descriptor/path fence.
|
|
145
|
+
params.assertBeforeMutation();
|
|
146
|
+
assertParentCurrent(current);
|
|
147
|
+
}
|
|
148
|
+
// Synchronous session admission already ends at the exact parent fence.
|
|
149
|
+
const mkdirChild = binding.mkdirChildBeneath;
|
|
150
|
+
if (typeof mkdirChild !== "function") {
|
|
151
|
+
throw new FsSafeError("helper-unavailable", "native direct-child parent creation is unavailable");
|
|
152
|
+
}
|
|
153
|
+
createdByMkdir = mkdirChild.call(binding, current.fd, segment, 0o777) === true;
|
|
154
|
+
child = await openParent(current.fd, parentPath, segment);
|
|
155
|
+
}
|
|
156
|
+
try {
|
|
157
|
+
const targetPath = joinPathSegmentRoute(child.guard.realPath, route, index + 1, params.basename);
|
|
158
|
+
if (session && targetPath !== session.retainedTargetPath) {
|
|
159
|
+
session.dispose();
|
|
160
|
+
session = undefined;
|
|
161
|
+
}
|
|
162
|
+
let evidence;
|
|
163
|
+
if (session && createdByMkdir && createReceipt) {
|
|
164
|
+
try {
|
|
165
|
+
evidence = Object.freeze({
|
|
166
|
+
admission: createReceipt,
|
|
167
|
+
parent: parentObservation(current.fd, { ...current.guard,
|
|
168
|
+
stat: inspectFileIdentitySync(() => fsSync.fstatSync(current.fd, { bigint: true }), current.guard.stat),
|
|
169
|
+
}),
|
|
170
|
+
child: child.observation,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
// Incomplete optional evidence returns to ordered admission below.
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
// Advancement refreshes both named directories after all Root/policy
|
|
178
|
+
// observations; these receipts supply expectations, not cached authority.
|
|
179
|
+
const advanced = evidence && session.advanceCreatedDirectory(evidence);
|
|
180
|
+
if (!advanced) {
|
|
181
|
+
await authorize(targetPath);
|
|
182
|
+
assertParentCurrent(child);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
closeAfterFailure(closeFd, child.fd, { error }, rootAdmission.reportCloseErrors);
|
|
187
|
+
throw error;
|
|
188
|
+
}
|
|
189
|
+
const previousFd = ownedFd;
|
|
190
|
+
current = child;
|
|
191
|
+
ownedFd = child.fd;
|
|
192
|
+
if (previousFd !== undefined)
|
|
193
|
+
closeFd(previousFd);
|
|
194
|
+
}
|
|
195
|
+
if (ownedFd === undefined) {
|
|
196
|
+
throw new FsSafeError("path-mismatch", "native write parent admission did not complete");
|
|
197
|
+
}
|
|
198
|
+
ownedFd = undefined;
|
|
199
|
+
return current;
|
|
200
|
+
}
|
|
201
|
+
catch (error) {
|
|
202
|
+
failure = { error };
|
|
203
|
+
throw error;
|
|
204
|
+
}
|
|
205
|
+
finally {
|
|
206
|
+
session?.dispose();
|
|
207
|
+
closeAfterFailure(closeFd, ownedFd, failure, rootAdmission.reportCloseErrors);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare const NATIVE_RENAME_SOURCE_IDENTITY_MISMATCH = "FS_SAFE_INTERNAL_RENAME_SOURCE_IDENTITY_MISMATCH";
|
|
2
|
+
export type NativeRenameFailureOutcome = "uncommitted" | "indeterminate";
|
|
3
|
+
export declare function classifyNativeRenameFailure(error: unknown): NativeRenameFailureOutcome;
|
|
4
|
+
//# sourceMappingURL=native-rename-outcome.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"native-rename-outcome.d.ts","sourceRoot":"","sources":["../src/native-rename-outcome.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,sCAAsC,qDACC,CAAC;AAErD,MAAM,MAAM,0BAA0B,GAAG,aAAa,GAAG,eAAe,CAAC;AAEzE,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,OAAO,GAAG,0BAA0B,CAMtF"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export const NATIVE_RENAME_SOURCE_IDENTITY_MISMATCH = "FS_SAFE_INTERNAL_RENAME_SOURCE_IDENTITY_MISMATCH";
|
|
2
|
+
export function classifyNativeRenameFailure(error) {
|
|
3
|
+
const code = error?.code ?? "";
|
|
4
|
+
// Ordinary errno can follow a committed remote rename whose reply was lost.
|
|
5
|
+
return code === NATIVE_RENAME_SOURCE_IDENTITY_MISMATCH
|
|
6
|
+
? "uncommitted"
|
|
7
|
+
: "indeterminate";
|
|
8
|
+
}
|
|
@@ -6,10 +6,11 @@ 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
|
+
type StagedPermissionPolicy = "private-creation" | "mode-only";
|
|
9
10
|
declare class NativeStagedFile implements StagedFile {
|
|
10
11
|
#private;
|
|
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>;
|
|
12
|
+
constructor(binding: NativeStagingBinding, parentFd: number, closeParentFd: (fd: number) => void, directory: StagedFileReceipt["directory"], portableNames: boolean, publishedMode: number, sync: boolean, assertBeforeMutation?: () => void, name?: string, strictFileSync?: boolean, permissionPolicy?: StagedPermissionPolicy);
|
|
13
|
+
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, exclusiveBasename?: string, strictFileSync?: boolean, permissionPolicy?: StagedPermissionPolicy): Promise<NativeStagedFile>;
|
|
13
14
|
static write(binding: NativeStagingBinding, parentFd: number, closeParentFd: (fd: number) => void, directory: StagedFileReceipt["directory"], params: PinnedWriteParams, parentGuard: AnyAsyncDirectoryGuard): Promise<FileIdentityStat>;
|
|
14
15
|
get receipt(): StagedFileReceipt;
|
|
15
16
|
assertCurrent(): Promise<void>;
|
|
@@ -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;
|
|
1
|
+
{"version":3,"file":"native-staged-file.d.ts","sourceRoot":"","sources":["../src/native-staged-file.ts"],"names":[],"mappings":"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"}
|
|
@@ -50,18 +50,22 @@ 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 { assertDarwinCreationAcl, privateFileMutationAssertion } from "./creation-darwin.js";
|
|
54
|
+
import { assertPrivateCreationFile } from "./creation-file-state.js";
|
|
53
55
|
import { requireNativeBinding } from "./native.js";
|
|
54
56
|
import { syncFileBestEffortSync } from "./file-sync.js";
|
|
55
57
|
import { randomUUID } from "node:crypto";
|
|
56
58
|
import fs from "node:fs";
|
|
57
59
|
import path from "node:path";
|
|
58
60
|
import { FsSafeError } from "./errors.js";
|
|
59
|
-
import { MutationAuthorityError } from "./mutation-authority.js";
|
|
61
|
+
import { assertSynchronousCallbackResult, MutationAuthorityError } from "./mutation-authority.js";
|
|
60
62
|
import { captureNativeFdClose } from "./native-binding.js";
|
|
61
|
-
import {
|
|
63
|
+
import { writePinnedInput } from "./pinned-write-input.js";
|
|
62
64
|
import { assertNativeCopyCompleted, createNativeCopyFile } from "./copy-file-input.js";
|
|
63
65
|
import { assertStagedDirectoryCurrent, openStagedDirectory } from "./staged-directory.js";
|
|
64
66
|
import { assertFinalSymlinkRejected } from "./root-symlink-policy.js";
|
|
67
|
+
import { classifyNativeRenameFailure } from "./native-rename-outcome.js";
|
|
68
|
+
import { createStagedFileReceipt, stagedFileFailure as failure } from "./staged-file-settlement.js";
|
|
65
69
|
export function assertNativeStaging(binding) {
|
|
66
70
|
if ([
|
|
67
71
|
binding.closeOwnedFd,
|
|
@@ -81,17 +85,6 @@ function assertBasename(name, portable) {
|
|
|
81
85
|
}
|
|
82
86
|
}
|
|
83
87
|
const NOT_PUBLISHED = Object.freeze({ status: "not-published" });
|
|
84
|
-
const UNCOMMITTED_RENAME_ERRORS = new Set([
|
|
85
|
-
"EACCES", "EBADF", "EBUSY", "EEXIST", "EINVAL", "EISDIR", "ELOOP", "EMLINK",
|
|
86
|
-
"ENAMETOOLONG", "ENOENT", "ENOSPC", "ENOSYS", "ENOTDIR", "ENOTEMPTY", "ENOTSUP",
|
|
87
|
-
"EPERM", "EROFS", "ETXTBSY", "EXDEV",
|
|
88
|
-
]);
|
|
89
|
-
function failure(error, details) {
|
|
90
|
-
const code = error instanceof FsSafeError
|
|
91
|
-
? error.code
|
|
92
|
-
: error?.code === "EEXIST" ? "already-exists" : "helper-failed";
|
|
93
|
-
return new FsSafeError(code, `staged file ${details.phase} failed`, { cause: error, details });
|
|
94
|
-
}
|
|
95
88
|
class NativeStagedFile {
|
|
96
89
|
#binding;
|
|
97
90
|
#closeFd;
|
|
@@ -101,12 +94,17 @@ class NativeStagedFile {
|
|
|
101
94
|
#portableNames;
|
|
102
95
|
#publishedMode;
|
|
103
96
|
#sync;
|
|
97
|
+
#strictFileSync;
|
|
98
|
+
#private;
|
|
99
|
+
#verifyMode;
|
|
104
100
|
#assertBeforeMutation;
|
|
105
|
-
#name
|
|
101
|
+
#name;
|
|
106
102
|
#state = { status: "open", publication: NOT_PUBLISHED };
|
|
107
103
|
#receipt;
|
|
108
104
|
#rejectFinalSymlink = false;
|
|
109
|
-
constructor(binding, parentFd, closeParentFd, directory, portableNames, publishedMode, sync, assertBeforeMutation) {
|
|
105
|
+
constructor(binding, parentFd, closeParentFd, directory, portableNames, publishedMode, sync, assertBeforeMutation, name = `.fs-safe-${randomUUID()}.tmp`, strictFileSync = false, permissionPolicy) {
|
|
106
|
+
assertBasename(name, portableNames);
|
|
107
|
+
this.#name = name;
|
|
110
108
|
this.#closeFd = captureNativeFdClose(binding);
|
|
111
109
|
this.#binding = binding;
|
|
112
110
|
this.#parentFd = parentFd;
|
|
@@ -115,16 +113,19 @@ class NativeStagedFile {
|
|
|
115
113
|
this.#portableNames = portableNames;
|
|
116
114
|
this.#publishedMode = publishedMode;
|
|
117
115
|
this.#sync = sync;
|
|
116
|
+
this.#strictFileSync = strictFileSync;
|
|
117
|
+
this.#private = permissionPolicy === "private-creation";
|
|
118
|
+
this.#verifyMode = permissionPolicy !== undefined;
|
|
118
119
|
this.#assertBeforeMutation = assertBeforeMutation;
|
|
119
120
|
}
|
|
120
121
|
// Public staging supplies a Node parent; pinned writes supply a native parent.
|
|
121
122
|
// The supplied closer owns parentFd even on construction or preparation failure.
|
|
122
123
|
static async create(binding, parentFd, closeParentFd, directory, input, mode, maxBytes,
|
|
123
124
|
// Public staging uses portable names; existing POSIX writes accept literal names.
|
|
124
|
-
portableNames = true, sync = true, assertBeforeMutation) {
|
|
125
|
+
portableNames = true, sync = true, assertBeforeMutation, exclusiveBasename, strictFileSync = false, permissionPolicy) {
|
|
125
126
|
let staged;
|
|
126
127
|
try {
|
|
127
|
-
staged = new NativeStagedFile(binding, parentFd, closeParentFd, directory, portableNames, mode, sync, assertBeforeMutation);
|
|
128
|
+
staged = new NativeStagedFile(binding, parentFd, closeParentFd, directory, portableNames, mode, sync, assertBeforeMutation, exclusiveBasename, strictFileSync, permissionPolicy);
|
|
128
129
|
}
|
|
129
130
|
catch (error) {
|
|
130
131
|
try {
|
|
@@ -141,15 +142,32 @@ class NativeStagedFile {
|
|
|
141
142
|
static async write(binding, parentFd, closeParentFd, directory, params, parentGuard) {
|
|
142
143
|
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
143
144
|
try {
|
|
145
|
+
const exclusive = params.overwrite === false && params.input.kind === "buffer" && params.input.stageBeforePublish === false;
|
|
144
146
|
// This owner never escapes. Only the internal verifier borrows its fd;
|
|
145
147
|
// public descriptor methods remain await-free and cannot race disposal.
|
|
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);
|
|
148
|
+
const staged = __addDisposableResource(env_1, await NativeStagedFile.create(binding, parentFd, closeParentFd, directory, params.input, params.mode, params.maxBytes, false, params.sync, params.assertBeforeMutation, exclusive ? params.basename : undefined, params.strictFileSync, params.private ? "private-creation" : params.verifyPosixMode === true ? "mode-only" : undefined), true);
|
|
147
149
|
staged.#rejectFinalSymlink = params.rejectFinalSymlink === true;
|
|
148
150
|
if (params.input.kind === "file")
|
|
149
151
|
await params.input.verifySource();
|
|
150
|
-
|
|
152
|
+
if (exclusive) {
|
|
153
|
+
staged.#assertCurrent();
|
|
154
|
+
params.assertBeforeMutation?.();
|
|
155
|
+
if (staged.#verifyMode)
|
|
156
|
+
staged.#assertCurrent();
|
|
157
|
+
}
|
|
158
|
+
const published = exclusive
|
|
159
|
+
? staged.#completePublication(params.basename, false, params.onPublished)
|
|
160
|
+
: await staged.publish(params.basename, { overwrite: params.overwrite !== false }, params.onPublished);
|
|
151
161
|
const identity = published.staged.identity;
|
|
152
|
-
|
|
162
|
+
try {
|
|
163
|
+
await params.verifyPublished?.(staged.#file(), identity, parentGuard);
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
if (params.overwrite === false && params.input.kind !== "file" && params.input.stageBeforePublish === true) {
|
|
167
|
+
throw failure(error, { phase: "publish", publication: published });
|
|
168
|
+
}
|
|
169
|
+
throw error;
|
|
170
|
+
}
|
|
153
171
|
return { dev: identity.dev, ino: identity.ino };
|
|
154
172
|
}
|
|
155
173
|
catch (e_1) {
|
|
@@ -181,13 +199,14 @@ class NativeStagedFile {
|
|
|
181
199
|
}
|
|
182
200
|
return state.fileFd;
|
|
183
201
|
}
|
|
184
|
-
#assertNamed(name) {
|
|
202
|
+
#assertNamed(name, expectedMode) {
|
|
185
203
|
const fd = this.#file();
|
|
186
204
|
const stat = this.#binding.stagedFileMatches(this.#parentFd, name, fd)
|
|
187
205
|
? fs.fstatSync(fd, { bigint: true }) : undefined;
|
|
188
206
|
if (!stat || stat.nlink !== 1n) {
|
|
189
207
|
throw new FsSafeError("path-mismatch", "staged entry no longer names the exclusive created file");
|
|
190
208
|
}
|
|
209
|
+
this.#assertPermissions(fd, stat, expectedMode);
|
|
191
210
|
return Number(stat.mode & 4095n);
|
|
192
211
|
}
|
|
193
212
|
#assertCurrent() {
|
|
@@ -195,7 +214,17 @@ class NativeStagedFile {
|
|
|
195
214
|
throw new FsSafeError("helper-failed", "staged file publication has already been attempted");
|
|
196
215
|
}
|
|
197
216
|
assertStagedDirectoryCurrent(this.#directory);
|
|
198
|
-
this.#assertNamed(this.#name);
|
|
217
|
+
this.#assertNamed(this.#name, 0o600);
|
|
218
|
+
}
|
|
219
|
+
#assertPermissions(fd, stat, expectedMode) {
|
|
220
|
+
if (this.#private)
|
|
221
|
+
assertPrivateCreationFile(stat, fd);
|
|
222
|
+
if (this.#verifyMode && Number(stat.mode & 4095n) !== expectedMode) {
|
|
223
|
+
throw new FsSafeError("insecure-permissions", "filesystem did not enforce the staged file mode");
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
#assertStagePermissions(fd) {
|
|
227
|
+
this.#assertPermissions(fd, fs.fstatSync(fd, { bigint: true }), 0o600);
|
|
199
228
|
}
|
|
200
229
|
async #prepare(input, maxBytes) {
|
|
201
230
|
try {
|
|
@@ -203,44 +232,60 @@ class NativeStagedFile {
|
|
|
203
232
|
if (!Number.isInteger(mode) || mode < 0 || mode > 0o7777) {
|
|
204
233
|
throw new FsSafeError("invalid-path", "invalid staged file mode");
|
|
205
234
|
}
|
|
235
|
+
const fileSignal = input.kind === "file" && this.#verifyMode ? input.signal : undefined;
|
|
236
|
+
if (input.kind === "file" && this.#verifyMode) {
|
|
237
|
+
fileSignal?.throwIfAborted();
|
|
238
|
+
if (input.clone === "always") {
|
|
239
|
+
throw new FsSafeError("helper-unavailable", "required cloning cannot verify staged permissions before copying");
|
|
240
|
+
}
|
|
241
|
+
}
|
|
206
242
|
assertStagedDirectoryCurrent(this.#directory);
|
|
207
243
|
// The exclusive open performs no fallible post-open checks. Store its fd
|
|
208
244
|
// before every subsequent operation, including the first metadata read.
|
|
209
245
|
const state = this.#open();
|
|
246
|
+
if (this.#private)
|
|
247
|
+
assertDarwinCreationAcl(this.#parentFd, "file");
|
|
210
248
|
this.#assertBeforeMutation?.();
|
|
211
|
-
|
|
249
|
+
if (this.#private)
|
|
250
|
+
assertDarwinCreationAcl(this.#parentFd, "file");
|
|
251
|
+
// Native copying populates its new file before returning its descriptor.
|
|
252
|
+
const copied = input.kind === "file" && !this.#verifyMode
|
|
212
253
|
? await createNativeCopyFile(this.#binding, input, this.#parentFd, this.#name, maxBytes, false)
|
|
213
254
|
: undefined;
|
|
214
255
|
state.fileFd = copied?.fd;
|
|
215
256
|
if (state.fileFd === undefined) {
|
|
216
257
|
this.#assertBeforeMutation?.();
|
|
258
|
+
if (this.#private)
|
|
259
|
+
assertDarwinCreationAcl(this.#parentFd, "file");
|
|
217
260
|
state.fileFd = this.#binding.createStagedFile(this.#parentFd, this.#name);
|
|
218
261
|
}
|
|
219
262
|
const fd = state.fileFd;
|
|
220
263
|
if (input.kind === "file")
|
|
221
264
|
assertNativeCopyCompleted(input, copied);
|
|
222
|
-
this.#
|
|
265
|
+
const assertBeforeChmod = this.#private
|
|
266
|
+
? privateFileMutationAssertion(fd, this.#assertBeforeMutation) : this.#assertBeforeMutation;
|
|
267
|
+
assertBeforeChmod?.();
|
|
223
268
|
fs.fchmodSync(fd, 0o600);
|
|
269
|
+
// Some filesystems report successful chmod without enforcing its mode.
|
|
270
|
+
if (this.#verifyMode)
|
|
271
|
+
this.#assertStagePermissions(fd);
|
|
272
|
+
const assertBeforeMutation = this.#verifyMode ? () => {
|
|
273
|
+
assertSynchronousCallbackResult(this.#assertBeforeMutation?.(), "assertBeforeMutation");
|
|
274
|
+
fileSignal?.throwIfAborted();
|
|
275
|
+
this.#assertStagePermissions(fd);
|
|
276
|
+
} : this.#assertBeforeMutation;
|
|
224
277
|
if (!copied)
|
|
225
|
-
await
|
|
226
|
-
if (this.#
|
|
227
|
-
|
|
278
|
+
await writePinnedInput(fd, input, maxBytes, assertBeforeMutation);
|
|
279
|
+
if (this.#verifyMode)
|
|
280
|
+
this.#assertStagePermissions(fd);
|
|
281
|
+
if (this.#sync) {
|
|
282
|
+
if (this.#strictFileSync)
|
|
283
|
+
fs.fsyncSync(fd);
|
|
284
|
+
else
|
|
285
|
+
syncFileBestEffortSync(fd);
|
|
286
|
+
}
|
|
228
287
|
const stat = fs.fstatSync(fd, { bigint: true });
|
|
229
|
-
this.#receipt =
|
|
230
|
-
directory: this.#directory,
|
|
231
|
-
temporaryBasename: this.#name,
|
|
232
|
-
identity: Object.freeze({
|
|
233
|
-
dev: stat.dev,
|
|
234
|
-
ino: stat.ino,
|
|
235
|
-
mode: Number(stat.mode & 4095n),
|
|
236
|
-
nlink: stat.nlink,
|
|
237
|
-
size: stat.size,
|
|
238
|
-
uid: Number(stat.uid),
|
|
239
|
-
gid: Number(stat.gid),
|
|
240
|
-
mtimeNs: stat.mtimeNs,
|
|
241
|
-
ctimeNs: stat.ctimeNs,
|
|
242
|
-
}),
|
|
243
|
-
});
|
|
288
|
+
this.#receipt = createStagedFileReceipt(this.#directory, this.#name, stat);
|
|
244
289
|
this.#assertCurrent();
|
|
245
290
|
}
|
|
246
291
|
catch (error) {
|
|
@@ -270,6 +315,10 @@ class NativeStagedFile {
|
|
|
270
315
|
this.#assertCurrent();
|
|
271
316
|
assertFinalSymlinkRejected(path.join(this.#directory.realPath, basename), this.#rejectFinalSymlink);
|
|
272
317
|
this.#assertBeforeMutation?.();
|
|
318
|
+
if (this.#assertBeforeMutation) {
|
|
319
|
+
this.#assertCurrent();
|
|
320
|
+
assertFinalSymlinkRejected(path.join(this.#directory.realPath, basename), this.#rejectFinalSymlink);
|
|
321
|
+
}
|
|
273
322
|
try {
|
|
274
323
|
if (overwrite) {
|
|
275
324
|
this.#binding.renameReplace(this.#parentFd, this.#name, this.#parentFd, basename);
|
|
@@ -279,39 +328,13 @@ class NativeStagedFile {
|
|
|
279
328
|
}
|
|
280
329
|
}
|
|
281
330
|
catch (error) {
|
|
282
|
-
//
|
|
283
|
-
if (
|
|
331
|
+
// Only explicit pre-dispatch provenance can rule out a committed rename.
|
|
332
|
+
if (classifyNativeRenameFailure(error) === "indeterminate") {
|
|
284
333
|
state.publication = Object.freeze({ status: "indeterminate", basename, overwrite });
|
|
285
334
|
}
|
|
286
335
|
throw error;
|
|
287
336
|
}
|
|
288
|
-
|
|
289
|
-
const receipt = Object.freeze({
|
|
290
|
-
status: "published",
|
|
291
|
-
staged: this.receipt,
|
|
292
|
-
basename,
|
|
293
|
-
overwrite,
|
|
294
|
-
});
|
|
295
|
-
state.publication = receipt;
|
|
296
|
-
onPublished?.(receipt.staged.identity);
|
|
297
|
-
const stagedMode = this.#assertNamed(basename);
|
|
298
|
-
assertStagedDirectoryCurrent(this.#directory);
|
|
299
|
-
// Keep contents private until the published name passes its identity
|
|
300
|
-
// fence. Mode changes use the owned fd, including for final mode 000.
|
|
301
|
-
const fd = this.#file();
|
|
302
|
-
if (this.#publishedMode !== 0o600 || stagedMode !== 0o600)
|
|
303
|
-
fs.fchmodSync(fd, this.#publishedMode);
|
|
304
|
-
// With sync enabled, content was synced before rename. A lost chmod leaves staged 0600,
|
|
305
|
-
// no wider than modes retaining owner rw. Restrictive modes and observed
|
|
306
|
-
// widening of the stage still need the permission correction made durable.
|
|
307
|
-
if (this.#sync && ((this.#publishedMode & 0o600) !== 0o600 || (stagedMode & ~this.#publishedMode) !== 0)) {
|
|
308
|
-
syncFileBestEffortSync(fd);
|
|
309
|
-
}
|
|
310
|
-
if (this.#sync)
|
|
311
|
-
syncFileBestEffortSync(this.#parentFd);
|
|
312
|
-
this.#assertNamed(basename);
|
|
313
|
-
assertStagedDirectoryCurrent(this.#directory);
|
|
314
|
-
return receipt;
|
|
337
|
+
return this.#completePublication(basename, overwrite, onPublished);
|
|
315
338
|
}
|
|
316
339
|
catch (error) {
|
|
317
340
|
if (error instanceof MutationAuthorityError)
|
|
@@ -323,6 +346,32 @@ class NativeStagedFile {
|
|
|
323
346
|
throw failure(error, { phase: "publish", publication });
|
|
324
347
|
}
|
|
325
348
|
}
|
|
349
|
+
#completePublication(basename, overwrite, onPublished) {
|
|
350
|
+
// Record complete content before fallible post-publication verification.
|
|
351
|
+
const receipt = Object.freeze({ status: "published", staged: this.receipt, basename, overwrite });
|
|
352
|
+
this.#open().publication = receipt;
|
|
353
|
+
onPublished?.(receipt.staged.identity);
|
|
354
|
+
const stagedMode = this.#assertNamed(basename, 0o600);
|
|
355
|
+
assertStagedDirectoryCurrent(this.#directory);
|
|
356
|
+
// Keep contents private until the name passes its identity fence. Mode
|
|
357
|
+
// changes use the owned fd, including for final mode 000.
|
|
358
|
+
const fd = this.#file();
|
|
359
|
+
if (this.#publishedMode !== 0o600 || stagedMode !== 0o600)
|
|
360
|
+
fs.fchmodSync(fd, this.#publishedMode);
|
|
361
|
+
// Content was synced during preparation. A lost chmod leaves 0600, no wider
|
|
362
|
+
// than modes retaining owner rw; restrictive modes still need a durable correction.
|
|
363
|
+
if (this.#sync && ((this.#publishedMode & 0o600) !== 0o600 || (stagedMode & ~this.#publishedMode) !== 0)) {
|
|
364
|
+
if (this.#strictFileSync)
|
|
365
|
+
fs.fsyncSync(fd);
|
|
366
|
+
else
|
|
367
|
+
syncFileBestEffortSync(fd);
|
|
368
|
+
}
|
|
369
|
+
if (this.#sync)
|
|
370
|
+
syncFileBestEffortSync(this.#parentFd);
|
|
371
|
+
this.#assertNamed(basename, this.#publishedMode);
|
|
372
|
+
assertStagedDirectoryCurrent(this.#directory);
|
|
373
|
+
return receipt;
|
|
374
|
+
}
|
|
326
375
|
#finalize() {
|
|
327
376
|
if (this.#state.status === "closed") {
|
|
328
377
|
return this.#state;
|
|
@@ -395,5 +444,5 @@ export async function stageFileInDirectory(options) {
|
|
|
395
444
|
const input = { kind: "buffer", data: Buffer.from(options.content) };
|
|
396
445
|
const mode = options.mode ?? 0o600;
|
|
397
446
|
const parent = openStagedDirectory(options.directory);
|
|
398
|
-
return await createNativeStage(binding, parent.fd, fs.closeSync, parent.receipt, input, mode);
|
|
447
|
+
return await createNativeStage(binding, parent.fd, fs.closeSync, parent.receipt, input, mode, undefined, true, true, undefined, undefined, false, "mode-only");
|
|
399
448
|
}
|
package/dist/output.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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,
|
|
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,CA+DrC"}
|
package/dist/output.js
CHANGED
|
@@ -57,6 +57,10 @@ export async function writeExternalFileWithinRoot(options) {
|
|
|
57
57
|
const requestedTargetPath = options.path;
|
|
58
58
|
assertNoWindowsPathAlias(path.dirname(requestedTargetPath), "filesystem", "output target parent uses a Windows filesystem namespace alias");
|
|
59
59
|
const producerIsolation = options.producerIsolation;
|
|
60
|
+
const write = options.write;
|
|
61
|
+
const mode = options.mode;
|
|
62
|
+
const staging = options.staging;
|
|
63
|
+
const fallbackFileName = options.fallbackFileName;
|
|
60
64
|
const targetRoot = await root(rootDir);
|
|
61
65
|
if (requestedTargetPath.length === 0) {
|
|
62
66
|
throw new FsSafeError("invalid-path", "target path is required");
|
|
@@ -68,11 +72,11 @@ export async function writeExternalFileWithinRoot(options) {
|
|
|
68
72
|
targetPath: requestedTargetPath,
|
|
69
73
|
});
|
|
70
74
|
assertFileTargetPath(rawTargetPath);
|
|
71
|
-
const targetPath = sanitizedTargetPath(rawTargetPath,
|
|
75
|
+
const targetPath = sanitizedTargetPath(rawTargetPath, fallbackFileName);
|
|
72
76
|
assertNoWindowsPathAlias(targetPath, "filesystem", "output target uses a Windows filesystem namespace alias");
|
|
73
77
|
const finalPath = await targetRoot.resolve(targetPath);
|
|
74
78
|
assertNoWindowsPathAlias(finalPath, "filesystem", "output target uses a Windows filesystem namespace alias");
|
|
75
|
-
if (
|
|
79
|
+
if (staging === "sibling") {
|
|
76
80
|
const parentPath = path.dirname(targetPath);
|
|
77
81
|
if (parentPath !== ".") {
|
|
78
82
|
await targetRoot.mkdir(parentPath);
|
|
@@ -80,23 +84,23 @@ export async function writeExternalFileWithinRoot(options) {
|
|
|
80
84
|
const siblingFinalPath = await targetRoot.resolve(targetPath);
|
|
81
85
|
const result = await writeExternalFileViaSibling({
|
|
82
86
|
finalPath: siblingFinalPath,
|
|
83
|
-
write
|
|
87
|
+
write,
|
|
84
88
|
producerIsolation,
|
|
85
|
-
fallbackFileName
|
|
89
|
+
fallbackFileName,
|
|
86
90
|
maxBytes,
|
|
87
|
-
mode
|
|
91
|
+
mode,
|
|
88
92
|
});
|
|
89
93
|
return { path: siblingFinalPath, result };
|
|
90
94
|
}
|
|
91
95
|
const staged = await tempFile({
|
|
92
96
|
prefix: "fs-safe-output",
|
|
93
|
-
fileName: tempFileNameForTarget(targetPath,
|
|
97
|
+
fileName: tempFileNameForTarget(targetPath, fallbackFileName),
|
|
94
98
|
});
|
|
95
99
|
try {
|
|
96
|
-
const result = await
|
|
100
|
+
const result = await Function.prototype.call.call(write, options, staged.path);
|
|
97
101
|
await targetRoot.copyIn(targetPath, staged.path, {
|
|
98
102
|
maxBytes,
|
|
99
|
-
mode
|
|
103
|
+
mode,
|
|
100
104
|
mkdir: true,
|
|
101
105
|
sourceHardlinks: "reject",
|
|
102
106
|
});
|
package/dist/owner-dacl.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"owner-dacl.d.ts","sourceRoot":"","sources":["../src/owner-dacl.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"owner-dacl.d.ts","sourceRoot":"","sources":["../src/owner-dacl.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,eAAe,GAAG;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,aAAa,EAAE,OAAO,CAAC;IACvB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,kBAAkB,EAAE,OAAO,CAAC;IAC5B,WAAW,EAAE,OAAO,CAAC;IACrB,SAAS,EAAE,OAAO,CAAC;IACnB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,YAAY,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACtC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,GAAG,MAAM,CAAC;IAC1B,KAAK,EAAE,eAAe,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAC1B;IACE,MAAM,EAAE,WAAW,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,OAAO,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAC;IAClB,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B,IAAI,EAAE,yBAAyB,EAAE,CAAC;CACnC,GACD;IACE,MAAM,EAAE,sBAAsB,CAAC;IAC/B,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC;CAC3B,CAAC;AAEN,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,kBAAkB,CAmCvE"}
|