RustForge

rust-kernel

Rustbelt: a hobby x86_64 kernel written in Rust

rust-kernel / tests / fat.rs
5506 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
//! FAT32 through the VFS: the partition on target/disk.img is mounted at
//! /mnt/disk0 (writes are fine: QEMU uses snapshot=on).
#![no_std]
#![no_main]
#![feature(custom_test_frameworks)]
#![test_runner(rust_kernel::test_runner)]
#![reexport_test_harness_main = "test_main"]

extern crate alloc;

use alloc::{string::String, sync::Arc, vec, vec::Vec};
use bootloader::{BootInfo, entry_point};
use core::panic::PanicInfo;
use fatfs::{Read, Seek, SeekFrom, Write};
use rust_kernel::block::{BlockDevice, RamDisk, SECTOR_SIZE};
use rust_kernel::errno::Errno;
use rust_kernel::fs::fat::BlockIo;
use rust_kernel::fs::file::{File, InodeFile};
use rust_kernel::fs::{self, InodeKind};
use rust_kernel::elf;

entry_point!(main);

fn main(boot_info: &'static BootInfo) -> ! {
    rust_kernel::init();
    rust_kernel::init_memory(boot_info);
    fs::init();
    rust_kernel::init_storage();
    test_main();
    rust_kernel::hlt_loop();
}

#[test_case]
fn fat_volume_is_mounted() {
    assert!(fs::mounts().iter().any(|(path, kind)| path == "/mnt/disk0" && *kind == "fat32"));
    let mut names: Vec<String> = fs::lookup("/mnt/disk0").unwrap().list().unwrap().into_iter().map(|e| e.name).collect();
    names.sort(); // FAT keeps directory entries in creation order
    assert_eq!(names, vec!["bin", "notes", "readme.txt", "scripts"]);
}

#[test_case]
fn read_text_file() {
    let file = InodeFile::open(fs::lookup("/mnt/disk0/readme.txt").unwrap()).unwrap();
    let mut buf = [0u8; 256];
    let n = file.read(&mut buf).unwrap();
    assert!(buf[..n].starts_with(b"Hello from the FAT32 disk!"));
    assert_eq!(n as u64, file.stat().unwrap().size);
    assert_eq!(file.read(&mut buf), Ok(0));
    // FAT is case-insensitive; subdirectories work.
    assert!(fs::lookup("/mnt/disk0/README.TXT").is_ok());
    assert_eq!(fs::lookup("/mnt/disk0/notes/todo.txt").unwrap().kind(), InodeKind::File);
    assert_eq!(fs::lookup("/mnt/disk0/nope").err(), Some(Errno::ENOENT));
    assert_eq!(fs::lookup("/mnt/disk0/readme.txt/x").err(), Some(Errno::ENOTDIR));
}

#[test_case]
fn programs_on_disk_are_valid_elf_files() {
    let bin = fs::lookup("/mnt/disk0/bin").unwrap();
    for entry in bin.list().unwrap() {
        let path = alloc::format!("/mnt/disk0/bin/{}", entry.name);
        let image = fs::read_all(&path).unwrap();
        assert_eq!(image.len(), fs::lookup(&path).unwrap().size());
        assert!(elf::check(&image).is_ok(), "{} is not a valid program", entry.name);
    }
}

#[test_case]
fn create_write_read_remove() {
    // 10 000 bytes = 20 clusters (1 sector each): exercises the FAT chain.
    let data: Vec<u8> = (0..10_000u32).map(|i| (i * 7 % 256) as u8).collect();
    let inode = fs::create("/mnt/disk0/notes/big.bin", InodeKind::File).unwrap();
    assert_eq!(inode.write_at(0, &data), Ok(data.len()));
    let mut back = vec![0u8; 12_000];
    assert_eq!(inode.read_at(0, &mut back), Ok(10_000));
    assert_eq!(&back[..10_000], &data[..]);
    // Overwrite in the middle, read across a sector boundary.
    inode.write_at(510, b"XYZW").unwrap();
    let mut four = [0u8; 4];
    inode.read_at(510, &mut four).unwrap();
    assert_eq!(&four, b"XYZW");
    // Writing past the end fills the gap with zeros.
    inode.write_at(10_010, b"!").unwrap();
    assert_eq!(inode.size(), 10_011);
    inode.read_at(10_000, &mut four).unwrap();
    assert_eq!(four, [0; 4]);
    // A fresh lookup sees the same data (it really went to the disk).
    assert_eq!(fs::lookup("/mnt/disk0/notes/big.bin").unwrap().size(), 10_011);
    inode.truncate().unwrap();
    assert_eq!(inode.size(), 0);
    fs::remove("/mnt/disk0/notes/big.bin", false).unwrap();
    assert_eq!(fs::lookup("/mnt/disk0/notes/big.bin").err(), Some(Errno::ENOENT));
}

#[test_case]
fn directories_on_fat() {
    fs::create("/mnt/disk0/newdir", InodeKind::Directory).unwrap();
    assert_eq!(fs::create("/mnt/disk0/newdir", InodeKind::Directory).err(), Some(Errno::EEXIST));
    fs::create("/mnt/disk0/newdir/a long file name.txt", InodeKind::File).unwrap();
    let list = fs::lookup("/mnt/disk0/newdir").unwrap().list().unwrap();
    assert_eq!(list.len(), 1);
    assert_eq!(list[0].name, "a long file name.txt"); // long file names (VFAT)
    assert_eq!(fs::remove("/mnt/disk0/newdir", true), Err(Errno::ENOTEMPTY));
    fs::remove("/mnt/disk0/newdir/a long file name.txt", false).unwrap();
    fs::remove("/mnt/disk0/newdir", true).unwrap();
    assert_eq!(fs::remove("/mnt/disk0", true), Err(Errno::EBUSY)); // mount point
}

#[test_case]
fn block_io_adapter_handles_unaligned_access() {
    // The byte-stream adapter fatfs uses, on a RAM disk.
    let disk: Arc<dyn BlockDevice> = Arc::new(RamDisk::new(4));
    let mut io = BlockIo::new(disk.clone());
    io.seek(SeekFrom::Start(508)).unwrap();
    io.write_all(b"spans two sectors").unwrap(); // bytes 508..525
    let mut sector = [0u8; SECTOR_SIZE];
    disk.read_sectors(1, &mut sector).unwrap();
    assert_eq!(&sector[..13], b"s two sectors"); // "span" went to sector 0
    disk.read_sectors(0, &mut sector).unwrap();
    assert_eq!(&sector[508..], b"span");
    io.seek(SeekFrom::Start(508)).unwrap();
    let mut back = [0u8; 17];
    io.read_exact(&mut back).unwrap();
    assert_eq!(&back, b"spans two sectors");
    assert_eq!(io.seek(SeekFrom::End(0)).unwrap(), 4 * SECTOR_SIZE as u64);
    assert!(io.seek(SeekFrom::End(1)).is_err()); // past the end of the disk
}

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