use crate::error::{Error, ErrorImpl};
use std::{
any, cmp,
ffi::{CStr, CString, OsStr},
marker::PhantomData,
mem,
os::unix::{
ffi::OsStrExt,
io::{AsRawFd, BorrowedFd, RawFd},
},
path::Path,
ptr, slice,
};
use bytemuck::Pod;
use libc::{c_char, c_int, size_t};
macro_rules! symver {
() => {};
(@with-meta $(#[$meta:meta])* $($block:tt)+) => {
#[cfg(cdylib)]
#[cfg(any(
target_arch = "arm",
target_arch = "aarch64",
target_arch = "x86",
target_arch = "x86_64",
target_arch = "riscv32",
target_arch = "riscv64",
// These are only supported after our MSRV and have corresponding
// #[rustversion::since(1.XY)] tags below.
target_arch = "loongarch64",
target_arch = "arm64ec",
target_arch = "s390x",
target_arch = "loongarch32",
// TODO: Once stabilised, add these arches:
//target_arch = "powerpc",
//target_arch = "powerpc64",
//target_arch = "sparc64",
))]
#[cfg_attr(target_arch = "loongarch64", ::rustversion::since(1.72))]
#[cfg_attr(target_arch = "arm64ec", ::rustversion::since(1.84))]
#[cfg_attr(target_arch = "s390x", ::rustversion::since(1.84))]
#[cfg_attr(target_arch = "loongarch32", ::rustversion::since(1.91))]
$(#[$meta])*
$($block)*
};
($(#[$meta:meta])* fn $implsym:ident <- ($symname:ident, version = $version:literal); $($tail:tt)*) => {
$crate::capi::utils::symver! {
@with-meta $(#[$meta])*
::std::arch::global_asm! {concat!(
".symver ",
stringify!($implsym),
", ",
stringify!($symname),
"@",
$version,
)}
}
$crate::capi::utils::symver! { $($tail)* }
};
($(#[$meta:meta])* fn $implsym:ident <- ($symname:ident, version = $version:literal, default); $($tail:tt)*) => {
$crate::capi::utils::symver! {
@with-meta $(#[$meta])*
::std::arch::global_asm! {concat!(
".symver ",
stringify!($implsym),
", ",
stringify!($symname),
"@@",
$version,
)}
}
$crate::capi::utils::symver! { $($tail)* }
};
}
pub(crate) use symver;
#[derive(PartialEq, Eq, Debug, Copy, Clone)]
#[repr(transparent)]
pub struct CBorrowedFd<'fd> {
inner: RawFd,
_phantom: PhantomData<BorrowedFd<'fd>>,
}
impl<'fd> CBorrowedFd<'fd> {
pub(crate) const unsafe fn from_raw_fd(fd: RawFd) -> Self {
Self {
inner: fd,
_phantom: PhantomData,
}
}
pub(crate) fn try_as_borrowed_fd(&self) -> Result<BorrowedFd<'fd>, Error> {
if self.inner.is_negative() {
Err(ErrorImpl::InvalidArgument {
name: "fd".into(),
description: "passed file descriptors must not be negative".into(),
}
.into())
} else {
Ok(unsafe { BorrowedFd::borrow_raw(self.inner) })
}
}
}
impl<'fd> AsRawFd for CBorrowedFd<'fd> {
fn as_raw_fd(&self) -> RawFd {
self.inner
}
}
impl<'fd> From<BorrowedFd<'fd>> for CBorrowedFd<'fd> {
fn from(fd: BorrowedFd<'_>) -> CBorrowedFd<'_> {
CBorrowedFd {
inner: fd.as_raw_fd(),
_phantom: PhantomData,
}
}
}
pub(crate) unsafe fn parse_path<'a>(path: *const c_char) -> Result<&'a Path, Error> {
if path.is_null() {
Err(ErrorImpl::InvalidArgument {
name: "path".into(),
description: "cannot be NULL".into(),
})?
}
let bytes = unsafe { CStr::from_ptr(path) }.to_bytes();
Ok(OsStr::from_bytes(bytes).as_ref())
}
pub(crate) unsafe fn copy_path_into_buffer(
path: impl AsRef<Path>,
buf: *mut c_char,
bufsize: size_t,
) -> Result<c_int, Error> {
let path = CString::new(path.as_ref().as_os_str().as_bytes())
.expect("link from readlink should not contain any nulls");
let path_len = path.to_bytes().len();
if !buf.is_null() && bufsize > 0 {
unsafe {
let to_copy = cmp::min(path_len, bufsize);
ptr::copy_nonoverlapping(path.as_ptr(), buf, to_copy);
}
}
Ok(path_len as c_int)
}
pub(crate) unsafe fn copy_from_extensible_struct<T: Pod>(
ptr: *const T,
size: usize,
) -> Result<T, Error> {
let raw_data = unsafe { slice::from_raw_parts(ptr as *const u8, size) };
let struct_size = mem::size_of::<T>();
#[allow(unused_assignments)] let mut struct_data_buf: Option<Vec<u8>> = None;
let (struct_data, trailing) = if raw_data.len() >= struct_size {
raw_data.split_at(struct_size)
} else {
let mut buf = vec![0u8; struct_size];
buf[0..raw_data.len()].copy_from_slice(raw_data);
struct_data_buf = Some(buf);
(
&struct_data_buf
.as_ref()
.expect("Option just assigned with Some must contain Some")[..],
&[][..],
)
};
debug_assert!(
struct_data.len() == struct_size,
"copy_from_extensible_struct should compute the struct size correctly"
);
if trailing.iter().any(|&ch| ch != 0) {
return Err(ErrorImpl::UnsupportedStructureData {
name: format!("c struct {}", any::type_name::<T>()).into(),
}
.into());
}
bytemuck::try_pod_read_unaligned(struct_data).map_err(|err| {
ErrorImpl::BytemuckPodCastError {
description: format!("cannot cast passed buffer into {}", any::type_name::<T>()).into(),
source: err,
}
.into()
})
}
pub(crate) trait Leakable: Sized {
fn leak(self) -> &'static mut Self {
Box::leak(Box::new(self))
}
unsafe fn unleak(&'static mut self) -> Self {
*unsafe { Box::from_raw(self as *mut Self) }
}
unsafe fn free(&'static mut self) {
let _ = unsafe { self.unleak() };
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::error::ErrorKind;
use bytemuck::{Pod, Zeroable};
use pretty_assertions::assert_eq;
#[repr(C)]
#[derive(PartialEq, Eq, Default, Debug, Clone, Copy, Pod, Zeroable)]
struct Struct {
foo: u64,
bar: u32,
baz: u32,
}
#[test]
fn extensible_struct() {
let example = Struct {
foo: 0xdeadbeeff00dcafe,
bar: 0x01234567,
baz: 0x89abcdef,
};
assert_eq!(
unsafe {
copy_from_extensible_struct(&example as *const Struct, mem::size_of::<Struct>())
}
.expect("copy_from_extensible_struct with size=sizeof(struct)"),
example,
"copy_from_extensible_struct(struct, sizeof(struct))",
);
}
#[test]
fn extensible_struct_short() {
let example = Struct {
foo: 0xdeadbeeff00dcafe,
bar: 0x01234567,
baz: 0x89abcdef,
};
assert_eq!(
unsafe { copy_from_extensible_struct(&example as *const Struct, 0) }
.expect("copy_from_extensible_struct with size=0"),
Struct::default(),
"copy_from_extensible_struct(struct, 0)",
);
assert_eq!(
unsafe {
copy_from_extensible_struct(
&example as *const Struct,
bytemuck::offset_of!(Struct, bar),
)
}
.expect("copy_from_extensible_struct with size=offsetof(struct.bar)"),
Struct {
foo: example.foo,
..Default::default()
},
"copy_from_extensible_struct(struct, offsetof(struct, bar))",
);
assert_eq!(
unsafe {
copy_from_extensible_struct(
&example as *const Struct,
bytemuck::offset_of!(Struct, baz),
)
}
.expect("copy_from_extensible_struct with size=offsetof(struct.baz)"),
Struct {
foo: example.foo,
bar: example.bar,
..Default::default()
},
"copy_from_extensible_struct(struct, offsetof(struct, bar))",
);
}
#[test]
fn extensible_struct_long() {
#[repr(C)]
#[derive(PartialEq, Eq, Default, Debug, Clone, Copy, Pod, Zeroable)]
struct StructV2 {
inner: Struct,
extra: u64,
}
let example_compatible = StructV2 {
inner: Struct {
foo: 0xdeadbeeff00dcafe,
bar: 0x01234567,
baz: 0x89abcdef,
},
extra: 0,
};
assert_eq!(
unsafe {
copy_from_extensible_struct(
&example_compatible as *const StructV2 as *const Struct,
mem::size_of::<StructV2>(),
)
}
.expect("copy_from_extensible_struct with size=sizeof(structv2)"),
example_compatible.inner,
"copy_from_extensible_struct(structv2, sizeof(structv2)) with only trailing zero bytes",
);
let example_compatible = StructV2 {
inner: Struct {
foo: 0xdeadbeeff00dcafe,
bar: 0x01234567,
baz: 0x89abcdef,
},
extra: 0x1,
};
assert_eq!(
unsafe {
copy_from_extensible_struct(
&example_compatible as *const StructV2 as *const Struct,
mem::size_of::<StructV2>(),
)
}
.map_err(|err| err.kind()),
Err(ErrorKind::UnsupportedStructureData),
"copy_from_extensible_struct(structv2, sizeof(structv2)) with trailing non-zero bytes",
);
}
}