// SPDX-License-Identifier: AGPL-3.0-or-later //! Definitions of messages passed between filesystem clients and servers. use super::{Handle, Inode}; use btlib::{ bterr, crypto::ConcretePub, BlockMetaSecrets, DirEntry, DirEntryKind, Epoch, IssuedProcRec, }; use bttp::CallMsg; use core::time::Duration; use paste::paste; use serde::{Deserialize, Serialize}; use std::{ fmt::Display, io, ops::{BitOr, BitOrAssign}, }; /// Top-level message type for the filesystem protocol. /// All messages passed from clients to servers are of this type. #[derive(Serialize, Deserialize)] pub enum FsMsg<'a> { #[serde(borrow)] Lookup(Lookup<'a>), #[serde(borrow)] Create(Create<'a>), Open(Open), Read(Read), #[serde(borrow)] Write(Write<&'a [u8]>), Flush(Flush), ReadDir(ReadDir), #[serde(borrow)] Link(Link<'a>), #[serde(borrow)] Unlink(Unlink<'a>), ReadMeta(ReadMeta), WriteMeta(WriteMeta), Allocate(Allocate), Close(Close), Forget(Forget), Lock(Lock), Unlock(Unlock), AddReadcap(AddReadcap), GrantAccess(GrantAccess), } /// The type for every reply sent from servers to clients. #[derive(Serialize, Deserialize)] pub enum FsReply<'a> { /// Indicates a message was received successfully but does not provide any additional data. Ack(()), Lookup(LookupReply), Create(CreateReply), Open(OpenReply), #[serde(borrow)] Read(ReadReply<'a>), Write(WriteReply), ReadDir(ReadDirReply), Link(LinkReply), ReadMeta(ReadMetaReply), WriteMeta(WriteMetaReply), } impl<'a> CallMsg<'a> for FsMsg<'a> { type Reply<'b> = FsReply<'b>; } #[repr(u64)] /// An enumeration of special Inodes. pub enum SpecInodes { RootDir = 1, Sb = 2, FirstFree = 11, } impl SpecInodes { pub fn value(self) -> Inode { self as Inode } } impl From for Inode { fn from(special: SpecInodes) -> Self { special.value() } } #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)] #[repr(u32)] // This type needs to match `libc::mode_t`. /// The type of a file (regular, directory, etc). pub enum FileType { /// Directory. Dir = libc::S_IFDIR, /// Regular file. Reg = libc::S_IFREG, } impl FileType { /// Returns the underlying mode bits for this file type. pub fn value(self) -> libc::mode_t { self as libc::mode_t } /// Attempts to convert the given mode bits into a `FileType` enum value. pub fn from_value(value: libc::mode_t) -> btlib::Result { if (value & libc::S_IFDIR) != 0 { return Ok(FileType::Dir); } if (value & libc::S_IFREG) != 0 { return Ok(FileType::Reg); } Err(bterr!("unknown file type: 0o{value:0o}")) } pub fn dir_entry_kind(self) -> DirEntryKind { match self { Self::Dir => DirEntryKind::Directory, Self::Reg => DirEntryKind::File, } } } impl From for libc::mode_t { fn from(file_type: FileType) -> Self { file_type.value() } } impl TryFrom for FileType { type Error = btlib::Error; fn try_from(value: libc::mode_t) -> btlib::Result { Self::from_value(value) } } impl From for DirEntryKind { fn from(value: FileType) -> Self { value.dir_entry_kind() } } impl BitOr for FileType { type Output = libc::mode_t; fn bitor(self, rhs: libc::mode_t) -> Self::Output { self.value() | rhs } } #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)] #[repr(i32)] /// The generators for the group of [Flags]. /// /// These are mostly values from libc, save for several custom values. Note that the presence of a /// flag in this enum does not guarantee it's supported. /// The standard libc `O_*` value which corresponds to each variant is given in the variant's /// comment. /// Consult your libc documentation for an explanation of what these means. pub enum FlagValue { // Standard flags. /// `O_RDONLY` ReadOnly = libc::O_RDONLY, /// `O_WRONLY` WriteOnly = libc::O_WRONLY, /// `O_RDWR` ReadWrite = libc::O_RDWR, /// `O_ACCMODE` AccMode = libc::O_ACCMODE, /// `O_CREAT` Create = libc::O_CREAT, /// `O_EXCL` Exclusive = libc::O_EXCL, /// `O_NOCTTY` NoCtty = libc::O_NOCTTY, /// `O_TRUNC` Truncate = libc::O_TRUNC, /// `O_APPEND` Append = libc::O_APPEND, /// `O_NONBLOCK` NonBlock = libc::O_NONBLOCK, /// `O_DSYNC` Dsync = libc::O_DSYNC, /// `O_ASYNC` Async = libc::O_ASYNC, /// `O_DIRECT` Direct = libc::O_DIRECT, /// `O_DIRECTORY` Directory = libc::O_DIRECTORY, /// `O_NOFOLLOW` NoFollow = libc::O_NOFOLLOW, /// `O_NOATIME` NoAtime = libc::O_NOATIME, /// `O_CLOEXEC` CloseExec = libc::O_CLOEXEC, /// `O_RSYNC` Rsync = libc::O_RSYNC, /// `O_PATH` Path = libc::O_PATH, /// `O_TMPFILE` TmpFile = libc::O_TMPFILE, // Custom flags. /// Indicates that a process block should be created. Process = 0x01000000, /// Indicates that a server block should be created. Server = 0x02000000, } impl FlagValue { /// Returns the underlying [i32] value. pub const fn value(self) -> i32 { self as i32 } } impl Copy for FlagValue {} impl From for i32 { fn from(flag_value: FlagValue) -> Self { flag_value.value() } } impl BitOr for FlagValue { type Output = Flags; fn bitor(self, rhs: Self) -> Self::Output { Flags::new(self.value() | rhs.value()) } } #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)] /// A wrapper type around [i32] with convenience methods for checking if `libc::O_*` /// flags have been set. pub struct Flags(i32); impl Copy for Flags {} impl Flags { /// Wraps the given [i32] value. pub const fn new(value: i32) -> Self { Self(value) } /// Returns the inner [i32] value. pub const fn value(self) -> i32 { self.0 } /// Returns true if these flags allow the file to be written to. pub const fn writeable(self) -> bool { const MASK: i32 = FlagValue::ReadWrite.value() | FlagValue::WriteOnly.value(); self.0 & MASK != 0 } /// Returns true if these flags allow the file to be read from. pub const fn readable(self) -> bool { !self.write_only() } /// Returns true if the file was opened readonly. pub const fn read_only(self) -> bool { self.0 == FlagValue::ReadOnly.value() } /// Returns true if the file was opened writeonly pub const fn write_only(self) -> bool { self.0 & FlagValue::WriteOnly.value() != 0 } /// Returns true if these flags are for a directory. pub const fn directory(self) -> bool { self.0 & FlagValue::Directory.value() != 0 } /// Returns true if these flags are for a process. pub const fn process(self) -> bool { self.0 & FlagValue::Process.value() != 0 } /// Returns true if these flags are for a server. pub const fn server(self) -> bool { self.0 & FlagValue::Server.value() != 0 } /// Asserts that these flags allow a file to be read. /// /// If the assertion fails then an [io::Error] with the errno [libc::EACCES] is returned. pub fn assert_readable(self) -> Result<(), io::Error> { if !self.readable() { Err(io::Error::from_raw_os_error(libc::EACCES)) } else { Ok(()) } } /// Asserts that these flags allow a file to be written. /// /// If the assertion fails then an [io::Error] with the errno [libc::EACCES] is returned. pub fn assert_writeable(self) -> Result<(), io::Error> { if !self.writeable() { Err(io::Error::from_raw_os_error(libc::EACCES)) } else { Ok(()) } } } impl From for Flags { fn from(value: i32) -> Self { Self::new(value) } } impl From for i32 { fn from(flags: Flags) -> Self { flags.value() } } impl From for Flags { fn from(flag_value: FlagValue) -> Self { Self::new(flag_value.value()) } } impl BitOr for Flags { type Output = Flags; fn bitor(self, rhs: Flags) -> Self::Output { Self::new(self.value() | rhs.value()) } } impl BitOr for Flags { type Output = Flags; fn bitor(self, rhs: FlagValue) -> Self::Output { Self::new(self.value() | rhs.value()) } } impl Display for Flags { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.value().fmt(f) } } impl Default for Flags { fn default() -> Self { Self::new(0) } } /// Attributes of a file. #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Hash, Default)] pub struct Attrs { pub mode: u32, pub uid: u32, pub gid: u32, pub atime: Epoch, pub mtime: Epoch, pub ctime: Epoch, pub tags: Vec<(String, Vec)>, } #[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Hash)] /// A type for indicating which fields in [Attrs] have been set and which should be ignored. /// /// This method was chosen over using [Option] for greater efficiency on the wire. pub struct AttrsSet(u16); macro_rules! field { ($index:expr, $name:ident) => { pub const $name: AttrsSet = AttrsSet::new(1 << $index); paste! { pub fn [<$name:lower>](self) -> bool { const MASK: u16 = 1 << $index; self.0 & MASK != 0 } } }; } impl AttrsSet { field!(0, MODE); field!(1, UID); field!(2, GID); field!(3, ATIME); field!(4, MTIME); field!(5, CTIME); pub const ALL: Self = Self::new( Self::MODE.0 | Self::UID.0 | Self::GID.0 | Self::ATIME.0 | Self::MTIME.0 | Self::CTIME.0, ); pub const fn new(value: u16) -> Self { Self(value) } pub const fn none() -> Self { Self(0) } pub const fn value(self) -> u16 { self.0 } } impl Copy for AttrsSet {} impl From for AttrsSet { fn from(value: u16) -> Self { Self::new(value) } } impl From for u16 { fn from(attr: AttrsSet) -> Self { attr.value() } } impl BitOr for AttrsSet { type Output = Self; fn bitor(self, rhs: Self) -> Self::Output { AttrsSet::new(self.value() | rhs.value()) } } impl BitOrAssign for AttrsSet { fn bitor_assign(&mut self, rhs: Self) { self.0 |= rhs.0 } } /// A filesystem entry. /// /// This struct includes attributes of a file as well as cache control /// information for how long it may be cached. #[derive(Debug, Serialize, Deserialize)] pub struct Entry { pub attr: BlockMetaSecrets, pub attr_timeout: Duration, pub entry_timeout: Duration, } /// A request to lookup a name in a directory. #[derive(Serialize, Deserialize)] pub struct Lookup<'a> { pub parent: Inode, pub name: &'a str, } /// A reply containing the [Entry] stored under a name in a directory. #[derive(Debug, Serialize, Deserialize)] pub struct LookupReply { pub inode: Inode, pub generation: u64, pub entry: Entry, } /// A request to create a file in a directory under a given name. #[derive(Serialize, Deserialize)] pub struct Create<'a> { pub parent: Inode, pub name: &'a str, pub flags: Flags, pub mode: u32, pub umask: u32, } /// A reply containing information about a newly created file. #[derive(Serialize, Deserialize)] pub struct CreateReply { pub inode: Inode, pub handle: Handle, pub entry: Entry, } /// A request to hope a file. #[derive(Serialize, Deserialize)] pub struct Open { pub inode: Inode, pub flags: Flags, } /// A reply which contains a handle to an open file. #[derive(Serialize, Deserialize)] pub struct OpenReply { pub handle: Handle, } /// A request to read data at a particular offset from an open file. #[derive(Serialize, Deserialize)] pub struct Read { pub inode: Inode, pub handle: Handle, pub offset: u64, pub size: u64, } /// A reply containing data read from a file. #[derive(Serialize, Deserialize)] pub struct ReadReply<'a> { pub data: &'a [u8], } /// A request to write data to a particular offset in an open file. #[derive(Serialize, Deserialize)] pub struct Write { pub inode: Inode, pub handle: Handle, pub offset: u64, pub data: R, } /// A reply containing the number of bytes written to a file. #[derive(Serialize, Deserialize)] pub struct WriteReply { pub written: u64, } /// A request to flush all data cached for an open file to durable storage. #[derive(Serialize, Deserialize)] pub struct Flush { pub inode: Inode, pub handle: Handle, } /// A request to read the contents of a directory. #[derive(Serialize, Deserialize)] pub struct ReadDir { pub inode: Inode, pub handle: Handle, /// The maximum number of directory entries to return in a single response. A value of 0 /// indicates there is no limit. Note that the server may impose it's own limit. pub limit: u32, /// An opaque value which the server uses to keep track of the client's position in reading /// the directory. A value of 0 indicates the directory is to be iterated from the beginning. pub state: u64, } /// A reply containing the contents of a directory. #[derive(Serialize, Deserialize)] pub struct ReadDirReply { pub entries: Vec<(String, DirEntry)>, /// This is the value to pass in a subsequent [ReadDir] message to continue reading this /// directory. A value of 0 indicates that all entries have been returned. pub new_state: u64, } /// A request to create a new hard link to a file. #[derive(Serialize, Deserialize)] pub struct Link<'a> { pub inode: Inode, pub new_parent: Inode, pub name: &'a str, } /// A reply containing the [Entry] of the newly created hard link. #[derive(Serialize, Deserialize)] pub struct LinkReply { pub entry: Entry, } /// A request to remove a name referring to an inode. /// /// If the inode becomes orphaned, it is removed from durable storage. #[derive(Serialize, Deserialize)] pub struct Unlink<'a> { pub parent: Inode, pub name: &'a str, } /// A request to read a file's metadata. #[derive(Serialize, Deserialize)] pub struct ReadMeta { pub inode: Inode, pub handle: Option, } /// A reply containing the metadata of a file. #[derive(Serialize, Deserialize)] pub struct ReadMetaReply { pub attrs: BlockMetaSecrets, pub valid_for: Duration, } /// A request to write the metadata of a file. #[derive(Serialize, Deserialize)] pub struct WriteMeta { pub inode: Inode, pub handle: Option, pub attrs: Attrs, /// The bits in this value indicate which fields in `attrs` have been initialized. pub attrs_set: AttrsSet, } /// A reply containing the newly written file metadata. #[derive(Serialize, Deserialize)] pub struct WriteMetaReply { pub attrs: BlockMetaSecrets, pub valid_for: Duration, } /// A request to pre-allocate a given amount of space from durable storage for a file. #[derive(Serialize, Deserialize)] pub struct Allocate { pub inode: Inode, pub handle: Handle, pub offset: Option, pub size: u64, } /// A request to close an open file. #[derive(Serialize, Deserialize)] pub struct Close { pub inode: Inode, pub handle: Handle, } /// A request to forget about an inode which was previously referenced. /// /// This message must be sent /// so the server knows when it can free resources associated with the inode. /// If `N` [Entry] structs are sent in a reply to [Lookup] messages, then the caller must send one /// or more [Forget] messages such that their `count` fields add up to `N`. #[derive(Serialize, Deserialize)] pub struct Forget { pub inode: Inode, pub count: u64, } /// The description of a region in a file to lock. #[derive(Serialize, Deserialize)] pub struct LockDesc { pub offset: u64, pub size: u64, pub exclusive: bool, } /// A request to lock a region of a file. #[derive(Serialize, Deserialize)] pub struct Lock { pub inode: Inode, pub handle: Handle, pub desc: LockDesc, } /// A request to unlock a region of a file. #[derive(Serialize, Deserialize)] pub struct Unlock { pub inode: Inode, pub handle: Handle, } /// A request to add a [btlib::Readcap] to a file. #[derive(Serialize, Deserialize)] pub struct AddReadcap { pub inode: Inode, pub handle: Handle, pub pub_creds: ConcretePub, } /// A request to give a process access to an inode. #[derive(Serialize, Deserialize)] pub struct GrantAccess { pub inode: Inode, pub record: IssuedProcRec, }