use alloc::{
borrow::Cow,
boxed::Box,
format,
string::{String, ToString},
sync::{Arc, Weak},
vec,
vec::Vec,
};
use core::{ffi::CStr, fmt::Write, iter, mem::size_of, sync::atomic::Ordering};
use ax_fs_ng::vfs::{FS_CONTEXT, current_fs_context};
use ax_lazyinit::LazyInit;
use ax_memory_addr::{MemoryAddr, VirtAddr};
#[cfg(target_arch = "aarch64")]
use ax_runtime::hal::pmu;
use ax_runtime::hal::{
paging::MappingFlags,
time::{monotonic_time, wall_time},
};
use ax_std::os::arceos::task::{
sched::{CpuId, CpuSet},
thread::ThreadState,
};
use axfs_ng_vfs::{DeviceId, Filesystem, NodePermission, NodeType, VfsError, VfsResult};
use kernel_elf_parser::{AuxEntry, AuxType};
use ksym::KallsymsMapped;
use zerocopy::IntoBytes;
use crate::{
file::{FD_TABLE, PidFd},
mm::{MappingFileInfo, ProcessMemStats},
pseudofs::{
DirMaker, DirMapping, DirectRwFsFileOps, NodeOpsMux, RwFile, SeqObject, SimpleDir,
SimpleDirOps, SimpleFile, SimpleFileOperation, SimpleFs, SpecialFsFile,
},
task::{
Cred, PidNamespaceRef, PidNumber, PidView, Process, ProcessData, ROOT_PID_NS, TaskStat,
TgidNumber, Thread, TidNumber, UserTaskRef, WeakUserTaskRef, current_user_task, processes,
tasks,
},
};
fn upgrade_proc_task(task: &WeakUserTaskRef) -> VfsResult<Option<UserTaskRef>> {
(*task).upgrade().map_err(|_| VfsError::BadState)
}
fn require_proc_task(task: &WeakUserTaskRef) -> VfsResult<UserTaskRef> {
upgrade_proc_task(task)?.ok_or(VfsError::NotFound)
}
pub static KALLSYMS: LazyInit<KallsymsMapped<'static>> = LazyInit::new();
static BOOT_ID: LazyInit<String> = LazyInit::new();
fn read_kallsyms() -> KallsymsMapped<'static> {
unsafe extern "C" {
fn _stext();
fn _etext();
fn __kallsyms_start();
fn __kallsyms_end();
}
let kallsyms_start = __kallsyms_start as *const () as usize;
let kallsyms_end = __kallsyms_end as *const () as usize;
let kallsyms_sec_size = kallsyms_end - kallsyms_start;
let kallsyms_sec =
unsafe { core::slice::from_raw_parts(__kallsyms_start as *const u8, kallsyms_sec_size) };
let total_size =
KallsymsMapped::check_total_bytes(kallsyms_sec).expect("Invalid kallsyms format");
let kallsyms = &kallsyms_sec[..total_size as usize];
info!("Read kallsyms, size: {}KB", kallsyms.len() / 1024);
KallsymsMapped::from_blob(
kallsyms,
_stext as *const () as u64,
_etext as *const () as u64,
)
.expect("Failed to create KallsymsMapped")
}
fn procfs_visible_pid(view: &PidView, proc: &Process) -> Option<u32> {
view.visible_number(&proc.identity()).map(PidNumber::get)
}
fn boot_id_proc_file(fs: Arc<SimpleFs>) -> Option<Arc<SimpleFile>> {
let generated_boot_id = boot_id_from_entropy(ax_runtime::hal::boot::boot_entropy())?;
let boot_id = BOOT_ID.get_or_init(|| generated_boot_id).clone();
let file = SimpleFile::new_regular(fs, move || Ok(boot_id.clone()));
let now = wall_time();
file.set_attrs(
NodePermission::from_bits_truncate(0o444),
0,
0,
now,
now,
now,
);
Some(file)
}
fn boot_id_from_entropy(boot_entropy: Option<[u8; 32]>) -> Option<String> {
let boot_entropy = boot_entropy?;
let random_bytes = boot_entropy[..16]
.try_into()
.expect("boot entropy contains 16 UUID bytes");
Some(format_boot_id(random_bytes))
}
fn format_boot_id(mut random_bytes: [u8; 16]) -> String {
random_bytes[6] = (random_bytes[6] & 0x0f) | 0x40;
random_bytes[8] = (random_bytes[8] & 0x3f) | 0x80;
format!(
"{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:\
02x}{:02x}{:02x}\n",
random_bytes[0],
random_bytes[1],
random_bytes[2],
random_bytes[3],
random_bytes[4],
random_bytes[5],
random_bytes[6],
random_bytes[7],
random_bytes[8],
random_bytes[9],
random_bytes[10],
random_bytes[11],
random_bytes[12],
random_bytes[13],
random_bytes[14],
random_bytes[15],
)
}
fn render_meminfo() -> String {
let total = ax_runtime::hal::mem::total_ram_size();
let usages = ax_alloc::global_allocator().usages();
let used = usages.get(ax_alloc::UsageKind::RustHeap)
+ usages.get(ax_alloc::UsageKind::VirtMem)
+ usages.get(ax_alloc::UsageKind::PageCache)
+ usages.get(ax_alloc::UsageKind::PageTable)
+ usages.get(ax_alloc::UsageKind::TaskStack)
+ usages.get(ax_alloc::UsageKind::Dma)
+ usages.get(ax_alloc::UsageKind::Global);
let cached = usages.get(ax_alloc::UsageKind::PageCache);
let page_tables = usages.get(ax_alloc::UsageKind::PageTable);
let anon_pages = usages.get(ax_alloc::UsageKind::VirtMem);
let free = total.saturating_sub(used);
let total_kb = total / 1024;
let free_kb = free / 1024;
let cached_kb = cached / 1024;
let available_kb = free_kb + cached_kb;
let page_tables_kb = page_tables / 1024;
let anon_pages_kb = anon_pages / 1024;
format!(
"MemTotal: {total_kb:>10} kB\n\
MemFree: {free_kb:>10} kB\n\
MemAvailable: {available_kb:>10} kB\n\
Buffers: 0 kB\n\
Cached: {cached_kb:>10} kB\n\
SwapCached: 0 kB\n\
SwapTotal: 0 kB\n\
SwapFree: 0 kB\n\
Dirty: 0 kB\n\
Writeback: 0 kB\n\
AnonPages: {anon_pages_kb:>10} kB\n\
Mapped: 0 kB\n\
Shmem: 0 kB\n\
KReclaimable: 0 kB\n\
Slab: 0 kB\n\
SReclaimable: 0 kB\n\
SUnreclaim: 0 kB\n\
KernelStack: 0 kB\n\
PageTables: {page_tables_kb:>10} kB\n\
NFS_Unstable: 0 kB\n\
Bounce: 0 kB\n\
WritebackTmp: 0 kB\n\
CommitLimit: {total_kb:>10} kB\n\
Committed_AS: 0 kB\n\
VmallocTotal: 34359738367 kB\n\
VmallocUsed: 0 kB\n\
VmallocChunk: 0 kB\n\
HugePages_Total: 0\n\
HugePages_Free: 0\n\
Hugepagesize: 2048 kB\n"
)
}
fn render_vmstat() -> String {
let total = ax_runtime::hal::mem::total_ram_size();
let usages = ax_alloc::global_allocator().usages();
let used = usages.get(ax_alloc::UsageKind::RustHeap)
+ usages.get(ax_alloc::UsageKind::VirtMem)
+ usages.get(ax_alloc::UsageKind::PageCache)
+ usages.get(ax_alloc::UsageKind::PageTable)
+ usages.get(ax_alloc::UsageKind::TaskStack)
+ usages.get(ax_alloc::UsageKind::Dma)
+ usages.get(ax_alloc::UsageKind::Global);
let free_pages = total.saturating_sub(used) / 4096;
let pgfault = crate::mm::PAGE_FAULT_COUNT.load(Ordering::Relaxed);
format!("nr_free_pages {free_pages}\npgfault {pgfault}\n")
}
fn render_cpuinfo() -> String {
let cpu_count = ax_runtime::hal::cpu_num();
let mut buf = String::new();
for i in 0..cpu_count {
render_cpu_entry(&mut buf, i);
}
buf
}
#[cfg(feature = "jpeg")]
fn read_dt_root_property(name: &str) -> Option<Vec<u8>> {
rdrive::with_fdt(|fdt| {
let root = fdt.get_by_path("/")?;
let prop = root.as_node().get_property(name)?;
(!prop.data.is_empty()).then(|| prop.data.clone())
})
.flatten()
.map(|mut bytes| {
if bytes.last() != Some(&0) {
bytes.push(0);
}
bytes
})
}
#[cfg(target_arch = "riscv64")]
fn render_cpu_entry(buf: &mut String, idx: usize) {
let _ = writeln!(buf, "processor\t: {idx}");
let _ = writeln!(buf, "hart\t\t: {idx}");
let _ = writeln!(buf, "isa\t\t: rv64imafdc_zicsr_zifencei");
let _ = writeln!(buf, "mmu\t\t: sv39");
let _ = writeln!(buf); }
#[cfg(target_arch = "aarch64")]
fn render_cpu_entry(buf: &mut String, idx: usize) {
let midr = pmu::cpu_id_raw().unwrap_or(0);
let implementer = (midr >> 24) & 0xff;
let variant = (midr >> 20) & 0xf;
let part = (midr >> 4) & 0xfff;
let revision = midr & 0xf;
let _ = writeln!(buf, "processor\t: {idx}");
let _ = writeln!(buf, "BogoMIPS\t: 100.00");
let _ = writeln!(buf, "CPU implementer\t: {implementer:#04x}");
let _ = writeln!(buf, "CPU architecture: 8");
let _ = writeln!(buf, "CPU variant\t: {variant:#x}");
let _ = writeln!(buf, "CPU part\t: {part:#05x}");
let _ = writeln!(buf, "CPU revision\t: {revision}");
let _ = writeln!(buf);
}
#[cfg(target_arch = "x86_64")]
fn render_cpu_entry(buf: &mut String, idx: usize) {
let _ = writeln!(buf, "processor\t: {idx}");
let _ = writeln!(buf, "vendor_id\t: GenuineIntel");
let _ = writeln!(buf, "cpu family\t: 6");
let _ = writeln!(buf, "model\t\t: 85");
let _ = writeln!(buf, "model name\t: QEMU Virtual CPU v2.5+");
let _ = writeln!(buf, "stepping\t: 0");
let _ = writeln!(
buf,
"flags\t\t: fpu de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush \
mmx fxsr sse sse2 ht syscall nx lm constant_tsc"
);
let _ = writeln!(buf);
}
#[cfg(target_arch = "loongarch64")]
fn render_cpu_entry(buf: &mut String, idx: usize) {
let _ = writeln!(buf, "processor\t\t: {idx}");
let _ = writeln!(buf, "core id\t\t\t: {idx}");
let _ = writeln!(buf, "Virtual Machine\t\t: no");
let _ = writeln!(buf, "Model Name\t\t: QEMU Virtual Machine");
let _ = writeln!(buf, "ISA\t\t\t: loongarch32 loongarch64");
let _ = writeln!(
buf,
"Feat\t\t\t: cpucfg lam ual fpu lsx lasx crc32 complex crypto lvz"
);
let _ = writeln!(buf);
}
#[cfg(not(any(
target_arch = "riscv64",
target_arch = "aarch64",
target_arch = "x86_64",
target_arch = "loongarch64"
)))]
fn render_cpu_entry(buf: &mut String, idx: usize) {
let _ = writeln!(buf, "processor\t: {idx}");
let _ = writeln!(buf);
}
fn render_stat() -> VfsResult<String> {
let up = monotonic_time();
let cpu_count = ax_runtime::hal::cpu_num() as u64;
let up_jiffies = up.as_secs() * 100 + (up.subsec_millis() / 10) as u64;
let total_budget = up_jiffies.saturating_mul(cpu_count);
let all_tasks = tasks();
let mut user_ms: u128 = 0;
let mut sys_ms: u128 = 0;
let mut procs_running: u64 = 0;
let mut procs_blocked: u64 = 0;
for task in &all_tasks {
let (u, s) = crate::task::task_cpu_time(task);
user_ms += u.as_millis();
sys_ms += s.as_millis();
match task.state() {
ThreadState::New | ThreadState::Running | ThreadState::Waking => procs_running += 1,
ThreadState::Parking | ThreadState::Blocked => procs_blocked += 1,
ThreadState::Exited => {}
}
}
let task_count = all_tasks.len() as u64;
let user_jiffies = (user_ms / 10) as u64;
let sys_jiffies = (sys_ms / 10) as u64;
let idle_jiffies = total_budget
.saturating_sub(user_jiffies)
.saturating_sub(sys_jiffies);
let procs_running = procs_running.max(1);
let btime = wall_time().as_secs().saturating_sub(up.as_secs());
let per_cpu_user = user_jiffies / cpu_count;
let per_cpu_sys = sys_jiffies / cpu_count;
let per_cpu_idle = idle_jiffies / cpu_count;
let irq_total = ax_runtime::diagnostics::timer_irq_count();
let mut buf = format!("cpu {user_jiffies} 0 {sys_jiffies} {idle_jiffies} 0 0 0 0 0 0\n");
for i in 0..cpu_count {
let _ = writeln!(
buf,
"cpu{i} {per_cpu_user} 0 {per_cpu_sys} {per_cpu_idle} 0 0 0 0 0 0"
);
}
let _ = writeln!(buf, "intr {irq_total}");
let _ = writeln!(buf, "ctxt 0");
let _ = writeln!(buf, "btime {btime}");
let _ = writeln!(buf, "processes {task_count}");
let _ = writeln!(buf, "procs_running {procs_running}");
let _ = writeln!(buf, "procs_blocked {procs_blocked}");
let _ = writeln!(buf, "softirq 0 0 0 0 0 0 0 0 0 0 0");
Ok(buf)
}
fn render_proc_net_arp() -> String {
let mut entries = ax_net::arp_entries();
entries.sort_by(|a, b| {
a.device
.cmp(&b.device)
.then_with(|| a.ip_addr.cmp(&b.ip_addr))
});
let mut buf = "IP address HW type Flags HW address Mask \
Device\n"
.to_string();
for entry in entries {
let ip = entry.ip_addr;
let mac = entry.hw_addr;
let ip_addr = format!("{}.{}.{}.{}", ip[0], ip[1], ip[2], ip[3]);
let _ = writeln!(
buf,
"{:<16} 0x{:<8x} 0x{:<8x} {:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x} * {}",
ip_addr,
entry.hw_type,
entry.flags,
mac[0],
mac[1],
mac[2],
mac[3],
mac[4],
mac[5],
entry.device
);
}
buf
}
fn render_proc_net_dev() -> String {
let stats = ax_net::net_dev_stats();
render_proc_net_dev_from_stats(&stats)
}
fn render_proc_net_dev_from_stats(stats: &[ax_net::NetDevStats]) -> String {
let mut buf = "Inter-| Receive | Transmit\n \
face |bytes packets errs drop fifo frame compressed multicast|bytes \
packets errs drop fifo colls carrier compressed\n"
.to_string();
for st in stats {
writeln!(
buf,
"{:>6}: {:>7} {:>7} {:>4} {:>4} {:>4} {:>5} {:>10} {:>9} {:>8} {:>7} {:>4} {:>4} \
{:>4} {:>5} {:>7} {:>10}",
st.name,
st.rx_bytes,
st.rx_packets,
st.rx_errors,
st.rx_dropped,
0u64, 0u64, 0u64, 0u64, st.tx_bytes,
st.tx_packets,
st.tx_errors,
st.tx_dropped,
0u64, 0u64, 0u64, 0u64, )
.expect("write to String cannot fail");
}
buf
}
fn render_proc_net_snmp() -> String {
let mut buf = String::new();
writeln!(
buf,
"Tcp: RtoAlgorithm RtoMin RtoMax MaxConn ActiveOpens PassiveOpens AttemptFails \
EstabResets CurrEstab InSegs OutSegs RetransSegs InErrs OutRsts InCsumErrors"
)
.expect("write to String cannot fail");
writeln!(buf, "Tcp: 1 200 120000 -1 0 0 0 0 0 0 0 0 0 0 0")
.expect("write to String cannot fail");
writeln!(
buf,
"Udp: InDatagrams NoPorts InErrors OutDatagrams RcvbufErrors SndbufErrors InCsumErrors \
IgnoredMulti MemErrors"
)
.expect("write to String cannot fail");
writeln!(buf, "Udp: 0 0 0 0 0 0 0 0 0").expect("write to String cannot fail");
buf
}
fn render_diskstats() -> String {
let identity = ax_fs_ng::root::root_block_identity();
let (reads, sectors_read, writes, sectors_written) = ax_fs_ng::block_io_stats();
format!(
"{} {} {} {reads} 0 {sectors_read} 0 {writes} 0 {sectors_written} 0 0 0 0\n",
identity.major, identity.minor, identity.name
)
}
fn render_proc_bus_usb_devices() -> String {
let mut snapshots = crate::pseudofs::usbfs::usb_device_snapshots();
snapshots.sort_by_key(|snapshot| (snapshot.bus_num, snapshot.device_num));
render_proc_bus_usb_devices_from_snapshots(&snapshots)
}
fn render_proc_bus_usb_devices_from_snapshots(
snapshots: &[crate::pseudofs::usbfs::UsbDeviceSnapshotInfo],
) -> String {
let mut out = String::new();
for snapshot in snapshots {
render_proc_bus_usb_device(snapshot, &mut out);
}
out
}
fn render_proc_bus_usb_device(
snapshot: &crate::pseudofs::usbfs::UsbDeviceSnapshotInfo,
out: &mut String,
) {
let blob = &snapshot.descriptor_blob;
if blob.len() < 18 || descriptor_u8(blob, 0) < 18 || descriptor_u8(blob, 1) != 0x01 {
return;
}
let device_class = descriptor_u8(blob, 4);
let device_subclass = descriptor_u8(blob, 5);
let device_protocol = descriptor_u8(blob, 6);
let max_packet_size = descriptor_u8(blob, 7);
let vendor_id = descriptor_u16(blob, 8);
let product_id = descriptor_u16(blob, 10);
let device_version = descriptor_u16(blob, 12);
let config_count = descriptor_u8(blob, 17);
let max_child_count = if device_class == 0x09 { 1 } else { 0 };
let _ = writeln!(
out,
"T: Bus={:02} Lev=00 Prnt=00 Port=00 Cnt=00 Dev#={:3} Spd=480 MxCh={:2}",
snapshot.bus_num, snapshot.device_num, max_child_count
);
let _ = writeln!(
out,
"D: Ver={} Cls={:02x}({:<5}) Sub={:02x} Prot={:02x} MxPS={:2} #Cfgs={:3}",
usb_bcd(descriptor_u16(blob, 2)),
device_class,
usb_class_label(device_class),
device_subclass,
device_protocol,
max_packet_size,
config_count
);
let _ = writeln!(
out,
"P: Vendor={:04x} ProdID={:04x} Rev={}",
vendor_id,
product_id,
usb_bcd(device_version)
);
let _ = writeln!(out);
let mut offset = 18usize;
while offset + 2 <= blob.len() {
let len = descriptor_u8(blob, offset) as usize;
let ty = descriptor_u8(blob, offset + 1);
if len == 0 {
break;
}
if ty != 0x02 || len < 9 || offset + len > blob.len() {
offset = offset.saturating_add(len);
continue;
}
let total = descriptor_u16(blob, offset + 2) as usize;
let config_end = offset.saturating_add(total).min(blob.len());
let active = descriptor_u8(blob, offset + 5);
let _ = writeln!(
out,
"C:* #Ifs={:2} Cfg#={:2} Atr={:02x} MxPwr={:3}mA",
descriptor_u8(blob, offset + 4),
active,
descriptor_u8(blob, offset + 7),
u16::from(descriptor_u8(blob, offset + 8)) * 2
);
let mut desc = offset + len;
while desc + 2 <= config_end {
let desc_len = descriptor_u8(blob, desc) as usize;
let desc_ty = descriptor_u8(blob, desc + 1);
if desc_len == 0 || desc + desc_len > config_end {
break;
}
match desc_ty {
0x04 if desc_len >= 9 => {
render_proc_bus_usb_interface(&blob[desc..desc + desc_len], out);
}
0x05 if desc_len >= 7 => {
render_proc_bus_usb_endpoint(&blob[desc..desc + desc_len], out);
}
_ => {}
}
desc += desc_len;
}
let _ = writeln!(out);
offset = config_end.max(offset + len);
}
}
fn render_proc_bus_usb_interface(desc: &[u8], out: &mut String) {
let class = descriptor_u8(desc, 5);
let _ = writeln!(
out,
"I:* If#={:2} Alt={:2} #EPs={:2} Cls={:02x}({:<5}) Sub={:02x} Prot={:02x} Driver={}",
descriptor_u8(desc, 2),
descriptor_u8(desc, 3),
descriptor_u8(desc, 4),
class,
usb_class_label(class),
descriptor_u8(desc, 6),
descriptor_u8(desc, 7),
if class == 0x09 { "hub" } else { "(none)" }
);
}
fn render_proc_bus_usb_endpoint(desc: &[u8], out: &mut String) {
let address = descriptor_u8(desc, 2);
let attributes = descriptor_u8(desc, 3);
let max_packet_size = descriptor_u16(desc, 4) & 0x07ff;
let _ = writeln!(
out,
"E: Ad={:02x}({}) Atr={:02x}({:<5}) MxPS={:4} Ivl={}ms",
address,
if address & 0x80 != 0 { "I" } else { "O" },
attributes,
usb_endpoint_type_label(attributes & 0x03),
max_packet_size,
descriptor_u8(desc, 6)
);
}
fn descriptor_u8(blob: &[u8], offset: usize) -> u8 {
blob.get(offset).copied().unwrap_or_default()
}
fn descriptor_u16(blob: &[u8], offset: usize) -> u16 {
u16::from_le_bytes([descriptor_u8(blob, offset), descriptor_u8(blob, offset + 1)])
}
fn usb_bcd(value: u16) -> String {
format!(
"{:2x}.{:x}{:x}",
(value >> 8) & 0xff,
(value >> 4) & 0x0f,
value & 0x0f
)
}
fn usb_class_label(class: u8) -> &'static str {
match class {
0x00 => ">ifc",
0x03 => "HID",
0x08 => "stor.",
0x09 => "hub",
0x0e => "video",
0xe0 => "wlcon",
0xef => "misc",
0xff => "vend.",
_ => "unk.",
}
}
fn usb_endpoint_type_label(ty: u8) -> &'static str {
match ty {
0 => "Ctrl",
1 => "Isoc",
2 => "Bulk",
3 => "Int.",
_ => "Unk.",
}
}
pub fn new_procfs(observer: PidNamespaceRef) -> Filesystem {
let view = PidView::new(observer);
SimpleFs::new_with("proc".into(), 0x9fa0, move |fs| builder(fs, view))
}
struct ProcessTaskDir {
fs: Arc<SimpleFs>,
process: Weak<Process>,
view: PidView,
}
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());
};
let view = self.view.clone();
Box::new(process.threads().into_iter().filter_map(move |tid| {
let identity = ROOT_PID_NS.lookup(tid.into())?;
view.visible_number(&identity)
.map(|number| number.get().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 identity = self
.view
.resolve_thread(TidNumber::try_from(tid).map_err(|_| VfsError::NotFound)?)
.map_err(|_| VfsError::NotFound)?;
let task = identity.live_task().ok_or(VfsError::NotFound)?;
if task.as_thread().proc_data.proc.pid() != process.pid() {
return Err(VfsError::NotFound);
}
let proc_data = process.identity().live_data().ok_or(VfsError::NotFound)?;
Ok(NodeOpsMux::Dir(SimpleDir::new_maker(
self.fs.clone(),
Arc::new(ThreadDir {
fs: self.fs.clone(),
task: task.downgrade(),
proc_data,
path_pid: process.pid().get(),
procfs_pid: None,
view: self.view.clone(),
}),
)))
}
fn is_cacheable(&self) -> bool {
false
}
}
fn render_thread_status(
task: &WeakUserTaskRef,
proc_data: &Arc<ProcessData>,
_path_pid: u32,
_procfs_pid: Option<u32>,
view: &PidView,
) -> VfsResult<String> {
let task = require_proc_task(task)?;
let thread = task.as_thread();
let aspace = proc_data.pin_aspace().map_err(VfsError::from)?;
let aspace = aspace.lock();
let mem = ProcessMemStats::collect(&aspace).map_err(VfsError::from)?;
let cred = thread.cred();
let name = task.name();
let num_threads = proc_data.proc.threads().len() as u32;
let tracer_pid = proc_data
.ptrace_tracer_identity()
.and_then(|identity| view.visible_number(&identity))
.map(TidNumber::from);
let ppid = proc_data
.proc
.parent()
.and_then(|parent| view.visible_number(&parent.identity()))
.map(TgidNumber::from);
let tgid = view
.visible_number(&proc_data.identity())
.map(TgidNumber::from)
.ok_or(VfsError::NotFound)?;
let pid = view
.visible_number(&thread.pid_identity())
.map(TidNumber::from)
.ok_or(VfsError::NotFound)?;
Ok(render_task_status(
TaskStatusBase {
name: &name,
state: task_status_state(&task),
tgid,
pid,
ppid,
tracer_pid,
cred: &cred,
num_threads,
},
task.affinity(),
ax_runtime::hal::cpu_num(),
&mem,
))
}
fn task_status_state(task: &UserTaskRef) -> &'static str {
match task.state() {
ThreadState::New | ThreadState::Running | ThreadState::Waking => "R (running)",
ThreadState::Parking | ThreadState::Blocked => "S (sleeping)",
ThreadState::Exited => "Z (zombie)",
}
}
struct TaskStatusBase<'a> {
name: &'a str,
state: &'a str,
tgid: TgidNumber,
pid: TidNumber,
ppid: Option<TgidNumber>,
tracer_pid: Option<TidNumber>,
cred: &'a crate::task::Cred,
num_threads: u32,
}
struct TaskStatusFields<'a> {
base: TaskStatusBase<'a>,
cpus_allowed: &'a str,
cpus_allowed_list: &'a str,
mem: &'a ProcessMemStats,
}
fn render_task_status(
base: TaskStatusBase<'_>,
cpumask: CpuSet,
cpu_num: usize,
mem: &ProcessMemStats,
) -> String {
let cpus_allowed = format_cpumask_hex(&cpumask, cpu_num);
let cpus_allowed_list = format_cpumask_list(&cpumask, cpu_num);
render_task_status_fields(&TaskStatusFields {
base,
cpus_allowed: &cpus_allowed,
cpus_allowed_list: &cpus_allowed_list,
mem,
})
}
#[rustfmt::skip]
fn render_task_status_fields(status: &TaskStatusFields<'_>) -> String {
let base = &status.base;
let groups = SupplementaryGroups(&base.cred.groups);
format!(
"Name:\t{}\n\
State:\t{}\n\
Tgid:\t{}\n\
Pid:\t{}\n\
PPid:\t{}\n\
TracerPid:\t{}\n\
Uid:\t{}\t{}\t{}\t{}\n\
Gid:\t{}\t{}\t{}\t{}\n\
Groups:\t{}\n\
CapInh:\t{:016x}\n\
CapPrm:\t{:016x}\n\
CapEff:\t{:016x}\n\
CapBnd:\t{:016x}\n\
CapAmb:\t{:016x}\n\
Threads:\t{}\n\
{}\
Cpus_allowed:\t{}\n\
Cpus_allowed_list:\t{}\n\
Mems_allowed:\t1\n\
Mems_allowed_list:\t0\n\
voluntary_ctxt_switches:\t0\n\
nonvoluntary_ctxt_switches:\t0",
base.name,
base.state,
base.tgid.get(),
base.pid.get(),
base.ppid.map_or(0, TgidNumber::get),
base.tracer_pid.map_or(0, TidNumber::get),
base.cred.uid, base.cred.euid, base.cred.suid, base.cred.fsuid,
base.cred.gid, base.cred.egid, base.cred.sgid, base.cred.fsgid,
groups,
base.cred.cap_inheritable,
base.cred.cap_permitted,
base.cred.cap_effective,
base.cred.cap_bounding,
base.cred.cap_ambient,
base.num_threads,
status.mem.format_status_vm_lines(),
status.cpus_allowed,
status.cpus_allowed_list,
)
}
struct SupplementaryGroups<'a>(&'a [u32]);
impl core::fmt::Display for SupplementaryGroups<'_> {
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
for (index, group) in self.0.iter().enumerate() {
if index != 0 {
formatter.write_str(" ")?;
}
write!(formatter, "{group}")?;
}
Ok(())
}
}
fn format_cpumask_hex(cpumask: &CpuSet, cpu_num: usize) -> String {
format_cpu_presence_hex(&collect_cpu_presence(cpumask, cpu_num))
}
fn format_cpu_presence_hex(cpu_presence: &[bool]) -> String {
let word_count = cpu_presence.len().div_ceil(32).max(1);
let mut words = vec![0u32; word_count];
for (cpu, allowed) in cpu_presence.iter().copied().enumerate() {
if allowed {
words[cpu / 32] |= 1u32 << (cpu % 32);
}
}
words
.iter()
.rev()
.map(|word| format!("{word:08x}"))
.collect::<Vec<_>>()
.join(",")
}
fn format_cpumask_list(cpumask: &CpuSet, cpu_num: usize) -> String {
format_cpu_presence_list(&collect_cpu_presence(cpumask, cpu_num))
}
fn format_cpu_presence_list(cpu_presence: &[bool]) -> String {
let mut ranges = Vec::new();
let mut cpu = 0;
while cpu < cpu_presence.len() {
if !cpu_presence[cpu] {
cpu += 1;
continue;
}
let start = cpu;
let mut end = cpu;
while end + 1 < cpu_presence.len() && cpu_presence[end + 1] {
end += 1;
}
ranges.push(if start == end {
start.to_string()
} else {
format!("{start}-{end}")
});
cpu = end + 1;
}
ranges.join(",")
}
fn collect_cpu_presence(cpus: &CpuSet, cpu_num: usize) -> Vec<bool> {
let mut cpu_presence = vec![false; cpu_num];
for (cpu, allowed) in cpu_presence.iter_mut().enumerate() {
*allowed = cpus.contains(CpuId::new(cpu as u32));
}
cpu_presence
}
struct ThreadFdDir {
fs: Arc<SimpleFs>,
task: WeakUserTaskRef,
}
impl SimpleDirOps for ThreadFdDir {
fn child_names<'a>(&'a self) -> Box<dyn Iterator<Item = Cow<'a, str>> + 'a> {
let task = match upgrade_proc_task(&self.task) {
Ok(Some(task)) => task,
Ok(None) => return Box::new(iter::empty()),
Err(error) => panic!("procfs fd directory has an invalid user extension: {error}"),
};
let fd_table = task.as_thread().clone_scope_item(&FD_TABLE);
let ids = fd_table
.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 = require_proc_task(&self.task)?;
let fd = name.parse::<u32>().map_err(|_| VfsError::NotFound)?;
let fd_table = task.as_thread().clone_scope_item(&FD_TABLE);
let path = fd_table
.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 ThreadFdInfoDir {
fs: Arc<SimpleFs>,
task: WeakUserTaskRef,
}
impl SimpleDirOps for ThreadFdInfoDir {
fn child_names<'a>(&'a self) -> Box<dyn Iterator<Item = Cow<'a, str>> + 'a> {
let task = match upgrade_proc_task(&self.task) {
Ok(Some(task)) => task,
Ok(None) => return Box::new(iter::empty()),
Err(error) => panic!("procfs fdinfo directory has an invalid user extension: {error}"),
};
let fd_table = task.as_thread().clone_scope_item(&FD_TABLE);
let ids = fd_table
.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 = require_proc_task(&self.task)?;
let fd = name.parse::<u32>().map_err(|_| VfsError::NotFound)?;
let fd_table = task.as_thread().clone_scope_item(&FD_TABLE);
let pidfd = fd_table
.read()
.get(fd as _)
.ok_or(VfsError::NotFound)?
.inner
.downcast_ref::<PidFd>()
.map(PidFd::identity);
let Some(identity) = pidfd else {
return Ok(SimpleFile::new_regular(fs, || Ok(Vec::new())).into());
};
Ok(SimpleFile::new_regular(fs, move || {
let pids: Vec<i32> = if identity.is_exited() {
vec![-1]
} else {
let observer_pid_ns = task.as_thread().active_pid_namespace();
PidView::new(observer_pid_ns)
.nspid_chain(&identity)
.map(|pids| pids.into_iter().map(|pid| pid.get() as i32).collect())
.unwrap_or_else(|| vec![0])
};
let pid = pids[0];
let nspid = pids
.iter()
.map(ToString::to_string)
.collect::<Vec<_>>()
.join(" ");
Ok(format!("Pid:\t{pid}\nNSpid:\t{nspid}\n").into_bytes())
})
.into())
}
fn is_cacheable(&self) -> bool {
false
}
}
struct NsDir {
fs: Arc<SimpleFs>,
task: WeakUserTaskRef,
}
impl SimpleDirOps for NsDir {
fn child_names<'a>(&'a self) -> Box<dyn Iterator<Item = Cow<'a, str>> + 'a> {
Box::new(
["uts", "ipc", "mnt", "pid", "net", "user", "cgroup"]
.into_iter()
.map(Cow::Borrowed),
)
}
fn lookup_child(&self, name: &str) -> VfsResult<NodeOpsMux> {
let fs = self.fs.clone();
let task_ref = self.task;
let Some(task) = upgrade_proc_task(&task_ref)? else {
return Err(VfsError::NotFound);
};
let proc_data = &task.as_thread().proc_data;
let content: String = match name {
"uts" => {
let nsproxy = proc_data.namespace_snapshot();
let ns_id = nsproxy.uts_ns.lock().id;
format!("uts:[{}]\n", ns_id)
}
"ipc" => {
let nsproxy = proc_data.namespace_snapshot();
let ns_id = nsproxy.ipc_ns.lock().ns_id;
format!("ipc:[{}]\n", ns_id)
}
"mnt" => {
let nsproxy = proc_data.namespace_snapshot();
let ns_id = nsproxy.mnt_ns.lock().id();
format!("mnt:[{}]\n", ns_id)
}
"pid" => {
let ns_id = proc_data.identity().active_namespace().id().get();
format!("pid:[{}]\n", ns_id)
}
"net" => {
let nsproxy = proc_data.namespace_snapshot();
let ns_id = nsproxy.net_ns.lock().ns_id;
format!("net:[{}]\n", ns_id)
}
"user" => {
let nsproxy = proc_data.namespace_snapshot();
let ns_id = nsproxy.user_ns.lock().id;
format!("user:[{}]\n", ns_id)
}
"cgroup" => {
let nsproxy = proc_data.namespace_snapshot();
let ns_id = nsproxy.cgroup_ns.lock().id();
format!("cgroup:[{}]\n", ns_id)
}
_ => return Err(VfsError::NotFound),
};
let content = content.into_bytes();
Ok(SimpleFile::new_regular(fs, move || Ok(content.clone())).into())
}
fn is_cacheable(&self) -> bool {
false
}
}
struct ThreadDir {
fs: Arc<SimpleFs>,
task: WeakUserTaskRef,
proc_data: Arc<ProcessData>,
path_pid: u32,
procfs_pid: Option<u32>,
view: PidView,
}
fn render_thread_maps(task: &WeakUserTaskRef) -> VfsResult<String> {
let mut output = String::new();
let task = match upgrade_proc_task(task) {
Ok(Some(task)) => task,
Ok(None) => return Ok(output),
Err(error) => return Err(error),
};
let aspace = task
.as_thread()
.proc_data
.pin_aspace()
.map_err(VfsError::from)?;
let mm = aspace.lock();
for area in mm.vma_inspection_records().map_err(VfsError::from)? {
let start = area.start();
let end = area.end();
let bi = area.file_info().clone();
let MappingFileInfo {
path,
offset: file_offset,
inode,
dev,
shared: is_shared,
} = bi;
let flag_end = if is_shared { 's' } else { 'p' };
let flags = area.reported_flags();
let perms = {
let r = if flags.contains(MappingFlags::READ) {
'r'
} else {
'-'
};
let w = if flags.contains(MappingFlags::WRITE) {
'w'
} else {
'-'
};
let x = if flags.contains(MappingFlags::EXECUTE) {
'x'
} else {
'-'
};
format!("{}{}{}{}", r, w, x, flag_end)
};
const ADDR_HEX_WIDTH: usize = core::mem::size_of::<usize>() * 2;
const MAPS_COL_WIDTH: usize = 25 + core::mem::size_of::<usize>() * 6 - 1;
let mut writer = SeqWriter::new(&mut output);
let dev = dev.map(DeviceId).map(|dev| (dev.major(), dev.minor()));
write!(
&mut writer,
"{:0width$x}-{:0width$x} {} {:08x} {:02x}:{:02x} {}",
start.as_usize(),
end.as_usize(),
perms,
file_offset.unwrap_or(0),
dev.map(|(major, _)| major).unwrap_or(0),
dev.map(|(_, minor)| minor).unwrap_or(0),
inode.unwrap_or(0),
width = ADDR_HEX_WIDTH,
)
.map_err(|_| VfsError::InvalidInput)?;
writer.pad_to(MAPS_COL_WIDTH)?;
if !path.is_empty() {
writer.write_str(&path)?;
}
writer.newline()?;
}
Ok(output)
}
fn render_thread_statm(
task: &WeakUserTaskRef,
proc_data: &Arc<ProcessData>,
_path_pid: u32,
) -> VfsResult<String> {
let _task = match upgrade_proc_task(task) {
Ok(Some(task)) => task,
Ok(None) => return Ok("0 0 0 0 0 0 0\n".into()),
Err(error) => return Err(error),
};
let aspace = proc_data.pin_aspace().map_err(VfsError::from)?;
let mm = aspace.lock();
Ok(ProcessMemStats::collect(&mm)
.map_err(VfsError::from)?
.format_statm())
}
fn render_thread_stat(
task: &WeakUserTaskRef,
proc_data: &Arc<ProcessData>,
_path_pid: u32,
_procfs_pid: Option<u32>,
view: &PidView,
) -> VfsResult<Vec<u8>> {
let task = require_proc_task(task)?;
let mut stat = TaskStat::from_thread(&task)?;
let aspace = proc_data.pin_aspace().map_err(VfsError::from)?;
let mm = aspace.lock();
let mem = ProcessMemStats::collect(&mm).map_err(VfsError::from)?;
stat.vsize = mem.vsize_bytes();
stat.rss = mem.rss_pages();
stat.start_code = mem.start_code;
stat.end_code = mem.end_code;
stat.start_stack = mem.start_stack;
let (start_data, end_data) = mm.executable_data_bounds();
stat.start_data = start_data as u64;
stat.end_data = end_data as u64;
stat.start_brk = mm.heap_start() as u64;
let thread = task.as_thread();
stat.pid = view
.visible_number(&thread.pid_identity())
.ok_or(VfsError::NotFound)?
.get();
stat.ppid = proc_data
.proc
.parent()
.and_then(|parent| view.visible_number(&parent.identity()))
.map_or(0, PidNumber::get);
let group = proc_data.proc.group();
stat.pgrp = view
.visible_number(&group.identity())
.ok_or(VfsError::NotFound)?
.get();
stat.session = view
.visible_number(&group.session().identity())
.ok_or(VfsError::NotFound)?
.get();
Ok(format!("{stat}").into_bytes())
}
fn render_thread_auxv(task: &UserTaskRef) -> Vec<u8> {
let mut entries = task.as_thread().proc_data.auxv().to_vec();
entries.push(AuxEntry::new(AuxType::NULL, 0));
let mut bytes = Vec::with_capacity(entries.len() * size_of::<AuxEntry>());
for entry in entries {
bytes.extend_from_slice(entry.as_bytes());
}
bytes
}
struct ProcMemFile {
proc_data: Arc<ProcessData>,
}
impl ProcMemFile {
fn check_access(&self) -> VfsResult<()> {
let current_task = current_user_task();
let current_proc = ¤t_task.as_thread().proc_data;
if current_proc.proc.pid() == self.proc_data.proc.pid() {
return Ok(());
}
let is_tracer = (self.proc_data.is_ptrace_traceme() || self.proc_data.is_ptrace_attached())
&& self
.proc_data
.ptrace_tracer_identity()
.is_some_and(|tracer| Arc::ptr_eq(&tracer, ¤t_proc.identity()))
&& self.proc_data.ptrace_stop_signo().is_some();
if is_tracer {
Ok(())
} else {
Err(VfsError::PermissionDenied)
}
}
fn populate_remote_range(&self, addr: usize, len: usize, flags: MappingFlags) -> VfsResult<()> {
if len == 0 {
return Ok(());
}
let start = VirtAddr::from_usize(addr);
let end = VirtAddr::from_usize(addr.checked_add(len).ok_or(VfsError::BadAddress)?);
let page_start = start.align_down_4k();
let page_end = end.align_up_4k();
let aspace = self.proc_data.pin_aspace().map_err(VfsError::from)?;
let mut aspace = aspace.lock();
Ok(aspace.populate_area(page_start, page_end - page_start, flags)?)
}
}
impl DirectRwFsFileOps for ProcMemFile {
fn read_at(&self, buf: &mut [u8], offset: u64) -> VfsResult<usize> {
self.check_access()?;
if buf.is_empty() {
return Ok(0);
}
let addr = usize::try_from(offset).map_err(|_| VfsError::BadAddress)?;
self.populate_remote_range(addr, buf.len(), MappingFlags::READ)?;
let aspace = self.proc_data.pin_aspace().map_err(VfsError::from)?;
let aspace = aspace.lock();
aspace.read(VirtAddr::from_usize(addr), buf)?;
Ok(buf.len())
}
fn write_at(&self, buf: &[u8], offset: u64) -> VfsResult<usize> {
self.check_access()?;
if buf.is_empty() {
return Ok(0);
}
let addr = usize::try_from(offset).map_err(|_| VfsError::BadAddress)?;
self.populate_remote_range(addr, buf.len(), MappingFlags::WRITE)?;
let aspace = self.proc_data.pin_aspace().map_err(VfsError::from)?;
let aspace = aspace.lock();
aspace.write(VirtAddr::from_usize(addr), buf)?;
drop(aspace);
ax_cpu::cache::flush_icache_all();
Ok(buf.len())
}
}
impl SimpleDirOps for ThreadDir {
fn child_names<'a>(&'a self) -> Box<dyn Iterator<Item = Cow<'a, str>> + 'a> {
Box::new(
[
"stat",
"statm",
"status",
"oom_score_adj",
"task",
"maps",
"mem",
"auxv",
"mounts",
"mountinfo",
"cmdline",
"comm",
"exe",
"environ",
"root",
"cwd",
"fd",
"fdinfo",
"uid_map",
"gid_map",
"setgroups",
"cgroup",
"ns",
]
.into_iter()
.map(Cow::Borrowed),
)
}
fn lookup_child(&self, name: &str) -> VfsResult<NodeOpsMux> {
let fs = self.fs.clone();
let task = require_proc_task(&self.task)?;
Ok(match name {
"stat" => {
let task = self.task;
let proc_data = self.proc_data.clone();
let path_pid = self.path_pid;
let procfs_pid = self.procfs_pid;
let view = self.view.clone();
SimpleFile::new_regular(fs, move || {
render_thread_stat(&task, &proc_data, path_pid, procfs_pid, &view)
})
.into()
}
"statm" => {
let task = self.task;
let proc_data = self.proc_data.clone();
let path_pid = self.path_pid;
SimpleFile::new_regular(fs, move || {
render_thread_statm(&task, &proc_data, path_pid)
})
.into()
}
"status" => {
let task = self.task;
let proc_data = self.proc_data.clone();
let path_pid = self.path_pid;
let procfs_pid = self.procfs_pid;
let view = self.view.clone();
SimpleFile::new_regular(fs, move || {
render_thread_status(&task, &proc_data, path_pid, procfs_pid, &view)
})
.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.trim_ascii_end().parse::<i32>().ok())
.filter(|value| (-1000..=1000).contains(value))
.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),
view: self.view.clone(),
}),
)
.into(),
"maps" => {
let task = self.task;
let seq = SeqObject::new(move || render_thread_maps(&task));
SpecialFsFile::new_regular_with_perm(
fs.clone(),
seq,
NodePermission::from_bits_truncate(0o444),
)
.into()
}
"mem" => SpecialFsFile::new_regular_with_perm(
fs,
ProcMemFile {
proc_data: task.as_thread().proc_data.clone(),
},
NodePermission::from_bits_truncate(0o600),
)
.into(),
"auxv" => SimpleFile::new_regular(fs, move || Ok(render_thread_auxv(&task))).into(),
"mounts" => {
let task = self.task;
SimpleFile::new_regular(fs, move || {
let task = require_proc_task(&task)?;
let ctx_arc = task
.as_thread()
.clone_scope_item(&FS_CONTEXT)
.ok_or(VfsError::NotFound)?;
let ctx = ctx_arc.lock();
Ok(crate::pseudofs::proc_mountinfo::render_mounts(&ctx))
})
.into()
}
"mountinfo" => {
let task = self.task;
SimpleFile::new_regular(fs, move || {
let task = require_proc_task(&task)?;
let ctx_arc = task
.as_thread()
.clone_scope_item(&FS_CONTEXT)
.ok_or(VfsError::NotFound)?;
let ctx = ctx_arc.lock();
Ok(crate::pseudofs::proc_mountinfo::render_mountinfo(&ctx))
})
.into()
}
"cmdline" => SimpleFile::new_regular(fs, move || {
let cmdline = task.as_thread().proc_data.cmdline();
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 name = task.name();
let copy_len = name.len().min(15);
let mut bytes = Vec::with_capacity(copy_len + 1);
bytes.extend_from_slice(&name.as_bytes()[..copy_len]);
bytes.push(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().to_string())
})
.into(),
"environ" => SimpleFile::new_regular(fs, move || {
let envp = task.as_thread().proc_data.envp();
let mut buf = Vec::new();
for env in envp.iter() {
buf.extend_from_slice(env.as_bytes());
buf.push(0);
}
Ok(buf)
})
.into(),
"root" => SimpleFile::new(fs, NodeType::Symlink, move || {
Ok(task.as_thread().proc_data.root_path().to_string())
})
.into(),
"cwd" => SimpleFile::new(fs, NodeType::Symlink, move || {
Ok(task.as_thread().proc_data.cwd_path().to_string())
})
.into(),
"fd" => SimpleDir::new_maker(
fs.clone(),
Arc::new(ThreadFdDir {
fs,
task: task.downgrade(),
}),
)
.into(),
"fdinfo" => SimpleDir::new_maker(
fs.clone(),
Arc::new(ThreadFdInfoDir {
fs,
task: task.downgrade(),
}),
)
.into(),
"uid_map" => SimpleFile::new_regular(
fs,
RwFile::new(move |req| match req {
SimpleFileOperation::Read => {
let thr = task.as_thread();
let cred = thr.cred();
let content = if thr.uid_map_written() || cred.euid != 65534 {
format!(" 0 {:>10} 4294967295\n", cred.uid)
} else {
"\n".to_string()
};
Ok(Some(content.into_bytes()))
}
SimpleFileOperation::Write(data) => {
let input =
core::str::from_utf8(data).map_err(|_| VfsError::InvalidInput)?;
let parts: Vec<&str> = input.split_whitespace().collect();
if parts.len() >= 3 {
let _mapped: u32 =
parts[0].parse().map_err(|_| VfsError::InvalidInput)?;
let orig: u32 = parts[1].parse().map_err(|_| VfsError::InvalidInput)?;
let _count: u32 =
parts[2].parse().map_err(|_| VfsError::InvalidInput)?;
let thr = task.as_thread();
let mut cred = (*thr.cred()).clone();
cred.uid = orig;
cred.euid = orig;
cred.suid = orig;
cred.fsuid = orig;
if orig == 0 {
let mask = Cred::cap_mask();
cred.cap_permitted = mask;
cred.cap_effective = mask;
cred.cap_bounding = mask;
} else {
cred.cap_permitted = 0;
cred.cap_effective = 0;
cred.cap_ambient = 0;
}
cred.sanitize_capabilities();
Thread::set_cred(thr, cred);
thr.set_uid_map_written(true);
let proc_data = &thr.proc_data;
let update = proc_data.namespace_update();
let nsproxy = update.snapshot();
nsproxy.user_ns.lock().uid_mapped = true;
}
Ok(None)
}
}),
)
.into(),
"gid_map" => SimpleFile::new_regular(
fs,
RwFile::new(move |req| match req {
SimpleFileOperation::Read => {
let thr = task.as_thread();
let cred = thr.cred();
let content = if thr.gid_map_written() || cred.egid != 65534 {
format!(" 0 {:>10} 4294967295\n", cred.gid)
} else {
"\n".to_string()
};
Ok(Some(content.into_bytes()))
}
SimpleFileOperation::Write(data) => {
let input =
core::str::from_utf8(data).map_err(|_| VfsError::InvalidInput)?;
let parts: Vec<&str> = input.split_whitespace().collect();
if parts.len() >= 3 {
let _mapped: u32 =
parts[0].parse().map_err(|_| VfsError::InvalidInput)?;
let orig: u32 = parts[1].parse().map_err(|_| VfsError::InvalidInput)?;
let _count: u32 =
parts[2].parse().map_err(|_| VfsError::InvalidInput)?;
let thr = task.as_thread();
let mut cred = (*thr.cred()).clone();
cred.gid = orig;
cred.egid = orig;
cred.sgid = orig;
cred.fsgid = orig;
cred.sanitize_capabilities();
Thread::set_cred(thr, cred);
thr.set_gid_map_written(true);
let proc_data = &thr.proc_data;
let update = proc_data.namespace_update();
let nsproxy = update.snapshot();
nsproxy.user_ns.lock().gid_mapped = true;
}
Ok(None)
}
}),
)
.into(),
"setgroups" => SimpleFile::new_regular(
fs,
RwFile::new(move |req| match req {
SimpleFileOperation::Read => {
let thr = task.as_thread();
let content = if thr.setgroups_deny() {
"deny\n"
} else {
"allow\n"
};
Ok(Some(content.as_bytes().to_vec()))
}
SimpleFileOperation::Write(data) => {
let input = core::str::from_utf8(data)
.map_err(|_| VfsError::InvalidInput)?
.trim();
if input == "deny" {
task.as_thread().set_setgroups_deny(true);
} else if input == "allow" {
task.as_thread().set_setgroups_deny(false);
}
Ok(None)
}
}),
)
.into(),
"cgroup" => SimpleFile::new_regular(fs, move || {
let reader = current_user_task();
let reader_cgroup_ns = reader
.as_thread()
.proc_data
.namespace_snapshot()
.cgroup_ns
.clone();
let reader_root = reader_cgroup_ns.lock().root();
let target_membership = task.as_thread().proc_data.cgroup_node();
let path = crate::cgroup::relative_path(&reader_root, &target_membership);
Ok(format!("0::{path}\n"))
})
.into(),
"ns" => SimpleDir::new_maker(
fs.clone(),
Arc::new(NsDir {
fs,
task: self.task,
}),
)
.into(),
_ => return Err(VfsError::NotFound),
})
}
fn is_cacheable(&self) -> bool {
false
}
}
struct ProcFsHandler {
fs: Arc<SimpleFs>,
view: PidView,
}
impl SimpleDirOps for ProcFsHandler {
fn child_names<'a>(&'a self) -> Box<dyn Iterator<Item = Cow<'a, str>> + 'a> {
Box::new(
processes()
.into_iter()
.filter_map(|proc_data| {
procfs_visible_pid(&self.view, &proc_data.proc)
.map(|pid| pid.to_string().into())
})
.chain([Cow::Borrowed("self")]),
)
}
fn lookup_child(&self, name: &str) -> VfsResult<NodeOpsMux> {
let (task, path_pid, procfs_pid, proc_data) = if name == "self" {
let task = current_user_task();
let proc_data = task.as_thread().proc_data.clone();
let path_pid =
procfs_visible_pid(&self.view, &proc_data.proc).ok_or(VfsError::NotFound)?;
(task, path_pid, None, proc_data)
} else {
let pid = name.parse::<u32>().map_err(|_| VfsError::NotFound)?;
let identity = self
.view
.resolve_process(TgidNumber::try_from(pid).map_err(|_| VfsError::NotFound)?)
.map_err(|_| VfsError::NotFound)?;
let proc_data = identity.live_data().ok_or(VfsError::NotFound)?;
let task = identity.live_task().ok_or(VfsError::NotFound)?;
let procfs_pid = Some(pid);
(task, pid, procfs_pid, proc_data)
};
let node = NodeOpsMux::Dir(SimpleDir::new_maker(
self.fs.clone(),
Arc::new(ThreadDir {
fs: self.fs.clone(),
task: task.downgrade(),
proc_data,
path_pid,
procfs_pid,
view: self.view.clone(),
}),
));
Ok(node)
}
fn is_cacheable(&self) -> bool {
false
}
}
fn mq_sysctl_file(
fs: &Arc<SimpleFs>,
cell: &'static core::sync::atomic::AtomicUsize,
min: usize,
max: usize,
) -> Arc<SimpleFile> {
SimpleFile::new_regular(
fs.clone(),
RwFile::new(move |req| match req {
SimpleFileOperation::Read => Ok(Some(
format!("{}\n", cell.load(Ordering::Relaxed)).into_bytes(),
)),
SimpleFileOperation::Write(data) => {
if !current_user_task()
.as_thread()
.cred()
.has_cap_sys_resource()
{
return Err(VfsError::OperationNotPermitted);
}
let text = core::str::from_utf8(data).map_err(|_| VfsError::InvalidInput)?;
let trimmed = text.trim();
if trimmed.is_empty() {
return Ok(None);
}
let value: usize = trimmed.parse().map_err(|_| VfsError::InvalidInput)?;
if value < min || value > max {
return Err(VfsError::InvalidInput);
}
cell.store(value, Ordering::Relaxed);
Ok(None)
}
}),
)
}
fn unsupported_limit_sysctl_file(fs: &Arc<SimpleFs>, value: &'static str) -> Arc<SimpleFile> {
SimpleFile::new_regular(
fs.clone(),
RwFile::new(move |operation| match operation {
SimpleFileOperation::Read => Ok(Some(value)),
SimpleFileOperation::Write(_) => Err(VfsError::OperationNotSupported),
}),
)
}
fn builder(fs: Arc<SimpleFs>, view: PidView) -> DirMaker {
let mut root = DirMapping::new();
root.add(
"mounts",
SimpleFile::new_regular(fs.clone(), || {
let fs_context = current_fs_context();
let ctx = fs_context.lock();
Ok(crate::pseudofs::proc_mountinfo::render_mounts(&ctx))
}),
);
root.add(
"mountinfo",
SimpleFile::new_regular(fs.clone(), || {
let fs_context = current_fs_context();
let ctx = fs_context.lock();
Ok(crate::pseudofs::proc_mountinfo::render_mountinfo(&ctx))
}),
);
root.add(
"filesystems",
SimpleFile::new_regular(fs.clone(), || {
Ok("nodev\tsysfs\nnodev\tproc\nnodev\ttmpfs\nnodev\tdevtmpfs\n\text4\n")
}),
);
root.add("stat", SimpleFile::new_regular(fs.clone(), render_stat));
root.add(
"diskstats",
SimpleFile::new_regular(fs.clone(), || Ok(render_diskstats())),
);
root.add(
"meminfo",
SimpleFile::new_regular(fs.clone(), || Ok(render_meminfo())),
);
root.add(
"vmstat",
SimpleFile::new_regular(fs.clone(), || Ok(render_vmstat())),
);
root.add(
"cpuinfo",
SimpleFile::new_regular(fs.clone(), || Ok(render_cpuinfo())),
);
root.add(
"uptime",
SimpleFile::new_regular(fs.clone(), || {
let up = monotonic_time();
let secs = up.as_secs();
let cs = up.subsec_millis() / 10;
let idle_secs = secs.saturating_mul(ax_runtime::hal::cpu_num() as u64);
Ok(format!("{secs}.{cs:02} {idle_secs}.00\n"))
}),
);
root.add(
"loadavg",
SimpleFile::new_regular(fs.clone(), || {
let all_tasks = tasks();
let running = all_tasks
.iter()
.filter(|task| {
matches!(
task.state(),
ThreadState::New | ThreadState::Running | ThreadState::Waking
)
})
.count();
let total = all_tasks.len();
Ok(format!("0.00 0.00 0.00 {running}/{total} 1\n"))
}),
);
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())
}
}),
);
root.add(
"interrupts",
SimpleFile::new_regular(fs.clone(), || {
Ok(format!("0: {}", ax_runtime::diagnostics::timer_irq_count()))
}),
);
root.add("sys", {
let mut sys = DirMapping::new();
sys.add("kernel", {
let mut kernel = DirMapping::new();
kernel.add("pid_max", unsupported_limit_sysctl_file(&fs, "32768\n"));
kernel.add(
"osrelease",
SimpleFile::new_regular(fs.clone(), || Ok("6.6.0-starry\n")),
);
kernel.add(
"ostype",
SimpleFile::new_regular(fs.clone(), || Ok("Linux\n")),
);
kernel.add(
"hostname",
SimpleFile::new_regular(
fs.clone(),
RwFile::new(move |req| match req {
SimpleFileOperation::Read => {
let nodename = {
let task = current_user_task();
let nsproxy = task.as_thread().proc_data.namespace_snapshot();
let uts_namespace = nsproxy.uts_ns.lock();
uts_namespace.nodename
};
let name_len = nodename
.iter()
.position(|&byte| byte == 0)
.unwrap_or(nodename.len());
let mut output = Vec::with_capacity(name_len + 1);
output.extend(
nodename[..name_len]
.iter()
.map(|byte| byte.to_ne_bytes()[0]),
);
output.push(b'\n');
Ok(Some(output))
}
SimpleFileOperation::Write(data) => {
if data.is_empty() {
return Ok(None);
}
let hostname = data.strip_suffix(b"\n").unwrap_or(data);
if hostname.len() > 64
|| hostname.iter().any(|byte| matches!(byte, 0 | b'\n'))
{
return Err(VfsError::InvalidInput);
}
if current_user_task().as_thread().cred().euid != 0 {
return Err(VfsError::OperationNotPermitted);
}
let mut nodename = [0; 65];
for (slot, byte) in nodename.iter_mut().zip(hostname) {
*slot = *byte as _;
}
let task = current_user_task();
let update = task.as_thread().proc_data.namespace_update();
update.snapshot().uts_ns.lock().nodename = nodename;
Ok(None)
}
}),
),
);
kernel.add("random", {
let mut random = DirMapping::new();
if let Some(boot_id) = boot_id_proc_file(fs.clone()) {
random.add("boot_id", boot_id);
}
SimpleDir::new_maker(fs.clone(), Arc::new(random))
});
kernel.add(
"perf_event_paranoid",
SimpleFile::new_regular(fs.clone(), || Ok("-1\n")),
);
kernel.add(
"perf_event_mlock_kb",
SimpleFile::new_regular(fs.clone(), || Ok("516\n")),
);
kernel.add(
"perf_event_max_sample_rate",
SimpleFile::new_regular(fs.clone(), || Ok("100000\n")),
);
SimpleDir::new_maker(fs.clone(), Arc::new(kernel))
});
sys.add("vm", {
let mut vm = DirMapping::new();
vm.add(
"overcommit_memory",
SimpleFile::new_regular(fs.clone(), || Ok("0\n")),
);
vm.add(
"max_map_count",
unsupported_limit_sysctl_file(&fs, "65530\n"),
);
SimpleDir::new_maker(fs.clone(), Arc::new(vm))
});
sys.add("fs", {
let mut fs_sys = DirMapping::new();
fs_sys.add(
"file-max",
SimpleFile::new_regular(fs.clone(), || Ok("1048576\n")),
);
fs_sys.add(
"nr_open",
SimpleFile::new_regular(fs.clone(), || Ok("1048576\n")),
);
fs_sys.add("mqueue", {
let mut mqueue = DirMapping::new();
mqueue.add(
"queues_max",
mq_sysctl_file(
&fs,
&crate::ipc::mqueue::MQ_QUEUES_MAX,
0,
i32::MAX as usize,
),
);
mqueue.add(
"msg_max",
mq_sysctl_file(
&fs,
&crate::ipc::mqueue::MQ_MSG_MAX,
crate::ipc::mqueue::MQ_MIN_MSG_MAX,
crate::ipc::mqueue::MQ_HARD_MSG_MAX,
),
);
mqueue.add(
"msgsize_max",
mq_sysctl_file(
&fs,
&crate::ipc::mqueue::MQ_MSGSIZE_MAX,
crate::ipc::mqueue::MQ_MIN_MSGSIZE_MAX,
crate::ipc::mqueue::MQ_HARD_MSGSIZE_MAX,
),
);
mqueue.add(
"msg_default",
mq_sysctl_file(
&fs,
&crate::ipc::mqueue::MQ_MSG_DEFAULT,
crate::ipc::mqueue::MQ_MIN_MSG_MAX,
crate::ipc::mqueue::MQ_HARD_MSG_MAX,
),
);
mqueue.add(
"msgsize_default",
mq_sysctl_file(
&fs,
&crate::ipc::mqueue::MQ_MSGSIZE_DEFAULT,
crate::ipc::mqueue::MQ_MIN_MSGSIZE_MAX,
crate::ipc::mqueue::MQ_HARD_MSGSIZE_MAX,
),
);
SimpleDir::new_maker(fs.clone(), Arc::new(mqueue))
});
SimpleDir::new_maker(fs.clone(), Arc::new(fs_sys))
});
sys.add("net", {
let mut net = DirMapping::new();
net.add("core", {
let mut core = DirMapping::new();
core.add(
"somaxconn",
SimpleFile::new_regular(fs.clone(), || Ok("4096\n")),
);
SimpleDir::new_maker(fs.clone(), Arc::new(core))
});
SimpleDir::new_maker(fs.clone(), Arc::new(net))
});
sys.add("user", {
let mut user = DirMapping::new();
user.add(
"max_user_namespaces",
SimpleFile::new_regular(fs.clone(), || Ok("65536\n")),
);
user.add(
"max_mnt_namespaces",
SimpleFile::new_regular(fs.clone(), || Ok("65536\n")),
);
user.add(
"max_pid_namespaces",
SimpleFile::new_regular(fs.clone(), || Ok("65536\n")),
);
user.add(
"max_net_namespaces",
SimpleFile::new_regular(fs.clone(), || Ok("65536\n")),
);
user.add(
"max_uts_namespaces",
SimpleFile::new_regular(fs.clone(), || Ok("65536\n")),
);
user.add(
"max_ipc_namespaces",
SimpleFile::new_regular(fs.clone(), || Ok("65536\n")),
);
user.add(
"max_cgroup_namespaces",
SimpleFile::new_regular(fs.clone(), || Ok("65536\n")),
);
SimpleDir::new_maker(fs.clone(), Arc::new(user))
});
SimpleDir::new_maker(fs.clone(), Arc::new(sys))
});
root.add("net", {
let mut net = DirMapping::new();
net.add(
"arp",
SimpleFile::new_regular(fs.clone(), || Ok(render_proc_net_arp())),
);
net.add(
"dev",
SimpleFile::new_regular(fs.clone(), || Ok(render_proc_net_dev())),
);
net.add(
"snmp",
SimpleFile::new_regular(fs.clone(), || Ok(render_proc_net_snmp())),
);
SimpleDir::new_maker(fs.clone(), Arc::new(net))
});
root.add("bus", {
let mut bus = DirMapping::new();
bus.add("usb", {
let mut usb = DirMapping::new();
usb.add(
"devices",
SimpleFile::new_regular(fs.clone(), || Ok(render_proc_bus_usb_devices())),
);
SimpleDir::new_maker(fs.clone(), Arc::new(usb))
});
SimpleDir::new_maker(fs.clone(), Arc::new(bus))
});
#[cfg(feature = "jpeg")]
if let Some(compatible) = read_dt_root_property("compatible") {
root.add("device-tree", {
let mut dt = DirMapping::new();
dt.add(
"compatible",
SimpleFile::new_regular(fs.clone(), move || Ok(compatible.clone())),
);
if let Some(model) = read_dt_root_property("model") {
dt.add(
"model",
SimpleFile::new_regular(fs.clone(), move || Ok(model.clone())),
);
}
SimpleDir::new_maker(fs.clone(), Arc::new(dt))
});
}
root.add("dynamic_debug", {
let mut dynamic_debug = DirMapping::new();
dynamic_debug.add(
"control",
super::dyn_debug::create_dyn_debug_control_file(fs.clone()),
);
SimpleDir::new_maker(fs.clone(), Arc::new(dynamic_debug))
});
static ALL_SYMS: LazyInit<String> = LazyInit::new();
KALLSYMS.get_or_init(read_kallsyms);
root.add("kallsyms", {
ALL_SYMS.get_or_init(|| KALLSYMS.dump_all_symbols());
let seq_obj = SeqObject::new(|| Ok(ALL_SYMS.as_str()));
SpecialFsFile::new_regular_with_perm(
fs.clone(),
seq_obj,
NodePermission::from_bits_truncate(0o444),
)
});
let proc_dir = ProcFsHandler {
fs: fs.clone(),
view,
};
SimpleDir::new_maker(fs, Arc::new(proc_dir.chain(root)))
}
pub struct SeqWriter<W: core::fmt::Write> {
inner: W,
col: usize,
}
impl<W: core::fmt::Write> SeqWriter<W> {
pub fn new(inner: W) -> Self {
Self { inner, col: 0 }
}
}
impl<W: core::fmt::Write> SeqWriter<W> {
fn write_str(&mut self, s: &str) -> VfsResult<()> {
self.col += s.len();
self.inner.write_str(s).map_err(|_| VfsError::Io)?;
Ok(())
}
#[allow(unused)]
fn write_char(&mut self, c: char) -> VfsResult<()> {
self.col += c.len_utf8();
self.inner.write_char(c).map_err(|_| VfsError::Io)?;
Ok(())
}
fn pad_to(&mut self, target: usize) -> VfsResult<()> {
if self.col < target {
let pad = target - self.col;
for _ in 0..pad {
self.inner.write_char(' ').map_err(|_| VfsError::Io)?;
}
self.col = target;
}
Ok(())
}
fn newline(&mut self) -> VfsResult<()> {
self.inner.write_char('\n').map_err(|_| VfsError::Io)?;
self.col = 0;
Ok(())
}
}
impl<W: core::fmt::Write> core::fmt::Write for SeqWriter<W> {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
self.write_str(s).map_err(|_| core::fmt::Error)
}
}
#[cfg(all(test, axtest))]
fn proc_mountinfo_lines_match_linux_layout() -> bool {
let ctx_arc = current_fs_context();
let ctx = ctx_arc.lock();
let text = crate::pseudofs::proc_mountinfo::render_mountinfo(&ctx);
!text.is_empty()
&& text.lines().any(|line| line.contains(" / / "))
&& text.lines().all(|line| {
let Some((pre_separator, post_separator)) = line.split_once(" - ") else {
return false;
};
pre_separator.split_whitespace().count() >= 6
&& post_separator.split_whitespace().count() >= 3
})
}
#[cfg(all(test, axtest))]
mod tests {
#[axtest::axtest]
fn proc_mountinfo_lines_match_linux_layout() {
assert!(super::proc_mountinfo_lines_match_linux_layout());
}
}