@openclaw/openshell-sandbox 2026.10.6 → 2026.10.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/node_modules/@openclaw/fs-safe/CHANGELOG.md +48 -0
- package/node_modules/@openclaw/fs-safe/README.md +14 -4
- 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/clone-metadata.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/copy-file-input.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/copy-file-input.js +4 -3
- package/node_modules/@openclaw/fs-safe/dist/copy-link.d.ts +26 -0
- package/node_modules/@openclaw/fs-safe/dist/copy-link.js +169 -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-root-file-sync.js +1 -1
- 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/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-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/entry-publication.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/file-handle-transfer.js +4 -2
- package/node_modules/@openclaw/fs-safe/dist/file-hash.js +4 -3
- 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/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/json-durable-queue-ownership.js +16 -3
- package/node_modules/@openclaw/fs-safe/dist/native-binding.d.ts +57 -18
- package/node_modules/@openclaw/fs-safe/dist/native-directory-observation.js +1 -1
- 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 +1 -1
- package/node_modules/@openclaw/fs-safe/dist/native-parent-admission.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/native-parent-admission.js +2 -0
- package/node_modules/@openclaw/fs-safe/dist/native-pinned-write-windows.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/native-pinned-write-windows.js +19 -1
- 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 +18 -0
- 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.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/native-staged-file.js +3 -0
- package/node_modules/@openclaw/fs-safe/dist/native.d.ts +5 -3
- package/node_modules/@openclaw/fs-safe/dist/native.js +25 -6
- 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/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 +36 -47
- package/node_modules/@openclaw/fs-safe/dist/pinned-write-input.js +2 -0
- package/node_modules/@openclaw/fs-safe/dist/pinned-write-staged.js +3 -0
- package/node_modules/@openclaw/fs-safe/dist/pinned-write-types.d.ts +2 -1
- package/node_modules/@openclaw/fs-safe/dist/pinned-write.js +5 -3
- 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-producer-handoff.js +2 -5
- package/node_modules/@openclaw/fs-safe/dist/publish-copy-stage.js +92 -10
- package/node_modules/@openclaw/fs-safe/dist/publish-file.d.ts +1 -1
- package/node_modules/@openclaw/fs-safe/dist/publish-file.js +12 -11
- 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.js +12 -4
- package/node_modules/@openclaw/fs-safe/dist/root-impl.js +6 -92
- package/node_modules/@openclaw/fs-safe/dist/root-move-noreplace.js +1 -1
- package/node_modules/@openclaw/fs-safe/dist/root-options.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/root-remove-native.js +8 -1
- package/node_modules/@openclaw/fs-safe/dist/sibling-staged-file.js +1 -1
- 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/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/watch-native.js +2 -2
- package/node_modules/@openclaw/fs-safe/dist/windows-security-bridge.cs +11 -5
- package/node_modules/@openclaw/fs-safe/dist/windows-security-command.js +9 -1
- 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/docs/archive.md +16 -0
- package/node_modules/@openclaw/fs-safe/docs/contributing.md +35 -5
- package/node_modules/@openclaw/fs-safe/docs/copy.md +22 -0
- package/node_modules/@openclaw/fs-safe/docs/durability.md +4 -2
- 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 +13 -5
- package/node_modules/@openclaw/fs-safe/docs/migrating-to-0.5.md +5 -5
- package/node_modules/@openclaw/fs-safe/docs/native-helper.md +15 -5
- package/node_modules/@openclaw/fs-safe/docs/native.md +51 -2
- 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/quickstart.md +23 -3
- package/node_modules/@openclaw/fs-safe/docs/reading.md +7 -1
- package/node_modules/@openclaw/fs-safe/docs/secret-file.md +5 -5
- package/node_modules/@openclaw/fs-safe/docs/sidecar-lock.md +31 -13
- package/node_modules/@openclaw/fs-safe/docs/store.md +11 -0
- package/node_modules/@openclaw/fs-safe/docs/types.md +45 -80
- package/node_modules/@openclaw/fs-safe/docs/writing.md +121 -14
- package/node_modules/@openclaw/fs-safe/package.json +18 -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-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
|
@@ -11,7 +11,7 @@ import { ownDirectoryReceipt } from "./directory-receipt.js";
|
|
|
11
11
|
import { hashFileHandle } from "./file-hash.js";
|
|
12
12
|
import { sameFileIdentity, sameFileIdentityForCleanup, } from "./file-identity.js";
|
|
13
13
|
import { syncFileBestEffortSync } from "./file-sync.js";
|
|
14
|
-
import { getNativeBinding, requireNativeBinding } from "./native.js";
|
|
14
|
+
import { getNativeBinding, requireNativeBinding, selectNativeBinding } from "./native.js";
|
|
15
15
|
import { captureNativeFdClose } from "./native-binding.js";
|
|
16
16
|
import { resolveReadOpenFlags } from "./read-open-flags.js";
|
|
17
17
|
import { directorySyncFailure, publicationFailure, rememberCreatedTarget, } from "./publish-file-failure.js";
|
|
@@ -19,11 +19,9 @@ import { admitStandalonePublicationPath, assertNoWindowsPathAlias } from "./wind
|
|
|
19
19
|
import { getFsSafeTestHooks } from "./test-hooks.js";
|
|
20
20
|
import { hasErrorCode } from "./file-cleanup.js";
|
|
21
21
|
import { writeAllToFile } from "./write-file-handle.js";
|
|
22
|
-
|
|
22
|
+
import { isHardlinkFallbackError } from "./hardlink-fallback.js";
|
|
23
|
+
export { isHardlinkFallbackError } from "./hardlink-fallback.js";
|
|
23
24
|
const NATIVE_COPY_FALLBACK_CODES = new Set(["EINVAL", "ENOSYS", "ENOTSUP", "EOPNOTSUPP", "EPERM", "EXDEV"]);
|
|
24
|
-
export function isHardlinkFallbackError(error) {
|
|
25
|
-
return HARDLINK_FALLBACK_CODES.has(error?.code ?? "");
|
|
26
|
-
}
|
|
27
25
|
function directoryOpenFlags() {
|
|
28
26
|
return (fsSync.constants.O_RDONLY |
|
|
29
27
|
(typeof fsSync.constants.O_DIRECTORY === "number" ? fsSync.constants.O_DIRECTORY : 0));
|
|
@@ -78,13 +76,16 @@ async function copyPinnedSource(params) {
|
|
|
78
76
|
if (params.native && params.targetNativeParent) {
|
|
79
77
|
const closeFd = captureNativeFdClose(params.native);
|
|
80
78
|
for (const method of ["clone", "copy-file-range"]) {
|
|
79
|
+
const native = selectNativeBinding(params.native, method === "clone" ? "cloneFileExclusive" : "copyFileRangeExclusive");
|
|
80
|
+
if (!native)
|
|
81
|
+
continue;
|
|
81
82
|
let nativeFd;
|
|
82
83
|
try {
|
|
83
84
|
if (method === "clone") {
|
|
84
|
-
nativeFd =
|
|
85
|
+
nativeFd = native.cloneFileExclusive(params.source.fd, params.targetNativeParent.handle.fd, params.targetNativeParent.basename);
|
|
85
86
|
}
|
|
86
87
|
else {
|
|
87
|
-
const copied = await
|
|
88
|
+
const copied = await native.copyFileRangeExclusive(params.source.fd, params.targetNativeParent.handle.fd, params.targetNativeParent.basename);
|
|
88
89
|
if (copied.errorCode) {
|
|
89
90
|
throw Object.assign(new Error(copied.errorMessage ?? "native copy failed"), {
|
|
90
91
|
code: copied.errorCode,
|
|
@@ -263,14 +264,14 @@ export async function publishFileExclusive(params) {
|
|
|
263
264
|
await parent.assertCurrent();
|
|
264
265
|
assertSourceCurrent();
|
|
265
266
|
const native = strategy === "rename-noreplace"
|
|
266
|
-
? requireNativeBinding()
|
|
267
|
-
: getNativeBinding();
|
|
267
|
+
? requireNativeBinding("renameNoReplace")
|
|
268
|
+
: getNativeBinding("linkBeneath");
|
|
268
269
|
if (native) {
|
|
269
270
|
sourceNativeParent = await openNativeParent(sourcePath);
|
|
270
271
|
targetNativeParent = await openNativeParent(targetPath);
|
|
271
272
|
}
|
|
272
273
|
if (strategy === "rename-noreplace") {
|
|
273
|
-
const binding = requireNativeBinding();
|
|
274
|
+
const binding = requireNativeBinding("renameNoReplace");
|
|
274
275
|
try {
|
|
275
276
|
const fallback = nativeMoveNoReplace(binding, [
|
|
276
277
|
sourceNativeParent.handle.fd, sourceNativeParent.basename,
|
|
@@ -313,7 +314,7 @@ export async function publishFileExclusive(params) {
|
|
|
313
314
|
return await finishPublication("rename-noreplace", source, sourceExactIdentity);
|
|
314
315
|
}
|
|
315
316
|
try {
|
|
316
|
-
if (native) {
|
|
317
|
+
if (native?.linkBeneath) {
|
|
317
318
|
native.linkBeneath(sourceNativeParent.handle.fd, sourceNativeParent.basename, targetNativeParent.handle.fd, targetNativeParent.basename);
|
|
318
319
|
}
|
|
319
320
|
else {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type BigIntStats } from "node:fs";
|
|
2
|
+
import type { OpenResult } from "./root-impl.js";
|
|
3
|
+
import type { RootContext } from "./root-context.js";
|
|
4
|
+
import type { RootCopyOptions, RootCopySource, HardlinkPolicy } from "./root-options.js";
|
|
5
|
+
import { onCopyPublication, onCopySourceAdmission, type CopyPublicationOptions } from "./copy-publication.js";
|
|
6
|
+
type RootCopyParams = RootCopyOptions & {
|
|
7
|
+
source: RootCopySource;
|
|
8
|
+
relativePath: string;
|
|
9
|
+
verifyPublished?: CopyPublicationOptions[typeof onCopyPublication];
|
|
10
|
+
admitSource?: CopyPublicationOptions[typeof onCopySourceAdmission];
|
|
11
|
+
};
|
|
12
|
+
export declare function copyFileInRoot(root: RootContext, params: RootCopyParams, openVerifiedLocalFile: (path: string, options: {
|
|
13
|
+
hardlinks?: HardlinkPolicy;
|
|
14
|
+
}) => Promise<{
|
|
15
|
+
opened: OpenResult;
|
|
16
|
+
identity: BigIntStats;
|
|
17
|
+
}>): Promise<void>;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import fsSync, {} from "node:fs";
|
|
2
|
+
import { assertCopySourceCurrent, resolveFileCopyCloneMode } from "./copy-file-input.js";
|
|
3
|
+
import { resolveCopyCloneMode } from "./copy-policy.js";
|
|
4
|
+
import { captureCopyMetadata } from "./copy-metadata.js";
|
|
5
|
+
import { admitCopyLink, assertCopyLinkAvailable } from "./copy-link.js";
|
|
6
|
+
import { assertAbsolutePathInput } from "./absolute-path.js";
|
|
7
|
+
import { assertNoNulPathInput } from "./path.js";
|
|
8
|
+
import { assertNoWindowsPathAlias } from "./windows-path-alias.js";
|
|
9
|
+
import { inspectFileIdentity } from "./strict-file-identity.js";
|
|
10
|
+
import { MutationAuthorityError } from "./mutation-authority.js";
|
|
11
|
+
import { serializePathWrite } from "./write-queue.js";
|
|
12
|
+
import { rootWriteQueueKey } from "./root-create-native.js";
|
|
13
|
+
import { resolvePinnedWriteTargetInRoot } from "./root-write-admission.js";
|
|
14
|
+
import { createCopyPublicationObserver, onCopyPublication, onCopySourceAdmission } from "./copy-publication.js";
|
|
15
|
+
import { isAlreadyExistsError, normalizePinnedWriteError } from "./root-errors.js";
|
|
16
|
+
import { runPinnedWriteHelper } from "./pinned-write.js";
|
|
17
|
+
import { FsSafeError } from "./errors.js";
|
|
18
|
+
export async function copyFileInRoot(root, params, openVerifiedLocalFile) {
|
|
19
|
+
params.signal?.throwIfAborted();
|
|
20
|
+
resolveCopyCloneMode(params.clone, "never");
|
|
21
|
+
if (params.sourceSymlinks !== undefined && params.sourceSymlinks !== "reject" && params.sourceSymlinks !== "copy-link") {
|
|
22
|
+
throw new FsSafeError("invalid-path", "sourceSymlinks must be reject or copy-link");
|
|
23
|
+
}
|
|
24
|
+
if (params.sourceSymlinks === "copy-link") {
|
|
25
|
+
if (typeof params.source !== "string") {
|
|
26
|
+
throw new FsSafeError("invalid-path", "sourceSymlinks: copy-link requires an absolute-path string source; open/stat capabilities cannot observe links without following them");
|
|
27
|
+
}
|
|
28
|
+
const link = await admitCopyLink(assertAbsolutePathInput(params.source), params.preserveMetadata === true, params.signal)
|
|
29
|
+
.catch(error => { throw normalizePinnedWriteError(error); });
|
|
30
|
+
if (link) {
|
|
31
|
+
if (params.overwrite === true)
|
|
32
|
+
throw new FsSafeError("invalid-path", "copy-link cannot overwrite an existing destination; omit overwrite or use false");
|
|
33
|
+
assertCopyLinkAvailable();
|
|
34
|
+
const mode = params.mode ?? (params.preserveSourceMode ? Number(link.identity.mode & 4095n) : undefined);
|
|
35
|
+
await copyInputInRoot(root, { ...params, overwrite: false }, link, mode);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const clone = resolveFileCopyCloneMode(params.clone);
|
|
40
|
+
let source;
|
|
41
|
+
let sourceIdentity;
|
|
42
|
+
if (typeof params.source === "string") {
|
|
43
|
+
assertNoNulPathInput(params.source, "source path contains a NUL byte");
|
|
44
|
+
assertNoWindowsPathAlias(params.source, "filesystem", "source path uses a Windows filesystem namespace alias");
|
|
45
|
+
({ opened: source, identity: sourceIdentity } = await openVerifiedLocalFile(params.source, {
|
|
46
|
+
hardlinks: params.sourceHardlinks,
|
|
47
|
+
}));
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
source = await params.source.root.open(params.source.relativePath, params.sourceHardlinks === undefined ? undefined : { hardlinks: params.sourceHardlinks });
|
|
51
|
+
try {
|
|
52
|
+
sourceIdentity = await inspectFileIdentity(() => fsSync.fstatSync(source.handle.fd, { bigint: true }));
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
await source.handle.close().catch(() => undefined);
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
if (params.maxBytes !== undefined && source.stat.size > params.maxBytes) {
|
|
61
|
+
throw new FsSafeError("too-large", `file exceeds limit of ${params.maxBytes} bytes (got ${source.stat.size})`);
|
|
62
|
+
}
|
|
63
|
+
let metadata;
|
|
64
|
+
try {
|
|
65
|
+
metadata = params.preserveMetadata ? captureCopyMetadata(source.handle.fd, sourceIdentity) : undefined;
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
throw normalizePinnedWriteError(error);
|
|
69
|
+
}
|
|
70
|
+
const sourceAdmission = params.admitSource?.(sourceIdentity, source.realPath);
|
|
71
|
+
const verifySource = async () => {
|
|
72
|
+
params.signal?.throwIfAborted();
|
|
73
|
+
try {
|
|
74
|
+
sourceAdmission?.verify();
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
throw new MutationAuthorityError(error);
|
|
78
|
+
}
|
|
79
|
+
if (typeof params.source !== "string")
|
|
80
|
+
await params.source.root.stat(".");
|
|
81
|
+
await assertCopySourceCurrent(source, sourceIdentity);
|
|
82
|
+
};
|
|
83
|
+
const mode = sourceAdmission?.mode ?? params.mode ?? (params.preserveSourceMode ? Number(sourceIdentity.mode & 4095n) : undefined);
|
|
84
|
+
await copyInputInRoot(root, params, {
|
|
85
|
+
kind: "file", handle: source.handle, size: source.stat.size, clone,
|
|
86
|
+
signal: params.signal, metadata, verifySource,
|
|
87
|
+
}, mode, () => assertCopySourceCurrent(source, sourceIdentity));
|
|
88
|
+
}
|
|
89
|
+
finally {
|
|
90
|
+
await source.handle.close().catch(() => { });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
async function copyInputInRoot(root, params, input, mode, verifyAdmission = input.verifySource) {
|
|
94
|
+
if (mode !== undefined && (!Number.isInteger(mode) || mode < 0 || mode > 0o7777)) {
|
|
95
|
+
throw new FsSafeError("invalid-path", "invalid copy mode");
|
|
96
|
+
}
|
|
97
|
+
await serializePathWrite(rootWriteQueueKey(root, params.relativePath), async () => {
|
|
98
|
+
const pinned = await resolvePinnedWriteTargetInRoot(root, params.relativePath, mode, params.denyMutations, params.overwrite !== false, params.mutationSymlinks);
|
|
99
|
+
await serializePathWrite(pinned.targetPath, async () => {
|
|
100
|
+
await verifyAdmission();
|
|
101
|
+
const observer = createCopyPublicationObserver(pinned.targetPath, params.onDestinationPublished);
|
|
102
|
+
try {
|
|
103
|
+
await runPinnedWriteHelper({
|
|
104
|
+
rootPath: pinned.rootReal, relativeParentPath: pinned.relativeParentPath, basename: pinned.basename,
|
|
105
|
+
mkdir: params.mkdir !== false, mode: pinned.mode, overwrite: params.overwrite !== false,
|
|
106
|
+
rejectFinalSymlink: params.mutationSymlinks !== undefined, maxBytes: params.maxBytes,
|
|
107
|
+
sync: params.durable !== false,
|
|
108
|
+
assertBeforeMutation: params.signal || params.assertBeforeMutation ? () => {
|
|
109
|
+
if (params.signal?.aborted)
|
|
110
|
+
throw new MutationAuthorityError(params.signal.reason);
|
|
111
|
+
params.assertBeforeMutation?.();
|
|
112
|
+
} : undefined,
|
|
113
|
+
verifyPublished: params.verifyPublished, onPublished: observer.onPublished, input,
|
|
114
|
+
rootIdentity: root.rootIdentity, mutationAdmission: pinned.mutationAdmission,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
observer.rethrowObserverFailure(error);
|
|
119
|
+
if (params.signal?.aborted && error === params.signal.reason)
|
|
120
|
+
throw error;
|
|
121
|
+
if (isAlreadyExistsError(error))
|
|
122
|
+
throw new FsSafeError("already-exists", "copy destination already exists", { cause: error });
|
|
123
|
+
throw normalizePinnedWriteError(error);
|
|
124
|
+
}
|
|
125
|
+
await input.verifySource();
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
}
|
|
@@ -63,10 +63,11 @@ import { getNativeBinding } from "./native.js";
|
|
|
63
63
|
import { captureNativeFdClose } from "./native-binding.js";
|
|
64
64
|
import { openNativeRootAdmission } from "./native-parent-admission.js";
|
|
65
65
|
import { capturePolicyAwareNativeParent } from "./native-policy-parent.js";
|
|
66
|
-
import { isNotFoundPathError, isPathInside } from "./path.js";
|
|
66
|
+
import { hasNodeErrorCode, isNotFoundPathError, isPathInside } from "./path.js";
|
|
67
67
|
import { preparePinnedWriteMutationAdmission } from "./pinned-mutation-admission.js";
|
|
68
68
|
import { resolvePathViaExistingAncestor } from "./root-path-existing.js";
|
|
69
69
|
import { admitPathInsideRoot } from "./root-boundary.js";
|
|
70
|
+
import { directoryComponentNotDirectoryError } from "./root-errors.js";
|
|
70
71
|
import { assertRootIdentityCurrentSync } from "./root-context.js";
|
|
71
72
|
import { inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
72
73
|
import { getFsSafeTestHooks } from "./test-hooks.js";
|
|
@@ -81,7 +82,7 @@ function unavailable() {
|
|
|
81
82
|
throw new FsSafeError("helper-unavailable", "native confined creation is unavailable");
|
|
82
83
|
}
|
|
83
84
|
async function withNativeDirectory(params, operation, discard) {
|
|
84
|
-
const binding = getNativeBinding();
|
|
85
|
+
const binding = getNativeBinding("openBeneath", "mkdirChildBeneath");
|
|
85
86
|
if (!binding?.openBeneath || !binding.mkdirChildBeneath || (params.private && process.platform === "win32"))
|
|
86
87
|
return unavailable();
|
|
87
88
|
const directoryTarget = params.directory === params.target;
|
|
@@ -232,10 +233,17 @@ export async function tryMkdirRootNative(params) {
|
|
|
232
233
|
// and uses handle-relative NtCreateFile with its protected security descriptor.
|
|
233
234
|
if (params.private && process.platform === "win32")
|
|
234
235
|
return false;
|
|
235
|
-
|
|
236
|
+
try {
|
|
237
|
+
return await withNativeDirectory(params, async (_binding, _fd, assertCurrent) => { assertCurrent(); return true; });
|
|
238
|
+
}
|
|
239
|
+
catch (error) {
|
|
240
|
+
if (hasNodeErrorCode(error, "ENOTDIR"))
|
|
241
|
+
throw directoryComponentNotDirectoryError(error);
|
|
242
|
+
throw error;
|
|
243
|
+
}
|
|
236
244
|
}
|
|
237
245
|
export async function tryOpenCreateRootNative(params) {
|
|
238
|
-
const binding = getNativeBinding();
|
|
246
|
+
const binding = getNativeBinding("openBeneath", "mkdirChildBeneath", "removeStagedFile", "openCreateBeneath");
|
|
239
247
|
if (!binding?.removeStagedFile || !binding.openCreateBeneath)
|
|
240
248
|
return unavailable();
|
|
241
249
|
const access = (params.existingFlags & fs.constants.O_RDWR) ? 0o600 : 0o200;
|
|
@@ -55,7 +55,7 @@ import fsSync, { constants as fsConstants } from "node:fs";
|
|
|
55
55
|
import fs from "node:fs/promises";
|
|
56
56
|
import path from "node:path";
|
|
57
57
|
import { normalizeMaxBytes } from "./byte-budget.js";
|
|
58
|
-
import {
|
|
58
|
+
import { copyFileInRoot } from "./root-copy.js";
|
|
59
59
|
import { assertPrivateFileCreationAvailable, resolveCreationPermissions } from "./creation-boundary.js";
|
|
60
60
|
import { assertAsyncDirectoryGuard, assertSyncDirectoryGuard, createAsyncDirectoryGuard, createNearestExistingDirectoryGuard } from "./directory-guard.js";
|
|
61
61
|
import { FsSafeError } from "./errors.js";
|
|
@@ -68,6 +68,7 @@ import { openedPathResolutionError, recordExclusiveCreateFailure, recordOpenedFi
|
|
|
68
68
|
import { runPinnedWriteHelper, runPinnedWriteWithRenamePolicy } from "./pinned-write.js";
|
|
69
69
|
import { preparePinnedWriteMutationAdmission, snapshotPinnedMutationPolicy } from "./pinned-mutation-admission.js";
|
|
70
70
|
import { getNativeBinding } from "./native.js";
|
|
71
|
+
import { getPinnedWriteNativeBinding } from "./native-pinned-write.js";
|
|
71
72
|
import { isFsSafeNativeRequired } from "./native-config.js";
|
|
72
73
|
import { validatePinnedRelativePath } from "./pinned-operation.js";
|
|
73
74
|
import { PATH_ALIAS_POLICIES } from "./path-policy.js";
|
|
@@ -104,7 +105,7 @@ import { assertRootFallbackWritePath } from "./root-write-lock-binding.js";
|
|
|
104
105
|
import { inspectFileIdentity, inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
105
106
|
import { admitMoveSourceStat, movePathNative } from "./root-move-noreplace.js";
|
|
106
107
|
import { admitRootReadHandle, inspectOpenedPathIdentitySync } from "./root-read-admission.js";
|
|
107
|
-
import {
|
|
108
|
+
import { onCopyPublication, onCopySourceAdmission } from "./copy-publication.js";
|
|
108
109
|
import { writeAllToFile } from "./write-file-handle.js";
|
|
109
110
|
import { createInputOptions, rethrowCreateInputError, rootWriteInput, retainSidecarCreated } from "./root-create-input.js";
|
|
110
111
|
import { assertFinalSymlinkRejected, mutationSymlinkResolution, readSymlinkResolution } from "./root-symlink-policy.js";
|
|
@@ -113,7 +114,7 @@ import { assertNoWindowsPathAlias, resolvePathPreservingWindowsRoot } from "./wi
|
|
|
113
114
|
import { resolvePinnedObservedPathInRoot } from "./root-observed-path.js";
|
|
114
115
|
import { registerFileLockSyncRootAdapter } from "./file-lock-sync-root.js";
|
|
115
116
|
import { mergeReadOptions, readDefaults, } from "./root-options.js";
|
|
116
|
-
import { composeMutationAssertions,
|
|
117
|
+
import { composeMutationAssertions, rethrowMutationAuthorityError } from "./mutation-authority.js";
|
|
117
118
|
export { DEFAULT_ROOT_MAX_BYTES } from "./root-options.js";
|
|
118
119
|
export { resolveOpenedFileRealPathForHandle } from "./opened-realpath.js";
|
|
119
120
|
function logWarn(message) {
|
|
@@ -355,7 +356,7 @@ export class RootHandle {
|
|
|
355
356
|
durable: options.durable ?? this.defaults.durable ?? true,
|
|
356
357
|
verifyPublished: options[onCopyPublication],
|
|
357
358
|
admitSource: options[onCopySourceAdmission],
|
|
358
|
-
}).catch(rethrowMutationAuthorityError);
|
|
359
|
+
}, openVerifiedLocalFile).catch(rethrowMutationAuthorityError);
|
|
359
360
|
}
|
|
360
361
|
async exists(relativePath) {
|
|
361
362
|
try {
|
|
@@ -897,7 +898,7 @@ async function writeFileInRoot(root, params) {
|
|
|
897
898
|
const input = rootWriteInput(params);
|
|
898
899
|
await serializePathWrite(rootWriteQueueKey(root, params.relativePath), async () => {
|
|
899
900
|
if (!params.private && input.kind === "buffer" && !input.stageBeforePublish && process.platform === "win32" &&
|
|
900
|
-
(params.renameIdentity === "verify-content-with-lock" || !
|
|
901
|
+
(params.renameIdentity === "verify-content-with-lock" || !getPinnedWriteNativeBinding({ input }))) {
|
|
901
902
|
await writeFileFallback(root, { ...params, data: input.data });
|
|
902
903
|
return;
|
|
903
904
|
}
|
|
@@ -965,93 +966,6 @@ async function commitPinnedWriteInRoot(root, pinned, params, input) {
|
|
|
965
966
|
throw normalizePinnedWriteError(error);
|
|
966
967
|
}
|
|
967
968
|
}
|
|
968
|
-
async function copyFileInRoot(root, params) {
|
|
969
|
-
params.signal?.throwIfAborted();
|
|
970
|
-
const clone = resolveFileCopyCloneMode(params.clone);
|
|
971
|
-
let source;
|
|
972
|
-
let sourceIdentity;
|
|
973
|
-
if (typeof params.source === "string") {
|
|
974
|
-
assertNoNulPathInput(params.source, "source path contains a NUL byte");
|
|
975
|
-
assertNoWindowsPathAlias(params.source, "filesystem", "source path uses a Windows filesystem namespace alias");
|
|
976
|
-
({ opened: source, identity: sourceIdentity } = await openVerifiedLocalFile(params.source, {
|
|
977
|
-
hardlinks: params.sourceHardlinks,
|
|
978
|
-
}));
|
|
979
|
-
}
|
|
980
|
-
else {
|
|
981
|
-
source = await params.source.root.open(params.source.relativePath, params.sourceHardlinks === undefined ? undefined : { hardlinks: params.sourceHardlinks });
|
|
982
|
-
try {
|
|
983
|
-
sourceIdentity = await inspectFileIdentity(() => fsSync.fstatSync(source.handle.fd, { bigint: true }));
|
|
984
|
-
}
|
|
985
|
-
catch (error) {
|
|
986
|
-
await source.handle.close().catch(() => undefined);
|
|
987
|
-
throw error;
|
|
988
|
-
}
|
|
989
|
-
}
|
|
990
|
-
if (params.maxBytes !== undefined && source.stat.size > params.maxBytes) {
|
|
991
|
-
await source.handle.close().catch(() => { });
|
|
992
|
-
throw new FsSafeError("too-large", `file exceeds limit of ${params.maxBytes} bytes (got ${source.stat.size})`);
|
|
993
|
-
}
|
|
994
|
-
try {
|
|
995
|
-
const sourceAdmission = params.admitSource?.(sourceIdentity, source.realPath);
|
|
996
|
-
const mode = sourceAdmission?.mode ?? params.mode;
|
|
997
|
-
await serializePathWrite(rootWriteQueueKey(root, params.relativePath), async () => {
|
|
998
|
-
const pinned = await resolvePinnedWriteTargetInRoot(root, params.relativePath, mode ?? (params.preserveSourceMode ? Number(sourceIdentity.mode & 4095n) : undefined), params.denyMutations, params.overwrite !== false, params.mutationSymlinks);
|
|
999
|
-
await serializePathWrite(pinned.targetPath, async () => {
|
|
1000
|
-
await assertCopySourceCurrent(source, sourceIdentity);
|
|
1001
|
-
const verifySource = async () => {
|
|
1002
|
-
params.signal?.throwIfAborted();
|
|
1003
|
-
try {
|
|
1004
|
-
sourceAdmission?.verify();
|
|
1005
|
-
}
|
|
1006
|
-
catch (error) {
|
|
1007
|
-
throw new MutationAuthorityError(error);
|
|
1008
|
-
}
|
|
1009
|
-
if (typeof params.source !== "string") {
|
|
1010
|
-
await params.source.root.stat(".");
|
|
1011
|
-
}
|
|
1012
|
-
await assertCopySourceCurrent(source, sourceIdentity);
|
|
1013
|
-
};
|
|
1014
|
-
const observer = createCopyPublicationObserver(pinned.targetPath, params.onDestinationPublished);
|
|
1015
|
-
try {
|
|
1016
|
-
await runPinnedWriteHelper({
|
|
1017
|
-
rootPath: pinned.rootReal,
|
|
1018
|
-
relativeParentPath: pinned.relativeParentPath,
|
|
1019
|
-
basename: pinned.basename,
|
|
1020
|
-
mkdir: params.mkdir !== false,
|
|
1021
|
-
mode: pinned.mode,
|
|
1022
|
-
overwrite: params.overwrite !== false,
|
|
1023
|
-
rejectFinalSymlink: params.mutationSymlinks !== undefined,
|
|
1024
|
-
maxBytes: params.maxBytes,
|
|
1025
|
-
sync: params.durable !== false,
|
|
1026
|
-
assertBeforeMutation: params.signal || params.assertBeforeMutation ? () => {
|
|
1027
|
-
if (params.signal?.aborted)
|
|
1028
|
-
throw new MutationAuthorityError(params.signal.reason);
|
|
1029
|
-
params.assertBeforeMutation?.();
|
|
1030
|
-
} : undefined,
|
|
1031
|
-
verifyPublished: params.verifyPublished,
|
|
1032
|
-
onPublished: observer.onPublished,
|
|
1033
|
-
input: { kind: "file", handle: source.handle, size: source.stat.size, clone, signal: params.signal, verifySource },
|
|
1034
|
-
rootIdentity: root.rootIdentity,
|
|
1035
|
-
mutationAdmission: pinned.mutationAdmission,
|
|
1036
|
-
});
|
|
1037
|
-
}
|
|
1038
|
-
catch (error) {
|
|
1039
|
-
observer.rethrowObserverFailure(error);
|
|
1040
|
-
if (params.signal?.aborted && error === params.signal.reason)
|
|
1041
|
-
throw error;
|
|
1042
|
-
if (isAlreadyExistsError(error)) {
|
|
1043
|
-
throw new FsSafeError("already-exists", "copy destination already exists", { cause: error });
|
|
1044
|
-
}
|
|
1045
|
-
throw normalizePinnedWriteError(error);
|
|
1046
|
-
}
|
|
1047
|
-
await verifySource();
|
|
1048
|
-
});
|
|
1049
|
-
});
|
|
1050
|
-
}
|
|
1051
|
-
finally {
|
|
1052
|
-
await source.handle.close().catch(() => { });
|
|
1053
|
-
}
|
|
1054
|
-
}
|
|
1055
969
|
async function resolvePinnedPathInRoot(root, params) {
|
|
1056
970
|
const resolved = await resolvePinnedRootPathInRoot(root, {
|
|
1057
971
|
relativePath: params.relativePath,
|
|
@@ -80,7 +80,7 @@ export async function movePathNative(root, params, paths, overwrite = false) {
|
|
|
80
80
|
throw error;
|
|
81
81
|
// Advisory fast rejection only. renameNoReplace owns the collision decision.
|
|
82
82
|
}
|
|
83
|
-
const binding = requireNativeBinding();
|
|
83
|
+
const binding = requireNativeBinding("openBeneath", overwrite ? "renameReplaceWithIdentity" : "renameNoReplace");
|
|
84
84
|
if (typeof (overwrite ? binding.renameReplaceWithIdentity : binding.renameNoReplace) !== "function") {
|
|
85
85
|
throw new FsSafeError("helper-unavailable", "native move is unavailable");
|
|
86
86
|
}
|
|
@@ -38,6 +38,8 @@ export type RootCopyOptions = Pick<RootDefaults, "assertBeforeMutation" | "denyM
|
|
|
38
38
|
clone?: CopyCloneMode;
|
|
39
39
|
signal?: AbortSignal;
|
|
40
40
|
preserveSourceMode?: boolean;
|
|
41
|
+
preserveMetadata?: boolean;
|
|
42
|
+
sourceSymlinks?: "reject" | "copy-link";
|
|
41
43
|
onDestinationPublished?: (receipt: RootCopyPublicationReceipt) => void;
|
|
42
44
|
};
|
|
43
45
|
export type RootCopySource = string | {
|
|
@@ -29,7 +29,7 @@ function normalize(error, details) {
|
|
|
29
29
|
return normalizeRemovePathError(error, details);
|
|
30
30
|
}
|
|
31
31
|
export async function removePathInRootNative(root, target, options, receipts) {
|
|
32
|
-
const binding = getNativeBinding();
|
|
32
|
+
const binding = getNativeBinding("openBeneath", "rootRemovalStat", "rootRemovalUnlink");
|
|
33
33
|
if (!binding?.rootRemovalStat || !binding.rootRemovalUnlink) {
|
|
34
34
|
unavailable();
|
|
35
35
|
}
|
|
@@ -39,6 +39,13 @@ export async function removePathInRootNative(root, target, options, receipts) {
|
|
|
39
39
|
const boundary = await captureNonrecursiveRemovalAdmission(root, target, options, receipts);
|
|
40
40
|
if (!boundary)
|
|
41
41
|
return;
|
|
42
|
+
if (options.recursive && options.maxEntries === 0) {
|
|
43
|
+
options.signal?.throwIfAborted();
|
|
44
|
+
boundary.assertCurrent();
|
|
45
|
+
throw new FsSafeError("too-large", "recursive removal budget exceeded", {
|
|
46
|
+
details: { operation: "remove", phase: "inspect", relativePath: "" },
|
|
47
|
+
});
|
|
48
|
+
}
|
|
42
49
|
const rootAdmission = await openNativeRootAdmission(binding, {
|
|
43
50
|
rootPath: root.rootReal, rootIdentity: root.rootIdentity, operation: "remove", reportCloseErrors: true, searchOnly: true,
|
|
44
51
|
}).catch(error => { throw normalizeRemoveGuardError(error); });
|
|
@@ -75,7 +75,7 @@ async function writeIsolatedProducer(params) {
|
|
|
75
75
|
const targetRoot = rootFromDirectoryGuard(parentGuard);
|
|
76
76
|
// Select the handoff before invoking the producer. Missing required native
|
|
77
77
|
// support must not leave producer output behind as a discovery side effect.
|
|
78
|
-
const native = getNativeBinding();
|
|
78
|
+
const native = getNativeBinding("openBeneath", "renameNoReplace");
|
|
79
79
|
const cleanupErrors = [];
|
|
80
80
|
const { target, identity } = await createOwnedTempFile({
|
|
81
81
|
rootDir: targetRoot.rootReal,
|
|
@@ -99,7 +99,7 @@ export function retainCreatedSidecar(lockPath, raw, createdFd, guard, nativePare
|
|
|
99
99
|
}
|
|
100
100
|
} };
|
|
101
101
|
}
|
|
102
|
-
if (!binding.stagedFileMatches || !binding.removeStagedFile)
|
|
102
|
+
if (!binding.openBeneath || !binding.stagedFileMatches || !binding.removeStagedFile)
|
|
103
103
|
return undefined;
|
|
104
104
|
const closeFile = captureNativeFdClose(binding);
|
|
105
105
|
const parentFd = binding.openBeneath(nativeParent.fd, "", nativeOpenFlags(fs.constants.O_RDONLY | fs.constants.O_DIRECTORY)).fd;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import fsSync from "node:fs";
|
|
1
2
|
import fs from "node:fs/promises";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import { canonicalPathFromExistingAncestor } from "./absolute-path.js";
|
|
@@ -23,7 +24,13 @@ export async function resolveSidecarTargetPath(resolved, lockRoot) {
|
|
|
23
24
|
assertNoWindowsPathAlias(normalized);
|
|
24
25
|
return normalized;
|
|
25
26
|
}
|
|
26
|
-
|
|
27
|
+
let parentExists = false;
|
|
28
|
+
try {
|
|
29
|
+
parentExists = fsSync.statSync(dir).isDirectory();
|
|
30
|
+
}
|
|
31
|
+
catch { /* Preserve mkdir errors below. */ }
|
|
32
|
+
if (!parentExists)
|
|
33
|
+
await fs.mkdir(recursiveMkdirPath(dir), { recursive: true });
|
|
27
34
|
let parent;
|
|
28
35
|
try {
|
|
29
36
|
parent = realpathSync.native(dir);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { FileHandle } from "node:fs/promises";
|
|
2
2
|
import type { FileIdentityStat } from "./file-identity.js";
|
|
3
|
+
export { holdWindowsSharingLock, setWindowsFileAttributes, readWindowsFileExtents } from "./windows-test-hooks.js";
|
|
3
4
|
export type FsSafeTestHooks = {
|
|
4
5
|
beforeWatchRegistration?: (path: string) => void | Promise<void>;
|
|
5
6
|
afterWatchRegistration?: (path: string) => void | Promise<void>;
|
|
@@ -5,9 +5,9 @@ import { watchStreamPaths } from "./watch-stream.js";
|
|
|
5
5
|
export function watchBinding(mode) {
|
|
6
6
|
if (mode === "poll")
|
|
7
7
|
return;
|
|
8
|
-
const binding = getNativeBinding(
|
|
8
|
+
const binding = getNativeBinding("watchRegister", "watchAdd", "watchUnregister", ...(process.platform === "darwin" ? ["watchConfigure", "watchEntries"] : []));
|
|
9
9
|
// Bun TSFN teardown remains unqualified; guarded native scans remain usable.
|
|
10
|
-
if (binding
|
|
10
|
+
if (binding && !process.versions.bun && !process.versions.deno && ["linux", "darwin", "win32"].includes(process.platform))
|
|
11
11
|
return binding;
|
|
12
12
|
if (mode === "events" || getFsSafeNativeConfig().mode === "require") {
|
|
13
13
|
throw new FsSafeError("helper-unavailable", "native watch events are unavailable", { details: { operation: "watch" } });
|
|
@@ -41,18 +41,19 @@ public static partial class FsSafeWindowsBridge {
|
|
|
41
41
|
|
|
42
42
|
sealed class Failure : Exception {
|
|
43
43
|
public readonly string Code;
|
|
44
|
-
public
|
|
44
|
+
public readonly uint? Errno;
|
|
45
|
+
public Failure(string code, string message, uint? errno=null) : base(message) { Code=code; Errno=errno; }
|
|
45
46
|
}
|
|
46
47
|
static Failure OsFailure(uint error, string operation, bool identity=false, bool fullIdentity=false) {
|
|
47
48
|
// Native security calls retain EACCES; shared handle identity calls use EPERM.
|
|
48
49
|
string code=error==5 ? (identity ? "EPERM" : "EACCES") : error==80 || error==183 ? "EEXIST" : error==2 || error==3 ? "ENOENT" :
|
|
49
50
|
identity && (error==112 || error==39) ? "ENOSPC" : identity && (error==32 || error==33) ? "EBUSY" :
|
|
50
51
|
fullIdentity && (error==1 || error==50 || error==87) ? "ENOTSUP" : "EIO";
|
|
51
|
-
return new Failure(code, operation+" failed with Windows error "+error);
|
|
52
|
+
return new Failure(code, operation+" failed with Windows error "+error, error);
|
|
52
53
|
}
|
|
53
54
|
static Failure CreateFailure(uint error) {
|
|
54
55
|
var failure=OsFailure(error,"create private directory",true);
|
|
55
|
-
return failure.Code=="EPERM" ? new Failure("EACCES",failure.Message) : failure;
|
|
56
|
+
return failure.Code=="EPERM" ? new Failure("EACCES",failure.Message,failure.Errno) : failure;
|
|
56
57
|
}
|
|
57
58
|
static void Require(bool condition, string code, string message) {
|
|
58
59
|
if (!condition) throw new Failure(code,message);
|
|
@@ -305,7 +306,8 @@ public static partial class FsSafeWindowsBridge {
|
|
|
305
306
|
var failure=primary as Failure;
|
|
306
307
|
throw new Failure(failure==null ? "EIO" : failure.Code,
|
|
307
308
|
(failure==null ? "private directory validation failed" : failure.Message)+
|
|
308
|
-
"; owned-handle cleanup failed with Windows error "+Marshal.GetLastWin32Error()
|
|
309
|
+
"; owned-handle cleanup failed with Windows error "+Marshal.GetLastWin32Error(),
|
|
310
|
+
failure==null ? null : failure.Errno);
|
|
309
311
|
}
|
|
310
312
|
throw;
|
|
311
313
|
}
|
|
@@ -330,7 +332,11 @@ public static partial class FsSafeWindowsBridge {
|
|
|
330
332
|
} else if(operation=="path") result=InspectPath(path);
|
|
331
333
|
else throw new Failure("EINVAL","unknown Windows security operation");
|
|
332
334
|
return Row("ok",true,"result",result);
|
|
333
|
-
} catch(Failure error) {
|
|
335
|
+
} catch(Failure error) {
|
|
336
|
+
var reply=Row("ok",false,"code",error.Code,"message",error.Message);
|
|
337
|
+
if(error.Errno.HasValue) reply.Add("errno",error.Errno.Value);
|
|
338
|
+
return reply;
|
|
339
|
+
}
|
|
334
340
|
catch(Exception) { return Row("ok",false,"code","EIO","message","Windows security descriptor processing failed"); }
|
|
335
341
|
}
|
|
336
342
|
}
|
|
@@ -85,7 +85,15 @@ function parseReplyValue(stdout, operation) {
|
|
|
85
85
|
if (typeof response.code !== "string" || !codes.has(response.code) || typeof response.message !== "string") {
|
|
86
86
|
unverified("Windows security command returned an invalid failure");
|
|
87
87
|
}
|
|
88
|
-
|
|
88
|
+
if (response.errno !== undefined &&
|
|
89
|
+
(typeof response.errno !== "number" || !Number.isInteger(response.errno) || response.errno < 0 || response.errno > 0xffff_ffff)) {
|
|
90
|
+
unverified("Windows security command returned an invalid native error number");
|
|
91
|
+
}
|
|
92
|
+
const failure = Object.assign(new Error(response.message), { code: response.code });
|
|
93
|
+
if (response.errno !== undefined) {
|
|
94
|
+
Object.defineProperty(failure, "errno", { value: response.errno, enumerable: true, configurable: true, writable: true });
|
|
95
|
+
}
|
|
96
|
+
throw failure;
|
|
89
97
|
}
|
|
90
98
|
if (operation === "create" && (!record(response.result) || response.result.created !== true || !isFullIdentity(response.result.identity))) {
|
|
91
99
|
unverified("Windows security command did not verify private-directory creation");
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Test-only handle that omits delete sharing. */
|
|
2
|
+
export declare function holdWindowsSharingLock(filePath: string): Disposable & {
|
|
3
|
+
close(): void;
|
|
4
|
+
};
|
|
5
|
+
/** Test-only attribute updates preserve unspecified bits on the opened object. */
|
|
6
|
+
export declare function setWindowsFileAttributes(filePath: string, attrs: {
|
|
7
|
+
readOnly?: boolean;
|
|
8
|
+
hidden?: boolean;
|
|
9
|
+
system?: boolean;
|
|
10
|
+
}): void;
|
|
11
|
+
/** Test-only physical mapping reader; lcn -1 represents sparse clusters. */
|
|
12
|
+
export declare function readWindowsFileExtents(filePath: string): {
|
|
13
|
+
vcn: bigint;
|
|
14
|
+
lcn: bigint;
|
|
15
|
+
clusters: bigint;
|
|
16
|
+
}[];
|