@openclaw/fs-safe 0.5.6 → 0.6.0

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.
Files changed (145) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/README.md +16 -10
  3. package/dist/advanced.d.ts +2 -1
  4. package/dist/advanced.d.ts.map +1 -1
  5. package/dist/advanced.js +1 -0
  6. package/dist/archive-crc32.d.ts +2 -0
  7. package/dist/archive-crc32.d.ts.map +1 -0
  8. package/dist/archive-crc32.js +14 -0
  9. package/dist/archive-kind.js +2 -2
  10. package/dist/archive-native.d.ts.map +1 -1
  11. package/dist/archive-native.js +7 -0
  12. package/dist/archive-read.d.ts.map +1 -1
  13. package/dist/archive-read.js +24 -5
  14. package/dist/archive-tar-meta.d.ts.map +1 -1
  15. package/dist/archive-tar-meta.js +53 -6
  16. package/dist/archive-tar-pax.d.ts +8 -0
  17. package/dist/archive-tar-pax.d.ts.map +1 -0
  18. package/dist/archive-tar-pax.js +100 -0
  19. package/dist/archive-tar-runtime.d.ts +1 -0
  20. package/dist/archive-tar-runtime.d.ts.map +1 -1
  21. package/dist/archive-zip-admission.d.ts +7 -0
  22. package/dist/archive-zip-admission.d.ts.map +1 -0
  23. package/dist/archive-zip-admission.js +60 -0
  24. package/dist/archive-zip-count.d.ts +2 -0
  25. package/dist/archive-zip-count.d.ts.map +1 -0
  26. package/dist/archive-zip-count.js +140 -0
  27. package/dist/archive-zip-directory.d.ts +8 -0
  28. package/dist/archive-zip-directory.d.ts.map +1 -0
  29. package/dist/archive-zip-directory.js +224 -0
  30. package/dist/archive-zip-integrity.d.ts.map +1 -1
  31. package/dist/archive-zip-integrity.js +1 -14
  32. package/dist/archive-zip-names.d.ts +12 -0
  33. package/dist/archive-zip-names.d.ts.map +1 -0
  34. package/dist/archive-zip-names.js +99 -0
  35. package/dist/archive-zip-preflight.d.ts +1 -1
  36. package/dist/archive-zip-preflight.d.ts.map +1 -1
  37. package/dist/archive-zip-preflight.js +8 -145
  38. package/dist/archive.d.ts.map +1 -1
  39. package/dist/archive.js +32 -23
  40. package/dist/file-hash.d.ts.map +1 -1
  41. package/dist/file-hash.js +25 -19
  42. package/dist/file-lock-sync.d.ts.map +1 -1
  43. package/dist/file-lock-sync.js +19 -7
  44. package/dist/native-binding.d.ts +3 -0
  45. package/dist/native-binding.d.ts.map +1 -1
  46. package/dist/native-operations.d.ts +4 -1
  47. package/dist/native-operations.d.ts.map +1 -1
  48. package/dist/native-operations.js +22 -6
  49. package/dist/native-pinned-write-windows.d.ts +8 -0
  50. package/dist/native-pinned-write-windows.d.ts.map +1 -0
  51. package/dist/native-pinned-write-windows.js +92 -0
  52. package/dist/native-pinned-write.d.ts.map +1 -1
  53. package/dist/native-pinned-write.js +136 -127
  54. package/dist/native-staged-file.d.ts +24 -0
  55. package/dist/native-staged-file.d.ts.map +1 -0
  56. package/dist/native-staged-file.js +337 -0
  57. package/dist/native.d.ts.map +1 -1
  58. package/dist/native.js +4 -4
  59. package/dist/opened-realpath.d.ts +2 -0
  60. package/dist/opened-realpath.d.ts.map +1 -1
  61. package/dist/opened-realpath.js +12 -7
  62. package/dist/owner-dacl.d.ts.map +1 -1
  63. package/dist/owner-dacl.js +2 -1
  64. package/dist/permission-exec.d.ts +19 -0
  65. package/dist/permission-exec.d.ts.map +1 -1
  66. package/dist/permission-exec.js +57 -11
  67. package/dist/permissions-public.d.ts +1 -1
  68. package/dist/permissions-public.d.ts.map +1 -1
  69. package/dist/permissions-windows.d.ts +3 -0
  70. package/dist/permissions-windows.d.ts.map +1 -1
  71. package/dist/permissions-windows.js +16 -5
  72. package/dist/permissions.d.ts +5 -0
  73. package/dist/permissions.d.ts.map +1 -1
  74. package/dist/pinned-write.d.ts +6 -0
  75. package/dist/pinned-write.d.ts.map +1 -1
  76. package/dist/pinned-write.js +19 -29
  77. package/dist/private-directory.d.ts.map +1 -1
  78. package/dist/private-directory.js +3 -2
  79. package/dist/root-impl.d.ts.map +1 -1
  80. package/dist/root-impl.js +78 -72
  81. package/dist/root-paths.d.ts +11 -14
  82. package/dist/root-paths.d.ts.map +1 -1
  83. package/dist/root-paths.js +36 -27
  84. package/dist/root-write-verification.d.ts +11 -0
  85. package/dist/root-write-verification.d.ts.map +1 -0
  86. package/dist/root-write-verification.js +91 -0
  87. package/dist/secret-file.d.ts +1 -6
  88. package/dist/secret-file.d.ts.map +1 -1
  89. package/dist/secret-file.js +43 -117
  90. package/dist/secret-read-async.d.ts +1 -1
  91. package/dist/secret-read-async.d.ts.map +1 -1
  92. package/dist/secret-read-async.js +45 -67
  93. package/dist/secret-read-policy.d.ts +13 -0
  94. package/dist/secret-read-policy.d.ts.map +1 -0
  95. package/dist/secret-read-policy.js +28 -0
  96. package/dist/secret.d.ts +2 -1
  97. package/dist/secret.d.ts.map +1 -1
  98. package/dist/secret.js +2 -1
  99. package/dist/secure-file.d.ts.map +1 -1
  100. package/dist/secure-file.js +25 -16
  101. package/dist/staged-directory.d.ts +16 -0
  102. package/dist/staged-directory.d.ts.map +1 -0
  103. package/dist/staged-directory.js +60 -0
  104. package/dist/staged-file-types.d.ts +56 -0
  105. package/dist/staged-file-types.d.ts.map +1 -0
  106. package/dist/staged-file-types.js +1 -0
  107. package/dist/staged-file.d.ts +10 -0
  108. package/dist/staged-file.d.ts.map +1 -0
  109. package/dist/staged-file.js +15 -0
  110. package/dist/strict-file-identity.d.ts +6 -0
  111. package/dist/strict-file-identity.d.ts.map +1 -0
  112. package/dist/strict-file-identity.js +48 -0
  113. package/dist/windows-owner.d.ts +3 -0
  114. package/dist/windows-owner.d.ts.map +1 -1
  115. package/dist/windows-owner.js +10 -2
  116. package/docs/advanced.md +11 -1
  117. package/docs/archive.md +62 -15
  118. package/docs/config.md +7 -0
  119. package/docs/contributing.md +45 -7
  120. package/docs/durability.md +17 -4
  121. package/docs/errors.md +16 -1
  122. package/docs/index.md +3 -1
  123. package/docs/install.md +15 -7
  124. package/docs/migrating-to-0.5.md +7 -7
  125. package/docs/migrating-to-0.6.md +43 -0
  126. package/docs/native-helper.md +15 -5
  127. package/docs/native.md +40 -11
  128. package/docs/path-scope.md +28 -2
  129. package/docs/permissions.md +13 -2
  130. package/docs/root.md +1 -1
  131. package/docs/secret-file.md +8 -0
  132. package/docs/secure-file.md +17 -1
  133. package/docs/security-model.md +15 -1
  134. package/docs/sidecar-lock.md +11 -1
  135. package/docs/staged-file.md +178 -0
  136. package/docs/testing.md +1 -1
  137. package/docs/writing.md +19 -0
  138. package/package.json +15 -8
  139. package/dist/native/darwin-arm64/fs-safe-native.node +0 -0
  140. package/dist/native/darwin-x64/fs-safe-native.node +0 -0
  141. package/dist/native/linux-arm64-gnu/fs-safe-native.node +0 -0
  142. package/dist/native/linux-arm64-musl/fs-safe-native.node +0 -0
  143. package/dist/native/linux-x64-gnu/fs-safe-native.node +0 -0
  144. package/dist/native/linux-x64-musl/fs-safe-native.node +0 -0
  145. package/dist/native/win32-x64-msvc/fs-safe-native.node +0 -0
@@ -0,0 +1,92 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import fsSync, {} from "node:fs";
3
+ import { FsSafeError } from "./errors.js";
4
+ import { nativeOpenFlags, removeNativeCreatedFileIfStillPinned, syncNativeFileBestEffort, writeNativeInput, } from "./native-operations.js";
5
+ export function sameNativeIdentity(left, right) {
6
+ return left.dev === right.dev && left.ino === right.ino;
7
+ }
8
+ function closeWriteFd(fd) {
9
+ if (fd === undefined)
10
+ return;
11
+ try {
12
+ fsSync.closeSync(fd);
13
+ }
14
+ catch (error) {
15
+ return error;
16
+ }
17
+ }
18
+ export async function runPinnedWriteWindows(binding, params, root, parentFd, parentGuard) {
19
+ const parentPath = parentGuard.realPath;
20
+ let tempFd;
21
+ let targetFd;
22
+ let tempIdentity;
23
+ let tempName = "";
24
+ let renamed = false;
25
+ let completed = false;
26
+ try {
27
+ tempName = `.${params.basename}.${randomUUID()}.native.tmp`;
28
+ tempFd = binding.openBeneath(parentFd, tempName, nativeOpenFlags(fsSync.constants.O_WRONLY | fsSync.constants.O_CREAT | fsSync.constants.O_EXCL)).fd;
29
+ tempIdentity = fsSync.fstatSync(tempFd);
30
+ const verificationIdentity = fsSync.fstatSync(tempFd, { bigint: true });
31
+ // Creation is requested at 0600 in the binding, but a restrictive umask
32
+ // can remove owner access. Keep the unpublished inode private and
33
+ // reopenable until the published name has been identity-fenced.
34
+ fsSync.fchmodSync(tempFd, 0o600);
35
+ await writeNativeInput(tempFd, params.input, params.maxBytes);
36
+ syncNativeFileBestEffort(tempFd);
37
+ if (params.overwrite === false) {
38
+ binding.renameNoReplace(parentFd, tempName, parentFd, params.basename);
39
+ }
40
+ else {
41
+ binding.renameReplace(parentFd, tempName, parentFd, params.basename);
42
+ }
43
+ renamed = true;
44
+ targetFd = binding.openBeneath(parentFd, params.basename, nativeOpenFlags(fsSync.constants.O_RDONLY)).fd;
45
+ const targetIdentity = binding.fstatIdentity(targetFd);
46
+ if (!targetIdentity.isFile || !sameNativeIdentity(tempIdentity, targetIdentity)) {
47
+ throw new FsSafeError("path-mismatch", "native write target changed after rename");
48
+ }
49
+ // Native exclusive creation starts at 0600. Apply the requested mode only
50
+ // after reopening and fencing the published name, both so mode 000 stays
51
+ // verifiable and so broader modes are never exposed before that fence.
52
+ try {
53
+ fsSync.fchmodSync(targetFd, params.mode);
54
+ syncNativeFileBestEffort(targetFd);
55
+ }
56
+ catch (error) {
57
+ closeWriteFd(targetFd);
58
+ targetFd = undefined;
59
+ removeNativeCreatedFileIfStillPinned({
60
+ binding,
61
+ parentPath,
62
+ parentFd,
63
+ basename: params.basename,
64
+ created: tempIdentity,
65
+ });
66
+ throw error;
67
+ }
68
+ syncNativeFileBestEffort(parentFd);
69
+ // Verification follows publication and final chmod, outside rollback handling.
70
+ await params.verifyPublished?.(targetFd, verificationIdentity, parentGuard);
71
+ completed = true;
72
+ return { dev: targetIdentity.dev, ino: targetIdentity.ino };
73
+ }
74
+ finally {
75
+ const targetCloseError = closeWriteFd(targetFd);
76
+ const tempCloseError = closeWriteFd(tempFd);
77
+ if (!renamed) {
78
+ removeNativeCreatedFileIfStillPinned({
79
+ binding,
80
+ parentPath,
81
+ parentFd,
82
+ basename: tempName,
83
+ created: tempIdentity,
84
+ });
85
+ }
86
+ const parentCloseError = closeWriteFd(parentFd);
87
+ await root.close().catch(() => undefined);
88
+ const closeError = targetCloseError ?? tempCloseError ?? parentCloseError;
89
+ if (completed && closeError !== undefined)
90
+ throw closeError;
91
+ }
92
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"native-pinned-write.d.ts","sourceRoot":"","sources":["../src/native-pinned-write.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAM3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,KAAK,EAAoB,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAiD7E,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,aAAa,EACtB,MAAM,EAAE,iBAAiB,GACxB,OAAO,CAAC,gBAAgB,CAAC,CAsH3B"}
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;AAG3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAG3D,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAmFvH"}
@@ -1,142 +1,151 @@
1
- import { randomUUID } from "node:crypto";
2
- import fsSync, {} from "node:fs";
1
+ var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
2
+ if (value !== null && value !== void 0) {
3
+ if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
4
+ var dispose, inner;
5
+ if (async) {
6
+ if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
7
+ dispose = value[Symbol.asyncDispose];
8
+ }
9
+ if (dispose === void 0) {
10
+ if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
11
+ dispose = value[Symbol.dispose];
12
+ if (async) inner = dispose;
13
+ }
14
+ if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
15
+ if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
16
+ env.stack.push({ value: value, dispose: dispose, async: async });
17
+ }
18
+ else if (async) {
19
+ env.stack.push({ async: true });
20
+ }
21
+ return value;
22
+ };
23
+ var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
24
+ return function (env) {
25
+ function fail(e) {
26
+ env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
27
+ env.hasError = true;
28
+ }
29
+ var r, s = 0;
30
+ function next() {
31
+ while (r = env.stack.pop()) {
32
+ try {
33
+ if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
34
+ if (r.dispose) {
35
+ var result = r.dispose.call(r.value);
36
+ if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
37
+ }
38
+ else s |= 1;
39
+ }
40
+ catch (e) {
41
+ fail(e);
42
+ }
43
+ }
44
+ if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
45
+ if (env.hasError) throw env.error;
46
+ }
47
+ return next();
48
+ };
49
+ })(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
50
+ var e = new Error(message);
51
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
52
+ });
53
+ import fsSync from "node:fs";
3
54
  import fs from "node:fs/promises";
4
55
  import path from "node:path";
5
56
  import { FsSafeError } from "./errors.js";
6
- import { removeNativeCreatedFileIfStillPinned, syncNativeFileBestEffort, writeNativeFd, } from "./native-operations.js";
7
- function assertWithinMaxBytes(bytes, maxBytes) {
8
- if (maxBytes !== undefined && bytes > maxBytes) {
9
- throw new FsSafeError("too-large", `file exceeds limit of ${maxBytes} bytes (got at least ${bytes})`);
10
- }
11
- }
12
- async function writeNativeInput(fd, input, maxBytes) {
13
- if (input.kind === "buffer") {
14
- const data = typeof input.data === "string"
15
- ? Buffer.from(input.data, input.encoding ?? "utf8")
16
- : Buffer.from(input.data);
17
- assertWithinMaxBytes(data.byteLength, maxBytes);
18
- writeNativeFd(fd, data);
19
- return;
20
- }
21
- let bytes = 0;
22
- for await (const chunk of input.stream) {
23
- const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
24
- bytes += buffer.byteLength;
25
- assertWithinMaxBytes(bytes, maxBytes);
26
- writeNativeFd(fd, buffer);
27
- }
28
- }
29
- function nativeOpenFlags(flags) {
30
- const closeOnExec = fsSync.constants.O_CLOEXEC;
31
- return (flags |
32
- (closeOnExec ?? 0) |
33
- (typeof fsSync.constants.O_NOFOLLOW === "number" ? fsSync.constants.O_NOFOLLOW : 0));
34
- }
35
- function sameNativeIdentity(left, right) {
36
- return left.dev === right.dev && left.ino === right.ino;
37
- }
57
+ import { runPinnedWriteWindows, sameNativeIdentity } from "./native-pinned-write-windows.js";
58
+ import { assertNativeStaging, writeNativeStage } from "./native-staged-file.js";
59
+ import { describeStagedDirectory, exactIdentityMatches } from "./staged-directory.js";
38
60
  export async function runPinnedWriteNative(binding, params) {
39
- const root = await fs.open(params.rootPath, fsSync.constants.O_RDONLY |
40
- (typeof fsSync.constants.O_DIRECTORY === "number" ? fsSync.constants.O_DIRECTORY : 0));
41
- let parentFd;
42
- let tempFd;
43
- let targetFd;
44
- let tempIdentity;
45
- let parentPath = params.rootPath;
46
- const tempName = `.${params.basename}.${randomUUID()}.native.tmp`;
47
- let renamed = false;
61
+ const env_1 = { stack: [], error: void 0, hasError: false };
48
62
  try {
49
- const rootIdentity = binding.fstatIdentity(root.fd);
50
- if (params.rootIdentity && !sameNativeIdentity(params.rootIdentity, rootIdentity)) {
51
- throw new FsSafeError("path-mismatch", "root path changed during native write");
52
- }
53
- if (params.mkdir) {
54
- binding.mkdirBeneath(root.fd, params.relativeParentPath, 0o777);
55
- }
56
- const parentFlags = fsSync.constants.O_RDONLY |
57
- (typeof fsSync.constants.O_DIRECTORY === "number" ? fsSync.constants.O_DIRECTORY : 0);
58
- parentFd = binding.openBeneath(root.fd, params.relativeParentPath, parentFlags).fd;
59
- parentPath = await fs.realpath(params.relativeParentPath
60
- ? path.join(params.rootPath, ...params.relativeParentPath.split("/"))
61
- : params.rootPath);
62
- const parentPathStat = await fs.lstat(parentPath);
63
- const parentIdentity = binding.fstatIdentity(parentFd);
64
- if (parentPathStat.isSymbolicLink() ||
65
- !sameNativeIdentity(parentPathStat, parentIdentity)) {
66
- throw new FsSafeError("path-mismatch", "native write parent changed during resolution");
63
+ const windows = process.platform === "win32";
64
+ if (!windows) {
65
+ assertNativeStaging(binding);
67
66
  }
68
- if (params.overwrite === false) {
69
- try {
70
- await fs.lstat(path.join(parentPath, params.basename));
71
- throw Object.assign(new Error("destination already exists"), { code: "EEXIST" });
67
+ const directoryFlags = fsSync.constants.O_RDONLY | (fsSync.constants.O_DIRECTORY ?? 0);
68
+ const root = await fs.open(params.rootPath, directoryFlags);
69
+ const posixRoot = __addDisposableResource(env_1, windows ? undefined : root, true);
70
+ let parentFd;
71
+ let windowsOwnsDirectories = false;
72
+ // Until stage construction takes ownership, even admission failures must close
73
+ // the raw POSIX parent. Disposal preserves both admission and close failures.
74
+ const parentGuard = __addDisposableResource(env_1, {
75
+ [Symbol.dispose]() {
76
+ if (!windows && parentFd !== undefined) {
77
+ fsSync.closeSync(parentFd);
78
+ }
79
+ },
80
+ }, false);
81
+ try {
82
+ let rootMatches;
83
+ if (windows) {
84
+ const identity = binding.fstatIdentity(root.fd);
85
+ rootMatches = !params.rootIdentity || sameNativeIdentity(params.rootIdentity, identity);
72
86
  }
73
- catch (error) {
74
- if (error.code !== "ENOENT") {
75
- throw error;
87
+ else {
88
+ const identity = fsSync.fstatSync(root.fd, { bigint: true });
89
+ rootMatches = !params.rootIdentity || exactIdentityMatches(params.rootIdentity, identity);
90
+ }
91
+ if (!rootMatches) {
92
+ throw new FsSafeError("path-mismatch", "root path changed during native write");
93
+ }
94
+ if (params.mkdir) {
95
+ binding.mkdirBeneath(root.fd, params.relativeParentPath, 0o777);
96
+ }
97
+ parentFd = binding.openBeneath(root.fd, params.relativeParentPath, directoryFlags).fd;
98
+ const parentPath = await fs.realpath(params.relativeParentPath
99
+ ? path.join(params.rootPath, ...params.relativeParentPath.split("/"))
100
+ : params.rootPath);
101
+ const directory = windows ? undefined : describeStagedDirectory(parentFd, parentPath);
102
+ const parentPathStat = await fs.lstat(parentPath);
103
+ if (windows) {
104
+ const parentIdentity = binding.fstatIdentity(parentFd);
105
+ if (parentPathStat.isSymbolicLink() || !sameNativeIdentity(parentPathStat, parentIdentity)) {
106
+ throw new FsSafeError("path-mismatch", "native write parent changed during resolution");
76
107
  }
77
108
  }
109
+ else if (parentPathStat.isSymbolicLink() || !exactIdentityMatches(parentPathStat, directory.identity)) {
110
+ throw new FsSafeError("path-mismatch", "native write parent changed during resolution");
111
+ }
112
+ const verificationGuard = { dir: parentPath, realPath: parentPath, stat: parentPathStat };
113
+ if (params.overwrite === false) {
114
+ try {
115
+ await fs.lstat(path.join(parentPath, params.basename));
116
+ throw Object.assign(new Error("destination already exists"), { code: "EEXIST" });
117
+ }
118
+ catch (error) {
119
+ if (error.code !== "ENOENT") {
120
+ throw error;
121
+ }
122
+ }
123
+ }
124
+ if (windows) {
125
+ // The Windows leaf owns both directories after admission.
126
+ windowsOwnsDirectories = true;
127
+ return await runPinnedWriteWindows(binding, params, root, parentFd, verificationGuard);
128
+ }
129
+ const ownedParent = parentFd;
130
+ parentFd = undefined;
131
+ return await writeNativeStage(binding, ownedParent, directory, params, verificationGuard);
78
132
  }
79
- tempFd = binding.openBeneath(parentFd, tempName, nativeOpenFlags(fsSync.constants.O_WRONLY | fsSync.constants.O_CREAT | fsSync.constants.O_EXCL)).fd;
80
- tempIdentity = fsSync.fstatSync(tempFd);
81
- // Creation is requested at 0600 in the binding, but a restrictive umask
82
- // can remove owner access. Keep the unpublished inode private and
83
- // reopenable until the published name has been identity-fenced.
84
- fsSync.fchmodSync(tempFd, 0o600);
85
- await writeNativeInput(tempFd, params.input, params.maxBytes);
86
- syncNativeFileBestEffort(tempFd);
87
- if (params.overwrite === false) {
88
- binding.renameNoReplace(parentFd, tempName, parentFd, params.basename);
89
- }
90
- else {
91
- binding.renameReplace(parentFd, tempName, parentFd, params.basename);
92
- }
93
- renamed = true;
94
- targetFd = binding.openBeneath(parentFd, params.basename, nativeOpenFlags(fsSync.constants.O_RDONLY)).fd;
95
- const targetIdentity = binding.fstatIdentity(targetFd);
96
- if (!targetIdentity.isFile || !sameNativeIdentity(tempIdentity, targetIdentity)) {
97
- throw new FsSafeError("path-mismatch", "native write target changed after rename");
98
- }
99
- // Native exclusive creation starts at 0600. Apply the requested mode only
100
- // after reopening and fencing the published name, both so mode 000 stays
101
- // verifiable and so broader modes are never exposed before that fence.
102
- try {
103
- fsSync.fchmodSync(targetFd, params.mode);
104
- syncNativeFileBestEffort(targetFd);
105
- }
106
- catch (error) {
107
- fsSync.closeSync(targetFd);
108
- targetFd = undefined;
109
- removeNativeCreatedFileIfStillPinned({
110
- binding,
111
- parentPath,
112
- parentFd,
113
- basename: params.basename,
114
- created: tempIdentity,
115
- });
116
- throw error;
133
+ finally {
134
+ if (windows && !windowsOwnsDirectories) {
135
+ if (parentFd !== undefined) {
136
+ fsSync.closeSync(parentFd);
137
+ }
138
+ await root.close().catch(() => undefined);
139
+ }
117
140
  }
118
- syncNativeFileBestEffort(parentFd);
119
- return { dev: targetIdentity.dev, ino: targetIdentity.ino };
141
+ }
142
+ catch (e_1) {
143
+ env_1.error = e_1;
144
+ env_1.hasError = true;
120
145
  }
121
146
  finally {
122
- if (targetFd !== undefined) {
123
- fsSync.closeSync(targetFd);
124
- }
125
- if (tempFd !== undefined) {
126
- fsSync.closeSync(tempFd);
127
- }
128
- if (!renamed && parentFd !== undefined) {
129
- removeNativeCreatedFileIfStillPinned({
130
- binding,
131
- parentPath,
132
- parentFd,
133
- basename: tempName,
134
- created: tempIdentity,
135
- });
136
- }
137
- if (parentFd !== undefined) {
138
- fsSync.closeSync(parentFd);
139
- }
140
- await root.close().catch(() => undefined);
147
+ const result_1 = __disposeResources(env_1);
148
+ if (result_1)
149
+ await result_1;
141
150
  }
142
151
  }
@@ -0,0 +1,24 @@
1
+ import type { AsyncDirectoryGuard } from "./directory-guard.js";
2
+ import type { FileIdentityStat } from "./file-identity.js";
3
+ import type { NativeBinding } from "./native-binding.js";
4
+ import type { PinnedWriteInput, PinnedWriteParams } from "./pinned-write.js";
5
+ import type { PublishedFileReceipt, StagedFile, StagedFileCleanupReceipt, StagedFileReceipt } from "./staged-file-types.js";
6
+ export type NativeStagingBinding = NativeBinding & Required<Pick<NativeBinding, "createStagedFile" | "stagedFileMatches" | "removeStagedFile">>;
7
+ export declare function assertNativeStaging(binding: NativeBinding): asserts binding is NativeStagingBinding;
8
+ declare class NativeStagedFile implements StagedFile {
9
+ #private;
10
+ constructor(binding: NativeStagingBinding, parentFd: number, directory: StagedFileReceipt["directory"], portableNames: boolean, publishedMode: number);
11
+ static create(binding: NativeStagingBinding, parentFd: number, directory: StagedFileReceipt["directory"], input: PinnedWriteInput, mode: number, maxBytes?: number, portableNames?: boolean): Promise<NativeStagedFile>;
12
+ static write(binding: NativeStagingBinding, parentFd: number, directory: StagedFileReceipt["directory"], params: PinnedWriteParams, parentGuard: AsyncDirectoryGuard): Promise<FileIdentityStat>;
13
+ get receipt(): StagedFileReceipt;
14
+ assertCurrent(): Promise<void>;
15
+ publish(basename: string, options: {
16
+ overwrite: boolean;
17
+ }): Promise<PublishedFileReceipt>;
18
+ cleanup(): Promise<StagedFileCleanupReceipt>;
19
+ [Symbol.asyncDispose](): Promise<void>;
20
+ }
21
+ export declare const createNativeStage: (...args: Parameters<typeof NativeStagedFile.create>) => Promise<StagedFile>;
22
+ export declare const writeNativeStage: typeof NativeStagedFile.write;
23
+ export {};
24
+ //# sourceMappingURL=native-staged-file.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"native-staged-file.d.ts","sourceRoot":"","sources":["../src/native-staged-file.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAEhE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,OAAO,KAAK,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAE7E,OAAO,KAAK,EACV,oBAAoB,EACpB,UAAU,EACV,wBAAwB,EAGxB,iBAAiB,EAClB,MAAM,wBAAwB,CAAC;AAEhC,MAAM,MAAM,oBAAoB,GAAG,aAAa,GAAG,QAAQ,CAAC,IAAI,CAC9D,aAAa,EACb,kBAAkB,GAAG,mBAAmB,GAAG,kBAAkB,CAC9D,CAAC,CAAC;AAEH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,OAAO,IAAI,oBAAoB,CAUnG;AA6BD,cAAM,gBAAiB,YAAW,UAAU;;IAU1C,YACE,OAAO,EAAE,oBAAoB,EAC7B,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,iBAAiB,CAAC,WAAW,CAAC,EACzC,aAAa,EAAE,OAAO,EACtB,aAAa,EAAE,MAAM,EAOtB;IAGD,OAAa,MAAM,CACjB,OAAO,EAAE,oBAAoB,EAC7B,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,iBAAiB,CAAC,WAAW,CAAC,EACzC,KAAK,EAAE,gBAAgB,EACvB,IAAI,EAAE,MAAM,EACZ,QAAQ,CAAC,EAAE,MAAM,EAEjB,aAAa,UAAO,GACnB,OAAO,CAAC,gBAAgB,CAAC,CAc3B;IAED,OAAa,KAAK,CAChB,OAAO,EAAE,oBAAoB,EAC7B,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,iBAAiB,CAAC,WAAW,CAAC,EACzC,MAAM,EAAE,iBAAiB,EACzB,WAAW,EAAE,mBAAmB,GAC/B,OAAO,CAAC,gBAAgB,CAAC,CAU3B;IAED,IAAI,OAAO,IAAI,iBAAiB,CAK/B;IAoFK,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAEnC;IAEK,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,SAAS,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAgD9F;IA6CK,OAAO,IAAI,OAAO,CAAC,wBAAwB,CAAC,CAMjD;IAEK,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAO3C;CACF;AAED,eAAO,MAAM,iBAAiB,EAAE,CAAC,GAAG,IAAI,EAAE,UAAU,CAAC,OAAO,gBAAgB,CAAC,MAAM,CAAC,KAAK,OAAO,CAAC,UAAU,CAClF,CAAC;AAC1B,eAAO,MAAM,gBAAgB,+BAAyB,CAAC"}