memmap2/
unix.rs

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