RustForge

rust-kernel

Rustbelt: a hobby x86_64 kernel written in Rust

rust-kernel / tests / boot_v2.rs
5988 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
//! Stage 21: boot contract v2. scripts/qemu.sh gives this test kernel one
//! more disk, target/rootfs-gpt.img (build.rs): a GPT disk with an ext2
//! partition of type "Linux root (x86-64)" and an exFAT "Microsoft basic
//! data" partition. Its /sbin/init, /bin/init and /bin/sh are all
//! user/inittest.c, which exits 7 (from /etc/init-status) when every check
//! passes as /sbin/init, 21 as /bin/init and 22 as /bin/sh.
//!
//! (The other half, "no root partition = the old layout", is checked by
//! tests/fat.rs and by scripts/run-headless.sh; main.rs's use of all this,
//! with QEMU's exit code, by scripts/boot-v2-check.sh.)
#![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 alloc::vec::Vec;
use bootloader::{BootInfo, entry_point};
use core::panic::PanicInfo;
use rustbelt::boot;
use rustbelt::errno::Errno;
use rustbelt::fs::{self, InodeKind};
use rustbelt::process::{self, ExitStatus};
use rustbelt::{memory, thread};

entry_point!(main);

fn main(boot_info: &'static BootInfo) -> ! {
    rustbelt::init();
    rustbelt::init_memory(boot_info);
    rustbelt::init_smp();
    fs::init();
    rustbelt::init_storage();
    rustbelt::timer::start(); // (its kernel stack isn't a leak)
    test_main();
    rustbelt::hlt_loop();
}

fn mount_type(path: &str) -> Option<&'static str> {
    fs::mounts().into_iter().find(|(p, _)| p == path).map(|(_, t)| t)
}

/// The first process this kernel starts: /sbin/init as pid 1. inittest
/// checks pid 1, argv, PATH, the mounts, /mnt/share and msync itself, and
/// exits 7 only if all of that held.
#[test_case]
fn init_runs_as_pid_1_and_its_status_comes_back() {
    let mut log = Vec::new();
    let before = memory::frames_in_use();
    let (path, status) = boot::run_init(false, &mut log).expect("no init started");
    assert_eq!(path, "/sbin/init");
    assert_eq!(status, ExitStatus::Exited(7), "inittest's checks failed (see its [inittest] lines)");
    assert!(log.is_empty(), "{:?}", log);
    // What init wrote to the share is really there.
    assert_eq!(fs::read_all("/mnt/share/from-init.txt").unwrap(), b"written by init\n");
    thread::sleep_ticks(3);
    assert_eq!(process::count(), 0);
    assert_eq!(memory::frames_in_use(), before, "running init leaked frames");
}

#[test_case]
fn the_root_partition_is_slash_and_the_rest_is_as_before() {
    assert!(boot::v2());
    assert_eq!(mount_type("/"), Some("ext2"));
    assert!(fs::read_all("/etc/rootfs-v2").unwrap().starts_with(b"This is the ext2 root"));
    // The kernel's own mounts are still there...
    assert_eq!(mount_type("/tmp"), Some("ramfs"));
    assert!(mount_type("/dev").is_some() && mount_type("/proc").is_some());
    // ...the FAT32 data disk is still /mnt/disk0 (B:), and the other disks
    // keep their numbers: the root and the share don't take one.
    assert_eq!(mount_type("/mnt/disk0"), Some("fat32"));
    assert_eq!(mount_type("/mnt/disk1"), Some("ext2"));
    assert_eq!(mount_type("/mnt/disk2"), Some("exfat"));
    assert!(mount_type("/mnt/disk9").is_some());
    assert!(mount_type("/mnt/disk10").is_none());
    assert!(fs::lookup("/mnt/disk0/bin").is_ok());
    // The exFAT partition is the share, mounted read-write.
    assert_eq!(mount_type("/mnt/share"), Some("exfat"));
    assert_eq!(fs::read_all("/mnt/share/hello.txt").unwrap(), b"Hello from the exFAT share!\n");
    let f = fs::create("/mnt/share/kernel.txt", InodeKind::File).unwrap();
    f.write_at(0, b"rw").unwrap();
    assert_eq!(fs::read_all("/mnt/share/kernel.txt").unwrap(), b"rw");
    fs::remove("/mnt/share/kernel.txt", false).unwrap();
}

#[test_case]
fn init_gets_the_documented_argv_and_env() {
    let s = |v: &[&str]| v.iter().map(|x| String::from(*x)).collect::<Vec<_>>();
    let a = boot::init_args("/sbin/init", false);
    assert_eq!(a.args, s(&["init"]));
    assert_eq!(a.env, s(&["PATH=/sbin:/bin:/usr/bin:/mnt/disk0/bin", "HOME=/", "TERM=linux"]));
    let a = boot::init_args("/bin/init", true);
    assert_eq!(a.args, s(&["init", "selftest"]));
    assert!(a.env.iter().any(|e| e == "RUSTBELT_HEADLESS=1"));
    let a = boot::init_args("/bin/sh", true);
    assert_eq!(a.args, s(&["sh", "/scripts/selftest.sh"]));
}

/// Take away /sbin/init, then /bin/init, then /bin/sh (the disk is
/// snapshot=on, so the file stays): the next one in line starts each time.
#[test_case]
fn the_fallback_chain() {
    let mut log = Vec::new();
    fs::remove("/sbin/init", false).unwrap();
    assert_eq!(boot::run_init(false, &mut log), Ok(("/bin/init", ExitStatus::Exited(21))));
    fs::remove("/bin/init", false).unwrap();
    assert_eq!(boot::run_init(false, &mut log), Ok(("/bin/sh", ExitStatus::Exited(22))));
    fs::remove("/bin/sh", false).unwrap();
    assert_eq!(boot::run_init(false, &mut log), Err(Errno::ENOENT));
    // Something that isn't a program is skipped (and logged), like Linux.
    fs::create("/sbin/init", InodeKind::File).unwrap().write_at(0, b"not an ELF file").unwrap();
    assert_eq!(boot::run_init(false, &mut log), Err(Errno::ENOENT));
    assert_eq!(log.len(), 1, "{:?}", log);
    assert!(log[0].starts_with("/sbin/init: can't start it"), "{:?}", log);
}

/// pid 1's status -> the value for isa-debug-exit (QEMU exits with
/// 2 * value + 1): exit(n) -> 33 + 2n.
#[test_case]
fn exit_status_to_qemu() {
    assert_eq!(boot::qemu_exit_value(ExitStatus::Exited(0)), 0x10); // 33, as before
    assert_eq!(boot::qemu_exit_value(ExitStatus::Exited(1)), 0x11); // 35, as before
    assert_eq!(boot::qemu_exit_value(ExitStatus::Exited(7)), 0x17); // 47
    assert_eq!(boot::qemu_exit_value(ExitStatus::Exited(111)), 0x7f); // 255
    assert_eq!(boot::qemu_exit_value(ExitStatus::Exited(112)), 0x11);
    assert_eq!(boot::qemu_exit_value(ExitStatus::Killed(9, "SIGKILL")), 0x11);
}

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