use crate::{
capi::utils::{CError, CPointerType, CRoot, ErrorWrap, Leakable},
error::{self, Error, ErrorExt},
resolvers::{Resolver, ResolverBackend, ResolverFlags},
Root,
};
use std::{cmp, mem, ptr, sync::atomic::Ordering};
use libc::c_void;
use snafu::OptionExt;
trait CConfig: Default {
type Object;
type ObjectInner;
fn verify(ptr_type: CPointerType, ptr: *mut c_void) -> Result<&'static Self::Object, Error>;
fn fetch(&mut self, from: &Self::ObjectInner) -> Result<(), Error>;
fn apply(&self, to: &mut Self::ObjectInner) -> Result<(), Error>;
}
#[repr(u64)]
#[allow(non_camel_case_types, dead_code)]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum CResolver {
__PATHRS_INVALID_RESOLVER = 0,
PATHRS_KERNEL_RESOLVER = 0xF000,
PATHRS_EMULATED_RESOLVER = 0xF001,
}
impl From<ResolverBackend> for CResolver {
fn from(other: ResolverBackend) -> Self {
match other {
ResolverBackend::Kernel => CResolver::PATHRS_KERNEL_RESOLVER,
ResolverBackend::Emulated => CResolver::PATHRS_EMULATED_RESOLVER,
}
}
}
impl Into<ResolverBackend> for CResolver {
fn into(self) -> ResolverBackend {
match self {
CResolver::PATHRS_KERNEL_RESOLVER => ResolverBackend::Kernel,
CResolver::PATHRS_EMULATED_RESOLVER => ResolverBackend::Emulated,
_ => panic!("invalid resolver: {:?}", self),
}
}
}
#[repr(align(8), C)]
#[derive(Debug)]
pub struct CRootConfig {
pub resolver: CResolver,
pub flags: u64,
}
impl Default for CRootConfig {
fn default() -> Self {
Self {
resolver: CResolver::__PATHRS_INVALID_RESOLVER,
flags: 0,
}
}
}
impl CConfig for CRootConfig {
type Object = CRoot;
type ObjectInner = Root;
fn verify(ptr_type: CPointerType, ptr: *mut c_void) -> Result<&'static Self::Object, Error> {
assert!(ptr_type == CPointerType::PATHRS_ROOT);
ensure!(
!ptr.is_null(),
error::InvalidArgument {
name: "ptr",
description: "ptr must be non-NULL",
}
);
Ok(unsafe { &*(ptr as *const CRoot) })
}
fn fetch(&mut self, root: &Self::ObjectInner) -> Result<(), Error> {
*self = Self {
resolver: root.resolver.backend.into(),
flags: root.resolver.flags.bits(),
};
Ok(())
}
fn apply(&self, root: &mut Self::ObjectInner) -> Result<(), Error> {
root.resolver = Resolver {
backend: self.resolver.into(),
flags: ResolverFlags::from_bits(self.flags).context(error::InvalidArgument {
name: "pathrs_config_global_t.flags",
description: "must only contain valid flags",
})?,
};
Ok(())
}
}
#[repr(align(8), C)]
#[derive(Debug, Default)]
pub struct CGlobalConfig {
pub error_backtraces: bool,
pub __padding: [u8; 7],
}
impl CConfig for CGlobalConfig {
type Object = ();
type ObjectInner = ();
fn verify(ptr_type: CPointerType, ptr: *mut c_void) -> Result<&'static Self::Object, Error> {
assert!(ptr_type == CPointerType::PATHRS_NONE);
ensure!(
ptr.is_null(),
error::InvalidArgument {
name: "ptr",
description: "ptr must be NULL with PATHRS_NONE",
}
);
Ok(&())
}
fn fetch(&mut self, _ptr: &Self::ObjectInner) -> Result<(), Error> {
self.error_backtraces = error::BACKTRACES_ENABLED.load(Ordering::SeqCst);
Ok(())
}
fn apply(&self, _ptr: &mut Self::ObjectInner) -> Result<(), Error> {
error::BACKTRACES_ENABLED.store(self.error_backtraces, Ordering::SeqCst);
Ok(())
}
}
fn copy_struct_in<T: CConfig>(
dst: &mut T,
ptr: *const c_void,
ptr_size: usize,
) -> Result<(), Error> {
let lib_size = mem::size_of::<T>();
*dst = Default::default();
let copy = cmp::min(ptr_size, lib_size);
let rest = cmp::max(ptr_size, lib_size) - copy;
if ptr_size > lib_size {
let start_ptr = unsafe { (ptr as *const u8).add(copy) };
for i in 0..rest {
let val = unsafe { ptr::read_unaligned(start_ptr.add(i)) };
ensure!(val == 0, error::InvalidArgument {
name: "new_cfg_ptr",
description: format!("trailing non-zero bytes in struct -- library too old (lib_size={}) or broken calling code", lib_size),
});
}
}
unsafe { ptr::copy_nonoverlapping(ptr as *const u8, dst as *mut T as *mut u8, copy) };
Ok(())
}
fn copy_struct_out<T: CConfig>(src: &T, ptr: *mut c_void, ptr_size: usize) -> Result<(), Error> {
let lib_size = mem::size_of::<T>();
unsafe { ptr::write_bytes(ptr as *mut u8, 0, ptr_size) };
let copy = cmp::min(ptr_size, lib_size);
unsafe { ptr::copy_nonoverlapping(src as *const T as *const u8, ptr as *mut u8, copy) };
Ok(())
}
#[no_mangle]
pub extern "C" fn pathrs_configure(
ptr_type: CPointerType,
ptr: *mut c_void,
old_cfg_ptr: *mut c_void,
new_cfg_ptr: *const c_void,
cfg_size: usize,
) -> Option<&'static mut CError> {
let mut error: Option<Error> = None;
error.wrap((), move || {
match ptr_type {
CPointerType::PATHRS_NONE => {
let _ = CGlobalConfig::verify(ptr_type, ptr)?;
if !old_cfg_ptr.is_null() {
let mut old_cfg = CGlobalConfig::default();
old_cfg.fetch(&())?;
copy_struct_out(&old_cfg, old_cfg_ptr, cfg_size)
.wrap("copy libpathrs config to caller old_cfg_ptr")?;
}
if !new_cfg_ptr.is_null() {
let mut new_cfg = CGlobalConfig::default();
copy_struct_in(&mut new_cfg, new_cfg_ptr, cfg_size)
.wrap("copy caller new_cfg_ptr to libpathrs config")?;
ensure!(
new_cfg.__padding.iter().all(|e| *e == 0),
error::InvalidArgument {
name: "new_cfg_ptr",
description: "unused padding fields must be zero",
}
);
new_cfg.apply(&mut ())?;
}
}
CPointerType::PATHRS_ROOT => {
let obj = CRootConfig::verify(ptr_type, ptr)?;
let mut root = obj.inner.write().unwrap();
let mut root = root.as_mut().context(error::InvalidArgument {
name: "ptr",
description: "invalid pathrs object",
})?;
if !old_cfg_ptr.is_null() {
let mut old_cfg = CRootConfig::default();
old_cfg.fetch(&root)?;
copy_struct_out(&old_cfg, old_cfg_ptr, cfg_size)
.wrap("copy libpathrs config to caller old_cfg_ptr")?;
}
if !new_cfg_ptr.is_null() {
let mut new_cfg = CRootConfig::default();
copy_struct_in(&mut new_cfg, new_cfg_ptr, cfg_size)
.wrap("copy caller new_cfg_ptr to libpathrs config")?;
new_cfg.apply(&mut root)?;
}
}
_ => {
return error::InvalidArgument {
name: "ptr_type",
description: "type cannot be configured",
}
.fail()?;
}
};
Ok(())
});
error.as_ref().map(CError::from).map(Leakable::leak)
}