diff --git a/nixide/src/errors/context.rs b/nixide/src/errors/context.rs index 1b4c4a5..87039f5 100644 --- a/nixide/src/errors/context.rs +++ b/nixide/src/errors/context.rs @@ -24,11 +24,12 @@ use std::ffi::c_uint; use std::ptr::NonNull; -use super::{NixError, NixideError, NixideResult}; +use super::{NixError, NixideResult}; +use crate::stdext::CCharPtrExt as _; use crate::sys; -use crate::util::bindings::wrap_libnix_string_callback; +use crate::util::panic_issue_call_failed; +use crate::util::wrap; use crate::util::wrappers::AsInnerPtr; -use crate::util::{panic_issue_call_failed, CCharPtrNixExt}; /// This object stores error state. /// @@ -72,9 +73,22 @@ impl AsInnerPtr for ErrorContext { } impl Into> for &ErrorContext { + /// # Panics + /// + /// This function will panic in the event that `context.get_err() == Some(err) && err == sys::nix_err_NIX_OK` + /// since `nixide::ErrorContext::get_err` is expected to return `None` to indicate `sys::nix_err_NIX_OK`. + /// + /// This function will panic in the event that `value != sys::nix_err_NIX_OK` + /// but that `context.get_code() == sys::nix_err_NIX_OK` fn into(self) -> NixideResult<()> { - let inner = self.get_err().ok_?; - let msg = self.get_msg()?; + let inner = match self.get_err() { + Some(err) => err, + None => return Ok(()), + }; + let msg = match self.get_msg() { + Some(msg) => msg, + None => return Ok(()), + }; let err = match inner { sys::nix_err_NIX_OK => unreachable!(), @@ -95,7 +109,7 @@ impl Into> for &ErrorContext { err => NixError::Undocumented(err), }; - Some(new_nixide_error!(NixError, inner, err, msg)) + Err(new_nixide_error!(NixError, inner, err, msg)) } } @@ -132,18 +146,13 @@ impl ErrorContext { /// Check the error code and return an error if it's not `NIX_OK`. pub fn peak(&self) -> NixideResult<()> { - match self.into() { - Some(err) => Err(err), - None => Ok(()), - } + self.into() } /// /// Equivalent to running `self.peak()` then `self.clear()` pub fn pop(&mut self) -> NixideResult<()> { - let error = self.peak(); - self.clear(); - error + self.peak().and_then(|_| Ok(self.clear())) } /// # Nix C++ API Internals @@ -214,13 +223,12 @@ impl ErrorContext { /// Hence we can just test whether the returned pointer is a `NULL` pointer, /// and avoid passing in a [sys::nix_c_context] struct. pub(super) fn get_msg(&self) -> Option { - // XXX: TODO: what happens if i DO actually use `null_mut` instead of ErrorContext::new? does rust just panic? let ctx = ErrorContext::new(); unsafe { // NOTE: an Err here only occurs when `self.get_code() == Ok(())` let mut n: c_uint = 0; sys::nix_err_msg(ctx.as_ptr(), self.as_ptr(), &mut n) - .to_utf8_string() + .to_utf8_string_n(n as usize) .ok() } } @@ -261,10 +269,9 @@ impl ErrorContext { /// } /// ``` pub(super) fn get_nix_err_name(&self) -> Option { - // XXX: TODO: what happens if i DO actually use `null_mut` instead of ErrorContext::new? does rust just panic? unsafe { // NOTE: an Err here only occurs when "Last error was not a nix error" - wrap_libnix_string_callback(|ctx, callback, user_data| { + wrap::nix_string_callback(|callback, user_data, ctx| { sys::nix_err_name(ctx.as_ptr(), self.as_ptr(), Some(callback), user_data) }) .ok() @@ -307,10 +314,9 @@ impl ErrorContext { /// } /// ``` pub(super) fn get_nix_err_info_msg(&self) -> Option { - // XXX: TODO: what happens if i DO actually use `null_mut` instead of ErrorContext::new? does rust just panic? unsafe { // NOTE: an Err here only occurs when "Last error was not a nix error" - wrap_libnix_string_callback(|ctx, callback, user_data| { + wrap::nix_string_callback(|callback, user_data, ctx| { sys::nix_err_info_msg(ctx.as_ptr(), self.as_ptr(), Some(callback), user_data) }) .ok() @@ -320,7 +326,6 @@ impl ErrorContext { impl Drop for ErrorContext { fn drop(&mut self) { - // SAFETY: We own the context and it's valid until drop unsafe { sys::nix_c_context_free(self.inner.as_ptr()); } diff --git a/nixide/src/errors/error.rs b/nixide/src/errors/error.rs index 986527f..bcebcf8 100644 --- a/nixide/src/errors/error.rs +++ b/nixide/src/errors/error.rs @@ -1,8 +1,7 @@ use std::fmt::{Display, Formatter, Result as FmtResult}; -use super::{ErrorContext, NixError}; +use super::NixError; use crate::sys; -use crate::util::panic_issue_call_failed; pub type NixideResult = Result; @@ -44,38 +43,38 @@ pub enum NixideError { macro_rules! new_nixide_error { (NixError, $inner:expr, $err:expr, $msg:expr) => {{ - NixideError::NixError { - trace: stdext::debug_name!(), + crate::NixideError::NixError { + trace: ::stdext::debug_name!(), inner: $inner, err: $err, msg: $msg, } }}; (StringNulByte) => {{ - NixideError::StringNulByte { - trace: stdext::debug_name!(), + crate::NixideError::StringNulByte { + trace: ::stdext::debug_name!(), } }}; (StringNotUtf8) => {{ - NixideError::StringNotUtf8 { - trace: stdext::debug_name!(), + crate::NixideError::StringNotUtf8 { + trace: ::stdext::debug_name!(), } }}; (NullPtr) => {{ - NixideError::NullPtr { - trace: stdext::debug_name!(), + crate::NixideError::NullPtr { + trace: ::stdext::debug_name!(), } }}; (InvalidArg, $name:expr, $reason:expr) => {{ - NixideError::InvalidArg { - trace: stdext::debug_name!(), + crate::NixideError::InvalidArg { + trace: ::stdext::debug_name!(), name: $name, reason: $reason, } }}; (InvalidType, $expected:expr, $got:expr) => {{ - NixideError::InvalidType { - trace: stdext::debug_name!(), + crate::NixideError::InvalidType { + trace: ::stdext::debug_name!(), expected: $expected, got: $got, } @@ -83,49 +82,14 @@ macro_rules! new_nixide_error { } pub(crate) use new_nixide_error; +#[allow(unused_macros)] macro_rules! retrace_nixide_error { ($x:expr) => {{ - new_nixide_error!($x.err) + crate::errors::new_nixide_error!($x.err) }}; } pub(crate) use retrace_nixide_error; -impl NixideError { - /// # Panics - /// - /// This function will panic in the event that `context.get_err() == Some(err) && err == sys::nix_err_NIX_OK` - /// since `nixide::ErrorContext::get_err` is expected to return `None` to indicate `sys::nix_err_NIX_OK`. - /// - /// - /// This function will panic in the event that `value != sys::nix_err_NIX_OK` - /// but that `context.get_code() == sys::nix_err_NIX_OK` - pub(super) fn from_error_context(context: &ErrorContext) -> Option { - let inner = context.get_err()?; - let msg = context.get_msg()?; - - let err = match inner { - sys::nix_err_NIX_OK => unreachable!(), - - sys::nix_err_NIX_ERR_OVERFLOW => NixError::Overflow, - sys::nix_err_NIX_ERR_KEY => NixError::KeyNotFound(None), - sys::nix_err_NIX_ERR_NIX_ERROR => NixError::ExprEval { - name: context - .get_nix_err_name() - .unwrap_or_else(|| panic_issue_call_failed!()), - - info_msg: context - .get_nix_err_info_msg() - .unwrap_or_else(|| panic_issue_call_failed!()), - }, - - sys::nix_err_NIX_ERR_UNKNOWN => NixError::Unknown, - err => NixError::Undocumented(err), - }; - - Some(new_nixide_error!(NixError, inner, err, msg)) - } -} - impl std::error::Error for NixideError {} impl Display for NixideError { @@ -172,3 +136,16 @@ impl Display for NixideError { } } } + +pub trait AsErr { + fn as_err(self) -> Result<(), T>; +} + +impl AsErr for Option { + fn as_err(self) -> Result<(), NixideError> { + match self { + Some(err) => Err(err), + None => Ok(()), + } + } +} diff --git a/nixide/src/errors/nix_error.rs b/nixide/src/errors/nix_error.rs index 39e6825..5e3938e 100644 --- a/nixide/src/errors/nix_error.rs +++ b/nixide/src/errors/nix_error.rs @@ -98,36 +98,6 @@ pub enum NixError { Undocumented(sys::nix_err), } -// impl NixError { -// /// # Panics -// /// -// /// This function will panic in the event that `context.get_err() == Some(err) && err == sys::nix_err_NIX_OK` -// /// since `nixide::ErrorContext::get_err` is expected to return `None` to indicate `sys::ni_err_NIX_OK`. -// /// -// /// -// /// This function will panic in the event that `value != sys::nix_err_NIX_OK` -// /// but that `context.get_code() == sys::nix_err_NIX_OK` -// pub(super) fn from_error_context(context: &ErrorContext) -> Option { -// #[allow(nonstandard_style)] -// match context.get_err()? { -// sys::nix_err_NIX_OK => unreachable!("call to `nixide::ErrorContext::get_err@nixide::NixError::from_context` failed: please open an issue on https://github.com/cry128/nixide"), - -// sys::nix_err_NIX_ERR_OVERFLOW => Some(NixError::Overflow), -// sys::nix_err_NIX_ERR_KEY => Some(NixError::KeyNotFound(None)), -// sys::nix_err_NIX_ERR_NIX_ERROR => Some(NixError::ExprEval { -// name: context -// .get_nix_err_name() -// .expect("call to `nixide::ErrorContext::get_nix_err_name@nixide::NixError::from_context` failed: please open an issue on https://github.com/cry128/nixide"), -// info_msg: context.get_nix_err_info_msg() -// .expect("call to `nixide::ErrorContext::get_nix_err_info_msg@nixide::NixError::from_context` failed: please open an issue on https://github.com/cry128/nixide"), -// }), - -// sys::nix_err_NIX_ERR_UNKNOWN => Some(NixError::Unknown), -// err => Some(NixError::Undocumented(err)), -// } -// } -// } - impl Display for NixError { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { match self { diff --git a/nixide/src/expr/value.rs b/nixide/src/expr/value.rs index 48a10e4..93f3787 100644 --- a/nixide/src/expr/value.rs +++ b/nixide/src/expr/value.rs @@ -40,7 +40,7 @@ impl Value { let ctx = ErrorContext::new(); unsafe { sys::nix_value_force(ctx.as_ptr(), state.as_ptr(), self.as_ptr()) }; - ctx.peak().as_err() + ctx.peak() } /// Force deep evaluation of this value. @@ -54,7 +54,7 @@ impl Value { let ctx = ErrorContext::new(); unsafe { sys::nix_value_force_deep(ctx.as_ptr(), state.as_ptr(), self.as_ptr()) }; - ctx.peak().as_err() + ctx.peak() } /// Get the type of this value. @@ -66,9 +66,7 @@ impl Value { // NOTE: an error here only occurs if `nix_get_type` catches an error, // NOTE: which in turn only happens if the `sys::nix_value*` is a null pointer // NOTE: or points to an uninitialised `nix_value` struct. - ctx.peak() - .as_err() - .unwrap_or_else(|_| panic!("TODO im sleepy rn")); + ctx.peak().unwrap_or_else(|_| panic!("TODO im sleepy rn")); value_type } diff --git a/nixide/src/flake/flake_reference.rs b/nixide/src/flake/flake_reference.rs index 3979e2a..c60a950 100644 --- a/nixide/src/flake/flake_reference.rs +++ b/nixide/src/flake/flake_reference.rs @@ -4,7 +4,7 @@ use std::ptr::{null_mut, NonNull}; use super::{FetchersSettings, FlakeReferenceParseFlags, FlakeSettings}; use crate::errors::new_nixide_error; use crate::sys; -use crate::util::bindings::wrap_libnix_string_callback; +use crate::util::bindings::wrap_nix_string_callback; use crate::util::wrappers::AsInnerPtr; use crate::NixideError; @@ -36,7 +36,7 @@ impl FlakeReference { reference: &str, ) -> Result<(FlakeReference, String), NixideError> { let mut ptr: *mut sys::nix_flake_reference = null_mut(); - let result = wrap_libnix_string_callback(|ctx, callback, user_data| unsafe { + let result = wrap_nix_string_callback(|ctx, callback, user_data| unsafe { sys::nix_flake_reference_and_fragment_from_string( ctx.as_ptr(), fetch_settings.as_ptr(), diff --git a/nixide/src/lib.rs b/nixide/src/lib.rs index 0ef0b16..05a1738 100644 --- a/nixide/src/lib.rs +++ b/nixide/src/lib.rs @@ -1,8 +1,8 @@ // #![warn(missing_docs)] pub(crate) mod errors; -mod expr; -mod flake; +// mod expr; +// mod flake; mod stdext; mod store; pub(crate) mod util; @@ -10,7 +10,7 @@ mod verbosity; mod version; pub use errors::{NixError, NixideError, NixideResult}; -pub use expr::{EvalState, EvalStateBuilder, Value, ValueType}; +// pub use expr::{EvalState, EvalStateBuilder, Value, ValueType}; pub use store::{Store, StorePath}; pub use verbosity::NixVerbosity; pub use version::NixVersion; diff --git a/nixide/src/stdext/cchar_ptr_ext.rs b/nixide/src/stdext/cchar_ptr_ext.rs index cd8bbfb..a4af4fe 100644 --- a/nixide/src/stdext/cchar_ptr_ext.rs +++ b/nixide/src/stdext/cchar_ptr_ext.rs @@ -1,30 +1,64 @@ use std::ffi::{c_char, CStr}; use std::slice::from_raw_parts; -use std::str::{from_utf8, Utf8Error}; +use std::str::from_utf8; + +use crate::errors::new_nixide_error; +use crate::NixideResult; pub trait CCharPtrExt { - fn to_utf8_string(self) -> Result>; + fn to_utf8_string(self) -> NixideResult; - fn to_utf8_string_n(self, n: usize) -> Result>; + fn to_utf8_string_n(self, n: usize) -> NixideResult; } impl CCharPtrExt for *const c_char { - fn to_utf8_string(self) -> Result> { + fn to_utf8_string(self) -> NixideResult { if self.is_null() { - return Err(None); + return Err(new_nixide_error!(NullPtr)); } let cstr = unsafe { CStr::from_ptr(self) }; match cstr.to_str() { Ok(s) => Ok(s.to_owned()), - Err(err) => Err(Some(err)), + Err(_) => Err(new_nixide_error!(StringNotUtf8)), } } - fn to_utf8_string_n(self, n: usize) -> Result> { + fn to_utf8_string_n(self, n: usize) -> NixideResult { if self.is_null() || n == 0 { - return Err(None); + return Err(new_nixide_error!(NullPtr)); } let bytes = unsafe { from_raw_parts(self.cast::(), n as usize) }; - from_utf8(bytes).map(str::to_string).map_err(Some) + match from_utf8(bytes) { + Ok(s) => Ok(s.to_string()), + Err(_) => Err(new_nixide_error!(StringNotUtf8)), + } } } + +// XXX: TODO: remove if unused +// pub trait CCharPtrExt { +// fn to_utf8_string(self) -> Result>; +// +// fn to_utf8_string_n(self, n: usize) -> Result>; +// } +// +// impl CCharPtrExt for *const c_char { +// fn to_utf8_string(self) -> Result> { +// if self.is_null() { +// return Err(None); +// } +// let cstr = unsafe { CStr::from_ptr(self) }; +// match cstr.to_str() { +// Ok(s) => Ok(s.to_owned()), +// Err(err) => Err(Some(err)), +// } +// } +// +// fn to_utf8_string_n(self, n: usize) -> Result> { +// if self.is_null() || n == 0 { +// return Err(None); +// } +// let bytes = unsafe { from_raw_parts(self.cast::(), n as usize) }; +// from_utf8(bytes).map(str::to_string).map_err(Some) +// } +// } diff --git a/nixide/src/store/mod.rs b/nixide/src/store/mod.rs index 9aa7f44..e98595d 100644 --- a/nixide/src/store/mod.rs +++ b/nixide/src/store/mod.rs @@ -16,15 +16,17 @@ mod tests; mod path; pub use path::*; -use std::ffi::{CStr, CString, NulError}; -use std::os::raw::{c_char, c_void}; +use std::ffi::CString; +use std::os::raw::c_char; use std::path::PathBuf; -use std::ptr::NonNull; +use std::ptr::{null, null_mut, NonNull}; use std::result::Result; -use std::sync::Arc; -use super::{ErrorContext, NixErrorCode}; -use crate::util::bindings::{wrap_libnix_pathbuf_callback, wrap_libnix_string_callback}; +use crate::errors::{new_nixide_error, ErrorContext}; +use crate::stdext::CCharPtrExt; +use crate::util::wrap; +use crate::util::wrappers::AsInnerPtr; +use crate::{NixideError, NixideResult}; use nixide_sys as sys; /// Nix store for managing packages and derivations. @@ -32,7 +34,12 @@ use nixide_sys as sys; /// The store provides access to Nix packages, derivations, and store paths. pub struct Store { pub(crate) inner: NonNull, - pub(crate) _context: Arc, +} + +impl AsInnerPtr for Store { + unsafe fn as_ptr(&self) -> *mut sys::Store { + self.inner.as_ptr() + } } impl Store { @@ -46,36 +53,19 @@ impl Store { /// # Errors /// /// Returns an error if the store cannot be opened. - pub fn open(context: &Arc, uri: Option<&str>) -> Result { - let uri_cstring: CString; - let uri_ptr = if let Some(uri) = uri { - uri_cstring = NixErrorCode::from_nulerror(CString::new(uri), "nixide::Store::open")?; - uri_cstring.as_ptr() - } else { - std::ptr::null() + pub fn open(uri: Option<&str>) -> Result { + let uri_ptr = match uri.map(CString::new) { + Some(Ok(c_uri)) => c_uri.as_ptr(), + Some(Err(_)) => Err(new_nixide_error!(StringNulByte))?, + None => null(), }; - // SAFETY: context is valid, uri_ptr is either null or valid CString - 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(NixErrorCode::NullPtr { - location: "nix_store_open", + let inner = wrap::nix_ptr_fn!(|ctx: &ErrorContext| unsafe { + // XXX: TODO: allow args to be parsed instead of just `null_mut` + sys::nix_store_open(ctx.as_ptr(), uri_ptr, null_mut()) })?; - Ok(Store { - inner, - _context: Arc::clone(context), - }) - } - - /// Get the raw store pointer. - /// - /// # Safety - /// - /// The caller must ensure the pointer is used safely. - pub(crate) unsafe fn as_ptr(&self) -> *mut sys::Store { - self.inner.as_ptr() + Ok(Store { inner }) } /// Realize a store path. @@ -99,72 +89,100 @@ impl Store { &self, path: &StorePath, callback: fn(&str, &StorePath), - ) -> Result, NixErrorCode> { - // Type alias for our userdata: (outputs vector, context) - type Userdata = ( - Vec<(String, StorePath)>, - Arc, - fn(&str, &StorePath), - ); + ) -> NixideResult<(String, StorePath)> { + // // Type alias for our userdata: (outputs vector, context) + // type Userdata = ( + // Vec<(String, StorePath)>, + // Arc, + // fn(&str, &StorePath), + // ); + // + // // Callback function that will be called for each realized output + // unsafe extern "C" fn realise_callback( + // userdata: *mut c_void, + // out_name_ptr: *const c_char, + // out_path_ptr: *const sys::StorePath, + // ) { + // // SAFETY: userdata is a valid pointer to our (Vec, Arc) tuple + // let (outputs, ctx, callback) = unsafe { &mut *(userdata as *mut Userdata) }; + // + // // SAFETY: outname is a valid C string from Nix + // let output_name = if !out_name_ptr.is_null() { + // unsafe { CStr::from_ptr(out_name_ptr).to_string_lossy().into_owned() } + // } else { + // String::from("out") // Default output name + // }; + // + // // SAFETY: out is a valid StorePath pointer from Nix, we need to clone it + // // because Nix owns the original and may free it after the callback + // if !out_path_ptr.is_null() { + // let cloned_path_ptr = + // unsafe { sys::nix_store_path_clone(out_path_ptr as *mut sys::StorePath) }; + // if let Some(inner) = NonNull::new(cloned_path_ptr) { + // let store_path = StorePath { inner }; + // + // callback(output_name.as_ref(), &store_path); + // + // outputs.push((output_name, store_path)); + // } + // } + // } + // + // // Create userdata with empty outputs vector and context + // let mut userdata: Userdata = (Vec::new(), Arc::new(ErrorContext::new()), callback); + // let userdata_ptr = &mut userdata as *mut Userdata as *mut std::os::raw::c_void; + // + // // SAFETY: All pointers are valid, callback is compatible with the FFI signature + // // - self._context is valid for the duration of this call + // // - self.inner is valid (checked in Store::open) + // // - path.inner is valid (checked in StorePath::parse) + // // - userdata_ptr points to valid stack memory + // // - realize_callback matches the expected C function signature + // let err = unsafe { + // sys::nix_store_realise( + // ctx.as_ptr(), + // self.inner.as_ptr(), + // path.as_ptr(), + // userdata_ptr, + // Some(realise_callback), + // ) + // }; - // Callback function that will be called for each realized output - unsafe extern "C" fn realise_callback( - userdata: *mut c_void, - out_name_ptr: *const c_char, - out_path_ptr: *const sys::StorePath, - ) { - // SAFETY: userdata is a valid pointer to our (Vec, Arc) tuple - let (outputs, context, callback) = unsafe { &mut *(userdata as *mut Userdata) }; + wrap::nix_callback!( + |userdata ; + output_name_ptr: *const c_char, + output_path_ptr: *const sys::StorePath| + -> NixideResult<(String, StorePath)> { + // XXX: TODO: test to see if this is ever null ("out" as a default feels unsafe...) + // let output_name = output_name_ptr.to_utf8_string().unwrap_or("out".to_owned()); + let output_name = output_name_ptr.to_utf8_string()?; - // SAFETY: outname is a valid C string from Nix - let output_name = if !out_name_ptr.is_null() { - unsafe { CStr::from_ptr(out_name_ptr).to_string_lossy().into_owned() } - } else { - String::from("out") // Default output name - }; - - // SAFETY: out is a valid StorePath pointer from Nix, we need to clone it - // because Nix owns the original and may free it after the callback - if !out_path_ptr.is_null() { - let cloned_path_ptr = - unsafe { sys::nix_store_path_clone(out_path_ptr as *mut sys::StorePath) }; - if let Some(inner) = NonNull::new(cloned_path_ptr) { - let store_path = StorePath { - inner, - _context: Arc::clone(context), - }; + // SAFETY: out is a valid StorePath pointer from Nix, we need to clone it + // because Nix owns the original and may free it after the callback + if !output_path_ptr.is_null() { + let inner = wrap::nix_ptr_fn!(|ctx| unsafe { + sys::nix_store_path_clone(output_path_ptr as *mut sys::StorePath) + })?; + let store_path = StorePath { inner }; callback(output_name.as_ref(), &store_path); - outputs.push((output_name, store_path)); + Ok((output_name, store_path)) + } else { + panic!("IDK YET"); + // XXX: TODO: how should `output_path_ptr.is_null()` be handled? } + }, + |callback, userdata, ctx: &ErrorContext| unsafe { + sys::nix_store_realise( + ctx.as_ptr(), + self.inner.as_ptr(), + path.as_ptr(), + userdata, + Some(callback), + ) } - } - - // Create userdata with empty outputs vector and context - let mut userdata: Userdata = (Vec::new(), Arc::clone(&self._context), callback); - let userdata_ptr = &mut userdata as *mut Userdata as *mut std::os::raw::c_void; - - // SAFETY: All pointers are valid, callback is compatible with the FFI signature - // - self._context is valid for the duration of this call - // - self.inner is valid (checked in Store::open) - // - path.inner is valid (checked in StorePath::parse) - // - userdata_ptr points to valid stack memory - // - realize_callback matches the expected C function signature - let err = unsafe { - sys::nix_store_realise( - self._context.as_ptr(), - self.inner.as_ptr(), - path.as_ptr(), - userdata_ptr, - Some(realise_callback), - ) - }; - - NixErrorCode::result_from(err, "nix_store_realise")?; - - // Return the collected outputs - Ok(userdata.0) + ) } /// Parse a store path string into a StorePath. @@ -191,40 +209,30 @@ impl Store { /// # Ok(()) /// # } /// ``` - pub fn store_path(&self, path: &str) -> Result { - StorePath::parse(&self._context, self, path) + pub fn store_path(&self, path: &str) -> Result { + StorePath::parse(self, path) } /// Get the version of a Nix store /// /// If the store doesn't have a version (like the dummy store), returns None - pub fn version(&self) -> Result { - wrap_libnix_string_callback("nix_store_get_version", |callback, user_data| unsafe { - sys::nix_store_get_version( - self._context.as_ptr(), - self.inner.as_ptr(), - Some(callback), - user_data, - ) + pub fn version(&self) -> Result { + wrap::nix_string_callback(|callback, user_data, ctx| unsafe { + sys::nix_store_get_version(ctx.as_ptr(), self.inner.as_ptr(), Some(callback), user_data) }) } /// Get the URI of a Nix store - pub fn uri(&self) -> Result { - wrap_libnix_string_callback("nix_store_get_uri", |callback, user_data| unsafe { - sys::nix_store_get_uri( - self._context.as_ptr(), - self.inner.as_ptr(), - Some(callback), - user_data, - ) + pub fn uri(&self) -> Result { + wrap::nix_string_callback(|callback, user_data, ctx| unsafe { + sys::nix_store_get_uri(ctx.as_ptr(), self.inner.as_ptr(), Some(callback), user_data) }) } - pub fn store_dir(&self) -> Result { - wrap_libnix_pathbuf_callback("nix_store_get_storedir", |callback, user_data| unsafe { + pub fn store_dir(&self) -> Result { + wrap::nix_pathbuf_callback(|callback, user_data, ctx| unsafe { sys::nix_store_get_storedir( - self._context.as_ptr(), + ctx.as_ptr(), self.inner.as_ptr(), Some(callback), user_data, @@ -236,38 +244,35 @@ impl Store { &self, dst_store: &Store, store_path: &StorePath, - ) -> Result<(), NixErrorCode> { - let err = unsafe { + ) -> Result<(), NixideError> { + wrap::nix_fn!(|ctx: &ErrorContext| unsafe { sys::nix_store_copy_closure( - self._context.as_ptr(), - self.inner.as_ptr(), - dst_store.inner.as_ptr(), - store_path.inner.as_ptr(), - ) - }; - NixErrorCode::result_from(err, "nix_store_copy_closure") + ctx.as_ptr(), + self.as_ptr(), + dst_store.as_ptr(), + store_path.as_ptr(), + ); // semi-colon to return () and not i32 + }) } pub fn copy_closure_from( &self, src_store: &Store, store_path: &StorePath, - ) -> Result<(), NixErrorCode> { - let err = unsafe { + ) -> Result<(), NixideError> { + wrap::nix_fn!(|ctx: &ErrorContext| unsafe { sys::nix_store_copy_closure( - self._context.as_ptr(), - src_store.inner.as_ptr(), - self.inner.as_ptr(), + ctx.as_ptr(), + src_store.as_ptr(), + self.as_ptr(), store_path.inner.as_ptr(), - ) - }; - NixErrorCode::result_from(err, "nix_store_copy_closure") + ); + }) // semi-colon to return () and not i32 } } impl Drop for Store { fn drop(&mut self) { - // SAFETY: We own the store and it's valid until drop unsafe { sys::nix_store_free(self.inner.as_ptr()); } diff --git a/nixide/src/store/path.rs b/nixide/src/store/path.rs index c298eb4..9696481 100644 --- a/nixide/src/store/path.rs +++ b/nixide/src/store/path.rs @@ -1,11 +1,11 @@ use std::ffi::CString; use std::path::PathBuf; use std::ptr::NonNull; -use std::sync::Arc; use super::Store; use crate::errors::{new_nixide_error, ErrorContext}; -use crate::util::bindings::{wrap_libnix_pathbuf_callback, wrap_libnix_string_callback}; +use crate::util::panic_issue_call_failed; +use crate::util::wrap; use crate::util::wrappers::AsInnerPtr; use crate::NixideError; @@ -14,10 +14,17 @@ use nixide_sys::{self as sys, nix_err_NIX_OK}; /// A path in the Nix store. /// /// Represents a store path that can be realized, queried, or manipulated. +/// pub struct StorePath { pub(crate) inner: NonNull, } +impl AsInnerPtr for StorePath { + unsafe fn as_ptr(&self) -> *mut sys::StorePath { + self.inner.as_ptr() + } +} + impl StorePath { /// Parse a store path string into a StorePath. /// @@ -30,24 +37,13 @@ impl StorePath { /// /// Returns an error if the path cannot be parsed. pub fn parse(store: &Store, path: &str) -> Result { - let path_cstring = CString::new(path).or(Err(new_nixide_error!( - InvalidArg, - "path", - "contains a `\\0` (NUL) byte".to_owned() - )))?; + let c_path = CString::new(path).or(Err(new_nixide_error!(StringNulByte)))?; - let ctx = ErrorContext::new(); - let path_ptr = unsafe { - sys::nix_store_parse_path(ctx.as_ptr(), store.as_ptr(), path_cstring.as_ptr()) - }; + let inner = wrap::nix_ptr_fn!(|ctx: &ErrorContext| unsafe { + sys::nix_store_parse_path(ctx.as_ptr(), store.as_ptr(), c_path.as_ptr()) + })?; - match ctx.peak() { - Some(err) => Err(err), - None => match NonNull::new(path_ptr) { - Some(inner) => Ok(Self { inner }), - None => Err(new_nixide_error!(NullPtr)), - }, - } + Ok(Self { inner }) } /// Get the name component of the store path. @@ -60,9 +56,8 @@ impl StorePath { /// Returns an error if the name cannot be retrieved. /// pub fn name(&self) -> Result { - wrap_libnix_string_callback(|_, callback, user_data| unsafe { + wrap::nix_string_callback(|callback, user_data, _| unsafe { sys::nix_store_path_name(self.inner.as_ptr(), Some(callback), user_data); - // NOTE: nix_store_path_name doesn't return nix_err, so we force it to return successfully nix_err_NIX_OK }) @@ -90,20 +85,14 @@ impl StorePath { /// * `store` - The store containing the path /// pub fn real_path(&self, store: &Store) -> Result { - wrap_libnix_pathbuf_callback(|ctx, callback, user_data| unsafe { - let err_code = sys::nix_store_real_path( + wrap::nix_pathbuf_callback(|callback, user_data, ctx| unsafe { + sys::nix_store_real_path( ctx.as_ptr(), store.inner.as_ptr(), self.as_ptr(), Some(callback), user_data, - ); - match ctx.pop() { - Some(err) => Err(err), - None => Ok(()), - } - - err_code + ) }) } @@ -115,45 +104,28 @@ impl StorePath { /// * `store` - The store containing the path /// pub fn is_valid(&self, store: &Store) -> bool { - let ctx = ErrorContext::new(); - unsafe { - sys::nix_store_is_valid_path(ctx.as_ptr(), store.inner.as_ptr(), self.inner.as_ptr()) - } - - ctx - } - - /// Get the raw store path pointer. - /// - /// # Safety - /// - /// The caller must ensure the pointer is used safely. - pub(crate) unsafe fn as_ptr(&self) -> *mut sys::StorePath { - self.inner.as_ptr() + wrap::nix_fn!(|ctx: &ErrorContext| unsafe { + sys::nix_store_is_valid_path(ctx.as_ptr(), store.as_ptr(), self.as_ptr()) + }) + .is_ok() } } impl Clone for StorePath { fn clone(&self) -> Self { - // SAFETY: self.inner is valid, nix_store_path_clone creates a new copy - let cloned_ptr = unsafe { sys::nix_store_path_clone(self.inner.as_ptr()) }; + let inner = wrap::nix_ptr_fn!(|_| unsafe { sys::nix_store_path_clone(self.as_ptr()) }) + .unwrap_or_else(|_| { + panic_issue_call_failed!("nix_store_path_clone returned None for valid path") + }); - // This should never fail as cloning a valid path should always succeed - let inner = - NonNull::new(cloned_ptr).expect("nix_store_path_clone returned null for valid path"); - - StorePath { - inner, - _context: Arc::clone(&self._context), - } + StorePath { inner } } } impl Drop for StorePath { fn drop(&mut self) { - // SAFETY: We own the store path and it's valid until drop unsafe { - sys::nix_store_path_free(self.inner.as_ptr()); + sys::nix_store_path_free(self.as_ptr()); } } } diff --git a/nixide/src/store/tests.rs b/nixide/src/store/tests.rs index 4fdef06..3e92b6c 100644 --- a/nixide/src/store/tests.rs +++ b/nixide/src/store/tests.rs @@ -5,23 +5,17 @@ use super::*; #[test] #[serial] fn test_store_opening() { - let ctx = Arc::new(ErrorContext::new().expect("Failed to create context")); - let _store = Store::open(&ctx, None).expect("Failed to open store"); + let _store = Store::open(None).expect("Failed to open store"); } #[test] #[serial] fn test_store_path_parse() { - let ctx = Arc::new(ErrorContext::new().expect("Failed to create context")); - let store = Store::open(&ctx, None).expect("Failed to open store"); + let store = Store::open(None).expect("Failed to open store"); // Try parsing a well-formed store path // Note: This may fail if the path doesn't exist in the store - let result = StorePath::parse( - &ctx, - &store, - "/nix/store/00000000000000000000000000000000-test", - ); + let result = StorePath::parse(&store, "/nix/store/00000000000000000000000000000000-test"); // We don't assert success here because the path might not exist // This test mainly checks that the API works correctly @@ -38,16 +32,11 @@ fn test_store_path_parse() { #[test] #[serial] fn test_store_path_clone() { - let ctx = Arc::new(ErrorContext::new().expect("Failed to create context")); - let store = Store::open(&ctx, None).expect("Failed to open store"); + let store = Store::open(None).expect("Failed to open store"); // Try to get a valid store path by parsing // Note: This test is somewhat limited without a guaranteed valid path - if let Ok(path) = StorePath::parse( - &ctx, - &store, - "/nix/store/00000000000000000000000000000000-test", - ) { + if let Ok(path) = StorePath::parse(&store, "/nix/store/00000000000000000000000000000000-test") { let cloned = path.clone(); // Assert that the cloned path has the same name as the original diff --git a/nixide/src/util/bindings.rs b/nixide/src/util/bindings.rs deleted file mode 100644 index 50408e6..0000000 --- a/nixide/src/util/bindings.rs +++ /dev/null @@ -1,47 +0,0 @@ -use std::mem::MaybeUninit; -use std::os::raw::{c_char, c_uint, c_void}; -use std::path::PathBuf; - -use crate::errors::{ErrorContext, NixideError}; -use crate::util::CCharPtrNixExt; - -pub fn wrap_libnix_string_callback(callback: F) -> Result -where - F: FnOnce( - &ErrorContext, - unsafe extern "C" fn(*const c_char, c_uint, *mut c_void), - *mut c_void, - ) -> i32, -{ - // Callback to receive the string - unsafe extern "C" fn wrapper_callback(start: *const c_char, n: c_uint, user_data: *mut c_void) { - let result = unsafe { &mut *(user_data as *mut Result) }; - - *result = start.to_utf8_string_sized(n as usize); - } - - let ctx = ErrorContext::new(); - let mut user_data: MaybeUninit> = MaybeUninit::uninit(); - - callback( - &ctx, - wrapper_callback, - user_data.as_mut_ptr() as *mut c_void, - ); - if let Some(err) = ctx.peak() { - return Err(err); - } - - unsafe { user_data.assume_init() } -} - -pub fn wrap_libnix_pathbuf_callback(callback: F) -> Result -where - F: FnOnce( - &ErrorContext, - unsafe extern "C" fn(*const c_char, c_uint, *mut c_void), - *mut c_void, - ) -> i32, -{ - wrap_libnix_string_callback(callback).map(PathBuf::from) -} diff --git a/nixide/src/util/mod.rs b/nixide/src/util/mod.rs index 5635219..2302b41 100644 --- a/nixide/src/util/mod.rs +++ b/nixide/src/util/mod.rs @@ -1,23 +1,38 @@ #[macro_use] pub mod panic; -pub(crate) mod bindings; -mod cchar_nix_ext; +pub(crate) mod wrap; pub mod wrappers; -pub use cchar_nix_ext::CCharPtrNixExt; pub(crate) use panic::*; -use crate::NixideError; +// use crate::NixideError; -pub trait AsErr { - fn as_err(self) -> Result<(), T>; -} +// pub trait AsErr { +// fn as_err(self) -> Result<(), T>; +// } -impl AsErr for Option { - fn as_err(self) -> Result<(), NixideError> { - match self { - Some(err) => Err(err), - None => Ok(()), - } - } -} +// impl AsErr for Option { +// fn as_err(self) -> Result<(), NixideError> { +// match self { +// Some(err) => Err(err), +// None => Ok(()), +// } +// } +// } + +// pub trait AsInnerPtr { +// /// Get a pointer to the underlying (`inner`) `libnix` C struct. +// /// +// /// # Safety +// /// +// /// Although this function isn't inherently `unsafe`, it is +// /// marked as such intentionally to force calls to be wrapped +// /// in `unsafe` blocks for clarity. +// unsafe fn as_ptr(&self) -> *mut T; +// } + +// pub trait FromC { +// /// Creates a new instance of [Self] from the underlying +// /// libnix C type [T]. +// unsafe fn from_c(value: T) -> Self; +// } diff --git a/nixide/src/util/panic.rs b/nixide/src/util/panic.rs index 7a22501..51f2f34 100644 --- a/nixide/src/util/panic.rs +++ b/nixide/src/util/panic.rs @@ -11,6 +11,9 @@ macro_rules! panic_issue_call_failed { () => {{ crate::util::panic_issue!("[nixide] call to `{}` failed", stdext::debug_name!()) }}; + ($($arg:expr),*) => {{ + crate::util::panic_issue!("[nixide] call to `{}` failed with \"{}\"", stdext::debug_name!(), format!($($arg),*)) + }}; } pub(crate) use panic_issue; diff --git a/nixide/src/util/wrap.rs b/nixide/src/util/wrap.rs new file mode 100644 index 0000000..1bb30cf --- /dev/null +++ b/nixide/src/util/wrap.rs @@ -0,0 +1,147 @@ +use std::mem::MaybeUninit; +use std::os::raw::{c_char, c_uint, c_void}; +use std::path::PathBuf; + +use crate::errors::{ErrorContext, NixideError}; +use crate::stdext::CCharPtrExt as _; +use crate::util::wrappers::AsInnerPtr; +use crate::NixideResult; + +struct UserData { + // inner: T, + inner: MaybeUninit, +} + +impl AsInnerPtr for UserData { + unsafe fn as_ptr(&self) -> *mut T { + self.inner.as_mut_ptr() + } +} + +macro_rules! nonnull { + ($ptr:expr $(,)? ) => {{ + match ::std::ptr::NonNull::new($ptr) { + ::std::option::Option::Some(p) => ::std::result::Result::Ok(p), + ::std::option::Option::None => { + ::std::result::Result::Err(crate::errors::new_nixide_error!(NullPtr)) + } + } + }}; +} +pub(crate) use nonnull; + +macro_rules! nix_fn { + ($callback:expr $(,)? ) => {{ + // XXX: TODO: what happens if i DO actually use `null_mut` instead of ErrorContext::new? does rust just panic? + let mut ctx = crate::errors::ErrorContext::new(); + let result = $callback( + &ctx, + ); + ctx.pop().and_then(|_| ::std::result::Result::Ok(result)) + }}; +} +pub(crate) use nix_fn; + +macro_rules! nix_ptr_fn { + ($callback:expr $(,)? ) => {{ + crate::util::wrap::nix_fn!($callback).and_then(|ptr| crate::util::wrap::nonnull!(ptr)) + }}; +} +pub(crate) use nix_ptr_fn; + +macro_rules! nix_callback { + ( | $($arg_name:ident : $arg_type:ty),* ; userdata $( : *mut c_void )? $(,)? | -> $ret:ty $body:block, $callback:expr $(,)? ) => {{ + // create a function item that wraps the closure body (so it has a concrete type) + #[allow(unused_variables)] + fn __captured_fn( $( $arg_name: $arg_type ),*, userdata: *mut ::std::os::raw::c_void) -> $ret $body + + unsafe extern "C" fn __wrapper_callback( + $( + $arg_name: $arg_type, + )* + userdata: *mut ::std::os::raw::c_void, + ) { + let result = unsafe { &mut *(userdata as *mut $ret) }; + + *result = __captured_fn( + $( + $arg_name, + )* + userdata, + ); + } + + // XXX: TODO: what happens if i DO actually use `null_mut` instead of ErrorContext::new? does rust just panic? + let mut ctx = crate::errors::ErrorContext::new(); + let mut result: ::std::mem::MaybeUninit<$ret> = ::std::mem::MaybeUninit::uninit(); + + $callback( + __wrapper_callback, + result.as_mut_ptr() as *mut ::std::os::raw::c_void, + &ctx, + ); + ctx.pop().and_then(|_| unsafe { result.assume_init() }) + + }}; + + ( | userdata $( : *mut c_void )? ; $($arg_name:ident : $arg_type:ty),* | -> $ret:ty $body:block, $callback:expr $(,)? ) => {{ + // create a function item that wraps the closure body (so it has a concrete type) + #[allow(unused_variables)] + fn __captured_fn(userdata: *mut ::std::os::raw::c_void, $($arg_name: $arg_type),*) -> $ret $body + + unsafe extern "C" fn __wrapper_callback( + userdata: *mut ::std::os::raw::c_void, + $( + $arg_name: $arg_type, + )* + ) { + let result = unsafe { &mut *(userdata as *mut $ret) }; + + *result = __captured_fn( + userdata, + $( + $arg_name, + )* + ); + } + + // XXX: TODO: what happens if i DO actually use `null_mut` instead of ErrorContext::new? does rust just panic? + let mut ctx = crate::errors::ErrorContext::new(); + let mut result: ::std::mem::MaybeUninit<$ret> = ::std::mem::MaybeUninit::uninit(); + + $callback( + __wrapper_callback, + result.as_mut_ptr() as *mut ::std::os::raw::c_void, + &ctx, + ); + ctx.pop().and_then(|_| unsafe { result.assume_init() }) + }}; +} +pub(crate) use nix_callback; + +pub fn nix_string_callback(callback: F) -> Result +where + F: FnOnce( + unsafe extern "C" fn(*const c_char, c_uint, *mut c_void), + *mut c_void, + &ErrorContext, + ) -> i32, +{ + crate::util::wrap::nix_callback!( + |start: *const c_char, n: c_uint ; userdata| -> NixideResult { + start.to_utf8_string_n(n as usize) + }, + callback + ) +} + +pub fn nix_pathbuf_callback(callback: F) -> Result +where + F: FnOnce( + unsafe extern "C" fn(*const c_char, c_uint, *mut c_void), + *mut c_void, + &ErrorContext, + ) -> i32, +{ + nix_string_callback(callback).map(::std::path::PathBuf::from) +} diff --git a/nixide/src/util/wrappers.rs b/nixide/src/util/wrappers.rs index 1f6af3b..829437a 100644 --- a/nixide/src/util/wrappers.rs +++ b/nixide/src/util/wrappers.rs @@ -1,3 +1,18 @@ +use crate::NixideError; + +pub trait AsErr { + fn as_err(self) -> Result<(), T>; +} + +impl AsErr for Option { + fn as_err(self) -> Result<(), NixideError> { + match self { + Some(err) => Err(err), + None => Ok(()), + } + } +} + pub trait AsInnerPtr { /// Get a pointer to the underlying (`inner`) `libnix` C struct. ///