RustForge

rust-kernel

Rustbelt: a hobby x86_64 kernel written in Rust

rust-kernel / tests / process.rs
5864 bytes ยท raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
//! Stage 14, the process model: fork with copy-on-write, execve with a
//! Linux-style initial stack, mmap and demand paging, signals, wait4, job
//! control. Most checks run inside the user test programs (forktest,
//! cowtest, mmaptest, sigtest: exit code = failed checks); this file starts
//! them and adds what only the kernel can see: memory leaks, the process
//! table limit, Ctrl+C from the keyboard.
#![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, sync::Arc};
use bootloader::{BootInfo, entry_point};
use core::panic::PanicInfo;
use rustbelt::process::{self, ExitStatus, SpawnArgs};
use rustbelt::{fs, memory, signal, 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();
    test_main();
    rustbelt::hlt_loop();
}

const BIN: &str = "/mnt/disk0/bin";

fn spawn(name: &str, args: &[&str], env: &[&str]) -> Arc<process::Process> {
    let mut spawn = SpawnArgs::kernel(name);
    spawn.args.extend(args.iter().map(|a| String::from(*a)));
    if !env.is_empty() {
        spawn.env = env.iter().map(|e| String::from(*e)).collect();
    }
    process::spawn_path(&alloc::format!("{}/{}", BIN, name), spawn).expect("spawn failed")
}

fn run(name: &str, args: &[&str]) -> ExitStatus {
    let p = spawn(name, args, &[]);
    process::wait(&p) // (also removes it from the process table)
}

/// Writes in a forked child don't show up in the parent (and back),
/// MAP_SHARED pages are shared.
#[test_case]
fn copy_on_write_isolation() {
    assert_eq!(run("cowtest", &[]), ExitStatus::Exited(0));
}

/// fork, wait4, execve, vfork, dup2 through pipes, a SIGSEGV'd child.
#[test_case]
fn fork_exec_wait() {
    assert_eq!(run("forktest", &[]), ExitStatus::Exited(0));
}

/// A program started by the kernel gets argv, envp and auxv exactly like
/// on Linux (AT_PHDR, AT_PAGESZ, AT_ENTRY, AT_RANDOM, AT_EXECFN...).
#[test_case]
fn exec_argv_envp_auxv() {
    let p = spawn("forktest", &["args", "two words", ""], &["FOO=bar", "EMPTY="]);
    assert_eq!(process::wait(&p), ExitStatus::Exited(0));
}

/// mmap/munmap/mprotect/brk, demand paging, and faults delivered to a
/// SIGSEGV handler that fixes them.
#[test_case]
fn mmap_and_fault_handlers() {
    assert_eq!(run("mmaptest", &[]), ExitStatus::Exited(0));
}

/// Without a handler, a bad write is SIGSEGV's default: terminate, 139.
#[test_case]
fn unhandled_fault_is_sigsegv() {
    let status = run("mmaptest", &["crash"]);
    assert!(matches!(status, ExitStatus::Killed(signal::SIGSEGV, _)), "{:?}", status);
    assert_eq!(status.code(), 139);
    assert_eq!(status.wait_status(), 11);
}

/// Handlers + sigreturn, masks, default actions, SIGCHLD, stop/continue,
/// SIGPIPE, EINTR and SA_RESTART, kill on process groups.
#[test_case]
fn signals() {
    assert_eq!(run("sigtest", &[]), ExitStatus::Exited(0));
}

/// wait4's status word, the way the W* macros decode it.
#[test_case]
fn wait_status_encoding() {
    assert_eq!(ExitStatus::Exited(3).wait_status(), 0x300);
    assert_eq!(ExitStatus::Exited(256 + 7).wait_status(), 0x700); // only 8 bits survive
    assert_eq!(ExitStatus::Killed(signal::SIGKILL, "x").wait_status(), 9);
    assert_eq!(ExitStatus::Killed(signal::SIGINT, "x").code(), 130);
}

/// A fork bomb hits the process limit (EAGAIN) instead of eating all
/// memory, and after killing it everything is given back.
#[test_case]
fn fork_bomb_is_bounded_and_leaks_nothing() {
    assert_eq!(run("forktest", &["bomb"]), ExitStatus::Exited(0)); // warm up (caches, page tables)
    let (frames, processes) = (memory::frames_in_use(), process::count());
    assert_eq!(run("forktest", &["bomb"]), ExitStatus::Exited(0));
    thread::sleep_ticks(2); // let exited threads' stacks be freed
    assert_eq!(process::count(), processes);
    assert_eq!(memory::frames_in_use(), frames, "frames leaked by the fork bomb");
}

/// Many rounds of fork + exec + exit + wait: the frame count comes back
/// exactly (shared-frame refcounts drop to zero, page tables are freed).
#[test_case]
fn no_frame_leaks_across_fork_exec_exit() {
    run("forktest", &[]);
    run("cowtest", &[]);
    let frames = memory::frames_in_use();
    let shared = memory::shared_frame_count();
    for _ in 0..3 {
        assert_eq!(run("forktest", &[]), ExitStatus::Exited(0));
        assert_eq!(run("cowtest", &[]), ExitStatus::Exited(0));
    }
    thread::sleep_ticks(2);
    assert_eq!(memory::shared_frame_count(), shared, "copy-on-write refcounts left behind");
    assert_eq!(memory::frames_in_use(), frames, "frames leaked across fork/exec/exit");
}

/// Ctrl+C on the console: the tty sends SIGINT to the foreground process
/// group, and `spin` (which never makes a syscall) dies of it.
#[test_case]
fn ctrl_c_interrupts_the_foreground_job() {
    let p = spawn("spin", &["1000"], &[]); // ~minutes of spinning
    thread::sleep_ticks(4);
    assert!(p.status().is_none(), "spin ended by itself");
    tty::set_foreground(p.pgid());
    tty::input_char('\u{3}'); // what the keyboard driver delivers for Ctrl+C
    let status = process::wait(&p);
    tty::set_foreground(0);
    assert!(matches!(status, ExitStatus::Killed(signal::SIGINT, _)), "{:?}", status);
    assert_eq!(status.code(), 130);
}

/// Ctrl+C with nobody in the foreground hits nobody.
#[test_case]
fn ctrl_c_without_a_foreground_job_does_nothing() {
    let p = spawn("spin", &["2"], &[]);
    tty::set_foreground(0);
    tty::input_char('\u{3}');
    assert_eq!(process::wait(&p), ExitStatus::Exited(0));
}

#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
    rustbelt::test_panic_handler(info)
}