diff options
Diffstat (limited to 'third_party/rust/memmap2/src/unix.rs')
-rw-r--r-- | third_party/rust/memmap2/src/unix.rs | 458 |
1 files changed, 458 insertions, 0 deletions
diff --git a/third_party/rust/memmap2/src/unix.rs b/third_party/rust/memmap2/src/unix.rs new file mode 100644 index 0000000000..bd8fcf32a8 --- /dev/null +++ b/third_party/rust/memmap2/src/unix.rs @@ -0,0 +1,458 @@ +extern crate libc; + +use std::fs::File; +use std::mem::ManuallyDrop; +use std::os::unix::io::{FromRawFd, RawFd}; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::{io, ptr}; + +#[cfg(any( + all(target_os = "linux", not(target_arch = "mips")), + target_os = "freebsd", + target_os = "android" +))] +const MAP_STACK: libc::c_int = libc::MAP_STACK; + +#[cfg(not(any( + all(target_os = "linux", not(target_arch = "mips")), + target_os = "freebsd", + target_os = "android" +)))] +const MAP_STACK: libc::c_int = 0; + +#[cfg(any(target_os = "linux", target_os = "android"))] +const MAP_POPULATE: libc::c_int = libc::MAP_POPULATE; + +#[cfg(not(any(target_os = "linux", target_os = "android")))] +const MAP_POPULATE: libc::c_int = 0; + +#[cfg(any(target_os = "linux", target_os = "android"))] +const MAP_HUGETLB: libc::c_int = libc::MAP_HUGETLB; + +#[cfg(target_os = "linux")] +const MAP_HUGE_MASK: libc::c_int = libc::MAP_HUGE_MASK; + +#[cfg(any(target_os = "linux", target_os = "android"))] +const MAP_HUGE_SHIFT: libc::c_int = libc::MAP_HUGE_SHIFT; + +#[cfg(not(any(target_os = "linux", target_os = "android")))] +const MAP_HUGETLB: libc::c_int = 0; + +#[cfg(not(target_os = "linux"))] +const MAP_HUGE_MASK: libc::c_int = 0; + +#[cfg(not(any(target_os = "linux", target_os = "android")))] +const MAP_HUGE_SHIFT: libc::c_int = 0; + +#[cfg(any( + target_os = "android", + all(target_os = "linux", not(target_env = "musl")) +))] +use libc::{mmap64 as mmap, off64_t as off_t}; + +#[cfg(not(any( + target_os = "android", + all(target_os = "linux", not(target_env = "musl")) +)))] +use libc::{mmap, off_t}; + +pub struct MmapInner { + ptr: *mut libc::c_void, + len: usize, +} + +impl MmapInner { + /// Creates a new `MmapInner`. + /// + /// This is a thin wrapper around the `mmap` system call. + fn new( + len: usize, + prot: libc::c_int, + flags: libc::c_int, + file: RawFd, + offset: u64, + ) -> io::Result<MmapInner> { + let alignment = offset % page_size() as u64; + let aligned_offset = offset - alignment; + + let (map_len, map_offset) = Self::adjust_mmap_params(len, alignment as usize)?; + + unsafe { + let ptr = mmap( + ptr::null_mut(), + map_len as libc::size_t, + prot, + flags, + file, + aligned_offset as off_t, + ); + + if ptr == libc::MAP_FAILED { + Err(io::Error::last_os_error()) + } else { + Ok(Self::from_raw_parts(ptr, len, map_offset)) + } + } + } + + fn adjust_mmap_params(len: usize, alignment: usize) -> io::Result<(usize, usize)> { + use std::isize; + + // Rust's slice cannot be larger than isize::MAX. + // See https://doc.rust-lang.org/std/slice/fn.from_raw_parts.html + // + // This is not a problem on 64-bit targets, but on 32-bit one + // having a file or an anonymous mapping larger than 2GB is quite normal + // and we have to prevent it. + // + // The code below is essentially the same as in Rust's std: + // https://github.com/rust-lang/rust/blob/db78ab70a88a0a5e89031d7ee4eccec835dcdbde/library/alloc/src/raw_vec.rs#L495 + if std::mem::size_of::<usize>() < 8 && len > isize::MAX as usize { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "memory map length overflows isize", + )); + } + + let map_len = len + alignment; + let map_offset = alignment; + + // `libc::mmap` does not support zero-size mappings. POSIX defines: + // + // https://pubs.opengroup.org/onlinepubs/9699919799/functions/mmap.html + // > If `len` is zero, `mmap()` shall fail and no mapping shall be established. + // + // So if we would create such a mapping, crate a one-byte mapping instead: + let map_len = map_len.max(1); + + // Note that in that case `MmapInner::len` is still set to zero, + // and `Mmap` will still dereferences to an empty slice. + // + // If this mapping is backed by an empty file, we create a mapping larger than the file. + // This is unusual but well-defined. On the same man page, POSIX further defines: + // + // > The `mmap()` function can be used to map a region of memory that is larger + // > than the current size of the object. + // + // (The object here is the file.) + // + // > Memory access within the mapping but beyond the current end of the underlying + // > objects may result in SIGBUS signals being sent to the process. The reason for this + // > is that the size of the object can be manipulated by other processes and can change + // > at any moment. The implementation should tell the application that a memory reference + // > is outside the object where this can be detected; otherwise, written data may be lost + // > and read data may not reflect actual data in the object. + // + // Because `MmapInner::len` is not incremented, this increment of `aligned_len` + // will not allow accesses past the end of the file and will not cause SIGBUS. + // + // (SIGBUS is still possible by mapping a non-empty file and then truncating it + // to a shorter size, but that is unrelated to this handling of empty files.) + Ok((map_len, map_offset)) + } + + /// Get the current memory mapping as a `(ptr, map_len, offset)` tuple. + /// + /// Note that `map_len` is the length of the memory mapping itself and + /// _not_ the one that would be passed to `from_raw_parts`. + fn as_mmap_params(&self) -> (*mut libc::c_void, usize, usize) { + let offset = self.ptr as usize % page_size(); + let len = self.len + offset; + + // There are two possible memory layouts we could have, depending on + // the length and offset passed when constructing this instance: + // + // 1. The "normal" memory layout looks like this: + // + // |<------------------>|<---------------------->| + // mmap ptr offset ptr public slice + // + // That is, we have + // - The start of the page-aligned memory mapping returned by mmap, + // followed by, + // - Some number of bytes that are memory mapped but ignored since + // they are before the byte offset requested by the user, followed + // by, + // - The actual memory mapped slice requested by the user. + // + // This maps cleanly to a (ptr, len, offset) tuple. + // + // 2. Then, we have the case where the user requested a zero-length + // memory mapping. mmap(2) does not support zero-length mappings so + // this crate works around that by actually making a mapping of + // length one. This means that we have + // - A length zero slice, followed by, + // - A single memory mapped byte + // + // Note that this only happens if the offset within the page is also + // zero. Otherwise, we have a memory map of offset bytes and not a + // zero-length memory map. + // + // This doesn't fit cleanly into a (ptr, len, offset) tuple. Instead, + // we fudge it slightly: a zero-length memory map turns into a + // mapping of length one and can't be told apart outside of this + // method without knowing the original length. + if len == 0 { + (self.ptr, 1, 0) + } else { + (unsafe { self.ptr.offset(-(offset as isize)) }, len, offset) + } + } + + /// Construct this `MmapInner` from its raw components + /// + /// # Safety + /// + /// - `ptr` must point to the start of memory mapping that can be freed + /// using `munmap(2)` (i.e. returned by `mmap(2)` or `mremap(2)`) + /// - The memory mapping at `ptr` must have a length of `len + offset`. + /// - If `len + offset == 0` then the memory mapping must be of length 1. + /// - `offset` must be less than the current page size. + unsafe fn from_raw_parts(ptr: *mut libc::c_void, len: usize, offset: usize) -> Self { + debug_assert_eq!(ptr as usize % page_size(), 0, "ptr not page-aligned"); + debug_assert!(offset < page_size(), "offset larger than page size"); + + Self { + ptr: ptr.add(offset), + len, + } + } + + pub fn map(len: usize, file: RawFd, offset: u64, populate: bool) -> io::Result<MmapInner> { + let populate = if populate { MAP_POPULATE } else { 0 }; + MmapInner::new( + len, + libc::PROT_READ, + libc::MAP_SHARED | populate, + file, + offset, + ) + } + + pub fn map_exec(len: usize, file: RawFd, offset: u64, populate: bool) -> io::Result<MmapInner> { + let populate = if populate { MAP_POPULATE } else { 0 }; + MmapInner::new( + len, + libc::PROT_READ | libc::PROT_EXEC, + libc::MAP_SHARED | populate, + file, + offset, + ) + } + + pub fn map_mut(len: usize, file: RawFd, offset: u64, populate: bool) -> io::Result<MmapInner> { + let populate = if populate { MAP_POPULATE } else { 0 }; + MmapInner::new( + len, + libc::PROT_READ | libc::PROT_WRITE, + libc::MAP_SHARED | populate, + file, + offset, + ) + } + + pub fn map_copy(len: usize, file: RawFd, offset: u64, populate: bool) -> io::Result<MmapInner> { + let populate = if populate { MAP_POPULATE } else { 0 }; + MmapInner::new( + len, + libc::PROT_READ | libc::PROT_WRITE, + libc::MAP_PRIVATE | populate, + file, + offset, + ) + } + + pub fn map_copy_read_only( + len: usize, + file: RawFd, + offset: u64, + populate: bool, + ) -> io::Result<MmapInner> { + let populate = if populate { MAP_POPULATE } else { 0 }; + MmapInner::new( + len, + libc::PROT_READ, + libc::MAP_PRIVATE | populate, + file, + offset, + ) + } + + /// Open an anonymous memory map. + pub fn map_anon( + len: usize, + stack: bool, + populate: bool, + huge: Option<u8>, + ) -> io::Result<MmapInner> { + let stack = if stack { MAP_STACK } else { 0 }; + let populate = if populate { MAP_POPULATE } else { 0 }; + let hugetlb = if huge.is_some() { MAP_HUGETLB } else { 0 }; + let offset = huge + .map(|mask| ((mask as u64) & (MAP_HUGE_MASK as u64)) << MAP_HUGE_SHIFT) + .unwrap_or(0); + MmapInner::new( + len, + libc::PROT_READ | libc::PROT_WRITE, + libc::MAP_PRIVATE | libc::MAP_ANON | stack | populate | hugetlb, + -1, + offset, + ) + } + + pub fn flush(&self, offset: usize, len: usize) -> io::Result<()> { + let alignment = (self.ptr as usize + offset) % page_size(); + let offset = offset as isize - alignment as isize; + let len = len + alignment; + let result = + unsafe { libc::msync(self.ptr.offset(offset), len as libc::size_t, libc::MS_SYNC) }; + if result == 0 { + Ok(()) + } else { + Err(io::Error::last_os_error()) + } + } + + pub fn flush_async(&self, offset: usize, len: usize) -> io::Result<()> { + let alignment = (self.ptr as usize + offset) % page_size(); + let offset = offset as isize - alignment as isize; + let len = len + alignment; + let result = + unsafe { libc::msync(self.ptr.offset(offset), len as libc::size_t, libc::MS_ASYNC) }; + if result == 0 { + Ok(()) + } else { + Err(io::Error::last_os_error()) + } + } + + fn mprotect(&mut self, prot: libc::c_int) -> io::Result<()> { + unsafe { + let alignment = self.ptr as usize % page_size(); + let ptr = self.ptr.offset(-(alignment as isize)); + let len = self.len + alignment; + let len = len.max(1); + if libc::mprotect(ptr, len, prot) == 0 { + Ok(()) + } else { + Err(io::Error::last_os_error()) + } + } + } + + pub fn make_read_only(&mut self) -> io::Result<()> { + self.mprotect(libc::PROT_READ) + } + + pub fn make_exec(&mut self) -> io::Result<()> { + self.mprotect(libc::PROT_READ | libc::PROT_EXEC) + } + + pub fn make_mut(&mut self) -> io::Result<()> { + self.mprotect(libc::PROT_READ | libc::PROT_WRITE) + } + + #[inline] + pub fn ptr(&self) -> *const u8 { + self.ptr as *const u8 + } + + #[inline] + pub fn mut_ptr(&mut self) -> *mut u8 { + self.ptr as *mut u8 + } + + #[inline] + pub fn len(&self) -> usize { + self.len + } + + pub fn advise(&self, advice: libc::c_int, offset: usize, len: usize) -> io::Result<()> { + let alignment = (self.ptr as usize + offset) % page_size(); + let offset = offset as isize - alignment as isize; + let len = len + alignment; + unsafe { + if libc::madvise(self.ptr.offset(offset), len, advice) != 0 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } + } + } + + #[cfg(target_os = "linux")] + pub fn remap(&mut self, new_len: usize, options: crate::RemapOptions) -> io::Result<()> { + let (old_ptr, old_len, offset) = self.as_mmap_params(); + let (map_len, offset) = Self::adjust_mmap_params(new_len, offset)?; + + unsafe { + let new_ptr = libc::mremap(old_ptr, old_len, map_len, options.into_flags()); + + if new_ptr == libc::MAP_FAILED { + Err(io::Error::last_os_error()) + } else { + // We explicitly don't drop self since the pointer within is no longer valid. + ptr::write(self, Self::from_raw_parts(new_ptr, new_len, offset)); + Ok(()) + } + } + } + + pub fn lock(&self) -> io::Result<()> { + unsafe { + if libc::mlock(self.ptr, self.len) != 0 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } + } + } + + pub fn unlock(&self) -> io::Result<()> { + unsafe { + if libc::munlock(self.ptr, self.len) != 0 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } + } + } +} + +impl Drop for MmapInner { + fn drop(&mut self) { + let (ptr, len, _) = self.as_mmap_params(); + + // Any errors during unmapping/closing are ignored as the only way + // to report them would be through panicking which is highly discouraged + // in Drop impls, c.f. https://github.com/rust-lang/lang-team/issues/97 + unsafe { libc::munmap(ptr, len as libc::size_t) }; + } +} + +unsafe impl Sync for MmapInner {} +unsafe impl Send for MmapInner {} + +fn page_size() -> usize { + static PAGE_SIZE: AtomicUsize = AtomicUsize::new(0); + + match PAGE_SIZE.load(Ordering::Relaxed) { + 0 => { + let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) as usize }; + + PAGE_SIZE.store(page_size, Ordering::Relaxed); + + page_size + } + page_size => page_size, + } +} + +pub fn file_len(file: RawFd) -> io::Result<u64> { + // SAFETY: We must not close the passed-in fd by dropping the File we create, + // we ensure this by immediately wrapping it in a ManuallyDrop. + unsafe { + let file = ManuallyDrop::new(File::from_raw_fd(file)); + Ok(file.metadata()?.len()) + } +} |