use std::os::raw::c_void; use std::sync::Arc; use parking_lot::{Mutex, RwLock}; use tracing::{debug, warn}; use widestring::U16CStr; use winfsp::filesystem::{ DirBuffer, DirInfo, DirMarker, FileInfo, FileSecurity, FileSystemContext, OpenFileInfo, VolumeInfo, WideNameInfo, }; use winfsp::FspError; use crate::api::{AssetMeta, CanClient}; use crate::tree::{NodeId, NodeKind, VirtualTree}; use crate::util; // NTSTATUS constants (raw i32 values to avoid windows crate version conflicts) const STATUS_OBJECT_NAME_NOT_FOUND: i32 = 0xC0000034_u32 as i32; const STATUS_NOT_A_DIRECTORY: i32 = 0xC0000103_u32 as i32; const STATUS_UNEXPECTED_NETWORK_ERROR: i32 = 0xC00000C4_u32 as i32; const STATUS_INVALID_DEVICE_REQUEST: i32 = 0xC0000010_u32 as i32; // File attribute constants const FILE_ATTRIBUTE_DIRECTORY: u32 = 0x10; const FILE_ATTRIBUTE_READONLY: u32 = 0x01; const FILE_ATTRIBUTE_ARCHIVE: u32 = 0x20; fn ntstatus(code: i32) -> FspError { FspError::NTSTATUS(code) } /// Shared cache state: asset list + virtual tree. pub struct CacheState { pub assets: Vec, pub tree: VirtualTree, } /// The WinFSP filesystem context for CAN service. pub struct CanFs { pub cache: Arc>, pub client: Arc, } /// Per-open-handle context. pub struct CanFileContext { node_id: NodeId, /// Lazily fetched file bytes. content: Mutex>>, /// Directory enumeration buffer. dir_buffer: DirBuffer, } impl FileSystemContext for CanFs { type FileContext = CanFileContext; fn get_security_by_name( &self, file_name: &U16CStr, _security_descriptor: Option<&mut [c_void]>, _resolve_reparse_points: impl FnOnce(&U16CStr) -> Option, ) -> winfsp::Result { let path = util::normalize_path(file_name); debug!("get_security_by_name: {}", path); let cache = self.cache.read(); let node_id = cache .tree .lookup(&path) .ok_or(ntstatus(STATUS_OBJECT_NAME_NOT_FOUND))?; let node = cache.tree.get(node_id); let attributes = if node.is_directory() { FILE_ATTRIBUTE_DIRECTORY } else { FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_ARCHIVE }; Ok(FileSecurity { reparse: false, sz_security_descriptor: 0, attributes, }) } fn open( &self, file_name: &U16CStr, _create_options: u32, _granted_access: u32, file_info: &mut OpenFileInfo, ) -> winfsp::Result { let path = util::normalize_path(file_name); debug!("open: {}", path); let cache = self.cache.read(); let node_id = cache .tree .lookup(&path) .ok_or(ntstatus(STATUS_OBJECT_NAME_NOT_FOUND))?; let node = cache.tree.get(node_id); let fi = file_info.as_mut(); if node.is_directory() { fi.file_attributes = FILE_ATTRIBUTE_DIRECTORY; fi.file_size = 0; fi.allocation_size = 0; } else { fi.file_attributes = FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_ARCHIVE; if let NodeKind::File { asset_index } = &node.kind { let sz = cache.assets[*asset_index].size as u64; fi.file_size = sz; fi.allocation_size = sz; } else { fi.file_size = 0; fi.allocation_size = 0; } } if let NodeKind::File { asset_index } = &node.kind { let ts = util::epoch_ms_to_filetime(cache.assets[*asset_index].timestamp); fi.creation_time = ts; fi.last_access_time = ts; fi.last_write_time = ts; fi.change_time = ts; } else { fi.creation_time = 0; fi.last_access_time = 0; fi.last_write_time = 0; fi.change_time = 0; } fi.index_number = 0; fi.hard_links = 0; fi.ea_size = 0; fi.reparse_tag = 0; Ok(CanFileContext { node_id, content: Mutex::new(None), dir_buffer: DirBuffer::new(), }) } fn close(&self, _context: Self::FileContext) {} fn get_file_info( &self, context: &Self::FileContext, file_info: &mut FileInfo, ) -> winfsp::Result<()> { let cache = self.cache.read(); let node = cache.tree.get(context.node_id); if node.is_directory() { file_info.file_attributes = FILE_ATTRIBUTE_DIRECTORY; file_info.file_size = 0; file_info.allocation_size = 0; file_info.creation_time = 0; file_info.last_access_time = 0; file_info.last_write_time = 0; file_info.change_time = 0; } else { file_info.file_attributes = FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_ARCHIVE; // Use actual downloaded size if available, otherwise metadata size let content = context.content.lock(); if let Some(ref bytes) = *content { let sz = bytes.len() as u64; file_info.file_size = sz; file_info.allocation_size = sz; } else if let NodeKind::File { asset_index } = &node.kind { let sz = cache.assets[*asset_index].size as u64; file_info.file_size = sz; file_info.allocation_size = sz; } else { file_info.file_size = 0; file_info.allocation_size = 0; } if let NodeKind::File { asset_index } = &node.kind { let ts = util::epoch_ms_to_filetime(cache.assets[*asset_index].timestamp); file_info.creation_time = ts; file_info.last_access_time = ts; file_info.last_write_time = ts; file_info.change_time = ts; } } file_info.index_number = 0; file_info.hard_links = 0; file_info.ea_size = 0; file_info.reparse_tag = 0; Ok(()) } fn read( &self, context: &Self::FileContext, buffer: &mut [u8], offset: u64, ) -> winfsp::Result { let mut content = context.content.lock(); if content.is_none() { let cache = self.cache.read(); let node = cache.tree.get(context.node_id); if let NodeKind::File { asset_index } = &node.kind { let hash = &cache.assets[*asset_index].hash; debug!("fetching bytes for {}", hash); match self.client.fetch_bytes(hash) { Ok(bytes) => { *content = Some(bytes); } Err(e) => { warn!("failed to fetch asset: {}", e); return Err(ntstatus(STATUS_UNEXPECTED_NETWORK_ERROR)); } } } else { return Err(ntstatus(STATUS_INVALID_DEVICE_REQUEST)); } } let bytes = content.as_ref().unwrap(); let offset = offset as usize; if offset >= bytes.len() { return Ok(0); } let end = (offset + buffer.len()).min(bytes.len()); let count = end - offset; buffer[..count].copy_from_slice(&bytes[offset..end]); Ok(count as u32) } fn read_directory( &self, context: &Self::FileContext, _pattern: Option<&U16CStr>, marker: DirMarker, buffer: &mut [u8], ) -> winfsp::Result { let cache = self.cache.read(); let node = cache.tree.get(context.node_id); if !node.is_directory() { return Err(ntstatus(STATUS_NOT_A_DIRECTORY)); } if let Ok(dir_buffer_lock) = context.dir_buffer.acquire(marker.is_none(), None) { // "." entry { let mut di: DirInfo = DirInfo::new(); let _ = di.set_name(std::ffi::OsStr::new(".")); di.file_info_mut().file_attributes = FILE_ATTRIBUTE_DIRECTORY; let _ = dir_buffer_lock.write(&mut di); } // ".." entry { let mut di: DirInfo = DirInfo::new(); let _ = di.set_name(std::ffi::OsStr::new("..")); di.file_info_mut().file_attributes = FILE_ATTRIBUTE_DIRECTORY; let _ = dir_buffer_lock.write(&mut di); } for &child_id in &node.children { let child = cache.tree.get(child_id); let mut di: DirInfo = DirInfo::new(); if di.set_name(std::ffi::OsStr::new(&child.name)).is_err() { continue; } let fi = di.file_info_mut(); if child.is_directory() { fi.file_attributes = FILE_ATTRIBUTE_DIRECTORY; fi.file_size = 0; fi.allocation_size = 0; } else { fi.file_attributes = FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_ARCHIVE; if let NodeKind::File { asset_index } = &child.kind { let sz = cache.assets[*asset_index].size as u64; fi.file_size = sz; fi.allocation_size = sz; } else { fi.file_size = 0; fi.allocation_size = 0; } } if let NodeKind::File { asset_index } = &child.kind { let ts = util::epoch_ms_to_filetime(cache.assets[*asset_index].timestamp); fi.creation_time = ts; fi.last_access_time = ts; fi.last_write_time = ts; fi.change_time = ts; } fi.index_number = 0; fi.hard_links = 0; fi.ea_size = 0; fi.reparse_tag = 0; let _ = dir_buffer_lock.write(&mut di); } } Ok(context.dir_buffer.read(marker, buffer)) } fn get_volume_info(&self, out_volume_info: &mut VolumeInfo) -> winfsp::Result<()> { out_volume_info.total_size = 1024 * 1024 * 1024; // 1 GB out_volume_info.free_size = 0; Ok(()) } }