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