Skip to main content

nix_bindings/
context.rs

1//! [`Context`] (Nix C-API context handle), [`Verbosity`], and free
2//! helpers that read process-global Nix state.
3
4#![cfg(feature = "store")]
5
6use std::{
7  ffi::{CStr, CString},
8  ptr::NonNull,
9};
10
11use crate::{
12  Error,
13  Result,
14  error::{check_err, string_from_callback},
15  sys,
16};
17
18/// Return the version of the Nix library being used.
19///
20/// This is a free function that does not require a context.
21#[must_use]
22pub fn nix_version() -> &'static str {
23  // SAFETY: nix_version_get returns a pointer to a static string literal
24  unsafe {
25    let ptr = sys::nix_version_get();
26    if ptr.is_null() {
27      "<unknown>"
28    } else {
29      CStr::from_ptr(ptr).to_str().unwrap_or("<unknown>")
30    }
31  }
32}
33
34/// Returns `true` when Nix is running in pure evaluation mode (`--pure-eval`).
35///
36/// Reads the global `pure-eval` Nix setting. The result reflects the setting
37/// at call time and is stable for the lifetime of a single Nix evaluation.
38///
39/// # Use in primops
40///
41/// [`primop::PrimOpRet::set_path`](crate::primop::PrimOpRet::set_path) and
42/// [`primop::PrimOpRet::make_path`](crate::primop::PrimOpRet::make_path)
43/// call `nix_init_path_string` internally, which the Nix evaluator rejects
44/// for absolute paths in pure mode. Check this function first so your
45/// primop can make the right call: return an explicit error, use a string,
46/// or refuse to run at all.
47pub fn is_pure_eval() -> bool {
48  // SAFETY: nix_setting_get is safe with a null context; errors are silently
49  // ignored, and a missing key means the default (false).
50  unsafe {
51    let val = string_from_callback(|cb, ud| {
52      sys::nix_setting_get(std::ptr::null_mut(), c"pure-eval".as_ptr(), cb, ud);
53    });
54    val.as_deref() == Some("true")
55  }
56}
57
58/// Verbosity level for Nix log output.
59#[derive(Debug, Clone, Copy, PartialEq, Eq)]
60pub enum Verbosity {
61  /// Only errors.
62  Error,
63  /// Warnings and errors.
64  Warn,
65  /// Notices, warnings, and errors.
66  Notice,
67  /// Info messages.
68  Info,
69  /// Talkative output.
70  Talkative,
71  /// Chatty output.
72  Chatty,
73  /// Debug output.
74  Debug,
75  /// Maximum verbosity (vomit).
76  Vomit,
77}
78
79impl Verbosity {
80  fn to_c(self) -> sys::nix_verbosity {
81    match self {
82      Verbosity::Error => sys::nix_verbosity_NIX_LVL_ERROR,
83      Verbosity::Warn => sys::nix_verbosity_NIX_LVL_WARN,
84      Verbosity::Notice => sys::nix_verbosity_NIX_LVL_NOTICE,
85      Verbosity::Info => sys::nix_verbosity_NIX_LVL_INFO,
86      Verbosity::Talkative => sys::nix_verbosity_NIX_LVL_TALKATIVE,
87      Verbosity::Chatty => sys::nix_verbosity_NIX_LVL_CHATTY,
88      Verbosity::Debug => sys::nix_verbosity_NIX_LVL_DEBUG,
89      Verbosity::Vomit => sys::nix_verbosity_NIX_LVL_VOMIT,
90    }
91  }
92}
93
94/// Nix context for managing library state.
95///
96/// This is the root object for all Nix operations. It manages the lifetime
97/// of the Nix C API context and provides automatic cleanup.
98pub struct Context {
99  inner: NonNull<sys::nix_c_context>,
100}
101
102impl Context {
103  /// Create a new Nix context.
104  ///
105  /// This initializes the Nix C API context and the required libraries.
106  ///
107  /// # Errors
108  ///
109  /// Returns an error if context creation or library initialization fails.
110  pub fn new() -> Result<Self> {
111    Self::new_inner(false)
112  }
113
114  /// Create a new Nix context without loading user/environment
115  /// configuration (`nix.conf`, `NIX_*` env vars).
116  ///
117  /// Equivalent to [`new`](Self::new) but uses
118  /// `nix_libstore_init_no_load_config` so the store is initialized in a
119  /// deterministic state. Useful in tests and sandboxed callers.
120  ///
121  /// Note: this affects only the *first* context created in the process;
122  /// subsequent calls (including [`new`](Self::new)) hit the one-shot
123  /// init gate and reuse whatever mode was selected first.
124  ///
125  /// # Errors
126  ///
127  /// Returns an error if context creation or library initialization fails.
128  pub fn new_no_load_config() -> Result<Self> {
129    Self::new_inner(true)
130  }
131
132  fn new_inner(no_load_config: bool) -> Result<Self> {
133    // SAFETY: nix_c_context_create is safe to call
134    let ctx_ptr = unsafe { sys::nix_c_context_create() };
135    let inner = NonNull::new(ctx_ptr).ok_or(Error::NullPointer)?;
136
137    let ctx = Context { inner };
138
139    // The nix_lib*_init functions are documented as one-shot. Reinvoking
140    // them across Contexts is a waste at best and a race at worst; the
141    // Once gate ensures exactly one initialization per process.
142    static INIT: std::sync::Once = std::sync::Once::new();
143    let mut init_err: Result<()> = Ok(());
144    INIT.call_once(|| {
145      // SAFETY: nix_lib*_init are safe with a fresh context.
146      let r = (|| unsafe {
147        check_err(
148          ctx.inner.as_ptr(),
149          sys::nix_libutil_init(ctx.inner.as_ptr()),
150        )?;
151        let store_err = if no_load_config {
152          sys::nix_libstore_init_no_load_config(ctx.inner.as_ptr())
153        } else {
154          sys::nix_libstore_init(ctx.inner.as_ptr())
155        };
156        check_err(ctx.inner.as_ptr(), store_err)?;
157        check_err(
158          ctx.inner.as_ptr(),
159          sys::nix_libexpr_init(ctx.inner.as_ptr()),
160        )
161      })();
162      init_err = r;
163    });
164    init_err?;
165
166    Ok(ctx)
167  }
168
169  /// Set a global Nix configuration setting.
170  ///
171  /// Settings take effect for new [`EvalState`](crate::EvalState) instances.
172  /// Use `"extra-<name>"` to append to an existing setting's value.
173  ///
174  /// # Errors
175  ///
176  /// Returns [`Error::KeyNotFound`] if the setting key is unknown.
177  pub fn set_setting(&self, key: &str, value: &str) -> Result<()> {
178    let key_c = CString::new(key)?;
179    let value_c = CString::new(value)?;
180    // SAFETY: context and strings are valid
181    unsafe {
182      check_err(
183        self.inner.as_ptr(),
184        sys::nix_setting_set(
185          self.inner.as_ptr(),
186          key_c.as_ptr(),
187          value_c.as_ptr(),
188        ),
189      )
190    }
191  }
192
193  /// Get the value of a global Nix configuration setting.
194  ///
195  /// # Errors
196  ///
197  /// Returns [`Error::KeyNotFound`] if the setting key is unknown.
198  pub fn get_setting(&self, key: &str) -> Result<String> {
199    let key_c = CString::new(key)?;
200    let mut err_code = sys::nix_err_NIX_OK;
201    // SAFETY: context and key are valid
202    let result = unsafe {
203      string_from_callback(|cb, ud| {
204        err_code =
205          sys::nix_setting_get(self.inner.as_ptr(), key_c.as_ptr(), cb, ud);
206      })
207    };
208    check_err(self.inner.as_ptr(), err_code)?;
209    result.ok_or_else(|| Error::KeyNotFound(key.to_string()))
210  }
211
212  /// Set the verbosity level for Nix log output.
213  ///
214  /// Note: Nix's verbosity is **process-global**; the `&self` receiver is
215  /// for ergonomic API consistency. Calling on any [`Context`] affects
216  /// every other context in the same process.
217  ///
218  /// # Errors
219  ///
220  /// Returns an error if the verbosity level cannot be set.
221  pub fn set_verbosity(&self, level: Verbosity) -> Result<()> {
222    // SAFETY: context is valid
223    unsafe {
224      check_err(
225        self.inner.as_ptr(),
226        sys::nix_set_verbosity(self.inner.as_ptr(), level.to_c()),
227      )
228    }
229  }
230
231  /// Clear any error state currently stored on the context.
232  ///
233  /// Nix's C API parks the last error message and code on the context.
234  /// Code paths that signal failure by returning a null pointer (e.g.
235  /// [`Value::get_attr`](crate::Value::get_attr)) inspect that buffer to
236  /// decide whether the null was a genuine failure or just an absent
237  /// value, so a stale message from an earlier unrelated call can confuse
238  /// them. Call this between recoverable operations to reset the buffer.
239  pub fn clear_error(&self) {
240    // SAFETY: context is valid for the lifetime of self.
241    unsafe { sys::nix_clear_err(self.inner.as_ptr()) };
242  }
243
244  /// Load Nix's configured plugins.
245  ///
246  /// Reads the `plugin-files` setting and loads each entry. Must be called
247  /// before any [`EvalState`](crate::EvalState) is built for plugins to be
248  /// visible.
249  ///
250  /// # Errors
251  ///
252  /// Returns an error if plugin loading fails.
253  #[cfg(feature = "main")]
254  pub fn init_plugins(&self) -> Result<()> {
255    // SAFETY: context is valid
256    unsafe {
257      check_err(
258        self.inner.as_ptr(),
259        sys::nix_init_plugins(self.inner.as_ptr()),
260      )
261    }
262  }
263
264  /// Set the log format Nix uses when writing log messages.
265  ///
266  /// Format strings recognised by Nix include `"raw"`, `"internal-json"`,
267  /// `"bar"`, and `"bar-with-logs"`. Setting is process-global, see
268  /// [`set_verbosity`](Self::set_verbosity).
269  ///
270  /// # Errors
271  ///
272  /// Returns an error if the format is unrecognised or the underlying
273  /// call fails.
274  #[cfg(feature = "main")]
275  pub fn set_log_format(&self, format: &str) -> Result<()> {
276    let format_c = CString::new(format)?;
277    // SAFETY: context and format are valid
278    unsafe {
279      check_err(
280        self.inner.as_ptr(),
281        sys::nix_set_log_format(self.inner.as_ptr(), format_c.as_ptr()),
282      )
283    }
284  }
285
286  /// Run a Nix GC cycle now.
287  ///
288  /// Useful in tests and long-running workers that hold onto large value
289  /// graphs. Cheap to call repeatedly; Nix's GC is incremental.
290  #[cfg(feature = "expr")]
291  pub fn gc_now() {
292    // SAFETY: nix_gc_now takes no arguments.
293    unsafe { sys::nix_gc_now() };
294  }
295
296  /// Get the raw context pointer.
297  ///
298  /// # Safety
299  ///
300  /// The caller must ensure the pointer is used safely.
301  pub(crate) unsafe fn as_ptr(&self) -> *mut sys::nix_c_context {
302    self.inner.as_ptr()
303  }
304}
305
306impl Drop for Context {
307  fn drop(&mut self) {
308    // SAFETY: We own the context and it's valid until drop
309    unsafe {
310      sys::nix_c_context_free(self.inner.as_ptr());
311    }
312  }
313}
314
315// SAFETY: `nix_c_context` is exclusively-owned mutable state. Moving the
316// pointer to another thread is sound provided the source thread releases
317// ownership; the Box-like value semantics of `Context` already enforce
318// that at the type level. `Sync` is NOT implemented: every C entry point
319// rewrites the per-context error buffer, so two threads holding
320// `&Context` would race on that buffer with no synchronization.
321unsafe impl Send for Context {}