From 2d34ebcef92ea8b5eaa75ddc16c35765cdeb8065 Mon Sep 17 00:00:00 2001 From: Ashwin Naren Date: Sat, 17 Jan 2026 22:10:33 -0800 Subject: [PATCH] procfs refactor --- Cargo.lock | 7 + Cargo.toml | 1 + libkernel/src/fs/mod.rs | 75 +++++ src/drivers/fs/proc.rs | 436 ++------------------------ src/drivers/fs/proc/root.rs | 100 ++++++ src/drivers/fs/proc/task/fd.rs | 153 +++++++++ src/drivers/fs/proc/task/mod.rs | 134 ++++++++ src/drivers/fs/proc/task/task.rs | 87 +++++ src/drivers/fs/proc/task/task_file.rs | 191 +++++++++++ 9 files changed, 771 insertions(+), 413 deletions(-) create mode 100644 src/drivers/fs/proc/root.rs create mode 100644 src/drivers/fs/proc/task/fd.rs create mode 100644 src/drivers/fs/proc/task/mod.rs create mode 100644 src/drivers/fs/proc/task/task.rs create mode 100644 src/drivers/fs/proc/task/task_file.rs diff --git a/Cargo.lock b/Cargo.lock index 88b5699..9103dd8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -306,6 +306,7 @@ dependencies = [ "paste", "rand", "ringbuf", + "rustc-hash", "tock-registers", ] @@ -456,6 +457,12 @@ dependencies = [ "portable-atomic-util", ] +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + [[package]] name = "rustc-std-workspace-core" version = "1.0.1" diff --git a/Cargo.toml b/Cargo.toml index f8feab7..11a9e1a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ ringbuf = { version = "0.4.8", default-features = false, features = ["alloc"] } bitflags = "2.9.1" futures = { version = "0.3.31", default-features = false, features = ["alloc", "async-await"] } rand = { version = "0.9.2", default-features = false, features = ["small_rng"] } +rustc-hash = { version = "2.1", default-features = false } [features] default = ["smp"] diff --git a/libkernel/src/fs/mod.rs b/libkernel/src/fs/mod.rs index 0b963b2..eb7294a 100644 --- a/libkernel/src/fs/mod.rs +++ b/libkernel/src/fs/mod.rs @@ -322,3 +322,78 @@ pub trait Inode: Send + Sync + Any { Ok(()) } } + +/// A simplified trait for read-only files in procfs/sysfs that provides default implementations +/// for common inode operations. +#[async_trait] +pub trait SimpleFile { + fn id(&self) -> InodeId; + async fn getattr(&self) -> Result; + async fn read(&self) -> Result>; + async fn readlink(&self) -> Result { + Err(KernelError::NotSupported) + } +} + +#[async_trait] +impl Inode for T +where + T: SimpleFile + Send + Sync + 'static, +{ + fn id(&self) -> InodeId { + self.id() + } + + async fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result { + let bytes = self.read().await?; + let end = usize::min(bytes.len().saturating_sub(offset as usize), buf.len()); + if end == 0 { + return Ok(0); + } + let slice = &bytes[offset as usize..offset as usize + end]; + buf[..end].copy_from_slice(slice); + Ok(end) + } + + async fn getattr(&self) -> Result { + self.getattr().await + } + + async fn lookup(&self, _name: &str) -> Result> { + Err(FsError::NotADirectory.into()) + } + + async fn readdir(&self, _start_offset: u64) -> crate::error::Result> { + Err(FsError::NotADirectory.into()) + } + + async fn readlink(&self) -> Result { + self.readlink().await + } +} + +pub struct SimpleDirStream { + entries: Vec, + idx: usize, +} + +impl SimpleDirStream { + pub fn new(entries: Vec, start_offset: u64) -> Self { + Self { + entries, + idx: start_offset as usize, + } + } +} + +#[async_trait] +impl DirStream for SimpleDirStream { + async fn next_entry(&mut self) -> Result> { + Ok(if let Some(entry) = self.entries.get(self.idx).cloned() { + self.idx += 1; + Some(entry) + } else { + None + }) + } +} diff --git a/src/drivers/fs/proc.rs b/src/drivers/fs/proc.rs index 43908ea..7336329 100644 --- a/src/drivers/fs/proc.rs +++ b/src/drivers/fs/proc.rs @@ -1,45 +1,41 @@ #![allow(clippy::module_name_repetitions)] -use crate::process::find_task_by_descriptor; -use crate::process::thread_group::Tgid; -use crate::sched::current::current_task; +mod root; +mod task; + +use crate::drivers::{Driver, FilesystemDriver}; use crate::sync::OnceLock; -use crate::{ - drivers::{Driver, FilesystemDriver}, - process::TASK_LIST, -}; -use alloc::string::String; -use alloc::{boxed::Box, format, string::ToString, sync::Arc, vec::Vec}; +use alloc::{boxed::Box, sync::Arc}; use async_trait::async_trait; -use core::sync::atomic::{AtomicU64, Ordering}; -use libkernel::fs::pathbuf::PathBuf; +use core::hash::Hasher; use libkernel::{ - error::{FsError, KernelError, Result}, - fs::{ - BlockDevice, DirStream, Dirent, FileType, Filesystem, Inode, InodeId, PROCFS_ID, - attr::{FileAttr, FilePermissions}, - }, + error::{KernelError, Result}, + fs::{BlockDevice, Filesystem, Inode, PROCFS_ID}, }; use log::warn; +use root::ProcRootInode; + +/// Deterministically generates an inode ID for the given path segments within the procfs filesystem. +fn get_inode_id(path_segments: &[&str]) -> u64 { + let mut hasher = rustc_hash::FxHasher::default(); + // Ensure non-collision if other filesystems also use this method + hasher.write(b"procfs"); + for segment in path_segments { + hasher.write(segment.as_bytes()); + } + let hash = hasher.finish(); + assert_ne!(hash, 0, "Generated inode ID cannot be zero"); + hash +} pub struct ProcFs { root: Arc, - next_inode_id: AtomicU64, } impl ProcFs { fn new() -> Arc { let root_inode = Arc::new(ProcRootInode::new()); - Arc::new(Self { - root: root_inode, - next_inode_id: AtomicU64::new(1), - }) - } - - /// Convenience helper to allocate a unique inode ID inside this filesystem. - fn alloc_inode_id(&self) -> InodeId { - let id = self.next_inode_id.fetch_add(1, Ordering::Relaxed); - InodeId::from_fsid_and_inodeid(PROCFS_ID, id) + Arc::new(Self { root: root_inode }) } } @@ -54,392 +50,6 @@ impl Filesystem for ProcFs { } } -struct ProcDirStream { - entries: Vec, - idx: usize, -} - -#[async_trait] -impl DirStream for ProcDirStream { - async fn next_entry(&mut self) -> Result> { - Ok(if let Some(entry) = self.entries.get(self.idx).cloned() { - self.idx += 1; - Some(entry) - } else { - None - }) - } -} - -struct ProcRootInode { - id: InodeId, - attr: FileAttr, -} - -impl ProcRootInode { - fn new() -> Self { - Self { - id: InodeId::from_fsid_and_inodeid(PROCFS_ID, 0), - attr: FileAttr { - file_type: FileType::Directory, - mode: FilePermissions::from_bits_retain(0o555), - ..FileAttr::default() - }, - } - } -} - -#[async_trait] -impl Inode for ProcRootInode { - fn id(&self) -> InodeId { - self.id - } - - async fn lookup(&self, name: &str) -> Result> { - // Lookup a PID directory. - let pid = if name == "self" { - let current_task = current_task(); - current_task.descriptor().tgid() - } else { - Tgid( - name.parse() - .map_err(|_| FsError::NotFound) - .map_err(Into::::into)?, - ) - }; - - // Validate that the process actually exists. - if !TASK_LIST.lock_save_irq().keys().any(|d| d.tgid() == pid) { - return Err(FsError::NotFound.into()); - } - - let fs = procfs(); - - let inode_id = fs.alloc_inode_id(); - Ok(Arc::new(ProcTaskInode::new(pid, inode_id))) - } - - async fn getattr(&self) -> Result { - Ok(self.attr.clone()) - } - - async fn readdir(&self, start_offset: u64) -> Result> { - let mut entries: Vec = Vec::new(); - // Gather task list under interrupt-safe lock. - let task_list = TASK_LIST.lock_save_irq(); - for (idx, (desc, _)) in task_list - .iter() - .filter(|(_, task)| task.upgrade().is_some()) - .enumerate() - { - // Use offset index as dirent offset. - let name = desc.tgid().value().to_string(); - let inode_id = - InodeId::from_fsid_and_inodeid(PROCFS_ID, ((desc.tgid().0 + 1) * 100) as u64); - entries.push(Dirent::new( - name, - inode_id, - FileType::Directory, - (idx + 1) as u64, - )); - } - let current_task = current_task(); - entries.push(Dirent::new( - "self".to_string(), - InodeId::from_fsid_and_inodeid( - PROCFS_ID, - ((current_task.process.tgid.0 + 1) * 100) as u64, - ), - FileType::Directory, - (entries.len() + 1) as u64, - )); - - // honour start_offset - let entries = entries.into_iter().skip(start_offset as usize).collect(); - - Ok(Box::new(ProcDirStream { entries, idx: 0 })) - } -} - -struct ProcTaskInode { - id: InodeId, - attr: FileAttr, - pid: Tgid, -} - -impl ProcTaskInode { - fn new(pid: Tgid, inode_id: InodeId) -> Self { - Self { - id: inode_id, - attr: FileAttr { - file_type: FileType::Directory, - mode: FilePermissions::from_bits_retain(0o555), - ..FileAttr::default() - }, - pid, - } - } -} - -#[async_trait] -impl Inode for ProcTaskInode { - fn id(&self) -> InodeId { - self.id - } - - async fn lookup(&self, name: &str) -> Result> { - if let Ok(file_type) = TaskFileType::try_from(name) { - let fs = procfs(); - let inode_id = fs.alloc_inode_id(); - Ok(Arc::new(ProcTaskFileInode::new( - self.pid, file_type, inode_id, - ))) - } else { - Err(FsError::NotFound.into()) - } - } - - async fn getattr(&self) -> Result { - Ok(self.attr.clone()) - } - - async fn readdir(&self, start_offset: u64) -> Result> { - let mut entries: Vec = Vec::new(); - let inode_offset = ((self.pid.value() + 1) * 100) as u64; - entries.push(Dirent::new( - "status".to_string(), - InodeId::from_fsid_and_inodeid(PROCFS_ID, inode_offset + 1), - FileType::File, - 1, - )); - entries.push(Dirent::new( - "comm".to_string(), - InodeId::from_fsid_and_inodeid(PROCFS_ID, inode_offset + 2), - FileType::File, - 2, - )); - entries.push(Dirent::new( - "state".to_string(), - InodeId::from_fsid_and_inodeid(PROCFS_ID, inode_offset + 3), - FileType::File, - 3, - )); - entries.push(Dirent::new( - "cwd".to_string(), - InodeId::from_fsid_and_inodeid(PROCFS_ID, inode_offset + 4), - FileType::Symlink, - 4, - )); - entries.push(Dirent::new( - "stat".to_string(), - InodeId::from_fsid_and_inodeid(PROCFS_ID, inode_offset + 5), - FileType::File, - 5, - )); - - // honour start_offset - let entries = entries.into_iter().skip(start_offset as usize).collect(); - - Ok(Box::new(ProcDirStream { entries, idx: 0 })) - } -} - -enum TaskFileType { - Status, - Comm, - Cwd, - State, - Stat, -} - -impl TryFrom<&str> for TaskFileType { - type Error = (); - - fn try_from(value: &str) -> core::result::Result { - match value { - "status" => Ok(TaskFileType::Status), - "comm" => Ok(TaskFileType::Comm), - "state" => Ok(TaskFileType::State), - "stat" => Ok(TaskFileType::Stat), - "cwd" => Ok(TaskFileType::Cwd), - _ => Err(()), - } - } -} - -struct ProcTaskFileInode { - id: InodeId, - file_type: TaskFileType, - attr: FileAttr, - pid: Tgid, -} - -impl ProcTaskFileInode { - fn new(pid: Tgid, file_type: TaskFileType, inode_id: InodeId) -> Self { - Self { - id: inode_id, - attr: FileAttr { - file_type: match file_type { - TaskFileType::Status - | TaskFileType::Comm - | TaskFileType::State - | TaskFileType::Stat => FileType::File, - TaskFileType::Cwd => FileType::Symlink, - }, - mode: FilePermissions::from_bits_retain(0o444), - ..FileAttr::default() - }, - pid, - file_type, - } - } -} - -#[async_trait] -impl Inode for ProcTaskFileInode { - fn id(&self) -> InodeId { - self.id - } - - async fn lookup(&self, _name: &str) -> Result> { - Err(FsError::NotADirectory.into()) - } - - async fn getattr(&self) -> Result { - Ok(self.attr.clone()) - } - - async fn readdir(&self, _start_offset: u64) -> Result> { - Err(FsError::NotADirectory.into()) - } - - async fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result { - let pid = self.pid; - // TODO: task needs to be the main thread of the process - let task_list = TASK_LIST.lock_save_irq(); - let id = task_list - .iter() - .find(|(desc, _)| desc.tgid() == pid) - .map(|(desc, _)| *desc); - drop(task_list); - let task_details = if let Some(desc) = id { - find_task_by_descriptor(&desc) - } else { - None - }; - - let status_string = if let Some(task) = task_details { - let state = *task.state.lock_save_irq(); - let name = task.comm.lock_save_irq(); - match self.file_type { - TaskFileType::Status => format!( - "Name:\t{name} -State:\t{state} -Tgid:\t{tgid} -FDSize:\t{fd_size} -Pid:\t{pid} -Threads:\t{tasks}\n", - name = name.as_str(), - tgid = task.process.tgid, - fd_size = task.fd_table.lock_save_irq().len(), - tasks = task.process.tasks.lock_save_irq().len(), - ), - TaskFileType::Comm => format!("{name}\n", name = name.as_str()), - TaskFileType::State => format!("{state}\n"), - TaskFileType::Stat => { - let mut output = String::new(); - output.push_str(&format!("{} ", task.process.tgid.0)); // pid - output.push_str(&format!("({}) ", name.as_str())); // comm - output.push_str(&format!("{state} ")); // state - output.push_str(&format!("{} ", 0)); // ppid - output.push_str(&format!("{} ", 0)); // pgrp - output.push_str(&format!("{} ", task.process.sid.lock_save_irq().value())); // session - output.push_str(&format!("{} ", 0)); // tty_nr - output.push_str(&format!("{} ", 0)); // tpgid - output.push_str(&format!("{} ", 0)); // flags - output.push_str(&format!("{} ", 0)); // minflt - output.push_str(&format!("{} ", 0)); // cminflt - output.push_str(&format!("{} ", 0)); // majflt - output.push_str(&format!("{} ", 0)); // cmajflt - output.push_str(&format!("{} ", task.process.utime.load(Ordering::Relaxed))); // utime - output.push_str(&format!("{} ", task.process.stime.load(Ordering::Relaxed))); // stime - output.push_str(&format!("{} ", 0)); // cutime - output.push_str(&format!("{} ", 0)); // cstime - output.push_str(&format!("{} ", *task.process.priority.lock_save_irq())); // priority - output.push_str(&format!("{} ", 0)); // nice - output.push_str(&format!("{} ", 0)); // num_threads - output.push_str(&format!("{} ", 0)); // itrealvalue - output.push_str(&format!("{} ", 0)); // starttime - output.push_str(&format!("{} ", 0)); // vsize - output.push_str(&format!("{} ", 0)); // rss - output.push_str(&format!("{} ", 0)); // rsslim - output.push_str(&format!("{} ", 0)); // startcode - output.push_str(&format!("{} ", 0)); // endcode - output.push_str(&format!("{} ", 0)); // startstack - output.push_str(&format!("{} ", 0)); // kstkesp - output.push_str(&format!("{} ", 0)); // kstkeip - output.push_str(&format!("{} ", 0)); // signal - output.push_str(&format!("{} ", 0)); // blocked - output.push_str(&format!("{} ", 0)); // sigignore - output.push_str(&format!("{} ", 0)); // sigcatch - output.push_str(&format!("{} ", 0)); // wchan - output.push_str(&format!("{} ", 0)); // nswap - output.push_str(&format!("{} ", 0)); // cnswap - output.push_str(&format!("{} ", 0)); // exit_signal - output.push_str(&format!("{} ", 0)); // processor - output.push_str(&format!("{} ", 0)); // rt_priority - output.push_str(&format!("{} ", 0)); // policy - output.push_str(&format!("{} ", 0)); // delayacct_blkio_ticks - output.push_str(&format!("{} ", 0)); // guest_time - output.push_str(&format!("{} ", 0)); // cguest_time - output.push_str(&format!("{} ", 0)); // start_data - output.push_str(&format!("{} ", 0)); // end_data - output.push_str(&format!("{} ", 0)); // start_brk - output.push_str(&format!("{} ", 0)); // arg_start - output.push_str(&format!("{} ", 0)); // arg_end - output.push_str(&format!("{} ", 0)); // env_start - output.push_str(&format!("{} ", 0)); // env_end - output.push_str(&format!("{} ", 0)); // exit_code - output.push('\n'); - output - } - TaskFileType::Cwd => task.cwd.lock_save_irq().clone().1.as_str().to_string(), - } - } else { - "State:\tGone\n".to_string() - }; - - let bytes = status_string.as_bytes(); - let end = usize::min(bytes.len().saturating_sub(offset as usize), buf.len()); - if end == 0 { - return Ok(0); - } - let slice = &bytes[offset as usize..offset as usize + end]; - buf[..end].copy_from_slice(slice); - Ok(end) - } - - async fn readlink(&self) -> Result { - if let TaskFileType::Cwd = self.file_type { - let pid = self.pid; - let task_list = TASK_LIST.lock_save_irq(); - let id = task_list.iter().find(|(desc, _)| desc.tgid() == pid); - let task_details = if let Some((desc, _)) = id { - find_task_by_descriptor(desc) - } else { - None - }; - return if let Some(task) = task_details { - let cwd = task.cwd.lock_save_irq(); - Ok(cwd.1.clone()) - } else { - Err(FsError::NotFound.into()) - }; - } - Err(KernelError::NotSupported) - } -} - static PROCFS_INSTANCE: OnceLock> = OnceLock::new(); /// Initializes and/or returns the global singleton [`ProcFs`] instance. diff --git a/src/drivers/fs/proc/root.rs b/src/drivers/fs/proc/root.rs new file mode 100644 index 0000000..e6771a2 --- /dev/null +++ b/src/drivers/fs/proc/root.rs @@ -0,0 +1,100 @@ +use crate::drivers::fs::proc::get_inode_id; +use crate::drivers::fs::proc::task::ProcTaskInode; +use crate::process::{TASK_LIST, TaskDescriptor, Tid}; +use crate::sched::current::current_task; +use alloc::boxed::Box; +use alloc::string::ToString; +use alloc::sync::Arc; +use alloc::vec::Vec; +use async_trait::async_trait; +use libkernel::error; +use libkernel::error::FsError; +use libkernel::fs::attr::{FileAttr, FilePermissions}; +use libkernel::fs::{DirStream, Dirent, FileType, Inode, InodeId, PROCFS_ID, SimpleDirStream}; + +pub struct ProcRootInode { + id: InodeId, + attr: FileAttr, +} + +impl ProcRootInode { + pub fn new() -> Self { + Self { + id: InodeId::from_fsid_and_inodeid(PROCFS_ID, 0), + attr: FileAttr { + file_type: FileType::Directory, + mode: FilePermissions::from_bits_retain(0o555), + ..FileAttr::default() + }, + } + } +} + +#[async_trait] +impl Inode for ProcRootInode { + fn id(&self) -> InodeId { + self.id + } + + async fn lookup(&self, name: &str) -> error::Result> { + // Lookup a PID directory. + let desc = if name == "self" { + let current_task = current_task(); + TaskDescriptor::from_tgid_tid(current_task.pgid(), Tid::from_tgid(current_task.pgid())) + } else if name == "thread-self" { + let current_task = current_task(); + current_task.descriptor() + } else { + let pid: u32 = name.parse().map_err(|_| FsError::NotFound)?; + // Search for the task descriptor. + TASK_LIST + .lock_save_irq() + .keys() + .find(|d| d.tgid().value() == pid) + .cloned() + .ok_or(FsError::NotFound)? + }; + + Ok(Arc::new(ProcTaskInode::new( + desc, + InodeId::from_fsid_and_inodeid(self.id.fs_id(), get_inode_id(&[name])), + ))) + } + + async fn getattr(&self) -> error::Result { + Ok(self.attr.clone()) + } + + async fn readdir(&self, start_offset: u64) -> error::Result> { + let mut entries: Vec = Vec::new(); + // Gather task list under interrupt-safe lock. + let task_list = TASK_LIST.lock_save_irq(); + for (desc, _) in task_list + .iter() + .filter(|(_, task)| task.upgrade().is_some()) + { + // Use offset index as dirent offset. + let name = desc.tgid().value().to_string(); + let inode_id = + InodeId::from_fsid_and_inodeid(PROCFS_ID, ((desc.tgid().0 + 1) * 100) as u64); + entries.push(Dirent::new( + name, + inode_id, + FileType::Directory, + get_inode_id(&[&desc.tgid().value().to_string()]), + )); + } + let current_task = current_task(); + entries.push(Dirent::new( + "self".to_string(), + InodeId::from_fsid_and_inodeid( + PROCFS_ID, + get_inode_id(&[¤t_task.descriptor().tgid().value().to_string()]), + ), + FileType::Directory, + (entries.len() + 1) as u64, + )); + + Ok(Box::new(SimpleDirStream::new(entries, start_offset))) + } +} diff --git a/src/drivers/fs/proc/task/fd.rs b/src/drivers/fs/proc/task/fd.rs new file mode 100644 index 0000000..fd56944 --- /dev/null +++ b/src/drivers/fs/proc/task/fd.rs @@ -0,0 +1,153 @@ +use crate::drivers::fs::proc::{get_inode_id, procfs}; +use crate::process::fd_table::Fd; +use crate::process::{TaskDescriptor, find_task_by_descriptor}; +use crate::sched::current::current_task_shared; +use alloc::boxed::Box; +use alloc::format; +use alloc::string::ToString; +use alloc::sync::Arc; +use alloc::vec::Vec; +use async_trait::async_trait; +use libkernel::error::Result; +use libkernel::error::{FsError, KernelError}; +use libkernel::fs::attr::FileAttr; +use libkernel::fs::{ + DirStream, Dirent, FileType, Filesystem, Inode, InodeId, SimpleDirStream, SimpleFile, +}; + +pub struct ProcFdInode { + id: InodeId, + attr: FileAttr, + desc: TaskDescriptor, + fd_info: bool, +} + +impl ProcFdInode { + pub fn new(desc: TaskDescriptor, fd_info: bool, inode_id: InodeId) -> Self { + Self { + id: inode_id, + attr: FileAttr { + file_type: FileType::Directory, + // Define appropriate file attributes for fdinfo. + ..FileAttr::default() + }, + desc, + fd_info, + } + } + + fn dir_name(&self) -> &str { + if self.fd_info { "fdinfo" } else { "fd" } + } +} + +#[async_trait] +impl Inode for ProcFdInode { + fn id(&self) -> InodeId { + self.id + } + + async fn getattr(&self) -> Result { + Ok(self.attr.clone()) + } + + async fn lookup(&self, name: &str) -> Result> { + let fd: i32 = name.parse().map_err(|_| FsError::NotFound)?; + let task = current_task_shared(); + let fd_table = task.fd_table.lock_save_irq(); + if fd_table.get(Fd(fd)).is_none() { + return Err(FsError::NotFound.into()); + } + let fs = procfs(); + let inode_id = InodeId::from_fsid_and_inodeid( + fs.id(), + get_inode_id(&[&self.desc.tid().value().to_string(), self.dir_name(), name]), + ); + Ok(Arc::new(ProcFdFile::new( + self.desc, + self.fd_info, + fd, + inode_id, + ))) + } + + async fn readdir(&self, start_offset: u64) -> Result> { + let task = find_task_by_descriptor(&self.desc).ok_or(FsError::NotFound)?; + let fd_table = task.fd_table.lock_save_irq(); + let mut entries = Vec::new(); + for fd in 0..fd_table.len() { + if fd_table.get(Fd(fd as i32)).is_none() { + continue; + } + let fd_str = fd.to_string(); + entries.push(Dirent { + id: InodeId::from_fsid_and_inodeid( + self.id.fs_id(), + get_inode_id(&[ + &self.desc.tid().value().to_string(), + self.dir_name(), + &fd_str, + ]), + ), + offset: fd as _, + file_type: FileType::File, + name: fd_str, + }); + } + + Ok(Box::new(SimpleDirStream::new(entries, start_offset))) + } +} + +// TODO: Support fd links in /proc/[pid]/fd/ + +pub struct ProcFdFile { + id: InodeId, + attr: FileAttr, + desc: TaskDescriptor, + fd_info: bool, + fd: i32, +} + +impl ProcFdFile { + pub fn new(desc: TaskDescriptor, fd_info: bool, fd: i32, inode_id: InodeId) -> Self { + Self { + id: inode_id, + attr: FileAttr { + file_type: FileType::File, + // Define appropriate file attributes for fdinfo file. + ..FileAttr::default() + }, + desc, + fd_info, + fd, + } + } +} + +#[async_trait] +impl SimpleFile for ProcFdFile { + fn id(&self) -> InodeId { + self.id + } + + async fn getattr(&self) -> Result { + Ok(self.attr.clone()) + } + + async fn read(&self) -> Result> { + let task = find_task_by_descriptor(&self.desc).ok_or(FsError::NotFound)?; + let fd_entry = task + .fd_table + .lock_save_irq() + .get(Fd(self.fd)) + .ok_or(FsError::NotFound)?; + let (_, ctx) = &mut *fd_entry.lock().await; + let info_string = format!("pos: {}\nflags: {}", ctx.pos, ctx.flags.bits()); + if self.fd_info { + Ok(info_string.into_bytes()) + } else { + Err(KernelError::NotSupported) + } + } +} diff --git a/src/drivers/fs/proc/task/mod.rs b/src/drivers/fs/proc/task/mod.rs new file mode 100644 index 0000000..e692db3 --- /dev/null +++ b/src/drivers/fs/proc/task/mod.rs @@ -0,0 +1,134 @@ +mod fd; +// TODO: allowlist this across the codebase +#[expect(clippy::module_inception)] +mod task; +mod task_file; + +use crate::drivers::fs::proc::task::task_file::{ProcTaskFileInode, TaskFileType}; +use crate::drivers::fs::proc::{get_inode_id, procfs}; +use crate::process::TaskDescriptor; +use alloc::boxed::Box; +use alloc::string::ToString; +use alloc::sync::Arc; +use alloc::vec::Vec; +use async_trait::async_trait; +use libkernel::error::FsError; +use libkernel::fs::attr::{FileAttr, FilePermissions}; +use libkernel::fs::{ + DirStream, Dirent, FileType, Filesystem, Inode, InodeId, PROCFS_ID, SimpleDirStream, +}; + +pub struct ProcTaskInode { + id: InodeId, + attr: FileAttr, + desc: TaskDescriptor, +} + +impl ProcTaskInode { + pub fn new(desc: TaskDescriptor, inode_id: InodeId) -> Self { + Self { + id: inode_id, + attr: FileAttr { + file_type: FileType::Directory, + mode: FilePermissions::from_bits_retain(0o555), + ..FileAttr::default() + }, + desc, + } + } +} + +#[async_trait] +impl Inode for ProcTaskInode { + fn id(&self) -> InodeId { + self.id + } + + async fn lookup(&self, name: &str) -> libkernel::error::Result> { + let fs = procfs(); + let inode_id = InodeId::from_fsid_and_inodeid( + fs.id(), + get_inode_id(&[&self.desc.tid().value().to_string(), name]), + ); + if name == "fdinfo" { + return Ok(Arc::new(fd::ProcFdInode::new(self.desc, true, inode_id))); + } else if name == "fd" { + return Ok(Arc::new(fd::ProcFdInode::new(self.desc, false, inode_id))); + } else if name == "task" && self.desc.tid().value() == self.desc.tgid().value() { + return Ok(Arc::new(task::ProcTaskDirInode::new( + self.desc.tgid(), + inode_id, + ))); + } + if let Ok(file_type) = TaskFileType::try_from(name) { + Ok(Arc::new(ProcTaskFileInode::new( + self.desc.tid(), + file_type, + inode_id, + ))) + } else { + Err(FsError::NotFound.into()) + } + } + + async fn getattr(&self) -> libkernel::error::Result { + Ok(self.attr.clone()) + } + + async fn readdir(&self, start_offset: u64) -> libkernel::error::Result> { + let mut entries: Vec = Vec::new(); + let initial_str = self.desc.tid().value().to_string(); + entries.push(Dirent::new( + "status".to_string(), + InodeId::from_fsid_and_inodeid(PROCFS_ID, get_inode_id(&[&initial_str, "status"])), + FileType::File, + 1, + )); + entries.push(Dirent::new( + "comm".to_string(), + InodeId::from_fsid_and_inodeid(PROCFS_ID, get_inode_id(&[&initial_str, "comm"])), + FileType::File, + 2, + )); + entries.push(Dirent::new( + "state".to_string(), + InodeId::from_fsid_and_inodeid(PROCFS_ID, get_inode_id(&[&initial_str, "state"])), + FileType::File, + 3, + )); + entries.push(Dirent::new( + "cwd".to_string(), + InodeId::from_fsid_and_inodeid(PROCFS_ID, get_inode_id(&[&initial_str, "cwd"])), + FileType::Symlink, + 4, + )); + entries.push(Dirent::new( + "stat".to_string(), + InodeId::from_fsid_and_inodeid(PROCFS_ID, get_inode_id(&[&initial_str, "stat"])), + FileType::File, + 5, + )); + entries.push(Dirent::new( + "fd".to_string(), + InodeId::from_fsid_and_inodeid(PROCFS_ID, get_inode_id(&[&initial_str, "fd"])), + FileType::Directory, + 6, + )); + entries.push(Dirent::new( + "fdinfo".to_string(), + InodeId::from_fsid_and_inodeid(PROCFS_ID, get_inode_id(&[&initial_str, "fdinfo"])), + FileType::Directory, + 7, + )); + if self.desc.tid().value() == self.desc.tgid().value() { + entries.push(Dirent::new( + "task".to_string(), + InodeId::from_fsid_and_inodeid(PROCFS_ID, get_inode_id(&[&initial_str, "task"])), + FileType::Directory, + 8, + )); + } + + Ok(Box::new(SimpleDirStream::new(entries, start_offset))) + } +} diff --git a/src/drivers/fs/proc/task/task.rs b/src/drivers/fs/proc/task/task.rs new file mode 100644 index 0000000..d737714 --- /dev/null +++ b/src/drivers/fs/proc/task/task.rs @@ -0,0 +1,87 @@ +use crate::drivers::fs::proc::task::ProcTaskInode; +use crate::drivers::fs::proc::{get_inode_id, procfs}; +use crate::process::thread_group::Tgid; +use crate::process::{TaskDescriptor, Tid, find_task_by_descriptor}; +use alloc::boxed::Box; +use alloc::string::ToString; +use alloc::sync::Arc; +use alloc::vec::Vec; +use async_trait::async_trait; +use libkernel::error::FsError; +use libkernel::fs::attr::FileAttr; +use libkernel::fs::{DirStream, Dirent, FileType, Filesystem, Inode, InodeId, SimpleDirStream}; + +pub struct ProcTaskDirInode { + id: InodeId, + attr: FileAttr, + tgid: Tgid, +} + +impl ProcTaskDirInode { + pub fn new(tgid: Tgid, inode_id: InodeId) -> Self { + Self { + id: inode_id, + attr: FileAttr { + file_type: FileType::Directory, + // Define appropriate file attributes for fdinfo. + ..FileAttr::default() + }, + tgid, + } + } +} + +#[async_trait] +impl Inode for ProcTaskDirInode { + fn id(&self) -> InodeId { + self.id + } + + async fn getattr(&self) -> libkernel::error::Result { + Ok(self.attr.clone()) + } + + async fn lookup(&self, name: &str) -> libkernel::error::Result> { + let tid = match name.parse::() { + Ok(tid) => Tid(tid), + Err(_) => return Err(FsError::NotFound.into()), + }; + let fs = procfs(); + let inode_id = InodeId::from_fsid_and_inodeid( + fs.id(), + get_inode_id(&[&self.tgid.value().to_string(), &tid.value().to_string()]), + ); + let desc = TaskDescriptor::from_tgid_tid(self.tgid, tid); + find_task_by_descriptor(&desc).ok_or(FsError::NotFound)?; + Ok(Arc::new(ProcTaskInode::new(desc, inode_id))) + } + + async fn readdir(&self, start_offset: u64) -> libkernel::error::Result> { + let task = find_task_by_descriptor(&TaskDescriptor::from_tgid_tid( + self.tgid, + Tid::from_tgid(self.tgid), + )) + .ok_or(FsError::NotFound)?; + let tasks = task.process.tasks.lock_save_irq(); + let mut entries = Vec::new(); + for (i, (_tid, task)) in tasks.iter().enumerate().skip(start_offset as usize) { + let Some(task) = task.upgrade() else { + continue; + }; + let id = InodeId::from_fsid_and_inodeid( + procfs().id(), + get_inode_id(&[ + &self.tgid.value().to_string(), + &task.tid.value().to_string(), + ]), + ); + entries.push(Dirent { + id, + offset: (i + 1) as u64, + file_type: FileType::Directory, + name: task.tid.value().to_string(), + }); + } + Ok(Box::new(SimpleDirStream::new(entries, start_offset))) + } +} diff --git a/src/drivers/fs/proc/task/task_file.rs b/src/drivers/fs/proc/task/task_file.rs new file mode 100644 index 0000000..c73a3bc --- /dev/null +++ b/src/drivers/fs/proc/task/task_file.rs @@ -0,0 +1,191 @@ +use crate::process::{TASK_LIST, Tid, find_task_by_descriptor}; +use alloc::boxed::Box; +use alloc::format; +use alloc::string::{String, ToString}; +use alloc::vec::Vec; +use async_trait::async_trait; +use core::sync::atomic::Ordering; +use libkernel::error::{FsError, KernelError}; +use libkernel::fs::attr::{FileAttr, FilePermissions}; +use libkernel::fs::pathbuf::PathBuf; +use libkernel::fs::{FileType, InodeId, SimpleFile}; + +pub enum TaskFileType { + Status, + Comm, + Cwd, + State, + Stat, +} + +impl TryFrom<&str> for TaskFileType { + type Error = (); + + fn try_from(value: &str) -> Result { + match value { + "status" => Ok(TaskFileType::Status), + "comm" => Ok(TaskFileType::Comm), + "state" => Ok(TaskFileType::State), + "stat" => Ok(TaskFileType::Stat), + "cwd" => Ok(TaskFileType::Cwd), + _ => Err(()), + } + } +} + +pub struct ProcTaskFileInode { + id: InodeId, + file_type: TaskFileType, + attr: FileAttr, + tid: Tid, +} + +impl ProcTaskFileInode { + pub fn new(tid: Tid, file_type: TaskFileType, inode_id: InodeId) -> Self { + Self { + id: inode_id, + attr: FileAttr { + file_type: match file_type { + TaskFileType::Status + | TaskFileType::Comm + | TaskFileType::State + | TaskFileType::Stat => FileType::File, + TaskFileType::Cwd => FileType::Symlink, + }, + mode: FilePermissions::from_bits_retain(0o444), + ..FileAttr::default() + }, + tid, + file_type, + } + } +} + +#[async_trait] +impl SimpleFile for ProcTaskFileInode { + fn id(&self) -> InodeId { + self.id + } + + async fn getattr(&self) -> libkernel::error::Result { + Ok(self.attr.clone()) + } + + async fn read(&self) -> libkernel::error::Result> { + let tid = self.tid; + let task_list = TASK_LIST.lock_save_irq(); + let id = task_list + .iter() + .find(|(desc, _)| desc.tid() == tid) + .map(|(desc, _)| *desc); + drop(task_list); + let task_details = if let Some(desc) = id { + find_task_by_descriptor(&desc) + } else { + None + }; + + let status_string = if let Some(task) = task_details { + let state = *task.state.lock_save_irq(); + let name = task.comm.lock_save_irq(); + match self.file_type { + TaskFileType::Status => format!( + "Name:\t{name} +State:\t{state} +Tgid:\t{tgid} +FDSize:\t{fd_size} +Pid:\t{pid} +Threads:\t{tasks}\n", + name = name.as_str(), + tgid = task.process.tgid, + fd_size = task.fd_table.lock_save_irq().len(), + pid = task.tid.value(), + tasks = task.process.tasks.lock_save_irq().len(), + ), + TaskFileType::Comm => format!("{name}\n", name = name.as_str()), + TaskFileType::State => format!("{state}\n"), + TaskFileType::Stat => { + let mut output = String::new(); + output.push_str(&format!("{} ", task.process.tgid.value())); // pid + output.push_str(&format!("({}) ", name.as_str())); // comm + output.push_str(&format!("{} ", state)); // state + output.push_str(&format!("{} ", 0)); // ppid + output.push_str(&format!("{} ", 0)); // pgrp + output.push_str(&format!("{} ", task.process.sid.lock_save_irq().value())); // session + output.push_str(&format!("{} ", 0)); // tty_nr + output.push_str(&format!("{} ", 0)); // tpgid + output.push_str(&format!("{} ", 0)); // flags + output.push_str(&format!("{} ", 0)); // minflt + output.push_str(&format!("{} ", 0)); // cminflt + output.push_str(&format!("{} ", 0)); // majflt + output.push_str(&format!("{} ", 0)); // cmajflt + output.push_str(&format!("{} ", task.process.utime.load(Ordering::Relaxed))); // utime + output.push_str(&format!("{} ", task.process.stime.load(Ordering::Relaxed))); // stime + output.push_str(&format!("{} ", 0)); // cutime + output.push_str(&format!("{} ", 0)); // cstime + output.push_str(&format!("{} ", *task.process.priority.lock_save_irq())); // priority + output.push_str(&format!("{} ", 0)); // nice + output.push_str(&format!("{} ", 0)); // num_threads + output.push_str(&format!("{} ", 0)); // itrealvalue + output.push_str(&format!("{} ", 0)); // starttime + output.push_str(&format!("{} ", 0)); // vsize + output.push_str(&format!("{} ", 0)); // rss + output.push_str(&format!("{} ", 0)); // rsslim + output.push_str(&format!("{} ", 0)); // startcode + output.push_str(&format!("{} ", 0)); // endcode + output.push_str(&format!("{} ", 0)); // startstack + output.push_str(&format!("{} ", 0)); // kstkesp + output.push_str(&format!("{} ", 0)); // kstkeip + output.push_str(&format!("{} ", 0)); // signal + output.push_str(&format!("{} ", 0)); // blocked + output.push_str(&format!("{} ", 0)); // sigignore + output.push_str(&format!("{} ", 0)); // sigcatch + output.push_str(&format!("{} ", 0)); // wchan + output.push_str(&format!("{} ", 0)); // nswap + output.push_str(&format!("{} ", 0)); // cnswap + output.push_str(&format!("{} ", 0)); // exit_signal + output.push_str(&format!("{} ", 0)); // processor + output.push_str(&format!("{} ", 0)); // rt_priority + output.push_str(&format!("{} ", 0)); // policy + output.push_str(&format!("{} ", 0)); // delayacct_blkio_ticks + output.push_str(&format!("{} ", 0)); // guest_time + output.push_str(&format!("{} ", 0)); // cguest_time + output.push_str(&format!("{} ", 0)); // start_data + output.push_str(&format!("{} ", 0)); // end_data + output.push_str(&format!("{} ", 0)); // start_brk + output.push_str(&format!("{} ", 0)); // arg_start + output.push_str(&format!("{} ", 0)); // arg_end + output.push_str(&format!("{} ", 0)); // env_start + output.push_str(&format!("{} ", 0)); // env_end + output.push_str(&format!("{} ", 0)); // exit_code + output.push('\n'); + output + } + TaskFileType::Cwd => task.cwd.lock_save_irq().clone().1.as_str().to_string(), + } + } else { + "State:\tGone\n".to_string() + }; + Ok(status_string.into_bytes()) + } + + async fn readlink(&self) -> libkernel::error::Result { + if let TaskFileType::Cwd = self.file_type { + let tid = self.tid; + let task_list = TASK_LIST.lock_save_irq(); + let id = task_list.iter().find(|(desc, _)| desc.tid() == tid); + let task_details = if let Some((desc, _)) = id { + find_task_by_descriptor(desc) + } else { + None + }; + return if let Some(task) = task_details { + let cwd = task.cwd.lock_save_irq(); + Ok(cwd.1.clone()) + } else { + Err(FsError::NotFound.into()) + }; + } + Err(KernelError::NotSupported) + } +}