mmap_safe/
lib.rs

1// Copyright 2018 Urs Schulz
2//
3// This file is part of mmap-safe.
4//
5// mmap-safe is free software: you can redistribute it and/or modify
6// it under the terms of the GNU Affero General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// mmap-safe is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13// GNU Affero General Public License for more details.
14//
15// You should have received a copy of the GNU Affero General Public License
16// along with mmap-safe.  If not, see <http://www.gnu.org/licenses/>.
17//
18//! This library provides a thin wrapper around the `memmap` crate in order to make file-backed
19//! mapped memory safe in Rust. Although the crate tries it's best at ensuring safety, this can not
20//! be fully guaranteed.
21//!
22//! # Safe and Unsafe usage
23//! Since Linux and other Unix-Systems currently do not provide mandatory file locks (Linux does
24//! but it's buggy and will be removed in the future), it is not possible to prevent parallel
25//! access to a certain file. Therefore file-backed mapped memory is inherently unsafe.
26//!
27//! However, if you access a file only through this library and not through the [`std::fs::File`]
28//! API, you are safe. This crate uses an advisory lock internally to make sure only one
29//! [`MappedFile`] instance ever exists at the same time for the same file.
30//!
31//! Oh, and don't use it on network filesystems, I haven't tested that.
32use std::fs::File;
33use std::fs::OpenOptions;
34use std::io;
35use std::io::Seek;
36use std::io::SeekFrom;
37use std::marker::PhantomData;
38use std::ops::Deref;
39use std::ops::DerefMut;
40use std::path::Path;
41
42use fs2::FileExt;
43
44use memmap::Mmap;
45use memmap::MmapMut;
46
47/// A file mapped to memory.
48pub struct Mapping<'a> {
49    mmap: Option<Mmap>,
50    _lt: PhantomData<&'a ()>,
51}
52
53
54/// A file mutably mapped to memory.
55pub struct MutMapping<'a> {
56    mmap: Option<MmapMut>,
57    _lt: PhantomData<&'a mut ()>,
58}
59
60impl<'a> MutMapping<'a> {
61    pub fn flush(&self) -> io::Result<()> {
62        self.mmap
63            .as_ref()
64            .map(|mm| mm.flush())
65            .or(Some(Ok(())))
66            .unwrap()
67    }
68
69    pub fn flush_range(&self, offset: usize, len: usize) -> io::Result<()> {
70        self.mmap
71            .as_ref()
72            .map(|mm| mm.flush_range(offset, len))
73            .or(Some(Ok(())))
74            .unwrap()
75    }
76}
77
78// We need this Drop in order to convince the borrow checker not to allow any new MutMapping
79// instances before the old ones got fully drop()ed.
80impl<'a> Drop for Mapping<'a> {
81    fn drop(&mut self) {}
82}
83
84impl<'a> Drop for MutMapping<'a> {
85    fn drop(&mut self) {}
86}
87
88impl<'a> Deref for Mapping<'a> {
89    type Target = [u8];
90
91    #[inline]
92    fn deref(&self) -> &Self::Target {
93        match self.mmap.as_ref() {
94            Some(mm) => mm,
95            None => &[],
96        }
97    }
98}
99
100impl<'a> AsRef<[u8]> for Mapping<'a> {
101    #[inline]
102    fn as_ref(&self) -> &[u8] {
103        self.deref()
104    }
105}
106
107impl<'a> Deref for MutMapping<'a> {
108    type Target = [u8];
109
110    #[inline]
111    fn deref(&self) -> &Self::Target {
112        match self.mmap.as_ref() {
113            Some(mm) => mm,
114            None => &[],
115        }
116    }
117}
118
119impl<'a> DerefMut for MutMapping<'a> {
120    #[inline]
121    fn deref_mut(&mut self) -> &mut [u8] {
122        match self.mmap.as_mut() {
123            Some(mm) => mm,
124            None => &mut [],
125        }
126    }
127}
128
129impl<'a> AsRef<[u8]> for MutMapping<'a> {
130    #[inline]
131    fn as_ref(&self) -> &[u8] {
132        self.deref()
133    }
134}
135
136impl<'a> AsMut<[u8]> for MutMapping<'a> {
137    #[inline]
138    fn as_mut(&mut self) -> &mut [u8] {
139        self.deref_mut()
140    }
141}
142
143/// A thin, safe wrapper for memory-mapped files.
144///
145/// This wrapper ensures memory safety by only providing one mutable reference at a time and by
146/// locking the file exclusively.
147pub struct MappedFile {
148    file: File,
149    size: u64,
150}
151
152impl Drop for MappedFile {
153    fn drop(&mut self) {
154        self.file.unlock().unwrap();
155    }
156}
157
158impl MappedFile {
159    #[inline]
160    pub fn open<P: AsRef<Path>>(path: P) -> io::Result<Self> {
161        let file = OpenOptions::new().read(true).write(true).open(path)?;
162        Self::new(file)
163    }
164
165    #[inline]
166    pub fn create<P: AsRef<Path>>(path: P) -> io::Result<Self> {
167        let file = OpenOptions::new()
168            .create(true)
169            .read(true)
170            .write(true)
171            .open(path)?;
172        Self::new(file)
173    }
174
175    #[inline]
176    pub fn new(mut file: File) -> io::Result<Self> {
177        // lock file & get size
178        file.try_lock_exclusive()?;
179        let size = file.seek(SeekFrom::End(0))?;
180
181        Ok(Self {
182            file: file,
183            size: size,
184        })
185    }
186
187    /// Returns mapped memory for the file.
188    #[inline]
189    pub fn map(&self, offset: u64, size: usize) -> io::Result<Mapping> {
190        let mmap = if size == 0 {
191            None
192        } else {
193            Some(unsafe {
194                memmap::MmapOptions::new()
195                    .offset(offset)
196                    .len(size)
197                    .map(&self.file)?
198            })
199        };
200
201        Ok(Mapping {
202            mmap: mmap,
203            _lt: PhantomData,
204        })
205    }
206
207    /// Returns mutably-mapped memory for the file.
208    #[inline]
209    pub fn map_mut<'a>(&'a mut self, offset: u64, size: usize) -> io::Result<MutMapping<'a>> {
210        let mmap = if size == 0 {
211            None
212        } else {
213            Some(unsafe {
214                memmap::MmapOptions::new()
215                    .offset(offset)
216                    .len(size)
217                    .map_mut(&self.file)?
218            })
219        };
220
221        Ok(MutMapping {
222            mmap: mmap,
223            _lt: PhantomData,
224        })
225    }
226
227    /// Resizes the file. When grown, it is guaranteed that all mapped memory regions on this file
228    /// stay valid. When the file gets shrunk, the behaviour of regions behind the new end of the
229    /// file is undefined.
230    #[inline]
231    pub fn resize(&mut self, size: u64) -> io::Result<()> {
232        self.file.set_len(size)?;
233        self.size = size;
234        Ok(())
235    }
236
237    /// Returns the current size of the file.
238    #[inline]
239    pub fn size(&self) -> u64 {
240        self.size
241    }
242
243    /// See [`File::sync_data`]
244    #[inline]
245    pub fn sync_data(&mut self) -> io::Result<()> {
246        self.file.sync_data()
247    }
248
249    /// See [`File::sync_all`]
250    #[inline]
251    pub fn sync_all(&mut self) -> io::Result<()> {
252        self.file.sync_all()
253    }
254
255    /// Converts this instance into a mutable mapping.
256    /// If you want to convert it back, call [`IntoMutMapping::unmap`].
257    #[inline]
258    pub fn into_mut_mapping(
259        self,
260        offset: u64,
261        size: usize,
262    ) -> Result<IntoMutMapping, (io::Error, Self)> {
263        let mmap = if size == 0 {
264            None
265        } else {
266            let mmap = unsafe {
267                memmap::MmapOptions::new()
268                    .offset(offset)
269                    .len(size)
270                    .map_mut(&self.file)
271            };
272
273            Some(match mmap {
274                Ok(mm) => mm,
275                Err(e) => return Err((e, self)),
276            })
277        };
278
279        Ok(IntoMutMapping {
280            mmap: mmap,
281            file: Some(self),
282        })
283    }
284}
285
286
287/// Mutably mapped file obtained by [`MappedFile::into_mut_mapping()`].
288pub struct IntoMutMapping {
289    mmap: Option<MmapMut>,
290    file: Option<MappedFile>,
291}
292
293
294impl IntoMutMapping {
295    /// Unmaps the file from memory and returns back the [`MappedFile`] instance it originated
296    /// from.
297    #[inline]
298    pub fn unmap(mut self) -> MappedFile {
299        self.file.take().unwrap()
300    }
301
302    /// Flushes unwritten changes to disc.
303    #[inline]
304    pub fn flush(&self) -> io::Result<()> {
305        self.mmap
306            .as_ref()
307            .map(|mm| mm.flush())
308            .or(Some(Ok(())))
309            .unwrap()
310    }
311
312    /// Flushes unwritten changes in the specific range to disc.
313    #[inline]
314    pub fn flush_range(&self, offset: usize, len: usize) -> io::Result<()> {
315        self.mmap
316            .as_ref()
317            .map(|mm| mm.flush_range(offset, len))
318            .or(Some(Ok(())))
319            .unwrap()
320    }
321
322    /// Returns the current size of the file.
323    pub fn size(&self) -> u64 {
324        self.file.as_ref().unwrap().size()
325    }
326}
327
328impl<'a> Deref for IntoMutMapping {
329    type Target = [u8];
330
331    #[inline]
332    fn deref(&self) -> &Self::Target {
333        match self.mmap.as_ref() {
334            Some(mm) => mm,
335            None => &[],
336        }
337    }
338}
339
340impl<'a> DerefMut for IntoMutMapping {
341    #[inline]
342    fn deref_mut(&mut self) -> &mut [u8] {
343        match self.mmap.as_mut() {
344            Some(mm) => mm,
345            None => &mut [],
346        }
347    }
348}
349
350impl<'a> AsRef<[u8]> for IntoMutMapping {
351    #[inline]
352    fn as_ref(&self) -> &[u8] {
353        self.deref()
354    }
355}
356
357impl<'a> AsMut<[u8]> for IntoMutMapping {
358    #[inline]
359    fn as_mut(&mut self) -> &mut [u8] {
360        self.deref_mut()
361    }
362}
363
364
365#[cfg(test)]
366mod tests {
367    // TODO: change test file names, maybe something temporary
368    use super::MappedFile;
369    use std::fs::remove_file;
370    use std::path::PathBuf;
371
372    fn temp_file_name() -> PathBuf {
373        use rand::Rng;
374
375        let mut p = std::env::temp_dir();
376        p.push(
377            String::from("cargo-test.mmap-safe.") + &rand::thread_rng()
378                .sample_iter(&rand::distributions::Alphanumeric)
379                .take(12)
380                .collect::<String>(),
381        );
382        p
383    }
384
385    #[test]
386    fn only_one_instance() {
387        let file = temp_file_name();
388        let mut mf = MappedFile::create(&file).unwrap();
389        mf.resize(10).unwrap();
390
391        assert!(MappedFile::open(&file).is_err());
392        assert!(MappedFile::create(&file).is_err());
393
394        remove_file(file).unwrap();
395    }
396
397    #[test]
398    fn write_works() {
399        let file = temp_file_name();
400        let mut mf = MappedFile::create(&file).unwrap();
401        mf.resize(10).unwrap();
402
403        {
404            let mut mapping = mf.map_mut(0, 10).unwrap();
405            mapping[0] = 0x42;
406        }
407
408        let mapping = mf.map(0, 10).unwrap();
409        assert_eq!(mapping[0], 0x42);
410
411        remove_file(file).unwrap();
412    }
413
414    #[test]
415    fn multiple_immutable_mappings() {
416        let file = temp_file_name();
417        let mut mf = MappedFile::create(&file).unwrap();
418        mf.resize(10).unwrap();
419
420        {
421            let mut mm = mf.map_mut(0, 10).unwrap();
422            mm[0] = 0x42;
423        }
424
425        let mm1 = mf.map(0, 10).unwrap();
426        let mm2 = mf.map(0, 10).unwrap();
427        assert_eq!(mm1[0], 0x42);
428        assert_eq!(mm2[0], 0x42);
429
430        remove_file(file).unwrap();
431    }
432
433    #[test]
434    fn empty_files() {
435        let file = temp_file_name();
436        let mut mf = MappedFile::create(&file).unwrap();
437
438        {
439            let mm = mf.map_mut(0, 0).unwrap();
440            assert_eq!(mm.len(), 0);
441        }
442
443        {
444            let mm = mf.map(0, 0).unwrap();
445            assert_eq!(mm.len(), 0);
446        }
447
448        mf.into_mut_mapping(0, 0).map_err(|(e, _)| e).unwrap();
449
450        remove_file(file).unwrap();
451    }
452}