fix: storage tables grow (handles, open files, encrypted opens, grants: Slots/Vec, no 8/16 caps; release follows all narrowed grants); storage says FULL, init spawn STORAGE_BUSY (was NOT_FOUND), pm BUSY; cap slots per process 64 -> 255

This commit is contained in:
2026-10-28 16:17:36 +01:00
parent 3b71f29f4c
commit 1f2134fcec
19 changed files with 208 additions and 128 deletions

View File

@@ -131,8 +131,13 @@ impl Launcher<'_> {
let Some(index) = self.spawner.children.iter().position(Option::is_none) else { let Some(index) = self.spawner.children.iter().position(Option::is_none) else {
return refused(reply::ERROR); return refused(reply::ERROR);
}; };
let Ok(mut file) = libkos::fs::Storage::attach(binary, PROGRAM_BUF_VA) else { let mut file = match libkos::fs::Storage::attach(binary, PROGRAM_BUF_VA) {
return refused(reply::NOT_FOUND); Ok(f) => f,
Err(libkos::fs::FsError::Full) => {
crate::outln!("init: cannot spawn: storage has no connection free");
return refused(reply::STORAGE_BUSY);
}
Err(_) => return refused(reply::NOT_FOUND),
}; };
// The grant is the file: its path inside it is empty. // The grant is the file: its path inside it is empty.
let found = file.open(b"").and_then(|h| file.close(h)).is_ok(); let found = file.open(b"").and_then(|h| file.close(h)).is_ok();

View File

@@ -68,9 +68,10 @@ fn out_of_range_arguments_do_not_decode() {
"a thread keeps 8 replies" "a thread keeps 8 replies"
); );
assert!(decode(SYS_REPLY_SAVED, &[7, 0, 0, 0, 0, NONE]).is_some()); assert!(decode(SYS_REPLY_SAVED, &[7, 0, 0, 0, 0, NONE]).is_some());
assert!(decode(SYS_RECEIVE, &[64, 0, 0, 0, 0, 0]).is_none()); let past = types::bounded::CapSlot::MAX as usize + 1;
assert!(decode(SYS_RECEIVE, &[past, 0, 0, 0, 0, 0]).is_none());
assert!( assert!(
decode(SYS_SEND, &[1, 0, 0, 0, 0, 64]).is_none(), decode(SYS_SEND, &[1, 0, 0, 0, 0, past]).is_none(),
"bad cap slot" "bad cap slot"
); );
assert!( assert!(

View File

@@ -25,6 +25,8 @@ pub enum FsError {
PathTooLong, PathTooLong,
/// Outside this connection's scope, or beyond its rights. /// Outside this connection's scope, or beyond its rights.
Denied, Denied,
/// Every connection storage has is in use.
Full,
/// The server refused, or the call itself failed. /// The server refused, or the call itself failed.
Failed, Failed,
} }
@@ -64,7 +66,7 @@ impl Storage {
syscall::Received::Signal { .. } => None, syscall::Received::Signal { .. } => None,
}; };
let (true, Some(region)) = (tag == reply::ATTACHED, cap) else { let (true, Some(region)) = (tag == reply::ATTACHED, cap) else {
return Err(FsError::Failed); return Err(if tag == reply::FULL { FsError::Full } else { FsError::Failed });
}; };
let mapped = crate::window::map_buffer(region, va).map_err(|_| FsError::Failed)?; let mapped = crate::window::map_buffer(region, va).map_err(|_| FsError::Failed)?;
let buf = mapped.get_mut(..size).ok_or(FsError::Failed)?; let buf = mapped.get_mut(..size).ok_or(FsError::Failed)?;

View File

@@ -16,6 +16,8 @@ pub enum SpawnError {
NotFound, NotFound,
/// More than what is left of this process's spawn allowance. /// More than what is left of this process's spawn allowance.
OverAllowance, OverAllowance,
/// Storage had no connection free to load it through.
StorageBusy,
Failed, Failed,
} }
@@ -44,6 +46,7 @@ pub fn spawn(init: CapSlot, binary: CapSlot, limits: Limits) -> Result<Spawned,
}), }),
(reply::NOT_FOUND, _) => Err(SpawnError::NotFound), (reply::NOT_FOUND, _) => Err(SpawnError::NotFound),
(reply::OVER_ALLOWANCE, _) => Err(SpawnError::OverAllowance), (reply::OVER_ALLOWANCE, _) => Err(SpawnError::OverAllowance),
(reply::STORAGE_BUSY, _) => Err(SpawnError::StorageBusy),
_ => Err(SpawnError::Failed), _ => Err(SpawnError::Failed),
} }
} }

View File

@@ -216,7 +216,7 @@ impl PermissionServer {
let spawned = libkos::spawn::spawn(self.init, file, Limits { pages: plan.app.memory.pages(), ..PROGRAM }); let spawned = libkos::spawn::spawn(self.init, file, Limits { pages: plan.app.memory.pages(), ..PROGRAM });
let _ = libkos::fs::release(file); let _ = libkos::fs::release(file);
let child = spawned.map_err(|e| match e { let child = spawned.map_err(|e| match e {
libkos::spawn::SpawnError::OverAllowance => code(reply::BUSY), libkos::spawn::SpawnError::OverAllowance | libkos::spawn::SpawnError::StorageBusy => code(reply::BUSY),
_ => code(reply::FAILED), _ => code(reply::FAILED),
})?; })?;

View File

@@ -12,9 +12,7 @@
use filecrypt::{CHUNK, Drbg, HEADER, Header, Key, OVERHEAD, PLAIN_CHUNK, area_key, chunk_offset, chunks, plain_len}; use filecrypt::{CHUNK, Drbg, HEADER, Header, Key, OVERHEAD, PLAIN_CHUNK, area_key, chunk_offset, chunks, plain_len};
use crate::filesystem::{DirEntry, FileHandle, FileSystem}; use crate::filesystem::{DirEntry, FileHandle, FileSystem};
use crate::slots::Slots;
/// The inner file system's handles this layer can track.
const MAX_HANDLES: usize = 16;
/// Where a path falls. /// Where a path falls.
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
@@ -66,7 +64,8 @@ pub struct Encrypted<F: FileSystem> {
user: [u8; USER_NAME], user: [u8; USER_NAME],
user_len: usize, user_len: usize,
rng: Option<Drbg>, rng: Option<Drbg>,
open: [Option<Open>; MAX_HANDLES], /// Sealed files open, by the inner file system's handle.
open: Slots<Open>,
} }
/// A user folder's name: 16 hex digits, or `guest`. /// A user folder's name: 16 hex digits, or `guest`.
@@ -74,7 +73,7 @@ pub const USER_NAME: usize = 16;
impl<F: FileSystem> Encrypted<F> { impl<F: FileSystem> Encrypted<F> {
pub fn new(inner: F) -> Self { pub fn new(inner: F) -> Self {
Self { inner, master: None, user: [0; USER_NAME], user_len: 0, rng: None, open: [const { None }; MAX_HANDLES] } Self { inner, master: None, user: [0; USER_NAME], user_len: 0, rng: None, open: Slots::new() }
} }
pub fn inner(&self) -> &F { pub fn inner(&self) -> &F {
@@ -114,10 +113,6 @@ impl<F: FileSystem> Encrypted<F> {
} }
} }
fn slot(&mut self, h: FileHandle) -> Option<&mut Option<Open>> {
self.open.get_mut(h.raw() as usize)
}
/// Chunk `index` of a file of `size` bytes, opened into `out`; its length. /// Chunk `index` of a file of `size` bytes, opened into `out`; its length.
fn read_chunk(inner: &mut F, h: FileHandle, key: &Key, size: usize, index: usize, out: &mut [u8; PLAIN_CHUNK]) -> Option<usize> { fn read_chunk(inner: &mut F, h: FileHandle, key: &Key, size: usize, index: usize, out: &mut [u8; PLAIN_CHUNK]) -> Option<usize> {
let last = index + 1 == chunks(size); let last = index + 1 == chunks(size);
@@ -153,10 +148,10 @@ impl<F: FileSystem> FileSystem for Encrypted<F> {
} }
fn lock(&mut self) { fn lock(&mut self) {
for (i, slot) in self.open.iter_mut().enumerate() { let open: alloc::vec::Vec<usize> = self.open.iter().map(|(i, _)| i).collect();
if slot.take().is_some() { for i in open {
self.inner.close(FileHandle::new(i as u16)); self.open.take(i);
} self.inner.close(FileHandle::new(i as u16));
} }
self.master = None; self.master = None;
self.rng = None; self.rng = None;
@@ -183,7 +178,7 @@ impl<F: FileSystem> FileSystem for Encrypted<F> {
return None; return None;
}; };
let key = header.file_key(&area); let key = header.file_key(&area);
*self.slot(h)? = Some(Open { key, size, pos: 0 }); self.open.put(h.raw() as usize, Open { key, size, pos: 0 });
Some(h) Some(h)
} }
@@ -203,12 +198,12 @@ impl<F: FileSystem> FileSystem for Encrypted<F> {
let _ = self.inner.delete(path); let _ = self.inner.delete(path);
return None; return None;
} }
*self.slot(h)? = Some(Open { key: file_key, size: 0, pos: 0 }); self.open.put(h.raw() as usize, Open { key: file_key, size: 0, pos: 0 });
Some(h) Some(h)
} }
fn read(&mut self, h: FileHandle, buf: &mut [u8]) -> Option<usize> { fn read(&mut self, h: FileHandle, buf: &mut [u8]) -> Option<usize> {
let Some(Some(f)) = self.open.get(h.raw() as usize) else { return self.inner.read(h, buf) }; let Some(f) = self.open.get(h.raw() as usize) else { return self.inner.read(h, buf) };
let (key, size, mut pos) = (f.key.clone(), f.size, f.pos); let (key, size, mut pos) = (f.key.clone(), f.size, f.pos);
let mut done = 0; let mut done = 0;
let mut chunk = [0u8; PLAIN_CHUNK]; let mut chunk = [0u8; PLAIN_CHUNK];
@@ -222,12 +217,12 @@ impl<F: FileSystem> FileSystem for Encrypted<F> {
pos += take; pos += take;
} }
chunk.fill(0); chunk.fill(0);
self.slot(h)?.as_mut()?.pos = pos; self.open.get_mut(h.raw() as usize)?.pos = pos;
Some(done) Some(done)
} }
fn write(&mut self, h: FileHandle, buf: &[u8]) -> Option<usize> { fn write(&mut self, h: FileHandle, buf: &[u8]) -> Option<usize> {
let Some(Some(f)) = self.open.get(h.raw() as usize) else { return self.inner.write(h, buf) }; let Some(f) = self.open.get(h.raw() as usize) else { return self.inner.write(h, buf) };
let (key, old_size, pos) = (f.key.clone(), f.size, f.pos); let (key, old_size, pos) = (f.key.clone(), f.size, f.pos);
let new_size = old_size.max(pos + buf.len()); let new_size = old_size.max(pos + buf.len());
let (old_last, new_last) = (chunks(old_size) - 1, chunks(new_size) - 1); let (old_last, new_last) = (chunks(old_size) - 1, chunks(new_size) - 1);
@@ -253,34 +248,32 @@ impl<F: FileSystem> FileSystem for Encrypted<F> {
} }
} }
chunk.fill(0); chunk.fill(0);
let f = self.slot(h)?.as_mut()?; let f = self.open.get_mut(h.raw() as usize)?;
f.size = new_size; f.size = new_size;
f.pos = pos + buf.len(); f.pos = pos + buf.len();
Some(buf.len()) Some(buf.len())
} }
fn seek(&mut self, h: FileHandle, offset: usize) -> bool { fn seek(&mut self, h: FileHandle, offset: usize) -> bool {
match self.slot(h) { match self.open.get_mut(h.raw() as usize) {
Some(Some(f)) if offset <= f.size => { Some(f) if offset <= f.size => {
f.pos = offset; f.pos = offset;
true true
} }
Some(Some(_)) => false, Some(_) => false,
_ => self.inner.seek(h, offset), None => self.inner.seek(h, offset),
} }
} }
fn close(&mut self, h: FileHandle) { fn close(&mut self, h: FileHandle) {
if let Some(slot) = self.slot(h) { self.open.take(h.raw() as usize);
*slot = None;
}
self.inner.close(h); self.inner.close(h);
} }
fn file_size(&mut self, h: FileHandle) -> Option<usize> { fn file_size(&mut self, h: FileHandle) -> Option<usize> {
match self.open.get(h.raw() as usize) { match self.open.get(h.raw() as usize) {
Some(Some(f)) => Some(f.size), Some(f) => Some(f.size),
_ => self.inner.file_size(h), None => self.inner.file_size(h),
} }
} }

View File

@@ -9,11 +9,11 @@ mod path;
use port_block_device::BlockDevice; use port_block_device::BlockDevice;
use crate::filesystem::{DirEntry, FileHandle, FileSystem}; use crate::filesystem::{DirEntry, FileHandle, FileSystem};
use crate::slots::Slots;
use io::read_block; use io::read_block;
use superblock::Superblock; use superblock::Superblock;
const ROOT_INODE: u32 = 2; const ROOT_INODE: u32 = 2;
const MAX_OPEN_FILES: usize = 8;
const EXTENT_MAGIC: u16 = 0xF30A; const EXTENT_MAGIC: u16 = 0xF30A;
@@ -31,7 +31,8 @@ pub struct Ext4<B: BlockDevice> {
device: B, device: B,
sb: Superblock, sb: Superblock,
gdt_block: u32, gdt_block: u32,
open_files: [Option<OpenFile>; MAX_OPEN_FILES], /// Files open, by handle: as many as storage's memory holds.
open_files: Slots<OpenFile>,
} }
impl<B: BlockDevice> Ext4<B> { impl<B: BlockDevice> Ext4<B> {
@@ -47,7 +48,7 @@ impl<B: BlockDevice> Ext4<B> {
device, device,
sb, sb,
gdt_block, gdt_block,
open_files: [const { None }; MAX_OPEN_FILES], open_files: Slots::new(),
}) })
} }
@@ -86,7 +87,7 @@ impl<B: BlockDevice> FileSystem for Ext4<B> {
if !inode.is_file() { return None; } if !inode.is_file() { return None; }
let handle = self.alloc_handle()?; let handle = self.alloc_handle()?;
self.open_files[handle.raw() as usize] = Some(OpenFile { self.open_files.put(handle.raw() as usize, OpenFile {
inode_num, inode_num,
size: inode.size as usize, size: inode.size as usize,
offset: 0, offset: 0,
@@ -96,8 +97,7 @@ impl<B: BlockDevice> FileSystem for Ext4<B> {
} }
fn read(&mut self, handle: FileHandle, buf: &mut [u8]) -> Option<usize> { fn read(&mut self, handle: FileHandle, buf: &mut [u8]) -> Option<usize> {
if handle.raw() as usize >= MAX_OPEN_FILES { return None; } let file = *self.open_files.get(handle.raw() as usize)?;
let file = *self.open_files[handle.raw() as usize].as_ref()?;
if file.offset >= file.size { return Some(0); } if file.offset >= file.size { return Some(0); }
// As much as `buf` holds, across blocks: a short read means the end. // As much as `buf` holds, across blocks: a short read means the end.
@@ -131,15 +131,14 @@ impl<B: BlockDevice> FileSystem for Ext4<B> {
done += chunk; done += chunk;
} }
if let Some(f) = self.open_files[handle.raw() as usize].as_mut() { if let Some(f) = self.open_files.get_mut(handle.raw() as usize) {
f.offset += done; f.offset += done;
} }
Some(done) Some(done)
} }
fn write(&mut self, handle: FileHandle, buf: &[u8]) -> Option<usize> { fn write(&mut self, handle: FileHandle, buf: &[u8]) -> Option<usize> {
if handle.raw() as usize >= MAX_OPEN_FILES { return None; } let file = *self.open_files.get(handle.raw() as usize)?;
let file = *self.open_files[handle.raw() as usize].as_ref()?;
let bs = self.sb.block_size; let bs = self.sb.block_size;
let mut written = 0; let mut written = 0;
let mut offset = file.offset; let mut offset = file.offset;
@@ -186,7 +185,7 @@ impl<B: BlockDevice> FileSystem for Ext4<B> {
} }
if written > 0 { if written > 0 {
if let Some(f) = self.open_files[handle.raw() as usize].as_mut() { if let Some(f) = self.open_files.get_mut(handle.raw() as usize) {
f.offset = offset; f.offset = offset;
f.size = size; f.size = size;
f.block_data = block_data; f.block_data = block_data;
@@ -205,8 +204,7 @@ impl<B: BlockDevice> FileSystem for Ext4<B> {
} }
fn close(&mut self, handle: FileHandle) { fn close(&mut self, handle: FileHandle) {
if handle.raw() as usize >= MAX_OPEN_FILES { return; } if let Some(file) = self.open_files.take(handle.raw() as usize)
if let Some(file) = self.open_files[handle.raw() as usize].take()
&& let Some(mut inode) = self.read_inode(file.inode_num) { && let Some(mut inode) = self.read_inode(file.inode_num) {
inode.size = file.size as u64; inode.size = file.size as u64;
inode.block_data = file.block_data; inode.block_data = file.block_data;
@@ -255,7 +253,7 @@ impl<B: BlockDevice> FileSystem for Ext4<B> {
} }
let handle = self.alloc_handle()?; let handle = self.alloc_handle()?;
self.open_files[handle.raw() as usize] = Some(OpenFile { self.open_files.put(handle.raw() as usize, OpenFile {
inode_num: new_inode_num, inode_num: new_inode_num,
size: 0, size: 0,
offset: 0, offset: 0,
@@ -297,18 +295,15 @@ impl<B: BlockDevice> FileSystem for Ext4<B> {
} }
self.clear_inode(child_inode_num); self.clear_inode(child_inode_num);
self.free_inode(child_inode_num); self.free_inode(child_inode_num);
for slot in self.open_files.iter_mut() { let gone: alloc::vec::Vec<usize> = self.open_files.iter().filter(|(_, f)| f.inode_num == child_inode_num).map(|(i, _)| i).collect();
if let Some(f) = slot for i in gone {
&& f.inode_num == child_inode_num { self.open_files.take(i);
*slot = None;
}
} }
true true
} }
fn file_size(&mut self, handle: FileHandle) -> Option<usize> { fn file_size(&mut self, handle: FileHandle) -> Option<usize> {
if handle.raw() as usize >= MAX_OPEN_FILES { return None; } self.open_files.get(handle.raw() as usize).map(|f| f.size)
self.open_files[handle.raw() as usize].as_ref().map(|f| f.size)
} }
fn mkdir(&mut self, path: &[u8]) -> bool { fn mkdir(&mut self, path: &[u8]) -> bool {
@@ -370,7 +365,7 @@ impl<B: BlockDevice> FileSystem for Ext4<B> {
} }
fn seek(&mut self, handle: FileHandle, offset: usize) -> bool { fn seek(&mut self, handle: FileHandle, offset: usize) -> bool {
match self.open_files.get_mut(handle.raw() as usize).and_then(Option::as_mut) { match self.open_files.get_mut(handle.raw() as usize) {
Some(f) if offset <= f.size => { Some(f) if offset <= f.size => {
f.offset = offset; f.offset = offset;
true true

View File

@@ -1,6 +1,6 @@
//! Path resolution, directory entry operations and allocation wrappers. //! Path resolution, directory entry operations and allocation wrappers.
use super::{bitmap, dir, Ext4, MAX_OPEN_FILES, ROOT_INODE}; use super::{bitmap, dir, Ext4, ROOT_INODE};
use crate::filesystem::FileHandle; use crate::filesystem::FileHandle;
use port_block_device::BlockDevice; use port_block_device::BlockDevice;
@@ -58,12 +58,7 @@ impl<B: BlockDevice> Ext4<B> {
} }
pub(super) fn alloc_handle(&self) -> Option<FileHandle> { pub(super) fn alloc_handle(&self) -> Option<FileHandle> {
for i in 0..MAX_OPEN_FILES { self.open_files.free().map(|i| FileHandle::new(i as u16))
if self.open_files[i].is_none() {
return Some(FileHandle::new(i as u16));
}
}
None
} }
pub(super) fn resolve_parent<'a>(&mut self, path: &'a [u8]) -> Option<(u32, &'a [u8])> { pub(super) fn resolve_parent<'a>(&mut self, path: &'a [u8]) -> Option<(u32, &'a [u8])> {

View File

@@ -2,12 +2,12 @@
//! endpoint, so a badge it does not know (never issued, or released) is //! endpoint, so a badge it does not know (never issued, or released) is
//! no authority at all. //! no authority at all.
use alloc::vec;
use alloc::vec::Vec;
use types::bounded::CapSlot; use types::bounded::CapSlot;
use crate::scope::Scope; use crate::scope::Scope;
pub const MAX_GRANTS: usize = 16;
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
struct Grant { struct Grant {
badge: usize, badge: usize,
@@ -20,7 +20,8 @@ struct Grant {
} }
pub struct Grants { pub struct Grants {
grants: [Option<Grant>; MAX_GRANTS], /// As many as storage's memory holds.
grants: Vec<Grant>,
/// Badges are never reused, so a released one stays dead. /// Badges are never reused, so a released one stays dead.
next_badge: usize, next_badge: usize,
} }
@@ -28,7 +29,7 @@ pub struct Grants {
impl Grants { impl Grants {
pub const fn new() -> Self { pub const fn new() -> Self {
Self { Self {
grants: [None; MAX_GRANTS], grants: Vec::new(),
next_badge: 1, next_badge: 1,
} }
} }
@@ -39,7 +40,6 @@ impl Grants {
b => self b => self
.grants .grants
.iter() .iter()
.flatten()
.find(|g| g.badge == b) .find(|g| g.badge == b)
.map(|g| g.scope), .map(|g| g.scope),
} }
@@ -53,11 +53,10 @@ impl Grants {
scope: Scope, scope: Scope,
mint: impl FnOnce(usize) -> Option<CapSlot>, mint: impl FnOnce(usize) -> Option<CapSlot>,
) -> Option<CapSlot> { ) -> Option<CapSlot> {
let free = self.grants.iter_mut().find(|g| g.is_none())?;
let badge = self.next_badge; let badge = self.next_badge;
let cap = mint(badge)?; let cap = mint(badge)?;
self.next_badge += 1; self.next_badge += 1;
*free = Some(Grant { self.grants.push(Grant {
badge, badge,
scope, scope,
parent, parent,
@@ -66,14 +65,9 @@ impl Grants {
Some(cap) Some(cap)
} }
/// The grants init made directly, into `out`; how many. /// The grants init made directly.
pub fn roots(&self, out: &mut [usize; MAX_GRANTS]) -> usize { pub fn roots(&self) -> Vec<usize> {
let mut n = 0; self.grants.iter().filter(|g| g.parent == 0).map(|g| g.badge).collect()
for g in self.grants.iter().flatten().filter(|g| g.parent == 0) {
out[n] = g.badge;
n += 1;
}
n
} }
/// Forgets `badge` and every grant narrowed from it, transitively, /// Forgets `badge` and every grant narrowed from it, transitively,
@@ -82,24 +76,19 @@ impl Grants {
if badge == 0 { if badge == 0 {
return; return;
} }
let mut doomed = [0usize; MAX_GRANTS]; let mut doomed = vec![badge];
doomed[0] = badge;
let mut n = 1;
let mut i = 0; let mut i = 0;
while i < n { while let Some(&b) = doomed.get(i) {
let b = doomed[i];
i += 1; i += 1;
for g in &mut self.grants { doomed.extend(self.grants.iter().filter(|g| g.parent == b).map(|g| g.badge));
let Some(grant) = g else { continue };
if grant.badge == b {
gone(grant.badge, grant.cap);
*g = None;
} else if grant.parent == b && n < MAX_GRANTS {
doomed[n] = grant.badge;
n += 1;
}
}
} }
self.grants.retain(|g| {
let dies = doomed.contains(&g.badge);
if dies {
gone(g.badge, g.cap);
}
!dies
});
} }
} }

View File

@@ -11,6 +11,7 @@ pub mod grants;
pub mod protocol; pub mod protocol;
pub mod scope; pub mod scope;
pub mod server; pub mod server;
mod slots;
#[cfg(test)] #[cfg(test)]
#[path = "tests/mod.rs"] #[path = "tests/mod.rs"]

View File

@@ -62,6 +62,8 @@ pub enum StorageResponse {
/// Carries the new capability. /// Carries the new capability.
Granted, Granted,
Denied, Denied,
/// No connection free.
Full,
} }
fn handle(word: usize) -> Option<FileHandle> { fn handle(word: usize) -> Option<FileHandle> {
@@ -143,6 +145,7 @@ impl Encode for StorageResponse {
NotAttached => [reply::NOT_ATTACHED, 0, 0, 0], NotAttached => [reply::NOT_ATTACHED, 0, 0, 0],
Granted => [reply::GRANTED, 0, 0, 0], Granted => [reply::GRANTED, 0, 0, 0],
Denied => [reply::DENIED, 0, 0, 0], Denied => [reply::DENIED, 0, 0, 0],
Full => [reply::FULL, 0, 0, 0],
} }
} }
} }
@@ -164,6 +167,7 @@ impl Decode for StorageResponse {
reply::NOT_ATTACHED => NotAttached, reply::NOT_ATTACHED => NotAttached,
reply::GRANTED => Granted, reply::GRANTED => Granted,
reply::DENIED => Denied, reply::DENIED => Denied,
reply::FULL => Full,
_ => return None, _ => return None,
}) })
} }

View File

@@ -10,9 +10,8 @@ use crate::scope::Scope;
mod session; mod session;
use types::storage::MAX_CLIENTS; use types::storage::MAX_CLIENTS;
/// Handles at or past this are never valid (the file systems hand out
/// fewer), so ownership fits a small table. use crate::slots::Slots;
const MAX_HANDLES: usize = 16;
const MAX_LISTED: usize = 64; const MAX_LISTED: usize = 64;
/// What the server needs from the kernel: regions for client buffers /// What the server needs from the kernel: regions for client buffers
@@ -50,7 +49,8 @@ pub struct StorageServer<F: FileSystem, H: Host> {
host: H, host: H,
grants: Grants, grants: Grants,
clients: [Option<Client>; MAX_CLIENTS], clients: [Option<Client>; MAX_CLIENTS],
owners: [Option<Owner>; MAX_HANDLES], /// Who opened each handle, by handle.
owners: Slots<Owner>,
log: Option<CapSlot>, log: Option<CapSlot>,
/// The master key's and the seed's halves, from the gatekeeper through /// The master key's and the seed's halves, from the gatekeeper through
/// init; handed to the file system (and forgotten here) once all in. /// init; handed to the file system (and forgotten here) once all in.
@@ -68,7 +68,7 @@ impl<F: FileSystem, H: Host> StorageServer<F, H> {
host, host,
grants: Grants::new(), grants: Grants::new(),
clients: [const { None }; MAX_CLIENTS], clients: [const { None }; MAX_CLIENTS],
owners: [None; MAX_HANDLES], owners: Slots::new(),
log: None, log: None,
key: [None; 2], key: [None; 2],
seed: [None; 2], seed: [None; 2],
@@ -144,7 +144,8 @@ impl<F: FileSystem, H: Host> StorageServer<F, H> {
); );
} }
let Some(index) = self.clients.iter().position(Option::is_none) else { let Some(index) = self.clients.iter().position(Option::is_none) else {
return (StorageResponse::Error, None); libkos::println!("storage: every connection ({}) is in use", MAX_CLIENTS);
return (StorageResponse::Full, None);
}; };
let Some((buf, cap)) = self.host.create(index) else { let Some((buf, cap)) = self.host.create(index) else {
return (StorageResponse::Error, None); return (StorageResponse::Error, None);
@@ -176,11 +177,10 @@ impl<F: FileSystem, H: Host> StorageServer<F, H> {
} = self; } = self;
grants.release(badge, |gone, cap| { grants.release(badge, |gone, cap| {
host.revoke(cap); host.revoke(cap);
for (raw, owner) in owners.iter_mut().enumerate() { let theirs: alloc::vec::Vec<usize> = owners.iter().filter(|(_, o)| o.badge == gone).map(|(raw, _)| raw).collect();
if owner.is_some_and(|o| o.badge == gone) { for raw in theirs {
fs.close(FileHandle::new(raw as u16)); fs.close(FileHandle::new(raw as u16));
*owner = None; owners.take(raw);
}
} }
for c in clients for c in clients
.iter_mut() .iter_mut()
@@ -198,7 +198,7 @@ impl<F: FileSystem, H: Host> StorageServer<F, H> {
} }
fn owner(&self, handle: FileHandle) -> Option<Owner> { fn owner(&self, handle: FileHandle) -> Option<Owner> {
self.owners.get(handle.raw() as usize).copied().flatten() self.owners.get(handle.raw() as usize).copied()
} }
/// Serves one request from `badge`, which holds `scope`. /// Serves one request from `badge`, which holds `scope`.
@@ -269,18 +269,11 @@ impl<F: FileSystem, H: Host> StorageServer<F, H> {
resolved => resolved.flatten(), resolved => resolved.flatten(),
}; };
let path = disk.as_ref().map_or(&[][..], |p| p.as_bytes()); let path = disk.as_ref().map_or(&[][..], |p| p.as_bytes());
let opened = |owners: &mut [Option<Owner>; MAX_HANDLES], h: Option<FileHandle>| match h { let opened = |owners: &mut Slots<Owner>, h: Option<FileHandle>| match h {
Some(h) => match owners.get_mut(h.raw() as usize) { Some(h) => {
Some(owner) => { owners.put(h.raw() as usize, Owner { badge, write: scope.write });
*owner = Some(Owner { R::Handle(h)
badge, }
write: scope.write,
});
R::Handle(h)
}
// A handle the table cannot track is one we cannot police.
None => R::Error,
},
None => R::NotFound, None => R::NotFound,
}; };
let response = match request { let response = match request {
@@ -311,9 +304,7 @@ impl<F: FileSystem, H: Host> StorageServer<F, H> {
}, },
Close { handle } => { Close { handle } => {
fs.close(handle); fs.close(handle);
if let Some(owner) = owners.get_mut(handle.raw() as usize) { owners.take(handle.raw() as usize);
*owner = None;
}
R::Ok R::Ok
} }
FileSize { handle } => match fs.file_size(handle) { FileSize { handle } => match fs.file_size(handle) {

View File

@@ -6,7 +6,6 @@ use types::user::UserFolder;
use super::{Host, StorageServer}; use super::{Host, StorageServer};
use crate::filesystem::{FileHandle, FileSystem}; use crate::filesystem::{FileHandle, FileSystem};
use crate::grants::MAX_GRANTS;
use crate::protocol::{MAX_PATH, StorageResponse}; use crate::protocol::{MAX_PATH, StorageResponse};
impl<F: FileSystem, H: Host> StorageServer<F, H> { impl<F: FileSystem, H: Host> StorageServer<F, H> {
@@ -46,15 +45,13 @@ impl<F: FileSystem, H: Host> StorageServer<F, H> {
if badge != 0 { if badge != 0 {
return StorageResponse::Error; return StorageResponse::Error;
} }
let mut roots = [0usize; MAX_GRANTS]; for root in self.grants.roots() {
let n = self.grants.roots(&mut roots);
for &root in &roots[..n] {
self.release(root); self.release(root);
} }
for (raw, owner) in self.owners.iter_mut().enumerate() { let open: alloc::vec::Vec<usize> = self.owners.iter().map(|(raw, _)| raw).collect();
if owner.take().is_some() { for raw in open {
self.fs.close(FileHandle::new(raw as u16)); self.owners.take(raw);
} self.fs.close(FileHandle::new(raw as u16));
} }
self.fs.lock(); self.fs.lock();
self.key = [None; 2]; self.key = [None; 2];

View File

@@ -0,0 +1,55 @@
//! A table that grows as it fills, bounded by storage's memory rather than
//! a count: an entry keeps its index for life (a handle names it), and a
//! freed index is reused first. At most what a `FileHandle` can name.
use alloc::vec::Vec;
pub const MAX: usize = u16::MAX as usize + 1;
pub struct Slots<T>(Vec<Option<T>>);
impl<T> Slots<T> {
pub const fn new() -> Self {
Self(Vec::new())
}
/// The index the next entry would get, if there is room for one.
pub fn free(&self) -> Option<usize> {
let i = self.0.iter().position(Option::is_none).unwrap_or(self.0.len());
(i < MAX).then_some(i)
}
/// Puts `value` at `index`, growing to it.
pub fn put(&mut self, index: usize, value: T) {
if index >= MAX {
return;
}
if self.0.len() <= index {
self.0.resize_with(index + 1, || None);
}
self.0[index] = Some(value);
}
pub fn get(&self, index: usize) -> Option<&T> {
self.0.get(index)?.as_ref()
}
pub fn get_mut(&mut self, index: usize) -> Option<&mut T> {
self.0.get_mut(index)?.as_mut()
}
pub fn take(&mut self, index: usize) -> Option<T> {
self.0.get_mut(index)?.take()
}
/// Every entry, with its index.
pub fn iter(&self) -> impl Iterator<Item = (usize, &T)> {
self.0.iter().enumerate().filter_map(|(i, v)| v.as_ref().map(|v| (i, v)))
}
}
impl<T> Default for Slots<T> {
fn default() -> Self {
Self::new()
}
}

View File

@@ -595,3 +595,18 @@ fn delete_nonempty_dir_returns_false() {
let count = fs.list_dir(b"parent", &mut entries).unwrap(); let count = fs.list_dir(b"parent", &mut entries).unwrap();
assert_eq!(count, 1); assert_eq!(count, 1);
} }
/// Open files were a table of 8: now as many as memory holds.
#[test]
fn many_files_open_at_once() {
let mut fs = Ext4::mount(format_ext4_disk()).unwrap();
let handles: alloc::vec::Vec<_> = (0..40).map(|_| fs.open(b"hello").expect("opens")).collect();
let mut buf = [0u8; 32];
assert_eq!(fs.read(handles[39], &mut buf), Some(13));
for h in &handles[..20] {
fs.close(*h);
}
// Closed handles are used again.
let again = fs.open(b"hello").unwrap();
assert!(handles[..20].contains(&again));
}

View File

@@ -532,3 +532,32 @@ fn only_init_purges_only_users_and_only_while_locked() {
assert_eq!(purge(&mut s, 0, b"users/x"), StorageResponse::Ok); assert_eq!(purge(&mut s, 0, b"users/x"), StorageResponse::Ok);
assert_eq!(purge(&mut s, 0, b"users"), StorageResponse::Ok); assert_eq!(purge(&mut s, 0, b"users"), StorageResponse::Ok);
} }
/// Grants were a table of 16, and releasing followed at most 16 narrowed
/// from one: neither is a limit any more.
#[test]
fn many_grants_and_all_narrowed_from_one_go_on_release() {
let mut s = server();
let root = Client::attach(&mut s, 0);
let siblings: Vec<usize> = (0..20).map(|_| root.grant(&mut s, b"", true)).collect();
let parent = Client::attach(&mut s, siblings[0]);
let children: Vec<usize> = (0..20).map(|_| parent.grant(&mut s, b"", false)).collect();
assert_eq!(call(&mut s, parent.badge, StorageRequest::Release).0, StorageResponse::Ok);
for badge in children {
assert_eq!(call(&mut s, badge, StorageRequest::Attach).0, StorageResponse::Denied, "child {badge}");
}
assert_eq!(call(&mut s, siblings[19], StorageRequest::Attach).0, StorageResponse::Attached { size: 4096 }, "a sibling stays");
}
/// Past the last connection, storage says it is full, not that it failed.
#[test]
fn a_connection_past_the_last_is_told_storage_is_full() {
let mut s = server();
let root = Client::attach(&mut s, 0);
let grants: Vec<usize> = (1..types::storage::MAX_CLIENTS).map(|_| root.grant(&mut s, b"", false)).collect();
for g in &grants {
Client::attach(&mut s, *g);
}
let one_more = root.grant(&mut s, b"", false);
assert_eq!(call(&mut s, one_more, StorageRequest::Attach).0, StorageResponse::Full);
}

View File

@@ -1,7 +1,8 @@
use crate::macros::bounded; use crate::macros::bounded;
// Kani runs the real kernel with tiny tables; see kernel/src/proofs. // Kani runs the real kernel with tiny tables; see kernel/src/proofs.
bounded!(CapSlot, u8, 0, if cfg!(kani) { 3 } else { 63 }); // 254 at most: `start_arg` hands a slot over as its value plus one, in a byte.
bounded!(CapSlot, u8, 0, if cfg!(kani) { 3 } else { 254 });
bounded!(Priority, u8, 0, 31); bounded!(Priority, u8, 0, 31);
// A caller a server thread saved to answer later; a thread keeps up to 8. // A caller a server thread saved to answer later; a thread keeps up to 8.
bounded!(SavedReply, u8, 0, 7); bounded!(SavedReply, u8, 0, 7);

View File

@@ -43,6 +43,8 @@ pub mod reply {
/// The limits do not fit in what is left of the client's allowance. /// The limits do not fit in what is left of the client's allowance.
pub const OVER_ALLOWANCE: usize = 2; pub const OVER_ALLOWANCE: usize = 2;
pub const ERROR: usize = 3; pub const ERROR: usize = 3;
/// Storage has no connection free to read the program through.
pub const STORAGE_BUSY: usize = 4;
} }
/// Pages in bits 0..32, endpoints, notifications and regions a byte each /// Pages in bits 0..32, endpoints, notifications and regions a byte each

View File

@@ -75,4 +75,6 @@ pub mod reply {
pub const GRANTED: usize = 11; pub const GRANTED: usize = 11;
/// Outside the caller's scope, beyond its rights, or no grant at all. /// Outside the caller's scope, beyond its rights, or no grant at all.
pub const DENIED: usize = 12; pub const DENIED: usize = 12;
/// Every connection is in use (`MAX_CLIENTS`): try again later.
pub const FULL: usize = 13;
} }