Merge pull request #42 from nixops4/store-path-binding

Add `StorePath::from_parts(hash, name)`
This commit is contained in:
Robert Hensing 2026-01-14 21:51:24 +01:00 committed by GitHub
commit 42f668b812
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 113 additions and 1 deletions

29
Cargo.lock generated
View file

@ -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]]
@ -316,6 +324,7 @@ dependencies = [
"bindgen",
"nix-bindings-util-sys",
"pkg-config",
"zerocopy",
]
[[package]]
@ -611,3 +620,23 @@ name = "wit-bindgen"
version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
[[package]]
name = "zerocopy"
version = "0.8.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ea879c944afe8a2b25fef16bb4ba234f47c694565e97383b36f3a878219065c"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf955aa904d6040f70dc8e9384444cb1030aed272ba3cb09bbc4ab9e7c1f34f5"
dependencies = [
"proc-macro2",
"quote",
"syn",
]

View file

@ -13,6 +13,7 @@ path = "src/lib.rs"
[dependencies]
nix-bindings-util-sys = { path = "../nix-bindings-util-sys", version = "0.2.1" }
zerocopy = { version = "0.8", features = ["derive"] }
[build-dependencies]
bindgen = "0.69"

View file

@ -9,6 +9,22 @@ impl bindgen::callbacks::ParseCallbacks for StripNixPrefix {
}
}
#[derive(Debug)]
struct AddZerocopyDerives {}
impl bindgen::callbacks::ParseCallbacks for AddZerocopyDerives {
fn add_derives(&self, info: &bindgen::callbacks::DeriveInfo<'_>) -> Vec<String> {
if info.name == "store_path_hash_part" {
vec![
"zerocopy::FromBytes".to_string(),
"zerocopy::IntoBytes".to_string(),
"zerocopy::Immutable".to_string(),
]
} else {
vec![]
}
}
}
fn main() {
println!("cargo:rerun-if-changed=include/nix-c-store.h");
println!("cargo:rustc-link-lib=nixstorec");
@ -29,6 +45,7 @@ fn main() {
.clang_args(args)
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
.parse_callbacks(Box::new(StripNixPrefix))
.parse_callbacks(Box::new(AddZerocopyDerives {}))
// Blocklist symbols from nix-bindings-util-sys
.blocklist_file(".*nix_api_util\\.h")
.generate()

View file

@ -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]

View file

@ -1,15 +1,24 @@
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;
#[cfg(nix_at_least = "2.33")]
const _: () = assert!(std::mem::size_of::<raw::store_path_hash_part>() == STORE_PATH_HASH_SIZE);
pub struct StorePath {
raw: NonNull<raw::StorePath>,
}
impl StorePath {
/// Get the name of the store path.
///
@ -26,6 +35,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<Self> {
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 +127,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 +140,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);
}
}