nix_bindings_expr/
primop.rs1use crate::eval_state::{EvalState, EvalStateWeak};
2use crate::value::Value;
3use anyhow::Result;
4use nix_bindings_expr_sys as raw;
5use nix_bindings_util::check_call;
6use nix_bindings_util_sys as raw_util;
7use std::ffi::{c_int, c_void, CStr, CString};
8use std::mem::ManuallyDrop;
9use std::ptr::{null, null_mut};
10
11#[derive(Debug)]
24pub struct RecoverableError(String);
25
26impl RecoverableError {
27 pub fn new(msg: impl Into<String>) -> Self {
28 RecoverableError(msg.into())
29 }
30}
31
32impl std::fmt::Display for RecoverableError {
33 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34 self.0.fmt(f)
35 }
36}
37
38impl std::error::Error for RecoverableError {}
39
40pub struct PrimOpMeta<'a, const N: usize> {
42 pub name: &'a CStr,
46
47 pub doc: &'a CStr,
50
51 pub args: [&'a CStr; N],
55}
56
57pub struct PrimOp {
58 pub(crate) ptr: *mut raw::PrimOp,
59}
60impl Drop for PrimOp {
61 fn drop(&mut self) {
62 unsafe {
63 raw::gc_decref(null_mut(), self.ptr as *mut c_void);
64 }
65 }
66}
67impl PrimOp {
68 pub fn new<const N: usize>(
74 eval_state: &mut EvalState,
75 meta: PrimOpMeta<N>,
76 f: Box<dyn Fn(&mut EvalState, &[Value; N]) -> Result<Value>>,
77 ) -> Result<PrimOp> {
78 assert!(N != 0);
79
80 let mut args = Vec::new();
81 for arg in meta.args {
82 args.push(arg.as_ptr());
83 }
84 args.push(null());
85
86 let user_data = {
89 let user_data = ManuallyDrop::new(Box::new(PrimOpContext {
92 arity: N,
93 function: Box::new(move |eval_state, args| f(eval_state, args.try_into().unwrap())),
94 eval_state: eval_state.weak_ref(),
95 }));
96 user_data.as_ref() as *const PrimOpContext as *mut c_void
97 };
98 let op = unsafe {
99 check_call!(raw::alloc_primop(
100 &mut eval_state.context,
101 FUNCTION_ADAPTER,
102 N as c_int,
103 meta.name.as_ptr(),
104 args.as_mut_ptr(), meta.doc.as_ptr(),
106 user_data
107 ))?
108 };
109 Ok(PrimOp { ptr: op })
110 }
111}
112
113struct PrimOpContext {
115 arity: usize,
116 function: Box<dyn Fn(&mut EvalState, &[Value]) -> Result<Value>>,
117 eval_state: EvalStateWeak,
118}
119
120unsafe extern "C" fn function_adapter(
121 user_data: *mut ::std::os::raw::c_void,
122 context_out: *mut raw_util::c_context,
123 _state: *mut raw::EvalState,
124 args: *mut *mut raw::Value,
125 ret: *mut raw::Value,
126) {
127 let primop_info = (user_data as *const PrimOpContext).as_ref().unwrap();
128 let mut eval_state = primop_info.eval_state.upgrade().unwrap_or_else(|| {
129 panic!("Nix primop called after EvalState was dropped");
130 });
131 let args_raw_slice = unsafe { std::slice::from_raw_parts(args, primop_info.arity) };
132 let args_vec: Vec<Value> = args_raw_slice
133 .iter()
134 .map(|v| Value::new_borrowed(*v))
135 .collect();
136 let args_slice = args_vec.as_slice();
137
138 let r = primop_info.function.as_ref()(&mut eval_state, args_slice);
139
140 match r {
141 Ok(v) => unsafe {
142 raw::copy_value(context_out, ret, v.raw_ptr());
143 },
144 Err(e) => unsafe {
145 let err_code = error_code(&e);
146 let cstr = CString::new(e.to_string()).unwrap_or_else(|_e| {
147 CString::new("<rust nix-expr application error message contained null byte>")
148 .unwrap()
149 });
150 raw_util::set_err_msg(context_out, err_code, cstr.as_ptr());
151 },
152 }
153}
154
155fn error_code(e: &anyhow::Error) -> raw_util::err {
156 #[cfg(nix_at_least = "2.34.0pre")]
157 if e.downcast_ref::<RecoverableError>().is_some() {
158 return raw_util::err_NIX_ERR_RECOVERABLE;
159 }
160 raw_util::err_NIX_ERR_UNKNOWN
161}
162
163static FUNCTION_ADAPTER: raw::PrimOpFun = Some(function_adapter);