@openclaw/fs-safe 0.13.0 → 0.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/copy-file-input.d.ts +1 -1
- package/dist/copy-file-input.d.ts.map +1 -1
- package/dist/copy-file-input.js +2 -0
- package/dist/native-binding.d.ts +3 -0
- package/dist/native-binding.d.ts.map +1 -1
- package/dist/native-binding.js +8 -1
- package/dist/native-operations.d.ts.map +1 -1
- package/dist/native-operations.js +6 -4
- package/dist/native-parent-admission.d.ts +1 -0
- package/dist/native-parent-admission.d.ts.map +1 -1
- package/dist/native-parent-admission.js +6 -3
- package/dist/native-pinned-write-windows.d.ts.map +1 -1
- package/dist/native-pinned-write-windows.js +8 -6
- package/dist/native-pinned-write.d.ts.map +1 -1
- package/dist/native-pinned-write.js +10 -7
- package/dist/native-staged-file.d.ts +4 -4
- package/dist/native-staged-file.d.ts.map +1 -1
- package/dist/native-staged-file.js +20 -10
- package/dist/native.d.ts +1 -1
- package/dist/native.d.ts.map +1 -1
- package/dist/native.js +7 -2
- package/dist/publish-file.d.ts.map +1 -1
- package/dist/publish-file.js +5 -3
- package/docs/native-helper.md +14 -1
- package/package.json +8 -8
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.13.1 - 2026-09-16
|
|
6
|
+
|
|
7
|
+
### Highlights
|
|
8
|
+
|
|
9
|
+
- **Correct native cleanup in Node workers:** guarded writes, moves, and staged-file cleanup close native-created descriptors through their originating binding, eliminating unmanaged-descriptor warnings while preserving Node's own descriptor tracking. ([#438](https://github.com/openclaw/fs-safe/pull/438))
|
|
10
|
+
|
|
11
|
+
### Fixes and compatibility
|
|
12
|
+
|
|
13
|
+
- Retain the correct closer through errors and native-mode changes, including staged files whose parent directory was opened by Node. Borrowed handles keep their caller-owned lifetime.
|
|
14
|
+
- On Windows, close exported descriptors through the same host runtime's libuv table that opened them. Descriptor-producing operations require complete native close support before allocation; stale helpers are treated as unavailable under the existing native-mode policy.
|
|
15
|
+
- Add real Worker coverage on Node 22/24 and verify that Windows native close releases both file and directory descriptors. Forced `Worker.terminate()` reclamation remains a [documented limitation](https://github.com/openclaw/fs-safe/blob/v0.13.1/docs/native-helper.md); callers should close retained resources and finish in-flight work before termination.
|
|
16
|
+
|
|
5
17
|
## 0.13.0 - 2026-09-16
|
|
6
18
|
|
|
7
19
|
### Highlights
|
|
@@ -2,7 +2,7 @@ import { type BigIntStats } from "node:fs";
|
|
|
2
2
|
import type { FileHandle } from "node:fs/promises";
|
|
3
3
|
import { type CopyCloneMode } from "./copy-policy.js";
|
|
4
4
|
import { type NativeBinding } from "./native.js";
|
|
5
|
-
import type
|
|
5
|
+
import { type NativeFileCopyResult } from "./native-binding.js";
|
|
6
6
|
export type CopyFileInput = {
|
|
7
7
|
kind: "file";
|
|
8
8
|
handle: FileHandle;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"copy-file-input.d.ts","sourceRoot":"","sources":["../src/copy-file-input.ts"],"names":[],"mappings":"AAAA,OAAW,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAC/C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAwB,KAAK,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAE5E,OAAO,EAAoB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"copy-file-input.d.ts","sourceRoot":"","sources":["../src/copy-file-input.ts"],"names":[],"mappings":"AAAA,OAAW,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAC/C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAwB,KAAK,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAE5E,OAAO,EAAoB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,EAAwB,KAAK,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAItF,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,UAAU,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,aAAa,CAAC;IACrB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/B,CAAC;AAEF,wBAAgB,wBAAwB,CAAC,IAAI,CAAC,EAAE,aAAa,GAAG,aAAa,CAM5E;AAED,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE;IAAE,MAAM,EAAE,UAAU,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,EAChD,QAAQ,EAAE,WAAW,GACpB,OAAO,CAAC,IAAI,CAAC,CASf;AAED,wBAAsB,iBAAiB,CACrC,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,aAAa,EACpB,QAAQ,CAAC,EAAE,MAAM,EACjB,oBAAoB,CAAC,EAAE,MAAM,IAAI,GAChC,OAAO,CAAC,IAAI,CAAC,CAIf;AAED,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,aAAa,EACrB,KAAK,EAAE,aAAa,EACpB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,IAAI,EAAE,OAAO,GACZ,OAAO,CAAC,oBAAoB,GAAG,SAAS,CAAC,CA+B3C;AAED,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,aAAa,EAAE,MAAM,CAAC,EAAE,oBAAoB,GAAG,IAAI,CAWnG"}
|
package/dist/copy-file-input.js
CHANGED
|
@@ -2,6 +2,7 @@ import fs, {} from "node:fs";
|
|
|
2
2
|
import { resolveCopyCloneMode } from "./copy-policy.js";
|
|
3
3
|
import { FsSafeError } from "./errors.js";
|
|
4
4
|
import { getNativeBinding } from "./native.js";
|
|
5
|
+
import { captureNativeFdClose } from "./native-binding.js";
|
|
5
6
|
import { inspectFileIdentity } from "./strict-file-identity.js";
|
|
6
7
|
import { transferFileHandle } from "./file-handle-transfer.js";
|
|
7
8
|
export function resolveFileCopyCloneMode(mode) {
|
|
@@ -34,6 +35,7 @@ export async function createNativeCopyFile(native, input, parentFd, basename, ma
|
|
|
34
35
|
}
|
|
35
36
|
return undefined;
|
|
36
37
|
}
|
|
38
|
+
captureNativeFdClose(native);
|
|
37
39
|
const nativeSignal = input.signal ? AbortSignal.any([input.signal]) : undefined;
|
|
38
40
|
try {
|
|
39
41
|
// The caller adopts this descriptor before observing a later cancellation.
|
package/dist/native-binding.d.ts
CHANGED
|
@@ -89,6 +89,8 @@ export interface NativeDarwinAclFacts {
|
|
|
89
89
|
state: "absent" | "empty" | "present";
|
|
90
90
|
}
|
|
91
91
|
export interface NativeBinding {
|
|
92
|
+
/** Internal: consumes only a descriptor returned by this binding. */
|
|
93
|
+
closeOwnedFd(fd: number): void;
|
|
92
94
|
/** Internal Darwin-only synchronous inspection; the caller retains its fd. */
|
|
93
95
|
inspectDarwinAcl?(fd: number): NativeDarwinAclFacts;
|
|
94
96
|
/** POSIX system canonicalization; confinement and identity policy stay with callers. */
|
|
@@ -139,4 +141,5 @@ export interface NativeBinding {
|
|
|
139
141
|
renameReplace(sourceRootFd: number, sourceRelPath: string, targetRootFd: number, targetRelPath: string): void;
|
|
140
142
|
sha256File(fd: number, maxBytes?: number, signal?: AbortSignal): Promise<NativeFileHash>;
|
|
141
143
|
}
|
|
144
|
+
export declare function captureNativeFdClose(binding: NativeBinding): (fd: number) => void;
|
|
142
145
|
//# sourceMappingURL=native-binding.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"native-binding.d.ts","sourceRoot":"","sources":["../src/native-binding.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"native-binding.d.ts","sourceRoot":"","sources":["../src/native-binding.ts"],"names":[],"mappings":"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"}
|
package/dist/native-binding.js
CHANGED
|
@@ -1 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
import { FsSafeError } from "./errors.js";
|
|
2
|
+
export function captureNativeFdClose(binding) {
|
|
3
|
+
if (typeof binding.closeOwnedFd !== "function") {
|
|
4
|
+
throw new FsSafeError("helper-unavailable", "native descriptor ownership is unavailable");
|
|
5
|
+
}
|
|
6
|
+
// Retained descriptors must remain disposable after native configuration changes.
|
|
7
|
+
return binding.closeOwnedFd.bind(binding);
|
|
8
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"native-operations.d.ts","sourceRoot":"","sources":["../src/native-operations.ts"],"names":[],"mappings":"AAAA,OAAe,EAAE,KAAK,WAAW,EAAE,KAAK,KAAK,EAAE,MAAM,SAAS,CAAC;AAG/D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"native-operations.d.ts","sourceRoot":"","sources":["../src/native-operations.ts"],"names":[],"mappings":"AAAA,OAAe,EAAE,KAAK,WAAW,EAAE,KAAK,KAAK,EAAE,MAAM,SAAS,CAAC;AAG/D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAK7D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAI1D,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,WAAW,EAAE,oBAAoB,CAAC;IAC3C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,IAAI,IAAI,OAAO,CAAC,KAAK,CAAC,CAAC;IACvB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,QAAQ,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5E,CAAC;AAEF,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAOrD;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAS5D;AAED,wBAAsB,gBAAgB,CACpC,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,gBAAgB,EACvB,QAAQ,CAAC,EAAE,MAAM,EACjB,oBAAoB,CAAC,EAAE,MAAM,IAAI,GAChC,OAAO,CAAC,IAAI,CAAC,CAoBf;AA0BD,wBAAgB,oCAAoC,CAAC,MAAM,EAAE;IAC3D,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,WAAW,CAAC;CACvB,GAAG,IAAI,CAqBP;AAED,wBAAsB,yBAAyB,CAC7C,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,gBAAgB,GAAG,SAAS,CAAC,CAyDvC"}
|
|
@@ -4,6 +4,7 @@ import path from "node:path";
|
|
|
4
4
|
import { FsSafeError } from "./errors.js";
|
|
5
5
|
import { sameFileIdentityForCleanup } from "./file-identity.js";
|
|
6
6
|
import { getNativeBinding } from "./native.js";
|
|
7
|
+
import { captureNativeFdClose } from "./native-binding.js";
|
|
7
8
|
import { writeAllToFile } from "./write-file-handle.js";
|
|
8
9
|
import { writeCopyFileToFd } from "./copy-file-input.js";
|
|
9
10
|
export function nativeOpenFlags(flags) {
|
|
@@ -44,7 +45,7 @@ export async function writeNativeInput(fd, input, maxBytes, assertBeforeMutation
|
|
|
44
45
|
}
|
|
45
46
|
}
|
|
46
47
|
}
|
|
47
|
-
function wrapNativeFd(fd, containment) {
|
|
48
|
+
function wrapNativeFd(fd, containment, closeFd) {
|
|
48
49
|
let open = true;
|
|
49
50
|
return {
|
|
50
51
|
fd,
|
|
@@ -52,7 +53,7 @@ function wrapNativeFd(fd, containment) {
|
|
|
52
53
|
async close() {
|
|
53
54
|
if (open) {
|
|
54
55
|
open = false;
|
|
55
|
-
|
|
56
|
+
closeFd(fd);
|
|
56
57
|
}
|
|
57
58
|
},
|
|
58
59
|
async stat() {
|
|
@@ -89,6 +90,7 @@ export async function createNativeExclusiveFile(targetPath, mode) {
|
|
|
89
90
|
if (!binding) {
|
|
90
91
|
return undefined;
|
|
91
92
|
}
|
|
93
|
+
const closeFd = captureNativeFdClose(binding);
|
|
92
94
|
const parentPath = path.dirname(targetPath);
|
|
93
95
|
const basename = path.basename(targetPath);
|
|
94
96
|
const parent = await fs.open(parentPath, fsSync.constants.O_RDONLY |
|
|
@@ -113,12 +115,12 @@ export async function createNativeExclusiveFile(targetPath, mode) {
|
|
|
113
115
|
fd = opened.fd;
|
|
114
116
|
fsSync.fchmodSync(fd, mode);
|
|
115
117
|
created = fsSync.fstatSync(fd, { bigint: true });
|
|
116
|
-
return wrapNativeFd(fd, opened.containment);
|
|
118
|
+
return wrapNativeFd(fd, opened.containment, closeFd);
|
|
117
119
|
}
|
|
118
120
|
catch (error) {
|
|
119
121
|
if (fd !== undefined) {
|
|
120
122
|
try {
|
|
121
|
-
|
|
123
|
+
closeFd(fd);
|
|
122
124
|
}
|
|
123
125
|
catch {
|
|
124
126
|
// Preserve the original error.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"native-parent-admission.d.ts","sourceRoot":"","sources":["../src/native-parent-admission.ts"],"names":[],"mappings":"AAAA,OAAe,EAAE,KAAK,WAAW,EAAE,KAAK,KAAK,EAAE,MAAM,SAAS,CAAC;AAC/D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAKnD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"native-parent-admission.d.ts","sourceRoot":"","sources":["../src/native-parent-admission.ts"],"names":[],"mappings":"AAAA,OAAe,EAAE,KAAK,WAAW,EAAE,KAAK,KAAK,EAAE,MAAM,SAAS,CAAC;AAC/D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAKnD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGjD,OAAO,EAAE,uBAAuB,EAAwB,MAAM,uBAAuB,CAAC;AAKtF,KAAK,YAAY,GAAG,IAAI,CAAC,gBAAgB,EAAE,KAAK,GAAG,KAAK,CAAC,CAAC;AAE1D,MAAM,MAAM,mBAAmB,GAAG;IAChC,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,UAAU,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,IAAI,IAAI,CAAC;IACd,KAAK,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,KAAK,GAAG,WAAW,CAAA;KAAE,CAAC;IACpE,eAAe,CAAC,EAAE,UAAU,CAAC,OAAO,uBAAuB,CAAC,CAAC;CAC9D,CAAC;AAEF,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,YAAY,EAClB,KAAK,EAAE,YAAY,GAClB,OAAO,CAET;AAaD,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,aAAa,EACtB,MAAM,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,YAAY,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAC5E,OAAO,CAAC,mBAAmB,CAAC,CAgD9B;AAED,wBAAsB,yBAAyB,CAC7C,OAAO,EAAE,aAAa,EACtB,aAAa,EAAE,mBAAmB,EAClC,kBAAkB,EAAE,MAAM,GACzB,OAAO,CAAC,qBAAqB,CAAC,CA+DhC;AAED,wBAAgB,0BAA0B,CAAC,SAAS,EAAE,qBAAqB,GAAG,SAAS,GAAG,IAAI,CAE7F"}
|
|
@@ -3,6 +3,7 @@ import fs from "node:fs/promises";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { inspectDirectoryIdentity } from "./directory-guard.js";
|
|
5
5
|
import { FsSafeError } from "./errors.js";
|
|
6
|
+
import { captureNativeFdClose } from "./native-binding.js";
|
|
6
7
|
import { realpathSync } from "./realpath.js";
|
|
7
8
|
import { describeStagedDirectory, exactIdentityMatches } from "./staged-directory.js";
|
|
8
9
|
import { inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
@@ -18,6 +19,7 @@ function assertParentAdmissionAvailable(binding) {
|
|
|
18
19
|
if (typeof binding.openBeneath !== "function") {
|
|
19
20
|
throw unavailable("native parent directory admission is unavailable");
|
|
20
21
|
}
|
|
22
|
+
captureNativeFdClose(binding);
|
|
21
23
|
}
|
|
22
24
|
export async function openNativeRootAdmission(binding, params) {
|
|
23
25
|
assertParentAdmissionAvailable(binding);
|
|
@@ -68,6 +70,7 @@ export async function openNativeRootAdmission(binding, params) {
|
|
|
68
70
|
}
|
|
69
71
|
export async function openNativeParentAdmission(binding, rootAdmission, relativeParentPath) {
|
|
70
72
|
assertParentAdmissionAvailable(binding);
|
|
73
|
+
const closeFd = captureNativeFdClose(binding);
|
|
71
74
|
const directoryFlags = fsSync.constants.O_RDONLY | (fsSync.constants.O_DIRECTORY ?? 0);
|
|
72
75
|
assertNoWindowsPathAlias(relativeParentPath, "relative", "native parent uses a Windows filesystem namespace alias");
|
|
73
76
|
const opened = binding.openBeneath(rootAdmission.root.fd, relativeParentPath, directoryFlags);
|
|
@@ -102,13 +105,14 @@ export async function openNativeParentAdmission(binding, rootAdmission, relative
|
|
|
102
105
|
}
|
|
103
106
|
return {
|
|
104
107
|
fd: parentFd,
|
|
108
|
+
close: () => closeFd(parentFd),
|
|
105
109
|
guard: { dir: parentPath, realPath: parentPath, stat: parentPathStat },
|
|
106
110
|
stagedDirectory,
|
|
107
111
|
};
|
|
108
112
|
}
|
|
109
113
|
catch (error) {
|
|
110
114
|
try {
|
|
111
|
-
|
|
115
|
+
closeFd(parentFd);
|
|
112
116
|
}
|
|
113
117
|
catch (closeError) {
|
|
114
118
|
if (process.platform !== "win32") {
|
|
@@ -119,6 +123,5 @@ export async function openNativeParentAdmission(binding, rootAdmission, relative
|
|
|
119
123
|
}
|
|
120
124
|
}
|
|
121
125
|
export function closeNativeParentAdmission(admission) {
|
|
122
|
-
|
|
123
|
-
fsSync.closeSync(admission.fd);
|
|
126
|
+
admission?.close();
|
|
124
127
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"native-pinned-write-windows.d.ts","sourceRoot":"","sources":["../src/native-pinned-write-windows.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAEnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAM3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"native-pinned-write-windows.d.ts","sourceRoot":"","sources":["../src/native-pinned-write-windows.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAEnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAM3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAa3D,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,aAAa,EACtB,MAAM,EAAE,iBAAiB,EACzB,IAAI,EAAE,UAAU,EAChB,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,sBAAsB,GAClC,OAAO,CAAC,gBAAgB,CAAC,CA2F3B"}
|
|
@@ -4,19 +4,21 @@ import fsSync, {} from "node:fs";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { FsSafeError } from "./errors.js";
|
|
6
6
|
import { nativeOpenFlags, removeNativeCreatedFileIfStillPinned, writeNativeInput, } from "./native-operations.js";
|
|
7
|
+
import { captureNativeFdClose } from "./native-binding.js";
|
|
7
8
|
import { inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
8
9
|
import { assertFinalSymlinkRejected } from "./root-symlink-policy.js";
|
|
9
|
-
function closeWriteFd(fd) {
|
|
10
|
+
function closeWriteFd(closeFd, fd) {
|
|
10
11
|
if (fd === undefined)
|
|
11
12
|
return;
|
|
12
13
|
try {
|
|
13
|
-
|
|
14
|
+
closeFd(fd);
|
|
14
15
|
}
|
|
15
16
|
catch (error) {
|
|
16
17
|
return error;
|
|
17
18
|
}
|
|
18
19
|
}
|
|
19
20
|
export async function runPinnedWriteWindows(binding, params, root, parentFd, parentGuard) {
|
|
21
|
+
const closeFd = captureNativeFdClose(binding);
|
|
20
22
|
const parentPath = parentGuard.realPath;
|
|
21
23
|
let tempFd;
|
|
22
24
|
let targetFd;
|
|
@@ -64,7 +66,7 @@ export async function runPinnedWriteWindows(binding, params, root, parentFd, par
|
|
|
64
66
|
syncFileBestEffortSync(targetFd);
|
|
65
67
|
}
|
|
66
68
|
catch (error) {
|
|
67
|
-
closeWriteFd(targetFd);
|
|
69
|
+
closeWriteFd(closeFd, targetFd);
|
|
68
70
|
targetFd = undefined;
|
|
69
71
|
// Staged copies and streamed creates preserve publication for caller recovery.
|
|
70
72
|
if (params.input.kind === "buffer" ||
|
|
@@ -86,8 +88,8 @@ export async function runPinnedWriteWindows(binding, params, root, parentFd, par
|
|
|
86
88
|
return { dev: targetIdentity.dev, ino: targetIdentity.ino };
|
|
87
89
|
}
|
|
88
90
|
finally {
|
|
89
|
-
const targetCloseError = closeWriteFd(targetFd);
|
|
90
|
-
const tempCloseError = closeWriteFd(tempFd);
|
|
91
|
+
const targetCloseError = closeWriteFd(closeFd, targetFd);
|
|
92
|
+
const tempCloseError = closeWriteFd(closeFd, tempFd);
|
|
91
93
|
if (!renamed) {
|
|
92
94
|
removeNativeCreatedFileIfStillPinned({
|
|
93
95
|
parentPath,
|
|
@@ -96,7 +98,7 @@ export async function runPinnedWriteWindows(binding, params, root, parentFd, par
|
|
|
96
98
|
created: tempIdentity,
|
|
97
99
|
});
|
|
98
100
|
}
|
|
99
|
-
const parentCloseError = closeWriteFd(parentFd);
|
|
101
|
+
const parentCloseError = closeWriteFd(closeFd, parentFd);
|
|
100
102
|
await root.close().catch(() => undefined);
|
|
101
103
|
const closeError = targetCloseError ?? tempCloseError ?? parentCloseError;
|
|
102
104
|
if (completed && closeError !== undefined)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"native-pinned-write.d.ts","sourceRoot":"","sources":["../src/native-pinned-write.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAI3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"native-pinned-write.d.ts","sourceRoot":"","sources":["../src/native-pinned-write.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAI3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD,OAAO,KAAK,EAIV,iBAAiB,EAClB,MAAM,mBAAmB,CAAC;AAqV3B,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAsGvH"}
|
|
@@ -57,6 +57,7 @@ import { FsSafeError } from "./errors.js";
|
|
|
57
57
|
import { runPinnedWriteWindows } from "./native-pinned-write-windows.js";
|
|
58
58
|
import { openNativeParentAdmission, openNativeRootAdmission } from "./native-parent-admission.js";
|
|
59
59
|
import { assertNativeStaging, writeNativeStage } from "./native-staged-file.js";
|
|
60
|
+
import { captureNativeFdClose } from "./native-binding.js";
|
|
60
61
|
import { assertPolicyStagedDirectoryCurrent, assertStagedDirectoryCurrent, describePolicyStagedDirectory, describeStagedDirectory, refreshPolicyStagedDirectoryObservation, } from "./staged-directory.js";
|
|
61
62
|
import { inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
62
63
|
import { realpathSync } from "./realpath.js";
|
|
@@ -148,6 +149,7 @@ function assertPosixParentCurrent(parent) {
|
|
|
148
149
|
: assertStagedDirectoryCurrent(parent.directory);
|
|
149
150
|
}
|
|
150
151
|
async function capturePolicyAwarePosixParent(binding, params, rootFd, directoryFlags) {
|
|
152
|
+
const closeFd = captureNativeFdClose(binding);
|
|
151
153
|
const segments = relativeParentSegments(params.relativeParentPath);
|
|
152
154
|
const parentSpelling = segments.length
|
|
153
155
|
? path.join(params.rootPath, ...segments)
|
|
@@ -190,7 +192,7 @@ async function capturePolicyAwarePosixParent(binding, params, rootFd, directoryF
|
|
|
190
192
|
return admitted;
|
|
191
193
|
}
|
|
192
194
|
catch (error) {
|
|
193
|
-
|
|
195
|
+
closeFd(parentFd);
|
|
194
196
|
throw error;
|
|
195
197
|
}
|
|
196
198
|
}
|
|
@@ -299,7 +301,7 @@ async function capturePolicyAwarePosixParent(binding, params, rootFd, directoryF
|
|
|
299
301
|
}
|
|
300
302
|
}
|
|
301
303
|
catch (error) {
|
|
302
|
-
|
|
304
|
+
closeFd(childFd);
|
|
303
305
|
throw error;
|
|
304
306
|
}
|
|
305
307
|
const previousOwnedFd = currentOwnedFd;
|
|
@@ -308,7 +310,7 @@ async function capturePolicyAwarePosixParent(binding, params, rootFd, directoryF
|
|
|
308
310
|
currentPath = child.parentPath;
|
|
309
311
|
current = child;
|
|
310
312
|
if (previousOwnedFd !== undefined)
|
|
311
|
-
|
|
313
|
+
closeFd(previousOwnedFd);
|
|
312
314
|
if (index === segments.length - 1) {
|
|
313
315
|
currentOwnedFd = undefined;
|
|
314
316
|
return child;
|
|
@@ -319,12 +321,13 @@ async function capturePolicyAwarePosixParent(binding, params, rootFd, directoryF
|
|
|
319
321
|
}
|
|
320
322
|
finally {
|
|
321
323
|
if (currentOwnedFd !== undefined)
|
|
322
|
-
|
|
324
|
+
closeFd(currentOwnedFd);
|
|
323
325
|
}
|
|
324
326
|
}
|
|
325
327
|
export async function runPinnedWriteNative(binding, params) {
|
|
326
328
|
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
327
329
|
try {
|
|
330
|
+
const closeFd = captureNativeFdClose(binding);
|
|
328
331
|
const windows = process.platform === "win32";
|
|
329
332
|
if (!windows) {
|
|
330
333
|
assertNativeStaging(binding);
|
|
@@ -344,7 +347,7 @@ export async function runPinnedWriteNative(binding, params) {
|
|
|
344
347
|
const parentGuard = __addDisposableResource(env_1, {
|
|
345
348
|
[Symbol.dispose]() {
|
|
346
349
|
if (!windows && parentFd !== undefined) {
|
|
347
|
-
|
|
350
|
+
closeFd(parentFd);
|
|
348
351
|
}
|
|
349
352
|
},
|
|
350
353
|
}, false);
|
|
@@ -400,7 +403,7 @@ export async function runPinnedWriteNative(binding, params) {
|
|
|
400
403
|
}
|
|
401
404
|
const ownedParent = parentFd;
|
|
402
405
|
parentFd = undefined;
|
|
403
|
-
return await writeNativeStage(binding, ownedParent, directory, params, verificationGuard);
|
|
406
|
+
return await writeNativeStage(binding, ownedParent, closeFd, directory, params, verificationGuard);
|
|
404
407
|
}
|
|
405
408
|
finally {
|
|
406
409
|
if (windows && !windowsOwnsDirectories) {
|
|
@@ -410,7 +413,7 @@ export async function runPinnedWriteNative(binding, params) {
|
|
|
410
413
|
// a close attempt. In particular, a parent close failure must not skip
|
|
411
414
|
// the root FileHandle close.
|
|
412
415
|
try {
|
|
413
|
-
|
|
416
|
+
closeFd(parentFd);
|
|
414
417
|
}
|
|
415
418
|
catch {
|
|
416
419
|
// Best effort while propagating the operation failure.
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import type { DirectoryReceipt } from "./directory-durability.js";
|
|
2
2
|
import type { AnyAsyncDirectoryGuard } from "./directory-guard.js";
|
|
3
3
|
import type { FileIdentityStat } from "./file-identity.js";
|
|
4
|
-
import type
|
|
4
|
+
import { type NativeBinding } from "./native-binding.js";
|
|
5
5
|
import type { PinnedWriteInput, PinnedWriteParams } from "./pinned-write.js";
|
|
6
6
|
import type { PublishedFileReceipt, StagedFile, StagedFileCleanupReceipt, StagedFileReceipt } from "./staged-file-types.js";
|
|
7
7
|
export type NativeStagingBinding = NativeBinding & Required<Pick<NativeBinding, "createStagedFile" | "stagedFileMatches" | "removeStagedFile">>;
|
|
8
8
|
export declare function assertNativeStaging(binding: NativeBinding): asserts binding is NativeStagingBinding;
|
|
9
9
|
declare class NativeStagedFile implements StagedFile {
|
|
10
10
|
#private;
|
|
11
|
-
constructor(binding: NativeStagingBinding, parentFd: number, directory: StagedFileReceipt["directory"], portableNames: boolean, publishedMode: number, sync: boolean, assertBeforeMutation?: () => void);
|
|
12
|
-
static create(binding: NativeStagingBinding, parentFd: number, directory: StagedFileReceipt["directory"], input: PinnedWriteInput, mode: number, maxBytes?: number, portableNames?: boolean, sync?: boolean, assertBeforeMutation?: () => void): Promise<NativeStagedFile>;
|
|
13
|
-
static write(binding: NativeStagingBinding, parentFd: number, directory: StagedFileReceipt["directory"], params: PinnedWriteParams, parentGuard: AnyAsyncDirectoryGuard): Promise<FileIdentityStat>;
|
|
11
|
+
constructor(binding: NativeStagingBinding, parentFd: number, closeParentFd: (fd: number) => void, directory: StagedFileReceipt["directory"], portableNames: boolean, publishedMode: number, sync: boolean, assertBeforeMutation?: () => void);
|
|
12
|
+
static create(binding: NativeStagingBinding, parentFd: number, closeParentFd: (fd: number) => void, directory: StagedFileReceipt["directory"], input: PinnedWriteInput, mode: number, maxBytes?: number, portableNames?: boolean, sync?: boolean, assertBeforeMutation?: () => void): Promise<NativeStagedFile>;
|
|
13
|
+
static write(binding: NativeStagingBinding, parentFd: number, closeParentFd: (fd: number) => void, directory: StagedFileReceipt["directory"], params: PinnedWriteParams, parentGuard: AnyAsyncDirectoryGuard): Promise<FileIdentityStat>;
|
|
14
14
|
get receipt(): StagedFileReceipt;
|
|
15
15
|
assertCurrent(): Promise<void>;
|
|
16
16
|
publish(basename: string, options: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"native-staged-file.d.ts","sourceRoot":"","sources":["../src/native-staged-file.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAMlE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAGnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"native-staged-file.d.ts","sourceRoot":"","sources":["../src/native-staged-file.ts"],"names":[],"mappings":"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"}
|
|
@@ -57,12 +57,14 @@ import fs from "node:fs";
|
|
|
57
57
|
import path from "node:path";
|
|
58
58
|
import { FsSafeError } from "./errors.js";
|
|
59
59
|
import { MutationAuthorityError } from "./mutation-authority.js";
|
|
60
|
+
import { captureNativeFdClose } from "./native-binding.js";
|
|
60
61
|
import { writeNativeInput } from "./native-operations.js";
|
|
61
62
|
import { assertNativeCopyCompleted, createNativeCopyFile } from "./copy-file-input.js";
|
|
62
63
|
import { assertStagedDirectoryCurrent, openStagedDirectory } from "./staged-directory.js";
|
|
63
64
|
import { assertFinalSymlinkRejected } from "./root-symlink-policy.js";
|
|
64
65
|
export function assertNativeStaging(binding) {
|
|
65
66
|
if ([
|
|
67
|
+
binding.closeOwnedFd,
|
|
66
68
|
binding.createStagedFile,
|
|
67
69
|
binding.stagedFileMatches,
|
|
68
70
|
binding.removeStagedFile,
|
|
@@ -92,7 +94,9 @@ function failure(error, details) {
|
|
|
92
94
|
}
|
|
93
95
|
class NativeStagedFile {
|
|
94
96
|
#binding;
|
|
97
|
+
#closeFd;
|
|
95
98
|
#parentFd;
|
|
99
|
+
#closeParentFd;
|
|
96
100
|
#directory;
|
|
97
101
|
#portableNames;
|
|
98
102
|
#publishedMode;
|
|
@@ -102,26 +106,29 @@ class NativeStagedFile {
|
|
|
102
106
|
#state = { status: "open", publication: NOT_PUBLISHED };
|
|
103
107
|
#receipt;
|
|
104
108
|
#rejectFinalSymlink = false;
|
|
105
|
-
constructor(binding, parentFd, directory, portableNames, publishedMode, sync, assertBeforeMutation) {
|
|
109
|
+
constructor(binding, parentFd, closeParentFd, directory, portableNames, publishedMode, sync, assertBeforeMutation) {
|
|
110
|
+
this.#closeFd = captureNativeFdClose(binding);
|
|
106
111
|
this.#binding = binding;
|
|
107
112
|
this.#parentFd = parentFd;
|
|
113
|
+
this.#closeParentFd = closeParentFd;
|
|
108
114
|
this.#directory = directory;
|
|
109
115
|
this.#portableNames = portableNames;
|
|
110
116
|
this.#publishedMode = publishedMode;
|
|
111
117
|
this.#sync = sync;
|
|
112
118
|
this.#assertBeforeMutation = assertBeforeMutation;
|
|
113
119
|
}
|
|
114
|
-
//
|
|
115
|
-
|
|
120
|
+
// Public staging supplies a Node parent; pinned writes supply a native parent.
|
|
121
|
+
// The supplied closer owns parentFd even on construction or preparation failure.
|
|
122
|
+
static async create(binding, parentFd, closeParentFd, directory, input, mode, maxBytes,
|
|
116
123
|
// Public staging uses portable names; existing POSIX writes accept literal names.
|
|
117
124
|
portableNames = true, sync = true, assertBeforeMutation) {
|
|
118
125
|
let staged;
|
|
119
126
|
try {
|
|
120
|
-
staged = new NativeStagedFile(binding, parentFd, directory, portableNames, mode, sync, assertBeforeMutation);
|
|
127
|
+
staged = new NativeStagedFile(binding, parentFd, closeParentFd, directory, portableNames, mode, sync, assertBeforeMutation);
|
|
121
128
|
}
|
|
122
129
|
catch (error) {
|
|
123
130
|
try {
|
|
124
|
-
|
|
131
|
+
closeParentFd(parentFd);
|
|
125
132
|
}
|
|
126
133
|
catch (closeError) {
|
|
127
134
|
throw new AggregateError([error, closeError], "staged owner construction and close failed");
|
|
@@ -131,12 +138,12 @@ class NativeStagedFile {
|
|
|
131
138
|
await staged.#prepare(input, maxBytes);
|
|
132
139
|
return staged;
|
|
133
140
|
}
|
|
134
|
-
static async write(binding, parentFd, directory, params, parentGuard) {
|
|
141
|
+
static async write(binding, parentFd, closeParentFd, directory, params, parentGuard) {
|
|
135
142
|
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
136
143
|
try {
|
|
137
144
|
// This owner never escapes. Only the internal verifier borrows its fd;
|
|
138
145
|
// public descriptor methods remain await-free and cannot race disposal.
|
|
139
|
-
const staged = __addDisposableResource(env_1, await NativeStagedFile.create(binding, parentFd, directory, params.input, params.mode, params.maxBytes, false, params.sync, params.assertBeforeMutation), true);
|
|
146
|
+
const staged = __addDisposableResource(env_1, await NativeStagedFile.create(binding, parentFd, closeParentFd, directory, params.input, params.mode, params.maxBytes, false, params.sync, params.assertBeforeMutation), true);
|
|
140
147
|
staged.#rejectFinalSymlink = params.rejectFinalSymlink === true;
|
|
141
148
|
if (params.input.kind === "file")
|
|
142
149
|
await params.input.verifySource();
|
|
@@ -336,12 +343,15 @@ class NativeStagedFile {
|
|
|
336
343
|
}
|
|
337
344
|
}
|
|
338
345
|
let resources = "closed";
|
|
339
|
-
for (const fd of [
|
|
346
|
+
for (const [fd, closeFd] of [
|
|
347
|
+
[state.fileFd, this.#closeFd],
|
|
348
|
+
[this.#parentFd, this.#closeParentFd],
|
|
349
|
+
]) {
|
|
340
350
|
if (fd === undefined) {
|
|
341
351
|
continue;
|
|
342
352
|
}
|
|
343
353
|
try {
|
|
344
|
-
|
|
354
|
+
closeFd(fd);
|
|
345
355
|
}
|
|
346
356
|
catch (error) {
|
|
347
357
|
resources = "close-failed";
|
|
@@ -385,5 +395,5 @@ export async function stageFileInDirectory(options) {
|
|
|
385
395
|
const input = { kind: "buffer", data: Buffer.from(options.content) };
|
|
386
396
|
const mode = options.mode ?? 0o600;
|
|
387
397
|
const parent = openStagedDirectory(options.directory);
|
|
388
|
-
return await createNativeStage(binding, parent.fd, parent.receipt, input, mode);
|
|
398
|
+
return await createNativeStage(binding, parent.fd, fs.closeSync, parent.receipt, input, mode);
|
|
389
399
|
}
|
package/dist/native.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type NativeBinding } from "./native-binding.js";
|
|
2
2
|
export type { NativeBinding } from "./native-binding.js";
|
|
3
3
|
export declare function __loadBundledNativeForTest(): NativeBinding;
|
|
4
4
|
export declare function __nativeLoaderDetectorsForTest(): {
|
package/dist/native.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"native.d.ts","sourceRoot":"","sources":["../src/native.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"native.d.ts","sourceRoot":"","sources":["../src/native.ts"],"names":[],"mappings":"AAQA,OAAO,EAAwB,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAG/E,YAAY,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAwKzD,wBAAgB,0BAA0B,IAAI,aAAa,CAE1D;AAED,wBAAgB,8BAA8B,IAAI;IAChD,MAAM,EAAE,OAAO,GAAG,SAAS,CAAC;IAC5B,UAAU,EAAE,OAAO,GAAG,SAAS,CAAC;IAChC,cAAc,EAAE,OAAO,GAAG,SAAS,CAAC;CACrC,CAMA;AAED,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,CAAC,QAAQ,EACzB,IAAI,EAAE,MAAM,EACZ,IAAI,UAAQ,GACX,MAAM,GAAG,SAAS,CAEpB;AAED,wBAAgB,gBAAgB,IAAI,aAAa,GAAG,SAAS,CAoB5D;AAED,wBAAgB,oBAAoB,IAAI,aAAa,CAQpD;AAED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,MAAM,aAAa,GAAG,IAAI,CAK1E;AAED,wBAAgB,0BAA0B,IAAI,IAAI,CAKjD"}
|
package/dist/native.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { closeSync, openSync, readSync, readdirSync, } from "node:fs";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import { FsSafeError } from "./errors.js";
|
|
4
|
+
import { captureNativeFdClose } from "./native-binding.js";
|
|
4
5
|
import { getFsSafeNativeConfig } from "./native-config.js";
|
|
5
6
|
const require = createRequire(import.meta.url);
|
|
6
7
|
let binding;
|
|
@@ -144,7 +145,9 @@ function loadBundledBinding() {
|
|
|
144
145
|
if (!target) {
|
|
145
146
|
throw new Error(`Unsupported OS or architecture: ${process.platform}-${process.arch}`);
|
|
146
147
|
}
|
|
147
|
-
|
|
148
|
+
const loaded = require(nativePackageForTarget(target));
|
|
149
|
+
captureNativeFdClose(loaded);
|
|
150
|
+
return loaded;
|
|
148
151
|
}
|
|
149
152
|
export function __loadBundledNativeForTest() {
|
|
150
153
|
return loadBundledBinding();
|
|
@@ -166,7 +169,9 @@ export function getNativeBinding() {
|
|
|
166
169
|
if (!attempted) {
|
|
167
170
|
attempted = true;
|
|
168
171
|
try {
|
|
169
|
-
|
|
172
|
+
const loaded = loadBinding();
|
|
173
|
+
captureNativeFdClose(loaded);
|
|
174
|
+
binding = loaded;
|
|
170
175
|
}
|
|
171
176
|
catch (error) {
|
|
172
177
|
loadError = error;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"publish-file.d.ts","sourceRoot":"","sources":["../src/publish-file.ts"],"names":[],"mappings":"AACA,OAAe,EAAoB,KAAK,KAAK,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"publish-file.d.ts","sourceRoot":"","sources":["../src/publish-file.ts"],"names":[],"mappings":"AACA,OAAe,EAAoB,KAAK,KAAK,EAAE,MAAM,SAAS,CAAC;AAG/D,OAAO,EAEL,KAAK,gBAAgB,EACrB,KAAK,oBAAoB,EAC1B,MAAM,2BAA2B,CAAC;AAGnC,OAAO,EAGL,KAAK,gBAAgB,EACtB,MAAM,oBAAoB,CAAC;AAK5B,OAAO,EAML,KAAK,qCAAqC,EAC3C,MAAM,2BAA2B,CAAC;AAKnC,YAAY,EACV,2BAA2B,EAC3B,wCAAwC,EACxC,kCAAkC,EAClC,gCAAgC,EAChC,qCAAqC,GACtC,MAAM,2BAA2B,CAAC;AAEnC,MAAM,MAAM,4BAA4B,GAAG,cAAc,GAAG,eAAe,GAAG,kBAAkB,CAAC;AAEjG,MAAM,MAAM,0BAA0B,GAAG;IACvC,MAAM,EAAE,UAAU,GAAG,gBAAgB,GAAG,kBAAkB,CAAC;IAC3D,QAAQ,EAAE,KAAK,CAAC;IAChB,aAAa,EAAE,oBAAoB,CAAC;CACrC,CAAC;AAIF,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAE/D;AA0ND,wBAAsB,oBAAoB,CAAC,MAAM,EAAE;IACjD,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,sBAAsB,CAAC,EAAE,gBAAgB,CAAC;IAC1C,QAAQ,EAAE,4BAA4B,CAAC;IACvC,aAAa,CAAC,EAAE,qCAAqC,CAAC;IACtD,aAAa,CAAC,EAAE,gBAAgB,CAAC;CAClC,GAAG,OAAO,CAAC,0BAA0B,CAAC,CA6NtC"}
|
package/dist/publish-file.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import fsSync, {} from "node:fs";
|
|
3
|
-
import fs from "node:fs/promises";
|
|
3
|
+
import fs, {} from "node:fs/promises";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { pinDirectory, } from "./directory-durability.js";
|
|
6
6
|
import { FsSafeError } from "./errors.js";
|
|
@@ -8,6 +8,7 @@ import { hashFileHandle } from "./file-hash.js";
|
|
|
8
8
|
import { sameFileIdentity, sameFileIdentityForCleanup, } from "./file-identity.js";
|
|
9
9
|
import { syncFileBestEffortSync } from "./file-sync.js";
|
|
10
10
|
import { getNativeBinding, requireNativeBinding } from "./native.js";
|
|
11
|
+
import { captureNativeFdClose } from "./native-binding.js";
|
|
11
12
|
import { resolveReadOpenFlags } from "./read-open-flags.js";
|
|
12
13
|
import { directorySyncFailure, publicationFailure, rememberCreatedTarget, } from "./publish-file-failure.js";
|
|
13
14
|
import { admitStandalonePublicationPath } from "./standalone-publication-path.js";
|
|
@@ -54,6 +55,7 @@ async function assertPinnedSourceCurrent(params) {
|
|
|
54
55
|
}
|
|
55
56
|
async function copyPinnedSource(params) {
|
|
56
57
|
if (params.native && params.targetNativeParent) {
|
|
58
|
+
const closeFd = captureNativeFdClose(params.native);
|
|
57
59
|
for (const method of ["clone", "copy-file-range"]) {
|
|
58
60
|
let nativeFd;
|
|
59
61
|
try {
|
|
@@ -95,7 +97,7 @@ async function copyPinnedSource(params) {
|
|
|
95
97
|
const hashed = await hashFileHandle(target, params.native);
|
|
96
98
|
const completedFd = nativeFd;
|
|
97
99
|
nativeFd = undefined;
|
|
98
|
-
|
|
100
|
+
closeFd(completedFd);
|
|
99
101
|
return {
|
|
100
102
|
handle: target,
|
|
101
103
|
exactIdentity: opened,
|
|
@@ -109,7 +111,7 @@ async function copyPinnedSource(params) {
|
|
|
109
111
|
}
|
|
110
112
|
finally {
|
|
111
113
|
if (nativeFd !== undefined)
|
|
112
|
-
|
|
114
|
+
closeFd(nativeFd);
|
|
113
115
|
}
|
|
114
116
|
}
|
|
115
117
|
}
|
package/docs/native-helper.md
CHANGED
|
@@ -43,6 +43,19 @@ when admitting a temp workspace. Containment and identity checks stay intact.
|
|
|
43
43
|
|
|
44
44
|
Configure the mode once during startup. Loading is lazy and cached; changing from `auto` to `require` after a failed load changes failure policy but does not repeatedly probe the binary.
|
|
45
45
|
|
|
46
|
+
Native-created descriptors retain their originating native close operation through
|
|
47
|
+
normal and error cleanup, including later mode changes. Node-created roots and
|
|
48
|
+
directory handles keep Node's close operation, and borrowed handles keep their
|
|
49
|
+
caller-owned lifetime. This preserves Node worker-thread descriptor tracking
|
|
50
|
+
without unmanaged-descriptor warnings. A helper missing native close support is
|
|
51
|
+
unavailable before descriptor allocation.
|
|
52
|
+
|
|
53
|
+
Close retained native resources and let in-flight operations finish before
|
|
54
|
+
forcibly terminating a worker. Native-created descriptors are not registered
|
|
55
|
+
with Node's automatic worker-exit cleanup; `Worker.terminate()` can leave them
|
|
56
|
+
open until process exit. The native close operation handles explicit cleanup,
|
|
57
|
+
not forced worker termination.
|
|
58
|
+
|
|
46
59
|
[`tempWorkspace()` and its scoped/sync variants](temp.md#private-temp-workspaces)
|
|
47
60
|
remain available in every mode. Their default compatible cleanup uses guarded
|
|
48
61
|
JavaScript quarantine when owned native tree removal is unavailable.
|
|
@@ -84,7 +97,7 @@ normalization, and the decision to fall back.
|
|
|
84
97
|
|
|
85
98
|
- Linux uses `openat2` with `RESOLVE_BENEATH | RESOLVE_NO_MAGICLINKS`, `renameat`, and `renameat2(RENAME_NOREPLACE)`. Owned-tree cleanup enumerates and unlinks through retained directory descriptors and rejects device crossings.
|
|
86
99
|
- macOS 15.4 and newer prefer `O_RESOLVE_BENEATH`; older kernels resolve components with `O_NOFOLLOW` and restart in-root symlinks from the pinned root descriptor. Both routes use an `F_GETPATH` post-open escape detector and report `best-effort` because directory rename races are not atomic with that check. Publication uses `renameat` for replacement and `renameatx_np(RENAME_EXCL)` for no-replace; owned-tree cleanup uses descriptor-relative `openat`/`unlinkat`.
|
|
87
|
-
- Windows uses handle-relative `NtCreateFile`, rejects reparse points during root-bounded traversal, uses `FileRenameInfoEx` with replacement selected explicitly by the TypeScript policy layer, and deletes owned trees through exact opened handles with `FileDispositionInfoEx`; symlink/reparse entries in owned trees are removed as leaves and never traversed. Descriptors crossing N-API are converted only by the host executable's paired `uv_get_osfhandle` and `uv_open_osfhandle` exports. A runtime without both exports is unsupported for these native operations; the binding never guesses a raw HANDLE or uses a foreign CRT descriptor table.
|
|
100
|
+
- Windows uses handle-relative `NtCreateFile`, rejects reparse points during root-bounded traversal, uses `FileRenameInfoEx` with replacement selected explicitly by the TypeScript policy layer, and deletes owned trees through exact opened handles with `FileDispositionInfoEx`; symlink/reparse entries in owned trees are removed as leaves and never traversed. Descriptors crossing N-API are converted only by the host executable's paired `uv_get_osfhandle` and `uv_open_osfhandle` exports. A runtime without both exports is unsupported for these native operations; the binding never guesses a raw HANDLE or uses a foreign CRT descriptor table. Descriptor-producing operations also require that same host's synchronous libuv close and request-management APIs before exporting an owned descriptor.
|
|
88
101
|
|
|
89
102
|
Native primitives back create-only and replacing pinned writes, no-clobber
|
|
90
103
|
`Root.move()`, async sidecar creation, guarded publication, archive acceleration,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/fs-safe",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.1",
|
|
4
4
|
"description": "Capability-style filesystem roots for Node.js apps that handle untrusted relative paths.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"filesystem",
|
|
@@ -167,13 +167,13 @@
|
|
|
167
167
|
"archive:producer-smoke": "node scripts/archive-producer-smoke.mjs"
|
|
168
168
|
},
|
|
169
169
|
"optionalDependencies": {
|
|
170
|
-
"@openclaw/fs-safe-darwin-arm64": "0.13.
|
|
171
|
-
"@openclaw/fs-safe-darwin-x64": "0.13.
|
|
172
|
-
"@openclaw/fs-safe-linux-arm64-gnu": "0.13.
|
|
173
|
-
"@openclaw/fs-safe-linux-arm64-musl": "0.13.
|
|
174
|
-
"@openclaw/fs-safe-linux-x64-gnu": "0.13.
|
|
175
|
-
"@openclaw/fs-safe-linux-x64-musl": "0.13.
|
|
176
|
-
"@openclaw/fs-safe-win32-x64-msvc": "0.13.
|
|
170
|
+
"@openclaw/fs-safe-darwin-arm64": "0.13.1",
|
|
171
|
+
"@openclaw/fs-safe-darwin-x64": "0.13.1",
|
|
172
|
+
"@openclaw/fs-safe-linux-arm64-gnu": "0.13.1",
|
|
173
|
+
"@openclaw/fs-safe-linux-arm64-musl": "0.13.1",
|
|
174
|
+
"@openclaw/fs-safe-linux-x64-gnu": "0.13.1",
|
|
175
|
+
"@openclaw/fs-safe-linux-x64-musl": "0.13.1",
|
|
176
|
+
"@openclaw/fs-safe-win32-x64-msvc": "0.13.1",
|
|
177
177
|
"jszip": "^3.10.2"
|
|
178
178
|
},
|
|
179
179
|
"devDependencies": {
|