nix_bindings_store/path/
mod.rs

1use std::ptr::NonNull;
2
3use anyhow::{Context as _, Result};
4use nix_bindings_store_sys as raw;
5#[cfg(nix_at_least = "2.33")]
6use nix_bindings_util::{check_call, context::Context};
7use nix_bindings_util::{
8    result_string_init,
9    string_return::{callback_get_result_string, callback_get_result_string_data},
10};
11
12/// The size of a store path hash in bytes (20 bytes, decoded from nix32).
13pub const STORE_PATH_HASH_SIZE: usize = 20;
14
15#[cfg(nix_at_least = "2.33")]
16const _: () = assert!(std::mem::size_of::<raw::store_path_hash_part>() == STORE_PATH_HASH_SIZE);
17
18pub struct StorePath {
19    raw: NonNull<raw::StorePath>,
20}
21
22impl StorePath {
23    /// Get the name of the store path.
24    ///
25    /// For a store path like `/nix/store/abc1234...-foo-1.2`, this function will return `foo-1.2`.
26    pub fn name(&self) -> Result<String> {
27        unsafe {
28            let mut r = result_string_init!();
29            raw::store_path_name(
30                self.as_ptr(),
31                Some(callback_get_result_string),
32                callback_get_result_string_data(&mut r),
33            );
34            r
35        }
36    }
37
38    /// Get the hash part of the store path.
39    ///
40    /// This returns the decoded hash (not the nix32-encoded string).
41    #[cfg(nix_at_least = "2.33")]
42    pub fn hash(&self) -> Result<[u8; STORE_PATH_HASH_SIZE]> {
43        let mut result = [0u8; STORE_PATH_HASH_SIZE];
44        let hash_part: &mut raw::store_path_hash_part = zerocopy::transmute_mut!(&mut result);
45
46        let mut ctx = Context::new();
47
48        unsafe {
49            check_call!(raw::store_path_hash(&mut ctx, self.as_ptr(), hash_part))?;
50        }
51        Ok(result)
52    }
53
54    /// Create a StorePath from hash and name components.
55    #[cfg(nix_at_least = "2.33")]
56    pub fn from_parts(hash: &[u8; STORE_PATH_HASH_SIZE], name: &str) -> Result<Self> {
57        let hash_part: &raw::store_path_hash_part = zerocopy::transmute_ref!(hash);
58
59        let mut ctx = Context::new();
60
61        let out_path = unsafe {
62            check_call!(raw::store_create_from_parts(
63                &mut ctx,
64                hash_part,
65                name.as_ptr() as *const i8,
66                name.len()
67            ))?
68        };
69
70        NonNull::new(out_path)
71            .map(|ptr| unsafe { Self::new_raw(ptr) })
72            .context("store_create_from_parts returned null")
73    }
74
75    /// This is a low level function that you shouldn't have to call unless you are developing the Nix bindings.
76    ///
77    /// Construct a new `StorePath` by first cloning the C store path.
78    ///
79    /// # Safety
80    ///
81    /// This does not take ownership of the C store path, so it should be a borrowed pointer, or you should free it.
82    pub unsafe fn new_raw_clone(raw: NonNull<raw::StorePath>) -> Self {
83        Self::new_raw(
84            NonNull::new(raw::store_path_clone(raw.as_ptr()))
85                .or_else(|| panic!("nix_store_path_clone returned a null pointer"))
86                .unwrap(),
87        )
88    }
89
90    /// This is a low level function that you shouldn't have to call unless you are developing the Nix bindings.
91    ///
92    /// Takes ownership of a C `nix_store_path`. It will be freed when the `StorePath` is dropped.
93    ///
94    /// # Safety
95    ///
96    /// The caller must ensure that the provided `NonNull<raw::StorePath>` is valid and that the ownership
97    /// semantics are correctly followed. The `raw` pointer must not be used after being passed to this function.
98    pub unsafe fn new_raw(raw: NonNull<raw::StorePath>) -> Self {
99        StorePath { raw }
100    }
101
102    /// This is a low level function that you shouldn't have to call unless you are developing the Nix bindings.
103    ///
104    /// Get a pointer to the underlying Nix C API store path.
105    ///
106    /// # Safety
107    ///
108    /// This function is unsafe because it returns a raw pointer. The caller must ensure that the pointer is not used beyond the lifetime of this `StorePath`.
109    pub unsafe fn as_ptr(&self) -> *mut raw::StorePath {
110        self.raw.as_ptr()
111    }
112}
113
114impl Clone for StorePath {
115    fn clone(&self) -> Self {
116        unsafe { Self::new_raw_clone(self.raw) }
117    }
118}
119
120impl Drop for StorePath {
121    fn drop(&mut self) {
122        unsafe {
123            raw::store_path_free(self.as_ptr());
124        }
125    }
126}
127
128#[cfg(all(feature = "harmonia", nix_at_least = "2.33"))]
129mod harmonia;
130
131#[cfg(test)]
132mod tests {
133    use super::*;
134    use hex_literal::hex;
135
136    #[test]
137    #[cfg(nix_at_least = "2.26" /* get_storedir */)]
138    fn store_path_name() {
139        let mut store = crate::store::Store::open(Some("dummy://"), []).unwrap();
140        let store_dir = store.get_storedir().unwrap();
141        let store_path_string =
142            format!("{store_dir}/rdd4pnr4x9rqc9wgbibhngv217w2xvxl-bash-interactive-5.2p26");
143        let store_path = store.parse_store_path(store_path_string.as_str()).unwrap();
144        assert_eq!(store_path.name().unwrap(), "bash-interactive-5.2p26");
145    }
146
147    #[test]
148    #[cfg(nix_at_least = "2.33")]
149    fn store_path_round_trip() {
150        let original_hash: [u8; STORE_PATH_HASH_SIZE] =
151            hex!("0123456789abcdef0011223344556677deadbeef");
152        let original_name = "foo.drv";
153
154        let store_path = StorePath::from_parts(&original_hash, original_name).unwrap();
155
156        // Round trip gets back what we started with
157        assert_eq!(store_path.hash().unwrap(), original_hash);
158        assert_eq!(store_path.name().unwrap(), original_name);
159    }
160}