#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct FileHandle(u16); impl FileHandle { pub const fn new(id: u16) -> Self { Self(id) } pub const fn raw(&self) -> u16 { self.0 } } pub const MAX_FILE_NAME: usize = 32; #[derive(Debug, Clone, Copy)] pub struct FileName { bytes: [u8; MAX_FILE_NAME], len: u8, } impl FileName { pub fn empty() -> Self { Self { bytes: [0; MAX_FILE_NAME], len: 0 } } pub fn from_bytes(src: &[u8]) -> Self { let mut name = Self::empty(); let len = src.len().min(MAX_FILE_NAME); name.bytes[..len].copy_from_slice(&src[..len]); name.len = len as u8; name } pub fn as_bytes(&self) -> &[u8] { &self.bytes[..self.len as usize] } pub fn len(&self) -> usize { self.len as usize } } impl PartialEq<[u8]> for FileName { fn eq(&self, other: &[u8]) -> bool { self.as_bytes() == other } } #[derive(Debug, Clone, Copy)] pub struct DirEntry { pub name: FileName, pub size: usize, pub is_dir: bool, } impl DirEntry { pub fn empty() -> Self { Self { name: FileName::empty(), size: 0, is_dir: false, } } } pub trait FileSystem { fn open(&mut self, path: &[u8]) -> Option; fn read(&mut self, handle: FileHandle, buf: &mut [u8]) -> Option; fn write(&mut self, handle: FileHandle, buf: &[u8]) -> Option; fn close(&mut self, handle: FileHandle); fn list_dir(&mut self, path: &[u8], entries: &mut [DirEntry]) -> Option; fn create(&mut self, path: &[u8]) -> Option; fn delete(&mut self, path: &[u8]) -> bool; fn file_size(&mut self, handle: FileHandle) -> Option; fn mkdir(&mut self, path: &[u8]) -> bool; /// Moves `handle`'s position to `offset` (at most the file's size). fn seek(&mut self, handle: FileHandle, offset: usize) -> bool; /// Removes the directory at `path`, which must be empty. fn rmdir(&mut self, path: &[u8]) -> bool; /// The signed-in user's master key, a fresh random seed, and the name /// of their folder; only an encrypting file system has a use for them. fn unlock(&mut self, _master: &[u8; 32], _seed: &[u8; 32], _user: &[u8]) {} /// The user signed out: their key is forgotten. fn lock(&mut self) {} /// Removes the tree at `path`, whatever is in it. fn purge(&mut self, path: &[u8]) -> bool { remove_tree(self, path) } /// Whether anything can be written at all; the server refuses /// writing requests up front when not. fn writable(&self) -> bool { true } } /// Removes everything under `path` and `path` itself, a directory or a /// file; true if nothing is left there. pub fn remove_tree(fs: &mut F, path: &[u8]) -> bool { if fs.delete(path) { return true; } const BATCH: usize = 16; const LONGEST: usize = 255; loop { let mut entries = [DirEntry::empty(); BATCH]; let Some(n) = fs.list_dir(path, &mut entries) else { // Neither a file nor a directory: nothing there. return true; }; let children = entries[..n].iter().filter(|e| e.name.as_bytes() != b"." && e.name.as_bytes() != b".."); let mut any = false; for e in children { any = true; let mut child = [0u8; LONGEST]; let name = e.name.as_bytes(); let len = path.len() + 1 + name.len(); if len > LONGEST { return false; } child[..path.len()].copy_from_slice(path); child[path.len()] = b'/'; child[path.len() + 1..len].copy_from_slice(name); if !remove_tree(fs, &child[..len]) { return false; } } if !any { return fs.rmdir(path); } } }