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}