//! /proc (stage 18): a window into the running process, made up on the fly. //! //! Linux's /proc has a directory for every process and lots of files. We //! have just the corner that programs (and C libraries) lean on most: //! //! /proc/self/exe a symlink to the program that's running //! /proc/self/fd/ one symlink per open file descriptor: //! /proc/self/fd/N to the file's path ("/tmp/notes.txt"), or a //! name like "pipe:[4123]" or "socket:[1007]" for //! things that have no path, or "/dev/pts/0" //! //! Nothing here is stored anywhere. Every lookup and every listing asks //! the process that is *asking* (`current()`) for its fd table, so two //! programs listing /proc/self/fd at once each see their own files. That //! is the whole trick of "self". //! //! Following one of the fd links (open, stat) is special-cased in //! syscall.rs, as on Linux: opening /proc/self/fd/N opens the file that's //! behind fd N again (a pipe or terminal: the same open file, like dup), //! even when the link's text isn't a real path ("pipe:[...]"). //! //! Differences from Linux: /proc/self is a directory, not a symlink to //! /proc/, and there are no /proc/ directories yet. use super::file::File; use super::{DirEntry, FileSystem, Inode, InodeKind, StatFs}; use crate::errno::Errno; use alloc::{format, string::String, sync::Arc, vec::Vec}; /// f_type of a mounted /proc (PROC_SUPER_MAGIC in ). pub const PROC_MAGIC: u64 = 0x9fa0; pub struct ProcFs; impl FileSystem for ProcFs { fn name(&self) -> &'static str { "proc" } fn root(&self) -> Arc { Arc::new(Node::Root) } fn statfs(&self) -> StatFs { StatFs { magic: PROC_MAGIC, block_size: 4096, name_max: 255, ..Default::default() } } } /// The handful of things in our /proc. #[derive(Clone, Copy, PartialEq, Eq, Debug)] enum Node { Root, SelfDir, FdDir, Exe, /// /proc/self/fd/N Fd(u64), } /// The open files of the process asking, by fd number. fn my_fds() -> Vec<(u64, Arc)> { crate::syscall::current().map(|p| p.open_files()).unwrap_or_default() } /// What /proc/self/fd/N says for this open file (readlink's answer). pub fn fd_link(file: &Arc) -> String { if let Some(name) = file.proc_link() { return name; // devices and terminals know their /dev name } if let Some(path) = file.path() { return path; } let ino = super::file::anon_ino(file); if file.as_socket().is_some() || file.as_unix().is_some() { format!("socket:[{}]", ino) } else if file.as_epoll().is_some() { String::from("anon_inode:[eventpoll]") } else if file.stat().is_ok_and(|s| s.kind == super::file::KIND_PIPE) { format!("pipe:[{}]", ino) } else { String::from("anon_inode:[unknown]") } } /// Is `path` (absolute, normalized) /proc/self/fd/N? Then N. pub fn fd_number(path: &str) -> Option { let n = path.strip_prefix("/proc/self/fd/")?; if n.is_empty() || !n.bytes().all(|b| b.is_ascii_digit()) || (n.len() > 1 && n.starts_with('0')) { return None; } n.parse().ok() } impl Inode for Node { fn kind(&self) -> InodeKind { match self { Node::Root | Node::SelfDir | Node::FdDir => InodeKind::Directory, Node::Exe | Node::Fd(_) => InodeKind::Symlink, } } fn size(&self) -> usize { match self { Node::Root => 1, Node::SelfDir => 2, Node::FdDir => my_fds().len(), _ => self.readlink().map(|t| t.len()).unwrap_or(0), } } fn mode(&self) -> u32 { match self { Node::Root | Node::SelfDir => 0o555, Node::FdDir => 0o500, Node::Exe => 0o777, Node::Fd(_) => 0o700, // lrwx------, like Linux } } fn ino(&self) -> Option { Some(match *self { Node::Root => 1, Node::SelfDir => 2, Node::FdDir => 3, Node::Exe => 4, Node::Fd(n) => 1000 + n, }) } fn owner(&self) -> (u32, u32) { // Everything under /proc/self belongs to whoever is looking. match (self, crate::syscall::current()) { (Node::Root, _) | (_, Err(_)) => (0, 0), (_, Ok(p)) => { let c = *p.creds.lock(); (c.euid, c.egid) } } } fn readlink(&self) -> Result { match *self { Node::Exe => Ok(crate::syscall::current()?.exe.lock().clone()), Node::Fd(n) => { let file = crate::syscall::current()?.file(n).map_err(|_| Errno::ENOENT)?; Ok(fd_link(&file)) } _ => Err(Errno::EINVAL), } } fn lookup(&self, name: &str) -> Result, Errno> { let node = match (self, name) { (Node::Root, "self") => Node::SelfDir, (Node::SelfDir, "fd") => Node::FdDir, (Node::SelfDir, "exe") => Node::Exe, (Node::FdDir, n) => { let fd = fd_number(&format!("/proc/self/fd/{}", n)).ok_or(Errno::ENOENT)?; crate::syscall::current()?.file(fd).map_err(|_| Errno::ENOENT)?; Node::Fd(fd) } (Node::Root | Node::SelfDir, _) => return Err(Errno::ENOENT), _ => return Err(Errno::ENOTDIR), }; Ok(Arc::new(node)) } fn list(&self) -> Result, Errno> { let entry = |name: String, kind, ino| DirEntry { name, kind, ino }; Ok(match self { Node::Root => alloc::vec![entry(String::from("self"), InodeKind::Directory, 2)], Node::SelfDir => alloc::vec![ entry(String::from("exe"), InodeKind::Symlink, 4), entry(String::from("fd"), InodeKind::Directory, 3), ], Node::FdDir => my_fds().into_iter().map(|(fd, _)| entry(format!("{}", fd), InodeKind::Symlink, 1000 + fd)).collect(), _ => return Err(Errno::ENOTDIR), }) } fn create(&self, _name: &str, _kind: InodeKind) -> Result, Errno> { Err(if self.kind() == InodeKind::Directory { Errno::EACCES } else { Errno::ENOTDIR }) } fn remove(&self, _name: &str) -> Result<(), Errno> { Err(if self.kind() == InodeKind::Directory { Errno::EACCES } else { Errno::ENOTDIR }) } }