//! K411: `cat MOTD` emits the verified logical bytes read from a RO TurkFS //! data sector, after separate superblock, root and inode GET completions. use super::*; use turkos_kernel::{ serial_console::{ConsoleEvent, SerialConsole}, turkfs, }; const RXFE: u32 = 1 << 4; const DATA_ERROR: u32 = 0x0f00; const FILE_SHA256: &[u8; 64] = b"fef31ee3f4e08b80f3c40cd901ec51eac7b25445610cdf54f3bdd2a5d63a02c2"; fn fail(reason: &str) -> ! { write_str("TURKOS/CAT-ERROR: "); write_line(reason); park() } fn ticks() -> u64 { let value: u64; unsafe { asm!("mrs {value}, CNTPCT_EL0", value = out(reg) value, options(nomem, nostack, preserves_flags)) }; value } pub(super) fn smoke_test(boot: &BootData, allocator: &mut memory::FrameAllocator) -> ! { let mut disk = virtio_ro_turkfs::RoDisk::open(boot, allocator, "TURKOS/CAT-GET") .unwrap_or_else(|e| fail(e)); let mut superblock = [0u8; 512]; let mut root_block = [0u8; 512]; let mut inode_block = [0u8; 512]; let mut data_block = [0u8; 512]; disk.read(turkfs::SUPERBLOCK_LBA, &mut superblock) .unwrap_or_else(|e| fail(e)); turkfs::require_format_version(&superblock).unwrap_or_else(|_| fail("format version")); turkfs::require_superblock_signature(&superblock) .unwrap_or_else(|_| fail("superblock signature")); let geometry = turkfs::require_block_geometry(&superblock, disk.capacity() * 512) .unwrap_or_else(|_| fail("block geometry")); let (root_lba, root_count) = turkfs::root_directory_location(&superblock, geometry) .unwrap_or_else(|_| fail("root descriptor")); disk.read(root_lba, &mut root_block) .unwrap_or_else(|e| fail(e)); let root = turkfs::parse_root_directory(root_lba, root_count, &root_block) .unwrap_or_else(|_| fail("root entry")); turkfs::require_utf8_names(&root).unwrap_or_else(|_| fail("root UTF-8")); for entry in &root.entries[..root.count] { if entry.name[..entry.name_len as usize] .iter() .any(|byte| *byte < 0x20 || *byte == 0x7f || *byte == b'/') { fail("unsafe root name"); } } let inode_lba = turkfs::inode_table_location(&superblock, geometry, root_lba) .unwrap_or_else(|_| fail("inode pointer")); disk.read(inode_lba, &mut inode_block) .unwrap_or_else(|e| fail(e)); let file = turkfs::bounded_single_block_file(&root, &inode_block, b"MOTD", geometry, inode_lba) .unwrap_or_else(|_| fail("MOTD inode")); disk.read(file.lba, &mut data_block) .unwrap_or_else(|e| fail(e)); let logical = &data_block[..file.size]; let digest = turkfs::file_sha256(logical); let hex = turkos_kernel::program_hash::hex_lower(&digest); if &hex != FILE_SHA256 { fail("MOTD hash"); } let text = core::str::from_utf8(logical).unwrap_or_else(|_| fail("MOTD UTF-8")); if text .bytes() .any(|byte| byte != b'\n' && (byte < 0x20 || byte == 0x7f)) { fail("MOTD control byte"); } disk.close(allocator).unwrap_or_else(|e| fail(e)); write_str("TURKOS/CAT-MOUNT: ROOT=0x"); write_hex(root_lba as usize); write_str(" INODE=0x"); write_hex(6); write_str(" DATA=0x"); write_hex(file.lba as usize); write_str(" SIZE=0x"); write_hex(file.size); write_str(" SHA256="); write_str(core::str::from_utf8(&hex).unwrap_or_else(|_| fail("hash encoding"))); write_str(" DMA-RESTORED=0x1\r\n"); let base = UART_BASE.load(Ordering::Relaxed); let frequency: u64; unsafe { asm!("mrs {value}, CNTFRQ_EL0", value = out(reg) frequency, options(nomem, nostack, preserves_flags)) }; if frequency == 0 { fail("zero counter frequency"); } let mut console = SerialConsole::cat_only(); write_line("TURKOS/CAT-PROMPT: NUMBER=0x1"); write_line("TURKOS/CAT: READY"); loop { let deadline = ticks() .checked_add(frequency.saturating_mul(5)) .unwrap_or(u64::MAX); while unsafe { read_volatile((base + 0x18) as *const u32) } & RXFE != 0 { if ticks() >= deadline { fail("input timeout"); } core::hint::spin_loop(); } let data = unsafe { read_volatile(base as *const u32) }; if data & DATA_ERROR != 0 { fail("PL011 data error"); } match console.feed(data as u8) { ConsoleEvent::Input | ConsoleEvent::SuppressedLf => {} ConsoleEvent::Cat { prompt, bytes, len } => { if &bytes[..len] != b"MOTD" { fail("unknown file"); } write_str("TURKOS/CAT-DATA: "); write_str(text); write_line("TURKOS/CAT-END"); write_str("TURKOS/CAT-PROMPT: NUMBER=0x"); write_hex(prompt as usize); write_str("\r\n"); write_line("TURKOS/CAT: PASS"); park() } ConsoleEvent::List { prompt } => { write_str("TURKOS/CAT-LS: COUNT=0x"); write_hex(root.count); write_str("\r\n"); for entry in &root.entries[..root.count] { write_str( core::str::from_utf8(&entry.name[..entry.name_len as usize]) .unwrap_or_else(|_| fail("root UTF-8")), ); write_str("\r\n"); } write_str("TURKOS/CAT-PROMPT: NUMBER=0x"); write_hex(prompt as usize); write_str("\r\n"); } ConsoleEvent::Help { prompt } => { write_str("TURKOS/CAT-HELP: COMMANDS="); write_line(console.commands()); write_str("TURKOS/CAT-PROMPT: NUMBER=0x"); write_hex(prompt as usize); write_str("\r\n"); } ConsoleEvent::Blank { prompt } => { write_str("TURKOS/CAT-PROMPT: NUMBER=0x"); write_hex(prompt as usize); write_str("\r\n"); } ConsoleEvent::Echo { prompt, bytes, len } => { write_str("TURKOS/CAT-ECHO: DATA="); for byte in &bytes[..len] { write_byte(*byte); } write_str("\r\nTURKOS/CAT-PROMPT: NUMBER=0x"); write_hex(prompt as usize); write_str("\r\n"); } ConsoleEvent::Unhandled(_, _) => fail("unhandled command"), ConsoleEvent::Run { .. } => fail("unexpected run command"), ConsoleEvent::Wait { .. } => fail("unexpected wait command"), ConsoleEvent::InvalidUtf8 { .. } => { fail("unexpected UTF-8 error in historical fixture") } ConsoleEvent::MalformedArguments { .. } => { fail("unexpected argument error in historical fixture") } ConsoleEvent::Cancelled { .. } => fail("unexpected Ctrl-C in historical fixture"), ConsoleEvent::TooLong => fail("overlong command"), } } }