From 65abededcadec096e356ce3bf8c66bed9e7b1e5c Mon Sep 17 00:00:00 2001 From: John Ericson Date: Mon, 15 Dec 2025 20:54:28 -0500 Subject: [PATCH] Add new `StorePath` bindings --- Cargo.lock | 8 +++++ nix-bindings-store/Cargo.toml | 2 ++ nix-bindings-store/src/path.rs | 62 +++++++++++++++++++++++++++++++++- 3 files changed, 71 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index f3d9634..4577bc4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -132,6 +132,12 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +[[package]] +name = "hex-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" + [[package]] name = "home" version = "0.5.11" @@ -301,12 +307,14 @@ version = "0.2.1" dependencies = [ "anyhow", "ctor", + "hex-literal", "lazy_static", "nix-bindings-store-sys", "nix-bindings-util", "nix-bindings-util-sys", "pkg-config", "tempfile", + "zerocopy", ] [[package]] diff --git a/nix-bindings-store/Cargo.toml b/nix-bindings-store/Cargo.toml index c665a0e..c37d62d 100644 --- a/nix-bindings-store/Cargo.toml +++ b/nix-bindings-store/Cargo.toml @@ -17,9 +17,11 @@ nix-bindings-util = { path = "../nix-bindings-util", version = "0.2.1" } nix-bindings-util-sys = { path = "../nix-bindings-util-sys", version = "0.2.1" } nix-bindings-store-sys = { path = "../nix-bindings-store-sys", version = "0.2.1" } lazy_static = "1.4" +zerocopy = "0.8" [dev-dependencies] ctor = "0.2" +hex-literal = "0.4" tempfile = "3.10" [build-dependencies] diff --git a/nix-bindings-store/src/path.rs b/nix-bindings-store/src/path.rs index 51469af..e3be645 100644 --- a/nix-bindings-store/src/path.rs +++ b/nix-bindings-store/src/path.rs @@ -1,15 +1,21 @@ use std::ptr::NonNull; -use anyhow::Result; +use anyhow::{Context as _, Result}; use nix_bindings_store_sys as raw; +#[cfg(nix_at_least = "2.33")] +use nix_bindings_util::{check_call, context::Context}; use nix_bindings_util::{ result_string_init, string_return::{callback_get_result_string, callback_get_result_string_data}, }; +/// The size of a store path hash in bytes (20 bytes, decoded from nix32). +pub const STORE_PATH_HASH_SIZE: usize = 20; + pub struct StorePath { raw: NonNull, } + impl StorePath { /// Get the name of the store path. /// @@ -26,6 +32,43 @@ impl StorePath { } } + /// Get the hash part of the store path. + /// + /// This returns the decoded hash (not the nix32-encoded string). + #[cfg(nix_at_least = "2.33")] + pub fn hash(&self) -> Result<[u8; STORE_PATH_HASH_SIZE]> { + let mut result = [0u8; STORE_PATH_HASH_SIZE]; + let hash_part: &mut raw::store_path_hash_part = zerocopy::transmute_mut!(&mut result); + + let mut ctx = Context::new(); + + unsafe { + check_call!(raw::store_path_hash(&mut ctx, self.as_ptr(), hash_part))?; + } + Ok(result) + } + + /// Create a StorePath from hash and name components. + #[cfg(nix_at_least = "2.33")] + pub fn from_parts(hash: &[u8; STORE_PATH_HASH_SIZE], name: &str) -> Result { + let hash_part: &raw::store_path_hash_part = zerocopy::transmute_ref!(hash); + + let mut ctx = Context::new(); + + let out_path = unsafe { + check_call!(raw::store_create_from_parts( + &mut ctx, + hash_part, + name.as_ptr() as *const i8, + name.len() + ))? + }; + + NonNull::new(out_path) + .map(|ptr| unsafe { Self::new_raw(ptr) }) + .context("store_create_from_parts returned null") + } + /// This is a low level function that you shouldn't have to call unless you are developing the Nix bindings. /// /// Construct a new `StorePath` by first cloning the C store path. @@ -81,6 +124,9 @@ impl Drop for StorePath { #[cfg(test)] mod tests { + use super::*; + use hex_literal::hex; + #[test] #[cfg(nix_at_least = "2.26" /* get_storedir */)] fn store_path_name() { @@ -91,4 +137,18 @@ mod tests { let store_path = store.parse_store_path(store_path_string.as_str()).unwrap(); assert_eq!(store_path.name().unwrap(), "bash-interactive-5.2p26"); } + + #[test] + #[cfg(nix_at_least = "2.33")] + fn store_path_round_trip() { + let original_hash: [u8; STORE_PATH_HASH_SIZE] = + hex!("0123456789abcdef0011223344556677deadbeef"); + let original_name = "foo.drv"; + + let store_path = StorePath::from_parts(&original_hash, original_name).unwrap(); + + // Round trip gets back what we started with + assert_eq!(store_path.hash().unwrap(), original_hash); + assert_eq!(store_path.name().unwrap(), original_name); + } }