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#[derive(Debug, Clone, Copy)]
15pub struct CopyPathOptions {
16 pub repair: bool,
18 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
31unsafe 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
55pub struct Store {
59 pub(crate) inner: NonNull<sys::Store>,
60 pub(crate) _context: Arc<Context>,
61}
62
63pub struct StorePath {
67 pub(crate) inner: NonNull<sys::StorePath>,
68 pub(crate) _context: Arc<Context>,
69}
70
71pub struct Derivation {
77 inner: *mut sys::nix_derivation,
78 _context: Arc<Context>,
79}
80
81impl StorePath {
82 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 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 pub fn name(&self) -> Result<String> {
134 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 pub fn hash_part(&self) -> Result<[u8; 20]> {
158 let mut hash = sys::nix_store_path_hash_part { bytes: [0u8; 20] };
159
160 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 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 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 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 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 unsafe {
242 sys::nix_store_path_free(self.inner.as_ptr());
243 }
244 }
245}
246
247unsafe 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
264impl 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 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 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 pub fn add_to_store(&self, store: &Store) -> Result<StorePath> {
324 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 pub fn from_store_path(
351 context: &Arc<Context>,
352 store: &Store,
353 path: &StorePath,
354 ) -> Result<Self> {
355 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 pub fn to_json(&self) -> Result<String> {
384 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 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 unsafe {
414 sys::nix_derivation_free(self.inner);
415 }
416 }
417}
418
419unsafe 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 pub fn open(context: &Arc<Context>, uri: Option<&str>) -> Result<Self> {
447 Self::open_with_params::<&str, &str>(context, uri, &[])
448 }
449
450 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 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 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 pub(crate) unsafe fn as_ptr(&self) -> *mut sys::Store {
528 self.inner.as_ptr()
529 }
530
531 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 pub fn store_path(&self, path: &str) -> Result<StorePath> {
614 StorePath::parse(&self._context, self, path)
615 }
616
617 #[must_use]
621 pub fn is_valid_path(&self, path: &StorePath) -> bool {
622 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 pub fn real_path(&self, path: &StorePath) -> Result<String> {
641 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 pub fn uri(&self) -> Result<String> {
663 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 pub fn store_dir(&self) -> Result<String> {
683 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 pub fn version(&self) -> Result<String> {
703 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 pub fn copy_closure(
725 &self,
726 dst_store: &Store,
727 path: &StorePath,
728 ) -> Result<()> {
729 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 pub fn copy_path(
750 &self,
751 dst_store: &Store,
752 path: &StorePath,
753 options: CopyPathOptions,
754 ) -> Result<()> {
755 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 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 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 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 #[cfg(feature = "shim")]
879 pub fn print_path(&self, path: &StorePath) -> Result<String> {
880 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 pub fn read_derivation(&self, path: &StorePath) -> Result<Derivation> {
905 Derivation::from_store_path(&self._context, self, path)
906 }
907
908 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 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 #[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 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 #[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 unsafe {
1010 sys::nix_store_free(self.inner.as_ptr());
1011 }
1012 }
1013}
1014
1015unsafe 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 let result = StorePath::parse(
1055 &ctx,
1056 &store,
1057 "/nix/store/00000000000000000000000000000000-test",
1058 );
1059
1060 match result {
1061 Ok(_) | Err(_) => {
1062 },
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 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 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 let valid = store.is_valid_path(&path);
1168 assert!(!valid, "Random path should not be valid in the store");
1169 }
1170 }
1171}