refactor(userspace): split syscall/fat16/ext4 by concern; trim comments
This commit is contained in:
@@ -1,7 +1,5 @@
|
|||||||
//! The spinner half of init's SMP self-test (see init's `selftest`):
|
//! Spinner half of init's SMP self-test: one thread per hart, each
|
||||||
//! maps the counter page it was given in slot 0 and runs one thread per
|
//! incrementing its own counter in the page given in slot 0.
|
||||||
//! hart, each incrementing its own counter for ever. Init watches the
|
|
||||||
//! counters through its own mapping of the same page.
|
|
||||||
|
|
||||||
#![no_std]
|
#![no_std]
|
||||||
#![no_main]
|
#![no_main]
|
||||||
@@ -14,8 +12,7 @@ use types::address::{EntryPoint, StackPointer, VirtualAddress};
|
|||||||
use types::bounded::{CapSlot, PageCount};
|
use types::bounded::{CapSlot, PageCount};
|
||||||
use types::mapping::PagePerms;
|
use types::mapping::PagePerms;
|
||||||
|
|
||||||
/// Where the counter page is mapped. Layout (words): 0 the next free
|
/// Counter page layout (words): 0 next free counter index, 1..=THREADS counters.
|
||||||
/// counter index, 1..=THREADS the counters.
|
|
||||||
const COUNTERS_VA: usize = 0x0100_0000;
|
const COUNTERS_VA: usize = 0x0100_0000;
|
||||||
const THREADS: usize = 4;
|
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 word = |i: usize| unsafe { &*((COUNTERS_VA + i * 8) as *const AtomicUsize) };
|
||||||
let mine = 1 + word(0).fetch_add(1, Ordering::Relaxed) % THREADS;
|
let mine = 1 + word(0).fetch_add(1, Ordering::Relaxed) % THREADS;
|
||||||
loop {
|
loop {
|
||||||
// Yield now and then: a spinner sharing init's hart would
|
// Yield so a spinner sharing init's hart cannot starve it.
|
||||||
// otherwise starve it until the next (1 s) timer tick.
|
|
||||||
for _ in 0..1024 {
|
for _ in 0..1024 {
|
||||||
word(mine).fetch_add(1, Ordering::Relaxed);
|
word(mine).fetch_add(1, Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,9 +29,8 @@ pub struct VirtioBlockDevice {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl VirtioBlockDevice {
|
impl VirtioBlockDevice {
|
||||||
/// Takes over the block device whose MMIO region `mmio` names: maps
|
/// Takes over the virtio block device behind `mmio`. `None` if it is
|
||||||
/// it, allocates the queue and request buffers, and resets the device
|
/// not one or any step fails.
|
||||||
/// onto them. `None` if any step fails or it is not a block device.
|
|
||||||
pub fn new(mmio: types::bounded::CapSlot) -> Option<Self> {
|
pub fn new(mmio: types::bounded::CapSlot) -> Option<Self> {
|
||||||
libkos::device::map_mmio(mmio, MMIO_BASE).ok()?;
|
libkos::device::map_mmio(mmio, MMIO_BASE).ok()?;
|
||||||
let regs = UserMmio;
|
let regs = UserMmio;
|
||||||
|
|||||||
@@ -37,10 +37,8 @@ pub struct VirtioFramebuffer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl VirtioFramebuffer {
|
impl VirtioFramebuffer {
|
||||||
/// Takes over the GPU whose MMIO region `mmio` names: maps it, sets up
|
/// Takes over the virtio GPU behind `mmio` and makes a framebuffer its
|
||||||
/// the control queue and command buffer, asks for the display size,
|
/// scanout. `None` if it is not a GPU or any step fails.
|
||||||
/// allocates a framebuffer and makes it the scanout. `None` if it is
|
|
||||||
/// not a virtio GPU or any step fails.
|
|
||||||
pub fn try_new(mmio: types::bounded::CapSlot) -> Option<Self> {
|
pub fn try_new(mmio: types::bounded::CapSlot) -> Option<Self> {
|
||||||
libkos::device::map_mmio(mmio, GPU_MMIO_VA).ok()?;
|
libkos::device::map_mmio(mmio, GPU_MMIO_VA).ok()?;
|
||||||
let regs = GpuMmio;
|
let regs = GpuMmio;
|
||||||
|
|||||||
@@ -31,10 +31,8 @@ pub struct VirtioInputDevice {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl VirtioInputDevice {
|
impl VirtioInputDevice {
|
||||||
/// Takes over the input device whose MMIO region `mmio` names: maps it,
|
/// Takes over the virtio input device behind `mmio`. `None` if it is
|
||||||
/// allocates the queue and event buffers, resets the device onto them
|
/// not one or any step fails.
|
||||||
/// and hands it every buffer. `None` if it is not a virtio input
|
|
||||||
/// device or a step fails.
|
|
||||||
pub fn try_new(mmio: types::bounded::CapSlot) -> Option<Self> {
|
pub fn try_new(mmio: types::bounded::CapSlot) -> Option<Self> {
|
||||||
libkos::device::map_mmio(mmio, INPUT_MMIO_VA).ok()?;
|
libkos::device::map_mmio(mmio, INPUT_MMIO_VA).ok()?;
|
||||||
let regs = MmioInput;
|
let regs = MmioInput;
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
//! Which driver gets which device. Boot gave init a capability to every
|
//! Finds the board's devices (`types::bootinfo`): disk, GPU, and keyboard
|
||||||
//! board device and a page saying where they are (`types::bootinfo`); init
|
//! (or the UART when there is none).
|
||||||
//! asks each virtio slot what it is and hands the disk, the GPU and the
|
|
||||||
//! keyboard (or, without one, the UART) to their drivers.
|
|
||||||
|
|
||||||
use hw_virtio::mmio::{
|
use hw_virtio::mmio::{
|
||||||
DEVICE_ID_BLOCK, DEVICE_ID_GPU, DEVICE_ID_INPUT, MAGIC, REG_DEVICE_ID, REG_MAGIC,
|
DEVICE_ID_BLOCK, DEVICE_ID_GPU, DEVICE_ID_INPUT, MAGIC, REG_DEVICE_ID, REG_MAGIC,
|
||||||
@@ -84,7 +82,7 @@ pub fn discover() -> Devices {
|
|||||||
},
|
},
|
||||||
_ => continue,
|
_ => continue,
|
||||||
};
|
};
|
||||||
// The first device of each kind wins.
|
// First device of each kind wins.
|
||||||
if place.mmio.is_none() {
|
if place.mmio.is_none() {
|
||||||
*place = dev;
|
*place = dev;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,5 @@
|
|||||||
//! SMP self-test, run only when the disk carries `smptest.elf` (the test
|
//! SMP self-test, run when the disk carries `smptest.elf`: after revoking a
|
||||||
//! image built by `make test-qemu`). It checks on real harts what host
|
//! spinner's page or killing it, no spinner counter may move on any hart.
|
||||||
//! 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.
|
|
||||||
|
|
||||||
use core::sync::atomic::{AtomicUsize, Ordering};
|
use core::sync::atomic::{AtomicUsize, Ordering};
|
||||||
use libkos::fs::Storage;
|
use libkos::fs::Storage;
|
||||||
@@ -17,7 +9,6 @@ use types::bounded::{CapSlot, PageCount};
|
|||||||
use types::mapping::PagePerms;
|
use types::mapping::PagePerms;
|
||||||
use types::permission::{Permission, PermissionSet};
|
use types::permission::{Permission, PermissionSet};
|
||||||
|
|
||||||
/// Where init sees the counter page; same layout as the spinner's.
|
|
||||||
const OBSERVE_VA: usize = 0x0140_0000;
|
const OBSERVE_VA: usize = 0x0140_0000;
|
||||||
const THREADS: usize = 4;
|
const THREADS: usize = 4;
|
||||||
/// QEMU virt's timebase is 10 MHz.
|
/// 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)
|
syscall::map_region(page, VirtualAddress::from_raw(OBSERVE_VA), PagePerms::ReadWrite)
|
||||||
.map_err(|_| "cannot map the counter page")?;
|
.map_err(|_| "cannot map the counter page")?;
|
||||||
|
|
||||||
// 1. Unmapping: revoking the spinner's copy of the page must unmap it
|
// Revoke must unmap the page on every hart; the spinner then faults and dies.
|
||||||
// on every hart before Revoke returns. The spinner then faults,
|
|
||||||
// which kills it from whichever hart faults first.
|
|
||||||
let spinner = start_spinner(storage, scratch, page)?;
|
let spinner = start_spinner(storage, scratch, page)?;
|
||||||
if !syscall::revoke(page) {
|
if !syscall::revoke(page) {
|
||||||
return Err("revoke failed");
|
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");
|
libkos::println!("smptest: revoke stops every hart: ok");
|
||||||
|
|
||||||
// 2. Killing: a process whose threads are running on other harts must
|
// Kill must stop threads running on other harts before it returns.
|
||||||
// stop everywhere before Kill returns.
|
|
||||||
let spinner = start_spinner(storage, scratch, page)?;
|
let spinner = start_spinner(storage, scratch, page)?;
|
||||||
if !syscall::kill(spinner) {
|
if !syscall::kill(spinner) {
|
||||||
return Err("kill failed");
|
return Err("kill failed");
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
//! Handing devices to drivers. Init holds every device's capabilities
|
//! Init hands each driver its device's capabilities, one per `Call` on the
|
||||||
//! (see `types::bootinfo`) and gives each driver its own over the driver's
|
//! driver's endpoint; drivers map MMIO and allocate DMA themselves.
|
||||||
//! 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.
|
|
||||||
|
|
||||||
use crate::syscall::{self, Received, Result};
|
use crate::syscall::{self, Received, Result};
|
||||||
use types::address::VirtualAddress;
|
use types::address::VirtualAddress;
|
||||||
|
|||||||
@@ -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<T> = core::result::Result<T, Error>;
|
|
||||||
|
|
||||||
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<CapSlot> {
|
|
||||||
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<CapSlot>,
|
|
||||||
},
|
|
||||||
/// A `Call`; answer it with `reply` or `reply_recv`.
|
|
||||||
Call {
|
|
||||||
badge: usize,
|
|
||||||
words: [usize; 4],
|
|
||||||
cap: Option<CapSlot>,
|
|
||||||
},
|
|
||||||
/// Bits from a notification.
|
|
||||||
Signal { bits: usize },
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Received {
|
|
||||||
fn decode(r: [usize; 8]) -> Result<Self> {
|
|
||||||
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<CapSlot> {
|
|
||||||
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<CapSlot> {
|
|
||||||
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<VirtualAddress> {
|
|
||||||
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<CapSlot> {
|
|
||||||
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<usize> {
|
|
||||||
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<CapSlot> {
|
|
||||||
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<CapSlot> {
|
|
||||||
slot_of(call0(SYS_CREATE_ENDPOINT)).ok()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn send_msg(endpoint: CapSlot, words: [usize; 4], cap: Option<CapSlot>) -> Result<()> {
|
|
||||||
raw(
|
|
||||||
SYS_SEND,
|
|
||||||
message_regs(endpoint.value() as usize, words, cap),
|
|
||||||
)
|
|
||||||
.map(|_| ())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn receive_msg(endpoint: CapSlot) -> Result<Received> {
|
|
||||||
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<CapSlot>) -> Result<Received> {
|
|
||||||
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<CapSlot>) -> 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<CapSlot>) -> Result<Received> {
|
|
||||||
raw(
|
|
||||||
SYS_REPLY_RECV,
|
|
||||||
message_regs(endpoint.value() as usize, words, cap),
|
|
||||||
)
|
|
||||||
.and_then(Received::decode)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- notifications and interrupts ----
|
|
||||||
|
|
||||||
pub fn create_notification() -> Result<CapSlot> {
|
|
||||||
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<usize> {
|
|
||||||
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<usize> {
|
|
||||||
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]);
|
|
||||||
}
|
|
||||||
51
crates/libkos/src/syscall/caps.rs
Normal file
51
crates/libkos/src/syscall/caps.rs
Normal file
@@ -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<CapSlot> {
|
||||||
|
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()
|
||||||
|
}
|
||||||
94
crates/libkos/src/syscall/ipc.rs
Normal file
94
crates/libkos/src/syscall/ipc.rs
Normal file
@@ -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<CapSlot>,
|
||||||
|
},
|
||||||
|
/// A `Call`; answer it with `reply` or `reply_recv`.
|
||||||
|
Call {
|
||||||
|
badge: usize,
|
||||||
|
words: [usize; 4],
|
||||||
|
cap: Option<CapSlot>,
|
||||||
|
},
|
||||||
|
/// Bits from a notification.
|
||||||
|
Signal { bits: usize },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Received {
|
||||||
|
fn decode(r: [usize; 8]) -> Result<Self> {
|
||||||
|
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<CapSlot> {
|
||||||
|
slot_of(call0(SYS_CREATE_ENDPOINT)).ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_msg(endpoint: CapSlot, words: [usize; 4], cap: Option<CapSlot>) -> Result<()> {
|
||||||
|
raw(
|
||||||
|
SYS_SEND,
|
||||||
|
message_regs(endpoint.value() as usize, words, cap),
|
||||||
|
)
|
||||||
|
.map(|_| ())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn receive_msg(endpoint: CapSlot) -> Result<Received> {
|
||||||
|
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<CapSlot>) -> Result<Received> {
|
||||||
|
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<CapSlot>) -> 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<CapSlot>) -> Result<Received> {
|
||||||
|
raw(
|
||||||
|
SYS_REPLY_RECV,
|
||||||
|
message_regs(endpoint.value() as usize, words, cap),
|
||||||
|
)
|
||||||
|
.and_then(Received::decode)
|
||||||
|
}
|
||||||
65
crates/libkos/src/syscall/memory.rs
Normal file
65
crates/libkos/src/syscall/memory.rs
Normal file
@@ -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<VirtualAddress> {
|
||||||
|
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<CapSlot> {
|
||||||
|
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<usize> {
|
||||||
|
raw(SYS_REGION_ADDRESS, [region.value() as usize, 0, 0, 0, 0, 0]).map(|r| r[1])
|
||||||
|
}
|
||||||
42
crates/libkos/src/syscall/mod.rs
Normal file
42
crates/libkos/src/syscall/mod.rs
Normal file
@@ -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<T> = core::result::Result<T, Error>;
|
||||||
|
|
||||||
|
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<CapSlot> {
|
||||||
|
reg_slot(r).flatten()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn slot_of(r: Result<[usize; 8]>) -> Result<CapSlot> {
|
||||||
|
r.and_then(|r| slot_result(r[1]).ok_or(Error::InvalidArgument))
|
||||||
|
}
|
||||||
58
crates/libkos/src/syscall/notify.rs
Normal file
58
crates/libkos/src/syscall/notify.rs
Normal file
@@ -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<CapSlot> {
|
||||||
|
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<usize> {
|
||||||
|
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<usize> {
|
||||||
|
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(|_| ())
|
||||||
|
}
|
||||||
74
crates/libkos/src/syscall/process.rs
Normal file
74
crates/libkos/src/syscall/process.rs
Normal file
@@ -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<CapSlot> {
|
||||||
|
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]);
|
||||||
|
}
|
||||||
@@ -96,9 +96,7 @@ pub fn add_entry<B: BlockDevice>(
|
|||||||
let free_space = rec_len - actual_size;
|
let free_space = rec_len - actual_size;
|
||||||
if free_space >= new_rec_needed {
|
if free_space >= new_rec_needed {
|
||||||
if entry_inode != 0 {
|
if entry_inode != 0 {
|
||||||
// Shrink this entry to its actual size
|
|
||||||
write_u16(&mut block_buf, pos + 4, actual_size as u16);
|
write_u16(&mut block_buf, pos + 4, actual_size as u16);
|
||||||
// Write new entry after it
|
|
||||||
let new_pos = pos + actual_size;
|
let new_pos = pos + actual_size;
|
||||||
write_u32(&mut block_buf, new_pos, child_inode);
|
write_u32(&mut block_buf, new_pos, child_inode);
|
||||||
write_u16(&mut block_buf, new_pos + 4, free_space as u16);
|
write_u16(&mut block_buf, new_pos + 4, free_space as u16);
|
||||||
@@ -106,9 +104,7 @@ pub fn add_entry<B: BlockDevice>(
|
|||||||
block_buf[new_pos + 7] = file_type;
|
block_buf[new_pos + 7] = file_type;
|
||||||
block_buf[new_pos + 8..new_pos + 8 + name.len()].copy_from_slice(name);
|
block_buf[new_pos + 8..new_pos + 8 + name.len()].copy_from_slice(name);
|
||||||
} else {
|
} else {
|
||||||
// Reuse deleted entry
|
|
||||||
write_u32(&mut block_buf, pos, child_inode);
|
write_u32(&mut block_buf, pos, child_inode);
|
||||||
// Keep rec_len as is
|
|
||||||
block_buf[pos + 6] = name.len() as u8;
|
block_buf[pos + 6] = name.len() as u8;
|
||||||
block_buf[pos + 7] = file_type;
|
block_buf[pos + 7] = file_type;
|
||||||
block_buf[pos + 8..pos + 8 + name.len()].copy_from_slice(name);
|
block_buf[pos + 8..pos + 8 + name.len()].copy_from_slice(name);
|
||||||
@@ -160,11 +156,9 @@ pub fn remove_entry<B: BlockDevice>(
|
|||||||
if entry_inode != 0 && name_len == name.len() {
|
if entry_inode != 0 && name_len == name.len() {
|
||||||
if &block_buf[pos + 8..pos + 8 + name_len] == name {
|
if &block_buf[pos + 8..pos + 8 + name_len] == name {
|
||||||
if let Some(pp) = prev_pos {
|
if let Some(pp) = prev_pos {
|
||||||
// Merge with previous entry
|
|
||||||
let prev_rec = read_u16(&block_buf, pp + 4) as usize;
|
let prev_rec = read_u16(&block_buf, pp + 4) as usize;
|
||||||
write_u16(&mut block_buf, pp + 4, (prev_rec + rec_len) as u16);
|
write_u16(&mut block_buf, pp + 4, (prev_rec + rec_len) as u16);
|
||||||
} else {
|
} else {
|
||||||
// First entry — zero the inode
|
|
||||||
write_u32(&mut block_buf, pos, 0);
|
write_u32(&mut block_buf, pos, 0);
|
||||||
}
|
}
|
||||||
write_block(device, phys, spb, &block_buf);
|
write_block(device, phys, spb, &block_buf);
|
||||||
|
|||||||
99
crates/servers/storage-server/src/fs/ext4/extent.rs
Normal file
99
crates/servers/storage-server/src/fs/ext4/extent.rs
Normal file
@@ -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<B: BlockDevice> Ext4<B> {
|
||||||
|
pub(super) fn read_extents(&self, inode: &Inode) -> Option<Extent> {
|
||||||
|
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<u64> {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
136
crates/servers/storage-server/src/fs/ext4/inode.rs
Normal file
136
crates/servers/storage-server/src/fs/ext4/inode.rs
Normal file
@@ -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<B: BlockDevice> Ext4<B> {
|
||||||
|
pub(super) fn read_inode(&mut self, inode_num: u32) -> Option<Inode> {
|
||||||
|
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<Inode> {
|
||||||
|
self.read_inode(inode_num)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_inode_pub(&mut self, inode_num: u32, inode: &Inode) -> bool {
|
||||||
|
self.write_inode(inode_num, inode)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,50 +3,21 @@ pub mod superblock;
|
|||||||
pub mod bgd;
|
pub mod bgd;
|
||||||
pub mod bitmap;
|
pub mod bitmap;
|
||||||
pub mod dir;
|
pub mod dir;
|
||||||
|
mod extent;
|
||||||
|
mod inode;
|
||||||
|
mod path;
|
||||||
|
|
||||||
use port_block_device::BlockDevice;
|
use port_block_device::BlockDevice;
|
||||||
use crate::filesystem::{DirEntry, FileHandle, FileName, FileSystem};
|
use crate::filesystem::{DirEntry, FileHandle, FileName, FileSystem};
|
||||||
use io::{read_block, read_u32, read_u16};
|
use io::read_block;
|
||||||
use superblock::Superblock;
|
use superblock::Superblock;
|
||||||
use bgd::BlockGroupDescriptor;
|
|
||||||
|
|
||||||
const ROOT_INODE: u32 = 2;
|
const ROOT_INODE: u32 = 2;
|
||||||
const MAX_OPEN_FILES: usize = 8;
|
const MAX_OPEN_FILES: usize = 8;
|
||||||
const EXTENT_MAGIC: u16 = 0xF30A;
|
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 use inode::{Extent, 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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
struct OpenFile {
|
struct OpenFile {
|
||||||
@@ -80,265 +51,6 @@ impl<B: BlockDevice> Ext4<B> {
|
|||||||
open_files: [const { None }; MAX_OPEN_FILES],
|
open_files: [const { None }; MAX_OPEN_FILES],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_inode(&mut self, inode_num: u32) -> Option<Inode> {
|
|
||||||
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<Inode> {
|
|
||||||
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<u32> {
|
|
||||||
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<u32> {
|
|
||||||
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<u32> {
|
|
||||||
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<u32> {
|
|
||||||
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<Extent> {
|
|
||||||
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<u64> {
|
|
||||||
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<u32> {
|
|
||||||
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<u32> {
|
|
||||||
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<FileHandle> {
|
|
||||||
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<B: BlockDevice> FileSystem for Ext4<B> {
|
impl<B: BlockDevice> FileSystem for Ext4<B> {
|
||||||
|
|||||||
80
crates/servers/storage-server/src/fs/ext4/path.rs
Normal file
80
crates/servers/storage-server/src/fs/ext4/path.rs
Normal file
@@ -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<B: BlockDevice> Ext4<B> {
|
||||||
|
pub fn alloc_block(&mut self) -> Option<u32> {
|
||||||
|
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<u32> {
|
||||||
|
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<u32> {
|
||||||
|
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<u32> {
|
||||||
|
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<u32> {
|
||||||
|
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<u32> {
|
||||||
|
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<FileHandle> {
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<Self> {
|
|
||||||
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<u32> {
|
|
||||||
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<B: BlockDevice> {
|
|
||||||
device: B,
|
|
||||||
bpb: Bpb,
|
|
||||||
open_files: [Option<OpenFile>; MAX_OPEN_FILES],
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<B: BlockDevice> Fat16<B> {
|
|
||||||
pub fn mount(mut device: B) -> Option<Self> {
|
|
||||||
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<u16> {
|
|
||||||
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<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 {
|
|
||||||
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<OpenFile>> {
|
|
||||||
self.open_files.get_mut(handle.raw() as usize)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn alloc_handle(&self) -> Option<FileHandle> {
|
|
||||||
for i in 0..MAX_OPEN_FILES {
|
|
||||||
if self.open_files[i].is_none() {
|
|
||||||
return Some(FileHandle::new(i as u16));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<B: BlockDevice> FileSystem for Fat16<B> {
|
|
||||||
fn open(&mut self, path: &[u8]) -> Option<FileHandle> {
|
|
||||||
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<usize> {
|
|
||||||
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<usize> {
|
|
||||||
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<usize> {
|
|
||||||
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<FileHandle> {
|
|
||||||
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<usize> {
|
|
||||||
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])
|
|
||||||
}
|
|
||||||
68
crates/servers/storage-server/src/fs/fat16/bpb.rs
Normal file
68
crates/servers/storage-server/src/fs/fat16/bpb.rs
Normal file
@@ -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<Self> {
|
||||||
|
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<u32> {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
151
crates/servers/storage-server/src/fs/fat16/dir.rs
Normal file
151
crates/servers/storage-server/src/fs/fat16/dir.rs
Normal file
@@ -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<B: BlockDevice> Fat16<B> {
|
||||||
|
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<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 {
|
||||||
|
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])
|
||||||
|
}
|
||||||
64
crates/servers/storage-server/src/fs/fat16/fat.rs
Normal file
64
crates/servers/storage-server/src/fs/fat16/fat.rs
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
//! FAT table: cluster allocation and chains.
|
||||||
|
|
||||||
|
use super::Fat16;
|
||||||
|
use super::SECTOR_SIZE;
|
||||||
|
use port_block_device::{BlockDevice, SectorId};
|
||||||
|
|
||||||
|
impl<B: BlockDevice> Fat16<B> {
|
||||||
|
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<u16> {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
275
crates/servers/storage-server/src/fs/fat16/mod.rs
Normal file
275
crates/servers/storage-server/src/fs/fat16/mod.rs
Normal file
@@ -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<B: BlockDevice> {
|
||||||
|
device: B,
|
||||||
|
bpb: Bpb,
|
||||||
|
open_files: [Option<OpenFile>; MAX_OPEN_FILES],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B: BlockDevice> Fat16<B> {
|
||||||
|
pub fn mount(mut device: B) -> Option<Self> {
|
||||||
|
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<OpenFile>> {
|
||||||
|
self.open_files.get_mut(handle.raw() as usize)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn alloc_handle(&self) -> Option<FileHandle> {
|
||||||
|
for i in 0..MAX_OPEN_FILES {
|
||||||
|
if self.open_files[i].is_none() {
|
||||||
|
return Some(FileHandle::new(i as u16));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B: BlockDevice> FileSystem for Fat16<B> {
|
||||||
|
fn open(&mut self, path: &[u8]) -> Option<FileHandle> {
|
||||||
|
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<usize> {
|
||||||
|
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<usize> {
|
||||||
|
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<usize> {
|
||||||
|
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<FileHandle> {
|
||||||
|
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<usize> {
|
||||||
|
self.slot(handle)?.as_ref().map(|f| f.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mkdir(&mut self, _path: &[u8]) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn writable(&self) -> bool {
|
||||||
|
self.device.writable()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,5 @@
|
|||||||
//! The storage protocol. A client first `Attach`es: the server makes it a
|
//! Storage protocol: a client `Attach`es to get a shared buffer; paths and
|
||||||
//! buffer region and replies with a capability to it, which the client
|
//! data travel through it, message words carry only lengths and handles.
|
||||||
//! 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.
|
|
||||||
|
|
||||||
use server_framework::{Decode, Encode};
|
use server_framework::{Decode, Encode};
|
||||||
use crate::filesystem::FileHandle;
|
use crate::filesystem::FileHandle;
|
||||||
|
|||||||
Reference in New Issue
Block a user