• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
new( len: usize, prot: libc::c_int, flags: libc::c_int, file: RawFd, offset: u64, ) -> io::Result<MmapInner>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 
adjust_mmap_params(len: usize, alignment: usize) -> io::Result<(usize, usize)>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`.
as_mmap_params(&self) -> (*mut libc::c_void, usize, usize)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.
from_raw_parts(ptr: *mut libc::c_void, len: usize, offset: usize) -> Self211     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 
map(len: usize, file: RawFd, offset: u64, populate: bool) -> io::Result<MmapInner>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 
map_exec(len: usize, file: RawFd, offset: u64, populate: bool) -> io::Result<MmapInner>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 
map_mut(len: usize, file: RawFd, offset: u64, populate: bool) -> io::Result<MmapInner>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 
map_copy(len: usize, file: RawFd, offset: u64, populate: bool) -> io::Result<MmapInner>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 
map_copy_read_only( len: usize, file: RawFd, offset: u64, populate: bool, ) -> io::Result<MmapInner>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.
map_anon( len: usize, stack: bool, populate: bool, huge: Option<u8>, ) -> io::Result<MmapInner>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 
flush(&self, offset: usize, len: usize) -> io::Result<()>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 
flush_async(&self, offset: usize, len: usize) -> io::Result<()>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 
mprotect(&mut self, prot: libc::c_int) -> io::Result<()>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 
make_read_only(&mut self) -> io::Result<()>343     pub fn make_read_only(&mut self) -> io::Result<()> {
344         self.mprotect(libc::PROT_READ)
345     }
346 
make_exec(&mut self) -> io::Result<()>347     pub fn make_exec(&mut self) -> io::Result<()> {
348         self.mprotect(libc::PROT_READ | libc::PROT_EXEC)
349     }
350 
make_mut(&mut self) -> io::Result<()>351     pub fn make_mut(&mut self) -> io::Result<()> {
352         self.mprotect(libc::PROT_READ | libc::PROT_WRITE)
353     }
354 
355     #[inline]
ptr(&self) -> *const u8356     pub fn ptr(&self) -> *const u8 {
357         self.ptr as *const u8
358     }
359 
360     #[inline]
mut_ptr(&mut self) -> *mut u8361     pub fn mut_ptr(&mut self) -> *mut u8 {
362         self.ptr as *mut u8
363     }
364 
365     #[inline]
len(&self) -> usize366     pub fn len(&self) -> usize {
367         self.len
368     }
369 
advise(&self, advice: libc::c_int, offset: usize, len: usize) -> io::Result<()>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")]
remap(&mut self, new_len: usize, options: crate::RemapOptions) -> io::Result<()>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 
lock(&self) -> io::Result<()>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 
unlock(&self) -> io::Result<()>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 {
drop(&mut self)423     fn drop(&mut self) {
424         let (ptr, len, _) = 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(ptr, len as libc::size_t) };
430     }
431 }
432 
433 unsafe impl Sync for MmapInner {}
434 unsafe impl Send for MmapInner {}
435 
page_size() -> usize436 fn page_size() -> usize {
437     static PAGE_SIZE: AtomicUsize = AtomicUsize::new(0);
438 
439     match PAGE_SIZE.load(Ordering::Relaxed) {
440         0 => {
441             let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) as usize };
442 
443             PAGE_SIZE.store(page_size, Ordering::Relaxed);
444 
445             page_size
446         }
447         page_size => page_size,
448     }
449 }
450 
file_len(file: RawFd) -> io::Result<u64>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::new(File::from_raw_fd(file));
456         Ok(file.metadata()?.len())
457     }
458 }
459