summaryrefslogtreecommitdiff
path: root/src/filesystem
diff options
context:
space:
mode:
authorChuyan Zhang <me@zcy.moe>2023-11-25 02:13:22 -0800
committerChuyan Zhang <me@zcy.moe>2023-11-25 02:13:22 -0800
commit76ac602c3d79bb39c133c81a38425a77bc0b8b1f (patch)
treea1de9a03d16bd38fc6ab6b9568d2df562a913d96 /src/filesystem
parentb8afa7cfb02b32278e268924e189170496f81c1b (diff)
downloadmyfs-76ac602c3d79bb39c133c81a38425a77bc0b8b1f.tar.gz
myfs-76ac602c3d79bb39c133c81a38425a77bc0b8b1f.zip
Some FUSE callbacks, some POSIX interface implementation
Diffstat (limited to 'src/filesystem')
-rw-r--r--src/filesystem/trait_impl.rs518
1 files changed, 333 insertions, 185 deletions
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<u32>,
uid: Option<u32>,
gid: Option<u32>,
- size: Option<u64>, // TODO 为什么 setattr 还可以设置 size??
+ size: Option<u64>, // TODO 当 setattr 被 ftruncate invoke 时会设置 size
atime: Option<TimeOrNow>,
mtime: Option<TimeOrNow>,
- ctime: Option<SystemTime>,
- fh: Option<u64>,
- crtime: Option<SystemTime>,
- _chgtime: Option<SystemTime>,
- _bkuptime: Option<SystemTime>,
- flags: Option<u32>,
+ _ctime: Option<SystemTime>,
+ fh: Option<u64>, // TODO 当 setattr 被 ftruncate invoke 时会提供 fh
+ _crtime: Option<SystemTime>, // 忽略
+ _chgtime: Option<SystemTime>, // 忽略
+ _bkuptime: Option<SystemTime>, // 忽略
+ _flags: Option<u32>, // 忽略
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);
}
}