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