#![allow(clippy::missing_safety_doc)] use lancer_core::packet_ring::{PacketRingReader, PacketRingWriter}; use lancer_vfs_proto::{ VFS_STATUS_CORRUPT, VFS_STATUS_CROSS_DEVICE, VFS_STATUS_DIR_NOT_EMPTY, VFS_STATUS_DISK_FULL, VFS_STATUS_FILE_EXISTS, VFS_STATUS_HANDLE_TABLE_FULL, VFS_STATUS_INTEGRITY_ERROR, VFS_STATUS_INVALID_HANDLE, VFS_STATUS_INVALID_OP, VFS_STATUS_IO_ERROR, VFS_STATUS_IS_A_DIRECTORY, VFS_STATUS_NAME_TOO_LONG, VFS_STATUS_NOT_A_DIRECTORY, VFS_STATUS_NOT_A_FILE, VFS_STATUS_NOT_FOUND, VFS_STATUS_OK, VFS_STATUS_PATH_TOO_DEEP, VFS_STATUS_PERMISSION_DENIED, VFS_STATUS_STALE_CAP, VFS_STATUS_SYMLINK_DEPTH, VFS_STATUS_UNSUPPORTED, VfsRequest, VfsResponse, }; pub use lancer_vfs_proto::{ FsRights, MOUNT_INFO_ENTRY_SIZE, MountInfoEntry, READDIR_ENTRY_SIZE, ReadDirEntry, }; use crate::syscall; pub const VFS_RING_VADDR: u64 = 0x7000_0000; pub const VFS_RING_BASE_SLOT: u64 = 11; pub const VFS_RING_FRAME_COUNT: u64 = 16; pub const CLIENT_NOTIF_SLOT: u8 = 28; pub const VFS_NOTIF_SLOT: u8 = 29; pub const RING_HALF_SIZE: usize = 4096; pub const RING_DATA_OFFSET: usize = 8192; pub const RING_DATA_PAGES: usize = 14; const RING_DATA_SIZE: usize = RING_DATA_PAGES * 4096; const REQUEST_SLOT_SIZE: u32 = 48; const PUSH_RETRY_LIMIT: u32 = 100_000; const POLL_RETRY_LIMIT: u32 = 100_000; const MAX_PATH_COMPONENTS: usize = 32; #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(u8)] pub enum FsStatus { NotFound = 1, PermissionDenied = 2, InvalidHandle = 3, HandleTableFull = 4, IoError = 5, DiskFull = 6, NotADirectory = 7, NotAFile = 8, IsADirectory = 9, DirNotEmpty = 10, FileExists = 11, NameTooLong = 12, PathTooDeep = 13, IntegrityError = 14, StaleCap = 15, InvalidOp = 16, SymlinkDepth = 17, Corrupt = 18, Unsupported = 19, CrossDevice = 20, Unknown = 255, } impl FsStatus { pub fn from_u8(v: u8) -> Self { match v { 0 => Self::Unknown, VFS_STATUS_NOT_FOUND => Self::NotFound, VFS_STATUS_PERMISSION_DENIED => Self::PermissionDenied, VFS_STATUS_INVALID_HANDLE => Self::InvalidHandle, VFS_STATUS_HANDLE_TABLE_FULL => Self::HandleTableFull, VFS_STATUS_IO_ERROR => Self::IoError, VFS_STATUS_DISK_FULL => Self::DiskFull, VFS_STATUS_NOT_A_DIRECTORY => Self::NotADirectory, VFS_STATUS_NOT_A_FILE => Self::NotAFile, VFS_STATUS_IS_A_DIRECTORY => Self::IsADirectory, VFS_STATUS_DIR_NOT_EMPTY => Self::DirNotEmpty, VFS_STATUS_FILE_EXISTS => Self::FileExists, VFS_STATUS_NAME_TOO_LONG => Self::NameTooLong, VFS_STATUS_PATH_TOO_DEEP => Self::PathTooDeep, VFS_STATUS_INTEGRITY_ERROR => Self::IntegrityError, VFS_STATUS_STALE_CAP => Self::StaleCap, VFS_STATUS_INVALID_OP => Self::InvalidOp, VFS_STATUS_SYMLINK_DEPTH => Self::SymlinkDepth, VFS_STATUS_CORRUPT => Self::Corrupt, VFS_STATUS_UNSUPPORTED => Self::Unsupported, VFS_STATUS_CROSS_DEVICE => Self::CrossDevice, _ => Self::Unknown, } } pub fn name(self) -> &'static str { match self { Self::NotFound => "NotFound", Self::PermissionDenied => "PermissionDenied", Self::InvalidHandle => "InvalidHandle", Self::HandleTableFull => "HandleTableFull", Self::IoError => "IoError", Self::DiskFull => "DiskFull", Self::NotADirectory => "NotADirectory", Self::NotAFile => "NotAFile", Self::IsADirectory => "IsADirectory", Self::DirNotEmpty => "DirNotEmpty", Self::FileExists => "FileExists", Self::NameTooLong => "NameTooLong", Self::PathTooDeep => "PathTooDeep", Self::IntegrityError => "IntegrityError", Self::StaleCap => "StaleCap", Self::InvalidOp => "InvalidOp", Self::SymlinkDepth => "SymlinkDepth", Self::Corrupt => "Corrupt", Self::Unsupported => "Unsupported", Self::CrossDevice => "CrossDevice", Self::Unknown => "Unknown", } } } #[derive(Debug, Clone, Copy)] pub struct FileStat { pub size: u64, pub inode_type: u8, pub block_count: u64, } impl FileStat { pub fn from_response(val0: u64, val1: u64) -> Self { Self { size: val0, inode_type: (val1 >> 48) as u8, block_count: val1 & 0xFFFF_FFFF_FFFF, } } } pub struct FsClient { request_ring: PacketRingWriter, response_ring: PacketRingReader, data_area: *mut u8, data_area_size: usize, next_tag: u32, vfs_notif_slot: u8, client_notif_slot: u8, } impl FsClient { pub unsafe fn new(ring_vaddr: usize, vfs_notif: u8, client_notif: u8) -> Self { let base = ring_vaddr as *mut u8; let request_ring = unsafe { PacketRingWriter::init(base, RING_HALF_SIZE, REQUEST_SLOT_SIZE) }; let response_ring = unsafe { PacketRingReader::attach(base.wrapping_add(RING_HALF_SIZE), RING_HALF_SIZE) }; let data_area = (ring_vaddr + RING_DATA_OFFSET) as *mut u8; Self { request_ring, response_ring, data_area, data_area_size: RING_DATA_SIZE, next_tag: 1, vfs_notif_slot: vfs_notif, client_notif_slot: client_notif, } } pub unsafe fn reattach(ring_vaddr: usize, vfs_notif: u8, client_notif: u8) -> Self { let base = ring_vaddr as *mut u8; let request_ring = unsafe { PacketRingWriter::attach(base, RING_HALF_SIZE) }; let response_ring = unsafe { PacketRingReader::attach(base.wrapping_add(RING_HALF_SIZE), RING_HALF_SIZE) }; let data_area = (ring_vaddr + RING_DATA_OFFSET) as *mut u8; Self { request_ring, response_ring, data_area, data_area_size: RING_DATA_SIZE, next_tag: 1, vfs_notif_slot: vfs_notif, client_notif_slot: client_notif, } } fn alloc_tag(&mut self) -> u32 { let tag = self.next_tag; self.next_tag = self.next_tag.wrapping_add(1); tag } fn write_data(&self, offset: usize, data: &[u8]) { let len = data.len().min(self.data_area_size.saturating_sub(offset)); unsafe { core::ptr::copy_nonoverlapping(data.as_ptr(), self.data_area.add(offset), len); } } fn read_data(&self, offset: usize, len: usize) -> &[u8] { let actual = len.min(self.data_area_size.saturating_sub(offset)); unsafe { core::slice::from_raw_parts(self.data_area.add(offset), actual) } } fn send_request(&mut self, req: &VfsRequest) -> Result { let data = req.as_bytes(); let pushed = (0..PUSH_RETRY_LIMIT).any(|_| self.request_ring.try_push(data)); if !pushed { return Err(FsStatus::IoError); } syscall::notify_signal(self.vfs_notif_slot as u64, 1); let tag = req.tag; let mut buf = [0u8; 24]; (0..POLL_RETRY_LIMIT) .find_map(|_| match self.response_ring.try_pop(&mut buf) { Some(len) if len >= VfsResponse::SIZE => { let resp = VfsResponse::from_bytes(&buf)?; match resp.tag == tag { true => Some(resp), false => None, } } _ => { syscall::notify_wait(self.client_notif_slot as u64); None } }) .ok_or(FsStatus::IoError) } fn check_response(resp: &VfsResponse) -> Result<(), FsStatus> { match resp.status { VFS_STATUS_OK => Ok(()), s => Err(FsStatus::from_u8(s)), } } pub fn open(&mut self, dir_handle: u8, name: &[u8], mode: FsRights) -> Result { self.write_data(0, name); let tag = self.alloc_tag(); let req = VfsRequest::open(tag, dir_handle, mode, 0, name.len() as u64); let resp = self.send_request(&req)?; Self::check_response(&resp)?; Ok(resp.val0 as u8) } pub fn close(&mut self, handle: u8) -> Result<(), FsStatus> { let tag = self.alloc_tag(); let req = VfsRequest::close(tag, handle); let resp = self.send_request(&req)?; Self::check_response(&resp) } pub fn read(&mut self, handle: u8, offset: u64, buf: &mut [u8]) -> Result { let req_len = buf.len().min(self.data_area_size) as u32; let tag = self.alloc_tag(); let req = VfsRequest::read(tag, handle, offset, req_len, 0); let resp = self.send_request(&req)?; Self::check_response(&resp)?; let n = resp.val0 as usize; let src = self.read_data(0, n); let copy_len = src.len().min(buf.len()); buf[..copy_len].copy_from_slice(&src[..copy_len]); Ok(copy_len) } pub fn write(&mut self, handle: u8, offset: u64, data: &[u8]) -> Result { let actual_len = data.len().min(self.data_area_size); self.write_data(0, &data[..actual_len]); let tag = self.alloc_tag(); let req = VfsRequest::write(tag, handle, offset, actual_len as u32, 0); let resp = self.send_request(&req)?; Self::check_response(&resp)?; Ok(resp.val0 as usize) } pub fn stat(&mut self, handle: u8) -> Result { let tag = self.alloc_tag(); let req = VfsRequest::stat(tag, handle); let resp = self.send_request(&req)?; Self::check_response(&resp)?; Ok(FileStat::from_response(resp.val0, resp.val1)) } pub fn mkdir(&mut self, dir_handle: u8, name: &[u8]) -> Result<(), FsStatus> { self.write_data(0, name); let tag = self.alloc_tag(); let req = VfsRequest::mkdir(tag, dir_handle, 0, name.len() as u64); let resp = self.send_request(&req)?; Self::check_response(&resp) } pub fn unlink(&mut self, dir_handle: u8, name: &[u8]) -> Result<(), FsStatus> { self.write_data(0, name); let tag = self.alloc_tag(); let req = VfsRequest::unlink(tag, dir_handle, 0, name.len() as u64); let resp = self.send_request(&req)?; Self::check_response(&resp) } pub fn rename( &mut self, src_dir: u8, src_name: &[u8], dst_dir: u8, dst_name: &[u8], ) -> Result<(), FsStatus> { self.write_data(0, src_name); self.write_data(src_name.len(), dst_name); let tag = self.alloc_tag(); let req = VfsRequest::rename( tag, src_dir, dst_dir, 0, src_name.len() as u32, src_name.len() as u32, dst_name.len() as u32, ); let resp = self.send_request(&req)?; Self::check_response(&resp) } pub fn readdir( &mut self, dir_handle: u8, cursor: u64, buf: &mut [u8], ) -> Result<(usize, u64), FsStatus> { let tag = self.alloc_tag(); let req = VfsRequest::readdir(tag, dir_handle, cursor, 0, buf.len() as u64); let resp = self.send_request(&req)?; Self::check_response(&resp)?; let entry_count = resp.val0 as usize; let next_cursor = resp.val1; let byte_count = entry_count * READDIR_ENTRY_SIZE; let src = self.read_data(0, byte_count); let copy_len = src.len().min(buf.len()); buf[..copy_len].copy_from_slice(&src[..copy_len]); let actual_entries = copy_len / READDIR_ENTRY_SIZE; Ok((actual_entries, next_cursor)) } pub fn truncate(&mut self, handle: u8, new_size: u64) -> Result<(), FsStatus> { let tag = self.alloc_tag(); let req = VfsRequest::truncate(tag, handle, new_size); let resp = self.send_request(&req)?; Self::check_response(&resp) } pub fn mount_info(&mut self, buf: &mut [u8]) -> Result { let tag = self.alloc_tag(); let req = VfsRequest::mount_info(tag); let resp = self.send_request(&req)?; Self::check_response(&resp)?; let count = resp.val0 as usize; let byte_count = count * MOUNT_INFO_ENTRY_SIZE; let src = self.read_data(0, byte_count); let copy_len = src.len().min(buf.len()); buf[..copy_len].copy_from_slice(&src[..copy_len]); Ok(count) } pub fn sync(&mut self) -> Result<(), FsStatus> { let tag = self.alloc_tag(); let req = VfsRequest::sync(tag); let resp = self.send_request(&req)?; Self::check_response(&resp) } pub fn extended_raw( &mut self, sub_opcode: u8, arg0: u64, arg1: u64, ) -> Result { let tag = self.alloc_tag(); let req = VfsRequest { opcode: lancer_vfs_proto::VfsOpcode::Extended as u8, handle: 0, flags: sub_opcode, _pad: 0, tag, arg0, arg1, arg2: 0, }; let resp = self.send_request(&req)?; Self::check_response(&resp)?; Ok(resp) } } pub unsafe fn init() -> FsClient { let map_ok = (0..VFS_RING_FRAME_COUNT).all(|i| { syscall::frame_map(VFS_RING_BASE_SLOT + i, VFS_RING_VADDR + i * 4096, 1) >= 0 }); match map_ok { true => {} false => { syscall::debug_print("fs::init: failed to map VFS ring frames\n"); syscall::exit(); } } syscall::notify_wait(CLIENT_NOTIF_SLOT as u64); unsafe { FsClient::new(VFS_RING_VADDR as usize, VFS_NOTIF_SLOT, CLIENT_NOTIF_SLOT) } } pub fn open_path(client: &mut FsClient, root_handle: u8, path: &[u8]) -> Result { open_path_with_rights(client, root_handle, path, FsRights::TRAVERSE_INHERITABLE) } pub fn open_path_with_rights( client: &mut FsClient, root_handle: u8, path: &[u8], rights: FsRights, ) -> Result { let trimmed = match path.first() { Some(&b'/') => &path[1..], _ => path, }; match trimmed.is_empty() { true => client.open(root_handle, b"", rights), false => open_path_components(client, root_handle, trimmed, 0, rights), } } fn open_path_components( client: &mut FsClient, dir_handle: u8, remaining: &[u8], depth: usize, leaf_rights: FsRights, ) -> Result { if depth >= MAX_PATH_COMPONENTS { return Err(FsStatus::PathTooDeep); } match remaining.iter().position(|&b| b == b'/') { Some(slash_pos) => { let component = &remaining[..slash_pos]; let rest = &remaining[slash_pos + 1..]; let intermediate = client.open(dir_handle, component, FsRights::ALL)?; let result = open_path_components(client, intermediate, rest, depth + 1, leaf_rights); let _ = client.close(intermediate); result } None => client.open(dir_handle, remaining, leaf_rights), } } fn strip_trailing_slashes(s: &[u8]) -> &[u8] { match s.last() { Some(&b'/') => strip_trailing_slashes(&s[..s.len() - 1]), _ => s, } } pub fn open_parent<'a>( client: &mut FsClient, root_handle: u8, path: &'a [u8], ) -> Result<(u8, &'a [u8]), FsStatus> { open_parent_with_rights(client, root_handle, path, FsRights::ALL) } pub fn open_parent_with_rights<'a>( client: &mut FsClient, root_handle: u8, path: &'a [u8], parent_rights: FsRights, ) -> Result<(u8, &'a [u8]), FsStatus> { let stripped = strip_trailing_slashes(path); let trimmed = match stripped.first() { Some(&b'/') => &stripped[1..], _ => stripped, }; match trimmed.is_empty() { true => Err(FsStatus::NotFound), false => match trimmed.iter().rposition(|&b| b == b'/') { Some(last_slash) => { let parent_path = &trimmed[..last_slash]; let basename = &trimmed[last_slash + 1..]; let parent_handle = open_path_with_rights(client, root_handle, parent_path, parent_rights)?; Ok((parent_handle, basename)) } None => { let parent = client.open(root_handle, b"", parent_rights)?; Ok((parent, trimmed)) } }, } }