diff --git a/src/flags.rs b/src/flags.rs index 4a263ac..61a0f4f 100644 --- a/src/flags.rs +++ b/src/flags.rs @@ -89,7 +89,7 @@ pub enum Flush impl Flush { #[inline(always)] - const fn get_ms(self) -> c_int + pub(super) const fn get_ms(self) -> c_int { use libc::{ MS_SYNC, MS_ASYNC, @@ -116,7 +116,7 @@ pub enum Advice { impl Advice { #[inline(always)] - const fn get_madv(self) -> c_int + pub(crate) const fn get_madv(self) -> c_int { use libc::{ MADV_NORMAL, diff --git a/src/lib.rs b/src/lib.rs index fe864b4..68c9033 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,9 +5,19 @@ use libc::{ }; use std::{ + os::unix::prelude::*, ops, mem, - ptr, + ptr::{ + self, + NonNull, + }, + io, + fmt, error, + + borrow::{ + Borrow, BorrowMut, + } }; mod uniq; @@ -40,3 +50,387 @@ pub struct MappedFile } //TODO: continue copying from the `TODO` line in `utf8encode/src/mmap.rs` +impl MappedFile { + /// Map the file `file` to `len` bytes with memory protection as provided by `perm`, and + /// mapping flags provided by `flags`. + /// + /// # Returns + /// If `mmap()` fails, then the current `errno` is returned alongside the `file` that was passed in, otherwise, a new mapping is + /// constructed over `file`, and that is returned. + /// + /// # Panics + /// If `mmap()` succeeds, but returns an invalid address (e.g. 0) + pub fn try_new(file: T, len: usize, perm: Perm, flags: Flags) -> Result> + { + #[inline(never)] + #[cold] + fn _panic_invalid_address() -> ! + { + panic!("Invalid/unsupported address returned from mmap()") + } + const NULL: *mut libc::c_void = ptr::null_mut(); + let fd = file.as_raw_fd(); + let slice = match unsafe { + mmap(ptr::null_mut(), len, perm.get_prot(), flags.get_flags(), fd, 0) + } { + MAP_FAILED => return Err(TryNewError::wrap_last_error(file)), + NULL => _panic_invalid_address(), + ptr => unsafe { + UniqueSlice { + mem: NonNull::new_unchecked(ptr as *mut u8), + end: match NonNull::new((ptr as *mut u8).add(len)) { + Some(n) => n, + _ => _panic_invalid_address(), + }, + } + }, + }; + Ok(Self { + file, + map: MappedSlice(slice) + }) + } + + /// Map the file `file` to `len` bytes with memory protection as provided by `perm`, and + /// mapping flags provided by `flags`. + /// + /// # Returns + /// If `mmap()` fails, then the current `errno` set by `mmap()` is returned, otherwise, a new mapping is + /// constructed over `file`, and that is returned. + /// If `mmap()` fails, `file` is dropped. To retain `file`, use `try_new()`. + /// + /// # Panics + /// If `mmap()` succeeds, but returns an invalid address (e.g. 0) + #[inline] + pub fn new(file: T, len: usize, perm: Perm, flags: Flags) -> io::Result + { + Self::try_new(file, len, perm, flags).map_err(Into::into) + } + + /// Sync the mapped memory to the backing file store via `msync()`. + /// + /// If this is a private mapping, or is mapped over a private file descriptor that does not refer to on-disk persistent storage, syncing the data is usually pointless. + /// + /// # Returns + /// If `msync()` fails. + pub fn flush(&mut self, flush: Flush) -> io::Result<()> + { + use libc::msync; + match unsafe { + msync(self.map.0.as_mut_ptr() as *mut _, self.map.0.len(), flush.get_ms()) + } { + 0 => Ok(()), + _ => Err(io::Error::last_os_error()) + } + } + + /// Replace the mapped file object with another that aliases the same file descriptor. + /// + /// # Warning + /// * The old file object is *not* dropped to prevent the file descriptor being closed. (see `replace_inner_unchecked()`). + /// If `T` contains other resources, this can cause a memory leak. + /// + /// # Panics + /// If `other`'s `AsRawFd` impl *does not* alias the already contained `T`'s. + pub fn replace_inner(self, other: U) -> MappedFile + { + assert_eq!(self.file.as_raw_fd(), other.as_raw_fd(), "File descriptors must alias"); + unsafe { + let (this, file) = self.replace_inner_unchecked(other); + mem::forget(file); + this + } + } + + /// Unmap the memory contained in `T` and return it. + /// Before the memory is unmapped, it is `msync()`'d according to `flush`. + /// + /// # Panics + /// If `msync()` fails. + #[inline] + pub fn into_inner_synced(mut self, flush: Flush) -> T + { + self.flush(flush).expect("Failed to sync data"); + drop(self.map); + self.file + } +} + +impl MappedFile { + + + #[inline(always)] + fn raw_parts(&self) -> (*mut u8, usize) + { + (self.map.0.mem.as_ptr(), self.map.0.len()) + } + + /// Set advise according to `adv`, and optionally advise the kernel on if the memory will be needed or not. + pub fn advise(&mut self, adv: Advice, needed: Option) -> io::Result<()> + { + use libc::{ + madvise, + MADV_WILLNEED, + MADV_DONTNEED + }; + let (addr, len) = self.raw_parts(); + match unsafe { madvise(addr as *mut _, len, adv.get_madv() | needed.map(|n| n.then(|| MADV_WILLNEED).unwrap_or(MADV_DONTNEED)).unwrap_or(0)) } { + 0 => Ok(()), + _ => Err(io::Error::last_os_error()) + } + } + + /// With advice, used as a builder-pattern alternative for `advise()`. + /// + /// # Returns + /// If `madvise()` fails, then the `io::Error` along with the previous instance is returned. + #[inline(always)] + pub fn try_with_advice(mut self, adv: Advice, needed: Option) -> Result> + { + match self.advise(adv, needed) { + Ok(_) => Ok(self), + Err(error) => Err(TryNewError { + error: Box::new(error), + value: self, + }) + } + } + + /// With advice, used as a builder-pattern alternative for `advise()`. + /// + /// # Returns + /// If `madvise()` fails, then the mapping is dropped and the error is returned. To keep the previous instance if the call failes, use `try_with_advice()`. + #[inline] + pub fn with_advice(self, adv: Advice, needed: Option) -> io::Result + { + self.try_with_advice(adv, needed).map_err(Into::into) + } + + /// Replace the inner file with another without checking static or dynamic bounding. + /// This function is extremely unsafe if the following conditions are not met in entirity. + /// + /// # Safety + /// * `U` and `T` **must** have an `AsRawFd::as_raw_fd()` impl that returns the same `RawFd` value unconditionally. + /// * The returned `T` in the tuple **must not** attempt close the file descriptor while the returned `MappedFile` in the tuple is alive. + /// * The returned values **should not** *both* attempt to close the file descriptor when dropped. To prevent the `MappedFile` from attempting to close the file descriptor, use `MappedFile::into_inner()` and ensure `U` does not close the file descriptor while `T` is alive. Alternatively, use a mechanism of `T` to prevent it from closing the file descriptor while `U` is alive. + #[inline(always)] + pub unsafe fn replace_inner_unchecked(self, other: U) -> (MappedFile, T) + { + let MappedFile{ file, map } = self; + (MappedFile { + file: other, + map + }, file) + } + + /// Unmap the memory contained in `T` and return it. + /// + /// # Warning + /// If the map is shared, or refers to a persistent file on disk, you should call `flush()` + /// first or use `into_inner_synced()` + #[inline] + pub fn into_inner(self) -> T + { + drop(self.map); + self.file + } + + /// The size of the mapped memory + #[inline] + pub fn len(&self) -> usize + { + self.map.0.len() + } + + /// Get a slice of the mapped memory + #[inline] + pub fn as_slice(&self) -> &[u8] + { + &self.map.0[..] + } + + /// Get a mutable slice of the mapped memory + #[inline] + pub fn as_slice_mut(&mut self) -> &mut [u8] + { + &mut self.map.0[..] + } + + /// Get a raw slice of the mapped memory + #[inline] + pub fn as_raw_slice(&self) -> *const [u8] + { + self.map.0.as_raw_slice() + } + + /// Get a raw mutable slice of the mapped memory + #[inline] + pub fn as_raw_slice_mut(&mut self) -> *mut [u8] + { + self.map.0.as_raw_slice_mut() + } + + /// Checks if the mapping dangles (i.e. `len() == 0`.) + #[inline] + pub fn is_empty(&self) -> bool + { + self.map.0.is_empty() + } +} + +/// Contains an OS error and a value. +pub struct TryNewError +{ + error: Box, + value: T +} + +impl error::Error for TryNewError +{ + #[inline] + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + Some(self.error.as_ref()) + } +} + +impl fmt::Display for TryNewError +{ + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + write!(f, "error in mapping of type {}", std::any::type_name::()) + } +} + +impl fmt::Debug for TryNewError +{ + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + f.debug_struct("TryNewError") + .field("error", &self.error) + .finish_non_exhaustive() + } +} + +impl TryNewError +{ + /// A reference to the value + #[inline] + pub fn value(&self) -> &T + { + &self.value + } + /// A mutable reference to the value + #[inline] + pub fn value_mut(&mut self) -> &mut T + { + &mut self.value + } + /// A reference to the IO error + #[inline] + pub fn error(&self) -> &io::Error + { + &self.error + } + /// Consume a boxed instance and return the boxed IO error. + #[inline] + pub fn into_error_box(self: Box) -> Box + { + self.error + } +} + +impl TryNewError +{ + #[inline] + fn wrap_last_error(value: T) -> Self + { + Self { + error: Box::new(io::Error::last_os_error()), + value, + } + } + /// Consume into the contained value + #[inline] + pub fn into_inner(self) -> T + { + self.value + } + + /// Consume into the IO error + #[inline] + pub fn into_error(self) -> io::Error + { + *self.error + } + /// Consume into the value and the error. + #[inline] + pub fn into_parts(self) -> (T, io::Error) + { + (self.value, *self.error) + } +} + +impl From>> for io::Error +{ + #[inline] + fn from(from: Box>) -> Self + { + *from.error + } +} + +impl From> for io::Error +{ + #[inline] + fn from(from: TryNewError) -> Self + { + from.into_error() + } +} + +impl Borrow for MappedFile +{ + #[inline] + fn borrow(&self) -> &T + { + &self.file + } +} + +impl Borrow<[u8]> for MappedFile +{ + #[inline] + fn borrow(&self) -> &[u8] + { + self.as_slice() + } +} + +impl BorrowMut<[u8]> for MappedFile +{ + #[inline] + fn borrow_mut(&mut self) -> &mut [u8] + { + self.as_slice_mut() + } +} + +impl ops::Deref for MappedFile +{ + type Target= [u8]; + #[inline] + fn deref(&self) -> &Self::Target + { + self.as_slice() + } +} +impl ops::DerefMut for MappedFile +{ + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target + { + self.as_slice_mut() + } +}