use std::{
ffi::{CStr, CString},
ptr::NonNull,
sync::Arc,
};
use super::{Context, Error, Result, check_err, string_from_callback, sys};
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 = NonNull::new(path_ptr).ok_or(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 {}
unsafe impl Sync for StorePath {}
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(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(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) };
let inner = cloned_ptr;
Derivation {
inner,
_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 {}
unsafe impl Sync for Derivation {}
impl Store {
pub fn open(context: &Arc<Context>, uri: Option<&str>) -> Result<Self> {
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 store_ptr = unsafe {
sys::nix_store_open(context.as_ptr(), uri_ptr, std::ptr::null_mut())
};
let inner = NonNull::new(store_ptr).ok_or(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 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,
repair: bool,
check_sigs: bool,
) -> 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(),
repair,
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 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 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 {}
unsafe impl Sync for Store {}
#[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");
}
}
}