From 35c6cabce35d06cdb45c2fc9dab2c3665da7faa1 Mon Sep 17 00:00:00 2001 From: Avril Date: Sat, 21 May 2022 04:11:28 +0100 Subject: [PATCH] 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.) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 − 吉 --- src/memfile.rs | 20 ++++ src/memfile/hp.rs | 286 +++++++++++++++++++++++++++++++--------------- 2 files changed, 211 insertions(+), 95 deletions(-) diff --git a/src/memfile.rs b/src/memfile.rs index 5e69f29..c194eeb 100644 --- a/src/memfile.rs +++ b/src/memfile.rs @@ -284,6 +284,7 @@ impl RawFile /// # 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}; @@ -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. #[cfg_attr(feature="logging", instrument(err))] pub fn open_mem(name: Option<&str>, len: usize) -> Result diff --git a/src/memfile/hp.rs b/src/memfile/hp.rs index cad511e..6d7816d 100644 --- a/src/memfile/hp.rs +++ b/src/memfile/hp.rs @@ -40,7 +40,7 @@ const CHECKED_MASK_CREATION: bool = if cfg!(feature="hugepage-checked-masks") || /// For most use-cases, `get_masks()` should be fine. #[cfg_attr(feature="logging", instrument(err, skip_all, fields(path = ?path.as_ref())))] #[inline] -pub fn get_masks_in

(path: P) -> eyre::Result> + 'static> +pub fn get_masks_in

(path: P) -> eyre::Result> + 'static> where P: AsRef { let path = path.as_ref(); @@ -69,8 +69,11 @@ where P: AsRef .with_section(|| ok.header("Bytes were")) .with_section(move || format!("{path:?}").header("Checked path was")) .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 { - 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")) @@ -84,52 +87,157 @@ where P: AsRef /// Find all `Mask`s on this system. #[cfg_attr(feature="logging", instrument(level="trace"))] #[inline] -pub fn get_masks() -> eyre::Result> + 'static> +pub fn get_masks() -> eyre::Result> + 'static> { get_masks_in(HUGEPAGE_SIZES_LOCATION) } -/// 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`, but retains the size of that huge-page. #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Copy)] -#[repr(transparent)] -pub struct Mask(c_uint); +pub struct SizedMask +{ + mask: Mask, + size: u64, +} -impl fmt::Display for Mask +impl SizedMask { #[inline] - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + pub const fn size(&self) -> u64 { - write!(f, "{}", self.raw()) + self.size + } + #[inline] + pub const fn as_mask(&self) -> &Mask + { + &self.mask } } -impl fmt::LowerHex for Mask +impl std::borrow::Borrow for SizedMask { #[inline] - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result - { - write!(f, "0x{:x}", self.raw()) - } + fn borrow(&self) -> &Mask { + &self.mask + } } -impl fmt::UpperHex for Mask +impl std::ops::Deref for SizedMask { + type Target = Mask; #[inline] - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result - { - write!(f, "0x{:X}", self.raw()) - } + fn deref(&self) -> &Self::Target { + &self.mask + } } -impl fmt::Binary for Mask +impl From for Mask { #[inline] - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + fn from(from: SizedMask) -> Self { - write!(f, "0b{:b}", self.raw()) + from.mask } } + +/// A huge-page mask that can be bitwise OR'd with `HUGETLB_MASK`. +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Copy)] +#[repr(transparent)] +pub struct Mask(c_uint); + +/// `Mask` and `SizedMask` trait impls +const _:() = { + macro_rules! mask_impls { + ($name:ident) => { + impl fmt::Display for $name + { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + write!(f, "{}", self.raw()) + } + } + + impl fmt::LowerHex for $name + { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + write!(f, "0x{:x}", self.raw()) + } + } + + impl fmt::UpperHex for $name + { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + write!(f, "0x{:X}", self.raw()) + } + } + + impl fmt::Binary for $name + { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + write!(f, "0b{:b}", self.raw()) + } + } + + // Comparisons + + impl PartialEq for $name + { + #[inline] + fn eq(&self, &other: &c_uint) -> bool + { + self.mask() == other + } + } + impl PartialEq for $name + { + #[inline] + fn eq(&self, &other: &c_int) -> bool + { + self.raw() == other + } + } + + }; + } + + mask_impls!(Mask); + mask_impls!(SizedMask); + + impl ops::BitOr 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] const fn log2_usize(x: usize) -> usize { const BITS: usize = std::mem::size_of::() * 8usize; //XXX Is this okay to be hardcoded? I can't find CHAR_BIT in core, so... @@ -256,49 +364,6 @@ impl TryFrom for Mask } } - -impl ops::BitOr 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 for Mask -{ - #[inline] - fn eq(&self, &other: &c_uint) -> bool - { - self.mask() == other - } -} -impl PartialEq 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 /// Take a directory path and try to parse the hugepage size from it. @@ -539,45 +604,76 @@ mod tests (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!?!?!?!? - // TODO: Or am I missing something here? Does it pre-allocate? What is this? - fn memfd_create_wrapper() -> eyre::Result<()> + #[test] + fn hugetlb_truncate_succeeds() -> eyre::Result<()> { - //crate::init()?; + /// XXX: Temporary alias until we have a owning `mmap()`'d fd data-structure that impl's `From` + 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 buf = { - let mut buf = vec![0; name.as_bytes_with_nul().len()]; - println!("Allocated {} bytes for buffer", buf.len()); - 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"))?; - println!("Created file {file:?}"); - file.allocate_size(buf.len() as u64).wrap_err(eyre!("fallocate() failed"))?; - println!("Set file-size to {}", buf.len()); - file - }.into(); - - use std::io::{Read, Write, Seek}; - - - println!("Writing {} bytes {:?}...", name.as_bytes_with_nul().len(), name.as_bytes_with_nul()); - file.write_all(name.as_bytes_with_nul()).wrap_err(eyre!("Writing failed"))?; - println!("Seeking back to 0..."); - file.seek(std::io::SeekFrom::Start(0)).wrap_err(eyre!("Seeking failed"))?; - println!("Reading {} bytes...", buf.len()); - file.read_exact(&mut buf[..]).wrap_err(eyre!("Reading failed"))?; + 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()?; - println!("Read {} bytes into: {:?}", buf.len(), buf); - - buf - }; - assert_eq!(CString::from_vec_with_nul(buf).expect("Invalid contents read"), name); + 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 buf = { + let mut buf = vec![0; name.as_bytes_with_nul().len()]; + println!("Allocated {} bytes for buffer", buf.len()); + let mut file: fs::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:?}, truncating to length of mask: {}", 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 {}", buf.len()); + file + }.into(); + + use std::io::{Read, Write, Seek}; + + + println!("Writing {} bytes {:?}...", name.as_bytes_with_nul().len(), name.as_bytes_with_nul()); + file.write_all(name.as_bytes_with_nul()).wrap_err(eyre!("Writing failed"))?; + println!("Seeking back to 0..."); + file.seek(std::io::SeekFrom::Start(0)).wrap_err(eyre!("Seeking failed"))?; + println!("Reading {} bytes...", buf.len()); + file.read_exact(&mut buf[..]).wrap_err(eyre!("Reading failed"))?; + + println!("Read {} bytes into: {:?}", buf.len(), buf); + + buf + }; + assert_eq!(CString::from_vec_with_nul(buf).expect("Invalid contents read"), name); - Ok(()) + Ok(()) + } + _hugetlb_write_fails().unwrap(); } } }