@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,9 +1,13 @@
1
- import fs from "node:fs";
1
+ import fs, {} from "node:fs";
2
2
  import path from "node:path";
3
- import { sameFileIdentity } from "./file-identity.js";
3
+ import { normalizeMaxBytes } from "./byte-budget.js";
4
+ import { syncQueueDirectoryCreation } from "./json-durable-queue-directory.js";
5
+ import { acknowledgeDurableQueueEntry, claimDurableQueueEntry, completeDeliveredQueueEntry, moveDurableQueueEntryToFailed, } from "./json-durable-queue-ownership.js";
4
6
  import { stringifyJsonDocument } from "./json-stringify.js";
7
+ import { resolveReadOpenFlags } from "./read-open-flags.js";
5
8
  import { replaceFileAtomic } from "./replace-file.js";
6
9
  import { assertSafePathSegment } from "./safe-path-segment.js";
10
+ import { inspectFileIdentity } from "./strict-file-identity.js";
7
11
  export const DEFAULT_JSON_DURABLE_QUEUE_ENTRY_MAX_BYTES = 16 * 1024 * 1024;
8
12
  function getErrnoCode(error) {
9
13
  return error && typeof error === "object" && "code" in error
@@ -46,6 +50,7 @@ export function resolveJsonDurableQueueEntryPaths(queueDir, id) {
46
50
  return {
47
51
  jsonPath: path.join(queueDir, `${id}.json`),
48
52
  deliveredPath: path.join(queueDir, `${id}.delivered`),
53
+ processingPath: path.join(queueDir, `${id}.processing`),
49
54
  };
50
55
  }
51
56
  export async function ensureJsonDurableQueueDirs(params) {
@@ -61,6 +66,7 @@ async function ensureJsonDurableQueueDir(dir, validationRoot) {
61
66
  await fs.promises.mkdir(dir, { recursive: true, mode: 0o700 });
62
67
  await assertNoSymlinkDirectorySegments(root, dir, false);
63
68
  await chmodQueueDirectory(dir);
69
+ await syncQueueDirectoryCreation(dir, root.path);
64
70
  }
65
71
  async function assertJsonDurableQueueDir(dir, validationRoot) {
66
72
  const root = validationRoot
@@ -208,35 +214,55 @@ export async function writeJsonDurableQueueEntry(params) {
208
214
  content: stringifyJsonDocument(params.entry, null, 2),
209
215
  mode: 0o600,
210
216
  tempPrefix: params.tempPrefix,
217
+ syncTempFile: true,
218
+ syncParentDir: true,
211
219
  });
212
220
  }
213
- async function readBoundedUtf8File(params) {
214
- const initialStat = await fs.promises.lstat(params.filePath);
215
- if (initialStat.isSymbolicLink() || !initialStat.isFile()) {
216
- throw new Error("queue entry is not a regular file");
217
- }
218
- if (initialStat.nlink > 1) {
219
- throw new Error("queue entry hardlinks are not allowed");
221
+ async function replaceJsonDurableQueueEntry(params) {
222
+ await replaceFileAtomic({
223
+ filePath: params.filePath,
224
+ content: stringifyJsonDocument(params.entry, null, 2),
225
+ mode: 0o600,
226
+ tempPrefix: params.tempPrefix,
227
+ syncTempFile: true,
228
+ syncParentDir: true,
229
+ });
230
+ }
231
+ async function inspectQueueEntry(inspect, maxBytes, expected) {
232
+ let inspectionFailed = false;
233
+ try {
234
+ return await inspectFileIdentity(async () => {
235
+ try {
236
+ const stat = await inspect();
237
+ if (stat.isSymbolicLink() || !stat.isFile()) {
238
+ throw new Error("queue entry is not a regular file");
239
+ }
240
+ if (stat.nlink > 1n)
241
+ throw new Error("queue entry hardlinks are not allowed");
242
+ if (stat.size > maxBytes)
243
+ throw new Error(`queue entry exceeds ${maxBytes} bytes`);
244
+ return stat;
245
+ }
246
+ catch (error) {
247
+ inspectionFailed = true;
248
+ throw error;
249
+ }
250
+ }, expected);
220
251
  }
221
- if (initialStat.size > params.maxBytes) {
222
- throw new Error(`queue entry exceeds ${params.maxBytes} bytes`);
252
+ catch (error) {
253
+ // Translate only the identity helper's rejection, never inspection errors.
254
+ if (inspectionFailed)
255
+ throw error;
256
+ throw new Error("queue entry changed during read", { cause: error });
223
257
  }
224
- const noFollow = typeof fs.constants.O_NOFOLLOW === "number" && process.platform !== "win32"
225
- ? fs.constants.O_NOFOLLOW
226
- : 0;
227
- const handle = await fs.promises.open(params.filePath, fs.constants.O_RDONLY | noFollow);
258
+ }
259
+ async function readBoundedUtf8File(params) {
260
+ const inspectPath = () => fs.promises.lstat(params.filePath, { bigint: true });
261
+ const initialStat = await inspectQueueEntry(inspectPath, params.maxBytes);
262
+ const handle = await fs.promises.open(params.filePath, resolveReadOpenFlags());
228
263
  try {
229
- const openedStat = await handle.stat();
230
- const pathStat = await fs.promises.lstat(params.filePath);
231
- if (!openedStat.isFile() ||
232
- pathStat.isSymbolicLink() ||
233
- !pathStat.isFile() ||
234
- openedStat.nlink > 1 ||
235
- pathStat.nlink > 1 ||
236
- !sameFileIdentity(initialStat, openedStat) ||
237
- !sameFileIdentity(pathStat, openedStat)) {
238
- throw new Error("queue entry changed during read");
239
- }
264
+ const openedStat = await inspectQueueEntry(() => handle.stat({ bigint: true }), params.maxBytes, initialStat);
265
+ await inspectQueueEntry(inspectPath, params.maxBytes, openedStat);
240
266
  const chunks = [];
241
267
  const scratch = Buffer.allocUnsafe(Math.min(64 * 1024, params.maxBytes + 1));
242
268
  let total = 0;
@@ -259,35 +285,26 @@ async function readBoundedUtf8File(params) {
259
285
  export async function readJsonDurableQueueEntry(filePath, options = {}) {
260
286
  return JSON.parse(await readBoundedUtf8File({
261
287
  filePath,
262
- maxBytes: options.maxBytes ?? DEFAULT_JSON_DURABLE_QUEUE_ENTRY_MAX_BYTES,
288
+ maxBytes: normalizeMaxBytes(options.maxBytes, {
289
+ defaultValue: DEFAULT_JSON_DURABLE_QUEUE_ENTRY_MAX_BYTES,
290
+ }),
263
291
  }));
264
292
  }
265
293
  export async function ackJsonDurableQueueEntry(paths) {
266
- try {
267
- await fs.promises.rename(paths.jsonPath, paths.deliveredPath);
268
- }
269
- catch (error) {
270
- if (getErrnoCode(error) === "ENOENT") {
271
- await unlinkBestEffort(paths.deliveredPath);
272
- return;
273
- }
274
- throw error;
275
- }
276
- await unlinkBestEffort(paths.deliveredPath);
294
+ await acknowledgeDurableQueueEntry(paths);
277
295
  }
278
296
  export async function loadJsonDurableQueueEntry(params) {
279
297
  try {
280
- const stat = await fs.promises.lstat(params.paths.jsonPath);
281
- if (!stat.isFile()) {
298
+ const claimedPath = await claimDurableQueueEntry(params.paths);
299
+ if (!claimedPath)
282
300
  return null;
283
- }
284
- const raw = await readJsonDurableQueueEntry(params.paths.jsonPath, {
301
+ const raw = await readJsonDurableQueueEntry(claimedPath, {
285
302
  maxBytes: params.maxBytes,
286
303
  });
287
304
  const result = params.read ? await params.read(raw, params.paths.jsonPath) : { entry: raw };
288
305
  if (result.migrated) {
289
- await writeJsonDurableQueueEntry({
290
- filePath: params.paths.jsonPath,
306
+ await replaceJsonDurableQueueEntry({
307
+ filePath: claimedPath,
291
308
  entry: result.entry,
292
309
  tempPrefix: params.tempPrefix,
293
310
  });
@@ -315,28 +332,41 @@ export async function loadPendingJsonDurableQueueEntries(options) {
315
332
  const now = Date.now();
316
333
  for (const file of files) {
317
334
  if (file.endsWith(".delivered")) {
318
- await unlinkBestEffort(path.join(options.queueDir, file));
335
+ const id = file.slice(0, -".delivered".length);
336
+ await completeDeliveredQueueEntry(resolveJsonDurableQueueEntryPaths(options.queueDir, id));
319
337
  }
320
338
  else if (options.cleanupTmpMaxAgeMs !== undefined && file.endsWith(".tmp")) {
321
339
  await unlinkStaleTmpBestEffort(path.join(options.queueDir, file), now, options.cleanupTmpMaxAgeMs);
322
340
  }
323
341
  }
324
- const entries = [];
342
+ const ids = [];
343
+ const seenIds = new Set();
325
344
  for (const file of files) {
326
- if (!file.endsWith(".json")) {
345
+ const suffix = file.endsWith(".processing")
346
+ ? ".processing"
347
+ : file.endsWith(".json")
348
+ ? ".json"
349
+ : null;
350
+ if (!suffix)
327
351
  continue;
328
- }
329
- const filePath = path.join(options.queueDir, file);
352
+ const id = file.slice(0, -suffix.length);
353
+ if (seenIds.has(id))
354
+ continue;
355
+ seenIds.add(id);
356
+ ids.push(id);
357
+ }
358
+ const entries = [];
359
+ for (const id of ids) {
330
360
  try {
331
- const stat = await fs.promises.lstat(filePath);
332
- if (!stat.isFile()) {
361
+ const paths = resolveJsonDurableQueueEntryPaths(options.queueDir, id);
362
+ const claimedPath = await claimDurableQueueEntry(paths);
363
+ if (!claimedPath)
333
364
  continue;
334
- }
335
- const raw = await readJsonDurableQueueEntry(filePath, { maxBytes: options.maxBytes });
336
- const result = options.read ? await options.read(raw, filePath) : { entry: raw };
365
+ const raw = await readJsonDurableQueueEntry(claimedPath, { maxBytes: options.maxBytes });
366
+ const result = options.read ? await options.read(raw, paths.jsonPath) : { entry: raw };
337
367
  if (result.migrated) {
338
- await writeJsonDurableQueueEntry({
339
- filePath,
368
+ await replaceJsonDurableQueueEntry({
369
+ filePath: claimedPath,
340
370
  entry: result.entry,
341
371
  tempPrefix: options.tempPrefix,
342
372
  });
@@ -354,5 +384,8 @@ export async function moveJsonDurableQueueEntryToFailed(params) {
354
384
  const roots = await queueValidationRoots(params.queueDir, params.failedDir);
355
385
  await assertJsonDurableQueueDir(params.queueDir, roots.queueRoot);
356
386
  await ensureJsonDurableQueueDir(params.failedDir, roots.failedRoot);
357
- await fs.promises.rename(path.join(params.queueDir, `${params.id}.json`), path.join(params.failedDir, `${params.id}.json`));
387
+ await moveDurableQueueEntryToFailed({
388
+ paths: resolveJsonDurableQueueEntryPaths(params.queueDir, params.id),
389
+ failedPath: path.join(params.failedDir, `${params.id}.json`),
390
+ });
358
391
  }
@@ -1 +1 @@
1
- {"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../src/json.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../src/json.ts"],"names":[],"mappings":"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"}
package/dist/json.js CHANGED
@@ -3,9 +3,11 @@ import fsSync from "node:fs";
3
3
  import path from "node:path";
4
4
  import { readFileDescriptorBoundedSync } from "./bounded-read.js";
5
5
  import { FsSafeError } from "./errors.js";
6
+ import { sameFileIdentityForCleanup } from "./file-identity.js";
6
7
  import { stringifyJsonDocument } from "./json-stringify.js";
7
8
  import { readRegularFile, readRegularFileSync, statRegularFile } from "./regular-file.js";
8
9
  import { openRootFileSync } from "./root-file.js";
10
+ import { resolveReadOpenFlags } from "./read-open-flags.js";
9
11
  import { writeTextAtomic } from "./text-atomic.js";
10
12
  import { sleep } from "./timing.js";
11
13
  const READ_RETRY_MAX_ATTEMPTS = 5;
@@ -48,19 +50,35 @@ async function readRegularFileIfExistsWithRetry(filePath, options = {}) {
48
50
  }
49
51
  const JSON_FILE_MODE = 0o600;
50
52
  const JSON_DIR_MODE = 0o700;
51
- const SUPPORTS_SYNC_NOFOLLOW = process.platform !== "win32" && "O_NOFOLLOW" in fsSync.constants;
52
53
  function getErrorCode(err) {
53
54
  return err instanceof Error ? err.code : undefined;
54
55
  }
55
- function trySetSecureMode(pathname) {
56
+ function trySetSecureMode(pathname, expectedIdentity) {
56
57
  let fd;
57
58
  try {
58
- fd = fsSync.openSync(pathname, fsSync.constants.O_RDONLY |
59
- (SUPPORTS_SYNC_NOFOLLOW ? fsSync.constants.O_NOFOLLOW : 0));
59
+ const before = fsSync.lstatSync(pathname, { bigint: true });
60
+ if (before.isSymbolicLink() ||
61
+ !before.isFile() ||
62
+ before.nlink !== 1n ||
63
+ !sameFileIdentityForCleanup(before, expectedIdentity)) {
64
+ return;
65
+ }
66
+ fd = fsSync.openSync(pathname, resolveReadOpenFlags());
67
+ const opened = fsSync.fstatSync(fd, { bigint: true });
68
+ const current = fsSync.lstatSync(pathname, { bigint: true });
69
+ if (!opened.isFile() ||
70
+ opened.nlink !== 1n ||
71
+ current.isSymbolicLink() ||
72
+ !current.isFile() ||
73
+ current.nlink !== 1n ||
74
+ !sameFileIdentityForCleanup(opened, expectedIdentity) ||
75
+ !sameFileIdentityForCleanup(current, opened)) {
76
+ return;
77
+ }
60
78
  fsSync.fchmodSync(fd, JSON_FILE_MODE);
61
79
  }
62
80
  catch {
63
- // best-effort on platforms without chmod support
81
+ // Best-effort mode application never falls back to a mutable pathname.
64
82
  }
65
83
  finally {
66
84
  if (fd !== undefined) {
@@ -129,6 +147,7 @@ function writeTempJsonFile(pathname, payload) {
129
147
  try {
130
148
  fsSync.writeFileSync(fd, payload, "utf8");
131
149
  fsSync.fsyncSync(fd);
150
+ return fsSync.fstatSync(fd, { bigint: true });
132
151
  }
133
152
  finally {
134
153
  fsSync.closeSync(fd);
@@ -147,16 +166,16 @@ export function tryReadJsonSync(pathname, options = {}) {
147
166
  }
148
167
  }
149
168
  export function writeJsonSync(pathname, data) {
150
- const targetPath = pathname;
151
- const tmpPath = `${targetPath}.${randomUUID()}.tmp`;
169
+ // Keep literal parent segments so staging follows the same symlinks as the target.
170
+ const tmpPath = path.format({ ...path.parse(pathname), base: `.fs-safe-${randomUUID()}.tmp` });
152
171
  const payload = `${stringifyJsonDocument(data, null, 2)}\n`;
153
- fsSync.mkdirSync(path.dirname(targetPath), { recursive: true, mode: JSON_DIR_MODE });
172
+ fsSync.mkdirSync(path.dirname(pathname), { recursive: true, mode: JSON_DIR_MODE });
154
173
  try {
155
- writeTempJsonFile(tmpPath, payload);
156
- trySetSecureMode(tmpPath);
157
- renameJsonFileWithFallback(tmpPath, targetPath);
158
- trySetSecureMode(targetPath);
159
- trySyncDirectory(targetPath);
174
+ const tempIdentity = writeTempJsonFile(tmpPath, payload);
175
+ trySetSecureMode(tmpPath, tempIdentity);
176
+ renameJsonFileWithFallback(tmpPath, pathname);
177
+ trySetSecureMode(pathname, tempIdentity);
178
+ trySyncDirectory(pathname);
160
179
  }
161
180
  finally {
162
181
  try {
@@ -1 +1 @@
1
- {"version":3,"file":"local-roots.d.ts","sourceRoot":"","sources":["../src/local-roots.ts"],"names":[],"mappings":"AAMA,OAAO,EAAQ,KAAK,cAAc,EAAE,KAAK,UAAU,EAAE,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;AAE3F,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG,UAAU,GAAG;IAC9C,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,oCAAoC,GAAG,sBAAsB,GAAG;IAC1E,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,6BAA6B,GAAG,sBAAsB,GAAG;IACnE,SAAS,CAAC,EAAE,cAAc,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,EAAE,aAAa,CAAC;CAC1B,CAAC;AAqGF,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,oCAAoC,GAC5C,oBAAoB,GAAG,IAAI,CA6B7B;AAED,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CA2CtC"}
1
+ {"version":3,"file":"local-roots.d.ts","sourceRoot":"","sources":["../src/local-roots.ts"],"names":[],"mappings":"AAOA,OAAO,EAAQ,KAAK,cAAc,EAAE,KAAK,UAAU,EAAE,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;AAE3F,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG,UAAU,GAAG;IAC9C,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,oCAAoC,GAAG,sBAAsB,GAAG;IAC1E,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,6BAA6B,GAAG,sBAAsB,GAAG;IACnE,SAAS,CAAC,EAAE,cAAc,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,EAAE,aAAa,CAAC;CAC1B,CAAC;AAqGF,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,oCAAoC,GAC5C,oBAAoB,GAAG,IAAI,CA6B7B;AAED,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CA4CtC"}
@@ -1,5 +1,6 @@
1
1
  import fsSync from "node:fs";
2
2
  import path from "node:path";
3
+ import { normalizeMaxBytes } from "./byte-budget.js";
3
4
  import { FsSafeError } from "./errors.js";
4
5
  import { expandHomePrefix, resolveUserPath } from "./home-dir.js";
5
6
  import { isFileUrl, safeFileURLToPath } from "./local-file-access.js";
@@ -129,6 +130,7 @@ export function resolveLocalPathFromRootsSync(options) {
129
130
  return null;
130
131
  }
131
132
  export async function readLocalFileFromRoots(options) {
133
+ const maxBytes = normalizeMaxBytes(options.maxBytes);
132
134
  const label = options.label ?? "local roots";
133
135
  const requestedPath = path.resolve(resolveLocalPathInput(options.filePath, "file path"));
134
136
  const rootDirs = options.roots.map((rootEntry) => resolveLocalRootInput(rootEntry, label));
@@ -147,8 +149,8 @@ export async function readLocalFileFromRoots(options) {
147
149
  };
148
150
  // Leave maxBytes absent when the caller omits it so Root's own default
149
151
  // cap remains in force instead of being overwritten by undefined.
150
- if (options.maxBytes !== undefined) {
151
- readOptions.maxBytes = options.maxBytes;
152
+ if (maxBytes !== undefined) {
153
+ readOptions.maxBytes = maxBytes;
152
154
  }
153
155
  // A trusted root symlink has two valid spellings. Preserve the caller's
154
156
  // lexical spelling when possible so Root.read() still enforces its
@@ -1,3 +1,4 @@
1
+ import type { TarMeterLimits } from "./archive-limits.js";
1
2
  export interface NativeFileHash {
2
3
  bytes: number;
3
4
  digest: string;
@@ -31,6 +32,11 @@ export interface NativeOpenBeneathResult {
31
32
  fd: number;
32
33
  containment: "kernel-atomic" | "best-effort";
33
34
  }
35
+ export interface NativeOwnedTreeRemovalResult {
36
+ outcome?: "removed" | "preserved";
37
+ errorCode?: string;
38
+ errorMessage?: string;
39
+ }
34
40
  export interface NativeWindowsAccessControlEntry {
35
41
  sid: string;
36
42
  mask: number;
@@ -68,14 +74,17 @@ export interface NativeBinding {
68
74
  cloneFileExclusive(sourceFd: number, targetRootFd: number, targetRelPath: string): number;
69
75
  copyFileRangeExclusive(sourceFd: number, targetRootFd: number, targetRelPath: string): Promise<NativeCopyResult>;
70
76
  createPrivateDirectory(path: string): void;
71
- extractArchiveNative(path: string, kind: string, rootFd: number, plan: NativeArchivePlanEntry[], maxMetaEntryBytes: number, signal: AbortSignal): Promise<void>;
77
+ extractArchiveNative(path: string, kind: string, rootFd: number, plan: NativeArchivePlanEntry[], limits: TarMeterLimits, signal: AbortSignal): Promise<void>;
72
78
  fstatIdentity(fd: number): NativeFileIdentity;
73
- inspectArchiveNative(path: string, kind: string, maxEntries: number, maxMetaEntryBytes: number, maxManifestBytes: number, signal: AbortSignal): Promise<NativeArchiveEntry[]>;
79
+ inspectArchiveNative(path: string, kind: string, limits: TarMeterLimits, signal: AbortSignal): Promise<NativeArchiveEntry[]>;
74
80
  linkBeneath(sourceRootFd: number, sourceRelPath: string, targetRootFd: number, targetRelPath: string): void;
75
81
  mkdirBeneath(rootFd: number, relPath: string, mode: number): void;
76
82
  openBeneath(rootFd: number, relPath: string, flags: number): NativeOpenBeneathResult;
77
- readArchiveEntryNative(path: string, kind: string, requested: string, maxBytes: number, maxEntries: number, maxMetaEntryBytes: number, signal: AbortSignal): Promise<Buffer>;
83
+ readArchiveEntryNative(path: string, kind: string, requested: string, maxBytes: number, limits: TarMeterLimits, signal: AbortSignal): Promise<Buffer>;
78
84
  readOwnerAndDacl(path: string): NativeWindowsSecurityFacts;
85
+ ownedTreeRemovalAvailable?(parentFd: number): boolean;
86
+ removeOwnedTree?(parentFd: number, basename: string, directoryFd: number): Promise<NativeOwnedTreeRemovalResult>;
87
+ removeOwnedTreeSync?(parentFd: number, basename: string, directoryFd: number): NativeOwnedTreeRemovalResult;
79
88
  renameNoReplace(sourceRootFd: number, sourceRelPath: string, targetRootFd: number, targetRelPath: string): void;
80
89
  renameReplace(sourceRootFd: number, sourceRelPath: string, targetRootFd: number, targetRelPath: string): void;
81
90
  sha256File(fd: number): Promise<NativeFileHash>;
@@ -1 +1 @@
1
- {"version":3,"file":"native-binding.d.ts","sourceRoot":"","sources":["../src/native-binding.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"native-binding.d.ts","sourceRoot":"","sources":["../src/native-binding.ts"],"names":[],"mappings":"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"}
@@ -24,7 +24,7 @@ export async function runPinnedWriteWindows(binding, params, root, parentFd, par
24
24
  let renamed = false;
25
25
  let completed = false;
26
26
  try {
27
- tempName = `.${params.basename}.${randomUUID()}.native.tmp`;
27
+ tempName = `.fs-safe-${randomUUID()}.tmp`;
28
28
  tempFd = binding.openBeneath(parentFd, tempName, nativeOpenFlags(fsSync.constants.O_WRONLY | fsSync.constants.O_CREAT | fsSync.constants.O_EXCL)).fd;
29
29
  tempIdentity = fsSync.fstatSync(tempFd);
30
30
  const verificationIdentity = fsSync.fstatSync(tempFd, { bigint: true });
@@ -0,0 +1,7 @@
1
+ import type { BigIntStats } from "node:fs";
2
+ import type { FileHandle } from "node:fs/promises";
3
+ export declare function recordFileOpenFailure(error: unknown, filePath: string): never;
4
+ export declare function openedPathResolutionError(error?: Error): Error;
5
+ export declare function recordPreOpenFileChange(error: unknown, handle: FileHandle, filePath: string, before: BigIntStats | undefined, opened: BigIntStats | undefined): Promise<void>;
6
+ export declare function recordOpenedFileFailure(error: unknown, handle: FileHandle, filePath: string, identity: BigIntStats): Promise<void>;
7
+ //# sourceMappingURL=opened-file-failure.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"opened-file-failure.d.ts","sourceRoot":"","sources":["../src/opened-file-failure.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAC3C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAKnD,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,KAAK,CAG7E;AAED,wBAAgB,yBAAyB,CACvC,KAAK,GAAE,KAA8E,GACpF,KAAK,CAGP;AAED,wBAAsB,uBAAuB,CAC3C,KAAK,EAAE,OAAO,EACd,MAAM,EAAE,UAAU,EAClB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,WAAW,GAAG,SAAS,EAC/B,MAAM,EAAE,WAAW,GAAG,SAAS,GAC9B,OAAO,CAAC,IAAI,CAAC,CAcf;AAED,wBAAsB,uBAAuB,CAC3C,KAAK,EAAE,OAAO,EACd,MAAM,EAAE,UAAU,EAClB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,WAAW,GACpB,OAAO,CAAC,IAAI,CAAC,CASf"}
@@ -0,0 +1,41 @@
1
+ import { FsSafeError } from "./errors.js";
2
+ import { inspectFileIdentity } from "./strict-file-identity.js";
3
+ import { isFileObservationFailure, recordFileObservationFailure } from "./file-observation.js";
4
+ export function recordFileOpenFailure(error, filePath) {
5
+ recordFileObservationFailure(error, `open:${filePath}`);
6
+ throw error;
7
+ }
8
+ export function openedPathResolutionError(error = new FsSafeError("path-mismatch", "unable to resolve opened file path")) {
9
+ recordFileObservationFailure(error, "resolution");
10
+ return error;
11
+ }
12
+ export async function recordPreOpenFileChange(error, handle, filePath, before, opened) {
13
+ if (!isFileObservationFailure(error, "identity") || !before?.isFile() || !opened?.isFile() ||
14
+ ![0n, 1n].includes(before.nlink) || ![0n, 1n].includes(opened.nlink))
15
+ return;
16
+ try {
17
+ await inspectFileIdentity(async () => before);
18
+ const current = await inspectFileIdentity(() => handle.stat({ bigint: true }), opened);
19
+ if (current.isFile() && [0n, 1n].includes(current.nlink) &&
20
+ (before.dev !== current.dev || before.ino !== current.ino)) {
21
+ // A stale pathname sample is not proof that its former inode was unlinked.
22
+ recordFileObservationFailure(error, `changed:${filePath}`);
23
+ }
24
+ }
25
+ catch {
26
+ // Unknown, changed, or closed descriptor evidence never permits a discard.
27
+ }
28
+ }
29
+ export async function recordOpenedFileFailure(error, handle, filePath, identity) {
30
+ if ((!isFileObservationFailure(error, "resolution") && !isFileObservationFailure(error, "identity")) ||
31
+ !identity.isFile() || identity.nlink > 1n)
32
+ return;
33
+ try {
34
+ const current = await inspectFileIdentity(() => handle.stat({ bigint: true }), identity);
35
+ if (current.isFile() && current.nlink === 0n)
36
+ recordFileObservationFailure(error, `unlinked:${filePath}`);
37
+ }
38
+ catch {
39
+ // A closed, changed, or unknown descriptor provides no retry evidence.
40
+ }
41
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"opened-realpath.d.ts","sourceRoot":"","sources":["../src/opened-realpath.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAGnD,OAAO,EAAoB,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAG7E,wBAAsB,kCAAkC,CACtD,MAAM,EAAE,UAAU,EAClB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,CAAC,CAGjB;AAED,wBAAsB,8BAA8B,CAClD,EAAE,EAAE,MAAM,EACV,UAAU,EAAE,gBAAgB,EAC5B,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,CAAC,CAqCjB"}
1
+ {"version":3,"file":"opened-realpath.d.ts","sourceRoot":"","sources":["../src/opened-realpath.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAGnD,OAAO,EAAoB,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAG7E,wBAAsB,kCAAkC,CACtD,MAAM,EAAE,UAAU,EAClB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,CAAC,CAGjB;AAED,wBAAsB,8BAA8B,CAClD,EAAE,EAAE,MAAM,EACV,UAAU,EAAE,gBAAgB,EAC5B,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,CAAC,CA2CjB"}
@@ -1,6 +1,6 @@
1
1
  import fs from "node:fs/promises";
2
2
  import path from "node:path";
3
- import { FsSafeError } from "./errors.js";
3
+ import { openedPathResolutionError } from "./opened-file-failure.js";
4
4
  import { sameFileIdentity } from "./file-identity.js";
5
5
  import { isNotFoundPathError } from "./path.js";
6
6
  export async function resolveOpenedFileRealPathForHandle(handle, ioPath) {
@@ -36,6 +36,12 @@ export async function resolveOpenedFileRealPathForFd(fd, handleStat, ioPath) {
36
36
  }
37
37
  catch (err) {
38
38
  if (!isNotFoundPathError(err)) {
39
+ // Windows can fail here on a deleted-but-open file. Brand only this
40
+ // resolver operation; the caller must still prove unlink on the same fd.
41
+ if (process.platform === "win32" && err instanceof Error &&
42
+ ["EPERM", "EBADF"].includes(err.code ?? "")) {
43
+ throw openedPathResolutionError(err);
44
+ }
39
45
  throw err;
40
46
  }
41
47
  }
@@ -43,7 +49,7 @@ export async function resolveOpenedFileRealPathForFd(fd, handleStat, ioPath) {
43
49
  if (parentResolved) {
44
50
  return parentResolved;
45
51
  }
46
- throw new FsSafeError("path-mismatch", "unable to resolve opened file path");
52
+ throw openedPathResolutionError();
47
53
  }
48
54
  async function resolveOpenedFileRealPathFromParent(handleStat, ioPath, statOptions) {
49
55
  let parentReal;
@@ -1 +1 @@
1
- {"version":3,"file":"output-sibling.d.ts","sourceRoot":"","sources":["../src/output-sibling.ts"],"names":[],"mappings":"AA+FA,wBAAsB,2BAA2B,CAAC,CAAC,EAAE,MAAM,EAAE;IAC3D,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IACxC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,GAAG,OAAO,CAAC,CAAC,CAAC,CA8Cb"}
1
+ {"version":3,"file":"output-sibling.d.ts","sourceRoot":"","sources":["../src/output-sibling.ts"],"names":[],"mappings":"AAoBA,wBAAsB,2BAA2B,CAAC,CAAC,EAAE,MAAM,EAAE;IAC3D,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IACxC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,GAAG,OAAO,CAAC,CAAC,CAAC,CAYb"}