|
@@ -1,5 +1,15 @@
|
|
|
use crate::{msg::*, server::FsProvider};
|
|
|
|
|
|
+use btlib::{
|
|
|
+ accessor::Accessor,
|
|
|
+ bterr,
|
|
|
+ crypto::{Creds, Decrypter, Signer},
|
|
|
+ error::{BtErr, DisplayErr},
|
|
|
+ BlockAccessor, BlockError, BlockMeta, BlockMetaSecrets, BlockOpenOptions, BlockPath,
|
|
|
+ BlockReader, BlockRecord, DirEntry, DirEntryKind, Directory, Epoch, FileBlock, FlushMeta,
|
|
|
+ MetaAccess, MetaReader, Positioned, Principaled, Result, Split, TrySeek, WriteDual,
|
|
|
+ ZeroExtendable,
|
|
|
+};
|
|
|
use btserde::{read_from, write_to};
|
|
|
use core::future::{ready, Ready};
|
|
|
use log::{debug, error, warn};
|
|
@@ -9,7 +19,7 @@ use std::{
|
|
|
collections::hash_map::{self, HashMap},
|
|
|
fmt::{Display, Formatter},
|
|
|
fs::File,
|
|
|
- io::{self, Seek, SeekFrom, Write as IoWrite},
|
|
|
+ io::{self, Read as IoRead, Seek, SeekFrom, Write as IoWrite},
|
|
|
path::{Path, PathBuf},
|
|
|
sync::{
|
|
|
atomic::{AtomicU64, Ordering},
|
|
@@ -18,17 +28,7 @@ use std::{
|
|
|
time::Duration,
|
|
|
};
|
|
|
|
|
|
-use btlib::{
|
|
|
- accessor::Accessor,
|
|
|
- bterr,
|
|
|
- crypto::{Creds, Decrypter, Signer},
|
|
|
- error::{BtErr, DisplayErr},
|
|
|
- BlockAccessor, BlockError, BlockMeta, BlockMetaSecrets, BlockOpenOptions, BlockPath,
|
|
|
- BlockReader, BlockRecord, DirEntry, DirEntryKind, Directory, Epoch, FileBlock, FlushMeta,
|
|
|
- MetaAccess, MetaReader, Positioned, Principaled, Result, Split, TrySeek,
|
|
|
-};
|
|
|
-
|
|
|
-pub use private::{LocalFsProvider, ModeAuthorizer, SpecInodes};
|
|
|
+pub use private::{Authorizer, AuthzContext, Error, LocalFs, ModeAuthorizer};
|
|
|
|
|
|
mod private {
|
|
|
use super::*;
|
|
@@ -81,19 +81,6 @@ mod private {
|
|
|
|
|
|
impl std::error::Error for Error {}
|
|
|
|
|
|
- #[repr(u64)]
|
|
|
- pub enum SpecInodes {
|
|
|
- RootDir = 1,
|
|
|
- Sb = 2,
|
|
|
- FirstFree = 11,
|
|
|
- }
|
|
|
-
|
|
|
- impl From<SpecInodes> for Inode {
|
|
|
- fn from(special: SpecInodes) -> Self {
|
|
|
- special as 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).
|
|
@@ -533,7 +520,7 @@ mod private {
|
|
|
}
|
|
|
|
|
|
/// Structure for managing the part of a blocktree which is stored in the local filesystem.
|
|
|
- pub struct LocalFsProvider<C, A> {
|
|
|
+ pub struct LocalFs<C, A> {
|
|
|
/// The path to the directory in the local filesystem where this blocktree is located.
|
|
|
path: PathBuf,
|
|
|
/// A map from inode numbers to their reference counts.
|
|
@@ -548,20 +535,20 @@ mod private {
|
|
|
authorizer: A,
|
|
|
}
|
|
|
|
|
|
- impl<C, A> LocalFsProvider<C, A> {
|
|
|
+ impl<C, A> LocalFs<C, A> {
|
|
|
/// The maximum number of directory entries that can be returned in any given call to
|
|
|
/// `read_dir`.
|
|
|
const DIR_ENTRY_LIMIT: usize = 1024;
|
|
|
}
|
|
|
|
|
|
- impl<C: Creds + 'static, A> LocalFsProvider<C, A> {
|
|
|
+ impl<C: Creds + 'static, A> LocalFs<C, A> {
|
|
|
/// Creates a new empty blocktree at the given path.
|
|
|
pub fn new_empty(
|
|
|
btdir: PathBuf,
|
|
|
generation: u64,
|
|
|
creds: C,
|
|
|
authorizer: A,
|
|
|
- ) -> Result<LocalFsProvider<C, A>> {
|
|
|
+ ) -> Result<LocalFs<C, A>> {
|
|
|
let root_block_path = creds
|
|
|
.writecap()
|
|
|
.ok_or(BlockError::MissingWritecap)?
|
|
@@ -613,11 +600,7 @@ mod private {
|
|
|
}
|
|
|
|
|
|
/// Opens an existing blocktree stored at the given path.
|
|
|
- pub fn new_existing(
|
|
|
- btdir: PathBuf,
|
|
|
- creds: C,
|
|
|
- authorizer: A,
|
|
|
- ) -> Result<LocalFsProvider<C, A>> {
|
|
|
+ pub fn new_existing(btdir: PathBuf, creds: C, authorizer: A) -> Result<LocalFs<C, A>> {
|
|
|
let root_block_path = creds
|
|
|
.writecap()
|
|
|
.ok_or(BlockError::MissingWritecap)?
|
|
@@ -645,7 +628,7 @@ mod private {
|
|
|
root_block: Accessor<FileBlock<C>>,
|
|
|
creds: C,
|
|
|
authorizer: A,
|
|
|
- ) -> Result<LocalFsProvider<C, A>> {
|
|
|
+ ) -> Result<LocalFs<C, A>> {
|
|
|
let mut inodes = HashMap::with_capacity(1);
|
|
|
let empty_path = Arc::new(BlockPath::default());
|
|
|
inodes.insert(
|
|
@@ -656,7 +639,7 @@ mod private {
|
|
|
SpecInodes::RootDir.into(),
|
|
|
RwLock::new(InodeTableValue::new(root_block, empty_path)),
|
|
|
);
|
|
|
- Ok(LocalFsProvider {
|
|
|
+ Ok(LocalFs {
|
|
|
path: btdir,
|
|
|
inodes: RwLock::new(inodes),
|
|
|
next_inode: AtomicU64::new(sb.next_inode),
|
|
@@ -923,16 +906,16 @@ mod private {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- unsafe impl<C: Sync, A: Sync> Sync for LocalFsProvider<C, A> {}
|
|
|
+ unsafe impl<C: Sync, A: Sync> Sync for LocalFs<C, A> {}
|
|
|
|
|
|
- impl<C: 'static + Creds + Clone + Sync, A: 'static + Authorizer + Sync> FsProvider
|
|
|
- for LocalFsProvider<C, A>
|
|
|
+ impl<C: 'static + Creds + Clone + Send + Sync, A: 'static + Authorizer + Send + Sync> FsProvider
|
|
|
+ for LocalFs<C, A>
|
|
|
{
|
|
|
type LookupFut<'c> = Ready<Result<LookupReply>>;
|
|
|
fn lookup<'c>(&'c self, from: &'c Arc<BlockPath>, msg: Lookup<'c>) -> Self::LookupFut<'c> {
|
|
|
let result = (move || {
|
|
|
let Lookup { parent, name, .. } = msg;
|
|
|
- debug!("LocalFsProvider::lookup, parent {parent}, {:?}", name);
|
|
|
+ debug!("lookup: parent {parent}, {:?}", name);
|
|
|
let (dir, block_path) = match self.borrow_block_mut(parent, |parent_block| {
|
|
|
self.authorizer
|
|
|
.can_exec(&self.authz_context(from, parent_block.meta()))?;
|
|
@@ -942,7 +925,7 @@ mod private {
|
|
|
}) {
|
|
|
Ok(pair) => pair,
|
|
|
Err(err) => {
|
|
|
- error!("LocalFsProvider::lookup failed to borrow inode {parent}: {err}");
|
|
|
+ error!("lookup failed to borrow inode {parent}: {err}");
|
|
|
return Err(err);
|
|
|
}
|
|
|
};
|
|
@@ -959,7 +942,7 @@ mod private {
|
|
|
}) {
|
|
|
Ok(stat) => stat,
|
|
|
Err(err) => {
|
|
|
- error!("LocalFsProvider::lookup failed to read stats for '{name}': {err}");
|
|
|
+ error!("lookup failed to read stats for '{name}': {err}");
|
|
|
return Err(err);
|
|
|
}
|
|
|
};
|
|
@@ -984,7 +967,7 @@ mod private {
|
|
|
mode,
|
|
|
umask,
|
|
|
} = msg;
|
|
|
- debug!("Blocktree::create, parent {parent}, name {:?}", name);
|
|
|
+ debug!("create: parent {parent}, name {:?}", name);
|
|
|
|
|
|
let name = msg.name.to_owned();
|
|
|
|
|
@@ -1059,7 +1042,7 @@ mod private {
|
|
|
fn open<'c>(&'c self, from: &'c Arc<BlockPath>, msg: Open) -> Self::OpenFut<'c> {
|
|
|
let result = (move || {
|
|
|
let Open { inode, flags } = msg;
|
|
|
- debug!("Blocktree::open, inode {inode}, flags {flags}");
|
|
|
+ debug!("open: inode {inode}, flags {flags}");
|
|
|
if flags.value() & libc::O_APPEND != 0 {
|
|
|
return Err(Self::unsupported_flag_err("O_APPEND"));
|
|
|
}
|
|
@@ -1091,7 +1074,7 @@ mod private {
|
|
|
|
|
|
fn read<'c, R, F>(&'c self, from: &'c Arc<BlockPath>, msg: Read, callback: F) -> Result<R>
|
|
|
where
|
|
|
- F: 'c + Send + FnOnce(&[u8]) -> R,
|
|
|
+ F: 'c + FnOnce(&[u8]) -> R,
|
|
|
{
|
|
|
let Read {
|
|
|
inode,
|
|
@@ -1099,7 +1082,7 @@ mod private {
|
|
|
offset,
|
|
|
size,
|
|
|
} = msg;
|
|
|
- debug!("inode {inode}, handle {handle}, offset {offset}, size {size}");
|
|
|
+ debug!("read: inode {inode}, handle {handle}, offset {offset}, size {size}");
|
|
|
let mut callback = Some(callback);
|
|
|
let output = self.access_block(from, inode, handle, |block, flags| {
|
|
|
flags.assert_readable()?;
|
|
@@ -1138,22 +1121,28 @@ mod private {
|
|
|
}
|
|
|
|
|
|
type WriteFut<'c> = Ready<Result<WriteReply>>;
|
|
|
- fn write<'c>(&'c self, from: &'c Arc<BlockPath>, msg: Write) -> Self::WriteFut<'c> {
|
|
|
+ fn write<'c, R>(
|
|
|
+ &'c self,
|
|
|
+ from: &'c Arc<BlockPath>,
|
|
|
+ inode: Inode,
|
|
|
+ handle: Handle,
|
|
|
+ offset: u64,
|
|
|
+ size: u64,
|
|
|
+ reader: R,
|
|
|
+ ) -> Self::WriteFut<'c>
|
|
|
+ where
|
|
|
+ R: 'c + IoRead,
|
|
|
+ {
|
|
|
let result = (move || {
|
|
|
- let Write {
|
|
|
- inode,
|
|
|
- handle,
|
|
|
- offset,
|
|
|
- data,
|
|
|
- } = msg;
|
|
|
- debug!("inode {inode}, handle {handle}, offset {offset}");
|
|
|
+ debug!("write: inode {inode}, handle {handle}, offset {offset}");
|
|
|
let written = self.access_block_mut(from, inode, handle, |block, flags| {
|
|
|
flags.assert_writeable()?;
|
|
|
let pos = block.pos() as u64;
|
|
|
if offset != pos {
|
|
|
block.seek(SeekFrom::Start(offset))?;
|
|
|
}
|
|
|
- block.write(data).bterr()
|
|
|
+ let size = size.try_into().display_err()?;
|
|
|
+ block.write_from(reader, size).bterr()
|
|
|
})?;
|
|
|
Ok(WriteReply {
|
|
|
written: written as u64,
|
|
@@ -1166,7 +1155,7 @@ mod private {
|
|
|
fn flush<'c>(&'c self, from: &'c Arc<BlockPath>, msg: Flush) -> Self::FlushFut<'c> {
|
|
|
let result = (|| {
|
|
|
let Flush { inode, handle } = msg;
|
|
|
- debug!("inode {inode}, handle {handle}");
|
|
|
+ debug!("flush: inode {inode}, handle {handle}");
|
|
|
self.access_block_mut(from, inode, handle, |block, flags| {
|
|
|
flags.assert_writeable()?;
|
|
|
block.flush().bterr()
|
|
@@ -1184,7 +1173,7 @@ mod private {
|
|
|
limit,
|
|
|
state,
|
|
|
} = msg;
|
|
|
- debug!("inode {inode}, handle {handle}, state {state}");
|
|
|
+ debug!("read_dir: inode {inode}, handle {handle}, state {state}");
|
|
|
self.access_value(inode, |value| {
|
|
|
let handle_value = value
|
|
|
.value(handle)
|
|
@@ -1217,14 +1206,14 @@ mod private {
|
|
|
ready(result)
|
|
|
}
|
|
|
|
|
|
- type LinkFut<'c> = Ready<Result<()>>;
|
|
|
+ type LinkFut<'c> = Ready<Result<LinkReply>>;
|
|
|
fn link<'c>(&'c self, from: &'c Arc<BlockPath>, msg: Link) -> Self::LinkFut<'c> {
|
|
|
let Link {
|
|
|
inode,
|
|
|
new_parent,
|
|
|
name,
|
|
|
} = msg;
|
|
|
- debug!("inode {inode}, new_parent {new_parent}, name {name}");
|
|
|
+ debug!("link: inode {inode}, new_parent {new_parent}, name {name}");
|
|
|
let result = self.borrow_block_mut(new_parent, |parent_block| {
|
|
|
self.authorizer
|
|
|
.can_write(&self.authz_context(from, parent_block.meta()))?;
|
|
@@ -1234,25 +1223,26 @@ mod private {
|
|
|
return Err(io::Error::from_raw_os_error(libc::EEXIST).into());
|
|
|
}
|
|
|
|
|
|
- let file_type = self.access_value_mut(inode, |value| {
|
|
|
+ let attr = self.access_value_mut(inode, |value| {
|
|
|
let block = value.block_mut();
|
|
|
let meta = block.mut_meta_body();
|
|
|
- let mode = meta.access_secrets(|secrets| {
|
|
|
+ let attr = meta.access_secrets(|secrets| {
|
|
|
secrets.nlink += 1;
|
|
|
- Ok(secrets.mode)
|
|
|
+ Ok(secrets.to_owned())
|
|
|
})?;
|
|
|
- let file_type = FileType::from_value(mode)?;
|
|
|
block.flush_meta()?;
|
|
|
value.incr_lookup_count(from);
|
|
|
- Ok(file_type)
|
|
|
+ Ok(attr)
|
|
|
})?;
|
|
|
+ let file_type = FileType::from_value(attr.mode)?;
|
|
|
let entry = match file_type {
|
|
|
FileType::Reg => DirEntry::File(BlockRecord::new(inode)),
|
|
|
FileType::Dir => DirEntry::Directory(BlockRecord::new(inode)),
|
|
|
};
|
|
|
dir.insert_entry(name.to_owned(), entry);
|
|
|
parent_block.write_dir(&dir)?;
|
|
|
- Ok(())
|
|
|
+ let entry = self.bt_entry(attr);
|
|
|
+ Ok(LinkReply { entry })
|
|
|
});
|
|
|
ready(result)
|
|
|
}
|
|
@@ -1261,7 +1251,7 @@ mod private {
|
|
|
fn unlink<'c>(&'c self, from: &'c Arc<BlockPath>, msg: Unlink) -> Self::UnlinkFut<'c> {
|
|
|
let result = (move || {
|
|
|
let Unlink { parent, name } = msg;
|
|
|
- debug!("parent {parent}, name {name}");
|
|
|
+ debug!("unlink: parent {parent}, name {name}");
|
|
|
let (block_path, inode) = self.borrow_block_mut(parent, |parent_block| {
|
|
|
self.authorizer
|
|
|
.can_write(&self.authz_context(from, parent_block.meta()))?;
|
|
@@ -1298,7 +1288,7 @@ mod private {
|
|
|
ready(result)
|
|
|
}
|
|
|
|
|
|
- type ReadMetaFut<'c> = Ready<Result<BlockMeta>>;
|
|
|
+ type ReadMetaFut<'c> = Ready<Result<ReadMetaReply>>;
|
|
|
fn read_meta<'c>(
|
|
|
&'c self,
|
|
|
from: &'c Arc<BlockPath>,
|
|
@@ -1306,18 +1296,23 @@ mod private {
|
|
|
) -> Self::ReadMetaFut<'c> {
|
|
|
let result = (move || {
|
|
|
let ReadMeta { inode, handle } = msg;
|
|
|
- debug!("inode {inode}, handle {:?}", handle);
|
|
|
+ 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()))?
|
|
|
} else {
|
|
|
self.borrow_block(inode, |block| Ok(block.meta().to_owned()))?
|
|
|
};
|
|
|
- Ok(meta)
|
|
|
+ debug!("read_meta: {:?}", meta.body().secrets()?);
|
|
|
+ let reply = ReadMetaReply {
|
|
|
+ meta,
|
|
|
+ valid_for: self.attr_timeout(),
|
|
|
+ };
|
|
|
+ Ok(reply)
|
|
|
})();
|
|
|
ready(result)
|
|
|
}
|
|
|
|
|
|
- type WriteMetaFut<'c> = Ready<Result<()>>;
|
|
|
+ type WriteMetaFut<'c> = Ready<Result<WriteMetaReply>>;
|
|
|
fn write_meta<'c>(
|
|
|
&'c self,
|
|
|
from: &'c Arc<BlockPath>,
|
|
@@ -1327,32 +1322,84 @@ mod private {
|
|
|
let WriteMeta {
|
|
|
inode,
|
|
|
handle,
|
|
|
- meta,
|
|
|
+ attrs,
|
|
|
+ attrs_set,
|
|
|
} = msg;
|
|
|
- debug!("inode {inode}, handle {:?}", handle);
|
|
|
+ debug!("write_meta: inode {inode}, handle {:?}", handle);
|
|
|
let cb = |block: &mut FileBlock<C>| {
|
|
|
self.authorizer
|
|
|
.can_write(&self.authz_context(from, block.meta()))?;
|
|
|
- *block.mut_meta() = meta;
|
|
|
+ let attrs = block.mut_meta_body().access_secrets(|secrets| {
|
|
|
+ if attrs_set.mode() {
|
|
|
+ secrets.mode = attrs.mode;
|
|
|
+ }
|
|
|
+ if attrs_set.uid() {
|
|
|
+ secrets.uid = attrs.uid;
|
|
|
+ }
|
|
|
+ if attrs_set.gid() {
|
|
|
+ secrets.gid = attrs.gid;
|
|
|
+ }
|
|
|
+ if attrs_set.atime() {
|
|
|
+ secrets.atime = attrs.atime;
|
|
|
+ }
|
|
|
+ if attrs_set.mtime() {
|
|
|
+ secrets.mtime = attrs.mtime;
|
|
|
+ }
|
|
|
+ if attrs_set.ctime() {
|
|
|
+ secrets.ctime = attrs.ctime;
|
|
|
+ }
|
|
|
+ for (key, value) in attrs.tags.into_iter() {
|
|
|
+ secrets.tags.insert(key, value);
|
|
|
+ }
|
|
|
+ Ok(secrets.to_owned())
|
|
|
+ })?;
|
|
|
block.flush_meta()?;
|
|
|
- Ok(())
|
|
|
+ Ok(attrs)
|
|
|
};
|
|
|
- if let Some(handle) = handle {
|
|
|
+ let attrs = if let Some(handle) = handle {
|
|
|
self.access_block_mut(from, inode, handle, |block, flags| {
|
|
|
flags.assert_writeable()?;
|
|
|
cb(block.get_mut())
|
|
|
})
|
|
|
} else {
|
|
|
self.borrow_block_mut(inode, |block| cb(block.get_mut()))
|
|
|
- }
|
|
|
+ }?;
|
|
|
+ Ok(WriteMetaReply {
|
|
|
+ attrs,
|
|
|
+ valid_for: self.attr_timeout(),
|
|
|
+ })
|
|
|
})();
|
|
|
ready(result)
|
|
|
}
|
|
|
|
|
|
+ type AllocateFut<'c> = Ready<Result<()>>;
|
|
|
+ fn allocate<'c>(
|
|
|
+ &'c self,
|
|
|
+ from: &'c Arc<BlockPath>,
|
|
|
+ msg: Allocate,
|
|
|
+ ) -> Self::AllocateFut<'c> {
|
|
|
+ let Allocate {
|
|
|
+ inode,
|
|
|
+ handle,
|
|
|
+ offset,
|
|
|
+ size,
|
|
|
+ } = msg;
|
|
|
+ debug!("allocate: inode {inode}, handle {handle}, offset {offset}, size {size}");
|
|
|
+ let result = self.access_block_mut(from, inode, handle, |block, _| {
|
|
|
+ let curr_size = block.meta_body().secrets()?.size;
|
|
|
+ let new_size = curr_size.max(offset + size);
|
|
|
+ if new_size > curr_size {
|
|
|
+ block.zero_extend(new_size - curr_size)?;
|
|
|
+ }
|
|
|
+ Ok(())
|
|
|
+ });
|
|
|
+ ready(result)
|
|
|
+ }
|
|
|
+
|
|
|
type CloseFut<'c> = Ready<Result<()>>;
|
|
|
fn close<'c>(&'c self, from: &'c Arc<BlockPath>, msg: Close) -> Self::CloseFut<'c> {
|
|
|
let Close { inode, handle } = msg;
|
|
|
- debug!("inode {inode}, handle {handle}");
|
|
|
+ debug!("close: inode {inode}, handle {handle}");
|
|
|
let result = self.access_value_mut(inode, |value| {
|
|
|
if let Err(err) =
|
|
|
value.access_block_mut(from, handle, |block, _| block.flush().bterr())
|
|
@@ -1372,7 +1419,7 @@ mod private {
|
|
|
fn forget<'c>(&'c self, from: &'c Arc<BlockPath>, msg: Forget) -> Self::ForgetFut<'c> {
|
|
|
let result = (move || {
|
|
|
let Forget { inode, count } = msg;
|
|
|
- debug!("inode {inode}, count {count}");
|
|
|
+ debug!("forget: inode {inode}, count {count}");
|
|
|
let mut inodes = self.inodes.write().display_err()?;
|
|
|
self.inode_forget(&mut inodes, from, inode, count).bterr()
|
|
|
})();
|
|
@@ -1390,819 +1437,3 @@ mod private {
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
-#[cfg(test)]
|
|
|
-mod tests {
|
|
|
-
|
|
|
- use super::private::Error;
|
|
|
- use super::*;
|
|
|
-
|
|
|
- use btlib::{
|
|
|
- crypto::{ConcreteCreds, CredsPriv},
|
|
|
- BlockMeta,
|
|
|
- };
|
|
|
- use bytes::BytesMut;
|
|
|
- use lazy_static::lazy_static;
|
|
|
- use std::sync::Arc;
|
|
|
- use tempdir::TempDir;
|
|
|
-
|
|
|
- lazy_static! {
|
|
|
- static ref ROOT_CREDS: ConcreteCreds = ConcreteCreds::generate().unwrap();
|
|
|
- static ref NODE_CREDS: ConcreteCreds = {
|
|
|
- let root_creds = &ROOT_CREDS;
|
|
|
- let mut node_creds = ConcreteCreds::generate().unwrap();
|
|
|
- let writecap = root_creds
|
|
|
- .issue_writecap(
|
|
|
- node_creds.principal(),
|
|
|
- vec![],
|
|
|
- Epoch::now() + Duration::from_secs(3600),
|
|
|
- )
|
|
|
- .unwrap();
|
|
|
- node_creds.set_writecap(writecap);
|
|
|
- node_creds
|
|
|
- };
|
|
|
- }
|
|
|
-
|
|
|
- fn node_creds() -> &'static ConcreteCreds {
|
|
|
- &NODE_CREDS
|
|
|
- }
|
|
|
-
|
|
|
- /// Tests for the [ModeAuthorizer] struct.
|
|
|
- mod mode_authorizer_tests {
|
|
|
- use super::{
|
|
|
- super::private::{Authorizer, AuthzContext},
|
|
|
- *,
|
|
|
- };
|
|
|
-
|
|
|
- struct TestCase {
|
|
|
- ctx_uid: u32,
|
|
|
- ctx_gid: u32,
|
|
|
- meta: BlockMeta,
|
|
|
- }
|
|
|
-
|
|
|
- impl TestCase {
|
|
|
- const BLOCK_UID: u32 = 1000;
|
|
|
- const BLOCK_GID: u32 = 1000;
|
|
|
-
|
|
|
- fn new(ctx_uid: u32, ctx_gid: u32, mode: u32) -> TestCase {
|
|
|
- let mut meta =
|
|
|
- BlockMeta::new(node_creds()).expect("failed to create block metadata");
|
|
|
- meta.mut_body()
|
|
|
- .access_secrets(|secrets| {
|
|
|
- secrets.uid = Self::BLOCK_UID;
|
|
|
- secrets.gid = Self::BLOCK_GID;
|
|
|
- secrets.mode = mode;
|
|
|
- Ok(())
|
|
|
- })
|
|
|
- .expect("failed to update secrets");
|
|
|
- TestCase {
|
|
|
- ctx_uid,
|
|
|
- ctx_gid,
|
|
|
- meta,
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- fn context(&self) -> AuthzContext<'_> {
|
|
|
- AuthzContext {
|
|
|
- uid: self.ctx_uid,
|
|
|
- gid: self.ctx_gid,
|
|
|
- meta: &self.meta,
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- #[test]
|
|
|
- fn cant_read_when_no_bits_set() {
|
|
|
- let case = TestCase::new(TestCase::BLOCK_UID, TestCase::BLOCK_GID, 0);
|
|
|
- let result = ModeAuthorizer {}.can_read(&case.context());
|
|
|
- assert!(result.is_err())
|
|
|
- }
|
|
|
-
|
|
|
- #[test]
|
|
|
- fn cant_write_when_no_bits_set() {
|
|
|
- let case = TestCase::new(TestCase::BLOCK_UID, TestCase::BLOCK_GID, 0);
|
|
|
- let result = ModeAuthorizer {}.can_write(&case.context());
|
|
|
- assert!(result.is_err())
|
|
|
- }
|
|
|
-
|
|
|
- #[test]
|
|
|
- fn cant_exec_when_no_bits_set() {
|
|
|
- let case = TestCase::new(TestCase::BLOCK_UID, TestCase::BLOCK_GID, 0);
|
|
|
- let result = ModeAuthorizer {}.can_exec(&case.context());
|
|
|
- assert!(result.is_err())
|
|
|
- }
|
|
|
-
|
|
|
- #[test]
|
|
|
- fn user_can_read_when_bit_set() {
|
|
|
- let case = TestCase::new(TestCase::BLOCK_UID, TestCase::BLOCK_GID, libc::S_IRUSR);
|
|
|
- let result = ModeAuthorizer {}.can_read(&case.context());
|
|
|
- assert!(result.is_ok())
|
|
|
- }
|
|
|
-
|
|
|
- #[test]
|
|
|
- fn user_can_write_when_bit_set() {
|
|
|
- let case = TestCase::new(TestCase::BLOCK_UID, TestCase::BLOCK_GID, libc::S_IWUSR);
|
|
|
- let result = ModeAuthorizer {}.can_write(&case.context());
|
|
|
- assert!(result.is_ok())
|
|
|
- }
|
|
|
-
|
|
|
- #[test]
|
|
|
- fn user_can_exec_when_bit_set() {
|
|
|
- let case = TestCase::new(TestCase::BLOCK_UID, TestCase::BLOCK_GID, libc::S_IXUSR);
|
|
|
- let result = ModeAuthorizer {}.can_exec(&case.context());
|
|
|
- assert!(result.is_ok())
|
|
|
- }
|
|
|
-
|
|
|
- #[test]
|
|
|
- fn group_can_read_when_bit_set() {
|
|
|
- let case = TestCase::new(TestCase::BLOCK_UID, TestCase::BLOCK_GID, libc::S_IRGRP);
|
|
|
- let result = ModeAuthorizer {}.can_read(&case.context());
|
|
|
- assert!(result.is_ok())
|
|
|
- }
|
|
|
-
|
|
|
- #[test]
|
|
|
- fn group_can_write_when_bit_set() {
|
|
|
- let case = TestCase::new(TestCase::BLOCK_UID, TestCase::BLOCK_GID, libc::S_IWGRP);
|
|
|
- let result = ModeAuthorizer {}.can_write(&case.context());
|
|
|
- assert!(result.is_ok())
|
|
|
- }
|
|
|
-
|
|
|
- #[test]
|
|
|
- fn group_can_exec_when_bit_set() {
|
|
|
- let case = TestCase::new(TestCase::BLOCK_UID, TestCase::BLOCK_GID, libc::S_IXGRP);
|
|
|
- let result = ModeAuthorizer {}.can_exec(&case.context());
|
|
|
- assert!(result.is_ok())
|
|
|
- }
|
|
|
-
|
|
|
- #[test]
|
|
|
- fn other_can_read_when_bit_set() {
|
|
|
- let case = TestCase::new(TestCase::BLOCK_UID, TestCase::BLOCK_GID, libc::S_IROTH);
|
|
|
- let result = ModeAuthorizer {}.can_read(&case.context());
|
|
|
- assert!(result.is_ok())
|
|
|
- }
|
|
|
-
|
|
|
- #[test]
|
|
|
- fn other_can_write_when_bit_set() {
|
|
|
- let case = TestCase::new(TestCase::BLOCK_UID, TestCase::BLOCK_GID, libc::S_IWOTH);
|
|
|
- let result = ModeAuthorizer {}.can_write(&case.context());
|
|
|
- assert!(result.is_ok())
|
|
|
- }
|
|
|
-
|
|
|
- #[test]
|
|
|
- fn other_can_exec_when_bit_set() {
|
|
|
- let case = TestCase::new(TestCase::BLOCK_UID, TestCase::BLOCK_GID, libc::S_IXOTH);
|
|
|
- let result = ModeAuthorizer {}.can_exec(&case.context());
|
|
|
- assert!(result.is_ok())
|
|
|
- }
|
|
|
-
|
|
|
- #[test]
|
|
|
- fn other_cant_write_even_if_user_can() {
|
|
|
- let case = TestCase::new(
|
|
|
- TestCase::BLOCK_UID + 1,
|
|
|
- TestCase::BLOCK_GID + 1,
|
|
|
- libc::S_IWUSR,
|
|
|
- );
|
|
|
- let result = ModeAuthorizer {}.can_write(&case.context());
|
|
|
- assert!(result.is_err())
|
|
|
- }
|
|
|
-
|
|
|
- #[test]
|
|
|
- fn other_cant_write_even_if_group_can() {
|
|
|
- let case = TestCase::new(
|
|
|
- TestCase::BLOCK_UID + 1,
|
|
|
- TestCase::BLOCK_GID + 1,
|
|
|
- libc::S_IWGRP,
|
|
|
- );
|
|
|
- let result = ModeAuthorizer {}.can_write(&case.context());
|
|
|
- assert!(result.is_err())
|
|
|
- }
|
|
|
-
|
|
|
- #[test]
|
|
|
- fn user_allowed_read_when_only_other_bit_set() {
|
|
|
- let case = TestCase::new(TestCase::BLOCK_UID, TestCase::BLOCK_GID, libc::S_IROTH);
|
|
|
- let result = ModeAuthorizer {}.can_read(&case.context());
|
|
|
- assert!(result.is_ok())
|
|
|
- }
|
|
|
-
|
|
|
- #[test]
|
|
|
- fn root_always_allowed() {
|
|
|
- let case = TestCase::new(0, 0, 0);
|
|
|
- let ctx = case.context();
|
|
|
- let authorizer = ModeAuthorizer {};
|
|
|
- assert!(authorizer.can_read(&ctx).is_ok());
|
|
|
- assert!(authorizer.can_write(&ctx).is_ok());
|
|
|
- assert!(authorizer.can_exec(&ctx).is_ok());
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- fn bind_path<C: CredsPriv>(creds: C) -> BlockPath {
|
|
|
- let writecap = creds
|
|
|
- .writecap()
|
|
|
- .ok_or(btlib::BlockError::MissingWritecap)
|
|
|
- .unwrap();
|
|
|
- writecap.bind_path()
|
|
|
- }
|
|
|
-
|
|
|
- struct BtTestCase {
|
|
|
- dir: TempDir,
|
|
|
- bt: LocalFsProvider<ConcreteCreds, ModeAuthorizer>,
|
|
|
- from: Arc<BlockPath>,
|
|
|
- }
|
|
|
-
|
|
|
- impl BtTestCase {
|
|
|
- fn new_empty() -> BtTestCase {
|
|
|
- let dir = TempDir::new("fuse").expect("failed to create temp dir");
|
|
|
- let bt = LocalFsProvider::new_empty(
|
|
|
- dir.path().to_owned(),
|
|
|
- 0,
|
|
|
- Self::creds(),
|
|
|
- ModeAuthorizer {},
|
|
|
- )
|
|
|
- .expect("failed to create empty blocktree");
|
|
|
- let from = Arc::new(bind_path(node_creds()));
|
|
|
- BtTestCase { dir, bt, from }
|
|
|
- }
|
|
|
-
|
|
|
- fn new_existing(dir: TempDir) -> BtTestCase {
|
|
|
- let bt = LocalFsProvider::new_existing(
|
|
|
- dir.path().to_owned(),
|
|
|
- Self::creds(),
|
|
|
- ModeAuthorizer {},
|
|
|
- )
|
|
|
- .expect("failed to create blocktree from existing directory");
|
|
|
- let from = Arc::new(bind_path(node_creds()));
|
|
|
- BtTestCase { dir, bt, from }
|
|
|
- }
|
|
|
-
|
|
|
- fn creds() -> ConcreteCreds {
|
|
|
- node_creds().clone()
|
|
|
- }
|
|
|
-
|
|
|
- fn from(&self) -> &Arc<BlockPath> {
|
|
|
- &self.from
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /// 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 = BtTestCase::new_empty();
|
|
|
- let bt = &case.bt;
|
|
|
- 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 mut actual = [0u8; LEN];
|
|
|
- let read_msg = Read {
|
|
|
- inode,
|
|
|
- handle,
|
|
|
- offset: 0,
|
|
|
- size: LEN as u64,
|
|
|
- };
|
|
|
- bt.read(from, read_msg, |data| actual.copy_from_slice(data))
|
|
|
- .unwrap();
|
|
|
-
|
|
|
- assert_eq!(expected, actual)
|
|
|
- }
|
|
|
-
|
|
|
- #[tokio::test]
|
|
|
- async fn lookup() {
|
|
|
- let case = BtTestCase::new_empty();
|
|
|
- let bt = &case.bt;
|
|
|
- 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 = BtTestCase::new_empty();
|
|
|
- let bt = &case.bt;
|
|
|
- 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 flush_msg = Flush { inode, handle };
|
|
|
- bt.flush(from, flush_msg).await.unwrap();
|
|
|
- }
|
|
|
-
|
|
|
- let case = BtTestCase::new_existing(case.dir);
|
|
|
- let from = case.from();
|
|
|
- let bt = &case.bt;
|
|
|
-
|
|
|
- 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 mut actual = BytesMut::new();
|
|
|
- let read_msg = Read {
|
|
|
- inode,
|
|
|
- handle,
|
|
|
- offset: 0,
|
|
|
- size: EXPECTED.len() as u64,
|
|
|
- };
|
|
|
- bt.read(from, read_msg, |data| actual.extend_from_slice(data))
|
|
|
- .unwrap();
|
|
|
-
|
|
|
- assert_eq!(EXPECTED, &actual)
|
|
|
- }
|
|
|
-
|
|
|
- /// 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 = BtTestCase::new_empty();
|
|
|
- let bt = &case.bt;
|
|
|
- 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,
|
|
|
- };
|
|
|
- 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 = BtTestCase::new_empty();
|
|
|
- let from = case.from();
|
|
|
- let trash = ".Trash";
|
|
|
- let file = "file.txt";
|
|
|
- let bt = &case.bt;
|
|
|
- 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 = BtTestCase::new_empty();
|
|
|
- let from = case.from();
|
|
|
- let file = "file.txt";
|
|
|
- let bt = &case.bt;
|
|
|
- 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 mut actual = Vec::with_capacity(SIZE);
|
|
|
- let read_msg = Read {
|
|
|
- inode,
|
|
|
- handle,
|
|
|
- offset: 0,
|
|
|
- size: SIZE as u64,
|
|
|
- };
|
|
|
- bt.read(from, read_msg, |data| actual.extend_from_slice(data))
|
|
|
- .unwrap();
|
|
|
-
|
|
|
- assert_eq!(&[0, 1, 2, 3], actual.as_slice());
|
|
|
- }
|
|
|
-
|
|
|
- /// 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 = BtTestCase::new_empty();
|
|
|
- let from = case.from();
|
|
|
- let file = "file.txt";
|
|
|
- let bt = &case.bt;
|
|
|
- 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 = Box::new(case);
|
|
|
- let cb = Arc::new(Box::new(move |offset: usize| {
|
|
|
- // Notice that we have concurrent reads to different offsets using the same handle.
|
|
|
- // Without proper synchronization, this shouldn't work.
|
|
|
- let mut actual = Vec::with_capacity(SIZE);
|
|
|
- let read_msg = Read {
|
|
|
- inode,
|
|
|
- handle,
|
|
|
- offset: offset as u64,
|
|
|
- size: SIZE as u64,
|
|
|
- };
|
|
|
- case.bt
|
|
|
- .read(case.from(), read_msg, |data| actual.extend_from_slice(data))
|
|
|
- .unwrap();
|
|
|
- let expected = integer_array::<SIZE>(offset as u8);
|
|
|
- assert_eq!(&expected, actual.as_slice());
|
|
|
- }));
|
|
|
-
|
|
|
- let mut handles = Vec::with_capacity(NREADS);
|
|
|
- for offset in (0..NREADS).map(|e| e * SIZE) {
|
|
|
- let thread_cb = cb.clone();
|
|
|
- handles.push(std::thread::spawn(move || thread_cb(offset)));
|
|
|
- }
|
|
|
- for handle in handles {
|
|
|
- handle.join().unwrap();
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- #[tokio::test]
|
|
|
- async fn rename_in_same_directory() {
|
|
|
- let case = BtTestCase::new_empty();
|
|
|
- let bt = &case.bt;
|
|
|
- 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 = BtTestCase::new_empty();
|
|
|
- let bt = &case.bt;
|
|
|
- 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 = BtTestCase::new_empty();
|
|
|
- let bt = &case.bt;
|
|
|
- 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 = BtTestCase::new_empty();
|
|
|
- let bt = &case.bt;
|
|
|
- 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 = BtTestCase::new_empty();
|
|
|
- let bt = &case.bt;
|
|
|
- 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],
|
|
|
- };
|
|
|
- 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)
|
|
|
- }
|
|
|
-}
|