nix_bindings_util/
settings.rs

1use anyhow::Result;
2use nix_bindings_util_sys as raw;
3use std::sync::Mutex;
4
5use crate::{
6    check_call, context, result_string_init,
7    string_return::{callback_get_result_string, callback_get_result_string_data},
8};
9
10// Global mutex to protect concurrent access to Nix settings
11// See the documentation on `set()` for important thread safety information.
12static SETTINGS_MUTEX: Mutex<()> = Mutex::new(());
13
14/// Set a Nix setting.
15///
16/// # Thread Safety
17///
18/// This function uses a mutex to serialize access through the Rust API.
19/// However, the underlying Nix settings system uses global mutable state
20/// without internal synchronization.
21///
22/// The mutex provides protection between Rust callers but cannot prevent:
23/// - C++ Nix code from modifying settings concurrently
24/// - Other Nix operations from reading settings during modification
25///
26/// For multi-threaded applications, ensure that no other Nix operations
27/// are running while changing settings. Settings are best modified during
28/// single-threaded initialization.
29pub fn set(key: &str, value: &str) -> Result<()> {
30    // Lock the mutex to ensure thread-safe access to global settings
31    let guard = SETTINGS_MUTEX.lock().unwrap();
32
33    let mut ctx = context::Context::new();
34    let key = std::ffi::CString::new(key)?;
35    let value = std::ffi::CString::new(value)?;
36    unsafe {
37        check_call!(raw::setting_set(&mut ctx, key.as_ptr(), value.as_ptr()))?;
38    }
39    drop(guard);
40    Ok(())
41}
42
43/// Get a Nix setting.
44///
45/// # Thread Safety
46///
47/// See the documentation on [`set()`] for important thread safety information.
48pub fn get(key: &str) -> Result<String> {
49    // Lock the mutex to ensure thread-safe access to global settings
50    let guard = SETTINGS_MUTEX.lock().unwrap();
51
52    let mut ctx = context::Context::new();
53    let key = std::ffi::CString::new(key)?;
54    let mut r: Result<String> = result_string_init!();
55    unsafe {
56        check_call!(raw::setting_get(
57            &mut ctx,
58            key.as_ptr(),
59            Some(callback_get_result_string),
60            callback_get_result_string_data(&mut r)
61        ))?;
62    }
63    drop(guard);
64    r
65}
66
67#[cfg(test)]
68mod tests {
69    use crate::check_call;
70
71    use super::*;
72
73    #[ctor::ctor]
74    fn setup() {
75        let mut ctx = context::Context::new();
76        unsafe {
77            check_call!(nix_bindings_util_sys::libutil_init(&mut ctx)).unwrap();
78        }
79    }
80
81    #[test]
82    fn set_get() {
83        // Something that shouldn't matter if it's a different value temporarily
84        let key = "json-log-path";
85
86        // Save the old value, in case it's important. Probably not.
87        // If this doesn't work, pick a different setting to test with
88        let old_value = get(key).unwrap();
89
90        let new_value = "/just/a/path/that/we/are/storing/into/some/option/for/testing/purposes";
91
92        let res_e = (|| {
93            set(key, new_value)?;
94            get(key)
95        })();
96
97        // Restore immediately; try not to affect other tests (if relevant).
98        set(key, old_value.as_str()).unwrap();
99
100        let res = res_e.unwrap();
101
102        assert_eq!(res, new_value);
103    }
104}