@openclaw/fs-safe 0.8.6 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +93 -0
- package/README.md +17 -0
- package/dist/advanced.d.ts +1 -0
- package/dist/advanced.d.ts.map +1 -1
- package/dist/advanced.js +1 -0
- package/dist/archive-crc32.d.ts.map +1 -1
- package/dist/archive-crc32.js +6 -1
- package/dist/archive-deadline.d.ts.map +1 -1
- package/dist/archive-deadline.js +3 -4
- package/dist/archive-durability.d.ts +24 -0
- package/dist/archive-durability.d.ts.map +1 -0
- package/dist/archive-durability.js +180 -0
- package/dist/archive-gzip-tail.d.ts +2 -0
- package/dist/archive-gzip-tail.d.ts.map +1 -1
- package/dist/archive-gzip-tail.js +22 -4
- package/dist/archive-input.js +4 -4
- package/dist/archive-kind.d.ts.map +1 -1
- package/dist/archive-kind.js +3 -2
- package/dist/archive-merge.d.ts +1 -0
- package/dist/archive-merge.d.ts.map +1 -1
- package/dist/archive-merge.js +42 -9
- package/dist/archive-native.d.ts +1 -0
- package/dist/archive-native.d.ts.map +1 -1
- package/dist/archive-native.js +4 -2
- package/dist/archive-options.d.ts +2 -0
- package/dist/archive-options.d.ts.map +1 -1
- package/dist/archive-parser.wasm +0 -0
- package/dist/archive-read.d.ts.map +1 -1
- package/dist/archive-read.js +72 -70
- package/dist/archive-staging.d.ts +1 -1
- package/dist/archive-staging.d.ts.map +1 -1
- package/dist/archive-staging.js +24 -19
- package/dist/archive-tar-stream.d.ts +11 -4
- package/dist/archive-tar-stream.d.ts.map +1 -1
- package/dist/archive-tar-stream.js +20 -8
- package/dist/archive-tar-wasm.d.ts.map +1 -1
- package/dist/archive-tar-wasm.js +2 -1
- package/dist/archive.d.ts.map +1 -1
- package/dist/archive.js +9 -4
- package/dist/bounded-read.d.ts +7 -0
- package/dist/bounded-read.d.ts.map +1 -1
- package/dist/bounded-read.js +73 -43
- package/dist/clone-metadata.d.ts +19 -0
- package/dist/clone-metadata.d.ts.map +1 -0
- package/dist/clone-metadata.js +32 -0
- package/dist/copy-file-input.d.ts +23 -0
- package/dist/copy-file-input.d.ts.map +1 -0
- package/dist/copy-file-input.js +91 -0
- package/dist/copy-policy.d.ts +3 -0
- package/dist/copy-policy.d.ts.map +1 -0
- package/dist/copy-policy.js +8 -0
- package/dist/copy-publication.d.ts +15 -0
- package/dist/copy-publication.d.ts.map +1 -0
- package/dist/copy-publication.js +30 -0
- package/dist/copy-tree-portable.d.ts +9 -0
- package/dist/copy-tree-portable.d.ts.map +1 -0
- package/dist/copy-tree-portable.js +191 -0
- package/dist/copy.d.ts +16 -0
- package/dist/copy.d.ts.map +1 -0
- package/dist/copy.js +123 -0
- package/dist/directory-mode-node.d.ts.map +1 -1
- package/dist/directory-mode-node.js +4 -4
- package/dist/durability.d.ts +1 -1
- package/dist/durability.d.ts.map +1 -1
- package/dist/error-detail.d.ts.map +1 -1
- package/dist/error-detail.js +4 -1
- package/dist/file-hash.d.ts +6 -2
- package/dist/file-hash.d.ts.map +1 -1
- package/dist/file-hash.js +49 -12
- package/dist/file-store-sync-write.d.ts +1 -0
- package/dist/file-store-sync-write.d.ts.map +1 -1
- package/dist/file-store-sync-write.js +9 -6
- package/dist/file-store.d.ts +4 -0
- package/dist/file-store.d.ts.map +1 -1
- package/dist/file-store.js +10 -1
- package/dist/filename.d.ts.map +1 -1
- package/dist/filename.js +2 -12
- package/dist/fs.d.ts.map +1 -1
- package/dist/fs.js +1 -2
- package/dist/guarded-mkdir.d.ts +1 -0
- package/dist/guarded-mkdir.d.ts.map +1 -1
- package/dist/guarded-mkdir.js +1 -0
- package/dist/guarded-mutation.d.ts +2 -0
- package/dist/guarded-mutation.d.ts.map +1 -1
- package/dist/guarded-mutation.js +9 -5
- package/dist/home-dir.d.ts.map +1 -1
- package/dist/home-dir.js +9 -7
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/install-path.d.ts.map +1 -1
- package/dist/install-path.js +9 -13
- package/dist/json-document-store.d.ts +3 -0
- package/dist/json-document-store.d.ts.map +1 -1
- package/dist/json-document-store.js +1 -0
- package/dist/json-durable-queue-directory.js +3 -3
- package/dist/json-durable-queue-ownership.js +3 -2
- package/dist/json-durable-queue.d.ts.map +1 -1
- package/dist/json-durable-queue.js +31 -27
- package/dist/local-roots.d.ts.map +1 -1
- package/dist/local-roots.js +22 -24
- package/dist/move-path-cleanup.d.ts.map +1 -1
- package/dist/move-path-cleanup.js +4 -3
- package/dist/move-path-stage.d.ts +8 -0
- package/dist/move-path-stage.d.ts.map +1 -0
- package/dist/move-path-stage.js +55 -0
- package/dist/move-path.d.ts.map +1 -1
- package/dist/move-path.js +55 -47
- package/dist/mutation-authority.d.ts +8 -0
- package/dist/mutation-authority.d.ts.map +1 -0
- package/dist/mutation-authority.js +36 -0
- package/dist/native-binding.d.ts +24 -1
- package/dist/native-binding.d.ts.map +1 -1
- package/dist/native-operations.d.ts +1 -1
- package/dist/native-operations.d.ts.map +1 -1
- package/dist/native-operations.js +11 -5
- package/dist/native-pinned-write-windows.d.ts.map +1 -1
- package/dist/native-pinned-write-windows.js +18 -7
- package/dist/native-pinned-write.d.ts.map +1 -1
- package/dist/native-pinned-write.js +1 -0
- package/dist/native-staged-file.d.ts +3 -3
- package/dist/native-staged-file.d.ts.map +1 -1
- package/dist/native-staged-file.js +35 -8
- package/dist/opened-file-failure.d.ts +1 -1
- package/dist/opened-file-failure.d.ts.map +1 -1
- package/dist/opened-file-failure.js +3 -2
- package/dist/path.d.ts.map +1 -1
- package/dist/path.js +3 -1
- package/dist/permissions-windows.d.ts.map +1 -1
- package/dist/permissions-windows.js +38 -48
- package/dist/permissions.js +3 -3
- package/dist/pinned-operation.js +1 -1
- package/dist/pinned-write.d.ts +5 -1
- package/dist/pinned-write.d.ts.map +1 -1
- package/dist/pinned-write.js +50 -30
- package/dist/positional-read.d.ts +9 -0
- package/dist/positional-read.d.ts.map +1 -0
- package/dist/positional-read.js +36 -0
- package/dist/private-temp-workspace.d.ts.map +1 -1
- package/dist/private-temp-workspace.js +9 -3
- package/dist/publish-copy-stage.d.ts +13 -0
- package/dist/publish-copy-stage.d.ts.map +1 -0
- package/dist/publish-copy-stage.js +47 -0
- package/dist/publish-file.d.ts.map +1 -1
- package/dist/publish-file.js +26 -25
- package/dist/replace-file-copy-fallback.d.ts.map +1 -1
- package/dist/replace-file-copy-fallback.js +30 -17
- package/dist/replace-file-copy-source.d.ts.map +1 -1
- package/dist/replace-file-copy-source.js +10 -5
- package/dist/replace-file-temp-owner.d.ts.map +1 -1
- package/dist/replace-file-temp-owner.js +6 -2
- package/dist/replace-file.d.ts.map +1 -1
- package/dist/replace-file.js +8 -3
- package/dist/root-context.d.ts +3 -0
- package/dist/root-context.d.ts.map +1 -1
- package/dist/root-context.js +51 -26
- package/dist/root-directory-list.d.ts +24 -0
- package/dist/root-directory-list.d.ts.map +1 -0
- package/dist/root-directory-list.js +201 -0
- package/dist/root-errors.d.ts +1 -0
- package/dist/root-errors.d.ts.map +1 -1
- package/dist/root-errors.js +3 -0
- package/dist/root-file.d.ts +2 -0
- package/dist/root-file.d.ts.map +1 -1
- package/dist/root-file.js +12 -4
- package/dist/root-impl.d.ts +6 -50
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +342 -208
- package/dist/root-options.d.ts +66 -0
- package/dist/root-options.d.ts.map +1 -0
- package/dist/root-options.js +18 -0
- package/dist/root-path-existing.d.ts +5 -0
- package/dist/root-path-existing.d.ts.map +1 -1
- package/dist/root-path-existing.js +56 -1
- package/dist/root-path.d.ts +1 -0
- package/dist/root-path.d.ts.map +1 -1
- package/dist/root-path.js +74 -162
- package/dist/root-symlink-policy.d.ts +13 -0
- package/dist/root-symlink-policy.d.ts.map +1 -0
- package/dist/root-symlink-policy.js +34 -0
- package/dist/root-walk.d.ts +5 -4
- package/dist/root-walk.d.ts.map +1 -1
- package/dist/root-walk.js +99 -50
- package/dist/root-write-mode.d.ts.map +1 -1
- package/dist/root-write-mode.js +1 -4
- package/dist/root.d.ts +3 -1
- package/dist/root.d.ts.map +1 -1
- package/dist/secret-file.d.ts +1 -0
- package/dist/secret-file.d.ts.map +1 -1
- package/dist/secret-file.js +1 -0
- package/dist/secret-read-async.js +5 -5
- package/dist/secure-file.d.ts +1 -1
- package/dist/secure-file.d.ts.map +1 -1
- package/dist/secure-file.js +23 -13
- package/dist/sibling-staged-file.js +7 -7
- package/dist/sibling-temp.d.ts.map +1 -1
- package/dist/sibling-temp.js +16 -7
- package/dist/sidecar-lock-acquire.d.ts +1 -1
- package/dist/sidecar-lock-acquire.d.ts.map +1 -1
- package/dist/sidecar-lock-acquire.js +6 -4
- package/dist/sidecar-lock-policy.d.ts.map +1 -1
- package/dist/sidecar-lock-policy.js +5 -3
- package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
- package/dist/sidecar-lock-reclaim.js +16 -4
- package/dist/sidecar-lock.d.ts.map +1 -1
- package/dist/sidecar-lock.js +2 -1
- package/dist/temp-cleanup.d.ts.map +1 -1
- package/dist/temp-cleanup.js +3 -2
- package/dist/temp-target.d.ts.map +1 -1
- package/dist/temp-target.js +9 -4
- package/dist/timing.d.ts +1 -0
- package/dist/timing.d.ts.map +1 -1
- package/dist/timing.js +25 -6
- package/dist/trash.js +2 -2
- package/dist/walk.d.ts.map +1 -1
- package/dist/walk.js +7 -27
- package/dist/windows-owner.d.ts +9 -12
- package/dist/windows-owner.d.ts.map +1 -1
- package/dist/windows-owner.js +24 -58
- package/dist/write-file-handle.d.ts +6 -0
- package/dist/write-file-handle.d.ts.map +1 -0
- package/dist/write-file-handle.js +25 -0
- package/dist/write-open-flags.d.ts.map +1 -1
- package/dist/write-open-flags.js +1 -2
- package/docs/advanced.md +18 -4
- package/docs/archive.md +57 -9
- package/docs/atomic.md +7 -0
- package/docs/contributing.md +7 -0
- package/docs/copy.md +86 -0
- package/docs/durability.md +17 -2
- package/docs/errors.md +1 -1
- package/docs/file-store.md +48 -3
- package/docs/json-store.md +10 -0
- package/docs/local-roots.md +8 -1
- package/docs/native.md +14 -1
- package/docs/permissions.md +20 -10
- package/docs/positional-read.md +63 -0
- package/docs/reading.md +6 -0
- package/docs/root.md +168 -6
- package/docs/secret-file.md +6 -0
- package/docs/security-model.md +14 -0
- package/docs/sidecar-lock.md +2 -0
- package/docs/timing.md +2 -0
- package/docs/types.md +18 -11
- package/docs/walk.md +54 -5
- package/docs/writing.md +25 -5
- package/package.json +16 -11
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,98 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
## 0.10.0 - 2026-09-13
|
|
6
|
+
|
|
7
|
+
### Highlights
|
|
8
|
+
|
|
9
|
+
- **Faster ZIP and TAR reads:** read archive members without temporary disk snapshots, reuse admitted native buffers, and reduce integrity-check and decompression overhead while retaining full validation.
|
|
10
|
+
- **Directory copies with native acceleration:** use `copyTree` to prefer or require APFS clones, Btrfs snapshots, or parallel ReFS/XFS reflinks, or choose portable byte copying for controlled, immutable templates and checkouts.
|
|
11
|
+
- **Guarded file copies:** copy from checked Root sources with exclusive publication, cancellation that waits for writes, and optional native cloning; copied data stays independent of its source.
|
|
12
|
+
- **Faster Windows byte copies:** reuse bounded buffers and parallelize directory copying, with native transfers between checked handles in automatic mode.
|
|
13
|
+
- **Reuse buffers for file reads:** new async and sync positional readers fill caller-owned buffers without changing the file's current offset; hashing gains byte limits and cancellation.
|
|
14
|
+
- **Walk large directories incrementally:** Root walks bound metadata work by the entry budget, with an opt-in filesystem-order stream for directories too wide to enumerate up front.
|
|
15
|
+
- **Recheck live access authority:** Root mutations can verify caller-owned leases or cancellation immediately before dispatch, and new symlink policies allow contained parent-directory aliases while rejecting final symlinks.
|
|
16
|
+
|
|
17
|
+
### Compatibility and upgrade notes
|
|
18
|
+
|
|
19
|
+
- **Ambiguous paths may now reject.** Reads preserve actual symlink and parent traversal instead of normalizing it away; mutations reject ambiguous symlink/parent combinations. Use the new explicit parent-alias policies when that behavior is intended.
|
|
20
|
+
- **Mutation failures are reported more accurately.** Failed parent-directory checks after fallback moves and removals now reject instead of reporting success. Treat rejection after dispatch as a potentially completed mutation, not proof that nothing changed.
|
|
21
|
+
- **Windows fallback writes honor durability settings.** Root defaults and per-call overrides now apply to JavaScript write/create fallbacks. Use `durable: false` explicitly for reconstructible data when syncing is unnecessary.
|
|
22
|
+
|
|
23
|
+
### Archive reads
|
|
24
|
+
|
|
25
|
+
- Retain admitted ZIP input buffers and parsed directories across native inspection and selected-member reads, removing disk staging and archive handoff copies. JavaScript ZIP reads also consume the admitted in-memory archive directly.
|
|
26
|
+
- Retain native TAR input and admitted member offsets. Plain TAR copies only the selected range after complete validation; gzip, zstd, and bzip2 replay bounded decompression with framing, trailer, padding, and limit checks intact. JavaScript TAR/gzip reads also avoid disk staging, with gzip output chunks matched to the WASM input window.
|
|
27
|
+
- Accelerate ZIP integrity checks with Node's native CRC32 on Node 22.2 and newer, retaining checksum validation and compatibility with earlier Node 22 versions. Reuse the strict UTF-8 decoder for TAR metadata without changing filename validation or BOM handling.
|
|
28
|
+
- Give each native archive pass its own abort signal so completed inspection cannot mask an extraction deadline.
|
|
29
|
+
|
|
30
|
+
### File reads, hashing, and directory walks
|
|
31
|
+
|
|
32
|
+
- Add `readFileWindowFully()` and `readFileWindowFullySync()` to fill caller-owned buffers through short reads, stop at EOF, and preserve descriptor offsets and ownership. Async cancellation waits for the pending read to settle before the buffer can be reused.
|
|
33
|
+
- Bound speculative allocations for large or exhausted files and accelerate synchronous bounded reads of regular files up to 16 MiB without extra chunk copies; retain one-read async performance through the default Root byte budget.
|
|
34
|
+
- Add byte limits and cooperative cancellation to `sha256File`, preserving descriptor ownership and waiting for native work to stop before rejecting. Batch larger JavaScript hash reads in bounded buffers up to 256 KiB to reduce filesystem calls.
|
|
35
|
+
- Bound Root walk metadata batches by the global entry budget and stop batches at directories and symlinks before descent. Add `order: "filesystem"` for incremental directory streaming with bounded lookahead; sorted traversal remains the default, and unbounded sorted walks retain fast snapshots.
|
|
36
|
+
- Reduce walk overhead by sharing equivalent directory checks and synchronous entry classification, reusing joined paths, and avoiding an extra promise per async entry. Preserve both operation and close failures during disposal, and report a followed symlink target's size consistently in filters and returned metadata.
|
|
37
|
+
- Speed up POSIX containment and filename helpers while preserving traversal rejection, Unicode handling, reserved names, and collision-resistant install names.
|
|
38
|
+
|
|
39
|
+
### File and directory copying
|
|
40
|
+
|
|
41
|
+
- Extend `Root.copyIn` with guarded Root sources, exclusive publication, settled cancellation, exact publication receipts, and optional native file cloning while keeping copied data independent. Share `clone: "auto" | "always" | "never"` with `copyTree`; Root keeps its `"never"` default, which uses ordinary reads and writes without clone or copy-offload calls.
|
|
42
|
+
- Add `copyTree` in `@openclaw/fs-safe/copy` with `clone: "auto"` (default), `"always"`, and `"never"` policies. Automatic copying prefers native cloning and falls back to byte copying only when the binding or filesystem capability is unavailable, or cloning cannot cross filesystems; strict cloning never falls back, and ordinary copying avoids clone and copy-offload calls. Destinations must be absent, and cancellation waits for admitted writes to settle.
|
|
43
|
+
- Speed up Windows directory byte copies with bounded parallelism, reusable 1 MiB buffers, and native transfers between checked handles in automatic mode. Honor copy concurrency across portable backends and settle admitted writes before reporting failures or cancellation.
|
|
44
|
+
- Support APFS directory clones, Btrfs subvolume preparation and snapshots, and parallel ReFS/XFS reflinks through `probeTreeClone` and `createCloneSource`. Preserve XFS file and directory extended attributes and ACLs, and restore directory timestamps after APFS bulk cloning.
|
|
45
|
+
- Add batched `readCloneFileMetadata` for APFS clone IDs and file metadata. These are point-in-time observations, not authorization or proof that later contents remain unchanged.
|
|
46
|
+
- Document metadata and filesystem limits: APFS directory cloning does not guarantee descendant ACL preservation or inheritance, Btrfs snapshots omit nested subvolume contents, and ReFS rejects unsupported reparse points and alternate data streams. Portable copying does not promise ownership, ACL, extended-attribute, alternate-stream, or sparse-layout preservation. Callers retain responsibility for source immutability, permission policy, and recovery after a failed or aborted copy.
|
|
47
|
+
|
|
48
|
+
### Root policies and path safety
|
|
49
|
+
|
|
50
|
+
- Add composed Root `assertBeforeMutation` callbacks that recheck live caller authority immediately before mutation dispatch, including each buffered-write chunk and direct file removal. Root-level and per-call checks both apply; refusal errors and owned cleanup are preserved across native and JavaScript writers.
|
|
51
|
+
- Add `symlinks: "follow-parents-within-root"` for reads and opt-in `mutationSymlinks` policies, following contained directory aliases while rejecting final symlinks at absolute root entry, open, and publication boundaries.
|
|
52
|
+
- Preserve checked canonical and raw traversal in Root, absolute-path, local-root, and `openRootFile`/`openRootFileSync` reads, including home expansion and parent components that cancel a missing prefix. Validation can no longer normalize away a rejected link or select a different in-root file; local `requireFile` reads retain final-symlink rejection.
|
|
53
|
+
- Pin Root directory identities as bigint values, reject indistinguishable numeric inode replacements, and fail closed after bounded retries when Windows root identity remains unknown. Require a verified opened identity before inheriting an existing write target's mode.
|
|
54
|
+
- Preserve Linux native directory-only open flags so the kernel rejects non-directories before FIFO blocking or truncation, while removing the redundant post-open stat.
|
|
55
|
+
- Expand leading home-directory prefixes before resolving parent segments, so `~/../file` resolves against the home directory's parent; keep other tildes literal. Shorten only the home directory and its descendants in error messages, preserving similarly prefixed sibling paths.
|
|
56
|
+
- Resolve Windows ACL principal names such as `constructor` and `__proto__` as real dictionary keys, preserving SID lookup and translated entries.
|
|
57
|
+
- Preserve Unicode Windows paths during fallback permission inspection by reading structured SID and access-mask facts instead of localized command output.
|
|
58
|
+
|
|
59
|
+
### Writes, moves, and cleanup
|
|
60
|
+
|
|
61
|
+
- Check the complete encoded newline before Root append, avoiding duplicated or missing separators in UTF-16LE files on Windows and other platforms.
|
|
62
|
+
- Preserve unowned staging replacements after Windows fallback write or sync failures, and retain exact parent-directory identities so owned partial-file cleanup works even when Windows directory indexes exceed numeric precision.
|
|
63
|
+
- Report failed post-operation parent checks after fallback moves and removals, including moved source parents, instead of silently reporting success.
|
|
64
|
+
- Close publication descriptors when initial inspection fails and relinquish descriptor numbers before potentially failing closes, preventing cleanup from closing a reused descriptor.
|
|
65
|
+
- Keep move-fallback publication and cleanup tied to the initially admitted staging identity, preserve substituted paths after copy failures, and reject writes that make no progress.
|
|
66
|
+
- Preserve recreated temporary paths after exit cleanup observes the original name missing; absence no longer authorizes removal of a later replacement.
|
|
67
|
+
- Allow explicitly authorized filesystem-root descendants in Trash moves and keep reservation-directory names bounded for long source filenames.
|
|
68
|
+
|
|
69
|
+
### Locks and durable queues
|
|
70
|
+
|
|
71
|
+
- Honor finite timeouts and retry delays above Node's single-timer limit by rearming bounded timers instead of expiring after approximately 1 ms.
|
|
72
|
+
- Keep zero-delay lock retries finite when a large backoff factor overflows, preserving retry and timeout budgets.
|
|
73
|
+
- Skip invalid delivered-marker names during durable queue batch loading, preserving malformed files while continuing to load valid pending entries.
|
|
74
|
+
- Read durable queue entries through the shared bounded buffer and admitted file-size hint, reducing filesystem calls and chunk copies while retaining exact identity and byte-limit checks.
|
|
75
|
+
|
|
76
|
+
### Validation and maintenance
|
|
77
|
+
|
|
78
|
+
- Add a method-by-method benchmark with callable API coverage checks, native/fallback reports, and fixture setup outside measurement.
|
|
79
|
+
- Give the full documentation-build smoke test a bounded longer runtime on slow Windows runners. Validate retained native archive buffers across concurrent reads and forced garbage collection; use efficient byte comparisons in large-buffer coverage tests without relaxing timeouts.
|
|
80
|
+
|
|
81
|
+
## 0.9.0 - 2026-09-11
|
|
82
|
+
|
|
83
|
+
**Highlights:** Faster bulk extraction and configurable durability, with Windows filesystem fixes and reliable mixed-version lock cleanup.
|
|
84
|
+
|
|
85
|
+
- **Compatibility:** `extractArchive` skips file and directory fsync by default for faster bulk imports; pass `durable: true` to restore the previous durability behavior.
|
|
86
|
+
- Add store-level and per-call `durable` options to FileStore writes, streams, copies, and synchronous writes, plus JSON-store defaults, so reconstructible data can skip fsync while preserving guarded atomic publication.
|
|
87
|
+
- Add `Root.copyIn({ durable: false })`, with per-call, root-default, then `true` precedence, to skip file and parent-directory fsync for reconstructible data.
|
|
88
|
+
- Speed up archive extraction by omitting private staging syncs, deferring opted-in publication durability to one bounded file/directory pass, and sharing duplicate destination guards without weakening containment checks.
|
|
89
|
+
- Keep process-exit lock cleanup working when a legacy shared manager lacks reclaim-guard state, continuing through later managers while preserving retained locks; thanks @metahacker.
|
|
90
|
+
- Publish read-only modes such as `0o400` through the Windows JavaScript write fallback by retaining private `0o600` staging and applying the final mode through the retained handle after rename.
|
|
91
|
+
- Preserve Windows file identity across open-induced `ctime` changes during guarded move fallback, and recreate directory junctions without requiring symlink privileges; thanks @giodl73-repo.
|
|
92
|
+
- Extend synchronous metadata observations to archive, copy, publication, move, and directory-mode paths while keeping data and structural I/O asynchronous and preserving native canonical paths.
|
|
93
|
+
- Keep durable publication coverage reliable under filesystem contention by allowing bounded I/O time and draining unfinished operations before fixture cleanup.
|
|
94
|
+
- Refresh JSZip to 3.10.2, Node declarations to 26.5.1, emnapi runtime build tooling to 2.0.0-alpha.5, the Rust hybrid-array dependency to 0.4.15, and pinned CodeQL actions to 4.38.0.
|
|
95
|
+
|
|
3
96
|
## 0.8.6 - 2026-09-07
|
|
4
97
|
|
|
5
98
|
**Highlights:** Clearer guidance for atomic writes on Windows exFAT, with a repeatable filesystem compatibility probe.
|
package/README.md
CHANGED
|
@@ -133,6 +133,11 @@ await fs.remove("notes/archive/today.txt");
|
|
|
133
133
|
|
|
134
134
|
`root()` takes the trusted directory; relative paths in subsequent calls are resolved against it. Defaults you pass to `root()` apply to every call below; per-call options override them.
|
|
135
135
|
|
|
136
|
+
`copyIn()` also accepts `{ root: sourceRoot, relativePath }`, `overwrite: false`,
|
|
137
|
+
and `clone: "auto"` for guarded, exclusive file copies with optional native
|
|
138
|
+
copy-on-write acceleration. Byte limits, cancellation, and publication receipts
|
|
139
|
+
are described in the [Root copy contract](docs/root.md#writes).
|
|
140
|
+
|
|
136
141
|
When you need metadata or a `FileHandle`:
|
|
137
142
|
|
|
138
143
|
```ts
|
|
@@ -148,6 +153,17 @@ await fs.create("notes/README.md", "seed\n"); // throws if it already exists
|
|
|
148
153
|
|
|
149
154
|
`write()` replaces file contents by default; pass `{ overwrite: false }` or use `create()` when an existing file should be an error. `move()` defaults to no clobber because it can otherwise delete an unrelated target while also consuming the source. Pass `{ overwrite: true }` when replacing the target is intended.
|
|
150
155
|
|
|
156
|
+
Mutating methods accept `assertBeforeMutation: () => void` for live lease or
|
|
157
|
+
cancellation checks immediately before filesystem dispatch. Root defaults and
|
|
158
|
+
per-call checks compose; cleanup and already-dispatched work still settle.
|
|
159
|
+
See [live mutation authority](docs/root.md#live-mutation-authority) for the exact
|
|
160
|
+
scope, including raw writable handles and lock bookkeeping.
|
|
161
|
+
For workspaces with directory aliases, use `symlinks: "follow-parents-within-root"`
|
|
162
|
+
on reads and `mutationSymlinks: "follow-parents-within-root"` on mutations or root
|
|
163
|
+
defaults. Contained parent symlinks are resolved by the library, while a final
|
|
164
|
+
symlink is rejected. Read policy and mutation policy are separate; omitting
|
|
165
|
+
`mutationSymlinks` preserves the existing mutation behavior. See [root policies](docs/root.md#defaults-vs-per-call-options).
|
|
166
|
+
|
|
151
167
|
Use `ensureRoot()` when a computed relative directory target resolves to the root itself (`""` or `"."`) and you want the operation to be accepted. `root()` still requires the trusted root directory to already exist.
|
|
152
168
|
|
|
153
169
|
## Reading
|
|
@@ -229,6 +245,7 @@ contract. Low-level helpers that OpenClaw needs to compose higher-level APIs are
|
|
|
229
245
|
| `@openclaw/fs-safe/file-lock` | async/sync sidecar locks, root-bounded sidecars, ownership verification, and stale policy |
|
|
230
246
|
| `@openclaw/fs-safe/permissions` | POSIX mode and Windows ACL inspection, raw owner/ACE facts, private-directory creation, and remediation helpers |
|
|
231
247
|
| `@openclaw/fs-safe/walk` | budget-bounded directory walking with symlink policy, filters, and truncation accounting; not root-bounded |
|
|
248
|
+
| `@openclaw/fs-safe/copy` | directory copying with `clone: "auto"`, `"always"`, or `"never"`; native APFS, Btrfs, ReFS, and XFS cloning, portable byte copying, and clone metadata; see [directory copying](docs/copy.md) |
|
|
232
249
|
| `@openclaw/fs-safe/archive` | policy-driven ZIP/TAR extraction, clamp/filter policy, metadata/path-depth limits, native gzip/zstd/bzip2, and bounded entry reads |
|
|
233
250
|
| `@openclaw/fs-safe/advanced` | lower-level composition helpers such as path scopes, root-file open, bounded descriptor reads, install paths, filename sanitizing, temp-file targets, sibling-temp writes, local-root readers, regular-file helpers, `pathExists`, and `withTimeout`; less stable than focused public subpaths |
|
|
234
251
|
| `@openclaw/fs-safe/errors` | `FsSafeError`, closed codes/categories, causes, and operation-specific details receipts |
|
package/dist/advanced.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { createAsyncLock } from "./async-lock.js";
|
|
2
2
|
export { stageFileInDirectory, type StagedFile, type StagedFileReceipt, type PublishedFileReceipt, type StagedFilePublication, type StagedFileCleanupReceipt, type StagedFileFailureDetails, } from "./native-staged-file.js";
|
|
3
3
|
export { readFileDescriptorBounded, readFileDescriptorBoundedSync, readFileHandleBounded, } from "./bounded-read.js";
|
|
4
|
+
export { readFileWindowFully, readFileWindowFullySync, type ReadFileWindowOptions, } from "./positional-read.js";
|
|
4
5
|
export { assertNoUnsafeDeviceReadPath, isUnsafeDeviceReadPath, matchUnsafeDeviceReadPath, type UnsafeDeviceReadPathMatch, type UnsafeDeviceReadPathOptions, type UnsafeDeviceReadPathReason, } from "./device-path.js";
|
|
5
6
|
export { assertAbsolutePathInput, canonicalPathFromExistingAncestor, ensureAbsoluteDirectory, findExistingAncestor, resolveAbsolutePathForRead, resolveAbsolutePathForWrite, type AbsolutePathSymlinkPolicy, type EnsureAbsoluteDirectoryOptions, type EnsureAbsoluteDirectoryResult, type ResolvedAbsolutePath, type ResolvedWritableAbsolutePath, } from "./absolute-path.js";
|
|
6
7
|
export { sameFileIdentity, type FileIdentityStat } from "./file-identity.js";
|
package/dist/advanced.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"advanced.d.ts","sourceRoot":"","sources":["../src/advanced.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"advanced.d.ts","sourceRoot":"","sources":["../src/advanced.ts"],"names":[],"mappings":"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"}
|
package/dist/advanced.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
export { createAsyncLock } from "./async-lock.js";
|
|
5
5
|
export { stageFileInDirectory, } from "./native-staged-file.js";
|
|
6
6
|
export { readFileDescriptorBounded, readFileDescriptorBoundedSync, readFileHandleBounded, } from "./bounded-read.js";
|
|
7
|
+
export { readFileWindowFully, readFileWindowFullySync, } from "./positional-read.js";
|
|
7
8
|
export { assertNoUnsafeDeviceReadPath, isUnsafeDeviceReadPath, matchUnsafeDeviceReadPath, } from "./device-path.js";
|
|
8
9
|
export { assertAbsolutePathInput, canonicalPathFromExistingAncestor, ensureAbsoluteDirectory, findExistingAncestor, resolveAbsolutePathForRead, resolveAbsolutePathForWrite, } from "./absolute-path.js";
|
|
9
10
|
export { sameFileIdentity } from "./file-identity.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"archive-crc32.d.ts","sourceRoot":"","sources":["../src/archive-crc32.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"archive-crc32.d.ts","sourceRoot":"","sources":["../src/archive-crc32.ts"],"names":[],"mappings":"AAYA,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAOpE"}
|
package/dist/archive-crc32.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
import zlib from "node:zlib";
|
|
2
|
+
const nativeCrc32 = zlib.crc32;
|
|
3
|
+
// Node 22.0–22.1 are supported but predate zlib.crc32.
|
|
4
|
+
const CRC32_TABLE = nativeCrc32 ? undefined : Array.from({ length: 256 }, (_, index) => {
|
|
2
5
|
let value = index;
|
|
3
6
|
for (let bit = 0; bit < 8; bit += 1) {
|
|
4
7
|
value = (value & 1) !== 0 ? 0xedb88320 ^ (value >>> 1) : value >>> 1;
|
|
@@ -6,6 +9,8 @@ const CRC32_TABLE = Array.from({ length: 256 }, (_, index) => {
|
|
|
6
9
|
return value >>> 0;
|
|
7
10
|
});
|
|
8
11
|
export function updateCrc32(previous, buffer) {
|
|
12
|
+
if (nativeCrc32)
|
|
13
|
+
return nativeCrc32(buffer, previous >>> 0);
|
|
9
14
|
let crc = previous ^ -1;
|
|
10
15
|
for (const byte of buffer) {
|
|
11
16
|
crc = (crc >>> 8) ^ (CRC32_TABLE[(crc ^ byte) & 0xff] ?? 0);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"archive-deadline.d.ts","sourceRoot":"","sources":["../src/archive-deadline.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"archive-deadline.d.ts","sourceRoot":"","sources":["../src/archive-deadline.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,EAAE,WAAW,CAAC;IACpB,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,sBAAsB,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IACjE,2BAA2B,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACjD,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB,CAAC;AAEF,wBAAsB,gCAAgC,CAAC,CAAC,EACtD,QAAQ,EAAE,kBAAkB,GAAG,SAAS,EACxC,GAAG,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACpB,OAAO,CAAC,CAAC,CAAC,CAEZ;AAWD,wBAAgB,0BAA0B,CACxC,GAAG,EAAE,OAAO,EACZ,QAAQ,EAAE,kBAAkB,GAC3B,OAAO,CAST;AAED,wBAAsB,eAAe,CAAC,CAAC,EACrC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EACnB,QAAQ,EAAE,kBAAkB,GAC3B,OAAO,CAAC,CAAC,CAAC,CAgBZ;AAuDD,wBAAsB,sBAAsB,CAAC,CAAC,EAC5C,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,CAAC,QAAQ,EAAE,kBAAkB,KAAK,OAAO,CAAC,CAAC,CAAC,GAChD,OAAO,CAAC,CAAC,CAAC,CAkBZ"}
|
package/dist/archive-deadline.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { scheduleTimeout } from "./timing.js";
|
|
1
2
|
export async function ownExtractionDestinationMutation(deadline, run) {
|
|
2
3
|
return deadline ? await deadline.ownDestinationMutation(run) : await run();
|
|
3
4
|
}
|
|
@@ -68,16 +69,14 @@ function createExtractionDeadline(timeoutMs, label) {
|
|
|
68
69
|
dispose: () => undefined,
|
|
69
70
|
};
|
|
70
71
|
}
|
|
71
|
-
const
|
|
72
|
+
const cancelTimeout = scheduleTimeout(() => {
|
|
72
73
|
controller.abort(timeoutError);
|
|
73
74
|
}, timeoutMs);
|
|
74
75
|
return {
|
|
75
76
|
signal: controller.signal,
|
|
76
77
|
check,
|
|
77
78
|
...mutationOwner,
|
|
78
|
-
dispose:
|
|
79
|
-
clearTimeout(timeoutId);
|
|
80
|
-
},
|
|
79
|
+
dispose: cancelTimeout,
|
|
81
80
|
};
|
|
82
81
|
}
|
|
83
82
|
export async function withExtractionDeadline(timeoutMs, label, run) {
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type ExtractionDeadline } from "./archive-deadline.js";
|
|
2
|
+
import type { AsyncDirectoryGuard } from "./directory-guard.js";
|
|
3
|
+
import type { PublishedWriteIdentity } from "./pinned-write.js";
|
|
4
|
+
import type { Root } from "./root.js";
|
|
5
|
+
export type ArchivePublishedFile = {
|
|
6
|
+
relativePath: string;
|
|
7
|
+
identity: PublishedWriteIdentity;
|
|
8
|
+
guards: readonly AsyncDirectoryGuard[];
|
|
9
|
+
};
|
|
10
|
+
export type ArchivePublishedDirectory = {
|
|
11
|
+
guard: AsyncDirectoryGuard;
|
|
12
|
+
parents: readonly AsyncDirectoryGuard[];
|
|
13
|
+
mode: number;
|
|
14
|
+
};
|
|
15
|
+
export declare function finalizeArchivePublication(params: {
|
|
16
|
+
targetRoot: Root;
|
|
17
|
+
destinationGuard: AsyncDirectoryGuard;
|
|
18
|
+
sourceGuard: AsyncDirectoryGuard;
|
|
19
|
+
files: readonly ArchivePublishedFile[];
|
|
20
|
+
directories: readonly ArchivePublishedDirectory[];
|
|
21
|
+
durable: boolean;
|
|
22
|
+
deadline?: ExtractionDeadline;
|
|
23
|
+
}): Promise<void>;
|
|
24
|
+
//# sourceMappingURL=archive-durability.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"archive-durability.d.ts","sourceRoot":"","sources":["../src/archive-durability.ts"],"names":[],"mappings":"AAEA,OAAO,EAAoC,KAAK,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAElG,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAIhE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAChE,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAMtC,MAAM,MAAM,oBAAoB,GAAG;IACjC,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,sBAAsB,CAAC;IACjC,MAAM,EAAE,SAAS,mBAAmB,EAAE,CAAC;CACxC,CAAC;AACF,MAAM,MAAM,yBAAyB,GAAG;IACtC,KAAK,EAAE,mBAAmB,CAAC;IAC3B,OAAO,EAAE,SAAS,mBAAmB,EAAE,CAAC;IACxC,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,wBAAsB,0BAA0B,CAAC,MAAM,EAAE;IACvD,UAAU,EAAE,IAAI,CAAC;IACjB,gBAAgB,EAAE,mBAAmB,CAAC;IACtC,WAAW,EAAE,mBAAmB,CAAC;IACjC,KAAK,EAAE,SAAS,oBAAoB,EAAE,CAAC;IACvC,WAAW,EAAE,SAAS,yBAAyB,EAAE,CAAC;IAClD,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,kBAAkB,CAAC;CAC/B,GAAG,OAAO,CAAC,IAAI,CAAC,CA4FhB"}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
|
|
2
|
+
if (value !== null && value !== void 0) {
|
|
3
|
+
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
|
4
|
+
var dispose, inner;
|
|
5
|
+
if (async) {
|
|
6
|
+
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
|
|
7
|
+
dispose = value[Symbol.asyncDispose];
|
|
8
|
+
}
|
|
9
|
+
if (dispose === void 0) {
|
|
10
|
+
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
|
|
11
|
+
dispose = value[Symbol.dispose];
|
|
12
|
+
if (async) inner = dispose;
|
|
13
|
+
}
|
|
14
|
+
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
|
|
15
|
+
if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
|
|
16
|
+
env.stack.push({ value: value, dispose: dispose, async: async });
|
|
17
|
+
}
|
|
18
|
+
else if (async) {
|
|
19
|
+
env.stack.push({ async: true });
|
|
20
|
+
}
|
|
21
|
+
return value;
|
|
22
|
+
};
|
|
23
|
+
var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
|
|
24
|
+
return function (env) {
|
|
25
|
+
function fail(e) {
|
|
26
|
+
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
|
|
27
|
+
env.hasError = true;
|
|
28
|
+
}
|
|
29
|
+
var r, s = 0;
|
|
30
|
+
function next() {
|
|
31
|
+
while (r = env.stack.pop()) {
|
|
32
|
+
try {
|
|
33
|
+
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
|
|
34
|
+
if (r.dispose) {
|
|
35
|
+
var result = r.dispose.call(r.value);
|
|
36
|
+
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
|
|
37
|
+
}
|
|
38
|
+
else s |= 1;
|
|
39
|
+
}
|
|
40
|
+
catch (e) {
|
|
41
|
+
fail(e);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
|
|
45
|
+
if (env.hasError) throw env.error;
|
|
46
|
+
}
|
|
47
|
+
return next();
|
|
48
|
+
};
|
|
49
|
+
})(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
50
|
+
var e = new Error(message);
|
|
51
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
52
|
+
});
|
|
53
|
+
import fsSync from "node:fs";
|
|
54
|
+
import path from "node:path";
|
|
55
|
+
import { ownExtractionDestinationMutation } from "./archive-deadline.js";
|
|
56
|
+
import { assertDirectoryIdentityGuard, assertResolvedInsideDestination, createArchiveSymlinkTraversalError } from "./archive-staging.js";
|
|
57
|
+
import { pinNodeDirectoryForMode } from "./directory-mode-node.js";
|
|
58
|
+
import { pinDirectory, syncDirectory } from "./directory-durability.js";
|
|
59
|
+
import { syncFileBestEffort } from "./file-sync.js";
|
|
60
|
+
import { normalizePinnedWriteError } from "./root-errors.js";
|
|
61
|
+
import { inspectFileIdentity } from "./strict-file-identity.js";
|
|
62
|
+
import { getFsSafeTestHooks } from "./test-hooks.js";
|
|
63
|
+
import { FsSafeError } from "./errors.js";
|
|
64
|
+
export async function finalizeArchivePublication(params) {
|
|
65
|
+
const check = () => params.deadline?.check();
|
|
66
|
+
const assertGuards = async (guards) => {
|
|
67
|
+
for (const guard of [params.destinationGuard, ...guards, params.sourceGuard]) {
|
|
68
|
+
await assertDirectoryIdentityGuard(guard);
|
|
69
|
+
check();
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
await ownExtractionDestinationMutation(params.deadline, async () => {
|
|
73
|
+
if (params.durable) {
|
|
74
|
+
// Join every active sync before propagating failure or starting another batch.
|
|
75
|
+
for (let offset = 0; offset < params.files.length; offset += 8) {
|
|
76
|
+
check();
|
|
77
|
+
const results = await Promise.allSettled(params.files.slice(offset, offset + 8).map(async (file) => {
|
|
78
|
+
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
79
|
+
try {
|
|
80
|
+
await assertGuards(file.guards);
|
|
81
|
+
const opened = __addDisposableResource(env_1, await params.targetRoot.open(file.relativePath, { hardlinks: "reject", symlinks: "reject" })
|
|
82
|
+
.catch((error) => {
|
|
83
|
+
if (error instanceof FsSafeError && (error.code === "hardlink" || error.code === "path-alias")) {
|
|
84
|
+
throw createArchiveSymlinkTraversalError(file.relativePath);
|
|
85
|
+
}
|
|
86
|
+
throw error;
|
|
87
|
+
}), true);
|
|
88
|
+
check();
|
|
89
|
+
await inspectFileIdentity(() => fsSync.fstatSync(opened.handle.fd, { bigint: true }), file.identity);
|
|
90
|
+
check();
|
|
91
|
+
await syncFileBestEffort(opened.handle).catch((error) => { throw normalizePinnedWriteError(error); });
|
|
92
|
+
check();
|
|
93
|
+
await assertGuards(file.guards);
|
|
94
|
+
await inspectFileIdentity(async () => {
|
|
95
|
+
const stat = fsSync.lstatSync(opened.realPath, { bigint: true });
|
|
96
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.nlink > 1n) {
|
|
97
|
+
throw new FsSafeError("path-mismatch", "archive file changed during durability pass");
|
|
98
|
+
}
|
|
99
|
+
return stat;
|
|
100
|
+
}, file.identity);
|
|
101
|
+
check();
|
|
102
|
+
}
|
|
103
|
+
catch (e_1) {
|
|
104
|
+
env_1.error = e_1;
|
|
105
|
+
env_1.hasError = true;
|
|
106
|
+
}
|
|
107
|
+
finally {
|
|
108
|
+
const result_1 = __disposeResources(env_1);
|
|
109
|
+
if (result_1)
|
|
110
|
+
await result_1;
|
|
111
|
+
}
|
|
112
|
+
}));
|
|
113
|
+
const failure = results.find((result) => result.status === "rejected");
|
|
114
|
+
if (failure?.status === "rejected")
|
|
115
|
+
throw failure.reason;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// Leave working modes intact until file syncing finishes. Re-pin one directory
|
|
119
|
+
// at a time against its original guard, so wide archives do not exhaust fds.
|
|
120
|
+
const directories = [...params.directories].sort((a, b) => b.guard.dir.split(path.sep).length - a.guard.dir.split(path.sep).length);
|
|
121
|
+
for (const directory of directories) {
|
|
122
|
+
const guards = [...directory.parents, directory.guard];
|
|
123
|
+
await assertGuards(guards);
|
|
124
|
+
const owner = await pinNodeDirectoryForMode(directory.guard.dir);
|
|
125
|
+
let pinned;
|
|
126
|
+
try {
|
|
127
|
+
check();
|
|
128
|
+
await assertGuards(guards);
|
|
129
|
+
if (params.durable && process.platform !== "win32") {
|
|
130
|
+
// An existing search-only directory may become readable in its final mode.
|
|
131
|
+
try {
|
|
132
|
+
pinned = await pinDirectory(directory.guard.dir);
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
if (error.code !== "EACCES")
|
|
136
|
+
throw error;
|
|
137
|
+
}
|
|
138
|
+
check();
|
|
139
|
+
}
|
|
140
|
+
await getFsSafeTestHooks()?.beforeArchiveOutputMutation?.("chmod", directory.guard.dir);
|
|
141
|
+
check();
|
|
142
|
+
await assertGuards(guards);
|
|
143
|
+
await owner.apply(directory.mode, { check, beforeChmod: async () => {
|
|
144
|
+
await assertGuards(guards);
|
|
145
|
+
await assertResolvedInsideDestination({ destinationRealDir: params.destinationGuard.realPath,
|
|
146
|
+
targetPath: directory.guard.dir, originalPath: path.relative(params.targetRoot.rootDir, directory.guard.dir) });
|
|
147
|
+
check();
|
|
148
|
+
} });
|
|
149
|
+
check();
|
|
150
|
+
await assertGuards(guards);
|
|
151
|
+
if (params.durable) {
|
|
152
|
+
if (process.platform === "win32") {
|
|
153
|
+
await syncDirectory(directory.guard.dir).catch((error) => { throw normalizePinnedWriteError(error); });
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
pinned ??= await pinDirectory(directory.guard.dir);
|
|
157
|
+
check();
|
|
158
|
+
await pinned.sync().catch((error) => { throw normalizePinnedWriteError(error); });
|
|
159
|
+
}
|
|
160
|
+
check();
|
|
161
|
+
await assertGuards(guards);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
finally {
|
|
165
|
+
try {
|
|
166
|
+
await pinned?.close();
|
|
167
|
+
}
|
|
168
|
+
finally {
|
|
169
|
+
await owner.close();
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (params.durable) {
|
|
174
|
+
await assertGuards([]);
|
|
175
|
+
await syncDirectory(params.destinationGuard.realPath).catch((error) => { throw normalizePinnedWriteError(error); });
|
|
176
|
+
check();
|
|
177
|
+
await assertGuards([]);
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Writable } from "node:stream";
|
|
2
2
|
import type { Gunzip } from "node:zlib";
|
|
3
|
+
/** Validate the same physical suffix when admission owns an in-memory input. */
|
|
4
|
+
export declare function validateGzipBufferTail(input: Buffer, consumed: number, signal?: AbortSignal): Promise<void>;
|
|
3
5
|
/** Track physical input, not the sum of bytes consumed across separate writes:
|
|
4
6
|
* gunzip can resume on a later chunk after leaving an earlier padding gap. */
|
|
5
7
|
export declare class GzipInput extends Writable {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"archive-gzip-tail.d.ts","sourceRoot":"","sources":["../src/archive-gzip-tail.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"archive-gzip-tail.d.ts","sourceRoot":"","sources":["../src/archive-gzip-tail.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AASxC,gFAAgF;AAChF,wBAAsB,sBAAsB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAWjH;AAED;8EAC8E;AAC9E,qBAAa,SAAU,SAAQ,QAAQ;IAIzB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAHpC,OAAO,CAAC,QAAQ,CAAK;IACrB,OAAO,CAAC,WAAW,CAAqB;IAExC,YAA6B,OAAO,EAAE,MAAM,EAE3C;IAED,IAAI,UAAU,IAAI,MAAM,CAEvB;IAEQ,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,cAAc,EAAE,QAAQ,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,KAAK,IAAI,GAAG,IAAI,CAcxG;IAEQ,MAAM,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,KAAK,IAAI,GAAG,IAAI,CAE9D;IAEQ,QAAQ,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,KAAK,IAAI,GAAG,IAAI,CAGpF;CACF;AAED;6EAC6E;AAC7E,wBAAsB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAoBvH"}
|
|
@@ -1,6 +1,26 @@
|
|
|
1
|
+
import fsSync from "node:fs";
|
|
1
2
|
import fs from "node:fs/promises";
|
|
2
3
|
import { Writable } from "node:stream";
|
|
3
4
|
import { ArchiveFormatError } from "./archive-errors.js";
|
|
5
|
+
function assertConsumedBoundary(consumed, size) {
|
|
6
|
+
if (!Number.isSafeInteger(consumed) || consumed <= 0 || consumed > size) {
|
|
7
|
+
throw new ArchiveFormatError("invalid gzip consumed-input boundary");
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
/** Validate the same physical suffix when admission owns an in-memory input. */
|
|
11
|
+
export async function validateGzipBufferTail(input, consumed, signal) {
|
|
12
|
+
signal?.throwIfAborted();
|
|
13
|
+
assertConsumedBoundary(consumed, input.length);
|
|
14
|
+
for (let position = consumed; position < input.length; position += 65536) {
|
|
15
|
+
signal?.throwIfAborted();
|
|
16
|
+
if (input.subarray(position, position + 65536).some(byte => byte !== 0)) {
|
|
17
|
+
throw new ArchiveFormatError("nonzero gzip container padding");
|
|
18
|
+
}
|
|
19
|
+
if (position + 65536 < input.length)
|
|
20
|
+
await new Promise(resolve => setImmediate(resolve));
|
|
21
|
+
}
|
|
22
|
+
signal?.throwIfAborted();
|
|
23
|
+
}
|
|
4
24
|
/** Track physical input, not the sum of bytes consumed across separate writes:
|
|
5
25
|
* gunzip can resume on a later chunk after leaving an earlier padding gap. */
|
|
6
26
|
export class GzipInput extends Writable {
|
|
@@ -48,10 +68,8 @@ export async function validateGzipContainerTail(filePath, consumed, signal) {
|
|
|
48
68
|
signal?.throwIfAborted();
|
|
49
69
|
const handle = await fs.open(filePath, "r");
|
|
50
70
|
try {
|
|
51
|
-
const { size } =
|
|
52
|
-
|
|
53
|
-
throw new ArchiveFormatError("invalid gzip consumed-input boundary");
|
|
54
|
-
}
|
|
71
|
+
const { size } = fsSync.fstatSync(handle.fd);
|
|
72
|
+
assertConsumedBoundary(consumed, size);
|
|
55
73
|
if (consumed === size)
|
|
56
74
|
return;
|
|
57
75
|
const buffer = Buffer.allocUnsafe(65536);
|
package/dist/archive-input.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { constants as fsConstants } from "node:fs";
|
|
1
|
+
import fsSync, { constants as fsConstants } from "node:fs";
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { ARCHIVE_LIMIT_ERROR_CODE, ArchiveLimitError, } from "./archive-limits.js";
|
|
@@ -25,7 +25,7 @@ export async function stageArchiveFileForExtraction(params) {
|
|
|
25
25
|
params.deadline.check();
|
|
26
26
|
const sourcePath = path.resolve(params.archivePath);
|
|
27
27
|
const initialStat = await inspectFileIdentity(async () => {
|
|
28
|
-
const stat =
|
|
28
|
+
const stat = fsSync.lstatSync(sourcePath, { bigint: true });
|
|
29
29
|
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
30
30
|
throw new Error(`archive is not a regular file: ${params.archivePath}`);
|
|
31
31
|
}
|
|
@@ -43,13 +43,13 @@ export async function stageArchiveFileForExtraction(params) {
|
|
|
43
43
|
fileName: path.basename(sourcePath),
|
|
44
44
|
});
|
|
45
45
|
const opened = await inspectFileIdentity(async () => {
|
|
46
|
-
const stat =
|
|
46
|
+
const stat = fsSync.fstatSync(handle.fd, { bigint: true });
|
|
47
47
|
if (!stat.isFile())
|
|
48
48
|
throw new Error("archive changed during validation");
|
|
49
49
|
return stat;
|
|
50
50
|
}, initialStat);
|
|
51
51
|
await inspectFileIdentity(async () => {
|
|
52
|
-
const stat =
|
|
52
|
+
const stat = fsSync.lstatSync(sourcePath, { bigint: true });
|
|
53
53
|
if (stat.isSymbolicLink() || !stat.isFile())
|
|
54
54
|
throw new Error("archive changed during validation");
|
|
55
55
|
return stat;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"archive-kind.d.ts","sourceRoot":"","sources":["../src/archive-kind.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"archive-kind.d.ts","sourceRoot":"","sources":["../src/archive-kind.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,WAAW,GAAG,KAAK,GAAG,WAAW,GAAG,UAAU,GAAG,KAAK,CAAC;AAmBnE,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAcvE;AAED,KAAK,2BAA2B,GAAG;IACjC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;CACxB,CAAC;AAkBF,wBAAsB,oBAAoB,CACxC,UAAU,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE,2BAA2B,GACpC,OAAO,CAAC,MAAM,CAAC,CA4BjB;AAED,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI,CAQjE"}
|
package/dist/archive-kind.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import fsSync from "node:fs";
|
|
1
2
|
import fs from "node:fs/promises";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import { FsSafeError } from "./errors.js";
|
|
@@ -37,7 +38,7 @@ async function hasPackedRootMarker(extractDir, rootMarkers) {
|
|
|
37
38
|
continue;
|
|
38
39
|
}
|
|
39
40
|
try {
|
|
40
|
-
|
|
41
|
+
fsSync.statSync(path.join(extractDir, trimmed));
|
|
41
42
|
return true;
|
|
42
43
|
}
|
|
43
44
|
catch {
|
|
@@ -49,7 +50,7 @@ async function hasPackedRootMarker(extractDir, rootMarkers) {
|
|
|
49
50
|
export async function resolvePackedRootDir(extractDir, options) {
|
|
50
51
|
const direct = path.join(extractDir, "package");
|
|
51
52
|
try {
|
|
52
|
-
const stat =
|
|
53
|
+
const stat = fsSync.statSync(direct);
|
|
53
54
|
if (stat.isDirectory()) {
|
|
54
55
|
return direct;
|
|
55
56
|
}
|
package/dist/archive-merge.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ type MergeParams = {
|
|
|
12
12
|
};
|
|
13
13
|
export declare function mergePlannedArchiveIntoDestination(params: MergeParams & {
|
|
14
14
|
entries: readonly ArchivePublicationEntry[];
|
|
15
|
+
durable?: boolean;
|
|
15
16
|
}): Promise<void>;
|
|
16
17
|
export declare function mergeExtractedTreeIntoDestination(params: MergeParams): Promise<void>;
|
|
17
18
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"archive-merge.d.ts","sourceRoot":"","sources":["../src/archive-merge.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"archive-merge.d.ts","sourceRoot":"","sources":["../src/archive-merge.ts"],"names":[],"mappings":"AAGA,OAAO,EAAoC,KAAK,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAkBlG,MAAM,MAAM,uBAAuB,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AACjG,KAAK,WAAW,GAAG;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,EAAE,kBAAkB,CAAC;CAC/B,CAAC;AAEF,wBAAsB,kCAAkC,CACtD,MAAM,EAAE,WAAW,GAAG;IAAE,OAAO,EAAE,SAAS,uBAAuB,EAAE,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GACvF,OAAO,CAAC,IAAI,CAAC,CAEf;AAED,wBAAsB,iCAAiC,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAE1F"}
|