@openclaw/fs-safe 0.14.0 → 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 +50 -0
- 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-write.js +3 -3
- package/dist/file-store.d.ts.map +1 -1
- package/dist/file-store.js +47 -12
- 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 +2 -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 +7 -11
- 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 +10 -5
- package/dist/pinned-mutation-observation.d.ts +0 -1
- package/dist/pinned-mutation-observation.d.ts.map +1 -1
- package/dist/pinned-mutation-observation.js +0 -19
- 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-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 +18 -10
- 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 +11 -35
- 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-complete-parent.d.ts.map +1 -1
- package/dist/root-write-complete-parent.js +7 -23
- 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/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/json-store.md +5 -0
- package/docs/native-helper.md +10 -3
- 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 +5 -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
package/dist/root-remove.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type RootContext } from "./root-context.js";
|
|
2
2
|
import type { RootRemoveOptions } from "./root-options.js";
|
|
3
3
|
import type { RemovalPathReceipts } from "./root-remove-receipt.js";
|
|
4
|
+
export declare const nonrecursiveRemovalKind: unique symbol;
|
|
4
5
|
export declare function validateRemoveOptions(options: RootRemoveOptions): void;
|
|
5
6
|
export declare function removePathInRootFallback(root: RootContext, targetPath: string, options: RootRemoveOptions, receipts?: RemovalPathReceipts): Promise<void>;
|
|
6
7
|
//# sourceMappingURL=root-remove.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"root-remove.d.ts","sourceRoot":"","sources":["../src/root-remove.ts"],"names":[],"mappings":"AAeA,OAAO,EAA6B,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAM3D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AASpE,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,iBAAiB,GAAG,IAAI,CAgBtE;
|
|
1
|
+
{"version":3,"file":"root-remove.d.ts","sourceRoot":"","sources":["../src/root-remove.ts"],"names":[],"mappings":"AAeA,OAAO,EAA6B,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAM3D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AASpE,eAAO,MAAM,uBAAuB,EAAE,OAAO,MAA4C,CAAC;AAK1F,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,iBAAiB,GAAG,IAAI,CAgBtE;AAyOD,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,WAAW,EACjB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,iBAAiB,EAC1B,QAAQ,CAAC,EAAE,mBAAmB,GAC7B,OAAO,CAAC,IAAI,CAAC,CA6Lf"}
|
package/dist/root-remove.js
CHANGED
|
@@ -16,6 +16,7 @@ import { createSuppressedError } from "./suppressed-error.js";
|
|
|
16
16
|
import { getFsSafeTestHooks } from "./test-hooks.js";
|
|
17
17
|
const DEFAULT_MAX_ENTRIES = 100_000;
|
|
18
18
|
const DEFAULT_MAX_DEPTH = 64;
|
|
19
|
+
export const nonrecursiveRemovalKind = Symbol("nonrecursive removal kind");
|
|
19
20
|
export function validateRemoveOptions(options) {
|
|
20
21
|
if (options.order !== undefined && options.order !== "filesystem" && options.order !== "sorted") {
|
|
21
22
|
throw new TypeError("remove order must be filesystem or sorted");
|
|
@@ -220,6 +221,9 @@ async function removeOne(root, targetPath, options, receipts) {
|
|
|
220
221
|
}
|
|
221
222
|
try {
|
|
222
223
|
const isDirectory = fsSync.lstatSync(targetPath).isDirectory();
|
|
224
|
+
if (!isDirectory && options[nonrecursiveRemovalKind] === "directory") {
|
|
225
|
+
throw new FsSafeError("path-mismatch", "store entry is no longer a directory");
|
|
226
|
+
}
|
|
223
227
|
assertFinalSymlinkRejected(targetPath, options.mutationSymlinks !== undefined);
|
|
224
228
|
assertNotAborted(options.signal);
|
|
225
229
|
options.assertBeforeMutation?.();
|
package/dist/root-walk.d.ts
CHANGED
|
@@ -21,7 +21,7 @@ export type RootWalkEntry = RootWalkDataEntry | {
|
|
|
21
21
|
size: 0;
|
|
22
22
|
error: unknown;
|
|
23
23
|
};
|
|
24
|
-
export type RootWalkEntryFilter = (entry: RootWalkDataEntry) => RootWalkEntryFilterResult
|
|
24
|
+
export type RootWalkEntryFilter = (entry: RootWalkDataEntry) => RootWalkEntryFilterResult | Promise<RootWalkEntryFilterResult>;
|
|
25
25
|
export type RootWalkOptions = {
|
|
26
26
|
maxDepth?: number;
|
|
27
27
|
maxEntries?: number;
|
package/dist/root-walk.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"root-walk.d.ts","sourceRoot":"","sources":["../src/root-walk.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,oBAAoB,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAC;AAClG,OAAO,KAAK,EAAY,QAAQ,EAAE,MAAM,YAAY,CAAC;AAGrD,MAAM,MAAM,qBAAqB,GAAG,MAAM,GAAG,oBAAoB,CAAC;AAClE,MAAM,MAAM,qBAAqB,GAAG,UAAU,GAAG,OAAO,CAAC;AACzD,MAAM,MAAM,8BAA8B,GAAG,OAAO,GAAG,iBAAiB,CAAC;AACzE,MAAM,MAAM,yBAAyB,GAAG,SAAS,GAAG,MAAM,GAAG,cAAc,CAAC;AAC5E,MAAM,MAAM,qBAAqB,GAAG,MAAM,GAAG,WAAW,GAAG,OAAO,CAAC;AACnE,MAAM,MAAM,iBAAiB,GAAG,qBAAqB,GAAG,iBAAiB,GAAG,WAAW,CAAC;AAExF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,qBAAqB,CAAC;IAC5B,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,aAAa,GACrB,iBAAiB,GACjB;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,CAAC,CAAA;CAAE,GACpD;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,iBAAiB,CAAC;IAAC,IAAI,EAAE,CAAC,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC;AAE/E,MAAM,MAAM,mBAAmB,GAAG,
|
|
1
|
+
{"version":3,"file":"root-walk.d.ts","sourceRoot":"","sources":["../src/root-walk.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,oBAAoB,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAC;AAClG,OAAO,KAAK,EAAY,QAAQ,EAAE,MAAM,YAAY,CAAC;AAGrD,MAAM,MAAM,qBAAqB,GAAG,MAAM,GAAG,oBAAoB,CAAC;AAClE,MAAM,MAAM,qBAAqB,GAAG,UAAU,GAAG,OAAO,CAAC;AACzD,MAAM,MAAM,8BAA8B,GAAG,OAAO,GAAG,iBAAiB,CAAC;AACzE,MAAM,MAAM,yBAAyB,GAAG,SAAS,GAAG,MAAM,GAAG,cAAc,CAAC;AAC5E,MAAM,MAAM,qBAAqB,GAAG,MAAM,GAAG,WAAW,GAAG,OAAO,CAAC;AACnE,MAAM,MAAM,iBAAiB,GAAG,qBAAqB,GAAG,iBAAiB,GAAG,WAAW,CAAC;AAExF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,qBAAqB,CAAC;IAC5B,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,aAAa,GACrB,iBAAiB,GACjB;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,CAAC,CAAA;CAAE,GACpD;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,iBAAiB,CAAC;IAAC,IAAI,EAAE,CAAC,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC;AAE/E,MAAM,MAAM,mBAAmB,GAAG,CAChC,KAAK,EAAE,iBAAiB,KACrB,yBAAyB,GAAG,OAAO,CAAC,yBAAyB,CAAC,CAAC;AAEpE,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,QAAQ,GAAG,YAAY,CAAC;IAChC,aAAa,EAAE,qBAAqB,CAAC;IACrC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,aAAa,CAAC,EAAE,qBAAqB,CAAC;IACtC,WAAW,CAAC,EAAE,mBAAmB,CAAC;IAClC,gBAAgB,CAAC,EAAE,8BAA8B,CAAC;CACnD,CAAC;AAEF,KAAK,kBAAkB,GAAG;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC9C,IAAI,CACF,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,2BAA2B,GACnC,OAAO,CAAC,oBAAoB,CAAC,CAAC;CAClC,CAAC;AAqBF,wBAAuB,QAAQ,CAC7B,IAAI,EAAE,kBAAkB,EACxB,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,eAAe,GACvB,cAAc,CAAC,aAAa,CAAC,CAmL/B"}
|
package/dist/root-walk.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { FsSafeError } from "./errors.js";
|
|
3
|
-
import { resolveRootPath } from "./root-path.js";
|
|
3
|
+
import { resolveRootPath, ROOT_PATH_ALIAS_POLICIES } from "./root-path.js";
|
|
4
4
|
import { createSuppressedError } from "./suppressed-error.js";
|
|
5
5
|
function validateBudget(name, value) {
|
|
6
6
|
if (value === undefined)
|
|
@@ -65,12 +65,16 @@ export async function* walkRoot(root, relativePath, options) {
|
|
|
65
65
|
options.signal?.throwIfAborted();
|
|
66
66
|
let listing;
|
|
67
67
|
try {
|
|
68
|
+
const skipChildSymlinks = depth > 0 && options.symlinkPolicy === "skip";
|
|
68
69
|
const resolvedDirectory = await resolveRootPath({
|
|
69
70
|
absolutePath: path.resolve(root.rootReal, directory),
|
|
70
71
|
rootPath: root.rootReal,
|
|
71
72
|
rootCanonicalPath: root.rootReal,
|
|
72
73
|
boundaryLabel: "root walk",
|
|
74
|
+
policy: skipChildSymlinks ? ROOT_PATH_ALIAS_POLICIES.unlinkTarget : undefined,
|
|
73
75
|
});
|
|
76
|
+
if (skipChildSymlinks && resolvedDirectory.kind === "symlink")
|
|
77
|
+
return;
|
|
74
78
|
if (!resolvedDirectory.exists || resolvedDirectory.kind !== "directory") {
|
|
75
79
|
throw new FsSafeError("not-file", `root walk path is not a directory: ${directory || "."}`);
|
|
76
80
|
}
|
|
@@ -140,7 +144,18 @@ export async function* walkRoot(root, relativePath, options) {
|
|
|
140
144
|
size = target.size;
|
|
141
145
|
}
|
|
142
146
|
const walkEntry = { relativePath: child, kind, size };
|
|
143
|
-
|
|
147
|
+
let filterResult = options.entryFilter?.(walkEntry) ?? "include";
|
|
148
|
+
if (typeof filterResult !== "string") {
|
|
149
|
+
filterResult = (await filterResult) ?? "include";
|
|
150
|
+
options.signal?.throwIfAborted();
|
|
151
|
+
try {
|
|
152
|
+
await listing.assertCurrent();
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
yield onDirectoryError(directory, error);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
144
159
|
if (!["include", "skip", "skip-subtree"].includes(filterResult)) {
|
|
145
160
|
throw new TypeError(`invalid root walk entryFilter result: ${String(filterResult)}`);
|
|
146
161
|
}
|
|
@@ -34,9 +34,7 @@ export type RootWritePathSelection = Readonly<{
|
|
|
34
34
|
export type RetainedRootWriteSelection = RootWritePathSelection & Readonly<{
|
|
35
35
|
identity: Readonly<Pick<BigIntStats, "dev" | "ino">>;
|
|
36
36
|
}>;
|
|
37
|
-
export declare function retainRootWriteSelection(owner: object, selection: RetainedRootWriteSelection): void;
|
|
38
37
|
export declare function createRootWriteSelectionForFd(selection: RootWritePathSelection, fd: number): RetainedRootWriteSelection;
|
|
39
|
-
export declare function takeRootWriteSelection(owner: object): RetainedRootWriteSelection | undefined;
|
|
40
38
|
export declare function assertRootWritePathSelectionSync(root: RootContext, selection: RootWritePathSelection): void;
|
|
41
39
|
export declare function assertRootWriteSelectionSync(root: RootContext, selection: RetainedRootWriteSelection, verifyBinding: boolean, fd?: number): void;
|
|
42
40
|
export declare function prepareRootWritePathSelection(params: Omit<RootWritePathSelection, "parentGuard"> & Readonly<{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"root-write-admission.d.ts","sourceRoot":"","sources":["../src/root-write-admission.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE3C,OAAO,EAA2B,KAAK,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AACvF,OAAO,EAIL,KAAK,sBAAsB,EAC5B,MAAM,sBAAsB,CAAC;AAQ9B,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,mBAAmB,CAAC;AAEtE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAGtD,OAAO,EAEL,KAAK,qBAAqB,EAC3B,MAAM,0BAA0B,CAAC;AAIlC,OAAO,EAEL,KAAK,uBAAuB,EAC7B,MAAM,iCAAiC,CAAC;AAEzC,MAAM,MAAM,iBAAiB,GAAG,QAAQ,CAAC;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,iBAAiB,CAAC,EAAE,4BAA4B,CAAC;CAClD,CAAC,CAAC;AAEH,MAAM,MAAM,gBAAgB,GAAG,OAAO,CAAC,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC,CAAC;AAE7E,MAAM,MAAM,gCAAgC,GAAG,QAAQ,CAAC;IACtD,SAAS,CAAC,kBAAkB,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACtD,CAAC,CAAC;AAEH,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC;IAC5C,YAAY,EAAE,gBAAgB,CAAC;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,CAAC,EAAE,4BAA4B,CAAC;IACjD,uBAAuB,CAAC,EAAE,gCAAgC,CAAC;CAC5D,CAAC,CAAC;AAEH,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC;IAC5C,iBAAiB,EAAE,4BAA4B,CAAC;IAChD,uBAAuB,EAAE,gCAAgC,CAAC;IAC1D,mBAAmB,EAAE,MAAM,CAAC;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,sBAAsB,CAAC;CACrC,CAAC,CAAC;AAEH,MAAM,MAAM,0BAA0B,GAAG,sBAAsB,GAAG,QAAQ,CAAC;IACzE,QAAQ,EAAE,QAAQ,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC;CACtD,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"root-write-admission.d.ts","sourceRoot":"","sources":["../src/root-write-admission.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,7 +6,7 @@ import { FsSafeError } from "./errors.js";
|
|
|
6
6
|
import { isNotFoundPathError } from "./path.js";
|
|
7
7
|
import { assertNoPathAliasEscape } from "./path-policy.js";
|
|
8
8
|
import { preparePinnedWriteMutationAdmission, snapshotPinnedMutationPolicy, } from "./pinned-mutation-admission.js";
|
|
9
|
-
import { admitPathInsideRoot } from "./root-boundary.js";
|
|
9
|
+
import { admitPathInsideRoot, sameNormalizedPathSpelling } from "./root-boundary.js";
|
|
10
10
|
import { resolvePathInRoot } from "./root-context.js";
|
|
11
11
|
import { hardlinkedPathNotAllowedError, outsideWorkspaceError } from "./root-errors.js";
|
|
12
12
|
import { inheritWriteTargetMode } from "./root-write-mode.js";
|
|
@@ -15,10 +15,6 @@ import { inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
|
15
15
|
import { realpathSync } from "./realpath.js";
|
|
16
16
|
import { getFsSafeTestHooks } from "./test-hooks.js";
|
|
17
17
|
import { assertPreparedRootWriteParentCurrent, } from "./root-write-complete-parent.js";
|
|
18
|
-
const retainedRootWriteSelections = new WeakMap();
|
|
19
|
-
export function retainRootWriteSelection(owner, selection) {
|
|
20
|
-
retainedRootWriteSelections.set(owner, selection);
|
|
21
|
-
}
|
|
22
18
|
export function createRootWriteSelectionForFd(selection, fd) {
|
|
23
19
|
const stat = inspectFileIdentitySync(() => fsSync.fstatSync(fd, { bigint: true }));
|
|
24
20
|
return Object.freeze({
|
|
@@ -26,11 +22,6 @@ export function createRootWriteSelectionForFd(selection, fd) {
|
|
|
26
22
|
identity: Object.freeze({ dev: stat.dev, ino: stat.ino }),
|
|
27
23
|
});
|
|
28
24
|
}
|
|
29
|
-
export function takeRootWriteSelection(owner) {
|
|
30
|
-
const selection = retainedRootWriteSelections.get(owner);
|
|
31
|
-
retainedRootWriteSelections.delete(owner);
|
|
32
|
-
return selection;
|
|
33
|
-
}
|
|
34
25
|
function writeSelectionChanged(cause) {
|
|
35
26
|
return new FsSafeError("path-mismatch", "write target changed during operation", {
|
|
36
27
|
cause: cause instanceof Error ? cause : undefined,
|
|
@@ -55,11 +46,6 @@ function inspectRegularSelectionPath(pathname, expected, follow) {
|
|
|
55
46
|
throw hardlinkedPathNotAllowedError();
|
|
56
47
|
return observed;
|
|
57
48
|
}
|
|
58
|
-
function sameNormalizedPathSpelling(left, right) {
|
|
59
|
-
// Windows can expose case-sensitive directories, so normalize separators
|
|
60
|
-
// and roots without using path.relative's case-folding comparison.
|
|
61
|
-
return path.resolve(left) === path.resolve(right);
|
|
62
|
-
}
|
|
63
49
|
function inspectSelectionBindingSync(root, selection, fd) {
|
|
64
50
|
inspectRegularSelectionPath(selection.selectedPath, selection.identity, false);
|
|
65
51
|
if (!sameNormalizedPathSpelling(selection.operationTargetPath, selection.selectedPath)) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"root-write-complete-parent.d.ts","sourceRoot":"","sources":["../src/root-write-complete-parent.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE3C,OAAO,EAIL,KAAK,mBAAmB,EACzB,MAAM,sBAAsB,CAAC;AAO9B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"root-write-complete-parent.d.ts","sourceRoot":"","sources":["../src/root-write-complete-parent.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE3C,OAAO,EAIL,KAAK,mBAAmB,EACzB,MAAM,sBAAsB,CAAC;AAO9B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AAKxE,KAAK,8BAA8B,GAAG,QAAQ,CAC1C;IAAE,MAAM,EAAE,KAAK,CAAA;CAAE,GACjB;IAAE,MAAM,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,WAAW,CAAA;CAAE,CACtC,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,uBAAuB,GAAG,QAAQ,CAAC;IAC7C,mBAAmB,EAAE,MAAM,CAAC;IAC5B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,WAAW,EAAE,mBAAmB,CAAC,WAAW,CAAC,CAAC;IAC9C,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,QAAQ,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACrD,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,8BAA8B,CAAC;CACxC,CAAC,CAAC;AAEH,KAAK,2BAA2B,GAAG,QAAQ,CAAC;IAC1C,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,sBAAsB,CAAC;IACtC,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,oBAAoB,CAAC,EAAE,MAAM,IAAI,CAAC;CACnC,CAAC,CAAC;AAEH,MAAM,MAAM,qBAAqB,GAAG,QAAQ,CAAC;IAC3C,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,sBAAsB,CAAC,mBAAmB,CAAC,CAAC;IAC/D,cAAc,CAAC,EAAE,uBAAuB,CAAC;CAC1C,CAAC,CAAC;AA2FH,wBAAgB,oCAAoC,CAClD,QAAQ,EAAE,uBAAuB,EACjC,YAAY,UAAQ,GACnB,IAAI,CAqBN;AAwCD,wBAAsB,4BAA4B,CAChD,IAAI,EAAE,WAAW,EACjB,MAAM,EAAE,2BAA2B,GAClC,OAAO,CAAC,qBAAqB,CAAC,CAsBhC"}
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import fsSync from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { assertDirectoryIdentitySync, createAsyncDirectoryGuard, inspectDirectoryIdentitySync, } from "./directory-guard.js";
|
|
4
|
-
import { WINDOWS_RESERVED_DEVICE_NAMES } from "./device-path.js";
|
|
5
4
|
import { FsSafeError } from "./errors.js";
|
|
6
5
|
import { getFsSafeNativeConfig } from "./native-config.js";
|
|
7
6
|
import { hasNodeErrorCode } from "./path.js";
|
|
8
7
|
import { realpathSync } from "./realpath.js";
|
|
9
|
-
import { admitPathInsideRoot } from "./root-boundary.js";
|
|
8
|
+
import { admitPathInsideRoot, sameNormalizedPathSpelling } from "./root-boundary.js";
|
|
9
|
+
import { ordinaryWindowsSegments } from "./pinned-mutation-shared-route.js";
|
|
10
10
|
import { prepareRootWriteTarget } from "./root-directory-creation.js";
|
|
11
11
|
import { canReuseParentWithMutationAssertion } from "./root-write-lock-binding.js";
|
|
12
|
-
import { isSafePathSegment } from "./safe-path-segment.js";
|
|
13
12
|
import { inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
14
13
|
import { getFsSafeTestHooks } from "./test-hooks.js";
|
|
15
14
|
function writeSelectionChanged(cause) {
|
|
@@ -17,30 +16,15 @@ function writeSelectionChanged(cause) {
|
|
|
17
16
|
cause: cause instanceof Error ? cause : undefined,
|
|
18
17
|
});
|
|
19
18
|
}
|
|
20
|
-
function sameNormalizedPathSpelling(left, right) {
|
|
21
|
-
// Windows can expose case-sensitive directories, so normalize separators
|
|
22
|
-
// and roots without using path.relative's case-folding comparison.
|
|
23
|
-
return path.resolve(left) === path.resolve(right);
|
|
24
|
-
}
|
|
25
|
-
function windowsReservedDeviceSegment(segment) {
|
|
26
|
-
const extension = segment.indexOf(".");
|
|
27
|
-
const stem = segment.slice(0, extension < 0 ? segment.length : extension).toUpperCase();
|
|
28
|
-
return WINDOWS_RESERVED_DEVICE_NAMES.has(stem);
|
|
29
|
-
}
|
|
30
19
|
function ordinarySharedWriteRoute(root, relativePath, operationTargetPath) {
|
|
31
20
|
if (process.versions.bun || relativePath === "" || relativePath.startsWith("~") ||
|
|
32
21
|
relativePath.includes("\0") || path.isAbsolute(relativePath))
|
|
33
22
|
return false;
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
if (segments.some((segment) => segment === "" || segment === "." || segment === ".." ||
|
|
40
|
-
(process.platform === "win32" &&
|
|
41
|
-
(!isSafePathSegment(segment, { allowDotPrefix: true }) ||
|
|
42
|
-
segment.endsWith(".") || segment.endsWith(" ") ||
|
|
43
|
-
windowsReservedDeviceSegment(segment)))))
|
|
23
|
+
if (process.platform === "win32") {
|
|
24
|
+
if (!ordinaryWindowsSegments(relativePath))
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
else if (relativePath.includes("\\") || relativePath.split("/").some(segment => segment === "" || segment === "." || segment === ".."))
|
|
44
28
|
return false;
|
|
45
29
|
const routed = path.resolve(root.rootReal, relativePath);
|
|
46
30
|
const admitted = admitPathInsideRoot({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"root-write-verification.d.ts","sourceRoot":"","sources":["../src/root-write-verification.ts"],"names":[],"mappings":"AAEA,OAAO,EAA6B,KAAK,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAK9F,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAEhE,OAAO,EAA6B,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAIhF,wBAAsB,uBAAuB,CAAC,MAAM,EAAE;IACpD,IAAI,EAAE,WAAW,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,gBAAgB,EAAE,sBAAsB,CAAC;IACzC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,sBAAsB,CAAC;CACrC,GAAG,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"root-write-verification.d.ts","sourceRoot":"","sources":["../src/root-write-verification.ts"],"names":[],"mappings":"AAEA,OAAO,EAA6B,KAAK,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAK9F,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAEhE,OAAO,EAA6B,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAIhF,wBAAsB,uBAAuB,CAAC,MAAM,EAAE;IACpD,IAAI,EAAE,WAAW,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,gBAAgB,EAAE,sBAAsB,CAAC;IACzC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,sBAAsB,CAAC;CACrC,GAAG,OAAO,CAAC,IAAI,CAAC,CA+EhB"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import fsSync, {} from "node:fs";
|
|
2
|
-
import fs from "node:fs/promises";
|
|
2
|
+
import fs, {} from "node:fs/promises";
|
|
3
3
|
import { assertAsyncDirectoryGuard } from "./directory-guard.js";
|
|
4
4
|
import { FsSafeError } from "./errors.js";
|
|
5
5
|
import { sameFileIdentity } from "./file-identity.js";
|
|
@@ -45,59 +45,46 @@ export async function verifyAtomicWriteResult(params) {
|
|
|
45
45
|
return stat;
|
|
46
46
|
};
|
|
47
47
|
try {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
assertPath(resolvedStat);
|
|
55
|
-
if (!admitPathInsideRoot({
|
|
56
|
-
rootPath: params.root.rootReal,
|
|
57
|
-
candidatePath: realPath,
|
|
58
|
-
rootIdentity: params.root.rootIdentity,
|
|
59
|
-
})) {
|
|
60
|
-
throw outsideWorkspaceError();
|
|
61
|
-
}
|
|
62
|
-
await assertAsyncDirectoryGuard(params.parentGuard);
|
|
63
|
-
await assertRootIdentityCurrent(params.root);
|
|
64
|
-
// Recheck after canonical resolution and directory checks, including late links.
|
|
65
|
-
assertPath(fsSync.lstatSync(params.targetPath, { bigint: true }));
|
|
66
|
-
assertDescriptor();
|
|
67
|
-
if (needsPathOpen) {
|
|
68
|
-
// A retained fd cannot prove that an opaque Windows pathname still names it.
|
|
69
|
-
// Reopen only for publication verification: the writer's exact identity is
|
|
70
|
-
// independent proof unavailable to ordinary readers. Never read any bytes.
|
|
71
|
-
assertNoUnsafeDeviceReadPath(params.targetPath);
|
|
72
|
-
const opened = await fs.open(params.targetPath, resolveReadOpenFlags()).catch((error) => {
|
|
73
|
-
if (isSymlinkOpenError(error)) {
|
|
74
|
-
throw new FsSafeError("symlink", "symlink open blocked", { cause: error });
|
|
75
|
-
}
|
|
76
|
-
if (hasNodeErrorCode(error, "EISDIR")) {
|
|
77
|
-
throw new FsSafeError("not-file", "not a file");
|
|
78
|
-
}
|
|
79
|
-
throw error;
|
|
80
|
-
});
|
|
81
|
-
try {
|
|
82
|
-
const reopenedStat = assertDescriptor(opened.fd);
|
|
48
|
+
let opened;
|
|
49
|
+
try {
|
|
50
|
+
while (true) {
|
|
51
|
+
// The original descriptor remains borrowed; only a verification reopen is owned here.
|
|
52
|
+
const fd = opened?.fd ?? params.fd;
|
|
53
|
+
const stat = assertDescriptor(fd);
|
|
83
54
|
assertPath(fsSync.lstatSync(params.targetPath, { bigint: true }));
|
|
84
|
-
const { realPath
|
|
85
|
-
|
|
55
|
+
const { realPath, stat: resolvedStat } = await resolveOpenedFileRealPathForFd(fd, stat, params.targetPath);
|
|
56
|
+
// Consume the resolver's observation only in this pass; the checks after
|
|
57
|
+
// the directory guards must still sample the current path and descriptor.
|
|
58
|
+
assertPath(resolvedStat);
|
|
86
59
|
if (!admitPathInsideRoot({
|
|
87
60
|
rootPath: params.root.rootReal,
|
|
88
|
-
candidatePath:
|
|
61
|
+
candidatePath: realPath,
|
|
89
62
|
rootIdentity: params.root.rootIdentity,
|
|
90
63
|
})) {
|
|
91
64
|
throw outsideWorkspaceError();
|
|
92
65
|
}
|
|
93
66
|
await assertAsyncDirectoryGuard(params.parentGuard);
|
|
94
67
|
await assertRootIdentityCurrent(params.root);
|
|
68
|
+
// Recheck after canonical resolution and directory checks, including late links.
|
|
95
69
|
assertPath(fsSync.lstatSync(params.targetPath, { bigint: true }));
|
|
96
|
-
assertDescriptor(
|
|
70
|
+
assertDescriptor(fd);
|
|
71
|
+
if (opened || !needsPathOpen)
|
|
72
|
+
break;
|
|
73
|
+
// Opaque Windows pathname identity needs one independent, read-free reopen.
|
|
74
|
+
assertNoUnsafeDeviceReadPath(params.targetPath);
|
|
75
|
+
opened = await fs.open(params.targetPath, resolveReadOpenFlags()).catch((error) => {
|
|
76
|
+
if (isSymlinkOpenError(error)) {
|
|
77
|
+
throw new FsSafeError("symlink", "symlink open blocked", { cause: error });
|
|
78
|
+
}
|
|
79
|
+
if (hasNodeErrorCode(error, "EISDIR"))
|
|
80
|
+
throw new FsSafeError("not-file", "not a file");
|
|
81
|
+
throw error;
|
|
82
|
+
});
|
|
97
83
|
}
|
|
98
|
-
|
|
84
|
+
}
|
|
85
|
+
finally {
|
|
86
|
+
if (opened)
|
|
99
87
|
await opened.close().catch(() => undefined);
|
|
100
|
-
}
|
|
101
88
|
}
|
|
102
89
|
}
|
|
103
90
|
catch (error) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"secret-file.d.ts","sourceRoot":"","sources":["../src/secret-file.ts"],"names":[],"mappings":"AAAA,OAAW,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAM/C,OAAO,EAAkF,KAAK,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"secret-file.d.ts","sourceRoot":"","sources":["../src/secret-file.ts"],"names":[],"mappings":"AAAA,OAAW,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAM/C,OAAO,EAAkF,KAAK,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAShJ,OAAO,EAIL,KAAK,qBAAqB,EAC3B,MAAM,yBAAyB,CAAC;AAQjC,eAAO,MAAM,uBAAuB,MAAQ,CAAC;AAC7C,eAAO,MAAM,wBAAwB,MAAQ,CAAC;AAE9C,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,qBAA0B,GAClC,MAAM,CA2CR;AAED,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,qBAA0B,GAClC,MAAM,GAAG,SAAS,CAUpB;AA8JD,KAAK,qBAAqB,GAAG;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,UAAU,CAAC;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AA+BF,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,IAAI,CAAC,qBAAqB,EAAE,SAAS,CAAC,GAC7C,OAAO,CAAC;IACT,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,mBAAmB,CAAC,WAAW,CAAC,CAAC;IAC5C,WAAW,EAAE,mBAAmB,CAAC,WAAW,CAAC,CAAC;IAC9C,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;CACvB,CAAC,CAgCD;AAwDD,wBAAsB,qBAAqB,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAUxF;AAED,wBAAsB,sBAAsB,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAoBzF"}
|
package/dist/secret-file.js
CHANGED
|
@@ -7,42 +7,20 @@ import { readFileDescriptorBoundedSync } from "./bounded-read.js";
|
|
|
7
7
|
import { assertAsyncDirectoryGuard, createAsyncDirectoryGuard, inspectDirectoryIdentity } from "./directory-guard.js";
|
|
8
8
|
import { pinNodeDirectoryForMode } from "./directory-mode-node.js";
|
|
9
9
|
import { assertOwnedDirectory } from "./directory-mode-owner.js";
|
|
10
|
-
import { assertNoUnsafeDeviceReadPath } from "./device-path.js";
|
|
11
10
|
import { FsSafeError } from "./errors.js";
|
|
12
11
|
import { resolveHomeRelativePath } from "./home-dir.js";
|
|
13
12
|
import { openPinnedFileSync } from "./pinned-open.js";
|
|
14
13
|
import { runPinnedWriteHelper } from "./pinned-write.js";
|
|
15
14
|
import { ensureTrailingSep } from "./root-context.js";
|
|
16
15
|
import { verifyAtomicWriteResult } from "./root-write-verification.js";
|
|
17
|
-
import {
|
|
16
|
+
import { prepareSecretRead, secretReadError, trimSecretFileContent, } from "./secret-read-policy.js";
|
|
18
17
|
import { inspectFileIdentity, inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
19
18
|
import { serializePathWrite } from "./write-queue.js";
|
|
20
19
|
import { assertNoWindowsPathAlias, resolvePathPreservingWindowsRoot, } from "./windows-path-alias.js";
|
|
21
20
|
export const PRIVATE_SECRET_DIR_MODE = 0o700;
|
|
22
21
|
export const PRIVATE_SECRET_FILE_MODE = 0o600;
|
|
23
22
|
export function readSecretFileSync(filePath, label, options = {}) {
|
|
24
|
-
const { resolvedPath, maxBytes } =
|
|
25
|
-
let rejectSymlink;
|
|
26
|
-
let previewStat;
|
|
27
|
-
try {
|
|
28
|
-
assertNoUnsafeDeviceReadPath(resolvedPath);
|
|
29
|
-
rejectSymlink = Boolean(options.rejectSymlink);
|
|
30
|
-
previewStat = inspectFileIdentitySync(() => inspectInput(`${label} file at ${resolvedPath} must not be a symlink.`));
|
|
31
|
-
}
|
|
32
|
-
catch (error) {
|
|
33
|
-
throw secretReadError(error instanceof FsSafeError ? error.code : secretPathErrorCode(error), "inspect", label, resolvedPath, error);
|
|
34
|
-
}
|
|
35
|
-
function inspectInput(symlinkMessage) {
|
|
36
|
-
const stat = rejectSymlink
|
|
37
|
-
? fs.lstatSync(resolvedPath, { bigint: true })
|
|
38
|
-
: fs.statSync(resolvedPath, { bigint: true });
|
|
39
|
-
if (rejectSymlink && stat.isSymbolicLink()) {
|
|
40
|
-
throw new FsSafeError("symlink", symlinkMessage);
|
|
41
|
-
}
|
|
42
|
-
return stat;
|
|
43
|
-
}
|
|
44
|
-
const rejectHardlinks = options.rejectHardlinks !== false;
|
|
45
|
-
assertSecretFilePreview(previewStat, label, resolvedPath, maxBytes, rejectHardlinks);
|
|
23
|
+
const { resolvedPath, maxBytes, rejectSymlink, rejectHardlinks, previewStat, inspectInput } = prepareSecretRead(filePath, label, options);
|
|
46
24
|
const opened = openPinnedFileSync({
|
|
47
25
|
filePath: resolvedPath,
|
|
48
26
|
rejectPathSymlink: rejectSymlink,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"secret-read-async.d.ts","sourceRoot":"","sources":["../src/secret-read-async.ts"],"names":[],"mappings":"AAQA,OAAO,
|
|
1
|
+
{"version":3,"file":"secret-read-async.d.ts","sourceRoot":"","sources":["../src/secret-read-async.ts"],"names":[],"mappings":"AAQA,OAAO,EAKL,KAAK,qBAAqB,EAC3B,MAAM,yBAAyB,CAAC;AAGjC,wBAAsB,cAAc,CAClC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC,MAAM,CAAC,CAqCjB;AAED,wBAAsB,iBAAiB,CACrC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAQ7B"}
|
|
@@ -5,32 +5,11 @@ import { assertNoUnsafeDeviceReadPath } from "./device-path.js";
|
|
|
5
5
|
import { FsSafeError } from "./errors.js";
|
|
6
6
|
import { resolveReadOpenFlags } from "./read-open-flags.js";
|
|
7
7
|
import { realpathSync } from "./realpath.js";
|
|
8
|
-
import { inspectFileIdentity
|
|
9
|
-
import {
|
|
8
|
+
import { inspectFileIdentity } from "./strict-file-identity.js";
|
|
9
|
+
import { prepareSecretRead, secretPathErrorCode, secretReadError, trimSecretFileContent, } from "./secret-read-policy.js";
|
|
10
10
|
import { assertNoWindowsPathAlias } from "./windows-path-alias.js";
|
|
11
11
|
export async function readSecretFile(filePath, label, options = {}) {
|
|
12
|
-
const { resolvedPath, maxBytes } =
|
|
13
|
-
let rejectSymlink;
|
|
14
|
-
let previewStat;
|
|
15
|
-
try {
|
|
16
|
-
assertNoUnsafeDeviceReadPath(resolvedPath);
|
|
17
|
-
rejectSymlink = Boolean(options.rejectSymlink);
|
|
18
|
-
previewStat = inspectFileIdentitySync(() => inspectInput(`${label} file at ${resolvedPath} must not be a symlink.`));
|
|
19
|
-
}
|
|
20
|
-
catch (error) {
|
|
21
|
-
throw secretReadError(error instanceof FsSafeError ? error.code : secretPathErrorCode(error), "inspect", label, resolvedPath, error);
|
|
22
|
-
}
|
|
23
|
-
function inspectInput(symlinkMessage) {
|
|
24
|
-
const stat = rejectSymlink
|
|
25
|
-
? fsSync.lstatSync(resolvedPath, { bigint: true })
|
|
26
|
-
: fsSync.statSync(resolvedPath, { bigint: true });
|
|
27
|
-
if (rejectSymlink && stat.isSymbolicLink()) {
|
|
28
|
-
throw new FsSafeError("symlink", symlinkMessage);
|
|
29
|
-
}
|
|
30
|
-
return stat;
|
|
31
|
-
}
|
|
32
|
-
const rejectHardlinks = options.rejectHardlinks !== false;
|
|
33
|
-
assertSecretFilePreview(previewStat, label, resolvedPath, maxBytes, rejectHardlinks);
|
|
12
|
+
const { resolvedPath, maxBytes, rejectSymlink, rejectHardlinks, previewStat, inspectInput } = prepareSecretRead(filePath, label, options);
|
|
34
13
|
let handle;
|
|
35
14
|
let raw;
|
|
36
15
|
try {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type BigIntStats } from "node:fs";
|
|
2
2
|
import { FsSafeError, type FsSafeErrorCode } from "./errors.js";
|
|
3
3
|
export declare const DEFAULT_SECRET_FILE_MAX_BYTES: number;
|
|
4
4
|
export type SecretFileReadOptions = {
|
|
@@ -10,8 +10,12 @@ export declare function secretPathErrorCode(error: unknown): FsSafeErrorCode;
|
|
|
10
10
|
export declare function secretReadError(code: FsSafeErrorCode, action: "inspect" | "read", label: string, resolvedPath: string, error: unknown): FsSafeError;
|
|
11
11
|
export declare function assertSecretFilePreview(stat: BigIntStats, label: string, resolvedPath: string, maxBytes: number, rejectHardlinks: boolean): void;
|
|
12
12
|
export declare function trimSecretFileContent(raw: string, label: string, resolvedPath: string): string;
|
|
13
|
-
export declare function
|
|
13
|
+
export declare function prepareSecretRead(filePath: string, label: string, options: SecretFileReadOptions): {
|
|
14
14
|
resolvedPath: string;
|
|
15
15
|
maxBytes: number;
|
|
16
|
+
rejectSymlink: boolean;
|
|
17
|
+
rejectHardlinks: boolean;
|
|
18
|
+
previewStat: BigIntStats;
|
|
19
|
+
inspectInput: (symlinkMessage: string) => BigIntStats;
|
|
16
20
|
};
|
|
17
21
|
//# sourceMappingURL=secret-read-policy.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"secret-read-policy.d.ts","sourceRoot":"","sources":["../src/secret-read-policy.ts"],"names":[],"mappings":"AAEA,
|
|
1
|
+
{"version":3,"file":"secret-read-policy.d.ts","sourceRoot":"","sources":["../src/secret-read-policy.ts"],"names":[],"mappings":"AAEA,OAAW,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAE/C,OAAO,EAAE,WAAW,EAAE,KAAK,eAAe,EAAE,MAAM,aAAa,CAAC;AAIhE,eAAO,MAAM,6BAA6B,QAAY,CAAC;AAEvD,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B,CAAC;AAEF,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,eAAe,CAGnE;AAED,wBAAgB,eAAe,CAC7B,IAAI,EAAE,eAAe,EACrB,MAAM,EAAE,SAAS,GAAG,MAAM,EAC1B,KAAK,EAAE,MAAM,EACb,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,OAAO,GACb,WAAW,CAGb;AAED,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,WAAW,EACjB,KAAK,EAAE,MAAM,EACb,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,EAChB,eAAe,EAAE,OAAO,GACvB,IAAI,CAUN;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAM9F;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,qBAAqB;;;;;;mCAyBzD,MAAM,KAAG,WAAW;EAY3D"}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { normalizeMaxBytes } from "./byte-budget.js";
|
|
2
2
|
import { resolveHomeRelativePath } from "./home-dir.js";
|
|
3
|
+
import fs, {} from "node:fs";
|
|
4
|
+
import { assertNoUnsafeDeviceReadPath } from "./device-path.js";
|
|
3
5
|
import { FsSafeError } from "./errors.js";
|
|
6
|
+
import { inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
4
7
|
import { assertNoWindowsPathAlias } from "./windows-path-alias.js";
|
|
5
8
|
export const DEFAULT_SECRET_FILE_MAX_BYTES = 16 * 1024;
|
|
6
9
|
export function secretPathErrorCode(error) {
|
|
@@ -29,7 +32,7 @@ export function trimSecretFileContent(raw, label, resolvedPath) {
|
|
|
29
32
|
}
|
|
30
33
|
return secret;
|
|
31
34
|
}
|
|
32
|
-
export function
|
|
35
|
+
export function prepareSecretRead(filePath, label, options) {
|
|
33
36
|
const trimmedPath = filePath.trim();
|
|
34
37
|
assertNoWindowsPathAlias(trimmedPath, "filesystem", `${label} file path uses a Windows filesystem namespace alias`);
|
|
35
38
|
const resolvedPath = resolveHomeRelativePath(trimmedPath);
|
|
@@ -40,5 +43,26 @@ export function resolveSecretReadPolicy(filePath, label, options) {
|
|
|
40
43
|
const maxBytes = normalizeMaxBytes(options.maxBytes, {
|
|
41
44
|
defaultValue: DEFAULT_SECRET_FILE_MAX_BYTES,
|
|
42
45
|
});
|
|
43
|
-
|
|
46
|
+
let rejectSymlink;
|
|
47
|
+
let previewStat;
|
|
48
|
+
try {
|
|
49
|
+
assertNoUnsafeDeviceReadPath(resolvedPath);
|
|
50
|
+
rejectSymlink = Boolean(options.rejectSymlink);
|
|
51
|
+
previewStat = inspectFileIdentitySync(() => inspectInput(`${label} file at ${resolvedPath} must not be a symlink.`));
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
throw secretReadError(error instanceof FsSafeError ? error.code : secretPathErrorCode(error), "inspect", label, resolvedPath, error);
|
|
55
|
+
}
|
|
56
|
+
function inspectInput(symlinkMessage) {
|
|
57
|
+
const stat = rejectSymlink
|
|
58
|
+
? fs.lstatSync(resolvedPath, { bigint: true })
|
|
59
|
+
: fs.statSync(resolvedPath, { bigint: true });
|
|
60
|
+
if (rejectSymlink && stat.isSymbolicLink()) {
|
|
61
|
+
throw new FsSafeError("symlink", symlinkMessage);
|
|
62
|
+
}
|
|
63
|
+
return stat;
|
|
64
|
+
}
|
|
65
|
+
const rejectHardlinks = options.rejectHardlinks !== false;
|
|
66
|
+
assertSecretFilePreview(previewStat, label, resolvedPath, maxBytes, rejectHardlinks);
|
|
67
|
+
return { resolvedPath, maxBytes, rejectSymlink, rejectHardlinks, previewStat, inspectInput };
|
|
44
68
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sibling-temp.d.ts","sourceRoot":"","sources":["../src/sibling-temp.ts"],"names":[],"mappings":"AAmBA,MAAM,MAAM,2BAA2B,CAAC,CAAC,IAAI;IAC3C,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IAC5C,oEAAoE;IACpE,iBAAiB,CAAC,EAAE,mBAAmB,CAAC;IACxC,gBAAgB,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,MAAM,CAAC;IACxC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uGAAuG;IACvG,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mEAAmE;IACnE,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,yEAAyE;IACzE,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,0BAA0B,CAAC,CAAC,IAAI;IAC1C,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,CAAC,CAAC;CACX,CAAC;AASF,wBAAsB,oBAAoB,CAAC,CAAC,EAC1C,OAAO,EAAE,2BAA2B,CAAC,CAAC,CAAC,GACtC,OAAO,CAAC,0BAA0B,CAAC,CAAC,CAAC,CAAC,
|
|
1
|
+
{"version":3,"file":"sibling-temp.d.ts","sourceRoot":"","sources":["../src/sibling-temp.ts"],"names":[],"mappings":"AAmBA,MAAM,MAAM,2BAA2B,CAAC,CAAC,IAAI;IAC3C,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IAC5C,oEAAoE;IACpE,iBAAiB,CAAC,EAAE,mBAAmB,CAAC;IACxC,gBAAgB,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,MAAM,CAAC;IACxC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uGAAuG;IACvG,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mEAAmE;IACnE,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,yEAAyE;IACzE,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,0BAA0B,CAAC,CAAC,IAAI;IAC1C,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,CAAC,CAAC;CACX,CAAC;AASF,wBAAsB,oBAAoB,CAAC,CAAC,EAC1C,OAAO,EAAE,2BAA2B,CAAC,CAAC,CAAC,GACtC,OAAO,CAAC,0BAA0B,CAAC,CAAC,CAAC,CAAC,CAkCxC;AAwBD,wBAAsB,uBAAuB,CAAC,MAAM,EAAE;IACpD,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,GAAG,OAAO,CAAC,IAAI,CAAC,CAyDhB"}
|
package/dist/sibling-temp.js
CHANGED
|
@@ -25,25 +25,33 @@ export async function writeSiblingTempFile(options) {
|
|
|
25
25
|
const dir = resolvePathPreservingWindowsRoot(dirInput);
|
|
26
26
|
assertNoWindowsPathAlias(dir, "filesystem", "sibling temp directory uses a Windows filesystem namespace alias");
|
|
27
27
|
const producerIsolation = options.producerIsolation;
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
const dirMode = options.dirMode ?? 0o700;
|
|
29
|
+
const chmodDir = options.chmodDir !== false;
|
|
30
|
+
const tempPrefix = options.tempPrefix;
|
|
31
|
+
const writeTemp = options.writeTemp;
|
|
32
|
+
const resolveFinalPath = options.resolveFinalPath;
|
|
33
|
+
const mode = options.mode;
|
|
34
|
+
const syncTempFile = options.syncTempFile === true;
|
|
35
|
+
const syncParentDir = options.syncParentDir === true;
|
|
36
|
+
await fs.mkdir(recursiveMkdirPath(dir), { recursive: true, mode: dirMode });
|
|
37
|
+
if (chmodDir) {
|
|
30
38
|
await applyDirectoryMode({
|
|
31
39
|
fsModule: fs,
|
|
32
40
|
dirPath: dir,
|
|
33
|
-
mode:
|
|
41
|
+
mode: dirMode,
|
|
34
42
|
ignoreChmodError: true,
|
|
35
43
|
});
|
|
36
44
|
}
|
|
37
45
|
return await writeCallbackSibling({
|
|
38
46
|
tempDir: dir,
|
|
39
|
-
tempName: buildTempName(
|
|
40
|
-
write:
|
|
47
|
+
tempName: buildTempName(tempPrefix),
|
|
48
|
+
write: writeTemp,
|
|
41
49
|
producerIsolation,
|
|
42
|
-
resolveFinalPath
|
|
43
|
-
mode
|
|
50
|
+
resolveFinalPath,
|
|
51
|
+
mode,
|
|
44
52
|
ignoreModeError: true,
|
|
45
|
-
syncTempFile
|
|
46
|
-
syncParentDir
|
|
53
|
+
syncTempFile,
|
|
54
|
+
syncParentDir,
|
|
47
55
|
});
|
|
48
56
|
}
|
|
49
57
|
function buildSiblingTempName(params) {
|
|
@@ -91,6 +99,9 @@ export async function writeViaSiblingTempPath(params) {
|
|
|
91
99
|
path.isAbsolute(relativeTargetPath)) {
|
|
92
100
|
throw new Error("Target path is outside the allowed root");
|
|
93
101
|
}
|
|
102
|
+
const writeTemp = params.writeTemp;
|
|
103
|
+
const fallbackFileName = params.fallbackFileName;
|
|
104
|
+
const tempPrefix = params.tempPrefix;
|
|
94
105
|
const rootGuard = await createAsyncDirectoryGuard(rootDir);
|
|
95
106
|
const workspace = await tempFile({
|
|
96
107
|
rootDir: resolveSecureTempRoot({
|
|
@@ -104,12 +115,12 @@ export async function writeViaSiblingTempPath(params) {
|
|
|
104
115
|
try {
|
|
105
116
|
const tempName = buildSiblingTempName({
|
|
106
117
|
targetPath: path.join(workspace.dir, path.basename(targetPath)),
|
|
107
|
-
fallbackFileName:
|
|
108
|
-
tempPrefix:
|
|
118
|
+
fallbackFileName: fallbackFileName ?? "output.bin",
|
|
119
|
+
tempPrefix: tempPrefix ?? ".fs-safe-output-",
|
|
109
120
|
});
|
|
110
121
|
const tempPath = resolveCallbackTempPath(workspace.dir, tempName);
|
|
111
122
|
await getFsSafeTestHooks()?.beforeSiblingTempWrite?.(tempPath);
|
|
112
|
-
await
|
|
123
|
+
await Function.prototype.call.call(writeTemp, params, tempPath);
|
|
113
124
|
await assertAsyncDirectoryGuard(rootGuard);
|
|
114
125
|
const targetRoot = await root(rootDir);
|
|
115
126
|
await targetRoot.copyIn(relativeTargetPath, tempPath, { mkdir: false });
|
|
@@ -1,31 +1,5 @@
|
|
|
1
|
-
import { type NativeFileHandle } from "./native-operations.js";
|
|
2
|
-
import type { Root } from "./root-impl.js";
|
|
3
|
-
import { type SidecarLockSnapshot } from "./sidecar-lock-reclaim.js";
|
|
4
1
|
import type { SidecarLockAcquireOptions, SidecarLockHandle } from "./sidecar-lock-types.js";
|
|
5
|
-
|
|
6
|
-
export type HeldSidecarLock
|
|
7
|
-
refCount: number;
|
|
8
|
-
reentrantOwner?: string;
|
|
9
|
-
handle: SidecarFileHandle;
|
|
10
|
-
lockPath: string;
|
|
11
|
-
snapshot: SidecarLockSnapshot;
|
|
12
|
-
acquiredAt: number;
|
|
13
|
-
metadata: Record<string, unknown>;
|
|
14
|
-
releasePromise?: Promise<void>;
|
|
15
|
-
lockRoot?: Root;
|
|
16
|
-
retainOnExit?: boolean;
|
|
17
|
-
parsePayload?: (raw: string) => unknown;
|
|
18
|
-
compromiseTimer?: NodeJS.Timeout;
|
|
19
|
-
};
|
|
20
|
-
type SidecarLockAcquisitionContext = {
|
|
21
|
-
held: Map<string, HeldSidecarLock>;
|
|
22
|
-
reclaimGuards: Set<string>;
|
|
23
|
-
ensureExitCleanupRegistered(): void;
|
|
24
|
-
handleForHeldLock(normalizedTargetPath: string, held: HeldSidecarLock): SidecarLockHandle;
|
|
25
|
-
releaseHeldLock(normalizedTargetPath: string, held: HeldSidecarLock, options?: {
|
|
26
|
-
force?: boolean;
|
|
27
|
-
}): Promise<boolean>;
|
|
28
|
-
};
|
|
2
|
+
import { type SidecarLockAcquisitionContext } from "./sidecar-lock-admission.js";
|
|
3
|
+
export type { HeldSidecarLock } from "./sidecar-lock-admission.js";
|
|
29
4
|
export declare function acquireSidecarLock<TPayload extends Record<string, unknown>>(options: SidecarLockAcquireOptions<TPayload>, context: SidecarLockAcquisitionContext): Promise<SidecarLockHandle>;
|
|
30
|
-
export {};
|
|
31
5
|
//# sourceMappingURL=sidecar-lock-acquire.d.ts.map
|