@openclaw/openshell-sandbox 2026.10.5-beta.3 → 2026.10.10-beta.1
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/README.md +1 -1
- 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/package.json +5 -5
- 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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type BigIntStats, type Stats } from "node:fs";
|
|
2
|
-
import type { DirectoryReceipt } from "./directory-durability.js";
|
|
3
|
-
import type
|
|
2
|
+
import type { DirectoryReceipt, ExactDirectoryReceipt } from "./directory-durability.js";
|
|
3
|
+
import { type FileIdentityInput } from "./file-identity.js";
|
|
4
4
|
type ExactIdentity = Readonly<Pick<BigIntStats, "dev" | "ino">>;
|
|
5
5
|
declare const numericFields: readonly ["dev", "mode", "nlink", "uid", "gid", "rdev", "blksize", "ino", "size", "blocks"];
|
|
6
6
|
declare const timeFields: readonly ["atime", "mtime", "ctime", "birthtime"];
|
|
@@ -13,11 +13,11 @@ type DirectoryAuthority = DirectoryProvenance & Readonly<{
|
|
|
13
13
|
path: string;
|
|
14
14
|
realPath: string;
|
|
15
15
|
}>;
|
|
16
|
-
export declare function directoryReceiptIdentity(
|
|
16
|
+
export declare function directoryReceiptIdentity(input: FileIdentityInput): ExactIdentity;
|
|
17
17
|
export declare function directoryReceiptAuthority(receipt: DirectoryReceipt<Stats | BigIntStats>): DirectoryAuthority;
|
|
18
|
-
export declare function ownDirectoryReceipt(receipt: DirectoryReceipt<Stats | BigIntStats>):
|
|
19
|
-
export declare function copyRetainedDirectoryReceipt(receipt: DirectoryReceipt):
|
|
20
|
-
export declare function createDirectoryReceiptFromIdentity(pathname: string, realPath: string, exactStat: BigIntStats):
|
|
21
|
-
export declare function createDirectoryReceiptSync(directoryPath: string, label: string, canonicalize?: (pathname: string) => string):
|
|
18
|
+
export declare function ownDirectoryReceipt(receipt: DirectoryReceipt<Stats | BigIntStats>): ExactDirectoryReceipt;
|
|
19
|
+
export declare function copyRetainedDirectoryReceipt(receipt: DirectoryReceipt): ExactDirectoryReceipt;
|
|
20
|
+
export declare function createDirectoryReceiptFromIdentity(pathname: string, realPath: string, exactStat: BigIntStats): ExactDirectoryReceipt;
|
|
21
|
+
export declare function createDirectoryReceiptSync(directoryPath: string, label: string, canonicalize?: (pathname: string) => string): ExactDirectoryReceipt;
|
|
22
22
|
export declare function assertDirectoryReceiptCurrentSync(receipt: DirectoryReceipt, label: string, canonicalize?: (pathname: string) => string): void;
|
|
23
23
|
export {};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs, {} from "node:fs";
|
|
2
2
|
import { inspectDirectoryIdentitySync } from "./directory-guard.js";
|
|
3
3
|
import { FsSafeError } from "./errors.js";
|
|
4
|
+
import { identityFromReceipt } from "./file-identity.js";
|
|
4
5
|
import { realpathSync } from "./realpath.js";
|
|
5
6
|
import { assertNoWindowsPathAlias, pathForWindowsFilesystem, resolvePathPreservingWindowsRoot, } from "./windows-path-alias.js";
|
|
6
7
|
const numericFields = ["dev", "mode", "nlink", "uid", "gid", "rdev", "blksize", "ino", "size", "blocks"];
|
|
@@ -9,10 +10,11 @@ const timeFields = ["atime", "mtime", "ctime", "birthtime"];
|
|
|
9
10
|
// fields and accept exact provenance only while its public identity is intact.
|
|
10
11
|
const authorities = new WeakMap();
|
|
11
12
|
const identities = new WeakMap();
|
|
12
|
-
export function directoryReceiptIdentity(
|
|
13
|
-
if (!
|
|
13
|
+
export function directoryReceiptIdentity(input) {
|
|
14
|
+
if (!input) {
|
|
14
15
|
throw new FsSafeError("path-mismatch", "directory receipt identity is missing");
|
|
15
16
|
}
|
|
17
|
+
const identity = identityFromReceipt(input);
|
|
16
18
|
const known = identities.get(identity);
|
|
17
19
|
const dev = identity.dev;
|
|
18
20
|
const ino = identity.ino;
|
|
@@ -41,9 +43,11 @@ function snapshotDirectoryReceipt(receipt) {
|
|
|
41
43
|
}
|
|
42
44
|
assertNoWindowsPathAlias(pathname, "filesystem");
|
|
43
45
|
assertNoWindowsPathAlias(realPath, "filesystem");
|
|
44
|
-
const identity = directoryReceiptIdentity(stat);
|
|
46
|
+
const identity = directoryReceiptIdentity(receipt.exactIdentity ?? stat);
|
|
45
47
|
const known = authorities.get(receipt);
|
|
46
|
-
|
|
48
|
+
const matches = (value, expected) => typeof value === "bigint" ? value === expected : value === Number(expected);
|
|
49
|
+
if (known && (!matches(stat.dev, known.identity.dev) || !matches(stat.ino, known.identity.ino) ||
|
|
50
|
+
pathname !== known.path || realPath !== known.realPath ||
|
|
47
51
|
identity.dev !== known.identity.dev || identity.ino !== known.identity.ino)) {
|
|
48
52
|
throw new FsSafeError("path-mismatch", "directory receipt changed before admission");
|
|
49
53
|
}
|
|
@@ -79,6 +83,7 @@ export function copyRetainedDirectoryReceipt(receipt) {
|
|
|
79
83
|
}
|
|
80
84
|
function receiptFromAuthority(authority) {
|
|
81
85
|
const receipt = {
|
|
86
|
+
exactIdentity: authority.identity,
|
|
82
87
|
path: authority.path,
|
|
83
88
|
realPath: authority.realPath,
|
|
84
89
|
identity: Object.assign(Object.create(fs.Stats.prototype), authority.metadata),
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
export { ensureDurableDirectory, pinDirectory, syncDirectory, syncDirectoryBestEffort, syncDirectoryBestEffortSync, syncDirectorySync, type DirectoryReceipt, type DirectorySyncOutcome, type DurableDirectoryReceipt, type EnsureDurableDirectoryOptions, type PinnedDirectory, } from "./directory-durability.js";
|
|
1
|
+
export { ensureDurableDirectory, pinDirectory, syncDirectory, syncDirectoryBestEffort, syncDirectoryBestEffortSync, syncDirectorySync, type DirectoryReceipt, type ExactDirectoryReceipt, type DirectorySyncOutcome, type DurableDirectoryReceipt, type EnsureDurableDirectoryOptions, type PinnedDirectory, } from "./directory-durability.js";
|
|
2
2
|
export { isHardlinkFallbackError, publishFileExclusive, type PublishFileExclusiveResult, type PublishFileExclusiveStrategy, } from "./publish-file.js";
|
|
3
3
|
export type { PublishFileExclusiveCleanup, PublishFileExclusiveDirectorySyncFailure, PublishFileExclusiveFailureDetails, PublishFileExclusiveFailurePhase, PublishFileExclusiveSyncFailurePolicy, } from "./publish-file-failure.js";
|
|
4
4
|
export { sha256File, sha256FileSync, type Sha256FileInput, type Sha256FileSyncInput, type Sha256FileOptions, type Sha256FileResult, } from "./file-hash.js";
|
|
5
|
+
export type { ExactFileIdentity, ExactIdentityReceipt } from "./exact-identity.js";
|
|
@@ -18,7 +18,7 @@ function basename(name) {
|
|
|
18
18
|
}
|
|
19
19
|
function identity(value) {
|
|
20
20
|
const { dev, ino } = value;
|
|
21
|
-
if (typeof dev !== "bigint" || typeof ino !== "bigint" || dev <
|
|
21
|
+
if (typeof dev !== "bigint" || typeof ino !== "bigint" || dev < -(1n << 63n) || ino < -(1n << 63n) || ino === 0n ||
|
|
22
22
|
dev > 0xffffffffffffffffn || ino > 0xffffffffffffffffn) {
|
|
23
23
|
throw new FsSafeError("path-mismatch", "publication requires an exact known identity");
|
|
24
24
|
}
|
|
@@ -230,7 +230,7 @@ export function retainEntryForPublication(options) {
|
|
|
230
230
|
inspectEntry(sourcePath, source.expected);
|
|
231
231
|
const sourceFd = kind === "symlink"
|
|
232
232
|
? binding.openStagedSymlink(sourceParent.fd, source.basename)
|
|
233
|
-
:
|
|
233
|
+
: native.openBeneath(sourceParent.fd, source.basename, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW | fs.constants.O_NONBLOCK |
|
|
234
234
|
(kind === "directory" ? fs.constants.O_DIRECTORY : 0))?.fd;
|
|
235
235
|
if (!Number.isInteger(sourceFd) || sourceFd < 0) {
|
|
236
236
|
throw new FsSafeError("helper-unavailable", "publication source descriptor is unavailable");
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type BigIntStats, type Stats } from "node:fs";
|
|
2
|
+
export type ExactFileIdentity = Readonly<{
|
|
3
|
+
dev: bigint;
|
|
4
|
+
ino: bigint;
|
|
5
|
+
}>;
|
|
6
|
+
export type ExactIdentityReceipt = {
|
|
7
|
+
readonly exactIdentity: ExactFileIdentity;
|
|
8
|
+
};
|
|
9
|
+
/** Copy scalars from one observation; never try to recover rounded identity bits. */
|
|
10
|
+
export declare function exactFileIdentity(stat: {
|
|
11
|
+
dev: number | bigint;
|
|
12
|
+
ino: number | bigint;
|
|
13
|
+
}): ExactFileIdentity;
|
|
14
|
+
/** Node stores unsigned libuv fields in a signed BigInt64Array. */
|
|
15
|
+
export declare function numberStatScalar(value: number | bigint): number;
|
|
16
|
+
/** Legacy metadata view of the very same bigint observation. */
|
|
17
|
+
export declare function numberStats(stat: BigIntStats): Stats;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import fs, {} from "node:fs";
|
|
2
|
+
import { FsSafeError } from "./errors.js";
|
|
3
|
+
/** Copy scalars from one observation; never try to recover rounded identity bits. */
|
|
4
|
+
export function exactFileIdentity(stat) {
|
|
5
|
+
const { dev, ino } = stat;
|
|
6
|
+
if ((typeof dev !== "bigint" && !Number.isSafeInteger(dev)) ||
|
|
7
|
+
(typeof ino !== "bigint" && !Number.isSafeInteger(ino))) {
|
|
8
|
+
throw new FsSafeError("path-mismatch", "file identity is not exact");
|
|
9
|
+
}
|
|
10
|
+
return Object.freeze({ dev: BigInt(dev), ino: BigInt(ino) });
|
|
11
|
+
}
|
|
12
|
+
/** Node stores unsigned libuv fields in a signed BigInt64Array. */
|
|
13
|
+
export function numberStatScalar(value) {
|
|
14
|
+
return typeof value === "bigint" ? Number(BigInt.asUintN(64, value)) : Number(value);
|
|
15
|
+
}
|
|
16
|
+
/** Legacy metadata view of the very same bigint observation. */
|
|
17
|
+
export function numberStats(stat) {
|
|
18
|
+
const result = Object.create(fs.Stats.prototype);
|
|
19
|
+
for (const field of ["dev", "mode", "nlink", "uid", "gid", "rdev", "blksize", "ino", "size", "blocks"]) {
|
|
20
|
+
result[field] = numberStatScalar(stat[field]);
|
|
21
|
+
}
|
|
22
|
+
for (const field of ["atime", "mtime", "ctime", "birthtime"]) {
|
|
23
|
+
const ns = stat[`${field}Ns`];
|
|
24
|
+
const remainder = ((ns % 1000000000n) + 1000000000n) % 1000000000n;
|
|
25
|
+
result[`${field}Ms`] = Number((ns - remainder) / 1000000000n) * 1_000 + Number(remainder) / 1_000_000;
|
|
26
|
+
// Stats.prototype supplies Node's lazy, correctly rounded Date getters.
|
|
27
|
+
}
|
|
28
|
+
return result;
|
|
29
|
+
}
|
|
@@ -113,7 +113,8 @@ export async function copyFileHandle(source, target, options = {}) {
|
|
|
113
113
|
const assertBeforeMutation = composeMutationAssertions(undefined, options.assertBeforeMutation);
|
|
114
114
|
const sourceStat = await inspectFileIdentity(() => {
|
|
115
115
|
signal?.throwIfAborted();
|
|
116
|
-
|
|
116
|
+
const fd = source.fd;
|
|
117
|
+
return fd < 0 ? source.stat({ bigint: true }) : fs.fstatSync(fd, { bigint: true });
|
|
117
118
|
});
|
|
118
119
|
signal?.throwIfAborted();
|
|
119
120
|
if (!sourceStat.isFile())
|
|
@@ -123,7 +124,8 @@ export async function copyFileHandle(source, target, options = {}) {
|
|
|
123
124
|
}
|
|
124
125
|
const targetStat = await inspectFileIdentity(() => {
|
|
125
126
|
signal?.throwIfAborted();
|
|
126
|
-
|
|
127
|
+
const fd = target.fd;
|
|
128
|
+
return fd < 0 ? target.stat({ bigint: true }) : fs.fstatSync(fd, { bigint: true });
|
|
127
129
|
});
|
|
128
130
|
signal?.throwIfAborted();
|
|
129
131
|
if (!targetStat.isFile())
|
|
@@ -10,7 +10,7 @@ export type Sha256FileResult = {
|
|
|
10
10
|
bytes: number;
|
|
11
11
|
digest: string;
|
|
12
12
|
};
|
|
13
|
-
export declare function hashFileHandle(handle: FileHandle, native?: NativeBinding | undefined, { maxBytes, signal }?: Sha256FileOptions): Promise<Sha256FileResult>;
|
|
13
|
+
export declare function hashFileHandle(handle: FileHandle, native?: NativeBinding | undefined, { maxBytes, signal }?: Sha256FileOptions, ownsPosition?: boolean): Promise<Sha256FileResult>;
|
|
14
14
|
export declare function sha256File(input: Sha256FileInput, options?: Sha256FileOptions): Promise<Sha256FileResult>;
|
|
15
15
|
/** Hash synchronously from offset zero, retaining ownership and position of a borrowed fd. */
|
|
16
16
|
export declare function sha256FileSync(input: Sha256FileSyncInput, options?: Sha256FileOptions): Sha256FileResult;
|
|
@@ -55,9 +55,9 @@ import fsSync from "node:fs";
|
|
|
55
55
|
import fs from "node:fs/promises";
|
|
56
56
|
import { normalizeMaxBytes } from "./byte-budget.js";
|
|
57
57
|
import { FsSafeError } from "./errors.js";
|
|
58
|
-
import { getNativeBinding } from "./native.js";
|
|
58
|
+
import { getNativeBinding, selectNativeBinding } from "./native.js";
|
|
59
59
|
import { resolveReadOpenFlags } from "./read-open-flags.js";
|
|
60
|
-
import {
|
|
60
|
+
import { inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
61
61
|
import { assertNoWindowsPathAlias } from "./windows-path-alias.js";
|
|
62
62
|
// Idle storage is bounded independently of caller concurrency. A pending read
|
|
63
63
|
// retains its exclusive borrow until the async hasher settles.
|
|
@@ -89,7 +89,7 @@ class HashScratch {
|
|
|
89
89
|
hashScratchBuffers.shift();
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
|
-
export async function hashFileHandle(handle, native = getNativeBinding(), { maxBytes = Infinity, signal } = {}) {
|
|
92
|
+
export async function hashFileHandle(handle, native = getNativeBinding(), { maxBytes = Infinity, signal } = {}, ownsPosition = false) {
|
|
93
93
|
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
94
94
|
try {
|
|
95
95
|
signal?.throwIfAborted();
|
|
@@ -100,11 +100,12 @@ export async function hashFileHandle(handle, native = getNativeBinding(), { maxB
|
|
|
100
100
|
if (stat.size > maxBytes) {
|
|
101
101
|
throw new FsSafeError("too-large", `SHA-256 input exceeds ${maxBytes} bytes`);
|
|
102
102
|
}
|
|
103
|
-
|
|
103
|
+
const hasher = selectNativeBinding(native, "sha256File");
|
|
104
|
+
if (hasher) {
|
|
104
105
|
// A completed N-API task can mask later aborts on the same signal.
|
|
105
106
|
const nativeSignal = signal ? AbortSignal.any([signal]) : undefined;
|
|
106
107
|
try {
|
|
107
|
-
const result = await
|
|
108
|
+
const result = await hasher.sha256File(handle.fd, Number.isFinite(maxBytes) ? maxBytes : undefined, nativeSignal, ownsPosition);
|
|
108
109
|
signal?.throwIfAborted();
|
|
109
110
|
return result;
|
|
110
111
|
}
|
|
@@ -183,7 +184,7 @@ function rethrowHashOpenError(error) {
|
|
|
183
184
|
}
|
|
184
185
|
async function hashPath(filePath, options) {
|
|
185
186
|
assertNoWindowsPathAlias(filePath, "filesystem", "SHA-256 path uses a Windows filesystem namespace alias");
|
|
186
|
-
const before =
|
|
187
|
+
const before = inspectFileIdentitySync(() => hashPathIdentity(filePath));
|
|
187
188
|
let handle;
|
|
188
189
|
try {
|
|
189
190
|
options.signal?.throwIfAborted();
|
|
@@ -195,9 +196,10 @@ async function hashPath(filePath, options) {
|
|
|
195
196
|
let completed = false;
|
|
196
197
|
try {
|
|
197
198
|
options.signal?.throwIfAborted();
|
|
198
|
-
const opened =
|
|
199
|
-
|
|
200
|
-
|
|
199
|
+
const opened = inspectFileIdentitySync(() => hashDescriptorIdentity(handle.fd), before);
|
|
200
|
+
inspectFileIdentitySync(() => hashPathIdentity(filePath, true), opened);
|
|
201
|
+
// Only this overload owns the handle and its otherwise unobservable cursor.
|
|
202
|
+
const result = await hashFileHandle(handle, getNativeBinding(), options, true);
|
|
201
203
|
completed = true;
|
|
202
204
|
return result;
|
|
203
205
|
}
|
|
@@ -1,7 +1,13 @@
|
|
|
1
|
+
import type { ExactIdentityReceipt } from "./exact-identity.js";
|
|
2
|
+
export type { ExactFileIdentity, ExactIdentityReceipt } from "./exact-identity.js";
|
|
1
3
|
export type FileIdentityStat = {
|
|
2
4
|
dev: number | bigint;
|
|
3
5
|
ino: number | bigint;
|
|
4
6
|
};
|
|
7
|
+
export type FileIdentityInput = FileIdentityStat | ExactIdentityReceipt;
|
|
8
|
+
/** Only the explicit companion is unambiguous; nested source/target receipts are not inferred. */
|
|
9
|
+
export declare function identityFromReceipt(value: FileIdentityInput): FileIdentityStat;
|
|
10
|
+
export declare function hasSafeFileIdentity(identity: FileIdentityStat): boolean;
|
|
5
11
|
export declare function sha256Hex(data: string | Buffer, encoding?: BufferEncoding): string;
|
|
6
|
-
export declare function sameFileIdentity(left:
|
|
12
|
+
export declare function sameFileIdentity(left: FileIdentityInput, right: FileIdentityInput, platform?: NodeJS.Platform): boolean;
|
|
7
13
|
export declare function sameFileIdentityForCleanup(left: FileIdentityStat, right: FileIdentityStat, platform?: NodeJS.Platform): boolean;
|
|
@@ -1,4 +1,20 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
+
/** Only the explicit companion is unambiguous; nested source/target receipts are not inferred. */
|
|
3
|
+
export function identityFromReceipt(value) {
|
|
4
|
+
if ("exactIdentity" in value) {
|
|
5
|
+
const exact = value.exactIdentity;
|
|
6
|
+
const dev = exact?.dev, ino = exact?.ino;
|
|
7
|
+
// A malformed explicit companion cannot fall back to a lossy legacy view.
|
|
8
|
+
return typeof dev === "bigint" && typeof ino === "bigint" ? { dev, ino } : { dev: NaN, ino: NaN };
|
|
9
|
+
}
|
|
10
|
+
return value;
|
|
11
|
+
}
|
|
12
|
+
export function hasSafeFileIdentity(identity) {
|
|
13
|
+
return isExactStatValue(identity.dev) && isExactStatValue(identity.ino);
|
|
14
|
+
}
|
|
15
|
+
function isExactStatValue(value) {
|
|
16
|
+
return typeof value === "bigint" || Number.isSafeInteger(value);
|
|
17
|
+
}
|
|
2
18
|
function isZero(value) {
|
|
3
19
|
return value === 0 || value === 0n;
|
|
4
20
|
}
|
|
@@ -16,17 +32,23 @@ export function sha256Hex(data, encoding) {
|
|
|
16
32
|
return createHash("sha256").update(buffer).digest("hex");
|
|
17
33
|
}
|
|
18
34
|
export function sameFileIdentity(left, right, platform = process.platform) {
|
|
35
|
+
left = identityFromReceipt(left);
|
|
36
|
+
right = identityFromReceipt(right);
|
|
37
|
+
const leftDev = left.dev;
|
|
38
|
+
const leftIno = left.ino;
|
|
39
|
+
const rightDev = right.dev;
|
|
40
|
+
const rightIno = right.ino;
|
|
19
41
|
// When Windows cannot open a path for stat, libuv's FindFirstFile fallback reports dev=0 and
|
|
20
42
|
// ino=0. Treating either unknown value as a mismatch caused nondeterministic path-mismatch
|
|
21
43
|
// failures on legitimate reads under antivirus or indexer contention.
|
|
22
|
-
return (
|
|
23
|
-
|
|
44
|
+
return (isExactStatValue(leftDev) && isExactStatValue(leftIno) &&
|
|
45
|
+
isExactStatValue(rightDev) && isExactStatValue(rightIno) &&
|
|
46
|
+
!isStatValueProvablyDifferent(leftDev, rightDev, platform) &&
|
|
47
|
+
!isStatValueProvablyDifferent(leftIno, rightIno, platform));
|
|
24
48
|
}
|
|
25
49
|
function sameCleanupStatValue(left, right) {
|
|
26
50
|
// Converting an unsafe number to bigint cannot recover an already rounded ID.
|
|
27
|
-
if (
|
|
28
|
-
return false;
|
|
29
|
-
if (typeof right === "number" && !Number.isSafeInteger(right))
|
|
51
|
+
if (!isExactStatValue(left) || !isExactStatValue(right))
|
|
30
52
|
return false;
|
|
31
53
|
return sameStatValue(left, right);
|
|
32
54
|
}
|
|
@@ -38,8 +38,7 @@ export function defaultSyncShouldReclaim(snapshot, staleMs, nowMs) {
|
|
|
38
38
|
}
|
|
39
39
|
export function syncReclaimGuardExists(reclaimGuardPath) {
|
|
40
40
|
try {
|
|
41
|
-
fs.lstatSync(reclaimGuardPath);
|
|
42
|
-
return true;
|
|
41
|
+
return fs.lstatSync(reclaimGuardPath, { throwIfNoEntry: false }) !== undefined;
|
|
43
42
|
}
|
|
44
43
|
catch (error) {
|
|
45
44
|
if (error.code === "ENOENT")
|
|
@@ -91,7 +91,14 @@ function canonicalLockParentSync(parent) {
|
|
|
91
91
|
function normalizeTargetPath(targetPath) {
|
|
92
92
|
const resolved = path.resolve(targetPath);
|
|
93
93
|
assertNoWindowsPathAlias(resolved);
|
|
94
|
-
|
|
94
|
+
const directory = path.dirname(resolved);
|
|
95
|
+
let parentExists = false;
|
|
96
|
+
try {
|
|
97
|
+
parentExists = fs.statSync(directory).isDirectory();
|
|
98
|
+
}
|
|
99
|
+
catch { /* Preserve mkdir errors below. */ }
|
|
100
|
+
if (!parentExists)
|
|
101
|
+
fs.mkdirSync(recursiveMkdirPath(directory), { recursive: true });
|
|
95
102
|
let parent;
|
|
96
103
|
try {
|
|
97
104
|
parent = canonicalLockParentSync(path.dirname(resolved));
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type SidecarLockAcquireOptions, type SidecarLockHandle, type SidecarLockHeldEntry, type SidecarLockRetryOptions, type SidecarLockStaleRecovery } from "./sidecar-lock.js";
|
|
2
|
+
export { tryAcquireWriteLease, type FileWriteLease } from "./file-write-lease.js";
|
|
2
3
|
export { acquireFileLockSync, withFileLockSync, type FileLockSyncAcquireOptions, type FileLockSyncHandle, } from "./file-lock-sync.js";
|
|
3
4
|
export type FileLockRetryOptions = SidecarLockRetryOptions;
|
|
4
5
|
export type FileLockStaleRecovery = SidecarLockStaleRecovery;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createSidecarLockManager, } from "./sidecar-lock.js";
|
|
2
2
|
import { getFsSafeLockConfig } from "./lock-config.js";
|
|
3
|
+
export { tryAcquireWriteLease } from "./file-write-lease.js";
|
|
3
4
|
export { acquireFileLockSync, withFileLockSync, } from "./file-lock-sync.js";
|
|
4
5
|
function resolveFileLockManagerKey(targetPath, managerKey) {
|
|
5
6
|
return managerKey ?? `fs-safe.file-lock:${targetPath}`;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type FileWriteLease = {
|
|
2
|
+
readonly fd: number;
|
|
3
|
+
/** False after release or when the kernel breaks or downgrades the lease. */
|
|
4
|
+
isHeld(): boolean;
|
|
5
|
+
release(): void;
|
|
6
|
+
[Symbol.dispose](): void;
|
|
7
|
+
};
|
|
8
|
+
/** Linux kernel write lease. The caller retains the open fd and SIGIO handling. */
|
|
9
|
+
export declare function tryAcquireWriteLease(fd: number): FileWriteLease | null;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { FsSafeError } from "./errors.js";
|
|
2
|
+
import { requireNativeBinding } from "./native.js";
|
|
3
|
+
/** Linux kernel write lease. The caller retains the open fd and SIGIO handling. */
|
|
4
|
+
export function tryAcquireWriteLease(fd) {
|
|
5
|
+
if (process.platform !== "linux") {
|
|
6
|
+
throw new FsSafeError("unsupported-platform", "File write leases require Linux");
|
|
7
|
+
}
|
|
8
|
+
if (!Number.isInteger(fd) || fd < 0 || fd > 0x7fffffff) {
|
|
9
|
+
throw new FsSafeError("invalid-path", "Expected an open file descriptor");
|
|
10
|
+
}
|
|
11
|
+
const native = requireNativeBinding();
|
|
12
|
+
const acquire = native.tryAcquireWriteLease?.bind(native);
|
|
13
|
+
const held = native.isFileWriteLeaseHeld?.bind(native);
|
|
14
|
+
const unlock = native.releaseFileWriteLease?.bind(native);
|
|
15
|
+
if (!acquire || !held || !unlock) {
|
|
16
|
+
throw new FsSafeError("helper-unavailable", "Native file write leases are unavailable");
|
|
17
|
+
}
|
|
18
|
+
const call = (operation) => {
|
|
19
|
+
try {
|
|
20
|
+
return operation();
|
|
21
|
+
}
|
|
22
|
+
catch (cause) {
|
|
23
|
+
throw new FsSafeError("helper-failed", "Native file write lease operation failed", { cause });
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
if (!call(() => acquire(fd)))
|
|
27
|
+
return null;
|
|
28
|
+
let released = false;
|
|
29
|
+
const release = () => {
|
|
30
|
+
if (released)
|
|
31
|
+
return;
|
|
32
|
+
// Never retry an ambiguous settlement against a potentially recycled fd.
|
|
33
|
+
released = true;
|
|
34
|
+
call(() => unlock(fd));
|
|
35
|
+
};
|
|
36
|
+
return Object.freeze({
|
|
37
|
+
fd,
|
|
38
|
+
isHeld: () => !released && call(() => held(fd)),
|
|
39
|
+
release,
|
|
40
|
+
[Symbol.dispose]: release,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
@@ -14,6 +14,7 @@ export declare function mkdirPathComponentsWithGuards(params: {
|
|
|
14
14
|
afterCreateComponent?: (receipt: PinnedCreatedDirectoryReceipt) => PinnedMutationAuthorizationToken | undefined;
|
|
15
15
|
assertBeforeMutation?: () => void;
|
|
16
16
|
createMissing?: boolean;
|
|
17
|
+
synchronousMkdir?: boolean;
|
|
17
18
|
mode?: number;
|
|
18
19
|
private?: boolean;
|
|
19
20
|
rejectSymlinks?: boolean;
|
|
@@ -2,7 +2,7 @@ import fsSync, {} from "node:fs";
|
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { createDirectoryWithAdmission } from "./create.js";
|
|
5
|
-
import {
|
|
5
|
+
import { assertDirectoryGuard, captureDirectoryGuard, inspectDirectoryIdentitySync, } from "./directory-guard.js";
|
|
6
6
|
import { FsSafeError } from "./errors.js";
|
|
7
7
|
import { hasNodeErrorCode, isNotFoundPathError, isPathRelativeEscape } from "./path.js";
|
|
8
8
|
import { directoryComponentNotDirectoryError, rootPathChangedError } from "./root-errors.js";
|
|
@@ -55,6 +55,9 @@ function createdDirectoryEvidence(admission, parent, childPath) {
|
|
|
55
55
|
return undefined;
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
|
+
function captureExactDirectoryGuard(dir) {
|
|
59
|
+
return captureDirectoryGuard(dir, "native", { bigint: true });
|
|
60
|
+
}
|
|
58
61
|
function suppliedExactRootIdentity(identity) {
|
|
59
62
|
return typeof identity?.dev === "bigint" && typeof identity.ino === "bigint"
|
|
60
63
|
? { dev: identity.dev, ino: identity.ino }
|
|
@@ -68,7 +71,7 @@ function assertGuardMatchesRootIdentity(guard, expected) {
|
|
|
68
71
|
function isPromiseLike(value) {
|
|
69
72
|
return typeof value?.then === "function";
|
|
70
73
|
}
|
|
71
|
-
|
|
74
|
+
function realpathOrThrowNotFile(target) {
|
|
72
75
|
try {
|
|
73
76
|
const canonical = realpathSync.native(pathForWindowsFilesystem(target));
|
|
74
77
|
assertNoWindowsPathAlias(canonical, "filesystem", "canonical directory uses a Windows filesystem namespace alias");
|
|
@@ -95,7 +98,7 @@ export async function mkdirPathComponentsWithGuards(params) {
|
|
|
95
98
|
const root = resolvePathPreservingWindowsRoot(rawRootReal);
|
|
96
99
|
const target = resolvePathPreservingWindowsRoot(rawTargetPath);
|
|
97
100
|
const suppliedIdentity = suppliedExactRootIdentity(params.rootIdentity);
|
|
98
|
-
const configuredRootGuard =
|
|
101
|
+
const configuredRootGuard = captureExactDirectoryGuard(root);
|
|
99
102
|
const checkedRootIdentity = suppliedIdentity ?? {
|
|
100
103
|
dev: configuredRootGuard.stat.dev,
|
|
101
104
|
ino: configuredRootGuard.stat.ino,
|
|
@@ -105,7 +108,7 @@ export async function mkdirPathComponentsWithGuards(params) {
|
|
|
105
108
|
const rootCanonical = resolvePathPreservingWindowsRoot(configuredRootGuard.realPath);
|
|
106
109
|
const rootGuard = rootCanonical === root
|
|
107
110
|
? configuredRootGuard
|
|
108
|
-
:
|
|
111
|
+
: captureExactDirectoryGuard(rootCanonical);
|
|
109
112
|
assertGuardMatchesRootIdentity(rootGuard, checkedRootIdentity);
|
|
110
113
|
const admissionParams = {
|
|
111
114
|
candidatePath: target,
|
|
@@ -138,11 +141,12 @@ export async function mkdirPathComponentsWithGuards(params) {
|
|
|
138
141
|
let created = false;
|
|
139
142
|
let createReceipt;
|
|
140
143
|
if (!params.revalidateParentAfterBeforeComponent) {
|
|
141
|
-
|
|
144
|
+
assertDirectoryGuard(parentGuard, "native");
|
|
142
145
|
}
|
|
143
|
-
|
|
146
|
+
if (params.beforeComponent)
|
|
147
|
+
await params.beforeComponent(next);
|
|
144
148
|
if (params.revalidateParentAfterBeforeComponent) {
|
|
145
|
-
|
|
149
|
+
assertDirectoryGuard(parentGuard, "native");
|
|
146
150
|
}
|
|
147
151
|
let shouldCreate = true;
|
|
148
152
|
if (params.beforeCreateComponent) {
|
|
@@ -165,7 +169,7 @@ export async function mkdirPathComponentsWithGuards(params) {
|
|
|
165
169
|
const authorization = params.beforeCreateComponent(next, prospectiveParent, retainedTargetPath, observedGuard(parentGuard));
|
|
166
170
|
if (isPromiseLike(authorization)) {
|
|
167
171
|
createReceipt = await authorization;
|
|
168
|
-
|
|
172
|
+
assertDirectoryGuard(parentGuard, "native");
|
|
169
173
|
}
|
|
170
174
|
else {
|
|
171
175
|
createReceipt = authorization;
|
|
@@ -193,7 +197,10 @@ export async function mkdirPathComponentsWithGuards(params) {
|
|
|
193
197
|
});
|
|
194
198
|
}
|
|
195
199
|
else {
|
|
196
|
-
|
|
200
|
+
if (params.synchronousMkdir)
|
|
201
|
+
fsSync.mkdirSync(next, { mode: params.mode });
|
|
202
|
+
else
|
|
203
|
+
await fs.mkdir(next, { mode: params.mode });
|
|
197
204
|
}
|
|
198
205
|
created = true;
|
|
199
206
|
}
|
|
@@ -217,7 +224,7 @@ export async function mkdirPathComponentsWithGuards(params) {
|
|
|
217
224
|
prospectiveParent = joinPathSegmentRoute(next, partRoute, index + 1);
|
|
218
225
|
}
|
|
219
226
|
await params.beforeUseComponent(next, prospectiveParent, retainedTargetPath);
|
|
220
|
-
|
|
227
|
+
assertDirectoryGuard(parentGuard, "native");
|
|
221
228
|
}
|
|
222
229
|
const stat = createdAuthorization ? undefined : fsSync.lstatSync(next);
|
|
223
230
|
if (stat && ((params.rejectSymlinks && stat.isSymbolicLink()) ||
|
|
@@ -228,7 +235,7 @@ export async function mkdirPathComponentsWithGuards(params) {
|
|
|
228
235
|
// segment at a time and realpath-check each segment before descending.
|
|
229
236
|
const nextReal = createdAuthorization && createdEvidence
|
|
230
237
|
? createdEvidence.receipt.child.canonicalPath
|
|
231
|
-
:
|
|
238
|
+
: realpathOrThrowNotFile(next);
|
|
232
239
|
const admittedNextReal = admitPathInsideRoot({
|
|
233
240
|
rootPath: rootCanonical,
|
|
234
241
|
candidatePath: nextReal,
|
|
@@ -245,9 +252,9 @@ export async function mkdirPathComponentsWithGuards(params) {
|
|
|
245
252
|
if (!targetStat.isDirectory()) {
|
|
246
253
|
throw directoryComponentNotDirectoryError();
|
|
247
254
|
}
|
|
248
|
-
currentGuard =
|
|
255
|
+
currentGuard = captureExactDirectoryGuard(admittedNextPath);
|
|
249
256
|
assertNoWindowsPathAlias(currentGuard.realPath, "filesystem", "canonical directory uses a Windows filesystem namespace alias");
|
|
250
|
-
|
|
257
|
+
assertDirectoryGuard(parentGuard, "native");
|
|
251
258
|
retainedTargetPath = undefined;
|
|
252
259
|
retainedParentPath = undefined;
|
|
253
260
|
current = admittedNextPath;
|
|
@@ -257,11 +264,11 @@ export async function mkdirPathComponentsWithGuards(params) {
|
|
|
257
264
|
currentGuard = createdEvidence.guard;
|
|
258
265
|
}
|
|
259
266
|
else {
|
|
260
|
-
currentGuard =
|
|
267
|
+
currentGuard = captureExactDirectoryGuard(admittedNextPath);
|
|
261
268
|
}
|
|
262
269
|
assertNoWindowsPathAlias(currentGuard.realPath, "filesystem", "canonical directory uses a Windows filesystem namespace alias");
|
|
263
270
|
if (!createdAuthorization || !createdEvidence) {
|
|
264
|
-
|
|
271
|
+
assertDirectoryGuard(parentGuard, "native");
|
|
265
272
|
}
|
|
266
273
|
current = admittedNextPath;
|
|
267
274
|
}
|
|
@@ -92,6 +92,9 @@ export const GUEST_FILESYSTEM_RENAME_NO_REPLACE_PYTHON = [
|
|
|
92
92
|
" try:",
|
|
93
93
|
" os.rename(src_basename + '/', dst_basename, src_dir_fd=src_parent_fd, dst_dir_fd=dst_parent_fd)",
|
|
94
94
|
" except OSError as error:",
|
|
95
|
+
" # ENOTDIR can describe the slash-checked source, not just the target.",
|
|
96
|
+
" if error.errno == errno.ENOTDIR:",
|
|
97
|
+
" verify(src_parent_fd, src_basename, 1)",
|
|
95
98
|
" if error.errno in (errno.ENOTEMPTY, errno.ENOTDIR, errno.EEXIST):",
|
|
96
99
|
" raise FileExistsError(errno.EEXIST, os.strerror(errno.EEXIST), dst_basename) from error",
|
|
97
100
|
" raise",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function isHardlinkFallbackError(error: unknown): boolean;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// These refusals leave the destination uncreated. EEXIST is deliberately excluded.
|
|
2
|
+
const HARDLINK_FALLBACK_CODES = new Set(["EACCES", "EPERM", "EXDEV", "ENOTSUP", "EOPNOTSUPP", "ENOSYS"]);
|
|
3
|
+
export function isHardlinkFallbackError(error) {
|
|
4
|
+
return HARDLINK_FALLBACK_CODES.has(error?.code ?? "");
|
|
5
|
+
}
|
|
@@ -3,6 +3,7 @@ export type * from "./errors.js";
|
|
|
3
3
|
export { DEFAULT_ROOT_MAX_BYTES, root, type Root, } from "./root.js";
|
|
4
4
|
export type * from "./root-public-types.js";
|
|
5
5
|
export { configureFsSafeNative, getFsSafeNativeConfig, } from "./native-config.js";
|
|
6
|
+
export { getFsSafeNativeStatus } from "./config.js";
|
|
6
7
|
export type * from "./config.js";
|
|
7
8
|
export { writeExternalFileWithinRoot, } from "./output.js";
|
|
8
9
|
export type * from "./output.js";
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { FsSafeError, categorizeFsSafeError, isNoReplaceUnsupported, } from "./errors.js";
|
|
2
2
|
export { DEFAULT_ROOT_MAX_BYTES, root, } from "./root.js";
|
|
3
3
|
export { configureFsSafeNative, getFsSafeNativeConfig, } from "./native-config.js";
|
|
4
|
+
export { getFsSafeNativeStatus } from "./config.js";
|
|
4
5
|
export { writeExternalFileWithinRoot, } from "./output.js";
|
|
5
6
|
export { configureFsSafeLocks, getFsSafeLockConfig, } from "./lock-config.js";
|
|
@@ -6,6 +6,7 @@ import path from "node:path";
|
|
|
6
6
|
import { syncDirectory } from "./directory-durability.js";
|
|
7
7
|
import { FsSafeError } from "./errors.js";
|
|
8
8
|
import { sameFileIdentityForCleanup, sha256Hex } from "./file-identity.js";
|
|
9
|
+
import { isHardlinkFallbackError } from "./hardlink-fallback.js";
|
|
9
10
|
import { inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
10
11
|
import { serializePathWrite } from "./write-queue.js";
|
|
11
12
|
import { assertNoWindowsPathAlias } from "./windows-path-alias.js";
|
|
@@ -61,6 +62,18 @@ async function withQueueEntryLock(paths, run) {
|
|
|
61
62
|
assertNoWindowsPathAlias(resolvedJsonPath);
|
|
62
63
|
return await serializePathWrite(paths.jsonPath, async () => await withQueueTransferLock(resolvedJsonPath, run));
|
|
63
64
|
}
|
|
65
|
+
async function linkQueueGeneration(sourcePath, targetPath) {
|
|
66
|
+
try {
|
|
67
|
+
await fs.link(sourcePath, targetPath);
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
// Shared inode identity is recovery evidence; a copy cannot replace it.
|
|
71
|
+
if (isHardlinkFallbackError(error)) {
|
|
72
|
+
throw new FsSafeError("helper-unavailable", "durable queue ownership requires hardlink support", { cause: error });
|
|
73
|
+
}
|
|
74
|
+
throw error;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
64
77
|
async function claimDurableQueueEntryUnlocked(paths, options = {}) {
|
|
65
78
|
const processingPath = paths.processingPath;
|
|
66
79
|
await recoverDurableQueueRetirement({ jsonPath: paths.jsonPath, processingPath });
|
|
@@ -92,7 +105,7 @@ async function claimDurableQueueEntryUnlocked(paths, options = {}) {
|
|
|
92
105
|
throw new FsSafeError("path-mismatch", "queue entry is not exclusively owned");
|
|
93
106
|
}
|
|
94
107
|
try {
|
|
95
|
-
await
|
|
108
|
+
await linkQueueGeneration(paths.jsonPath, processingPath);
|
|
96
109
|
}
|
|
97
110
|
catch (error) {
|
|
98
111
|
if (getErrorCode(error) === "EEXIST") {
|
|
@@ -227,7 +240,7 @@ export async function moveDurableQueueEntryToFailed(params) {
|
|
|
227
240
|
throw new FsSafeError("already-exists", "failed queue destination already exists");
|
|
228
241
|
}
|
|
229
242
|
if (!failed)
|
|
230
|
-
await
|
|
243
|
+
await linkQueueGeneration(sourcePath, failedPath);
|
|
231
244
|
await syncDirectory(path.dirname(failedPath));
|
|
232
245
|
await fs.unlink(sourcePath);
|
|
233
246
|
await syncDirectory(path.dirname(sourcePath));
|
|
@@ -305,7 +318,7 @@ async function recoverDurableQueueRetirement(params) {
|
|
|
305
318
|
if (!sameFileIdentityForCleanup(entry, processing)) {
|
|
306
319
|
const pending = regularQueueFileIdentity(params.jsonPath, "retirement");
|
|
307
320
|
if (!pending) {
|
|
308
|
-
await
|
|
321
|
+
await linkQueueGeneration(entryPath, params.jsonPath);
|
|
309
322
|
await syncDirectory(path.dirname(params.jsonPath));
|
|
310
323
|
}
|
|
311
324
|
else if (sameFileIdentityForCleanup(pending, entry)) {
|