611 lines
20 KiB
Rust
611 lines
20 KiB
Rust
use alloc::{
|
|
sync::Arc,
|
|
vec::Vec,
|
|
};
|
|
use core::mem;
|
|
|
|
use spin::{RwLock, RwLockWriteGuard};
|
|
|
|
use crate::context::memory::PageSpan;
|
|
use crate::context::{Context, ContextId, memory::AddrSpace, WaitpidKey};
|
|
|
|
use crate::Bootstrap;
|
|
use crate::context;
|
|
use crate::interrupt;
|
|
use crate::paging::mapper::{InactiveFlusher, PageFlushAll};
|
|
use crate::paging::{Page, PageFlags, VirtualAddress};
|
|
use crate::ptrace;
|
|
use crate::start::usermode;
|
|
use crate::syscall::data::SigAction;
|
|
use crate::syscall::error::*;
|
|
use crate::syscall::flag::{wifcontinued, wifstopped, MapFlags,
|
|
PTRACE_STOP_EXIT, SIG_BLOCK, SIG_SETMASK, SIG_UNBLOCK,
|
|
SIGCONT, SIGTERM, WaitFlags, WCONTINUED, WNOHANG, WUNTRACED};
|
|
use crate::syscall::ptrace_event;
|
|
|
|
use super::usercopy::{UserSliceWo, UserSliceRo};
|
|
|
|
fn empty<'lock>(context_lock: &'lock RwLock<Context>, mut context: RwLockWriteGuard<'lock, Context>, reaping: bool) -> RwLockWriteGuard<'lock, Context> {
|
|
// NOTE: If we do not replace the grants `Arc`, then a strange situation can appear where the
|
|
// main thread and another thread exit simultaneously before either one is reaped. If that
|
|
// happens, then the last context that runs exit will think that there is still are still
|
|
// remaining references to the grants, where there are in fact none. However, if either one is
|
|
// reaped before, then that reference will disappear, and no leak will occur.
|
|
//
|
|
// By removing the reference to the address space when the context will no longer be used, this
|
|
// problem will never occur.
|
|
let addr_space_arc = match context.addr_space.take() {
|
|
Some(a) => a,
|
|
None => return context,
|
|
};
|
|
|
|
if let Ok(mut addr_space) = Arc::try_unwrap(addr_space_arc).map(RwLock::into_inner) {
|
|
let mapper = &mut addr_space.table.utable;
|
|
|
|
for grant in addr_space.grants.into_iter() {
|
|
let unmap_result = if reaping {
|
|
log::error!("{}: {}: Grant should not exist: {:?}", context.id.into(), context.name, grant);
|
|
|
|
grant.unmap(mapper, &mut InactiveFlusher::new())
|
|
} else {
|
|
grant.unmap(mapper, PageFlushAll::new())
|
|
};
|
|
|
|
if unmap_result.file_desc.is_some() {
|
|
drop(context);
|
|
|
|
drop(unmap_result);
|
|
|
|
context = context_lock.write();
|
|
}
|
|
}
|
|
}
|
|
context
|
|
}
|
|
|
|
pub fn exit(status: usize) -> ! {
|
|
ptrace::breakpoint_callback(PTRACE_STOP_EXIT, Some(ptrace_event!(PTRACE_STOP_EXIT, status)));
|
|
|
|
{
|
|
let context_lock = context::current().expect("exit failed to find context");
|
|
|
|
let close_files;
|
|
let pid = {
|
|
let mut context = context_lock.write();
|
|
close_files = Arc::try_unwrap(mem::take(&mut context.files)).map_or_else(|_| Vec::new(), RwLock::into_inner);
|
|
context.id
|
|
};
|
|
|
|
// TODO: Find a better way to implement this, perhaps when the init process calls exit.
|
|
if pid == ContextId::from(1) {
|
|
println!("Main kernel thread exited with status {:X}", status);
|
|
|
|
extern {
|
|
fn kreset() -> !;
|
|
fn kstop() -> !;
|
|
}
|
|
|
|
if status == SIGTERM {
|
|
unsafe { kreset(); }
|
|
} else {
|
|
unsafe { kstop(); }
|
|
}
|
|
}
|
|
|
|
// Files must be closed while context is valid so that messages can be passed
|
|
for (_fd, file_opt) in close_files.into_iter().enumerate() {
|
|
if let Some(file) = file_opt {
|
|
let _ = file.close();
|
|
}
|
|
}
|
|
|
|
// PGID and PPID must be grabbed after close, as context switches could change PGID or PPID if parent exits
|
|
let (pgid, ppid) = {
|
|
let context = context_lock.read();
|
|
(context.pgid, context.ppid)
|
|
};
|
|
|
|
// Transfer child processes to parent
|
|
{
|
|
let contexts = context::contexts();
|
|
for (_id, context_lock) in contexts.iter() {
|
|
let mut context = context_lock.write();
|
|
if context.ppid == pid {
|
|
context.ppid = ppid;
|
|
context.vfork = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
let (vfork, children) = {
|
|
let mut context = context_lock.write();
|
|
|
|
context = empty(&context_lock, context, false);
|
|
|
|
let vfork = context.vfork;
|
|
context.vfork = false;
|
|
|
|
context.status = context::Status::Exited(status);
|
|
|
|
let children = context.waitpid.receive_all();
|
|
|
|
(vfork, children)
|
|
};
|
|
|
|
{
|
|
let contexts = context::contexts();
|
|
if let Some(parent_lock) = contexts.get(ppid) {
|
|
let waitpid = {
|
|
let mut parent = parent_lock.write();
|
|
if vfork && ! parent.unblock() {
|
|
println!("{}: {} not blocked for exit vfork unblock", pid.into(), ppid.into());
|
|
}
|
|
Arc::clone(&parent.waitpid)
|
|
};
|
|
|
|
for (c_pid, c_status) in children {
|
|
waitpid.send(c_pid, c_status);
|
|
}
|
|
|
|
waitpid.send(WaitpidKey {
|
|
pid: Some(pid),
|
|
pgid: Some(pgid)
|
|
}, (pid, status));
|
|
} else {
|
|
println!("{}: {} not found for exit vfork unblock", pid.into(), ppid.into());
|
|
}
|
|
}
|
|
|
|
// Alert any tracers waiting of this process
|
|
ptrace::close_tracee(pid);
|
|
}
|
|
|
|
let _ = unsafe { context::switch() };
|
|
|
|
unreachable!();
|
|
}
|
|
|
|
pub fn getpid() -> Result<ContextId> {
|
|
let contexts = context::contexts();
|
|
let context_lock = contexts.current().ok_or(Error::new(ESRCH))?;
|
|
let context = context_lock.read();
|
|
Ok(context.id)
|
|
}
|
|
|
|
pub fn getpgid(pid: ContextId) -> Result<ContextId> {
|
|
let contexts = context::contexts();
|
|
let context_lock = if pid.into() == 0 {
|
|
contexts.current().ok_or(Error::new(ESRCH))?
|
|
} else {
|
|
contexts.get(pid).ok_or(Error::new(ESRCH))?
|
|
};
|
|
let context = context_lock.read();
|
|
Ok(context.pgid)
|
|
}
|
|
|
|
pub fn getppid() -> Result<ContextId> {
|
|
let contexts = context::contexts();
|
|
let context_lock = contexts.current().ok_or(Error::new(ESRCH))?;
|
|
let context = context_lock.read();
|
|
Ok(context.ppid)
|
|
}
|
|
|
|
pub fn kill(pid: ContextId, sig: usize) -> Result<usize> {
|
|
let (ruid, euid, current_pgid) = {
|
|
let contexts = context::contexts();
|
|
let context_lock = contexts.current().ok_or(Error::new(ESRCH))?;
|
|
let context = context_lock.read();
|
|
(context.ruid, context.euid, context.pgid)
|
|
};
|
|
|
|
if sig < 0x7F {
|
|
let mut found = 0;
|
|
let mut sent = 0;
|
|
|
|
{
|
|
let contexts = context::contexts();
|
|
|
|
let send = |context: &mut context::Context| -> bool {
|
|
if euid == 0
|
|
|| euid == context.ruid
|
|
|| ruid == context.ruid
|
|
{
|
|
// If sig = 0, test that process exists and can be
|
|
// signalled, but don't send any signal.
|
|
if sig != 0 {
|
|
//TODO: sigprocmask
|
|
context.pending.push_back(sig as u8);
|
|
// Convert stopped processes to blocked if sending SIGCONT
|
|
if sig == SIGCONT {
|
|
if let context::Status::Stopped(_sig) = context.status {
|
|
context.status = context::Status::Blocked;
|
|
}
|
|
}
|
|
}
|
|
true
|
|
} else {
|
|
false
|
|
}
|
|
};
|
|
|
|
if pid.into() as isize > 0 {
|
|
// Send to a single process
|
|
if let Some(context_lock) = contexts.get(pid) {
|
|
let mut context = context_lock.write();
|
|
|
|
found += 1;
|
|
if send(&mut context) {
|
|
sent += 1;
|
|
}
|
|
}
|
|
} else if pid.into() as isize == -1 {
|
|
// Send to every process with permission, except for init
|
|
for (_id, context_lock) in contexts.iter() {
|
|
let mut context = context_lock.write();
|
|
|
|
if context.id.into() > 2 {
|
|
found += 1;
|
|
|
|
if send(&mut context) {
|
|
sent += 1;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
let pgid = if pid.into() == 0 {
|
|
current_pgid
|
|
} else {
|
|
ContextId::from(-(pid.into() as isize) as usize)
|
|
};
|
|
|
|
// Send to every process in the process group whose ID
|
|
for (_id, context_lock) in contexts.iter() {
|
|
let mut context = context_lock.write();
|
|
|
|
if context.pgid == pgid {
|
|
found += 1;
|
|
|
|
if send(&mut context) {
|
|
sent += 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if found == 0 {
|
|
Err(Error::new(ESRCH))
|
|
} else if sent == 0 {
|
|
Err(Error::new(EPERM))
|
|
} else {
|
|
// Switch to ensure delivery to self
|
|
unsafe { context::switch(); }
|
|
|
|
Ok(0)
|
|
}
|
|
} else {
|
|
Err(Error::new(EINVAL))
|
|
}
|
|
}
|
|
|
|
pub fn mprotect(address: usize, size: usize, flags: MapFlags) -> Result<usize> {
|
|
// println!("mprotect {:#X}, {}, {:#X}", address, size, flags);
|
|
|
|
let span = PageSpan::validate_nonempty(VirtualAddress::new(address), size).ok_or(Error::new(EINVAL))?;
|
|
|
|
AddrSpace::current()?.write().mprotect(span, flags).map(|()| 0)
|
|
}
|
|
|
|
pub fn setpgid(pid: ContextId, pgid: ContextId) -> Result<usize> {
|
|
let contexts = context::contexts();
|
|
|
|
let current_pid = {
|
|
let context_lock = contexts.current().ok_or(Error::new(ESRCH))?;
|
|
let context = context_lock.read();
|
|
context.id
|
|
};
|
|
|
|
let context_lock = if pid.into() == 0 {
|
|
contexts.current().ok_or(Error::new(ESRCH))?
|
|
} else {
|
|
contexts.get(pid).ok_or(Error::new(ESRCH))?
|
|
};
|
|
|
|
let mut context = context_lock.write();
|
|
if context.id == current_pid || context.ppid == current_pid {
|
|
if pgid.into() == 0 {
|
|
context.pgid = context.id;
|
|
} else {
|
|
context.pgid = pgid;
|
|
}
|
|
Ok(0)
|
|
} else {
|
|
Err(Error::new(ESRCH))
|
|
}
|
|
}
|
|
|
|
pub fn sigaction(sig: usize, act_opt: Option<UserSliceRo>, oldact_opt: Option<UserSliceWo>, restorer: usize) -> Result<()> {
|
|
if sig == 0 || sig > 0x7F {
|
|
return Err(Error::new(EINVAL));
|
|
}
|
|
let contexts = context::contexts();
|
|
let context_lock = contexts.current().ok_or(Error::new(ESRCH))?;
|
|
let context = context_lock.read();
|
|
let mut actions = context.actions.write();
|
|
|
|
if let Some(oldact) = oldact_opt {
|
|
oldact.copy_exactly(&actions[sig].0)?;
|
|
}
|
|
|
|
if let Some(act) = act_opt {
|
|
actions[sig] = (unsafe { act.read_exact::<SigAction>()? }, restorer);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn sigprocmask(how: usize, mask_opt: Option<UserSliceRo>, oldmask_opt: Option<UserSliceWo>) -> Result<()> {
|
|
{
|
|
let contexts = context::contexts();
|
|
let context_lock = contexts.current().ok_or(Error::new(ESRCH))?;
|
|
let mut context = context_lock.write();
|
|
|
|
let [old_lo, old_hi] = context.sigmask;
|
|
|
|
if let Some(oldmask) = oldmask_opt {
|
|
// TODO: sigprocmask should be u64
|
|
let (lo_dst, hi_dst) = oldmask.split_at(core::mem::size_of::<u64>()).ok_or(Error::new(EINVAL))?;
|
|
lo_dst.write_u64(old_lo)?;
|
|
hi_dst.write_u64(old_hi)?;
|
|
}
|
|
|
|
if let Some(mask) = mask_opt {
|
|
let (lo_src, hi_src) = mask.split_at(core::mem::size_of::<u64>()).ok_or(Error::new(EINVAL))?;
|
|
let lo_arg = lo_src.read_u64()?;
|
|
let hi_arg = hi_src.read_u64()?;
|
|
|
|
context.sigmask = match how {
|
|
SIG_BLOCK => {
|
|
[old_lo | lo_arg, old_hi | hi_arg]
|
|
},
|
|
SIG_UNBLOCK => {
|
|
[old_lo & !lo_arg, old_hi & !hi_arg]
|
|
},
|
|
SIG_SETMASK => {
|
|
[lo_arg, hi_arg]
|
|
},
|
|
_ => {
|
|
return Err(Error::new(EINVAL));
|
|
}
|
|
};
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub fn sigreturn() -> Result<usize> {
|
|
{
|
|
let contexts = context::contexts();
|
|
let context_lock = contexts.current().ok_or(Error::new(ESRCH))?;
|
|
let mut context = context_lock.write();
|
|
if context.ksig.is_some() {
|
|
context.ksig_restore = true;
|
|
context.block("sigreturn");
|
|
} else {
|
|
return Err(Error::new(EINVAL));
|
|
}
|
|
}
|
|
|
|
let _ = unsafe { context::switch() };
|
|
|
|
unreachable!();
|
|
}
|
|
|
|
pub fn umask(mask: usize) -> Result<usize> {
|
|
let previous;
|
|
{
|
|
let contexts = context::contexts();
|
|
let context_lock = contexts.current().ok_or(Error::new(ESRCH))?;
|
|
let mut context = context_lock.write();
|
|
previous = context.umask;
|
|
context.umask = mask;
|
|
}
|
|
|
|
Ok(previous)
|
|
}
|
|
|
|
fn reap(pid: ContextId) -> Result<ContextId> {
|
|
// Spin until not running
|
|
let mut running = true;
|
|
while running {
|
|
{
|
|
let contexts = context::contexts();
|
|
let context_lock = contexts.get(pid).ok_or(Error::new(ESRCH))?;
|
|
let context = context_lock.read();
|
|
running = context.running;
|
|
}
|
|
|
|
interrupt::pause();
|
|
}
|
|
|
|
let mut contexts = context::contexts_mut();
|
|
let context_lock = contexts.remove(pid).ok_or(Error::new(ESRCH))?;
|
|
{
|
|
let context = context_lock.write();
|
|
empty(&context_lock, context, true);
|
|
}
|
|
drop(context_lock);
|
|
|
|
Ok(pid)
|
|
}
|
|
|
|
pub fn waitpid(pid: ContextId, status_ptr: Option<UserSliceWo>, flags: WaitFlags) -> Result<ContextId> {
|
|
let (ppid, waitpid) = {
|
|
let contexts = context::contexts();
|
|
let context_lock = contexts.current().ok_or(Error::new(ESRCH))?;
|
|
let context = context_lock.read();
|
|
(context.id, Arc::clone(&context.waitpid))
|
|
};
|
|
let write_status = |value| status_ptr.map(|ptr| ptr.write_usize(value)).unwrap_or(Ok(()));
|
|
|
|
let grim_reaper = |w_pid: ContextId, status: usize| -> Option<Result<ContextId>> {
|
|
if wifcontinued(status) {
|
|
if flags & WCONTINUED == WCONTINUED {
|
|
Some(write_status(status).map(|()| w_pid))
|
|
} else {
|
|
None
|
|
}
|
|
} else if wifstopped(status) {
|
|
if flags & WUNTRACED == WUNTRACED {
|
|
Some(write_status(status).map(|()| w_pid))
|
|
} else {
|
|
None
|
|
}
|
|
} else {
|
|
Some(write_status(status).and_then(|()| reap(w_pid)))
|
|
}
|
|
};
|
|
|
|
loop {
|
|
let res_opt = if pid.into() == 0 {
|
|
// Check for existence of child
|
|
{
|
|
let mut found = false;
|
|
|
|
let contexts = context::contexts();
|
|
for (_id, context_lock) in contexts.iter() {
|
|
let context = context_lock.read();
|
|
if context.ppid == ppid {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ! found {
|
|
return Err(Error::new(ECHILD));
|
|
}
|
|
}
|
|
|
|
if flags & WNOHANG == WNOHANG {
|
|
if let Some((_wid, (w_pid, status))) = waitpid.receive_any_nonblock() {
|
|
grim_reaper(w_pid, status)
|
|
} else {
|
|
Some(Ok(ContextId::from(0)))
|
|
}
|
|
} else {
|
|
let (_wid, (w_pid, status)) = waitpid.receive_any("waitpid any");
|
|
grim_reaper(w_pid, status)
|
|
}
|
|
} else if (pid.into() as isize) < 0 {
|
|
let pgid = ContextId::from(-(pid.into() as isize) as usize);
|
|
|
|
// Check for existence of child in process group PGID
|
|
{
|
|
let mut found = false;
|
|
|
|
let contexts = context::contexts();
|
|
for (_id, context_lock) in contexts.iter() {
|
|
let context = context_lock.read();
|
|
if context.pgid == pgid {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ! found {
|
|
return Err(Error::new(ECHILD));
|
|
}
|
|
}
|
|
|
|
if flags & WNOHANG == WNOHANG {
|
|
if let Some((w_pid, status)) = waitpid.receive_nonblock(&WaitpidKey {
|
|
pid: None,
|
|
pgid: Some(pgid)
|
|
}) {
|
|
grim_reaper(w_pid, status)
|
|
} else {
|
|
Some(Ok(ContextId::from(0)))
|
|
}
|
|
} else {
|
|
let (w_pid, status) = waitpid.receive(&WaitpidKey {
|
|
pid: None,
|
|
pgid: Some(pgid)
|
|
}, "waitpid pgid");
|
|
grim_reaper(w_pid, status)
|
|
}
|
|
} else {
|
|
let hack_status = {
|
|
let contexts = context::contexts();
|
|
let context_lock = contexts.get(pid).ok_or(Error::new(ECHILD))?;
|
|
let mut context = context_lock.write();
|
|
if context.ppid != ppid {
|
|
println!("TODO: Hack for rustc - changing ppid of {} from {} to {}", context.id.into(), context.ppid.into(), ppid.into());
|
|
context.ppid = ppid;
|
|
//return Err(Error::new(ECHILD));
|
|
Some(context.status)
|
|
} else {
|
|
None
|
|
}
|
|
};
|
|
|
|
if let Some(context::Status::Exited(status)) = hack_status {
|
|
let _ = waitpid.receive_nonblock(&WaitpidKey {
|
|
pid: Some(pid),
|
|
pgid: None
|
|
});
|
|
grim_reaper(pid, status)
|
|
} else if flags & WNOHANG == WNOHANG {
|
|
if let Some((w_pid, status)) = waitpid.receive_nonblock(&WaitpidKey {
|
|
pid: Some(pid),
|
|
pgid: None
|
|
}) {
|
|
grim_reaper(w_pid, status)
|
|
} else {
|
|
Some(Ok(ContextId::from(0)))
|
|
}
|
|
} else {
|
|
let (w_pid, status) = waitpid.receive(&WaitpidKey {
|
|
pid: Some(pid),
|
|
pgid: None
|
|
}, "waitpid pid");
|
|
grim_reaper(w_pid, status)
|
|
}
|
|
};
|
|
|
|
if let Some(res) = res_opt {
|
|
return res;
|
|
}
|
|
}
|
|
}
|
|
|
|
pub unsafe fn usermode_bootstrap(bootstrap: &Bootstrap) -> ! {
|
|
assert_ne!(bootstrap.page_count, 0);
|
|
|
|
{
|
|
let addr_space = Arc::clone(context::contexts().current()
|
|
.expect("expected a context to exist when executing init")
|
|
.read().addr_space()
|
|
.expect("expected bootstrap context to have an address space"));
|
|
|
|
// TODO: Use AddrSpace::mmap.
|
|
let mut addr_space = addr_space.write();
|
|
let addr_space = &mut *addr_space;
|
|
|
|
// TODO: Mark as owned and then support reclaiming the memory to the allocator if
|
|
// deallocated?
|
|
addr_space.grants.insert(context::memory::Grant::physmap(
|
|
bootstrap.base.clone(),
|
|
PageSpan::new(
|
|
Page::containing_address(VirtualAddress::new(0)),
|
|
bootstrap.page_count,
|
|
),
|
|
PageFlags::new().user(true).write(true).execute(true),
|
|
&mut addr_space.table.utable,
|
|
PageFlushAll::new(),
|
|
).expect("failed to physmap bootstrap memory"));
|
|
}
|
|
|
|
// Start in a minimal environment without any stack.
|
|
usermode(bootstrap.entry, 0, 0, 0);
|
|
}
|