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(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),
})
}
}
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)
}
}
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");
}
#[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");
}
}
}