// TODO: This scheme can be simplified significantly, and through it, several other APIs where it's // dubious whether they require dedicated schemes (like irq, dtb, acpi). In particular, the kernel // could abandon the filesystem-like APIs here in favor of SYS_CALL, and instead let userspace wrap // those to say shell-accessible fs-like APIs. use ::syscall::{ dirent::{DirEntry, DirentBuf, DirentKind}, EACCES, EINVAL, EIO, EISDIR, ENOTDIR, EPERM, }; use alloc::{sync::Arc, vec::Vec}; use core::str; #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] use crate::arch::interrupt; use crate::{ context::file::InternalFlags, sync::{CleanLockToken, RwLock, L1}, syscall::{ data::Stat, error::{Error, Result, EBADF, ENOENT}, flag::{MODE_DIR, MODE_FILE}, usercopy::{UserSliceRo, UserSliceWo}, }, }; use super::{CallerCtx, HandleMap, KernelScheme, OpenResult, StrOrBytes}; mod block; mod context; mod cpu; mod exe; mod fdstat; mod iostat; mod irq; mod log; mod stat; mod syscall; mod uname; enum Handle { TopLevel, Resource { path: &'static str, kind: Kind, data: Arc>>>, }, SchemeRoot, } #[derive(Clone, Copy)] enum Kind { Rd(fn(&mut CleanLockToken) -> Result>), Wr(fn(&[u8], &mut CleanLockToken) -> Result), } use Kind::{Rd, Wr}; impl Kind { fn generate_data(&self, token: &mut CleanLockToken) -> Result> { match self { Rd(handler) => handler(token), Wr(_) => Err(Error::new(EISDIR)), } } } /// System information scheme pub struct SysScheme; static HANDLES: RwLock> = RwLock::new(HandleMap::new()); const FILES: &[(&str, Kind)] = &[ ("block", Rd(block::resource)), ("context", Rd(context::resource)), ("cpu", Rd(cpu::resource)), #[cfg(feature = "sys_fdstat")] ("fdstat", Rd(fdstat::resource)), ("exe", Rd(exe::resource)), ("iostat", Rd(iostat::resource)), ("irq", Rd(irq::resource)), ("log", Rd(log::resource)), ("syscall", Rd(syscall::resource)), ("uname", Rd(uname::resource)), ("env", Rd(|_| Ok(Vec::from(crate::startup::init_env())))), #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] ("spurious_irq", Rd(interrupt::irq::spurious_irq_resource)), ("stat", Rd(stat::resource)), // Disabled because the debugger is inherently unsafe and probably will break the system. /* ("trigger_debugger", Rd(|token| unsafe { crate::debugger::debugger(None, token); Ok(Vec::new()) })), */ ( "update_time_offset", Wr(crate::time::sys_update_time_offset), ), ( "kstop", Wr(|arg, token| unsafe { match arg.trim_ascii() { b"shutdown" => crate::stop::kstop(token), b"reset" => crate::stop::kreset(), b"emergency_reset" => crate::stop::emergency_reset(), _ => Err(Error::new(EINVAL)), } }), ), ]; impl KernelScheme for SysScheme { fn scheme_root(&self, token: &mut CleanLockToken) -> Result { let id = HANDLES.write(token.token()).insert(Handle::SchemeRoot); Ok(id) } fn kopenat( &self, id: usize, user_buf: StrOrBytes, _flags: usize, _fcntl_flags: u32, ctx: CallerCtx, token: &mut CleanLockToken, ) -> Result { if !matches!(HANDLES.read(token.token()).get(id)?, Handle::SchemeRoot) { return Err(Error::new(EACCES)); } let path = user_buf .as_str() .or(Err(Error::new(EINVAL)))? .trim_matches('/'); if path.is_empty() { let id = HANDLES.write(token.token()).insert(Handle::TopLevel); Ok(OpenResult::SchemeLocal(id, InternalFlags::POSITIONED)) } else { //Have to iterate to get the path without allocation let entry = FILES .iter() .find(|(entry_path, _)| *entry_path == path) .ok_or(Error::new(ENOENT))?; if matches!(entry.1, Wr(_)) && ctx.uid != 0 { return Err(Error::new(EPERM)); } // TODO: Initialize resources during openat to use them as a snapshot. let id = HANDLES.write(token.token()).insert(Handle::Resource { path: entry.0, kind: entry.1, data: Arc::new(RwLock::new(None)), }); Ok(OpenResult::SchemeLocal(id, InternalFlags::POSITIONED)) } } fn fsize(&self, id: usize, token: &mut CleanLockToken) -> Result { let (kind, data_lock) = { match HANDLES.read(token.token()).get(id)? { Handle::TopLevel => return Ok(0), Handle::Resource { kind, data, .. } => (*kind, data.clone()), Handle::SchemeRoot => return Err(Error::new(EBADF)), } }; if matches!(kind, Kind::Wr(_)) { return Ok(0); } let is_data_none = data_lock.write(token.token()).is_none(); if is_data_none { let new_data = kind.generate_data(token)?; let mut data_guard = data_lock.write(token.token()); if data_guard.is_none() { *data_guard = Some(new_data); } } let data_guard = data_lock.read(token.token()); let data = data_guard.as_ref().ok_or(Error::new(EIO))?; Ok(data.len() as u64) } fn close(&self, id: usize, token: &mut CleanLockToken) -> Result<()> { HANDLES.write(token.token()).remove(id)?; Ok(()) } fn kfpath(&self, id: usize, buf: UserSliceWo, token: &mut CleanLockToken) -> Result { let path = match HANDLES.read(token.token()).get(id)? { Handle::TopLevel => "", Handle::Resource { path, .. } => path, Handle::SchemeRoot => return Err(Error::new(EBADF)), }; const FIRST: &[u8] = b"sys:"; let mut bytes_read = buf.copy_common_bytes_from_slice(FIRST)?; if let Some(remaining) = buf.advance(FIRST.len()) { bytes_read += remaining.copy_common_bytes_from_slice(path.as_bytes())?; } Ok(bytes_read) } fn kreadoff( &self, id: usize, buffer: UserSliceWo, pos: u64, _flags: u32, _stored_flags: u32, token: &mut CleanLockToken, ) -> Result { let Ok(pos) = usize::try_from(pos) else { return Ok(0); }; let (kind, data_lock) = { match HANDLES.read(token.token()).get(id)? { Handle::Resource { kind, data, .. } => (*kind, data.clone()), _ => return Err(Error::new(EBADF)), } }; let is_data_none = data_lock.write(token.token()).is_none(); if is_data_none { let new_data = kind.generate_data(token)?; let mut data_guard = data_lock.write(token.token()); if data_guard.is_none() { *data_guard = Some(new_data); } } let data_guard = data_lock.read(token.token()); let data = data_guard.as_ref().ok_or(Error::new(EIO))?; let avail_buf = data.get(pos..).unwrap_or(&[]); buffer.copy_common_bytes_from_slice(avail_buf) } fn kwriteoff( &self, id: usize, buffer: UserSliceRo, _pos: u64, _flags: u32, _stored_flags: u32, token: &mut CleanLockToken, ) -> Result { let (handler, intermediate, len) = match HANDLES.read(token.token()).get(id)? { Handle::TopLevel | Handle::Resource { kind: Kind::Rd(_), .. } => return Err(Error::new(EISDIR)), Handle::Resource { kind: Kind::Wr(handler), .. } => { let mut intermediate = [0_u8; 256]; let len = buffer.copy_common_bytes_to_slice(&mut intermediate)?; (*handler, intermediate, len) } Handle::SchemeRoot => return Err(Error::new(EBADF)), }; handler(&intermediate[..len], token) } fn getdents( &self, id: usize, buf: UserSliceWo, header_size: u16, first_index: u64, token: &mut CleanLockToken, ) -> Result { let Ok(first_index) = usize::try_from(first_index) else { return Ok(0); }; match HANDLES.read(token.token()).get(id)? { Handle::Resource { .. } => Err(Error::new(ENOTDIR)), Handle::TopLevel => { let mut buf = DirentBuf::new(buf, header_size).ok_or(Error::new(EIO))?; for (this_idx, (name, _)) in FILES.iter().enumerate().skip(first_index) { buf.entry(DirEntry { inode: this_idx as u64, next_opaque_id: this_idx as u64 + 1, kind: DirentKind::Regular, name, })?; } Ok(buf.finalize()) } Handle::SchemeRoot => Err(Error::new(EBADF)), } } fn kfstat(&self, id: usize, buf: UserSliceWo, token: &mut CleanLockToken) -> Result<()> { let stat_base = { let handles = HANDLES.read(token.token()); match handles.get(id)? { Handle::Resource { kind, data, .. } => Some((*kind, data.clone())), Handle::TopLevel => None, Handle::SchemeRoot => return Err(Error::new(EBADF)), } }; let stat = if let Some((kind, data_lock)) = stat_base { let is_data_none = data_lock.write(token.token()).is_none(); if is_data_none { let new_data = kind.generate_data(token)?; let mut data_guard = data_lock.write(token.token()); if data_guard.is_none() { *data_guard = Some(new_data); } } let data_guard = data_lock.read(token.token()); let data = data_guard.as_ref().ok_or(Error::new(EIO))?; let size = match kind { Kind::Rd(_) => data.len() as u64, Kind::Wr(_) => 0, }; Stat { st_mode: 0o666 | MODE_FILE, st_uid: 0, st_gid: 0, st_size: size, ..Default::default() } } else { Stat { st_mode: 0o444 | MODE_DIR, st_uid: 0, st_gid: 0, st_size: 0, ..Default::default() } }; buf.copy_exactly(&stat)?; Ok(()) } }