liteboxfs 0.2.0

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

use crate::hash::{BlockHash, BlockHasher};

use super::types::{
    BlockEncoding, BlockId, BlockIndex, BlockLen, BlockList, BlockSignature, FileId, FileOffset,
    HoleLen,
};

/// The result of reading a block from the block store.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ReadBlock {
    pub signature: BlockSignature,
    pub encoding: BlockEncoding,
}

/// The backing store for file data.
pub trait DataStore {
    /// List the blocks that comprise the given file's contents.
    fn list_blocks(&mut self, file: FileId) -> crate::Result<BlockList>;

    /// Read the contents of the block with a given ID.
    ///
    /// If the block is a data block, `buf` will be filled with the block's data. If the block is a
    /// hole, `buf` will be ignored.
    fn read_block(&mut self, block: BlockId, buf: &mut Vec<u8>) -> crate::Result<ReadBlock>;

    /// Insert a block into the block store.
    ///
    /// This writes the given data at the given offset from the start of the file, overwriting
    /// in-place. This method is responsible for splitting and merging blocks as needed; the
    /// algorithm for doing so is up to the implementation.
    ///
    /// This method is responsible for checking if a block with the given hash already exists in
    /// the block store and deduplicating it.
    ///
    /// This method returns the new list of blocks for the file.
    fn write_block(
        &mut self,
        file: FileId,
        offset: FileOffset,
        block_list: BlockList,
        input: BlockStoreInput,
    ) -> crate::Result<BlockList>;

    /// Truncate the given file to the given length.
    ///
    /// If `len` is larger than the size of the file, this is a no-op. This method does not grow
    /// the file or create sparse holes.
    fn truncate(
        &mut self,
        file: FileId,
        block_list: BlockList,
        len: FileOffset,
    ) -> crate::Result<BlockList>;
}

#[derive(Debug, Clone)]
pub enum BlockStoreInput<'a> {
    Data {
        bytes: &'a [u8],
        hash: BlockHash,
        encoding: BlockEncoding,
        /// The logical (uncompressed) length of this block. If `None`, this defaults to
        /// `bytes.len()`.
        len: Option<BlockLen>,
    },
    Hole {
        len: HoleLen,
    },
}

impl<'a> From<&'a [u8]> for BlockStoreInput<'a> {
    fn from(bytes: &'a [u8]) -> Self {
        BlockStoreInput::Data {
            bytes,
            hash: BlockHasher::hash(bytes),
            encoding: BlockEncoding::NONE,
            len: None,
        }
    }
}

impl<'a> BlockStoreInput<'a> {
    pub fn len(&self) -> BlockLen {
        match self {
            BlockStoreInput::Data {
                bytes,
                len: logical_len,
                ..
            } => logical_len.unwrap_or(bytes.len()),
            BlockStoreInput::Hole { len } => *len as BlockLen,
        }
    }

    pub fn encoding(&self) -> BlockEncoding {
        match self {
            BlockStoreInput::Data { encoding, .. } => *encoding,
            BlockStoreInput::Hole { .. } => BlockEncoding::NONE,
        }
    }
}

/// A backing store for blocks of data.
///
/// Each file consists of a list of blocks, which may be either data blocks or holes.
pub trait BlockStore {
    /// List the blocks that comprise the given file's contents.
    fn list_blocks(&self, file: FileId) -> crate::Result<BlockList>;

    /// Read the contents of the block with a given ID.
    ///
    /// If the block is a data block, the block's data will be appended to `buf` (meaning you'll
    /// usually want to clear the vector first). If the block is a hole, `buf` will be ignored.
    fn read_block(&mut self, block: BlockId, buf: &mut Vec<u8>) -> crate::Result<ReadBlock>;

    /// Replace the block at a given index in the file's block list.
    ///
    /// This returns the block ID of the new block.
    fn replace_block(
        &mut self,
        file: FileId,
        index: BlockIndex,
        input: BlockStoreInput,
    ) -> crate::Result<BlockId>;

    /// Insert a block at the given index in file's block list, shifting all subsequent blocks
    /// forward.
    ///
    /// This returns the block ID of the new block.
    fn insert_block(
        &mut self,
        file: FileId,
        index: BlockIndex,
        input: BlockStoreInput,
    ) -> crate::Result<BlockId>;

    /// Append a block to the end of a file's block list.
    ///
    /// As a performance optimization, this accepts the index of the new block, because we already
    /// have the length of the block list in memory. This index MUST be correct; the behavior is
    /// unspecified if it is not.
    ///
    /// This returns the block ID of the new block.
    fn append_block(
        &mut self,
        file: FileId,
        index: BlockIndex,
        input: BlockStoreInput,
    ) -> crate::Result<BlockId>;

    /// Remove a slice of blocks from the given file's block list, shifting all subsequent blocks
    /// backward.
    ///
    /// This returns the block IDs of the removed blocks.
    fn remove_blocks<R: RangeBounds<usize>>(
        &mut self,
        file: FileId,
        blocks: R,
    ) -> crate::Result<Vec<BlockId>>;
}