Files
k-os/crates/libkos/src/fs.rs

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),
}
}