use std::{
ffi::{CStr, CString},
fmt,
panic::{self, AssertUnwindSafe},
ptr::NonNull,
sync::Arc,
};
use super::{Context, Error, Result, check_err, string_from_callback, sys};
#[derive(Debug, Clone, Copy)]
pub struct CopyPathOptions {
pub repair: bool,
pub check_sigs: bool,
}
impl Default for CopyPathOptions {
fn default() -> Self {
CopyPathOptions {
repair: false,
check_sigs: true,
}
}
}
unsafe fn null_or_context_err(ctx: &Context, fallback: Error) -> Error {
unsafe {
let ptr = sys::nix_err_msg(
std::ptr::null_mut(),
ctx.as_ptr(),
std::ptr::null_mut(),
);
if ptr.is_null() {
return fallback;
}
let msg = CStr::from_ptr(ptr).to_string_lossy().into_owned();
if msg.is_empty() {
fallback
} else {
Error::Unknown(msg)
}
}
}
pub struct Store {
pub(crate) inner: NonNull<sys::Store>,
pub(crate) _context: Arc<Context>,
}
pub struct StorePath {
pub(crate) inner: NonNull<sys::StorePath>,
pub(crate) _context: Arc<Context>,
}
pub struct Derivation {
inner: *mut sys::nix_derivation,
_context: Arc<Context>,
}
impl StorePath {
pub fn parse(
context: &Arc<Context>,
store: &Store,
path: &str,
) -> Result<Self> {
let path_cstring = CString::new(path)?;
let path_ptr = unsafe {
sys::nix_store_parse_path(
context.as_ptr(),
store.as_ptr(),
path_cstring.as_ptr(),
)
};
let inner = match NonNull::new(path_ptr) {
Some(p) => p,
None => {
return Err(unsafe {
null_or_context_err(context, Error::NullPointer)
});
},
};
Ok(StorePath {
inner,
_context: Arc::clone(context),
})
}
pub fn name(&self) -> Result<String> {
let result = unsafe {
string_from_callback(|cb, ud| {
sys::nix_store_path_name(self.inner.as_ptr(), cb, ud);
})
};
result.ok_or(Error::NullPointer)
}
pub fn hash_part(&self) -> Result<[u8; 20]> {
let mut hash = sys::nix_store_path_hash_part { bytes: [0u8; 20] };
let err = unsafe {
sys::nix_store_path_hash(
self._context.as_ptr(),
self.inner.as_ptr(),
&mut hash,
)
};
check_err(unsafe { self._context.as_ptr() }, err)?;
Ok(hash.bytes)
}
pub fn from_parts(
context: &Arc<Context>,
hash: &[u8; 20],
name: &str,
) -> Result<Self> {
let hash_struct = sys::nix_store_path_hash_part { bytes: *hash };
let name_c = CString::new(name)?;
let path_ptr = unsafe {
sys::nix_store_create_from_parts(
context.as_ptr(),
&hash_struct,
name_c.as_ptr(),
name.len(),
)
};
let inner = NonNull::new(path_ptr).ok_or(Error::NullPointer)?;
Ok(StorePath {
inner,
_context: Arc::clone(context),
})
}
pub(crate) unsafe fn as_ptr(&self) -> *mut sys::StorePath {
self.inner.as_ptr()
}
}
impl Clone for StorePath {
fn clone(&self) -> Self {
let cloned_ptr = unsafe { sys::nix_store_path_clone(self.inner.as_ptr()) };
let inner = NonNull::new(cloned_ptr)
.expect("nix_store_path_clone returned null for valid path");
StorePath {
inner,
_context: Arc::clone(&self._context),
}
}
}
impl Drop for StorePath {
fn drop(&mut self) {
unsafe {
sys::nix_store_path_free(self.inner.as_ptr());
}
}
}
unsafe impl Send for StorePath {}
impl fmt::Debug for StorePath {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let name = self.name().unwrap_or_else(|_| "<unknown>".into());
write!(f, "StorePath({name})")
}
}
impl fmt::Display for StorePath {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.name() {
Ok(n) => write!(f, "{n}"),
Err(_) => write!(f, "<store-path>"),
}
}
}
impl Derivation {
pub fn from_json(
context: &Arc<Context>,
store: &Store,
json: &str,
) -> Result<Self> {
let json_c = CString::new(json)?;
let drv_ptr = unsafe {
sys::nix_derivation_from_json(
context.as_ptr(),
store.as_ptr(),
json_c.as_ptr(),
)
};
if drv_ptr.is_null() {
return Err(unsafe { null_or_context_err(context, Error::NullPointer) });
}
Ok(Derivation {
inner: drv_ptr,
_context: Arc::clone(context),
})
}
pub fn add_to_store(&mut self, store: &Store) -> Result<StorePath> {
let path_ptr = unsafe {
sys::nix_add_derivation(
self._context.as_ptr(),
store.as_ptr(),
self.inner,
)
};
let inner = NonNull::new(path_ptr).ok_or(Error::NullPointer)?;
Ok(StorePath {
inner,
_context: Arc::clone(&self._context),
})
}
pub fn from_store_path(
context: &Arc<Context>,
store: &Store,
path: &StorePath,
) -> Result<Self> {
let drv_ptr = unsafe {
sys::nix_store_drv_from_store_path(
context.as_ptr(),
store.as_ptr(),
path.inner.as_ptr(),
)
};
if drv_ptr.is_null() {
return Err(unsafe { null_or_context_err(context, Error::NullPointer) });
}
Ok(Derivation {
inner: drv_ptr,
_context: Arc::clone(context),
})
}
pub fn to_json(&self) -> Result<String> {
let result = unsafe {
string_from_callback(|cb, ud| {
sys::nix_derivation_to_json(self._context.as_ptr(), self.inner, cb, ud);
})
};
result.ok_or(Error::NullPointer)
}
}
impl Clone for Derivation {
fn clone(&self) -> Self {
let cloned_ptr = unsafe { sys::nix_derivation_clone(self.inner) };
assert!(
!cloned_ptr.is_null(),
"nix_derivation_clone returned null for a valid derivation"
);
Derivation {
inner: cloned_ptr,
_context: Arc::clone(&self._context),
}
}
}
impl Drop for Derivation {
fn drop(&mut self) {
unsafe {
sys::nix_derivation_free(self.inner);
}
}
}
unsafe impl Send for Derivation {}
impl fmt::Debug for Derivation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Derivation").finish_non_exhaustive()
}
}
impl Store {
pub fn open(context: &Arc<Context>, uri: Option<&str>) -> Result<Self> {
Self::open_with_params::<&str, &str>(context, uri, &[])
}
pub fn open_with_params<K, V>(
context: &Arc<Context>,
uri: Option<&str>,
params: &[(K, V)],
) -> Result<Self>
where
K: AsRef<str>,
V: AsRef<str>,
{
let uri_cstring;
let uri_ptr = if let Some(uri) = uri {
uri_cstring = CString::new(uri)?;
uri_cstring.as_ptr()
} else {
std::ptr::null()
};
let key_vals: Vec<(CString, CString)> = params
.iter()
.map(|(k, v)| Ok((CString::new(k.as_ref())?, CString::new(v.as_ref())?)))
.collect::<Result<_>>()?;
let mut inner_arrays: Vec<[*const std::os::raw::c_char; 3]> = key_vals
.iter()
.map(|(k, v)| [k.as_ptr(), v.as_ptr(), std::ptr::null()])
.collect();
let mut outer: Vec<*mut *const std::os::raw::c_char> = inner_arrays
.iter_mut()
.map(|arr| arr.as_mut_ptr())
.collect();
outer.push(std::ptr::null_mut());
let params_ptr = if params.is_empty() {
std::ptr::null_mut()
} else {
outer.as_mut_ptr()
};
let store_ptr =
unsafe { sys::nix_store_open(context.as_ptr(), uri_ptr, params_ptr) };
drop(outer);
drop(inner_arrays);
drop(key_vals);
let inner = match NonNull::new(store_ptr) {
Some(p) => p,
None => {
return Err(unsafe {
null_or_context_err(context, Error::NullPointer)
});
},
};
Ok(Store {
inner,
_context: Arc::clone(context),
})
}
pub(crate) unsafe fn as_ptr(&self) -> *mut sys::Store {
self.inner.as_ptr()
}
pub fn realize(&self, path: &StorePath) -> Result<Vec<(String, StorePath)>> {
type Userdata = (Vec<(String, StorePath)>, Arc<Context>);
unsafe extern "C" fn realize_callback(
userdata: *mut std::os::raw::c_void,
outname: *const std::os::raw::c_char,
out: *const sys::StorePath,
) {
let _ = panic::catch_unwind(AssertUnwindSafe(|| {
let data = unsafe { &mut *(userdata as *mut Userdata) };
let (outputs, context) = data;
let name = if !outname.is_null() {
unsafe { CStr::from_ptr(outname).to_string_lossy().into_owned() }
} else {
String::from("out")
};
if !out.is_null() {
let cloned_path =
unsafe { sys::nix_store_path_clone(out as *mut sys::StorePath) };
if let Some(inner) = NonNull::new(cloned_path) {
outputs.push((name, StorePath {
inner,
_context: Arc::clone(context),
}));
}
}
}));
}
let mut userdata: Userdata = (Vec::new(), Arc::clone(&self._context));
let userdata_ptr =
&mut userdata as *mut Userdata as *mut std::os::raw::c_void;
let err = unsafe {
sys::nix_store_realise(
self._context.as_ptr(),
self.inner.as_ptr(),
path.as_ptr(),
userdata_ptr,
Some(realize_callback),
)
};
check_err(unsafe { self._context.as_ptr() }, err)?;
Ok(userdata.0)
}
pub fn store_path(&self, path: &str) -> Result<StorePath> {
StorePath::parse(&self._context, self, path)
}
#[must_use]
pub fn is_valid_path(&self, path: &StorePath) -> bool {
unsafe {
sys::nix_store_is_valid_path(
self._context.as_ptr(),
self.inner.as_ptr(),
path.inner.as_ptr(),
)
}
}
pub fn real_path(&self, path: &StorePath) -> Result<String> {
let result = unsafe {
string_from_callback(|cb, ud| {
sys::nix_store_real_path(
self._context.as_ptr(),
self.inner.as_ptr(),
path.inner.as_ptr(),
cb,
ud,
);
})
};
result.ok_or(Error::NullPointer)
}
pub fn uri(&self) -> Result<String> {
let result = unsafe {
string_from_callback(|cb, ud| {
sys::nix_store_get_uri(
self._context.as_ptr(),
self.inner.as_ptr(),
cb,
ud,
);
})
};
result.ok_or(Error::NullPointer)
}
pub fn store_dir(&self) -> Result<String> {
let result = unsafe {
string_from_callback(|cb, ud| {
sys::nix_store_get_storedir(
self._context.as_ptr(),
self.inner.as_ptr(),
cb,
ud,
);
})
};
result.ok_or(Error::NullPointer)
}
pub fn version(&self) -> Result<String> {
let result = unsafe {
string_from_callback(|cb, ud| {
sys::nix_store_get_version(
self._context.as_ptr(),
self.inner.as_ptr(),
cb,
ud,
);
})
};
result.ok_or(Error::NullPointer)
}
pub fn copy_closure(
&self,
dst_store: &Store,
path: &StorePath,
) -> Result<()> {
let err = unsafe {
sys::nix_store_copy_closure(
self._context.as_ptr(),
self.inner.as_ptr(),
dst_store.as_ptr(),
path.inner.as_ptr(),
)
};
check_err(unsafe { self._context.as_ptr() }, err)
}
pub fn copy_path(
&self,
dst_store: &Store,
path: &StorePath,
options: CopyPathOptions,
) -> Result<()> {
let err = unsafe {
sys::nix_store_copy_path(
self._context.as_ptr(),
self.inner.as_ptr(),
dst_store.as_ptr(),
path.inner.as_ptr(),
options.repair,
options.check_sigs,
)
};
check_err(unsafe { self._context.as_ptr() }, err)
}
pub fn get_fs_closure<F>(
&self,
path: &StorePath,
flip_direction: bool,
include_outputs: bool,
include_derivers: bool,
mut callback: F,
) -> Result<()>
where
F: FnMut(&StorePath),
{
type Userdata<'a> = (&'a mut dyn FnMut(&StorePath), Arc<Context>);
unsafe extern "C" fn closure_callback(
_context: *mut sys::nix_c_context,
userdata: *mut std::os::raw::c_void,
sp: *const sys::StorePath,
) {
let _ = panic::catch_unwind(AssertUnwindSafe(|| {
let data = unsafe { &mut *(userdata as *mut Userdata<'_>) };
let (cb, ctx) = data;
if !sp.is_null() {
let cloned = unsafe { sys::nix_store_path_clone(sp as *mut _) };
if let Some(inner) = NonNull::new(cloned) {
let p = StorePath {
inner,
_context: Arc::clone(ctx),
};
cb(&p);
}
}
}));
}
let mut userdata: Userdata<'_> =
(&mut callback, Arc::clone(&self._context));
let userdata_ptr =
&mut userdata as *mut Userdata<'_> as *mut std::os::raw::c_void;
let err = unsafe {
sys::nix_store_get_fs_closure(
self._context.as_ptr(),
self.inner.as_ptr(),
path.inner.as_ptr(),
flip_direction,
include_outputs,
include_derivers,
userdata_ptr,
Some(closure_callback),
)
};
check_err(unsafe { self._context.as_ptr() }, err)
}
pub fn collect_fs_closure(
&self,
path: &StorePath,
flip_direction: bool,
include_outputs: bool,
include_derivers: bool,
) -> Result<Vec<StorePath>> {
let mut out = Vec::new();
self.get_fs_closure(
path,
flip_direction,
include_outputs,
include_derivers,
|p| out.push(p.clone()),
)?;
Ok(out)
}
#[cfg(feature = "shim")]
pub fn print_path(&self, path: &StorePath) -> Result<String> {
let mut err_code = sys::nix_err_NIX_OK;
let result = unsafe {
crate::string_from_callback(|cb, ud| {
err_code = sys::nix_store_path_to_string(
self._context.as_ptr(),
self.inner.as_ptr(),
path.inner.as_ptr(),
cb,
ud,
);
})
};
check_err(unsafe { self._context.as_ptr() }, err_code)?;
result.ok_or(Error::NullPointer)
}
pub fn read_derivation(&self, path: &StorePath) -> Result<Derivation> {
Derivation::from_store_path(&self._context, self, path)
}
pub fn query_path_from_hash_part(
&self,
hash: &str,
) -> Result<Option<StorePath>> {
let hash_c = CString::new(hash)?;
let path_ptr = unsafe {
sys::nix_store_query_path_from_hash_part(
self._context.as_ptr(),
self.inner.as_ptr(),
hash_c.as_ptr(),
)
};
if path_ptr.is_null() {
return Ok(None);
}
let inner = NonNull::new(path_ptr).ok_or(Error::NullPointer)?;
Ok(Some(StorePath {
inner,
_context: Arc::clone(&self._context),
}))
}
#[cfg(feature = "shim")]
pub fn add_bytes_to_store(
&self,
name: &str,
data: &[u8],
) -> Result<StorePath> {
let name_c = CString::new(name)?;
let mut out_path: *mut sys::StorePath = std::ptr::null_mut();
let err = unsafe {
sys::nix_store_add_bytes_to_store(
self._context.as_ptr(),
self.inner.as_ptr(),
name_c.as_ptr(),
data.as_ptr(),
data.len(),
&mut out_path,
)
};
unsafe {
check_err(self._context.as_ptr(), err)?;
}
let inner = NonNull::new(out_path).ok_or(Error::NullPointer)?;
Ok(StorePath {
inner,
_context: Arc::clone(&self._context),
})
}
#[cfg(feature = "shim")]
pub fn add_text_to_store(&self, name: &str, text: &str) -> Result<StorePath> {
self.add_bytes_to_store(name, text.as_bytes())
}
}
impl Drop for Store {
fn drop(&mut self) {
unsafe {
sys::nix_store_free(self.inner.as_ptr());
}
}
}
unsafe impl Send for Store {}
impl fmt::Debug for Store {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let uri = self.uri().unwrap_or_else(|_| "<unknown>".into());
f.debug_struct("Store").field("uri", &uri).finish()
}
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use serial_test::serial;
use super::*;
#[test]
#[serial]
fn test_store_opening() {
let ctx = Arc::new(Context::new().expect("Failed to create context"));
let _store = Store::open(&ctx, None).expect("Failed to open store");
}
#[test]
#[serial]
fn test_store_path_parse() {
let ctx = Arc::new(Context::new().expect("Failed to create context"));
let store = Store::open(&ctx, None).expect("Failed to open store");
let result = StorePath::parse(
&ctx,
&store,
"/nix/store/00000000000000000000000000000000-test",
);
match result {
Ok(_) | Err(_) => {
},
}
}
#[test]
#[serial]
fn test_store_path_clone() {
let ctx = Arc::new(Context::new().expect("Failed to create context"));
let store = Store::open(&ctx, None).expect("Failed to open store");
if let Ok(path) = StorePath::parse(
&ctx,
&store,
"/nix/store/00000000000000000000000000000000-test",
) {
let cloned = path.clone();
let original_name = path.name().expect("Failed to get original name");
let cloned_name = cloned.name().expect("Failed to get cloned name");
assert_eq!(
original_name, cloned_name,
"Cloned path should have the same name"
);
}
}
#[test]
#[serial]
fn test_store_uri() {
let ctx = Arc::new(Context::new().expect("Failed to create context"));
let store = Store::open(&ctx, None).expect("Failed to open store");
let uri = store.uri().expect("Failed to get store URI");
assert!(!uri.is_empty(), "Store URI should not be empty");
}
#[test]
#[serial]
fn test_store_dir() {
let ctx = Arc::new(Context::new().expect("Failed to create context"));
let store = Store::open(&ctx, None).expect("Failed to open store");
let dir = store.store_dir().expect("Failed to get store directory");
assert!(!dir.is_empty(), "Store directory should not be empty");
}
#[test]
#[serial]
fn test_store_version() {
let ctx = Arc::new(Context::new().expect("Failed to create context"));
let store = Store::open(&ctx, None).expect("Failed to open store");
let ver = store.version().expect("Failed to get store version");
assert!(!ver.is_empty(), "Store version should not be empty");
}
#[cfg(feature = "shim")]
#[test]
#[serial]
fn test_add_bytes_to_store_roundtrip() {
let ctx = Arc::new(Context::new().expect("Failed to create context"));
let store = Store::open(&ctx, None).expect("Failed to open store");
let data: &[u8] = b"hello\0world\xff";
let path = store
.add_bytes_to_store("nix-bindings-bytes-test.bin", data)
.expect("add_bytes_to_store failed");
assert_eq!(path.name().expect("name"), "nix-bindings-bytes-test.bin");
assert!(store.is_valid_path(&path));
let path2 = store
.add_bytes_to_store("nix-bindings-bytes-test.bin", data)
.expect("second add failed");
assert_eq!(
path.hash_part().expect("hash"),
path2.hash_part().expect("hash"),
);
}
#[cfg(feature = "shim")]
#[test]
#[serial]
fn test_add_text_to_store_delegates() {
let ctx = Arc::new(Context::new().expect("Failed to create context"));
let store = Store::open(&ctx, None).expect("Failed to open store");
let p = store
.add_text_to_store("nix-bindings-text-test.txt", "hello, world\n")
.expect("add_text_to_store failed");
assert!(store.is_valid_path(&p));
}
#[test]
#[serial]
fn test_store_is_valid_path() {
let ctx = Arc::new(Context::new().expect("Failed to create context"));
let store = Store::open(&ctx, None).expect("Failed to open store");
if let Ok(path) = StorePath::parse(
&ctx,
&store,
"/nix/store/00000000000000000000000000000000-test",
) {
let valid = store.is_valid_path(&path);
assert!(!valid, "Random path should not be valid in the store");
}
}
}