//! Stage 16, "libc batch 1": the system calls musl and dash use once a //! program is running -- signal waiting (rt_sigsuspend, sigtimedwait, //! sigqueueinfo, sigaltstack), umask and rlimits, fcntl record locks, //! truncate/fsync/chmod/utimensat, prctl, sched_getaffinity, madvise, //! robust futex lists, and what happens to orphaned children. The detailed //! checks live in user/libctest.rs (exit code = failed checks); this file //! runs each group and checks nothing is left behind. #![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, memory, syscall, thread}; 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(); test_main(); rustbelt::hlt_loop(); } fn run(args: &[&str]) -> ExitStatus { let mut spawn = SpawnArgs::kernel("libctest"); spawn.args.extend(args.iter().map(|a| String::from(*a))); let p = process::spawn_path("/mnt/disk0/bin/libctest", spawn).expect("spawn failed"); process::wait(&p) } fn counts() -> (usize, usize, usize) { thread::sleep_ticks(3); // let exited threads' stacks be freed (thread::thread_count(), process::count(), memory::frames_in_use()) } /// Run one group twice (the first warms caches), check it passes and /// threads, processes, memory frames and file locks come back to where /// they were. fn run_without_leaks(group: &str) { assert_eq!(run(&[group]), ExitStatus::Exited(0), "libctest {} failed (warm-up)", group); let before = counts(); assert_eq!(run(&[group]), ExitStatus::Exited(0), "libctest {} failed", group); assert_eq!(counts(), before, "libctest {} leaked something", group); assert_eq!(fs::lock::count(), 0, "libctest {} left file locks behind", group); } /// dash's `wait`: block SIGCHLD, fork, rt_sigsuspend until the handler ran. /// Before stage 16 rt_sigsuspend was ENOSYS and dash spun forever here. #[test_case] fn dash_wait_with_sigsuspend() { run_without_leaks("wait"); } /// rt_sigpending, rt_sigtimedwait, queued real-time signals, sigaltstack. #[test_case] fn signal_waiting_and_queueing() { run_without_leaks("signals"); } /// fcntl: F_GETFL/F_SETFL, F_DUPFD vs RLIMIT_NOFILE, record locks between /// processes (conflicts, waiting, EDEADLK, dropped on close and exit). #[test_case] fn fcntl_flags_and_record_locks() { run_without_leaks("locks"); } /// truncate/ftruncate, fsync, chmod family, umask, utimensat on ramfs, /// ext2 and FAT; EROFS on btrfs. #[test_case] fn file_metadata_calls() { run_without_leaks("files"); } /// rlimits, prctl, sched_getaffinity, madvise, robust futex lists. #[test_case] fn process_odds_and_ends() { run_without_leaks("misc"); } /// A process exits leaving a running child and a zombie child: both are /// cleaned up (their frames come back); a PR_SET_CHILD_SUBREAPER ancestor /// adopts and reaps orphans instead. #[test_case] fn orphans_are_freed() { assert_eq!(run(&["orphans"]), ExitStatus::Exited(0), "libctest orphans failed (warm-up)"); thread::sleep_ticks(10); let before = counts(); for _ in 0..3 { assert_eq!(run(&["orphans"]), ExitStatus::Exited(0), "libctest orphans failed"); } thread::sleep_ticks(10); // the last orphan is still sleeping 40 ms assert_eq!(counts(), before, "orphaned children leaked"); } /// `--trace` names syscalls it doesn't know by number. #[test_case] fn trace_names_unknown_syscalls() { assert_eq!(syscall::trace_name(130), "rt_sigsuspend"); assert_eq!(syscall::trace_name(204), "sched_getaffinity"); assert_eq!(syscall::trace_name(999), "syscall_999"); } #[panic_handler] fn panic(info: &PanicInfo) -> ! { rustbelt::test_panic_handler(info) }