From 192745df45c23b0d9f869a81cea92c59a74979d6 Mon Sep 17 00:00:00 2001 From: Koichi Date: Thu, 26 Jun 2025 15:07:17 +0900 Subject: [PATCH 01/28] add async vfs path Signed-off-by: Koichi --- awkernel_async_lib/src/file/filesystem.rs | 143 +++- awkernel_async_lib/src/file/path.rs | 989 ++++++---------------- 2 files changed, 358 insertions(+), 774 deletions(-) diff --git a/awkernel_async_lib/src/file/filesystem.rs b/awkernel_async_lib/src/file/filesystem.rs index 9a1063df0..9a13a0e4d 100644 --- a/awkernel_async_lib/src/file/filesystem.rs +++ b/awkernel_async_lib/src/file/filesystem.rs @@ -1,14 +1,82 @@ //! The async filesystem trait definitions needed to implement new async virtual filesystems +use super::path::AsyncVfsPath; +use crate::time::Time; +use alloc::{boxed::Box, string::String}; +use async_trait::async_trait; +use awkernel_lib::file::{ + error::IoError, + io::SeekFrom, + vfs::error::{VfsError, VfsErrorKind, VfsResult}, + vfs::path::VfsMetadata, +}; +use futures::stream::Stream; -use crate::async_vfs::{AsyncVfsPath, SeekAndRead}; -use crate::error::VfsErrorKind; -use crate::{VfsError, VfsMetadata, VfsResult}; +// NOTE: We're currently using our own AsyncSeekAndRead and AsyncSeekAndWrite traits. We might replace these with traits from embedded-io-async in the future. However, that change would involve many modifications, and embedded-io-async doesn't seem stable yet, so we're sticking with our current approach for now." +#[async_trait] +pub trait AsyncSeekAndRead: Send + Unpin { + async fn read(&mut self, buf: &mut [u8]) -> Result>; -use async_std::io::Write; -use async_std::stream::Stream; -use async_trait::async_trait; -use std::fmt::Debug; -use std::time::SystemTime; + async fn seek(&mut self, pos: SeekFrom) -> Result>; + + async fn read_exact(&mut self, mut buf: &mut [u8]) -> Result<(), VfsError> { + while !buf.is_empty() { + match self.read(buf).await { + Ok(0) => return Ok(()), + Ok(n) => { + buf = &mut buf[n..]; + } + Err(e) => return Err(e), + } + } + + Ok(()) + } +} + +#[async_trait] +pub trait AsyncSeekAndWrite: Send + Unpin { + async fn write(&mut self, buf: &[u8]) -> Result>; + + async fn write_all(&mut self, buf: &[u8]) -> Result<(), VfsError>; + + async fn flush(&mut self) -> Result<(), VfsError>; + + async fn seek(&mut self, pos: SeekFrom) -> Result>; +} + +#[async_trait] +impl AsyncSeekAndRead for Box + Send + Unpin> { + async fn read(&mut self, buf: &mut [u8]) -> Result> { + (**self).read(buf).await + } + + async fn seek(&mut self, pos: SeekFrom) -> Result> { + (**self).seek(pos).await + } + + async fn read_exact(&mut self, buf: &mut [u8]) -> Result<(), VfsError> { + (**self).read_exact(buf).await + } +} + +#[async_trait] +impl AsyncSeekAndWrite for Box + Send + Unpin> { + async fn write(&mut self, buf: &[u8]) -> Result> { + (**self).write(buf).await + } + + async fn write_all(&mut self, buf: &[u8]) -> Result<(), VfsError> { + (**self).write_all(buf).await + } + + async fn flush(&mut self) -> Result<(), VfsError> { + (**self).flush().await + } + + async fn seek(&mut self, pos: SeekFrom) -> Result> { + (**self).seek(pos).await + } +} /// File system implementations must implement this trait /// All path parameters are absolute, starting with '/', except for the root directory @@ -18,58 +86,83 @@ use std::time::SystemTime; /// /// Please use the test_macros [test_macros::test_async_vfs!] and [test_macros::test_async_vfs_readonly!] #[async_trait] -pub trait AsyncFileSystem: Debug + Sync + Send + 'static { +pub trait AsyncFileSystem: Sync + Send + 'static { + /// The error type that can be returned by this file system. + type Error: IoError + Clone + Send + Sync; + /// Iterates over all direct children of this directory path /// NOTE: the returned String items denote the local bare filenames, i.e. they should not contain "/" anywhere async fn read_dir( &self, path: &str, - ) -> VfsResult + Send>>; + ) -> VfsResult + Send>, Self::Error>; + /// Creates the directory at this path /// /// Note that the parent directory must already exist. - async fn create_dir(&self, path: &str) -> VfsResult<()>; + async fn create_dir(&self, path: &str) -> VfsResult<(), Self::Error>; + /// Opens the file at this path for reading - async fn open_file(&self, path: &str) -> VfsResult>; + async fn open_file( + &self, + path: &str, + ) -> VfsResult + Send + Unpin>, Self::Error>; + /// Creates a file at this path for writing - async fn create_file(&self, path: &str) -> VfsResult>; + async fn create_file( + &self, + path: &str, + ) -> VfsResult + Send + Unpin>, Self::Error>; + /// Opens the file at this path for appending - async fn append_file(&self, path: &str) -> VfsResult>; + async fn append_file( + &self, + path: &str, + ) -> VfsResult + Send + Unpin>, Self::Error>; + /// Returns the file metadata for the file at this path - async fn metadata(&self, path: &str) -> VfsResult; + async fn metadata(&self, path: &str) -> VfsResult; + /// Sets the files creation timestamp, if the implementation supports it - async fn set_creation_time(&self, _path: &str, _time: SystemTime) -> VfsResult<()> { + async fn set_creation_time(&self, _path: &str, _time: Time) -> VfsResult<(), Self::Error> { Err(VfsError::from(VfsErrorKind::NotSupported)) } /// Sets the files modification timestamp, if the implementation supports it - async fn set_modification_time(&self, _path: &str, _time: SystemTime) -> VfsResult<()> { + async fn set_modification_time(&self, _path: &str, _time: Time) -> VfsResult<(), Self::Error> { Err(VfsError::from(VfsErrorKind::NotSupported)) } /// Sets the files access timestamp, if the implementation supports it - async fn set_access_time(&self, _path: &str, _time: SystemTime) -> VfsResult<()> { + async fn set_access_time(&self, _path: &str, _time: Time) -> VfsResult<(), Self::Error> { Err(VfsError::from(VfsErrorKind::NotSupported)) } /// Returns true if a file or directory at path exists, false otherwise - async fn exists(&self, path: &str) -> VfsResult; + async fn exists(&self, path: &str) -> VfsResult; + /// Removes the file at this path - async fn remove_file(&self, path: &str) -> VfsResult<()>; + async fn remove_file(&self, path: &str) -> VfsResult<(), Self::Error>; + /// Removes the directory at this path - async fn remove_dir(&self, path: &str) -> VfsResult<()>; + async fn remove_dir(&self, path: &str) -> VfsResult<(), Self::Error>; + /// Copies the src path to the destination path within the same filesystem (optional) - async fn copy_file(&self, _src: &str, _dest: &str) -> VfsResult<()> { + async fn copy_file(&self, _src: &str, _dest: &str) -> VfsResult<(), Self::Error> { Err(VfsErrorKind::NotSupported.into()) } /// Moves the src path to the destination path within the same filesystem (optional) - async fn move_file(&self, _src: &str, _dest: &str) -> VfsResult<()> { + async fn move_file(&self, _src: &str, _dest: &str) -> VfsResult<(), Self::Error> { Err(VfsErrorKind::NotSupported.into()) } /// Moves the src directory to the destination path within the same filesystem (optional) - async fn move_dir(&self, _src: &str, _dest: &str) -> VfsResult<()> { + async fn move_dir(&self, _src: &str, _dest: &str) -> VfsResult<(), Self::Error> { Err(VfsErrorKind::NotSupported.into()) } } -impl From for AsyncVfsPath { +impl From for AsyncVfsPath +where + E: IoError + Clone + Send + Sync + 'static, + T: AsyncFileSystem, +{ fn from(filesystem: T) -> Self { AsyncVfsPath::new(filesystem) } diff --git a/awkernel_async_lib/src/file/path.rs b/awkernel_async_lib/src/file/path.rs index 20b6358e2..e5dac09ba 100644 --- a/awkernel_async_lib/src/file/path.rs +++ b/awkernel_async_lib/src/file/path.rs @@ -1,190 +1,120 @@ -//! Virtual filesystem path +//! Virtual filesystem path (async version) //! //! The virtual file system abstraction generalizes over file systems and allow using //! different VirtualFileSystem implementations (i.e. an in memory implementation for unit tests) -use crate::async_vfs::AsyncFileSystem; -use crate::error::{VfsError, VfsErrorKind}; -use crate::path::PathLike; -use crate::path::VfsFileType; -use crate::{VfsMetadata, VfsResult}; - +use crate::file::fatfs::AsyncFatFs; + +use super::filesystem::{AsyncFileSystem, AsyncSeekAndRead, AsyncSeekAndWrite}; +use awkernel_lib::{ + file::{ + error::IoError, + memfs::InMemoryDiskError, + vfs::{ + error::{VfsError, VfsErrorKind, VfsResult}, + path::{PathLike, VfsFileType, VfsMetadata}, + }, + }, + time::Time, +}; + +use alloc::{ + boxed::Box, + format, + string::{String, ToString}, + sync::Arc, + vec, + vec::Vec, +}; use async_recursion::async_recursion; -use async_std::io::{Read, ReadExt, Seek, Write}; -use async_std::sync::Arc; -use async_std::task::{Context, Poll}; -use futures::{future::BoxFuture, FutureExt, Stream, StreamExt}; -use std::pin::Pin; -use std::time::SystemTime; - -/// Trait combining Seek and Read, return value for opening files -pub trait SeekAndRead: Seek + Read {} - -impl SeekAndRead for T where T: Seek + Read {} - -#[derive(Debug)] -struct AsyncVFS { - fs: Box, +use core::{ + pin::Pin, + task::{Context, Poll}, +}; +use futures::{future::BoxFuture, stream::BoxStream, FutureExt, Stream, StreamExt}; + +struct AsyncVfs { + fs: Box>, } /// A virtual filesystem path, identifying a single file or directory in this virtual filesystem -#[derive(Clone, Debug)] -pub struct AsyncVfsPath { - path: String, - fs: Arc, +#[derive(Clone)] +pub struct AsyncVfsPath { + path: Arc, + fs: Arc>, } -impl PathLike for AsyncVfsPath { +impl PathLike for AsyncVfsPath { + type Error = E; fn get_path(&self) -> String { - self.path.clone() + self.path.to_string() } } -impl PartialEq for AsyncVfsPath { +impl PartialEq for AsyncVfsPath { fn eq(&self, other: &Self) -> bool { self.path == other.path && Arc::ptr_eq(&self.fs, &other.fs) } } -impl Eq for AsyncVfsPath {} +impl Eq for AsyncVfsPath {} -impl AsyncVfsPath { +impl AsyncVfsPath { + pub fn new_in_memory_fatfs() -> Self { + let fs = AsyncFatFs::new_in_memory(); + AsyncVfsPath::new(fs) + } +} + +impl AsyncVfsPath { /// Creates a root path for the given filesystem - /// - /// ``` - /// # use vfs::async_vfs::{AsyncPhysicalFS, AsyncVfsPath}; - /// let path = AsyncVfsPath::new(AsyncPhysicalFS::new(".")); - /// ```` - pub fn new(filesystem: T) -> Self { + pub fn new>(filesystem: T) -> Self { AsyncVfsPath { - path: "".to_string(), - fs: Arc::new(AsyncVFS { + path: "".into(), + fs: Arc::new(AsyncVfs { fs: Box::new(filesystem), }), } } /// Returns the string representation of this path - /// - /// ``` - /// # use vfs::async_vfs::{AsyncPhysicalFS, AsyncVfsPath}; - /// # use vfs::VfsError; - /// let path = AsyncVfsPath::new(AsyncPhysicalFS::new(".")); - /// - /// assert_eq!(path.as_str(), ""); - /// assert_eq!(path.join("foo.txt")?.as_str(), "/foo.txt"); - /// # Ok::<(), VfsError>(()) - /// ```` pub fn as_str(&self) -> &str { &self.path } /// Appends a path segment to this path, returning the result - /// - /// ``` - /// # use vfs::async_vfs::{AsyncPhysicalFS, AsyncVfsPath}; - /// # use vfs::VfsError; - /// let path = AsyncVfsPath::new(AsyncPhysicalFS::new(".")); - /// - /// assert_eq!(path.join("foo.txt")?.as_str(), "/foo.txt"); - /// assert_eq!(path.join("foo/bar.txt")?.as_str(), "/foo/bar.txt"); - /// - /// let foo = path.join("foo")?; - /// - /// assert_eq!(path.join("foo/bar.txt")?, foo.join("bar.txt")?); - /// assert_eq!(path, foo.join("..")?); - /// # Ok::<(), VfsError>(()) - /// ``` - pub fn join(&self, path: impl AsRef) -> VfsResult { + pub fn join(&self, path: impl AsRef) -> VfsResult { let new_path = self.join_internal(&self.path, path.as_ref())?; Ok(Self { - path: new_path, + path: Arc::from(new_path), fs: self.fs.clone(), }) } /// Returns the root path of this filesystem - /// - /// ``` - /// # use vfs::async_vfs::{AsyncMemoryFS, AsyncVfsPath}; - /// # use vfs::{VfsError, VfsFileType}; - /// let path = AsyncVfsPath::new(AsyncMemoryFS::new()); - /// let directory = path.join("foo/bar")?; - /// - /// assert_eq!(directory.root(), path); - /// # Ok::<(), VfsError>(()) - /// ``` - pub fn root(&self) -> AsyncVfsPath { + pub fn root(&self) -> Self { AsyncVfsPath { - path: "".to_string(), + path: "".into(), fs: self.fs.clone(), } } /// Returns true if this is the root path - /// - /// ``` - /// # use vfs::async_vfs::{AsyncMemoryFS, AsyncVfsPath}; - /// # use vfs::{VfsError, VfsFileType}; - /// let path = AsyncVfsPath::new(AsyncMemoryFS::new()); - /// assert!(path.is_root()); - /// let path = path.join("foo/bar")?; - /// assert!(! path.is_root()); - /// # Ok::<(), VfsError>(()) - /// ``` pub fn is_root(&self) -> bool { self.path.is_empty() } /// Creates the directory at this path - /// - /// Note that the parent directory must exist, while the given path must not exist. - /// - /// Returns VfsErrorKind::FileExists if a file already exists at the given path - /// Returns VfsErrorKind::DirectoryExists if a directory already exists at the given path - /// - /// ``` - /// # use vfs::async_vfs::{AsyncMemoryFS, AsyncVfsPath}; - /// # use vfs::{VfsError, VfsFileType}; - /// # tokio_test::block_on(async { - /// let path = AsyncVfsPath::new(AsyncMemoryFS::new()); - /// let directory = path.join("foo")?; - /// - /// directory.create_dir().await?; - /// - /// assert!(directory.exists().await?); - /// assert_eq!(directory.metadata().await?.file_type, VfsFileType::Directory); - /// # Ok::<(), VfsError>(()) - /// # }); - /// ``` - pub async fn create_dir(&self) -> VfsResult<()> { + pub async fn create_dir(&self) -> VfsResult<(), E> { self.get_parent("create directory").await?; self.fs.fs.create_dir(&self.path).await.map_err(|err| { - err.with_path(&self.path) + err.with_path(&*self.path) .with_context(|| "Could not create directory") }) } /// Creates the directory at this path, also creating parent directories as necessary - /// - /// ``` - /// # use vfs::async_vfs::{AsyncMemoryFS, AsyncVfsPath}; - /// # use vfs::{VfsError, VfsFileType}; - /// # tokio_test::block_on(async { - /// let path = AsyncVfsPath::new(AsyncMemoryFS::new()); - /// let directory = path.join("foo/bar")?; - /// - /// directory.create_dir_all().await?; - /// - /// assert!(directory.exists().await?); - /// assert_eq!(directory.metadata().await?.file_type, VfsFileType::Directory); - /// let parent = path.join("foo")?; - /// assert!(parent.exists().await?); - /// assert_eq!(parent.metadata().await?.file_type, VfsFileType::Directory); - /// # Ok::<(), VfsError>(()) - /// # }); - /// ``` - pub async fn create_dir_all(&self) -> VfsResult<()> { + pub async fn create_dir_all(&self) -> VfsResult<(), E> { let mut pos = 1; let path = &self.path; if path.is_empty() { @@ -202,9 +132,9 @@ impl AsyncVfsPath { match error.kind() { VfsErrorKind::DirectoryExists => {} _ => { - return Err(error.with_path(directory).with_context(|| { - format!("Could not create directories at '{}'", path) - })) + return Err(error + .with_path(directory) + .with_context(|| format!("Could not create directories at '{path}'"))) } } } @@ -217,214 +147,88 @@ impl AsyncVfsPath { } /// Iterates over all entries of this directory path - /// - /// ``` - /// # use vfs::async_vfs::{AsyncMemoryFS, AsyncVfsPath}; - /// # use vfs::VfsError; - /// use futures::stream::Collect; - /// use futures::stream::StreamExt; - /// # tokio_test::block_on(async { - /// let path = AsyncVfsPath::new(AsyncMemoryFS::new()); - /// path.join("foo")?.create_dir().await?; - /// path.join("bar")?.create_dir().await?; - /// - /// let mut directories: Vec<_> = path.read_dir().await?.collect().await; - /// - /// directories.sort_by_key(|path| path.as_str().to_string()); - /// assert_eq!(directories, vec![path.join("bar")?, path.join("foo")?]); - /// # Ok::<(), VfsError>(()) - /// # }); - /// ``` - pub async fn read_dir(&self) -> VfsResult + Send>> { + pub async fn read_dir(&self) -> VfsResult, E> { let parent = self.path.clone(); let fs = self.fs.clone(); - Ok(Box::new( - self.fs - .fs - .read_dir(&self.path) - .await - .map_err(|err| { - err.with_path(&self.path) - .with_context(|| "Could not read directory") - })? - .map(move |path| { - println!("{:?}", path); - AsyncVfsPath { - path: format!("{}/{}", parent, path), - fs: fs.clone(), - } - }), - )) + let stream = self + .fs + .fs + .read_dir(&self.path) + .await + .map_err(|err| { + err.with_path(&*self.path) + .with_context(|| "Could not read directory") + })? + .map(move |path_str| Self { + path: format!("{parent}/{path_str}").into(), + fs: fs.clone(), + }); + Ok(Box::pin(stream)) } /// Creates a file at this path for writing, overwriting any existing file - /// - /// ``` - /// # use vfs::async_vfs::{AsyncMemoryFS, AsyncVfsPath}; - /// # use vfs::VfsError; - /// use async_std::io:: {ReadExt, WriteExt}; - /// # tokio_test::block_on(async { - /// let path = AsyncVfsPath::new(AsyncMemoryFS::new()); - /// let file = path.join("foo.txt")?; - /// - /// write!(file.create_file().await?, "Hello, world!").await?; - /// - /// let mut result = String::new(); - /// file.open_file().await?.read_to_string(&mut result).await?; - /// assert_eq!(&result, "Hello, world!"); - /// # Ok::<(), VfsError>(()) - /// # }); - /// ``` - pub async fn create_file(&self) -> VfsResult> { + pub async fn create_file(&self) -> VfsResult + Send + Unpin>, E> { self.get_parent("create file").await?; self.fs.fs.create_file(&self.path).await.map_err(|err| { - err.with_path(&self.path) + err.with_path(&*self.path) .with_context(|| "Could not create file") }) } /// Opens the file at this path for reading - /// - /// ``` - /// # use vfs::async_vfs::{AsyncMemoryFS, AsyncVfsPath}; - /// # use vfs::VfsError; - /// use async_std::io:: {ReadExt, WriteExt}; - /// # tokio_test::block_on(async { - /// let path = AsyncVfsPath::new(AsyncMemoryFS::new()); - /// let file = path.join("foo.txt")?; - /// write!(file.create_file().await?, "Hello, world!").await?; - /// let mut result = String::new(); - /// - /// file.open_file().await?.read_to_string(&mut result).await?; - /// - /// assert_eq!(&result, "Hello, world!"); - /// # Ok::<(), VfsError>(()) - /// # }); - /// ``` - pub async fn open_file(&self) -> VfsResult> { + pub async fn open_file(&self) -> VfsResult + Send + Unpin>, E> { self.fs.fs.open_file(&self.path).await.map_err(|err| { - err.with_path(&self.path) + err.with_path(&*self.path) .with_context(|| "Could not open file") }) } /// Checks whether parent is a directory - async fn get_parent(&self, action: &str) -> VfsResult<()> { + async fn get_parent(&self, action: &str) -> VfsResult<(), E> { let parent = self.parent(); if !parent.exists().await? { return Err(VfsError::from(VfsErrorKind::Other(format!( - "Could not {}, parent directory does not exist", - action + "Could not {action}, parent directory does not exist" ))) - .with_path(&self.path)); + .with_path(&*self.path)); } let metadata = parent.metadata().await?; if metadata.file_type != VfsFileType::Directory { return Err(VfsError::from(VfsErrorKind::Other(format!( - "Could not {}, parent path is not a directory", - action + "Could not {action}, parent path is not a directory" ))) - .with_path(&self.path)); + .with_path(&*self.path)); } Ok(()) } /// Opens the file at this path for appending - /// - /// ``` - /// # use vfs::async_vfs::{AsyncMemoryFS, AsyncVfsPath}; - /// # use vfs::VfsError; - /// use async_std::io:: {ReadExt, WriteExt}; - /// # tokio_test::block_on(async { - /// let path = AsyncVfsPath::new(AsyncMemoryFS::new()); - /// let file = path.join("foo.txt")?; - /// write!(file.create_file().await?, "Hello, ").await?; - /// write!(file.append_file().await?, "world!").await?; - /// let mut result = String::new(); - /// file.open_file().await?.read_to_string(&mut result).await?; - /// assert_eq!(&result, "Hello, world!"); - /// # Ok::<(), VfsError>(()) - /// # }); - /// ``` - pub async fn append_file(&self) -> VfsResult> { + pub async fn append_file(&self) -> VfsResult + Send + Unpin>, E> { self.fs.fs.append_file(&self.path).await.map_err(|err| { - err.with_path(&self.path) + err.with_path(&*self.path) .with_context(|| "Could not open file for appending") }) } /// Removes the file at this path - /// - /// ``` - /// use async_std::io:: {ReadExt, WriteExt}; - /// # use vfs::async_vfs::{AsyncMemoryFS , AsyncVfsPath}; - /// # use vfs::VfsError; - /// # tokio_test::block_on(async { - /// let path = AsyncVfsPath::new(AsyncMemoryFS::new()); - /// let file = path.join("foo.txt")?; - /// write!(file.create_file().await?, "Hello, ").await?; - /// assert!(file.exists().await?); - /// - /// file.remove_file().await?; - /// - /// assert!(!file.exists().await?); - /// # Ok::<(), VfsError>(()) - /// # }); - /// ``` - pub async fn remove_file(&self) -> VfsResult<()> { + pub async fn remove_file(&self) -> VfsResult<(), E> { self.fs.fs.remove_file(&self.path).await.map_err(|err| { - err.with_path(&self.path) + err.with_path(&*self.path) .with_context(|| "Could not remove file") }) } /// Removes the directory at this path - /// - /// The directory must be empty. - /// - /// ``` - /// # tokio_test::block_on(async { - /// use vfs::async_vfs::{AsyncMemoryFS, AsyncVfsPath}; - /// use vfs::VfsError; - /// let path = AsyncVfsPath::new(AsyncMemoryFS::new()); - /// let directory = path.join("foo")?; - /// directory.create_dir().await; - /// assert!(directory.exists().await?); - /// - /// directory.remove_dir().await?; - /// - /// assert!(!directory.exists().await?); - /// # Ok::<(), VfsError>(()) - /// # }); - /// ``` - pub async fn remove_dir(&self) -> VfsResult<()> { + pub async fn remove_dir(&self) -> VfsResult<(), E> { self.fs.fs.remove_dir(&self.path).await.map_err(|err| { - err.with_path(&self.path) + err.with_path(&*self.path) .with_context(|| "Could not remove directory") }) } /// Ensures that the directory at this path is removed, recursively deleting all contents if necessary - /// - /// Returns successfully if directory does not exist - /// - /// ``` - /// # use vfs::async_vfs::{AsyncMemoryFS, AsyncVfsPath}; - /// # use vfs::VfsError; - /// # tokio_test::block_on(async { - /// let path = AsyncVfsPath::new(AsyncMemoryFS::new()); - /// let directory = path.join("foo")?; - /// directory.join("bar")?.create_dir_all().await?; - /// assert!(directory.exists().await?); - /// - /// directory.remove_dir_all().await?; - /// - /// assert!(!directory.exists().await?); - /// # Ok::<(), VfsError>(()) - /// # }); - /// ``` #[async_recursion] - pub async fn remove_dir_all(&self) -> VfsResult<()> { + pub async fn remove_dir_all(&self) -> VfsResult<(), E> { if !self.exists().await? { return Ok(()); } @@ -441,53 +245,15 @@ impl AsyncVfsPath { } /// Returns the file metadata for the file at this path - /// - /// ``` - /// use vfs::async_vfs::{AsyncMemoryFS, AsyncVfsPath}; - /// use vfs::{VfsError, VfsFileType, VfsMetadata}; - /// use async_std::io::WriteExt; - /// # tokio_test::block_on(async { - /// let path = AsyncVfsPath::new(AsyncMemoryFS::new()); - /// let directory = path.join("foo")?; - /// directory.create_dir().await?; - /// - /// assert_eq!(directory.metadata().await?.len, 0); - /// assert_eq!(directory.metadata().await?.file_type, VfsFileType::Directory); - /// - /// let file = path.join("bar.txt")?; - /// write!(file.create_file().await?, "Hello, world!").await?; - /// - /// assert_eq!(file.metadata().await?.len, 13); - /// assert_eq!(file.metadata().await?.file_type, VfsFileType::File); - /// # Ok::<(), VfsError>(()) - /// # }); - pub async fn metadata(&self) -> VfsResult { + pub async fn metadata(&self) -> VfsResult { self.fs.fs.metadata(&self.path).await.map_err(|err| { - err.with_path(&self.path) + err.with_path(&*self.path) .with_context(|| "Could not get metadata") }) } /// Sets the files creation timestamp at this path - /// - /// ``` - /// use vfs::async_vfs::{AsyncMemoryFS, AsyncVfsPath}; - /// use vfs::{VfsError, VfsFileType, VfsMetadata, VfsPath}; - /// use async_std::io::WriteExt; - /// # tokio_test::block_on(async { - /// let path = AsyncVfsPath::new(AsyncMemoryFS::new()); - /// let file = path.join("foo.txt")?; - /// file.create_file(); - /// - /// let time = std::time::SystemTime::now(); - /// file.set_creation_time(time).await?; - /// - /// assert_eq!(file.metadata().await?.len, 0); - /// assert_eq!(file.metadata().await?.created, Some(time)); - /// - /// # Ok::<(), VfsError>(()) - /// # }); - pub async fn set_creation_time(&self, time: SystemTime) -> VfsResult<()> { + pub async fn set_creation_time(&self, time: Time) -> VfsResult<(), E> { self.fs .fs .set_creation_time(&self.path, time) @@ -499,25 +265,7 @@ impl AsyncVfsPath { } /// Sets the files modification timestamp at this path - /// - /// ``` - /// use vfs::async_vfs::{AsyncMemoryFS, AsyncVfsPath}; - /// use vfs::{VfsError, VfsFileType, VfsMetadata, VfsPath}; - /// use async_std::io::WriteExt; - /// # tokio_test::block_on(async { - /// let path = AsyncVfsPath::new(AsyncMemoryFS::new()); - /// let file = path.join("foo.txt")?; - /// file.create_file(); - /// - /// let time = std::time::SystemTime::now(); - /// file.set_modification_time(time).await?; - /// - /// assert_eq!(file.metadata().await?.len, 0); - /// assert_eq!(file.metadata().await?.modified, Some(time)); - /// - /// # Ok::<(), VfsError>(()) - /// # }); - pub async fn set_modification_time(&self, time: SystemTime) -> VfsResult<()> { + pub async fn set_modification_time(&self, time: Time) -> VfsResult<(), E> { self.fs .fs .set_modification_time(&self.path, time) @@ -529,25 +277,7 @@ impl AsyncVfsPath { } /// Sets the files access timestamp at this path - /// - /// ``` - /// use vfs::async_vfs::{AsyncMemoryFS, AsyncVfsPath}; - /// use vfs::{VfsError, VfsFileType, VfsMetadata, VfsPath}; - /// use async_std::io::WriteExt; - /// # tokio_test::block_on(async { - /// let path = AsyncVfsPath::new(AsyncMemoryFS::new()); - /// let file = path.join("foo.txt")?; - /// file.create_file(); - /// - /// let time = std::time::SystemTime::now(); - /// file.set_access_time(time).await?; - /// - /// assert_eq!(file.metadata().await?.len, 0); - /// assert_eq!(file.metadata().await?.accessed, Some(time)); - /// - /// # Ok::<(), VfsError>(()) - /// # }); - pub async fn set_access_time(&self, time: SystemTime) -> VfsResult<()> { + pub async fn set_access_time(&self, time: Time) -> VfsResult<(), E> { self.fs .fs .set_access_time(&self.path, time) @@ -559,24 +289,7 @@ impl AsyncVfsPath { } /// Returns `true` if the path exists and is pointing at a regular file, otherwise returns `false`. - /// - /// Note that this call may fail if the file's existence cannot be determined or the metadata can not be retrieved - /// - /// ``` - /// # use vfs::async_vfs::{AsyncMemoryFS, AsyncVfsPath}; - /// # use vfs::{VfsError, VfsFileType, VfsMetadata}; - /// # tokio_test::block_on(async { - /// let path = AsyncVfsPath::new(AsyncMemoryFS::new()); - /// let directory = path.join("foo")?; - /// directory.create_dir().await?; - /// let file = path.join("foo.txt")?; - /// file.create_file().await?; - /// - /// assert!(!directory.is_file().await?); - /// assert!(file.is_file().await?); - /// # Ok::<(), VfsError>(()) - /// # }); - pub async fn is_file(&self) -> VfsResult { + pub async fn is_file(&self) -> VfsResult { if !self.exists().await? { return Ok(false); } @@ -585,24 +298,7 @@ impl AsyncVfsPath { } /// Returns `true` if the path exists and is pointing at a directory, otherwise returns `false`. - /// - /// Note that this call may fail if the directory's existence cannot be determined or the metadata can not be retrieved - /// - /// ``` - /// # use vfs::async_vfs::{AsyncMemoryFS, AsyncVfsPath}; - /// # use vfs::{VfsError, VfsFileType, VfsMetadata}; - /// # tokio_test::block_on(async { - /// let path = AsyncVfsPath::new(AsyncMemoryFS::new()); - /// let directory = path.join("foo")?; - /// directory.create_dir().await?; - /// let file = path.join("foo.txt")?; - /// file.create_file().await?; - /// - /// assert!(directory.is_dir().await?); - /// assert!(!file.is_dir().await?); - /// # Ok::<(), VfsError>(()) - /// # }); - pub async fn is_dir(&self) -> VfsResult { + pub async fn is_dir(&self) -> VfsResult { if !self.exists().await? { return Ok(false); } @@ -611,334 +307,161 @@ impl AsyncVfsPath { } /// Returns true if a file or directory exists at this path, false otherwise - /// - /// ``` - /// # use vfs::async_vfs::{AsyncMemoryFS, AsyncVfsPath}; - /// # use vfs::{VfsError, VfsFileType, VfsMetadata}; - /// # tokio_test::block_on(async { - /// let path = AsyncVfsPath::new(AsyncMemoryFS::new()); - /// let directory = path.join("foo")?; - /// - /// assert!(!directory.exists().await?); - /// - /// directory.create_dir().await?; - /// - /// assert!(directory.exists().await?); - /// # Ok::<(), VfsError>(()) - /// # }); - pub async fn exists(&self) -> VfsResult { + pub async fn exists(&self) -> VfsResult { self.fs.fs.exists(&self.path).await } /// Returns the filename portion of this path - /// - /// ``` - /// # use vfs::async_vfs::{AsyncMemoryFS, AsyncVfsPath}; - /// # use vfs::{VfsError, VfsFileType, VfsMetadata}; - /// let path = AsyncVfsPath::new(AsyncMemoryFS::new()); - /// let file = path.join("foo/bar.txt")?; - /// - /// assert_eq!(&file.filename(), "bar.txt"); - /// - /// # Ok::<(), VfsError>(()) pub fn filename(&self) -> String { self.filename_internal() } /// Returns the extension portion of this path - /// - /// ``` - /// # use vfs::async_vfs::{AsyncMemoryFS, AsyncVfsPath}; - /// # use vfs::{VfsError, VfsFileType, VfsMetadata}; - /// let path = AsyncVfsPath::new(AsyncMemoryFS::new()); - /// - /// assert_eq!(path.join("foo/bar.txt")?.extension(), Some("txt".to_string())); - /// assert_eq!(path.join("foo/bar.txt.zip")?.extension(), Some("zip".to_string())); - /// assert_eq!(path.join("foo/bar")?.extension(), None); - /// - /// # Ok::<(), VfsError>(()) pub fn extension(&self) -> Option { self.extension_internal() } /// Returns the parent path of this portion of this path - /// - /// Root will return itself. - /// - /// ``` - /// # use vfs::async_vfs::{AsyncMemoryFS, AsyncVfsPath}; - /// # use vfs::{VfsError, VfsFileType, VfsMetadata}; - /// let path = AsyncVfsPath::new(AsyncMemoryFS::new()); - /// - /// assert_eq!(path.parent(), path.root()); - /// assert_eq!(path.join("foo/bar")?.parent(), path.join("foo")?); - /// assert_eq!(path.join("foo")?.parent(), path); - /// - /// # Ok::<(), VfsError>(()) pub fn parent(&self) -> Self { let parent_path = self.parent_internal(&self.path); Self { - path: parent_path, + path: parent_path.into(), fs: self.fs.clone(), } } /// Recursively iterates over all the directories and files at this path - /// - /// Directories are visited before their children - /// - /// Note that the iterator items can contain errors, usually when directories are removed during the iteration. - /// The returned paths may also point to non-existant files if there is concurrent removal. - /// - /// Also note that loops in the file system hierarchy may cause this iterator to never terminate. - /// - /// ``` - /// # use vfs::async_vfs::{AsyncMemoryFS, AsyncVfsPath}; - /// # use vfs::{VfsError, VfsResult}; - /// use futures::stream::StreamExt; - /// # tokio_test::block_on(async { - /// let root = AsyncVfsPath::new(AsyncMemoryFS::new()); - /// root.join("foo/bar")?.create_dir_all().await?; - /// root.join("fizz/buzz")?.create_dir_all().await?; - /// root.join("foo/bar/baz")?.create_file().await?; - /// - /// let mut directories = root.walk_dir().await?.map(|res| res.unwrap()).collect::>().await; - /// - /// directories.sort_by_key(|path| path.as_str().to_string()); - /// let expected = vec!["fizz", "fizz/buzz", "foo", "foo/bar", "foo/bar/baz"].iter().map(|path| root.join(path)).collect::>>()?; - /// assert_eq!(directories, expected); - /// # Ok::<(), VfsError>(()) - /// # }); - /// ``` - pub async fn walk_dir(&self) -> VfsResult { + pub async fn walk_dir(&self) -> VfsResult, E> { Ok(WalkDirIterator { inner: self.read_dir().await?, todo: vec![], - prev_result: None, - metadata_fut: None, - read_dir_fut: None, + _pending_meta: None, + pending_read: None, }) } /// Reads a complete file to a string - /// - /// Returns an error if the file does not exist or is not valid UTF-8 - /// - /// ``` - /// # use vfs::async_vfs::{AsyncMemoryFS, AsyncVfsPath}; - /// # use vfs::VfsError; - /// use async_std::io::{ReadExt, WriteExt}; - /// # tokio_test::block_on(async { - /// let path = AsyncVfsPath::new(AsyncMemoryFS::new()); - /// let file = path.join("foo.txt")?; - /// write!(file.create_file().await?, "Hello, world!").await?; - /// - /// let result = file.read_to_string().await?; - /// - /// assert_eq!(&result, "Hello, world!"); - /// # Ok::<(), VfsError>(()) - /// # }); - /// ``` - pub async fn read_to_string(&self) -> VfsResult { + pub async fn read_to_string(&self) -> VfsResult { let metadata = self.metadata().await?; if metadata.file_type != VfsFileType::File { return Err( VfsError::from(VfsErrorKind::Other("Path is a directory".into())) - .with_path(&self.path) + .with_path(&*self.path) .with_context(|| "Could not read path"), ); } - let mut result = String::with_capacity(metadata.len as usize); + let mut buffer = vec![0; metadata.len as usize]; self.open_file() .await? - .read_to_string(&mut result) + .read_exact(&mut buffer) .await - .map_err(|source| { - VfsError::from(source) - .with_path(&self.path) + .map_err(|err| { + err.with_path(&*self.path) .with_context(|| "Could not read path") })?; - Ok(result) + + String::from_utf8(buffer).map_err(|_| { + VfsError::from(VfsErrorKind::Other("Invalid UTF-8 sequence".into())) + .with_path(&*self.path) + .with_context(|| "Could not read path as string") + }) } /// Copies a file to a new destination - /// - /// The destination must not exist, but its parent directory must - /// - /// ``` - /// use async_std::io::{ReadExt, WriteExt}; - /// # use vfs::async_vfs::{AsyncMemoryFS, AsyncVfsPath}; - /// # use vfs::VfsError; - /// # tokio_test::block_on(async { - /// let path = AsyncVfsPath::new(AsyncMemoryFS::new()); - /// let src = path.join("foo.txt")?; - /// write!(src.create_file().await?, "Hello, world!").await?; - /// let dest = path.join("bar.txt")?; - /// - /// src.copy_file(&dest).await?; - /// - /// assert_eq!(dest.read_to_string().await?, "Hello, world!"); - /// # Ok::<(), VfsError>(()) - /// # }); - /// ``` - pub async fn copy_file(&self, destination: &AsyncVfsPath) -> VfsResult<()> { + pub async fn copy_file(&self, destination: &AsyncVfsPath) -> VfsResult<(), E> { async { if destination.exists().await? { - return Err(VfsError::from(VfsErrorKind::Other( - "Destination exists already".into(), - )) - .with_path(&self.path)); + return Err( + VfsError::from(VfsErrorKind::Other("Destination exists already".into())) + .with_path(&*self.path), + ); } if Arc::ptr_eq(&self.fs, &destination.fs) { let result = self.fs.fs.copy_file(&self.path, &destination.path).await; - match result { - Err(err) => match err.kind() { - VfsErrorKind::NotSupported => { - // continue - } - _ => return Err(err), - }, - other => return other, + if !matches!(result, Err(ref err) if matches!(err.kind(), VfsErrorKind::NotSupported)) + { + return result; } } let mut src = self.open_file().await?; let mut dest = destination.create_file().await?; - async_std::io::copy(&mut src, &mut dest) - .await - .map_err(|source| { - VfsError::from(source) - .with_path(&self.path) - .with_context(|| "Could not read path") - })?; + simple_async_copy(&mut src, &mut dest).await.map_err(|err| match err { + CopyError::ReadError(e) => e, + CopyError::WriteError(e) => e, + })?; Ok(()) } .await .map_err(|err| { - err.with_path(&self.path).with_context(|| { - format!( - "Could not copy '{}' to '{}'", - self.as_str(), - destination.as_str() - ) + err.with_path(&*self.path).with_context(|| { + format!("Could not copy '{}' to '{}'", self.as_str(), destination.as_str()) }) - })?; - Ok(()) + }) } /// Moves or renames a file to a new destination - /// - /// The destination must not exist, but its parent directory must - /// - /// ``` - /// # use vfs::async_vfs::{AsyncMemoryFS, AsyncVfsPath}; - /// # use vfs::VfsError; - /// use async_std::io::{ReadExt, WriteExt}; - /// # tokio_test::block_on(async { - /// let path = AsyncVfsPath::new(AsyncMemoryFS::new()); - /// let src = path.join("foo.txt")?; - /// write!(src.create_file().await?, "Hello, world!").await?; - /// let dest = path.join("bar.txt")?; - /// - /// src.move_file(&dest).await?; - /// - /// assert_eq!(dest.read_to_string().await?, "Hello, world!"); - /// assert!(!src.exists().await?); - /// # Ok::<(), VfsError>(()) - /// # }); - /// ``` - pub async fn move_file(&self, destination: &AsyncVfsPath) -> VfsResult<()> { + pub async fn move_file(&self, destination: &AsyncVfsPath) -> VfsResult<(), E> { async { if destination.exists().await? { return Err(VfsError::from(VfsErrorKind::Other( "Destination exists already".into(), )) - .with_path(&destination.path)); + .with_path(&*destination.path)); } if Arc::ptr_eq(&self.fs, &destination.fs) { - let result = self.fs.fs.move_file(&self.path, &destination.path); - match result.await { - Err(err) => match err.kind() { - VfsErrorKind::NotSupported => { - // continue - } - _ => return Err(err), - }, - other => return other, + let result = self.fs.fs.move_file(&self.path, &destination.path).await; + if !matches!(result, Err(ref err) if matches!(err.kind(), VfsErrorKind::NotSupported)) + { + return result; } } let mut src = self.open_file().await?; let mut dest = destination.create_file().await?; - async_std::io::copy(&mut src, &mut dest) - .await - .map_err(|source| { - VfsError::from(source) - .with_path(&self.path) - .with_context(|| "Could not read path") - })?; + simple_async_copy(&mut src, &mut dest).await.map_err(|err| match err { + CopyError::ReadError(e) => e, + CopyError::WriteError(e) => e, + })?; self.remove_file().await?; Ok(()) } .await .map_err(|err| { - err.with_path(&self.path).with_context(|| { - format!( - "Could not move '{}' to '{}'", - self.as_str(), - destination.as_str() - ) + err.with_path(&*self.path).with_context(|| { + format!("Could not move '{}' to '{}'", self.as_str(), destination.as_str()) }) - })?; - Ok(()) + }) } /// Copies a directory to a new destination, recursively - /// - /// The destination must not exist, but the parent directory must - /// - /// Returns the number of files copied - /// - /// ``` - /// # use vfs::async_vfs::{AsyncMemoryFS, AsyncVfsPath}; - /// # use vfs::VfsError; - /// # tokio_test::block_on(async { - /// let path = AsyncVfsPath::new(AsyncMemoryFS::new()); - /// let src = path.join("foo")?; - /// src.join("dir")?.create_dir_all().await?; - /// let dest = path.join("bar.txt")?; - /// - /// src.copy_dir(&dest).await?; - /// - /// assert!(dest.join("dir")?.exists().await?); - /// # Ok::<(), VfsError>(()) - /// # }); - /// ``` - pub async fn copy_dir(&self, destination: &AsyncVfsPath) -> VfsResult { + pub async fn copy_dir(&self, destination: &AsyncVfsPath) -> VfsResult { let files_copied = async { let mut files_copied = 0u64; if destination.exists().await? { return Err(VfsError::from(VfsErrorKind::Other( "Destination exists already".into(), )) - .with_path(&destination.path)); + .with_path(&*destination.path)); } destination.create_dir().await?; - let prefix = self.path.as_str(); + let prefix = &self.path; let prefix_len = prefix.len(); let mut path_stream = self.walk_dir().await?; - while let Some(file) = path_stream.next().await { - let src_path: AsyncVfsPath = file?; + while let Some(result) = path_stream.next().await { + let src_path = result?; let dest_path = destination.join(&src_path.as_str()[prefix_len + 1..])?; match src_path.metadata().await?.file_type { VfsFileType::Directory => dest_path.create_dir().await?, - VfsFileType::File => src_path.copy_file(&dest_path).await?, + VfsFileType::File => { + src_path.copy_file(&dest_path).await?; + files_copied += 1; + } } - files_copied += 1; } Ok(files_copied) } .await - .map_err(|err| { - err.with_path(&self.path).with_context(|| { + .map_err(|err: VfsError| { + err.with_path(&*self.path).with_context(|| { format!( "Could not copy directory '{}' to '{}'", self.as_str(), @@ -950,162 +473,130 @@ impl AsyncVfsPath { } /// Moves a directory to a new destination, including subdirectories and files - /// - /// The destination must not exist, but its parent directory must - /// - /// ``` - /// # use vfs::async_vfs::{AsyncMemoryFS, AsyncVfsPath}; - /// # use vfs::VfsError; - /// # tokio_test::block_on(async { - /// let path = AsyncVfsPath::new(AsyncMemoryFS::new()); - /// let src = path.join("foo")?; - /// src.join("dir")?.create_dir_all().await?; - /// let dest = path.join("bar.txt")?; - /// - /// src.move_dir(&dest).await?; - /// - /// assert!(dest.join("dir")?.exists().await?); - /// assert!(!src.join("dir")?.exists().await?); - /// # Ok::<(), VfsError>(()) - /// # }); - /// ``` - pub async fn move_dir(&self, destination: &AsyncVfsPath) -> VfsResult<()> { + pub async fn move_dir(&self, destination: &AsyncVfsPath) -> VfsResult<(), E> { async { if destination.exists().await? { - return Err(VfsError::from(VfsErrorKind::Other( - "Destination exists already".into(), - )) - .with_path(&destination.path)); + return Err( + VfsError::from(VfsErrorKind::Other("Destination exists already".into())) + .with_path(&*destination.path), + ); } if Arc::ptr_eq(&self.fs, &destination.fs) { let result = self.fs.fs.move_dir(&self.path, &destination.path).await; - match result { - Err(err) => match err.kind() { - VfsErrorKind::NotSupported => { - // continue - } - _ => return Err(err), - }, - other => return other, + if !matches!(result, Err(ref err) if matches!(err.kind(), VfsErrorKind::NotSupported)) + { + return result; } } destination.create_dir().await?; - let prefix = self.path.as_str(); + let prefix = &self.path; let prefix_len = prefix.len(); let mut path_stream = self.walk_dir().await?; - while let Some(file) = path_stream.next().await { - let src_path: AsyncVfsPath = file?; + while let Some(result) = path_stream.next().await { + let src_path = result?; let dest_path = destination.join(&src_path.as_str()[prefix_len + 1..])?; match src_path.metadata().await?.file_type { VfsFileType::Directory => dest_path.create_dir().await?, - VfsFileType::File => src_path.copy_file(&dest_path).await?, + VfsFileType::File => src_path.move_file(&dest_path).await?, } } self.remove_dir_all().await?; Ok(()) } .await - .map_err(|err| { - err.with_path(&self.path).with_context(|| { + .map_err(|err: VfsError| { + err.with_path(&*self.path).with_context(|| { format!( "Could not move directory '{}' to '{}'", self.as_str(), destination.as_str() ) }) - })?; - Ok(()) + }) } } /// An iterator for recursively walking a file hierarchy -pub struct WalkDirIterator { - /// the path iterator of the current directory - inner: Box + Send + Unpin>, - /// stack of subdirectories still to walk - todo: Vec, - /// used to store the previous yield of the todo stream, - /// which would otherwise get dropped if path.metadata() is pending - prev_result: Option, - // Used to store futures when poll_next returns pending - // this ensures a new future is not spawned on each poll. - read_dir_fut: Option< - BoxFuture<'static, Result + Send + Unpin)>, VfsError>>, - >, - metadata_fut: Option>>, +pub struct WalkDirIterator { + inner: BoxStream<'static, AsyncVfsPath>, + todo: Vec>, + _pending_meta: Option>>, + pending_read: Option>, E>>>, } -impl std::fmt::Debug for WalkDirIterator { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str("WalkDirIterator")?; - self.todo.fmt(f) - } -} +impl Unpin for WalkDirIterator {} -impl Stream for WalkDirIterator { - type Item = VfsResult; +impl Stream for WalkDirIterator { + type Item = VfsResult, E>; fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let this = self.get_mut(); - // Check if we have a previously stored result from last call - // that we could not utilize due to pending path.metadata() call - let result = if this.prev_result.is_none() { - loop { - match this.inner.poll_next_unpin(cx) { - Poll::Pending => return Poll::Pending, - Poll::Ready(Some(path)) => break Ok(path), - Poll::Ready(None) => { - let directory = if this.todo.is_empty() { - return Poll::Ready(None); - } else { - this.todo[this.todo.len() - 1].clone() - }; - let mut read_dir_fut = if this.read_dir_fut.is_some() { - this.read_dir_fut.take().unwrap() - } else { - Box::pin(async move { directory.read_dir().await }) - }; - match read_dir_fut.poll_unpin(cx) { - Poll::Pending => { - this.read_dir_fut = Some(read_dir_fut); - return Poll::Pending; - } - Poll::Ready(Err(err)) => { - let _ = this.todo.pop(); - break Err(err); - } - Poll::Ready(Ok(iterator)) => { - let _ = this.todo.pop(); - this.inner = iterator; + + loop { + // First, try to poll the current directory's stream + match this.inner.poll_next_unpin(cx) { + Poll::Ready(Some(path)) => { + // We got a path, now check if it's a directory to add to `todo` + return Poll::Ready(Some(Ok(path))); + } + Poll::Ready(None) => { + // The current directory is exhausted. Try to pop from `todo`. + match this.todo.pop() { + None => return Poll::Ready(None), // All done + Some(dir_path) => { + let mut fut = this.pending_read.take().unwrap_or_else(|| { + let fut = async move { dir_path.read_dir().await }; + Box::pin(fut) + }); + match fut.poll_unpin(cx) { + Poll::Ready(Ok(stream)) => { + this.inner = stream; + continue; // Loop to poll the new stream + } + Poll::Ready(Err(e)) => return Poll::Ready(Some(Err(e))), + Poll::Pending => { + this.pending_read = Some(fut); + return Poll::Pending; + } } } } } - } - } else { - Ok(this.prev_result.take().unwrap()) - }; - if let Ok(path) = &result { - let mut metadata_fut = if this.metadata_fut.is_some() { - this.metadata_fut.take().unwrap() - } else { - let path_clone = path.clone(); - Box::pin(async move { path_clone.metadata().await }) - }; - match metadata_fut.poll_unpin(cx) { - Poll::Pending => { - this.prev_result = Some(path.clone()); - this.metadata_fut = Some(metadata_fut); - return Poll::Pending; - } - Poll::Ready(Ok(meta)) => { - if meta.file_type == VfsFileType::Directory { - this.todo.push(path.clone()); - } - } - Poll::Ready(Err(err)) => return Poll::Ready(Some(Err(err))), + Poll::Pending => return Poll::Pending, } } - Poll::Ready(Some(result)) + } +} + +#[derive(Debug)] +pub enum CopyError { + ReadError(VfsError), + WriteError(VfsError), +} + +const COPY_BUF_SIZE: usize = 8 * 1024; + +pub async fn simple_async_copy( + reader: &mut R, + writer: &mut W, +) -> Result> +where + R: AsyncSeekAndRead + Unpin + ?Sized, + W: AsyncSeekAndWrite + Unpin + ?Sized, +{ + let mut buf = [0; COPY_BUF_SIZE]; + let mut total_bytes_copied = 0; + loop { + let bytes_read = match reader.read(&mut buf).await { + Ok(0) => return Ok(total_bytes_copied), + Ok(n) => n, + Err(e) => return Err(CopyError::ReadError(e)), + }; + + writer + .write_all(&buf[..bytes_read]) + .await + .map_err(CopyError::WriteError)?; + total_bytes_copied += bytes_read as u64; } } From aa09abfd6a6ea87fbc2448da0492a8166f5d8262 Mon Sep 17 00:00:00 2001 From: Koichi Date: Fri, 27 Jun 2025 11:32:17 +0900 Subject: [PATCH 02/28] fix for unpin Signed-off-by: Koichi --- awkernel_async_lib/src/file/path.rs | 160 ++++++++++++++++++---------- 1 file changed, 101 insertions(+), 59 deletions(-) diff --git a/awkernel_async_lib/src/file/path.rs b/awkernel_async_lib/src/file/path.rs index e5dac09ba..c1150c9b5 100644 --- a/awkernel_async_lib/src/file/path.rs +++ b/awkernel_async_lib/src/file/path.rs @@ -31,23 +31,24 @@ use core::{ pin::Pin, task::{Context, Poll}, }; -use futures::{future::BoxFuture, stream::BoxStream, FutureExt, Stream, StreamExt}; +use futures::{future::BoxFuture, FutureExt, Stream, StreamExt}; -struct AsyncVfs { +struct AsyncVFS { fs: Box>, } /// A virtual filesystem path, identifying a single file or directory in this virtual filesystem #[derive(Clone)] pub struct AsyncVfsPath { - path: Arc, - fs: Arc>, + //path: Arc, + path: String, + fs: Arc>, } impl PathLike for AsyncVfsPath { type Error = E; fn get_path(&self) -> String { - self.path.to_string() + self.path.clone() } } @@ -62,7 +63,7 @@ impl Eq for AsyncVfsPath {} impl AsyncVfsPath { pub fn new_in_memory_fatfs() -> Self { let fs = AsyncFatFs::new_in_memory(); - AsyncVfsPath::new(fs) + AsyncVfsPath::from(fs) } } @@ -70,8 +71,8 @@ impl AsyncVfsPath { /// Creates a root path for the given filesystem pub fn new>(filesystem: T) -> Self { AsyncVfsPath { - path: "".into(), - fs: Arc::new(AsyncVfs { + path: "".to_string(), + fs: Arc::new(AsyncVFS { fs: Box::new(filesystem), }), } @@ -86,7 +87,7 @@ impl AsyncVfsPath { pub fn join(&self, path: impl AsRef) -> VfsResult { let new_path = self.join_internal(&self.path, path.as_ref())?; Ok(Self { - path: Arc::from(new_path), + path: new_path, fs: self.fs.clone(), }) } @@ -94,7 +95,7 @@ impl AsyncVfsPath { /// Returns the root path of this filesystem pub fn root(&self) -> Self { AsyncVfsPath { - path: "".into(), + path: "".to_string(), fs: self.fs.clone(), } } @@ -108,7 +109,7 @@ impl AsyncVfsPath { pub async fn create_dir(&self) -> VfsResult<(), E> { self.get_parent("create directory").await?; self.fs.fs.create_dir(&self.path).await.map_err(|err| { - err.with_path(&*self.path) + err.with_path(&self.path) .with_context(|| "Could not create directory") }) } @@ -147,23 +148,25 @@ impl AsyncVfsPath { } /// Iterates over all entries of this directory path - pub async fn read_dir(&self) -> VfsResult, E> { + pub async fn read_dir( + &self, + ) -> VfsResult> + Send>, E> { let parent = self.path.clone(); let fs = self.fs.clone(); - let stream = self - .fs - .fs - .read_dir(&self.path) - .await - .map_err(|err| { - err.with_path(&*self.path) - .with_context(|| "Could not read directory") - })? - .map(move |path_str| Self { - path: format!("{parent}/{path_str}").into(), - fs: fs.clone(), - }); - Ok(Box::pin(stream)) + Ok(Box::new( + self.fs + .fs + .read_dir(&self.path) + .await + .map_err(|err| { + err.with_path(&self.path) + .with_context(|| "Could not read directory") + })? + .map(move |path| AsyncVfsPath { + path: format!("{}/{}", parent, path), + fs: fs.clone(), + }), + )) } /// Creates a file at this path for writing, overwriting any existing file @@ -335,8 +338,9 @@ impl AsyncVfsPath { Ok(WalkDirIterator { inner: self.read_dir().await?, todo: vec![], - _pending_meta: None, - pending_read: None, + prev_result: None, + metadata_fut: None, + read_dir_fut: None, }) } @@ -518,10 +522,22 @@ impl AsyncVfsPath { /// An iterator for recursively walking a file hierarchy pub struct WalkDirIterator { - inner: BoxStream<'static, AsyncVfsPath>, + /// the path iterator of the current directory + inner: Box> + Send + Unpin>, + /// stack of subdirectories still to walk todo: Vec>, - _pending_meta: Option>>, - pending_read: Option>, E>>>, + /// used to store the previous yield of the todo stream, + /// which would otherwise get dropped if path.metadata() is pending + prev_result: Option>, + // Used to store futures when poll_next returns pending + // this ensures a new future is not spawned on each poll. + read_dir_fut: Option< + BoxFuture< + 'static, + Result> + Send + Unpin)>, VfsError>, + >, + >, + metadata_fut: Option>>>, } impl Unpin for WalkDirIterator {} @@ -531,40 +547,66 @@ impl Stream for WalkDirIterator { fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let this = self.get_mut(); - - loop { - // First, try to poll the current directory's stream - match this.inner.poll_next_unpin(cx) { - Poll::Ready(Some(path)) => { - // We got a path, now check if it's a directory to add to `todo` - return Poll::Ready(Some(Ok(path))); - } - Poll::Ready(None) => { - // The current directory is exhausted. Try to pop from `todo`. - match this.todo.pop() { - None => return Poll::Ready(None), // All done - Some(dir_path) => { - let mut fut = this.pending_read.take().unwrap_or_else(|| { - let fut = async move { dir_path.read_dir().await }; - Box::pin(fut) - }); - match fut.poll_unpin(cx) { - Poll::Ready(Ok(stream)) => { - this.inner = stream; - continue; // Loop to poll the new stream - } - Poll::Ready(Err(e)) => return Poll::Ready(Some(Err(e))), - Poll::Pending => { - this.pending_read = Some(fut); - return Poll::Pending; - } + // Check if we have a previously stored result from last call + // that we could not utilize due to pending path.metadata() call + let result = if this.prev_result.is_none() { + loop { + match this.inner.poll_next_unpin(cx) { + Poll::Pending => return Poll::Pending, + Poll::Ready(Some(path)) => break Ok(path), + Poll::Ready(None) => { + let directory = if this.todo.is_empty() { + return Poll::Ready(None); + } else { + this.todo[this.todo.len() - 1].clone() + }; + let mut read_dir_fut = if this.read_dir_fut.is_some() { + this.read_dir_fut.take().unwrap() + } else { + Box::pin(async move { directory.read_dir().await }) + }; + match read_dir_fut.poll_unpin(cx) { + Poll::Pending => { + this.read_dir_fut = Some(read_dir_fut); + return Poll::Pending; + } + Poll::Ready(Err(err)) => { + let _ = this.todo.pop(); + break Err(err); + } + Poll::Ready(Ok(iterator)) => { + let _ = this.todo.pop(); + this.inner = iterator; } } } } - Poll::Pending => return Poll::Pending, + } + } else { + Ok(this.prev_result.take().unwrap()) + }; + if let Ok(path) = &result { + let mut metadata_fut = if this.metadata_fut.is_some() { + this.metadata_fut.take().unwrap() + } else { + let path_clone = path.clone(); + Box::pin(async move { path_clone.metadata().await }) + }; + match metadata_fut.poll_unpin(cx) { + Poll::Pending => { + this.prev_result = Some(path.clone()); + this.metadata_fut = Some(metadata_fut); + return Poll::Pending; + } + Poll::Ready(Ok(meta)) => { + if meta.file_type == VfsFileType::Directory { + this.todo.push(path.clone()); + } + } + Poll::Ready(Err(err)) => return Poll::Ready(Some(Err(err))), } } + Poll::Ready(Some(result)) } } From 94cc80bf168714f6eb025340c6a97264f3ef3e0f Mon Sep 17 00:00:00 2001 From: Koichi Date: Fri, 27 Jun 2025 12:29:35 +0900 Subject: [PATCH 03/28] delete WalkDirIterator related Signed-off-by: Koichi --- awkernel_async_lib/src/file/path.rs | 323 +-------- awkernel_lib/src/file/vfs/path.rs | 979 +--------------------------- 2 files changed, 37 insertions(+), 1265 deletions(-) diff --git a/awkernel_async_lib/src/file/path.rs b/awkernel_async_lib/src/file/path.rs index c1150c9b5..b5edef60b 100644 --- a/awkernel_async_lib/src/file/path.rs +++ b/awkernel_async_lib/src/file/path.rs @@ -7,6 +7,7 @@ use crate::file::fatfs::AsyncFatFs; use super::filesystem::{AsyncFileSystem, AsyncSeekAndRead, AsyncSeekAndWrite}; use awkernel_lib::{ + console, file::{ error::IoError, memfs::InMemoryDiskError, @@ -40,7 +41,6 @@ struct AsyncVFS { /// A virtual filesystem path, identifying a single file or directory in this virtual filesystem #[derive(Clone)] pub struct AsyncVfsPath { - //path: Arc, path: String, fs: Arc>, } @@ -162,9 +162,12 @@ impl AsyncVfsPath { err.with_path(&self.path) .with_context(|| "Could not read directory") })? - .map(move |path| AsyncVfsPath { - path: format!("{}/{}", parent, path), - fs: fs.clone(), + .map(move |path| { + console::print(path.as_str()); + AsyncVfsPath { + path: format!("{}/{}", parent, path), + fs: fs.clone(), + } }), )) } @@ -173,7 +176,7 @@ impl AsyncVfsPath { pub async fn create_file(&self) -> VfsResult + Send + Unpin>, E> { self.get_parent("create file").await?; self.fs.fs.create_file(&self.path).await.map_err(|err| { - err.with_path(&*self.path) + err.with_path(&self.path) .with_context(|| "Could not create file") }) } @@ -181,7 +184,7 @@ impl AsyncVfsPath { /// Opens the file at this path for reading pub async fn open_file(&self) -> VfsResult + Send + Unpin>, E> { self.fs.fs.open_file(&self.path).await.map_err(|err| { - err.with_path(&*self.path) + err.with_path(&self.path) .with_context(|| "Could not open file") }) } @@ -193,14 +196,14 @@ impl AsyncVfsPath { return Err(VfsError::from(VfsErrorKind::Other(format!( "Could not {action}, parent directory does not exist" ))) - .with_path(&*self.path)); + .with_path(&self.path)); } let metadata = parent.metadata().await?; if metadata.file_type != VfsFileType::Directory { return Err(VfsError::from(VfsErrorKind::Other(format!( "Could not {action}, parent path is not a directory" ))) - .with_path(&*self.path)); + .with_path(&self.path)); } Ok(()) } @@ -208,7 +211,7 @@ impl AsyncVfsPath { /// Opens the file at this path for appending pub async fn append_file(&self) -> VfsResult + Send + Unpin>, E> { self.fs.fs.append_file(&self.path).await.map_err(|err| { - err.with_path(&*self.path) + err.with_path(&self.path) .with_context(|| "Could not open file for appending") }) } @@ -216,7 +219,7 @@ impl AsyncVfsPath { /// Removes the file at this path pub async fn remove_file(&self) -> VfsResult<(), E> { self.fs.fs.remove_file(&self.path).await.map_err(|err| { - err.with_path(&*self.path) + err.with_path(&self.path) .with_context(|| "Could not remove file") }) } @@ -224,7 +227,7 @@ impl AsyncVfsPath { /// Removes the directory at this path pub async fn remove_dir(&self) -> VfsResult<(), E> { self.fs.fs.remove_dir(&self.path).await.map_err(|err| { - err.with_path(&*self.path) + err.with_path(&self.path) .with_context(|| "Could not remove directory") }) } @@ -250,7 +253,7 @@ impl AsyncVfsPath { /// Returns the file metadata for the file at this path pub async fn metadata(&self) -> VfsResult { self.fs.fs.metadata(&self.path).await.map_err(|err| { - err.with_path(&*self.path) + err.with_path(&self.path) .with_context(|| "Could not get metadata") }) } @@ -262,7 +265,7 @@ impl AsyncVfsPath { .set_creation_time(&self.path, time) .await .map_err(|err| { - err.with_path(&*self.path) + err.with_path(&self.path) .with_context(|| "Could not set creation timestamp.") }) } @@ -274,7 +277,7 @@ impl AsyncVfsPath { .set_modification_time(&self.path, time) .await .map_err(|err| { - err.with_path(&*self.path) + err.with_path(&self.path) .with_context(|| "Could not set modification timestamp.") }) } @@ -286,7 +289,7 @@ impl AsyncVfsPath { .set_access_time(&self.path, time) .await .map_err(|err| { - err.with_path(&*self.path) + err.with_path(&self.path) .with_context(|| "Could not set access timestamp.") }) } @@ -328,29 +331,18 @@ impl AsyncVfsPath { pub fn parent(&self) -> Self { let parent_path = self.parent_internal(&self.path); Self { - path: parent_path.into(), + path: parent_path, fs: self.fs.clone(), } } - /// Recursively iterates over all the directories and files at this path - pub async fn walk_dir(&self) -> VfsResult, E> { - Ok(WalkDirIterator { - inner: self.read_dir().await?, - todo: vec![], - prev_result: None, - metadata_fut: None, - read_dir_fut: None, - }) - } - /// Reads a complete file to a string pub async fn read_to_string(&self) -> VfsResult { let metadata = self.metadata().await?; if metadata.file_type != VfsFileType::File { return Err( VfsError::from(VfsErrorKind::Other("Path is a directory".into())) - .with_path(&*self.path) + .with_path(&self.path) .with_context(|| "Could not read path"), ); } @@ -360,285 +352,14 @@ impl AsyncVfsPath { .read_exact(&mut buffer) .await .map_err(|err| { - err.with_path(&*self.path) + err.with_path(&self.path) .with_context(|| "Could not read path") })?; String::from_utf8(buffer).map_err(|_| { VfsError::from(VfsErrorKind::Other("Invalid UTF-8 sequence".into())) - .with_path(&*self.path) + .with_path(&self.path) .with_context(|| "Could not read path as string") }) } - - /// Copies a file to a new destination - pub async fn copy_file(&self, destination: &AsyncVfsPath) -> VfsResult<(), E> { - async { - if destination.exists().await? { - return Err( - VfsError::from(VfsErrorKind::Other("Destination exists already".into())) - .with_path(&*self.path), - ); - } - if Arc::ptr_eq(&self.fs, &destination.fs) { - let result = self.fs.fs.copy_file(&self.path, &destination.path).await; - if !matches!(result, Err(ref err) if matches!(err.kind(), VfsErrorKind::NotSupported)) - { - return result; - } - } - let mut src = self.open_file().await?; - let mut dest = destination.create_file().await?; - simple_async_copy(&mut src, &mut dest).await.map_err(|err| match err { - CopyError::ReadError(e) => e, - CopyError::WriteError(e) => e, - })?; - Ok(()) - } - .await - .map_err(|err| { - err.with_path(&*self.path).with_context(|| { - format!("Could not copy '{}' to '{}'", self.as_str(), destination.as_str()) - }) - }) - } - - /// Moves or renames a file to a new destination - pub async fn move_file(&self, destination: &AsyncVfsPath) -> VfsResult<(), E> { - async { - if destination.exists().await? { - return Err(VfsError::from(VfsErrorKind::Other( - "Destination exists already".into(), - )) - .with_path(&*destination.path)); - } - if Arc::ptr_eq(&self.fs, &destination.fs) { - let result = self.fs.fs.move_file(&self.path, &destination.path).await; - if !matches!(result, Err(ref err) if matches!(err.kind(), VfsErrorKind::NotSupported)) - { - return result; - } - } - let mut src = self.open_file().await?; - let mut dest = destination.create_file().await?; - simple_async_copy(&mut src, &mut dest).await.map_err(|err| match err { - CopyError::ReadError(e) => e, - CopyError::WriteError(e) => e, - })?; - self.remove_file().await?; - Ok(()) - } - .await - .map_err(|err| { - err.with_path(&*self.path).with_context(|| { - format!("Could not move '{}' to '{}'", self.as_str(), destination.as_str()) - }) - }) - } - - /// Copies a directory to a new destination, recursively - pub async fn copy_dir(&self, destination: &AsyncVfsPath) -> VfsResult { - let files_copied = async { - let mut files_copied = 0u64; - if destination.exists().await? { - return Err(VfsError::from(VfsErrorKind::Other( - "Destination exists already".into(), - )) - .with_path(&*destination.path)); - } - destination.create_dir().await?; - let prefix = &self.path; - let prefix_len = prefix.len(); - let mut path_stream = self.walk_dir().await?; - while let Some(result) = path_stream.next().await { - let src_path = result?; - let dest_path = destination.join(&src_path.as_str()[prefix_len + 1..])?; - match src_path.metadata().await?.file_type { - VfsFileType::Directory => dest_path.create_dir().await?, - VfsFileType::File => { - src_path.copy_file(&dest_path).await?; - files_copied += 1; - } - } - } - Ok(files_copied) - } - .await - .map_err(|err: VfsError| { - err.with_path(&*self.path).with_context(|| { - format!( - "Could not copy directory '{}' to '{}'", - self.as_str(), - destination.as_str() - ) - }) - })?; - Ok(files_copied) - } - - /// Moves a directory to a new destination, including subdirectories and files - pub async fn move_dir(&self, destination: &AsyncVfsPath) -> VfsResult<(), E> { - async { - if destination.exists().await? { - return Err( - VfsError::from(VfsErrorKind::Other("Destination exists already".into())) - .with_path(&*destination.path), - ); - } - if Arc::ptr_eq(&self.fs, &destination.fs) { - let result = self.fs.fs.move_dir(&self.path, &destination.path).await; - if !matches!(result, Err(ref err) if matches!(err.kind(), VfsErrorKind::NotSupported)) - { - return result; - } - } - destination.create_dir().await?; - let prefix = &self.path; - let prefix_len = prefix.len(); - let mut path_stream = self.walk_dir().await?; - while let Some(result) = path_stream.next().await { - let src_path = result?; - let dest_path = destination.join(&src_path.as_str()[prefix_len + 1..])?; - match src_path.metadata().await?.file_type { - VfsFileType::Directory => dest_path.create_dir().await?, - VfsFileType::File => src_path.move_file(&dest_path).await?, - } - } - self.remove_dir_all().await?; - Ok(()) - } - .await - .map_err(|err: VfsError| { - err.with_path(&*self.path).with_context(|| { - format!( - "Could not move directory '{}' to '{}'", - self.as_str(), - destination.as_str() - ) - }) - }) - } -} - -/// An iterator for recursively walking a file hierarchy -pub struct WalkDirIterator { - /// the path iterator of the current directory - inner: Box> + Send + Unpin>, - /// stack of subdirectories still to walk - todo: Vec>, - /// used to store the previous yield of the todo stream, - /// which would otherwise get dropped if path.metadata() is pending - prev_result: Option>, - // Used to store futures when poll_next returns pending - // this ensures a new future is not spawned on each poll. - read_dir_fut: Option< - BoxFuture< - 'static, - Result> + Send + Unpin)>, VfsError>, - >, - >, - metadata_fut: Option>>>, -} - -impl Unpin for WalkDirIterator {} - -impl Stream for WalkDirIterator { - type Item = VfsResult, E>; - - fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - let this = self.get_mut(); - // Check if we have a previously stored result from last call - // that we could not utilize due to pending path.metadata() call - let result = if this.prev_result.is_none() { - loop { - match this.inner.poll_next_unpin(cx) { - Poll::Pending => return Poll::Pending, - Poll::Ready(Some(path)) => break Ok(path), - Poll::Ready(None) => { - let directory = if this.todo.is_empty() { - return Poll::Ready(None); - } else { - this.todo[this.todo.len() - 1].clone() - }; - let mut read_dir_fut = if this.read_dir_fut.is_some() { - this.read_dir_fut.take().unwrap() - } else { - Box::pin(async move { directory.read_dir().await }) - }; - match read_dir_fut.poll_unpin(cx) { - Poll::Pending => { - this.read_dir_fut = Some(read_dir_fut); - return Poll::Pending; - } - Poll::Ready(Err(err)) => { - let _ = this.todo.pop(); - break Err(err); - } - Poll::Ready(Ok(iterator)) => { - let _ = this.todo.pop(); - this.inner = iterator; - } - } - } - } - } - } else { - Ok(this.prev_result.take().unwrap()) - }; - if let Ok(path) = &result { - let mut metadata_fut = if this.metadata_fut.is_some() { - this.metadata_fut.take().unwrap() - } else { - let path_clone = path.clone(); - Box::pin(async move { path_clone.metadata().await }) - }; - match metadata_fut.poll_unpin(cx) { - Poll::Pending => { - this.prev_result = Some(path.clone()); - this.metadata_fut = Some(metadata_fut); - return Poll::Pending; - } - Poll::Ready(Ok(meta)) => { - if meta.file_type == VfsFileType::Directory { - this.todo.push(path.clone()); - } - } - Poll::Ready(Err(err)) => return Poll::Ready(Some(Err(err))), - } - } - Poll::Ready(Some(result)) - } -} - -#[derive(Debug)] -pub enum CopyError { - ReadError(VfsError), - WriteError(VfsError), -} - -const COPY_BUF_SIZE: usize = 8 * 1024; - -pub async fn simple_async_copy( - reader: &mut R, - writer: &mut W, -) -> Result> -where - R: AsyncSeekAndRead + Unpin + ?Sized, - W: AsyncSeekAndWrite + Unpin + ?Sized, -{ - let mut buf = [0; COPY_BUF_SIZE]; - let mut total_bytes_copied = 0; - loop { - let bytes_read = match reader.read(&mut buf).await { - Ok(0) => return Ok(total_bytes_copied), - Ok(n) => n, - Err(e) => return Err(CopyError::ReadError(e)), - }; - - writer - .write_all(&buf[..bytes_read]) - .await - .map_err(CopyError::WriteError)?; - total_bytes_copied += bytes_read as u64; - } } diff --git a/awkernel_lib/src/file/vfs/path.rs b/awkernel_lib/src/file/vfs/path.rs index 08146132a..c2b4b06e3 100644 --- a/awkernel_lib/src/file/vfs/path.rs +++ b/awkernel_lib/src/file/vfs/path.rs @@ -3,12 +3,16 @@ //! The virtual file system abstraction generalizes over file systems and allow using //! different VirtualFileSystem implementations (i.e. an in memory implementation for unit tests) -use std::io::{Read, Seek, Write}; -use std::sync::Arc; -use std::time::SystemTime; +use super::super::error::IoError; +use super::super::io::{Read, Seek, Write}; +use crate::time::Time; +use alloc::{ + string::{String, ToString}, + vec::Vec, +}; -use crate::error::VfsErrorKind; -use crate::{FileSystem, VfsError, VfsResult}; +use super::error::VfsErrorKind; +use super::{error::VfsError, error::VfsResult}; /// Trait combining Seek and Read, return value for opening files pub trait SeekAndRead: Seek + Read {} @@ -21,7 +25,8 @@ impl SeekAndRead for T where T: Seek + Read {} impl SeekAndWrite for T where T: Seek + Write {} /// A trait for common non-async behaviour of both sync and async paths -pub(crate) trait PathLike: Clone { +pub trait PathLike: Clone { + type Error: IoError; fn get_path(&self) -> String; fn filename_internal(&self) -> String { let path = self.get_path(); @@ -45,7 +50,7 @@ pub(crate) trait PathLike: Clone { index.map(|idx| path[..idx].to_string()).unwrap_or_default() } - fn join_internal(&self, in_path: &str, path: &str) -> VfsResult { + fn join_internal(&self, in_path: &str, path: &str) -> VfsResult { if path.is_empty() { return Ok(in_path.to_string()); } @@ -109,963 +114,9 @@ pub struct VfsMetadata { /// Length of the file in bytes, 0 for directories pub len: u64, /// Creation time of the file, if supported by the vfs implementation - pub created: Option, + pub created: Option