rust-kernel
Rustbelt: a hobby x86_64 kernel written in Rust
6444 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 | //! VFS + ramfs, used directly from the kernel (no user mode involved).
#![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, vec, vec::Vec};
use bootloader::{BootInfo, entry_point};
use core::panic::PanicInfo;
use rustbelt::errno::Errno;
use rustbelt::fs::file::{File, InodeFile, KIND_FILE, SEEK_CUR, SEEK_END, SEEK_SET, Stat};
use rustbelt::fs::{self, InodeKind, ramfs::RamFs};
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();
test_main();
rustbelt::hlt_loop();
}
#[test_case]
fn read_boot_files() {
let file = InodeFile::open(fs::lookup("/hello.txt").unwrap(), "/hello.txt".into()).unwrap();
let mut buf = [0u8; 64];
let n = file.read(&mut buf).unwrap();
assert!(buf[..n].starts_with(b"Hello from the ramfs!"));
assert_eq!(file.read(&mut buf), Ok(0)); // EOF
assert_eq!(fs::lookup("/etc/motd").unwrap().kind(), InodeKind::File);
assert_eq!(fs::lookup("/etc").unwrap().kind(), InodeKind::Directory);
assert_eq!(fs::lookup("//etc/./motd").unwrap().kind(), InodeKind::File);
}
#[test_case]
fn lookup_errors() {
assert_eq!(fs::lookup("/nope").err(), Some(Errno::ENOENT));
assert_eq!(fs::lookup("/hello.txt/x").err(), Some(Errno::ENOTDIR));
assert_eq!(fs::lookup("relative").err(), Some(Errno::EINVAL));
// Directories can be opened (for getdents) but not read like files.
let dir = InodeFile::open(fs::lookup("/etc").unwrap(), "/etc".into()).unwrap();
assert_eq!(dir.read(&mut [0u8; 8]), Err(Errno::EISDIR));
}
#[test_case]
fn mounts_are_separate_file_systems() {
// /tmp is its own ramfs: files created there don't show up in "/"'s tmp dir.
fs::create("/tmp/a.txt", InodeKind::File).unwrap();
assert!(fs::lookup("/tmp/a.txt").is_ok());
let root_tmp_dir = fs::lookup("/").unwrap().lookup("tmp").unwrap();
assert_eq!(root_tmp_dir.list().unwrap().len(), 0);
assert_eq!(fs::create("/tmp/a.txt", InodeKind::File).err(), Some(Errno::EEXIST));
// Mount a third one; the longest matching mount point wins.
fs::create("/tmp/deeper", InodeKind::Directory).unwrap();
fs::mount("/tmp/deeper", RamFs::new()).unwrap();
assert_eq!(fs::lookup("/tmp/deeper").unwrap().list().unwrap().len(), 0);
assert_eq!(fs::mount("/tmp/deeper", RamFs::new()).err(), Some(Errno::EEXIST));
assert_eq!(fs::mounts().len(), 4); // "/", /dev (devfs), /tmp, /tmp/deeper
}
#[test_case]
fn write_grow_and_list() {
let inode = fs::create("/tmp/grow.bin", InodeKind::File).unwrap();
inode.write_at(4, b"abc").unwrap(); // gap is filled with zeros
let mut buf = vec![0xffu8; 10];
assert_eq!(inode.read_at(0, &mut buf), Ok(7));
assert_eq!(&buf[..7], b"\0\0\0\0abc");
let names: Vec<String> = fs::lookup("/").unwrap().list().unwrap().into_iter().map(|e| e.name).collect();
assert_eq!(names, vec!["dev", "etc", "hello.txt", "mnt", "tmp"]);
}
#[test_case]
fn paths_are_normalized() {
assert_eq!(fs::normalize("/", "a/b").unwrap(), "/a/b");
assert_eq!(fs::normalize("/home/tyler", "../etc//./motd").unwrap(), "/home/etc/motd");
assert_eq!(fs::normalize("/x", "/../../y/").unwrap(), "/y"); // ".." of "/" is "/"
assert_eq!(fs::normalize("/x", "..").unwrap(), "/");
assert_eq!(fs::normalize("/", "").err(), Some(Errno::ENOENT));
// ".." works across mount points: /tmp/.. is the root ramfs again.
assert_eq!(fs::lookup("/tmp/../etc/motd").unwrap().kind(), InodeKind::File);
}
#[test_case]
fn remove_files_and_directories() {
fs::create("/tmp/rm", InodeKind::Directory).unwrap();
fs::create("/tmp/rm/f", InodeKind::File).unwrap();
assert_eq!(fs::remove("/tmp/rm", true), Err(Errno::ENOTEMPTY));
assert_eq!(fs::remove("/tmp/rm", false), Err(Errno::EISDIR)); // unlink a dir
assert_eq!(fs::remove("/tmp/rm/f", true), Err(Errno::ENOTDIR)); // rmdir a file
fs::remove("/tmp/rm/f", false).unwrap();
fs::remove("/tmp/rm", true).unwrap();
assert_eq!(fs::lookup("/tmp/rm").err(), Some(Errno::ENOENT));
assert_eq!(fs::remove("/tmp", true), Err(Errno::EBUSY)); // mount point
}
#[test_case]
fn seek_stat_and_truncate() {
let inode = fs::create("/tmp/seek.txt", InodeKind::File).unwrap();
let file = InodeFile::open(inode.clone(), "/tmp/seek.txt".into()).unwrap();
file.write(b"0123456789").unwrap();
assert_eq!(file.seek(-3, SEEK_END), Ok(7));
let mut buf = [0u8; 8];
assert_eq!(file.read(&mut buf), Ok(3));
assert_eq!(&buf[..3], b"789");
assert_eq!(file.seek(2, SEEK_SET), Ok(2));
assert_eq!(file.seek(1, SEEK_CUR), Ok(3));
assert_eq!(file.seek(-4, SEEK_CUR), Err(Errno::EINVAL)); // before the start
assert_eq!(file.stat(), Ok(Stat { kind: KIND_FILE, size: 10, mode: 0 }));
inode.truncate().unwrap();
assert_eq!(inode.size(), 0);
}
#[test_case]
fn getdents_returns_linux_dirent64_records() {
let dir = InodeFile::open(fs::lookup("/etc").unwrap(), "/etc".into()).unwrap();
let mut buf = [0u8; 64];
let n = dir.getdents(&mut buf).unwrap();
// One entry, "motd": 19-byte header + "motd\0" = 24 bytes (already 8-aligned).
assert_eq!(n, 24);
assert_eq!(u16::from_le_bytes([buf[16], buf[17]]), 24); // d_reclen
assert_eq!(buf[18], 8); // DT_REG
assert_eq!(&buf[19..24], b"motd\0");
assert_eq!(dir.getdents(&mut buf), Ok(0)); // end of directory
assert_eq!(dir.seek(0, SEEK_SET), Ok(0)); // rewind
assert_eq!(dir.getdents(&mut [0u8; 10]), Err(Errno::EINVAL)); // too small
}
#[test_case]
fn pipes_pass_bytes_and_report_eof() {
let (reader, writer) = fs::pipe::new();
assert_eq!(writer.write(b"through the pipe"), Ok(16));
let mut buf = [0u8; 7];
assert_eq!(reader.read(&mut buf), Ok(7));
assert_eq!(&buf, b"through");
assert_eq!(reader.seek(0, SEEK_SET), Err(Errno::ESPIPE));
drop(writer); // last writer gone: remaining data, then EOF (no blocking)
let mut rest = [0u8; 32];
assert_eq!(reader.read(&mut rest), Ok(9));
assert_eq!(reader.read(&mut rest), Ok(0));
let (reader, writer) = fs::pipe::new();
drop(reader);
assert_eq!(writer.write(b"x"), Err(Errno::EPIPE));
}
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
rustbelt::test_panic_handler(info)
}
|