From 24cff8455b014bcc5abfe74294910b2c06eb397d Mon Sep 17 00:00:00 2001 From: RatCornu Date: Fri, 26 Jan 2024 16:10:18 +0100 Subject: [PATCH] feat(fs): update file traits to support write operations --- src/dev/mod.rs | 2 +- src/file.rs | 41 +++++++++++++--- src/fs/error.rs | 4 ++ src/fs/ext2/block.rs | 2 +- src/fs/ext2/file.rs | 108 ++++++++++++++++++++++++++++++++----------- src/fs/mod.rs | 12 ++--- src/io.rs | 24 +++++----- 7 files changed, 137 insertions(+), 56 deletions(-) diff --git a/src/dev/mod.rs b/src/dev/mod.rs index 7b847d3..83e3322 100644 --- a/src/dev/mod.rs +++ b/src/dev/mod.rs @@ -330,7 +330,7 @@ impl_device!(&mut [T]); impl_device!(Vec); impl_device!(Box<[T]>); -impl + Read + Write + Seek> Device for RefCell { +impl + Read + Write + Seek> Device for RefCell { #[inline] fn size(&self) -> Size { let mut device = self.borrow_mut(); diff --git a/src/file.rs b/src/file.rs index 34cae21..47c5fb9 100644 --- a/src/file.rs +++ b/src/file.rs @@ -103,13 +103,13 @@ pub struct DirectoryEntry<'path, Dir: Directory> { /// Defined in [this POSIX definition](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_129). pub trait Directory: Sized + File { /// Type of the regular files in the [`FileSystem`](crate::fs::FileSystem) this directory belongs to. - type Regular: Regular; + type Regular: Regular; /// Type of the symbolic links in the [`FileSystem`](crate::fs::FileSystem) this directory belongs to. - type SymbolicLink: SymbolicLink; + type SymbolicLink: SymbolicLink; /// Type of the other files (if any) in the [`FileSystem`](crate::fs::FileSystem) this directory belongs to. - type File: File; + type File: File; /// Returns the directory entries contained. /// @@ -120,9 +120,27 @@ pub trait Directory: Sized + File { /// # Errors /// /// Returns an [`DevError`](crate::dev::error::DevError) if the device on which the directory is located could not be read. - #[allow(clippy::type_complexity)] fn entries(&self) -> Result>, Error>; + /// Adds a new empty entry to the directory, meaning that a new file will be created. + /// + /// # Errors + /// + /// Returns an [`EntryAlreadyExist`](crate::fs::error::FsError::EntryAlreadyExist) error if the entry already exist. + /// + /// Returns an [`DevError`](crate::dev::error::DevError) if the device on which the directory is located could not be written. + fn add_entry(&mut self, entry: DirectoryEntry) -> Result<(), Error>; + + /// Removes an entry from the directory. + /// + /// # Errors + /// + /// Returns an [`NotFound`](crate::fs::error::FsError::NotFound) error if there is no entry with the given name in this + /// directory. + /// + /// Returns an [`DevError`](crate::dev::error::DevError) if the device on which the directory is located could not be written. + fn remove_entry(&mut self, name: UnixStr) -> Result<(), Error>; + /// Returns the entry with the given name. /// /// # Errors @@ -156,8 +174,19 @@ pub trait Directory: Sized + File { /// /// Defined in [this POSIX definition](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_381). pub trait SymbolicLink: File { - /// Returns the string stored in this symbolic link - fn pointed_file(&self) -> &str; + /// Returns the string stored in this symbolic link. + /// + /// # Errors + /// + /// Returns an [`DevError`](crate::dev::error::DevError) if the device on which the directory is located could not be read. + fn get_pointed_file(&self) -> Result<&str, Error>; + + /// Sets the pointed file in this symbolic link. + /// + /// # Errors + /// + /// Returns an [`DevError`](crate::dev::error::DevError) if the device on which the directory is located could not be written. + fn set_pointed_file(&mut self, pointed_file: &str) -> Result<(), Error>; } /// Enumeration of possible file types in a standard UNIX-like filesystem. diff --git a/src/fs/error.rs b/src/fs/error.rs index c08294f..2411753 100644 --- a/src/fs/error.rs +++ b/src/fs/error.rs @@ -10,6 +10,9 @@ use crate::file::Type; #[allow(clippy::module_name_repetitions)] #[derive(Debug)] pub enum FsError { + /// Indicates that the given [`File`](crate::file::File) already exist in the given directory. + EntryAlreadyExist(String), + /// Indicates that the given [`Path`](crate::path::Path) is too long to be resolved. NameTooLong(String), @@ -38,6 +41,7 @@ impl Display for FsError { #[inline] fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { match self { + Self::EntryAlreadyExist(file) => write!(formatter, "Entry Already Exist: \"{file}\" already exist in given directory"), 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"), diff --git a/src/fs/ext2/block.rs b/src/fs/ext2/block.rs index 2068b90..c6742b6 100644 --- a/src/fs/ext2/block.rs +++ b/src/fs/ext2/block.rs @@ -157,7 +157,7 @@ impl> From> for u32 { } impl> Base for Block { - type Error = Ext2Error; + type IOError = Ext2Error; } impl> Read for Block { diff --git a/src/fs/ext2/file.rs b/src/fs/ext2/file.rs index 54842b2..0a46e0c 100644 --- a/src/fs/ext2/file.rs +++ b/src/fs/ext2/file.rs @@ -1,12 +1,13 @@ //! Interface to manipulate UNIX files on an ext2 filesystem. +use alloc::borrow::ToOwned; use alloc::string::String; use alloc::vec; use alloc::vec::Vec; use core::fmt::Debug; use core::mem::size_of; use core::ops::{AddAssign, SubAssign}; -use core::ptr::addr_of; +use core::ptr::{addr_of, addr_of_mut}; use core::slice::from_raw_parts; use itertools::Itertools; @@ -110,6 +111,35 @@ impl> File { Ok(()) } + + /// General implementation of [`truncate`](file::Regular::truncate) for ext2's [`File`]. + /// + /// # Errors + /// + /// Same as [`truncate`](file::Regular::truncate). + #[inline] + pub fn truncate(&mut self, size: u64) -> Result<(), Error> { + if u64::from(self.inode.size) <= size { + return Ok(()); + } + + let fs = self.filesystem.borrow(); + + let mut new_inode = self.inode; + // SAFETY: the result is smaller than `u32::MAX` + new_inode.size = unsafe { u32::try_from(u64::from(u32::MAX) & size).unwrap_unchecked() }; + + let starting_addr = Inode::starting_addr(&fs.device, fs.superblock(), self.inode_number)?; + + // SAFETY: this writes an inode at the starting address of the inode + unsafe { + fs.device.borrow_mut().write_at(starting_addr, new_inode)?; + }; + + drop(fs); + + self.update_inner_inode() + } } impl> file::File for File { @@ -160,12 +190,12 @@ pub struct Regular> { } impl> Base for File { - type Error = Ext2Error; + type IOError = Ext2Error; } impl> Read for File { #[inline] - fn read(&mut self, buf: &mut [u8]) -> Result> { + fn read(&mut self, buf: &mut [u8]) -> Result> { let filesystem = self.filesystem.borrow(); self.inode .read_data(&filesystem.device, &filesystem.superblock, buf, self.io_offset) @@ -203,7 +233,7 @@ impl> Write for File { #[inline] #[allow(clippy::too_many_lines)] #[allow(clippy::cognitive_complexity)] // TODO: make this understandable for a human - fn write(&mut self, buf: &[u8]) -> Result> { + fn write(&mut self, buf: &[u8]) -> Result> { /// Writes the given `blocks` number in the indirect block with the number `block_number`. fn write_indirect_block>( filesystem: &Celled>, @@ -783,7 +813,7 @@ impl> file::File for Regular { } impl> Base for Regular { - type Error = Ext2Error; + type IOError = Ext2Error; } impl> Read for Regular { @@ -815,26 +845,7 @@ impl> Seek for Regular { impl> file::Regular for Regular { #[inline] fn truncate(&mut self, size: u64) -> Result<(), Error<::Error>> { - if u64::from(self.file.inode.size) <= size { - return Ok(()); - } - - let fs = self.file.filesystem.borrow(); - - let mut new_inode = self.file.inode; - // SAFETY: the result is smaller than `u32::MAX` - new_inode.size = unsafe { u32::try_from(u64::from(u32::MAX) & size).unwrap_unchecked() }; - - let starting_addr = Inode::starting_addr(&fs.device, fs.superblock(), self.file.inode_number)?; - - // SAFETY: this writes an inode at the starting address of the inode - unsafe { - fs.device.borrow_mut().write_at(starting_addr, new_inode)?; - }; - - drop(fs); - - self.file.update_inner_inode() + self.file.truncate(size) } } @@ -908,6 +919,16 @@ impl> file::Directory for Directory { Ok(entries) } + + #[inline] + fn add_entry(&mut self, entry: DirectoryEntry) -> Result<(), Error> { + todo!() + } + + #[inline] + fn remove_entry(&mut self, entry_name: crate::path::UnixStr) -> Result<(), Error> { + todo!() + } } /// Interface for ext2's symbolic links. @@ -972,8 +993,41 @@ impl> file::File for SymbolicLink { impl> file::SymbolicLink for SymbolicLink { #[inline] - fn pointed_file(&self) -> &str { - &self.pointed_file + fn get_pointed_file(&self) -> Result<&str, Error> { + Ok(&self.pointed_file) + } + + #[inline] + fn set_pointed_file(&mut self, pointed_file: &str) -> Result<(), Error> { + let bytes = pointed_file.as_bytes(); + + if bytes.len() > PATH_MAX { + Err(Error::Fs(FsError::NameTooLong(pointed_file.to_owned()))) + } else if bytes.len() > SYMBOLIC_LINK_INODE_STORE_LIMIT { + self.file.seek(SeekFrom::Start(0))?; + self.file.write(bytes)?; + self.file.truncate(bytes.len() as u64) + } else { + let mut new_inode = self.file.inode; + // SAFETY: `bytes.len() < PATH_MAX << u32::MAX` + new_inode.size = unsafe { u32::try_from(bytes.len()).unwrap_unchecked() }; + + let data_ptr = addr_of_mut!(new_inode.blocks).cast::(); + // SAFETY: there are `SYMBOLIC_LINK_INODE_STORE_LIMIT` bytes available to store the data + let data_slice = unsafe { core::slice::from_raw_parts_mut(data_ptr, bytes.len()) }; + data_slice.clone_from_slice(bytes); + + let fs = self.file.filesystem.borrow(); + let starting_addr = Inode::starting_addr(&fs.device, fs.superblock(), self.file.inode_number)?; + // SAFETY: the starting address correspond to the one of this inode + unsafe { + fs.device.borrow_mut().write_at(starting_addr, new_inode)?; + }; + + drop(fs); + + self.file.update_inner_inode() + } } } diff --git a/src/fs/mod.rs b/src/fs/mod.rs index 5bb7e25..259e0ea 100644 --- a/src/fs/mod.rs +++ b/src/fs/mod.rs @@ -9,7 +9,7 @@ use core::str::FromStr; use itertools::{Itertools, Position}; use crate::error::Error; -use crate::file::{Directory, File, Regular, SymbolicLink, TypeWithFile}; +use crate::file::{Directory, SymbolicLink, TypeWithFile}; use crate::fs::error::FsError; use crate::path::{Component, Path}; @@ -71,13 +71,7 @@ pub trait FileSystem { /// Auxiliary function used to store the visited symlinks during the pathname resolution to detect loops caused bt symbolic /// links. #[inline] - fn path_resolution< - E: core::error::Error, - R: Regular, - SL: SymbolicLink, - F: File, - D: Directory, - >( + fn path_resolution>( fs: &impl FileSystem, path: &Path, mut current_dir: D, @@ -145,7 +139,7 @@ pub trait FileSystem { || !trailing_blackslash || !symlink_resolution => { - let pointed_file = symlink.pointed_file().to_owned(); + let pointed_file = symlink.get_pointed_file()?.to_owned(); if pointed_file.is_empty() { return Err(Error::Fs(FsError::NoEnt(filename.to_string()))); }; diff --git a/src/io.rs b/src/io.rs index 6e89245..afd074f 100644 --- a/src/io.rs +++ b/src/io.rs @@ -9,7 +9,7 @@ use crate::error::Error; /// Base I/O trait that must be implemented for all types implementing [`Read`], [`Write`] or [`Seek`]. pub trait Base { /// Error type corresponding to the [`FileSystem`](crate::fs::FileSystem) implemented. - type Error: core::error::Error; + type IOError: core::error::Error; } /// Allows for reading bytes from a source. @@ -27,7 +27,7 @@ pub trait Read: Base { /// # Errors /// /// Returns an [`DevError`] if the device on which the directory is located could not be read. - fn read(&mut self, buf: &mut [u8]) -> Result>; + fn read(&mut self, buf: &mut [u8]) -> Result>; /// Read the exact number of bytes required to fill buf. /// @@ -40,7 +40,7 @@ pub trait Read: Base { /// Otherwise, returns the same errors as [`read`](Read::read). #[allow(clippy::indexing_slicing)] #[inline] - fn read_exact(&mut self, mut buf: &mut [u8]) -> Result<(), Error> { + fn read_exact(&mut self, mut buf: &mut [u8]) -> Result<(), Error> { while !buf.is_empty() { match self.read(buf) { Ok(0) => break, @@ -70,7 +70,7 @@ pub trait Write: Base { /// # Errors /// /// Returns an [`DevError`] if the device on which the directory is located could not be written. - fn write(&mut self, buf: &[u8]) -> Result>; + fn write(&mut self, buf: &[u8]) -> Result>; /// Flush this output stream, ensuring that all intermediately buffered contents reach their destination. /// @@ -79,7 +79,7 @@ pub trait Write: Base { /// # Errors /// /// Returns an [`DevError`] if the device on which the directory is located could not be read. - fn flush(&mut self) -> Result<(), Error>; + fn flush(&mut self) -> Result<(), Error>; /// Attempts to write an entire buffer into this writer. /// @@ -92,7 +92,7 @@ pub trait Write: Base { /// Otherwise, returns the same errors as [`write`](Write::write). #[allow(clippy::indexing_slicing)] #[inline] - fn write_all(&mut self, mut buf: &[u8]) -> Result<(), Error> { + fn write_all(&mut self, mut buf: &[u8]) -> Result<(), Error> { while !buf.is_empty() { match self.write(buf) { Ok(0) => { @@ -160,7 +160,7 @@ pub trait Seek: Base { /// # Errors /// /// Returns an [`DevError`] if the device on which the directory is located could not be read. - fn seek(&mut self, pos: SeekFrom) -> Result>; + fn seek(&mut self, pos: SeekFrom) -> Result>; } /// A wrapper struct for types that have implementations for [`std::io`] traits. @@ -185,13 +185,13 @@ impl StdIOWrapper { #[cfg(any(feature = "std", test))] impl Base for StdIOWrapper { - type Error = std::io::Error; + type IOError = std::io::Error; } #[cfg(any(feature = "std", test))] impl Read for StdIOWrapper { #[inline] - fn read(&mut self, buf: &mut [u8]) -> Result> { + fn read(&mut self, buf: &mut [u8]) -> Result> { self.inner.read(buf).map_err(Error::IO) } } @@ -199,12 +199,12 @@ impl Read for StdIOWrapper { #[cfg(any(feature = "std", test))] impl Write for StdIOWrapper { #[inline] - fn write(&mut self, buf: &[u8]) -> Result> { + fn write(&mut self, buf: &[u8]) -> Result> { self.inner.write(buf).map_err(Error::IO) } #[inline] - fn flush(&mut self) -> Result<(), Error> { + fn flush(&mut self) -> Result<(), Error> { self.inner.flush().map_err(Error::IO) } } @@ -212,7 +212,7 @@ impl Write for StdIOWrapper { #[cfg(any(feature = "std", test))] impl Seek for StdIOWrapper { #[inline] - fn seek(&mut self, pos: SeekFrom) -> Result> { + fn seek(&mut self, pos: SeekFrom) -> Result> { self.inner.seek(pos.into()).map_err(Error::IO) } }