@openclaw/fs-safe 0.13.1 → 0.15.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 +64 -1
- package/README.md +10 -4
- package/dist/advanced.d.ts +1 -1
- package/dist/advanced.d.ts.map +1 -1
- package/dist/advanced.js +1 -1
- package/dist/archive-merge.d.ts.map +1 -1
- package/dist/archive-merge.js +113 -46
- package/dist/archive-read.d.ts.map +1 -1
- package/dist/archive-read.js +4 -4
- package/dist/archive-zip-directory.d.ts +4 -0
- package/dist/archive-zip-directory.d.ts.map +1 -1
- package/dist/archive-zip-directory.js +2 -0
- package/dist/archive-zip-entry.d.ts +6 -2
- package/dist/archive-zip-entry.d.ts.map +1 -1
- package/dist/archive-zip-entry.js +23 -8
- package/dist/archive-zip-integrity.d.ts.map +1 -1
- package/dist/archive-zip-integrity.js +3 -4
- package/dist/archive-zip-loader.d.ts.map +1 -1
- package/dist/archive-zip-loader.js +107 -31
- package/dist/archive-zip-names.d.ts +1 -0
- package/dist/archive-zip-names.d.ts.map +1 -1
- package/dist/archive-zip-names.js +6 -0
- package/dist/archive.js +6 -5
- package/dist/bounded-read-stream.d.ts +0 -1
- package/dist/bounded-read-stream.d.ts.map +1 -1
- package/dist/bounded-read-stream.js +0 -6
- package/dist/copy-publication.d.ts +6 -0
- package/dist/copy-publication.d.ts.map +1 -1
- package/dist/copy-publication.js +3 -0
- package/dist/copy-tree-portable.d.ts.map +1 -1
- package/dist/copy-tree-portable.js +44 -24
- package/dist/copy.d.ts.map +1 -1
- package/dist/copy.js +29 -11
- package/dist/directory-mode-owner.js +5 -5
- package/dist/file-handle-transfer.d.ts +2 -0
- package/dist/file-handle-transfer.d.ts.map +1 -1
- package/dist/file-handle-transfer.js +57 -2
- package/dist/file-identity.d.ts.map +1 -1
- package/dist/file-identity.js +18 -4
- package/dist/file-lock-sync-admission.d.ts +19 -0
- package/dist/file-lock-sync-admission.d.ts.map +1 -0
- package/dist/file-lock-sync-admission.js +93 -0
- package/dist/file-lock-sync-root-acquire.d.ts +4 -0
- package/dist/file-lock-sync-root-acquire.d.ts.map +1 -0
- package/dist/file-lock-sync-root-acquire.js +370 -0
- package/dist/file-lock-sync-root-arbitration.d.ts +18 -0
- package/dist/file-lock-sync-root-arbitration.d.ts.map +1 -0
- package/dist/file-lock-sync-root-arbitration.js +66 -0
- package/dist/file-lock-sync-root-held.d.ts +34 -0
- package/dist/file-lock-sync-root-held.d.ts.map +1 -0
- package/dist/file-lock-sync-root-held.js +393 -0
- package/dist/file-lock-sync-root-io.d.ts +44 -0
- package/dist/file-lock-sync-root-io.d.ts.map +1 -0
- package/dist/file-lock-sync-root-io.js +209 -0
- package/dist/file-lock-sync-root-mutation.d.ts +17 -0
- package/dist/file-lock-sync-root-mutation.d.ts.map +1 -0
- package/dist/file-lock-sync-root-mutation.js +277 -0
- package/dist/file-lock-sync-root-options.d.ts +20 -0
- package/dist/file-lock-sync-root-options.d.ts.map +1 -0
- package/dist/file-lock-sync-root-options.js +58 -0
- package/dist/file-lock-sync-root-registration.d.ts +2 -0
- package/dist/file-lock-sync-root-registration.d.ts.map +1 -0
- package/dist/file-lock-sync-root-registration.js +90 -0
- package/dist/file-lock-sync-root.d.ts +36 -0
- package/dist/file-lock-sync-root.d.ts.map +1 -0
- package/dist/file-lock-sync-root.js +361 -0
- package/dist/file-lock-sync-stale-admission.d.ts +24 -0
- package/dist/file-lock-sync-stale-admission.d.ts.map +1 -0
- package/dist/file-lock-sync-stale-admission.js +205 -0
- package/dist/file-lock-sync.d.ts.map +1 -1
- package/dist/file-lock-sync.js +245 -205
- package/dist/file-store-prune.d.ts.map +1 -1
- package/dist/file-store-prune.js +5 -1
- package/dist/file-store-sync-directory.d.ts.map +1 -1
- package/dist/file-store-sync-directory.js +14 -7
- package/dist/file-store-sync-write.js +3 -3
- package/dist/file-store.d.ts.map +1 -1
- package/dist/file-store.js +47 -12
- package/dist/guest-dispatch-python.js +1 -1
- package/dist/json-document-store.d.ts.map +1 -1
- package/dist/json-document-store.js +22 -15
- package/dist/json-durable-queue-ownership.d.ts +0 -1
- package/dist/json-durable-queue-ownership.d.ts.map +1 -1
- package/dist/json-durable-queue-ownership.js +0 -6
- package/dist/native-binding.d.ts +8 -0
- package/dist/native-binding.d.ts.map +1 -1
- package/dist/native-parent-admission.d.ts +3 -2
- package/dist/native-parent-admission.d.ts.map +1 -1
- package/dist/native-parent-admission.js +24 -5
- package/dist/native-pinned-write-windows.d.ts.map +1 -1
- package/dist/native-pinned-write-windows.js +7 -7
- package/dist/native-pinned-write.d.ts.map +1 -1
- package/dist/native-pinned-write.js +202 -172
- package/dist/native-policy-directory-observation.d.ts +12 -0
- package/dist/native-policy-directory-observation.d.ts.map +1 -0
- package/dist/native-policy-directory-observation.js +61 -0
- package/dist/native-policy-parent-windows.d.ts +14 -0
- package/dist/native-policy-parent-windows.d.ts.map +1 -0
- package/dist/native-policy-parent-windows.js +200 -0
- package/dist/native-rename-outcome.d.ts +4 -0
- package/dist/native-rename-outcome.d.ts.map +1 -0
- package/dist/native-rename-outcome.js +8 -0
- package/dist/native-staged-file.d.ts +2 -2
- package/dist/native-staged-file.d.ts.map +1 -1
- package/dist/native-staged-file.js +42 -40
- package/dist/output.d.ts.map +1 -1
- package/dist/output.js +12 -8
- package/dist/path-prefix.d.ts.map +1 -1
- package/dist/path-prefix.js +30 -8
- package/dist/path-suffix-aliases.d.ts +2 -0
- package/dist/path-suffix-aliases.d.ts.map +1 -1
- package/dist/path-suffix-aliases.js +25 -17
- package/dist/permission-exec.d.ts +2 -0
- package/dist/permission-exec.d.ts.map +1 -1
- package/dist/permission-exec.js +150 -21
- package/dist/permissions-windows.js +1 -1
- package/dist/pinned-mutation-admission.d.ts.map +1 -1
- package/dist/pinned-mutation-admission.js +23 -29
- package/dist/pinned-mutation-observation.d.ts +7 -2
- package/dist/pinned-mutation-observation.d.ts.map +1 -1
- package/dist/pinned-mutation-observation.js +81 -18
- package/dist/pinned-mutation-shared-route.d.ts +1 -0
- package/dist/pinned-mutation-shared-route.d.ts.map +1 -1
- package/dist/pinned-mutation-shared-route.js +1 -1
- package/dist/pinned-write-types.d.ts +2 -0
- package/dist/pinned-write-types.d.ts.map +1 -1
- package/dist/private-temp-workspace.d.ts.map +1 -1
- package/dist/private-temp-workspace.js +75 -121
- package/dist/regular-file.d.ts.map +1 -1
- package/dist/regular-file.js +1 -15
- package/dist/replace-directory.d.ts.map +1 -1
- package/dist/replace-directory.js +256 -18
- package/dist/replace-file-copy-fallback.d.ts.map +1 -1
- package/dist/replace-file-copy-fallback.js +62 -70
- package/dist/replace-file-copy-source.d.ts.map +1 -1
- package/dist/replace-file-copy-source.js +10 -12
- package/dist/replace-file-temp-owner.d.ts +5 -2
- package/dist/replace-file-temp-owner.d.ts.map +1 -1
- package/dist/replace-file-temp-owner.js +65 -30
- package/dist/replace-file.js +6 -6
- package/dist/retained-directory-replacement.d.ts +26 -0
- package/dist/retained-directory-replacement.d.ts.map +1 -0
- package/dist/retained-directory-replacement.js +193 -0
- package/dist/root-boundary.d.ts +1 -0
- package/dist/root-boundary.d.ts.map +1 -1
- package/dist/root-boundary.js +4 -0
- package/dist/root-context.d.ts +0 -8
- package/dist/root-context.d.ts.map +1 -1
- package/dist/root-context.js +0 -3
- package/dist/root-create-input.d.ts +6 -0
- package/dist/root-create-input.d.ts.map +1 -1
- package/dist/root-create-input.js +5 -1
- package/dist/root-directory-creation.d.ts.map +1 -1
- package/dist/root-directory-creation.js +5 -4
- package/dist/root-directory-list.d.ts +1 -0
- package/dist/root-directory-list.d.ts.map +1 -1
- package/dist/root-directory-list.js +1 -0
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +20 -11
- package/dist/root-move-noreplace.d.ts.map +1 -1
- package/dist/root-move-noreplace.js +2 -2
- package/dist/root-path-errors.d.ts +1 -0
- package/dist/root-path-errors.d.ts.map +1 -1
- package/dist/root-path-errors.js +11 -2
- package/dist/root-path-existing.d.ts.map +1 -1
- package/dist/root-path-existing.js +12 -37
- package/dist/root-path.js +1 -13
- package/dist/root-remove.d.ts +1 -0
- package/dist/root-remove.d.ts.map +1 -1
- package/dist/root-remove.js +4 -0
- package/dist/root-walk.d.ts +1 -1
- package/dist/root-walk.d.ts.map +1 -1
- package/dist/root-walk.js +17 -2
- package/dist/root-write-admission.d.ts +0 -2
- package/dist/root-write-admission.d.ts.map +1 -1
- package/dist/root-write-admission.js +1 -15
- package/dist/root-write-compatibility.d.ts +1 -2
- package/dist/root-write-compatibility.d.ts.map +1 -1
- package/dist/root-write-compatibility.js +17 -68
- package/dist/root-write-complete-parent.d.ts.map +1 -1
- package/dist/root-write-complete-parent.js +10 -24
- package/dist/root-write-lock-binding.d.ts +15 -0
- package/dist/root-write-lock-binding.d.ts.map +1 -0
- package/dist/root-write-lock-binding.js +162 -0
- package/dist/root-write-verification.d.ts.map +1 -1
- package/dist/root-write-verification.js +29 -42
- package/dist/secret-file.d.ts.map +1 -1
- package/dist/secret-file.js +2 -24
- package/dist/secret-read-async.d.ts.map +1 -1
- package/dist/secret-read-async.js +3 -24
- package/dist/secret-read-policy.d.ts +6 -2
- package/dist/secret-read-policy.d.ts.map +1 -1
- package/dist/secret-read-policy.js +26 -2
- package/dist/sibling-temp.d.ts.map +1 -1
- package/dist/sibling-temp.js +23 -12
- package/dist/sidecar-lock-acquire.d.ts +2 -28
- package/dist/sidecar-lock-acquire.d.ts.map +1 -1
- package/dist/sidecar-lock-acquire.js +288 -199
- package/dist/sidecar-lock-admission-context.d.ts +19 -0
- package/dist/sidecar-lock-admission-context.d.ts.map +1 -0
- package/dist/sidecar-lock-admission-context.js +60 -0
- package/dist/sidecar-lock-admission-parser.d.ts +43 -0
- package/dist/sidecar-lock-admission-parser.d.ts.map +1 -0
- package/dist/sidecar-lock-admission-parser.js +113 -0
- package/dist/sidecar-lock-admission.d.ts +35 -0
- package/dist/sidecar-lock-admission.d.ts.map +1 -0
- package/dist/sidecar-lock-admission.js +7 -0
- package/dist/sidecar-lock-reclaim.d.ts +9 -4
- package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
- package/dist/sidecar-lock-reclaim.js +80 -25
- package/dist/sidecar-lock-stale-admission.d.ts +39 -0
- package/dist/sidecar-lock-stale-admission.d.ts.map +1 -0
- package/dist/sidecar-lock-stale-admission.js +232 -0
- package/dist/sidecar-lock-target.d.ts +8 -0
- package/dist/sidecar-lock-target.d.ts.map +1 -0
- package/dist/sidecar-lock-target.js +55 -0
- package/dist/sidecar-lock.d.ts.map +1 -1
- package/dist/sidecar-lock.js +100 -16
- package/dist/staged-directory.d.ts +5 -2
- package/dist/staged-directory.d.ts.map +1 -1
- package/dist/staged-directory.js +37 -1
- package/dist/temp-workspace-admission.d.ts.map +1 -1
- package/dist/temp-workspace-admission.js +28 -4
- package/dist/temp-workspace-descriptor.d.ts.map +1 -1
- package/dist/temp-workspace-descriptor.js +9 -27
- package/dist/temp-workspace-owner.d.ts.map +1 -1
- package/dist/temp-workspace-owner.js +8 -8
- package/dist/walk.d.ts +5 -1
- package/dist/walk.d.ts.map +1 -1
- package/dist/walk.js +19 -6
- package/dist/windows-owner.d.ts.map +1 -1
- package/dist/windows-owner.js +2 -2
- package/docs/advanced.md +1 -1
- package/docs/archive.md +36 -9
- package/docs/atomic.md +85 -8
- package/docs/copy.md +35 -0
- package/docs/file-store.md +19 -0
- package/docs/guest.md +1 -1
- package/docs/json-store.md +5 -0
- package/docs/native-helper.md +10 -3
- package/docs/native.md +8 -0
- package/docs/output.md +6 -0
- package/docs/path-prefix.md +10 -0
- package/docs/path-suffix-aliases.md +51 -6
- package/docs/permissions.md +13 -0
- package/docs/public-api.md +3 -2
- package/docs/root.md +22 -3
- package/docs/sidecar-lock.md +109 -4
- package/docs/staged-file.md +7 -3
- package/docs/temp.md +20 -3
- package/docs/walk.md +67 -1
- package/docs/writing.md +11 -1
- package/package.json +10 -10
- package/dist/darwin-acl.d.ts +0 -4
- package/dist/darwin-acl.d.ts.map +0 -1
- package/dist/darwin-acl.js +0 -24
|
@@ -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;AAQ/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;
|
|
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;AAQ/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"}
|
|
@@ -10,10 +10,28 @@ const NOFOLLOW = SUPPORTS_NOFOLLOW ? syncFs.constants.O_NOFOLLOW : 0;
|
|
|
10
10
|
const OPEN_READ_FLAGS = resolveReadOpenFlags();
|
|
11
11
|
const OPEN_READ_WRITE_FLAGS = syncFs.constants.O_RDWR | NOFOLLOW;
|
|
12
12
|
const OPEN_WRITE_EXCLUSIVE_FLAGS = syncFs.constants.O_WRONLY | syncFs.constants.O_CREAT | syncFs.constants.O_EXCL | NOFOLLOW;
|
|
13
|
+
function closeSyncAfterAdmissionFailure(fsModule, fd, error) {
|
|
14
|
+
try {
|
|
15
|
+
fsModule.closeSync(fd);
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
// Preserve the already-selected admission failure.
|
|
19
|
+
}
|
|
20
|
+
throw error;
|
|
21
|
+
}
|
|
13
22
|
function notFound(error) {
|
|
14
23
|
return error.code === "ENOENT";
|
|
15
24
|
}
|
|
16
|
-
function assertPinnedDestination(pathname, opened, dest, hardlinks) {
|
|
25
|
+
function assertPinnedDestination(pathname, opened, dest, hardlinks, admission) {
|
|
26
|
+
if (admission === "hardlinks") {
|
|
27
|
+
if (pathname.isSymbolicLink() || !pathname.isFile() || !sameFileIdentity(pathname, opened)) {
|
|
28
|
+
throw new FsSafeError("path-mismatch", `Atomic replace destination changed while opening: ${dest}`);
|
|
29
|
+
}
|
|
30
|
+
if (opened.nlink > 1) {
|
|
31
|
+
throw new FsSafeError("hardlink", `Hardlinked atomic replace destination not allowed: ${dest}`);
|
|
32
|
+
}
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
17
35
|
if (pathname.isSymbolicLink()) {
|
|
18
36
|
throw new FsSafeError("symlink", `Refusing copy fallback through symlink destination: ${dest}`);
|
|
19
37
|
}
|
|
@@ -27,7 +45,7 @@ function assertPinnedDestination(pathname, opened, dest, hardlinks) {
|
|
|
27
45
|
throw new FsSafeError("hardlink", `Hardlinked copy fallback destination not allowed: ${dest}`);
|
|
28
46
|
}
|
|
29
47
|
}
|
|
30
|
-
async function openPinnedDestination(fsModule, dest, hardlinks) {
|
|
48
|
+
async function openPinnedDestination(fsModule, dest, admission, hardlinks) {
|
|
31
49
|
let preview;
|
|
32
50
|
try {
|
|
33
51
|
preview = fsModule === fs ? syncFs.lstatSync(dest) : await fsModule.lstat(dest);
|
|
@@ -39,22 +57,24 @@ async function openPinnedDestination(fsModule, dest, hardlinks) {
|
|
|
39
57
|
}
|
|
40
58
|
if (!preview)
|
|
41
59
|
return null;
|
|
42
|
-
if (preview.isSymbolicLink())
|
|
60
|
+
if (admission === "hardlinks" && (preview.isSymbolicLink() || !preview.isFile()))
|
|
61
|
+
return null;
|
|
62
|
+
if (admission === "restore" && preview.isSymbolicLink()) {
|
|
43
63
|
throw new FsSafeError("symlink", `Refusing copy fallback through symlink destination: ${dest}`);
|
|
44
64
|
}
|
|
45
|
-
const handle = await fsModule.open(dest, OPEN_READ_WRITE_FLAGS);
|
|
65
|
+
const handle = await fsModule.open(dest, admission === "restore" ? OPEN_READ_WRITE_FLAGS : OPEN_READ_FLAGS);
|
|
46
66
|
try {
|
|
47
67
|
const opened = fsModule === fs ? syncFs.fstatSync(handle.fd) : await handle.stat();
|
|
48
68
|
const current = fsModule === fs ? syncFs.lstatSync(dest) : await fsModule.lstat(dest);
|
|
49
|
-
assertPinnedDestination(current, opened, dest, hardlinks);
|
|
50
|
-
return
|
|
69
|
+
assertPinnedDestination(current, opened, dest, hardlinks, admission);
|
|
70
|
+
return handle;
|
|
51
71
|
}
|
|
52
72
|
catch (error) {
|
|
53
73
|
await handle.close().catch(() => undefined);
|
|
54
74
|
throw error;
|
|
55
75
|
}
|
|
56
76
|
}
|
|
57
|
-
function openPinnedDestinationSync(fsModule, dest, hardlinks) {
|
|
77
|
+
function openPinnedDestinationSync(fsModule, dest, admission, hardlinks) {
|
|
58
78
|
let preview;
|
|
59
79
|
try {
|
|
60
80
|
preview = fsModule.lstatSync(dest);
|
|
@@ -64,77 +84,34 @@ function openPinnedDestinationSync(fsModule, dest, hardlinks) {
|
|
|
64
84
|
return null;
|
|
65
85
|
throw error;
|
|
66
86
|
}
|
|
67
|
-
if (preview.isSymbolicLink())
|
|
87
|
+
if (admission === "hardlinks" && (preview.isSymbolicLink() || !preview.isFile()))
|
|
88
|
+
return null;
|
|
89
|
+
if (admission === "restore" && preview.isSymbolicLink()) {
|
|
68
90
|
throw new FsSafeError("symlink", `Refusing copy fallback through symlink destination: ${dest}`);
|
|
69
91
|
}
|
|
70
|
-
const fd = fsModule.openSync(dest, OPEN_READ_WRITE_FLAGS);
|
|
92
|
+
const fd = fsModule.openSync(dest, admission === "restore" ? OPEN_READ_WRITE_FLAGS : OPEN_READ_FLAGS);
|
|
71
93
|
try {
|
|
72
94
|
const opened = fsModule.fstatSync(fd);
|
|
73
|
-
assertPinnedDestination(fsModule.lstatSync(dest), opened, dest, hardlinks);
|
|
74
|
-
return
|
|
95
|
+
assertPinnedDestination(fsModule.lstatSync(dest), opened, dest, hardlinks, admission);
|
|
96
|
+
return fd;
|
|
75
97
|
}
|
|
76
98
|
catch (error) {
|
|
77
|
-
fsModule
|
|
78
|
-
throw error;
|
|
99
|
+
closeSyncAfterAdmissionFailure(fsModule, fd, error);
|
|
79
100
|
}
|
|
80
101
|
}
|
|
81
102
|
export async function assertDestinationHardlinkPolicy(fsModule, dest, policy) {
|
|
82
103
|
if (policy !== "reject")
|
|
83
104
|
return;
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
preview = fsModule === fs ? syncFs.lstatSync(dest) : await fsModule.lstat(dest);
|
|
87
|
-
}
|
|
88
|
-
catch (error) {
|
|
89
|
-
if (notFound(error))
|
|
90
|
-
return;
|
|
91
|
-
throw error;
|
|
92
|
-
}
|
|
93
|
-
if (!preview || preview.isSymbolicLink() || !preview.isFile())
|
|
94
|
-
return;
|
|
95
|
-
const handle = await fsModule.open(dest, OPEN_READ_FLAGS);
|
|
96
|
-
try {
|
|
97
|
-
const opened = fsModule === fs ? syncFs.fstatSync(handle.fd) : await handle.stat();
|
|
98
|
-
const current = fsModule === fs ? syncFs.lstatSync(dest) : await fsModule.lstat(dest);
|
|
99
|
-
if (current.isSymbolicLink() || !current.isFile() || !sameFileIdentity(current, opened)) {
|
|
100
|
-
throw new FsSafeError("path-mismatch", `Atomic replace destination changed while opening: ${dest}`);
|
|
101
|
-
}
|
|
102
|
-
if (opened.nlink > 1) {
|
|
103
|
-
throw new FsSafeError("hardlink", `Hardlinked atomic replace destination not allowed: ${dest}`);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
finally {
|
|
105
|
+
const handle = await openPinnedDestination(fsModule, dest, "hardlinks");
|
|
106
|
+
if (handle)
|
|
107
107
|
await handle.close().catch(() => undefined);
|
|
108
|
-
}
|
|
109
108
|
}
|
|
110
109
|
export function assertDestinationHardlinkPolicySync(fsModule, dest, policy) {
|
|
111
110
|
if (policy !== "reject")
|
|
112
111
|
return;
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
preview = fsModule.lstatSync(dest);
|
|
116
|
-
}
|
|
117
|
-
catch (error) {
|
|
118
|
-
if (notFound(error))
|
|
119
|
-
return;
|
|
120
|
-
throw error;
|
|
121
|
-
}
|
|
122
|
-
if (preview.isSymbolicLink() || !preview.isFile())
|
|
123
|
-
return;
|
|
124
|
-
const fd = fsModule.openSync(dest, OPEN_READ_FLAGS);
|
|
125
|
-
try {
|
|
126
|
-
const opened = fsModule.fstatSync(fd);
|
|
127
|
-
const current = fsModule.lstatSync(dest);
|
|
128
|
-
if (current.isSymbolicLink() || !current.isFile() || !sameFileIdentity(current, opened)) {
|
|
129
|
-
throw new FsSafeError("path-mismatch", `Atomic replace destination changed while opening: ${dest}`);
|
|
130
|
-
}
|
|
131
|
-
if (opened.nlink > 1) {
|
|
132
|
-
throw new FsSafeError("hardlink", `Hardlinked atomic replace destination not allowed: ${dest}`);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
finally {
|
|
112
|
+
const fd = openPinnedDestinationSync(fsModule, dest, "hardlinks");
|
|
113
|
+
if (fd !== null)
|
|
136
114
|
fsModule.closeSync(fd);
|
|
137
|
-
}
|
|
138
115
|
}
|
|
139
116
|
function restoreReadOptions(stat, maxBytes) {
|
|
140
117
|
return {
|
|
@@ -183,7 +160,7 @@ function writeAllSync(fsModule, fd, data) {
|
|
|
183
160
|
function restoreFailure(writeError, cleanup, restoreError) {
|
|
184
161
|
const primary = writeError instanceof Error ? writeError : new Error(String(writeError));
|
|
185
162
|
const details = { cleanup };
|
|
186
|
-
const cause =
|
|
163
|
+
const cause = cleanup === "restore-failed"
|
|
187
164
|
? new AggregateError([primary, restoreError], "copy fallback and original restoration both failed")
|
|
188
165
|
: primary;
|
|
189
166
|
return new FsSafeError("helper-failed", cleanup === "restored"
|
|
@@ -246,11 +223,12 @@ export async function copyFallbackReplace(params) {
|
|
|
246
223
|
});
|
|
247
224
|
const { replacement } = source;
|
|
248
225
|
let destHandle = null;
|
|
226
|
+
let closeRequiredForSuccess = false;
|
|
249
227
|
try {
|
|
250
228
|
if (params.restore === "restore-original") {
|
|
251
|
-
const pinned = await openPinnedDestination(params.fsModule, params.dest, params.destinationHardlinks);
|
|
229
|
+
const pinned = await openPinnedDestination(params.fsModule, params.dest, "restore", params.destinationHardlinks);
|
|
252
230
|
if (pinned) {
|
|
253
|
-
destHandle = pinned
|
|
231
|
+
destHandle = pinned;
|
|
254
232
|
await replacePinnedWithRestore(params.fsModule, destHandle, replacement, params.maxRestoreBytes, source.mode);
|
|
255
233
|
}
|
|
256
234
|
}
|
|
@@ -277,10 +255,20 @@ export async function copyFallbackReplace(params) {
|
|
|
277
255
|
if (params.sync) {
|
|
278
256
|
await destHandle.sync();
|
|
279
257
|
}
|
|
258
|
+
closeRequiredForSuccess = !params.sync;
|
|
280
259
|
}
|
|
281
260
|
}
|
|
282
261
|
finally {
|
|
283
|
-
|
|
262
|
+
if (destHandle) {
|
|
263
|
+
try {
|
|
264
|
+
await destHandle.close();
|
|
265
|
+
}
|
|
266
|
+
catch (closeError) {
|
|
267
|
+
if (closeRequiredForSuccess) {
|
|
268
|
+
throw closeError;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
284
272
|
}
|
|
285
273
|
}
|
|
286
274
|
export function copyFallbackReplaceSync(params) {
|
|
@@ -291,11 +279,12 @@ export function copyFallbackReplaceSync(params) {
|
|
|
291
279
|
});
|
|
292
280
|
const { replacement } = source;
|
|
293
281
|
let destFd;
|
|
282
|
+
let closeRequiredForSuccess = false;
|
|
294
283
|
try {
|
|
295
284
|
if (params.restore === "restore-original") {
|
|
296
|
-
const pinned = openPinnedDestinationSync(params.fsModule, params.dest, params.destinationHardlinks);
|
|
297
|
-
if (pinned) {
|
|
298
|
-
destFd = pinned
|
|
285
|
+
const pinned = openPinnedDestinationSync(params.fsModule, params.dest, "restore", params.destinationHardlinks);
|
|
286
|
+
if (pinned !== null) {
|
|
287
|
+
destFd = pinned;
|
|
299
288
|
replacePinnedWithRestoreSync(params.fsModule, destFd, replacement, params.maxRestoreBytes, source.mode, params.fchmodSync);
|
|
300
289
|
}
|
|
301
290
|
}
|
|
@@ -321,6 +310,7 @@ export function copyFallbackReplaceSync(params) {
|
|
|
321
310
|
if (params.sync) {
|
|
322
311
|
params.fsModule.fsyncSync(destFd);
|
|
323
312
|
}
|
|
313
|
+
closeRequiredForSuccess = !params.sync;
|
|
324
314
|
}
|
|
325
315
|
}
|
|
326
316
|
finally {
|
|
@@ -328,8 +318,10 @@ export function copyFallbackReplaceSync(params) {
|
|
|
328
318
|
try {
|
|
329
319
|
params.fsModule.closeSync(destFd);
|
|
330
320
|
}
|
|
331
|
-
catch {
|
|
332
|
-
|
|
321
|
+
catch (closeError) {
|
|
322
|
+
if (closeRequiredForSuccess) {
|
|
323
|
+
throw closeError;
|
|
324
|
+
}
|
|
333
325
|
}
|
|
334
326
|
}
|
|
335
327
|
}
|
|
@@ -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,
|
|
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,CAyBjD;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,CAyBxC"}
|
|
@@ -17,6 +17,14 @@ function assertSourcePreview(source, src) {
|
|
|
17
17
|
throw new FsSafeError("hardlink", `Hardlinked copy fallback source not allowed: ${src}`);
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
|
+
function assertOpenedSource(opened, current, src) {
|
|
21
|
+
if (!opened.isFile() || current.isSymbolicLink() || !sameFileIdentity(opened, current)) {
|
|
22
|
+
throw new FsSafeError("path-mismatch", `Copy fallback source changed while opening: ${src}`);
|
|
23
|
+
}
|
|
24
|
+
if (opened.nlink !== 1) {
|
|
25
|
+
throw new FsSafeError("hardlink", `Hardlinked copy fallback source not allowed: ${src}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
20
28
|
function sourceSymlinkError(src, cause) {
|
|
21
29
|
return new FsSafeError("symlink", `Refusing copy fallback from non-file source: ${src}`, {
|
|
22
30
|
cause,
|
|
@@ -66,12 +74,7 @@ export async function readOwnedCopySource(params) {
|
|
|
66
74
|
const opened = params.fsModule === fs ? syncFs.fstatSync(handle.fd) : await handle.stat();
|
|
67
75
|
const current = params.fsModule === fs
|
|
68
76
|
? syncFs.lstatSync(params.src) : await params.fsModule.lstat(params.src);
|
|
69
|
-
|
|
70
|
-
throw new FsSafeError("path-mismatch", `Copy fallback source changed while opening: ${params.src}`);
|
|
71
|
-
}
|
|
72
|
-
if (opened.nlink !== 1) {
|
|
73
|
-
throw new FsSafeError("hardlink", `Hardlinked copy fallback source not allowed: ${params.src}`);
|
|
74
|
-
}
|
|
77
|
+
assertOpenedSource(opened, current, params.src);
|
|
75
78
|
return { replacement: await handle.readFile(), mode: opened.mode };
|
|
76
79
|
}
|
|
77
80
|
finally {
|
|
@@ -88,12 +91,7 @@ export function readOwnedCopySourceSync(params) {
|
|
|
88
91
|
}
|
|
89
92
|
const opened = params.fsModule.fstatSync(fd);
|
|
90
93
|
const current = params.fsModule.lstatSync(params.src);
|
|
91
|
-
|
|
92
|
-
throw new FsSafeError("path-mismatch", `Copy fallback source changed while opening: ${params.src}`);
|
|
93
|
-
}
|
|
94
|
-
if (opened.nlink !== 1) {
|
|
95
|
-
throw new FsSafeError("hardlink", `Hardlinked copy fallback source not allowed: ${params.src}`);
|
|
96
|
-
}
|
|
94
|
+
assertOpenedSource(opened, current, params.src);
|
|
97
95
|
return { replacement: readAllSync(params.fsModule, fd, opened.size), mode: opened.mode };
|
|
98
96
|
}
|
|
99
97
|
finally {
|
|
@@ -3,6 +3,9 @@ import fs, { type FileHandle } from "node:fs/promises";
|
|
|
3
3
|
import { type AnyAsyncDirectoryGuard } from "./directory-guard.js";
|
|
4
4
|
type AsyncOwnerFileSystem = Pick<typeof fs, "lstat" | "open" | "unlink">;
|
|
5
5
|
type SyncOwnerFileSystem = Pick<typeof syncFs, "closeSync" | "fstatSync" | "lstatSync" | "openSync" | "readFileSync" | "unlinkSync">;
|
|
6
|
+
export type AtomicTempFailure = Readonly<{
|
|
7
|
+
error: unknown;
|
|
8
|
+
}>;
|
|
6
9
|
export declare function removePathIfIdentityUnchanged(targetPath: string, identity: Pick<BigIntStats, "dev" | "ino">): Promise<void>;
|
|
7
10
|
export declare function cleanupPinnedFilePath(params: {
|
|
8
11
|
pathname: string;
|
|
@@ -26,7 +29,7 @@ export declare class AsyncAtomicTempOwner {
|
|
|
26
29
|
markRenamed(): void;
|
|
27
30
|
finish(params: {
|
|
28
31
|
fsModule: AsyncOwnerFileSystem;
|
|
29
|
-
|
|
32
|
+
originalFailure?: AtomicTempFailure;
|
|
30
33
|
throwOnCleanupError: boolean;
|
|
31
34
|
}): Promise<void>;
|
|
32
35
|
}
|
|
@@ -46,7 +49,7 @@ export declare class SyncAtomicTempOwner {
|
|
|
46
49
|
markRenamed(): void;
|
|
47
50
|
finish(params: {
|
|
48
51
|
fsModule: SyncOwnerFileSystem;
|
|
49
|
-
|
|
52
|
+
originalFailure?: AtomicTempFailure;
|
|
50
53
|
throwOnCleanupError: boolean;
|
|
51
54
|
}): void;
|
|
52
55
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"replace-file-temp-owner.d.ts","sourceRoot":"","sources":["../src/replace-file-temp-owner.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,EAAE,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,EAAE,EAAE,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEvD,OAAO,EAAwD,KAAK,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAQzH,KAAK,oBAAoB,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAC,CAAC;AACzE,KAAK,mBAAmB,GAAG,IAAI,CAC7B,OAAO,MAAM,EACb,WAAW,GAAG,WAAW,GAAG,WAAW,GAAG,UAAU,GAAG,cAAc,GAAG,YAAY,CACrF,CAAC;AAIF,wBAAsB,6BAA6B,CACjD,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,IAAI,CAAC,WAAW,EAAE,KAAK,GAAG,KAAK,CAAC,GACzC,OAAO,CAAC,IAAI,CAAC,CAOf;
|
|
1
|
+
{"version":3,"file":"replace-file-temp-owner.d.ts","sourceRoot":"","sources":["../src/replace-file-temp-owner.ts"],"names":[],"mappings":"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"}
|
|
@@ -9,6 +9,33 @@ import { withAsyncDirectoryGuards } from "./guarded-mutation.js";
|
|
|
9
9
|
import { inspectFileIdentity, inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
10
10
|
import { registerTempPathForExit } from "./temp-cleanup.js";
|
|
11
11
|
const PUBLISHED_READ_FLAGS = resolveReadOpenFlags();
|
|
12
|
+
function hasErrorCode(error, expected) {
|
|
13
|
+
if ((typeof error !== "object" || error === null) && typeof error !== "function") {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
try {
|
|
17
|
+
return Reflect.get(error, "code") === expected;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function describeFailure(error) {
|
|
24
|
+
try {
|
|
25
|
+
return String(error);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return "<unprintable failure>";
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function isErrorValue(error) {
|
|
32
|
+
try {
|
|
33
|
+
return error instanceof Error;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
12
39
|
export async function removePathIfIdentityUnchanged(targetPath, identity) {
|
|
13
40
|
const parentGuard = await createAsyncDirectoryGuard(path.dirname(targetPath), { bigint: true });
|
|
14
41
|
await withAsyncDirectoryGuards([parentGuard], async () => {
|
|
@@ -34,11 +61,11 @@ function missingOwnedFile(pathname, cause) {
|
|
|
34
61
|
cause,
|
|
35
62
|
});
|
|
36
63
|
}
|
|
37
|
-
function cleanupFailure(
|
|
38
|
-
if (
|
|
39
|
-
return new Error(`Atomic file replace failed (${
|
|
64
|
+
function cleanupFailure(originalFailure, cleanupError) {
|
|
65
|
+
if (originalFailure) {
|
|
66
|
+
return new Error(`Atomic file replace failed (${describeFailure(originalFailure.error)}); cleanup also failed (${describeFailure(cleanupError)})`, { cause: originalFailure.error });
|
|
40
67
|
}
|
|
41
|
-
return cleanupError
|
|
68
|
+
return isErrorValue(cleanupError) ? cleanupError : new Error(describeFailure(cleanupError));
|
|
42
69
|
}
|
|
43
70
|
async function cleanupOwnedPath(params) {
|
|
44
71
|
if (!params.identity)
|
|
@@ -57,10 +84,10 @@ async function cleanupOwnedPath(params) {
|
|
|
57
84
|
return true;
|
|
58
85
|
}
|
|
59
86
|
catch (cleanupError) {
|
|
60
|
-
if (cleanupError
|
|
87
|
+
if (hasErrorCode(cleanupError, "ENOENT"))
|
|
61
88
|
return true;
|
|
62
89
|
if (params.throwOnCleanupError) {
|
|
63
|
-
throw cleanupFailure(params.
|
|
90
|
+
throw cleanupFailure(params.originalFailure, cleanupError);
|
|
64
91
|
}
|
|
65
92
|
return false;
|
|
66
93
|
}
|
|
@@ -108,10 +135,10 @@ function cleanupOwnedPathSync(params) {
|
|
|
108
135
|
return true;
|
|
109
136
|
}
|
|
110
137
|
catch (cleanupError) {
|
|
111
|
-
if (cleanupError
|
|
138
|
+
if (hasErrorCode(cleanupError, "ENOENT"))
|
|
112
139
|
return true;
|
|
113
140
|
if (params.throwOnCleanupError) {
|
|
114
|
-
throw cleanupFailure(params.
|
|
141
|
+
throw cleanupFailure(params.originalFailure, cleanupError);
|
|
115
142
|
}
|
|
116
143
|
return false;
|
|
117
144
|
}
|
|
@@ -202,7 +229,9 @@ export class AsyncAtomicTempOwner {
|
|
|
202
229
|
if (sha256Hex(await published.readFile()) !== expectedHash) {
|
|
203
230
|
throw new FsSafeError("path-mismatch", `Atomic replace published content changed: ${pathname}`);
|
|
204
231
|
}
|
|
205
|
-
|
|
232
|
+
const previousHandle = this.#handle;
|
|
233
|
+
this.#handle = undefined;
|
|
234
|
+
await previousHandle?.close();
|
|
206
235
|
this.#handle = published;
|
|
207
236
|
this.#identity = identity;
|
|
208
237
|
published = undefined;
|
|
@@ -216,7 +245,7 @@ export class AsyncAtomicTempOwner {
|
|
|
216
245
|
this.#unregister();
|
|
217
246
|
}
|
|
218
247
|
async finish(params) {
|
|
219
|
-
let
|
|
248
|
+
let deferredFailure;
|
|
220
249
|
let cleanupComplete = !this.#exists;
|
|
221
250
|
if (this.#exists) {
|
|
222
251
|
try {
|
|
@@ -224,28 +253,32 @@ export class AsyncAtomicTempOwner {
|
|
|
224
253
|
fsModule: params.fsModule,
|
|
225
254
|
pathname: this.pathname,
|
|
226
255
|
identity: this.#identity,
|
|
227
|
-
|
|
256
|
+
originalFailure: params.originalFailure,
|
|
228
257
|
throwOnCleanupError: params.throwOnCleanupError,
|
|
229
258
|
});
|
|
230
259
|
}
|
|
231
260
|
catch (error) {
|
|
232
|
-
|
|
261
|
+
deferredFailure = { error };
|
|
233
262
|
}
|
|
234
263
|
}
|
|
235
264
|
if (cleanupComplete)
|
|
236
265
|
this.#unregister();
|
|
266
|
+
const handle = this.#handle;
|
|
267
|
+
this.#handle = undefined;
|
|
237
268
|
try {
|
|
238
|
-
await
|
|
269
|
+
await handle?.close();
|
|
239
270
|
}
|
|
240
271
|
catch (closeError) {
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
272
|
+
deferredFailure = {
|
|
273
|
+
error: deferredFailure
|
|
274
|
+
? new AggregateError([deferredFailure.error, closeError], "Atomic temp cleanup and close failed")
|
|
275
|
+
: params.originalFailure
|
|
276
|
+
? new AggregateError([params.originalFailure.error, closeError], "Atomic file replace and close failed")
|
|
277
|
+
: closeError,
|
|
278
|
+
};
|
|
246
279
|
}
|
|
247
|
-
if (
|
|
248
|
-
throw
|
|
280
|
+
if (deferredFailure)
|
|
281
|
+
throw deferredFailure.error;
|
|
249
282
|
}
|
|
250
283
|
}
|
|
251
284
|
export class SyncAtomicTempOwner {
|
|
@@ -353,7 +386,7 @@ export class SyncAtomicTempOwner {
|
|
|
353
386
|
this.#unregister();
|
|
354
387
|
}
|
|
355
388
|
finish(params) {
|
|
356
|
-
let
|
|
389
|
+
let deferredFailure;
|
|
357
390
|
let cleanupComplete = !this.#exists;
|
|
358
391
|
if (this.#exists) {
|
|
359
392
|
try {
|
|
@@ -361,12 +394,12 @@ export class SyncAtomicTempOwner {
|
|
|
361
394
|
fsModule: params.fsModule,
|
|
362
395
|
pathname: this.pathname,
|
|
363
396
|
identity: this.#identity,
|
|
364
|
-
|
|
397
|
+
originalFailure: params.originalFailure,
|
|
365
398
|
throwOnCleanupError: params.throwOnCleanupError,
|
|
366
399
|
});
|
|
367
400
|
}
|
|
368
401
|
catch (error) {
|
|
369
|
-
|
|
402
|
+
deferredFailure = { error };
|
|
370
403
|
}
|
|
371
404
|
}
|
|
372
405
|
if (cleanupComplete)
|
|
@@ -378,14 +411,16 @@ export class SyncAtomicTempOwner {
|
|
|
378
411
|
params.fsModule.closeSync(fd);
|
|
379
412
|
}
|
|
380
413
|
catch (closeError) {
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
414
|
+
deferredFailure = {
|
|
415
|
+
error: deferredFailure
|
|
416
|
+
? new AggregateError([deferredFailure.error, closeError], "Atomic temp cleanup and close failed")
|
|
417
|
+
: params.originalFailure
|
|
418
|
+
? new AggregateError([params.originalFailure.error, closeError], "Atomic file replace and close failed")
|
|
419
|
+
: closeError,
|
|
420
|
+
};
|
|
386
421
|
}
|
|
387
422
|
}
|
|
388
|
-
if (
|
|
389
|
-
throw
|
|
423
|
+
if (deferredFailure)
|
|
424
|
+
throw deferredFailure.error;
|
|
390
425
|
}
|
|
391
426
|
}
|
package/dist/replace-file.js
CHANGED
|
@@ -171,7 +171,7 @@ async function replaceFileAtomicUnserialized(options, filePath, renameIdentity,
|
|
|
171
171
|
const expectedHash = atomicExpectedContentHash(renameIdentity, options.content);
|
|
172
172
|
const tempPath = buildReplaceTempPath(filePath, options.tempPrefix);
|
|
173
173
|
const tempOwner = new AsyncAtomicTempOwner(tempPath);
|
|
174
|
-
let
|
|
174
|
+
let originalFailure;
|
|
175
175
|
try {
|
|
176
176
|
await fsModule.mkdir(fsModule === fs ? recursiveMkdirPath(dir) : dir, { recursive: true, mode: dirMode });
|
|
177
177
|
await applyDirectoryMode({ fsModule, dirPath: dir, mode: dirMode });
|
|
@@ -229,13 +229,13 @@ async function replaceFileAtomicUnserialized(options, filePath, renameIdentity,
|
|
|
229
229
|
return result;
|
|
230
230
|
}
|
|
231
231
|
catch (error) {
|
|
232
|
-
|
|
232
|
+
originalFailure = { error };
|
|
233
233
|
throw error;
|
|
234
234
|
}
|
|
235
235
|
finally {
|
|
236
236
|
await tempOwner.finish({
|
|
237
237
|
fsModule,
|
|
238
|
-
|
|
238
|
+
originalFailure,
|
|
239
239
|
throwOnCleanupError: options.throwOnCleanupError === true,
|
|
240
240
|
});
|
|
241
241
|
}
|
|
@@ -271,7 +271,7 @@ function replaceFileAtomicSyncUnserialized(options, filePath, renameIdentity) {
|
|
|
271
271
|
}
|
|
272
272
|
const tempPath = buildReplaceTempPath(filePath, options.tempPrefix);
|
|
273
273
|
const tempOwner = new SyncAtomicTempOwner(tempPath);
|
|
274
|
-
let
|
|
274
|
+
let originalFailure;
|
|
275
275
|
try {
|
|
276
276
|
fsModule.mkdirSync(fsModule === syncFs ? recursiveMkdirPath(dir) : dir, { recursive: true, mode: dirMode });
|
|
277
277
|
applyDirectoryModeSync({ fsModule, dirPath: dir, mode: dirMode, fchmodSync });
|
|
@@ -327,13 +327,13 @@ function replaceFileAtomicSyncUnserialized(options, filePath, renameIdentity) {
|
|
|
327
327
|
return result;
|
|
328
328
|
}
|
|
329
329
|
catch (error) {
|
|
330
|
-
|
|
330
|
+
originalFailure = { error };
|
|
331
331
|
throw error;
|
|
332
332
|
}
|
|
333
333
|
finally {
|
|
334
334
|
tempOwner.finish({
|
|
335
335
|
fsModule,
|
|
336
|
-
|
|
336
|
+
originalFailure,
|
|
337
337
|
throwOnCleanupError: options.throwOnCleanupError === true,
|
|
338
338
|
});
|
|
339
339
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { BigIntStats } from "node:fs";
|
|
2
|
+
import type { NativeBinding, NativeOwnedTreeRemovalResult } from "./native-binding.js";
|
|
3
|
+
type RenameBinding = NativeBinding & Required<Pick<NativeBinding, "renameNoReplaceWithIdentity">>;
|
|
4
|
+
export type RetainedReplacementParent = Readonly<{
|
|
5
|
+
path: string;
|
|
6
|
+
realPath: string;
|
|
7
|
+
identity: Pick<BigIntStats, "dev" | "ino">;
|
|
8
|
+
}>;
|
|
9
|
+
export declare class RetainedDirectoryReplacement {
|
|
10
|
+
#private;
|
|
11
|
+
private constructor();
|
|
12
|
+
static requireBinding(): RenameBinding;
|
|
13
|
+
static retain(params: {
|
|
14
|
+
targetParent: RetainedReplacementParent;
|
|
15
|
+
stagedParent: RetainedReplacementParent;
|
|
16
|
+
originalTarget?: Readonly<{
|
|
17
|
+
path: string;
|
|
18
|
+
identity: Pick<BigIntStats, "dev" | "ino">;
|
|
19
|
+
}>;
|
|
20
|
+
}, binding?: RenameBinding): RetainedDirectoryReplacement;
|
|
21
|
+
renameNoReplace(sourceParent: "staged" | "target", sourceBasename: string, targetBasename: string, expectedSource: Pick<BigIntStats, "dev" | "ino">): void;
|
|
22
|
+
removeOriginal(backupBasename: string): Promise<NativeOwnedTreeRemovalResult["outcome"]>;
|
|
23
|
+
close(): void;
|
|
24
|
+
}
|
|
25
|
+
export {};
|
|
26
|
+
//# sourceMappingURL=retained-directory-replacement.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retained-directory-replacement.d.ts","sourceRoot":"","sources":["../src/retained-directory-replacement.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAI3C,OAAO,KAAK,EAAE,aAAa,EAAE,4BAA4B,EAAE,MAAM,qBAAqB,CAAC;AAGvF,KAAK,aAAa,GAAG,aAAa,GAAG,QAAQ,CAAC,IAAI,CAAC,aAAa,EAC9D,6BAA6B,CAAC,CAAC,CAAC;AAIlC,MAAM,MAAM,yBAAyB,GAAG,QAAQ,CAAC;IAC/C,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,IAAI,CAAC,WAAW,EAAE,KAAK,GAAG,KAAK,CAAC,CAAC;CAC5C,CAAC,CAAC;AAsHH,qBAAa,4BAA4B;;IAQvC,OAAO,eAUN;IAED,MAAM,CAAC,cAAc,IAAI,aAAa,CAQrC;IAED,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE;QACpB,YAAY,EAAE,yBAAyB,CAAC;QACxC,YAAY,EAAE,yBAAyB,CAAC;QACxC,cAAc,CAAC,EAAE,QAAQ,CAAC;YACxB,IAAI,EAAE,MAAM,CAAC;YACb,QAAQ,EAAE,IAAI,CAAC,WAAW,EAAE,KAAK,GAAG,KAAK,CAAC,CAAC;SAC5C,CAAC,CAAC;KACJ,EAAE,OAAO,gBAAgD,GAAG,4BAA4B,CAiDxF;IAED,eAAe,CACb,YAAY,EAAE,QAAQ,GAAG,QAAQ,EACjC,cAAc,EAAE,MAAM,EACtB,cAAc,EAAE,MAAM,EACtB,cAAc,EAAE,IAAI,CAAC,WAAW,EAAE,KAAK,GAAG,KAAK,CAAC,GAC/C,IAAI,CAYN;IAEK,cAAc,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,4BAA4B,CAAC,SAAS,CAAC,CAAC,CA2B7F;IAED,KAAK,IAAI,IAAI,CAQZ;CACF"}
|