mirror of
https://github.com/asterinas/asterinas.git
synced 2025-06-25 10:23:23 +00:00
Add Ext2 fs and basic bio layer
This commit is contained in:
committed by
Tate, Hongliang Tian
parent
1616f2d32c
commit
9473889c6b
6
Cargo.lock
generated
6
Cargo.lock
generated
@ -121,9 +121,12 @@ dependencies = [
|
||||
"aster-util",
|
||||
"bitflags 1.3.2",
|
||||
"component",
|
||||
"int-to-c-enum",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"pod",
|
||||
"spin 0.9.8",
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -270,10 +273,12 @@ dependencies = [
|
||||
"aster-util",
|
||||
"aster-virtio",
|
||||
"bitflags 1.3.2",
|
||||
"bitvec",
|
||||
"controlled",
|
||||
"core2",
|
||||
"cpio-decoder",
|
||||
"getrandom",
|
||||
"inherit-methods-macro",
|
||||
"int-to-c-enum",
|
||||
"intrusive-collections",
|
||||
"keyable-arc",
|
||||
@ -287,6 +292,7 @@ dependencies = [
|
||||
"ringbuf",
|
||||
"smoltcp",
|
||||
"spin 0.9.8",
|
||||
"static_assertions",
|
||||
"tdx-guest",
|
||||
"time",
|
||||
"typeflags",
|
||||
|
@ -543,7 +543,7 @@ impl<'a> VmReader<'a> {
|
||||
/// Limits the length of remaining data.
|
||||
///
|
||||
/// This method ensures the postcondition of `self.remain() <= max_remain`.
|
||||
pub const fn limit(&mut self, max_remain: usize) -> &mut Self {
|
||||
pub const fn limit(mut self, max_remain: usize) -> Self {
|
||||
if max_remain < self.remain() {
|
||||
// Safety: the new end is less than the old end.
|
||||
unsafe { self.end = self.cursor.add(max_remain) };
|
||||
@ -557,7 +557,7 @@ impl<'a> VmReader<'a> {
|
||||
/// # Panic
|
||||
///
|
||||
/// If `nbytes` is greater than `self.remain()`, then the method panics.
|
||||
pub fn skip(&mut self, nbytes: usize) -> &mut Self {
|
||||
pub fn skip(mut self, nbytes: usize) -> Self {
|
||||
assert!(nbytes <= self.remain());
|
||||
|
||||
// Safety: the new cursor is less than or equal to the end.
|
||||
@ -653,7 +653,7 @@ impl<'a> VmWriter<'a> {
|
||||
/// Limits the length of available space.
|
||||
///
|
||||
/// This method ensures the postcondition of `self.avail() <= max_avail`.
|
||||
pub const fn limit(&mut self, max_avail: usize) -> &mut Self {
|
||||
pub const fn limit(mut self, max_avail: usize) -> Self {
|
||||
if max_avail < self.avail() {
|
||||
// Safety: the new end is less than the old end.
|
||||
unsafe { self.end = self.cursor.add(max_avail) };
|
||||
@ -667,7 +667,7 @@ impl<'a> VmWriter<'a> {
|
||||
/// # Panic
|
||||
///
|
||||
/// If `nbytes` is greater than `self.avail()`, then the method panics.
|
||||
pub fn skip(&mut self, nbytes: usize) -> &mut Self {
|
||||
pub fn skip(mut self, nbytes: usize) -> Self {
|
||||
assert!(nbytes <= self.avail());
|
||||
|
||||
// Safety: the new cursor is less than or equal to the end.
|
||||
|
@ -3,6 +3,7 @@ CUR_DIR := $(patsubst %/,%,$(dir $(MKFILE_PATH)))
|
||||
BUILD_DIR := $(CUR_DIR)/build
|
||||
INITRAMFS := $(BUILD_DIR)/initramfs
|
||||
INITRAMFS_IMAGE := $(BUILD_DIR)/initramfs.cpio.gz
|
||||
EXT2_IMAGE := $(BUILD_DIR)/ext2.img
|
||||
SHELL := /bin/bash
|
||||
INITRAMFS_EMPTY_DIRS := \
|
||||
$(INITRAMFS)/sbin \
|
||||
@ -10,7 +11,8 @@ INITRAMFS_EMPTY_DIRS := \
|
||||
$(INITRAMFS)/tmp \
|
||||
$(INITRAMFS)/opt \
|
||||
$(INITRAMFS)/proc \
|
||||
$(INITRAMFS)/dev
|
||||
$(INITRAMFS)/dev \
|
||||
$(INITRAMFS)/ext2
|
||||
INITRAMFS_ALL_DIRS := \
|
||||
$(INITRAMFS)/etc \
|
||||
$(INITRAMFS)/lib/x86_64-linux-gnu \
|
||||
@ -90,7 +92,11 @@ endif
|
||||
@echo "Generating the initramfs image..."
|
||||
@(cd $(INITRAMFS); find . | cpio -o -H newc | gzip) > $@
|
||||
|
||||
build: $(INITRAMFS_IMAGE)
|
||||
$(EXT2_IMAGE):
|
||||
@dd if=/dev/zero of=$(EXT2_IMAGE) bs=2G count=1
|
||||
@mke2fs $(EXT2_IMAGE)
|
||||
|
||||
build: $(INITRAMFS_IMAGE) $(EXT2_IMAGE)
|
||||
|
||||
clean:
|
||||
@rm -rf $(BUILD_DIR)
|
||||
|
@ -14,7 +14,6 @@ pub mod gdb;
|
||||
pub mod machine;
|
||||
|
||||
use std::{
|
||||
fs::OpenOptions,
|
||||
path::{Path, PathBuf},
|
||||
process::Command,
|
||||
};
|
||||
@ -175,9 +174,11 @@ fn main() {
|
||||
qemu_cmd.args(qemu_grub_efi::NOIOMMU_DEVICE_ARGS);
|
||||
}
|
||||
|
||||
let fs_image = create_fs_image(args.path.as_path());
|
||||
// TODO: Add arguments to the runner CLI tool so that the user can specify
|
||||
// a list of disk drives, each of which may be in a different FS format.
|
||||
let ext2_image = get_fs_image(&PathBuf::from("regression/build/ext2.img"), 0);
|
||||
qemu_cmd.arg("-drive");
|
||||
qemu_cmd.arg(fs_image);
|
||||
qemu_cmd.arg(ext2_image);
|
||||
|
||||
if args.boot_method == BootMethod::Microvm {
|
||||
let image = microvm::create_bootdev_image(args.path);
|
||||
@ -221,20 +222,14 @@ fn main() {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_fs_image(path: &Path) -> String {
|
||||
let mut fs_img_path = path.parent().unwrap().to_str().unwrap().to_string();
|
||||
fs_img_path.push_str("/fs.img");
|
||||
let path = Path::new(fs_img_path.as_str());
|
||||
if path.exists() {
|
||||
return format!("file={},if=none,format=raw,id=x0", fs_img_path.as_str());
|
||||
pub fn get_fs_image(path: &Path, drive_id: u32) -> String {
|
||||
if !path.exists() {
|
||||
panic!("can not find the fs image")
|
||||
}
|
||||
let f = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create(true)
|
||||
.open(fs_img_path.as_str())
|
||||
.unwrap();
|
||||
// 32MiB
|
||||
f.set_len(64 * 1024 * 1024).unwrap();
|
||||
format!("file={},if=none,format=raw,id=x0", fs_img_path.as_str())
|
||||
|
||||
format!(
|
||||
"file={},if=none,format=raw,id=x{}",
|
||||
path.to_string_lossy(),
|
||||
drive_id
|
||||
)
|
||||
}
|
||||
|
@ -8,10 +8,13 @@ edition = "2021"
|
||||
[dependencies]
|
||||
bitflags = "1.3"
|
||||
spin = "0.9.4"
|
||||
pod = { git = "https://github.com/asterinas/pod", rev = "d7dba56" }
|
||||
aster-frame = { path = "../../../framework/aster-frame" }
|
||||
aster-util = { path = "../../libs/aster-util" }
|
||||
int-to-c-enum = { path = "../../libs/int-to-c-enum" }
|
||||
component = { path = "../../libs/comp-sys/component" }
|
||||
log = "0.4"
|
||||
static_assertions = "1.1.0"
|
||||
|
||||
[features]
|
||||
|
||||
|
475
services/comps/block/src/bio.rs
Normal file
475
services/comps/block/src/bio.rs
Normal file
@ -0,0 +1,475 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
use super::{id::Sid, BlockDevice};
|
||||
|
||||
use aster_frame::{
|
||||
sync::WaitQueue,
|
||||
vm::{VmFrame, VmReader, VmSegment, VmWriter},
|
||||
};
|
||||
use int_to_c_enum::TryFromInt;
|
||||
|
||||
/// The unit for block I/O.
|
||||
///
|
||||
/// Each `Bio` packs the following information:
|
||||
/// (1) The type of the I/O,
|
||||
/// (2) The target sectors on the device for doing I/O,
|
||||
/// (3) The memory locations (`BioSegment`) from/to which data are read/written,
|
||||
/// (4) The optional callback function that will be invoked when the I/O is completed.
|
||||
pub struct Bio(Arc<BioInner>);
|
||||
|
||||
impl Bio {
|
||||
/// Constructs a new `Bio`.
|
||||
///
|
||||
/// The `type_` describes the type of the I/O.
|
||||
/// The `start_sid` is the starting sector id on the device.
|
||||
/// The `segments` describes the memory segments.
|
||||
/// The `complete_fn` is the optional callback function.
|
||||
pub fn new(
|
||||
type_: BioType,
|
||||
start_sid: Sid,
|
||||
segments: Vec<BioSegment>,
|
||||
complete_fn: Option<fn(&SubmittedBio)>,
|
||||
) -> Self {
|
||||
let nsectors = segments
|
||||
.iter()
|
||||
.map(|segment| segment.nsectors().to_raw())
|
||||
.sum();
|
||||
|
||||
let inner = Arc::new(BioInner {
|
||||
type_,
|
||||
sid_range: start_sid..start_sid + nsectors,
|
||||
segments,
|
||||
complete_fn,
|
||||
status: AtomicU32::new(BioStatus::Init as u32),
|
||||
wait_queue: WaitQueue::new(),
|
||||
});
|
||||
Self(inner)
|
||||
}
|
||||
|
||||
/// Returns the type.
|
||||
pub fn type_(&self) -> BioType {
|
||||
self.0.type_()
|
||||
}
|
||||
|
||||
/// Returns the range of target sectors on the device.
|
||||
pub fn sid_range(&self) -> &Range<Sid> {
|
||||
self.0.sid_range()
|
||||
}
|
||||
|
||||
/// Returns the slice to the memory segments.
|
||||
pub fn segments(&self) -> &[BioSegment] {
|
||||
self.0.segments()
|
||||
}
|
||||
|
||||
/// Returns the status.
|
||||
pub fn status(&self) -> BioStatus {
|
||||
self.0.status()
|
||||
}
|
||||
|
||||
/// Submits self to the `block_device` asynchronously.
|
||||
///
|
||||
/// Returns a `BioWaiter` to the caller to wait for its completion.
|
||||
///
|
||||
/// # Panic
|
||||
///
|
||||
/// The caller must not submit a `Bio` more than once. Otherwise, a panic shall be triggered.
|
||||
pub fn submit(&self, block_device: &dyn BlockDevice) -> Result<BioWaiter, BioEnqueueError> {
|
||||
// Change the status from "Init" to "Submit".
|
||||
let result = self.0.status.compare_exchange(
|
||||
BioStatus::Init as u32,
|
||||
BioStatus::Submit as u32,
|
||||
Ordering::Release,
|
||||
Ordering::Relaxed,
|
||||
);
|
||||
assert!(result.is_ok());
|
||||
|
||||
if let Err(e) = block_device
|
||||
.request_queue()
|
||||
.enqueue(SubmittedBio(self.0.clone()))
|
||||
{
|
||||
// Fail to submit, revert the status.
|
||||
let result = self.0.status.compare_exchange(
|
||||
BioStatus::Submit as u32,
|
||||
BioStatus::Init as u32,
|
||||
Ordering::Release,
|
||||
Ordering::Relaxed,
|
||||
);
|
||||
assert!(result.is_ok());
|
||||
return Err(e);
|
||||
}
|
||||
|
||||
Ok(BioWaiter {
|
||||
bios: vec![self.0.clone()],
|
||||
})
|
||||
}
|
||||
|
||||
/// Submits self to the `block_device` and waits for the result synchronously.
|
||||
///
|
||||
/// Returns the result status of the `Bio`.
|
||||
///
|
||||
/// # Panic
|
||||
///
|
||||
/// The caller must not submit a `Bio` more than once. Otherwise, a panic shall be triggered.
|
||||
pub fn submit_sync(
|
||||
&self,
|
||||
block_device: &dyn BlockDevice,
|
||||
) -> Result<BioStatus, BioEnqueueError> {
|
||||
let waiter = self.submit(block_device)?;
|
||||
match waiter.wait() {
|
||||
Some(status) => {
|
||||
assert!(status == BioStatus::Complete);
|
||||
Ok(status)
|
||||
}
|
||||
None => {
|
||||
let status = self.status();
|
||||
assert!(status != BioStatus::Complete);
|
||||
Ok(status)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The error type returned when enqueueing the `Bio`.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub enum BioEnqueueError {
|
||||
/// The request queue is full
|
||||
IsFull,
|
||||
/// Refuse to enqueue the bio
|
||||
Refused,
|
||||
}
|
||||
|
||||
impl From<BioEnqueueError> for aster_frame::Error {
|
||||
fn from(_error: BioEnqueueError) -> Self {
|
||||
aster_frame::Error::NotEnoughResources
|
||||
}
|
||||
}
|
||||
|
||||
/// A waiter for `Bio` submissions.
|
||||
///
|
||||
/// This structure holds a list of `Bio` requests and provides functionality to
|
||||
/// wait for their completion and retrieve their statuses.
|
||||
#[must_use]
|
||||
pub struct BioWaiter {
|
||||
bios: Vec<Arc<BioInner>>,
|
||||
}
|
||||
|
||||
impl BioWaiter {
|
||||
/// Constructs a new `BioWaiter` instance with no `Bio` requests.
|
||||
pub fn new() -> Self {
|
||||
Self { bios: Vec::new() }
|
||||
}
|
||||
|
||||
/// Returns the number of `Bio` requests associated with `self`.
|
||||
pub fn nreqs(&self) -> usize {
|
||||
self.bios.len()
|
||||
}
|
||||
|
||||
/// Gets the `index`-th `Bio` request associated with `self`.
|
||||
///
|
||||
/// # Panic
|
||||
///
|
||||
/// If the `index` is out of bounds, this method will panic.
|
||||
pub fn req(&self, index: usize) -> Bio {
|
||||
Bio(self.bios[index].clone())
|
||||
}
|
||||
|
||||
/// Returns the status of the `index`-th `Bio` request associated with `self`.
|
||||
///
|
||||
/// # Panic
|
||||
///
|
||||
/// If the `index` is out of bounds, this method will panic.
|
||||
pub fn status(&self, index: usize) -> BioStatus {
|
||||
self.bios[index].status()
|
||||
}
|
||||
|
||||
/// Merges the `Bio` requests from another `BioWaiter` into this one.
|
||||
///
|
||||
/// The another `BioWaiter`'s `Bio` requests are appended to the end of
|
||||
/// the `Bio` list of `self`, effectively concatenating the two lists.
|
||||
pub fn concat(&mut self, mut other: Self) {
|
||||
self.bios.append(&mut other.bios);
|
||||
}
|
||||
|
||||
/// Waits for the completion of all `Bio` requests.
|
||||
///
|
||||
/// This method iterates through each `Bio` in the list, waiting for their
|
||||
/// completion.
|
||||
///
|
||||
/// The return value is an option indicating whether all the requests in the list
|
||||
/// have successfully completed.
|
||||
/// On success this value is guaranteed to be equal to `Some(BioStatus::Complete)`.
|
||||
pub fn wait(&self) -> Option<BioStatus> {
|
||||
let mut ret = Some(BioStatus::Complete);
|
||||
|
||||
for bio in self.bios.iter() {
|
||||
let status = bio.wait_queue.wait_until(|| {
|
||||
let status = bio.status();
|
||||
if status != BioStatus::Submit {
|
||||
Some(status)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
if status != BioStatus::Complete && ret.is_some() {
|
||||
ret = None;
|
||||
}
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for BioWaiter {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// A submitted `Bio` object.
|
||||
///
|
||||
/// The request queue of block device only accepts a `SubmittedBio` into the queue.
|
||||
pub struct SubmittedBio(Arc<BioInner>);
|
||||
|
||||
impl SubmittedBio {
|
||||
/// Returns the type.
|
||||
pub fn type_(&self) -> BioType {
|
||||
self.0.type_()
|
||||
}
|
||||
|
||||
/// Returns the range of target sectors on the device.
|
||||
pub fn sid_range(&self) -> &Range<Sid> {
|
||||
self.0.sid_range()
|
||||
}
|
||||
|
||||
/// Returns the slice to the memory segments.
|
||||
pub fn segments(&self) -> &[BioSegment] {
|
||||
self.0.segments()
|
||||
}
|
||||
|
||||
/// Returns the status.
|
||||
pub fn status(&self) -> BioStatus {
|
||||
self.0.status()
|
||||
}
|
||||
|
||||
/// Completes the `Bio` with the `status` and invokes the callback function.
|
||||
///
|
||||
/// When the driver finishes the request for this `Bio`, it will call this method.
|
||||
pub fn complete(&self, status: BioStatus) {
|
||||
assert!(status != BioStatus::Init && status != BioStatus::Submit);
|
||||
|
||||
// Set the status.
|
||||
let result = self.0.status.compare_exchange(
|
||||
BioStatus::Submit as u32,
|
||||
status as u32,
|
||||
Ordering::Release,
|
||||
Ordering::Relaxed,
|
||||
);
|
||||
assert!(result.is_ok());
|
||||
|
||||
self.0.wait_queue.wake_all();
|
||||
if let Some(complete_fn) = self.0.complete_fn {
|
||||
complete_fn(self);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The common inner part of `Bio`.
|
||||
struct BioInner {
|
||||
/// The type of the I/O
|
||||
type_: BioType,
|
||||
/// The range of the sector id on device
|
||||
sid_range: Range<Sid>,
|
||||
/// The memory segments in this `Bio`
|
||||
segments: Vec<BioSegment>,
|
||||
/// The I/O completion method
|
||||
complete_fn: Option<fn(&SubmittedBio)>,
|
||||
/// The I/O status
|
||||
status: AtomicU32,
|
||||
/// The wait queue for I/O completion
|
||||
wait_queue: WaitQueue,
|
||||
}
|
||||
|
||||
impl BioInner {
|
||||
pub fn type_(&self) -> BioType {
|
||||
self.type_
|
||||
}
|
||||
|
||||
pub fn sid_range(&self) -> &Range<Sid> {
|
||||
&self.sid_range
|
||||
}
|
||||
|
||||
pub fn segments(&self) -> &[BioSegment] {
|
||||
&self.segments
|
||||
}
|
||||
|
||||
pub fn status(&self) -> BioStatus {
|
||||
BioStatus::try_from(self.status.load(Ordering::Relaxed)).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/// The type of `Bio`.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, TryFromInt)]
|
||||
#[repr(u8)]
|
||||
pub enum BioType {
|
||||
/// Read sectors from the device.
|
||||
Read = 0,
|
||||
/// Write sectors into the device.
|
||||
Write = 1,
|
||||
/// Flush the volatile write cache.
|
||||
Flush = 2,
|
||||
/// Discard sectors.
|
||||
Discard = 3,
|
||||
}
|
||||
|
||||
/// The status of `Bio`.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, TryFromInt)]
|
||||
#[repr(u32)]
|
||||
pub enum BioStatus {
|
||||
/// The initial status for a newly created `Bio`.
|
||||
Init = 0,
|
||||
/// After a `Bio` is submitted, its status will be changed to "Submit".
|
||||
Submit = 1,
|
||||
/// The I/O operation has been successfully completed.
|
||||
Complete = 2,
|
||||
/// The I/O operation is not supported.
|
||||
NotSupported = 3,
|
||||
/// Insufficient space is available to perform the I/O operation.
|
||||
NoSpace = 4,
|
||||
/// An error occurred while doing I/O.
|
||||
IoError = 5,
|
||||
}
|
||||
|
||||
/// `BioSegment` is a smallest memory unit in block I/O.
|
||||
///
|
||||
/// It is a contiguous memory region that contains multiple sectors.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BioSegment {
|
||||
/// The contiguous pages on which this segment resides.
|
||||
pages: Pages,
|
||||
/// The offset (in bytes) relative to the first page.
|
||||
offset: AlignedUsize<SECTOR_SIZE>,
|
||||
// The length (in bytes), may cross pages.
|
||||
len: AlignedUsize<SECTOR_SIZE>,
|
||||
}
|
||||
|
||||
const SECTOR_SIZE: u16 = super::SECTOR_SIZE as u16;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum Pages {
|
||||
Frame(VmFrame),
|
||||
Segment(VmSegment),
|
||||
}
|
||||
|
||||
impl<'a> BioSegment {
|
||||
/// Constructs a new `BioSegment` from `VmSegment`.
|
||||
pub fn from_segment(segment: VmSegment, offset: usize, len: usize) -> Self {
|
||||
assert!(offset + len <= segment.nbytes());
|
||||
|
||||
Self {
|
||||
pages: Pages::Segment(segment),
|
||||
offset: AlignedUsize::<SECTOR_SIZE>::new(offset).unwrap(),
|
||||
len: AlignedUsize::<SECTOR_SIZE>::new(len).unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Constructs a new `BioSegment` from `VmFrame`.
|
||||
pub fn from_frame(frame: VmFrame, offset: usize, len: usize) -> Self {
|
||||
assert!(offset + len <= super::BLOCK_SIZE);
|
||||
|
||||
Self {
|
||||
pages: Pages::Frame(frame),
|
||||
offset: AlignedUsize::<SECTOR_SIZE>::new(offset).unwrap(),
|
||||
len: AlignedUsize::<SECTOR_SIZE>::new(len).unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the number of sectors.
|
||||
pub fn nsectors(&self) -> Sid {
|
||||
Sid::from_offset(self.len.value())
|
||||
}
|
||||
|
||||
/// Returns the number of bytes.
|
||||
pub fn nbytes(&self) -> usize {
|
||||
self.len.value()
|
||||
}
|
||||
|
||||
/// Returns a reader to read data from it.
|
||||
pub fn reader(&'a self) -> VmReader<'a> {
|
||||
let reader = match &self.pages {
|
||||
Pages::Segment(segment) => segment.reader(),
|
||||
Pages::Frame(frame) => frame.reader(),
|
||||
};
|
||||
reader.skip(self.offset.value()).limit(self.len.value())
|
||||
}
|
||||
|
||||
/// Returns a writer to write data into it.
|
||||
pub fn writer(&'a self) -> VmWriter<'a> {
|
||||
let writer = match &self.pages {
|
||||
Pages::Segment(segment) => segment.writer(),
|
||||
Pages::Frame(frame) => frame.writer(),
|
||||
};
|
||||
writer.skip(self.offset.value()).limit(self.len.value())
|
||||
}
|
||||
}
|
||||
|
||||
/// An aligned unsigned integer number.
|
||||
///
|
||||
/// An instance of `AlignedUsize<const N: u16>` is guaranteed to have a value that is a multiple
|
||||
/// of `N`, a predetermined const value. It is preferable to express an unsigned integer value
|
||||
/// in type `AlignedUsize<_>` instead of `usize` if the value must satisfy an alignment requirement.
|
||||
/// This helps readability and prevents bugs.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// const SECTOR_SIZE: u16 = 512;
|
||||
///
|
||||
/// let sector_num = 1234; // The 1234-th sector
|
||||
/// let sector_offset: AlignedUsize<SECTOR_SIZE> = {
|
||||
/// let sector_offset = sector_num * (SECTOR_SIZE as usize);
|
||||
/// AlignedUsize::<SECTOR_SIZE>::new(sector_offset).unwrap()
|
||||
/// };
|
||||
/// assert!(sector_offset.value() % sector_offset.align() == 0);
|
||||
/// ```
|
||||
///
|
||||
/// # Limitation
|
||||
///
|
||||
/// Currently, the alignment const value must be expressed in `u16`;
|
||||
/// it is not possible to use a larger or smaller type.
|
||||
/// This limitation is inherited from that of Rust's const generics:
|
||||
/// your code can be generic over the _value_ of a const, but not the _type_ of the const.
|
||||
/// We choose `u16` because it is reasonably large to represent any alignment value
|
||||
/// used in practice.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AlignedUsize<const N: u16>(usize);
|
||||
|
||||
impl<const N: u16> AlignedUsize<N> {
|
||||
/// Constructs a new instance of aligned integer if the given value is aligned.
|
||||
pub fn new(val: usize) -> Option<Self> {
|
||||
if val % (N as usize) == 0 {
|
||||
Some(Self(val))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the value.
|
||||
pub fn value(&self) -> usize {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Returns the corresponding ID.
|
||||
///
|
||||
/// The so-called "ID" of an aligned integer is defined to be `self.value() / self.align()`.
|
||||
/// This value is named ID because one common use case is using `Aligned` to express
|
||||
/// the byte offset of a sector, block, or page. In this case, the `id` method returns
|
||||
/// the ID of the corresponding sector, block, or page.
|
||||
pub fn id(&self) -> usize {
|
||||
self.value() / self.align()
|
||||
}
|
||||
|
||||
/// Returns the alignment.
|
||||
pub fn align(&self) -> usize {
|
||||
N as usize
|
||||
}
|
||||
}
|
100
services/comps/block/src/id.rs
Normal file
100
services/comps/block/src/id.rs
Normal file
@ -0,0 +1,100 @@
|
||||
use core::{
|
||||
iter::Step,
|
||||
ops::{Add, Sub},
|
||||
};
|
||||
use pod::Pod;
|
||||
use static_assertions::const_assert;
|
||||
|
||||
/// The block index used in the filesystem.
|
||||
pub type Bid = BlockId<BLOCK_SIZE>;
|
||||
/// The sector index used in the device.
|
||||
pub type Sid = BlockId<SECTOR_SIZE>;
|
||||
|
||||
impl From<Bid> for Sid {
|
||||
fn from(bid: Bid) -> Self {
|
||||
Self::new(bid.to_raw() * (BLOCK_SIZE / SECTOR_SIZE) as u64)
|
||||
}
|
||||
}
|
||||
|
||||
const BLOCK_SIZE: u16 = super::BLOCK_SIZE as u16;
|
||||
const SECTOR_SIZE: u16 = super::SECTOR_SIZE as u16;
|
||||
const_assert!(BLOCK_SIZE / SECTOR_SIZE >= 1);
|
||||
|
||||
/// An index of a block.
|
||||
///
|
||||
/// The `BlockId<const N: u16>` is a generic type that is parameterized by a constant `N`, which
|
||||
/// represents the size of each block in bytes. The `BlockId<_>` provides a type-safe way of handling
|
||||
/// block indices.
|
||||
/// An Instance of `BlockId<_>` is guaranteed to represent valid block index, derived from byte offset
|
||||
/// and the specified block size `N`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// const BLOCK_SIZE: u16 = 512;
|
||||
///
|
||||
/// let bytes_offset = 2048;
|
||||
/// let block_id = BlockId<BLOCK_SIZE>::from_offset(bytes_offset);
|
||||
/// assert!(block_id == (bytes_offset / BLOCK_SIZE));
|
||||
/// ```
|
||||
///
|
||||
/// # Limitation
|
||||
///
|
||||
/// Currently, the block size is expressed in `u16`. We choose `u16` because
|
||||
/// it is reasonably large to represent the common block size used in practice.
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Pod)]
|
||||
pub struct BlockId<const N: u16>(u64);
|
||||
|
||||
impl<const N: u16> BlockId<N> {
|
||||
/// Constructs an id from a raw id.
|
||||
pub const fn new(raw_id: u64) -> Self {
|
||||
Self(raw_id)
|
||||
}
|
||||
|
||||
/// Constructs an id from a byte offset.
|
||||
pub const fn from_offset(offset: usize) -> Self {
|
||||
Self((offset / (N as usize)) as _)
|
||||
}
|
||||
|
||||
/// Converts to a byte offset.
|
||||
pub fn to_offset(self) -> usize {
|
||||
(self.0 as usize) * (N as usize)
|
||||
}
|
||||
|
||||
/// Converts to raw id.
|
||||
pub fn to_raw(self) -> u64 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: u16> Add<u64> for BlockId<N> {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, other: u64) -> Self::Output {
|
||||
Self(self.0 + other)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: u16> Sub<u64> for BlockId<N> {
|
||||
type Output = Self;
|
||||
|
||||
fn sub(self, other: u64) -> Self::Output {
|
||||
Self(self.0 - other)
|
||||
}
|
||||
}
|
||||
|
||||
/// Implements the `Step` trait to iterate over `Range<Id>`.
|
||||
impl<const N: u16> Step for BlockId<N> {
|
||||
fn steps_between(start: &Self, end: &Self) -> Option<usize> {
|
||||
u64::steps_between(&start.0, &end.0)
|
||||
}
|
||||
|
||||
fn forward_checked(start: Self, count: usize) -> Option<Self> {
|
||||
u64::forward_checked(start.0, count).map(Self::new)
|
||||
}
|
||||
|
||||
fn backward_checked(start: Self, count: usize) -> Option<Self> {
|
||||
u64::backward_checked(start.0, count).map(Self::new)
|
||||
}
|
||||
}
|
237
services/comps/block/src/impl_block_device.rs
Normal file
237
services/comps/block/src/impl_block_device.rs
Normal file
@ -0,0 +1,237 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
use super::{
|
||||
bio::{Bio, BioEnqueueError, BioSegment, BioStatus, BioType, BioWaiter, SubmittedBio},
|
||||
id::{Bid, Sid},
|
||||
BlockDevice, BLOCK_SIZE, SECTOR_SIZE,
|
||||
};
|
||||
|
||||
use aster_frame::vm::{VmAllocOptions, VmFrame, VmIo, VmSegment};
|
||||
|
||||
/// Implements several commonly used APIs for the block device to conveniently
|
||||
/// read and write block(s).
|
||||
impl dyn BlockDevice {
|
||||
/// Synchronously reads contiguous blocks starting from the `bid`.
|
||||
pub fn read_blocks_sync(
|
||||
&self,
|
||||
bid: Bid,
|
||||
segment: &VmSegment,
|
||||
) -> Result<BioStatus, BioEnqueueError> {
|
||||
let bio = create_bio_from_segment(BioType::Read, bid, segment);
|
||||
let status = bio.submit_sync(self)?;
|
||||
Ok(status)
|
||||
}
|
||||
|
||||
/// Asynchronously reads contiguous blocks starting from the `bid`.
|
||||
pub fn read_blocks(&self, bid: Bid, segment: &VmSegment) -> Result<BioWaiter, BioEnqueueError> {
|
||||
let bio = create_bio_from_segment(BioType::Read, bid, segment);
|
||||
bio.submit(self)
|
||||
}
|
||||
|
||||
/// Synchronously reads one block indicated by the `bid`.
|
||||
pub fn read_block_sync(&self, bid: Bid, frame: &VmFrame) -> Result<BioStatus, BioEnqueueError> {
|
||||
let bio = create_bio_from_frame(BioType::Read, bid, frame);
|
||||
let status = bio.submit_sync(self)?;
|
||||
Ok(status)
|
||||
}
|
||||
|
||||
/// Asynchronously reads one block indicated by the `bid`.
|
||||
pub fn read_block(&self, bid: Bid, frame: &VmFrame) -> Result<BioWaiter, BioEnqueueError> {
|
||||
let bio = create_bio_from_frame(BioType::Read, bid, frame);
|
||||
bio.submit(self)
|
||||
}
|
||||
|
||||
/// Synchronously writes contiguous blocks starting from the `bid`.
|
||||
pub fn write_blocks_sync(
|
||||
&self,
|
||||
bid: Bid,
|
||||
segment: &VmSegment,
|
||||
) -> Result<BioStatus, BioEnqueueError> {
|
||||
let bio = create_bio_from_segment(BioType::Write, bid, segment);
|
||||
let status = bio.submit_sync(self)?;
|
||||
Ok(status)
|
||||
}
|
||||
|
||||
/// Asynchronously writes contiguous blocks starting from the `bid`.
|
||||
pub fn write_blocks(
|
||||
&self,
|
||||
bid: Bid,
|
||||
segment: &VmSegment,
|
||||
) -> Result<BioWaiter, BioEnqueueError> {
|
||||
let bio = create_bio_from_segment(BioType::Write, bid, segment);
|
||||
bio.submit(self)
|
||||
}
|
||||
|
||||
/// Synchronously writes one block indicated by the `bid`.
|
||||
pub fn write_block_sync(
|
||||
&self,
|
||||
bid: Bid,
|
||||
frame: &VmFrame,
|
||||
) -> Result<BioStatus, BioEnqueueError> {
|
||||
let bio = create_bio_from_frame(BioType::Write, bid, frame);
|
||||
let status = bio.submit_sync(self)?;
|
||||
Ok(status)
|
||||
}
|
||||
|
||||
/// Asynchronously writes one block indicated by the `bid`.
|
||||
pub fn write_block(&self, bid: Bid, frame: &VmFrame) -> Result<BioWaiter, BioEnqueueError> {
|
||||
let bio = create_bio_from_frame(BioType::Write, bid, frame);
|
||||
bio.submit(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl VmIo for dyn BlockDevice {
|
||||
/// Reads consecutive bytes of several sectors in size.
|
||||
fn read_bytes(&self, offset: usize, buf: &mut [u8]) -> aster_frame::Result<()> {
|
||||
if offset % SECTOR_SIZE != 0 || buf.len() % SECTOR_SIZE != 0 {
|
||||
return Err(aster_frame::Error::InvalidArgs);
|
||||
}
|
||||
if buf.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let (bio, bio_segment) = {
|
||||
let num_blocks = {
|
||||
let first = Bid::from_offset(offset).to_raw();
|
||||
let last = Bid::from_offset(offset + buf.len() - 1).to_raw();
|
||||
last - first + 1
|
||||
};
|
||||
let segment = VmAllocOptions::new(num_blocks as usize)
|
||||
.uninit(true)
|
||||
.is_contiguous(true)
|
||||
.alloc_contiguous()?;
|
||||
let bio_segment = BioSegment::from_segment(segment, offset % BLOCK_SIZE, buf.len());
|
||||
|
||||
(
|
||||
Bio::new(
|
||||
BioType::Read,
|
||||
Sid::from_offset(offset),
|
||||
vec![bio_segment.clone()],
|
||||
None,
|
||||
),
|
||||
bio_segment,
|
||||
)
|
||||
};
|
||||
|
||||
let status = bio.submit_sync(self)?;
|
||||
match status {
|
||||
BioStatus::Complete => {
|
||||
let _ = bio_segment.reader().read(&mut buf.into());
|
||||
Ok(())
|
||||
}
|
||||
_ => Err(aster_frame::Error::IoError),
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes consecutive bytes of several sectors in size.
|
||||
fn write_bytes(&self, offset: usize, buf: &[u8]) -> aster_frame::Result<()> {
|
||||
if offset % SECTOR_SIZE != 0 || buf.len() % SECTOR_SIZE != 0 {
|
||||
return Err(aster_frame::Error::InvalidArgs);
|
||||
}
|
||||
if buf.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let bio = {
|
||||
let num_blocks = {
|
||||
let first = Bid::from_offset(offset).to_raw();
|
||||
let last = Bid::from_offset(offset + buf.len() - 1).to_raw();
|
||||
last - first + 1
|
||||
};
|
||||
let segment = VmAllocOptions::new(num_blocks as usize)
|
||||
.uninit(true)
|
||||
.is_contiguous(true)
|
||||
.alloc_contiguous()?;
|
||||
segment.write_bytes(offset % BLOCK_SIZE, buf)?;
|
||||
let len = segment
|
||||
.writer()
|
||||
.skip(offset % BLOCK_SIZE)
|
||||
.write(&mut buf.into());
|
||||
let bio_segment = BioSegment::from_segment(segment, offset % BLOCK_SIZE, len);
|
||||
Bio::new(
|
||||
BioType::Write,
|
||||
Sid::from_offset(offset),
|
||||
vec![bio_segment],
|
||||
None,
|
||||
)
|
||||
};
|
||||
|
||||
let status = bio.submit_sync(self)?;
|
||||
match status {
|
||||
BioStatus::Complete => Ok(()),
|
||||
_ => Err(aster_frame::Error::IoError),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl dyn BlockDevice {
|
||||
/// Asynchronously writes consecutive bytes of several sectors in size.
|
||||
pub fn write_bytes_async(&self, offset: usize, buf: &[u8]) -> aster_frame::Result<BioWaiter> {
|
||||
if offset % SECTOR_SIZE != 0 || buf.len() % SECTOR_SIZE != 0 {
|
||||
return Err(aster_frame::Error::InvalidArgs);
|
||||
}
|
||||
if buf.is_empty() {
|
||||
return Ok(BioWaiter::new());
|
||||
}
|
||||
|
||||
let bio = {
|
||||
let num_blocks = {
|
||||
let first = Bid::from_offset(offset).to_raw();
|
||||
let last = Bid::from_offset(offset + buf.len() - 1).to_raw();
|
||||
last - first + 1
|
||||
};
|
||||
let segment = VmAllocOptions::new(num_blocks as usize)
|
||||
.uninit(true)
|
||||
.is_contiguous(true)
|
||||
.alloc_contiguous()?;
|
||||
segment.write_bytes(offset % BLOCK_SIZE, buf)?;
|
||||
let len = segment
|
||||
.writer()
|
||||
.skip(offset % BLOCK_SIZE)
|
||||
.write(&mut buf.into());
|
||||
let bio_segment = BioSegment::from_segment(segment, offset % BLOCK_SIZE, len);
|
||||
Bio::new(
|
||||
BioType::Write,
|
||||
Sid::from_offset(offset),
|
||||
vec![bio_segment],
|
||||
Some(general_complete_fn),
|
||||
)
|
||||
};
|
||||
|
||||
let complete = bio.submit(self)?;
|
||||
Ok(complete)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Maybe we should have a builder for `Bio`.
|
||||
fn create_bio_from_segment(type_: BioType, bid: Bid, segment: &VmSegment) -> Bio {
|
||||
let bio_segment = BioSegment::from_segment(segment.clone(), 0, segment.nbytes());
|
||||
Bio::new(
|
||||
type_,
|
||||
Sid::from(bid),
|
||||
vec![bio_segment],
|
||||
Some(general_complete_fn),
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: Maybe we should have a builder for `Bio`.
|
||||
fn create_bio_from_frame(type_: BioType, bid: Bid, frame: &VmFrame) -> Bio {
|
||||
let bio_segment = BioSegment::from_frame(frame.clone(), 0, BLOCK_SIZE);
|
||||
Bio::new(
|
||||
type_,
|
||||
Sid::from(bid),
|
||||
vec![bio_segment],
|
||||
Some(general_complete_fn),
|
||||
)
|
||||
}
|
||||
|
||||
fn general_complete_fn(bio: &SubmittedBio) {
|
||||
match bio.status() {
|
||||
BioStatus::Complete => (),
|
||||
err_status => log::error!(
|
||||
"faild to do {:?} on the device with error status: {:?}",
|
||||
bio.type_(),
|
||||
err_status
|
||||
),
|
||||
}
|
||||
}
|
@ -1,32 +1,67 @@
|
||||
//! The block devices of Asterinas.
|
||||
//!
|
||||
//!This crate provides a number of base components for block devices, including
|
||||
//! an abstraction of block devices, as well as the registration and lookup of block devices.
|
||||
//!
|
||||
//! Block devices use a queue-based model for asynchronous I/O operations. It is necessary
|
||||
//! for a block device to maintain a queue to handle I/O requests. The users (e.g., fs)
|
||||
//! submit I/O requests to this queue and wait for their completion. Drivers implementing
|
||||
//! block devices can create their own queues as needed, with the possibility to reorder
|
||||
//! and merge requests within the queue.
|
||||
//!
|
||||
//! This crate also offers the `Bio` related data structures and APIs to accomplish
|
||||
//! safe and convenient block I/O operations, for exmaple:
|
||||
//!
|
||||
//! ```no_run
|
||||
//! // Creates a bio request.
|
||||
//! let bio = Bio::new(BioType::Write, sid, segments, None);
|
||||
//! // Submits to the block device.
|
||||
//! let bio_waiter = bio.submit(block_device)?;
|
||||
//! // Waits for the the completion.
|
||||
//! let Some(status) = bio_waiter.wait() else {
|
||||
//! return Err(IoError);
|
||||
//! };
|
||||
//! assert!(status == BioStatus::Complete);
|
||||
//! ```
|
||||
//!
|
||||
#![no_std]
|
||||
#![forbid(unsafe_code)]
|
||||
#![feature(fn_traits)]
|
||||
#![feature(step_trait)]
|
||||
#![feature(trait_upcasting)]
|
||||
#![allow(dead_code)]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
use core::any::Any;
|
||||
use core::fmt::Debug;
|
||||
pub mod bio;
|
||||
pub mod id;
|
||||
mod impl_block_device;
|
||||
mod prelude;
|
||||
pub mod request_queue;
|
||||
|
||||
use self::{prelude::*, request_queue::BioRequestQueue};
|
||||
|
||||
use alloc::collections::BTreeMap;
|
||||
use alloc::string::String;
|
||||
use alloc::sync::Arc;
|
||||
use alloc::vec::Vec;
|
||||
use aster_frame::sync::SpinLock;
|
||||
use aster_frame::vm::VmReader;
|
||||
use aster_frame::vm::VmWriter;
|
||||
use component::init_component;
|
||||
use component::ComponentInitError;
|
||||
|
||||
use spin::Once;
|
||||
|
||||
pub const BLK_SIZE: usize = 512;
|
||||
pub const BLOCK_SIZE: usize = aster_frame::config::PAGE_SIZE;
|
||||
pub const SECTOR_SIZE: usize = 512;
|
||||
|
||||
pub trait BlockDevice: Send + Sync + Any + Debug {
|
||||
fn read_block(&self, block_id: usize, buf: &[VmWriter]);
|
||||
fn write_block(&self, block_id: usize, buf: &[VmReader]);
|
||||
/// Returns this block device's request queue, to which block I/O requests may be submitted.
|
||||
fn request_queue(&self) -> &dyn BioRequestQueue;
|
||||
fn handle_irq(&self);
|
||||
}
|
||||
|
||||
impl dyn BlockDevice {
|
||||
pub fn downcast_ref<T: BlockDevice>(&self) -> Option<&T> {
|
||||
(self as &dyn Any).downcast_ref::<T>()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register_device(name: String, device: Arc<dyn BlockDevice>) {
|
||||
COMPONENT
|
||||
.get()
|
||||
|
9
services/comps/block/src/prelude.rs
Normal file
9
services/comps/block/src/prelude.rs
Normal file
@ -0,0 +1,9 @@
|
||||
pub(crate) use alloc::collections::{BTreeMap, VecDeque};
|
||||
pub(crate) use alloc::string::String;
|
||||
pub(crate) use alloc::sync::Arc;
|
||||
pub(crate) use alloc::vec;
|
||||
pub(crate) use alloc::vec::Vec;
|
||||
pub(crate) use core::any::Any;
|
||||
pub(crate) use core::fmt::Debug;
|
||||
pub(crate) use core::ops::Range;
|
||||
pub(crate) use core::sync::atomic::{AtomicU32, Ordering};
|
93
services/comps/block/src/request_queue.rs
Normal file
93
services/comps/block/src/request_queue.rs
Normal file
@ -0,0 +1,93 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
use super::{
|
||||
bio::{BioEnqueueError, BioType, SubmittedBio},
|
||||
id::Sid,
|
||||
};
|
||||
|
||||
/// Represents the software staging queue for the `BioRequest` objects.
|
||||
pub trait BioRequestQueue {
|
||||
/// Enqueues a `SubmittedBio` to this queue.
|
||||
///
|
||||
/// This `SubmittedBio` will be merged into an existing `BioRequest`, or a new
|
||||
/// `BioRequest` will be created from the `SubmittedBio` before being placed
|
||||
/// into the queue.
|
||||
///
|
||||
/// This method will wake up the waiter if a new `BioRequest` is enqueued.
|
||||
fn enqueue(&self, bio: SubmittedBio) -> Result<(), BioEnqueueError>;
|
||||
|
||||
/// Dequeues a `BioRequest` from this queue.
|
||||
///
|
||||
/// This method will wait until one request can be retrieved.
|
||||
fn dequeue(&self) -> BioRequest;
|
||||
}
|
||||
|
||||
/// The block I/O request.
|
||||
pub struct BioRequest {
|
||||
/// The type of the I/O
|
||||
type_: BioType,
|
||||
/// The range of target sectors on the device
|
||||
sid_range: Range<Sid>,
|
||||
/// The submitted bios
|
||||
bios: VecDeque<SubmittedBio>,
|
||||
}
|
||||
|
||||
impl BioRequest {
|
||||
/// Returns the type of the I/O.
|
||||
pub fn type_(&self) -> BioType {
|
||||
self.type_
|
||||
}
|
||||
|
||||
/// Returns the range of sector id on device.
|
||||
pub fn sid_range(&self) -> &Range<Sid> {
|
||||
&self.sid_range
|
||||
}
|
||||
|
||||
/// Returns an iterator to the `SubmittedBio`s.
|
||||
pub fn bios(&self) -> impl Iterator<Item = &SubmittedBio> {
|
||||
self.bios.iter()
|
||||
}
|
||||
|
||||
/// Returns `true` if can merge the `SubmittedBio`, `false` otherwise.
|
||||
pub fn can_merge(&self, rq_bio: &SubmittedBio) -> bool {
|
||||
if rq_bio.type_() != self.type_ {
|
||||
return false;
|
||||
}
|
||||
|
||||
rq_bio.sid_range().start == self.sid_range.end
|
||||
|| rq_bio.sid_range().end == self.sid_range.start
|
||||
}
|
||||
|
||||
/// Merges the `SubmittedBio` into this request.
|
||||
///
|
||||
/// The merged `SubmittedBio` can only be placed at the front or back.
|
||||
///
|
||||
/// # Panic
|
||||
///
|
||||
/// If the `SubmittedBio` can not be merged, this method will panic.
|
||||
pub fn merge_bio(&mut self, rq_bio: SubmittedBio) {
|
||||
assert!(self.can_merge(&rq_bio));
|
||||
|
||||
if rq_bio.sid_range().start == self.sid_range.end {
|
||||
self.sid_range.end = rq_bio.sid_range().end;
|
||||
self.bios.push_back(rq_bio);
|
||||
} else {
|
||||
self.sid_range.start = rq_bio.sid_range().start;
|
||||
self.bios.push_front(rq_bio);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SubmittedBio> for BioRequest {
|
||||
fn from(bio: SubmittedBio) -> Self {
|
||||
Self {
|
||||
type_: bio.type_(),
|
||||
sid_range: bio.sid_range().clone(),
|
||||
bios: {
|
||||
let mut bios = VecDeque::with_capacity(1);
|
||||
bios.push_front(bio);
|
||||
bios
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
@ -1,28 +1,132 @@
|
||||
use core::{hint::spin_loop, mem::size_of};
|
||||
use core::{
|
||||
fmt::Debug,
|
||||
hint::spin_loop,
|
||||
mem::size_of,
|
||||
sync::atomic::{AtomicUsize, Ordering},
|
||||
};
|
||||
|
||||
use alloc::{boxed::Box, string::ToString, sync::Arc};
|
||||
use alloc::{boxed::Box, string::ToString, sync::Arc, vec::Vec};
|
||||
use alloc::{boxed::Box, collections::VecDeque, string::ToString, sync::Arc, vec::Vec};
|
||||
use aster_block::{
|
||||
bio::{BioEnqueueError, BioStatus, BioType, SubmittedBio},
|
||||
id::Sid,
|
||||
request_queue::{BioRequest, BioRequestQueue},
|
||||
};
|
||||
use aster_frame::{
|
||||
io_mem::IoMem,
|
||||
sync::SpinLock,
|
||||
sync::{Mutex, WaitQueue},
|
||||
trap::TrapFrame,
|
||||
vm::{VmAllocOptions, VmFrame, VmIo, VmReader, VmWriter},
|
||||
};
|
||||
use aster_util::safe_ptr::SafePtr;
|
||||
use log::info;
|
||||
use pod::Pod;
|
||||
|
||||
use crate::{
|
||||
device::block::{BlkReq, BlkResp, ReqType, RespStatus},
|
||||
device::block::{ReqType, RespStatus},
|
||||
device::VirtioDeviceError,
|
||||
queue::VirtQueue,
|
||||
transport::VirtioTransport,
|
||||
};
|
||||
|
||||
use super::{BlkFeatures, VirtioBlkConfig};
|
||||
use super::{BlockFeatures, VirtioBlockConfig};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BlockDevice {
|
||||
config: SafePtr<VirtioBlkConfig, IoMem>,
|
||||
device: DeviceInner,
|
||||
/// The software staging queue.
|
||||
queue: BioSingleQueue,
|
||||
}
|
||||
|
||||
impl BlockDevice {
|
||||
/// Creates a new VirtIO-Block driver and registers it.
|
||||
pub(crate) fn init(transport: Box<dyn VirtioTransport>) -> Result<(), VirtioDeviceError> {
|
||||
let block_device = {
|
||||
let device = DeviceInner::init(transport)?;
|
||||
Self {
|
||||
device,
|
||||
queue: BioSingleQueue::new(),
|
||||
}
|
||||
};
|
||||
aster_block::register_device(super::DEVICE_NAME.to_string(), Arc::new(block_device));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Dequeues a `BioRequest` from the software staging queue and
|
||||
/// processes the request.
|
||||
///
|
||||
/// TODO: Current read and write operations are still synchronous,
|
||||
/// it needs to be modified to use the queue-based asynchronous programming pattern.
|
||||
pub fn handle_requests(&self) {
|
||||
let request = self.queue.dequeue();
|
||||
match request.type_() {
|
||||
BioType::Read => self.do_read(&request),
|
||||
BioType::Write => self.do_write(&request),
|
||||
BioType::Flush | BioType::Discard => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn do_read(&self, request: &BioRequest) {
|
||||
let start_sid = request.sid_range().start;
|
||||
|
||||
let writers = {
|
||||
let mut writers = Vec::new();
|
||||
for bio in request.bios() {
|
||||
for segment in bio.segments() {
|
||||
writers.push(segment.writer());
|
||||
}
|
||||
}
|
||||
writers
|
||||
};
|
||||
|
||||
self.device.read(start_sid, writers.as_slice());
|
||||
|
||||
for bio in request.bios() {
|
||||
bio.complete(BioStatus::Complete);
|
||||
}
|
||||
}
|
||||
|
||||
fn do_write(&self, request: &BioRequest) {
|
||||
let start_sid = request.sid_range().start;
|
||||
|
||||
let readers = {
|
||||
let mut readers = Vec::new();
|
||||
for bio in request.bios() {
|
||||
for segment in bio.segments() {
|
||||
readers.push(segment.reader());
|
||||
}
|
||||
}
|
||||
readers
|
||||
};
|
||||
|
||||
self.device.write(start_sid, readers.as_slice());
|
||||
|
||||
for bio in request.bios() {
|
||||
bio.complete(BioStatus::Complete);
|
||||
}
|
||||
}
|
||||
|
||||
/// Negotiate features for the device specified bits 0~23
|
||||
pub(crate) fn negotiate_features(features: u64) -> u64 {
|
||||
let feature = BlockFeatures::from_bits(features).unwrap();
|
||||
let support_features = BlockFeatures::from_bits(features).unwrap();
|
||||
(feature & support_features).bits
|
||||
}
|
||||
}
|
||||
|
||||
impl aster_block::BlockDevice for BlockDevice {
|
||||
fn request_queue(&self) -> &dyn BioRequestQueue {
|
||||
&self.queue
|
||||
}
|
||||
|
||||
fn handle_irq(&self) {
|
||||
info!("Virtio block device handle irq");
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct DeviceInner {
|
||||
config: SafePtr<VirtioBlockConfig, IoMem>,
|
||||
queue: SpinLock<VirtQueue>,
|
||||
transport: Box<dyn VirtioTransport>,
|
||||
/// Block requests, we use VmFrame to store the requests so that
|
||||
@ -34,109 +138,10 @@ pub struct BlockDevice {
|
||||
id_allocator: SpinLock<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl BlockDevice {
|
||||
/// read data from block device, this function is blocking
|
||||
/// FIEME: replace slice with a more secure data structure to use dma mapping.
|
||||
pub fn read(&self, block_id: usize, buf: &[VmWriter]) {
|
||||
// FIXME: Handling cases without id.
|
||||
let id = self.id_allocator.lock().pop().unwrap() as usize;
|
||||
let req = BlkReq {
|
||||
type_: ReqType::In as _,
|
||||
reserved: 0,
|
||||
sector: block_id as u64,
|
||||
};
|
||||
let resp = BlkResp::default();
|
||||
self.block_requests
|
||||
.write_val(id * size_of::<BlkReq>(), &req)
|
||||
.unwrap();
|
||||
self.block_responses
|
||||
.write_val(id * size_of::<BlkResp>(), &resp)
|
||||
.unwrap();
|
||||
let req = self
|
||||
.block_requests
|
||||
.reader()
|
||||
.skip(id * size_of::<BlkReq>())
|
||||
.limit(size_of::<BlkReq>());
|
||||
let resp = self
|
||||
.block_responses
|
||||
.writer()
|
||||
.skip(id * size_of::<BlkResp>())
|
||||
.limit(size_of::<BlkResp>());
|
||||
|
||||
let mut outputs: Vec<&VmWriter<'_>> = buf.iter().collect();
|
||||
outputs.push(&resp);
|
||||
let mut queue = self.queue.lock_irq_disabled();
|
||||
let token = queue
|
||||
.add_vm(&[&req], outputs.as_slice())
|
||||
.expect("add queue failed");
|
||||
queue.notify();
|
||||
while !queue.can_pop() {
|
||||
spin_loop();
|
||||
}
|
||||
queue.pop_used_with_token(token).expect("pop used failed");
|
||||
let resp: BlkResp = self
|
||||
.block_responses
|
||||
.read_val(id * size_of::<BlkResp>())
|
||||
.unwrap();
|
||||
self.id_allocator.lock().push(id as u8);
|
||||
match RespStatus::try_from(resp.status).unwrap() {
|
||||
RespStatus::Ok => {}
|
||||
_ => panic!("io error in block device"),
|
||||
};
|
||||
}
|
||||
/// write data to block device, this function is blocking
|
||||
/// FIEME: replace slice with a more secure data structure to use dma mapping.
|
||||
pub fn write(&self, block_id: usize, buf: &[VmReader]) {
|
||||
// FIXME: Handling cases without id.
|
||||
let id = self.id_allocator.lock().pop().unwrap() as usize;
|
||||
let req = BlkReq {
|
||||
type_: ReqType::Out as _,
|
||||
reserved: 0,
|
||||
sector: block_id as u64,
|
||||
};
|
||||
let resp = BlkResp::default();
|
||||
self.block_requests
|
||||
.write_val(id * size_of::<BlkReq>(), &req)
|
||||
.unwrap();
|
||||
self.block_responses
|
||||
.write_val(id * size_of::<BlkResp>(), &resp)
|
||||
.unwrap();
|
||||
let req = self
|
||||
.block_requests
|
||||
.reader()
|
||||
.skip(id * size_of::<BlkReq>())
|
||||
.limit(size_of::<BlkReq>());
|
||||
let resp = self
|
||||
.block_responses
|
||||
.writer()
|
||||
.skip(id * size_of::<BlkResp>())
|
||||
.limit(size_of::<BlkResp>());
|
||||
|
||||
let mut queue = self.queue.lock_irq_disabled();
|
||||
let mut inputs: Vec<&VmReader<'_>> = buf.iter().collect();
|
||||
inputs.insert(0, &req);
|
||||
let token = queue
|
||||
.add_vm(inputs.as_slice(), &[&resp])
|
||||
.expect("add queue failed");
|
||||
queue.notify();
|
||||
while !queue.can_pop() {
|
||||
spin_loop();
|
||||
}
|
||||
queue.pop_used_with_token(token).expect("pop used failed");
|
||||
let resp: BlkResp = self
|
||||
.block_responses
|
||||
.read_val(id * size_of::<BlkResp>())
|
||||
.unwrap();
|
||||
self.id_allocator.lock().push(id as u8);
|
||||
match RespStatus::try_from(resp.status).unwrap() {
|
||||
RespStatus::Ok => {}
|
||||
_ => panic!("io error in block device:{:?}", resp.status),
|
||||
};
|
||||
}
|
||||
|
||||
/// Create a new VirtIO-Block driver.
|
||||
pub(crate) fn init(mut transport: Box<dyn VirtioTransport>) -> Result<(), VirtioDeviceError> {
|
||||
let config = VirtioBlkConfig::new(transport.as_mut());
|
||||
impl DeviceInner {
|
||||
/// Creates and inits the device.
|
||||
pub fn init(mut transport: Box<dyn VirtioTransport>) -> Result<Self, VirtioDeviceError> {
|
||||
let config = VirtioBlockConfig::new(transport.as_mut());
|
||||
let num_queues = transport.num_queues();
|
||||
if num_queues != 1 {
|
||||
return Err(VirtioDeviceError::QueuesAmountDoNotMatch(num_queues, 1));
|
||||
@ -170,30 +175,225 @@ impl BlockDevice {
|
||||
info!("Virtio block device config space change");
|
||||
}
|
||||
device.transport.finish_init();
|
||||
Ok(device)
|
||||
}
|
||||
|
||||
aster_block::register_device(super::DEVICE_NAME.to_string(), Arc::new(device));
|
||||
/// Reads data from the block device, this function is blocking.
|
||||
/// FIEME: replace slice with a more secure data structure to use dma mapping.
|
||||
pub fn read(&self, sector_id: Sid, buf: &[VmWriter]) {
|
||||
// FIXME: Handling cases without id.
|
||||
let id = self.id_allocator.lock().pop().unwrap() as usize;
|
||||
let req = BlockReq {
|
||||
type_: ReqType::In as _,
|
||||
reserved: 0,
|
||||
sector: sector_id.to_raw(),
|
||||
};
|
||||
let resp = BlockResp::default();
|
||||
self.block_requests
|
||||
.write_val(id * size_of::<BlockReq>(), &req)
|
||||
.unwrap();
|
||||
self.block_responses
|
||||
.write_val(id * size_of::<BlockResp>(), &resp)
|
||||
.unwrap();
|
||||
let req_reader = self
|
||||
.block_requests
|
||||
.reader()
|
||||
.skip(id * size_of::<BlockReq>())
|
||||
.limit(size_of::<BlockReq>());
|
||||
let resp_writer = self
|
||||
.block_responses
|
||||
.writer()
|
||||
.skip(id * size_of::<BlockResp>())
|
||||
.limit(size_of::<BlockResp>());
|
||||
|
||||
let mut outputs: Vec<&VmWriter<'_>> = buf.iter().collect();
|
||||
outputs.push(&resp_writer);
|
||||
let mut queue = self.queue.lock_irq_disabled();
|
||||
let token = queue
|
||||
.add_vm(&[&req_reader], outputs.as_slice())
|
||||
.expect("add queue failed");
|
||||
queue.notify();
|
||||
while !queue.can_pop() {
|
||||
spin_loop();
|
||||
}
|
||||
queue.pop_used_with_token(token).expect("pop used failed");
|
||||
let resp: BlockResp = self
|
||||
.block_responses
|
||||
.read_val(id * size_of::<BlockResp>())
|
||||
.unwrap();
|
||||
self.id_allocator.lock().push(id as u8);
|
||||
match RespStatus::try_from(resp.status).unwrap() {
|
||||
RespStatus::Ok => {}
|
||||
_ => panic!("io error in block device"),
|
||||
};
|
||||
}
|
||||
|
||||
/// Writes data to the block device, this function is blocking.
|
||||
/// FIEME: replace slice with a more secure data structure to use dma mapping.
|
||||
pub fn write(&self, sector_id: Sid, buf: &[VmReader]) {
|
||||
// FIXME: Handling cases without id.
|
||||
let id = self.id_allocator.lock().pop().unwrap() as usize;
|
||||
let req = BlockReq {
|
||||
type_: ReqType::Out as _,
|
||||
reserved: 0,
|
||||
sector: sector_id.to_raw(),
|
||||
};
|
||||
let resp = BlockResp::default();
|
||||
self.block_requests
|
||||
.write_val(id * size_of::<BlockReq>(), &req)
|
||||
.unwrap();
|
||||
self.block_responses
|
||||
.write_val(id * size_of::<BlockResp>(), &resp)
|
||||
.unwrap();
|
||||
let req_reader = self
|
||||
.block_requests
|
||||
.reader()
|
||||
.skip(id * size_of::<BlockReq>())
|
||||
.limit(size_of::<BlockReq>());
|
||||
let resp_writer = self
|
||||
.block_responses
|
||||
.writer()
|
||||
.skip(id * size_of::<BlockResp>())
|
||||
.limit(size_of::<BlockResp>());
|
||||
|
||||
let mut queue = self.queue.lock_irq_disabled();
|
||||
let mut inputs: Vec<&VmReader<'_>> = buf.iter().collect();
|
||||
inputs.insert(0, &req_reader);
|
||||
let token = queue
|
||||
.add_vm(inputs.as_slice(), &[&resp_writer])
|
||||
.expect("add queue failed");
|
||||
queue.notify();
|
||||
while !queue.can_pop() {
|
||||
spin_loop();
|
||||
}
|
||||
queue.pop_used_with_token(token).expect("pop used failed");
|
||||
let resp: BlockResp = self
|
||||
.block_responses
|
||||
.read_val(id * size_of::<BlockResp>())
|
||||
.unwrap();
|
||||
self.id_allocator.lock().push(id as u8);
|
||||
match RespStatus::try_from(resp.status).unwrap() {
|
||||
RespStatus::Ok => {}
|
||||
_ => panic!("io error in block device:{:?}", resp.status),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone, Pod)]
|
||||
struct BlockReq {
|
||||
pub type_: u32,
|
||||
pub reserved: u32,
|
||||
pub sector: u64,
|
||||
}
|
||||
|
||||
/// Response of a VirtIOBlock request.
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone, Pod)]
|
||||
struct BlockResp {
|
||||
pub status: u8,
|
||||
}
|
||||
|
||||
impl Default for BlockResp {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
status: RespStatus::_NotReady as _,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A simple block I/O request queue with a single queue.
|
||||
///
|
||||
/// It is a FIFO producer-consumer queue, where the producer (e.g., filesystem)
|
||||
/// submits requests to the queue, and the consumer (e.g., block device driver)
|
||||
/// continuously consumes and processes these requests from the queue.
|
||||
pub struct BioSingleQueue {
|
||||
queue: Mutex<VecDeque<BioRequest>>,
|
||||
num_requests: AtomicUsize,
|
||||
wait_queue: WaitQueue,
|
||||
}
|
||||
|
||||
impl BioSingleQueue {
|
||||
/// Creates an empty queue.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
queue: Mutex::new(VecDeque::new()),
|
||||
num_requests: AtomicUsize::new(0),
|
||||
wait_queue: WaitQueue::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the number of requests currently in this queue.
|
||||
pub fn num_requests(&self) -> usize {
|
||||
self.num_requests.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
fn dec_num_requests(&self) {
|
||||
self.num_requests.fetch_sub(1, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
fn inc_num_requests(&self) {
|
||||
self.num_requests.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for BioSingleQueue {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for BioSingleQueue {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||
f.debug_struct("BioSingleQueue")
|
||||
.field("num_requests", &self.num_requests())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl BioRequestQueue for BioSingleQueue {
|
||||
/// Enqueues a `SubmittedBio` to this queue.
|
||||
///
|
||||
/// When enqueueing the `SubmittedBio`, try to insert it into the last request if the
|
||||
/// type is same and the sector range is contiguous.
|
||||
/// Otherwise, creates and inserts a new request for the `SubmittedBio`.
|
||||
fn enqueue(&self, bio: SubmittedBio) -> Result<(), BioEnqueueError> {
|
||||
let mut queue = self.queue.lock();
|
||||
if let Some(request) = queue.front_mut() {
|
||||
if request.can_merge(&bio) {
|
||||
request.merge_bio(bio);
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
let new_request = BioRequest::from(bio);
|
||||
queue.push_front(new_request);
|
||||
drop(queue);
|
||||
self.inc_num_requests();
|
||||
self.wait_queue.wake_all();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Negotiate features for the device specified bits 0~23
|
||||
pub(crate) fn negotiate_features(features: u64) -> u64 {
|
||||
let feature = BlkFeatures::from_bits(features).unwrap();
|
||||
let support_features = BlkFeatures::from_bits(features).unwrap();
|
||||
(feature & support_features).bits
|
||||
/// Dequeues a `BioRequest` from this queue.
|
||||
fn dequeue(&self) -> BioRequest {
|
||||
let mut num_requests = self.num_requests();
|
||||
|
||||
loop {
|
||||
if num_requests > 0 {
|
||||
if let Some(request) = self.queue.lock().pop_back() {
|
||||
self.dec_num_requests();
|
||||
return request;
|
||||
}
|
||||
}
|
||||
|
||||
impl aster_block::BlockDevice for BlockDevice {
|
||||
fn read_block(&self, block_id: usize, buf: &[VmWriter]) {
|
||||
self.read(block_id, buf);
|
||||
num_requests = self.wait_queue.wait_until(|| {
|
||||
let num_requests = self.num_requests();
|
||||
if num_requests > 0 {
|
||||
Some(num_requests)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn write_block(&self, block_id: usize, buf: &[VmReader]) {
|
||||
self.write(block_id, buf);
|
||||
}
|
||||
|
||||
fn handle_irq(&self) {
|
||||
info!("Virtio block device handle irq");
|
||||
}
|
||||
}
|
||||
|
@ -8,12 +8,11 @@ use pod::Pod;
|
||||
|
||||
use crate::transport::VirtioTransport;
|
||||
|
||||
pub const BLK_SIZE: usize = 512;
|
||||
pub static DEVICE_NAME: &str = "Virtio-Block";
|
||||
|
||||
bitflags! {
|
||||
/// features for virtio block device
|
||||
pub(crate) struct BlkFeatures : u64{
|
||||
pub(crate) struct BlockFeatures : u64 {
|
||||
const BARRIER = 1 << 0;
|
||||
const SIZE_MAX = 1 << 1;
|
||||
const SEG_MAX = 1 << 2;
|
||||
@ -29,29 +28,6 @@ bitflags! {
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone, Pod)]
|
||||
pub struct BlkReq {
|
||||
pub type_: u32,
|
||||
pub reserved: u32,
|
||||
pub sector: u64,
|
||||
}
|
||||
|
||||
/// Response of a VirtIOBlk request.
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone, Pod)]
|
||||
pub struct BlkResp {
|
||||
pub status: u8,
|
||||
}
|
||||
|
||||
impl Default for BlkResp {
|
||||
fn default() -> Self {
|
||||
BlkResp {
|
||||
status: RespStatus::_NotReady as _,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(u32)]
|
||||
#[derive(Debug, Copy, Clone, TryFromInt)]
|
||||
pub enum ReqType {
|
||||
@ -77,12 +53,12 @@ pub enum RespStatus {
|
||||
|
||||
#[derive(Debug, Copy, Clone, Pod)]
|
||||
#[repr(C)]
|
||||
pub struct VirtioBlkConfig {
|
||||
pub struct VirtioBlockConfig {
|
||||
capacity: u64,
|
||||
size_max: u64,
|
||||
geometry: VirtioBlkGeometry,
|
||||
geometry: VirtioBlockGeometry,
|
||||
blk_size: u32,
|
||||
topology: VirtioBlkTopology,
|
||||
topology: VirtioBlockTopology,
|
||||
writeback: u8,
|
||||
unused0: [u8; 3],
|
||||
max_discard_sectors: u32,
|
||||
@ -96,7 +72,7 @@ pub struct VirtioBlkConfig {
|
||||
|
||||
#[derive(Debug, Copy, Clone, Pod)]
|
||||
#[repr(C)]
|
||||
pub struct VirtioBlkGeometry {
|
||||
pub struct VirtioBlockGeometry {
|
||||
cylinders: u16,
|
||||
heads: u8,
|
||||
sectors: u8,
|
||||
@ -104,14 +80,14 @@ pub struct VirtioBlkGeometry {
|
||||
|
||||
#[derive(Debug, Copy, Clone, Pod)]
|
||||
#[repr(C)]
|
||||
pub struct VirtioBlkTopology {
|
||||
pub struct VirtioBlockTopology {
|
||||
physical_block_exp: u8,
|
||||
alignment_offset: u8,
|
||||
min_io_size: u16,
|
||||
opt_io_size: u32,
|
||||
}
|
||||
|
||||
impl VirtioBlkConfig {
|
||||
impl VirtioBlockConfig {
|
||||
pub(self) fn new(transport: &dyn VirtioTransport) -> SafePtr<Self, IoMem> {
|
||||
let memory = transport.device_config_memory();
|
||||
SafePtr::new(memory, 0)
|
||||
|
@ -64,6 +64,9 @@ log = "0.4"
|
||||
getrandom = { version = "0.2.10", default-features = false, features = [
|
||||
"rdrand",
|
||||
] }
|
||||
bitvec = { version = "1.0", default-features = false, features = ["alloc"] }
|
||||
static_assertions = "1.1.0"
|
||||
inherit-methods-macro = { git = "https://github.com/asterinas/inherit-methods-macro", rev = "98f7e3e" }
|
||||
|
||||
[dependencies.lazy_static]
|
||||
version = "1.0"
|
||||
|
@ -1,10 +1,3 @@
|
||||
use core::mem::size_of;
|
||||
|
||||
use alloc::vec::Vec;
|
||||
use aster_frame::{
|
||||
println,
|
||||
vm::{VmAllocOptions, VmIo},
|
||||
};
|
||||
use log::info;
|
||||
|
||||
pub fn init() {
|
||||
@ -13,43 +6,3 @@ pub fn init() {
|
||||
info!("Found Input device, name:{}", name);
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn block_device_test() {
|
||||
for (_, device) in aster_block::all_devices() {
|
||||
let write_frame = VmAllocOptions::new(1).alloc_single().unwrap();
|
||||
let read_frame = VmAllocOptions::new(1).alloc_single().unwrap();
|
||||
info!("write_buffer address:{:x}", write_frame.start_paddr());
|
||||
info!("read_buffer address:{:x}", read_frame.start_paddr());
|
||||
|
||||
// init write frame
|
||||
for i in 0..=8 {
|
||||
let slice: [u8; 512] = [i; 512];
|
||||
write_frame.write_slice(i as usize * 512, &slice);
|
||||
}
|
||||
|
||||
// Test multiple Writer & Reader
|
||||
let mut writers = Vec::with_capacity(8);
|
||||
for i in 0..8 {
|
||||
let writer = read_frame.writer().skip(i * 512).limit(512);
|
||||
writers.push(writer);
|
||||
}
|
||||
|
||||
let mut readers = Vec::with_capacity(8);
|
||||
for i in 0..8 {
|
||||
let reader = write_frame.reader().skip(i * 512).limit(512);
|
||||
readers.push(reader);
|
||||
}
|
||||
|
||||
device.write_block(0, readers.as_slice());
|
||||
device.read_block(0, writers.as_slice());
|
||||
let mut read_slice = [0u8; 512];
|
||||
let mut write_slice = [0u8; 512];
|
||||
for i in 0..8 {
|
||||
read_frame.read_bytes(i * size_of::<[u8; 512]>(), &mut read_slice);
|
||||
write_frame.read_bytes(i * size_of::<[u8; 512]>(), &mut write_slice);
|
||||
assert_eq!(read_slice, write_slice);
|
||||
}
|
||||
println!("block device test passed!");
|
||||
}
|
||||
}
|
||||
|
@ -191,12 +191,48 @@ impl From<aster_frame::Error> for Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<aster_block::bio::BioEnqueueError> for Error {
|
||||
fn from(error: aster_block::bio::BioEnqueueError) -> Self {
|
||||
match error {
|
||||
aster_block::bio::BioEnqueueError::IsFull => {
|
||||
Error::with_message(Errno::EBUSY, "The request queue is full")
|
||||
}
|
||||
aster_block::bio::BioEnqueueError::Refused => {
|
||||
Error::with_message(Errno::EBUSY, "Refuse to enqueue the bio")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<aster_block::bio::BioStatus> for Error {
|
||||
fn from(err_status: aster_block::bio::BioStatus) -> Self {
|
||||
match err_status {
|
||||
aster_block::bio::BioStatus::NotSupported => {
|
||||
Error::with_message(Errno::EIO, "I/O operation is not supported")
|
||||
}
|
||||
aster_block::bio::BioStatus::NoSpace => {
|
||||
Error::with_message(Errno::ENOSPC, "Insufficient space on device")
|
||||
}
|
||||
aster_block::bio::BioStatus::IoError => {
|
||||
Error::with_message(Errno::EIO, "I/O operation fails")
|
||||
}
|
||||
status => panic!("Can not convert the status: {:?} to an error", status),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<core::str::Utf8Error> for Error {
|
||||
fn from(_: core::str::Utf8Error) -> Self {
|
||||
Error::with_message(Errno::EINVAL, "Invalid utf-8 string")
|
||||
}
|
||||
}
|
||||
|
||||
impl From<alloc::string::FromUtf8Error> for Error {
|
||||
fn from(_: alloc::string::FromUtf8Error) -> Self {
|
||||
Error::with_message(Errno::EINVAL, "Invalid utf-8 string")
|
||||
}
|
||||
}
|
||||
|
||||
impl From<core::ffi::FromBytesUntilNulError> for Error {
|
||||
fn from(_: core::ffi::FromBytesUntilNulError) -> Self {
|
||||
Error::with_message(Errno::E2BIG, "Cannot find null in cstring")
|
||||
|
@ -6,7 +6,6 @@ use crate::fs::utils::{
|
||||
};
|
||||
use crate::prelude::*;
|
||||
|
||||
use aster_frame::vm::VmFrame;
|
||||
use aster_util::{id_allocator::IdAlloc, slot_vec::SlotVec};
|
||||
use core::time::Duration;
|
||||
|
||||
@ -140,12 +139,18 @@ impl Inode for RootInode {
|
||||
self.metadata.size
|
||||
}
|
||||
|
||||
fn resize(&self, new_size: usize) {}
|
||||
fn resize(&self, new_size: usize) -> Result<()> {
|
||||
Err(Error::new(Errno::EISDIR))
|
||||
}
|
||||
|
||||
fn metadata(&self) -> Metadata {
|
||||
self.metadata.clone()
|
||||
}
|
||||
|
||||
fn ino(&self) -> u64 {
|
||||
self.metadata.ino as _
|
||||
}
|
||||
|
||||
fn type_(&self) -> InodeType {
|
||||
self.metadata.type_
|
||||
}
|
||||
|
@ -65,12 +65,18 @@ impl Inode for Ptmx {
|
||||
self.metadata.size
|
||||
}
|
||||
|
||||
fn resize(&self, new_size: usize) {}
|
||||
fn resize(&self, new_size: usize) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn metadata(&self) -> Metadata {
|
||||
self.metadata.clone()
|
||||
}
|
||||
|
||||
fn ino(&self) -> u64 {
|
||||
self.metadata.ino as _
|
||||
}
|
||||
|
||||
fn type_(&self) -> InodeType {
|
||||
self.metadata.type_
|
||||
}
|
||||
@ -93,14 +99,6 @@ impl Inode for Ptmx {
|
||||
|
||||
fn set_mtime(&self, time: Duration) {}
|
||||
|
||||
fn read_page(&self, idx: usize, frame: &VmFrame) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_page(&self, idx: usize, frame: &VmFrame) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_at(&self, offset: usize, buf: &mut [u8]) -> Result<usize> {
|
||||
Ok(0)
|
||||
}
|
||||
|
@ -45,12 +45,18 @@ impl Inode for PtySlaveInode {
|
||||
self.metadata.size
|
||||
}
|
||||
|
||||
fn resize(&self, new_size: usize) {}
|
||||
fn resize(&self, new_size: usize) -> Result<()> {
|
||||
Err(Error::new(Errno::EPERM))
|
||||
}
|
||||
|
||||
fn metadata(&self) -> Metadata {
|
||||
self.metadata.clone()
|
||||
}
|
||||
|
||||
fn ino(&self) -> u64 {
|
||||
self.metadata.ino as _
|
||||
}
|
||||
|
||||
fn type_(&self) -> InodeType {
|
||||
self.metadata.type_
|
||||
}
|
||||
@ -73,14 +79,6 @@ impl Inode for PtySlaveInode {
|
||||
|
||||
fn set_mtime(&self, time: Duration) {}
|
||||
|
||||
fn read_page(&self, idx: usize, frame: &VmFrame) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_page(&self, idx: usize, frame: &VmFrame) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_at(&self, offset: usize, buf: &mut [u8]) -> Result<usize> {
|
||||
self.device.read(buf)
|
||||
}
|
||||
|
476
services/libs/aster-std/src/fs/ext2/block_group.rs
Normal file
476
services/libs/aster-std/src/fs/ext2/block_group.rs
Normal file
@ -0,0 +1,476 @@
|
||||
use super::fs::Ext2;
|
||||
use super::inode::{Inode, InodeDesc, RawInode};
|
||||
use super::prelude::*;
|
||||
use super::super_block::SuperBlock;
|
||||
|
||||
use aster_util::id_allocator::IdAlloc;
|
||||
|
||||
/// Blocks are clustered into block groups in order to reduce fragmentation and minimise
|
||||
/// the amount of head seeking when reading a large amount of consecutive data.
|
||||
pub(super) struct BlockGroup {
|
||||
idx: usize,
|
||||
bg_impl: Arc<BlockGroupImpl>,
|
||||
raw_inodes_cache: PageCache,
|
||||
}
|
||||
|
||||
struct BlockGroupImpl {
|
||||
inode_table_bid: Bid,
|
||||
raw_inodes_size: usize,
|
||||
inner: RwMutex<Inner>,
|
||||
fs: Weak<Ext2>,
|
||||
}
|
||||
|
||||
impl BlockGroup {
|
||||
/// Loads and constructs a block group.
|
||||
pub fn load(
|
||||
group_descriptors_segment: &VmSegment,
|
||||
idx: usize,
|
||||
block_device: &dyn BlockDevice,
|
||||
super_block: &SuperBlock,
|
||||
fs: Weak<Ext2>,
|
||||
) -> Result<Self> {
|
||||
let raw_inodes_size = (super_block.inodes_per_group() as usize) * super_block.inode_size();
|
||||
|
||||
let bg_impl = {
|
||||
let metadata = {
|
||||
let descriptor = {
|
||||
// Read the block group descriptor
|
||||
// TODO: if the main is corrupted, should we load the backup?
|
||||
let offset = idx * core::mem::size_of::<RawGroupDescriptor>();
|
||||
let raw_descriptor = group_descriptors_segment
|
||||
.read_val::<RawGroupDescriptor>(offset)
|
||||
.unwrap();
|
||||
GroupDescriptor::from(raw_descriptor)
|
||||
};
|
||||
|
||||
let get_bitmap = |bid: Bid, capacity: usize| -> Result<IdAlloc> {
|
||||
if capacity > BLOCK_SIZE * 8 {
|
||||
return_errno_with_message!(Errno::EINVAL, "bad bitmap");
|
||||
}
|
||||
let mut buf = vec![0u8; BLOCK_SIZE];
|
||||
block_device.read_bytes(bid.to_offset(), &mut buf)?;
|
||||
Ok(IdAlloc::from_bytes_with_capacity(&buf, capacity))
|
||||
};
|
||||
|
||||
let block_bitmap = get_bitmap(
|
||||
descriptor.block_bitmap_bid,
|
||||
super_block.blocks_per_group() as usize,
|
||||
)?;
|
||||
let inode_bitmap = get_bitmap(
|
||||
descriptor.inode_bitmap_bid,
|
||||
super_block.inodes_per_group() as usize,
|
||||
)?;
|
||||
|
||||
GroupMetadata {
|
||||
descriptor,
|
||||
block_bitmap,
|
||||
inode_bitmap,
|
||||
}
|
||||
};
|
||||
|
||||
Arc::new(BlockGroupImpl {
|
||||
inode_table_bid: metadata.descriptor.inode_table_bid,
|
||||
raw_inodes_size,
|
||||
inner: RwMutex::new(Inner {
|
||||
metadata: Dirty::new(metadata),
|
||||
inode_cache: BTreeMap::new(),
|
||||
}),
|
||||
fs,
|
||||
})
|
||||
};
|
||||
|
||||
let raw_inodes_cache =
|
||||
PageCache::with_capacity(raw_inodes_size, Arc::downgrade(&bg_impl) as _)?;
|
||||
|
||||
Ok(Self {
|
||||
idx,
|
||||
bg_impl,
|
||||
raw_inodes_cache,
|
||||
})
|
||||
}
|
||||
|
||||
/// Finds and returns the inode.
|
||||
pub fn lookup_inode(&self, inode_idx: u32) -> Result<Arc<Inode>> {
|
||||
// The fast path
|
||||
let inner = self.bg_impl.inner.read();
|
||||
if !inner.metadata.is_inode_allocated(inode_idx) {
|
||||
return_errno!(Errno::ENOENT);
|
||||
}
|
||||
if let Some(inode) = inner.inode_cache.get(&inode_idx) {
|
||||
return Ok(inode.clone());
|
||||
}
|
||||
|
||||
// The slow path
|
||||
drop(inner);
|
||||
let mut inner = self.bg_impl.inner.write();
|
||||
if !inner.metadata.is_inode_allocated(inode_idx) {
|
||||
return_errno!(Errno::ENOENT);
|
||||
}
|
||||
if let Some(inode) = inner.inode_cache.get(&inode_idx) {
|
||||
return Ok(inode.clone());
|
||||
}
|
||||
|
||||
// Loads the inode, then inserts it into the inode cache.
|
||||
let inode = self.load_inode(inode_idx)?;
|
||||
inner.inode_cache.insert(inode_idx, inode.clone());
|
||||
Ok(inode)
|
||||
}
|
||||
|
||||
/// Loads an existing inode.
|
||||
///
|
||||
/// This method may load the raw inode metadata from block device.
|
||||
fn load_inode(&self, inode_idx: u32) -> Result<Arc<Inode>> {
|
||||
let fs = self.fs();
|
||||
let raw_inode = {
|
||||
let offset = (inode_idx as usize) * fs.inode_size();
|
||||
self.raw_inodes_cache
|
||||
.pages()
|
||||
.read_val::<RawInode>(offset)
|
||||
.unwrap()
|
||||
};
|
||||
let inode_desc = Dirty::new(InodeDesc::try_from(raw_inode)?);
|
||||
let ino = inode_idx + self.idx as u32 * fs.inodes_per_group() + 1;
|
||||
|
||||
Ok(Inode::new(ino, self.idx, inode_desc, Arc::downgrade(&fs)))
|
||||
}
|
||||
|
||||
/// Inserts the inode into the inode cache.
|
||||
///
|
||||
/// # Panic
|
||||
///
|
||||
/// If `inode_idx` has not been allocated before, then the method panics.
|
||||
pub fn insert_cache(&self, inode_idx: u32, inode: Arc<Inode>) {
|
||||
let mut inner = self.bg_impl.inner.write();
|
||||
assert!(inner.metadata.is_inode_allocated(inode_idx));
|
||||
inner.inode_cache.insert(inode_idx, inode);
|
||||
}
|
||||
|
||||
/// Allocates and returns an inode index.
|
||||
pub fn alloc_inode(&self, is_dir: bool) -> Option<u32> {
|
||||
// The fast path
|
||||
if self.bg_impl.inner.read().metadata.free_inodes_count() == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
// The slow path
|
||||
self.bg_impl.inner.write().metadata.alloc_inode(is_dir)
|
||||
}
|
||||
|
||||
/// Frees the allocated inode idx.
|
||||
///
|
||||
/// # Panic
|
||||
///
|
||||
/// If `inode_idx` has not been allocated before, then the method panics.
|
||||
pub fn free_inode(&self, inode_idx: u32, is_dir: bool) {
|
||||
let mut inner = self.bg_impl.inner.write();
|
||||
assert!(inner.metadata.is_inode_allocated(inode_idx));
|
||||
|
||||
inner.metadata.free_inode(inode_idx, is_dir);
|
||||
inner.inode_cache.remove(&inode_idx);
|
||||
}
|
||||
|
||||
/// Allocates and returns a block index.
|
||||
pub fn alloc_block(&self) -> Option<u32> {
|
||||
// The fast path
|
||||
if self.bg_impl.inner.read().metadata.free_blocks_count() == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
// The slow path
|
||||
self.bg_impl.inner.write().metadata.alloc_block()
|
||||
}
|
||||
|
||||
/// Frees the allocated block idx.
|
||||
///
|
||||
/// # Panic
|
||||
///
|
||||
/// If `block_idx` has not been allocated before, then the method panics.
|
||||
pub fn free_block(&self, block_idx: u32) {
|
||||
let mut inner = self.bg_impl.inner.write();
|
||||
assert!(inner.metadata.is_block_allocated(block_idx));
|
||||
|
||||
inner.metadata.free_block(block_idx);
|
||||
}
|
||||
|
||||
/// Writes back the raw inode metadata to the raw inode metadata cache.
|
||||
pub fn sync_raw_inode(&self, inode_idx: u32, raw_inode: &RawInode) {
|
||||
let offset = (inode_idx as usize) * self.fs().inode_size();
|
||||
self.raw_inodes_cache
|
||||
.pages()
|
||||
.write_val(offset, raw_inode)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
/// Writes back the metadata of this group.
|
||||
pub fn sync_metadata(&self, super_block: &SuperBlock) -> Result<()> {
|
||||
if !self.bg_impl.inner.read().metadata.is_dirty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut inner = self.bg_impl.inner.write();
|
||||
let fs = self.fs();
|
||||
// Writes back the descriptor.
|
||||
let raw_descriptor = RawGroupDescriptor::from(&inner.metadata.descriptor);
|
||||
self.fs().sync_group_descriptor(self.idx, &raw_descriptor)?;
|
||||
|
||||
let mut bio_waiter = BioWaiter::new();
|
||||
// Writes back the inode bitmap.
|
||||
let inode_bitmap_bid = inner.metadata.descriptor.inode_bitmap_bid;
|
||||
bio_waiter.concat(fs.block_device().write_bytes_async(
|
||||
inode_bitmap_bid.to_offset(),
|
||||
inner.metadata.inode_bitmap.as_bytes(),
|
||||
)?);
|
||||
|
||||
// Writes back the block bitmap.
|
||||
let block_bitmap_bid = inner.metadata.descriptor.block_bitmap_bid;
|
||||
bio_waiter.concat(fs.block_device().write_bytes_async(
|
||||
block_bitmap_bid.to_offset(),
|
||||
inner.metadata.block_bitmap.as_bytes(),
|
||||
)?);
|
||||
|
||||
// Waits for the completion of all submitted bios.
|
||||
bio_waiter.wait().ok_or_else(|| {
|
||||
Error::with_message(Errno::EIO, "failed to sync metadata of block group")
|
||||
})?;
|
||||
|
||||
inner.metadata.clear_dirty();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Writes back all of the cached inodes.
|
||||
///
|
||||
/// The `sync_all` method of inode may modify the data of this block group,
|
||||
/// so we should not hold the lock while syncing the inodes.
|
||||
pub fn sync_all_inodes(&self) -> Result<()> {
|
||||
// Removes the inodes that is unused from the inode cache.
|
||||
let unused_inodes: Vec<Arc<Inode>> = self
|
||||
.bg_impl
|
||||
.inner
|
||||
.write()
|
||||
.inode_cache
|
||||
.extract_if(|_, inode| Arc::strong_count(inode) == 1)
|
||||
.map(|(_, inode)| inode)
|
||||
.collect();
|
||||
|
||||
// Writes back the unused inodes.
|
||||
for inode in unused_inodes.iter() {
|
||||
inode.sync_all()?;
|
||||
}
|
||||
drop(unused_inodes);
|
||||
|
||||
// Writes back the remaining inodes in the inode cache.
|
||||
let remaining_inodes: Vec<Arc<Inode>> = self
|
||||
.bg_impl
|
||||
.inner
|
||||
.read()
|
||||
.inode_cache
|
||||
.values()
|
||||
.cloned()
|
||||
.collect();
|
||||
for inode in remaining_inodes.iter() {
|
||||
inode.sync_all()?;
|
||||
}
|
||||
drop(remaining_inodes);
|
||||
|
||||
// Writes back the raw inode metadata.
|
||||
self.raw_inodes_cache
|
||||
.pages()
|
||||
.decommit(0..self.bg_impl.raw_inodes_size)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn fs(&self) -> Arc<Ext2> {
|
||||
self.bg_impl.fs.upgrade().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for BlockGroup {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||
f.debug_struct("BlockGroup")
|
||||
.field("idx", &self.idx)
|
||||
.field("descriptor", &self.bg_impl.inner.read().metadata.descriptor)
|
||||
.field(
|
||||
"block_bitmap",
|
||||
&self.bg_impl.inner.read().metadata.block_bitmap,
|
||||
)
|
||||
.field(
|
||||
"inode_bitmap",
|
||||
&self.bg_impl.inner.read().metadata.inode_bitmap,
|
||||
)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl PageCacheBackend for BlockGroupImpl {
|
||||
fn read_page(&self, idx: usize, frame: &VmFrame) -> Result<()> {
|
||||
let bid = self.inode_table_bid + idx as u64;
|
||||
self.fs.upgrade().unwrap().read_block(bid, frame)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_page(&self, idx: usize, frame: &VmFrame) -> Result<()> {
|
||||
let bid = self.inode_table_bid + idx as u64;
|
||||
self.fs.upgrade().unwrap().write_block(bid, frame)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn npages(&self) -> usize {
|
||||
self.raw_inodes_size.div_ceil(BLOCK_SIZE)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Inner {
|
||||
metadata: Dirty<GroupMetadata>,
|
||||
inode_cache: BTreeMap<u32, Arc<Inode>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct GroupMetadata {
|
||||
descriptor: GroupDescriptor,
|
||||
block_bitmap: IdAlloc,
|
||||
inode_bitmap: IdAlloc,
|
||||
}
|
||||
|
||||
impl GroupMetadata {
|
||||
pub fn is_inode_allocated(&self, inode_idx: u32) -> bool {
|
||||
self.inode_bitmap.is_allocated(inode_idx as usize)
|
||||
}
|
||||
|
||||
pub fn alloc_inode(&mut self, is_dir: bool) -> Option<u32> {
|
||||
let Some(inode_idx) = self.inode_bitmap.alloc() else {
|
||||
return None;
|
||||
};
|
||||
self.dec_free_inodes();
|
||||
if is_dir {
|
||||
self.inc_dirs();
|
||||
}
|
||||
Some(inode_idx as u32)
|
||||
}
|
||||
|
||||
pub fn free_inode(&mut self, inode_idx: u32, is_dir: bool) {
|
||||
self.inode_bitmap.free(inode_idx as usize);
|
||||
self.inc_free_inodes();
|
||||
if is_dir {
|
||||
self.dec_dirs();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_block_allocated(&self, block_idx: u32) -> bool {
|
||||
self.block_bitmap.is_allocated(block_idx as usize)
|
||||
}
|
||||
|
||||
pub fn alloc_block(&mut self) -> Option<u32> {
|
||||
let Some(block_idx) = self.block_bitmap.alloc() else {
|
||||
return None;
|
||||
};
|
||||
self.dec_free_blocks();
|
||||
Some(block_idx as u32)
|
||||
}
|
||||
|
||||
pub fn free_block(&mut self, block_idx: u32) {
|
||||
self.block_bitmap.free(block_idx as usize);
|
||||
self.inc_free_blocks();
|
||||
}
|
||||
|
||||
pub fn free_inodes_count(&self) -> u16 {
|
||||
self.descriptor.free_inodes_count
|
||||
}
|
||||
|
||||
pub fn free_blocks_count(&self) -> u16 {
|
||||
self.descriptor.free_blocks_count
|
||||
}
|
||||
|
||||
pub fn inc_free_inodes(&mut self) {
|
||||
self.descriptor.free_inodes_count += 1;
|
||||
}
|
||||
|
||||
pub fn dec_free_inodes(&mut self) {
|
||||
debug_assert!(self.descriptor.free_inodes_count > 0);
|
||||
self.descriptor.free_inodes_count -= 1;
|
||||
}
|
||||
|
||||
pub fn inc_free_blocks(&mut self) {
|
||||
self.descriptor.free_blocks_count += 1;
|
||||
}
|
||||
|
||||
pub fn dec_free_blocks(&mut self) {
|
||||
debug_assert!(self.descriptor.free_blocks_count > 0);
|
||||
self.descriptor.free_blocks_count -= 1;
|
||||
}
|
||||
|
||||
pub fn inc_dirs(&mut self) {
|
||||
self.descriptor.dirs_count += 1;
|
||||
}
|
||||
|
||||
pub fn dec_dirs(&mut self) {
|
||||
debug_assert!(self.descriptor.dirs_count > 0);
|
||||
self.descriptor.dirs_count -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// The in-memory rust block group descriptor.
|
||||
///
|
||||
/// The block group descriptor contains information regarding where important data
|
||||
/// structures for that group are located.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
struct GroupDescriptor {
|
||||
/// Blocks usage bitmap block
|
||||
block_bitmap_bid: Bid,
|
||||
/// Inodes usage bitmap block
|
||||
inode_bitmap_bid: Bid,
|
||||
/// Starting block of inode table
|
||||
inode_table_bid: Bid,
|
||||
/// Number of free blocks in group
|
||||
free_blocks_count: u16,
|
||||
/// Number of free inodes in group
|
||||
free_inodes_count: u16,
|
||||
/// Number of directories in group
|
||||
dirs_count: u16,
|
||||
}
|
||||
|
||||
impl From<RawGroupDescriptor> for GroupDescriptor {
|
||||
fn from(desc: RawGroupDescriptor) -> Self {
|
||||
Self {
|
||||
block_bitmap_bid: Bid::new(desc.block_bitmap as _),
|
||||
inode_bitmap_bid: Bid::new(desc.inode_bitmap as _),
|
||||
inode_table_bid: Bid::new(desc.inode_table as _),
|
||||
free_blocks_count: desc.free_blocks_count,
|
||||
free_inodes_count: desc.free_inodes_count,
|
||||
dirs_count: desc.dirs_count,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const_assert!(core::mem::size_of::<RawGroupDescriptor>() == 32);
|
||||
|
||||
/// The raw block group descriptor.
|
||||
///
|
||||
/// The table starts on the first block following the superblock.
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug, Pod)]
|
||||
pub(super) struct RawGroupDescriptor {
|
||||
pub block_bitmap: u32,
|
||||
pub inode_bitmap: u32,
|
||||
pub inode_table: u32,
|
||||
pub free_blocks_count: u16,
|
||||
pub free_inodes_count: u16,
|
||||
pub dirs_count: u16,
|
||||
pad: u16,
|
||||
reserved: [u32; 3],
|
||||
}
|
||||
|
||||
impl From<&GroupDescriptor> for RawGroupDescriptor {
|
||||
fn from(desc: &GroupDescriptor) -> Self {
|
||||
Self {
|
||||
block_bitmap: desc.block_bitmap_bid.to_raw() as _,
|
||||
inode_bitmap: desc.inode_bitmap_bid.to_raw() as _,
|
||||
inode_table: desc.inode_table_bid.to_raw() as _,
|
||||
free_blocks_count: desc.free_blocks_count,
|
||||
free_inodes_count: desc.free_inodes_count,
|
||||
dirs_count: desc.dirs_count,
|
||||
pad: 0u16,
|
||||
reserved: [0u32; 3],
|
||||
}
|
||||
}
|
||||
}
|
56
services/libs/aster-std/src/fs/ext2/blocks_hole.rs
Normal file
56
services/libs/aster-std/src/fs/ext2/blocks_hole.rs
Normal file
@ -0,0 +1,56 @@
|
||||
use bitvec::prelude::BitVec;
|
||||
|
||||
/// A blocks hole descriptor implemented by the `BitVec`.
|
||||
///
|
||||
/// The true bit implies that the block is a hole, and conversely.
|
||||
pub(super) struct BlocksHoleDesc(BitVec);
|
||||
|
||||
impl BlocksHoleDesc {
|
||||
/// Constructs a blocks hole descriptor with initial size.
|
||||
///
|
||||
/// The `initial_size` usually is the number of blocks for a file.
|
||||
pub fn new(initial_size: usize) -> Self {
|
||||
let mut bit_vec = BitVec::with_capacity(initial_size);
|
||||
bit_vec.resize(initial_size, false);
|
||||
Self(bit_vec)
|
||||
}
|
||||
|
||||
/// Returns the size.
|
||||
pub fn size(&self) -> usize {
|
||||
self.0.len()
|
||||
}
|
||||
|
||||
/// Resizes the blocks hole to a new size.
|
||||
///
|
||||
/// If `new_size` is greater than current size, the new blocks are all marked as hole.
|
||||
pub fn resize(&mut self, new_size: usize) {
|
||||
self.0.resize(new_size, true);
|
||||
}
|
||||
|
||||
/// Returns if the block `idx` is a hole.
|
||||
///
|
||||
/// # Panic
|
||||
///
|
||||
/// If the `idx` is out of bounds, this method will panic.
|
||||
pub fn is_hole(&self, idx: usize) -> bool {
|
||||
self.0[idx]
|
||||
}
|
||||
|
||||
/// Marks the block `idx` as a hole.
|
||||
///
|
||||
/// # Panic
|
||||
///
|
||||
/// If the `idx` is out of bounds, this method will panic.
|
||||
pub fn set(&mut self, idx: usize) {
|
||||
self.0.set(idx, true);
|
||||
}
|
||||
|
||||
/// Unmarks the block `idx` as a hole.
|
||||
///
|
||||
/// # Panic
|
||||
///
|
||||
/// If the `idx` is out of bounds, this method will panic.
|
||||
pub fn unset(&mut self, idx: usize) {
|
||||
self.0.set(idx, false);
|
||||
}
|
||||
}
|
321
services/libs/aster-std/src/fs/ext2/dir.rs
Normal file
321
services/libs/aster-std/src/fs/ext2/dir.rs
Normal file
@ -0,0 +1,321 @@
|
||||
use super::inode::{FileType, MAX_FNAME_LEN};
|
||||
use super::prelude::*;
|
||||
|
||||
use core::iter::Iterator;
|
||||
|
||||
/// The data structure in a directory's data block. It is stored in a linked list.
|
||||
///
|
||||
/// Each entry contains the name of the entry, the inode number, the file type,
|
||||
/// and the distance within the directory file to the next entry.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DirEntry {
|
||||
/// The header part.
|
||||
header: DirEntryHeader,
|
||||
/// Name of the entry, up to 255 bytes (excluding the null terminator).
|
||||
name: CStr256,
|
||||
}
|
||||
|
||||
impl DirEntry {
|
||||
/// Constructs a new `DirEntry` object with the specified inode (`ino`),
|
||||
/// name (`name`), and file type (`file_type`).
|
||||
pub(super) fn new(ino: u32, name: &str, file_type: FileType) -> Self {
|
||||
debug_assert!(name.len() <= MAX_FNAME_LEN);
|
||||
|
||||
let record_len = (Self::header_len() + name.len()).align_up(4) as u16;
|
||||
Self {
|
||||
header: DirEntryHeader {
|
||||
ino,
|
||||
record_len,
|
||||
name_len: name.len() as u8,
|
||||
file_type: DirEntryFileType::from(file_type) as _,
|
||||
},
|
||||
name: CStr256::from(name),
|
||||
}
|
||||
}
|
||||
|
||||
/// Constructs a `DirEntry` with the name "." and `self_ino` as its inode.
|
||||
pub(super) fn self_entry(self_ino: u32) -> Self {
|
||||
Self::new(self_ino, ".", FileType::Dir)
|
||||
}
|
||||
|
||||
/// Constructs a `DirEntry` with the name ".." and `parent_ino` as its inode.
|
||||
pub(super) fn parent_entry(parent_ino: u32) -> Self {
|
||||
Self::new(parent_ino, "..", FileType::Dir)
|
||||
}
|
||||
|
||||
/// Returns a reference to the header.
|
||||
fn header(&self) -> &DirEntryHeader {
|
||||
&self.header
|
||||
}
|
||||
|
||||
/// Returns the length of the header.
|
||||
fn header_len() -> usize {
|
||||
core::mem::size_of::<DirEntryHeader>()
|
||||
}
|
||||
|
||||
/// Returns the inode number.
|
||||
pub fn ino(&self) -> u32 {
|
||||
self.header.ino
|
||||
}
|
||||
|
||||
/// Modifies the inode number.
|
||||
pub fn set_ino(&mut self, ino: u32) {
|
||||
self.header.ino = ino;
|
||||
}
|
||||
|
||||
/// Returns the name.
|
||||
pub fn name(&self) -> &str {
|
||||
self.name.as_str().unwrap()
|
||||
}
|
||||
|
||||
/// Returns the type.
|
||||
pub fn type_(&self) -> FileType {
|
||||
FileType::from(DirEntryFileType::try_from(self.header.file_type).unwrap())
|
||||
}
|
||||
|
||||
/// Returns the distance to the next entry.
|
||||
pub fn record_len(&self) -> usize {
|
||||
self.header.record_len as _
|
||||
}
|
||||
|
||||
/// Modifies the distance to the next entry.
|
||||
pub(super) fn set_record_len(&mut self, record_len: usize) {
|
||||
debug_assert!(record_len >= self.actual_len());
|
||||
self.header.record_len = record_len as _;
|
||||
}
|
||||
|
||||
/// Returns the actual length of the current entry.
|
||||
pub(super) fn actual_len(&self) -> usize {
|
||||
(Self::header_len() + self.name.len()).align_up(4)
|
||||
}
|
||||
|
||||
/// Returns the length of the gap between the current entry and the next entry.
|
||||
pub(super) fn gap_len(&self) -> usize {
|
||||
self.record_len() - self.actual_len()
|
||||
}
|
||||
}
|
||||
|
||||
/// The header of `DirEntry`.
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug, Pod)]
|
||||
struct DirEntryHeader {
|
||||
/// Inode number
|
||||
ino: u32,
|
||||
/// Directory entry length
|
||||
record_len: u16,
|
||||
/// Name Length
|
||||
name_len: u8,
|
||||
/// Type indicator
|
||||
file_type: u8,
|
||||
}
|
||||
|
||||
/// The type indicator in the `DirEntry`.
|
||||
#[repr(u8)]
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, TryFromInt)]
|
||||
enum DirEntryFileType {
|
||||
Unknown = 0,
|
||||
File = 1,
|
||||
Dir = 2,
|
||||
Char = 3,
|
||||
Block = 4,
|
||||
Fifo = 5,
|
||||
Socket = 6,
|
||||
Symlink = 7,
|
||||
}
|
||||
|
||||
impl From<FileType> for DirEntryFileType {
|
||||
fn from(file_type: FileType) -> Self {
|
||||
match file_type {
|
||||
FileType::Fifo => Self::Fifo,
|
||||
FileType::Char => Self::Char,
|
||||
FileType::Dir => Self::Dir,
|
||||
FileType::Block => Self::Block,
|
||||
FileType::File => Self::File,
|
||||
FileType::Symlink => Self::Symlink,
|
||||
FileType::Socket => Self::Socket,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DirEntryFileType> for FileType {
|
||||
fn from(file_type: DirEntryFileType) -> Self {
|
||||
match file_type {
|
||||
DirEntryFileType::Fifo => Self::Fifo,
|
||||
DirEntryFileType::Char => Self::Char,
|
||||
DirEntryFileType::Dir => Self::Dir,
|
||||
DirEntryFileType::Block => Self::Block,
|
||||
DirEntryFileType::File => Self::File,
|
||||
DirEntryFileType::Symlink => Self::Symlink,
|
||||
DirEntryFileType::Socket => Self::Socket,
|
||||
DirEntryFileType::Unknown => panic!("unknown file type"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A reader for reading `DirEntry` from the page cache.
|
||||
pub struct DirEntryReader<'a> {
|
||||
page_cache: &'a PageCache,
|
||||
offset: usize,
|
||||
}
|
||||
|
||||
impl<'a> DirEntryReader<'a> {
|
||||
/// Constructs a reader with the given page cache and offset.
|
||||
pub(super) fn new(page_cache: &'a PageCache, from_offset: usize) -> Self {
|
||||
Self {
|
||||
page_cache,
|
||||
offset: from_offset,
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads one `DirEntry` from the current offset.
|
||||
pub fn read_entry(&mut self) -> Result<DirEntry> {
|
||||
let header = self
|
||||
.page_cache
|
||||
.pages()
|
||||
.read_val::<DirEntryHeader>(self.offset)?;
|
||||
if header.ino == 0 {
|
||||
return_errno!(Errno::ENOENT);
|
||||
}
|
||||
|
||||
let mut name = vec![0u8; header.name_len as _];
|
||||
self.page_cache
|
||||
.pages()
|
||||
.read_bytes(self.offset + DirEntry::header_len(), &mut name)?;
|
||||
let entry = DirEntry {
|
||||
header,
|
||||
name: CStr256::from(name.as_slice()),
|
||||
};
|
||||
self.offset += entry.record_len();
|
||||
|
||||
Ok(entry)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for DirEntryReader<'a> {
|
||||
type Item = (usize, DirEntry);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let offset = self.offset;
|
||||
let entry = match self.read_entry() {
|
||||
Ok(entry) => entry,
|
||||
Err(_) => {
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
Some((offset, entry))
|
||||
}
|
||||
}
|
||||
|
||||
/// A writer for modifying `DirEntry` of the page cache.
|
||||
pub struct DirEntryWriter<'a> {
|
||||
page_cache: &'a PageCache,
|
||||
offset: usize,
|
||||
}
|
||||
|
||||
impl<'a> DirEntryWriter<'a> {
|
||||
/// Constructs a writer with the given page cache and offset.
|
||||
pub(super) fn new(page_cache: &'a PageCache, from_offset: usize) -> Self {
|
||||
Self {
|
||||
page_cache,
|
||||
offset: from_offset,
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes a `DirEntry` at the current offset.
|
||||
pub fn write_entry(&mut self, entry: &DirEntry) -> Result<()> {
|
||||
self.page_cache
|
||||
.pages()
|
||||
.write_val(self.offset, entry.header())?;
|
||||
self.page_cache.pages().write_bytes(
|
||||
self.offset + DirEntry::header_len(),
|
||||
entry.name().as_bytes(),
|
||||
)?;
|
||||
self.offset += entry.record_len();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Appends a new `DirEntry` starting from the current offset.
|
||||
///
|
||||
/// If there is a gap between existing entries, inserts the new entry into the gap;
|
||||
/// If there is no available space, expands the size and appends the new entry at the end.
|
||||
pub fn append_entry(&mut self, mut new_entry: DirEntry) -> Result<()> {
|
||||
let Some((offset, mut entry)) = DirEntryReader::new(self.page_cache, self.offset)
|
||||
.find(|(_, entry)| entry.gap_len() >= new_entry.record_len())
|
||||
else {
|
||||
// Resize and append it at the new block.
|
||||
let old_size = self.page_cache.pages().size();
|
||||
let new_size = old_size + BLOCK_SIZE;
|
||||
self.page_cache.pages().resize(new_size)?;
|
||||
new_entry.set_record_len(BLOCK_SIZE);
|
||||
self.offset = old_size;
|
||||
self.write_entry(&new_entry)?;
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
// Write in the gap between existing entries.
|
||||
new_entry.set_record_len(entry.gap_len());
|
||||
entry.set_record_len(entry.actual_len());
|
||||
self.offset = offset;
|
||||
self.write_entry(&entry)?;
|
||||
self.write_entry(&new_entry)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Removes and returns an existing `DirEntry` indicated by `name`.
|
||||
pub fn remove_entry(&mut self, name: &str) -> Result<DirEntry> {
|
||||
let self_entry_record_len = DirEntry::self_entry(0).record_len();
|
||||
let reader = DirEntryReader::new(self.page_cache, 0);
|
||||
let next_reader = DirEntryReader::new(self.page_cache, self_entry_record_len);
|
||||
let Some(((pre_offset, mut pre_entry), (offset, entry))) = reader
|
||||
.zip(next_reader)
|
||||
.find(|((offset, _), (_, dir_entry))| dir_entry.name() == name)
|
||||
else {
|
||||
return_errno!(Errno::ENOENT);
|
||||
};
|
||||
|
||||
if DirEntryReader::new(self.page_cache, offset)
|
||||
.next()
|
||||
.is_none()
|
||||
&& Bid::from_offset(pre_offset) != Bid::from_offset(offset)
|
||||
{
|
||||
// Shrink the size.
|
||||
let new_size = pre_offset.align_up(BLOCK_SIZE);
|
||||
self.page_cache.pages().resize(new_size)?;
|
||||
pre_entry.set_record_len(new_size - pre_offset);
|
||||
self.offset = pre_offset;
|
||||
self.write_entry(&pre_entry)?;
|
||||
} else {
|
||||
// Update the previous entry.
|
||||
pre_entry.set_record_len(pre_entry.record_len() + entry.record_len());
|
||||
self.offset = pre_offset;
|
||||
self.write_entry(&pre_entry)?;
|
||||
}
|
||||
|
||||
Ok(entry)
|
||||
}
|
||||
|
||||
/// Renames the `DirEntry` from `old_name` to the `new_name` from the current offset.
|
||||
///
|
||||
/// It will moves the `DirEntry` to another position,
|
||||
/// if the record length is not big enough.
|
||||
pub fn rename_entry(&mut self, old_name: &str, new_name: &str) -> Result<()> {
|
||||
let (offset, entry) = DirEntryReader::new(self.page_cache, self.offset)
|
||||
.find(|(offset, entry)| entry.name() == old_name)
|
||||
.ok_or(Error::new(Errno::ENOENT))?;
|
||||
|
||||
let mut new_entry = DirEntry::new(entry.ino(), new_name, entry.type_());
|
||||
if new_entry.record_len() <= entry.record_len() {
|
||||
// Just rename the entry.
|
||||
new_entry.set_record_len(entry.record_len());
|
||||
self.offset = offset;
|
||||
self.write_entry(&new_entry)?;
|
||||
} else {
|
||||
// Move to another position.
|
||||
self.remove_entry(old_name)?;
|
||||
self.offset = 0;
|
||||
self.append_entry(new_entry)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
366
services/libs/aster-std/src/fs/ext2/fs.rs
Normal file
366
services/libs/aster-std/src/fs/ext2/fs.rs
Normal file
@ -0,0 +1,366 @@
|
||||
use super::block_group::{BlockGroup, RawGroupDescriptor};
|
||||
use super::inode::{FilePerm, FileType, Inode, InodeDesc, RawInode};
|
||||
use super::prelude::*;
|
||||
use super::super_block::{RawSuperBlock, SuperBlock, SUPER_BLOCK_OFFSET};
|
||||
|
||||
/// The root inode number.
|
||||
const ROOT_INO: u32 = 2;
|
||||
|
||||
/// The Ext2 filesystem.
|
||||
#[derive(Debug)]
|
||||
pub struct Ext2 {
|
||||
block_device: Arc<dyn BlockDevice>,
|
||||
super_block: RwMutex<Dirty<SuperBlock>>,
|
||||
block_groups: Vec<BlockGroup>,
|
||||
inodes_per_group: u32,
|
||||
blocks_per_group: u32,
|
||||
inode_size: usize,
|
||||
block_size: usize,
|
||||
group_descriptors_segment: VmSegment,
|
||||
self_ref: Weak<Self>,
|
||||
}
|
||||
|
||||
impl Ext2 {
|
||||
/// Opens and loads an Ext2 from the `block_device`.
|
||||
pub fn open(block_device: Arc<dyn BlockDevice>) -> Result<Arc<Self>> {
|
||||
// Load the superblock
|
||||
// TODO: if the main superblock is corrupted, should we load the backup?
|
||||
let super_block = {
|
||||
let raw_super_block = block_device.read_val::<RawSuperBlock>(SUPER_BLOCK_OFFSET)?;
|
||||
SuperBlock::try_from(raw_super_block)?
|
||||
};
|
||||
assert!(super_block.block_size() == BLOCK_SIZE);
|
||||
|
||||
let group_descriptors_segment = {
|
||||
let npages = ((super_block.block_groups_count() as usize)
|
||||
* core::mem::size_of::<RawGroupDescriptor>())
|
||||
.div_ceil(BLOCK_SIZE);
|
||||
let segment = VmAllocOptions::new(npages)
|
||||
.uninit(true)
|
||||
.is_contiguous(true)
|
||||
.alloc_contiguous()?;
|
||||
match block_device.read_blocks_sync(super_block.group_descriptors_bid(0), &segment)? {
|
||||
BioStatus::Complete => (),
|
||||
err_status => {
|
||||
return Err(Error::from(err_status));
|
||||
}
|
||||
}
|
||||
segment
|
||||
};
|
||||
|
||||
// Load the block groups information
|
||||
let load_block_groups = |fs: Weak<Ext2>,
|
||||
block_device: &dyn BlockDevice,
|
||||
group_descriptors_segment: &VmSegment|
|
||||
-> Result<Vec<BlockGroup>> {
|
||||
let block_groups_count = super_block.block_groups_count() as usize;
|
||||
let mut block_groups = Vec::with_capacity(block_groups_count);
|
||||
for idx in 0..block_groups_count {
|
||||
let block_group = BlockGroup::load(
|
||||
group_descriptors_segment,
|
||||
idx,
|
||||
block_device,
|
||||
&super_block,
|
||||
fs.clone(),
|
||||
)?;
|
||||
block_groups.push(block_group);
|
||||
}
|
||||
Ok(block_groups)
|
||||
};
|
||||
|
||||
let ext2 = Arc::new_cyclic(|weak_ref| Self {
|
||||
inodes_per_group: super_block.inodes_per_group(),
|
||||
blocks_per_group: super_block.blocks_per_group(),
|
||||
inode_size: super_block.inode_size(),
|
||||
block_size: super_block.block_size(),
|
||||
block_groups: load_block_groups(
|
||||
weak_ref.clone(),
|
||||
block_device.as_ref(),
|
||||
&group_descriptors_segment,
|
||||
)
|
||||
.unwrap(),
|
||||
block_device,
|
||||
super_block: RwMutex::new(Dirty::new(super_block)),
|
||||
group_descriptors_segment,
|
||||
self_ref: weak_ref.clone(),
|
||||
});
|
||||
Ok(ext2)
|
||||
}
|
||||
|
||||
/// Returns the block device.
|
||||
pub fn block_device(&self) -> &dyn BlockDevice {
|
||||
self.block_device.as_ref()
|
||||
}
|
||||
|
||||
/// Returns the size of block.
|
||||
pub fn block_size(&self) -> usize {
|
||||
self.block_size
|
||||
}
|
||||
|
||||
/// Returns the size of inode.
|
||||
pub fn inode_size(&self) -> usize {
|
||||
self.inode_size
|
||||
}
|
||||
|
||||
/// Returns the number of inodes in each block group.
|
||||
pub fn inodes_per_group(&self) -> u32 {
|
||||
self.inodes_per_group
|
||||
}
|
||||
|
||||
/// Returns the number of blocks in each block group.
|
||||
pub fn blocks_per_group(&self) -> u32 {
|
||||
self.blocks_per_group
|
||||
}
|
||||
|
||||
/// Returns the super block.
|
||||
pub fn super_block(&self) -> RwMutexReadGuard<'_, Dirty<SuperBlock>> {
|
||||
self.super_block.read()
|
||||
}
|
||||
|
||||
/// Returns the root inode.
|
||||
pub fn root_inode(&self) -> Result<Arc<Inode>> {
|
||||
self.lookup_inode(ROOT_INO)
|
||||
}
|
||||
|
||||
/// Finds and returns the inode by `ino`.
|
||||
pub(super) fn lookup_inode(&self, ino: u32) -> Result<Arc<Inode>> {
|
||||
let (_, block_group) = self.block_group_of_ino(ino)?;
|
||||
let inode_idx = self.inode_idx(ino);
|
||||
block_group.lookup_inode(inode_idx)
|
||||
}
|
||||
|
||||
/// Creates a new inode.
|
||||
pub(super) fn create_inode(
|
||||
&self,
|
||||
dir_block_group_idx: usize,
|
||||
file_type: FileType,
|
||||
file_perm: FilePerm,
|
||||
) -> Result<Arc<Inode>> {
|
||||
let (block_group_idx, ino) =
|
||||
self.alloc_ino(dir_block_group_idx, file_type == FileType::Dir)?;
|
||||
let inode = {
|
||||
let inode_desc = InodeDesc::new(file_type, file_perm);
|
||||
Inode::new(ino, block_group_idx, inode_desc, self.self_ref.clone())
|
||||
};
|
||||
let block_group = &self.block_groups[block_group_idx];
|
||||
block_group.insert_cache(self.inode_idx(ino), inode.clone());
|
||||
Ok(inode)
|
||||
}
|
||||
|
||||
/// Allocates a new inode number, internally used by `new_inode`.
|
||||
///
|
||||
/// Attempts to allocate from the `dir_block_group_idx` group first.
|
||||
/// If allocation is not possible from this group, then search the remaining groups.
|
||||
fn alloc_ino(&self, dir_block_group_idx: usize, is_dir: bool) -> Result<(usize, u32)> {
|
||||
let mut block_group_idx = dir_block_group_idx;
|
||||
if block_group_idx >= self.block_groups.len() {
|
||||
return_errno_with_message!(Errno::EINVAL, "invalid block group idx");
|
||||
}
|
||||
|
||||
for _ in 0..self.block_groups.len() {
|
||||
if block_group_idx >= self.block_groups.len() {
|
||||
block_group_idx = 0;
|
||||
}
|
||||
let block_group = &self.block_groups[block_group_idx];
|
||||
if let Some(inode_idx) = block_group.alloc_inode(is_dir) {
|
||||
let ino = block_group_idx as u32 * self.inodes_per_group + inode_idx + 1;
|
||||
self.super_block.write().dec_free_inodes();
|
||||
return Ok((block_group_idx, ino));
|
||||
}
|
||||
block_group_idx += 1;
|
||||
}
|
||||
|
||||
return_errno_with_message!(Errno::ENOSPC, "no space on device");
|
||||
}
|
||||
|
||||
/// Frees an inode.
|
||||
pub(super) fn free_inode(&self, ino: u32, is_dir: bool) -> Result<()> {
|
||||
let (_, block_group) = self.block_group_of_ino(ino)?;
|
||||
let inode_idx = self.inode_idx(ino);
|
||||
// In order to prevent value underflow, it is necessary to increment
|
||||
// the free inode counter prior to freeing the inode.
|
||||
self.super_block.write().inc_free_inodes();
|
||||
block_group.free_inode(inode_idx, is_dir);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Writes back the metadata of inode.
|
||||
pub(super) fn sync_inode(&self, ino: u32, inode: &InodeDesc) -> Result<()> {
|
||||
let (_, block_group) = self.block_group_of_ino(ino)?;
|
||||
let inode_idx = self.inode_idx(ino);
|
||||
block_group.sync_raw_inode(inode_idx, &RawInode::from(inode));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Writes back the block group descriptor to the descriptors table.
|
||||
pub(super) fn sync_group_descriptor(
|
||||
&self,
|
||||
block_group_idx: usize,
|
||||
raw_descriptor: &RawGroupDescriptor,
|
||||
) -> Result<()> {
|
||||
let offset = block_group_idx * core::mem::size_of::<RawGroupDescriptor>();
|
||||
self.group_descriptors_segment
|
||||
.write_val(offset, raw_descriptor)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Allocates a new block.
|
||||
///
|
||||
/// Attempts to allocate from the `block_group_idx` group first.
|
||||
/// If allocation is not possible from this group, then search the remaining groups.
|
||||
pub(super) fn alloc_block(&self, block_group_idx: usize) -> Result<Bid> {
|
||||
let mut block_group_idx = block_group_idx;
|
||||
if block_group_idx >= self.block_groups.len() {
|
||||
return_errno_with_message!(Errno::EINVAL, "invalid block group idx");
|
||||
}
|
||||
|
||||
for _ in 0..self.block_groups.len() {
|
||||
if block_group_idx >= self.block_groups.len() {
|
||||
block_group_idx = 0;
|
||||
}
|
||||
let block_group = &self.block_groups[block_group_idx];
|
||||
if let Some(block_idx) = block_group.alloc_block() {
|
||||
let bid = block_group_idx as u32 * self.blocks_per_group + block_idx;
|
||||
self.super_block.write().dec_free_blocks();
|
||||
return Ok(Bid::new(bid as _));
|
||||
}
|
||||
block_group_idx += 1;
|
||||
}
|
||||
|
||||
return_errno_with_message!(Errno::ENOSPC, "no space on device");
|
||||
}
|
||||
|
||||
/// Frees a block.
|
||||
pub(super) fn free_block(&self, bid: Bid) -> Result<()> {
|
||||
let (_, block_group) = self.block_group_of_bid(bid)?;
|
||||
let block_idx = self.block_idx(bid);
|
||||
// In order to prevent value underflow, it is necessary to increment
|
||||
// the free block counter prior to freeing the block.
|
||||
self.super_block.write().inc_free_blocks();
|
||||
block_group.free_block(block_idx);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Reads contiguous blocks starting from the `bid` synchronously.
|
||||
pub(super) fn read_blocks(&self, bid: Bid, segment: &VmSegment) -> Result<()> {
|
||||
let status = self.block_device.read_blocks_sync(bid, segment)?;
|
||||
match status {
|
||||
BioStatus::Complete => Ok(()),
|
||||
err_status => Err(Error::from(err_status)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads one block indicated by the `bid` synchronously.
|
||||
pub(super) fn read_block(&self, bid: Bid, frame: &VmFrame) -> Result<()> {
|
||||
let status = self.block_device.read_block_sync(bid, frame)?;
|
||||
match status {
|
||||
BioStatus::Complete => Ok(()),
|
||||
err_status => Err(Error::from(err_status)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes contiguous blocks starting from the `bid` synchronously.
|
||||
pub(super) fn write_blocks(&self, bid: Bid, segment: &VmSegment) -> Result<()> {
|
||||
let status = self.block_device.write_blocks_sync(bid, segment)?;
|
||||
match status {
|
||||
BioStatus::Complete => Ok(()),
|
||||
err_status => Err(Error::from(err_status)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes one block indicated by the `bid` synchronously.
|
||||
pub(super) fn write_block(&self, bid: Bid, frame: &VmFrame) -> Result<()> {
|
||||
let status = self.block_device.write_block_sync(bid, frame)?;
|
||||
match status {
|
||||
BioStatus::Complete => Ok(()),
|
||||
err_status => Err(Error::from(err_status)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes back the metadata to the block device.
|
||||
pub fn sync_metadata(&self) -> Result<()> {
|
||||
// If the superblock is clean, the block groups must be clean.
|
||||
if !self.super_block.read().is_dirty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut super_block = self.super_block.write();
|
||||
// Writes back the metadata of block groups
|
||||
for block_group in &self.block_groups {
|
||||
block_group.sync_metadata(&super_block)?;
|
||||
}
|
||||
|
||||
let mut bio_waiter = BioWaiter::new();
|
||||
// Writes back the main superblock and group descriptor table.
|
||||
let raw_super_block = RawSuperBlock::from((*super_block).deref());
|
||||
bio_waiter.concat(
|
||||
self.block_device
|
||||
.write_bytes_async(SUPER_BLOCK_OFFSET, raw_super_block.as_bytes())?,
|
||||
);
|
||||
bio_waiter.concat(self.block_device.write_blocks(
|
||||
super_block.group_descriptors_bid(0),
|
||||
&self.group_descriptors_segment,
|
||||
)?);
|
||||
|
||||
// Writes back the backups of superblock and group descriptor table.
|
||||
let mut raw_super_block_backup = raw_super_block;
|
||||
for idx in 1..super_block.block_groups_count() {
|
||||
if super_block.is_backup_group(idx as usize) {
|
||||
raw_super_block_backup.block_group_idx = idx as u16;
|
||||
bio_waiter.concat(self.block_device.write_bytes_async(
|
||||
super_block.bid(idx as usize).to_offset(),
|
||||
raw_super_block_backup.as_bytes(),
|
||||
)?);
|
||||
bio_waiter.concat(self.block_device.write_blocks(
|
||||
super_block.group_descriptors_bid(idx as usize),
|
||||
&self.group_descriptors_segment,
|
||||
)?);
|
||||
}
|
||||
}
|
||||
|
||||
// Waits for the completion of all submitted bios.
|
||||
bio_waiter
|
||||
.wait()
|
||||
.ok_or_else(|| Error::with_message(Errno::EIO, "failed to sync metadata of fs"))?;
|
||||
|
||||
// Reset to clean.
|
||||
super_block.clear_dirty();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Writes back all the cached inodes to the block device.
|
||||
pub fn sync_all_inodes(&self) -> Result<()> {
|
||||
for block_group in &self.block_groups {
|
||||
block_group.sync_all_inodes()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn block_group_of_bid(&self, bid: Bid) -> Result<(usize, &BlockGroup)> {
|
||||
let block_group_idx = (bid.to_raw() / (self.blocks_per_group as u64)) as usize;
|
||||
if block_group_idx >= self.block_groups.len() {
|
||||
return_errno!(Errno::ENOENT);
|
||||
}
|
||||
Ok((block_group_idx, &self.block_groups[block_group_idx]))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn block_group_of_ino(&self, ino: u32) -> Result<(usize, &BlockGroup)> {
|
||||
let block_group_idx = ((ino - 1) / self.inodes_per_group) as usize;
|
||||
if block_group_idx >= self.block_groups.len() {
|
||||
return_errno!(Errno::ENOENT);
|
||||
}
|
||||
Ok((block_group_idx, &self.block_groups[block_group_idx]))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn inode_idx(&self, ino: u32) -> u32 {
|
||||
(ino - 1) % self.inodes_per_group
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn block_idx(&self, bid: Bid) -> u32 {
|
||||
(bid.to_raw() as u32) % self.blocks_per_group
|
||||
}
|
||||
}
|
43
services/libs/aster-std/src/fs/ext2/impl_for_vfs/fs.rs
Normal file
43
services/libs/aster-std/src/fs/ext2/impl_for_vfs/fs.rs
Normal file
@ -0,0 +1,43 @@
|
||||
use crate::fs::ext2::{utils::Dirty, Ext2, SuperBlock as Ext2SuperBlock, MAGIC_NUM as EXT2_MAGIC};
|
||||
use crate::fs::utils::{FileSystem, FsFlags, Inode, SuperBlock, NAME_MAX};
|
||||
use crate::prelude::*;
|
||||
|
||||
use aster_frame::sync::RwMutexReadGuard;
|
||||
|
||||
impl FileSystem for Ext2 {
|
||||
fn sync(&self) -> Result<()> {
|
||||
self.sync_all_inodes()?;
|
||||
self.sync_metadata()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn root_inode(&self) -> Arc<dyn Inode> {
|
||||
self.root_inode().unwrap()
|
||||
}
|
||||
|
||||
fn sb(&self) -> SuperBlock {
|
||||
SuperBlock::from(self.super_block())
|
||||
}
|
||||
|
||||
fn flags(&self) -> FsFlags {
|
||||
FsFlags::empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RwMutexReadGuard<'_, Dirty<Ext2SuperBlock>>> for SuperBlock {
|
||||
fn from(ext2_sb: RwMutexReadGuard<Dirty<Ext2SuperBlock>>) -> Self {
|
||||
Self {
|
||||
magic: EXT2_MAGIC as _,
|
||||
bsize: ext2_sb.block_size(),
|
||||
blocks: ext2_sb.total_blocks() as _,
|
||||
bfree: ext2_sb.free_blocks() as _,
|
||||
bavail: ext2_sb.free_blocks() as _,
|
||||
files: ext2_sb.total_inodes() as _,
|
||||
ffree: ext2_sb.free_inodes() as _,
|
||||
fsid: 0, // TODO
|
||||
namelen: NAME_MAX,
|
||||
frsize: ext2_sb.fragment_size(),
|
||||
flags: 0, // TODO
|
||||
}
|
||||
}
|
||||
}
|
175
services/libs/aster-std/src/fs/ext2/impl_for_vfs/inode.rs
Normal file
175
services/libs/aster-std/src/fs/ext2/impl_for_vfs/inode.rs
Normal file
@ -0,0 +1,175 @@
|
||||
use crate::fs::device::Device;
|
||||
use crate::fs::ext2::{FilePerm, FileType, Inode as Ext2Inode};
|
||||
use crate::fs::utils::{
|
||||
DirentVisitor, FileSystem, Inode, InodeMode, InodeType, IoctlCmd, Metadata,
|
||||
};
|
||||
use crate::prelude::*;
|
||||
use crate::vm::vmo::Vmo;
|
||||
|
||||
use aster_rights::Full;
|
||||
use core::time::Duration;
|
||||
|
||||
impl Inode for Ext2Inode {
|
||||
fn len(&self) -> usize {
|
||||
self.file_size() as _
|
||||
}
|
||||
|
||||
fn resize(&self, new_size: usize) -> Result<()> {
|
||||
self.resize(new_size)
|
||||
}
|
||||
|
||||
fn metadata(&self) -> Metadata {
|
||||
Metadata {
|
||||
dev: 0, // TODO: ID of block device
|
||||
ino: self.ino() as _,
|
||||
size: self.file_size() as _,
|
||||
blk_size: self.fs().super_block().block_size(),
|
||||
blocks: self.blocks_count() as _,
|
||||
atime: self.atime(),
|
||||
mtime: self.mtime(),
|
||||
ctime: self.ctime(),
|
||||
type_: InodeType::from(self.file_type()),
|
||||
mode: InodeMode::from(self.file_perm()),
|
||||
nlinks: self.hard_links() as _,
|
||||
uid: self.uid() as _,
|
||||
gid: self.gid() as _,
|
||||
rdev: self.device_id(),
|
||||
}
|
||||
}
|
||||
|
||||
fn atime(&self) -> Duration {
|
||||
self.atime()
|
||||
}
|
||||
|
||||
fn set_atime(&self, time: Duration) {
|
||||
self.set_atime(time)
|
||||
}
|
||||
|
||||
fn mtime(&self) -> Duration {
|
||||
self.mtime()
|
||||
}
|
||||
|
||||
fn set_mtime(&self, time: Duration) {
|
||||
self.set_mtime(time)
|
||||
}
|
||||
|
||||
fn ino(&self) -> u64 {
|
||||
self.ino() as _
|
||||
}
|
||||
|
||||
fn type_(&self) -> InodeType {
|
||||
InodeType::from(self.file_type())
|
||||
}
|
||||
|
||||
fn mode(&self) -> InodeMode {
|
||||
InodeMode::from(self.file_perm())
|
||||
}
|
||||
|
||||
fn set_mode(&self, mode: InodeMode) {
|
||||
self.set_file_perm(mode.into());
|
||||
}
|
||||
|
||||
fn page_cache(&self) -> Option<Vmo<Full>> {
|
||||
Some(self.page_cache())
|
||||
}
|
||||
|
||||
fn read_at(&self, offset: usize, buf: &mut [u8]) -> Result<usize> {
|
||||
self.read_at(offset, buf)
|
||||
}
|
||||
|
||||
fn read_direct_at(&self, offset: usize, buf: &mut [u8]) -> Result<usize> {
|
||||
self.read_direct_at(offset, buf)
|
||||
}
|
||||
|
||||
fn write_at(&self, offset: usize, buf: &[u8]) -> Result<usize> {
|
||||
self.write_at(offset, buf)
|
||||
}
|
||||
|
||||
fn write_direct_at(&self, offset: usize, buf: &[u8]) -> Result<usize> {
|
||||
self.write_direct_at(offset, buf)
|
||||
}
|
||||
|
||||
fn create(&self, name: &str, type_: InodeType, mode: InodeMode) -> Result<Arc<dyn Inode>> {
|
||||
Ok(self.create(name, type_.into(), mode.into())?)
|
||||
}
|
||||
|
||||
fn mknod(&self, name: &str, mode: InodeMode, dev: Arc<dyn Device>) -> Result<Arc<dyn Inode>> {
|
||||
let inode = self.create(name, InodeType::from(dev.type_()).into(), mode.into())?;
|
||||
inode.set_device_id(dev.id().into()).unwrap();
|
||||
Ok(inode)
|
||||
}
|
||||
|
||||
fn lookup(&self, name: &str) -> Result<Arc<dyn Inode>> {
|
||||
Ok(self.lookup(name)?)
|
||||
}
|
||||
|
||||
fn readdir_at(&self, offset: usize, visitor: &mut dyn DirentVisitor) -> Result<usize> {
|
||||
self.readdir_at(offset, visitor)
|
||||
}
|
||||
|
||||
fn link(&self, old: &Arc<dyn Inode>, name: &str) -> Result<()> {
|
||||
let old = old
|
||||
.downcast_ref::<Ext2Inode>()
|
||||
.ok_or_else(|| Error::with_message(Errno::EXDEV, "not same fs"))?;
|
||||
self.link(old, name)
|
||||
}
|
||||
|
||||
fn unlink(&self, name: &str) -> Result<()> {
|
||||
self.unlink(name)
|
||||
}
|
||||
|
||||
fn rmdir(&self, name: &str) -> Result<()> {
|
||||
self.rmdir(name)
|
||||
}
|
||||
|
||||
fn rename(&self, old_name: &str, target: &Arc<dyn Inode>, new_name: &str) -> Result<()> {
|
||||
let target = target
|
||||
.downcast_ref::<Ext2Inode>()
|
||||
.ok_or_else(|| Error::with_message(Errno::EXDEV, "not same fs"))?;
|
||||
self.rename(old_name, target, new_name)
|
||||
}
|
||||
|
||||
fn read_link(&self) -> Result<String> {
|
||||
self.read_link()
|
||||
}
|
||||
|
||||
fn write_link(&self, target: &str) -> Result<()> {
|
||||
self.write_link(target)
|
||||
}
|
||||
|
||||
fn ioctl(&self, cmd: IoctlCmd, arg: usize) -> Result<i32> {
|
||||
Err(Error::new(Errno::EINVAL))
|
||||
}
|
||||
|
||||
fn sync(&self) -> Result<()> {
|
||||
self.sync_all()
|
||||
}
|
||||
|
||||
fn fs(&self) -> Arc<dyn FileSystem> {
|
||||
self.fs()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FilePerm> for InodeMode {
|
||||
fn from(perm: FilePerm) -> Self {
|
||||
Self::from_bits_truncate(perm.bits() as _)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<InodeMode> for FilePerm {
|
||||
fn from(mode: InodeMode) -> Self {
|
||||
Self::from_bits_truncate(mode.bits() as _)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FileType> for InodeType {
|
||||
fn from(type_: FileType) -> Self {
|
||||
Self::try_from(type_ as u32).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<InodeType> for FileType {
|
||||
fn from(type_: InodeType) -> Self {
|
||||
Self::try_from(type_ as u16).unwrap()
|
||||
}
|
||||
}
|
2
services/libs/aster-std/src/fs/ext2/impl_for_vfs/mod.rs
Normal file
2
services/libs/aster-std/src/fs/ext2/impl_for_vfs/mod.rs
Normal file
@ -0,0 +1,2 @@
|
||||
mod fs;
|
||||
mod inode;
|
1407
services/libs/aster-std/src/fs/ext2/inode.rs
Normal file
1407
services/libs/aster-std/src/fs/ext2/inode.rs
Normal file
File diff suppressed because it is too large
Load Diff
50
services/libs/aster-std/src/fs/ext2/mod.rs
Normal file
50
services/libs/aster-std/src/fs/ext2/mod.rs
Normal file
@ -0,0 +1,50 @@
|
||||
//! A safe Rust Ext2 filesystem.
|
||||
//!
|
||||
//! The Second Extended File System(Ext2) is a major rewrite of the Ext filesystem.
|
||||
//! It is the predominant filesystem in use by Linux from the early 1990s to the early 2000s.
|
||||
//! The structures of Ext3 and Ext4 are based on Ext2 and add some additional options
|
||||
//! such as journaling.
|
||||
//!
|
||||
//! The features of this version of Ext2 are as follows:
|
||||
//! 1. No unsafe Rust. The filesystem is written is Rust without any unsafe code,
|
||||
//! ensuring that there are no memory safety issues in the code.
|
||||
//! 2. Deep integration with PageCache. The data and metadata of the filesystem are
|
||||
//! stored in PageCache, which accelerates the performance of data access.
|
||||
//! 3. Compatible with queue-based block device. The filesystem can submits multiple
|
||||
//! BIO requests to be block device at once, thereby enhancing I/O performance.
|
||||
//!
|
||||
//! # Example
|
||||
//!
|
||||
//! ```no_run
|
||||
//! // Opens an Ext2 from the block device.
|
||||
//! let ext2 = Ext2::open(block_device)?;
|
||||
//! // Lookup the root inode.
|
||||
//! let root = ext2.root_inode()?;
|
||||
//! // Create a file inside root directory.
|
||||
//! let file = root.create("file", FileType::File, FilePerm::from_bits_truncate(0o666))?;
|
||||
//! // Write data into the file.
|
||||
//! const WRITE_DATA: &[u8] = b"Hello, World";
|
||||
//! let len = file.write_at(0, WRITE_DATA)?;
|
||||
//! assert!(len == WRITE_DATA.len());
|
||||
//! ```
|
||||
//!
|
||||
//! # Limitation
|
||||
//!
|
||||
//! Here we summarizes the features that need to be implemented in the future.
|
||||
//! 1. Supports large file.
|
||||
//! 2. Supports merging small read/write operations.
|
||||
//! 3. Handles the intermediate failure status correctly.
|
||||
|
||||
pub use fs::Ext2;
|
||||
pub use inode::{FilePerm, FileType, Inode};
|
||||
pub use super_block::{SuperBlock, MAGIC_NUM};
|
||||
|
||||
mod block_group;
|
||||
mod blocks_hole;
|
||||
mod dir;
|
||||
mod fs;
|
||||
mod impl_for_vfs;
|
||||
mod inode;
|
||||
mod prelude;
|
||||
mod super_block;
|
||||
mod utils;
|
23
services/libs/aster-std/src/fs/ext2/prelude.rs
Normal file
23
services/libs/aster-std/src/fs/ext2/prelude.rs
Normal file
@ -0,0 +1,23 @@
|
||||
pub(super) use super::utils::{Dirty, IsPowerOf};
|
||||
|
||||
pub(super) use crate::fs::utils::{
|
||||
CStr256, DirentVisitor, InodeType, PageCache, PageCacheBackend, Str16, Str64,
|
||||
};
|
||||
pub(super) use crate::prelude::*;
|
||||
pub(super) use crate::time::UnixTime;
|
||||
pub(super) use crate::vm::vmo::Vmo;
|
||||
|
||||
pub(super) use align_ext::AlignExt;
|
||||
pub(super) use aster_block::{
|
||||
bio::{BioStatus, BioWaiter},
|
||||
id::Bid,
|
||||
BlockDevice, BLOCK_SIZE,
|
||||
};
|
||||
pub(super) use aster_frame::sync::{RwMutex, RwMutexReadGuard};
|
||||
pub(super) use aster_frame::vm::VmAllocOptions;
|
||||
pub(super) use aster_frame::vm::VmIo;
|
||||
pub(super) use aster_frame::vm::{VmFrame, VmSegment};
|
||||
pub(super) use aster_rights::Full;
|
||||
pub(super) use core::ops::{Deref, DerefMut};
|
||||
pub(super) use core::time::Duration;
|
||||
pub(super) use static_assertions::const_assert;
|
542
services/libs/aster-std/src/fs/ext2/super_block.rs
Normal file
542
services/libs/aster-std/src/fs/ext2/super_block.rs
Normal file
@ -0,0 +1,542 @@
|
||||
use super::inode::RawInode;
|
||||
use super::prelude::*;
|
||||
|
||||
/// The magic number of Ext2.
|
||||
pub const MAGIC_NUM: u16 = 0xef53;
|
||||
|
||||
/// The main superblock is located at byte 1024 from the beginning of the device.
|
||||
pub const SUPER_BLOCK_OFFSET: usize = 1024;
|
||||
|
||||
const SUPER_BLOCK_SIZE: usize = 1024;
|
||||
|
||||
/// The in-memory rust superblock.
|
||||
///
|
||||
/// It contains all information about the layout of the Ext2.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct SuperBlock {
|
||||
/// Total number of inodes.
|
||||
inodes_count: u32,
|
||||
/// Total number of blocks.
|
||||
blocks_count: u32,
|
||||
/// Total number of reserved blocks.
|
||||
reserved_blocks_count: u32,
|
||||
/// Total number of free blocks.
|
||||
free_blocks_count: u32,
|
||||
/// Total number of free inodes.
|
||||
free_inodes_count: u32,
|
||||
/// First data block.
|
||||
first_data_block: Bid,
|
||||
/// Block size.
|
||||
block_size: usize,
|
||||
/// Fragment size.
|
||||
frag_size: usize,
|
||||
/// Number of blocks in each block group.
|
||||
blocks_per_group: u32,
|
||||
/// Number of fragments in each block group.
|
||||
frags_per_group: u32,
|
||||
/// Number of inodes in each block group.
|
||||
inodes_per_group: u32,
|
||||
/// Mount time.
|
||||
mtime: UnixTime,
|
||||
/// Write time.
|
||||
wtime: UnixTime,
|
||||
/// Mount count.
|
||||
mnt_count: u16,
|
||||
/// Maximal mount count.
|
||||
max_mnt_count: u16,
|
||||
/// Magic signature.
|
||||
magic: u16,
|
||||
/// Filesystem state.
|
||||
state: FsState,
|
||||
/// Behaviour when detecting errors.
|
||||
errors_behaviour: ErrorsBehaviour,
|
||||
/// Time of last check.
|
||||
last_check_time: UnixTime,
|
||||
/// Interval between checks.
|
||||
check_interval: Duration,
|
||||
/// Creator OS ID.
|
||||
creator_os: OsId,
|
||||
/// Revision level.
|
||||
rev_level: RevLevel,
|
||||
/// Default uid for reserved blocks.
|
||||
def_resuid: u32,
|
||||
/// Default gid for reserved blocks.
|
||||
def_resgid: u32,
|
||||
//
|
||||
// These fields are valid for RevLevel::Dynamic only.
|
||||
//
|
||||
/// First non-reserved inode number.
|
||||
first_ino: u32,
|
||||
/// Size of inode structure.
|
||||
inode_size: usize,
|
||||
/// Block group that this superblock is part of (if backup copy).
|
||||
block_group_idx: usize,
|
||||
/// Compatible feature set.
|
||||
feature_compat: FeatureCompatSet,
|
||||
/// Incompatible feature set.
|
||||
feature_incompat: FeatureInCompatSet,
|
||||
/// Readonly-compatible feature set.
|
||||
feature_ro_compat: FeatureRoCompatSet,
|
||||
/// 128-bit uuid for volume.
|
||||
uuid: [u8; 16],
|
||||
/// Volume name.
|
||||
volume_name: Str16,
|
||||
/// Directory where last mounted.
|
||||
last_mounted_dir: Str64,
|
||||
///
|
||||
/// This fields are valid if the FeatureCompatSet::DIR_PREALLOC is set.
|
||||
///
|
||||
/// Number of blocks to preallocate for files.
|
||||
prealloc_file_blocks: u8,
|
||||
/// Number of blocks to preallocate for directories.
|
||||
prealloc_dir_blocks: u8,
|
||||
}
|
||||
|
||||
impl TryFrom<RawSuperBlock> for SuperBlock {
|
||||
type Error = crate::error::Error;
|
||||
|
||||
fn try_from(sb: RawSuperBlock) -> Result<Self> {
|
||||
Ok(Self {
|
||||
inodes_count: sb.inodes_count,
|
||||
blocks_count: sb.blocks_count,
|
||||
reserved_blocks_count: sb.reserved_blocks_count,
|
||||
free_blocks_count: sb.free_blocks_count,
|
||||
free_inodes_count: sb.free_inodes_count,
|
||||
first_data_block: Bid::new(sb.first_data_block as _),
|
||||
block_size: 1024 << sb.log_block_size,
|
||||
frag_size: 1024 << sb.log_frag_size,
|
||||
blocks_per_group: sb.blocks_per_group,
|
||||
frags_per_group: sb.frags_per_group,
|
||||
inodes_per_group: sb.inodes_per_group,
|
||||
mtime: sb.mtime,
|
||||
wtime: sb.wtime,
|
||||
mnt_count: sb.mnt_count,
|
||||
max_mnt_count: sb.max_mnt_count,
|
||||
magic: {
|
||||
if sb.magic != MAGIC_NUM {
|
||||
return_errno_with_message!(Errno::EINVAL, "bad ext2 magic number");
|
||||
}
|
||||
MAGIC_NUM
|
||||
},
|
||||
state: {
|
||||
let state = FsState::try_from(sb.state)
|
||||
.map_err(|_| Error::with_message(Errno::EINVAL, "invalid fs state"))?;
|
||||
if state == FsState::Corrupted {
|
||||
return_errno_with_message!(Errno::EUCLEAN, "fs is corrupted");
|
||||
}
|
||||
state
|
||||
},
|
||||
errors_behaviour: ErrorsBehaviour::try_from(sb.errors)
|
||||
.map_err(|_| Error::with_message(Errno::EINVAL, "invalid errors behaviour"))?,
|
||||
last_check_time: sb.last_check_time,
|
||||
check_interval: Duration::from_secs(sb.check_interval as _),
|
||||
creator_os: {
|
||||
let os_id = OsId::try_from(sb.creator_os)
|
||||
.map_err(|_| Error::with_message(Errno::EINVAL, "invalid creater os"))?;
|
||||
if os_id != OsId::Linux {
|
||||
return_errno_with_message!(Errno::EINVAL, "not supported os id");
|
||||
}
|
||||
OsId::Linux
|
||||
},
|
||||
rev_level: {
|
||||
let rev_level = RevLevel::try_from(sb.rev_level)
|
||||
.map_err(|_| Error::with_message(Errno::EINVAL, "invalid revision level"))?;
|
||||
if rev_level != RevLevel::Dynamic {
|
||||
return_errno_with_message!(Errno::EINVAL, "not supported rev level");
|
||||
}
|
||||
RevLevel::Dynamic
|
||||
},
|
||||
def_resuid: sb.def_resuid as _,
|
||||
def_resgid: sb.def_resgid as _,
|
||||
first_ino: sb.first_ino,
|
||||
inode_size: {
|
||||
let inode_size = sb.inode_size as _;
|
||||
if inode_size < core::mem::size_of::<RawInode>() {
|
||||
return_errno_with_message!(Errno::EINVAL, "inode size is too small");
|
||||
}
|
||||
inode_size
|
||||
},
|
||||
block_group_idx: sb.block_group_idx as _,
|
||||
feature_compat: FeatureCompatSet::from_bits(sb.feature_compat).ok_or(
|
||||
Error::with_message(Errno::EINVAL, "invalid feature compat set"),
|
||||
)?,
|
||||
feature_incompat: FeatureInCompatSet::from_bits(sb.feature_incompat).ok_or(
|
||||
Error::with_message(Errno::EINVAL, "invalid feature incompat set"),
|
||||
)?,
|
||||
feature_ro_compat: FeatureRoCompatSet::from_bits(sb.feature_ro_compat).ok_or(
|
||||
Error::with_message(Errno::EINVAL, "invalid feature ro compat set"),
|
||||
)?,
|
||||
uuid: sb.uuid,
|
||||
volume_name: sb.volume_name,
|
||||
last_mounted_dir: sb.last_mounted_dir,
|
||||
prealloc_file_blocks: sb.prealloc_file_blocks,
|
||||
prealloc_dir_blocks: sb.prealloc_dir_blocks,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl SuperBlock {
|
||||
/// Returns the block size.
|
||||
pub fn block_size(&self) -> usize {
|
||||
self.block_size
|
||||
}
|
||||
|
||||
/// Returns the size of inode structure.
|
||||
pub fn inode_size(&self) -> usize {
|
||||
self.inode_size
|
||||
}
|
||||
|
||||
/// Returns the fragment size.
|
||||
pub fn fragment_size(&self) -> usize {
|
||||
self.frag_size
|
||||
}
|
||||
|
||||
/// Returns total number of inodes.
|
||||
pub fn total_inodes(&self) -> u32 {
|
||||
self.inodes_count
|
||||
}
|
||||
|
||||
/// Returns total number of blocks.
|
||||
pub fn total_blocks(&self) -> u32 {
|
||||
self.blocks_count
|
||||
}
|
||||
|
||||
/// Returns the number of blocks in each block group.
|
||||
pub fn blocks_per_group(&self) -> u32 {
|
||||
self.blocks_per_group
|
||||
}
|
||||
|
||||
/// Returns the number of inodes in each block group.
|
||||
pub fn inodes_per_group(&self) -> u32 {
|
||||
self.inodes_per_group
|
||||
}
|
||||
|
||||
/// Returns the number of block groups.
|
||||
pub fn block_groups_count(&self) -> u32 {
|
||||
self.blocks_count / self.blocks_per_group
|
||||
}
|
||||
|
||||
/// Returns the filesystem state.
|
||||
pub fn state(&self) -> FsState {
|
||||
self.state
|
||||
}
|
||||
|
||||
/// Returns the revision level.
|
||||
pub fn rev_level(&self) -> RevLevel {
|
||||
self.rev_level
|
||||
}
|
||||
|
||||
/// Returns the compatible feature set.
|
||||
pub fn feature_compat(&self) -> FeatureCompatSet {
|
||||
self.feature_compat
|
||||
}
|
||||
|
||||
/// Returns the incompatible feature set.
|
||||
pub fn feature_incompat(&self) -> FeatureInCompatSet {
|
||||
self.feature_incompat
|
||||
}
|
||||
|
||||
/// Returns the readonly-compatible feature set.
|
||||
pub fn feature_ro_compat(&self) -> FeatureRoCompatSet {
|
||||
self.feature_ro_compat
|
||||
}
|
||||
|
||||
/// Returns the number of free blocks.
|
||||
pub fn free_blocks(&self) -> u32 {
|
||||
self.free_blocks_count
|
||||
}
|
||||
|
||||
/// Increase the number of free blocks.
|
||||
pub(super) fn inc_free_blocks(&mut self) {
|
||||
self.free_blocks_count += 1;
|
||||
}
|
||||
|
||||
/// Decrease the number of free blocks.
|
||||
pub(super) fn dec_free_blocks(&mut self) {
|
||||
debug_assert!(self.free_blocks_count > 0);
|
||||
self.free_blocks_count -= 1;
|
||||
}
|
||||
|
||||
/// Returns the number of free inodes.
|
||||
pub fn free_inodes(&self) -> u32 {
|
||||
self.free_inodes_count
|
||||
}
|
||||
|
||||
/// Increase the number of free inodes.
|
||||
pub(super) fn inc_free_inodes(&mut self) {
|
||||
self.free_inodes_count += 1;
|
||||
}
|
||||
|
||||
/// Decrease the number of free inodes.
|
||||
pub(super) fn dec_free_inodes(&mut self) {
|
||||
debug_assert!(self.free_inodes_count > 0);
|
||||
self.free_inodes_count -= 1;
|
||||
}
|
||||
|
||||
/// Checks if the block group will backup the super block.
|
||||
pub(super) fn is_backup_group(&self, block_group_idx: usize) -> bool {
|
||||
if block_group_idx == 0 {
|
||||
false
|
||||
} else if self
|
||||
.feature_ro_compat
|
||||
.contains(FeatureRoCompatSet::SPARSE_SUPER)
|
||||
{
|
||||
// The backup groups chosen are 1 and powers of 3, 5 and 7.
|
||||
block_group_idx == 1
|
||||
|| block_group_idx.is_power_of(3)
|
||||
|| block_group_idx.is_power_of(5)
|
||||
|| block_group_idx.is_power_of(7)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the starting block id of the super block
|
||||
/// inside the block group pointed by `block_group_idx`.
|
||||
///
|
||||
/// # Panic
|
||||
///
|
||||
/// If `block_group_idx` is neither 0 nor a backup block group index,
|
||||
/// then the method panics.
|
||||
pub(super) fn bid(&self, block_group_idx: usize) -> Bid {
|
||||
if block_group_idx == 0 {
|
||||
let bid = (SUPER_BLOCK_OFFSET / self.block_size) as u64;
|
||||
return Bid::new(bid);
|
||||
}
|
||||
|
||||
assert!(self.is_backup_group(block_group_idx));
|
||||
let super_block_bid = block_group_idx * (self.blocks_per_group as usize);
|
||||
Bid::new(super_block_bid as u64)
|
||||
}
|
||||
|
||||
/// Returns the starting block id of the block group descripter table
|
||||
/// inside the block group pointed by `block_group_idx`.
|
||||
///
|
||||
/// # Panic
|
||||
///
|
||||
/// If `block_group_idx` is neither 0 nor a backup block group index,
|
||||
/// then the method panics.
|
||||
pub(super) fn group_descriptors_bid(&self, block_group_idx: usize) -> Bid {
|
||||
let super_block_bid = self.bid(block_group_idx);
|
||||
super_block_bid + (SUPER_BLOCK_SIZE.div_ceil(self.block_size) as u64)
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
/// Compatible feature set.
|
||||
pub struct FeatureCompatSet: u32 {
|
||||
/// Preallocate some number of blocks to a directory when creating a new one
|
||||
const DIR_PREALLOC = 1 << 0;
|
||||
/// AFS server inodes exist
|
||||
const IMAGIC_INODES = 1 << 1;
|
||||
/// File system has a journal
|
||||
const HAS_JOURNAL = 1 << 2;
|
||||
/// Inodes have extended attributes
|
||||
const EXT_ATTR = 1 << 3;
|
||||
/// File system can resize itself for larger partitions
|
||||
const RESIZE_INO = 1 << 4;
|
||||
/// Directories use hash index
|
||||
const DIR_INDEX = 1 << 5;
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
/// Incompatible feature set.
|
||||
pub struct FeatureInCompatSet: u32 {
|
||||
/// Compression is used
|
||||
const COMPRESSION = 1 << 0;
|
||||
/// Directory entries contain a type field
|
||||
const FILETYPE = 1 << 1;
|
||||
/// File system needs to replay its journal
|
||||
const RECOVER = 1 << 2;
|
||||
/// File system uses a journal device
|
||||
const JOURNAL_DEV = 1 << 3;
|
||||
/// Metablock block group
|
||||
const META_BG = 1 << 4;
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
/// Readonly-compatible feature set.
|
||||
pub struct FeatureRoCompatSet: u32 {
|
||||
/// Sparse superblocks and group descriptor tables
|
||||
const SPARSE_SUPER = 1 << 0;
|
||||
/// File system uses a 64-bit file size
|
||||
const LARGE_FILE = 1 << 1;
|
||||
/// Directory contents are stored in the form of a Binary Tree
|
||||
const BTREE_DIR = 1 << 2;
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(u16)]
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, TryFromInt)]
|
||||
pub enum FsState {
|
||||
/// Unmounted cleanly
|
||||
Valid = 1,
|
||||
/// Errors detected
|
||||
Err = 2,
|
||||
/// Filesystem is corrupted (EUCLEAN)
|
||||
Corrupted = 117,
|
||||
}
|
||||
|
||||
#[repr(u16)]
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, TryFromInt)]
|
||||
pub enum ErrorsBehaviour {
|
||||
/// Continue execution
|
||||
Continue = 1,
|
||||
// Remount fs read-only
|
||||
RemountReadonly = 2,
|
||||
// Should panic
|
||||
Panic = 3,
|
||||
}
|
||||
|
||||
impl Default for ErrorsBehaviour {
|
||||
fn default() -> Self {
|
||||
Self::Continue
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(u32)]
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, TryFromInt)]
|
||||
pub enum OsId {
|
||||
Linux = 0,
|
||||
Hurd = 1,
|
||||
Masix = 2,
|
||||
FreeBSD = 3,
|
||||
Lites = 4,
|
||||
}
|
||||
|
||||
#[repr(u32)]
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, TryFromInt)]
|
||||
pub enum RevLevel {
|
||||
/// The good old (original) format.
|
||||
GoodOld = 0,
|
||||
/// V2 format with dynamic inode size.
|
||||
Dynamic = 1,
|
||||
}
|
||||
|
||||
const_assert!(core::mem::size_of::<RawSuperBlock>() == SUPER_BLOCK_SIZE);
|
||||
|
||||
/// The raw superblock, it must be exactly 1024 bytes in length.
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug, Pod, Default)]
|
||||
pub(super) struct RawSuperBlock {
|
||||
pub inodes_count: u32,
|
||||
pub blocks_count: u32,
|
||||
pub reserved_blocks_count: u32,
|
||||
pub free_blocks_count: u32,
|
||||
pub free_inodes_count: u32,
|
||||
pub first_data_block: u32,
|
||||
/// The number to left-shift 1024 to obtain the block size.
|
||||
pub log_block_size: u32,
|
||||
/// The number to left-shift 1024 to obtain the fragment size.
|
||||
pub log_frag_size: u32,
|
||||
pub blocks_per_group: u32,
|
||||
pub frags_per_group: u32,
|
||||
pub inodes_per_group: u32,
|
||||
/// Mount time.
|
||||
pub mtime: UnixTime,
|
||||
/// Write time.
|
||||
pub wtime: UnixTime,
|
||||
pub mnt_count: u16,
|
||||
pub max_mnt_count: u16,
|
||||
pub magic: u16,
|
||||
pub state: u16,
|
||||
pub errors: u16,
|
||||
pub min_rev_level: u16,
|
||||
/// Time of last check.
|
||||
pub last_check_time: UnixTime,
|
||||
pub check_interval: u32,
|
||||
pub creator_os: u32,
|
||||
pub rev_level: u32,
|
||||
pub def_resuid: u16,
|
||||
pub def_resgid: u16,
|
||||
pub first_ino: u32,
|
||||
pub inode_size: u16,
|
||||
pub block_group_idx: u16,
|
||||
pub feature_compat: u32,
|
||||
pub feature_incompat: u32,
|
||||
pub feature_ro_compat: u32,
|
||||
pub uuid: [u8; 16],
|
||||
pub volume_name: Str16,
|
||||
pub last_mounted_dir: Str64,
|
||||
pub algorithm_usage_bitmap: u32,
|
||||
pub prealloc_file_blocks: u8,
|
||||
pub prealloc_dir_blocks: u8,
|
||||
padding1: u16,
|
||||
///
|
||||
/// This fileds are for journaling support in Ext3.
|
||||
///
|
||||
/// Uuid of journal superblock.
|
||||
pub journal_uuid: [u8; 16],
|
||||
/// Inode number of journal file.
|
||||
pub journal_ino: u32,
|
||||
/// Device number of journal file.
|
||||
pub journal_dev: u32,
|
||||
/// Start of list of inodes to delete.
|
||||
pub last_orphan: u32,
|
||||
/// HTREE hash seed.
|
||||
pub hash_seed: [u32; 4],
|
||||
/// Default hash version to use
|
||||
pub def_hash_version: u8,
|
||||
reserved_char_pad: u8,
|
||||
reserved_word_pad: u16,
|
||||
/// Default mount options.
|
||||
pub default_mount_opts: u32,
|
||||
/// First metablock block group.
|
||||
pub first_meta_bg: u32,
|
||||
reserved: Reserved,
|
||||
}
|
||||
|
||||
impl From<&SuperBlock> for RawSuperBlock {
|
||||
fn from(sb: &SuperBlock) -> Self {
|
||||
Self {
|
||||
inodes_count: sb.inodes_count,
|
||||
blocks_count: sb.blocks_count,
|
||||
reserved_blocks_count: sb.reserved_blocks_count,
|
||||
free_blocks_count: sb.free_blocks_count,
|
||||
free_inodes_count: sb.free_inodes_count,
|
||||
first_data_block: sb.first_data_block.to_raw() as u32,
|
||||
log_block_size: (sb.block_size >> 11) as u32,
|
||||
log_frag_size: (sb.frag_size >> 11) as u32,
|
||||
blocks_per_group: sb.blocks_per_group,
|
||||
frags_per_group: sb.frags_per_group,
|
||||
inodes_per_group: sb.inodes_per_group,
|
||||
mtime: sb.mtime,
|
||||
wtime: sb.wtime,
|
||||
mnt_count: sb.mnt_count,
|
||||
max_mnt_count: sb.max_mnt_count,
|
||||
magic: sb.magic,
|
||||
state: sb.state as u16,
|
||||
errors: sb.errors_behaviour as u16,
|
||||
last_check_time: sb.last_check_time,
|
||||
check_interval: sb.check_interval.as_secs() as u32,
|
||||
creator_os: sb.creator_os as u32,
|
||||
rev_level: sb.rev_level as u32,
|
||||
def_resuid: sb.def_resuid as u16,
|
||||
def_resgid: sb.def_resgid as u16,
|
||||
first_ino: sb.first_ino,
|
||||
inode_size: sb.inode_size as u16,
|
||||
block_group_idx: sb.block_group_idx as u16,
|
||||
feature_compat: sb.feature_compat.bits(),
|
||||
feature_incompat: sb.feature_incompat.bits(),
|
||||
feature_ro_compat: sb.feature_ro_compat.bits(),
|
||||
uuid: sb.uuid,
|
||||
volume_name: sb.volume_name,
|
||||
last_mounted_dir: sb.last_mounted_dir,
|
||||
prealloc_file_blocks: sb.prealloc_file_blocks,
|
||||
prealloc_dir_blocks: sb.prealloc_dir_blocks,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug, Pod)]
|
||||
struct Reserved([u32; 190]);
|
||||
|
||||
impl Default for Reserved {
|
||||
fn default() -> Self {
|
||||
Self([0u32; 190])
|
||||
}
|
||||
}
|
93
services/libs/aster-std/src/fs/ext2/utils.rs
Normal file
93
services/libs/aster-std/src/fs/ext2/utils.rs
Normal file
@ -0,0 +1,93 @@
|
||||
use super::prelude::*;
|
||||
|
||||
use core::ops::MulAssign;
|
||||
|
||||
pub trait IsPowerOf: Copy + Sized + MulAssign + PartialOrd {
|
||||
/// Returns true if and only if `self == x^k` for some `k` where `k > 0`.
|
||||
///
|
||||
/// The `x` must be a positive value.
|
||||
fn is_power_of(&self, x: Self) -> bool {
|
||||
let mut power = x;
|
||||
while power < *self {
|
||||
power *= x;
|
||||
}
|
||||
|
||||
power == *self
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_ipo_for {
|
||||
($($ipo_ty:ty),*) => {
|
||||
$(impl IsPowerOf for $ipo_ty {})*
|
||||
};
|
||||
}
|
||||
|
||||
impl_ipo_for!(u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, isize, usize);
|
||||
|
||||
/// The `Dirty` wraps a value of type `T` with functions similar to that of a rw-lock,
|
||||
/// but simply sets a dirty flag on `write()`.
|
||||
pub struct Dirty<T: Debug> {
|
||||
value: T,
|
||||
dirty: bool,
|
||||
}
|
||||
|
||||
impl<T: Debug> Dirty<T> {
|
||||
/// Creates a new Dirty without setting the dirty flag.
|
||||
pub fn new(val: T) -> Dirty<T> {
|
||||
Dirty {
|
||||
value: val,
|
||||
dirty: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new Dirty with setting the dirty flag.
|
||||
pub fn new_dirty(val: T) -> Dirty<T> {
|
||||
Dirty {
|
||||
value: val,
|
||||
dirty: true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if dirty, false otherwise.
|
||||
pub fn is_dirty(&self) -> bool {
|
||||
self.dirty
|
||||
}
|
||||
|
||||
/// Clears the dirty flag.
|
||||
pub fn clear_dirty(&mut self) {
|
||||
self.dirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Debug> Deref for Dirty<T> {
|
||||
type Target = T;
|
||||
|
||||
/// Returns the imutable value.
|
||||
fn deref(&self) -> &T {
|
||||
&self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Debug> DerefMut for Dirty<T> {
|
||||
/// Returns the mutable value, sets the dirty flag.
|
||||
fn deref_mut(&mut self) -> &mut T {
|
||||
self.dirty = true;
|
||||
&mut self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Debug> Drop for Dirty<T> {
|
||||
/// Guards if it is dirty when dropping.
|
||||
fn drop(&mut self) {
|
||||
if self.is_dirty() {
|
||||
warn!("[{:?}] is dirty then dropping", self.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Debug> Debug for Dirty<T> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||
let tag = if self.dirty { "Dirty" } else { "Clean" };
|
||||
write!(f, "[{}] {:?}", tag, self.value)
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
pub mod device;
|
||||
pub mod devpts;
|
||||
pub mod epoll;
|
||||
pub mod ext2;
|
||||
pub mod file_handle;
|
||||
pub mod file_table;
|
||||
pub mod fs_resolver;
|
||||
@ -10,3 +11,28 @@ pub mod procfs;
|
||||
pub mod ramfs;
|
||||
pub mod rootfs;
|
||||
pub mod utils;
|
||||
|
||||
use crate::fs::{ext2::Ext2, fs_resolver::FsPath};
|
||||
use crate::prelude::*;
|
||||
use crate::thread::kernel_thread::KernelThreadExt;
|
||||
use aster_virtio::device::block::device::BlockDevice as VirtIoBlockDevice;
|
||||
use aster_virtio::device::block::DEVICE_NAME as VIRTIO_BLOCK_NAME;
|
||||
|
||||
pub fn lazy_init() {
|
||||
let block_device = aster_block::get_device(VIRTIO_BLOCK_NAME).unwrap();
|
||||
let cloned_block_device = block_device.clone();
|
||||
|
||||
let task_fn = move || {
|
||||
info!("spawn the virt-io-block thread");
|
||||
let virtio_block_device = block_device.downcast_ref::<VirtIoBlockDevice>().unwrap();
|
||||
loop {
|
||||
virtio_block_device.handle_requests();
|
||||
}
|
||||
};
|
||||
crate::Thread::spawn_kernel_thread(crate::ThreadOptions::new(task_fn));
|
||||
|
||||
let ext2_fs = Ext2::open(cloned_block_device).unwrap();
|
||||
let target_path = FsPath::try_from("/ext2").unwrap();
|
||||
println!("[kernel] Mount Ext2 fs at {:?} ", target_path);
|
||||
self::rootfs::mount_fs_at(ext2_fs, &target_path).unwrap();
|
||||
}
|
||||
|
@ -58,12 +58,18 @@ impl<D: DirOps + 'static> Inode for ProcDir<D> {
|
||||
self.info.size()
|
||||
}
|
||||
|
||||
fn resize(&self, _new_size: usize) {}
|
||||
fn resize(&self, _new_size: usize) -> Result<()> {
|
||||
Err(Error::new(Errno::EISDIR))
|
||||
}
|
||||
|
||||
fn metadata(&self) -> Metadata {
|
||||
self.info.metadata()
|
||||
}
|
||||
|
||||
fn ino(&self) -> u64 {
|
||||
self.info.ino()
|
||||
}
|
||||
|
||||
fn type_(&self) -> InodeType {
|
||||
InodeType::Dir
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
use aster_frame::vm::VmFrame;
|
||||
use core::time::Duration;
|
||||
|
||||
use crate::fs::utils::{FileSystem, Inode, InodeMode, InodeType, IoctlCmd, Metadata};
|
||||
@ -31,12 +30,18 @@ impl<F: FileOps + 'static> Inode for ProcFile<F> {
|
||||
self.info.size()
|
||||
}
|
||||
|
||||
fn resize(&self, _new_size: usize) {}
|
||||
fn resize(&self, _new_size: usize) -> Result<()> {
|
||||
Err(Error::new(Errno::EPERM))
|
||||
}
|
||||
|
||||
fn metadata(&self) -> Metadata {
|
||||
self.info.metadata()
|
||||
}
|
||||
|
||||
fn ino(&self) -> u64 {
|
||||
self.info.ino()
|
||||
}
|
||||
|
||||
fn type_(&self) -> InodeType {
|
||||
InodeType::File
|
||||
}
|
||||
@ -65,14 +70,6 @@ impl<F: FileOps + 'static> Inode for ProcFile<F> {
|
||||
self.info.set_mtime(time)
|
||||
}
|
||||
|
||||
fn read_page(&self, _idx: usize, _frame: &VmFrame) -> Result<()> {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
fn write_page(&self, _idx: usize, _frame: &VmFrame) -> Result<()> {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
fn read_at(&self, offset: usize, buf: &mut [u8]) -> Result<usize> {
|
||||
let data = self.inner.data()?;
|
||||
let start = data.len().min(offset);
|
||||
|
@ -38,6 +38,10 @@ impl ProcInodeInfo {
|
||||
self.metadata.read().clone()
|
||||
}
|
||||
|
||||
pub fn ino(&self) -> u64 {
|
||||
self.metadata.read().ino as _
|
||||
}
|
||||
|
||||
pub fn size(&self) -> usize {
|
||||
self.metadata.read().size
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
use aster_frame::vm::VmFrame;
|
||||
use core::time::Duration;
|
||||
|
||||
use crate::fs::utils::{FileSystem, Inode, InodeMode, InodeType, IoctlCmd, Metadata};
|
||||
@ -31,12 +30,18 @@ impl<S: SymOps + 'static> Inode for ProcSym<S> {
|
||||
self.info.size()
|
||||
}
|
||||
|
||||
fn resize(&self, _new_size: usize) {}
|
||||
fn resize(&self, _new_size: usize) -> Result<()> {
|
||||
Err(Error::new(Errno::EPERM))
|
||||
}
|
||||
|
||||
fn metadata(&self) -> Metadata {
|
||||
self.info.metadata()
|
||||
}
|
||||
|
||||
fn ino(&self) -> u64 {
|
||||
self.info.ino()
|
||||
}
|
||||
|
||||
fn type_(&self) -> InodeType {
|
||||
InodeType::SymLink
|
||||
}
|
||||
@ -65,14 +70,6 @@ impl<S: SymOps + 'static> Inode for ProcSym<S> {
|
||||
self.info.set_mtime(time)
|
||||
}
|
||||
|
||||
fn read_page(&self, _idx: usize, _frame: &VmFrame) -> Result<()> {
|
||||
Err(Error::new(Errno::EPERM))
|
||||
}
|
||||
|
||||
fn write_page(&self, _idx: usize, _frame: &VmFrame) -> Result<()> {
|
||||
Err(Error::new(Errno::EPERM))
|
||||
}
|
||||
|
||||
fn read_at(&self, _offset: usize, _buf: &mut [u8]) -> Result<usize> {
|
||||
Err(Error::new(Errno::EPERM))
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
use alloc::str;
|
||||
use aster_frame::sync::{RwLock, RwLockWriteGuard};
|
||||
use aster_frame::vm::{VmFrame, VmIo};
|
||||
use aster_frame::sync::RwLockWriteGuard;
|
||||
use aster_frame::vm::VmFrame;
|
||||
use aster_frame::vm::VmIo;
|
||||
use aster_rights::Full;
|
||||
use aster_util::slot_vec::SlotVec;
|
||||
use core::sync::atomic::{AtomicUsize, Ordering};
|
||||
@ -10,8 +10,8 @@ use super::*;
|
||||
use crate::events::IoEvents;
|
||||
use crate::fs::device::Device;
|
||||
use crate::fs::utils::{
|
||||
DirentVisitor, FileSystem, FsFlags, Inode, InodeMode, InodeType, IoctlCmd, Metadata, PageCache,
|
||||
SuperBlock,
|
||||
CStr256, DirentVisitor, FileSystem, FsFlags, Inode, InodeMode, InodeType, IoctlCmd, Metadata,
|
||||
PageCache, PageCacheBackend, SuperBlock,
|
||||
};
|
||||
use crate::prelude::*;
|
||||
use crate::process::signal::Poller;
|
||||
@ -219,7 +219,7 @@ impl Inner {
|
||||
}
|
||||
|
||||
struct DirEntry {
|
||||
children: SlotVec<(Str256, Arc<RamInode>)>,
|
||||
children: SlotVec<(CStr256, Arc<RamInode>)>,
|
||||
this: Weak<RamInode>,
|
||||
parent: Weak<RamInode>,
|
||||
}
|
||||
@ -248,7 +248,7 @@ impl DirEntry {
|
||||
} else {
|
||||
self.children
|
||||
.iter()
|
||||
.any(|(child, _)| child.as_ref() == name)
|
||||
.any(|(child, _)| child.as_str().unwrap() == name)
|
||||
}
|
||||
}
|
||||
|
||||
@ -260,16 +260,16 @@ impl DirEntry {
|
||||
} else {
|
||||
self.children
|
||||
.idxes_and_items()
|
||||
.find(|(_, (child, _))| child.as_ref() == name)
|
||||
.find(|(_, (child, _))| child.as_str().unwrap() == name)
|
||||
.map(|(idx, (_, inode))| (idx + 2, inode.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
fn append_entry(&mut self, name: &str, inode: Arc<RamInode>) -> usize {
|
||||
self.children.put((Str256::from(name), inode))
|
||||
self.children.put((CStr256::from(name), inode))
|
||||
}
|
||||
|
||||
fn remove_entry(&mut self, idx: usize) -> Option<(Str256, Arc<RamInode>)> {
|
||||
fn remove_entry(&mut self, idx: usize) -> Option<(CStr256, Arc<RamInode>)> {
|
||||
assert!(idx >= 2);
|
||||
self.children.remove(idx - 2)
|
||||
}
|
||||
@ -277,8 +277,8 @@ impl DirEntry {
|
||||
fn substitute_entry(
|
||||
&mut self,
|
||||
idx: usize,
|
||||
new_entry: (Str256, Arc<RamInode>),
|
||||
) -> Option<(Str256, Arc<RamInode>)> {
|
||||
new_entry: (CStr256, Arc<RamInode>),
|
||||
) -> Option<(CStr256, Arc<RamInode>)> {
|
||||
assert!(idx >= 2);
|
||||
self.children.put_at(idx - 2, new_entry)
|
||||
}
|
||||
@ -315,7 +315,7 @@ impl DirEntry {
|
||||
.skip_while(|(offset, _)| offset < &start_idx)
|
||||
{
|
||||
visitor.visit(
|
||||
name.as_ref(),
|
||||
name.as_str().unwrap(),
|
||||
child.metadata().ino as u64,
|
||||
child.metadata().type_,
|
||||
offset,
|
||||
@ -337,36 +337,6 @@ impl DirEntry {
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, PartialEq, PartialOrd, Eq, Ord)]
|
||||
pub struct Str256([u8; 256]);
|
||||
|
||||
impl AsRef<str> for Str256 {
|
||||
fn as_ref(&self) -> &str {
|
||||
let len = self.0.iter().enumerate().find(|(_, &b)| b == 0).unwrap().0;
|
||||
str::from_utf8(&self.0[0..len]).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a str> for Str256 {
|
||||
fn from(s: &'a str) -> Self {
|
||||
let mut inner = [0u8; 256];
|
||||
let len = if s.len() > NAME_MAX {
|
||||
NAME_MAX
|
||||
} else {
|
||||
s.len()
|
||||
};
|
||||
inner[0..len].copy_from_slice(&s.as_bytes()[0..len]);
|
||||
Self(inner)
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Debug for Str256 {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||
write!(f, "{}", self.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
impl RamInode {
|
||||
fn new_dir(fs: &Arc<RamFS>, mode: InodeMode, parent: &Weak<Self>) -> Arc<Self> {
|
||||
Arc::new_cyclic(|weak_self| {
|
||||
@ -439,7 +409,7 @@ impl RamInode {
|
||||
}
|
||||
}
|
||||
|
||||
impl Inode for RamInode {
|
||||
impl PageCacheBackend for RamInode {
|
||||
fn read_page(&self, _idx: usize, _frame: &VmFrame) -> Result<()> {
|
||||
// do nothing
|
||||
Ok(())
|
||||
@ -450,6 +420,12 @@ impl Inode for RamInode {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn npages(&self) -> usize {
|
||||
self.0.read().metadata.blocks
|
||||
}
|
||||
}
|
||||
|
||||
impl Inode for RamInode {
|
||||
fn page_cache(&self) -> Option<Vmo<Full>> {
|
||||
self.0
|
||||
.read()
|
||||
@ -515,8 +491,9 @@ impl Inode for RamInode {
|
||||
self.0.read().metadata.size
|
||||
}
|
||||
|
||||
fn resize(&self, new_size: usize) {
|
||||
self.0.write().resize(new_size)
|
||||
fn resize(&self, new_size: usize) -> Result<()> {
|
||||
self.0.write().resize(new_size);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn atime(&self) -> Duration {
|
||||
@ -535,6 +512,10 @@ impl Inode for RamInode {
|
||||
self.0.write().metadata.mtime = time;
|
||||
}
|
||||
|
||||
fn ino(&self) -> u64 {
|
||||
self.0.read().metadata.ino as _
|
||||
}
|
||||
|
||||
fn type_(&self) -> InodeType {
|
||||
self.0.read().metadata.type_
|
||||
}
|
||||
@ -780,7 +761,7 @@ impl Inode for RamInode {
|
||||
let (idx, inode) = self_dir
|
||||
.get_entry(old_name)
|
||||
.ok_or(Error::new(Errno::ENOENT))?;
|
||||
self_dir.substitute_entry(idx, (Str256::from(new_name), inode));
|
||||
self_dir.substitute_entry(idx, (CStr256::from(new_name), inode));
|
||||
} else {
|
||||
let (mut self_inode, mut target_inode) = write_lock_two_inodes(self, target);
|
||||
let self_dir = self_inode.inner.as_direntry_mut().unwrap();
|
||||
|
@ -3,7 +3,7 @@ use crate::prelude::*;
|
||||
use super::fs_resolver::{FsPath, FsResolver};
|
||||
use super::procfs::ProcFS;
|
||||
use super::ramfs::RamFS;
|
||||
use super::utils::{InodeMode, InodeType, MountNode};
|
||||
use super::utils::{FileSystem, InodeMode, InodeType, MountNode};
|
||||
|
||||
use cpio_decoder::{CpioDecoder, FileType};
|
||||
use lending_iterator::LendingIterator;
|
||||
@ -77,11 +77,18 @@ pub fn init(initramfs_buf: &[u8]) -> Result<()> {
|
||||
// Mount DevFS
|
||||
let dev_dentry = fs.lookup(&FsPath::try_from("/dev")?)?;
|
||||
dev_dentry.mount(RamFS::new())?;
|
||||
|
||||
println!("[kernel] rootfs is ready");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn mount_fs_at(fs: Arc<dyn FileSystem>, fs_path: &FsPath) -> Result<()> {
|
||||
let target_dentry = FsResolver::new().lookup(fs_path)?;
|
||||
target_dentry.mount(fs)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
static ROOT_MOUNT: Once<Arc<MountNode>> = Once::new();
|
||||
|
||||
pub fn init_root_mount() {
|
||||
|
@ -1,4 +1,3 @@
|
||||
use aster_frame::vm::VmFrame;
|
||||
use aster_rights::Full;
|
||||
use core::time::Duration;
|
||||
use core2::io::{Error as IoError, ErrorKind as IoErrorKind, Result as IoResult, Write};
|
||||
@ -232,10 +231,12 @@ pub trait Inode: Any + Sync + Send {
|
||||
self.len() == 0
|
||||
}
|
||||
|
||||
fn resize(&self, new_size: usize);
|
||||
fn resize(&self, new_size: usize) -> Result<()>;
|
||||
|
||||
fn metadata(&self) -> Metadata;
|
||||
|
||||
fn ino(&self) -> u64;
|
||||
|
||||
fn type_(&self) -> InodeType;
|
||||
|
||||
fn mode(&self) -> InodeMode;
|
||||
@ -250,14 +251,6 @@ pub trait Inode: Any + Sync + Send {
|
||||
|
||||
fn set_mtime(&self, time: Duration);
|
||||
|
||||
fn read_page(&self, idx: usize, frame: &VmFrame) -> Result<()> {
|
||||
Err(Error::new(Errno::EISDIR))
|
||||
}
|
||||
|
||||
fn write_page(&self, idx: usize, frame: &VmFrame) -> Result<()> {
|
||||
Err(Error::new(Errno::EISDIR))
|
||||
}
|
||||
|
||||
fn page_cache(&self) -> Option<Vmo<Full>> {
|
||||
None
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ pub use fs::{FileSystem, FsFlags, SuperBlock};
|
||||
pub use inode::{Inode, InodeMode, InodeType, Metadata};
|
||||
pub use ioctl::IoctlCmd;
|
||||
pub use mount::MountNode;
|
||||
pub use page_cache::PageCache;
|
||||
pub use page_cache::{PageCache, PageCacheBackend};
|
||||
pub use status_flags::StatusFlags;
|
||||
|
||||
mod access_mode;
|
||||
@ -28,6 +28,8 @@ mod mount;
|
||||
mod page_cache;
|
||||
mod status_flags;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
#[derive(Copy, PartialEq, Eq, Clone, Debug)]
|
||||
pub enum SeekFrom {
|
||||
Start(usize),
|
||||
@ -43,3 +45,152 @@ pub const NAME_MAX: usize = 255;
|
||||
|
||||
/// The upper limit for resolving symbolic links
|
||||
pub const SYMLINKS_MAX: usize = 40;
|
||||
|
||||
pub type CStr256 = FixedCStr<256>;
|
||||
pub type Str16 = FixedStr<16>;
|
||||
pub type Str64 = FixedStr<64>;
|
||||
|
||||
/// An owned C-compatible string with a fixed capacity of `N`.
|
||||
///
|
||||
/// The string is terminated with a null byte.
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Pod)]
|
||||
pub struct FixedCStr<const N: usize>([u8; N]);
|
||||
|
||||
impl<const N: usize> FixedCStr<N> {
|
||||
pub fn len(&self) -> usize {
|
||||
self.0.iter().position(|&b| b == 0).unwrap()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> Result<&str> {
|
||||
Ok(alloc::str::from_utf8(self.as_bytes())?)
|
||||
}
|
||||
|
||||
pub fn as_cstr(&self) -> Result<&CStr> {
|
||||
Ok(CStr::from_bytes_with_nul(self.as_bytes_with_nul())?)
|
||||
}
|
||||
|
||||
pub fn as_bytes(&self) -> &[u8] {
|
||||
&self.0[0..self.len()]
|
||||
}
|
||||
|
||||
pub fn as_bytes_with_nul(&self) -> &[u8] {
|
||||
&self.0[0..=self.len()]
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, const N: usize> From<&'a [u8]> for FixedCStr<N> {
|
||||
fn from(bytes: &'a [u8]) -> Self {
|
||||
assert!(N > 0);
|
||||
|
||||
let mut inner = [0u8; N];
|
||||
let len = {
|
||||
let mut nul_byte_idx = match bytes.iter().position(|&b| b == 0) {
|
||||
Some(idx) => idx,
|
||||
None => bytes.len(),
|
||||
};
|
||||
if nul_byte_idx >= N {
|
||||
nul_byte_idx = N - 1;
|
||||
}
|
||||
nul_byte_idx
|
||||
};
|
||||
inner[0..len].copy_from_slice(&bytes[0..len]);
|
||||
Self(inner)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, const N: usize> From<&'a str> for FixedCStr<N> {
|
||||
fn from(string: &'a str) -> Self {
|
||||
let bytes = string.as_bytes();
|
||||
Self::from(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, const N: usize> From<&'a CStr> for FixedCStr<N> {
|
||||
fn from(cstr: &'a CStr) -> Self {
|
||||
let bytes = cstr.to_bytes_with_nul();
|
||||
Self::from(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Default for FixedCStr<N> {
|
||||
fn default() -> Self {
|
||||
Self([0u8; N])
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Debug for FixedCStr<N> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||
match self.as_cstr() {
|
||||
Ok(cstr) => write!(f, "{:?}", cstr),
|
||||
Err(_) => write!(f, "{:?}", self.as_bytes()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An owned string with a fixed capacity of `N`.
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Pod)]
|
||||
pub struct FixedStr<const N: usize>([u8; N]);
|
||||
|
||||
impl<const N: usize> FixedStr<N> {
|
||||
pub fn len(&self) -> usize {
|
||||
self.0.iter().position(|&b| b == 0).unwrap_or(N)
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> Result<&str> {
|
||||
Ok(alloc::str::from_utf8(self.as_bytes())?)
|
||||
}
|
||||
|
||||
pub fn as_bytes(&self) -> &[u8] {
|
||||
&self.0[0..self.len()]
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, const N: usize> From<&'a [u8]> for FixedStr<N> {
|
||||
fn from(bytes: &'a [u8]) -> Self {
|
||||
let mut inner = [0u8; N];
|
||||
let len = {
|
||||
let mut nul_byte_idx = match bytes.iter().position(|&b| b == 0) {
|
||||
Some(idx) => idx,
|
||||
None => bytes.len(),
|
||||
};
|
||||
if nul_byte_idx > N {
|
||||
nul_byte_idx = N;
|
||||
}
|
||||
nul_byte_idx
|
||||
};
|
||||
inner[0..len].copy_from_slice(&bytes[0..len]);
|
||||
Self(inner)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, const N: usize> From<&'a str> for FixedStr<N> {
|
||||
fn from(string: &'a str) -> Self {
|
||||
let bytes = string.as_bytes();
|
||||
Self::from(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Default for FixedStr<N> {
|
||||
fn default() -> Self {
|
||||
Self([0u8; N])
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Debug for FixedStr<N> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||
match self.as_str() {
|
||||
Ok(string) => write!(f, "{}", string),
|
||||
Err(_) => write!(f, "{:?}", self.as_bytes()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
use super::Inode;
|
||||
use crate::prelude::*;
|
||||
use crate::vm::vmo::{get_page_idx_range, Pager, Vmo, VmoFlags, VmoOptions};
|
||||
use aster_rights::Full;
|
||||
@ -13,9 +12,9 @@ pub struct PageCache {
|
||||
}
|
||||
|
||||
impl PageCache {
|
||||
/// Creates an empty size page cache associated with a new inode.
|
||||
pub fn new(backed_inode: Weak<dyn Inode>) -> Result<Self> {
|
||||
let manager = Arc::new(PageCacheManager::new(backed_inode));
|
||||
/// Creates an empty size page cache associated with a new backend.
|
||||
pub fn new(backend: Weak<dyn PageCacheBackend>) -> Result<Self> {
|
||||
let manager = Arc::new(PageCacheManager::new(backend));
|
||||
let pages = VmoOptions::<Full>::new(0)
|
||||
.flags(VmoFlags::RESIZABLE)
|
||||
.pager(manager.clone())
|
||||
@ -23,12 +22,12 @@ impl PageCache {
|
||||
Ok(Self { pages, manager })
|
||||
}
|
||||
|
||||
/// Creates a page cache associated with an existing inode.
|
||||
/// Creates a page cache associated with an existing backend.
|
||||
///
|
||||
/// The `capacity` is the initial cache size required by the inode.
|
||||
/// It is usually used the same size as the inode.
|
||||
pub fn with_capacity(capacity: usize, backed_inode: Weak<dyn Inode>) -> Result<Self> {
|
||||
let manager = Arc::new(PageCacheManager::new(backed_inode));
|
||||
/// The `capacity` is the initial cache size required by the backend.
|
||||
/// This size usually corresponds to the size of the backend.
|
||||
pub fn with_capacity(capacity: usize, backend: Weak<dyn PageCacheBackend>) -> Result<Self> {
|
||||
let manager = Arc::new(PageCacheManager::new(backend));
|
||||
let pages = VmoOptions::<Full>::new(capacity)
|
||||
.flags(VmoFlags::RESIZABLE)
|
||||
.pager(manager.clone())
|
||||
@ -36,7 +35,7 @@ impl PageCache {
|
||||
Ok(Self { pages, manager })
|
||||
}
|
||||
|
||||
/// Returns the Vmo object backed by inode.
|
||||
/// Returns the Vmo object.
|
||||
// TODO: The capability is too high,restrict it to eliminate the possibility of misuse.
|
||||
// For example, the `resize` api should be forbidded.
|
||||
pub fn pages(&self) -> Vmo<Full> {
|
||||
@ -44,10 +43,15 @@ impl PageCache {
|
||||
}
|
||||
|
||||
/// Evict the data within a specified range from the page cache and persist
|
||||
/// them to the disk.
|
||||
/// them to the backend.
|
||||
pub fn evict_range(&self, range: Range<usize>) -> Result<()> {
|
||||
self.manager.evict_range(range)
|
||||
}
|
||||
|
||||
/// Returns the backend.
|
||||
pub fn backend(&self) -> Arc<dyn PageCacheBackend> {
|
||||
self.manager.backend()
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for PageCache {
|
||||
@ -61,33 +65,36 @@ impl Debug for PageCache {
|
||||
|
||||
struct PageCacheManager {
|
||||
pages: Mutex<LruCache<usize, Page>>,
|
||||
backed_inode: Weak<dyn Inode>,
|
||||
backend: Weak<dyn PageCacheBackend>,
|
||||
}
|
||||
|
||||
impl PageCacheManager {
|
||||
pub fn new(backed_inode: Weak<dyn Inode>) -> Self {
|
||||
pub fn new(backend: Weak<dyn PageCacheBackend>) -> Self {
|
||||
Self {
|
||||
pages: Mutex::new(LruCache::unbounded()),
|
||||
backed_inode,
|
||||
backend,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn backend(&self) -> Arc<dyn PageCacheBackend> {
|
||||
self.backend.upgrade().unwrap()
|
||||
}
|
||||
|
||||
pub fn evict_range(&self, range: Range<usize>) -> Result<()> {
|
||||
let page_idx_range = get_page_idx_range(&range);
|
||||
let mut pages = self.pages.lock();
|
||||
for page_idx in page_idx_range {
|
||||
if let Some(page) = pages.get_mut(&page_idx) {
|
||||
for idx in page_idx_range {
|
||||
if let Some(page) = pages.get_mut(&idx) {
|
||||
if let PageState::Dirty = page.state() {
|
||||
self.backed_inode
|
||||
.upgrade()
|
||||
.unwrap()
|
||||
.write_page(page_idx, page.frame())?;
|
||||
let backend = self.backend();
|
||||
if idx < backend.npages() {
|
||||
backend.write_page(idx, page.frame())?;
|
||||
page.set_state(PageState::UpToDate);
|
||||
}
|
||||
} else {
|
||||
warn!("page {} is not in page cache, do nothing", page_idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@ -101,53 +108,50 @@ impl Debug for PageCacheManager {
|
||||
}
|
||||
|
||||
impl Pager for PageCacheManager {
|
||||
fn commit_page(&self, offset: usize) -> Result<VmFrame> {
|
||||
let page_idx = offset / PAGE_SIZE;
|
||||
fn commit_page(&self, idx: usize) -> Result<VmFrame> {
|
||||
let mut pages = self.pages.lock();
|
||||
let frame = if let Some(page) = pages.get(&page_idx) {
|
||||
let frame = if let Some(page) = pages.get(&idx) {
|
||||
page.frame().clone()
|
||||
} else {
|
||||
let backed_inode = self.backed_inode.upgrade().unwrap();
|
||||
let page = if offset < backed_inode.len() {
|
||||
let backend = self.backend();
|
||||
let page = if idx < backend.npages() {
|
||||
let mut page = Page::alloc()?;
|
||||
backed_inode.read_page(page_idx, page.frame())?;
|
||||
backend.read_page(idx, page.frame())?;
|
||||
page.set_state(PageState::UpToDate);
|
||||
page
|
||||
} else {
|
||||
Page::alloc_zero()?
|
||||
};
|
||||
let frame = page.frame().clone();
|
||||
pages.put(page_idx, page);
|
||||
pages.put(idx, page);
|
||||
frame
|
||||
};
|
||||
|
||||
Ok(frame)
|
||||
}
|
||||
|
||||
fn update_page(&self, offset: usize) -> Result<()> {
|
||||
let page_idx = offset / PAGE_SIZE;
|
||||
fn update_page(&self, idx: usize) -> Result<()> {
|
||||
let mut pages = self.pages.lock();
|
||||
if let Some(page) = pages.get_mut(&page_idx) {
|
||||
if let Some(page) = pages.get_mut(&idx) {
|
||||
page.set_state(PageState::Dirty);
|
||||
} else {
|
||||
error!("page {} is not in page cache", page_idx);
|
||||
panic!();
|
||||
warn!("The page {} is not in page cache", idx);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn decommit_page(&self, offset: usize) -> Result<()> {
|
||||
let page_idx = offset / PAGE_SIZE;
|
||||
fn decommit_page(&self, idx: usize) -> Result<()> {
|
||||
let mut pages = self.pages.lock();
|
||||
if let Some(page) = pages.pop(&page_idx) {
|
||||
if let Some(page) = pages.pop(&idx) {
|
||||
if let PageState::Dirty = page.state() {
|
||||
self.backed_inode
|
||||
.upgrade()
|
||||
.unwrap()
|
||||
.write_page(page_idx, page.frame())?
|
||||
let backend = self.backend();
|
||||
if idx < backend.npages() {
|
||||
backend.write_page(idx, page.frame())?;
|
||||
}
|
||||
} else {
|
||||
warn!("page {} is not in page cache, do nothing", page_idx);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@ -200,3 +204,13 @@ enum PageState {
|
||||
/// The page is available to read and write.
|
||||
Dirty,
|
||||
}
|
||||
|
||||
/// This trait represents the backend for the page cache.
|
||||
pub trait PageCacheBackend: Sync + Send {
|
||||
/// Reads a page from the backend.
|
||||
fn read_page(&self, idx: usize, frame: &VmFrame) -> Result<()>;
|
||||
/// Writes a page to the backend.
|
||||
fn write_page(&self, idx: usize, frame: &VmFrame) -> Result<()>;
|
||||
/// Returns the number of pages in the backend.
|
||||
fn npages(&self) -> usize;
|
||||
}
|
||||
|
@ -19,6 +19,8 @@
|
||||
#![feature(register_tool)]
|
||||
#![feature(trait_upcasting)]
|
||||
#![feature(format_args_nl)]
|
||||
#![feature(int_roundings)]
|
||||
#![feature(step_trait)]
|
||||
#![register_tool(component_access_control)]
|
||||
|
||||
use crate::{
|
||||
@ -73,6 +75,7 @@ fn init_thread() {
|
||||
current_thread!().tid()
|
||||
);
|
||||
net::lazy_init();
|
||||
fs::lazy_init();
|
||||
// driver::pci::virtio::block::block_device_test();
|
||||
let thread = Thread::spawn_kernel_thread(ThreadOptions::new(|| {
|
||||
println!("[kernel] Hello world from kernel!");
|
||||
|
@ -13,7 +13,7 @@ pub(crate) use alloc::sync::Weak;
|
||||
pub(crate) use alloc::vec;
|
||||
pub(crate) use alloc::vec::Vec;
|
||||
pub(crate) use aster_frame::config::PAGE_SIZE;
|
||||
pub(crate) use aster_frame::sync::{Mutex, MutexGuard, RwLock, SpinLock, SpinLockGuard};
|
||||
pub(crate) use aster_frame::sync::{Mutex, MutexGuard, RwLock, RwMutex, SpinLock, SpinLockGuard};
|
||||
pub(crate) use aster_frame::vm::Vaddr;
|
||||
pub(crate) use bitflags::bitflags;
|
||||
pub(crate) use core::any::Any;
|
||||
|
@ -379,13 +379,13 @@ fn clone_cpu_context(
|
||||
}
|
||||
|
||||
fn clone_fs(
|
||||
parent_fs: &Arc<RwLock<FsResolver>>,
|
||||
parent_fs: &Arc<RwMutex<FsResolver>>,
|
||||
clone_flags: CloneFlags,
|
||||
) -> Arc<RwLock<FsResolver>> {
|
||||
) -> Arc<RwMutex<FsResolver>> {
|
||||
if clone_flags.contains(CloneFlags::CLONE_FS) {
|
||||
parent_fs.clone()
|
||||
} else {
|
||||
Arc::new(RwLock::new(parent_fs.read().clone()))
|
||||
Arc::new(RwMutex::new(parent_fs.read().clone()))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,7 @@ pub struct ProcessBuilder<'a> {
|
||||
envp: Option<Vec<CString>>,
|
||||
process_vm: Option<ProcessVm>,
|
||||
file_table: Option<Arc<Mutex<FileTable>>>,
|
||||
fs: Option<Arc<RwLock<FsResolver>>>,
|
||||
fs: Option<Arc<RwMutex<FsResolver>>>,
|
||||
umask: Option<Arc<RwLock<FileCreationMask>>>,
|
||||
resource_limits: Option<ResourceLimits>,
|
||||
sig_dispositions: Option<Arc<Mutex<SigDispositions>>>,
|
||||
@ -64,7 +64,7 @@ impl<'a> ProcessBuilder<'a> {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn fs(&mut self, fs: Arc<RwLock<FsResolver>>) -> &mut Self {
|
||||
pub fn fs(&mut self, fs: Arc<RwMutex<FsResolver>>) -> &mut Self {
|
||||
self.fs = Some(fs);
|
||||
self
|
||||
}
|
||||
@ -142,7 +142,7 @@ impl<'a> ProcessBuilder<'a> {
|
||||
.unwrap();
|
||||
|
||||
let fs = fs
|
||||
.or_else(|| Some(Arc::new(RwLock::new(FsResolver::new()))))
|
||||
.or_else(|| Some(Arc::new(RwMutex::new(FsResolver::new()))))
|
||||
.unwrap();
|
||||
|
||||
let umask = umask
|
||||
|
@ -64,7 +64,7 @@ pub struct Process {
|
||||
/// File table
|
||||
file_table: Arc<Mutex<FileTable>>,
|
||||
/// FsResolver
|
||||
fs: Arc<RwLock<FsResolver>>,
|
||||
fs: Arc<RwMutex<FsResolver>>,
|
||||
/// umask
|
||||
umask: Arc<RwLock<FileCreationMask>>,
|
||||
/// resource limits
|
||||
@ -84,7 +84,7 @@ impl Process {
|
||||
executable_path: String,
|
||||
process_vm: ProcessVm,
|
||||
file_table: Arc<Mutex<FileTable>>,
|
||||
fs: Arc<RwLock<FsResolver>>,
|
||||
fs: Arc<RwMutex<FsResolver>>,
|
||||
umask: Arc<RwLock<FileCreationMask>>,
|
||||
sig_dispositions: Arc<Mutex<SigDispositions>>,
|
||||
resource_limits: ResourceLimits,
|
||||
@ -496,7 +496,7 @@ impl Process {
|
||||
&self.file_table
|
||||
}
|
||||
|
||||
pub fn fs(&self) -> &Arc<RwLock<FsResolver>> {
|
||||
pub fn fs(&self) -> &Arc<RwMutex<FsResolver>> {
|
||||
&self.fs
|
||||
}
|
||||
|
||||
@ -595,7 +595,7 @@ mod test {
|
||||
String::new(),
|
||||
ProcessVm::alloc(),
|
||||
Arc::new(Mutex::new(FileTable::new())),
|
||||
Arc::new(RwLock::new(FsResolver::new())),
|
||||
Arc::new(RwMutex::new(FsResolver::new())),
|
||||
Arc::new(RwLock::new(FileCreationMask::default())),
|
||||
Arc::new(Mutex::new(SigDispositions::default())),
|
||||
ResourceLimits::default(),
|
||||
|
@ -104,3 +104,25 @@ pub fn now_as_duration(clock_id: &ClockID) -> Result<Duration> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Unix time measures time by the number of seconds that have elapsed since
|
||||
/// the Unix epoch, without adjustments made due to leap seconds.
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Copy, Clone, Pod)]
|
||||
pub struct UnixTime {
|
||||
sec: u32,
|
||||
}
|
||||
|
||||
impl From<Duration> for UnixTime {
|
||||
fn from(duration: Duration) -> Self {
|
||||
Self {
|
||||
sec: duration.as_secs() as u32,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<UnixTime> for Duration {
|
||||
fn from(time: UnixTime) -> Self {
|
||||
Duration::from_secs(time.sec as _)
|
||||
}
|
||||
}
|
||||
|
@ -154,7 +154,7 @@ impl VmoInner {
|
||||
}
|
||||
let frame = match &self.pager {
|
||||
None => VmAllocOptions::new(1).alloc_single()?,
|
||||
Some(pager) => pager.commit_page(offset)?,
|
||||
Some(pager) => pager.commit_page(page_idx)?,
|
||||
};
|
||||
self.insert_frame(page_idx, frame);
|
||||
Ok(())
|
||||
@ -164,7 +164,7 @@ impl VmoInner {
|
||||
let page_idx = offset / PAGE_SIZE;
|
||||
if self.committed_pages.remove(&page_idx).is_some() {
|
||||
if let Some(pager) = &self.pager {
|
||||
pager.decommit_page(offset)?;
|
||||
pager.decommit_page(page_idx)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
@ -302,7 +302,7 @@ impl Vmo_ {
|
||||
if let Some(pager) = &self.inner.lock().pager {
|
||||
let page_idx_range = get_page_idx_range(&write_range);
|
||||
for page_idx in page_idx_range {
|
||||
pager.update_page(page_idx * PAGE_SIZE)?;
|
||||
pager.update_page(page_idx)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
@ -11,7 +11,7 @@ use aster_frame::vm::VmFrame;
|
||||
/// Finally, when a frame is no longer needed (i.e., on decommits),
|
||||
/// the frame pager will also be notified.
|
||||
pub trait Pager: Send + Sync {
|
||||
/// Ask the pager to provide a frame at a specified offset (in bytes).
|
||||
/// Ask the pager to provide a frame at a specified index.
|
||||
///
|
||||
/// After a page of a VMO is committed, the VMO shall not call this method
|
||||
/// again until the page is decommitted. But a robust implementation of
|
||||
@ -22,13 +22,10 @@ pub trait Pager: Send + Sync {
|
||||
/// and is to be committed again, then the pager is free to return
|
||||
/// whatever frame that may or may not be the same as the last time.
|
||||
///
|
||||
/// It is up to the pager to decide the range of valid offsets.
|
||||
///
|
||||
/// The offset will be rounded down to page boundary.
|
||||
fn commit_page(&self, offset: usize) -> Result<VmFrame>;
|
||||
/// It is up to the pager to decide the range of valid indices.
|
||||
fn commit_page(&self, idx: usize) -> Result<VmFrame>;
|
||||
|
||||
/// Notify the pager that the frame at a specified offset (in bytes)
|
||||
/// has been updated.
|
||||
/// Notify the pager that the frame at a specified index has been updated.
|
||||
///
|
||||
/// Being aware of the updates allow the pager (e.g., an inode) to
|
||||
/// know which pages are dirty and only write back the _dirty_ pages back
|
||||
@ -38,12 +35,9 @@ pub trait Pager: Send + Sync {
|
||||
/// But a robust implementation of `Pager` should not make
|
||||
/// such an assumption for its correctness; instead, it should simply ignore the
|
||||
/// call or return an error.
|
||||
///
|
||||
/// The offset will be rounded down to page boundary.
|
||||
fn update_page(&self, offset: usize) -> Result<()>;
|
||||
fn update_page(&self, idx: usize) -> Result<()>;
|
||||
|
||||
/// Notify the pager that the frame at the specified offset (in bytes)
|
||||
/// has been decommitted.
|
||||
/// Notify the pager that the frame at the specified index has been decommitted.
|
||||
///
|
||||
/// Knowing that a frame is no longer needed, the pager (e.g., an inode)
|
||||
/// can free the frame after writing back its data to the disk.
|
||||
@ -52,7 +46,5 @@ pub trait Pager: Send + Sync {
|
||||
/// But a robust implementation of `Pager` should not make
|
||||
/// such an assumption for its correctness; instead, it should simply ignore the
|
||||
/// call or return an error.
|
||||
///
|
||||
/// The offset will be rounded down to page boundary.
|
||||
fn decommit_page(&self, offset: usize) -> Result<()>;
|
||||
fn decommit_page(&self, idx: usize) -> Result<()>;
|
||||
}
|
||||
|
@ -1,15 +1,16 @@
|
||||
use bitvec::prelude::BitVec;
|
||||
use core::fmt::Debug;
|
||||
|
||||
/// An id allocator with BitVec.
|
||||
/// The true bit means the id is allocated and vice versa.
|
||||
#[derive(Clone, Debug)]
|
||||
/// An id allocator implemented by the bitmap.
|
||||
/// The true bit implies that the id is allocated, and vice versa.
|
||||
#[derive(Clone)]
|
||||
pub struct IdAlloc {
|
||||
bitset: BitVec,
|
||||
bitset: BitVec<u8>,
|
||||
first_available_id: usize,
|
||||
}
|
||||
|
||||
impl IdAlloc {
|
||||
/// Constructs a new id allocator with the maximum capacity.
|
||||
/// Constructs a new id allocator with a maximum capacity.
|
||||
pub fn with_capacity(capacity: usize) -> Self {
|
||||
let mut bitset = BitVec::with_capacity(capacity);
|
||||
bitset.resize(capacity, false);
|
||||
@ -19,9 +20,33 @@ impl IdAlloc {
|
||||
}
|
||||
}
|
||||
|
||||
/// Allocates and returns an id.
|
||||
/// Constructs a new id allocator from a slice of `u8` bytes and a maximum capacity.
|
||||
///
|
||||
/// Returns None if can not allocate.
|
||||
/// The slice of `u8` bytes is the raw data of a bitmap.
|
||||
pub fn from_bytes_with_capacity(slice: &[u8], capacity: usize) -> Self {
|
||||
let bitset = if capacity > slice.len() * 8 {
|
||||
let mut bitset = BitVec::from_slice(slice);
|
||||
bitset.resize(capacity, false);
|
||||
bitset
|
||||
} else {
|
||||
let mut bitset = BitVec::from_slice(&slice[..capacity.div_ceil(8)]);
|
||||
bitset.truncate(capacity);
|
||||
bitset
|
||||
};
|
||||
|
||||
let first_available_id = (0..bitset.len())
|
||||
.find(|&i| !bitset[i])
|
||||
.map_or(bitset.len(), |i| i);
|
||||
|
||||
Self {
|
||||
bitset,
|
||||
first_available_id,
|
||||
}
|
||||
}
|
||||
|
||||
/// Allocates and returns a new `id`.
|
||||
///
|
||||
/// If allocation is not possible, it returns `None`.
|
||||
pub fn alloc(&mut self) -> Option<usize> {
|
||||
if self.first_available_id < self.bitset.len() {
|
||||
let id = self.first_available_id;
|
||||
@ -35,9 +60,11 @@ impl IdAlloc {
|
||||
}
|
||||
}
|
||||
|
||||
/// Frees the allocated id.
|
||||
/// Releases the allocated `id`.
|
||||
///
|
||||
/// This panics if the id is out of bounds.
|
||||
/// # Panic
|
||||
///
|
||||
/// If the `id` is out of bounds, this method will panic.
|
||||
pub fn free(&mut self, id: usize) {
|
||||
debug_assert!(self.is_allocated(id));
|
||||
|
||||
@ -47,10 +74,26 @@ impl IdAlloc {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true is the id is allocated.
|
||||
/// Returns true if the `id` is allocated.
|
||||
///
|
||||
/// This panics if the id is out of bounds.
|
||||
/// # Panic
|
||||
///
|
||||
/// If the `id` is out of bounds, this method will panic.
|
||||
pub fn is_allocated(&self, id: usize) -> bool {
|
||||
self.bitset[id]
|
||||
}
|
||||
|
||||
/// Views the id allocator as a slice of `u8` bytes.
|
||||
pub fn as_bytes(&self) -> &[u8] {
|
||||
self.bitset.as_raw_slice()
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for IdAlloc {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||
f.debug_struct("IdAlloc")
|
||||
.field("len", &self.bitset.len())
|
||||
.field("first_available_id", &self.first_available_id)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
//! The util of Asterinas.
|
||||
#![no_std]
|
||||
#![forbid(unsafe_code)]
|
||||
#![feature(int_roundings)]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
|
Reference in New Issue
Block a user