@openclaw/fs-safe 0.6.0 → 0.7.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.
Files changed (202) hide show
  1. package/CHANGELOG.md +79 -0
  2. package/README.md +7 -0
  3. package/dist/archive-deadline.d.ts +3 -0
  4. package/dist/archive-deadline.d.ts.map +1 -1
  5. package/dist/archive-deadline.js +44 -8
  6. package/dist/archive-entry.d.ts.map +1 -1
  7. package/dist/archive-entry.js +1 -0
  8. package/dist/archive-errors.d.ts +1 -0
  9. package/dist/archive-errors.d.ts.map +1 -1
  10. package/dist/archive-errors.js +3 -0
  11. package/dist/archive-input.d.ts.map +1 -1
  12. package/dist/archive-input.js +26 -16
  13. package/dist/archive-limits.d.ts +11 -3
  14. package/dist/archive-limits.d.ts.map +1 -1
  15. package/dist/archive-limits.js +24 -0
  16. package/dist/archive-native.d.ts +3 -2
  17. package/dist/archive-native.d.ts.map +1 -1
  18. package/dist/archive-native.js +24 -9
  19. package/dist/archive-policy.d.ts +2 -0
  20. package/dist/archive-policy.d.ts.map +1 -1
  21. package/dist/archive-policy.js +9 -1
  22. package/dist/archive-read.d.ts.map +1 -1
  23. package/dist/archive-read.js +69 -41
  24. package/dist/archive-staging.d.ts +3 -0
  25. package/dist/archive-staging.d.ts.map +1 -1
  26. package/dist/archive-staging.js +91 -43
  27. package/dist/archive-tar-admission.d.ts +7 -0
  28. package/dist/archive-tar-admission.d.ts.map +1 -0
  29. package/dist/archive-tar-admission.js +43 -0
  30. package/dist/archive-tar-gnu.d.ts +2 -0
  31. package/dist/archive-tar-gnu.d.ts.map +1 -0
  32. package/dist/archive-tar-gnu.js +20 -0
  33. package/dist/archive-tar-header.d.ts +8 -0
  34. package/dist/archive-tar-header.d.ts.map +1 -0
  35. package/dist/archive-tar-header.js +47 -0
  36. package/dist/archive-tar-meta.d.ts +29 -1
  37. package/dist/archive-tar-meta.d.ts.map +1 -1
  38. package/dist/archive-tar-meta.js +104 -35
  39. package/dist/archive-tar-runtime.d.ts +3 -0
  40. package/dist/archive-tar-runtime.d.ts.map +1 -1
  41. package/dist/archive-tar-runtime.js +3 -0
  42. package/dist/archive-tar.d.ts.map +1 -1
  43. package/dist/archive-tar.js +8 -2
  44. package/dist/archive-zip-names.js +2 -2
  45. package/dist/archive.d.ts.map +1 -1
  46. package/dist/archive.js +38 -27
  47. package/dist/atomic.d.ts +1 -1
  48. package/dist/atomic.d.ts.map +1 -1
  49. package/dist/bounded-read-stream.d.ts.map +1 -1
  50. package/dist/bounded-read-stream.js +2 -4
  51. package/dist/bounded-read.d.ts.map +1 -1
  52. package/dist/bounded-read.js +3 -10
  53. package/dist/byte-budget.d.ts +5 -0
  54. package/dist/byte-budget.d.ts.map +1 -0
  55. package/dist/byte-budget.js +9 -0
  56. package/dist/directory-guard.d.ts +2 -1
  57. package/dist/directory-guard.d.ts.map +1 -1
  58. package/dist/directory-guard.js +10 -0
  59. package/dist/file-lock-sync.d.ts.map +1 -1
  60. package/dist/file-lock-sync.js +81 -30
  61. package/dist/file-observation.d.ts +9 -0
  62. package/dist/file-observation.d.ts.map +1 -0
  63. package/dist/file-observation.js +22 -0
  64. package/dist/file-store-boundary.d.ts.map +1 -1
  65. package/dist/file-store-boundary.js +9 -5
  66. package/dist/file-store-limit.d.ts +2 -0
  67. package/dist/file-store-limit.d.ts.map +1 -0
  68. package/dist/file-store-limit.js +8 -0
  69. package/dist/file-store-sync-write.d.ts.map +1 -1
  70. package/dist/file-store-sync-write.js +38 -8
  71. package/dist/file-store.d.ts.map +1 -1
  72. package/dist/file-store.js +26 -26
  73. package/dist/json-durable-queue-directory.d.ts +2 -0
  74. package/dist/json-durable-queue-directory.d.ts.map +1 -0
  75. package/dist/json-durable-queue-directory.js +20 -0
  76. package/dist/json-durable-queue-ownership.d.ts +14 -0
  77. package/dist/json-durable-queue-ownership.d.ts.map +1 -0
  78. package/dist/json-durable-queue-ownership.js +168 -0
  79. package/dist/json-durable-queue-retirement.d.ts +9 -0
  80. package/dist/json-durable-queue-retirement.d.ts.map +1 -0
  81. package/dist/json-durable-queue-retirement.js +126 -0
  82. package/dist/json-durable-queue-transfer-lock.d.ts +2 -0
  83. package/dist/json-durable-queue-transfer-lock.d.ts.map +1 -0
  84. package/dist/json-durable-queue-transfer-lock.js +19 -0
  85. package/dist/json-durable-queue.d.ts +1 -0
  86. package/dist/json-durable-queue.d.ts.map +1 -1
  87. package/dist/json-durable-queue.js +90 -57
  88. package/dist/json.d.ts.map +1 -1
  89. package/dist/json.js +32 -13
  90. package/dist/local-roots.d.ts.map +1 -1
  91. package/dist/local-roots.js +4 -2
  92. package/dist/native-binding.d.ts +12 -3
  93. package/dist/native-binding.d.ts.map +1 -1
  94. package/dist/native-pinned-write-windows.js +1 -1
  95. package/dist/opened-file-failure.d.ts +7 -0
  96. package/dist/opened-file-failure.d.ts.map +1 -0
  97. package/dist/opened-file-failure.js +41 -0
  98. package/dist/opened-realpath.d.ts.map +1 -1
  99. package/dist/opened-realpath.js +8 -2
  100. package/dist/output-sibling.d.ts.map +1 -1
  101. package/dist/output-sibling.js +11 -110
  102. package/dist/output.d.ts.map +1 -1
  103. package/dist/output.js +4 -2
  104. package/dist/pinned-open.d.ts.map +1 -1
  105. package/dist/pinned-open.js +27 -48
  106. package/dist/pinned-write.d.ts.map +1 -1
  107. package/dist/pinned-write.js +10 -10
  108. package/dist/private-temp-workspace.d.ts +3 -1
  109. package/dist/private-temp-workspace.d.ts.map +1 -1
  110. package/dist/private-temp-workspace.js +81 -56
  111. package/dist/publish-file.d.ts.map +1 -1
  112. package/dist/publish-file.js +2 -4
  113. package/dist/read-opened-file.d.ts.map +1 -1
  114. package/dist/read-opened-file.js +6 -4
  115. package/dist/regular-file.d.ts.map +1 -1
  116. package/dist/regular-file.js +136 -82
  117. package/dist/replace-file-copy-fallback.d.ts +3 -1
  118. package/dist/replace-file-copy-fallback.d.ts.map +1 -1
  119. package/dist/replace-file-copy-fallback.js +21 -36
  120. package/dist/replace-file-copy-source.d.ts +21 -0
  121. package/dist/replace-file-copy-source.d.ts.map +1 -0
  122. package/dist/replace-file-copy-source.js +112 -0
  123. package/dist/replace-file-descriptor.d.ts +13 -3
  124. package/dist/replace-file-descriptor.d.ts.map +1 -1
  125. package/dist/replace-file-descriptor.js +32 -7
  126. package/dist/replace-file-rename-policy.d.ts +7 -0
  127. package/dist/replace-file-rename-policy.d.ts.map +1 -0
  128. package/dist/replace-file-rename-policy.js +30 -0
  129. package/dist/replace-file-temp-owner.d.ts +46 -0
  130. package/dist/replace-file-temp-owner.d.ts.map +1 -0
  131. package/dist/replace-file-temp-owner.js +346 -0
  132. package/dist/replace-file.d.ts +6 -1
  133. package/dist/replace-file.d.ts.map +1 -1
  134. package/dist/replace-file.js +72 -58
  135. package/dist/root-impl.d.ts.map +1 -1
  136. package/dist/root-impl.js +113 -82
  137. package/dist/secret-file.d.ts.map +1 -1
  138. package/dist/secret-file.js +6 -3
  139. package/dist/secret-read-async.d.ts.map +1 -1
  140. package/dist/secret-read-async.js +6 -5
  141. package/dist/secure-file.d.ts.map +1 -1
  142. package/dist/secure-file.js +17 -13
  143. package/dist/sibling-staged-file.d.ts +15 -0
  144. package/dist/sibling-staged-file.d.ts.map +1 -0
  145. package/dist/sibling-staged-file.js +148 -0
  146. package/dist/sibling-temp.d.ts +3 -0
  147. package/dist/sibling-temp.d.ts.map +1 -1
  148. package/dist/sibling-temp.js +30 -74
  149. package/dist/sidecar-lock-acquire.d.ts.map +1 -1
  150. package/dist/sidecar-lock-acquire.js +91 -37
  151. package/dist/sidecar-lock-handle.d.ts +6 -2
  152. package/dist/sidecar-lock-handle.d.ts.map +1 -1
  153. package/dist/sidecar-lock-handle.js +17 -3
  154. package/dist/sidecar-lock-policy.d.ts +2 -0
  155. package/dist/sidecar-lock-policy.d.ts.map +1 -1
  156. package/dist/sidecar-lock-policy.js +29 -0
  157. package/dist/sidecar-lock-reclaim.d.ts +16 -5
  158. package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
  159. package/dist/sidecar-lock-reclaim.js +60 -28
  160. package/dist/sidecar-lock-root.d.ts +3 -0
  161. package/dist/sidecar-lock-root.d.ts.map +1 -0
  162. package/dist/sidecar-lock-root.js +76 -0
  163. package/dist/sidecar-lock.d.ts.map +1 -1
  164. package/dist/sidecar-lock.js +28 -16
  165. package/dist/strict-file-identity.d.ts.map +1 -1
  166. package/dist/strict-file-identity.js +4 -1
  167. package/dist/suppressed-error.d.ts +6 -0
  168. package/dist/suppressed-error.d.ts.map +1 -0
  169. package/dist/suppressed-error.js +15 -0
  170. package/dist/temp-cleanup.d.ts +2 -0
  171. package/dist/temp-cleanup.d.ts.map +1 -1
  172. package/dist/temp-cleanup.js +25 -10
  173. package/dist/temp-workspace-owner.d.ts +23 -0
  174. package/dist/temp-workspace-owner.d.ts.map +1 -0
  175. package/dist/temp-workspace-owner.js +320 -0
  176. package/dist/temp.d.ts +1 -1
  177. package/dist/temp.d.ts.map +1 -1
  178. package/dist/test-hooks.d.ts +5 -0
  179. package/dist/test-hooks.d.ts.map +1 -1
  180. package/docs/advanced.md +8 -1
  181. package/docs/archive.md +202 -26
  182. package/docs/atomic.md +11 -2
  183. package/docs/durability.md +1 -1
  184. package/docs/file-store.md +2 -0
  185. package/docs/install.md +8 -4
  186. package/docs/json.md +11 -4
  187. package/docs/native-helper.md +15 -3
  188. package/docs/native.md +33 -6
  189. package/docs/output.md +16 -3
  190. package/docs/public-api.md +6 -3
  191. package/docs/quickstart.md +1 -1
  192. package/docs/reading.md +1 -1
  193. package/docs/regular-file.md +9 -2
  194. package/docs/root.md +2 -0
  195. package/docs/secret-file.md +5 -1
  196. package/docs/secure-file.md +4 -2
  197. package/docs/security-model.md +18 -0
  198. package/docs/sidecar-lock.md +60 -4
  199. package/docs/store.md +13 -0
  200. package/docs/temp.md +128 -14
  201. package/docs/writing.md +6 -0
  202. package/package.json +10 -10
@@ -1,11 +1,12 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import syncFs from "node:fs";
2
+ import syncFs, {} from "node:fs";
3
3
  import fs from "node:fs/promises";
4
4
  import path from "node:path";
5
5
  import { assertDestinationHardlinkPolicy, assertDestinationHardlinkPolicySync, copyFallbackReplace, copyFallbackReplaceSync, } from "./replace-file-copy-fallback.js";
6
6
  import { applyDirectoryMode, applyDirectoryModeSync, writeTempFile, writeTempFileSync, } from "./replace-file-descriptor.js";
7
+ import { atomicExpectedContentHash, validateRenameIdentity, withAtomicRenameIdentityLock, withAtomicRenameIdentityLockSync, } from "./replace-file-rename-policy.js";
8
+ import { AsyncAtomicTempOwner, SyncAtomicTempOwner } from "./replace-file-temp-owner.js";
7
9
  import { assertSafePathPrefix } from "./safe-path-segment.js";
8
- import { registerTempPathForExit } from "./temp-cleanup.js";
9
10
  import { sleep, sleepSync } from "./timing.js";
10
11
  import { serializePathWrite } from "./write-queue.js";
11
12
  function isRetryableRenameError(error) {
@@ -17,6 +18,7 @@ function isPermissionRenameError(error) {
17
18
  }
18
19
  async function renameWithRetry(params) {
19
20
  for (let attempt = 0; attempt <= params.maxRetries; attempt++) {
21
+ await params.assertSourceCurrent();
20
22
  try {
21
23
  await params.fsModule.rename(params.src, params.dest);
22
24
  return { method: "rename" };
@@ -34,6 +36,7 @@ async function renameWithRetry(params) {
34
36
  destinationHardlinks: params.destinationHardlinks,
35
37
  restore: params.copyFallbackRestore,
36
38
  maxRestoreBytes: params.maxRestoreBytes,
39
+ expectedSourceIdentity: params.sourceIdentity,
37
40
  sync: params.syncFallback,
38
41
  });
39
42
  return { method: "copy-fallback" };
@@ -45,6 +48,7 @@ async function renameWithRetry(params) {
45
48
  }
46
49
  function renameWithRetrySync(params) {
47
50
  for (let attempt = 0; attempt <= params.maxRetries; attempt++) {
51
+ params.assertSourceCurrent();
48
52
  try {
49
53
  params.fsModule.renameSync(params.src, params.dest);
50
54
  return { method: "rename" };
@@ -62,6 +66,7 @@ function renameWithRetrySync(params) {
62
66
  destinationHardlinks: params.destinationHardlinks,
63
67
  restore: params.copyFallbackRestore,
64
68
  maxRestoreBytes: params.maxRestoreBytes,
69
+ expectedSourceIdentity: params.sourceIdentity,
65
70
  fchmodSync: params.fchmodSync,
66
71
  sync: params.syncFallback,
67
72
  });
@@ -158,26 +163,20 @@ function syncDirectoryBestEffortSync(fsModule, dirPath) {
158
163
  }
159
164
  }
160
165
  }
161
- async function cleanupTempFile(params) {
162
- const cleanupError = await params.fsModule
163
- .rm(params.tempPath, { force: true })
164
- .catch((error) => error);
165
- if (!cleanupError)
166
- return true;
167
- if (params.throwOnCleanupError) {
168
- if (params.originalError !== undefined) {
169
- throw new Error(`Atomic file replace failed (${String(params.originalError)}); cleanup also failed (${String(cleanupError)})`, { cause: params.originalError });
170
- }
171
- throw cleanupError;
172
- }
173
- return false;
174
- }
175
166
  export async function replaceFileAtomic(options) {
176
167
  const filePath = options.filePath;
177
168
  validateReplaceFilePath(filePath);
178
169
  validateRestoreOptions(options);
170
+ validateRenameIdentity(options.renameIdentity);
179
171
  return await serializePathWrite(path.resolve(filePath), async () => {
180
- return await replaceFileAtomicUnserialized(options);
172
+ if (options.renameIdentity !== "verify-content-with-lock") {
173
+ return await replaceFileAtomicUnserialized(options);
174
+ }
175
+ await (options.fileSystem?.promises ?? fs).mkdir(path.dirname(filePath), {
176
+ recursive: true,
177
+ mode: options.dirMode ?? 0o700,
178
+ });
179
+ return await withAtomicRenameIdentityLock(filePath, async () => await replaceFileAtomicUnserialized(options));
181
180
  });
182
181
  }
183
182
  async function replaceFileAtomicUnserialized(options) {
@@ -186,24 +185,27 @@ async function replaceFileAtomicUnserialized(options) {
186
185
  const dir = path.dirname(filePath);
187
186
  const dirMode = options.dirMode ?? 0o700;
188
187
  const mode = await resolveMode(options);
188
+ const expectedHash = atomicExpectedContentHash(options.renameIdentity, options.content);
189
189
  const tempPath = buildReplaceTempPath(filePath, options.tempPrefix);
190
- const unregisterTempPath = registerTempPathForExit(tempPath);
191
- let tempExists = false;
190
+ const tempOwner = new AsyncAtomicTempOwner(tempPath);
192
191
  let originalError;
193
192
  try {
194
193
  await fsModule.mkdir(dir, { recursive: true, mode: dirMode });
195
194
  await applyDirectoryMode({ fsModule, dirPath: dir, mode: dirMode });
196
- tempExists = true;
197
- unregisterTempPath.setIdentity(await writeTempFile({
195
+ tempOwner.start();
196
+ tempOwner.adopt(await writeTempFile({
198
197
  fsModule,
199
198
  tempPath,
200
199
  content: options.content,
201
200
  mode,
202
201
  sync: options.syncTempFile === true,
202
+ onIdentity: tempOwner.onIdentity,
203
203
  }));
204
+ await tempOwner.assertCurrent(fsModule);
204
205
  if (options.beforeRename) {
205
206
  await options.beforeRename({ filePath, tempPath });
206
207
  }
208
+ await tempOwner.assertCurrent(fsModule);
207
209
  await assertDestinationHardlinkPolicy(fsModule, filePath, options.destinationHardlinks);
208
210
  const result = await renameWithRetry({
209
211
  fsModule,
@@ -215,15 +217,23 @@ async function replaceFileAtomicUnserialized(options) {
215
217
  copyFallbackRestore: options.copyFallbackRestore ?? "none",
216
218
  maxRestoreBytes: options.maxRestoreBytes,
217
219
  destinationHardlinks: options.destinationHardlinks,
220
+ sourceIdentity: tempOwner.identity,
221
+ assertSourceCurrent: () => tempOwner.assertCurrent(fsModule),
218
222
  syncFallback: options.syncTempFile === true,
219
223
  });
220
224
  if (result.method === "rename") {
221
- tempExists = false;
222
- unregisterTempPath();
225
+ tempOwner.markRenamed();
226
+ await tempOwner.assertPublished(fsModule, filePath, expectedHash);
227
+ }
228
+ else {
229
+ await tempOwner.assertCurrent(fsModule);
223
230
  }
224
231
  if (options.syncParentDir) {
225
232
  await syncDirectoryBestEffort(fsModule, dir);
226
233
  }
234
+ if (result.method === "rename") {
235
+ await tempOwner.assertPublished(fsModule, filePath, expectedHash);
236
+ }
227
237
  return result;
228
238
  }
229
239
  catch (error) {
@@ -231,27 +241,34 @@ async function replaceFileAtomicUnserialized(options) {
231
241
  throw error;
232
242
  }
233
243
  finally {
234
- let tempRemoved = !tempExists;
235
- if (tempExists) {
236
- tempRemoved = await cleanupTempFile({
237
- fsModule,
238
- tempPath,
239
- originalError,
240
- throwOnCleanupError: options.throwOnCleanupError === true,
241
- });
242
- }
243
- if (tempRemoved)
244
- unregisterTempPath();
244
+ await tempOwner.finish({
245
+ fsModule,
246
+ originalError,
247
+ throwOnCleanupError: options.throwOnCleanupError === true,
248
+ });
245
249
  }
246
250
  }
247
251
  export function replaceFileAtomicSync(options) {
248
252
  const filePath = options.filePath;
249
253
  validateReplaceFilePath(filePath);
250
254
  validateRestoreOptions(options);
255
+ validateRenameIdentity(options.renameIdentity);
256
+ if (options.renameIdentity !== "verify-content-with-lock") {
257
+ return replaceFileAtomicSyncUnserialized(options);
258
+ }
259
+ (options.fileSystem ?? syncFs).mkdirSync(path.dirname(filePath), {
260
+ recursive: true,
261
+ mode: options.dirMode ?? 0o700,
262
+ });
263
+ return withAtomicRenameIdentityLockSync(filePath, () => replaceFileAtomicSyncUnserialized(options));
264
+ }
265
+ function replaceFileAtomicSyncUnserialized(options) {
266
+ const filePath = options.filePath;
251
267
  const fsModule = options.fileSystem ?? syncFs;
252
268
  const dir = path.dirname(filePath);
253
269
  const dirMode = options.dirMode ?? 0o700;
254
270
  const mode = resolveModeSync(options);
271
+ const expectedHash = atomicExpectedContentHash(options.renameIdentity, options.content);
255
272
  const fchmodSync = options.fileSystem?.fchmodSync ?? (options.fileSystem === undefined ? syncFs.fchmodSync : undefined);
256
273
  if (!fchmodSync &&
257
274
  (options.mode !== undefined ||
@@ -260,24 +277,26 @@ export function replaceFileAtomicSync(options) {
260
277
  throw missingFchmodSyncError();
261
278
  }
262
279
  const tempPath = buildReplaceTempPath(filePath, options.tempPrefix);
263
- const unregisterTempPath = registerTempPathForExit(tempPath);
264
- let tempExists = false;
280
+ const tempOwner = new SyncAtomicTempOwner(tempPath);
265
281
  let originalError;
266
282
  try {
267
283
  fsModule.mkdirSync(dir, { recursive: true, mode: dirMode });
268
284
  applyDirectoryModeSync({ fsModule, dirPath: dir, mode: dirMode, fchmodSync });
269
- tempExists = true;
270
- unregisterTempPath.setIdentity(writeTempFileSync({
285
+ tempOwner.start();
286
+ tempOwner.adopt(writeTempFileSync({
271
287
  fsModule,
272
288
  tempPath,
273
289
  content: options.content,
274
290
  mode,
275
291
  fchmodSync,
276
292
  sync: options.syncTempFile === true,
293
+ onIdentity: tempOwner.onIdentity,
277
294
  }));
295
+ tempOwner.assertCurrent(fsModule);
278
296
  if (options.beforeRename) {
279
297
  options.beforeRename({ filePath, tempPath });
280
298
  }
299
+ tempOwner.assertCurrent(fsModule);
281
300
  assertDestinationHardlinkPolicySync(fsModule, filePath, options.destinationHardlinks);
282
301
  const result = renameWithRetrySync({
283
302
  fsModule,
@@ -289,16 +308,24 @@ export function replaceFileAtomicSync(options) {
289
308
  copyFallbackRestore: options.copyFallbackRestore ?? "none",
290
309
  maxRestoreBytes: options.maxRestoreBytes,
291
310
  destinationHardlinks: options.destinationHardlinks,
311
+ sourceIdentity: tempOwner.identity,
312
+ assertSourceCurrent: () => tempOwner.assertCurrent(fsModule),
292
313
  fchmodSync,
293
314
  syncFallback: options.syncTempFile === true,
294
315
  });
295
316
  if (result.method === "rename") {
296
- tempExists = false;
297
- unregisterTempPath();
317
+ tempOwner.markRenamed();
318
+ tempOwner.assertPublished(fsModule, filePath, expectedHash);
319
+ }
320
+ else {
321
+ tempOwner.assertCurrent(fsModule);
298
322
  }
299
323
  if (options.syncParentDir) {
300
324
  syncDirectoryBestEffortSync(fsModule, dir);
301
325
  }
326
+ if (result.method === "rename") {
327
+ tempOwner.assertPublished(fsModule, filePath, expectedHash);
328
+ }
302
329
  return result;
303
330
  }
304
331
  catch (error) {
@@ -306,23 +333,10 @@ export function replaceFileAtomicSync(options) {
306
333
  throw error;
307
334
  }
308
335
  finally {
309
- let tempRemoved = !tempExists;
310
- if (tempExists) {
311
- try {
312
- fsModule.rmSync(tempPath, { force: true });
313
- tempRemoved = true;
314
- }
315
- catch (cleanupError) {
316
- if (options.throwOnCleanupError) {
317
- if (originalError !== undefined) {
318
- throw new Error(`Atomic file replace failed (${String(originalError)}); cleanup also failed (${String(cleanupError)})`, { cause: originalError });
319
- }
320
- throw cleanupError;
321
- }
322
- // The temp file is best-effort cleanup after write failure.
323
- }
324
- }
325
- if (tempRemoved)
326
- unregisterTempPath();
336
+ tempOwner.finish({
337
+ fsModule,
338
+ originalError,
339
+ throwOnCleanupError: options.throwOnCleanupError === true,
340
+ });
327
341
  }
328
342
  }
@@ -1 +1 @@
1
- {"version":3,"file":"root-impl.d.ts","sourceRoot":"","sources":["../src/root-impl.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAe,KAAK,EAAE,MAAM,SAAS,CAAC;AAElD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAGnD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAW7D,OAAO,EAGL,KAAK,kBAAkB,EACxB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACL,KAAK,oBAAoB,EAG1B,MAAM,mBAAmB,CAAC;AAY3B,OAAO,EAAwB,KAAK,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAyB9E,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACrD,OAAO,EAAY,KAAK,aAAa,EAAE,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAMpF,YAAY,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAC9D,YAAY,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAC9D,OAAO,EAAE,kCAAkC,EAAE,MAAM,sBAAsB,CAAC;AAC1E,YAAY,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACxD,MAAM,MAAM,UAAU,GAAG;IACvB,MAAM,EAAE,UAAU,CAAC;IACnB,WAAW,EAAE,oBAAoB,CAAC;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,KAAK,CAAC;IACZ,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,YAAY,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,oBAAoB,CAAC;AAC5D,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,OAAO,CAAC;AAChD,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAE/D,MAAM,MAAM,YAAY,GAAG;IACzB,SAAS,CAAC,EAAE,cAAc,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,kBAAkB,CAAC;IACnC,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,cAAc,CAAC,EAAE,oBAAoB,CAAC;IACtC,QAAQ,CAAC,EAAE,aAAa,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,IAAI,CAChC,YAAY,EACZ,WAAW,GAAG,UAAU,GAAG,iBAAiB,GAAG,UAAU,CAC1D,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,IAAI,CAAC,eAAe,EAAE,UAAU,CAAC,CAAC;AAEhE,MAAM,MAAM,gBAAgB,GAAG,IAAI,CAAC,YAAY,EAAE,eAAe,GAAG,OAAO,GAAG,MAAM,GAAG,gBAAgB,CAAC,GAAG;IACzG,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG,IAAI,CAAC,YAAY,EAAE,eAAe,GAAG,OAAO,GAAG,MAAM,CAAC,GAAG;IAC7F,SAAS,CAAC,EAAE,gBAAgB,CAAC;CAC9B,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,IAAI,CAAC,YAAY,EAAE,eAAe,GAAG,UAAU,GAAG,OAAO,GAAG,MAAM,CAAC,GAAG;IAClG,eAAe,CAAC,EAAE,cAAc,CAAC;CAClC,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG,gBAAgB,GAAG;IACpD,QAAQ,CAAC,EAAE,UAAU,CAAC,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;IAChD,KAAK,CAAC,EAAE,UAAU,CAAC,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;IAC7C,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG,IAAI,CAAC,gBAAgB,EAAE,WAAW,CAAC,CAAC;AACpE,MAAM,MAAM,qBAAqB,GAAG,IAAI,CAAC,oBAAoB,EAAE,WAAW,CAAC,CAAC;AAE5E,MAAM,MAAM,iBAAiB,GAAG,gBAAgB,GAAG;IACjD,sBAAsB,CAAC,EAAE,OAAO,CAAC;CAClC,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,IAAI,CAAC,YAAY,EAAE,eAAe,CAAC,GAAG;IAClE,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG,IAAI,CAAC,YAAY,EAAE,eAAe,CAAC,CAAC;AACpE,MAAM,MAAM,gBAAgB,GAAG,IAAI,CAAC,YAAY,EAAE,eAAe,CAAC,CAAC;AA6BnE,eAAO,MAAM,sBAAsB,QAAmB,CAAC;AA6IvD,MAAM,WAAW,IAAI;IACnB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC;IAEhC,OAAO,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC/C,IAAI,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAC3E,IAAI,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAC3E,SAAS,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC5E,QAAQ,CACN,YAAY,EAAE,MAAM,EACpB,OAAO,CAAC,EAAE,eAAe,GAAG;QAAE,QAAQ,CAAC,EAAE,cAAc,CAAA;KAAE,GACxD,OAAO,CAAC,MAAM,CAAC,CAAC;IACnB,QAAQ,CAAC,CAAC,GAAG,OAAO,EAClB,YAAY,EAAE,MAAM,EACpB,OAAO,CAAC,EAAE,eAAe,GAAG;QAAE,QAAQ,CAAC,EAAE,cAAc,CAAA;KAAE,GACxD,OAAO,CAAC,CAAC,CAAC,CAAC;IACd,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAC/E,MAAM,CAAC,OAAO,CAAC,EAAE,eAAe,GAAG,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACzE,YAAY,CACV,YAAY,EAAE,MAAM,EACpB,OAAO,CAAC,EAAE,uBAAuB,GAChC,OAAO,CAAC,kBAAkB,CAAC,CAAC;IAC/B,MAAM,CACJ,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,MAAM,GAAG,MAAM,EACrB,OAAO,CAAC,EAAE,iBAAiB,GAC1B,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB,MAAM,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzE,KAAK,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvE,UAAU,CAAC,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtD,KAAK,CACH,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,MAAM,GAAG,MAAM,EACrB,OAAO,CAAC,EAAE,gBAAgB,GACzB,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB,MAAM,CACJ,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,MAAM,GAAG,MAAM,EACrB,OAAO,CAAC,EAAE,iBAAiB,GAC1B,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB,SAAS,CACP,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,OAAO,EACb,OAAO,CAAC,EAAE,oBAAoB,GAC7B,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB,UAAU,CACR,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,OAAO,EACb,OAAO,CAAC,EAAE,qBAAqB,GAC9B,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB,MAAM,CAAC,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3F,MAAM,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/C,IAAI,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC9C,IAAI,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,aAAa,CAAC,EAAE,KAAK,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACnF,IAAI,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,aAAa,EAAE,IAAI,CAAA;KAAE,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;IAClF,IAAI,CACF,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE,eAAe,GACxB,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB,IAAI,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,GAAG,qBAAqB,CAAC,aAAa,CAAC,CAAC;CAC5F;AAyRD,wBAAsB,IAAI,CACxB,OAAO,EAAE,MAAM,EACf,QAAQ,GAAE,YAAiB,GAC1B,OAAO,CAAC,IAAI,CAAC,CAEf;AAqFD,wBAAsB,mBAAmB,CAAC,MAAM,EAAE;IAChD,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,UAAU,CAAC,CAOtB;AAED,wBAAsB,mBAAmB,CAAC,MAAM,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,UAAU,CAAC,CAG3F;AAED,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,EAAE,UAAU,CAAC;IACnB,WAAW,EAAE,oBAAoB,CAAC;IAClC,eAAe,EAAE,OAAO,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,KAAK,CAAC;IACZ,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC,CAAC"}
1
+ {"version":3,"file":"root-impl.d.ts","sourceRoot":"","sources":["../src/root-impl.ts"],"names":[],"mappings":"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"}
package/dist/root-impl.js CHANGED
@@ -2,6 +2,7 @@ import { randomUUID } from "node:crypto";
2
2
  import { constants as fsConstants } from "node:fs";
3
3
  import fs from "node:fs/promises";
4
4
  import path from "node:path";
5
+ import { normalizeMaxBytes } from "./byte-budget.js";
5
6
  import { assertAsyncDirectoryGuard, createAsyncDirectoryGuard, createNearestExistingDirectoryGuard } from "./directory-guard.js";
6
7
  import { FsSafeError } from "./errors.js";
7
8
  import { syncDirectoryBestEffort } from "./fsync.js";
@@ -10,6 +11,7 @@ import { mkdirPathComponentsWithGuards } from "./guarded-mkdir.js";
10
11
  import { withAsyncDirectoryGuards } from "./guarded-mutation.js";
11
12
  import { assertMutationNotDenied, mergeDenyMutationPolicies, } from "./deny-mutations.js";
12
13
  import { resolveOpenedFileRealPathForFd, resolveOpenedFileRealPathForHandle } from "./opened-realpath.js";
14
+ import { openedPathResolutionError, recordFileOpenFailure, recordOpenedFileFailure, recordPreOpenFileChange } from "./opened-file-failure.js";
13
15
  import { runPinnedWriteHelper, runPinnedWriteWithRenamePolicy, } from "./pinned-write.js";
14
16
  import { getNativeBinding } from "./native.js";
15
17
  import { validatePinnedOperationPayload } from "./pinned-operation.js";
@@ -101,25 +103,15 @@ async function openVerifiedLocalFile(filePath, options) {
101
103
  if (preOpenStat) {
102
104
  await fsSafeTestHooks?.afterPreOpenLstat?.(filePath);
103
105
  }
106
+ const openFlags = options?.symlinks === "follow-within-root"
107
+ ? OPEN_READ_FOLLOW_FLAGS
108
+ : OPEN_READ_FLAGS;
109
+ await fsSafeTestHooks?.beforeOpen?.(filePath, openFlags);
104
110
  let handle;
105
111
  try {
106
- const openFlags = options?.symlinks === "follow-within-root"
107
- ? OPEN_READ_FOLLOW_FLAGS
108
- : OPEN_READ_FLAGS;
109
- await fsSafeTestHooks?.beforeOpen?.(filePath, openFlags);
110
- handle = await fs.open(filePath, openFlags);
111
- try {
112
- await fsSafeTestHooks?.afterOpen?.(filePath, handle);
113
- }
114
- catch (err) {
115
- await handle.close().catch(() => { });
116
- throw err;
117
- }
112
+ handle = await fs.open(filePath, openFlags).catch((error) => recordFileOpenFailure(isNotFoundPathError(error) ? fileNotFoundError() : error, filePath));
118
113
  }
119
114
  catch (err) {
120
- if (isNotFoundPathError(err)) {
121
- throw fileNotFoundError();
122
- }
123
115
  if (isSymlinkOpenError(err)) {
124
116
  throw new FsSafeError("symlink", "symlink open blocked", { cause: err });
125
117
  }
@@ -130,16 +122,35 @@ async function openVerifiedLocalFile(filePath, options) {
130
122
  throw err;
131
123
  }
132
124
  try {
125
+ await fsSafeTestHooks?.afterOpen?.(filePath, handle);
133
126
  const stat = await handle.stat();
134
127
  if (!stat.isFile()) {
135
128
  throw new FsSafeError("not-file", "not a file");
136
129
  }
137
130
  // Keep numeric Stats for the public receipt, never for identity verification.
138
- const identity = await inspectFileIdentity(() => handle.stat({ bigint: true }), preOpenStat && !preOpenStat.isSymbolicLink() ? preOpenStat : undefined);
131
+ let openedIdentity;
132
+ const identity = await inspectFileIdentity(async () => (openedIdentity = await handle.stat({ bigint: true })), preOpenStat && !preOpenStat.isSymbolicLink() ? preOpenStat : undefined).catch(async (error) => {
133
+ if ([0, 1].includes(stat.nlink)) {
134
+ await recordPreOpenFileChange(error, handle, filePath, preOpenStat, openedIdentity);
135
+ }
136
+ throw error;
137
+ });
139
138
  if (options?.hardlinks === "reject" && stat.nlink > 1) {
140
139
  throw hardlinkedPathNotAllowedError();
141
140
  }
142
- await inspectFileIdentity(async () => {
141
+ const inspectPathIdentity = async (inspect) => {
142
+ try {
143
+ return await inspectFileIdentity(inspect, identity);
144
+ }
145
+ catch (error) {
146
+ const failure = isNotFoundPathError(error) ? openedPathResolutionError(fileNotFoundError()) : error;
147
+ if (stat.nlink <= 1 && (!preOpenStat || preOpenStat.nlink <= 1n)) {
148
+ await recordOpenedFileFailure(failure, handle, filePath, identity);
149
+ }
150
+ throw failure;
151
+ }
152
+ };
153
+ await inspectPathIdentity(async () => {
143
154
  const pathStat = options?.symlinks === "follow-within-root"
144
155
  ? await fs.stat(filePath, { bigint: true })
145
156
  : await fs.lstat(filePath, { bigint: true });
@@ -147,25 +158,26 @@ async function openVerifiedLocalFile(filePath, options) {
147
158
  throw new FsSafeError("symlink", "symlink not allowed");
148
159
  }
149
160
  return pathStat;
150
- }, identity);
151
- const realPath = await resolveOpenedFileRealPathForFd(handle.fd, identity, filePath);
152
- await inspectFileIdentity(async () => {
161
+ });
162
+ await fsSafeTestHooks?.afterOpenedPathIdentityCheck?.(filePath, handle);
163
+ const realPath = await resolveOpenedFileRealPathForFd(handle.fd, identity, filePath)
164
+ .catch(async (error) => {
165
+ if (stat.nlink <= 1 && (!preOpenStat || preOpenStat.nlink <= 1n)) {
166
+ await recordOpenedFileFailure(error, handle, filePath, identity);
167
+ }
168
+ throw error;
169
+ });
170
+ await inspectPathIdentity(async () => {
153
171
  const realStat = await fs.stat(realPath, { bigint: true });
154
172
  if (options?.hardlinks === "reject" && realStat.nlink > 1n) {
155
173
  throw hardlinkedPathNotAllowedError();
156
174
  }
157
175
  return realStat;
158
- }, identity);
159
- return openResult({ handle, realPath, stat });
176
+ });
177
+ return { opened: openResult({ handle, realPath, stat }), identity };
160
178
  }
161
179
  catch (err) {
162
180
  await handle.close().catch(() => { });
163
- if (err instanceof FsSafeError) {
164
- throw err;
165
- }
166
- if (isNotFoundPathError(err)) {
167
- throw fileNotFoundError();
168
- }
169
181
  throw err;
170
182
  }
171
183
  }
@@ -210,8 +222,7 @@ class RootHandle {
210
222
  async read(relativePath, options = {}) {
211
223
  return await readFileInRoot(this.context, {
212
224
  relativePath,
213
- ...readDefaults(this.defaults),
214
- ...options,
225
+ ...mergeReadOptions(this.defaults, options),
215
226
  });
216
227
  }
217
228
  async readBytes(relativePath, options = {}) {
@@ -227,8 +238,7 @@ class RootHandle {
227
238
  async readAbsolute(filePath, options = {}) {
228
239
  return await readPathInRoot(this.context, {
229
240
  filePath,
230
- ...readDefaults(this.defaults),
231
- ...options,
241
+ ...mergeReadOptions(this.defaults, options),
232
242
  });
233
243
  }
234
244
  reader(options = {}) {
@@ -313,13 +323,14 @@ class RootHandle {
313
323
  }
314
324
  async copyIn(relativePath, sourcePath, options = {}) {
315
325
  assertValidRootDestinationPath(relativePath);
326
+ const { maxBytes, ...copyOptions } = this.mutationOptions(options);
316
327
  await copyFileInRoot(this.context, {
317
328
  sourcePath,
318
329
  relativePath,
319
- maxBytes: this.defaults.maxBytes,
330
+ maxBytes: normalizeMaxBytes(maxBytes, { defaultValue: this.defaults.maxBytes }),
320
331
  mkdir: this.defaults.mkdir,
321
332
  mode: this.defaults.mode,
322
- ...this.mutationOptions(options),
333
+ ...copyOptions,
323
334
  });
324
335
  }
325
336
  async exists(relativePath) {
@@ -369,12 +380,24 @@ class RootHandle {
369
380
  function readDefaults(defaults) {
370
381
  return {
371
382
  hardlinks: defaults.hardlinks,
372
- maxBytes: defaults.maxBytes ?? DEFAULT_ROOT_MAX_BYTES,
383
+ maxBytes: normalizeMaxBytes(defaults.maxBytes, { defaultValue: DEFAULT_ROOT_MAX_BYTES }),
373
384
  nonBlockingRead: defaults.nonBlockingRead,
374
385
  symlinks: defaults.symlinks,
375
386
  };
376
387
  }
388
+ function mergeReadOptions(defaults, options) {
389
+ const merged = readDefaults(defaults);
390
+ if (options.hardlinks !== undefined)
391
+ merged.hardlinks = options.hardlinks;
392
+ merged.maxBytes = normalizeMaxBytes(options.maxBytes, { defaultValue: merged.maxBytes });
393
+ if (options.nonBlockingRead !== undefined)
394
+ merged.nonBlockingRead = options.nonBlockingRead;
395
+ if (options.symlinks !== undefined)
396
+ merged.symlinks = options.symlinks;
397
+ return merged;
398
+ }
377
399
  export async function root(rootDir, defaults = {}) {
400
+ normalizeMaxBytes(defaults.maxBytes);
378
401
  return new RootHandle(await resolveRootContext(rootDir), defaults);
379
402
  }
380
403
  async function openFileInRoot(root, params) {
@@ -383,19 +406,10 @@ async function openFileInRoot(root, params) {
383
406
  rejectUnsafeDeviceReads: true,
384
407
  rejectSymlinks: params.symlinks !== "follow-within-root",
385
408
  });
386
- let opened;
387
- try {
388
- opened = await openVerifiedLocalFile(resolved, {
389
- nonBlockingRead: params.nonBlockingRead,
390
- symlinks: params.symlinks,
391
- });
392
- }
393
- catch (err) {
394
- if (err instanceof FsSafeError) {
395
- throw err;
396
- }
397
- throw err;
398
- }
409
+ const { opened } = await openVerifiedLocalFile(resolved, {
410
+ nonBlockingRead: params.nonBlockingRead,
411
+ symlinks: params.symlinks,
412
+ });
399
413
  if (params.hardlinks !== "allow" && opened.stat.nlink > 1) {
400
414
  await opened.handle.close().catch(() => { });
401
415
  throw hardlinkedPathNotAllowedError();
@@ -430,9 +444,10 @@ async function readPathInRoot(root, params) {
430
444
  });
431
445
  }
432
446
  export async function readLocalFileSafely(params) {
447
+ const maxBytes = normalizeMaxBytes(params.maxBytes);
433
448
  const opened = await openLocalFileSafely({ filePath: params.filePath });
434
449
  try {
435
- return await readOpenedFileSafely({ opened, maxBytes: params.maxBytes });
450
+ return await readOpenedFileSafely({ opened, maxBytes });
436
451
  }
437
452
  finally {
438
453
  await opened.handle.close().catch(() => { });
@@ -440,15 +455,13 @@ export async function readLocalFileSafely(params) {
440
455
  }
441
456
  export async function openLocalFileSafely(params) {
442
457
  assertNoNulPathInput(params.filePath, "file path contains a NUL byte");
443
- return await openVerifiedLocalFile(params.filePath);
458
+ return (await openVerifiedLocalFile(params.filePath)).opened;
444
459
  }
445
460
  function emitWriteBoundaryWarning(reason) {
446
461
  logWarn(`security: fs-safe write boundary warning (${reason})`);
447
462
  }
448
463
  function buildAtomicWriteTempPath(targetPath) {
449
- const dir = path.dirname(targetPath);
450
- const base = path.basename(targetPath);
451
- return path.join(dir, `.${base}.${process.pid}.${randomUUID()}.tmp`);
464
+ return path.join(path.dirname(targetPath), `.fs-safe-${randomUUID()}.tmp`);
452
465
  }
453
466
  function rootWriteQueueKey(root, relativePath) {
454
467
  return `${root.rootReal}\0${relativePath}`;
@@ -687,7 +700,7 @@ async function writeFileInRoot(root, params) {
687
700
  await writeFileFallback(root, params);
688
701
  return;
689
702
  }
690
- const pinned = await resolvePinnedWriteTargetInRoot(root, params.relativePath, params.mode, params.denyMutations);
703
+ const pinned = await resolvePinnedWriteTargetInRoot(root, params.relativePath, params.mode, params.denyMutations, params.overwrite);
691
704
  await serializePathWrite(pinned.targetPath, async () => {
692
705
  await commitPinnedWriteInRoot(root, pinned, params);
693
706
  });
@@ -743,7 +756,7 @@ async function commitPinnedWriteInRoot(root, pinned, params) {
743
756
  async function copyFileInRoot(root, params) {
744
757
  assertValidRootRelativePath(params.relativePath);
745
758
  assertNoNulPathInput(params.sourcePath, "source path contains a NUL byte");
746
- const source = await openVerifiedLocalFile(params.sourcePath, {
759
+ const { opened: source, identity: sourceIdentity } = await openVerifiedLocalFile(params.sourcePath, {
747
760
  hardlinks: params.sourceHardlinks,
748
761
  });
749
762
  if (params.maxBytes !== undefined && source.stat.size > params.maxBytes) {
@@ -754,7 +767,7 @@ async function copyFileInRoot(root, params) {
754
767
  await serializePathWrite(rootWriteQueueKey(root, params.relativePath), async () => {
755
768
  const pinned = await resolvePinnedWriteTargetInRoot(root, params.relativePath, params.mode, params.denyMutations);
756
769
  await serializePathWrite(pinned.targetPath, async () => {
757
- await assertCopySourceCurrent(source);
770
+ await assertCopySourceCurrent(source, sourceIdentity);
758
771
  let identity;
759
772
  try {
760
773
  identity = await runPinnedWriteHelper({
@@ -773,7 +786,7 @@ async function copyFileInRoot(root, params) {
773
786
  throw normalizePinnedWriteError(error);
774
787
  }
775
788
  try {
776
- await assertCopySourcePathCurrent(source);
789
+ await assertCopySourcePathCurrent(source, sourceIdentity);
777
790
  }
778
791
  catch (error) {
779
792
  await removePathIfIdentityUnchanged(pinned.targetPath, identity).catch(() => undefined);
@@ -786,18 +799,18 @@ async function copyFileInRoot(root, params) {
786
799
  await source.handle.close().catch(() => { });
787
800
  }
788
801
  }
789
- async function assertCopySourceCurrent(source) {
790
- const opened = await source.handle.stat();
791
- if (!sameFileIdentity(opened, source.stat)) {
792
- throw new FsSafeError("path-mismatch", "copy source descriptor changed");
793
- }
794
- await assertCopySourcePathCurrent(source);
802
+ async function assertCopySourceCurrent(source, identity) {
803
+ await inspectFileIdentity(() => source.handle.stat({ bigint: true }), identity);
804
+ await assertCopySourcePathCurrent(source, identity);
795
805
  }
796
- async function assertCopySourcePathCurrent(source) {
797
- const current = await fs.lstat(source.realPath);
798
- if (current.isSymbolicLink() || !current.isFile() || !sameFileIdentity(current, source.stat)) {
799
- throw new FsSafeError("path-mismatch", "copy source path changed");
800
- }
806
+ async function assertCopySourcePathCurrent(source, identity) {
807
+ await inspectFileIdentity(async () => {
808
+ const current = await fs.lstat(source.realPath, { bigint: true });
809
+ if (current.isSymbolicLink() || !current.isFile()) {
810
+ throw new FsSafeError("path-mismatch", "copy source path changed");
811
+ }
812
+ return current;
813
+ }, identity);
801
814
  }
802
815
  async function removePathIfIdentityUnchanged(targetPath, identity) {
803
816
  const parentGuard = await createAsyncDirectoryGuard(path.dirname(targetPath));
@@ -809,7 +822,7 @@ async function removePathIfIdentityUnchanged(targetPath, identity) {
809
822
  await fs.rm(targetPath);
810
823
  });
811
824
  }
812
- async function resolvePinnedWriteTargetInRoot(root, relativePath, requestedMode, denyMutations) {
825
+ async function resolvePinnedWriteTargetInRoot(root, relativePath, requestedMode, denyMutations, overwrite = true) {
813
826
  const { rootReal, rootWithSep, resolved } = await resolveGuardedWritePathInRoot(root, {
814
827
  relativePath,
815
828
  denyMutations,
@@ -827,22 +840,40 @@ async function resolvePinnedWriteTargetInRoot(root, relativePath, requestedMode,
827
840
  if (!basename || basename === "." || basename === "/") {
828
841
  throw new FsSafeError("invalid-path", "invalid target path");
829
842
  }
843
+ // Exclusive publication cannot inherit an existing file's mode. Keep type
844
+ // and alias checks, but never open a competing owner's record just to reject it.
845
+ if (!overwrite) {
846
+ try {
847
+ const existing = await fs.stat(resolved);
848
+ if (!existing.isFile())
849
+ throw new FsSafeError("not-file", "not a file");
850
+ if (existing.nlink > 1)
851
+ throw hardlinkedPathNotAllowedError();
852
+ throw new FsSafeError("already-exists", "file already exists");
853
+ }
854
+ catch (error) {
855
+ if (!isNotFoundPathError(error))
856
+ throw error;
857
+ }
858
+ }
830
859
  let mode = requestedMode ?? 0o600;
831
860
  try {
832
- const opened = await openFileInRoot(root, {
833
- relativePath,
834
- hardlinks: "reject",
835
- nonBlockingRead: true,
836
- symlinks: "follow-within-root",
837
- });
838
- try {
839
- mode = requestedMode ?? (opened.stat.mode & 0o777);
840
- if (!isPathInside(rootWithSep, opened.realPath)) {
841
- throw outsideWorkspaceError();
861
+ if (overwrite) {
862
+ const opened = await openFileInRoot(root, {
863
+ relativePath,
864
+ hardlinks: "reject",
865
+ nonBlockingRead: true,
866
+ symlinks: "follow-within-root",
867
+ });
868
+ try {
869
+ mode = requestedMode ?? (opened.stat.mode & 0o777);
870
+ if (!isPathInside(rootWithSep, opened.realPath)) {
871
+ throw outsideWorkspaceError();
872
+ }
873
+ }
874
+ finally {
875
+ await opened.handle.close().catch(() => { });
842
876
  }
843
- }
844
- finally {
845
- await opened.handle.close().catch(() => { });
846
877
  }
847
878
  }
848
879
  catch (err) {
@@ -1 +1 @@
1
- {"version":3,"file":"secret-file.d.ts","sourceRoot":"","sources":["../src/secret-file.ts"],"names":[],"mappings":"AAWA,OAAO,EAML,KAAK,qBAAqB,EAC3B,MAAM,yBAAyB,CAAC;AAIjC,eAAO,MAAM,uBAAuB,MAAQ,CAAC;AAC7C,eAAO,MAAM,wBAAwB,MAAQ,CAAC;AAE9C,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,qBAA0B,GAClC,MAAM,CAsER;AAED,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,qBAA0B,GAClC,MAAM,GAAG,SAAS,CAUpB;AAoID,KAAK,qBAAqB,GAAG;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,UAAU,CAAC;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAkEF,wBAAsB,qBAAqB,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAKxF;AAED,wBAAsB,sBAAsB,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAezF"}
1
+ {"version":3,"file":"secret-file.d.ts","sourceRoot":"","sources":["../src/secret-file.ts"],"names":[],"mappings":"AAaA,OAAO,EAML,KAAK,qBAAqB,EAC3B,MAAM,yBAAyB,CAAC;AAIjC,eAAO,MAAM,uBAAuB,MAAQ,CAAC;AAC7C,eAAO,MAAM,wBAAwB,MAAQ,CAAC;AAE9C,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,qBAA0B,GAClC,MAAM,CAwER;AAED,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,qBAA0B,GAClC,MAAM,GAAG,SAAS,CAUpB;AAkID,KAAK,qBAAqB,GAAG;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,UAAU,CAAC;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAkEF,wBAAsB,qBAAqB,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAKxF;AAED,wBAAsB,sBAAsB,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAezF"}