blown-fuse 0.3.1

Async filesystem in userspace (FUSE) daemon library
Documentation
use bytemuck::Zeroable;
use nix::sys::stat::SFlag;

use std::{
    convert::Infallible,
    ffi::OsStr,
    future::Future,
    ops::{ControlFlow, FromResidual, Try},
    time::{SystemTime, UNIX_EPOCH},
};

use super::{Done, Operation, Reply, Request};
use crate::{proto, Errno, FuseResult};

#[doc(no_inline)]
pub use nix::{
    dir::Type as EntryType,
    fcntl::OFlag as OpenFlags,
    sys::stat::Mode,
    unistd::{AccessFlags, Gid, Pid, Uid},
};

pub use proto::FsyncFlags;

#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub struct Ino(pub u64);

#[derive(Copy, Clone, Eq, PartialEq)]
pub struct Ttl {
    seconds: u64,
    nanoseconds: u32,
}

#[derive(Copy, Clone, Default, Eq, PartialEq)]
pub struct Timestamp {
    seconds: i64,
    nanoseconds: u32,
}

pub enum Interruptible<'o, O: Operation<'o>, T> {
    Completed(Reply<'o, O>, T),
    Interrupted(Done<'o>),
}

pub trait Stat {
    fn ino(&self) -> Ino;
    fn inode_type(&self) -> EntryType;
    fn attrs(&self) -> (Attrs, Ttl);
}

pub trait Known {
    type Inode: Stat;

    fn inode(&self) -> &Self::Inode;
    fn unveil(self);
}

pub struct Failed<'o, E>(pub Done<'o>, pub E);

pub trait Finish<'o, O: Operation<'o>> {
    fn finish(&self, reply: Reply<'o, O>) -> Done<'o>;
}

#[derive(Clone)]
pub struct Attrs(proto::Attrs);

pub struct Entry<'a, K> {
    pub offset: u64,
    pub name: &'a OsStr,
    pub inode: K,
    pub ttl: Ttl,
}

#[derive(Copy, Clone)]
pub struct FsInfo(proto::StatfsOut);

impl Ino {
    pub const NULL: Self = Ino(0);

    pub const ROOT: Self = Ino(proto::ROOT_ID);

    pub fn as_raw(self) -> u64 {
        self.0
    }
}

impl std::fmt::Display for Ino {
    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(formatter, "{}", self.0)
    }
}

impl Ttl {
    pub const NULL: Self = Ttl {
        seconds: 0,
        nanoseconds: 0,
    };

    pub const MAX: Self = Ttl {
        seconds: u64::MAX,
        nanoseconds: u32::MAX,
    };

    pub fn new(seconds: u64, nanoseconds: u32) -> Ttl {
        assert!(nanoseconds < 1_000_000_000);

        Ttl {
            seconds,
            nanoseconds,
        }
    }

    pub fn seconds(self) -> u64 {
        self.seconds
    }

    pub fn nanoseconds(self) -> u32 {
        self.nanoseconds
    }
}

impl Timestamp {
    pub fn new(seconds: i64, nanoseconds: u32) -> Self {
        Timestamp {
            seconds,
            nanoseconds,
        }
    }
}

impl From<SystemTime> for Timestamp {
    fn from(time: SystemTime) -> Self {
        let (seconds, nanoseconds) = match time.duration_since(UNIX_EPOCH) {
            Ok(duration) => {
                let secs = duration.as_secs().try_into().unwrap();
                (secs, duration.subsec_nanos())
            }

            Err(before_epoch) => {
                let duration = before_epoch.duration();
                let secs = -i64::try_from(duration.as_secs()).unwrap();
                (secs, duration.subsec_nanos())
            }
        };

        Timestamp {
            seconds,
            nanoseconds,
        }
    }
}

impl<'o, E> From<Failed<'o, E>> for Done<'o> {
    fn from(failed: Failed<'o, E>) -> Done<'o> {
        failed.0
    }
}

impl<'o, O: Operation<'o>> Finish<'o, O> for Errno {
    fn finish(&self, reply: Reply<'o, O>) -> Done<'o> {
        reply.fail(*self)
    }
}

impl<'o, O: Operation<'o>> Finish<'o, O> for std::io::Error {
    fn finish(&self, reply: Reply<'o, O>) -> Done<'o> {
        reply.fail(
            self.raw_os_error()
                .map(Errno::from_i32)
                .unwrap_or(Errno::EIO),
        )
    }
}

impl<'o, O: Operation<'o>> Request<'o, O> {
    pub fn ino(&self) -> Ino {
        Ino(self.header.ino)
    }

    pub fn generation(&self) -> u64 {
        0
    }

    pub fn uid(&self) -> Uid {
        Uid::from_raw(self.header.uid)
    }

    pub fn gid(&self) -> Gid {
        Gid::from_raw(self.header.gid)
    }

    pub fn pid(&self) -> Pid {
        Pid::from_raw(self.header.pid as i32)
    }
}

impl<'o, O: Operation<'o>> Reply<'o, O> {
    pub async fn interruptible<F, T>(self, f: F) -> Interruptible<'o, O, T>
    where
        F: Future<Output = T>,
    {
        tokio::pin!(f);
        let mut rx = self.session.interrupt_rx();

        use Interruptible::*;
        loop {
            tokio::select! {
                output = &mut f => break Completed(self, output),

                result = rx.recv() => match result {
                    Ok(unique) if unique == self.unique => {
                        break Interrupted(self.interrupted());
                    }

                    _ => continue,
                }
            }
        }
    }

    pub fn and_then<T, E>(self, result: Result<T, E>) -> Result<(Self, T), Failed<'o, E>>
    where
        E: Finish<'o, O>,
    {
        match result {
            Ok(t) => Ok((self, t)),
            Err(error) => {
                let done = error.finish(self);
                Err(Failed(done, error))
            }
        }
    }

    pub fn fail(self, errno: Errno) -> Done<'o> {
        let result = self.session.fail(self.unique, errno as i32);
        self.finish(result)
    }

    pub fn not_implemented(self) -> Done<'o> {
        self.fail(Errno::ENOSYS)
    }

    pub fn not_permitted(self) -> Done<'o> {
        self.fail(Errno::EPERM)
    }

    pub fn io_error(self) -> Done<'o> {
        self.fail(Errno::EIO)
    }

    pub fn invalid_argument(self) -> Done<'o> {
        self.fail(Errno::EINVAL)
    }

    pub fn interrupted(self) -> Done<'o> {
        self.fail(Errno::EINTR)
    }

    pub(crate) fn finish(self, result: FuseResult<()>) -> Done<'o> {
        if let Err(error) = result {
            log::error!("Replying to request {}: {}", self.unique, error);
        }

        Done::new()
    }
}

impl<'o, O: Operation<'o>> From<(Reply<'o, O>, Errno)> for Done<'o> {
    fn from((reply, errno): (Reply<'o, O>, Errno)) -> Done<'o> {
        reply.fail(errno)
    }
}

impl<'o> FromResidual<Done<'o>> for Done<'o> {
    fn from_residual(residual: Done<'o>) -> Self {
        residual
    }
}

impl<'o, T: Into<Done<'o>>> FromResidual<Result<Infallible, T>> for Done<'o> {
    fn from_residual(residual: Result<Infallible, T>) -> Self {
        match residual {
            Ok(_) => unreachable!(),
            Err(t) => t.into(),
        }
    }
}

impl<'o, O: Operation<'o>> FromResidual<Interruptible<'o, O, Infallible>> for Done<'o> {
    fn from_residual(residual: Interruptible<'o, O, Infallible>) -> Self {
        match residual {
            Interruptible::Completed(_, _) => unreachable!(),
            Interruptible::Interrupted(done) => done,
        }
    }
}

impl Try for Done<'_> {
    type Output = Self;
    type Residual = Self;

    fn from_output(output: Self::Output) -> Self {
        output
    }

    fn branch(self) -> ControlFlow<Self::Residual, Self::Output> {
        ControlFlow::Break(self)
    }
}

impl<'o, O: Operation<'o>, T> FromResidual<Interruptible<'o, O, Infallible>>
    for Interruptible<'o, O, T>
{
    fn from_residual(residual: Interruptible<'o, O, Infallible>) -> Self {
        use Interruptible::*;

        match residual {
            Completed(_, _) => unreachable!(),
            Interrupted(done) => Interrupted(done),
        }
    }
}

impl<'o, O: Operation<'o>, T> Try for Interruptible<'o, O, T> {
    type Output = (Reply<'o, O>, T);
    type Residual = Interruptible<'o, O, Infallible>;

    fn from_output((reply, t): Self::Output) -> Self {
        Self::Completed(reply, t)
    }

    fn branch(self) -> ControlFlow<Self::Residual, Self::Output> {
        use Interruptible::*;

        match self {
            Completed(reply, t) => ControlFlow::Continue((reply, t)),
            Interrupted(done) => ControlFlow::Break(Interrupted(done)),
        }
    }
}

impl Attrs {
    #[must_use]
    pub fn size(self, size: u64) -> Self {
        Attrs(proto::Attrs { size, ..self.0 })
    }

    #[must_use]
    pub fn owner(self, uid: Uid, gid: Gid) -> Self {
        Attrs(proto::Attrs {
            uid: uid.as_raw(),
            gid: gid.as_raw(),
            ..self.0
        })
    }

    #[must_use]
    pub fn mode(self, mode: Mode) -> Self {
        Attrs(proto::Attrs {
            mode: mode.bits(),
            ..self.0
        })
    }

    #[must_use]
    pub fn blocks(self, blocks: u64) -> Self {
        Attrs(proto::Attrs { blocks, ..self.0 })
    }

    #[must_use]
    pub fn block_size(self, block_size: u32) -> Self {
        Attrs(proto::Attrs {
            blksize: block_size,
            ..self.0
        })
    }

    #[must_use]
    pub fn device(self, device: u32) -> Self {
        Attrs(proto::Attrs {
            rdev: device,
            ..self.0
        })
    }

    #[must_use]
    pub fn times(self, access: Timestamp, modify: Timestamp, change: Timestamp) -> Self {
        Attrs(proto::Attrs {
            atime: access.seconds as _,
            mtime: modify.seconds as _,
            ctime: change.seconds as _,
            atimensec: access.nanoseconds,
            mtimensec: modify.nanoseconds,
            ctimensec: change.nanoseconds,
            ..self.0
        })
    }

    #[must_use]
    pub fn links(self, links: u32) -> Self {
        Attrs(proto::Attrs {
            nlink: links,
            ..self.0
        })
    }

    pub(crate) fn finish(self, inode: &impl Stat) -> proto::Attrs {
        let Ino(ino) = inode.ino();
        let inode_type = match inode.inode_type() {
            EntryType::Fifo => SFlag::S_IFIFO,
            EntryType::CharacterDevice => SFlag::S_IFCHR,
            EntryType::Directory => SFlag::S_IFDIR,
            EntryType::BlockDevice => SFlag::S_IFBLK,
            EntryType::File => SFlag::S_IFREG,
            EntryType::Symlink => SFlag::S_IFLNK,
            EntryType::Socket => SFlag::S_IFSOCK,
        };

        proto::Attrs {
            ino,
            mode: self.0.mode | inode_type.bits(),
            ..self.0
        }
    }
}

impl Default for Attrs {
    fn default() -> Self {
        Attrs(Zeroable::zeroed()).links(1)
    }
}

impl FsInfo {
    #[must_use]
    pub fn blocks(self, size: u32, total: u64, free: u64, available: u64) -> Self {
        FsInfo(proto::StatfsOut {
            bsize: size,
            blocks: total,
            bfree: free,
            bavail: available,
            ..self.0
        })
    }

    #[must_use]
    pub fn inodes(self, total: u64, free: u64) -> Self {
        FsInfo(proto::StatfsOut {
            files: total,
            ffree: free,
            ..self.0
        })
    }

    #[must_use]
    pub fn max_filename(self, max: u32) -> Self {
        FsInfo(proto::StatfsOut {
            namelen: max,
            ..self.0
        })
    }
}

impl Default for FsInfo {
    fn default() -> Self {
        FsInfo(Zeroable::zeroed())
    }
}

impl From<FsInfo> for proto::StatfsOut {
    fn from(FsInfo(statfs): FsInfo) -> proto::StatfsOut {
        statfs
    }
}