From f3802f63004ab1d8e12e5baca777ee23c9b1d0c3 Mon Sep 17 00:00:00 2001 From: _cry64 Date: Tue, 24 Mar 2026 22:28:25 +1000 Subject: [PATCH] i'll get this macro one day --- TODO.md | 2 + nixide/src/errors/context.rs | 6 +- nixide/src/store/mod.rs | 34 +++--- nixide/src/util/wrap.rs | 219 ++++++++++++++++++++++------------- 4 files changed, 157 insertions(+), 104 deletions(-) diff --git a/TODO.md b/TODO.md index e8fdabf..ffeff7f 100644 --- a/TODO.md +++ b/TODO.md @@ -1,3 +1,5 @@ +- [ ] rename `AsInnerPtr::as_ptr` to `AsInnerPtr::as_mut_ptr` + - [ ] add NixError::from_nonnull that replaces calls to NonNull::new(...).ok_or(...) - [ ] replace all `use nixide_sys as sys;` -> `use crate::sys;` - [ ] store NonNull pointers in structs! diff --git a/nixide/src/errors/context.rs b/nixide/src/errors/context.rs index 87039f5..5c13781 100644 --- a/nixide/src/errors/context.rs +++ b/nixide/src/errors/context.rs @@ -271,8 +271,8 @@ impl ErrorContext { pub(super) fn get_nix_err_name(&self) -> Option { unsafe { // NOTE: an Err here only occurs when "Last error was not a nix error" - wrap::nix_string_callback(|callback, user_data, ctx| { - sys::nix_err_name(ctx.as_ptr(), self.as_ptr(), Some(callback), user_data) + wrap::nix_string_callback!(|callback, userdata: *mut __UserData, ctx: &ErrorContext| { + sys::nix_err_name(ctx.as_ptr(), self.as_ptr(), Some(callback), userdata) }) .ok() } @@ -316,7 +316,7 @@ impl ErrorContext { pub(super) fn get_nix_err_info_msg(&self) -> Option { unsafe { // NOTE: an Err here only occurs when "Last error was not a nix error" - wrap::nix_string_callback(|callback, user_data, ctx| { + wrap::nix_string_callback!(|callback, user_data, ctx| { sys::nix_err_info_msg(ctx.as_ptr(), self.as_ptr(), Some(callback), user_data) }) .ok() diff --git a/nixide/src/store/mod.rs b/nixide/src/store/mod.rs index e98595d..8e27fff 100644 --- a/nixide/src/store/mod.rs +++ b/nixide/src/store/mod.rs @@ -16,15 +16,14 @@ mod tests; mod path; pub use path::*; -use std::ffi::CString; -use std::os::raw::c_char; +use std::ffi::{c_char, c_void, CString}; use std::path::PathBuf; use std::ptr::{null, null_mut, NonNull}; use std::result::Result; use crate::errors::{new_nixide_error, ErrorContext}; use crate::stdext::CCharPtrExt; -use crate::util::wrap; +use crate::util::wrap::{self, UserData}; use crate::util::wrappers::AsInnerPtr; use crate::{NixideError, NixideResult}; use nixide_sys as sys; @@ -149,36 +148,33 @@ impl Store { // }; wrap::nix_callback!( - |userdata ; + |userdata: fn(&str, &StorePath); 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()); + // NOTE: this also ensures `output_name_ptr` isn't null let output_name = output_name_ptr.to_utf8_string()?; - // 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 }; + 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); + let callback = userdata.inner; + callback(output_name.as_ref(), &store_path); - Ok((output_name, store_path)) - } else { - panic!("IDK YET"); - // XXX: TODO: how should `output_path_ptr.is_null()` be handled? - } + Ok((output_name, store_path)) }, - |callback, userdata, ctx: &ErrorContext| unsafe { + |callback, + state: *mut UserData>, + ctx: &ErrorContext| unsafe { sys::nix_store_realise( ctx.as_ptr(), self.inner.as_ptr(), path.as_ptr(), - userdata, + (*state).inner_ptr() as *mut c_void, Some(callback), ) } diff --git a/nixide/src/util/wrap.rs b/nixide/src/util/wrap.rs index 1bb30cf..5ede2a7 100644 --- a/nixide/src/util/wrap.rs +++ b/nixide/src/util/wrap.rs @@ -1,29 +1,47 @@ -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, +#[repr(C)] +#[derive(Debug)] +pub(crate) struct UserData { + pub inner: S, + pub retval: T, } -impl AsInnerPtr for UserData { - unsafe fn as_ptr(&self) -> *mut T { - self.inner.as_mut_ptr() +impl AsMut> for UserData { + fn as_mut(&mut self) -> &mut UserData { + self } } +impl UserData { + /// # Warning + /// + /// Ensure `self.retval` has been initialised before unwrapping! + /// + pub unsafe fn unwrap(self) -> (S, T) { + (self.inner, self.retval) + } + + pub unsafe fn as_mut_ptr(&mut self) -> *mut Self { + self as *mut Self + } + + pub unsafe fn inner_ptr(&mut self) -> *mut S { + unsafe { + let ptr = self.as_mut_ptr(); + &raw mut (*ptr).inner + } + } + + // pub unsafe fn retval_ptr(&mut self) -> *mut c_void { + // &mut self.retval as *mut T as *mut c_void + // } +} + 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)) + ::std::result::Result::Err($crate::errors::new_nixide_error!(NullPtr)) } } }}; @@ -32,116 +50,153 @@ 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)) + 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)) + $crate::util::wrap::nix_fn!($callback).and_then(|ptr| $crate::util::wrap::nonnull!(ptr)) }}; } pub(crate) use nix_ptr_fn; +macro_rules! __nix_callback { + ($userdata_type:ty, $ret:ty, $callback:expr) => {{ + let mut __ctx = $crate::errors::ErrorContext::new(); + let mut __state: ::std::mem::MaybeUninit<__UserData> = ::std::mem::MaybeUninit::uninit(); + + $callback(__wrapper_callback, __state.as_mut_ptr(), &__ctx); + + // add type annotations for compiler + let __return: $ret = __ctx + .pop() + .and_then(|_| unsafe { __state.assume_init().retval }); + __return + }}; +} +pub(crate) use __nix_callback; + +/// `libnix` functions consistently either expect the `userdata`/`user_data` (inconsistently named in the API...) +/// field to be the first or last parameter (differs between function). The `nix_callback!` macro allows the +/// position to be specified by either the following syntax: +/// +/// ```rs +/// nix_callback(userdata; ...); // first parameter +/// nix_callback(...; userdata); // last parameter +/// ``` +/// macro_rules! nix_callback { - ( | $($arg_name:ident : $arg_type:ty),* ; userdata $( : *mut c_void )? $(,)? | -> $ret:ty $body:block, $callback:expr $(,)? ) => {{ + ( | $userdata:ident : $userdata_type:ty; $($arg_name:ident : $arg_type:ty),* $(,)? | -> $ret:ty $body:block, $function:expr $(,)? ) => {{ + type __UserData = $crate::util::wrap::UserData<$userdata_type, $ret>; // 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 + fn __captured_fn($userdata: &mut __UserData, $($arg_name: $arg_type),*) -> $ret $body unsafe extern "C" fn __wrapper_callback( + $userdata: *mut ::std::ffi::c_void, $( $arg_name: $arg_type, )* - userdata: *mut ::std::os::raw::c_void, ) { - let result = unsafe { &mut *(userdata as *mut $ret) }; + let ud = unsafe { &mut *($userdata as *mut __UserData) }; + let stored_retval = &raw mut ud.retval; - *result = __captured_fn( + let retval = __captured_fn( + ud, $( $arg_name, )* - userdata, ); + + unsafe { + stored_retval.write(retval) + }; } - // 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(); + let mut __ctx: $crate::errors::ErrorContext = $crate::errors::ErrorContext::new(); + let mut __state: ::std::mem::MaybeUninit<__UserData> = ::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() }) + // fn __captured_function( + // callback: unsafe extern "C" fn( + // $userdata: *mut ::std::ffi::c_void, + // $( + // $arg_name: $arg_type, + // )* + // ), + // state: *mut __UserData, + // ctx: &$crate::errors::ErrorContext, + // ) { + // $function(callback, state, ctx); + // } + $function(__wrapper_callback, __state.as_mut_ptr(), &__ctx); + + // add type annotations for compiler + __ctx.pop().and_then(|_| unsafe { __state.assume_init().retval }) }}; - ( | userdata $( : *mut c_void )? ; $($arg_name:ident : $arg_type:ty),* | -> $ret:ty $body:block, $callback:expr $(,)? ) => {{ + ( | $($arg_name:ident : $arg_type:ty),* ; $userdata:ident : $userdata_type:ty $(,)? | -> $ret:ty $body:block, $callback:expr $(,)? ) => {{ + type __UserData = $crate::util::wrap::UserData<$userdata_type, $ret>; // 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 __captured_fn( $( $arg_name: $arg_type ),*, $userdata: &mut __UserData) -> $ret $body unsafe extern "C" fn __wrapper_callback( - userdata: *mut ::std::os::raw::c_void, $( $arg_name: $arg_type, )* + $userdata: *mut ::std::ffi::c_void, ) { - let result = unsafe { &mut *(userdata as *mut $ret) }; + unsafe { + let ud = &mut *($userdata as *mut __UserData); + let stored_retval = &raw mut ud.retval; - *result = __captured_fn( - userdata, - $( - $arg_name, - )* - ); + let retval = __captured_fn( + $( + $arg_name, + )* + ud, + ); + + stored_retval.write(retval) + } } - // 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(); + // $crate::util::wrap::__nix_callback!($userdata_type, $ret, $callback) + let mut __ctx = $crate::errors::ErrorContext::new(); + let mut __state: ::std::mem::MaybeUninit< + __UserData + > = ::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() }) + $callback(__wrapper_callback, __state.as_mut_ptr(), &__ctx); + + // add type annotations for compiler + let __return: $ret = __ctx.pop().and_then(|_| unsafe { __state.assume_init().retval }); + __return }}; } 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 - ) +// XXX: TODO: convert these to declarative macros +macro_rules! nix_string_callback { + ($callback:expr $(,)?) => {{ + $crate::util::wrap::nix_callback!( + |start: *const ::std::ffi::c_char, n: ::std::ffi::c_uint ; userdata: ()| -> $crate::NixideResult { + start.to_utf8_string_n(n as usize) + }, + $callback + ) + }}; } +pub(crate) use nix_string_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) +macro_rules! nix_pathbuf_callback { + ($callback:expr $(,)?) => {{ + $crate::util::wrap::nix_string_callback!($callback).map(::std::path::PathBuf::from) + }}; } +pub(crate) use nix_pathbuf_callback;