use crate::disk::inode::InodeMode; 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, 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::time::SystemTime; 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!("`init()"); Ok(()) } fn destroy(&mut self) { debug!("destroy()"); // TODO 写回 } fn lookup(&mut self, req: &Request<'_>, parent: u64, name: &OsStr, reply: ReplyEntry) { if name.len() > 255 { reply.error(ENAMETOOLONG); return; } 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) { reply.attr(&TTL, &to_fileattr(ino as usize, inode)); } else { reply.error(ENOENT); } } fn setattr( &mut self, req: &Request<'_>, ino: u64, mode: Option, uid: Option, gid: Option, size: Option, // TODO 当 setattr 被 ftruncate invoke 时会设置 size atime: Option, mtime: 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) { // 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, }; 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, }; inode.ctime = current_time; } } reply.attr(&TTL, &to_fileattr(ino as usize, inode)); return; } else { reply.error(ENOENT); } } fn readlink(&mut self, _req: &Request<'_>, ino: u64, reply: ReplyData) { debug!("[Not Implemented] readlink(ino: {})", ino); reply.error(ENOSYS); } fn mknod( &mut self, req: &Request<'_>, parent: u64, name: &OsStr, mode: u32, _umask: u32, // umask 是用不到的 _rdev: u32, // the device number (only valid if created file is a device) reply: ReplyEntry, ) { debug!( "Filesystem::mknod(parent: {}, name: {:?}, mode: {}, umask: {})", parent, name, mode, _umask ); 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 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 { // parent 不是 IFDIR -> Not a directory reply.error(ENOTDIR); } } else { reply.error(EACCES); } } else { // parent 不存在 -> No such file or directory reply.error(ENOENT); } } fn mkdir( &mut self, req: &Request<'_>, parent: u64, name: &OsStr, mode: u32, _umask: u32, // umask 应该也是用不到的 reply: ReplyEntry, ) { 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 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 { // parent 不是 IFDIR -> Not a directory reply.error(ENOTDIR); } } else { reply.error(EACCES); } } else { // parent 不存在 -> No such file or directory reply.error(ENOENT); } } fn unlink(&mut self, _req: &Request<'_>, parent: u64, name: &OsStr, reply: ReplyEmpty) { debug!("unlink(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(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); } } fn read( &mut self, _req: &Request<'_>, ino: u64, _fh: u64, offset: i64, _size: u32, _flags: i32, _lock_owner: Option, reply: ReplyData, ) { 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<'_>, ino: 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); } } 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) { 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); } } fn lseek( &mut self, _req: &Request<'_>, ino: u64, fh: u64, offset: i64, whence: i32, reply: ReplyLseek, ) { reply.error(ENOSYS); } }