use crate::disk::inode::InodeMode; use crate::utils::permissions::get_groups; use crate::utils::permissions::{check_access, clear_suid_sgid}; use crate::utils::{from_systime, time_now, to_fileattr, to_filetype}; use crate::{AyaFS, TTL}; use fuser::TimeOrNow::{Now, SpecificTime}; use fuser::{ Filesystem, KernelConfig, ReplyAttr, ReplyData, ReplyDirectory, ReplyEmpty, ReplyEntry, ReplyLseek, ReplyOpen, ReplyWrite, Request, TimeOrNow, }; use libc::{c_int, EACCES, EBADF, EEXIST, EINVAL, EIO, EISDIR, ENAMETOOLONG, ENOENT, ENOSPC, ENOSYS, ENOTDIR, ENOTEMPTY, EPERM, O_ACCMODE, O_RDONLY, O_RDWR, O_WRONLY, 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 crate::block_device::BLOCK_SIZE; use crate::disk::block::DataBlock; 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, &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) { 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, ) { reply.error(EACCES); return; } // parent 不是 IFDIR -> Not a directory if !parent_inode.is_dir() { reply.error(ENOTDIR); return; } // 文件名长度超过 255, 返回 filename too long if name.len() > 255 { reply.error(ENAMETOOLONG); return; } let mut parent_inode = parent_inode.clone(); // 如果已经存在, 返回 already exists if self .lookup_name(parent as usize, &parent_inode, name) .is_ok() { reply.error(EEXIST); return; } let mode = mode as u16; if let Some((child_inode_index, child_inode)) = self.create_file(mode, req.uid(), req.gid(), 0) { let child_inode = child_inode.clone(); let file_attr = to_fileattr(child_inode_index, &child_inode); if let Err(err_code) = self.add_direntry( parent as usize, &mut parent_inode, child_inode_index, name, child_inode.mode.into(), ) { reply.error(err_code); return; } 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 不存在 -> 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) { // 无权限创建 -> EACCES if !check_access( req.uid(), req.gid(), parent_inode.uid, parent_inode.gid, parent_inode.mode, W_OK, ) { reply.error(EACCES); return; } // parent 不是 IFDIR -> Not a directory if !parent_inode.is_dir() { reply.error(ENOTDIR); return; } // 文件名长度超过 255 -> filename too long if name.len() > 255 { reply.error(ENAMETOOLONG); return; } let mut parent_inode = parent_inode.clone(); // 已经存在 -> File exists if self .lookup_name(parent as usize, &parent_inode, name) .is_ok() { reply.error(EEXIST); return; } let mode = mode as u16; if let Some((child_inode_index, child_inode)) = self.create_directory(mode, req.uid(), req.gid(), 0, Some(parent as usize)) { let child_inode = child_inode.clone(); let file_attr = to_fileattr(child_inode_index, &child_inode); if let Err(err_code) = self.add_direntry( parent as usize, &mut parent_inode, child_inode_index, name, child_inode.mode.into(), ) { reply.error(err_code); return; } 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 不存在 -> 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(parent_inode) = self.get_inode(parent as usize) { // 无权限删除 -> EACCES if !check_access( req.uid(), req.gid(), parent_inode.uid, parent_inode.gid, parent_inode.mode, W_OK, ) { reply.error(EACCES); return; } // parent 不是 dir -> ENOTDIR if !parent_inode.is_dir() { reply.error(ENOTDIR); return; } // 文件名长度超过 255 -> filename too long if name.len() > 255 { reply.error(ENAMETOOLONG); return; } let mut parent_inode = parent_inode.clone(); // 不存在 -> No such file or directory if let Ok((inode_index, entry_index, mut inode)) = self.lookup_name(parent as usize, &parent_inode, name) { let inode_index = inode_index as usize; // 要删除的 entry 是目录, 不能用 unlink if inode.is_dir() { reply.error(EISDIR); return; } inode.n_links -= 1; if inode.n_links == 0 { // n_links == 0 -> 整个 inode 都要删除掉 match self.remove_file(inode_index) { Ok(flag) => debug!(" unlink {}", flag), Err(err_code) => { reply.error(err_code); return; } } } else { // n_links > 0 -> 只是移除了一个 hard link, 将改动写回 self.update_inode(inode_index, inode); } // 删除 dir entry if let Err(err_code) = self.remove_direntry(parent as usize, &mut parent_inode, name, entry_index) { reply.error(err_code); return; } 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(parent_inode) = self.get_inode(parent as usize) { // 无权限删除 -> EACCES if !check_access( req.uid(), req.gid(), parent_inode.uid, parent_inode.gid, parent_inode.mode, W_OK, ) { reply.error(EACCES); return; } // parent 不是 dir -> ENOTDIR if !parent_inode.is_dir() { reply.error(ENOTDIR); return; } // 文件名长度超过 255 -> filename too long if name.len() > 255 { reply.error(ENAMETOOLONG); return; } let mut parent_inode = parent_inode.clone(); // 不存在 -> No such file or directory if let Ok((inode_index, entry_index, inode)) = self.lookup_name(parent as usize, &parent_inode, name) { let inode_index = inode_index as usize; // 要删除的 entry 是一般文件, 不用 rmdir if inode.is_file() { reply.error(ENOTDIR); return; } // 一定有 . 和 .. 所以 size == 2 就是空目录 // 目录非空 -> ENOTEMPTY if inode.size > 2 { reply.error(ENOTEMPTY); return; } // directory 没有 hard link, 删了就是删了 match self.remove_dir(inode_index) { Ok(flag) => debug!(" rmdir {}", flag), Err(err_code) => { reply.error(err_code); return; } } // 删除 dir entry if let Err(err_code) = self.remove_direntry(parent as usize, &mut parent_inode, name, entry_index) { reply.error(err_code); return; } reply.ok(); } else { reply.error(ENOENT); } } else { reply.error(ENOENT); } } fn open(&mut self, req: &Request<'_>, ino: u64, flags: i32, reply: ReplyOpen) { debug!("open(ino: {:#x?}, flags: {:#x?})", ino, flags); let (access_mask, read, write) = match flags & O_ACCMODE { O_RDONLY => { // Behavior is undefined, but most filesystems return EACCES if flags & libc::O_TRUNC != 0 { reply.error(libc::EACCES); return; } (R_OK, true, false) } O_WRONLY => (W_OK, false, true), O_RDWR => (R_OK | W_OK, true, true), _ => { // flag 非法 reply.error(EINVAL); return; } }; match self.get_inode(ino as usize) { Some(inode) => { if !check_access( req.uid(), req.gid(), inode.uid, inode.gid, inode.mode, access_mask, ) { reply.error(EACCES); return; } let fd = self.allocate_file_descriptor(ino as usize, read, write); reply.opened(fd, 0); } None => { reply.error(ENOENT); } } } // read [offset, offset + size) // - EOF < offset + size -> return EOF - offset // - EOF > offset + size -> return size fn read( &mut self, req: &Request<'_>, ino: u64, fh: u64, offset: i64, size: u32, _flags: i32, _lock_owner: Option, // 用不到! reply: ReplyData, ) { assert_eq!(self.file_handle_map.get(&fh).unwrap().0, ino as usize); if let Some(inode) = self.get_inode(ino as usize) { if inode.is_dir() { reply.error(EISDIR); return; } if !check_access(req.uid(), req.gid(), inode.uid, inode.gid, inode.mode, R_OK) { reply.error(EACCES); return; } debug!("reading inode {:#x} (offset {} size {})", ino, offset, size); if offset as u32 >= inode.size { // offset 在 EOF 后面, 直接返回一个 0 长度的空 buffer reply.data(&Vec::new()); return; } // let read_length = size.min(inode.size.saturating_sub(offset as u32)) as usize; // 这和下面那个是等同的但是不利于让人看懂…… let read_length = if offset as u32 + size <= inode.size { size // 没有越过 EOF, 读取 size 个 byte } else { inode.size - offset as u32 // 越过了 EOF, 读取 inode.size - offset 个 byte } as usize; let mut read_buffer = vec![0u8; read_length]; let mut read_ptr = 0usize; let inode = inode.clone(); while read_ptr < read_length { let current_point = offset as usize + read_ptr; let current_block_index = current_point / BLOCK_SIZE; let current_offset = current_point % BLOCK_SIZE; let read_length_within_block = if BLOCK_SIZE - current_offset < read_length - read_ptr { BLOCK_SIZE - current_offset // 可以读到 block 最后 } else { read_length - read_ptr // 读到中途会停下来 }; if let Some(block) = self.access_block::(&inode, current_block_index) { (&mut read_buffer[read_ptr .. read_ptr + read_length_within_block]) .copy_from_slice(&block.block.0[current_offset .. current_offset + read_length_within_block]); read_ptr += read_length_within_block; } else { reply.error(EIO); return; } } reply.data(&read_buffer); } } // 写了多少就返回多少 fn write( &mut self, req: &Request<'_>, ino: u64, fh: u64, offset: i64, data: &[u8], write_flags: u32, flags: i32, _lock_owner: Option, reply: ReplyWrite, ) { assert_eq!(self.file_handle_map.get(&fh).unwrap().0, ino as usize); if let Some(inode) = self.get_inode(ino as usize) { if inode.is_dir() { reply.error(EISDIR); return; } if !check_access(req.uid(), req.gid(), inode.uid, inode.gid, inode.mode, W_OK) { reply.error(EACCES); return; } debug!("writing inode {:#x} (offset {} size {})", ino, offset, data.len()); let write_length = data.len(); let mut write_ptr = 0usize; let mut inode = inode.clone(); while write_ptr < write_length { let current_point = offset as usize + write_ptr; let current_block_index = current_point / BLOCK_SIZE; let current_offset = current_point % BLOCK_SIZE; let write_length_within_block = if BLOCK_SIZE - current_offset < write_length - write_ptr { BLOCK_SIZE - current_offset // 可以写满 block } else { write_length - write_ptr // 写完 buffer 就停下来 }; // 当前块已分配, 直接往里写 if let Some(block) = self.access_block_mut::(&inode, current_block_index) { debug!("writing in block {} within inode", current_block_index); (block.block.0[ .. current_offset + write_length_within_block]) .copy_from_slice(&data[write_ptr .. write_ptr + write_length_within_block]); write_ptr += write_length_within_block; } else { // 当前块未分配,尝试分配 if let Some((block_index, block_index_within_inode)) = self.allocate_block_for(&mut inode) { debug!("allocating block {} within inode, block index is {}", block_index_within_inode, block_index); // 能分配, 往里写 let block = self.access_block_mut::(&inode, block_index_within_inode).unwrap(); (block.block.0[ .. current_offset + write_length_within_block]) .copy_from_slice(&data[write_ptr .. write_ptr + write_length_within_block]); write_ptr += write_length_within_block; } else { // 分配不了, 没空间了 break; } } } inode.size = inode.size.max(offset as u32 + write_length as u32); self.update_inode(ino as usize, inode); reply.written(write_length as u32); } else { reply.error(ENOENT); } } fn release( &mut self, _req: &Request<'_>, ino: u64, fh: u64, _flags: i32, _lock_owner: Option, _flush: bool, reply: ReplyEmpty, ) { debug!("release(ino: {:#x?}, fh: {}", ino, fh); if self.file_handle_map.contains_key(&fh) { self.file_handle_map.remove(&fh); reply.ok(); } else { reply.error(EBADF); } } fn opendir(&mut self, req: &Request<'_>, ino: u64, flags: i32, reply: ReplyOpen) { debug!("opendir(ino: {:#x?}, flags: {:#x?})", ino, flags); let (access_mask, read, write) = match flags & O_ACCMODE { O_RDONLY => { // Behavior is undefined, but most filesystems return EACCES if flags & libc::O_TRUNC != 0 { reply.error(libc::EACCES); return; } (R_OK, true, false) } O_WRONLY => (W_OK, false, true), O_RDWR => (R_OK | W_OK, true, true), _ => { // flag 非法 reply.error(EINVAL); return; } }; match self.get_inode(ino as usize) { Some(inode) => { if !check_access( req.uid(), req.gid(), inode.uid, inode.gid, inode.mode, access_mask, ) { reply.error(EACCES); return; } let fd = self.allocate_file_descriptor(ino as usize, read, write); reply.opened(fd, 0); } None => { reply.error(ENOENT); } } } fn readdir( &mut self, req: &Request<'_>, ino: u64, fh: u64, // 如果 invoke 它的 opendir 里有指定 fh, 那么这里会收到 fh 参数 offset: i64, mut reply: ReplyDirectory, ) { assert_eq!(self.file_handle_map.get(&fh).unwrap().0, ino as usize); if let Some(inode) = self.get_inode(ino as usize) { if !inode.is_dir() { reply.error(ENOTDIR); return; } if !check_access(req.uid(), req.gid(), inode.uid, inode.gid, inode.mode, R_OK) { reply.error(EACCES); return; } debug!("reading dir entries from inode {:#x} with offset {}", ino, offset); let inode = inode.clone(); self.load_direntry_map(ino as usize, &inode).unwrap(); for (entry_index, (name, dir_entry)) in self .dir_entry_map .get(&(ino as usize)) .unwrap() .iter() .enumerate() .skip(offset as usize) { debug!(" entry {} from inode {:#x} with name {:?}", entry_index, dir_entry.inode, name); if reply.add( dir_entry.inode as u64, entry_index as i64 + 1, to_filetype(dir_entry.file_type).expect("not 0x0!"), name ) { break; } } reply.ok(); } else { reply.error(ENOENT); } } fn releasedir( &mut self, _req: &Request<'_>, ino: u64, fh: u64, _flags: i32, reply: ReplyEmpty, ) { debug!("releasedir(ino: {:#x?}, fh: {}", ino, fh); if self.file_handle_map.contains_key(&fh) { self.file_handle_map.remove(&fh); reply.ok(); } else { reply.error(EBADF); } } 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::access(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); } } }