//! The storage client (protocol: `storage_server::protocol`, wire numbers: //! `types::storage`). Attach once; paths and data then go through the //! buffer the server shares with this process. use crate::syscall; use types::address::VirtualAddress; use types::bounded::CapSlot; use types::mapping::PagePerms; use types::storage::{DIR_ENTRY_SIZE, DIR_FLAG_DIRECTORY, DIR_NAME_SIZE, MAX_PATH, op, reply}; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct FileHandle(u16); impl FileHandle { pub const fn raw(&self) -> u16 { self.0 } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum FsError { NotFound, /// The disk cannot be written. ReadOnly, /// A path longer than the server accepts. PathTooLong, /// Outside this connection's scope, or beyond its rights. Denied, /// The server refused, or the call itself failed. Failed, } pub type Result = core::result::Result; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct DirEntry { pub name: [u8; DIR_NAME_SIZE], pub name_len: usize, pub size: u64, pub is_dir: bool, } impl DirEntry { pub fn name(&self) -> &[u8] { &self.name[..self.name_len] } } /// A connection to the storage server. pub struct Storage { ep: CapSlot, buf: &'static mut [u8], } impl Storage { /// Attaches to the server behind `ep`, mapping the buffer it shares /// at `va` (which must be free for `BUFFER_SIZE` bytes). pub fn attach(ep: CapSlot, va: usize) -> Result { let reply = syscall::call(ep, [op::ATTACH, 0, 0, 0], None).map_err(|_| FsError::Failed)?; let [tag, size, _, _] = reply.words(); let cap = match reply { syscall::Received::Message { cap, .. } | syscall::Received::Call { cap, .. } => cap, syscall::Received::Signal { .. } => None, }; let (true, Some(region)) = (tag == reply::ATTACHED, cap) else { return Err(FsError::Failed); }; syscall::map_region(region, VirtualAddress::from_raw(va), PagePerms::ReadWrite) .map_err(|_| FsError::Failed)?; // SAFETY: the region the server made, `size` bytes, is now mapped // read-write at `va` and stays so while this process holds it. let buf = unsafe { core::slice::from_raw_parts_mut(va as *mut u8, size) }; Ok(Self { ep, buf }) } fn call(&self, words: [usize; 4]) -> Result<[usize; 4]> { let r = syscall::request(self.ep, words).map_err(|_| FsError::Failed)?; match r[0] { reply::NOT_FOUND => Err(FsError::NotFound), reply::READ_ONLY => Err(FsError::ReadOnly), reply::DENIED => Err(FsError::Denied), reply::ERROR | reply::NOT_ATTACHED => Err(FsError::Failed), _ => Ok(r), } } fn expect(&self, words: [usize; 4], tag: usize) -> Result { let r = self.call(words)?; if r[0] == tag { Ok(r[1]) } else { Err(FsError::Failed) } } /// Puts `path` in the buffer; returns its length. fn put_path(&mut self, path: &[u8]) -> Result { if path.len() > MAX_PATH || path.len() > self.buf.len() { return Err(FsError::PathTooLong); } self.buf[..path.len()].copy_from_slice(path); Ok(path.len()) } fn with_path(&mut self, opcode: usize, path: &[u8], tag: usize) -> Result { let len = self.put_path(path)?; self.expect([opcode, len, 0, 0], tag) } /// A storage capability to `path` inside this connection's scope, /// writable only if `write` and this one is. It can be passed on; /// `release` takes it back. pub fn grant(&mut self, path: &[u8], write: bool) -> Result { let len = self.put_path(path)?; let reply = syscall::call(self.ep, [op::GRANT, len, usize::from(write), 0], None) .map_err(|_| FsError::Failed)?; match (reply.words()[0], reply) { (reply::GRANTED, syscall::Received::Message { cap: Some(cap), .. }) => Ok(cap), (reply::DENIED, _) => Err(FsError::Denied), _ => Err(FsError::Failed), } } pub fn open(&mut self, path: &[u8]) -> Result { self.with_path(op::OPEN, path, reply::HANDLE).map(|h| FileHandle(h as u16)) } pub fn create(&mut self, path: &[u8]) -> Result { self.with_path(op::CREATE, path, reply::HANDLE).map(|h| FileHandle(h as u16)) } pub fn delete(&mut self, path: &[u8]) -> Result<()> { self.with_path(op::DELETE, path, reply::OK).map(|_| ()) } pub fn mkdir(&mut self, path: &[u8]) -> Result<()> { self.with_path(op::MKDIR, path, reply::OK).map(|_| ()) } pub fn close(&mut self, handle: FileHandle) -> Result<()> { self.expect([op::CLOSE, handle.0 as usize, 0, 0], reply::OK).map(|_| ()) } pub fn file_size(&mut self, handle: FileHandle) -> Result { self.expect([op::FILE_SIZE, handle.0 as usize, 0, 0], reply::SIZE) } /// Reads from the current position into `out`, as many round trips as /// it takes; returns how much was read (less than `out.len()` at the /// end of the file). pub fn read(&mut self, handle: FileHandle, out: &mut [u8]) -> Result { let mut done = 0; while done < out.len() { let want = (out.len() - done).min(self.buf.len()); let got = self.expect([op::READ, handle.0 as usize, want, 0], reply::DATA)?; let got = got.min(want); out[done..done + got].copy_from_slice(&self.buf[..got]); done += got; if got < want { break; } } Ok(done) } /// Writes all of `data` at the current position; returns how much the /// server took (less only if it ran out of room). pub fn write(&mut self, handle: FileHandle, data: &[u8]) -> Result { let mut done = 0; while done < data.len() { let chunk = (data.len() - done).min(self.buf.len()); self.buf[..chunk].copy_from_slice(&data[done..done + chunk]); let took = self.expect([op::WRITE, handle.0 as usize, chunk, 0], reply::WRITTEN)?; done += took.min(chunk); if took < chunk { break; } } Ok(done) } /// Calls `f` with every entry of the directory at `path`. pub fn list_dir(&mut self, path: &[u8], mut f: impl FnMut(&DirEntry)) -> Result { let count = self.with_path(op::LIST_DIR, path, reply::DIR_ENTRIES)?; let count = count.min(self.buf.len() / DIR_ENTRY_SIZE); for record in self.buf.chunks(DIR_ENTRY_SIZE).take(count) { let mut name = [0u8; DIR_NAME_SIZE]; name.copy_from_slice(&record[..DIR_NAME_SIZE]); let name_len = name.iter().position(|&b| b == 0).unwrap_or(DIR_NAME_SIZE); let word = |at: usize| { let mut w = [0u8; 8]; w.copy_from_slice(&record[at..at + 8]); u64::from_le_bytes(w) }; f(&DirEntry { name, name_len, size: word(DIR_NAME_SIZE), is_dir: word(DIR_NAME_SIZE + 8) & DIR_FLAG_DIRECTORY != 0, }); } Ok(count) } } /// Takes back `grant` and everything granted from it, from every holder; /// the slot is empty afterwards. pub fn release(grant: CapSlot) -> Result<()> { match syscall::request(grant, [op::RELEASE, 0, 0, 0]) { Ok([reply::OK, ..]) => Ok(()), Ok([reply::DENIED, ..]) => Err(FsError::Denied), _ => Err(FsError::Failed), } }