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:
2026-10-27 12:42:37 +01:00
parent 694c675a40
commit 909b3f0503
19 changed files with 827 additions and 506 deletions

View File

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