Files
RedBear-OS/src/scheme/proc.rs
T

2317 lines
90 KiB
Rust

use crate::{
context::{
self,
context::{HardBlockedReason, LockedFdTbl, SignalState},
file::InternalFlags,
memory::{handle_notify_files, AddrSpace, AddrSpaceWrapper, Grant, PageSpan},
Context, ContextLock, Status,
},
memory::{Page, VirtualAddress, PAGE_SIZE},
ptrace,
scheme::{self, memory::MemoryScheme, FileHandle, KernelScheme},
sync::{CleanLockToken, LockToken, RwLock, L1, L4},
syscall::{
data::{GrantDesc, Map, SetSighandlerData, Stat},
error::*,
flag::*,
usercopy::{UserSliceRo, UserSliceRw, UserSliceWo},
EnvRegisters, FloatRegisters, IntRegisters,
},
};
use super::{CallerCtx, KernelSchemes, OpenResult};
use ::syscall::{GrantFlags, ProcSchemeAttrs, SigProcControl, Sigcontrol};
use alloc::{
boxed::Box,
string::{String, ToString},
sync::{Arc, Weak},
vec::Vec,
};
use core::{
mem::size_of,
num::NonZeroUsize,
slice, str,
sync::atomic::{AtomicUsize, Ordering},
};
use hashbrown::{
hash_map::{DefaultHashBuilder, Entry},
HashMap,
};
use syscall::data::GlobalSchemes;
use syscall::dirent::{DirEntry, DirentBuf, DirentKind};
fn read_from(dst: UserSliceWo, src: &[u8], offset: u64) -> Result<usize> {
let avail_src = usize::try_from(offset)
.ok()
.and_then(|o| src.get(o..))
.unwrap_or(&[]);
dst.copy_common_bytes_from_slice(avail_src)
}
fn try_stop_context<T>(
context_ref: Arc<ContextLock>,
token: &mut CleanLockToken,
callback: impl FnOnce(&mut Context, LockToken<'_, L4>) -> Result<T>,
) -> Result<T> {
if context::is_current(&context_ref) {
let context = &mut context_ref.write(token.token());
let (context, token) = context.token_split();
return callback(context, token);
}
// Stop process
let (prev_status, mut running) = {
let mut context = context_ref.write(token.token());
(
core::mem::replace(
&mut context.status,
context::Status::HardBlocked {
reason: HardBlockedReason::NotYetStarted,
},
),
context.running,
)
};
// Wait until stopped
while running {
context::switch(token);
running = context_ref.read(token.token()).running;
}
let mut context = context_ref.write(token.token());
assert!(
!context.running,
"process can't have been restarted, we stopped it!"
);
let (context, token) = context.token_split();
let ret = callback(context, token);
if matches!(context.status, Status::HardBlocked { reason: HardBlockedReason::NotYetStarted }) {
context.status = prev_status;
}
ret
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum RegsKind {
Float,
Int,
Env,
}
#[derive(Clone)]
enum ContextHandle {
// Opened by the process manager, after which it is locked. This capability is used to open
// Attr handles, to set ens/euid/egid/pid.
Authority,
Attr,
Groups,
Status {
privileged: bool,
}, // can write ContextVerb
/// Deprecated compatibility handles; prefer `ctx` for atomic save/restore.
Regs(RegsKind),
Ctx,
Sighandler,
Start,
NewFiletable {
filetable: Arc<LockedFdTbl>,
binary_format: bool,
data: Box<[u8]>,
},
Filetable {
filetable: Weak<LockedFdTbl>,
binary_format: bool,
data: Box<[u8]>,
},
AddrSpace {
addrspace: Arc<AddrSpaceWrapper>,
},
CurrentAddrSpace,
AwaitingAddrSpaceChange {
new: Arc<AddrSpaceWrapper>,
new_sp: usize,
new_ip: usize,
arg1: Option<usize>,
},
CurrentFiletable,
AwaitingFiletableChange {
new_ft: Arc<LockedFdTbl>,
},
// TODO: Remove this once openat is implemented, or allow openat-via-dup via e.g. the top-level
// directory.
OpenViaDup,
SchedAffinity,
MmapMinAddr(Arc<AddrSpaceWrapper>),
ProcRoot,
ProcCpuinfo,
ProcMeminfo,
ProcUptime,
ProcLoadavg,
ProcVersion,
ProcFilesystems,
ProcDir {
pid: usize,
},
ProcFdDir {
pid: usize,
},
ProcStat {
pid: usize,
},
ProcComm {
pid: usize,
},
ProcCmdline {
pid: usize,
},
ProcStatus {
pid: usize,
},
ProcMaps {
pid: usize,
},
ProcStatm {
pid: usize,
},
ProcLimits {
pid: usize,
},
ProcIo {
pid: usize,
},
}
#[derive(Clone)]
struct Handle {
context: Arc<ContextLock>,
kind: ContextHandle,
}
pub struct ProcScheme;
static NEXT_ID: AtomicUsize = AtomicUsize::new(1);
static HANDLES: RwLock<L1, HashMap<usize, Handle>> =
RwLock::new(HashMap::with_hasher(DefaultHashBuilder::new()));
#[cfg(feature = "debugger")]
#[allow(dead_code)]
pub fn foreach_addrsp(
token: &mut CleanLockToken,
mut f: impl FnMut(&Arc<AddrSpaceWrapper>, LockToken<L1>),
) {
let mut handles_guard = HANDLES.read(token.token());
let (handles, mut token) = handles_guard.token_split();
for (_, handle) in handles.iter() {
let Handle {
kind:
ContextHandle::AddrSpace { addrspace, .. }
| ContextHandle::AwaitingAddrSpaceChange { new: addrspace, .. }
| ContextHandle::MmapMinAddr(addrspace),
..
} = handle
else {
continue;
};
f(&addrspace, token.token());
}
}
fn new_handle(
(handle, fl): (Handle, InternalFlags),
token: &mut CleanLockToken,
) -> Result<(usize, InternalFlags)> {
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
let _ = HANDLES.write(token.token()).insert(id, handle);
Ok((id, fl))
}
fn proc_status_char(status: &Status) -> char {
match status {
Status::Runnable => 'R',
Status::Blocked => 'S',
Status::HardBlocked { reason } => match reason {
HardBlockedReason::AwaitingMmap { .. } => 'D',
HardBlockedReason::Stopped => 'T',
HardBlockedReason::NotYetStarted => 'S',
},
Status::Dead { .. } => 'Z',
}
}
fn proc_vsize(addr_space: &Option<Arc<AddrSpaceWrapper>>, token: &mut CleanLockToken) -> usize {
addr_space
.as_ref()
.map(|addrspace| {
let guard = addrspace.acquire_read(token.downgrade());
guard
.grants
.iter()
.map(|(_, info)| info.page_count() * PAGE_SIZE)
.sum()
})
.unwrap_or(0)
}
fn proc_rss(addr_space: &Option<Arc<AddrSpaceWrapper>>, token: &mut CleanLockToken) -> usize {
addr_space
.as_ref()
.map(|addrspace| {
let guard = addrspace.acquire_read(token.downgrade());
guard
.grants
.iter()
.map(|(_, info)| {
use crate::context::memory::Provider;
match info.provider {
Provider::Allocated { .. }
| Provider::AllocatedShared { .. }
| Provider::PhysBorrowed { .. }
| Provider::External { .. }
| Provider::FmapBorrowed { .. } => info.page_count() * PAGE_SIZE,
}
})
.sum()
})
.unwrap_or(0)
}
fn require_zero_offset(offset: u64) -> Result<()> {
// sys_write passes u64::MAX as the offset for non-positioned file descriptors.
// Proc scheme context handles (regs, sighandler, etc.) always operate at offset 0,
// so accept both 0 (explicit pwrite at 0) and u64::MAX (non-positioned write).
if offset == 0 || offset == u64::MAX {
Ok(())
} else {
Err(Error::new(EINVAL))
}
}
fn validate_kfmap_flags(flags: MapFlags, consume: bool) -> Result<()> {
let flags = MapFlags::from_bits(flags.bits()).ok_or(Error::new(EINVAL))?;
let shared = flags.contains(MapFlags::MAP_SHARED);
let private = flags.contains(MapFlags::MAP_PRIVATE);
if shared == private {
return Err(Error::new(EINVAL));
}
if flags.contains(MapFlags::MAP_FIXED) && flags.contains(MapFlags::MAP_FIXED_NOREPLACE) {
return Err(Error::new(EINVAL));
}
if consume && !shared {
return Err(Error::new(EINVAL));
}
Ok(())
}
fn proc_context(pid: usize, token: &mut CleanLockToken) -> Option<Arc<ContextLock>> {
let mut contexts = context::contexts(token.downgrade());
let (contexts, mut token2) = contexts.token_split();
contexts.iter().find_map(|ctx| {
let ctx = Arc::clone(&ctx.0);
let matches = ctx.read(token2.token()).pid == pid;
matches.then_some(ctx)
})
}
fn proc_stat_line(
pid: usize,
comm: &str,
state: char,
ppid: usize,
priority: usize,
utime: u128,
stime: u128,
pgrp: usize,
session: usize,
nice: i32,
num_threads: usize,
starttime: u128,
vsize: usize,
rss: usize,
) -> String {
format!(
"{pid} ({comm}) {state} {ppid} {pgrp} {session} {tty_nr} {tpgid} {flags} {minflt} {cminflt} {majflt} {cmajflt} {utime} {stime} {cutime} {cstime} {priority} {nice} {num_threads} {itrealvalue} {starttime} {vsize} {rss} {rsslim}\n",
pid = pid,
comm = comm,
state = state,
ppid = ppid,
pgrp = pgrp,
session = session,
tty_nr = 0,
tpgid = 0,
flags = 0,
minflt = 0,
cminflt = 0,
majflt = 0,
cmajflt = 0,
utime = utime,
stime = stime,
cutime = 0,
cstime = 0,
priority = priority,
nice = nice,
num_threads = num_threads,
itrealvalue = 0,
starttime = starttime,
vsize = vsize,
rss = rss / PAGE_SIZE,
rsslim = 0,
)
}
fn proc_status_text(context: &Context, num_threads: usize, vsize: usize, rss: usize) -> String {
let comm = context.name.as_str();
let state = proc_status_char(&context.status);
let ppid = context.owner_proc_id.map_or(0, NonZeroUsize::get);
format!(
"Name:\t{}\nState:\t{}\nPid:\t{}\nPPid:\t{}\nUid:\t{}\t{}\t{}\t{}\nGid:\t{}\t{}\t{}\t{}\nVmSize:\t{} kB\nVmRSS:\t{} kB\nThreads:\t{}\nCpus_allowed_list:\t{}\n",
comm,
state,
context.pid,
ppid,
context.euid,
context.euid,
context.euid,
context.euid,
context.egid,
context.egid,
context.egid,
context.egid,
vsize / 1024,
rss / 1024,
num_threads,
context.sched_affinity
)
}
enum OpenTy {
Ctxt(Arc<ContextLock>),
Auth,
}
impl ProcScheme {
fn proc_open(path: &str) -> Option<ContextHandle> {
let path = path.trim_start_matches('/');
if path.is_empty() || path == "proc" {
return Some(ContextHandle::ProcRoot);
}
if path == "proc/cpuinfo" || path == "cpuinfo" {
return Some(ContextHandle::ProcCpuinfo);
}
if path == "proc/meminfo" || path == "meminfo" {
return Some(ContextHandle::ProcMeminfo);
}
if path == "proc/uptime" || path == "uptime" {
return Some(ContextHandle::ProcUptime);
}
if path == "proc/loadavg" || path == "loadavg" {
return Some(ContextHandle::ProcLoadavg);
}
if path == "proc/version" || path == "version" {
return Some(ContextHandle::ProcVersion);
}
if path == "proc/filesystems" || path == "filesystems" {
return Some(ContextHandle::ProcFilesystems);
}
let mut parts = path.split('/');
let pid = parts.next()?.parse::<usize>().ok()?;
match (parts.next(), parts.next()) {
(None, None) => Some(ContextHandle::ProcDir { pid }),
(Some("fd"), None) => Some(ContextHandle::ProcFdDir { pid }),
(Some("stat"), None) => Some(ContextHandle::ProcStat { pid }),
(Some("comm"), None) => Some(ContextHandle::ProcComm { pid }),
(Some("cmdline"), None) => Some(ContextHandle::ProcCmdline { pid }),
(Some("status"), None) => Some(ContextHandle::ProcStatus { pid }),
(Some("maps"), None) => Some(ContextHandle::ProcMaps { pid }),
(Some("statm"), None) => Some(ContextHandle::ProcStatm { pid }),
(Some("limits"), None) => Some(ContextHandle::ProcLimits { pid }),
(Some("io"), None) => Some(ContextHandle::ProcIo { pid }),
_ => None,
}
}
fn openat_context(
&self,
path: &str,
context: Arc<ContextLock>,
token: &mut CleanLockToken,
) -> Result<Option<(ContextHandle, bool)>> {
Ok(Some(match path {
"addrspace" => (
ContextHandle::AddrSpace {
addrspace: Arc::clone(
context
.read(token.token())
.addr_space()
.map_err(|_| Error::new(ENOENT))?,
),
},
true,
),
"filetable" => (
ContextHandle::Filetable {
filetable: Arc::downgrade(&context.read(token.token()).files),
binary_format: false,
data: Box::new([]),
},
true,
),
"filetable-binary" => (
ContextHandle::Filetable {
filetable: Arc::downgrade(&context.read(token.token()).files),
binary_format: true,
data: Box::new([]),
},
true,
),
"current-addrspace" => (ContextHandle::CurrentAddrSpace, false),
"current-filetable" => (ContextHandle::CurrentFiletable, false),
"ctx" => (ContextHandle::Ctx, false),
// Deprecated compatibility handles; prefer `ctx` for atomic save/restore.
"regs/float" => (ContextHandle::Regs(RegsKind::Float), false),
"regs/int" => (ContextHandle::Regs(RegsKind::Int), false),
"regs/env" => (ContextHandle::Regs(RegsKind::Env), false),
"sighandler" => (ContextHandle::Sighandler, false),
"start" => (ContextHandle::Start, false),
"open_via_dup" => (ContextHandle::OpenViaDup, false),
"mmap-min-addr" => (
ContextHandle::MmapMinAddr(Arc::clone(
context
.read(token.token())
.addr_space()
.map_err(|_| Error::new(ENOENT))?,
)),
false,
),
"sched-affinity" => (ContextHandle::SchedAffinity, true),
"status" => (ContextHandle::Status { privileged: false }, false),
"proc" => (ContextHandle::ProcRoot, true),
_ if path.starts_with("auth-") => {
let nonprefix = &path["auth-".len()..];
let next_dash = nonprefix.find('-').ok_or(Error::new(ENOENT))?;
let auth_fd = nonprefix[..next_dash]
.parse::<usize>()
.map_err(|_| Error::new(ENOENT))?;
let actual_name = &nonprefix[next_dash + 1..];
let handle = match actual_name {
"attrs" => ContextHandle::Attr,
"status" => ContextHandle::Status { privileged: true },
"groups" => ContextHandle::Groups,
_ => return Err(Error::new(ENOENT)),
};
let (hopefully_this_scheme, number) = extract_scheme_number(auth_fd, token)?;
verify_scheme(hopefully_this_scheme)?;
if !matches!(
HANDLES
.read(token.token())
.get(&number)
.ok_or(Error::new(ENOENT))?
.kind,
ContextHandle::Authority
) {
return Err(Error::new(ENOENT));
}
(handle, false)
}
_ => return Ok(None),
}))
}
fn open_inner(
&self,
ty: OpenTy,
operation_str: Option<&str>,
_flags: usize,
token: &mut CleanLockToken,
) -> Result<(usize, InternalFlags)> {
let operation_name = operation_str.ok_or(Error::new(EINVAL))?;
// /proc/self[/...] → resolve /proc/<current-pid>[/...]
if operation_name == "proc/self" || operation_name == "self" {
let pid = context::current().read(token.token()).pid;
let handle = Handle {
context: context::current(),
kind: ContextHandle::ProcDir { pid },
};
return new_handle((handle, InternalFlags::POSITIONED), token);
}
if operation_name.starts_with("proc/self/") || operation_name.starts_with("self/") {
let pid = context::current().read(token.token()).pid;
let rest = if operation_name.starts_with("proc/self/") {
&operation_name["proc/self/".len()..]
} else {
&operation_name["self/".len()..]
};
let resolved = format!("proc/{}/{}", pid, rest);
let mut parts = resolved.split('/');
let _ = parts.next(); // "proc"
let pid = parts.next().and_then(|p| p.parse::<usize>().ok()).unwrap_or(pid);
if let Some(kind) = Self::proc_open(&resolved) {
let handle = Handle {
context: context::current(),
kind,
};
return new_handle((handle, InternalFlags::POSITIONED), token);
}
}
if let Some(kind) = Self::proc_open(operation_name) {
let handle = Handle {
context: context::current(),
kind,
};
return new_handle((handle, InternalFlags::POSITIONED), token);
}
let (mut handle, positioned) = match ty {
OpenTy::Ctxt(context) => {
match self.openat_context(operation_name, Arc::clone(&context), token)? {
Some((kind, positioned)) => (Handle { context, kind }, positioned),
_ => {
return Err(Error::new(EINVAL));
}
}
}
OpenTy::Auth => {
extern "C" fn ret() {}
let context = match operation_str.ok_or(Error::new(ENOENT))? {
"new-context" => {
let id = NonZeroUsize::new(NEXT_ID.fetch_add(1, Ordering::Relaxed))
.ok_or(Error::new(EMFILE))?;
let context = context::spawn(true, Some(id), ret, token)?;
{
let parent_groups =
context::current().read(token.token()).groups.clone();
context.write(token.token()).groups = parent_groups;
}
HANDLES.write(token.token()).insert(
id.get(),
Handle {
context,
kind: ContextHandle::OpenViaDup,
},
);
return Ok((id.get(), InternalFlags::empty()));
}
"cur-context" => context::current(),
_ => return Err(Error::new(ENOENT)),
};
(
Handle {
context,
kind: ContextHandle::OpenViaDup,
},
false,
)
}
};
{
let filetable_opt = match handle {
Handle {
kind:
ContextHandle::Filetable {
ref filetable,
binary_format,
ref mut data,
},
..
} => Some((
filetable.upgrade().ok_or(Error::new(EOWNERDEAD))?,
binary_format,
data,
)),
Handle {
kind:
ContextHandle::NewFiletable {
ref filetable,
binary_format,
ref mut data,
},
..
} => Some((Arc::clone(filetable), binary_format, data)),
_ => None,
};
if let Some((filetable, binary_format, data)) = filetable_opt {
*data = if binary_format {
let mut data = Vec::new();
for index in filetable
.read(token.token())
.enumerate()
.filter_map(|(idx, val)| val.as_ref().map(|_| idx))
{
data.extend((index as u64).to_le_bytes());
}
data.into_boxed_slice()
} else {
use core::fmt::Write;
let mut data = String::new();
for index in filetable
.read(token.token())
.enumerate()
.filter_map(|(idx, val)| val.as_ref().map(|_| idx))
{
writeln!(data, "{}", index).unwrap();
}
data.into_bytes().into_boxed_slice()
};
}
};
let (id, int_fl) = new_handle(
(
handle.clone(),
if positioned {
InternalFlags::POSITIONED
} else {
InternalFlags::empty()
},
),
token,
)?;
Ok((id, int_fl))
}
}
impl KernelScheme for ProcScheme {
fn scheme_root(&self, token: &mut CleanLockToken) -> Result<usize> {
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
HANDLES.write(token.token()).insert(
id,
Handle {
// TODO: placeholder
context: context::current(),
kind: ContextHandle::Authority,
},
);
Ok(id)
}
fn fevent(
&self,
id: usize,
_flags: EventFlags,
token: &mut CleanLockToken,
) -> Result<EventFlags> {
let handles = HANDLES.read(token.token());
let _handle = handles.get(&id).ok_or(Error::new(EBADF))?;
Ok(EventFlags::empty())
}
fn close(&self, id: usize, token: &mut CleanLockToken) -> Result<()> {
let handle = HANDLES
.write(token.token())
.remove(&id)
.ok_or(Error::new(EBADF))?;
match handle {
Handle {
context,
kind:
ContextHandle::AwaitingAddrSpaceChange {
new,
new_sp,
new_ip,
arg1,
},
} => {
let old_ctx = try_stop_context(context, token, |context, _| {
let regs = context.regs_mut().ok_or(Error::new(EBADFD))?;
regs.set_instr_pointer(new_ip);
regs.set_stack_pointer(new_sp);
#[cfg(any(
target_arch = "x86_64",
target_arch = "aarch64",
target_arch = "riscv64"
))]
regs.set_arg1(arg1);
// TODO: Lock ordering violation
let mut token = unsafe { CleanLockToken::new() };
Ok(context.set_addr_space(Some(new), token.downgrade()))
})?;
if let Some(old_ctx) = old_ctx
&& let Some(addrspace) = Arc::into_inner(old_ctx)
{
addrspace.into_drop(token);
}
let _ = ptrace::send_event(
crate::syscall::ptrace_event!(PTRACE_EVENT_ADDRSPACE_SWITCH, 0),
token,
);
}
Handle {
kind: ContextHandle::AddrSpace { addrspace } | ContextHandle::MmapMinAddr(addrspace),
..
} => {
if let Some(addrspace) = Arc::into_inner(addrspace) {
addrspace.into_drop(token);
}
}
Handle {
kind: ContextHandle::AwaitingFiletableChange { new_ft },
context,
} => {
let ft = new_ft.read(token.token());
let posix_count = ft.posix_fdtbl.iter().filter(|f| f.is_some()).count();
let upper_count = ft.upper_fdtbl.iter().filter(|f| f.is_some()).count();
let ctx_name = context.read(token.token()).name;
info!(
"AwaitingFiletableChange applied: ctx='{}' posix_fds={} upper_fds={}",
ctx_name,
posix_count,
upper_count
);
let mut upper_list = String::new();
for (i, f) in ft.upper_fdtbl.iter().enumerate() {
if f.is_some() {
upper_list.push_str(&format!(" {:x}", i));
}
}
info!("upper FDs present:{}", upper_list);
context.write(token.token()).files = new_ft;
}
_ => (),
}
Ok(())
}
fn kfmap(
&self,
id: usize,
dst_addr_space: &Arc<AddrSpaceWrapper>,
map: &crate::syscall::data::Map,
consume: bool,
token: &mut CleanLockToken,
) -> Result<usize> {
let handle = HANDLES
.read(token.token())
.get(&id)
.ok_or(Error::new(EBADF))?
.clone();
let Handle { kind, ref context } = handle;
match kind {
ContextHandle::AddrSpace { ref addrspace } => {
if Arc::ptr_eq(addrspace, dst_addr_space) {
return Err(Error::new(EBUSY));
}
let PageSpan {
base: requested_dst_page,
..
} = crate::syscall::validate_region(map.address, map.size)?;
let src_span =
PageSpan::validate_nonempty(VirtualAddress::new(map.offset), map.size)
.ok_or(Error::new(EINVAL))?;
let fixed = map.flags.contains(MapFlags::MAP_FIXED)
|| map.flags.contains(MapFlags::MAP_FIXED_NOREPLACE);
let requested_dst_base = (map.address != 0 || fixed).then_some(requested_dst_page);
let mut src_addr_space_guard = addrspace.acquire_write(token.downgrade());
let (src_addr_space, lock_token) = src_addr_space_guard.token_split();
let src_page_count = NonZeroUsize::new(src_span.count).ok_or(Error::new(EINVAL))?;
let mut notify_files = Vec::new();
validate_kfmap_flags(map.flags, consume)?;
let result_base = if consume {
dst_addr_space.r#move(
Some((addrspace, &mut *src_addr_space)),
src_span,
requested_dst_base,
src_page_count.get(),
map.flags,
Some(&mut notify_files),
lock_token,
)?
} else {
// SAFETY: We've compared Arc::ptr_eq(addrspace, dst_addr_space) before
let mut dst_addrsp_guard =
unsafe { dst_addr_space.acquire_rewrite(lock_token) };
dst_addrsp_guard.mmap(
dst_addr_space,
requested_dst_base,
src_page_count,
map.flags,
Some(&mut notify_files),
|dst_page, _, dst_mapper, flusher| {
Grant::borrow(
Arc::clone(addrspace),
src_addr_space,
src_span.base,
dst_page,
src_span.count,
map.flags,
dst_mapper,
flusher,
true,
true,
false,
)
},
)?
};
drop(src_addr_space_guard);
handle_notify_files(notify_files, token);
Ok(result_base.start_address().data())
}
ContextHandle::Sighandler => {
let context = context.read(token.token());
// let (context, token) = context.token_split();
let sig = context.sig.as_ref().ok_or(Error::new(EBADF))?;
let frame = match map.offset {
// tctl
0 => &sig.thread_control,
// pctl
PAGE_SIZE => &sig.proc_control,
_ => return Err(Error::new(EINVAL)),
};
// TODO: Allocated or AllocatedShared?
let addrsp = AddrSpace::current()?;
// TODO: Lock ordering violation
let mut token = unsafe { CleanLockToken::new() };
let page = addrsp.acquire_write(token.downgrade()).mmap_anywhere(
&addrsp,
NonZeroUsize::new(1).unwrap(),
MapFlags::PROT_READ | MapFlags::PROT_WRITE,
|page, flags, mapper, flusher| {
Grant::allocated_shared_one_page(
frame.get(),
page,
flags,
mapper,
flusher,
false,
)
},
)?;
Ok(page.start_address().data())
}
_ => Err(Error::new(EBADF)),
}
}
fn kreadoff(
&self,
id: usize,
buf: UserSliceWo,
offset: u64,
_read_flags: u32,
_stored_flags: u32,
token: &mut CleanLockToken,
) -> Result<usize> {
// Don't hold a global lock during the context switch later on
let handle = {
let handles = HANDLES.read(token.token());
handles.get(&id).ok_or(Error::new(EBADF))?.clone()
};
let Handle { context, kind } = handle;
kind.kreadoff(id, context, buf, offset, token)
}
fn kcall(
&self,
fds: &[usize],
_payload: UserSliceRw,
_flags: CallFlags,
metadata: &[u64],
token: &mut CleanLockToken,
) -> Result<usize> {
let id = fds.first().copied().ok_or(Error::new(EINVAL))?;
// TODO: simplify
let handle = {
let mut handles = HANDLES.write(token.token());
let handle = handles.get_mut(&id).ok_or(Error::new(EBADF))?;
handle.clone()
};
let ContextHandle::OpenViaDup = handle.kind else {
return Err(Error::new(EBADF));
};
let verb: u8 = (*metadata.first().ok_or(Error::new(EINVAL))?)
.try_into()
.map_err(|_| Error::new(EINVAL))?;
let verb = ProcSchemeVerb::try_from_raw(verb).ok_or(Error::new(EINVAL))?;
match verb {
ProcSchemeVerb::Iopl => context::current()
.write(token.token())
.set_userspace_io_allowed(true),
_ => return Err(Error::new(EINVAL)),
}
Ok(0)
}
fn kwriteoff(
&self,
id: usize,
buf: UserSliceRo,
offset: u64,
_fcntl_flags: u32,
_stored_flags: u32,
token: &mut CleanLockToken,
) -> Result<usize> {
// TODO: offset
// Don't hold a global lock during the context switch later on
let handle = {
let mut handles = HANDLES.write(token.token());
let handle = handles.get_mut(&id).ok_or(Error::new(EBADF))?;
handle.clone()
};
let Handle { context, kind } = handle;
kind.kwriteoff(id, context, buf, offset, token)
}
fn kfpath(&self, _id: usize, buf: UserSliceWo, _token: &mut CleanLockToken) -> Result<usize> {
//TODO: construct useful path?
buf.copy_common_bytes_from_slice("/scheme/kernel.proc/".as_bytes())
}
fn getdents(
&self,
id: usize,
buf: UserSliceWo,
header_size: u16,
opaque_id_start: u64,
token: &mut CleanLockToken,
) -> Result<usize> {
let Ok(opaque) = usize::try_from(opaque_id_start) else {
return Ok(0);
};
let handle = {
let handles = HANDLES.read(token.token());
handles.get(&id).ok_or(Error::new(EBADF))?.clone()
};
let mut buf = DirentBuf::new(buf, header_size).ok_or(Error::new(EIO))?;
match handle.kind {
ContextHandle::ProcRoot => {
// First: system files (cpuinfo, meminfo, uptime, loadavg, version, filesystems)
for (idx, name) in ["cpuinfo", "meminfo", "uptime", "loadavg", "version", "filesystems"].iter().enumerate() {
buf.entry(DirEntry {
inode: 0,
next_opaque_id: (idx + 1) as u64,
kind: DirentKind::Regular,
name,
})?;
}
// Then: process directories (one per PID)
let mut pids = Vec::new();
let mut contexts = context::contexts(token.downgrade());
let (contexts, mut token) = contexts.token_split();
for context_ref in contexts.iter() {
pids.push(context_ref.read(token.token()).pid);
}
pids.sort_unstable();
let base_idx = 6; // after 6 system files
for (idx, pid) in pids.into_iter().enumerate().skip(opaque.saturating_sub(base_idx)) {
let name = format!("{}", pid);
buf.entry(DirEntry {
inode: pid as u64,
next_opaque_id: (base_idx + idx + 1) as u64,
kind: DirentKind::Directory,
name: &name,
})?;
}
Ok(buf.finalize())
}
ContextHandle::ProcDir { pid } => {
for (idx, name) in ["stat", "comm", "cmdline", "status", "maps", "statm", "limits", "io", "fd"]
.iter()
.enumerate()
.skip(opaque)
{
buf.entry(DirEntry {
inode: pid as u64,
next_opaque_id: (idx + 1) as u64,
kind: if *name == "fd" { DirentKind::Directory } else { DirentKind::Regular },
name,
})?;
}
Ok(buf.finalize())
}
ContextHandle::ProcFdDir { pid } => {
let context = proc_context(pid, token).ok_or(Error::new(ESRCH))?;
let files = context.read(token.token()).files.clone();
let guard = files.read(token.token());
let fds: Vec<usize> = guard.posix_fdtbl.iter()
.enumerate()
.filter(|(_, fd)| fd.is_some())
.map(|(i, _)| i)
.collect();
for (idx, &fd) in fds.iter().enumerate().skip(opaque) {
let name = format!("{}", fd);
buf.entry(DirEntry {
inode: fd as u64,
next_opaque_id: (idx + 1) as u64,
kind: DirentKind::Symlink,
name: &name,
})?;
}
Ok(buf.finalize())
}
_ => Err(Error::new(ENOTDIR)),
}
}
fn kfstat(&self, id: usize, buffer: UserSliceWo, token: &mut CleanLockToken) -> Result<()> {
let handles = HANDLES.read(token.token());
let handle = handles.get(&id).ok_or(Error::new(EBADF))?;
buffer.copy_exactly(&Stat {
st_mode: match handle.kind {
ContextHandle::ProcRoot | ContextHandle::ProcDir { .. } | ContextHandle::ProcFdDir { .. } => MODE_DIR | 0o755,
_ => MODE_FILE | 0o666,
},
st_size: handle.fsize()?,
..Stat::default()
})?;
Ok(())
}
fn fsize(&self, id: usize, token: &mut CleanLockToken) -> Result<u64> {
let mut handles = HANDLES.write(token.token());
let handle = handles.get_mut(&id).ok_or(Error::new(EBADF))?;
match handle.kind {
ContextHandle::ProcRoot | ContextHandle::ProcDir { .. } => Ok(0),
_ => handle.fsize(),
}
}
/// Dup is currently used to implement clone() and execve().
fn kdup(
&self,
old_id: usize,
raw_buf: UserSliceRo,
_: CallerCtx,
token: &mut CleanLockToken,
) -> Result<OpenResult> {
let info = {
let handles = HANDLES.read(token.token());
let handle = handles.get(&old_id).ok_or(Error::new(EBADF))?;
handle.clone()
};
let handle = |h, positioned| {
(
h,
if positioned {
InternalFlags::POSITIONED
} else {
InternalFlags::empty()
},
)
};
let mut array = [0_u8; 64];
if raw_buf.len() > array.len() {
return Err(Error::new(EINVAL));
}
raw_buf.copy_to_slice(&mut array[..raw_buf.len()])?;
let buf = &array[..raw_buf.len()];
new_handle(
match info {
Handle {
kind: ContextHandle::Authority,
..
} => {
return self
.open_inner(
OpenTy::Auth,
Some(core::str::from_utf8(buf).map_err(|_| Error::new(EINVAL))?)
.filter(|s| !s.is_empty()),
O_RDWR | O_CLOEXEC,
token,
)
.map(|(r, fl)| OpenResult::SchemeLocal(r, fl))
}
Handle {
kind: ContextHandle::OpenViaDup,
context,
} => {
return self
.open_inner(
OpenTy::Ctxt(context),
Some(core::str::from_utf8(buf).map_err(|_| Error::new(EINVAL))?)
.filter(|s| !s.is_empty()),
O_RDWR | O_CLOEXEC,
token,
)
.map(|(r, fl)| OpenResult::SchemeLocal(r, fl));
}
Handle {
kind:
ContextHandle::Filetable {
ref filetable,
binary_format,
ref data,
},
context,
} => {
// TODO: Maybe allow userspace to either copy or transfer recently dupped file
// descriptors between file tables.
if buf != b"copy" {
return Err(Error::new(EINVAL));
}
let filetable = filetable.upgrade().ok_or(Error::new(EOWNERDEAD))?;
let new_filetable =
Arc::new(RwLock::new(filetable.read(token.token()).clone()));
handle(
Handle {
kind: ContextHandle::NewFiletable {
filetable: new_filetable,
binary_format,
data: data.clone(),
},
context,
},
true,
)
}
Handle {
kind: ContextHandle::AddrSpace { ref addrspace },
context,
} => {
const GRANT_FD_PREFIX: &[u8] = b"grant-fd-";
let kind = match buf {
// TODO: Better way to obtain new empty address spaces, perhaps using SYS_OPEN. But
// in that case, what scheme?
b"empty" => ContextHandle::AddrSpace {
addrspace: AddrSpaceWrapper::new()?,
},
b"exclusive" => ContextHandle::AddrSpace {
addrspace: addrspace.try_clone(token)?,
},
b"mmap-min-addr" => ContextHandle::MmapMinAddr(Arc::clone(addrspace)),
_ if buf.starts_with(GRANT_FD_PREFIX) => {
let string = core::str::from_utf8(&buf[GRANT_FD_PREFIX.len()..])
.map_err(|_| Error::new(EINVAL))?;
let page_addr = usize::from_str_radix(string, 16)
.map_err(|_| Error::new(EINVAL))?;
if page_addr % PAGE_SIZE != 0 {
return Err(Error::new(EINVAL));
}
let page = Page::containing_address(VirtualAddress::new(page_addr));
let mut token = token.token();
let read_lock = addrspace.acquire_read(token.downgrade());
let (_, info) =
read_lock.grants.contains(page).ok_or(Error::new(EINVAL))?;
return Ok(OpenResult::External(
info.file_ref()
.map(|r| Arc::clone(&r.description))
.ok_or(Error::new(EBADF))?,
));
}
_ => return Err(Error::new(EINVAL)),
};
handle(Handle { context, kind }, true)
}
_ => return Err(Error::new(EINVAL)),
},
token,
)
.map(|(r, fl)| OpenResult::SchemeLocal(r, fl))
}
}
fn extract_scheme_number(fd: usize, token: &mut CleanLockToken) -> Result<(KernelSchemes, usize)> {
let (scheme_id, number) = {
let current_lock = context::current();
let mut current = current_lock.read(token.token());
let (context, mut token) = current.token_split();
let file_descriptor = context
.get_file(FileHandle::from(fd), &mut token)
.ok_or(Error::new(EBADF))?;
let desc = file_descriptor.description.read(token.token());
(desc.scheme, desc.number)
};
let scheme = scheme::get_scheme(token.token(), scheme_id)?;
Ok((scheme, number))
}
fn verify_scheme(scheme: KernelSchemes) -> Result<()> {
if !matches!(scheme, KernelSchemes::Global(GlobalSchemes::Proc)) {
return Err(Error::new(EBADF));
}
Ok(())
}
impl Handle {
fn fsize(&self) -> Result<u64> {
match self.kind {
ContextHandle::Filetable { ref data, .. }
| ContextHandle::NewFiletable { ref data, .. } => Ok(data.len() as u64),
_ => Ok(0),
}
}
}
impl ContextHandle {
fn kwriteoff(
self,
id: usize,
context: Arc<ContextLock>,
buf: UserSliceRo,
offset: u64,
token: &mut CleanLockToken,
) -> Result<usize> {
match self {
Self::AddrSpace { addrspace } => {
let mut chunks = buf.usizes();
let mut words_read = 0;
let word_size = size_of::<usize>() as u64;
if offset % word_size != 0 {
return Err(Error::new(EINVAL));
}
let offset_words = usize::try_from(offset / word_size).map_err(|_| Error::new(EINVAL))?;
for _ in 0..offset_words {
chunks.next().ok_or(Error::new(EINVAL))??;
}
let mut next = || {
words_read += 1;
chunks.next().ok_or(Error::new(EINVAL))
};
match next()?? {
op @ ADDRSPACE_OP_MMAP | op @ ADDRSPACE_OP_TRANSFER => {
let fd = next()??;
let offset = next()??;
let page_span = crate::syscall::validate_region(next()??, next()??)?;
let flags = MapFlags::from_bits(next()??).ok_or(Error::new(EINVAL))?;
if fd == !0 {
if op == ADDRSPACE_OP_TRANSFER {
return Err(Error::new(EOPNOTSUPP));
}
return MemoryScheme::fmap_anonymous(
&addrspace,
&Map {
offset,
size: page_span.count * PAGE_SIZE,
address: page_span.base.start_address().data(),
flags,
},
false,
token,
);
} else {
let (scheme, number) = extract_scheme_number(fd, token)?;
// ADDRSPACE_OP_MMAP and ADDRSPACE_OP_TRANSFER return the target address
// rather than the amount of written bytes.
// FIXME maybe make all these operations calls rather than writes?
return scheme.kfmap(
number,
&addrspace,
&Map {
offset,
size: page_span.count * PAGE_SIZE,
address: page_span.base.start_address().data(),
flags,
},
op == ADDRSPACE_OP_TRANSFER,
token,
);
}
}
ADDRSPACE_OP_MUNMAP => {
let page_span = crate::syscall::validate_region(next()??, next()??)?;
let unpin = false;
let res = addrspace.munmap(page_span, unpin, token)?;
for r in res {
let _ = r.unmap(token);
}
}
ADDRSPACE_OP_MPROTECT => {
let page_span = crate::syscall::validate_region(next()??, next()??)?;
let flags = MapFlags::from_bits(next()??).ok_or(Error::new(EINVAL))?;
addrspace.mprotect(page_span, flags, token)?;
}
_ => return Err(Error::new(EINVAL)),
}
Ok(words_read * size_of::<usize>())
}
ContextHandle::Ctx => {
require_zero_offset(offset)?;
let regs = unsafe { buf.read_exact::<syscall::FullContextRegs>()? };
try_stop_context(context, token, |context, _| {
context.set_fx_regs(regs.float);
match context.regs_mut() {
None => Err(Error::new(ENOTRECOVERABLE)),
Some(stack) => {
stack.load(&regs.int);
context.write_env_regs(regs.env)
}
}
})?;
Ok(size_of::<syscall::FullContextRegs>())
}
ContextHandle::Regs(kind) => match kind {
RegsKind::Float => {
require_zero_offset(offset)?;
let regs = unsafe { buf.read_exact::<FloatRegisters>()? };
try_stop_context(context, token, |context, _| {
// NOTE: The kernel will never touch floats
// Ignore the rare case of floating point
// registers being uninitiated
context.set_fx_regs(regs);
Ok(size_of::<FloatRegisters>())
})
}
RegsKind::Int => {
require_zero_offset(offset)?;
let regs = unsafe { buf.read_exact::<IntRegisters>()? };
try_stop_context(context, token, |context, _| match context.regs_mut() {
None => {
println!(
"{}:{}: Couldn't read registers from stopped process",
file!(),
line!()
);
Err(Error::new(ENOTRECOVERABLE))
}
Some(stack) => {
stack.load(&regs);
Ok(size_of::<IntRegisters>())
}
})
}
RegsKind::Env => {
require_zero_offset(offset)?;
let regs = unsafe { buf.read_exact::<EnvRegisters>()? };
write_env_regs(context, regs, token)?;
Ok(size_of::<EnvRegisters>())
}
},
ContextHandle::Sighandler => {
require_zero_offset(offset)?;
let data = unsafe { buf.read_exact::<SetSighandlerData>()? };
if data.user_handler >= crate::USER_END_OFFSET
|| data.excp_handler >= crate::USER_END_OFFSET
{
return Err(Error::new(EPERM));
}
if data.thread_control_addr >= crate::USER_END_OFFSET
|| data.proc_control_addr >= crate::USER_END_OFFSET
{
return Err(Error::new(EFAULT));
}
let state = if data.thread_control_addr != 0 && data.proc_control_addr != 0 {
let validate_off = |addr, sz| {
let off: usize = addr % PAGE_SIZE;
if off.is_multiple_of(align_of::<usize>()) && off + sz <= PAGE_SIZE {
Ok(off as u16)
} else {
Err(Error::new(EINVAL))
}
};
let addrsp = Arc::clone(context.read(token.token()).addr_space()?);
Some(SignalState {
threadctl_off: validate_off(
data.thread_control_addr,
size_of::<Sigcontrol>(),
)?,
procctl_off: validate_off(
data.proc_control_addr,
size_of::<SigProcControl>(),
)?,
user_handler: NonZeroUsize::new(data.user_handler)
.ok_or(Error::new(EINVAL))?,
excp_handler: NonZeroUsize::new(data.excp_handler),
thread_control: addrsp.borrow_frame_enforce_rw_allocated(
Page::containing_address(VirtualAddress::new(data.thread_control_addr)),
token,
)?,
proc_control: addrsp.borrow_frame_enforce_rw_allocated(
Page::containing_address(VirtualAddress::new(data.proc_control_addr)),
token,
)?,
})
} else {
None
};
context.write(token.token()).sig = state;
Ok(size_of::<SetSighandlerData>())
}
ContextHandle::Start => {
require_zero_offset(offset)?;
match context.write(token.token()).status {
ref mut status @ Status::HardBlocked {
reason: HardBlockedReason::NotYetStarted,
} => {
*status = Status::Runnable;
Ok(buf.len())
}
_ => Err(Error::new(EINVAL)),
}
},
ContextHandle::Filetable { .. } | ContextHandle::NewFiletable { .. } => {
Err(Error::new(EBADF))
}
ContextHandle::CurrentFiletable => {
require_zero_offset(offset)?;
let filetable_fd = buf.read_usize()?;
let (hopefully_this_scheme, number) = extract_scheme_number(filetable_fd, token)?;
verify_scheme(hopefully_this_scheme)?;
let mut handles = HANDLES.write(token.token());
let Entry::Occupied(mut entry) = handles.entry(number) else {
return Err(Error::new(EBADF));
};
let filetable = match *entry.get_mut() {
Handle {
kind: ContextHandle::Filetable { ref filetable, .. },
..
} => filetable.upgrade().ok_or(Error::new(EOWNERDEAD))?,
Handle {
kind:
ContextHandle::NewFiletable {
ref filetable,
binary_format,
ref data,
},
..
} => {
let ft = Arc::clone(filetable);
*entry.get_mut() = Handle {
kind: ContextHandle::Filetable {
filetable: Arc::downgrade(filetable),
binary_format,
data: data.clone(),
},
context: Arc::clone(&context),
};
ft
}
_ => return Err(Error::new(EBADF)),
};
*handles.get_mut(&id).ok_or(Error::new(EBADF))? = Handle {
kind: ContextHandle::AwaitingFiletableChange { new_ft: filetable },
context,
};
Ok(size_of::<usize>())
}
ContextHandle::CurrentAddrSpace => {
require_zero_offset(offset)?;
let mut iter = buf.usizes();
let addrspace_fd = iter.next().ok_or(Error::new(EINVAL))??;
let sp = iter.next().ok_or(Error::new(EINVAL))??;
let ip = iter.next().ok_or(Error::new(EINVAL))??;
let arg1 = iter.next().transpose()?;
let (hopefully_this_scheme, number) = extract_scheme_number(addrspace_fd, token)?;
verify_scheme(hopefully_this_scheme)?;
let mut handles = HANDLES.write(token.token());
let &Handle {
kind: ContextHandle::AddrSpace { ref addrspace },
..
} = handles.get(&number).ok_or(Error::new(EBADF))?
else {
return Err(Error::new(EBADF));
};
*handles.get_mut(&id).ok_or(Error::new(EBADF))? = Handle {
context,
kind: Self::AwaitingAddrSpaceChange {
new: Arc::clone(addrspace),
new_sp: sp,
new_ip: ip,
arg1,
},
};
let written = if arg1.is_some() {
4 * size_of::<usize>()
} else {
3 * size_of::<usize>()
};
Ok(written)
}
Self::MmapMinAddr(ref addrspace) => {
require_zero_offset(offset)?;
let val = buf.read_usize()?;
if val % PAGE_SIZE != 0 || val > crate::USER_END_OFFSET {
return Err(Error::new(EINVAL));
}
let mut lock_token = token.token();
addrspace.acquire_write(lock_token.downgrade()).mmap_min = val;
Ok(size_of::<usize>())
}
Self::SchedAffinity => {
require_zero_offset(offset)?;
let mask = unsafe { buf.read_exact::<crate::cpu_set::RawMask>()? };
context
.write(token.token())
.sched_affinity
.override_from(&mask);
Ok(size_of_val(&mask))
}
ContextHandle::Status { privileged } => {
let mut args = buf.usizes();
let user_data = args.next().ok_or(Error::new(EINVAL))??;
let context_verb =
ContextVerb::try_from_raw(user_data).ok_or(Error::new(EINVAL))?;
match context_verb {
// TODO: lwp_park/lwp_unpark for bypassing procmgr?
ContextVerb::Unstop | ContextVerb::Stop if !privileged => {
Err(Error::new(EPERM))
}
ContextVerb::Stop => {
let mut guard = context.write(token.token());
match guard.status {
Status::Dead { .. } => return Err(Error::new(EOWNERDEAD)),
Status::HardBlocked {
reason: HardBlockedReason::AwaitingMmap { .. },
} => {
// Process is blocked waiting for an mmap grant.
// Allow the stop to proceed — the mmap grant cleanup
// or completion will be handled when the process resumes.
// Cross-referenced with Linux kernel/signal.c:
// signal handling doesn't special-case mmap-in-progress;
// fault handler retries on resume.
}
_ => (),
}
guard.status = Status::HardBlocked {
reason: HardBlockedReason::Stopped,
};
drop(guard);
let mut spins = 10_000usize;
while context.read(token.token()).running {
if spins == 0 {
return Err(Error::new(EAGAIN));
}
spins -= 1;
context::switch(token);
}
Ok(size_of::<usize>())
}
ContextVerb::Unstop => {
let mut guard = context.write(token.token());
if let Status::HardBlocked {
reason: HardBlockedReason::Stopped,
} = guard.status
{
guard.status = Status::Runnable;
}
Ok(size_of::<usize>())
}
ContextVerb::Interrupt => {
let mut guard = context.write(token.token());
guard.unblock();
Ok(size_of::<usize>())
}
ContextVerb::ForceKill => {
if context::is_current(&context) {
//trace!("FORCEKILL SELF {} {}", context.read().debug_id, context.read().pid);
// The following functionality simplifies the cleanup step when detached threads
// terminate.
if let Some(post_unmap) = args.next() {
let base = post_unmap?;
let size = args.next().ok_or(Error::new(EINVAL))??;
if size > 0 {
let addrsp =
Arc::clone(context.read(token.token()).addr_space()?);
let res = addrsp.munmap(
PageSpan::validate_nonempty(
VirtualAddress::new(base),
size,
)
.ok_or(Error::new(EINVAL))?,
false,
token,
)?;
for r in res {
let _ = r.unmap(token);
}
}
}
crate::syscall::exit_this_context(None, token);
} else {
let mut ctxt = context.write(token.token());
//trace!("FORCEKILL NONSELF={} {}, SELF={}", ctxt.debug_id, ctxt.pid, context::current().read().debug_id);
if let context::Status::Dead { .. } = ctxt.status {
return Ok(size_of::<usize>());
}
ctxt.status = context::Status::Runnable;
ctxt.being_sigkilled = true;
Ok(size_of::<usize>())
}
}
}
}
ContextHandle::Attr => {
require_zero_offset(offset)?;
let info = unsafe { buf.read_exact::<ProcSchemeAttrs>()? };
let mut guard = context.write(token.token());
let len = info
.debug_name
.iter()
.position(|c| *c == 0)
.unwrap_or(info.debug_name.len())
.min(guard.name.capacity());
let debug_name = core::str::from_utf8(&info.debug_name[..len])
.map_err(|_| Error::new(EINVAL))?;
guard.name.clear();
guard.name.push_str(debug_name);
guard.pid = info.pid as usize;
guard.euid = info.euid;
guard.egid = info.egid;
guard.prio = (info.prio as usize).min(39);
if guard.pgrp == 0 {
guard.pgrp = guard.pid;
}
if guard.session == 0 {
guard.session = guard.pid;
}
Ok(size_of::<ProcSchemeAttrs>())
}
Self::Groups => {
require_zero_offset(offset)?;
const NGROUPS_MAX: usize = 65536;
if buf.len() % size_of::<u32>() != 0 {
return Err(Error::new(EINVAL));
}
let count = buf.len() / size_of::<u32>();
if count > NGROUPS_MAX {
return Err(Error::new(EINVAL));
}
let mut groups = Vec::with_capacity(count);
for chunk in buf.in_exact_chunks(size_of::<u32>()).take(count) {
groups.push(chunk.read_u32()?);
}
let proc_id = {
let guard = context.read(token.token());
guard.owner_proc_id
};
{
let mut guard = context.write(token.token());
guard.groups = groups.clone();
}
if let Some(pid) = proc_id {
let mut contexts = context::contexts(token.downgrade());
let (contexts, mut t) = contexts.token_split();
for context_ref in contexts.iter() {
let mut ctx = context_ref.write(t.token());
if ctx.owner_proc_id == Some(pid) {
ctx.groups = groups.clone();
}
}
}
Ok(count * size_of::<u32>())
}
ContextHandle::OpenViaDup => {
require_zero_offset(offset)?;
let mut args = buf.usizes();
let user_data = args.next().ok_or(Error::new(EINVAL))??;
let context_verb =
ContextVerb::try_from_raw(user_data).ok_or(Error::new(EINVAL))?;
match context_verb {
ContextVerb::ForceKill => {
if context::is_current(&context) {
//trace!("FORCEKILL SELF {} {}", context.read().debug_id, context.read().pid);
// The following functionality simplifies the cleanup step when detached threads
// terminate.
if let Some(post_unmap) = args.next() {
let base = post_unmap?;
let size = args.next().ok_or(Error::new(EINVAL))??;
if size > 0 {
let addrsp =
Arc::clone(context.read(token.token()).addr_space()?);
let res = addrsp.munmap(
PageSpan::validate_nonempty(
VirtualAddress::new(base),
size,
)
.ok_or(Error::new(EINVAL))?,
false,
token,
)?;
for r in res {
let _ = r.unmap(token);
}
}
}
crate::syscall::exit_this_context(None, token);
} else {
Err(Error::new(EPERM))
}
}
_ => Err(Error::new(EINVAL)),
}
}
_ => Err(Error::new(EBADF)),
}
}
fn kreadoff(
&self,
_id: usize,
context: Arc<ContextLock>,
buf: UserSliceWo,
offset: u64,
token: &mut CleanLockToken,
) -> Result<usize> {
match self {
ContextHandle::Ctx => {
require_zero_offset(offset)?;
let mut regs = syscall::FullContextRegs::default();
try_stop_context(context, token, |context, _| {
match context.regs() {
None => {
assert!(!context.running, "try_stop_context is broken, clearly");
println!(
"{}:{}: Couldn't read registers from stopped process",
file!(),
line!()
);
Err(Error::new(ENOTRECOVERABLE))
}
Some(stack) => {
stack.save(&mut regs.int);
regs.float = context.get_fx_regs();
regs.env = context.read_env_regs()?;
Ok(())
}
}
})?;
buf.copy_common_bytes_from_slice(unsafe {
slice::from_raw_parts(
&regs as *const syscall::FullContextRegs as *const u8,
size_of::<syscall::FullContextRegs>(),
)
})
}
ContextHandle::Regs(kind) => {
union Output {
float: FloatRegisters,
int: IntRegisters,
env: EnvRegisters,
}
let (output, size) = match kind {
RegsKind::Float => {
let context = context.read(token.token());
// NOTE: The kernel will never touch floats
(
Output {
float: context.get_fx_regs(),
},
size_of::<FloatRegisters>(),
)
}
RegsKind::Int => {
try_stop_context(context, token, |context, _| match context.regs() {
None => {
assert!(!context.running, "try_stop_context is broken, clearly");
println!(
"{}:{}: Couldn't read registers from stopped process",
file!(),
line!()
);
Err(Error::new(ENOTRECOVERABLE))
}
Some(stack) => {
let mut regs = IntRegisters::default();
stack.save(&mut regs);
Ok((Output { int: regs }, size_of::<IntRegisters>()))
}
})?
}
RegsKind::Env => (
Output {
env: read_env_regs(context, token)?,
},
size_of::<EnvRegisters>(),
),
};
let src_buf =
unsafe { slice::from_raw_parts(&output as *const _ as *const u8, size) };
buf.copy_common_bytes_from_slice(src_buf)
}
ContextHandle::AddrSpace { addrspace } => {
let Ok(offset) = usize::try_from(offset) else {
return Ok(0);
};
let grants_to_skip = offset / size_of::<GrantDesc>();
// Output a list of grant descriptors, sufficient to allow relibc's fork()
// implementation to fmap MAP_SHARED grants.
let mut grants_read = 0;
let mut dst = [GrantDesc::default(); 16];
let mut token = token.token();
let addr_space = addrspace.acquire_read(token.downgrade());
for (dst, (grant_base, grant_info)) in dst
.iter_mut()
.zip(addr_space.grants.iter().skip(grants_to_skip))
{
*dst = GrantDesc {
base: grant_base.start_address().data(),
size: grant_info.page_count() * PAGE_SIZE,
flags: grant_info.grant_flags(),
// The !0 is not a sentinel value; the availability of `offset` is
// indicated by the GRANT_SCHEME flag.
offset: grant_info.file_ref().map_or(!0, |f| f.base_offset as u64),
};
grants_read += 1;
}
for (src, chunk) in dst
.iter()
.take(grants_read)
.zip(buf.in_exact_chunks(size_of::<GrantDesc>()))
{
chunk.copy_exactly(src)?;
}
Ok(grants_read * size_of::<GrantDesc>())
}
ContextHandle::Filetable { data, .. } => read_from(buf, data, offset),
ContextHandle::ProcCpuinfo => {
let mut output = String::new();
crate::arch::device::cpu::cpu_info(&mut output).map_err(|_| Error::new(EIO))?;
read_from(buf, output.as_bytes(), offset)
}
ContextHandle::ProcMeminfo => {
let total_kib = crate::memory::total_frames() as u64 * PAGE_SIZE as u64 / 1024;
let free_kib = crate::memory::free_frames() as u64 * PAGE_SIZE as u64 / 1024;
let output = format!(
"MemTotal: {} kB\n\
MemFree: {} kB\n\
MemAvailable: {} kB\n\
Buffers: 0 kB\n\
Cached: 0 kB\n\
SwapCached: 0 kB\n\
SwapTotal: 0 kB\n\
SwapFree: 0 kB\n",
total_kib, free_kib, free_kib
);
read_from(buf, output.as_bytes(), offset)
}
ContextHandle::ProcUptime => {
let uptime_s = crate::time::monotonic(token) as f64 / crate::time::NANOS_PER_SEC as f64;
let idle_s = 0.0f64;
let output = format!("{:.2} {:.2}\n", uptime_s, idle_s);
read_from(buf, output.as_bytes(), offset)
}
ContextHandle::ProcLoadavg => {
let mut contexts = context::contexts(token.downgrade());
let (contexts, mut t) = contexts.token_split();
let mut nr_runnable: usize = 0;
let mut total: usize = 0;
for ctx in contexts.iter() {
total += 1;
if ctx.read(t.token()).status.is_runnable() {
nr_runnable += 1;
}
}
let output = format!("0.00 0.00 0.00 {}/{total} 0\n", nr_runnable);
read_from(buf, output.as_bytes(), offset)
}
ContextHandle::ProcVersion => {
let output = format!(
"Red Bear OS version {} ({}) {}\n",
env!("CARGO_PKG_VERSION"),
option_env!("TARGET").unwrap_or("x86_64"),
option_env!("COOKBOOK_SOURCE_IDENT").unwrap_or("")
);
read_from(buf, output.as_bytes(), offset)
}
ContextHandle::ProcFilesystems => {
// Linux /proc/filesystems lists registered filesystem types.
// Cross-referenced with Linux 7.1 fs/filesystems.c filesystems_proc_show().
// Red Bear supports redoxfs as the native filesystem.
let output = "nodev\tsysfs\nnodev\tproc\nnodev\tdevtmpfs\n\tredoxfs\n";
read_from(buf, output.as_bytes(), offset)
}
ContextHandle::ProcStat { pid } => {
let context = proc_context(*pid, token).ok_or(Error::new(ESRCH))?;
let (pid, comm, state, ppid, priority, utime, stime, pgrp, session, starttime, owner_proc_id, addr_space) = {
let context = context.read(token.token());
(
context.pid,
context.name.to_string(),
proc_status_char(&context.status),
context.owner_proc_id.map_or(0, NonZeroUsize::get),
context.prio,
context.utime / crate::time::NANOS_PER_SEC,
context.stime / crate::time::NANOS_PER_SEC,
context.pgrp,
context.session,
context.start_time / (crate::time::NANOS_PER_SEC / 100),
context.owner_proc_id,
context.addr_space.clone(),
)
};
let num_threads = {
let mut contexts = context::contexts(token.downgrade());
let (contexts, mut token2) = contexts.token_split();
contexts
.iter()
.filter(|ctx| ctx.read(token2.token()).owner_proc_id == owner_proc_id)
.count()
};
let vsize = proc_vsize(&addr_space, token);
let rss = proc_rss(&addr_space, token);
let nice = (priority as i32 / 2) - 20;
read_from(
buf,
proc_stat_line(
pid,
&comm,
state,
ppid,
priority,
utime,
stime,
pgrp,
session,
nice,
num_threads,
starttime,
vsize,
rss,
)
.as_bytes(),
offset,
)
}
ContextHandle::ProcComm { pid } => {
let context = proc_context(*pid, token).ok_or(Error::new(ESRCH))?;
let context = context.read(token.token());
read_from(buf, context.name.as_bytes(), offset)
}
ContextHandle::ProcCmdline { pid } => {
let context = proc_context(*pid, token).ok_or(Error::new(ESRCH))?;
let context = context.read(token.token());
let mut cmd = context.name.as_bytes().to_vec();
cmd.push(0);
read_from(buf, &cmd, offset)
}
ContextHandle::ProcStatus { pid } => {
let context = proc_context(*pid, token).ok_or(Error::new(ESRCH))?;
let owner_proc_id = context.read(token.token()).owner_proc_id;
let num_threads = {
let mut contexts = context::contexts(token.downgrade());
let (contexts, mut token2) = contexts.token_split();
contexts
.iter()
.filter(|ctx| ctx.read(token2.token()).owner_proc_id == owner_proc_id)
.count()
};
let (vsize, rss) = {
let addr_space = {
let context = context.read(token.token());
context.addr_space.clone()
};
let vsize = proc_vsize(&addr_space, token);
let rss = proc_rss(&addr_space, token);
(vsize, rss)
};
let context = context.read(token.token());
read_from(buf, proc_status_text(&context, num_threads, vsize, rss).as_bytes(), offset)
}
ContextHandle::ProcMaps { pid } => {
let context = proc_context(*pid, token).ok_or(Error::new(ESRCH))?;
let addr_space = context.read(token.token()).addr_space.clone();
let mut output = String::new();
if let Some(addr_space) = addr_space {
let token2 = token.downgrade();
let guard = addr_space.acquire_read(token2);
for (grant_base, grant_info) in guard.grants.iter() {
let start = grant_base.start_address().data();
let size = grant_info.page_count() * PAGE_SIZE;
let end = start + size;
let grant_flags = grant_info.grant_flags();
let perms = alloc::format!(
"{}{}{}p",
if grant_flags.contains(GrantFlags::GRANT_READ) { 'r' } else { '-' },
if grant_flags.contains(GrantFlags::GRANT_WRITE) { 'w' } else { '-' },
if grant_flags.contains(GrantFlags::GRANT_EXEC) { 'x' } else { '-' },
);
let path_str = if grant_info.file_ref().is_some() {
String::from("[file]")
} else if start < 0x10000000 {
String::from("[vdso]")
} else {
String::from("[heap]")
};
output.push_str(&alloc::format!(
"{:08x}-{:08x} {} 00000000 00:00 0 {}\n",
start, end, perms, path_str
));
}
}
read_from(buf, output.as_bytes(), offset)
}
ContextHandle::ProcStatm { pid } => {
let context = proc_context(*pid, token).ok_or(Error::new(ESRCH))?;
let addr_space = context.read(token.token()).addr_space.clone();
let (size, resident) = if let Some(addr_space) = addr_space {
let token2 = token.downgrade();
let guard = addr_space.acquire_read(token2);
let mut total_size = 0usize;
let mut total_resident = 0usize;
for (_grant_base, grant_info) in guard.grants.iter() {
let page_count = grant_info.page_count() * PAGE_SIZE;
total_size += page_count;
if grant_info.grant_flags().contains(GrantFlags::GRANT_PHYS) {
total_resident += page_count;
}
}
(total_size / PAGE_SIZE, total_resident / PAGE_SIZE)
} else {
(0, 0)
};
// Format: size resident shared text lib data dt (in pages)
// Cross-referenced with Linux 7.1 fs/proc/array.c:proc_pid_statm().
// On Red Bear we approximate: shared=0, text=size, lib=0, data=size, dt=0.
let output = alloc::format!(
"{} {} 0 {} 0 {} 0\n",
size, resident, size, size
);
read_from(buf, output.as_bytes(), offset)
}
ContextHandle::ProcLimits { pid } => {
let context = proc_context(*pid, token).ok_or(Error::new(ESRCH))?;
let rlimits = context.read(token.token()).rlimits;
// Linux 7.1 fs/proc/array.c:proc_pid_limits() format
// 16 RLIMITs with soft/hard values and units string
let names = [
("Max cpu time", "seconds"),
("Max file size", "bytes"),
("Max data size", "bytes"),
("Max stack size", "bytes"),
("Max core file size", "bytes"),
("Max resident set", "bytes"),
("Max processes", "processes"),
("Max open files", "files"),
("Max locked memory", "bytes"),
("Max address space", "bytes"),
("Max file locks", "locks"),
("Max sigpending", "signals"),
("Max msgqueue size", "bytes"),
("Max nice priority", "0"),
("Max realtime priority", "0"),
("Max realtime timeout", "us"),
];
let format_limit = |v: u64| -> alloc::string::String {
if v == u64::MAX { String::from("unlimited") }
else { alloc::format!("{}", v) }
};
let mut output = String::from(
"Limit Soft Limit Hard Limit Units\n",
);
for (i, (name, units)) in names.iter().enumerate() {
let soft = format_limit(rlimits[i]);
let hard = format_limit(if i + 16 < rlimits.len() { rlimits[i + 16] } else { u64::MAX });
output.push_str(&alloc::format!(
"{:<24} {:>20} {:>20} {}\n",
name, soft, hard, units
));
}
read_from(buf, output.as_bytes(), offset)
}
ContextHandle::ProcIo { pid } => {
let context = proc_context(*pid, token).ok_or(Error::new(ESRCH))?;
let context = context.read(token.token());
// Linux 7.1 fs/proc/base.c do_task_io_accounting() format
// /proc/[pid]/io is a key=value text file
let output = alloc::format!(
"rchar: {}\nwchar: {}\nsyscr: {}\nsyscw: {}\n\
read_bytes: {}\nwrite_bytes: {}\ncancelled_write_bytes: {}\n",
context.io_rchar,
context.io_wchar,
context.io_syscr,
context.io_syscw,
context.io_read_bytes,
context.io_write_bytes,
context.io_cancelled_write_bytes
);
read_from(buf, output.as_bytes(), offset)
}
ContextHandle::ProcRoot | ContextHandle::ProcDir { .. } => Err(Error::new(EBADF)),
ContextHandle::MmapMinAddr(addrspace) => {
let mut token = token.token();
let addr = addrspace.acquire_read(token.downgrade());
buf.write_usize(addr.mmap_min)?;
Ok(size_of::<usize>())
}
ContextHandle::SchedAffinity => {
let mask = context.read(token.token()).sched_affinity.to_raw();
buf.copy_exactly(crate::cpu_set::mask_as_bytes(&mask))?;
Ok(size_of_val(&mask))
} // TODO: Replace write() with SYS_SENDFD?
ContextHandle::Status { .. } => {
let status = {
let context = context.read(token.token());
match context.status {
Status::Runnable | Status::Dead { excp: None }
if context.being_sigkilled =>
{
ContextStatus::ForceKilled
}
Status::Dead { excp: None } => ContextStatus::Dead,
Status::Dead { excp: Some(excp) } => {
let (status, payload) =
buf.split_at(size_of::<usize>()).ok_or(Error::new(EINVAL))?;
status.copy_from_slice(
&(ContextStatus::UnhandledExcp as usize).to_ne_bytes(),
)?;
let len = payload.copy_common_bytes_from_slice(&excp)?;
return Ok(size_of::<usize>() + len);
}
Status::Runnable => ContextStatus::Runnable,
Status::Blocked => ContextStatus::Blocked,
Status::HardBlocked {
reason: HardBlockedReason::NotYetStarted,
} => ContextStatus::NotYetStarted,
Status::HardBlocked {
reason: HardBlockedReason::Stopped,
} => ContextStatus::Stopped,
_ => ContextStatus::Other,
}
};
buf.copy_common_bytes_from_slice(&(status as usize).to_ne_bytes())
}
ContextHandle::Attr => {
let mut debug_name = [0; 32];
let c = &context.read(token.token());
let (euid, egid, pid, name, prio) =
(c.euid, c.egid, c.pid as u32, c.name, c.prio as u32);
let min = name.len().min(debug_name.len());
debug_name[..min].copy_from_slice(&name.as_bytes()[..min]);
buf.copy_common_bytes_from_slice(&ProcSchemeAttrs {
pid,
euid,
egid,
prio,
debug_name,
})
}
Self::Groups => {
let c = &context.read(token.token());
let max = buf.len() / size_of::<u32>();
let count = c.groups.len().min(max);
for (chunk, gid) in buf.in_exact_chunks(size_of::<u32>()).zip(&c.groups).take(count) {
chunk.copy_from_slice(&gid.to_ne_bytes())?;
}
Ok(count * size_of::<u32>())
}
ContextHandle::Sighandler => {
let data = match context.read(token.token()).sig {
Some(ref sig) => SetSighandlerData {
excp_handler: sig.excp_handler.map_or(0, NonZeroUsize::get),
user_handler: sig.user_handler.get(),
proc_control_addr: sig.procctl_off.into(),
thread_control_addr: sig.threadctl_off.into(),
},
None => SetSighandlerData::default(),
};
buf.copy_common_bytes_from_slice(&data)
}
// TODO: Find a better way to switch address spaces, since they also require switching
// the instruction and stack pointer. Maybe remove `<pid>/regs` altogether and replace it
// with `<pid>/ctx`
_ => Err(Error::new(EBADF)),
}
}
}
fn write_env_regs(
context: Arc<ContextLock>,
regs: EnvRegisters,
token: &mut CleanLockToken,
) -> Result<()> {
if context::is_current(&context) {
context::current()
.write(token.token())
.write_current_env_regs(regs)
} else {
try_stop_context(context, token, |context, _| context.write_env_regs(regs))
}
}
fn read_env_regs(context: Arc<ContextLock>, token: &mut CleanLockToken) -> Result<EnvRegisters> {
if context::is_current(&context) {
context::current()
.read(token.token())
.read_current_env_regs()
} else {
try_stop_context(context, token, |context, _| context.read_env_regs())
}
}