@openclaw/matrix 2026.10.5-beta.4 → 2026.10.10-beta.2
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/dist/.setup/{approval-handler.runtime-rxLn8CbP.mjs → approval-handler.runtime-CsqMdj7h.mjs} +9 -9
- package/dist/.setup/{approval-reactions-DhB6gNPU.mjs → approval-reactions-D2pEUrFS.mjs} +1 -1
- package/dist/.setup/{channel-BcwGspPw.mjs → channel-Dw4_imdd.mjs} +132 -111
- package/dist/.setup/{channel.runtime-CUc6rbu1.mjs → channel.runtime-2EEtIlyu.mjs} +6 -6
- package/dist/.setup/{channel.setup-CwuYD_CT.mjs → channel.setup-Be8TYKMA.mjs} +8 -3
- package/dist/.setup/{cli-mbL9jCro.mjs → cli-CEmWh7wh.mjs} +99 -37
- package/dist/.setup/{cli-metadata-6MOGuyHq.mjs → cli-metadata-CBLGwJT6.mjs} +1 -1
- package/dist/.setup/{client-BlTvpEWC.mjs → client-BKiiI29H.mjs} +3 -3
- package/dist/.setup/{client-vPPbaxNp.mjs → client-C4Gibhzl.mjs} +94 -120
- package/dist/.setup/{client-bootstrap-D05xy1HB.mjs → client-bootstrap-kOIDV4z_.mjs} +3 -3
- package/dist/.setup/{create-client-CroC4BVc.mjs → create-client-BS8UYx6N.mjs} +23 -15
- package/dist/.setup/{credentials-Bf9PQ7xs.mjs → credentials-CPnJZaZB.mjs} +3 -5
- package/dist/.setup/{credentials-read-CqRiEAQa.mjs → credentials-read-D2ULV55A.mjs} +12 -4
- package/dist/.setup/{crypto-runtime-CpJ0b7vY.mjs → crypto-runtime-D8yY7kjD.mjs} +168 -290
- package/dist/.setup/crypto-state-store-D4wrCvFs.mjs +185 -0
- package/dist/.setup/crypto-store-ownership-Z_Y5WRKr.mjs +174 -0
- package/dist/.setup/crypto-unsafe-state-doctor-C8szCYmf.mjs +54 -0
- package/dist/.setup/{directory-live-B1WbfSHo.mjs → directory-live-BpSz-FQB.mjs} +1 -1
- package/dist/.setup/{draft-stream-Bdrh7dRt.mjs → draft-stream-DYf0th1n.mjs} +1 -1
- package/dist/.setup/{exec-approvals-BgNWWJg1.mjs → exec-approvals-DaM-5OA5.mjs} +33 -43
- package/dist/.setup/{file-sync-store-BdKx_gMi.mjs → file-sync-store-CUH8EAJq.mjs} +3 -3
- package/dist/.setup/idb-persistence-BHTYPRe_.mjs +233 -0
- package/dist/.setup/idb-persistence-lock-Bh3ZLIsE.mjs +14 -0
- package/dist/.setup/{messages-B1CZqU8u.mjs → messages-_XHVg3do.mjs} +3 -18
- package/dist/.setup/{monitor-Dk_fQL1k.mjs → monitor-DRUSJ_Ev.mjs} +31 -39
- package/dist/.setup/{onboarding-CrSexor_.mjs → onboarding-CK9Ksgio.mjs} +3 -3
- package/dist/.setup/{outbound-LR3SqPIq.mjs → outbound-D5Z_4FBg.mjs} +28 -36
- package/dist/.setup/{profile-update-C7bjaGR6.mjs → profile-update--Pf3jCXD.mjs} +2 -2
- package/dist/.setup/{reaction-events-CTSvOlXO.mjs → reaction-events-BG9spedG.mjs} +6 -6
- package/dist/.setup/{resolve-targets-BVVbbvP6.mjs → resolve-targets-BJ5sIKeM.mjs} +1 -1
- package/dist/.setup/{retired-state-DW5pLH05.mjs → retired-state-DOCruzWd.mjs} +10 -10
- package/dist/.setup/{room-info-9Uqlpt1V.mjs → room-info-BE7bbX_c.mjs} +3 -3
- package/dist/.setup/runtime-setter-api-i2MOXoT3.mjs +2 -0
- package/dist/.setup/{sdk-Cfc6m7fY.mjs → sdk-ByU8So5S.mjs} +242 -139
- package/dist/.setup/secret-contract-DxyzKSzf.mjs +90 -0
- package/dist/.setup/secret-contract-api-Dukgvike.mjs +2 -0
- package/dist/.setup/{send-LCOuTVc1.mjs → send-BprM-Wmx.mjs} +19 -42
- package/dist/.setup/{setup-bootstrap-CV-hHBXu.mjs → setup-bootstrap-DmznRqYe.mjs} +2 -6
- package/dist/.setup/{setup-core-CKnNTfgD.mjs → setup-core-DcCk1_Tv.mjs} +4 -7
- package/dist/.setup/setup-plugin-api-BU6UGksW.mjs +2 -0
- package/dist/.setup/{startup-verification-CgfUb4yJ.mjs → startup-verification-UU8DUvi4.mjs} +5 -13
- package/dist/.setup/state-layout-walk-BQMgMykU.mjs +27 -0
- package/dist/.setup/{storage-DkC6bHgA.mjs → storage-l8V0XsGM.mjs} +7 -166
- package/dist/.setup/{subagent-hooks-D8jyeQsO.mjs → subagent-hooks-CSY4iwa_.mjs} +7 -6
- package/dist/.setup/{sync-cache-state-D4G-fi3t.mjs → sync-cache-state-Cnm4TCEI.mjs} +1 -1
- package/dist/.setup/thread-bindings-rrwQAS6R.mjs +620 -0
- package/dist/.setup/{thread-bindings-shared-CbsFNbAY.mjs → thread-bindings-shared-CPjUA0Ib.mjs} +26 -5
- package/dist/.setup/{tool-actions-DAp6_XSJ.mjs → tool-actions-DBrk6Yei.mjs} +9 -11
- package/dist/.setup/{verification-B3VswneT.mjs → verification--qUbmRd-.mjs} +2 -2
- package/dist/api.js +6 -6
- package/dist/auth-presence.js +16 -2
- package/dist/channel-plugin-api.js +1 -2
- package/dist/cli-metadata.js +1 -2
- package/dist/contract-api.js +2 -2
- package/dist/doctor-contract-api.js +20 -27
- package/dist/index.js +38 -12
- package/dist/plugin-entry.handlers.runtime.js +32 -5
- package/dist/runtime-api.js +3 -3
- package/dist/runtime-setter-api.js +1 -2
- package/dist/secret-contract-api.js +1 -2
- package/dist/session-binding-contract-api.js +1 -1
- package/dist/setup-entry.js +3 -3
- package/dist/setup-plugin-api.js +1 -2
- package/dist/subagent-hooks-api.js +1 -1
- package/dist/thread-binding-api.js +1 -2
- package/dist/thread-bindings-runtime.js +1 -2
- package/node_modules/@openclaw/fs-safe/CHANGELOG.md +148 -0
- package/node_modules/@openclaw/fs-safe/README.md +20 -4
- package/node_modules/@openclaw/fs-safe/dist/advanced.d.ts +2 -1
- package/node_modules/@openclaw/fs-safe/dist/advanced.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-merge.js +28 -3
- package/node_modules/@openclaw/fs-safe/dist/archive-parser.wasm +0 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-read.js +11 -5
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-loader.js +4 -2
- package/node_modules/@openclaw/fs-safe/dist/archive.js +2 -2
- package/node_modules/@openclaw/fs-safe/dist/atomic-io.js +2 -2
- package/node_modules/@openclaw/fs-safe/dist/bounded-read.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/bounded-read.js +9 -2
- package/node_modules/@openclaw/fs-safe/dist/clone-metadata.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/clone-metadata.js +2 -1
- package/node_modules/@openclaw/fs-safe/dist/config.d.ts +6 -0
- package/node_modules/@openclaw/fs-safe/dist/config.js +5 -0
- package/node_modules/@openclaw/fs-safe/dist/copy-file-input.d.ts +3 -1
- package/node_modules/@openclaw/fs-safe/dist/copy-file-input.js +8 -7
- package/node_modules/@openclaw/fs-safe/dist/copy-link.d.ts +26 -0
- package/node_modules/@openclaw/fs-safe/dist/copy-link.js +170 -0
- package/node_modules/@openclaw/fs-safe/dist/copy-metadata.d.ts +9 -0
- package/node_modules/@openclaw/fs-safe/dist/copy-metadata.js +39 -0
- package/node_modules/@openclaw/fs-safe/dist/copy-path-admission.js +11 -2
- package/node_modules/@openclaw/fs-safe/dist/copy-root-file-sync.d.ts +2 -4
- package/node_modules/@openclaw/fs-safe/dist/copy-root-file-sync.js +4 -2
- package/node_modules/@openclaw/fs-safe/dist/copy-tree-portable.js +3 -6
- package/node_modules/@openclaw/fs-safe/dist/copy.js +3 -3
- package/node_modules/@openclaw/fs-safe/dist/create.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/creation-boundary.js +7 -4
- package/node_modules/@openclaw/fs-safe/dist/darwin-acl.d.ts +12 -0
- package/node_modules/@openclaw/fs-safe/dist/darwin-acl.js +29 -0
- package/node_modules/@openclaw/fs-safe/dist/directory-durability.d.ts +7 -4
- package/node_modules/@openclaw/fs-safe/dist/directory-guard.js +20 -5
- package/node_modules/@openclaw/fs-safe/dist/directory-mode-node.d.ts +3 -0
- package/node_modules/@openclaw/fs-safe/dist/directory-mode-node.js +27 -2
- package/node_modules/@openclaw/fs-safe/dist/directory-receipt.d.ts +7 -7
- package/node_modules/@openclaw/fs-safe/dist/directory-receipt.js +9 -4
- package/node_modules/@openclaw/fs-safe/dist/durability.d.ts +2 -1
- package/node_modules/@openclaw/fs-safe/dist/entry-publication.js +2 -2
- package/node_modules/@openclaw/fs-safe/dist/exact-identity.d.ts +17 -0
- package/node_modules/@openclaw/fs-safe/dist/exact-identity.js +29 -0
- package/node_modules/@openclaw/fs-safe/dist/file-handle-transfer.js +4 -2
- package/node_modules/@openclaw/fs-safe/dist/file-hash.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/file-hash.js +11 -9
- package/node_modules/@openclaw/fs-safe/dist/file-identity.d.ts +7 -1
- package/node_modules/@openclaw/fs-safe/dist/file-identity.js +27 -5
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-admission.js +1 -2
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync.js +8 -1
- package/node_modules/@openclaw/fs-safe/dist/file-lock.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/file-lock.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/file-write-lease.d.ts +9 -0
- package/node_modules/@openclaw/fs-safe/dist/file-write-lease.js +42 -0
- package/node_modules/@openclaw/fs-safe/dist/guarded-mkdir.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/guarded-mkdir.js +22 -15
- package/node_modules/@openclaw/fs-safe/dist/guest-native-python.js +3 -0
- package/node_modules/@openclaw/fs-safe/dist/hardlink-fallback.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/hardlink-fallback.js +5 -0
- package/node_modules/@openclaw/fs-safe/dist/index.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/index.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/json-durable-queue-ownership.js +16 -3
- package/node_modules/@openclaw/fs-safe/dist/local-file-descriptor.js +24 -13
- package/node_modules/@openclaw/fs-safe/dist/native-binding.d.ts +85 -18
- package/node_modules/@openclaw/fs-safe/dist/native-config.d.ts +5 -0
- package/node_modules/@openclaw/fs-safe/dist/native-config.js +11 -0
- package/node_modules/@openclaw/fs-safe/dist/native-directory-observation.js +6 -4
- package/node_modules/@openclaw/fs-safe/dist/native-fallback-warning.d.ts +2 -1
- package/node_modules/@openclaw/fs-safe/dist/native-fallback-warning.js +26 -2
- package/node_modules/@openclaw/fs-safe/dist/native-guidance.d.ts +3 -0
- package/node_modules/@openclaw/fs-safe/dist/native-guidance.js +21 -0
- package/node_modules/@openclaw/fs-safe/dist/native-move-fallback.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/native-move-fallback.js +2 -0
- package/node_modules/@openclaw/fs-safe/dist/native-operations.js +8 -6
- package/node_modules/@openclaw/fs-safe/dist/native-parent-admission.d.ts +44 -4
- package/node_modules/@openclaw/fs-safe/dist/native-parent-admission.js +85 -15
- package/node_modules/@openclaw/fs-safe/dist/native-pinned-write-windows.d.ts +3 -3
- package/node_modules/@openclaw/fs-safe/dist/native-pinned-write-windows.js +33 -6
- package/node_modules/@openclaw/fs-safe/dist/native-pinned-write.d.ts +4 -1
- package/node_modules/@openclaw/fs-safe/dist/native-pinned-write.js +22 -3
- package/node_modules/@openclaw/fs-safe/dist/native-policy-directory-observation.js +4 -3
- package/node_modules/@openclaw/fs-safe/dist/native-policy-parent.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/native-policy-parent.js +3 -0
- package/node_modules/@openclaw/fs-safe/dist/native-staged-file-metadata.d.ts +3 -0
- package/node_modules/@openclaw/fs-safe/dist/native-staged-file-metadata.js +16 -0
- package/node_modules/@openclaw/fs-safe/dist/native-staged-file.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/native-staged-file.js +15 -5
- package/node_modules/@openclaw/fs-safe/dist/native-stat-identity.d.ts +3 -0
- package/node_modules/@openclaw/fs-safe/dist/native-stat-identity.js +7 -0
- package/node_modules/@openclaw/fs-safe/dist/native-status.d.ts +17 -0
- package/node_modules/@openclaw/fs-safe/dist/native-status.js +30 -0
- package/node_modules/@openclaw/fs-safe/dist/native.d.ts +31 -4
- package/node_modules/@openclaw/fs-safe/dist/native.js +107 -9
- package/node_modules/@openclaw/fs-safe/dist/opened-realpath.js +9 -1
- package/node_modules/@openclaw/fs-safe/dist/overwrite-file-handle.js +4 -1
- package/node_modules/@openclaw/fs-safe/dist/owner-dacl-batch.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/owner-dacl.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/permission-exec.js +9 -1
- package/node_modules/@openclaw/fs-safe/dist/permissions-public.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/permissions-public.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/permissions-windows.js +83 -47
- package/node_modules/@openclaw/fs-safe/dist/pinned-open.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/pinned-open.js +8 -3
- package/node_modules/@openclaw/fs-safe/dist/pinned-write-input.js +13 -0
- package/node_modules/@openclaw/fs-safe/dist/pinned-write-staged.js +10 -8
- package/node_modules/@openclaw/fs-safe/dist/pinned-write-types.d.ts +2 -1
- package/node_modules/@openclaw/fs-safe/dist/pinned-write.js +7 -5
- package/node_modules/@openclaw/fs-safe/dist/pipe.d.ts +9 -0
- package/node_modules/@openclaw/fs-safe/dist/pipe.js +26 -0
- package/node_modules/@openclaw/fs-safe/dist/private-directory.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/private-producer-handoff.js +2 -5
- package/node_modules/@openclaw/fs-safe/dist/private-temp-workspace.d.ts +3 -2
- package/node_modules/@openclaw/fs-safe/dist/private-temp-workspace.js +38 -10
- package/node_modules/@openclaw/fs-safe/dist/publish-copy-stage.js +92 -10
- package/node_modules/@openclaw/fs-safe/dist/publish-file-failure.d.ts +3 -0
- package/node_modules/@openclaw/fs-safe/dist/publish-file-failure.js +3 -0
- package/node_modules/@openclaw/fs-safe/dist/publish-file.d.ts +5 -3
- package/node_modules/@openclaw/fs-safe/dist/publish-file.js +26 -20
- package/node_modules/@openclaw/fs-safe/dist/read-opened-file.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/read-opened-file.js +3 -4
- package/node_modules/@openclaw/fs-safe/dist/realpath.js +32 -0
- package/node_modules/@openclaw/fs-safe/dist/regular-file.d.ts +4 -0
- package/node_modules/@openclaw/fs-safe/dist/regular-file.js +11 -15
- package/node_modules/@openclaw/fs-safe/dist/replace-file-descriptor.js +3 -2
- package/node_modules/@openclaw/fs-safe/dist/root-copy.d.ts +18 -0
- package/node_modules/@openclaw/fs-safe/dist/root-copy.js +128 -0
- package/node_modules/@openclaw/fs-safe/dist/root-create-native.d.ts +8 -13
- package/node_modules/@openclaw/fs-safe/dist/root-create-native.js +252 -27
- package/node_modules/@openclaw/fs-safe/dist/root-directory-creation.js +5 -1
- package/node_modules/@openclaw/fs-safe/dist/root-directory-list-types.d.ts +11 -8
- package/node_modules/@openclaw/fs-safe/dist/root-directory-list.js +35 -13
- package/node_modules/@openclaw/fs-safe/dist/root-file-open-batch-sync.d.ts +8 -0
- package/node_modules/@openclaw/fs-safe/dist/root-file-open-batch-sync.js +108 -0
- package/node_modules/@openclaw/fs-safe/dist/root-file.d.ts +7 -0
- package/node_modules/@openclaw/fs-safe/dist/root-file.js +15 -0
- package/node_modules/@openclaw/fs-safe/dist/root-impl.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/root-impl.js +154 -175
- package/node_modules/@openclaw/fs-safe/dist/root-mkdir-native.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/root-mkdir-native.js +239 -0
- package/node_modules/@openclaw/fs-safe/dist/root-move-noreplace.js +37 -15
- package/node_modules/@openclaw/fs-safe/dist/root-native-creation-path.d.ts +32 -0
- package/node_modules/@openclaw/fs-safe/dist/root-native-creation-path.js +56 -0
- package/node_modules/@openclaw/fs-safe/dist/root-options.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/root-public-types.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/root-remove-native.js +95 -16
- package/node_modules/@openclaw/fs-safe/dist/root-remove.js +6 -4
- package/node_modules/@openclaw/fs-safe/dist/root-walk.js +23 -19
- package/node_modules/@openclaw/fs-safe/dist/root-write-admission.js +8 -6
- package/node_modules/@openclaw/fs-safe/dist/root-write-publication.js +5 -1
- package/node_modules/@openclaw/fs-safe/dist/secure-file-windows.js +3 -1
- package/node_modules/@openclaw/fs-safe/dist/secure-file.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/secure-file.js +10 -4
- package/node_modules/@openclaw/fs-safe/dist/sibling-staged-file.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.js +2 -2
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-retained.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-target.js +8 -1
- package/node_modules/@openclaw/fs-safe/dist/staged-directory.d.ts +13 -0
- package/node_modules/@openclaw/fs-safe/dist/staged-directory.js +29 -0
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-child-admission.js +5 -3
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-descriptor.d.ts +8 -0
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-descriptor.js +13 -2
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-owner.js +71 -3
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-ownership.d.ts +2 -1
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-ownership.js +22 -0
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-types.d.ts +3 -0
- package/node_modules/@openclaw/fs-safe/dist/test-hooks.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/test-hooks.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/trash.js +2 -2
- package/node_modules/@openclaw/fs-safe/dist/types.d.ts +4 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-native.js +2 -2
- package/node_modules/@openclaw/fs-safe/dist/windows-appcontainer.d.ts +5 -0
- package/node_modules/@openclaw/fs-safe/dist/windows-appcontainer.js +39 -0
- package/node_modules/@openclaw/fs-safe/dist/windows-owner.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/windows-owner.js +25 -36
- package/node_modules/@openclaw/fs-safe/dist/windows-security-command.js +24 -5
- package/node_modules/@openclaw/fs-safe/dist/windows-security-facts.d.ts +2 -1
- package/node_modules/@openclaw/fs-safe/dist/windows-security-facts.js +4 -0
- package/node_modules/@openclaw/fs-safe/dist/windows-security-helper.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/windows-security-helper.exe +0 -0
- package/node_modules/@openclaw/fs-safe/dist/windows-security-helper.js +7 -0
- package/node_modules/@openclaw/fs-safe/dist/windows-test-hooks.d.ts +16 -0
- package/node_modules/@openclaw/fs-safe/dist/windows-test-hooks.js +51 -0
- package/node_modules/@openclaw/fs-safe/dist/write-file-handle.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/write-file-handle.js +12 -10
- package/node_modules/@openclaw/fs-safe/docs/advanced.md +2 -1
- package/node_modules/@openclaw/fs-safe/docs/archive.md +16 -0
- package/node_modules/@openclaw/fs-safe/docs/config.md +71 -1
- package/node_modules/@openclaw/fs-safe/docs/contributing.md +45 -5
- package/node_modules/@openclaw/fs-safe/docs/copy.md +45 -5
- package/node_modules/@openclaw/fs-safe/docs/durability.md +34 -7
- package/node_modules/@openclaw/fs-safe/docs/errors.md +8 -0
- package/node_modules/@openclaw/fs-safe/docs/guest.md +2 -0
- package/node_modules/@openclaw/fs-safe/docs/install.md +47 -27
- package/node_modules/@openclaw/fs-safe/docs/migrating-to-0.5.md +6 -6
- package/node_modules/@openclaw/fs-safe/docs/migrating-to-0.6.md +1 -1
- package/node_modules/@openclaw/fs-safe/docs/native-helper.md +79 -6
- package/node_modules/@openclaw/fs-safe/docs/native.md +279 -8
- package/node_modules/@openclaw/fs-safe/docs/permissions.md +64 -15
- package/node_modules/@openclaw/fs-safe/docs/pipe.md +81 -0
- package/node_modules/@openclaw/fs-safe/docs/platform-capabilities.md +63 -0
- package/node_modules/@openclaw/fs-safe/docs/public-api.md +10 -0
- package/node_modules/@openclaw/fs-safe/docs/queue-link-free-design.md +237 -0
- package/node_modules/@openclaw/fs-safe/docs/quickstart.md +23 -3
- package/node_modules/@openclaw/fs-safe/docs/reading.md +56 -2
- package/node_modules/@openclaw/fs-safe/docs/regular-file.md +2 -0
- package/node_modules/@openclaw/fs-safe/docs/secret-file.md +5 -5
- package/node_modules/@openclaw/fs-safe/docs/secure-file.md +1 -1
- package/node_modules/@openclaw/fs-safe/docs/security-model.md +35 -0
- package/node_modules/@openclaw/fs-safe/docs/sidecar-lock.md +31 -13
- package/node_modules/@openclaw/fs-safe/docs/staged-file.md +18 -0
- package/node_modules/@openclaw/fs-safe/docs/store.md +15 -0
- package/node_modules/@openclaw/fs-safe/docs/temp.md +39 -0
- package/node_modules/@openclaw/fs-safe/docs/testing.md +13 -0
- package/node_modules/@openclaw/fs-safe/docs/types.md +51 -81
- package/node_modules/@openclaw/fs-safe/docs/walk.md +4 -1
- package/node_modules/@openclaw/fs-safe/docs/watch.md +3 -2
- package/node_modules/@openclaw/fs-safe/docs/writing.md +121 -14
- package/node_modules/@openclaw/fs-safe/package.json +21 -12
- 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-freebsd-arm64/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-freebsd-arm64/package.json +29 -0
- package/node_modules/@openclaw/fs-safe-freebsd-x64/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-freebsd-x64/package.json +29 -0
- 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-arm64-msvc/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-win32-arm64-msvc/package.json +29 -0
- 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/typebox/build/value/clean/from_object.mjs +1 -1
- package/node_modules/typebox/package.json +1 -1
- package/openclaw.plugin.json +3 -0
- package/package.json +7 -6
- package/dist/.setup/runtime-setter-api-DtxDAM-x.mjs +0 -3
- package/dist/.setup/secret-contract-DSBRvhdZ.mjs +0 -95
- package/dist/.setup/secret-contract-api-DzEJ-X7n.mjs +0 -3
- package/dist/.setup/setup-plugin-api-BDAYk1a5.mjs +0 -3
- package/dist/.setup/thread-bindings-CT4P-OSZ.mjs +0 -365
- package/node_modules/@openclaw/fs-safe/dist/windows-security-bridge.cs +0 -336
- package/node_modules/@openclaw/fs-safe/dist/windows-security-bridge.ps1 +0 -90
|
@@ -10,6 +10,9 @@
|
|
|
10
10
|
|
|
11
11
|
Capability-style filesystem roots for Node.js apps that handle untrusted relative paths.
|
|
12
12
|
|
|
13
|
+
Platform-specific additions include [Linux write leases, Darwin ACL inspection,
|
|
14
|
+
and Windows test fixtures](docs/platform-capabilities.md).
|
|
15
|
+
|
|
13
16
|
Think Go's `os.Root` / `OpenInRoot` or Rust's [`cap-std`](https://github.com/bytecodealliance/cap-std), but for Node. Hand `root()` a trusted directory and you get back a handle whose every method resolves relative paths against it and defends against `..`, symlink swaps, hardlink aliases, and TOCTOU rename races. The exact containment strength is reported per mechanism: Linux `openat2` opens are kernel-atomic; guarded Linux fallback, macOS, Windows, and JavaScript paths are best-effort.
|
|
14
17
|
|
|
15
18
|
```ts
|
|
@@ -81,6 +84,12 @@ kernel-atomic. Read the [native helper policy](docs/native-helper.md) and
|
|
|
81
84
|
[operation/platform matrix](docs/security-model.md#native-root-mutation-capabilities)
|
|
82
85
|
when concurrent mutation is in scope.
|
|
83
86
|
|
|
87
|
+
For doctor/startup checks, use
|
|
88
|
+
[`await getFsSafeNativeStatus({ probe: true })`](docs/config.md#getfssafenativestatus-options)
|
|
89
|
+
from `@openclaw/fs-safe/config` to report whether the native addon is active,
|
|
90
|
+
why it is unavailable, and what to do next. Calling it without options only reads
|
|
91
|
+
the cache. Status is per module instance; Workers must be checked separately.
|
|
92
|
+
|
|
84
93
|
## Migrating from the Python helper
|
|
85
94
|
|
|
86
95
|
Version 0.5 replaced the Python worker with prebuilt native bindings. Follow the
|
|
@@ -113,9 +122,14 @@ operation; per-call options handle exceptions. See the [Root reference](docs/roo
|
|
|
113
122
|
|
|
114
123
|
`write()` replaces contents by default. Use `create()` or `overwrite: false`
|
|
115
124
|
when an existing destination should be an error. `move()` defaults to no clobber
|
|
116
|
-
and requires native support
|
|
117
|
-
|
|
118
|
-
|
|
125
|
+
and requires native support. On Linux, `auto` can replace a concurrently created
|
|
126
|
+
empty directory if no-replace rename is unsupported; use native `require` for
|
|
127
|
+
strict refusal. See the [move fallback contract](docs/writing.md#fs-move-from-to-options).
|
|
128
|
+
Pass `overwrite: true` when replacement is intended.
|
|
129
|
+
|
|
130
|
+
Staged JavaScript copies and creates also work when hardlinks are refused
|
|
131
|
+
(including Android/Termux), using a verified empty placeholder before atomic
|
|
132
|
+
replacement. See the [visibility and concurrency tradeoffs](docs/writing.md#publication-without-hardlinks).
|
|
119
133
|
|
|
120
134
|
See [Writing](docs/writing.md) for copy sources, mutation authority, symlink
|
|
121
135
|
policy, writable handles, and bounded removal, and [Creation](docs/creation.md)
|
|
@@ -159,6 +173,8 @@ and error exports. Prefer focused subpaths when a consumer needs a narrower
|
|
|
159
173
|
contract. Low-level helpers that OpenClaw needs to compose higher-level APIs are grouped under
|
|
160
174
|
`@openclaw/fs-safe/advanced` instead of being separate public leaf contracts.
|
|
161
175
|
|
|
176
|
+
Use [`@openclaw/fs-safe/pipe`](docs/pipe.md) for native-owned, reopenable anonymous pipes and one-shot child input.
|
|
177
|
+
|
|
162
178
|
See the [complete subpath catalogue](docs/install.md#subpath-exports) for every entry point and its contents.
|
|
163
179
|
|
|
164
180
|
## Failure semantics in the name
|
|
@@ -236,7 +252,7 @@ updates:
|
|
|
236
252
|
import { fileStore } from "@openclaw/fs-safe/store";
|
|
237
253
|
|
|
238
254
|
const files = fileStore({ rootDir: "/safe/workspace/state", private: true });
|
|
239
|
-
const store = files.json("settings.json", { lock: true });
|
|
255
|
+
const store = files.json<{ enabled: boolean }>("settings.json", { lock: true });
|
|
240
256
|
|
|
241
257
|
await store.updateOr({ enabled: false }, (current) => ({ ...current, enabled: true }));
|
|
242
258
|
```
|
|
@@ -17,7 +17,7 @@ export { readFileWindowFully, readFileWindowFullySync, type ReadFileWindowOption
|
|
|
17
17
|
export { writeFileWindowFully, type WriteFileWindowOptions } from "./write-file-handle.js";
|
|
18
18
|
export { assertNoUnsafeDeviceReadPath, isUnsafeDeviceReadPath, matchUnsafeDeviceReadPath, type UnsafeDeviceReadPathMatch, type UnsafeDeviceReadPathOptions, type UnsafeDeviceReadPathReason, } from "./device-path.js";
|
|
19
19
|
export { assertAbsolutePathInput, canonicalPathFromExistingAncestor, ensureAbsoluteDirectory, findExistingAncestor, resolveAbsolutePathForRead, resolveAbsolutePathForWrite, type AbsolutePathSymlinkPolicy, type EnsureAbsoluteDirectoryOptions, type EnsureAbsoluteDirectoryResult, type ResolvedAbsolutePath, type ResolvedWritableAbsolutePath, } from "./absolute-path.js";
|
|
20
|
-
export { sameFileIdentity, type FileIdentityStat } from "./file-identity.js";
|
|
20
|
+
export { sameFileIdentity, type FileIdentityStat, type FileIdentityInput, type ExactFileIdentity, type ExactIdentityReceipt } from "./file-identity.js";
|
|
21
21
|
export { sanitizeUntrustedFileName } from "./filename.js";
|
|
22
22
|
export { pathExists, pathExistsSync } from "./fs.js";
|
|
23
23
|
export { resolveLocalPathFromRootsSync, readLocalFileFromRoots, type LocalRootsInputOptions, type LocalRootsPathResult, type LocalRootsReadResult, type ReadLocalFileFromRootsOptions, type ResolveLocalPathFromRootsSyncOptions, } from "./local-roots.js";
|
|
@@ -26,6 +26,7 @@ export { formatPosixMode } from "./permissions.js";
|
|
|
26
26
|
export { configureFsSafeLocks, getFsSafeLockConfig, type FsSafeLockConfig, } from "./lock-config.js";
|
|
27
27
|
export { assertNoHardlinkedFinalPath, assertNoPathAliasEscape, PATH_ALIAS_POLICIES, type PathAliasPolicy, } from "./path-policy.js";
|
|
28
28
|
export { openRootFile, openRootFileSync, canUseRootFileOpen, matchRootFileOpenFailure, type OpenRootFileParams, type OpenRootFileSyncParams, type RootFileOpenFailure, type RootFileOpenFailureReason, type RootFileOpenResult, } from "./root-file.js";
|
|
29
|
+
export { createRootFileOpenBatchSync, type RootFileOpenBatchSync } from "./root-file-open-batch-sync.js";
|
|
29
30
|
export { ROOT_PATH_ALIAS_POLICIES, resolvePathViaExistingAncestorSync, resolveRootPath, resolveRootPathSync, type ResolvedRootPath, type RootPathAliasPolicy, } from "./root-path.js";
|
|
30
31
|
export { ensureDirectoryWithinRoot, pathScope, resolveExistingPathsWithinRoot, resolvePathWithinRoot, resolvePathsWithinRoot, resolveStrictExistingPathsWithinRoot, resolveWritablePathWithinRoot, type PathScope, type PathScopeOptions, type PathScopeResolveOptions, } from "./root-paths.js";
|
|
31
32
|
export { safeDirName, safePathSegmentHashed, safePathSegmentHashedV2, resolveSafeInstallDir, assertCanonicalPathWithinBase, } from "./install-path.js";
|
|
@@ -27,6 +27,7 @@ export { formatPosixMode } from "./permissions.js";
|
|
|
27
27
|
export { configureFsSafeLocks, getFsSafeLockConfig, } from "./lock-config.js";
|
|
28
28
|
export { assertNoHardlinkedFinalPath, assertNoPathAliasEscape, PATH_ALIAS_POLICIES, } from "./path-policy.js";
|
|
29
29
|
export { openRootFile, openRootFileSync, canUseRootFileOpen, matchRootFileOpenFailure, } from "./root-file.js";
|
|
30
|
+
export { createRootFileOpenBatchSync } from "./root-file-open-batch-sync.js";
|
|
30
31
|
export { ROOT_PATH_ALIAS_POLICIES, resolvePathViaExistingAncestorSync, resolveRootPath, resolveRootPathSync, } from "./root-path.js";
|
|
31
32
|
export { ensureDirectoryWithinRoot, pathScope, resolveExistingPathsWithinRoot, resolvePathWithinRoot, resolvePathsWithinRoot, resolveStrictExistingPathsWithinRoot, resolveWritablePathWithinRoot, } from "./root-paths.js";
|
|
32
33
|
export { safeDirName, safePathSegmentHashed, safePathSegmentHashedV2, resolveSafeInstallDir, assertCanonicalPathWithinBase, } from "./install-path.js";
|
|
@@ -17,6 +17,7 @@ import { syncFileBestEffortSync } from "./file-sync.js";
|
|
|
17
17
|
import { finalizeArchivePublication } from "./archive-durability.js";
|
|
18
18
|
import { assertNoWindowsPathAlias } from "./windows-path-alias.js";
|
|
19
19
|
import { inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
20
|
+
import { ArchiveSecurityError } from "./archive-errors.js";
|
|
20
21
|
export async function withStagedArchivePublication(params, extract) {
|
|
21
22
|
await withStagedArchiveDestination({
|
|
22
23
|
destinationRealDir: params.destinationGuard.realPath,
|
|
@@ -101,6 +102,23 @@ async function mergeTree(params, publication, durable = true, entryUmask = 0) {
|
|
|
101
102
|
assertDirectoryIdentityGuard(destinationGuard);
|
|
102
103
|
check();
|
|
103
104
|
for (const ancestor of ancestors) {
|
|
105
|
+
// Verify every retained ancestor, including its current name and physical path.
|
|
106
|
+
// Shallow chains cost less to verify directly than crossing the native bridge.
|
|
107
|
+
if (ancestors.length >= 4 && ancestor.owner.verifyCanonical) {
|
|
108
|
+
let verified;
|
|
109
|
+
try {
|
|
110
|
+
verified = await ancestor.owner.verifyCanonical();
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
if (error instanceof FsSafeError || isNotFoundPathError(error)) {
|
|
114
|
+
throw createArchiveSymlinkTraversalError(path.relative(destinationDir, ancestor.guard.dir));
|
|
115
|
+
}
|
|
116
|
+
throw error;
|
|
117
|
+
}
|
|
118
|
+
check();
|
|
119
|
+
if (verified)
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
104
122
|
assertDirectoryIdentityGuard(ancestor.guard);
|
|
105
123
|
check();
|
|
106
124
|
await ancestor.owner.verify(check);
|
|
@@ -156,15 +174,22 @@ async function mergeTree(params, publication, durable = true, entryUmask = 0) {
|
|
|
156
174
|
await ownExtractionDestinationMutation(params.deadline, async () => {
|
|
157
175
|
await assertGuards();
|
|
158
176
|
assertSourceFrontier();
|
|
159
|
-
const
|
|
177
|
+
const guard = await createDirectoryIdentityGuard(destinationPath).catch((error) => {
|
|
178
|
+
if (error instanceof ArchiveSecurityError && error.code === "destination-symlink") {
|
|
179
|
+
throw createArchiveSymlinkTraversalError(originalPath);
|
|
180
|
+
}
|
|
181
|
+
throw error;
|
|
182
|
+
});
|
|
183
|
+
check();
|
|
184
|
+
const owner = await pinNodeDirectoryForMode(destinationPath, {
|
|
185
|
+
expectedIdentity: guard.stat, canonicalPath: guard.realPath,
|
|
186
|
+
}).catch((error) => {
|
|
160
187
|
if (error instanceof FsSafeError && (error.code === "not-file" || error.code === "path-mismatch")) {
|
|
161
188
|
throw createArchiveSymlinkTraversalError(originalPath);
|
|
162
189
|
}
|
|
163
190
|
throw error;
|
|
164
191
|
});
|
|
165
192
|
try {
|
|
166
|
-
check();
|
|
167
|
-
const guard = await createDirectoryIdentityGuard(destinationPath);
|
|
168
193
|
check();
|
|
169
194
|
await owner.verify(check);
|
|
170
195
|
ancestors.push({ guard, owner });
|
|
Binary file
|
|
@@ -17,7 +17,7 @@ import { FsSafeError } from "./errors.js";
|
|
|
17
17
|
import { inspectFileIdentity } from "./strict-file-identity.js";
|
|
18
18
|
import { resolveReadOpenFlags } from "./read-open-flags.js";
|
|
19
19
|
import { realpathSync } from "./realpath.js";
|
|
20
|
-
import { getNativeBinding } from "./native.js";
|
|
20
|
+
import { assertNativeCapabilities, getNativeBinding } from "./native.js";
|
|
21
21
|
import { admitZipBuffer } from "./archive-zip-admission.js";
|
|
22
22
|
import { validateNativeZipManifest } from "./archive-zip-manifest.js";
|
|
23
23
|
import { resolveExtractLimits, resolveTarMeterLimits } from "./archive-limits.js";
|
|
@@ -199,9 +199,15 @@ async function readNativeBufferEntry(native, buffer, kind, requested, displayPat
|
|
|
199
199
|
try {
|
|
200
200
|
const signal = new AbortController().signal;
|
|
201
201
|
const limits = resolveTarMeterLimits();
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
202
|
+
let reader;
|
|
203
|
+
if (kind === "zip") {
|
|
204
|
+
assertNativeCapabilities(native, "openZipBufferNative");
|
|
205
|
+
reader = await native.openZipBufferNative(buffer, limits, AbortSignal.any([signal]));
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
assertNativeCapabilities(native, "openTarBufferNative");
|
|
209
|
+
reader = await native.openTarBufferNative(buffer, kind, limits, AbortSignal.any([signal]));
|
|
210
|
+
}
|
|
205
211
|
const manifest = reader.entries;
|
|
206
212
|
if (kind === "zip")
|
|
207
213
|
validateNativeZipManifest(manifest, zipEntries);
|
|
@@ -227,7 +233,7 @@ export async function readArchiveEntry(archivePath, entryPath, options) {
|
|
|
227
233
|
const zipEntries = [];
|
|
228
234
|
if (kind === "zip")
|
|
229
235
|
admitZipBuffer(buffer, resolveExtractLimits(), entry => { zipEntries.push(entry); });
|
|
230
|
-
const native = getNativeBinding();
|
|
236
|
+
const native = getNativeBinding(kind === "zip" ? "openZipBufferNative" : "openTarBufferNative");
|
|
231
237
|
if (native)
|
|
232
238
|
return await readNativeBufferEntry(native, buffer, kind, requestedEntry, entryPath, maxBytes, zipEntries);
|
|
233
239
|
return kind === "zip" ? await readZipEntry(buffer, requestedEntry, maxBytes, zipEntries)
|
|
@@ -118,11 +118,13 @@ export async function loadAdmittedZipArchive(buffer, admitted) {
|
|
|
118
118
|
const entries = new Map();
|
|
119
119
|
for (const [name, entry, physical] of normalized) {
|
|
120
120
|
const appendSlash = physical.kind === "directory" && !name.endsWith("/");
|
|
121
|
-
|
|
121
|
+
// JSZip reduces a relative root marker such as "./" to an empty name.
|
|
122
|
+
// Keep its directory spelling relative when restoring the terminal slash.
|
|
123
|
+
const normalizedName = appendSlash ? `${name || "."}/` : name;
|
|
122
124
|
if (Object.hasOwn(files, normalizedName))
|
|
123
125
|
collision();
|
|
124
126
|
if (appendSlash) {
|
|
125
|
-
entry.name = `${entry.name}/`;
|
|
127
|
+
entry.name = `${entry.name || "."}/`;
|
|
126
128
|
}
|
|
127
129
|
files[normalizedName] = entry;
|
|
128
130
|
entries.set(physical.portableKey, createAdmittedZipEntry(entry, normalizedName, physical));
|
|
@@ -154,7 +154,7 @@ export async function extractArchive(params) {
|
|
|
154
154
|
const label = kind === "zip" ? "extract zip" : "extract tar";
|
|
155
155
|
const limits = resolveExtractLimits(params.limits);
|
|
156
156
|
const tarLimits = resolveTarMeterLimits(limits);
|
|
157
|
-
const native = getNativeBinding();
|
|
157
|
+
const native = getNativeBinding("inspectArchiveNative", "extractArchiveNative");
|
|
158
158
|
assertNoWindowsPathAlias(archivePath, "filesystem", "archive source uses a Windows filesystem namespace alias");
|
|
159
159
|
assertNoWindowsPathAlias(destDir, "filesystem", "archive destination uses a Windows filesystem namespace alias");
|
|
160
160
|
// Read the declared public fields before private adapters copy options: class
|
|
@@ -282,7 +282,7 @@ export async function inspectTarArchive(params) {
|
|
|
282
282
|
const onFiltered = resolveArchiveFilteredEntryPolicy(params.onFiltered);
|
|
283
283
|
const limits = resolveExtractLimits(params.limits);
|
|
284
284
|
const tarLimits = resolveTarMeterLimits(limits);
|
|
285
|
-
const native = getNativeBinding();
|
|
285
|
+
const native = getNativeBinding("inspectArchiveNative");
|
|
286
286
|
assertNoWindowsPathAlias(archivePath, "filesystem", "archive source uses a Windows filesystem namespace alias");
|
|
287
287
|
return await withExtractionDeadline(params.timeoutMs, "inspect tar", async (deadline) => {
|
|
288
288
|
const staged = await stageArchiveFileForExtraction({ archivePath, limits, deadline });
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import syncFs, {} from "node:fs";
|
|
2
2
|
import fs, {} from "node:fs/promises";
|
|
3
|
-
import { readBoundedSync } from "./bounded-read.js";
|
|
3
|
+
import { readBoundedSync, readFileHandleBounded } from "./bounded-read.js";
|
|
4
4
|
import { recursiveMkdirPath } from "./recursive-mkdir-path.js";
|
|
5
5
|
import { inspectFileIdentity, inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
6
6
|
import { sleep, sleepSync } from "./timing.js";
|
|
@@ -207,7 +207,7 @@ export class AtomicFile {
|
|
|
207
207
|
}
|
|
208
208
|
*readFile(sizeHint) {
|
|
209
209
|
if (typeof this.resource !== "number")
|
|
210
|
-
return yield* wait(this.resource
|
|
210
|
+
return yield* wait(readFileHandleBounded(this.resource, Infinity));
|
|
211
211
|
if (sizeHint === undefined)
|
|
212
212
|
return this.io.syncFs.readFileSync(this.resource);
|
|
213
213
|
const fd = this.resource;
|
|
@@ -13,6 +13,7 @@ export declare function readBoundedAsync(maxBytes: number, readChunk: (scratch:
|
|
|
13
13
|
* force an unbounded allocation.
|
|
14
14
|
*/
|
|
15
15
|
export declare function readFileHandleBounded(handle: ReadableFileHandle, maxBytes: number): Promise<Buffer>;
|
|
16
|
+
export declare function readFileHandleBoundedWithSize(handle: ReadableFileHandle, maxBytes: number, initialSize?: number): Promise<Buffer>;
|
|
16
17
|
/** Async bounded read from a numeric descriptor. The caller owns the descriptor. */
|
|
17
18
|
export declare function readFileDescriptorBounded(fd: number, maxBytes: number): Promise<Buffer>;
|
|
18
19
|
export declare function readBoundedSync(maxBytes: number, readChunk: (scratch: Buffer, length: number) => number, options?: {
|
|
@@ -73,11 +73,18 @@ export async function readBoundedAsync(maxBytes, readChunk, options = {}) {
|
|
|
73
73
|
* force an unbounded allocation.
|
|
74
74
|
*/
|
|
75
75
|
export async function readFileHandleBounded(handle, maxBytes) {
|
|
76
|
+
return await readFileHandleBoundedWithSize(handle, maxBytes);
|
|
77
|
+
}
|
|
78
|
+
// Internal: the admitted size bounds the first allocation, never the read.
|
|
79
|
+
export function readFileHandleBoundedWithSize(handle, maxBytes, initialSize) {
|
|
76
80
|
normalizeMaxBytes(maxBytes);
|
|
77
81
|
const fd = "fd" in handle && typeof handle.fd === "number" ? handle.fd : undefined;
|
|
78
|
-
return
|
|
82
|
+
return readBoundedAsync(maxBytes, async (scratch, length) => {
|
|
79
83
|
return (await handle.read(scratch, 0, length, null)).bytesRead;
|
|
80
|
-
}, {
|
|
84
|
+
}, {
|
|
85
|
+
initialSize,
|
|
86
|
+
observeRegularFileSize: fd === undefined ? undefined : () => regularFileSize(fd),
|
|
87
|
+
});
|
|
81
88
|
}
|
|
82
89
|
function regularFileSize(fd) {
|
|
83
90
|
try {
|
|
@@ -6,7 +6,7 @@ import { getNativeBinding, requireNativeBinding } from "./native.js";
|
|
|
6
6
|
*/
|
|
7
7
|
export async function readCloneFileMetadata(files) {
|
|
8
8
|
const paths = files.map(assertAbsolutePathInput);
|
|
9
|
-
const native = process.platform === "darwin" ? requireNativeBinding() : getNativeBinding();
|
|
9
|
+
const native = process.platform === "darwin" ? requireNativeBinding("readCloneFileMetadata") : getNativeBinding("readCloneFileMetadata");
|
|
10
10
|
if (!native)
|
|
11
11
|
return paths.map(() => undefined);
|
|
12
12
|
const results = await native.readCloneFileMetadata(paths);
|
|
@@ -19,6 +19,7 @@ export async function readCloneFileMetadata(files) {
|
|
|
19
19
|
result.readUInt32LE(20) !== 0x100)
|
|
20
20
|
return undefined;
|
|
21
21
|
return {
|
|
22
|
+
exactIdentity: Object.freeze({ dev: BigInt(result.readUInt32LE(24)), ino: result.readBigInt64LE(76) }),
|
|
22
23
|
dev: result.readUInt32LE(24),
|
|
23
24
|
type: result.readUInt32LE(28),
|
|
24
25
|
mtimeSec: Number(result.readBigInt64LE(32)),
|
|
@@ -1,2 +1,8 @@
|
|
|
1
|
+
import type { FsSafeNativeStatus } from "./native-status.js";
|
|
1
2
|
export { configureFsSafeNative, getFsSafeNativeConfig, type FsSafeNativeConfig, type FsSafeNativeMode, } from "./native-config.js";
|
|
3
|
+
export type { FsSafeNativeStatus, FsSafeNativeStatusReason, } from "./native-status.js";
|
|
2
4
|
export { configureFsSafeLocks, getFsSafeLockConfig, type FsSafeLockConfig, } from "./lock-config.js";
|
|
5
|
+
/** Read native status without loading the native module on config import. */
|
|
6
|
+
export declare function getFsSafeNativeStatus(options?: {
|
|
7
|
+
probe?: boolean;
|
|
8
|
+
}): Promise<FsSafeNativeStatus>;
|
|
@@ -1,2 +1,7 @@
|
|
|
1
1
|
export { configureFsSafeNative, getFsSafeNativeConfig, } from "./native-config.js";
|
|
2
2
|
export { configureFsSafeLocks, getFsSafeLockConfig, } from "./lock-config.js";
|
|
3
|
+
/** Read native status without loading the native module on config import. */
|
|
4
|
+
export async function getFsSafeNativeStatus(options) {
|
|
5
|
+
const { readFsSafeNativeStatus } = await import("./native-status.js");
|
|
6
|
+
return readFsSafeNativeStatus(options);
|
|
7
|
+
}
|
|
@@ -3,19 +3,21 @@ import type { FileHandle } from "node:fs/promises";
|
|
|
3
3
|
import { type CopyCloneMode } from "./copy-policy.js";
|
|
4
4
|
import { type NativeBinding } from "./native.js";
|
|
5
5
|
import { type NativeFileCopyResult } from "./native-binding.js";
|
|
6
|
+
import type { CopyMetadata } from "./copy-metadata.js";
|
|
6
7
|
export type CopyFileInput = {
|
|
7
8
|
kind: "file";
|
|
8
9
|
handle: FileHandle;
|
|
9
10
|
size: number;
|
|
10
11
|
clone: CopyCloneMode;
|
|
11
12
|
signal?: AbortSignal;
|
|
13
|
+
metadata?: CopyMetadata;
|
|
12
14
|
verifySource(): Promise<void>;
|
|
13
15
|
};
|
|
14
16
|
export declare function resolveFileCopyCloneMode(mode?: CopyCloneMode): CopyCloneMode;
|
|
15
17
|
export declare function assertCopySourceCurrent(source: {
|
|
16
18
|
handle: FileHandle;
|
|
17
19
|
realPath: string;
|
|
18
|
-
}, identity: BigIntStats):
|
|
20
|
+
}, identity: BigIntStats): void;
|
|
19
21
|
export declare function writeCopyFileToFd(fd: number, input: CopyFileInput, maxBytes?: number, assertBeforeMutation?: () => void): Promise<void>;
|
|
20
22
|
export declare function createNativeCopyFile(native: NativeBinding, input: CopyFileInput, parentFd: number, basename: string, maxBytes: number | undefined): Promise<NativeFileCopyResult | undefined>;
|
|
21
23
|
export declare function assertNativeCopyCompleted(input: CopyFileInput, copied?: NativeFileCopyResult): void;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import fs, {} from "node:fs";
|
|
2
2
|
import { resolveCopyCloneMode } from "./copy-policy.js";
|
|
3
3
|
import { FsSafeError } from "./errors.js";
|
|
4
|
-
import { getNativeBinding } from "./native.js";
|
|
4
|
+
import { getNativeBinding, selectNativeBinding } from "./native.js";
|
|
5
5
|
import { captureNativeFdClose } from "./native-binding.js";
|
|
6
|
-
import {
|
|
6
|
+
import { inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
7
7
|
import { transferFileHandle } from "./file-handle-transfer.js";
|
|
8
8
|
export function resolveFileCopyCloneMode(mode) {
|
|
9
9
|
const clone = resolveCopyCloneMode(mode, "never");
|
|
@@ -12,9 +12,9 @@ export function resolveFileCopyCloneMode(mode) {
|
|
|
12
12
|
}
|
|
13
13
|
return clone;
|
|
14
14
|
}
|
|
15
|
-
export
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
export function assertCopySourceCurrent(source, identity) {
|
|
16
|
+
inspectFileIdentitySync(() => fs.fstatSync(source.handle.fd, { bigint: true }), identity);
|
|
17
|
+
inspectFileIdentitySync(() => {
|
|
18
18
|
const current = fs.lstatSync(source.realPath, { bigint: true });
|
|
19
19
|
if (current.isSymbolicLink() || !current.isFile()) {
|
|
20
20
|
throw new FsSafeError("path-mismatch", "copy source path changed");
|
|
@@ -29,7 +29,8 @@ export async function writeCopyFileToFd(fd, input, maxBytes, assertBeforeMutatio
|
|
|
29
29
|
}
|
|
30
30
|
export async function createNativeCopyFile(native, input, parentFd, basename, maxBytes) {
|
|
31
31
|
input.signal?.throwIfAborted();
|
|
32
|
-
|
|
32
|
+
const copier = selectNativeBinding(native, "copyFileExclusive");
|
|
33
|
+
if (!copier) {
|
|
33
34
|
if (input.clone === "always") {
|
|
34
35
|
throw new FsSafeError("helper-unavailable", "native file cloning is unavailable");
|
|
35
36
|
}
|
|
@@ -39,7 +40,7 @@ export async function createNativeCopyFile(native, input, parentFd, basename, ma
|
|
|
39
40
|
const nativeSignal = input.signal ? AbortSignal.any([input.signal]) : undefined;
|
|
40
41
|
try {
|
|
41
42
|
// The caller adopts this descriptor before observing a later cancellation.
|
|
42
|
-
return await
|
|
43
|
+
return await copier.copyFileExclusive(input.handle.fd, parentFd, basename, input.clone, maxBytes !== undefined && Number.isFinite(maxBytes) ? maxBytes : undefined, nativeSignal);
|
|
43
44
|
}
|
|
44
45
|
catch (error) {
|
|
45
46
|
const code = error?.code;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type BigIntStats } from "node:fs";
|
|
2
|
+
import { type AnyAsyncDirectoryGuard } from "./directory-guard.js";
|
|
3
|
+
import type { NativeBinding } from "./native-binding.js";
|
|
4
|
+
import type { PinnedWriteParams } from "./pinned-write-types.js";
|
|
5
|
+
export type CopyLinkInput = {
|
|
6
|
+
kind: "link";
|
|
7
|
+
stageBeforePublish: true;
|
|
8
|
+
sourcePath: string;
|
|
9
|
+
identity: BigIntStats;
|
|
10
|
+
target?: Buffer;
|
|
11
|
+
preserveMetadata: boolean;
|
|
12
|
+
signal?: AbortSignal;
|
|
13
|
+
verifySource(): Promise<void>;
|
|
14
|
+
};
|
|
15
|
+
/** Shared by tree byte copying and per-file copying; never resolves the target. */
|
|
16
|
+
export declare function readCopyLinkTarget(source: string): Promise<Buffer>;
|
|
17
|
+
export declare function assertCopyLinkAvailable(): void;
|
|
18
|
+
export declare function admitCopyLink(sourcePath: string, preserveMetadata: boolean, signal?: AbortSignal): Promise<CopyLinkInput | undefined>;
|
|
19
|
+
/** The caller has admitted the destination parent under Root's mutation policy. */
|
|
20
|
+
export declare function copyLinkAtParent(params: PinnedWriteParams, input: CopyLinkInput, parent: AnyAsyncDirectoryGuard, native?: {
|
|
21
|
+
binding: NativeBinding;
|
|
22
|
+
fd: number;
|
|
23
|
+
}): Promise<{
|
|
24
|
+
dev: bigint;
|
|
25
|
+
ino: bigint;
|
|
26
|
+
}>;
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import fs, {} from "node:fs";
|
|
3
|
+
import fsp from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { assertAsyncDirectoryGuard, assertSyncDirectoryGuard, createAsyncDirectoryGuard } from "./directory-guard.js";
|
|
6
|
+
import { FsSafeError } from "./errors.js";
|
|
7
|
+
import { statIdentityFromNative } from "./native-stat-identity.js";
|
|
8
|
+
import { getNativeBinding } from "./native.js";
|
|
9
|
+
import { isFsSafeNativeRequired } from "./native-config.js";
|
|
10
|
+
import { inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
11
|
+
import { timestampSeconds } from "./copy-metadata.js";
|
|
12
|
+
import { syncDirectoryBestEffort } from "./directory-durability.js";
|
|
13
|
+
import { noReplaceUnavailable } from "./native-noreplace.js";
|
|
14
|
+
import { createSuppressedError } from "./suppressed-error.js";
|
|
15
|
+
/** Shared by tree byte copying and per-file copying; never resolves the target. */
|
|
16
|
+
export async function readCopyLinkTarget(source) {
|
|
17
|
+
return await fsp.readlink(source, { encoding: "buffer" });
|
|
18
|
+
}
|
|
19
|
+
export function assertCopyLinkAvailable() {
|
|
20
|
+
const native = getNativeBinding();
|
|
21
|
+
if (process.platform === "darwin" && !native?.createCopySymlink) {
|
|
22
|
+
throw new FsSafeError("helper-unavailable", "exclusive macOS link copying requires the native helper; link(2) follows its source");
|
|
23
|
+
}
|
|
24
|
+
if (process.platform === "win32" && (!native?.copyLinkExclusive || !native.publishCopyLink || !native.removeCopyLink)) {
|
|
25
|
+
throw new FsSafeError("helper-unavailable", "copying Windows links without following them requires the native helper");
|
|
26
|
+
}
|
|
27
|
+
if (native && process.platform !== "win32" && !native.createCopySymlink) {
|
|
28
|
+
throw new FsSafeError("helper-unavailable", "native link copying is unavailable");
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export async function admitCopyLink(sourcePath, preserveMetadata, signal) {
|
|
32
|
+
const parent = await createAsyncDirectoryGuard(path.dirname(sourcePath), { bigint: true });
|
|
33
|
+
sourcePath = path.join(parent.realPath, path.basename(sourcePath));
|
|
34
|
+
const identity = fs.lstatSync(sourcePath, { bigint: true });
|
|
35
|
+
if (!identity.isSymbolicLink())
|
|
36
|
+
return undefined;
|
|
37
|
+
const target = process.platform === "win32" ? undefined : await readCopyLinkTarget(sourcePath);
|
|
38
|
+
const verifySource = async () => {
|
|
39
|
+
signal?.throwIfAborted();
|
|
40
|
+
await assertAsyncDirectoryGuard(parent);
|
|
41
|
+
const current = inspectFileIdentitySync(() => fs.lstatSync(sourcePath, { bigint: true }), identity);
|
|
42
|
+
if (!current.isSymbolicLink() || current.mtimeNs !== identity.mtimeNs || current.ctimeNs !== identity.ctimeNs) {
|
|
43
|
+
throw new FsSafeError("path-mismatch", "copy source link changed");
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
await verifySource();
|
|
47
|
+
return { kind: "link", stageBeforePublish: true, sourcePath, identity, target, preserveMetadata, signal, verifySource };
|
|
48
|
+
}
|
|
49
|
+
/** The caller has admitted the destination parent under Root's mutation policy. */
|
|
50
|
+
export async function copyLinkAtParent(params, input, parent, native) {
|
|
51
|
+
const temporaryName = `.fs-safe-${randomUUID()}.tmp`;
|
|
52
|
+
const temporaryPath = path.join(parent.realPath, temporaryName);
|
|
53
|
+
const destination = path.join(parent.realPath, params.basename);
|
|
54
|
+
let identity;
|
|
55
|
+
let sourceParent;
|
|
56
|
+
let failure;
|
|
57
|
+
const assertStage = () => {
|
|
58
|
+
assertSyncDirectoryGuard(parent);
|
|
59
|
+
const stat = inspectFileIdentitySync(() => fs.lstatSync(temporaryPath, { bigint: true }), identity);
|
|
60
|
+
if (!stat.isSymbolicLink() || stat.nlink !== 1n)
|
|
61
|
+
throw new FsSafeError("path-mismatch", "copy link stage changed");
|
|
62
|
+
};
|
|
63
|
+
try {
|
|
64
|
+
await input.verifySource();
|
|
65
|
+
await assertAsyncDirectoryGuard(parent);
|
|
66
|
+
if (process.platform === "win32") {
|
|
67
|
+
if (!native?.binding.copyLinkExclusive)
|
|
68
|
+
throw new FsSafeError("helper-unavailable", "native Windows link copying is unavailable");
|
|
69
|
+
const sourceGuard = await createAsyncDirectoryGuard(path.dirname(input.sourcePath), { bigint: true });
|
|
70
|
+
sourceParent = await fsp.open(sourceGuard.realPath, fs.constants.O_RDONLY);
|
|
71
|
+
inspectFileIdentitySync(() => fs.fstatSync(sourceParent.fd, { bigint: true }), sourceGuard.stat);
|
|
72
|
+
await input.verifySource();
|
|
73
|
+
params.assertBeforeMutation?.();
|
|
74
|
+
assertSyncDirectoryGuard(parent);
|
|
75
|
+
identity = native.binding.copyLinkExclusive(sourceParent.fd, path.basename(input.sourcePath), native.fd, temporaryName, input.identity.dev, input.identity.ino, input.preserveMetadata, params.mode);
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
if (native && !native.binding.createCopySymlink)
|
|
79
|
+
throw new FsSafeError("helper-unavailable", "native link copying is unavailable");
|
|
80
|
+
params.assertBeforeMutation?.();
|
|
81
|
+
assertSyncDirectoryGuard(parent);
|
|
82
|
+
if (native?.binding.createCopySymlink) {
|
|
83
|
+
const created = native.binding.createCopySymlink(native.fd, temporaryName, input.target, params.mode, input.preserveMetadata ? input.identity.atimeNs : undefined, input.preserveMetadata ? input.identity.mtimeNs : undefined);
|
|
84
|
+
identity = { dev: statIdentityFromNative(created.dev), ino: statIdentityFromNative(created.ino) };
|
|
85
|
+
if (created.errorCode)
|
|
86
|
+
throw Object.assign(new Error(created.errorMessage), { code: created.errorCode });
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
fs.symlinkSync(input.target, temporaryPath);
|
|
90
|
+
identity = fs.lstatSync(temporaryPath, { bigint: true });
|
|
91
|
+
}
|
|
92
|
+
if (!native && input.preserveMetadata) {
|
|
93
|
+
assertStage();
|
|
94
|
+
fs.lutimesSync(temporaryPath, timestampSeconds(input.identity.atimeNs), timestampSeconds(input.identity.mtimeNs));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
assertStage();
|
|
98
|
+
await input.verifySource();
|
|
99
|
+
params.assertBeforeMutation?.();
|
|
100
|
+
input.signal?.throwIfAborted();
|
|
101
|
+
assertStage();
|
|
102
|
+
if (process.platform === "win32") {
|
|
103
|
+
native.binding.publishCopyLink(native.fd, temporaryName, params.basename, identity.dev, identity.ino);
|
|
104
|
+
}
|
|
105
|
+
else if (native) {
|
|
106
|
+
const renameNoReplace = native.binding.renameNoReplace;
|
|
107
|
+
if (!renameNoReplace)
|
|
108
|
+
throw new FsSafeError("helper-unavailable", "native link publication is unavailable");
|
|
109
|
+
try {
|
|
110
|
+
renameNoReplace(native.fd, temporaryName, native.fd, params.basename);
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
const unavailable = noReplaceUnavailable(error, "link copy publication", true);
|
|
114
|
+
if (!unavailable || isFsSafeNativeRequired() || process.platform !== "linux")
|
|
115
|
+
throw error;
|
|
116
|
+
// Linux link(2) links the symlink inode itself, never its target.
|
|
117
|
+
assertStage();
|
|
118
|
+
fs.linkSync(temporaryPath, destination);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
fs.linkSync(temporaryPath, destination);
|
|
123
|
+
}
|
|
124
|
+
params.onPublished?.(identity);
|
|
125
|
+
assertSyncDirectoryGuard(parent);
|
|
126
|
+
const final = inspectFileIdentitySync(() => fs.lstatSync(destination, { bigint: true }), identity);
|
|
127
|
+
if (!final.isSymbolicLink())
|
|
128
|
+
throw new FsSafeError("path-mismatch", "published copy link changed");
|
|
129
|
+
if (params.sync !== false)
|
|
130
|
+
await syncDirectoryBestEffort(parent.realPath);
|
|
131
|
+
await input.verifySource();
|
|
132
|
+
return identity;
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
failure = { error };
|
|
136
|
+
throw error;
|
|
137
|
+
}
|
|
138
|
+
finally {
|
|
139
|
+
let cleanupFailure;
|
|
140
|
+
try {
|
|
141
|
+
if (identity) {
|
|
142
|
+
assertSyncDirectoryGuard(parent);
|
|
143
|
+
const remaining = fs.lstatSync(temporaryPath, { bigint: true, throwIfNoEntry: false });
|
|
144
|
+
if (remaining) {
|
|
145
|
+
inspectFileIdentitySync(() => remaining, identity);
|
|
146
|
+
if (!remaining.isSymbolicLink())
|
|
147
|
+
throw new FsSafeError("path-mismatch", "copy link cleanup identity changed");
|
|
148
|
+
if (process.platform === "win32")
|
|
149
|
+
native.binding.removeCopyLink(native.fd, temporaryName, identity.dev, identity.ino);
|
|
150
|
+
else
|
|
151
|
+
fs.unlinkSync(temporaryPath);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
cleanupFailure = { error };
|
|
157
|
+
}
|
|
158
|
+
try {
|
|
159
|
+
await sourceParent?.close();
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
cleanupFailure = { error: cleanupFailure
|
|
163
|
+
? createSuppressedError(error, cleanupFailure.error, "copy link cleanup and close failed") : error };
|
|
164
|
+
}
|
|
165
|
+
if (cleanupFailure)
|
|
166
|
+
throw failure
|
|
167
|
+
? createSuppressedError(cleanupFailure.error, failure.error, "copy link operation and cleanup failed")
|
|
168
|
+
: cleanupFailure.error;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type BigIntStats } from "node:fs";
|
|
2
|
+
export type CopyMetadata = Readonly<{
|
|
3
|
+
atimeNs: bigint;
|
|
4
|
+
mtimeNs: bigint;
|
|
5
|
+
restoreNative?: (fd: number) => void;
|
|
6
|
+
}>;
|
|
7
|
+
export declare function timestampSeconds(nanoseconds: bigint): string;
|
|
8
|
+
export declare function captureCopyMetadata(fd: number, stat: BigIntStats): CopyMetadata;
|
|
9
|
+
export declare function restoreCopyMetadata(fd: number, metadata: CopyMetadata | undefined): void;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import fs, {} from "node:fs";
|
|
2
|
+
import { FsSafeError } from "./errors.js";
|
|
3
|
+
import { getNativeBinding } from "./native.js";
|
|
4
|
+
import { isFsSafeNativeRequired } from "./native-config.js";
|
|
5
|
+
export function timestampSeconds(nanoseconds) {
|
|
6
|
+
// Numeric strings retain fractional and pre-epoch times in Node's utimes API.
|
|
7
|
+
return String(Number(nanoseconds / 1000000000n) + Number(nanoseconds % 1000000000n) / 1e9);
|
|
8
|
+
}
|
|
9
|
+
export function captureCopyMetadata(fd, stat) {
|
|
10
|
+
let restoreNative;
|
|
11
|
+
const native = getNativeBinding();
|
|
12
|
+
if (process.platform === "win32") {
|
|
13
|
+
if (native?.readCopyMetadata && native.restoreCopyMetadata) {
|
|
14
|
+
const snapshot = native.readCopyMetadata(fd);
|
|
15
|
+
const restore = native.restoreCopyMetadata.bind(native);
|
|
16
|
+
restoreNative = target => restore(target, snapshot);
|
|
17
|
+
}
|
|
18
|
+
else if (isFsSafeNativeRequired()) {
|
|
19
|
+
throw new FsSafeError("helper-unavailable", "native Windows copy metadata preservation is unavailable");
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
else if (native?.restoreCopyFileTimes) {
|
|
23
|
+
const restore = native.restoreCopyFileTimes.bind(native);
|
|
24
|
+
const { atimeNs, mtimeNs } = stat;
|
|
25
|
+
restoreNative = target => restore(target, atimeNs, mtimeNs);
|
|
26
|
+
}
|
|
27
|
+
else if (isFsSafeNativeRequired()) {
|
|
28
|
+
throw new FsSafeError("helper-unavailable", "native copy timestamp preservation is unavailable");
|
|
29
|
+
}
|
|
30
|
+
return Object.freeze({ atimeNs: stat.atimeNs, mtimeNs: stat.mtimeNs, restoreNative });
|
|
31
|
+
}
|
|
32
|
+
export function restoreCopyMetadata(fd, metadata) {
|
|
33
|
+
if (!metadata)
|
|
34
|
+
return;
|
|
35
|
+
if (metadata.restoreNative)
|
|
36
|
+
metadata.restoreNative(fd);
|
|
37
|
+
else
|
|
38
|
+
fs.futimesSync(fd, timestampSeconds(metadata.atimeNs), timestampSeconds(metadata.mtimeNs));
|
|
39
|
+
}
|