@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 { type
|
|
1
|
+
import { type PermissionFailureFields } from "./permission-exec.js";
|
|
2
2
|
import { type PermissionCheck, type PermissionCheckOptions } from "./permissions.js";
|
|
3
3
|
export type PermissionExec = (command: string, args: string[]) => Promise<{
|
|
4
4
|
stdout: string;
|
|
@@ -13,16 +13,13 @@ export type WindowsAclEntry = {
|
|
|
13
13
|
canRead: boolean;
|
|
14
14
|
canWrite: boolean;
|
|
15
15
|
};
|
|
16
|
-
export type WindowsAclSummary = {
|
|
16
|
+
export type WindowsAclSummary = Omit<PermissionFailureFields & {
|
|
17
17
|
ok: boolean;
|
|
18
18
|
entries: WindowsAclEntry[];
|
|
19
19
|
untrustedWorld: WindowsAclEntry[];
|
|
20
20
|
untrustedGroup: WindowsAclEntry[];
|
|
21
21
|
trusted: WindowsAclEntry[];
|
|
22
|
-
|
|
23
|
-
errorDetail?: PermissionCommandFailure;
|
|
24
|
-
errorCause?: unknown;
|
|
25
|
-
};
|
|
22
|
+
}, never>;
|
|
26
23
|
export type WindowsUserInfoProvider = () => {
|
|
27
24
|
username?: string | null;
|
|
28
25
|
};
|
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { PermissionFailureFields } from "./permission-exec.js";
|
|
2
2
|
import { type PermissionExec } from "./permissions-windows.js";
|
|
3
|
-
export type
|
|
4
|
-
export { createIcaclsResetCommand, formatIcaclsResetCommand, formatWindowsAclSummary, inspectWindowsAcl, parseIcaclsOutput, resolveWindowsUserPrincipal, summarizeWindowsAcl, } from "./permissions-windows.js";
|
|
5
|
-
export type { IcaclsResetCommandOptions, PermissionExec, WindowsAclEntry, WindowsAclSummary, WindowsUserInfoProvider, } from "./permissions-windows.js";
|
|
6
|
-
export type PermissionCheck = {
|
|
3
|
+
export type PermissionCheck = Omit<PermissionFailureFields & {
|
|
7
4
|
ok: boolean;
|
|
8
5
|
isSymlink: boolean;
|
|
9
6
|
isDir: boolean;
|
|
@@ -21,11 +18,7 @@ export type PermissionCheck = {
|
|
|
21
18
|
/** Owner-query failure detail when Windows ownership could not be verified. */
|
|
22
19
|
ownerError?: string;
|
|
23
20
|
aclSummary?: string;
|
|
24
|
-
|
|
25
|
-
errorDetail?: PermissionCommandFailure;
|
|
26
|
-
/** Original inspection failure, retained separately from serializable diagnostics. */
|
|
27
|
-
errorCause?: unknown;
|
|
28
|
-
};
|
|
21
|
+
}, never>;
|
|
29
22
|
export type PermissionCheckOptions = {
|
|
30
23
|
platform?: NodeJS.Platform;
|
|
31
24
|
env?: NodeJS.ProcessEnv;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import fsSync from "node:fs";
|
|
2
2
|
import { formatIcaclsResetCommand, inspectWindowsPermissions, } from "./permissions-windows.js";
|
|
3
3
|
import { hasWindowsPathAlias, pathForWindowsFilesystem, } from "./windows-path-alias.js";
|
|
4
|
-
export { createIcaclsResetCommand, formatIcaclsResetCommand, formatWindowsAclSummary, inspectWindowsAcl, parseIcaclsOutput, resolveWindowsUserPrincipal, summarizeWindowsAcl, } from "./permissions-windows.js";
|
|
5
4
|
function failedSafeStat(error) {
|
|
6
5
|
return {
|
|
7
6
|
ok: false,
|
|
@@ -9,7 +9,6 @@ export type PinnedMutationPolicySnapshot = Readonly<{
|
|
|
9
9
|
export declare function snapshotPinnedMutationPolicy(denyMutations: DenyMutationPolicy | undefined, mutationSymlinks: MutationSymlinkPolicy | undefined): PinnedMutationPolicySnapshot | undefined;
|
|
10
10
|
export declare function preparePinnedWriteMutationAdmission(params: {
|
|
11
11
|
rootReal: string;
|
|
12
|
-
rootWithSep: string;
|
|
13
12
|
rootIdentity?: RootBoundaryIdentity;
|
|
14
13
|
resolvedTargetPath: string;
|
|
15
14
|
defaultRelativeParentPath: string;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { assertMutationNotDenied } from "./deny-mutations.js";
|
|
3
|
+
import { createMutationDenyMatcher } from "./deny-mutation-match.js";
|
|
3
4
|
import { FsSafeError } from "./errors.js";
|
|
4
5
|
import { isPathInside } from "./path.js";
|
|
5
6
|
import { admitPathInsideRoot } from "./root-boundary.js";
|
|
@@ -100,8 +101,9 @@ function epochCurrent(epoch, directories) {
|
|
|
100
101
|
getFsSafeNativeConfig().mode === epoch.mode;
|
|
101
102
|
}
|
|
102
103
|
function assertCachedNotDenied(target, epoch) {
|
|
103
|
-
|
|
104
|
-
|
|
104
|
+
const matches = createMutationDenyMatcher();
|
|
105
|
+
if (epoch.paths.some((paths) => paths.some((denied) => matches(target, denied, false))) ||
|
|
106
|
+
epoch.prefixes.some((paths) => paths.some((denied) => matches(target, denied, true)))) {
|
|
105
107
|
throw new FsSafeError("denied-path", "path is denied by denyMutations policy");
|
|
106
108
|
}
|
|
107
109
|
}
|
|
@@ -1,14 +1,9 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { PinnedMutationPolicySnapshot } from "./pinned-mutation-admission.js";
|
|
2
2
|
import type { RootBoundaryIdentity } from "./root-boundary.js";
|
|
3
|
-
import type { MutationSymlinkPolicy } from "./root-symlink-policy.js";
|
|
4
3
|
export type ExactRootIdentity = Readonly<{
|
|
5
4
|
dev: bigint;
|
|
6
5
|
ino: bigint;
|
|
7
6
|
}>;
|
|
8
|
-
type SharedMutationPolicy = Readonly<{
|
|
9
|
-
denyMutations?: DenyMutationPolicy;
|
|
10
|
-
mutationSymlinks?: MutationSymlinkPolicy;
|
|
11
|
-
}>;
|
|
12
7
|
export declare function ordinaryWindowsSegments(relativePath: string): boolean;
|
|
13
8
|
export declare function ordinarySharedAbsoluteInsideRoot(rootReal: string, candidatePath: string, rootIdentity: ExactRootIdentity): boolean;
|
|
14
9
|
export declare function simpleSharedRoute(params: {
|
|
@@ -16,9 +11,8 @@ export declare function simpleSharedRoute(params: {
|
|
|
16
11
|
rootIdentity?: RootBoundaryIdentity;
|
|
17
12
|
originalPath?: string;
|
|
18
13
|
selectedTarget: string;
|
|
19
|
-
policy:
|
|
14
|
+
policy: PinnedMutationPolicySnapshot;
|
|
20
15
|
}): {
|
|
21
16
|
route: string;
|
|
22
17
|
rootIdentity: ExactRootIdentity;
|
|
23
18
|
} | undefined;
|
|
24
|
-
export {};
|
|
@@ -14,10 +14,10 @@ export type PinnedOpenSyncResult = {
|
|
|
14
14
|
export type PinnedOpenSyncAllowedType = "file" | "directory";
|
|
15
15
|
export type PinnedOpenSyncFinalAdmission = (params: {
|
|
16
16
|
path: string;
|
|
17
|
-
preRealpathIdentity: fs.BigIntStats;
|
|
18
17
|
descriptorIdentity: fs.BigIntStats;
|
|
19
18
|
}) => string;
|
|
20
19
|
export type PinnedOpenSyncFs = Pick<typeof fs, "constants" | "lstatSync" | "realpathSync" | "openSync" | "fstatSync" | "closeSync">;
|
|
20
|
+
export declare function isExpectedPathError(error: unknown): boolean;
|
|
21
21
|
export declare function openPinnedFileSync(params: {
|
|
22
22
|
filePath: string;
|
|
23
23
|
resolvedPath?: string;
|
|
@@ -5,7 +5,7 @@ import { inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
|
5
5
|
import { resolveReadOpenFlags } from "./read-open-flags.js";
|
|
6
6
|
import { assertNoWindowsPathAlias } from "./windows-path-alias.js";
|
|
7
7
|
import { realpathSync } from "./realpath.js";
|
|
8
|
-
function isExpectedPathError(error) {
|
|
8
|
+
export function isExpectedPathError(error) {
|
|
9
9
|
const code = typeof error === "object" && error !== null && "code" in error ? String(error.code) : "";
|
|
10
10
|
return code === "ENOENT" || code === "ENOTDIR" || code === "ELOOP";
|
|
11
11
|
}
|
|
@@ -53,14 +53,13 @@ export function openPinnedFileSync(params) {
|
|
|
53
53
|
assertAllowedStat(stat, allowedType, statPolicy);
|
|
54
54
|
return stat;
|
|
55
55
|
}, preOpenStat);
|
|
56
|
-
|
|
56
|
+
inspectFileIdentitySync(() => {
|
|
57
57
|
const stat = ioFs.lstatSync(realPath, { bigint: true });
|
|
58
58
|
assertAllowedStat(stat, allowedType, statPolicy);
|
|
59
59
|
return stat;
|
|
60
60
|
}, identity);
|
|
61
61
|
const admittedPath = params.finalAdmission?.({
|
|
62
62
|
path: realPath,
|
|
63
|
-
preRealpathIdentity: pathIdentity,
|
|
64
63
|
descriptorIdentity: identity,
|
|
65
64
|
}) ?? realPath;
|
|
66
65
|
const opened = { ok: true, path: admittedPath, fd, stat: openedStat, identity };
|
|
@@ -22,6 +22,10 @@ export function publishCopyStage(params) {
|
|
|
22
22
|
// Caller authority checks can synchronously replace a parent or staged entry.
|
|
23
23
|
if (params.assertBeforeMutation)
|
|
24
24
|
assertCurrent();
|
|
25
|
+
// An observed collision needs no dispatch; errno after a link attempt remains ambiguous.
|
|
26
|
+
if (fs.lstatSync(targetPath, { throwIfNoEntry: false })) {
|
|
27
|
+
throw new FsSafeError("already-exists", "destination already exists");
|
|
28
|
+
}
|
|
25
29
|
params.onPublicationAttempt?.();
|
|
26
30
|
fs.linkSync(temporaryPath, targetPath);
|
|
27
31
|
let observerRejected = false;
|
|
@@ -7,12 +7,9 @@ export type ReadResult = {
|
|
|
7
7
|
realPath: string;
|
|
8
8
|
stat: Stats;
|
|
9
9
|
};
|
|
10
|
-
type OpenedFile = {
|
|
10
|
+
type OpenedFile = Omit<ReadResult & {
|
|
11
11
|
handle: FileHandle;
|
|
12
|
-
|
|
13
|
-
realPath: string;
|
|
14
|
-
stat: Stats;
|
|
15
|
-
};
|
|
12
|
+
}, "buffer">;
|
|
16
13
|
export declare function readOpenedFileSafely(params: {
|
|
17
14
|
opened: OpenedFile;
|
|
18
15
|
maxBytes?: number;
|
|
@@ -208,10 +208,10 @@ function inspectAppendPath(filePath) {
|
|
|
208
208
|
}
|
|
209
209
|
function prepareRegularAppend(filePath, rejectSymlinkParents) {
|
|
210
210
|
if (rejectSymlinkParents === true) {
|
|
211
|
-
const
|
|
211
|
+
const unresolvedDir = path.dirname(filePath);
|
|
212
212
|
assertNoSymlinkParentsSync({
|
|
213
|
-
rootDir: path.parse(
|
|
214
|
-
targetPath:
|
|
213
|
+
rootDir: path.parse(resolvePathPreservingWindowsRoot(unresolvedDir)).root,
|
|
214
|
+
targetPath: unresolvedDir,
|
|
215
215
|
allowMissing: false,
|
|
216
216
|
allowRootChildSymlink: true,
|
|
217
217
|
requireDirectories: true,
|
|
@@ -6,7 +6,7 @@ import { assertSyncDirectoryGuard, createAsyncDirectoryGuard, inspectDirectoryId
|
|
|
6
6
|
import { FsSafeError } from "./errors.js";
|
|
7
7
|
import { classifyNativeRenameFailure, NATIVE_RENAME_SOURCE_IDENTITY_MISMATCH, } from "./native-rename-outcome.js";
|
|
8
8
|
import { recursiveMkdirPath } from "./recursive-mkdir-path.js";
|
|
9
|
-
import { RetainedDirectoryReplacement } from "./retained-directory-replacement.js";
|
|
9
|
+
import { copyOperationalCode, RetainedDirectoryReplacement } from "./retained-directory-replacement.js";
|
|
10
10
|
import { assertSafePathPrefix } from "./safe-path-segment.js";
|
|
11
11
|
import { admitStandalonePublicationPath, assertNoWindowsPathAlias } from "./windows-path-alias.js";
|
|
12
12
|
import { serializePathWrite } from "./write-queue.js";
|
|
@@ -17,30 +17,18 @@ function assertGuardsCurrent(guards) {
|
|
|
17
17
|
for (const guard of guards)
|
|
18
18
|
assertSyncDirectoryGuard(guard);
|
|
19
19
|
}
|
|
20
|
-
function
|
|
21
|
-
const code = source?.code;
|
|
22
|
-
if (typeof code === "string") {
|
|
23
|
-
Object.defineProperty(target, "code", { configurable: true, value: code });
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
function replacementFailure(error, message, details) {
|
|
20
|
+
function replacementFailure(error, message, details, cause = error) {
|
|
27
21
|
if (error instanceof FsSafeError) {
|
|
28
|
-
return new FsSafeError(error.code, message, { cause
|
|
22
|
+
return new FsSafeError(error.code, message, { cause, details });
|
|
29
23
|
}
|
|
30
|
-
const failure = new Error(message, { cause
|
|
24
|
+
const failure = new Error(message, { cause });
|
|
31
25
|
copyOperationalCode(failure, error);
|
|
32
26
|
Object.defineProperty(failure, "details", { configurable: true, value: details });
|
|
33
27
|
return failure;
|
|
34
28
|
}
|
|
35
29
|
function replacementAggregate(error, recoveryError, message, details) {
|
|
36
30
|
const combined = new AggregateError([error, recoveryError], "directory replacement operation and recovery both failed");
|
|
37
|
-
|
|
38
|
-
return new FsSafeError(error.code, message, { cause: combined, details });
|
|
39
|
-
}
|
|
40
|
-
const failure = new Error(message, { cause: combined });
|
|
41
|
-
copyOperationalCode(failure, error);
|
|
42
|
-
Object.defineProperty(failure, "details", { configurable: true, value: details });
|
|
43
|
-
return failure;
|
|
31
|
+
return replacementFailure(error, message, details, combined);
|
|
44
32
|
}
|
|
45
33
|
function isMissingDirectoryEntry(error) {
|
|
46
34
|
return error?.code === "ENOENT";
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type syncFs from "node:fs";
|
|
2
|
+
import type { FileHandle } from "node:fs/promises";
|
|
3
|
+
export declare function writeAtomicDestination(handle: FileHandle, data: Buffer, beforeWrite?: () => Promise<void>, assertBeforeMutation?: () => void, onWriting?: () => void): Promise<void>;
|
|
4
|
+
export declare function writeAtomicDestinationSync(fsModule: Pick<typeof syncFs, "ftruncateSync" | "writeSync">, fd: number, data: Buffer, beforeWrite?: () => void, onWriting?: () => void): void;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export async function writeAtomicDestination(handle, data, beforeWrite, assertBeforeMutation, onWriting) {
|
|
2
|
+
if (beforeWrite)
|
|
3
|
+
await beforeWrite();
|
|
4
|
+
assertBeforeMutation?.();
|
|
5
|
+
await handle.truncate(0);
|
|
6
|
+
onWriting?.();
|
|
7
|
+
let written = 0;
|
|
8
|
+
while (written < data.length) {
|
|
9
|
+
if (beforeWrite)
|
|
10
|
+
await beforeWrite();
|
|
11
|
+
assertBeforeMutation?.();
|
|
12
|
+
const result = await handle.write(data, written, data.length - written, written);
|
|
13
|
+
if (result.bytesWritten === 0)
|
|
14
|
+
throw new Error("Copy fallback write made no progress");
|
|
15
|
+
written += result.bytesWritten;
|
|
16
|
+
}
|
|
17
|
+
if (beforeWrite)
|
|
18
|
+
await beforeWrite();
|
|
19
|
+
assertBeforeMutation?.();
|
|
20
|
+
await handle.truncate(data.length);
|
|
21
|
+
}
|
|
22
|
+
export function writeAtomicDestinationSync(fsModule, fd, data, beforeWrite, onWriting) {
|
|
23
|
+
beforeWrite?.();
|
|
24
|
+
fsModule.ftruncateSync(fd, 0);
|
|
25
|
+
onWriting?.();
|
|
26
|
+
let written = 0;
|
|
27
|
+
while (written < data.length) {
|
|
28
|
+
beforeWrite?.();
|
|
29
|
+
const bytesWritten = fsModule.writeSync(fd, data, written, data.length - written, written);
|
|
30
|
+
if (bytesWritten === 0)
|
|
31
|
+
throw new Error("Copy fallback write made no progress");
|
|
32
|
+
written += bytesWritten;
|
|
33
|
+
}
|
|
34
|
+
beforeWrite?.();
|
|
35
|
+
fsModule.ftruncateSync(fd, data.length);
|
|
36
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import syncFs, { type BigIntStats } from "node:fs";
|
|
2
|
+
import { AtomicMutation } from "./replace-file-mutation.js";
|
|
2
3
|
export type ReplaceFileDestinationHardlinkPolicy = "reject";
|
|
3
4
|
export type ReplaceFileCopyFallbackRestorePolicy = "restore-original" | "none";
|
|
4
5
|
export type ReplaceFileAtomicRestoreCleanup = "restored" | "restore-failed";
|
|
@@ -9,9 +10,8 @@ type AsyncFallbackFs = {
|
|
|
9
10
|
lstat: typeof import("node:fs/promises").lstat;
|
|
10
11
|
open: typeof import("node:fs/promises").open;
|
|
11
12
|
rm: typeof import("node:fs/promises").rm;
|
|
12
|
-
unlink: typeof import("node:fs/promises").unlink;
|
|
13
13
|
};
|
|
14
|
-
type SyncFallbackFs = Pick<typeof syncFs, "closeSync" | "fstatSync" | "fsyncSync" | "ftruncateSync" | "lstatSync" | "openSync" | "readSync" | "rmSync" | "
|
|
14
|
+
type SyncFallbackFs = Pick<typeof syncFs, "closeSync" | "fstatSync" | "fsyncSync" | "ftruncateSync" | "lstatSync" | "openSync" | "readSync" | "rmSync" | "writeSync">;
|
|
15
15
|
export declare function assertDestinationHardlinkPolicy(fsModule: AsyncFallbackFs, dest: string, policy?: ReplaceFileDestinationHardlinkPolicy): Promise<void>;
|
|
16
16
|
export declare function assertDestinationHardlinkPolicySync(fsModule: SyncFallbackFs, dest: string, policy?: ReplaceFileDestinationHardlinkPolicy): void;
|
|
17
17
|
export declare function copyFallbackReplace(params: {
|
|
@@ -23,16 +23,10 @@ export declare function copyFallbackReplace(params: {
|
|
|
23
23
|
maxRestoreBytes?: number;
|
|
24
24
|
expectedSourceIdentity?: BigIntStats;
|
|
25
25
|
sync: boolean;
|
|
26
|
+
mutation?: AtomicMutation;
|
|
26
27
|
}): Promise<void>;
|
|
27
|
-
export declare function copyFallbackReplaceSync(params: {
|
|
28
|
+
export declare function copyFallbackReplaceSync(params: Omit<Parameters<typeof copyFallbackReplace>[0], "fsModule"> & {
|
|
28
29
|
fsModule: SyncFallbackFs;
|
|
29
|
-
src: string;
|
|
30
|
-
dest: string;
|
|
31
|
-
destinationHardlinks?: ReplaceFileDestinationHardlinkPolicy;
|
|
32
|
-
restore: ReplaceFileCopyFallbackRestorePolicy;
|
|
33
|
-
maxRestoreBytes?: number;
|
|
34
|
-
expectedSourceIdentity?: BigIntStats;
|
|
35
30
|
fchmodSync?: (fd: number, mode: number) => void;
|
|
36
|
-
sync: boolean;
|
|
37
31
|
}): void;
|
|
38
32
|
export {};
|
|
@@ -6,6 +6,9 @@ import { inspectFileIdentity, inspectFileIdentitySync } from "./strict-file-iden
|
|
|
6
6
|
import { readOwnedCopySource, readOwnedCopySourceSync } from "./replace-file-copy-source.js";
|
|
7
7
|
import { resolveReadOpenFlags } from "./read-open-flags.js";
|
|
8
8
|
import { hasErrorCode } from "./file-cleanup.js";
|
|
9
|
+
import { writeAtomicDestination, writeAtomicDestinationSync } from "./replace-file-buffer.js";
|
|
10
|
+
import { AtomicMutation } from "./replace-file-mutation.js";
|
|
11
|
+
import { captureAtomicDestination, captureAtomicDestinationSync } from "./replace-file-destination.js";
|
|
9
12
|
const SUPPORTS_NOFOLLOW = process.platform !== "win32" && "O_NOFOLLOW" in syncFs.constants;
|
|
10
13
|
const NOFOLLOW = SUPPORTS_NOFOLLOW ? syncFs.constants.O_NOFOLLOW : 0;
|
|
11
14
|
const OPEN_READ_FLAGS = resolveReadOpenFlags();
|
|
@@ -44,7 +47,7 @@ function inspectPinnedDestinationSync(fsModule, fd, dest, admission, hardlinks)
|
|
|
44
47
|
inspectFileIdentitySync(() => admitDestinationKind(fsModule.lstatSync(dest, { bigint: true }), opened, dest, admission), opened);
|
|
45
48
|
assertDestinationLinks(opened, dest, admission, hardlinks);
|
|
46
49
|
}
|
|
47
|
-
async function openPinnedDestination(fsModule, dest, admission, hardlinks) {
|
|
50
|
+
async function openPinnedDestination(fsModule, dest, admission, hardlinks, mutation) {
|
|
48
51
|
let preview;
|
|
49
52
|
try {
|
|
50
53
|
preview = fsModule === fs ? syncFs.lstatSync(dest) : await fsModule.lstat(dest);
|
|
@@ -61,6 +64,7 @@ async function openPinnedDestination(fsModule, dest, admission, hardlinks) {
|
|
|
61
64
|
if (admission === "restore" && preview.isSymbolicLink()) {
|
|
62
65
|
throw new FsSafeError("symlink", `Refusing copy fallback through symlink destination: ${dest}`);
|
|
63
66
|
}
|
|
67
|
+
mutation?.assert();
|
|
64
68
|
const handle = await fsModule.open(dest, admission === "restore" ? OPEN_READ_WRITE_FLAGS : OPEN_READ_FLAGS);
|
|
65
69
|
try {
|
|
66
70
|
if (fsModule === fs)
|
|
@@ -82,7 +86,7 @@ async function openPinnedDestination(fsModule, dest, admission, hardlinks) {
|
|
|
82
86
|
throw error;
|
|
83
87
|
}
|
|
84
88
|
}
|
|
85
|
-
function openPinnedDestinationSync(fsModule, dest, admission, hardlinks) {
|
|
89
|
+
function openPinnedDestinationSync(fsModule, dest, admission, hardlinks, mutation) {
|
|
86
90
|
let preview;
|
|
87
91
|
try {
|
|
88
92
|
preview = fsModule.lstatSync(dest);
|
|
@@ -97,6 +101,7 @@ function openPinnedDestinationSync(fsModule, dest, admission, hardlinks) {
|
|
|
97
101
|
if (admission === "restore" && preview.isSymbolicLink()) {
|
|
98
102
|
throw new FsSafeError("symlink", `Refusing copy fallback through symlink destination: ${dest}`);
|
|
99
103
|
}
|
|
104
|
+
mutation?.assert();
|
|
100
105
|
const fd = fsModule.openSync(dest, admission === "restore" ? OPEN_READ_WRITE_FLAGS : OPEN_READ_FLAGS);
|
|
101
106
|
try {
|
|
102
107
|
inspectPinnedDestinationSync(fsModule, fd, dest, admission, hardlinks);
|
|
@@ -148,28 +153,6 @@ function readRestoreSnapshotSync(fsModule, fd, maxBytes, stat) {
|
|
|
148
153
|
return bytesRead;
|
|
149
154
|
}, restoreReadOptions(stat, maxBytes));
|
|
150
155
|
}
|
|
151
|
-
async function writeAll(handle, data) {
|
|
152
|
-
await handle.truncate(0);
|
|
153
|
-
let written = 0;
|
|
154
|
-
while (written < data.length) {
|
|
155
|
-
const result = await handle.write(data, written, data.length - written, written);
|
|
156
|
-
if (result.bytesWritten === 0)
|
|
157
|
-
throw new Error("Copy fallback write made no progress");
|
|
158
|
-
written += result.bytesWritten;
|
|
159
|
-
}
|
|
160
|
-
await handle.truncate(data.length);
|
|
161
|
-
}
|
|
162
|
-
function writeAllSync(fsModule, fd, data) {
|
|
163
|
-
fsModule.ftruncateSync(fd, 0);
|
|
164
|
-
let written = 0;
|
|
165
|
-
while (written < data.length) {
|
|
166
|
-
const bytesWritten = fsModule.writeSync(fd, data, written, data.length - written, written);
|
|
167
|
-
if (bytesWritten === 0)
|
|
168
|
-
throw new Error("Copy fallback write made no progress");
|
|
169
|
-
written += bytesWritten;
|
|
170
|
-
}
|
|
171
|
-
fsModule.ftruncateSync(fd, data.length);
|
|
172
|
-
}
|
|
173
156
|
function restoreFailure(writeError, cleanup, restoreError) {
|
|
174
157
|
const primary = writeError instanceof Error ? writeError : new Error(String(writeError));
|
|
175
158
|
const details = { cleanup };
|
|
@@ -180,23 +163,29 @@ function restoreFailure(writeError, cleanup, restoreError) {
|
|
|
180
163
|
? `Atomic copy fallback failed; original destination restored: ${primary.message}`
|
|
181
164
|
: `Atomic copy fallback failed and original restoration failed: ${primary.message}`, { cause, details });
|
|
182
165
|
}
|
|
183
|
-
async function replacePinnedWithRestore(fsModule, handle, replacement, maxRestoreBytes, replacementMode) {
|
|
166
|
+
async function replacePinnedWithRestore(fsModule, handle, replacement, maxRestoreBytes, replacementMode, destination, mutation) {
|
|
184
167
|
const originalStat = fsModule === fs ? syncFs.fstatSync(handle.fd) : await handle.stat();
|
|
185
168
|
const originalMode = originalStat.mode;
|
|
186
169
|
const original = await readRestoreSnapshot(handle, maxRestoreBytes, originalStat);
|
|
187
170
|
try {
|
|
188
|
-
await
|
|
171
|
+
await writeAtomicDestination(handle, replacement, destination?.beforeWrite, destination?.assertBeforeMutation, destination?.writing);
|
|
172
|
+
if (destination)
|
|
173
|
+
await destination.verify();
|
|
189
174
|
await handle.chmod(replacementMode);
|
|
190
175
|
await handle.sync();
|
|
191
176
|
}
|
|
192
177
|
catch (writeError) {
|
|
178
|
+
mutation.rethrowRefusal();
|
|
193
179
|
try {
|
|
194
|
-
await
|
|
180
|
+
await writeAtomicDestination(handle, original, destination?.beforeRestore, destination?.assertBeforeMutation, destination?.writing);
|
|
181
|
+
if (destination)
|
|
182
|
+
await destination.verifyRestore();
|
|
195
183
|
await handle.chmod(originalMode);
|
|
196
184
|
await handle.sync();
|
|
197
185
|
throw restoreFailure(writeError, "restored");
|
|
198
186
|
}
|
|
199
187
|
catch (restoreError) {
|
|
188
|
+
mutation.rethrowRefusal();
|
|
200
189
|
if (restoreError instanceof FsSafeError && restoreError.details?.cleanup === "restored") {
|
|
201
190
|
throw restoreError;
|
|
202
191
|
}
|
|
@@ -204,23 +193,27 @@ async function replacePinnedWithRestore(fsModule, handle, replacement, maxRestor
|
|
|
204
193
|
}
|
|
205
194
|
}
|
|
206
195
|
}
|
|
207
|
-
function replacePinnedWithRestoreSync(fsModule, fd, replacement, maxRestoreBytes, replacementMode, fchmodSync) {
|
|
196
|
+
function replacePinnedWithRestoreSync(fsModule, fd, replacement, maxRestoreBytes, replacementMode, fchmodSync, destination, mutation) {
|
|
208
197
|
const originalStat = fsModule.fstatSync(fd);
|
|
209
198
|
const originalMode = originalStat.mode;
|
|
210
199
|
const original = readRestoreSnapshotSync(fsModule, fd, maxRestoreBytes, originalStat);
|
|
211
200
|
try {
|
|
212
|
-
|
|
201
|
+
writeAtomicDestinationSync(fsModule, fd, replacement, destination?.beforeWrite, destination?.writing);
|
|
202
|
+
destination?.verify();
|
|
213
203
|
fchmodSync?.(fd, replacementMode);
|
|
214
204
|
fsModule.fsyncSync(fd);
|
|
215
205
|
}
|
|
216
206
|
catch (writeError) {
|
|
207
|
+
mutation?.rethrowRefusal();
|
|
217
208
|
try {
|
|
218
|
-
|
|
209
|
+
writeAtomicDestinationSync(fsModule, fd, original, destination?.beforeRestore, destination?.writing);
|
|
210
|
+
destination?.verifyRestore();
|
|
219
211
|
fchmodSync?.(fd, originalMode);
|
|
220
212
|
fsModule.fsyncSync(fd);
|
|
221
213
|
throw restoreFailure(writeError, "restored");
|
|
222
214
|
}
|
|
223
215
|
catch (restoreError) {
|
|
216
|
+
mutation?.rethrowRefusal();
|
|
224
217
|
if (restoreError instanceof FsSafeError && restoreError.details?.cleanup === "restored") {
|
|
225
218
|
throw restoreError;
|
|
226
219
|
}
|
|
@@ -229,6 +222,7 @@ function replacePinnedWithRestoreSync(fsModule, fd, replacement, maxRestoreBytes
|
|
|
229
222
|
}
|
|
230
223
|
}
|
|
231
224
|
export async function copyFallbackReplace(params) {
|
|
225
|
+
const mutation = params.mutation ?? new AtomicMutation({});
|
|
232
226
|
const source = await readOwnedCopySource({
|
|
233
227
|
fsModule: params.fsModule,
|
|
234
228
|
src: params.src,
|
|
@@ -237,12 +231,16 @@ export async function copyFallbackReplace(params) {
|
|
|
237
231
|
const { replacement } = source;
|
|
238
232
|
let destHandle = null;
|
|
239
233
|
let closeRequiredForSuccess = false;
|
|
234
|
+
let completed = false;
|
|
235
|
+
let destination;
|
|
240
236
|
try {
|
|
241
237
|
if (params.restore === "restore-original") {
|
|
242
|
-
const pinned = await openPinnedDestination(params.fsModule, params.dest, "restore", params.destinationHardlinks);
|
|
238
|
+
const pinned = await openPinnedDestination(params.fsModule, params.dest, "restore", params.destinationHardlinks, mutation);
|
|
243
239
|
if (pinned) {
|
|
244
240
|
destHandle = pinned;
|
|
245
|
-
|
|
241
|
+
if (mutation.active)
|
|
242
|
+
destination = await captureAtomicDestination(params.fsModule, pinned, params.dest, mutation, params.destinationHardlinks === "reject");
|
|
243
|
+
await replacePinnedWithRestore(params.fsModule, destHandle, replacement, params.maxRestoreBytes, source.mode, destination, mutation);
|
|
246
244
|
}
|
|
247
245
|
}
|
|
248
246
|
if (!destHandle) {
|
|
@@ -260,16 +258,31 @@ export async function copyFallbackReplace(params) {
|
|
|
260
258
|
}
|
|
261
259
|
if (destStat) {
|
|
262
260
|
await assertDestinationHardlinkPolicy(params.fsModule, params.dest, params.destinationHardlinks);
|
|
261
|
+
mutation.assert();
|
|
263
262
|
await params.fsModule.rm(params.dest, { force: true });
|
|
263
|
+
mutation.removed(params.dest);
|
|
264
264
|
}
|
|
265
|
+
mutation.assert();
|
|
265
266
|
destHandle = await params.fsModule.open(params.dest, OPEN_WRITE_EXCLUSIVE_FLAGS, source.mode & 0o777);
|
|
266
|
-
|
|
267
|
+
if (mutation.active) {
|
|
268
|
+
destination = await captureAtomicDestination(params.fsModule, destHandle, params.dest, mutation, params.destinationHardlinks === "reject");
|
|
269
|
+
destination.writing();
|
|
270
|
+
await writeAtomicDestination(destHandle, replacement, destination.beforeWrite, destination.assertBeforeMutation);
|
|
271
|
+
await destination.verify();
|
|
272
|
+
}
|
|
273
|
+
else {
|
|
274
|
+
await destHandle.writeFile(replacement);
|
|
275
|
+
}
|
|
267
276
|
await destHandle.chmod(source.mode);
|
|
268
277
|
if (params.sync) {
|
|
269
278
|
await destHandle.sync();
|
|
270
279
|
}
|
|
271
280
|
closeRequiredForSuccess = !params.sync;
|
|
272
281
|
}
|
|
282
|
+
if (destination)
|
|
283
|
+
await destination.verify();
|
|
284
|
+
destination?.published();
|
|
285
|
+
completed = true;
|
|
273
286
|
}
|
|
274
287
|
finally {
|
|
275
288
|
if (destHandle) {
|
|
@@ -277,7 +290,7 @@ export async function copyFallbackReplace(params) {
|
|
|
277
290
|
await destHandle.close();
|
|
278
291
|
}
|
|
279
292
|
catch (closeError) {
|
|
280
|
-
if (closeRequiredForSuccess) {
|
|
293
|
+
if (closeRequiredForSuccess && completed) {
|
|
281
294
|
throw closeError;
|
|
282
295
|
}
|
|
283
296
|
}
|
|
@@ -285,6 +298,7 @@ export async function copyFallbackReplace(params) {
|
|
|
285
298
|
}
|
|
286
299
|
}
|
|
287
300
|
export function copyFallbackReplaceSync(params) {
|
|
301
|
+
const mutation = params.mutation ?? new AtomicMutation({});
|
|
288
302
|
const source = readOwnedCopySourceSync({
|
|
289
303
|
fsModule: params.fsModule,
|
|
290
304
|
src: params.src,
|
|
@@ -293,12 +307,16 @@ export function copyFallbackReplaceSync(params) {
|
|
|
293
307
|
const { replacement } = source;
|
|
294
308
|
let destFd;
|
|
295
309
|
let closeRequiredForSuccess = false;
|
|
310
|
+
let completed = false;
|
|
311
|
+
let destination;
|
|
296
312
|
try {
|
|
297
313
|
if (params.restore === "restore-original") {
|
|
298
|
-
const pinned = openPinnedDestinationSync(params.fsModule, params.dest, "restore", params.destinationHardlinks);
|
|
314
|
+
const pinned = openPinnedDestinationSync(params.fsModule, params.dest, "restore", params.destinationHardlinks, mutation);
|
|
299
315
|
if (pinned !== null) {
|
|
300
316
|
destFd = pinned;
|
|
301
|
-
|
|
317
|
+
if (mutation.active)
|
|
318
|
+
destination = captureAtomicDestinationSync(params.fsModule, pinned, params.dest, mutation, params.destinationHardlinks === "reject");
|
|
319
|
+
replacePinnedWithRestoreSync(params.fsModule, destFd, replacement, params.maxRestoreBytes, source.mode, params.fchmodSync, destination, mutation);
|
|
302
320
|
}
|
|
303
321
|
}
|
|
304
322
|
if (destFd === undefined) {
|
|
@@ -315,16 +333,26 @@ export function copyFallbackReplaceSync(params) {
|
|
|
315
333
|
}
|
|
316
334
|
if (destStat) {
|
|
317
335
|
assertDestinationHardlinkPolicySync(params.fsModule, params.dest, params.destinationHardlinks);
|
|
336
|
+
mutation.assert();
|
|
318
337
|
params.fsModule.rmSync(params.dest, { force: true });
|
|
338
|
+
mutation.removed(params.dest);
|
|
319
339
|
}
|
|
340
|
+
mutation.assert();
|
|
320
341
|
destFd = params.fsModule.openSync(params.dest, OPEN_WRITE_EXCLUSIVE_FLAGS, source.mode & 0o777);
|
|
321
|
-
|
|
342
|
+
if (mutation.active)
|
|
343
|
+
destination = captureAtomicDestinationSync(params.fsModule, destFd, params.dest, mutation, params.destinationHardlinks === "reject");
|
|
344
|
+
destination?.writing();
|
|
345
|
+
writeAtomicDestinationSync(params.fsModule, destFd, replacement, destination?.beforeWrite);
|
|
346
|
+
destination?.verify();
|
|
322
347
|
params.fchmodSync?.(destFd, source.mode);
|
|
323
348
|
if (params.sync) {
|
|
324
349
|
params.fsModule.fsyncSync(destFd);
|
|
325
350
|
}
|
|
326
351
|
closeRequiredForSuccess = !params.sync;
|
|
327
352
|
}
|
|
353
|
+
destination?.verify();
|
|
354
|
+
destination?.published();
|
|
355
|
+
completed = true;
|
|
328
356
|
}
|
|
329
357
|
finally {
|
|
330
358
|
if (destFd !== undefined) {
|
|
@@ -332,7 +360,7 @@ export function copyFallbackReplaceSync(params) {
|
|
|
332
360
|
params.fsModule.closeSync(destFd);
|
|
333
361
|
}
|
|
334
362
|
catch (closeError) {
|
|
335
|
-
if (closeRequiredForSuccess) {
|
|
363
|
+
if (closeRequiredForSuccess && completed) {
|
|
336
364
|
throw closeError;
|
|
337
365
|
}
|
|
338
366
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import syncFs, { type BigIntStats } from "node:fs";
|
|
2
2
|
import fs, { type FileHandle } from "node:fs/promises";
|
|
3
|
-
import { type DirectoryModeOwner } from "./directory-mode-
|
|
3
|
+
import { type DirectoryModeOwner } from "./directory-mode-node.js";
|
|
4
|
+
import type { AtomicMutation } from "./replace-file-mutation.js";
|
|
4
5
|
type AsyncTempFileSystem = Pick<typeof fs, "lstat" | "open" | "writeFile">;
|
|
5
6
|
type SyncTempFileSystem = Pick<typeof syncFs, "closeSync" | "fstatSync" | "fsyncSync" | "lstatSync" | "openSync" | "writeFileSync">;
|
|
6
7
|
export type SyncFchmod = (fd: number, mode: number) => void;
|
|
@@ -11,18 +12,17 @@ export declare function pinDirectoryForMode(params: {
|
|
|
11
12
|
dirPath: string;
|
|
12
13
|
/** Compatibility for best-effort directory modes; admission and close still fail closed. */
|
|
13
14
|
ignoreChmodError?: boolean;
|
|
15
|
+
mutation?: AtomicMutation;
|
|
14
16
|
}): Promise<DirectoryModeOwner | undefined>;
|
|
15
|
-
export declare function applyDirectoryMode(params: {
|
|
16
|
-
fsModule: AsyncTempFileSystem;
|
|
17
|
-
dirPath: string;
|
|
17
|
+
export declare function applyDirectoryMode(params: Parameters<typeof pinDirectoryForMode>[0] & {
|
|
18
18
|
mode: number;
|
|
19
|
-
ignoreChmodError?: boolean;
|
|
20
19
|
}): Promise<void>;
|
|
21
20
|
export declare function applyDirectoryModeSync(params: {
|
|
22
21
|
fsModule: SyncTempFileSystem;
|
|
23
22
|
dirPath: string;
|
|
24
23
|
mode: number;
|
|
25
24
|
fchmodSync?: SyncFchmod;
|
|
25
|
+
mutation?: AtomicMutation;
|
|
26
26
|
}): void;
|
|
27
27
|
export declare function writeTempFile(params: {
|
|
28
28
|
fsModule: AsyncTempFileSystem;
|
|
@@ -31,18 +31,14 @@ export declare function writeTempFile(params: {
|
|
|
31
31
|
mode: number;
|
|
32
32
|
sync: boolean;
|
|
33
33
|
onIdentity?: (identity: BigIntStats) => void;
|
|
34
|
+
mutation?: AtomicMutation;
|
|
34
35
|
}): Promise<{
|
|
35
36
|
handle: FileHandle;
|
|
36
37
|
identity: BigIntStats;
|
|
37
38
|
}>;
|
|
38
|
-
export declare function writeTempFileSync(params: {
|
|
39
|
+
export declare function writeTempFileSync(params: Omit<Parameters<typeof writeTempFile>[0], "fsModule"> & {
|
|
39
40
|
fsModule: SyncTempFileSystem;
|
|
40
|
-
tempPath: string;
|
|
41
|
-
content: string | Uint8Array;
|
|
42
|
-
mode: number;
|
|
43
41
|
fchmodSync?: SyncFchmod;
|
|
44
|
-
sync: boolean;
|
|
45
|
-
onIdentity?: (identity: BigIntStats) => void;
|
|
46
42
|
}): {
|
|
47
43
|
fd: number;
|
|
48
44
|
identity: BigIntStats;
|