diff --git a/bootstrap/.gitignore b/bootstrap/.gitignore new file mode 100644 index 0000000000..ea8c4bf7f3 --- /dev/null +++ b/bootstrap/.gitignore @@ -0,0 +1 @@ +/target diff --git a/bootstrap/Cargo.lock b/bootstrap/Cargo.lock new file mode 100644 index 0000000000..7fcc3f58f0 --- /dev/null +++ b/bootstrap/Cargo.lock @@ -0,0 +1,240 @@ +# This file is automatically @generated by Cargo. +# 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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "bitflags" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" + +[[package]] +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.git#696ae4eca1df5e3b76a57f62903ee1a94b9c6aa6" + +[[package]] +name = "goblin" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27c1b4369c2cd341b5de549380158b105a04c331be5db9110eef7b6d2742134" +dependencies = [ + "log", + "plain", + "scroll", +] + +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +dependencies = [ + "foldhash", +] + +[[package]] +name = "libc" +version = "0.2.172" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" + +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags", + "libc", + "redox_syscall", +] + +[[package]] +name = "linked_list_allocator" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afa463f5405ee81cdb9cc2baf37e08ec7e4c8209442b5d72c04cfb2cd6e6286" +dependencies = [ + "spinning_top", +] + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox-initfs" +version = "0.2.0" +source = "git+https://gitlab.redox-os.org/redox-os/redox-initfs.git#d0802237d7894881df7ddd338dfc64220d0646ff" +dependencies = [ + "plain", +] + +[[package]] +name = "redox-path" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436d45c2b6a5b159d43da708e62b25be3a4a3d5550d654b72216ade4c4bfd717" + +[[package]] +name = "redox-rt" +version = "0.1.0" +source = "git+https://gitlab.redox-os.org/redox-os/relibc.git#696ae4eca1df5e3b76a57f62903ee1a94b9c6aa6" +dependencies = [ + "bitflags", + "generic-rt", + "goblin", + "plain", + "redox_syscall", +] + +[[package]] +name = "redox-scheme" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a14f94bc95b388d8f40a61f25577863f1cf151aaf600a5b7778d1a2018ff7742" +dependencies = [ + "libredox", + "redox_syscall", +] + +[[package]] +name = "redox_syscall" +version = "0.5.11" +source = "git+https://gitlab.redox-os.org/redox-os/syscall.git?branch=master#8f646a63ac9c3aa532a37ddfdc136ec19878f0d6" +dependencies = [ + "bitflags", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "scroll" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04c565b551bafbef4157586fa379538366e4385d42082f255bfd96e4fe8519da" +dependencies = [ + "scroll_derive", +] + +[[package]] +name = "scroll_derive" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1db149f81d46d2deba7cd3c50772474707729550221e69588478ebf9ada425ae" +dependencies = [ + "proc-macro2", + "quote", + "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b9eb1a2f4c41445a3a0ff9abc5221c5fcd28e1f13cd7c0397706f9ac938ddb0" +dependencies = [ + "lock_api", +] + +[[package]] +name = "syn" +version = "2.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" diff --git a/bootstrap/Cargo.toml b/bootstrap/Cargo.toml new file mode 100644 index 0000000000..b41643737a --- /dev/null +++ b/bootstrap/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "bootstrap" +version = "0.0.0" +authors = ["4lDO2 <4lDO2@protonmail.com>"] +edition = "2021" +license = "MIT" + +[lib] +name = "bootstrap" +crate-type = ["staticlib"] + +[dependencies] +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 = { 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" +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/bootstrap/LICENSE b/bootstrap/LICENSE new file mode 100644 index 0000000000..2c457a7229 --- /dev/null +++ b/bootstrap/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 4lDO2 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/bootstrap/README.md b/bootstrap/README.md new file mode 100644 index 0000000000..a56ea88e81 --- /dev/null +++ b/bootstrap/README.md @@ -0,0 +1,26 @@ +# bootstrap + +First code that the kernel executes, responsible for spawning init. + +## How To Contribute + +To learn how to contribute to this system component you need to read the following document: + +- [CONTRIBUTING.md](https://gitlab.redox-os.org/redox-os/redox/-/blob/master/CONTRIBUTING.md) + +## Development + +To learn how to do development with this system component inside the Redox build system you need to read the [Build System](https://doc.redox-os.org/book/build-system-reference.html) and [Coding and Building](https://doc.redox-os.org/book/coding-and-building.html) pages. + +### How To 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/bootstrap/src/aarch64.ld b/bootstrap/src/aarch64.ld new file mode 100644 index 0000000000..cee830f08a --- /dev/null +++ b/bootstrap/src/aarch64.ld @@ -0,0 +1,46 @@ +ENTRY(_start) +OUTPUT_FORMAT("elf64-littleaarch64", "elf64-littleaarch64", "elf64-littleaarch64") + +SECTIONS { + . = 4096 + 4096; /* Reserved for the null page and the initfs header prepended by redox-initfs-ar */ + __initfs_header = . - 4096; + . += SIZEOF_HEADERS; + . = ALIGN(4096); + + .text : { + __text_start = .; + *(.text*) + . = ALIGN(4096); + __text_end = .; + } + .rodata : { + __rodata_start = .; + *(.rodata*) + . = ALIGN(4096); + __rodata_end = .; + } + .data : { + __data_start = .; + *(.data*) + . = ALIGN(4096); + __data_end = .; + + *(.tbss*) + . = ALIGN(4096); + *(.tdata*) + . = ALIGN(4096); + + __bss_start = .; + *(.bss*) + . = ALIGN(4096); + __bss_end = .; + } + + /DISCARD/ : { + *(.comment*) + *(.eh_frame*) + *(.gcc_except_table*) + *(.note*) + *(.rel.eh_frame*) + } +} diff --git a/bootstrap/src/aarch64.rs b/bootstrap/src/aarch64.rs new file mode 100644 index 0000000000..6e1a732175 --- /dev/null +++ b/bootstrap/src/aarch64.rs @@ -0,0 +1,53 @@ +use core::mem; +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; + +const STACK_SIZE: usize = 64 * 1024; // 64 KiB +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), + address: STACK_START, // highest possible user address +}; + +core::arch::global_asm!( + " + .globl _start + _start: + // Setup a stack. + ldr x8, ={number} + ldr x0, ={fd} + ldr x1, ={map} // pointer to Map struct + ldr x2, ={map_size} // size of Map struct + svc 0 + + // Failure if return value is zero + cbz x0, 1f + + // Failure if return value is negative + tbnz x0, 63, 1f + + // Set up stack frame + mov sp, x0 + add sp, sp, #{stack_size} + mov fp, sp + + // Stack has the same alignment as `size`. + bl start + // `start` must never return. + + // failure, emit undefined instruction + 1: + udf #0 + ", + fd = const usize::MAX, // dummy fd indicates anonymous map + map = sym MAP, + map_size = const mem::size_of::(), + number = const SYS_FMAP, + stack_size = const STACK_SIZE, +); diff --git a/bootstrap/src/exec.rs b/bootstrap/src/exec.rs new file mode 100644 index 0000000000..589c467e72 --- /dev/null +++ b/bootstrap/src/exec.rs @@ -0,0 +1,163 @@ +use alloc::borrow::ToOwned; +use alloc::vec::Vec; + +use syscall::flag::{O_CLOEXEC, O_RDONLY}; +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]; + + let fd = FdGuard::new( + syscall::open("/scheme/sys/env", O_RDONLY).expect("bootstrap: failed to open env"), + ); + let bytes_read = syscall::read(*fd, &mut env).expect("bootstrap: failed to read env"); + + if bytes_read >= env.len() { + // TODO: Handle this, we can allocate as much as we want in theory. + panic!("env is too large"); + } + let env = &mut env[..bytes_read]; + + let raw_iter = || env.split(|c| *c == b'\n').filter(|var| !var.is_empty()); + + let iter = || raw_iter().filter(|var| !var.starts_with(b"INITFS_")); + + iter().map(|var| var.to_owned()).collect::>() + }; + + extern "C" { + // The linker script will define this as the location of the initfs header. + static __initfs_header: u8; + } + + let initfs_length = unsafe { + (*(core::ptr::addr_of!(__initfs_header) as *const redox_initfs::types::Header)).initfs_size + }; + + 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 { + cwd: Some(CWD), + default_scheme: Some(DEFAULT_SCHEME), + 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"; + let total_args_envs_auxvpointee_size = path.len() + + 1 + + envs.len() + + envs.iter().map(|v| v.len()).sum::() + + CWD.len() + + 1 + + DEFAULT_SCHEME.len() + + 1; + + let image_file = FdGuard::new(syscall::open(path, O_RDONLY).expect("failed to open init")); + let memory = FdGuard::new(syscall::open("/scheme/memory", 0).expect("failed to open memory")); + + fexec_impl( + image_file, + init_thr_fd, + init_proc_fd, + &memory, + path.as_bytes(), + [path], + envs.iter(), + total_args_envs_auxvpointee_size, + &extrainfo, + None, + ) + .expect("failed to execute init"); + + unreachable!() +} + +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(&ForkArgs::Init { this_thr_fd, auth }) { + Err(err) => { + panic!("Failed to fork in order to start {name}: {err}"); + } + // Continue serving the scheme as the child. + Ok(0) => { + let _ = syscall::close(read); + } + // Return in order to execute init, as the parent. + Ok(_) => { + let _ = syscall::close(write); + loop { + match syscall::read(read, &mut [0]) { + Err(Error { errno: EINTR }) => continue, + _ => break, + } + } + + return; + } + } + inner(write); +} diff --git a/bootstrap/src/i686.ld b/bootstrap/src/i686.ld new file mode 100644 index 0000000000..9bfb676b6b --- /dev/null +++ b/bootstrap/src/i686.ld @@ -0,0 +1,46 @@ +ENTRY(_start) +OUTPUT_FORMAT(elf32-i386) + +SECTIONS { + . = 4096 + 4096; /* Reserved for the null page and the initfs header prepended by redox-initfs-ar */ + __initfs_header = . - 4096; + . += SIZEOF_HEADERS; + . = ALIGN(4096); + + .text : { + __text_start = .; + *(.text*) + . = ALIGN(4096); + __text_end = .; + } + .rodata : { + __rodata_start = .; + *(.rodata*) + . = ALIGN(4096); + __rodata_end = .; + } + .data : { + __data_start = .; + *(.data*) + . = ALIGN(4096); + __data_end = .; + + *(.tbss*) + . = ALIGN(4096); + *(.tdata*) + . = ALIGN(4096); + + __bss_start = .; + *(.bss*) + . = ALIGN(4096); + __bss_end = .; + } + + /DISCARD/ : { + *(.comment*) + *(.eh_frame*) + *(.gcc_except_table*) + *(.note*) + *(.rel.eh_frame*) + } +} diff --git a/bootstrap/src/i686.rs b/bootstrap/src/i686.rs new file mode 100644 index 0000000000..a3805d10e2 --- /dev/null +++ b/bootstrap/src/i686.rs @@ -0,0 +1,49 @@ +use core::mem; +use syscall::{data::Map, flag::MapFlags, number::SYS_FMAP}; + +const STACK_SIZE: usize = 64 * 1024; // 64 KiB +pub const USERMODE_END: usize = 0x8000_0000; +pub const STACK_START: usize = USERMODE_END - STACK_SIZE; + +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), + address: STACK_START, // highest possible user address +}; + +core::arch::global_asm!( + " + .globl _start + _start: + # Setup a stack. + mov eax, {number} + mov ebx, {fd} + mov ecx, offset {map} # pointer to Map struct + mov edx, {map_size} # size of Map struct + int 0x80 + + # Test for success (nonzero value). + cmp eax, 0 + jg 1f + # (failure) + ud2 + 1: + # Subtract 16 since all instructions seem to hate non-canonical ESP values :) + lea esp, [eax+{stack_size}-16] + mov ebp, esp + + # Stack has the same alignment as `size`. + call start + # `start` must never return. + ud2 + ", + fd = const usize::MAX, // dummy fd indicates anonymous map + map = sym MAP, + map_size = const mem::size_of::(), + number = const SYS_FMAP, + stack_size = const STACK_SIZE, +); diff --git a/bootstrap/src/initfs.rs b/bootstrap/src/initfs.rs new file mode 100644 index 0000000000..a594140c9c --- /dev/null +++ b/bootstrap/src/initfs.rs @@ -0,0 +1,365 @@ +use core::convert::TryFrom; +#[allow(deprecated)] +use core::hash::{BuildHasherDefault, SipHasher}; +use core::str; + +use alloc::string::String; + +use hashbrown::HashMap; +use redox_initfs::{types::Timespec, InitFs, Inode, InodeDir, InodeKind, InodeStruct}; + +use redox_path::canonicalize_to_standard; +use redox_scheme::{scheme::SchemeSync, CallerCtx, OpenResult, RequestKind}; + +use redox_scheme::{SignalBehavior, Socket}; +use syscall::data::Stat; +use syscall::dirent::DirEntry; +use syscall::dirent::DirentBuf; +use syscall::dirent::DirentKind; +use syscall::error::*; +use syscall::flag::*; +use syscall::schemev2::NewFdFlags; + +struct Handle { + inode: Inode, + // TODO: Any better way to implement fpath? Or maybe work around it, e.g. by giving paths such + // as `initfs:__inodes__/`? + filename: String, +} +pub struct InitFsScheme { + #[allow(deprecated)] + handles: HashMap>, + next_id: usize, + fs: InitFs<'static>, +} +impl InitFsScheme { + pub fn new(bytes: &'static [u8]) -> Self { + Self { + handles: HashMap::default(), + next_id: 0, + fs: InitFs::new(bytes).expect("failed to parse initfs"), + } + } + + fn get_inode(fs: &InitFs<'static>, inode: Inode) -> Result> { + fs.get_inode(inode).ok_or_else(|| Error::new(EIO)) + } + fn next_id(&mut self) -> usize { + assert_ne!(self.next_id, usize::MAX, "usize overflow in initfs scheme"); + self.next_id += 1; + self.next_id + } +} + +struct Iter { + dir: InodeDir<'static>, + idx: u32, +} +impl Iterator for Iter { + type Item = Result>; + + fn next(&mut self) -> Option { + let entry = self.dir.get_entry(self.idx).map_err(|_| Error::new(EIO)); + self.idx += 1; + entry.transpose() + } + fn size_hint(&self) -> (usize, Option) { + match self.dir.entry_count().ok() { + Some(size) => { + let size = + usize::try_from(size).expect("expected u32 to be convertible into usize"); + (size, Some(size)) + } + None => (0, None), + } + } +} + +fn inode_len(inode: InodeStruct<'static>) -> Result { + Ok(match inode.kind() { + InodeKind::File(file) => file.data().map_err(|_| Error::new(EIO))?.len(), + InodeKind::Dir(dir) => (Iter { dir, idx: 0 }).fold(0, |len, entry| { + len + entry + .and_then(|entry| entry.name().map_err(|_| Error::new(EIO))) + .map_or(0, |name| name.len() + 1) + }), + InodeKind::Link(link) => link.data().map_err(|_| Error::new(EIO))?.len(), + InodeKind::Unknown => return Err(Error::new(EIO)), + }) +} + +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('/') + // divide into components + .split('/') + // filter out double slashes (e.g. /usr//bin/...) + .filter(|c| !c.is_empty()); + + let mut current_inode = InitFs::ROOT_INODE; + + while let Some(component) = components.next() { + match component { + "." => continue, + ".." => { + let _ = components.next_back(); + continue; + } + + _ => (), + } + + let current_inode_struct = Self::get_inode(&self.fs, current_inode)?; + + let dir = match current_inode_struct.kind() { + InodeKind::Dir(dir) => dir, + + // TODO: Support symlinks in other position than xopen target + InodeKind::Link(_) => { + return Err(Error::new(EOPNOTSUPP)); + } + + // If we still have more components in the path, and the file tree for that + // particular branch is not all directories except the last, then that file cannot + // exist. + InodeKind::File(_) | InodeKind::Unknown => return Err(Error::new(ENOENT)), + }; + + let mut entries = Iter { dir, idx: 0 }; + + current_inode = loop { + let entry_res = match entries.next() { + Some(e) => e, + None => return Err(Error::new(ENOENT)), + }; + let entry = entry_res?; + let name = entry.name().map_err(|_| Error::new(EIO))?; + if name == component.as_bytes() { + break entry.inode(); + } + }; + } + + // xopen target is link -- return EXDEV so that the file is opened as a link. + // TODO: Maybe follow initfs-local symlinks here? Would be faster + let is_link = matches!( + Self::get_inode(&self.fs, current_inode)?.kind(), + InodeKind::Link(_) + ); + let o_symlink = flags & O_SYMLINK != 0; + if is_link && !o_symlink { + return Err(Error::new(EXDEV)); + } + + let id = self.next_id(); + let old = self.handles.insert( + id, + Handle { + inode: current_inode, + filename: path.into(), + }, + ); + assert!(old.is_none()); + + Ok(OpenResult::ThisScheme { + number: id, + flags: NewFdFlags::POSITIONED, + }) + } + + fn read( + &mut self, + id: usize, + buffer: &mut [u8], + offset: u64, + _fcntl_flags: u32, + _ctx: &CallerCtx, + ) -> Result { + let Ok(offset) = usize::try_from(offset) else { + return Ok(0); + }; + + let handle = self.handles.get_mut(&id).ok_or(Error::new(EBADF))?; + + match Self::get_inode(&self.fs, handle.inode)?.kind() { + InodeKind::File(file) => { + let data = file.data().map_err(|_| Error::new(EIO))?; + let src_buf = &data[core::cmp::min(offset, data.len())..]; + + let to_copy = core::cmp::min(src_buf.len(), buffer.len()); + buffer[..to_copy].copy_from_slice(&src_buf[..to_copy]); + + Ok(to_copy) + } + InodeKind::Dir(_) => Err(Error::new(EISDIR)), + InodeKind::Link(link) => { + let link_data = link.data().map_err(|_| Error::new(EIO))?; + let path = core::str::from_utf8(link_data).map_err(|_| Error::new(ENOENT))?; + let cannonical = + canonicalize_to_standard(Some("/"), path).ok_or_else(|| Error::new(ENOENT))?; + let data = cannonical.as_bytes(); + + let src_buf = &data[core::cmp::min(offset, data.len())..]; + + let to_copy = core::cmp::min(src_buf.len(), buffer.len()); + buffer[..to_copy].copy_from_slice(&src_buf[..to_copy]); + + Ok(to_copy) + } + InodeKind::Unknown => Err(Error::new(EIO)), + } + } + fn getdents<'buf>( + &mut self, + id: usize, + mut buf: DirentBuf<&'buf mut [u8]>, + opaque_offset: u64, + ) -> Result> { + let Ok(offset) = u32::try_from(opaque_offset) else { + return Ok(buf); + }; + let handle = self.handles.get(&id).ok_or(Error::new(EBADF))?; + let InodeKind::Dir(dir) = Self::get_inode(&self.fs, handle.inode)?.kind() else { + return Err(Error::new(ENOTDIR)); + }; + let iter = Iter { dir, idx: offset }; + for (index, entry) in iter.enumerate() { + let entry = entry?; + buf.entry(DirEntry { + // TODO: Add getter + //inode: entry.inode(), + inode: 0, + + name: entry + .name() + .ok() + .and_then(|utf8| core::str::from_utf8(utf8).ok()) + .ok_or(Error::new(EIO))?, + next_opaque_id: index as u64 + 1, + kind: DirentKind::Unspecified, + })?; + } + Ok(buf) + } + + 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, _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], _ctx: &CallerCtx) -> Result { + let handle = self.handles.get(&id).ok_or(Error::new(EBADF))?; + + // TODO: Copy scheme part in kernel + let scheme_path = b"/scheme/initfs"; + let scheme_bytes = core::cmp::min(scheme_path.len(), buf.len()); + buf[..scheme_bytes].copy_from_slice(&scheme_path[..scheme_bytes]); + + let source = handle.filename.as_bytes(); + let path_bytes = core::cmp::min(buf.len() - scheme_bytes, source.len()); + buf[scheme_bytes..scheme_bytes + path_bytes].copy_from_slice(&source[..path_bytes]); + + Ok(scheme_bytes + path_bytes) + } + + 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(); + + let inode = Self::get_inode(&self.fs, handle.inode)?; + + stat.st_mode = inode.mode() + | match inode.kind() { + InodeKind::Dir(_) => MODE_DIR, + InodeKind::File(_) => MODE_FILE, + _ => 0, + }; + stat.st_uid = inode.uid(); + stat.st_gid = inode.gid(); + stat.st_size = u64::try_from(inode_len(inode)?).unwrap_or(u64::MAX); + + stat.st_ctime = sec.get(); + stat.st_ctime_nsec = nsec.get(); + stat.st_mtime = sec.get(); + stat.st_mtime_nsec = nsec.get(); + + Ok(()) + } + + fn fsync(&mut self, id: usize, _ctx: &CallerCtx) -> Result<()> { + if !self.handles.contains_key(&id) { + return Err(Error::new(EBADF)); + } + + 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 _ = syscall::write(sync_pipe, &[0]); + let _ = syscall::close(sync_pipe); + + loop { + let Some(req) = socket + .next_request(SignalBehavior::Restart) + .expect("bootstrap: failed to read scheme request from 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); + } + _ => (), + } + } + + unreachable!() +} + +// TODO: Restructure bootstrap so it calls into relibc, or a split-off derivative without the C +// parts, such as "redox-rt". + +#[no_mangle] +pub unsafe extern "C" fn redox_read_v1(fd: usize, ptr: *mut u8, len: usize) -> isize { + Error::mux(syscall::read(fd, core::slice::from_raw_parts_mut(ptr, len))) as isize +} + +#[no_mangle] +pub unsafe extern "C" fn redox_write_v1(fd: usize, ptr: *const u8, len: usize) -> isize { + Error::mux(syscall::write(fd, core::slice::from_raw_parts(ptr, len))) as isize +} + +#[no_mangle] +pub unsafe extern "C" fn redox_open_v1(ptr: *const u8, len: usize, flags: usize) -> isize { + Error::mux(syscall::open(core::str::from_raw_parts(ptr, len), flags)) as isize +} + +#[no_mangle] +pub extern "C" fn redox_close_v1(fd: usize) -> isize { + Error::mux(syscall::close(fd)) as isize +} diff --git a/bootstrap/src/lib.rs b/bootstrap/src/lib.rs new file mode 100644 index 0000000000..6ed7505f9c --- /dev/null +++ b/bootstrap/src/lib.rs @@ -0,0 +1,127 @@ +#![no_std] +#![allow(internal_features)] +#![feature(core_intrinsics, let_chains, iter_intersperse, str_from_raw_parts)] + +#[cfg(target_arch = "aarch64")] +#[path = "aarch64.rs"] +pub mod arch; + +#[cfg(target_arch = "x86")] +#[path = "i686.rs"] +pub mod arch; + +#[cfg(target_arch = "x86_64")] +#[path = "x86_64.rs"] +pub mod arch; + +#[cfg(target_arch = "riscv64")] +#[path = "riscv64.rs"] +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; + +#[panic_handler] +fn panic_handler(info: &core::panic::PanicInfo) -> ! { + use core::fmt::Write; + + struct Writer; + + impl Write for Writer { + fn write_str(&mut self, s: &str) -> core::fmt::Result { + syscall::write(1, s.as_bytes()) + .map_err(|_| core::fmt::Error) + .map(|_| ()) + } + } + + let _ = writeln!(&mut Writer, "{}", info); + core::intrinsics::abort(); +} + +const HEAP_OFF: usize = arch::USERMODE_END / 2; + +struct Allocator; +#[global_allocator] +static ALLOCATOR: Allocator = Allocator; + +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 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 { + offset: 0, + size: SIZE, + address: HEAP_OFF, + flags: MapFlags::PROT_WRITE + | MapFlags::PROT_READ + | MapFlags::MAP_PRIVATE + | MapFlags::MAP_FIXED_NOREPLACE, + }, + ) + .expect("failed to map initial heap"); + linked_list_allocator::Heap::new(HEAP_OFF as *mut u8, SIZE) + }); + + match heap.allocate_first_fit(layout) { + Ok(p) => p.as_ptr(), + Err(_) => { + if layout.size() > HEAP_INCREASE_BY || layout.align() > 4096 { + return core::ptr::null_mut(); + } + + let _ = syscall::fmap( + !0, + &Map { + offset: 0, + size: HEAP_INCREASE_BY, + address: state.heap_top, + flags: MapFlags::PROT_WRITE + | MapFlags::PROT_READ + | MapFlags::MAP_PRIVATE + | MapFlags::MAP_FIXED_NOREPLACE, + }, + ) + .expect("failed to extend heap"); + heap.extend(HEAP_INCREASE_BY); + state.heap_top += HEAP_INCREASE_BY; + + return self.alloc(layout); + } + } + } + unsafe fn dealloc(&self, ptr: *mut u8, layout: core::alloc::Layout) { + (&mut *ALLOC_STATE.0.get()) + .heap + .as_mut() + .unwrap() + .deallocate(core::ptr::NonNull::new(ptr).unwrap(), layout) + } +} diff --git a/bootstrap/src/procmgr.rs b/bootstrap/src/procmgr.rs new file mode 100644 index 0000000000..b73c91a5ed --- /dev/null +++ b/bootstrap/src/procmgr.rs @@ -0,0 +1,2497 @@ +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, SIGHUP, + 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, + EACCES, 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>>, + pid: ProcessId, + 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, + disabled_setpgid: bool, + + 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], + pid: INIT_PID, + 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, + disabled_setpgid: false, + 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, + pid: child_pid, + pgid, + sid, + ruid, + euid, + suid, + rgid, + egid, + sgid, + rns, + ens, + name, + + status: ProcessStatus::PossiblyRunnable, + disabled_setpgid: false, + 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, + )), + ProcCall::DisableSetpgid => { + if let Some(proc) = self.processes.get(&fd_pid) { + proc.borrow_mut().disabled_setpgid = true; + Response::ready_ok(0, op) + } else { + Response::ready_err(ESRCH, 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 + .iter() + .any(|(pid, rc)| *pid != caller_pid && rc.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 caller_sid = caller_proc.borrow().sid; + + let proc_rc = self.processes.get(&target_pid).ok_or(Error::new(ESRCH))?; + let mut proc = proc_rc.borrow_mut(); + + if proc.ppid != caller_pid && target_pid != caller_pid { + return Err(Error::new(ESRCH)); + } + + let mut parent = if proc.ppid == target_pid { + None // init + } else { + Some( + self.processes + .get(&proc.ppid) + .ok_or(Error::new(ESRCH))? + .borrow_mut(), + ) + }; + + // Session leaders cannot have their pgid changed. + if proc.sid == target_pid { + return Err(Error::new(EPERM)); + } + + // Cannot change the pgid of a process in a different session. + if caller_sid != proc.sid { + return Err(Error::new(EPERM)); + } + + // New pgid must either already exit, or be the same as the target pid. + if new_pgid != target_pid && !self.groups.contains_key(&new_pgid) { + return Err(Error::new(EPERM)); + } + + // After execv(), i.e. ProcCall::DisableSetpgid, setpgid where target_pid is a child + // process of the calling process, shall return EACCESS. + if proc.ppid == caller_pid && proc.disabled_setpgid { + return Err(Error::new(EACCES)); + } + + if proc.pgid == new_pgid { + return Ok(()); + } + + 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(EBADFD))?; + + 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, mut sts)) = waitpid.get(key).map(|(k, v)| (*k, *v)) { + waitpid.remove(key); + /*while let Some((_, new_sts)) = waitpid.remove(&WaitpidKey { pid: Some(pid), pgid: None }) { + sts = new_sts; + }*/ + 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(ECHILD))?; + 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) { + awoken.extend(init_rc.borrow_mut().waitpid_waiting.drain(..)); + + // TODO(opt): Store list of children in each process? + let children_iter = || { + self.processes + .values() + .filter(|p| !Rc::ptr_eq(p, init_rc)) + .filter(|p| p.borrow().ppid == current_pid) + }; + + // TODO(opt): Avoid allocation? + let affected_pgids = children_iter() + .map(|child_rc| { + let child_pgid = child_rc.borrow().pgid; + (child_pgid, self.pgrp_is_orphaned(child_pgid)) + }) + .chain(Some((pgid, self.pgrp_is_orphaned(pgid)))) + .collect::>>(); + + // Transfer children to init + for child_rc in children_iter() { + let mut child = child_rc.borrow_mut(); + log::trace!( + "Reparenting {:?} (ppid {:?}) => {:?}", + child.pid, + child.ppid, + INIT_PID + ); + child.ppid = INIT_PID; + init_rc.borrow_mut().waitpid.append(&mut child.waitpid); + drop(child); + } + // Check if any process group ID would become orphaned as a result of + // this exit. + for (affected_pgid, was_orphaned) in affected_pgids { + let is_orphaned = self.pgrp_is_orphaned(affected_pgid); + + if !was_orphaned.unwrap_or(false) + && is_orphaned.unwrap_or(false) + && let Some(group) = + self.groups.get(&affected_pgid).map(|r| r.borrow()) + { + for process_rc in + group.processes.iter().filter_map(|w| Weak::upgrade(&w)) + { + if !matches!( + process_rc.borrow().status, + ProcessStatus::Stopped(_) + ) { + continue; + } + let sighup_pid = process_rc.borrow().pid; + log::trace!("SENDING SIGCONT TO {sighup_pid:?}"); + if let Err(err) = self.on_send_sig( + INIT_PID, + KillTarget::Proc(sighup_pid), + SIGCONT as u8, + &mut false, + KillMode::Idempotent, + false, + awoken, + ) { + log::warn!("Failed to send newly-orphaned-pgid SIGHUP to PID {sighup_pid:?}: {err}"); + } + log::trace!("SENDING SIGHUP TO {sighup_pid:?}"); + if let Err(err) = self.on_send_sig( + INIT_PID, + KillTarget::Proc(sighup_pid), + SIGHUP as u8, + &mut false, + KillMode::Idempotent, + false, + awoken, + ) { + log::warn!("Failed to send newly-orphaned-pgid SIGHUP to PID {sighup_pid:?}: {err}"); + } + } + } + } + } + + 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 name_len = new_name_raw + .iter() + .position(|c| *c == 0) + .unwrap_or(new_name_raw.len()); + + let new_name = + core::str::from_utf8(&new_name_raw[..name_len]).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, parent = process.parent, + // parent's pgid == process's pgid + // OR + // parent's session id != process's session id + let cond = parent.pgid == process.pgid || parent.sid != process.sid; + if !cond { + log::trace!( + "COUNTEREXAMPLE: process {:#?} parent {:#?}", + process, + parent + ); + } + still_true &= cond; + } + 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/bootstrap/src/riscv64.ld b/bootstrap/src/riscv64.ld new file mode 100644 index 0000000000..98b5239bbd --- /dev/null +++ b/bootstrap/src/riscv64.ld @@ -0,0 +1,44 @@ +ENTRY(_start) +OUTPUT_FORMAT(elf64-littleriscv) + +SECTIONS { + . = 4096 + 4096; /* Reserved for the null page and the initfs header prepended by redox-initfs-ar */ + __initfs_header = . - 4096; + . += SIZEOF_HEADERS; + . = ALIGN(4096); + + .text : { + __text_start = .; + *(.text*) + . = ALIGN(4096); + __text_end = .; + } + .rodata : { + __rodata_start = .; + *(.rodata*) + . = ALIGN(4096); + __rodata_end = .; + } + .data : { + __data_start = .; + *(.data*) + *(.sdata*) + *(.got*) + . = ALIGN(4096); + __data_end = .; + + __bss_start = .; + *(.bss*) + *(.sbss*) + . = ALIGN(4096); + __bss_end = .; + } + + /DISCARD/ : { + *(.comment*) + *(.eh_frame*) + *(.gcc_except_table*) + *(.note*) + *(.rel.eh_frame*) + } +} diff --git a/bootstrap/src/riscv64.rs b/bootstrap/src/riscv64.rs new file mode 100644 index 0000000000..6a36039a4f --- /dev/null +++ b/bootstrap/src/riscv64.rs @@ -0,0 +1,47 @@ +use core::mem; +use syscall::{data::Map, flag::MapFlags, number::SYS_FMAP}; + +const STACK_SIZE: usize = 64 * 1024; // 64 KiB +pub const USERMODE_END: usize = 1 << 47; // Assuming Sv48; it should work with Sv57 also +pub const STACK_START: usize = USERMODE_END - STACK_SIZE; + +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), + address: STACK_START, // highest possible user address +}; + +core::arch::global_asm!( + " + .globl _start +_start: + # Setup a stack. + li a7, {number} + li a0, {fd} + la a1, {map} # pointer to Map struct + li a2, {map_size} # size of Map struct + ecall + + # Test for success (nonzero value). + bne a0, x0, 2f + # (failure) + unimp +2: + li sp, {stack_size} + add sp, sp, a0 + mv fp, x0 + + jal start + # `start` must never return. + unimp + ", + fd = const usize::MAX, // dummy fd indicates anonymous map + map = sym MAP, + map_size = const mem::size_of::(), + number = const SYS_FMAP, + stack_size = const STACK_SIZE, +); diff --git a/bootstrap/src/start.rs b/bootstrap/src/start.rs new file mode 100644 index 0000000000..f85ec084ab --- /dev/null +++ b/bootstrap/src/start.rs @@ -0,0 +1,84 @@ +use syscall::flag::MapFlags; + +mod offsets { + extern "C" { + // text (R-X) + static __text_start: u8; + static __text_end: u8; + // rodata (R--) + static __rodata_start: u8; + static __rodata_end: u8; + // data+bss (RW-) + static __data_start: u8; + static __bss_end: u8; + } + pub fn text() -> (usize, usize) { + unsafe { + ( + &__text_start as *const u8 as usize, + &__text_end as *const u8 as usize, + ) + } + } + pub fn rodata() -> (usize, usize) { + unsafe { + ( + &__rodata_start as *const u8 as usize, + &__rodata_end as *const u8 as usize, + ) + } + } + pub fn data_and_bss() -> (usize, usize) { + unsafe { + ( + &__data_start as *const u8 as usize, + &__bss_end as *const u8 as usize, + ) + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn start() -> ! { + // Remap self, from the previous RWX + + let (text_start, text_end) = offsets::text(); + let (rodata_start, rodata_end) = offsets::rodata(); + let (data_start, data_end) = offsets::data_and_bss(); + + let _ = syscall::open("/scheme/debug", syscall::O_RDONLY); // stdin + let _ = syscall::open("/scheme/debug", syscall::O_WRONLY); // stdout + let _ = syscall::open("/scheme/debug", syscall::O_WRONLY); // stderr + + let _ = syscall::mprotect(4096, 4096, MapFlags::PROT_READ | MapFlags::MAP_PRIVATE) + .expect("mprotect failed for initfs header page"); + + let _ = syscall::mprotect( + text_start, + text_end - text_start, + MapFlags::PROT_READ | MapFlags::PROT_EXEC | MapFlags::MAP_PRIVATE, + ) + .expect("mprotect failed for .text"); + let _ = syscall::mprotect( + rodata_start, + rodata_end - rodata_start, + MapFlags::PROT_READ | MapFlags::MAP_PRIVATE, + ) + .expect("mprotect failed for .rodata"); + let _ = syscall::mprotect( + data_start, + data_end - data_start, + MapFlags::PROT_READ | MapFlags::PROT_WRITE | MapFlags::MAP_PRIVATE, + ) + .expect("mprotect failed for .data/.bss"); + let _ = syscall::mprotect( + data_end, + crate::arch::STACK_START - data_end, + MapFlags::PROT_READ | MapFlags::MAP_PRIVATE, + ) + .expect("mprotect failed for rest of memory"); + + // FIXME make the initfs read-only + + crate::exec::main(); +} diff --git a/bootstrap/src/x86_64.ld b/bootstrap/src/x86_64.ld new file mode 100644 index 0000000000..ecb2094222 --- /dev/null +++ b/bootstrap/src/x86_64.ld @@ -0,0 +1,46 @@ +ENTRY(_start) +OUTPUT_FORMAT(elf64-x86-64) + +SECTIONS { + . = 4096 + 4096; /* Reserved for the null page and the initfs header prepended by redox-initfs-ar */ + __initfs_header = . - 4096; + . += SIZEOF_HEADERS; + . = ALIGN(4096); + + .text : { + __text_start = .; + *(.text*) + . = ALIGN(4096); + __text_end = .; + } + .rodata : { + __rodata_start = .; + *(.rodata*) + . = ALIGN(4096); + __rodata_end = .; + } + .data : { + __data_start = .; + *(.data*) + . = ALIGN(4096); + __data_end = .; + + *(.tbss*) + . = ALIGN(4096); + *(.tdata*) + . = ALIGN(4096); + + __bss_start = .; + *(.bss*) + . = ALIGN(4096); + __bss_end = .; + } + + /DISCARD/ : { + *(.comment*) + *(.eh_frame*) + *(.gcc_except_table*) + *(.note*) + *(.rel.eh_frame*) + } +} diff --git a/bootstrap/src/x86_64.rs b/bootstrap/src/x86_64.rs new file mode 100644 index 0000000000..f7110a64e9 --- /dev/null +++ b/bootstrap/src/x86_64.rs @@ -0,0 +1,49 @@ +use core::mem; +use syscall::{data::Map, flag::MapFlags, number::SYS_FMAP}; + +const STACK_SIZE: usize = 64 * 1024; // 64 KiB +pub const USERMODE_END: usize = 0x0000_8000_0000_0000; +pub const STACK_START: usize = USERMODE_END - STACK_SIZE; + +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), + address: STACK_START, // highest possible user address +}; + +core::arch::global_asm!( + " + .globl _start + _start: + # Setup a stack. + mov rax, {number} + mov rdi, {fd} + mov rsi, offset {map} # pointer to Map struct + mov rdx, {map_size} # size of Map struct + syscall + + # Test for success (nonzero value). + cmp rax, 0 + jg 1f + # (failure) + ud2 + 1: + # Subtract 16 since all instructions seem to hate non-canonical RSP values :) + lea rsp, [rax+{stack_size}-16] + mov rbp, rsp + + # Stack has the same alignment as `size`. + call start + # `start` must never return. + ud2 + ", + fd = const usize::MAX, // dummy fd indicates anonymous map + map = sym MAP, + map_size = const mem::size_of::(), + number = const SYS_FMAP, + stack_size = const STACK_SIZE, +);