feat(storage): full file protocol over per-client shared region (badged clients, owned handles, clamped lengths, ReadOnly on unwritable disk); init loads via libkos::fs; drop boot shared buffer
This commit is contained in:
@@ -1,5 +1,12 @@
|
||||
//! 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);
|
||||
@@ -10,78 +17,167 @@ impl FileHandle {
|
||||
}
|
||||
}
|
||||
|
||||
fn pack_path(path: &[u8]) -> (usize, usize) {
|
||||
let mut w0 = [0u8; 8];
|
||||
let mut w1 = [0u8; 8];
|
||||
for (i, &b) in path.iter().enumerate() {
|
||||
if i < 8 {
|
||||
w0[i] = b;
|
||||
} else if i < 16 {
|
||||
w1[i - 8] = b;
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum FsError {
|
||||
NotFound,
|
||||
/// The disk cannot be written.
|
||||
ReadOnly,
|
||||
/// A path longer than the server accepts.
|
||||
PathTooLong,
|
||||
/// 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::ERROR | reply::NOT_ATTACHED => Err(FsError::Failed),
|
||||
_ => Ok(r),
|
||||
}
|
||||
}
|
||||
(usize::from_le_bytes(w0), usize::from_le_bytes(w1))
|
||||
}
|
||||
|
||||
/// A failed call reads as an error reply (tag `usize::MAX`).
|
||||
fn ipc(ep: CapSlot, w0: usize, w1: usize, w2: usize, w3: usize) -> (usize, usize, usize, usize) {
|
||||
let r = syscall::request(ep, [w0, w1, w2, w3]).unwrap_or([usize::MAX, 0, 0, 0]);
|
||||
(r[0], r[1], r[2], r[3])
|
||||
}
|
||||
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) }
|
||||
}
|
||||
|
||||
pub fn open(ep: CapSlot, path: &[u8]) -> Option<FileHandle> {
|
||||
let (p0, p1) = pack_path(path);
|
||||
let (tag, handle, _, _) = ipc(ep, 0, p0, p1, 0);
|
||||
if tag == 0 {
|
||||
Some(FileHandle(handle as u16))
|
||||
} else {
|
||||
None
|
||||
/// 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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read(ep: CapSlot, handle: FileHandle, max_bytes: usize) -> Option<usize> {
|
||||
let (tag, bytes_read, _, _) = ipc(ep, 1, handle.0 as usize, max_bytes, 0);
|
||||
if tag == 1 { Some(bytes_read) } else { None }
|
||||
}
|
||||
|
||||
pub fn write(ep: CapSlot, handle: FileHandle, data_word: usize) -> Option<usize> {
|
||||
let (tag, bytes_written, _, _) = ipc(ep, 2, handle.0 as usize, data_word, 0);
|
||||
if tag == 2 { Some(bytes_written) } else { None }
|
||||
}
|
||||
|
||||
pub fn close(ep: CapSlot, handle: FileHandle) {
|
||||
ipc(ep, 3, handle.0 as usize, 0, 0);
|
||||
}
|
||||
|
||||
pub fn list_dir(ep: CapSlot, path: &[u8]) -> Option<usize> {
|
||||
let (p0, p1) = pack_path(path);
|
||||
let (tag, count, _, _) = ipc(ep, 4, p0, p1, 0);
|
||||
if tag == 4 { Some(count) } else { None }
|
||||
}
|
||||
|
||||
pub fn create(ep: CapSlot, path: &[u8]) -> Option<FileHandle> {
|
||||
let (p0, p1) = pack_path(path);
|
||||
let (tag, handle, _, _) = ipc(ep, 5, p0, p1, 0);
|
||||
if tag == 0 {
|
||||
Some(FileHandle(handle as u16))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn delete(ep: CapSlot, path: &[u8]) -> bool {
|
||||
let (p0, p1) = pack_path(path);
|
||||
let (tag, _, _, _) = ipc(ep, 6, p0, p1, 0);
|
||||
tag == 3
|
||||
}
|
||||
|
||||
pub fn file_size(ep: CapSlot, handle: FileHandle) -> Option<usize> {
|
||||
let (tag, size, _, _) = ipc(ep, 7, handle.0 as usize, 0, 0);
|
||||
if tag == 5 { Some(size) } else { None }
|
||||
}
|
||||
|
||||
pub fn mkdir(ep: CapSlot, path: &[u8]) -> bool {
|
||||
let (p0, p1) = pack_path(path);
|
||||
let (tag, _, _, _) = ipc(ep, 8, p0, p1, 0);
|
||||
tag == 3
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user