argus-chunk 0.1.0

Argus watches over chunk IO — little-endian wire primitives, bounds-checked spans, and raw/zstd codecs
Documentation
//! Read-only memory-mapped file ownership.
//!
//! Chunked formats can validate their small metadata regions once, then borrow
//! payload slices directly from the mapping. This module deliberately does not
//! parse or validate any particular file format.

use std::fs::File;
use std::io;
use std::path::Path;

use memmap2::Mmap;

/// Read-only memory map of a file.
///
/// Holds the [`File`] so the mapping remains valid for the lifetime of this
/// value. The map borrows no path and therefore remains usable if the directory
/// entry is renamed after opening.
///
/// Consumers should treat mapped files as immutable sealed artifacts. External
/// truncation or mutation while a map is live may produce platform-dependent
/// behavior.
pub struct MappedFile {
    _file: File,
    mmap: Mmap,
}

impl MappedFile {
    /// Open `path` read-only and map the whole file.
    ///
    /// # Errors
    ///
    /// Returns I/O errors from open or mmap.
    ///
    /// # Mapping contract
    ///
    /// Although this method is safe to call, [`memmap2`] cannot prevent another
    /// process from truncating or rewriting the file. The caller must ensure
    /// the path follows an immutable/sealed-file convention while mapped.
    pub fn open(path: impl AsRef<Path>) -> io::Result<Self> {
        let file = File::open(path.as_ref())?;
        // SAFETY: `memmap2::map` is unsafe because external file mutation can
        // invalidate assumptions about the mapping. Argus retains the handle;
        // consuming formats enforce their sealed-file concurrency contract.
        let mmap = unsafe { memmap2::MmapOptions::new().map(&file)? };
        Ok(Self { _file: file, mmap })
    }

    /// Borrow the complete mapped file as bytes.
    ///
    /// The slice remains valid for the lifetime of `self`.
    #[inline]
    #[must_use]
    pub fn as_bytes(&self) -> &[u8] {
        &self.mmap
    }

    /// Return the mapped file length in bytes.
    #[inline]
    #[must_use]
    pub fn len(&self) -> usize {
        self.mmap.len()
    }

    /// Return whether the mapped file has zero bytes.
    #[inline]
    #[must_use]
    pub fn is_empty(&self) -> bool {
        self.mmap.is_empty()
    }
}

impl AsRef<[u8]> for MappedFile {
    fn as_ref(&self) -> &[u8] {
        self.as_bytes()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::io::Write;

    #[test]
    fn open_reads_file() {
        let dir = tempfile::tempdir().unwrap();
        let path = dir.path().join("sample.bin");
        {
            let mut f = File::create(&path).unwrap();
            f.write_all(b"TESS").unwrap();
            f.write_all(&[0u8; 60]).unwrap();
        }
        let map = MappedFile::open(&path).unwrap();
        assert_eq!(map.len(), 64);
        assert_eq!(&map.as_bytes()[..4], b"TESS");
    }
}