|
@@ -180,18 +180,24 @@ mod private {
|
|
|
|
|
|
struct InodeTableValue {
|
|
|
blocks: HashMap<Handle, Box<dyn Block>>,
|
|
|
+ dirs: HashMap<Handle, Directory>,
|
|
|
next_handle: Handle,
|
|
|
lookup_count: AtomicU64,
|
|
|
unclaimed_handles: Vec<Handle>,
|
|
|
}
|
|
|
|
|
|
impl InodeTableValue {
|
|
|
+ /// If more than this number of unclaimed blocks are open, then blocks are closed until
|
|
|
+ /// only this number remain open.
|
|
|
+ const UNCLAIMED_HANDLE_LIMIT: usize = 3;
|
|
|
+
|
|
|
fn new(block: Box<dyn Block>) -> InodeTableValue {
|
|
|
const FIRST_HANDLE: Handle = 1;
|
|
|
let mut blocks = HashMap::with_capacity(1);
|
|
|
blocks.insert(FIRST_HANDLE, block);
|
|
|
Self {
|
|
|
blocks,
|
|
|
+ dirs: HashMap::new(),
|
|
|
next_handle: FIRST_HANDLE + 1,
|
|
|
lookup_count: AtomicU64::new(1),
|
|
|
unclaimed_handles: vec![FIRST_HANDLE],
|
|
@@ -243,6 +249,10 @@ mod private {
|
|
|
|
|
|
fn give_handle(&mut self, handle: Handle) {
|
|
|
self.unclaimed_handles.push(handle);
|
|
|
+ while self.unclaimed_handles.len() > Self::UNCLAIMED_HANDLE_LIMIT {
|
|
|
+ let handle = self.unclaimed_handles.pop().unwrap();
|
|
|
+ self.blocks.remove(&handle);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
fn incr_lookup_count(&self) -> u64 {
|
|
@@ -754,13 +764,13 @@ mod private {
|
|
|
|
|
|
fn release(
|
|
|
&self,
|
|
|
- ctx: &Context,
|
|
|
+ _ctx: &Context,
|
|
|
inode: Self::Inode,
|
|
|
- flags: u32,
|
|
|
+ _flags: u32,
|
|
|
handle: Self::Handle,
|
|
|
flush: bool,
|
|
|
- flock_release: bool,
|
|
|
- lock_owner: Option<u64>,
|
|
|
+ _flock_release: bool,
|
|
|
+ _lock_owner: Option<u64>,
|
|
|
) -> io::Result<()> {
|
|
|
debug!("Blocktree::release called on inode {inode}");
|
|
|
if flush {
|
|
@@ -773,12 +783,20 @@ mod private {
|
|
|
&self,
|
|
|
ctx: &Context,
|
|
|
inode: Self::Inode,
|
|
|
- flags: u32,
|
|
|
+ _flags: u32,
|
|
|
) -> io::Result<(Option<Self::Handle>, OpenOptions)> {
|
|
|
debug!("Blocktree::opendir called on inode {inode}");
|
|
|
- let handle = self.take_block_if_ok(inode, |handle, block| {
|
|
|
- let ctx = AuthzContext::new(ctx, block.meta());
|
|
|
- self.authorizer.can_exec(&ctx)?;
|
|
|
+ let handle = self.access_value_mut(inode, |value| {
|
|
|
+ let dir = value.try_borrow_block(|block| {
|
|
|
+ let ctx = AuthzContext::new(ctx, block.meta());
|
|
|
+ self.authorizer.can_exec(&ctx)?;
|
|
|
+ block.seek(SeekFrom::Start(0))?;
|
|
|
+ let dir: Directory = read_from(block)?;
|
|
|
+ Ok(dir)
|
|
|
+ })?;
|
|
|
+ let handle = value.next_handle;
|
|
|
+ value.next_handle += 1;
|
|
|
+ value.dirs.insert(handle, dir);
|
|
|
Ok(handle)
|
|
|
})?;
|
|
|
Ok((Some(handle), OpenOptions::empty()))
|
|
@@ -786,13 +804,16 @@ mod private {
|
|
|
|
|
|
fn releasedir(
|
|
|
&self,
|
|
|
- ctx: &Context,
|
|
|
+ _ctx: &Context,
|
|
|
inode: Self::Inode,
|
|
|
- flags: u32,
|
|
|
+ _flags: u32,
|
|
|
handle: Self::Handle,
|
|
|
) -> io::Result<()> {
|
|
|
debug!("Blocktree::releasedir called for inode {inode}");
|
|
|
- self.give_handle(inode, handle)
|
|
|
+ self.access_value_mut(inode, |value| {
|
|
|
+ value.dirs.remove(&handle);
|
|
|
+ Ok(())
|
|
|
+ })
|
|
|
}
|
|
|
|
|
|
fn create(
|
|
@@ -814,6 +835,9 @@ mod private {
|
|
|
|
|
|
// Add a directory entry to the parent for the new inode.
|
|
|
let mut block_path = self.borrow_block(parent, |block| {
|
|
|
+ let ctx = AuthzContext::new(ctx, block.meta());
|
|
|
+ self.authorizer.can_write(&ctx)?;
|
|
|
+
|
|
|
block.seek(SeekFrom::Start(0))?;
|
|
|
let mut dir: Directory = read_from(block).box_err()?;
|
|
|
dir.add_file(name.clone(), inode)?;
|
|
@@ -830,9 +854,9 @@ mod private {
|
|
|
let block = value.block(handle)?;
|
|
|
Ok(block.mut_meta_body().access_secrets(|secrets| {
|
|
|
secrets.inode = inode;
|
|
|
- secrets.mode = Self::default_file_mode();
|
|
|
- secrets.uid = Self::uid();
|
|
|
- secrets.gid = Self::gid();
|
|
|
+ secrets.mode = args.mode;
|
|
|
+ secrets.uid = ctx.uid;
|
|
|
+ secrets.gid = ctx.gid;
|
|
|
let now = Epoch::now();
|
|
|
secrets.atime = now;
|
|
|
secrets.ctime = now;
|
|
@@ -962,7 +986,7 @@ mod private {
|
|
|
|
|
|
fn readdir(
|
|
|
&self,
|
|
|
- ctx: &Context,
|
|
|
+ _ctx: &Context,
|
|
|
inode: Self::Inode,
|
|
|
handle: Self::Handle,
|
|
|
size: u32,
|
|
@@ -972,31 +996,29 @@ mod private {
|
|
|
) -> io::Result<usize>,
|
|
|
) -> io::Result<()> {
|
|
|
debug!("Blocktree::readdir called on inode {inode}");
|
|
|
- let dir = self.access_block(inode, handle, |block| {
|
|
|
- block.seek(SeekFrom::Start(0))?;
|
|
|
- Ok(read_from::<Directory, _>(block)?)
|
|
|
- })?;
|
|
|
- let mut index = 0u64;
|
|
|
- for (name, entry) in dir.entries() {
|
|
|
- index += 1;
|
|
|
- // TODO: Using a simple index will fail when the directory is being modified
|
|
|
- // concurrently.
|
|
|
- if index <= offset {
|
|
|
- continue;
|
|
|
+ self.access_value(inode, |value| {
|
|
|
+ let dir = value.dirs.get(&handle)
|
|
|
+ .ok_or(Error::InvalidHandle { handle, inode })?;
|
|
|
+ let mut index: u64 = 0;
|
|
|
+ for (name, entry) in dir.entries() {
|
|
|
+ index += 1;
|
|
|
+ if index <= offset {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ let inode = match entry.inode() {
|
|
|
+ Some(inode) => inode,
|
|
|
+ None => continue,
|
|
|
+ };
|
|
|
+ let dir_entry = FuseDirEntry {
|
|
|
+ ino: inode,
|
|
|
+ offset: index,
|
|
|
+ type_: entry.kind() as u32,
|
|
|
+ name: name.as_bytes(),
|
|
|
+ };
|
|
|
+ add_entry(dir_entry)?;
|
|
|
}
|
|
|
- let inode = match entry.inode() {
|
|
|
- Some(inode) => inode,
|
|
|
- None => continue,
|
|
|
- };
|
|
|
- let dir_entry = FuseDirEntry {
|
|
|
- ino: inode,
|
|
|
- offset: index,
|
|
|
- type_: entry.kind() as u32,
|
|
|
- name: name.as_bytes(),
|
|
|
- };
|
|
|
- add_entry(dir_entry)?;
|
|
|
- }
|
|
|
- Ok(())
|
|
|
+ Ok(())
|
|
|
+ })
|
|
|
}
|
|
|
|
|
|
fn getattr(
|
|
@@ -1313,8 +1335,10 @@ mod private {
|
|
|
#[cfg(test)]
|
|
|
mod tests {
|
|
|
use std::ffi::CString;
|
|
|
-
|
|
|
- use fuse_backend_rs::api::filesystem::{FileSystem, FsOptions};
|
|
|
+ use fuse_backend_rs::{
|
|
|
+ abi::fuse_abi::CreateIn,
|
|
|
+ api::filesystem::{Context, FileSystem, FsOptions}
|
|
|
+ };
|
|
|
use tempdir::TempDir;
|
|
|
|
|
|
use test_helpers::*;
|
|
@@ -1509,6 +1533,16 @@ mod tests {
|
|
|
fn creds() -> ConcreteCreds {
|
|
|
test_helpers::NODE_CREDS.clone()
|
|
|
}
|
|
|
+
|
|
|
+ fn context(&self) -> Context {
|
|
|
+ let (stat, ..) = self.bt.getattr(&Default::default(), SpecInodes::RootDir.into(), None)
|
|
|
+ .expect("getattr failed");
|
|
|
+ Context {
|
|
|
+ uid: stat.st_uid,
|
|
|
+ gid: stat.st_gid,
|
|
|
+ pid: 1,
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/// Tests that a new file can be created, written to and the written data can be read from it.
|
|
@@ -1516,15 +1550,15 @@ mod tests {
|
|
|
fn create_write_lseek_read() {
|
|
|
let case = BtTestCase::new_empty();
|
|
|
let bt = &case.bt;
|
|
|
-
|
|
|
+ let ctx = case.context();
|
|
|
let name = CString::new("README.md").unwrap();
|
|
|
|
|
|
let (entry, handle, ..) = bt
|
|
|
.create(
|
|
|
- &Default::default(),
|
|
|
+ &ctx,
|
|
|
SpecInodes::RootDir.into(),
|
|
|
name.as_c_str(),
|
|
|
- Default::default(),
|
|
|
+ CreateIn { mode: libc::S_IFREG | 0o644, umask: 0, flags: 0, fuse_flags: 0 },
|
|
|
)
|
|
|
.expect("failed to create file");
|
|
|
let inode = entry.inode;
|
|
@@ -1534,7 +1568,7 @@ mod tests {
|
|
|
let mut expected = BtCursor::new([1u8; LEN]);
|
|
|
let written = bt
|
|
|
.write(
|
|
|
- &Default::default(),
|
|
|
+ &ctx,
|
|
|
inode,
|
|
|
handle,
|
|
|
&mut expected,
|
|
@@ -1548,13 +1582,13 @@ mod tests {
|
|
|
.expect("write failed");
|
|
|
assert_eq!(LEN, written);
|
|
|
|
|
|
- bt.lseek(&Default::default(), inode, handle, 0, 0)
|
|
|
+ bt.lseek(&ctx, inode, handle, 0, 0)
|
|
|
.expect("lseek failed");
|
|
|
|
|
|
let mut actual = BtCursor::new([0u8; LEN]);
|
|
|
let read = bt
|
|
|
.read(
|
|
|
- &Default::default(),
|
|
|
+ &ctx,
|
|
|
inode,
|
|
|
handle,
|
|
|
&mut actual,
|
|
@@ -1573,11 +1607,12 @@ mod tests {
|
|
|
fn lookup() {
|
|
|
let case = BtTestCase::new_empty();
|
|
|
let bt = &case.bt;
|
|
|
+ let ctx = case.context();
|
|
|
|
|
|
let name = CString::new("README.md").unwrap();
|
|
|
let (expected, ..) = bt
|
|
|
.create(
|
|
|
- &Default::default(),
|
|
|
+ &ctx,
|
|
|
SpecInodes::RootDir.into(),
|
|
|
name.as_c_str(),
|
|
|
Default::default(),
|
|
@@ -1598,17 +1633,17 @@ mod tests {
|
|
|
fn new_existing() {
|
|
|
const EXPECTED: &[u8] = b"cool as cucumbers";
|
|
|
let name = CString::new("RESIGNATION.docx").unwrap();
|
|
|
-
|
|
|
let case = BtTestCase::new_empty();
|
|
|
let bt = &case.bt;
|
|
|
+ let ctx = case.context();
|
|
|
|
|
|
{
|
|
|
let (entry, handle, ..) = bt
|
|
|
.create(
|
|
|
- &Default::default(),
|
|
|
+ &ctx,
|
|
|
SpecInodes::RootDir.into(),
|
|
|
name.as_c_str(),
|
|
|
- Default::default(),
|
|
|
+ CreateIn { mode: libc::S_IFREG | 0o644, umask: 0, flags: 0, fuse_flags: 0 },
|
|
|
)
|
|
|
.expect("failed to create file");
|
|
|
let inode = entry.inode;
|