@openclaw/fs-safe 0.15.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +86 -0
- package/README.md +36 -7
- package/dist/absolute-path.d.ts.map +1 -1
- package/dist/absolute-path.js +2 -8
- package/dist/advanced.d.ts +3 -0
- package/dist/advanced.d.ts.map +1 -1
- package/dist/advanced.js +3 -0
- package/dist/archive-deadline.d.ts.map +1 -1
- package/dist/archive-deadline.js +22 -23
- package/dist/archive-kind.d.ts +0 -1
- package/dist/archive-kind.d.ts.map +1 -1
- package/dist/archive-kind.js +5 -17
- package/dist/archive-merge.d.ts +1 -0
- package/dist/archive-merge.d.ts.map +1 -1
- package/dist/archive-merge.js +4 -4
- package/dist/archive-native.d.ts +1 -0
- package/dist/archive-native.d.ts.map +1 -1
- package/dist/archive-native.js +1 -0
- package/dist/archive-options.d.ts +2 -0
- package/dist/archive-options.d.ts.map +1 -1
- package/dist/archive-parser.wasm +0 -0
- package/dist/archive-read.d.ts.map +1 -1
- package/dist/archive-read.js +6 -7
- package/dist/archive-tar-stream.d.ts +3 -0
- package/dist/archive-tar-stream.d.ts.map +1 -1
- package/dist/archive-tar-stream.js +56 -37
- package/dist/archive-tar-wasm.d.ts +16 -4
- package/dist/archive-tar-wasm.d.ts.map +1 -1
- package/dist/archive-tar-wasm.js +134 -34
- package/dist/archive-zip-count.d.ts.map +1 -1
- package/dist/archive-zip-count.js +21 -1
- package/dist/archive-zip-directory.d.ts.map +1 -1
- package/dist/archive-zip-directory.js +23 -1
- package/dist/archive-zip-loader.d.ts +2 -0
- package/dist/archive-zip-loader.d.ts.map +1 -1
- package/dist/archive-zip-loader.js +7 -0
- package/dist/archive-zip-names.d.ts.map +1 -1
- package/dist/archive-zip-names.js +7 -2
- package/dist/archive.d.ts.map +1 -1
- package/dist/archive.js +14 -9
- package/dist/byte-view.d.ts +3 -0
- package/dist/byte-view.d.ts.map +1 -0
- package/dist/byte-view.js +13 -0
- package/dist/clone-metadata.d.ts +1 -0
- package/dist/clone-metadata.d.ts.map +1 -1
- package/dist/clone-metadata.js +6 -2
- package/dist/create-directory.d.ts +20 -0
- package/dist/create-directory.d.ts.map +1 -0
- package/dist/create-directory.js +130 -0
- package/dist/create-file-async.d.ts +7 -0
- package/dist/create-file-async.d.ts.map +1 -0
- package/dist/create-file-async.js +121 -0
- package/dist/create-file.d.ts +8 -0
- package/dist/create-file.d.ts.map +1 -0
- package/dist/create-file.js +190 -0
- package/dist/create-owned-file.d.ts +8 -0
- package/dist/create-owned-file.d.ts.map +1 -0
- package/dist/create-owned-file.js +16 -0
- package/dist/create.d.ts +4 -0
- package/dist/create.d.ts.map +1 -0
- package/dist/create.js +2 -0
- package/dist/creation-darwin.d.ts +6 -0
- package/dist/creation-darwin.d.ts.map +1 -0
- package/dist/creation-darwin.js +70 -0
- package/dist/creation-file-state.d.ts +19 -0
- package/dist/creation-file-state.d.ts.map +1 -0
- package/dist/creation-file-state.js +118 -0
- package/dist/creation-path.d.ts +21 -0
- package/dist/creation-path.d.ts.map +1 -0
- package/dist/creation-path.js +71 -0
- package/dist/creation-permissions.d.ts +19 -0
- package/dist/creation-permissions.d.ts.map +1 -0
- package/dist/creation-permissions.js +125 -0
- package/dist/directory-durability.d.ts +7 -7
- package/dist/directory-durability.d.ts.map +1 -1
- package/dist/directory-durability.js +22 -80
- package/dist/directory-guard.d.ts +3 -0
- package/dist/directory-guard.d.ts.map +1 -1
- package/dist/directory-mode-node.d.ts +2 -0
- package/dist/directory-mode-node.d.ts.map +1 -1
- package/dist/directory-mode-node.js +8 -0
- package/dist/directory-receipt.d.ts +24 -0
- package/dist/directory-receipt.d.ts.map +1 -0
- package/dist/directory-receipt.js +123 -0
- package/dist/file-cleanup.d.ts +20 -0
- package/dist/file-cleanup.d.ts.map +1 -0
- package/dist/file-cleanup.js +81 -0
- package/dist/file-contents.d.ts +6 -0
- package/dist/file-contents.d.ts.map +1 -0
- package/dist/file-contents.js +40 -0
- package/dist/file-hash.d.ts.map +1 -1
- package/dist/file-hash.js +16 -4
- package/dist/file-lock-sync-root-acquire.d.ts.map +1 -1
- package/dist/file-lock-sync-root-acquire.js +3 -0
- package/dist/file-lock-sync-root-held.d.ts +1 -2
- package/dist/file-lock-sync-root-held.d.ts.map +1 -1
- package/dist/file-lock-sync-root-held.js +7 -5
- package/dist/file-lock-sync-stale-admission.d.ts.map +1 -1
- package/dist/file-lock-sync-stale-admission.js +3 -0
- package/dist/file-lock-sync.d.ts.map +1 -1
- package/dist/file-lock-sync.js +8 -11
- package/dist/file-observation.d.ts +1 -1
- package/dist/file-observation.d.ts.map +1 -1
- package/dist/file-store-boundary.d.ts +2 -6
- package/dist/file-store-boundary.d.ts.map +1 -1
- package/dist/file-store-boundary.js +3 -9
- package/dist/file-store-sync-write.d.ts.map +1 -1
- package/dist/file-store-sync-write.js +2 -5
- package/dist/file-store.js +3 -3
- package/dist/guarded-mkdir.d.ts +1 -0
- package/dist/guarded-mkdir.d.ts.map +1 -1
- package/dist/guarded-mkdir.js +27 -19
- package/dist/install-path.d.ts.map +1 -1
- package/dist/install-path.js +2 -5
- package/dist/json-durable-queue-ownership.d.ts.map +1 -1
- package/dist/json-durable-queue-ownership.js +2 -6
- package/dist/json-durable-queue-paths.d.ts.map +1 -1
- package/dist/json-durable-queue-paths.js +2 -24
- package/dist/json-durable-queue.d.ts.map +1 -1
- package/dist/json-durable-queue.js +10 -9
- package/dist/json.d.ts.map +1 -1
- package/dist/json.js +32 -75
- package/dist/local-roots.d.ts.map +1 -1
- package/dist/local-roots.js +19 -21
- package/dist/move-path-cleanup.d.ts +5 -19
- package/dist/move-path-cleanup.d.ts.map +1 -1
- package/dist/move-path-cleanup.js +57 -21
- package/dist/move-path.d.ts.map +1 -1
- package/dist/move-path.js +63 -40
- package/dist/native-binding.d.ts +11 -1
- package/dist/native-binding.d.ts.map +1 -1
- package/dist/native-fallback-warning.d.ts +4 -0
- package/dist/native-fallback-warning.d.ts.map +1 -0
- package/dist/native-fallback-warning.js +11 -0
- package/dist/native-operations.d.ts +0 -2
- package/dist/native-operations.d.ts.map +1 -1
- package/dist/native-operations.js +0 -24
- package/dist/native-parent-admission.d.ts +2 -0
- package/dist/native-parent-admission.d.ts.map +1 -1
- package/dist/native-parent-admission.js +3 -2
- package/dist/native-pinned-write-windows.d.ts +1 -1
- package/dist/native-pinned-write-windows.d.ts.map +1 -1
- package/dist/native-pinned-write-windows.js +173 -28
- package/dist/native-pinned-write.d.ts.map +1 -1
- package/dist/native-pinned-write.js +19 -3
- package/dist/native-policy-parent-windows.d.ts.map +1 -1
- package/dist/native-policy-parent-windows.js +15 -6
- package/dist/native-staged-file.d.ts +5 -3
- package/dist/native-staged-file.d.ts.map +1 -1
- package/dist/native-staged-file.js +90 -40
- package/dist/native.js +2 -2
- package/dist/opened-realpath.d.ts.map +1 -1
- package/dist/opened-realpath.js +11 -2
- package/dist/owner-dacl.d.ts.map +1 -1
- package/dist/owner-dacl.js +10 -4
- package/dist/path.d.ts.map +1 -1
- package/dist/path.js +2 -1
- package/dist/permissions.d.ts.map +1 -1
- package/dist/permissions.js +3 -17
- package/dist/pinned-write-input.d.ts +4 -0
- package/dist/pinned-write-input.d.ts.map +1 -0
- package/dist/pinned-write-input.js +35 -0
- package/dist/pinned-write-mode.d.ts +5 -0
- package/dist/pinned-write-mode.d.ts.map +1 -0
- package/dist/pinned-write-mode.js +31 -0
- package/dist/pinned-write-staged.d.ts +6 -0
- package/dist/pinned-write-staged.d.ts.map +1 -0
- package/dist/pinned-write-staged.js +186 -0
- package/dist/pinned-write-types.d.ts +3 -0
- package/dist/pinned-write-types.d.ts.map +1 -1
- package/dist/pinned-write.d.ts.map +1 -1
- package/dist/pinned-write.js +41 -147
- package/dist/private-directory.d.ts.map +1 -1
- package/dist/private-directory.js +18 -4
- package/dist/private-producer-handoff-sync.d.ts +14 -0
- package/dist/private-producer-handoff-sync.d.ts.map +1 -0
- package/dist/private-producer-handoff-sync.js +114 -0
- package/dist/private-producer-handoff.d.ts +22 -4
- package/dist/private-producer-handoff.d.ts.map +1 -1
- package/dist/private-producer-handoff.js +140 -77
- package/dist/publish-copy-stage.d.ts +2 -1
- package/dist/publish-copy-stage.d.ts.map +1 -1
- package/dist/publish-copy-stage.js +16 -7
- package/dist/publish-file.d.ts +2 -2
- package/dist/publish-file.d.ts.map +1 -1
- package/dist/publish-file.js +58 -98
- package/dist/regular-file.d.ts.map +1 -1
- package/dist/regular-file.js +35 -44
- package/dist/replace-file-copy-fallback.d.ts.map +1 -1
- package/dist/replace-file-copy-fallback.js +28 -26
- package/dist/replace-file-copy-source.d.ts.map +1 -1
- package/dist/replace-file-copy-source.js +13 -22
- package/dist/replace-file-descriptor.d.ts.map +1 -1
- package/dist/replace-file-descriptor.js +10 -16
- package/dist/replace-file-temp-owner.d.ts +0 -7
- package/dist/replace-file-temp-owner.d.ts.map +1 -1
- package/dist/replace-file-temp-owner.js +9 -60
- package/dist/replace-file.d.ts.map +1 -1
- package/dist/replace-file.js +9 -13
- package/dist/root-create-input.d.ts +2 -1
- package/dist/root-create-input.d.ts.map +1 -1
- package/dist/root-create-input.js +13 -4
- package/dist/root-directory-creation.d.ts +3 -3
- package/dist/root-directory-creation.d.ts.map +1 -1
- package/dist/root-directory-creation.js +15 -3
- package/dist/root-directory-list.d.ts.map +1 -1
- package/dist/root-directory-list.js +20 -3
- package/dist/root-file-final-admission.d.ts +1 -1
- package/dist/root-file-final-admission.d.ts.map +1 -1
- package/dist/root-file-final-admission.js +5 -2
- package/dist/root-file.d.ts.map +1 -1
- package/dist/root-file.js +3 -2
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +78 -27
- package/dist/root-move-noreplace.d.ts +2 -0
- package/dist/root-move-noreplace.d.ts.map +1 -1
- package/dist/root-move-noreplace.js +22 -13
- package/dist/root-options.d.ts +12 -4
- package/dist/root-options.d.ts.map +1 -1
- package/dist/root-path-stat.d.ts.map +1 -1
- package/dist/root-path-stat.js +59 -7
- package/dist/root-read-admission.d.ts.map +1 -1
- package/dist/root-read-admission.js +7 -2
- package/dist/root-remove.d.ts.map +1 -1
- package/dist/root-remove.js +15 -1
- package/dist/root-write-publication.js +1 -1
- package/dist/secret-file.d.ts.map +1 -1
- package/dist/secret-file.js +1 -0
- package/dist/secure-file-windows.d.ts +6 -0
- package/dist/secure-file-windows.d.ts.map +1 -1
- package/dist/secure-file-windows.js +34 -117
- package/dist/secure-file.js +2 -2
- package/dist/sidecar-lock-acquire.d.ts.map +1 -1
- package/dist/sidecar-lock-acquire.js +4 -6
- package/dist/sidecar-lock-handle.d.ts +3 -0
- package/dist/sidecar-lock-handle.d.ts.map +1 -1
- package/dist/sidecar-lock-handle.js +6 -0
- package/dist/sidecar-lock-reclaim.d.ts +1 -1
- package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
- package/dist/sidecar-lock-reclaim.js +11 -8
- package/dist/sidecar-lock-root.d.ts.map +1 -1
- package/dist/sidecar-lock-root.js +2 -1
- package/dist/sidecar-lock.d.ts.map +1 -1
- package/dist/sidecar-lock.js +3 -5
- package/dist/staged-directory.d.ts +2 -2
- package/dist/staged-directory.d.ts.map +1 -1
- package/dist/staged-directory.js +6 -6
- package/dist/staged-file-settlement.d.ts +17 -0
- package/dist/staged-file-settlement.d.ts.map +1 -0
- package/dist/staged-file-settlement.js +57 -0
- package/dist/strict-file-identity.d.ts +1 -1
- package/dist/strict-file-identity.d.ts.map +1 -1
- package/dist/strict-file-identity.js +9 -9
- package/dist/symlink-parents.d.ts.map +1 -1
- package/dist/symlink-parents.js +2 -27
- package/dist/temp-workspace-owner.js +4 -4
- package/dist/unicode-path.d.ts.map +1 -1
- package/dist/unicode-path.js +3 -0
- package/dist/walk.d.ts.map +1 -1
- package/dist/walk.js +4 -2
- package/dist/windows-owner.d.ts.map +1 -1
- package/dist/windows-owner.js +2 -1
- package/dist/windows-security-bridge.cs +336 -0
- package/dist/windows-security-bridge.ps1 +15 -0
- package/dist/windows-security-command.d.ts +26 -0
- package/dist/windows-security-command.d.ts.map +1 -0
- package/dist/windows-security-command.js +363 -0
- package/dist/windows-security-facts.d.ts +6 -0
- package/dist/windows-security-facts.d.ts.map +1 -0
- package/dist/windows-security-facts.js +108 -0
- package/dist/write-file-handle.d.ts +7 -0
- package/dist/write-file-handle.d.ts.map +1 -1
- package/dist/write-file-handle.js +23 -0
- package/dist/write-open-flags.d.ts.map +1 -1
- package/dist/write-open-flags.js +1 -8
- package/dist/write-queue.d.ts.map +1 -1
- package/dist/write-queue.js +1 -4
- package/docs/advanced.md +71 -2
- package/docs/archive.md +102 -39
- package/docs/atomic.md +29 -5
- package/docs/config.md +6 -2
- package/docs/contributing.md +48 -4
- package/docs/copy.md +2 -0
- package/docs/creation.md +132 -0
- package/docs/durability.md +59 -0
- package/docs/file-contents.md +68 -0
- package/docs/install.md +31 -7
- package/docs/json.md +5 -4
- package/docs/local-roots.md +2 -0
- package/docs/migrating-to-0.5.md +15 -6
- package/docs/migrating-to-0.6.md +9 -4
- package/docs/mutation-policy-proof.md +5 -3
- package/docs/native-helper.md +22 -9
- package/docs/native.md +47 -15
- package/docs/path.md +4 -4
- package/docs/permissions.md +37 -14
- package/docs/public-api.md +5 -0
- package/docs/quickstart.md +1 -1
- package/docs/reading.md +2 -2
- package/docs/regular-file.md +3 -0
- package/docs/root.md +43 -0
- package/docs/secret-file.md +11 -2
- package/docs/secure-file.md +9 -4
- package/docs/sidecar-lock.md +14 -5
- package/docs/staged-file.md +9 -3
- package/docs/store.md +3 -1
- package/docs/temp.md +4 -1
- package/docs/types.md +18 -2
- package/docs/walk.md +7 -0
- package/docs/writing.md +80 -7
- package/package.json +18 -15
package/dist/publish-file.js
CHANGED
|
@@ -4,6 +4,7 @@ import fs, {} from "node:fs/promises";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { pinDirectory, } from "./directory-durability.js";
|
|
6
6
|
import { FsSafeError } from "./errors.js";
|
|
7
|
+
import { ownDirectoryReceipt } from "./directory-receipt.js";
|
|
7
8
|
import { hashFileHandle } from "./file-hash.js";
|
|
8
9
|
import { sameFileIdentity, sameFileIdentityForCleanup, } from "./file-identity.js";
|
|
9
10
|
import { syncFileBestEffortSync } from "./file-sync.js";
|
|
@@ -14,6 +15,8 @@ import { directorySyncFailure, publicationFailure, rememberCreatedTarget, } from
|
|
|
14
15
|
import { admitStandalonePublicationPath } from "./standalone-publication-path.js";
|
|
15
16
|
import { getFsSafeTestHooks } from "./test-hooks.js";
|
|
16
17
|
import { assertNoWindowsPathAlias } from "./windows-path-alias.js";
|
|
18
|
+
import { hasErrorCode } from "./file-cleanup.js";
|
|
19
|
+
import { writeAllToFile } from "./write-file-handle.js";
|
|
17
20
|
const HARDLINK_FALLBACK_CODES = new Set(["EPERM", "EXDEV", "ENOTSUP", "EOPNOTSUPP", "ENOSYS"]);
|
|
18
21
|
const NATIVE_COPY_FALLBACK_CODES = new Set(["EINVAL", "ENOSYS", "ENOTSUP", "EOPNOTSUPP", "EPERM", "EXDEV"]);
|
|
19
22
|
export function isHardlinkFallbackError(error) {
|
|
@@ -40,7 +43,7 @@ async function openNativeParent(filePath) {
|
|
|
40
43
|
throw error;
|
|
41
44
|
}
|
|
42
45
|
}
|
|
43
|
-
|
|
46
|
+
function assertPinnedSourceCurrent(params) {
|
|
44
47
|
// Windows file indexes can exceed Number.MAX_SAFE_INTEGER, so publication
|
|
45
48
|
// fences must compare bigint stats instead of rounded numeric identities.
|
|
46
49
|
const opened = fsSync.fstatSync(params.handle.fd, { bigint: true });
|
|
@@ -53,6 +56,23 @@ async function assertPinnedSourceCurrent(params) {
|
|
|
53
56
|
throw new FsSafeError("path-mismatch", "publication source changed during operation");
|
|
54
57
|
}
|
|
55
58
|
}
|
|
59
|
+
function assertPublishedTargetCurrent(targetPath, identity, message) {
|
|
60
|
+
const current = fsSync.lstatSync(targetPath, { bigint: true });
|
|
61
|
+
if (current.isSymbolicLink() || !current.isFile() || !sameFileIdentity(current, identity)) {
|
|
62
|
+
throw new FsSafeError("path-mismatch", message);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function assertRenamedSourceAbsent(sourcePath) {
|
|
66
|
+
try {
|
|
67
|
+
fsSync.lstatSync(sourcePath);
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
if (hasErrorCode(error, "ENOENT"))
|
|
71
|
+
return;
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
throw new FsSafeError("path-mismatch", "no-replace publication source still exists");
|
|
75
|
+
}
|
|
56
76
|
async function copyPinnedSource(params) {
|
|
57
77
|
if (params.native && params.targetNativeParent) {
|
|
58
78
|
const closeFd = captureNativeFdClose(params.native);
|
|
@@ -130,15 +150,9 @@ async function copyPinnedSource(params) {
|
|
|
130
150
|
if (bytesRead === 0) {
|
|
131
151
|
break;
|
|
132
152
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
const result = await target.write(buffer, written, bytesRead - written, position + written);
|
|
137
|
-
if (result.bytesWritten <= 0) {
|
|
138
|
-
throw new FsSafeError("helper-failed", "exclusive publication copy made no progress");
|
|
139
|
-
}
|
|
140
|
-
written += result.bytesWritten;
|
|
141
|
-
}
|
|
153
|
+
const chunk = buffer.subarray(0, bytesRead);
|
|
154
|
+
hash.update(chunk);
|
|
155
|
+
await writeAllToFile(target, chunk, { position });
|
|
142
156
|
position += bytesRead;
|
|
143
157
|
}
|
|
144
158
|
await target.sync();
|
|
@@ -183,8 +197,7 @@ export async function publishFileExclusive(params) {
|
|
|
183
197
|
const targetPathInput = admitStandalonePublicationPath(params.targetPath, "publication target uses a Windows filesystem namespace alias");
|
|
184
198
|
const parentReceiptInput = params.parentReceipt;
|
|
185
199
|
const parentReceipt = parentReceiptInput
|
|
186
|
-
?
|
|
187
|
-
identity: parentReceiptInput.identity }
|
|
200
|
+
? ownDirectoryReceipt(parentReceiptInput)
|
|
188
201
|
: undefined;
|
|
189
202
|
if (parentReceipt) {
|
|
190
203
|
assertNoWindowsPathAlias(parentReceipt.path, "filesystem", "publication parent uses a Windows filesystem namespace alias");
|
|
@@ -208,6 +221,7 @@ export async function publishFileExclusive(params) {
|
|
|
208
221
|
let parent;
|
|
209
222
|
let sourceNativeParent;
|
|
210
223
|
let targetNativeParent;
|
|
224
|
+
let copiedTarget;
|
|
211
225
|
const strategy = params.strategy;
|
|
212
226
|
const failure = {
|
|
213
227
|
phase: strategy === "rename-noreplace" ? "rename-create" : "hardlink-create",
|
|
@@ -232,8 +246,20 @@ export async function publishFileExclusive(params) {
|
|
|
232
246
|
: sourceIdentity))) {
|
|
233
247
|
throw new FsSafeError("path-mismatch", "publication source identity did not match");
|
|
234
248
|
}
|
|
249
|
+
const assertSourceCurrent = () => assertPinnedSourceCurrent({ sourcePath, handle: source, identity: sourceExactIdentity });
|
|
250
|
+
const finishPublication = async (method, handle, identity) => {
|
|
251
|
+
const verifyPhase = failure.phase;
|
|
252
|
+
const directorySync = await syncPublishedParent({ parent: parent, failure, method, targetPath });
|
|
253
|
+
failure.phase = verifyPhase;
|
|
254
|
+
if (method === "rename-noreplace")
|
|
255
|
+
assertRenamedSourceAbsent(sourcePath);
|
|
256
|
+
else
|
|
257
|
+
assertSourceCurrent();
|
|
258
|
+
assertPublishedTargetCurrent(targetPath, identity, "publication target changed during directory sync");
|
|
259
|
+
return { method, identity: fsSync.fstatSync(handle.fd), directorySync };
|
|
260
|
+
};
|
|
235
261
|
await parent.assertCurrent();
|
|
236
|
-
|
|
262
|
+
assertSourceCurrent();
|
|
237
263
|
const native = strategy === "rename-noreplace"
|
|
238
264
|
? requireNativeBinding()
|
|
239
265
|
: getNativeBinding();
|
|
@@ -248,33 +274,10 @@ export async function publishFileExclusive(params) {
|
|
|
248
274
|
// A failed post-rename fence must not delete the only remaining name.
|
|
249
275
|
failure.preserveTarget = true;
|
|
250
276
|
await getFsSafeTestHooks()?.afterPublishTargetCreated?.("rename-noreplace", targetPath, sourceExactIdentity);
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
!targetExactIdentity.isFile() ||
|
|
254
|
-
!sameFileIdentity(targetExactIdentity, sourceExactIdentity)) {
|
|
255
|
-
throw new FsSafeError("path-mismatch", "no-replace publication target changed");
|
|
256
|
-
}
|
|
257
|
-
try {
|
|
258
|
-
fsSync.lstatSync(sourcePath);
|
|
259
|
-
throw new FsSafeError("path-mismatch", "no-replace publication source still exists");
|
|
260
|
-
}
|
|
261
|
-
catch (error) {
|
|
262
|
-
if (error.code !== "ENOENT") {
|
|
263
|
-
throw error;
|
|
264
|
-
}
|
|
265
|
-
}
|
|
277
|
+
assertPublishedTargetCurrent(targetPath, sourceExactIdentity, "no-replace publication target changed");
|
|
278
|
+
assertRenamedSourceAbsent(sourcePath);
|
|
266
279
|
syncFileBestEffortSync(sourceNativeParent.handle.fd);
|
|
267
|
-
|
|
268
|
-
return {
|
|
269
|
-
method: "rename-noreplace",
|
|
270
|
-
identity: targetIdentity,
|
|
271
|
-
directorySync: await syncPublishedParent({
|
|
272
|
-
parent,
|
|
273
|
-
failure,
|
|
274
|
-
method: "rename-noreplace",
|
|
275
|
-
targetPath,
|
|
276
|
-
}),
|
|
277
|
-
};
|
|
280
|
+
return await finishPublication("rename-noreplace", source, sourceExactIdentity);
|
|
278
281
|
}
|
|
279
282
|
try {
|
|
280
283
|
if (native) {
|
|
@@ -285,24 +288,9 @@ export async function publishFileExclusive(params) {
|
|
|
285
288
|
}
|
|
286
289
|
rememberCreatedTarget(failure, sourceExactIdentity, "hardlink-verify");
|
|
287
290
|
await getFsSafeTestHooks()?.afterPublishTargetCreated?.("hardlink", targetPath, sourceExactIdentity);
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
!sameFileIdentity(targetExactIdentity, sourceExactIdentity)) {
|
|
292
|
-
throw new FsSafeError("path-mismatch", "hardlink publication target changed");
|
|
293
|
-
}
|
|
294
|
-
await assertPinnedSourceCurrent({ sourcePath, handle: source, identity: sourceExactIdentity });
|
|
295
|
-
const targetIdentity = fsSync.lstatSync(targetPath);
|
|
296
|
-
return {
|
|
297
|
-
method: "hardlink",
|
|
298
|
-
identity: targetIdentity,
|
|
299
|
-
directorySync: await syncPublishedParent({
|
|
300
|
-
parent,
|
|
301
|
-
failure,
|
|
302
|
-
method: "hardlink",
|
|
303
|
-
targetPath,
|
|
304
|
-
}),
|
|
305
|
-
};
|
|
291
|
+
assertPublishedTargetCurrent(targetPath, sourceExactIdentity, "hardlink publication target changed");
|
|
292
|
+
assertSourceCurrent();
|
|
293
|
+
return await finishPublication("hardlink", source, sourceExactIdentity);
|
|
306
294
|
}
|
|
307
295
|
catch (error) {
|
|
308
296
|
if (failure.targetCreated ||
|
|
@@ -312,48 +300,19 @@ export async function publishFileExclusive(params) {
|
|
|
312
300
|
}
|
|
313
301
|
}
|
|
314
302
|
failure.phase = "copy-create";
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
target = copied.handle;
|
|
326
|
-
targetIdentity = fsSync.fstatSync(target.fd);
|
|
327
|
-
const targetPathStat = fsSync.lstatSync(targetPath);
|
|
328
|
-
const targetPathExactStat = fsSync.lstatSync(targetPath, { bigint: true });
|
|
329
|
-
const copiedBack = await hashFileHandle(target, native);
|
|
330
|
-
const sourceAfter = await hashFileHandle(source, native);
|
|
331
|
-
if (targetPathStat.isSymbolicLink() ||
|
|
332
|
-
targetPathExactStat.isSymbolicLink() ||
|
|
333
|
-
!sameFileIdentity(targetPathExactStat, copied.exactIdentity) ||
|
|
334
|
-
copiedBack.bytes !== copied.bytes ||
|
|
335
|
-
copiedBack.digest !== copied.digest ||
|
|
336
|
-
sourceAfter.bytes !== copied.bytes ||
|
|
337
|
-
sourceAfter.digest !== copied.digest) {
|
|
338
|
-
throw new FsSafeError("path-mismatch", "exclusive publication copy failed content fencing");
|
|
339
|
-
}
|
|
340
|
-
await assertPinnedSourceCurrent({ sourcePath, handle: source, identity: sourceExactIdentity });
|
|
341
|
-
const directorySync = await syncPublishedParent({
|
|
342
|
-
parent,
|
|
343
|
-
failure,
|
|
344
|
-
method: "exclusive-copy",
|
|
345
|
-
targetPath,
|
|
346
|
-
});
|
|
347
|
-
return { method: "exclusive-copy", identity: targetPathStat, directorySync };
|
|
348
|
-
}
|
|
349
|
-
catch (error) {
|
|
350
|
-
await target?.close().catch(() => undefined);
|
|
351
|
-
target = undefined;
|
|
352
|
-
throw error;
|
|
353
|
-
}
|
|
354
|
-
finally {
|
|
355
|
-
await target?.close().catch(() => undefined);
|
|
303
|
+
const copied = await copyPinnedSource({ source, targetPath, native, targetNativeParent, failure });
|
|
304
|
+
copiedTarget = copied.handle;
|
|
305
|
+
const copiedBack = await hashFileHandle(copiedTarget, native);
|
|
306
|
+
const sourceAfter = await hashFileHandle(source, native);
|
|
307
|
+
assertPublishedTargetCurrent(targetPath, copied.exactIdentity, "exclusive publication copy failed content fencing");
|
|
308
|
+
if (copiedBack.bytes !== copied.bytes ||
|
|
309
|
+
copiedBack.digest !== copied.digest ||
|
|
310
|
+
sourceAfter.bytes !== copied.bytes ||
|
|
311
|
+
sourceAfter.digest !== copied.digest) {
|
|
312
|
+
throw new FsSafeError("path-mismatch", "exclusive publication copy failed content fencing");
|
|
356
313
|
}
|
|
314
|
+
assertSourceCurrent();
|
|
315
|
+
return await finishPublication("exclusive-copy", copiedTarget, copied.exactIdentity);
|
|
357
316
|
}
|
|
358
317
|
catch (error) {
|
|
359
318
|
if (!failure.targetCreated)
|
|
@@ -365,6 +324,7 @@ export async function publishFileExclusive(params) {
|
|
|
365
324
|
throw publicationFailure(error, failure, cleanup);
|
|
366
325
|
}
|
|
367
326
|
finally {
|
|
327
|
+
await copiedTarget?.close().catch(() => undefined);
|
|
368
328
|
await sourceNativeParent?.handle.close().catch(() => undefined);
|
|
369
329
|
await targetNativeParent?.handle.close().catch(() => undefined);
|
|
370
330
|
await source.close().catch(() => undefined);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"regular-file.d.ts","sourceRoot":"","sources":["../src/regular-file.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAe,KAAK,EAAE,MAAM,SAAS,CAAC;AAClD,OAAO,MAAM,MAAM,SAAS,CAAC;AAuB7B,MAAM,MAAM,qBAAqB,GAAG;IAAE,OAAO,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,KAAK,CAAA;CAAE,CAAC;AAExF,KAAK,8BAA8B,GAAG,IAAI,CACxC,OAAO,MAAM,CAAC,SAAS,EACvB,UAAU,GAAG,SAAS,GAAG,UAAU,CACpC,GACC,OAAO,CAAC,IAAI,CAAC,OAAO,MAAM,CAAC,SAAS,EAAE,YAAY,GAAG,YAAY,CAAC,CAAC,CAAC;AAEtE,MAAM,MAAM,wBAAwB,GAAG;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,UAAU,CAAC;IAC7B,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC,CAAC;AAEF,wBAAgB,6BAA6B,CAC3C,SAAS,GAAE,8BAAiD,GAC3D,MAAM,CASR;AAaD,wBAAsB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAEtF;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,qBAAqB,CAe3E;AAED,wBAAsB,eAAe,CAAC,MAAM,EAAE;IAC5C,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,KAAK,CAAA;CAAE,CAAC,CA+D3C;AA2DD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG;IACpF,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,KAAK,CAAC;CACb,CAsCA;
|
|
1
|
+
{"version":3,"file":"regular-file.d.ts","sourceRoot":"","sources":["../src/regular-file.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAe,KAAK,EAAE,MAAM,SAAS,CAAC;AAClD,OAAO,MAAM,MAAM,SAAS,CAAC;AAuB7B,MAAM,MAAM,qBAAqB,GAAG;IAAE,OAAO,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,KAAK,CAAA;CAAE,CAAC;AAExF,KAAK,8BAA8B,GAAG,IAAI,CACxC,OAAO,MAAM,CAAC,SAAS,EACvB,UAAU,GAAG,SAAS,GAAG,UAAU,CACpC,GACC,OAAO,CAAC,IAAI,CAAC,OAAO,MAAM,CAAC,SAAS,EAAE,YAAY,GAAG,YAAY,CAAC,CAAC,CAAC;AAEtE,MAAM,MAAM,wBAAwB,GAAG;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,UAAU,CAAC;IAC7B,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC,CAAC;AAEF,wBAAgB,6BAA6B,CAC3C,SAAS,GAAE,8BAAiD,GAC3D,MAAM,CASR;AAaD,wBAAsB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAEtF;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,qBAAqB,CAe3E;AAED,wBAAsB,eAAe,CAAC,MAAM,EAAE;IAC5C,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,KAAK,CAAA;CAAE,CAAC,CA+D3C;AA2DD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG;IACpF,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,KAAK,CAAC;CACb,CAsCA;AAoCD,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,wBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC,CAqExF;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,wBAAwB,GAAG,IAAI,CAqE7E"}
|
package/dist/regular-file.js
CHANGED
|
@@ -223,10 +223,26 @@ function throwAppendIdentityError(error, filePath) {
|
|
|
223
223
|
}
|
|
224
224
|
throw error;
|
|
225
225
|
}
|
|
226
|
-
|
|
226
|
+
function captureAppendOptions(options) {
|
|
227
227
|
const filePath = options.filePath;
|
|
228
228
|
assertNoWindowsPathAlias(filePath, "filesystem", "file path uses a Windows filesystem namespace alias");
|
|
229
|
-
|
|
229
|
+
return {
|
|
230
|
+
filePath, content: options.content, encoding: options.encoding ?? "utf8",
|
|
231
|
+
mode: options.mode ?? 0o600, maxFileBytes: options.maxFileBytes,
|
|
232
|
+
rejectSymlinkParents: options.rejectSymlinkParents,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
function inspectAppendPath(filePath) {
|
|
236
|
+
const stat = fsSync.lstatSync(filePath, { bigint: true });
|
|
237
|
+
if (stat.isSymbolicLink())
|
|
238
|
+
throw new Error(`Refusing to append through symlink: ${filePath}`);
|
|
239
|
+
if (!stat.isFile())
|
|
240
|
+
throw new Error(`Refusing to append to non-file: ${filePath}`);
|
|
241
|
+
return stat;
|
|
242
|
+
}
|
|
243
|
+
export async function appendRegularFile(options) {
|
|
244
|
+
const { filePath, content, encoding, mode, maxFileBytes, rejectSymlinkParents } = captureAppendOptions(options);
|
|
245
|
+
if (rejectSymlinkParents === true) {
|
|
230
246
|
const resolvedDir = resolvePathPreservingWindowsRoot(path.dirname(filePath));
|
|
231
247
|
await assertNoSymlinkParents({
|
|
232
248
|
rootDir: path.parse(resolvedDir).root,
|
|
@@ -239,33 +255,22 @@ export async function appendRegularFile(options) {
|
|
|
239
255
|
}
|
|
240
256
|
let preOpenStat;
|
|
241
257
|
try {
|
|
242
|
-
preOpenStat = await inspectFileIdentity(async () =>
|
|
243
|
-
const stat = fsSync.lstatSync(filePath, { bigint: true });
|
|
244
|
-
if (stat.isSymbolicLink()) {
|
|
245
|
-
throw new Error(`Refusing to append through symlink: ${filePath}`);
|
|
246
|
-
}
|
|
247
|
-
if (!stat.isFile()) {
|
|
248
|
-
throw new Error(`Refusing to append to non-file: ${filePath}`);
|
|
249
|
-
}
|
|
250
|
-
return stat;
|
|
251
|
-
});
|
|
258
|
+
preOpenStat = await inspectFileIdentity(async () => inspectAppendPath(filePath));
|
|
252
259
|
}
|
|
253
260
|
catch (error) {
|
|
254
261
|
if (!isNotFoundPathError(error))
|
|
255
262
|
throw error;
|
|
256
263
|
}
|
|
257
|
-
const contentBytes = Buffer.isBuffer(
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
if (options.maxFileBytes !== undefined &&
|
|
261
|
-
Number(preOpenStat?.size ?? 0n) + contentBytes > options.maxFileBytes) {
|
|
264
|
+
const contentBytes = Buffer.isBuffer(content) ? content.byteLength : Buffer.byteLength(content, encoding);
|
|
265
|
+
if (maxFileBytes !== undefined &&
|
|
266
|
+
Number(preOpenStat?.size ?? 0n) + contentBytes > maxFileBytes) {
|
|
262
267
|
return;
|
|
263
268
|
}
|
|
264
269
|
await getFsSafeTestHooks()?.beforeRegularFileAppendOpen?.(filePath);
|
|
265
270
|
const flags = resolveRegularFileAppendFlags();
|
|
266
271
|
let handle;
|
|
267
272
|
try {
|
|
268
|
-
handle = await fs.open(filePath, flags,
|
|
273
|
+
handle = await fs.open(filePath, flags, mode);
|
|
269
274
|
}
|
|
270
275
|
catch (error) {
|
|
271
276
|
if (await isNonRegularWriteOpenError(error, filePath, flags)) {
|
|
@@ -290,14 +295,13 @@ export async function appendRegularFile(options) {
|
|
|
290
295
|
catch (error) {
|
|
291
296
|
throwAppendIdentityError(error, filePath);
|
|
292
297
|
}
|
|
293
|
-
if (
|
|
294
|
-
Number(identity.size) + contentBytes >
|
|
298
|
+
if (maxFileBytes !== undefined &&
|
|
299
|
+
Number(identity.size) + contentBytes > maxFileBytes) {
|
|
295
300
|
return;
|
|
296
301
|
}
|
|
297
|
-
const mode = options.mode ?? 0o600;
|
|
298
302
|
// Tighten before writing; restore explicit special bits only after content is complete.
|
|
299
303
|
await handle.chmod(mode);
|
|
300
|
-
await handle.appendFile(
|
|
304
|
+
await handle.appendFile(content, encoding);
|
|
301
305
|
if (mode & 0o7000)
|
|
302
306
|
await handle.chmod(mode);
|
|
303
307
|
}
|
|
@@ -306,9 +310,8 @@ export async function appendRegularFile(options) {
|
|
|
306
310
|
}
|
|
307
311
|
}
|
|
308
312
|
export function appendRegularFileSync(options) {
|
|
309
|
-
const filePath = options
|
|
310
|
-
|
|
311
|
-
if (options.rejectSymlinkParents === true) {
|
|
313
|
+
const { filePath, content, encoding, mode, maxFileBytes, rejectSymlinkParents } = captureAppendOptions(options);
|
|
314
|
+
if (rejectSymlinkParents === true) {
|
|
312
315
|
const resolvedDir = resolvePathPreservingWindowsRoot(path.dirname(filePath));
|
|
313
316
|
assertNoSymlinkParentsSync({
|
|
314
317
|
rootDir: path.parse(resolvedDir).root,
|
|
@@ -321,33 +324,22 @@ export function appendRegularFileSync(options) {
|
|
|
321
324
|
}
|
|
322
325
|
let preOpenStat;
|
|
323
326
|
try {
|
|
324
|
-
preOpenStat = inspectFileIdentitySync(() =>
|
|
325
|
-
const stat = fsSync.lstatSync(filePath, { bigint: true });
|
|
326
|
-
if (stat.isSymbolicLink()) {
|
|
327
|
-
throw new Error(`Refusing to append through symlink: ${filePath}`);
|
|
328
|
-
}
|
|
329
|
-
if (!stat.isFile()) {
|
|
330
|
-
throw new Error(`Refusing to append to non-file: ${filePath}`);
|
|
331
|
-
}
|
|
332
|
-
return stat;
|
|
333
|
-
});
|
|
327
|
+
preOpenStat = inspectFileIdentitySync(() => inspectAppendPath(filePath));
|
|
334
328
|
}
|
|
335
329
|
catch (error) {
|
|
336
330
|
if (!isNotFoundPathError(error))
|
|
337
331
|
throw error;
|
|
338
332
|
}
|
|
339
|
-
const contentBuffer = typeof
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
if (options.maxFileBytes !== undefined &&
|
|
343
|
-
Number(preOpenStat?.size ?? 0n) + contentBuffer.byteLength > options.maxFileBytes) {
|
|
333
|
+
const contentBuffer = typeof content === "string" ? Buffer.from(content, encoding) : Buffer.from(content);
|
|
334
|
+
if (maxFileBytes !== undefined &&
|
|
335
|
+
Number(preOpenStat?.size ?? 0n) + contentBuffer.byteLength > maxFileBytes) {
|
|
344
336
|
return;
|
|
345
337
|
}
|
|
346
338
|
getFsSafeTestHooks()?.beforeRegularFileAppendOpenSync?.(filePath);
|
|
347
339
|
const flags = resolveRegularFileAppendFlags();
|
|
348
340
|
let fd;
|
|
349
341
|
try {
|
|
350
|
-
fd = fsSync.openSync(filePath, flags,
|
|
342
|
+
fd = fsSync.openSync(filePath, flags, mode);
|
|
351
343
|
}
|
|
352
344
|
catch (error) {
|
|
353
345
|
if (isNonRegularWriteOpenErrorSync(error, filePath, flags)) {
|
|
@@ -372,11 +364,10 @@ export function appendRegularFileSync(options) {
|
|
|
372
364
|
catch (error) {
|
|
373
365
|
throwAppendIdentityError(error, filePath);
|
|
374
366
|
}
|
|
375
|
-
if (
|
|
376
|
-
Number(identity.size) + contentBuffer.byteLength >
|
|
367
|
+
if (maxFileBytes !== undefined &&
|
|
368
|
+
Number(identity.size) + contentBuffer.byteLength > maxFileBytes) {
|
|
377
369
|
return;
|
|
378
370
|
}
|
|
379
|
-
const mode = options.mode ?? 0o600;
|
|
380
371
|
fsSync.fchmodSync(fd, mode);
|
|
381
372
|
fsSync.appendFileSync(fd, contentBuffer);
|
|
382
373
|
if (mode & 0o7000)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"replace-file-copy-fallback.d.ts","sourceRoot":"","sources":["../src/replace-file-copy-fallback.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,EAAE,EAAE,KAAK,WAAW,EAAc,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"replace-file-copy-fallback.d.ts","sourceRoot":"","sources":["../src/replace-file-copy-fallback.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,EAAE,EAAE,KAAK,WAAW,EAAc,MAAM,SAAS,CAAC;AAS/D,MAAM,MAAM,oCAAoC,GAAG,QAAQ,CAAC;AAC5D,MAAM,MAAM,oCAAoC,GAAG,kBAAkB,GAAG,MAAM,CAAC;AAC/E,MAAM,MAAM,+BAA+B,GAAG,UAAU,GAAG,gBAAgB,CAAC;AAC5E,MAAM,MAAM,sCAAsC,GAAG;IACnD,OAAO,EAAE,+BAA+B,CAAC;CAC1C,CAAC;AAEF,KAAK,eAAe,GAAG;IACrB,KAAK,EAAE,cAAc,kBAAkB,EAAE,KAAK,CAAC;IAC/C,IAAI,EAAE,cAAc,kBAAkB,EAAE,IAAI,CAAC;IAC7C,EAAE,EAAE,cAAc,kBAAkB,EAAE,EAAE,CAAC;IACzC,MAAM,EAAE,cAAc,kBAAkB,EAAE,MAAM,CAAC;CAClD,CAAC;AAEF,KAAK,cAAc,GAAG,IAAI,CACxB,OAAO,MAAM,EACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,eAAe,GACf,WAAW,GACX,UAAU,GACV,UAAU,GACV,QAAQ,GACR,YAAY,GACZ,WAAW,CACd,CAAC;AAqHF,wBAAsB,+BAA+B,CACnD,QAAQ,EAAE,eAAe,EACzB,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,oCAAoC,GAC5C,OAAO,CAAC,IAAI,CAAC,CAIf;AAED,wBAAgB,mCAAmC,CACjD,QAAQ,EAAE,cAAc,EACxB,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,oCAAoC,GAC5C,IAAI,CAIN;AA+HD,wBAAsB,mBAAmB,CAAC,MAAM,EAAE;IAChD,QAAQ,EAAE,eAAe,CAAC;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,oBAAoB,CAAC,EAAE,oCAAoC,CAAC;IAC5D,OAAO,EAAE,oCAAoC,CAAC;IAC9C,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,sBAAsB,CAAC,EAAE,WAAW,CAAC;IACrC,IAAI,EAAE,OAAO,CAAC;CACf,GAAG,OAAO,CAAC,IAAI,CAAC,CAuEhB;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE;IAC9C,QAAQ,EAAE,cAAc,CAAC;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,oBAAoB,CAAC,EAAE,oCAAoC,CAAC;IAC5D,OAAO,EAAE,oCAAoC,CAAC;IAC9C,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,sBAAsB,CAAC,EAAE,WAAW,CAAC;IACrC,UAAU,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD,IAAI,EAAE,OAAO,CAAC;CACf,GAAG,IAAI,CAuEP"}
|
|
@@ -2,9 +2,10 @@ import syncFs, {} from "node:fs";
|
|
|
2
2
|
import fs, {} from "node:fs/promises";
|
|
3
3
|
import { readBoundedAsync, readBoundedSync } from "./bounded-read.js";
|
|
4
4
|
import { FsSafeError } from "./errors.js";
|
|
5
|
-
import {
|
|
5
|
+
import { inspectFileIdentity, inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
6
6
|
import { readOwnedCopySource, readOwnedCopySourceSync } from "./replace-file-copy-source.js";
|
|
7
7
|
import { resolveReadOpenFlags } from "./read-open-flags.js";
|
|
8
|
+
import { hasErrorCode } from "./file-cleanup.js";
|
|
8
9
|
const SUPPORTS_NOFOLLOW = process.platform !== "win32" && "O_NOFOLLOW" in syncFs.constants;
|
|
9
10
|
const NOFOLLOW = SUPPORTS_NOFOLLOW ? syncFs.constants.O_NOFOLLOW : 0;
|
|
10
11
|
const OPEN_READ_FLAGS = resolveReadOpenFlags();
|
|
@@ -19,39 +20,37 @@ function closeSyncAfterAdmissionFailure(fsModule, fd, error) {
|
|
|
19
20
|
}
|
|
20
21
|
throw error;
|
|
21
22
|
}
|
|
22
|
-
function
|
|
23
|
-
return error.code === "ENOENT";
|
|
24
|
-
}
|
|
25
|
-
function assertPinnedDestination(pathname, opened, dest, hardlinks, admission) {
|
|
23
|
+
function admitDestinationKind(pathname, opened, dest, admission) {
|
|
26
24
|
if (admission === "hardlinks") {
|
|
27
|
-
if (pathname.isSymbolicLink() || !pathname.isFile()
|
|
25
|
+
if (pathname.isSymbolicLink() || !pathname.isFile()) {
|
|
28
26
|
throw new FsSafeError("path-mismatch", `Atomic replace destination changed while opening: ${dest}`);
|
|
29
27
|
}
|
|
30
|
-
if (opened.nlink > 1) {
|
|
31
|
-
throw new FsSafeError("hardlink", `Hardlinked atomic replace destination not allowed: ${dest}`);
|
|
32
|
-
}
|
|
33
|
-
return;
|
|
34
28
|
}
|
|
35
|
-
if (pathname.isSymbolicLink()) {
|
|
29
|
+
else if (pathname.isSymbolicLink()) {
|
|
36
30
|
throw new FsSafeError("symlink", `Refusing copy fallback through symlink destination: ${dest}`);
|
|
37
31
|
}
|
|
38
|
-
if (!pathname.isFile() || !opened.isFile()) {
|
|
32
|
+
else if (!pathname.isFile() || !opened.isFile()) {
|
|
39
33
|
throw new FsSafeError("not-file", `Copy fallback destination must be a regular file: ${dest}`);
|
|
40
34
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
if (hardlinks === "reject" && opened.nlink >
|
|
45
|
-
throw new FsSafeError("hardlink", `Hardlinked copy fallback destination not allowed: ${dest}`);
|
|
35
|
+
return pathname;
|
|
36
|
+
}
|
|
37
|
+
function assertDestinationLinks(opened, dest, admission, hardlinks) {
|
|
38
|
+
if ((admission === "hardlinks" || hardlinks === "reject") && opened.nlink > 1n) {
|
|
39
|
+
throw new FsSafeError("hardlink", `Hardlinked ${admission === "hardlinks" ? "atomic replace" : "copy fallback"} destination not allowed: ${dest}`);
|
|
46
40
|
}
|
|
47
41
|
}
|
|
42
|
+
function inspectPinnedDestinationSync(fsModule, fd, dest, admission, hardlinks) {
|
|
43
|
+
const opened = inspectFileIdentitySync(() => fsModule.fstatSync(fd, { bigint: true }));
|
|
44
|
+
inspectFileIdentitySync(() => admitDestinationKind(fsModule.lstatSync(dest, { bigint: true }), opened, dest, admission), opened);
|
|
45
|
+
assertDestinationLinks(opened, dest, admission, hardlinks);
|
|
46
|
+
}
|
|
48
47
|
async function openPinnedDestination(fsModule, dest, admission, hardlinks) {
|
|
49
48
|
let preview;
|
|
50
49
|
try {
|
|
51
50
|
preview = fsModule === fs ? syncFs.lstatSync(dest) : await fsModule.lstat(dest);
|
|
52
51
|
}
|
|
53
52
|
catch (error) {
|
|
54
|
-
if (
|
|
53
|
+
if (hasErrorCode(error, "ENOENT"))
|
|
55
54
|
return null;
|
|
56
55
|
throw error;
|
|
57
56
|
}
|
|
@@ -64,9 +63,13 @@ async function openPinnedDestination(fsModule, dest, admission, hardlinks) {
|
|
|
64
63
|
}
|
|
65
64
|
const handle = await fsModule.open(dest, admission === "restore" ? OPEN_READ_WRITE_FLAGS : OPEN_READ_FLAGS);
|
|
66
65
|
try {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
66
|
+
if (fsModule === fs)
|
|
67
|
+
inspectPinnedDestinationSync(syncFs, handle.fd, dest, admission, hardlinks);
|
|
68
|
+
else {
|
|
69
|
+
const opened = await inspectFileIdentity(() => handle.stat({ bigint: true }));
|
|
70
|
+
await inspectFileIdentity(async () => admitDestinationKind(await fsModule.lstat(dest, { bigint: true }), opened, dest, admission), opened);
|
|
71
|
+
assertDestinationLinks(opened, dest, admission, hardlinks);
|
|
72
|
+
}
|
|
70
73
|
return handle;
|
|
71
74
|
}
|
|
72
75
|
catch (error) {
|
|
@@ -80,7 +83,7 @@ function openPinnedDestinationSync(fsModule, dest, admission, hardlinks) {
|
|
|
80
83
|
preview = fsModule.lstatSync(dest);
|
|
81
84
|
}
|
|
82
85
|
catch (error) {
|
|
83
|
-
if (
|
|
86
|
+
if (hasErrorCode(error, "ENOENT"))
|
|
84
87
|
return null;
|
|
85
88
|
throw error;
|
|
86
89
|
}
|
|
@@ -91,8 +94,7 @@ function openPinnedDestinationSync(fsModule, dest, admission, hardlinks) {
|
|
|
91
94
|
}
|
|
92
95
|
const fd = fsModule.openSync(dest, admission === "restore" ? OPEN_READ_WRITE_FLAGS : OPEN_READ_FLAGS);
|
|
93
96
|
try {
|
|
94
|
-
|
|
95
|
-
assertPinnedDestination(fsModule.lstatSync(dest), opened, dest, hardlinks, admission);
|
|
97
|
+
inspectPinnedDestinationSync(fsModule, fd, dest, admission, hardlinks);
|
|
96
98
|
return fd;
|
|
97
99
|
}
|
|
98
100
|
catch (error) {
|
|
@@ -239,7 +241,7 @@ export async function copyFallbackReplace(params) {
|
|
|
239
241
|
? syncFs.lstatSync(params.dest) : await params.fsModule.lstat(params.dest);
|
|
240
242
|
}
|
|
241
243
|
catch (error) {
|
|
242
|
-
if (!
|
|
244
|
+
if (!hasErrorCode(error, "ENOENT"))
|
|
243
245
|
throw error;
|
|
244
246
|
}
|
|
245
247
|
if (destStat?.isSymbolicLink()) {
|
|
@@ -294,7 +296,7 @@ export function copyFallbackReplaceSync(params) {
|
|
|
294
296
|
destStat = params.fsModule.lstatSync(params.dest);
|
|
295
297
|
}
|
|
296
298
|
catch (error) {
|
|
297
|
-
if (!
|
|
299
|
+
if (!hasErrorCode(error, "ENOENT"))
|
|
298
300
|
throw error;
|
|
299
301
|
}
|
|
300
302
|
if (destStat?.isSymbolicLink()) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"replace-file-copy-source.d.ts","sourceRoot":"","sources":["../src/replace-file-copy-source.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,EAAE,EAAE,KAAK,WAAW,EAAc,MAAM,SAAS,CAAC;AAQ/D,KAAK,qBAAqB,GAAG,IAAI,CAAC,cAAc,kBAAkB,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC,CAAC;AACvF,KAAK,oBAAoB,GAAG,IAAI,CAC9B,OAAO,MAAM,EACb,WAAW,GAAG,WAAW,GAAG,WAAW,GAAG,UAAU,GAAG,UAAU,CAClE,CAAC;AA8DF,wBAAsB,mBAAmB,CAAC,MAAM,EAAE;IAChD,QAAQ,EAAE,qBAAqB,CAAC;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,gBAAgB,CAAC,EAAE,WAAW,CAAC;CAChC,GAAG,OAAO,CAAC;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,
|
|
1
|
+
{"version":3,"file":"replace-file-copy-source.d.ts","sourceRoot":"","sources":["../src/replace-file-copy-source.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,EAAE,EAAE,KAAK,WAAW,EAAc,MAAM,SAAS,CAAC;AAQ/D,KAAK,qBAAqB,GAAG,IAAI,CAAC,cAAc,kBAAkB,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC,CAAC;AACvF,KAAK,oBAAoB,GAAG,IAAI,CAC9B,OAAO,MAAM,EACb,WAAW,GAAG,WAAW,GAAG,WAAW,GAAG,UAAU,GAAG,UAAU,CAClE,CAAC;AA8DF,wBAAsB,mBAAmB,CAAC,MAAM,EAAE;IAChD,QAAQ,EAAE,qBAAqB,CAAC;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,gBAAgB,CAAC,EAAE,WAAW,CAAC;CAChC,GAAG,OAAO,CAAC;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAoBjD;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE;IAC9C,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,gBAAgB,CAAC,EAAE,WAAW,CAAC;CAChC,GAAG;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAqBxC"}
|
|
@@ -3,8 +3,8 @@ import fs, {} from "node:fs/promises";
|
|
|
3
3
|
import { readBoundedSync } from "./bounded-read.js";
|
|
4
4
|
import { resolveReadOpenFlags } from "./read-open-flags.js";
|
|
5
5
|
import { FsSafeError } from "./errors.js";
|
|
6
|
-
import { sameFileIdentity } from "./file-identity.js";
|
|
7
6
|
import { inspectFileIdentity, inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
7
|
+
import { hasErrorCode } from "./file-cleanup.js";
|
|
8
8
|
const OPEN_READ_FLAGS = resolveReadOpenFlags();
|
|
9
9
|
function assertSourcePreview(source, src) {
|
|
10
10
|
if (source.isSymbolicLink()) {
|
|
@@ -18,10 +18,10 @@ function assertSourcePreview(source, src) {
|
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
function assertOpenedSource(opened, current, src) {
|
|
21
|
-
if (!opened.isFile() || current.isSymbolicLink()
|
|
21
|
+
if (!opened.isFile() || current.isSymbolicLink()) {
|
|
22
22
|
throw new FsSafeError("path-mismatch", `Copy fallback source changed while opening: ${src}`);
|
|
23
23
|
}
|
|
24
|
-
if (opened.nlink !==
|
|
24
|
+
if (opened.nlink !== 1n) {
|
|
25
25
|
throw new FsSafeError("hardlink", `Hardlinked copy fallback source not allowed: ${src}`);
|
|
26
26
|
}
|
|
27
27
|
}
|
|
@@ -35,7 +35,7 @@ async function openSource(fsModule, src) {
|
|
|
35
35
|
return await fsModule.open(src, OPEN_READ_FLAGS);
|
|
36
36
|
}
|
|
37
37
|
catch (error) {
|
|
38
|
-
if (error
|
|
38
|
+
if (hasErrorCode(error, "ELOOP")) {
|
|
39
39
|
throw sourceSymlinkError(src, error);
|
|
40
40
|
}
|
|
41
41
|
throw error;
|
|
@@ -46,7 +46,7 @@ function openSourceSync(fsModule, src) {
|
|
|
46
46
|
return fsModule.openSync(src, OPEN_READ_FLAGS);
|
|
47
47
|
}
|
|
48
48
|
catch (error) {
|
|
49
|
-
if (error
|
|
49
|
+
if (hasErrorCode(error, "ELOOP")) {
|
|
50
50
|
throw sourceSymlinkError(src, error);
|
|
51
51
|
}
|
|
52
52
|
throw error;
|
|
@@ -65,17 +65,12 @@ export async function readOwnedCopySource(params) {
|
|
|
65
65
|
? syncFs.lstatSync(params.src) : await params.fsModule.lstat(params.src), params.src);
|
|
66
66
|
const handle = await openSource(params.fsModule, params.src);
|
|
67
67
|
try {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
? syncFs.lstatSync(params.src, { bigint: true }) : params.fsModule.lstat(params.src, { bigint: true }), exact);
|
|
73
|
-
}
|
|
74
|
-
const opened = params.fsModule === fs ? syncFs.fstatSync(handle.fd) : await handle.stat();
|
|
75
|
-
const current = params.fsModule === fs
|
|
76
|
-
? syncFs.lstatSync(params.src) : await params.fsModule.lstat(params.src);
|
|
68
|
+
const opened = await inspectFileIdentity(() => params.fsModule === fs
|
|
69
|
+
? syncFs.fstatSync(handle.fd, { bigint: true }) : handle.stat({ bigint: true }), params.expectedIdentity);
|
|
70
|
+
const current = await inspectFileIdentity(() => params.fsModule === fs
|
|
71
|
+
? syncFs.lstatSync(params.src, { bigint: true }) : params.fsModule.lstat(params.src, { bigint: true }), opened);
|
|
77
72
|
assertOpenedSource(opened, current, params.src);
|
|
78
|
-
return { replacement: await handle.readFile(), mode: opened.mode };
|
|
73
|
+
return { replacement: await handle.readFile(), mode: Number(opened.mode) };
|
|
79
74
|
}
|
|
80
75
|
finally {
|
|
81
76
|
await handle.close().catch(() => undefined);
|
|
@@ -85,14 +80,10 @@ export function readOwnedCopySourceSync(params) {
|
|
|
85
80
|
assertSourcePreview(params.fsModule.lstatSync(params.src), params.src);
|
|
86
81
|
const fd = openSourceSync(params.fsModule, params.src);
|
|
87
82
|
try {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
inspectFileIdentitySync(() => params.fsModule.lstatSync(params.src, { bigint: true }), exact);
|
|
91
|
-
}
|
|
92
|
-
const opened = params.fsModule.fstatSync(fd);
|
|
93
|
-
const current = params.fsModule.lstatSync(params.src);
|
|
83
|
+
const opened = inspectFileIdentitySync(() => params.fsModule.fstatSync(fd, { bigint: true }), params.expectedIdentity);
|
|
84
|
+
const current = inspectFileIdentitySync(() => params.fsModule.lstatSync(params.src, { bigint: true }), opened);
|
|
94
85
|
assertOpenedSource(opened, current, params.src);
|
|
95
|
-
return { replacement: readAllSync(params.fsModule, fd, opened.size), mode: opened.mode };
|
|
86
|
+
return { replacement: readAllSync(params.fsModule, fd, Number(opened.size)), mode: Number(opened.mode) };
|
|
96
87
|
}
|
|
97
88
|
finally {
|
|
98
89
|
try {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"replace-file-descriptor.d.ts","sourceRoot":"","sources":["../src/replace-file-descriptor.ts"],"names":[],"mappings":"AACA,OAAO,MAAM,EAAE,EAAE,KAAK,WAAW,EAAc,MAAM,SAAS,CAAC;AAC/D,OAAO,EAAE,EAAE,EAAE,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"replace-file-descriptor.d.ts","sourceRoot":"","sources":["../src/replace-file-descriptor.ts"],"names":[],"mappings":"AACA,OAAO,MAAM,EAAE,EAAE,KAAK,WAAW,EAAc,MAAM,SAAS,CAAC;AAC/D,OAAO,EAAE,EAAE,EAAE,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAGvD,OAAO,EAAoB,KAAK,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAEtF,KAAK,mBAAmB,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,OAAO,GAAG,MAAM,GAAG,WAAW,CAAC,CAAC;AAC3E,KAAK,kBAAkB,GAAG,IAAI,CAC5B,OAAO,MAAM,EACb,WAAW,GAAG,WAAW,GAAG,WAAW,GAAG,WAAW,GAAG,UAAU,GAAG,eAAe,CACrF,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;AAE5D,wBAAsB,uBAAuB,CAC3C,QAAQ,EAAE,IAAI,CAAC,OAAO,EAAE,EAAE,MAAM,CAAC,EACjC,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,IAAI,CAAC,CAUf;AAED,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,IAAI,CAAC,OAAO,MAAM,EAAE,UAAU,GAAG,WAAW,GAAG,WAAW,CAAC,EACrE,OAAO,EAAE,MAAM,GACd,IAAI,CAgBN;AAkBD,wBAAsB,mBAAmB,CAAC,MAAM,EAAE;IAChD,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,4FAA4F;IAC5F,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B,GAAG,OAAO,CAAC,kBAAkB,GAAG,SAAS,CAAC,CA8B1C;AAED,wBAAsB,kBAAkB,CAAC,MAAM,EAAE;IAC/C,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B,GAAG,OAAO,CAAC,IAAI,CAAC,CAOhB;AAED,wBAAgB,sBAAsB,CAAC,MAAM,EAAE;IAC7C,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,UAAU,CAAC;CACzB,GAAG,IAAI,CAcP;AAED,wBAAsB,aAAa,CAAC,MAAM,EAAE;IAC1C,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,UAAU,CAAC;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,CAAC;IACd,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,WAAW,KAAK,IAAI,CAAC;CAC9C,GAAG,OAAO,CAAC;IAAE,MAAM,EAAE,UAAU,CAAC;IAAC,QAAQ,EAAE,WAAW,CAAA;CAAE,CAAC,CAuBzD;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE;IACxC,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,UAAU,CAAC;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,IAAI,EAAE,OAAO,CAAC;IACd,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,WAAW,KAAK,IAAI,CAAC;CAC9C,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,WAAW,CAAA;CAAE,CAoBxC"}
|