use anyhow::{bail, Result}; use nix_bindings_util_sys as raw; use std::ptr::null_mut; use std::ptr::NonNull; /// A context for error handling, when interacting directly with the generated bindings for the C API in [nix_bindings_util_sys]. /// /// The `nix-store` and `nix-expr` libraries that consume this type internally store a private context in their `EvalState` and `Store` structs to avoid allocating a new context for each operation. The state of a context is irrelevant when used correctly (e.g. with [check_call!]), so it's safe to reuse, and safe to allocate more contexts in methods such as [Clone::clone]. pub struct Context { inner: NonNull, } impl Default for Context { fn default() -> Self { Self::new() } } impl Context { pub fn new() -> Self { let ctx = unsafe { raw::c_context_create() }; if ctx.is_null() { // We've failed to allocate a (relatively small) Context struct. // We're almost certainly going to crash anyways. panic!("nix_c_context_create returned a null pointer"); } Context { inner: NonNull::new(ctx).unwrap(), } } /// Access the C context pointer. /// /// We recommend to use `check_call!` if possible. pub fn ptr(&mut self) -> *mut raw::c_context { self.inner.as_ptr() } /// Check the error code and return an error if it's not `NIX_OK`. /// /// We recommend to use `check_call!` if possible. pub fn check_err(&self) -> Result<()> { let err = unsafe { raw::err_code(self.inner.as_ptr()) }; if err != raw::err_NIX_OK { // msgp is a borrowed pointer (pointing into the context), so we don't need to free it let msgp = unsafe { raw::err_msg(null_mut(), self.inner.as_ptr(), null_mut()) }; // Turn the i8 pointer into a Rust string by copying let msg: &str = unsafe { core::ffi::CStr::from_ptr(msgp).to_str()? }; bail!("{}", msg); } Ok(()) } pub fn clear(&mut self) { unsafe { raw::set_err_msg(self.inner.as_ptr(), raw::err_NIX_OK, c"".as_ptr()); } } pub fn check_err_and_clear(&mut self) -> Result<()> { let r = self.check_err(); if r.is_err() { self.clear(); } r } pub fn check_one_call_or_key_none T>( &mut self, f: F, ) -> Result> { let t = f(self.ptr()); if unsafe { raw::err_code(self.inner.as_ptr()) == raw::err_NIX_ERR_KEY } { self.clear(); return Ok(None); } self.check_err_and_clear()?; Ok(Some(t)) } } impl Drop for Context { fn drop(&mut self) { unsafe { raw::c_context_free(self.inner.as_ptr()); } } } #[macro_export] macro_rules! check_call { ($($f:ident)::+($ctx:expr $(, $arg:expr)*)) => { { let ctx : &mut $crate::context::Context = $ctx; let ret = $($f)::*(ctx.ptr() $(, $arg)*); match ctx.check_err() { Ok(_) => Ok(ret), Err(e) => { ctx.clear(); Err(e) } } } } } pub use check_call; // TODO: Generalize this macro to work with any error code or any error handling logic #[macro_export] macro_rules! check_call_opt_key { ($($f:ident)::+($ctx:expr, $($arg:expr),*)) => { { let ctx : &mut $crate::context::Context = $ctx; let ret = $($f)::*(ctx.ptr(), $($arg,)*); if unsafe { raw::err_code(ctx.ptr()) == raw::err_NIX_ERR_KEY } { ctx.clear(); return Ok(None); } match ctx.check_err() { Ok(_) => Ok(Some(ret)), Err(e) => { ctx.clear(); Err(e) } } } } } pub use check_call_opt_key; #[cfg(test)] mod tests { use super::*; #[test] fn context_new_and_drop() { // don't crash let _c = Context::new(); } fn set_dummy_err(ctx_ptr: *mut raw::c_context) { unsafe { raw::set_err_msg( ctx_ptr, raw::err_NIX_ERR_UNKNOWN, c"dummy error message".as_ptr(), ); } } #[test] fn check_call_dynamic_context() { let r = check_call!(set_dummy_err(&mut Context::new())); assert!(r.is_err()); assert_eq!(r.unwrap_err().to_string(), "dummy error message"); } }