From b4390b7b6dfa8a3483c29a7f458f4ada34201195 Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Tue, 27 Oct 2026 13:21:53 +0100 Subject: [PATCH] refactor(userspace): split syscall/fat16/ext4 by concern; trim comments --- crates/apps/smptest/src/main.rs | 12 +- crates/drivers/block/virtio/src/lib.rs | 5 +- crates/drivers/framebuffer/virtio/src/lib.rs | 6 +- crates/drivers/input/virtio/src/lib.rs | 6 +- crates/init/src/devices.rs | 8 +- crates/init/src/selftest.rs | 20 +- crates/libkos/src/device.rs | 7 +- crates/libkos/src/syscall.rs | 346 ------------ crates/libkos/src/syscall/caps.rs | 51 ++ crates/libkos/src/syscall/ipc.rs | 94 ++++ crates/libkos/src/syscall/memory.rs | 65 +++ crates/libkos/src/syscall/mod.rs | 42 ++ crates/libkos/src/syscall/notify.rs | 58 ++ crates/libkos/src/syscall/process.rs | 74 +++ .../servers/storage-server/src/fs/ext4/dir.rs | 6 - .../storage-server/src/fs/ext4/extent.rs | 99 ++++ .../storage-server/src/fs/ext4/inode.rs | 136 +++++ .../servers/storage-server/src/fs/ext4/mod.rs | 298 +--------- .../storage-server/src/fs/ext4/path.rs | 80 +++ crates/servers/storage-server/src/fs/fat16.rs | 532 ------------------ .../storage-server/src/fs/fat16/bpb.rs | 68 +++ .../storage-server/src/fs/fat16/dir.rs | 151 +++++ .../storage-server/src/fs/fat16/fat.rs | 64 +++ .../storage-server/src/fs/fat16/mod.rs | 275 +++++++++ crates/servers/storage-server/src/protocol.rs | 9 +- 25 files changed, 1283 insertions(+), 1229 deletions(-) delete mode 100644 crates/libkos/src/syscall.rs create mode 100644 crates/libkos/src/syscall/caps.rs create mode 100644 crates/libkos/src/syscall/ipc.rs create mode 100644 crates/libkos/src/syscall/memory.rs create mode 100644 crates/libkos/src/syscall/mod.rs create mode 100644 crates/libkos/src/syscall/notify.rs create mode 100644 crates/libkos/src/syscall/process.rs create mode 100644 crates/servers/storage-server/src/fs/ext4/extent.rs create mode 100644 crates/servers/storage-server/src/fs/ext4/inode.rs create mode 100644 crates/servers/storage-server/src/fs/ext4/path.rs delete mode 100644 crates/servers/storage-server/src/fs/fat16.rs create mode 100644 crates/servers/storage-server/src/fs/fat16/bpb.rs create mode 100644 crates/servers/storage-server/src/fs/fat16/dir.rs create mode 100644 crates/servers/storage-server/src/fs/fat16/fat.rs create mode 100644 crates/servers/storage-server/src/fs/fat16/mod.rs diff --git a/crates/apps/smptest/src/main.rs b/crates/apps/smptest/src/main.rs index 454096e..92da515 100644 --- a/crates/apps/smptest/src/main.rs +++ b/crates/apps/smptest/src/main.rs @@ -1,7 +1,5 @@ -//! The spinner half of init's SMP self-test (see init's `selftest`): -//! maps the counter page it was given in slot 0 and runs one thread per -//! hart, each incrementing its own counter for ever. Init watches the -//! counters through its own mapping of the same page. +//! Spinner half of init's SMP self-test: one thread per hart, each +//! incrementing its own counter in the page given in slot 0. #![no_std] #![no_main] @@ -14,8 +12,7 @@ use types::address::{EntryPoint, StackPointer, VirtualAddress}; use types::bounded::{CapSlot, PageCount}; use types::mapping::PagePerms; -/// Where the counter page is mapped. Layout (words): 0 the next free -/// counter index, 1..=THREADS the counters. +/// Counter page layout (words): 0 next free counter index, 1..=THREADS counters. const COUNTERS_VA: usize = 0x0100_0000; const THREADS: usize = 4; @@ -41,8 +38,7 @@ extern "C" fn spin() -> ! { let word = |i: usize| unsafe { &*((COUNTERS_VA + i * 8) as *const AtomicUsize) }; let mine = 1 + word(0).fetch_add(1, Ordering::Relaxed) % THREADS; loop { - // Yield now and then: a spinner sharing init's hart would - // otherwise starve it until the next (1 s) timer tick. + // Yield so a spinner sharing init's hart cannot starve it. for _ in 0..1024 { word(mine).fetch_add(1, Ordering::Relaxed); } diff --git a/crates/drivers/block/virtio/src/lib.rs b/crates/drivers/block/virtio/src/lib.rs index 6f03388..5d9c520 100644 --- a/crates/drivers/block/virtio/src/lib.rs +++ b/crates/drivers/block/virtio/src/lib.rs @@ -29,9 +29,8 @@ pub struct VirtioBlockDevice { } impl VirtioBlockDevice { - /// Takes over the block device whose MMIO region `mmio` names: maps - /// it, allocates the queue and request buffers, and resets the device - /// onto them. `None` if any step fails or it is not a block device. + /// Takes over the virtio block device behind `mmio`. `None` if it is + /// not one or any step fails. pub fn new(mmio: types::bounded::CapSlot) -> Option { libkos::device::map_mmio(mmio, MMIO_BASE).ok()?; let regs = UserMmio; diff --git a/crates/drivers/framebuffer/virtio/src/lib.rs b/crates/drivers/framebuffer/virtio/src/lib.rs index ee1ed82..848857c 100644 --- a/crates/drivers/framebuffer/virtio/src/lib.rs +++ b/crates/drivers/framebuffer/virtio/src/lib.rs @@ -37,10 +37,8 @@ pub struct VirtioFramebuffer { } impl VirtioFramebuffer { - /// Takes over the GPU whose MMIO region `mmio` names: maps it, sets up - /// the control queue and command buffer, asks for the display size, - /// allocates a framebuffer and makes it the scanout. `None` if it is - /// not a virtio GPU or any step fails. + /// Takes over the virtio GPU behind `mmio` and makes a framebuffer its + /// scanout. `None` if it is not a GPU or any step fails. pub fn try_new(mmio: types::bounded::CapSlot) -> Option { libkos::device::map_mmio(mmio, GPU_MMIO_VA).ok()?; let regs = GpuMmio; diff --git a/crates/drivers/input/virtio/src/lib.rs b/crates/drivers/input/virtio/src/lib.rs index b7d6051..9b3d02c 100644 --- a/crates/drivers/input/virtio/src/lib.rs +++ b/crates/drivers/input/virtio/src/lib.rs @@ -31,10 +31,8 @@ pub struct VirtioInputDevice { } impl VirtioInputDevice { - /// Takes over the input device whose MMIO region `mmio` names: maps it, - /// allocates the queue and event buffers, resets the device onto them - /// and hands it every buffer. `None` if it is not a virtio input - /// device or a step fails. + /// Takes over the virtio input device behind `mmio`. `None` if it is + /// not one or any step fails. pub fn try_new(mmio: types::bounded::CapSlot) -> Option { libkos::device::map_mmio(mmio, INPUT_MMIO_VA).ok()?; let regs = MmioInput; diff --git a/crates/init/src/devices.rs b/crates/init/src/devices.rs index b39f796..a48eed4 100644 --- a/crates/init/src/devices.rs +++ b/crates/init/src/devices.rs @@ -1,7 +1,5 @@ -//! Which driver gets which device. Boot gave init a capability to every -//! board device and a page saying where they are (`types::bootinfo`); init -//! asks each virtio slot what it is and hands the disk, the GPU and the -//! keyboard (or, without one, the UART) to their drivers. +//! Finds the board's devices (`types::bootinfo`): disk, GPU, and keyboard +//! (or the UART when there is none). use hw_virtio::mmio::{ DEVICE_ID_BLOCK, DEVICE_ID_GPU, DEVICE_ID_INPUT, MAGIC, REG_DEVICE_ID, REG_MAGIC, @@ -84,7 +82,7 @@ pub fn discover() -> Devices { }, _ => continue, }; - // The first device of each kind wins. + // First device of each kind wins. if place.mmio.is_none() { *place = dev; } diff --git a/crates/init/src/selftest.rs b/crates/init/src/selftest.rs index 50db340..fc00b4d 100644 --- a/crates/init/src/selftest.rs +++ b/crates/init/src/selftest.rs @@ -1,13 +1,5 @@ -//! SMP self-test, run only when the disk carries `smptest.elf` (the test -//! image built by `make test-qemu`). It checks on real harts what host -//! tests cannot: that unmapping a page and killing a process take effect -//! on every other hart before the call returns. -//! -//! A spinner process runs one thread per hart, each incrementing its own -//! counter in a page init also maps. Init then cuts the spinner off -//! (revoking its copy of the page, or killing it) and requires that no -//! counter moves afterwards: a stale TLB entry or a thread left running -//! on another hart would keep counting. +//! SMP self-test, run when the disk carries `smptest.elf`: after revoking a +//! spinner's page or killing it, no spinner counter may move on any hart. use core::sync::atomic::{AtomicUsize, Ordering}; use libkos::fs::Storage; @@ -17,7 +9,6 @@ use types::bounded::{CapSlot, PageCount}; use types::mapping::PagePerms; use types::permission::{Permission, PermissionSet}; -/// Where init sees the counter page; same layout as the spinner's. const OBSERVE_VA: usize = 0x0140_0000; const THREADS: usize = 4; /// QEMU virt's timebase is 10 MHz. @@ -79,9 +70,7 @@ fn run_phases(storage: &mut Storage, scratch: &mut [u8]) -> Result<(), &'static syscall::map_region(page, VirtualAddress::from_raw(OBSERVE_VA), PagePerms::ReadWrite) .map_err(|_| "cannot map the counter page")?; - // 1. Unmapping: revoking the spinner's copy of the page must unmap it - // on every hart before Revoke returns. The spinner then faults, - // which kills it from whichever hart faults first. + // Revoke must unmap the page on every hart; the spinner then faults and dies. let spinner = start_spinner(storage, scratch, page)?; if !syscall::revoke(page) { return Err("revoke failed"); @@ -92,8 +81,7 @@ fn run_phases(storage: &mut Storage, scratch: &mut [u8]) -> Result<(), &'static } libkos::println!("smptest: revoke stops every hart: ok"); - // 2. Killing: a process whose threads are running on other harts must - // stop everywhere before Kill returns. + // Kill must stop threads running on other harts before it returns. let spinner = start_spinner(storage, scratch, page)?; if !syscall::kill(spinner) { return Err("kill failed"); diff --git a/crates/libkos/src/device.rs b/crates/libkos/src/device.rs index b628eab..6c27620 100644 --- a/crates/libkos/src/device.rs +++ b/crates/libkos/src/device.rs @@ -1,8 +1,5 @@ -//! Handing devices to drivers. Init holds every device's capabilities -//! (see `types::bootinfo`) and gives each driver its own over the driver's -//! endpoint, one capability per `Call`; the driver collects them before it -//! starts serving. Drivers then map their MMIO and allocate DMA memory -//! themselves; nothing is set up for them behind their back. +//! Init hands each driver its device's capabilities, one per `Call` on the +//! driver's endpoint; drivers map MMIO and allocate DMA themselves. use crate::syscall::{self, Received, Result}; use types::address::VirtualAddress; diff --git a/crates/libkos/src/syscall.rs b/crates/libkos/src/syscall.rs deleted file mode 100644 index 0156ef2..0000000 --- a/crates/libkos/src/syscall.rs +++ /dev/null @@ -1,346 +0,0 @@ -//! Thin wrappers over the kernel's register ABI (docs/ipc.md). Every call -//! returns a status in `a0`; results follow in `a1..`. - -use crate::arch; -use types::address::{EntryPoint, StackPointer, VirtualAddress}; -use types::badge::Badge; -use types::bounded::{CapSlot, PageCount}; -use types::id::ProcessId; -use types::mapping::PagePerms; -use types::permission::PermissionSet; -use types::syscall_abi::*; -use types::time::Ticks; - -pub use types::syscall_abi::ErrorCode as Error; - -pub type Result = core::result::Result; - -fn raw(nr: usize, args: [usize; 6]) -> Result<[usize; 8]> { - let r = arch::syscall(nr, args); - match r[0] { - 0 => Ok(r), - code => Err(Error::from_raw(code).unwrap_or(Error::InvalidArgument)), - } -} - -fn call0(nr: usize) -> Result<[usize; 8]> { - raw(nr, [0; 6]) -} - -fn slot_result(r: usize) -> Option { - reg_slot(r).flatten() -} - -/// What a receiving call got. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Received { - /// A `Send`. `badge` identifies the sender's capability. - Message { - badge: usize, - words: [usize; 4], - cap: Option, - }, - /// A `Call`; answer it with `reply` or `reply_recv`. - Call { - badge: usize, - words: [usize; 4], - cap: Option, - }, - /// Bits from a notification. - Signal { bits: usize }, -} - -impl Received { - fn decode(r: [usize; 8]) -> Result { - let d = regs_delivery(&r).ok_or(Error::InvalidArgument)?; - let (badge, words, cap) = (d.badge, d.words, d.cap); - Ok(match d.kind { - DeliveryKind::Message => Received::Message { badge, words, cap }, - DeliveryKind::Call => Received::Call { badge, words, cap }, - DeliveryKind::Signal => Received::Signal { bits: badge }, - }) - } - - pub fn words(&self) -> [usize; 4] { - match self { - Received::Message { words, .. } | Received::Call { words, .. } => *words, - Received::Signal { .. } => [0; 4], - } - } -} - -fn slot_of(r: Result<[usize; 8]>) -> Result { - r.and_then(|r| slot_result(r[1]).ok_or(Error::InvalidArgument)) -} - -// ---- threads, processes, time ---- - -pub fn exit() -> ! { - let _ = call0(SYS_EXIT); - loop {} -} - -pub fn exit_thread() -> ! { - let _ = call0(SYS_EXIT_THREAD); - loop {} -} - -pub fn yield_now() { - let _ = call0(SYS_YIELD); -} - -/// Stops the machine. Needs a SystemControl capability; without one the -/// call fails and this spins, since there is nothing sensible to return. -pub fn halt(control: CapSlot) -> ! { - let _ = raw(SYS_HALT, [control.value() as usize, 0, 0, 0, 0, 0]); - loop {} -} - -pub fn get_time() -> Ticks { - Ticks::from_raw(call0(SYS_GET_TIME).map_or(0, |r| r[1]) as u64) -} - -pub fn get_process_id() -> ProcessId { - ProcessId::from_raw(call0(SYS_GET_PROCESS_ID).map_or(0, |r| r[1]) as u16) -} - -pub fn create_process(create_cap: CapSlot) -> Option { - slot_of(raw( - SYS_CREATE_PROCESS, - [create_cap.value() as usize, 0, 0, 0, 0, 0], - )) - .ok() -} - -pub fn spawn_thread(entry: EntryPoint, stack: StackPointer) -> usize { - raw(SYS_SPAWN_THREAD, [entry.raw(), stack.raw(), 0, 0, 0, 0]).map_or(NONE, |r| r[1]) -} - -pub fn spawn_thread_in(process_cap: CapSlot, entry: EntryPoint, stack: StackPointer) -> usize { - raw( - SYS_SPAWN_THREAD_IN, - [ - process_cap.value() as usize, - entry.raw(), - stack.raw(), - 0, - 0, - 0, - ], - ) - .map_or(NONE, |r| r[1]) -} - -pub fn kill(process_cap: CapSlot) -> bool { - raw(SYS_KILL, [process_cap.value() as usize, 0, 0, 0, 0, 0]).is_ok() -} - -// ---- memory ---- - -pub fn request_pages(count: PageCount) -> Option { - raw(SYS_REQUEST_PAGES, [count.value() as usize, 0, 0, 0, 0, 0]) - .ok() - .map(|r| VirtualAddress::from_raw(r[1])) -} - -pub fn map_page( - process_cap: CapSlot, - src_va: VirtualAddress, - dst_va: VirtualAddress, - executable: bool, -) -> bool { - raw( - SYS_MAP_PAGE, - [ - process_cap.value() as usize, - src_va.raw(), - dst_va.raw(), - executable as usize, - 0, - 0, - ], - ) - .is_ok() -} - -/// Allocates `pages` of physically contiguous shared memory, charged to -/// this process, and returns a capability to it. -pub fn create_region(pages: PageCount) -> Result { - slot_of(raw( - SYS_CREATE_REGION, - [pages.value() as usize, 0, 0, 0, 0, 0], - )) -} - -/// Maps the region behind `region` at `va`. The mapping lasts as long as -/// the capability: dropping or having it revoked unmaps it. -pub fn map_region(region: CapSlot, va: VirtualAddress, perms: PagePerms) -> Result<()> { - let perms = match perms { - PagePerms::ReadOnly => PERMS_READ_ONLY, - PagePerms::ReadWrite => PERMS_READ_WRITE, - PagePerms::ReadExecute => PERMS_READ_EXECUTE, - }; - raw( - SYS_MAP_REGION, - [region.value() as usize, va.raw(), perms, 0, 0, 0], - ) - .map(|_| ()) -} - -/// The physical base of the region behind `region`, for programming a -/// device's DMA. Pool regions are physically contiguous. -pub fn region_address(region: CapSlot) -> Result { - raw(SYS_REGION_ADDRESS, [region.value() as usize, 0, 0, 0, 0, 0]).map(|r| r[1]) -} - -// ---- capabilities ---- - -pub fn delegate(slot: CapSlot, to: CapSlot, permissions: PermissionSet) -> bool { - raw( - SYS_DELEGATE, - [ - slot.value() as usize, - to.value() as usize, - permissions.bits() as usize, - 0, - 0, - 0, - ], - ) - .is_ok() -} - -/// Copies an unbadged endpoint or notification capability with `badge` -/// and at most `permissions`. -pub fn mint(slot: CapSlot, badge: Badge, permissions: PermissionSet) -> Result { - slot_of(raw( - SYS_MINT, - [ - slot.value() as usize, - badge.raw(), - permissions.bits() as usize, - 0, - 0, - 0, - ], - )) -} - -/// Deletes every capability derived from `slot` (in any process) and keeps -/// `slot` itself. -pub fn revoke(slot: CapSlot) -> bool { - raw(SYS_REVOKE, [slot.value() as usize, 0, 0, 0, 0, 0]).is_ok() -} - -/// Removes a capability from this process's own table, freeing the slot. -pub fn drop_cap(slot: CapSlot) -> bool { - raw(SYS_DROP_CAP, [slot.value() as usize, 0, 0, 0, 0, 0]).is_ok() -} - -// ---- endpoints ---- - -pub fn create_endpoint() -> Option { - slot_of(call0(SYS_CREATE_ENDPOINT)).ok() -} - -pub fn send_msg(endpoint: CapSlot, words: [usize; 4], cap: Option) -> Result<()> { - raw( - SYS_SEND, - message_regs(endpoint.value() as usize, words, cap), - ) - .map(|_| ()) -} - -pub fn receive_msg(endpoint: CapSlot) -> Result { - raw(SYS_RECEIVE, [endpoint.value() as usize, 0, 0, 0, 0, 0]).and_then(Received::decode) -} - -/// Sends and waits for the reply. -pub fn call(endpoint: CapSlot, words: [usize; 4], cap: Option) -> Result { - raw( - SYS_CALL, - message_regs(endpoint.value() as usize, words, cap), - ) - .and_then(Received::decode) -} - -/// Calls a server with four words and returns its reply's words. Needs -/// only Execute on the endpoint; the reply comes back through the -/// kernel's reply slot, so it can never reach another client. -pub fn request(endpoint: CapSlot, words: [usize; 4]) -> Result<[usize; 4]> { - match call(endpoint, words, None)? { - Received::Signal { .. } => Err(Error::InvalidArgument), - reply => Ok(reply.words()), - } -} - -/// Answers the last `Call` this thread received. -pub fn reply(words: [usize; 4], cap: Option) -> Result<()> { - raw(SYS_REPLY, message_regs(0, words, cap)).map(|_| ()) -} - -/// Replies to the pending `Call` (if any), then receives the next message. -pub fn reply_recv(endpoint: CapSlot, words: [usize; 4], cap: Option) -> Result { - raw( - SYS_REPLY_RECV, - message_regs(endpoint.value() as usize, words, cap), - ) - .and_then(Received::decode) -} - -// ---- notifications and interrupts ---- - -pub fn create_notification() -> Result { - slot_of(call0(SYS_CREATE_NOTIFICATION)) -} - -pub fn signal(notification: CapSlot) -> Result<()> { - raw(SYS_SIGNAL, [notification.value() as usize, 0, 0, 0, 0, 0]).map(|_| ()) -} - -/// Blocks until the notification has bits, then returns and clears them. -pub fn wait(notification: CapSlot) -> Result { - raw(SYS_WAIT, [notification.value() as usize, 0, 0, 0, 0, 0]).map(|r| r[1]) -} - -/// Returns and clears the bits without blocking. -pub fn poll(notification: CapSlot) -> Result { - raw(SYS_POLL, [notification.value() as usize, 0, 0, 0, 0, 0]).map(|r| r[1]) -} - -/// After this, `receive` also returns when the notification is signalled. -pub fn bind_notification(notification: CapSlot) -> Result<()> { - raw( - SYS_BIND_NOTIFICATION, - [notification.value() as usize, 0, 0, 0, 0, 0], - ) - .map(|_| ()) -} - -/// Delivers interrupt `irq` as a signal on `notification`. -pub fn set_irq_notification(irq: CapSlot, notification: CapSlot) -> Result<()> { - raw( - SYS_SET_IRQ_NOTIFICATION, - [ - irq.value() as usize, - notification.value() as usize, - 0, - 0, - 0, - 0, - ], - ) - .map(|_| ()) -} - -pub fn ack_interrupt(irq: CapSlot) -> Result<()> { - raw( - SYS_ACKNOWLEDGE_INTERRUPT, - [irq.value() as usize, 0, 0, 0, 0, 0], - ) - .map(|_| ()) -} - -pub fn debug_putchar(byte: u8) { - let _ = raw(SYS_DEBUG_PUTCHAR, [byte as usize, 0, 0, 0, 0, 0]); -} diff --git a/crates/libkos/src/syscall/caps.rs b/crates/libkos/src/syscall/caps.rs new file mode 100644 index 0000000..148e7aa --- /dev/null +++ b/crates/libkos/src/syscall/caps.rs @@ -0,0 +1,51 @@ +//! Capability management. + +use super::{raw, slot_of, Result}; +use types::badge::Badge; +use types::bounded::CapSlot; +use types::permission::PermissionSet; +use types::syscall_abi::*; + +// ---- capabilities ---- + +pub fn delegate(slot: CapSlot, to: CapSlot, permissions: PermissionSet) -> bool { + raw( + SYS_DELEGATE, + [ + slot.value() as usize, + to.value() as usize, + permissions.bits() as usize, + 0, + 0, + 0, + ], + ) + .is_ok() +} + +/// Copies an unbadged endpoint or notification capability with `badge` +/// and at most `permissions`. +pub fn mint(slot: CapSlot, badge: Badge, permissions: PermissionSet) -> Result { + slot_of(raw( + SYS_MINT, + [ + slot.value() as usize, + badge.raw(), + permissions.bits() as usize, + 0, + 0, + 0, + ], + )) +} + +/// Deletes every capability derived from `slot` (in any process) and keeps +/// `slot` itself. +pub fn revoke(slot: CapSlot) -> bool { + raw(SYS_REVOKE, [slot.value() as usize, 0, 0, 0, 0, 0]).is_ok() +} + +/// Removes a capability from this process's own table, freeing the slot. +pub fn drop_cap(slot: CapSlot) -> bool { + raw(SYS_DROP_CAP, [slot.value() as usize, 0, 0, 0, 0, 0]).is_ok() +} diff --git a/crates/libkos/src/syscall/ipc.rs b/crates/libkos/src/syscall/ipc.rs new file mode 100644 index 0000000..9f997e3 --- /dev/null +++ b/crates/libkos/src/syscall/ipc.rs @@ -0,0 +1,94 @@ +//! Endpoints and message passing. + +use super::{call0, raw, slot_of, Error, Result}; +use types::bounded::CapSlot; +use types::syscall_abi::*; + +/// What a receiving call got. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Received { + /// A `Send`. `badge` identifies the sender's capability. + Message { + badge: usize, + words: [usize; 4], + cap: Option, + }, + /// A `Call`; answer it with `reply` or `reply_recv`. + Call { + badge: usize, + words: [usize; 4], + cap: Option, + }, + /// Bits from a notification. + Signal { bits: usize }, +} + +impl Received { + fn decode(r: [usize; 8]) -> Result { + let d = regs_delivery(&r).ok_or(Error::InvalidArgument)?; + let (badge, words, cap) = (d.badge, d.words, d.cap); + Ok(match d.kind { + DeliveryKind::Message => Received::Message { badge, words, cap }, + DeliveryKind::Call => Received::Call { badge, words, cap }, + DeliveryKind::Signal => Received::Signal { bits: badge }, + }) + } + + pub fn words(&self) -> [usize; 4] { + match self { + Received::Message { words, .. } | Received::Call { words, .. } => *words, + Received::Signal { .. } => [0; 4], + } + } +} + +// ---- endpoints ---- + +pub fn create_endpoint() -> Option { + slot_of(call0(SYS_CREATE_ENDPOINT)).ok() +} + +pub fn send_msg(endpoint: CapSlot, words: [usize; 4], cap: Option) -> Result<()> { + raw( + SYS_SEND, + message_regs(endpoint.value() as usize, words, cap), + ) + .map(|_| ()) +} + +pub fn receive_msg(endpoint: CapSlot) -> Result { + raw(SYS_RECEIVE, [endpoint.value() as usize, 0, 0, 0, 0, 0]).and_then(Received::decode) +} + +/// Sends and waits for the reply. +pub fn call(endpoint: CapSlot, words: [usize; 4], cap: Option) -> Result { + raw( + SYS_CALL, + message_regs(endpoint.value() as usize, words, cap), + ) + .and_then(Received::decode) +} + +/// Calls a server with four words and returns its reply's words. Needs +/// only Execute on the endpoint; the reply comes back through the +/// kernel's reply slot, so it can never reach another client. +pub fn request(endpoint: CapSlot, words: [usize; 4]) -> Result<[usize; 4]> { + match call(endpoint, words, None)? { + Received::Signal { .. } => Err(Error::InvalidArgument), + reply => Ok(reply.words()), + } +} + +/// Answers the last `Call` this thread received. +pub fn reply(words: [usize; 4], cap: Option) -> Result<()> { + raw(SYS_REPLY, message_regs(0, words, cap)).map(|_| ()) +} + +/// Replies to the pending `Call` (if any), then receives the next message. +pub fn reply_recv(endpoint: CapSlot, words: [usize; 4], cap: Option) -> Result { + raw( + SYS_REPLY_RECV, + message_regs(endpoint.value() as usize, words, cap), + ) + .and_then(Received::decode) +} diff --git a/crates/libkos/src/syscall/memory.rs b/crates/libkos/src/syscall/memory.rs new file mode 100644 index 0000000..25bda0e --- /dev/null +++ b/crates/libkos/src/syscall/memory.rs @@ -0,0 +1,65 @@ +//! Pages and shared regions. + +use super::{raw, slot_of, Result}; +use types::address::VirtualAddress; +use types::bounded::{CapSlot, PageCount}; +use types::mapping::PagePerms; +use types::syscall_abi::*; + +// ---- memory ---- + +pub fn request_pages(count: PageCount) -> Option { + raw(SYS_REQUEST_PAGES, [count.value() as usize, 0, 0, 0, 0, 0]) + .ok() + .map(|r| VirtualAddress::from_raw(r[1])) +} + +pub fn map_page( + process_cap: CapSlot, + src_va: VirtualAddress, + dst_va: VirtualAddress, + executable: bool, +) -> bool { + raw( + SYS_MAP_PAGE, + [ + process_cap.value() as usize, + src_va.raw(), + dst_va.raw(), + executable as usize, + 0, + 0, + ], + ) + .is_ok() +} + +/// Allocates `pages` of physically contiguous shared memory, charged to +/// this process, and returns a capability to it. +pub fn create_region(pages: PageCount) -> Result { + slot_of(raw( + SYS_CREATE_REGION, + [pages.value() as usize, 0, 0, 0, 0, 0], + )) +} + +/// Maps the region behind `region` at `va`. The mapping lasts as long as +/// the capability: dropping or having it revoked unmaps it. +pub fn map_region(region: CapSlot, va: VirtualAddress, perms: PagePerms) -> Result<()> { + let perms = match perms { + PagePerms::ReadOnly => PERMS_READ_ONLY, + PagePerms::ReadWrite => PERMS_READ_WRITE, + PagePerms::ReadExecute => PERMS_READ_EXECUTE, + }; + raw( + SYS_MAP_REGION, + [region.value() as usize, va.raw(), perms, 0, 0, 0], + ) + .map(|_| ()) +} + +/// The physical base of the region behind `region`, for programming a +/// device's DMA. Pool regions are physically contiguous. +pub fn region_address(region: CapSlot) -> Result { + raw(SYS_REGION_ADDRESS, [region.value() as usize, 0, 0, 0, 0, 0]).map(|r| r[1]) +} diff --git a/crates/libkos/src/syscall/mod.rs b/crates/libkos/src/syscall/mod.rs new file mode 100644 index 0000000..4b797cb --- /dev/null +++ b/crates/libkos/src/syscall/mod.rs @@ -0,0 +1,42 @@ +//! Thin wrappers over the kernel's register ABI (docs/ipc.md). Every call +//! returns a status in `a0`; results follow in `a1..`. + +mod caps; +mod ipc; +mod memory; +mod notify; +mod process; + +pub use caps::*; +pub use ipc::*; +pub use memory::*; +pub use notify::*; +pub use process::*; + +use crate::arch; +use types::bounded::CapSlot; +use types::syscall_abi::*; + +pub use types::syscall_abi::ErrorCode as Error; + +pub type Result = core::result::Result; + +fn raw(nr: usize, args: [usize; 6]) -> Result<[usize; 8]> { + let r = arch::syscall(nr, args); + match r[0] { + 0 => Ok(r), + code => Err(Error::from_raw(code).unwrap_or(Error::InvalidArgument)), + } +} + +fn call0(nr: usize) -> Result<[usize; 8]> { + raw(nr, [0; 6]) +} + +fn slot_result(r: usize) -> Option { + reg_slot(r).flatten() +} + +fn slot_of(r: Result<[usize; 8]>) -> Result { + r.and_then(|r| slot_result(r[1]).ok_or(Error::InvalidArgument)) +} diff --git a/crates/libkos/src/syscall/notify.rs b/crates/libkos/src/syscall/notify.rs new file mode 100644 index 0000000..b0b1f1b --- /dev/null +++ b/crates/libkos/src/syscall/notify.rs @@ -0,0 +1,58 @@ +//! Notifications and interrupts. + +use super::{call0, raw, slot_of, Result}; +use types::bounded::CapSlot; +use types::syscall_abi::*; + +// ---- notifications and interrupts ---- + +pub fn create_notification() -> Result { + slot_of(call0(SYS_CREATE_NOTIFICATION)) +} + +pub fn signal(notification: CapSlot) -> Result<()> { + raw(SYS_SIGNAL, [notification.value() as usize, 0, 0, 0, 0, 0]).map(|_| ()) +} + +/// Blocks until the notification has bits, then returns and clears them. +pub fn wait(notification: CapSlot) -> Result { + raw(SYS_WAIT, [notification.value() as usize, 0, 0, 0, 0, 0]).map(|r| r[1]) +} + +/// Returns and clears the bits without blocking. +pub fn poll(notification: CapSlot) -> Result { + raw(SYS_POLL, [notification.value() as usize, 0, 0, 0, 0, 0]).map(|r| r[1]) +} + +/// After this, `receive` also returns when the notification is signalled. +pub fn bind_notification(notification: CapSlot) -> Result<()> { + raw( + SYS_BIND_NOTIFICATION, + [notification.value() as usize, 0, 0, 0, 0, 0], + ) + .map(|_| ()) +} + +/// Delivers interrupt `irq` as a signal on `notification`. +pub fn set_irq_notification(irq: CapSlot, notification: CapSlot) -> Result<()> { + raw( + SYS_SET_IRQ_NOTIFICATION, + [ + irq.value() as usize, + notification.value() as usize, + 0, + 0, + 0, + 0, + ], + ) + .map(|_| ()) +} + +pub fn ack_interrupt(irq: CapSlot) -> Result<()> { + raw( + SYS_ACKNOWLEDGE_INTERRUPT, + [irq.value() as usize, 0, 0, 0, 0, 0], + ) + .map(|_| ()) +} diff --git a/crates/libkos/src/syscall/process.rs b/crates/libkos/src/syscall/process.rs new file mode 100644 index 0000000..7753418 --- /dev/null +++ b/crates/libkos/src/syscall/process.rs @@ -0,0 +1,74 @@ +//! Threads, processes, time and debug output. + +use super::{call0, raw, slot_of}; +use types::address::{EntryPoint, StackPointer}; +use types::bounded::CapSlot; +use types::id::ProcessId; +use types::syscall_abi::*; +use types::time::Ticks; + +// ---- threads, processes, time ---- + +pub fn exit() -> ! { + let _ = call0(SYS_EXIT); + loop {} +} + +pub fn exit_thread() -> ! { + let _ = call0(SYS_EXIT_THREAD); + loop {} +} + +pub fn yield_now() { + let _ = call0(SYS_YIELD); +} + +/// Stops the machine. Needs a SystemControl capability; without one the +/// call fails and this spins, since there is nothing sensible to return. +pub fn halt(control: CapSlot) -> ! { + let _ = raw(SYS_HALT, [control.value() as usize, 0, 0, 0, 0, 0]); + loop {} +} + +pub fn get_time() -> Ticks { + Ticks::from_raw(call0(SYS_GET_TIME).map_or(0, |r| r[1]) as u64) +} + +pub fn get_process_id() -> ProcessId { + ProcessId::from_raw(call0(SYS_GET_PROCESS_ID).map_or(0, |r| r[1]) as u16) +} + +pub fn create_process(create_cap: CapSlot) -> Option { + slot_of(raw( + SYS_CREATE_PROCESS, + [create_cap.value() as usize, 0, 0, 0, 0, 0], + )) + .ok() +} + +pub fn spawn_thread(entry: EntryPoint, stack: StackPointer) -> usize { + raw(SYS_SPAWN_THREAD, [entry.raw(), stack.raw(), 0, 0, 0, 0]).map_or(NONE, |r| r[1]) +} + +pub fn spawn_thread_in(process_cap: CapSlot, entry: EntryPoint, stack: StackPointer) -> usize { + raw( + SYS_SPAWN_THREAD_IN, + [ + process_cap.value() as usize, + entry.raw(), + stack.raw(), + 0, + 0, + 0, + ], + ) + .map_or(NONE, |r| r[1]) +} + +pub fn kill(process_cap: CapSlot) -> bool { + raw(SYS_KILL, [process_cap.value() as usize, 0, 0, 0, 0, 0]).is_ok() +} + +pub fn debug_putchar(byte: u8) { + let _ = raw(SYS_DEBUG_PUTCHAR, [byte as usize, 0, 0, 0, 0, 0]); +} diff --git a/crates/servers/storage-server/src/fs/ext4/dir.rs b/crates/servers/storage-server/src/fs/ext4/dir.rs index 254f08c..eec008c 100644 --- a/crates/servers/storage-server/src/fs/ext4/dir.rs +++ b/crates/servers/storage-server/src/fs/ext4/dir.rs @@ -96,9 +96,7 @@ pub fn add_entry( let free_space = rec_len - actual_size; if free_space >= new_rec_needed { if entry_inode != 0 { - // Shrink this entry to its actual size write_u16(&mut block_buf, pos + 4, actual_size as u16); - // Write new entry after it let new_pos = pos + actual_size; write_u32(&mut block_buf, new_pos, child_inode); write_u16(&mut block_buf, new_pos + 4, free_space as u16); @@ -106,9 +104,7 @@ pub fn add_entry( block_buf[new_pos + 7] = file_type; block_buf[new_pos + 8..new_pos + 8 + name.len()].copy_from_slice(name); } else { - // Reuse deleted entry write_u32(&mut block_buf, pos, child_inode); - // Keep rec_len as is block_buf[pos + 6] = name.len() as u8; block_buf[pos + 7] = file_type; block_buf[pos + 8..pos + 8 + name.len()].copy_from_slice(name); @@ -160,11 +156,9 @@ pub fn remove_entry( if entry_inode != 0 && name_len == name.len() { if &block_buf[pos + 8..pos + 8 + name_len] == name { if let Some(pp) = prev_pos { - // Merge with previous entry let prev_rec = read_u16(&block_buf, pp + 4) as usize; write_u16(&mut block_buf, pp + 4, (prev_rec + rec_len) as u16); } else { - // First entry — zero the inode write_u32(&mut block_buf, pos, 0); } write_block(device, phys, spb, &block_buf); diff --git a/crates/servers/storage-server/src/fs/ext4/extent.rs b/crates/servers/storage-server/src/fs/ext4/extent.rs new file mode 100644 index 0000000..76209f3 --- /dev/null +++ b/crates/servers/storage-server/src/fs/ext4/extent.rs @@ -0,0 +1,99 @@ +//! Extent tree (depth 0 only): block mapping and allocation. + +use super::io::{self, read_u16}; +use super::{Ext4, Extent, Inode, EXTENT_MAGIC}; +use port_block_device::BlockDevice; + +impl Ext4 { + pub(super) fn read_extents(&self, inode: &Inode) -> Option { + if !inode.uses_extents() { return None; } + let hdr = &inode.block_data; + let magic = read_u16(hdr, 0); + if magic != EXTENT_MAGIC { return None; } + let entries = read_u16(hdr, 2); + let depth = read_u16(hdr, 6); + if depth != 0 || entries == 0 { return None; } + Some(Extent::parse(&hdr[12..24])) + } + + pub(super) fn map_logical_block(&mut self, inode: &Inode, logical_block: u32) -> Option { + if !inode.uses_extents() { return None; } + let hdr = &inode.block_data; + let magic = read_u16(hdr, 0); + if magic != EXTENT_MAGIC { return None; } + let entries = read_u16(hdr, 2); + let depth = read_u16(hdr, 6); + if depth != 0 { + return None; + } + for i in 0..entries as usize { + let off = 12 + i * 12; + if off + 12 > hdr.len() { break; } + let ext = Extent::parse(&hdr[off..off + 12]); + if logical_block >= ext.logical_block + && logical_block < ext.logical_block + ext.length as u32 + { + return Some(ext.physical_block + (logical_block - ext.logical_block) as u64); + } + } + None + } + + pub(super) fn append_extent(block_data: &mut [u8], logical_block: u32, physical_block: u64, length: u16) -> bool { + let magic = read_u16(block_data, 0); + if magic != EXTENT_MAGIC { return false; } + let entries = read_u16(block_data, 2); + let max = read_u16(block_data, 4); + + if entries > 0 { + let last_off = 12 + (entries as usize - 1) * 12; + let last = Extent::parse(&block_data[last_off..last_off + 12]); + if last.physical_block + last.length as u64 == physical_block + && last.logical_block + last.length as u32 == logical_block + { + let new_len = last.length + length; + io::write_u16(block_data, last_off + 4, new_len); + return true; + } + } + + if entries >= max { return false; } + let off = 12 + entries as usize * 12; + if off + 12 > block_data.len() { return false; } + io::write_u32(block_data, off, logical_block); + io::write_u16(block_data, off + 4, length); + io::write_u16(block_data, off + 6, (physical_block >> 32) as u16); + io::write_u32(block_data, off + 8, physical_block as u32); + io::write_u16(block_data, 2, entries + 1); + true + } + + pub(super) fn count_extent_blocks(block_data: &[u8; 60]) -> u32 { + let magic = read_u16(block_data, 0); + if magic != EXTENT_MAGIC { return 0; } + let entries = read_u16(block_data, 2); + let mut total = 0u32; + for i in 0..entries as usize { + let off = 12 + i * 12; + if off + 12 > block_data.len() { break; } + total += read_u16(block_data, off + 4) as u32; + } + total + } + + pub(super) fn free_all_blocks(&mut self, inode: &Inode) { + if !inode.uses_extents() { return; } + let hdr = &inode.block_data; + let magic = read_u16(hdr, 0); + if magic != EXTENT_MAGIC { return; } + let entries = read_u16(hdr, 2); + for i in 0..entries as usize { + let off = 12 + i * 12; + if off + 12 > hdr.len() { break; } + let ext = Extent::parse(&hdr[off..off + 12]); + for b in 0..ext.length as u32 { + self.free_block(ext.physical_block as u32 + b); + } + } + } +} diff --git a/crates/servers/storage-server/src/fs/ext4/inode.rs b/crates/servers/storage-server/src/fs/ext4/inode.rs new file mode 100644 index 0000000..0429eb1 --- /dev/null +++ b/crates/servers/storage-server/src/fs/ext4/inode.rs @@ -0,0 +1,136 @@ +//! Inode and extent records, and inode table access. + +use super::bgd::BlockGroupDescriptor; +use super::io::{self, read_block, read_u16, read_u32}; +use super::{Ext4, EXTENT_MAGIC}; +use port_block_device::BlockDevice; + +pub struct Inode { + pub mode: u16, + pub size: u64, + pub flags: u32, + pub links_count: u16, + pub blocks_count: u32, + pub block_data: [u8; 60], +} + +impl Inode { + pub fn is_dir(&self) -> bool { self.mode & 0xF000 == 0x4000 } + pub fn is_file(&self) -> bool { self.mode & 0xF000 == 0x8000 } + pub fn uses_extents(&self) -> bool { self.flags & 0x80000 != 0 } +} + +#[derive(Clone, Copy)] +pub struct Extent { + pub logical_block: u32, + pub length: u16, + pub physical_block: u64, +} + +impl Extent { + pub fn parse(data: &[u8]) -> Self { + let start_hi = read_u16(data, 6) as u64; + let start_lo = read_u32(data, 8) as u64; + Self { + logical_block: read_u32(data, 0), + length: read_u16(data, 4), + physical_block: (start_hi << 32) | start_lo, + } + } +} + +impl Ext4 { + pub(super) fn read_inode(&mut self, inode_num: u32) -> Option { + let bgd = BlockGroupDescriptor::read( + &mut self.device, self.gdt_block, + self.sb.inode_block_group(inode_num), + self.sb.sectors_per_block(), + )?; + let index = self.sb.inode_index_in_group(inode_num); + + let inode_offset = (index as usize) * (self.sb.inode_size as usize); + let block_offset = inode_offset / self.sb.block_size; + let offset_in_block = inode_offset % self.sb.block_size; + + let mut block_buf = [0u8; 4096]; + read_block( + &mut self.device, + (bgd.inode_table + block_offset as u32) as u64, + self.sb.sectors_per_block(), + &mut block_buf, + ); + + let d = &block_buf[offset_in_block..]; + if d.len() < 128 { return None; } + + let size_lo = read_u32(d, 0x04) as u64; + let size_hi = read_u32(d, 0x6C) as u64; + let size = size_lo | (size_hi << 32); + + let mut block_data = [0u8; 60]; + block_data.copy_from_slice(&d[0x28..0x64]); + + Some(Inode { + mode: read_u16(d, 0x00), + size, + flags: read_u32(d, 0x20), + links_count: read_u16(d, 0x1A), + blocks_count: read_u32(d, 0x1C), + block_data, + }) + } + + pub(super) fn write_inode(&mut self, inode_num: u32, inode: &Inode) -> bool { + let bgd = match BlockGroupDescriptor::read( + &mut self.device, self.gdt_block, + self.sb.inode_block_group(inode_num), + self.sb.sectors_per_block(), + ) { + Some(b) => b, + None => return false, + }; + let index = self.sb.inode_index_in_group(inode_num); + let inode_offset = (index as usize) * (self.sb.inode_size as usize); + let block_offset = inode_offset / self.sb.block_size; + let offset_in_block = inode_offset % self.sb.block_size; + + let mut block_buf = [0u8; 4096]; + let block_num = (bgd.inode_table + block_offset as u32) as u64; + read_block(&mut self.device, block_num, self.sb.sectors_per_block(), &mut block_buf); + + let d = &mut block_buf[offset_in_block..]; + io::write_u16(d, 0x00, inode.mode); + io::write_u32(d, 0x04, inode.size as u32); + io::write_u16(d, 0x1A, inode.links_count); + io::write_u32(d, 0x1C, inode.blocks_count); + io::write_u32(d, 0x20, inode.flags); + d[0x28..0x64].copy_from_slice(&inode.block_data); + io::write_u32(d, 0x6C, (inode.size >> 32) as u32); + + io::write_block(&mut self.device, block_num, self.sb.sectors_per_block(), &block_buf) + } + + pub fn init_empty_inode(mode: u16) -> Inode { + let mut block_data = [0u8; 60]; + io::write_u16(&mut block_data, 0, EXTENT_MAGIC); + io::write_u16(&mut block_data, 2, 0); // entries = 0 + io::write_u16(&mut block_data, 4, 4); // max = 4 + io::write_u16(&mut block_data, 6, 0); // depth = 0 + Inode { + mode, + size: 0, + flags: 0x80000, + links_count: 1, + blocks_count: 0, + block_data, + } + } + + pub fn read_inode_pub(&mut self, inode_num: u32) -> Option { + self.read_inode(inode_num) + } + + pub fn write_inode_pub(&mut self, inode_num: u32, inode: &Inode) -> bool { + self.write_inode(inode_num, inode) + } +} diff --git a/crates/servers/storage-server/src/fs/ext4/mod.rs b/crates/servers/storage-server/src/fs/ext4/mod.rs index 1b536a4..57c49e0 100644 --- a/crates/servers/storage-server/src/fs/ext4/mod.rs +++ b/crates/servers/storage-server/src/fs/ext4/mod.rs @@ -3,50 +3,21 @@ pub mod superblock; pub mod bgd; pub mod bitmap; pub mod dir; +mod extent; +mod inode; +mod path; use port_block_device::BlockDevice; use crate::filesystem::{DirEntry, FileHandle, FileName, FileSystem}; -use io::{read_block, read_u32, read_u16}; +use io::read_block; use superblock::Superblock; -use bgd::BlockGroupDescriptor; const ROOT_INODE: u32 = 2; const MAX_OPEN_FILES: usize = 8; const EXTENT_MAGIC: u16 = 0xF30A; -pub struct Inode { - pub mode: u16, - pub size: u64, - pub flags: u32, - pub links_count: u16, - pub blocks_count: u32, - pub block_data: [u8; 60], -} -impl Inode { - pub fn is_dir(&self) -> bool { self.mode & 0xF000 == 0x4000 } - pub fn is_file(&self) -> bool { self.mode & 0xF000 == 0x8000 } - pub fn uses_extents(&self) -> bool { self.flags & 0x80000 != 0 } -} - -#[derive(Clone, Copy)] -pub struct Extent { - pub logical_block: u32, - pub length: u16, - pub physical_block: u64, -} - -impl Extent { - pub fn parse(data: &[u8]) -> Self { - let start_hi = read_u16(data, 6) as u64; - let start_lo = read_u32(data, 8) as u64; - Self { - logical_block: read_u32(data, 0), - length: read_u16(data, 4), - physical_block: (start_hi << 32) | start_lo, - } - } -} +pub use inode::{Extent, Inode}; #[derive(Clone, Copy)] struct OpenFile { @@ -80,265 +51,6 @@ impl Ext4 { open_files: [const { None }; MAX_OPEN_FILES], }) } - - fn read_inode(&mut self, inode_num: u32) -> Option { - let bgd = BlockGroupDescriptor::read( - &mut self.device, self.gdt_block, - self.sb.inode_block_group(inode_num), - self.sb.sectors_per_block(), - )?; - let index = self.sb.inode_index_in_group(inode_num); - - let inode_offset = (index as usize) * (self.sb.inode_size as usize); - let block_offset = inode_offset / self.sb.block_size; - let offset_in_block = inode_offset % self.sb.block_size; - - let mut block_buf = [0u8; 4096]; - read_block( - &mut self.device, - (bgd.inode_table + block_offset as u32) as u64, - self.sb.sectors_per_block(), - &mut block_buf, - ); - - let d = &block_buf[offset_in_block..]; - if d.len() < 128 { return None; } - - let size_lo = read_u32(d, 0x04) as u64; - let size_hi = read_u32(d, 0x6C) as u64; - let size = size_lo | (size_hi << 32); - - let mut block_data = [0u8; 60]; - block_data.copy_from_slice(&d[0x28..0x64]); - - Some(Inode { - mode: read_u16(d, 0x00), - size, - flags: read_u32(d, 0x20), - links_count: read_u16(d, 0x1A), - blocks_count: read_u32(d, 0x1C), - block_data, - }) - } - - fn write_inode(&mut self, inode_num: u32, inode: &Inode) -> bool { - let bgd = match BlockGroupDescriptor::read( - &mut self.device, self.gdt_block, - self.sb.inode_block_group(inode_num), - self.sb.sectors_per_block(), - ) { - Some(b) => b, - None => return false, - }; - let index = self.sb.inode_index_in_group(inode_num); - let inode_offset = (index as usize) * (self.sb.inode_size as usize); - let block_offset = inode_offset / self.sb.block_size; - let offset_in_block = inode_offset % self.sb.block_size; - - let mut block_buf = [0u8; 4096]; - let block_num = (bgd.inode_table + block_offset as u32) as u64; - read_block(&mut self.device, block_num, self.sb.sectors_per_block(), &mut block_buf); - - let d = &mut block_buf[offset_in_block..]; - io::write_u16(d, 0x00, inode.mode); - io::write_u32(d, 0x04, inode.size as u32); - io::write_u16(d, 0x1A, inode.links_count); - io::write_u32(d, 0x1C, inode.blocks_count); - io::write_u32(d, 0x20, inode.flags); - d[0x28..0x64].copy_from_slice(&inode.block_data); - io::write_u32(d, 0x6C, (inode.size >> 32) as u32); - - io::write_block(&mut self.device, block_num, self.sb.sectors_per_block(), &block_buf) - } - - pub fn init_empty_inode(mode: u16) -> Inode { - let mut block_data = [0u8; 60]; - io::write_u16(&mut block_data, 0, EXTENT_MAGIC); - io::write_u16(&mut block_data, 2, 0); // entries = 0 - io::write_u16(&mut block_data, 4, 4); // max = 4 - io::write_u16(&mut block_data, 6, 0); // depth = 0 - Inode { - mode, - size: 0, - flags: 0x80000, - links_count: 1, - blocks_count: 0, - block_data, - } - } - - pub fn read_inode_pub(&mut self, inode_num: u32) -> Option { - self.read_inode(inode_num) - } - - pub fn write_inode_pub(&mut self, inode_num: u32, inode: &Inode) -> bool { - self.write_inode(inode_num, inode) - } - - pub fn alloc_block(&mut self) -> Option { - let spb = self.sb.sectors_per_block(); - bitmap::alloc_block(&mut self.device, &mut self.sb, self.gdt_block, spb) - } - - pub fn free_block(&mut self, block: u32) { - let spb = self.sb.sectors_per_block(); - bitmap::free_block(&mut self.device, &mut self.sb, self.gdt_block, block, spb); - } - - pub fn alloc_inode(&mut self) -> Option { - let spb = self.sb.sectors_per_block(); - bitmap::alloc_inode(&mut self.device, &mut self.sb, self.gdt_block, spb) - } - - pub fn free_inode(&mut self, inode: u32) { - let spb = self.sb.sectors_per_block(); - bitmap::free_inode(&mut self.device, &mut self.sb, self.gdt_block, inode, spb); - } - - pub fn find_in_dir_pub(&mut self, dir_inode_num: u32, name: &[u8]) -> Option { - let inode = self.read_inode(dir_inode_num)?; - dir::find_entry(&mut self.device, &self.sb, self.gdt_block, &inode, name) - } - - pub fn add_dir_entry(&mut self, dir_inode_num: u32, name: &[u8], child_inode: u32, file_type: u8) -> bool { - let inode = match self.read_inode(dir_inode_num) { - Some(i) => i, - None => return false, - }; - dir::add_entry(&mut self.device, &self.sb, self.gdt_block, &inode, name, child_inode, file_type) - } - - pub fn remove_dir_entry(&mut self, dir_inode_num: u32, name: &[u8]) -> Option { - let inode = self.read_inode(dir_inode_num)?; - dir::remove_entry(&mut self.device, &self.sb, self.gdt_block, &inode, name) - } - - fn read_extents(&self, inode: &Inode) -> Option { - if !inode.uses_extents() { return None; } - let hdr = &inode.block_data; - let magic = read_u16(hdr, 0); - if magic != EXTENT_MAGIC { return None; } - let entries = read_u16(hdr, 2); - let depth = read_u16(hdr, 6); - if depth != 0 || entries == 0 { return None; } - Some(Extent::parse(&hdr[12..24])) - } - - fn map_logical_block(&mut self, inode: &Inode, logical_block: u32) -> Option { - if !inode.uses_extents() { return None; } - let hdr = &inode.block_data; - let magic = read_u16(hdr, 0); - if magic != EXTENT_MAGIC { return None; } - let entries = read_u16(hdr, 2); - let depth = read_u16(hdr, 6); - if depth != 0 { - return None; - } - for i in 0..entries as usize { - let off = 12 + i * 12; - if off + 12 > hdr.len() { break; } - let ext = Extent::parse(&hdr[off..off + 12]); - if logical_block >= ext.logical_block - && logical_block < ext.logical_block + ext.length as u32 - { - return Some(ext.physical_block + (logical_block - ext.logical_block) as u64); - } - } - None - } - - fn find_in_dir(&mut self, dir_inode: u32, name: &[u8]) -> Option { - let inode = self.read_inode(dir_inode)?; - dir::find_entry(&mut self.device, &self.sb, self.gdt_block, &inode, name) - } - - fn resolve_path(&mut self, path: &[u8]) -> Option { - let mut current = ROOT_INODE; - for component in path.split(|&b| b == b'/') { - if component.is_empty() { continue; } - current = self.find_in_dir(current, component)?; - } - Some(current) - } - - 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 - } - - fn resolve_parent<'a>(&mut self, path: &'a [u8]) -> Option<(u32, &'a [u8])> { - let path = if path.starts_with(b"/") { &path[1..] } else { path }; - if let Some(last_slash) = path.iter().rposition(|&b| b == b'/') { - let parent_path = &path[..last_slash]; - let filename = &path[last_slash + 1..]; - let parent_inode = self.resolve_path(parent_path)?; - Some((parent_inode, filename)) - } else { - Some((ROOT_INODE, path)) - } - } - - fn append_extent(block_data: &mut [u8], logical_block: u32, physical_block: u64, length: u16) -> bool { - let magic = read_u16(block_data, 0); - if magic != EXTENT_MAGIC { return false; } - let entries = read_u16(block_data, 2); - let max = read_u16(block_data, 4); - - if entries > 0 { - let last_off = 12 + (entries as usize - 1) * 12; - let last = Extent::parse(&block_data[last_off..last_off + 12]); - if last.physical_block + last.length as u64 == physical_block - && last.logical_block + last.length as u32 == logical_block - { - let new_len = last.length + length; - io::write_u16(block_data, last_off + 4, new_len); - return true; - } - } - - if entries >= max { return false; } - let off = 12 + entries as usize * 12; - if off + 12 > block_data.len() { return false; } - io::write_u32(block_data, off, logical_block); - io::write_u16(block_data, off + 4, length); - io::write_u16(block_data, off + 6, (physical_block >> 32) as u16); - io::write_u32(block_data, off + 8, physical_block as u32); - io::write_u16(block_data, 2, entries + 1); - true - } - - fn count_extent_blocks(block_data: &[u8; 60]) -> u32 { - let magic = read_u16(block_data, 0); - if magic != EXTENT_MAGIC { return 0; } - let entries = read_u16(block_data, 2); - let mut total = 0u32; - for i in 0..entries as usize { - let off = 12 + i * 12; - if off + 12 > block_data.len() { break; } - total += read_u16(block_data, off + 4) as u32; - } - total - } - - fn free_all_blocks(&mut self, inode: &Inode) { - if !inode.uses_extents() { return; } - let hdr = &inode.block_data; - let magic = read_u16(hdr, 0); - if magic != EXTENT_MAGIC { return; } - let entries = read_u16(hdr, 2); - for i in 0..entries as usize { - let off = 12 + i * 12; - if off + 12 > hdr.len() { break; } - let ext = Extent::parse(&hdr[off..off + 12]); - for b in 0..ext.length as u32 { - self.free_block(ext.physical_block as u32 + b); - } - } - } } impl FileSystem for Ext4 { diff --git a/crates/servers/storage-server/src/fs/ext4/path.rs b/crates/servers/storage-server/src/fs/ext4/path.rs new file mode 100644 index 0000000..66171dc --- /dev/null +++ b/crates/servers/storage-server/src/fs/ext4/path.rs @@ -0,0 +1,80 @@ +//! Path resolution, directory entry operations and allocation wrappers. + +use super::{bitmap, dir, Ext4, MAX_OPEN_FILES, ROOT_INODE}; +use crate::filesystem::FileHandle; +use port_block_device::BlockDevice; + +impl Ext4 { + pub fn alloc_block(&mut self) -> Option { + let spb = self.sb.sectors_per_block(); + bitmap::alloc_block(&mut self.device, &mut self.sb, self.gdt_block, spb) + } + + pub fn free_block(&mut self, block: u32) { + let spb = self.sb.sectors_per_block(); + bitmap::free_block(&mut self.device, &mut self.sb, self.gdt_block, block, spb); + } + + pub fn alloc_inode(&mut self) -> Option { + let spb = self.sb.sectors_per_block(); + bitmap::alloc_inode(&mut self.device, &mut self.sb, self.gdt_block, spb) + } + + pub fn free_inode(&mut self, inode: u32) { + let spb = self.sb.sectors_per_block(); + bitmap::free_inode(&mut self.device, &mut self.sb, self.gdt_block, inode, spb); + } + + pub fn find_in_dir_pub(&mut self, dir_inode_num: u32, name: &[u8]) -> Option { + let inode = self.read_inode(dir_inode_num)?; + dir::find_entry(&mut self.device, &self.sb, self.gdt_block, &inode, name) + } + + pub fn add_dir_entry(&mut self, dir_inode_num: u32, name: &[u8], child_inode: u32, file_type: u8) -> bool { + let inode = match self.read_inode(dir_inode_num) { + Some(i) => i, + None => return false, + }; + dir::add_entry(&mut self.device, &self.sb, self.gdt_block, &inode, name, child_inode, file_type) + } + + pub fn remove_dir_entry(&mut self, dir_inode_num: u32, name: &[u8]) -> Option { + let inode = self.read_inode(dir_inode_num)?; + dir::remove_entry(&mut self.device, &self.sb, self.gdt_block, &inode, name) + } + + pub(super) fn find_in_dir(&mut self, dir_inode: u32, name: &[u8]) -> Option { + let inode = self.read_inode(dir_inode)?; + dir::find_entry(&mut self.device, &self.sb, self.gdt_block, &inode, name) + } + + pub(super) fn resolve_path(&mut self, path: &[u8]) -> Option { + let mut current = ROOT_INODE; + for component in path.split(|&b| b == b'/') { + if component.is_empty() { continue; } + current = self.find_in_dir(current, component)?; + } + Some(current) + } + + 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 + } + + pub(super) fn resolve_parent<'a>(&mut self, path: &'a [u8]) -> Option<(u32, &'a [u8])> { + let path = if path.starts_with(b"/") { &path[1..] } else { path }; + if let Some(last_slash) = path.iter().rposition(|&b| b == b'/') { + let parent_path = &path[..last_slash]; + let filename = &path[last_slash + 1..]; + let parent_inode = self.resolve_path(parent_path)?; + Some((parent_inode, filename)) + } else { + Some((ROOT_INODE, path)) + } + } +} diff --git a/crates/servers/storage-server/src/fs/fat16.rs b/crates/servers/storage-server/src/fs/fat16.rs deleted file mode 100644 index bbd1237..0000000 --- a/crates/servers/storage-server/src/fs/fat16.rs +++ /dev/null @@ -1,532 +0,0 @@ -use crate::filesystem::{DirEntry, FileHandle, FileName, FileSystem}; -use port_block_device::{BlockDevice, SectorId}; - -const SECTOR_SIZE: usize = 512; -const MAX_OPEN_FILES: usize = 8; - -struct Bpb { - bytes_per_sector: u16, - sectors_per_cluster: u8, - reserved_sectors: u16, - num_fats: u8, - root_entry_count: u16, - total_sectors: u16, - sectors_per_fat: u16, -} - -impl Bpb { - /// Reads the BPB, refusing geometry the rest of this module cannot - /// work with: the disk is untrusted input like any client request. - fn parse(sector: &[u8]) -> Option { - if sector.len() < SECTOR_SIZE { - return None; - } - if sector[510] != 0x55 || sector[511] != 0xAA { - return None; - } - let bpb = Self { - bytes_per_sector: u16::from_le_bytes([sector[11], sector[12]]), - sectors_per_cluster: sector[13], - reserved_sectors: u16::from_le_bytes([sector[14], sector[15]]), - num_fats: sector[16], - root_entry_count: u16::from_le_bytes([sector[17], sector[18]]), - total_sectors: u16::from_le_bytes([sector[19], sector[20]]), - sectors_per_fat: u16::from_le_bytes([sector[22], sector[23]]), - }; - let usable = bpb.bytes_per_sector as usize == SECTOR_SIZE - && bpb.sectors_per_cluster.is_power_of_two() - && bpb.num_fats > 0 - && bpb.sectors_per_fat > 0; - usable.then_some(bpb) - } - - fn fat_start(&self) -> u32 { - self.reserved_sectors as u32 - } - - fn root_dir_start(&self) -> u32 { - self.fat_start() + (self.num_fats as u32) * (self.sectors_per_fat as u32) - } - - fn root_dir_sectors(&self) -> u32 { - ((self.root_entry_count as u32) * 32 + (self.bytes_per_sector as u32) - 1) - / (self.bytes_per_sector as u32) - } - - fn data_start(&self) -> u32 { - self.root_dir_start() + self.root_dir_sectors() - } - - /// The first sector of data cluster `cluster`. Clusters 0 and 1 are - /// reserved and hold no data. - fn cluster_to_sector(&self, cluster: u16) -> Option { - let index = (cluster as u32).checked_sub(2)?; - Some(self.data_start() + index * (self.sectors_per_cluster as u32)) - } - - fn cluster_size(&self) -> usize { - self.sectors_per_cluster as usize * SECTOR_SIZE - } -} - -#[derive(Clone, Copy)] -struct OpenFile { - first_cluster: u16, - size: usize, - offset: usize, - name: FileName, -} - -pub struct Fat16 { - device: B, - bpb: Bpb, - open_files: [Option; MAX_OPEN_FILES], -} - -impl Fat16 { - pub fn mount(mut device: B) -> Option { - let mut buf = [0u8; SECTOR_SIZE]; - if !device.read_sector(SectorId::new(0), &mut buf) { - return None; - } - let bpb = Bpb::parse(&buf)?; - Some(Self { - device, - bpb, - open_files: [const { None }; MAX_OPEN_FILES], - }) - } - - fn read_fat_entry(&mut self, cluster: u16) -> u16 { - let fat_offset = (cluster as u32) * 2; - let sector = self.bpb.fat_start() + fat_offset / (SECTOR_SIZE as u32); - let offset = (fat_offset % (SECTOR_SIZE as u32)) as usize; - let mut buf = [0u8; SECTOR_SIZE]; - if !self.device.read_sector(SectorId::new(sector), &mut buf) { - return 0xFFFF; - } - u16::from_le_bytes([buf[offset], buf[offset + 1]]) - } - - fn write_fat_entry(&mut self, cluster: u16, value: u16) { - let fat_offset = (cluster as u32) * 2; - let sector = self.bpb.fat_start() + fat_offset / (SECTOR_SIZE as u32); - let offset = (fat_offset % (SECTOR_SIZE as u32)) as usize; - let mut buf = [0u8; SECTOR_SIZE]; - self.device.read_sector(SectorId::new(sector), &mut buf); - buf[offset] = value as u8; - buf[offset + 1] = (value >> 8) as u8; - self.device.write_sector(SectorId::new(sector), &buf); - } - - fn alloc_cluster(&mut self) -> Option { - let max = self.bpb.total_sectors as u16 / self.bpb.sectors_per_cluster as u16; - for i in 2..max { - if self.read_fat_entry(i) == 0 { - self.write_fat_entry(i, 0xFFFF); - return Some(i); - } - } - None - } - - fn free_chain(&mut self, start: u16) { - let mut cluster = start; - loop { - if cluster < 2 || cluster >= 0xFFF8 { - break; - } - let next = self.read_fat_entry(cluster); - self.write_fat_entry(cluster, 0); - cluster = next; - } - } - - fn find_in_root_dir(&mut self, name: &[u8]) -> Option<(u16, usize)> { - let mut buf = [0u8; SECTOR_SIZE]; - let fat_name = to_fat_name(name); - for s in 0..self.bpb.root_dir_sectors() { - if !self - .device - .read_sector(SectorId::new(self.bpb.root_dir_start() + s), &mut buf) - { - continue; - } - for i in 0..(SECTOR_SIZE / 32) { - let off = i * 32; - if buf[off] == 0x00 || buf[off] == 0xE5 { - continue; - } - if buf[off..off + 11] == fat_name { - let cluster = u16::from_le_bytes([buf[off + 26], buf[off + 27]]); - let size = u32::from_le_bytes([ - buf[off + 28], - buf[off + 29], - buf[off + 30], - buf[off + 31], - ]); - return Some((cluster, size as usize)); - } - } - } - None - } - - fn create_dir_entry(&mut self, name: &[u8], cluster: u16) -> bool { - let mut buf = [0u8; SECTOR_SIZE]; - let fat_name = to_fat_name(name); - for s in 0..self.bpb.root_dir_sectors() { - let sector = self.bpb.root_dir_start() + s; - if !self.device.read_sector(SectorId::new(sector), &mut buf) { - continue; - } - for i in 0..(SECTOR_SIZE / 32) { - let off = i * 32; - if buf[off] == 0x00 || buf[off] == 0xE5 { - buf[off..off + 11].copy_from_slice(&fat_name); - buf[off + 11] = 0x20; - for b in &mut buf[off + 12..off + 26] { - *b = 0; - } - buf[off + 26] = cluster as u8; - buf[off + 27] = (cluster >> 8) as u8; - for b in &mut buf[off + 28..off + 32] { - *b = 0; - } - self.device.write_sector(SectorId::new(sector), &buf); - return true; - } - } - } - false - } - - fn delete_dir_entry(&mut self, name: &[u8]) -> Option { - let mut buf = [0u8; SECTOR_SIZE]; - let fat_name = to_fat_name(name); - for s in 0..self.bpb.root_dir_sectors() { - let sector = self.bpb.root_dir_start() + s; - if !self.device.read_sector(SectorId::new(sector), &mut buf) { - continue; - } - for i in 0..(SECTOR_SIZE / 32) { - let off = i * 32; - if buf[off..off + 11] == fat_name { - let cluster = u16::from_le_bytes([buf[off + 26], buf[off + 27]]); - buf[off] = 0xE5; - self.device.write_sector(SectorId::new(sector), &buf); - return Some(cluster); - } - } - } - None - } - - fn update_dir_size(&mut self, name: &[u8], size: u32, cluster: u16) { - let mut buf = [0u8; SECTOR_SIZE]; - let fat_name = to_fat_name(name); - for s in 0..self.bpb.root_dir_sectors() { - let sector = self.bpb.root_dir_start() + s; - if !self.device.read_sector(SectorId::new(sector), &mut buf) { - continue; - } - for i in 0..(SECTOR_SIZE / 32) { - let off = i * 32; - if buf[off..off + 11] == fat_name { - buf[off + 28..off + 32].copy_from_slice(&size.to_le_bytes()); - buf[off + 26] = cluster as u8; - buf[off + 27] = (cluster >> 8) as u8; - self.device.write_sector(SectorId::new(sector), &buf); - return; - } - } - } - } - - fn follow_chain(&mut self, start: u16, skip_clusters: usize) -> u16 { - let mut cluster = start; - for _ in 0..skip_clusters { - let next = self.read_fat_entry(cluster); - if next >= 0xFFF8 { - return cluster; - } - cluster = next; - } - cluster - } - - /// The open-file slot `handle` names; `None` for handles past the - /// table, which come straight from clients. - fn slot(&mut self, handle: FileHandle) -> Option<&mut Option> { - self.open_files.get_mut(handle.raw() as usize) - } - - 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 - } -} - -impl FileSystem for Fat16 { - fn open(&mut self, path: &[u8]) -> Option { - let (cluster, size) = self.find_in_root_dir(path)?; - let handle = self.alloc_handle()?; - *self.slot(handle)? = Some(OpenFile { - first_cluster: cluster, - size, - offset: 0, - name: FileName::from_bytes(path), - }); - Some(handle) - } - - fn read(&mut self, handle: FileHandle, buf: &mut [u8]) -> Option { - let file = (*self.slot(handle)?)?; - if file.offset >= file.size { - return Some(0); - } - - let remaining = file.size - file.offset; - let to_read = buf.len().min(remaining); - let cs = self.bpb.cluster_size(); - - let skip_clusters = file.offset / cs; - let mut cluster = self.follow_chain(file.first_cluster, skip_clusters); - let mut skip = file.offset % cs; - let mut read = 0; - let mut sector_buf = [0u8; SECTOR_SIZE]; - - while read < to_read { - let sector_in_cluster = skip / SECTOR_SIZE; - let byte_in_sector = skip % SECTOR_SIZE; - let Some(base) = self.bpb.cluster_to_sector(cluster) else { - break; - }; - let sector = base + sector_in_cluster as u32; - if !self - .device - .read_sector(SectorId::new(sector), &mut sector_buf) - { - break; - } - - let chunk = (SECTOR_SIZE - byte_in_sector).min(to_read - read); - buf[read..read + chunk] - .copy_from_slice(§or_buf[byte_in_sector..byte_in_sector + chunk]); - read += chunk; - skip += chunk; - - if skip >= cs { - let next = self.read_fat_entry(cluster); - if next >= 0xFFF8 { - break; - } - cluster = next; - skip = 0; - } - } - - if let Some(Some(f)) = self.slot(handle) { - f.offset += read; - } - Some(read) - } - - fn write(&mut self, handle: FileHandle, buf: &[u8]) -> Option { - let mut file = (*self.slot(handle)?)?; - let cs = self.bpb.cluster_size(); - - if file.first_cluster < 2 { - let c = self.alloc_cluster()?; - file.first_cluster = c; - } - - let skip_clusters = file.offset / cs; - let mut cluster = self.follow_chain(file.first_cluster, skip_clusters); - let mut pos = file.offset % cs; - let mut written = 0; - let mut sector_buf = [0u8; SECTOR_SIZE]; - - while written < buf.len() { - let sector_in_cluster = pos / SECTOR_SIZE; - let byte_in_sector = pos % SECTOR_SIZE; - let Some(base) = self.bpb.cluster_to_sector(cluster) else { - break; - }; - let sector = base + sector_in_cluster as u32; - if !self.device.read_sector(SectorId::new(sector), &mut sector_buf) { - break; - } - - let chunk = (SECTOR_SIZE - byte_in_sector).min(buf.len() - written); - sector_buf[byte_in_sector..byte_in_sector + chunk] - .copy_from_slice(&buf[written..written + chunk]); - // Bytes count as written only once they reached the disk. - if !self.device.write_sector(SectorId::new(sector), §or_buf) { - break; - } - - written += chunk; - pos += chunk; - - if pos >= cs { - let next = self.read_fat_entry(cluster); - if next >= 0xFFF8 { - if written < buf.len() { - if let Some(new_c) = self.alloc_cluster() { - self.write_fat_entry(cluster, new_c); - cluster = new_c; - } else { - break; - } - } - } else { - cluster = next; - } - pos = 0; - } - } - - file.offset += written; - if file.offset > file.size { - file.size = file.offset; - } - *self.slot(handle)? = Some(file); - Some(written) - } - - fn close(&mut self, handle: FileHandle) { - let Some(file) = self.slot(handle).and_then(Option::take) else { - return; - }; - let mut name_buf = [0u8; 32]; - let name_bytes = file.name.as_bytes(); - let len = name_bytes.len().min(32); - name_buf[..len].copy_from_slice(&name_bytes[..len]); - self.update_dir_size(&name_buf[..len], file.size as u32, file.first_cluster); - } - - fn list_dir(&mut self, _path: &[u8], entries: &mut [DirEntry]) -> Option { - let mut buf = [0u8; SECTOR_SIZE]; - let mut count = 0; - for s in 0..self.bpb.root_dir_sectors() { - if !self - .device - .read_sector(SectorId::new(self.bpb.root_dir_start() + s), &mut buf) - { - continue; - } - for i in 0..(SECTOR_SIZE / 32) { - if count >= entries.len() { - return Some(count); - } - let off = i * 32; - if buf[off] == 0x00 { - return Some(count); - } - if buf[off] == 0xE5 || buf[off + 11] & 0x08 != 0 { - continue; - } - let size = u32::from_le_bytes([ - buf[off + 28], - buf[off + 29], - buf[off + 30], - buf[off + 31], - ]); - entries[count] = DirEntry { - name: from_fat_name(&buf[off..off + 11]), - size: size as usize, - is_dir: buf[off + 11] & 0x10 != 0, - }; - count += 1; - } - } - Some(count) - } - - fn create(&mut self, path: &[u8]) -> Option { - if self.find_in_root_dir(path).is_some() { - return None; - } - let cluster = self.alloc_cluster()?; - if !self.create_dir_entry(path, cluster) { - self.write_fat_entry(cluster, 0); - return None; - } - let handle = self.alloc_handle()?; - *self.slot(handle)? = Some(OpenFile { - first_cluster: cluster, - size: 0, - offset: 0, - name: FileName::from_bytes(path), - }); - Some(handle) - } - - fn delete(&mut self, path: &[u8]) -> bool { - if let Some(cluster) = self.delete_dir_entry(path) { - self.free_chain(cluster); - true - } else { - false - } - } - - fn file_size(&mut self, handle: FileHandle) -> Option { - self.slot(handle)?.as_ref().map(|f| f.size) - } - - fn mkdir(&mut self, _path: &[u8]) -> bool { - false - } - - fn writable(&self) -> bool { - self.device.writable() - } -} - -fn to_fat_name(name: &[u8]) -> [u8; 11] { - let mut fat = [b' '; 11]; - let dot_pos = name.iter().position(|&b| b == b'.').unwrap_or(name.len()); - for i in 0..dot_pos.min(8) { - fat[i] = name[i].to_ascii_uppercase(); - } - if dot_pos < name.len() { - for i in 0..(name.len() - dot_pos - 1).min(3) { - fat[8 + i] = name[dot_pos + 1 + i].to_ascii_uppercase(); - } - } - fat -} - -fn from_fat_name(raw: &[u8]) -> FileName { - let mut buf = [0u8; 32]; - let mut pos = 0; - let name_end = raw[..8] - .iter() - .rposition(|&b| b != b' ') - .map(|i| i + 1) - .unwrap_or(0); - for i in 0..name_end { - buf[pos] = raw[i].to_ascii_lowercase(); - pos += 1; - } - let ext_end = raw[8..11] - .iter() - .rposition(|&b| b != b' ') - .map(|i| i + 1) - .unwrap_or(0); - if ext_end > 0 { - buf[pos] = b'.'; - pos += 1; - for i in 0..ext_end { - buf[pos] = raw[8 + i].to_ascii_lowercase(); - pos += 1; - } - } - FileName::from_bytes(&buf[..pos]) -} diff --git a/crates/servers/storage-server/src/fs/fat16/bpb.rs b/crates/servers/storage-server/src/fs/fat16/bpb.rs new file mode 100644 index 0000000..c685fc0 --- /dev/null +++ b/crates/servers/storage-server/src/fs/fat16/bpb.rs @@ -0,0 +1,68 @@ +//! BIOS parameter block: disk geometry. + +use super::SECTOR_SIZE; + +pub(super) struct Bpb { + pub(super) bytes_per_sector: u16, + pub(super) sectors_per_cluster: u8, + pub(super) reserved_sectors: u16, + pub(super) num_fats: u8, + pub(super) root_entry_count: u16, + pub(super) total_sectors: u16, + pub(super) sectors_per_fat: u16, +} + +impl Bpb { + /// Reads the BPB, refusing geometry the rest of this module cannot + /// work with: the disk is untrusted input like any client request. + pub(super) fn parse(sector: &[u8]) -> Option { + if sector.len() < SECTOR_SIZE { + return None; + } + if sector[510] != 0x55 || sector[511] != 0xAA { + return None; + } + let bpb = Self { + bytes_per_sector: u16::from_le_bytes([sector[11], sector[12]]), + sectors_per_cluster: sector[13], + reserved_sectors: u16::from_le_bytes([sector[14], sector[15]]), + num_fats: sector[16], + root_entry_count: u16::from_le_bytes([sector[17], sector[18]]), + total_sectors: u16::from_le_bytes([sector[19], sector[20]]), + sectors_per_fat: u16::from_le_bytes([sector[22], sector[23]]), + }; + let usable = bpb.bytes_per_sector as usize == SECTOR_SIZE + && bpb.sectors_per_cluster.is_power_of_two() + && bpb.num_fats > 0 + && bpb.sectors_per_fat > 0; + usable.then_some(bpb) + } + + pub(super) fn fat_start(&self) -> u32 { + self.reserved_sectors as u32 + } + + pub(super) fn root_dir_start(&self) -> u32 { + self.fat_start() + (self.num_fats as u32) * (self.sectors_per_fat as u32) + } + + pub(super) fn root_dir_sectors(&self) -> u32 { + ((self.root_entry_count as u32) * 32 + (self.bytes_per_sector as u32) - 1) + / (self.bytes_per_sector as u32) + } + + pub(super) fn data_start(&self) -> u32 { + self.root_dir_start() + self.root_dir_sectors() + } + + /// The first sector of data cluster `cluster`. Clusters 0 and 1 are + /// reserved and hold no data. + pub(super) fn cluster_to_sector(&self, cluster: u16) -> Option { + let index = (cluster as u32).checked_sub(2)?; + Some(self.data_start() + index * (self.sectors_per_cluster as u32)) + } + + pub(super) fn cluster_size(&self) -> usize { + self.sectors_per_cluster as usize * SECTOR_SIZE + } +} diff --git a/crates/servers/storage-server/src/fs/fat16/dir.rs b/crates/servers/storage-server/src/fs/fat16/dir.rs new file mode 100644 index 0000000..f7a8bdd --- /dev/null +++ b/crates/servers/storage-server/src/fs/fat16/dir.rs @@ -0,0 +1,151 @@ +//! Root directory entries and 8.3 names. + +use super::Fat16; +use super::SECTOR_SIZE; +use crate::filesystem::FileName; +use port_block_device::{BlockDevice, SectorId}; + +impl Fat16 { + pub(super) fn find_in_root_dir(&mut self, name: &[u8]) -> Option<(u16, usize)> { + let mut buf = [0u8; SECTOR_SIZE]; + let fat_name = to_fat_name(name); + for s in 0..self.bpb.root_dir_sectors() { + if !self + .device + .read_sector(SectorId::new(self.bpb.root_dir_start() + s), &mut buf) + { + continue; + } + for i in 0..(SECTOR_SIZE / 32) { + let off = i * 32; + if buf[off] == 0x00 || buf[off] == 0xE5 { + continue; + } + if buf[off..off + 11] == fat_name { + let cluster = u16::from_le_bytes([buf[off + 26], buf[off + 27]]); + let size = u32::from_le_bytes([ + buf[off + 28], + buf[off + 29], + buf[off + 30], + buf[off + 31], + ]); + return Some((cluster, size as usize)); + } + } + } + None + } + + pub(super) fn create_dir_entry(&mut self, name: &[u8], cluster: u16) -> bool { + let mut buf = [0u8; SECTOR_SIZE]; + let fat_name = to_fat_name(name); + for s in 0..self.bpb.root_dir_sectors() { + let sector = self.bpb.root_dir_start() + s; + if !self.device.read_sector(SectorId::new(sector), &mut buf) { + continue; + } + for i in 0..(SECTOR_SIZE / 32) { + let off = i * 32; + if buf[off] == 0x00 || buf[off] == 0xE5 { + buf[off..off + 11].copy_from_slice(&fat_name); + buf[off + 11] = 0x20; + for b in &mut buf[off + 12..off + 26] { + *b = 0; + } + buf[off + 26] = cluster as u8; + buf[off + 27] = (cluster >> 8) as u8; + for b in &mut buf[off + 28..off + 32] { + *b = 0; + } + self.device.write_sector(SectorId::new(sector), &buf); + return true; + } + } + } + false + } + + pub(super) fn delete_dir_entry(&mut self, name: &[u8]) -> Option { + let mut buf = [0u8; SECTOR_SIZE]; + let fat_name = to_fat_name(name); + for s in 0..self.bpb.root_dir_sectors() { + let sector = self.bpb.root_dir_start() + s; + if !self.device.read_sector(SectorId::new(sector), &mut buf) { + continue; + } + for i in 0..(SECTOR_SIZE / 32) { + let off = i * 32; + if buf[off..off + 11] == fat_name { + let cluster = u16::from_le_bytes([buf[off + 26], buf[off + 27]]); + buf[off] = 0xE5; + self.device.write_sector(SectorId::new(sector), &buf); + return Some(cluster); + } + } + } + None + } + + pub(super) fn update_dir_size(&mut self, name: &[u8], size: u32, cluster: u16) { + let mut buf = [0u8; SECTOR_SIZE]; + let fat_name = to_fat_name(name); + for s in 0..self.bpb.root_dir_sectors() { + let sector = self.bpb.root_dir_start() + s; + if !self.device.read_sector(SectorId::new(sector), &mut buf) { + continue; + } + for i in 0..(SECTOR_SIZE / 32) { + let off = i * 32; + if buf[off..off + 11] == fat_name { + buf[off + 28..off + 32].copy_from_slice(&size.to_le_bytes()); + buf[off + 26] = cluster as u8; + buf[off + 27] = (cluster >> 8) as u8; + self.device.write_sector(SectorId::new(sector), &buf); + return; + } + } + } + } +} + +pub(super) fn to_fat_name(name: &[u8]) -> [u8; 11] { + let mut fat = [b' '; 11]; + let dot_pos = name.iter().position(|&b| b == b'.').unwrap_or(name.len()); + for i in 0..dot_pos.min(8) { + fat[i] = name[i].to_ascii_uppercase(); + } + if dot_pos < name.len() { + for i in 0..(name.len() - dot_pos - 1).min(3) { + fat[8 + i] = name[dot_pos + 1 + i].to_ascii_uppercase(); + } + } + fat +} + +pub(super) fn from_fat_name(raw: &[u8]) -> FileName { + let mut buf = [0u8; 32]; + let mut pos = 0; + let name_end = raw[..8] + .iter() + .rposition(|&b| b != b' ') + .map(|i| i + 1) + .unwrap_or(0); + for i in 0..name_end { + buf[pos] = raw[i].to_ascii_lowercase(); + pos += 1; + } + let ext_end = raw[8..11] + .iter() + .rposition(|&b| b != b' ') + .map(|i| i + 1) + .unwrap_or(0); + if ext_end > 0 { + buf[pos] = b'.'; + pos += 1; + for i in 0..ext_end { + buf[pos] = raw[8 + i].to_ascii_lowercase(); + pos += 1; + } + } + FileName::from_bytes(&buf[..pos]) +} diff --git a/crates/servers/storage-server/src/fs/fat16/fat.rs b/crates/servers/storage-server/src/fs/fat16/fat.rs new file mode 100644 index 0000000..f1db602 --- /dev/null +++ b/crates/servers/storage-server/src/fs/fat16/fat.rs @@ -0,0 +1,64 @@ +//! FAT table: cluster allocation and chains. + +use super::Fat16; +use super::SECTOR_SIZE; +use port_block_device::{BlockDevice, SectorId}; + +impl Fat16 { + pub(super) fn read_fat_entry(&mut self, cluster: u16) -> u16 { + let fat_offset = (cluster as u32) * 2; + let sector = self.bpb.fat_start() + fat_offset / (SECTOR_SIZE as u32); + let offset = (fat_offset % (SECTOR_SIZE as u32)) as usize; + let mut buf = [0u8; SECTOR_SIZE]; + if !self.device.read_sector(SectorId::new(sector), &mut buf) { + return 0xFFFF; + } + u16::from_le_bytes([buf[offset], buf[offset + 1]]) + } + + pub(super) fn write_fat_entry(&mut self, cluster: u16, value: u16) { + let fat_offset = (cluster as u32) * 2; + let sector = self.bpb.fat_start() + fat_offset / (SECTOR_SIZE as u32); + let offset = (fat_offset % (SECTOR_SIZE as u32)) as usize; + let mut buf = [0u8; SECTOR_SIZE]; + self.device.read_sector(SectorId::new(sector), &mut buf); + buf[offset] = value as u8; + buf[offset + 1] = (value >> 8) as u8; + self.device.write_sector(SectorId::new(sector), &buf); + } + + pub(super) fn alloc_cluster(&mut self) -> Option { + let max = self.bpb.total_sectors as u16 / self.bpb.sectors_per_cluster as u16; + for i in 2..max { + if self.read_fat_entry(i) == 0 { + self.write_fat_entry(i, 0xFFFF); + return Some(i); + } + } + None + } + + pub(super) fn free_chain(&mut self, start: u16) { + let mut cluster = start; + loop { + if cluster < 2 || cluster >= 0xFFF8 { + break; + } + let next = self.read_fat_entry(cluster); + self.write_fat_entry(cluster, 0); + cluster = next; + } + } + + pub(super) fn follow_chain(&mut self, start: u16, skip_clusters: usize) -> u16 { + let mut cluster = start; + for _ in 0..skip_clusters { + let next = self.read_fat_entry(cluster); + if next >= 0xFFF8 { + return cluster; + } + cluster = next; + } + cluster + } +} diff --git a/crates/servers/storage-server/src/fs/fat16/mod.rs b/crates/servers/storage-server/src/fs/fat16/mod.rs new file mode 100644 index 0000000..4789443 --- /dev/null +++ b/crates/servers/storage-server/src/fs/fat16/mod.rs @@ -0,0 +1,275 @@ +//! FAT16 filesystem over a block device (root directory only). + +mod bpb; +mod dir; +mod fat; + +use crate::filesystem::{DirEntry, FileHandle, FileName, FileSystem}; +use bpb::Bpb; +use dir::from_fat_name; +use port_block_device::{BlockDevice, SectorId}; + +const SECTOR_SIZE: usize = 512; +const MAX_OPEN_FILES: usize = 8; + +#[derive(Clone, Copy)] +struct OpenFile { + first_cluster: u16, + size: usize, + offset: usize, + name: FileName, +} + +pub struct Fat16 { + device: B, + bpb: Bpb, + open_files: [Option; MAX_OPEN_FILES], +} + +impl Fat16 { + pub fn mount(mut device: B) -> Option { + let mut buf = [0u8; SECTOR_SIZE]; + if !device.read_sector(SectorId::new(0), &mut buf) { + return None; + } + let bpb = Bpb::parse(&buf)?; + Some(Self { + device, + bpb, + open_files: [const { None }; MAX_OPEN_FILES], + }) + } + + /// The open-file slot `handle` names; `None` for handles past the + /// table, which come straight from clients. + fn slot(&mut self, handle: FileHandle) -> Option<&mut Option> { + self.open_files.get_mut(handle.raw() as usize) + } + + 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 + } +} + +impl FileSystem for Fat16 { + fn open(&mut self, path: &[u8]) -> Option { + let (cluster, size) = self.find_in_root_dir(path)?; + let handle = self.alloc_handle()?; + *self.slot(handle)? = Some(OpenFile { + first_cluster: cluster, + size, + offset: 0, + name: FileName::from_bytes(path), + }); + Some(handle) + } + + fn read(&mut self, handle: FileHandle, buf: &mut [u8]) -> Option { + let file = (*self.slot(handle)?)?; + if file.offset >= file.size { + return Some(0); + } + + let remaining = file.size - file.offset; + let to_read = buf.len().min(remaining); + let cs = self.bpb.cluster_size(); + + let skip_clusters = file.offset / cs; + let mut cluster = self.follow_chain(file.first_cluster, skip_clusters); + let mut skip = file.offset % cs; + let mut read = 0; + let mut sector_buf = [0u8; SECTOR_SIZE]; + + while read < to_read { + let sector_in_cluster = skip / SECTOR_SIZE; + let byte_in_sector = skip % SECTOR_SIZE; + let Some(base) = self.bpb.cluster_to_sector(cluster) else { + break; + }; + let sector = base + sector_in_cluster as u32; + if !self + .device + .read_sector(SectorId::new(sector), &mut sector_buf) + { + break; + } + + let chunk = (SECTOR_SIZE - byte_in_sector).min(to_read - read); + buf[read..read + chunk] + .copy_from_slice(§or_buf[byte_in_sector..byte_in_sector + chunk]); + read += chunk; + skip += chunk; + + if skip >= cs { + let next = self.read_fat_entry(cluster); + if next >= 0xFFF8 { + break; + } + cluster = next; + skip = 0; + } + } + + if let Some(Some(f)) = self.slot(handle) { + f.offset += read; + } + Some(read) + } + + fn write(&mut self, handle: FileHandle, buf: &[u8]) -> Option { + let mut file = (*self.slot(handle)?)?; + let cs = self.bpb.cluster_size(); + + if file.first_cluster < 2 { + let c = self.alloc_cluster()?; + file.first_cluster = c; + } + + let skip_clusters = file.offset / cs; + let mut cluster = self.follow_chain(file.first_cluster, skip_clusters); + let mut pos = file.offset % cs; + let mut written = 0; + let mut sector_buf = [0u8; SECTOR_SIZE]; + + while written < buf.len() { + let sector_in_cluster = pos / SECTOR_SIZE; + let byte_in_sector = pos % SECTOR_SIZE; + let Some(base) = self.bpb.cluster_to_sector(cluster) else { + break; + }; + let sector = base + sector_in_cluster as u32; + if !self.device.read_sector(SectorId::new(sector), &mut sector_buf) { + break; + } + + let chunk = (SECTOR_SIZE - byte_in_sector).min(buf.len() - written); + sector_buf[byte_in_sector..byte_in_sector + chunk] + .copy_from_slice(&buf[written..written + chunk]); + // Bytes count as written only once they reached the disk. + if !self.device.write_sector(SectorId::new(sector), §or_buf) { + break; + } + + written += chunk; + pos += chunk; + + if pos >= cs { + let next = self.read_fat_entry(cluster); + if next >= 0xFFF8 { + if written < buf.len() { + if let Some(new_c) = self.alloc_cluster() { + self.write_fat_entry(cluster, new_c); + cluster = new_c; + } else { + break; + } + } + } else { + cluster = next; + } + pos = 0; + } + } + + file.offset += written; + if file.offset > file.size { + file.size = file.offset; + } + *self.slot(handle)? = Some(file); + Some(written) + } + + fn close(&mut self, handle: FileHandle) { + let Some(file) = self.slot(handle).and_then(Option::take) else { + return; + }; + let mut name_buf = [0u8; 32]; + let name_bytes = file.name.as_bytes(); + let len = name_bytes.len().min(32); + name_buf[..len].copy_from_slice(&name_bytes[..len]); + self.update_dir_size(&name_buf[..len], file.size as u32, file.first_cluster); + } + + fn list_dir(&mut self, _path: &[u8], entries: &mut [DirEntry]) -> Option { + let mut buf = [0u8; SECTOR_SIZE]; + let mut count = 0; + for s in 0..self.bpb.root_dir_sectors() { + if !self + .device + .read_sector(SectorId::new(self.bpb.root_dir_start() + s), &mut buf) + { + continue; + } + for i in 0..(SECTOR_SIZE / 32) { + if count >= entries.len() { + return Some(count); + } + let off = i * 32; + if buf[off] == 0x00 { + return Some(count); + } + if buf[off] == 0xE5 || buf[off + 11] & 0x08 != 0 { + continue; + } + let size = u32::from_le_bytes([ + buf[off + 28], + buf[off + 29], + buf[off + 30], + buf[off + 31], + ]); + entries[count] = DirEntry { + name: from_fat_name(&buf[off..off + 11]), + size: size as usize, + is_dir: buf[off + 11] & 0x10 != 0, + }; + count += 1; + } + } + Some(count) + } + + fn create(&mut self, path: &[u8]) -> Option { + if self.find_in_root_dir(path).is_some() { + return None; + } + let cluster = self.alloc_cluster()?; + if !self.create_dir_entry(path, cluster) { + self.write_fat_entry(cluster, 0); + return None; + } + let handle = self.alloc_handle()?; + *self.slot(handle)? = Some(OpenFile { + first_cluster: cluster, + size: 0, + offset: 0, + name: FileName::from_bytes(path), + }); + Some(handle) + } + + fn delete(&mut self, path: &[u8]) -> bool { + if let Some(cluster) = self.delete_dir_entry(path) { + self.free_chain(cluster); + true + } else { + false + } + } + + fn file_size(&mut self, handle: FileHandle) -> Option { + self.slot(handle)?.as_ref().map(|f| f.size) + } + + fn mkdir(&mut self, _path: &[u8]) -> bool { + false + } + + fn writable(&self) -> bool { + self.device.writable() + } +} diff --git a/crates/servers/storage-server/src/protocol.rs b/crates/servers/storage-server/src/protocol.rs index 87f0f2a..755bfb0 100644 --- a/crates/servers/storage-server/src/protocol.rs +++ b/crates/servers/storage-server/src/protocol.rs @@ -1,10 +1,5 @@ -//! 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. +//! Storage protocol: a client `Attach`es to get a shared buffer; paths and +//! data travel through it, message words carry only lengths and handles. use server_framework::{Decode, Encode}; use crate::filesystem::FileHandle;