liteboxfs 0.2.0

A modern POSIX filesystem in a SQLite database
Documentation
use crate::{
    errors::InternalError,
    file_metadata::{Acl, AclMode, AclQualifier, Gid, Uid},
};

#[derive(Debug, Clone, Copy)]
struct RawAclVersion(u32);

#[derive(Debug, Clone, Copy)]
struct RawAclTag(u16);

#[derive(Debug, Clone, Copy)]
struct RawAclPerm(u16);

#[derive(Debug, Clone, Copy)]
struct RawAclId(u32);

const POSIX_ACL_XATTR_VERSION: RawAclVersion = RawAclVersion(0x0002);
const UNDEFINED_ID: RawAclId = RawAclId(0);

#[derive(Debug, Clone, Copy)]
enum AclTag {
    Undefined,
    UserObj,
    User,
    GroupObj,
    Group,
    Mask,
    Other,
}

impl From<AclTag> for RawAclTag {
    fn from(tag: AclTag) -> Self {
        RawAclTag(match tag {
            AclTag::Undefined => 0,
            AclTag::UserObj => 1,
            AclTag::User => 2,
            AclTag::GroupObj => 3,
            AclTag::Group => 4,
            AclTag::Mask => 5,
            AclTag::Other => 6,
        })
    }
}

#[derive(Debug)]
struct RawAclEntry {
    tag: RawAclTag,
    perm: RawAclPerm,
    id: Option<RawAclId>,
}

impl RawAclEntry {
    fn serialize(&self, buf: &mut Vec<u8>) {
        buf.extend_from_slice(&self.tag.0.to_le_bytes());
        buf.extend_from_slice(&self.perm.0.to_le_bytes());
        buf.extend_from_slice(&self.id.unwrap_or(UNDEFINED_ID).0.to_le_bytes());
    }
}

#[derive(Debug)]
struct RawAcl(Vec<RawAclEntry>);

impl RawAcl {
    fn serialize(&self) -> Vec<u8> {
        let mut buf = Vec::new();

        buf.extend_from_slice(&POSIX_ACL_XATTR_VERSION.0.to_le_bytes());

        for entry in &self.0 {
            entry.serialize(&mut buf);
        }

        buf
    }
}

impl From<(AclQualifier, AclMode)> for RawAclEntry {
    fn from((qualifier, mode): (AclQualifier, AclMode)) -> Self {
        match qualifier {
            AclQualifier::User(uid) => RawAclEntry {
                tag: AclTag::User.into(),
                perm: RawAclPerm(mode.bits()),
                id: Some(RawAclId(uid.as_raw())),
            },
            AclQualifier::Group(gid) => RawAclEntry {
                tag: AclTag::Group.into(),
                perm: RawAclPerm(mode.bits()),
                id: Some(RawAclId(gid.as_raw())),
            },
            AclQualifier::OwningUser => RawAclEntry {
                tag: AclTag::UserObj.into(),
                perm: RawAclPerm(mode.bits()),
                id: None,
            },
            AclQualifier::OwningGroup => RawAclEntry {
                tag: AclTag::GroupObj.into(),
                perm: RawAclPerm(mode.bits()),
                id: None,
            },
            AclQualifier::Other => RawAclEntry {
                tag: AclTag::Other.into(),
                perm: RawAclPerm(mode.bits()),
                id: None,
            },
            AclQualifier::Mask => RawAclEntry {
                tag: AclTag::Mask.into(),
                perm: RawAclPerm(mode.bits()),
                id: None,
            },
        }
    }
}

impl From<Acl> for RawAcl {
    fn from(acl: Acl) -> Self {
        RawAcl(acl.into_iter().map(RawAclEntry::from).collect())
    }
}

impl TryFrom<RawAclTag> for AclTag {
    type Error = ();

    fn try_from(tag: RawAclTag) -> Result<Self, Self::Error> {
        match tag.0 {
            0 => Ok(AclTag::Undefined),
            1 => Ok(AclTag::UserObj),
            2 => Ok(AclTag::User),
            3 => Ok(AclTag::GroupObj),
            4 => Ok(AclTag::Group),
            5 => Ok(AclTag::Mask),
            6 => Ok(AclTag::Other),
            _ => Err(()),
        }
    }
}

impl Acl {
    /// Convert this ACL into its serialized POSIX binary representation.
    pub(crate) fn serialize(&self) -> Vec<u8> {
        RawAcl::from(self.clone()).serialize()
    }

    /// Parse an ACL from its serialized POSIX binary representation.
    ///
    /// Returns an empty ACL if the data is malformed or empty.
    pub(crate) fn deserialize(data: &[u8]) -> crate::Result<Self> {
        if data.is_empty() {
            return Ok(Acl::new());
        }

        // Need at least 4 bytes for version header.
        let Some(version_bytes) = data.get(..4) else {
            return Err(InternalError::MalformedAcl.into());
        };

        let Ok(version_array): Result<[u8; 4], _> = version_bytes.try_into() else {
            return Err(InternalError::MalformedAcl.into());
        };

        let version = u32::from_le_bytes(version_array);
        if version != POSIX_ACL_XATTR_VERSION.0 {
            return Err(InternalError::MalformedAcl.into());
        }

        // Each entry is 8 bytes (2 tag + 2 perm + 4 id).
        let Some(entry_data) = data.get(4..) else {
            return Err(InternalError::MalformedAcl.into());
        };
        if !entry_data.len().is_multiple_of(8) {
            return Err(InternalError::MalformedAcl.into());
        }

        let mut acl = Acl::new();

        for chunk in entry_data.chunks_exact(8) {
            let Ok(tag_bytes): Result<[u8; 2], _> = chunk.get(..2).unwrap_or_default().try_into()
            else {
                return Err(InternalError::MalformedAcl.into());
            };

            let Ok(perm_bytes): Result<[u8; 2], _> = chunk.get(2..4).unwrap_or_default().try_into()
            else {
                return Err(InternalError::MalformedAcl.into());
            };

            let Ok(id_bytes): Result<[u8; 4], _> = chunk.get(4..8).unwrap_or_default().try_into()
            else {
                return Err(InternalError::MalformedAcl.into());
            };

            let tag = RawAclTag(u16::from_le_bytes(tag_bytes));
            let perm = RawAclPerm(u16::from_le_bytes(perm_bytes));
            let id = RawAclId(u32::from_le_bytes(id_bytes));

            let Ok(acl_tag) = AclTag::try_from(tag) else {
                return Err(InternalError::MalformedAcl.into());
            };

            let mode = AclMode::from_bits_truncate(perm.0);

            let qualifier = match acl_tag {
                AclTag::Undefined => continue,
                AclTag::UserObj => AclQualifier::OwningUser,
                AclTag::User => AclQualifier::User(Uid::from_raw(id.0)),
                AclTag::GroupObj => AclQualifier::OwningGroup,
                AclTag::Group => AclQualifier::Group(Gid::from_raw(id.0)),
                AclTag::Mask => AclQualifier::Mask,
                AclTag::Other => AclQualifier::Other,
            };

            acl.set(qualifier, mode);
        }

        Ok(acl)
    }
}