memfile::hp: Changed `get_masks()` to return an iterator of `SizedMask` instead of just `Mask` to retain the size of the huge-page itself in bytes (for `ftruncate()` calls, and `mmap()` calls.)

memfile::hp: Switched from `fallocate()` to `ftruncate()`: This system-call actually does allow the size to be set, as long as it is a multiple of the huge-page size used to create it.

memfile:: Added `RawFile::truncate_size()`: Set size using `ftruncate()` instead of `fallocate()`: This difference matters a lot with hugetlbfs-backed FDs.

Fortune for collect's current commit: Blessing − 吉
hugetlb
Avril 3 years ago
parent 8ea3a23e27
commit 35c6cabce3
Signed by: flanchan
GPG Key ID: 284488987C31F630

@ -284,6 +284,7 @@ impl RawFile
/// # Note /// # Note
/// This does not *extend* the file's capacity, it is instead similar to `fs::File::set_len()`. /// This does not *extend* the file's capacity, it is instead similar to `fs::File::set_len()`.
#[cfg_attr(feature="logging", instrument(err))] #[cfg_attr(feature="logging", instrument(err))]
#[inline]
pub fn allocate_size(&mut self, size: u64) -> io::Result<()> pub fn allocate_size(&mut self, size: u64) -> io::Result<()>
{ {
use libc::{ fallocate, off_t}; use libc::{ fallocate, off_t};
@ -296,6 +297,25 @@ impl RawFile
} }
} }
/// 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. /// Open a new in-memory (W+R) file with an optional name and a fixed size.
#[cfg_attr(feature="logging", instrument(err))] #[cfg_attr(feature="logging", instrument(err))]
pub fn open_mem(name: Option<&str>, len: usize) -> Result<Self, error::MemfileError> pub fn open_mem(name: Option<&str>, len: usize) -> Result<Self, error::MemfileError>

@ -40,7 +40,7 @@ const CHECKED_MASK_CREATION: bool = if cfg!(feature="hugepage-checked-masks") ||
/// For most use-cases, `get_masks()` should be fine. /// For most use-cases, `get_masks()` should be fine.
#[cfg_attr(feature="logging", instrument(err, skip_all, fields(path = ?path.as_ref())))] #[cfg_attr(feature="logging", instrument(err, skip_all, fields(path = ?path.as_ref())))]
#[inline] #[inline]
pub fn get_masks_in<P>(path: P) -> eyre::Result<impl Iterator<Item=eyre::Result<Mask>> + 'static> pub fn get_masks_in<P>(path: P) -> eyre::Result<impl Iterator<Item=eyre::Result<SizedMask>> + 'static>
where P: AsRef<Path> where P: AsRef<Path>
{ {
let path = path.as_ref(); let path = path.as_ref();
@ -69,8 +69,11 @@ where P: AsRef<Path>
.with_section(|| ok.header("Bytes were")) .with_section(|| ok.header("Bytes were"))
.with_section(move || format!("{path:?}").header("Checked path was")) .with_section(move || format!("{path:?}").header("Checked path was"))
.with_section(root_path_section.clone())) .with_section(root_path_section.clone()))
.and_then(|mask| Ok(SizedMask{mask, size: ok.try_into().wrap_err("Size was larger than `u64`")?}))
// .map(|mask| -> eyre::Result<_> { Ok(SizedMask{mask, size: ok.try_into()?}) })
} else { } else {
Ok(Mask::new(ok)) Ok(SizedMask{ mask: Mask::new(ok), size: ok as u64 })
} }
}, },
Ok((None, path)) => Err(eyre!("Failed to extract bytes from path")) Ok((None, path)) => Err(eyre!("Failed to extract bytes from path"))
@ -84,51 +87,156 @@ where P: AsRef<Path>
/// Find all `Mask`s on this system. /// Find all `Mask`s on this system.
#[cfg_attr(feature="logging", instrument(level="trace"))] #[cfg_attr(feature="logging", instrument(level="trace"))]
#[inline] #[inline]
pub fn get_masks() -> eyre::Result<impl Iterator<Item=eyre::Result<Mask>> + 'static> pub fn get_masks() -> eyre::Result<impl Iterator<Item=eyre::Result<SizedMask>> + 'static>
{ {
get_masks_in(HUGEPAGE_SIZES_LOCATION) get_masks_in(HUGEPAGE_SIZES_LOCATION)
} }
/// A huge-page mask that can be bitwise OR'd with `HUGETLB_MASK`, but retains the size of that huge-page.
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Copy)]
pub struct SizedMask
{
mask: Mask,
size: u64,
}
impl SizedMask
{
#[inline]
pub const fn size(&self) -> u64
{
self.size
}
#[inline]
pub const fn as_mask(&self) -> &Mask
{
&self.mask
}
}
impl std::borrow::Borrow<Mask> for SizedMask
{
#[inline]
fn borrow(&self) -> &Mask {
&self.mask
}
}
impl std::ops::Deref for SizedMask
{
type Target = Mask;
#[inline]
fn deref(&self) -> &Self::Target {
&self.mask
}
}
impl From<SizedMask> for Mask
{
#[inline]
fn from(from: SizedMask) -> Self
{
from.mask
}
}
/// A huge-page mask that can be bitwise OR'd with `HUGETLB_MASK`. /// A huge-page mask that can be bitwise OR'd with `HUGETLB_MASK`.
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Copy)] #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Copy)]
#[repr(transparent)] #[repr(transparent)]
pub struct Mask(c_uint); pub struct Mask(c_uint);
impl fmt::Display for Mask /// `Mask` and `SizedMask` trait impls
{ const _:() = {
macro_rules! mask_impls {
($name:ident) => {
impl fmt::Display for $name
{
#[inline] #[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
{ {
write!(f, "{}", self.raw()) write!(f, "{}", self.raw())
} }
} }
impl fmt::LowerHex for Mask impl fmt::LowerHex for $name
{ {
#[inline] #[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
{ {
write!(f, "0x{:x}", self.raw()) write!(f, "0x{:x}", self.raw())
} }
} }
impl fmt::UpperHex for Mask impl fmt::UpperHex for $name
{ {
#[inline] #[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
{ {
write!(f, "0x{:X}", self.raw()) write!(f, "0x{:X}", self.raw())
} }
} }
impl fmt::Binary for Mask impl fmt::Binary for $name
{ {
#[inline] #[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
{ {
write!(f, "0b{:b}", self.raw()) write!(f, "0b{:b}", self.raw())
} }
} }
// Comparisons
impl PartialEq<c_uint> for $name
{
#[inline]
fn eq(&self, &other: &c_uint) -> bool
{
self.mask() == other
}
}
impl PartialEq<c_int> for $name
{
#[inline]
fn eq(&self, &other: &c_int) -> bool
{
self.raw() == other
}
}
};
}
mask_impls!(Mask);
mask_impls!(SizedMask);
impl ops::BitOr<c_uint> for Mask
{
type Output= c_uint;
#[inline]
fn bitor(self, rhs: c_uint) -> Self::Output {
self.mask() | rhs
}
}
impl ops::BitOr for Mask
{
type Output= Self;
#[inline]
fn bitor(self, rhs: Self) -> Self::Output {
Self(self.0 | rhs.0)
}
}
impl ops::BitOrAssign for Mask
{
#[inline]
fn bitor_assign(&mut self, rhs: Self) {
self.0 |= rhs.0;
}
}
};
#[inline] #[inline]
const fn log2_usize(x: usize) -> usize { const fn log2_usize(x: usize) -> usize {
@ -256,49 +364,6 @@ impl TryFrom<usize> for Mask
} }
} }
impl ops::BitOr<c_uint> for Mask
{
type Output= c_uint;
#[inline]
fn bitor(self, rhs: c_uint) -> Self::Output {
self.mask() | rhs
}
}
impl ops::BitOr for Mask
{
type Output= Self;
#[inline]
fn bitor(self, rhs: Self) -> Self::Output {
Self(self.0 | rhs.0)
}
}
impl ops::BitOrAssign for Mask
{
#[inline]
fn bitor_assign(&mut self, rhs: Self) {
self.0 |= rhs.0;
}
}
impl PartialEq<c_uint> for Mask
{
#[inline]
fn eq(&self, &other: &c_uint) -> bool
{
self.mask() == other
}
}
impl PartialEq<c_int> for Mask
{
#[inline]
fn eq(&self, &other: &c_int) -> bool
{
self.raw() == other
}
}
//TODO: add test `.memfd_create_wrapper{,_flags}()` usage, too with some `MAP_HUGE_` constants as sizes //TODO: add test `.memfd_create_wrapper{,_flags}()` usage, too with some `MAP_HUGE_` constants as sizes
/// Take a directory path and try to parse the hugepage size from it. /// Take a directory path and try to parse the hugepage size from it.
@ -539,10 +604,38 @@ mod tests
(masks > 0).then(|| drop(println!("Found {masks} masks on system"))).ok_or(eyre!("Found no masks")) (masks > 0).then(|| drop(println!("Found {masks} masks on system"))).ok_or(eyre!("Found no masks"))
} }
//#[test] TODO: XXX: AAAAA: system does not support huge-page memfd_create() allocations!?!?!?!? #[test]
// TODO: Or am I missing something here? Does it pre-allocate? What is this? fn hugetlb_truncate_succeeds() -> eyre::Result<()>
fn memfd_create_wrapper() -> eyre::Result<()> {
/// XXX: Temporary alias until we have a owning `mmap()`'d fd data-structure that impl's `From<impl IntoRawFd>`
type MappedFile = fs::File;
use std::ffi::CString;
let name = CString::new(Vec::from_iter(b"memfd_create_wrapper() test".into_iter().copied())).unwrap();
let mask = super::get_masks()?.next().ok_or(eyre!("No masks found"))?.wrap_err("Failed to extract mask")?;
eprintln!("Using mask: {mask:x} ({mask:b})");
let create = mask.memfd_create_wrapper_flags();
let file: MappedFile = {
let mut file = create(name.as_ptr(), super::MEMFD_CREATE_FLAGS).wrap_err(eyre!("Failed to create file"))?;
println!("Created file {file:?}, attempting ftruncate({})", mask.size());
// XXX: Note: `fallocate()` fails on hugetlb files, but `ftruncate()` does not.
file.truncate_size(mask.size()).wrap_err(eyre!("ftruncate() failed"))?;
println!("Set file-size to {}", mask.size());
file
}.into();
drop(file); //TODO: `mmap()` file to `mask.size()`.
Ok(())
}
#[test]
#[should_panic]
// TODO: `write()` syscall is not allowed here. Try to come up with an example that uses `splice()` and `send_file()`.
fn hugetlb_write_fails()
{ {
fn _hugetlb_write_fails() -> eyre::Result<()> {
//crate::init()?; //crate::init()?;
use std::ffi::CString; use std::ffi::CString;
@ -554,9 +647,10 @@ mod tests
let mut buf = vec![0; name.as_bytes_with_nul().len()]; let mut buf = vec![0; name.as_bytes_with_nul().len()];
println!("Allocated {} bytes for buffer", buf.len()); println!("Allocated {} bytes for buffer", buf.len());
let mut file: fs::File = { let mut file: fs::File = {
let mut file = unsafe {super::RawFile::from_raw_fd( libc::memfd_create(name.as_ptr(), super::MEMFD_CREATE_FLAGS | mask.mask()) ) };//.wrap_err(eyre!("Failed to create file"))?; let mut file = create(name.as_ptr(), super::MEMFD_CREATE_FLAGS)/*unsafe {super::RawFile::from_raw_fd( libc::memfd_create(name.as_ptr(), super::MEMFD_CREATE_FLAGS | mask.mask()) ) };*/.wrap_err(eyre!("Failed to create file"))?;
println!("Created file {file:?}"); println!("Created file {file:?}, truncating to length of mask: {}", mask.size());
file.allocate_size(buf.len() as u64).wrap_err(eyre!("fallocate() failed"))?; // XXX: Note: `fallocate()` fails on hugetlb files, but `ftruncate()` does not.
file.truncate_size(mask.size()).wrap_err(eyre!("ftruncate() failed"))?;
println!("Set file-size to {}", buf.len()); println!("Set file-size to {}", buf.len());
file file
}.into(); }.into();
@ -579,5 +673,7 @@ mod tests
Ok(()) Ok(())
} }
_hugetlb_write_fails().unwrap();
}
} }
} }

Loading…
Cancel
Save