diff --git a/src/ffi.rs b/src/ffi.rs new file mode 100644 index 0000000..5d09048 --- /dev/null +++ b/src/ffi.rs @@ -0,0 +1,198 @@ +//! Useful for C-interop +use super::*; + +macro_rules! c_try { + ($call:expr => $invalid:literal; $fmt:literal $(, $args:expr)*) => { + { + #[inline(never)] + #[cold] + fn _panic_bad_c_call<'a>(args: ::std::fmt::Arguments<'a>) -> ! + { + panic!("C call failed (invalid return {}): {}", $invalid, args) + } + + let res = unsafe { $call }; + if res == $invalid { + _panic_bad_c_call(format_args!($fmt $(, $args)*)) + } + res + } + }; + ($call:expr => if $func:expr; $fmt:literal $(, $args:expr)*) => { + { + #[inline(never)] + #[cold] + fn _panic_bad_c_call<'a, T: ::std::fmt::Display>(invalid: T, args: ::std::fmt::Arguments<'a>) -> ! + { + panic!("C call failed (invalid return {}): {}", invalid, args) + } + let res = unsafe { $call }; + if $func(res) { + _panic_bad_c_call(res, format_args!($fmt $(, $args)*)); + } + res + } + }; + (? $call:expr => if $func:expr; $fmt:literal $(, $args:expr)*) => { + { + let res = unsafe { $call }; + if $func(res) { + Err(FFIError::from_last_error(res, format_args!($fmt $(, $args)*))) + } else { + Ok(res) + } + } + }; + (? $call:expr => $invalid:literal; $fmt:literal $(, $args:expr)*) => { + { + let res = unsafe { $call }; + if res == $invalid { + Err(FFIError::from_last_error($invalid, format_args!($fmt $(, $args)*))) + } else { + Ok(res) + } + } + }; + /* Eh... Idk why this doesn't work... + ($call:expr => {$($invalid:pat $(if $pred:pat)?),+} => $fmt:literal $(, $args:expr)*) => { + { + #[inline(never)] + #[cold] + fn _panic_bad_c_call<'a, T: ::std::fmt::Display>(invalid: T, args: ::std::fmt::Arguments<'a>) -> ! + { + panic!("C call failed (invalid return {}): {}", invalid, args) +} + let res = $call; + match res { + $($invalid $(if $pred)? => _panic_bad_c_call(res, format_args!($fmt $(, $args)*))),* + x => x, +} +} +};*/ +} +pub(crate) use c_try; + +/// Error context for a failed C call. +/// Returns the invalid return value, the `errno` error, and a message. +#[derive(Debug)] +pub(crate) struct FFIError<'a, T>(T, io::Error, fmt::Arguments<'a>); + +impl<'a, T> FFIError<'a, T> +where FFIError<'a, T>: error::Error +{ + #[inline(never)] + #[cold] + fn from_last_error(value: T, arguments: fmt::Arguments<'a>) -> Self + { + Self(value, io::Error::last_os_error(), arguments) + } +} + + +impl<'a, T> AsRef for FFIError<'a, T> +{ + #[inline] + fn as_ref(&self) -> &io::Error { + &self.1 + } +} + +impl<'a, T> FFIError<'a, T> +{ + /// A reference to the value + #[inline] + pub fn value(&self) -> &T + { + &self.0 + } + + /// Clone an instance of the value + #[inline] + pub fn to_value(&self) -> T + where T: Clone + { + self.0.clone() + } + + /// Consume into the value + #[inline] + pub fn into_value(self) -> T + { + self.0 + } + + + /// Consume into a recursive 2-tuple of `((value, error), message)`. + #[inline] + pub fn into_parts(self) -> ((T, io::Error), impl fmt::Display + fmt::Debug + 'a) + { + ((self.0, self.1), self.2) + } + + /// A reference to the inner OS error + #[inline] + pub fn error(&self) -> &io::Error + { + &self.1 + } + + /// Get a reference to an opaque type that can be formatted into the message + #[inline] + pub fn message(&self) -> &(impl fmt::Display + fmt::Debug + 'a) + { + &self.2 + } + + /// Consume an opaque type that can be formatted into the message + pub fn into_message(self) -> impl fmt::Display + fmt::Debug + 'a + { + self.2 + } +/* This doesn't work... + /// Render any referenced arguments in the message into a string, reducing the lifetime requirement of the message to `'static`. + /// + /// # Notes + /// If `T` is not also `'static`, then the resulting instance will not be `'static` itself. If `T` is not `'static`, use `into_owned()` instead. + #[inline] + pub fn message_into_owned(self) -> FFIError<'static, T> + { + FFIError(self.0, self.1, format_args!("{}", self.2.to_string())) + } + + /// Clone any referenced arguments of the message and the value into a non-referential object, reducing the lifetime requirements of the returned instance to `'static`. + #[inline] + pub fn into_owned(self) -> FFIError<'static, T::Owned> + where T: ToOwned, + T::Owned: 'static + { + FFIError(self.0.to_owned(), self.1, format_args!("{}", self.2.to_string())) +} + */ +} + +impl<'a, T> error::Error for FFIError<'a, T> +where FFIError<'a, T>: fmt::Display + fmt::Debug +{ + #[inline] + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + Some(&self.1) + } +} +impl<'a, T: fmt::Debug> fmt::Display for FFIError<'a, T> +{ + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + write!(f, "C call failed (invalid return {:?}): {}", self.0, &self.2) + } +} + +impl<'a, T> From> for io::Error +{ + #[inline] + fn from(from: FFIError<'a, T>) -> Self + { + from.1 + } +} + diff --git a/src/file.rs b/src/file.rs new file mode 100644 index 0000000..a3d8c9a --- /dev/null +++ b/src/file.rs @@ -0,0 +1,39 @@ +//! Types for, and operations on file descriptors. Useful for mapping +use super::*; + +/// Raw file-descriptor for standard input +pub const STDIN_FILENO: RawFd = libc::STDIN_FILENO; +/// Raw file-descriptor for standard output +pub const STDOUT_FILENO: RawFd = libc::STDOUT_FILENO; +/// Raw file-descriptor for standard error +pub const STDERR_FILENO: RawFd = libc::STDERR_FILENO; + +mod managed; +mod unmanaged; + +pub use self::{ + managed::*, + unmanaged::*, +}; + +pub mod memory; + +#[cfg(test)] +mod tests +{ + use super::*; + + #[test] + fn std_in_out_err_fileno() + { + #[inline(always)] + fn test_fileno(expected_name: &'static str, got: RawFd) + { + assert_eq!(EXPECTED, got, "{expected_name} invalid: expected: {EXPECTED}, got {got}"); + } + + test_fileno::("STDIN_FILENO", std::io::stdin().as_raw_fd()); + test_fileno::("STDOUT_FILENO", std::io::stdout().as_raw_fd()); + test_fileno::("STDERR_FILENO", std::io::stderr().as_raw_fd()); + } +} diff --git a/src/file/managed.rs b/src/file/managed.rs new file mode 100644 index 0000000..3e9f3e7 --- /dev/null +++ b/src/file/managed.rs @@ -0,0 +1,36 @@ +//! Represents a managed `RawFd`. +//! This will `close()` its contained `RawFd` on drop. +//! +//! Can be useful for OS operations on file descriptors without leaking open fds. +use super::*; +use std::{ + ops, +}; +use libc::{ + dup, dup2, + close, +}; + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[repr(transparent)] +pub struct ManagedFD(RawFd); + +impl Clone for ManagedFD { + fn clone(&self) -> Self { + Self(c_try!(dup(self.0) => if |x| x < 0; "dup(): failed to duplicate file descriptor {}", self.0)) + } + fn clone_from(&mut self, source: &Self) { + c_try!(dup2(self.0, source.0) => -1; "dup2(): failed to set file descriptor {} to alias {}", self.0, source.0); + } +} + +impl ops::Drop for ManagedFD +{ + fn drop(&mut self) { + unsafe { + close(self.0); + } + } +} + +//TODO: implement the rest of ManagedFD from `memfd` module in `utf8encode` diff --git a/src/file/memory.rs b/src/file/memory.rs new file mode 100644 index 0000000..480806d --- /dev/null +++ b/src/file/memory.rs @@ -0,0 +1,12 @@ +//! Provides physical in-memory file descriptors. +//! +//! This can be useful for temporary buffers where a file descriptor is required. +//! Huge-pages can also be used for this memory. +use super::*; + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[repr(transparent)] +pub struct MemoryFile(ManagedFD); + +mod hugetlb; +//TODO: implement `memfd` (and its hugetlb interface, extracted to `hugetlb.rs`) from `utf8encode`. diff --git a/src/file/memory/hugetlb.rs b/src/file/memory/hugetlb.rs new file mode 100644 index 0000000..6074de6 --- /dev/null +++ b/src/file/memory/hugetlb.rs @@ -0,0 +1,4 @@ +//! Huge-page interface for `MemoryFile`. +use super::*; + +//TODO: implement `memfd`'s hugetlb interface from `utf8encode` here. diff --git a/src/file/unmanaged.rs b/src/file/unmanaged.rs new file mode 100644 index 0000000..a68e93f --- /dev/null +++ b/src/file/unmanaged.rs @@ -0,0 +1,9 @@ +//! Provides a wrapper over `RawFd` that does not close it on drop. +//! This can be useful for aliasing file descriptors. +use super::*; + +/// Represents a `RawFd` but does not provide any ownership of it. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct UnmanagedFD(RawFd); + +//TODO: implement a full version of the temporary struct `UnmanagedFD` from `utf8encode` diff --git a/src/lib.rs b/src/lib.rs index 68c9033..ff21069 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,6 +20,11 @@ use std::{ } }; +mod ffi; +use ffi::c_try; + +pub mod file; + mod uniq; use uniq::UniqueSlice; @@ -434,3 +439,5 @@ impl ops::DerefMut for MappedFile self.as_slice_mut() } } +//TODO: Continue copying from `utf8encode` at the //TODO (cont.) line +