211 lines
7.6 KiB
Rust
211 lines
7.6 KiB
Rust
//! 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<T> = core::result::Result<T, FsError>;
|
|
|
|
#[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<Self> {
|
|
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<usize> {
|
|
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<usize> {
|
|
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<usize> {
|
|
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<CapSlot> {
|
|
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<FileHandle> {
|
|
self.with_path(op::OPEN, path, reply::HANDLE).map(|h| FileHandle(h as u16))
|
|
}
|
|
|
|
pub fn create(&mut self, path: &[u8]) -> Result<FileHandle> {
|
|
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<usize> {
|
|
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<usize> {
|
|
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<usize> {
|
|
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<usize> {
|
|
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),
|
|
}
|
|
}
|