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