diff --git a/Cargo.lock b/Cargo.lock index 6f88f5e6c0..13ab2e20b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + [[package]] name = "autocfg" version = "1.4.0" @@ -18,19 +24,29 @@ checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" name = "bootstrap" version = "0.0.0" dependencies = [ + "arrayvec", "hashbrown", "linked_list_allocator", + "log", + "plain", "redox-initfs", "redox-path", "redox-rt", "redox-scheme", "redox_syscall", + "slab", ] +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "generic-rt" version = "0.1.0" -source = "git+https://gitlab.redox-os.org/redox-os/relibc#5ea71a02c7f9e436ed0dcf53bc7a5bdb311279cf" +source = "git+https://gitlab.redox-os.org/redox-os/relibc.git#040009ab3d384e75d62b7e7dd3176d910203501a" [[package]] name = "goblin" @@ -45,15 +61,18 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.5" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +dependencies = [ + "foldhash", +] [[package]] name = "libc" -version = "0.2.171" +version = "0.2.172" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] name = "libredox" @@ -99,9 +118,9 @@ checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" [[package]] name = "proc-macro2" -version = "1.0.94" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] @@ -132,7 +151,7 @@ checksum = "436d45c2b6a5b159d43da708e62b25be3a4a3d5550d654b72216ade4c4bfd717" [[package]] name = "redox-rt" version = "0.1.0" -source = "git+https://gitlab.redox-os.org/redox-os/relibc#5ea71a02c7f9e436ed0dcf53bc7a5bdb311279cf" +source = "git+https://gitlab.redox-os.org/redox-os/relibc.git#040009ab3d384e75d62b7e7dd3176d910203501a" dependencies = [ "bitflags", "generic-rt", @@ -143,8 +162,9 @@ dependencies = [ [[package]] name = "redox-scheme" -version = "0.2.3" -source = "git+https://gitlab.redox-os.org/redox-os/redox-scheme.git?tag=v0.2.3#dc6a047842353ed49c1788396d9135de2c3d7382" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6469e083bf650071e9a80003c53171dfccc5ef17ae1d24972396c3c792e7c709" dependencies = [ "libredox", "redox_syscall", @@ -152,9 +172,8 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1" +version = "0.5.11" +source = "git+https://gitlab.redox-os.org/redox-os/syscall.git?branch=master#8f646a63ac9c3aa532a37ddfdc136ec19878f0d6" dependencies = [ "bitflags", ] @@ -185,6 +204,15 @@ dependencies = [ "syn", ] +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + [[package]] name = "spinning_top" version = "0.2.5" diff --git a/Cargo.toml b/Cargo.toml index f00f646349..b41643737a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,15 +10,20 @@ name = "bootstrap" crate-type = ["staticlib"] [dependencies] -hashbrown = { version = "0.14", default-features = false, features = [ +hashbrown = { version = "0.15", default-features = false, features = [ "inline-more", + "default-hasher", ] } linked_list_allocator = "0.10" +log = { version = "0.4", default-features = false } +plain = "0.2" redox-initfs = { git = "https://gitlab.redox-os.org/redox-os/redox-initfs.git", default-features = false } -redox_syscall = "0.5.4" -redox-scheme = { git = "https://gitlab.redox-os.org/redox-os/redox-scheme.git", tag = "v0.2.3" } -redox-rt = { git = "https://gitlab.redox-os.org/redox-os/relibc", default-features = false } +redox_syscall = { version = "0.5.4", default-features = false } +redox-scheme = { version = "0.6", default-features = false } +redox-rt = { git = "https://gitlab.redox-os.org/redox-os/relibc.git", default-features = false } redox-path = "0.3.1" +slab = { version = "0.4.9", default-features = false } +arrayvec = { version = "0.7.6", default-features = false } [profile.release] panic = "abort" @@ -26,3 +31,6 @@ lto = "fat" [profile.dev] panic = "abort" + +[patch.crates-io] +redox_syscall = { git = "https://gitlab.redox-os.org/redox-os/syscall.git", branch = "master" } diff --git a/README.md b/README.md index 2396cc28a1..a56ea88e81 100644 --- a/README.md +++ b/README.md @@ -17,3 +17,10 @@ To learn how to do development with this system component inside the Redox build To build this system component you need to download the Redox build system, you can learn how to do it on the [Building Redox](https://doc.redox-os.org/book/podman-build.html) page. This is necessary because they only work with cross-compilation to a Redox virtual machine, but you can do some testing from Linux. + +## Funding - Process Manager (`src/procmgr.rs`) + +The _Unix-style Signals and Process Management_ project is funded through [NGI Zero Core](https://nlnet.nl/core), a fund established by [NLnet](https://nlnet.nl) with financial support from the European Commission's [Next Generation Internet](https://ngi.eu) program. Learn more at the [NLnet project page](https://nlnet.nl/project/RedoxOS-Signals). + +[NLnet foundation logo](https://nlnet.nl) +[NGI Zero Logo](https://nlnet.nl/core) diff --git a/src/aarch64.rs b/src/aarch64.rs index 56aecc3ee5..6e1a732175 100644 --- a/src/aarch64.rs +++ b/src/aarch64.rs @@ -1,9 +1,5 @@ use core::mem; -use syscall::{ - data::Map, - flag::MapFlags, - number::SYS_FMAP, -}; +use syscall::{data::Map, flag::MapFlags, number::SYS_FMAP}; pub const USERMODE_END: usize = 0x0000_8000_0000_0000; pub const STACK_START: usize = USERMODE_END - STACK_SIZE; @@ -13,9 +9,9 @@ static MAP: Map = Map { offset: 0, size: STACK_SIZE, flags: MapFlags::PROT_READ - .union(MapFlags::PROT_WRITE) - .union(MapFlags::MAP_PRIVATE) - .union(MapFlags::MAP_FIXED_NOREPLACE), + .union(MapFlags::PROT_WRITE) + .union(MapFlags::MAP_PRIVATE) + .union(MapFlags::MAP_FIXED_NOREPLACE), address: STACK_START, // highest possible user address }; diff --git a/src/exec.rs b/src/exec.rs index 0083c906b3..589c467e72 100644 --- a/src/exec.rs +++ b/src/exec.rs @@ -6,7 +6,37 @@ use syscall::{Error, EINTR}; use redox_rt::proc::*; +struct Logger; + +impl log::Log for Logger { + fn enabled(&self, metadata: &log::Metadata) -> bool { + metadata.level() <= log::max_level() + } + fn log(&self, record: &log::Record) { + let file = record.file().unwrap_or(""); + let line = record.line().unwrap_or(0); + let level = record.level(); + let msg = record.args(); + let _ = syscall::write( + 1, + alloc::format!("[{file}:{line} {level}] {msg}\n").as_bytes(), + ); + } + fn flush(&self) {} +} + pub fn main() -> ! { + let auth = FdGuard::new( + syscall::open("/scheme/kernel.proc/authority", O_CLOEXEC) + .expect("failed to get proc authority"), + ); + let this_thr_fd = + FdGuard::new(syscall::dup(*auth, b"cur-context").expect("failed to open open_via_dup")); + let this_thr_fd = unsafe { redox_rt::initialize_freestanding(this_thr_fd) }; + + log::set_max_level(log::LevelFilter::Debug); + let _ = log::set_logger(&Logger); + let envs = { let mut env = [0_u8; 4096]; @@ -37,14 +67,29 @@ pub fn main() -> ! { (*(core::ptr::addr_of!(__initfs_header) as *const redox_initfs::types::Header)).initfs_size }; - unsafe { - // Creating a reference to NULL is UB. Mask the UB for now using black_box. - // FIXME use a raw pointer and inline asm for reading instead for the initfs header. - spawn_initfs( - core::ptr::addr_of!(__initfs_header), - initfs_length.get() as usize, - ); - } + spawn( + "initfs daemon", + &auth, + &this_thr_fd, + move |write_fd| unsafe { + // Creating a reference to NULL is UB. Mask the UB for now using black_box. + // FIXME use a raw pointer and inline asm for reading instead for the initfs header. + let initfs_start = core::ptr::addr_of!(__initfs_header); + let initfs_length = initfs_length.get() as usize; + + crate::initfs::run( + core::slice::from_raw_parts(initfs_start, initfs_length), + write_fd, + ); + }, + ); + + spawn("process manager", &auth, &this_thr_fd, |write_fd| { + crate::procmgr::run(write_fd, &auth) + }); + let [init_proc_fd, init_thr_fd] = unsafe { redox_rt::proc::make_init() }; + // from this point, this_thr_fd is no longer valid + const CWD: &[u8] = b"/scheme/initfs"; const DEFAULT_SCHEME: &[u8] = b"initfs"; let extrainfo = ExtraInfo { @@ -53,6 +98,8 @@ pub fn main() -> ! { sigprocmask: 0, sigignmask: 0, umask: redox_rt::sys::get_umask(), + thr_fd: **init_thr_fd, + proc_fd: **init_proc_fd, }; let path = "/scheme/initfs/bin/init"; @@ -66,15 +113,12 @@ pub fn main() -> ! { + 1; let image_file = FdGuard::new(syscall::open(path, O_RDONLY).expect("failed to open init")); - let open_via_dup = FdGuard::new( - syscall::open("/scheme/thisproc/current/open_via_dup", 0) - .expect("failed to open open_via_dup"), - ); let memory = FdGuard::new(syscall::open("/scheme/memory", 0).expect("failed to open memory")); fexec_impl( image_file, - open_via_dup, + init_thr_fd, + init_proc_fd, &memory, path.as_bytes(), [path], @@ -88,15 +132,15 @@ pub fn main() -> ! { unreachable!() } -unsafe fn spawn_initfs(initfs_start: *const u8, initfs_length: usize) { +pub(crate) fn spawn(name: &str, auth: &FdGuard, this_thr_fd: &FdGuard, inner: impl FnOnce(usize)) { let read = syscall::open("/scheme/pipe", O_CLOEXEC).expect("failed to open sync read pipe"); // The write pipe will not inherit O_CLOEXEC, but is closed by the daemon later. let write = syscall::dup(read, b"write").expect("failed to open sync write pipe"); - match fork_impl() { + match fork_impl(&ForkArgs::Init { this_thr_fd, auth }) { Err(err) => { - panic!("Failed to fork in order to start initfs daemon: {}", err); + panic!("Failed to fork in order to start {name}: {err}"); } // Continue serving the scheme as the child. Ok(0) => { @@ -115,8 +159,5 @@ unsafe fn spawn_initfs(initfs_start: *const u8, initfs_length: usize) { return; } } - crate::initfs::run( - core::slice::from_raw_parts(initfs_start, initfs_length), - write, - ); + inner(write); } diff --git a/src/initfs.rs b/src/initfs.rs index b1e97cdfbc..a594140c9c 100644 --- a/src/initfs.rs +++ b/src/initfs.rs @@ -9,14 +9,9 @@ use hashbrown::HashMap; use redox_initfs::{types::Timespec, InitFs, Inode, InodeDir, InodeKind, InodeStruct}; use redox_path::canonicalize_to_standard; -use redox_scheme::CallerCtx; -use redox_scheme::OpenResult; -use redox_scheme::RequestKind; -use redox_scheme::SchemeMut; +use redox_scheme::{scheme::SchemeSync, CallerCtx, OpenResult, RequestKind}; -use redox_scheme::SignalBehavior; -use redox_scheme::Socket; -use redox_scheme::V2; +use redox_scheme::{SignalBehavior, Socket}; use syscall::data::Stat; use syscall::dirent::DirEntry; use syscall::dirent::DirentBuf; @@ -93,8 +88,8 @@ fn inode_len(inode: InodeStruct<'static>) -> Result { }) } -impl SchemeMut for InitFsScheme { - fn xopen(&mut self, path: &str, flags: usize, _ctx: &CallerCtx) -> Result { +impl SchemeSync for InitFsScheme { + fn open(&mut self, path: &str, flags: usize, _ctx: &CallerCtx) -> Result { let mut components = path // trim leading and trailing slash .trim_matches('/') @@ -180,6 +175,7 @@ impl SchemeMut for InitFsScheme { buffer: &mut [u8], offset: u64, _fcntl_flags: u32, + _ctx: &CallerCtx, ) -> Result { let Ok(offset) = usize::try_from(offset) else { return Ok(0); @@ -248,19 +244,19 @@ impl SchemeMut for InitFsScheme { Ok(buf) } - fn fsize(&mut self, id: usize) -> Result { + fn fsize(&mut self, id: usize, _ctx: &CallerCtx) -> Result { let handle = self.handles.get_mut(&id).ok_or(Error::new(EBADF))?; Ok(inode_len(Self::get_inode(&self.fs, handle.inode)?)? as u64) } - fn fcntl(&mut self, id: usize, _cmd: usize, _arg: usize) -> Result { + fn fcntl(&mut self, id: usize, _cmd: usize, _arg: usize, _ctx: &CallerCtx) -> Result { let _handle = self.handles.get(&id).ok_or(Error::new(EBADF))?; Ok(0) } - fn fpath(&mut self, id: usize, buf: &mut [u8]) -> Result { + fn fpath(&mut self, id: usize, buf: &mut [u8], _ctx: &CallerCtx) -> Result { let handle = self.handles.get(&id).ok_or(Error::new(EBADF))?; // TODO: Copy scheme part in kernel @@ -275,7 +271,7 @@ impl SchemeMut for InitFsScheme { Ok(scheme_bytes + path_bytes) } - fn fstat(&mut self, id: usize, stat: &mut Stat) -> Result { + fn fstat(&mut self, id: usize, stat: &mut Stat, _ctx: &CallerCtx) -> Result<()> { let handle = self.handles.get(&id).ok_or(Error::new(EBADF))?; let Timespec { sec, nsec } = self.fs.image_creation_time(); @@ -297,52 +293,51 @@ impl SchemeMut for InitFsScheme { stat.st_mtime = sec.get(); stat.st_mtime_nsec = nsec.get(); - Ok(0) + Ok(()) } - fn fsync(&mut self, id: usize) -> Result { + fn fsync(&mut self, id: usize, _ctx: &CallerCtx) -> Result<()> { if !self.handles.contains_key(&id) { return Err(Error::new(EBADF)); } - Ok(0) - } - - fn close(&mut self, id: usize) -> Result { - let _ = self.handles.remove(&id).ok_or(Error::new(EBADF))?; - Ok(0) + Ok(()) } } pub fn run(bytes: &'static [u8], sync_pipe: usize) -> ! { let mut scheme = InitFsScheme::new(bytes); - let socket = Socket::::create("initfs").expect("failed to open initfs scheme socket"); + let socket = Socket::create("initfs").expect("failed to open initfs scheme socket"); let _ = syscall::write(sync_pipe, &[0]); let _ = syscall::close(sync_pipe); loop { - let RequestKind::Call(req) = (match socket + let Some(req) = socket .next_request(SignalBehavior::Restart) .expect("bootstrap: failed to read scheme request from kernel") - { - Some(req) => req.kind(), - None => break, - }) else { - continue; - }; - let resp = req.handle_scheme_mut(&mut scheme); - - if !socket - .write_response(resp, SignalBehavior::Restart) - .expect("bootstrap: failed to write scheme response to kernel") - { + else { break; + }; + match req.kind() { + RequestKind::Call(req) => { + let resp = req.handle_sync(&mut scheme); + + if !socket + .write_response(resp, SignalBehavior::Restart) + .expect("bootstrap: failed to write scheme response to kernel") + { + break; + } + } + RequestKind::OnClose { id } => { + scheme.handles.remove(&id); + } + _ => (), } } - syscall::exit(0).expect("initfs: failed to exit"); unreachable!() } diff --git a/src/lib.rs b/src/lib.rs index 7fc400fe98..6ed7505f9c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,6 @@ #![no_std] #![allow(internal_features)] -#![feature(asm_const, core_intrinsics, str_from_raw_parts, iter_intersperse)] +#![feature(core_intrinsics, let_chains, iter_intersperse, str_from_raw_parts)] #[cfg(target_arch = "aarch64")] #[path = "aarch64.rs"] @@ -20,10 +20,13 @@ pub mod arch; pub mod exec; pub mod initfs; +pub mod procmgr; pub mod start; extern crate alloc; +use core::cell::UnsafeCell; + use syscall::data::Map; use syscall::flag::MapFlags; @@ -51,15 +54,26 @@ struct Allocator; #[global_allocator] static ALLOCATOR: Allocator = Allocator; -static mut HEAP: Option = None; -static mut HEAP_TOP: usize = HEAP_OFF + SIZE; +struct AllocStateInner { + heap: Option, + heap_top: usize, +} +struct AllocState(UnsafeCell); +unsafe impl Send for AllocState {} +unsafe impl Sync for AllocState {} +static ALLOC_STATE: AllocState = AllocState(UnsafeCell::new(AllocStateInner { + heap: None, + heap_top: HEAP_OFF + SIZE, +})); + const SIZE: usize = 1024 * 1024; const HEAP_INCREASE_BY: usize = SIZE; unsafe impl alloc::alloc::GlobalAlloc for Allocator { unsafe fn alloc(&self, layout: core::alloc::Layout) -> *mut u8 { - let heap = HEAP.get_or_insert_with(|| { - HEAP_TOP = HEAP_OFF + SIZE; + let state = &mut (*ALLOC_STATE.0.get()); + let heap = state.heap.get_or_insert_with(|| { + state.heap_top = HEAP_OFF + SIZE; let _ = syscall::fmap( !0, &Map { @@ -88,7 +102,7 @@ unsafe impl alloc::alloc::GlobalAlloc for Allocator { &Map { offset: 0, size: HEAP_INCREASE_BY, - address: HEAP_TOP, + address: state.heap_top, flags: MapFlags::PROT_WRITE | MapFlags::PROT_READ | MapFlags::MAP_PRIVATE @@ -97,14 +111,16 @@ unsafe impl alloc::alloc::GlobalAlloc for Allocator { ) .expect("failed to extend heap"); heap.extend(HEAP_INCREASE_BY); - HEAP_TOP += HEAP_INCREASE_BY; + state.heap_top += HEAP_INCREASE_BY; return self.alloc(layout); } } } unsafe fn dealloc(&self, ptr: *mut u8, layout: core::alloc::Layout) { - HEAP.as_mut() + (&mut *ALLOC_STATE.0.get()) + .heap + .as_mut() .unwrap() .deallocate(core::ptr::NonNull::new(ptr).unwrap(), layout) } diff --git a/src/procmgr.rs b/src/procmgr.rs new file mode 100644 index 0000000000..299307e446 --- /dev/null +++ b/src/procmgr.rs @@ -0,0 +1,2372 @@ +use core::cell::RefCell; +use core::cmp; +use core::hash::BuildHasherDefault; +use core::mem::size_of; +use core::num::{NonZeroU8, NonZeroUsize}; +use core::ops::Deref; +use core::ptr::NonNull; +use core::str::FromStr; +use core::sync::atomic::Ordering; +use core::task::Poll::*; +use core::task::{Context, Poll}; + +use alloc::collections::btree_map::BTreeMap; +use alloc::collections::VecDeque; +use alloc::rc::{Rc, Weak}; +use alloc::vec; +use alloc::vec::Vec; + +use arrayvec::ArrayString; +use hashbrown::hash_map::{Entry, OccupiedEntry, VacantEntry}; +use hashbrown::{DefaultHashBuilder, HashMap, HashSet}; + +use redox_rt::proc::FdGuard; +use redox_rt::protocol::{ + ProcCall, ProcKillTarget, ProcMeta, RtSigInfo, ThreadCall, WaitFlags, SIGCHLD, SIGCONT, + SIGKILL, SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOU, +}; +use redox_scheme::scheme::{IntoTag, Op, OpCall}; +use redox_scheme::{ + CallerCtx, Id, OpenResult, Request, RequestKind, Response, SendFdRequest, SignalBehavior, + Socket, Tag, +}; +use slab::Slab; +use syscall::schemev2::NewFdFlags; +use syscall::{ + sig_bit, ContextStatus, ContextVerb, CtxtStsBuf, Error, Event, EventFlags, FobtainFdFlags, + MapFlags, ProcSchemeAttrs, Result, SenderInfo, SetSighandlerData, SigProcControl, Sigcontrol, + EAGAIN, EBADF, EBADFD, ECHILD, EEXIST, EINTR, EINVAL, EIO, ENOENT, ENOSYS, EOPNOTSUPP, + EOWNERDEAD, EPERM, ERESTART, ESRCH, EWOULDBLOCK, O_CLOEXEC, O_CREAT, PAGE_SIZE, +}; + +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +enum VirtualId { + KernelId(Id), + // TODO: slab or something for better ID reuse + InternalId(u64), +} + +pub fn run(write_fd: usize, auth: &FdGuard) { + let socket = Socket::nonblock("proc").expect("failed to open proc scheme socket"); + + // TODO? + let socket_ident = socket.inner().raw(); + + let queue = RawEventQueue::new().expect("failed to create event queue"); + + queue + .subscribe(socket.inner().raw(), socket_ident, EventFlags::EVENT_READ) + .expect("failed to listen to scheme socket events"); + + let mut scheme = ProcScheme::new(auth, &queue); + + log::info!("process manager started"); + let _ = syscall::write(write_fd, &[0]); + let _ = syscall::close(write_fd); + + let mut states = HashMap::::new(); + let mut awoken = VecDeque::::new(); + let mut new_awoken = VecDeque::new(); + + 'outer: loop { + log::trace!("AWOKEN {awoken:#?}"); + while !awoken.is_empty() || !new_awoken.is_empty() { + awoken.append(&mut new_awoken); + for awoken in awoken.drain(..) { + //log::trace!("ALL STATES {states:#?}, AWOKEN {awoken:#?}"); + let Entry::Occupied(state) = states.entry(awoken) else { + continue; + }; + match scheme.work_on(state, &mut new_awoken) { + Ready(resp) => { + loop { + match socket.write_response(resp, SignalBehavior::Interrupt) { + Ok(false) => break 'outer, + Ok(_) => break, + Err(err) if err.errno == EINTR => continue, + Err(err) => { + panic!("bootstrap: failed to write scheme response to kernel: {err}") + } + } + } + } + Pending => continue, + } + } + } + // TODO: multiple events? + let event = queue.next_event().expect("failed to get next event"); + + if event.data == socket_ident { + 'reqs: loop { + let req = loop { + match socket.next_request(SignalBehavior::Interrupt) { + Ok(None) => break 'outer, + Ok(Some(req)) => break req, + Err(e) if e.errno == EINTR => continue, + // spurious event + Err(e) if e.errno == EWOULDBLOCK || e.errno == EAGAIN => break 'reqs, + Err(other) => { + panic!("bootstrap: failed to read scheme request from kernel: {other}") + } + } + }; + log::trace!("REQ{req:#?}"); + let Ready(resp) = + handle_scheme(req, &socket, &mut scheme, &mut states, &mut awoken) + else { + continue 'reqs; + }; + loop { + match socket.write_response(resp, SignalBehavior::Interrupt) { + Ok(false) => break 'outer, + Ok(_) => break, + Err(err) if err.errno == EINTR => continue, + Err(err) => { + panic!("bootstrap: failed to write scheme response to kernel: {err}") + } + } + } + } + } else if let Some(thread) = scheme.thread_lookup.get(&event.data) { + let Some(thread_rc) = thread.upgrade() else { + log::trace!("DEAD THREAD EVENT FROM {}", event.data,); + continue; + }; + let thread = thread_rc.borrow(); + let pid = thread.pid; + let Some(proc_rc) = scheme.processes.get(&pid) else { + // TODO(err)? + continue; + }; + let mut proc = proc_rc.borrow_mut(); + log::trace!("THREAD EVENT FROM {}, {}", event.data, thread.pid.0); + let mut sts_buf = CtxtStsBuf::default(); + let _ = syscall::read(*thread.status_hndl, &mut sts_buf).unwrap(); + + let status = if sts_buf.status == ContextStatus::Dead as usize { + // dont-care, already called explicit exit() + 0 + } else if sts_buf.status == ContextStatus::ForceKilled as usize { + // TODO: "killed by SIGKILL" + 1 + } else if sts_buf.status == ContextStatus::UnhandledExcp as usize { + // TODO: translate arch-specific exception kind + // into signal (SIGSEGV, SIGBUS, SIGILL, SIGFPE) + 1 + } else { + // spurious event + continue; + }; + + log::trace!("--THREAD DIED {}, {}", event.data, thread.pid.0); + + if let Err(err) = scheme.queue.unsubscribe(event.data, event.data) { + log::error!("failed to unsubscribe from fd {}", event.data); + } + scheme.thread_lookup.remove(&event.data); + proc.threads.retain(|rc| !Rc::ptr_eq(rc, &thread_rc)); + + if matches!(proc.status, ProcessStatus::Exiting { .. }) { + log::trace!("WAKING UP {}", proc.awaiting_threads_term.len(),); + awoken.extend(proc.awaiting_threads_term.drain(..)); // TODO(opt) + } else if proc.threads.is_empty() { + let internal_id = scheme.next_internal_id; + scheme.next_internal_id += 1; + let Entry::Vacant(entry) = states.entry(VirtualId::InternalId(internal_id)) else { + log::error!("internal ID reuse!"); + continue; + }; + drop(thread); + drop(proc); + let Pending = scheme.on_exit_start(pid, status, entry, &mut awoken, None) else { + unreachable!("not possible with tag=None"); + }; + } + } else { + log::warn!("TODO: UNKNOWN EVENT {event:?}"); + } + } + + unreachable!() +} +fn handle_scheme<'a>( + req: Request, + socket: &'a Socket, + scheme: &mut ProcScheme<'a>, + states: &mut HashMap, + awoken: &mut VecDeque, +) -> Poll { + match req.kind() { + RequestKind::Call(req) => { + let caller = req.caller(); + let req_id = VirtualId::KernelId(req.request_id()); + let op = match req.op() { + Ok(op) => op, + Err(req) => return Response::ready_err(ENOSYS, req), + }; + match op { + Op::Open(op) => Ready(Response::open_dup_like( + scheme.on_open(op.path(), op.flags, &caller), + op, + )), + Op::Dup(op) => Ready(Response::open_dup_like(scheme.on_dup(op.fd, op.buf()), op)), + Op::Read(mut op) => Ready(Response::new( + scheme.on_read(op.fd, op.offset, op.buf()), + op, + )), + Op::Call(op) => scheme.on_call( + { + // TODO: cleanup + states.remove(&req_id); + if let Entry::Vacant(entry) = states.entry(req_id) { + entry + } else { + unreachable!() + } + }, + op, + awoken, + ), + Op::Fsize { req, fd } => { + if let Handle::Ps(ref b) = &scheme.handles[fd] { + Response::ready_ok(b.len(), req) + } else { + Response::ready_err(EOPNOTSUPP, req) + } + } + Op::Fstat(mut op) => { + if let Handle::Ps(ref b) = &scheme.handles[op.fd] { + op.buf().st_size = b.len() as _; + op.buf().st_mode = syscall::MODE_FILE | 0o444; + Response::ready_ok(0, op) + } else { + Response::ready_err(EOPNOTSUPP, op) + } + } + _ => { + log::trace!("UNKNOWN: {op:?}"); + Response::ready_err(ENOSYS, op) + } + } + } + RequestKind::SendFd(req) => Ready(scheme.on_sendfd(socket, req)), + + // ignore + _ => Pending, + } +} +#[derive(Debug)] +enum PendingState { + AwaitingStatusChange { + waiter: ProcessId, + target: WaitpidTarget, + flags: WaitFlags, + op: OpCall, + }, + AwaitingThreadsTermination(ProcessId, Option), + Placeholder, +} +/*impl IntoTag for PendingState { + fn into_tag(self) -> Tag { + match self { + Self::AwaitingThreadsTermination(_, tag) => tag, + Self::AwaitingStatusChange { op, .. } => op.into_tag(), + Self::Placeholder => unreachable!(), + } + } +}*/ + +#[derive(Debug)] +pub struct Page { + ptr: NonNull, + off: u16, +} +impl Page { + pub fn map(fd: &FdGuard, req_offset: usize, displacement: u16) -> Result { + Ok(Self { + off: displacement, + ptr: NonNull::new(unsafe { + syscall::fmap( + **fd, + &syscall::Map { + offset: req_offset, + size: PAGE_SIZE, + flags: MapFlags::PROT_READ | MapFlags::PROT_WRITE | MapFlags::MAP_SHARED, + address: 0, + }, + )? as *mut T + }) + .unwrap(), + }) + } +} +impl Deref for Page { + type Target = T; + + fn deref(&self) -> &T { + unsafe { &*self.ptr.as_ptr().byte_add(self.off.into()) } + } +} +impl Drop for Page { + fn drop(&mut self) { + unsafe { + let _ = syscall::funmap(self.ptr.as_ptr() as usize, PAGE_SIZE); + } + } +} + +const NAME_CAPAC: usize = 32; + +#[derive(Debug)] +struct Process { + threads: Vec>>, + ppid: ProcessId, + pgid: ProcessId, + sid: ProcessId, + name: ArrayString, + + ruid: u32, + euid: u32, + suid: u32, + rgid: u32, + egid: u32, + sgid: u32, + rns: u32, + ens: u32, + + status: ProcessStatus, + + awaiting_threads_term: Vec, + + waitpid: BTreeMap, + waitpid_waiting: VecDeque, + + sig_pctl: Option>, + rtqs: Vec>, +} +#[derive(Copy, Clone, Debug)] +struct WaitpidKey { + pid: Option, + pgid: Option, +} + +// TODO: Is this valid? (transitive?) +impl Ord for WaitpidKey { + fn cmp(&self, other: &WaitpidKey) -> cmp::Ordering { + // If both have pid set, compare that + if let Some(s_pid) = self.pid { + if let Some(o_pid) = other.pid { + return s_pid.cmp(&o_pid); + } + } + + // If both have pgid set, compare that + if let Some(s_pgid) = self.pgid { + if let Some(o_pgid) = other.pgid { + return s_pgid.cmp(&o_pgid); + } + } + + // If either has pid set, it is greater + if self.pid.is_some() { + return cmp::Ordering::Greater; + } + + if other.pid.is_some() { + return cmp::Ordering::Less; + } + + // If either has pgid set, it is greater + if self.pgid.is_some() { + return cmp::Ordering::Greater; + } + + if other.pgid.is_some() { + return cmp::Ordering::Less; + } + + // If all pid and pgid are None, they are equal + cmp::Ordering::Equal + } +} + +impl PartialOrd for WaitpidKey { + fn partial_cmp(&self, other: &WaitpidKey) -> Option { + Some(self.cmp(other)) + } +} + +impl PartialEq for WaitpidKey { + fn eq(&self, other: &WaitpidKey) -> bool { + self.cmp(other) == cmp::Ordering::Equal + } +} + +impl Eq for WaitpidKey {} +#[derive(Debug, Clone, Copy)] +enum ProcessStatus { + PossiblyRunnable, + Stopped(usize), + Exiting { + signal: Option, + status: u8, + }, + Exited { + signal: Option, + status: u8, + }, +} +#[derive(Debug)] +struct Thread { + fd: FdGuard, + status_hndl: FdGuard, + pid: ProcessId, + sig_ctrl: Option>, +} +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +struct ProcessId(usize); + +const INIT_PID: ProcessId = ProcessId(1); + +struct ProcScheme<'a> { + processes: HashMap>, DefaultHashBuilder>, + groups: HashMap>>, + sessions: HashSet, + handles: Slab, + + thread_lookup: HashMap>>, + + next_internal_id: u64, + + init_claimed: bool, + next_id: ProcessId, + + queue: &'a RawEventQueue, + auth: &'a FdGuard, +} +#[derive(Debug, Default)] +struct Pgrp { + processes: Vec>>, +} +#[derive(Clone, Copy, Debug)] +enum WaitpidStatus { + Continued, + Stopped { + signal: NonZeroU8, + }, + Terminated { + signal: Option, + status: u8, + }, +} + +#[derive(Debug)] +enum Handle { + Init, + Proc(ProcessId), + + // Needs to be weak so the thread is owned only by the process. Otherwise there would be a + // cyclic reference since the underlying context's file table almost certainly contains the + // thread fd itself, linked to this handle. + Thread(Weak>), + + // TODO: stateless API, perhaps using intermediate daemon for providing a file-like API + Ps(Vec), +} + +#[derive(Clone, Copy, Debug)] +enum WaitpidTarget { + SingleProc(ProcessId), + ProcGroup(ProcessId), + AnyChild, + AnyGroupMember, +} +// TODO(feat): Add 'syscall' backend for redox-event so it can act both as library-ABI frontend and +// backend +struct RawEventQueue(FdGuard); +impl RawEventQueue { + pub fn new() -> Result { + syscall::open("/scheme/event", O_CREAT) + .map(FdGuard::new) + .map(Self) + } + pub fn subscribe(&self, fd: usize, ident: usize, flags: EventFlags) -> Result<()> { + let _ = syscall::write( + *self.0, + &Event { + id: fd, + data: ident, + flags, + }, + )?; + Ok(()) + } + pub fn unsubscribe(&self, fd: usize, ident: usize) -> Result<()> { + self.subscribe(fd, ident, EventFlags::empty()) + } + pub fn next_event(&self) -> Result { + let mut event = Event::default(); + let read = syscall::read(*self.0, &mut event)?; + assert_eq!( + read, + size_of::(), + "event queue EOF currently undefined" + ); + Ok(event) + } +} + +impl<'a> ProcScheme<'a> { + pub fn new(auth: &'a FdGuard, queue: &'a RawEventQueue) -> ProcScheme<'a> { + ProcScheme { + processes: HashMap::new(), + groups: HashMap::new(), + sessions: HashSet::new(), + thread_lookup: HashMap::new(), + handles: Slab::new(), + init_claimed: false, + next_id: ProcessId(2), + next_internal_id: 1, + queue, + auth, + } + } + fn new_id(&mut self) -> ProcessId { + let id = self.next_id; + self.next_id.0 += 1; + id + } + fn on_sendfd(&mut self, socket: &Socket, req: SendFdRequest) -> Response { + match self.handles[req.id()] { + ref mut st @ Handle::Init => { + let mut fd_out = usize::MAX; + if let Err(e) = req.obtain_fd(socket, FobtainFdFlags::empty(), Err(&mut fd_out)) { + return Response::new(Err(e), req); + }; + let fd = FdGuard::new(fd_out); + + // TODO: Use global thread id etc. rather than reusing fd for identifier? + self.queue + .subscribe(*fd, fd_out, EventFlags::EVENT_READ) + .expect("TODO"); + let status_hndl = FdGuard::new( + syscall::dup( + *fd, + alloc::format!("auth-{}-status", **self.auth).as_bytes(), + ) + .expect("TODO"), + ); + + let thread = Rc::new(RefCell::new(Thread { + fd, + status_hndl, + pid: INIT_PID, + sig_ctrl: None, + })); + let thread_weak = Rc::downgrade(&thread); + let process = Rc::new(RefCell::new(Process { + threads: vec![thread], + ppid: INIT_PID, + sid: INIT_PID, + pgid: INIT_PID, + ruid: 0, + euid: 0, + suid: 0, + rgid: 0, + egid: 0, + sgid: 0, + rns: 1, + ens: 1, + name: ArrayString::<32>::from_str("[init]").unwrap(), + + status: ProcessStatus::PossiblyRunnable, + awaiting_threads_term: Vec::new(), + waitpid: BTreeMap::new(), + waitpid_waiting: VecDeque::new(), + + sig_pctl: None, + rtqs: Vec::new(), + })); + self.groups.insert( + INIT_PID, + Rc::new(RefCell::new(Pgrp { + processes: vec![Rc::downgrade(&process)], + })), + ); + self.processes.insert(INIT_PID, process); + self.sessions.insert(INIT_PID); + + self.thread_lookup.insert(fd_out, thread_weak); + + *st = Handle::Proc(INIT_PID); + Response::ok(0, req) + } + _ => Response::err(EBADF, req), + } + } + fn fork(&mut self, parent_pid: ProcessId) -> Result { + let child_pid = self.new_id(); + + let proc_guard = self.processes.get(&parent_pid).ok_or(Error::new(EBADFD))?; + + let Process { + pgid, + sid, + ruid, + euid, + suid, + rgid, + egid, + sgid, + ens, + rns, + name, + .. + } = *proc_guard.borrow(); + + let new_ctxt_fd = FdGuard::new(syscall::dup(**self.auth, b"new-context")?); + let status_fd = FdGuard::new(syscall::dup( + *new_ctxt_fd, + alloc::format!("auth-{}-status", **self.auth).as_bytes(), + )?); + + self.queue + .subscribe(*new_ctxt_fd, *new_ctxt_fd, EventFlags::EVENT_READ) + .expect("TODO"); + + let thread_ident = *new_ctxt_fd; + let thread = Rc::new(RefCell::new(Thread { + fd: new_ctxt_fd, + status_hndl: status_fd, + pid: child_pid, + sig_ctrl: None, // TODO + })); + let thread_weak = Rc::downgrade(&thread); + let new_process = Rc::new(RefCell::new(Process { + threads: vec![thread], + ppid: parent_pid, + pgid, + sid, + ruid, + euid, + suid, + rgid, + egid, + sgid, + rns, + ens, + name, + + status: ProcessStatus::PossiblyRunnable, + awaiting_threads_term: Vec::new(), + + waitpid: BTreeMap::new(), + waitpid_waiting: VecDeque::new(), + + sig_pctl: None, // TODO + rtqs: Vec::new(), + })); + if let Err(err) = new_process + .borrow_mut() + .sync_kernel_attrs(child_pid, self.auth) + { + log::warn!("Failed to set kernel attrs when forking: {err}"); + } + + if let Some(group) = self.groups.get(&pgid) { + group + .borrow_mut() + .processes + .push(Rc::downgrade(&new_process)); + } + + self.processes.insert(child_pid, new_process); + self.thread_lookup.insert(thread_ident, thread_weak); + Ok(child_pid) + } + fn new_thread(&mut self, pid: ProcessId) -> Result>> { + // TODO: deduplicate code with fork + let proc_rc = self.processes.get_mut(&pid).ok_or(Error::new(EBADFD))?; + let mut proc = proc_rc.borrow_mut(); + + let ctxt_fd = FdGuard::new(syscall::dup(**self.auth, b"new-context")?); + + // TODO: sync_kernel_attrs? + let attr_fd = FdGuard::new(syscall::dup( + *ctxt_fd, + alloc::format!("auth-{}-attrs", **self.auth).as_bytes(), + )?); + let _ = syscall::write( + *attr_fd, + &ProcSchemeAttrs { + pid: pid.0 as u32, + euid: proc.euid, + egid: proc.egid, + ens: proc.ens, + debug_name: arraystring_to_bytes(proc.name), + }, + )?; + + let status_hndl = FdGuard::new(syscall::dup( + *ctxt_fd, + alloc::format!("auth-{}-status", **self.auth).as_bytes(), + )?); + + self.queue + .subscribe(*ctxt_fd, *ctxt_fd, EventFlags::EVENT_READ) + .expect("TODO"); + + let ident = *ctxt_fd; + let thread = Rc::new(RefCell::new(Thread { + fd: ctxt_fd, + status_hndl, + pid, + sig_ctrl: None, + })); + let thread_weak = Rc::downgrade(&thread); + proc.threads.push(Rc::clone(&thread)); + self.thread_lookup.insert(ident, thread_weak); + Ok(thread) + } + fn on_open(&mut self, path: &str, flags: usize, ctx: &CallerCtx) -> Result { + let path = path.trim_start_matches('/'); + Ok(match path { + "init" => { + if core::mem::replace(&mut self.init_claimed, true) { + return Err(Error::new(EEXIST)); + } + OpenResult::ThisScheme { + number: self.handles.insert(Handle::Init), + flags: NewFdFlags::empty(), + } + } + "ps" => { + let data = self.ps_data(ctx)?; + OpenResult::ThisScheme { + number: self.handles.insert(Handle::Ps(data)), + flags: NewFdFlags::POSITIONED, + } + } + + _ => return Err(Error::new(ENOENT)), + }) + } + fn on_read(&mut self, id: usize, offset: u64, buf: &mut [u8]) -> Result { + match self.handles[id] { + Handle::Proc(pid) => { + let proc_rc = self.processes.get(&pid).ok_or(Error::new(EBADFD))?; + let process = proc_rc.borrow(); + let metadata = ProcMeta { + pid: pid.0 as u32, + pgid: process.pgid.0 as u32, + ppid: process.ppid.0 as u32, + ruid: process.ruid, + euid: process.euid, + suid: process.suid, + rgid: process.rgid, + egid: process.egid, + sgid: process.sgid, + ens: process.ens, + rns: process.rns, + }; + *buf.get_mut(..size_of::()) + .and_then(|b| plain::from_mut_bytes(b).ok()) + .ok_or(Error::new(EINVAL))? = metadata; + Ok(size_of::()) + } + Handle::Ps(ref src_buf) => { + let src_buf = usize::try_from(offset) + .ok() + .and_then(|o| src_buf.get(o..)) + .unwrap_or(&[]); + let len = src_buf.len().min(buf.len()); + buf[..len].copy_from_slice(&src_buf[..len]); + Ok(len) + } + Handle::Init | Handle::Thread(_) => return Err(Error::new(EBADF)), + } + } + fn on_dup(&mut self, old_id: usize, buf: &[u8]) -> Result { + log::trace!("Dup request"); + match self.handles[old_id] { + Handle::Proc(pid) => match buf { + b"fork" => { + log::trace!("Forking {pid:?}"); + let child_pid = self.fork(pid)?; + Ok(OpenResult::ThisScheme { + number: self.handles.insert(Handle::Proc(child_pid)), + flags: NewFdFlags::empty(), + }) + } + b"new-thread" => { + let thread = self.new_thread(pid)?; + Ok(OpenResult::ThisScheme { + number: self.handles.insert(Handle::Thread(Rc::downgrade(&thread))), + flags: NewFdFlags::empty(), + }) + } + w if w.starts_with(b"thread-") => { + let idx = core::str::from_utf8(&w["thread-".len()..]) + .ok() + .and_then(|s| s.parse::().ok()) + .ok_or(Error::new(EINVAL))?; + let process = self.processes.get(&pid).ok_or(Error::new(EBADFD))?.borrow(); + let thread = Rc::downgrade(process.threads.get(idx).ok_or(Error::new(ENOENT))?); + + return Ok(OpenResult::ThisScheme { + number: self.handles.insert(Handle::Thread(thread)), + flags: NewFdFlags::empty(), + }); + } + _ => return Err(Error::new(EINVAL)), + }, + Handle::Thread(ref thread_weak) => { + let thread_rc = thread_weak.upgrade().ok_or(Error::new(EOWNERDEAD))?; + let thread = thread_rc.borrow(); + + // By forwarding all dup calls to the kernel, this fd is now effectively the same + // as the underlying fd since that fd can't do anything itself. + Ok(OpenResult::OtherScheme { + fd: syscall::dup(*thread.fd, buf)?, + }) + } + Handle::Init | Handle::Ps(_) => Err(Error::new(EBADF)), + } + } + fn on_call( + &mut self, + state: VacantEntry, + mut op: OpCall, + awoken: &mut VecDeque, + ) -> Poll { + let id = op.fd; + let (payload, metadata) = op.payload_and_metadata(); + match self.handles[id] { + Handle::Init => Response::ready_err(EBADF, op), + Handle::Thread(ref thr_weak) => { + let Some(thr) = thr_weak.upgrade() else { + return Response::ready_err(EOWNERDEAD, op); + }; + let Some(verb) = ThreadCall::try_from_raw(metadata[0] as usize) else { + return Response::ready_err(EINVAL, op); + }; + match verb { + ThreadCall::SyncSigTctl => Ready(Response::new( + Self::on_sync_sigtctl(&mut *thr.borrow_mut()).map(|()| 0), + op, + )), + ThreadCall::SignalThread => Ready(Response::new( + self.on_kill_thread(&thr, metadata[1] as u8, awoken) + .map(|()| 0), + op, + )), + } + } + Handle::Proc(fd_pid) => { + let Some(verb) = ProcCall::try_from_raw(metadata[0] as usize) else { + return Response::ready_err(EINVAL, op); + }; + fn cvt_u32(u: u32) -> Option { + if u == u32::MAX { + None + } else { + Some(u) + } + } + match verb { + ProcCall::Setrens => Ready(Response::new( + self.on_setrens( + fd_pid, + cvt_u32(metadata[1] as u32), + cvt_u32(metadata[2] as u32), + ) + .map(|()| 0), + op, + )), + ProcCall::Exit => self.on_exit_start( + fd_pid, + metadata[1] as u16, + state, + awoken, + Some(op.into_tag()), + ), + ProcCall::Waitpid | ProcCall::Waitpgid => { + let req_pid = ProcessId(metadata[1] as usize); + let target = match (verb, metadata[1] == 0) { + (ProcCall::Waitpid, true) => WaitpidTarget::AnyChild, + (ProcCall::Waitpid, false) => WaitpidTarget::SingleProc(req_pid), + (ProcCall::Waitpgid, true) => WaitpidTarget::AnyGroupMember, + (ProcCall::Waitpgid, false) => WaitpidTarget::ProcGroup(req_pid), + _ => unreachable!(), + }; + let flags = match WaitFlags::from_bits(metadata[2] as usize) { + Some(fl) => fl, + None => { + return Response::ready_err(EINVAL, op); + } + }; + let state = state.insert_entry(PendingState::AwaitingStatusChange { + waiter: fd_pid, + target, + flags, + op, + }); + self.work_on(state, awoken) + } + ProcCall::Setpgid => { + let target_pid = NonZeroUsize::new(metadata[1] as usize) + .map_or(fd_pid, |n| ProcessId(n.get())); + + let new_pgid = NonZeroUsize::new(metadata[2] as usize) + .map_or(target_pid, |n| ProcessId(n.get())); + if new_pgid.0 == usize::wrapping_neg(1) { + Ready(Response::new( + self.on_getpgid(fd_pid, target_pid).map(|ProcessId(p)| p), + op, + )) + } else { + Ready(Response::new( + self.on_setpgid(fd_pid, target_pid, new_pgid, awoken) + .map(|()| 0), + op, + )) + } + } + ProcCall::Getsid => { + let req_pid = NonZeroUsize::new(metadata[1] as usize) + .map_or(fd_pid, |n| ProcessId(n.get())); + Ready(Response::new( + self.on_getsid(fd_pid, req_pid).map(|ProcessId(s)| s), + op, + )) + } + ProcCall::Getppid => Ready(Response::new( + self.on_getppid(fd_pid).map(|ProcessId(p)| p), + op, + )), + ProcCall::Setsid => Ready(Response::new( + self.on_setsid(fd_pid, awoken).map(|()| 0), + op, + )), + ProcCall::SetResugid => Ready(Response::new( + self.on_setresugid(fd_pid, payload).map(|()| 0), + op, + )), + ProcCall::Kill | ProcCall::Sigq => { + let (payload, metadata) = op.payload_and_metadata(); + let target = ProcKillTarget::from_raw(metadata[1] as usize); + let Some(signal) = u8::try_from(metadata[2]).ok().filter(|s| *s <= 64) + else { + return Response::ready_err(EINVAL, op); + }; + let mode = match verb { + ProcCall::Kill => KillMode::Idempotent, + ProcCall::Sigq => KillMode::Queued({ + let mut buf = [0_u8; size_of::()]; + if payload.len() != size_of::() { + return Response::ready_err(EINVAL, op); + } + buf.copy_from_slice(payload); + *plain::from_bytes(&buf).unwrap() + }), + _ => unreachable!(), + }; + + Ready(Response::new( + self.on_kill(fd_pid, target, signal, mode, awoken) + .map(|()| 0), + op, + )) + } + ProcCall::SyncSigPctl => { + Ready(Response::new(self.on_sync_sigpctl(fd_pid).map(|()| 0), op)) + } + ProcCall::Sigdeq => Ready(Response::new( + self.on_sigdeq(fd_pid, payload).map(|()| 0), + op, + )), + ProcCall::Rename => Ready(Response::new( + self.on_proc_rename(fd_pid, payload).map(|()| 0), + op, + )), + } + } + Handle::Ps(_) => Response::ready_err(EOPNOTSUPP, op), + } + } + fn on_getpgid(&mut self, caller_pid: ProcessId, target_pid: ProcessId) -> Result { + log::trace!("GETPGID from {caller_pid:?} target {target_pid:?}"); + let caller_proc = self + .processes + .get(&caller_pid) + .ok_or(Error::new(ESRCH))? + .borrow(); + let target_proc = self + .processes + .get(&target_pid) + .ok_or(Error::new(ESRCH))? + .borrow(); + + // Although not required, POSIX allows the impl to forbid getting the pgid of processes + // outside of the caller's session. + if caller_proc.sid != target_proc.sid && caller_proc.euid != 0 { + return Err(Error::new(EPERM)); + } + + Ok(target_proc.pgid) + } + fn on_setsid(&mut self, caller_pid: ProcessId, awoken: &mut VecDeque) -> Result<()> { + // TODO: more efficient? + // POSIX: any other process's pgid matches the caller pid + if self + .processes + .values() + .any(|p| p.borrow().pgid == caller_pid) + { + return Err(Error::new(EPERM)); + } + + let caller_proc_rc = self.processes.get(&caller_pid).ok_or(Error::new(ESRCH))?; + let mut caller_proc = caller_proc_rc.borrow_mut(); + let mut parent = (caller_proc.ppid != caller_pid) + .then(|| { + self.processes + .get(&caller_proc.ppid) + .map(|p| p.borrow_mut()) + }) + .ok_or(Error::new(ESRCH))?; + + // POSIX: already a process group leader + if caller_proc.pgid == caller_pid { + return Err(Error::new(EPERM)); + } + + Self::set_pgid( + caller_proc_rc, + &mut *caller_proc, + parent.as_deref_mut(), + &mut self.groups, + caller_pid, + awoken, + )?; + caller_proc.sid = caller_pid; + + // TODO: Remove controlling terminal + Ok(()) + } + fn on_getppid(&mut self, caller_pid: ProcessId) -> Result { + log::trace!("GETPPID {caller_pid:?}"); + let ppid = self + .processes + .get(&caller_pid) + .ok_or(Error::new(ESRCH))? + .borrow() + .ppid; + log::trace!("GETPPID {caller_pid:?} -> {ppid:?}"); + Ok(ppid) + } + fn on_getsid(&mut self, caller_pid: ProcessId, req_pid: ProcessId) -> Result { + let caller_proc = self + .processes + .get(&caller_pid) + .ok_or(Error::new(ESRCH))? + .borrow(); + let requested_proc = self + .processes + .get(&req_pid) + .ok_or(Error::new(ESRCH))? + .borrow(); + + // POSIX allows, but does not require, the implementation to forbid getting the session ID of processes outside + // the current session. + if caller_proc.sid != requested_proc.sid && caller_proc.euid != 0 { + return Err(Error::new(EPERM)); + } + + Ok(requested_proc.sid) + } + fn on_setpgid( + &mut self, + caller_pid: ProcessId, + target_pid: ProcessId, + new_pgid: ProcessId, + awoken: &mut VecDeque, + ) -> Result<()> { + //let caller_proc = self.processes.get(&caller_pid).ok_or(Error::new(ESRCH))?; + + let proc_rc = self.processes.get(&target_pid).ok_or(Error::new(ESRCH))?; + let mut proc = proc_rc.borrow_mut(); + + let mut parent = (proc.ppid != target_pid) + .then(|| self.processes.get(&proc.ppid).map(|p| p.borrow_mut())) + .ok_or(Error::new(ESRCH))?; + + if proc.pgid == new_pgid { + return Ok(()); + } + + // Session leaders cannot have their pgid changed. + if proc.sid == target_pid { + return Err(Error::new(EPERM)); + } + + // TODO: other security checks? + Self::set_pgid( + proc_rc, + &mut *proc, + parent.as_deref_mut(), + &mut self.groups, + new_pgid, + awoken, + )?; + + Ok(()) + } + fn set_pgid( + proc_rc: &Rc>, + proc: &mut Process, + parent: Option<&mut Process>, + groups: &mut HashMap>>, + new_pgid: ProcessId, + awoken: &mut VecDeque, + ) -> Result<()> { + let old_pgid = proc.pgid; + assert_ne!(old_pgid, new_pgid); + + if let Some(parent) = parent { + // Some waitpid waiters may end up waiting for no children, if a child sets its pgid + // and the parent was waiting with a pgid filter. Ensure the waiter is awoken and + // possibly returns ECHILD. + awoken.extend(parent.waitpid_waiting.drain(..)); + } + + let proc_weak = Rc::downgrade(&proc_rc); + let shall_remove = { + let mut old_group = groups.get(&old_pgid).ok_or(Error::new(ESRCH))?.borrow_mut(); + old_group.processes.retain(|w| !Weak::ptr_eq(w, &proc_weak)); + old_group.processes.is_empty() + }; + if shall_remove { + groups.remove(&old_pgid); + } + groups + .entry(new_pgid) + .or_default() + .borrow_mut() + .processes + .push(proc_weak); + proc.pgid = new_pgid; + Ok(()) + } + fn on_exit_start( + &mut self, + pid: ProcessId, + status: u16, + mut state: VacantEntry, + awoken: &mut VecDeque, + tag: Option, + ) -> Poll { + let Some(proc_rc) = self.processes.get(&pid) else { + return if let Some(tag) = tag { + Response::ready_err(EBADFD, tag) + } else { + Pending + }; + }; + let mut process_guard = proc_rc.borrow_mut(); + let process = &mut *process_guard; + + match process.status { + ProcessStatus::Stopped(_) | ProcessStatus::PossiblyRunnable => (), + //ProcessStatus::Exiting => return Pending, + ProcessStatus::Exiting { .. } => { + return if let Some(tag) = tag { + Response::ready_err(EAGAIN, tag) + } else { + Pending + } + } + ProcessStatus::Exited { .. } => { + return if let Some(tag) = tag { + Response::ready_err(ESRCH, tag) + } else { + Pending + } + } + } + + // Forbid the caller from giving statuses corresponding to e.g. WIFCONTINUED which exit() + // obviously can never be. + + log::trace!("Killed with raw status {status:?}"); + + // TODO: Are WIFEXITED and WIFSIGNALED mutually exclusive? + let (status, signal) = if status & 0xff == status { + (status as u8, None) + } else { + // TODO: Only allow valid and catchable signal numbers. + let sig = (status >> 8) as u8; + if !matches!(sig, 1..=64) { + return if let Some(tag) = tag { + Response::ready_err(EINVAL, tag) + } else { + Pending + }; + } + (0, NonZeroU8::new(sig)) + }; + + process.status = ProcessStatus::Exiting { status, signal }; + if !process.threads.is_empty() { + // terminate all threads (possibly including the caller, resulting in EINTR and a + // to-be-ignored cancellation request to this scheme). + for thread in &process.threads { + let mut thread = thread.borrow_mut(); + // TODO: cancel all threads anyway on error? + if let Err(err) = syscall::write(*thread.status_hndl, &usize::MAX.to_ne_bytes()) { + if let Some(tag) = tag { + return Response::ready_err(err.errno, tag); + } + } + } + + log::trace!("EXIT PENDING"); + //self.debug(); + // TODO: check? + process.awaiting_threads_term.push(*state.key()); + } + drop(process_guard); + self.work_on( + state.insert_entry(PendingState::AwaitingThreadsTermination(pid, tag)), + awoken, + ) + } + fn on_waitpid( + &mut self, + this_pid: ProcessId, + target: WaitpidTarget, + flags: WaitFlags, + req_id: VirtualId, + ) -> Poll> { + if matches!( + target, + WaitpidTarget::AnyChild | WaitpidTarget::AnyGroupMember + ) { + // Check for existence of child. + // TODO(opt): inefficient, keep refcount? + if !self.processes.values().any(|p| p.borrow().ppid == this_pid) { + return Ready(Err(Error::new(ECHILD))); + } + } + + let proc_rc = self.processes.get(&this_pid).ok_or(Error::new(ESRCH))?; + + log::trace!("PROCS {:#?}", self.processes); + + let mut proc_guard = proc_rc.borrow_mut(); + let proc = &mut *proc_guard; + + let recv_nonblock = |waitpid: &mut BTreeMap, + key: &WaitpidKey| + -> Option<(ProcessId, WaitpidStatus)> { + if let Some((pid, sts)) = waitpid.get(key).map(|(k, v)| (*k, *v)) { + waitpid.remove(key); + Some((pid, sts)) + } else { + None + } + }; + let mut grim_reaper = + |w_pid: ProcessId, status: WaitpidStatus, scheme: &mut ProcScheme| match status { + WaitpidStatus::Continued => { + if flags.contains(WaitFlags::WCONTINUED) { + Ready((w_pid.0, 0xffff)) + } else { + Pending + } + } + WaitpidStatus::Stopped { signal } => { + if flags.contains(WaitFlags::WUNTRACED) { + Ready((w_pid.0, 0x7f | (i32::from(signal.get()) << 8))) + } else { + Pending + } + } + WaitpidStatus::Terminated { signal, status } => { + scheme.reap(w_pid); + Ready(( + w_pid.0, + i32::from(signal.map_or(0, NonZeroU8::get)) | (i32::from(status) << 8), + )) + } + }; + + match target { + WaitpidTarget::AnyChild | WaitpidTarget::AnyGroupMember => { + let kv = (if matches!(target, WaitpidTarget::AnyChild) { + proc.waitpid.first_key_value() + } else { + proc.waitpid.get_key_value(&WaitpidKey { + pid: None, + pgid: Some(proc.pgid), + }) + }) + .map(|(k, v)| (*k, *v)); + if let Some((wid, (w_pid, status))) = kv { + let _ = proc.waitpid.remove(&wid); + drop(proc_guard); + grim_reaper(w_pid, status, self).map(Ok) + } else if flags.contains(WaitFlags::WNOHANG) { + Ready(Ok((0, 0))) + } else { + proc.waitpid_waiting.push_back(req_id); + Pending + } + } + WaitpidTarget::SingleProc(pid) => { + if this_pid == pid { + return Ready(Err(Error::new(EINVAL))); + } + let target_proc_rc = self.processes.get(&pid).ok_or(Error::new(ESRCH))?; + let mut target_proc = target_proc_rc.borrow_mut(); + + if target_proc.ppid != this_pid { + return Ready(Err(Error::new(ECHILD))); + } + let key = WaitpidKey { + pid: Some(pid), + pgid: None, + }; + if let ProcessStatus::Exited { status, signal } = target_proc.status { + let _ = recv_nonblock(&mut proc.waitpid, &key); + drop(proc_guard); + drop(target_proc); + grim_reaper(pid, WaitpidStatus::Terminated { signal, status }, self).map(Ok) + } else { + let res = recv_nonblock(&mut proc.waitpid, &key); + if let Some((w_pid, status)) = res { + drop(proc_guard); + drop(target_proc); + grim_reaper(w_pid, status, self).map(Ok) + } else if flags.contains(WaitFlags::WNOHANG) { + Ready(Ok((0, 0))) + } else { + proc.waitpid_waiting.push_back(req_id); + Pending + } + } + } + WaitpidTarget::ProcGroup(pgid) => { + if let Some(group_rc) = self.groups.get(&pgid) { + let group = group_rc.borrow(); + if !group + .processes + .iter() + .filter_map(Weak::upgrade) + .filter(|r| !Rc::ptr_eq(r, proc_rc)) + .any(|p| p.borrow().ppid == this_pid) + { + return Ready(Err(Error::new(ECHILD))); + } + } else { + return Ready(Err(Error::new(ECHILD))); + } + + let key = WaitpidKey { + pid: None, + pgid: Some(pgid), + }; + if let Some(&(w_pid, status)) = proc.waitpid.get(&key) { + let _ = proc.waitpid.remove(&key); + drop(proc_guard); + grim_reaper(w_pid, status, self).map(Ok) + } else if flags.contains(WaitFlags::WNOHANG) { + Ready(Ok((0, 0))) + } else { + proc.waitpid_waiting.push_back(req_id); + Pending + } + } + } + } + fn reap(&mut self, pid: ProcessId) { + let Entry::Occupied(entry) = self.processes.entry(pid) else { + return; + }; + let pgid = { + let proc = entry.get().borrow(); + if !proc.threads.is_empty() { + log::error!( + "reaping process (pid {pid:?} with remaining threads: {:#?}", + proc.threads + ); + return; + } + proc.pgid + }; + let proc_rc = entry.remove(); + let proc_weak = Rc::downgrade(&proc_rc); + + let Entry::Occupied(group) = self.groups.entry(pgid) else { + log::error!("Process missing from its group"); + return; + }; + group + .get() + .borrow_mut() + .processes + .retain(|p| !Weak::ptr_eq(&proc_weak, p)); + if group.get().borrow_mut().processes.is_empty() { + group.remove(); + } + // TODO: notify parent's other waiters if ECHILD would now occur? + } + fn on_setresugid(&mut self, pid: ProcessId, raw_buf: &[u8]) -> Result<()> { + let [new_ruid, new_euid, new_suid, new_rgid, new_egid, new_sgid] = { + let raw_ids: [u32; 6] = plain::slice_from_bytes::(raw_buf) + .unwrap() + .try_into() + .map_err(|_| Error::new(EINVAL))?; + raw_ids.map(|i| if i == u32::MAX { None } else { Some(i) }) + }; + let mut proc = self + .processes + .get(&pid) + .ok_or(Error::new(ESRCH))? + .borrow_mut(); + + if proc.euid != 0 { + if ![new_ruid, new_euid, new_suid] + .iter() + .filter_map(|x| *x) + .all(|new_id| [proc.ruid, proc.euid, proc.suid].contains(&new_id)) + { + return Err(Error::new(EPERM)); + } + if ![new_rgid, new_egid, new_sgid] + .iter() + .filter_map(|x| *x) + .all(|new_id| [proc.rgid, proc.egid, proc.sgid].contains(&new_id)) + { + return Err(Error::new(EPERM)); + } + } + + if let Some(new_ruid) = new_ruid { + proc.ruid = new_ruid; + } + if let Some(new_euid) = new_euid { + proc.euid = new_euid; + } + if let Some(new_suid) = new_suid { + proc.suid = new_suid; + } + if let Some(new_rgid) = new_rgid { + proc.rgid = new_rgid; + } + if let Some(new_egid) = new_egid { + proc.egid = new_egid; + } + if let Some(new_sgid) = new_sgid { + proc.sgid = new_sgid; + } + if let Err(err) = proc.sync_kernel_attrs(pid, self.auth) { + log::warn!("Failed to sync proc attrs in setresugid: {err}"); + } + Ok(()) + } + fn ancestors(&self, pid: ProcessId) -> impl Iterator + '_ { + struct Iter<'a> { + cur: Option, + procs: &'a HashMap>, DefaultHashBuilder>, + } + impl Iterator for Iter<'_> { + type Item = ProcessId; + + fn next(&mut self) -> Option { + let proc = self.procs.get(&self.cur?)?; + let ppid = proc.borrow().ppid; + self.cur = Some(ppid); + Some(ppid) + } + } + Iter { + cur: Some(pid), + procs: &self.processes, + } + } + fn on_setrens(&mut self, pid: ProcessId, rns: Option, ens: Option) -> Result<()> { + let proc_rc = self.processes.get(&pid).ok_or(Error::new(EBADFD))?; + let mut process = proc_rc.borrow_mut(); + + let setrns = if rns.is_none() { + // Ignore RNS if -1 is passed + false + } else if rns == Some(0) { + // Allow entering capability mode + true + } else if process.rns == 0 { + // Do not allow leaving capability mode + return Err(Error::new(EPERM)); + } else if process.euid == 0 { + // Allow setting RNS if root + true + } else if rns == Some(process.ens) { + // Allow setting RNS if used for ENS + true + } else if rns == Some(process.rns) { + // Allow setting RNS if used for RNS + true + } else { + // Not permitted otherwise + return Err(Error::new(EPERM)); + }; + + let setens = if ens.is_none() { + // Ignore ENS if -1 is passed + false + } else if ens == Some(0) { + // Allow entering capability mode + true + } else if process.ens == 0 { + // Do not allow leaving capability mode + return Err(Error::new(EPERM)); + } else if process.euid == 0 { + // Allow setting ENS if root + true + } else if ens == Some(process.ens) { + // Allow setting ENS if used for ENS + true + } else if ens == Some(process.rns) { + // Allow setting ENS if used for RNS + true + } else { + // Not permitted otherwise + return Err(Error::new(EPERM)); + }; + + if setrns { + process.rns = rns.unwrap(); + } + if setens { + process.ens = ens.unwrap(); + } + if setrns || setens { + if let Err(err) = process.sync_kernel_attrs(pid, self.auth) { + log::warn!("Failed to sync kernel attrs in setrens: {err}"); + } + } + Ok(()) + } + fn work_on( + &mut self, + mut state_entry: OccupiedEntry, + awoken: &mut VecDeque, + ) -> Poll { + let req_id = *state_entry.key(); + let mut state = state_entry.get_mut(); + let this_state = core::mem::replace(state, PendingState::Placeholder); + match this_state { + PendingState::Placeholder => return Pending, // unreachable!(), + PendingState::AwaitingThreadsTermination(current_pid, tag) => { + let Some(proc_rc) = self.processes.get(¤t_pid) else { + return if let Some(tag) = tag { + Response::ready_err(ESRCH, tag) + } else { + state_entry.remove(); + Pending + }; + }; + let mut proc_guard = proc_rc.borrow_mut(); + let proc = &mut *proc_guard; + + if proc.threads.is_empty() { + log::trace!("WORKING ON AWAIT TERM"); + let (signal, status) = match proc.status { + ProcessStatus::Exiting { signal, status } => (signal, status), + ProcessStatus::Exited { .. } => { + return if let Some(tag) = tag { + Response::ready_ok(0, tag) + } else { + state_entry.remove(); + Pending + } + } + _ => { + return if let Some(tag) = tag { + Response::ready_err(ESRCH, tag) // TODO? + } else { + state_entry.remove(); + Pending + }; + } + }; + // TODO: Properly remove state + state_entry.remove(); + + proc.status = ProcessStatus::Exited { signal, status }; + + let (ppid, pgid) = (proc.ppid, proc.pgid); + drop(proc_guard); + + if let Some(parent_rc) = self.processes.get(&ppid) { + if let Some(init_rc) = self.processes.get(&INIT_PID) { + let mut init = init_rc.borrow_mut(); + // Transfer children to init + for child_rc in self + .processes + .values() + .filter(|p| !Rc::ptr_eq(p, init_rc)) + .filter(|p| p.borrow().ppid == current_pid) + { + let mut child = child_rc.borrow_mut(); + child.ppid = INIT_PID; + init.waitpid.append(&mut child.waitpid); + } + awoken.extend(init.waitpid_waiting.drain(..)); + } + + let mut parent = parent_rc.borrow_mut(); + + parent.waitpid.insert( + WaitpidKey { + pid: Some(current_pid), + pgid: Some(pgid), + }, + (current_pid, WaitpidStatus::Terminated { signal, status }), + ); + log::trace!("AWAKING WAITPID {:?}", parent.waitpid_waiting); + // TODO(opt): inefficient + awoken.extend(parent.waitpid_waiting.drain(..)); + } + if let Some(tag) = tag { + Ready(Response::new(Ok(0), tag)) + } else { + // state was removed earlier + Pending + } + } else { + log::trace!("WAITING AGAIN"); + proc.awaiting_threads_term.push(req_id); + *state = PendingState::AwaitingThreadsTermination(current_pid, tag); + Pending + } + } + PendingState::AwaitingStatusChange { + waiter, + target, + flags, + mut op, + } => { + log::trace!("WAITPID {req_id:?}, {waiter:?}: {target:?} flags {flags:?}"); + let res = self.on_waitpid(waiter, target, flags, req_id); + log::trace!( + "WAITPID {req_id:?}, {waiter:?}: {target:?} flags {flags:?} -> {res:?}" + ); + + match res { + Ready(Ok((pid, status))) => { + if let Ok(status_out) = plain::from_mut_bytes::(op.payload()) { + *status_out = status; + } + Response::ready_ok(pid, op) + } + Ready(Err(e)) => Response::ready_err(e.errno, op), + Pending => { + *state = PendingState::AwaitingStatusChange { + waiter, + target, + flags, + op, + }; + Pending + } + } + } + } + } + fn debug(&self) { + log::trace!("PROCESSES\n{:#?}", self.processes,); + log::trace!("HANDLES\n{:#?}", self.handles,); + } + fn on_kill_thread( + &mut self, + thread: &Rc>, + signal: u8, + awoken: &mut VecDeque, + ) -> Result<()> { + let mut killed_self = false; + + let is_sigchld_to_parent = false; + + let caller_pid = thread.borrow().pid; // TODO(feat): allow this to be specified? + + self.on_send_sig( + caller_pid, + KillTarget::Thread(Rc::clone(thread)), + signal, + &mut killed_self, + KillMode::Idempotent, + is_sigchld_to_parent, + awoken, + )?; + + if killed_self { + // TODO: is this the most accurate error code? + Err(Error::new(ERESTART)) + } else { + Ok(()) + } + } + fn on_kill( + &mut self, + caller_pid: ProcessId, + target: ProcKillTarget, + signal: u8, + mode: KillMode, + awoken: &mut VecDeque, + ) -> Result<()> { + log::trace!("KILL(from {caller_pid:?}) TARGET {target:?} {signal} {mode:?}"); + let mut num_succeeded = 0; + + // if this is set and we would otherwise have succeeded, return EINTR so it can check its + // own mask + let mut killed_self = false; + + // SIGCHLD to parent are not generated by on_kill, but by on_send_sig itself + let is_sigchld_to_parent = false; + + let match_grp = match target { + ProcKillTarget::SingleProc(pid) => { + self.on_send_sig( + caller_pid, + KillTarget::Proc(ProcessId(pid)), + signal, + &mut killed_self, + mode, + is_sigchld_to_parent, + awoken, + )?; + return if killed_self { + Err(Error::new(ERESTART)) + } else { + Ok(()) + }; + } + ProcKillTarget::All => None, + ProcKillTarget::ProcGroup(grp) => Some(ProcessId(grp)), + ProcKillTarget::ThisGroup => Some( + self.processes + .get(&caller_pid) + .ok_or(Error::new(ESRCH))? + .borrow() + .pgid, + ), + }; + log::trace!("match group {match_grp:?}"); + + let mut err_opt = None; + + for (pid, proc_rc) in self.processes.iter() { + if match_grp.map_or(false, |g| proc_rc.borrow().pgid != g) { + continue; + } + let res = self.on_send_sig( + caller_pid, + KillTarget::Proc(*pid), + signal, + &mut killed_self, + mode, + is_sigchld_to_parent, + awoken, + ); + match res { + Ok(()) => (), + Err(err) => err_opt = Some(err), + } + } + + if num_succeeded == 0 + && let Some(err) = err_opt + { + Err(err) + } else if killed_self { + Err(Error::new(ERESTART)) + } else { + Ok(()) + } + } + fn on_send_sig( + &self, + caller_pid: ProcessId, + target: KillTarget, + signal: u8, + killed_self: &mut bool, + mode: KillMode, + is_sigchld_to_parent: bool, + awoken: &mut VecDeque, + ) -> Result<()> { + log::trace!("SEND_SIG(from {caller_pid:?}) TARGET {target:?} {signal} {mode:?}"); + let sig = usize::from(signal); + + if sig > 64 { + return Err(Error::new(EINVAL)); + } + + let sig_group = (sig - 1) / 32; + let sig_idx = (sig - 1) % 32; + + let target_pid = match target { + KillTarget::Proc(pid) => pid, + KillTarget::Thread(ref thread) => thread.borrow().pid, + }; + let target_proc_rc = self.processes.get(&target_pid).ok_or(Error::new(ESRCH))?; + + let sender = SenderInfo { + pid: caller_pid.0 as u32, + ruid: self + .processes + .get(&caller_pid) + .ok_or(Error::new(ESRCH))? + .borrow() + .ruid, + }; + + enum SendResult { + LacksPermission, + Succeeded, + SucceededSigchld { + orig_signal: NonZeroU8, + ppid: ProcessId, + pgid: ProcessId, + }, + SucceededSigcont { + ppid: ProcessId, + pgid: ProcessId, + }, + FullQ, + Invalid, + } + let (caller_euid, caller_ruid) = { + let caller = self + .processes + .get(&caller_pid) + .ok_or(Error::new(ESRCH))? + .borrow(); + (caller.euid, caller.ruid) + }; + + let result = (|| { + // XXX: It's not currently possible for procmgr to know what thread called, so the + // EINTR will be coarser. That shouldn't affect program logic though, since the + // trampoline always checks the masks anyway. + // TODO(feat): allow regular kill (alongside thread-kill) to operate on *thread fds*? + let is_self = target_pid == caller_pid; + + let mut target_proc_guard = target_proc_rc.borrow_mut(); + let mut target_proc = &mut *target_proc_guard; + + if caller_euid != 0 + && caller_euid != target_proc.ruid + && caller_ruid != target_proc.ruid + { + return SendResult::LacksPermission; + } + + // If sig = 0, test that process exists and can be signalled, but don't send any + // signal. + let Some(nz_signal) = NonZeroU8::new(signal) else { + return SendResult::Succeeded; + }; + + let Some(mut sig_pctl) = target_proc.sig_pctl.as_ref() else { + log::trace!("No pctl {caller_pid:?} => {target_pid:?}"); + return SendResult::Invalid; + }; + + if sig == SIGCONT + && let ProcessStatus::Stopped(_sig) = target_proc.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. + target_proc.status = ProcessStatus::PossiblyRunnable; + + if !sig_pctl.signal_will_ign(SIGCONT, false) { + sig_pctl + .pending + .fetch_or(sig_bit(SIGCONT), Ordering::Relaxed); + } + + // TODO: which threads should become Runnable? + for thread_rc in target_proc.threads.iter() { + let mut thread = thread_rc.borrow_mut(); + if let Some(ref tctl) = thread.sig_ctrl { + tctl.word[0].fetch_and( + !(sig_bit(SIGSTOP) + | sig_bit(SIGTTIN) + | sig_bit(SIGTTOU) + | sig_bit(SIGTSTP)), + Ordering::Relaxed, + ); + } + let _ = syscall::write( + *thread.status_hndl, + &(ContextVerb::Unstop as usize).to_ne_bytes(), + ) + .expect("TODO"); + } + // POSIX XSI allows but does not reqiure SIGCHLD to be sent when SIGCONT occurs. + return SendResult::SucceededSigcont { + ppid: target_proc.ppid, + pgid: target_proc.pgid, + }; + } + let is_conditional_stop = matches!(sig, SIGTTIN | SIGTTOU | SIGTSTP); + if sig == SIGSTOP + || (is_conditional_stop + && target_proc + .sig_pctl + .as_ref() + .map_or(false, |proc| proc.signal_will_stop(sig))) + { + if is_conditional_stop { + let pgid = target_proc.pgid; + drop(target_proc_guard); + + if self.pgrp_is_orphaned(pgid).unwrap_or(true) { + // POSIX requires that processes in orphaned process groups never be stopped in + // due to SIGTTIN/SIGTTOU/SIGTSTP. + return SendResult::Succeeded; + } + + target_proc_guard = target_proc_rc.borrow_mut(); + target_proc = &mut *target_proc_guard; + sig_pctl = target_proc.sig_pctl.as_mut().expect("already checked"); + } + + target_proc.status = ProcessStatus::Stopped(sig); + + for thread in &target_proc.threads { + let thread = thread.borrow(); + let _ = syscall::write( + *thread.status_hndl, + &(ContextVerb::Stop as usize).to_ne_bytes(), + ) + .expect("TODO"); + if let Some(ref tctl) = thread.sig_ctrl { + tctl.word[0].fetch_and(!sig_bit(SIGCONT), Ordering::Relaxed); + } + } + + // TODO: Actually wait for, or IPI the context first, then clear bit. Not atomically safe otherwise? + sig_pctl + .pending + .fetch_and(!sig_bit(SIGCONT), Ordering::Relaxed); + + return SendResult::SucceededSigchld { + orig_signal: nz_signal, + ppid: target_proc.ppid, + pgid: target_proc.pgid, + }; + } + if sig == SIGKILL { + for thread in &target_proc.threads { + let thread = thread.borrow(); + let _ = syscall::write( + *thread.status_hndl, + &(ContextVerb::ForceKill as usize).to_ne_bytes(), + ) + .expect("TODO"); + } + + *killed_self |= is_self; + + // exit() will signal the parent, rather than immediately in kill() + return SendResult::Succeeded; + } + if !sig_pctl.signal_will_ign(sig, is_sigchld_to_parent) { + match target { + KillTarget::Thread(ref thread_rc) => { + let thread = thread_rc.borrow(); + let Some(ref tctl) = thread.sig_ctrl else { + log::trace!("No tctl"); + return SendResult::Invalid; + }; + + tctl.sender_infos[sig_idx].store(sender.raw(), Ordering::Relaxed); + let bit = 1 << sig_idx; + + let _was_new = tctl.word[sig_group].fetch_or(bit, Ordering::Release); + if (tctl.word[sig_group].load(Ordering::Relaxed) >> 32) & bit != 0 { + *killed_self |= is_self; + let _ = syscall::write( + *thread.status_hndl, + &(ContextVerb::Interrupt as usize).to_ne_bytes(), + ) + .expect("TODO"); + } + } + KillTarget::Proc(proc) => { + match mode { + KillMode::Queued(arg) => { + if sig_group != 1 { + log::trace!("Out of range"); + return SendResult::Invalid; + } + let rtidx = sig_idx; + //log::trace!("QUEUEING {arg:?} RTIDX {rtidx}"); + if rtidx >= target_proc.rtqs.len() { + target_proc.rtqs.resize_with(rtidx + 1, VecDeque::new); + } + let rtq = target_proc.rtqs.get_mut(rtidx).unwrap(); + + // TODO(feat): configurable limit? + if rtq.len() > 32 { + return SendResult::FullQ; + } + + rtq.push_back(arg); + } + KillMode::Idempotent => { + if sig_pctl.pending.load(Ordering::Acquire) & sig_bit(sig) != 0 { + // If already pending, do not send this signal. While possible that + // another thread is concurrently clearing pending, and that other + // spuriously awoken threads would benefit from actually receiving + // this signal, there is no requirement by POSIX for such signals + // not to be mergeable. So unless the signal handler is observed to + // happen-before this syscall, it can be ignored. The pending bits + // would certainly have been cleared, thus contradicting this + // already reached statement. + return SendResult::Succeeded; + } + + if sig_group != 0 { + log::trace!("Invalid sig group"); + return SendResult::Invalid; + } + sig_pctl.sender_infos[sig_idx] + .store(sender.raw(), Ordering::Relaxed); + } + } + + sig_pctl.pending.fetch_or(sig_bit(sig), Ordering::Release); + + for thread in target_proc.threads.iter() { + let thread = thread.borrow(); + let Some(ref tctl) = thread.sig_ctrl else { + continue; + }; + if (tctl.word[sig_group].load(Ordering::Relaxed) >> 32) & sig_bit(sig) + != 0 + { + let _ = syscall::write( + *thread.status_hndl, + &(ContextVerb::Interrupt as usize).to_ne_bytes(), + ) + .expect("TODO"); + *killed_self |= is_self; + break; + } + } + } + } + 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 + } + })(); + + match result { + // TODO: succeed even if *some* (when group/all procs is specified) fail? + SendResult::LacksPermission => return Err(Error::new(EPERM)), + + SendResult::Succeeded => (), + SendResult::FullQ => return Err(Error::new(EAGAIN)), + SendResult::Invalid => { + log::trace!("Invalid signal configuration for {target_pid:?}"); + return Err(Error::new(ESRCH)); + } + SendResult::SucceededSigchld { + ppid, + pgid, + orig_signal, + } => { + { + let mut parent = self + .processes + .get(&ppid) + .ok_or(Error::new(ESRCH))? + .borrow_mut(); + parent.waitpid.insert( + WaitpidKey { + pid: Some(target_pid), + pgid: Some(pgid), + }, + ( + target_pid, + WaitpidStatus::Stopped { + signal: orig_signal, + }, + ), + ); + awoken.extend(parent.waitpid_waiting.drain(..)); + } + // TODO(err): Just ignore EINVAL (missing signal config), otherwise handle error? + if ppid != INIT_PID { + if let Err(err) = self.on_send_sig( + INIT_PID, // caller, TODO? + KillTarget::Proc(ppid), + SIGCHLD as u8, + killed_self, + KillMode::Idempotent, + true, + awoken, + ) { + log::trace!("failed to SIGCHLD parent (SIGSTOP): {err}"); + } + } + } + SendResult::SucceededSigcont { ppid, pgid } => { + { + let mut parent = self + .processes + .get(&ppid) + .ok_or(Error::new(ESRCH))? + .borrow_mut(); + parent.waitpid.insert( + WaitpidKey { + pid: Some(target_pid), + pgid: Some(pgid), + }, + (target_pid, WaitpidStatus::Continued), + ); + awoken.extend(parent.waitpid_waiting.drain(..)); + } + // POSIX XSI allows but does not require SIGCONT to send signals to the parent. + // TODO(err): Just ignore EINVAL (missing signal config), otherwise handle error? + if ppid != INIT_PID { + if let Err(err) = self.on_send_sig( + INIT_PID, // caller, TODO? + KillTarget::Proc(ppid), + SIGCHLD as u8, + killed_self, + KillMode::Idempotent, + true, + awoken, + ) { + log::trace!("failed to SIGCHLD parent (SIGCONT): {err}"); + } + } + } + } + + Ok(()) + } + fn real_tctl_pctl_intra_page_offsets(fd: &FdGuard) -> Result<[u16; 2]> { + let mut buf = SetSighandlerData::default(); + let _ = syscall::read(**fd, &mut buf)?; + Ok([ + (buf.thread_control_addr % PAGE_SIZE) as u16, + (buf.proc_control_addr % PAGE_SIZE) as u16, + ]) + } + fn on_proc_rename(&mut self, pid: ProcessId, new_name_raw: &[u8]) -> Result<()> { + let new_name = core::str::from_utf8(new_name_raw).map_err(|_| Error::new(EINVAL))?; + let mut proc = self + .processes + .get(&pid) + .ok_or(Error::new(ESRCH))? + .borrow_mut(); + proc.name = ArrayString::from_str(&new_name[..new_name.len().min(NAME_CAPAC)]).unwrap(); + if let Err(err) = proc.sync_kernel_attrs(pid, self.auth) { + log::warn!("Failed to set kernel attrs when renaming proc: {err}"); + } + Ok(()) + } + fn on_sync_sigtctl(thread: &mut Thread) -> Result<()> { + log::trace!("Sync tctl {:?}", thread.pid); + let sigcontrol_fd = FdGuard::new(syscall::dup(*thread.fd, b"sighandler")?); + let [tctl_off, _] = Self::real_tctl_pctl_intra_page_offsets(&sigcontrol_fd)?; + log::trace!("read intra offsets"); + thread + .sig_ctrl + .replace(Page::map(&sigcontrol_fd, 0, tctl_off)?); + Ok(()) + } + fn on_sync_sigpctl(&mut self, pid: ProcessId) -> Result<()> { + log::trace!("Sync pctl {pid:?}"); + let mut proc = self + .processes + .get(&pid) + .ok_or(Error::new(ESRCH))? + .borrow_mut(); + let any_thread = proc.threads.first().ok_or(Error::new(EINVAL))?; + let sigcontrol_fd = FdGuard::new(syscall::dup(*any_thread.borrow().fd, b"sighandler")?); + let [_, pctl_off] = Self::real_tctl_pctl_intra_page_offsets(&sigcontrol_fd)?; + proc.sig_pctl + .replace(Page::map(&sigcontrol_fd, PAGE_SIZE, pctl_off)?); + Ok(()) + } + fn on_sigdeq(&mut self, pid: ProcessId, payload: &mut [u8]) -> Result<()> { + let sig_idx = { + let bytes = <[u8; 4]>::try_from(payload.get(..4).ok_or(Error::new(EINVAL))?).unwrap(); + u32::from_ne_bytes(bytes) + }; + log::trace!("SIGDEQ {pid:?} idx {sig_idx}"); + let mut dst = payload + .get_mut(..size_of::()) + .ok_or(Error::new(EINVAL))?; + if sig_idx >= 32 { + return Err(Error::new(EINVAL)); + } + let mut proc = self + .processes + .get_mut(&pid) + .ok_or(Error::new(ESRCH))? + .borrow_mut(); + let proc = &mut *proc; + + let pctl = proc.sig_pctl.as_ref().ok_or(Error::new(EBADF))?; + + let q = proc + .rtqs + .get_mut(sig_idx as usize) + .ok_or(Error::new(EAGAIN))?; + let Some(front) = q.pop_front() else { + return Err(Error::new(EAGAIN)); + }; + + if q.is_empty() { + pctl.pending + .fetch_and(!(1 << (32 + sig_idx as usize)), Ordering::Relaxed); + } + dst.copy_from_slice(unsafe { plain::as_bytes(&front) }); + Ok(()) + } + fn pgrp_is_orphaned(&self, grp: ProcessId) -> Option { + let group = self.groups.get(&grp)?.borrow(); + + let mut still_true = true; + + for process_rc in group.processes.iter().filter_map(Weak::upgrade) { + let process = process_rc.borrow(); + let Some(parent_rc) = self.processes.get(&process_rc.borrow().ppid) else { + // TODO(err): what to do here? + continue; + }; + let parent = parent_rc.borrow(); + + // POSIX defines orphaned process groups as those where + // + // forall process in group, + // process's parent pgid == process's pgid + // OR + // process's session id != process's session id + still_true &= parent.pgid == process.pgid || parent.sid != process.sid; + } + Some(still_true) + } + fn ps_data(&mut self, _ctx: &CallerCtx) -> Result> { + // TODO: enforce uid == 0? + + let mut string = alloc::format!( + "{:<6}{:<6}{:<6}{:<6}{:<6}{:<6}{:<6}{:<6}{:<6}{:<6}{:<6}{:<8}{:<16}\n", + "PID", + "PGID", + "PPID", + "SID", + "RUID", + "RGID", + "RNS", + "EUID", + "EGID", + "ENS", + "NTHRD", + "STATUS", + "NAME", + ); + for (pid, process_rc) in self.processes.iter() { + let process = process_rc.borrow(); + let status = match process.status { + ProcessStatus::PossiblyRunnable => "R", + ProcessStatus::Stopped(_) => "S", + ProcessStatus::Exiting { .. } => "E", + ProcessStatus::Exited { .. } => "X", + }; + use core::fmt::Write; + writeln!( + string, + "{:<6}{:<6}{:<6}{:<6}{:<6}{:<6}{:<6}{:<6}{:<6}{:<6}{:<6}{:<8}{:<16}", + pid.0, + process.pgid.0, + process.ppid.0, + process.sid.0, + process.ruid, + process.rgid, + process.rns, + process.euid, + process.egid, + process.ens, + process.threads.len(), + status, + process.name, + ) + .unwrap(); + } + + // Useful for debugging memory leaks. + log::trace!("NEXT FD: {}", { + let nextfd = syscall::dup(0, &[]).unwrap(); + syscall::close(nextfd); + nextfd + }); + + Ok(string.into_bytes()) + } +} +#[derive(Clone, Copy, Debug)] +enum KillMode { + Idempotent, + Queued(RtSigInfo), +} +#[derive(Debug)] +enum KillTarget { + Proc(ProcessId), + Thread(Rc>), +} +fn arraystring_to_bytes(s: ArrayString) -> [u8; C] { + let mut buf = [0_u8; C]; + let min = buf.len().min(s.len()); + buf[..min].copy_from_slice(&s.as_bytes()[..min]); + buf +} +impl Process { + fn sync_kernel_attrs(&mut self, my_pid: ProcessId, auth: &FdGuard) -> Result<()> { + // TODO: continue with other threads if one fails? + for thread_rc in &self.threads { + let thread = thread_rc.borrow(); + let attr_fd = FdGuard::new(syscall::dup( + *thread.fd, + alloc::format!("auth-{}-attrs", **auth).as_bytes(), + )?); + let _ = syscall::write( + *attr_fd, + &ProcSchemeAttrs { + pid: my_pid.0 as u32, + euid: self.euid, + egid: self.egid, + ens: self.ens, + debug_name: arraystring_to_bytes(self.name), + }, + )?; + } + Ok(()) + } +} diff --git a/src/start.rs b/src/start.rs index ebaeb17505..f85ec084ab 100644 --- a/src/start.rs +++ b/src/start.rs @@ -78,8 +78,6 @@ pub unsafe extern "C" fn start() -> ! { ) .expect("mprotect failed for rest of memory"); - redox_rt::initialize_freestanding(); - // FIXME make the initfs read-only crate::exec::main();