ソースを参照

partial work: file array rework and asynchronize

Signed-off-by: greatbridf <greatbridf@icloud.com>
greatbridf 5 ヶ月 前
コミット
db931a8038

+ 11 - 3
Cargo.lock

@@ -146,7 +146,7 @@ dependencies = [
  "eonix_runtime",
  "eonix_sync",
  "ext4_rs",
- "intrusive-collections",
+ "intrusive-collections 0.9.8",
  "intrusive_list",
  "itertools",
  "pointers",
@@ -213,7 +213,7 @@ dependencies = [
  "eonix_percpu",
  "eonix_preempt",
  "eonix_sync",
- "intrusive-collections",
+ "intrusive-collections 0.9.7",
  "pointers",
 ]
 
@@ -246,7 +246,7 @@ dependencies = [
  "eonix_preempt",
  "eonix_spin",
  "eonix_sync_base",
- "intrusive-collections",
+ "intrusive-collections 0.9.7",
 ]
 
 [[package]]
@@ -274,6 +274,14 @@ dependencies = [
  "memoffset",
 ]
 
+[[package]]
+name = "intrusive-collections"
+version = "0.9.8"
+source = "git+https://github.com/greatbridf/intrusive-rs#0e2d88bffc9df606566fba2d61d1217182b06975"
+dependencies = [
+ "memoffset",
+]
+
 [[package]]
 name = "intrusive_list"
 version = "0.1.0"

+ 1 - 1
Cargo.toml

@@ -25,7 +25,7 @@ posix_types = { path = "./crates/posix_types" }
 slab_allocator = { path = "./crates/slab_allocator" }
 
 bitflags = "2.6.0"
-intrusive-collections = "0.9.7"
+intrusive-collections = { version = "0.9.8", git = "https://github.com/greatbridf/intrusive-rs" }
 itertools = { version = "0.13.0", default-features = false }
 acpi = "5.2.0"
 align_ext = "0.1.0"

+ 28 - 0
crates/posix_types/src/getdent.rs

@@ -0,0 +1,28 @@
+#[derive(Copy, Clone, Debug)]
+#[repr(C, packed)]
+pub struct UserDirent64 {
+    /// Inode number
+    pub d_ino: u64,
+    /// Implementation defined. We ignore it
+    pub d_off: u64,
+    /// Length of this record
+    pub d_reclen: u16,
+    /// File type. Set to 0
+    pub d_type: u8,
+    /// Filename with a padding '\0'
+    pub d_name: [u8; 0],
+}
+
+/// File type is at offset `d_reclen - 1`. Set it to 0
+#[derive(Copy, Clone, Debug)]
+#[repr(C, packed)]
+pub struct UserDirent {
+    /// Inode number
+    pub d_ino: u32,
+    /// Implementation defined. We ignore it
+    pub d_off: u32,
+    /// Length of this record
+    pub d_reclen: u16,
+    /// Filename with a padding '\0'
+    pub d_name: [u8; 0],
+}

+ 1 - 0
crates/posix_types/src/lib.rs

@@ -2,6 +2,7 @@
 
 pub mod constants;
 pub mod ctypes;
+pub mod getdent;
 pub mod namei;
 pub mod open;
 pub mod poll;

+ 17 - 20
src/fs/ext4.rs

@@ -2,6 +2,7 @@ use core::sync::atomic::{AtomicU32, AtomicU64, Ordering};
 
 use crate::kernel::mem::{PageCache, PageCacheBackend};
 use crate::kernel::task::block_on;
+use crate::kernel::vfs::inode::{AtomicMode, Mode};
 use crate::{
     io::{Buffer, ByteBuffer},
     kernel::{
@@ -12,7 +13,6 @@ use crate::{
             dentry::Dentry,
             inode::{define_struct_inode, AtomicNlink, Ino, Inode, InodeData},
             mount::{register_filesystem, Mount, MountCreator},
-            s_isdir, s_isreg,
             vfs::Vfs,
             DevId, FsContext,
         },
@@ -86,30 +86,27 @@ impl Ext4Fs {
     fn get_or_insert(
         &self,
         icache: &mut BTreeMap<Ino, Ext4Inode>,
-        mut idata: InodeData,
+        idata: InodeData,
     ) -> Arc<dyn Inode> {
         match icache.entry(idata.ino) {
             Entry::Occupied(occupied) => occupied.get().clone().into_inner(),
-            Entry::Vacant(vacant) => {
-                let mode = *idata.mode.get_mut();
-                if s_isreg(mode) {
-                    vacant
-                        .insert(Ext4Inode::File(FileInode::new(idata)))
-                        .clone()
-                        .into_inner()
-                } else if s_isdir(mode) {
-                    vacant
-                        .insert(Ext4Inode::Dir(Arc::new(DirInode { idata })))
-                        .clone()
-                        .into_inner()
-                } else {
-                    println_warn!("ext4: Unsupported inode type: {mode:#o}");
+            Entry::Vacant(vacant) => match idata.mode.load().format() {
+                Mode::REG => vacant
+                    .insert(Ext4Inode::File(FileInode::new(idata)))
+                    .clone()
+                    .into_inner(),
+                Mode::DIR => vacant
+                    .insert(Ext4Inode::Dir(Arc::new(DirInode { idata })))
+                    .clone()
+                    .into_inner(),
+                mode => {
+                    println_warn!("ext4: Unsupported inode type: {:#o}", mode.format_bits());
                     vacant
                         .insert(Ext4Inode::File(FileInode::new(idata)))
                         .clone()
                         .into_inner()
                 }
-            }
+            },
         }
     }
 }
@@ -137,7 +134,7 @@ impl Ext4Fs {
                     nlink: AtomicNlink::new(root_inode.inode.links_count() as _),
                     uid: AtomicU32::new(root_inode.inode.uid() as _),
                     gid: AtomicU32::new(root_inode.inode.gid() as _),
-                    mode: AtomicU32::new(root_inode.inode.mode() as _),
+                    mode: AtomicMode::new(root_inode.inode.mode() as _),
                     atime: Spin::new(Instant::new(
                         root_inode.inode.atime() as _,
                         root_inode.inode.i_atime_extra() as _,
@@ -201,7 +198,7 @@ impl PageCacheBackend for FileInode {
         self.read_direct(page, offset)
     }
 
-    fn write_page(&self, page: &crate::kernel::mem::CachePage, offset: usize) -> KResult<usize> {
+    fn write_page(&self, _page: &crate::kernel::mem::CachePage, _offset: usize) -> KResult<usize> {
         todo!()
     }
 
@@ -269,7 +266,7 @@ impl Inode for DirInode {
                 nlink: AtomicNlink::new(attr.nlink as _),
                 uid: AtomicU32::new(attr.uid),
                 gid: AtomicU32::new(attr.gid),
-                mode: AtomicU32::new(attr.kind.bits() as u32 | real_perm),
+                mode: AtomicMode::new(attr.kind.bits() as u32 | real_perm),
                 atime: Spin::new(Instant::new(attr.atime as _, 0)),
                 ctime: Spin::new(Instant::new(attr.ctime as _, 0)),
                 mtime: Spin::new(Instant::new(attr.mtime as _, 0)),

+ 3 - 4
src/fs/fat32.rs

@@ -5,12 +5,11 @@ use crate::io::Stream;
 use crate::kernel::constants::EIO;
 use crate::kernel::mem::AsMemoryBlock;
 use crate::kernel::task::block_on;
-use crate::kernel::vfs::inode::WriteOffset;
+use crate::kernel::vfs::inode::{Mode, WriteOffset};
 use crate::{
     io::{Buffer, ByteBuffer, UninitBuffer},
     kernel::{
         block::{make_device, BlockDevice, BlockDeviceRequest},
-        constants::{S_IFDIR, S_IFREG},
         mem::{
             paging::Page,
             {CachePage, PageCache, PageCacheBackend},
@@ -253,7 +252,7 @@ impl FileInode {
 
         // Safety: We are initializing the inode
         inode.nlink.store(1, Ordering::Relaxed);
-        inode.mode.store(S_IFREG | 0o777, Ordering::Relaxed);
+        inode.mode.store(Mode::REG.perm(0o777));
         inode.size.store(size as u64, Ordering::Relaxed);
 
         inode
@@ -343,7 +342,7 @@ impl DirInode {
 
         // Safety: We are initializing the inode
         inode.nlink.store(2, Ordering::Relaxed);
-        inode.mode.store(S_IFDIR | 0o777, Ordering::Relaxed);
+        inode.mode.store(Mode::DIR.perm(0o777));
         inode.size.store(size as u64, Ordering::Relaxed);
 
         inode

+ 6 - 6
src/fs/procfs.rs

@@ -1,10 +1,10 @@
 use crate::kernel::constants::{EACCES, ENOTDIR};
 use crate::kernel::task::block_on;
 use crate::kernel::timer::Instant;
+use crate::kernel::vfs::inode::{AtomicMode, Mode};
 use crate::{
     io::Buffer,
     kernel::{
-        constants::{S_IFDIR, S_IFREG},
         mem::paging::PageBuffer,
         vfs::{
             dentry::Dentry,
@@ -69,12 +69,12 @@ define_struct_inode! {
 
 impl FileInode {
     pub fn new(ino: Ino, vfs: Weak<ProcFs>, file: Box<dyn ProcFsFile>) -> Arc<Self> {
-        let mut mode = S_IFREG;
+        let mut mode = Mode::REG;
         if file.can_read() {
-            mode |= 0o444;
+            mode.set_perm(0o444);
         }
         if file.can_write() {
-            mode |= 0o200;
+            mode.set_perm(0o222);
         }
 
         let mut inode = Self {
@@ -82,7 +82,7 @@ impl FileInode {
             file,
         };
 
-        inode.idata.mode.store(mode, Ordering::Relaxed);
+        inode.idata.mode.store(mode);
         inode.idata.nlink.store(1, Ordering::Relaxed);
         *inode.ctime.get_mut() = Instant::now();
         *inode.mtime.get_mut() = Instant::now();
@@ -123,7 +123,7 @@ impl DirInode {
     pub fn new(ino: Ino, vfs: Weak<ProcFs>) -> Arc<Self> {
         Self::new_locked(ino, vfs, |inode, rwsem| unsafe {
             addr_of_mut_field!(inode, entries).write(Locked::new(vec![], rwsem));
-            addr_of_mut_field!(&mut *inode, mode).write((S_IFDIR | 0o755).into());
+            addr_of_mut_field!(&mut *inode, mode).write(AtomicMode::from(Mode::DIR.perm(0o755)));
             addr_of_mut_field!(&mut *inode, nlink).write(1.into());
             addr_of_mut_field!(&mut *inode, ctime).write(Spin::new(Instant::now()));
             addr_of_mut_field!(&mut *inode, mtime).write(Spin::new(Instant::now()));

+ 22 - 34
src/fs/tmpfs.rs

@@ -3,16 +3,14 @@ use crate::kernel::constants::{EEXIST, EINVAL, EIO, EISDIR, ENOENT, ENOSYS, ENOT
 use crate::kernel::mem::{CachePage, PageCache, PageCacheBackend};
 use crate::kernel::task::block_on;
 use crate::kernel::timer::Instant;
-use crate::kernel::vfs::inode::InodeData;
 use crate::kernel::vfs::inode::RenameData;
+use crate::kernel::vfs::inode::{AtomicMode, InodeData};
 use crate::{
     io::Buffer,
-    kernel::constants::{S_IFBLK, S_IFCHR, S_IFDIR, S_IFLNK, S_IFREG},
     kernel::vfs::{
         dentry::{dcache, Dentry},
         inode::{define_struct_inode, AtomicIno, Ino, Inode, Mode, WriteOffset},
         mount::{register_filesystem, Mount, MountCreator, MS_RDONLY},
-        s_isblk, s_ischr,
         vfs::Vfs,
         DevId,
     },
@@ -46,7 +44,7 @@ impl NodeInode {
         Self::new_locked(ino, vfs, |inode, _| unsafe {
             addr_of_mut_field!(inode, devid).write(devid);
 
-            addr_of_mut_field!(&mut *inode, mode).write(mode.into());
+            addr_of_mut_field!(&mut *inode, mode).write(AtomicMode::from(mode));
             addr_of_mut_field!(&mut *inode, nlink).write(1.into());
             addr_of_mut_field!(&mut *inode, ctime).write(Spin::new(Instant::now()));
             addr_of_mut_field!(&mut *inode, mtime).write(Spin::new(Instant::now()));
@@ -74,7 +72,8 @@ impl DirectoryInode {
                 .write(Locked::new(vec![(Arc::from(b".".as_slice()), ino)], rwsem));
 
             addr_of_mut_field!(&mut *inode, size).write(1.into());
-            addr_of_mut_field!(&mut *inode, mode).write((S_IFDIR | (mode & 0o777)).into());
+            addr_of_mut_field!(&mut *inode, mode)
+                .write(AtomicMode::from(Mode::DIR.perm(mode.non_format_bits())));
             addr_of_mut_field!(&mut *inode, nlink).write(1.into()); // link from `.` to itself
             addr_of_mut_field!(&mut *inode, ctime).write(Spin::new(Instant::now()));
             addr_of_mut_field!(&mut *inode, mtime).write(Spin::new(Instant::now()));
@@ -108,7 +107,7 @@ impl DirectoryInode {
         _file_lock: ProofMut<()>,
     ) -> KResult<()> {
         // SAFETY: `file_lock` has done the synchronization
-        if file.mode.load(Ordering::Relaxed) & S_IFDIR != 0 {
+        if file.mode.load().is_dir() {
             return Err(EISDIR);
         }
 
@@ -163,7 +162,7 @@ impl Inode for DirectoryInode {
     }
 
     fn mknod(&self, at: &Dentry, mode: Mode, dev: DevId) -> KResult<()> {
-        if !s_ischr(mode) && !s_isblk(mode) {
+        if !mode.is_chr() && !mode.is_blk() {
             return Err(EINVAL);
         }
 
@@ -173,12 +172,7 @@ impl Inode for DirectoryInode {
         let rwsem = block_on(self.rwsem.write());
 
         let ino = vfs.assign_ino();
-        let file = NodeInode::new(
-            ino,
-            self.vfs.clone(),
-            mode & (0o777 | S_IFBLK | S_IFCHR),
-            dev,
-        );
+        let file = NodeInode::new(ino, self.vfs.clone(), mode, dev);
 
         self.link(at.get_name(), file.as_ref(), rwsem.prove_mut());
         at.save_reg(file)
@@ -243,9 +237,8 @@ impl Inode for DirectoryInode {
         let _lock = block_on(self.rwsem.write());
 
         // SAFETY: `rwsem` has done the synchronization
-        let old = self.mode.load(Ordering::Relaxed);
-        self.mode
-            .store((old & !0o777) | (mode & 0o777), Ordering::Relaxed);
+        let old = self.mode.load();
+        self.mode.store(old.perm(mode.non_format_bits()));
         *self.ctime.lock() = Instant::now();
 
         Ok(())
@@ -331,12 +324,10 @@ impl Inode for DirectoryInode {
                 let _new_file_lock = block_on(new_file.rwsem.write());
 
                 // SAFETY: `new_file_lock` has done the synchronization
-                if new_file.mode.load(Ordering::Relaxed) & S_IFDIR != 0 {
-                    return Err(EISDIR);
-                } else {
-                    if old_file.mode.load(Ordering::Relaxed) & S_IFDIR != 0 {
-                        return Err(ENOTDIR);
-                    }
+                match (new_file.mode.load(), old_file.mode.load()) {
+                    (Mode::DIR, _) => return Err(EISDIR),
+                    (_, Mode::DIR) => return Err(ENOTDIR),
+                    _ => {}
                 }
 
                 entries.remove(new_idx);
@@ -393,10 +384,10 @@ impl Inode for DirectoryInode {
                 let new_file = new_file.unwrap();
                 let new_file_lock = block_on(new_file.rwsem.write());
 
-                if old_file.mode.load(Ordering::Relaxed) & S_IFDIR != 0
-                    && new_file.mode.load(Ordering::Relaxed) & S_IFDIR == 0
-                {
-                    return Err(ENOTDIR);
+                match (old_file.mode.load(), new_file.mode.load()) {
+                    (Mode::DIR, Mode::DIR) => {}
+                    (Mode::DIR, _) => return Err(ENOTDIR),
+                    (_, _) => {}
                 }
 
                 // Unlink the old file that was replaced
@@ -442,7 +433,7 @@ impl SymlinkInode {
             let len = target.len();
             addr_of_mut_field!(inode, target).write(target);
 
-            addr_of_mut_field!(&mut *inode, mode).write((S_IFLNK | 0o777).into());
+            addr_of_mut_field!(&mut *inode, mode).write(AtomicMode::from(Mode::LNK.perm(0o777)));
             addr_of_mut_field!(&mut *inode, size).write((len as u64).into());
             addr_of_mut_field!(&mut *inode, ctime).write(Spin::new(Instant::now()));
             addr_of_mut_field!(&mut *inode, mtime).write(Spin::new(Instant::now()));
@@ -482,9 +473,7 @@ impl FileInode {
             pages: PageCache::new(weak_self.clone()),
         });
 
-        inode
-            .mode
-            .store(S_IFREG | (mode & 0o777), Ordering::Relaxed);
+        inode.mode.store(Mode::REG.perm(mode.non_format_bits()));
         inode.nlink.store(1, Ordering::Relaxed);
         inode.size.store(size as u64, Ordering::Relaxed);
         inode
@@ -557,9 +546,8 @@ impl Inode for FileInode {
         let _lock = block_on(self.rwsem.write());
 
         // SAFETY: `rwsem` has done the synchronization
-        let old = self.mode.load(Ordering::Relaxed);
-        self.mode
-            .store((old & !0o777) | (mode & 0o777), Ordering::Relaxed);
+        let old = self.mode.load();
+        self.mode.store(old.perm(mode.non_format_bits()));
         *self.ctime.lock() = Instant::now();
 
         Ok(())
@@ -600,7 +588,7 @@ impl TmpFs {
         });
 
         let weak = Arc::downgrade(&tmpfs);
-        let root_dir = DirectoryInode::new(0, weak, 0o755);
+        let root_dir = DirectoryInode::new(0, weak, Mode::new(0o755));
 
         Ok((tmpfs, root_dir))
     }

+ 2 - 5
src/kernel/chardev.rs

@@ -4,10 +4,7 @@ use super::{
     constants::{EEXIST, EIO},
     task::{block_on, ProcessList, Thread},
     terminal::Terminal,
-    vfs::{
-        file::{File, FileType, TerminalFile},
-        DevId,
-    },
+    vfs::{DevId, File, FileType, TerminalFile},
 };
 use crate::{
     io::{Buffer, Stream, StreamRead},
@@ -71,7 +68,7 @@ impl CharDevice {
         }
     }
 
-    pub fn open(self: &Arc<Self>, flags: OpenFlags) -> KResult<Arc<File>> {
+    pub fn open(self: &Arc<Self>, flags: OpenFlags) -> KResult<File> {
         Ok(match &self.device {
             CharDeviceType::Terminal(terminal) => {
                 let procs = block_on(ProcessList::get().read());

+ 8 - 2
src/kernel/syscall.rs

@@ -263,13 +263,19 @@ impl<T> Deref for UserMut<T> {
 
 impl<T> core::fmt::Debug for User<T> {
     fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
-        write!(f, "User({:#x?})", self.0.addr())
+        match self.0 {
+            VAddr::NULL => write!(f, "User(NULL)"),
+            _ => write!(f, "User({:#018x?})", self.0.addr()),
+        }
     }
 }
 
 impl<T> core::fmt::Debug for UserMut<T> {
     fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
-        write!(f, "UserMut({:#x?})", self.0.addr())
+        match self.0 {
+            VAddr::NULL => write!(f, "UserMut(NULL)"),
+            _ => write!(f, "UserMut({:#018x?})", self.0.addr()),
+        }
     }
 }
 

+ 21 - 14
src/kernel/syscall/file_rw.rs

@@ -1,20 +1,19 @@
 use super::{FromSyscallArg, User};
 use crate::io::IntoStream;
 use crate::kernel::constants::{
-    EBADF, EFAULT, EINVAL, ENOENT, ENOSYS, ENOTDIR, SEEK_CUR, SEEK_END, SEEK_SET, S_IFBLK, S_IFCHR,
+    EBADF, EFAULT, EINVAL, ENOENT, ENOSYS, ENOTDIR, SEEK_CUR, SEEK_END, SEEK_SET,
 };
 use crate::kernel::syscall::UserMut;
 use crate::kernel::task::Thread;
 use crate::kernel::timer::sleep;
 use crate::kernel::vfs::filearray::FD;
+use crate::kernel::vfs::inode::Mode;
+use crate::kernel::vfs::{PollEvent, SeekOption};
 use crate::{
     io::{Buffer, BufferFill},
     kernel::{
         user::{CheckedUserPointer, UserBuffer, UserPointer, UserPointerMut, UserString},
-        vfs::{
-            dentry::Dentry,
-            file::{PollEvent, SeekOption},
-        },
+        vfs::dentry::Dentry,
     },
     path::Path,
     prelude::*,
@@ -120,8 +119,12 @@ async fn pwrite64(fd: FD, buffer: User<u8>, count: usize, offset: usize) -> KRes
 }
 
 #[eonix_macros::define_syscall(SYS_OPENAT)]
-async fn openat(dirfd: FD, pathname: User<u8>, flags: OpenFlags, mode: u32) -> KResult<FD> {
+async fn openat(dirfd: FD, pathname: User<u8>, flags: OpenFlags, mut mode: Mode) -> KResult<FD> {
     let dentry = dentry_from(thread, dirfd, pathname, flags.follow_symlink())?;
+
+    let umask = *thread.fs_context.umask.lock();
+    mode.mask_perm(!umask.non_format_bits());
+
     thread.files.open(&dentry, flags, mode)
 }
 
@@ -133,7 +136,7 @@ async fn open(path: User<u8>, flags: OpenFlags, mode: u32) -> KResult<FD> {
 
 #[eonix_macros::define_syscall(SYS_CLOSE)]
 async fn close(fd: FD) -> KResult<()> {
-    thread.files.close(fd)
+    thread.files.close(fd).await
 }
 
 #[eonix_macros::define_syscall(SYS_DUP)]
@@ -149,7 +152,7 @@ async fn dup2(old_fd: FD, new_fd: FD) -> KResult<FD> {
 
 #[eonix_macros::define_syscall(SYS_DUP3)]
 async fn dup3(old_fd: FD, new_fd: FD, flags: OpenFlags) -> KResult<FD> {
-    thread.files.dup_to(old_fd, new_fd, flags)
+    thread.files.dup_to(old_fd, new_fd, flags).await
 }
 
 #[eonix_macros::define_syscall(SYS_PIPE2)]
@@ -254,9 +257,9 @@ async fn statx(
 }
 
 #[eonix_macros::define_syscall(SYS_MKDIRAT)]
-async fn mkdirat(dirfd: FD, pathname: User<u8>, mode: u32) -> KResult<()> {
+async fn mkdirat(dirfd: FD, pathname: User<u8>, mut mode: Mode) -> KResult<()> {
     let umask = *thread.fs_context.umask.lock();
-    let mode = mode & !umask & 0o777;
+    mode.mask_perm(!umask.non_format_bits());
 
     let dentry = dentry_from(thread, dirfd, pathname, true)?;
     dentry.mkdir(mode)
@@ -311,11 +314,15 @@ async fn symlink(target: User<u8>, linkpath: User<u8>) -> KResult<()> {
 }
 
 #[eonix_macros::define_syscall(SYS_MKNODAT)]
-async fn mknodat(dirfd: FD, pathname: User<u8>, mode: u32, dev: u32) -> KResult<()> {
+async fn mknodat(dirfd: FD, pathname: User<u8>, mut mode: Mode, dev: u32) -> KResult<()> {
+    if !mode.is_blk() && !mode.is_chr() {
+        return Err(EINVAL);
+    }
+
     let dentry = dentry_from(thread, dirfd, pathname, true)?;
 
     let umask = *thread.fs_context.umask.lock();
-    let mode = mode & ((!umask & 0o777) | (S_IFBLK | S_IFCHR));
+    mode.mask_perm(!umask.non_format_bits());
 
     dentry.mknod(mode, dev)
 }
@@ -616,7 +623,7 @@ async fn fchownat(
 }
 
 #[eonix_macros::define_syscall(SYS_FCHMODAT)]
-async fn fchmodat(dirfd: FD, pathname: User<u8>, mode: u32, flags: AtFlags) -> KResult<()> {
+async fn fchmodat(dirfd: FD, pathname: User<u8>, mode: Mode, flags: AtFlags) -> KResult<()> {
     let dentry = if flags.at_empty_path() {
         let file = thread.files.get(dirfd).ok_or(EBADF)?;
         file.as_path().ok_or(EBADF)?.clone()
@@ -632,7 +639,7 @@ async fn fchmodat(dirfd: FD, pathname: User<u8>, mode: u32, flags: AtFlags) -> K
 }
 
 #[eonix_macros::define_syscall(SYS_FCHMOD)]
-async fn chmod(pathname: User<u8>, mode: u32) -> KResult<()> {
+async fn chmod(pathname: User<u8>, mode: Mode) -> KResult<()> {
     sys_fchmodat(thread, FD::AT_FDCWD, pathname, mode, AtFlags::empty()).await
 }
 

+ 7 - 6
src/kernel/syscall/mm.rs

@@ -4,6 +4,7 @@ use crate::kernel::constants::{EBADF, EEXIST, EINVAL, ENOENT};
 use crate::kernel::mem::FileMapping;
 use crate::kernel::task::Thread;
 use crate::kernel::vfs::filearray::FD;
+use crate::kernel::vfs::inode::Mode;
 use crate::{
     kernel::{
         constants::{UserMmapFlags, UserMmapProtocol},
@@ -66,11 +67,11 @@ async fn do_mmap2(
             Mapping::Anonymous
         } else {
             // The mode is unimportant here, since we are checking prot in mm_area.
-            let shared_area =
-                SHM_MANAGER
-                    .lock()
-                    .await
-                    .create_shared_area(len, thread.process.pid, 0x777);
+            let shared_area = SHM_MANAGER.lock().await.create_shared_area(
+                len,
+                thread.process.pid,
+                Mode::REG.perm(0o777),
+            );
             Mapping::File(FileMapping::new(shared_area.area.clone(), 0, len))
         }
     } else {
@@ -185,7 +186,7 @@ async fn shmget(key: usize, size: usize, shmflg: u32) -> KResult<u32> {
     let mut shm_manager = SHM_MANAGER.lock().await;
     let shmid = gen_shm_id(key)?;
 
-    let mode = shmflg & 0o777;
+    let mode = Mode::REG.perm(shmflg);
     let shmflg = ShmFlags::from_bits_truncate(shmflg);
 
     if key == IPC_PRIVATE {

+ 4 - 5
src/kernel/syscall/procops.rs

@@ -16,6 +16,7 @@ use crate::kernel::task::{parse_futexop, CloneArgs};
 use crate::kernel::timer::sleep;
 use crate::kernel::user::UserString;
 use crate::kernel::user::{UserPointer, UserPointerMut};
+use crate::kernel::vfs::inode::Mode;
 use crate::kernel::vfs::{self, dentry::Dentry};
 use crate::path::Path;
 use crate::{kernel::user::UserBuffer, prelude::*};
@@ -99,12 +100,10 @@ async fn clock_nanosleep(
 }
 
 #[eonix_macros::define_syscall(SYS_UMASK)]
-async fn umask(mask: u32) -> KResult<u32> {
+async fn umask(mask: Mode) -> KResult<Mode> {
     let mut umask = thread.fs_context.umask.lock();
 
-    let old = *umask;
-    *umask = mask & 0o777;
-    Ok(old)
+    Ok(core::mem::replace(&mut *umask, mask.non_format()))
 }
 
 #[eonix_macros::define_syscall(SYS_GETCWD)]
@@ -221,7 +220,7 @@ async fn execve(exec: User<u8>, argv: User<PtrT>, envp: User<PtrT>) -> KResult<S
         thread.process.mm_list.replace(Some(load_info.mm_list));
     }
 
-    thread.files.on_exec();
+    thread.files.on_exec().await;
     thread.signal_list.clear_non_ignore();
     thread.set_name(dentry.get_name());
 

+ 1 - 1
src/kernel/task/process_list.rs

@@ -148,7 +148,7 @@ impl ProcessList {
         if thread.tid == process.pid {
             assert_eq!(thread.tid, process.pid);
 
-            thread.files.close_all();
+            thread.files.close_all().await;
 
             // If we are the session leader, we should drop the control terminal.
             if process.session(self.prove()).sid == process.pid {

+ 12 - 12
src/kernel/vfs/dentry.rs

@@ -2,7 +2,7 @@ pub mod dcache;
 
 use super::{
     inode::{Ino, Inode, Mode, RenameData, WriteOffset},
-    s_isblk, s_ischr, s_isdir, s_isreg, DevId, FsContext,
+    DevId, FsContext,
 };
 use crate::{
     hash::KernelHasher,
@@ -250,7 +250,7 @@ impl Dentry {
             }
 
             let parent = self.parent().get_inode()?;
-            parent.creat(self, mode as u32)
+            parent.creat(self, mode)
         }
     }
 }
@@ -409,14 +409,14 @@ impl Dentry {
         let inode = self.get_inode()?;
 
         // Safety: Changing mode alone will have no effect on the file's contents
-        match inode.mode.load(Ordering::Relaxed) {
-            mode if s_isdir(mode) => Err(EISDIR),
-            mode if s_isreg(mode) => inode.read(buffer, offset),
-            mode if s_isblk(mode) => {
+        match inode.mode.load().format() {
+            Mode::DIR => Err(EISDIR),
+            Mode::REG => inode.read(buffer, offset),
+            Mode::BLK => {
                 let device = BlockDevice::get(inode.devid()?)?;
                 Ok(device.read_some(offset, buffer)?.allow_partial())
             }
-            mode if s_ischr(mode) => {
+            Mode::CHR => {
                 let device = CharDevice::get(inode.devid()?).ok_or(EPERM)?;
                 device.read(buffer)
             }
@@ -427,11 +427,11 @@ impl Dentry {
     pub fn write(&self, stream: &mut dyn Stream, offset: WriteOffset) -> KResult<usize> {
         let inode = self.get_inode()?;
         // Safety: Changing mode alone will have no effect on the file's contents
-        match inode.mode.load(Ordering::Relaxed) {
-            mode if s_isdir(mode) => Err(EISDIR),
-            mode if s_isreg(mode) => inode.write(stream, offset),
-            mode if s_isblk(mode) => Err(EINVAL), // TODO
-            mode if s_ischr(mode) => CharDevice::get(inode.devid()?).ok_or(EPERM)?.write(stream),
+        match inode.mode.load().format() {
+            Mode::DIR => Err(EISDIR),
+            Mode::REG => inode.write(stream, offset),
+            Mode::BLK => Err(EINVAL), // TODO
+            Mode::CHR => CharDevice::get(inode.devid()?).ok_or(EPERM)?.write(stream),
             _ => Err(EINVAL),
         }
     }

+ 4 - 4
src/kernel/vfs/dentry/dcache.rs

@@ -1,9 +1,9 @@
 use super::{Dentry, Inode};
 use crate::kernel::constants::ENOENT;
 use crate::kernel::task::block_on;
+use crate::kernel::vfs::inode::Mode;
 use crate::rcu::RCUPointer;
 use crate::{
-    kernel::vfs::{s_isdir, s_islnk},
     prelude::*,
     rcu::{RCUIterator, RCUList},
 };
@@ -57,9 +57,9 @@ pub fn d_try_revalidate(dentry: &Arc<Dentry>) {
 ///
 /// Dentry flags will be determined by the inode's mode.
 pub fn d_save(dentry: &Arc<Dentry>, inode: Arc<dyn Inode>) -> KResult<()> {
-    match inode.mode.load(Ordering::Acquire) {
-        mode if s_isdir(mode) => dentry.save_dir(inode),
-        mode if s_islnk(mode) => dentry.save_symlink(inode),
+    match inode.mode.load().format() {
+        Mode::DIR => dentry.save_dir(inode),
+        Mode::LNK => dentry.save_symlink(inode),
         _ => dentry.save_reg(inode),
     }
 }

+ 0 - 637
src/kernel/vfs/file.rs

@@ -1,637 +0,0 @@
-use super::{
-    dentry::Dentry,
-    inode::{Mode, WriteOffset},
-    s_isblk, s_isreg,
-};
-use crate::{
-    io::{Buffer, BufferFill, ByteBuffer, Chunks, IntoStream},
-    kernel::{
-        constants::{TCGETS, TCSETS, TIOCGPGRP, TIOCGWINSZ, TIOCSPGRP},
-        mem::{paging::Page, AsMemoryBlock as _},
-        task::Thread,
-        terminal::{Terminal, TerminalIORequest},
-        user::{UserPointer, UserPointerMut},
-        vfs::inode::Inode,
-        CharDevice,
-    },
-    prelude::*,
-    sync::CondVar,
-};
-use crate::{
-    io::{Stream, StreamRead},
-    kernel::constants::{
-        EBADF, EFAULT, EINTR, EINVAL, ENOTDIR, ENOTTY, EOVERFLOW, EPIPE, ESPIPE, S_IFMT,
-    },
-};
-use alloc::{collections::vec_deque::VecDeque, sync::Arc};
-use bitflags::bitflags;
-use core::{
-    ops::{ControlFlow, Deref},
-    sync::atomic::{AtomicU32, Ordering},
-};
-use eonix_sync::Mutex;
-use posix_types::{open::OpenFlags, signal::Signal, stat::StatX};
-
-pub struct InodeFile {
-    read: bool,
-    write: bool,
-    append: bool,
-    /// Only a few modes those won't possibly change are cached here to speed up file operations.
-    /// Specifically, `S_IFMT` masked bits.
-    mode: Mode,
-    cursor: Mutex<usize>,
-    dentry: Arc<Dentry>,
-}
-
-pub struct PipeInner {
-    buffer: VecDeque<u8>,
-    read_closed: bool,
-    write_closed: bool,
-}
-
-pub struct Pipe {
-    inner: Mutex<PipeInner>,
-    cv_read: CondVar,
-    cv_write: CondVar,
-}
-
-pub struct PipeReadEnd {
-    pipe: Arc<Pipe>,
-}
-
-pub struct PipeWriteEnd {
-    pipe: Arc<Pipe>,
-}
-
-pub struct TerminalFile {
-    terminal: Arc<Terminal>,
-}
-
-// TODO: We should use `File` as the base type, instead of `Arc<File>`
-//       If we need shared states, like for `InodeFile`, the files themselves should
-//       have their own shared semantics. All `File` variants will just keep the
-//       `Clone` semantics.
-//
-//       e.g. The `CharDevice` itself is stateless.
-pub enum FileType {
-    Inode(InodeFile),
-    PipeRead(PipeReadEnd),
-    PipeWrite(PipeWriteEnd),
-    TTY(TerminalFile),
-    CharDev(Arc<CharDevice>),
-}
-
-pub struct File {
-    flags: AtomicU32,
-    file_type: FileType,
-}
-
-impl File {
-    pub fn get_inode(&self) -> KResult<Option<Arc<dyn Inode>>> {
-        match &self.file_type {
-            FileType::Inode(inode_file) => Ok(Some(inode_file.dentry.get_inode()?)),
-            _ => Ok(None),
-        }
-    }
-}
-
-pub enum SeekOption {
-    Set(usize),
-    Current(isize),
-    End(isize),
-}
-
-bitflags! {
-    pub struct PollEvent: u16 {
-        const Readable = 0x0001;
-        const Writable = 0x0002;
-    }
-}
-
-impl Drop for PipeReadEnd {
-    fn drop(&mut self) {
-        self.pipe.close_read();
-    }
-}
-
-impl Drop for PipeWriteEnd {
-    fn drop(&mut self) {
-        self.pipe.close_write();
-    }
-}
-
-fn send_sigpipe_to_current() {
-    let current = Thread::current();
-    current.raise(Signal::SIGPIPE);
-}
-
-impl Pipe {
-    const PIPE_SIZE: usize = 4096;
-
-    /// # Return
-    /// `(read_end, write_end)`
-    pub fn new(flags: OpenFlags) -> (Arc<File>, Arc<File>) {
-        let pipe = Arc::new(Self {
-            inner: Mutex::new(PipeInner {
-                buffer: VecDeque::with_capacity(Self::PIPE_SIZE),
-                read_closed: false,
-                write_closed: false,
-            }),
-            cv_read: CondVar::new(),
-            cv_write: CondVar::new(),
-        });
-
-        let read_flags = flags.difference(OpenFlags::O_WRONLY | OpenFlags::O_RDWR);
-        let mut write_flags = read_flags;
-        write_flags.insert(OpenFlags::O_WRONLY);
-
-        (
-            Arc::new(File {
-                flags: AtomicU32::new(read_flags.bits()),
-                file_type: FileType::PipeRead(PipeReadEnd { pipe: pipe.clone() }),
-            }),
-            Arc::new(File {
-                flags: AtomicU32::new(write_flags.bits()),
-                file_type: FileType::PipeWrite(PipeWriteEnd { pipe }),
-            }),
-        )
-    }
-
-    async fn close_read(&self) {
-        let mut inner = self.inner.lock().await;
-        if inner.read_closed {
-            return;
-        }
-
-        inner.read_closed = true;
-        self.cv_write.notify_all();
-    }
-
-    async fn close_write(&self) {
-        let mut inner = self.inner.lock().await;
-        if inner.write_closed {
-            return;
-        }
-
-        inner.write_closed = true;
-        self.cv_read.notify_all();
-    }
-
-    async fn poll(&self, event: PollEvent) -> KResult<PollEvent> {
-        if !event.contains(PollEvent::Readable) {
-            unimplemented!("Poll event not supported.");
-        }
-
-        let mut inner = self.inner.lock().await;
-        while inner.buffer.is_empty() && !inner.write_closed {
-            inner = self.cv_read.wait(inner).await;
-        }
-
-        if Thread::current().signal_list.has_pending_signal() {
-            return Err(EINTR);
-        }
-
-        let mut retval = PollEvent::empty();
-        if inner.write_closed {
-            retval |= PollEvent::Writable;
-        }
-
-        if !inner.buffer.is_empty() {
-            retval |= PollEvent::Readable;
-        }
-
-        Ok(retval)
-    }
-
-    async fn read(&self, buffer: &mut dyn Buffer) -> KResult<usize> {
-        let mut inner = self.inner.lock().await;
-
-        while !inner.write_closed && inner.buffer.is_empty() {
-            inner = self.cv_read.wait(inner).await;
-            if Thread::current().signal_list.has_pending_signal() {
-                return Err(EINTR);
-            }
-        }
-
-        let (data1, data2) = inner.buffer.as_slices();
-        let nread = buffer.fill(data1)?.allow_partial() + buffer.fill(data2)?.allow_partial();
-        inner.buffer.drain(..nread);
-
-        self.cv_write.notify_all();
-        Ok(nread)
-    }
-
-    async fn write_atomic(&self, data: &[u8]) -> KResult<usize> {
-        let mut inner = self.inner.lock().await;
-
-        if inner.read_closed {
-            send_sigpipe_to_current();
-            return Err(EPIPE);
-        }
-
-        while inner.buffer.len() + data.len() > Self::PIPE_SIZE {
-            inner = self.cv_write.wait(inner).await;
-            if Thread::current().signal_list.has_pending_signal() {
-                return Err(EINTR);
-            }
-
-            if inner.read_closed {
-                send_sigpipe_to_current();
-                return Err(EPIPE);
-            }
-        }
-
-        inner.buffer.extend(data);
-
-        self.cv_read.notify_all();
-        return Ok(data.len());
-    }
-
-    async fn write(&self, stream: &mut dyn Stream) -> KResult<usize> {
-        let mut buffer = [0; Self::PIPE_SIZE];
-        let mut total = 0;
-        while let Some(data) = stream.poll_data(&mut buffer)? {
-            let nwrote = self.write_atomic(data).await?;
-            total += nwrote;
-            if nwrote != data.len() {
-                break;
-            }
-        }
-        Ok(total)
-    }
-}
-
-#[derive(Copy, Clone, Debug)]
-#[repr(C, packed)]
-struct UserDirent64 {
-    /// Inode number
-    d_ino: u64,
-    /// Implementation defined. We ignore it
-    d_off: u64,
-    /// Length of this record
-    d_reclen: u16,
-    /// File type. Set to 0
-    d_type: u8,
-    /// Filename with a padding '\0'
-    d_name: [u8; 0],
-}
-
-/// File type is at offset `d_reclen - 1`. Set it to 0
-#[derive(Copy, Clone, Debug)]
-#[repr(C, packed)]
-struct UserDirent {
-    /// Inode number
-    d_ino: u32,
-    /// Implementation defined. We ignore it
-    d_off: u32,
-    /// Length of this record
-    d_reclen: u16,
-    /// Filename with a padding '\0'
-    d_name: [u8; 0],
-}
-
-impl InodeFile {
-    pub fn new(dentry: Arc<Dentry>, flags: OpenFlags) -> Arc<File> {
-        // SAFETY: `dentry` used to create `InodeFile` is valid.
-        // SAFETY: `mode` should never change with respect to the `S_IFMT` fields.
-        let cached_mode = dentry
-            .get_inode()
-            .expect("`dentry` is invalid")
-            .mode
-            .load(Ordering::Relaxed)
-            & S_IFMT;
-
-        let (read, write, append) = flags.as_rwa();
-
-        Arc::new(File {
-            flags: AtomicU32::new(flags.bits()),
-            file_type: FileType::Inode(InodeFile {
-                dentry,
-                read,
-                write,
-                append,
-                mode: cached_mode,
-                cursor: Mutex::new(0),
-            }),
-        })
-    }
-
-    async fn seek(&self, option: SeekOption) -> KResult<usize> {
-        let mut cursor = self.cursor.lock().await;
-
-        let new_cursor = match option {
-            SeekOption::Current(off) => cursor.checked_add_signed(off).ok_or(EOVERFLOW)?,
-            SeekOption::Set(n) => n,
-            SeekOption::End(off) => {
-                let inode = self.dentry.get_inode()?;
-                let size = inode.size.load(Ordering::Relaxed) as usize;
-                size.checked_add_signed(off).ok_or(EOVERFLOW)?
-            }
-        };
-
-        *cursor = new_cursor;
-        Ok(new_cursor)
-    }
-
-    async fn write(&self, stream: &mut dyn Stream, offset: Option<usize>) -> KResult<usize> {
-        if !self.write {
-            return Err(EBADF);
-        }
-
-        let mut cursor = self.cursor.lock().await;
-
-        if self.append {
-            let nwrote = self.dentry.write(stream, WriteOffset::End(&mut cursor))?;
-
-            Ok(nwrote)
-        } else {
-            let nwrote = if let Some(offset) = offset {
-                self.dentry.write(stream, WriteOffset::Position(offset))?
-            } else {
-                let nwrote = self.dentry.write(stream, WriteOffset::Position(*cursor))?;
-                *cursor += nwrote;
-                nwrote
-            };
-
-            Ok(nwrote)
-        }
-    }
-
-    async fn read(&self, buffer: &mut dyn Buffer, offset: Option<usize>) -> KResult<usize> {
-        if !self.read {
-            return Err(EBADF);
-        }
-
-        let nread = if let Some(offset) = offset {
-            let nread = self.dentry.read(buffer, offset)?;
-            nread
-        } else {
-            let mut cursor = self.cursor.lock().await;
-
-            let nread = self.dentry.read(buffer, *cursor)?;
-
-            *cursor += nread;
-            nread
-        };
-
-        Ok(nread)
-    }
-
-    async fn getdents64(&self, buffer: &mut dyn Buffer) -> KResult<()> {
-        let mut cursor = self.cursor.lock().await;
-
-        let nread = self.dentry.readdir(*cursor, |filename, ino| {
-            // Filename length + 1 for padding '\0'
-            let real_record_len = core::mem::size_of::<UserDirent64>() + filename.len() + 1;
-
-            if buffer.available() < real_record_len {
-                return Ok(ControlFlow::Break(()));
-            }
-
-            let record = UserDirent64 {
-                d_ino: ino,
-                d_off: 0,
-                d_reclen: real_record_len as u16,
-                d_type: 0,
-                d_name: [0; 0],
-            };
-
-            buffer.copy(&record)?.ok_or(EFAULT)?;
-            buffer.fill(filename)?.ok_or(EFAULT)?;
-            buffer.fill(&[0])?.ok_or(EFAULT)?;
-
-            Ok(ControlFlow::Continue(()))
-        })?;
-
-        *cursor += nread;
-        Ok(())
-    }
-
-    async fn getdents(&self, buffer: &mut dyn Buffer) -> KResult<()> {
-        let mut cursor = self.cursor.lock().await;
-
-        let nread = self.dentry.readdir(*cursor, |filename, ino| {
-            // + 1 for filename length padding '\0', + 1 for d_type.
-            let real_record_len = core::mem::size_of::<UserDirent>() + filename.len() + 2;
-
-            if buffer.available() < real_record_len {
-                return Ok(ControlFlow::Break(()));
-            }
-
-            let record = UserDirent {
-                d_ino: ino as u32,
-                d_off: 0,
-                d_reclen: real_record_len as u16,
-                d_name: [0; 0],
-            };
-
-            buffer.copy(&record)?.ok_or(EFAULT)?;
-            buffer.fill(filename)?.ok_or(EFAULT)?;
-            buffer.fill(&[0, 0])?.ok_or(EFAULT)?;
-
-            Ok(ControlFlow::Continue(()))
-        })?;
-
-        *cursor += nread;
-        Ok(())
-    }
-}
-
-impl TerminalFile {
-    pub fn new(tty: Arc<Terminal>, flags: OpenFlags) -> Arc<File> {
-        Arc::new(File {
-            flags: AtomicU32::new(flags.bits()),
-            file_type: FileType::TTY(TerminalFile { terminal: tty }),
-        })
-    }
-
-    async fn read(&self, buffer: &mut dyn Buffer) -> KResult<usize> {
-        self.terminal.read(buffer).await
-    }
-
-    fn write(&self, stream: &mut dyn Stream) -> KResult<usize> {
-        stream.read_till_end(&mut [0; 128], |data| {
-            self.terminal.write(data);
-            Ok(())
-        })
-    }
-
-    async fn poll(&self, event: PollEvent) -> KResult<PollEvent> {
-        if !event.contains(PollEvent::Readable) {
-            unimplemented!("Poll event not supported.")
-        }
-
-        self.terminal.poll_in().await.map(|_| PollEvent::Readable)
-    }
-
-    async fn ioctl(&self, request: usize, arg3: usize) -> KResult<()> {
-        self.terminal
-            .ioctl(match request as u32 {
-                TCGETS => TerminalIORequest::GetTermios(UserPointerMut::with_addr(arg3)?),
-                TCSETS => TerminalIORequest::SetTermios(UserPointer::with_addr(arg3)?),
-                TIOCGPGRP => TerminalIORequest::GetProcessGroup(UserPointerMut::with_addr(arg3)?),
-                TIOCSPGRP => TerminalIORequest::SetProcessGroup(UserPointer::with_addr(arg3)?),
-                TIOCGWINSZ => TerminalIORequest::GetWindowSize(UserPointerMut::with_addr(arg3)?),
-                _ => return Err(EINVAL),
-            })
-            .await
-    }
-}
-
-impl FileType {
-    pub async fn read(&self, buffer: &mut dyn Buffer, offset: Option<usize>) -> KResult<usize> {
-        match self {
-            FileType::Inode(inode) => inode.read(buffer, offset).await,
-            FileType::PipeRead(pipe) => pipe.pipe.read(buffer).await,
-            FileType::TTY(tty) => tty.read(buffer).await,
-            FileType::CharDev(device) => device.read(buffer),
-            _ => Err(EBADF),
-        }
-    }
-
-    // TODO
-    // /// Read from the file into the given buffers.
-    // ///
-    // /// Reads are atomic, not intermingled with other reads or writes.
-    // pub fn readv<'r, 'i, I: Iterator<Item = &'i mut dyn Buffer>>(
-    //     &'r self,
-    //     buffers: I,
-    // ) -> KResult<usize> {
-    //     match self {
-    //         File::Inode(inode) => inode.readv(buffers),
-    //         File::PipeRead(pipe) => pipe.pipe.readv(buffers),
-    //         _ => Err(EBADF),
-    //     }
-    // }
-
-    pub async fn write(&self, stream: &mut dyn Stream, offset: Option<usize>) -> KResult<usize> {
-        match self {
-            FileType::Inode(inode) => inode.write(stream, offset).await,
-            FileType::PipeWrite(pipe) => pipe.pipe.write(stream).await,
-            FileType::TTY(tty) => tty.write(stream),
-            FileType::CharDev(device) => device.write(stream),
-            _ => Err(EBADF),
-        }
-    }
-
-    pub async fn seek(&self, option: SeekOption) -> KResult<usize> {
-        match self {
-            FileType::Inode(inode) => inode.seek(option).await,
-            _ => Err(ESPIPE),
-        }
-    }
-
-    pub async fn getdents(&self, buffer: &mut dyn Buffer) -> KResult<()> {
-        match self {
-            FileType::Inode(inode) => inode.getdents(buffer).await,
-            _ => Err(ENOTDIR),
-        }
-    }
-
-    pub async fn getdents64(&self, buffer: &mut dyn Buffer) -> KResult<()> {
-        match self {
-            FileType::Inode(inode) => inode.getdents64(buffer).await,
-            _ => Err(ENOTDIR),
-        }
-    }
-
-    pub async fn sendfile(&self, dest_file: &Self, count: usize) -> KResult<usize> {
-        let buffer_page = Page::alloc();
-        // SAFETY: We are the only owner of the page.
-        let buffer = unsafe { buffer_page.as_memblk().as_bytes_mut() };
-
-        match self {
-            FileType::Inode(file) if s_isblk(file.mode) || s_isreg(file.mode) => (),
-            _ => return Err(EINVAL),
-        }
-
-        let mut nsent = 0;
-        for (cur, len) in Chunks::new(0, count, buffer.len()) {
-            if Thread::current().signal_list.has_pending_signal() {
-                return if cur == 0 { Err(EINTR) } else { Ok(cur) };
-            }
-            let nread = self
-                .read(&mut ByteBuffer::new(&mut buffer[..len]), None)
-                .await?;
-            if nread == 0 {
-                break;
-            }
-
-            let nwrote = dest_file
-                .write(&mut buffer[..nread].into_stream(), None)
-                .await?;
-            nsent += nwrote;
-
-            if nwrote != len {
-                break;
-            }
-        }
-
-        Ok(nsent)
-    }
-
-    pub async fn ioctl(&self, request: usize, arg3: usize) -> KResult<usize> {
-        match self {
-            FileType::TTY(tty) => tty.ioctl(request, arg3).await.map(|_| 0),
-            _ => Err(ENOTTY),
-        }
-    }
-
-    pub async fn poll(&self, event: PollEvent) -> KResult<PollEvent> {
-        match self {
-            FileType::Inode(_) => Ok(event),
-            FileType::TTY(tty) => tty.poll(event).await,
-            FileType::PipeRead(PipeReadEnd { pipe })
-            | FileType::PipeWrite(PipeWriteEnd { pipe }) => pipe.poll(event).await,
-            _ => unimplemented!("Poll event not supported."),
-        }
-    }
-
-    pub fn statx(&self, buffer: &mut StatX, mask: u32) -> KResult<()> {
-        match self {
-            FileType::Inode(inode) => inode.dentry.statx(buffer, mask),
-            _ => Err(EBADF),
-        }
-    }
-
-    pub fn as_path(&self) -> Option<&Arc<Dentry>> {
-        match self {
-            FileType::Inode(inode_file) => Some(&inode_file.dentry),
-            _ => None,
-        }
-    }
-}
-
-impl File {
-    pub fn new(flags: OpenFlags, file_type: FileType) -> Arc<Self> {
-        Arc::new(Self {
-            flags: AtomicU32::new(flags.bits()),
-            file_type,
-        })
-    }
-
-    pub fn get_flags(&self) -> OpenFlags {
-        OpenFlags::from_bits_retain(self.flags.load(Ordering::Relaxed))
-    }
-
-    pub fn set_flags(&self, flags: OpenFlags) {
-        let flags = flags.difference(
-            OpenFlags::O_WRONLY
-                | OpenFlags::O_RDWR
-                | OpenFlags::O_CREAT
-                | OpenFlags::O_TRUNC
-                | OpenFlags::O_EXCL,
-            // | OpenFlags::O_NOCTTY,
-        );
-
-        self.flags.store(flags.bits(), Ordering::Relaxed);
-    }
-}
-
-impl Deref for File {
-    type Target = FileType;
-
-    fn deref(&self) -> &Self::Target {
-        &self.file_type
-    }
-}

+ 223 - 0
src/kernel/vfs/file/inode_file.rs

@@ -0,0 +1,223 @@
+use super::{File, FileType, SeekOption};
+use crate::{
+    io::{Buffer, BufferFill, Stream},
+    kernel::{
+        constants::{EBADF, EFAULT, ENOTDIR, EOVERFLOW, ESPIPE},
+        vfs::{
+            dentry::Dentry,
+            inode::{Inode, Mode, WriteOffset},
+        },
+    },
+    prelude::KResult,
+};
+use alloc::sync::Arc;
+use core::{ops::ControlFlow, sync::atomic::Ordering};
+use eonix_sync::Mutex;
+use posix_types::{
+    getdent::{UserDirent, UserDirent64},
+    open::OpenFlags,
+    stat::StatX,
+};
+
+pub struct InodeFile {
+    pub r: bool,
+    pub w: bool,
+    pub a: bool,
+    /// Only a few modes those won't possibly change are cached here to speed up file operations.
+    /// Specifically, `S_IFMT` masked bits.
+    pub mode: Mode,
+    cursor: Mutex<usize>,
+    dentry: Arc<Dentry>,
+}
+
+impl InodeFile {
+    pub fn new(dentry: Arc<Dentry>, flags: OpenFlags) -> File {
+        // SAFETY: `dentry` used to create `InodeFile` is valid.
+        // SAFETY: `mode` should never change with respect to the `S_IFMT` fields.
+        let cached_mode = dentry
+            .get_inode()
+            .expect("`dentry` is invalid")
+            .mode
+            .load()
+            .format();
+
+        let (r, w, a) = flags.as_rwa();
+
+        File::new(
+            flags,
+            FileType::Inode(InodeFile {
+                dentry,
+                r,
+                w,
+                a,
+                mode: cached_mode,
+                cursor: Mutex::new(0),
+            }),
+        )
+    }
+
+    pub fn sendfile_check(&self) -> KResult<()> {
+        match self.mode {
+            Mode::REG | Mode::BLK => Ok(()),
+            _ => Err(EBADF),
+        }
+    }
+
+    pub async fn write(&self, stream: &mut dyn Stream, offset: Option<usize>) -> KResult<usize> {
+        if !self.w {
+            return Err(EBADF);
+        }
+
+        let mut cursor = self.cursor.lock().await;
+
+        if self.a {
+            let nwrote = self.dentry.write(stream, WriteOffset::End(&mut cursor))?;
+
+            Ok(nwrote)
+        } else {
+            let nwrote = if let Some(offset) = offset {
+                self.dentry.write(stream, WriteOffset::Position(offset))?
+            } else {
+                let nwrote = self.dentry.write(stream, WriteOffset::Position(*cursor))?;
+                *cursor += nwrote;
+                nwrote
+            };
+
+            Ok(nwrote)
+        }
+    }
+
+    pub async fn read(&self, buffer: &mut dyn Buffer, offset: Option<usize>) -> KResult<usize> {
+        if !self.r {
+            return Err(EBADF);
+        }
+
+        let nread = if let Some(offset) = offset {
+            let nread = self.dentry.read(buffer, offset)?;
+            nread
+        } else {
+            let mut cursor = self.cursor.lock().await;
+
+            let nread = self.dentry.read(buffer, *cursor)?;
+
+            *cursor += nread;
+            nread
+        };
+
+        Ok(nread)
+    }
+}
+
+impl File {
+    pub fn get_inode(&self) -> KResult<Option<Arc<dyn Inode>>> {
+        if let FileType::Inode(inode_file) = &**self {
+            Ok(Some(inode_file.dentry.get_inode()?))
+        } else {
+            Ok(None)
+        }
+    }
+
+    pub async fn getdents(&self, buffer: &mut dyn Buffer) -> KResult<()> {
+        let FileType::Inode(inode_file) = &**self else {
+            return Err(ENOTDIR);
+        };
+
+        let mut cursor = inode_file.cursor.lock().await;
+
+        let nread = inode_file.dentry.readdir(*cursor, |filename, ino| {
+            // + 1 for filename length padding '\0', + 1 for d_type.
+            let real_record_len = core::mem::size_of::<UserDirent>() + filename.len() + 2;
+
+            if buffer.available() < real_record_len {
+                return Ok(ControlFlow::Break(()));
+            }
+
+            let record = UserDirent {
+                d_ino: ino as u32,
+                d_off: 0,
+                d_reclen: real_record_len as u16,
+                d_name: [0; 0],
+            };
+
+            buffer.copy(&record)?.ok_or(EFAULT)?;
+            buffer.fill(filename)?.ok_or(EFAULT)?;
+            buffer.fill(&[0, 0])?.ok_or(EFAULT)?;
+
+            Ok(ControlFlow::Continue(()))
+        })?;
+
+        *cursor += nread;
+        Ok(())
+    }
+
+    pub async fn getdents64(&self, buffer: &mut dyn Buffer) -> KResult<()> {
+        let FileType::Inode(inode_file) = &**self else {
+            return Err(ENOTDIR);
+        };
+
+        let mut cursor = inode_file.cursor.lock().await;
+
+        let nread = inode_file.dentry.readdir(*cursor, |filename, ino| {
+            // Filename length + 1 for padding '\0'
+            let real_record_len = core::mem::size_of::<UserDirent64>() + filename.len() + 1;
+
+            if buffer.available() < real_record_len {
+                return Ok(ControlFlow::Break(()));
+            }
+
+            let record = UserDirent64 {
+                d_ino: ino,
+                d_off: 0,
+                d_reclen: real_record_len as u16,
+                d_type: 0,
+                d_name: [0; 0],
+            };
+
+            buffer.copy(&record)?.ok_or(EFAULT)?;
+            buffer.fill(filename)?.ok_or(EFAULT)?;
+            buffer.fill(&[0])?.ok_or(EFAULT)?;
+
+            Ok(ControlFlow::Continue(()))
+        })?;
+
+        *cursor += nread;
+        Ok(())
+    }
+
+    pub async fn seek(&self, option: SeekOption) -> KResult<usize> {
+        let FileType::Inode(inode_file) = &**self else {
+            return Err(ESPIPE);
+        };
+
+        let mut cursor = inode_file.cursor.lock().await;
+
+        let new_cursor = match option {
+            SeekOption::Current(off) => cursor.checked_add_signed(off).ok_or(EOVERFLOW)?,
+            SeekOption::Set(n) => n,
+            SeekOption::End(off) => {
+                let inode = inode_file.dentry.get_inode()?;
+                let size = inode.size.load(Ordering::Relaxed) as usize;
+                size.checked_add_signed(off).ok_or(EOVERFLOW)?
+            }
+        };
+
+        *cursor = new_cursor;
+        Ok(new_cursor)
+    }
+
+    pub fn statx(&self, buffer: &mut StatX, mask: u32) -> KResult<()> {
+        if let FileType::Inode(inode) = &**self {
+            inode.dentry.statx(buffer, mask)
+        } else {
+            Err(EBADF)
+        }
+    }
+
+    pub fn as_path(&self) -> Option<&Arc<Dentry>> {
+        if let FileType::Inode(inode_file) = &**self {
+            Some(&inode_file.dentry)
+        } else {
+            None
+        }
+    }
+}

+ 232 - 0
src/kernel/vfs/file/mod.rs

@@ -0,0 +1,232 @@
+mod inode_file;
+mod pipe;
+mod terminal_file;
+
+use crate::{
+    io::{Buffer, ByteBuffer, Chunks, IntoStream, Stream},
+    kernel::{
+        constants::{EBADF, EINTR, EINVAL, ENOTTY},
+        mem::{AsMemoryBlock, Page},
+        task::Thread,
+        CharDevice,
+    },
+    prelude::KResult,
+};
+use alloc::sync::Arc;
+use bitflags::bitflags;
+use core::{
+    ops::Deref,
+    sync::atomic::{AtomicI32, AtomicU32, Ordering},
+};
+use pipe::{PipeReadEnd, PipeWriteEnd};
+use posix_types::open::OpenFlags;
+
+pub use inode_file::InodeFile;
+pub use pipe::Pipe;
+pub use terminal_file::TerminalFile;
+
+pub enum FileType {
+    Inode(InodeFile),
+    PipeRead(PipeReadEnd),
+    PipeWrite(PipeWriteEnd),
+    Terminal(TerminalFile),
+    CharDev(Arc<CharDevice>),
+}
+
+struct FileData {
+    flags: AtomicU32,
+    open_count: AtomicI32,
+    file_type: FileType,
+}
+
+#[derive(Clone)]
+pub struct File(Arc<FileData>);
+
+pub enum SeekOption {
+    Set(usize),
+    Current(isize),
+    End(isize),
+}
+
+bitflags! {
+    pub struct PollEvent: u16 {
+        const Readable = 0x0001;
+        const Writable = 0x0002;
+    }
+}
+
+impl FileType {
+    pub async fn read(&self, buffer: &mut dyn Buffer, offset: Option<usize>) -> KResult<usize> {
+        match self {
+            FileType::Inode(inode) => inode.read(buffer, offset).await,
+            FileType::PipeRead(pipe) => pipe.read(buffer).await,
+            FileType::Terminal(tty) => tty.read(buffer).await,
+            FileType::CharDev(device) => device.read(buffer),
+            _ => Err(EBADF),
+        }
+    }
+
+    // TODO
+    // /// Read from the file into the given buffers.
+    // ///
+    // /// Reads are atomic, not intermingled with other reads or writes.
+    // pub fn readv<'r, 'i, I: Iterator<Item = &'i mut dyn Buffer>>(
+    //     &'r self,
+    //     buffers: I,
+    // ) -> KResult<usize> {
+    //     match self {
+    //         File::Inode(inode) => inode.readv(buffers),
+    //         File::PipeRead(pipe) => pipe.pipe.readv(buffers),
+    //         _ => Err(EBADF),
+    //     }
+    // }
+
+    pub async fn write(&self, stream: &mut dyn Stream, offset: Option<usize>) -> KResult<usize> {
+        match self {
+            FileType::Inode(inode) => inode.write(stream, offset).await,
+            FileType::PipeWrite(pipe) => pipe.write(stream).await,
+            FileType::Terminal(tty) => tty.write(stream),
+            FileType::CharDev(device) => device.write(stream),
+            _ => Err(EBADF),
+        }
+    }
+
+    fn sendfile_check(&self) -> KResult<()> {
+        match self {
+            FileType::Inode(file) => file.sendfile_check(),
+            _ => Err(EINVAL),
+        }
+    }
+
+    pub async fn sendfile(&self, dest_file: &Self, count: usize) -> KResult<usize> {
+        let buffer_page = Page::alloc();
+        // SAFETY: We are the only owner of the page.
+        let buffer = unsafe { buffer_page.as_memblk().as_bytes_mut() };
+
+        self.sendfile_check()?;
+
+        let mut nsent = 0;
+        for (cur, len) in Chunks::new(0, count, buffer.len()) {
+            if Thread::current().signal_list.has_pending_signal() {
+                return if cur == 0 { Err(EINTR) } else { Ok(cur) };
+            }
+            let nread = self
+                .read(&mut ByteBuffer::new(&mut buffer[..len]), None)
+                .await?;
+            if nread == 0 {
+                break;
+            }
+
+            let nwrote = dest_file
+                .write(&mut buffer[..nread].into_stream(), None)
+                .await?;
+            nsent += nwrote;
+
+            if nwrote != len {
+                break;
+            }
+        }
+
+        Ok(nsent)
+    }
+
+    pub async fn ioctl(&self, request: usize, arg3: usize) -> KResult<usize> {
+        match self {
+            FileType::Terminal(tty) => tty.ioctl(request, arg3).await.map(|_| 0),
+            _ => Err(ENOTTY),
+        }
+    }
+
+    pub async fn poll(&self, event: PollEvent) -> KResult<PollEvent> {
+        match self {
+            FileType::Inode(_) => Ok(event),
+            FileType::Terminal(tty) => tty.poll(event).await,
+            FileType::PipeRead(pipe) => pipe.poll(event).await,
+            FileType::PipeWrite(pipe) => pipe.poll(event).await,
+            _ => unimplemented!("Poll event not supported."),
+        }
+    }
+}
+
+impl File {
+    pub fn new(flags: OpenFlags, file_type: FileType) -> Self {
+        Self(Arc::new(FileData {
+            flags: AtomicU32::new(flags.bits()),
+            open_count: AtomicI32::new(1),
+            file_type,
+        }))
+    }
+
+    pub fn get_flags(&self) -> OpenFlags {
+        OpenFlags::from_bits_retain(self.0.flags.load(Ordering::Relaxed))
+    }
+
+    pub fn set_flags(&self, flags: OpenFlags) {
+        let flags = flags.difference(
+            OpenFlags::O_WRONLY
+                | OpenFlags::O_RDWR
+                | OpenFlags::O_CREAT
+                | OpenFlags::O_TRUNC
+                | OpenFlags::O_EXCL,
+            // | OpenFlags::O_NOCTTY,
+        );
+
+        self.0.flags.store(flags.bits(), Ordering::Relaxed);
+    }
+
+    /// Duplicate the file descriptor in order to store it in some [FileArray].
+    ///
+    /// The [`File`]s stored in [FileArray]s hold an "open count", which is used
+    /// to track how many references to the file are currently open.
+    ///
+    /// # Panics
+    /// The [`File`]s stored in [FileArray]s MUST be retrieved by calling this
+    /// method. Otherwise, when the last reference to the file is dropped,
+    /// something bad will happen. ;)
+    ///
+    /// [FileArray]: crate::kernel::vfs::filearray::FileArray
+    pub fn dup(&self) -> Self {
+        self.0.open_count.fetch_add(1, Ordering::Relaxed);
+        Self(self.0.clone())
+    }
+
+    /// Close the file descriptor, decrementing the open count.
+    pub async fn close(self) {
+        // Due to rust async drop limits, we have to do this manually...
+        //
+        // Users of files can clone and drop it freely, but references held by
+        // file arrays must be dropped by calling this function (in order to
+        // await for the async close operation of the inner FileType).
+        match self.0.open_count.fetch_sub(1, Ordering::Relaxed) {
+            ..1 => panic!("File open count underflow."),
+            1 => {}
+            _ => return,
+        }
+
+        match &self.0.file_type {
+            FileType::PipeRead(pipe) => pipe.close().await,
+            FileType::PipeWrite(pipe) => pipe.close().await,
+            _ => {}
+        }
+    }
+}
+
+impl Drop for FileData {
+    fn drop(&mut self) {
+        // If you're "lucky" enough to see this, it means that you've violated
+        // the file reference counting rules. Check File::close() for details. ;)
+        assert_eq!(
+            self.open_count.load(Ordering::Relaxed),
+            0,
+            "File dropped with open count 0, check the comments for details."
+        );
+    }
+}
+
+impl Deref for File {
+    type Target = FileType;
+
+    fn deref(&self) -> &Self::Target {
+        &self.0.file_type
+    }
+}

+ 211 - 0
src/kernel/vfs/file/pipe.rs

@@ -0,0 +1,211 @@
+use super::{File, FileType, PollEvent};
+use crate::{
+    io::{Buffer, Stream},
+    kernel::{
+        constants::{EINTR, EPIPE},
+        task::Thread,
+    },
+    prelude::KResult,
+    sync::CondVar,
+};
+use alloc::{collections::vec_deque::VecDeque, sync::Arc};
+use eonix_sync::Mutex;
+use posix_types::{open::OpenFlags, signal::Signal};
+
+struct PipeInner {
+    buffer: VecDeque<u8>,
+    read_closed: bool,
+    write_closed: bool,
+}
+
+pub struct Pipe {
+    inner: Mutex<PipeInner>,
+    cv_read: CondVar,
+    cv_write: CondVar,
+}
+
+pub struct PipeReadEnd {
+    pipe: Arc<Pipe>,
+}
+
+pub struct PipeWriteEnd {
+    pipe: Arc<Pipe>,
+}
+
+fn send_sigpipe_to_current() {
+    let current = Thread::current();
+    current.raise(Signal::SIGPIPE);
+}
+
+impl Pipe {
+    const PIPE_SIZE: usize = 4096;
+
+    /// # Return
+    /// `(read_end, write_end)`
+    pub fn new(flags: OpenFlags) -> (File, File) {
+        let pipe = Arc::new(Self {
+            inner: Mutex::new(PipeInner {
+                buffer: VecDeque::with_capacity(Self::PIPE_SIZE),
+                read_closed: false,
+                write_closed: false,
+            }),
+            cv_read: CondVar::new(),
+            cv_write: CondVar::new(),
+        });
+
+        let read_flags = flags.difference(OpenFlags::O_WRONLY | OpenFlags::O_RDWR);
+        let mut write_flags = read_flags;
+        write_flags.insert(OpenFlags::O_WRONLY);
+
+        let read_pipe = pipe.clone();
+        let write_pipe = pipe;
+
+        (
+            File::new(
+                read_flags,
+                FileType::PipeRead(PipeReadEnd { pipe: read_pipe }),
+            ),
+            File::new(
+                write_flags,
+                FileType::PipeWrite(PipeWriteEnd { pipe: write_pipe }),
+            ),
+        )
+    }
+
+    pub async fn poll(&self, event: PollEvent) -> KResult<PollEvent> {
+        if !event.contains(PollEvent::Readable) {
+            unimplemented!("Poll event not supported.");
+        }
+
+        let mut inner = self.inner.lock().await;
+        while inner.buffer.is_empty() && !inner.write_closed {
+            inner = self.cv_read.wait(inner).await;
+        }
+
+        if Thread::current().signal_list.has_pending_signal() {
+            return Err(EINTR);
+        }
+
+        let mut retval = PollEvent::empty();
+        if inner.write_closed {
+            retval |= PollEvent::Writable;
+        }
+
+        if !inner.buffer.is_empty() {
+            retval |= PollEvent::Readable;
+        }
+
+        Ok(retval)
+    }
+
+    pub async fn read(&self, buffer: &mut dyn Buffer) -> KResult<usize> {
+        let mut inner = self.inner.lock().await;
+
+        while !inner.write_closed && inner.buffer.is_empty() {
+            inner = self.cv_read.wait(inner).await;
+            if Thread::current().signal_list.has_pending_signal() {
+                return Err(EINTR);
+            }
+        }
+
+        let (data1, data2) = inner.buffer.as_slices();
+        let nread = buffer.fill(data1)?.allow_partial() + buffer.fill(data2)?.allow_partial();
+        inner.buffer.drain(..nread);
+
+        self.cv_write.notify_all();
+        Ok(nread)
+    }
+
+    async fn write_atomic(&self, data: &[u8]) -> KResult<usize> {
+        let mut inner = self.inner.lock().await;
+
+        if inner.read_closed {
+            send_sigpipe_to_current();
+            return Err(EPIPE);
+        }
+
+        while inner.buffer.len() + data.len() > Self::PIPE_SIZE {
+            inner = self.cv_write.wait(inner).await;
+            if Thread::current().signal_list.has_pending_signal() {
+                return Err(EINTR);
+            }
+
+            if inner.read_closed {
+                send_sigpipe_to_current();
+                return Err(EPIPE);
+            }
+        }
+
+        inner.buffer.extend(data);
+
+        self.cv_read.notify_all();
+        return Ok(data.len());
+    }
+
+    pub async fn write(&self, stream: &mut dyn Stream) -> KResult<usize> {
+        let mut buffer = [0; Self::PIPE_SIZE];
+        let mut total = 0;
+        while let Some(data) = stream.poll_data(&mut buffer)? {
+            let nwrote = self.write_atomic(data).await?;
+            total += nwrote;
+            if nwrote != data.len() {
+                break;
+            }
+        }
+        Ok(total)
+    }
+}
+
+impl PipeReadEnd {
+    pub async fn read(&self, buffer: &mut dyn Buffer) -> KResult<usize> {
+        self.pipe.read(buffer).await
+    }
+
+    pub async fn poll(&self, event: PollEvent) -> KResult<PollEvent> {
+        self.pipe.poll(event).await
+    }
+
+    pub async fn close(&self) {
+        let mut inner = self.pipe.inner.lock().await;
+        if inner.read_closed {
+            return;
+        }
+
+        inner.read_closed = true;
+        self.pipe.cv_write.notify_all();
+    }
+}
+
+impl PipeWriteEnd {
+    pub async fn write(&self, stream: &mut dyn Stream) -> KResult<usize> {
+        self.pipe.write(stream).await
+    }
+
+    pub async fn poll(&self, event: PollEvent) -> KResult<PollEvent> {
+        self.pipe.poll(event).await
+    }
+
+    pub async fn close(&self) {
+        let mut inner = self.pipe.inner.lock().await;
+        if inner.write_closed {
+            return;
+        }
+
+        inner.write_closed = true;
+        self.pipe.cv_read.notify_all();
+    }
+}
+
+impl Drop for Pipe {
+    fn drop(&mut self) {
+        debug_assert!(
+            self.inner.get_mut().read_closed,
+            "Pipe read end should be closed before dropping (check File::close())."
+        );
+
+        debug_assert!(
+            self.inner.get_mut().write_closed,
+            "Pipe write end should be closed before dropping (check File::close())."
+        );
+    }
+}

+ 55 - 0
src/kernel/vfs/file/terminal_file.rs

@@ -0,0 +1,55 @@
+use super::{File, FileType, PollEvent};
+use crate::{
+    io::{Buffer, Stream, StreamRead},
+    kernel::{
+        constants::{EINVAL, TCGETS, TCSETS, TIOCGPGRP, TIOCGWINSZ, TIOCSPGRP},
+        terminal::TerminalIORequest,
+        user::{UserPointer, UserPointerMut},
+        Terminal,
+    },
+    prelude::KResult,
+};
+use alloc::sync::Arc;
+use posix_types::open::OpenFlags;
+
+pub struct TerminalFile {
+    terminal: Arc<Terminal>,
+}
+
+impl TerminalFile {
+    pub fn new(tty: Arc<Terminal>, flags: OpenFlags) -> File {
+        File::new(flags, FileType::Terminal(TerminalFile { terminal: tty }))
+    }
+
+    pub async fn read(&self, buffer: &mut dyn Buffer) -> KResult<usize> {
+        self.terminal.read(buffer).await
+    }
+
+    pub fn write(&self, stream: &mut dyn Stream) -> KResult<usize> {
+        stream.read_till_end(&mut [0; 128], |data| {
+            self.terminal.write(data);
+            Ok(())
+        })
+    }
+
+    pub async fn poll(&self, event: PollEvent) -> KResult<PollEvent> {
+        if !event.contains(PollEvent::Readable) {
+            unimplemented!("Poll event not supported.")
+        }
+
+        self.terminal.poll_in().await.map(|_| PollEvent::Readable)
+    }
+
+    pub async fn ioctl(&self, request: usize, arg3: usize) -> KResult<()> {
+        self.terminal
+            .ioctl(match request as u32 {
+                TCGETS => TerminalIORequest::GetTermios(UserPointerMut::with_addr(arg3)?),
+                TCSETS => TerminalIORequest::SetTermios(UserPointer::with_addr(arg3)?),
+                TIOCGPGRP => TerminalIORequest::GetProcessGroup(UserPointerMut::with_addr(arg3)?),
+                TIOCSPGRP => TerminalIORequest::SetProcessGroup(UserPointer::with_addr(arg3)?),
+                TIOCGWINSZ => TerminalIORequest::GetWindowSize(UserPointerMut::with_addr(arg3)?),
+                _ => return Err(EINVAL),
+            })
+            .await
+    }
+}

+ 258 - 129
src/kernel/vfs/filearray.rs

@@ -1,7 +1,7 @@
 use super::{
-    file::{File, InodeFile, TerminalFile},
+    file::{File, InodeFile, Pipe},
     inode::Mode,
-    s_ischr, Spin,
+    Spin, TerminalFile,
 };
 use crate::kernel::{
     constants::{
@@ -10,19 +10,13 @@ use crate::kernel::{
     syscall::{FromSyscallArg, SyscallRetVal},
 };
 use crate::{
-    kernel::{
-        console::get_console,
-        constants::ENXIO,
-        vfs::{dentry::Dentry, file::Pipe, s_isdir, s_isreg},
-        CharDevice,
-    },
+    kernel::{console::get_console, constants::ENXIO, vfs::dentry::Dentry, CharDevice},
     prelude::*,
 };
-use alloc::{
-    collections::btree_map::{BTreeMap, Entry},
-    sync::Arc,
+use alloc::sync::Arc;
+use intrusive_collections::{
+    intrusive_adapter, rbtree::Entry, Bound, KeyAdapter, RBTree, RBTreeAtomicLink,
 };
-use core::sync::atomic::Ordering;
 use itertools::{
     FoldWhile::{Continue, Done},
     Itertools,
@@ -34,14 +28,33 @@ pub struct FD(u32);
 
 #[derive(Clone)]
 struct OpenFile {
+    fd: FD,
     flags: FDFlags,
-    file: Arc<File>,
+    file: File,
+
+    link: RBTreeAtomicLink,
+}
+
+intrusive_adapter!(
+    OpenFileAdapter = Box<OpenFile>: OpenFile { link: RBTreeAtomicLink }
+);
+
+impl<'a> KeyAdapter<'a> for OpenFileAdapter {
+    type Key = FD;
+
+    fn get_key(&self, value: &'a OpenFile) -> Self::Key {
+        value.fd
+    }
 }
 
 #[derive(Clone)]
+struct FDAllocator {
+    min_avail: FD,
+}
+
 struct FileArrayInner {
-    files: BTreeMap<FD, OpenFile>,
-    fd_min_avail: FD,
+    files: RBTree<OpenFileAdapter>,
+    fd_alloc: FDAllocator,
 }
 
 pub struct FileArray {
@@ -49,109 +62,202 @@ pub struct FileArray {
 }
 
 impl OpenFile {
+    fn new(fd: FD, flags: FDFlags, file: File) -> Box<Self> {
+        Box::new(Self {
+            fd,
+            flags,
+            file,
+            link: RBTreeAtomicLink::new(),
+        })
+    }
+
     pub fn close_on_exec(&self) -> bool {
         self.flags.contains(FDFlags::FD_CLOEXEC)
     }
 }
 
+impl FDAllocator {
+    const fn new() -> Self {
+        Self { min_avail: FD(0) }
+    }
+
+    fn reinit(&mut self) {
+        self.min_avail = FD(0);
+    }
+
+    fn find_available(&mut self, from: FD, files: &RBTree<OpenFileAdapter>) -> FD {
+        files
+            .range(Bound::Included(&from), Bound::Unbounded)
+            .fold_while(from, |current, OpenFile { fd, .. }| {
+                if current == *fd {
+                    Continue(FD(current.0 + 1))
+                } else {
+                    Done(current)
+                }
+            })
+            .into_inner()
+    }
+
+    /// Allocate a new file descriptor starting from `from`.
+    ///
+    /// Returned file descriptor should be used immediately.
+    ///
+    fn allocate_fd(&mut self, from: FD, files: &RBTree<OpenFileAdapter>) -> FD {
+        let from = FD::max(from, self.min_avail);
+
+        if from == self.min_avail {
+            let next_min_avail = self.find_available(FD(from.0 + 1), files);
+            let allocated = self.min_avail;
+            self.min_avail = next_min_avail;
+            allocated
+        } else {
+            self.find_available(from, files)
+        }
+    }
+
+    fn release_fd(&mut self, fd: FD) {
+        if fd < self.min_avail {
+            self.min_avail = fd;
+        }
+    }
+
+    fn next_fd(&mut self, files: &RBTree<OpenFileAdapter>) -> FD {
+        self.allocate_fd(self.min_avail, files)
+    }
+}
+
 impl FileArray {
     pub fn new() -> Arc<Self> {
         Arc::new(FileArray {
             inner: Spin::new(FileArrayInner {
-                files: BTreeMap::new(),
-                fd_min_avail: FD(0),
+                files: RBTree::new(OpenFileAdapter::new()),
+                fd_alloc: FDAllocator::new(),
             }),
         })
     }
 
-    #[allow(dead_code)]
     pub fn new_shared(other: &Arc<Self>) -> Arc<Self> {
         other.clone()
     }
 
     pub fn new_cloned(other: &Self) -> Arc<Self> {
         Arc::new(Self {
-            inner: Spin::new(other.inner.lock().clone()),
+            inner: Spin::new({
+                let (new_files, new_fd_alloc) = {
+                    let mut new_files = RBTree::new(OpenFileAdapter::new());
+                    let other_inner = other.inner.lock();
+
+                    for file in other_inner.files.iter() {
+                        let new_file = OpenFile::new(file.fd, file.flags, file.file.dup());
+                        new_files.insert(new_file);
+                    }
+                    (new_files, other_inner.fd_alloc.clone())
+                };
+
+                FileArrayInner {
+                    files: new_files,
+                    fd_alloc: new_fd_alloc,
+                }
+            }),
         })
     }
 
     /// Acquires the file array lock.
-    pub fn get(&self, fd: FD) -> Option<Arc<File>> {
+    pub fn get(&self, fd: FD) -> Option<File> {
         self.inner.lock().get(fd)
     }
 
-    pub fn close_all(&self) {
-        let _old_files = {
+    pub async fn close_all(&self) {
+        let old_files = {
             let mut inner = self.inner.lock();
-            inner.fd_min_avail = FD(0);
-            core::mem::take(&mut inner.files)
+            inner.fd_alloc.reinit();
+            inner.files.take()
         };
+
+        for file in old_files.into_iter() {
+            file.file.close().await;
+        }
     }
 
-    pub fn close(&self, fd: FD) -> KResult<()> {
-        let _file = {
+    pub async fn close(&self, fd: FD) -> KResult<()> {
+        let file = {
             let mut inner = self.inner.lock();
-            let file = inner.files.remove(&fd).ok_or(EBADF)?;
-            inner.release_fd(fd);
-            file
+            let file = inner.files.find_mut(&fd).remove().ok_or(EBADF)?;
+            inner.fd_alloc.release_fd(file.fd);
+            file.file
         };
+
+        file.close().await;
         Ok(())
     }
 
-    pub fn on_exec(&self) -> () {
-        let mut inner = self.inner.lock();
+    pub async fn on_exec(&self) {
+        let files_to_close = {
+            let mut inner = self.inner.lock();
+            let (files, fd_alloc) = inner.split_borrow();
 
-        // TODO: This is not efficient. We should avoid cloning.
-        let fds_to_close = inner
-            .files
-            .iter()
-            .filter(|(_, ofile)| ofile.close_on_exec())
-            .map(|(&fd, _)| fd)
-            .collect::<Vec<_>>();
+            files.pick(|ofile| {
+                if ofile.close_on_exec() {
+                    fd_alloc.release_fd(ofile.fd);
+                    true
+                } else {
+                    false
+                }
+            })
+        };
 
-        inner.files.retain(|_, ofile| !ofile.close_on_exec());
-        fds_to_close.into_iter().for_each(|fd| inner.release_fd(fd));
+        for open_file in files_to_close.into_iter() {
+            open_file.file.close().await;
+        }
     }
-}
 
-impl FileArray {
     pub fn dup(&self, old_fd: FD) -> KResult<FD> {
         let mut inner = self.inner.lock();
-        let old_file = inner.files.get(&old_fd).ok_or(EBADF)?;
+        let (files, fd_alloc) = inner.split_borrow();
+
+        let old_file = files.get_fd(old_fd).ok_or(EBADF)?;
 
-        let new_file_data = old_file.file.clone();
+        let new_file_data = old_file.file.dup();
         let new_file_flags = old_file.flags;
-        let new_fd = inner.next_fd();
+        let new_fd = fd_alloc.next_fd(files);
 
         inner.do_insert(new_fd, new_file_flags, new_file_data);
 
         Ok(new_fd)
     }
 
-    pub fn dup_to(&self, old_fd: FD, new_fd: FD, flags: OpenFlags) -> KResult<FD> {
-        let fdflags = flags.as_fd_flags();
-
+    /// Duplicates the file to a new file descriptor, returning the old file
+    /// description to be dropped.
+    fn dup_to_no_close(&self, old_fd: FD, new_fd: FD, fd_flags: FDFlags) -> KResult<Option<File>> {
         let mut inner = self.inner.lock();
-        let old_file = inner.files.get(&old_fd).ok_or(EBADF)?;
+        let (files, fd_alloc) = inner.split_borrow();
 
-        let new_file_data = old_file.file.clone();
+        let old_file = files.get_fd(old_fd).ok_or(EBADF)?;
+        let new_file_data = old_file.file.dup();
 
-        match inner.files.entry(new_fd) {
-            Entry::Vacant(_) => {}
-            Entry::Occupied(entry) => {
-                let new_file = entry.into_mut();
-                let mut file_swap = new_file_data;
+        match files.entry(&new_fd) {
+            Entry::Vacant(_) => {
+                assert_eq!(new_fd, fd_alloc.allocate_fd(new_fd, files));
+                inner.do_insert(new_fd, fd_flags, new_file_data);
 
-                new_file.flags = fdflags;
-                core::mem::swap(&mut file_swap, &mut new_file.file);
+                Ok(None)
+            }
+            Entry::Occupied(mut entry) => {
+                let mut file = entry.remove().unwrap();
+                file.flags = fd_flags;
+                let old_file = core::mem::replace(&mut file.file, new_file_data);
 
-                drop(inner);
-                return Ok(new_fd);
+                entry.insert(file);
+
+                Ok(Some(old_file))
             }
         }
+    }
 
-        assert_eq!(new_fd, inner.allocate_fd(new_fd));
-        inner.do_insert(new_fd, fdflags, new_file_data);
+    pub async fn dup_to(&self, old_fd: FD, new_fd: FD, flags: OpenFlags) -> KResult<FD> {
+        if let Some(old_file) = self.dup_to_no_close(old_fd, new_fd, flags.as_fd_flags())? {
+            old_file.close().await;
+        }
 
         Ok(new_fd)
     }
@@ -160,9 +266,10 @@ impl FileArray {
     /// `(read_fd, write_fd)`
     pub fn pipe(&self, flags: OpenFlags) -> KResult<(FD, FD)> {
         let mut inner = self.inner.lock();
+        let (files, fd_alloc) = inner.split_borrow();
 
-        let read_fd = inner.next_fd();
-        let write_fd = inner.next_fd();
+        let read_fd = fd_alloc.next_fd(files);
+        let write_fd = fd_alloc.next_fd(files);
 
         let fdflag = flags.as_fd_flags();
 
@@ -179,23 +286,20 @@ impl FileArray {
         let fdflag = flags.as_fd_flags();
 
         let inode = dentry.get_inode()?;
-        let filemode = inode.mode.load(Ordering::Relaxed);
+        let file_format = inode.mode.load().format();
 
-        if flags.directory() {
-            if !s_isdir(filemode) {
-                return Err(ENOTDIR);
-            }
-        } else {
-            if s_isdir(filemode) && flags.write() {
-                return Err(EISDIR);
-            }
+        match (flags.directory(), file_format, flags.write()) {
+            (true, Mode::DIR, _) => {}
+            (true, _, _) => return Err(ENOTDIR),
+            (false, Mode::DIR, true) => return Err(EISDIR),
+            _ => {}
         }
 
-        if flags.truncate() && flags.write() && s_isreg(filemode) {
+        if flags.truncate() && flags.write() && file_format.is_reg() {
             inode.truncate(0)?;
         }
 
-        let file = if s_ischr(filemode) {
+        let file = if file_format.is_chr() {
             let device = CharDevice::get(inode.devid()?).ok_or(ENXIO)?;
             device.open(flags)?
         } else {
@@ -203,7 +307,8 @@ impl FileArray {
         };
 
         let mut inner = self.inner.lock();
-        let fd = inner.next_fd();
+        let (files, fd_alloc) = inner.split_borrow();
+        let fd = fd_alloc.next_fd(files);
         inner.do_insert(fd, fdflag, file);
 
         Ok(fd)
@@ -211,43 +316,59 @@ impl FileArray {
 
     pub fn fcntl(&self, fd: FD, cmd: u32, arg: usize) -> KResult<usize> {
         let mut inner = self.inner.lock();
-        let ofile = inner.files.get_mut(&fd).ok_or(EBADF)?;
+        let (files, fd_alloc) = inner.split_borrow();
+
+        let mut cursor = files.find_mut(&fd);
 
-        match cmd {
+        let ret = match cmd {
             F_DUPFD | F_DUPFD_CLOEXEC => {
+                let ofile = cursor.get().ok_or(EBADF)?;
+
                 let cloexec = cmd == F_DUPFD_CLOEXEC || ofile.flags.close_on_exec();
                 let flags = cloexec
                     .then_some(FDFlags::FD_CLOEXEC)
                     .unwrap_or(FDFlags::empty());
 
-                let new_file_data = ofile.file.clone();
-                let new_fd = inner.allocate_fd(FD(arg as u32));
+                let new_file_data = ofile.file.dup();
+                let new_fd = fd_alloc.allocate_fd(FD(arg as u32), files);
 
                 inner.do_insert(new_fd, flags, new_file_data);
 
-                Ok(new_fd.0 as usize)
+                new_fd.0 as usize
             }
-            F_GETFD => Ok(ofile.flags.bits() as usize),
+            F_GETFD => cursor.get().ok_or(EBADF)?.flags.bits() as usize,
             F_SETFD => {
+                let mut ofile = cursor.remove().ok_or(EBADF)?;
                 ofile.flags = FDFlags::from_bits_truncate(arg as u32);
-                Ok(0)
+                cursor.insert(ofile);
+                0
             }
-            F_GETFL => Ok(ofile.file.get_flags().bits() as usize),
+            F_GETFL => cursor.get().ok_or(EBADF)?.file.get_flags().bits() as usize,
             F_SETFL => {
-                ofile
+                cursor
+                    .get()
+                    .ok_or(EBADF)?
                     .file
                     .set_flags(OpenFlags::from_bits_retain(arg as u32));
 
-                Ok(0)
+                0
             }
             _ => unimplemented!("fcntl: cmd={}", cmd),
-        }
+        };
+
+        Ok(ret)
     }
 
     /// Only used for init process.
     pub fn open_console(&self) {
         let mut inner = self.inner.lock();
-        let (stdin, stdout, stderr) = (inner.next_fd(), inner.next_fd(), inner.next_fd());
+        let (files, fd_alloc) = inner.split_borrow();
+
+        let (stdin, stdout, stderr) = (
+            fd_alloc.next_fd(files),
+            fd_alloc.next_fd(files),
+            fd_alloc.next_fd(files),
+        );
         let console_terminal = get_console().expect("No console terminal");
 
         inner.do_insert(
@@ -269,53 +390,25 @@ impl FileArray {
 }
 
 impl FileArrayInner {
-    fn get(&mut self, fd: FD) -> Option<Arc<File>> {
-        self.files.get(&fd).map(|f| f.file.clone())
-    }
-
-    fn find_available(&mut self, from: FD) -> FD {
-        self.files
-            .range(&from..)
-            .fold_while(from, |current, (&key, _)| {
-                if current == key {
-                    Continue(FD(current.0 + 1))
-                } else {
-                    Done(current)
-                }
-            })
-            .into_inner()
-    }
-
-    /// Allocate a new file descriptor starting from `from`.
-    ///
-    /// Returned file descriptor should be used immediately.
-    ///
-    fn allocate_fd(&mut self, from: FD) -> FD {
-        let from = FD::max(from, self.fd_min_avail);
-
-        if from == self.fd_min_avail {
-            let next_min_avail = self.find_available(FD(from.0 + 1));
-            let allocated = self.fd_min_avail;
-            self.fd_min_avail = next_min_avail;
-            allocated
-        } else {
-            self.find_available(from)
-        }
+    fn get(&mut self, fd: FD) -> Option<File> {
+        self.files.get_fd(fd).map(|open| open.file.clone())
     }
 
-    fn release_fd(&mut self, fd: FD) {
-        if fd < self.fd_min_avail {
-            self.fd_min_avail = fd;
+    /// Insert a file description to the file array.
+    fn do_insert(&mut self, fd: FD, flags: FDFlags, file: File) {
+        match self.files.entry(&fd) {
+            Entry::Occupied(_) => {
+                panic!("File descriptor {fd:?} already exists in the file array.");
+            }
+            Entry::Vacant(insert_cursor) => {
+                insert_cursor.insert(OpenFile::new(fd, flags, file));
+            }
         }
     }
 
-    fn next_fd(&mut self) -> FD {
-        self.allocate_fd(self.fd_min_avail)
-    }
-
-    /// Insert a file description to the file array.
-    fn do_insert(&mut self, fd: FD, flags: FDFlags, file: Arc<File>) {
-        assert!(self.files.insert(fd, OpenFile { flags, file }).is_none());
+    fn split_borrow(&mut self) -> (&mut RBTree<OpenFileAdapter>, &mut FDAllocator) {
+        let Self { files, fd_alloc } = self;
+        (files, fd_alloc)
     }
 }
 
@@ -343,3 +436,39 @@ impl SyscallRetVal for FD {
         Some(self.0 as usize)
     }
 }
+
+trait FilesExt {
+    fn get_fd(&self, fd: FD) -> Option<&OpenFile>;
+
+    fn pick<P>(&mut self, pred: P) -> Self
+    where
+        P: FnMut(&OpenFile) -> bool;
+}
+
+impl FilesExt for RBTree<OpenFileAdapter> {
+    fn get_fd(&self, fd: FD) -> Option<&OpenFile> {
+        self.find(&fd).get()
+    }
+
+    fn pick<P>(&mut self, mut pred: P) -> Self
+    where
+        P: FnMut(&OpenFile) -> bool,
+    {
+        let mut picked = RBTree::new(OpenFileAdapter::new());
+
+        // TODO: might be better if we start picking from somewhere else
+        //       or using a different approach.
+        let mut cursor = self.front_mut();
+        while let Some(open_file) = cursor.get() {
+            if !pred(open_file) {
+                cursor.move_next();
+                continue;
+            }
+
+            picked.insert(cursor.remove().unwrap());
+            cursor.move_next();
+        }
+
+        picked
+    }
+}

+ 147 - 9
src/kernel/vfs/inode.rs

@@ -1,10 +1,12 @@
-use super::{dentry::Dentry, s_isblk, s_ischr, vfs::Vfs, DevId};
+use super::{dentry::Dentry, vfs::Vfs, DevId};
 use crate::io::Stream;
 use crate::kernel::constants::{
     EINVAL, EISDIR, ENOTDIR, EPERM, STATX_ATIME, STATX_BLOCKS, STATX_CTIME, STATX_GID, STATX_INO,
-    STATX_MODE, STATX_MTIME, STATX_NLINK, STATX_SIZE, STATX_TYPE, STATX_UID, S_IFDIR, S_IFMT,
+    STATX_MODE, STATX_MTIME, STATX_NLINK, STATX_SIZE, STATX_TYPE, STATX_UID, S_IFBLK, S_IFCHR,
+    S_IFDIR, S_IFLNK, S_IFMT, S_IFREG,
 };
 use crate::kernel::mem::PageCache;
+use crate::kernel::syscall::{FromSyscallArg, SyscallRetVal};
 use crate::kernel::task::block_on;
 use crate::kernel::timer::Instant;
 use crate::{io::Buffer, prelude::*};
@@ -32,8 +34,11 @@ pub type AtomicUid = AtomicU32;
 #[allow(dead_code)]
 pub type Gid = u32;
 pub type AtomicGid = AtomicU32;
-pub type Mode = u32;
-pub type AtomicMode = AtomicU32;
+
+#[derive(Clone, Copy, PartialEq, Eq)]
+pub struct Mode(u32);
+
+pub struct AtomicMode(AtomicU32);
 
 #[derive(Debug)]
 pub struct InodeData {
@@ -97,7 +102,7 @@ pub struct RenameData<'a, 'b> {
 #[allow(unused_variables)]
 pub trait Inode: Send + Sync + InodeInner + Any {
     fn is_dir(&self) -> bool {
-        self.mode.load(Ordering::SeqCst) & S_IFDIR != 0
+        self.mode.load().is_dir()
     }
 
     fn lookup(&self, dentry: &Arc<Dentry>) -> KResult<Option<Arc<dyn Inode>>> {
@@ -181,7 +186,7 @@ pub trait Inode: Send + Sync + InodeInner + Any {
         let vfs = self.vfs.upgrade().expect("Vfs is dropped");
 
         let size = self.size.load(Ordering::Relaxed);
-        let mode = self.mode.load(Ordering::Relaxed);
+        let mode = self.mode.load();
 
         if mask & STATX_NLINK != 0 {
             stat.stx_nlink = self.nlink.load(Ordering::Acquire) as _;
@@ -213,13 +218,13 @@ pub trait Inode: Send + Sync + InodeInner + Any {
 
         stat.stx_mode = 0;
         if mask & STATX_MODE != 0 {
-            stat.stx_mode |= (mode & !S_IFMT) as u16;
+            stat.stx_mode |= mode.non_format_bits() as u16;
             stat.stx_mask |= STATX_MODE;
         }
 
         if mask & STATX_TYPE != 0 {
-            stat.stx_mode |= (mode & S_IFMT) as u16;
-            if s_isblk(mode) || s_ischr(mode) {
+            stat.stx_mode |= mode.format_bits() as u16;
+            if mode.is_blk() || mode.is_chr() {
                 let devid = self.devid();
                 stat.stx_rdev_major = (devid? >> 8) & 0xff;
                 stat.stx_rdev_minor = devid? & 0xff;
@@ -354,3 +359,136 @@ macro_rules! define_struct_inode {
 }
 
 pub(crate) use define_struct_inode;
+
+impl Mode {
+    pub const REG: Self = Self(S_IFREG);
+    pub const DIR: Self = Self(S_IFDIR);
+    pub const LNK: Self = Self(S_IFLNK);
+    pub const BLK: Self = Self(S_IFBLK);
+    pub const CHR: Self = Self(S_IFCHR);
+
+    pub const fn new(bits: u32) -> Self {
+        Self(bits)
+    }
+
+    pub const fn is_blk(&self) -> bool {
+        (self.0 & S_IFMT) == S_IFBLK
+    }
+
+    pub const fn is_chr(&self) -> bool {
+        (self.0 & S_IFMT) == S_IFCHR
+    }
+
+    pub const fn is_reg(&self) -> bool {
+        (self.0 & S_IFMT) == S_IFREG
+    }
+
+    pub const fn is_dir(&self) -> bool {
+        (self.0 & S_IFMT) == S_IFDIR
+    }
+
+    pub const fn is_lnk(&self) -> bool {
+        (self.0 & S_IFMT) == S_IFLNK
+    }
+
+    pub const fn bits(&self) -> u32 {
+        self.0
+    }
+
+    pub const fn format_bits(&self) -> u32 {
+        self.0 & S_IFMT
+    }
+
+    pub const fn format(&self) -> Self {
+        Self::new(self.format_bits())
+    }
+
+    pub const fn non_format_bits(&self) -> u32 {
+        self.0 & !S_IFMT
+    }
+
+    pub const fn non_format(&self) -> Self {
+        Self::new(self.non_format_bits())
+    }
+
+    pub const fn perm(self, perm: u32) -> Self {
+        Self::new((self.0 & !0o777) | (perm & 0o777))
+    }
+
+    pub const fn set_perm(&mut self, perm: u32) {
+        *self = self.perm(perm);
+    }
+
+    pub const fn mask_perm(&mut self, perm_mask: u32) {
+        let perm_mask = perm_mask & 0o777;
+        let self_perm = self.non_format_bits() & 0o777;
+
+        *self = self.perm(self_perm & perm_mask);
+    }
+}
+
+impl AtomicMode {
+    pub const fn new(bits: u32) -> Self {
+        Self(AtomicU32::new(bits))
+    }
+
+    pub const fn from(mode: Mode) -> Self {
+        Self::new(mode.0)
+    }
+
+    pub fn load(&self) -> Mode {
+        Mode(self.0.load(Ordering::Relaxed))
+    }
+
+    pub fn store(&self, mode: Mode) {
+        self.0.store(mode.0, Ordering::Relaxed);
+    }
+}
+
+impl core::fmt::Debug for AtomicMode {
+    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+        f.debug_struct("AtomicMode")
+            .field("bits", &self.load().0)
+            .finish()
+    }
+}
+
+impl core::fmt::Debug for Mode {
+    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+        let format_name = match self.format() {
+            Mode::REG => "REG",
+            Mode::DIR => "DIR",
+            Mode::LNK => "LNK",
+            Mode::BLK => "BLK",
+            Mode::CHR => "CHR",
+            _ => "UNK",
+        };
+
+        match self.non_format_bits() & !0o777 {
+            0 => write!(
+                f,
+                "Mode({format_name}, {perm:#o})",
+                perm = self.non_format_bits()
+            )?,
+            rem => write!(
+                f,
+                "Mode({format_name}, {perm:#o}, rem={rem:#x})",
+                perm = self.non_format_bits() & 0o777
+            )?,
+        }
+
+        Ok(())
+    }
+}
+
+impl FromSyscallArg for Mode {
+    fn from_arg(value: usize) -> Self {
+        Mode::new(value as u32)
+    }
+}
+
+impl SyscallRetVal for Mode {
+    fn into_retval(self) -> Option<usize> {
+        Some(self.bits() as usize)
+    }
+}

+ 4 - 23
src/kernel/vfs/mod.rs

@@ -1,4 +1,3 @@
-use crate::kernel::constants::{S_IFBLK, S_IFCHR, S_IFDIR, S_IFLNK, S_IFMT, S_IFREG};
 use crate::prelude::*;
 use alloc::sync::Arc;
 use dentry::Dentry;
@@ -6,33 +5,15 @@ use eonix_sync::LazyLock;
 use inode::Mode;
 
 pub mod dentry;
-pub mod file;
+mod file;
 pub mod filearray;
 pub mod inode;
 pub mod mount;
 pub mod vfs;
 
-pub type DevId = u32;
-
-pub fn s_isreg(mode: Mode) -> bool {
-    (mode & S_IFMT) == S_IFREG
-}
+pub use file::{File, FileType, PollEvent, SeekOption, TerminalFile};
 
-pub fn s_isdir(mode: Mode) -> bool {
-    (mode & S_IFMT) == S_IFDIR
-}
-
-pub fn s_ischr(mode: Mode) -> bool {
-    (mode & S_IFMT) == S_IFCHR
-}
-
-pub fn s_isblk(mode: Mode) -> bool {
-    (mode & S_IFMT) == S_IFBLK
-}
-
-pub fn s_islnk(mode: Mode) -> bool {
-    (mode & S_IFMT) == S_IFLNK
-}
+pub type DevId = u32;
 
 pub struct FsContext {
     pub fsroot: Arc<Dentry>,
@@ -44,7 +25,7 @@ static GLOBAL_FS_CONTEXT: LazyLock<Arc<FsContext>> = LazyLock::new(|| {
     Arc::new(FsContext {
         fsroot: Dentry::root().clone(),
         cwd: Spin::new(Dentry::root().clone()),
-        umask: Spin::new(0o022),
+        umask: Spin::new(Mode::new(0o022)),
     })
 });
 

+ 2 - 1
src/lib.rs

@@ -41,6 +41,7 @@ use kernel::{
     task::{KernelStack, ProcessBuilder, ProcessList, ProgramLoader, ThreadBuilder},
     vfs::{
         dentry::Dentry,
+        inode::Mode,
         mount::{do_mount, MS_NOATIME, MS_NODEV, MS_NOSUID, MS_RDONLY},
         FsContext,
     },
@@ -214,7 +215,7 @@ async fn init_process(early_kstack: PRange) {
         let fs_context = FsContext::global();
         let mnt_dir = Dentry::open(fs_context, Path::new(b"/mnt/").unwrap(), true).unwrap();
 
-        mnt_dir.mkdir(0o755).unwrap();
+        mnt_dir.mkdir(Mode::new(0o755)).unwrap();
 
         do_mount(
             &mnt_dir,