//! Bounded EL0 stat service for the read-only metadata guest probe. use super::*; use turkos_kernel::runtime::process_table::Outcome; use turkos_kernel::runtime::syscall_abi as abi; use os_vfs_stat::{Catalog, Error as StatError, Inode, Kind, Name, StatWire}; static mut OS_K1575_STAT_COUNT: u64 = 0; static mut OS_K1575_STAT_CANARY: [u8; 16] = *b"STAT-USER-GUARD!"; fn count() -> u64 { unsafe { read_volatile(core::ptr::addr_of!(OS_K1575_STAT_COUNT)) } } pub(super) fn begin() { unsafe { write_volatile(core::ptr::addr_of_mut!(OS_K1575_STAT_COUNT), 0); write_volatile(core::ptr::addr_of_mut!(OS_K1575_STAT_CANARY), *b"STAT-USER-GUARD!"); } } pub(super) fn finish() -> Result<(), &'static str> { if count() != 4 || unsafe { read_volatile(core::ptr::addr_of!(OS_K1575_STAT_CANARY)) } != *b"STAT-USER-GUARD!" { return Err("stat-guest-incomplete"); } Ok(()) } fn catalog() -> Result { let root = Inode { number: 1, kind: Kind::Directory, permissions: 0o555, uid: 0, gid: 0, size: 64, allocated_bytes: 512 }; let empty = Inode { number: 2, kind: Kind::Regular, permissions: 0o444, uid: 0, gid: 0, size: 0, allocated_bytes: 0 }; let wide = Inode { number: 3, kind: Kind::Regular, permissions: 0o444, uid: 0, gid: 0, size: 513, allocated_bytes: 1024 }; Catalog::new(root, Name::new(b"bos")?, empty, Name::new(b"buyuk")?, wide) } pub(super) fn dispatch(episode: &mut OsK1215AEl0Episode, frame: &mut os_k920_exception::Frame, table: &mut OsK1105Table<'_>) -> u64 { if frame.elr < episode.launch.user_pc || frame.elr >= episode.launch.user_pc + 4096 || !frame.elr.is_multiple_of(4) { park(); } let pid = episode.pid; if frame.x[7] == 3 { write_line("TURKOS/OS/K1575D-TRACE: STAT-ENTER"); if frame.x[8] != 230 || episode.calls != 1 || frame.x[1] == 0 || frame.x[1] > 24 || frame.x[0] == 0 || frame.x[2] == 0 || frame.x[3] != os_vfs_stat::STAT_WIRE_BYTES as u64 || count() >= 4 { park(); } let expected = [b"bos".as_slice(), b"buyuk".as_slice(), b"bozuk".as_slice(), b"/".as_slice()][count() as usize]; if episode.case != (if count() == 3 { 1 } else { 0 }) || frame.x[1] as usize != expected.len() { park(); } let before = table.snapshot(); let resources = table.record(pid).unwrap_or_else(|_| park()).resources; let mut name = [0u8; 24]; table.copy_current_user_data(pid, frame.x[0], &mut name[..expected.len()]).unwrap_or_else(|_| park()); write_line("TURKOS/OS/K1575D-TRACE: STAT-NAME-COPIED"); if &name[..expected.len()] != expected || table.snapshot() != before || table.record(pid).map(|row| row.resources) != Ok(resources) || unsafe { read_volatile(core::ptr::addr_of!(OS_K1575_STAT_CANARY)) } != *b"STAT-USER-GUARD!" { park(); } let catalog = catalog().unwrap_or_else(|_| park()); if expected == b"bozuk" { let corrupt = Inode { number: 3, kind: Kind::Regular, permissions: 0o444, uid: 0, gid: 0, size: 1025, allocated_bytes: 1024 }; let rejected = Catalog::new(Inode { number: 1, kind: Kind::Directory, permissions: 0o555, uid: 0, gid: 0, size: 64, allocated_bytes: 512 }, Name::new(b"bos").unwrap_or_else(|_| park()), Inode { number: 2, kind: Kind::Regular, permissions: 0o444, uid: 0, gid: 0, size: 0, allocated_bytes: 0 }, Name::new(b"buyuk").unwrap_or_else(|_| park()), corrupt); if rejected != Err(StatError::CorruptInodeSize) || catalog.stat_file(b"buyuk").map(|stat| stat.size) != Ok(513) { park(); } frame.x[0] = abi::AbiResult::Err(abi::Errno::Io).encode(); write_line("TURKOS/OS/K1575-STAT: CORRUPT-SIZE-REJECTED"); } else { let stat = if expected == b"/" { catalog.stat_root() } else { catalog.stat_file(expected).unwrap_or_else(|_| park()) }; let wire = StatWire::from(stat); let bytes = unsafe { core::slice::from_raw_parts( core::ptr::addr_of!(wire).cast::(), os_vfs_stat::STAT_WIRE_BYTES) }; table.write_current_user_data(pid, frame.x[2], bytes) .unwrap_or_else(|_| park()); write_line("TURKOS/OS/K1575D-TRACE: STAT-OUTPUT-COPIED"); frame.x[0] = 0; match count() { 0 => write_line("TURKOS/OS/K1575-STAT: EMPTY=0 BLOCKS=0"), 1 => write_line("TURKOS/OS/K1575-STAT: WIDE=513 BLOCKS=2"), 3 => write_line("TURKOS/OS/K1575-STAT: ROOT=DIRECTORY"), _ => park(), } } if table.snapshot() != before || table.record(pid).map(|row| row.resources) != Ok(resources) { park(); } unsafe { write_volatile(core::ptr::addr_of_mut!(OS_K1575_STAT_COUNT), count() + 1); } return 0; } if frame.x[7] == 2 && frame.x[8] == 3 { let request = turkos_os_user_runtime_abi::decode( frame.x[7], frame.x[8], core::array::from_fn(|i| frame.x[i])) .unwrap_or_else(|_| park()); let turkos_os_user_runtime_abi::Request::Exit(code) = request else { park(); }; if episode.case == 0 { if code != 0 || episode.calls != 2 || count() != 4 { park(); } } else if code != 42 || episode.calls != 1 || count() != 4 { park(); } table.exit_current(code).unwrap_or_else(|_| park()); let outcome = Outcome::Exited(code); if table.record(pid).map(|row| row.outcome) != Ok(Some(outcome)) { park(); } episode.outcome = Some(outcome); return 1; } if frame.x[7] != abi::ABI_VERSION { park(); } let mut abi_frame = turkos_os_exceptions::exceptions::Frame::EMPTY; abi_frame.x = frame.x; let call = abi::decode_call(&abi_frame).unwrap_or_else(|_| park()); if call == abi::Call::Role { if episode.calls != 0 { park(); } episode.calls = 1; frame.x[0] = episode.case as u64; return 0; } if let abi::Call::RunChild(1) = call { if episode.case != 0 || episode.calls != 1 || count() != 3 { park(); } return os_k1215a_run_child(episode as *mut _, frame as *mut _, episode.launch.kernel_root); } park() }