liteboxfs 0.1.0

A modern POSIX filesystem in a SQLite database
Documentation
use std::{cell::RefCell, collections::HashMap, ffi::OsString};

use fuser::{FileHandle, FileType as FuseFileType, INodeNo};
use nix::fcntl::OFlag;

use super::pool::IdPool;
use crate::{FileDiscriminant, FileId, RawFd};

impl FileDiscriminant {
    pub(crate) fn to_fuse_file_type(self) -> fuser::FileType {
        match self {
            FileDiscriminant::Regular => fuser::FileType::RegularFile,
            FileDiscriminant::Dir => fuser::FileType::Directory,
            FileDiscriminant::Symlink => fuser::FileType::Symlink,
            FileDiscriminant::Block => fuser::FileType::BlockDevice,
            FileDiscriminant::Char => fuser::FileType::CharDevice,
            FileDiscriminant::Pipe => fuser::FileType::NamedPipe,
        }
    }
}

/// A directory entry for an open file handle.
#[derive(Debug, Clone)]
pub struct DirectoryEntry {
    pub file_name: OsString,
    pub file_type: FuseFileType,
    pub inode: INodeNo,
}

/// The state associated with a file handle.
#[derive(Debug)]
pub struct FileHandleState {
    /// The flags the handle was opened with.
    pub flags: OFlag,

    /// The current seek position of the file.
    pub position: u64,

    pub file_id: FileId,

    /// A [`RawFd`] that keeps the underlying file alive for the lifetime of this handle. This is
    /// what enables POSIX unlink-while-open: even after the path is removed, the file remains
    /// readable and writable via this handle's [`FileId`] until the handle is released. Only
    /// regular files have one, since other kinds can't be unlinked via [`Filesystem::unlink`].
    ///
    /// Stored in a [`RefCell`] so the FUSE `release` path can take ownership through a shared
    /// reference to [`FuseState`], without breaking the "mutate state only after the database
    /// savepoint commits" invariant in [`FuseStateGuard::exec`].
    ///
    /// [`Filesystem::unlink`]: crate::Filesystem::unlink
    /// [`FuseState`]: crate::fuse::state::FuseState
    /// [`FuseStateGuard::exec`]: crate::fuse::state::FuseStateGuard::exec
    pub fd: RefCell<Option<RawFd>>,
}

/// The state associated with a directory handle.
#[derive(Debug)]
pub struct DirectoryHandleState {
    /// The list of directory entries for this directory handle.
    pub entries: Vec<DirectoryEntry>,
}

/// The state associated with a file or directory handle.
#[derive(Debug)]
pub enum HandleState {
    File(FileHandleState),
    Directory(DirectoryHandleState),
}

/// A table for allocating file handles in a virtual file system.
#[derive(Debug, Default)]
pub struct HandleTable {
    /// A pool of unique integers to act as file handles.
    pool: IdPool,

    /// A map of file handles to their state.
    state: HashMap<FileHandle, HandleState>,
}

impl HandleTable {
    /// Return a new empty `HandleTable`.
    pub fn new() -> Self {
        Self::default()
    }

    /// Get a new file handle with the given `state`.
    pub fn open(&mut self, state: HandleState) -> FileHandle {
        let fh = FileHandle(self.pool.next());
        self.state.insert(fh, state);
        fh
    }

    /// Remove the given `fh` from the table, returning its [`HandleState`] if present.
    pub fn close(&mut self, fh: FileHandle) -> Option<HandleState> {
        self.pool.recycle(fh.0);
        self.state.remove(&fh)
    }

    /// Get the state associated with the given `fh`.
    pub fn state(&self, fh: FileHandle) -> Option<&HandleState> {
        self.state.get(&fh)
    }

    /// Get the state associated with the given `fh`.
    pub fn state_mut(&mut self, fh: FileHandle) -> Option<&mut HandleState> {
        self.state.get_mut(&fh)
    }
}