Commit 9c7d438
feat(fs): sha256_file, write_etc_atomic, probe_config_files — closes Plan §6.4 (#9)
* feat(fs): land sha256_file, write_etc_atomic, probe_config_files
M1-W1-G — closes Plan §6.4. pearlite-fs is the third pure crate of M1
Week 1; primitives the engine and adapters share for hashing files,
atomically writing /etc with mode/owner/group preserved, and probing
declared ConfigEntry targets into a ConfigFileInventory.
Public API:
- pub fn sha256_file(p: &Path) -> Result<[u8; 32], FsError>
- pub fn write_etc_atomic(target, content, mode, owner, group) ->
Result<(), FsError>
- pub fn probe_config_files(targets: &[ConfigEntry]) -> ConfigFileInventory
ConfigEntry / ConfigFileInventory / ConfigFileMeta are re-exported
read-only from pearlite-schema (PR #3).
Internal modules: hash.rs, atomic.rs, chown.rs, inventory.rs, errors.rs.
Implementation notes:
- hash.rs: 64 KiB chunked read; constant memory regardless of file
size. Heap-allocated buffer (clippy::large_stack_arrays denies the
on-stack version).
- atomic.rs: PRD §7.4 recipe — temp + chmod + chown BEFORE rename so
the file appears with the right ownership atomically — followed by
fsync of the file's data, atomic rename, fsync of the parent
directory.
- chown.rs: nix::unistd::User/Group for name<->id resolution; libc
chown via nix; Permissions::from_mode for chmod. Plan §6.4 hard
rule: no std::process::Command for ownership ops.
- inventory.rs: stat each target, hash, encode hex, look up
uid/gid -> name. Missing targets are silently absent — the diff
engine treats absence as drift in its own pass.
Workspace deps added:
- nix = { version = "0.31", features = ["user", "fs"] }
- hex = "0.4"
Tests:
- hash: known_file_known_digest (SHA-256("abc")), empty_file_known_digest,
missing_file_yields_io_error.
- chown: unknown_user_yields_typed_error, unknown_group_yields_typed_error.
- atomic: write_then_read_round_trips, mode_preserved_through_temp,
chown_to_self_succeeds (chowns to the running user since CI lacks
root), unknown_owner_yields_typed_error.
- inventory: missing_target_yields_none_in_inventory,
present_target_records_sha256_and_mode, empty_target_list_yields_empty_inventory.
- 2 fork-based tests stubbed as #[ignore]:
crash_after_write_before_rename and
rename_atomic_visible_to_concurrent_reader. These need rusty-fork
machinery and land in M1 W2 with engine integration tests.
Plan §6.4 "Done when" status:
- sha256 implementation is constant-memory (chunked I/O). ✓
- Atomic write tested via simulated kill-9 in unit tests using forked
processes. PARTIAL — ignored stubs in place; full test machinery
arrives in M1 W2.
- No std::process::Command — chown happens through libc directly via
nix. ✓
- pearlite-audit reports no §13 violations. ✓
Verification:
- cargo test -p pearlite-fs (12 passed; 0 failed; 2 ignored)
- cargo clippy --workspace --all-targets -- -D warnings (zero warnings)
- cargo fmt --all --check
- scripts/ci/check-spdx.sh
- pearlite-audit check . (1 check, 0 violations)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(fs): pin path dep to a version to satisfy cargo-deny wildcard rule
cargo-deny treats `pearlite-schema = { path = "..." }` as a wildcard
dep because no version constraint is present. The workspace lints have
`wildcards = "deny"`. Pinning to "0.1" matches workspace.package.version
(0.1.0) and the workspace SemVer baseline.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>1 parent 6ce3b46 commit 9c7d438
9 files changed
Lines changed: 752 additions & 0 deletions
File tree
- crates/pearlite-fs
- src
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
41 | 41 | | |
42 | 42 | | |
43 | 43 | | |
| 44 | + | |
| 45 | + | |
44 | 46 | | |
45 | 47 | | |
46 | 48 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
12 | 12 | | |
13 | 13 | | |
14 | 14 | | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
0 commit comments