Skip to main content

strat9_kernel/vfs/
procfs.rs

1//! Procfs - process filesystem (similar to Linux & BSD /proc)
2//!
3//! Provides information about running processes and system state.
4//!
5//! # Structure
6//!
7//! ```text
8//! /proc/
9//!   self/       -> symlink to current process
10//!   <pid>/      -> process directory
11//!     status    -> process status
12//!     cmdline   -> command line
13//!     fd/       -> file descriptors
14//!   cpuinfo     -> CPU information
15//!   meminfo     -> memory information
16//!   version     -> kernel version
17//! ```
18
19use crate::{
20    process::{current_pid, get_all_tasks, get_parent_pid},
21    silo,
22    syscall::error::SyscallError,
23    vfs::scheme::{
24        finalize_pseudo_stat, DirEntry, FileFlags, FileStat, OpenFlags, OpenResult, Scheme,
25        DEV_PROCFS, DT_DIR, DT_REG,
26    },
27};
28use alloc::{format, string::String, sync::Arc, vec::Vec};
29use core::fmt::Write;
30
31/// Procfs scheme
32pub struct ProcScheme {
33    // Empty for now - stateless
34}
35
36impl ProcScheme {
37    /// Creates a new instance.
38    pub fn new() -> Self {
39        ProcScheme {}
40    }
41
42    /// Get procfs entry content
43    fn get_entry(&self, path: &str) -> Result<ProcEntry, SyscallError> {
44        // Handle /proc/self
45        if path == "self" || path == "self/" {
46            if let Some(pid) = current_pid() {
47                return Ok(ProcEntry::File(format!("{}\n", pid)));
48            }
49            return Err(SyscallError::NotFound);
50        }
51
52        // Handle /proc/self/status
53        if path.starts_with("self/") {
54            let subpath = &path[5..];
55            if let Some(pid) = current_pid() {
56                return self.get_process_entry(pid as u64, subpath);
57            }
58            return Err(SyscallError::NotFound);
59        }
60
61        // Handle /proc/<pid>/...
62        if let Some(pid_end) = path.find('/') {
63            let pid_str = &path[..pid_end];
64            let subpath = &path[pid_end + 1..];
65            if let Ok(pid) = pid_str.parse::<u64>() {
66                return self.get_process_entry(pid, subpath);
67            }
68        }
69
70        // Handle /proc/<pid> (directory listing)
71        if path.parse::<u64>().is_ok() {
72            return Ok(ProcEntry::Directory);
73        }
74
75        // Handle root entries
76        match path {
77            "" | "/" => Ok(ProcEntry::Directory),
78            "cpuinfo" => Ok(ProcEntry::File(self.get_cpuinfo())),
79            "meminfo" => Ok(ProcEntry::File(self.get_meminfo())),
80            "version" => Ok(ProcEntry::File(self.get_version())),
81            "silos" => Ok(ProcEntry::File(self.get_silos())),
82            _ => Err(SyscallError::NotFound),
83        }
84    }
85
86    /// Get process-specific entry
87    fn get_process_entry(&self, pid: u64, subpath: &str) -> Result<ProcEntry, SyscallError> {
88        let tasks = get_all_tasks().ok_or(SyscallError::NotFound)?;
89        let task = tasks
90            .iter()
91            .find(|t| t.pid as u64 == pid)
92            .ok_or(SyscallError::NotFound)?;
93
94        match subpath {
95            "" | "/" => Ok(ProcEntry::Directory),
96            "status" => Ok(ProcEntry::File(self.get_process_status(task))),
97            "cmdline" => Ok(ProcEntry::File(format!(
98                "{}\n",
99                crate::arch::serial::get_cmdline()
100            ))),
101            _ => Err(SyscallError::NotFound),
102        }
103    }
104
105    /// Generate /proc/cpuinfo content from actual CPUID data.
106    fn get_cpuinfo(&self) -> String {
107        let mut output = String::new();
108        let cpu_count = crate::arch::percpu::get_cpu_count();
109        let host = crate::arch::cpuid::host();
110        let flags = crate::arch::cpuid::features_to_flags_string(host.features);
111        let has_fpu = host.features.contains(crate::arch::cpuid::CpuFeatures::FPU);
112
113        for i in 0..cpu_count {
114            let _ = writeln!(output, "processor\t: {}", i);
115            let _ = writeln!(output, "vendor_id\t: {}", host.vendor_string());
116            let _ = writeln!(output, "cpu family\t: {}", host.family);
117            let _ = writeln!(output, "model\t\t: {}", host.model);
118            let _ = writeln!(output, "model name\t: {}", host.model_name_str());
119            let _ = writeln!(output, "stepping\t: {}", host.stepping);
120            let _ = writeln!(output, "cpu MHz\t\t: 2400.000");
121            let _ = writeln!(output, "cache size\t: 4096 KB");
122            let _ = writeln!(output, "physical id\t: {}", i);
123            let _ = writeln!(output, "siblings\t: 1");
124            let _ = writeln!(output, "core id\t\t: {}", i);
125            let _ = writeln!(output, "cpu cores\t: 1");
126            let _ = writeln!(output, "apicid\t\t: {}", i);
127            let _ = writeln!(output, "fpu\t\t: {}", if has_fpu { "yes" } else { "no" });
128            let _ = writeln!(
129                output,
130                "fpu_exception\t: {}",
131                if has_fpu { "yes" } else { "no" }
132            );
133            let _ = writeln!(output, "cpuid level\t: 13");
134            let _ = writeln!(output, "wp\t\t: yes");
135            let _ = writeln!(output, "flags\t\t: {}", flags);
136            let _ = writeln!(output, "bogomips\t: 4800.00");
137            let _ = writeln!(output, "clflush size\t: 64");
138            let _ = writeln!(output, "cache_alignment\t: 64");
139            let _ = writeln!(output, "address sizes\t: 40 bits physical, 48 bits virtual");
140            let _ = writeln!(output, "power management:\n");
141            let _ = writeln!(output, "");
142        }
143
144        output
145    }
146
147    /// Generate /proc/meminfo content
148    fn get_meminfo(&self) -> String {
149        // Simplified meminfo for now
150        let mut output = String::new();
151        let _ = writeln!(output, "MemTotal:       {:>10} kB", 262144); // 256 MB default
152        let _ = writeln!(output, "MemFree:        {:>10} kB", 131072);
153        let _ = writeln!(output, "MemUsed:        {:>10} kB", 131072);
154        let _ = writeln!(output, "Buffers:               0 kB");
155        let _ = writeln!(output, "Cached:                0 kB");
156        let _ = writeln!(output, "SwapTotal:             0 kB");
157        let _ = writeln!(output, "SwapFree:              0 kB");
158        output
159    }
160
161    /// Generate /proc/version content
162    fn get_version(&self) -> String {
163        format!("Strat9-OS version 0.1.0 (Bedrock) #1 SMP x86_64 Strat9\n")
164    }
165
166    /// Generate /proc/silos content
167    fn get_silos(&self) -> String {
168        let mut output = String::new();
169        let mut silos = silo::list_silos_snapshot();
170        silos.sort_by_key(|s| s.id);
171        let _ = writeln!(
172            output,
173            "id\tstate\ttasks\tmem_used\tmem_min\tmem_max\tgfx_flags\tgfx_sessions\tgfx_ttl\tlabel\tname"
174        );
175        for s in silos {
176            let label = s.strate_label.unwrap_or_else(|| String::from("-"));
177            let max = if s.mem_max_bytes == 0 {
178                String::from("unlimited")
179            } else {
180                format!("{}", s.mem_max_bytes)
181            };
182            let _ = writeln!(
183                output,
184                "{}\t{:?}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}",
185                s.id,
186                s.state,
187                s.task_count,
188                s.mem_usage_bytes,
189                s.mem_min_bytes,
190                max,
191                s.graphics_flags,
192                s.graphics_max_sessions,
193                s.graphics_session_ttl_sec,
194                label,
195                s.name
196            );
197        }
198        output
199    }
200
201    /// Generate process status
202    fn get_process_status(&self, task: &Arc<crate::process::task::Task>) -> String {
203        let mut output = String::new();
204
205        let _ = writeln!(output, "Name:\t{}", task.name);
206        let _ = writeln!(output, "State:\tR (running)");
207        let _ = writeln!(output, "Tgid:\t{}", task.tgid);
208        let _ = writeln!(output, "Pid:\t{}", task.pid);
209        let _ = writeln!(
210            output,
211            "PPid:\t{}",
212            get_parent_pid(task.id).map(|p| p as u64).unwrap_or(0)
213        );
214        let _ = writeln!(
215            output,
216            "Pgid:\t{}",
217            task.pgid.load(core::sync::atomic::Ordering::Relaxed)
218        );
219        let _ = writeln!(
220            output,
221            "Sid:\t{}",
222            task.sid.load(core::sync::atomic::Ordering::Relaxed)
223        );
224        let uid = task.uid.load(core::sync::atomic::Ordering::Relaxed);
225        let euid = task.euid.load(core::sync::atomic::Ordering::Relaxed);
226        let gid = task.gid.load(core::sync::atomic::Ordering::Relaxed);
227        let egid = task.egid.load(core::sync::atomic::Ordering::Relaxed);
228        let _ = writeln!(output, "Uid:\t{}\t{}\t{}\t{}", uid, euid, euid, euid);
229        let _ = writeln!(output, "Gid:\t{}\t{}\t{}\t{}", gid, egid, egid, egid);
230        let _ = writeln!(output, "Threads:\t1");
231        if let Some((silo_id, label, mem_used, mem_min, mem_max)) =
232            silo::silo_info_for_task(task.id)
233        {
234            let label = label.unwrap_or_else(|| String::from("-"));
235            let _ = writeln!(output, "SiloId:\t{}", silo_id);
236            let _ = writeln!(output, "SiloLabel:\t{}", label);
237            let _ = writeln!(output, "SiloMemUsed:\t{}", mem_used);
238            let _ = writeln!(output, "SiloMemMin:\t{}", mem_min);
239            if mem_max == 0 {
240                let _ = writeln!(output, "SiloMemMax:\tunlimited");
241            } else {
242                let _ = writeln!(output, "SiloMemMax:\t{}", mem_max);
243            }
244        }
245
246        output
247    }
248}
249
250/// Procfs entry type
251#[allow(dead_code)]
252enum ProcEntry {
253    File(String),
254    Directory,
255}
256
257/// Kind constants for procfs file-id encoding.
258/// Fixed IDs (0, 1, 10, 11, 12) use kind = 0 and are stored as-is.
259const KIND_PROC_DIR: u64 = 1; // /proc/<pid>  directory
260const KIND_PROC_STATUS: u64 = 2; // /proc/<pid>/status
261const KIND_PROC_CMDLINE: u64 = 3; // /proc/<pid>/cmdline
262
263impl ProcScheme {
264    /// Encode a (kind, pid) pair into a file_id.
265    ///
266    /// The high 32 bits hold the kind; the low 32 bits hold the pid.
267    /// This guarantees no collision between entries regardless of PID value.
268    fn encode_id(kind: u64, pid: u64) -> u64 {
269        (kind << 32) | (pid & 0xFFFF_FFFF)
270    }
271
272    /// Decode a file_id back into (kind, pid).
273    fn decode_id(id: u64) -> (u64, u64) {
274        (id >> 32, id & 0xFFFF_FFFF)
275    }
276}
277
278impl Scheme for ProcScheme {
279    /// Performs the open operation.
280    fn open(&self, path: &str, _flags: OpenFlags) -> Result<OpenResult, SyscallError> {
281        let entry = self.get_entry(path)?;
282
283        let (flags, file_id) = match entry {
284            ProcEntry::File(_) => {
285                let id = match path {
286                    "cpuinfo" => 10,
287                    "meminfo" => 11,
288                    "version" => 12,
289                    "silos" => 13,
290                    "self/status" => Self::encode_id(
291                        KIND_PROC_STATUS,
292                        current_pid().map(|p| p as u64).unwrap_or(0),
293                    ),
294                    "self/cmdline" => Self::encode_id(
295                        KIND_PROC_CMDLINE,
296                        current_pid().map(|p| p as u64).unwrap_or(0),
297                    ),
298                    _ if path.starts_with("self") => 1,
299                    _ => {
300                        if let Some(slash_idx) = path.find('/') {
301                            let pid_str = &path[..slash_idx];
302                            let sub = &path[slash_idx + 1..];
303                            if let Ok(pid) = pid_str.parse::<u64>() {
304                                match sub {
305                                    "status" => Self::encode_id(KIND_PROC_STATUS, pid),
306                                    "cmdline" => Self::encode_id(KIND_PROC_CMDLINE, pid),
307                                    _ => 0xFFFF,
308                                }
309                            } else {
310                                0xFFFF
311                            }
312                        } else {
313                            0xFFFF
314                        }
315                    }
316                };
317                (FileFlags::empty(), id)
318            }
319            ProcEntry::Directory => {
320                let id = if path.is_empty() || path == "/" {
321                    0
322                } else if path == "self" || path == "self/" {
323                    1
324                } else if let Ok(pid) = path.parse::<u64>() {
325                    Self::encode_id(KIND_PROC_DIR, pid)
326                } else {
327                    2
328                };
329                (FileFlags::DIRECTORY, id)
330            }
331        };
332
333        if file_id == 0xFFFF {
334            return Err(SyscallError::NotFound);
335        }
336
337        Ok(OpenResult {
338            file_id,
339            size: None,
340            flags,
341        })
342    }
343
344    /// Performs the read operation.
345    fn read(&self, file_id: u64, offset: u64, buf: &mut [u8]) -> Result<usize, SyscallError> {
346        let (kind, pid) = Self::decode_id(file_id);
347        let content = if file_id == 0 {
348            // Root directory listing
349            let mut list = String::from("self\ncpuinfo\nmeminfo\nversion\n");
350            if let Some(tasks) = get_all_tasks() {
351                for task in tasks {
352                    let _ = writeln!(list, "{}", task.pid);
353                }
354            }
355            list
356        } else if file_id == 1 || kind == KIND_PROC_DIR {
357            // Process directory listing (self dir or /proc/<pid>)
358            String::from("status\ncmdline\n")
359        } else {
360            // File content : distinguish by kind (high 32 bits) for process files,
361            // or by the literal file_id for well-known fixed files.
362            match (kind, file_id) {
363                (0, 10) => self.get_cpuinfo(),
364                (0, 11) => self.get_meminfo(),
365                (0, 12) => self.get_version(),
366                (0, 13) => self.get_silos(),
367                (KIND_PROC_STATUS, _) => {
368                    let tasks = get_all_tasks().ok_or(SyscallError::NotFound)?;
369                    let task = tasks
370                        .iter()
371                        .find(|t| t.pid as u64 == pid)
372                        .ok_or(SyscallError::NotFound)?;
373                    self.get_process_status(task)
374                }
375                (KIND_PROC_CMDLINE, _) => {
376                    let tasks = get_all_tasks().ok_or(SyscallError::NotFound)?;
377                    let task = tasks
378                        .iter()
379                        .find(|t| t.pid as u64 == pid)
380                        .ok_or(SyscallError::NotFound)?;
381                    format!("{}\n", task.name)
382                }
383                _ => return Err(SyscallError::IoError),
384            }
385        };
386
387        if offset >= content.len() as u64 {
388            return Ok(0);
389        }
390
391        let start = offset as usize;
392        let end = core::cmp::min(start + buf.len(), content.len());
393        let to_copy = end - start;
394
395        buf[..to_copy].copy_from_slice(&content.as_bytes()[start..end]);
396        Ok(to_copy)
397    }
398
399    /// Performs the write operation.
400    fn write(&self, _file_id: u64, _offset: u64, _buf: &[u8]) -> Result<usize, SyscallError> {
401        Err(SyscallError::PermissionDenied)
402    }
403
404    /// Performs the close operation.
405    fn close(&self, _file_id: u64) -> Result<(), SyscallError> {
406        Ok(())
407    }
408
409    /// Performs the stat operation.
410    fn stat(&self, file_id: u64) -> Result<FileStat, SyscallError> {
411        let (kind, _pid) = Self::decode_id(file_id);
412        let is_dir = file_id == 0 || file_id == 1 || kind == KIND_PROC_DIR;
413        let mut st = if is_dir {
414            FileStat {
415                st_ino: file_id,
416                st_mode: 0o040555,
417                st_nlink: 2,
418                st_size: 0,
419                st_blksize: 512,
420                st_blocks: 0,
421                ..FileStat::zeroed()
422            }
423        } else {
424            FileStat {
425                st_ino: file_id,
426                st_mode: 0o100444,
427                st_nlink: 1,
428                st_size: 0,
429                st_blksize: 512,
430                st_blocks: 0,
431                ..FileStat::zeroed()
432            }
433        };
434
435        let (kind, pid) = Self::decode_id(file_id);
436        let per_process_node =
437            kind == KIND_PROC_DIR || kind == KIND_PROC_STATUS || kind == KIND_PROC_CMDLINE;
438        if per_process_node && pid != 0 {
439            if let Some(tasks) = get_all_tasks() {
440                if let Some(task) = tasks.iter().find(|t| t.pid as u64 == pid) {
441                    st.st_uid = task.uid.load(core::sync::atomic::Ordering::Relaxed);
442                    st.st_gid = task.gid.load(core::sync::atomic::Ordering::Relaxed);
443                }
444            }
445        }
446        Ok(finalize_pseudo_stat(st, DEV_PROCFS, 0))
447    }
448
449    /// Performs the readdir operation.
450    fn readdir(&self, file_id: u64) -> Result<Vec<DirEntry>, SyscallError> {
451        let (kind, pid) = Self::decode_id(file_id);
452        if file_id == 0 {
453            let mut entries = Vec::new();
454            entries.push(DirEntry {
455                ino: 1,
456                file_type: DT_DIR,
457                name: String::from("self"),
458            });
459            entries.push(DirEntry {
460                ino: 10,
461                file_type: DT_REG,
462                name: String::from("cpuinfo"),
463            });
464            entries.push(DirEntry {
465                ino: 11,
466                file_type: DT_REG,
467                name: String::from("meminfo"),
468            });
469            entries.push(DirEntry {
470                ino: 12,
471                file_type: DT_REG,
472                name: String::from("version"),
473            });
474            entries.push(DirEntry {
475                ino: 13,
476                file_type: DT_REG,
477                name: String::from("silos"),
478            });
479            if let Some(tasks) = get_all_tasks() {
480                for task in tasks {
481                    entries.push(DirEntry {
482                        ino: Self::encode_id(KIND_PROC_DIR, task.pid as u64),
483                        file_type: DT_DIR,
484                        name: format!("{}", task.pid),
485                    });
486                }
487            }
488            Ok(entries)
489        } else if file_id == 1 || kind == KIND_PROC_DIR {
490            let effective_pid = if file_id == 1 {
491                current_pid().map(|p| p as u64).unwrap_or(0)
492            } else {
493                pid
494            };
495            Ok(alloc::vec![
496                DirEntry {
497                    ino: Self::encode_id(KIND_PROC_STATUS, effective_pid),
498                    file_type: DT_REG,
499                    name: String::from("status"),
500                },
501                DirEntry {
502                    ino: Self::encode_id(KIND_PROC_CMDLINE, effective_pid),
503                    file_type: DT_REG,
504                    name: String::from("cmdline"),
505                },
506            ])
507        } else {
508            Err(SyscallError::InvalidArgument)
509        }
510    }
511}