You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
530 lines
15 KiB
530 lines
15 KiB
//! Memory file handling
|
|
use super::*;
|
|
use std::os::unix::io::*;
|
|
use std::{
|
|
mem,
|
|
ops,
|
|
fs,
|
|
io,
|
|
path::Path,
|
|
borrow::{
|
|
Borrow,
|
|
BorrowMut,
|
|
},
|
|
};
|
|
|
|
pub mod fd;
|
|
pub mod error;
|
|
mod map;
|
|
#[cfg(feature="hugetlb")]
|
|
mod hp;
|
|
|
|
|
|
/// Flags passed to `memfd_create()` when used in this module
|
|
const MEMFD_CREATE_FLAGS: libc::c_uint = libc::MFD_CLOEXEC;
|
|
|
|
#[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
|
#[repr(transparent)]
|
|
pub struct RawFile(fd::RawFileDescriptor);
|
|
|
|
/// Attempt to get the length of a stream's file descriptor
|
|
#[inline]
|
|
#[cfg_attr(feature="logging", instrument(level="debug", err, skip_all, fields(from_fd = from.as_raw_fd())))]
|
|
pub fn stream_len(from: &(impl AsRawFd + ?Sized)) -> io::Result<u64>
|
|
{
|
|
let mut stat = std::mem::MaybeUninit::uninit();
|
|
match unsafe { libc::fstat(from.as_raw_fd(), stat.as_mut_ptr()) } {
|
|
-1 => Err(io::Error::last_os_error()),
|
|
_ => {
|
|
let stat = unsafe { stat.assume_init() };
|
|
debug_assert!(stat.st_size >= 0, "bad stat size");
|
|
Ok(stat.st_size as u64)
|
|
},
|
|
}
|
|
}
|
|
|
|
/// Create an in-memory `File`, with an optional name
|
|
#[cfg_attr(feature="logging", instrument(level="info", err))]
|
|
pub fn create_memfile(name: Option<&str>, size: usize) -> eyre::Result<fs::File>
|
|
{
|
|
if_trace!(debug!("Attempting to allocate {size} bytes of contiguous physical memory for memory file named {:?}", name.unwrap_or("<unbound>")));
|
|
RawFile::open_mem(name, size).map(Into::into)
|
|
.wrap_err(eyre!("Failed to open in-memory file")
|
|
.with_section(move || format!("{:?}", name).header("Proposed name"))
|
|
.with_section(|| size.header("Requested physical memory buffer size")))
|
|
}
|
|
|
|
impl Clone for RawFile
|
|
{
|
|
#[inline]
|
|
#[cfg_attr(feature="logging", instrument(skip_all))]
|
|
fn clone(&self) -> Self {
|
|
self.try_clone().expect("failed to duplicate raw fd")
|
|
}
|
|
|
|
#[inline]
|
|
fn clone_from(&mut self, source: &Self)
|
|
{
|
|
if (!cfg!(debug_assertions)) || !std::ptr::eq(self, source) {
|
|
#[cfg(feature="logging")]
|
|
let span = trace_span!("clone_from()", self = ?self, source= ?source);
|
|
#[cfg(feature="logging")]
|
|
let _span = span.enter();
|
|
|
|
self.try_link_from(source).expect("failed to duplicate raw fd into self");
|
|
} else {
|
|
#[cfg(feature="logging")]
|
|
let span = trace_span!("clone_from()", self = ?format!("0x{:x}", self as *mut _ as usize), source = ?format!("0x{:x}", source as *const _ as usize));
|
|
#[cfg(feature="logging")]
|
|
let _span = span.enter();
|
|
|
|
if_trace!(error!("`self` and `source` are the same variable. This should never happen!"));
|
|
#[cfg(not(feature="logging"))]
|
|
panic!("Mutable reference and shared reference point to the same location in memory")
|
|
}
|
|
}
|
|
}
|
|
|
|
impl RawFile
|
|
{
|
|
/// Get the raw fd for this raw file
|
|
#[inline(always)]
|
|
pub const fn fileno(&self) -> &fd::RawFileDescriptor
|
|
{
|
|
&self.0//.clone_const()
|
|
}
|
|
|
|
#[inline(always)]
|
|
pub fn into_fileno(self) -> fd::RawFileDescriptor
|
|
{
|
|
// SAFETY: We know this is safe since we are just converting the released (valid) fd from `self`
|
|
unsafe {
|
|
fd::RawFileDescriptor::new_unchecked(self.into_raw_fd())
|
|
}
|
|
}
|
|
|
|
#[inline(always)]
|
|
pub unsafe fn from_fileno(fd: fd::RawFileDescriptor) -> Self
|
|
{
|
|
Self::from_raw_fd(fd.get())
|
|
}
|
|
|
|
#[inline(always)]
|
|
pub(crate) const fn take_ownership_of_unchecked(fd: RawFd) -> Self
|
|
{
|
|
//! **Internal**: Non-`unsafe` and `const` version of `take_ownership_of_raw_unchecked()`
|
|
//! : assumes `fd` is `>= 0`
|
|
//!
|
|
//! For use in `memfile` functions where `fd` has already been checked for validation (since `unsafe fn`s aren't first-class :/)
|
|
unsafe {
|
|
Self(fd::RawFileDescriptor::new_unchecked(fd))
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
pub fn take_ownership_of(fd: impl Into<fd::RawFileDescriptor>) -> Self
|
|
{
|
|
Self(fd.into())
|
|
}
|
|
|
|
#[inline]
|
|
pub fn take_ownership_of_raw(fd: impl Into<RawFd>) -> Result<Self, RawFd>
|
|
{
|
|
let fd = fd.into();
|
|
Ok(Self(fd.try_into().map_err(|_| fd)?))
|
|
}
|
|
|
|
#[inline]
|
|
pub unsafe fn take_ownership_of_raw_unchecked(fd: impl Into<RawFd>) -> Self
|
|
{
|
|
Self(fd::RawFileDescriptor::new_unchecked(fd.into()))
|
|
}
|
|
|
|
/// Attempt to link this instance's fd to another container over an fd
|
|
///
|
|
/// This is a safe wrapper around `dup2()`, as `clone()` is a safe wrapper around `dup()`.
|
|
///
|
|
/// # Note
|
|
/// If `T` is a buffered container (e.g. `std::io::BufWriter<T: AsRawFd>`), make sure the buffer is flushed *before* calling this method on it, or the buffered data will be lost.
|
|
#[cfg_attr(feature="logging", instrument(err, skip(other), fields(other = ?other.as_raw_fd())))]
|
|
pub fn try_link_to<'o, T: ?Sized>(&self, other: &'o mut T) -> Result<&'o mut T, error::DuplicateError>
|
|
where T: AsRawFd
|
|
{
|
|
if unsafe {
|
|
libc::dup2(self.0.get(), other.as_raw_fd())
|
|
} < 0 {
|
|
Err(error::DuplicateError::new_dup2(self, other))
|
|
} else {
|
|
Ok(other)
|
|
}
|
|
}
|
|
|
|
/// Attempt to link `other`'s contained file descriptor to this instance's fd.
|
|
///
|
|
/// This is a safe wrapper around `dup2()`, and an analogue of `try_link_to()`.
|
|
///
|
|
/// # Note
|
|
/// After this call succeeds, writing to `self` will have the same effect of writing directly to `other`'s contained file descriptor. If `other` is a buffered stream, you must ensure that `other` has been flushed *before* writing anything to `self`.
|
|
#[cfg_attr(feature="logging", instrument(err, skip(other), fields(other = ?other.as_raw_fd())))]
|
|
pub fn try_link_from<'i, T: ?Sized>(&mut self, other: &'i T) -> Result<&'i T, error::DuplicateError>
|
|
where T: AsRawFd
|
|
{
|
|
if unsafe {
|
|
libc::dup2(other.as_raw_fd(), self.0.get())
|
|
} < 0 {
|
|
Err(error::DuplicateError::new_dup2(other, self))
|
|
} else {
|
|
Ok(other)
|
|
}
|
|
}
|
|
|
|
/// Link `other`'s contained file descriptor to this instance's fd.
|
|
///
|
|
/// # Panics
|
|
/// If the call to `dup2()` fails.
|
|
///
|
|
/// # Note
|
|
/// This is a panicking version of `try_link_from()`. See that function for more information on how to safely use `self` after this call.
|
|
#[inline]
|
|
#[cfg_attr(feature="logging", instrument(skip_all))]
|
|
pub fn link_from<'i, T: ?Sized>(&mut self, other: &'i T) -> &'i T
|
|
where T: AsRawFd
|
|
{
|
|
self.try_link_from(other).expect("failed to duplicate file descriptor from another container")
|
|
}
|
|
|
|
/// Attempt to link this instance's fd to another container over an fd
|
|
///
|
|
/// # Panics
|
|
/// If the call to `dup2()` fails.
|
|
///
|
|
/// # Note
|
|
/// This is a panicking version of `try_link_to()`. See that function for more information on how to safely use `self` after this call.
|
|
#[inline]
|
|
#[cfg_attr(feature="logging", instrument(skip_all))]
|
|
pub fn link_to<'o, T: ?Sized>(&self, other: &'o mut T) -> &'o mut T
|
|
where T: AsRawFd
|
|
{
|
|
self.try_link_to(other).expect("failed to duplicate file descriptor into another container")
|
|
}
|
|
|
|
/// Attempt to duplicate this raw file
|
|
#[cfg_attr(feature="logging", instrument(err))]
|
|
pub fn try_clone(&self) -> Result<Self, error::DuplicateError>
|
|
{
|
|
match unsafe { libc::dup(self.0.get()) }
|
|
{
|
|
-1 => Err(error::DuplicateError::new_dup(self)),
|
|
fd => Ok(Self::take_ownership_of_unchecked(fd))
|
|
}
|
|
}
|
|
|
|
/// Consume a managed file into a raw file, attempting to synchronise it first.
|
|
///
|
|
/// # Note
|
|
/// This method attempts to sync the file's data.
|
|
/// To also attempt to sync the file's metadata, set `metadata` to true.
|
|
///
|
|
/// # Returns
|
|
/// If the sync should fail, the original file is returned, along with the error from the sync.
|
|
#[inline(always)]
|
|
#[cfg_attr(feature="logging", instrument(level="debug"))]
|
|
pub fn try_from_file_synced(file: fs::File, metadata: bool) -> Result<Self, (fs::File, io::Error)>
|
|
{
|
|
if_trace!(trace!("syncing file data"));
|
|
match if metadata {
|
|
file.sync_all()
|
|
} else {
|
|
file.sync_data()
|
|
} {
|
|
Ok(()) => unsafe {
|
|
if_trace!(debug!("sync succeeded, consumeing fd"));
|
|
Ok(Self::from_raw_fd(file.into_raw_fd()))
|
|
},
|
|
Err(ioe) => {
|
|
if_trace!({
|
|
#[cfg(feature="logging")]
|
|
let span = warn_span!("failed_path", file = ?file, error = ?ioe);
|
|
#[cfg(feature="logging")]
|
|
let _spen = span.enter();
|
|
error!("sync failed: {ioe}")
|
|
});
|
|
Err((file, ioe))
|
|
},
|
|
}
|
|
}
|
|
|
|
/// Consume a managed fd type into a raw file
|
|
#[inline(always)]
|
|
pub fn from_file(file: impl IntoRawFd) -> Self
|
|
{
|
|
unsafe {
|
|
Self::from_raw_fd(file.into_raw_fd())
|
|
}
|
|
}
|
|
|
|
/// Consume into another managed file type container
|
|
#[inline(always)]
|
|
pub fn into_file<T: FromRawFd>(self) -> T
|
|
{
|
|
unsafe {
|
|
T::from_raw_fd(self.into_raw_fd())
|
|
}
|
|
}
|
|
|
|
/// Attempt to open a new raw file with these options
|
|
#[inline]
|
|
pub fn open(path: impl AsRef<Path>, opt: impl Borrow<fs::OpenOptions>) -> io::Result<Self>
|
|
{
|
|
opt.borrow().open(path).map(Into::into)
|
|
}
|
|
|
|
/// Allocates `size` bytes for this file.
|
|
///
|
|
/// # Note
|
|
/// This does not *extend* the file's capacity, it is instead similar to `fs::File::set_len()`.
|
|
#[cfg_attr(feature="logging", instrument(err))]
|
|
#[inline]
|
|
pub fn allocate_size(&mut self, size: u64) -> io::Result<()>
|
|
{
|
|
use libc::{ fallocate, off_t};
|
|
if_trace!(trace!("attempting fallocate({}, 0, 0, {size}) (max offset: {})", self.0.get(), off_t::MAX));
|
|
match unsafe { fallocate(self.0.get(), 0, 0, if cfg!(debug_assertions) {
|
|
size.try_into().map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "Offset larger than max offset size"))?
|
|
} else { size as off_t }) } { //XXX is this biteise AND check needed? fallocate() should already error if the size is negative with these parameters, no?
|
|
-1 => Err(io::Error::last_os_error()),
|
|
_ => Ok(())
|
|
}
|
|
}
|
|
|
|
/// Sets the size of this file.
|
|
///
|
|
/// The only real difference is that this will work on a `hugetlbfs` file, whereas `allocate_size()` will not.
|
|
/// # Note
|
|
/// This is essentially `fs::File::set_len()`.
|
|
#[cfg_attr(feature="logging", instrument(err))]
|
|
#[inline]
|
|
pub fn truncate_size(&mut self, size: u64) -> io::Result<()>
|
|
{
|
|
use libc::{ ftruncate, off_t};
|
|
if_trace!(trace!("attempting ftruncate({}, {size}) (max offset: {})", self.0.get(), off_t::MAX));
|
|
match unsafe { ftruncate(self.0.get(), if cfg!(debug_assertions) {
|
|
size.try_into().map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "Offset larger than max offset size"))?
|
|
} else { size as off_t }) } {
|
|
-1 => Err(io::Error::last_os_error()),
|
|
_ => Ok(())
|
|
}
|
|
}
|
|
|
|
/// Open a new in-memory (W+R) file with an optional name and a fixed size.
|
|
#[cfg_attr(feature="logging", instrument(level="debug", skip_all, err))]
|
|
pub fn open_mem(name: Option<&str>, len: usize) -> Result<Self, error::MemfileError>
|
|
{
|
|
use std::{
|
|
ffi::CString,
|
|
borrow::Cow,
|
|
};
|
|
lazy_static! {
|
|
static ref DEFAULT_NAME: CString = CString::new(format!(concat!("<memfile@", file!(), "->", "{}", ":", line!(), "-", column!(), ">"), function!())).unwrap();
|
|
}
|
|
|
|
use libc::{
|
|
memfd_create,
|
|
fallocate,
|
|
};
|
|
use error::MemfileCreationStep::*;
|
|
|
|
let bname: Cow<CString> = match name {
|
|
Some(s) => Cow::Owned(CString::new(Vec::from(s)).expect("Invalid name")),
|
|
None => Cow::Borrowed(&DEFAULT_NAME),
|
|
};
|
|
|
|
let bname = bname.as_bytes_with_nul();
|
|
if_trace!(trace!("created nul-terminated buffer for name `{:?}': ({})", std::str::from_utf8(bname), bname.len()));
|
|
|
|
macro_rules! attempt_call
|
|
{
|
|
($errcon:literal, $expr:expr, $step:expr) => {
|
|
//if_trace!(debug!("attempting systemcall"));
|
|
match unsafe {
|
|
$expr
|
|
} {
|
|
$errcon => {
|
|
if_trace!(warn!("systemcall failed: {}", error::raw_errno()));
|
|
Err($step)
|
|
},
|
|
x => Ok(x)
|
|
}
|
|
}
|
|
}
|
|
|
|
let fd = attempt_call!(-1, memfd_create(bname.as_ptr() as *const _, MEMFD_CREATE_FLAGS), Create(name.map(str::to_owned), MEMFD_CREATE_FLAGS))
|
|
.map(Self::take_ownership_of_unchecked)?; // Ensures `fd` is dropped if any subsequent calls fail
|
|
|
|
#[cfg(feature="logging")]
|
|
let using_memfile = debug_span!("setup_memfd", fd = ?fd.0.get());
|
|
{
|
|
#[cfg(feature="logging")]
|
|
let _span = using_memfile.enter();
|
|
|
|
if len > 0 {
|
|
attempt_call!(-1
|
|
, fallocate(fd.0.get(), 0, 0, len.try_into()
|
|
.map_err(|_| Allocate(None, len))?)
|
|
, Allocate(Some(fd.fileno().clone()), len))?;
|
|
if cfg!(debug_assertions) {
|
|
if_trace!(trace!("Allocated {len} bytes to memory buffer"));
|
|
let seeked;
|
|
assert_eq!(attempt_call!(-1
|
|
, { seeked = libc::lseek(fd.0.get(), 0, libc::SEEK_CUR); seeked }
|
|
, io::Error::last_os_error())
|
|
.expect("Failed to check seek position in fd")
|
|
, 0, "memfd seek position is non-zero after fallocate()");
|
|
if_trace!(if seeked != 0 { warn!("Seek offset is non-zero: {seeked}") } else { trace!("Seek offset verified ok") });
|
|
}
|
|
} else {
|
|
if_trace!(trace!("No length provided, skipping fallocate() call"));
|
|
}
|
|
}
|
|
Ok(fd)
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
impl io::Write for RawFile
|
|
{
|
|
#[inline]
|
|
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
|
match unsafe {
|
|
libc::write(self.0.get(), buf.as_ptr() as *const _, buf.len())
|
|
} {
|
|
-1 => Err(io::Error::last_os_error()),
|
|
wr => Ok(wr as usize)
|
|
}
|
|
}
|
|
#[inline]
|
|
fn flush(&mut self) -> io::Result<()> {
|
|
// Not buffered
|
|
Ok(())
|
|
}
|
|
|
|
#[inline]
|
|
fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result<usize> {
|
|
// SAFETY: IoSlice is guaranteed to be ABI-compatible with `struct iovec`
|
|
match unsafe {
|
|
libc::writev(self.0.get(), bufs.as_ptr() as *const _, bufs.len() as i32)
|
|
} {
|
|
-1 => Err(io::Error::last_os_error()),
|
|
wr => Ok(wr as usize)
|
|
}
|
|
}
|
|
}
|
|
|
|
impl io::Read for RawFile
|
|
{
|
|
#[inline]
|
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
|
match unsafe {
|
|
libc::read(self.0.get(), buf.as_mut_ptr() as *mut _, buf.len())
|
|
} {
|
|
-1 => Err(io::Error::last_os_error()),
|
|
wr => Ok(wr as usize)
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
fn read_vectored(&mut self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result<usize> {
|
|
// SAFETY: IoSlice is guaranteed to be ABI-compatible with `struct iovec`
|
|
match unsafe {
|
|
libc::readv(self.0.get(), bufs.as_mut_ptr() as *mut _, bufs.len() as i32)
|
|
} {
|
|
-1 => Err(io::Error::last_os_error()),
|
|
wr => Ok(wr as usize)
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<fs::File> for RawFile
|
|
{
|
|
#[inline]
|
|
fn from(from: fs::File) -> Self
|
|
{
|
|
Self::from_file(from)
|
|
}
|
|
}
|
|
|
|
impl From<RawFile> for fs::File
|
|
{
|
|
#[inline]
|
|
fn from(from: RawFile) -> Self
|
|
{
|
|
from.into_file()
|
|
}
|
|
}
|
|
|
|
impl ops::Drop for RawFile
|
|
{
|
|
#[inline]
|
|
fn drop(&mut self) {
|
|
unsafe {
|
|
libc::close(self.0.get());
|
|
}
|
|
}
|
|
}
|
|
|
|
impl AsRawFd for RawFile
|
|
{
|
|
#[inline]
|
|
fn as_raw_fd(&self) -> RawFd {
|
|
self.0.get()
|
|
}
|
|
}
|
|
|
|
impl FromRawFd for RawFile
|
|
{
|
|
#[inline]
|
|
unsafe fn from_raw_fd(fd: RawFd) -> Self {
|
|
Self(fd::RawFileDescriptor::new(fd))
|
|
}
|
|
}
|
|
|
|
impl IntoRawFd for RawFile
|
|
{
|
|
#[inline]
|
|
fn into_raw_fd(self) -> RawFd {
|
|
let fd = self.0.get();
|
|
mem::forget(self); // prevent close
|
|
fd
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests
|
|
{
|
|
use super::*;
|
|
#[test]
|
|
fn memory_mapping() -> eyre::Result<()>
|
|
{
|
|
use std::io::*;
|
|
const STRING: &[u8] = b"Hello world!";
|
|
let mut file = {
|
|
let mut file = RawFile::open_mem(None, 4096)?;
|
|
file.write_all(STRING)?;
|
|
let mut file = fs::File::from(file);
|
|
file.seek(SeekFrom::Start(0))?;
|
|
file
|
|
};
|
|
let v = {
|
|
let mut buf = vec![0; STRING.len()];
|
|
file.read_exact(&mut buf[..])?;
|
|
buf
|
|
};
|
|
|
|
assert_eq!(v.len(), STRING.len(), "Invalid read size.");
|
|
assert_eq!(&v[..], &STRING[..], "Invalid read data.");
|
|
Ok(())
|
|
}
|
|
}
|