@openclaw/fs-safe 0.15.0 → 0.17.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 (311) hide show
  1. package/CHANGELOG.md +86 -0
  2. package/README.md +36 -7
  3. package/dist/absolute-path.d.ts.map +1 -1
  4. package/dist/absolute-path.js +2 -8
  5. package/dist/advanced.d.ts +3 -0
  6. package/dist/advanced.d.ts.map +1 -1
  7. package/dist/advanced.js +3 -0
  8. package/dist/archive-deadline.d.ts.map +1 -1
  9. package/dist/archive-deadline.js +22 -23
  10. package/dist/archive-kind.d.ts +0 -1
  11. package/dist/archive-kind.d.ts.map +1 -1
  12. package/dist/archive-kind.js +5 -17
  13. package/dist/archive-merge.d.ts +1 -0
  14. package/dist/archive-merge.d.ts.map +1 -1
  15. package/dist/archive-merge.js +4 -4
  16. package/dist/archive-native.d.ts +1 -0
  17. package/dist/archive-native.d.ts.map +1 -1
  18. package/dist/archive-native.js +1 -0
  19. package/dist/archive-options.d.ts +2 -0
  20. package/dist/archive-options.d.ts.map +1 -1
  21. package/dist/archive-parser.wasm +0 -0
  22. package/dist/archive-read.d.ts.map +1 -1
  23. package/dist/archive-read.js +6 -7
  24. package/dist/archive-tar-stream.d.ts +3 -0
  25. package/dist/archive-tar-stream.d.ts.map +1 -1
  26. package/dist/archive-tar-stream.js +56 -37
  27. package/dist/archive-tar-wasm.d.ts +16 -4
  28. package/dist/archive-tar-wasm.d.ts.map +1 -1
  29. package/dist/archive-tar-wasm.js +134 -34
  30. package/dist/archive-zip-count.d.ts.map +1 -1
  31. package/dist/archive-zip-count.js +21 -1
  32. package/dist/archive-zip-directory.d.ts.map +1 -1
  33. package/dist/archive-zip-directory.js +23 -1
  34. package/dist/archive-zip-loader.d.ts +2 -0
  35. package/dist/archive-zip-loader.d.ts.map +1 -1
  36. package/dist/archive-zip-loader.js +7 -0
  37. package/dist/archive-zip-names.d.ts.map +1 -1
  38. package/dist/archive-zip-names.js +7 -2
  39. package/dist/archive.d.ts.map +1 -1
  40. package/dist/archive.js +14 -9
  41. package/dist/byte-view.d.ts +3 -0
  42. package/dist/byte-view.d.ts.map +1 -0
  43. package/dist/byte-view.js +13 -0
  44. package/dist/clone-metadata.d.ts +1 -0
  45. package/dist/clone-metadata.d.ts.map +1 -1
  46. package/dist/clone-metadata.js +6 -2
  47. package/dist/create-directory.d.ts +20 -0
  48. package/dist/create-directory.d.ts.map +1 -0
  49. package/dist/create-directory.js +130 -0
  50. package/dist/create-file-async.d.ts +7 -0
  51. package/dist/create-file-async.d.ts.map +1 -0
  52. package/dist/create-file-async.js +121 -0
  53. package/dist/create-file.d.ts +8 -0
  54. package/dist/create-file.d.ts.map +1 -0
  55. package/dist/create-file.js +190 -0
  56. package/dist/create-owned-file.d.ts +8 -0
  57. package/dist/create-owned-file.d.ts.map +1 -0
  58. package/dist/create-owned-file.js +16 -0
  59. package/dist/create.d.ts +4 -0
  60. package/dist/create.d.ts.map +1 -0
  61. package/dist/create.js +2 -0
  62. package/dist/creation-darwin.d.ts +6 -0
  63. package/dist/creation-darwin.d.ts.map +1 -0
  64. package/dist/creation-darwin.js +70 -0
  65. package/dist/creation-file-state.d.ts +19 -0
  66. package/dist/creation-file-state.d.ts.map +1 -0
  67. package/dist/creation-file-state.js +118 -0
  68. package/dist/creation-path.d.ts +21 -0
  69. package/dist/creation-path.d.ts.map +1 -0
  70. package/dist/creation-path.js +71 -0
  71. package/dist/creation-permissions.d.ts +19 -0
  72. package/dist/creation-permissions.d.ts.map +1 -0
  73. package/dist/creation-permissions.js +125 -0
  74. package/dist/directory-durability.d.ts +7 -7
  75. package/dist/directory-durability.d.ts.map +1 -1
  76. package/dist/directory-durability.js +22 -80
  77. package/dist/directory-guard.d.ts +3 -0
  78. package/dist/directory-guard.d.ts.map +1 -1
  79. package/dist/directory-mode-node.d.ts +2 -0
  80. package/dist/directory-mode-node.d.ts.map +1 -1
  81. package/dist/directory-mode-node.js +8 -0
  82. package/dist/directory-receipt.d.ts +24 -0
  83. package/dist/directory-receipt.d.ts.map +1 -0
  84. package/dist/directory-receipt.js +123 -0
  85. package/dist/file-cleanup.d.ts +20 -0
  86. package/dist/file-cleanup.d.ts.map +1 -0
  87. package/dist/file-cleanup.js +81 -0
  88. package/dist/file-contents.d.ts +6 -0
  89. package/dist/file-contents.d.ts.map +1 -0
  90. package/dist/file-contents.js +40 -0
  91. package/dist/file-hash.d.ts.map +1 -1
  92. package/dist/file-hash.js +16 -4
  93. package/dist/file-lock-sync-root-acquire.d.ts.map +1 -1
  94. package/dist/file-lock-sync-root-acquire.js +3 -0
  95. package/dist/file-lock-sync-root-held.d.ts +1 -2
  96. package/dist/file-lock-sync-root-held.d.ts.map +1 -1
  97. package/dist/file-lock-sync-root-held.js +7 -5
  98. package/dist/file-lock-sync-stale-admission.d.ts.map +1 -1
  99. package/dist/file-lock-sync-stale-admission.js +3 -0
  100. package/dist/file-lock-sync.d.ts.map +1 -1
  101. package/dist/file-lock-sync.js +8 -11
  102. package/dist/file-observation.d.ts +1 -1
  103. package/dist/file-observation.d.ts.map +1 -1
  104. package/dist/file-store-boundary.d.ts +2 -6
  105. package/dist/file-store-boundary.d.ts.map +1 -1
  106. package/dist/file-store-boundary.js +3 -9
  107. package/dist/file-store-sync-write.d.ts.map +1 -1
  108. package/dist/file-store-sync-write.js +2 -5
  109. package/dist/file-store.js +3 -3
  110. package/dist/guarded-mkdir.d.ts +1 -0
  111. package/dist/guarded-mkdir.d.ts.map +1 -1
  112. package/dist/guarded-mkdir.js +27 -19
  113. package/dist/install-path.d.ts.map +1 -1
  114. package/dist/install-path.js +2 -5
  115. package/dist/json-durable-queue-ownership.d.ts.map +1 -1
  116. package/dist/json-durable-queue-ownership.js +2 -6
  117. package/dist/json-durable-queue-paths.d.ts.map +1 -1
  118. package/dist/json-durable-queue-paths.js +2 -24
  119. package/dist/json-durable-queue.d.ts.map +1 -1
  120. package/dist/json-durable-queue.js +10 -9
  121. package/dist/json.d.ts.map +1 -1
  122. package/dist/json.js +32 -75
  123. package/dist/local-roots.d.ts.map +1 -1
  124. package/dist/local-roots.js +19 -21
  125. package/dist/move-path-cleanup.d.ts +5 -19
  126. package/dist/move-path-cleanup.d.ts.map +1 -1
  127. package/dist/move-path-cleanup.js +57 -21
  128. package/dist/move-path.d.ts.map +1 -1
  129. package/dist/move-path.js +63 -40
  130. package/dist/native-binding.d.ts +11 -1
  131. package/dist/native-binding.d.ts.map +1 -1
  132. package/dist/native-fallback-warning.d.ts +4 -0
  133. package/dist/native-fallback-warning.d.ts.map +1 -0
  134. package/dist/native-fallback-warning.js +11 -0
  135. package/dist/native-operations.d.ts +0 -2
  136. package/dist/native-operations.d.ts.map +1 -1
  137. package/dist/native-operations.js +0 -24
  138. package/dist/native-parent-admission.d.ts +2 -0
  139. package/dist/native-parent-admission.d.ts.map +1 -1
  140. package/dist/native-parent-admission.js +3 -2
  141. package/dist/native-pinned-write-windows.d.ts +1 -1
  142. package/dist/native-pinned-write-windows.d.ts.map +1 -1
  143. package/dist/native-pinned-write-windows.js +173 -28
  144. package/dist/native-pinned-write.d.ts.map +1 -1
  145. package/dist/native-pinned-write.js +19 -3
  146. package/dist/native-policy-parent-windows.d.ts.map +1 -1
  147. package/dist/native-policy-parent-windows.js +15 -6
  148. package/dist/native-staged-file.d.ts +5 -3
  149. package/dist/native-staged-file.d.ts.map +1 -1
  150. package/dist/native-staged-file.js +90 -40
  151. package/dist/native.js +2 -2
  152. package/dist/opened-realpath.d.ts.map +1 -1
  153. package/dist/opened-realpath.js +11 -2
  154. package/dist/owner-dacl.d.ts.map +1 -1
  155. package/dist/owner-dacl.js +10 -4
  156. package/dist/path.d.ts.map +1 -1
  157. package/dist/path.js +2 -1
  158. package/dist/permissions.d.ts.map +1 -1
  159. package/dist/permissions.js +3 -17
  160. package/dist/pinned-write-input.d.ts +4 -0
  161. package/dist/pinned-write-input.d.ts.map +1 -0
  162. package/dist/pinned-write-input.js +35 -0
  163. package/dist/pinned-write-mode.d.ts +5 -0
  164. package/dist/pinned-write-mode.d.ts.map +1 -0
  165. package/dist/pinned-write-mode.js +31 -0
  166. package/dist/pinned-write-staged.d.ts +6 -0
  167. package/dist/pinned-write-staged.d.ts.map +1 -0
  168. package/dist/pinned-write-staged.js +186 -0
  169. package/dist/pinned-write-types.d.ts +3 -0
  170. package/dist/pinned-write-types.d.ts.map +1 -1
  171. package/dist/pinned-write.d.ts.map +1 -1
  172. package/dist/pinned-write.js +41 -147
  173. package/dist/private-directory.d.ts.map +1 -1
  174. package/dist/private-directory.js +18 -4
  175. package/dist/private-producer-handoff-sync.d.ts +14 -0
  176. package/dist/private-producer-handoff-sync.d.ts.map +1 -0
  177. package/dist/private-producer-handoff-sync.js +114 -0
  178. package/dist/private-producer-handoff.d.ts +22 -4
  179. package/dist/private-producer-handoff.d.ts.map +1 -1
  180. package/dist/private-producer-handoff.js +140 -77
  181. package/dist/publish-copy-stage.d.ts +2 -1
  182. package/dist/publish-copy-stage.d.ts.map +1 -1
  183. package/dist/publish-copy-stage.js +16 -7
  184. package/dist/publish-file.d.ts +2 -2
  185. package/dist/publish-file.d.ts.map +1 -1
  186. package/dist/publish-file.js +58 -98
  187. package/dist/regular-file.d.ts.map +1 -1
  188. package/dist/regular-file.js +35 -44
  189. package/dist/replace-file-copy-fallback.d.ts.map +1 -1
  190. package/dist/replace-file-copy-fallback.js +28 -26
  191. package/dist/replace-file-copy-source.d.ts.map +1 -1
  192. package/dist/replace-file-copy-source.js +13 -22
  193. package/dist/replace-file-descriptor.d.ts.map +1 -1
  194. package/dist/replace-file-descriptor.js +10 -16
  195. package/dist/replace-file-temp-owner.d.ts +0 -7
  196. package/dist/replace-file-temp-owner.d.ts.map +1 -1
  197. package/dist/replace-file-temp-owner.js +9 -60
  198. package/dist/replace-file.d.ts.map +1 -1
  199. package/dist/replace-file.js +9 -13
  200. package/dist/root-create-input.d.ts +2 -1
  201. package/dist/root-create-input.d.ts.map +1 -1
  202. package/dist/root-create-input.js +13 -4
  203. package/dist/root-directory-creation.d.ts +3 -3
  204. package/dist/root-directory-creation.d.ts.map +1 -1
  205. package/dist/root-directory-creation.js +15 -3
  206. package/dist/root-directory-list.d.ts.map +1 -1
  207. package/dist/root-directory-list.js +20 -3
  208. package/dist/root-file-final-admission.d.ts +1 -1
  209. package/dist/root-file-final-admission.d.ts.map +1 -1
  210. package/dist/root-file-final-admission.js +5 -2
  211. package/dist/root-file.d.ts.map +1 -1
  212. package/dist/root-file.js +3 -2
  213. package/dist/root-impl.d.ts.map +1 -1
  214. package/dist/root-impl.js +78 -27
  215. package/dist/root-move-noreplace.d.ts +2 -0
  216. package/dist/root-move-noreplace.d.ts.map +1 -1
  217. package/dist/root-move-noreplace.js +22 -13
  218. package/dist/root-options.d.ts +12 -4
  219. package/dist/root-options.d.ts.map +1 -1
  220. package/dist/root-path-stat.d.ts.map +1 -1
  221. package/dist/root-path-stat.js +59 -7
  222. package/dist/root-read-admission.d.ts.map +1 -1
  223. package/dist/root-read-admission.js +7 -2
  224. package/dist/root-remove.d.ts.map +1 -1
  225. package/dist/root-remove.js +15 -1
  226. package/dist/root-write-publication.js +1 -1
  227. package/dist/secret-file.d.ts.map +1 -1
  228. package/dist/secret-file.js +1 -0
  229. package/dist/secure-file-windows.d.ts +6 -0
  230. package/dist/secure-file-windows.d.ts.map +1 -1
  231. package/dist/secure-file-windows.js +34 -117
  232. package/dist/secure-file.js +2 -2
  233. package/dist/sidecar-lock-acquire.d.ts.map +1 -1
  234. package/dist/sidecar-lock-acquire.js +4 -6
  235. package/dist/sidecar-lock-handle.d.ts +3 -0
  236. package/dist/sidecar-lock-handle.d.ts.map +1 -1
  237. package/dist/sidecar-lock-handle.js +6 -0
  238. package/dist/sidecar-lock-reclaim.d.ts +1 -1
  239. package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
  240. package/dist/sidecar-lock-reclaim.js +11 -8
  241. package/dist/sidecar-lock-root.d.ts.map +1 -1
  242. package/dist/sidecar-lock-root.js +2 -1
  243. package/dist/sidecar-lock.d.ts.map +1 -1
  244. package/dist/sidecar-lock.js +3 -5
  245. package/dist/staged-directory.d.ts +2 -2
  246. package/dist/staged-directory.d.ts.map +1 -1
  247. package/dist/staged-directory.js +6 -6
  248. package/dist/staged-file-settlement.d.ts +17 -0
  249. package/dist/staged-file-settlement.d.ts.map +1 -0
  250. package/dist/staged-file-settlement.js +57 -0
  251. package/dist/strict-file-identity.d.ts +1 -1
  252. package/dist/strict-file-identity.d.ts.map +1 -1
  253. package/dist/strict-file-identity.js +9 -9
  254. package/dist/symlink-parents.d.ts.map +1 -1
  255. package/dist/symlink-parents.js +2 -27
  256. package/dist/temp-workspace-owner.js +4 -4
  257. package/dist/unicode-path.d.ts.map +1 -1
  258. package/dist/unicode-path.js +3 -0
  259. package/dist/walk.d.ts.map +1 -1
  260. package/dist/walk.js +4 -2
  261. package/dist/windows-owner.d.ts.map +1 -1
  262. package/dist/windows-owner.js +2 -1
  263. package/dist/windows-security-bridge.cs +336 -0
  264. package/dist/windows-security-bridge.ps1 +15 -0
  265. package/dist/windows-security-command.d.ts +26 -0
  266. package/dist/windows-security-command.d.ts.map +1 -0
  267. package/dist/windows-security-command.js +363 -0
  268. package/dist/windows-security-facts.d.ts +6 -0
  269. package/dist/windows-security-facts.d.ts.map +1 -0
  270. package/dist/windows-security-facts.js +108 -0
  271. package/dist/write-file-handle.d.ts +7 -0
  272. package/dist/write-file-handle.d.ts.map +1 -1
  273. package/dist/write-file-handle.js +23 -0
  274. package/dist/write-open-flags.d.ts.map +1 -1
  275. package/dist/write-open-flags.js +1 -8
  276. package/dist/write-queue.d.ts.map +1 -1
  277. package/dist/write-queue.js +1 -4
  278. package/docs/advanced.md +71 -2
  279. package/docs/archive.md +102 -39
  280. package/docs/atomic.md +29 -5
  281. package/docs/config.md +6 -2
  282. package/docs/contributing.md +48 -4
  283. package/docs/copy.md +2 -0
  284. package/docs/creation.md +132 -0
  285. package/docs/durability.md +59 -0
  286. package/docs/file-contents.md +68 -0
  287. package/docs/install.md +31 -7
  288. package/docs/json.md +5 -4
  289. package/docs/local-roots.md +2 -0
  290. package/docs/migrating-to-0.5.md +15 -6
  291. package/docs/migrating-to-0.6.md +9 -4
  292. package/docs/mutation-policy-proof.md +5 -3
  293. package/docs/native-helper.md +22 -9
  294. package/docs/native.md +47 -15
  295. package/docs/path.md +4 -4
  296. package/docs/permissions.md +37 -14
  297. package/docs/public-api.md +5 -0
  298. package/docs/quickstart.md +1 -1
  299. package/docs/reading.md +2 -2
  300. package/docs/regular-file.md +3 -0
  301. package/docs/root.md +43 -0
  302. package/docs/secret-file.md +11 -2
  303. package/docs/secure-file.md +9 -4
  304. package/docs/sidecar-lock.md +14 -5
  305. package/docs/staged-file.md +9 -3
  306. package/docs/store.md +3 -1
  307. package/docs/temp.md +4 -1
  308. package/docs/types.md +18 -2
  309. package/docs/walk.md +7 -0
  310. package/docs/writing.md +80 -7
  311. package/package.json +18 -15
package/dist/archive.js CHANGED
@@ -9,10 +9,11 @@ import { pipeline } from "node:stream/promises";
9
9
  import { createArchiveOutputPathTracker, resolveArchiveOutputPath, stripArchivePath, validateArchiveEntryPath, } from "./archive-entry.js";
10
10
  import { createPipelineTimeoutError, waitForDeadline, withExtractionDeadline, } from "./archive-deadline.js";
11
11
  import { assertArchiveEntryCountWithinLimit, assertArchiveEntryPathComponentsWithinLimit, createByteBudgetTracker, createExtractBudgetTransform, resolveExtractLimits, resolveTarMeterLimits, } from "./archive-limits.js";
12
- import { assertPortableArchiveKind, resolveArchiveKind } from "./archive-kind.js";
12
+ import { resolveArchiveKind } from "./archive-kind.js";
13
13
  import { prepareArchiveDestinationGuard, preparePrivateArchiveOutputPath, withStagedArchiveDestination, } from "./archive-staging.js";
14
14
  import { mergePlannedArchiveIntoDestination } from "./archive-merge.js";
15
15
  import { loadZipArchiveWithPreflight } from "./archive-zip-preflight.js";
16
+ import { admittedZipEntries } from "./archive-zip-loader.js";
16
17
  import { zipEntryKind, zipEntryDeclaredSize, zipEntryMode, } from "./archive-zip-entry.js";
17
18
  import { createZipIntegrityTransform, normalizeZipIntegrityError, } from "./archive-zip-integrity.js";
18
19
  import { FsSafeError } from "./errors.js";
@@ -131,7 +132,7 @@ async function extractZip(params) {
131
132
  params.deadline.check();
132
133
  const zip = await waitForDeadline(loadZipArchiveWithPreflight(buffer, limits), params.deadline);
133
134
  params.deadline.check();
134
- const entries = Object.values(zip.files);
135
+ const entries = admittedZipEntries(zip);
135
136
  const strip = Math.max(0, Math.floor(params.stripComponents ?? 0));
136
137
  assertArchiveEntryCountWithinLimit(entries.length, limits);
137
138
  const budget = createByteBudgetTracker(limits);
@@ -192,6 +193,7 @@ async function extractZip(params) {
192
193
  await mergePlannedArchiveIntoDestination({
193
194
  entries: acceptedEntries,
194
195
  durable: params.durable,
196
+ entryUmask: params.entryUmask,
195
197
  sourceDir: stagingRealDir,
196
198
  destinationGuard,
197
199
  deadline: params.deadline,
@@ -207,6 +209,10 @@ async function extractZip(params) {
207
209
  export async function extractArchive(params) {
208
210
  const archivePath = params.archivePath;
209
211
  const destDir = params.destDir;
212
+ const { entryUmask = 0 } = params;
213
+ if (!Number.isInteger(entryUmask) || entryUmask < 0 || entryUmask > 0o777) {
214
+ throw new RangeError("archive entryUmask must be an integer between 0 and 0o777");
215
+ }
210
216
  const onFiltered = resolveArchiveFilteredEntryPolicy(params.onFiltered);
211
217
  const kind = params.kind ?? resolveArchiveKind(archivePath);
212
218
  if (!kind) {
@@ -224,14 +230,13 @@ export async function extractArchive(params) {
224
230
  archivePath, destDir,
225
231
  durable: params.durable,
226
232
  stripComponents: params.stripComponents, limits,
227
- entryModes: params.entryModes, entryFilter: params.entryFilter, onFiltered,
233
+ entryModes: params.entryModes, entryUmask, entryFilter: params.entryFilter, onFiltered,
228
234
  };
229
235
  if (native) {
230
236
  await withExtractionDeadline(params.timeoutMs, label, async (deadline) => extractNativeArchive({ ...options, binding: native, kind, tarLimits, deadline }));
231
237
  return;
232
238
  }
233
- assertPortableArchiveKind(kind);
234
- if (kind === "tar") {
239
+ if (kind !== "zip") {
235
240
  await withExtractionDeadline(params.timeoutMs, label, async (deadline) => {
236
241
  const stagedArchive = await stageArchiveFileForExtraction({
237
242
  archivePath: options.archivePath,
@@ -239,7 +244,7 @@ export async function extractArchive(params) {
239
244
  deadline,
240
245
  });
241
246
  try {
242
- await extractWasmTar({ archivePath: stagedArchive.path, options, limits, tarLimits, deadline });
247
+ await extractWasmTar({ archivePath: stagedArchive.path, kind, options, limits, tarLimits, deadline });
243
248
  }
244
249
  finally {
245
250
  await stagedArchive.cleanup();
@@ -252,7 +257,7 @@ export async function extractArchive(params) {
252
257
  async function extractWasmTar(params) {
253
258
  const { options, deadline, tarLimits } = params;
254
259
  const manifest = [];
255
- await inspectTar({ archivePath: params.archivePath, limits: tarLimits, signal: deadline.signal,
260
+ await inspectTar({ archivePath: params.archivePath, kind: params.kind, limits: tarLimits, signal: deadline.signal,
256
261
  onMember: (entry) => { manifest.push(entry); } });
257
262
  deadline.check();
258
263
  const destinationGuard = await prepareArchiveDestinationGuard(options.destDir);
@@ -265,7 +270,7 @@ async function extractWasmTar(params) {
265
270
  const planned = planEntry(entry);
266
271
  return planned ? [{ ...entry, ...planned }] : [];
267
272
  });
268
- await replayTar({ archivePath: params.archivePath, limits: tarLimits, signal: deadline.signal, members: accepted,
273
+ await replayTar({ archivePath: params.archivePath, kind: params.kind, limits: tarLimits, signal: deadline.signal, members: accepted,
269
274
  async consume(member, payload) {
270
275
  deadline.check();
271
276
  await preparePrivateArchiveOutputPath({ destinationDir: stagingDir, destinationRealDir: stagingDir,
@@ -282,7 +287,7 @@ async function extractWasmTar(params) {
282
287
  });
283
288
  deadline.check();
284
289
  await mergePlannedArchiveIntoDestination({ entries: accepted, sourceDir: stagingDir, destinationGuard,
285
- deadline, durable: options.durable });
290
+ deadline, durable: options.durable, entryUmask: options.entryUmask });
286
291
  deadline.check();
287
292
  } });
288
293
  }
@@ -0,0 +1,3 @@
1
+ /** Snapshot intrinsic view bounds without copying the borrowed payload. */
2
+ export declare function snapshotByteView(bytes: Uint8Array): Uint8Array;
3
+ //# sourceMappingURL=byte-view.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"byte-view.d.ts","sourceRoot":"","sources":["../src/byte-view.ts"],"names":[],"mappings":"AAMA,2EAA2E;AAC3E,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,UAAU,GAAG,UAAU,CAK9D"}
@@ -0,0 +1,13 @@
1
+ const ByteView = Uint8Array;
2
+ const typedArray = Object.getPrototypeOf(ByteView.prototype);
3
+ const viewLength = Object.getOwnPropertyDescriptor(typedArray, "byteLength").get;
4
+ const viewOffset = Object.getOwnPropertyDescriptor(typedArray, "byteOffset").get;
5
+ const viewBuffer = Object.getOwnPropertyDescriptor(typedArray, "buffer").get;
6
+ /** Snapshot intrinsic view bounds without copying the borrowed payload. */
7
+ export function snapshotByteView(bytes) {
8
+ const length = Reflect.apply(viewLength, bytes, []);
9
+ // Preserve empty, detached, and out-of-bounds views as zero-byte inputs.
10
+ if (length === 0)
11
+ return new ByteView(0);
12
+ return new ByteView(Reflect.apply(viewBuffer, bytes, []), Reflect.apply(viewOffset, bytes, []), length);
13
+ }
@@ -13,6 +13,7 @@ export type CloneFileMetadata = {
13
13
  cloneId: bigint;
14
14
  };
15
15
  /** Batched APFS metadata snapshots; absent on unsupported files or filesystems.
16
+ * On macOS, these snapshots require the native helper.
16
17
  * These are point-in-time facts, not authorization or a guarantee against later edits.
17
18
  */
18
19
  export declare function readCloneFileMetadata(files: readonly string[]): Promise<(CloneFileMetadata | undefined)[]>;
@@ -1 +1 @@
1
- {"version":3,"file":"clone-metadata.d.ts","sourceRoot":"","sources":["../src/clone-metadata.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,iBAAiB,GAAG;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF;;GAEG;AACH,wBAAsB,qBAAqB,CACzC,KAAK,EAAE,SAAS,MAAM,EAAE,GACvB,OAAO,CAAC,CAAC,iBAAiB,GAAG,SAAS,CAAC,EAAE,CAAC,CA4B5C"}
1
+ {"version":3,"file":"clone-metadata.d.ts","sourceRoot":"","sources":["../src/clone-metadata.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,iBAAiB,GAAG;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF;;;GAGG;AACH,wBAAsB,qBAAqB,CACzC,KAAK,EAAE,SAAS,MAAM,EAAE,GACvB,OAAO,CAAC,CAAC,iBAAiB,GAAG,SAAS,CAAC,EAAE,CAAC,CA8B5C"}
@@ -1,11 +1,15 @@
1
1
  import { assertAbsolutePathInput } from "./absolute-path.js";
2
- import { requireNativeBinding } from "./native.js";
2
+ import { getNativeBinding, requireNativeBinding } from "./native.js";
3
3
  /** Batched APFS metadata snapshots; absent on unsupported files or filesystems.
4
+ * On macOS, these snapshots require the native helper.
4
5
  * These are point-in-time facts, not authorization or a guarantee against later edits.
5
6
  */
6
7
  export async function readCloneFileMetadata(files) {
7
8
  const paths = files.map(assertAbsolutePathInput);
8
- const results = await requireNativeBinding().readCloneFileMetadata(paths);
9
+ const native = process.platform === "darwin" ? requireNativeBinding() : getNativeBinding();
10
+ if (!native)
11
+ return paths.map(() => undefined);
12
+ const results = await native.readCloneFileMetadata(paths);
9
13
  return results.map((result) => {
10
14
  if (!result ||
11
15
  result.length !== 100 ||
@@ -0,0 +1,20 @@
1
+ import { type BigIntStats } from "node:fs";
2
+ import { type CreationPermissions } from "./creation-permissions.js";
3
+ import { type CreationParentIdentity } from "./creation-path.js";
4
+ export type CreateDirectoryOptions = CreationPermissions & {
5
+ assertBeforeMutation?: () => void;
6
+ };
7
+ export type CreationAdmission = {
8
+ expectedParentIdentity?: CreationParentIdentity;
9
+ };
10
+ export type CreationDirectoryReceipt = {
11
+ stat: BigIntStats;
12
+ windowsIdentity?: string;
13
+ };
14
+ export declare function createDirectory(targetPath: string, options?: CreateDirectoryOptions): Promise<void>;
15
+ export declare function createDirectoryWithAdmission(targetPath: string, options?: CreateDirectoryOptions, admission?: CreationAdmission): Promise<void>;
16
+ export declare function createDirectoryWithReceipt(targetPath: string, options?: CreateDirectoryOptions, admission?: CreationAdmission): Promise<CreationDirectoryReceipt>;
17
+ export declare function createDirectorySync(targetPath: string, options?: CreateDirectoryOptions): void;
18
+ export declare function createDirectorySyncWithAdmission(targetPath: string, options?: CreateDirectoryOptions, admission?: CreationAdmission): void;
19
+ export declare function createDirectoryWithReceiptSync(targetPath: string, options?: CreateDirectoryOptions, admission?: CreationAdmission): CreationDirectoryReceipt;
20
+ //# sourceMappingURL=create-directory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-directory.d.ts","sourceRoot":"","sources":["../src/create-directory.ts"],"names":[],"mappings":"AAAA,OAAe,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAMnD,OAAO,EAML,KAAK,mBAAmB,EACzB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAA0C,KAAK,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AASzG,MAAM,MAAM,sBAAsB,GAAG,mBAAmB,GAAG;IAAE,oBAAoB,CAAC,EAAE,MAAM,IAAI,CAAA;CAAE,CAAC;AACjG,MAAM,MAAM,iBAAiB,GAAG;IAAE,sBAAsB,CAAC,EAAE,sBAAsB,CAAA;CAAE,CAAC;AACpF,MAAM,MAAM,wBAAwB,GAAG;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,eAAe,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAwBvF,wBAAsB,eAAe,CACnC,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,IAAI,CAAC,CAEf;AAED,wBAAsB,4BAA4B,CAChD,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,sBAA2B,EACpC,SAAS,GAAE,iBAAsB,GAChC,OAAO,CAAC,IAAI,CAAC,CAEf;AAED,wBAAsB,0BAA0B,CAC9C,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,sBAA2B,EACpC,SAAS,GAAE,iBAAsB,GAChC,OAAO,CAAC,wBAAwB,CAAC,CAmCnC;AAED,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,sBAA2B,GACnC,IAAI,CAEN;AAED,wBAAgB,gCAAgC,CAC9C,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,sBAA2B,EACpC,SAAS,GAAE,iBAAsB,GAChC,IAAI,CAEN;AAED,wBAAgB,8BAA8B,CAC5C,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,sBAA2B,EACpC,SAAS,GAAE,iBAAsB,GAChC,wBAAwB,CAmC1B"}
@@ -0,0 +1,130 @@
1
+ import fsSync, {} from "node:fs";
2
+ import fs from "node:fs/promises";
3
+ import { FsSafeError } from "./errors.js";
4
+ import { getNativeBinding } from "./native.js";
5
+ import { getFsSafeNativeConfig } from "./native-config.js";
6
+ import { assertSynchronousCallbackResult } from "./mutation-authority.js";
7
+ import { assertPrivateDirectory, assertPrivateDirectorySync, inspectCreationDirectory, inspectCreationDirectorySync, resolveCreationPermissions, } from "./creation-permissions.js";
8
+ import { creationCollision, prepareCreationPath } from "./creation-path.js";
9
+ import { createPrivateWindowsDirectoryCommand, createPrivateWindowsDirectoryCommandSync, } from "./windows-security-command.js";
10
+ import { warnNativeFallback } from "./native-fallback-warning.js";
11
+ import { inspectDirectoryIdentitySync } from "./directory-guard.js";
12
+ import { assertDarwinCreationDirectoryAcl, assertDarwinPrivateDirectoryMode } from "./creation-darwin.js";
13
+ function directoryAdoptionFailure(error, target, windowsIdentity) {
14
+ return new FsSafeError("helper-failed", "created directory admission failed", {
15
+ cause: error,
16
+ details: {
17
+ publication: { status: "published" }, path: target, cleanup: "preserved",
18
+ ...(windowsIdentity === undefined ? {} : { windowsIdentity }),
19
+ },
20
+ });
21
+ }
22
+ function privateDirectoryBackend() {
23
+ const binding = getNativeBinding();
24
+ if (typeof binding?.createPrivateDirectoryWithParentIdentity === "function") {
25
+ return binding.createPrivateDirectoryWithParentIdentity.bind(binding);
26
+ }
27
+ if (getFsSafeNativeConfig().mode === "require") {
28
+ throw new FsSafeError("helper-unavailable", "private directory creation requires an up-to-date native parent identity capability");
29
+ }
30
+ warnNativeFallback("windows-private-directory", "Private Windows directory creation uses a slower built-in system command.");
31
+ return undefined;
32
+ }
33
+ export async function createDirectory(targetPath, options = {}) {
34
+ await createDirectoryWithAdmission(targetPath, options, {});
35
+ }
36
+ export async function createDirectoryWithAdmission(targetPath, options = {}, admission = {}) {
37
+ await createDirectoryWithReceipt(targetPath, options, admission);
38
+ }
39
+ export async function createDirectoryWithReceipt(targetPath, options = {}, admission = {}) {
40
+ const permissions = resolveCreationPermissions(options, true);
41
+ const assertion = options.assertBeforeMutation;
42
+ const selected = prepareCreationPath(targetPath, admission.expectedParentIdentity);
43
+ const backend = permissions.private && process.platform === "win32" ? privateDirectoryBackend() : undefined;
44
+ const parentIdentity = permissions.private && process.platform === "win32"
45
+ ? await inspectCreationDirectory(selected.parent.dir, false) : undefined;
46
+ if (permissions.private)
47
+ assertDarwinCreationDirectoryAcl(selected.parent.dir, selected.parent.stat, "directory");
48
+ assertSynchronousCallbackResult(assertion?.(), "assertBeforeMutation");
49
+ selected.assertParent();
50
+ if (permissions.private)
51
+ assertDarwinPrivateDirectoryMode(permissions.mode);
52
+ if (permissions.private)
53
+ assertDarwinCreationDirectoryAcl(selected.parent.dir, selected.parent.stat, "directory");
54
+ let created = false;
55
+ let windowsIdentity;
56
+ try {
57
+ if (parentIdentity !== undefined) {
58
+ const receipt = backend
59
+ ? backend(selected.target, parentIdentity)
60
+ : await createPrivateWindowsDirectoryCommand(selected.target, parentIdentity);
61
+ created = true;
62
+ windowsIdentity = receipt.identity;
63
+ if ((await inspectCreationDirectory(selected.target, true)) !== receipt.identity) {
64
+ throw new FsSafeError("path-mismatch", "created private directory changed before adoption");
65
+ }
66
+ }
67
+ else {
68
+ await fs.mkdir(selected.target, { mode: permissions.mode });
69
+ created = true;
70
+ if (permissions.private)
71
+ await assertPrivateDirectory(selected.target);
72
+ }
73
+ selected.assertParent();
74
+ return { stat: inspectDirectoryIdentitySync(selected.target), windowsIdentity };
75
+ }
76
+ catch (error) {
77
+ if (created)
78
+ throw directoryAdoptionFailure(error, selected.target, windowsIdentity);
79
+ throw creationCollision(error);
80
+ }
81
+ }
82
+ export function createDirectorySync(targetPath, options = {}) {
83
+ createDirectoryWithReceiptSync(targetPath, options, {});
84
+ }
85
+ export function createDirectorySyncWithAdmission(targetPath, options = {}, admission = {}) {
86
+ createDirectoryWithReceiptSync(targetPath, options, admission);
87
+ }
88
+ export function createDirectoryWithReceiptSync(targetPath, options = {}, admission = {}) {
89
+ const permissions = resolveCreationPermissions(options, true);
90
+ const assertion = options.assertBeforeMutation;
91
+ const selected = prepareCreationPath(targetPath, admission.expectedParentIdentity);
92
+ const backend = permissions.private && process.platform === "win32" ? privateDirectoryBackend() : undefined;
93
+ const parentIdentity = permissions.private && process.platform === "win32"
94
+ ? inspectCreationDirectorySync(selected.parent.dir, false) : undefined;
95
+ if (permissions.private)
96
+ assertDarwinCreationDirectoryAcl(selected.parent.dir, selected.parent.stat, "directory");
97
+ assertSynchronousCallbackResult(assertion?.(), "assertBeforeMutation");
98
+ selected.assertParent();
99
+ if (permissions.private)
100
+ assertDarwinPrivateDirectoryMode(permissions.mode);
101
+ if (permissions.private)
102
+ assertDarwinCreationDirectoryAcl(selected.parent.dir, selected.parent.stat, "directory");
103
+ let created = false;
104
+ let windowsIdentity;
105
+ try {
106
+ if (parentIdentity !== undefined) {
107
+ const receipt = backend
108
+ ? backend(selected.target, parentIdentity)
109
+ : createPrivateWindowsDirectoryCommandSync(selected.target, parentIdentity);
110
+ created = true;
111
+ windowsIdentity = receipt.identity;
112
+ if (inspectCreationDirectorySync(selected.target, true) !== receipt.identity) {
113
+ throw new FsSafeError("path-mismatch", "created private directory changed before adoption");
114
+ }
115
+ }
116
+ else {
117
+ fsSync.mkdirSync(selected.target, { mode: permissions.mode });
118
+ created = true;
119
+ if (permissions.private)
120
+ assertPrivateDirectorySync(selected.target);
121
+ }
122
+ selected.assertParent();
123
+ return { stat: inspectDirectoryIdentitySync(selected.target), windowsIdentity };
124
+ }
125
+ catch (error) {
126
+ if (created)
127
+ throw directoryAdoptionFailure(error, selected.target, windowsIdentity);
128
+ throw creationCollision(error);
129
+ }
130
+ }
@@ -0,0 +1,7 @@
1
+ import { type FileHandle } from "node:fs/promises";
2
+ import { type CreationPath } from "./creation-path.js";
3
+ export declare function createPrivateWindowsFileHandle(selected: CreationPath, options: {
4
+ mode: number;
5
+ assertBeforeMutation?: () => void;
6
+ }): Promise<FileHandle>;
7
+ //# sourceMappingURL=create-file-async.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-file-async.d.ts","sourceRoot":"","sources":["../src/create-file-async.ts"],"names":[],"mappings":"AAEA,OAAW,EAAE,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAevD,OAAO,EAAmD,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAKxG,wBAAsB,8BAA8B,CAClD,QAAQ,EAAE,YAAY,EACtB,OAAO,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,oBAAoB,CAAC,EAAE,MAAM,IAAI,CAAA;CAAE,GAC3D,OAAO,CAAC,UAAU,CAAC,CA0FrB"}
@@ -0,0 +1,121 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import fsSync, {} from "node:fs";
3
+ import fs, {} from "node:fs/promises";
4
+ import path from "node:path";
5
+ import { createDirectoryWithReceipt } from "./create-directory.js";
6
+ import { assertDirectoryIdentitySync } from "./directory-guard.js";
7
+ import { FsSafeError } from "./errors.js";
8
+ import { assertSynchronousCallbackResult } from "./mutation-authority.js";
9
+ import { assertCreationFile, creationPublicationAfterFailure, privateFileSettlementFailure, rethrowPrivateStageCreationFailure, removeRecordedCreationFile, } from "./creation-file-state.js";
10
+ import { inspectCreationDirectory, protectCreatedFile, verifyCreatedFile } from "./creation-permissions.js";
11
+ import { creationCollision, removeCreationDirectoryAsync } from "./creation-path.js";
12
+ import { handoffCreatedFile } from "./private-producer-handoff.js";
13
+ import { resolveReadOpenFlags } from "./read-open-flags.js";
14
+ import { hasUnsettledWindowsSecurityCommand } from "./windows-security-command.js";
15
+ export async function createPrivateWindowsFileHandle(selected, options) {
16
+ const assertion = options.assertBeforeMutation;
17
+ const existing = await fs.lstat(selected.target).catch((error) => {
18
+ if (error.code !== "ENOENT")
19
+ throw error;
20
+ return undefined;
21
+ });
22
+ selected.assertParent();
23
+ if (existing)
24
+ throw new FsSafeError("already-exists", "creation target already exists");
25
+ const parentIdentity = await inspectCreationDirectory(selected.parent.dir, false);
26
+ selected.assertParent();
27
+ const stageDirectory = path.join(selected.parent.dir, `.fs-safe-create-${randomUUID()}`);
28
+ let stage;
29
+ try {
30
+ stage = await createDirectoryWithReceipt(stageDirectory, { private: true, assertBeforeMutation: assertion }, {
31
+ expectedParentIdentity: selected.parent.stat,
32
+ });
33
+ }
34
+ catch (error) {
35
+ rethrowPrivateStageCreationFailure(error, selected.target, stageDirectory);
36
+ }
37
+ const stageIdentity = stage.windowsIdentity;
38
+ const stagePath = path.join(stageDirectory, "file");
39
+ const assertStage = () => {
40
+ selected.assertParent();
41
+ assertDirectoryIdentitySync(stageDirectory, stage.stat);
42
+ };
43
+ let file;
44
+ let identity;
45
+ let publication = "not-published";
46
+ try {
47
+ if (await inspectCreationDirectory(stageDirectory, true) !== stageIdentity) {
48
+ throw new FsSafeError("path-mismatch", "private staging directory changed before file creation");
49
+ }
50
+ assertSynchronousCallbackResult(assertion?.(), "assertBeforeMutation");
51
+ assertStage();
52
+ file = await fs.open(stagePath, fsSync.constants.O_RDWR | fsSync.constants.O_CREAT |
53
+ fsSync.constants.O_EXCL | resolveReadOpenFlags(), 0o600);
54
+ assertStage();
55
+ identity = assertCreationFile(file.fd, stagePath);
56
+ assertSynchronousCallbackResult(assertion?.(), "assertBeforeMutation");
57
+ assertStage();
58
+ assertCreationFile(file.fd, stagePath, identity);
59
+ const windowsIdentity = await protectCreatedFile(file.fd, stagePath, stageIdentity);
60
+ assertStage();
61
+ assertCreationFile(file.fd, stagePath, identity);
62
+ const source = file;
63
+ file = undefined;
64
+ // The handoff consumes the source pin on both success and failure.
65
+ file = await handoffCreatedFile({
66
+ source, sourcePath: stagePath, targetPath: selected.target, identity,
67
+ assertSourceParent: assertStage, assertTargetParent: selected.assertParent,
68
+ assertBeforeMutation: assertion,
69
+ verifyDescriptor: (fd, pathname, links) => verifyCreatedFile(fd, pathname, windowsIdentity, pathname === stagePath ? stageIdentity : parentIdentity, links),
70
+ onPublished: () => { publication = "published"; },
71
+ });
72
+ assertSynchronousCallbackResult(assertion?.(), "assertBeforeMutation");
73
+ selected.assertParent();
74
+ assertCreationFile(file.fd, selected.target, identity);
75
+ await verifyCreatedFile(file.fd, selected.target, windowsIdentity, parentIdentity);
76
+ selected.assertParent();
77
+ assertCreationFile(file.fd, selected.target, identity);
78
+ await file.chmod(options.mode);
79
+ await verifyCreatedFile(file.fd, selected.target, windowsIdentity, parentIdentity);
80
+ selected.assertParent();
81
+ assertCreationFile(file.fd, selected.target, identity);
82
+ await removeCreationDirectoryAsync(stageDirectory, stage.stat, selected.assertParent);
83
+ selected.assertParent();
84
+ assertCreationFile(file.fd, selected.target, identity);
85
+ return file;
86
+ }
87
+ catch (primary) {
88
+ publication = creationPublicationAfterFailure(primary, publication);
89
+ const preserved = publication === "indeterminate" || hasUnsettledWindowsSecurityCommand(primary);
90
+ const cleanup = [];
91
+ if (file) {
92
+ const owned = file;
93
+ file = undefined;
94
+ try {
95
+ await owned.close();
96
+ }
97
+ catch (error) {
98
+ cleanup.push(error);
99
+ }
100
+ }
101
+ if (!preserved) {
102
+ if (identity) {
103
+ try {
104
+ await removeRecordedCreationFile(stagePath, identity, assertStage);
105
+ }
106
+ catch (error) {
107
+ cleanup.push(error);
108
+ }
109
+ }
110
+ try {
111
+ await removeCreationDirectoryAsync(stageDirectory, stage.stat, selected.assertParent);
112
+ }
113
+ catch (error) {
114
+ cleanup.push(error);
115
+ }
116
+ }
117
+ if (publication === "not-published" && cleanup.length === 0 && !preserved)
118
+ throw creationCollision(primary);
119
+ throw privateFileSettlementFailure({ primary, cleanup, publication, path: selected.target, stageDirectory, preserved });
120
+ }
121
+ }
@@ -0,0 +1,8 @@
1
+ import { type FileHandle } from "node:fs/promises";
2
+ import { type CreateDirectoryOptions, type CreationAdmission } from "./create-directory.js";
3
+ import { type OwnedFileDescriptorSync } from "./create-owned-file.js";
4
+ export type CreateFileOptions = CreateDirectoryOptions;
5
+ export declare function createFileSync(targetPath: string, options?: CreateFileOptions): OwnedFileDescriptorSync;
6
+ /** Internal async writer; Windows preparation uses the nonblocking stage owner. */
7
+ export declare function createFileHandle(targetPath: string, options?: CreateFileOptions, admission?: CreationAdmission): Promise<FileHandle>;
8
+ //# sourceMappingURL=create-file.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-file.d.ts","sourceRoot":"","sources":["../src/create-file.ts"],"names":[],"mappings":"AAEA,OAAgB,EAAE,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE5D,OAAO,EAAkC,KAAK,sBAAsB,EAAE,KAAK,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC5H,OAAO,EAAyB,KAAK,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AA2B7F,MAAM,MAAM,iBAAiB,GAAG,sBAAsB,CAAC;AA8GvD,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,GAAE,iBAAsB,GAAG,uBAAuB,CAE3G;AAED,mFAAmF;AACnF,wBAAsB,gBAAgB,CACpC,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,iBAAsB,EAC/B,SAAS,GAAE,iBAAsB,GAChC,OAAO,CAAC,UAAU,CAAC,CA+BrB"}
@@ -0,0 +1,190 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import fs, {} from "node:fs";
3
+ import fsAsync, {} from "node:fs/promises";
4
+ import path from "node:path";
5
+ import { createDirectoryWithReceiptSync } from "./create-directory.js";
6
+ import { ownFileDescriptorSync } from "./create-owned-file.js";
7
+ import { assertDirectoryIdentitySync } from "./directory-guard.js";
8
+ import { FsSafeError } from "./errors.js";
9
+ import { assertSynchronousCallbackResult } from "./mutation-authority.js";
10
+ import { assertPrivateFileCreationAvailable, inspectCreationDirectorySync, protectCreatedFileSync, resolveCreationPermissions, verifyCreatedFileSync, } from "./creation-permissions.js";
11
+ import { creationCollision, prepareCreationPath, removeCreationDirectory } from "./creation-path.js";
12
+ import { handoffCreatedFileSync } from "./private-producer-handoff-sync.js";
13
+ import { resolveReadOpenFlags } from "./read-open-flags.js";
14
+ import { assertCreationFile, assertPrivateCreationFile, creationPublicationAfterFailure, privateFileSettlementFailure, rethrowPrivateStageCreationFailure, removeRecordedCreationFileSync, } from "./creation-file-state.js";
15
+ import { createPrivateWindowsFileHandle } from "./create-file-async.js";
16
+ import { assertDarwinCreationDirectoryAcl } from "./creation-darwin.js";
17
+ function createPrivateWindowsFile(selected, options, mode) {
18
+ const assertion = options.assertBeforeMutation;
19
+ if (fs.lstatSync(selected.target, { throwIfNoEntry: false })) {
20
+ throw new FsSafeError("already-exists", "creation target already exists");
21
+ }
22
+ const parentIdentity = inspectCreationDirectorySync(selected.parent.dir, false);
23
+ selected.assertParent();
24
+ const stageDirectory = path.join(selected.parent.dir, `.fs-safe-create-${randomUUID()}`);
25
+ let stage;
26
+ try {
27
+ stage = createDirectoryWithReceiptSync(stageDirectory, { private: true, assertBeforeMutation: assertion }, {
28
+ expectedParentIdentity: selected.parent.stat,
29
+ });
30
+ }
31
+ catch (error) {
32
+ rethrowPrivateStageCreationFailure(error, selected.target, stageDirectory);
33
+ }
34
+ const stageIdentity = stage.windowsIdentity;
35
+ const stagePath = path.join(stageDirectory, "file");
36
+ const assertStage = () => {
37
+ selected.assertParent();
38
+ assertDirectoryIdentitySync(stageDirectory, stage.stat);
39
+ };
40
+ let file;
41
+ let identity;
42
+ let windowsIdentity;
43
+ let publication = "not-published";
44
+ try {
45
+ if (inspectCreationDirectorySync(stageDirectory, true) !== stageIdentity) {
46
+ throw new FsSafeError("path-mismatch", "private staging directory changed before file creation");
47
+ }
48
+ assertSynchronousCallbackResult(assertion?.(), "assertBeforeMutation");
49
+ assertStage();
50
+ file = ownFileDescriptorSync(fs.openSync(stagePath, fs.constants.O_RDWR | fs.constants.O_CREAT |
51
+ fs.constants.O_EXCL | resolveReadOpenFlags(), 0o600));
52
+ identity = assertCreationFile(file.fd, stagePath);
53
+ // The inherited ACL must already be restrictive. This freezes that policy;
54
+ // the bridge refuses to repair broad access on an exposed file.
55
+ assertSynchronousCallbackResult(assertion?.(), "assertBeforeMutation");
56
+ assertStage();
57
+ assertCreationFile(file.fd, stagePath, identity);
58
+ windowsIdentity = protectCreatedFileSync(file.fd, stagePath, stageIdentity);
59
+ assertStage();
60
+ file = handoffCreatedFileSync({
61
+ source: file, sourcePath: stagePath, targetPath: selected.target, identity,
62
+ assertSourceParent: assertStage, assertTargetParent: selected.assertParent,
63
+ assertBeforeMutation: assertion,
64
+ verifyDescriptor: (fd, pathname, links) => verifyCreatedFileSync(fd, pathname, windowsIdentity, pathname === stagePath ? stageIdentity : parentIdentity, links),
65
+ onPublished: () => { publication = "published"; },
66
+ });
67
+ // Windows may clear the read-only attribute while retiring the stage name.
68
+ // Finalize it only after the published-name descriptor owns the file.
69
+ fs.fchmodSync(file.fd, mode);
70
+ selected.assertParent();
71
+ identity = assertCreationFile(file.fd, selected.target, identity);
72
+ verifyCreatedFileSync(file.fd, selected.target, windowsIdentity, parentIdentity);
73
+ removeCreationDirectory(stageDirectory, stage.stat, selected.assertParent);
74
+ return file;
75
+ }
76
+ catch (primary) {
77
+ publication = creationPublicationAfterFailure(primary, publication);
78
+ const cleanup = [];
79
+ if (file) {
80
+ try {
81
+ file.close();
82
+ }
83
+ catch (error) {
84
+ cleanup.push(error);
85
+ }
86
+ }
87
+ if (publication !== "indeterminate") {
88
+ if (identity) {
89
+ try {
90
+ removeRecordedCreationFileSync(stagePath, identity, assertStage);
91
+ }
92
+ catch (error) {
93
+ cleanup.push(error);
94
+ }
95
+ }
96
+ try {
97
+ removeCreationDirectory(stageDirectory, stage.stat, selected.assertParent);
98
+ }
99
+ catch (error) {
100
+ cleanup.push(error);
101
+ }
102
+ }
103
+ if (publication === "not-published" && cleanup.length === 0)
104
+ throw creationCollision(primary);
105
+ throw privateFileSettlementFailure({ primary, cleanup, publication, path: selected.target, stageDirectory });
106
+ }
107
+ }
108
+ function createFileCore(targetPath, options, admission) {
109
+ const permissions = resolveCreationPermissions(options, false);
110
+ const assertion = options.assertBeforeMutation;
111
+ const selected = prepareCreationPath(targetPath, admission.expectedParentIdentity);
112
+ if (permissions.private && process.platform === "win32") {
113
+ assertPrivateFileCreationAvailable();
114
+ return createPrivateWindowsFile(selected, { ...permissions, assertBeforeMutation: assertion }, permissions.mode);
115
+ }
116
+ if (permissions.private)
117
+ assertDarwinCreationDirectoryAcl(selected.parent.dir, selected.parent.stat, "file");
118
+ assertSynchronousCallbackResult(assertion?.(), "assertBeforeMutation");
119
+ selected.assertParent();
120
+ if (permissions.private)
121
+ assertDarwinCreationDirectoryAcl(selected.parent.dir, selected.parent.stat, "file");
122
+ let file;
123
+ try {
124
+ file = ownFileDescriptorSync(fs.openSync(selected.target, fs.constants.O_RDWR | fs.constants.O_CREAT |
125
+ fs.constants.O_EXCL | resolveReadOpenFlags(), permissions.mode ?? 0o666));
126
+ }
127
+ catch (error) {
128
+ throw creationCollision(error);
129
+ }
130
+ try {
131
+ const identity = assertCreationFile(file.fd, selected.target);
132
+ selected.assertParent();
133
+ if (permissions.private)
134
+ assertPrivateCreationFile(identity, file.fd);
135
+ return file;
136
+ }
137
+ catch (primary) {
138
+ let failure = primary;
139
+ try {
140
+ file.close();
141
+ }
142
+ catch (closeError) {
143
+ failure = new AggregateError([primary, closeError], "created file admission and close failed");
144
+ }
145
+ throw new FsSafeError("helper-failed", "created file admission failed", {
146
+ cause: failure, details: { publication: { status: "published" }, path: selected.target, cleanup: "preserved" },
147
+ });
148
+ }
149
+ }
150
+ export function createFileSync(targetPath, options = {}) {
151
+ return createFileCore(targetPath, options, {});
152
+ }
153
+ /** Internal async writer; Windows preparation uses the nonblocking stage owner. */
154
+ export async function createFileHandle(targetPath, options = {}, admission = {}) {
155
+ const permissions = resolveCreationPermissions(options, false);
156
+ const assertion = options.assertBeforeMutation;
157
+ if (process.platform !== "win32" || !permissions.private) {
158
+ const selected = prepareCreationPath(targetPath, admission.expectedParentIdentity);
159
+ if (permissions.private)
160
+ assertDarwinCreationDirectoryAcl(selected.parent.dir, selected.parent.stat, "file");
161
+ assertSynchronousCallbackResult(assertion?.(), "assertBeforeMutation");
162
+ selected.assertParent();
163
+ if (permissions.private)
164
+ assertDarwinCreationDirectoryAcl(selected.parent.dir, selected.parent.stat, "file");
165
+ const handle = await fsAsync.open(selected.target, fs.constants.O_RDWR | fs.constants.O_CREAT |
166
+ fs.constants.O_EXCL | resolveReadOpenFlags(), permissions.mode ?? 0o666)
167
+ .catch(error => { throw creationCollision(error); });
168
+ try {
169
+ const identity = assertCreationFile(handle.fd, selected.target);
170
+ selected.assertParent();
171
+ if (permissions.private)
172
+ assertPrivateCreationFile(identity, handle.fd);
173
+ return handle;
174
+ }
175
+ catch (primary) {
176
+ let failure = primary;
177
+ try {
178
+ await handle.close();
179
+ }
180
+ catch (closeError) {
181
+ failure = new AggregateError([primary, closeError], "created file admission and close failed");
182
+ }
183
+ throw new FsSafeError("helper-failed", "created file handle admission failed", {
184
+ cause: failure, details: { publication: { status: "published" }, path: selected.target, cleanup: "preserved" },
185
+ });
186
+ }
187
+ }
188
+ assertPrivateFileCreationAvailable();
189
+ return await createPrivateWindowsFileHandle(prepareCreationPath(targetPath, admission.expectedParentIdentity), { mode: permissions.mode, assertBeforeMutation: assertion });
190
+ }
@@ -0,0 +1,8 @@
1
+ export type OwnedFileDescriptorSync = {
2
+ readonly fd: number;
3
+ close(): void;
4
+ [Symbol.dispose](): void;
5
+ };
6
+ /** Capture the descriptor's creator-owned closer before it can be reused. */
7
+ export declare function ownFileDescriptorSync(fd: number, closeFd?: (fd: number) => void): OwnedFileDescriptorSync;
8
+ //# sourceMappingURL=create-owned-file.d.ts.map