liteboxfs 0.2.0

A modern POSIX filesystem in a SQLite database
Documentation
use std::cmp;

use crate::block::{BlockData, BlockId};

/// A pre-allocated buffer of null bytes to read from when reading a hole.
#[derive(Debug, Default)]
struct HoleCache {
    null_bytes: Vec<u8>,
}

impl HoleCache {
    pub fn new() -> Self {
        Self {
            null_bytes: Vec::new(),
        }
    }

    pub fn fill(&mut self, buf: &mut [u8]) {
        if self.null_bytes.len() < buf.len() {
            self.null_bytes.resize(buf.len(), 0);
        }

        buf.copy_from_slice(match &self.null_bytes.get(..buf.len()) {
            Some(slice) => slice,
            None => unreachable!("By this point, the null bytes cache should have been resized to accommodate the requested buffer."),
        });
    }
}

/// An in-memory cache for the block that was most recently read.
#[derive(Debug)]
pub struct CachedBlock {
    id: BlockId,
    data: BlockData,
    hole_cache: HoleCache,
}

impl CachedBlock {
    pub fn new(id: BlockId, data: BlockData) -> Self {
        Self {
            id,
            data,
            hole_cache: HoleCache::new(),
        }
    }

    /// The ID of the block currently in the cache.
    pub fn id(&self) -> BlockId {
        self.id
    }

    /// Replace the block in this cache with a new one.
    pub fn replace<F, R>(&mut self, id: BlockId, f: F) -> R
    where
        F: FnOnce(&mut BlockData) -> R,
    {
        self.id = id;
        f(&mut self.data)
    }

    /// Fill `buf` with bytes from this cache starting at the given offset `off`.
    pub fn fill(&mut self, buf: &mut [u8], off: usize) -> usize {
        let block_size = match &self.data {
            BlockData::Data { bytes } => bytes.len(),
            BlockData::Hole { len } => *len as usize,
        };

        let bytes_read = cmp::min(buf.len(), block_size - off);

        let buf_slice_to_fill = match buf.get_mut(..bytes_read) {
            Some(slice) => slice,
            None => unreachable!(
                "We should have already constrained the number of bytes we're trying to copy to the size of the buffer."
            ),
        };

        match &self.data {
            BlockData::Data { bytes } => {
                buf_slice_to_fill.copy_from_slice(match bytes.get(off..off + bytes_read) {
                    Some(slice) => slice,
                    None => panic!(
                        "Attempted to read at an offset which is beyond the end of the data block."
                    ),
                })
            }
            BlockData::Hole { .. } => self.hole_cache.fill(buf_slice_to_fill),
        }

        bytes_read
    }
}