procfs refactor

This commit is contained in:
Ashwin Naren
2026-01-17 22:10:33 -08:00
parent c5a0976a73
commit 2d34ebcef9
9 changed files with 771 additions and 413 deletions

7
Cargo.lock generated
View File

@@ -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"

View File

@@ -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"]

View File

@@ -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<FileAttr>;
async fn read(&self) -> Result<Vec<u8>>;
async fn readlink(&self) -> Result<PathBuf> {
Err(KernelError::NotSupported)
}
}
#[async_trait]
impl<T> 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<usize> {
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<FileAttr> {
self.getattr().await
}
async fn lookup(&self, _name: &str) -> Result<Arc<dyn Inode>> {
Err(FsError::NotADirectory.into())
}
async fn readdir(&self, _start_offset: u64) -> crate::error::Result<Box<dyn DirStream>> {
Err(FsError::NotADirectory.into())
}
async fn readlink(&self) -> Result<PathBuf> {
self.readlink().await
}
}
pub struct SimpleDirStream {
entries: Vec<Dirent>,
idx: usize,
}
impl SimpleDirStream {
pub fn new(entries: Vec<Dirent>, start_offset: u64) -> Self {
Self {
entries,
idx: start_offset as usize,
}
}
}
#[async_trait]
impl DirStream for SimpleDirStream {
async fn next_entry(&mut self) -> Result<Option<Dirent>> {
Ok(if let Some(entry) = self.entries.get(self.idx).cloned() {
self.idx += 1;
Some(entry)
} else {
None
})
}
}

View File

@@ -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<ProcRootInode>,
next_inode_id: AtomicU64,
}
impl ProcFs {
fn new() -> Arc<Self> {
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<Dirent>,
idx: usize,
}
#[async_trait]
impl DirStream for ProcDirStream {
async fn next_entry(&mut self) -> Result<Option<Dirent>> {
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<Arc<dyn Inode>> {
// 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::<KernelError>::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<FileAttr> {
Ok(self.attr.clone())
}
async fn readdir(&self, start_offset: u64) -> Result<Box<dyn DirStream>> {
let mut entries: Vec<Dirent> = 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<Arc<dyn Inode>> {
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<FileAttr> {
Ok(self.attr.clone())
}
async fn readdir(&self, start_offset: u64) -> Result<Box<dyn DirStream>> {
let mut entries: Vec<Dirent> = 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<TaskFileType, Self::Error> {
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<Arc<dyn Inode>> {
Err(FsError::NotADirectory.into())
}
async fn getattr(&self) -> Result<FileAttr> {
Ok(self.attr.clone())
}
async fn readdir(&self, _start_offset: u64) -> Result<Box<dyn DirStream>> {
Err(FsError::NotADirectory.into())
}
async fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<usize> {
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<PathBuf> {
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<Arc<ProcFs>> = OnceLock::new();
/// Initializes and/or returns the global singleton [`ProcFs`] instance.

100
src/drivers/fs/proc/root.rs Normal file
View File

@@ -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<Arc<dyn Inode>> {
// 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<FileAttr> {
Ok(self.attr.clone())
}
async fn readdir(&self, start_offset: u64) -> error::Result<Box<dyn DirStream>> {
let mut entries: Vec<Dirent> = 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(&[&current_task.descriptor().tgid().value().to_string()]),
),
FileType::Directory,
(entries.len() + 1) as u64,
));
Ok(Box::new(SimpleDirStream::new(entries, start_offset)))
}
}

View File

@@ -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<FileAttr> {
Ok(self.attr.clone())
}
async fn lookup(&self, name: &str) -> Result<Arc<dyn Inode>> {
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<Box<dyn DirStream>> {
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<FileAttr> {
Ok(self.attr.clone())
}
async fn read(&self) -> Result<Vec<u8>> {
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)
}
}
}

View File

@@ -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<Arc<dyn Inode>> {
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<FileAttr> {
Ok(self.attr.clone())
}
async fn readdir(&self, start_offset: u64) -> libkernel::error::Result<Box<dyn DirStream>> {
let mut entries: Vec<Dirent> = 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)))
}
}

View File

@@ -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<FileAttr> {
Ok(self.attr.clone())
}
async fn lookup(&self, name: &str) -> libkernel::error::Result<Arc<dyn Inode>> {
let tid = match name.parse::<u32>() {
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<Box<dyn DirStream>> {
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)))
}
}

View File

@@ -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<TaskFileType, Self::Error> {
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<FileAttr> {
Ok(self.attr.clone())
}
async fn read(&self) -> libkernel::error::Result<Vec<u8>> {
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<PathBuf> {
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)
}
}