123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053 |
- // SPDX-License-Identifier: AGPL-3.0-or-later
- use btfproto::{
- local_fs::{LocalFs, ModeAuthorizer},
- msg::{GrantAccess, SpecInodes},
- server::FsProvider,
- };
- use btlib::{
- crypto::{Creds, CredsPriv, CredsPub},
- AuthzAttrs, BlockError, BlockPath, IssuedProcRec,
- };
- use std::{
- net::{IpAddr, Ipv6Addr},
- path::PathBuf,
- sync::Arc,
- };
- use tempdir::TempDir;
- pub fn bind_path<C: CredsPriv>(creds: C) -> BlockPath {
- let writecap = creds
- .writecap()
- .ok_or(btlib::BlockError::MissingWritecap)
- .unwrap();
- writecap.bind_path()
- }
- pub type ConcreteFs = LocalFs<ModeAuthorizer>;
- pub struct LocalFsTest {
- dir: TempDir,
- fs: ConcreteFs,
- node_bind_path: Arc<BlockPath>,
- }
- fn node_creds() -> Arc<dyn Creds> {
- Arc::new(super::node_creds())
- }
- fn root_creds() -> Arc<dyn Creds> {
- Arc::new(super::root_creds())
- }
- impl LocalFsTest {
- pub const NODE_UID: u32 = 1000;
- pub const NODE_GID: u32 = 1000;
- pub async fn new_empty() -> LocalFsTest {
- let dir = TempDir::new("fuse").expect("failed to create temp dir");
- let node_creds = node_creds();
- Self::grant_node_access(dir.path().to_owned()).await;
- let node_writecap = node_creds
- .writecap()
- .ok_or(BlockError::MissingWritecap)
- .unwrap();
- let node_bind_path = Arc::new(node_writecap.bind_path());
- let fs = LocalFs::new_existing(dir.path().to_owned(), node_creds, ModeAuthorizer)
- .expect("failed to create empty blocktree");
- LocalFsTest {
- dir,
- fs,
- node_bind_path,
- }
- }
- pub fn new_existing(dir: TempDir) -> LocalFsTest {
- let fs = LocalFs::new_existing(dir.path().to_owned(), node_creds(), ModeAuthorizer)
- .expect("failed to create blocktree from existing directory");
- let from = Arc::new(bind_path(node_creds()));
- LocalFsTest {
- dir,
- fs,
- node_bind_path: from,
- }
- }
- async fn grant_node_access(path: PathBuf) {
- let root_creds = root_creds();
- let root_writecap = root_creds
- .writecap()
- .ok_or(BlockError::MissingWritecap)
- .unwrap();
- let root_bind_path = Arc::new(root_writecap.bind_path());
- let node_creds = node_creds();
- let node_writecap = node_creds
- .writecap()
- .ok_or(BlockError::MissingWritecap)
- .unwrap();
- let fs = LocalFs::new_empty(path, 0, root_creds, ModeAuthorizer)
- .await
- .unwrap();
- let proc_rec = IssuedProcRec {
- addr: IpAddr::V6(Ipv6Addr::LOCALHOST),
- pub_creds: node_creds.concrete_pub(),
- writecap: node_writecap.to_owned(),
- authz_attrs: AuthzAttrs {
- uid: Self::NODE_UID,
- gid: Self::NODE_GID,
- supp_gids: Vec::new(),
- },
- };
- let msg = GrantAccess {
- inode: SpecInodes::RootDir.into(),
- record: proc_rec,
- };
- fs.grant_access(&root_bind_path, msg).await.unwrap();
- }
- pub fn into_parts(self) -> (TempDir, ConcreteFs, Arc<BlockPath>) {
- (self.dir, self.fs, self.node_bind_path)
- }
- pub fn fs(&self) -> &ConcreteFs {
- &self.fs
- }
- pub fn from(&self) -> &Arc<BlockPath> {
- &self.node_bind_path
- }
- }
- #[cfg(test)]
- mod tests {
- use super::*;
- use btfproto::{local_fs::Error, msg::*};
- use btlib::{Inode, Result, SECTOR_SZ_DEFAULT};
- use btlib_tests::fs_queries::num_files;
- use std::{
- fs::read_dir,
- io::{self, Cursor, Write as IoWrite},
- ops::Deref,
- sync::Arc,
- };
- /// Tests that a new file can be created, written to and the written data can be read from it.
- #[tokio::test]
- async fn create_write_read() {
- let case = LocalFsTest::new_empty().await;
- let bt = &case.fs;
- let from = case.from();
- let name = "README.md";
- let flags = Flags::new(libc::O_RDWR);
- let create_msg = Create {
- parent: SpecInodes::RootDir.into(),
- name,
- flags,
- mode: libc::S_IFREG | 0o644,
- umask: 0,
- };
- let CreateReply { inode, handle, .. } = bt.create(from, create_msg).await.unwrap();
- const LEN: usize = 32;
- let expected = [1u8; LEN];
- let write_msg = Write {
- inode,
- handle,
- offset: 0,
- data: expected.as_slice(),
- };
- let WriteReply { written, .. } = bt.write(from, write_msg).await.unwrap();
- assert_eq!(LEN as u64, written);
- let read_msg = Read {
- inode,
- handle,
- offset: 0,
- size: LEN as u64,
- };
- let guard = bt.read(from, read_msg).await.unwrap();
- assert_eq!(expected, guard.deref())
- }
- #[tokio::test]
- async fn lookup() {
- let case = LocalFsTest::new_empty().await;
- let bt = &case.fs;
- let from = case.from();
- let name = "README.md";
- let create_msg = Create {
- parent: SpecInodes::RootDir.into(),
- name,
- flags: Flags::default(),
- mode: 0,
- umask: 0,
- };
- let create_reply = bt.create(from, create_msg).await.unwrap();
- let lookup_msg = Lookup {
- parent: SpecInodes::RootDir.into(),
- name,
- };
- let lookup_reply = bt.lookup(from, lookup_msg).await.unwrap();
- assert_eq!(create_reply.inode, lookup_reply.inode);
- }
- /// Tests that data written by one instance of [Blocktree] can be read by a subsequent
- /// instance.
- #[tokio::test]
- async fn new_existing() {
- const EXPECTED: &[u8] = b"cool as cucumbers";
- let name = "RESIGNATION.docx";
- let case = LocalFsTest::new_empty().await;
- let bt = &case.fs;
- let from = case.from();
- let flags = Flags::new(libc::O_RDWR);
- {
- let create_msg = Create {
- parent: SpecInodes::RootDir.into(),
- name,
- mode: libc::S_IFREG | 0o644,
- flags,
- umask: 0,
- };
- let CreateReply { handle, inode, .. } = bt.create(from, create_msg).await.unwrap();
- let write_msg = Write {
- inode,
- handle,
- offset: 0,
- data: EXPECTED,
- };
- let WriteReply { written, .. } = bt.write(from, write_msg).await.unwrap();
- assert_eq!(EXPECTED.len() as u64, written);
- let close_msg = Close { inode, handle };
- bt.close(from, close_msg).await.unwrap();
- }
- let case = LocalFsTest::new_existing(case.dir);
- let from = case.from();
- let bt = &case.fs;
- let lookup_msg = Lookup {
- parent: SpecInodes::RootDir.into(),
- name,
- };
- let LookupReply { inode, .. } = bt.lookup(from, lookup_msg).await.unwrap();
- let open_msg = Open {
- inode,
- flags: Flags::new(libc::O_RDONLY),
- };
- let OpenReply { handle, .. } = bt.open(from, open_msg).await.unwrap();
- let read_msg = Read {
- inode,
- handle,
- offset: 0,
- size: EXPECTED.len() as u64,
- };
- let guard = bt.read(from, read_msg).await.unwrap();
- assert_eq!(EXPECTED, guard.deref())
- }
- /// Tests that an error is returned by the `Blocktree::write` method if the file was opened
- /// read-only.
- #[tokio::test]
- async fn open_read_only_write_is_error() {
- let name = "books.ods";
- let case = LocalFsTest::new_empty().await;
- let bt = &case.fs;
- let from = case.from();
- let create_msg = Create {
- parent: SpecInodes::RootDir.into(),
- name,
- flags: Flags::default(),
- mode: libc::S_IFREG | 0o644,
- umask: 0,
- };
- let CreateReply { inode, handle, .. } = bt.create(from, create_msg).await.unwrap();
- let close_msg = Close { inode, handle };
- bt.close(from, close_msg).await.unwrap();
- let open_msg = Open {
- inode,
- flags: libc::O_RDONLY.into(),
- };
- let OpenReply { handle, .. } = bt.open(from, open_msg).await.unwrap();
- let data = [1u8; 32];
- let write_msg = Write {
- inode,
- handle,
- offset: 0,
- data: data.as_slice(),
- };
- let result = bt.write(from, write_msg).await;
- let err = result.err().unwrap();
- let err = err.downcast::<Error>().unwrap();
- let actual_handle = if let Error::ReadOnlyHandle(actual_handle) = err {
- Some(actual_handle)
- } else {
- None
- };
- assert_eq!(Some(handle), actual_handle);
- }
- /// Asserts that the given [Result] is an [Err] and that it contains an [io::Error] which
- /// corresponds to the [libc::ENOENT] error code.
- fn assert_enoent<T>(result: Result<T>) {
- let err = result.err().unwrap().downcast::<io::Error>().unwrap();
- assert_eq!(libc::ENOENT, err.raw_os_error().unwrap());
- }
- /// Tests that multiple handles see consistent metadata associated with a block.
- #[tokio::test]
- async fn ensure_metadata_consistency() {
- let case = LocalFsTest::new_empty().await;
- let from = case.from();
- let trash = ".Trash";
- let file = "file.txt";
- let bt = &case.fs;
- let root = SpecInodes::RootDir.into();
- let open_msg = Open {
- inode: root,
- flags: libc::O_DIRECTORY.into(),
- };
- let OpenReply { handle, .. } = bt.open(from, open_msg).await.unwrap();
- // Because the directory is open, this will cause a new handle for this block to be opened.
- let lookup_msg = Lookup {
- parent: root,
- name: trash,
- };
- let result = bt.lookup(from, lookup_msg).await;
- assert_enoent(result);
- let close_msg = Close {
- inode: root,
- handle,
- };
- bt.close(from, close_msg).await.unwrap();
- let create_msg = Create {
- parent: root,
- name: file,
- flags: Flags::default(),
- mode: libc::S_IFREG | 0o644,
- umask: 0,
- };
- bt.create(from, create_msg).await.unwrap();
- let open_msg = Open {
- inode: root,
- flags: libc::O_DIRECTORY.into(),
- };
- bt.open(from, open_msg).await.unwrap();
- // Since the directory is open, the second handle will be used for this lookup.
- let lookup_msg = Lookup {
- parent: root,
- name: trash,
- };
- let result = bt.lookup(from, lookup_msg).await;
- assert!(result.is_err());
- }
- /// Tests that the `size` parameter actually limits the number of read bytes.
- #[tokio::test]
- async fn read_with_smaller_size() {
- const DATA: [u8; 8] = [0, 1, 2, 3, 4, 5, 6, 7];
- let case = LocalFsTest::new_empty().await;
- let from = case.from();
- let file = "file.txt";
- let bt = &case.fs;
- let root: Inode = SpecInodes::RootDir.into();
- let create_msg = Create {
- parent: root,
- name: file,
- flags: libc::O_RDWR.into(),
- mode: libc::S_IFREG | 0o644,
- umask: 0,
- };
- let CreateReply { inode, handle, .. } = bt.create(from, create_msg).await.unwrap();
- let write_msg = Write {
- inode,
- handle,
- offset: 0,
- data: DATA.as_slice(),
- };
- let WriteReply { written, .. } = bt.write(from, write_msg).await.unwrap();
- assert_eq!(DATA.len() as u64, written);
- const SIZE: usize = DATA.len() / 2;
- let read_msg = Read {
- inode,
- handle,
- offset: 0,
- size: SIZE as u64,
- };
- let guard = bt.read(from, read_msg).await.unwrap();
- assert_eq!(&[0, 1, 2, 3], guard.deref());
- }
- /// Returns an integer array starting at the given value and increasing by one for each
- /// subsequent entry.
- pub const fn integer_array<const N: usize>(start: u8) -> [u8; N] {
- let mut array = [0u8; N];
- let mut k = 0usize;
- while k < N {
- array[k] = start.wrapping_add(k as u8);
- k += 1;
- }
- array
- }
- #[tokio::test]
- async fn concurrent_reads() {
- // The size of each of the reads.
- const SIZE: usize = 4;
- // The number of concurrent reads.
- const NREADS: usize = 32;
- const DATA_LEN: usize = SIZE * NREADS;
- const DATA: [u8; DATA_LEN] = integer_array::<DATA_LEN>(0);
- let case = LocalFsTest::new_empty().await;
- let from = case.from();
- let file = "file.txt";
- let bt = &case.fs;
- let root: Inode = SpecInodes::RootDir.into();
- let mode = libc::S_IFREG | 0o644;
- let create_msg = Create {
- parent: root,
- name: file,
- flags: libc::O_RDWR.into(),
- mode,
- umask: 0,
- };
- let CreateReply { inode, handle, .. } = bt.create(from, create_msg).await.unwrap();
- let write_msg = Write {
- inode,
- handle,
- offset: 0,
- data: DATA.as_slice(),
- };
- let WriteReply { written, .. } = bt.write(from, write_msg).await.unwrap();
- assert_eq!(DATA.len() as u64, written);
- let case = Arc::new(case);
- let mut handles = Vec::with_capacity(NREADS);
- for offset in (0..NREADS).map(|e| e * SIZE) {
- let case = case.clone();
- handles.push(tokio::spawn(async move {
- // Notice that we have concurrent reads to different offsets using the same handle.
- // Without proper synchronization, this shouldn't work.
- let read_msg = Read {
- inode,
- handle,
- offset: offset as u64,
- size: SIZE as u64,
- };
- let guard = case.fs.read(case.from(), read_msg).await.unwrap();
- let expected = integer_array::<SIZE>(offset as u8);
- assert_eq!(&expected, guard.deref());
- }));
- }
- for handle in handles {
- handle.await.unwrap();
- }
- }
- #[tokio::test]
- async fn rename_in_same_directory() {
- let case = LocalFsTest::new_empty().await;
- let bt = &case.fs;
- let from = case.from();
- let root: Inode = SpecInodes::RootDir.into();
- let src_name = "src";
- let dst_name = "dst";
- let create_msg = Create {
- parent: root,
- name: src_name,
- flags: Flags::default(),
- mode: libc::S_IFREG | 0o644,
- umask: 0,
- };
- let CreateReply { inode, .. } = bt.create(from, create_msg).await.unwrap();
- let link_msg = Link {
- inode,
- new_parent: root,
- name: dst_name,
- };
- bt.link(from, link_msg).await.unwrap();
- let unlink_msg = Unlink {
- parent: root,
- name: src_name,
- };
- bt.unlink(from, unlink_msg).await.unwrap();
- let lookup_msg = Lookup {
- parent: root,
- name: dst_name,
- };
- let LookupReply {
- inode: actual_inode,
- ..
- } = bt.lookup(from, lookup_msg).await.unwrap();
- assert_eq!(inode, actual_inode);
- let lookup_msg = Lookup {
- parent: root,
- name: src_name,
- };
- let result = bt.lookup(from, lookup_msg).await;
- assert_enoent(result);
- }
- #[tokio::test]
- async fn rename_to_different_directory() {
- let case = LocalFsTest::new_empty().await;
- let bt = &case.fs;
- let from = case.from();
- let root = SpecInodes::RootDir.into();
- let dir_name = "dir";
- let file_name = "file";
- let create_msg = Create {
- parent: root,
- name: dir_name,
- flags: libc::O_DIRECTORY.into(),
- mode: libc::S_IFDIR | 0o755,
- umask: 0,
- };
- let CreateReply { inode: dir, .. } = bt.create(from, create_msg).await.unwrap();
- let create_msg = Create {
- parent: root,
- name: file_name,
- flags: Flags::default(),
- mode: libc::S_IFREG | 0o644,
- umask: 0,
- };
- let CreateReply { inode: file, .. } = bt.create(from, create_msg).await.unwrap();
- let link_msg = Link {
- inode: file,
- new_parent: dir,
- name: file_name,
- };
- bt.link(from, link_msg).await.unwrap();
- let unlink_msg = Unlink {
- parent: root,
- name: file_name,
- };
- bt.unlink(from, unlink_msg).await.unwrap();
- let lookup_msg = Lookup {
- parent: dir,
- name: file_name,
- };
- let LookupReply {
- inode: actual_inode,
- ..
- } = bt.lookup(from, lookup_msg).await.unwrap();
- assert_eq!(file, actual_inode);
- let lookup_msg = Lookup {
- parent: root,
- name: file_name,
- };
- let result = bt.lookup(from, lookup_msg).await;
- assert_enoent(result);
- }
- #[tokio::test]
- async fn rename_no_replace_same_directory() {
- let case = LocalFsTest::new_empty().await;
- let bt = &case.fs;
- let from = case.from();
- let root: Inode = SpecInodes::RootDir.into();
- let oldname = "old";
- let newname = "new";
- let create_msg = Create {
- parent: root,
- name: oldname,
- flags: Flags::default(),
- mode: 0o644,
- umask: 0,
- };
- let CreateReply { inode, .. } = bt.create(from, create_msg).await.unwrap();
- let create_msg = Create {
- parent: root,
- name: newname,
- flags: Flags::default(),
- mode: 0o644,
- umask: 0,
- };
- bt.create(from, create_msg).await.unwrap();
- let link_msg = Link {
- inode,
- new_parent: root,
- name: newname,
- };
- let result = bt.link(from, link_msg).await;
- let err = result.err().unwrap().downcast::<io::Error>().unwrap();
- assert_eq!(io::ErrorKind::AlreadyExists, err.kind());
- }
- #[tokio::test]
- async fn rename_no_replace_different_directory() {
- let case = LocalFsTest::new_empty().await;
- let bt = &case.fs;
- let from = case.from();
- let root = SpecInodes::RootDir.into();
- let dir_name = "dir";
- let file_name = "file";
- let create_msg = Create {
- parent: root,
- name: dir_name,
- flags: libc::O_DIRECTORY.into(),
- mode: 0o755,
- umask: 0,
- };
- let CreateReply { inode: dir, .. } = bt.create(from, create_msg).await.unwrap();
- let create_msg = Create {
- parent: root,
- name: file_name,
- flags: Flags::default(),
- mode: 0o644,
- umask: 0,
- };
- let CreateReply { inode, .. } = bt.create(from, create_msg).await.unwrap();
- let create_msg = Create {
- parent: dir,
- name: file_name,
- flags: Flags::default(),
- mode: 0o644,
- umask: 0,
- };
- bt.create(from, create_msg).await.unwrap();
- let link_msg = Link {
- inode,
- new_parent: dir,
- name: file_name,
- };
- let result = bt.link(from, link_msg).await;
- let err = result.err().unwrap().downcast::<io::Error>().unwrap();
- assert_eq!(io::ErrorKind::AlreadyExists, err.kind());
- }
- #[tokio::test]
- async fn read_from_non_owner_is_err() {
- let case = LocalFsTest::new_empty().await;
- let bt = &case.fs;
- let name = "file.txt";
- let owner = case.from();
- let mut other = owner.as_ref().clone();
- other.push_component("subdir".to_owned());
- let other = Arc::new(other);
- let create_msg = Create {
- parent: SpecInodes::RootDir.into(),
- name,
- flags: libc::O_RDWR.into(),
- mode: 0o644,
- umask: 0,
- };
- let CreateReply { inode, handle, .. } = bt.create(owner, create_msg).await.unwrap();
- let write_msg = Write {
- inode,
- handle,
- offset: 0,
- data: [1, 2, 3].as_slice(),
- };
- let result = bt.write(&other, write_msg).await;
- let err = result.err().unwrap().downcast::<Error>().unwrap();
- let matched = if let Error::WrongOwner = err {
- true
- } else {
- false
- };
- assert!(matched)
- }
- #[tokio::test]
- async fn allocate_full_sectors_zero_remain_non_zero() {
- let case = LocalFsTest::new_empty().await;
- let bt = &case.fs;
- let name = "file.txt";
- let from = case.from();
- let create_msg = Create {
- parent: SpecInodes::RootDir.into(),
- name,
- flags: libc::O_RDWR.into(),
- mode: 0o644,
- umask: 0,
- };
- let CreateReply { inode, handle, .. } = bt.create(from, create_msg).await.unwrap();
- const LEN: u64 = 8;
- let alloc_msg = Allocate {
- inode,
- handle,
- offset: None,
- size: LEN,
- };
- bt.allocate(from, alloc_msg).await.unwrap();
- let read_meta_msg = ReadMeta {
- inode,
- handle: Some(handle),
- };
- let ReadMetaReply { attrs, .. } = bt.read_meta(from, read_meta_msg).await.unwrap();
- let read_msg = Read {
- inode,
- handle,
- offset: 0,
- size: LEN,
- };
- let guard = bt.read(from, read_msg).await.unwrap();
- assert_eq!([0u8; 8], guard.deref());
- assert_eq!(guard.len() as u64, attrs.size);
- }
- #[tokio::test]
- async fn allocate_full_sectors_non_zero_remain_non_zero() {
- let case = LocalFsTest::new_empty().await;
- let bt = &case.fs;
- let name = "file.txt";
- let from = case.from();
- let create_msg = Create {
- parent: SpecInodes::RootDir.into(),
- name,
- flags: libc::O_RDWR.into(),
- mode: 0o644,
- umask: 0,
- };
- let CreateReply { inode, handle, .. } = bt.create(from, create_msg).await.unwrap();
- const LEN: usize = SECTOR_SZ_DEFAULT + 1;
- let mut size = LEN as u64;
- let alloc_msg = Allocate {
- inode,
- handle,
- offset: None,
- size,
- };
- bt.allocate(from, alloc_msg).await.unwrap();
- let mut actual = Cursor::new(Vec::with_capacity(LEN));
- while size > 0 {
- let read_msg = Read {
- inode,
- handle,
- offset: 0,
- size,
- };
- let guard = bt.read(from, read_msg).await.unwrap();
- let data = guard.deref();
- actual.write(data).unwrap();
- size -= data.len() as u64;
- }
- let read_meta_msg = ReadMeta {
- inode,
- handle: Some(handle),
- };
- let ReadMetaReply { attrs, .. } = bt.read_meta(from, read_meta_msg).await.unwrap();
- assert!(vec![0u8; LEN].eq(&actual.into_inner()));
- assert_eq!(LEN as u64, attrs.size);
- }
- #[tokio::test]
- async fn allocate_full_sectors_non_zero_remain_zero() {
- let case = LocalFsTest::new_empty().await;
- let bt = &case.fs;
- let name = "file.txt";
- let from = case.from();
- let create_msg = Create {
- parent: SpecInodes::RootDir.into(),
- name,
- flags: libc::O_RDWR.into(),
- mode: 0o644,
- umask: 0,
- };
- let CreateReply { inode, handle, .. } = bt.create(from, create_msg).await.unwrap();
- const LEN: usize = SECTOR_SZ_DEFAULT;
- let size = LEN as u64;
- let alloc_msg = Allocate {
- inode,
- handle,
- offset: None,
- size,
- };
- bt.allocate(from, alloc_msg).await.unwrap();
- let read_meta_msg = ReadMeta {
- inode,
- handle: Some(handle),
- };
- let ReadMetaReply { attrs, .. } = bt.read_meta(from, read_meta_msg).await.unwrap();
- let read_msg = Read {
- inode,
- handle,
- offset: 0,
- size,
- };
- let guard = bt.read(from, read_msg).await.unwrap();
- assert_eq!(vec![0u8; LEN], guard.deref());
- 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,
- /// then no change to the block occurs.
- #[tokio::test]
- async fn allocate_new_size_not_greater_than_curr_size() {
- let case = LocalFsTest::new_empty().await;
- let bt = &case.fs;
- let name = "file.txt";
- let from = case.from();
- let create_msg = Create {
- parent: SpecInodes::RootDir.into(),
- name,
- flags: libc::O_RDWR.into(),
- mode: 0o644,
- umask: 0,
- };
- let CreateReply { inode, handle, .. } = bt.create(from, create_msg).await.unwrap();
- const LEN: usize = 8;
- let write_msg = Write {
- inode,
- handle,
- offset: 0,
- data: [1u8; LEN].as_slice(),
- };
- let WriteReply { written, .. } = bt.write(from, write_msg).await.unwrap();
- assert_eq!(LEN as u64, written);
- let alloc_msg = Allocate {
- inode,
- handle,
- offset: None,
- size: (LEN / 2) as u64,
- };
- bt.allocate(from, alloc_msg).await.unwrap();
- let read_meta_msg = ReadMeta {
- inode,
- handle: Some(handle),
- };
- let ReadMetaReply { attrs, .. } = bt.read_meta(from, read_meta_msg).await.unwrap();
- let read_msg = Read {
- inode,
- handle,
- offset: 0,
- size: LEN as u64,
- };
- let actual = bt.read(from, read_msg).await.unwrap();
- assert_eq!([1u8; LEN], actual.deref());
- assert_eq!(LEN as u64, attrs.size);
- }
- #[tokio::test]
- async fn read_at_non_current_position() {
- const FILENAME: &str = "MANIFESTO.rtf";
- let case = LocalFsTest::new_empty().await;
- let bt = &case.fs;
- let from = case.from();
- let msg = Create {
- parent: SpecInodes::RootDir.into(),
- name: FILENAME,
- flags: FlagValue::ReadWrite.into(),
- mode: 0o644,
- umask: 0,
- };
- let CreateReply {
- inode,
- handle,
- entry,
- ..
- } = bt.create(from, msg).await.unwrap();
- let sect_sz64 = entry.attr.sect_sz;
- let sect_sz: usize = sect_sz64.try_into().unwrap();
- let mut data = vec![1u8; sect_sz];
- let msg = Write {
- inode,
- handle,
- offset: 0,
- data: data.as_slice(),
- };
- let WriteReply { written, .. } = bt.write(from, msg).await.unwrap();
- assert_eq!(sect_sz64, written);
- data.truncate(0);
- data.extend(std::iter::repeat(2).take(sect_sz));
- let msg = Write {
- inode,
- handle,
- offset: sect_sz64,
- data: data.as_slice(),
- };
- let WriteReply { written, .. } = bt.write(from, msg).await.unwrap();
- assert_eq!(sect_sz64, written);
- // The Accessor for this block should now have the second sector loaded, so it will have to
- // seek back to the first in order to respond to this read request.
- let msg = Read {
- inode,
- handle,
- offset: 0,
- size: sect_sz64,
- };
- let guard = bt.read(from, msg).await.unwrap();
- assert!(guard
- .deref()
- .iter()
- .map(|e| *e)
- .eq(std::iter::repeat(1u8).take(sect_sz)));
- }
- #[tokio::test]
- async fn unlink_after_forget_file_is_deleted() {
- const FILENAME: &str = "file";
- let case = LocalFsTest::new_empty().await;
- let block_dir = case.dir.path();
- let bt = &case.fs;
- let from = case.from();
- let expected = num_files(block_dir).unwrap();
- let msg = Create {
- parent: SpecInodes::RootDir.into(),
- name: FILENAME,
- flags: FlagValue::ReadWrite.into(),
- mode: 0o644,
- umask: 0,
- };
- let CreateReply { inode, handle, .. } = bt.create(from, msg).await.unwrap();
- const DATA: [u8; 8] = [1u8; 8];
- let msg = Write {
- inode,
- handle,
- offset: 0,
- data: DATA.as_slice(),
- };
- bt.write(from, msg).await.unwrap();
- let msg = Close { inode, handle };
- bt.close(from, msg).await.unwrap();
- let more = num_files(block_dir).unwrap();
- assert!(more > expected);
- let msg = Forget { inode, count: 1 };
- bt.forget(from, msg).await.unwrap();
- let msg = Unlink {
- parent: SpecInodes::RootDir.into(),
- name: FILENAME,
- };
- bt.unlink(from, msg).await.unwrap();
- let actual = num_files(block_dir).unwrap();
- assert_eq!(expected, actual);
- }
- #[tokio::test]
- async fn forget_after_unlink_file_is_deleted() {
- const FILENAME: &str = "file";
- let case = LocalFsTest::new_empty().await;
- let block_dir = case.dir.path();
- let bt = &case.fs;
- let from = case.from();
- let expected = num_files(block_dir).unwrap();
- let msg = Create {
- parent: SpecInodes::RootDir.into(),
- name: FILENAME,
- flags: FlagValue::ReadWrite.into(),
- mode: 0o644,
- umask: 0,
- };
- let CreateReply { inode, handle, .. } = bt.create(from, msg).await.unwrap();
- const DATA: [u8; 8] = [1u8; 8];
- let msg = Write {
- inode,
- handle,
- offset: 0,
- data: DATA.as_slice(),
- };
- bt.write(from, msg).await.unwrap();
- let msg = Close { inode, handle };
- bt.close(from, msg).await.unwrap();
- let more = num_files(block_dir).unwrap();
- let msg = Unlink {
- parent: SpecInodes::RootDir.into(),
- name: FILENAME,
- };
- bt.unlink(from, msg).await.unwrap();
- assert!(more > expected);
- let msg = Forget { inode, count: 1 };
- bt.forget(from, msg).await.unwrap();
- let actual = num_files(block_dir).unwrap();
- assert_eq!(expected, actual);
- }
- #[tokio::test]
- async fn after_unlink_no_empty_directories() {
- const FILENAME: &str = "file";
- let case = LocalFsTest::new_empty().await;
- let bt = &case.fs;
- let from = case.from();
- let msg = Create {
- parent: SpecInodes::RootDir.into(),
- name: FILENAME,
- flags: FlagValue::ReadWrite.into(),
- mode: 0o644,
- umask: 0,
- };
- let CreateReply { inode, handle, .. } = bt.create(from, msg).await.unwrap();
- const DATA: [u8; 8] = [1u8; 8];
- let msg = Write {
- inode,
- handle,
- offset: 0,
- data: DATA.as_slice(),
- };
- bt.write(from, msg).await.unwrap();
- let msg = Close { inode, handle };
- bt.close(from, msg).await.unwrap();
- let msg = Forget { inode, count: 1 };
- bt.forget(from, msg).await.unwrap();
- let msg = Unlink {
- parent: SpecInodes::RootDir.into(),
- name: FILENAME,
- };
- bt.unlink(from, msg).await.unwrap();
- let mut path = case.dir.path().to_owned();
- let entries = read_dir(&path).unwrap();
- path.push("x");
- for entry in entries {
- let entry = entry.unwrap();
- if !entry.file_type().unwrap().is_dir() {
- continue;
- }
- path.pop();
- path.push(entry.file_name());
- let empty = read_dir(&path).unwrap().next().is_none();
- assert!(!empty);
- }
- }
- }
|