@openclaw/feishu 2026.9.8 → 2026.10.1-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.setup/{channel-DV4hM2Uf.mjs → channel-CX62T71S.mjs} +146 -245
- package/dist/.setup/{channel.runtime-CXQcH5zZ.mjs → channel.runtime-COiaq-Rv.mjs} +6 -12
- package/dist/.setup/{chat-D8MlFAZi.mjs → chat-BdyvImsY.mjs} +6 -9
- package/dist/.setup/{client-BYG-_IZl.mjs → client-BsBVWJzN.mjs} +1 -1
- package/dist/.setup/{doctor-contract-3XC0vbIw.mjs → doctor-contract-B1B9ogdd.mjs} +212 -27
- package/dist/.setup/{monitor-D_rsHBZ6.mjs → monitor-7DvwUs4Y.mjs} +5 -6
- package/dist/.setup/{monitor.account-DvvY7i6k.mjs → monitor.account-7xVu7XWY.mjs} +190 -393
- package/dist/.setup/{probe-_ViqSi0j.mjs → probe-B9o7iey6.mjs} +13 -39
- package/dist/.setup/{reply-delivery-result-COAjaukc.mjs → reply-delivery-result-Bt-BWBEA.mjs} +151 -297
- package/dist/.setup/{setup-api-C4S5I3ac.mjs → setup-api-DmzX9S01.mjs} +1 -1
- package/dist/.setup/{subagent-hooks-BaFoMu8W.mjs → subagent-hooks-CUXg0pC-.mjs} +5 -11
- package/dist/.setup/{thread-bindings-EdLXPXzw.mjs → thread-bindings-jW2x5VuN.mjs} +23 -41
- package/dist/api.js +234 -297
- package/dist/channel-plugin-api.js +1 -1
- package/dist/doctor-contract-api.js +2 -2
- package/dist/session-binding-contract-api.js +1 -1
- package/dist/setup-api.js +1 -1
- package/dist/setup-entry.js +1 -1
- package/dist/subagent-hooks-api.js +1 -1
- package/node_modules/@openclaw/fs-safe/CHANGELOG.md +95 -0
- package/node_modules/@openclaw/fs-safe/README.md +70 -379
- package/node_modules/@openclaw/fs-safe/dist/absolute-path.js +35 -51
- package/node_modules/@openclaw/fs-safe/dist/advanced.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/advanced.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-input.js +2 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-parser.wasm +0 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-staging.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/archive-staging.js +9 -10
- package/node_modules/@openclaw/fs-safe/dist/archive.js +2 -5
- package/node_modules/@openclaw/fs-safe/dist/atomic-io.d.ts +51 -0
- package/node_modules/@openclaw/fs-safe/dist/atomic-io.js +242 -0
- package/node_modules/@openclaw/fs-safe/dist/config.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/config.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/copy-file-input.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/copy-file-input.js +2 -2
- package/node_modules/@openclaw/fs-safe/dist/copy-tree-portable.js +2 -0
- package/node_modules/@openclaw/fs-safe/dist/create.js +12 -5
- package/node_modules/@openclaw/fs-safe/dist/directory-guard.d.ts +11 -0
- package/node_modules/@openclaw/fs-safe/dist/directory-guard.js +4 -7
- package/node_modules/@openclaw/fs-safe/dist/entry-publication-types.d.ts +55 -0
- package/node_modules/@openclaw/fs-safe/dist/entry-publication-types.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/entry-publication.d.ts +8 -0
- package/node_modules/@openclaw/fs-safe/dist/entry-publication.js +262 -0
- package/node_modules/@openclaw/fs-safe/dist/exclusive-create.d.ts +3 -0
- package/node_modules/@openclaw/fs-safe/dist/exclusive-create.js +22 -0
- package/node_modules/@openclaw/fs-safe/dist/file-cleanup.d.ts +1 -6
- package/node_modules/@openclaw/fs-safe/dist/file-cleanup.js +3 -6
- package/node_modules/@openclaw/fs-safe/dist/file-identity.js +3 -6
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-io.js +2 -2
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-mutation.js +9 -0
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root.js +4 -6
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-stale-admission.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync.js +8 -0
- package/node_modules/@openclaw/fs-safe/dist/file-store-boundary.js +2 -0
- package/node_modules/@openclaw/fs-safe/dist/file-store-sync-write.js +20 -18
- package/node_modules/@openclaw/fs-safe/dist/file-store.js +4 -10
- package/node_modules/@openclaw/fs-safe/dist/fs.d.ts +2 -10
- package/node_modules/@openclaw/fs-safe/dist/fs.js +2 -10
- package/node_modules/@openclaw/fs-safe/dist/guarded-mkdir.d.ts +3 -4
- package/node_modules/@openclaw/fs-safe/dist/guarded-mkdir.js +6 -18
- package/node_modules/@openclaw/fs-safe/dist/guest-dispatch-python.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/guest-native-python.js +0 -2
- package/node_modules/@openclaw/fs-safe/dist/guest.js +3 -9
- package/node_modules/@openclaw/fs-safe/dist/index.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/index.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/json-durable-queue-directory.js +1 -5
- package/node_modules/@openclaw/fs-safe/dist/json.js +19 -37
- package/node_modules/@openclaw/fs-safe/dist/move-path.js +2 -0
- package/node_modules/@openclaw/fs-safe/dist/mutation-authority.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/mutation-authority.js +9 -1
- package/node_modules/@openclaw/fs-safe/dist/native-binding.d.ts +47 -2
- package/node_modules/@openclaw/fs-safe/dist/native-config.d.ts +1 -9
- package/node_modules/@openclaw/fs-safe/dist/native-config.js +6 -40
- package/node_modules/@openclaw/fs-safe/dist/native-parent-admission.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/native-parent-admission.js +5 -2
- package/node_modules/@openclaw/fs-safe/dist/native-pinned-write.js +11 -316
- package/node_modules/@openclaw/fs-safe/dist/native-policy-parent-windows.d.ts +7 -7
- package/node_modules/@openclaw/fs-safe/dist/native-policy-parent-windows.js +28 -175
- package/node_modules/@openclaw/fs-safe/dist/native-policy-parent.d.ts +15 -0
- package/node_modules/@openclaw/fs-safe/dist/native-policy-parent.js +418 -0
- package/node_modules/@openclaw/fs-safe/dist/native-rename-outcome.js +9 -4
- package/node_modules/@openclaw/fs-safe/dist/native-staged-file.js +17 -13
- package/node_modules/@openclaw/fs-safe/dist/native-staged-symlink.js +6 -24
- package/node_modules/@openclaw/fs-safe/dist/native.js +5 -1
- package/node_modules/@openclaw/fs-safe/dist/path-case.js +10 -9
- package/node_modules/@openclaw/fs-safe/dist/path-scope-lexical.js +4 -1
- package/node_modules/@openclaw/fs-safe/dist/path-segment-route.d.ts +1 -0
- package/node_modules/@openclaw/fs-safe/dist/path-segment-route.js +3 -0
- package/node_modules/@openclaw/fs-safe/dist/path.js +4 -1
- package/node_modules/@openclaw/fs-safe/dist/permissions-windows.d.ts +0 -1
- package/node_modules/@openclaw/fs-safe/dist/pinned-mutation-admission.js +1 -3
- package/node_modules/@openclaw/fs-safe/dist/pinned-write-staged.js +2 -0
- package/node_modules/@openclaw/fs-safe/dist/pinned-write.js +9 -10
- package/node_modules/@openclaw/fs-safe/dist/private-producer-handoff.js +3 -6
- package/node_modules/@openclaw/fs-safe/dist/publish-copy-stage.js +2 -2
- package/node_modules/@openclaw/fs-safe/dist/publish-file.js +4 -3
- package/node_modules/@openclaw/fs-safe/dist/replace-file-buffer.d.ts +3 -4
- package/node_modules/@openclaw/fs-safe/dist/replace-file-buffer.js +18 -32
- package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-fallback.d.ts +5 -17
- package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-fallback.js +92 -230
- package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-source.d.ts +4 -15
- package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-source.js +22 -63
- package/node_modules/@openclaw/fs-safe/dist/replace-file-descriptor.d.ts +9 -34
- package/node_modules/@openclaw/fs-safe/dist/replace-file-descriptor.js +78 -83
- package/node_modules/@openclaw/fs-safe/dist/replace-file-destination.d.ts +18 -20
- package/node_modules/@openclaw/fs-safe/dist/replace-file-destination.js +64 -98
- package/node_modules/@openclaw/fs-safe/dist/replace-file-temp-owner.d.ts +16 -33
- package/node_modules/@openclaw/fs-safe/dist/replace-file-temp-owner.js +61 -200
- package/node_modules/@openclaw/fs-safe/dist/replace-file-types.d.ts +1 -6
- package/node_modules/@openclaw/fs-safe/dist/replace-file.js +76 -216
- package/node_modules/@openclaw/fs-safe/dist/root-boundary.js +14 -24
- package/node_modules/@openclaw/fs-safe/dist/root-context.d.ts +1 -4
- package/node_modules/@openclaw/fs-safe/dist/root-context.js +4 -19
- package/node_modules/@openclaw/fs-safe/dist/root-create-native.d.ts +52 -0
- package/node_modules/@openclaw/fs-safe/dist/root-create-native.js +350 -0
- package/node_modules/@openclaw/fs-safe/dist/root-directory-list-types.d.ts +35 -0
- package/node_modules/@openclaw/fs-safe/dist/root-directory-list-types.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/root-directory-list.d.ts +2 -27
- package/node_modules/@openclaw/fs-safe/dist/root-directory-list.js +31 -28
- package/node_modules/@openclaw/fs-safe/dist/root-directory.js +1 -4
- package/node_modules/@openclaw/fs-safe/dist/root-errors.js +34 -0
- package/node_modules/@openclaw/fs-safe/dist/root-file.js +9 -21
- package/node_modules/@openclaw/fs-safe/dist/root-impl.js +105 -37
- package/node_modules/@openclaw/fs-safe/dist/root-move-noreplace.d.ts +7 -2
- package/node_modules/@openclaw/fs-safe/dist/root-move-noreplace.js +67 -38
- package/node_modules/@openclaw/fs-safe/dist/root-observed-path.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/root-observed-path.js +0 -2
- package/node_modules/@openclaw/fs-safe/dist/root-options.d.ts +2 -3
- package/node_modules/@openclaw/fs-safe/dist/root-path-existing.js +3 -1
- package/node_modules/@openclaw/fs-safe/dist/root-path-stat.js +68 -85
- package/node_modules/@openclaw/fs-safe/dist/root-path.js +6 -1
- package/node_modules/@openclaw/fs-safe/dist/root-paths-lexical.d.ts +4 -0
- package/node_modules/@openclaw/fs-safe/dist/root-paths-lexical.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/root-paths.js +4 -9
- package/node_modules/@openclaw/fs-safe/dist/root-remove-native.d.ts +4 -0
- package/node_modules/@openclaw/fs-safe/dist/root-remove-native.js +311 -0
- package/node_modules/@openclaw/fs-safe/dist/root-remove.d.ts +11 -0
- package/node_modules/@openclaw/fs-safe/dist/root-remove.js +3 -1
- package/node_modules/@openclaw/fs-safe/dist/root-walk.d.ts +2 -2
- package/node_modules/@openclaw/fs-safe/dist/root-walk.js +1 -3
- package/node_modules/@openclaw/fs-safe/dist/root-write-admission.d.ts +3 -3
- package/node_modules/@openclaw/fs-safe/dist/root-write-admission.js +8 -10
- package/node_modules/@openclaw/fs-safe/dist/safe-path-segment.d.ts +0 -1
- package/node_modules/@openclaw/fs-safe/dist/safe-path-segment.js +0 -4
- package/node_modules/@openclaw/fs-safe/dist/secret-file.js +12 -9
- package/node_modules/@openclaw/fs-safe/dist/secure-temp-dir.d.ts +0 -2
- package/node_modules/@openclaw/fs-safe/dist/sibling-staged-file.js +2 -0
- package/node_modules/@openclaw/fs-safe/dist/sibling-temp.js +7 -4
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-acquire.js +13 -6
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.d.ts +0 -3
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.js +5 -23
- package/node_modules/@openclaw/fs-safe/dist/staged-file-settlement.d.ts +2 -1
- package/node_modules/@openclaw/fs-safe/dist/staged-file-settlement.js +11 -6
- package/node_modules/@openclaw/fs-safe/dist/temp-cleanup.js +1 -8
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-child-admission.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-child-admission.js +2 -9
- package/node_modules/@openclaw/fs-safe/dist/text-atomic.d.ts +1 -6
- package/node_modules/@openclaw/fs-safe/dist/trash.js +6 -2
- package/node_modules/@openclaw/fs-safe/dist/walk.js +100 -46
- package/node_modules/@openclaw/fs-safe/dist/watch-hints.d.ts +7 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-hints.js +235 -19
- package/node_modules/@openclaw/fs-safe/dist/watch-native.d.ts +21 -2
- package/node_modules/@openclaw/fs-safe/dist/watch-native.js +21 -3
- package/node_modules/@openclaw/fs-safe/dist/watch-rescan.d.ts +6 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-rescan.js +111 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-scan.d.ts +13 -0
- package/node_modules/@openclaw/fs-safe/dist/watch-scan.js +27 -4
- package/node_modules/@openclaw/fs-safe/dist/watch-stream.js +2 -0
- package/node_modules/@openclaw/fs-safe/dist/watch.js +131 -40
- package/node_modules/@openclaw/fs-safe/dist/windows-path-alias.d.ts +8 -0
- package/node_modules/@openclaw/fs-safe/dist/windows-path-alias.js +24 -6
- package/node_modules/@openclaw/fs-safe/dist/windows-path-syntax.d.ts +13 -0
- package/node_modules/@openclaw/fs-safe/dist/windows-path-syntax.js +51 -0
- package/node_modules/@openclaw/fs-safe/docs/advanced.md +4 -0
- package/node_modules/@openclaw/fs-safe/docs/atomic.md +4 -1
- package/node_modules/@openclaw/fs-safe/docs/config.md +6 -14
- package/node_modules/@openclaw/fs-safe/docs/contributing.md +4 -14
- package/node_modules/@openclaw/fs-safe/docs/durability.md +6 -29
- package/node_modules/@openclaw/fs-safe/docs/entry-publication.md +146 -0
- package/node_modules/@openclaw/fs-safe/docs/errors.md +16 -0
- package/node_modules/@openclaw/fs-safe/docs/file-store.md +28 -5
- package/node_modules/@openclaw/fs-safe/docs/index.md +2 -31
- package/node_modules/@openclaw/fs-safe/docs/install.md +10 -31
- package/node_modules/@openclaw/fs-safe/docs/json.md +1 -1
- package/node_modules/@openclaw/fs-safe/docs/local-roots.md +0 -1
- package/node_modules/@openclaw/fs-safe/docs/migrating-to-0.5.md +12 -12
- package/node_modules/@openclaw/fs-safe/docs/native-helper.md +35 -96
- package/node_modules/@openclaw/fs-safe/docs/native.md +22 -20
- package/node_modules/@openclaw/fs-safe/docs/path.md +1 -1
- package/node_modules/@openclaw/fs-safe/docs/permissions.md +1 -2
- package/node_modules/@openclaw/fs-safe/docs/public-api.md +1 -2
- package/node_modules/@openclaw/fs-safe/docs/reading.md +1 -2
- package/node_modules/@openclaw/fs-safe/docs/root.md +26 -184
- package/node_modules/@openclaw/fs-safe/docs/secret-file.md +1 -1
- package/node_modules/@openclaw/fs-safe/docs/security-model.md +115 -3
- package/node_modules/@openclaw/fs-safe/docs/store.md +2 -2
- package/node_modules/@openclaw/fs-safe/docs/temp.md +29 -65
- package/node_modules/@openclaw/fs-safe/docs/testing.md +90 -58
- package/node_modules/@openclaw/fs-safe/docs/types.md +3 -16
- package/node_modules/@openclaw/fs-safe/docs/walk.md +33 -5
- package/node_modules/@openclaw/fs-safe/docs/watch.md +96 -14
- package/node_modules/@openclaw/fs-safe/docs/writing.md +172 -16
- package/node_modules/@openclaw/fs-safe/package.json +10 -10
- package/node_modules/@openclaw/fs-safe-darwin-arm64/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-darwin-arm64/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-darwin-x64/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-darwin-x64/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-linux-arm64-gnu/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-linux-arm64-gnu/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-linux-arm64-musl/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-linux-arm64-musl/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-linux-x64-gnu/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-linux-x64-gnu/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-linux-x64-musl/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-linux-x64-musl/package.json +1 -1
- package/node_modules/@openclaw/fs-safe-win32-x64-msvc/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-win32-x64-msvc/package.json +1 -1
- package/package.json +5 -5
- package/skills/feishu-wiki/SKILL.md +1 -1
- package/dist/.setup/accounts-wRqItHug.mjs +0 -206
- package/node_modules/@openclaw/fs-safe/dist/watch-alias.d.ts +0 -6
- package/node_modules/@openclaw/fs-safe/dist/watch-alias.js +0 -88
- package/node_modules/@openclaw/fs-safe/docs/mutation-policy-proof.md +0 -69
- package/node_modules/@openclaw/fs-safe/docs/private-file-store.md +0 -68
- package/node_modules/@openclaw/fs-safe/docs/test-hooks.md +0 -110
|
@@ -69,16 +69,20 @@ export function matchRootFileOpenFailure(failure, handlers) {
|
|
|
69
69
|
}
|
|
70
70
|
return handlers.fallback(failure);
|
|
71
71
|
}
|
|
72
|
-
function
|
|
72
|
+
function finalizeRootFileOpen(params) {
|
|
73
|
+
if ("ok" in params.resolved) {
|
|
74
|
+
return params.resolved;
|
|
75
|
+
}
|
|
76
|
+
const resolved = params.resolved;
|
|
73
77
|
const rejectHardlinks = params.rejectHardlinks ?? true;
|
|
74
78
|
const opened = openPinnedFileSync({
|
|
75
|
-
filePath:
|
|
76
|
-
resolvedPath:
|
|
79
|
+
filePath: resolved.absolutePath,
|
|
80
|
+
resolvedPath: resolved.resolvedPath,
|
|
77
81
|
rejectHardlinks,
|
|
78
82
|
maxBytes: params.maxBytes,
|
|
79
83
|
allowedType: params.allowedType,
|
|
80
84
|
ioFs: params.ioFs,
|
|
81
|
-
finalAdmission: createRootFileFinalAdmission(params.ioFs,
|
|
85
|
+
finalAdmission: createRootFileFinalAdmission(params.ioFs, resolved.rootObservation, resolved.boundaryLabel, rejectHardlinks),
|
|
82
86
|
});
|
|
83
87
|
if (!opened.ok) {
|
|
84
88
|
return opened;
|
|
@@ -88,25 +92,9 @@ function openRootFileResolved(params) {
|
|
|
88
92
|
path: opened.path,
|
|
89
93
|
fd: opened.fd,
|
|
90
94
|
stat: opened.stat,
|
|
91
|
-
rootRealPath:
|
|
95
|
+
rootRealPath: resolved.rootRealPath,
|
|
92
96
|
};
|
|
93
97
|
}
|
|
94
|
-
function finalizeRootFileOpen(params) {
|
|
95
|
-
if ("ok" in params.resolved) {
|
|
96
|
-
return params.resolved;
|
|
97
|
-
}
|
|
98
|
-
return openRootFileResolved({
|
|
99
|
-
absolutePath: params.resolved.absolutePath,
|
|
100
|
-
resolvedPath: params.resolved.resolvedPath,
|
|
101
|
-
rootRealPath: params.resolved.rootRealPath,
|
|
102
|
-
boundaryLabel: params.resolved.boundaryLabel,
|
|
103
|
-
rootObservation: params.resolved.rootObservation,
|
|
104
|
-
maxBytes: params.maxBytes,
|
|
105
|
-
rejectHardlinks: params.rejectHardlinks,
|
|
106
|
-
allowedType: params.allowedType,
|
|
107
|
-
ioFs: params.ioFs,
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
98
|
export async function openRootFile(params) {
|
|
111
99
|
const ioFs = params.ioFs ?? fs;
|
|
112
100
|
const rawAbsolutePath = params.absolutePath;
|
|
@@ -50,7 +50,6 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
|
|
|
50
50
|
var e = new Error(message);
|
|
51
51
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
52
52
|
});
|
|
53
|
-
import { randomUUID } from "node:crypto";
|
|
54
53
|
import { registerRootHandleContext } from "./root-handle-context.js";
|
|
55
54
|
import fsSync, { constants as fsConstants } from "node:fs";
|
|
56
55
|
import fs from "node:fs/promises";
|
|
@@ -69,6 +68,7 @@ import { openedPathResolutionError, recordExclusiveCreateFailure, recordOpenedFi
|
|
|
69
68
|
import { runPinnedWriteHelper, runPinnedWriteWithRenamePolicy } from "./pinned-write.js";
|
|
70
69
|
import { preparePinnedWriteMutationAdmission, snapshotPinnedMutationPolicy } from "./pinned-mutation-admission.js";
|
|
71
70
|
import { getNativeBinding } from "./native.js";
|
|
71
|
+
import { isFsSafeNativeRequired } from "./native-config.js";
|
|
72
72
|
import { validatePinnedRelativePath } from "./pinned-operation.js";
|
|
73
73
|
import { PATH_ALIAS_POLICIES } from "./path-policy.js";
|
|
74
74
|
import { assertNoNulPathInput, hasNodeErrorCode, isNotFoundPathError, isSymlinkOpenError, } from "./path.js";
|
|
@@ -78,6 +78,7 @@ import { sameFileIdentity } from "./file-identity.js";
|
|
|
78
78
|
import { removePathIfIdentityUnchanged } from "./replace-file-temp-owner.js";
|
|
79
79
|
import { realpathSync } from "./realpath.js";
|
|
80
80
|
import { mkdirPathFallback, prepareRootWriteTarget, tryMkdirAtExactParent } from "./root-directory-creation.js";
|
|
81
|
+
import { buildAtomicWriteTempPath, rootWriteQueueKey, tryMkdirRootNative, tryOpenCreateRootNative } from "./root-create-native.js";
|
|
81
82
|
import { isNonRegularWriteOpenError, resolveNonblockingWriteFlag } from "./write-open-flags.js";
|
|
82
83
|
import { resolveRootPath, resolveRootPathSync, resolveRootPathForRemoval } from "./root-path.js";
|
|
83
84
|
import { RemovalPathReceipts } from "./root-remove-receipt.js";
|
|
@@ -93,6 +94,7 @@ import { stringifyJsonDocument } from "./json-stringify.js";
|
|
|
93
94
|
import { walkRoot } from "./root-walk.js";
|
|
94
95
|
import { registerTempPathForExit } from "./temp-cleanup.js";
|
|
95
96
|
import { removePathInRootFallback, validateRemoveOptions } from "./root-remove.js";
|
|
97
|
+
import { removePathInRootNative } from "./root-remove-native.js";
|
|
96
98
|
import { serializePathWrite } from "./write-queue.js";
|
|
97
99
|
import { verifyAtomicWriteResult } from "./root-write-verification.js";
|
|
98
100
|
import { assertRootWritePathSelectionSync, assertRootWriteSelectionSync, createRootWriteSelectionForFd, prepareGuardedRootWritePathSelection, resolveGuardedWritePathInRoot, resolveGuardedWriteTargetInRoot, resolvePinnedWriteTargetInRoot, refreshRetainedRootWriteAdmission, refreshRootWritePathSelection, } from "./root-write-admission.js";
|
|
@@ -101,12 +103,13 @@ import { finishRootFallbackWrite } from "./root-write-publication.js";
|
|
|
101
103
|
import { withRootFallbackCompatibilityLock } from "./root-write-compatibility.js";
|
|
102
104
|
import { assertRootFallbackWritePath } from "./root-write-lock-binding.js";
|
|
103
105
|
import { inspectFileIdentity, inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
104
|
-
import { admitMoveSourceStat,
|
|
106
|
+
import { admitMoveSourceStat, movePathNative } from "./root-move-noreplace.js";
|
|
105
107
|
import { admitRootReadHandle, inspectOpenedPathIdentitySync } from "./root-read-admission.js";
|
|
106
108
|
import { createCopyPublicationObserver, onCopyPublication, onCopySourceAdmission } from "./copy-publication.js";
|
|
107
109
|
import { writeAllToFile } from "./write-file-handle.js";
|
|
108
110
|
import { createInputOptions, rethrowCreateInputError, rootWriteInput } from "./root-create-input.js";
|
|
109
111
|
import { assertFinalSymlinkRejected, mutationSymlinkResolution, readSymlinkResolution } from "./root-symlink-policy.js";
|
|
112
|
+
import { assertExclusiveCreateLeaf } from "./exclusive-create.js";
|
|
110
113
|
import { assertNoWindowsPathAlias, resolvePathPreservingWindowsRoot } from "./windows-path-alias.js";
|
|
111
114
|
import { resolvePinnedObservedPathInRoot } from "./root-observed-path.js";
|
|
112
115
|
import { registerFileLockSyncRootAdapter } from "./file-lock-sync-root.js";
|
|
@@ -386,12 +389,14 @@ export class RootHandle {
|
|
|
386
389
|
const { assertBeforeMutation } = mutationOptions;
|
|
387
390
|
const { denyMutations, mutationSymlinks } = snapshotPinnedMutationPolicy(mutationOptions.denyMutations, mutationOptions.mutationSymlinks) ?? {};
|
|
388
391
|
const overwrite = options.overwrite ?? false;
|
|
392
|
+
const requireNative = isFsSafeNativeRequired();
|
|
389
393
|
await assertMoveMutationAllowed(this.context, {
|
|
390
394
|
fromRelative,
|
|
391
395
|
toRelative,
|
|
392
396
|
denyMutations,
|
|
393
397
|
});
|
|
394
398
|
await movePathFallback(this.context, {
|
|
399
|
+
requireNative,
|
|
395
400
|
fromRelative,
|
|
396
401
|
denyMutations,
|
|
397
402
|
assertBeforeMutation,
|
|
@@ -492,13 +497,11 @@ export async function openLocalFileSafely(params) {
|
|
|
492
497
|
function emitWriteBoundaryWarning(reason) {
|
|
493
498
|
logWarn(`security: fs-safe write boundary warning (${reason})`);
|
|
494
499
|
}
|
|
495
|
-
function buildAtomicWriteTempPath(targetPath) {
|
|
496
|
-
return path.join(path.dirname(targetPath), `.fs-safe-${randomUUID()}.tmp`);
|
|
497
|
-
}
|
|
498
|
-
function rootWriteQueueKey(root, relativePath) {
|
|
499
|
-
return `${root.rootReal}\0${relativePath}`;
|
|
500
|
-
}
|
|
501
500
|
async function openWritableFileInRoot(root, params, options) {
|
|
501
|
+
const requireNative = isFsSafeNativeRequired();
|
|
502
|
+
const policy = requireNative ? snapshotPinnedMutationPolicy(params.denyMutations, params.mutationSymlinks) : undefined;
|
|
503
|
+
if (policy)
|
|
504
|
+
params = { ...params, ...policy };
|
|
502
505
|
const guardedTarget = params.denyMutations === undefined && params.mutationSymlinks === undefined
|
|
503
506
|
? undefined
|
|
504
507
|
: await resolveGuardedWriteTargetInRoot(root, {
|
|
@@ -509,12 +512,20 @@ async function openWritableFileInRoot(root, params, options) {
|
|
|
509
512
|
const { resolved } = guardedTarget?.resolvedPath ?? await resolveGuardedWritePathInRoot(root, {
|
|
510
513
|
relativePath: params.relativePath,
|
|
511
514
|
});
|
|
512
|
-
const
|
|
515
|
+
const resolveCurrent = async () => (await resolveGuardedWritePathInRoot(root, {
|
|
516
|
+
relativePath: params.relativePath, ...policy,
|
|
517
|
+
})).resolved;
|
|
518
|
+
const nativeParent = requireNative && params.mkdir !== false && await tryMkdirRootNative({
|
|
519
|
+
originalPath: params.relativePath,
|
|
520
|
+
root, directory: path.dirname(resolved), target: resolved, mkdir: true, policy,
|
|
521
|
+
resolveCurrent, assertBeforeMutation: params.assertBeforeMutation,
|
|
522
|
+
});
|
|
523
|
+
const prepared = !nativeParent && guardedTarget ? await prepareSharedRootWriteTarget(root, {
|
|
513
524
|
relativePath: params.relativePath, guardedTarget, mkdir: params.mkdir,
|
|
514
525
|
assertBeforeMutation: params.assertBeforeMutation,
|
|
515
526
|
}) : undefined;
|
|
516
527
|
const preparedParent = prepared?.preparedParent;
|
|
517
|
-
let ioPath = prepared?.targetPath ?? (params.mkdir === false ? resolved :
|
|
528
|
+
let ioPath = nativeParent ? resolved : prepared?.targetPath ?? (params.mkdir === false ? resolved :
|
|
518
529
|
await prepareRootWriteTarget(root, resolved, params.assertBeforeMutation));
|
|
519
530
|
const operationTargetPath = ioPath;
|
|
520
531
|
try {
|
|
@@ -542,6 +553,8 @@ async function openWritableFileInRoot(root, params, options) {
|
|
|
542
553
|
const mode = params.mode ?? 0o600;
|
|
543
554
|
let handle;
|
|
544
555
|
let createdForWrite = false;
|
|
556
|
+
let cleanupCreated;
|
|
557
|
+
let releaseCreationParent;
|
|
545
558
|
let writePathSelection = undefined;
|
|
546
559
|
const existingFlags = params.append ? OPEN_APPEND_EXISTING_FLAGS : OPEN_WRITE_EXISTING_FLAGS;
|
|
547
560
|
const createFlags = params.append ? OPEN_APPEND_CREATE_FLAGS : OPEN_WRITE_CREATE_FLAGS;
|
|
@@ -574,10 +587,29 @@ async function openWritableFileInRoot(root, params, options) {
|
|
|
574
587
|
}
|
|
575
588
|
if (writePathSelection)
|
|
576
589
|
await refreshRootWritePathSelection(writePathSelection);
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
590
|
+
const nativeCreated = requireNative ? await tryOpenCreateRootNative({
|
|
591
|
+
originalPath: params.relativePath,
|
|
592
|
+
root, directory: path.dirname(ioPath), target: ioPath, mkdir: false, policy,
|
|
593
|
+
resolveCurrent, assertBeforeMutation() {
|
|
594
|
+
params.assertBeforeMutation?.();
|
|
595
|
+
if (writePathSelection)
|
|
596
|
+
assertRootWritePathSelectionSync(root, writePathSelection);
|
|
597
|
+
},
|
|
598
|
+
flags: createFlags, existingFlags, mode,
|
|
599
|
+
}) : undefined;
|
|
600
|
+
if (nativeCreated)
|
|
601
|
+
handle = nativeCreated.handle;
|
|
602
|
+
else {
|
|
603
|
+
params.assertBeforeMutation?.();
|
|
604
|
+
if (writePathSelection)
|
|
605
|
+
assertRootWritePathSelectionSync(root, writePathSelection);
|
|
606
|
+
// Windows can follow a dangling symlink even with O_EXCL.
|
|
607
|
+
if (!SUPPORTS_NOFOLLOW)
|
|
608
|
+
assertFinalSymlinkRejected(ioPath, true);
|
|
609
|
+
handle = await fs.open(ioPath, createFlags, mode);
|
|
610
|
+
}
|
|
611
|
+
cleanupCreated = nativeCreated?.cleanupCreated;
|
|
612
|
+
releaseCreationParent = nativeCreated?.releaseCreationParent;
|
|
581
613
|
createdForWrite = true;
|
|
582
614
|
}
|
|
583
615
|
}
|
|
@@ -670,16 +702,16 @@ async function openWritableFileInRoot(root, params, options) {
|
|
|
670
702
|
// irreversible side effects if a symlink target changes before validation.
|
|
671
703
|
if (params.append !== true && params.truncateExisting !== false && !createdForWrite) {
|
|
672
704
|
if (writeSelection)
|
|
673
|
-
await refreshRetainedRootWriteAdmission(root, writeSelection,
|
|
705
|
+
await refreshRetainedRootWriteAdmission(root, writeSelection, handle.fd);
|
|
674
706
|
assertFinalSymlinkRejected(ioPath, params.mutationSymlinks !== undefined);
|
|
675
707
|
params.assertBeforeMutation?.();
|
|
676
708
|
if (writeSelection)
|
|
677
|
-
assertRootWriteSelectionSync(root, writeSelection,
|
|
709
|
+
assertRootWriteSelectionSync(root, writeSelection, handle.fd);
|
|
678
710
|
await handle.truncate(0);
|
|
679
711
|
}
|
|
680
712
|
if (writeSelection) {
|
|
681
|
-
await refreshRetainedRootWriteAdmission(root, writeSelection,
|
|
682
|
-
assertRootWriteSelectionSync(root, writeSelection,
|
|
713
|
+
await refreshRetainedRootWriteAdmission(root, writeSelection, handle.fd);
|
|
714
|
+
assertRootWriteSelectionSync(root, writeSelection, handle.fd);
|
|
683
715
|
}
|
|
684
716
|
const result = {
|
|
685
717
|
handle,
|
|
@@ -689,13 +721,21 @@ async function openWritableFileInRoot(root, params, options) {
|
|
|
689
721
|
stat,
|
|
690
722
|
[Symbol.asyncDispose]: () => handle.close().catch(() => undefined),
|
|
691
723
|
};
|
|
692
|
-
|
|
724
|
+
if (!params.keepCreationParent)
|
|
725
|
+
releaseCreationParent?.();
|
|
726
|
+
return { opened: result, identity, writeSelection, cleanupCreated, releaseCreationParent };
|
|
693
727
|
}
|
|
694
728
|
catch (err) {
|
|
695
729
|
const cleanupCreatedPath = createdForWrite && err instanceof FsSafeError;
|
|
696
730
|
const cleanupPath = realPathForCleanup ?? ioPath;
|
|
731
|
+
if (cleanupCreatedPath && cleanupCreated)
|
|
732
|
+
await cleanupCreated().catch(() => { });
|
|
733
|
+
try {
|
|
734
|
+
releaseCreationParent?.();
|
|
735
|
+
}
|
|
736
|
+
catch { /* Preserve the admission failure. */ }
|
|
697
737
|
await handle.close().catch(() => { });
|
|
698
|
-
if (cleanupCreatedPath && createdIdentity) {
|
|
738
|
+
if (cleanupCreatedPath && createdIdentity && !cleanupCreated) {
|
|
699
739
|
await removePathIfIdentityUnchanged(cleanupPath, createdIdentity).catch(() => { });
|
|
700
740
|
}
|
|
701
741
|
throw err;
|
|
@@ -704,7 +744,7 @@ async function openWritableFileInRoot(root, params, options) {
|
|
|
704
744
|
async function appendFileInRoot(root, params) {
|
|
705
745
|
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
706
746
|
try {
|
|
707
|
-
const { opened: target, identity } = await openWritableFileInRoot(root, {
|
|
747
|
+
const { opened: target, identity, cleanupCreated, releaseCreationParent } = await openWritableFileInRoot(root, {
|
|
708
748
|
relativePath: params.relativePath,
|
|
709
749
|
mkdir: params.mkdir,
|
|
710
750
|
mode: params.mode,
|
|
@@ -713,17 +753,23 @@ async function appendFileInRoot(root, params) {
|
|
|
713
753
|
mutationSymlinks: params.mutationSymlinks,
|
|
714
754
|
truncateExisting: false,
|
|
715
755
|
append: true,
|
|
756
|
+
keepCreationParent: true,
|
|
716
757
|
});
|
|
758
|
+
const creationParent = __addDisposableResource(env_1, { [Symbol.dispose]() { releaseCreationParent?.(); } }, false);
|
|
717
759
|
let dispatched = false;
|
|
718
760
|
// Reverse disposal order closes the handle before path cleanup and retains both failures.
|
|
719
761
|
const cleanup = __addDisposableResource(env_1, {
|
|
720
762
|
async [Symbol.asyncDispose]() {
|
|
721
|
-
if (!dispatched && target.createdForWrite) {
|
|
763
|
+
if (!dispatched && target.createdForWrite && !cleanupCreated) {
|
|
722
764
|
await removePathIfIdentityUnchanged(target.realPath, identity);
|
|
723
765
|
}
|
|
724
766
|
},
|
|
725
767
|
}, true);
|
|
726
768
|
const handle = __addDisposableResource(env_1, target.handle, true);
|
|
769
|
+
const nativeCleanup = __addDisposableResource(env_1, { async [Symbol.asyncDispose]() {
|
|
770
|
+
if (!dispatched && target.createdForWrite)
|
|
771
|
+
await cleanupCreated?.();
|
|
772
|
+
} }, true);
|
|
727
773
|
try {
|
|
728
774
|
let prefix = "";
|
|
729
775
|
if (params.prependNewlineIfNeeded === true &&
|
|
@@ -773,6 +819,9 @@ async function appendFileInRoot(root, params) {
|
|
|
773
819
|
}
|
|
774
820
|
async function removePathInRoot(root, params) {
|
|
775
821
|
validatePinnedRelativePath(params.relativePath);
|
|
822
|
+
const requireNative = isFsSafeNativeRequired();
|
|
823
|
+
if (requireNative)
|
|
824
|
+
params = { ...params, ...snapshotPinnedMutationPolicy(params.denyMutations, params.mutationSymlinks) };
|
|
776
825
|
const removalReceipts = params.recursive ? undefined : new RemovalPathReceipts();
|
|
777
826
|
const resolved = await resolvePinnedPathInRoot(root, {
|
|
778
827
|
relativePath: params.relativePath,
|
|
@@ -782,7 +831,10 @@ async function removePathInRoot(root, params) {
|
|
|
782
831
|
removalReceipts,
|
|
783
832
|
});
|
|
784
833
|
try {
|
|
785
|
-
|
|
834
|
+
if (requireNative)
|
|
835
|
+
await removePathInRootNative(root, resolved.resolved, params, removalReceipts);
|
|
836
|
+
else
|
|
837
|
+
await removePathInRootFallback(root, resolved.resolved, params, removalReceipts);
|
|
786
838
|
}
|
|
787
839
|
catch (error) {
|
|
788
840
|
if (params.recursive)
|
|
@@ -791,6 +843,7 @@ async function removePathInRoot(root, params) {
|
|
|
791
843
|
}
|
|
792
844
|
}
|
|
793
845
|
async function mkdirPathInRoot(root, params) {
|
|
846
|
+
const requireNative = isFsSafeNativeRequired();
|
|
794
847
|
const privateMode = resolveCreationPermissions(params, true).private;
|
|
795
848
|
validatePinnedRelativePath(params.relativePath);
|
|
796
849
|
const policy = params.denyMutations === undefined && params.mutationSymlinks === undefined
|
|
@@ -801,6 +854,13 @@ async function mkdirPathInRoot(root, params) {
|
|
|
801
854
|
? async () => await resolvePinnedPathInRoot(root, resolution)
|
|
802
855
|
: undefined;
|
|
803
856
|
const resolved = await resolvePinnedPathInRoot(root, resolution);
|
|
857
|
+
if (requireNative && await tryMkdirRootNative({
|
|
858
|
+
originalPath: params.relativePath,
|
|
859
|
+
root, directory: resolved.resolved, target: resolved.resolved, mkdir: true, private: privateMode,
|
|
860
|
+
policy, resolveCurrent: async () => (await resolvePinnedPathInRoot(root, resolution)).resolved,
|
|
861
|
+
assertBeforeMutation: params.assertBeforeMutation,
|
|
862
|
+
}))
|
|
863
|
+
return;
|
|
804
864
|
const prepared = policy && resolved.relativePosix !== ""
|
|
805
865
|
? await preparePinnedWriteMutationAdmission({
|
|
806
866
|
rootReal: resolved.rootReal,
|
|
@@ -1057,17 +1117,13 @@ async function resolvePinnedRootPathInRoot(root, params) {
|
|
|
1057
1117
|
}
|
|
1058
1118
|
async function statPathFallback(root, relativePath) {
|
|
1059
1119
|
const initialObservationHook = getFsSafeTestHooks()?.beforeRootStatInitialObservation;
|
|
1060
|
-
const
|
|
1061
|
-
?
|
|
1120
|
+
const resolved = initialObservationHook
|
|
1121
|
+
? await resolvePinnedPathInRoot(root, { relativePath, allowRoot: true })
|
|
1062
1122
|
: await resolvePinnedObservedPathInRoot(root, relativePath, "stat");
|
|
1063
|
-
const resolved = observed ??
|
|
1064
|
-
await resolvePinnedPathInRoot(root, { relativePath, allowRoot: true });
|
|
1065
1123
|
return await statResolvedPathInRoot(root, resolved.resolved, resolved.receipt);
|
|
1066
1124
|
}
|
|
1067
1125
|
async function listPathFallback(root, relativePath, withFileTypes) {
|
|
1068
|
-
const
|
|
1069
|
-
const resolved = observed ??
|
|
1070
|
-
await resolvePinnedPathInRoot(root, { relativePath, allowRoot: true });
|
|
1126
|
+
const resolved = await resolvePinnedObservedPathInRoot(root, relativePath, "directory");
|
|
1071
1127
|
return await listDirectoryPath(root, resolved.resolved, withFileTypes, resolved.receipt);
|
|
1072
1128
|
}
|
|
1073
1129
|
async function movePathFallback(root, params) {
|
|
@@ -1126,13 +1182,23 @@ async function movePathFallback(root, params) {
|
|
|
1126
1182
|
if (!pinnedTarget) {
|
|
1127
1183
|
throw new FsSafeError("path-mismatch", "destination admission was not completed");
|
|
1128
1184
|
}
|
|
1129
|
-
|
|
1130
|
-
|
|
1185
|
+
const nativeReplace = params.overwrite && params.requireNative
|
|
1186
|
+
? getNativeBinding()?.renameReplaceWithIdentity : undefined;
|
|
1187
|
+
if (params.overwrite && !nativeReplace && params.requireNative) {
|
|
1188
|
+
throw new FsSafeError("helper-unavailable", "native overwrite move is unavailable");
|
|
1189
|
+
}
|
|
1190
|
+
if (!params.overwrite || nativeReplace) {
|
|
1191
|
+
await movePathNative(root, params, {
|
|
1131
1192
|
sourcePath: source.resolved,
|
|
1132
1193
|
sourceParentPath: path.dirname(pinnedSource.canonicalPath),
|
|
1133
1194
|
targetPath: target.resolved,
|
|
1134
1195
|
targetParentPath: path.dirname(pinnedTarget.canonicalPath),
|
|
1135
|
-
|
|
1196
|
+
sourceOriginalPath: originalRoutes?.[0],
|
|
1197
|
+
targetOriginalPath: originalRoutes?.[1],
|
|
1198
|
+
sourceCanonicalPath: pinnedSource.canonicalPath,
|
|
1199
|
+
targetCanonicalPath: pinnedTarget.canonicalPath,
|
|
1200
|
+
expectedSourceIdentity: sourceIdentity,
|
|
1201
|
+
}, params.overwrite);
|
|
1136
1202
|
return;
|
|
1137
1203
|
}
|
|
1138
1204
|
const guardOptions = { bigint: sourceIdentity !== undefined };
|
|
@@ -1244,20 +1310,21 @@ async function writeFileFallbackUnlocked(root, params, expectedWritePath) {
|
|
|
1244
1310
|
try {
|
|
1245
1311
|
tempPath = buildAtomicWriteTempPath(destinationPath);
|
|
1246
1312
|
if (retainedSelection) {
|
|
1247
|
-
await refreshRetainedRootWriteAdmission(root, retainedSelection,
|
|
1313
|
+
await refreshRetainedRootWriteAdmission(root, retainedSelection, target?.handle.fd);
|
|
1248
1314
|
}
|
|
1249
1315
|
else if (missingSelection) {
|
|
1250
1316
|
await refreshRootWritePathSelection(missingSelection);
|
|
1251
1317
|
}
|
|
1252
1318
|
params.assertBeforeMutation?.();
|
|
1253
1319
|
if (retainedSelection) {
|
|
1254
|
-
assertRootWriteSelectionSync(root, retainedSelection,
|
|
1320
|
+
assertRootWriteSelectionSync(root, retainedSelection, target?.handle.fd);
|
|
1255
1321
|
}
|
|
1256
1322
|
else if (missingSelection) {
|
|
1257
1323
|
assertRootWritePathSelectionSync(root, missingSelection);
|
|
1258
1324
|
}
|
|
1259
1325
|
assertRootIdentityCurrentSync(root);
|
|
1260
1326
|
assertSyncDirectoryGuard(destinationGuard);
|
|
1327
|
+
assertExclusiveCreateLeaf(tempPath);
|
|
1261
1328
|
writtenHandle = await fs.open(tempPath, OPEN_WRITE_CREATE_FLAGS, 0o600);
|
|
1262
1329
|
writtenIdentity = fsSync.fstatSync(writtenHandle.fd, { bigint: true });
|
|
1263
1330
|
// Preserve the creation mask when no existing destination supplies a mode.
|
|
@@ -1291,7 +1358,7 @@ async function writeFileFallbackUnlocked(root, params, expectedWritePath) {
|
|
|
1291
1358
|
// Windows cannot replace a destination while its old handle remains open.
|
|
1292
1359
|
await target?.handle.close();
|
|
1293
1360
|
if (retainedSelection) {
|
|
1294
|
-
await refreshRetainedRootWriteAdmission(root, retainedSelection
|
|
1361
|
+
await refreshRetainedRootWriteAdmission(root, retainedSelection);
|
|
1295
1362
|
}
|
|
1296
1363
|
else if (missingSelection) {
|
|
1297
1364
|
await refreshRootWritePathSelection(missingSelection);
|
|
@@ -1299,7 +1366,7 @@ async function writeFileFallbackUnlocked(root, params, expectedWritePath) {
|
|
|
1299
1366
|
assertFinalSymlinkRejected(destinationPath, params.mutationSymlinks !== undefined);
|
|
1300
1367
|
params.assertBeforeMutation?.();
|
|
1301
1368
|
if (retainedSelection) {
|
|
1302
|
-
assertRootWriteSelectionSync(root, retainedSelection
|
|
1369
|
+
assertRootWriteSelectionSync(root, retainedSelection);
|
|
1303
1370
|
}
|
|
1304
1371
|
else if (missingSelection) {
|
|
1305
1372
|
assertRootWritePathSelectionSync(root, missingSelection);
|
|
@@ -1388,6 +1455,7 @@ async function writeMissingFileFallback(root, params, expectedWritePath) {
|
|
|
1388
1455
|
params.assertBeforeMutation?.();
|
|
1389
1456
|
if (mutationAdmission)
|
|
1390
1457
|
assertSyncDirectoryGuard(parentGuard);
|
|
1458
|
+
assertExclusiveCreateLeaf(targetPath);
|
|
1391
1459
|
const handle = await fs.open(targetPath, OPEN_WRITE_CREATE_FLAGS, params.mode ?? 0o600).catch((error) => recordExclusiveCreateFailure(error, targetPath));
|
|
1392
1460
|
writtenHandle = handle;
|
|
1393
1461
|
created = true;
|
|
@@ -2,9 +2,14 @@ import { type BigIntStats, type Stats } from "node:fs";
|
|
|
2
2
|
import type { RootMoveOptions } from "./root-options.js";
|
|
3
3
|
import { type RootContext } from "./root-context.js";
|
|
4
4
|
export declare function admitMoveSourceStat<T extends Stats | BigIntStats>(stat: T, overwrite?: boolean): T;
|
|
5
|
-
export declare function
|
|
5
|
+
export declare function movePathNative(root: RootContext, params: Pick<RootMoveOptions, "assertBeforeMutation" | "denyMutations" | "mutationSymlinks">, paths: {
|
|
6
6
|
sourcePath: string;
|
|
7
7
|
sourceParentPath: string;
|
|
8
8
|
targetPath: string;
|
|
9
9
|
targetParentPath: string;
|
|
10
|
-
|
|
10
|
+
sourceOriginalPath?: string;
|
|
11
|
+
targetOriginalPath?: string;
|
|
12
|
+
sourceCanonicalPath?: string;
|
|
13
|
+
targetCanonicalPath?: string;
|
|
14
|
+
expectedSourceIdentity?: BigIntStats;
|
|
15
|
+
}, overwrite?: boolean): Promise<void>;
|
|
@@ -10,7 +10,8 @@ import { assertRootIdentityCurrent, assertRootIdentityCurrentSync } from "./root
|
|
|
10
10
|
import { resolveRootPathSync } from "./root-path.js";
|
|
11
11
|
import { admitPathInsideRoot } from "./root-boundary.js";
|
|
12
12
|
import { errorCauseOptions, fileNotFoundError, hardlinkedPathNotAllowedError, isAlreadyExistsError, normalizePinnedPathError, outsideWorkspaceError, } from "./root-errors.js";
|
|
13
|
-
import { assertFinalSymlinkRejected } from "./root-symlink-policy.js";
|
|
13
|
+
import { assertFinalSymlinkRejected, mutationSymlinkResolution } from "./root-symlink-policy.js";
|
|
14
|
+
import { PATH_ALIAS_POLICIES } from "./path-policy.js";
|
|
14
15
|
import { createSuppressedError } from "./suppressed-error.js";
|
|
15
16
|
import { inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
16
17
|
import { getFsSafeTestHooks } from "./test-hooks.js";
|
|
@@ -40,11 +41,19 @@ function nativePrimitiveUnavailable(error) {
|
|
|
40
41
|
return code === "ENOSYS" || code === "ENOTSUP" || code === "EOPNOTSUPP";
|
|
41
42
|
}
|
|
42
43
|
function normalizeMoveError(error) {
|
|
44
|
+
if (error instanceof FsSafeError)
|
|
45
|
+
return error;
|
|
46
|
+
const code = error?.code;
|
|
47
|
+
if (code === "path-mismatch" || code === "hardlink") {
|
|
48
|
+
return new FsSafeError(code, "native move source changed", errorCauseOptions(error));
|
|
49
|
+
}
|
|
50
|
+
if (code === "ELOOP")
|
|
51
|
+
return new FsSafeError("symlink", "native move source is a symlink", errorCauseOptions(error));
|
|
43
52
|
if (isNotFoundPathError(error)) {
|
|
44
53
|
return fileNotFoundError(error instanceof Error ? error : undefined);
|
|
45
54
|
}
|
|
46
55
|
if (nativePrimitiveUnavailable(error)) {
|
|
47
|
-
return new FsSafeError("helper-unavailable", "native
|
|
56
|
+
return new FsSafeError("helper-unavailable", "native move is unavailable", errorCauseOptions(error));
|
|
48
57
|
}
|
|
49
58
|
return error;
|
|
50
59
|
}
|
|
@@ -66,26 +75,28 @@ export function admitMoveSourceStat(stat, overwrite = false) {
|
|
|
66
75
|
}
|
|
67
76
|
return stat;
|
|
68
77
|
}
|
|
69
|
-
export async function
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
export async function movePathNative(root, params, paths, overwrite = false) {
|
|
79
|
+
if (!overwrite)
|
|
80
|
+
try {
|
|
81
|
+
fsSync.lstatSync(paths.targetPath);
|
|
82
|
+
throw new FsSafeError("already-exists", "destination exists");
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
if (error instanceof FsSafeError)
|
|
86
|
+
throw error;
|
|
87
|
+
if (!isNotFoundPathError(error))
|
|
88
|
+
throw error;
|
|
89
|
+
// Advisory fast rejection only. renameNoReplace owns the collision decision.
|
|
90
|
+
}
|
|
81
91
|
const binding = requireNativeBinding();
|
|
82
|
-
if (typeof binding.renameNoReplace !== "function") {
|
|
83
|
-
throw new FsSafeError("helper-unavailable", "native
|
|
92
|
+
if (typeof (overwrite ? binding.renameReplaceWithIdentity : binding.renameNoReplace) !== "function") {
|
|
93
|
+
throw new FsSafeError("helper-unavailable", "native move is unavailable");
|
|
84
94
|
}
|
|
85
95
|
const rootAdmission = await openNativeRootAdmission(binding, {
|
|
86
96
|
rootPath: root.rootReal,
|
|
87
97
|
rootIdentity: root.rootIdentity,
|
|
88
98
|
operation: "native move",
|
|
99
|
+
searchOnly: true,
|
|
89
100
|
});
|
|
90
101
|
let sourceParent;
|
|
91
102
|
let targetParent;
|
|
@@ -113,14 +124,13 @@ export async function movePathNoReplaceNative(root, params, paths) {
|
|
|
113
124
|
await assertRootIdentityCurrent(root);
|
|
114
125
|
for (const admission of parentAdmissions)
|
|
115
126
|
assertSyncDirectoryGuard(admission.guard);
|
|
116
|
-
//
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
127
|
+
// Overwrite publication always binds an exact source; no-clobber retains
|
|
128
|
+
// its existing callback-dependent identity contract.
|
|
129
|
+
const sourceStat = overwrite || params.assertBeforeMutation
|
|
130
|
+
? inspectFileIdentitySync(() => admitMoveSourceStat(fsSync.lstatSync(admittedSourcePath, { bigint: true }), overwrite), paths.expectedSourceIdentity)
|
|
131
|
+
: undefined;
|
|
132
|
+
if (!sourceStat)
|
|
122
133
|
admitMoveSourceStat(fsSync.lstatSync(admittedSourcePath));
|
|
123
|
-
}
|
|
124
134
|
assertFinalSymlinkRejected(admittedTargetPath, params.mutationSymlinks !== undefined);
|
|
125
135
|
params.assertBeforeMutation?.();
|
|
126
136
|
if (params.assertBeforeMutation || params.mutationSymlinks === "reject") {
|
|
@@ -145,21 +155,43 @@ export async function movePathNoReplaceNative(root, params, paths) {
|
|
|
145
155
|
assertSyncDirectoryGuard(admission.guard);
|
|
146
156
|
}
|
|
147
157
|
// The callback can change a leaf without replacing its admitted parent.
|
|
158
|
+
if (overwrite && params.assertBeforeMutation) {
|
|
159
|
+
for (const [original, selected, expected, policy] of [
|
|
160
|
+
[paths.sourceOriginalPath, admittedSourcePath, paths.sourceCanonicalPath, PATH_ALIAS_POLICIES.strict],
|
|
161
|
+
[paths.targetOriginalPath, admittedTargetPath, paths.targetCanonicalPath, PATH_ALIAS_POLICIES.unlinkTarget],
|
|
162
|
+
]) {
|
|
163
|
+
if (original === undefined || expected === undefined)
|
|
164
|
+
continue;
|
|
165
|
+
for (const absolutePath of new Set([path.isAbsolute(original) ? original : `${root.rootWithSep}${original}`, selected])) {
|
|
166
|
+
const current = resolveRootPathSync({ absolutePath, rootPath: root.rootReal,
|
|
167
|
+
rootCanonicalPath: root.rootReal, rootIdentity: root.rootIdentity, boundaryLabel: "root", policy,
|
|
168
|
+
...mutationSymlinkResolution(params.mutationSymlinks) });
|
|
169
|
+
if (current.canonicalPath !== expected)
|
|
170
|
+
throw new FsSafeError("path-mismatch", "move route changed during authorization");
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
148
174
|
if (sourceStat !== undefined) {
|
|
149
|
-
inspectFileIdentitySync(() => admitMoveSourceStat(fsSync.lstatSync(admittedSourcePath, { bigint: true })), sourceStat);
|
|
175
|
+
inspectFileIdentitySync(() => admitMoveSourceStat(fsSync.lstatSync(admittedSourcePath, { bigint: true }), overwrite), sourceStat);
|
|
150
176
|
}
|
|
151
177
|
try {
|
|
152
|
-
|
|
178
|
+
assertFinalSymlinkRejected(admittedTargetPath, params.mutationSymlinks !== undefined);
|
|
179
|
+
if (overwrite)
|
|
180
|
+
binding.renameReplaceWithIdentity(sourceParent.fd, path.basename(paths.sourcePath), targetParent.fd, path.basename(paths.targetPath), sourceStat.dev, sourceStat.ino);
|
|
181
|
+
else
|
|
182
|
+
binding.renameNoReplace(sourceParent.fd, path.basename(paths.sourcePath), targetParent.fd, path.basename(paths.targetPath));
|
|
153
183
|
}
|
|
154
184
|
catch (error) {
|
|
155
|
-
if (isAlreadyExistsError(error) || error?.code === "ENOTEMPTY") {
|
|
185
|
+
if (isAlreadyExistsError(error) || (!overwrite && error?.code === "ENOTEMPTY")) {
|
|
156
186
|
throw new FsSafeError("already-exists", "destination exists", errorCauseOptions(error));
|
|
157
187
|
}
|
|
158
|
-
throw normalizeRenameNoReplaceError(error);
|
|
188
|
+
throw overwrite ? normalizeMoveError(error) : normalizeRenameNoReplaceError(error);
|
|
159
189
|
}
|
|
160
190
|
try {
|
|
161
191
|
for (const admission of parentAdmissions)
|
|
162
192
|
await assertAsyncDirectoryGuard(admission.guard);
|
|
193
|
+
if (overwrite)
|
|
194
|
+
inspectFileIdentitySync(() => fsSync.lstatSync(admittedTargetPath, { bigint: true }), sourceStat);
|
|
163
195
|
}
|
|
164
196
|
catch (error) {
|
|
165
197
|
throw normalizePinnedPathError(error);
|
|
@@ -188,18 +220,15 @@ export async function movePathNoReplaceNative(root, params, paths) {
|
|
|
188
220
|
catch (error) {
|
|
189
221
|
closeErrors.push(error);
|
|
190
222
|
}
|
|
191
|
-
if (
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
223
|
+
if (closeErrors.length > 0) {
|
|
224
|
+
const closeError = closeErrors.length === 1
|
|
225
|
+
? closeErrors[0]
|
|
226
|
+
: new AggregateError(closeErrors, "native move descriptor closes failed");
|
|
227
|
+
if (failed) {
|
|
196
228
|
throw createSuppressedError(closeError, operationError, "native move and descriptor close failed");
|
|
197
229
|
}
|
|
198
|
-
throw
|
|
199
|
-
}
|
|
200
|
-
if (closeErrors.length === 1)
|
|
201
|
-
throw closeErrors[0];
|
|
202
|
-
if (closeErrors.length > 1) {
|
|
203
|
-
throw new AggregateError(closeErrors, "native move descriptor closes failed");
|
|
230
|
+
throw closeError;
|
|
204
231
|
}
|
|
232
|
+
if (failed)
|
|
233
|
+
throw operationError;
|
|
205
234
|
}
|
|
@@ -4,4 +4,4 @@ export type PinnedObservedPath = {
|
|
|
4
4
|
resolved: string;
|
|
5
5
|
receipt?: RootPathObservationReceipt;
|
|
6
6
|
};
|
|
7
|
-
export declare function resolvePinnedObservedPathInRoot(root: RootContext, relativePath: string, kind: RootPathObservationKind): Promise<PinnedObservedPath
|
|
7
|
+
export declare function resolvePinnedObservedPathInRoot(root: RootContext, relativePath: string, kind: RootPathObservationKind): Promise<PinnedObservedPath>;
|
|
@@ -11,8 +11,6 @@ import { errorCauseOptions, fileNotFoundError, outsideWorkspaceError } from "./r
|
|
|
11
11
|
import { getFsSafeTestHooks } from "./test-hooks.js";
|
|
12
12
|
export async function resolvePinnedObservedPathInRoot(root, relativePath, kind) {
|
|
13
13
|
const rootGuard = await createRootObservationGuard(root);
|
|
14
|
-
if (!rootGuard)
|
|
15
|
-
return undefined;
|
|
16
14
|
// Race hooks exercise the established JavaScript observation points. Do not
|
|
17
15
|
// silently move those points into the native helper in test configurations.
|
|
18
16
|
const directoryObserver = getNativeDirectoryObservationBackend(getFsSafeTestHooks() === undefined);
|
|
@@ -19,12 +19,11 @@ export type RootDefaults = {
|
|
|
19
19
|
mkdir?: boolean;
|
|
20
20
|
mode?: number;
|
|
21
21
|
denyMutations?: DenyMutationPolicy;
|
|
22
|
-
nonBlockingRead?: boolean;
|
|
23
22
|
renameIdentity?: RenameIdentityPolicy;
|
|
24
23
|
symlinks?: SymlinkPolicy;
|
|
25
24
|
mutationSymlinks?: MutationSymlinkPolicy;
|
|
26
25
|
};
|
|
27
|
-
export type RootReadOptions = Pick<RootDefaults, "hardlinks" | "maxBytes" | "
|
|
26
|
+
export type RootReadOptions = Pick<RootDefaults, "hardlinks" | "maxBytes" | "symlinks">;
|
|
28
27
|
export type RootOpenOptions = Omit<RootReadOptions, "maxBytes">;
|
|
29
28
|
export type RootWriteOptions = Pick<RootDefaults, "assertBeforeMutation" | "denyMutations" | "mutationSymlinks" | "durable" | "mkdir" | "mode" | "renameIdentity"> & {
|
|
30
29
|
encoding?: BufferEncoding;
|
|
@@ -79,6 +78,6 @@ export type RootRemoveOptions = Pick<RootDefaults, "assertBeforeMutation" | "den
|
|
|
79
78
|
export type RootMkdirOptions = Pick<RootDefaults, "assertBeforeMutation" | "denyMutations" | "mutationSymlinks"> & {
|
|
80
79
|
private?: boolean;
|
|
81
80
|
};
|
|
82
|
-
export type RootReadParams =
|
|
81
|
+
export type RootReadParams = RootReadOptions;
|
|
83
82
|
export declare function readDefaults(defaults: RootDefaults): RootReadParams;
|
|
84
83
|
export declare function mergeReadOptions(defaults: RootDefaults, options: RootReadOptions): RootReadParams;
|