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 {}