Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add vfs API and memory_vfs implementation #639

Closed
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions kernel/memory_fs/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[package]
name = "memory_fs"
version = "0.1.0"
description = "In-memory file system"
edition = "2021"

[dependencies]
spin = "0.9.0"
vfs = { path = "../vfs" }
272 changes: 272 additions & 0 deletions kernel/memory_fs/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,272 @@
//! In-memory file system.
//!
//! This is expected to eventually replace `memfs`.

#![cfg_attr(not(test), no_std)]

extern crate alloc;

use alloc::{
borrow::ToOwned,
string::String,
sync::{Arc, Weak},
vec::Vec,
};
use vfs::{Directory, File, FileSystem, Node, NodeKind, SeekFrom};

#[derive(Default)]
pub struct MemoryFileSystem {
root: Arc<MemoryDirectory>,
}

impl MemoryFileSystem {
pub fn new() -> Self {
Self::default()
}
}

impl FileSystem for MemoryFileSystem {
fn root(&self) -> Arc<dyn Directory> {
Arc::clone(&self.root) as Arc<dyn Directory>
}
}

#[derive(Debug, Default)]
pub struct MemoryDirectory {
name: spin::Mutex<String>,
parent: Option<Weak<MemoryDirectory>>,
nodes: spin::Mutex<Vec<DirectoryEntry>>,
}

impl Node for MemoryDirectory {
fn name(&self) -> String {
self.name.lock().clone()
}

fn set_name(&self, name: String) {
*self.name.lock() = name;
}

fn parent(&self) -> Option<Arc<dyn Directory>> {
match self.parent {
Some(ref parent) => Some(Weak::clone(parent).upgrade()? as Arc<dyn Directory>),
None => None,
}
}

fn as_specific(self: Arc<Self>) -> NodeKind {
NodeKind::Directory(self)
}
}

impl Directory for MemoryDirectory {
fn get_entry(&self, name: &str) -> Option<Arc<dyn Node>> {
self.nodes
.lock()
.iter()
.find(|node| node.name() == name)
.cloned()
.map(|node| node.into_node())
}

fn insert_file_entry(self: Arc<Self>, name: &str) -> Result<Arc<dyn File>, &'static str> {
let file = Arc::new(MemoryFile {
name: spin::Mutex::new(name.to_owned()),
parent: Arc::downgrade(&self),
..Default::default()
});

let mut nodes = self.nodes.lock();
if nodes.iter().any(|node| node.name() == name) {
return Err("node with same name already exists");
}
nodes.push(DirectoryEntry::File(Arc::clone(&file)));

Ok(file)
}

fn insert_directory_entry(
self: Arc<Self>,
name: &str,
) -> Result<Arc<dyn Directory>, &'static str> {
let dir = Arc::new(MemoryDirectory {
name: spin::Mutex::new(name.to_owned()),
parent: Some(Arc::downgrade(&self)),
..Default::default()
});

let mut nodes = self.nodes.lock();
if nodes.iter().any(|node| node.name() == name) {
return Err("node with same name already exists");
}
nodes.push(DirectoryEntry::Directory(Arc::clone(&dir)));

Ok(dir)
}

fn remove_entry(self: Arc<Self>, name: &str) -> Option<Arc<dyn Node>> {
let mut nodes = self.nodes.lock();
let index = nodes.iter().position(|node| node.name() == name)?;
Some(nodes.remove(index).into_node())
}

fn list(&self) -> Vec<Arc<dyn Node>> {
self.nodes.lock().clone().into_iter().map(|entry| entry.into_node()).collect()
}
}

#[derive(Clone, Debug)]
enum DirectoryEntry {
Directory(Arc<MemoryDirectory>),
File(Arc<MemoryFile>),
}

impl DirectoryEntry {
fn name(&self) -> String {
match self {
DirectoryEntry::Directory(dir) => dir.name(),
DirectoryEntry::File(file) => file.name(),
}
}

fn into_node(self) -> Arc<dyn Node> {
match self {
DirectoryEntry::Directory(dir) => dir,
DirectoryEntry::File(file) => file,
}
}
}

#[derive(Debug, Default)]
pub struct MemoryFile {
name: spin::Mutex<String>,
parent: Weak<MemoryDirectory>,
state: spin::Mutex<FileState>,
}

#[derive(Debug, Default)]
struct FileState {
offset: usize,
data: Vec<u8>,
}

impl Node for MemoryFile {
fn name(&self) -> String {
self.name.lock().clone()
}

fn set_name(&self, name: String) {
*self.name.lock() = name;
}

fn parent(&self) -> Option<Arc<dyn Directory>> {
Some(Weak::clone(&self.parent).upgrade()? as Arc<dyn Directory>)
}

fn as_specific(self: Arc<Self>) -> NodeKind {
NodeKind::File(self)
}
}

impl File for MemoryFile {
fn read(&self, buffer: &mut [u8]) -> Result<usize, &'static str> {
let mut state = self.state.lock();
let read_len = core::cmp::min(state.data.len() - state.offset, buffer.len());

buffer[..read_len].copy_from_slice(&state.data[state.offset..(state.offset + read_len)]);

state.offset += read_len;
Ok(read_len)
}

fn write(&self, buffer: &[u8]) -> Result<usize, &'static str> {
let mut state = self.state.lock();
let write_len = core::cmp::min(state.data.len() - state.offset, buffer.len());

let offset = state.offset;
state.data[offset..(offset + write_len)].clone_from_slice(&buffer[..write_len]);

if write_len < buffer.len() {
state.data.extend(&buffer[write_len..]);
}

state.offset += buffer.len();
Ok(buffer.len())
}

fn seek(&self, offset: SeekFrom) -> Result<usize, &'static str> {
let mut state = self.state.lock();
state.offset = match offset {
SeekFrom::Start(o) => o,
SeekFrom::Current(o) => (o + state.offset as isize)
.try_into()
.map_err(|_| "tried to seek to negative offset")?,
SeekFrom::End(o) => (state.data.len() as isize + o)
.try_into()
.map_err(|_| "tried to seek to negative offset")?,
};
Ok(state.offset)
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_fs_path_resolution() {
let fs = MemoryFileSystem::new();

let temp_dir = fs.root().insert_directory("temp".into()).unwrap();
let foo_file = Arc::clone(&temp_dir).insert_file("foo".into()).unwrap();
let bar_file = fs.root().insert_file("bar".into()).unwrap();

// We only compare the data pointers of the dynamic object.
// https://rust-lang.github.io/rust-clippy/master/index.html#vtable_address_comparisons
macro_rules! dyn_cmp {
($left:expr, $right:expr) => {
assert_eq!(Arc::as_ptr(&$left) as *const (), Arc::as_ptr(&$right) as *const ());
};
}

dyn_cmp!(fs.root(), fs.root().get_directory(".".into()).unwrap());
dyn_cmp!(fs.root(), fs.root().get_directory(".////.//./".into()).unwrap());

dyn_cmp!(temp_dir, fs.root().get_directory("temp".into()).unwrap());
dyn_cmp!(temp_dir, temp_dir.clone().get_directory(".".into()).unwrap());

dyn_cmp!(foo_file, fs.root().get_file("temp/foo".into()).unwrap());
dyn_cmp!(foo_file, temp_dir.clone().get_file("foo".into()).unwrap());

dyn_cmp!(bar_file, fs.root().get_file("bar".into()).unwrap());
dyn_cmp!(bar_file, temp_dir.get_file("../bar".into()).unwrap());
}

#[test]
fn test_files() {
let fs = MemoryFileSystem::new();
let foo_file = fs.root().insert_file("foo".into()).unwrap();

assert_eq!(foo_file.write(&[0, 1, 2, 3, 4, 5]), Ok(6));

let mut buf = [0; 1];

assert_eq!(foo_file.seek(SeekFrom::Current(-1)), Ok(5));
assert_eq!(foo_file.read(&mut buf), Ok(1));
assert_eq!(buf, [5]);

assert_eq!(foo_file.seek(SeekFrom::Start(1)), Ok(1));
assert_eq!(foo_file.read(&mut buf), Ok(1));
assert_eq!(buf, [1]);

// Offset is at two now.

assert_eq!(foo_file.seek(SeekFrom::Current(2)), Ok(4));
assert_eq!(foo_file.read(&mut buf), Ok(1));
assert_eq!(buf, [4]);

assert_eq!(foo_file.seek(SeekFrom::End(-3)), Ok(3));
assert_eq!(foo_file.read(&mut buf), Ok(1));
assert_eq!(buf, [3]);
}
}
8 changes: 8 additions & 0 deletions kernel/vfs/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[package]
name = "vfs"
version = "0.1.0"
description = "File system abstractions"
edition = "2021"

[dependencies]
spin = "0.9.0"
74 changes: 74 additions & 0 deletions kernel/vfs/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
//! File system abstractions for Theseus.
//!
//! These are expected to eventually replace `fs_node`.
//!
//! Theseus uses forest mounting to keep track of different file systems. The
//! differences between forest mounting and tree mounting are explained below:
//!
//! - Forest mounting (i.e. Windows flavour): There are multiple directory tree
//! structures, with each one being a contained file system. For example,
//! Windows' drives (e.g. `C:`). Technically Windows does support folder mount
//! points but we're ignoring that for the sake of simplicity.
//! - Tree mounting (i.e. Unix flavour): There is a single directory tree
//! structure originiating in the root directory. Filesystems are mounted to
//! subdirectories of the root.
//!
//! Theseus file systems are similar to Windows drives, except they use an
//! arbitrary string identifier rather than a letter.

#![cfg_attr(not(test), no_std)]
#![feature(trait_upcasting)]
#![allow(incomplete_features)]

extern crate alloc;

use alloc::{string::String, sync::Arc, vec::Vec};
use spin::RwLock;

mod node;
mod path;

pub use path::*;
pub use node::*;

/// The currently mounted file systems.
///
/// We use a [`Vec`] rather than a [`HashMap`] because it's more performant when
/// the number of entries is in the tens. It also avoids the indirection of
/// `lazy_static`.
static FILE_SYSTEMS: RwLock<Vec<(String, Arc<dyn FileSystem>)>> = RwLock::new(Vec::new());

/// A file system.
pub trait FileSystem: Send + Sync {
/// Returns the root directory.
fn root(&self) -> Arc<dyn Directory>;
}

/// Returns the file system with the specified `id`.
pub fn file_system(id: &str) -> Option<Arc<dyn FileSystem>> {
FILE_SYSTEMS.read().iter().find(|s| s.0 == id).map(|(_, fs)| fs).cloned()
}

/// Mounts a file system.
///
/// # Errors
///
/// Returns an error if a file system with the specified `id` already exists.
#[allow(clippy::result_unit_err)]
pub fn mount_file_system(id: String, fs: Arc<dyn FileSystem>) -> Result<(), ()> {
let mut file_systems = FILE_SYSTEMS.write();
if file_systems.iter().any(|s| s.0 == id) {
return Err(());
};
file_systems.push((id, fs));
Ok(())
}

/// Unmounts the file system with the specified `id`.
///
/// Returns the file system if it exists.
pub fn unmount_file_system(id: &str) -> Option<Arc<dyn FileSystem>> {
let mut file_systems = FILE_SYSTEMS.write();
let index = file_systems.iter().position(|s| s.0 == id)?;
Some(file_systems.remove(index).1)
tsoutsman marked this conversation as resolved.
Show resolved Hide resolved
}
Loading