Skip to main content

graph_r/
mmap.rs

1// Author: spicyzhug@gmail.com
2// Copyright: lituus-io, all rights reserved
3//
4//! Read-only images for zero-copy readers: a memory-mapped file, or bytes
5//! already in memory (an object fetched from storage) served in place. The
6//! crate's single unsafe island (`#![deny(unsafe_code)]` at the root; this
7//! module opts out locally).
8//!
9//! # Safety envelope
10//!
11//! A mapping is only ever taken over files that are replaced atomically by
12//! rename and never truncated in place by a cooperating process (graph-r's
13//! base, dependants' immutable images). In-place truncation by a foreign
14//! actor is outside the contract; a SIGBUS is then accepted.
15
16#![allow(unsafe_code)]
17
18use crate::error::Result;
19use std::fs::File;
20use std::path::Path;
21
22/// A read-only base image: a memory-mapped file, or bytes already in memory
23/// (an object fetched from storage) served in place.
24pub struct MappedFile {
25    backing: Backing,
26}
27
28enum Backing {
29    Map(memmap2::Mmap),
30    Heap(bytes::Bytes),
31}
32
33impl std::fmt::Debug for MappedFile {
34    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35        let kind = match self.backing {
36            Backing::Map(_) => "mmap",
37            Backing::Heap(_) => "heap",
38        };
39        f.debug_struct("MappedFile").field("kind", &kind).field("len", &self.bytes().len()).finish()
40    }
41}
42
43impl MappedFile {
44    /// Map the file at `path` read-only.
45    pub fn open(path: &Path) -> Result<Self> {
46        let file = File::open(path)?;
47        // SAFETY: the file is mapped read-only. Cooperating processes replace
48        // it only via atomic rename (the old inode stays alive under this
49        // map); hostile in-place truncation faults, which we accept.
50        let mmap = unsafe { memmap2::Mmap::map(&file)? };
51        Ok(Self { backing: Backing::Map(mmap) })
52    }
53
54    /// Serve `bytes` in place (no copy; no file, so no SIGBUS exposure).
55    #[must_use]
56    pub fn from_bytes(bytes: bytes::Bytes) -> Self {
57        Self { backing: Backing::Heap(bytes) }
58    }
59
60    /// The image bytes.
61    #[must_use]
62    pub fn bytes(&self) -> &[u8] {
63        match &self.backing {
64            Backing::Map(m) => m,
65            Backing::Heap(b) => b,
66        }
67    }
68}
69
70/// View `bytes` as `f32`s in place (zero per-element decode), for formats
71/// that 64-align their vector sections. Errors, never UB, when the length is
72/// not a whole number of floats or the memory is not `f32`-aligned.
73pub fn cast_f32(bytes: &[u8]) -> Result<&[f32]> {
74    const _: () = assert!(cfg!(target_endian = "little"), "images are little-endian");
75    if !bytes.len().is_multiple_of(4) {
76        return Err(crate::Error::format("f32 section length is not a multiple of 4"));
77    }
78    if !(bytes.as_ptr() as usize).is_multiple_of(std::mem::align_of::<f32>()) {
79        return Err(crate::Error::format("f32 section is not aligned"));
80    }
81    // SAFETY: `bytes` is `len * 4` initialized bytes, verified f32-aligned;
82    // every bit pattern is a valid f32; the result borrows `bytes`.
83    #[allow(clippy::cast_ptr_alignment)] // verified above
84    Ok(unsafe { std::slice::from_raw_parts(bytes.as_ptr().cast::<f32>(), bytes.len() / 4) })
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90
91    #[test]
92    fn heap_images_are_served_in_place() {
93        let b = bytes::Bytes::from_static(b"image");
94        let m = MappedFile::from_bytes(b.clone());
95        assert_eq!(m.bytes().as_ptr(), b.as_ptr(), "no copy");
96    }
97
98    #[test]
99    fn mmap_reads_file_bytes() {
100        let dir = tempfile::tempdir().unwrap();
101        let p = dir.path().join("f");
102        std::fs::write(&p, b"abc123").unwrap();
103        let m = MappedFile::open(&p).unwrap();
104        assert_eq!(m.bytes(), b"abc123");
105    }
106
107    #[test]
108    fn f32_views_check_length_and_alignment() {
109        let v: Vec<f32> = vec![1.0, -2.5, 3.25];
110        let raw: Vec<u8> = v.iter().flat_map(|f| f.to_le_bytes()).collect();
111        let mut aligned = vec![0u32; 4];
112        let buf = as_bytes(&mut aligned);
113        buf[..12].copy_from_slice(&raw);
114        assert_eq!(cast_f32(&buf[..12]).unwrap(), &v[..]);
115        assert!(cast_f32(&buf[..11]).is_err());
116        assert!(cast_f32(&buf[1..13]).is_err());
117    }
118
119    fn as_bytes(words: &mut [u32]) -> &mut [u8] {
120        // SAFETY: u8 has alignment 1, every byte is valid; same allocation.
121        unsafe { std::slice::from_raw_parts_mut(words.as_mut_ptr().cast::<u8>(), words.len() * 4) }
122    }
123}