Files
RedBear-OS/src/scheme/sys/mod.rs
T
2026-04-13 20:51:24 +02:00

334 lines
11 KiB
Rust

// 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<RwLock<L1, Option<Vec<u8>>>>,
},
SchemeRoot,
}
#[derive(Clone, Copy)]
enum Kind {
Rd(fn(&mut CleanLockToken) -> Result<Vec<u8>>),
Wr(fn(&[u8], &mut CleanLockToken) -> Result<usize>),
}
use Kind::{Rd, Wr};
impl Kind {
fn generate_data(&self, token: &mut CleanLockToken) -> Result<Vec<u8>> {
match self {
Rd(handler) => handler(token),
Wr(_) => Err(Error::new(EISDIR)),
}
}
}
/// System information scheme
pub struct SysScheme;
static HANDLES: RwLock<L1, HandleMap<Handle>> = 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<usize> {
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<OpenResult> {
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<u64> {
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<usize> {
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<usize> {
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<usize> {
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<usize> {
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(())
}
}