// K1290 development probe: all published file bytes come from a validated // read-only TurkFS block device. The transport lease is released before EL0. use super::*; use turkos_kernel::runtime::syscall_abi::Errno; const ONE_SHA256: [u8; 32] = [0xdf, 0x7e, 0x70, 0xe5, 0x02, 0x15, 0x44, 0xf4, 0x83, 0x4b, 0xbe, 0xe6, 0x4a, 0x9e, 0x37, 0x89, 0xfe, 0xbc, 0x4b, 0xe8, 0x14, 0x70, 0xdf, 0x62, 0x9c, 0xad, 0x6d, 0xdb, 0x03, 0x32, 0x0a, 0x5c]; const TAIL_SHA256: [u8; 32] = [0x83, 0x88, 0xc9, 0x8b, 0x90, 0x62, 0x54, 0x1c, 0xe3, 0x87, 0xd9, 0x8a, 0x91, 0x0f, 0x52, 0x0f, 0x4f, 0x94, 0x78, 0xc2, 0xcb, 0xe5, 0x62, 0x2a, 0xba, 0xdd, 0x35, 0x96, 0x7f, 0xb0, 0xda, 0x9a]; const SHORT_READ: usize = 128; #[derive(Clone, Copy)] struct Open { fd: u64, file: u8, cursor: usize, } pub(super) struct CatDisk { one: [u8; 1], tail: [u8; 513], open: Option, next_fd: u64, } impl CatDisk { pub(super) fn load(boot: &BootData, allocator: &mut memory::FrameAllocator) -> Result { let mut disk = virtio_ro_turkfs::RoDisk::open( boot, allocator, "TURKOS/OS/K1290C-DISK-READ")?; let result = Self::load_open(&mut disk); let released = if result.is_err() { disk.close_after_timeout(allocator) } else { disk.close(allocator) }; released?; result } pub(super) fn read_sector_for_vfs(disk: &mut virtio_ro_turkfs::RoDisk, lba: u64, output: &mut [u8; 512]) -> Result<(), Errno> { disk.read(lba, output).map_err(|_| Errno::Io) } pub(super) fn inject_short_sector_for_vfs(disk: &mut virtio_ro_turkfs::RoDisk, lba: u64, output: &mut [u8; 512]) -> Result<(), Errno> { disk.read_with_short_used_injection(lba, output).map_err(|_| Errno::Io) } fn load_open(disk: &mut virtio_ro_turkfs::RoDisk) -> Result { use turkos_kernel::turkfs; let mut superblock = [0; 512]; Self::read_sector_for_vfs(disk, turkfs::SUPERBLOCK_LBA, &mut superblock) .map_err(|_| "cat block EIO")?; turkfs::require_superblock_signature(&superblock) .map_err(|_| "cat signature")?; turkfs::require_format_version(&superblock) .map_err(|_| "cat version")?; let geometry = turkfs::require_block_geometry( &superblock, disk.capacity().checked_mul(512).ok_or("cat capacity")?) .map_err(|_| "cat geometry")?; let (root_lba, root_count) = turkfs::root_directory_location(&superblock, geometry) .map_err(|_| "cat root location")?; let inode_lba = turkfs::inode_table_location( &superblock, geometry, root_lba).map_err(|_| "cat inode location")?; let mut root_block = [0; 512]; let mut inode_block = [0; 512]; Self::read_sector_for_vfs(disk, root_lba, &mut root_block) .map_err(|_| "cat block EIO")?; Self::read_sector_for_vfs(disk, inode_lba, &mut inode_block) .map_err(|_| "cat block EIO")?; let root = turkfs::parse_root_directory(root_lba, root_count, &root_block) .map_err(|_| "cat root")?; turkfs::require_utf8_names(&root).map_err(|_| "cat root names")?; turkfs::read_known_empty_file(&root, &inode_block, b"README") .map_err(|_| "cat empty inode")?; let one = turkfs::bounded_single_block_file( &root, &inode_block, b"ONE", geometry, inode_lba) .map_err(|_| "cat one inode")?; let tail = turkfs::known_partial_file_lba( &root, &inode_block, b"TAIL", geometry, inode_lba) .map_err(|_| "cat tail inode")?; if one.size != 1 || one.lba == tail || one.lba == tail + 1 || one.lba == root_lba || one.lba == inode_lba || tail == root_lba || tail == inode_lba { return Err("cat extent overlap"); } let mut one_sector = [0; 512]; let mut tail_first = [0; 512]; let mut tail_last = [0; 512]; Self::read_sector_for_vfs(disk, one.lba, &mut one_sector) .map_err(|_| "cat block EIO")?; Self::read_sector_for_vfs(disk, tail, &mut tail_first) .map_err(|_| "cat block EIO")?; Self::read_sector_for_vfs(disk, tail + 1, &mut tail_last) .map_err(|_| "cat block EIO")?; let mut tail_bytes = [0; 513]; tail_bytes[..512].copy_from_slice(&tail_first); tail_bytes[512] = tail_last[0]; let one_bytes = [one_sector[0]]; if turkfs::file_sha256(&one_bytes) != ONE_SHA256 || turkfs::file_sha256(&tail_bytes) != TAIL_SHA256 { return Err("cat logical file hash"); } Ok(Self {one: one_bytes, tail: tail_bytes, open: None, next_fd: 0x1290_0001}) } pub(super) fn open(&mut self, name: &[u8]) -> Result { if self.open.is_some() { return Err(Errno::Access); } let file = match name { b"README" => 0, b"ONE" => 1, b"TAIL" => 2, _ => return Err(Errno::NoEnt), }; let fd = self.next_fd; self.next_fd = self.next_fd.checked_add(1).ok_or(Errno::Overflow)?; self.open = Some(Open {fd, file, cursor: 0}); Ok(fd) } pub(super) fn read(&mut self, fd: u64, output: &mut [u8]) -> Result { let mut handle = self.open.ok_or(Errno::BadFd)?; if handle.fd != fd { return Err(Errno::BadFd); } let bytes: &[u8] = match handle.file { 0 => &[], 1 => &self.one, 2 => &self.tail, _ => return Err(Errno::Io), }; let count = output.len().min(SHORT_READ) .min(bytes.len().saturating_sub(handle.cursor)); output[..count].copy_from_slice(&bytes[handle.cursor..handle.cursor + count]); handle.cursor += count; self.open = Some(handle); Ok(count) } pub(super) fn close(&mut self, fd: u64) -> Result<(), Errno> { if self.open.as_ref().map(|handle| handle.fd) != Some(fd) { return Err(Errno::BadFd); } self.open = None; Ok(()) } pub(super) fn current_len(&self) -> Option { self.open.map(|handle| match handle.file { 0 => 0, 1 => self.one.len(), _ => self.tail.len(), }) } pub(super) fn matches_output(&self, offset: usize, bytes: &[u8]) -> bool { let Some(handle) = self.open else { return false; }; let expected: &[u8] = match handle.file { 0 => &[], 1 => &self.one, 2 => &self.tail, _ => return false, }; offset.checked_add(bytes.len()).is_some_and(|end| end <= handle.cursor && end <= expected.len() && &expected[offset..end] == bytes) } pub(super) const fn owner_released(&self) -> bool { self.open.is_none() } }