Skip to content
This repository has been archived by the owner on Mar 15, 2024. It is now read-only.

Commit

Permalink
feat: implementation of pathname_resolution
Browse files Browse the repository at this point in the history
  • Loading branch information
RatCornu committed Oct 23, 2023
1 parent 369d51b commit 66ad0d2
Show file tree
Hide file tree
Showing 8 changed files with 632 additions and 107 deletions.
11 changes: 9 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
[package]
name = "efs"
version = "0.1.0"
edition = "2021"
authors = ["RatCornu <[email protected]>"]
license = "MIT OR Apache-2.0"
edition = "2021"
description = "An OS and architecture independent implementation of some Unix filesystems in Rust."
documentation = "https://ratcornu.github.io/efs/"
readme = "README.md"
homepage = "https://github.com/RatCornu/efs"
repository = "https://github.com/RatCornu/efs"
license = "MIT OR Apache-2.0"
keywords = ["filesystem", "no-std"]
categories = ["filesystem", "no-std"]

[lib]
name = "efs"
path = "src/lib.rs"

[dependencies]
derive_more = "0.99"
itertools = { version = "0.11", default-features = false, features = ["use_alloc"] }
no_std_io = "0.6.0"
once_cell = "1"
regex = "1"
Expand Down
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
[![Build][build-badge]][build-link]
[![Documentation][documentation-badge]][documentation-link]
[![crates.io-badge]][crates.io-link]

[build-badge]: https://github.com/RatCornu/efs/workflows/Build/badge.svg
[documentation-badge]: https://github.com/RatCornu/efs/workflows/Documentation/badge.svg

[build-link]: https://github.com/RatCornu/efs/actions?query=workflow:"Build"

[documentation-badge]: https://github.com/RatCornu/efs/workflows/Documentation/badge.svg
[documentation-link]: https://github.com/RatCornu/efs/actions?query=workflow:"Documentation"

[crates.io-badge]: https://img.shields.io/crates/v/efs.svg
[crates.io-link]: https://crates.io/crates/efs

# Extended fs

An OS and architecture independent implementation of some Unix filesystems in Rust.
1 change: 1 addition & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
buildInputs = with pkgs; [
rust
cargo
cargo-deny
clippy
rustfmt
alejandra
Expand Down
36 changes: 36 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//! Interface for `efs` possible errors

use core::error;
use core::fmt::{self, Display};

use no_std_io::io;

use crate::fs::FsError;
use crate::path::PathError;

/// Enumeration of possible sources of error
#[allow(clippy::error_impl_error)]
#[derive(Debug)]
pub enum Error {
/// I/O error
IO(io::ErrorKind),

/// Path error
Path(PathError),

/// Filesystem error
Fs(FsError),
}

impl Display for Error {
#[inline]
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::IO(io_error) => write!(formatter, "I/O Error: {io_error:?}"),
Self::Path(path_error) => write!(formatter, "Path Error: {path_error}"),
Self::Fs(fs_error) => write!(formatter, "Filesystem Error: {fs_error}"),
}
}
}

impl error::Error for Error {}
37 changes: 33 additions & 4 deletions src/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,17 @@
//! See [this Wikipedia page](https://en.wikipedia.org/wiki/Unix_file_types) and [the POSIX header of `<sys/stat.h>`](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/sys_stat.h.html) for more informations.

use alloc::boxed::Box;
use alloc::string::String;
use alloc::vec::Vec;

use no_std_io::io::{Read, Seek, Write};

use crate::path::{UnixStr, CUR_DIR, PARENT_DIR};
use crate::types::{Blkcnt, Blksize, Dev, Gid, Ino, Mode, Nlink, Off, Timespec, Uid};

/// Minimal stat structure
///
/// More informations on [the POSIX definition](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/sys_stat.h.html#tag_13_62)
#[derive(Debug, Clone)]
pub struct Stat {
/// Device ID of device containing file
pub st_dev: Dev,
Expand Down Expand Up @@ -73,14 +74,14 @@ pub trait Regular: File + Read + Write + Seek {}
/// An object that associates a filename with a file. Several directory entries can associate names with the same file.
///
/// Defined in [this POSIX definition](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_130).
pub struct DirectoryEntry {
pub struct DirectoryEntry<'path> {
/// Name of the file pointed by this directory entry
///
/// See more informations on valid filenames in [this POSIX definition](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_170).
pub filename: String,
pub filename: UnixStr<'path>,

/// File pointed by this directory entry.
pub file: Box<dyn File>,
pub file: Type,
}

/// A file that contains directory entries. No two directory entries in the same directory have the same name.
Expand All @@ -93,6 +94,34 @@ pub trait Directory: File {
///
/// The result must contain at least the entries `.` (the current directory) and `..` (the parent directory).
fn entries(&self) -> Vec<DirectoryEntry>;

/// Returns the entry with the given name.
#[inline]
fn entry(&self, name: UnixStr) -> Option<Type> {
let children = self.entries();
children.into_iter().find(|entry| entry.filename == name).map(|entry| entry.file)
}

/// Returns the parent directory.
///
/// If `self` if the root directory, it must return itself.
#[inline]
fn parent(&self) -> Box<dyn Directory> {
let Some(Type::Directory(parent_entry)) = self.entry(PARENT_DIR.clone()) else {
unreachable!("`entries` must return `..` that corresponds to the parent directory.")
};
parent_entry
}
}

impl Clone for Box<dyn Directory> {
#[inline]
fn clone(&self) -> Self {
let Some(Type::Directory(parent_entry)) = self.entry(CUR_DIR.clone()) else {
unreachable!("`entries` must return `.` that corresponds to the current directory.")
};
parent_entry
}
}

/// A type of file with the property that when the file is encountered during pathname resolution, a string stored by the file is
Expand Down
186 changes: 179 additions & 7 deletions src/fs.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,198 @@
//! General interface for filesystems

use alloc::borrow::ToOwned;
use alloc::boxed::Box;
use alloc::string::{String, ToString};
use alloc::vec;
use alloc::vec::Vec;
use core::fmt::{self, Display};
use core::str::FromStr;

use no_std_io::io::Result;
use itertools::{Itertools, Position};
use no_std_io::io;

use crate::file::{Directory, File};
use crate::path::Path;
use crate::error::Error;
use crate::file::{Directory, Type};
use crate::path::{Component, Path};

/// Maximal length for a path.
///
/// This is defined in [this POSIX definition](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_13).
///
/// This value is the same as the one defined in [the linux's `limits.h` header](https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/include/uapi/linux/limits.h?h=v6.5.8#n13).
pub const PATH_MAX: usize = 4_096;

/// Enumeration of possible errors encountered with [`FileSystem`]s' manipulation.
#[allow(clippy::module_name_repetitions)]
#[derive(Debug)]
pub enum FsError {
/// Indicates that the given [`Path`] is too long to be resolved
NameTooLong(String),

/// Indicates that the given filename is not a [`Directory`]
NotDir(String),

/// Indicates that the given filename is not a [`Directory`]
NoEnt(String),

/// Indicates that a loop has been encountered during the given path resolution
Loop(String),
}

impl Display for FsError {
#[inline]
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Loop(path) => write!(formatter, "Loop: a loop has been encountered during the resolution of \"{path}\""),
Self::NameTooLong(path) => write!(formatter, "Name too long: \"{path}\" is too long to be resolved"),
Self::NotDir(filename) => write!(formatter, "Not a Directory: \"{filename}\" is not a directory"),
Self::NoEnt(filename) => write!(formatter, "No entry: \"{filename}\" is an symbolic link pointing at an empty string"),
}
}
}

/// A filesystem
pub trait FileSystem {
/// Returns the root directory of the filesystem.
fn root(&self) -> Box<dyn Directory>;

/// Returns the double slash root directory of the filesystem.
///
/// If you do not have any idea of what this is, you are probably looking for [`root`](trait.FileSystem.html#tymethod.root).
///
/// See [`Component::DoubleSlashRootDir`] and [`Path`] for more informations.
fn double_slash_root(&self) -> Box<dyn Directory>;

/// Performs a pathname resolution as described in [this POSIX definition](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_13).
///
/// Returns the file of this filesystem corresponding to the given [`Path`].
/// Returns the file of this filesystem corresponding to the given `path`, starting at the `current_dir`.
///
/// `symlink_resolution` indicates whether the function calling this method is required to act on the symbolic link itself, or
/// certain arguments direct that the function act on the symbolic link itself.
///
/// # Errors
///
/// Returns an [`NotFound`](no_std_io::io::ErrorKind) if the given path does not leed to an existing path.
/// Returns an [`NotFound`](no_std_io::io::ErrorKind) error if the given path does not leed to an existing path.
///
/// Returns an [`NotDir`](enum.FsError.html#variant.NotDir) error if one of the components of the file is not a directory.
#[inline]
fn pathname_resolution(&self, _path: &Path) -> Result<Box<dyn File>> {
todo!()
fn pathname_resolution(&self, path: &Path, current_dir: Box<dyn Directory>, symlink_resolution: bool) -> Result<Type, Error>
where
Self: Sized,
{
/// Auxiliary function used to store the visited symlinks during the pathname resolution to detect loops caused bt symbolic
/// links.
#[inline]
fn inner_resolution(
fs: &impl FileSystem,
path: &Path,
mut current_dir: Box<dyn Directory>,
symlink_resolution: bool,
mut visited_symlinks: Vec<String>,
) -> Result<Type, Error> {
let canonical_path = path.canonical();
let trailing_blackslash = canonical_path.as_unix_str().has_trailing_backslash();
let mut symlink_encountered = None;

let mut components = canonical_path.components();

for (pos, comp) in components.with_position() {
match comp {
Component::RootDir => {
if pos == Position::First || pos == Position::Only {
current_dir = fs.root();
} else {
unreachable!("The root directory cannot be encountered during the pathname resolution");
}
},
Component::DoubleSlashRootDir => {
if pos == Position::First || pos == Position::Only {
current_dir = fs.double_slash_root();
} else {
unreachable!("The double slash root directory cannot be encountered during the pathname resolution");
}
},
Component::CurDir => {},
Component::ParentDir => {
current_dir = current_dir.parent();
},
Component::Normal(filename) => {
let children = current_dir.entries();
let Some(entry) = children.into_iter().find(|entry| entry.filename == filename).map(|entry| entry.file)
else {
return Err(Error::IO(io::ErrorKind::NotFound));
};

#[allow(clippy::wildcard_enum_match_arm)]
match entry {
Type::Directory(dir) => {
current_dir = dir;
},
// This case is the symbolic link resolution, which is the one described as **not** being the one
// explained in the following paragraph from the POSIX definition of the
// pathname resolution:
//
// If a symbolic link is encountered during pathname resolution, the behavior shall depend on whether
// the pathname component is at the end of the pathname and on the function
// being performed. If all of the following are true, then pathname
// resolution is complete:
// 1. This is the last pathname component of the pathname.
// 2. The pathname has no trailing <slash>.
// 3. The function is required to act on the symbolic link itself, or certain arguments direct that
// the
// function act on the symbolic link itself.
Type::SymbolicLink(symlink)
if (pos != Position::Last && pos != Position::Only)
|| !trailing_blackslash
|| !symlink_resolution =>
{
let pointed_file = symlink.pointed_file().to_owned();
if pointed_file.is_empty() {
return Err(Error::Fs(FsError::NoEnt(filename.to_string())));
};

symlink_encountered = Some(pointed_file);
break;
},
_ => {
return if (pos == Position::Last || pos == Position::Only) && !trailing_blackslash {
Ok(entry)
} else {
Err(Error::Fs(FsError::NotDir(filename.to_string())))
};
},
}
},
}
}

match symlink_encountered {
None => Ok(Type::Directory(current_dir)),
Some(pointed_file) => {
if visited_symlinks.contains(&pointed_file) {
return Err(Error::Fs(FsError::Loop(pointed_file)));
}
visited_symlinks.push(pointed_file.clone());

let pointed_path = match Path::from_str(&pointed_file) {
Ok(path) => path,
Err(path_error) => return Err(Error::Path(path_error)),
};

let complete_path = match TryInto::<Path>::try_into(&components) {
Ok(remaining_path) => pointed_path.join(&remaining_path),
Err(_) => pointed_path,
};

if complete_path.len() >= PATH_MAX {
Err(Error::Fs(FsError::NameTooLong(complete_path.to_string())))
} else {
inner_resolution(fs, &complete_path, current_dir, symlink_resolution, visited_symlinks)
}
},
}
}

inner_resolution(self, path, current_dir, symlink_resolution, vec![])
}
}
4 changes: 4 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
clippy::shadow_unrelated,
clippy::todo,
clippy::unreachable,
clippy::use_debug,
clippy::unwrap_in_result,
clippy::wildcard_in_or_patterns,
const_item_mutation
Expand All @@ -54,12 +55,15 @@
)
)]
#![feature(const_mut_refs)]
#![feature(error_in_core)]
#![feature(iter_advance_by)]
#![feature(let_chains)]
#![feature(trait_upcasting)]

extern crate alloc;
extern crate core;

pub mod error;
pub mod file;
pub mod fs;
pub mod path;
Expand Down
Loading

0 comments on commit 66ad0d2

Please sign in to comment.