use alloc::{sync::Arc, vec::Vec}; use core::{mem, num::NonZeroUsize, sync::atomic::Ordering}; use spinning_top::RwSpinlock; use syscall::{sig_bit, SIGCHLD, SIGKILL, SIGSTOP, SIGTERM, SIGTSTP, SIGTTIN, SIGTTOU}; use rmm::Arch; use spin::RwLock; use crate::context::{ memory::{AddrSpace, Grant, PageSpan}, process::{self, Process, ProcessId, ProcessInfo, ProcessStatus}, Context, ContextRef, WaitpidKey, }; use crate::{ context, interrupt, paging::{Page, VirtualAddress, PAGE_SIZE}, ptrace, syscall::{ error::*, flag::{ wifcontinued, wifstopped, MapFlags, WaitFlags, PTRACE_STOP_EXIT, SIGCONT, WCONTINUED, WNOHANG, WUNTRACED, }, ptrace_event, }, Bootstrap, CurrentRmmArch, }; use super::usercopy::UserSliceWo; pub fn exit_context(context_lock: Arc>) { if !context::is_current(&context_lock) { context_lock.write().status = context::Status::Dead; while context_lock.read().running { context::switch(); } } let close_files; let addrspace_opt; { let mut context = context_lock.write(); close_files = Arc::try_unwrap(mem::take(&mut context.files)) .map_or_else(|_| Vec::new(), RwLock::into_inner); addrspace_opt = context .set_addr_space(None) .and_then(|a| Arc::try_unwrap(a).ok()); drop(context.syscall_head.take()); drop(context.syscall_tail.take()); } // Files must be closed while context is valid so that messages can be passed for file_opt in close_files.into_iter() { if let Some(file) = file_opt { let _ = file.close(); } } drop(addrspace_opt); // TODO: Should status == Status::HardBlocked be handled differently? context_lock.write().status = context::Status::Dead; let _ = context::contexts_mut().remove(&ContextRef(context_lock)); } pub fn exit(status: usize) -> ! { ptrace::breakpoint_callback( PTRACE_STOP_EXIT, Some(ptrace_event!(PTRACE_STOP_EXIT, status)), ); let current_context = context::current(); let current_process = process::current().expect("no active process during exit syscall"); let current_pid = current_process.read().pid; let threads = core::mem::take(&mut current_process.write().threads); for context_lock in threads.into_iter().filter_map(|t| t.upgrade()) { // Current context must be closed last, as it would otherwise be impossible to context // switch back, if closing file descriptors require scheme calls. if Arc::ptr_eq(&context_lock, ¤t_context) { continue; } exit_context(context_lock); } exit_context(current_context); { // PGID and PPID must be grabbed after close, as context switches could change PGID or PPID if parent exits let (pgid, ppid) = { let process = current_process.read(); (process.pgid, process.ppid) }; let _ = kill(ppid, SIGCHLD, true); // Transfer child processes to parent (TODO: to init) { let processes = context::process::PROCESSES.read(); for (_child_pid, child_process_lock) in processes.iter() { let mut process = child_process_lock.write(); if process.ppid == current_pid { process.ppid = ppid; } } } current_process.write().status = ProcessStatus::Exited(status); let children = current_process.write().waitpid.receive_all(); { let processes = process::PROCESSES.read(); if let Some(parent_lock) = processes.get(&ppid) { let waitpid = Arc::clone(&parent_lock.write().waitpid); for (c_pid, c_status) in children { waitpid.send(c_pid, c_status); } waitpid.send( WaitpidKey { pid: Some(current_pid), pgid: Some(pgid), }, (current_pid, status), ); } } // Alert any tracers waiting of this process ptrace::close_tracee(current_pid); } let _ = context::switch(); unreachable!(); } pub fn getpid() -> Result { context::current_pid() } pub fn getpgid(pid: ProcessId) -> Result { let process_lock = if pid.get() == 0 { process::current()? } else { Arc::clone( process::PROCESSES .read() .get(&pid) .ok_or(Error::new(ESRCH))?, ) }; let process = process_lock.read(); Ok(process.pgid) } pub fn getppid() -> Result { Ok(process::current()?.read().ppid) } pub fn kill(pid: ProcessId, sig: usize, parent_sigchld: bool) -> Result { let (ruid, euid, current_pgid) = { let process_lock = process::current()?; let process = process_lock.read(); (process.ruid, process.euid, process.pgid) }; if euid == 0 && pid.get() == 1 { match sig { SIGTERM => unsafe { crate::stop::kreset() }, SIGKILL => unsafe { crate::stop::kstop() }, _ => return Ok(0), // error? } } if sig > 0x3F { return Err(Error::new(EINVAL)); } let sig_group = sig / 32; let mut found = 0; let mut sent = 0; let mut killed_self = false; { let processes = process::PROCESSES.read(); enum SendResult { Forbidden, Succeeded, SucceededSigchld { ppid: ProcessId, pgid: ProcessId, orig_signal: usize, }, SucceededSigcont { ppid: ProcessId, pgid: ProcessId, }, } let mut send = |context_lock: &Arc>, process_lock: &Arc>, proc_info: &ProcessInfo| -> SendResult { let is_self = context::is_current(context_lock); // Non-root users cannot kill arbitrarily. if euid != 0 && euid != proc_info.ruid && ruid != proc_info.ruid { return SendResult::Forbidden; } // If sig = 0, test that process exists and can be signalled, but don't send any // signal. if sig == 0 { return SendResult::Succeeded; } let mut process_guard = process_lock.write(); if sig == SIGCONT && let ProcessStatus::Stopped(_sig) = process_guard.status { // Convert stopped processes to blocked if sending SIGCONT, regardless of whether // SIGCONT is blocked or ignored. It can however be controlled whether the process // will additionally ignore, defer, or handle that signal. process_guard.status = ProcessStatus::PossiblyRunnable; drop(process_guard); if let Some((tctl, pctl, _st)) = context_lock.write().sigcontrol() { if !pctl.signal_will_ign(SIGCONT, false) { tctl.word[0].fetch_or(sig_bit(SIGCONT), Ordering::Relaxed); } if (tctl.word[0].load(Ordering::Relaxed) >> 32) & sig_bit(SIGCONT) != 0 { // already Runnable, SIGCONT handler will run like any other signal } } // POSIX XSI allows but does not reqiure SIGCHLD to be sent when SIGCONT occurs. return SendResult::SucceededSigcont { ppid: proc_info.ppid, pgid: proc_info.pgid, }; } drop(process_guard); let mut context_guard = context_lock.write(); if sig == SIGSTOP || (matches!(sig, SIGTTIN | SIGTTOU | SIGTSTP) && context_guard .sigcontrol() .map_or(false, |(_, proc, _)| proc.signal_will_stop(sig))) { context_guard.status = context::Status::Blocked; // TODO: Actually wait for, or IPI the context first, then clear bit. Not atomically safe otherwise. if let Some((ctl, _, _)) = context_guard.sigcontrol() { ctl.word[0].fetch_and(!sig_bit(SIGCONT), Ordering::Relaxed); } drop(context_guard); process_lock.write().status = ProcessStatus::Stopped(sig); return SendResult::SucceededSigchld { ppid: proc_info.ppid, pgid: proc_info.pgid, orig_signal: sig, }; } if sig == SIGKILL { context_guard.being_sigkilled = true; context_guard.unblock(); drop(context_guard); process_lock.write().status = ProcessStatus::Exited(SIGKILL); killed_self |= is_self; // exit() will signal the parent, rather than immediately in kill() return SendResult::Succeeded; } if let Some((tctl, pctl, _st)) = context_guard.sigcontrol() && !pctl.signal_will_ign(sig, parent_sigchld) { let _was_new = tctl.word[sig_group].fetch_or(sig_bit(sig), Ordering::Relaxed); if (tctl.word[sig_group].load(Ordering::Relaxed) >> 32) & sig_bit(sig) != 0 { context_guard.unblock(); killed_self |= is_self; } SendResult::Succeeded } else { // Discard signals if sighandler is unset. This includes both special contexts such // as bootstrap, and child processes or threads that have not yet been started. // This is semantically equivalent to having all signals except SIGSTOP and SIGKILL // blocked/ignored (SIGCONT can be ignored and masked, but will always continue // stopped processes first). SendResult::Succeeded } }; let mut handle_send = |pid, result| -> Result<()> { match result { SendResult::Forbidden => (), SendResult::Succeeded => sent += 1, SendResult::SucceededSigchld { ppid, pgid, orig_signal, } => { sent += 1; let waitpid = Arc::clone( &process::PROCESSES .read() .get(&ppid) .ok_or(Error::new(ESRCH))? .read() .waitpid, ); waitpid.send( WaitpidKey { pid: Some(pid), pgid: Some(pgid), }, (pid, (orig_signal << 8) | 0x7f), ); kill(ppid, SIGCHLD, true)?; } SendResult::SucceededSigcont { ppid, pgid } => { sent += 1; process::PROCESSES .read() .get(&ppid) .ok_or(Error::new(ESRCH))? .read() .waitpid .send( WaitpidKey { pid: Some(pid), pgid: Some(pgid), }, (pid, 0xffff), ); } } Ok(()) }; if pid.get() as isize > 0 { // Send to a single process if let Some(process_lock) = processes.get(&pid) { found += 1; let (context_lock, info) = { let process = process_lock.read(); ( process .threads .first() .ok_or(Error::new(ESRCH))? .upgrade() .ok_or(Error::new(ESRCH))?, process.info, ) }; let result = send(&context_lock, process_lock, &info); handle_send(pid, result)?; } } else if pid.get() == 1_usize.wrapping_neg() { // Send to every process with permission, except for init for (pid, process_lock) in processes.iter() { let (context_lock, info) = { let process = process_lock.read(); ( process .threads .first() .ok_or(Error::new(ESRCH))? .upgrade() .ok_or(Error::new(ESRCH))?, process.info, ) }; if info.pid.get() <= 1 { continue; } found += 1; let result = send(&context_lock, process_lock, &info); handle_send(*pid, result)?; } } else { let pgid = if pid.get() == 0 { current_pgid } else { ProcessId::from(pid.get().wrapping_neg()) }; // Send to every process in the process group whose ID for (pid, process_lock) in processes.iter() { let (context_lock, info) = { let process = process_lock.read(); ( process .threads .first() .ok_or(Error::new(ESRCH))? .upgrade() .ok_or(Error::new(ESRCH))?, process.info, ) }; if info.pgid != pgid { continue; } found += 1; let result = send(&context_lock, process_lock, &info); handle_send(*pid, result)?; } } } if found == 0 { Err(Error::new(ESRCH)) } else if sent == 0 { Err(Error::new(EPERM)) } else if killed_self { // Inform userspace it should check its own mask Err(Error::new(EINTR)) } else { Ok(0) } } pub fn mprotect(address: usize, size: usize, flags: MapFlags) -> Result<()> { // println!("mprotect {:#X}, {}, {:#X}", address, size, flags); let span = PageSpan::validate_nonempty(VirtualAddress::new(address), size) .ok_or(Error::new(EINVAL))?; AddrSpace::current()?.mprotect(span, flags) } pub fn setpgid(pid: ProcessId, pgid: ProcessId) -> Result<()> { let current_pid = context::current_pid()?; let processes = process::PROCESSES.read(); let process_lock = if pid.get() == 0 { process::current()? } else { Arc::clone(processes.get(&pid).ok_or(Error::new(ESRCH))?) }; let mut process = process_lock.write(); if process.pid == current_pid || process.ppid == current_pid { if pgid.get() == 0 { process.pgid = process.pid; } else { process.pgid = pgid; } Ok(()) } else { Err(Error::new(ESRCH)) } } pub fn umask(mask: usize) -> Result { let previous; { let process_lock = process::current()?; let mut process = process_lock.write(); previous = process.umask; process.umask = mask; } Ok(previous) } fn reap(pid: ProcessId) -> Result { let process_lock = Arc::clone( process::PROCESSES .read() .get(&pid) .ok_or(Error::new(ESRCH))?, ); // Spin until not running loop { // TODO: exit WaitCondition? { let mut process = process_lock.read(); if process .threads .iter() .all(|t| t.upgrade().map_or(true, |t| !t.read().running)) { break; } } // TODO: context switch? interrupt::pause(); } let _ = process::PROCESSES .write() .remove(&pid) .ok_or(Error::new(ESRCH))?; Ok(pid) } pub fn waitpid( pid: ProcessId, status_ptr: Option, flags: WaitFlags, ) -> Result { let (ppid, waitpid) = { let process_lock = process::current()?; let process = process_lock.read(); (process.pid, Arc::clone(&process.waitpid)) }; let write_status = |value| { status_ptr .map(|ptr| ptr.write_usize(value)) .unwrap_or(Ok(())) }; let grim_reaper = |w_pid: ProcessId, status: usize| -> Option> { 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.get() == 0 { // Check for existence of child { let mut found = false; let processes = process::PROCESSES.read(); for (_id, process_lock) in processes.iter() { let process = process_lock.read(); if process.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(ProcessId::from(0))) } } else { let (_wid, (w_pid, status)) = waitpid.receive_any("waitpid any"); grim_reaper(w_pid, status) } } else if (pid.get() as isize) < 0 { let pgid = ProcessId::from(-(pid.get() as isize) as usize); // Check for existence of child in process group PGID { let mut found = false; let processes = process::PROCESSES.read(); for (_pid, process_lock) in processes.iter() { let process = process_lock.read(); if process.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(ProcessId::from(0))) } } else { let (w_pid, status) = waitpid.receive( &WaitpidKey { pid: None, pgid: Some(pgid), }, "waitpid pgid", ); grim_reaper(w_pid, status) } } else { let status = { let process_lock = Arc::clone( process::PROCESSES .read() .get(&pid) .ok_or(Error::new(ESRCH))?, ); let process_guard = process_lock.read(); if process_guard.ppid != ppid { return Err(Error::new(ECHILD)); } process_guard.status }; if let ProcessStatus::Exited(status) = status { let _ = waitpid.receive_nonblock(&WaitpidKey { pid: Some(pid), pgid: None, }); grim_reaper(pid, status) } else if flags & WNOHANG == WNOHANG { let res = waitpid.receive_nonblock(&WaitpidKey { pid: Some(pid), pgid: None, }); if let Some((w_pid, status)) = res { grim_reaper(w_pid, status) } else { Some(Ok(ProcessId::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::current() .read() .addr_space() .expect("expected bootstrap context to have an address space"), ); let base = Page::containing_address(VirtualAddress::new(PAGE_SIZE)); let flags = MapFlags::MAP_FIXED_NOREPLACE | MapFlags::PROT_EXEC | MapFlags::PROT_READ | MapFlags::PROT_WRITE; let page_count = NonZeroUsize::new(bootstrap.page_count).expect("bootstrap contained no pages!"); let _base_page = addr_space.acquire_write().mmap( &addr_space, Some(base), page_count, flags, &mut Vec::new(), |page, flags, mapper, flusher| { let shared = false; Ok(Grant::zeroed( PageSpan::new(page, bootstrap.page_count), flags, mapper, flusher, shared, )?) }, ); } let bootstrap_slice = unsafe { bootstrap_mem(bootstrap) }; UserSliceWo::new(PAGE_SIZE, bootstrap.page_count * PAGE_SIZE) .expect("failed to create bootstrap user slice") .copy_from_slice(bootstrap_slice) .expect("failed to copy memory to bootstrap"); let bootstrap_entry = u64::from_le_bytes(bootstrap_slice[0x1a..0x22].try_into().unwrap()); log::info!("Bootstrap entry point: {:X}", bootstrap_entry); assert_ne!(bootstrap_entry, 0); // Start in a minimal environment without any stack. match context::current() .write() .regs_mut() .expect("bootstrap needs registers to be available") { ref mut regs => { regs.init(); regs.set_instr_pointer(bootstrap_entry.try_into().unwrap()); } } } pub unsafe fn bootstrap_mem(bootstrap: &crate::Bootstrap) -> &'static [u8] { core::slice::from_raw_parts( CurrentRmmArch::phys_to_virt(bootstrap.base.start_address()).data() as *const u8, bootstrap.page_count * PAGE_SIZE, ) }