liteboxfs 0.1.0

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

use fuser::{FileAttr, INodeNo};
use nix::sys::stat;

use crate::{Acl, AclMode, AclQualifier, File, FileDiscriminant, FileKind, FileMode};

/// Extract the user permission bits from a file `mode`.
fn user_perm(mode: u32) -> u16 {
    ((mode & 0o700) >> 6) as u16
}

/// Extract the group permission bits from a file `mode`.
fn group_perm(mode: u32) -> u16 {
    ((mode & 0o070) >> 3) as u16
}

/// Extract the other permission bits from a file `mode`.
fn other_perm(mode: u32) -> u16 {
    (mode & 0o007) as u16
}

/// Modify the given `acl` so its permissions do not exceed the given `mode`.
///
/// This modifies the entries in `acl` which correspond to permissions in the file mode so that
/// they do not exceed the permissions granted by the given `mode`.
fn constrain_acl(acl: &mut Acl, mode: FileMode) {
    if let Some(acl_mode) = acl.get_mut(AclQualifier::OwningUser) {
        *acl_mode = AclMode::from_bits(acl_mode.bits() & user_perm(mode.bits())).unwrap();
    }
    if let Some(acl_mode) = match acl.get_mut(AclQualifier::Mask) {
        Some(acl_mode) => Some(acl_mode),
        None => acl.get_mut(AclQualifier::OwningGroup),
    } {
        *acl_mode = AclMode::from_bits(acl_mode.bits() & group_perm(mode.bits())).unwrap();
    }
    if let Some(acl_mode) = acl.get_mut(AclQualifier::Other) {
        *acl_mode = AclMode::from_bits(acl_mode.bits() & other_perm(mode.bits())).unwrap();
    }
}

/// Return a new `mode` which does not exceed the permissions of the given `acl`.
fn constrain_mode(acl: &Acl, mode: FileMode) -> FileMode {
    let mut acl_mode = 0u16;

    let user_mode = acl
        .get(AclQualifier::OwningUser)
        .unwrap_or(AclMode::RWX)
        .bits();
    acl_mode |= user_mode << 6;

    let group_mode = acl.get(AclQualifier::OwningGroup);
    let mask_or_group_mode = acl
        .get(AclQualifier::Mask)
        .or(group_mode)
        .unwrap_or(AclMode::RWX)
        .bits();
    acl_mode |= mask_or_group_mode << 3;

    let other_mode = acl.get(AclQualifier::Other).unwrap_or(AclMode::RWX).bits();
    acl_mode |= other_mode;

    FileMode::from_bits_truncate(acl_mode as u32 & mode.bits())
}

/// The value of `st_rdev` value to use if the file is not a character or block device.
const NON_SPECIAL_RDEV: u32 = 0;

/// The block size used to calculate `st_blocks`.
const BLOCK_SIZE: u32 = 512;

#[derive(Debug)]
pub struct FileAttrWithoutInode {
    attrs: FileAttr,
}

impl FileAttrWithoutInode {
    pub fn with_inode(mut self, inode: INodeNo) -> FileAttr {
        self.attrs.ino = inode;
        self.attrs
    }
}

impl<'conn, 'fs> File<'conn, 'fs> {
    /// Get the FUSE `FileAttr` for this file with the given `inode`.
    pub(super) fn attrs(&mut self) -> crate::Result<FileAttrWithoutInode> {
        let metadata = self.metadata()?;
        let link_count = self.link_count()?;

        let size = match self.kind() {
            FileKind::Regular => self.len()?,
            FileKind::Dir => 0,
            // The `st_size` of a symlink should be the length of the pathname it contains.
            FileKind::Symlink { target } => target.as_os_str().len() as u64,
            _ => 0,
        };

        let acl = self.access_acl()?;

        // The mode returned needs to take into account the ACL mask if it is set, because it
        // affects the group permissions.
        let mode = match acl.get(AclQualifier::Mask) {
            None => metadata.mode().bits(),
            Some(mask_mode) => (metadata.mode().bits() & 0o707) | ((mask_mode.bits() as u32) << 3),
        };

        Ok(FileAttrWithoutInode {
            attrs: FileAttr {
                // The inode number is set later.
                ino: INodeNo(0),
                size,
                blocks: size / u64::from(BLOCK_SIZE),
                atime: metadata.accessed(),
                mtime: metadata.modified(),
                ctime: metadata.changed(),
                crtime: metadata.created().unwrap_or_else(SystemTime::now),
                kind: self.kind().discriminant().to_fuse_file_type(),
                perm: mode as u16,
                nlink: link_count,
                uid: metadata.user().into(),
                gid: metadata.group().into(),
                rdev: match self.kind() {
                    FileKind::Block { dev } => stat::makedev(dev.major(), dev.minor()) as u32,
                    FileKind::Char { dev } => stat::makedev(dev.major(), dev.minor()) as u32,
                    _ => NON_SPECIAL_RDEV,
                },
                blksize: BLOCK_SIZE,
                flags: 0,
            },
        })
    }

    /// Set the access ACL and mode for a file with the given `parent` and `mode`.
    ///
    /// This calculates the appropriate access ACL and mode for a new entry based on the `parent`
    /// default ACL and the given `mode`. If `mode` is `None`, this method does not set the mode.
    pub(super) fn constrain_permissions(
        &mut self,
        parent_default_acl: &Acl,
        mode: Option<FileMode>,
    ) -> crate::Result<()> {
        let mut access_acl = parent_default_acl.clone();

        if matches!(self.kind().discriminant(), FileDiscriminant::Dir) {
            self.set_default_acl(parent_default_acl)?;
        }

        if let Some(mode) = mode {
            constrain_acl(&mut access_acl, mode);
            self.set_mode(constrain_mode(&access_acl, mode))?;
        };

        self.set_access_acl(&access_acl)?;

        Ok(())
    }
}