@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,3 +1,4 @@
|
|
|
1
|
+
import { numberStats } from "./exact-identity.js";
|
|
1
2
|
import fsSync from "node:fs";
|
|
2
3
|
import fs from "node:fs/promises";
|
|
3
4
|
import { FsSafeError } from "./errors.js";
|
|
@@ -5,7 +6,7 @@ import { recordFileOpenFailure, recordPreOpenFileChange } from "./opened-file-fa
|
|
|
5
6
|
import { assertNoUnsafeDeviceReadPath, hasNodeErrorCode, isNotFoundPathError, isSymlinkOpenError, } from "./path.js";
|
|
6
7
|
import { resolveReadOpenFlags } from "./read-open-flags.js";
|
|
7
8
|
import { fileNotFoundError, hardlinkedPathNotAllowedError } from "./root-errors.js";
|
|
8
|
-
import {
|
|
9
|
+
import { inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
9
10
|
import { getFsSafeTestHooks } from "./test-hooks.js";
|
|
10
11
|
const OPEN_READ_FLAGS = resolveReadOpenFlags();
|
|
11
12
|
const OPEN_READ_FOLLOW_FLAGS = resolveReadOpenFlags({ followSymlinks: true });
|
|
@@ -19,7 +20,7 @@ export async function openLocalFileDescriptor(filePath, options) {
|
|
|
19
20
|
// Reject directories before opening so we never surface EISDIR to callers (e.g. tool
|
|
20
21
|
// results that get sent to messaging channels). See openclaw/openclaw#31186.
|
|
21
22
|
try {
|
|
22
|
-
preOpenStat =
|
|
23
|
+
preOpenStat = inspectFileIdentitySync(() => {
|
|
23
24
|
const stat = fsSync.lstatSync(filePath, { bigint: true });
|
|
24
25
|
observedBeforeOpen = true;
|
|
25
26
|
if (stat.isSymbolicLink() && options?.symlinks !== "follow-within-root") {
|
|
@@ -37,13 +38,14 @@ export async function openLocalFileDescriptor(filePath, options) {
|
|
|
37
38
|
}
|
|
38
39
|
// Only an initial lookup failure falls through; a failed re-inspection aborts.
|
|
39
40
|
}
|
|
40
|
-
if (preOpenStat) {
|
|
41
|
-
await fsSafeTestHooks
|
|
41
|
+
if (preOpenStat && fsSafeTestHooks?.afterPreOpenLstat) {
|
|
42
|
+
await fsSafeTestHooks.afterPreOpenLstat(filePath);
|
|
42
43
|
}
|
|
43
44
|
const openFlags = (options?.symlinks === "follow-within-root"
|
|
44
45
|
? OPEN_READ_FOLLOW_FLAGS
|
|
45
46
|
: OPEN_READ_FLAGS) | (options?.readWrite ? fsSync.constants.O_RDWR : 0);
|
|
46
|
-
|
|
47
|
+
if (fsSafeTestHooks?.beforeOpen)
|
|
48
|
+
await fsSafeTestHooks.beforeOpen(filePath, openFlags);
|
|
47
49
|
let handle;
|
|
48
50
|
try {
|
|
49
51
|
handle = await fs.open(filePath, openFlags).catch((error) => recordFileOpenFailure(isNotFoundPathError(error) ? fileNotFoundError() : error, filePath));
|
|
@@ -59,19 +61,28 @@ export async function openLocalFileDescriptor(filePath, options) {
|
|
|
59
61
|
throw err;
|
|
60
62
|
}
|
|
61
63
|
try {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
if (!stat.isFile()) {
|
|
65
|
-
throw new FsSafeError("not-file", "not a file");
|
|
66
|
-
}
|
|
64
|
+
if (fsSafeTestHooks?.afterOpen)
|
|
65
|
+
await fsSafeTestHooks.afterOpen(filePath, handle);
|
|
67
66
|
// Keep numeric Stats for the public receipt, never for identity verification.
|
|
68
67
|
let openedIdentity;
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
let identity;
|
|
69
|
+
try {
|
|
70
|
+
identity = inspectFileIdentitySync(() => {
|
|
71
|
+
openedIdentity = fsSync.fstatSync(handle.fd, { bigint: true });
|
|
72
|
+
if (!openedIdentity.isFile())
|
|
73
|
+
throw new FsSafeError("not-file", "not a file");
|
|
74
|
+
return openedIdentity;
|
|
75
|
+
}, preOpenStat && !preOpenStat.isSymbolicLink() ? preOpenStat : undefined);
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
if (openedIdentity && [0n, 1n].includes(openedIdentity.nlink)) {
|
|
71
79
|
await recordPreOpenFileChange(error, handle, filePath, preOpenStat, openedIdentity);
|
|
72
80
|
}
|
|
73
81
|
throw error;
|
|
74
|
-
}
|
|
82
|
+
}
|
|
83
|
+
if (!identity.isFile())
|
|
84
|
+
throw new FsSafeError("not-file", "not a file");
|
|
85
|
+
const stat = numberStats(identity);
|
|
75
86
|
if (options?.hardlinks === "reject" && stat.nlink > 1) {
|
|
76
87
|
throw hardlinkedPathNotAllowedError();
|
|
77
88
|
}
|
|
@@ -81,6 +81,9 @@ export interface NativeOwnedTreeRemovalResult {
|
|
|
81
81
|
errorCode?: string;
|
|
82
82
|
errorMessage?: string;
|
|
83
83
|
}
|
|
84
|
+
export interface NativeEmptyTreeRemovalResult extends Omit<NativeOwnedTreeRemovalResult, "outcome"> {
|
|
85
|
+
outcome?: "removed" | "preserved" | "not-empty";
|
|
86
|
+
}
|
|
84
87
|
export interface NativeRootRemovalEntry {
|
|
85
88
|
dev: bigint;
|
|
86
89
|
ino: bigint;
|
|
@@ -101,6 +104,7 @@ export interface NativeWindowsAccessControlEntry {
|
|
|
101
104
|
export interface NativeWindowsSecurityFacts {
|
|
102
105
|
ownerSid: string;
|
|
103
106
|
currentUserSid: string;
|
|
107
|
+
currentAppContainerSid?: string | null;
|
|
104
108
|
ownerClass: string;
|
|
105
109
|
worldWritable: boolean;
|
|
106
110
|
groupWritable: boolean;
|
|
@@ -124,6 +128,8 @@ export interface NativeWindowsDirectoryReceipt {
|
|
|
124
128
|
}
|
|
125
129
|
export interface NativeDarwinAclFacts {
|
|
126
130
|
state: "absent" | "empty" | "present";
|
|
131
|
+
inheritsToFiles?: boolean;
|
|
132
|
+
inheritsToDirectories?: boolean;
|
|
127
133
|
}
|
|
128
134
|
type NativeTwoPathArgs = [
|
|
129
135
|
sourceRootFd: number,
|
|
@@ -132,6 +138,37 @@ type NativeTwoPathArgs = [
|
|
|
132
138
|
targetRelPath: string
|
|
133
139
|
];
|
|
134
140
|
export interface NativeBinding {
|
|
141
|
+
readCopyMetadata?(fd: number): Buffer;
|
|
142
|
+
restoreCopyMetadata?(fd: number, snapshot: Buffer): void;
|
|
143
|
+
restoreCopyFileTimes?(fd: number, atimeNs: bigint, mtimeNs: bigint): void;
|
|
144
|
+
copyLinkExclusive?(sourceParent: number, sourceName: string, parent: number, name: string, dev: bigint, ino: bigint, preserveMetadata: boolean, mode: number): {
|
|
145
|
+
dev: bigint;
|
|
146
|
+
ino: bigint;
|
|
147
|
+
};
|
|
148
|
+
publishCopyLink?(parent: number, name: string, destination: string, dev: bigint, ino: bigint): void;
|
|
149
|
+
removeCopyLink?(parent: number, name: string, dev: bigint, ino: bigint): void;
|
|
150
|
+
createCopySymlink?(parent: number, name: string, target: Buffer, mode: number, atimeNs?: bigint, mtimeNs?: bigint): {
|
|
151
|
+
dev: bigint;
|
|
152
|
+
ino: bigint;
|
|
153
|
+
errorCode?: string;
|
|
154
|
+
errorMessage?: string;
|
|
155
|
+
};
|
|
156
|
+
tryAcquireWriteLease?(fd: number): boolean;
|
|
157
|
+
isFileWriteLeaseHeld?(fd: number): boolean;
|
|
158
|
+
releaseFileWriteLease?(fd: number): void;
|
|
159
|
+
holdWindowsSharingLock?(path: string): {
|
|
160
|
+
close(): void;
|
|
161
|
+
};
|
|
162
|
+
setWindowsFileAttributes?(path: string, attrs: {
|
|
163
|
+
readOnly?: boolean;
|
|
164
|
+
hidden?: boolean;
|
|
165
|
+
system?: boolean;
|
|
166
|
+
}): void;
|
|
167
|
+
readWindowsFileExtents?(path: string): {
|
|
168
|
+
vcn: bigint;
|
|
169
|
+
lcn: bigint;
|
|
170
|
+
clusters: bigint;
|
|
171
|
+
}[];
|
|
135
172
|
rootRemovalStat?(parent: number, name: string): NativeRootRemovalEntry;
|
|
136
173
|
rootRemovalUnlink?(parent: number, name: string, dev: bigint, ino: bigint, directory: boolean): void;
|
|
137
174
|
openRootRemovalDirectory?(parent: number, name: string, dev: bigint, ino: bigint): NativeRootRemovalDirectory;
|
|
@@ -169,6 +206,11 @@ export interface NativeBinding {
|
|
|
169
206
|
};
|
|
170
207
|
/** Internal: consumes only a descriptor returned by this binding. */
|
|
171
208
|
closeOwnedFd(fd: number): void;
|
|
209
|
+
createPipe?(): {
|
|
210
|
+
reader: number;
|
|
211
|
+
writer: number;
|
|
212
|
+
atomicCloseOnExec: boolean;
|
|
213
|
+
};
|
|
172
214
|
/** Internal Darwin-only synchronous inspection; the caller retains its fd. */
|
|
173
215
|
inspectDarwinAcl?(fd: number, inheritanceTarget?: "file" | "directory"): NativeDarwinAclFacts;
|
|
174
216
|
/** POSIX system canonicalization; confinement and identity policy stay with callers. */
|
|
@@ -176,6 +218,10 @@ export interface NativeBinding {
|
|
|
176
218
|
path?: string;
|
|
177
219
|
errno?: number;
|
|
178
220
|
};
|
|
221
|
+
/** Internal Windows normalized name resolution after a denied DOS-name query. */
|
|
222
|
+
resolveWindowsCanonicalPath?(path: string): string | null;
|
|
223
|
+
/** Internal immutable primary-token membership, cached across Workers. */
|
|
224
|
+
isWindowsAppContainer?(): boolean;
|
|
179
225
|
/** Internal: exact directory identity and canonical path from one no-follow handle. */
|
|
180
226
|
observeDirectory?(path: string): NativeDirectoryObservation;
|
|
181
227
|
/** Internal POSIX-only exact name/descriptor observation; the caller retains its fd. */
|
|
@@ -183,66 +229,87 @@ export interface NativeBinding {
|
|
|
183
229
|
/** Linux/Windows byte transfer; callers retain both admitted descriptors until settlement. */
|
|
184
230
|
copyFileContents?(sourceFd: number, targetFd: number, signal?: AbortSignal): Promise<void>;
|
|
185
231
|
/** Internal: same private, immutable input ownership as the ZIP buffer reader. */
|
|
186
|
-
openTarBufferNative(buffer: Buffer, kind: string, limits: TarMeterLimits, signal?: AbortSignal): Promise<{
|
|
232
|
+
openTarBufferNative?(buffer: Buffer, kind: string, limits: TarMeterLimits, signal?: AbortSignal): Promise<{
|
|
187
233
|
readonly entries: NativeArchiveEntry[];
|
|
188
234
|
readEntry(index: number, maxBytes: number, signal?: AbortSignal): Promise<Buffer>;
|
|
189
235
|
}>;
|
|
190
236
|
/** Internal: input remains private, unpooled and immutable until the reader is released. */
|
|
191
|
-
openZipBufferNative(buffer: Buffer, limits: TarMeterLimits, signal?: AbortSignal): Promise<{
|
|
237
|
+
openZipBufferNative?(buffer: Buffer, limits: TarMeterLimits, signal?: AbortSignal): Promise<{
|
|
192
238
|
readonly entries: NativeArchiveEntry[];
|
|
193
239
|
readEntry(index: number, maxBytes: number, signal?: AbortSignal): Promise<Buffer>;
|
|
194
240
|
}>;
|
|
195
|
-
readCloneFileMetadata(paths: string[]): Promise<(Buffer | null)[]>;
|
|
196
|
-
probeTreeClone(parentFd: number): "apfs" | "btrfs" | "refs" | "xfs" | "zfs" | null;
|
|
197
|
-
cloneTree(sourceFd: number | null, parentFd: number, basename: string, concurrency: number, signal?: AbortSignal): Promise<void>;
|
|
241
|
+
readCloneFileMetadata?(paths: string[]): Promise<(Buffer | null)[]>;
|
|
242
|
+
probeTreeClone?(parentFd: number): "apfs" | "btrfs" | "refs" | "xfs" | "zfs" | null;
|
|
243
|
+
cloneTree?(sourceFd: number | null, parentFd: number, basename: string, concurrency: number, signal?: AbortSignal): Promise<void>;
|
|
198
244
|
openStagedSymlink?(parentFd: number, basename: string): number;
|
|
199
245
|
stagedSymlinkTarget?(parentFd: number, basename: string, linkFd: number): string;
|
|
200
246
|
stagedSymlinkMatches?(parentFd: number, basename: string, linkFd: number): boolean;
|
|
201
247
|
publishStagedSymlink?(parentFd: number, basename: string, linkFd: number, destination: string): void;
|
|
202
248
|
removeStagedSymlink?(parentFd: number, basename: string, linkFd: number): "removed" | "name-absent" | "preserved";
|
|
203
249
|
createStagedFile?(parentFd: number, basename: string): number;
|
|
250
|
+
stagedFileMetadata?(parentFd: number, basename: string, fd: number): {
|
|
251
|
+
mode: bigint;
|
|
252
|
+
nlink: bigint;
|
|
253
|
+
} | null;
|
|
204
254
|
stagedFileMatches?(parentFd: number, basename: string, fileFd: number): boolean;
|
|
255
|
+
stagedDirectoryMatches?(fd: number, path: string, dev: bigint, ino: bigint): boolean;
|
|
205
256
|
removeStagedFile?(parentFd: number, basename: string, fileFd: number): "removed" | "name-absent" | "preserved";
|
|
206
257
|
copyFileExclusive?(sourceFd: number, parentFd: number, basename: string, clone: CopyCloneMode, maxBytes: number | undefined, signal: AbortSignal | undefined): Promise<NativeFileCopyResult>;
|
|
207
258
|
copyFileExclusiveSync?(sourceFd: number, parentFd: number, basename: string, clone: CopyCloneMode, maxBytes: number | undefined): NativeFileCopyResult;
|
|
208
|
-
cloneFileExclusive(sourceFd: number, targetRootFd: number, targetRelPath: string): number;
|
|
209
|
-
copyFileRangeExclusive(sourceFd: number, targetRootFd: number, targetRelPath: string): Promise<NativeCopyResult>;
|
|
210
|
-
createPrivateDirectory(path: string): void;
|
|
259
|
+
cloneFileExclusive?(sourceFd: number, targetRootFd: number, targetRelPath: string): number;
|
|
260
|
+
copyFileRangeExclusive?(sourceFd: number, targetRootFd: number, targetRelPath: string): Promise<NativeCopyResult>;
|
|
261
|
+
createPrivateDirectory?(path: string): void;
|
|
211
262
|
/** Internal Windows creation receipts use full FILE_ID_INFO, never Node's projection. */
|
|
212
263
|
inspectWindowsDirectory?(path: string, requirePrivate: boolean): NativeWindowsDirectoryReceipt;
|
|
213
264
|
createPrivateDirectoryWithParentIdentity?(path: string, expectedParentIdentity: string): NativeWindowsDirectoryReceipt;
|
|
214
265
|
/** Protects an already-private borrowed Node file; descriptor ownership stays with Node. */
|
|
215
266
|
protectPrivateWindowsFile?(fd: number, path: string, expectedParentIdentity: string): NativeWindowsDirectoryReceipt;
|
|
216
267
|
verifyPrivateWindowsFile?(fd: number, path: string, expectedFileIdentity: string, expectedParentIdentity: string, expectedLinks: number): void;
|
|
217
|
-
extractArchiveNative(path: string, kind: string, rootFd: number, plan: NativeArchivePlanEntry[], limits: TarMeterLimits, signal: AbortSignal): Promise<void>;
|
|
218
|
-
fstatIdentity(fd: number): NativeFileIdentity;
|
|
219
|
-
inspectArchiveNative(path: string, kind: string, limits: TarMeterLimits, signal: AbortSignal): Promise<NativeArchiveEntry[]>;
|
|
220
|
-
linkBeneath(...args: NativeTwoPathArgs): void;
|
|
268
|
+
extractArchiveNative?(path: string, kind: string, rootFd: number, plan: NativeArchivePlanEntry[], limits: TarMeterLimits, signal: AbortSignal): Promise<void>;
|
|
269
|
+
fstatIdentity?(fd: number): NativeFileIdentity;
|
|
270
|
+
inspectArchiveNative?(path: string, kind: string, limits: TarMeterLimits, signal: AbortSignal): Promise<NativeArchiveEntry[]>;
|
|
271
|
+
linkBeneath?(...args: NativeTwoPathArgs): void;
|
|
221
272
|
/** Direct-child mkdir; true is receipt provenance only, never cleanup ownership. */
|
|
222
273
|
mkdirChildBeneath?(parentFd: number, basename: string, mode: number): boolean;
|
|
223
274
|
mkdirOpenChildBeneath?(parentFd: number, basename: string, mode: number, flags: number): {
|
|
224
275
|
fd: number;
|
|
225
276
|
created: boolean;
|
|
226
277
|
};
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
278
|
+
createTempDirectoryBeneath?(parentFd: number, basename: string): {
|
|
279
|
+
fd: number;
|
|
280
|
+
searchOnly: boolean;
|
|
281
|
+
};
|
|
282
|
+
verifyRootDirectory?(pathname: string, dev: bigint, ino: bigint): null | {
|
|
283
|
+
errorCode?: string;
|
|
284
|
+
errorMessage?: string;
|
|
285
|
+
errorErrno?: number;
|
|
286
|
+
closeErrorCode?: string;
|
|
287
|
+
closeErrorMessage?: string;
|
|
288
|
+
closeErrorErrno?: number;
|
|
289
|
+
fallbackFd?: number;
|
|
290
|
+
};
|
|
291
|
+
mkdirBeneath?(rootFd: number, relPath: string, mode: number): void;
|
|
292
|
+
openBeneath?(rootFd: number, relPath: string, flags: number): NativeOpenBeneathResult;
|
|
293
|
+
openPublicationMetadata?(parentFd: number, basename: string): number;
|
|
294
|
+
readOwnerAndDacl?(path: string): NativeWindowsSecurityFacts;
|
|
230
295
|
/** Internal Windows-only inspection of the exact borrowed Node descriptor. */
|
|
231
296
|
inspectWindowsSecureFileHandle?(fd: number): NativeWindowsDescriptorSecurityFacts;
|
|
232
297
|
ownedTreeRemovalAvailable?(parentFd: number): boolean;
|
|
298
|
+
tryRemoveEmptyOwnedTree?(parentFd: number, basename: string, directoryFd: number): NativeEmptyTreeRemovalResult;
|
|
233
299
|
removeOwnedTree?(parentFd: number, basename: string, directoryFd: number): Promise<NativeOwnedTreeRemovalResult>;
|
|
234
300
|
removeOwnedTreeSync?(parentFd: number, basename: string, directoryFd: number): NativeOwnedTreeRemovalResult;
|
|
235
301
|
/** Local filesystem admission for one-way cooperative entry publication. */
|
|
236
302
|
retainWindowsEntryPublication?(sourcePath: string, name: string, sourceDev: bigint, sourceIno: bigint, targetPath: string, targetName: string, targetDev: bigint, targetIno: bigint, dev: bigint, ino: bigint, kind: "file" | "directory" | "symlink"): NativeWindowsEntryPublication;
|
|
237
303
|
entryPublicationFilesystem?(parentFd: number): string;
|
|
238
304
|
publishRetainedEntryNoReplace?(sourceParentFd: number, sourceBasename: string, sourceFd: number, targetParentFd: number, targetBasename: string): NativePublicationTransition;
|
|
239
|
-
renameNoReplace(...args: NativeTwoPathArgs): void;
|
|
305
|
+
renameNoReplace?(...args: NativeTwoPathArgs): void;
|
|
240
306
|
/** Linux auto-mode fallback through already-admitted direct-child parents. */
|
|
241
307
|
moveNoReplaceFallback?(...args: [...paths: NativeTwoPathArgs, expectedSourceDev: bigint, expectedSourceIno: bigint]): void;
|
|
242
308
|
/** Identity-fenced retained-directory rename capability. */
|
|
243
309
|
renameNoReplaceWithIdentity?(...args: [...paths: NativeTwoPathArgs, expectedSourceDev: bigint, expectedSourceIno: bigint]): void;
|
|
244
|
-
renameReplace(...args: NativeTwoPathArgs): void;
|
|
245
|
-
sha256File(fd: number, maxBytes?: number, signal?: AbortSignal): Promise<NativeFileHash>;
|
|
310
|
+
renameReplace?(...args: NativeTwoPathArgs): void;
|
|
311
|
+
sha256File?(fd: number, maxBytes?: number, signal?: AbortSignal, ownsPosition?: boolean): Promise<NativeFileHash>;
|
|
246
312
|
}
|
|
313
|
+
export type CapableNativeBinding<K extends keyof NativeBinding> = NativeBinding & Required<Pick<NativeBinding, K>>;
|
|
247
314
|
export declare function captureNativeFdClose(binding: NativeBinding): (fd: number) => void;
|
|
248
315
|
export {};
|
|
@@ -4,5 +4,10 @@ export type FsSafeNativeConfig = {
|
|
|
4
4
|
};
|
|
5
5
|
export declare function configureFsSafeNative(config: Partial<FsSafeNativeConfig>): void;
|
|
6
6
|
export declare function getFsSafeNativeConfig(): FsSafeNativeConfig;
|
|
7
|
+
/** Internal provenance; keep the public configuration shape unchanged. */
|
|
8
|
+
export declare function getNativeModeSetting(): {
|
|
9
|
+
mode: FsSafeNativeMode;
|
|
10
|
+
source: "programmatic configuration" | "FS_SAFE_NATIVE_MODE" | "OPENCLAW_FS_SAFE_NATIVE_MODE" | "default";
|
|
11
|
+
};
|
|
7
12
|
export declare function isFsSafeNativeRequired(): boolean;
|
|
8
13
|
export declare function __resetFsSafeNativeConfigForTest(): void;
|
|
@@ -26,6 +26,17 @@ export function getFsSafeNativeConfig() {
|
|
|
26
26
|
"auto",
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
|
+
/** Internal provenance; keep the public configuration shape unchanged. */
|
|
30
|
+
export function getNativeModeSetting() {
|
|
31
|
+
if (overrideConfig.mode !== undefined)
|
|
32
|
+
return { mode: overrideConfig.mode, source: "programmatic configuration" };
|
|
33
|
+
for (const source of ["FS_SAFE_NATIVE_MODE", "OPENCLAW_FS_SAFE_NATIVE_MODE"]) {
|
|
34
|
+
const mode = parseMode(process.env[source]);
|
|
35
|
+
if (mode !== undefined)
|
|
36
|
+
return { mode, source };
|
|
37
|
+
}
|
|
38
|
+
return { mode: "auto", source: "default" };
|
|
39
|
+
}
|
|
29
40
|
export function isFsSafeNativeRequired() {
|
|
30
41
|
return (overrideConfig.mode ??
|
|
31
42
|
parseMode(process.env.FS_SAFE_NATIVE_MODE) ??
|
|
@@ -2,23 +2,25 @@ import path from "node:path";
|
|
|
2
2
|
import { FsSafeError } from "./errors.js";
|
|
3
3
|
import { getNativeBinding } from "./native.js";
|
|
4
4
|
import { inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
5
|
+
import { isNativeStatIdentity, statIdentityFromNative } from "./native-stat-identity.js";
|
|
5
6
|
export function getNativeDirectoryObservationBackend(enabled = true) {
|
|
6
7
|
if (!enabled)
|
|
7
8
|
return undefined;
|
|
8
|
-
const binding = getNativeBinding();
|
|
9
|
+
const binding = getNativeBinding("observeDirectory");
|
|
9
10
|
return binding && typeof binding.observeDirectory === "function"
|
|
10
11
|
? binding
|
|
11
12
|
: undefined;
|
|
12
13
|
}
|
|
13
14
|
function validateNativeDirectoryObservation(observation) {
|
|
14
|
-
if (!observation ||
|
|
15
|
-
|
|
15
|
+
if (!observation || !isNativeStatIdentity(observation.dev) ||
|
|
16
|
+
!isNativeStatIdentity(observation.ino) ||
|
|
16
17
|
typeof observation.realPath !== "string" ||
|
|
17
18
|
observation.realPath.includes("\0") ||
|
|
18
19
|
(!path.isAbsolute(observation.realPath) && !path.win32.isAbsolute(observation.realPath))) {
|
|
19
20
|
throw new FsSafeError("path-mismatch", "native directory observation is invalid");
|
|
20
21
|
}
|
|
21
|
-
|
|
22
|
+
const dev = statIdentityFromNative(observation.dev), ino = statIdentityFromNative(observation.ino);
|
|
23
|
+
return dev === observation.dev && ino === observation.ino ? observation : { ...observation, dev, ino };
|
|
22
24
|
}
|
|
23
25
|
/** Exact identity, type, and canonical path captured from one retained handle. */
|
|
24
26
|
export function inspectNativeDirectoryObservation(backend, pathname, expected, platform = process.platform) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type NativeBinding } from "./native.js";
|
|
1
2
|
/** Warn once per capability, without including caller paths or native error text. */
|
|
2
|
-
export declare function warnNativeFallback(feature: string, limitation: string): void;
|
|
3
|
+
export declare function warnNativeFallback(feature: string, limitation: string, ...capabilities: (keyof NativeBinding)[]): void;
|
|
3
4
|
export declare function __resetNativeFallbackWarningsForTest(): void;
|
|
@@ -1,10 +1,34 @@
|
|
|
1
|
+
import { getNativeFallbackDiagnostic } from "./native.js";
|
|
2
|
+
import { getNativeModeSetting } from "./native-config.js";
|
|
3
|
+
import { nativeGuidance } from "./native-guidance.js";
|
|
1
4
|
const warnedFeatures = new Set();
|
|
5
|
+
function fallbackReason(capabilities) {
|
|
6
|
+
const diagnostic = getNativeFallbackDiagnostic(...capabilities);
|
|
7
|
+
const expected = diagnostic.packageName === undefined
|
|
8
|
+
? `No native package is published for ${diagnostic.target}.`
|
|
9
|
+
: `Expected ${diagnostic.packageName}@${diagnostic.packageVersion}.`;
|
|
10
|
+
const guidance = nativeGuidance(diagnostic.reason, getNativeModeSetting().source);
|
|
11
|
+
switch (diagnostic.reason) {
|
|
12
|
+
case "disabled":
|
|
13
|
+
return `native support is disabled by ${diagnostic.source} (disabled). ${expected} ${guidance}`;
|
|
14
|
+
case "unsupported-target":
|
|
15
|
+
return `the target ${diagnostic.target} is unsupported (unsupported-target). ${expected} ${guidance}`;
|
|
16
|
+
case "package-missing":
|
|
17
|
+
return `the selected native package could not be resolved (package-missing). ${expected} ${guidance}`;
|
|
18
|
+
case "load-failed":
|
|
19
|
+
return `the native package could not be loaded (load-failed: ${diagnostic.code}). ${expected} ${guidance}`;
|
|
20
|
+
case "capability-missing":
|
|
21
|
+
return `the native package lacks requested functions (capability-missing: ${diagnostic.capabilities.join(", ")}). ${expected} ${guidance}`;
|
|
22
|
+
default:
|
|
23
|
+
return `the requested native operation is unavailable (${diagnostic.reason}). ${expected}`;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
2
26
|
/** Warn once per capability, without including caller paths or native error text. */
|
|
3
|
-
export function warnNativeFallback(feature, limitation) {
|
|
27
|
+
export function warnNativeFallback(feature, limitation, ...capabilities) {
|
|
4
28
|
if (warnedFeatures.has(feature))
|
|
5
29
|
return;
|
|
6
30
|
warnedFeatures.add(feature);
|
|
7
|
-
process.emitWarning(`${feature} is using its portable fallback because
|
|
31
|
+
process.emitWarning(`${feature} is using its portable fallback because ${fallbackReason(capabilities)} ${limitation}`, { code: "FS_SAFE_NATIVE_FALLBACK", type: "FsSafeWarning" });
|
|
8
32
|
}
|
|
9
33
|
export function __resetNativeFallbackWarningsForTest() {
|
|
10
34
|
warnedFeatures.clear();
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { getNativeModeSetting } from "./native-config.js";
|
|
2
|
+
/** Shared, path-free next steps for status reports and fallback warnings. */
|
|
3
|
+
export declare function nativeGuidance(reason: string, modeSource: ReturnType<typeof getNativeModeSetting>["source"]): string | undefined;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** Shared, path-free next steps for status reports and fallback warnings. */
|
|
2
|
+
export function nativeGuidance(reason, modeSource) {
|
|
3
|
+
switch (reason) {
|
|
4
|
+
case "disabled": {
|
|
5
|
+
const enable = modeSource === "programmatic configuration"
|
|
6
|
+
? 'call configureFsSafeNative({ mode: "auto" }) or configureFsSafeNative({ mode: "require" })'
|
|
7
|
+
: `set ${modeSource}=auto or ${modeSource}=require`;
|
|
8
|
+
return `If native loading is intended, ${enable}; keep intentional off settings.`;
|
|
9
|
+
}
|
|
10
|
+
case "unsupported-target":
|
|
11
|
+
return "Portable mode is expected on this target.";
|
|
12
|
+
case "package-missing":
|
|
13
|
+
return "Reinstall with optional dependencies included: npm --include=optional, or pnpm without --no-optional / optional=false. Check the lockfile and registry mirror.";
|
|
14
|
+
case "load-failed":
|
|
15
|
+
return "Check Node architecture, Linux libc compatibility, package dependencies, and OS code-integrity policy. Reinstalling identical bytes may not help.";
|
|
16
|
+
case "capability-missing":
|
|
17
|
+
return "Use matching fs-safe and platform-package versions; some targets intentionally provide only a subset of capabilities.";
|
|
18
|
+
default:
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { BigIntStats } from "node:fs";
|
|
2
2
|
import type { NativeBinding } from "./native-binding.js";
|
|
3
|
-
export declare function nativeMoveNoReplace(binding: NativeBinding, paths: Parameters<NativeBinding["renameNoReplace"]
|
|
3
|
+
export declare function nativeMoveNoReplace(binding: NativeBinding, paths: Parameters<NonNullable<NativeBinding["renameNoReplace"]>>, expected: BigIntStats | undefined, allowFallback: boolean, operation?: string): "link-unlink" | undefined;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { FsSafeError } from "./errors.js";
|
|
2
|
+
import { assertNativeCapabilities } from "./native.js";
|
|
2
3
|
import { cachedNoReplaceUnavailable, noReplaceUnavailable, rememberNoReplaceUnavailable, } from "./native-noreplace.js";
|
|
3
4
|
function fallbackError(error, operation) {
|
|
4
5
|
const code = error?.code;
|
|
@@ -24,6 +25,7 @@ function fallbackError(error, operation) {
|
|
|
24
25
|
return error;
|
|
25
26
|
}
|
|
26
27
|
export function nativeMoveNoReplace(binding, paths, expected, allowFallback, operation = "move") {
|
|
28
|
+
assertNativeCapabilities(binding, "renameNoReplace");
|
|
27
29
|
const [sourceFd, source, targetFd, target] = paths;
|
|
28
30
|
let unavailable = cachedNoReplaceUnavailable(binding, sourceFd, operation);
|
|
29
31
|
if (!unavailable) {
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import fsSync, {} from "node:fs";
|
|
2
|
-
import fs from "node:fs/promises";
|
|
3
2
|
import path from "node:path";
|
|
4
3
|
import { sameFileIdentityForCleanup } from "./file-identity.js";
|
|
5
4
|
import { getNativeBinding } from "./native.js";
|
|
@@ -32,21 +31,21 @@ export function removeNativeCreatedFileIfStillPinned(params) {
|
|
|
32
31
|
}
|
|
33
32
|
}
|
|
34
33
|
export async function createNativeExclusiveFile(targetPath, mode) {
|
|
35
|
-
const binding = getNativeBinding();
|
|
34
|
+
const binding = getNativeBinding("openBeneath");
|
|
36
35
|
if (!binding) {
|
|
37
36
|
return undefined;
|
|
38
37
|
}
|
|
39
38
|
const closeFd = captureNativeFdClose(binding);
|
|
40
39
|
const parentPath = path.dirname(targetPath);
|
|
41
40
|
const basename = path.basename(targetPath);
|
|
42
|
-
const
|
|
41
|
+
const parentFd = fsSync.openSync(parentPath, fsSync.constants.O_RDONLY |
|
|
43
42
|
(typeof fsSync.constants.O_DIRECTORY === "number" ? fsSync.constants.O_DIRECTORY : 0));
|
|
44
43
|
let fd;
|
|
45
44
|
let created;
|
|
46
45
|
try {
|
|
47
46
|
let opened;
|
|
48
47
|
try {
|
|
49
|
-
opened = binding.openBeneath(
|
|
48
|
+
opened = binding.openBeneath(parentFd, basename, nativeOpenFlags(fsSync.constants.O_WRONLY | fsSync.constants.O_CREAT | fsSync.constants.O_EXCL));
|
|
50
49
|
}
|
|
51
50
|
catch (error) {
|
|
52
51
|
// The parent open above and every post-create operation remain untagged.
|
|
@@ -94,7 +93,7 @@ export async function createNativeExclusiveFile(targetPath, mode) {
|
|
|
94
93
|
}
|
|
95
94
|
removeNativeCreatedFileIfStillPinned({
|
|
96
95
|
parentPath,
|
|
97
|
-
parentFd
|
|
96
|
+
parentFd,
|
|
98
97
|
basename,
|
|
99
98
|
created,
|
|
100
99
|
});
|
|
@@ -102,6 +101,9 @@ export async function createNativeExclusiveFile(targetPath, mode) {
|
|
|
102
101
|
throw error;
|
|
103
102
|
}
|
|
104
103
|
finally {
|
|
105
|
-
|
|
104
|
+
try {
|
|
105
|
+
fsSync.closeSync(parentFd);
|
|
106
|
+
}
|
|
107
|
+
catch { /* Preserve the creation outcome. */ }
|
|
106
108
|
}
|
|
107
109
|
}
|
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
import { type BigIntStats, type Stats } from "node:fs";
|
|
2
2
|
import type { FileHandle } from "node:fs/promises";
|
|
3
|
-
import type
|
|
4
|
-
import type
|
|
3
|
+
import { type FileIdentityStat } from "./file-identity.js";
|
|
4
|
+
import { type NativeBinding } from "./native.js";
|
|
5
5
|
import { describeStagedDirectory } from "./staged-directory.js";
|
|
6
6
|
type RootIdentity = Pick<FileIdentityStat, "dev" | "ino">;
|
|
7
|
+
type NativeRootHandle = {
|
|
8
|
+
readonly fd: number;
|
|
9
|
+
close(): Promise<void>;
|
|
10
|
+
[Symbol.asyncDispose](): Promise<void>;
|
|
11
|
+
};
|
|
7
12
|
export type NativeRootAdmission = {
|
|
8
13
|
directoryFlags?: number;
|
|
9
14
|
exactRoot: boolean;
|
|
10
15
|
reportCloseErrors?: boolean;
|
|
11
16
|
operation: string;
|
|
12
|
-
root:
|
|
17
|
+
root: NativeRootHandle;
|
|
13
18
|
rootPath: string;
|
|
14
19
|
};
|
|
15
20
|
export type NativeParentAdmission = {
|
|
@@ -23,6 +28,41 @@ export type NativeParentAdmission = {
|
|
|
23
28
|
stagedDirectory?: ReturnType<typeof describeStagedDirectory>;
|
|
24
29
|
};
|
|
25
30
|
export declare function sameNativeIdentity(left: RootIdentity, right: RootIdentity): boolean;
|
|
31
|
+
/** Short native mutations need a retained descriptor, not a Node FileHandle. */
|
|
32
|
+
export declare function openExactNativeRootSync(binding: NativeBinding, params: {
|
|
33
|
+
rootPath: string;
|
|
34
|
+
rootIdentity: {
|
|
35
|
+
dev: bigint;
|
|
36
|
+
ino: bigint;
|
|
37
|
+
};
|
|
38
|
+
}): {
|
|
39
|
+
fd: number;
|
|
40
|
+
stat: BigIntStats;
|
|
41
|
+
close(): void;
|
|
42
|
+
[Symbol.dispose](): void;
|
|
43
|
+
};
|
|
44
|
+
export declare function openExactNativeRootAdmissionSync(binding: NativeBinding, params: {
|
|
45
|
+
rootPath: string;
|
|
46
|
+
rootIdentity: {
|
|
47
|
+
dev: bigint;
|
|
48
|
+
ino: bigint;
|
|
49
|
+
};
|
|
50
|
+
operation: string;
|
|
51
|
+
reportCloseErrors?: boolean;
|
|
52
|
+
}): {
|
|
53
|
+
directoryFlags: number;
|
|
54
|
+
exactRoot: boolean;
|
|
55
|
+
reportCloseErrors: boolean | undefined;
|
|
56
|
+
operation: string;
|
|
57
|
+
root: {
|
|
58
|
+
fd: number;
|
|
59
|
+
stat: BigIntStats;
|
|
60
|
+
close(): void;
|
|
61
|
+
[Symbol.dispose](): void;
|
|
62
|
+
};
|
|
63
|
+
rootPath: string;
|
|
64
|
+
};
|
|
65
|
+
export declare function borrowNativeRootParent(admission: ReturnType<typeof openExactNativeRootAdmissionSync>): NativeParentAdmission;
|
|
26
66
|
export declare function openNativeRootAdmission(binding: NativeBinding, params: {
|
|
27
67
|
rootPath: string;
|
|
28
68
|
rootIdentity?: RootIdentity;
|
|
@@ -32,5 +72,5 @@ export declare function openNativeRootAdmission(binding: NativeBinding, params:
|
|
|
32
72
|
}): Promise<NativeRootAdmission>;
|
|
33
73
|
export declare function openNativeParentAdmission(binding: NativeBinding, rootAdmission: Omit<NativeRootAdmission, "root"> & {
|
|
34
74
|
root: Pick<FileHandle, "fd">;
|
|
35
|
-
}, relativeParentPath: string, observation?: "native-directory"): Promise<NativeParentAdmission>;
|
|
75
|
+
}, relativeParentPath: string, observation?: "native-directory" | "native-publication"): Promise<NativeParentAdmission>;
|
|
36
76
|
export {};
|