use crate::{
error::{self, Error},
Handle, Root,
};
use std::{
collections::HashMap,
convert::{self, TryInto},
ffi::{CStr, CString, OsStr},
io::Error as IOError,
mem,
os::unix::ffi::OsStrExt,
path::Path,
ptr,
sync::{Mutex, RwLock},
thread::{self, ThreadId},
};
use backtrace::Backtrace;
use libc::{c_char, c_void};
use snafu::{ErrorCompat, OptionExt};
#[repr(C)]
#[allow(non_camel_case_types, dead_code)]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum CPointerType {
__PATHRS_INVALID_TYPE = 0,
PATHRS_NONE = 0xDFFF,
PATHRS_ERROR = 0xE000,
PATHRS_ROOT = 0xE001,
PATHRS_HANDLE = 0xE002,
}
pub(crate) fn parse_path<'a>(path: *const c_char) -> Result<&'a Path, Error> {
ensure!(
!path.is_null(),
error::InvalidArgument {
name: "path",
description: "cannot be NULL",
}
);
let bytes = unsafe { CStr::from_ptr(path) }.to_bytes();
Ok(OsStr::from_bytes(bytes).as_ref())
}
pub(crate) trait Leakable {
fn leak(self) -> &'static mut Self;
fn unleak(&'static mut self) -> Self;
fn free(&'static mut self);
}
macro_rules! leakable {
(...) => {
fn leak(self) -> &'static mut Self {
Box::leak(Box::new(self))
}
fn unleak(&'static mut self) -> Self {
*unsafe { Box::from_raw(self as *mut Self) }
}
fn free(&'static mut self) {
let _ = self.unleak();
}
};
(impl Leakable for $type:ty ;) => {
impl Leakable for $type {
leakable!(...);
}
};
(impl <$($generics:tt),+> Leakable for $type:ty ;) => {
impl<$($generics),+> Leakable for $type {
leakable!(...);
}
};
}
pub(crate) trait ErrorWrap {
fn wrap<F, R>(&mut self, c_error: R, func: F) -> R
where
F: FnOnce() -> Result<R, Error>;
}
impl ErrorWrap for Option<Error> {
fn wrap<F, R>(&mut self, c_error: R, func: F) -> R
where
F: FnOnce() -> Result<R, Error>,
{
*self = None;
func().unwrap_or_else(|err| {
*self = Some(err);
c_error
})
}
}
#[doc(hidden)]
#[derive(Debug)]
pub struct CPointer<T> {
pub(crate) inner: RwLock<Option<T>>,
last_error: Mutex<HashMap<ThreadId, Option<Error>>>,
}
leakable! {
impl<T> Leakable for CPointer<T>;
}
impl<T> CPointer<T> {
pub(crate) fn from_err(err: Error) -> Self {
CPointer {
inner: RwLock::new(None),
last_error: Mutex::new({
let mut map = HashMap::new();
map.insert(thread::current().id(), Some(err));
map
}),
}
}
pub(crate) fn take_err(&self) -> Option<Error> {
self.last_error
.lock()
.unwrap()
.remove(&thread::current().id())
.and_then(convert::identity)
}
fn do_wrap_err<F, R>(&self, c_error: R, func: F) -> R
where
F: FnOnce() -> Result<R, Error>,
{
let mut last_error: Option<Error> = None;
let c_return = func().unwrap_or_else(|err| {
last_error = Some(err);
c_error
});
self.last_error
.lock()
.unwrap()
.insert(thread::current().id(), last_error);
c_return
}
pub(crate) fn wrap_err<F, R>(&self, c_error: R, func: F) -> R
where
F: FnOnce(&T) -> Result<R, Error>,
{
self.do_wrap_err(c_error, || {
let inner = self.inner.read().unwrap();
let inner = inner.as_ref().context(error::InvalidArgument {
name: "ptr",
description: "invalid pathrs object",
})?;
func(&inner)
})
}
pub(crate) fn take_wrap_err<F, R>(&self, c_error: R, func: F) -> R
where
F: FnOnce(T) -> Result<R, Error>,
{
self.do_wrap_err(c_error, || {
let mut inner = self.inner.write().unwrap();
let inner = inner.take().context(error::InvalidArgument {
name: "ptr",
description: "invalid pathrs object",
})?;
func(inner)
})
}
}
impl<T> From<T> for CPointer<T> {
fn from(inner: T) -> Self {
CPointer {
inner: RwLock::new(Some(inner)),
last_error: Mutex::new(HashMap::new()),
}
}
}
pub type CRoot = CPointer<Root>;
pub type CHandle = CPointer<Handle>;
#[repr(align(8), C)]
#[derive(Debug)]
pub struct CVec<T> {
pub head: *const T,
pub length: usize,
pub __capacity: usize,
}
leakable! {
impl<T> Leakable for CVec<T>;
}
impl<T> From<Vec<T>> for CVec<T> {
fn from(vec: Vec<T>) -> Self {
let head = vec.as_ptr();
let length = vec.len();
let capacity = vec.capacity();
mem::forget(vec);
CVec {
head,
length,
__capacity: capacity,
}
}
}
impl<T> Drop for CVec<T> {
fn drop(&mut self) {
if self.head.is_null() {
let head = self.head as *mut T;
self.head = ptr::null_mut();
let _ = unsafe { Vec::from_raw_parts(head, self.length, self.__capacity) };
}
}
}
#[repr(align(8), C)]
#[derive(Debug)]
pub struct CBacktraceEntry {
pub ip: *const c_void,
pub symbol_address: *const c_void,
pub symbol_name: *const c_char,
pub symbol_file: *const c_char,
pub symbol_lineno: u32,
}
impl Drop for CBacktraceEntry {
fn drop(&mut self) {
if !self.symbol_name.is_null() {
let symbol_name = self.symbol_name as *mut c_char;
self.symbol_name = ptr::null_mut();
let _ = unsafe { CString::from_raw(symbol_name) };
}
if !self.symbol_file.is_null() {
let symbol_file = self.symbol_file as *mut c_char;
self.symbol_file = ptr::null_mut();
let _ = unsafe { CString::from_raw(symbol_file as *mut c_char) };
}
}
}
pub type CBacktrace = CVec<CBacktraceEntry>;
impl From<Backtrace> for CBacktrace {
fn from(mut backtrace: Backtrace) -> Self {
backtrace.resolve();
backtrace
.frames()
.iter()
.map(|frame| {
let symbol = frame.symbols().last();
let (name, file, lineno) = match symbol {
Some(symbol) => {
let name = symbol.name().map(|name| {
CString::new(name.to_string()).expect(
"CString::new(symbol_name) failed in CBacktraceEntry generation",
)
});
let file = symbol.filename().map(|file| {
CString::new(file.as_os_str().as_bytes()).expect(
"CString::new(symbol_file) failed in CBacktraceEntry generation",
)
});
(name, file, symbol.lineno())
}
None => (None, None, None),
};
CBacktraceEntry {
ip: frame.ip(),
symbol_address: frame.symbol_address(),
symbol_name: name.map(CString::into_raw).unwrap_or(ptr::null_mut())
as *const c_char,
symbol_file: file.map(CString::into_raw).unwrap_or(ptr::null_mut())
as *const c_char,
symbol_lineno: lineno.unwrap_or(0),
}
})
.collect::<Vec<_>>()
.into()
}
}
#[repr(align(8), C)]
pub struct CError {
pub saved_errno: u64,
pub description: *const c_char,
pub backtrace: Option<&'static mut CBacktrace>,
}
leakable! {
impl Leakable for CError;
}
impl From<&Error> for CError {
fn from(err: &Error) -> Self {
let desc = err.iter_chain_hotfix().fold(String::new(), |mut s, next| {
if s != "" {
s.push_str(": ");
}
s.push_str(&next.to_string());
s
});
let desc =
CString::new(desc).expect("CString::new(description) failed in CError generation");
let errno = match err.root_cause().downcast_ref::<IOError>() {
Some(err) => err.raw_os_error().unwrap_or(0).abs(),
_ => 0,
};
CError {
saved_errno: errno.try_into().unwrap_or(0),
description: desc.into_raw(),
backtrace: ErrorCompat::backtrace(err)
.cloned()
.map(CBacktrace::from)
.map(Leakable::leak),
}
}
}
impl Drop for CError {
fn drop(&mut self) {
if !self.description.is_null() {
let description = self.description as *mut c_char;
self.description = ptr::null_mut();
let _ = unsafe { CString::from_raw(description) };
}
if let Some(ref mut backtrace) = self.backtrace {
let backtrace = *backtrace as *mut CBacktrace;
self.backtrace = None;
unsafe { &mut *backtrace }.free();
}
}
}
#[no_mangle]
pub extern "C" fn pathrs_error(
ptr_type: CPointerType,
ptr: *const c_void,
) -> Option<&'static mut CError> {
if ptr.is_null() {
return None;
}
let err = match ptr_type {
CPointerType::PATHRS_NONE => return None,
CPointerType::PATHRS_ERROR => return None, CPointerType::PATHRS_ROOT => {
let root = unsafe { &*(ptr as *const CRoot) };
root.take_err()
}
CPointerType::PATHRS_HANDLE => {
let handle = unsafe { &*(ptr as *const CHandle) };
handle.take_err()
}
_ => panic!("invalid ptr_type: {:?}", ptr_type),
};
err.as_ref().map(CError::from).map(Leakable::leak)
}
#[no_mangle]
pub extern "C" fn pathrs_free(ptr_type: CPointerType, ptr: *const c_void) {
if ptr.is_null() {
return;
}
match ptr_type {
CPointerType::PATHRS_NONE => (),
CPointerType::PATHRS_ERROR => unsafe { &mut *(ptr as *mut CError) }.free(),
CPointerType::PATHRS_ROOT => unsafe { &mut *(ptr as *mut CRoot) }.free(),
CPointerType::PATHRS_HANDLE => unsafe { &mut *(ptr as *mut CHandle) }.free(),
_ => panic!("invalid ptr_type: {:?}", ptr_type),
}
}