//! Stage 15, the plumbing a C library needs: threads and futexes, poll and //! epoll, nonblocking I/O, loopback networking, ttys and ptys. As in //! stage 14 the detailed checks live in user programs (threads, //! echoserver, ptytest, nbtest: exit code = failed checks); this file runs //! them and adds what only the kernel can see: nothing is left behind //! (threads, processes, futex waiters, ptys, sockets, memory frames). #![no_std] #![no_main] #![feature(custom_test_frameworks)] #![test_runner(rustbelt::test_runner)] #![reexport_test_harness_main = "test_main"] extern crate alloc; use alloc::string::String; use bootloader::{BootInfo, entry_point}; use core::panic::PanicInfo; use rustbelt::process::{self, ExitStatus, SpawnArgs}; use rustbelt::{fs, futex, memory, net, thread, tty}; entry_point!(main); fn main(boot_info: &'static BootInfo) -> ! { rustbelt::init(); rustbelt::init_memory(boot_info); rustbelt::init_smp(); // all 4 CPUs run threads fs::init(); rustbelt::init_storage(); rustbelt::init_net(); // brings up 127.0.0.1 (and eth0 if QEMU has one) test_main(); rustbelt::hlt_loop(); } fn run(name: &str, args: &[&str]) -> ExitStatus { let mut spawn = SpawnArgs::kernel(name); spawn.args.extend(args.iter().map(|a| String::from(*a))); let p = process::spawn_path(&alloc::format!("/mnt/disk0/bin/{}", name), spawn).expect("spawn failed"); process::wait(&p) } /// Snapshot of everything a buggy program could leak. #[derive(Debug, PartialEq)] struct Counts { threads: usize, processes: usize, futex_waiters: usize, ptys: usize, sockets: usize, frames: usize, } fn counts() -> Counts { thread::sleep_ticks(3); // let exited threads' stacks be freed Counts { threads: thread::thread_count(), processes: process::count(), futex_waiters: futex::waiters(), ptys: tty::pty_count(), sockets: net::socket_count(), frames: memory::frames_in_use(), } } /// Run a test program twice: once to warm caches, then again to check that /// it passes and that every count comes back to where it was. fn run_without_leaks(name: &str) { assert_eq!(run(name, &[]), ExitStatus::Exited(0), "{} failed (warm-up)", name); let before = counts(); assert_eq!(run(name, &[]), ExitStatus::Exited(0), "{} failed", name); let after = counts(); assert_eq!(after, before, "{} leaked something", name); assert_eq!(after.futex_waiters, 0, "futex waiters left behind"); assert_eq!(after.ptys, 0, "ptys left behind"); } /// 4 threads sum 1..=40,000,000 under a futex mutex, on all 4 CPUs. #[test_case] fn threads_and_futexes() { run_without_leaks("threads"); } /// One epoll and one poll server over 127.0.0.1, three forked clients each. #[test_case] fn poll_epoll_echo_over_loopback() { run_without_leaks("echoserver"); } /// pty round trips, canonical vs raw, ^C -> SIGINT, hangup on close. #[test_case] fn ptys_and_line_discipline() { run_without_leaks("ptytest"); } /// EAGAIN everywhere, fcntl/FIONBIO, writev/pread/pwrite, clocks. #[test_case] fn nonblocking_io_and_libc_extras() { run_without_leaks("nbtest"); } /// Loopback works without DHCP and without the NIC: 127.0.0.1 is local /// whatever eth0 is doing. #[test_case] fn loopback_is_always_there() { assert!(net::is_loopback([127, 0, 0, 1])); assert!(!net::is_loopback([10, 0, 2, 15])); } #[panic_handler] fn panic(info: &PanicInfo) -> ! { rustbelt::test_panic_handler(info) }