@openclaw/openshell-sandbox 2026.9.6 → 2026.9.7
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/assets/icon.png +0 -0
- package/dist/index.js +75 -229
- package/node_modules/@openclaw/fs-safe/CHANGELOG.md +102 -0
- package/node_modules/@openclaw/fs-safe/README.md +24 -6
- package/node_modules/@openclaw/fs-safe/dist/advanced.d.ts +6 -1
- package/node_modules/@openclaw/fs-safe/dist/advanced.js +3 -1
- package/node_modules/@openclaw/fs-safe/dist/archive-durability.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/archive-merge.js +1 -2
- package/node_modules/@openclaw/fs-safe/dist/archive-plan.d.ts +2 -7
- package/node_modules/@openclaw/fs-safe/dist/archive-read.js +9 -18
- package/node_modules/@openclaw/fs-safe/dist/archive-staging.d.ts +1 -2
- package/node_modules/@openclaw/fs-safe/dist/archive-staging.js +3 -3
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-directory.js +4 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-entry.d.ts +11 -11
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-entry.js +3 -35
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-integrity.d.ts +2 -2
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-integrity.js +2 -12
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-loader.d.ts +7 -3
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-loader.js +13 -10
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-manifest.js +3 -1
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-preflight.d.ts +2 -3
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-preflight.js +16 -8
- package/node_modules/@openclaw/fs-safe/dist/archive.d.ts +6 -3
- package/node_modules/@openclaw/fs-safe/dist/archive.js +22 -19
- package/node_modules/@openclaw/fs-safe/dist/atomic.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/copy-publication.d.ts +1 -3
- package/node_modules/@openclaw/fs-safe/dist/copy-publication.js +2 -6
- package/node_modules/@openclaw/fs-safe/dist/deny-mutation-match.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/deny-mutation-match.js +71 -0
- package/node_modules/@openclaw/fs-safe/dist/deny-mutations.js +4 -3
- package/node_modules/@openclaw/fs-safe/dist/directory-guard.d.ts +0 -1
- package/node_modules/@openclaw/fs-safe/dist/directory-guard.js +0 -3
- package/node_modules/@openclaw/fs-safe/dist/directory-mode-node.d.ts +20 -2
- package/node_modules/@openclaw/fs-safe/dist/directory-mode-node.js +77 -1
- package/node_modules/@openclaw/fs-safe/dist/directory-receipt.js +5 -7
- package/node_modules/@openclaw/fs-safe/dist/effective-uid.js +1 -4
- package/node_modules/@openclaw/fs-safe/dist/errors.d.ts +3 -1
- package/node_modules/@openclaw/fs-safe/dist/errors.js +3 -2
- package/node_modules/@openclaw/fs-safe/dist/file-identity.js +10 -2
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-acquisition.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-admission.js +5 -1
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-held.d.ts +4 -11
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-held.js +1 -4
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-io.d.ts +3 -6
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-io.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-options.d.ts +2 -10
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root.d.ts +2 -4
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync.js +2 -2
- package/node_modules/@openclaw/fs-safe/dist/file-store-boundary.d.ts +3 -7
- package/node_modules/@openclaw/fs-safe/dist/file-store-boundary.js +7 -10
- package/node_modules/@openclaw/fs-safe/dist/file-store-prune.js +7 -6
- package/node_modules/@openclaw/fs-safe/dist/file-store.d.ts +4 -7
- package/node_modules/@openclaw/fs-safe/dist/file-store.js +19 -21
- package/node_modules/@openclaw/fs-safe/dist/guest-native-python.js +23 -31
- package/node_modules/@openclaw/fs-safe/dist/guest.js +21 -12
- package/node_modules/@openclaw/fs-safe/dist/json-document-store.d.ts +4 -9
- package/node_modules/@openclaw/fs-safe/dist/json-durable-queue-ownership.d.ts +1 -5
- package/node_modules/@openclaw/fs-safe/dist/json-durable-queue.d.ts +0 -1
- package/node_modules/@openclaw/fs-safe/dist/json-durable-queue.js +2 -7
- package/node_modules/@openclaw/fs-safe/dist/local-file-access.js +2 -5
- package/node_modules/@openclaw/fs-safe/dist/local-file-descriptor.d.ts +2 -5
- package/node_modules/@openclaw/fs-safe/dist/local-roots.d.ts +2 -7
- package/node_modules/@openclaw/fs-safe/dist/move-path-cleanup.js +4 -4
- package/node_modules/@openclaw/fs-safe/dist/mutation-authority.js +5 -0
- package/node_modules/@openclaw/fs-safe/dist/native-binding.d.ts +51 -14
- package/node_modules/@openclaw/fs-safe/dist/native-pinned-write.js +8 -2
- package/node_modules/@openclaw/fs-safe/dist/native-staged-symlink.d.ts +13 -0
- package/node_modules/@openclaw/fs-safe/dist/native-staged-symlink.js +303 -0
- package/node_modules/@openclaw/fs-safe/dist/owner-dacl-batch-worker.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/owner-dacl-batch-worker.js +54 -0
- package/node_modules/@openclaw/fs-safe/dist/owner-dacl-batch.d.ts +5 -0
- package/node_modules/@openclaw/fs-safe/dist/owner-dacl-batch.js +64 -0
- package/node_modules/@openclaw/fs-safe/dist/owner-dacl.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/owner-dacl.js +3 -0
- package/node_modules/@openclaw/fs-safe/dist/path.js +17 -1
- package/node_modules/@openclaw/fs-safe/dist/permission-exec.d.ts +6 -0
- package/node_modules/@openclaw/fs-safe/dist/permission-exec.js +3 -6
- package/node_modules/@openclaw/fs-safe/dist/permissions-public.d.ts +3 -1
- package/node_modules/@openclaw/fs-safe/dist/permissions-public.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/permissions-windows.d.ts +3 -6
- package/node_modules/@openclaw/fs-safe/dist/permissions.d.ts +3 -10
- package/node_modules/@openclaw/fs-safe/dist/permissions.js +0 -1
- package/node_modules/@openclaw/fs-safe/dist/pinned-mutation-admission.d.ts +0 -1
- package/node_modules/@openclaw/fs-safe/dist/pinned-mutation-admission.js +4 -2
- package/node_modules/@openclaw/fs-safe/dist/pinned-mutation-shared-route.d.ts +2 -8
- package/node_modules/@openclaw/fs-safe/dist/pinned-open.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/pinned-open.js +2 -3
- package/node_modules/@openclaw/fs-safe/dist/publish-copy-stage.js +4 -0
- package/node_modules/@openclaw/fs-safe/dist/read-opened-file.d.ts +2 -5
- package/node_modules/@openclaw/fs-safe/dist/regular-file.js +3 -3
- package/node_modules/@openclaw/fs-safe/dist/replace-directory.js +5 -17
- package/node_modules/@openclaw/fs-safe/dist/replace-file-buffer.d.ts +4 -0
- package/node_modules/@openclaw/fs-safe/dist/replace-file-buffer.js +36 -0
- package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-fallback.d.ts +4 -10
- package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-fallback.js +66 -38
- package/node_modules/@openclaw/fs-safe/dist/replace-file-descriptor.d.ts +7 -11
- package/node_modules/@openclaw/fs-safe/dist/replace-file-descriptor.js +10 -2
- package/node_modules/@openclaw/fs-safe/dist/replace-file-destination.d.ts +21 -0
- package/node_modules/@openclaw/fs-safe/dist/replace-file-destination.js +123 -0
- package/node_modules/@openclaw/fs-safe/dist/replace-file-mutation.d.ts +26 -0
- package/node_modules/@openclaw/fs-safe/dist/replace-file-mutation.js +47 -0
- package/node_modules/@openclaw/fs-safe/dist/replace-file-temp-owner.d.ts +2 -2
- package/node_modules/@openclaw/fs-safe/dist/replace-file-temp-owner.js +16 -4
- package/node_modules/@openclaw/fs-safe/dist/replace-file-types.d.ts +55 -0
- package/node_modules/@openclaw/fs-safe/dist/replace-file-types.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/replace-file.d.ts +3 -55
- package/node_modules/@openclaw/fs-safe/dist/replace-file.js +31 -11
- package/node_modules/@openclaw/fs-safe/dist/retained-directory-replacement.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/retained-directory-replacement.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/retained-file-types.d.ts +50 -0
- package/node_modules/@openclaw/fs-safe/dist/retained-file-types.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/retained-file.d.ts +3 -0
- package/node_modules/@openclaw/fs-safe/dist/retained-file.js +121 -0
- package/node_modules/@openclaw/fs-safe/dist/root-context.js +3 -2
- package/node_modules/@openclaw/fs-safe/dist/root-directory-entry.d.ts +9 -0
- package/node_modules/@openclaw/fs-safe/dist/root-directory-entry.js +28 -0
- package/node_modules/@openclaw/fs-safe/dist/root-directory-list.d.ts +9 -1
- package/node_modules/@openclaw/fs-safe/dist/root-directory-list.js +88 -37
- package/node_modules/@openclaw/fs-safe/dist/root-file-final-admission.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/root-file-final-admission.js +1 -6
- package/node_modules/@openclaw/fs-safe/dist/root-file.js +2 -5
- package/node_modules/@openclaw/fs-safe/dist/root-handle-context.d.ts +4 -0
- package/node_modules/@openclaw/fs-safe/dist/root-handle-context.js +12 -0
- package/node_modules/@openclaw/fs-safe/dist/root-impl.d.ts +3 -3
- package/node_modules/@openclaw/fs-safe/dist/root-impl.js +8 -5
- package/node_modules/@openclaw/fs-safe/dist/root-move-noreplace.d.ts +2 -7
- package/node_modules/@openclaw/fs-safe/dist/root-move-noreplace.js +3 -3
- package/node_modules/@openclaw/fs-safe/dist/root-observed-path.d.ts +0 -1
- package/node_modules/@openclaw/fs-safe/dist/root-observed-path.js +0 -3
- package/node_modules/@openclaw/fs-safe/dist/root-path-observation.d.ts +4 -11
- package/node_modules/@openclaw/fs-safe/dist/root-path.js +7 -10
- package/node_modules/@openclaw/fs-safe/dist/root-paths.d.ts +2 -6
- package/node_modules/@openclaw/fs-safe/dist/root-remove-identity.d.ts +1 -3
- package/node_modules/@openclaw/fs-safe/dist/root-walk.d.ts +19 -12
- package/node_modules/@openclaw/fs-safe/dist/root-walk.js +56 -20
- package/node_modules/@openclaw/fs-safe/dist/root-write-admission.js +1 -6
- package/node_modules/@openclaw/fs-safe/dist/root-write-complete-parent.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/root-write-complete-parent.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/safe-path-segment.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/safe-path-segment.js +8 -2
- package/node_modules/@openclaw/fs-safe/dist/secret-file.d.ts +6 -2
- package/node_modules/@openclaw/fs-safe/dist/secret-file.js +12 -16
- package/node_modules/@openclaw/fs-safe/dist/secret-read-policy.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/secure-file-windows.js +5 -14
- package/node_modules/@openclaw/fs-safe/dist/secure-file.js +3 -2
- package/node_modules/@openclaw/fs-safe/dist/secure-temp-dir.js +2 -7
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-acquire.js +4 -4
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-admission-parser.d.ts +1 -2
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-handle.d.ts +2 -8
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-policy.d.ts +2 -7
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.d.ts +2 -2
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.js +11 -12
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-stale-admission.d.ts +1 -5
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock.js +9 -7
- package/node_modules/@openclaw/fs-safe/dist/staged-symlink-types.d.ts +39 -0
- package/node_modules/@openclaw/fs-safe/dist/staged-symlink-types.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/stat-observation.js +3 -10
- package/node_modules/@openclaw/fs-safe/dist/strict-file-identity.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/strict-file-identity.js +6 -6
- package/node_modules/@openclaw/fs-safe/dist/symlink-parents.js +58 -7
- package/node_modules/@openclaw/fs-safe/dist/temp-target.js +8 -10
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-admission.js +28 -33
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-child-admission.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-child-admission.js +16 -17
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-descriptor.js +1 -2
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-owner.js +4 -9
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-ownership.d.ts +8 -0
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-ownership.js +52 -0
- package/node_modules/@openclaw/fs-safe/dist/test-hooks.d.ts +5 -1
- package/node_modules/@openclaw/fs-safe/dist/text-atomic.d.ts +2 -1
- package/node_modules/@openclaw/fs-safe/dist/text-atomic.js +2 -0
- package/node_modules/@openclaw/fs-safe/dist/trash.js +27 -1
- package/node_modules/@openclaw/fs-safe/dist/walk.d.ts +2 -5
- package/node_modules/@openclaw/fs-safe/dist/watch-alias.d.ts +6 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-alias.js +88 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-hints.d.ts +9 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-hints.js +93 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-native.d.ts +36 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-native.js +73 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-scan.d.ts +28 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-scan.js +300 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-stream.d.ts +8 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-stream.js +32 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-types.d.ts +60 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-types.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/watch.d.ts +5 -0
- package/node_modules/@openclaw/fs-safe/dist/watch.js +530 -0
- package/node_modules/@openclaw/fs-safe/dist/windows-owner.d.ts +3 -7
- package/node_modules/@openclaw/fs-safe/dist/windows-owner.js +0 -1
- package/node_modules/@openclaw/fs-safe/dist/windows-security-bridge.cs +6 -6
- package/node_modules/@openclaw/fs-safe/dist/windows-security-bridge.ps1 +78 -3
- package/node_modules/@openclaw/fs-safe/dist/windows-security-command.d.ts +8 -0
- package/node_modules/@openclaw/fs-safe/dist/windows-security-command.js +66 -15
- package/node_modules/@openclaw/fs-safe/dist/windows-security-facts.d.ts +4 -0
- package/node_modules/@openclaw/fs-safe/dist/windows-security-facts.js +6 -2
- package/node_modules/@openclaw/fs-safe/docs/advanced.md +4 -2
- package/node_modules/@openclaw/fs-safe/docs/archive.md +16 -0
- package/node_modules/@openclaw/fs-safe/docs/atomic.md +93 -3
- package/node_modules/@openclaw/fs-safe/docs/contributing.md +102 -4
- package/node_modules/@openclaw/fs-safe/docs/copy.md +2 -0
- package/node_modules/@openclaw/fs-safe/docs/durability.md +7 -0
- package/node_modules/@openclaw/fs-safe/docs/file-store.md +5 -0
- package/node_modules/@openclaw/fs-safe/docs/guest.md +7 -1
- package/node_modules/@openclaw/fs-safe/docs/index.md +1 -0
- package/node_modules/@openclaw/fs-safe/docs/install.md +30 -0
- package/node_modules/@openclaw/fs-safe/docs/native-helper.md +20 -4
- package/node_modules/@openclaw/fs-safe/docs/native.md +63 -1
- package/node_modules/@openclaw/fs-safe/docs/permissions.md +66 -0
- package/node_modules/@openclaw/fs-safe/docs/public-api.md +30 -3
- package/node_modules/@openclaw/fs-safe/docs/retained-file.md +115 -0
- package/node_modules/@openclaw/fs-safe/docs/root.md +35 -9
- package/node_modules/@openclaw/fs-safe/docs/secret-file.md +19 -0
- package/node_modules/@openclaw/fs-safe/docs/security-model.md +4 -1
- package/node_modules/@openclaw/fs-safe/docs/sidecar-lock.md +5 -1
- package/node_modules/@openclaw/fs-safe/docs/staged-symlink.md +124 -0
- package/node_modules/@openclaw/fs-safe/docs/store.md +3 -1
- package/node_modules/@openclaw/fs-safe/docs/temp.md +24 -4
- package/node_modules/@openclaw/fs-safe/docs/testing.md +210 -0
- package/node_modules/@openclaw/fs-safe/docs/types.md +6 -0
- package/node_modules/@openclaw/fs-safe/docs/walk.md +34 -1
- package/node_modules/@openclaw/fs-safe/docs/watch.md +251 -0
- package/node_modules/@openclaw/fs-safe/docs/writing.md +21 -0
- package/node_modules/@openclaw/fs-safe/package.json +13 -9
- package/node_modules/@openclaw/fs-safe-darwin-arm64/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-darwin-arm64/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-darwin-x64/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-darwin-x64/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-linux-arm64-gnu/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-linux-arm64-gnu/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-linux-arm64-musl/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-linux-arm64-musl/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-linux-x64-gnu/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-linux-x64-gnu/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-linux-x64-musl/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-linux-x64-musl/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-win32-x64-msvc/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-win32-x64-msvc/package.json +1 -1
- package/node_modules/p-limit/index.d.ts +2 -0
- package/node_modules/p-limit/index.js +17 -1
- package/node_modules/p-limit/package.json +1 -1
- package/node_modules/p-limit/readme.md +2 -0
- package/package.json +6 -6
- package/node_modules/@openclaw/fs-safe/dist/directory-mode-owner.d.ts +0 -20
- package/node_modules/@openclaw/fs-safe/dist/directory-mode-owner.js +0 -78
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createTarEntryPlanner, createArchiveEntrySelector, resolveArchiveFilteredEntryPolicy, createArchiveEntryPlanner, } from "./archive-plan.js";
|
|
1
|
+
import { createTarEntryPlanner, createArchiveEntrySelector, resolveArchiveFilteredEntryPolicy, resolveArchiveEntryMode, createArchiveEntryPlanner, } from "./archive-plan.js";
|
|
2
2
|
import { inspectTar, replayTar } from "./archive-tar-stream.js";
|
|
3
3
|
import { runPinnedWriteHelper } from "./pinned-write.js";
|
|
4
4
|
import { constants as fsConstants } from "node:fs";
|
|
@@ -11,9 +11,7 @@ import { assertArchiveEntryCountWithinLimit, createByteBudgetTracker, createExtr
|
|
|
11
11
|
import { resolveArchiveKind } from "./archive-kind.js";
|
|
12
12
|
import { prepareArchiveDestinationGuard, preparePrivateArchiveOutputPath, } from "./archive-staging.js";
|
|
13
13
|
import { withStagedArchivePublication } from "./archive-merge.js";
|
|
14
|
-
import {
|
|
15
|
-
import { admittedZipEntries } from "./archive-zip-loader.js";
|
|
16
|
-
import { zipEntryKind, zipEntryDeclaredSize, zipEntryMode, } from "./archive-zip-entry.js";
|
|
14
|
+
import { loadZipArchiveWithAdmission } from "./archive-zip-preflight.js";
|
|
17
15
|
import { createZipIntegrityTransform, normalizeZipIntegrityError, } from "./archive-zip-integrity.js";
|
|
18
16
|
import { ArchiveSecurityError, ArchiveFormatError, isArchiveFormatErrorMessage, } from "./archive-errors.js";
|
|
19
17
|
import { stageArchiveFileForExtraction } from "./archive-input.js";
|
|
@@ -29,10 +27,12 @@ export { resolveArchiveKind, resolvePackedRootDir } from "./archive-kind.js";
|
|
|
29
27
|
export { readArchiveEntry } from "./archive-read.js";
|
|
30
28
|
export { ARCHIVE_LIMIT_ERROR_CODE, ArchiveLimitError, DEFAULT_MAX_ARCHIVE_BYTES_ZIP, DEFAULT_MAX_ENTRIES, DEFAULT_MAX_EXTRACTED_BYTES, DEFAULT_MAX_ENTRY_BYTES, DEFAULT_MAX_META_ENTRY_BYTES, DEFAULT_MAX_ENTRY_PATH_COMPONENTS, } from "./archive-limits.js";
|
|
31
29
|
export { ArchiveFormatError } from "./archive-errors.js";
|
|
32
|
-
export { ArchiveSecurityError } from "./archive-
|
|
33
|
-
export { createArchiveSymlinkTraversalError,
|
|
30
|
+
export { ArchiveSecurityError } from "./archive-errors.js";
|
|
31
|
+
export { createArchiveSymlinkTraversalError, prepareArchiveDestinationDir, prepareArchiveOutputPath, withStagedArchiveDestination, } from "./archive-staging.js";
|
|
32
|
+
export { mergeExtractedTreeIntoDestination } from "./archive-merge.js";
|
|
34
33
|
export { createTarEntryPreflightChecker } from "./archive-plan.js";
|
|
35
|
-
export { loadZipArchiveWithPreflight
|
|
34
|
+
export { loadZipArchiveWithPreflight } from "./archive-zip-preflight.js";
|
|
35
|
+
export { readZipCentralDirectoryEntryCount } from "./archive-zip-count.js";
|
|
36
36
|
const SUPPORTS_NOFOLLOW = process.platform !== "win32" && "O_NOFOLLOW" in fsConstants;
|
|
37
37
|
const OPEN_WRITE_CREATE_FLAGS = fsConstants.O_WRONLY |
|
|
38
38
|
fsConstants.O_CREAT |
|
|
@@ -49,7 +49,7 @@ async function readZipEntryStream(entry) {
|
|
|
49
49
|
async function writeZipFileEntry(params) {
|
|
50
50
|
params.deadline.check();
|
|
51
51
|
params.budget.startEntry();
|
|
52
|
-
const readable = await readZipEntryStream(params.entry);
|
|
52
|
+
const readable = await readZipEntryStream(params.record.entry);
|
|
53
53
|
const destinationPath = params.outPath;
|
|
54
54
|
let tempHandle = null;
|
|
55
55
|
let handleClosedByStream = false;
|
|
@@ -67,7 +67,7 @@ async function writeZipFileEntry(params) {
|
|
|
67
67
|
handleClosedByStream = true;
|
|
68
68
|
});
|
|
69
69
|
try {
|
|
70
|
-
await pipeline(readable, createExtractBudgetTransform({ onChunkBytes: params.budget.addBytes }), createZipIntegrityTransform(params.
|
|
70
|
+
await pipeline(readable, createExtractBudgetTransform({ onChunkBytes: params.budget.addBytes }), createZipIntegrityTransform(params.record), writable, { signal: params.deadline.signal });
|
|
71
71
|
}
|
|
72
72
|
catch (err) {
|
|
73
73
|
throw normalizeZipIntegrityError(createPipelineTimeoutError(err, params.deadline));
|
|
@@ -101,26 +101,29 @@ async function extractZip(params) {
|
|
|
101
101
|
deadline.check();
|
|
102
102
|
const buffer = await fs.readFile(params.archivePath, { signal: deadline.signal });
|
|
103
103
|
deadline.check();
|
|
104
|
-
const
|
|
104
|
+
const { entries } = await waitForDeadline(loadZipArchiveWithAdmission(buffer, limits), deadline);
|
|
105
105
|
deadline.check();
|
|
106
|
-
|
|
107
|
-
assertArchiveEntryCountWithinLimit(entries.length, limits);
|
|
106
|
+
assertArchiveEntryCountWithinLimit(entries.size, limits);
|
|
108
107
|
const budget = createByteBudgetTracker(limits);
|
|
109
108
|
await withStagedArchivePublication({ ...params, destinationGuard }, async (stagingDir) => {
|
|
110
109
|
const { select } = createArchiveEntrySelector({ ...params, rootDir: stagingDir });
|
|
111
110
|
const acceptedEntries = [];
|
|
112
|
-
for (const
|
|
111
|
+
for (const record of entries.values()) {
|
|
113
112
|
deadline.check();
|
|
114
|
-
const entryKind =
|
|
115
|
-
const relPath = select({ path:
|
|
113
|
+
const { entry, kind: entryKind, size, mode: archivedMode } = record;
|
|
114
|
+
const relPath = select({ path: record.name, kind: entryKind, size });
|
|
116
115
|
if (relPath === null)
|
|
117
116
|
continue;
|
|
118
117
|
if (entryKind === "symlink") {
|
|
119
|
-
throw new ArchiveSecurityError("entry-link", `zip entry is a link: ${
|
|
118
|
+
throw new ArchiveSecurityError("entry-link", `zip entry is a link: ${record.name}`);
|
|
120
119
|
}
|
|
121
120
|
if (entryKind === "other")
|
|
122
121
|
continue;
|
|
123
|
-
const mode =
|
|
122
|
+
const mode = resolveArchiveEntryMode({
|
|
123
|
+
kind: entry.dir ? "directory" : "file",
|
|
124
|
+
archivedMode,
|
|
125
|
+
policy: params.entryModes,
|
|
126
|
+
});
|
|
124
127
|
acceptedEntries.push({ path: relPath, kind: entry.dir ? "directory" : "file", mode });
|
|
125
128
|
const outPath = path.join(stagingDir, relPath);
|
|
126
129
|
await preparePrivateArchiveOutputPath({
|
|
@@ -128,12 +131,12 @@ async function extractZip(params) {
|
|
|
128
131
|
destinationRealDir: stagingDir,
|
|
129
132
|
relPath,
|
|
130
133
|
outPath,
|
|
131
|
-
originalPath:
|
|
134
|
+
originalPath: record.name,
|
|
132
135
|
isDirectory: entry.dir,
|
|
133
136
|
deadline,
|
|
134
137
|
});
|
|
135
138
|
if (!entry.dir) {
|
|
136
|
-
await writeZipFileEntry({
|
|
139
|
+
await writeZipFileEntry({ record, outPath, budget, deadline });
|
|
137
140
|
}
|
|
138
141
|
}
|
|
139
142
|
return acceptedEntries;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { replaceFileAtomic, replaceFileAtomicSync, type ReplaceFileAtomicFileSystem, type ReplaceFileAtomicOptions, type ReplaceFileAtomicResult, type ReplaceFileAtomicSyncFileSystem, type ReplaceFileAtomicSyncOptions, } from "./replace-file.js";
|
|
1
|
+
export { replaceFileAtomic, replaceFileAtomicSync, type ReplaceFileAtomicFileSystem, type ReplaceFileAtomicOptions, type ReplaceFileAtomicResult, type ReplaceFileAtomicDestinationState, type ReplaceFileAtomicSyncFileSystem, type ReplaceFileAtomicSyncOptions, } from "./replace-file.js";
|
|
2
2
|
export type { RenameIdentityPolicy } from "./pinned-write-types.js";
|
|
3
3
|
export type { ReplaceFileAtomicRestoreCleanup, ReplaceFileAtomicRestoreFailureDetails, ReplaceFileCopyFallbackRestorePolicy, ReplaceFileDestinationHardlinkPolicy, } from "./replace-file-copy-fallback.js";
|
|
4
4
|
export { writeTextAtomic, type WriteTextAtomicOptions } from "./text-atomic.js";
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import type { BigIntStats } from "node:fs";
|
|
2
2
|
import type { PinnedWriteParams, PublishedWriteIdentity } from "./pinned-write-types.js";
|
|
3
|
-
export type RootCopyPublicationReceipt = Readonly<{
|
|
3
|
+
export type RootCopyPublicationReceipt = Readonly<PublishedWriteIdentity & {
|
|
4
4
|
path: string;
|
|
5
|
-
dev: bigint;
|
|
6
|
-
ino: bigint;
|
|
7
5
|
}>;
|
|
8
6
|
export declare function createCopyPublicationObserver(path: string, notify?: (receipt: RootCopyPublicationReceipt) => void): {
|
|
9
7
|
onPublished(identity: PublishedWriteIdentity): void;
|
|
@@ -1,16 +1,12 @@
|
|
|
1
1
|
import { FsSafeError } from "./errors.js";
|
|
2
|
+
import { assertSynchronousCallbackResult } from "./mutation-authority.js";
|
|
2
3
|
export function createCopyPublicationObserver(path, notify) {
|
|
3
4
|
let rejected;
|
|
4
5
|
return {
|
|
5
6
|
onPublished(identity) {
|
|
6
7
|
const receipt = Object.freeze({ path, dev: identity.dev, ino: identity.ino });
|
|
7
8
|
try {
|
|
8
|
-
|
|
9
|
-
if (result !== null && (typeof result === "object" || typeof result === "function") &&
|
|
10
|
-
"then" in result && typeof result.then === "function") {
|
|
11
|
-
void Promise.resolve(result).catch(() => undefined);
|
|
12
|
-
throw new TypeError("onDestinationPublished must be synchronous");
|
|
13
|
-
}
|
|
9
|
+
assertSynchronousCallbackResult(notify?.(receipt), "onDestinationPublished");
|
|
14
10
|
}
|
|
15
11
|
catch (error) {
|
|
16
12
|
rejected = { error };
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { isPathInside } from "./path.js";
|
|
3
|
+
import { probePathCaseInsensitiveSync } from "./path-case.js";
|
|
4
|
+
import { observeMutationPath } from "./pinned-mutation-observation.js";
|
|
5
|
+
function foldedName(name) {
|
|
6
|
+
// Decompose before folding so combining marks precede an expanded Greek subscript.
|
|
7
|
+
// Default folding preserves dotless i even though its uppercase mapping is I.
|
|
8
|
+
return name.normalize("NFD").replace(/[^\u0131]+/gu, part => part.toLowerCase().toUpperCase().toLowerCase()).normalize("NFC");
|
|
9
|
+
}
|
|
10
|
+
function asciiCasePair(left, right) {
|
|
11
|
+
return /^[\x00-\x7f]*$/.test(left) && /^[\x00-\x7f]*$/.test(right) &&
|
|
12
|
+
left.toLowerCase() === right.toLowerCase();
|
|
13
|
+
}
|
|
14
|
+
/** Cache observations only within one synchronous policy check, never across mutations or callbacks. */
|
|
15
|
+
export function createMutationDenyMatcher() {
|
|
16
|
+
const observations = new Map();
|
|
17
|
+
const caseObservations = new Map();
|
|
18
|
+
const observe = (pathname) => {
|
|
19
|
+
if (!observations.has(pathname))
|
|
20
|
+
observations.set(pathname, observeMutationPath(pathname));
|
|
21
|
+
return observations.get(pathname);
|
|
22
|
+
};
|
|
23
|
+
const prospectiveInside = (parent, child) => {
|
|
24
|
+
const root = path.parse(parent).root;
|
|
25
|
+
if (root !== path.parse(child).root)
|
|
26
|
+
return false;
|
|
27
|
+
const left = parent.slice(root.length).split(path.sep);
|
|
28
|
+
const right = child.slice(root.length).split(path.sep).slice(0, left.length);
|
|
29
|
+
if (left.length !== right.length || !left.every((name, i) => foldedName(name) === foldedName(right[i]))) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
// Byte-identical relations were already handled without any observations.
|
|
33
|
+
if (left.every((name, i) => name === right[i]))
|
|
34
|
+
return false;
|
|
35
|
+
const alignedChild = path.join(root, ...right);
|
|
36
|
+
const a = observe(parent);
|
|
37
|
+
const b = observe(alignedChild);
|
|
38
|
+
if (!a || !b)
|
|
39
|
+
return true;
|
|
40
|
+
// Distinct existing ancestors are real objects, not prospective aliases.
|
|
41
|
+
if (a.canonicalAncestor !== b.canonicalAncestor ||
|
|
42
|
+
a.missingSegments.length === 0 || a.missingSegments.length !== b.missingSegments.length)
|
|
43
|
+
return false;
|
|
44
|
+
const missingLeft = a.missingSegments;
|
|
45
|
+
const missingRight = b.missingSegments;
|
|
46
|
+
if (!missingLeft.every((name, i) => foldedName(name) === foldedName(missingRight[i])))
|
|
47
|
+
return false;
|
|
48
|
+
const difference = missingLeft.findIndex((name, i) => name !== missingRight[i]);
|
|
49
|
+
if (difference === -1)
|
|
50
|
+
return true;
|
|
51
|
+
// A read-only ASCII observation can disprove aliasing at this existing parent.
|
|
52
|
+
// It cannot prove Unicode sensitivity or the behavior of a future directory.
|
|
53
|
+
if (difference === 0 && asciiCasePair(missingLeft[0], missingRight[0])) {
|
|
54
|
+
if (!caseObservations.has(a.canonicalAncestor)) {
|
|
55
|
+
caseObservations.set(a.canonicalAncestor, probePathCaseInsensitiveSync(path.join(a.canonicalAncestor, missingLeft[0]), { allowTemporaryProbe: false }));
|
|
56
|
+
}
|
|
57
|
+
if (caseObservations.get(a.canonicalAncestor) === false)
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
// Suffix probing writes directories. Policy admission has no authority for
|
|
61
|
+
// those side effects, so unproven case/normalization equivalence fails closed.
|
|
62
|
+
return true;
|
|
63
|
+
};
|
|
64
|
+
return (target, denied, prefix, protectAncestors = false) => {
|
|
65
|
+
if ((isPathInside(denied, target) && (prefix || isPathInside(target, denied))) ||
|
|
66
|
+
(protectAncestors && isPathInside(target, denied)))
|
|
67
|
+
return true;
|
|
68
|
+
return ((prefix || target.split(path.sep).length === denied.split(path.sep).length) &&
|
|
69
|
+
prospectiveInside(denied, target)) || (protectAncestors && prospectiveInside(target, denied));
|
|
70
|
+
};
|
|
71
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { createMutationDenyMatcher } from "./deny-mutation-match.js";
|
|
3
4
|
import { FsSafeError } from "./errors.js";
|
|
4
|
-
import { assertNoNulPathInput, isNotFoundPathError
|
|
5
|
+
import { assertNoNulPathInput, isNotFoundPathError } from "./path.js";
|
|
5
6
|
import { realpathSync } from "./realpath.js";
|
|
6
7
|
import { resolveExistingAncestor } from "./root-path-existing.js";
|
|
7
8
|
import { assertNoWindowsPathAlias, pathForWindowsFilesystem, resolvePathPreservingWindowsRoot, } from "./windows-path-alias.js";
|
|
@@ -65,6 +66,7 @@ export function assertMutationNotDenied(filePath, policy, options = {}, mode = "
|
|
|
65
66
|
const strict = mode === "sync-root-lock";
|
|
66
67
|
const comparablePaths = strict ? strictMutationComparablePaths : resolveMutationComparablePaths;
|
|
67
68
|
const targets = comparablePaths(filePath);
|
|
69
|
+
const matches = createMutationDenyMatcher();
|
|
68
70
|
// Validate one complete phase at a time. A paths denial must not read or
|
|
69
71
|
// canonicalize prefixes, and invalid later paths retain validation precedence.
|
|
70
72
|
for (const kind of ["paths", "prefixes"]) {
|
|
@@ -72,8 +74,7 @@ export function assertMutationNotDenied(filePath, policy, options = {}, mode = "
|
|
|
72
74
|
const deniedPaths = comparablePaths(entry);
|
|
73
75
|
for (const target of targets) {
|
|
74
76
|
for (const denied of deniedPaths) {
|
|
75
|
-
if ((
|
|
76
|
-
(options.protectAncestors === true && isPathInside(target, denied))) {
|
|
77
|
+
if (matches(target, denied, kind === "prefixes", options.protectAncestors)) {
|
|
77
78
|
throw new FsSafeError("denied-path", "path is denied by denyMutations policy");
|
|
78
79
|
}
|
|
79
80
|
}
|
|
@@ -36,7 +36,6 @@ export declare function createNearestExistingDirectoryGuard(rootReal: string, ta
|
|
|
36
36
|
export declare function createNearestExistingDirectoryGuard(rootReal: string, targetPath: string, options: {
|
|
37
37
|
bigint: boolean;
|
|
38
38
|
}): Promise<AnyAsyncDirectoryGuard>;
|
|
39
|
-
export declare function createNearestExistingSyncDirectoryGuard(rootReal: string, targetPath: string): SyncDirectoryGuard;
|
|
40
39
|
export declare function inspectDirectoryIdentity(dir: string, expected?: Pick<BigIntStats, "dev" | "ino">): Promise<BigIntStats>;
|
|
41
40
|
export declare function observeDirectoryIdentitySync(dir: string, options: {
|
|
42
41
|
bigint: true;
|
|
@@ -84,9 +84,6 @@ function assertDirectoryGuard(guard, mode) {
|
|
|
84
84
|
export async function createNearestExistingDirectoryGuard(rootReal, targetPath, options = { bigint: false }) {
|
|
85
85
|
return nearestExistingDirectoryGuard(rootReal, targetPath, "native", options);
|
|
86
86
|
}
|
|
87
|
-
export function createNearestExistingSyncDirectoryGuard(rootReal, targetPath) {
|
|
88
|
-
return nearestExistingDirectoryGuard(rootReal, targetPath, "normalized");
|
|
89
|
-
}
|
|
90
87
|
function nearestExistingDirectoryGuard(rootReal, targetPath, mode, options) {
|
|
91
88
|
assertNoWindowsPathAlias(rootReal, "filesystem");
|
|
92
89
|
assertNoWindowsPathAlias(targetPath, "filesystem");
|
|
@@ -1,5 +1,23 @@
|
|
|
1
|
-
import { type BigIntStats } from "node:fs";
|
|
2
|
-
|
|
1
|
+
import { type BigIntStats, type Stats } from "node:fs";
|
|
2
|
+
export type DirectoryModeChecks = {
|
|
3
|
+
check?: () => void;
|
|
4
|
+
beforeChmod?: () => Promise<void>;
|
|
5
|
+
};
|
|
6
|
+
export type DirectoryModeOwner = {
|
|
7
|
+
verify(check?: () => void): Promise<void>;
|
|
8
|
+
apply(mode: number, checks?: DirectoryModeChecks): Promise<void>;
|
|
9
|
+
close(): Promise<void>;
|
|
10
|
+
};
|
|
11
|
+
export declare function assertOwnedDirectory(expected: Stats | BigIntStats, actual: Stats | BigIntStats): void;
|
|
12
|
+
/** Serializes use and close: even a queued path-based fd operation retains its descriptor. */
|
|
13
|
+
export declare function ownDirectoryMode(params: {
|
|
14
|
+
inspect: () => Promise<number>;
|
|
15
|
+
chmod: (mode: number) => Promise<void>;
|
|
16
|
+
prepareChmod?: () => Promise<void>;
|
|
17
|
+
verifyChmod?: () => Promise<void>;
|
|
18
|
+
close: () => Promise<void>;
|
|
19
|
+
ignoreChmodError?: boolean;
|
|
20
|
+
}): DirectoryModeOwner;
|
|
3
21
|
/** Darwin descriptor inspection avoids requesting directory-content reads. */
|
|
4
22
|
export declare function nodeDarwinDirectoryMetadataFlags(): number;
|
|
5
23
|
export declare function nodeDirectorySearchOnlyFlags(): {
|
|
@@ -3,7 +3,83 @@ import fs from "node:fs/promises";
|
|
|
3
3
|
import { FsSafeError } from "./errors.js";
|
|
4
4
|
import { inspectDirectoryIdentity, inspectDirectoryIdentitySync } from "./directory-guard.js";
|
|
5
5
|
import { inspectFileIdentity, inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
6
|
-
import {
|
|
6
|
+
import { sameFileIdentityForCleanup } from "./file-identity.js";
|
|
7
|
+
export function assertOwnedDirectory(expected, actual) {
|
|
8
|
+
if (actual.isSymbolicLink() || !actual.isDirectory()) {
|
|
9
|
+
throw new FsSafeError("not-file", "directory mode target must be a real directory");
|
|
10
|
+
}
|
|
11
|
+
if (!sameFileIdentityForCleanup(expected, actual)) {
|
|
12
|
+
throw new FsSafeError("path-mismatch", "directory changed before its mode could be applied");
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/** Serializes use and close: even a queued path-based fd operation retains its descriptor. */
|
|
16
|
+
export function ownDirectoryMode(params) {
|
|
17
|
+
let pending = Promise.resolve();
|
|
18
|
+
let closing;
|
|
19
|
+
const enqueue = (run) => {
|
|
20
|
+
if (closing)
|
|
21
|
+
return Promise.reject(new FsSafeError("path-mismatch", "directory mode owner is closed"));
|
|
22
|
+
const operation = pending.then(run);
|
|
23
|
+
pending = operation.catch(() => undefined);
|
|
24
|
+
return operation;
|
|
25
|
+
};
|
|
26
|
+
return {
|
|
27
|
+
verify: (check) => enqueue(async () => {
|
|
28
|
+
check?.();
|
|
29
|
+
await params.inspect();
|
|
30
|
+
check?.();
|
|
31
|
+
}),
|
|
32
|
+
apply: (mode, checks = {}) => enqueue(async () => {
|
|
33
|
+
// inspect() reports permission bits only; chmod ignores file-type bits,
|
|
34
|
+
// so tolerate raw stat modes (e.g. S_IFDIR | 0o755) by masking up front.
|
|
35
|
+
mode &= 0o7777;
|
|
36
|
+
checks.check?.();
|
|
37
|
+
const currentMode = await params.inspect();
|
|
38
|
+
checks.check?.();
|
|
39
|
+
if (currentMode === mode && !checks.beforeChmod && !checks.check)
|
|
40
|
+
return;
|
|
41
|
+
if (currentMode !== mode) {
|
|
42
|
+
await params.prepareChmod?.();
|
|
43
|
+
checks.check?.();
|
|
44
|
+
}
|
|
45
|
+
await checks.beforeChmod?.();
|
|
46
|
+
checks.check?.();
|
|
47
|
+
// Hooks/ancestor checks can yield; recheck the original named association.
|
|
48
|
+
await params.inspect();
|
|
49
|
+
checks.check?.();
|
|
50
|
+
let dispatchDeadlineFailure;
|
|
51
|
+
if (currentMode !== mode) {
|
|
52
|
+
checks.check?.();
|
|
53
|
+
try {
|
|
54
|
+
await params.chmod(mode);
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
if (!params.ignoreChmodError)
|
|
58
|
+
throw error;
|
|
59
|
+
}
|
|
60
|
+
// Expiry cannot release the fd while post-dispatch verification is pending.
|
|
61
|
+
try {
|
|
62
|
+
checks.check?.();
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
dispatchDeadlineFailure = { error };
|
|
66
|
+
}
|
|
67
|
+
await params.verifyChmod?.();
|
|
68
|
+
}
|
|
69
|
+
const finalMode = await params.inspect();
|
|
70
|
+
if (dispatchDeadlineFailure)
|
|
71
|
+
throw dispatchDeadlineFailure.error;
|
|
72
|
+
checks.check?.();
|
|
73
|
+
if (!params.ignoreChmodError && finalMode !== mode) {
|
|
74
|
+
throw new FsSafeError("path-mismatch", "directory final mode could not be verified");
|
|
75
|
+
}
|
|
76
|
+
}),
|
|
77
|
+
close() {
|
|
78
|
+
closing ??= pending.then(params.close);
|
|
79
|
+
return closing;
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
7
83
|
/** Darwin descriptor inspection avoids requesting directory-content reads. */
|
|
8
84
|
export function nodeDarwinDirectoryMetadataFlags() {
|
|
9
85
|
if (process.platform !== "darwin" || (process.arch !== "x64" && process.arch !== "arm64")) {
|
|
@@ -70,11 +70,6 @@ function snapshotDirectoryMetadata(stat, identity) {
|
|
|
70
70
|
export function directoryReceiptAuthority(receipt) {
|
|
71
71
|
return authorities.get(receipt) ?? snapshotDirectoryReceipt(receipt);
|
|
72
72
|
}
|
|
73
|
-
function rememberReceipt(receipt, authority) {
|
|
74
|
-
authorities.set(receipt, authority);
|
|
75
|
-
identities.set(receipt.identity, authority);
|
|
76
|
-
return receipt;
|
|
77
|
-
}
|
|
78
73
|
export function ownDirectoryReceipt(receipt) {
|
|
79
74
|
return receiptFromAuthority(snapshotDirectoryReceipt(receipt));
|
|
80
75
|
}
|
|
@@ -83,11 +78,14 @@ export function copyRetainedDirectoryReceipt(receipt) {
|
|
|
83
78
|
return receiptFromAuthority(authority);
|
|
84
79
|
}
|
|
85
80
|
function receiptFromAuthority(authority) {
|
|
86
|
-
|
|
81
|
+
const receipt = {
|
|
87
82
|
path: authority.path,
|
|
88
83
|
realPath: authority.realPath,
|
|
89
84
|
identity: Object.assign(Object.create(fs.Stats.prototype), authority.metadata),
|
|
90
|
-
}
|
|
85
|
+
};
|
|
86
|
+
authorities.set(receipt, authority);
|
|
87
|
+
identities.set(receipt.identity, authority);
|
|
88
|
+
return receipt;
|
|
91
89
|
}
|
|
92
90
|
// The caller already admitted the exact observation and its paths.
|
|
93
91
|
export function createDirectoryReceiptFromIdentity(pathname, realPath, exactStat) {
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
function isValidUid(value) {
|
|
2
|
-
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
3
|
-
}
|
|
4
1
|
function unavailable(cause) {
|
|
5
2
|
return new Error("Effective user identity is unavailable.", {
|
|
6
3
|
...(cause === undefined ? {} : { cause }),
|
|
@@ -18,7 +15,7 @@ export function resolveEffectiveUid() {
|
|
|
18
15
|
catch (cause) {
|
|
19
16
|
throw unavailable(cause);
|
|
20
17
|
}
|
|
21
|
-
if (!
|
|
18
|
+
if (!(typeof uid === "number" && Number.isSafeInteger(uid) && uid >= 0)) {
|
|
22
19
|
throw unavailable();
|
|
23
20
|
}
|
|
24
21
|
return uid;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
export type FsSafeErrorCode = "already-exists" | "denied-path" | "device-path" | "hardlink" | "
|
|
1
|
+
export type FsSafeErrorCode = (typeof OPERATIONAL_CODE_VALUES)[number] | "already-exists" | "denied-path" | "device-path" | "hardlink" | "invalid-path" | "insecure-permissions" | "not-file" | "not-owned" | "outside-workspace" | "path-alias" | "path-mismatch" | "secret-exists" | "store-reentrant-update" | "symlink" | "too-large";
|
|
2
2
|
export type FsSafeErrorCategory = "policy" | "operational";
|
|
3
3
|
export type FsSafeErrorDetails = Readonly<Record<string, unknown>>;
|
|
4
|
+
declare const OPERATIONAL_CODE_VALUES: readonly ["helper-failed", "helper-unavailable", "not-empty", "not-found", "not-removable", "permission-unverified", "read-failed", "timeout", "unsupported-platform"];
|
|
4
5
|
export declare function categorizeFsSafeError(code: FsSafeErrorCode): FsSafeErrorCategory;
|
|
5
6
|
export declare class FsSafeError extends Error {
|
|
6
7
|
readonly code: FsSafeErrorCode;
|
|
@@ -11,3 +12,4 @@ export declare class FsSafeError extends Error {
|
|
|
11
12
|
details?: FsSafeErrorDetails;
|
|
12
13
|
});
|
|
13
14
|
}
|
|
15
|
+
export {};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const
|
|
1
|
+
const OPERATIONAL_CODE_VALUES = [
|
|
2
2
|
"helper-failed",
|
|
3
3
|
"helper-unavailable",
|
|
4
4
|
"not-empty",
|
|
@@ -8,7 +8,8 @@ const OPERATIONAL_CODES = new Set([
|
|
|
8
8
|
"read-failed",
|
|
9
9
|
"timeout",
|
|
10
10
|
"unsupported-platform",
|
|
11
|
-
]
|
|
11
|
+
];
|
|
12
|
+
const OPERATIONAL_CODES = new Set(OPERATIONAL_CODE_VALUES);
|
|
12
13
|
export function categorizeFsSafeError(code) {
|
|
13
14
|
return OPERATIONAL_CODES.has(code) ? "operational" : "policy";
|
|
14
15
|
}
|
|
@@ -22,9 +22,17 @@ export function sameFileIdentity(left, right, platform = process.platform) {
|
|
|
22
22
|
return (!isStatValueProvablyDifferent(left.dev, right.dev, platform) &&
|
|
23
23
|
!isStatValueProvablyDifferent(left.ino, right.ino, platform));
|
|
24
24
|
}
|
|
25
|
+
function sameCleanupStatValue(left, right) {
|
|
26
|
+
// Converting an unsafe number to bigint cannot recover an already rounded ID.
|
|
27
|
+
if (typeof left === "number" && !Number.isSafeInteger(left))
|
|
28
|
+
return false;
|
|
29
|
+
if (typeof right === "number" && !Number.isSafeInteger(right))
|
|
30
|
+
return false;
|
|
31
|
+
return sameStatValue(left, right);
|
|
32
|
+
}
|
|
25
33
|
export function sameFileIdentityForCleanup(left, right, platform = process.platform) {
|
|
26
34
|
if (platform !== "win32") {
|
|
27
|
-
return
|
|
35
|
+
return sameCleanupStatValue(left.dev, right.dev) && sameCleanupStatValue(left.ino, right.ino);
|
|
28
36
|
}
|
|
29
37
|
// A zero Windows device or inode is unknown, not proof that a pathname still
|
|
30
38
|
// names the object we created. Cleanup must fail closed rather than delete a
|
|
@@ -44,5 +52,5 @@ export function sameFileIdentityForCleanup(left, right, platform = process.platf
|
|
|
44
52
|
const rightIno = right.ino;
|
|
45
53
|
if (isZero(rightIno))
|
|
46
54
|
return false;
|
|
47
|
-
return
|
|
55
|
+
return sameCleanupStatValue(leftDev, rightDev) && sameCleanupStatValue(leftIno, rightIno);
|
|
48
56
|
}
|
|
@@ -66,7 +66,7 @@ export class SyncLockAcquisition {
|
|
|
66
66
|
}
|
|
67
67
|
catch (waitError) {
|
|
68
68
|
// Exhausted backoff must preserve the filesystem denial diagnosis.
|
|
69
|
-
if (waitError
|
|
69
|
+
if (waitError?.code === "file_lock_timeout")
|
|
70
70
|
throw error;
|
|
71
71
|
throw waitError;
|
|
72
72
|
}
|
|
@@ -30,7 +30,11 @@ export function defaultSyncShouldReclaim(snapshot, staleMs, nowMs) {
|
|
|
30
30
|
const createdAtMs = sidecarLockPayloadCreatedAtMs(snapshot.payload);
|
|
31
31
|
if (createdAtMs !== null)
|
|
32
32
|
return nowMs - createdAtMs > staleMs;
|
|
33
|
-
|
|
33
|
+
if (!snapshot.stat)
|
|
34
|
+
return true;
|
|
35
|
+
const mtimeMs = "mtimeNs" in snapshot.stat
|
|
36
|
+
? Number(snapshot.stat.mtimeNs) / 1e6 : snapshot.stat.mtimeMs;
|
|
37
|
+
return nowMs - mtimeMs > staleMs;
|
|
34
38
|
}
|
|
35
39
|
export function syncReclaimGuardExists(reclaimGuardPath) {
|
|
36
40
|
try {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { type SyncHeldLock } from "./file-lock-sync-admission.js";
|
|
2
|
+
import type { SidecarLockOptionFields } from "./sidecar-lock-types.js";
|
|
1
3
|
import type { FileLockSyncHandle } from "./file-lock-sync.js";
|
|
2
|
-
import { type SidecarLockSnapshot } from "./sidecar-lock-reclaim.js";
|
|
3
4
|
import { type FileLockSyncRootAuthority, type FileLockSyncRootPath } from "./file-lock-sync-root.js";
|
|
4
5
|
import { type FileLockSyncRootFileReceipt, type FileLockSyncRootSnapshot } from "./file-lock-sync-root-io.js";
|
|
5
6
|
type RootSyncHeldLockReleaseState = "active" | "releasing" | "exit-cleaning" | "released";
|
|
@@ -7,22 +8,14 @@ type RootSyncHeldLockHandleDisposition = {
|
|
|
7
8
|
held: RootSyncHeldLock;
|
|
8
9
|
released: boolean;
|
|
9
10
|
};
|
|
10
|
-
export type RootSyncHeldLock = {
|
|
11
|
+
export type RootSyncHeldLock = SidecarLockOptionFields<SyncHeldLock & {
|
|
11
12
|
deferredExitReleases?: Set<RootSyncHeldLockHandleDisposition>;
|
|
12
|
-
fd: number | undefined;
|
|
13
|
-
lockPath: string;
|
|
14
|
-
normalizedTargetPath: string;
|
|
15
|
-
parsePayload?: (raw: string) => unknown;
|
|
16
|
-
refCount: number;
|
|
17
|
-
reentrantOwner?: string;
|
|
18
13
|
releaseState: RootSyncHeldLockReleaseState;
|
|
19
14
|
revision: number;
|
|
20
15
|
rootAuthority: FileLockSyncRootAuthority;
|
|
21
16
|
rootPath: FileLockSyncRootPath;
|
|
22
17
|
rootReceipt: FileLockSyncRootFileReceipt;
|
|
23
|
-
|
|
24
|
-
timer?: NodeJS.Timeout;
|
|
25
|
-
};
|
|
18
|
+
}>;
|
|
26
19
|
export declare function readRootSidecarSnapshotSync(rootPath: FileLockSyncRootPath, parsePayload?: (raw: string) => unknown, onOpenFailure?: (error: unknown) => void, expectedReceipt?: FileLockSyncRootFileReceipt): FileLockSyncRootSnapshot | null;
|
|
27
20
|
export declare function getRootSyncHeldLocks(): Map<string, RootSyncHeldLock>;
|
|
28
21
|
export declare function ensureRootSyncExitCleanupRegistered(): void;
|
|
@@ -53,9 +53,6 @@ export function getRootSyncHeldLocks() {
|
|
|
53
53
|
}
|
|
54
54
|
return globalWithState[ROOT_SYNC_HELD_LOCKS_KEY];
|
|
55
55
|
}
|
|
56
|
-
function existingRootSyncHeldLocks() {
|
|
57
|
-
return globalThis[ROOT_SYNC_HELD_LOCKS_KEY];
|
|
58
|
-
}
|
|
59
56
|
function reactivateExactHeldLock(heldLocks, held, restoreConsumedReference) {
|
|
60
57
|
if (heldLocks.get(held.normalizedTargetPath) !== held || held.releaseState === "released") {
|
|
61
58
|
return false;
|
|
@@ -123,7 +120,7 @@ function releaseAllRootSyncHeldLocks() {
|
|
|
123
120
|
try {
|
|
124
121
|
// A newListener callback can invoke the candidate handler before
|
|
125
122
|
// registration succeeds. Do not create acquisition state in that window.
|
|
126
|
-
const heldLocks =
|
|
123
|
+
const heldLocks = globalThis[ROOT_SYNC_HELD_LOCKS_KEY];
|
|
127
124
|
if (!heldLocks)
|
|
128
125
|
return;
|
|
129
126
|
getSyncLockAdmissions().clear();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type BigIntStats
|
|
1
|
+
import { type BigIntStats } from "node:fs";
|
|
2
2
|
import type { RootDefaults } from "./root-options.js";
|
|
3
3
|
import { type FileLockSyncRootPath } from "./file-lock-sync-root.js";
|
|
4
4
|
export type ExactIdentity = Readonly<{
|
|
@@ -14,15 +14,12 @@ export type FileLockSyncRootFileReceipt = Readonly<{
|
|
|
14
14
|
identity: ExactIdentity;
|
|
15
15
|
parent: DirectoryReceipt;
|
|
16
16
|
}>;
|
|
17
|
-
export type FileLockSyncRootDirectoryReceipt =
|
|
18
|
-
identity: ExactIdentity;
|
|
19
|
-
parent: DirectoryReceipt;
|
|
20
|
-
}>;
|
|
17
|
+
export type FileLockSyncRootDirectoryReceipt = FileLockSyncRootFileReceipt;
|
|
21
18
|
export type FileLockSyncRootDiskSnapshot = {
|
|
22
19
|
ownershipToken?: never;
|
|
23
20
|
payload: unknown;
|
|
24
21
|
raw: string;
|
|
25
|
-
stat:
|
|
22
|
+
stat: BigIntStats;
|
|
26
23
|
};
|
|
27
24
|
export type FileLockSyncRootSnapshot = Readonly<{
|
|
28
25
|
receipt: FileLockSyncRootFileReceipt;
|
|
@@ -143,7 +143,7 @@ export function readFileLockSyncRootSnapshot(pathAuthority, options = {}) {
|
|
|
143
143
|
throw error;
|
|
144
144
|
}
|
|
145
145
|
assertRegularFile(after, pathAuthority.authority.hardlinks);
|
|
146
|
-
const stat = fs.fstatSync(fd);
|
|
146
|
+
const stat = fs.fstatSync(fd, { bigint: true });
|
|
147
147
|
if (expectedReceipt) {
|
|
148
148
|
assertRetainedParentCurrent(pathAuthority, parent);
|
|
149
149
|
}
|
|
@@ -1,19 +1,11 @@
|
|
|
1
1
|
import type { FileLockSyncAcquireOptions } from "./file-lock-sync.js";
|
|
2
|
-
import type {
|
|
3
|
-
type CapturedRootSyncAcquireOptions<TPayload extends Record<string, unknown>> = Readonly<{
|
|
4
|
-
compromiseCheckIntervalMs?: number;
|
|
5
|
-
lockPath?: string;
|
|
6
|
-
onCompromised?: (info: SidecarLockCompromisedInfo) => void;
|
|
2
|
+
import type { SidecarLockRetryOptions } from "./sidecar-lock-types.js";
|
|
3
|
+
type CapturedRootSyncAcquireOptions<TPayload extends Record<string, unknown>> = Readonly<Pick<FileLockSyncAcquireOptions<TPayload>, "compromiseCheckIntervalMs" | "lockPath" | "onCompromised" | "parsePayload" | "payload" | "reentrantOwner" | "staleRecovery" | "timeoutMs"> & {
|
|
7
4
|
optionsReceiver: FileLockSyncAcquireOptions<TPayload>;
|
|
8
|
-
parsePayload?: (raw: string) => unknown;
|
|
9
|
-
payload: () => TPayload;
|
|
10
|
-
reentrantOwner?: string;
|
|
11
5
|
retry: Readonly<SidecarLockRetryOptions>;
|
|
12
6
|
shouldReclaim?: FileLockSyncAcquireOptions<TPayload>["shouldReclaim"];
|
|
13
7
|
shouldRemoveStaleLock?: FileLockSyncAcquireOptions<TPayload>["shouldRemoveStaleLock"];
|
|
14
8
|
staleMs: number;
|
|
15
|
-
staleRecovery?: SidecarLockStaleRecovery;
|
|
16
|
-
timeoutMs?: number;
|
|
17
9
|
}>;
|
|
18
10
|
export declare function captureRootSyncAcquireOptions<TPayload extends Record<string, unknown>>(options: FileLockSyncAcquireOptions<TPayload>): CapturedRootSyncAcquireOptions<TPayload>;
|
|
19
11
|
export {};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type DenyMutationPolicy } from "./deny-mutations.js";
|
|
1
2
|
import { type RootContext } from "./root-context.js";
|
|
2
3
|
import type { Root } from "./root-impl.js";
|
|
3
4
|
import type { RootDefaults } from "./root-options.js";
|
|
@@ -7,10 +8,7 @@ export type FileLockSyncRootAuthority = Readonly<{
|
|
|
7
8
|
adapter: object;
|
|
8
9
|
context: RootContext;
|
|
9
10
|
assertBeforeMutation?: () => void;
|
|
10
|
-
denyMutations?: Readonly<
|
|
11
|
-
paths?: readonly string[];
|
|
12
|
-
prefixes?: readonly string[];
|
|
13
|
-
}>;
|
|
11
|
+
denyMutations?: Readonly<DenyMutationPolicy>;
|
|
14
12
|
hardlinks?: RootDefaults["hardlinks"];
|
|
15
13
|
mutationPolicy: RootPathResolutionPolicy;
|
|
16
14
|
readPolicy: RootPathResolutionPolicy;
|