nix_bindings/
store.rs

1use std::{ffi::{CStr, CString}, ptr::NonNull, sync::Arc};
2
3use super::{Context, Error, Result, sys};
4
5/// Nix store for managing packages and derivations.
6///
7/// The store provides access to Nix packages, derivations, and store paths.
8pub struct Store {
9  pub(crate) inner:    NonNull<sys::Store>,
10  pub(crate) _context: Arc<Context>,
11}
12
13/// A path in the Nix store.
14///
15/// Represents a store path that can be realized, queried, or manipulated.
16pub struct StorePath {
17  pub(crate) inner:    NonNull<sys::StorePath>,
18  pub(crate) _context: Arc<Context>,
19}
20
21impl StorePath {
22  /// Parse a store path string into a StorePath.
23  ///
24  /// # Arguments
25  ///
26  /// * `context` - The Nix context
27  /// * `store` - The store containing the path
28  /// * `path` - The store path string (e.g., "/nix/store/...")
29  ///
30  /// # Errors
31  ///
32  /// Returns an error if the path cannot be parsed.
33  pub fn parse(context: &Arc<Context>, store: &Store, path: &str) -> Result<Self> {
34    let path_cstring = CString::new(path)?;
35
36    // SAFETY: context, store, and path_cstring are valid
37    let path_ptr = unsafe {
38      sys::nix_store_parse_path(
39        context.as_ptr(),
40        store.as_ptr(),
41        path_cstring.as_ptr(),
42      )
43    };
44
45    let inner = NonNull::new(path_ptr).ok_or(Error::NullPointer)?;
46
47    Ok(StorePath {
48      inner,
49      _context: Arc::clone(context),
50    })
51  }
52
53  /// Get the name component of the store path.
54  ///
55  /// This returns the name part of the store path (everything after the hash).
56  /// For example, for "/nix/store/abc123...-hello-1.0", this returns "hello-1.0".
57  ///
58  /// # Errors
59  ///
60  /// Returns an error if the name cannot be retrieved.
61  pub fn name(&self) -> Result<String> {
62    // Callback to receive the string
63    unsafe extern "C" fn name_callback(
64      start: *const std::os::raw::c_char,
65      n: std::os::raw::c_uint,
66      user_data: *mut std::os::raw::c_void,
67    ) {
68      let result = unsafe { &mut *(user_data as *mut Option<String>) };
69
70      if !start.is_null() && n > 0 {
71        let bytes = unsafe {
72          std::slice::from_raw_parts(start.cast::<u8>(), n as usize)
73        };
74        if let Ok(s) = std::str::from_utf8(bytes) {
75          *result = Some(s.to_string());
76        }
77      }
78    }
79
80    let mut result: Option<String> = None;
81    let user_data = &mut result as *mut _ as *mut std::os::raw::c_void;
82
83    // SAFETY: self.inner is valid, callback matches expected signature
84    unsafe {
85      sys::nix_store_path_name(self.inner.as_ptr(), Some(name_callback), user_data);
86    }
87
88    result.ok_or(Error::NullPointer)
89  }
90
91  /// Get the raw store path pointer.
92  ///
93  /// # Safety
94  ///
95  /// The caller must ensure the pointer is used safely.
96  pub(crate) unsafe fn as_ptr(&self) -> *mut sys::StorePath {
97    self.inner.as_ptr()
98  }
99}
100
101impl Clone for StorePath {
102  fn clone(&self) -> Self {
103    // SAFETY: self.inner is valid, nix_store_path_clone creates a new copy
104    let cloned_ptr = unsafe { sys::nix_store_path_clone(self.inner.as_ptr()) };
105
106    // This should never fail as cloning a valid path should always succeed
107    let inner = NonNull::new(cloned_ptr)
108      .expect("nix_store_path_clone returned null for valid path");
109
110    StorePath {
111      inner,
112      _context: Arc::clone(&self._context),
113    }
114  }
115}
116
117impl Drop for StorePath {
118  fn drop(&mut self) {
119    // SAFETY: We own the store path and it's valid until drop
120    unsafe {
121      sys::nix_store_path_free(self.inner.as_ptr());
122    }
123  }
124}
125
126// SAFETY: StorePath can be shared between threads
127unsafe impl Send for StorePath {}
128unsafe impl Sync for StorePath {}
129
130impl Store {
131  /// Open a Nix store.
132  ///
133  /// # Arguments
134  ///
135  /// * `context` - The Nix context
136  /// * `uri` - Optional store URI (None for default store)
137  ///
138  /// # Errors
139  ///
140  /// Returns an error if the store cannot be opened.
141  pub fn open(context: &Arc<Context>, uri: Option<&str>) -> Result<Self> {
142    let uri_cstring;
143    let uri_ptr = if let Some(uri) = uri {
144      uri_cstring = CString::new(uri)?;
145      uri_cstring.as_ptr()
146    } else {
147      std::ptr::null()
148    };
149
150    // SAFETY: context is valid, uri_ptr is either null or valid CString
151    let store_ptr = unsafe {
152      sys::nix_store_open(context.as_ptr(), uri_ptr, std::ptr::null_mut())
153    };
154
155    let inner = NonNull::new(store_ptr).ok_or(Error::NullPointer)?;
156
157    Ok(Store {
158      inner,
159      _context: Arc::clone(context),
160    })
161  }
162
163  /// Get the raw store pointer.
164  ///
165  /// # Safety
166  ///
167  /// The caller must ensure the pointer is used safely.
168  pub(crate) unsafe fn as_ptr(&self) -> *mut sys::Store {
169    self.inner.as_ptr()
170  }
171
172  /// Realize a store path.
173  ///
174  /// This builds/downloads the store path and all its dependencies,
175  /// making them available in the local store.
176  ///
177  /// # Arguments
178  ///
179  /// * `path` - The store path to realize
180  ///
181  /// # Returns
182  ///
183  /// A vector of (output_name, store_path) tuples for each realized output.
184  /// For example, a derivation might produce outputs like ("out", path1), ("dev", path2).
185  ///
186  /// # Errors
187  ///
188  /// Returns an error if the path cannot be realized.
189  pub fn realize(&self, path: &StorePath) -> Result<Vec<(String, StorePath)>> {
190    // Type alias for our userdata: (outputs vector, context)
191    type Userdata = (Vec<(String, StorePath)>, Arc<Context>);
192
193    // Callback function that will be called for each realized output
194    unsafe extern "C" fn realize_callback(
195      userdata: *mut std::os::raw::c_void,
196      outname: *const std::os::raw::c_char,
197      out: *const sys::StorePath,
198    ) {
199      // SAFETY: userdata is a valid pointer to our (Vec, Arc<Context>) tuple
200      let data = unsafe { &mut *(userdata as *mut Userdata) };
201      let (outputs, context) = data;
202
203      // SAFETY: outname is a valid C string from Nix
204      let name = if !outname.is_null() {
205        unsafe {
206          CStr::from_ptr(outname)
207            .to_string_lossy()
208            .into_owned()
209        }
210      } else {
211        String::from("out") // Default output name
212      };
213
214      // SAFETY: out is a valid StorePath pointer from Nix, we need to clone it
215      // because Nix owns the original and may free it after the callback
216      if !out.is_null() {
217        let cloned_path =
218          unsafe { sys::nix_store_path_clone(out as *mut sys::StorePath) };
219        if let Some(inner) = NonNull::new(cloned_path) {
220          let store_path = StorePath {
221            inner,
222            _context: Arc::clone(context),
223          };
224          outputs.push((name, store_path));
225        }
226      }
227    }
228
229    // Create userdata with empty outputs vector and context
230    let mut userdata: Userdata = (Vec::new(), Arc::clone(&self._context));
231    let userdata_ptr = &mut userdata as *mut Userdata as *mut std::os::raw::c_void;
232
233    // SAFETY: All pointers are valid, callback is compatible with the FFI signature
234    // - self._context is valid for the duration of this call
235    // - self.inner is valid (checked in Store::open)
236    // - path.inner is valid (checked in StorePath::parse)
237    // - userdata_ptr points to valid stack memory
238    // - realize_callback matches the expected C function signature
239    let err = unsafe {
240      sys::nix_store_realise(
241        self._context.as_ptr(),
242        self.inner.as_ptr(),
243        path.as_ptr(),
244        userdata_ptr,
245        Some(realize_callback),
246      )
247    };
248
249    super::check_err(err)?;
250
251    // Return the collected outputs
252    Ok(userdata.0)
253  }
254
255  /// Parse a store path string into a StorePath.
256  ///
257  /// This is a convenience method that wraps `StorePath::parse()`.
258  ///
259  /// # Arguments
260  ///
261  /// * `path` - The store path string (e.g., "/nix/store/...")
262  ///
263  /// # Errors
264  ///
265  /// Returns an error if the path cannot be parsed.
266  ///
267  /// # Example
268  ///
269  /// ```no_run
270  /// # use std::sync::Arc;
271  /// # use nix_bindings::{Context, Store};
272  /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
273  /// let ctx = Arc::new(Context::new()?);
274  /// let store = Store::open(&ctx, None)?;
275  /// let path = store.store_path("/nix/store/...")?;
276  /// # Ok(())
277  /// # }
278  /// ```
279  pub fn store_path(&self, path: &str) -> Result<StorePath> {
280    StorePath::parse(&self._context, self, path)
281  }
282
283}
284
285impl Drop for Store {
286  fn drop(&mut self) {
287    // SAFETY: We own the store and it's valid until drop
288    unsafe {
289      sys::nix_store_free(self.inner.as_ptr());
290    }
291  }
292}
293
294// SAFETY: Store can be shared between threads
295unsafe impl Send for Store {}
296unsafe impl Sync for Store {}
297
298#[cfg(test)]
299mod tests {
300  use serial_test::serial;
301
302  use super::*;
303
304  #[test]
305  #[serial]
306  fn test_store_opening() {
307    let ctx = Arc::new(Context::new().expect("Failed to create context"));
308    let _store = Store::open(&ctx, None).expect("Failed to open store");
309  }
310
311  #[test]
312  #[serial]
313  fn test_store_path_parse() {
314    let ctx = Arc::new(Context::new().expect("Failed to create context"));
315    let store = Store::open(&ctx, None).expect("Failed to open store");
316
317    // Try parsing a well-formed store path
318    // Note: This may fail if the path doesn't exist in the store
319    let result =
320      StorePath::parse(&ctx, &store, "/nix/store/00000000000000000000000000000000-test");
321
322    // We don't assert success here because the path might not exist
323    // This test mainly checks that the API works correctly
324    match result {
325      Ok(_path) => {
326        // Successfully parsed the path
327      },
328      Err(_) => {
329        // Path doesn't exist or is invalid, which is expected
330      },
331    }
332  }
333
334  #[test]
335  #[serial]
336  fn test_store_path_clone() {
337    let ctx = Arc::new(Context::new().expect("Failed to create context"));
338    let store = Store::open(&ctx, None).expect("Failed to open store");
339
340    // Try to get a valid store path by parsing
341    // Note: This test is somewhat limited without a guaranteed valid path
342    if let Ok(path) =
343      StorePath::parse(&ctx, &store, "/nix/store/00000000000000000000000000000000-test")
344    {
345      let cloned = path.clone();
346
347      // Assert that the cloned path has the same name as the original
348      let original_name = path.name().expect("Failed to get original path name");
349      let cloned_name = cloned.name().expect("Failed to get cloned path name");
350
351      assert_eq!(original_name, cloned_name, "Cloned path should have the same name as original");
352    }
353  }
354
355  // Note: test_realize is not included because it requires a valid store path
356  // to realize, which we can't guarantee in a unit test. Integration tests
357  // would be more appropriate for testing realize() with actual derivations.
358}