Skip to main content

nix_bindings/
store.rs

1use std::{
2  ffi::{CStr, CString},
3  fmt,
4  panic::{self, AssertUnwindSafe},
5  ptr::NonNull,
6  sync::Arc,
7};
8
9use super::{Context, Error, Result, check_err, string_from_callback, sys};
10
11/// Options for [`Store::copy_path`].
12///
13/// Default: no repair, signature checks enforced.
14#[derive(Debug, Clone, Copy)]
15pub struct CopyPathOptions {
16  /// Repair the destination path if it is corrupted.
17  pub repair:     bool,
18  /// Verify the path's signatures before copying.
19  pub check_sigs: bool,
20}
21
22impl Default for CopyPathOptions {
23  fn default() -> Self {
24    CopyPathOptions {
25      repair:     false,
26      check_sigs: true,
27    }
28  }
29}
30
31/// Convert a null pointer + the context's last error into an [`Error`].
32///
33/// Used in C APIs that signal failure by returning null and parking the
34/// real message on the context. Without this the user only sees
35/// `Error::NullPointer` and loses the diagnostic.
36unsafe fn null_or_context_err(ctx: &Context, fallback: Error) -> Error {
37  unsafe {
38    let ptr = sys::nix_err_msg(
39      std::ptr::null_mut(),
40      ctx.as_ptr(),
41      std::ptr::null_mut(),
42    );
43    if ptr.is_null() {
44      return fallback;
45    }
46    let msg = CStr::from_ptr(ptr).to_string_lossy().into_owned();
47    if msg.is_empty() {
48      fallback
49    } else {
50      Error::Unknown(msg)
51    }
52  }
53}
54
55/// Nix store for managing packages and derivations.
56///
57/// The store provides access to Nix packages, derivations, and store paths.
58pub struct Store {
59  pub(crate) inner:    NonNull<sys::Store>,
60  pub(crate) _context: Arc<Context>,
61}
62
63/// A path in the Nix store.
64///
65/// Represents a store path that can be realized or queried.
66pub struct StorePath {
67  pub(crate) inner:    NonNull<sys::StorePath>,
68  pub(crate) _context: Arc<Context>,
69}
70
71/// A Nix derivation loaded from its JSON representation.
72///
73/// Derivations are the build recipes used by the Nix store. They describe
74/// how to produce a store path from inputs. Use [`Derivation::from_json`]
75/// to construct one and [`Derivation::add_to_store`] to register it.
76pub struct Derivation {
77  inner:    *mut sys::nix_derivation,
78  _context: Arc<Context>,
79}
80
81impl StorePath {
82  /// Parse a store path string into a `StorePath`.
83  ///
84  /// # Arguments
85  ///
86  /// * `context` - The Nix context
87  /// * `store` - The store containing the path
88  /// * `path` - The store path string (e.g., `"/nix/store/..."`)
89  ///
90  /// # Errors
91  ///
92  /// Returns an error if the path string is not a valid store path.
93  pub fn parse(
94    context: &Arc<Context>,
95    store: &Store,
96    path: &str,
97  ) -> Result<Self> {
98    let path_cstring = CString::new(path)?;
99
100    // SAFETY: context, store, and path_cstring are valid
101    let path_ptr = unsafe {
102      sys::nix_store_parse_path(
103        context.as_ptr(),
104        store.as_ptr(),
105        path_cstring.as_ptr(),
106      )
107    };
108
109    let inner = match NonNull::new(path_ptr) {
110      Some(p) => p,
111      None => {
112        return Err(unsafe {
113          null_or_context_err(context, Error::NullPointer)
114        });
115      },
116    };
117
118    Ok(StorePath {
119      inner,
120      _context: Arc::clone(context),
121    })
122  }
123
124  /// Get the name component of the store path.
125  ///
126  /// Returns the name part of the store path (everything after the hash).
127  /// For example, for `"/nix/store/abc123...-hello-1.0"` this returns
128  /// `"hello-1.0"`.
129  ///
130  /// # Errors
131  ///
132  /// Returns an error if the name cannot be retrieved.
133  pub fn name(&self) -> Result<String> {
134    // SAFETY: self.inner is valid, callback matches expected signature
135    let result = unsafe {
136      string_from_callback(|cb, ud| {
137        sys::nix_store_path_name(self.inner.as_ptr(), cb, ud);
138      })
139    };
140    result.ok_or(Error::NullPointer)
141  }
142
143  /// Get the hash component of the store path as raw bytes.
144  ///
145  /// The 20-byte hash is decoded from the "nix32" encoding
146  /// in the store path. For example, for
147  /// `"/nix/store/abc123...-hello-1.0"` this returns the raw
148  /// hash bytes corresponding to `"abc123..."`.
149  ///
150  /// # Returns
151  ///
152  /// The raw 20-byte hash.
153  ///
154  /// # Errors
155  ///
156  /// Returns an error if the hash cannot be retrieved.
157  pub fn hash_part(&self) -> Result<[u8; 20]> {
158    let mut hash = sys::nix_store_path_hash_part { bytes: [0u8; 20] };
159
160    // SAFETY: context and store path are valid
161    let err = unsafe {
162      sys::nix_store_path_hash(
163        self._context.as_ptr(),
164        self.inner.as_ptr(),
165        &mut hash,
166      )
167    };
168    check_err(unsafe { self._context.as_ptr() }, err)?;
169
170    Ok(hash.bytes)
171  }
172
173  /// Create a `StorePath` from its constituent hash and name parts.
174  ///
175  /// Unlike [`parse`](StorePath::parse), this does not require a `Store`
176  /// reference or the `/nix/store` prefix.
177  ///
178  /// # Arguments
179  ///
180  /// * `hash` - The 20-byte raw hash (as produced by
181  ///   [`hash_part`](Self::hash_part)).
182  /// * `name` - The name component (e.g., `"hello-1.0"`).
183  ///
184  /// # Returns
185  ///
186  /// A new `StorePath`.
187  ///
188  /// # Errors
189  ///
190  /// Returns an error if the path cannot be created.
191  pub fn from_parts(
192    context: &Arc<Context>,
193    hash: &[u8; 20],
194    name: &str,
195  ) -> Result<Self> {
196    let hash_struct = sys::nix_store_path_hash_part { bytes: *hash };
197    let name_c = CString::new(name)?;
198
199    // SAFETY: context, hash, and name are valid
200    let path_ptr = unsafe {
201      sys::nix_store_create_from_parts(
202        context.as_ptr(),
203        &hash_struct,
204        name_c.as_ptr(),
205        name.len(),
206      )
207    };
208
209    let inner = NonNull::new(path_ptr).ok_or(Error::NullPointer)?;
210
211    Ok(StorePath {
212      inner,
213      _context: Arc::clone(context),
214    })
215  }
216
217  /// Get the raw store path pointer.
218  pub(crate) unsafe fn as_ptr(&self) -> *mut sys::StorePath {
219    self.inner.as_ptr()
220  }
221}
222
223impl Clone for StorePath {
224  fn clone(&self) -> Self {
225    // SAFETY: self.inner is valid
226    let cloned_ptr = unsafe { sys::nix_store_path_clone(self.inner.as_ptr()) };
227
228    let inner = NonNull::new(cloned_ptr)
229      .expect("nix_store_path_clone returned null for valid path");
230
231    StorePath {
232      inner,
233      _context: Arc::clone(&self._context),
234    }
235  }
236}
237
238impl Drop for StorePath {
239  fn drop(&mut self) {
240    // SAFETY: We own the store path and it is valid until drop
241    unsafe {
242      sys::nix_store_path_free(self.inner.as_ptr());
243    }
244  }
245}
246
247// SAFETY: `StorePath` owns its `*mut sys::StorePath` outright; the C
248// object has no thread affinity. Accessor methods route through
249// `Arc<Context>` which the move carries along. The caller is
250// responsible for not keeping a cloned `Arc<Context>` live on the
251// source thread (see the `# Thread Safety` section in the crate root).
252//
253// `Sync` is NOT implemented: name/hash accessors touch the shared
254// context error buffer; concurrent `&StorePath` reads would race.
255unsafe impl Send for StorePath {}
256
257impl fmt::Debug for StorePath {
258  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
259    let name = self.name().unwrap_or_else(|_| "<unknown>".into());
260    write!(f, "StorePath({name})")
261  }
262}
263
264/// Renders the **name component only** (e.g. `hello-1.0`), not the canonical
265/// `/nix/store/<hash>-<name>` string.
266///
267/// The Nix C API does not expose a store-directory-aware print function;
268/// without a [`Store`] handle there is no way to recover the full path. Use
269/// [`Store::print_path`] (requires the `shim` feature) when you need the
270/// canonical form.
271impl fmt::Display for StorePath {
272  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
273    match self.name() {
274      Ok(n) => write!(f, "{n}"),
275      Err(_) => write!(f, "<store-path>"),
276    }
277  }
278}
279
280impl Derivation {
281  /// Parse a derivation from its JSON representation.
282  ///
283  /// # Arguments
284  ///
285  /// * `context` - The Nix context
286  /// * `store` - The store to use
287  /// * `json` - JSON string describing the derivation
288  ///
289  /// # Errors
290  ///
291  /// Returns an error if the JSON is not a valid derivation description.
292  pub fn from_json(
293    context: &Arc<Context>,
294    store: &Store,
295    json: &str,
296  ) -> Result<Self> {
297    let json_c = CString::new(json)?;
298
299    // SAFETY: context, store, and json_c are valid
300    let drv_ptr = unsafe {
301      sys::nix_derivation_from_json(
302        context.as_ptr(),
303        store.as_ptr(),
304        json_c.as_ptr(),
305      )
306    };
307
308    if drv_ptr.is_null() {
309      return Err(unsafe { null_or_context_err(context, Error::NullPointer) });
310    }
311
312    Ok(Derivation {
313      inner:    drv_ptr,
314      _context: Arc::clone(context),
315    })
316  }
317
318  /// Add this derivation to the store and return its output store path.
319  ///
320  /// # Errors
321  ///
322  /// Returns an error if the derivation cannot be registered in the store.
323  pub fn add_to_store(&self, store: &Store) -> Result<StorePath> {
324    // SAFETY: context, store, and inner are valid
325    let path_ptr = unsafe {
326      sys::nix_add_derivation(
327        self._context.as_ptr(),
328        store.as_ptr(),
329        self.inner,
330      )
331    };
332
333    let inner = NonNull::new(path_ptr).ok_or(Error::NullPointer)?;
334
335    Ok(StorePath {
336      inner,
337      _context: Arc::clone(&self._context),
338    })
339  }
340
341  /// Read a derivation from the store by its store path.
342  ///
343  /// # Returns
344  ///
345  /// The derivation object associated with the given `.drv` path.
346  ///
347  /// # Errors
348  ///
349  /// Returns an error if the derivation cannot be read.
350  pub fn from_store_path(
351    context: &Arc<Context>,
352    store: &Store,
353    path: &StorePath,
354  ) -> Result<Self> {
355    // SAFETY: context, store, and path are valid
356    let drv_ptr = unsafe {
357      sys::nix_store_drv_from_store_path(
358        context.as_ptr(),
359        store.as_ptr(),
360        path.inner.as_ptr(),
361      )
362    };
363
364    if drv_ptr.is_null() {
365      return Err(unsafe { null_or_context_err(context, Error::NullPointer) });
366    }
367
368    Ok(Derivation {
369      inner:    drv_ptr,
370      _context: Arc::clone(context),
371    })
372  }
373
374  /// Serialize this derivation to its JSON representation.
375  ///
376  /// # Returns
377  ///
378  /// The derivation as a JSON string.
379  ///
380  /// # Errors
381  ///
382  /// Returns an error if serialization fails.
383  pub fn to_json(&self) -> Result<String> {
384    // SAFETY: inner is valid, callback matches expected signature
385    let result = unsafe {
386      string_from_callback(|cb, ud| {
387        sys::nix_derivation_to_json(self._context.as_ptr(), self.inner, cb, ud);
388      })
389    };
390    result.ok_or(Error::NullPointer)
391  }
392}
393
394impl Clone for Derivation {
395  fn clone(&self) -> Self {
396    // SAFETY: self.inner is valid
397    let cloned_ptr = unsafe { sys::nix_derivation_clone(self.inner) };
398    assert!(
399      !cloned_ptr.is_null(),
400      "nix_derivation_clone returned null for a valid derivation"
401    );
402
403    Derivation {
404      inner:    cloned_ptr,
405      _context: Arc::clone(&self._context),
406    }
407  }
408}
409
410impl Drop for Derivation {
411  fn drop(&mut self) {
412    // SAFETY: We own the derivation and it is valid until drop
413    unsafe {
414      sys::nix_derivation_free(self.inner);
415    }
416  }
417}
418
419// SAFETY: `Derivation` owns its `*mut sys::nix_derivation` outright and
420// keeps the context alive through `Arc<Context>`. Same move-only
421// contract as `StorePath`: source thread must not retain an aliased
422// `Arc<Context>` and call into the C API concurrently.
423//
424// `Sync` is NOT implemented: `to_json`, `add_to_store`, and
425// `from_store_path` write through the context error buffer that
426// `&Derivation` shares with every other wrapper.
427unsafe impl Send for Derivation {}
428
429impl fmt::Debug for Derivation {
430  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
431    f.debug_struct("Derivation").finish_non_exhaustive()
432  }
433}
434
435impl Store {
436  /// Open a Nix store.
437  ///
438  /// # Arguments
439  ///
440  /// * `context` - The Nix context
441  /// * `uri` - Optional store URI (`None` for the default local store)
442  ///
443  /// # Errors
444  ///
445  /// Returns an error if the store cannot be opened.
446  pub fn open(context: &Arc<Context>, uri: Option<&str>) -> Result<Self> {
447    Self::open_with_params::<&str, &str>(context, uri, &[])
448  }
449
450  /// Open a Nix store with additional store parameters.
451  ///
452  /// Equivalent to passing `?key1=value1&key2=value2` in the URI. Useful for
453  /// configuring daemon connections, signing keys, or cache options.
454  ///
455  /// # Errors
456  ///
457  /// Returns an error if the store cannot be opened.
458  pub fn open_with_params<K, V>(
459    context: &Arc<Context>,
460    uri: Option<&str>,
461    params: &[(K, V)],
462  ) -> Result<Self>
463  where
464    K: AsRef<str>,
465    V: AsRef<str>,
466  {
467    let uri_cstring;
468    let uri_ptr = if let Some(uri) = uri {
469      uri_cstring = CString::new(uri)?;
470      uri_cstring.as_ptr()
471    } else {
472      std::ptr::null()
473    };
474
475    // Build the params structure. Each inner pair is a 3-slot array
476    // [key, value, NULL]; the outer array of pair-pointers is NULL-terminated.
477    // We must keep the backing CStrings alive across the call.
478    let key_vals: Vec<(CString, CString)> = params
479      .iter()
480      .map(|(k, v)| Ok((CString::new(k.as_ref())?, CString::new(v.as_ref())?)))
481      .collect::<Result<_>>()?;
482
483    let mut inner_arrays: Vec<[*const std::os::raw::c_char; 3]> = key_vals
484      .iter()
485      .map(|(k, v)| [k.as_ptr(), v.as_ptr(), std::ptr::null()])
486      .collect();
487
488    let mut outer: Vec<*mut *const std::os::raw::c_char> = inner_arrays
489      .iter_mut()
490      .map(|arr| arr.as_mut_ptr())
491      .collect();
492    outer.push(std::ptr::null_mut());
493
494    let params_ptr = if params.is_empty() {
495      std::ptr::null_mut()
496    } else {
497      outer.as_mut_ptr()
498    };
499
500    // SAFETY: context valid; uri_ptr is null or a valid CString; params_ptr
501    // is null (no params) or a properly null-terminated array of
502    // null-terminated key/value pairs kept alive by `key_vals` and
503    // `inner_arrays` until the call returns.
504    let store_ptr =
505      unsafe { sys::nix_store_open(context.as_ptr(), uri_ptr, params_ptr) };
506
507    drop(outer);
508    drop(inner_arrays);
509    drop(key_vals);
510
511    let inner = match NonNull::new(store_ptr) {
512      Some(p) => p,
513      None => {
514        return Err(unsafe {
515          null_or_context_err(context, Error::NullPointer)
516        });
517      },
518    };
519
520    Ok(Store {
521      inner,
522      _context: Arc::clone(context),
523    })
524  }
525
526  /// Get the raw store pointer.
527  pub(crate) unsafe fn as_ptr(&self) -> *mut sys::Store {
528    self.inner.as_ptr()
529  }
530
531  /// Realize a store path.
532  ///
533  /// Builds or downloads the store path and all its dependencies, making
534  /// them available in the local store.
535  ///
536  /// # Returns
537  ///
538  /// A vector of `(output_name, store_path)` pairs for each realized output.
539  ///
540  /// # Errors
541  ///
542  /// Returns an error if the path cannot be realized.
543  pub fn realize(&self, path: &StorePath) -> Result<Vec<(String, StorePath)>> {
544    type Userdata = (Vec<(String, StorePath)>, Arc<Context>);
545
546    unsafe extern "C" fn realize_callback(
547      userdata: *mut std::os::raw::c_void,
548      outname: *const std::os::raw::c_char,
549      out: *const sys::StorePath,
550    ) {
551      let _ = panic::catch_unwind(AssertUnwindSafe(|| {
552        let data = unsafe { &mut *(userdata as *mut Userdata) };
553        let (outputs, context) = data;
554
555        let name = if !outname.is_null() {
556          unsafe { CStr::from_ptr(outname).to_string_lossy().into_owned() }
557        } else {
558          String::from("out")
559        };
560
561        if !out.is_null() {
562          let cloned_path =
563            unsafe { sys::nix_store_path_clone(out as *mut sys::StorePath) };
564          if let Some(inner) = NonNull::new(cloned_path) {
565            outputs.push((name, StorePath {
566              inner,
567              _context: Arc::clone(context),
568            }));
569          }
570        }
571      }));
572    }
573
574    let mut userdata: Userdata = (Vec::new(), Arc::clone(&self._context));
575    let userdata_ptr =
576      &mut userdata as *mut Userdata as *mut std::os::raw::c_void;
577
578    let err = unsafe {
579      sys::nix_store_realise(
580        self._context.as_ptr(),
581        self.inner.as_ptr(),
582        path.as_ptr(),
583        userdata_ptr,
584        Some(realize_callback),
585      )
586    };
587
588    check_err(unsafe { self._context.as_ptr() }, err)?;
589
590    Ok(userdata.0)
591  }
592
593  /// Parse a store path string into a [`StorePath`].
594  ///
595  /// Convenience wrapper around [`StorePath::parse`].
596  ///
597  /// # Errors
598  ///
599  /// Returns an error if the path cannot be parsed.
600  ///
601  /// # Example
602  ///
603  /// ```no_run
604  /// # use std::sync::Arc;
605  /// # use nix_bindings::{Context, Store};
606  /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
607  /// let ctx = Arc::new(Context::new()?);
608  /// let store = Store::open(&ctx, None)?;
609  /// let path = store.store_path("/nix/store/...")?;
610  /// # Ok(())
611  /// # }
612  /// ```
613  pub fn store_path(&self, path: &str) -> Result<StorePath> {
614    StorePath::parse(&self._context, self, path)
615  }
616
617  /// Check whether a store path is present and valid in the store.
618  ///
619  /// Returns `true` if the path exists in the store's database.
620  #[must_use]
621  pub fn is_valid_path(&self, path: &StorePath) -> bool {
622    // SAFETY: context, store, and path are valid
623    unsafe {
624      sys::nix_store_is_valid_path(
625        self._context.as_ptr(),
626        self.inner.as_ptr(),
627        path.inner.as_ptr(),
628      )
629    }
630  }
631
632  /// Resolve the real filesystem path for a store path.
633  ///
634  /// For content-addressed stores (e.g., a binary cache) this may differ
635  /// from the store path itself.
636  ///
637  /// # Errors
638  ///
639  /// Returns an error if the path cannot be resolved.
640  pub fn real_path(&self, path: &StorePath) -> Result<String> {
641    // SAFETY: context, store, and path are valid; callback is safe
642    let result = unsafe {
643      string_from_callback(|cb, ud| {
644        sys::nix_store_real_path(
645          self._context.as_ptr(),
646          self.inner.as_ptr(),
647          path.inner.as_ptr(),
648          cb,
649          ud,
650        );
651      })
652    };
653    result.ok_or(Error::NullPointer)
654  }
655
656  /// Get the URI identifying this store (e.g., `"local"` or
657  /// `"https://cache.nixos.org"`).
658  ///
659  /// # Errors
660  ///
661  /// Returns an error if the URI cannot be retrieved.
662  pub fn uri(&self) -> Result<String> {
663    // SAFETY: context and store are valid; callback is safe
664    let result = unsafe {
665      string_from_callback(|cb, ud| {
666        sys::nix_store_get_uri(
667          self._context.as_ptr(),
668          self.inner.as_ptr(),
669          cb,
670          ud,
671        );
672      })
673    };
674    result.ok_or(Error::NullPointer)
675  }
676
677  /// Get the store directory (e.g., `"/nix/store"`).
678  ///
679  /// # Errors
680  ///
681  /// Returns an error if the directory cannot be retrieved.
682  pub fn store_dir(&self) -> Result<String> {
683    // SAFETY: context and store are valid; callback is safe
684    let result = unsafe {
685      string_from_callback(|cb, ud| {
686        sys::nix_store_get_storedir(
687          self._context.as_ptr(),
688          self.inner.as_ptr(),
689          cb,
690          ud,
691        );
692      })
693    };
694    result.ok_or(Error::NullPointer)
695  }
696
697  /// Get the version string of the store daemon.
698  ///
699  /// # Errors
700  ///
701  /// Returns an error if the version cannot be retrieved.
702  pub fn version(&self) -> Result<String> {
703    // SAFETY: context and store are valid; callback is safe
704    let result = unsafe {
705      string_from_callback(|cb, ud| {
706        sys::nix_store_get_version(
707          self._context.as_ptr(),
708          self.inner.as_ptr(),
709          cb,
710          ud,
711        );
712      })
713    };
714    result.ok_or(Error::NullPointer)
715  }
716
717  /// Copy the closure of `path` from `self` into `dst_store`.
718  ///
719  /// This copies the store path and all its transitive dependencies.
720  ///
721  /// # Errors
722  ///
723  /// Returns an error if the copy operation fails.
724  pub fn copy_closure(
725    &self,
726    dst_store: &Store,
727    path: &StorePath,
728  ) -> Result<()> {
729    // SAFETY: context, src store, dst store, and path are valid
730    let err = unsafe {
731      sys::nix_store_copy_closure(
732        self._context.as_ptr(),
733        self.inner.as_ptr(),
734        dst_store.as_ptr(),
735        path.inner.as_ptr(),
736      )
737    };
738    check_err(unsafe { self._context.as_ptr() }, err)
739  }
740
741  /// Copy a single path from this store into `dst_store`.
742  ///
743  /// Unlike [`copy_closure`](Self::copy_closure), this copies only the
744  /// path itself, not its dependencies.
745  ///
746  /// # Errors
747  ///
748  /// Returns an error if the copy operation fails.
749  pub fn copy_path(
750    &self,
751    dst_store: &Store,
752    path: &StorePath,
753    options: CopyPathOptions,
754  ) -> Result<()> {
755    // SAFETY: all pointers are valid
756    let err = unsafe {
757      sys::nix_store_copy_path(
758        self._context.as_ptr(),
759        self.inner.as_ptr(),
760        dst_store.as_ptr(),
761        path.inner.as_ptr(),
762        options.repair,
763        options.check_sigs,
764      )
765    };
766    check_err(unsafe { self._context.as_ptr() }, err)
767  }
768
769  /// Enumerate the filesystem closure of a store path.
770  ///
771  /// Calls `callback` once for each store path in the closure (in no
772  /// particular order).
773  ///
774  /// # Arguments
775  ///
776  /// * `flip_direction` - If false, return paths referenced by paths in the
777  ///   closure (forward). If true, return paths that reference paths in the
778  ///   closure (backward).
779  /// * `include_outputs` - For derivations, also include their outputs.
780  /// * `include_derivers` - For outputs, also include the derivation that
781  ///   produced them.
782  ///
783  /// # Errors
784  ///
785  /// Returns an error if the operation fails.
786  pub fn get_fs_closure<F>(
787    &self,
788    path: &StorePath,
789    flip_direction: bool,
790    include_outputs: bool,
791    include_derivers: bool,
792    mut callback: F,
793  ) -> Result<()>
794  where
795    F: FnMut(&StorePath),
796  {
797    type Userdata<'a> = (&'a mut dyn FnMut(&StorePath), Arc<Context>);
798
799    unsafe extern "C" fn closure_callback(
800      _context: *mut sys::nix_c_context,
801      userdata: *mut std::os::raw::c_void,
802      sp: *const sys::StorePath,
803    ) {
804      let _ = panic::catch_unwind(AssertUnwindSafe(|| {
805        let data = unsafe { &mut *(userdata as *mut Userdata<'_>) };
806        let (cb, ctx) = data;
807
808        if !sp.is_null() {
809          let cloned = unsafe { sys::nix_store_path_clone(sp as *mut _) };
810          if let Some(inner) = NonNull::new(cloned) {
811            let p = StorePath {
812              inner,
813              _context: Arc::clone(ctx),
814            };
815            cb(&p);
816          }
817        }
818      }));
819    }
820
821    let mut userdata: Userdata<'_> =
822      (&mut callback, Arc::clone(&self._context));
823    let userdata_ptr =
824      &mut userdata as *mut Userdata<'_> as *mut std::os::raw::c_void;
825
826    // SAFETY: all pointers are valid
827    let err = unsafe {
828      sys::nix_store_get_fs_closure(
829        self._context.as_ptr(),
830        self.inner.as_ptr(),
831        path.inner.as_ptr(),
832        flip_direction,
833        include_outputs,
834        include_derivers,
835        userdata_ptr,
836        Some(closure_callback),
837      )
838    };
839    check_err(unsafe { self._context.as_ptr() }, err)
840  }
841
842  /// Collect the filesystem closure into a `Vec<StorePath>`.
843  ///
844  /// Convenience wrapper around [`get_fs_closure`](Self::get_fs_closure) that
845  /// gathers every visited path into a vector. Use the callback form directly
846  /// if you want to stream paths without allocating the full closure.
847  ///
848  /// # Errors
849  ///
850  /// Returns an error if the operation fails.
851  pub fn collect_fs_closure(
852    &self,
853    path: &StorePath,
854    flip_direction: bool,
855    include_outputs: bool,
856    include_derivers: bool,
857  ) -> Result<Vec<StorePath>> {
858    let mut out = Vec::new();
859    self.get_fs_closure(
860      path,
861      flip_direction,
862      include_outputs,
863      include_derivers,
864      |p| out.push(p.clone()),
865    )?;
866    Ok(out)
867  }
868
869  /// Render a [`StorePath`] back to its canonical `/nix/store/...` string.
870  ///
871  /// Requires the `shim` feature: the C API does not expose a path-to-string
872  /// function directly; this calls a C++ shim that delegates to
873  /// `store->printStorePath(path)`.
874  ///
875  /// # Errors
876  ///
877  /// Returns an error if the rendering fails.
878  #[cfg(feature = "shim")]
879  pub fn print_path(&self, path: &StorePath) -> Result<String> {
880    // SAFETY: context, store, and path are valid
881    let mut err_code = sys::nix_err_NIX_OK;
882    let result = unsafe {
883      crate::string_from_callback(|cb, ud| {
884        err_code = sys::nix_store_path_to_string(
885          self._context.as_ptr(),
886          self.inner.as_ptr(),
887          path.inner.as_ptr(),
888          cb,
889          ud,
890        );
891      })
892    };
893    check_err(unsafe { self._context.as_ptr() }, err_code)?;
894    result.ok_or(Error::NullPointer)
895  }
896
897  /// Read a derivation from this store by its store path.
898  ///
899  /// Convenience wrapper around [`Derivation::from_store_path`].
900  ///
901  /// # Errors
902  ///
903  /// Returns an error if the derivation cannot be read.
904  pub fn read_derivation(&self, path: &StorePath) -> Result<Derivation> {
905    Derivation::from_store_path(&self._context, self, path)
906  }
907
908  /// Look up the full store path from a hash part.
909  ///
910  /// # Returns
911  ///
912  /// `Some(StorePath)` if a matching path exists, `None` otherwise.
913  pub fn query_path_from_hash_part(
914    &self,
915    hash: &str,
916  ) -> Result<Option<StorePath>> {
917    let hash_c = CString::new(hash)?;
918
919    // SAFETY: context, store, and hash_c are valid
920    let path_ptr = unsafe {
921      sys::nix_store_query_path_from_hash_part(
922        self._context.as_ptr(),
923        self.inner.as_ptr(),
924        hash_c.as_ptr(),
925      )
926    };
927
928    if path_ptr.is_null() {
929      return Ok(None);
930    }
931
932    let inner = NonNull::new(path_ptr).ok_or(Error::NullPointer)?;
933
934    Ok(Some(StorePath {
935      inner,
936      _context: Arc::clone(&self._context),
937    }))
938  }
939
940  /// Add arbitrary bytes to the Nix store as a flat, content-addressed file.
941  ///
942  /// Equivalent to `builtins.toFile`, but accepts any byte sequence
943  /// (including embedded NULs and non-UTF-8 data). The path's hash is
944  /// derived from the content, so identical bytes always produce the
945  /// same store path. The resulting path has no references.
946  ///
947  /// # Arguments
948  ///
949  /// * `name` - The filename that will appear in the store path
950  /// * `data` - The bytes to write
951  ///
952  /// # Errors
953  ///
954  /// Returns an error if the store operation fails.
955  #[cfg(feature = "shim")]
956  pub fn add_bytes_to_store(
957    &self,
958    name: &str,
959    data: &[u8],
960  ) -> Result<StorePath> {
961    let name_c = CString::new(name)?;
962
963    let mut out_path: *mut sys::StorePath = std::ptr::null_mut();
964
965    // SAFETY: context, store, and name are valid; `data` points to `data.len()`
966    // readable bytes (or is dangling for an empty slice, which the shim
967    // tolerates when len == 0). `out_path` is a writable out-pointer.
968    let err = unsafe {
969      sys::nix_store_add_bytes_to_store(
970        self._context.as_ptr(),
971        self.inner.as_ptr(),
972        name_c.as_ptr(),
973        data.as_ptr(),
974        data.len(),
975        &mut out_path,
976      )
977    };
978
979    unsafe {
980      check_err(self._context.as_ptr(), err)?;
981    }
982
983    let inner = NonNull::new(out_path).ok_or(Error::NullPointer)?;
984
985    Ok(StorePath {
986      inner,
987      _context: Arc::clone(&self._context),
988    })
989  }
990
991  /// Add text content to the Nix store.
992  ///
993  /// Thin convenience wrapper over
994  /// [`add_bytes_to_store`](Self::add_bytes_to_store) that writes the UTF-8
995  /// bytes of `text`.
996  ///
997  /// # Errors
998  ///
999  /// Returns an error if the store operation fails.
1000  #[cfg(feature = "shim")]
1001  pub fn add_text_to_store(&self, name: &str, text: &str) -> Result<StorePath> {
1002    self.add_bytes_to_store(name, text.as_bytes())
1003  }
1004}
1005
1006impl Drop for Store {
1007  fn drop(&mut self) {
1008    // SAFETY: We own the store and it is valid until drop
1009    unsafe {
1010      sys::nix_store_free(self.inner.as_ptr());
1011    }
1012  }
1013}
1014
1015// SAFETY: `Store` owns its `*mut sys::Store` and keeps the context
1016// alive through `Arc<Context>`. The Nix store handle is documented as
1017// safe to move between threads provided no other thread is currently
1018// driving it. Same `Arc<Context>` aliasing caveat as `StorePath` and
1019// `Derivation` above.
1020//
1021// `Sync` is NOT implemented: every accessor (`uri`, `realize`,
1022// `copy_path`, ...) writes through the shared context error buffer.
1023unsafe impl Send for Store {}
1024
1025impl fmt::Debug for Store {
1026  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1027    let uri = self.uri().unwrap_or_else(|_| "<unknown>".into());
1028    f.debug_struct("Store").field("uri", &uri).finish()
1029  }
1030}
1031
1032#[cfg(test)]
1033mod tests {
1034  use std::sync::Arc;
1035
1036  use serial_test::serial;
1037
1038  use super::*;
1039
1040  #[test]
1041  #[serial]
1042  fn test_store_opening() {
1043    let ctx = Arc::new(Context::new().expect("Failed to create context"));
1044    let _store = Store::open(&ctx, None).expect("Failed to open store");
1045  }
1046
1047  #[test]
1048  #[serial]
1049  fn test_store_path_parse() {
1050    let ctx = Arc::new(Context::new().expect("Failed to create context"));
1051    let store = Store::open(&ctx, None).expect("Failed to open store");
1052
1053    // Well-formed path; may or may not exist in the local store
1054    let result = StorePath::parse(
1055      &ctx,
1056      &store,
1057      "/nix/store/00000000000000000000000000000000-test",
1058    );
1059
1060    match result {
1061      Ok(_) | Err(_) => {
1062        // Either outcome is acceptable; we just verify the API does not
1063        // panic or invoke UB
1064      },
1065    }
1066  }
1067
1068  #[test]
1069  #[serial]
1070  fn test_store_path_clone() {
1071    let ctx = Arc::new(Context::new().expect("Failed to create context"));
1072    let store = Store::open(&ctx, None).expect("Failed to open store");
1073
1074    if let Ok(path) = StorePath::parse(
1075      &ctx,
1076      &store,
1077      "/nix/store/00000000000000000000000000000000-test",
1078    ) {
1079      let cloned = path.clone();
1080      let original_name = path.name().expect("Failed to get original name");
1081      let cloned_name = cloned.name().expect("Failed to get cloned name");
1082      assert_eq!(
1083        original_name, cloned_name,
1084        "Cloned path should have the same name"
1085      );
1086    }
1087  }
1088
1089  #[test]
1090  #[serial]
1091  fn test_store_uri() {
1092    let ctx = Arc::new(Context::new().expect("Failed to create context"));
1093    let store = Store::open(&ctx, None).expect("Failed to open store");
1094    let uri = store.uri().expect("Failed to get store URI");
1095    assert!(!uri.is_empty(), "Store URI should not be empty");
1096  }
1097
1098  #[test]
1099  #[serial]
1100  fn test_store_dir() {
1101    let ctx = Arc::new(Context::new().expect("Failed to create context"));
1102    let store = Store::open(&ctx, None).expect("Failed to open store");
1103    let dir = store.store_dir().expect("Failed to get store directory");
1104    assert!(!dir.is_empty(), "Store directory should not be empty");
1105  }
1106
1107  #[test]
1108  #[serial]
1109  fn test_store_version() {
1110    let ctx = Arc::new(Context::new().expect("Failed to create context"));
1111    let store = Store::open(&ctx, None).expect("Failed to open store");
1112    let ver = store.version().expect("Failed to get store version");
1113    assert!(!ver.is_empty(), "Store version should not be empty");
1114  }
1115
1116  #[cfg(feature = "shim")]
1117  #[test]
1118  #[serial]
1119  fn test_add_bytes_to_store_roundtrip() {
1120    let ctx = Arc::new(Context::new().expect("Failed to create context"));
1121    let store = Store::open(&ctx, None).expect("Failed to open store");
1122
1123    // Embedded NUL + non-UTF-8 byte; CString::new would have rejected this.
1124    let data: &[u8] = b"hello\0world\xff";
1125    let path = store
1126      .add_bytes_to_store("nix-bindings-bytes-test.bin", data)
1127      .expect("add_bytes_to_store failed");
1128
1129    assert_eq!(path.name().expect("name"), "nix-bindings-bytes-test.bin");
1130    assert!(store.is_valid_path(&path));
1131
1132    // Determinism: identical bytes → identical path.
1133    let path2 = store
1134      .add_bytes_to_store("nix-bindings-bytes-test.bin", data)
1135      .expect("second add failed");
1136    assert_eq!(
1137      path.hash_part().expect("hash"),
1138      path2.hash_part().expect("hash"),
1139    );
1140  }
1141
1142  #[cfg(feature = "shim")]
1143  #[test]
1144  #[serial]
1145  fn test_add_text_to_store_delegates() {
1146    let ctx = Arc::new(Context::new().expect("Failed to create context"));
1147    let store = Store::open(&ctx, None).expect("Failed to open store");
1148
1149    let p = store
1150      .add_text_to_store("nix-bindings-text-test.txt", "hello, world\n")
1151      .expect("add_text_to_store failed");
1152    assert!(store.is_valid_path(&p));
1153  }
1154
1155  #[test]
1156  #[serial]
1157  fn test_store_is_valid_path() {
1158    let ctx = Arc::new(Context::new().expect("Failed to create context"));
1159    let store = Store::open(&ctx, None).expect("Failed to open store");
1160
1161    if let Ok(path) = StorePath::parse(
1162      &ctx,
1163      &store,
1164      "/nix/store/00000000000000000000000000000000-test",
1165    ) {
1166      // A random hash is almost certainly not valid
1167      let valid = store.is_valid_path(&path);
1168      assert!(!valid, "Random path should not be valid in the store");
1169    }
1170  }
1171}