@openclaw/fs-safe 0.12.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +61 -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/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 +14 -0
- package/dist/native-binding.d.ts.map +1 -1
- package/dist/native-directory-observation.d.ts +17 -0
- package/dist/native-directory-observation.d.ts.map +1 -0
- package/dist/native-directory-observation.js +37 -0
- package/dist/native-parent-admission.d.ts +31 -0
- package/dist/native-parent-admission.d.ts.map +1 -0
- package/dist/native-parent-admission.js +124 -0
- package/dist/native-pinned-write-windows.d.ts +0 -1
- package/dist/native-pinned-write-windows.d.ts.map +1 -1
- package/dist/native-pinned-write-windows.js +0 -3
- package/dist/native-pinned-write.d.ts.map +1 -1
- package/dist/native-pinned-write.js +309 -40
- package/dist/output.d.ts.map +1 -1
- package/dist/output.js +15 -5
- package/dist/overwrite-file-handle.d.ts.map +1 -1
- package/dist/overwrite-file-handle.js +5 -1
- package/dist/owner-dacl.d.ts.map +1 -1
- package/dist/owner-dacl.js +2 -0
- package/dist/path-policy.d.ts.map +1 -1
- package/dist/path-policy.js +7 -2
- package/dist/path-prefix.d.ts +7 -0
- package/dist/path-prefix.d.ts.map +1 -0
- package/dist/path-prefix.js +82 -0
- package/dist/path-scope-lexical.d.ts.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 +33 -29
- package/dist/regular-file.d.ts.map +1 -1
- package/dist/regular-file.js +56 -45
- package/dist/replace-directory.d.ts.map +1 -1
- package/dist/replace-directory.js +12 -5
- package/dist/replace-file-temp-owner.d.ts +1 -0
- package/dist/replace-file-temp-owner.d.ts.map +1 -1
- package/dist/replace-file-temp-owner.js +12 -1
- package/dist/replace-file.d.ts.map +1 -1
- package/dist/replace-file.js +24 -24
- package/dist/root-boundary.d.ts +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 +26 -9
- 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
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import fsSync, { constants } from "node:fs";
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import { FsSafeError } from "./errors.js";
|
|
4
|
-
import { inspectDirectoryIdentity } from "./directory-guard.js";
|
|
5
|
-
import { inspectFileIdentity } from "./strict-file-identity.js";
|
|
4
|
+
import { inspectDirectoryIdentity, inspectDirectoryIdentitySync } from "./directory-guard.js";
|
|
5
|
+
import { inspectFileIdentity, inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
6
6
|
import { assertOwnedDirectory, ownDirectoryMode } from "./directory-mode-owner.js";
|
|
7
|
-
function
|
|
7
|
+
export function nodeDirectorySearchOnlyFlags() {
|
|
8
8
|
if (process.arch !== "x64" && process.arch !== "arm64")
|
|
9
9
|
return undefined;
|
|
10
10
|
// Darwin SDK O_SEARCH = O_EXEC (0x40000000) | O_DIRECTORY, on x64/arm64.
|
|
@@ -41,7 +41,7 @@ export async function pinNodeDirectoryForMode(dirPath, options = {}) {
|
|
|
41
41
|
const handle = await fs.open(dirPath, constants.O_RDONLY | flags).catch(async (error) => {
|
|
42
42
|
if (error.code !== "EACCES")
|
|
43
43
|
throw error;
|
|
44
|
-
const route =
|
|
44
|
+
const route = nodeDirectorySearchOnlyFlags();
|
|
45
45
|
if (!route)
|
|
46
46
|
throw error;
|
|
47
47
|
proc = route.proc;
|
|
@@ -91,3 +91,101 @@ export async function pinNodeDirectoryForMode(dirPath, options = {}) {
|
|
|
91
91
|
throw error;
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
|
+
/** Synchronous counterpart with the same no-follow, identity, and owner checks. */
|
|
95
|
+
export function pinNodeDirectoryForModeSync(dirPath, options = {}) {
|
|
96
|
+
const expected = inspectDirectoryIdentitySync(dirPath, options.expectedIdentity);
|
|
97
|
+
const assertOwner = (stat) => {
|
|
98
|
+
if (options.ownerUid !== undefined && stat.uid !== BigInt(options.ownerUid)) {
|
|
99
|
+
throw new FsSafeError("not-owned", "directory mode target must retain its expected owner");
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
assertOwner(expected);
|
|
103
|
+
if (process.platform === "win32") {
|
|
104
|
+
return {
|
|
105
|
+
apply: (_mode, check) => {
|
|
106
|
+
check?.();
|
|
107
|
+
assertOwner(inspectDirectoryIdentitySync(dirPath, expected));
|
|
108
|
+
check?.();
|
|
109
|
+
},
|
|
110
|
+
close: () => undefined,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
if ([constants.O_DIRECTORY, constants.O_NOFOLLOW, constants.O_NONBLOCK, constants.O_RDONLY]
|
|
114
|
+
.some((flag) => typeof flag !== "number")) {
|
|
115
|
+
throw new FsSafeError("helper-unavailable", "no-follow directory mode descriptors are unavailable");
|
|
116
|
+
}
|
|
117
|
+
const flags = constants.O_DIRECTORY | constants.O_NOFOLLOW | constants.O_NONBLOCK;
|
|
118
|
+
let fd;
|
|
119
|
+
let proc = false;
|
|
120
|
+
try {
|
|
121
|
+
fd = fsSync.openSync(dirPath, constants.O_RDONLY | flags);
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
if (error.code !== "EACCES")
|
|
125
|
+
throw error;
|
|
126
|
+
const route = nodeDirectorySearchOnlyFlags();
|
|
127
|
+
if (!route)
|
|
128
|
+
throw error;
|
|
129
|
+
proc = route.proc;
|
|
130
|
+
fd = fsSync.openSync(dirPath, route.flags | flags);
|
|
131
|
+
}
|
|
132
|
+
let closed = false;
|
|
133
|
+
const inspect = () => {
|
|
134
|
+
if (closed)
|
|
135
|
+
throw new FsSafeError("path-mismatch", "directory mode descriptor is closed");
|
|
136
|
+
const stat = inspectFileIdentitySync(() => fsSync.fstatSync(fd, { bigint: true }), expected);
|
|
137
|
+
assertOwnedDirectory(expected, stat);
|
|
138
|
+
assertOwner(stat);
|
|
139
|
+
inspectDirectoryIdentitySync(dirPath, expected);
|
|
140
|
+
return Number(stat.mode & 4095n);
|
|
141
|
+
};
|
|
142
|
+
const procPath = `/proc/self/fd/${fd}`;
|
|
143
|
+
const assertProcAuthority = () => {
|
|
144
|
+
if (fsSync.statfsSync("/proc/self/fd", { bigint: true }).type !== 0x9fa0n) {
|
|
145
|
+
throw new FsSafeError("path-mismatch", "directory mode requires a trusted procfs fd namespace");
|
|
146
|
+
}
|
|
147
|
+
const opened = inspectFileIdentitySync(() => fsSync.fstatSync(fd, { bigint: true }), expected);
|
|
148
|
+
const followed = inspectFileIdentitySync(() => fsSync.statSync(procPath, { bigint: true }), expected);
|
|
149
|
+
assertOwnedDirectory(opened, followed);
|
|
150
|
+
assertOwner(opened);
|
|
151
|
+
assertOwner(followed);
|
|
152
|
+
};
|
|
153
|
+
try {
|
|
154
|
+
inspect();
|
|
155
|
+
return {
|
|
156
|
+
apply: (mode, check) => {
|
|
157
|
+
mode &= 0o7777;
|
|
158
|
+
check?.();
|
|
159
|
+
const currentMode = inspect();
|
|
160
|
+
check?.();
|
|
161
|
+
if (currentMode !== mode) {
|
|
162
|
+
if (proc)
|
|
163
|
+
assertProcAuthority();
|
|
164
|
+
check?.();
|
|
165
|
+
inspect();
|
|
166
|
+
if (proc)
|
|
167
|
+
fsSync.chmodSync(procPath, mode);
|
|
168
|
+
else
|
|
169
|
+
fsSync.fchmodSync(fd, mode);
|
|
170
|
+
if (proc)
|
|
171
|
+
assertProcAuthority();
|
|
172
|
+
}
|
|
173
|
+
const finalMode = inspect();
|
|
174
|
+
check?.();
|
|
175
|
+
if (finalMode !== mode) {
|
|
176
|
+
throw new FsSafeError("path-mismatch", "directory final mode could not be verified");
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
close: () => {
|
|
180
|
+
if (closed)
|
|
181
|
+
return;
|
|
182
|
+
closed = true;
|
|
183
|
+
fsSync.closeSync(fd);
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
fsSync.closeSync(fd);
|
|
189
|
+
throw error;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"effective-uid.d.ts","sourceRoot":"","sources":["../src/effective-uid.ts"],"names":[],"mappings":"AAUA,wBAAgB,mBAAmB,IAAI,MAAM,CAe5C"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
function isValidUid(value) {
|
|
2
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
3
|
+
}
|
|
4
|
+
function unavailable(cause) {
|
|
5
|
+
return new Error("Effective user identity is unavailable.", {
|
|
6
|
+
...(cause === undefined ? {} : { cause }),
|
|
7
|
+
});
|
|
8
|
+
}
|
|
9
|
+
export function resolveEffectiveUid() {
|
|
10
|
+
const provider = process.geteuid;
|
|
11
|
+
if (typeof provider !== "function") {
|
|
12
|
+
throw unavailable();
|
|
13
|
+
}
|
|
14
|
+
let uid;
|
|
15
|
+
try {
|
|
16
|
+
uid = provider.call(process);
|
|
17
|
+
}
|
|
18
|
+
catch (cause) {
|
|
19
|
+
throw unavailable(cause);
|
|
20
|
+
}
|
|
21
|
+
if (!isValidUid(uid)) {
|
|
22
|
+
throw unavailable();
|
|
23
|
+
}
|
|
24
|
+
return uid;
|
|
25
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-handle-transfer.d.ts","sourceRoot":"","sources":["../src/file-handle-transfer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAWnD,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;IACtC,oBAAoB,CAAC,EAAE,MAAM,IAAI,CAAC;CACnC,CAAC;
|
|
1
|
+
{"version":3,"file":"file-handle-transfer.d.ts","sourceRoot":"","sources":["../src/file-handle-transfer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAWnD,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;IACtC,oBAAoB,CAAC,EAAE,MAAM,IAAI,CAAC;CACnC,CAAC;AAkDF,uFAAuF;AACvF,wBAAsB,cAAc,CAClC,MAAM,EAAE,UAAU,EAClB,MAAM,EAAE,UAAU,EAClB,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC,MAAM,CAAC,CA0CjB;AAGD,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,UAAU,EAClB,MAAM,EAAE,UAAU,GAAG,MAAM,EAC3B,OAAO,EAAE,qBAAqB,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,cAAc,CAAC,EAAE,MAAM,CAAA;CAAE,GAC7E,OAAO,CAAC,MAAM,CAAC,CAwBjB"}
|
|
@@ -3,61 +3,132 @@ import { FsSafeError } from "./errors.js";
|
|
|
3
3
|
import { assertSynchronousCallbackResult, composeMutationAssertions, rethrowMutationAuthorityError, } from "./mutation-authority.js";
|
|
4
4
|
import { writeAllToFile } from "./write-file-handle.js";
|
|
5
5
|
import { inspectFileIdentity } from "./strict-file-identity.js";
|
|
6
|
+
const NO_CALLBACK_ARGUMENTS = Object.freeze([]);
|
|
7
|
+
function defineReceiverValue(receiver, key, value) {
|
|
8
|
+
Object.defineProperty(receiver, key, {
|
|
9
|
+
value, enumerable: true, writable: true, configurable: true,
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
function copyTransferReceiver(options, maxBytes, sizeHint, signal, onChunk, assertBeforeMutation) {
|
|
13
|
+
const receiver = {};
|
|
14
|
+
const spreadSource = Object(options);
|
|
15
|
+
// Preserve the former late object-spread boundary, including proxy traps and
|
|
16
|
+
// unrelated getter failures. Authority-bearing values stay on their early
|
|
17
|
+
// snapshots, while descriptors still run in original own-key order.
|
|
18
|
+
for (const key of Reflect.ownKeys(spreadSource)) {
|
|
19
|
+
if (!Object.getOwnPropertyDescriptor(spreadSource, key)?.enumerable)
|
|
20
|
+
continue;
|
|
21
|
+
let value;
|
|
22
|
+
switch (key) {
|
|
23
|
+
case "maxBytes":
|
|
24
|
+
value = maxBytes;
|
|
25
|
+
break;
|
|
26
|
+
case "signal":
|
|
27
|
+
value = signal;
|
|
28
|
+
break;
|
|
29
|
+
case "onChunk":
|
|
30
|
+
value = onChunk;
|
|
31
|
+
break;
|
|
32
|
+
case "assertBeforeMutation":
|
|
33
|
+
value = assertBeforeMutation;
|
|
34
|
+
break;
|
|
35
|
+
default: value = Reflect.get(spreadSource, key);
|
|
36
|
+
}
|
|
37
|
+
// Create a data property so an enumerable __proto__ key cannot alter the
|
|
38
|
+
// ordinary receiver prototype, matching object spread.
|
|
39
|
+
defineReceiverValue(receiver, key, value);
|
|
40
|
+
}
|
|
41
|
+
// Match the explicit fields that followed the former spread. Updating an
|
|
42
|
+
// existing key retains its original position; absent keys append in order.
|
|
43
|
+
defineReceiverValue(receiver, "maxBytes", maxBytes);
|
|
44
|
+
defineReceiverValue(receiver, "sizeHint", sizeHint);
|
|
45
|
+
defineReceiverValue(receiver, "targetPosition", 0);
|
|
46
|
+
defineReceiverValue(receiver, "assertBeforeMutation", assertBeforeMutation);
|
|
47
|
+
return receiver;
|
|
48
|
+
}
|
|
6
49
|
/** Copies caller-owned regular files from offset zero without moving either cursor. */
|
|
7
50
|
export async function copyFileHandle(source, target, options = {}) {
|
|
8
|
-
options.
|
|
51
|
+
// Snapshot caller-owned options before the first await. This preserves
|
|
52
|
+
// inherited/non-enumerable callbacks and prevents accessor mutation from
|
|
53
|
+
// replacing cancellation or mutation authority mid-transfer.
|
|
54
|
+
const signal = options.signal;
|
|
55
|
+
signal?.throwIfAborted();
|
|
9
56
|
const maxBytes = normalizeMaxBytes(options.maxBytes);
|
|
57
|
+
const onChunk = options.onChunk;
|
|
58
|
+
const assertBeforeMutation = composeMutationAssertions(undefined, options.assertBeforeMutation);
|
|
10
59
|
const sourceStat = await inspectFileIdentity(() => {
|
|
11
|
-
|
|
60
|
+
signal?.throwIfAborted();
|
|
12
61
|
return source.stat({ bigint: true });
|
|
13
62
|
});
|
|
14
|
-
|
|
63
|
+
signal?.throwIfAborted();
|
|
15
64
|
if (!sourceStat.isFile())
|
|
16
65
|
throw new FsSafeError("not-file", "copy source handle must be a regular file");
|
|
17
66
|
if (maxBytes !== undefined && Number.isFinite(maxBytes) && sourceStat.size > BigInt(maxBytes)) {
|
|
18
67
|
throw new FsSafeError("too-large", `file exceeds limit of ${maxBytes} bytes`);
|
|
19
68
|
}
|
|
20
69
|
const targetStat = await inspectFileIdentity(() => {
|
|
21
|
-
|
|
70
|
+
signal?.throwIfAborted();
|
|
22
71
|
return target.stat({ bigint: true });
|
|
23
72
|
});
|
|
24
|
-
|
|
73
|
+
signal?.throwIfAborted();
|
|
25
74
|
if (!targetStat.isFile())
|
|
26
75
|
throw new FsSafeError("not-file", "copy target handle must be a regular file");
|
|
27
76
|
if (sourceStat.dev === targetStat.dev && sourceStat.ino === targetStat.ino) {
|
|
28
77
|
throw new FsSafeError("path-alias", "copy source and target handles refer to the same file");
|
|
29
78
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
79
|
+
const sizeHint = Number(sourceStat.size);
|
|
80
|
+
const callbackThis = copyTransferReceiver(options, maxBytes, sizeHint, signal, onChunk, assertBeforeMutation);
|
|
81
|
+
const beforeWrite = () => {
|
|
82
|
+
signal?.throwIfAborted();
|
|
83
|
+
assertBeforeMutation?.();
|
|
84
|
+
};
|
|
85
|
+
return await transferFileHandleCore(source, target, sizeHint, maxBytes, 0, signal, onChunk, beforeWrite, callbackThis).catch(rethrowMutationAuthorityError);
|
|
37
86
|
}
|
|
38
87
|
// Root.copyIn supplies already-admitted identities and retains its owned target cursor.
|
|
39
|
-
export
|
|
40
|
-
|
|
41
|
-
|
|
88
|
+
export function transferFileHandle(source, target, options) {
|
|
89
|
+
try {
|
|
90
|
+
const maxBytes = options.maxBytes;
|
|
91
|
+
const sizeHint = options.sizeHint;
|
|
92
|
+
const targetPosition = options.targetPosition;
|
|
93
|
+
const signal = options.signal;
|
|
94
|
+
const onChunk = options.onChunk;
|
|
95
|
+
const assertBeforeMutation = options.assertBeforeMutation;
|
|
96
|
+
const beforeWrite = signal === undefined && assertBeforeMutation === undefined
|
|
97
|
+
? undefined
|
|
98
|
+
: () => {
|
|
99
|
+
signal?.throwIfAborted();
|
|
100
|
+
if (assertBeforeMutation) {
|
|
101
|
+
Reflect.apply(assertBeforeMutation, options, NO_CALLBACK_ARGUMENTS);
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
return transferFileHandleCore(source, target, sizeHint, maxBytes, targetPosition, signal, onChunk, beforeWrite, options);
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
// Keep the former async-function contract for synchronous option getters.
|
|
108
|
+
return Promise.reject(error);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
async function transferFileHandleCore(source, target, sizeHint, maxBytes, targetPosition, signal, onChunk, beforeWrite, callbackThis) {
|
|
112
|
+
const limit = maxBytes ?? Infinity;
|
|
113
|
+
const buffer = Buffer.allocUnsafe(Math.min(512 * 1024, Math.max(64 * 1024, sizeHint), limit + 1));
|
|
42
114
|
let position = 0;
|
|
43
115
|
while (true) {
|
|
44
|
-
|
|
45
|
-
const { bytesRead } = await source.read(buffer, 0, Math.min(buffer.length,
|
|
46
|
-
|
|
47
|
-
if (bytesRead >
|
|
48
|
-
throw new FsSafeError("too-large", `file exceeds limit of ${
|
|
116
|
+
signal?.throwIfAborted();
|
|
117
|
+
const { bytesRead } = await source.read(buffer, 0, Math.min(buffer.length, limit - position + 1), position);
|
|
118
|
+
signal?.throwIfAborted();
|
|
119
|
+
if (bytesRead > limit - position) {
|
|
120
|
+
throw new FsSafeError("too-large", `file exceeds limit of ${limit} bytes`);
|
|
49
121
|
}
|
|
50
122
|
if (bytesRead === 0)
|
|
51
123
|
return position;
|
|
52
124
|
const chunk = buffer.subarray(0, bytesRead);
|
|
53
|
-
if (
|
|
54
|
-
|
|
125
|
+
if (onChunk) {
|
|
126
|
+
const returned = Function.prototype.call.call(onChunk, callbackThis, chunk);
|
|
127
|
+
assertSynchronousCallbackResult(returned, "onChunk");
|
|
128
|
+
}
|
|
55
129
|
await writeAllToFile(target, chunk, {
|
|
56
|
-
position:
|
|
57
|
-
assertBeforeMutation:
|
|
58
|
-
options.signal?.throwIfAborted();
|
|
59
|
-
options.assertBeforeMutation?.();
|
|
60
|
-
},
|
|
130
|
+
position: targetPosition === undefined ? undefined : targetPosition + position,
|
|
131
|
+
assertBeforeMutation: beforeWrite,
|
|
61
132
|
});
|
|
62
133
|
position += bytesRead;
|
|
63
134
|
}
|
package/dist/file-hash.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-hash.d.ts","sourceRoot":"","sources":["../src/file-hash.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAInD,OAAO,EAAoB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"file-hash.d.ts","sourceRoot":"","sources":["../src/file-hash.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAInD,OAAO,EAAoB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAKnE,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,UAAU,CAAC;AAClD,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,MAAM,CAAC;AAElD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,wBAAsB,cAAc,CAClC,MAAM,EAAE,UAAU,EAClB,MAAM,GAAE,aAAa,GAAG,SAA8B,EACtD,EAAE,QAAmB,EAAE,MAAM,EAAE,GAAE,iBAAsB,GACtD,OAAO,CAAC,gBAAgB,CAAC,CAwD3B;AA0DD,wBAAsB,UAAU,CAC9B,KAAK,EAAE,eAAe,EACtB,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,gBAAgB,CAAC,CAM3B;AAwDD,8FAA8F;AAC9F,wBAAgB,cAAc,CAC5B,KAAK,EAAE,mBAAmB,EAC1B,OAAO,GAAE,iBAAsB,GAC9B,gBAAgB,CAMlB"}
|
package/dist/file-hash.js
CHANGED
|
@@ -6,6 +6,7 @@ import { FsSafeError } from "./errors.js";
|
|
|
6
6
|
import { getNativeBinding } from "./native.js";
|
|
7
7
|
import { resolveReadOpenFlags } from "./read-open-flags.js";
|
|
8
8
|
import { inspectFileIdentity, inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
9
|
+
import { assertNoWindowsPathAlias } from "./windows-path-alias.js";
|
|
9
10
|
export async function hashFileHandle(handle, native = getNativeBinding(), { maxBytes = Infinity, signal } = {}) {
|
|
10
11
|
signal?.throwIfAborted();
|
|
11
12
|
const stat = fsSync.fstatSync(handle.fd);
|
|
@@ -88,6 +89,7 @@ function rethrowHashOpenError(error) {
|
|
|
88
89
|
throw error;
|
|
89
90
|
}
|
|
90
91
|
async function hashPath(filePath, options) {
|
|
92
|
+
assertNoWindowsPathAlias(filePath, "filesystem", "SHA-256 path uses a Windows filesystem namespace alias");
|
|
91
93
|
const before = await inspectFileIdentity(() => hashPathIdentity(filePath));
|
|
92
94
|
let handle;
|
|
93
95
|
try {
|
|
@@ -145,6 +147,7 @@ function hashDescriptorSync(fd, { maxBytes = Infinity, signal }) {
|
|
|
145
147
|
}
|
|
146
148
|
}
|
|
147
149
|
function hashPathSync(filePath, options) {
|
|
150
|
+
assertNoWindowsPathAlias(filePath, "filesystem", "SHA-256 path uses a Windows filesystem namespace alias");
|
|
148
151
|
const before = inspectFileIdentitySync(() => hashPathIdentity(filePath));
|
|
149
152
|
let fd;
|
|
150
153
|
try {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-lock-sync.d.ts","sourceRoot":"","sources":["../src/file-lock-sync.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EASL,KAAK,wBAAwB,EAC9B,MAAM,2BAA2B,CAAC;AAWnC,OAAO,KAAK,EACV,0BAA0B,EAC1B,uBAAuB,EACvB,wBAAwB,EACzB,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"file-lock-sync.d.ts","sourceRoot":"","sources":["../src/file-lock-sync.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EASL,KAAK,wBAAwB,EAC9B,MAAM,2BAA2B,CAAC;AAWnC,OAAO,KAAK,EACV,0BAA0B,EAC1B,uBAAuB,EACvB,wBAAwB,EACzB,MAAM,yBAAyB,CAAC;AAQjC,MAAM,MAAM,0BAA0B,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI;IACjF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,uBAAuB,CAAC;IAChC,aAAa,CAAC,EAAE,wBAAwB,CAAC;IACzC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,QAAQ,CAAC;IACxB,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE;QACvB,QAAQ,EAAE,MAAM,CAAC;QACjB,oBAAoB,EAAE,MAAM,CAAC;QAC7B,OAAO,EAAE,OAAO,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;QAChB,KAAK,EAAE,MAAM,CAAC;QACd,iBAAiB,EAAE,KAAK,CAAC;KAC1B,KAAK,OAAO,CAAC;IACd,qBAAqB,CAAC,EAAE,CAAC,QAAQ,EAAE,wBAAwB,KAAK,OAAO,CAAC;IACxE,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IACxC,QAAQ,CAAC,EAAE,IAAI,CAAC;IAChB,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,0BAA0B,KAAK,IAAI,CAAC;IAC3D,yBAAyB,CAAC,EAAE,MAAM,CAAC;CACpC,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,eAAe,IAAI,OAAO,CAAC;IAC3B,OAAO,IAAI,IAAI,CAAC;IAChB,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC;CAC1B,CAAC;AA8JF,wBAAgB,mBAAmB,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC1E,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,0BAA0B,CAAC,QAAQ,CAAC,GAC5C,kBAAkB,CA4NpB;AAED,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC1E,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,0BAA0B,CAAC,QAAQ,CAAC,EAC7C,EAAE,EAAE,MAAM,CAAC,GACV,CAAC,CAOH"}
|
package/dist/file-lock-sync.js
CHANGED
|
@@ -5,8 +5,10 @@ import { readSidecarLockSnapshotSync, readSidecarLockRawSnapshotSync, parseSidec
|
|
|
5
5
|
import { computeSidecarLockDelayMs, isTransientLockFileDenial, maxTransientLockDenials, sidecarLockPayloadCreatedAtMs, validateSidecarLockStaleMs, validateSidecarLockCompromiseCheckIntervalMs, validateSidecarLockRetryOptions, validateSidecarLockTimeoutMs, } from "./sidecar-lock-policy.js";
|
|
6
6
|
import { getFsSafeLockConfig } from "./lock-config.js";
|
|
7
7
|
import { sleepSync } from "./timing.js";
|
|
8
|
+
import { assertNoWindowsPathAlias } from "./windows-path-alias.js";
|
|
8
9
|
import { realpathSync } from "./realpath.js";
|
|
9
10
|
import { recursiveMkdirPath } from "./recursive-mkdir-path.js";
|
|
11
|
+
import { createSuppressedError } from "./suppressed-error.js";
|
|
10
12
|
const SYNC_HELD_LOCKS_KEY = Symbol.for("fsSafe.syncSidecarLocks");
|
|
11
13
|
const SYNC_CLEANUP_REGISTERED_KEY = Symbol.for("fsSafe.syncSidecarLockCleanupRegistered");
|
|
12
14
|
const SYNC_CLEANUP_HANDLER_KEY = Symbol.for("fsSafe.syncSidecarLockCleanupHandler");
|
|
@@ -97,26 +99,36 @@ function canonicalLockParentSync(parent) {
|
|
|
97
99
|
}
|
|
98
100
|
function normalizeTargetPath(targetPath) {
|
|
99
101
|
const resolved = path.resolve(targetPath);
|
|
102
|
+
assertNoWindowsPathAlias(resolved);
|
|
100
103
|
fs.mkdirSync(recursiveMkdirPath(path.dirname(resolved)), { recursive: true });
|
|
104
|
+
let parent;
|
|
101
105
|
try {
|
|
102
|
-
|
|
106
|
+
parent = canonicalLockParentSync(path.dirname(resolved));
|
|
103
107
|
}
|
|
104
108
|
catch {
|
|
105
109
|
return resolved;
|
|
106
110
|
}
|
|
111
|
+
assertNoWindowsPathAlias(parent);
|
|
112
|
+
const normalized = path.join(parent, path.basename(resolved));
|
|
113
|
+
assertNoWindowsPathAlias(normalized);
|
|
114
|
+
return normalized;
|
|
107
115
|
}
|
|
108
116
|
function boundedLockPath(lockPath, lockRoot) {
|
|
109
117
|
const resolved = path.resolve(lockPath);
|
|
118
|
+
assertNoWindowsPathAlias(resolved);
|
|
110
119
|
if (!lockRoot)
|
|
111
120
|
return resolved;
|
|
112
121
|
relativeSidecarLockPath(lockRoot, resolved);
|
|
113
122
|
const parent = path.dirname(resolved);
|
|
114
123
|
const parentReal = canonicalLockParentSync(parent);
|
|
124
|
+
assertNoWindowsPathAlias(parentReal);
|
|
115
125
|
const parentRelative = path.relative(lockRoot.rootReal, parentReal);
|
|
116
126
|
if (parentRelative === ".." || parentRelative.startsWith(`..${path.sep}`) || path.isAbsolute(parentRelative)) {
|
|
117
127
|
throw new FsSafeError("outside-workspace", "sidecar lock parent is outside lockRoot");
|
|
118
128
|
}
|
|
119
|
-
|
|
129
|
+
const bounded = path.join(parentReal, path.basename(resolved));
|
|
130
|
+
assertNoWindowsPathAlias(bounded);
|
|
131
|
+
return bounded;
|
|
120
132
|
}
|
|
121
133
|
function defaultShouldReclaim(snapshot, staleMs, nowMs) {
|
|
122
134
|
const createdAtMs = sidecarLockPayloadCreatedAtMs(snapshot.payload);
|
|
@@ -145,8 +157,12 @@ export function acquireFileLockSync(targetPath, options) {
|
|
|
145
157
|
const staleMs = options.staleMs ?? defaults.staleMs ?? 30_000;
|
|
146
158
|
validateSidecarLockStaleMs(staleMs);
|
|
147
159
|
validateSidecarLockCompromiseCheckIntervalMs(options.compromiseCheckIntervalMs);
|
|
160
|
+
const explicitLockPath = options.lockPath;
|
|
161
|
+
assertNoWindowsPathAlias(targetPath);
|
|
162
|
+
if (explicitLockPath !== undefined)
|
|
163
|
+
assertNoWindowsPathAlias(explicitLockPath);
|
|
148
164
|
const normalizedTargetPath = normalizeTargetPath(targetPath);
|
|
149
|
-
const lockPath = boundedLockPath(
|
|
165
|
+
const lockPath = boundedLockPath(explicitLockPath ?? `${normalizedTargetPath}.lock`, options.lockRoot);
|
|
150
166
|
const heldLocks = getSyncHeldLocks();
|
|
151
167
|
const held = heldLocks.get(normalizedTargetPath);
|
|
152
168
|
if (held &&
|
|
@@ -256,10 +272,24 @@ export function acquireFileLockSync(targetPath, options) {
|
|
|
256
272
|
}
|
|
257
273
|
catch (error) {
|
|
258
274
|
if (fd !== undefined) {
|
|
259
|
-
const failed = { payload: null
|
|
260
|
-
|
|
275
|
+
const failed = { payload: null };
|
|
276
|
+
try {
|
|
277
|
+
failed.stat = fs.fstatSync(fd);
|
|
278
|
+
}
|
|
279
|
+
catch {
|
|
280
|
+
// Missing identity leaves the sidecar in place, but must not skip close.
|
|
281
|
+
}
|
|
282
|
+
const failedFd = fd;
|
|
283
|
+
// A close error may still free the number; consume ownership before closing.
|
|
261
284
|
fd = undefined;
|
|
262
|
-
|
|
285
|
+
try {
|
|
286
|
+
fs.closeSync(failedFd);
|
|
287
|
+
if (failed.stat)
|
|
288
|
+
removeSidecarLockIfUnchangedSync(lockPath, failed);
|
|
289
|
+
}
|
|
290
|
+
catch (cleanupError) {
|
|
291
|
+
throw createSuppressedError(error, cleanupError, "file lock acquisition and cleanup both failed");
|
|
292
|
+
}
|
|
263
293
|
}
|
|
264
294
|
if (lockFileCreateDenied && retryLockFileDenial(error))
|
|
265
295
|
continue;
|
package/dist/file-lock.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-lock.d.ts","sourceRoot":"","sources":["../src/file-lock.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,yBAAyB,EAC9B,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EACzB,KAAK,uBAAuB,EAC5B,KAAK,wBAAwB,EAC9B,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACL,mBAAmB,EACnB,gBAAgB,EAChB,KAAK,0BAA0B,EAC/B,KAAK,kBAAkB,GACxB,MAAM,qBAAqB,CAAC;AAE7B,MAAM,MAAM,oBAAoB,GAAG,uBAAuB,CAAC;AAC3D,MAAM,MAAM,qBAAqB,GAAG,wBAAwB,CAAC;AAE7D,MAAM,MAAM,sBAAsB,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,IAAI,CACjF,yBAAyB,CAAC,QAAQ,CAAC,EACnC,YAAY,GAAG,SAAS,CACzB,GAAG;IACF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG,iBAAiB,CAAC;AAC/C,MAAM,MAAM,iBAAiB,GAAG,oBAAoB,CAAC;AACrD,YAAY,EAAE,0BAA0B,EAAE,MAAM,mBAAmB,CAAC;AAEpE,MAAM,MAAM,eAAe,GAAG;IAC5B,OAAO,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9C,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,sBAAsB,CAAC,QAAQ,CAAC,GACxC,OAAO,CAAC,cAAc,CAAC,CAAC;IAC3B,QAAQ,CAAC,CAAC,EAAE,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAClD,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,sBAAsB,CAAC,QAAQ,CAAC,EACzC,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACnB,OAAO,CAAC,CAAC,CAAC,CAAC;IACd,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,KAAK,IAAI,IAAI,CAAC;IACd,WAAW,IAAI,iBAAiB,EAAE,CAAC;CACpC,CAAC;
|
|
1
|
+
{"version":3,"file":"file-lock.d.ts","sourceRoot":"","sources":["../src/file-lock.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,yBAAyB,EAC9B,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EACzB,KAAK,uBAAuB,EAC5B,KAAK,wBAAwB,EAC9B,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACL,mBAAmB,EACnB,gBAAgB,EAChB,KAAK,0BAA0B,EAC/B,KAAK,kBAAkB,GACxB,MAAM,qBAAqB,CAAC;AAE7B,MAAM,MAAM,oBAAoB,GAAG,uBAAuB,CAAC;AAC3D,MAAM,MAAM,qBAAqB,GAAG,wBAAwB,CAAC;AAE7D,MAAM,MAAM,sBAAsB,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,IAAI,CACjF,yBAAyB,CAAC,QAAQ,CAAC,EACnC,YAAY,GAAG,SAAS,CACzB,GAAG;IACF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG,iBAAiB,CAAC;AAC/C,MAAM,MAAM,iBAAiB,GAAG,oBAAoB,CAAC;AACrD,YAAY,EAAE,0BAA0B,EAAE,MAAM,mBAAmB,CAAC;AAEpE,MAAM,MAAM,eAAe,GAAG;IAC5B,OAAO,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9C,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,sBAAsB,CAAC,QAAQ,CAAC,GACxC,OAAO,CAAC,cAAc,CAAC,CAAC;IAC3B,QAAQ,CAAC,CAAC,EAAE,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAClD,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,sBAAsB,CAAC,QAAQ,CAAC,EACzC,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACnB,OAAO,CAAC,CAAC,CAAC,CAAC;IACd,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,KAAK,IAAI,IAAI,CAAC;IACd,WAAW,IAAI,iBAAiB,EAAE,CAAC;CACpC,CAAC;AAiCF,wBAAsB,eAAe,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5E,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,sBAAsB,CAAC,QAAQ,CAAC,GACxC,OAAO,CAAC,cAAc,CAAC,CAGzB;AAED,wBAAsB,YAAY,CAAC,CAAC,EAAE,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5E,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,sBAAsB,CAAC,QAAQ,CAAC,EACzC,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACnB,OAAO,CAAC,CAAC,CAAC,CAGZ;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,CAmBlE;AAED,wBAAsB,2BAA2B,CAC/C,UAAU,EAAE,MAAM,EAClB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,IAAI,CAAC,CAEf;AAED,wBAAgB,2BAA2B,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAEzF"}
|
package/dist/file-lock.js
CHANGED
|
@@ -5,13 +5,29 @@ function resolveFileLockManagerKey(targetPath, managerKey) {
|
|
|
5
5
|
return managerKey ?? `fs-safe.file-lock:${targetPath}`;
|
|
6
6
|
}
|
|
7
7
|
function withLockDefaults(options) {
|
|
8
|
+
const { managerKey: _managerKey, lockPath } = options;
|
|
9
|
+
const acquireOptions = {};
|
|
10
|
+
for (const key of Reflect.ownKeys(options)) {
|
|
11
|
+
// Node 22 object-rest can read excluded accessors; exclude before copying.
|
|
12
|
+
if (key === "managerKey" || key === "lockPath")
|
|
13
|
+
continue;
|
|
14
|
+
if (!Object.getOwnPropertyDescriptor(options, key)?.enumerable)
|
|
15
|
+
continue;
|
|
16
|
+
Object.defineProperty(acquireOptions, key, {
|
|
17
|
+
value: Reflect.get(options, key),
|
|
18
|
+
enumerable: true,
|
|
19
|
+
writable: true,
|
|
20
|
+
configurable: true,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
8
23
|
const defaults = getFsSafeLockConfig();
|
|
9
24
|
return {
|
|
10
|
-
...
|
|
11
|
-
retry:
|
|
12
|
-
staleMs:
|
|
13
|
-
staleRecovery:
|
|
14
|
-
timeoutMs:
|
|
25
|
+
...acquireOptions,
|
|
26
|
+
retry: acquireOptions.retry ?? defaults.retry,
|
|
27
|
+
staleMs: acquireOptions.staleMs ?? defaults.staleMs ?? 30_000,
|
|
28
|
+
staleRecovery: acquireOptions.staleRecovery ?? defaults.staleRecovery,
|
|
29
|
+
timeoutMs: acquireOptions.timeoutMs ?? defaults.timeoutMs,
|
|
30
|
+
...(lockPath === undefined ? {} : { lockPath }),
|
|
15
31
|
};
|
|
16
32
|
}
|
|
17
33
|
export async function acquireFileLock(targetPath, options) {
|
|
@@ -26,12 +42,16 @@ export function createFileLockManager(key) {
|
|
|
26
42
|
const manager = createSidecarLockManager(key);
|
|
27
43
|
return {
|
|
28
44
|
acquire: async (targetPath, options) => {
|
|
29
|
-
|
|
30
|
-
|
|
45
|
+
return await manager.acquire({
|
|
46
|
+
...withLockDefaults(options),
|
|
47
|
+
targetPath,
|
|
48
|
+
});
|
|
31
49
|
},
|
|
32
50
|
withLock: async (targetPath, options, fn) => {
|
|
33
|
-
|
|
34
|
-
|
|
51
|
+
return await manager.withLock({
|
|
52
|
+
...withLockDefaults(options),
|
|
53
|
+
targetPath,
|
|
54
|
+
}, fn);
|
|
35
55
|
},
|
|
36
56
|
drain: manager.drain,
|
|
37
57
|
reset: manager.reset,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
type FailureKind = "identity" | "resolution" | `open:${string}` | `exclusive-create:${string}` | `unlinked:${string}` | `changed:${string}`;
|
|
1
|
+
type FailureKind = "identity" | "resolution" | `open:${string}` | `exclusive-create:${string}` | `unlinked:${string}` | `changed:${string}` | `stat-leaf-missing:${string}`;
|
|
2
2
|
export declare function recordFileObservationFailure(error: unknown, kind: FailureKind): void;
|
|
3
3
|
export declare function isFileObservationFailure(error: unknown, kind: FailureKind): boolean;
|
|
4
4
|
export declare function fileObservation(): {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-observation.d.ts","sourceRoot":"","sources":["../src/file-observation.ts"],"names":[],"mappings":"AAEA,KAAK,WAAW,GAAG,UAAU,GAAG,YAAY,GAAG,QAAQ,MAAM,EAAE,GAAG,oBAAoB,MAAM,EAAE,GAAG,YAAY,MAAM,EAAE,GAAG,WAAW,MAAM,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"file-observation.d.ts","sourceRoot":"","sources":["../src/file-observation.ts"],"names":[],"mappings":"AAEA,KAAK,WAAW,GAAG,UAAU,GAAG,YAAY,GAAG,QAAQ,MAAM,EAAE,GAAG,oBAAoB,MAAM,EAAE,GAAG,YAAY,MAAM,EAAE,GAAG,WAAW,MAAM,EAAE,GAAG,qBAAqB,MAAM,EAAE,CAAC;AAG5K,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CAMpF;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,OAAO,CAEnF;AAGD,wBAAgB,eAAe;IAG3B,GAAG,CAAC,CAAC,aAAa,MAAM,CAAC,GAAG,CAAC;IAC7B,GAAG,QAAQ,OAAO,QAAQ,WAAW,GAAG,OAAO;EAElD"}
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
+
import { type BigIntStats } from "node:fs";
|
|
1
2
|
import type { Readable } from "node:stream";
|
|
2
|
-
import {
|
|
3
|
+
import type { SyncDirectoryGuard } from "./directory-guard.js";
|
|
3
4
|
import { type Root } from "./root.js";
|
|
4
5
|
import { prepareSecretFileWrite } from "./secret-file.js";
|
|
5
|
-
export type SyncParentGuard = SyncDirectoryGuard
|
|
6
|
+
export type SyncParentGuard = SyncDirectoryGuard & {
|
|
7
|
+
/** Exact authority retained separately from the legacy numeric durability receipt. */
|
|
8
|
+
readonly exactStat: BigIntStats;
|
|
9
|
+
};
|
|
6
10
|
export declare function ensureParentInRoot(scopedRoot: Root, relativePath: string, mode: number): Promise<void>;
|
|
7
11
|
export declare function openWritableStoreRoot(params: {
|
|
8
12
|
rootDir: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-store-boundary.d.ts","sourceRoot":"","sources":["../src/file-store-boundary.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"file-store-boundary.d.ts","sourceRoot":"","sources":["../src/file-store-boundary.ts"],"names":[],"mappings":"AAAA,OAAe,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAGnD,OAAO,KAAK,EAAa,QAAQ,EAAE,MAAM,aAAa,CAAC;AAIvD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAO/D,OAAO,EAA4C,KAAK,IAAI,EAAE,MAAM,WAAW,CAAC;AAGhF,OAAO,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAK1D,MAAM,MAAM,eAAe,GAAG,kBAAkB,GAAG;IACjD,sFAAsF;IACtF,QAAQ,CAAC,SAAS,EAAE,WAAW,CAAC;CACjC,CAAC;AAOF,wBAAsB,kBAAkB,CACtC,UAAU,EAAE,IAAI,EAChB,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,IAAI,CAAC,CAOf;AAED,wBAAsB,qBAAqB,CAAC,MAAM,EAAE;IAClD,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,IAAI,CAAC,CAMhB;AAED,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,UAAU,CAAC,OAAO,sBAAsB,CAAC,CAAC,CAAC,CAAC,GACnD,OAAO,CAAC,IAAI,CAAC,CAUf;AAsCD,wBAAsB,uBAAuB,CAAC,MAAM,EAAE;IACpD,MAAM,EAAE,QAAQ,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAAC,CAwC1D;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI,CAMrE;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,eAAe,CASlB;AAED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,eAAe,GAAG,OAAO,CAAC;CAC1C,GAAG,eAAe,CAUlB"}
|