133 lines
3.9 KiB
Rust
133 lines
3.9 KiB
Rust
#[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<FileHandle>;
|
|
fn read(&mut self, handle: FileHandle, buf: &mut [u8]) -> Option<usize>;
|
|
fn write(&mut self, handle: FileHandle, buf: &[u8]) -> Option<usize>;
|
|
fn close(&mut self, handle: FileHandle);
|
|
fn list_dir(&mut self, path: &[u8], entries: &mut [DirEntry]) -> Option<usize>;
|
|
fn create(&mut self, path: &[u8]) -> Option<FileHandle>;
|
|
fn delete(&mut self, path: &[u8]) -> bool;
|
|
fn file_size(&mut self, handle: FileHandle) -> Option<usize>;
|
|
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<F: FileSystem + ?Sized>(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);
|
|
}
|
|
}
|
|
}
|