use alloc::{
borrow::Cow,
boxed::Box,
format,
string::{String, ToString},
sync::{Arc, Weak},
vec,
vec::Vec,
};
use core::{
ffi::CStr,
iter,
sync::atomic::{AtomicUsize, Ordering},
};
use ax_task::{AxTaskRef, WeakAxTaskRef, current};
use axfs_ng_vfs::{Filesystem, NodeType, VfsError, VfsResult};
use indoc::indoc;
use starry_process::Process;
use crate::{
file::FD_TABLE,
pseudofs::{
DirMaker, DirMapping, NodeOpsMux, RwFile, SimpleDir, SimpleDirOps, SimpleFile,
SimpleFileOperation, SimpleFs,
},
task::{AsThread, TaskStat, get_task, tasks},
};
const DUMMY_MEMINFO: &str = indoc! {"
MemTotal: 32536204 kB
MemFree: 5506524 kB
MemAvailable: 18768344 kB
Buffers: 3264 kB
Cached: 14454588 kB
SwapCached: 0 kB
Active: 18229700 kB
Inactive: 6540624 kB
Active(anon): 11380224 kB
Inactive(anon): 0 kB
Active(file): 6849476 kB
Inactive(file): 6540624 kB
Unevictable: 930088 kB
Mlocked: 1136 kB
SwapTotal: 4194300 kB
SwapFree: 4194300 kB
Zswap: 0 kB
Zswapped: 0 kB
Dirty: 47952 kB
Writeback: 0 kB
AnonPages: 10992512 kB
Mapped: 1361184 kB
Shmem: 1068056 kB
KReclaimable: 341440 kB
Slab: 628996 kB
SReclaimable: 341440 kB
SUnreclaim: 287556 kB
KernelStack: 28704 kB
PageTables: 85308 kB
SecPageTables: 2084 kB
NFS_Unstable: 0 kB
Bounce: 0 kB
WritebackTmp: 0 kB
CommitLimit: 20462400 kB
Committed_AS: 45105316 kB
VmallocTotal: 34359738367 kB
VmallocUsed: 205924 kB
VmallocChunk: 0 kB
Percpu: 23840 kB
HardwareCorrupted: 0 kB
AnonHugePages: 1417216 kB
ShmemHugePages: 0 kB
ShmemPmdMapped: 0 kB
FileHugePages: 477184 kB
FilePmdMapped: 288768 kB
CmaTotal: 0 kB
CmaFree: 0 kB
Unaccepted: 0 kB
HugePages_Total: 0
HugePages_Free: 0
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 2048 kB
Hugetlb: 0 kB
DirectMap4k: 1739900 kB
DirectMap2M: 31492096 kB
DirectMap1G: 1048576 kB
"};
pub fn new_procfs() -> Filesystem {
SimpleFs::new_with("proc".into(), 0x9fa0, builder)
}
struct ProcessTaskDir {
fs: Arc<SimpleFs>,
process: Weak<Process>,
}
impl SimpleDirOps for ProcessTaskDir {
fn child_names<'a>(&'a self) -> Box<dyn Iterator<Item = Cow<'a, str>> + 'a> {
let Some(process) = self.process.upgrade() else {
return Box::new(iter::empty());
};
Box::new(
process
.threads()
.into_iter()
.map(|tid| tid.to_string().into()),
)
}
fn lookup_child(&self, name: &str) -> VfsResult<NodeOpsMux> {
let process = self.process.upgrade().ok_or(VfsError::NotFound)?;
let tid = name.parse::<u32>().map_err(|_| VfsError::NotFound)?;
let task = get_task(tid).map_err(|_| VfsError::NotFound)?;
if task.as_thread().proc_data.proc.pid() != process.pid() {
return Err(VfsError::NotFound);
}
Ok(NodeOpsMux::Dir(SimpleDir::new_maker(
self.fs.clone(),
Arc::new(ThreadDir {
fs: self.fs.clone(),
task: Arc::downgrade(&task),
}),
)))
}
fn is_cacheable(&self) -> bool {
false
}
}
#[rustfmt::skip]
fn task_status(task: &AxTaskRef) -> String {
format!(
"Tgid:\t{}\n\
Pid:\t{}\n\
Uid:\t0 0 0 0\n\
Gid:\t0 0 0 0\n\
Cpus_allowed:\t1\n\
Cpus_allowed_list:\t0\n\
Mems_allowed:\t1\n\
Mems_allowed_list:\t0",
task.as_thread().proc_data.proc.pid(),
task.id().as_u64()
)
}
struct ThreadFdDir {
fs: Arc<SimpleFs>,
task: WeakAxTaskRef,
}
impl SimpleDirOps for ThreadFdDir {
fn child_names<'a>(&'a self) -> Box<dyn Iterator<Item = Cow<'a, str>> + 'a> {
let Some(task) = self.task.upgrade() else {
return Box::new(iter::empty());
};
let ids = FD_TABLE
.scope(&task.as_thread().proc_data.scope.read())
.read()
.ids()
.map(|id| Cow::Owned(id.to_string()))
.collect::<Vec<_>>();
Box::new(ids.into_iter())
}
fn lookup_child(&self, name: &str) -> VfsResult<NodeOpsMux> {
let fs = self.fs.clone();
let task = self.task.upgrade().ok_or(VfsError::NotFound)?;
let fd = name.parse::<u32>().map_err(|_| VfsError::NotFound)?;
let path = FD_TABLE
.scope(&task.as_thread().proc_data.scope.read())
.read()
.get(fd as _)
.ok_or(VfsError::NotFound)?
.inner
.path()
.into_owned();
Ok(SimpleFile::new(fs, NodeType::Symlink, move || Ok(path.clone())).into())
}
fn is_cacheable(&self) -> bool {
false
}
}
struct ThreadDir {
fs: Arc<SimpleFs>,
task: WeakAxTaskRef,
}
impl SimpleDirOps for ThreadDir {
fn child_names<'a>(&'a self) -> Box<dyn Iterator<Item = Cow<'a, str>> + 'a> {
Box::new(
[
"stat",
"status",
"oom_score_adj",
"task",
"maps",
"mounts",
"cmdline",
"comm",
"exe",
"fd",
]
.into_iter()
.map(Cow::Borrowed),
)
}
fn lookup_child(&self, name: &str) -> VfsResult<NodeOpsMux> {
let fs = self.fs.clone();
let task = self.task.upgrade().ok_or(VfsError::NotFound)?;
Ok(match name {
"stat" => SimpleFile::new_regular(fs, move || {
Ok(format!("{}", TaskStat::from_thread(&task)?).into_bytes())
})
.into(),
"status" => SimpleFile::new_regular(fs, move || Ok(task_status(&task))).into(),
"oom_score_adj" => SimpleFile::new_regular(
fs,
RwFile::new(move |req| match req {
SimpleFileOperation::Read => Ok(Some(
task.as_thread().oom_score_adj().to_string().into_bytes(),
)),
SimpleFileOperation::Write(data) => {
if !data.is_empty() {
let value = str::from_utf8(data)
.ok()
.and_then(|it| it.parse::<i32>().ok())
.ok_or(VfsError::InvalidInput)?;
task.as_thread().set_oom_score_adj(value);
}
Ok(None)
}
}),
)
.into(),
"task" => SimpleDir::new_maker(
fs.clone(),
Arc::new(ProcessTaskDir {
fs,
process: Arc::downgrade(&task.as_thread().proc_data.proc),
}),
)
.into(),
"maps" => SimpleFile::new_regular(fs, move || {
Ok(indoc! {"
7f000000-7f001000 r--p 00000000 00:00 0 [vdso]
7f001000-7f003000 r-xp 00001000 00:00 0 [vdso]
7f003000-7f005000 r--p 00003000 00:00 0 [vdso]
7f005000-7f007000 rw-p 00005000 00:00 0 [vdso]
"})
})
.into(),
"mounts" => SimpleFile::new_regular(fs, move || {
Ok("proc /proc proc rw,nosuid,nodev,noexec,relatime 0 0\n")
})
.into(),
"cmdline" => SimpleFile::new_regular(fs, move || {
let cmdline = task.as_thread().proc_data.cmdline.read();
let mut buf = Vec::new();
for arg in cmdline.iter() {
buf.extend_from_slice(arg.as_bytes());
buf.push(0);
}
Ok(buf)
})
.into(),
"comm" => SimpleFile::new_regular(
fs,
RwFile::new(move |req| match req {
SimpleFileOperation::Read => {
let mut bytes = vec![0; 16];
let name = task.name();
let copy_len = name.len().min(15);
bytes[..copy_len].copy_from_slice(&name.as_bytes()[..copy_len]);
bytes[copy_len] = b'\n';
Ok(Some(bytes))
}
SimpleFileOperation::Write(data) => {
if !data.is_empty() {
let mut input = [0; 16];
let copy_len = data.len().min(15);
input[..copy_len].copy_from_slice(&data[..copy_len]);
task.set_name(
CStr::from_bytes_until_nul(&input)
.map_err(|_| VfsError::InvalidInput)?
.to_str()
.map_err(|_| VfsError::InvalidInput)?,
);
}
Ok(None)
}
}),
)
.into(),
"exe" => SimpleFile::new(fs, NodeType::Symlink, move || {
Ok(task.as_thread().proc_data.exe_path.read().clone())
})
.into(),
"fd" => SimpleDir::new_maker(
fs.clone(),
Arc::new(ThreadFdDir {
fs,
task: Arc::downgrade(&task),
}),
)
.into(),
_ => return Err(VfsError::NotFound),
})
}
fn is_cacheable(&self) -> bool {
false
}
}
struct ProcFsHandler(Arc<SimpleFs>);
impl SimpleDirOps for ProcFsHandler {
fn child_names<'a>(&'a self) -> Box<dyn Iterator<Item = Cow<'a, str>> + 'a> {
Box::new(
tasks()
.into_iter()
.map(|task| task.id().as_u64().to_string().into())
.chain([Cow::Borrowed("self")]),
)
}
fn lookup_child(&self, name: &str) -> VfsResult<NodeOpsMux> {
let task = if name == "self" {
current().clone()
} else {
let tid = name.parse::<u32>().map_err(|_| VfsError::NotFound)?;
get_task(tid).map_err(|_| VfsError::NotFound)?
};
let node = NodeOpsMux::Dir(SimpleDir::new_maker(
self.0.clone(),
Arc::new(ThreadDir {
fs: self.0.clone(),
task: Arc::downgrade(&task),
}),
));
Ok(node)
}
fn is_cacheable(&self) -> bool {
false
}
}
fn builder(fs: Arc<SimpleFs>) -> DirMaker {
let mut root = DirMapping::new();
root.add(
"mounts",
SimpleFile::new_regular(fs.clone(), || {
Ok("proc /proc proc rw,nosuid,nodev,noexec,relatime 0 0\n")
}),
);
root.add(
"meminfo",
SimpleFile::new_regular(fs.clone(), || Ok(DUMMY_MEMINFO)),
);
root.add(
"meminfo2",
SimpleFile::new_regular(fs.clone(), || {
let allocator = ax_alloc::global_allocator();
Ok(format!("{:?}\n", allocator.usages()))
}),
);
root.add(
"instret",
SimpleFile::new_regular(fs.clone(), || {
#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))]
{
Ok(format!("{}\n", riscv::register::instret::read64()))
}
#[cfg(not(any(target_arch = "riscv32", target_arch = "riscv64")))]
{
Ok("0\n".to_string())
}
}),
);
{
static IRQ_CNT: AtomicUsize = AtomicUsize::new(0);
ax_task::register_timer_callback(|_| {
IRQ_CNT.fetch_add(1, Ordering::Relaxed);
});
root.add(
"interrupts",
SimpleFile::new_regular(fs.clone(), || {
Ok(format!("0: {}", IRQ_CNT.load(Ordering::Relaxed)))
}),
);
}
root.add("sys", {
let mut sys = DirMapping::new();
sys.add("kernel", {
let mut kernel = DirMapping::new();
kernel.add(
"pid_max",
SimpleFile::new_regular(fs.clone(), || Ok("32768\n")),
);
SimpleDir::new_maker(fs.clone(), Arc::new(kernel))
});
SimpleDir::new_maker(fs.clone(), Arc::new(sys))
});
let proc_dir = ProcFsHandler(fs.clone());
SimpleDir::new_maker(fs, Arc::new(proc_dir.chain(root)))
}