//! 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(rustbelt::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 rustbelt::block::{BlockDevice, RamDisk, SECTOR_SIZE}; use rustbelt::errno::Errno; use rustbelt::fs::fat::BlockIo; use rustbelt::fs::file::{File, InodeFile}; use rustbelt::fs::{self, InodeKind}; use rustbelt::elf; 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(); } #[test_case] fn fat_volume_is_mounted() { assert!(fs::mounts().iter().any(|(path, kind)| path == "/mnt/disk0" && *kind == "fat32")); let mut names: Vec = 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(), "/mnt/disk0/readme.txt".into()).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()); if entry.name == "dynhello" { // Stage 19: dynamically linked on purpose, to check it's refused. assert!(matches!(elf::check(&image), Err(elf::ElfError::NeedsInterpreter))); continue; } 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 = (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 = 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!(§or[..13], b"s two sectors"); // "span" went to sector 0 disk.read_sectors(0, &mut sector).unwrap(); assert_eq!(§or[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 } /// Stage 21: with no "Linux root (x86-64)" partition on any disk (none of /// the usual test disks has one), boot contract v1 stays exactly as it /// was: "/" is the ramfs, no /mnt/share, and the disks are /mnt/disk0... #[test_case] fn no_root_partition_keeps_the_old_layout() { let mounts = fs::mounts(); let mount_type = |path: &str| mounts.iter().find(|(p, _)| p == path).map(|(_, t)| *t); assert!(!rustbelt::boot::v2()); assert_eq!(mount_type("/"), Some("ramfs")); assert!(fs::read_all("/hello.txt").is_ok()); assert_eq!(mount_type("/mnt/share"), None); assert_eq!(mount_type("/mnt/disk0"), Some("fat32")); assert_eq!(mount_type("/mnt/disk2"), Some("exfat")); // the whole-disk exFAT is not a share } #[panic_handler] fn panic(info: &PanicInfo) -> ! { rustbelt::test_panic_handler(info) }