Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • pigeonmoelleux/skavos
1 result
Show changes
Commits on Source (28)
/coverage
/target
storage
\ No newline at end of file
......@@ -24,6 +24,12 @@ dependencies = [
"version_check",
]
[[package]]
name = "anyhow"
version = "1.0.71"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
[[package]]
name = "autocfg"
version = "1.1.0"
......@@ -42,6 +48,12 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6776fc96284a0bb647b615056fc496d1fe1644a7ab01829818a6d91cae888b84"
[[package]]
name = "bootloader_api"
version = "0.11.3"
......@@ -60,6 +72,12 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "circular-buffer"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5887fa83bbf76a5cf15915add06c187cfc9d00f2ae9aa1f33d108a02ba7af345"
[[package]]
name = "conquer-once"
version = "0.3.2"
......@@ -216,9 +234,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "proc-macro2"
version = "1.0.56"
version = "1.0.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435"
checksum = "fa1fb82fc0c281dd9671101b66b771ebbe1eaf967b96ac8740dcba4b70005ca8"
dependencies = [
"unicode-ident",
]
......@@ -231,9 +249,9 @@ checksum = "9ff023245bfcc73fb890e1f8d5383825b3131cc920020a5c487d6f113dfc428a"
[[package]]
name = "quote"
version = "1.0.26"
version = "1.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc"
checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500"
dependencies = [
"proc-macro2",
]
......@@ -244,7 +262,7 @@ version = "10.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c297679cb867470fa8c9f67dbba74a78d78e3e98d7cf2b08d6d71540f797332"
dependencies = [
"bitflags",
"bitflags 1.3.2",
]
[[package]]
......@@ -297,7 +315,10 @@ name = "skavos"
version = "0.1.0"
dependencies = [
"acpi",
"anyhow",
"bitflags 2.3.1",
"bootloader_api",
"circular-buffer",
"conquer-once",
"crossbeam-queue",
"derive_more",
......@@ -342,7 +363,7 @@ version = "0.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b074eb9300ad949edd74c529c0e8d451625af71bb948e6b65fe69f72dc1363d9"
dependencies = [
"bitflags",
"bitflags 1.3.2",
"rustversion",
"x86_64",
]
......@@ -413,7 +434,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2781db97787217ad2a2845c396a5efe286f87467a5810836db6d74926e94a385"
dependencies = [
"bit_field",
"bitflags",
"bitflags 1.3.2",
"raw-cpuid",
]
......@@ -424,7 +445,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "100555a863c0092238c2e0e814c1096c1e5cf066a309c696a87e907b5f8c5d69"
dependencies = [
"bit_field",
"bitflags",
"bitflags 1.3.2",
"rustversion",
"volatile",
]
......@@ -12,7 +12,10 @@ license = "GPL-3.0-or-later"
[dependencies]
acpi = "4"
anyhow = { version = "1", default-features = false }
bitflags = "2"
bootloader_api = "0.11"
circular-buffer = { version = "0.1", default-features = false }
conquer-once = { version = "0.3", default-features = false }
crossbeam-queue = { version = "0.3", default-features = false, features = [ "alloc" ] }
derive_more = "0.99"
......@@ -31,3 +34,15 @@ x86_64 = "0.14"
[dev-dependencies]
minicov = "0.3"
[package.metadata.skavos-bootimage]
run-storage = "storage"
test-success-exit-code = 13
test-args = [
"-serial", "stdio",
"-serial", "null",
"-serial", "file:target/output.profraw",
"-display", "none",
"-device", "isa-debug-exit,iobase=0xf4,iosize=0x04"
]
test-mountpoint = "fs_tests"
\ No newline at end of file
FROM debian:bookworm-slim
ARG RUST_TOOLCHAIN=nightly-2023-04-30
RUN apt-get update && apt-get install -y build-essential clang curl git grcov lcov openssh-client rsync qemu-system-x86
RUN apt-get update && apt-get install -y build-essential clang curl e2fsprogs git grcov lcov openssh-client rsync qemu-system-x86
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | bash -s -- -y
ENV PATH="/root/.cargo/bin:${PATH}"
......
# SkavOS
## Description
SkavOS is a minimal kernel written in Rust by Balthazar Patiachvili and Vincent Lafeychine, developed for the course "Operating Systems" at the ENS Paris.
## Installation
You can install it by running the following steps. You will need at least [`clang`](https://clang.llvm.org/) and [`qemu`](https://www.qemu.org/).
On a debian-based device, you can install the following packages with `apt`:
```bash
$ sudo apt-get install build-essential clang curl e2fsprogs git grcov lcov openssh-client rsync qemu-system-x86
```
* Firstly, you need to install the good version of Rust unstable:
```bash
$ RUST_TOOLCHAIN=nightly-2023-04-30
$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
$ rustup toolchain install ${RUST_TOOLCHAIN} --component clippy llvm-tools-preview rust-src rustfmt
$ rustup target add x86_64-unknown-none --toolchain ${RUST_TOOLCHAIN}
$ cargo install cargo-deny
```
* Then, you will need to install the wrapper of `qemu` used in this project : [`skavos-bootimage`](https://gitlab.crans.org/v-lafeychine/skavos-bootimage/):
```bash
$ git clone https://gitlab.crans.org/v-lafeychine/skavos-bootimage.git
$ cd skavos-bootimage && cargo install --path . && cd ..
```
* Finally, you can clone [this github repository](https://gitlab.crans.org/pigeonmoelleux/skavos) then compile this project:
```bash
$ git clone https://gitlab.crans.org/pigeonmoelleux/skavos.git
$ cd skavos && cargo build --release
```
## Run the project
To run this project, after following the steps described in [Installation](#installation), you need to create a storage file named `storage` at the project's root:
```bash
$ dd if=/dev/zero of=storage bs=4M count=10
$ mkfs.ext2 storage
```
Then, you can finally run the project with:
```bash
$ cargo run
```
### Show the logs
A syslogger is implemented in this project, you can show one log at time by pressing `ALT + L`, and every log at once using `ALT + P`.
## Run the tests
This project contains a lot of tests with a measure of the coverage.
You can run the tests with:
```bash
$ cargo test
```
The result of the coverage measure can be seen in the `target/` directory.
Moreover, the website https://perso.crans.org/v-lafeychine/skavos/coverage/ contains the results of the coverage for the head of each branch is is updated automatically on each commit on which all the pipelines succeed.
N.B: The docker image has not been updated for the branch 8, so the coverage is not correct since a few commits.
## Documentation
The documentation can be built locally with:
```bash
$ cargo doc
```
then can be opened with your navigator (e.g for [`firefox`](https://www.mozilla.org/fr/firefox/new/)):
```bash
$ firefox target/x86_64/doc/skavos/index.html
```
......@@ -58,7 +58,7 @@
help = "Launch tests and generate HTML coverage website";
}];
packages = with pkgs; [ cargo-deny clang_15 grcov lcov qemu rust-dev ];
packages = with pkgs; [ cargo-deny clang_15 e2fsprogs grcov lcov qemu rust-dev ];
};
});
}
Hello world!
\ No newline at end of file
This is a second child of bar
\ No newline at end of file
This is a file
and this is a second line
!!!
\ No newline at end of file
//! Interface with the superblock and the block group descriptor table
//!
//! See the [OSdev wiki](https://wiki.osdev.org/Ext2) for more informations
use anyhow::{anyhow, Result};
use bitflags::bitflags;
use log::error;
use super::read;
/// Starting byte of the superblock in a Ext2 storage device
const SUPERBLOCK_START_BYTE: usize = 1024;
/// Starting byte of the block group descriptor table in a Ext2 storage device
const BLOCK_GROUP_DESCRIPTOR_TABLE_START_BYTE: usize = 2048;
/// Ext2 signature, used to help confirm the presence of an Ext2 volume
const EXT2_SIGNATURE: u16 = 0xef53;
/// Size in bytes of a group block descriptor
const GROUP_BLOCK_DESCRIPTOR_SIZE: usize = 32;
/// Superblock of the Ext2 filesystem
///
/// This implementation contains also the extended fields described [here](https://wiki.osdev.org/Ext2#Extended_Superblock_Fields)
#[repr(packed)]
#[derive(Debug)]
pub struct Superblock {
/// Total number of inodes in file system
pub total_inodes: u32,
/// Total number of blocks in file system
pub total_blocks: u32,
/// Number of blocks reserved for superuser (see offset 80)
pub number_blocks_superuser_reserved: u32,
/// Total number of unallocated blocks
pub total_unallocated_blocks: u32,
/// Total number of unallocated inodes
pub total_unallocated_inodes: u32,
/// Block number of the block containing the superblock (also the starting block number, NOT always zero.)
pub superblock_block_number: u32,
/// log2 (block size) - 10. (In other words, the number to shift 1,024 to the left by to obtain the block size)
pub shifted_block_size: u32,
/// log2 (fragment size) - 10. (In other words, the number to shift 1,024 to the left by to obtain the fragment size)
pub shifted_fragment_size: u32,
/// Number of blocks in each block group
pub blocks_per_group: u32,
/// Number of fragments in each block group
pub fragments_per_group: u32,
/// Number of inodes in each block group
pub inodes_per_group: u32,
/// Last mount time (in POSIX time)
pub last_mount_time: u32,
/// Last written time (in POSIX time)
pub last_written_time: u32,
/// Number of times the volume has been mounted since its last consistency check (fsck)
pub nb_volumes_mounted_since_fsck: u16,
/// Number of mounts allowed before a consistency check (fsck) must be done
pub nb_mounts_before_fsck: u16,
/// Ext2 signature (0xef53), used to help confirm the presence of Ext2 on a volume
pub ext2_signature: u16,
/// File system state
pub file_system_state: u16,
/// What to do when an error is detected
pub error_handling_method: u16,
/// Minor portion of version (combine with Major portion below to construct full version field)
pub version_minor: u16,
/// POSIX time of last consistency check (fsck)
pub time_last_fsck: u32,
/// Interval (in POSIX time) between forced consistency checks (fsck)
pub interval_forced_fsck: u32,
/// Operating system ID from which the filesystem on this volume was created
pub os_id: u32,
/// Major portion of version (combine with Minor portion above to construct full version field)
pub version_major: u32,
/// User ID that can use reserved blocks
pub reserved_blocks_user_id: u16,
/// Group ID that can use reserved blocks
pub reserved_blocks_group_id: u16,
/// First non-reserved inode in file system. (In versions < 1.0, this is fixed as 11)
pub first_non_reserved_inode: u32,
/// Size of each inode structure in bytes. (In versions < 1.0, this is fixed as 128)
pub inode_byte_size: u16,
/// Block group that this superblock is part of (if backup copy)
pub superblock_block_number_backup_copy: u16,
/// Optional features present (features that are not required to read or write, but usually result in a performance increase)
pub optional_features: u32,
/// Required features present (features that are required to be supported to read or write)
pub required_features: u32,
/// Features that if not supported, the volume must be mounted read-only
pub forced_read_only_features: u32,
/// File system ID (what is output by blkid)
pub file_system_id: u128,
/// Volume name (C-style string: characters terminated by a 0 byte)
pub volume_name: [u8; 16],
/// Path volume was last mounted to (C-style string: characters terminated by a 0 byte)
pub path_last_mount: [u8; 64],
/// Compression algorithms used (see Required features above)
pub compression_algorithms: u32,
/// Number of blocks to preallocate for files
pub preallocated_blocks_per_file: u8,
/// Number of blocks to preallocate for directories
pub preallocated_blocks_per_dir: u8,
/// (Unused)
pub _unused: u16,
/// Journal ID (same style as the File system ID above)
pub journal_id: u128,
/// Journal inode
pub journal_inode: u32,
/// Journal device
pub journal_device: u32,
/// Head of orphan inode list
pub head_orphan_node_list: u32,
}
impl Superblock {
/// Returns the superblock of the device if it exists
pub fn new() -> Result<Self> {
// SAFETY: if the read is successful, a `Superblock` should be present in the first bytes of the buffer
let Ok(superblock) = (unsafe { read::<Self>(SUPERBLOCK_START_BYTE) }) else { return Err(anyhow!("Ext2 superblock read: could not read the buffer pointer")) };
if superblock.ext2_signature == EXT2_SIGNATURE
&& superblock.total_blocks.div_ceil(superblock.blocks_per_group)
== superblock.total_inodes.div_ceil(superblock.inodes_per_group)
{
Ok(superblock)
} else {
Err(anyhow!("Ext2 superblock read: malformed superblock"))
}
}
/// Returns the number of block groups
///
/// It is equal to the round up of the total number of blocks divided by the number of blocks per block group
#[allow(unused)]
const fn total_block_groups(&self) -> usize {
self.total_blocks.div_ceil(self.blocks_per_group) as usize
}
/// Returns the size of a block in the filesystem described by this superblock
pub const fn block_size(&self) -> usize {
1024 << self.shifted_block_size
}
}
/// File System States
///
/// See the [OSdev wiki](https://wiki.osdev.org/Ext2#File_System_States) for more informations
#[repr(u16)]
#[derive(Debug)]
pub enum State {
/// File system is clean
Clean = 0x0001,
/// File system has errors
Errors = 0x0002,
}
impl TryFrom<u16> for State {
type Error = anyhow::Error;
fn try_from(value: u16) -> Result<Self, Self::Error> {
match value {
0x0001 => Ok(Self::Clean),
0x0002 => Ok(Self::Errors),
_ => {
error!("Ext2 superblock: tried to convert {:x} into a state (which can only be 1 or 2)", value);
Err(anyhow::Error::msg("Ext2 superblock: tried to convert a value different than 1 or 2 into a state"))
},
}
}
}
impl From<State> for u16 {
fn from(value: State) -> Self {
value as Self
}
}
/// Error Handling Methods
///
/// See the [OSdev wiki](https://wiki.osdev.org/Ext2#Error_Handling_Methods) for more informations
#[repr(u16)]
#[derive(Debug)]
pub enum ErrorsHandling {
/// Ignore the error (continue on)
Ignore = 0x0001,
/// Remount file system as read-only
Remount = 0x0002,
/// Kernel panic
Panic = 0x03,
}
impl TryFrom<u16> for ErrorsHandling {
type Error = anyhow::Error;
fn try_from(value: u16) -> Result<Self, Self::Error> {
match value {
0x0001 => Ok(Self::Ignore),
0x0002 => Ok(Self::Remount),
0x0003 => Ok(Self::Panic),
_ => {
error!(
"Ext2 superblock: tried to convert {:x} into an error handling method (which can only between 1 and 3)",
value
);
Err(anyhow::Error::msg(
"Ext2 superblock: tried to convert a value different than 1, 2 or 3 into an error handling method",
))
},
}
}
}
impl From<ErrorsHandling> for u16 {
fn from(value: ErrorsHandling) -> Self {
value as Self
}
}
bitflags! {
/// These are optional features for an implementation to support, but offer performance or reliability gains to
/// implementations that do support them.
pub struct OptionalFeatures: u16 {
/// Preallocate some number of (contiguous?) blocks (see byte 205 in the superblock) to a directory when creating
/// a new one (to reduce fragmentation?)
const PREALLOCATE_BLOCS = 0x0001;
/// AFS server inodes exist
const AFS = 0x0002;
/// File system has a journal (Ext3)
const JOURNAL_DEVICE = 0x0004;
/// Inodes have extended attributes
const INODES_EXTENDED_ATTRIBUTES = 0x0008;
/// File system can resize itself for larger partitions
const RESIZE_PARTITIONS = 0x0010;
/// Directories use hash index
const HASH_INDEX = 0x0020;
}
}
bitflags! {
/// These features if present on a file system are required to be supported by an implementation in order to correctly
/// read from or write to the file system
pub struct RequiredFeatures: u16 {
/// Compression is used
const COMPRESSION = 0x0001;
/// Directory entries contain a type field
const TYPE_FIELD = 0x0002;
/// File system needs to replay its journal
const JOURNAL_REPLAY = 0x0004;
/// File system uses a journal device
const JOURNAL_DEVICE = 0x0008;
}
}
bitflags! {
/// These features, if present on a file system, are required in order for an implementation to write to the file system,
/// but are not required to read from the file system.
pub struct ReadOnlyFeatures: u16 {
/// Sparse superblocks and group descriptor tables
const SPARSE_SUPERBLOCKS = 0x0001;
/// File system uses a 64-bit file size
const SIZE_64_BITS = 0x0002;
/// Directory contents are stored in the form of a Binary Tree
const BINARY_TREE = 0x0004;
}
}
/// Contains a descriptor for each block group in the file system.
#[repr(packed)]
#[derive(Debug)]
#[allow(clippy::module_name_repetitions)]
pub struct BlockGroupDescriptor {
/// Block address of block usage bitmap
pub block_usage_bitmap_block_address: u32,
/// Block address of inode usage bitmap
pub inode_usage_bitmap_block_address: u32,
/// Starting block address of inode table
pub inode_table_starting_block_address: u32,
/// Number of unallocated blocks in group
pub total_unallocated_blocks: u16,
/// Number of unallocated inodes in group
pub total_unallocated_inodes: u16,
/// Number of directories in group
pub total_dir: u16,
}
impl BlockGroupDescriptor {
/// Returns the `n`th block group descriptor
pub fn new(n: usize) -> Result<Self> {
// SAFETY: the given offset is coherent with the documentation
unsafe { read::<Self>(BLOCK_GROUP_DESCRIPTOR_TABLE_START_BYTE + n * GROUP_BLOCK_DESCRIPTOR_SIZE) }
}
}
//! Implementation of directories in a Ext2 filesystem
//!
//! See more informations on the [OSdev wiki](https://wiki.osdev.org/Ext2#Directories)
use alloc::borrow::ToOwned;
use alloc::string::String;
use alloc::sync::Arc;
use alloc::vec::Vec;
use core::fmt::Debug;
use core::mem;
use anyhow::{Ok, Result};
use log::warn;
use spin::Mutex;
use super::inode::{Inode, TypePermissions};
use super::SUPERBLOCK;
use crate::fs::ext2::inode::INODE_CACHE;
use crate::fs::ext2::{read, read_raw};
use crate::fs::node::{self, Interface, Kind, Node, Ref};
use crate::kernel::device::block::Manipulation;
/// Structure distinct from [`Entry`] to make easier its read on the device
#[repr(packed)]
#[derive(Debug, Clone)]
struct Subfields {
/// Inode index
inode: u32,
/// Total size of this entry (Including all subfields)
total_size: u16,
/// Name Length least-significant 8 bits
name_length_least_bits: u8,
/// Type indicator (only if the feature bit for "directory entries have file type byte" is set, else this is the
/// most-significant 8 bits of the Name Length)
type_indicator: u8,
}
/// A directory entry
#[derive(Clone)]
pub struct Entry {
/// Inode index
pub inode: u32,
/// Total size of this entry (Including all subfields)
pub total_size: u16,
/// Name Length least-significant 8 bits
pub name_length_least_bits: u8,
/// Type indicator (only if the feature bit for "directory entries have file type byte" is set, else this is the
/// most-significant 8 bits of the Name Length)
pub type_indicator: u8,
/// Name of the directory
pub name: String,
}
impl Debug for Entry {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Entry")
.field("inode", &self.inode)
.field("total_size", &self.total_size)
.field("name_length_least_bits", &self.name_length_least_bits)
.field("type_indicator", &self.type_indicator)
.field("name", &self.name)
.finish()
}
}
impl Interface for Entry {
fn name(&self) -> String {
self.name.clone()
}
fn kind(&self) -> Kind {
match self.type_indicator {
0 => panic!("Unknown type"),
1 => Kind::File,
2 => Kind::Directory,
3 => Kind::CharacterDevice,
4 => Kind::BlockDevice,
5 => Kind::Fifo,
6 => Kind::UnixSocket,
7 => Kind::Symlink,
_ => unreachable!("Directory Entry Type can only be between 0 and 7"),
}
}
fn get_parent_dir(&self) -> Ref<dyn node::Directory + Send> {
todo!()
}
fn set_parent_dir(&mut self, _new_parent: Ref<dyn node::Directory + Send>) {
todo!()
}
}
impl Entry {
/// Returns the entry at the given `offset`
fn new(offset: usize) -> Result<Self> {
// SAFETY: there is always an entry at the base offset pointed by an inode
let subfields = unsafe { read::<Subfields>(offset) }?;
let name_size = subfields.total_size as usize - mem::size_of::<Subfields>();
// SAFETY: it is ensure that the given offset correspond to an entry
let Result::Ok(mut name) = String::from_utf8(unsafe { read_raw(offset + mem::size_of::<Subfields>(), name_size) }?.to_vec()) else { unreachable!("Entry names are encoded in ISO-Latin-1 which is a subset of UTF-8"); };
if let Some(index) = name.find('\0') {
name.truncate(index);
}
Ok(Self {
inode: subfields.inode,
total_size: subfields.total_size,
name_length_least_bits: subfields.name_length_least_bits,
type_indicator: subfields.type_indicator,
name,
})
}
}
/// An interface for a complete directory
#[derive(Clone)]
pub struct Directory {
/// Entries contained in the directory
entries: Vec<Entry>,
/// Name of this directory
///
/// This field cannot be read directly so it is stored also here
name: String,
/// Reference to the parent directory
parent: Ref<dyn node::Directory + Send>,
}
impl Debug for Directory {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Directory")
.field("entries", &self.entries)
.field("name", &self.name)
.finish()
}
}
impl Interface for Directory {
fn name(&self) -> String {
self.name.clone()
}
fn kind(&self) -> Kind {
Kind::Directory
}
fn get_parent_dir(&self) -> Ref<dyn node::Directory + Send> {
if self.is_root() { Arc::new(Mutex::new(self.clone())) } else { Arc::clone(&self.parent) }
}
fn set_parent_dir(&mut self, _new_parent: Ref<dyn node::Directory + Send>) {
todo!()
}
}
impl node::Directory for Directory {
fn get(&self, name: &str) -> Option<node::Node> {
let entries = self.children();
entries.into_iter().find(|entry| entry.name() == name)
}
fn insert(&mut self, _node: node::Node) -> Option<node::Node> {
todo!()
}
fn remove(&mut self, _node: node::Node) -> Option<node::Node> {
todo!()
}
fn children(&self) -> Vec<node::Node> {
let mut children = Vec::new();
let self_reference: Arc<Mutex<dyn node::Directory + Send>> = Arc::new(Mutex::new(self.clone()));
let mut inode_cache = INODE_CACHE.lock();
for entry in &self.entries {
let inode = inode_cache.get(entry.inode as usize).expect("Could not retrieve an inode");
let inode_type = TypePermissions::from_bits_truncate(inode.type_and_permissions);
if inode_type.intersects(TypePermissions::DIRECTORY) {
children.push(Node::Directory(Arc::new(Mutex::new(
Self::new(&inode, entry.name.clone(), Arc::clone(&self_reference))
.expect("Could not retrieve a directory contained in an inode"),
))));
} else if inode_type.intersects(TypePermissions::FILE) {
children.push(Node::File(Arc::new(Mutex::new(File::new(entry, &inode).unwrap_or_else(|err| panic!("{err}"))))));
} else {
warn!(
"Tried to use the entry {:?} : Ext2 is not implemented yet for other inode types than files and directory",
entry
);
}
}
children
}
}
// SAFETY: all the directories will be shared behind mutexes
unsafe impl Send for Directory {}
impl Directory {
/// Returns the complete directory pointed by the inode
pub fn new(inode: &Inode, name: String, parent: Ref<dyn node::Directory + Send>) -> Result<Self> {
let superblock = SUPERBLOCK.get().expect("The superblock has not been initialized yet");
let mut entries = Vec::new();
let mut accumulated_size = 0_usize;
while accumulated_size < superblock.block_size() {
let entry = Entry::new(inode.dbp00 as usize * superblock.block_size() + accumulated_size)?;
accumulated_size += entry.total_size as usize;
entries.push(entry);
}
Ok(Self {
entries,
name,
parent,
})
}
/// Returns the root of the filesystem pointed by the given `inode`
pub fn root(inode: &Inode) -> Result<Self> {
let superblock = SUPERBLOCK.get().expect("The superblock has not been initialized yet");
let mut entries = Vec::new();
let mut accumulated_size = 0_usize;
while accumulated_size < superblock.block_size() {
let entry = Entry::new(inode.dbp00 as usize * superblock.block_size() + accumulated_size)?;
accumulated_size += entry.total_size as usize;
if accumulated_size > superblock.block_size() {
break;
}
entries.push(entry);
}
// SAFETY: the root parent will be never read
let parent = unsafe { Arc::<Mutex<Self>>::new_uninit().assume_init() };
Ok(Self {
entries,
name: "/".to_owned(),
parent,
})
}
}
/// An interface for a file
#[derive(Debug)]
#[allow(unused)]
pub struct File {
/// Entry of this file
entry: Entry,
/// Complete length of the file
length: usize,
/// Direct block pointers to the data
direct_block_pointers: [u32; 12],
/// Singly Indirect Block Pointer to the data
singly_indirect_block_pointer: u32,
/// Doubly Indirect Block Pointer to the data
doubly_indirect_block_pointer: u32,
/// Triply Indirect Block Pointer to the data
triply_indirect_block_pointer: u32,
}
impl Interface for File {
fn name(&self) -> String {
self.entry.name()
}
fn kind(&self) -> Kind {
Kind::File
}
fn get_parent_dir(&self) -> Ref<dyn node::Directory + Send> {
self.entry.get_parent_dir()
}
fn set_parent_dir(&mut self, new_parent: Ref<dyn node::Directory + Send>) {
self.entry.set_parent_dir(new_parent);
}
}
impl Manipulation for File {
fn block_size(&self) -> usize {
SUPERBLOCK.get().expect("The superblock has not been initialized yet").block_size()
}
fn len(&self) -> usize {
self.length
}
fn read(&self, _buffer: &mut [u8], _offset: usize, _length: usize) -> Result<usize> {
todo!()
}
fn write(&mut self, _buffer: &[u8], _offset: usize, _length: usize) -> Result<usize> {
todo!()
}
fn flush(&mut self) -> Result<()> {
Ok(())
}
}
impl node::File for File {}
// SAFETY: files will be always shared behind mutexes
unsafe impl Send for File {}
impl File {
/// Creates a new file
fn new(entry: &Entry, inode: &Inode) -> Result<Self> {
Ok(Self {
entry: entry.clone(),
length: inode.data_size(),
direct_block_pointers: [
inode.dbp00,
inode.dbp01,
inode.dbp02,
inode.dbp03,
inode.dbp04,
inode.dbp05,
inode.dbp06,
inode.dbp07,
inode.dbp08,
inode.dbp09,
inode.dbp10,
inode.dbp11,
],
singly_indirect_block_pointer: inode.singly_indirect_block_pointer,
doubly_indirect_block_pointer: inode.doubly_indirect_block_pointer,
triply_indirect_block_pointer: inode.triply_indirect_block_pointer,
})
}
}
//! Interface with the Inodes
//!
//! See the [OSdev wiki](https://wiki.osdev.org/Ext2#Inodes) for more informations
use alloc::collections::btree_map::Entry;
use alloc::collections::BTreeMap;
use anyhow::Result;
use bitflags::bitflags;
use spin::Mutex;
use super::block::{BlockGroupDescriptor, Superblock};
use super::{read, SUPERBLOCK};
/// Inode index of the root
///
/// See the [OSdev wiki](https://wiki.osdev.org/Ext2#How_To_Read_the_Root_Directory) for more informations
pub const ROOT_INODE_INDEX: usize = 2;
/// General structure of an Ext2 inode
#[repr(packed)]
#[derive(Debug, Clone)]
pub struct Inode {
/// Type and Permissions
pub type_and_permissions: u16,
/// User ID
pub user_id: u16,
/// Lower 32 bits of size in bytes
pub lower_truncation_byte_size: u32,
/// Last Access Time (in POSIX time)
pub last_access_time: u32,
/// Creation Time (in POSIX time)
pub creation_time: u32,
/// Last Modification time (in POSIX time)
pub last_modification_time: u32,
/// Deletion time (in POSIX time)
pub deletion_time: u32,
/// Group ID
pub group_id: u16,
/// Count of hard links (directory entries) to this inode. When this reaches 0, the data blocks are marked as unallocated.
pub total_hard_links: u16,
/// Count of disk sectors (not Ext2 blocks) in use by this inode, not counting the actual inode structure nor directory entries
/// linking to the inode.
pub total_disk_sectors: u32,
/// Flags
pub flags: u32,
/// Operating System Specific value #1
pub os_specific_1: u32,
/// Direct Block Pointer 0
pub dbp00: u32,
/// Direct Block Pointer 1
pub dbp01: u32,
/// Direct Block Pointer 2
pub dbp02: u32,
/// Direct Block Pointer 3
pub dbp03: u32,
/// Direct Block Pointer 4
pub dbp04: u32,
/// Direct Block Pointer 5
pub dbp05: u32,
/// Direct Block Pointer 6
pub dbp06: u32,
/// Direct Block Pointer 7
pub dbp07: u32,
/// Direct Block Pointer 8
pub dbp08: u32,
/// Direct Block Pointer 9
pub dbp09: u32,
/// Direct Block Pointer 10
pub dbp10: u32,
/// Direct Block Pointer 11
pub dbp11: u32,
/// Singly Indirect Block Pointer (Points to a block that is a list of block pointers to data)
pub singly_indirect_block_pointer: u32,
/// Doubly Indirect Block Pointer (Points to a block that is a list of block pointers to Singly Indirect Blocks)
pub doubly_indirect_block_pointer: u32,
/// Triply Indirect Block Pointer (Points to a block that is a list of block pointers to Doubly Indirect Blocks)
pub triply_indirect_block_pointer: u32,
/// Generation number (Primarily used for NFS)
pub generation_number: u32,
/// In Ext2 version 0, this field is reserved. In version >= 1, Extended attribute block (File ACL).
pub extended_attribute_block: u32,
/// In Ext2 version 0, this field is reserved. In version >= 1, Upper 32 bits of file size (if feature bit set) if it's a file,
/// Directory ACL if it's a directory
pub higher_truncation_byte_size_or_directory_acl: u32,
/// Block address of fragment
pub fragment_block_address: u32,
/// Operating System Specific value #2
pub os_specific_2: [u8; 12],
}
impl Inode {
/// Returns the `n`th inode
///
/// Beware that the inode indexing starts at **1**
fn new(n: usize) -> Result<Self> {
let superblock = SUPERBLOCK.get().expect("The superblock has not been initialized yet");
let block_group = Self::block_group(superblock, n);
let block_group_descriptor = BlockGroupDescriptor::new(block_group)?;
let inode_table_starting_block = block_group_descriptor.inode_table_starting_block_address as usize;
let index = Self::group_index(superblock, n);
// SAFETY: if the superblock is well formed and the device contains an Ext2 filesystem, then this offset contain an inode
unsafe { read::<Self>(inode_table_starting_block * superblock.block_size() + index * superblock.inode_byte_size as usize) }
}
/// Returns the block group of the inode `n`
///
/// See the [OSdev wiki](https://wiki.osdev.org/Ext2#Determining_which_Block_Group_contains_an_Inode) for more informations
pub const fn block_group(superblock: &Superblock, n: usize) -> usize {
(n - 1) / superblock.inodes_per_group as usize
}
/// Returns the index of the inode `n` in its block group
///
/// See the [OSdev wiki](https://wiki.osdev.org/Ext2#Finding_an_inode_inside_of_a_Block_Group) for more informations
pub const fn group_index(superblock: &Superblock, n: usize) -> usize {
(n - 1) % superblock.inodes_per_group as usize
}
/// Returns the index of the block containing the inode `n`
///
/// See the [OSdev wiki](https://wiki.osdev.org/Ext2#Finding_an_inode_inside_of_a_Block_Group) for more informations
#[allow(unused)]
pub const fn containing_block(superblock: &Superblock, n: usize) -> usize {
(n * superblock.inode_byte_size as usize) / superblock.block_size()
}
/// Returns the complete size of the data pointed by this inode
#[allow(clippy::cast_possible_truncation)]
pub const fn data_size(&self) -> usize {
self.lower_truncation_byte_size as usize & ((self.higher_truncation_byte_size_or_directory_acl as u64) << 32_u64) as usize
}
}
bitflags! {
/// Indicators of the inode type and permissions
///
/// The type indicator occupies the top hex digit (bits 15 to 12).
///
/// The permission indicator occupies the bottom 12 bits.
#[derive(Debug)]
pub struct TypePermissions: u16 {
// Types
/// FIFO
const FIFO = 0x1000;
/// Character device
const CHARACTER_DEVICE = 0x2000;
/// Directory
const DIRECTORY = 0x4000;
/// Block device
const BLOCK_DEVICE = 0x6000;
/// Regular file
const FILE = 0x8000;
/// Symbolic link
const SYMBOLIC_LINK = 0xA000;
/// Unix socket
const SOCKET = 0xC000;
// Permissions
/// Other - execute permission
const OTHER_X = 0x0001;
/// Other - write permission
const OTHER_W = 0x0002;
/// Other - read permission
const OTHER_R = 0x0004;
/// Group - execute permission
const GROUP_X = 0x0008;
/// Group - write permission
const GROUP_W = 0x0010;
/// Group - read permission
const GROUP_R = 0x0020;
/// User - execute permission
const USER_X = 0x0040;
/// User - write permission
const USER_W = 0x0080;
/// User - read permission
const USER_R = 0x0100;
/// Sticky bit
const STICKY = 0x0200;
/// Set group ID
const SET_GROUP_ID = 0x0400;
/// Set user ID
const SET_USER_ID = 0x0800;
}
}
bitflags! {
/// Inode Flags
#[derive(Debug)]
pub struct Flags: u32 {
/// Secure deletion (not used)
const SECURE_DELETION = 0x0000_0001;
/// Keep a copy of data when deleted (not used)
const DELETION_KEEP_DATA_COPY = 0x0000_0002;
/// File compression (not used)
const FILE_COMPRESSION = 0x0000_0004;
/// Synchronous updates—new data is written immediately to disk
const SYNCHRONOUS_UPDATES = 0x0000_0008;
/// Immutable file (content cannot be changed)
const IMMUTABLE_FILE = 0x0000_0010;
/// Append only
const APPEND_ONLY = 0x0000_0020;
/// File is not included in `dump` command
const DUMP_EXCLUDED = 0x0000_0040;
/// Last accessed time should not updated
const LAST_ACCESSED_TIME_UPDATE_DISABLE = 0x0000_0080;
/// Hash indexed directory
const HASHED_INDEXED_DIR = 0x0001_0000;
/// AFS directory
const AFS_DIR = 0x0002_0000;
/// Journal file data
const JOURNAL_FILE_DATA = 0x0004_0000;
}
}
/// Structure used to cache inodes not to read them when it is not useful
#[derive(Debug)]
pub struct Cache {
/// Inode dictionary
cache: BTreeMap<usize, Inode>,
}
impl Cache {
/// Returns the inode with the given `index`
pub fn get(&mut self, index: usize) -> Result<Inode> {
match self.cache.entry(index) {
Entry::Vacant(entry) => {
let inode = Inode::new(index)?;
entry.insert(inode.clone());
Ok(inode)
},
Entry::Occupied(entry) => Ok(entry.get().clone()),
}
}
}
/// Global inode cache
pub static INODE_CACHE: Mutex<Cache> = Mutex::new(Cache {
cache: BTreeMap::new(),
});
//! Implementation of the Second Extended Filesystem (ext2fs) filesystem
use alloc::slice;
use alloc::sync::Arc;
use alloc::vec::Vec;
use core::{mem, ptr};
use anyhow::{Ok, Result};
use conquer_once::spin::OnceCell;
use log::info;
use spin::Mutex;
use self::block::Superblock;
use self::inode::ROOT_INODE_INDEX;
use crate::fs::ext2::entry::Directory;
use crate::fs::ext2::inode::INODE_CACHE;
use crate::kernel::device::storage::Device;
#[allow(clippy::same_name_method)]
mod block;
mod entry;
#[allow(clippy::same_name_method)]
mod inode;
/// Device on which the Ext2 filesystem is located
static DEVICE: OnceCell<Arc<Mutex<dyn Device + Send>>> = OnceCell::uninit();
/// Superblock of the device
static SUPERBLOCK: OnceCell<Superblock> = OnceCell::uninit();
/// Reads the device at the given offset **in bytes** and returns the expected structure
///
/// # Safety
///
/// Must ensure that the structure `T` is present on the device at the given offset
unsafe fn read<T>(offset: usize) -> Result<T> {
Ok(ptr::read(read_raw(offset, mem::size_of::<T>())?.as_ptr().cast::<T>()))
}
/// Reads the device at the given offset **in bytes** and returns the `n` raw bytes
///
/// # Safety
///
/// Must ensure that the structure `T` is present on the device at the given offset
#[allow(clippy::indexing_slicing)]
unsafe fn read_raw(offset: usize, n: usize) -> Result<&'static [u8]> {
let binding = DEVICE.get().expect("The device has not been initialized yet").lock();
let block_size = binding.block_size();
let mut buffer_size = 0_usize;
while buffer_size < n {
buffer_size += block_size;
}
let (raw_buffer, _, _) = Vec::<u8>::with_capacity(buffer_size).into_raw_parts();
let buffer = slice::from_raw_parts_mut(raw_buffer, buffer_size);
binding.read(buffer, offset / block_size, buffer_size / block_size)?;
Ok(buffer[offset % block_size..offset % block_size + n].as_ref())
}
/// Searches for an Ext2 filesystem, and returns the root if found
pub fn search(device: &Arc<Mutex<dyn Device + Send>>) -> Result<Arc<Mutex<dyn super::node::Directory + Send>>> {
DEVICE.init_once(|| Arc::clone(device));
let superblock = Superblock::new()?;
SUPERBLOCK.init_once(|| superblock);
let mut inode_cache = INODE_CACHE.lock();
let root_inode = inode_cache.get(ROOT_INODE_INDEX)?;
let root = Directory::root(&root_inode)?;
info!("Found the root of the filesystem : {:?}", root);
Ok(Arc::new(Mutex::new(root)))
}
//! Everything related to the File System (fs)
//!
//! Contains modules for the Virtual File System (vfs) and an ext2 implementations
use alloc::sync::Arc;
use conquer_once::spin::OnceCell;
use log::{error, trace};
use spin::Mutex;
use self::node::Directory;
use crate::kernel::device::storage::Device;
mod ext2;
pub mod node;
pub mod path;
pub mod vfs;
/// Root of the filesystem
pub static ROOT: OnceCell<Arc<Mutex<dyn Directory + Send>>> = OnceCell::uninit();
/// Initializes the filesystem in the given storage device and return the root of this filesystem
///
/// If no filesystem is found, an empty one will be created
pub fn init(device: &Arc<Mutex<dyn Device + Send>>) {
trace!("Initialization of the device {:?}", device.lock().uuid());
match ext2::search(device) {
Ok(root) => ROOT.init_once(|| root),
Err(err) => error!("{}", err),
}
}
#[cfg(test)]
mod test {
use alloc::string::String;
use alloc::vec::Vec;
use ::alloc::vec;
use super::node::Interface;
use super::ROOT;
use crate::fs::node::Node;
#[test_case]
fn files_and_directories() {
let root = ROOT.get().unwrap().lock();
assert!(root.is_root());
assert_eq!(root.children().iter().map(Interface::name).collect::<Vec<String>>(), vec![
".",
"..",
"lost+found",
"bar",
"foo.txt"
]);
let Node::Directory(bar) = root.get("bar").unwrap() else { panic!(); };
let binding = bar.lock();
assert_eq!(binding.children().iter().map(Interface::name).collect::<Vec<String>>(), vec![".", "..", "boo", "far.txt"]);
}
}
//! Interface for files, directories and symbolic links
//!
//! The general structure of the file systems is the Node Graph in Unix style.
//!
//! The node named "/" is a special node called "root" of the filesystem.
use alloc::string::String;
use alloc::sync::Arc;
use alloc::vec::Vec;
use spin::Mutex;
use super::path::Path;
use crate::kernel::device::block::Manipulation;
/// Type alias for a thread-safe shared reference behind a mutex
pub type Ref<T> = Arc<Mutex<T>>;
/// Enumeration of possible inode types
#[derive(Debug, Clone, Copy)]
#[allow(unused)]
pub enum Kind {
/// Storage unit of a filesystem
File,
/// Node containing other nodes
Directory,
/// Node pointing towards an other node in the filesystem
Symlink,
/// FIFO
Fifo,
/// An inode that refers to a device communicating by sending chars (bytes) of data
CharacterDevice,
/// An inode that refers to a device communicating by sending blocks of data
BlockDevice,
/// Communication flow between two processes
UnixSocket,
}
/// Enumeration of possible nodes
#[allow(unused)]
pub enum Node {
/// A reference to a file
File(Ref<dyn File + Send>),
/// A reference to a directory
Directory(Ref<dyn Directory + Send>),
/// A reference to a symbolic link
Symlink(Ref<dyn Symlink + Send>),
/// A reference to a FIFO
Fifo(Ref<dyn Interface + Send>),
/// A reference to a character device
CharacterDevice(Ref<dyn Interface + Send>),
/// A reference to a block device
BlockDevice(Ref<dyn Interface + Send>),
/// A reference to a Unix socket
UnixSocket(Ref<dyn Interface + Send>),
}
impl From<Node> for Kind {
fn from(value: Node) -> Self {
match value {
Node::File(_) => Self::File,
Node::Directory(_) => Self::Directory,
Node::Symlink(_) => Self::Symlink,
Node::Fifo(_) => Self::Fifo,
Node::CharacterDevice(_) => Self::CharacterDevice,
Node::BlockDevice(_) => Self::BlockDevice,
Node::UnixSocket(_) => Self::UnixSocket,
}
}
}
/// General interface for every Interface in a filesystem
pub trait Interface {
/// Returns the name of the node
///
/// A node's name cannot contain the character `/`
fn name(&self) -> String;
/// Returns the kind of the node
fn kind(&self) -> Kind;
/// Returns the parent directory of the node
///
/// The parent directory of the root "/" should by itself
fn get_parent_dir(&self) -> Ref<dyn Directory + Send>;
/// Sets the parent directory of the node
fn set_parent_dir(&mut self, new_parent: Ref<dyn Directory + Send>);
/// Returns whether the node is the root of the filesystem or not
fn is_root(&self) -> bool {
self.name() == "/"
}
/// Returns the absolute path of the node
fn absolute_path(&self) -> Path {
if self.is_root() {
Path::new_absolute()
} else {
let mut parent_path = self.get_parent_dir().lock().absolute_path();
parent_path.push(&self.name());
parent_path
}
}
}
impl PartialEq for dyn Interface {
fn eq(&self, other: &Self) -> bool {
self.absolute_path() == other.absolute_path()
}
}
impl Eq for dyn Interface {}
/// General interface for directories
pub trait Directory: Interface {
/// Returns the child with the corresponding name
fn get(&self, name: &str) -> Option<Node>;
/// Inserts the given `node` in the directory
///
/// If a node with the same name is already present in this directory, that node is replaced and return
fn insert(&mut self, node: Node) -> Option<Node>;
/// Removes the given `node` in the directory then returns it
fn remove(&mut self, node: Node) -> Option<Node>;
/// Lists the nodes in this directory
fn children(&self) -> Vec<Node>;
}
/// General interface for files
pub trait File: Interface + Manipulation {}
/// General interface for symbolic links
pub trait Symlink: Interface {
/// Returns the node pointed by the symbolic link
fn pointed_node(&self) -> Node;
}
impl Interface for Node {
fn name(&self) -> String {
match self {
Self::File(file) => file.lock().name(),
Self::Directory(directory) => directory.lock().name(),
Self::Symlink(symlink) => symlink.lock().name(),
Self::Fifo(fifo) => fifo.lock().name(),
Self::CharacterDevice(character_device) => character_device.lock().name(),
Self::BlockDevice(block_device) => block_device.lock().name(),
Self::UnixSocket(unix_socket) => unix_socket.lock().name(),
}
}
fn kind(&self) -> Kind {
match self {
Self::File(file) => file.lock().kind(),
Self::Directory(directory) => directory.lock().kind(),
Self::Symlink(symlink) => symlink.lock().kind(),
Self::Fifo(fifo) => fifo.lock().kind(),
Self::CharacterDevice(character_device) => character_device.lock().kind(),
Self::BlockDevice(block_device) => block_device.lock().kind(),
Self::UnixSocket(unix_socket) => unix_socket.lock().kind(),
}
}
fn get_parent_dir(&self) -> Ref<dyn Directory + Send> {
match self {
Self::File(file) => file.lock().get_parent_dir(),
Self::Directory(directory) => directory.lock().get_parent_dir(),
Self::Symlink(symlink) => symlink.lock().get_parent_dir(),
Self::Fifo(fifo) => fifo.lock().get_parent_dir(),
Self::CharacterDevice(character_device) => character_device.lock().get_parent_dir(),
Self::BlockDevice(block_device) => block_device.lock().get_parent_dir(),
Self::UnixSocket(unix_socket) => unix_socket.lock().get_parent_dir(),
}
}
fn set_parent_dir(&mut self, new_parent: Ref<dyn Directory + Send>) {
match self {
Self::File(file) => file.lock().set_parent_dir(new_parent),
Self::Directory(directory) => directory.lock().set_parent_dir(new_parent),
Self::Symlink(symlink) => symlink.lock().set_parent_dir(new_parent),
Self::Fifo(fifo) => fifo.lock().set_parent_dir(new_parent),
Self::CharacterDevice(character_device) => character_device.lock().set_parent_dir(new_parent),
Self::BlockDevice(block_device) => block_device.lock().set_parent_dir(new_parent),
Self::UnixSocket(unix_socket) => unix_socket.lock().set_parent_dir(new_parent),
}
}
}
//! Interface to use paths in the filesystem
use alloc::borrow::ToOwned;
use alloc::string::String;
use derive_more::{Deref, DerefMut};
use super::node::{Interface, Ref};
/// Memory allocated non-empty path structure
#[derive(Debug, Deref, DerefMut, Clone, PartialEq, Eq)]
pub struct Path(String);
impl From<Ref<dyn Interface>> for Path {
fn from(value: Ref<dyn Interface>) -> Self {
let node = value.lock();
if node.is_root() {
Self::new_absolute()
} else {
let mut parent_path = Self::from(node.get_parent_dir() as Ref<dyn Interface>);
parent_path.push(&node.name());
parent_path
}
}
}
impl Path {
/// Returns a new absolute path (the path of the filesystem's root)
pub fn new_absolute() -> Self {
Self("/".to_owned())
}
/// Returns a new relative path (the path of the "current" node)
#[allow(unused)]
pub fn new_relative() -> Self {
Self(".".to_owned())
}
/// Returns whether `self` is relative or not
#[allow(unused)]
pub fn is_relative(&self) -> bool {
// SAFETY: it is ensure in this structure that no empty path can be created
unsafe { self.chars().next().unwrap_unchecked() == '.' }
}
/// Returns whether `self` is absolute or not
#[allow(unused)]
pub fn is_absolute(&self) -> bool {
!self.is_relative()
}
/// Returns whether `self` represents the root or not
///
/// Is `is_relative` if `true`, then this function will always return `false`.
#[allow(unused)]
pub fn is_root(&self) -> bool {
self.is_absolute() && self.ends_with('/')
}
/// Extends `self` with a node represented as a [`String`]
pub fn push(&mut self, node: &str) {
if self.0 != "/" {
self.extend_one('/');
}
self.extend(node.chars());
}
/// Truncates `self` to its parent dir
///
/// Returns the name of the node truncated, and [`None`] if it does nothing
#[allow(unused)]
pub fn pop(&mut self) -> Option<String> {
if self.0 == "." || self.0 == "/" {
return None;
};
let binding = self.clone();
let relative = self.is_relative();
let mut ancestors = binding.split('/');
// SAFETY: the path is non empty and not reduced to "/" or "."
let node = unsafe { ancestors.next_back().unwrap_unchecked() }.to_owned();
if relative {
ancestors.next();
};
self.clone_from(&if relative { Self::new_relative() } else { Self::new_absolute() });
ancestors.for_each(|ancestor| self.push(ancestor));
Some(node)
}
}
#[cfg(test)]
mod test {
use super::Path;
#[test_case]
fn absolute() {
let root = Path::new_absolute();
assert!(root.is_absolute());
assert!(root.is_root());
assert_eq!(&root.0, "/");
}
#[test_case]
fn relative() {
let current = Path::new_relative();
assert!(current.is_relative());
assert!(!current.is_root());
assert_eq!(&current.0, ".");
}
#[test_case]
fn absolute_manipulation() {
let mut path = Path::new_absolute();
path.push("bin");
path.push("ls");
assert_eq!(&path.0, "/bin/ls");
assert_eq!(&path.pop().unwrap(), "ls");
assert_eq!(&path.0, "/bin");
}
#[test_case]
fn relative_manipulation() {
let mut path = Path::new_relative();
path.push("foo");
path.push("bar");
assert_eq!(&path.0, "./foo/bar");
assert_eq!(&path.pop().unwrap(), "bar");
assert_eq!(&path.0, "./foo");
}
}
//! Virtual File System (vfs)
//!
//! Interface between the file system and the kernel/user
This diff is collapsed.
//! Structures and traits for block manipulation
//!
//! Blocks are stream of bytes of fixed length
use anyhow::Result;
/// Specifies all the necessary functions for a object to manipulate blocks
pub trait Manipulation {
/// Returns the size in bytes of a single block
fn block_size(&self) -> usize;
/// Returns the length in bytes of the full I/O stream
fn len(&self) -> usize;
/// Reads `length` blocks of data from the I/O stream into the given `buffer` starting at the given `offset` (in **blocks**)
///
/// Returns the number of blocks read
fn read(&self, buffer: &mut [u8], offset: usize, length: usize) -> Result<usize>;
/// Writes `length` blocks of data from the given `buffer` into I/O stream starting at the given `offset` (in **blocks**)
///
/// Returns the number of blocks written
fn write(&mut self, buffer: &[u8], offset: usize, length: usize) -> Result<usize>;
/// Flushes the output stream, ensuring all contents in intermediate buffers are fully written out
fn flush(&mut self) -> Result<()>;
}
//! Device interface
use alloc::collections::BTreeMap;
use alloc::sync::Arc;
use alloc::vec::Vec;
use conquer_once::spin::OnceCell;
use log::{info, warn};
use spin::Mutex;
use self::ata::IdeController;
use self::storage::Uuid;
#[allow(clippy::same_name_method)]
mod ata;
pub mod block;
mod pci;
pub mod storage;
/// List of references towards all initialized storage controllers
pub static STORAGE_CONTROLLERS: OnceCell<Vec<Arc<Mutex<dyn storage::Controller + Send>>>> = OnceCell::uninit();
/// UUID of the UEFI file which should not be considered as an available storage device
pub const UEFI_UUID: Uuid =
[0x4D, 0x51, 0x30, 0x30, 0x30, 0x30, 0x20, 0x31, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20];
/// Initializes the devices
///
/// Currently, is is only used for storage devices.
pub fn init() {
let devices = pci::scan();
info!("{} devices found during PCI scan", devices.len());
let storage_controllers = STORAGE_CONTROLLERS.get_or_init(|| {
let mut controllers = BTreeMap::<Vec<Uuid>, Arc<Mutex<dyn storage::Controller + Send>>>::new();
// To ensure each storage controller is only found once
devices
.values()
.flatten()
.filter_map(|device| {
Some(Arc::new(Mutex::new(IdeController::new(device)?)) as Arc<Mutex<dyn storage::Controller + Send>>)
})
.for_each(|controller| {
let uuids = controller.lock().devices().iter().map(|device| device.lock().uuid()).collect();
if let Some(controller) = controllers.insert(uuids, controller) {
warn!(
"The storage device controller {:?} has been found several times",
controller
.lock()
.devices()
.iter()
.map(|device| device.lock().uuid())
.collect::<Vec<Uuid>>()
);
}
});
controllers.into_values().collect()
});
info!(
"{} storage controller(s) and {} storage drive(s) found",
storage_controllers.len(),
storage_controllers
.iter()
.fold(0_usize, |acc, controller| acc + controller.lock().devices().len())
);
}