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,12 +1,13 @@
|
|||||||
// What boot still sets up for userspace, all through the kernel's mapping
|
// What boot still sets up for userspace, all through the kernel's mapping
|
||||||
// API: a buffer init and storage share, the display's page quota, and the
|
// API: the display's page quota and the BootInfo page that tells init
|
||||||
// BootInfo page that tells init where its device capabilities are. Drivers
|
// where its device capabilities are. Drivers set up their own devices
|
||||||
// set up their own devices with the capabilities init hands them.
|
// with the capabilities init hands them; processes share memory through
|
||||||
|
// regions they create.
|
||||||
|
|
||||||
use crate::hw::uart::Uart;
|
use crate::hw::uart::Uart;
|
||||||
use types::address::VirtualAddress;
|
use types::address::VirtualAddress;
|
||||||
use types::bootinfo::{BOOT_INFO_VA, BootDevice, BootInfo, MAGIC, MAX_BOOT_DEVICES, NO_SLOT};
|
use types::bootinfo::{BOOT_INFO_VA, BootDevice, BootInfo, MAGIC, MAX_BOOT_DEVICES, NO_SLOT};
|
||||||
use types::bounded::{CapSlot, PageCount};
|
use types::bounded::CapSlot;
|
||||||
use types::id::ProcessId;
|
use types::id::ProcessId;
|
||||||
use types::mapping::PagePerms;
|
use types::mapping::PagePerms;
|
||||||
|
|
||||||
@@ -17,25 +18,6 @@ fn kern() -> &'static mut kernel::Kernel<crate::platform::RiscVPlatform> {
|
|||||||
unsafe { &mut *crate::kernel_ptr() }
|
unsafe { &mut *crate::kernel_ptr() }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pages(uart: &Uart, n: usize) -> PageCount {
|
|
||||||
u16::try_from(n)
|
|
||||||
.ok()
|
|
||||||
.and_then(PageCount::new)
|
|
||||||
.unwrap_or_else(|| crate::halt(uart, "bad page count"))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Shares a fresh region between `owner` and `other` at the same `va`.
|
|
||||||
pub(crate) fn shared_buffer(uart: &Uart, owner: ProcessId, other: ProcessId, va: usize, n: usize) {
|
|
||||||
let id = kern()
|
|
||||||
.create_pool_region(owner, pages(uart, n))
|
|
||||||
.unwrap_or_else(|_| crate::halt(uart, "shared region failed"));
|
|
||||||
for pid in [owner, other] {
|
|
||||||
kern()
|
|
||||||
.map_region(id, pid, VirtualAddress::from_raw(va), PagePerms::ReadWrite)
|
|
||||||
.unwrap_or_else(|_| crate::halt(uart, "shared map failed"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Lets the display server hold a framebuffer.
|
/// Lets the display server hold a framebuffer.
|
||||||
pub(crate) fn display_quota(uart: &Uart, display: ProcessId) {
|
pub(crate) fn display_quota(uart: &Uart, display: ProcessId) {
|
||||||
kern()
|
kern()
|
||||||
|
|||||||
@@ -106,9 +106,6 @@ boot_page_table:
|
|||||||
"#
|
"#
|
||||||
);
|
);
|
||||||
|
|
||||||
const SHARED_BUF_VA: usize = 0x30000;
|
|
||||||
const SHARED_BUF_PAGES: usize = 16;
|
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
extern "C" fn kmain(hart: usize) -> ! {
|
extern "C" fn kmain(hart: usize) -> ! {
|
||||||
let uart = Uart::new();
|
let uart = Uart::new();
|
||||||
@@ -121,10 +118,7 @@ extern "C" fn kmain(hart: usize) -> ! {
|
|||||||
let mut blk =
|
let mut blk =
|
||||||
boot::hardware::probe_block_device(&uart).unwrap_or_else(|| halt(&uart, "no block device"));
|
boot::hardware::probe_block_device(&uart).unwrap_or_else(|| halt(&uart, "no block device"));
|
||||||
|
|
||||||
let (init, init_thread) = boot::loader::load_init(&uart, &mut blk);
|
let (init, _) = boot::loader::load_init(&uart, &mut blk);
|
||||||
if let Some(ctx) = kern_mut().thread_context_mut(init_thread) {
|
|
||||||
ctx.registers_mut()[10] = SHARED_BUF_VA;
|
|
||||||
}
|
|
||||||
let storage = boot::loader::load_server(&uart, &mut blk, b"storage.elf", "storage");
|
let storage = boot::loader::load_server(&uart, &mut blk, b"storage.elf", "storage");
|
||||||
let display = boot::loader::load_server(&uart, &mut blk, b"display.elf", "display");
|
let display = boot::loader::load_server(&uart, &mut blk, b"display.elf", "display");
|
||||||
let input = boot::loader::load_server(&uart, &mut blk, b"input.elf", "input");
|
let input = boot::loader::load_server(&uart, &mut blk, b"input.elf", "input");
|
||||||
@@ -133,13 +127,6 @@ extern "C" fn kmain(hart: usize) -> ! {
|
|||||||
kern_mut()
|
kern_mut()
|
||||||
.register_board(init.pid, &board)
|
.register_board(init.pid, &board)
|
||||||
.unwrap_or_else(|_| halt(&uart, "board registration failed"));
|
.unwrap_or_else(|_| halt(&uart, "board registration failed"));
|
||||||
devices::shared_buffer(
|
|
||||||
&uart,
|
|
||||||
init.pid,
|
|
||||||
storage.pid,
|
|
||||||
SHARED_BUF_VA,
|
|
||||||
SHARED_BUF_PAGES,
|
|
||||||
);
|
|
||||||
devices::display_quota(&uart, display.pid);
|
devices::display_quota(&uart, display.pid);
|
||||||
boot::caps::grant_all(&uart, init.pid, storage.pid, display.pid, input.pid);
|
boot::caps::grant_all(&uart, init.pid, storage.pid, display.pid, input.pid);
|
||||||
// After grant_all, so the slots init already relies on stay put.
|
// After grant_all, so the slots init already relies on stay put.
|
||||||
|
|||||||
@@ -76,4 +76,7 @@ impl BlockDevice for VirtioBlockDevice {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn write_sector(&mut self, _sector: SectorId, _buf: &[u8]) -> bool { false }
|
fn write_sector(&mut self, _sector: SectorId, _buf: &[u8]) -> bool { false }
|
||||||
|
|
||||||
|
/// Writing is not implemented yet.
|
||||||
|
fn writable(&self) -> bool { false }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,26 +7,33 @@ mod devices;
|
|||||||
use types::address::{EntryPoint, StackPointer, VirtualAddress};
|
use types::address::{EntryPoint, StackPointer, VirtualAddress};
|
||||||
use types::bounded::{CapSlot, PageCount};
|
use types::bounded::{CapSlot, PageCount};
|
||||||
use types::permission::{Permission, PermissionSet};
|
use types::permission::{Permission, PermissionSet};
|
||||||
|
use libkos::fs::Storage;
|
||||||
use libkos::syscall;
|
use libkos::syscall;
|
||||||
|
|
||||||
const PAGE_SIZE: usize = 4096;
|
const PAGE_SIZE: usize = 4096;
|
||||||
const USER_CODE_BASE: usize = 0x10000;
|
const USER_CODE_BASE: usize = 0x10000;
|
||||||
const USER_STACK_TOP: StackPointer = StackPointer::from_raw(USER_CODE_BASE);
|
const USER_STACK_TOP: StackPointer = StackPointer::from_raw(USER_CODE_BASE);
|
||||||
const USER_STACK_PAGES: usize = 4;
|
const USER_STACK_PAGES: usize = 4;
|
||||||
const CMD_READ_FILE: usize = 1;
|
/// Where a program file is read to before it is loaded (16 pages: the
|
||||||
|
/// largest program init will load).
|
||||||
|
const SCRATCH_VA: usize = 0x30000;
|
||||||
|
const SCRATCH_PAGES: u16 = 16;
|
||||||
|
/// Where the buffer storage shares with init is mapped.
|
||||||
|
const STORAGE_BUF_VA: usize = 0x0180_0000;
|
||||||
|
|
||||||
const CREATE_CAP: CapSlot = CapSlot::const_new(0);
|
const CREATE_CAP: CapSlot = CapSlot::const_new(0);
|
||||||
const STORAGE_EP: CapSlot = CapSlot::const_new(2);
|
const STORAGE_EP: CapSlot = CapSlot::const_new(2);
|
||||||
const DISPLAY_EP: CapSlot = CapSlot::const_new(3);
|
const DISPLAY_EP: CapSlot = CapSlot::const_new(3);
|
||||||
const INPUT_EP: CapSlot = CapSlot::const_new(5);
|
const INPUT_EP: CapSlot = CapSlot::const_new(5);
|
||||||
|
|
||||||
fn request_file(shared_buf: *mut u8, filename: &[u8]) -> usize {
|
/// Reads all of `filename` into `scratch`; its size, or `None` if it is
|
||||||
unsafe {
|
/// missing, unreadable or larger than `scratch`.
|
||||||
for (i, &b) in filename.iter().enumerate() {
|
fn read_file(storage: &mut Storage, scratch: &mut [u8], filename: &[u8]) -> Option<usize> {
|
||||||
core::ptr::write_volatile(shared_buf.add(i), b);
|
let handle = storage.open(filename).ok()?;
|
||||||
}
|
let size = storage.file_size(handle).ok().filter(|s| *s <= scratch.len());
|
||||||
}
|
let read = size.and_then(|s| storage.read(handle, &mut scratch[..s]).ok());
|
||||||
syscall::request(STORAGE_EP, [CMD_READ_FILE, filename.len(), 0, 0]).map_or(0, |r| r[0])
|
let _ = storage.close(handle);
|
||||||
|
read.filter(|r| Some(*r) == size)
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LoadedProcess {
|
struct LoadedProcess {
|
||||||
@@ -34,13 +41,9 @@ struct LoadedProcess {
|
|||||||
entry_point: EntryPoint,
|
entry_point: EntryPoint,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_binary(shared_buf: *mut u8, filename: &[u8]) -> Option<LoadedProcess> {
|
fn load_binary(storage: &mut Storage, scratch: &mut [u8], filename: &[u8]) -> Option<LoadedProcess> {
|
||||||
let file_size = request_file(shared_buf, filename);
|
let file_size = read_file(storage, scratch, filename)?;
|
||||||
if file_size == 0 {
|
let elf_data = &scratch[..file_size];
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let elf_data = unsafe { core::slice::from_raw_parts(shared_buf as *const u8, file_size) };
|
|
||||||
let elf_info = match elf::parse(elf_data) {
|
let elf_info = match elf::parse(elf_data) {
|
||||||
Ok(info) => info,
|
Ok(info) => info,
|
||||||
Err(_) => return None,
|
Err(_) => return None,
|
||||||
@@ -63,16 +66,15 @@ fn load_binary(shared_buf: *mut u8, filename: &[u8]) -> Option<LoadedProcess> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let copy_len = segment.file_size.min(segment.mem_size);
|
let copy_len = segment.file_size.min(segment.mem_size);
|
||||||
if copy_len > 0 {
|
let src = segment
|
||||||
let src = segment.file_offset;
|
.file_offset
|
||||||
if src + copy_len <= file_size {
|
.checked_add(copy_len)
|
||||||
unsafe {
|
.and_then(|end| elf_data.get(segment.file_offset..end));
|
||||||
core::ptr::copy_nonoverlapping(
|
if let Some(src) = src.filter(|_| copy_len > 0) {
|
||||||
shared_buf.add(src),
|
// SAFETY: the pages just requested hold seg_pages * PAGE_SIZE
|
||||||
local_ptr.add(offset_in_page),
|
// bytes, which covers offset_in_page + copy_len.
|
||||||
copy_len,
|
unsafe {
|
||||||
);
|
core::ptr::copy_nonoverlapping(src.as_ptr(), local_ptr.add(offset_in_page), copy_len);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,9 +109,7 @@ const SERVERS: &[ServerInfo] = &[
|
|||||||
ServerInfo { filename: b"session.elf", name: "session" },
|
ServerInfo { filename: b"session.elf", name: "session" },
|
||||||
];
|
];
|
||||||
|
|
||||||
pub fn run(shared_buf_va: usize) -> ! {
|
pub fn run() -> ! {
|
||||||
let shared_buf = shared_buf_va as *mut u8;
|
|
||||||
|
|
||||||
libkos::println!("init: started (pid={:#x})", syscall::get_process_id().raw());
|
libkos::println!("init: started (pid={:#x})", syscall::get_process_id().raw());
|
||||||
|
|
||||||
// Drivers wait for their device before serving, so this comes first:
|
// Drivers wait for their device before serving, so this comes first:
|
||||||
@@ -124,6 +124,15 @@ pub fn run(shared_buf_va: usize) -> ! {
|
|||||||
libkos::println!("init: {}: {}", name, if given { "handed to its driver" } else { "none" });
|
libkos::println!("init: {}: {}", name, if given { "handed to its driver" } else { "none" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let Ok(mut storage) = Storage::attach(STORAGE_EP, STORAGE_BUF_VA) else {
|
||||||
|
libkos::println!("init: cannot reach storage, nothing to load");
|
||||||
|
idle();
|
||||||
|
};
|
||||||
|
let Some(scratch) = scratch() else {
|
||||||
|
libkos::println!("init: no memory to load programs into");
|
||||||
|
idle();
|
||||||
|
};
|
||||||
|
|
||||||
let ipc_perms = PermissionSet::empty()
|
let ipc_perms = PermissionSet::empty()
|
||||||
.with(Permission::Read)
|
.with(Permission::Read)
|
||||||
.with(Permission::Execute);
|
.with(Permission::Execute);
|
||||||
@@ -131,7 +140,7 @@ pub fn run(shared_buf_va: usize) -> ! {
|
|||||||
for server in SERVERS {
|
for server in SERVERS {
|
||||||
libkos::print!("init: loading {}... ", server.name);
|
libkos::print!("init: loading {}... ", server.name);
|
||||||
|
|
||||||
let loaded = match load_binary(shared_buf, server.filename) {
|
let loaded = match load_binary(&mut storage, scratch, server.filename) {
|
||||||
Some(l) => l,
|
Some(l) => l,
|
||||||
None => {
|
None => {
|
||||||
libkos::println!("SKIP");
|
libkos::println!("SKIP");
|
||||||
@@ -148,7 +157,7 @@ pub fn run(shared_buf_va: usize) -> ! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
libkos::print!("init: loading snake... ");
|
libkos::print!("init: loading snake... ");
|
||||||
if let Some(snake) = load_binary(shared_buf, b"snake.elf") {
|
if let Some(snake) = load_binary(&mut storage, scratch, b"snake.elf") {
|
||||||
// A client only calls: Execute, never Read, so it cannot pose as
|
// A client only calls: Execute, never Read, so it cannot pose as
|
||||||
// the server and take other clients' requests.
|
// the server and take other clients' requests.
|
||||||
let client = PermissionSet::empty().with(Permission::Execute);
|
let client = PermissionSet::empty().with(Permission::Execute);
|
||||||
@@ -162,8 +171,25 @@ pub fn run(shared_buf_va: usize) -> ! {
|
|||||||
|
|
||||||
libkos::println!("init: all loaded, supervising");
|
libkos::println!("init: all loaded, supervising");
|
||||||
|
|
||||||
// Nothing to supervise yet: block on a notification nobody signals
|
idle();
|
||||||
// rather than spin a core on Yield.
|
}
|
||||||
|
|
||||||
|
/// A region of init's own to read program files into.
|
||||||
|
fn scratch() -> Option<&'static mut [u8]> {
|
||||||
|
let region = syscall::create_region(PageCount::new(SCRATCH_PAGES)?).ok()?;
|
||||||
|
syscall::map_region(
|
||||||
|
region,
|
||||||
|
VirtualAddress::from_raw(SCRATCH_VA),
|
||||||
|
types::mapping::PagePerms::ReadWrite,
|
||||||
|
)
|
||||||
|
.ok()?;
|
||||||
|
// SAFETY: the region is mapped read-write at SCRATCH_VA for good.
|
||||||
|
Some(unsafe { core::slice::from_raw_parts_mut(SCRATCH_VA as *mut u8, SCRATCH_PAGES as usize * PAGE_SIZE) })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Nothing to supervise yet: block on a notification nobody signals
|
||||||
|
/// rather than spin a core on Yield.
|
||||||
|
fn idle() -> ! {
|
||||||
let idle = syscall::create_notification();
|
let idle = syscall::create_notification();
|
||||||
loop {
|
loop {
|
||||||
match idle {
|
match idle {
|
||||||
|
|||||||
@@ -4,6 +4,6 @@
|
|||||||
extern crate libkos;
|
extern crate libkos;
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
extern "C" fn main(shared_buf: usize) {
|
extern "C" fn main() {
|
||||||
init::run(shared_buf);
|
init::run();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 crate::syscall;
|
||||||
|
use types::address::VirtualAddress;
|
||||||
use types::bounded::CapSlot;
|
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)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub struct FileHandle(u16);
|
pub struct FileHandle(u16);
|
||||||
@@ -10,78 +17,167 @@ impl FileHandle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pack_path(path: &[u8]) -> (usize, usize) {
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
let mut w0 = [0u8; 8];
|
pub enum FsError {
|
||||||
let mut w1 = [0u8; 8];
|
NotFound,
|
||||||
for (i, &b) in path.iter().enumerate() {
|
/// The disk cannot be written.
|
||||||
if i < 8 {
|
ReadOnly,
|
||||||
w0[i] = b;
|
/// A path longer than the server accepts.
|
||||||
} else if i < 16 {
|
PathTooLong,
|
||||||
w1[i - 8] = b;
|
/// 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 expect(&self, words: [usize; 4], tag: usize) -> Result<usize> {
|
||||||
fn ipc(ep: CapSlot, w0: usize, w1: usize, w2: usize, w3: usize) -> (usize, usize, usize, usize) {
|
let r = self.call(words)?;
|
||||||
let r = syscall::request(ep, [w0, w1, w2, w3]).unwrap_or([usize::MAX, 0, 0, 0]);
|
if r[0] == tag { Ok(r[1]) } else { Err(FsError::Failed) }
|
||||||
(r[0], r[1], r[2], r[3])
|
}
|
||||||
}
|
|
||||||
|
|
||||||
pub fn open(ep: CapSlot, path: &[u8]) -> Option<FileHandle> {
|
/// Puts `path` in the buffer; returns its length.
|
||||||
let (p0, p1) = pack_path(path);
|
fn put_path(&mut self, path: &[u8]) -> Result<usize> {
|
||||||
let (tag, handle, _, _) = ipc(ep, 0, p0, p1, 0);
|
if path.len() > MAX_PATH || path.len() > self.buf.len() {
|
||||||
if tag == 0 {
|
return Err(FsError::PathTooLong);
|
||||||
Some(FileHandle(handle as u16))
|
}
|
||||||
} else {
|
self.buf[..path.len()].copy_from_slice(path);
|
||||||
None
|
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
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -18,4 +18,8 @@ pub trait BlockDevice {
|
|||||||
fn sector_count(&self) -> usize;
|
fn sector_count(&self) -> usize;
|
||||||
fn read_sector(&mut self, sector: SectorId, buf: &mut [u8]) -> bool;
|
fn read_sector(&mut self, sector: SectorId, buf: &mut [u8]) -> bool;
|
||||||
fn write_sector(&mut self, sector: SectorId, buf: &[u8]) -> bool;
|
fn write_sector(&mut self, sector: SectorId, buf: &[u8]) -> bool;
|
||||||
|
/// Whether `write_sector` can succeed at all.
|
||||||
|
fn writable(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,4 +6,4 @@ pub mod server;
|
|||||||
|
|
||||||
pub use codec::{Decode, Encode};
|
pub use codec::{Decode, Encode};
|
||||||
pub use executor::block_on;
|
pub use executor::block_on;
|
||||||
pub use server::{serve, Server};
|
pub use server::{MALFORMED, Server, serve};
|
||||||
|
|||||||
@@ -18,6 +18,22 @@ pub trait Server {
|
|||||||
/// server's device interrupt). `bits` are the signalled bits.
|
/// server's device interrupt). `bits` are the signalled bits.
|
||||||
fn signal(&mut self, _bits: usize) {}
|
fn signal(&mut self, _bits: usize) {}
|
||||||
|
|
||||||
|
/// Handles one message, seeing who sent it (`badge`) and any
|
||||||
|
/// capability it carried; returns the reply and a capability to send
|
||||||
|
/// back with it. The default decodes the words and calls `handle`;
|
||||||
|
/// servers that track clients or pass capabilities override it.
|
||||||
|
fn handle_call(
|
||||||
|
&mut self,
|
||||||
|
_badge: usize,
|
||||||
|
words: [usize; 4],
|
||||||
|
_cap: Option<CapSlot>,
|
||||||
|
) -> ([usize; 4], Option<CapSlot>) {
|
||||||
|
match Self::Request::decode(words) {
|
||||||
|
Some(request) => (self.handle(request).encode(), None),
|
||||||
|
None => ([MALFORMED, 0, 0, 0], None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Serves `endpoint` until receiving on it fails (it was destroyed or
|
/// Serves `endpoint` until receiving on it fails (it was destroyed or
|
||||||
/// the capability revoked), then returns why. Every `Call` gets a
|
/// the capability revoked), then returns why. Every `Call` gets a
|
||||||
/// reply, `[MALFORMED, ..]` if it does not decode; a one-way `Send` is
|
/// reply, `[MALFORMED, ..]` if it does not decode; a one-way `Send` is
|
||||||
@@ -31,17 +47,12 @@ pub trait Server {
|
|||||||
Err(e) => return e,
|
Err(e) => return e,
|
||||||
};
|
};
|
||||||
next = match message {
|
next = match message {
|
||||||
Received::Call { words, .. } => {
|
Received::Call { badge, words, cap } => {
|
||||||
let reply = match Self::Request::decode(words) {
|
let (reply, give) = self.handle_call(badge, words, cap);
|
||||||
Some(request) => self.handle(request).encode(),
|
reply_recv(endpoint, reply, give)
|
||||||
None => [MALFORMED, 0, 0, 0],
|
|
||||||
};
|
|
||||||
reply_recv(endpoint, reply, None)
|
|
||||||
}
|
}
|
||||||
Received::Message { words, .. } => {
|
Received::Message { badge, words, cap } => {
|
||||||
if let Some(request) = Self::Request::decode(words) {
|
let _ = self.handle_call(badge, words, cap);
|
||||||
let _ = self.handle(request);
|
|
||||||
}
|
|
||||||
receive_msg(endpoint)
|
receive_msg(endpoint)
|
||||||
}
|
}
|
||||||
Received::Signal { bits } => {
|
Received::Signal { bits } => {
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ impl FileEntry {
|
|||||||
pub struct MockFileSystem {
|
pub struct MockFileSystem {
|
||||||
files: [Option<FileEntry>; MAX_FILES],
|
files: [Option<FileEntry>; MAX_FILES],
|
||||||
count: usize,
|
count: usize,
|
||||||
|
read_only: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MockFileSystem {
|
impl MockFileSystem {
|
||||||
@@ -31,9 +32,16 @@ impl MockFileSystem {
|
|||||||
Self {
|
Self {
|
||||||
files: [const { None }; MAX_FILES],
|
files: [const { None }; MAX_FILES],
|
||||||
count: 0,
|
count: 0,
|
||||||
|
read_only: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The same file system, refusing all writes.
|
||||||
|
pub fn read_only(mut self) -> Self {
|
||||||
|
self.read_only = true;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
fn find_by_name(&self, path: &[u8]) -> Option<usize> {
|
fn find_by_name(&self, path: &[u8]) -> Option<usize> {
|
||||||
for i in 0..MAX_FILES {
|
for i in 0..MAX_FILES {
|
||||||
if let Some(entry) = &self.files[i] {
|
if let Some(entry) = &self.files[i] {
|
||||||
@@ -47,6 +55,10 @@ impl MockFileSystem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl FileSystem for MockFileSystem {
|
impl FileSystem for MockFileSystem {
|
||||||
|
fn writable(&self) -> bool {
|
||||||
|
!self.read_only
|
||||||
|
}
|
||||||
|
|
||||||
fn open(&mut self, path: &[u8]) -> Option<FileHandle> {
|
fn open(&mut self, path: &[u8]) -> Option<FileHandle> {
|
||||||
let idx = self.find_by_name(path)?;
|
let idx = self.find_by_name(path)?;
|
||||||
if let Some(entry) = &mut self.files[idx] {
|
if let Some(entry) = &mut self.files[idx] {
|
||||||
|
|||||||
@@ -74,4 +74,9 @@ pub trait FileSystem {
|
|||||||
fn delete(&mut self, path: &[u8]) -> bool;
|
fn delete(&mut self, path: &[u8]) -> bool;
|
||||||
fn file_size(&mut self, handle: FileHandle) -> Option<usize>;
|
fn file_size(&mut self, handle: FileHandle) -> Option<usize>;
|
||||||
fn mkdir(&mut self, path: &[u8]) -> bool;
|
fn mkdir(&mut self, path: &[u8]) -> bool;
|
||||||
|
/// Whether anything can be written at all; the server refuses
|
||||||
|
/// writing requests up front when not.
|
||||||
|
fn writable(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -358,13 +358,17 @@ impl<B: BlockDevice> FileSystem for Fat16<B> {
|
|||||||
break;
|
break;
|
||||||
};
|
};
|
||||||
let sector = base + sector_in_cluster as u32;
|
let sector = base + sector_in_cluster as u32;
|
||||||
self.device
|
if !self.device.read_sector(SectorId::new(sector), &mut sector_buf) {
|
||||||
.read_sector(SectorId::new(sector), &mut sector_buf);
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
let chunk = (SECTOR_SIZE - byte_in_sector).min(buf.len() - written);
|
let chunk = (SECTOR_SIZE - byte_in_sector).min(buf.len() - written);
|
||||||
sector_buf[byte_in_sector..byte_in_sector + chunk]
|
sector_buf[byte_in_sector..byte_in_sector + chunk]
|
||||||
.copy_from_slice(&buf[written..written + chunk]);
|
.copy_from_slice(&buf[written..written + chunk]);
|
||||||
self.device.write_sector(SectorId::new(sector), §or_buf);
|
// Bytes count as written only once they reached the disk.
|
||||||
|
if !self.device.write_sector(SectorId::new(sector), §or_buf) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
written += chunk;
|
written += chunk;
|
||||||
pos += chunk;
|
pos += chunk;
|
||||||
@@ -479,6 +483,10 @@ impl<B: BlockDevice> FileSystem for Fat16<B> {
|
|||||||
fn mkdir(&mut self, _path: &[u8]) -> bool {
|
fn mkdir(&mut self, _path: &[u8]) -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn writable(&self) -> bool {
|
||||||
|
self.device.writable()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_fat_name(name: &[u8]) -> [u8; 11] {
|
fn to_fat_name(name: &[u8]) -> [u8; 11] {
|
||||||
|
|||||||
@@ -13,19 +13,33 @@ pub mod server;
|
|||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
use libkos::syscall;
|
use libkos::syscall;
|
||||||
|
use types::address::VirtualAddress;
|
||||||
|
use types::bounded::{CapSlot, PageCount};
|
||||||
|
use types::mapping::PagePerms;
|
||||||
|
|
||||||
const CMD_READ_FILE: usize = 1;
|
/// Client buffers sit high in storage's window, clear of its heap.
|
||||||
const SHARED_BUF_VA: usize = 0x30000;
|
const BUFFERS_VA: usize = 0x0100_0000;
|
||||||
const SHARED_BUF_SIZE: usize = 64 * 1024;
|
|
||||||
|
|
||||||
fn shared_buf() -> &'static mut [u8] {
|
/// Each client's buffer is a fresh region, mapped here and handed to the
|
||||||
unsafe { core::slice::from_raw_parts_mut(SHARED_BUF_VA as *mut u8, SHARED_BUF_SIZE) }
|
/// client, which maps the same pages in its own space.
|
||||||
|
struct RegionBuffers;
|
||||||
|
|
||||||
|
impl server::Buffers for RegionBuffers {
|
||||||
|
fn create(&mut self, client: usize) -> Option<(&'static mut [u8], Option<CapSlot>)> {
|
||||||
|
let va = BUFFERS_VA + client * protocol::BUFFER_SIZE;
|
||||||
|
let region = syscall::create_region(PageCount::new(protocol::BUFFER_PAGES as u16)?).ok()?;
|
||||||
|
syscall::map_region(region, VirtualAddress::from_raw(va), PagePerms::ReadWrite).ok()?;
|
||||||
|
// SAFETY: the region is mapped read-write at `va` for as long as
|
||||||
|
// the server holds `region`, which it never drops.
|
||||||
|
let buf = unsafe { core::slice::from_raw_parts_mut(va as *mut u8, protocol::BUFFER_SIZE) };
|
||||||
|
Some((buf, Some(region)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run() -> ! {
|
pub fn run() -> ! {
|
||||||
libkos::println!("storage: started");
|
libkos::println!("storage: started");
|
||||||
|
|
||||||
let ep = types::bounded::CapSlot::new(0).unwrap();
|
let ep = CapSlot::new(0).unwrap();
|
||||||
// Init hands over the disk before asking for anything.
|
// Init hands over the disk before asking for anything.
|
||||||
let Some(device) = libkos::device::receive_device(ep)
|
let Some(device) = libkos::device::receive_device(ep)
|
||||||
.mmio
|
.mmio
|
||||||
@@ -34,72 +48,11 @@ pub fn run() -> ! {
|
|||||||
libkos::println!("storage: no block device, exiting");
|
libkos::println!("storage: no block device, exiting");
|
||||||
syscall::exit();
|
syscall::exit();
|
||||||
};
|
};
|
||||||
let fat16 = fs::fat16::Fat16::mount(device);
|
let Some(fat16) = fs::fat16::Fat16::mount(device) else {
|
||||||
if fat16.is_none() {
|
|
||||||
libkos::println!("storage: FAT16 mount failed, exiting");
|
libkos::println!("storage: FAT16 mount failed, exiting");
|
||||||
syscall::exit();
|
syscall::exit();
|
||||||
}
|
|
||||||
let mut fat16 = fat16.unwrap();
|
|
||||||
libkos::println!("storage: FAT16 mounted");
|
|
||||||
|
|
||||||
|
|
||||||
let mut next = syscall::receive_msg(ep);
|
|
||||||
loop {
|
|
||||||
let message = match next {
|
|
||||||
Ok(m) => m,
|
|
||||||
Err(e) => {
|
|
||||||
libkos::println!("storage: endpoint unusable ({:?}), exiting", e);
|
|
||||||
syscall::exit();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let [w0, w1, _, _] = message.words();
|
|
||||||
let reply = match w0 {
|
|
||||||
CMD_READ_FILE => {
|
|
||||||
let filename_len = w1;
|
|
||||||
let buf = shared_buf();
|
|
||||||
let filename_len = filename_len.min(buf.len());
|
|
||||||
|
|
||||||
let mut name_buf = [0u8; 32];
|
|
||||||
let name_len = filename_len.min(32);
|
|
||||||
name_buf[..name_len].copy_from_slice(&buf[..name_len]);
|
|
||||||
|
|
||||||
let name = core::str::from_utf8(&name_buf[..name_len]).unwrap_or("?");
|
|
||||||
libkos::print!("storage: read {}...", name);
|
|
||||||
|
|
||||||
let file_size = read_entire_file(&mut fat16, &name_buf[..name_len], buf);
|
|
||||||
|
|
||||||
if file_size > 0 {
|
|
||||||
libkos::println!("ok");
|
|
||||||
} else {
|
|
||||||
libkos::println!("NOT FOUND");
|
|
||||||
}
|
|
||||||
|
|
||||||
[file_size, 0, 0, 0]
|
|
||||||
}
|
|
||||||
_ => [0, 0, 0, 0],
|
|
||||||
};
|
|
||||||
next = match message {
|
|
||||||
syscall::Received::Call { .. } => syscall::reply_recv(ep, reply, None),
|
|
||||||
_ => syscall::receive_msg(ep),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_entire_file<B: port_block_device::BlockDevice>(
|
|
||||||
fat: &mut fs::fat16::Fat16<B>,
|
|
||||||
filename: &[u8],
|
|
||||||
out: &mut [u8],
|
|
||||||
) -> usize {
|
|
||||||
use filesystem::FileSystem;
|
|
||||||
|
|
||||||
let handle = match fat.open(filename) {
|
|
||||||
Some(h) => h,
|
|
||||||
None => return 0,
|
|
||||||
};
|
};
|
||||||
|
libkos::println!("storage: FAT16 mounted");
|
||||||
let size = fat.file_size(handle).unwrap_or(0);
|
let mut srv = server::StorageServer::new(fat16, RegionBuffers);
|
||||||
let to_read = size.min(out.len());
|
server_framework::serve(&mut srv, ep)
|
||||||
let read = fat.read(handle, &mut out[..to_read]).unwrap_or(0);
|
|
||||||
fat.close(handle);
|
|
||||||
read
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,126 +1,129 @@
|
|||||||
|
//! The storage protocol. A client first `Attach`es: the server makes it a
|
||||||
|
//! buffer region and replies with a capability to it, which the client
|
||||||
|
//! maps. Paths, file data and directory listings then travel through that
|
||||||
|
//! buffer; the message words carry only lengths and handles, and the
|
||||||
|
//! server clamps every length to the buffer. Clients are told apart by
|
||||||
|
//! their endpoint badge, and a handle only works for the client that
|
||||||
|
//! opened it.
|
||||||
|
|
||||||
use server_framework::{Decode, Encode};
|
use server_framework::{Decode, Encode};
|
||||||
use crate::filesystem::FileHandle;
|
use crate::filesystem::FileHandle;
|
||||||
|
|
||||||
|
pub use types::storage::{BUFFER_PAGES, BUFFER_SIZE, DIR_ENTRY_SIZE, DIR_NAME_SIZE, MAX_PATH};
|
||||||
|
use types::storage::{op, reply};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub struct PackedPath {
|
|
||||||
word0: usize,
|
|
||||||
word1: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PackedPath {
|
|
||||||
pub fn from_bytes(src: &[u8]) -> Self {
|
|
||||||
let mut w0 = [0u8; 8];
|
|
||||||
let mut w1 = [0u8; 8];
|
|
||||||
for (i, &b) in src.iter().enumerate() {
|
|
||||||
if i < 8 { w0[i] = b; }
|
|
||||||
else if i < 16 { w1[i - 8] = b; }
|
|
||||||
}
|
|
||||||
Self {
|
|
||||||
word0: usize::from_le_bytes(w0),
|
|
||||||
word1: usize::from_le_bytes(w1),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_words(word0: usize, word1: usize) -> Self {
|
|
||||||
Self { word0, word1 }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_bytes(&self) -> [u8; 16] {
|
|
||||||
let mut buf = [0u8; 16];
|
|
||||||
buf[..8].copy_from_slice(&self.word0.to_le_bytes());
|
|
||||||
buf[8..16].copy_from_slice(&self.word1.to_le_bytes());
|
|
||||||
buf
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn word0(&self) -> usize { self.word0 }
|
|
||||||
pub fn word1(&self) -> usize { self.word1 }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub enum StorageRequest {
|
pub enum StorageRequest {
|
||||||
Open { path: PackedPath },
|
Attach,
|
||||||
Read { handle: FileHandle, max_bytes: usize },
|
/// The path is the first `path_len` bytes of the buffer.
|
||||||
Write { handle: FileHandle, data_word: usize },
|
Open { path_len: usize },
|
||||||
|
/// Reads up to `len` bytes into the buffer.
|
||||||
|
Read { handle: FileHandle, len: usize },
|
||||||
|
/// Writes the first `len` bytes of the buffer.
|
||||||
|
Write { handle: FileHandle, len: usize },
|
||||||
Close { handle: FileHandle },
|
Close { handle: FileHandle },
|
||||||
ListDir { path: PackedPath },
|
|
||||||
Create { path: PackedPath },
|
|
||||||
Delete { path: PackedPath },
|
|
||||||
FileSize { handle: FileHandle },
|
FileSize { handle: FileHandle },
|
||||||
Mkdir { path: PackedPath },
|
/// Fills the buffer with `DIR_ENTRY_SIZE` records.
|
||||||
|
ListDir { path_len: usize },
|
||||||
|
Create { path_len: usize },
|
||||||
|
Delete { path_len: usize },
|
||||||
|
Mkdir { path_len: usize },
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum StorageResponse {
|
pub enum StorageResponse {
|
||||||
|
/// Carries the buffer capability; `size` bytes are usable.
|
||||||
|
Attached { size: usize },
|
||||||
Handle(FileHandle),
|
Handle(FileHandle),
|
||||||
Data { bytes_read: usize },
|
Data { bytes: usize },
|
||||||
Written { bytes_written: usize },
|
Written { bytes: usize },
|
||||||
Ok,
|
Ok,
|
||||||
DirEntries { count: usize },
|
DirEntries { count: usize },
|
||||||
Size(usize),
|
Size(usize),
|
||||||
NotFound,
|
NotFound,
|
||||||
Error,
|
Error,
|
||||||
|
/// The disk cannot be written.
|
||||||
|
ReadOnly,
|
||||||
|
/// The client has not attached a buffer yet.
|
||||||
|
NotAttached,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle(word: usize) -> Option<FileHandle> {
|
||||||
|
u16::try_from(word).ok().map(FileHandle::new)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Encode for StorageRequest {
|
impl Encode for StorageRequest {
|
||||||
fn encode(&self) -> [usize; 4] {
|
fn encode(&self) -> [usize; 4] {
|
||||||
match self {
|
use StorageRequest::*;
|
||||||
StorageRequest::Open { path } => [0, path.word0(), path.word1(), 0],
|
match *self {
|
||||||
StorageRequest::Read { handle, max_bytes } => [1, handle.raw() as usize, *max_bytes, 0],
|
Attach => [op::ATTACH, 0, 0, 0],
|
||||||
StorageRequest::Write { handle, data_word } => [2, handle.raw() as usize, *data_word, 0],
|
Open { path_len } => [op::OPEN, path_len, 0, 0],
|
||||||
StorageRequest::Close { handle } => [3, handle.raw() as usize, 0, 0],
|
Read { handle, len } => [op::READ, handle.raw() as usize, len, 0],
|
||||||
StorageRequest::ListDir { path } => [4, path.word0(), path.word1(), 0],
|
Write { handle, len } => [op::WRITE, handle.raw() as usize, len, 0],
|
||||||
StorageRequest::Create { path } => [5, path.word0(), path.word1(), 0],
|
Close { handle } => [op::CLOSE, handle.raw() as usize, 0, 0],
|
||||||
StorageRequest::Delete { path } => [6, path.word0(), path.word1(), 0],
|
FileSize { handle } => [op::FILE_SIZE, handle.raw() as usize, 0, 0],
|
||||||
StorageRequest::FileSize { handle } => [7, handle.raw() as usize, 0, 0],
|
ListDir { path_len } => [op::LIST_DIR, path_len, 0, 0],
|
||||||
StorageRequest::Mkdir { path } => [8, path.word0(), path.word1(), 0],
|
Create { path_len } => [op::CREATE, path_len, 0, 0],
|
||||||
|
Delete { path_len } => [op::DELETE, path_len, 0, 0],
|
||||||
|
Mkdir { path_len } => [op::MKDIR, path_len, 0, 0],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Decode for StorageRequest {
|
impl Decode for StorageRequest {
|
||||||
fn decode(words: [usize; 4]) -> Option<Self> {
|
fn decode(w: [usize; 4]) -> Option<Self> {
|
||||||
match words[0] {
|
use StorageRequest::*;
|
||||||
0 => Some(StorageRequest::Open { path: PackedPath::from_words(words[1], words[2]) }),
|
Some(match w[0] {
|
||||||
1 => Some(StorageRequest::Read { handle: FileHandle::new(words[1] as u16), max_bytes: words[2] }),
|
op::ATTACH => Attach,
|
||||||
2 => Some(StorageRequest::Write { handle: FileHandle::new(words[1] as u16), data_word: words[2] }),
|
op::OPEN => Open { path_len: w[1] },
|
||||||
3 => Some(StorageRequest::Close { handle: FileHandle::new(words[1] as u16) }),
|
op::READ => Read { handle: handle(w[1])?, len: w[2] },
|
||||||
4 => Some(StorageRequest::ListDir { path: PackedPath::from_words(words[1], words[2]) }),
|
op::WRITE => Write { handle: handle(w[1])?, len: w[2] },
|
||||||
5 => Some(StorageRequest::Create { path: PackedPath::from_words(words[1], words[2]) }),
|
op::CLOSE => Close { handle: handle(w[1])? },
|
||||||
6 => Some(StorageRequest::Delete { path: PackedPath::from_words(words[1], words[2]) }),
|
op::FILE_SIZE => FileSize { handle: handle(w[1])? },
|
||||||
7 => Some(StorageRequest::FileSize { handle: FileHandle::new(words[1] as u16) }),
|
op::LIST_DIR => ListDir { path_len: w[1] },
|
||||||
8 => Some(StorageRequest::Mkdir { path: PackedPath::from_words(words[1], words[2]) }),
|
op::CREATE => Create { path_len: w[1] },
|
||||||
_ => None,
|
op::DELETE => Delete { path_len: w[1] },
|
||||||
}
|
op::MKDIR => Mkdir { path_len: w[1] },
|
||||||
|
_ => return None,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Encode for StorageResponse {
|
impl Encode for StorageResponse {
|
||||||
fn encode(&self) -> [usize; 4] {
|
fn encode(&self) -> [usize; 4] {
|
||||||
match self {
|
use StorageResponse::*;
|
||||||
StorageResponse::Handle(h) => [0, h.raw() as usize, 0, 0],
|
match *self {
|
||||||
StorageResponse::Data { bytes_read } => [1, *bytes_read, 0, 0],
|
Attached { size } => [reply::ATTACHED, size, 0, 0],
|
||||||
StorageResponse::Written { bytes_written } => [2, *bytes_written, 0, 0],
|
Handle(h) => [reply::HANDLE, h.raw() as usize, 0, 0],
|
||||||
StorageResponse::Ok => [3, 0, 0, 0],
|
Data { bytes } => [reply::DATA, bytes, 0, 0],
|
||||||
StorageResponse::DirEntries { count } => [4, *count, 0, 0],
|
Written { bytes } => [reply::WRITTEN, bytes, 0, 0],
|
||||||
StorageResponse::Size(s) => [5, *s, 0, 0],
|
Ok => [reply::OK, 0, 0, 0],
|
||||||
StorageResponse::NotFound => [6, 0, 0, 0],
|
DirEntries { count } => [reply::DIR_ENTRIES, count, 0, 0],
|
||||||
StorageResponse::Error => [7, 0, 0, 0],
|
Size(s) => [reply::SIZE, s, 0, 0],
|
||||||
|
NotFound => [reply::NOT_FOUND, 0, 0, 0],
|
||||||
|
Error => [reply::ERROR, 0, 0, 0],
|
||||||
|
ReadOnly => [reply::READ_ONLY, 0, 0, 0],
|
||||||
|
NotAttached => [reply::NOT_ATTACHED, 0, 0, 0],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Decode for StorageResponse {
|
impl Decode for StorageResponse {
|
||||||
fn decode(words: [usize; 4]) -> Option<Self> {
|
fn decode(w: [usize; 4]) -> Option<Self> {
|
||||||
match words[0] {
|
use StorageResponse::*;
|
||||||
0 => Some(StorageResponse::Handle(FileHandle::new(words[1] as u16))),
|
Some(match w[0] {
|
||||||
1 => Some(StorageResponse::Data { bytes_read: words[1] }),
|
reply::ATTACHED => Attached { size: w[1] },
|
||||||
2 => Some(StorageResponse::Written { bytes_written: words[1] }),
|
reply::HANDLE => Handle(handle(w[1])?),
|
||||||
3 => Some(StorageResponse::Ok),
|
reply::DATA => Data { bytes: w[1] },
|
||||||
4 => Some(StorageResponse::DirEntries { count: words[1] }),
|
reply::WRITTEN => Written { bytes: w[1] },
|
||||||
5 => Some(StorageResponse::Size(words[1])),
|
reply::OK => Ok,
|
||||||
6 => Some(StorageResponse::NotFound),
|
reply::DIR_ENTRIES => DirEntries { count: w[1] },
|
||||||
7 => Some(StorageResponse::Error),
|
reply::SIZE => Size(w[1]),
|
||||||
_ => None,
|
reply::NOT_FOUND => NotFound,
|
||||||
}
|
reply::ERROR => Error,
|
||||||
|
reply::READ_ONLY => ReadOnly,
|
||||||
|
reply::NOT_ATTACHED => NotAttached,
|
||||||
|
_ => return None,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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::filesystem::{DirEntry, FileHandle, FileSystem};
|
||||||
use crate::protocol::{StorageRequest, StorageResponse};
|
use crate::protocol::{
|
||||||
|
DIR_ENTRY_SIZE, DIR_NAME_SIZE, MAX_PATH, StorageRequest, StorageResponse,
|
||||||
|
};
|
||||||
|
|
||||||
pub struct StorageServer<F: FileSystem> {
|
const MAX_CLIENTS: usize = 8;
|
||||||
fs: F,
|
/// 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> {
|
struct Client {
|
||||||
pub fn new(fs: F) -> Self {
|
badge: usize,
|
||||||
Self { fs }
|
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 {
|
pub fn filesystem(&self) -> &F {
|
||||||
&self.fs
|
&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 Request = StorageRequest;
|
||||||
type Response = StorageResponse;
|
type Response = StorageResponse;
|
||||||
|
|
||||||
|
/// Without a badge: as the unbadged client.
|
||||||
fn handle(&mut self, request: StorageRequest) -> StorageResponse {
|
fn handle(&mut self, request: StorageRequest) -> StorageResponse {
|
||||||
match request {
|
self.serve(0, request).0
|
||||||
StorageRequest::Open { path } => {
|
}
|
||||||
let buf = path.to_bytes();
|
|
||||||
let len = buf.iter().position(|&b| b == 0).unwrap_or(16);
|
fn handle_call(
|
||||||
match self.fs.open(&buf[..len]) {
|
&mut self,
|
||||||
Some(h) => StorageResponse::Handle(h),
|
badge: usize,
|
||||||
None => StorageResponse::NotFound,
|
words: [usize; 4],
|
||||||
}
|
_cap: Option<CapSlot>,
|
||||||
}
|
) -> ([usize; 4], Option<CapSlot>) {
|
||||||
StorageRequest::Read { handle, max_bytes } => {
|
match StorageRequest::decode(words) {
|
||||||
let mut buf = [0u8; 512];
|
Some(request) => {
|
||||||
let read_len = max_bytes.min(buf.len());
|
let (response, cap) = self.serve(badge, request);
|
||||||
match self.fs.read(handle, &mut buf[..read_len]) {
|
(response.encode(), cap)
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
None => ([MALFORMED, 0, 0, 0], None),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,78 +3,58 @@ use crate::protocol::*;
|
|||||||
use server_framework::{Decode, Encode};
|
use server_framework::{Decode, Encode};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn open_roundtrip() {
|
fn every_request_roundtrips() {
|
||||||
let path = PackedPath::from_bytes(b"test.txt");
|
let h = FileHandle::new(3);
|
||||||
let req = StorageRequest::Open { path };
|
for req in [
|
||||||
assert_eq!(StorageRequest::decode(req.encode()), Some(req));
|
StorageRequest::Attach,
|
||||||
|
StorageRequest::Open { path_len: 9 },
|
||||||
|
StorageRequest::Read { handle: h, len: 100 },
|
||||||
|
StorageRequest::Write { handle: h, len: 7 },
|
||||||
|
StorageRequest::Close { handle: h },
|
||||||
|
StorageRequest::FileSize { handle: h },
|
||||||
|
StorageRequest::ListDir { path_len: 1 },
|
||||||
|
StorageRequest::Create { path_len: 5 },
|
||||||
|
StorageRequest::Delete { path_len: 5 },
|
||||||
|
StorageRequest::Mkdir { path_len: 4 },
|
||||||
|
] {
|
||||||
|
assert_eq!(StorageRequest::decode(req.encode()), Some(req));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn read_roundtrip() {
|
fn every_response_roundtrips() {
|
||||||
let req = StorageRequest::Read { handle: FileHandle::new(3), max_bytes: 512 };
|
for resp in [
|
||||||
assert_eq!(StorageRequest::decode(req.encode()), Some(req));
|
StorageResponse::Attached { size: BUFFER_SIZE },
|
||||||
}
|
StorageResponse::Handle(FileHandle::new(2)),
|
||||||
|
StorageResponse::Data { bytes: 512 },
|
||||||
#[test]
|
StorageResponse::Written { bytes: 8 },
|
||||||
fn write_roundtrip() {
|
|
||||||
let req = StorageRequest::Write { handle: FileHandle::new(1), data_word: 42 };
|
|
||||||
assert_eq!(StorageRequest::decode(req.encode()), Some(req));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn close_roundtrip() {
|
|
||||||
let req = StorageRequest::Close { handle: FileHandle::new(5) };
|
|
||||||
assert_eq!(StorageRequest::decode(req.encode()), Some(req));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn file_size_roundtrip() {
|
|
||||||
let req = StorageRequest::FileSize { handle: FileHandle::new(2) };
|
|
||||||
assert_eq!(StorageRequest::decode(req.encode()), Some(req));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn handle_response_roundtrip() {
|
|
||||||
let resp = StorageResponse::Handle(FileHandle::new(7));
|
|
||||||
assert_eq!(StorageResponse::decode(resp.encode()), Some(resp));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn data_response_roundtrip() {
|
|
||||||
let resp = StorageResponse::Data { bytes_read: 256 };
|
|
||||||
assert_eq!(StorageResponse::decode(resp.encode()), Some(resp));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn all_response_variants() {
|
|
||||||
let variants = [
|
|
||||||
StorageResponse::Ok,
|
StorageResponse::Ok,
|
||||||
|
StorageResponse::DirEntries { count: 3 },
|
||||||
|
StorageResponse::Size(4096),
|
||||||
StorageResponse::NotFound,
|
StorageResponse::NotFound,
|
||||||
StorageResponse::Error,
|
StorageResponse::Error,
|
||||||
StorageResponse::Size(1024),
|
StorageResponse::ReadOnly,
|
||||||
StorageResponse::DirEntries { count: 3 },
|
StorageResponse::NotAttached,
|
||||||
];
|
] {
|
||||||
for resp in variants {
|
|
||||||
assert_eq!(StorageResponse::decode(resp.encode()), Some(resp));
|
assert_eq!(StorageResponse::decode(resp.encode()), Some(resp));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn mkdir_roundtrip() {
|
fn handles_that_do_not_fit_are_rejected_not_truncated() {
|
||||||
let path = PackedPath::from_bytes(b"mydir");
|
assert_eq!(StorageRequest::decode([2, 0x1_0003, 10, 0]), None);
|
||||||
let req = StorageRequest::Mkdir { path };
|
assert_eq!(StorageRequest::decode([4, usize::MAX, 0, 0]), None);
|
||||||
assert_eq!(StorageRequest::decode(req.encode()), Some(req));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn invalid_request_returns_none() {
|
fn unknown_opcodes_do_not_decode() {
|
||||||
assert!(StorageRequest::decode([99, 0, 0, 0]).is_none());
|
assert_eq!(StorageRequest::decode([99, 0, 0, 0]), None);
|
||||||
|
assert_eq!(StorageRequest::decode([usize::MAX, 0, 0, 0]), None);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn packed_path_roundtrip() {
|
fn wire_numbers_are_the_shared_ones() {
|
||||||
let path = PackedPath::from_bytes(b"hello");
|
use types::storage::{op, reply};
|
||||||
let buf = path.to_bytes();
|
assert_eq!(StorageRequest::Read { handle: FileHandle::new(0), len: 0 }.encode()[0], op::READ);
|
||||||
assert_eq!(&buf[..5], b"hello");
|
assert_eq!(StorageResponse::ReadOnly.encode()[0], reply::READ_ONLY);
|
||||||
assert_eq!(buf[5], 0);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,87 +1,182 @@
|
|||||||
|
extern crate std;
|
||||||
|
|
||||||
use crate::adapters::mock_fs::MockFileSystem;
|
use crate::adapters::mock_fs::MockFileSystem;
|
||||||
|
use crate::filesystem::FileHandle;
|
||||||
use crate::protocol::*;
|
use crate::protocol::*;
|
||||||
use crate::server::StorageServer;
|
use crate::server::{Buffers, StorageServer};
|
||||||
use server_framework::Server;
|
use server_framework::{Decode, Encode, MALFORMED, Server};
|
||||||
|
use std::boxed::Box;
|
||||||
|
use std::vec;
|
||||||
|
use types::bounded::CapSlot;
|
||||||
|
|
||||||
fn test_server() -> StorageServer<MockFileSystem> {
|
/// Plain memory instead of regions; the "capability" is a fixed slot.
|
||||||
StorageServer::new(MockFileSystem::new())
|
struct TestBuffers;
|
||||||
|
|
||||||
|
impl Buffers for TestBuffers {
|
||||||
|
fn create(&mut self, _client: usize) -> Option<(&'static mut [u8], Option<CapSlot>)> {
|
||||||
|
Some((Box::leak(vec![0u8; 4096].into_boxed_slice()), CapSlot::new(9)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn path(s: &[u8]) -> PackedPath {
|
type Srv = StorageServer<MockFileSystem, TestBuffers>;
|
||||||
PackedPath::from_bytes(s)
|
|
||||||
|
fn server() -> Srv {
|
||||||
|
StorageServer::new(MockFileSystem::new(), TestBuffers)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// One request from `badge`, returning the decoded reply and any cap.
|
||||||
|
fn call(s: &mut Srv, badge: usize, req: StorageRequest) -> (StorageResponse, Option<CapSlot>) {
|
||||||
|
let (words, cap) = s.handle_call(badge, req.encode(), None);
|
||||||
|
(StorageResponse::decode(words).expect("valid reply"), cap)
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Client {
|
||||||
|
badge: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Client {
|
||||||
|
fn attach(s: &mut Srv, badge: usize) -> Self {
|
||||||
|
let (resp, cap) = call(s, badge, StorageRequest::Attach);
|
||||||
|
assert_eq!(resp, StorageResponse::Attached { size: 4096 });
|
||||||
|
assert_eq!(cap, CapSlot::new(9), "the reply carries the buffer");
|
||||||
|
Self { badge }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn put(&self, s: &mut Srv, bytes: &[u8]) {
|
||||||
|
s.client_buffer_for_test(self.badge).unwrap()[..bytes.len()].copy_from_slice(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get(&self, s: &mut Srv, len: usize) -> std::vec::Vec<u8> {
|
||||||
|
s.client_buffer_for_test(self.badge).unwrap()[..len].to_vec()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create(&self, s: &mut Srv, path: &[u8]) -> FileHandle {
|
||||||
|
self.put(s, path);
|
||||||
|
match call(s, self.badge, StorageRequest::Create { path_len: path.len() }).0 {
|
||||||
|
StorageResponse::Handle(h) => h,
|
||||||
|
other => panic!("create: {other:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn create_and_open() {
|
fn nothing_works_before_attaching() {
|
||||||
let mut server = test_server();
|
let mut s = server();
|
||||||
|
let (resp, _) = call(&mut s, 0, StorageRequest::Open { path_len: 3 });
|
||||||
let resp = server.handle(StorageRequest::Create { path: path(b"test.txt") });
|
assert_eq!(resp, StorageResponse::NotAttached);
|
||||||
assert!(matches!(resp, StorageResponse::Handle(_)));
|
|
||||||
|
|
||||||
let resp = server.handle(StorageRequest::Open { path: path(b"test.txt") });
|
|
||||||
assert!(matches!(resp, StorageResponse::Handle(_)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn open_nonexistent_returns_not_found() {
|
fn a_client_attaches_once() {
|
||||||
let mut server = test_server();
|
let mut s = server();
|
||||||
let resp = server.handle(StorageRequest::Open { path: path(b"nope.txt") });
|
Client::attach(&mut s, 0);
|
||||||
assert_eq!(resp, StorageResponse::NotFound);
|
assert_eq!(call(&mut s, 0, StorageRequest::Attach).0, StorageResponse::Error);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn write_then_read() {
|
fn data_goes_through_the_buffer_both_ways() {
|
||||||
let mut server = test_server();
|
let mut s = server();
|
||||||
|
let c = Client::attach(&mut s, 0);
|
||||||
|
let h = c.create(&mut s, b"note.txt");
|
||||||
|
c.put(&mut s, b"hello, storage");
|
||||||
|
assert_eq!(
|
||||||
|
call(&mut s, 0, StorageRequest::Write { handle: h, len: 14 }).0,
|
||||||
|
StorageResponse::Written { bytes: 14 }
|
||||||
|
);
|
||||||
|
assert_eq!(call(&mut s, 0, StorageRequest::Close { handle: h }).0, StorageResponse::Ok);
|
||||||
|
|
||||||
let handle = match server.handle(StorageRequest::Create { path: path(b"data") }) {
|
c.put(&mut s, b"note.txt");
|
||||||
StorageResponse::Handle(h) => h,
|
let StorageResponse::Handle(h) = call(&mut s, 0, StorageRequest::Open { path_len: 8 }).0 else {
|
||||||
_ => panic!("expected handle"),
|
panic!("open");
|
||||||
};
|
};
|
||||||
|
assert_eq!(call(&mut s, 0, StorageRequest::FileSize { handle: h }).0, StorageResponse::Size(14));
|
||||||
let resp = server.handle(StorageRequest::Write { handle, data_word: 0xDEADBEEF });
|
c.put(&mut s, &[0; 32]);
|
||||||
assert!(matches!(resp, StorageResponse::Written { bytes_written: 8 }));
|
assert_eq!(
|
||||||
|
call(&mut s, 0, StorageRequest::Read { handle: h, len: 64 }).0,
|
||||||
let resp = server.handle(StorageRequest::FileSize { handle });
|
StorageResponse::Data { bytes: 14 }
|
||||||
assert_eq!(resp, StorageResponse::Size(8));
|
);
|
||||||
|
assert_eq!(c.get(&mut s, 14), b"hello, storage");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn delete_file() {
|
fn a_handle_works_only_for_the_client_that_opened_it() {
|
||||||
let mut server = test_server();
|
let mut s = server();
|
||||||
|
let a = Client::attach(&mut s, 1);
|
||||||
server.handle(StorageRequest::Create { path: path(b"del.txt") });
|
Client::attach(&mut s, 2);
|
||||||
let resp = server.handle(StorageRequest::Delete { path: path(b"del.txt") });
|
let h = a.create(&mut s, b"mine");
|
||||||
assert_eq!(resp, StorageResponse::Ok);
|
for req in [
|
||||||
|
StorageRequest::Read { handle: h, len: 4 },
|
||||||
let resp = server.handle(StorageRequest::Open { path: path(b"del.txt") });
|
StorageRequest::Write { handle: h, len: 4 },
|
||||||
assert_eq!(resp, StorageResponse::NotFound);
|
StorageRequest::FileSize { handle: h },
|
||||||
|
StorageRequest::Close { handle: h },
|
||||||
|
] {
|
||||||
|
assert_eq!(call(&mut s, 2, req).0, StorageResponse::Error, "{req:?}");
|
||||||
|
}
|
||||||
|
assert_eq!(call(&mut s, 1, StorageRequest::FileSize { handle: h }).0, StorageResponse::Size(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn list_dir() {
|
fn hostile_lengths_are_clamped_to_the_buffer() {
|
||||||
let mut server = test_server();
|
let mut s = server();
|
||||||
|
let c = Client::attach(&mut s, 0);
|
||||||
server.handle(StorageRequest::Create { path: path(b"a.txt") });
|
let h = c.create(&mut s, b"x");
|
||||||
server.handle(StorageRequest::Create { path: path(b"b.txt") });
|
for len in [4097, usize::MAX] {
|
||||||
|
assert!(matches!(call(&mut s, 0, StorageRequest::Write { handle: h, len }).0, StorageResponse::Written { .. }));
|
||||||
let resp = server.handle(StorageRequest::ListDir { path: path(b"/") });
|
assert!(matches!(call(&mut s, 0, StorageRequest::Read { handle: h, len }).0, StorageResponse::Data { .. }));
|
||||||
assert_eq!(resp, StorageResponse::DirEntries { count: 2 });
|
assert_eq!(call(&mut s, 0, StorageRequest::Open { path_len: len }).0, StorageResponse::NotFound);
|
||||||
|
assert!(matches!(call(&mut s, 0, StorageRequest::ListDir { path_len: len }).0, StorageResponse::DirEntries { .. }));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn mkdir_returns_ok() {
|
fn handles_never_opened_are_refused() {
|
||||||
let mut server = test_server();
|
let mut s = server();
|
||||||
let resp = server.handle(StorageRequest::Mkdir { path: path(b"subdir") });
|
Client::attach(&mut s, 0);
|
||||||
assert_eq!(resp, StorageResponse::Ok);
|
for raw in [0, 15, 16, u16::MAX] {
|
||||||
|
let h = FileHandle::new(raw);
|
||||||
|
assert_eq!(call(&mut s, 0, StorageRequest::Read { handle: h, len: 1 }).0, StorageResponse::Error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn close_succeeds() {
|
fn a_read_only_disk_refuses_every_write_up_front() {
|
||||||
let mut server = test_server();
|
let mut s = StorageServer::new(MockFileSystem::new().read_only(), TestBuffers);
|
||||||
let handle = match server.handle(StorageRequest::Create { path: path(b"c.txt") }) {
|
let c = Client::attach(&mut s, 0);
|
||||||
StorageResponse::Handle(h) => h,
|
c.put(&mut s, b"f");
|
||||||
_ => panic!("expected handle"),
|
for req in [
|
||||||
};
|
StorageRequest::Create { path_len: 1 },
|
||||||
let resp = server.handle(StorageRequest::Close { handle });
|
StorageRequest::Delete { path_len: 1 },
|
||||||
assert_eq!(resp, StorageResponse::Ok);
|
StorageRequest::Mkdir { path_len: 1 },
|
||||||
|
StorageRequest::Write { handle: FileHandle::new(0), len: 1 },
|
||||||
|
] {
|
||||||
|
let expect = match req {
|
||||||
|
// A handle nobody opened is refused before the disk is asked.
|
||||||
|
StorageRequest::Write { .. } => StorageResponse::Error,
|
||||||
|
_ => StorageResponse::ReadOnly,
|
||||||
|
};
|
||||||
|
assert_eq!(call(&mut s, 0, req).0, expect, "{req:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn listings_are_records_in_the_buffer() {
|
||||||
|
let mut s = server();
|
||||||
|
let c = Client::attach(&mut s, 0);
|
||||||
|
for name in [b"a.txt".as_slice(), b"bee.bin"] {
|
||||||
|
let h = c.create(&mut s, name);
|
||||||
|
call(&mut s, 0, StorageRequest::Close { handle: h });
|
||||||
|
}
|
||||||
|
c.put(&mut s, b"/");
|
||||||
|
assert_eq!(call(&mut s, 0, StorageRequest::ListDir { path_len: 1 }).0, StorageResponse::DirEntries { count: 2 });
|
||||||
|
let records = c.get(&mut s, 2 * DIR_ENTRY_SIZE);
|
||||||
|
assert_eq!(&records[..5], b"a.txt");
|
||||||
|
assert_eq!(records[5], 0);
|
||||||
|
assert_eq!(&records[DIR_ENTRY_SIZE..DIR_ENTRY_SIZE + 7], b"bee.bin");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn garbage_gets_a_malformed_reply() {
|
||||||
|
let mut s = server();
|
||||||
|
let (words, cap) = s.handle_call(0, [99, 0, 0, 0], None);
|
||||||
|
assert_eq!((words[0], cap), (MALFORMED, None));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,5 +12,6 @@ pub mod mapping;
|
|||||||
pub mod permission;
|
pub mod permission;
|
||||||
#[cfg(kani)]
|
#[cfg(kani)]
|
||||||
pub mod proofs;
|
pub mod proofs;
|
||||||
|
pub mod storage;
|
||||||
pub mod syscall_abi;
|
pub mod syscall_abi;
|
||||||
pub mod time;
|
pub mod time;
|
||||||
|
|||||||
43
crates/types/src/storage.rs
Normal file
43
crates/types/src/storage.rs
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
//! The storage protocol's wire numbers, shared by the server
|
||||||
|
//! (`storage_server::protocol`) and the client (`libkos::fs`) so the two
|
||||||
|
//! cannot drift apart.
|
||||||
|
|
||||||
|
/// Size of each client's buffer.
|
||||||
|
pub const BUFFER_PAGES: usize = 16;
|
||||||
|
pub const BUFFER_SIZE: usize = BUFFER_PAGES * 4096;
|
||||||
|
/// Longest path the server will look at.
|
||||||
|
pub const MAX_PATH: usize = 255;
|
||||||
|
/// One `ListDir` record: the name (NUL padded), then the size and flags
|
||||||
|
/// as little-endian u64s. Flag bit 0: a directory.
|
||||||
|
pub const DIR_ENTRY_SIZE: usize = 48;
|
||||||
|
pub const DIR_NAME_SIZE: usize = 32;
|
||||||
|
pub const DIR_FLAG_DIRECTORY: u64 = 1;
|
||||||
|
|
||||||
|
/// Request opcodes (word 0).
|
||||||
|
pub mod op {
|
||||||
|
pub const ATTACH: usize = 0;
|
||||||
|
pub const OPEN: usize = 1;
|
||||||
|
pub const READ: usize = 2;
|
||||||
|
pub const WRITE: usize = 3;
|
||||||
|
pub const CLOSE: usize = 4;
|
||||||
|
pub const FILE_SIZE: usize = 5;
|
||||||
|
pub const LIST_DIR: usize = 6;
|
||||||
|
pub const CREATE: usize = 7;
|
||||||
|
pub const DELETE: usize = 8;
|
||||||
|
pub const MKDIR: usize = 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reply tags (word 0).
|
||||||
|
pub mod reply {
|
||||||
|
pub const ATTACHED: usize = 0;
|
||||||
|
pub const HANDLE: usize = 1;
|
||||||
|
pub const DATA: usize = 2;
|
||||||
|
pub const WRITTEN: usize = 3;
|
||||||
|
pub const OK: usize = 4;
|
||||||
|
pub const DIR_ENTRIES: usize = 5;
|
||||||
|
pub const SIZE: usize = 6;
|
||||||
|
pub const NOT_FOUND: usize = 7;
|
||||||
|
pub const ERROR: usize = 8;
|
||||||
|
pub const READ_ONLY: usize = 9;
|
||||||
|
pub const NOT_ATTACHED: usize = 10;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user