|
@@ -5,7 +5,8 @@ mod private {
|
|
|
use fuse_backend_rs::{
|
|
|
abi::fuse_abi::{stat64, statvfs64, CreateIn},
|
|
|
api::filesystem::{
|
|
|
- Context, DirEntry as FuseDirEntry, Entry, FileSystem, FsOptions, OpenOptions, SetattrValid,
|
|
|
+ Context, DirEntry as FuseDirEntry, Entry, FileSystem, FsOptions, OpenOptions,
|
|
|
+ SetattrValid,
|
|
|
},
|
|
|
};
|
|
|
use log::{debug, error, warn};
|
|
@@ -151,6 +152,22 @@ mod private {
|
|
|
fn can_exec<'a>(&self, ctx: &AuthzContext<'a>) -> io::Result<()>;
|
|
|
}
|
|
|
|
|
|
+ trait AuthorizerExt: Authorizer {
|
|
|
+ fn read_allowed(&self, ctx: &Context, meta: &BlockMeta) -> io::Result<()> {
|
|
|
+ Authorizer::can_read(self, &AuthzContext::new(ctx, meta))
|
|
|
+ }
|
|
|
+
|
|
|
+ fn write_allowed(&self, ctx: &Context, meta: &BlockMeta) -> io::Result<()> {
|
|
|
+ Authorizer::can_write(self, &AuthzContext::new(ctx, meta))
|
|
|
+ }
|
|
|
+
|
|
|
+ fn exec_allowed(&self, ctx: &Context, meta: &BlockMeta) -> io::Result<()> {
|
|
|
+ Authorizer::can_exec(self, &AuthzContext::new(ctx, meta))
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ impl<T: Authorizer> AuthorizerExt for T {}
|
|
|
+
|
|
|
/// A particularly simple authorizer that just looks at the mode bits in the block metadata
|
|
|
/// to make authorization decisions.
|
|
|
pub struct ModeAuthorizer {}
|
|
@@ -163,10 +180,17 @@ mod private {
|
|
|
Err(io::Error::new(io::ErrorKind::PermissionDenied, denied_msg))
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ fn user_is_root(ctx: &AuthzContext<'_>) -> bool {
|
|
|
+ ctx.uid == 0
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
impl Authorizer for ModeAuthorizer {
|
|
|
fn can_read<'a>(&self, ctx: &AuthzContext<'a>) -> io::Result<()> {
|
|
|
+ if Self::user_is_root(ctx) {
|
|
|
+ return Ok(());
|
|
|
+ }
|
|
|
let secrets = ctx.meta.body.secrets()?;
|
|
|
let mask = (libc::S_IRUSR * (secrets.uid == ctx.uid) as u32)
|
|
|
| (libc::S_IRGRP * (secrets.gid == ctx.gid) as u32)
|
|
@@ -175,6 +199,9 @@ mod private {
|
|
|
}
|
|
|
|
|
|
fn can_write<'a>(&self, ctx: &AuthzContext<'a>) -> io::Result<()> {
|
|
|
+ if Self::user_is_root(ctx) {
|
|
|
+ return Ok(());
|
|
|
+ }
|
|
|
let secrets = ctx.meta.body.secrets()?;
|
|
|
let mask = (libc::S_IWUSR * (secrets.uid == ctx.uid) as u32)
|
|
|
| (libc::S_IWGRP * (secrets.gid == ctx.gid) as u32)
|
|
@@ -183,6 +210,9 @@ mod private {
|
|
|
}
|
|
|
|
|
|
fn can_exec<'a>(&self, ctx: &AuthzContext<'a>) -> io::Result<()> {
|
|
|
+ if Self::user_is_root(ctx) {
|
|
|
+ return Ok(());
|
|
|
+ }
|
|
|
let secrets = ctx.meta.body.secrets()?;
|
|
|
let mask = (libc::S_IXUSR * (secrets.uid == ctx.uid) as u32)
|
|
|
| (libc::S_IXGRP * (secrets.gid == ctx.gid) as u32)
|
|
@@ -672,16 +702,6 @@ mod private {
|
|
|
})
|
|
|
}
|
|
|
|
|
|
- fn give_handle(&self, inode: Inode, handle: Handle) -> io::Result<()> {
|
|
|
- self.access_value_mut(inode, |value| {
|
|
|
- let block = value.block_mut(handle)?;
|
|
|
- // Be kind, rewind.
|
|
|
- block.seek(SeekFrom::Start(0))?;
|
|
|
- value.give_handle(handle);
|
|
|
- Ok(())
|
|
|
- })
|
|
|
- }
|
|
|
-
|
|
|
fn open_value<T, F: FnOnce(&mut InodeTableValue) -> io::Result<T>>(
|
|
|
&self,
|
|
|
inode: Inode,
|
|
@@ -781,6 +801,17 @@ mod private {
|
|
|
format!("unsupported flag: {flag}"),
|
|
|
))
|
|
|
}
|
|
|
+
|
|
|
+ fn fuse_entry(&self, inode: Inode, stat: stat64) -> Entry {
|
|
|
+ Entry {
|
|
|
+ generation: self.generation,
|
|
|
+ inode,
|
|
|
+ attr: stat,
|
|
|
+ attr_flags: 0,
|
|
|
+ attr_timeout: self.attr_timeout(),
|
|
|
+ entry_timeout: self.entry_timeout(),
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
unsafe impl<C: Sync, A: Sync> Sync for Blocktree<C, A> {}
|
|
@@ -802,10 +833,8 @@ mod private {
|
|
|
debug!("Blocktree::lookup called on parent {parent}");
|
|
|
let name = name.to_str().box_err()?;
|
|
|
let (dir, block_path) = self.borrow_block(parent, |block| {
|
|
|
- self.authorizer
|
|
|
- .can_exec(&AuthzContext::new(ctx, block.meta()))?;
|
|
|
- block.seek(SeekFrom::Start(0))?;
|
|
|
- let dir: Directory = read_from(block)?;
|
|
|
+ self.authorizer.exec_allowed(ctx, block.meta())?;
|
|
|
+ let dir = block.read_dir()?;
|
|
|
let path = block.meta_body().path.to_owned();
|
|
|
Ok((dir, path))
|
|
|
})?;
|
|
@@ -821,14 +850,7 @@ mod private {
|
|
|
value.incr_lookup_count();
|
|
|
Ok(stat)
|
|
|
})?;
|
|
|
- Ok(Entry {
|
|
|
- inode,
|
|
|
- generation: self.generation,
|
|
|
- attr: stat,
|
|
|
- attr_flags: 0,
|
|
|
- attr_timeout: self.attr_timeout(),
|
|
|
- entry_timeout: self.entry_timeout(),
|
|
|
- })
|
|
|
+ Ok(self.fuse_entry(inode, stat))
|
|
|
}
|
|
|
|
|
|
fn open(
|
|
@@ -876,10 +898,17 @@ mod private {
|
|
|
_lock_owner: Option<u64>,
|
|
|
) -> io::Result<()> {
|
|
|
debug!("Blocktree::release called on inode {inode}");
|
|
|
- if flush {
|
|
|
- self.access_block_mut(inode, handle, |block| block.flush())?;
|
|
|
- };
|
|
|
- self.give_handle(inode, handle)
|
|
|
+ self.access_value_mut(inode, |value| {
|
|
|
+ value.access_block_mut(handle, |block| {
|
|
|
+ if flush {
|
|
|
+ block.flush()?;
|
|
|
+ }
|
|
|
+ // Be kind, rewind.
|
|
|
+ block.seek(SeekFrom::Start(0))
|
|
|
+ })?;
|
|
|
+ value.give_handle(handle);
|
|
|
+ Ok(())
|
|
|
+ })
|
|
|
}
|
|
|
|
|
|
fn opendir(
|
|
@@ -891,8 +920,7 @@ mod private {
|
|
|
debug!("Blocktree::opendir called on inode {inode}");
|
|
|
let handle = self.access_value_mut(inode, |value| {
|
|
|
value.borrow_block(|block| {
|
|
|
- let ctx = AuthzContext::new(ctx, block.meta());
|
|
|
- self.authorizer.can_exec(&ctx)?;
|
|
|
+ self.authorizer.exec_allowed(ctx, block.meta())?;
|
|
|
Ok(())
|
|
|
})?;
|
|
|
let handle = value.take_handle()?;
|
|
@@ -935,20 +963,17 @@ 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)?;
|
|
|
+ self.authorizer.write_allowed(ctx, block.meta())?;
|
|
|
|
|
|
- block.seek(SeekFrom::Start(0))?;
|
|
|
- let mut dir: Directory = read_from(block).box_err()?;
|
|
|
+ let mut dir = block.read_dir()?;
|
|
|
dir.add_file(name.clone(), inode)?;
|
|
|
- block.seek(SeekFrom::Start(0))?;
|
|
|
- write_to(&dir, block).box_err()?;
|
|
|
- block.flush()?;
|
|
|
+ block.write_dir(&dir)?;
|
|
|
+
|
|
|
Ok(block.meta_body().path.clone())
|
|
|
})?;
|
|
|
block_path.push_component(name);
|
|
|
|
|
|
- let (handle, attr) =
|
|
|
+ let (handle, stat) =
|
|
|
self.open_then_take_handle(inode, block_path, |handle, value| {
|
|
|
let block = value.block_mut(handle)?;
|
|
|
Ok(block.mut_meta_body().access_secrets(|secrets| {
|
|
@@ -961,19 +986,15 @@ mod private {
|
|
|
secrets.ctime = now;
|
|
|
secrets.mtime = now;
|
|
|
secrets.nlink = 1;
|
|
|
- Ok((handle, secrets.attr()))
|
|
|
+ Ok((handle, secrets.stat()))
|
|
|
})?)
|
|
|
})?;
|
|
|
|
|
|
- let entry = Entry {
|
|
|
- inode,
|
|
|
- generation: self.generation,
|
|
|
- attr: attr.into(),
|
|
|
- attr_flags: 0,
|
|
|
- attr_timeout: self.attr_timeout(),
|
|
|
- entry_timeout: self.entry_timeout(),
|
|
|
- };
|
|
|
- Ok((entry, Some(handle), OpenOptions::empty()))
|
|
|
+ Ok((
|
|
|
+ self.fuse_entry(inode, stat),
|
|
|
+ Some(handle),
|
|
|
+ OpenOptions::empty(),
|
|
|
+ ))
|
|
|
}
|
|
|
|
|
|
fn write(
|
|
@@ -1147,8 +1168,7 @@ mod private {
|
|
|
_handle: Option<Self::Handle>,
|
|
|
) -> io::Result<(stat64, Duration)> {
|
|
|
debug!("Blocktree::getattr called for inode {inode}");
|
|
|
- let mut stat = self.access_meta(inode, |meta| Ok(meta.body.secrets()?.stat()))?;
|
|
|
- stat.st_ino = inode;
|
|
|
+ let stat = self.access_meta(inode, |meta| Ok(meta.body.secrets()?.stat()))?;
|
|
|
Ok((stat, self.attr_timeout()))
|
|
|
}
|
|
|
|
|
@@ -1176,11 +1196,9 @@ mod private {
|
|
|
debug!("Blocktree::unlink called on parent {parent}");
|
|
|
let name = name.to_str().box_err()?;
|
|
|
let (block_path, inode) = self.borrow_block(parent, |block| {
|
|
|
- let ctx = AuthzContext::new(ctx, block.meta());
|
|
|
- self.authorizer.can_write(&ctx)?;
|
|
|
+ self.authorizer.write_allowed(ctx, block.meta())?;
|
|
|
|
|
|
- block.seek(SeekFrom::Start(0))?;
|
|
|
- let mut dir: Directory = read_from(block)?;
|
|
|
+ let mut dir = block.read_dir()?;
|
|
|
let entry = match dir.entries.remove(name) {
|
|
|
None => return Err(io::Error::from_raw_os_error(libc::ENOENT)),
|
|
|
Some(entry) => entry,
|
|
@@ -1191,8 +1209,7 @@ mod private {
|
|
|
"no inode associated with the given name",
|
|
|
)
|
|
|
})?;
|
|
|
- block.seek(SeekFrom::Start(0))?;
|
|
|
- write_to(&dir, block)?;
|
|
|
+ block.write_dir(&dir)?;
|
|
|
|
|
|
let mut block_path = block.meta_body().path.clone();
|
|
|
block_path.push_component(name.to_owned());
|
|
@@ -1222,44 +1239,34 @@ mod private {
|
|
|
debug!("Blocktree::link called for inode {inode}");
|
|
|
let newname = newname.to_str().box_err()?;
|
|
|
self.borrow_block(newparent, |block| {
|
|
|
- let ctx = AuthzContext::new(ctx, block.meta());
|
|
|
- self.authorizer.can_write(&ctx)?;
|
|
|
+ self.authorizer.write_allowed(ctx, block.meta())?;
|
|
|
|
|
|
- block.seek(SeekFrom::Start(0))?;
|
|
|
- let mut dir: Directory = read_from(block)?;
|
|
|
+ let mut dir = block.read_dir()?;
|
|
|
if dir.entries.contains_key(newname) {
|
|
|
return Err(io::Error::from_raw_os_error(libc::EEXIST));
|
|
|
}
|
|
|
|
|
|
- let (file_type, attr) = self.access_value_mut(inode, |value| {
|
|
|
- let (file_type, attr) = value.borrow_block(|block| {
|
|
|
+ let (file_type, stat) = self.access_value_mut(inode, |value| {
|
|
|
+ let (file_type, stat) = value.borrow_block(|block| {
|
|
|
let meta = block.mut_meta_body();
|
|
|
- let (mode, attr) = meta.access_secrets(|secrets| {
|
|
|
+ let (mode, stat) = meta.access_secrets(|secrets| {
|
|
|
secrets.nlink += 1;
|
|
|
- Ok((secrets.mode, secrets.attr()))
|
|
|
+ Ok((secrets.mode, secrets.stat()))
|
|
|
})?;
|
|
|
let file_type = FileType::from_value(mode)?;
|
|
|
block.flush_meta()?;
|
|
|
- Ok((file_type, attr))
|
|
|
+ Ok((file_type, stat))
|
|
|
})?;
|
|
|
value.incr_lookup_count();
|
|
|
- Ok((file_type, attr))
|
|
|
+ Ok((file_type, stat))
|
|
|
})?;
|
|
|
let entry = match file_type {
|
|
|
FileType::Reg => DirEntry::File(BlockRecord::new(inode)),
|
|
|
FileType::Dir => DirEntry::Directory(BlockRecord::new(inode)),
|
|
|
};
|
|
|
dir.entries.insert(newname.to_owned(), entry);
|
|
|
- block.seek(SeekFrom::Start(0))?;
|
|
|
- write_to(&dir, block)?;
|
|
|
- Ok(Entry {
|
|
|
- inode,
|
|
|
- generation: self.generation,
|
|
|
- attr: attr.into(),
|
|
|
- attr_flags: 0,
|
|
|
- attr_timeout: self.attr_timeout(),
|
|
|
- entry_timeout: self.entry_timeout(),
|
|
|
- })
|
|
|
+ block.write_dir(&dir)?;
|
|
|
+ Ok(self.fuse_entry(inode, stat))
|
|
|
})
|
|
|
}
|
|
|
|
|
@@ -1273,8 +1280,7 @@ mod private {
|
|
|
) -> io::Result<(stat64, Duration)> {
|
|
|
debug!("Blocktree::setattr called for inode {inode}");
|
|
|
let stat = self.borrow_block(inode, |block| {
|
|
|
- let ctx = AuthzContext::new(ctx, block.meta());
|
|
|
- self.authorizer.can_write(&ctx)?;
|
|
|
+ self.authorizer.write_allowed(ctx, block.meta())?;
|
|
|
|
|
|
let stat = block.mut_meta_body().access_secrets(|secrets| {
|
|
|
if valid.intersects(SetattrValid::MODE) {
|
|
@@ -1321,6 +1327,65 @@ mod private {
|
|
|
Ok((stat, self.attr_timeout()))
|
|
|
}
|
|
|
|
|
|
+ fn mkdir(
|
|
|
+ &self,
|
|
|
+ ctx: &Context,
|
|
|
+ parent: Self::Inode,
|
|
|
+ name: &CStr,
|
|
|
+ mode: u32,
|
|
|
+ umask: u32,
|
|
|
+ ) -> io::Result<Entry> {
|
|
|
+ debug!("Blocktree::mkdir called");
|
|
|
+ let name = name.to_str().box_err()?.to_owned();
|
|
|
+ let (inode, mut block_path) = self.borrow_block(parent, |block| {
|
|
|
+ self.authorizer.write_allowed(ctx, block.meta())?;
|
|
|
+
|
|
|
+ let mut dir = block.read_dir()?;
|
|
|
+ if dir.entries.contains_key(&name) {
|
|
|
+ return Err(io::Error::from_raw_os_error(libc::EEXIST));
|
|
|
+ }
|
|
|
+
|
|
|
+ let inode = self.next_inode()?;
|
|
|
+ dir.add_file(name.clone(), inode)?;
|
|
|
+ block.write_dir(&dir)?;
|
|
|
+
|
|
|
+ Ok((inode, block.meta_body().path.clone()))
|
|
|
+ })?;
|
|
|
+ block_path.push_component(name);
|
|
|
+ let stat = self.open_value(inode, block_path, |value| {
|
|
|
+ let stat = value.borrow_block(|block| {
|
|
|
+ let stat = block.mut_meta_body().access_secrets(|secrets| {
|
|
|
+ secrets.mode = libc::S_IFDIR | mode & !umask;
|
|
|
+ secrets.uid = ctx.uid;
|
|
|
+ secrets.gid = ctx.gid;
|
|
|
+ Ok(secrets.stat())
|
|
|
+ })?;
|
|
|
+ block.write_dir(&Directory::new())?;
|
|
|
+ block.flush_meta()?;
|
|
|
+ Ok(stat)
|
|
|
+ })?;
|
|
|
+ value.incr_lookup_count();
|
|
|
+ Ok(stat)
|
|
|
+ })?;
|
|
|
+ Ok(self.fuse_entry(inode, stat))
|
|
|
+ }
|
|
|
+
|
|
|
+ fn rmdir(&self, ctx: &Context, parent: Self::Inode, name: &CStr) -> io::Result<()> {
|
|
|
+ debug!("Blocktree::rmdir called on parent {parent}");
|
|
|
+ let name = name.to_str().box_err()?.to_owned();
|
|
|
+ self.borrow_block(parent, |block| {
|
|
|
+ self.authorizer.write_allowed(ctx, block.meta())?;
|
|
|
+
|
|
|
+ let mut dir = block.read_dir()?;
|
|
|
+ if dir.entries.remove(&name).is_none() {
|
|
|
+ return Err(io::Error::from_raw_os_error(libc::ENOENT));
|
|
|
+ }
|
|
|
+
|
|
|
+ block.write_dir(&dir)?;
|
|
|
+ Ok(())
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
//////////////////////////////////
|
|
|
// METHODS WHICH ARE NOT SUPPORTED
|
|
|
//////////////////////////////////
|
|
@@ -1429,18 +1494,6 @@ mod private {
|
|
|
Self::not_supported()
|
|
|
}
|
|
|
|
|
|
- fn mkdir(
|
|
|
- &self,
|
|
|
- _ctx: &Context,
|
|
|
- _parent: Self::Inode,
|
|
|
- _name: &CStr,
|
|
|
- _mode: u32,
|
|
|
- _umask: u32,
|
|
|
- ) -> io::Result<Entry> {
|
|
|
- debug!("Blocktree::mkdir called");
|
|
|
- Self::not_supported()
|
|
|
- }
|
|
|
-
|
|
|
fn mknod(
|
|
|
&self,
|
|
|
_ctx: &Context,
|
|
@@ -1511,11 +1564,6 @@ mod private {
|
|
|
Self::not_supported()
|
|
|
}
|
|
|
|
|
|
- fn rmdir(&self, _ctx: &Context, parent: Self::Inode, _name: &CStr) -> io::Result<()> {
|
|
|
- debug!("Blocktree::rmdir called on parent {parent}");
|
|
|
- Self::not_supported()
|
|
|
- }
|
|
|
-
|
|
|
fn setlk(
|
|
|
&self,
|
|
|
_ctx: &Context,
|
|
@@ -1745,6 +1793,16 @@ mod tests {
|
|
|
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());
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
struct BtTestCase {
|