Skip to main content

blazesym/
mmap.rs

1use std::fs::File;
2use std::ops::Deref;
3use std::ops::Range;
4use std::path::Path;
5use std::rc::Rc;
6
7use memmap2::Mmap as Mapping;
8use memmap2::MmapOptions;
9
10use crate::Error;
11use crate::ErrorExt as _;
12use crate::Result;
13
14#[cfg(not(windows))]
15pub(crate) type Advice = memmap2::Advice;
16
17
18#[doc(hidden)]
19#[derive(Debug)]
20pub struct Builder {
21    exec: bool,
22}
23
24impl Builder {
25    fn new() -> Self {
26        Self { exec: false }
27    }
28
29    /// Configure the mapping to be executable.
30    #[doc(hidden)]
31    pub fn exec(mut self) -> Self {
32        self.exec = true;
33        self
34    }
35
36    /// Memory map the file at the provided `path`.
37    #[doc(hidden)]
38    pub fn open<P>(self, path: P) -> Result<Mmap>
39    where
40        P: AsRef<Path>,
41    {
42        let file = File::open(path)?;
43        self.map(&file)
44    }
45
46    /// Map the provided file into memory, in its entirety.
47    pub(crate) fn map(self, file: &File) -> Result<Mmap> {
48        let len = libc::size_t::try_from(file.metadata()?.len())
49            .map_err(Error::with_invalid_data)
50            .context("file is too large to mmap")?;
51
52        // The kernel does not allow mmap'ing a region of size 0. We
53        // want to enable this case transparently, though.
54        let mmap = if len == 0 {
55            Mmap {
56                mapping: None,
57                view: 0..1,
58            }
59        } else {
60            let opts = MmapOptions::new();
61
62            // SAFETY: The library doesn't modify mmap'ed data, but we cannot
63            //         control users modifying files behind our back. This is a
64            //         systemic problem of contemporary memory mapping
65            //         implementations.
66            let mapping = if self.exec {
67                unsafe { opts.map_exec(file) }
68            } else {
69                unsafe { opts.map(file) }
70            }?;
71
72            Mmap {
73                mapping: Some(Rc::new(mapping)),
74                view: 0..len as u64,
75            }
76        };
77        Ok(mmap)
78    }
79}
80
81
82/// A type encapsulating a region of mapped memory.
83#[derive(Clone, Debug)]
84pub struct Mmap {
85    /// The actual memory mapping.
86    mapping: Option<Rc<Mapping>>,
87    /// The view on the memory mapping that this object represents.
88    view: Range<u64>,
89}
90
91#[allow(rustdoc::private_intra_doc_links)]
92impl Mmap {
93    /// Create a [`Builder`] for creating a customizable memory mapping.
94    #[doc(hidden)]
95    pub fn builder() -> Builder {
96        Builder::new()
97    }
98
99    /// Map the provided file into memory, in its entirety.
100    pub(crate) fn map(file: &File) -> Result<Self> {
101        Self::builder().map(file)
102    }
103
104    /// Provide a hint about the mapping to the kernel.
105    #[cfg(not(windows))]
106    pub(crate) fn advise(&self, advise: Advice) -> Result<()> {
107        self.mapping
108            .as_ref()
109            .map(|mmap| mmap.advise(advise).map_err(Error::from))
110            .unwrap_or(Ok(()))
111    }
112
113    /// Create a new `Mmap` object (sharing the same underlying memory mapping
114    /// as the current one) that restricts its view to the provided `range`.
115    /// Adjustment happens relative to the current view.
116    #[doc(hidden)]
117    pub fn constrain(&self, range: Range<u64>) -> Option<Self> {
118        if self.view.start + range.end > self.view.end {
119            return None
120        }
121
122        let mut mmap = self.clone();
123        mmap.view.end = mmap.view.start + range.end;
124        mmap.view.start += range.start;
125        Some(mmap)
126    }
127}
128
129impl Deref for Mmap {
130    type Target = [u8];
131
132    fn deref(&self) -> &Self::Target {
133        if let Some(mapping) = &self.mapping {
134            mapping
135                .deref()
136                .get(self.view.start as usize..self.view.end as usize)
137                .unwrap_or(&[])
138        } else {
139            &[]
140        }
141    }
142}
143
144
145#[cfg(test)]
146mod tests {
147    use super::*;
148
149    use std::ffi::CStr;
150    use std::io::Write;
151
152    use tempfile::tempfile;
153    use test_log::test;
154
155    use crate::util::ReadRaw;
156
157
158    /// Exercise the `Debug` representation of various types.
159    #[test]
160    fn debug_repr() {
161        let builder = Builder::new();
162        assert_ne!(format!("{builder:?}"), "");
163    }
164
165    /// Check that we can `mmap` an empty file.
166    #[test]
167    fn mmap_empty_file() {
168        let file = tempfile().unwrap();
169        let mmap = Mmap::map(&file).unwrap();
170        assert_eq!(mmap.deref(), &[] as &[u8]);
171    }
172
173    /// Check that we can `mmap` a file.
174    #[test]
175    fn mmap() {
176        let mut file = tempfile().unwrap();
177        let cstr = b"Daniel was here. Briefly.\0";
178        let () = file.write_all(cstr).unwrap();
179        let () = file.sync_all().unwrap();
180
181        let mmap = Mmap::map(&file).unwrap();
182        let mut data = mmap.deref();
183        let s = data.read_cstr().unwrap();
184        assert_eq!(
185            s.to_str().unwrap(),
186            CStr::from_bytes_with_nul(cstr).unwrap().to_str().unwrap()
187        );
188    }
189
190    /// Check that we can properly restrict the view of a `Mmap`.
191    #[test]
192    fn view_constraining() {
193        let mut file = tempfile().unwrap();
194        let s = b"abcdefghijklmnopqrstuvwxyz";
195        let () = file.write_all(s).unwrap();
196        let () = file.sync_all().unwrap();
197
198        let mmap = Mmap::map(&file).unwrap();
199        assert_eq!(mmap.deref(), b"abcdefghijklmnopqrstuvwxyz");
200
201        let mmap = mmap.constrain(1..15).unwrap();
202        assert_eq!(mmap.deref(), b"bcdefghijklmno");
203
204        let mmap = mmap.constrain(5..6).unwrap();
205        assert_eq!(mmap.deref(), b"g");
206
207        assert!(mmap.constrain(1..2).is_none());
208    }
209}