feat(userspace): shell runs programs (ls, cat) via spawn w/ terminal+storage+args page; ext4 holes read as zeros; storage re-attach same buffer

This commit is contained in:
2026-10-27 17:22:08 +01:00
parent 0fd568cdad
commit c603a578c0
22 changed files with 396 additions and 66 deletions

View File

@@ -106,9 +106,15 @@ impl<B: BlockDevice> FileSystem for Ext4<B> {
let mut block_buf = [0u8; 4096];
while done < to_read {
let offset = file.offset + done;
let phys_block = self.map_logical_block(&inode, (offset / bs) as u32)?;
if !read_block(&mut self.device, phys_block, self.sb.sectors_per_block(), &mut block_buf) {
return None;
// A block no extent maps is a hole (mkfs leaves zero blocks
// out): it reads as zeros.
match self.map_logical_block(&inode, (offset / bs) as u32) {
Some(phys_block) => {
if !read_block(&mut self.device, phys_block, self.sb.sectors_per_block(), &mut block_buf) {
return None;
}
}
None => block_buf.fill(0),
}
let byte_in_block = offset % bs;
let chunk = (to_read - done).min(bs - byte_in_block);

View File

@@ -23,6 +23,8 @@ pub trait Buffers {
struct Client {
badge: usize,
buf: &'static mut [u8],
/// What maps `buf`, sent again when the same client attaches again.
cap: Option<CapSlot>,
}
pub struct StorageServer<F: FileSystem, B: Buffers> {
@@ -77,9 +79,11 @@ impl<F: FileSystem, B: Buffers> StorageServer<F, B> {
self.clients.iter_mut().flatten().find(|c| c.badge == badge)
}
/// A client attaching again gets its buffer again: the programs a shell
/// runs one after another share its badge.
fn attach(&mut self, badge: usize) -> (StorageResponse, Option<CapSlot>) {
if self.client(badge).is_some() {
return (StorageResponse::Error, None);
if let Some(c) = self.client(badge) {
return (StorageResponse::Attached { size: c.buf.len() }, c.cap);
}
let Some(index) = self.clients.iter().position(Option::is_none) else {
return (StorageResponse::Error, None);
@@ -89,7 +93,7 @@ impl<F: FileSystem, B: Buffers> StorageServer<F, B> {
};
let size = buf.len();
if let Some(slot) = self.clients.get_mut(index) {
*slot = Some(Client { badge, buf });
*slot = Some(Client { badge, buf, cap });
}
(StorageResponse::Attached { size }, cap)
}

View File

@@ -170,3 +170,16 @@ fn one_read_fills_the_buffer_across_blocks() {
assert_eq!(fs.read(h, &mut buf), Some(total - 10_000));
assert_eq!(fs.read(h, &mut buf), Some(0));
}
#[test]
fn holes_read_as_zeros() {
let dir = scratch("sparse");
// Data, then a zero block mkfs stores as a hole, then data again.
let mut data = std::vec![b'x'; 4096];
data.extend(std::vec![0u8; 4096]);
data.extend(b"end");
std::fs::write(dir.join("root/sparse.bin"), &data).unwrap();
let Some(img) = mkfs(&dir, MKFS_FEATURES) else { return };
let mut fs = Ext4::mount(FileDisk(img)).expect("mount");
assert_eq!(read_all(&mut fs, b"sparse.bin"), data);
}

View File

@@ -67,10 +67,14 @@ fn nothing_works_before_attaching() {
}
#[test]
fn a_client_attaches_once() {
fn attaching_again_hands_back_the_same_buffer() {
let mut s = server();
Client::attach(&mut s, 0);
assert_eq!(call(&mut s, 0, StorageRequest::Attach).0, StorageResponse::Error);
let (first, cap) = call(&mut s, 0, StorageRequest::Attach);
assert_eq!(call(&mut s, 0, StorageRequest::Attach), (first, cap));
// Not a new client: the other seven places are still free.
for badge in 1..8 {
assert!(matches!(call(&mut s, badge, StorageRequest::Attach).0, StorageResponse::Attached { .. }));
}
}
#[test]