Skip to content

Commit 9c7d438

Browse files
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

Cargo.lock

Lines changed: 191 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ sha2 = "0.10"
4141
tempfile = "3"
4242
rayon = "1"
4343
uuid = { version = "1", features = ["v7", "serde"] }
44+
nix = { version = "0.31", features = ["user", "fs"] }
45+
hex = "0.4"
4446

4547
[workspace.lints.rust]
4648
unsafe_code = "forbid"

crates/pearlite-fs/Cargo.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,12 @@ repository.workspace = true
1212
workspace = true
1313

1414
[dependencies]
15+
pearlite-schema = { path = "../pearlite-schema", version = "0.1" }
16+
sha2 = { workspace = true }
17+
tempfile = { workspace = true }
18+
thiserror = { workspace = true }
19+
nix = { workspace = true }
20+
hex = { workspace = true }
21+
22+
[dev-dependencies]
23+
tempfile = { workspace = true }

0 commit comments

Comments
 (0)