| 1 | extern crate libc; |
| 2 | |
| 3 | use std::fs::File; |
| 4 | use std::mem::ManuallyDrop; |
| 5 | use std::os::unix::io::{FromRawFd, RawFd}; |
| 6 | use std::sync::atomic::{AtomicUsize, Ordering}; |
| 7 | use std::{io, ptr}; |
| 8 | |
| 9 | #[cfg (any( |
| 10 | all(target_os = "linux" , not(target_arch = "mips" )), |
| 11 | target_os = "freebsd" , |
| 12 | target_os = "android" |
| 13 | ))] |
| 14 | const MAP_STACK: libc::c_int = libc::MAP_STACK; |
| 15 | |
| 16 | #[cfg (not(any( |
| 17 | all(target_os = "linux" , not(target_arch = "mips" )), |
| 18 | target_os = "freebsd" , |
| 19 | target_os = "android" |
| 20 | )))] |
| 21 | const MAP_STACK: libc::c_int = 0; |
| 22 | |
| 23 | #[cfg (any(target_os = "linux" , target_os = "android" ))] |
| 24 | const MAP_POPULATE: libc::c_int = libc::MAP_POPULATE; |
| 25 | |
| 26 | #[cfg (not(any(target_os = "linux" , target_os = "android" )))] |
| 27 | const MAP_POPULATE: libc::c_int = 0; |
| 28 | |
| 29 | #[cfg (any(target_os = "linux" , target_os = "android" ))] |
| 30 | const MAP_HUGETLB: libc::c_int = libc::MAP_HUGETLB; |
| 31 | |
| 32 | #[cfg (target_os = "linux" )] |
| 33 | const MAP_HUGE_MASK: libc::c_int = libc::MAP_HUGE_MASK; |
| 34 | |
| 35 | #[cfg (any(target_os = "linux" , target_os = "android" ))] |
| 36 | const MAP_HUGE_SHIFT: libc::c_int = libc::MAP_HUGE_SHIFT; |
| 37 | |
| 38 | #[cfg (not(any(target_os = "linux" , target_os = "android" )))] |
| 39 | const MAP_HUGETLB: libc::c_int = 0; |
| 40 | |
| 41 | #[cfg (not(target_os = "linux" ))] |
| 42 | const MAP_HUGE_MASK: libc::c_int = 0; |
| 43 | |
| 44 | #[cfg (not(any(target_os = "linux" , target_os = "android" )))] |
| 45 | const MAP_HUGE_SHIFT: libc::c_int = 0; |
| 46 | |
| 47 | #[cfg (any( |
| 48 | target_os = "android" , |
| 49 | all(target_os = "linux" , not(target_env = "musl" )) |
| 50 | ))] |
| 51 | use libc::{mmap64 as mmap, off64_t as off_t}; |
| 52 | |
| 53 | #[cfg (not(any( |
| 54 | target_os = "android" , |
| 55 | all(target_os = "linux" , not(target_env = "musl" )) |
| 56 | )))] |
| 57 | use libc::{mmap, off_t}; |
| 58 | |
| 59 | pub struct MmapInner { |
| 60 | ptr: *mut libc::c_void, |
| 61 | len: usize, |
| 62 | } |
| 63 | |
| 64 | impl MmapInner { |
| 65 | /// Creates a new `MmapInner`. |
| 66 | /// |
| 67 | /// This is a thin wrapper around the `mmap` system call. |
| 68 | fn new( |
| 69 | len: usize, |
| 70 | prot: libc::c_int, |
| 71 | flags: libc::c_int, |
| 72 | file: RawFd, |
| 73 | offset: u64, |
| 74 | ) -> io::Result<MmapInner> { |
| 75 | let alignment = offset % page_size() as u64; |
| 76 | let aligned_offset = offset - alignment; |
| 77 | |
| 78 | let (map_len, map_offset) = Self::adjust_mmap_params(len, alignment as usize)?; |
| 79 | |
| 80 | unsafe { |
| 81 | let ptr = mmap( |
| 82 | ptr::null_mut(), |
| 83 | map_len as libc::size_t, |
| 84 | prot, |
| 85 | flags, |
| 86 | file, |
| 87 | aligned_offset as off_t, |
| 88 | ); |
| 89 | |
| 90 | if ptr == libc::MAP_FAILED { |
| 91 | Err(io::Error::last_os_error()) |
| 92 | } else { |
| 93 | Ok(Self::from_raw_parts(ptr, len, map_offset)) |
| 94 | } |
| 95 | } |
| 96 | } |
| 97 | |
| 98 | fn adjust_mmap_params(len: usize, alignment: usize) -> io::Result<(usize, usize)> { |
| 99 | use std::isize; |
| 100 | |
| 101 | // Rust's slice cannot be larger than isize::MAX. |
| 102 | // See https://doc.rust-lang.org/std/slice/fn.from_raw_parts.html |
| 103 | // |
| 104 | // This is not a problem on 64-bit targets, but on 32-bit one |
| 105 | // having a file or an anonymous mapping larger than 2GB is quite normal |
| 106 | // and we have to prevent it. |
| 107 | // |
| 108 | // The code below is essentially the same as in Rust's std: |
| 109 | // https://github.com/rust-lang/rust/blob/db78ab70a88a0a5e89031d7ee4eccec835dcdbde/library/alloc/src/raw_vec.rs#L495 |
| 110 | if std::mem::size_of::<usize>() < 8 && len > isize::MAX as usize { |
| 111 | return Err(io::Error::new( |
| 112 | io::ErrorKind::InvalidData, |
| 113 | "memory map length overflows isize" , |
| 114 | )); |
| 115 | } |
| 116 | |
| 117 | let map_len = len + alignment; |
| 118 | let map_offset = alignment; |
| 119 | |
| 120 | // `libc::mmap` does not support zero-size mappings. POSIX defines: |
| 121 | // |
| 122 | // https://pubs.opengroup.org/onlinepubs/9699919799/functions/mmap.html |
| 123 | // > If `len` is zero, `mmap()` shall fail and no mapping shall be established. |
| 124 | // |
| 125 | // So if we would create such a mapping, crate a one-byte mapping instead: |
| 126 | let map_len = map_len.max(1); |
| 127 | |
| 128 | // Note that in that case `MmapInner::len` is still set to zero, |
| 129 | // and `Mmap` will still dereferences to an empty slice. |
| 130 | // |
| 131 | // If this mapping is backed by an empty file, we create a mapping larger than the file. |
| 132 | // This is unusual but well-defined. On the same man page, POSIX further defines: |
| 133 | // |
| 134 | // > The `mmap()` function can be used to map a region of memory that is larger |
| 135 | // > than the current size of the object. |
| 136 | // |
| 137 | // (The object here is the file.) |
| 138 | // |
| 139 | // > Memory access within the mapping but beyond the current end of the underlying |
| 140 | // > objects may result in SIGBUS signals being sent to the process. The reason for this |
| 141 | // > is that the size of the object can be manipulated by other processes and can change |
| 142 | // > at any moment. The implementation should tell the application that a memory reference |
| 143 | // > is outside the object where this can be detected; otherwise, written data may be lost |
| 144 | // > and read data may not reflect actual data in the object. |
| 145 | // |
| 146 | // Because `MmapInner::len` is not incremented, this increment of `aligned_len` |
| 147 | // will not allow accesses past the end of the file and will not cause SIGBUS. |
| 148 | // |
| 149 | // (SIGBUS is still possible by mapping a non-empty file and then truncating it |
| 150 | // to a shorter size, but that is unrelated to this handling of empty files.) |
| 151 | Ok((map_len, map_offset)) |
| 152 | } |
| 153 | |
| 154 | /// Get the current memory mapping as a `(ptr, map_len, offset)` tuple. |
| 155 | /// |
| 156 | /// Note that `map_len` is the length of the memory mapping itself and |
| 157 | /// _not_ the one that would be passed to `from_raw_parts`. |
| 158 | fn as_mmap_params(&self) -> (*mut libc::c_void, usize, usize) { |
| 159 | let offset = self.ptr as usize % page_size(); |
| 160 | let len = self.len + offset; |
| 161 | |
| 162 | // There are two possible memory layouts we could have, depending on |
| 163 | // the length and offset passed when constructing this instance: |
| 164 | // |
| 165 | // 1. The "normal" memory layout looks like this: |
| 166 | // |
| 167 | // |<------------------>|<---------------------->| |
| 168 | // mmap ptr offset ptr public slice |
| 169 | // |
| 170 | // That is, we have |
| 171 | // - The start of the page-aligned memory mapping returned by mmap, |
| 172 | // followed by, |
| 173 | // - Some number of bytes that are memory mapped but ignored since |
| 174 | // they are before the byte offset requested by the user, followed |
| 175 | // by, |
| 176 | // - The actual memory mapped slice requested by the user. |
| 177 | // |
| 178 | // This maps cleanly to a (ptr, len, offset) tuple. |
| 179 | // |
| 180 | // 2. Then, we have the case where the user requested a zero-length |
| 181 | // memory mapping. mmap(2) does not support zero-length mappings so |
| 182 | // this crate works around that by actually making a mapping of |
| 183 | // length one. This means that we have |
| 184 | // - A length zero slice, followed by, |
| 185 | // - A single memory mapped byte |
| 186 | // |
| 187 | // Note that this only happens if the offset within the page is also |
| 188 | // zero. Otherwise, we have a memory map of offset bytes and not a |
| 189 | // zero-length memory map. |
| 190 | // |
| 191 | // This doesn't fit cleanly into a (ptr, len, offset) tuple. Instead, |
| 192 | // we fudge it slightly: a zero-length memory map turns into a |
| 193 | // mapping of length one and can't be told apart outside of this |
| 194 | // method without knowing the original length. |
| 195 | if len == 0 { |
| 196 | (self.ptr, 1, 0) |
| 197 | } else { |
| 198 | (unsafe { self.ptr.offset(-(offset as isize)) }, len, offset) |
| 199 | } |
| 200 | } |
| 201 | |
| 202 | /// Construct this `MmapInner` from its raw components |
| 203 | /// |
| 204 | /// # Safety |
| 205 | /// |
| 206 | /// - `ptr` must point to the start of memory mapping that can be freed |
| 207 | /// using `munmap(2)` (i.e. returned by `mmap(2)` or `mremap(2)`) |
| 208 | /// - The memory mapping at `ptr` must have a length of `len + offset`. |
| 209 | /// - If `len + offset == 0` then the memory mapping must be of length 1. |
| 210 | /// - `offset` must be less than the current page size. |
| 211 | unsafe fn from_raw_parts(ptr: *mut libc::c_void, len: usize, offset: usize) -> Self { |
| 212 | debug_assert_eq!(ptr as usize % page_size(), 0, "ptr not page-aligned" ); |
| 213 | debug_assert!(offset < page_size(), "offset larger than page size" ); |
| 214 | |
| 215 | Self { |
| 216 | ptr: ptr.add(offset), |
| 217 | len, |
| 218 | } |
| 219 | } |
| 220 | |
| 221 | pub fn map(len: usize, file: RawFd, offset: u64, populate: bool) -> io::Result<MmapInner> { |
| 222 | let populate = if populate { MAP_POPULATE } else { 0 }; |
| 223 | MmapInner::new( |
| 224 | len, |
| 225 | libc::PROT_READ, |
| 226 | libc::MAP_SHARED | populate, |
| 227 | file, |
| 228 | offset, |
| 229 | ) |
| 230 | } |
| 231 | |
| 232 | pub fn map_exec(len: usize, file: RawFd, offset: u64, populate: bool) -> io::Result<MmapInner> { |
| 233 | let populate = if populate { MAP_POPULATE } else { 0 }; |
| 234 | MmapInner::new( |
| 235 | len, |
| 236 | libc::PROT_READ | libc::PROT_EXEC, |
| 237 | libc::MAP_SHARED | populate, |
| 238 | file, |
| 239 | offset, |
| 240 | ) |
| 241 | } |
| 242 | |
| 243 | pub fn map_mut(len: usize, file: RawFd, offset: u64, populate: bool) -> io::Result<MmapInner> { |
| 244 | let populate = if populate { MAP_POPULATE } else { 0 }; |
| 245 | MmapInner::new( |
| 246 | len, |
| 247 | libc::PROT_READ | libc::PROT_WRITE, |
| 248 | libc::MAP_SHARED | populate, |
| 249 | file, |
| 250 | offset, |
| 251 | ) |
| 252 | } |
| 253 | |
| 254 | pub fn map_copy(len: usize, file: RawFd, offset: u64, populate: bool) -> io::Result<MmapInner> { |
| 255 | let populate = if populate { MAP_POPULATE } else { 0 }; |
| 256 | MmapInner::new( |
| 257 | len, |
| 258 | libc::PROT_READ | libc::PROT_WRITE, |
| 259 | libc::MAP_PRIVATE | populate, |
| 260 | file, |
| 261 | offset, |
| 262 | ) |
| 263 | } |
| 264 | |
| 265 | pub fn map_copy_read_only( |
| 266 | len: usize, |
| 267 | file: RawFd, |
| 268 | offset: u64, |
| 269 | populate: bool, |
| 270 | ) -> io::Result<MmapInner> { |
| 271 | let populate = if populate { MAP_POPULATE } else { 0 }; |
| 272 | MmapInner::new( |
| 273 | len, |
| 274 | libc::PROT_READ, |
| 275 | libc::MAP_PRIVATE | populate, |
| 276 | file, |
| 277 | offset, |
| 278 | ) |
| 279 | } |
| 280 | |
| 281 | /// Open an anonymous memory map. |
| 282 | pub fn map_anon( |
| 283 | len: usize, |
| 284 | stack: bool, |
| 285 | populate: bool, |
| 286 | huge: Option<u8>, |
| 287 | ) -> io::Result<MmapInner> { |
| 288 | let stack = if stack { MAP_STACK } else { 0 }; |
| 289 | let populate = if populate { MAP_POPULATE } else { 0 }; |
| 290 | let hugetlb = if huge.is_some() { MAP_HUGETLB } else { 0 }; |
| 291 | let offset = huge |
| 292 | .map(|mask| ((mask as u64) & (MAP_HUGE_MASK as u64)) << MAP_HUGE_SHIFT) |
| 293 | .unwrap_or(0); |
| 294 | MmapInner::new( |
| 295 | len, |
| 296 | libc::PROT_READ | libc::PROT_WRITE, |
| 297 | libc::MAP_PRIVATE | libc::MAP_ANON | stack | populate | hugetlb, |
| 298 | -1, |
| 299 | offset, |
| 300 | ) |
| 301 | } |
| 302 | |
| 303 | pub fn flush(&self, offset: usize, len: usize) -> io::Result<()> { |
| 304 | let alignment = (self.ptr as usize + offset) % page_size(); |
| 305 | let offset = offset as isize - alignment as isize; |
| 306 | let len = len + alignment; |
| 307 | let result = |
| 308 | unsafe { libc::msync(self.ptr.offset(offset), len as libc::size_t, libc::MS_SYNC) }; |
| 309 | if result == 0 { |
| 310 | Ok(()) |
| 311 | } else { |
| 312 | Err(io::Error::last_os_error()) |
| 313 | } |
| 314 | } |
| 315 | |
| 316 | pub fn flush_async(&self, offset: usize, len: usize) -> io::Result<()> { |
| 317 | let alignment = (self.ptr as usize + offset) % page_size(); |
| 318 | let offset = offset as isize - alignment as isize; |
| 319 | let len = len + alignment; |
| 320 | let result = |
| 321 | unsafe { libc::msync(self.ptr.offset(offset), len as libc::size_t, libc::MS_ASYNC) }; |
| 322 | if result == 0 { |
| 323 | Ok(()) |
| 324 | } else { |
| 325 | Err(io::Error::last_os_error()) |
| 326 | } |
| 327 | } |
| 328 | |
| 329 | fn mprotect(&mut self, prot: libc::c_int) -> io::Result<()> { |
| 330 | unsafe { |
| 331 | let alignment = self.ptr as usize % page_size(); |
| 332 | let ptr = self.ptr.offset(-(alignment as isize)); |
| 333 | let len = self.len + alignment; |
| 334 | let len = len.max(1); |
| 335 | if libc::mprotect(ptr, len, prot) == 0 { |
| 336 | Ok(()) |
| 337 | } else { |
| 338 | Err(io::Error::last_os_error()) |
| 339 | } |
| 340 | } |
| 341 | } |
| 342 | |
| 343 | pub fn make_read_only(&mut self) -> io::Result<()> { |
| 344 | self.mprotect(libc::PROT_READ) |
| 345 | } |
| 346 | |
| 347 | pub fn make_exec(&mut self) -> io::Result<()> { |
| 348 | self.mprotect(libc::PROT_READ | libc::PROT_EXEC) |
| 349 | } |
| 350 | |
| 351 | pub fn make_mut(&mut self) -> io::Result<()> { |
| 352 | self.mprotect(libc::PROT_READ | libc::PROT_WRITE) |
| 353 | } |
| 354 | |
| 355 | #[inline ] |
| 356 | pub fn ptr(&self) -> *const u8 { |
| 357 | self.ptr as *const u8 |
| 358 | } |
| 359 | |
| 360 | #[inline ] |
| 361 | pub fn mut_ptr(&mut self) -> *mut u8 { |
| 362 | self.ptr as *mut u8 |
| 363 | } |
| 364 | |
| 365 | #[inline ] |
| 366 | pub fn len(&self) -> usize { |
| 367 | self.len |
| 368 | } |
| 369 | |
| 370 | pub fn advise(&self, advice: libc::c_int, offset: usize, len: usize) -> io::Result<()> { |
| 371 | let alignment = (self.ptr as usize + offset) % page_size(); |
| 372 | let offset = offset as isize - alignment as isize; |
| 373 | let len = len + alignment; |
| 374 | unsafe { |
| 375 | if libc::madvise(self.ptr.offset(offset), len, advice) != 0 { |
| 376 | Err(io::Error::last_os_error()) |
| 377 | } else { |
| 378 | Ok(()) |
| 379 | } |
| 380 | } |
| 381 | } |
| 382 | |
| 383 | #[cfg (target_os = "linux" )] |
| 384 | pub fn remap(&mut self, new_len: usize, options: crate::RemapOptions) -> io::Result<()> { |
| 385 | let (old_ptr, old_len, offset) = self.as_mmap_params(); |
| 386 | let (map_len, offset) = Self::adjust_mmap_params(new_len, offset)?; |
| 387 | |
| 388 | unsafe { |
| 389 | let new_ptr = libc::mremap(old_ptr, old_len, map_len, options.into_flags()); |
| 390 | |
| 391 | if new_ptr == libc::MAP_FAILED { |
| 392 | Err(io::Error::last_os_error()) |
| 393 | } else { |
| 394 | // We explicitly don't drop self since the pointer within is no longer valid. |
| 395 | ptr::write(self, Self::from_raw_parts(new_ptr, new_len, offset)); |
| 396 | Ok(()) |
| 397 | } |
| 398 | } |
| 399 | } |
| 400 | |
| 401 | pub fn lock(&self) -> io::Result<()> { |
| 402 | unsafe { |
| 403 | if libc::mlock(self.ptr, self.len) != 0 { |
| 404 | Err(io::Error::last_os_error()) |
| 405 | } else { |
| 406 | Ok(()) |
| 407 | } |
| 408 | } |
| 409 | } |
| 410 | |
| 411 | pub fn unlock(&self) -> io::Result<()> { |
| 412 | unsafe { |
| 413 | if libc::munlock(self.ptr, self.len) != 0 { |
| 414 | Err(io::Error::last_os_error()) |
| 415 | } else { |
| 416 | Ok(()) |
| 417 | } |
| 418 | } |
| 419 | } |
| 420 | } |
| 421 | |
| 422 | impl Drop for MmapInner { |
| 423 | fn drop(&mut self) { |
| 424 | let (ptr: *mut c_void, len: usize, _) = self.as_mmap_params(); |
| 425 | |
| 426 | // Any errors during unmapping/closing are ignored as the only way |
| 427 | // to report them would be through panicking which is highly discouraged |
| 428 | // in Drop impls, c.f. https://github.com/rust-lang/lang-team/issues/97 |
| 429 | unsafe { libc::munmap(addr:ptr, len as libc::size_t) }; |
| 430 | } |
| 431 | } |
| 432 | |
| 433 | unsafe impl Sync for MmapInner {} |
| 434 | unsafe impl Send for MmapInner {} |
| 435 | |
| 436 | fn page_size() -> usize { |
| 437 | static PAGE_SIZE: AtomicUsize = AtomicUsize::new(0); |
| 438 | |
| 439 | match PAGE_SIZE.load(order:Ordering::Relaxed) { |
| 440 | 0 => { |
| 441 | let page_size: usize = unsafe { libc::sysconf(name:libc::_SC_PAGESIZE) as usize }; |
| 442 | |
| 443 | PAGE_SIZE.store(val:page_size, order:Ordering::Relaxed); |
| 444 | |
| 445 | page_size |
| 446 | } |
| 447 | page_size: usize => page_size, |
| 448 | } |
| 449 | } |
| 450 | |
| 451 | pub fn file_len(file: RawFd) -> io::Result<u64> { |
| 452 | // SAFETY: We must not close the passed-in fd by dropping the File we create, |
| 453 | // we ensure this by immediately wrapping it in a ManuallyDrop. |
| 454 | unsafe { |
| 455 | let file: ManuallyDrop = ManuallyDrop::new(File::from_raw_fd(file)); |
| 456 | Ok(file.metadata()?.len()) |
| 457 | } |
| 458 | } |
| 459 | |