diff --git a/Cargo.lock b/Cargo.lock index b766f63601..d9f6926078 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -140,7 +140,6 @@ dependencies = [ name = "arceos-shell" version = "0.1.0" dependencies = [ - "axfeat", "axfs_ramfs", "axfs_vfs", "axstd", @@ -466,7 +465,6 @@ dependencies = [ "arceos_api", "axerrno", "axfeat", - "axfs", "axio", "spinlock", ] diff --git a/apps/fs/shell/Cargo.toml b/apps/fs/shell/Cargo.toml index de85e19879..bd99c1d11b 100644 --- a/apps/fs/shell/Cargo.toml +++ b/apps/fs/shell/Cargo.toml @@ -7,7 +7,7 @@ authors = ["Yuekai Jia "] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] -use-ramfs = ["axfeat/myfs", "dep:axfs_vfs", "dep:axfs_ramfs", "dep:crate_interface"] +use-ramfs = ["axstd/myfs", "dep:axfs_vfs", "dep:axfs_ramfs", "dep:crate_interface"] default = [] [dependencies] @@ -15,4 +15,3 @@ axfs_vfs = { path = "../../../crates/axfs_vfs", optional = true } axfs_ramfs = { path = "../../../crates/axfs_ramfs", optional = true } crate_interface = { path = "../../../crates/crate_interface", optional = true } axstd = { path = "../../../ulib/axstd", features = ["alloc", "fs"], optional = true } -axfeat = { path = "../../../api/axfeat", optional = true } diff --git a/apps/fs/shell/src/ramfs.rs b/apps/fs/shell/src/ramfs.rs index 07922bb209..450cafaebe 100644 --- a/apps/fs/shell/src/ramfs.rs +++ b/apps/fs/shell/src/ramfs.rs @@ -3,13 +3,13 @@ extern crate alloc; use alloc::sync::Arc; use axfs_ramfs::RamFileSystem; use axfs_vfs::VfsOps; -use std::os::arceos::axfs::fops::{Disk, MyFileSystemIf}; +use std::os::arceos::api::fs::{AxDisk, MyFileSystemIf}; struct MyFileSystemIfImpl; #[crate_interface::impl_interface] impl MyFileSystemIf for MyFileSystemIfImpl { - fn new_myfs(_disk: Disk) -> Arc { + fn new_myfs(_disk: AxDisk) -> Arc { Arc::new(RamFileSystem::new()) } } diff --git a/modules/axfs/src/api/file.rs b/modules/axfs/src/api/file.rs index 0f6b3f8d2a..6941c90cb3 100644 --- a/modules/axfs/src/api/file.rs +++ b/modules/axfs/src/api/file.rs @@ -92,27 +92,33 @@ impl Metadata { /// Returns the size of the file, in bytes, this metadata is for. #[allow(clippy::len_without_is_empty)] - pub fn len(&self) -> u64 { + pub const fn len(&self) -> u64 { self.0.size() } /// Returns the permissions of the file this metadata is for. - pub fn permissions(&self) -> Permissions { + pub const fn permissions(&self) -> Permissions { self.0.perm() } - /// Returns the inner raw metadata [`fops::FileAttr`]. - pub const fn raw_metadata(&self) -> &fops::FileAttr { - &self.0 + /// Returns the total size of this file in bytes. + pub const fn size(&self) -> u64 { + self.0.size() + } + + /// Returns the number of blocks allocated to the file, in 512-byte units. + pub const fn blocks(&self) -> u64 { + self.0.blocks() } } impl fmt::Debug for Metadata { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Metadata") - .field("file_type", &self.0.file_type()) - .field("is_dir", &self.0.is_dir()) - .field("is_file", &self.0.is_file()) + .field("file_type", &self.file_type()) + .field("is_dir", &self.is_dir()) + .field("is_file", &self.is_file()) + .field("permissions", &self.permissions()) .finish_non_exhaustive() } } diff --git a/ulib/axlibc/src/file.rs b/ulib/axlibc/src/file.rs index 26ed7a6abb..78e2a598bc 100644 --- a/ulib/axlibc/src/file.rs +++ b/ulib/axlibc/src/file.rs @@ -40,9 +40,8 @@ impl FileLike for File { fn stat(&self) -> LinuxResult { let metadata = self.0.lock().metadata()?; - let metadata = metadata.raw_metadata(); let ty = metadata.file_type() as u8; - let perm = metadata.perm().bits() as u32; + let perm = metadata.permissions().bits() as u32; let st_mode = ((ty as u32) << 12) | perm; Ok(ctypes::stat { st_ino: 1, diff --git a/ulib/axstd/Cargo.toml b/ulib/axstd/Cargo.toml index 98b7f0df46..f2f8ccb6f4 100644 --- a/ulib/axstd/Cargo.toml +++ b/ulib/axstd/Cargo.toml @@ -42,8 +42,8 @@ sched_rr = ["axfeat/sched_rr"] sched_cfs = ["axfeat/sched_cfs"] # File system -fs = ["dep:axfs", "arceos_api/fs", "axfeat/fs"] -myfs = ["axfeat/myfs"] +fs = ["arceos_api/fs", "axfeat/fs"] +myfs = ["arceos_api/myfs", "axfeat/myfs"] # Networking net = ["arceos_api/net", "axfeat/net"] @@ -68,7 +68,6 @@ log-level-debug = ["axfeat/log-level-debug"] log-level-trace = ["axfeat/log-level-trace"] [dependencies] -axfs = { path = "../../modules/axfs", optional = true } axfeat = { path = "../../api/axfeat" } arceos_api = { path = "../../api/arceos_api" } axio = { path = "../../crates/axio" } diff --git a/ulib/axstd/src/fs.rs b/ulib/axstd/src/fs.rs deleted file mode 100644 index 92e8068a2f..0000000000 --- a/ulib/axstd/src/fs.rs +++ /dev/null @@ -1,5 +0,0 @@ -//! Filesystem manipulation operations. - -pub use axfs::api::{canonicalize, metadata, read, read_to_string, remove_file, write}; -pub use axfs::api::{create_dir, create_dir_all, read_dir, remove_dir, rename}; -pub use axfs::api::{DirEntry, File, FileType, Metadata, OpenOptions, Permissions, ReadDir}; diff --git a/ulib/axstd/src/fs/dir.rs b/ulib/axstd/src/fs/dir.rs new file mode 100644 index 0000000000..bf49cc85a0 --- /dev/null +++ b/ulib/axstd/src/fs/dir.rs @@ -0,0 +1,154 @@ +extern crate alloc; + +use alloc::string::String; +use core::fmt; + +use super::FileType; +use crate::io::Result; + +use arceos_api::fs as api; + +/// Iterator over the entries in a directory. +pub struct ReadDir<'a> { + path: &'a str, + inner: api::AxDirHandle, + buf_pos: usize, + buf_end: usize, + end_of_stream: bool, + dirent_buf: [api::AxDirEntry; 31], +} + +/// Entries returned by the [`ReadDir`] iterator. +pub struct DirEntry<'a> { + dir_path: &'a str, + entry_name: String, + entry_type: FileType, +} + +/// A builder used to create directories in various manners. +#[derive(Default, Debug)] +pub struct DirBuilder { + recursive: bool, +} + +impl<'a> ReadDir<'a> { + pub(super) fn new(path: &'a str) -> Result { + let mut opts = api::AxOpenOptions::new(); + opts.read(true); + let inner = api::ax_open_dir(path, &opts)?; + + const EMPTY: api::AxDirEntry = api::AxDirEntry::default(); + let dirent_buf = [EMPTY; 31]; + Ok(ReadDir { + path, + inner, + end_of_stream: false, + buf_pos: 0, + buf_end: 0, + dirent_buf, + }) + } +} + +impl<'a> Iterator for ReadDir<'a> { + type Item = Result>; + + fn next(&mut self) -> Option>> { + if self.end_of_stream { + return None; + } + + loop { + if self.buf_pos >= self.buf_end { + match api::ax_read_dir(&mut self.inner, &mut self.dirent_buf) { + Ok(n) => { + if n == 0 { + self.end_of_stream = true; + return None; + } + self.buf_pos = 0; + self.buf_end = n; + } + Err(e) => { + self.end_of_stream = true; + return Some(Err(e)); + } + } + } + let entry = &self.dirent_buf[self.buf_pos]; + self.buf_pos += 1; + let name_bytes = entry.name_as_bytes(); + if name_bytes == b"." || name_bytes == b".." { + continue; + } + let entry_name = unsafe { core::str::from_utf8_unchecked(name_bytes).into() }; + let entry_type = entry.entry_type(); + + return Some(Ok(DirEntry { + dir_path: self.path, + entry_name, + entry_type, + })); + } + } +} + +impl<'a> DirEntry<'a> { + /// Returns the full path to the file that this entry represents. + /// + /// The full path is created by joining the original path to `read_dir` + /// with the filename of this entry. + pub fn path(&self) -> String { + String::from(self.dir_path.trim_end_matches('/')) + "/" + &self.entry_name + } + + /// Returns the bare file name of this directory entry without any other + /// leading path component. + pub fn file_name(&self) -> String { + self.entry_name.clone() + } + + /// Returns the file type for the file that this entry points at. + pub fn file_type(&self) -> FileType { + self.entry_type + } +} + +impl fmt::Debug for DirEntry<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("DirEntry").field(&self.path()).finish() + } +} + +impl DirBuilder { + /// Creates a new set of options with default mode/security settings for all + /// platforms and also non-recursive. + pub fn new() -> Self { + Self { recursive: false } + } + + /// Indicates that directories should be created recursively, creating all + /// parent directories. Parents that do not exist are created with the same + /// security and permissions settings. + pub fn recursive(&mut self, recursive: bool) -> &mut Self { + self.recursive = recursive; + self + } + + /// Creates the specified directory with the options configured in this + /// builder. + pub fn create(&self, path: &str) -> Result<()> { + if self.recursive { + self.create_dir_all(path) + } else { + api::ax_create_dir(path) + } + } + + fn create_dir_all(&self, _path: &str) -> Result<()> { + axerrno::ax_err!( + Unsupported, + "Recursive directory creation is not supported yet" + ) + } +} diff --git a/ulib/axstd/src/fs/file.rs b/ulib/axstd/src/fs/file.rs new file mode 100644 index 0000000000..6984057cca --- /dev/null +++ b/ulib/axstd/src/fs/file.rs @@ -0,0 +1,187 @@ +use crate::io::{prelude::*, Result, SeekFrom}; +use core::fmt; + +use arceos_api::fs as api; + +/// A structure representing a type of file with accessors for each file type. +/// It is returned by [`Metadata::file_type`] method. +pub type FileType = api::AxFileType; + +/// Representation of the various permissions on a file. +pub type Permissions = api::AxFilePerm; + +/// An object providing access to an open file on the filesystem. +pub struct File { + inner: api::AxFileHandle, +} + +/// Metadata information about a file. +pub struct Metadata(api::AxFileAttr); + +/// Options and flags which can be used to configure how a file is opened. +#[derive(Clone, Debug)] +pub struct OpenOptions(api::AxOpenOptions); + +impl OpenOptions { + /// Creates a blank new set of options ready for configuration. + pub const fn new() -> Self { + OpenOptions(api::AxOpenOptions::new()) + } + + /// Sets the option for read access. + pub fn read(&mut self, read: bool) -> &mut Self { + self.0.read(read); + self + } + + /// Sets the option for write access. + pub fn write(&mut self, write: bool) -> &mut Self { + self.0.write(write); + self + } + + /// Sets the option for the append mode. + pub fn append(&mut self, append: bool) -> &mut Self { + self.0.append(append); + self + } + + /// Sets the option for truncating a previous file. + pub fn truncate(&mut self, truncate: bool) -> &mut Self { + self.0.truncate(truncate); + self + } + + /// Sets the option to create a new file, or open it if it already exists. + pub fn create(&mut self, create: bool) -> &mut Self { + self.0.create(create); + self + } + + /// Sets the option to create a new file, failing if it already exists. + pub fn create_new(&mut self, create_new: bool) -> &mut Self { + self.0.create_new(create_new); + self + } + + /// Opens a file at `path` with the options specified by `self`. + pub fn open(&self, path: &str) -> Result { + api::ax_open_file(path, &self.0).map(|inner| File { inner }) + } +} + +impl Metadata { + /// Returns the file type for this metadata. + pub const fn file_type(&self) -> FileType { + self.0.file_type() + } + + /// Returns `true` if this metadata is for a directory. The + /// result is mutually exclusive to the result of + /// [`Metadata::is_file`]. + pub const fn is_dir(&self) -> bool { + self.0.is_dir() + } + + /// Returns `true` if this metadata is for a regular file. The + /// result is mutually exclusive to the result of + /// [`Metadata::is_dir`]. + pub const fn is_file(&self) -> bool { + self.0.is_file() + } + + /// Returns the size of the file, in bytes, this metadata is for. + #[allow(clippy::len_without_is_empty)] + pub const fn len(&self) -> u64 { + self.0.size() + } + + /// Returns the permissions of the file this metadata is for. + pub const fn permissions(&self) -> Permissions { + self.0.perm() + } + + /// Returns the total size of this file in bytes. + pub const fn size(&self) -> u64 { + self.0.size() + } + + /// Returns the number of blocks allocated to the file, in 512-byte units. + pub const fn blocks(&self) -> u64 { + self.0.blocks() + } +} + +impl fmt::Debug for Metadata { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Metadata") + .field("file_type", &self.file_type()) + .field("is_dir", &self.is_dir()) + .field("is_file", &self.is_file()) + .field("permissions", &self.permissions()) + .finish_non_exhaustive() + } +} + +impl File { + /// Attempts to open a file in read-only mode. + pub fn open(path: &str) -> Result { + OpenOptions::new().read(true).open(path) + } + + /// Opens a file in write-only mode. + pub fn create(path: &str) -> Result { + OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(path) + } + + /// Creates a new file in read-write mode; error if the file exists. + pub fn create_new(path: &str) -> Result { + OpenOptions::new() + .read(true) + .write(true) + .create_new(true) + .open(path) + } + + /// Returns a new OpenOptions object. + pub fn options() -> OpenOptions { + OpenOptions::new() + } + + /// Truncates or extends the underlying file, updating the size of + /// this file to become `size`. + pub fn set_len(&self, size: u64) -> Result<()> { + api::ax_truncate_file(&self.inner, size) + } + + /// Queries metadata about the underlying file. + pub fn metadata(&self) -> Result { + api::ax_file_attr(&self.inner).map(Metadata) + } +} + +impl Read for File { + fn read(&mut self, buf: &mut [u8]) -> Result { + api::ax_read_file(&mut self.inner, buf) + } +} + +impl Write for File { + fn write(&mut self, buf: &[u8]) -> Result { + api::ax_write_file(&mut self.inner, buf) + } + + fn flush(&mut self) -> Result<()> { + api::ax_flush_file(&self.inner) + } +} + +impl Seek for File { + fn seek(&mut self, pos: SeekFrom) -> Result { + api::ax_seek_file(&mut self.inner, pos) + } +} diff --git a/ulib/axstd/src/fs/mod.rs b/ulib/axstd/src/fs/mod.rs new file mode 100644 index 0000000000..5308045b97 --- /dev/null +++ b/ulib/axstd/src/fs/mod.rs @@ -0,0 +1,77 @@ +//! Filesystem manipulation operations. + +mod dir; +mod file; + +use crate::io::{self, prelude::*}; + +#[cfg(feature = "alloc")] +use alloc::{string::String, vec::Vec}; + +pub use self::dir::{DirBuilder, DirEntry, ReadDir}; +pub use self::file::{File, FileType, Metadata, OpenOptions, Permissions}; + +/// Read the entire contents of a file into a bytes vector. +#[cfg(feature = "alloc")] +pub fn read(path: &str) -> io::Result> { + let mut file = File::open(path)?; + let size = file.metadata().map(|m| m.len()).unwrap_or(0); + let mut bytes = Vec::with_capacity(size as usize); + file.read_to_end(&mut bytes)?; + Ok(bytes) +} + +/// Read the entire contents of a file into a string. +#[cfg(feature = "alloc")] +pub fn read_to_string(path: &str) -> io::Result { + let mut file = File::open(path)?; + let size = file.metadata().map(|m| m.len()).unwrap_or(0); + let mut string = String::with_capacity(size as usize); + file.read_to_string(&mut string)?; + Ok(string) +} + +/// Write a slice as the entire contents of a file. +pub fn write>(path: &str, contents: C) -> io::Result<()> { + File::create(path)?.write_all(contents.as_ref()) +} + +/// Given a path, query the file system to get information about a file, +/// directory, etc. +pub fn metadata(path: &str) -> io::Result { + File::open(path)?.metadata() +} + +/// Returns an iterator over the entries within a directory. +pub fn read_dir(path: &str) -> io::Result { + ReadDir::new(path) +} + +/// Creates a new, empty directory at the provided path. +pub fn create_dir(path: &str) -> io::Result<()> { + DirBuilder::new().create(path) +} + +/// Recursively create a directory and all of its parent components if they +/// are missing. +pub fn create_dir_all(path: &str) -> io::Result<()> { + DirBuilder::new().recursive(true).create(path) +} + +/// Removes an empty directory. +pub fn remove_dir(path: &str) -> io::Result<()> { + arceos_api::fs::ax_remove_dir(path) +} + +/// Removes a file from the filesystem. +pub fn remove_file(path: &str) -> io::Result<()> { + arceos_api::fs::ax_remove_file(path) +} + +/// Rename a file or directory to a new name. +/// Delete the original file if `old` already exists. +/// +/// This only works then the new path is in the same mounted fs. +pub fn rename(old: &str, new: &str) -> io::Result<()> { + arceos_api::fs::ax_rename(old, new) +} diff --git a/ulib/axstd/src/os.rs b/ulib/axstd/src/os.rs index dccf227b3d..d1bdf8258e 100644 --- a/ulib/axstd/src/os.rs +++ b/ulib/axstd/src/os.rs @@ -3,6 +3,4 @@ /// ArceOS-specific definitions. pub mod arceos { pub use arceos_api as api; - #[cfg(feature = "fs")] - pub use axfs; }