From 1f2134fcec9fe15165dac47943bda6b6b20b309e Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Wed, 28 Oct 2026 16:17:36 +0100 Subject: [PATCH] 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 --- crates/init/src/spawn.rs | 9 ++- crates/kernel/src/tests/abi.rs | 5 +- crates/libkos/src/fs.rs | 4 +- crates/libkos/src/spawn.rs | 3 + .../servers/permission-manager/src/server.rs | 2 +- .../servers/storage-server/src/encrypted.rs | 49 +++++++---------- .../servers/storage-server/src/fs/ext4/mod.rs | 37 ++++++------- .../storage-server/src/fs/ext4/path.rs | 9 +-- crates/servers/storage-server/src/grants.rs | 49 +++++++---------- crates/servers/storage-server/src/lib.rs | 1 + crates/servers/storage-server/src/protocol.rs | 4 ++ crates/servers/storage-server/src/server.rs | 45 ++++++--------- .../storage-server/src/server/session.rs | 13 ++--- crates/servers/storage-server/src/slots.rs | 55 +++++++++++++++++++ .../servers/storage-server/src/tests/ext4.rs | 15 +++++ .../storage-server/src/tests/server.rs | 29 ++++++++++ crates/types/src/bounded.rs | 3 +- crates/types/src/spawn.rs | 2 + crates/types/src/storage.rs | 2 + 19 files changed, 208 insertions(+), 128 deletions(-) create mode 100644 crates/servers/storage-server/src/slots.rs diff --git a/crates/init/src/spawn.rs b/crates/init/src/spawn.rs index 133e09c..548c841 100644 --- a/crates/init/src/spawn.rs +++ b/crates/init/src/spawn.rs @@ -131,8 +131,13 @@ impl Launcher<'_> { let Some(index) = self.spawner.children.iter().position(Option::is_none) else { return refused(reply::ERROR); }; - let Ok(mut file) = libkos::fs::Storage::attach(binary, PROGRAM_BUF_VA) else { - return refused(reply::NOT_FOUND); + let mut file = match libkos::fs::Storage::attach(binary, PROGRAM_BUF_VA) { + 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. let found = file.open(b"").and_then(|h| file.close(h)).is_ok(); diff --git a/crates/kernel/src/tests/abi.rs b/crates/kernel/src/tests/abi.rs index 148f537..c82994c 100644 --- a/crates/kernel/src/tests/abi.rs +++ b/crates/kernel/src/tests/abi.rs @@ -68,9 +68,10 @@ fn out_of_range_arguments_do_not_decode() { "a thread keeps 8 replies" ); 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!( - 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" ); assert!( diff --git a/crates/libkos/src/fs.rs b/crates/libkos/src/fs.rs index e4334dc..a6647fb 100644 --- a/crates/libkos/src/fs.rs +++ b/crates/libkos/src/fs.rs @@ -25,6 +25,8 @@ pub enum FsError { PathTooLong, /// Outside this connection's scope, or beyond its rights. Denied, + /// Every connection storage has is in use. + Full, /// The server refused, or the call itself failed. Failed, } @@ -64,7 +66,7 @@ impl Storage { syscall::Received::Signal { .. } => None, }; 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 buf = mapped.get_mut(..size).ok_or(FsError::Failed)?; diff --git a/crates/libkos/src/spawn.rs b/crates/libkos/src/spawn.rs index 5772651..c6e94ae 100644 --- a/crates/libkos/src/spawn.rs +++ b/crates/libkos/src/spawn.rs @@ -16,6 +16,8 @@ pub enum SpawnError { NotFound, /// More than what is left of this process's spawn allowance. OverAllowance, + /// Storage had no connection free to load it through. + StorageBusy, Failed, } @@ -44,6 +46,7 @@ pub fn spawn(init: CapSlot, binary: CapSlot, limits: Limits) -> Result Err(SpawnError::NotFound), (reply::OVER_ALLOWANCE, _) => Err(SpawnError::OverAllowance), + (reply::STORAGE_BUSY, _) => Err(SpawnError::StorageBusy), _ => Err(SpawnError::Failed), } } diff --git a/crates/servers/permission-manager/src/server.rs b/crates/servers/permission-manager/src/server.rs index 6468795..3f1b5dc 100644 --- a/crates/servers/permission-manager/src/server.rs +++ b/crates/servers/permission-manager/src/server.rs @@ -216,7 +216,7 @@ impl PermissionServer { let spawned = libkos::spawn::spawn(self.init, file, Limits { pages: plan.app.memory.pages(), ..PROGRAM }); let _ = libkos::fs::release(file); 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), })?; diff --git a/crates/servers/storage-server/src/encrypted.rs b/crates/servers/storage-server/src/encrypted.rs index 12904ad..b6dddaf 100644 --- a/crates/servers/storage-server/src/encrypted.rs +++ b/crates/servers/storage-server/src/encrypted.rs @@ -12,9 +12,7 @@ use filecrypt::{CHUNK, Drbg, HEADER, Header, Key, OVERHEAD, PLAIN_CHUNK, area_key, chunk_offset, chunks, plain_len}; use crate::filesystem::{DirEntry, FileHandle, FileSystem}; - -/// The inner file system's handles this layer can track. -const MAX_HANDLES: usize = 16; +use crate::slots::Slots; /// Where a path falls. #[derive(Debug, PartialEq, Eq)] @@ -66,7 +64,8 @@ pub struct Encrypted { user: [u8; USER_NAME], user_len: usize, rng: Option, - open: [Option; MAX_HANDLES], + /// Sealed files open, by the inner file system's handle. + open: Slots, } /// A user folder's name: 16 hex digits, or `guest`. @@ -74,7 +73,7 @@ pub const USER_NAME: usize = 16; impl Encrypted { 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 { @@ -114,10 +113,6 @@ impl Encrypted { } } - fn slot(&mut self, h: FileHandle) -> Option<&mut Option> { - self.open.get_mut(h.raw() as usize) - } - /// 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 { let last = index + 1 == chunks(size); @@ -153,10 +148,10 @@ impl FileSystem for Encrypted { } fn lock(&mut self) { - for (i, slot) in self.open.iter_mut().enumerate() { - if slot.take().is_some() { - self.inner.close(FileHandle::new(i as u16)); - } + let open: alloc::vec::Vec = self.open.iter().map(|(i, _)| i).collect(); + for i in open { + self.open.take(i); + self.inner.close(FileHandle::new(i as u16)); } self.master = None; self.rng = None; @@ -183,7 +178,7 @@ impl FileSystem for Encrypted { return None; }; 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) } @@ -203,12 +198,12 @@ impl FileSystem for Encrypted { let _ = self.inner.delete(path); 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) } fn read(&mut self, h: FileHandle, buf: &mut [u8]) -> Option { - 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 mut done = 0; let mut chunk = [0u8; PLAIN_CHUNK]; @@ -222,12 +217,12 @@ impl FileSystem for Encrypted { pos += take; } chunk.fill(0); - self.slot(h)?.as_mut()?.pos = pos; + self.open.get_mut(h.raw() as usize)?.pos = pos; Some(done) } fn write(&mut self, h: FileHandle, buf: &[u8]) -> Option { - 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 new_size = old_size.max(pos + buf.len()); let (old_last, new_last) = (chunks(old_size) - 1, chunks(new_size) - 1); @@ -253,34 +248,32 @@ impl FileSystem for Encrypted { } } 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.pos = pos + buf.len(); Some(buf.len()) } fn seek(&mut self, h: FileHandle, offset: usize) -> bool { - match self.slot(h) { - Some(Some(f)) if offset <= f.size => { + match self.open.get_mut(h.raw() as usize) { + Some(f) if offset <= f.size => { f.pos = offset; true } - Some(Some(_)) => false, - _ => self.inner.seek(h, offset), + Some(_) => false, + None => self.inner.seek(h, offset), } } fn close(&mut self, h: FileHandle) { - if let Some(slot) = self.slot(h) { - *slot = None; - } + self.open.take(h.raw() as usize); self.inner.close(h); } fn file_size(&mut self, h: FileHandle) -> Option { match self.open.get(h.raw() as usize) { - Some(Some(f)) => Some(f.size), - _ => self.inner.file_size(h), + Some(f) => Some(f.size), + None => self.inner.file_size(h), } } diff --git a/crates/servers/storage-server/src/fs/ext4/mod.rs b/crates/servers/storage-server/src/fs/ext4/mod.rs index b453293..1a7e61e 100644 --- a/crates/servers/storage-server/src/fs/ext4/mod.rs +++ b/crates/servers/storage-server/src/fs/ext4/mod.rs @@ -9,11 +9,11 @@ mod path; use port_block_device::BlockDevice; use crate::filesystem::{DirEntry, FileHandle, FileSystem}; +use crate::slots::Slots; use io::read_block; use superblock::Superblock; const ROOT_INODE: u32 = 2; -const MAX_OPEN_FILES: usize = 8; const EXTENT_MAGIC: u16 = 0xF30A; @@ -31,7 +31,8 @@ pub struct Ext4 { device: B, sb: Superblock, gdt_block: u32, - open_files: [Option; MAX_OPEN_FILES], + /// Files open, by handle: as many as storage's memory holds. + open_files: Slots, } impl Ext4 { @@ -47,7 +48,7 @@ impl Ext4 { device, sb, gdt_block, - open_files: [const { None }; MAX_OPEN_FILES], + open_files: Slots::new(), }) } @@ -86,7 +87,7 @@ impl FileSystem for Ext4 { if !inode.is_file() { return None; } 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, size: inode.size as usize, offset: 0, @@ -96,8 +97,7 @@ impl FileSystem for Ext4 { } fn read(&mut self, handle: FileHandle, buf: &mut [u8]) -> Option { - if handle.raw() as usize >= MAX_OPEN_FILES { return None; } - let file = *self.open_files[handle.raw() as usize].as_ref()?; + let file = *self.open_files.get(handle.raw() as usize)?; if file.offset >= file.size { return Some(0); } // As much as `buf` holds, across blocks: a short read means the end. @@ -131,15 +131,14 @@ impl FileSystem for Ext4 { 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; } Some(done) } fn write(&mut self, handle: FileHandle, buf: &[u8]) -> Option { - if handle.raw() as usize >= MAX_OPEN_FILES { return None; } - let file = *self.open_files[handle.raw() as usize].as_ref()?; + let file = *self.open_files.get(handle.raw() as usize)?; let bs = self.sb.block_size; let mut written = 0; let mut offset = file.offset; @@ -186,7 +185,7 @@ impl FileSystem for Ext4 { } 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.size = size; f.block_data = block_data; @@ -205,8 +204,7 @@ impl FileSystem for Ext4 { } fn close(&mut self, handle: FileHandle) { - if handle.raw() as usize >= MAX_OPEN_FILES { return; } - if let Some(file) = self.open_files[handle.raw() as usize].take() + if let Some(file) = self.open_files.take(handle.raw() as usize) && let Some(mut inode) = self.read_inode(file.inode_num) { inode.size = file.size as u64; inode.block_data = file.block_data; @@ -255,7 +253,7 @@ impl FileSystem for Ext4 { } 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, size: 0, offset: 0, @@ -297,18 +295,15 @@ impl FileSystem for Ext4 { } self.clear_inode(child_inode_num); self.free_inode(child_inode_num); - for slot in self.open_files.iter_mut() { - if let Some(f) = slot - && f.inode_num == child_inode_num { - *slot = None; - } + let gone: alloc::vec::Vec = self.open_files.iter().filter(|(_, f)| f.inode_num == child_inode_num).map(|(i, _)| i).collect(); + for i in gone { + self.open_files.take(i); } true } fn file_size(&mut self, handle: FileHandle) -> Option { - if handle.raw() as usize >= MAX_OPEN_FILES { return None; } - self.open_files[handle.raw() as usize].as_ref().map(|f| f.size) + self.open_files.get(handle.raw() as usize).map(|f| f.size) } fn mkdir(&mut self, path: &[u8]) -> bool { @@ -370,7 +365,7 @@ impl FileSystem for Ext4 { } 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 => { f.offset = offset; true diff --git a/crates/servers/storage-server/src/fs/ext4/path.rs b/crates/servers/storage-server/src/fs/ext4/path.rs index 5df04a1..35ec359 100644 --- a/crates/servers/storage-server/src/fs/ext4/path.rs +++ b/crates/servers/storage-server/src/fs/ext4/path.rs @@ -1,6 +1,6 @@ //! 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 port_block_device::BlockDevice; @@ -58,12 +58,7 @@ impl Ext4 { } pub(super) fn alloc_handle(&self) -> Option { - for i in 0..MAX_OPEN_FILES { - if self.open_files[i].is_none() { - return Some(FileHandle::new(i as u16)); - } - } - None + self.open_files.free().map(|i| FileHandle::new(i as u16)) } pub(super) fn resolve_parent<'a>(&mut self, path: &'a [u8]) -> Option<(u32, &'a [u8])> { diff --git a/crates/servers/storage-server/src/grants.rs b/crates/servers/storage-server/src/grants.rs index 144d7be..938c3b7 100644 --- a/crates/servers/storage-server/src/grants.rs +++ b/crates/servers/storage-server/src/grants.rs @@ -2,12 +2,12 @@ //! endpoint, so a badge it does not know (never issued, or released) is //! no authority at all. +use alloc::vec; +use alloc::vec::Vec; use types::bounded::CapSlot; use crate::scope::Scope; -pub const MAX_GRANTS: usize = 16; - #[derive(Debug, Clone, Copy)] struct Grant { badge: usize, @@ -20,7 +20,8 @@ struct Grant { } pub struct Grants { - grants: [Option; MAX_GRANTS], + /// As many as storage's memory holds. + grants: Vec, /// Badges are never reused, so a released one stays dead. next_badge: usize, } @@ -28,7 +29,7 @@ pub struct Grants { impl Grants { pub const fn new() -> Self { Self { - grants: [None; MAX_GRANTS], + grants: Vec::new(), next_badge: 1, } } @@ -39,7 +40,6 @@ impl Grants { b => self .grants .iter() - .flatten() .find(|g| g.badge == b) .map(|g| g.scope), } @@ -53,11 +53,10 @@ impl Grants { scope: Scope, mint: impl FnOnce(usize) -> Option, ) -> Option { - let free = self.grants.iter_mut().find(|g| g.is_none())?; let badge = self.next_badge; let cap = mint(badge)?; self.next_badge += 1; - *free = Some(Grant { + self.grants.push(Grant { badge, scope, parent, @@ -66,14 +65,9 @@ impl Grants { Some(cap) } - /// The grants init made directly, into `out`; how many. - pub fn roots(&self, out: &mut [usize; MAX_GRANTS]) -> usize { - let mut n = 0; - for g in self.grants.iter().flatten().filter(|g| g.parent == 0) { - out[n] = g.badge; - n += 1; - } - n + /// The grants init made directly. + pub fn roots(&self) -> Vec { + self.grants.iter().filter(|g| g.parent == 0).map(|g| g.badge).collect() } /// Forgets `badge` and every grant narrowed from it, transitively, @@ -82,24 +76,19 @@ impl Grants { if badge == 0 { return; } - let mut doomed = [0usize; MAX_GRANTS]; - doomed[0] = badge; - let mut n = 1; + let mut doomed = vec![badge]; let mut i = 0; - while i < n { - let b = doomed[i]; + while let Some(&b) = doomed.get(i) { i += 1; - for g in &mut self.grants { - 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; - } - } + doomed.extend(self.grants.iter().filter(|g| g.parent == b).map(|g| g.badge)); } + self.grants.retain(|g| { + let dies = doomed.contains(&g.badge); + if dies { + gone(g.badge, g.cap); + } + !dies + }); } } diff --git a/crates/servers/storage-server/src/lib.rs b/crates/servers/storage-server/src/lib.rs index 7e3dca4..71ce590 100644 --- a/crates/servers/storage-server/src/lib.rs +++ b/crates/servers/storage-server/src/lib.rs @@ -11,6 +11,7 @@ pub mod grants; pub mod protocol; pub mod scope; pub mod server; +mod slots; #[cfg(test)] #[path = "tests/mod.rs"] diff --git a/crates/servers/storage-server/src/protocol.rs b/crates/servers/storage-server/src/protocol.rs index 2e74761..8d53cb6 100644 --- a/crates/servers/storage-server/src/protocol.rs +++ b/crates/servers/storage-server/src/protocol.rs @@ -62,6 +62,8 @@ pub enum StorageResponse { /// Carries the new capability. Granted, Denied, + /// No connection free. + Full, } fn handle(word: usize) -> Option { @@ -143,6 +145,7 @@ impl Encode for StorageResponse { NotAttached => [reply::NOT_ATTACHED, 0, 0, 0], Granted => [reply::GRANTED, 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::GRANTED => Granted, reply::DENIED => Denied, + reply::FULL => Full, _ => return None, }) } diff --git a/crates/servers/storage-server/src/server.rs b/crates/servers/storage-server/src/server.rs index fee2569..ed0ca8c 100644 --- a/crates/servers/storage-server/src/server.rs +++ b/crates/servers/storage-server/src/server.rs @@ -10,9 +10,8 @@ use crate::scope::Scope; mod session; 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. -const MAX_HANDLES: usize = 16; + +use crate::slots::Slots; const MAX_LISTED: usize = 64; /// What the server needs from the kernel: regions for client buffers @@ -50,7 +49,8 @@ pub struct StorageServer { host: H, grants: Grants, clients: [Option; MAX_CLIENTS], - owners: [Option; MAX_HANDLES], + /// Who opened each handle, by handle. + owners: Slots, log: Option, /// 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. @@ -68,7 +68,7 @@ impl StorageServer { host, grants: Grants::new(), clients: [const { None }; MAX_CLIENTS], - owners: [None; MAX_HANDLES], + owners: Slots::new(), log: None, key: [None; 2], seed: [None; 2], @@ -144,7 +144,8 @@ impl StorageServer { ); } 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 { return (StorageResponse::Error, None); @@ -176,11 +177,10 @@ impl StorageServer { } = self; grants.release(badge, |gone, cap| { host.revoke(cap); - for (raw, owner) in owners.iter_mut().enumerate() { - if owner.is_some_and(|o| o.badge == gone) { - fs.close(FileHandle::new(raw as u16)); - *owner = None; - } + let theirs: alloc::vec::Vec = owners.iter().filter(|(_, o)| o.badge == gone).map(|(raw, _)| raw).collect(); + for raw in theirs { + fs.close(FileHandle::new(raw as u16)); + owners.take(raw); } for c in clients .iter_mut() @@ -198,7 +198,7 @@ impl StorageServer { } fn owner(&self, handle: FileHandle) -> Option { - 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`. @@ -269,18 +269,11 @@ impl StorageServer { resolved => resolved.flatten(), }; let path = disk.as_ref().map_or(&[][..], |p| p.as_bytes()); - let opened = |owners: &mut [Option; MAX_HANDLES], h: Option| match h { - Some(h) => match owners.get_mut(h.raw() as usize) { - Some(owner) => { - *owner = Some(Owner { - badge, - write: scope.write, - }); - R::Handle(h) - } - // A handle the table cannot track is one we cannot police. - None => R::Error, - }, + let opened = |owners: &mut Slots, h: Option| match h { + Some(h) => { + owners.put(h.raw() as usize, Owner { badge, write: scope.write }); + R::Handle(h) + } None => R::NotFound, }; let response = match request { @@ -311,9 +304,7 @@ impl StorageServer { }, Close { handle } => { fs.close(handle); - if let Some(owner) = owners.get_mut(handle.raw() as usize) { - *owner = None; - } + owners.take(handle.raw() as usize); R::Ok } FileSize { handle } => match fs.file_size(handle) { diff --git a/crates/servers/storage-server/src/server/session.rs b/crates/servers/storage-server/src/server/session.rs index da8549a..49ceca8 100644 --- a/crates/servers/storage-server/src/server/session.rs +++ b/crates/servers/storage-server/src/server/session.rs @@ -6,7 +6,6 @@ use types::user::UserFolder; use super::{Host, StorageServer}; use crate::filesystem::{FileHandle, FileSystem}; -use crate::grants::MAX_GRANTS; use crate::protocol::{MAX_PATH, StorageResponse}; impl StorageServer { @@ -46,15 +45,13 @@ impl StorageServer { if badge != 0 { return StorageResponse::Error; } - let mut roots = [0usize; MAX_GRANTS]; - let n = self.grants.roots(&mut roots); - for &root in &roots[..n] { + for root in self.grants.roots() { self.release(root); } - for (raw, owner) in self.owners.iter_mut().enumerate() { - if owner.take().is_some() { - self.fs.close(FileHandle::new(raw as u16)); - } + let open: alloc::vec::Vec = self.owners.iter().map(|(raw, _)| raw).collect(); + for raw in open { + self.owners.take(raw); + self.fs.close(FileHandle::new(raw as u16)); } self.fs.lock(); self.key = [None; 2]; diff --git a/crates/servers/storage-server/src/slots.rs b/crates/servers/storage-server/src/slots.rs new file mode 100644 index 0000000..872d5b9 --- /dev/null +++ b/crates/servers/storage-server/src/slots.rs @@ -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(Vec>); + +impl Slots { + 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 { + 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 { + self.0.get_mut(index)?.take() + } + + /// Every entry, with its index. + pub fn iter(&self) -> impl Iterator { + self.0.iter().enumerate().filter_map(|(i, v)| v.as_ref().map(|v| (i, v))) + } +} + +impl Default for Slots { + fn default() -> Self { + Self::new() + } +} diff --git a/crates/servers/storage-server/src/tests/ext4.rs b/crates/servers/storage-server/src/tests/ext4.rs index bde2f75..579b8a6 100644 --- a/crates/servers/storage-server/src/tests/ext4.rs +++ b/crates/servers/storage-server/src/tests/ext4.rs @@ -595,3 +595,18 @@ fn delete_nonempty_dir_returns_false() { let count = fs.list_dir(b"parent", &mut entries).unwrap(); 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)); +} diff --git a/crates/servers/storage-server/src/tests/server.rs b/crates/servers/storage-server/src/tests/server.rs index 4cf74d6..09f347d 100644 --- a/crates/servers/storage-server/src/tests/server.rs +++ b/crates/servers/storage-server/src/tests/server.rs @@ -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"), 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 = (0..20).map(|_| root.grant(&mut s, b"", true)).collect(); + let parent = Client::attach(&mut s, siblings[0]); + let children: Vec = (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 = (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); +} diff --git a/crates/types/src/bounded.rs b/crates/types/src/bounded.rs index a68200e..bfea8fb 100644 --- a/crates/types/src/bounded.rs +++ b/crates/types/src/bounded.rs @@ -1,7 +1,8 @@ use crate::macros::bounded; // 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); // A caller a server thread saved to answer later; a thread keeps up to 8. bounded!(SavedReply, u8, 0, 7); diff --git a/crates/types/src/spawn.rs b/crates/types/src/spawn.rs index b0b7ce4..22fc14d 100644 --- a/crates/types/src/spawn.rs +++ b/crates/types/src/spawn.rs @@ -43,6 +43,8 @@ pub mod reply { /// The limits do not fit in what is left of the client's allowance. pub const OVER_ALLOWANCE: usize = 2; 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 diff --git a/crates/types/src/storage.rs b/crates/types/src/storage.rs index 87bfbd0..4850d24 100644 --- a/crates/types/src/storage.rs +++ b/crates/types/src/storage.rs @@ -75,4 +75,6 @@ pub mod reply { pub const GRANTED: usize = 11; /// Outside the caller's scope, beyond its rights, or no grant at all. pub const DENIED: usize = 12; + /// Every connection is in use (`MAX_CLIENTS`): try again later. + pub const FULL: usize = 13; }