maint: Move to /rust/* to /
This makes it easier for tooling to find the Rust stuff. Rust/non-rust is not a useful distinction in this repo anymore anyway.
This commit is contained in:
parent
8c64b5e380
commit
55eacf43c3
33 changed files with 5 additions and 5 deletions
158
nix-bindings-util/src/context.rs
Normal file
158
nix-bindings-util/src/context.rs
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
use anyhow::{bail, Result};
|
||||
use nix_bindings_bindgen_raw as raw;
|
||||
use std::os::raw::c_char;
|
||||
use std::ptr::null_mut;
|
||||
use std::ptr::NonNull;
|
||||
|
||||
/// A context for error handling, when interacting directly with the generated bindings for the C API in [nix_bindings_bindgen_raw].
|
||||
///
|
||||
/// 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].
|
||||
pub struct Context {
|
||||
inner: NonNull<raw::c_context>,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
pub fn new() -> Self {
|
||||
let ctx = unsafe { raw::c_context_create() };
|
||||
if ctx.is_null() {
|
||||
// We've failed to allocate a (relatively small) Context struct.
|
||||
// We're almost certainly going to crash anyways.
|
||||
panic!("nix_c_context_create returned a null pointer");
|
||||
}
|
||||
Context {
|
||||
inner: NonNull::new(ctx).unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Access the C context pointer.
|
||||
///
|
||||
/// We recommend to use `check_call!` if possible.
|
||||
pub fn ptr(&mut self) -> *mut raw::c_context {
|
||||
self.inner.as_ptr()
|
||||
}
|
||||
|
||||
/// Check the error code and return an error if it's not `NIX_OK`.
|
||||
///
|
||||
/// We recommend to use `check_call!` if possible.
|
||||
pub fn check_err(&self) -> Result<()> {
|
||||
let err = unsafe { raw::err_code(self.inner.as_ptr()) };
|
||||
if err != raw::err_NIX_OK {
|
||||
// msgp is a borrowed pointer (pointing into the context), so we don't need to free it
|
||||
let msgp = unsafe { raw::err_msg(null_mut(), self.inner.as_ptr(), null_mut()) };
|
||||
// Turn the i8 pointer into a Rust string by copying
|
||||
let msg: &str = unsafe { core::ffi::CStr::from_ptr(msgp).to_str()? };
|
||||
bail!("{}", msg);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
unsafe {
|
||||
raw::set_err_msg(
|
||||
self.inner.as_ptr(),
|
||||
raw::err_NIX_OK,
|
||||
b"\0".as_ptr() as *const c_char,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_err_and_clear(&mut self) -> Result<()> {
|
||||
let r = self.check_err();
|
||||
if r.is_err() {
|
||||
self.clear();
|
||||
}
|
||||
r
|
||||
}
|
||||
|
||||
pub fn check_one_call_or_key_none<T, F: FnOnce(*mut raw::c_context) -> T>(
|
||||
&mut self,
|
||||
f: F,
|
||||
) -> Result<Option<T>> {
|
||||
let t = f(self.ptr());
|
||||
if unsafe { raw::err_code(self.inner.as_ptr()) == raw::err_NIX_ERR_KEY } {
|
||||
self.clear();
|
||||
return Ok(None);
|
||||
}
|
||||
self.check_err_and_clear()?;
|
||||
Ok(Some(t))
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Context {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
raw::c_context_free(self.inner.as_ptr());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! check_call {
|
||||
($($f:ident)::+($ctx:expr $(, $arg:expr)*)) => {
|
||||
{
|
||||
let ctx : &mut $crate::context::Context = $ctx;
|
||||
let ret = $($f)::*(ctx.ptr() $(, $arg)*);
|
||||
match ctx.check_err() {
|
||||
Ok(_) => Ok(ret),
|
||||
Err(e) => {
|
||||
ctx.clear();
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub use check_call;
|
||||
|
||||
// TODO: Generalize this macro to work with any error code or any error handling logic
|
||||
#[macro_export]
|
||||
macro_rules! check_call_opt_key {
|
||||
($($f:ident)::+($ctx:expr, $($arg:expr),*)) => {
|
||||
{
|
||||
let ctx : &mut $crate::context::Context = $ctx;
|
||||
let ret = $($f)::*(ctx.ptr(), $($arg,)*);
|
||||
if unsafe { raw::err_code(ctx.ptr()) == raw::err_NIX_ERR_KEY } {
|
||||
ctx.clear();
|
||||
return Ok(None);
|
||||
}
|
||||
match ctx.check_err() {
|
||||
Ok(_) => Ok(Some(ret)),
|
||||
Err(e) => {
|
||||
ctx.clear();
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub use check_call_opt_key;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn context_new_and_drop() {
|
||||
// don't crash
|
||||
let _c = Context::new();
|
||||
}
|
||||
|
||||
fn set_dummy_err(ctx_ptr: *mut raw::c_context) {
|
||||
unsafe {
|
||||
raw::set_err_msg(
|
||||
ctx_ptr,
|
||||
raw::err_NIX_ERR_UNKNOWN.try_into().unwrap(),
|
||||
b"dummy error message\0".as_ptr() as *const i8,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_call_dynamic_context() {
|
||||
let r = check_call!(set_dummy_err(&mut Context::new()));
|
||||
assert!(r.is_err());
|
||||
assert_eq!(r.unwrap_err().to_string(), "dummy error message");
|
||||
}
|
||||
}
|
||||
4
nix-bindings-util/src/lib.rs
Normal file
4
nix-bindings-util/src/lib.rs
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
pub mod context;
|
||||
pub mod settings;
|
||||
#[macro_use]
|
||||
pub mod string_return;
|
||||
104
nix-bindings-util/src/settings.rs
Normal file
104
nix-bindings-util/src/settings.rs
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
use anyhow::Result;
|
||||
use nix_bindings_bindgen_raw as raw;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use crate::{
|
||||
check_call, context, result_string_init,
|
||||
string_return::{callback_get_result_string, callback_get_result_string_data},
|
||||
};
|
||||
|
||||
// Global mutex to protect concurrent access to Nix settings
|
||||
// See the documentation on `set()` for important thread safety information.
|
||||
static SETTINGS_MUTEX: Mutex<()> = Mutex::new(());
|
||||
|
||||
/// Set a Nix setting.
|
||||
///
|
||||
/// # Thread Safety
|
||||
///
|
||||
/// This function uses a mutex to serialize access through the Rust API.
|
||||
/// However, the underlying Nix settings system uses global mutable state
|
||||
/// without internal synchronization.
|
||||
///
|
||||
/// The mutex provides protection between Rust callers but cannot prevent:
|
||||
/// - C++ Nix code from modifying settings concurrently
|
||||
/// - Other Nix operations from reading settings during modification
|
||||
///
|
||||
/// For multi-threaded applications, ensure that no other Nix operations
|
||||
/// are running while changing settings. Settings are best modified during
|
||||
/// single-threaded initialization.
|
||||
pub fn set(key: &str, value: &str) -> Result<()> {
|
||||
// Lock the mutex to ensure thread-safe access to global settings
|
||||
let guard = SETTINGS_MUTEX.lock().unwrap();
|
||||
|
||||
let mut ctx = context::Context::new();
|
||||
let key = std::ffi::CString::new(key)?;
|
||||
let value = std::ffi::CString::new(value)?;
|
||||
unsafe {
|
||||
check_call!(raw::setting_set(&mut ctx, key.as_ptr(), value.as_ptr()))?;
|
||||
}
|
||||
drop(guard);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get a Nix setting.
|
||||
///
|
||||
/// # Thread Safety
|
||||
///
|
||||
/// See the documentation on [`set()`] for important thread safety information.
|
||||
pub fn get(key: &str) -> Result<String> {
|
||||
// Lock the mutex to ensure thread-safe access to global settings
|
||||
let guard = SETTINGS_MUTEX.lock().unwrap();
|
||||
|
||||
let mut ctx = context::Context::new();
|
||||
let key = std::ffi::CString::new(key)?;
|
||||
let mut r: Result<String> = result_string_init!();
|
||||
unsafe {
|
||||
check_call!(raw::setting_get(
|
||||
&mut ctx,
|
||||
key.as_ptr(),
|
||||
Some(callback_get_result_string),
|
||||
callback_get_result_string_data(&mut r)
|
||||
))?;
|
||||
}
|
||||
drop(guard);
|
||||
r
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::check_call;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[ctor::ctor]
|
||||
fn setup() {
|
||||
let mut ctx = context::Context::new();
|
||||
unsafe {
|
||||
check_call!(raw::libstore_init(&mut ctx)).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_get() {
|
||||
// Something that shouldn't matter if it's a different value temporarily
|
||||
let key = "user-agent-suffix";
|
||||
|
||||
// Save the old value, in case it's important. Probably not.
|
||||
// If this doesn't work, pick a different setting to test with
|
||||
let old_value = get(key).unwrap();
|
||||
|
||||
let new_value = "just a string that we're storing into some option for testing purposes";
|
||||
|
||||
let res_e = (|| {
|
||||
set(key, new_value)?;
|
||||
get(key)
|
||||
})();
|
||||
|
||||
// Restore immediately; try not to affect other tests (if relevant).
|
||||
set(key, old_value.as_str()).unwrap();
|
||||
|
||||
let res = res_e.unwrap();
|
||||
|
||||
assert_eq!(res, new_value);
|
||||
}
|
||||
}
|
||||
88
nix-bindings-util/src/string_return.rs
Normal file
88
nix-bindings-util/src/string_return.rs
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
use anyhow::Result;
|
||||
|
||||
/// Callback for nix_store_get_uri and other functions that return a string.
|
||||
///
|
||||
/// This function is used by the other nix_* crates, and you should never need to call it yourself.
|
||||
///
|
||||
/// Some functions in the nix library "return" strings without giving you ownership over them, by letting you pass a callback function that gets to look at that string. This callback simply turns that string pointer into an owned rust String.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// _Manual memory management_
|
||||
///
|
||||
/// Only for passing to the nix C API. Do not call this function directly.
|
||||
pub unsafe extern "C" fn callback_get_result_string(
|
||||
start: *const ::std::os::raw::c_char,
|
||||
n: std::os::raw::c_uint,
|
||||
user_data: *mut std::os::raw::c_void,
|
||||
) {
|
||||
let ret = user_data as *mut Result<String>;
|
||||
|
||||
if start.is_null() {
|
||||
if n != 0 {
|
||||
panic!("callback_get_result_string: start is null but n is not zero");
|
||||
}
|
||||
*ret = Ok(String::new());
|
||||
return;
|
||||
}
|
||||
|
||||
let slice = std::slice::from_raw_parts(start as *const u8, n as usize);
|
||||
|
||||
if (*ret).is_ok() {
|
||||
panic!(
|
||||
"callback_get_result_string: Result must be initialized to Err. Did Nix call us twice?"
|
||||
);
|
||||
}
|
||||
|
||||
*ret = String::from_utf8(slice.to_vec())
|
||||
.map_err(|e| anyhow::format_err!("Nix string is not valid UTF-8: {}", e));
|
||||
}
|
||||
|
||||
pub fn callback_get_result_string_data(vec: &mut Result<String>) -> *mut std::os::raw::c_void {
|
||||
vec as *mut Result<String> as *mut std::os::raw::c_void
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! result_string_init {
|
||||
() => {
|
||||
Err(anyhow::anyhow!("String was not set by Nix C API"))
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use nix_bindings_bindgen_raw as raw;
|
||||
|
||||
/// Typecheck the function signature against the generated bindings in nix_bindings_bindgen_raw.
|
||||
static _CALLBACK_GET_RESULT_STRING: raw::get_string_callback = Some(callback_get_result_string);
|
||||
|
||||
#[test]
|
||||
fn test_callback_get_result_string_empty() {
|
||||
let mut ret: Result<String> = result_string_init!();
|
||||
let start: *const std::os::raw::c_char = std::ptr::null();
|
||||
let n: std::os::raw::c_uint = 0;
|
||||
let user_data: *mut std::os::raw::c_void = callback_get_result_string_data(&mut ret);
|
||||
|
||||
unsafe {
|
||||
callback_get_result_string(start, n, user_data);
|
||||
}
|
||||
|
||||
let s = ret.unwrap();
|
||||
assert_eq!(s, "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_callback_result_string() {
|
||||
let mut ret: Result<String> = result_string_init!();
|
||||
let start: *const std::os::raw::c_char = b"helloGARBAGE".as_ptr() as *const i8;
|
||||
let n: std::os::raw::c_uint = 5;
|
||||
let user_data: *mut std::os::raw::c_void = callback_get_result_string_data(&mut ret);
|
||||
unsafe {
|
||||
callback_get_result_string(start, n, user_data);
|
||||
}
|
||||
|
||||
let s = ret.unwrap();
|
||||
assert_eq!(s, "hello");
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue