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/disk/allocation.rs | 2 +- src/disk/block.rs | 119 +++++----- src/disk/inode.rs | 41 +++- src/filesystem/trait_impl.rs | 518 +++++++++++++++++++++++++++---------------- src/main.rs | 14 +- src/memory/cached_block.rs | 85 +------ src/memory/cached_inode.rs | 71 +++++- src/memory/inode_iter.rs | 233 +++++++++++++++++++ src/memory/mod.rs | 1 + src/utils/constants.rs | 1 + src/utils/mod.rs | 61 ++++- src/utils/permissions.rs | 62 ++++++ 12 files changed, 865 insertions(+), 343 deletions(-) create mode 100644 src/memory/inode_iter.rs create mode 100644 src/utils/constants.rs create mode 100644 src/utils/permissions.rs diff --git a/src/disk/allocation.rs b/src/disk/allocation.rs index 5643dd3..bde2014 100644 --- a/src/disk/allocation.rs +++ b/src/disk/allocation.rs @@ -1,6 +1,6 @@ use crate::disk::block::{DataBlock, DoubleIndirectBlock, IndirectBlock, TripleIndirectBlock}; use crate::disk::inode::Inode; -use crate::memory::cached_block::{convert, convert_mut}; +use crate::memory::cached_block::convert; use crate::AyaFS; impl AyaFS { diff --git a/src/disk/block.rs b/src/disk/block.rs index d287ee6..0058b3e 100644 --- a/src/disk/block.rs +++ b/src/disk/block.rs @@ -1,7 +1,10 @@ use crate::disk::inode::Inode; +use std::ffi::{OsStr, OsString}; +use std::os::unix::ffi::OsStrExt; pub trait Block: Default + Clone {} +#[repr(C)] #[derive(Clone)] pub struct DataBlock(pub(crate) [u8; 4096]); @@ -13,6 +16,7 @@ impl Default for DataBlock { impl Block for DataBlock {} +#[repr(C)] #[derive(Clone)] pub struct InodeBlock { pub(crate) inodes: [Inode; 16], @@ -47,53 +51,70 @@ impl Block for InodeBlock {} const FULL_MAP: u32 = 0b111_111_111_111_111; +#[repr(C)] #[derive(Clone)] -pub struct DirectoryBlock { - entries: [[u8; 256]; 15], - inode_ids: [usize; 15], - occupancy_map: u32, - reserved: [u8; 132], +pub struct DirectoryEntry { + pub inode: u32, + pub record_len: u16, + pub name_len: u8, + pub file_type: u8, + pub name: [u8; 256], } -impl Default for DirectoryBlock { +impl DirectoryEntry { + pub(crate) fn name(&self) -> OsString { + let name = &self.name[0..self.name_len as usize]; + OsStr::from_bytes(name).to_os_string() + } +} + +impl Default for DirectoryEntry { fn default() -> Self { Self { - entries: [[0; 256]; 15], - inode_ids: [0; 15], - occupancy_map: 0, - reserved: [0xFF; 132], + inode: 0, + record_len: 0, + name_len: 0, + file_type: 0x0, + name: [0; 256], } } } -impl Block for DirectoryBlock {} - -impl DirectoryBlock { - fn vacant(&self) -> bool { - self.occupancy_map & FULL_MAP != FULL_MAP - } - - fn first_free(&self) -> Option { - todo!() - } - - fn mark_busy(&mut self, entry_id: usize) { - todo!() - } +#[repr(C)] +#[derive(Clone)] +pub struct DirectoryBlock { + pub entries: [DirectoryEntry; 15], + reserved: [u8; 136], +} - /// 需要判断 entry_name.len() <= 255 - pub fn write_entry(&mut self, entry_name: &[u8], entry_inode_id: usize) -> Option { - if let Some(entry_id) = self.first_free() { - self.mark_busy(entry_id); - self.entries[entry_id].copy_from_slice(entry_name); - self.inode_ids[entry_id] = entry_inode_id; - Some(entry_id) - } else { - None +impl Default for DirectoryBlock { + fn default() -> Self { + Self { + entries: [ + DirectoryEntry::default(), + DirectoryEntry::default(), + DirectoryEntry::default(), + DirectoryEntry::default(), + DirectoryEntry::default(), + DirectoryEntry::default(), + DirectoryEntry::default(), + DirectoryEntry::default(), + DirectoryEntry::default(), + DirectoryEntry::default(), + DirectoryEntry::default(), + DirectoryEntry::default(), + DirectoryEntry::default(), + DirectoryEntry::default(), + DirectoryEntry::default(), + ], + reserved: [0xFF; 136], } } } +impl Block for DirectoryBlock {} + +#[repr(C)] #[derive(Clone)] pub struct IndirectBlock { pub entries: [u32; 1024], @@ -107,16 +128,7 @@ impl Default for IndirectBlock { impl Block for IndirectBlock {} -impl IndirectBlock { - pub fn full(&self) -> bool { - todo!() - } - - pub fn allocate(&mut self) -> Option { - todo!() - } -} - +#[repr(C)] #[derive(Clone)] pub struct DoubleIndirectBlock { pub indirect: [u32; 1024], @@ -132,16 +144,7 @@ impl Default for DoubleIndirectBlock { impl Block for DoubleIndirectBlock {} -impl DoubleIndirectBlock { - pub fn full(&self) -> bool { - todo!() - } - - pub fn allocate(&mut self) -> Option { - todo!() - } -} - +#[repr(C)] #[derive(Clone)] pub struct TripleIndirectBlock { pub double_indirect: [u32; 1024], @@ -156,13 +159,3 @@ impl Default for TripleIndirectBlock { } impl Block for TripleIndirectBlock {} - -impl TripleIndirectBlock { - pub fn full(&self) -> bool { - todo!() - } - - pub fn allocate(&mut self) -> Option { - todo!() - } -} diff --git a/src/disk/inode.rs b/src/disk/inode.rs index 1095aca..256d2f5 100644 --- a/src/disk/inode.rs +++ b/src/disk/inode.rs @@ -1,11 +1,11 @@ use bitflags::bitflags; use fuser::{FileAttr, FileType}; -use std::fs::File; +use crate::utils; -const DIRECT_NUMBER: usize = 15; +pub const DIRECT_NUMBER: usize = 15; #[derive(Debug, Clone, Copy)] -pub struct InodeMode(u16); +pub struct InodeMode(pub u16); bitflags! { impl InodeMode: u16 { @@ -33,6 +33,34 @@ bitflags! { } impl InodeMode { + pub(crate) fn exec_other(&self) -> bool { + self.0 & Self::IXOTH.0 != 0 + } + pub(crate) fn write_other(&self) -> bool { + self.0 & Self::IWOTH.0 != 0 + } + pub(crate) fn read_other(&self) -> bool { + self.0 & Self::IROTH.0 != 0 + } + pub(crate) fn exec_group(&self) -> bool { + self.0 & Self::IXGRP.0 != 0 + } + pub(crate) fn write_group(&self) -> bool { + self.0 & Self::IWGRP.0 != 0 + } + pub(crate) fn read_group(&self) -> bool { + self.0 & Self::IRGRP.0 != 0 + } + pub(crate) fn exec_user(&self) -> bool { + self.0 & Self::IXUSR.0 != 0 + } + pub(crate) fn write_user(&self) -> bool { + self.0 & Self::IWUSR.0 != 0 + } + pub(crate) fn read_user(&self) -> bool { + self.0 & Self::IRUSR.0 != 0 + } + pub(crate) fn perm(&self) -> u16 { self.0 & 0x0FFF } @@ -69,6 +97,13 @@ impl From for FileType { } } +impl From for u8 { + fn from(value: InodeMode) -> Self { + utils::from_filetype(value.into()) + } +} + + /// Pretty much the same with ext2, with minor changes: /// - removed OS dependent attributes (osd1 & osd2) /// - removed i_faddr since fragmentation is not supported 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); } } diff --git a/src/main.rs b/src/main.rs index 2df0a10..e0bfa2e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -30,7 +30,7 @@ struct Args { allow_root: bool, } -const TTL: Duration = Duration::from_secs(1); +const TTL: Duration = Duration::new(0, 0); const INODE_PER_BLOCK: usize = BLOCK_SIZE / INODE_SIZE; /// The design of MyFS is rather simple: @@ -111,17 +111,11 @@ impl AyaFS { + inode_bitmap_block_number + inode_block_number + 1, - cached_inodes: BlockCache::new(device.clone(), 256), - cached_blocks: BlockCache::new(device.clone(), 256), + cached_inodes: BlockCache::new(device.clone(), 1024), + cached_blocks: BlockCache::new(device.clone(), 8192), }; - fs.create_inode( - 0o755, - InodeMode::IFDIR, - get_current_uid(), - get_current_gid(), - 0, - ); + fs.create_directory(0o755, get_current_uid(), get_current_gid(), 0); fs } diff --git a/src/memory/cached_block.rs b/src/memory/cached_block.rs index eb59a4b..77cdf61 100644 --- a/src/memory/cached_block.rs +++ b/src/memory/cached_block.rs @@ -1,5 +1,6 @@ use crate::block_device::{BlockDevice, BLOCK_SIZE}; use crate::disk::block::Block; +use crate::disk::inode::Inode; use crate::AyaFS; use and_then_some::BoolExt; use log::debug; @@ -70,17 +71,18 @@ impl BlockCache { /// 从 LRU cache 里获取一个 block 的引用, 如果没有在 cache 中会加载. pub(crate) fn get_block(&mut self, index: usize) -> Option<&CachedBlock> { - self.load_block(index) - .and_then(|| self.cache.get(&index).map(convert::)) + if !self.cache.contains(&index) { + self.load_block(index); + } + self.cache.get(&index).map(convert::) } /// 从 LRU cache 里获取一个 block 的可变引用, 如果没有在 cache 中会加载. pub(crate) fn get_block_mut(&mut self, index: usize) -> Option<&mut CachedBlock> { - debug!("Blockcache get block mut"); - self.load_block(index).and_then(|| { - debug!("block loaded"); - self.cache.get_mut(&index).map(convert_mut::) - }) + if !self.cache.contains(&index) { + self.load_block(index); + } + self.cache.get_mut(&index).map(convert_mut::) } /// 从 LRU cache 中读取一个 block 的引用, *不会* 影响 LRU cache 的结构, 如果没有在 cache 中不会加载. @@ -145,73 +147,4 @@ impl AyaFS { pub(crate) fn update_block(&mut self, block: CachedBlock) -> bool { self.cached_blocks.update_block(block) } - - // pub(crate) fn update_block(&mut self, block: CachedBlock) -> bool { - // if self.cached_blocks.contains(&block.index) { - // let data_block = convert::(&block).clone(); - // self.cached_blocks.push(block.index, data_block); - // true - // } else { - // false - // } - // } - // - // /// 从 LRU cache 里获取一个 block 的引用, 如果没有在 cache 中会加载. - // pub(crate) fn get_block(&mut self, index: usize) -> Option<&CachedBlock> { - // self.load_block(index) - // .and_then(|| self.cached_blocks.get(&index).map(convert::)) - // } - // - // /// 从 LRU cache 里获取一个 block 的可变引用, 如果没有在 cache 中会加载. - // pub(crate) fn get_block_mut(&mut self, index: usize) -> Option<&mut CachedBlock> { - // self.load_block(index).and_then(|| { - // self.cached_blocks - // .get_mut(&index) - // .map(convert_mut::) - // }) - // } - // - // /// 从 LRU cache 中读取一个 block 的引用, *不会* 影响 LRU cache 的结构, 如果没有在 cache 中不会加载. - // pub(crate) fn peek_block(&self, index: usize) -> Option<&CachedBlock> { - // self.cached_blocks.peek(&index).map(convert::) - // } - // - // /// 从 LRU cache 中读取一个 block 的可变引用, *不会* 影响 LRU cache 的结构, 如果没有在 cache 中不会加载. - // pub(crate) fn peek_block_mut(&mut self, index: usize) -> Option<&mut CachedBlock> { - // self.cached_blocks.peek_mut(&index).map(convert_mut::) - // } - // - // pub(crate) fn load_block(&mut self, index: usize) -> bool { - // // 先看这个 block 是不是 valid, 不 valid 直接返回 false. - // if !self.data_bitmap.query(index) { - // self.cached_blocks.pop(&index); - // // deallocate 时只更新 bitmap 没有动 cache, lazy 地驱逐 cache 中的无效 entry. - // return false; - // } - // - // // 接下来这个 block 一定 valid. 如果 cache 里没有这个 block 就把它 load 到 cache 里 - // if self.cached_blocks.contains(&index) == false { - // let block = DataBlock::default(); - // let buffer = unsafe { - // std::slice::from_raw_parts_mut(&block as *const DataBlock as *mut u8, BLOCK_SIZE) - // }; - // self.device.read(index, buffer); - // let cached_block = CachedBlock { - // block, - // index, - // dirty: false, - // }; - // if let Some((old_index, old_block)) = self.cached_blocks.push(index, cached_block) { - // assert_ne!(old_index, index); // 只有 block 不在 cache 里的时候才会插入 - // if old_block.dirty { - // let old_block_ptr = &old_block.block as *const DataBlock as *mut u8; - // let old_block_buffer = - // unsafe { std::slice::from_raw_parts(old_block_ptr, BLOCK_SIZE) }; - // self.device.write(old_index, old_block_buffer); - // } - // } - // } - // - // true - // } } diff --git a/src/memory/cached_inode.rs b/src/memory/cached_inode.rs index dd5e5c3..be87850 100644 --- a/src/memory/cached_inode.rs +++ b/src/memory/cached_inode.rs @@ -1,10 +1,76 @@ -use crate::disk::block::InodeBlock; +use crate::disk::block::{DirectoryBlock, DirectoryEntry, InodeBlock}; use crate::disk::inode::{Inode, InodeMode, INODE_SIZE}; +use crate::utils::from_filetype; use crate::{utils, AyaFS}; use and_then_some::BoolExt; +use fuser::FileType; use log::debug; impl AyaFS { + pub(crate) fn create_file( + &mut self, + permissions: u16, + uid: u32, + gid: u32, + flags: u32, + ) -> Option<(usize, &Inode)> { + self.inode_bitmap.allocate().map(|inode_index| { + self.get_inode_mut(inode_index).map(|inode| { + *inode = Inode::file(permissions, uid, gid, utils::time_now(), flags, 0, 0, 0); + }); + (inode_index, self.get_inode(inode_index).unwrap()) + }) + } + + pub(crate) fn create_directory( + &mut self, + permissions: u16, + uid: u32, + gid: u32, + flags: u32, + ) -> Option<(usize, &Inode)> { + self.inode_bitmap.allocate().map(|inode_index| { + // 创建 Inode + let mut new_inode = + Inode::directory(permissions, uid, gid, utils::time_now(), flags, 0, 0, 0); + // 分配第一个 direct block + new_inode.direct[0] = self.allocate_block_for(&mut new_inode).unwrap(); + new_inode.size = 2; + // 在 direct block 里分配 . 和 .. + if let Some(directory_block) = + self.get_block_mut::(new_inode.direct[0] as usize) + { + let dot = '.'.to_ascii_lowercase() as u8; + // add dot entry + directory_block.block.entries[0] = DirectoryEntry { + inode: inode_index as u32, + record_len: 264, + name_len: 1, + file_type: 0x2, + name: [0; 256], + }; + directory_block.block.entries[0].name[0] = dot; + + // add dot dot entry + directory_block.block.entries[1] = DirectoryEntry { + inode: 0, // TODO set this as parent inode number + record_len: 264, + name_len: 2, + file_type: 0x2, + name: [0; 256], + }; + directory_block.block.entries[1].name[0] = dot; + directory_block.block.entries[1].name[1] = dot; + } + // 把 inode 放到指定位置 + self.get_inode_mut(inode_index).map(|inode| { + *inode = new_inode; + }); + + (inode_index, self.get_inode(inode_index).unwrap()) + }) + } + pub(crate) fn create_inode( &mut self, permissions: u16, @@ -13,9 +79,7 @@ impl AyaFS { gid: u32, flags: u32, ) -> Option { - debug!("create inode"); self.inode_bitmap.allocate().map(|inode_index| { - debug!("creating inode"); self.get_inode_mut(inode_index).map(|inode| { *inode = Inode::make_inode( permissions, @@ -29,7 +93,6 @@ impl AyaFS { 0, ); }); - debug!("inode created"); inode_index }) } diff --git a/src/memory/inode_iter.rs b/src/memory/inode_iter.rs new file mode 100644 index 0000000..d5909bc --- /dev/null +++ b/src/memory/inode_iter.rs @@ -0,0 +1,233 @@ +use std::ffi::OsStr; +use std::os::unix::ffi::OsStrExt; +use libc::{c_int, ENOENT, ENOSPC}; +use crate::disk::block::{Block, DirectoryBlock, DirectoryEntry, DoubleIndirectBlock, IndirectBlock, TripleIndirectBlock}; +use crate::disk::inode::{Inode, DIRECT_NUMBER}; +use crate::{AyaFS, INODE_PER_BLOCK}; +use crate::memory::cached_block::CachedBlock; + +impl AyaFS { + pub(crate) fn access_block(&mut self, inode: &Inode, mut block_index_within_inode: usize) -> Option<&CachedBlock> { + // direct block + if block_index_within_inode < DIRECT_NUMBER { + let block_index = inode.direct[block_index_within_inode] as usize; + return self.get_block::(block_index); + } else { + block_index_within_inode -= DIRECT_NUMBER; + } + + // indirect block + let indirect_number = INODE_PER_BLOCK; + if block_index_within_inode < indirect_number { + return if let Some(indirect_block) = + self.get_block::(inode.single_indirect as usize) + { + let block_index = indirect_block.block.entries[block_index_within_inode] as usize; + self.get_block::(block_index) + } else { + None + }; + } else { + block_index_within_inode -= indirect_number; + } + + // double indirect block + let double_indirect_number = INODE_PER_BLOCK * INODE_PER_BLOCK; + if block_index_within_inode < double_indirect_number { + if let Some(double_indirect_block) = + self.get_block::(inode.double_indirect as usize) + { + // 取出 double indirect block + let indirect_block_index = double_indirect_block.block.indirect + [block_index_within_inode / INODE_PER_BLOCK] + as usize; + // 要找的 entry 在 double indirect block 中的第几个 indirect block + if let Some(indirect_block) = self.get_block::(indirect_block_index) + { + let block_index = indirect_block.block.entries + [block_index_within_inode % INODE_PER_BLOCK] + as usize; + // 拿到 DirectoryBlock 的 index + return self.get_block::(block_index); + } + } + return None; + } else { + block_index_within_inode -= double_indirect_number; + } + + // triple indirect block + if let Some(triple_indirect_block) = + self.get_block::(inode.triple_indirect as usize) + { + // 取出 triple indirect block + let double_indirect_block_index = triple_indirect_block.block.double_indirect + [block_index_within_inode / (INODE_PER_BLOCK * INODE_PER_BLOCK)] + as usize; + // 要找的 entry 在 triple indirect block 中的第几个 double indirect block + if let Some(double_indirect_block) = + self.get_block::(double_indirect_block_index) + { + // 取出 double indirect block + let indirect_block_index = double_indirect_block.block.indirect + [block_index_within_inode % (INODE_PER_BLOCK * INODE_PER_BLOCK) + / INODE_PER_BLOCK] as usize; + // 要找的 entry 在 double indirect block 中的第几个 indirect block + if let Some(indirect_block) = self.get_block::(indirect_block_index) + { + let block_index = indirect_block.block.entries + [block_index_within_inode % INODE_PER_BLOCK] + as usize; + // DirectoryBlock 的 index + return self.get_block::(block_index); + } + } + } + + None + } + + pub(crate) fn add_direntry( + &mut self, + parent_inode: &mut Inode, + child_inode_index: u32, + child_inode_name: &OsStr, + child_inode: &Inode, + ) -> Result { + let entry_index = parent_inode.size; + let block_index_within_inode = (entry_index / 15) as usize; + let entry_index_within_block = (entry_index % 15) as usize; + + // TODO 如果 block 内 offset 是 0 (或者访问到 block_index_within_inode 无效?)则为 Inode 分配新的 block + + // TODO 把新的 entry 写到对应位置 + + Err(ENOSPC) + } + pub(crate) fn get_direntry( + &mut self, + inode: &Inode, + entry_index: u32 + ) -> Option { + let block_index_within_inode = (entry_index / 15) as usize; + let entry_index_within_block = (entry_index % 15) as usize; + + self.access_block::(inode, block_index_within_inode) + .map(|directory_block| { + directory_block.block.entries[entry_index_within_block].clone() + }) + } + + // pub(crate) fn get_direntry( + // &mut self, + // inode: &Inode, + // entry_index: u32, + // ) -> Option { + // // 每个 DirectoryBlock 里有 15 个 entry, 先确定 block 再确定 offset + // let mut block_index_within_inode = (entry_index / 15) as usize; + // let entry_index_within_block = (entry_index % 15) as usize; + // + // // direct block + // if block_index_within_inode < DIRECT_NUMBER { + // let block_index = inode.direct[block_index_within_inode] as usize; + // return self + // .get_block::(block_index) + // .map(|inode_block| inode_block.block.entries[entry_index_within_block].clone()); + // } else { + // block_index_within_inode -= DIRECT_NUMBER; + // } + // + // // indirect block + // let indirect_number = INODE_PER_BLOCK; + // if block_index_within_inode < indirect_number { + // return if let Some(indirect_block) = + // self.get_block::(inode.single_indirect as usize) + // { + // let block_index = indirect_block.block.entries[block_index_within_inode] as usize; + // self.get_block::(block_index) + // .map(|inode_block| inode_block.block.entries[entry_index_within_block].clone()) + // } else { + // None + // }; + // } else { + // block_index_within_inode -= indirect_number; + // } + // + // // double indirect block + // let double_indirect_number = INODE_PER_BLOCK * INODE_PER_BLOCK; + // if block_index_within_inode < double_indirect_number { + // if let Some(double_indirect_block) = + // self.get_block::(inode.double_indirect as usize) + // { + // // 取出 double indirect block + // let indirect_block_index = double_indirect_block.block.indirect + // [block_index_within_inode / INODE_PER_BLOCK] + // as usize; + // // 要找的 entry 在 double indirect block 中的第几个 indirect block + // if let Some(indirect_block) = self.get_block::(indirect_block_index) + // { + // let block_index = indirect_block.block.entries + // [block_index_within_inode % INODE_PER_BLOCK] + // as usize; + // // 拿到 DirectoryBlock 的 index + // return self + // .get_block::(block_index) + // .map(|inode_block| { + // inode_block.block.entries[entry_index_within_block].clone() + // }); + // } + // } + // return None; + // } else { + // block_index_within_inode -= double_indirect_number; + // } + // + // // triple indirect block + // if let Some(triple_indirect_block) = + // self.get_block::(inode.triple_indirect as usize) + // { + // // 取出 triple indirect block + // let double_indirect_block_index = triple_indirect_block.block.double_indirect + // [block_index_within_inode / (INODE_PER_BLOCK * INODE_PER_BLOCK)] + // as usize; + // // 要找的 entry 在 triple indirect block 中的第几个 double indirect block + // if let Some(double_indirect_block) = + // self.get_block::(double_indirect_block_index) + // { + // // 取出 double indirect block + // let indirect_block_index = double_indirect_block.block.indirect + // [block_index_within_inode % (INODE_PER_BLOCK * INODE_PER_BLOCK) + // / INODE_PER_BLOCK] as usize; + // // 要找的 entry 在 double indirect block 中的第几个 indirect block + // if let Some(indirect_block) = self.get_block::(indirect_block_index) + // { + // let block_index = indirect_block.block.entries + // [block_index_within_inode % INODE_PER_BLOCK] + // as usize; + // // DirectoryBlock 的 index + // return self + // .get_block::(block_index) + // .map(|inode_block| { + // inode_block.block.entries[entry_index_within_block].clone() + // }); + // } + // } + // } + // None + // } + + // TODO 实现一个带 cache 的版本 + pub fn lookup_name(&mut self, parent_inode: &Inode, name: &OsStr) -> Result<(u32, Inode), c_int> { + let mut entry_index = 0; + while entry_index < parent_inode.size { + if let Some(entry) = self.get_direntry(parent_inode, entry_index) { + if entry.name() == name { + let inode = self.get_inode(entry.inode as usize).unwrap().clone(); + return Ok((entry.inode, inode)) + } + } + entry_index += 1; + } + Err(ENOENT) + } +} diff --git a/src/memory/mod.rs b/src/memory/mod.rs index 3380d0f..dce7c42 100644 --- a/src/memory/mod.rs +++ b/src/memory/mod.rs @@ -2,3 +2,4 @@ /// This is where the crucial block and inode methods presented to upper calls implemented. pub mod cached_block; pub mod cached_inode; +mod inode_iter; diff --git a/src/utils/constants.rs b/src/utils/constants.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/utils/constants.rs @@ -0,0 +1 @@ + diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 60b09f2..0a9b825 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,7 +1,10 @@ +mod constants; +pub mod permissions; + use crate::block_device::BLOCK_SIZE; use crate::disk::inode::{Inode, INODE_SIZE}; use crate::{AyaFS, INODE_PER_BLOCK}; -use fuser::FileAttr; +use fuser::{FileAttr, FileType}; use std::time::{Duration, SystemTime, UNIX_EPOCH}; pub(crate) fn time_now() -> u32 { @@ -42,6 +45,62 @@ pub(crate) fn to_systime(time: u32) -> SystemTime { UNIX_EPOCH + Duration::from_secs(time as u64) } +// File type code, one of: +// 0x0 Unknown. +// 0x1 Regular file. +// 0x2 Directory. +// 0x3 Character device file. +// 0x4 Block device file. +// 0x5 FIFO. +// 0x6 Socket. +// 0x7 Symbolic link. + +pub(crate) fn from_filetype(file_type: FileType) -> u8 { + match file_type { + FileType::NamedPipe => 0x5, + FileType::CharDevice => 0x3, + FileType::BlockDevice => 0x4, + FileType::Directory => 0x2, + FileType::RegularFile => 0x1, + FileType::Symlink => 0x7, + FileType::Socket => 0x6, + } +} + +pub(crate) fn to_filetype(file_type: u8) -> Option { + match file_type { + 0x0 => None, + 0x1 => Some(FileType::RegularFile), + 0x2 => Some(FileType::Directory), + 0x3 => Some(FileType::CharDevice), + 0x4 => Some(FileType::BlockDevice), + 0x5 => Some(FileType::NamedPipe), + 0x6 => Some(FileType::Socket), + 0x7 => Some(FileType::Symlink), + _ => panic!("bad filetype"), + } +} + +pub(crate) fn to_fileattr(inode_index: usize, inode: &Inode) -> FileAttr { + FileAttr { + ino: inode_index as u64, + size: inode.size as u64, + blocks: inode.n_blocks as u64, + atime: to_systime(inode.atime), + mtime: to_systime(inode.mtime), + ctime: to_systime(inode.ctime), + crtime: 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, + } +} + impl AyaFS { /// 输入 inode 编号, 返回它对应的 block number 和 block 内 offset pub(crate) fn locate_inode(&self, inode_index: usize) -> (usize, usize) { diff --git a/src/utils/permissions.rs b/src/utils/permissions.rs new file mode 100644 index 0000000..a3afe77 --- /dev/null +++ b/src/utils/permissions.rs @@ -0,0 +1,62 @@ +use std::fs::File; +use std::io::BufRead; +use libc::{F_OK, S_ISGID, S_ISUID, S_IXGRP, X_OK}; +use crate::disk::inode::InodeMode; + +pub(crate) fn get_groups(pid: u32) -> Vec { + let file = File::open(format!("/proc/{pid}/task/{pid}/status")) + .expect(format!("pid {pid} incorrect!").as_str()); + for line in std::io::BufReader::new(file).lines() { + let line = line.unwrap(); + if line.starts_with("Groups:") { + return line["Groups: ".len()..] + .split(' ') + .filter(|x| !x.trim().is_empty()) + .map(|x| x.parse::().unwrap()) + .collect(); + } + } + + Vec::new() +} + +pub(crate) fn clear_suid_sgid(mut mode: InodeMode) -> InodeMode { + mode.0 &= !S_ISUID as u16; + if mode.0 & S_IXGRP as u16 != 0 { + mode.0 &= !S_ISGID as u16; + } + mode +} + +pub(crate) fn check_access( + incoming_uid: u32, + incoming_gid: u32, + uid: u32, + gid: u32, + perm: InodeMode, + mut mask: i32, +) -> bool { + if mask == F_OK { + return true; + } + + let perm = i32::from(perm.0); + // root + if incoming_uid == 0 { + // 读写任何东西都是可以的, 执行只有 IXOTH/IXGRP/IXUSR 之一设置才可以 + mask &= X_OK; + mask -= mask & (perm >> 6); + mask -= mask & (perm >> 3); + mask -= mask & perm; + return mask == 0; + } + + if incoming_uid == uid { + mask -= mask & (perm >> 6); + } else if incoming_gid == gid { + mask -= mask & (perm >> 3); + } else { + mask -= mask & perm; + } + mask == 0 +} \ No newline at end of file -- cgit v1.2.3-70-g09d2