Преглед на файлове

Wrote several more tests btfsd.

Matthew Carr преди 2 години
родител
ревизия
fac9db666a

+ 8 - 8
crates/btfproto-tests/src/local_fs.rs

@@ -712,10 +712,10 @@ mod tests {
             inode,
             handle: Some(handle),
         };
-        let ReadMetaReply { meta, .. } = bt.read_meta(from, read_meta_msg).await.unwrap();
+        let ReadMetaReply { attrs, .. } = bt.read_meta(from, read_meta_msg).await.unwrap();
 
         assert_eq!([0u8; 8], actual);
-        assert_eq!(actual.len() as u64, meta.body().secrets().unwrap().size);
+        assert_eq!(actual.len() as u64, attrs.size);
     }
 
     #[tokio::test]
@@ -761,10 +761,10 @@ mod tests {
             inode,
             handle: Some(handle),
         };
-        let ReadMetaReply { meta, .. } = bt.read_meta(from, read_meta_msg).await.unwrap();
+        let ReadMetaReply { attrs, .. } = bt.read_meta(from, read_meta_msg).await.unwrap();
 
         assert_eq!(vec![0u8; LEN], actual.into_inner());
-        assert_eq!(LEN as u64, meta.body().secrets().unwrap().size);
+        assert_eq!(LEN as u64, attrs.size);
     }
 
     #[tokio::test]
@@ -804,10 +804,10 @@ mod tests {
             inode,
             handle: Some(handle),
         };
-        let ReadMetaReply { meta, .. } = bt.read_meta(from, read_meta_msg).await.unwrap();
+        let ReadMetaReply { attrs, .. } = bt.read_meta(from, read_meta_msg).await.unwrap();
 
         assert_eq!(vec![0u8; LEN], actual);
-        assert_eq!(LEN as u64, meta.body().secrets().unwrap().size);
+        assert_eq!(LEN as u64, attrs.size);
     }
 
     /// Tests that when the new_size of the block is not greater than the current size of the block,
@@ -853,9 +853,9 @@ mod tests {
             inode,
             handle: Some(handle),
         };
-        let ReadMetaReply { meta, .. } = bt.read_meta(from, read_meta_msg).await.unwrap();
+        let ReadMetaReply { attrs, .. } = bt.read_meta(from, read_meta_msg).await.unwrap();
 
         assert_eq!([1u8; LEN], actual);
-        assert_eq!(LEN as u64, meta.body().secrets().unwrap().size);
+        assert_eq!(LEN as u64, attrs.size);
     }
 }

+ 3 - 2
crates/btfproto/src/client.rs

@@ -51,6 +51,7 @@ extractor_callback!(Open);
 extractor_callback!(Write);
 extractor_callback!(Link);
 extractor_callback!(ReadDir);
+extractor_callback!(WriteMeta);
 
 struct AckCallback;
 
@@ -223,14 +224,14 @@ impl<T: Transmitter> FsClient<T> {
         handle: Option<Handle>,
         attrs: Attrs,
         attrs_set: AttrsSet,
-    ) -> Result<()> {
+    ) -> Result<WriteMetaReply> {
         let msg = FsMsg::WriteMeta(WriteMeta {
             inode,
             handle,
             attrs,
             attrs_set,
         });
-        self.tx.call(msg, AckCallback).await?
+        self.tx.call(msg, ExtractWriteMeta).await?
     }
 
     pub async fn close(&self, inode: Inode, handle: Handle) -> Result<()> {

+ 6 - 60
crates/btfproto/src/local_fs.rs

@@ -7,7 +7,7 @@ use btlib::{
     crypto::{Creds, Decrypter, Signer},
     error::{BtErr, DisplayErr},
     AuthzAttrs, BlockAccessor, BlockError, BlockMeta, BlockMetaSecrets, BlockOpenOptions,
-    BlockPath, BlockReader, DirEntry, DirEntryKind, Directory, Epoch, FileBlock, FlushMeta,
+    BlockPath, BlockReader, DirEntry, Directory, Epoch, FileBlock, FlushMeta,
     IssuedProcRec, MetaAccess, MetaReader, Positioned, Principal, Principaled, ProcRec, Result,
     Split, TrySeek, WriteDual, ZeroExtendable,
 };
@@ -83,60 +83,6 @@ mod private {
 
     impl std::error::Error for Error {}
 
-    #[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).
-    enum FileType {
-        /// Directory.
-        Dir = libc::S_IFDIR,
-        /// Regular file.
-        Reg = libc::S_IFREG,
-    }
-
-    impl FileType {
-        /// Returns the underlying mode bits for this file type.
-        fn value(self) -> libc::mode_t {
-            self as libc::mode_t
-        }
-
-        /// Attempts to convert the given mode bits into a `FileType` enum value.
-        fn from_value(value: libc::mode_t) -> Result<Self> {
-            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}"))
-        }
-
-        fn dir_entry_kind(self) -> DirEntryKind {
-            match self {
-                Self::Dir => DirEntryKind::Directory,
-                Self::Reg => DirEntryKind::File,
-            }
-        }
-    }
-
-    impl From<FileType> for libc::mode_t {
-        fn from(file_type: FileType) -> Self {
-            file_type.value()
-        }
-    }
-
-    impl TryFrom<libc::mode_t> for FileType {
-        type Error = btlib::Error;
-        fn try_from(value: libc::mode_t) -> Result<Self> {
-            Self::from_value(value)
-        }
-    }
-
-    impl From<FileType> for DirEntryKind {
-        fn from(value: FileType) -> Self {
-            value.dir_entry_kind()
-        }
-    }
-
     /// This type provides context for an authorization decision as to whether a given process will
     /// be allowed to access a block.
     pub struct AuthzContext<'a> {
@@ -1447,14 +1393,14 @@ mod private {
             let result = (move || {
                 let ReadMeta { inode, handle } = msg;
                 debug!("read_meta: inode {inode}, handle {:?}", handle);
-                let meta = if let Some(handle) = handle {
-                    self.access_block(from, inode, handle, |block, _| Ok(block.meta().to_owned()))?
+                let attrs = if let Some(handle) = handle {
+                    self.access_block(from, inode, handle, |block, _| Ok(block.meta_body().secrets()?.to_owned()))?
                 } else {
-                    self.borrow_block(inode, |block| Ok(block.meta().to_owned()))?
+                    self.borrow_block(inode, |block| Ok(block.meta_body().secrets()?.to_owned()))?
                 };
-                debug!("read_meta: {:?}", meta.body().secrets()?);
+                debug!("read_meta attrs: {:?}", attrs);
                 let reply = ReadMetaReply {
-                    meta,
+                    attrs,
                     valid_for: self.attr_timeout(),
                 };
                 Ok(reply)

+ 68 - 3
crates/btfproto/src/msg.rs

@@ -3,7 +3,7 @@ use super::{Handle, Inode};
 
 use btlib::{
     crypto::{AsymKeyPub, Encrypt},
-    BlockMeta, BlockMetaSecrets, DirEntry, Epoch, IssuedProcRec, Principal,
+    BlockMetaSecrets, DirEntry, Epoch, IssuedProcRec, Principal, DirEntryKind, bterr,
 };
 use btmsg::CallMsg;
 use core::time::Duration;
@@ -90,6 +90,67 @@ impl From<SpecInodes> for Inode {
     }
 }
 
+#[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<Self> {
+        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<FileType> for libc::mode_t {
+    fn from(file_type: FileType) -> Self {
+        file_type.value()
+    }
+}
+
+impl TryFrom<libc::mode_t> for FileType {
+    type Error = btlib::Error;
+    fn try_from(value: libc::mode_t) -> btlib::Result<Self> {
+        Self::from_value(value)
+    }
+}
+
+impl From<FileType> for DirEntryKind {
+    fn from(value: FileType) -> Self {
+        value.dir_entry_kind()
+    }
+}
+
+impl BitOr<libc::mode_t> 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 [Flag]s. These are mostly copied from [libc], save for
@@ -286,6 +347,10 @@ impl AttrsSet {
     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)
     }
@@ -451,7 +516,7 @@ pub struct ReadMeta {
 
 #[derive(Serialize, Deserialize)]
 pub struct ReadMetaReply {
-    pub meta: BlockMeta,
+    pub attrs: BlockMetaSecrets,
     pub valid_for: Duration,
 }
 
@@ -460,7 +525,7 @@ pub struct WriteMeta {
     pub inode: Inode,
     pub handle: Option<Handle>,
     pub attrs: Attrs,
-    /// The bits in this value determine which fields in `attrs` have been initialized.
+    /// The bits in this value indicate which fields in `attrs` have been initialized.
     pub attrs_set: AttrsSet,
 }
 

+ 174 - 3
crates/btfsd/src/main.rs

@@ -72,8 +72,8 @@ async fn main() {
 mod tests {
     use super::*;
 
-    use btlib::log::BuilderExt;
     use btfproto::{client::FsClient, msg::*};
+    use btlib::{log::BuilderExt, BlockMetaSecrets, Epoch};
     use btlib_tests::TpmCredStoreHarness;
     use btmsg::Transmitter;
     use std::{future::ready, net::Ipv4Addr};
@@ -199,7 +199,10 @@ mod tests {
         client.flush(inode, handle).await.unwrap();
         let case = existing_case(case).await;
         let client = &case.client;
-        let LookupReply { inode, .. } = client.lookup(SpecInodes::RootDir.into(), FILENAME).await.unwrap();
+        let LookupReply { inode, .. } = client
+            .lookup(SpecInodes::RootDir.into(), FILENAME)
+            .await
+            .unwrap();
         let OpenReply { handle, .. } = client
             .open(inode, FlagValue::ReadOnly.into())
             .await
@@ -333,7 +336,10 @@ mod tests {
             handle: root_handle,
             ..
         } = client
-            .open(SpecInodes::RootDir.into(), FlagValue::ReadOnly | FlagValue::Directory)
+            .open(
+                SpecInodes::RootDir.into(),
+                FlagValue::ReadOnly | FlagValue::Directory,
+            )
             .await
             .unwrap();
         let ReadDirReply { entries, .. } = client
@@ -345,4 +351,169 @@ mod tests {
         assert!(filenames.contains(&FIRSTNAME));
         assert!(filenames.contains(&LASTNAME));
     }
+
+    #[tokio::test]
+    async fn unlink() {
+        const FIRSTNAME: &str = "Jean-Luc";
+        const LASTNAME: &str = "Picard";
+        let case = new_case().await;
+        let client = &case.client;
+
+        let CreateReply { inode, .. } = client
+            .create(
+                SpecInodes::RootDir.into(),
+                FIRSTNAME,
+                Flags::default(),
+                0o644,
+                0,
+            )
+            .await
+            .unwrap();
+        client
+            .link(inode, SpecInodes::RootDir.into(), LASTNAME)
+            .await
+            .unwrap();
+        client
+            .unlink(SpecInodes::RootDir.into(), FIRSTNAME)
+            .await
+            .unwrap();
+        let OpenReply {
+            handle: root_handle,
+            ..
+        } = client
+            .open(
+                SpecInodes::RootDir.into(),
+                FlagValue::ReadOnly | FlagValue::Directory,
+            )
+            .await
+            .unwrap();
+        let ReadDirReply { entries, .. } = client
+            .read_dir(SpecInodes::RootDir.into(), root_handle, 0, 0)
+            .await
+            .unwrap();
+
+        let filenames: Vec<_> = entries.iter().map(|e| e.0.as_str()).collect();
+        assert!(filenames.contains(&LASTNAME));
+        assert!(!filenames.contains(&FIRSTNAME));
+    }
+
+    #[tokio::test]
+    async fn delete() {
+        const FILENAME: &str = "MANIFESTO.tex";
+        let case = new_case().await;
+        let client = &case.client;
+
+        client
+            .create(
+                SpecInodes::RootDir.into(),
+                FILENAME,
+                Flags::default(),
+                0o644,
+                0,
+            )
+            .await
+            .unwrap();
+        client
+            .unlink(SpecInodes::RootDir.into(), FILENAME)
+            .await
+            .unwrap();
+        let OpenReply {
+            handle: root_handle,
+            ..
+        } = client
+            .open(
+                SpecInodes::RootDir.into(),
+                FlagValue::ReadOnly | FlagValue::Directory,
+            )
+            .await
+            .unwrap();
+        let ReadDirReply { entries, .. } = client
+            .read_dir(SpecInodes::RootDir.into(), root_handle, 0, 0)
+            .await
+            .unwrap();
+
+        let filenames: Vec<_> = entries.iter().map(|e| e.0.as_str()).collect();
+        assert!(!filenames.contains(&FILENAME));
+    }
+
+    #[tokio::test]
+    async fn read_meta() {
+        const FILENAME: &str = "kibosh.txt";
+        const EXPECTED: u32 = 0o600;
+        let case = new_case().await;
+        let client = &case.client;
+
+        let before_create = Epoch::now();
+        let CreateReply { inode, handle, .. } = client
+            .create(
+                SpecInodes::RootDir.into(),
+                FILENAME,
+                FlagValue::ReadWrite.into(),
+                EXPECTED,
+                0,
+            )
+            .await
+            .unwrap();
+        let before_read = Epoch::now();
+        let ReadMetaReply { attrs, .. } = client.read_meta(inode, Some(handle)).await.unwrap();
+        let after = Epoch::now();
+
+        let actual = attrs.mode;
+        assert_eq!(FileType::Reg | EXPECTED, actual);
+        assert!(before_create <= attrs.ctime && attrs.ctime <= before_read);
+        assert!(before_read <= attrs.mtime && attrs.mtime <= after);
+        assert!(before_read <= attrs.atime && attrs.atime <= after);
+        assert_eq!(attrs.block_id.inode, inode);
+        assert_eq!(attrs.size, 0);
+        assert!(attrs.tags.is_empty());
+    }
+
+    #[tokio::test]
+    async fn write_meta() {
+        fn check(expected: &Attrs, actual: &BlockMetaSecrets) {
+            assert_eq!(expected.mode, actual.mode);
+            assert_eq!(expected.uid, actual.uid);
+            assert_eq!(expected.gid, actual.gid);
+            assert_eq!(expected.atime, actual.atime);
+            assert_eq!(expected.mtime, actual.mtime);
+            assert_eq!(expected.ctime, actual.ctime);
+            assert_eq!(expected.tags.len(), actual.tags.len());
+            for (key, expected_value) in expected.tags.iter() {
+                let actual_value = actual.tags.get(key).unwrap();
+                assert_eq!(expected_value, actual_value);
+            }
+        }
+
+        const FILENAME: &str = "word_salad.docx";
+        let expected = Attrs {
+            mode: 0o600,
+            uid: 9290,
+            gid: 2190,
+            atime: 23193.into(),
+            mtime: 53432.into(),
+            ctime: 87239.into(),
+            tags: Vec::new(),
+        };
+        let case = new_case().await;
+        let client = &case.client;
+
+        let CreateReply { inode, handle, .. } = client
+            .create(
+                SpecInodes::RootDir.into(),
+                FILENAME,
+                FlagValue::ReadWrite.into(),
+                expected.mode | 0o011,
+                0,
+            )
+            .await
+            .unwrap();
+        let WriteMetaReply { attrs, .. } = client
+            .write_meta(inode, Some(handle), expected.clone(), AttrsSet::ALL)
+            .await
+            .unwrap();
+        check(&expected, &attrs);
+        let ReadMetaReply { attrs, .. } = client.read_meta(inode, Some(handle)).await.unwrap();
+
+        check(&expected, &attrs);
+    }
 }

+ 2 - 2
crates/btfuse/src/fuse_fs.rs

@@ -434,9 +434,9 @@ mod private {
                 let path = self.path_from_luid(ctx.uid);
                 let msg = ReadMeta { inode, handle };
                 let ReadMetaReply {
-                    meta, valid_for, ..
+                    attrs, valid_for, ..
                 } = self.provider.read_meta(path, msg).await?;
-                let attrs = self.convert_ruid(meta.body().secrets()?.clone())?;
+                let attrs = self.convert_ruid(attrs)?;
                 let stat = attrs.stat()?;
                 Ok((stat, valid_for))
             })

+ 7 - 0
crates/btlib/src/lib.rs

@@ -1456,6 +1456,13 @@ impl Sub<Duration> for Epoch {
     }
 }
 
+impl Sub<Epoch> for Epoch {
+    type Output = Duration;
+    fn sub(self, other: Epoch) -> Self::Output {
+        Duration::from_secs(self.0 - other.0)
+    }
+}
+
 /// The serial number of a block fragment.
 #[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Hashable, Clone)]
 pub struct FragmentSerial(u32);

+ 2 - 2
crates/btmsg/src/lib.rs

@@ -210,7 +210,7 @@ pub trait Receiver {
     type StopFut<'a>: 'a + Future<Output = Result<()>> + Send
     where
         Self: 'a;
-    fn stop<'a>(&'a self) -> Self::StopFut<'a>;
+    fn stop(&self) -> Self::StopFut<'_>;
 }
 
 /// A type which can be used to transmit messages.
@@ -560,7 +560,7 @@ impl Receiver for QuicReceiver {
     }
 
     type StopFut<'a> = Ready<Result<()>>;
-    fn stop<'a>(&'a self) -> Self::StopFut<'a> {
+    fn stop(&self) -> Self::StopFut<'_> {
         ready(self.stop_tx.send(()).map(|_| ()).map_err(|err| err.into()))
     }
 }