//! User mode: real ring-3 programs (user/*.rs, loaded from the FAT32 disk) //! run as processes, make syscalls, get killed when they misbehave, get //! preempted when they hog the CPU, spawn children and wait for them. //! Plus the ELF loader, user-pointer checks and blocking primitives. #![no_std] #![no_main] #![feature(custom_test_frameworks)] #![test_runner(rust_kernel::test_runner)] #![reexport_test_harness_main = "test_main"] extern crate alloc; use alloc::{string::String, sync::Arc, vec, vec::Vec}; use bootloader::{BootInfo, entry_point}; use core::panic::PanicInfo; use core::sync::atomic::{AtomicBool, Ordering}; use rust_kernel::errno::Errno; use rust_kernel::fs::file::File; use rust_kernel::interrupts::TICKS; use rust_kernel::memory::{self, USER_START}; use rust_kernel::process::{self, ExitStatus, SpawnArgs}; use rust_kernel::thread::{self, WaitQueue}; use rust_kernel::{elf, fs, syscall}; entry_point!(main); fn main(boot_info: &'static BootInfo) -> ! { rust_kernel::init(); rust_kernel::init_memory(boot_info); fs::init(); rust_kernel::init_storage(); test_main(); rust_kernel::hlt_loop(); } const BIN: &str = "/mnt/disk0/bin"; /// Start /mnt/disk0/bin/ with these arguments (after the name). fn run(name: &str, extra_args: &[&str]) -> Arc { let mut spawn = SpawnArgs::kernel(name); spawn.args.extend(extra_args.iter().map(|a| String::from(*a))); process::spawn_path(&alloc::format!("{}/{}", BIN, name), spawn).expect("spawn failed") } /// hello does ~10 syscalls (open/read/write/close on the ramfs, error cases) /// and exits with the number of failed checks. #[test_case] fn hello_program_passes_its_syscall_checks() { let p = run("hello", &[]); assert_eq!(process::wait(&p), ExitStatus::Exited(0)); // It created /tmp/note.txt through the syscalls; the kernel sees it too. assert_eq!(fs::lookup("/tmp/note.txt").expect("hello's file is missing").size(), 19); } /// demo checks cwd, FAT reads, stat, lseek, getdents64, mkdir/rmdir/unlink, /// pipes, spawn + wait (incl. a killed child) -- exit code = failures. #[test_case] fn demo_program_passes_its_checks() { let p = run("demo", &[]); assert_eq!(process::wait(&p), ExitStatus::Exited(0)); } /// evil reads kernel memory -> page fault in ring 3 -> killed. We're still here. #[test_case] fn kernel_memory_access_kills_only_the_process() { let p = run("evil", &[]); let status = process::wait(&p); assert_eq!(status, ExitStatus::Killed("page fault")); assert_eq!(status.code(), 139); } /// spin never makes a syscall while it loops. If preemption didn't work, /// this kernel thread couldn't run again until spin finished. #[test_case] fn spinning_process_is_preempted() { let p = run("spin", &[]); let start = TICKS.load(Ordering::Relaxed); let mut samples_while_spinning = 0; while p.status().is_none() { // Busy-wait (no hlt): we only get here again if the timer took the // CPU away from spin and gave it back to us. if TICKS.load(Ordering::Relaxed) > start + 3 { samples_while_spinning += 1; } assert!(TICKS.load(Ordering::Relaxed) < start + 18 * 30, "spin didn't finish"); core::hint::spin_loop(); } assert_eq!(process::wait(&p), ExitStatus::Exited(0)); assert!(samples_while_spinning > 0, "never ran while spin was running"); } /// Arguments arrive in the child; its exit code comes back through wait. /// (Tracing is on: its syscalls get printed to the serial log.) #[test_case] fn arguments_and_exit_codes() { let mut spawn = SpawnArgs::kernel("child"); spawn.args.push(String::from("7")); spawn.trace = true; let p = process::spawn_path("/mnt/disk0/bin/child", spawn).unwrap(); assert_eq!(process::wait(&p), ExitStatus::Exited(7)); // Reaped: the pid is gone from the process table. assert!(process::get(p.pid).is_none()); } /// A child's stdout can be a pipe: we read what it printed. #[test_case] fn child_stdout_into_a_pipe() { let (reader, writer) = fs::pipe::new(); let mut spawn = SpawnArgs::kernel("child"); spawn.args.push(String::from("pipe")); spawn.stdio[1] = Arc::new(writer); let p = process::spawn_path("/mnt/disk0/bin/child", spawn).unwrap(); let mut out = Vec::new(); let mut buf = [0u8; 32]; loop { // Blocks until the child writes or exits (its exit closes the pipe). match reader.read(&mut buf).unwrap() { 0 => break, n => out.extend_from_slice(&buf[..n]), } } assert!(out.starts_with(b"hello through a pipe from pid")); assert_eq!(process::wait(&p), ExitStatus::Exited(0)); } /// wait with WNOHANG: EAGAIN while the child runs (the shell uses this to /// check on background jobs without blocking). #[test_case] fn wait_nohang_does_not_block() { let (reader, writer) = fs::pipe::new(); let mut spawn = SpawnArgs::kernel("child"); spawn.args.push(String::from("cat")); // copies stdin until EOF spawn.stdio[0] = Arc::new(reader); let p = process::spawn_path("/mnt/disk0/bin/child", spawn).unwrap(); assert_eq!(process::wait_child(0, p.pid, process::WNOHANG), Err(Errno::EAGAIN)); drop(writer); // EOF for the child: it exits assert_eq!(process::wait(&p), ExitStatus::Exited(0)); } #[test_case] fn spawn_errors() { let spawn = || SpawnArgs::kernel("x"); assert_eq!(process::spawn_path("/mnt/disk0/bin/nope", spawn()).err(), Some(Errno::ENOENT)); assert_eq!(process::spawn_path("/mnt/disk0/readme.txt", spawn()).err(), Some(Errno::ENOEXEC)); assert_eq!(process::spawn_path("/mnt/disk0/bin", spawn()).err(), Some(Errno::EISDIR)); assert_eq!(process::wait_child(12345, 1, 0).err(), Some(Errno::ECHILD)); } #[test_case] fn elf_loader_rejects_garbage() { let hello = fs::read_all("/mnt/disk0/bin/hello").unwrap(); assert_eq!(elf::check(&[b'x'; 128]), Err(elf::ElfError::NotElf)); assert_eq!(elf::check(b"\x7fELF"), Err(elf::ElfError::TooShort)); let entry = elf::check(&hello).expect("hello should parse"); assert!(entry >= USER_START); // A truncated program: the header is fine, the program headers are cut off. assert!(elf::check(&hello[..70]).is_err()); } #[test_case] fn user_pointers_are_validated() { // Kernel addresses, unmapped user addresses and overflowing ranges all fail. assert_eq!(syscall::user_slice(0x20_0000, 16).err(), Some(Errno::EFAULT)); assert_eq!(syscall::user_slice(USER_START, 16).err(), Some(Errno::EFAULT)); assert_eq!(syscall::user_slice(u64::MAX - 4, 16).err(), Some(Errno::EFAULT)); assert!(!memory::user_range_ok(USER_START - 8, 16, false)); // Zero bytes: any pointer is fine. assert_eq!(syscall::user_slice(1, 0).map(|s| s.len()), Ok(0)); } /// sleep_ticks really blocks: the thread is off the run queue meanwhile. #[test_case] fn sleep_blocks_for_the_requested_time() { let start = TICKS.load(Ordering::Relaxed); thread::sleep_ticks(4); let slept = TICKS.load(Ordering::Relaxed) - start; assert!((4..8).contains(&slept), "slept {} ticks", slept); } static FLAG: AtomicBool = AtomicBool::new(false); static QUEUE: WaitQueue = WaitQueue::new(); /// A thread blocked on a WaitQueue wakes when another thread signals it. #[test_case] fn wait_queue_wakes_the_waiter() { thread::spawn("waker", || { thread::sleep_ticks(2); FLAG.store(true, Ordering::SeqCst); QUEUE.wake_all(); }); let start = TICKS.load(Ordering::Relaxed); let value = QUEUE.wait_until(|| FLAG.load(Ordering::SeqCst).then_some(42)); assert_eq!(value, 42); assert!(TICKS.load(Ordering::Relaxed) - start >= 2); let _ = vec![0u8; 1]; // the heap still works after all the switching } #[panic_handler] fn panic(info: &PanicInfo) -> ! { rust_kernel::test_panic_handler(info) }