2317 lines
90 KiB
Rust
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(®s.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(®s);
|
|
|
|
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(
|
|
®s 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())
|
|
}
|
|
}
|