nix_bindings_util/
context.rs

1use anyhow::{bail, Result};
2use nix_bindings_util_sys as raw;
3use std::ptr::null_mut;
4use std::ptr::NonNull;
5
6/// A context for error handling, when interacting directly with the generated bindings for the C API in [nix_bindings_util_sys].
7///
8/// 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].
9pub struct Context {
10    inner: NonNull<raw::c_context>,
11}
12
13impl Default for Context {
14    fn default() -> Self {
15        Self::new()
16    }
17}
18
19impl Context {
20    pub fn new() -> Self {
21        let ctx = unsafe { raw::c_context_create() };
22        if ctx.is_null() {
23            // We've failed to allocate a (relatively small) Context struct.
24            // We're almost certainly going to crash anyways.
25            panic!("nix_c_context_create returned a null pointer");
26        }
27        Context {
28            inner: NonNull::new(ctx).unwrap(),
29        }
30    }
31
32    /// Access the C context pointer.
33    ///
34    /// We recommend to use `check_call!` if possible.
35    pub fn ptr(&mut self) -> *mut raw::c_context {
36        self.inner.as_ptr()
37    }
38
39    /// Check the error code and return an error if it's not `NIX_OK`.
40    ///
41    /// We recommend to use `check_call!` if possible.
42    pub fn check_err(&self) -> Result<()> {
43        let err = unsafe { raw::err_code(self.inner.as_ptr()) };
44        if err != raw::err_NIX_OK {
45            // msgp is a borrowed pointer (pointing into the context), so we don't need to free it
46            let msgp = unsafe { raw::err_msg(null_mut(), self.inner.as_ptr(), null_mut()) };
47            // Turn the i8 pointer into a Rust string by copying
48            let msg: &str = unsafe { core::ffi::CStr::from_ptr(msgp).to_str()? };
49            bail!("{}", msg);
50        }
51        Ok(())
52    }
53
54    pub fn clear(&mut self) {
55        unsafe {
56            raw::set_err_msg(self.inner.as_ptr(), raw::err_NIX_OK, c"".as_ptr());
57        }
58    }
59
60    pub fn check_err_and_clear(&mut self) -> Result<()> {
61        let r = self.check_err();
62        if r.is_err() {
63            self.clear();
64        }
65        r
66    }
67
68    pub fn check_one_call_or_key_none<T, F: FnOnce(*mut raw::c_context) -> T>(
69        &mut self,
70        f: F,
71    ) -> Result<Option<T>> {
72        let t = f(self.ptr());
73        if unsafe { raw::err_code(self.inner.as_ptr()) == raw::err_NIX_ERR_KEY } {
74            self.clear();
75            return Ok(None);
76        }
77        self.check_err_and_clear()?;
78        Ok(Some(t))
79    }
80}
81
82impl Drop for Context {
83    fn drop(&mut self) {
84        unsafe {
85            raw::c_context_free(self.inner.as_ptr());
86        }
87    }
88}
89
90#[macro_export]
91macro_rules! check_call {
92    ($($f:ident)::+($ctx:expr $(, $arg:expr)*)) => {
93        {
94            let ctx : &mut $crate::context::Context = $ctx;
95            let ret = $($f)::*(ctx.ptr() $(, $arg)*);
96            match ctx.check_err() {
97                Ok(_) => Ok(ret),
98                Err(e) => {
99                    ctx.clear();
100                    Err(e)
101                }
102            }
103        }
104    }
105}
106
107pub use check_call;
108
109// TODO: Generalize this macro to work with any error code or any error handling logic
110#[macro_export]
111macro_rules! check_call_opt_key {
112    ($($f:ident)::+($ctx:expr, $($arg:expr),*)) => {
113        {
114            let ctx : &mut $crate::context::Context = $ctx;
115            let ret = $($f)::*(ctx.ptr(), $($arg,)*);
116            if unsafe { $crate::raw_sys::err_code(ctx.ptr()) == $crate::raw_sys::err_NIX_ERR_KEY } {
117                ctx.clear();
118                return Ok(None);
119            }
120            match ctx.check_err() {
121                Ok(_) => Ok(Some(ret)),
122                Err(e) => {
123                    ctx.clear();
124                    Err(e)
125                }
126            }
127        }
128    }
129}
130
131pub use check_call_opt_key;
132
133#[cfg(test)]
134mod tests {
135    use super::*;
136
137    #[test]
138    fn context_new_and_drop() {
139        // don't crash
140        let _c = Context::new();
141    }
142
143    fn set_dummy_err(ctx_ptr: *mut raw::c_context) {
144        unsafe {
145            raw::set_err_msg(
146                ctx_ptr,
147                raw::err_NIX_ERR_UNKNOWN,
148                c"dummy error message".as_ptr(),
149            );
150        }
151    }
152
153    #[test]
154    fn check_call_dynamic_context() {
155        let r = check_call!(set_dummy_err(&mut Context::new()));
156        assert!(r.is_err());
157        assert_eq!(r.unwrap_err().to_string(), "dummy error message");
158    }
159}