@openclaw/fs-safe 0.16.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 (180) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/README.md +8 -1
  3. package/dist/absolute-path.d.ts.map +1 -1
  4. package/dist/absolute-path.js +2 -8
  5. package/dist/advanced.d.ts +2 -0
  6. package/dist/advanced.d.ts.map +1 -1
  7. package/dist/advanced.js +2 -0
  8. package/dist/archive-deadline.d.ts.map +1 -1
  9. package/dist/archive-deadline.js +22 -23
  10. package/dist/archive-merge.d.ts +1 -0
  11. package/dist/archive-merge.d.ts.map +1 -1
  12. package/dist/archive-merge.js +4 -4
  13. package/dist/archive-native.d.ts +1 -0
  14. package/dist/archive-native.d.ts.map +1 -1
  15. package/dist/archive-native.js +1 -0
  16. package/dist/archive-options.d.ts +2 -0
  17. package/dist/archive-options.d.ts.map +1 -1
  18. package/dist/archive-parser.wasm +0 -0
  19. package/dist/archive-zip-count.d.ts.map +1 -1
  20. package/dist/archive-zip-count.js +21 -1
  21. package/dist/archive-zip-directory.d.ts.map +1 -1
  22. package/dist/archive-zip-directory.js +23 -1
  23. package/dist/archive-zip-loader.d.ts +2 -0
  24. package/dist/archive-zip-loader.d.ts.map +1 -1
  25. package/dist/archive-zip-loader.js +7 -0
  26. package/dist/archive-zip-names.d.ts.map +1 -1
  27. package/dist/archive-zip-names.js +7 -2
  28. package/dist/archive.d.ts.map +1 -1
  29. package/dist/archive.js +9 -3
  30. package/dist/byte-view.d.ts +3 -0
  31. package/dist/byte-view.d.ts.map +1 -0
  32. package/dist/byte-view.js +13 -0
  33. package/dist/creation-darwin.d.ts +0 -1
  34. package/dist/creation-darwin.d.ts.map +1 -1
  35. package/dist/creation-darwin.js +0 -9
  36. package/dist/directory-durability.d.ts +6 -6
  37. package/dist/directory-durability.d.ts.map +1 -1
  38. package/dist/directory-receipt.d.ts +2 -2
  39. package/dist/directory-receipt.d.ts.map +1 -1
  40. package/dist/directory-receipt.js +15 -19
  41. package/dist/file-cleanup.d.ts +1 -0
  42. package/dist/file-cleanup.d.ts.map +1 -1
  43. package/dist/file-cleanup.js +7 -4
  44. package/dist/file-contents.d.ts +6 -0
  45. package/dist/file-contents.d.ts.map +1 -0
  46. package/dist/file-contents.js +40 -0
  47. package/dist/file-hash.d.ts.map +1 -1
  48. package/dist/file-hash.js +16 -4
  49. package/dist/file-lock-sync-root-acquire.d.ts.map +1 -1
  50. package/dist/file-lock-sync-root-acquire.js +3 -0
  51. package/dist/file-lock-sync-root-held.d.ts +1 -2
  52. package/dist/file-lock-sync-root-held.d.ts.map +1 -1
  53. package/dist/file-lock-sync-root-held.js +7 -5
  54. package/dist/file-lock-sync-stale-admission.d.ts.map +1 -1
  55. package/dist/file-lock-sync-stale-admission.js +3 -0
  56. package/dist/file-lock-sync.d.ts.map +1 -1
  57. package/dist/file-lock-sync.js +8 -11
  58. package/dist/file-store.js +3 -3
  59. package/dist/guarded-mkdir.d.ts.map +1 -1
  60. package/dist/guarded-mkdir.js +6 -27
  61. package/dist/install-path.d.ts.map +1 -1
  62. package/dist/install-path.js +2 -5
  63. package/dist/json-durable-queue-ownership.d.ts.map +1 -1
  64. package/dist/json-durable-queue-ownership.js +2 -6
  65. package/dist/json-durable-queue-paths.d.ts.map +1 -1
  66. package/dist/json-durable-queue-paths.js +2 -24
  67. package/dist/json-durable-queue.d.ts.map +1 -1
  68. package/dist/json-durable-queue.js +10 -9
  69. package/dist/json.d.ts.map +1 -1
  70. package/dist/json.js +32 -75
  71. package/dist/local-roots.d.ts.map +1 -1
  72. package/dist/local-roots.js +19 -21
  73. package/dist/move-path-cleanup.d.ts +5 -19
  74. package/dist/move-path-cleanup.d.ts.map +1 -1
  75. package/dist/move-path-cleanup.js +57 -21
  76. package/dist/move-path.d.ts.map +1 -1
  77. package/dist/move-path.js +62 -39
  78. package/dist/native-staged-file.d.ts +2 -1
  79. package/dist/native-staged-file.d.ts.map +1 -1
  80. package/dist/native-staged-file.js +8 -5
  81. package/dist/native.js +2 -2
  82. package/dist/opened-realpath.d.ts.map +1 -1
  83. package/dist/opened-realpath.js +11 -2
  84. package/dist/path.d.ts.map +1 -1
  85. package/dist/path.js +2 -1
  86. package/dist/permissions.d.ts.map +1 -1
  87. package/dist/permissions.js +3 -17
  88. package/dist/pinned-write-input.d.ts.map +1 -1
  89. package/dist/pinned-write-input.js +11 -1
  90. package/dist/pinned-write-mode.d.ts +3 -3
  91. package/dist/pinned-write-mode.d.ts.map +1 -1
  92. package/dist/pinned-write-mode.js +15 -8
  93. package/dist/pinned-write-staged.d.ts.map +1 -1
  94. package/dist/pinned-write-staged.js +10 -11
  95. package/dist/pinned-write.d.ts.map +1 -1
  96. package/dist/pinned-write.js +17 -13
  97. package/dist/publish-file.d.ts +2 -2
  98. package/dist/publish-file.d.ts.map +1 -1
  99. package/dist/publish-file.js +56 -96
  100. package/dist/regular-file.d.ts.map +1 -1
  101. package/dist/regular-file.js +35 -44
  102. package/dist/replace-file-copy-fallback.d.ts.map +1 -1
  103. package/dist/replace-file-copy-fallback.js +28 -26
  104. package/dist/replace-file-copy-source.d.ts.map +1 -1
  105. package/dist/replace-file-copy-source.js +13 -22
  106. package/dist/replace-file-descriptor.d.ts.map +1 -1
  107. package/dist/replace-file-descriptor.js +10 -16
  108. package/dist/replace-file-temp-owner.js +6 -6
  109. package/dist/replace-file.d.ts.map +1 -1
  110. package/dist/replace-file.js +9 -13
  111. package/dist/root-directory-list.d.ts.map +1 -1
  112. package/dist/root-directory-list.js +20 -3
  113. package/dist/root-file-final-admission.d.ts +1 -1
  114. package/dist/root-file-final-admission.d.ts.map +1 -1
  115. package/dist/root-file-final-admission.js +5 -2
  116. package/dist/root-file.d.ts.map +1 -1
  117. package/dist/root-file.js +3 -2
  118. package/dist/root-impl.d.ts.map +1 -1
  119. package/dist/root-impl.js +50 -20
  120. package/dist/root-move-noreplace.d.ts +2 -0
  121. package/dist/root-move-noreplace.d.ts.map +1 -1
  122. package/dist/root-move-noreplace.js +2 -2
  123. package/dist/root-read-admission.d.ts.map +1 -1
  124. package/dist/root-read-admission.js +7 -2
  125. package/dist/root-remove.d.ts.map +1 -1
  126. package/dist/root-remove.js +15 -1
  127. package/dist/sidecar-lock-acquire.d.ts.map +1 -1
  128. package/dist/sidecar-lock-acquire.js +4 -6
  129. package/dist/sidecar-lock-handle.d.ts +3 -0
  130. package/dist/sidecar-lock-handle.d.ts.map +1 -1
  131. package/dist/sidecar-lock-handle.js +6 -0
  132. package/dist/sidecar-lock-reclaim.d.ts +1 -1
  133. package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
  134. package/dist/sidecar-lock-reclaim.js +11 -8
  135. package/dist/sidecar-lock.d.ts.map +1 -1
  136. package/dist/sidecar-lock.js +3 -5
  137. package/dist/staged-directory.d.ts +2 -2
  138. package/dist/staged-directory.d.ts.map +1 -1
  139. package/dist/strict-file-identity.d.ts +1 -1
  140. package/dist/strict-file-identity.d.ts.map +1 -1
  141. package/dist/strict-file-identity.js +9 -9
  142. package/dist/symlink-parents.d.ts.map +1 -1
  143. package/dist/symlink-parents.js +2 -27
  144. package/dist/temp-workspace-owner.js +4 -4
  145. package/dist/unicode-path.d.ts.map +1 -1
  146. package/dist/unicode-path.js +3 -0
  147. package/dist/walk.d.ts.map +1 -1
  148. package/dist/walk.js +4 -2
  149. package/dist/write-file-handle.d.ts +7 -0
  150. package/dist/write-file-handle.d.ts.map +1 -1
  151. package/dist/write-file-handle.js +23 -0
  152. package/dist/write-open-flags.d.ts.map +1 -1
  153. package/dist/write-open-flags.js +1 -8
  154. package/dist/write-queue.d.ts.map +1 -1
  155. package/dist/write-queue.js +1 -4
  156. package/docs/advanced.md +68 -1
  157. package/docs/archive.md +41 -2
  158. package/docs/atomic.md +29 -5
  159. package/docs/contributing.md +4 -0
  160. package/docs/creation.md +8 -4
  161. package/docs/durability.md +35 -0
  162. package/docs/file-contents.md +68 -0
  163. package/docs/json.md +5 -4
  164. package/docs/local-roots.md +2 -0
  165. package/docs/mutation-policy-proof.md +5 -3
  166. package/docs/native.md +9 -8
  167. package/docs/path.md +4 -4
  168. package/docs/public-api.md +5 -0
  169. package/docs/quickstart.md +1 -1
  170. package/docs/reading.md +2 -2
  171. package/docs/regular-file.md +3 -0
  172. package/docs/root.md +9 -0
  173. package/docs/sidecar-lock.md +9 -1
  174. package/docs/staged-file.md +4 -3
  175. package/docs/store.md +3 -1
  176. package/docs/temp.md +4 -1
  177. package/docs/types.md +18 -2
  178. package/docs/walk.md +7 -0
  179. package/docs/writing.md +9 -2
  180. package/package.json +8 -8
package/dist/move-path.js CHANGED
@@ -2,10 +2,10 @@ import { randomUUID } from "node:crypto";
2
2
  import fsSync, { constants as fsConstants } from "node:fs";
3
3
  import fs from "node:fs/promises";
4
4
  import path from "node:path";
5
- import { createAsyncDirectoryGuard, inspectDirectoryIdentitySync } from "./directory-guard.js";
5
+ import { assertSyncDirectoryGuard, createAsyncDirectoryGuard } from "./directory-guard.js";
6
6
  import { FsSafeError } from "./errors.js";
7
7
  import { guardedRename } from "./guarded-mutation.js";
8
- import { assertSourceStillMatches, cleanupCopiedEntry, createCleanupCopiedEntryState, entryIdentity, sameIdentity, sourceChangedError, } from "./move-path-cleanup.js";
8
+ import { assertSourceStillMatches, cleanupCopiedEntry, createCleanupCopiedEntryState, entryIdentity, inspectSourceDirectory, inspectSourceEntry, sameIdentity, sourceChangedError, } from "./move-path-cleanup.js";
9
9
  import { resolveReadOpenFlags } from "./read-open-flags.js";
10
10
  import { realpathSync } from "./realpath.js";
11
11
  import { cleanupPinnedFilePath } from "./file-cleanup.js";
@@ -57,7 +57,7 @@ function isSameOrDescendant(parentPath, candidatePath) {
57
57
  (!path.isAbsolute(relative) && !relative.startsWith(`..${path.sep}`) && relative !== ".."));
58
58
  }
59
59
  async function assertCopyDestinationOutsideSource(sourcePath, targetPath, expectedIdentity) {
60
- const sourceStat = fsSync.lstatSync(sourcePath);
60
+ const sourceStat = inspectSourceEntry(sourcePath, () => fsSync.lstatSync(sourcePath, { bigint: true }));
61
61
  const sourceIdentity = entryIdentity(sourceStat);
62
62
  if (expectedIdentity && !sameIdentity(expectedIdentity, sourceIdentity)) {
63
63
  throw sourceChangedError(sourcePath);
@@ -80,7 +80,7 @@ async function assertCopyDestinationOutsideSource(sourcePath, targetPath, expect
80
80
  return sourceIdentity;
81
81
  }
82
82
  function modeBits(mode) {
83
- return mode & 0o777;
83
+ return Number(mode & 511n);
84
84
  }
85
85
  async function chmodDirectoryPinned(directoryPath, mode) {
86
86
  if (process.platform === "win32") {
@@ -120,21 +120,25 @@ async function copyRegularFilePinned(params) {
120
120
  throw error;
121
121
  }
122
122
  try {
123
- const openedStat = fsSync.fstatSync(sourceHandle.fd);
123
+ const inspectOpenedSource = () => inspectSourceEntry(params.from, () => {
124
+ const stat = fsSync.fstatSync(sourceHandle.fd, { bigint: true });
125
+ if (params.rejectHardlinks && stat.nlink > 1n)
126
+ throw hardlinkedSourceError(params.from);
127
+ if (!stat.isFile())
128
+ throw sourceChangedError(params.from);
129
+ return stat;
130
+ });
131
+ const openedStat = inspectOpenedSource();
124
132
  openedIdentity = entryIdentity(openedStat);
125
- if (params.rejectHardlinks && openedStat.nlink > 1) {
126
- throw hardlinkedSourceError(params.from);
127
- }
128
133
  const openAdvancedWindowsCtime = process.platform === "win32" &&
129
134
  params.identity.dev === openedIdentity.dev &&
130
135
  params.identity.ino === openedIdentity.ino &&
131
136
  params.identity.mode === openedIdentity.mode &&
132
137
  params.identity.nlink === openedIdentity.nlink &&
133
138
  params.identity.size === openedIdentity.size &&
134
- params.identity.mtimeMs === openedIdentity.mtimeMs &&
135
- openedIdentity.ctimeMs >= params.identity.ctimeMs;
136
- if (!openedStat.isFile() ||
137
- (!sameIdentity(params.identity, openedIdentity) && !openAdvancedWindowsCtime)) {
139
+ params.identity.mtimeNs === openedIdentity.mtimeNs &&
140
+ openedIdentity.ctimeNs >= params.identity.ctimeNs;
141
+ if (!sameIdentity(params.identity, openedIdentity) && !openAdvancedWindowsCtime) {
138
142
  throw sourceChangedError(params.from);
139
143
  }
140
144
  await assertSourceStillMatches(params.from, openedIdentity);
@@ -154,11 +158,7 @@ async function copyRegularFilePinned(params) {
154
158
  }
155
159
  // Re-check the opened source before the staged tree can be committed. If
156
160
  // it changed while we copied, the caller should retry the move.
157
- const finalSourceStat = fsSync.fstatSync(sourceHandle.fd);
158
- if (params.rejectHardlinks && finalSourceStat.nlink > 1) {
159
- throw hardlinkedSourceError(params.from);
160
- }
161
- if (!sameIdentity(openedIdentity, entryIdentity(finalSourceStat))) {
161
+ if (!sameIdentity(openedIdentity, inspectOpenedSource())) {
162
162
  throw sourceChangedError(params.from);
163
163
  }
164
164
  await destinationHandle.chmod(modeBits(params.mode));
@@ -178,21 +178,8 @@ async function copyRegularFilePinned(params) {
178
178
  }
179
179
  return openedIdentity;
180
180
  }
181
- function inspectSourceDirectory(sourcePath, expected) {
182
- try {
183
- return inspectDirectoryIdentitySync(sourcePath, expected);
184
- }
185
- catch (error) {
186
- const code = error?.code;
187
- if (code === "ENOENT" || code === "ENOTDIR" ||
188
- (error instanceof FsSafeError && (error.code === "path-mismatch" || error.code === "not-file"))) {
189
- throw sourceChangedError(sourcePath);
190
- }
191
- throw error;
192
- }
193
- }
194
181
  async function copyEntryWithManifest(from, to, options, expectedIdentity) {
195
- const sourceStat = fsSync.lstatSync(from);
182
+ const sourceStat = inspectSourceEntry(from, () => fsSync.lstatSync(from, { bigint: true }));
196
183
  const identity = entryIdentity(sourceStat);
197
184
  if (expectedIdentity && !sameIdentity(expectedIdentity, identity)) {
198
185
  throw sourceChangedError(from);
@@ -208,8 +195,7 @@ async function copyEntryWithManifest(from, to, options, expectedIdentity) {
208
195
  return { ...identity, kind: "leaf" };
209
196
  }
210
197
  if (sourceStat.isDirectory()) {
211
- const directoryStat = inspectSourceDirectory(from);
212
- const directoryIdentity = Object.freeze({ dev: directoryStat.dev, ino: directoryStat.ino });
198
+ const directoryIdentity = Object.freeze({ dev: sourceStat.dev, ino: sourceStat.ino });
213
199
  await fs.mkdir(to, { mode: modeBits(sourceStat.mode) || 0o755 });
214
200
  options.onCreated?.(fsSync.lstatSync(to, { bigint: true }));
215
201
  const children = [];
@@ -226,6 +212,7 @@ async function copyEntryWithManifest(from, to, options, expectedIdentity) {
226
212
  name: child,
227
213
  manifest: await copyEntryWithManifest(path.join(from, child), path.join(to, child), {
228
214
  sourceHardlinks: options.sourceHardlinks, budget: options.budget,
215
+ aliases: options.aliases,
229
216
  }),
230
217
  });
231
218
  }
@@ -241,9 +228,13 @@ async function copyEntryWithManifest(from, to, options, expectedIdentity) {
241
228
  if (!sourceStat.isFile()) {
242
229
  throw new Error(`Refusing to move non-file path with copy fallback: ${from}`);
243
230
  }
244
- if (options.sourceHardlinks === "reject" && sourceStat.nlink > 1) {
231
+ if (options.sourceHardlinks === "reject" && sourceStat.nlink > 1n) {
245
232
  throw hardlinkedSourceError(from);
246
233
  }
234
+ const aliasKey = options.aliases && identity.nlink > 1n ? `${identity.dev}:${identity.ino}` : undefined;
235
+ const alias = aliasKey ? options.aliases.get(aliasKey) : undefined;
236
+ if (alias && !sameIdentity(alias, identity))
237
+ throw sourceChangedError(from);
247
238
  const copiedIdentity = await copyRegularFilePinned({
248
239
  from,
249
240
  identity,
@@ -252,7 +243,14 @@ async function copyEntryWithManifest(from, to, options, expectedIdentity) {
252
243
  to,
253
244
  onCreated: options.onCreated,
254
245
  });
255
- return { ...copiedIdentity, kind: "leaf" };
246
+ // A permitted Windows open advances every name's ctime. Share its receipt
247
+ // only after the preceding fingerprint and this complete copy were verified.
248
+ if (alias)
249
+ return Object.assign(alias, copiedIdentity);
250
+ const manifest = { ...copiedIdentity, kind: "leaf" };
251
+ if (aliasKey)
252
+ options.aliases.set(aliasKey, manifest);
253
+ return manifest;
256
254
  }
257
255
  function assertSynchronousResult(returned, name) {
258
256
  if (returned !== undefined) {
@@ -275,13 +273,13 @@ export async function movePathWithCopyFallback(options) {
275
273
  const callerPublished = options.onDestinationPublished;
276
274
  let assertionRejected = false;
277
275
  let destinationPublished = false;
278
- const assertBeforeMutation = () => {
279
- assertSynchronousResult(callerMutationAssert?.(), "assertBeforeMutation");
276
+ const assertBeforeMutation = callerMutationAssert == null ? undefined : () => {
277
+ assertSynchronousResult(callerMutationAssert(), "assertBeforeMutation");
280
278
  };
281
279
  const assertBeforeRename = () => {
282
280
  try {
283
281
  assertSynchronousResult(callerRenameAssert?.(), "assertBeforeRename");
284
- assertBeforeMutation();
282
+ assertBeforeMutation?.();
285
283
  }
286
284
  catch (error) {
287
285
  assertionRejected = true;
@@ -332,6 +330,29 @@ export async function movePathWithCopyFallback(options) {
332
330
  // never become the published target; the copy loop fences nlink again.
333
331
  }
334
332
  const sourceIdentity = await assertCopyDestinationOutsideSource(sourcePath, targetPath);
333
+ const sourceParentPath = path.dirname(sourcePath);
334
+ const sourceParent = assertBeforeMutation || callerRenameAssert || callerPublished
335
+ ? await createAsyncDirectoryGuard(realpathSync.native(sourceParentPath), { bigint: true })
336
+ : undefined;
337
+ const assertSourceParent = sourceParent ? () => {
338
+ try {
339
+ if (realpathSync.native(sourceParentPath) !== sourceParent.realPath)
340
+ throw sourceChangedError(sourcePath);
341
+ assertSyncDirectoryGuard(sourceParent);
342
+ }
343
+ catch (error) {
344
+ const code = error?.code;
345
+ if (code === "ENOENT" || code === "ENOTDIR" || code === "ELOOP" ||
346
+ (error instanceof FsSafeError && (error.code === "path-mismatch" || error.code === "not-file"))) {
347
+ throw sourceChangedError(sourcePath);
348
+ }
349
+ throw error;
350
+ }
351
+ } : undefined;
352
+ const assertBeforeCleanup = assertSourceParent ? () => {
353
+ assertBeforeMutation?.();
354
+ assertSourceParent();
355
+ } : undefined;
335
356
  const targetDir = path.dirname(targetPath);
336
357
  const staged = path.join(targetDir, `.fs-safe-move-${process.pid}-${randomUUID()}.tmp`);
337
358
  const stage = await createMoveStageOwner(staged);
@@ -339,6 +360,7 @@ export async function movePathWithCopyFallback(options) {
339
360
  const manifest = await copyEntryWithManifest(sourcePath, staged, {
340
361
  sourceHardlinks: rejectHardlinks ? "reject" : "allow",
341
362
  onCreated: stage.record,
363
+ ...(!rejectHardlinks && process.platform === "win32" ? { aliases: new Map() } : {}),
342
364
  ...(rejectHardlinks ? { budget: { discovered: 1 } } : {}),
343
365
  }, sourceIdentity);
344
366
  const cleanupState = createCleanupCopiedEntryState(sourcePath, manifest);
@@ -355,7 +377,8 @@ export async function movePathWithCopyFallback(options) {
355
377
  onRenamed();
356
378
  },
357
379
  });
358
- const cleanupResult = await cleanupCopiedEntry(sourcePath, manifest, cleanupState, assertBeforeMutation);
380
+ assertSourceParent?.();
381
+ const cleanupResult = await cleanupCopiedEntry(sourcePath, manifest, cleanupState, assertBeforeCleanup);
359
382
  if (cleanupResult === "stale") {
360
383
  throw sourceChangedError(sourcePath);
361
384
  }
@@ -1,4 +1,5 @@
1
1
  import type { DirectoryReceipt } from "./directory-durability.js";
2
+ import { type BigIntStats, type Stats } from "node:fs";
2
3
  import type { AnyAsyncDirectoryGuard } from "./directory-guard.js";
3
4
  import type { FileIdentityStat } from "./file-identity.js";
4
5
  import { type NativeBinding } from "./native-binding.js";
@@ -24,7 +25,7 @@ export declare const createNativeStage: (...args: Parameters<typeof NativeStaged
24
25
  export declare const writeNativeStage: typeof NativeStagedFile.write;
25
26
  export type { PublishedFileReceipt, StagedFile, StagedFileCleanupReceipt, StagedFileFailureDetails, StagedFilePublication, StagedFileReceipt, } from "./staged-file-types.js";
26
27
  export declare function stageFileInDirectory(options: {
27
- directory: string | DirectoryReceipt;
28
+ directory: string | DirectoryReceipt<Stats | BigIntStats>;
28
29
  content: string | Uint8Array;
29
30
  /** Published mode; the unpublished stage stays at 0600. Defaults to 0600. */
30
31
  mode?: number;
@@ -1 +1 @@
1
- {"version":3,"file":"native-staged-file.d.ts","sourceRoot":"","sources":["../src/native-staged-file.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"native-staged-file.d.ts","sourceRoot":"","sources":["../src/native-staged-file.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAMlE,OAAW,EAAE,KAAK,WAAW,EAAE,KAAK,KAAK,EAAE,MAAM,SAAS,CAAC;AAE3D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAGnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,EAAwB,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAG/E,OAAO,KAAK,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAG7E,OAAO,KAAK,EACV,oBAAoB,EACpB,UAAU,EACV,wBAAwB,EAGxB,iBAAiB,EAClB,MAAM,wBAAwB,CAAC;AAIhC,MAAM,MAAM,oBAAoB,GAAG,aAAa,GAAG,QAAQ,CAAC,IAAI,CAC9D,aAAa,EACb,kBAAkB,GAAG,mBAAmB,GAAG,kBAAkB,CAC9D,CAAC,CAAC;AAEH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,OAAO,IAAI,oBAAoB,CAWnG;AAeD,KAAK,sBAAsB,GAAG,kBAAkB,GAAG,WAAW,CAAC;AAE/D,cAAM,gBAAiB,YAAW,UAAU;;IAkB1C,YACE,OAAO,EAAE,oBAAoB,EAC7B,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,EACnC,SAAS,EAAE,iBAAiB,CAAC,WAAW,CAAC,EACzC,aAAa,EAAE,OAAO,EACtB,aAAa,EAAE,MAAM,EACrB,IAAI,EAAE,OAAO,EACb,oBAAoB,CAAC,EAAE,MAAM,IAAI,EACjC,IAAI,SAAiC,EACrC,cAAc,UAAQ,EACtB,gBAAgB,CAAC,EAAE,sBAAsB,EAgB1C;IAID,OAAa,MAAM,CACjB,OAAO,EAAE,oBAAoB,EAC7B,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,EACnC,SAAS,EAAE,iBAAiB,CAAC,WAAW,CAAC,EACzC,KAAK,EAAE,gBAAgB,EACvB,IAAI,EAAE,MAAM,EACZ,QAAQ,CAAC,EAAE,MAAM,EAEjB,aAAa,UAAO,EACpB,IAAI,UAAO,EACX,oBAAoB,CAAC,EAAE,MAAM,IAAI,EACjC,iBAAiB,CAAC,EAAE,MAAM,EAC1B,cAAc,UAAQ,EACtB,gBAAgB,CAAC,EAAE,sBAAsB,GACxC,OAAO,CAAC,gBAAgB,CAAC,CAc3B;IAED,OAAa,KAAK,CAChB,OAAO,EAAE,oBAAoB,EAC7B,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,EACnC,SAAS,EAAE,iBAAiB,CAAC,WAAW,CAAC,EACzC,MAAM,EAAE,iBAAiB,EACzB,WAAW,EAAE,sBAAsB,GAClC,OAAO,CAAC,gBAAgB,CAAC,CA8B3B;IAED,IAAI,OAAO,IAAI,iBAAiB,CAK/B;IAuHK,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAEnC;IAEK,OAAO,CACX,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE;QAAE,SAAS,EAAE,OAAO,CAAA;KAAE,EAC/B,WAAW,CAAC,EAAE,iBAAiB,CAAC,aAAa,CAAC,GAC7C,OAAO,CAAC,oBAAoB,CAAC,CAqC/B;IAuEK,OAAO,IAAI,OAAO,CAAC,wBAAwB,CAAC,CAMjD;IAEK,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAO3C;CACF;AAED,eAAO,MAAM,iBAAiB,EAAE,CAAC,GAAG,IAAI,EAAE,UAAU,CAAC,OAAO,gBAAgB,CAAC,MAAM,CAAC,KAAK,OAAO,CAAC,UAAU,CAClF,CAAC;AAC1B,eAAO,MAAM,gBAAgB,+BAAyB,CAAC;AAEvD,YAAY,EACV,oBAAoB,EAAE,UAAU,EAAE,wBAAwB,EAAE,wBAAwB,EACpF,qBAAqB,EAAE,iBAAiB,GACzC,MAAM,wBAAwB,CAAC;AAEhC,wBAAsB,oBAAoB,CAAC,OAAO,EAAE;IAClD,SAAS,EAAE,MAAM,GAAG,gBAAgB,CAAC,KAAK,GAAG,WAAW,CAAC,CAAC;IAC1D,OAAO,EAAE,MAAM,GAAG,UAAU,CAAC;IAC7B,6EAA6E;IAC7E,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,GAAG,OAAO,CAAC,UAAU,CAAC,CAatB"}
@@ -50,12 +50,12 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
50
50
  var e = new Error(message);
51
51
  return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
52
52
  });
53
- import { assertDarwinCreationAcl, privateFileMutationAssertion } from "./creation-darwin.js";
53
+ import { assertDarwinCreationAcl } from "./creation-darwin.js";
54
54
  import { assertPrivateCreationFile } from "./creation-file-state.js";
55
55
  import { requireNativeBinding } from "./native.js";
56
56
  import { syncFileBestEffortSync } from "./file-sync.js";
57
57
  import { randomUUID } from "node:crypto";
58
- import fs from "node:fs";
58
+ import fs, {} from "node:fs";
59
59
  import path from "node:path";
60
60
  import { FsSafeError } from "./errors.js";
61
61
  import { assertSynchronousCallbackResult, MutationAuthorityError } from "./mutation-authority.js";
@@ -262,9 +262,12 @@ class NativeStagedFile {
262
262
  const fd = state.fileFd;
263
263
  if (input.kind === "file")
264
264
  assertNativeCopyCompleted(input, copied);
265
- const assertBeforeChmod = this.#private
266
- ? privateFileMutationAssertion(fd, this.#assertBeforeMutation) : this.#assertBeforeMutation;
267
- assertBeforeChmod?.();
265
+ const assertBeforeChmod = this.#assertBeforeMutation;
266
+ const beforeChmodResult = assertBeforeChmod?.();
267
+ if (this.#private) {
268
+ assertSynchronousCallbackResult(beforeChmodResult, "assertBeforeMutation");
269
+ assertPrivateCreationFile(fs.fstatSync(fd, { bigint: true }), fd);
270
+ }
268
271
  fs.fchmodSync(fd, 0o600);
269
272
  // Some filesystems report successful chmod without enforcing its mode.
270
273
  if (this.#verifyMode)
package/dist/native.js CHANGED
@@ -22,7 +22,7 @@ function isMuslFromReport() {
22
22
  return true;
23
23
  }
24
24
  catch {
25
- // Continue with filesystem and ELF inspection.
25
+ // Continue with ELF and filesystem inspection.
26
26
  }
27
27
  return undefined;
28
28
  }
@@ -114,7 +114,7 @@ function isMuslFromElfInterpreter() {
114
114
  function isMusl() {
115
115
  if (process.platform !== "linux")
116
116
  return false;
117
- for (const detector of [isMuslFromReport, isMuslFromFilesystem, isMuslFromElfInterpreter]) {
117
+ for (const detector of [isMuslFromReport, isMuslFromElfInterpreter, isMuslFromFilesystem]) {
118
118
  const result = detector();
119
119
  if (result !== undefined)
120
120
  return result;
@@ -1 +1 @@
1
- {"version":3,"file":"opened-realpath.d.ts","sourceRoot":"","sources":["../src/opened-realpath.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAClD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAGnD,OAAO,EAAoB,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAI7E,wBAAsB,kCAAkC,CACtD,MAAM,EAAE,UAAU,EAClB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,CAAC,CAGjB;AAED,wBAAgB,8BAA8B,CAC5C,EAAE,EAAE,MAAM,EACV,UAAU,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,EACxC,MAAM,EAAE,MAAM,GACb,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,WAAW,CAAA;CAAE,CAAC,CAAC;AACpD,wBAAgB,8BAA8B,CAC5C,EAAE,EAAE,MAAM,EACV,UAAU,EAAE,gBAAgB,EAC5B,MAAM,EAAE,MAAM,GACb,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,KAAK,GAAG,WAAW,CAAA;CAAE,CAAC,CAAC"}
1
+ {"version":3,"file":"opened-realpath.d.ts","sourceRoot":"","sources":["../src/opened-realpath.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAClD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAGnD,OAAO,EAAoB,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAK7E,wBAAsB,kCAAkC,CACtD,MAAM,EAAE,UAAU,EAClB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,CAAC,CAWjB;AAED,wBAAgB,8BAA8B,CAC5C,EAAE,EAAE,MAAM,EACV,UAAU,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,EACxC,MAAM,EAAE,MAAM,GACb,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,WAAW,CAAA;CAAE,CAAC,CAAC;AACpD,wBAAgB,8BAA8B,CAC5C,EAAE,EAAE,MAAM,EACV,UAAU,EAAE,gBAAgB,EAC5B,MAAM,EAAE,MAAM,GACb,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,KAAK,GAAG,WAAW,CAAA;CAAE,CAAC,CAAC"}
@@ -5,9 +5,18 @@ import { openedPathResolutionError } from "./opened-file-failure.js";
5
5
  import { sameFileIdentity } from "./file-identity.js";
6
6
  import { isNotFoundPathError } from "./path.js";
7
7
  import { realpathSync } from "./realpath.js";
8
+ import { inspectFileIdentitySync } from "./strict-file-identity.js";
8
9
  export async function resolveOpenedFileRealPathForHandle(handle, ioPath) {
9
- const handleStat = fsSync.fstatSync(handle.fd);
10
- return (await resolveOpenedFileRealPathForFd(handle.fd, handleStat, ioPath)).realPath;
10
+ const fd = handle.fd;
11
+ const identity = inspectFileIdentitySync(() => fsSync.fstatSync(fd, { bigint: true }));
12
+ const resolved = await resolveOpenedFileRealPathForFd(fd, identity, ioPath);
13
+ let first = resolved.stat;
14
+ inspectFileIdentitySync(() => {
15
+ const current = first ?? fsSync.statSync(resolved.realPath, { bigint: true });
16
+ first = undefined;
17
+ return current;
18
+ }, identity);
19
+ return resolved.realPath;
11
20
  }
12
21
  export async function resolveOpenedFileRealPathForFd(fd, handleStat, ioPath) {
13
22
  const statOptions = typeof handleStat.dev === "bigint" || typeof handleStat.ino === "bigint"
@@ -1 +1 @@
1
- {"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../src/path.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AAczB,OAAO,EACL,4BAA4B,EAC5B,sBAAsB,EACtB,yBAAyB,EACzB,KAAK,yBAAyB,EAC9B,KAAK,2BAA2B,EAChC,KAAK,0BAA0B,GAChC,MAAM,kBAAkB,CAAC;AAM1B,wBAAgB,iCAAiC,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAWvE;AAaD,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,cAAc,CAI1E;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAEtE;AAED,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,SAA6B,GAAG,IAAI,CAIjG;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAE3D;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAE1D;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CA0ClE;AAED,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAElE;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAG1D;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAExE;AAED,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,GAAG,IAAI,CAa/F;AAED,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,EACrB,IAAI,CAAC,EAAE;IAAE,eAAe,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,GAChE,OAAO,CAsBT;AAED,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,EAAE,CAAC,KAAK,GAAG,IAAI,CAMhE;AAED,wBAAgB,qBAAqB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,EAAE,CA+BpE;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAWrF"}
1
+ {"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../src/path.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AAczB,OAAO,EACL,4BAA4B,EAC5B,sBAAsB,EACtB,yBAAyB,EACzB,KAAK,yBAAyB,EAC9B,KAAK,2BAA2B,EAChC,KAAK,0BAA0B,GAChC,MAAM,kBAAkB,CAAC;AAM1B,wBAAgB,iCAAiC,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAWvE;AAaD,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,cAAc,CAI1E;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAEtE;AAED,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,SAA6B,GAAG,IAAI,CAIjG;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAE3D;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAE1D;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CA0ClE;AAED,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAElE;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAG1D;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAExE;AAED,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,GAAG,IAAI,CAa/F;AAED,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,EACrB,IAAI,CAAC,EAAE;IAAE,eAAe,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,GAChE,OAAO,CAsBT;AAED,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,EAAE,CAAC,KAAK,GAAG,IAAI,CAMhE;AAED,wBAAgB,qBAAqB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,EAAE,CA+BpE;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CASrF"}
package/dist/path.js CHANGED
@@ -160,7 +160,8 @@ export function splitSafeRelativePath(relativePath) {
160
160
  export function resolveSafeRelativePath(rootDir, relativePath) {
161
161
  assertNoWindowsPathAlias(rootDir, "filesystem", "root dir uses a Windows filesystem namespace alias");
162
162
  const root = resolvePathPreservingWindowsRoot(rootDir);
163
- const target = resolvePathFromBasePreservingWindowsRoot(root, ...splitSafeRelativePath(relativePath));
163
+ splitSafeRelativePath(relativePath);
164
+ const target = resolvePathFromBasePreservingWindowsRoot(root, relativePath);
164
165
  if (!isPathInside(root, target)) {
165
166
  throw new FsSafeError("outside-workspace", "relative path escapes root");
166
167
  }
@@ -1 +1 @@
1
- {"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../src/permissions.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AACrE,OAAO,EAGL,KAAK,cAAc,EACpB,MAAM,0BAA0B,CAAC;AAKlC,YAAY,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AACrE,OAAO,EACL,wBAAwB,EACxB,wBAAwB,EACxB,uBAAuB,EACvB,iBAAiB,EACjB,iBAAiB,EACjB,2BAA2B,EAC3B,mBAAmB,GACpB,MAAM,0BAA0B,CAAC;AAClC,YAAY,EACV,yBAAyB,EACzB,cAAc,EACd,eAAe,EACf,iBAAiB,EACjB,uBAAuB,GACxB,MAAM,0BAA0B,CAAC;AAElC,MAAM,MAAM,eAAe,GAAG;IAC5B,EAAE,EAAE,OAAO,CAAC;IACZ,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,OAAO,GAAG,aAAa,GAAG,SAAS,CAAC;IAC5C,aAAa,EAAE,OAAO,CAAC;IACvB,aAAa,EAAE,OAAO,CAAC;IACvB,aAAa,EAAE,OAAO,CAAC;IACvB,aAAa,EAAE,OAAO,CAAC;IACvB,iEAAiE;IACjE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,qFAAqF;IACrF,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,+EAA+E;IAC/E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,wBAAwB,CAAC;IACvC,sFAAsF;IACtF,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;IAC3B,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,IAAI,CAAC,EAAE,cAAc,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,EAAE,EAAE,OAAO,CAAC;IACZ,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AA8BF,wBAAsB,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAK1E;AAED,wBAAsB,sBAAsB,CAC1C,UAAU,EAAE,MAAM,EAClB,IAAI,CAAC,EAAE,sBAAsB,GAC5B,OAAO,CAAC,eAAe,CAAC,CA0E1B;AAED,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe,GAAG,MAAM,CAKzF;AAED,wBAAgB,2BAA2B,CAAC,MAAM,EAAE;IAClD,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,eAAe,CAAC;IACvB,KAAK,EAAE,OAAO,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;CACzB,GAAG,MAAM,CAYT;AASD,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAE3D;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAEvD;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAE5D;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAE5D;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAE5D;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAE5D;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEpD"}
1
+ {"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../src/permissions.ts"],"names":[],"mappings":"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"}
@@ -39,23 +39,9 @@ export async function inspectPathPermissions(targetPath, opts) {
39
39
  const admissionPlatform = process.platform === "win32"
40
40
  ? process.platform
41
41
  : (opts?.platform ?? process.platform);
42
- if (hasWindowsPathAlias(targetPath, "filesystem", admissionPlatform)) {
43
- const st = failedSafeStat("Path uses a Windows filesystem namespace alias");
44
- return {
45
- ok: false,
46
- isSymlink: false,
47
- isDir: false,
48
- mode: null,
49
- bits: null,
50
- source: "unknown",
51
- worldWritable: false,
52
- groupWritable: false,
53
- worldReadable: false,
54
- groupReadable: false,
55
- error: st.error,
56
- };
57
- }
58
- const st = await safeStatAdmitted(targetPath);
42
+ const st = hasWindowsPathAlias(targetPath, "filesystem", admissionPlatform)
43
+ ? failedSafeStat("Path uses a Windows filesystem namespace alias")
44
+ : await safeStatAdmitted(targetPath);
59
45
  if (!st.ok) {
60
46
  return {
61
47
  ok: false,
@@ -1 +1 @@
1
- {"version":3,"file":"pinned-write-input.d.ts","sourceRoot":"","sources":["../src/pinned-write-input.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAGnD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAGhE,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,UAAU,GAAG,MAAM,EAC3B,KAAK,EAAE,gBAAgB,EACvB,QAAQ,CAAC,EAAE,MAAM,EACjB,oBAAoB,CAAC,EAAE,MAAM,IAAI,GAChC,OAAO,CAAC,IAAI,CAAC,CAoBf"}
1
+ {"version":3,"file":"pinned-write-input.d.ts","sourceRoot":"","sources":["../src/pinned-write-input.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAKnD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAWhE,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,UAAU,GAAG,MAAM,EAC3B,KAAK,EAAE,gBAAgB,EACvB,QAAQ,CAAC,EAAE,MAAM,EACjB,oBAAoB,CAAC,EAAE,MAAM,IAAI,GAChC,OAAO,CAAC,IAAI,CAAC,CAoBf"}
@@ -1,6 +1,16 @@
1
+ import { types } from "node:util";
2
+ import { snapshotByteView } from "./byte-view.js";
1
3
  import { writeCopyFileToFd } from "./copy-file-input.js";
2
4
  import { FsSafeError } from "./errors.js";
3
5
  import { writeAllToFile } from "./write-file-handle.js";
6
+ const isUint8Array = types.isUint8Array;
7
+ function streamChunkBytes(chunk) {
8
+ if (Buffer.isBuffer(chunk))
9
+ return chunk;
10
+ if (!isUint8Array(chunk))
11
+ return Buffer.from(chunk);
12
+ return snapshotByteView(chunk);
13
+ }
4
14
  export async function writePinnedInput(target, input, maxBytes, assertBeforeMutation) {
5
15
  if (input.kind === "file") {
6
16
  await writeCopyFileToFd(typeof target === "number" ? target : target.fd, input, maxBytes, assertBeforeMutation);
@@ -19,7 +29,7 @@ export async function writePinnedInput(target, input, maxBytes, assertBeforeMuta
19
29
  }
20
30
  else {
21
31
  for await (const chunk of input.stream) {
22
- await write(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
32
+ await write(streamChunkBytes(chunk));
23
33
  }
24
34
  }
25
35
  }
@@ -1,5 +1,5 @@
1
1
  import type { FileHandle } from "node:fs/promises";
2
- export declare function assertPinnedWriteMode(fd: number, mode: number): void;
3
- export declare function preparePinnedWriteMode(handle: FileHandle, createdMode: number, assertBeforeMutation?: () => void): Promise<(() => void) | undefined>;
4
- export declare function pinnedWriteModeAssertion(fd: number, mode: number, assertBeforeMutation?: () => void): () => void;
2
+ export declare function assertPinnedWriteMode(fd: number, mode: number, privateCreation?: boolean): void;
3
+ export declare function preparePinnedWriteMode(handle: FileHandle, createdMode: number, assertBeforeMutation?: () => void, privateCreation?: boolean): Promise<(() => void) | undefined>;
4
+ export declare function pinnedWriteModeAssertion(fd: number, mode: number, assertBeforeMutation?: () => void, privateCreation?: boolean): () => void;
5
5
  //# sourceMappingURL=pinned-write-mode.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"pinned-write-mode.d.ts","sourceRoot":"","sources":["../src/pinned-write-mode.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAInD,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAKpE;AAED,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,UAAU,EAClB,WAAW,EAAE,MAAM,EACnB,oBAAoB,CAAC,EAAE,MAAM,IAAI,GAChC,OAAO,CAAC,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,CAAC,CAKnC;AAED,wBAAgB,wBAAwB,CACtC,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,MAAM,EACZ,oBAAoB,CAAC,EAAE,MAAM,IAAI,GAChC,MAAM,IAAI,CAKZ"}
1
+ {"version":3,"file":"pinned-write-mode.d.ts","sourceRoot":"","sources":["../src/pinned-write-mode.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAKnD,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,eAAe,UAAQ,GAAG,IAAI,CAO7F;AAED,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,UAAU,EAClB,WAAW,EAAE,MAAM,EACnB,oBAAoB,CAAC,EAAE,MAAM,IAAI,EACjC,eAAe,UAAQ,GACtB,OAAO,CAAC,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,CAAC,CAQnC;AAED,wBAAgB,wBAAwB,CACtC,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,MAAM,EACZ,oBAAoB,CAAC,EAAE,MAAM,IAAI,EACjC,eAAe,UAAQ,GACtB,MAAM,IAAI,CAKZ"}
@@ -1,24 +1,31 @@
1
1
  import fs from "node:fs";
2
2
  import { FsSafeError } from "./errors.js";
3
3
  import { assertSynchronousCallbackResult } from "./mutation-authority.js";
4
- export function assertPinnedWriteMode(fd, mode) {
4
+ import { assertPrivateCreationFile } from "./creation-file-state.js";
5
+ export function assertPinnedWriteMode(fd, mode, privateCreation = false) {
5
6
  if (process.platform === "win32")
6
7
  return;
7
- if (Number(fs.fstatSync(fd, { bigint: true }).mode & 4095n) !== mode) {
8
- throw new FsSafeError("insecure-permissions", "filesystem did not enforce the secret file mode");
8
+ const stat = fs.fstatSync(fd, { bigint: true });
9
+ if (privateCreation)
10
+ assertPrivateCreationFile(stat, fd);
11
+ if (Number(stat.mode & 4095n) !== mode) {
12
+ throw new FsSafeError("insecure-permissions", "filesystem did not enforce the requested file mode");
9
13
  }
10
14
  }
11
- export async function preparePinnedWriteMode(handle, createdMode, assertBeforeMutation) {
15
+ export async function preparePinnedWriteMode(handle, createdMode, assertBeforeMutation, privateCreation = false) {
12
16
  if (process.platform === "win32")
13
17
  return assertBeforeMutation;
14
18
  assertSynchronousCallbackResult(assertBeforeMutation?.(), "assertBeforeMutation");
15
- if (createdMode !== 0o600)
19
+ if (createdMode !== 0o600) {
20
+ if (privateCreation)
21
+ assertPrivateCreationFile(fs.fstatSync(handle.fd, { bigint: true }), handle.fd);
16
22
  await handle.chmod(0o600);
17
- return pinnedWriteModeAssertion(handle.fd, 0o600, assertBeforeMutation);
23
+ }
24
+ return pinnedWriteModeAssertion(handle.fd, 0o600, assertBeforeMutation, privateCreation);
18
25
  }
19
- export function pinnedWriteModeAssertion(fd, mode, assertBeforeMutation) {
26
+ export function pinnedWriteModeAssertion(fd, mode, assertBeforeMutation, privateCreation = false) {
20
27
  return () => {
21
28
  assertSynchronousCallbackResult(assertBeforeMutation?.(), "assertBeforeMutation");
22
- assertPinnedWriteMode(fd, mode);
29
+ assertPinnedWriteMode(fd, mode, privateCreation);
23
30
  };
24
31
  }
@@ -1 +1 @@
1
- {"version":3,"file":"pinned-write-staged.d.ts","sourceRoot":"","sources":["../src/pinned-write-staged.ts"],"names":[],"mappings":"AACA,OAAe,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAMnD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAGhE,OAAO,EAA+B,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAMxF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AASjE,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,iBAAiB,EACzB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,mBAAmB,CAAC,WAAW,CAAC,GAC5C,OAAO,CAAC,gBAAgB,CAAC,CAqJ3B"}
1
+ {"version":3,"file":"pinned-write-staged.d.ts","sourceRoot":"","sources":["../src/pinned-write-staged.ts"],"names":[],"mappings":"AACA,OAAe,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAMnD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAGhE,OAAO,EAA+B,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAKxF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AASjE,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,iBAAiB,EACzB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,mBAAmB,CAAC,WAAW,CAAC,GAC5C,OAAO,CAAC,gBAAgB,CAAC,CAqJ3B"}
@@ -9,7 +9,6 @@ import { syncDirectoryBestEffort } from "./directory-durability.js";
9
9
  import { FsSafeError } from "./errors.js";
10
10
  import { sameFileIdentity, sha256Hex } from "./file-identity.js";
11
11
  import { syncFileBestEffort } from "./file-sync.js";
12
- import { assertDarwinCreationAcl, privateFileMutationAssertion } from "./creation-darwin.js";
13
12
  import { withAsyncDirectoryGuards } from "./guarded-mutation.js";
14
13
  import { writePinnedInput } from "./pinned-write-input.js";
15
14
  import { assertPinnedWriteMode, pinnedWriteModeAssertion, preparePinnedWriteMode } from "./pinned-write-mode.js";
@@ -20,7 +19,8 @@ import { assertFinalSymlinkRejected } from "./root-symlink-policy.js";
20
19
  import { createStagedFileReceipt, settleStagedFile } from "./staged-file-settlement.js";
21
20
  import { getFsSafeTestHooks } from "./test-hooks.js";
22
21
  export async function runPinnedStagedWrite(params, parentPath, parentGuard) {
23
- const verifyPosixMode = params.verifyPosixMode === true && process.platform !== "win32";
22
+ const verifyPosixMode = process.platform !== "win32" &&
23
+ (params.verifyPosixMode === true || params.private === true);
24
24
  const targetPath = path.join(parentPath, params.basename);
25
25
  // Private staging must not consume the destination basename's filename budget.
26
26
  const tempPath = path.join(parentPath, `.fs-safe-${randomUUID()}.tmp`);
@@ -49,18 +49,15 @@ export async function runPinnedStagedWrite(params, parentPath, parentGuard) {
49
49
  : await fs.open(tempPath, tempFlags, verifyPosixMode ? 0o600 : params.mode);
50
50
  let verificationIdentity = fsSync.fstatSync(handle.fd, { bigint: true });
51
51
  tempIdentity = verificationIdentity;
52
- let assertBeforeMutation = params.private
53
- ? privateFileMutationAssertion(handle.fd, params.assertBeforeMutation) : params.assertBeforeMutation;
52
+ let assertBeforeMutation = params.assertBeforeMutation;
54
53
  const assertBeforeWrite = verifyPosixMode
55
- ? await preparePinnedWriteMode(handle, Number(tempIdentity.mode & 4095n), assertBeforeMutation)
54
+ ? await preparePinnedWriteMode(handle, Number(tempIdentity.mode & 4095n), assertBeforeMutation, params.private)
56
55
  : assertBeforeMutation;
57
56
  if (verifyPosixMode)
58
- assertPinnedWriteMode(handle.fd, 0o600);
57
+ assertPinnedWriteMode(handle.fd, 0o600, params.private);
59
58
  await writePinnedInput(handle, params.input, params.maxBytes, assertBeforeWrite);
60
59
  if (verifyPosixMode)
61
- assertPinnedWriteMode(handle.fd, 0o600);
62
- if (params.private)
63
- assertDarwinCreationAcl(handle.fd);
60
+ assertPinnedWriteMode(handle.fd, 0o600, params.private);
64
61
  tempStat = fsSync.fstatSync(handle.fd);
65
62
  const tempPathStat = fsSync.lstatSync(tempPath);
66
63
  if (tempPathStat.isSymbolicLink() || !sameFileIdentity(tempPathStat, tempStat)) {
@@ -69,8 +66,8 @@ export async function runPinnedStagedWrite(params, parentPath, parentGuard) {
69
66
  const expectedTempStat = tempStat;
70
67
  await handle.chmod(params.mode);
71
68
  if (verifyPosixMode) {
72
- assertPinnedWriteMode(handle.fd, params.mode);
73
- assertBeforeMutation = pinnedWriteModeAssertion(handle.fd, params.mode, assertBeforeMutation);
69
+ assertPinnedWriteMode(handle.fd, params.mode, params.private);
70
+ assertBeforeMutation = pinnedWriteModeAssertion(handle.fd, params.mode, params.assertBeforeMutation, params.private);
74
71
  }
75
72
  if (params.sync !== false) {
76
73
  if (params.strictFileSync)
@@ -146,6 +143,8 @@ export async function runPinnedStagedWrite(params, parentPath, parentGuard) {
146
143
  }
147
144
  });
148
145
  await params.verifyPublished?.((readHandle ?? handle).fd, verificationIdentity, parentGuard);
146
+ if (verifyPosixMode)
147
+ assertPinnedWriteMode((readHandle ?? handle).fd, params.mode, params.private);
149
148
  return { dev: verifiedIdentity.dev, ino: verifiedIdentity.ino };
150
149
  }
151
150
  catch (error) {
@@ -1 +1 @@
1
- {"version":3,"file":"pinned-write.d.ts","sourceRoot":"","sources":["../src/pinned-write.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAgB3D,OAAO,KAAK,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AACvF,YAAY,EACV,gBAAgB,EAAE,oBAAoB,EAAE,sBAAsB,EAC9D,8BAA8B,EAAE,gCAAgC,EAChE,6BAA6B,EAAE,+BAA+B,EAC9D,4BAA4B,EAAE,iBAAiB,GAChD,MAAM,yBAAyB,CAAC;AAkDjC,wBAAsB,oBAAoB,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAsD/F;AAED,wBAAsB,8BAA8B,CAClD,MAAM,EAAE,iBAAiB,GAAG;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,oBAAoB,CAAC;CACvC,GACA,OAAO,CAAC,gBAAgB,CAAC,CAkB3B;AAED,wBAAsB,iCAAiC,CAAC,CAAC,EACvD,MAAM,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,kBAAkB,EAAE,MAAM,CAAA;CAAE,EAC5E,GAAG,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACpB,OAAO,CAAC,CAAC,CAAC,CAiBZ"}
1
+ {"version":3,"file":"pinned-write.d.ts","sourceRoot":"","sources":["../src/pinned-write.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAgB3D,OAAO,KAAK,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AACvF,YAAY,EACV,gBAAgB,EAAE,oBAAoB,EAAE,sBAAsB,EAC9D,8BAA8B,EAAE,gCAAgC,EAChE,6BAA6B,EAAE,+BAA+B,EAC9D,4BAA4B,EAAE,iBAAiB,GAChD,MAAM,yBAAyB,CAAC;AAkDjC,wBAAsB,oBAAoB,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAsD/F;AAED,wBAAsB,8BAA8B,CAClD,MAAM,EAAE,iBAAiB,GAAG;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,oBAAoB,CAAC;CACvC,GACA,OAAO,CAAC,gBAAgB,CAAC,CAkB3B;AAED,wBAAsB,iCAAiC,CAAC,CAAC,EACvD,MAAM,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,kBAAkB,EAAE,MAAM,CAAA;CAAE,EAC5E,GAAG,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACpB,OAAO,CAAC,CAAC,CAAC,CAiBZ"}
@@ -1,5 +1,4 @@
1
1
  import { syncFileBestEffort } from "./file-sync.js";
2
- import { assertDarwinCreationAcl, privateFileMutationAssertion } from "./creation-darwin.js";
3
2
  import fsSync, {} from "node:fs";
4
3
  import fs from "node:fs/promises";
5
4
  import path from "node:path";
@@ -124,7 +123,8 @@ export async function withPinnedWriteRenameIdentityLock(params, run) {
124
123
  }, run);
125
124
  }
126
125
  async function runPinnedWriteFallback(params) {
127
- const verifyPosixMode = params.verifyPosixMode === true && process.platform !== "win32";
126
+ const verifyPosixMode = process.platform !== "win32" &&
127
+ (params.verifyPosixMode === true || params.private === true);
128
128
  const exactRoot = typeof params.rootIdentity?.dev === "bigint" && typeof params.rootIdentity.ino === "bigint"
129
129
  ? { dev: params.rootIdentity.dev, ino: params.rootIdentity.ino } : undefined;
130
130
  const mutationAdmission = params.mutationAdmission;
@@ -250,25 +250,23 @@ async function runPinnedWriteFallback(params) {
250
250
  },
251
251
  });
252
252
  let created = true;
253
+ let completed = false;
253
254
  let createdIdentity;
254
255
  try {
255
256
  const verificationIdentity = fsSync.fstatSync(handle.fd, { bigint: true });
256
257
  createdIdentity = verificationIdentity;
257
- let assertBeforeWrite = params.private
258
- ? privateFileMutationAssertion(handle.fd, assertBeforeMutation) : assertBeforeMutation;
259
- if (verifyPosixMode) {
260
- assertBeforeWrite = await preparePinnedWriteMode(handle, Number(createdIdentity.mode & 4095n), assertBeforeWrite);
261
- assertPinnedWriteMode(handle.fd, 0o600);
262
- }
258
+ const assertBeforeWrite = verifyPosixMode
259
+ ? await preparePinnedWriteMode(handle, Number(createdIdentity.mode & 4095n), assertBeforeMutation, params.private)
260
+ : assertBeforeMutation;
261
+ if (verifyPosixMode)
262
+ assertPinnedWriteMode(handle.fd, 0o600, params.private);
263
263
  await writePinnedInput(handle, params.input, params.maxBytes, assertBeforeWrite);
264
264
  if (verifyPosixMode)
265
- assertPinnedWriteMode(handle.fd, 0o600);
266
- if (params.private)
267
- assertDarwinCreationAcl(handle.fd);
265
+ assertPinnedWriteMode(handle.fd, 0o600, params.private);
268
266
  // Content writes may clear set-ID bits; finalize them through the owned fd.
269
267
  await handle.chmod(params.mode);
270
268
  if (verifyPosixMode)
271
- assertPinnedWriteMode(handle.fd, params.mode);
269
+ assertPinnedWriteMode(handle.fd, params.mode, params.private);
272
270
  if (params.sync !== false) {
273
271
  if (params.strictFileSync)
274
272
  await handle.sync();
@@ -281,6 +279,9 @@ async function runPinnedWriteFallback(params) {
281
279
  // Publication is complete. A failed outer check must not remove its target.
282
280
  created = false;
283
281
  await params.verifyPublished?.(handle.fd, verificationIdentity, parentGuard);
282
+ if (verifyPosixMode)
283
+ assertPinnedWriteMode(handle.fd, params.mode, params.private);
284
+ completed = true;
284
285
  return { dev: stat.dev, ino: stat.ino };
285
286
  }
286
287
  finally {
@@ -290,7 +291,10 @@ async function runPinnedWriteFallback(params) {
290
291
  }
291
292
  }
292
293
  finally {
293
- await handle.close().catch(() => undefined);
294
+ if (completed)
295
+ await handle.close();
296
+ else
297
+ await handle.close().catch(() => undefined);
294
298
  }
295
299
  }
296
300
  }