@openclaw/fs-safe 0.9.0 → 0.10.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 (178) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/README.md +17 -0
  3. package/dist/advanced.d.ts +1 -0
  4. package/dist/advanced.d.ts.map +1 -1
  5. package/dist/advanced.js +1 -0
  6. package/dist/archive-crc32.d.ts.map +1 -1
  7. package/dist/archive-crc32.js +6 -1
  8. package/dist/archive-deadline.d.ts.map +1 -1
  9. package/dist/archive-deadline.js +3 -4
  10. package/dist/archive-gzip-tail.d.ts +2 -0
  11. package/dist/archive-gzip-tail.d.ts.map +1 -1
  12. package/dist/archive-gzip-tail.js +20 -3
  13. package/dist/archive-merge.js +1 -1
  14. package/dist/archive-native.d.ts.map +1 -1
  15. package/dist/archive-native.js +3 -2
  16. package/dist/archive-read.d.ts.map +1 -1
  17. package/dist/archive-read.js +67 -66
  18. package/dist/archive-tar-stream.d.ts +11 -4
  19. package/dist/archive-tar-stream.d.ts.map +1 -1
  20. package/dist/archive-tar-stream.js +20 -8
  21. package/dist/archive-tar-wasm.d.ts.map +1 -1
  22. package/dist/archive-tar-wasm.js +2 -1
  23. package/dist/bounded-read.d.ts +7 -0
  24. package/dist/bounded-read.d.ts.map +1 -1
  25. package/dist/bounded-read.js +73 -43
  26. package/dist/clone-metadata.d.ts +19 -0
  27. package/dist/clone-metadata.d.ts.map +1 -0
  28. package/dist/clone-metadata.js +32 -0
  29. package/dist/copy-file-input.d.ts +23 -0
  30. package/dist/copy-file-input.d.ts.map +1 -0
  31. package/dist/copy-file-input.js +91 -0
  32. package/dist/copy-policy.d.ts +3 -0
  33. package/dist/copy-policy.d.ts.map +1 -0
  34. package/dist/copy-policy.js +8 -0
  35. package/dist/copy-publication.d.ts +10 -1
  36. package/dist/copy-publication.d.ts.map +1 -1
  37. package/dist/copy-publication.js +27 -0
  38. package/dist/copy-tree-portable.d.ts +9 -0
  39. package/dist/copy-tree-portable.d.ts.map +1 -0
  40. package/dist/copy-tree-portable.js +191 -0
  41. package/dist/copy.d.ts +16 -0
  42. package/dist/copy.d.ts.map +1 -0
  43. package/dist/copy.js +123 -0
  44. package/dist/durability.d.ts +1 -1
  45. package/dist/durability.d.ts.map +1 -1
  46. package/dist/error-detail.d.ts.map +1 -1
  47. package/dist/error-detail.js +4 -1
  48. package/dist/file-hash.d.ts +6 -2
  49. package/dist/file-hash.d.ts.map +1 -1
  50. package/dist/file-hash.js +44 -8
  51. package/dist/filename.d.ts.map +1 -1
  52. package/dist/filename.js +2 -12
  53. package/dist/guarded-mkdir.d.ts +1 -0
  54. package/dist/guarded-mkdir.d.ts.map +1 -1
  55. package/dist/guarded-mkdir.js +1 -0
  56. package/dist/guarded-mutation.d.ts +2 -0
  57. package/dist/guarded-mutation.d.ts.map +1 -1
  58. package/dist/guarded-mutation.js +8 -4
  59. package/dist/home-dir.d.ts.map +1 -1
  60. package/dist/home-dir.js +9 -7
  61. package/dist/index.d.ts +1 -1
  62. package/dist/index.d.ts.map +1 -1
  63. package/dist/install-path.d.ts.map +1 -1
  64. package/dist/install-path.js +2 -6
  65. package/dist/json-durable-queue.d.ts.map +1 -1
  66. package/dist/json-durable-queue.js +13 -14
  67. package/dist/local-roots.d.ts.map +1 -1
  68. package/dist/local-roots.js +22 -24
  69. package/dist/move-path-stage.d.ts +8 -0
  70. package/dist/move-path-stage.d.ts.map +1 -0
  71. package/dist/move-path-stage.js +55 -0
  72. package/dist/move-path.d.ts.map +1 -1
  73. package/dist/move-path.js +26 -31
  74. package/dist/mutation-authority.d.ts +8 -0
  75. package/dist/mutation-authority.d.ts.map +1 -0
  76. package/dist/mutation-authority.js +36 -0
  77. package/dist/native-binding.d.ts +24 -1
  78. package/dist/native-binding.d.ts.map +1 -1
  79. package/dist/native-operations.d.ts +1 -1
  80. package/dist/native-operations.d.ts.map +1 -1
  81. package/dist/native-operations.js +11 -5
  82. package/dist/native-pinned-write-windows.d.ts.map +1 -1
  83. package/dist/native-pinned-write-windows.js +18 -7
  84. package/dist/native-pinned-write.d.ts.map +1 -1
  85. package/dist/native-pinned-write.js +1 -0
  86. package/dist/native-staged-file.d.ts +3 -3
  87. package/dist/native-staged-file.d.ts.map +1 -1
  88. package/dist/native-staged-file.js +35 -8
  89. package/dist/path.d.ts.map +1 -1
  90. package/dist/path.js +3 -1
  91. package/dist/permissions-windows.d.ts.map +1 -1
  92. package/dist/permissions-windows.js +38 -48
  93. package/dist/pinned-operation.js +1 -1
  94. package/dist/pinned-write.d.ts +5 -1
  95. package/dist/pinned-write.d.ts.map +1 -1
  96. package/dist/pinned-write.js +50 -30
  97. package/dist/positional-read.d.ts +9 -0
  98. package/dist/positional-read.d.ts.map +1 -0
  99. package/dist/positional-read.js +36 -0
  100. package/dist/publish-copy-stage.d.ts +13 -0
  101. package/dist/publish-copy-stage.d.ts.map +1 -0
  102. package/dist/publish-copy-stage.js +47 -0
  103. package/dist/publish-file.d.ts.map +1 -1
  104. package/dist/publish-file.js +6 -5
  105. package/dist/replace-file-temp-owner.d.ts.map +1 -1
  106. package/dist/replace-file-temp-owner.js +6 -2
  107. package/dist/root-context.d.ts +3 -0
  108. package/dist/root-context.d.ts.map +1 -1
  109. package/dist/root-context.js +51 -26
  110. package/dist/root-directory-list.d.ts +24 -0
  111. package/dist/root-directory-list.d.ts.map +1 -0
  112. package/dist/root-directory-list.js +201 -0
  113. package/dist/root-errors.d.ts +1 -0
  114. package/dist/root-errors.d.ts.map +1 -1
  115. package/dist/root-errors.js +3 -0
  116. package/dist/root-file.d.ts +2 -0
  117. package/dist/root-file.d.ts.map +1 -1
  118. package/dist/root-file.js +12 -4
  119. package/dist/root-impl.d.ts +6 -50
  120. package/dist/root-impl.d.ts.map +1 -1
  121. package/dist/root-impl.js +330 -210
  122. package/dist/root-options.d.ts +66 -0
  123. package/dist/root-options.d.ts.map +1 -0
  124. package/dist/root-options.js +18 -0
  125. package/dist/root-path-existing.d.ts +5 -0
  126. package/dist/root-path-existing.d.ts.map +1 -1
  127. package/dist/root-path-existing.js +56 -1
  128. package/dist/root-path.d.ts +1 -0
  129. package/dist/root-path.d.ts.map +1 -1
  130. package/dist/root-path.js +74 -162
  131. package/dist/root-symlink-policy.d.ts +13 -0
  132. package/dist/root-symlink-policy.d.ts.map +1 -0
  133. package/dist/root-symlink-policy.js +34 -0
  134. package/dist/root-walk.d.ts +5 -4
  135. package/dist/root-walk.d.ts.map +1 -1
  136. package/dist/root-walk.js +99 -50
  137. package/dist/root-write-mode.d.ts.map +1 -1
  138. package/dist/root-write-mode.js +1 -4
  139. package/dist/root.d.ts +3 -1
  140. package/dist/root.d.ts.map +1 -1
  141. package/dist/secure-file.d.ts.map +1 -1
  142. package/dist/secure-file.js +4 -4
  143. package/dist/sidecar-lock-acquire.d.ts.map +1 -1
  144. package/dist/sidecar-lock-acquire.js +2 -1
  145. package/dist/sidecar-lock-policy.d.ts.map +1 -1
  146. package/dist/sidecar-lock-policy.js +3 -1
  147. package/dist/temp-cleanup.d.ts.map +1 -1
  148. package/dist/temp-cleanup.js +3 -2
  149. package/dist/timing.d.ts +1 -0
  150. package/dist/timing.d.ts.map +1 -1
  151. package/dist/timing.js +25 -6
  152. package/dist/trash.js +2 -2
  153. package/dist/walk.d.ts.map +1 -1
  154. package/dist/walk.js +6 -26
  155. package/dist/windows-owner.d.ts +9 -12
  156. package/dist/windows-owner.d.ts.map +1 -1
  157. package/dist/windows-owner.js +24 -58
  158. package/dist/write-file-handle.d.ts +6 -0
  159. package/dist/write-file-handle.d.ts.map +1 -0
  160. package/dist/write-file-handle.js +25 -0
  161. package/docs/advanced.md +18 -4
  162. package/docs/archive.md +22 -7
  163. package/docs/atomic.md +7 -0
  164. package/docs/contributing.md +7 -0
  165. package/docs/copy.md +86 -0
  166. package/docs/durability.md +17 -2
  167. package/docs/errors.md +1 -1
  168. package/docs/local-roots.md +8 -1
  169. package/docs/native.md +14 -1
  170. package/docs/permissions.md +20 -10
  171. package/docs/positional-read.md +63 -0
  172. package/docs/root.md +166 -4
  173. package/docs/security-model.md +14 -0
  174. package/docs/timing.md +2 -0
  175. package/docs/types.md +18 -11
  176. package/docs/walk.md +54 -5
  177. package/docs/writing.md +20 -2
  178. package/package.json +13 -8
@@ -2,10 +2,9 @@ import fsSync from "node:fs";
2
2
  import path from "node:path";
3
3
  import { normalizeMaxBytes } from "./byte-budget.js";
4
4
  import { FsSafeError } from "./errors.js";
5
- import { expandHomePrefix, resolveHomeRelativePath } from "./home-dir.js";
5
+ import { expandHomePrefix, resolveRequiredHomeDir } from "./home-dir.js";
6
6
  import { isFileUrl, safeFileURLToPath } from "./local-file-access.js";
7
- import { isPathInside } from "./path.js";
8
- import { resolveRootPathSync } from "./root-path.js";
7
+ import { ROOT_PATH_ALIAS_POLICIES, resolveRootPathSync } from "./root-path.js";
9
8
  import { root } from "./root.js";
10
9
  function resolveLocalPathInput(input, label) {
11
10
  if (isFileUrl(input)) {
@@ -20,7 +19,14 @@ function resolveLocalPathInput(input, label) {
20
19
  if (input.includes("\0")) {
21
20
  throw new FsSafeError("invalid-path", `${label} must not contain NUL bytes`);
22
21
  }
23
- return resolveHomeRelativePath(input);
22
+ const homePrefix = input === "~" || input.startsWith("~/") ||
23
+ (path.sep === "\\" && input.startsWith("~\\"));
24
+ const expanded = homePrefix ? `${resolveRequiredHomeDir()}${path.sep}${input.slice(2)}` : input;
25
+ if (path.isAbsolute(expanded))
26
+ return expanded;
27
+ const drive = path.parse(expanded).root;
28
+ const base = drive ? path.resolve(drive) : process.cwd();
29
+ return `${base}${path.sep}${expanded.slice(drive.length)}`;
24
30
  }
25
31
  function resolveLocalRootInput(input, label) {
26
32
  const trimmed = input.trim();
@@ -54,7 +60,7 @@ function resolveRootRealSync(rootDir) {
54
60
  }
55
61
  export function resolveLocalPathFromRootsSync(options) {
56
62
  const label = options.label ?? "local roots";
57
- const requestedPath = path.resolve(resolveLocalPathInput(options.filePath, "file path"));
63
+ const requestedPath = resolveLocalPathInput(options.filePath, "file path");
58
64
  const rootDirs = options.roots.map((rootEntry) => resolveLocalRootInput(rootEntry, label));
59
65
  for (const rootDir of rootDirs) {
60
66
  const rootReal = resolveRootRealSync(rootDir);
@@ -69,6 +75,7 @@ export function resolveLocalPathFromRootsSync(options) {
69
75
  rootCanonicalPath: rootReal,
70
76
  boundaryLabel: label,
71
77
  rejectUnresolvedSymlinks: true,
78
+ policy: options.requireFile ? ROOT_PATH_ALIAS_POLICIES.unlinkTarget : undefined,
72
79
  });
73
80
  }
74
81
  catch {
@@ -77,11 +84,12 @@ export function resolveLocalPathFromRootsSync(options) {
77
84
  if (!candidate.exists && options.allowMissing !== true) {
78
85
  continue;
79
86
  }
87
+ if (candidate.exists && options.requireFile === true && candidate.kind !== "file")
88
+ continue;
80
89
  if (candidate.exists && options.requireFile === true) {
81
90
  try {
82
- if (!fsSync.lstatSync(requestedPath).isFile()) {
91
+ if (!fsSync.lstatSync(requestedPath).isFile())
83
92
  continue;
84
- }
85
93
  }
86
94
  catch {
87
95
  continue;
@@ -94,7 +102,7 @@ export function resolveLocalPathFromRootsSync(options) {
94
102
  export async function readLocalFileFromRoots(options) {
95
103
  const maxBytes = normalizeMaxBytes(options.maxBytes);
96
104
  const label = options.label ?? "local roots";
97
- const requestedPath = path.resolve(resolveLocalPathInput(options.filePath, "file path"));
105
+ const requestedPath = resolveLocalPathInput(options.filePath, "file path");
98
106
  const rootDirs = options.roots.map((rootEntry) => resolveLocalRootInput(rootEntry, label));
99
107
  for (const rootDir of rootDirs) {
100
108
  let scopedRoot;
@@ -113,22 +121,12 @@ export async function readLocalFileFromRoots(options) {
113
121
  if (maxBytes !== undefined) {
114
122
  readOptions.maxBytes = maxBytes;
115
123
  }
116
- // A trusted root symlink has two valid spellings. Preserve the caller's
117
- // lexical spelling when possible so Root.read() still enforces its
118
- // symlink policy, while also accepting a path expressed below rootReal.
119
- const relativePaths = [scopedRoot.rootDir, scopedRoot.rootReal]
120
- .filter((rootPath, index, roots) => roots.indexOf(rootPath) === index)
121
- .filter((rootPath) => isPathInside(rootPath, requestedPath))
122
- .map((rootPath) => path.relative(rootPath, requestedPath))
123
- .filter(Boolean);
124
- for (const relativePath of relativePaths) {
125
- try {
126
- const result = await scopedRoot.read(relativePath, readOptions);
127
- return { ...result, root: scopedRoot.rootReal };
128
- }
129
- catch {
130
- // Try the canonical spelling before moving to the next configured root.
131
- }
124
+ try {
125
+ const result = await scopedRoot.readAbsolute(requestedPath, readOptions);
126
+ return { ...result, root: scopedRoot.rootReal };
127
+ }
128
+ catch {
129
+ // Root handles both trusted spellings; try the next configured root.
132
130
  }
133
131
  }
134
132
  return null;
@@ -0,0 +1,8 @@
1
+ import { type BigIntStats } from "node:fs";
2
+ export declare function createMoveStageOwner(staged: string): Promise<{
3
+ record(created: BigIntStats): void;
4
+ assertCurrent: () => void;
5
+ published(): void;
6
+ cleanup(): Promise<void>;
7
+ }>;
8
+ //# sourceMappingURL=move-path-stage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"move-path-stage.d.ts","sourceRoot":"","sources":["../src/move-path-stage.ts"],"names":[],"mappings":"AAAA,OAAe,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAQnD,wBAAsB,oBAAoB,CAAC,MAAM,EAAE,MAAM;oBAkBrC,WAAW,GAAG,IAAI;yBAPV,IAAI;iBAkBf,IAAI;eACA,OAAO,CAAC,IAAI,CAAC;GAajC"}
@@ -0,0 +1,55 @@
1
+ import fsSync, {} from "node:fs";
2
+ import path from "node:path";
3
+ import { createAsyncDirectoryGuard } from "./directory-guard.js";
4
+ import { FsSafeError } from "./errors.js";
5
+ import { guardedRm, guardedRmSync } from "./guarded-mutation.js";
6
+ import { sameFileIdentityForCleanup } from "./file-identity.js";
7
+ import { registerTempPathForExit } from "./temp-cleanup.js";
8
+ export async function createMoveStageOwner(staged) {
9
+ const parent = await createAsyncDirectoryGuard(path.dirname(staged), { bigint: true });
10
+ let identity;
11
+ let unregister;
12
+ const changed = () => new FsSafeError("path-mismatch", "move staging path changed before publication");
13
+ const assertParent = () => {
14
+ const current = fsSync.lstatSync(parent.dir, { bigint: true });
15
+ if (!current.isDirectory() || current.isSymbolicLink() ||
16
+ !sameFileIdentityForCleanup(current, parent.stat) ||
17
+ fsSync.realpathSync.native(parent.dir) !== parent.realPath)
18
+ throw changed();
19
+ };
20
+ const assertCurrent = () => {
21
+ assertParent();
22
+ if (!identity || !sameFileIdentityForCleanup(fsSync.lstatSync(staged, { bigint: true }), identity)) {
23
+ throw changed();
24
+ }
25
+ };
26
+ return {
27
+ record(created) {
28
+ if (!sameFileIdentityForCleanup(created, created))
29
+ throw changed();
30
+ identity = created;
31
+ unregister = registerTempPathForExit(staged, {
32
+ identity,
33
+ cleanupSync: () => {
34
+ guardedRmSync({ target: staged, recursive: created.isDirectory(), assertBeforeMutation: assertCurrent });
35
+ },
36
+ });
37
+ },
38
+ assertCurrent,
39
+ published() { unregister?.(); },
40
+ async cleanup() {
41
+ if (!identity)
42
+ return;
43
+ try {
44
+ await guardedRm({ target: staged, recursive: identity.isDirectory(), assertBeforeMutation: assertCurrent });
45
+ unregister?.();
46
+ }
47
+ catch (error) {
48
+ if (error.code === "ENOENT" || error instanceof FsSafeError) {
49
+ unregister?.();
50
+ }
51
+ // Retry only the original receipt after transient cleanup I/O failures.
52
+ }
53
+ },
54
+ };
55
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"move-path.d.ts","sourceRoot":"","sources":["../src/move-path.ts"],"names":[],"mappings":"AAoBA,MAAM,MAAM,0BAA0B,GAAG,QAAQ,CAAC;IAChD,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACb,CAAC,CAAC;AAEH,MAAM,MAAM,+BAA+B,GAAG;IAC5C,qFAAqF;IACrF,oBAAoB,CAAC,EAAE,MAAM,IAAI,CAAC;IAClC,uFAAuF;IACvF,kBAAkB,CAAC,EAAE,MAAM,IAAI,CAAC;IAChC,qFAAqF;IACrF,sBAAsB,CAAC,EAAE,CAAC,OAAO,EAAE,0BAA0B,KAAK,IAAI,CAAC;IACvE,IAAI,EAAE,MAAM,CAAC;IACb,eAAe,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;IACrC,EAAE,EAAE,MAAM,CAAC;CACZ,CAAC;AAEF,KAAK,sBAAsB,GAAG,cAAc,GAAG,uBAAuB,CAAC;AAEvE,wBAAgB,oCAAoC,CAClD,KAAK,EAAE,OAAO,EACd,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,sBAAsB,GAAG,SAAS,CASpC;AAuQD,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,+BAA+B,GACvC,OAAO,CAAC,IAAI,CAAC,CAqHf"}
1
+ {"version":3,"file":"move-path.d.ts","sourceRoot":"","sources":["../src/move-path.ts"],"names":[],"mappings":"AAsBA,MAAM,MAAM,0BAA0B,GAAG,QAAQ,CAAC;IAChD,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACb,CAAC,CAAC;AAEH,MAAM,MAAM,+BAA+B,GAAG;IAC5C,qFAAqF;IACrF,oBAAoB,CAAC,EAAE,MAAM,IAAI,CAAC;IAClC,uFAAuF;IACvF,kBAAkB,CAAC,EAAE,MAAM,IAAI,CAAC;IAChC,qFAAqF;IACrF,sBAAsB,CAAC,EAAE,CAAC,OAAO,EAAE,0BAA0B,KAAK,IAAI,CAAC;IACvE,IAAI,EAAE,MAAM,CAAC;IACb,eAAe,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;IACrC,EAAE,EAAE,MAAM,CAAC;CACZ,CAAC;AAEF,KAAK,sBAAsB,GAAG,cAAc,GAAG,uBAAuB,CAAC;AAEvE,wBAAgB,oCAAoC,CAClD,KAAK,EAAE,OAAO,EACd,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,sBAAsB,GAAG,SAAS,CASpC;AAiRD,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,+BAA+B,GACvC,OAAO,CAAC,IAAI,CAAC,CAwGf"}
package/dist/move-path.js CHANGED
@@ -2,11 +2,13 @@ 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 } from "./directory-guard.js";
5
6
  import { FsSafeError } from "./errors.js";
6
7
  import { guardedRename } from "./guarded-mutation.js";
7
8
  import { assertSourceStillMatches, cleanupCopiedEntry, createCleanupCopiedEntryState, entryIdentity, sameIdentity, sourceChangedError, } from "./move-path-cleanup.js";
8
9
  import { resolveReadOpenFlags } from "./read-open-flags.js";
9
- import { registerTempPathForExit } from "./temp-cleanup.js";
10
+ import { cleanupPinnedFilePath } from "./replace-file-temp-owner.js";
11
+ import { createMoveStageOwner } from "./move-path-stage.js";
10
12
  export function moveCopyFallbackReasonForRenameError(error, platform = process.platform) {
11
13
  const code = error?.code;
12
14
  if (code === "EXDEV") {
@@ -96,11 +98,12 @@ async function writeAll(handle, buffer, bytesRead) {
96
98
  let offset = 0;
97
99
  while (offset < bytesRead) {
98
100
  const { bytesWritten } = await handle.write(buffer, offset, bytesRead - offset);
101
+ if (bytesWritten <= 0)
102
+ throw new FsSafeError("helper-failed", "move copy made no progress");
99
103
  offset += bytesWritten;
100
104
  }
101
105
  }
102
106
  async function copyRegularFilePinned(params) {
103
- let destinationCreated = false;
104
107
  let openedIdentity;
105
108
  let sourceHandle;
106
109
  try {
@@ -132,9 +135,12 @@ async function copyRegularFilePinned(params) {
132
135
  throw sourceChangedError(params.from);
133
136
  }
134
137
  await assertSourceStillMatches(params.from, openedIdentity);
138
+ const parentGuard = await createAsyncDirectoryGuard(path.dirname(params.to), { bigint: true });
135
139
  const destinationHandle = await fs.open(params.to, fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL, modeBits(params.mode) || 0o666);
136
- destinationCreated = true;
140
+ let destinationIdentity;
137
141
  try {
142
+ destinationIdentity = fsSync.fstatSync(destinationHandle.fd, { bigint: true });
143
+ params.onCreated?.(destinationIdentity);
138
144
  const scratch = Buffer.allocUnsafe(64 * 1024);
139
145
  while (true) {
140
146
  const { bytesRead } = await sourceHandle.read(scratch, 0, scratch.length, null);
@@ -154,16 +160,16 @@ async function copyRegularFilePinned(params) {
154
160
  }
155
161
  await destinationHandle.chmod(modeBits(params.mode));
156
162
  }
163
+ catch (error) {
164
+ await cleanupPinnedFilePath({
165
+ pathname: params.to, handle: destinationHandle, identity: destinationIdentity, parentGuard,
166
+ });
167
+ throw error;
168
+ }
157
169
  finally {
158
170
  await destinationHandle.close();
159
171
  }
160
172
  }
161
- catch (error) {
162
- if (destinationCreated) {
163
- await fs.rm(params.to, { force: true }).catch(() => undefined);
164
- }
165
- throw error;
166
- }
167
173
  finally {
168
174
  await sourceHandle.close();
169
175
  }
@@ -179,6 +185,7 @@ async function copyEntryWithManifest(from, to, options, expectedIdentity) {
179
185
  const target = await fs.readlink(from);
180
186
  const targetType = process.platform === "win32" && fsSync.statSync(from).isDirectory() ? "junction" : undefined;
181
187
  await fs.symlink(target, to, targetType);
188
+ options.onCreated?.(fsSync.lstatSync(to, { bigint: true }));
182
189
  // readlink() is path-based; verify the symlink we copied is still the one
183
190
  // we inspected before letting the staged destination become visible.
184
191
  await assertSourceStillMatches(from, identity);
@@ -186,6 +193,7 @@ async function copyEntryWithManifest(from, to, options, expectedIdentity) {
186
193
  }
187
194
  if (sourceStat.isDirectory()) {
188
195
  await fs.mkdir(to, { mode: modeBits(sourceStat.mode) || 0o755 });
196
+ options.onCreated?.(fsSync.lstatSync(to, { bigint: true }));
189
197
  const children = [];
190
198
  const childNames = [];
191
199
  const directory = await fs.opendir(from);
@@ -198,7 +206,9 @@ async function copyEntryWithManifest(from, to, options, expectedIdentity) {
198
206
  for (const child of childNames) {
199
207
  children.push({
200
208
  name: child,
201
- manifest: await copyEntryWithManifest(path.join(from, child), path.join(to, child), options),
209
+ manifest: await copyEntryWithManifest(path.join(from, child), path.join(to, child), {
210
+ sourceHardlinks: options.sourceHardlinks, budget: options.budget,
211
+ }),
202
212
  });
203
213
  }
204
214
  // Directory traversal is path-based in Node. Treat a changed parent as a
@@ -221,6 +231,7 @@ async function copyEntryWithManifest(from, to, options, expectedIdentity) {
221
231
  mode: sourceStat.mode,
222
232
  rejectHardlinks: options.sourceHardlinks === "reject",
223
233
  to,
234
+ onCreated: options.onCreated,
224
235
  });
225
236
  return { ...copiedIdentity, kind: "leaf" };
226
237
  }
@@ -299,22 +310,22 @@ export async function movePathWithCopyFallback(options) {
299
310
  const sourceIdentity = await assertCopyDestinationOutsideSource(sourcePath, targetPath);
300
311
  const targetDir = path.dirname(targetPath);
301
312
  const staged = path.join(targetDir, `.fs-safe-move-${process.pid}-${randomUUID()}.tmp`);
302
- const unregisterStaged = registerTempPathForExit(staged, { recursive: true });
313
+ const stage = await createMoveStageOwner(staged);
303
314
  try {
304
315
  const manifest = await copyEntryWithManifest(sourcePath, staged, {
305
316
  sourceHardlinks: rejectHardlinks ? "reject" : "allow",
317
+ onCreated: stage.record,
306
318
  ...(rejectHardlinks ? { budget: { discovered: 1 } } : {}),
307
319
  }, sourceIdentity);
308
320
  const cleanupState = createCleanupCopiedEntryState(sourcePath, manifest);
309
- unregisterStaged.setIdentity(fsSync.lstatSync(staged, { bigint: true }));
310
321
  await assertCopyDestinationOutsideSource(sourcePath, targetPath, manifest);
311
322
  await guardedRename({
312
323
  from: staged,
313
324
  to: targetPath,
314
- assertBeforeRename,
325
+ assertBeforeRename: () => { assertBeforeRename(); stage.assertCurrent(); },
315
326
  onSourceInspected,
316
327
  onRenamed: () => {
317
- unregisterStaged();
328
+ stage.published();
318
329
  onRenamed();
319
330
  },
320
331
  });
@@ -325,23 +336,7 @@ export async function movePathWithCopyFallback(options) {
325
336
  }
326
337
  finally {
327
338
  if (!destinationPublished) {
328
- try {
329
- const stagedIdentity = fsSync.lstatSync(staged, { bigint: true });
330
- if (!stagedIdentity.isSymbolicLink())
331
- unregisterStaged.setIdentity(stagedIdentity);
332
- }
333
- catch (error) {
334
- if (error.code === "ENOENT") {
335
- unregisterStaged();
336
- }
337
- }
338
- try {
339
- await fs.rm(staged, { recursive: true, force: true });
340
- unregisterStaged();
341
- }
342
- catch {
343
- // Keep the identity-bound exit cleanup registered for a later retry.
344
- }
339
+ await stage.cleanup();
345
340
  }
346
341
  }
347
342
  }
@@ -0,0 +1,8 @@
1
+ import { FsSafeError } from "./errors.js";
2
+ export declare class MutationAuthorityError extends FsSafeError {
3
+ readonly rejection: unknown;
4
+ constructor(rejection: unknown);
5
+ }
6
+ export declare function composeMutationAssertions(defaultAssertion?: () => void, callAssertion?: () => void): (() => void) | undefined;
7
+ export declare function rethrowMutationAuthorityError(error: unknown): never;
8
+ //# sourceMappingURL=mutation-authority.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mutation-authority.d.ts","sourceRoot":"","sources":["../src/mutation-authority.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,qBAAa,sBAAuB,SAAQ,WAAW;IACzC,QAAQ,CAAC,SAAS,EAAE,OAAO;IAAvC,YAAqB,SAAS,EAAE,OAAO,EAEtC;CACF;AAcD,wBAAgB,yBAAyB,CACvC,gBAAgB,CAAC,EAAE,MAAM,IAAI,EAC7B,aAAa,CAAC,EAAE,MAAM,IAAI,GACzB,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,CAU1B;AAED,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,CAInE"}
@@ -0,0 +1,36 @@
1
+ import { FsSafeError } from "./errors.js";
2
+ export class MutationAuthorityError extends FsSafeError {
3
+ rejection;
4
+ constructor(rejection) {
5
+ super("denied-path", "mutation authority rejected the operation", { cause: rejection });
6
+ this.rejection = rejection;
7
+ }
8
+ }
9
+ function assertSynchronous(returned) {
10
+ if (returned !== null &&
11
+ (typeof returned === "object" || typeof returned === "function") &&
12
+ typeof returned.then === "function") {
13
+ // TypeScript permits async functions for () => void. Do not admit their work.
14
+ void Promise.resolve(returned).catch(() => undefined);
15
+ throw new TypeError("assertBeforeMutation must be synchronous");
16
+ }
17
+ }
18
+ export function composeMutationAssertions(defaultAssertion, callAssertion) {
19
+ if (!defaultAssertion && !callAssertion)
20
+ return undefined;
21
+ return () => {
22
+ try {
23
+ assertSynchronous(defaultAssertion?.());
24
+ assertSynchronous(callAssertion?.());
25
+ }
26
+ catch (error) {
27
+ throw new MutationAuthorityError(error);
28
+ }
29
+ };
30
+ }
31
+ export function rethrowMutationAuthorityError(error) {
32
+ if (error instanceof MutationAuthorityError)
33
+ throw error.rejection;
34
+ // Cleanup failures and indeterminate publication receipts must retain their context.
35
+ throw error;
36
+ }
@@ -1,5 +1,6 @@
1
1
  import type { TarMeterLimits } from "./archive-limits.js";
2
2
  import type { ArchiveMemberKind } from "./archive-plan.js";
3
+ import type { CopyCloneMode } from "./copy-policy.js";
3
4
  export interface NativeFileHash {
4
5
  bytes: number;
5
6
  digest: string;
@@ -29,6 +30,12 @@ export interface NativeCopyResult {
29
30
  errorCode?: string;
30
31
  errorMessage?: string;
31
32
  }
33
+ export interface NativeFileCopyResult {
34
+ fd: number;
35
+ method: "clone" | "copy-file-range" | "copy";
36
+ errorCode?: string;
37
+ errorMessage?: string;
38
+ }
32
39
  export interface NativeOpenBeneathResult {
33
40
  fd: number;
34
41
  containment: "kernel-atomic" | "best-effort";
@@ -69,9 +76,25 @@ export interface NativeWindowsSecurityFacts {
69
76
  aces: NativeWindowsAccessControlEntry[];
70
77
  }
71
78
  export interface NativeBinding {
79
+ /** Windows-only byte transfer; callers retain both admitted descriptors until settlement. */
80
+ copyFileContents?(sourceFd: number, targetFd: number, signal?: AbortSignal): Promise<void>;
81
+ /** Internal: same private, immutable input ownership as the ZIP buffer reader. */
82
+ openTarBufferNative(buffer: Buffer, kind: string, limits: TarMeterLimits, signal?: AbortSignal): Promise<{
83
+ readonly entries: NativeArchiveEntry[];
84
+ readEntry(index: number, maxBytes: number, signal?: AbortSignal): Promise<Buffer>;
85
+ }>;
86
+ /** Internal: input remains private, unpooled and immutable until the reader is released. */
87
+ openZipBufferNative(buffer: Buffer, limits: TarMeterLimits, signal?: AbortSignal): Promise<{
88
+ readonly entries: NativeArchiveEntry[];
89
+ readEntry(index: number, maxBytes: number, signal?: AbortSignal): Promise<Buffer>;
90
+ }>;
91
+ readCloneFileMetadata(paths: string[]): Promise<(Buffer | null)[]>;
92
+ probeTreeClone(parentFd: number): "apfs" | "btrfs" | "refs" | "xfs" | null;
93
+ cloneTree(sourceFd: number | null, parentFd: number, basename: string, concurrency: number, signal?: AbortSignal): Promise<void>;
72
94
  createStagedFile?(parentFd: number, basename: string): number;
73
95
  stagedFileMatches?(parentFd: number, basename: string, fileFd: number): boolean;
74
96
  removeStagedFile?(parentFd: number, basename: string, fileFd: number): "removed" | "name-absent" | "preserved";
97
+ copyFileExclusive?(sourceFd: number, parentFd: number, basename: string, clone: CopyCloneMode, maxBytes: number | undefined, signal: AbortSignal | undefined, sync: boolean): Promise<NativeFileCopyResult>;
75
98
  cloneFileExclusive(sourceFd: number, targetRootFd: number, targetRelPath: string): number;
76
99
  copyFileRangeExclusive(sourceFd: number, targetRootFd: number, targetRelPath: string): Promise<NativeCopyResult>;
77
100
  createPrivateDirectory(path: string): void;
@@ -88,6 +111,6 @@ export interface NativeBinding {
88
111
  removeOwnedTreeSync?(parentFd: number, basename: string, directoryFd: number): NativeOwnedTreeRemovalResult;
89
112
  renameNoReplace(sourceRootFd: number, sourceRelPath: string, targetRootFd: number, targetRelPath: string): void;
90
113
  renameReplace(sourceRootFd: number, sourceRelPath: string, targetRootFd: number, targetRelPath: string): void;
91
- sha256File(fd: number): Promise<NativeFileHash>;
114
+ sha256File(fd: number, maxBytes?: number, signal?: AbortSignal): Promise<NativeFileHash>;
92
115
  }
93
116
  //# sourceMappingURL=native-binding.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"native-binding.d.ts","sourceRoot":"","sources":["../src/native-binding.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"native-binding.d.ts","sourceRoot":"","sources":["../src/native-binding.ts"],"names":[],"mappings":"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"}
@@ -10,7 +10,7 @@ export type NativeFileHandle = {
10
10
  };
11
11
  export declare function nativeOpenFlags(flags: number): number;
12
12
  export declare function writeNativeFd(fd: number, data: Buffer): void;
13
- export declare function writeNativeInput(fd: number, input: PinnedWriteInput, maxBytes?: number): Promise<void>;
13
+ export declare function writeNativeInput(fd: number, input: PinnedWriteInput, maxBytes?: number, assertBeforeMutation?: () => void): Promise<void>;
14
14
  export declare function removeNativeCreatedFileIfStillPinned(params: {
15
15
  parentPath: string;
16
16
  parentFd: number;
@@ -1 +1 @@
1
- {"version":3,"file":"native-operations.d.ts","sourceRoot":"","sources":["../src/native-operations.ts"],"names":[],"mappings":"AAAA,OAAe,EAAE,KAAK,WAAW,EAAE,KAAK,KAAK,EAAE,MAAM,SAAS,CAAC;AAG/D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAI7D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAE1D,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,WAAW,EAAE,oBAAoB,CAAC;IAC3C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,IAAI,IAAI,OAAO,CAAC,KAAK,CAAC,CAAC;IACvB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,QAAQ,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5E,CAAC;AAEF,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAOrD;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAS5D;AAED,wBAAsB,gBAAgB,CACpC,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,gBAAgB,EACvB,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CAgBf;AAsBD,wBAAgB,oCAAoC,CAAC,MAAM,EAAE;IAC3D,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,WAAW,CAAC;CACvB,GAAG,IAAI,CAqBP;AAED,wBAAsB,yBAAyB,CAC7C,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,gBAAgB,GAAG,SAAS,CAAC,CAwDvC"}
1
+ {"version":3,"file":"native-operations.d.ts","sourceRoot":"","sources":["../src/native-operations.ts"],"names":[],"mappings":"AAAA,OAAe,EAAE,KAAK,WAAW,EAAE,KAAK,KAAK,EAAE,MAAM,SAAS,CAAC;AAG/D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAI7D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAI1D,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,WAAW,EAAE,oBAAoB,CAAC;IAC3C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,IAAI,IAAI,OAAO,CAAC,KAAK,CAAC,CAAC;IACvB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,QAAQ,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5E,CAAC;AAEF,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAOrD;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAS5D;AAED,wBAAsB,gBAAgB,CACpC,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,gBAAgB,EACvB,QAAQ,CAAC,EAAE,MAAM,EACjB,oBAAoB,CAAC,EAAE,MAAM,IAAI,GAChC,OAAO,CAAC,IAAI,CAAC,CAoBf;AAsBD,wBAAgB,oCAAoC,CAAC,MAAM,EAAE;IAC3D,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,WAAW,CAAC;CACvB,GAAG,IAAI,CAqBP;AAED,wBAAsB,yBAAyB,CAC7C,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,gBAAgB,GAAG,SAAS,CAAC,CAwDvC"}
@@ -4,6 +4,8 @@ import path from "node:path";
4
4
  import { FsSafeError } from "./errors.js";
5
5
  import { sameFileIdentityForCleanup } from "./file-identity.js";
6
6
  import { getNativeBinding } from "./native.js";
7
+ import { writeAllToFile } from "./write-file-handle.js";
8
+ import { writeCopyFileToFd } from "./copy-file-input.js";
7
9
  export function nativeOpenFlags(flags) {
8
10
  const closeOnExec = fsSync.constants.O_CLOEXEC;
9
11
  return (flags |
@@ -20,21 +22,25 @@ export function writeNativeFd(fd, data) {
20
22
  offset += written;
21
23
  }
22
24
  }
23
- export async function writeNativeInput(fd, input, maxBytes) {
25
+ export async function writeNativeInput(fd, input, maxBytes, assertBeforeMutation) {
26
+ if (input.kind === "file") {
27
+ await writeCopyFileToFd(fd, input, maxBytes, assertBeforeMutation);
28
+ return;
29
+ }
24
30
  let bytes = 0;
25
- const write = (data) => {
31
+ const write = async (data) => {
26
32
  bytes += data.byteLength;
27
33
  if (maxBytes !== undefined && bytes > maxBytes) {
28
34
  throw new FsSafeError("too-large", `file exceeds limit of ${maxBytes} bytes (got at least ${bytes})`);
29
35
  }
30
- writeNativeFd(fd, data);
36
+ await writeAllToFile(fd, data, { assertBeforeMutation });
31
37
  };
32
38
  if (input.kind === "buffer") {
33
- write(typeof input.data === "string" ? Buffer.from(input.data, input.encoding ?? "utf8") : input.data);
39
+ await write(typeof input.data === "string" ? Buffer.from(input.data, input.encoding ?? "utf8") : input.data);
34
40
  }
35
41
  else {
36
42
  for await (const chunk of input.stream) {
37
- write(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
43
+ await write(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
38
44
  }
39
45
  }
40
46
  }
@@ -1 +1 @@
1
- {"version":3,"file":"native-pinned-write-windows.d.ts","sourceRoot":"","sources":["../src/native-pinned-write-windows.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAEnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAM3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAG3D,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,IAAI,CAAC,gBAAgB,EAAE,KAAK,GAAG,KAAK,CAAC,EAC3C,KAAK,EAAE,IAAI,CAAC,gBAAgB,EAAE,KAAK,GAAG,KAAK,CAAC,GAC3C,OAAO,CAET;AAWD,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,aAAa,EACtB,MAAM,EAAE,iBAAiB,EACzB,IAAI,EAAE,UAAU,EAChB,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,sBAAsB,GAClC,OAAO,CAAC,gBAAgB,CAAC,CAiF3B"}
1
+ {"version":3,"file":"native-pinned-write-windows.d.ts","sourceRoot":"","sources":["../src/native-pinned-write-windows.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAEnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAM3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAI3D,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,IAAI,CAAC,gBAAgB,EAAE,KAAK,GAAG,KAAK,CAAC,EAC3C,KAAK,EAAE,IAAI,CAAC,gBAAgB,EAAE,KAAK,GAAG,KAAK,CAAC,GAC3C,OAAO,CAET;AAWD,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,aAAa,EACtB,MAAM,EAAE,iBAAiB,EACzB,IAAI,EAAE,UAAU,EAChB,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,sBAAsB,GAClC,OAAO,CAAC,gBAAgB,CAAC,CAyF3B"}
@@ -1,9 +1,11 @@
1
1
  import { syncFileBestEffortSync } from "./file-sync.js";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import fsSync, {} from "node:fs";
4
+ import path from "node:path";
4
5
  import { FsSafeError } from "./errors.js";
5
6
  import { nativeOpenFlags, removeNativeCreatedFileIfStillPinned, writeNativeInput, } from "./native-operations.js";
6
7
  import { inspectFileIdentitySync } from "./strict-file-identity.js";
8
+ import { assertFinalSymlinkRejected } from "./root-symlink-policy.js";
7
9
  export function sameNativeIdentity(left, right) {
8
10
  return left.dev === right.dev && left.ino === right.ino;
9
11
  }
@@ -27,6 +29,7 @@ export async function runPinnedWriteWindows(binding, params, root, parentFd, par
27
29
  let completed = false;
28
30
  try {
29
31
  tempName = `.fs-safe-${randomUUID()}.tmp`;
32
+ params.assertBeforeMutation?.();
30
33
  tempFd = binding.openBeneath(parentFd, tempName, nativeOpenFlags(fsSync.constants.O_WRONLY | fsSync.constants.O_CREAT | fsSync.constants.O_EXCL)).fd;
31
34
  const verificationIdentity = inspectFileIdentitySync(() => fsSync.fstatSync(tempFd, { bigint: true }));
32
35
  tempIdentity = verificationIdentity;
@@ -34,9 +37,13 @@ export async function runPinnedWriteWindows(binding, params, root, parentFd, par
34
37
  // can remove owner access. Keep the unpublished inode private and
35
38
  // reopenable until the published name has been identity-fenced.
36
39
  fsSync.fchmodSync(tempFd, 0o600);
37
- await writeNativeInput(tempFd, params.input, params.maxBytes);
40
+ await writeNativeInput(tempFd, params.input, params.maxBytes, params.assertBeforeMutation);
38
41
  if (params.sync !== false)
39
42
  syncFileBestEffortSync(tempFd);
43
+ if (params.input.kind === "file")
44
+ await params.input.verifySource();
45
+ assertFinalSymlinkRejected(path.join(parentPath, params.basename), params.rejectFinalSymlink);
46
+ params.assertBeforeMutation?.();
40
47
  if (params.overwrite === false) {
41
48
  binding.renameNoReplace(parentFd, tempName, parentFd, params.basename);
42
49
  }
@@ -44,6 +51,7 @@ export async function runPinnedWriteWindows(binding, params, root, parentFd, par
44
51
  binding.renameReplace(parentFd, tempName, parentFd, params.basename);
45
52
  }
46
53
  renamed = true;
54
+ params.onPublished?.(verificationIdentity);
47
55
  targetFd = binding.openBeneath(parentFd, params.basename, nativeOpenFlags(fsSync.constants.O_RDONLY)).fd;
48
56
  const targetStat = inspectFileIdentitySync(() => fsSync.fstatSync(targetFd, { bigint: true }), verificationIdentity);
49
57
  const targetIdentity = binding.fstatIdentity(targetFd);
@@ -61,12 +69,15 @@ export async function runPinnedWriteWindows(binding, params, root, parentFd, par
61
69
  catch (error) {
62
70
  closeWriteFd(targetFd);
63
71
  targetFd = undefined;
64
- removeNativeCreatedFileIfStillPinned({
65
- parentPath,
66
- parentFd,
67
- basename: params.basename,
68
- created: tempIdentity,
69
- });
72
+ // Copies preserve the published name for caller-owned recovery.
73
+ if (params.input.kind !== "file") {
74
+ removeNativeCreatedFileIfStillPinned({
75
+ parentPath,
76
+ parentFd,
77
+ basename: params.basename,
78
+ created: tempIdentity,
79
+ });
80
+ }
70
81
  throw error;
71
82
  }
72
83
  if (params.sync !== false)
@@ -1 +1 @@
1
- {"version":3,"file":"native-pinned-write.d.ts","sourceRoot":"","sources":["../src/native-pinned-write.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAG3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAI3D,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CA0FvH"}
1
+ {"version":3,"file":"native-pinned-write.d.ts","sourceRoot":"","sources":["../src/native-pinned-write.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAG3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAI3D,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CA2FvH"}
@@ -100,6 +100,7 @@ export async function runPinnedWriteNative(binding, params) {
100
100
  throw new FsSafeError("path-mismatch", "root path changed during native write");
101
101
  }
102
102
  if (params.mkdir) {
103
+ params.assertBeforeMutation?.();
103
104
  binding.mkdirBeneath(root.fd, params.relativeParentPath, 0o777);
104
105
  }
105
106
  parentFd = binding.openBeneath(root.fd, params.relativeParentPath, directoryFlags).fd;
@@ -8,14 +8,14 @@ export type NativeStagingBinding = NativeBinding & Required<Pick<NativeBinding,
8
8
  export declare function assertNativeStaging(binding: NativeBinding): asserts binding is NativeStagingBinding;
9
9
  declare class NativeStagedFile implements StagedFile {
10
10
  #private;
11
- constructor(binding: NativeStagingBinding, parentFd: number, directory: StagedFileReceipt["directory"], portableNames: boolean, publishedMode: number, sync: boolean);
12
- static create(binding: NativeStagingBinding, parentFd: number, directory: StagedFileReceipt["directory"], input: PinnedWriteInput, mode: number, maxBytes?: number, portableNames?: boolean, sync?: boolean): Promise<NativeStagedFile>;
11
+ constructor(binding: NativeStagingBinding, parentFd: number, directory: StagedFileReceipt["directory"], portableNames: boolean, publishedMode: number, sync: boolean, assertBeforeMutation?: () => void);
12
+ static create(binding: NativeStagingBinding, parentFd: number, directory: StagedFileReceipt["directory"], input: PinnedWriteInput, mode: number, maxBytes?: number, portableNames?: boolean, sync?: boolean, assertBeforeMutation?: () => void): Promise<NativeStagedFile>;
13
13
  static write(binding: NativeStagingBinding, parentFd: number, directory: StagedFileReceipt["directory"], params: PinnedWriteParams, parentGuard: AnyAsyncDirectoryGuard): Promise<FileIdentityStat>;
14
14
  get receipt(): StagedFileReceipt;
15
15
  assertCurrent(): Promise<void>;
16
16
  publish(basename: string, options: {
17
17
  overwrite: boolean;
18
- }): Promise<PublishedFileReceipt>;
18
+ }, onPublished?: PinnedWriteParams["onPublished"]): Promise<PublishedFileReceipt>;
19
19
  cleanup(): Promise<StagedFileCleanupReceipt>;
20
20
  [Symbol.asyncDispose](): Promise<void>;
21
21
  }
@@ -1 +1 @@
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;AAKlE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAEnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,OAAO,KAAK,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAE7E,OAAO,KAAK,EACV,oBAAoB,EACpB,UAAU,EACV,wBAAwB,EAGxB,iBAAiB,EAClB,MAAM,wBAAwB,CAAC;AAEhC,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,CAUnG;AA6BD,cAAM,gBAAiB,YAAW,UAAU;;IAW1C,YACE,OAAO,EAAE,oBAAoB,EAC7B,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,iBAAiB,CAAC,WAAW,CAAC,EACzC,aAAa,EAAE,OAAO,EACtB,aAAa,EAAE,MAAM,EACrB,IAAI,EAAE,OAAO,EAQd;IAGD,OAAa,MAAM,CACjB,OAAO,EAAE,oBAAoB,EAC7B,QAAQ,EAAE,MAAM,EAChB,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,GACV,OAAO,CAAC,gBAAgB,CAAC,CAc3B;IAED,OAAa,KAAK,CAChB,OAAO,EAAE,oBAAoB,EAC7B,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,iBAAiB,CAAC,WAAW,CAAC,EACzC,MAAM,EAAE,iBAAiB,EACzB,WAAW,EAAE,sBAAsB,GAClC,OAAO,CAAC,gBAAgB,CAAC,CAU3B;IAED,IAAI,OAAO,IAAI,iBAAiB,CAK/B;IAoFK,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAEnC;IAEK,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,SAAS,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAqD9F;IA6CK,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;IACrC,OAAO,EAAE,MAAM,GAAG,UAAU,CAAC;IAC7B,6EAA6E;IAC7E,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,GAAG,OAAO,CAAC,UAAU,CAAC,CAUtB"}
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,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAGnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAGzD,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;AAEhC,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,CAUnG;AA6BD,cAAM,gBAAiB,YAAW,UAAU;;IAa1C,YACE,OAAO,EAAE,oBAAoB,EAC7B,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,iBAAiB,CAAC,WAAW,CAAC,EACzC,aAAa,EAAE,OAAO,EACtB,aAAa,EAAE,MAAM,EACrB,IAAI,EAAE,OAAO,EACb,oBAAoB,CAAC,EAAE,MAAM,IAAI,EASlC;IAGD,OAAa,MAAM,CACjB,OAAO,EAAE,oBAAoB,EAC7B,QAAQ,EAAE,MAAM,EAChB,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,GAChC,OAAO,CAAC,gBAAgB,CAAC,CAc3B;IAED,OAAa,KAAK,CAChB,OAAO,EAAE,oBAAoB,EAC7B,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,iBAAiB,CAAC,WAAW,CAAC,EACzC,MAAM,EAAE,iBAAiB,EACzB,WAAW,EAAE,sBAAsB,GAClC,OAAO,CAAC,gBAAgB,CAAC,CAc3B;IAED,IAAI,OAAO,IAAI,iBAAiB,CAK/B;IA8FK,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,CAyD/B;IA6CK,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;IACrC,OAAO,EAAE,MAAM,GAAG,UAAU,CAAC;IAC7B,6EAA6E;IAC7E,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,GAAG,OAAO,CAAC,UAAU,CAAC,CAUtB"}