Skip to main content

argus/
mmap.rs

1//! Read-only memory-mapped file ownership.
2//!
3//! Chunked formats can validate their small metadata regions once, then borrow
4//! payload slices directly from the mapping. This module deliberately does not
5//! parse or validate any particular file format.
6
7use std::fs::File;
8use std::io;
9use std::path::Path;
10
11use memmap2::Mmap;
12
13/// Read-only memory map of a file.
14///
15/// Holds the [`File`] so the mapping remains valid for the lifetime of this
16/// value. The map borrows no path and therefore remains usable if the directory
17/// entry is renamed after opening.
18///
19/// Consumers should treat mapped files as immutable sealed artifacts. External
20/// truncation or mutation while a map is live may produce platform-dependent
21/// behavior.
22pub struct MappedFile {
23    _file: File,
24    mmap: Mmap,
25}
26
27impl MappedFile {
28    /// Open `path` read-only and map the whole file.
29    ///
30    /// # Errors
31    ///
32    /// Returns I/O errors from open or mmap.
33    ///
34    /// # Mapping contract
35    ///
36    /// Although this method is safe to call, [`memmap2`] cannot prevent another
37    /// process from truncating or rewriting the file. The caller must ensure
38    /// the path follows an immutable/sealed-file convention while mapped.
39    pub fn open(path: impl AsRef<Path>) -> io::Result<Self> {
40        let file = File::open(path.as_ref())?;
41        // SAFETY: `memmap2::map` is unsafe because external file mutation can
42        // invalidate assumptions about the mapping. Argus retains the handle;
43        // consuming formats enforce their sealed-file concurrency contract.
44        let mmap = unsafe { memmap2::MmapOptions::new().map(&file)? };
45        Ok(Self { _file: file, mmap })
46    }
47
48    /// Borrow the complete mapped file as bytes.
49    ///
50    /// The slice remains valid for the lifetime of `self`.
51    #[inline]
52    #[must_use]
53    pub fn as_bytes(&self) -> &[u8] {
54        &self.mmap
55    }
56
57    /// Return the mapped file length in bytes.
58    #[inline]
59    #[must_use]
60    pub fn len(&self) -> usize {
61        self.mmap.len()
62    }
63
64    /// Return whether the mapped file has zero bytes.
65    #[inline]
66    #[must_use]
67    pub fn is_empty(&self) -> bool {
68        self.mmap.is_empty()
69    }
70}
71
72impl AsRef<[u8]> for MappedFile {
73    fn as_ref(&self) -> &[u8] {
74        self.as_bytes()
75    }
76}
77
78#[cfg(test)]
79mod tests {
80    use super::*;
81    use std::io::Write;
82
83    #[test]
84    fn open_reads_file() {
85        let dir = tempfile::tempdir().unwrap();
86        let path = dir.path().join("sample.bin");
87        {
88            let mut f = File::create(&path).unwrap();
89            f.write_all(b"TESS").unwrap();
90            f.write_all(&[0u8; 60]).unwrap();
91        }
92        let map = MappedFile::open(&path).unwrap();
93        assert_eq!(map.len(), 64);
94        assert_eq!(&map.as_bytes()[..4], b"TESS");
95    }
96}