From 76ac602c3d79bb39c133c81a38425a77bc0b8b1f Mon Sep 17 00:00:00 2001 From: Chuyan Zhang Date: Sat, 25 Nov 2023 02:13:22 -0800 Subject: Some FUSE callbacks, some POSIX interface implementation --- src/filesystem/trait_impl.rs | 518 +++++++++++++++++++++++++++---------------- 1 file changed, 333 insertions(+), 185 deletions(-) (limited to 'src/filesystem/trait_impl.rs') diff --git a/src/filesystem/trait_impl.rs b/src/filesystem/trait_impl.rs index a25a383..c533c41 100644 --- a/src/filesystem/trait_impl.rs +++ b/src/filesystem/trait_impl.rs @@ -1,67 +1,69 @@ -use crate::block_device::BLOCK_SIZE; + use crate::disk::inode::InodeMode; -use crate::utils::make_fileattr; -use crate::{utils, AyaFS, TTL}; -use fuser::{ - FileAttr, FileType, Filesystem, KernelConfig, ReplyAttr, ReplyData, ReplyDirectory, ReplyEmpty, - ReplyEntry, ReplyLseek, ReplyWrite, Request, TimeOrNow, +use crate::utils::{from_systime, time_now, to_fileattr, to_filetype}; +use crate::utils::permissions::get_groups; +use crate::{AyaFS, TTL}; +use fuser::{Filesystem, KernelConfig, ReplyAttr, ReplyData, ReplyDirectory, ReplyEmpty, + ReplyEntry, ReplyLseek, ReplyOpen, Request, TimeOrNow, }; -use libc::{c_int, ENAMETOOLONG, ENOENT, ENOSPC, ENOSYS, ENOTDIR}; +use libc::{c_int, EACCES, EEXIST, ENAMETOOLONG, ENOENT, ENOSPC, ENOSYS, ENOTDIR, EPERM, R_OK, S_ISGID, S_ISUID, S_IXGRP, S_IXOTH, S_IXUSR, W_OK}; use log::debug; use std::ffi::OsStr; -use std::os::unix::ffi::OsStrExt; use std::time::SystemTime; -use users::{get_current_gid, get_current_uid}; +use fuser::TimeOrNow::{Now, SpecificTime}; +use crate::utils::permissions::{check_access, clear_suid_sgid}; + +impl AyaFS { + +} impl Filesystem for AyaFS { fn init(&mut self, _req: &Request<'_>, _config: &mut KernelConfig) -> Result<(), c_int> { - debug!("Filesystem::init called."); + debug!("`init()"); Ok(()) } fn destroy(&mut self) { - debug!("Filesystem::destroy()"); + debug!("destroy()"); + // TODO 写回 } - fn lookup(&mut self, _req: &Request<'_>, parent: u64, name: &OsStr, reply: ReplyEntry) { - let parent = parent as usize; - if let Some(inode) = self.get_inode(parent) { - // debug!("{:?}", inode); + fn lookup(&mut self, req: &Request<'_>, parent: u64, name: &OsStr, reply: ReplyEntry) { + if name.len() > 255 { + reply.error(ENAMETOOLONG); + return; } - // if self.inode_active(parent) { - // let (block, offset) = self.locate_inode(parent); - // let inode = self.get_inode(block, offset); - // debug!("{:?}", inode); - // } - reply.error(ENOENT); - } - fn forget(&mut self, _req: &Request<'_>, _ino: u64, _nlookup: u64) { - debug!("Filesystem::forget()"); - todo!("This is a dumb implementation") + let parent = parent as usize; + if let Some(parent_inode) = self.get_inode(parent) { + if check_access( + req.uid(), + req.gid(), + parent_inode.uid, + parent_inode.gid, + parent_inode.mode, + R_OK + ) { + let parent_inode = parent_inode.clone(); + match self.lookup_name(&parent_inode, name) { + Ok((inode_index, inode)) => { + let attr = to_fileattr(inode_index as usize, &inode); + reply.entry(&TTL, &attr, 0); + }, + Err(err_code) => reply.error(err_code) + }; + } else { + reply.error(EACCES); + } + } else { + reply.error(ENOENT); + } } fn getattr(&mut self, _req: &Request<'_>, ino: u64, reply: ReplyAttr) { debug!("Filesystem::getattr(ino: {})", ino); if let Some(inode) = self.get_inode(ino as usize) { - let attr = FileAttr { - ino, - size: inode.size as u64, - blocks: inode.n_blocks as u64, - atime: utils::to_systime(inode.atime), - mtime: utils::to_systime(inode.mtime), - ctime: utils::to_systime(inode.ctime), - crtime: utils::to_systime(inode.crtime), - kind: inode.mode.into(), - perm: inode.mode.perm(), - nlink: inode.n_links as u32, - uid: inode.uid, - gid: inode.gid, - rdev: 0, - blksize: BLOCK_SIZE as u32, - flags: inode.flags, - }; - reply.attr(&TTL, &attr); + reply.attr(&TTL, &to_fileattr(ino as usize, inode)); } else { reply.error(ENOENT); } @@ -69,96 +71,159 @@ impl Filesystem for AyaFS { fn setattr( &mut self, - _req: &Request<'_>, + req: &Request<'_>, ino: u64, mode: Option, uid: Option, gid: Option, - size: Option, // TODO 为什么 setattr 还可以设置 size?? + size: Option, // TODO 当 setattr 被 ftruncate invoke 时会设置 size atime: Option, mtime: Option, - ctime: Option, - fh: Option, - crtime: Option, - _chgtime: Option, - _bkuptime: Option, - flags: Option, + _ctime: Option, + fh: Option, // TODO 当 setattr 被 ftruncate invoke 时会提供 fh + _crtime: Option, // 忽略 + _chgtime: Option, // 忽略 + _bkuptime: Option, // 忽略 + _flags: Option, // 忽略 reply: ReplyAttr, ) { if let Some(inode) = self.get_inode_mut(ino as usize) { - let mode = mode - .and_then(|mode_value| { - InodeMode::validate(mode_value as u16) // high 16 bit truncated - }) - .unwrap_or(inode.mode); - inode.mode = mode; - // 如果传入的 InodeMode 是合法结果, 那么得到 Some(mode), 否则 None - // 需要检查权限吗? 如果需要, 如何知道 invoke 这个 callback 的 UID/GID? - - let size = size.unwrap_or(inode.size as u64); - inode.size = size as u32; - // TODO 为什么可以设置 size 呢…… - - let uid = uid.unwrap_or(inode.uid); - let gid = gid.unwrap_or(inode.gid); - inode.uid = uid; - inode.gid = gid; - - let (atime, atime_inner) = atime - .map(|atime| { - let system_time = match atime { - TimeOrNow::SpecificTime(system_time) => system_time, - TimeOrNow::Now => SystemTime::now(), + // chmod + if let Some(mode) = mode { + debug!("chmod on inode {:#x?} mode {:o}", ino, mode); + if req.uid() != 0 && req.uid() != inode.uid { + reply.error(EPERM); + return; + } // uid == 0 (root) or uid == inode.uid (user itself) + + if req.uid() != 0 && req.gid() != inode.gid && !get_groups(req.pid()).contains(&inode.gid) { + inode.mode = InodeMode((mode & !S_ISGID) as u16); + } else { + inode.mode = InodeMode(mode as u16); + } + + inode.ctime = time_now(); + reply.attr(&TTL, &to_fileattr(ino as usize, inode)); + return; + } + + // chown + if uid.is_some() || gid.is_some() { + debug!("chown on inode {:#x?} uid {:?} gid {:?}", ino, uid, gid); + if let Some(uid) = uid { + // 虽然只有 root 可以 chown 但是 user chown 自己也是不报错的 + if req.uid() != 0 && !(uid == inode.uid && uid == req.uid()) { + reply.error(EPERM); + return; + } + } + if let Some(gid) = gid { + // 只有 root 和 file owner 才可以 chgrp + if req.uid() != 0 && req.uid() != inode.uid { + reply.error(EPERM); + return; + } + // root 可以任意 chgrp, 非 root 只能修改到用户自己所在的组里 + if req.gid() != 0 && !get_groups(req.pid()).contains(&gid) { + reply.error(EPERM); + return; + } + } + + // 可执行文件要清除 suid & sgid + if inode.mode.0 & (S_IXUSR | S_IXGRP | S_IXOTH) as u16 != 0 { + inode.mode = clear_suid_sgid(inode.mode); + } + + // chown 要清除 suid + if let Some(uid) = uid { + inode.uid = uid; + inode.mode.0 &= !S_ISUID as u16; + } + + if let Some(gid) = gid { + inode.gid = gid; + if req.uid() != 0 { + inode.mode.0 &= !S_ISGID as u16; + } + } + inode.ctime = time_now(); + reply.attr(&TTL, &to_fileattr(ino as usize, inode)); + return; + } + + // ftruncate + if let Some(size) = size { + debug!("ftruncate on inode {:#x?} size {:?}", ino, size); + if let Some(file_handle) = fh { + // 有 file handle 的 + todo!() + } else { + // 没有 file handle 基于 inode 的 + todo!() + } + } + + // time 相关 + if atime.is_some() || mtime.is_some() { + let current_time = time_now(); + if let Some(atime) = atime { + debug!("utimensat on inode {:#x?}, atime {:?}", ino, atime); + // root 和 user 可以随意修改 atime, 其他用户只能 touch (即 atime == Now) + if req.uid() != 0 && req.uid() != inode.uid && atime != Now { + reply.error(EPERM); + return; + } + + if req.uid() != 0 && req.uid() != inode.uid && check_access( + req.uid(), + req.gid(), + inode.uid, + inode.gid, + inode.mode, + R_OK, // atime 来说是 read TODO 对吗 + ) { + reply.error(EACCES); + return; + } + + inode.atime = match atime { + SpecificTime(time) => from_systime(time), + Now => current_time, }; - (system_time, utils::from_systime(system_time)) - }) - .unwrap_or((utils::to_systime(inode.atime), inode.atime)); - inode.atime = atime_inner; - - let (mtime, mtime_inner) = mtime - .map(|mtime| { - let system_time = match mtime { - TimeOrNow::SpecificTime(system_time) => system_time, - TimeOrNow::Now => SystemTime::now(), + inode.ctime = current_time; + } + if let Some(mtime) = mtime { + debug!("utimensat on inode {:#x?}, mtime {:?}", ino, mtime); + // root 和 user 可以随意修改 mtime, 其他用户只能 mtime == Now + if req.uid() != 0 && req.uid() != inode.uid && mtime != Now { + reply.error(EPERM); + return; + } + + if req.uid() != 0 && req.uid() != inode.uid && check_access( + req.uid(), + req.gid(), + inode.uid, + inode.gid, + inode.mode, + W_OK, // mtime 来说是 write + ) { + reply.error(EACCES); + return; + } + + inode.mtime = match mtime { + SpecificTime(time) => from_systime(time), + Now => current_time, }; - (system_time, utils::from_systime(system_time)) - }) - .unwrap_or((utils::to_systime(inode.mtime), inode.mtime)); - inode.mtime = mtime_inner; - - let (ctime, ctime_inner) = ctime - .map(|ctime| (ctime, utils::from_systime(ctime))) - .unwrap_or((utils::to_systime(inode.ctime), inode.ctime)); - inode.ctime = ctime_inner; - - let (crtime, crtime_inner) = crtime - .map(|crtime| (crtime, utils::from_systime(crtime))) - .unwrap_or((utils::to_systime(inode.crtime), inode.crtime)); - inode.crtime = crtime_inner; - - let flags = flags.unwrap_or(inode.flags); - inode.flags = flags; - - let attr = FileAttr { - ino, - size, - blocks: inode.n_blocks as u64, - atime, - mtime, - ctime, - crtime, - kind: mode.into(), - perm: mode.perm(), - nlink: inode.n_links as u32, - uid, - gid, - rdev: 0, - blksize: BLOCK_SIZE as u32, - flags, - }; - reply.attr(&TTL, &attr); + inode.ctime = current_time; + } + } + reply.attr(&TTL, &to_fileattr(ino as usize, inode)); + return; } else { - reply.error(ENOSYS); + reply.error(ENOENT); } } @@ -169,7 +234,7 @@ impl Filesystem for AyaFS { fn mknod( &mut self, - _req: &Request<'_>, + req: &Request<'_>, parent: u64, name: &OsStr, mode: u32, @@ -181,33 +246,51 @@ impl Filesystem for AyaFS { "Filesystem::mknod(parent: {}, name: {:?}, mode: {}, umask: {})", parent, name, mode, _umask ); - if let Some(inode) = self.get_inode(parent as usize) { - if inode.is_dir() { - let name = name.as_bytes(); - if name.len() <= 255 { - let permissions = (mode & 0o777) as u16; - if let Some(inode_index) = self.create_inode( - permissions, - InodeMode::IFREG, - get_current_uid(), - get_current_gid(), + + if let Some(parent_inode) = self.get_inode(parent as usize) { + if check_access( + req.uid(), + req.gid(), + parent_inode.uid, + parent_inode.gid, + parent_inode.mode, + W_OK + ) { + if parent_inode.is_dir() { + let parent_inode = parent_inode.clone(); + // 如果已经存在, 返回 already exists + if self.lookup_name(&parent_inode, name).is_ok() { + reply.error(EEXIST); + return; + } + // 文件名长度超过 255, 返回 filename too long + if name.len() > 255 { + reply.error(ENAMETOOLONG); + return; + } + + let mode = mode as u16; + if let Some((inode_index, inode)) = self.create_file( + mode, + req.uid(), + req.gid(), 0, ) { - let inode = self.get_inode(inode_index).unwrap(); - let file_attr = make_fileattr(inode_index, inode); + let file_attr = to_fileattr(inode_index, inode); // TODO 把 inode 挂到 parent 下 + self.update_inode(parent as usize, parent_inode); + // 前面 clone 了, 这里写回 reply.entry(&TTL, &file_attr, 0); } else { // create_inode 失败 -> no enough space reply.error(ENOSPC); } } else { - // name 长度超过 255 -> File name too long - reply.error(ENAMETOOLONG); + // parent 不是 IFDIR -> Not a directory + reply.error(ENOTDIR); } } else { - // parent 不是 IFDIR -> Not a directory - reply.error(ENOTDIR); + reply.error(EACCES); } } else { // parent 不存在 -> No such file or directory @@ -217,40 +300,57 @@ impl Filesystem for AyaFS { fn mkdir( &mut self, - _req: &Request<'_>, + req: &Request<'_>, parent: u64, name: &OsStr, mode: u32, _umask: u32, // umask 应该也是用不到的 reply: ReplyEntry, ) { - if let Some(inode) = self.get_inode(parent as usize) { - if inode.is_dir() { - let name = name.as_bytes(); - if name.len() <= 255 { - let permissions = (mode & 0o777) as u16; - if let Some(inode_index) = self.create_inode( - permissions, - InodeMode::IFDIR, - get_current_uid(), - get_current_gid(), + if let Some(parent_inode) = self.get_inode(parent as usize) { + if check_access( + req.uid(), + req.gid(), + parent_inode.uid, + parent_inode.gid, + parent_inode.mode, + W_OK + ) { + if parent_inode.is_dir() { + let parent_inode = parent_inode.clone(); + // 如果已经存在, 返回 already exists + if self.lookup_name(&parent_inode, name).is_ok() { + reply.error(EEXIST); + return; + } + // 文件名长度超过 255, 返回 filename too long + if name.len() > 255 { + reply.error(ENAMETOOLONG); + return; + } + + let mode = mode as u16; + if let Some((inode_index, inode)) = self.create_directory( + mode, + req.uid(), + req.gid(), 0, ) { - let inode = self.get_inode(inode_index).unwrap(); - let file_attr = make_fileattr(inode_index, inode); + let file_attr = to_fileattr(inode_index, inode); // TODO 把 inode 挂到 parent 下 + self.update_inode(parent as usize, parent_inode); + // 前面 clone 了, 这里写回 reply.entry(&TTL, &file_attr, 0); } else { // create_inode 失败 -> no enough space reply.error(ENOSPC); } } else { - // name 长度超过 255 -> File name too long - reply.error(ENAMETOOLONG); + // parent 不是 IFDIR -> Not a directory + reply.error(ENOTDIR); } } else { - // parent 不是 IFDIR -> Not a directory - reply.error(ENOTDIR); + reply.error(EACCES); } } else { // parent 不存在 -> No such file or directory @@ -259,13 +359,28 @@ impl Filesystem for AyaFS { } fn unlink(&mut self, _req: &Request<'_>, parent: u64, name: &OsStr, reply: ReplyEmpty) { - debug!( - "unlink(parent: {:#x?}, name: {:?})", - parent, name, - ); + debug!("unlink(parent: {:#x?}, name: {:?})", parent, name,); if let Some(inode) = self.get_inode_mut(parent as usize) { - // TODO 找到这个 inode 并且删掉 - reply.ok(); + if inode.is_file() { + // TODO 找到这个 inode 并且删掉 + reply.ok(); + } else { + reply.error(ENOENT); + } + } else { + reply.error(ENOENT); + } + } + + fn rmdir(&mut self, _req: &Request<'_>, parent: u64, name: &OsStr, reply: ReplyEmpty) { + debug!("rmdir(parent: {:#x?}, name: {:?}", parent, name,); + if let Some(inode) = self.get_inode_mut(parent as usize) { + if inode.is_file() { + // TODO 找到这个 inode 并且删掉 + reply.ok(); + } else { + reply.error(ENOTDIR); + } } else { reply.error(ENOENT); } @@ -285,45 +400,78 @@ impl Filesystem for AyaFS { todo!() } + fn opendir(&mut self, _req: &Request<'_>, ino: u64, flags: i32, reply: ReplyOpen) { + debug!("OPENDIR ino {:#x} flags {}", ino, flags); + reply.opened(1, 0); + } + fn readdir( &mut self, - _req: &Request<'_>, + req: &Request<'_>, ino: u64, - _fh: u64, + _fh: u64, // 如果 invoke 它的 opendir 里有指定 fh, 那么这里会收到 fh 参数 offset: i64, mut reply: ReplyDirectory, ) { if let Some(inode) = self.get_inode(ino as usize) { + if inode.is_dir() { + if check_access( + req.uid(), + req.gid(), + inode.uid, + inode.gid, + inode.mode, + R_OK, + ) { + let inode = inode.clone(); + let mut entry_index = offset as u32; + + while let Some(dir_entry) = self.get_direntry(&inode, entry_index) { + debug!("reading inode {:#x} index {}", ino, entry_index); + if entry_index >= inode.size { + break; + } + // 最开头应该添加 `.` 和 `..`, 但这件事应该在 mkdir 的时候就在 inode 里加好 + // reply.add 返回 true 说明 buffer 已满, 停止累加 + if reply.add( + dir_entry.inode as u64, + entry_index as i64, + to_filetype(dir_entry.file_type).expect("file type should not be 0x0!"), + dir_entry.name(), + ) { + break; + } + entry_index += 1; + } + reply.ok(); + } else { + reply.error(EACCES); + } + } else { + reply.error(ENOTDIR); + } } else { reply.error(ENOENT); } - - // if ino != 1 { - // reply.error(ENOENT); - // return; - // } - // - // let entries = vec![ - // (1, FileType::Directory, "."), - // (1, FileType::Directory, ".."), - // (2, FileType::RegularFile, "hello.txt"), - // ]; - // - // for (i, entry) in entries.into_iter().enumerate().skip(offset as usize) { - // // i + 1 means the index of the next entry - // if reply.add(entry.0, (i + 1) as i64, entry.1, entry.2) { - // break; - // } - // } - // reply.ok(); } - fn access(&mut self, _req: &Request<'_>, ino: u64, mask: i32, reply: ReplyEmpty) { + fn access(&mut self, req: &Request<'_>, ino: u64, mask: i32, reply: ReplyEmpty) { + // mask: + // - 要么是 libc::F_OK (aka 0), 检查文件是否存在 + // - 要么是 libc::R_OK,libc::W_OK,libc::X_OK (aka 4/2/1) 构成的 bitmask, 检查是否有对应权限 debug!("Filesystem::getattr(ino: {}, mask: {})", ino, mask); + if let Some(inode) = self.get_inode(ino as usize) { - reply.ok() + if mask == libc::F_OK // 只要检查是否存在 + || check_access(req.uid(), req.gid(), inode.uid, inode.gid, inode.mode, mask) + // 需要检查 rwx 权限 + { + reply.ok(); + } else { + reply.error(EACCES); + } } else { - reply.error(ENOENT) + reply.error(ENOENT); } } -- cgit v1.2.3-70-g09d2