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}