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,96 +1,208 @@
|
||||
use server_framework::Server;
|
||||
use server_framework::{Decode, Encode, MALFORMED, Server};
|
||||
use types::bounded::CapSlot;
|
||||
|
||||
use crate::filesystem::{FileSystem, DirEntry};
|
||||
use crate::protocol::{StorageRequest, StorageResponse};
|
||||
use crate::filesystem::{DirEntry, FileHandle, FileSystem};
|
||||
use crate::protocol::{
|
||||
DIR_ENTRY_SIZE, DIR_NAME_SIZE, MAX_PATH, StorageRequest, StorageResponse,
|
||||
};
|
||||
|
||||
pub struct StorageServer<F: FileSystem> {
|
||||
fs: F,
|
||||
const MAX_CLIENTS: usize = 8;
|
||||
/// Handles at or past this are never valid (the file systems hand out
|
||||
/// fewer), so ownership fits a small table.
|
||||
const MAX_HANDLES: usize = 16;
|
||||
const MAX_LISTED: usize = 64;
|
||||
|
||||
/// Where client buffers come from: a fresh region per client in the
|
||||
/// real server, plain memory in tests.
|
||||
pub trait Buffers {
|
||||
/// A buffer for a new client and the capability that lets the client
|
||||
/// map it, if there is one to send.
|
||||
fn create(&mut self, client: usize) -> Option<(&'static mut [u8], Option<CapSlot>)>;
|
||||
}
|
||||
|
||||
impl<F: FileSystem> StorageServer<F> {
|
||||
pub fn new(fs: F) -> Self {
|
||||
Self { fs }
|
||||
struct Client {
|
||||
badge: usize,
|
||||
buf: &'static mut [u8],
|
||||
}
|
||||
|
||||
pub struct StorageServer<F: FileSystem, B: Buffers> {
|
||||
fs: F,
|
||||
buffers: B,
|
||||
clients: [Option<Client>; MAX_CLIENTS],
|
||||
/// The badge of the client that opened each handle.
|
||||
owners: [Option<usize>; MAX_HANDLES],
|
||||
}
|
||||
|
||||
impl<F: FileSystem, B: Buffers> StorageServer<F, B> {
|
||||
pub fn new(fs: F, buffers: B) -> Self {
|
||||
Self {
|
||||
fs,
|
||||
buffers,
|
||||
clients: [const { None }; MAX_CLIENTS],
|
||||
owners: [None; MAX_HANDLES],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn filesystem(&self) -> &F {
|
||||
&self.fs
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn client_buffer_for_test(&mut self, badge: usize) -> Option<&mut [u8]> {
|
||||
self.client(badge).map(|c| &mut *c.buf)
|
||||
}
|
||||
|
||||
fn client(&mut self, badge: usize) -> Option<&mut Client> {
|
||||
self.clients.iter_mut().flatten().find(|c| c.badge == badge)
|
||||
}
|
||||
|
||||
fn attach(&mut self, badge: usize) -> (StorageResponse, Option<CapSlot>) {
|
||||
if self.client(badge).is_some() {
|
||||
return (StorageResponse::Error, None);
|
||||
}
|
||||
let Some(index) = self.clients.iter().position(Option::is_none) else {
|
||||
return (StorageResponse::Error, None);
|
||||
};
|
||||
let Some((buf, cap)) = self.buffers.create(index) else {
|
||||
return (StorageResponse::Error, None);
|
||||
};
|
||||
let size = buf.len();
|
||||
if let Some(slot) = self.clients.get_mut(index) {
|
||||
*slot = Some(Client { badge, buf });
|
||||
}
|
||||
(StorageResponse::Attached { size }, cap)
|
||||
}
|
||||
|
||||
fn owns(&self, badge: usize, handle: FileHandle) -> bool {
|
||||
self.owners.get(handle.raw() as usize).copied().flatten() == Some(badge)
|
||||
}
|
||||
|
||||
/// Serves one request from `badge`.
|
||||
fn serve(&mut self, badge: usize, request: StorageRequest) -> (StorageResponse, Option<CapSlot>) {
|
||||
use StorageRequest::*;
|
||||
use StorageResponse as R;
|
||||
if request == Attach {
|
||||
return self.attach(badge);
|
||||
}
|
||||
let handle_ok = match request {
|
||||
Read { handle, .. } | Write { handle, .. } | Close { handle } | FileSize { handle } => {
|
||||
self.owns(badge, handle)
|
||||
}
|
||||
_ => true,
|
||||
};
|
||||
if !handle_ok {
|
||||
return (R::Error, None);
|
||||
}
|
||||
let mutates = matches!(request, Write { .. } | Create { .. } | Delete { .. } | Mkdir { .. });
|
||||
if mutates && !self.fs.writable() {
|
||||
return (R::ReadOnly, None);
|
||||
}
|
||||
// Split borrows: the client's buffer, the file system, the owners.
|
||||
let Self { fs, clients, owners, .. } = self;
|
||||
let Some(client) = clients.iter_mut().flatten().find(|c| c.badge == badge) else {
|
||||
return (R::NotAttached, None);
|
||||
};
|
||||
let buf = &mut *client.buf;
|
||||
let room = buf.len();
|
||||
let path = |len: usize| ..len.min(MAX_PATH).min(room);
|
||||
let opened = |owners: &mut [Option<usize>; MAX_HANDLES], h: Option<FileHandle>| match h {
|
||||
Some(h) => match owners.get_mut(h.raw() as usize) {
|
||||
Some(owner) => {
|
||||
*owner = Some(badge);
|
||||
R::Handle(h)
|
||||
}
|
||||
// A handle the table cannot track is one we cannot police.
|
||||
None => R::Error,
|
||||
},
|
||||
None => R::NotFound,
|
||||
};
|
||||
let response = match request {
|
||||
Attach => R::Error,
|
||||
Open { path_len } => {
|
||||
let h = fs.open(&buf[path(path_len)]);
|
||||
opened(owners, h)
|
||||
}
|
||||
Create { path_len } => match fs.create(&buf[path(path_len)]) {
|
||||
None => R::Error,
|
||||
h => opened(owners, h),
|
||||
},
|
||||
Read { handle, len } => match fs.read(handle, &mut buf[..len.min(room)]) {
|
||||
Some(bytes) => R::Data { bytes },
|
||||
None => R::Error,
|
||||
},
|
||||
Write { handle, len } => match fs.write(handle, &buf[..len.min(room)]) {
|
||||
Some(bytes) => R::Written { bytes },
|
||||
None => R::Error,
|
||||
},
|
||||
Close { handle } => {
|
||||
fs.close(handle);
|
||||
if let Some(owner) = owners.get_mut(handle.raw() as usize) {
|
||||
*owner = None;
|
||||
}
|
||||
R::Ok
|
||||
}
|
||||
FileSize { handle } => match fs.file_size(handle) {
|
||||
Some(size) => R::Size(size),
|
||||
None => R::Error,
|
||||
},
|
||||
ListDir { path_len } => {
|
||||
let mut name = [0u8; MAX_PATH];
|
||||
let p = &buf[path(path_len)];
|
||||
name[..p.len()].copy_from_slice(p);
|
||||
let mut entries = [DirEntry::empty(); MAX_LISTED];
|
||||
let room = (buf.len() / DIR_ENTRY_SIZE).min(MAX_LISTED);
|
||||
match fs.list_dir(&name[..p.len()], &mut entries[..room]) {
|
||||
Some(count) => {
|
||||
for (entry, record) in entries.iter().take(count).zip(buf.chunks_mut(DIR_ENTRY_SIZE)) {
|
||||
write_entry(entry, record);
|
||||
}
|
||||
R::DirEntries { count }
|
||||
}
|
||||
None => R::NotFound,
|
||||
}
|
||||
}
|
||||
Delete { path_len } => {
|
||||
if fs.delete(&buf[path(path_len)]) { R::Ok } else { R::NotFound }
|
||||
}
|
||||
Mkdir { path_len } => {
|
||||
if fs.mkdir(&buf[path(path_len)]) { R::Ok } else { R::Error }
|
||||
}
|
||||
};
|
||||
(response, None)
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: FileSystem> Server for StorageServer<F> {
|
||||
fn write_entry(entry: &DirEntry, record: &mut [u8]) {
|
||||
record.fill(0);
|
||||
let name = entry.name.as_bytes();
|
||||
let n = name.len().min(DIR_NAME_SIZE);
|
||||
record[..n].copy_from_slice(&name[..n]);
|
||||
record[DIR_NAME_SIZE..DIR_NAME_SIZE + 8].copy_from_slice(&(entry.size as u64).to_le_bytes());
|
||||
let flags = if entry.is_dir { types::storage::DIR_FLAG_DIRECTORY } else { 0 };
|
||||
record[DIR_NAME_SIZE + 8..DIR_ENTRY_SIZE].copy_from_slice(&flags.to_le_bytes());
|
||||
}
|
||||
|
||||
impl<F: FileSystem, B: Buffers> Server for StorageServer<F, B> {
|
||||
type Request = StorageRequest;
|
||||
type Response = StorageResponse;
|
||||
|
||||
/// Without a badge: as the unbadged client.
|
||||
fn handle(&mut self, request: StorageRequest) -> StorageResponse {
|
||||
match request {
|
||||
StorageRequest::Open { path } => {
|
||||
let buf = path.to_bytes();
|
||||
let len = buf.iter().position(|&b| b == 0).unwrap_or(16);
|
||||
match self.fs.open(&buf[..len]) {
|
||||
Some(h) => StorageResponse::Handle(h),
|
||||
None => StorageResponse::NotFound,
|
||||
}
|
||||
}
|
||||
StorageRequest::Read { handle, max_bytes } => {
|
||||
let mut buf = [0u8; 512];
|
||||
let read_len = max_bytes.min(buf.len());
|
||||
match self.fs.read(handle, &mut buf[..read_len]) {
|
||||
Some(n) => StorageResponse::Data { bytes_read: n },
|
||||
None => StorageResponse::Error,
|
||||
}
|
||||
}
|
||||
StorageRequest::Write { handle, data_word } => {
|
||||
let data = data_word.to_le_bytes();
|
||||
match self.fs.write(handle, &data) {
|
||||
Some(n) => StorageResponse::Written { bytes_written: n },
|
||||
None => StorageResponse::Error,
|
||||
}
|
||||
}
|
||||
StorageRequest::Close { handle } => {
|
||||
self.fs.close(handle);
|
||||
StorageResponse::Ok
|
||||
}
|
||||
StorageRequest::ListDir { path } => {
|
||||
let buf = path.to_bytes();
|
||||
let len = buf.iter().position(|&b| b == 0).unwrap_or(16);
|
||||
let mut entries = [DirEntry::empty(); 16];
|
||||
match self.fs.list_dir(&buf[..len], &mut entries) {
|
||||
Some(count) => StorageResponse::DirEntries { count },
|
||||
None => StorageResponse::NotFound,
|
||||
}
|
||||
}
|
||||
StorageRequest::Create { path } => {
|
||||
let buf = path.to_bytes();
|
||||
let len = buf.iter().position(|&b| b == 0).unwrap_or(16);
|
||||
match self.fs.create(&buf[..len]) {
|
||||
Some(h) => StorageResponse::Handle(h),
|
||||
None => StorageResponse::Error,
|
||||
}
|
||||
}
|
||||
StorageRequest::Delete { path } => {
|
||||
let buf = path.to_bytes();
|
||||
let len = buf.iter().position(|&b| b == 0).unwrap_or(16);
|
||||
if self.fs.delete(&buf[..len]) {
|
||||
StorageResponse::Ok
|
||||
} else {
|
||||
StorageResponse::NotFound
|
||||
}
|
||||
}
|
||||
StorageRequest::FileSize { handle } => {
|
||||
match self.fs.file_size(handle) {
|
||||
Some(size) => StorageResponse::Size(size),
|
||||
None => StorageResponse::Error,
|
||||
}
|
||||
}
|
||||
StorageRequest::Mkdir { path } => {
|
||||
let buf = path.to_bytes();
|
||||
let len = buf.iter().position(|&b| b == 0).unwrap_or(16);
|
||||
if self.fs.mkdir(&buf[..len]) {
|
||||
StorageResponse::Ok
|
||||
} else {
|
||||
StorageResponse::Error
|
||||
}
|
||||
self.serve(0, request).0
|
||||
}
|
||||
|
||||
fn handle_call(
|
||||
&mut self,
|
||||
badge: usize,
|
||||
words: [usize; 4],
|
||||
_cap: Option<CapSlot>,
|
||||
) -> ([usize; 4], Option<CapSlot>) {
|
||||
match StorageRequest::decode(words) {
|
||||
Some(request) => {
|
||||
let (response, cap) = self.serve(badge, request);
|
||||
(response.encode(), cap)
|
||||
}
|
||||
None => ([MALFORMED, 0, 0, 0], None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user