use std::ptr::NonNull;
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},
};
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 {
pub fn name(&self) -> Result<String> {
unsafe {
let mut r = result_string_init!();
raw::store_path_name(
self.as_ptr(),
Some(callback_get_result_string),
callback_get_result_string_data(&mut r),
);
r
}
}
#[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)
}
#[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 std::ffi::c_char,
name.len()
))?
};
NonNull::new(out_path)
.map(|ptr| unsafe { Self::new_raw(ptr) })
.context("store_create_from_parts returned null")
}
pub unsafe fn new_raw_clone(raw: NonNull<raw::StorePath>) -> Self {
Self::new_raw(
NonNull::new(raw::store_path_clone(raw.as_ptr()))
.or_else(|| panic!("nix_store_path_clone returned a null pointer"))
.unwrap(),
)
}
pub unsafe fn new_raw(raw: NonNull<raw::StorePath>) -> Self {
StorePath { raw }
}
pub unsafe fn as_ptr(&self) -> *mut raw::StorePath {
self.raw.as_ptr()
}
}
impl Clone for StorePath {
fn clone(&self) -> Self {
unsafe { Self::new_raw_clone(self.raw) }
}
}
impl Drop for StorePath {
fn drop(&mut self) {
unsafe {
raw::store_path_free(self.as_ptr());
}
}
}
#[cfg(all(feature = "harmonia", nix_at_least = "2.33"))]
mod harmonia;
#[cfg(test)]
mod tests {
use super::*;
use hex_literal::hex;
#[test]
#[cfg(nix_at_least = "2.26" /* get_storedir */)]
fn store_path_name() {
let mut store = crate::store::Store::open(Some("dummy://"), []).unwrap();
let store_dir = store.get_storedir().unwrap();
let store_path_string =
format!("{store_dir}/rdd4pnr4x9rqc9wgbibhngv217w2xvxl-bash-interactive-5.2p26");
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();
assert_eq!(store_path.hash().unwrap(), original_hash);
assert_eq!(store_path.name().unwrap(), original_name);
}
}