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

@@ -7,26 +7,33 @@ mod devices;
use types::address::{EntryPoint, StackPointer, VirtualAddress};
use types::bounded::{CapSlot, PageCount};
use types::permission::{Permission, PermissionSet};
use libkos::fs::Storage;
use libkos::syscall;
const PAGE_SIZE: usize = 4096;
const USER_CODE_BASE: usize = 0x10000;
const USER_STACK_TOP: StackPointer = StackPointer::from_raw(USER_CODE_BASE);
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 STORAGE_EP: CapSlot = CapSlot::const_new(2);
const DISPLAY_EP: CapSlot = CapSlot::const_new(3);
const INPUT_EP: CapSlot = CapSlot::const_new(5);
fn request_file(shared_buf: *mut u8, filename: &[u8]) -> usize {
unsafe {
for (i, &b) in filename.iter().enumerate() {
core::ptr::write_volatile(shared_buf.add(i), b);
}
}
syscall::request(STORAGE_EP, [CMD_READ_FILE, filename.len(), 0, 0]).map_or(0, |r| r[0])
/// Reads all of `filename` into `scratch`; its size, or `None` if it is
/// missing, unreadable or larger than `scratch`.
fn read_file(storage: &mut Storage, scratch: &mut [u8], filename: &[u8]) -> Option<usize> {
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());
let _ = storage.close(handle);
read.filter(|r| Some(*r) == size)
}
struct LoadedProcess {
@@ -34,13 +41,9 @@ struct LoadedProcess {
entry_point: EntryPoint,
}
fn load_binary(shared_buf: *mut u8, filename: &[u8]) -> Option<LoadedProcess> {
let file_size = request_file(shared_buf, filename);
if file_size == 0 {
return None;
}
let elf_data = unsafe { core::slice::from_raw_parts(shared_buf as *const u8, file_size) };
fn load_binary(storage: &mut Storage, scratch: &mut [u8], filename: &[u8]) -> Option<LoadedProcess> {
let file_size = read_file(storage, scratch, filename)?;
let elf_data = &scratch[..file_size];
let elf_info = match elf::parse(elf_data) {
Ok(info) => info,
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);
if copy_len > 0 {
let src = segment.file_offset;
if src + copy_len <= file_size {
unsafe {
core::ptr::copy_nonoverlapping(
shared_buf.add(src),
local_ptr.add(offset_in_page),
copy_len,
);
}
let src = segment
.file_offset
.checked_add(copy_len)
.and_then(|end| elf_data.get(segment.file_offset..end));
if let Some(src) = src.filter(|_| copy_len > 0) {
// SAFETY: the pages just requested hold seg_pages * PAGE_SIZE
// bytes, which covers offset_in_page + 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" },
];
pub fn run(shared_buf_va: usize) -> ! {
let shared_buf = shared_buf_va as *mut u8;
pub fn run() -> ! {
libkos::println!("init: started (pid={:#x})", syscall::get_process_id().raw());
// 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" });
}
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()
.with(Permission::Read)
.with(Permission::Execute);
@@ -131,7 +140,7 @@ pub fn run(shared_buf_va: usize) -> ! {
for server in SERVERS {
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,
None => {
libkos::println!("SKIP");
@@ -148,7 +157,7 @@ pub fn run(shared_buf_va: usize) -> ! {
}
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
// the server and take other clients' requests.
let client = PermissionSet::empty().with(Permission::Execute);
@@ -162,8 +171,25 @@ pub fn run(shared_buf_va: usize) -> ! {
libkos::println!("init: all loaded, supervising");
// Nothing to supervise yet: block on a notification nobody signals
// rather than spin a core on Yield.
idle();
}
/// 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();
loop {
match idle {