@openclaw/fs-safe 0.9.0 → 0.11.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 (326) hide show
  1. package/CHANGELOG.md +134 -0
  2. package/LICENSE +1 -0
  3. package/README.md +52 -4
  4. package/dist/absolute-path.d.ts.map +1 -1
  5. package/dist/absolute-path.js +3 -2
  6. package/dist/advanced.d.ts +5 -0
  7. package/dist/advanced.d.ts.map +1 -1
  8. package/dist/advanced.js +5 -0
  9. package/dist/archive-crc32.d.ts.map +1 -1
  10. package/dist/archive-crc32.js +6 -1
  11. package/dist/archive-deadline.d.ts.map +1 -1
  12. package/dist/archive-deadline.js +3 -4
  13. package/dist/archive-durability.d.ts +6 -6
  14. package/dist/archive-durability.d.ts.map +1 -1
  15. package/dist/archive-durability.js +1 -1
  16. package/dist/archive-entry.d.ts.map +1 -1
  17. package/dist/archive-entry.js +4 -5
  18. package/dist/archive-gzip-tail.d.ts +3 -0
  19. package/dist/archive-gzip-tail.d.ts.map +1 -1
  20. package/dist/archive-gzip-tail.js +23 -3
  21. package/dist/archive-input.d.ts.map +1 -1
  22. package/dist/archive-input.js +4 -2
  23. package/dist/archive-merge.d.ts +5 -1
  24. package/dist/archive-merge.d.ts.map +1 -1
  25. package/dist/archive-merge.js +16 -13
  26. package/dist/archive-native.d.ts.map +1 -1
  27. package/dist/archive-native.js +7 -6
  28. package/dist/archive-parser.wasm +0 -0
  29. package/dist/archive-read.d.ts.map +1 -1
  30. package/dist/archive-read.js +83 -74
  31. package/dist/archive-staging.d.ts +6 -3
  32. package/dist/archive-staging.d.ts.map +1 -1
  33. package/dist/archive-staging.js +42 -22
  34. package/dist/archive-tar-stream.d.ts +11 -4
  35. package/dist/archive-tar-stream.d.ts.map +1 -1
  36. package/dist/archive-tar-stream.js +23 -10
  37. package/dist/archive-tar-wasm.d.ts.map +1 -1
  38. package/dist/archive-tar-wasm.js +18 -14
  39. package/dist/archive-zip-admission.d.ts +1 -1
  40. package/dist/archive-zip-admission.d.ts.map +1 -1
  41. package/dist/archive-zip-admission.js +48 -12
  42. package/dist/archive-zip-loader.d.ts +6 -0
  43. package/dist/archive-zip-loader.d.ts.map +1 -0
  44. package/dist/archive-zip-loader.js +38 -0
  45. package/dist/archive-zip-names.d.ts.map +1 -1
  46. package/dist/archive-zip-names.js +13 -8
  47. package/dist/archive-zip-preflight.d.ts +2 -3
  48. package/dist/archive-zip-preflight.d.ts.map +1 -1
  49. package/dist/archive-zip-preflight.js +2 -34
  50. package/dist/archive.d.ts.map +1 -1
  51. package/dist/archive.js +12 -10
  52. package/dist/bounded-read.d.ts +12 -0
  53. package/dist/bounded-read.d.ts.map +1 -1
  54. package/dist/bounded-read.js +82 -45
  55. package/dist/clone-metadata.d.ts +19 -0
  56. package/dist/clone-metadata.d.ts.map +1 -0
  57. package/dist/clone-metadata.js +32 -0
  58. package/dist/copy-file-input.d.ts +22 -0
  59. package/dist/copy-file-input.d.ts.map +1 -0
  60. package/dist/copy-file-input.js +69 -0
  61. package/dist/copy-policy.d.ts +3 -0
  62. package/dist/copy-policy.d.ts.map +1 -0
  63. package/dist/copy-policy.js +8 -0
  64. package/dist/copy-publication.d.ts +10 -1
  65. package/dist/copy-publication.d.ts.map +1 -1
  66. package/dist/copy-publication.js +27 -0
  67. package/dist/copy-tree-portable.d.ts +9 -0
  68. package/dist/copy-tree-portable.d.ts.map +1 -0
  69. package/dist/copy-tree-portable.js +222 -0
  70. package/dist/copy.d.ts +16 -0
  71. package/dist/copy.d.ts.map +1 -0
  72. package/dist/copy.js +125 -0
  73. package/dist/directory-durability.d.ts.map +1 -1
  74. package/dist/directory-durability.js +5 -4
  75. package/dist/directory-guard.d.ts +11 -1
  76. package/dist/directory-guard.d.ts.map +1 -1
  77. package/dist/directory-guard.js +53 -11
  78. package/dist/durability.d.ts +1 -1
  79. package/dist/durability.d.ts.map +1 -1
  80. package/dist/durability.js +1 -1
  81. package/dist/error-detail.d.ts.map +1 -1
  82. package/dist/error-detail.js +4 -1
  83. package/dist/file-handle-transfer.d.ts +14 -0
  84. package/dist/file-handle-transfer.d.ts.map +1 -0
  85. package/dist/file-handle-transfer.js +64 -0
  86. package/dist/file-hash.d.ts +9 -2
  87. package/dist/file-hash.d.ts.map +1 -1
  88. package/dist/file-hash.js +135 -39
  89. package/dist/file-lock-sync.d.ts.map +1 -1
  90. package/dist/file-lock-sync.js +8 -4
  91. package/dist/file-store-boundary.d.ts.map +1 -1
  92. package/dist/file-store-boundary.js +7 -5
  93. package/dist/file-store-path.d.ts +3 -0
  94. package/dist/file-store-path.d.ts.map +1 -0
  95. package/dist/file-store-path.js +27 -0
  96. package/dist/file-store-prune.d.ts.map +1 -1
  97. package/dist/file-store-prune.js +6 -4
  98. package/dist/file-store-sync-write.d.ts.map +1 -1
  99. package/dist/file-store-sync-write.js +56 -44
  100. package/dist/file-store.d.ts.map +1 -1
  101. package/dist/file-store.js +2 -18
  102. package/dist/filename.d.ts.map +1 -1
  103. package/dist/filename.js +4 -13
  104. package/dist/guarded-mkdir.d.ts +1 -0
  105. package/dist/guarded-mkdir.d.ts.map +1 -1
  106. package/dist/guarded-mkdir.js +4 -2
  107. package/dist/guarded-mutation.d.ts +2 -0
  108. package/dist/guarded-mutation.d.ts.map +1 -1
  109. package/dist/guarded-mutation.js +8 -4
  110. package/dist/guest-dispatch-python.d.ts +2 -0
  111. package/dist/guest-dispatch-python.d.ts.map +1 -0
  112. package/dist/guest-dispatch-python.js +117 -0
  113. package/dist/guest-native-python.d.ts +4 -0
  114. package/dist/guest-native-python.d.ts.map +1 -0
  115. package/dist/guest-native-python.js +135 -0
  116. package/dist/guest.d.ts +9 -0
  117. package/dist/guest.d.ts.map +1 -0
  118. package/dist/guest.js +413 -0
  119. package/dist/home-dir.d.ts.map +1 -1
  120. package/dist/home-dir.js +9 -7
  121. package/dist/index.d.ts +1 -1
  122. package/dist/index.d.ts.map +1 -1
  123. package/dist/install-path.d.ts.map +1 -1
  124. package/dist/install-path.js +5 -8
  125. package/dist/json-durable-queue-directory.js +3 -3
  126. package/dist/json-durable-queue.d.ts.map +1 -1
  127. package/dist/json-durable-queue.js +19 -18
  128. package/dist/json.d.ts.map +1 -1
  129. package/dist/json.js +2 -1
  130. package/dist/local-roots.d.ts.map +1 -1
  131. package/dist/local-roots.js +24 -25
  132. package/dist/move-path-stage.d.ts +8 -0
  133. package/dist/move-path-stage.d.ts.map +1 -0
  134. package/dist/move-path-stage.js +56 -0
  135. package/dist/move-path.d.ts.map +1 -1
  136. package/dist/move-path.js +30 -34
  137. package/dist/mutation-authority.d.ts +9 -0
  138. package/dist/mutation-authority.d.ts.map +1 -0
  139. package/dist/mutation-authority.js +36 -0
  140. package/dist/native-binding.d.ts +29 -1
  141. package/dist/native-binding.d.ts.map +1 -1
  142. package/dist/native-operations.d.ts +1 -1
  143. package/dist/native-operations.d.ts.map +1 -1
  144. package/dist/native-operations.js +11 -5
  145. package/dist/native-pinned-write-windows.d.ts.map +1 -1
  146. package/dist/native-pinned-write-windows.js +19 -7
  147. package/dist/native-pinned-write.d.ts.map +1 -1
  148. package/dist/native-pinned-write.js +3 -1
  149. package/dist/native-staged-file.d.ts +3 -3
  150. package/dist/native-staged-file.d.ts.map +1 -1
  151. package/dist/native-staged-file.js +35 -8
  152. package/dist/opened-realpath.d.ts.map +1 -1
  153. package/dist/opened-realpath.js +5 -4
  154. package/dist/output.d.ts +2 -0
  155. package/dist/output.d.ts.map +1 -1
  156. package/dist/output.js +2 -0
  157. package/dist/overwrite-file-handle.d.ts +8 -0
  158. package/dist/overwrite-file-handle.d.ts.map +1 -0
  159. package/dist/overwrite-file-handle.js +42 -0
  160. package/dist/path-case.d.ts +7 -0
  161. package/dist/path-case.d.ts.map +1 -0
  162. package/dist/path-case.js +136 -0
  163. package/dist/path.d.ts.map +1 -1
  164. package/dist/path.js +5 -2
  165. package/dist/permissions-windows.d.ts +1 -1
  166. package/dist/permissions-windows.d.ts.map +1 -1
  167. package/dist/permissions-windows.js +85 -53
  168. package/dist/pinned-open.d.ts.map +1 -1
  169. package/dist/pinned-open.js +3 -1
  170. package/dist/pinned-operation.js +1 -1
  171. package/dist/pinned-write.d.ts +7 -3
  172. package/dist/pinned-write.d.ts.map +1 -1
  173. package/dist/pinned-write.js +51 -30
  174. package/dist/positional-read.d.ts +9 -0
  175. package/dist/positional-read.d.ts.map +1 -0
  176. package/dist/positional-read.js +36 -0
  177. package/dist/private-temp-workspace.d.ts.map +1 -1
  178. package/dist/private-temp-workspace.js +6 -4
  179. package/dist/publish-copy-stage.d.ts +13 -0
  180. package/dist/publish-copy-stage.d.ts.map +1 -0
  181. package/dist/publish-copy-stage.js +47 -0
  182. package/dist/publish-file.d.ts.map +1 -1
  183. package/dist/publish-file.js +6 -5
  184. package/dist/realpath.d.ts +4 -0
  185. package/dist/realpath.d.ts.map +1 -0
  186. package/dist/realpath.js +43 -0
  187. package/dist/recursive-mkdir-path.d.ts +3 -0
  188. package/dist/recursive-mkdir-path.d.ts.map +1 -0
  189. package/dist/recursive-mkdir-path.js +8 -0
  190. package/dist/replace-directory.d.ts.map +1 -1
  191. package/dist/replace-directory.js +2 -1
  192. package/dist/replace-file-copy-fallback.d.ts.map +1 -1
  193. package/dist/replace-file-copy-fallback.js +23 -31
  194. package/dist/replace-file-copy-source.d.ts.map +1 -1
  195. package/dist/replace-file-copy-source.js +7 -12
  196. package/dist/replace-file-mode.d.ts +3 -0
  197. package/dist/replace-file-mode.d.ts.map +1 -0
  198. package/dist/replace-file-mode.js +10 -0
  199. package/dist/replace-file-temp-owner.d.ts.map +1 -1
  200. package/dist/replace-file-temp-owner.js +6 -2
  201. package/dist/replace-file.d.ts +1 -0
  202. package/dist/replace-file.d.ts.map +1 -1
  203. package/dist/replace-file.js +14 -8
  204. package/dist/root-context.d.ts +3 -0
  205. package/dist/root-context.d.ts.map +1 -1
  206. package/dist/root-context.js +53 -28
  207. package/dist/root-create-input.d.ts +10 -0
  208. package/dist/root-create-input.d.ts.map +1 -0
  209. package/dist/root-create-input.js +80 -0
  210. package/dist/root-directory-list.d.ts +26 -0
  211. package/dist/root-directory-list.d.ts.map +1 -0
  212. package/dist/root-directory-list.js +219 -0
  213. package/dist/root-entries.d.ts +11 -0
  214. package/dist/root-entries.d.ts.map +1 -0
  215. package/dist/root-entries.js +61 -0
  216. package/dist/root-errors.d.ts +6 -5
  217. package/dist/root-errors.d.ts.map +1 -1
  218. package/dist/root-errors.js +16 -12
  219. package/dist/root-file.d.ts +2 -0
  220. package/dist/root-file.d.ts.map +1 -1
  221. package/dist/root-file.js +12 -4
  222. package/dist/root-impl.d.ts +17 -51
  223. package/dist/root-impl.d.ts.map +1 -1
  224. package/dist/root-impl.js +358 -234
  225. package/dist/root-options.d.ts +77 -0
  226. package/dist/root-options.d.ts.map +1 -0
  227. package/dist/root-options.js +18 -0
  228. package/dist/root-path-existing.d.ts +5 -0
  229. package/dist/root-path-existing.d.ts.map +1 -1
  230. package/dist/root-path-existing.js +59 -3
  231. package/dist/root-path-symlink.d.ts.map +1 -1
  232. package/dist/root-path-symlink.js +3 -2
  233. package/dist/root-path.d.ts +1 -0
  234. package/dist/root-path.d.ts.map +1 -1
  235. package/dist/root-path.js +74 -162
  236. package/dist/root-paths.d.ts.map +1 -1
  237. package/dist/root-paths.js +13 -9
  238. package/dist/root-remove.d.ts +5 -0
  239. package/dist/root-remove.d.ts.map +1 -0
  240. package/dist/root-remove.js +286 -0
  241. package/dist/root-symlink-policy.d.ts +14 -0
  242. package/dist/root-symlink-policy.d.ts.map +1 -0
  243. package/dist/root-symlink-policy.js +34 -0
  244. package/dist/root-walk.d.ts +5 -4
  245. package/dist/root-walk.d.ts.map +1 -1
  246. package/dist/root-walk.js +99 -50
  247. package/dist/root-write-mode.d.ts.map +1 -1
  248. package/dist/root-write-mode.js +3 -5
  249. package/dist/root.d.ts +4 -1
  250. package/dist/root.d.ts.map +1 -1
  251. package/dist/secret-file.d.ts.map +1 -1
  252. package/dist/secret-file.js +2 -1
  253. package/dist/secret-read-async.d.ts.map +1 -1
  254. package/dist/secret-read-async.js +2 -1
  255. package/dist/secure-file.d.ts.map +1 -1
  256. package/dist/secure-file.js +25 -8
  257. package/dist/secure-temp-dir.d.ts.map +1 -1
  258. package/dist/secure-temp-dir.js +2 -1
  259. package/dist/sibling-staged-file.d.ts +1 -0
  260. package/dist/sibling-staged-file.d.ts.map +1 -1
  261. package/dist/sibling-staged-file.js +42 -8
  262. package/dist/sibling-temp.d.ts +2 -0
  263. package/dist/sibling-temp.d.ts.map +1 -1
  264. package/dist/sibling-temp.js +6 -4
  265. package/dist/sidecar-lock-acquire.d.ts.map +1 -1
  266. package/dist/sidecar-lock-acquire.js +17 -5
  267. package/dist/sidecar-lock-policy.d.ts +2 -0
  268. package/dist/sidecar-lock-policy.d.ts.map +1 -1
  269. package/dist/sidecar-lock-policy.js +20 -1
  270. package/dist/staged-directory.d.ts.map +1 -1
  271. package/dist/staged-directory.js +4 -3
  272. package/dist/temp-cleanup.d.ts.map +1 -1
  273. package/dist/temp-cleanup.js +3 -2
  274. package/dist/temp-target.d.ts +14 -12
  275. package/dist/temp-target.d.ts.map +1 -1
  276. package/dist/temp-target.js +12 -6
  277. package/dist/timing.d.ts +1 -0
  278. package/dist/timing.d.ts.map +1 -1
  279. package/dist/timing.js +25 -6
  280. package/dist/trash.d.ts.map +1 -1
  281. package/dist/trash.js +9 -7
  282. package/dist/unicode-path.d.ts +3 -0
  283. package/dist/unicode-path.d.ts.map +1 -0
  284. package/dist/unicode-path.js +13 -0
  285. package/dist/walk.d.ts.map +1 -1
  286. package/dist/walk.js +9 -28
  287. package/dist/windows-owner.d.ts +9 -12
  288. package/dist/windows-owner.d.ts.map +1 -1
  289. package/dist/windows-owner.js +24 -58
  290. package/dist/write-file-handle.d.ts +7 -0
  291. package/dist/write-file-handle.d.ts.map +1 -0
  292. package/dist/write-file-handle.js +26 -0
  293. package/docs/advanced.md +22 -4
  294. package/docs/archive.md +44 -9
  295. package/docs/atomic.md +24 -1
  296. package/docs/config.md +1 -0
  297. package/docs/contributing.md +36 -1
  298. package/docs/copy.md +155 -0
  299. package/docs/directory-identity.md +85 -0
  300. package/docs/durability.md +53 -3
  301. package/docs/entries.md +109 -0
  302. package/docs/errors.md +4 -4
  303. package/docs/file-store.md +15 -0
  304. package/docs/guest.md +141 -0
  305. package/docs/in-place-write.md +81 -0
  306. package/docs/index.md +2 -0
  307. package/docs/install.md +31 -0
  308. package/docs/local-roots.md +8 -1
  309. package/docs/native-helper.md +10 -3
  310. package/docs/native.md +18 -1
  311. package/docs/output.md +32 -6
  312. package/docs/path-case.md +64 -0
  313. package/docs/path-scope.md +1 -1
  314. package/docs/permissions.md +29 -10
  315. package/docs/positional-read.md +63 -0
  316. package/docs/public-api.md +31 -2
  317. package/docs/root.md +196 -6
  318. package/docs/secure-file.md +2 -0
  319. package/docs/security-model.md +14 -0
  320. package/docs/sidecar-lock.md +12 -3
  321. package/docs/temp.md +35 -6
  322. package/docs/timing.md +2 -0
  323. package/docs/types.md +19 -12
  324. package/docs/walk.md +54 -5
  325. package/docs/writing.md +173 -5
  326. package/package.json +19 -8
package/dist/file-hash.js CHANGED
@@ -1,23 +1,53 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import fsSync from "node:fs";
3
3
  import fs from "node:fs/promises";
4
+ import { normalizeMaxBytes } from "./byte-budget.js";
4
5
  import { FsSafeError } from "./errors.js";
5
6
  import { getNativeBinding } from "./native.js";
6
7
  import { resolveReadOpenFlags } from "./read-open-flags.js";
7
- import { inspectFileIdentity } from "./strict-file-identity.js";
8
- export async function hashFileHandle(handle, native = getNativeBinding()) {
8
+ import { inspectFileIdentity, inspectFileIdentitySync } from "./strict-file-identity.js";
9
+ export async function hashFileHandle(handle, native = getNativeBinding(), { maxBytes = Infinity, signal } = {}) {
10
+ signal?.throwIfAborted();
9
11
  const stat = fsSync.fstatSync(handle.fd);
10
12
  if (!stat.isFile()) {
11
13
  throw new FsSafeError("not-file", "SHA-256 input is not a regular file");
12
14
  }
15
+ if (stat.size > maxBytes) {
16
+ throw new FsSafeError("too-large", `SHA-256 input exceeds ${maxBytes} bytes`);
17
+ }
13
18
  if (native) {
14
- return await native.sha256File(handle.fd);
19
+ // A completed N-API task can mask later aborts on the same signal.
20
+ const nativeSignal = signal ? AbortSignal.any([signal]) : undefined;
21
+ try {
22
+ const result = await native.sha256File(handle.fd, Number.isFinite(maxBytes) ? maxBytes : undefined, nativeSignal);
23
+ signal?.throwIfAborted();
24
+ return result;
25
+ }
26
+ catch (error) {
27
+ // N-API settles only after compute stops; never race descriptor cleanup.
28
+ signal?.throwIfAborted();
29
+ if (error?.code === "too-large") {
30
+ throw new FsSafeError("too-large", `SHA-256 input exceeds ${maxBytes} bytes`, { cause: error });
31
+ }
32
+ throw error;
33
+ }
34
+ finally {
35
+ // Node retains composite signals while N-API's abort listener is attached.
36
+ if (nativeSignal)
37
+ nativeSignal.onabort = null;
38
+ }
15
39
  }
16
40
  const hash = createHash("sha256");
17
- const buffer = Buffer.allocUnsafe(64 * 1024);
41
+ const buffer = Buffer.allocUnsafe(Math.min(256 * 1024, Math.max(64 * 1024, stat.size)));
18
42
  let position = 0;
19
43
  while (true) {
20
- const { bytesRead } = await handle.read(buffer, 0, buffer.length, position);
44
+ signal?.throwIfAborted();
45
+ const length = Math.min(buffer.length, maxBytes - position + 1);
46
+ const { bytesRead } = await handle.read(buffer, 0, length, position);
47
+ signal?.throwIfAborted();
48
+ if (bytesRead > maxBytes - position) {
49
+ throw new FsSafeError("too-large", `SHA-256 input exceeds ${maxBytes} bytes`);
50
+ }
21
51
  if (bytesRead === 0) {
22
52
  return { bytes: position, digest: hash.digest("hex") };
23
53
  }
@@ -25,50 +55,116 @@ export async function hashFileHandle(handle, native = getNativeBinding()) {
25
55
  position += bytesRead;
26
56
  }
27
57
  }
28
- async function hashPath(filePath) {
29
- const before = await inspectFileIdentity(async () => {
30
- const stat = fsSync.lstatSync(filePath, { bigint: true });
31
- if (stat.isSymbolicLink()) {
32
- throw new FsSafeError("symlink", "SHA-256 path must not be a symbolic link");
33
- }
34
- if (!stat.isFile()) {
35
- throw new FsSafeError("not-file", "SHA-256 path is not a regular file");
36
- }
37
- return stat;
38
- });
58
+ function hashPathIdentity(filePath, afterOpen = false) {
59
+ const stat = fsSync.lstatSync(filePath, { bigint: true });
60
+ if (afterOpen && (stat.isSymbolicLink() || !stat.isFile())) {
61
+ throw new FsSafeError("path-mismatch", "SHA-256 path changed while opening");
62
+ }
63
+ if (stat.isSymbolicLink()) {
64
+ throw new FsSafeError("symlink", "SHA-256 path must not be a symbolic link");
65
+ }
66
+ if (!stat.isFile()) {
67
+ throw new FsSafeError("not-file", "SHA-256 path is not a regular file");
68
+ }
69
+ return stat;
70
+ }
71
+ function hashDescriptorIdentity(fd) {
72
+ const stat = fsSync.fstatSync(fd, { bigint: true });
73
+ if (!stat.isFile()) {
74
+ throw new FsSafeError("not-file", "SHA-256 path is not a regular file");
75
+ }
76
+ return stat;
77
+ }
78
+ function rethrowHashOpenError(error) {
79
+ if (error?.code === "ELOOP") {
80
+ throw new FsSafeError("symlink", "SHA-256 path must not be a symbolic link", {
81
+ cause: error,
82
+ });
83
+ }
84
+ throw error;
85
+ }
86
+ async function hashPath(filePath, options) {
87
+ const before = await inspectFileIdentity(() => hashPathIdentity(filePath));
39
88
  let handle;
40
89
  try {
90
+ options.signal?.throwIfAborted();
41
91
  handle = await fs.open(filePath, resolveReadOpenFlags());
42
92
  }
43
93
  catch (error) {
44
- if (error?.code === "ELOOP") {
45
- throw new FsSafeError("symlink", "SHA-256 path must not be a symbolic link", {
46
- cause: error,
47
- });
48
- }
49
- throw error;
94
+ rethrowHashOpenError(error);
50
95
  }
51
96
  try {
52
- const opened = await inspectFileIdentity(async () => {
53
- const stat = fsSync.fstatSync(handle.fd, { bigint: true });
54
- if (!stat.isFile()) {
55
- throw new FsSafeError("not-file", "SHA-256 path is not a regular file");
56
- }
57
- return stat;
58
- }, before);
59
- await inspectFileIdentity(async () => {
60
- const stat = fsSync.lstatSync(filePath, { bigint: true });
61
- if (stat.isSymbolicLink() || !stat.isFile()) {
62
- throw new FsSafeError("path-mismatch", "SHA-256 path changed while opening");
63
- }
64
- return stat;
65
- }, opened);
66
- return await hashFileHandle(handle);
97
+ options.signal?.throwIfAborted();
98
+ const opened = await inspectFileIdentity(() => hashDescriptorIdentity(handle.fd), before);
99
+ await inspectFileIdentity(() => hashPathIdentity(filePath, true), opened);
100
+ return await hashFileHandle(handle, getNativeBinding(), options);
67
101
  }
68
102
  finally {
69
103
  await handle.close().catch(() => undefined);
70
104
  }
71
105
  }
72
- export async function sha256File(input) {
73
- return typeof input === "string" ? await hashPath(input) : await hashFileHandle(input);
106
+ export async function sha256File(input, options = {}) {
107
+ options.signal?.throwIfAborted();
108
+ const normalized = { maxBytes: normalizeMaxBytes(options.maxBytes), signal: options.signal };
109
+ return typeof input === "string"
110
+ ? await hashPath(input, normalized)
111
+ : await hashFileHandle(input, getNativeBinding(), normalized);
112
+ }
113
+ function hashDescriptorSync(fd, { maxBytes = Infinity, signal }) {
114
+ signal?.throwIfAborted();
115
+ const stat = fsSync.fstatSync(fd);
116
+ if (!stat.isFile()) {
117
+ throw new FsSafeError("not-file", "SHA-256 input is not a regular file");
118
+ }
119
+ if (stat.size > maxBytes) {
120
+ throw new FsSafeError("too-large", `SHA-256 input exceeds ${maxBytes} bytes`);
121
+ }
122
+ const hash = createHash("sha256");
123
+ const buffer = Buffer.allocUnsafe(Math.min(256 * 1024, Math.max(64 * 1024, stat.size)));
124
+ let position = 0;
125
+ while (true) {
126
+ signal?.throwIfAborted();
127
+ const length = Math.min(buffer.length, maxBytes - position + 1);
128
+ const bytesRead = fsSync.readSync(fd, buffer, 0, length, position);
129
+ signal?.throwIfAborted();
130
+ if (bytesRead > maxBytes - position) {
131
+ throw new FsSafeError("too-large", `SHA-256 input exceeds ${maxBytes} bytes`);
132
+ }
133
+ if (bytesRead === 0) {
134
+ return { bytes: position, digest: hash.digest("hex") };
135
+ }
136
+ hash.update(buffer.subarray(0, bytesRead));
137
+ position += bytesRead;
138
+ }
139
+ }
140
+ function hashPathSync(filePath, options) {
141
+ const before = inspectFileIdentitySync(() => hashPathIdentity(filePath));
142
+ let fd;
143
+ try {
144
+ options.signal?.throwIfAborted();
145
+ fd = fsSync.openSync(filePath, resolveReadOpenFlags());
146
+ }
147
+ catch (error) {
148
+ rethrowHashOpenError(error);
149
+ }
150
+ try {
151
+ options.signal?.throwIfAborted();
152
+ const opened = inspectFileIdentitySync(() => hashDescriptorIdentity(fd), before);
153
+ inspectFileIdentitySync(() => hashPathIdentity(filePath, true), opened);
154
+ return hashDescriptorSync(fd, options);
155
+ }
156
+ finally {
157
+ try {
158
+ fsSync.closeSync(fd);
159
+ }
160
+ catch { /* Preserve the hashing outcome. */ }
161
+ }
162
+ }
163
+ /** Hash synchronously from offset zero, retaining ownership and position of a borrowed fd. */
164
+ export function sha256FileSync(input, options = {}) {
165
+ options.signal?.throwIfAborted();
166
+ const normalized = { maxBytes: normalizeMaxBytes(options.maxBytes), signal: options.signal };
167
+ return typeof input === "string"
168
+ ? hashPathSync(input, normalized)
169
+ : hashDescriptorSync(input, normalized);
74
170
  }
@@ -1 +1 @@
1
- {"version":3,"file":"file-lock-sync.d.ts","sourceRoot":"","sources":["../src/file-lock-sync.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EASL,KAAK,wBAAwB,EAC9B,MAAM,2BAA2B,CAAC;AASnC,OAAO,KAAK,EACV,0BAA0B,EAC1B,uBAAuB,EACvB,wBAAwB,EACzB,MAAM,yBAAyB,CAAC;AAIjC,MAAM,MAAM,0BAA0B,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI;IACjF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,uBAAuB,CAAC;IAChC,aAAa,CAAC,EAAE,wBAAwB,CAAC;IACzC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,QAAQ,CAAC;IACxB,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE;QACvB,QAAQ,EAAE,MAAM,CAAC;QACjB,oBAAoB,EAAE,MAAM,CAAC;QAC7B,OAAO,EAAE,OAAO,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;QAChB,KAAK,EAAE,MAAM,CAAC;QACd,iBAAiB,EAAE,KAAK,CAAC;KAC1B,KAAK,OAAO,CAAC;IACd,qBAAqB,CAAC,EAAE,CAAC,QAAQ,EAAE,wBAAwB,KAAK,OAAO,CAAC;IACxE,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IACxC,QAAQ,CAAC,EAAE,IAAI,CAAC;IAChB,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,0BAA0B,KAAK,IAAI,CAAC;IAC3D,yBAAyB,CAAC,EAAE,MAAM,CAAC;CACpC,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,eAAe,IAAI,OAAO,CAAC;IAC3B,OAAO,IAAI,IAAI,CAAC;IAChB,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC;CAC1B,CAAC;AAoJF,wBAAgB,mBAAmB,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC1E,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,0BAA0B,CAAC,QAAQ,CAAC,GAC5C,kBAAkB,CAwMpB;AAED,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC1E,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,0BAA0B,CAAC,QAAQ,CAAC,EAC7C,EAAE,EAAE,MAAM,CAAC,GACV,CAAC,CAOH"}
1
+ {"version":3,"file":"file-lock-sync.d.ts","sourceRoot":"","sources":["../src/file-lock-sync.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EASL,KAAK,wBAAwB,EAC9B,MAAM,2BAA2B,CAAC;AAWnC,OAAO,KAAK,EACV,0BAA0B,EAC1B,uBAAuB,EACvB,wBAAwB,EACzB,MAAM,yBAAyB,CAAC;AAMjC,MAAM,MAAM,0BAA0B,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI;IACjF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,uBAAuB,CAAC;IAChC,aAAa,CAAC,EAAE,wBAAwB,CAAC;IACzC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,QAAQ,CAAC;IACxB,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE;QACvB,QAAQ,EAAE,MAAM,CAAC;QACjB,oBAAoB,EAAE,MAAM,CAAC;QAC7B,OAAO,EAAE,OAAO,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;QAChB,KAAK,EAAE,MAAM,CAAC;QACd,iBAAiB,EAAE,KAAK,CAAC;KAC1B,KAAK,OAAO,CAAC;IACd,qBAAqB,CAAC,EAAE,CAAC,QAAQ,EAAE,wBAAwB,KAAK,OAAO,CAAC;IACxE,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IACxC,QAAQ,CAAC,EAAE,IAAI,CAAC;IAChB,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,0BAA0B,KAAK,IAAI,CAAC;IAC3D,yBAAyB,CAAC,EAAE,MAAM,CAAC;CACpC,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,eAAe,IAAI,OAAO,CAAC;IAC3B,OAAO,IAAI,IAAI,CAAC;IAChB,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC;CAC1B,CAAC;AAoJF,wBAAgB,mBAAmB,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC1E,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,0BAA0B,CAAC,QAAQ,CAAC,GAC5C,kBAAkB,CA0MpB;AAED,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC1E,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,0BAA0B,CAAC,QAAQ,CAAC,EAC7C,EAAE,EAAE,MAAM,CAAC,GACV,CAAC,CAOH"}
@@ -2,9 +2,11 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { FsSafeError } from "./errors.js";
4
4
  import { readSidecarLockSnapshotSync, readSidecarLockRawSnapshotSync, parseSidecarLockSnapshot, relativeSidecarLockPath, removeSidecarLockIfUnchangedSync, serializeSidecarLockPayload, sidecarLockSnapshotMatches, } from "./sidecar-lock-reclaim.js";
5
- import { computeSidecarLockDelayMs, isTransientLockFileDenial, maxTransientLockDenials, sidecarLockPayloadCreatedAtMs, validateSidecarLockRetryOptions, validateSidecarLockTimeoutMs, } from "./sidecar-lock-policy.js";
5
+ import { computeSidecarLockDelayMs, isTransientLockFileDenial, maxTransientLockDenials, sidecarLockPayloadCreatedAtMs, validateSidecarLockStaleMs, validateSidecarLockCompromiseCheckIntervalMs, validateSidecarLockRetryOptions, validateSidecarLockTimeoutMs, } from "./sidecar-lock-policy.js";
6
6
  import { getFsSafeLockConfig } from "./lock-config.js";
7
7
  import { sleepSync } from "./timing.js";
8
+ import { realpathSync } from "./realpath.js";
9
+ import { recursiveMkdirPath } from "./recursive-mkdir-path.js";
8
10
  const SYNC_HELD_LOCKS_KEY = Symbol.for("fsSafe.syncSidecarLocks");
9
11
  const SYNC_CLEANUP_REGISTERED_KEY = Symbol.for("fsSafe.syncSidecarLockCleanupRegistered");
10
12
  const SYNC_CLEANUP_HANDLER_KEY = Symbol.for("fsSafe.syncSidecarLockCleanupHandler");
@@ -91,11 +93,11 @@ function createSyncHeldLockHandle(held) {
91
93
  }
92
94
  function canonicalLockParentSync(parent) {
93
95
  // Match Root's async realpath, including Windows short-name expansion.
94
- return process.platform === "win32" ? fs.realpathSync.native(parent) : fs.realpathSync(parent);
96
+ return process.platform === "win32" ? realpathSync.native(parent) : realpathSync(parent);
95
97
  }
96
98
  function normalizeTargetPath(targetPath) {
97
99
  const resolved = path.resolve(targetPath);
98
- fs.mkdirSync(path.dirname(resolved), { recursive: true });
100
+ fs.mkdirSync(recursiveMkdirPath(path.dirname(resolved)), { recursive: true });
99
101
  try {
100
102
  return path.join(canonicalLockParentSync(path.dirname(resolved)), path.basename(resolved));
101
103
  }
@@ -140,6 +142,9 @@ export function acquireFileLockSync(targetPath, options) {
140
142
  const timeoutMs = options.timeoutMs ?? defaults.timeoutMs;
141
143
  validateSidecarLockRetryOptions(retry);
142
144
  validateSidecarLockTimeoutMs(timeoutMs);
145
+ const staleMs = options.staleMs ?? defaults.staleMs ?? 30_000;
146
+ validateSidecarLockStaleMs(staleMs);
147
+ validateSidecarLockCompromiseCheckIntervalMs(options.compromiseCheckIntervalMs);
143
148
  const normalizedTargetPath = normalizeTargetPath(targetPath);
144
149
  const lockPath = boundedLockPath(options.lockPath ?? `${normalizedTargetPath}.lock`, options.lockRoot);
145
150
  const heldLocks = getSyncHeldLocks();
@@ -153,7 +158,6 @@ export function acquireFileLockSync(targetPath, options) {
153
158
  }
154
159
  // Process defaults fill the same fields here as withLockDefaults() fills for
155
160
  // the asynchronous manager, so both acquirers honor configureFsSafeLocks().
156
- const staleMs = options.staleMs ?? defaults.staleMs ?? 30_000;
157
161
  const staleRecovery = options.staleRecovery ?? defaults.staleRecovery;
158
162
  const startedAt = Date.now();
159
163
  let attempt = 0;
@@ -1 +1 @@
1
- {"version":3,"file":"file-store-boundary.d.ts","sourceRoot":"","sources":["../src/file-store-boundary.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAa,QAAQ,EAAE,MAAM,aAAa,CAAC;AAIvD,OAAO,EAGL,KAAK,kBAAkB,EACxB,MAAM,sBAAsB,CAAC;AAI9B,OAAO,EAA4C,KAAK,IAAI,EAAE,MAAM,WAAW,CAAC;AAGhF,OAAO,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAG1D,MAAM,MAAM,eAAe,GAAG,kBAAkB,CAAC;AAOjD,wBAAsB,kBAAkB,CACtC,UAAU,EAAE,IAAI,EAChB,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,IAAI,CAAC,CAOf;AAED,wBAAsB,qBAAqB,CAAC,MAAM,EAAE;IAClD,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,IAAI,CAAC,CAKhB;AAED,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,UAAU,CAAC,OAAO,sBAAsB,CAAC,CAAC,CAAC,CAAC,GACnD,OAAO,CAAC,IAAI,CAAC,CASf;AAsCD,wBAAsB,uBAAuB,CAAC,MAAM,EAAE;IACpD,MAAM,EAAE,QAAQ,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAAC,CAwC1D;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI,CAWrE;AAUD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,eAAe,CAOlB;AAED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,eAAe,GAAG,OAAO,CAAC;CAC1C,GAAG,eAAe,CAyDlB"}
1
+ {"version":3,"file":"file-store-boundary.d.ts","sourceRoot":"","sources":["../src/file-store-boundary.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAa,QAAQ,EAAE,MAAM,aAAa,CAAC;AAIvD,OAAO,EAGL,KAAK,kBAAkB,EACxB,MAAM,sBAAsB,CAAC;AAI9B,OAAO,EAA4C,KAAK,IAAI,EAAE,MAAM,WAAW,CAAC;AAGhF,OAAO,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAK1D,MAAM,MAAM,eAAe,GAAG,kBAAkB,CAAC;AAOjD,wBAAsB,kBAAkB,CACtC,UAAU,EAAE,IAAI,EAChB,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,IAAI,CAAC,CAOf;AAED,wBAAsB,qBAAqB,CAAC,MAAM,EAAE;IAClD,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,IAAI,CAAC,CAKhB;AAED,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,UAAU,CAAC,OAAO,sBAAsB,CAAC,CAAC,CAAC,CAAC,GACnD,OAAO,CAAC,IAAI,CAAC,CASf;AAsCD,wBAAsB,uBAAuB,CAAC,MAAM,EAAE;IACpD,MAAM,EAAE,QAAQ,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAAC,CAwC1D;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI,CAWrE;AAUD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,eAAe,CAOlB;AAED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,eAAe,GAAG,OAAO,CAAC;CAC1C,GAAG,eAAe,CAyDlB"}
@@ -13,6 +13,8 @@ import { ensureTrailingSep } from "./root-context.js";
13
13
  import { RootHandle } from "./root-impl.js";
14
14
  import { prepareSecretFileWrite } from "./secret-file.js";
15
15
  import { resolveSecureTempRoot } from "./secure-temp-dir.js";
16
+ import { realpathSync } from "./realpath.js";
17
+ import { recursiveMkdirPath } from "./recursive-mkdir-path.js";
16
18
  function parentRelativePath(relativePath) {
17
19
  const parent = path.posix.dirname(relativePath);
18
20
  return parent === "." ? "" : parent;
@@ -27,7 +29,7 @@ export async function ensureParentInRoot(scopedRoot, relativePath, mode) {
27
29
  }
28
30
  export async function openWritableStoreRoot(params) {
29
31
  const maxBytes = normalizeMaxBytes(params.maxBytes);
30
- await fs.mkdir(params.rootDir, { recursive: true, mode: params.dirMode });
32
+ await fs.mkdir(recursiveMkdirPath(params.rootDir), { recursive: true, mode: params.dirMode });
31
33
  await fs.chmod(params.rootDir, params.dirMode).catch(() => undefined);
32
34
  return await root(params.rootDir, { hardlinks: "reject", maxBytes });
33
35
  }
@@ -149,12 +151,12 @@ export function ensureStoreDirectorySync(params) {
149
151
  if (isPathRelativeEscape(relative)) {
150
152
  throw new FsSafeError("outside-workspace", "file path escapes store root");
151
153
  }
152
- syncFs.mkdirSync(rootDir, { recursive: true, mode: params.mode });
154
+ syncFs.mkdirSync(recursiveMkdirPath(rootDir), { recursive: true, mode: params.mode });
153
155
  const rootStat = syncFs.lstatSync(rootDir);
154
156
  if (rootStat.isSymbolicLink() || !rootStat.isDirectory()) {
155
157
  throw new FsSafeError("not-file", `${params.messagePrefix} root must be a directory: ${rootDir}`);
156
158
  }
157
- const rootReal = syncFs.realpathSync(rootDir);
159
+ const rootReal = realpathSync(rootDir);
158
160
  chmodDirectorySyncBestEffort(rootDir, params.mode);
159
161
  let current = rootDir;
160
162
  for (const segment of path.relative(rootDir, dir).split(path.sep).filter(Boolean)) {
@@ -172,8 +174,8 @@ export function ensureStoreDirectorySync(params) {
172
174
  syncFs.mkdirSync(current, { mode: params.mode });
173
175
  }
174
176
  const currentRootStat = syncFs.lstatSync(rootDir);
175
- const currentRootReal = syncFs.realpathSync(rootDir);
176
- const currentReal = syncFs.realpathSync(current);
177
+ const currentRootReal = realpathSync(rootDir);
178
+ const currentReal = realpathSync(current);
177
179
  if (currentRootStat.isSymbolicLink() ||
178
180
  !currentRootStat.isDirectory() ||
179
181
  !sameFileIdentity(rootStat, currentRootStat) ||
@@ -0,0 +1,3 @@
1
+ export declare function assertRelativePath(relativePath: string): string;
2
+ export declare function resolveStorePath(rootDir: string, relativePath: string): string;
3
+ //# sourceMappingURL=file-store-path.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-store-path.d.ts","sourceRoot":"","sources":["../src/file-store-path.ts"],"names":[],"mappings":"AAIA,wBAAgB,kBAAkB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAe/D;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAS9E"}
@@ -0,0 +1,27 @@
1
+ import path from "node:path";
2
+ import { FsSafeError } from "./errors.js";
3
+ import { isPathInside, splitSafeRelativePath } from "./path.js";
4
+ export function assertRelativePath(relativePath) {
5
+ const raw = relativePath.trim();
6
+ if (!raw || raw !== relativePath) {
7
+ throw new FsSafeError("invalid-path", "store key must be non-empty and unpadded");
8
+ }
9
+ const segments = splitSafeRelativePath(raw);
10
+ if (segments.length === 0 ||
11
+ segments.join("/") !== raw ||
12
+ raw.normalize("NFC") !== raw ||
13
+ segments.some((segment) => /[ .]$/u.test(segment))) {
14
+ throw new FsSafeError("invalid-path", "store key must use one canonical relative spelling");
15
+ }
16
+ return raw;
17
+ }
18
+ export function resolveStorePath(rootDir, relativePath) {
19
+ const key = assertRelativePath(relativePath);
20
+ const root = path.resolve(rootDir);
21
+ // The immutable key already passed segment validation; keep the containment check.
22
+ const target = path.resolve(root, key);
23
+ if (!isPathInside(root, target)) {
24
+ throw new FsSafeError("outside-workspace", "relative path escapes root");
25
+ }
26
+ return target;
27
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"file-store-prune.d.ts","sourceRoot":"","sources":["../src/file-store-prune.ts"],"names":[],"mappings":"AAQA,MAAM,MAAM,qBAAqB,GAAG;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B,CAAC;AAEF,wBAAsB,wBAAwB,CAAC,MAAM,EAAE;IACrD,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,qBAAqB,CAAC;CAChC,GAAG,OAAO,CAAC,IAAI,CAAC,CAsFhB"}
1
+ {"version":3,"file":"file-store-prune.d.ts","sourceRoot":"","sources":["../src/file-store-prune.ts"],"names":[],"mappings":"AAUA,MAAM,MAAM,qBAAqB,GAAG;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B,CAAC;AAEF,wBAAsB,wBAAwB,CAAC,MAAM,EAAE;IACrD,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,qBAAqB,CAAC;CAChC,GAAG,OAAO,CAAC,IAAI,CAAC,CAsFhB"}
@@ -3,6 +3,8 @@ import fs from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { FsSafeError } from "./errors.js";
5
5
  import { isPathInside } from "./path.js";
6
+ import { realpathSync } from "./realpath.js";
7
+ import { recursiveMkdirPath } from "./recursive-mkdir-path.js";
6
8
  import { root } from "./root.js";
7
9
  import { getFsSafeTestHooks } from "./test-hooks.js";
8
10
  export async function pruneExpiredStoreEntries(params) {
@@ -10,8 +12,8 @@ export async function pruneExpiredStoreEntries(params) {
10
12
  const recursive = params.options.recursive ?? false;
11
13
  const maxDepth = params.options.maxDepth;
12
14
  const pruneEmptyDirs = (recursive || maxDepth !== undefined) && (params.options.pruneEmptyDirs ?? false);
13
- await fs.mkdir(params.rootDir, { recursive: true, mode: params.dirMode });
14
- const rootReal = fsSync.realpathSync.native(params.rootDir);
15
+ await fs.mkdir(recursiveMkdirPath(params.rootDir), { recursive: true, mode: params.dirMode });
16
+ const rootReal = realpathSync.native(params.rootDir);
15
17
  const scopedRoot = await root(rootReal);
16
18
  const rootGuard = {
17
19
  dir: rootReal,
@@ -24,7 +26,7 @@ export async function pruneExpiredStoreEntries(params) {
24
26
  !stat.isDirectory() ||
25
27
  stat.dev !== rootGuard.stat.dev ||
26
28
  stat.ino !== rootGuard.stat.ino ||
27
- fsSync.realpathSync.native(rootGuard.dir) !== rootGuard.realPath) {
29
+ realpathSync.native(rootGuard.dir) !== rootGuard.realPath) {
28
30
  throw new FsSafeError("path-mismatch", "store root changed during prune");
29
31
  }
30
32
  }
@@ -33,7 +35,7 @@ export async function pruneExpiredStoreEntries(params) {
33
35
  if (!before || before.isSymbolicLink() || !before.isDirectory()) {
34
36
  return null;
35
37
  }
36
- const real = observeOrNull(() => fsSync.realpathSync.native(dir));
38
+ const real = observeOrNull(() => realpathSync.native(dir));
37
39
  if (!real || !isPathInside(rootReal, real)) {
38
40
  return null;
39
41
  }
@@ -1 +1 @@
1
- {"version":3,"file":"file-store-sync-write.d.ts","sourceRoot":"","sources":["../src/file-store-sync-write.ts"],"names":[],"mappings":"AAwBA,wBAAgB,mBAAmB,CAAC,MAAM,EAAE;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,UAAU,CAAC;IAC7B,WAAW,EAAE,OAAO,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,MAAM,CA+GT"}
1
+ {"version":3,"file":"file-store-sync-write.d.ts","sourceRoot":"","sources":["../src/file-store-sync-write.ts"],"names":[],"mappings":"AAwDA,wBAAgB,mBAAmB,CAAC,MAAM,EAAE;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,UAAU,CAAC;IAC7B,WAAW,EAAE,OAAO,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,MAAM,CAwFT"}
@@ -1,11 +1,14 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import fs from "node:fs";
2
+ import fs, {} from "node:fs";
3
3
  import path from "node:path";
4
4
  import { syncDirectorySync } from "./directory-durability.js";
5
5
  import { FsSafeError } from "./errors.js";
6
- import { sameFileIdentity } from "./file-identity.js";
7
6
  import { assertSyncDirectoryGuard, ensureParentSync, ensureStoreDirectorySync, } from "./file-store-boundary.js";
8
7
  import { isPathInside } from "./path.js";
8
+ import { resolveReadOpenFlags } from "./read-open-flags.js";
9
+ import { writeTempFileSync } from "./replace-file-descriptor.js";
10
+ import { SyncAtomicTempOwner } from "./replace-file-temp-owner.js";
11
+ import { inspectFileIdentitySync } from "./strict-file-identity.js";
9
12
  import { getFsSafeTestHooks } from "./test-hooks.js";
10
13
  function ensurePrivateDirectorySync(rootDir, targetDir, mode) {
11
14
  return ensureStoreDirectorySync({
@@ -15,6 +18,35 @@ function ensurePrivateDirectorySync(rootDir, targetDir, mode) {
15
18
  messagePrefix: "private store",
16
19
  });
17
20
  }
21
+ function verifyStoreFile(fd, expected, filePath) {
22
+ const assertFile = (stat, allowUnknown = false) => {
23
+ const unknown = allowUnknown && process.platform === "win32" && (stat.dev === 0n || stat.ino === 0n);
24
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.nlink !== 1n ||
25
+ (stat.dev !== expected.dev && !(unknown && stat.dev === 0n)) ||
26
+ (stat.ino !== expected.ino && !(unknown && stat.ino === 0n))) {
27
+ throw new FsSafeError("path-mismatch", "store file changed during write");
28
+ }
29
+ return unknown;
30
+ };
31
+ const assertDescriptor = (descriptor) => {
32
+ assertFile(inspectFileIdentitySync(() => fs.fstatSync(descriptor, { bigint: true }), expected));
33
+ };
34
+ assertDescriptor(fd);
35
+ if (assertFile(fs.lstatSync(filePath, { bigint: true }), true)) {
36
+ // A Windows pathname can have opaque metadata even while the retained writer is known.
37
+ // Reopen for identity only; equal content cannot establish that publication kept its file.
38
+ const reopened = fs.openSync(filePath, resolveReadOpenFlags());
39
+ try {
40
+ assertDescriptor(reopened);
41
+ assertFile(fs.lstatSync(filePath, { bigint: true }), true);
42
+ assertDescriptor(reopened);
43
+ }
44
+ finally {
45
+ fs.closeSync(reopened);
46
+ }
47
+ }
48
+ assertDescriptor(fd);
49
+ }
18
50
  export function writeFileSyncAtomic(params) {
19
51
  const filePath = path.resolve(params.filePath);
20
52
  if (!isPathInside(params.rootDir, filePath)) {
@@ -43,58 +75,41 @@ export function writeFileSyncAtomic(params) {
43
75
  });
44
76
  }
45
77
  const tempPath = path.join(parentGuard?.dir ?? path.dirname(filePath), `.fs-safe-${process.pid}-${randomUUID()}.tmp`);
46
- let tempExists = false;
78
+ const owner = new SyncAtomicTempOwner(tempPath);
79
+ let originalError;
47
80
  try {
48
81
  getFsSafeTestHooks()?.beforeFileStoreSyncPrivateWrite?.(filePath);
49
82
  if (parentGuard) {
50
83
  assertSyncDirectoryGuard(parentGuard);
51
84
  }
52
- const tempStat = (() => {
53
- const descriptor = fs.openSync(tempPath, "wx", params.mode);
54
- tempExists = true;
55
- try {
56
- fs.writeFileSync(descriptor, params.content);
85
+ owner.start();
86
+ const temp = writeTempFileSync({
87
+ fsModule: fs, tempPath, content: params.content, mode: params.mode, sync: false,
88
+ onIdentity: owner.onIdentity,
89
+ fchmodSync: (descriptor, mode) => {
57
90
  try {
58
- fs.fchmodSync(descriptor, params.mode);
91
+ fs.fchmodSync(descriptor, mode);
59
92
  }
60
93
  catch {
61
94
  // Best-effort on platforms that do not enforce POSIX modes.
62
95
  }
63
- const opened = fs.fstatSync(descriptor);
64
- if (!opened.isFile() || opened.nlink > 1) {
65
- throw new FsSafeError("path-mismatch", "store temp is not an owned regular file");
66
- }
67
- if (params.durable)
68
- fs.fsyncSync(descriptor);
69
- return opened;
70
- }
71
- finally {
72
- fs.closeSync(descriptor);
73
- }
74
- })();
75
- const tempPathStat = fs.lstatSync(tempPath);
76
- if (tempPathStat.isSymbolicLink() ||
77
- !tempPathStat.isFile() ||
78
- tempPathStat.nlink > 1 ||
79
- !sameFileIdentity(tempPathStat, tempStat)) {
80
- throw new FsSafeError("path-mismatch", "store temp changed before publication");
81
- }
96
+ },
97
+ });
98
+ owner.adopt(temp);
99
+ // Preserve the store's strict fsync errors; the generic temp helper tolerates EPERM.
100
+ if (params.durable)
101
+ fs.fsyncSync(temp.fd);
102
+ verifyStoreFile(temp.fd, owner.identity, tempPath);
82
103
  if (parentGuard) {
83
104
  assertSyncDirectoryGuard(parentGuard);
84
105
  }
85
106
  fs.renameSync(tempPath, filePath);
86
- tempExists = false;
107
+ owner.markRenamed();
87
108
  if (parentGuard) {
88
109
  assertSyncDirectoryGuard(parentGuard);
89
110
  }
90
111
  try {
91
- const publishedStat = fs.lstatSync(filePath);
92
- if (publishedStat.isSymbolicLink() ||
93
- !publishedStat.isFile() ||
94
- publishedStat.nlink > 1 ||
95
- !sameFileIdentity(tempStat, publishedStat)) {
96
- throw new FsSafeError("path-mismatch", "store target changed after write");
97
- }
112
+ verifyStoreFile(temp.fd, owner.identity, filePath);
98
113
  }
99
114
  catch (error) {
100
115
  if (error instanceof FsSafeError) {
@@ -117,14 +132,11 @@ export function writeFileSyncAtomic(params) {
117
132
  }
118
133
  return filePath;
119
134
  }
135
+ catch (error) {
136
+ originalError = error;
137
+ throw error;
138
+ }
120
139
  finally {
121
- if (tempExists) {
122
- try {
123
- fs.unlinkSync(tempPath);
124
- }
125
- catch {
126
- // Best-effort cleanup after write failure.
127
- }
128
- }
140
+ owner.finish({ fsModule: fs, originalError, throwOnCleanupError: false });
129
141
  }
130
142
  }
@@ -1 +1 @@
1
- {"version":3,"file":"file-store.d.ts","sourceRoot":"","sources":["../src/file-store.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"file-store.d.ts","sourceRoot":"","sources":["../src/file-store.ts"],"names":[],"mappings":"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"}
@@ -5,35 +5,19 @@ import { normalizeMaxBytes } from "./byte-budget.js";
5
5
  import { readFileDescriptorBoundedSync } from "./bounded-read.js";
6
6
  import { FsSafeError } from "./errors.js";
7
7
  import { assertFileStoreMaxBytes } from "./file-store-limit.js";
8
+ import { assertRelativePath, resolveStorePath } from "./file-store-path.js";
8
9
  import { pruneExpiredStoreEntries } from "./file-store-prune.js";
9
10
  import { ensureParentInRoot, openPrivateStoreLockRoot, openWritableStoreRoot, writeStreamToTempSource, } from "./file-store-boundary.js";
10
11
  import { writeFileSyncAtomic } from "./file-store-sync-write.js";
11
12
  import { createJsonStore } from "./json-document-store.js";
12
13
  import { stringifyJsonDocument } from "./json-stringify.js";
13
- import { isNotFoundPathError, resolveSafeRelativePath, splitSafeRelativePath } from "./path.js";
14
+ import { isNotFoundPathError } from "./path.js";
14
15
  import { throwFsSafeReadError } from "./root-errors.js";
15
16
  import { root } from "./root.js";
16
17
  import { DEFAULT_ROOT_MAX_BYTES } from "./root-impl.js";
17
18
  import { readRegularFile } from "./regular-file.js";
18
19
  import { matchRootFileOpenFailure, openRootFileSync } from "./root-file.js";
19
20
  import { writeSecretFileAtomic } from "./secret-file.js";
20
- function assertRelativePath(relativePath) {
21
- const raw = relativePath.trim();
22
- if (!raw || raw !== relativePath) {
23
- throw new FsSafeError("invalid-path", "store key must be non-empty and unpadded");
24
- }
25
- const segments = splitSafeRelativePath(raw);
26
- if (segments.length === 0 ||
27
- segments.join("/") !== raw ||
28
- raw.normalize("NFC") !== raw ||
29
- segments.some((segment) => /[ .]$/u.test(segment))) {
30
- throw new FsSafeError("invalid-path", "store key must use one canonical relative spelling");
31
- }
32
- return raw;
33
- }
34
- function resolveStorePath(rootDir, relativePath) {
35
- return resolveSafeRelativePath(rootDir, assertRelativePath(relativePath));
36
- }
37
21
  function isNotFound(error) {
38
22
  return error instanceof FsSafeError ? error.code === "not-found" : isNotFoundPathError(error);
39
23
  }
@@ -1 +1 @@
1
- {"version":3,"file":"filename.d.ts","sourceRoot":"","sources":["../src/filename.ts"],"names":[],"mappings":"AAqCA,yFAAyF;AACzF,wBAAgB,8BAA8B,CAAC,MAAM,EAAE;IACrD,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,MAAM,CA8BT;AAED,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAgCxF"}
1
+ {"version":3,"file":"filename.d.ts","sourceRoot":"","sources":["../src/filename.ts"],"names":[],"mappings":"AAmCA,yFAAyF;AACzF,wBAAgB,8BAA8B,CAAC,MAAM,EAAE;IACrD,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,MAAM,CA8BT;AAED,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAoBxF"}