From cb34789f81e36415c26c847ad8f1515d28147835 Mon Sep 17 00:00:00 2001 From: Youjie Zheng Date: Sat, 22 Feb 2025 17:01:42 +0800 Subject: [PATCH] [feat] Support for ext4 junior --- Cargo.lock | 10 + api/arceos_posix_api/ctypes.h | 2 +- api/arceos_posix_api/src/imp/fd_ops.rs | 18 +- api/arceos_posix_api/src/imp/fs.rs | 175 +++++++- api/arceos_posix_api/src/imp/mod.rs | 2 + api/arceos_posix_api/src/imp/path_link.rs | 392 +++++++++++++++++ api/arceos_posix_api/src/imp/time.rs | 14 + api/arceos_posix_api/src/lib.rs | 13 +- api/arceos_posix_api/src/utils.rs | 2 + api/axfeat/Cargo.toml | 1 + modules/axfs/Cargo.toml | 3 + modules/axfs/src/api/mod.rs | 5 + modules/axfs/src/dev.rs | 31 +- modules/axfs/src/fops.rs | 54 ++- modules/axfs/src/fs/fatfs.rs | 112 ++++- modules/axfs/src/fs/lwext4_rust.rs | 404 ++++++++++++++++++ modules/axfs/src/fs/mod.rs | 3 + modules/axfs/src/lib.rs | 1 + modules/axfs/src/root.rs | 53 ++- .../src/platform/riscv64_qemu_virt/console.rs | 15 + .../src/platform/riscv64_qemu_virt/mod.rs | 2 + modules/axmm/src/aspace.rs | 136 +++++- modules/axtask/src/api.rs | 10 +- modules/axtask/src/task.rs | 39 +- ulib/axstd/Cargo.toml | 1 + 25 files changed, 1438 insertions(+), 60 deletions(-) create mode 100644 api/arceos_posix_api/src/imp/path_link.rs create mode 100644 modules/axfs/src/fs/lwext4_rust.rs diff --git a/Cargo.lock b/Cargo.lock index 9d11c86299..efc82002ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -411,6 +411,8 @@ dependencies = [ "fatfs", "lazyinit", "log", + "lwext4_rust", + "spin", ] [[package]] @@ -1230,6 +1232,14 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "lwext4_rust" +version = "0.2.0" +source = "git+https://github.com/elliott10/lwext4_rust.git#83489293ef4535672caee0c5755729f3e0315a96" +dependencies = [ + "log", +] + [[package]] name = "managed" version = "0.8.0" diff --git a/api/arceos_posix_api/ctypes.h b/api/arceos_posix_api/ctypes.h index 1acb874677..1ff3448bc3 100644 --- a/api/arceos_posix_api/ctypes.h +++ b/api/arceos_posix_api/ctypes.h @@ -3,7 +3,6 @@ #include #include #include -#include #include #include #include @@ -12,4 +11,5 @@ #include #include #include +#include #include diff --git a/api/arceos_posix_api/src/imp/fd_ops.rs b/api/arceos_posix_api/src/imp/fd_ops.rs index 7274b4cb13..42b5a3aab0 100644 --- a/api/arceos_posix_api/src/imp/fd_ops.rs +++ b/api/arceos_posix_api/src/imp/fd_ops.rs @@ -23,9 +23,23 @@ pub trait FileLike: Send + Sync { } def_resource! { - pub(crate) static FD_TABLE: ResArc, AX_FILE_LIMIT>>> = ResArc::new(); + pub static FD_TABLE: ResArc, AX_FILE_LIMIT>>> = ResArc::new(); } +impl FD_TABLE { + /// Return a copy of the inner table. + pub fn copy_inner(&self) -> RwLock, AX_FILE_LIMIT>> { + let table = self.read(); + let mut new_table = FlattenObjects::new(); + let count = table.count(); + for i in 0..count { + let _ = new_table.add_at(i, table.get(i).unwrap().clone()); + } + RwLock::new(new_table) + } +} + +/// Get a file by `fd`. pub fn get_file_like(fd: c_int) -> LinuxResult> { FD_TABLE .read() @@ -34,10 +48,12 @@ pub fn get_file_like(fd: c_int) -> LinuxResult> { .ok_or(LinuxError::EBADF) } +/// Add a file to the file descriptor table. pub fn add_file_like(f: Arc) -> LinuxResult { Ok(FD_TABLE.write().add(f).map_err(|_| LinuxError::EMFILE)? as c_int) } +/// Close a file by `fd`. pub fn close_file_like(fd: c_int) -> LinuxResult { let f = FD_TABLE .write() diff --git a/api/arceos_posix_api/src/imp/fs.rs b/api/arceos_posix_api/src/imp/fs.rs index 0e877e6081..73997ecc6c 100644 --- a/api/arceos_posix_api/src/imp/fs.rs +++ b/api/arceos_posix_api/src/imp/fs.rs @@ -1,3 +1,4 @@ +use alloc::string::{String, ToString}; use alloc::sync::Arc; use core::ffi::{c_char, c_int}; @@ -7,16 +8,20 @@ use axio::{PollState, SeekFrom}; use axsync::Mutex; use super::fd_ops::{FileLike, get_file_like}; +use crate::AT_FDCWD; use crate::{ctypes, utils::char_ptr_to_str}; +/// File wrapper for `axfs::fops::File`. pub struct File { inner: Mutex, + path: String, } impl File { - fn new(inner: axfs::fops::File) -> Self { + fn new(inner: axfs::fops::File, path: String) -> Self { Self { inner: Mutex::new(inner), + path, } } @@ -30,6 +35,16 @@ impl File { .downcast::() .map_err(|_| LinuxError::EINVAL) } + + /// Get the path of the file. + pub fn path(&self) -> &str { + &self.path + } + + /// Get the inner node of the file. + pub fn inner(&self) -> &Mutex { + &self.inner + } } impl FileLike for File { @@ -97,7 +112,11 @@ fn flags_to_options(flags: c_int, _mode: ctypes::mode_t) -> OpenOptions { options.create(true); } if flags & ctypes::O_EXEC != 0 { - options.create_new(true); + //options.create_new(true); + options.execute(true); + } + if flags & ctypes::O_DIRECTORY != 0 { + options.directory(true); } options } @@ -111,11 +130,89 @@ pub fn sys_open(filename: *const c_char, flags: c_int, mode: ctypes::mode_t) -> debug!("sys_open <= {:?} {:#o} {:#o}", filename, flags, mode); syscall_body!(sys_open, { let options = flags_to_options(flags, mode); - let file = axfs::fops::File::open(filename?, &options)?; - File::new(file).add_to_fd_table() + if options.has_directory() { + return Directory::from_path(filename?.into(), &options) + .and_then(Directory::add_to_fd_table); + } + add_file_or_directory_fd( + axfs::fops::File::open, + axfs::fops::Directory::open_dir, + filename?, + &options, + ) }) } +/// Open or create a file. +/// fd: file descriptor +/// filename: file path to be opened or created +/// flags: open flags +/// mode: see man 7 inode +/// return new file descriptor if succeed, or return -1. +pub fn sys_openat( + dirfd: c_int, + filename: *const c_char, + flags: c_int, + mode: ctypes::mode_t, +) -> c_int { + let filename = match char_ptr_to_str(filename) { + Ok(s) => s, + Err(_) => return -1, + }; + + debug!( + "sys_openat <= {} {:?} {:#o} {:#o}", + dirfd, filename, flags, mode + ); + + let options = flags_to_options(flags, mode); + + if filename.starts_with('/') || dirfd == AT_FDCWD as _ { + return sys_open(filename.as_ptr() as _, flags, mode); + } + + match Directory::from_fd(dirfd).and_then(|dir| { + add_file_or_directory_fd( + |filename, options| dir.inner.lock().open_file_at(filename, options), + |filename, options| dir.inner.lock().open_dir_at(filename, options), + filename, + &options, + ) + }) { + Ok(fd) => fd, + Err(e) => { + debug!("sys_openat => {}", e); + -1 + } + } +} + +/// Use the function to open file or directory, then add into file descriptor table. +/// First try opening files, if fails, try directory. +fn add_file_or_directory_fd( + open_file: F, + open_dir: D, + filename: &str, + options: &OpenOptions, +) -> LinuxResult +where + E: Into, + F: FnOnce(&str, &OpenOptions) -> Result, + D: FnOnce(&str, &OpenOptions) -> Result, +{ + open_file(filename, options) + .map_err(Into::into) + .map(|f| File::new(f, filename.into())) + .and_then(File::add_to_fd_table) + .or_else(|e| match e { + LinuxError::EISDIR => open_dir(filename, options) + .map_err(Into::into) + .map(|d| Directory::new(d, filename.into())) + .and_then(Directory::add_to_fd_table), + _ => Err(e), + }) +} + /// Set the position of the file indicated by `fd`. /// /// Return its position after seek. @@ -146,7 +243,7 @@ pub unsafe fn sys_stat(path: *const c_char, buf: *mut ctypes::stat) -> c_int { let mut options = OpenOptions::new(); options.read(true); let file = axfs::fops::File::open(path?, &options)?; - let st = File::new(file).stat()?; + let st = File::new(file, path?.to_string()).stat()?; unsafe { *buf = st }; Ok(0) }) @@ -183,7 +280,6 @@ pub unsafe fn sys_lstat(path: *const c_char, buf: *mut ctypes::stat) -> ctypes:: } /// Get the path of the current directory. -#[allow(clippy::unnecessary_cast)] // `c_char` is either `i8` or `u8` pub fn sys_getcwd(buf: *mut c_char, size: usize) -> *mut c_char { debug!("sys_getcwd <= {:#x} {}", buf as usize, size); syscall_body!(sys_getcwd, { @@ -216,3 +312,70 @@ pub fn sys_rename(old: *const c_char, new: *const c_char) -> c_int { Ok(0) }) } + +/// Directory wrapper for `axfs::fops::Directory`. +pub struct Directory { + inner: Mutex, + path: String, +} + +impl Directory { + fn new(inner: axfs::fops::Directory, path: String) -> Self { + Self { + inner: Mutex::new(inner), + path, + } + } + + fn from_path(path: String, options: &OpenOptions) -> LinuxResult { + axfs::fops::Directory::open_dir(&path, options) + .map_err(Into::into) + .map(|d| Self::new(d, path)) + } + + fn add_to_fd_table(self) -> LinuxResult { + super::fd_ops::add_file_like(Arc::new(self)) + } + + /// Open a directory by `fd`. + pub fn from_fd(fd: c_int) -> LinuxResult> { + let f = super::fd_ops::get_file_like(fd)?; + f.into_any() + .downcast::() + .map_err(|_| LinuxError::EINVAL) + } + + /// Get the path of the directory. + pub fn path(&self) -> &str { + &self.path + } +} + +impl FileLike for Directory { + fn read(&self, _buf: &mut [u8]) -> LinuxResult { + Err(LinuxError::EBADF) + } + + fn write(&self, _buf: &[u8]) -> LinuxResult { + Err(LinuxError::EBADF) + } + + fn stat(&self) -> LinuxResult { + Err(LinuxError::EBADF) + } + + fn into_any(self: Arc) -> Arc { + self + } + + fn poll(&self) -> LinuxResult { + Ok(PollState { + readable: true, + writable: false, + }) + } + + fn set_nonblocking(&self, _nonblocking: bool) -> LinuxResult { + Ok(()) + } +} diff --git a/api/arceos_posix_api/src/imp/mod.rs b/api/arceos_posix_api/src/imp/mod.rs index 603f934baa..5e1945f3ab 100644 --- a/api/arceos_posix_api/src/imp/mod.rs +++ b/api/arceos_posix_api/src/imp/mod.rs @@ -14,6 +14,8 @@ pub mod fs; pub mod io_mpx; #[cfg(feature = "net")] pub mod net; +#[cfg(feature = "fs")] +pub mod path_link; #[cfg(feature = "pipe")] pub mod pipe; #[cfg(feature = "multitask")] diff --git a/api/arceos_posix_api/src/imp/path_link.rs b/api/arceos_posix_api/src/imp/path_link.rs new file mode 100644 index 0000000000..3a43daa3f1 --- /dev/null +++ b/api/arceos_posix_api/src/imp/path_link.rs @@ -0,0 +1,392 @@ +use alloc::collections::BTreeMap; +use alloc::format; +use core::fmt; +use core::ops::Deref; +use spin::RwLock; + +use alloc::string::{String, ToString}; +use axerrno::{AxError, AxResult}; +use axfs::api::{canonicalize, current_dir}; + +use super::fd_ops::FD_TABLE; + +/// 一个规范化的文件路径表示 +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug)] +pub struct FilePath(String); + +impl FilePath { + /// 从路径字符串创建一个新的 `FilePath`,路径将被规范化。 + /// 输入路径可以是绝对路径或相对路径。 + pub fn new>(path: P) -> AxResult { + let path = path.as_ref(); + let canonical = canonicalize(path).map_err(|_| AxError::NotFound)?; + let mut new_path = canonical.trim().to_string(); + + // 如果原始路径以 '/' 结尾,那么规范化后的路径也应以 '/' 结尾 + if path.ends_with('/') && !new_path.ends_with('/') { + new_path.push('/'); + } + + assert!( + new_path.starts_with('/'), + "canonical path should start with /" + ); + + Ok(Self(HARDLINK_MANAGER.real_path(&new_path))) + } + + /// 返回底层路径的字符串切片 + pub fn as_str(&self) -> &str { + &self.0 + } + + /// 返回父目录路径 + pub fn parent(&self) -> AxResult<&str> { + if self.is_root() { + return Ok("/"); + } + + // 查找最后一个斜杠,考虑可能的尾部斜杠 + let mut path = self.as_str(); + if path.ends_with('/') { + path = path.strip_suffix('/').unwrap(); + } + let pos = path.rfind('/').ok_or(AxError::NotFound)?; + + Ok(&path[..=pos]) + } + + /// 返回文件名或目录名组件 + pub fn name(&self) -> AxResult<&str> { + if self.is_root() { + return Ok("/"); + } + + let mut path = self.as_str(); + if path.ends_with('/') { + path = path.strip_suffix('/').unwrap(); + } + let start_pos = path.rfind('/').ok_or(AxError::NotFound)?; + + let end_pos = if path.ends_with('/') { + path.len() - 1 + } else { + path.len() + }; + Ok(&path[start_pos + 1..end_pos]) + } + + /// 判断是否为根目录 + pub fn is_root(&self) -> bool { + self.0 == "/" + } + + /// 判断是否为目录(以 '/' 结尾) + pub fn is_dir(&self) -> bool { + self.0.ends_with('/') + } + + /// 判断是否为常规文件(不以 '/' 结尾) + pub fn is_file(&self) -> bool { + !self.is_dir() + } + + /// Whether the path exists + pub fn exists(&self) -> bool { + axfs::api::absolute_path_exists(&self.0) + } + + /// 判断此路径是否以给定前缀路径开头 + pub fn starts_with(&self, prefix: &FilePath) -> bool { + self.0.starts_with(&prefix.0) + } + + /// 判断此路径是否以给定后缀路径结尾 + pub fn ends_with(&self, suffix: &FilePath) -> bool { + self.0.ends_with(&suffix.0) + } + + /// 将此路径与相对路径组件连接 + pub fn join>(&self, path: P) -> AxResult { + let mut new_path = self.0.clone(); + if !new_path.ends_with('/') { + new_path.push('/'); + } + new_path.push_str(path.as_ref()); + FilePath::new(new_path) + } + + /// 返回此路径组件的迭代器 + pub fn components(&self) -> impl Iterator { + self.0.trim_matches('/').split('/') + } +} + +impl fmt::Display for FilePath { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(&self.0) + } +} + +impl AsRef for FilePath { + fn as_ref(&self) -> &str { + &self.0 + } +} + +impl From<&str> for FilePath { + fn from(s: &str) -> Self { + FilePath::new(s).unwrap() + } +} + +impl Deref for FilePath { + type Target = str; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +/// 错误类型 +#[derive(Debug)] +pub enum LinkError { + LinkExists, // 链接已存在 + InvalidPath, // 无效路径 + NotFound, // 文件不存在 + NotFile, // 不是文件 +} + +impl From for AxError { + fn from(err: LinkError) -> AxError { + match err { + LinkError::LinkExists => AxError::AlreadyExists, + LinkError::InvalidPath => AxError::InvalidInput, + LinkError::NotFound => AxError::NotFound, + LinkError::NotFile => AxError::InvalidInput, + } + } +} + +/// A global hardlink manager +pub static HARDLINK_MANAGER: HardlinkManager = HardlinkManager::new(); + +/// A manager for hardlinks +pub struct HardlinkManager { + inner: RwLock, +} +struct LinkManagerInner { + links: BTreeMap, + ref_counts: BTreeMap, +} + +// 关于innner的操作都在atomic_开头的函数中 +impl HardlinkManager { + pub const fn new() -> Self { + Self { + inner: RwLock::new(LinkManagerInner { + links: BTreeMap::new(), + ref_counts: BTreeMap::new(), + }), + } + } + + /// 创建链接 + /// 如果目标路径不存在,则返回 `LinkError::NotFound` + /// 如果目标路径不是文件,则返回 `LinkError::NotFile` + pub fn create_link(&self, src: &FilePath, dst: &FilePath) -> Result<(), LinkError> { + if !dst.exists() { + return Err(LinkError::NotFound); + } + if !dst.is_dir() { + return Err(LinkError::NotFile); + } + + let mut inner = self.inner.write(); + self.atomic_link_update(&mut inner, src, dst); + Ok(()) + } + + /// 移除链接 + /// 链接数量为零 或 没有链接时, 删除文件 + /// 如果路径对应的链接不存在 或 路径对应的文件不存在,则返回 `None` + /// 否则返回链接的目标路径 + pub fn remove_link(&self, src: &FilePath) -> Option { + let mut inner = self.inner.write(); + self.atomic_link_remove(&mut inner, src).or_else(|| { + axfs::api::remove_file(src.as_str()) + .ok() + .map(|_| src.to_string()) + }) + } + + pub fn real_path(&self, path: &str) -> String { + self.inner + .read() + .links + .get(path) + .cloned() + .unwrap_or_else(|| path.to_string()) + } + + pub fn link_count(&self, path: &FilePath) -> usize { + let inner = self.inner.read(); + inner + .ref_counts + .get(path.as_str()) + .copied() + .unwrap_or_else(|| if path.exists() { 1 } else { 0 }) + } + + // 原子操作helpers + + /// 创建或更新链接 + /// 如果链接已存在,则更新目标路径 + /// 如果目标路径不存在,则返回 `LinkError::NotFound` + fn atomic_link_update(&self, inner: &mut LinkManagerInner, src: &FilePath, dst: &FilePath) { + if let Some(old_dst) = inner.links.get(src.as_str()) { + if old_dst == dst.as_str() { + return; + } + self.decrease_ref_count(inner, &old_dst.to_string()); + } + inner.links.insert(src.to_string(), dst.to_string()); + *inner.ref_counts.entry(dst.to_string()).or_insert(0) += 1; + } + + /// 移除链接 + /// 如果链接不存在,则返回 `None`,否则返回链接的目标路径 + fn atomic_link_remove(&self, inner: &mut LinkManagerInner, src: &FilePath) -> Option { + inner.links.remove(src.as_str()).inspect(|dst| { + self.decrease_ref_count(inner, dst); + }) + } + + /// 减少引用计数 + /// 如果引用计数为零,则删除链接,并删除文件,如果删除文件失败,则返回 `None` + /// 如果链接不存在,则返回 `None` + fn decrease_ref_count(&self, inner: &mut LinkManagerInner, path: &str) -> Option<()> { + match inner.ref_counts.get_mut(path) { + Some(count) => { + *count -= 1; + if *count == 0 { + inner.ref_counts.remove(path); + axfs::api::remove_file(path).ok()? + } + Some(()) + } + None => { + axlog::error!("link exists but ref count is zero"); + None + } + } + } +} + +/// A constant representing the current working directory +pub const AT_FDCWD: isize = -100; + +/// 处理路径并返回规范化后的 `FilePath` +/// +/// * `dir_fd` - 目录的文件描述符,如果是 `AT_FDCWD`,则操作当前工作目录 +/// +/// * `path_addr` - 路径的地址,如果为 `None`,则操作由 `dir_fd` 指定的文件 +/// +/// * `force_dir` - 如果为 `true`,则将路径视为目录 +/// +/// 该函数会处理链接并规范化路径 +pub fn handle_file_path( + dir_fd: isize, + path_addr: Option<*const u8>, + force_dir: bool, +) -> AxResult { + // 获取路径字符串 + let path = match path_addr { + Some(addr) => { + if addr.is_null() { + axlog::warn!("路径地址为空"); + return Err(AxError::BadAddress); + } + crate::utils::char_ptr_to_str(addr as *const _) + .map_err(|_| AxError::NotFound)? + .to_string() + } + None => String::new(), + }; + + // 处理空路径的情况 + let mut path = if path.is_empty() { + handle_empty_path(dir_fd)? + } else { + path + }; + + // 处理相对路径的情况 + if !path.starts_with('/') { + if dir_fd == AT_FDCWD { + path = prepend_cwd(&path)?; + } else { + path = handle_relative_path(dir_fd, &path)?; + } + } + + // 根据 `force_dir` 和路径结尾调整路径 + path = adjust_path_suffix(path, force_dir); + + // 创建并返回 `FilePath` + FilePath::new(&path) +} + +fn handle_empty_path(dir_fd: isize) -> AxResult { + const AT_FDCWD: isize = -100; + if dir_fd == AT_FDCWD { + return Ok(String::from(".")); + } + + let fd_table = FD_TABLE.write(); + if dir_fd >= fd_table.count() as isize || dir_fd < 0 { + axlog::warn!("文件描述符索引超出范围"); + return Err(AxError::InvalidInput); + } + super::fs::Directory::from_fd(dir_fd as i32) + .map(|dir| dir.path().to_string()) + .map_err(|_| AxError::NotFound) +} + +fn handle_relative_path(dir_fd: isize, path: &str) -> AxResult { + let fd_table = FD_TABLE.write(); + if dir_fd >= fd_table.count() as isize || dir_fd < 0 { + axlog::warn!("文件描述符索引超出范围"); + return Err(AxError::InvalidInput); + } + match super::fs::Directory::from_fd(dir_fd as i32) { + Ok(dir) => { + // 假设目录路径以 '/' 结尾,无需手动添加 + let combined_path = format!("{}{}", dir.path(), path); + axlog::info!("处理后的路径: {} (目录: {})", combined_path, dir.path()); + Ok(combined_path) + } + Err(_) => { + axlog::warn!("文件描述符不存在"); + Err(AxError::NotFound) + } + } +} + +fn prepend_cwd(path: &str) -> AxResult { + let cwd = current_dir().map_err(|_| AxError::NotFound)?; + debug_assert!(cwd.ends_with('/'), "当前工作目录路径应以 '/' 结尾"); + Ok(format!("{}{}", cwd, path)) +} + +/// 根据 `force_dir` 和路径结尾调整路径 +fn adjust_path_suffix(mut path: String, force_dir: bool) -> String { + if force_dir && !path.ends_with('/') { + path.push('/'); + } + if path.ends_with('.') { + // 防止路径以 '.' 结尾 + path.push('/'); + } + path +} diff --git a/api/arceos_posix_api/src/imp/time.rs b/api/arceos_posix_api/src/imp/time.rs index 62458f489e..3ae0ff885f 100644 --- a/api/arceos_posix_api/src/imp/time.rs +++ b/api/arceos_posix_api/src/imp/time.rs @@ -90,3 +90,17 @@ pub unsafe fn sys_nanosleep(req: *const ctypes::timespec, rem: *mut ctypes::time Ok(0) }) } + +/// Get current system time and store in specific struct +pub unsafe fn sys_get_time_of_day(ts: *mut ctypes::timeval) -> c_int { + syscall_body!(sys_get_time_of_day, { + let current_us = axhal::time::monotonic_time_nanos() as usize / 1000; + unsafe { + *ts = ctypes::timeval { + tv_sec: (current_us / 1_000_000) as i64, + tv_usec: (current_us % 1_000_000) as i64, + } + } + Ok(0) + }) +} diff --git a/api/arceos_posix_api/src/lib.rs b/api/arceos_posix_api/src/lib.rs index 50ccb833f4..bca58c6141 100644 --- a/api/arceos_posix_api/src/lib.rs +++ b/api/arceos_posix_api/src/lib.rs @@ -18,6 +18,7 @@ extern crate alloc; mod utils; mod imp; +pub use utils::char_ptr_to_str; /// Platform-specific constants and parameters. pub mod config { @@ -31,15 +32,21 @@ pub mod config { pub mod ctypes; pub use imp::io::{sys_read, sys_write, sys_writev}; +pub use imp::path_link::{AT_FDCWD, FilePath, HARDLINK_MANAGER, handle_file_path}; pub use imp::resources::{sys_getrlimit, sys_setrlimit}; pub use imp::sys::sys_sysconf; pub use imp::task::{sys_exit, sys_getpid, sys_sched_yield}; -pub use imp::time::{sys_clock_gettime, sys_nanosleep}; +pub use imp::time::{sys_clock_gettime, sys_get_time_of_day, sys_nanosleep}; #[cfg(feature = "fd")] -pub use imp::fd_ops::{sys_close, sys_dup, sys_dup2, sys_fcntl}; +pub use imp::fd_ops::{ + FD_TABLE, add_file_like, get_file_like, sys_close, sys_dup, sys_dup2, sys_fcntl, +}; #[cfg(feature = "fs")] -pub use imp::fs::{sys_fstat, sys_getcwd, sys_lseek, sys_lstat, sys_open, sys_rename, sys_stat}; +pub use imp::fs::{ + Directory, File, sys_fstat, sys_getcwd, sys_lseek, sys_lstat, sys_open, sys_openat, sys_rename, + sys_stat, +}; #[cfg(feature = "select")] pub use imp::io_mpx::sys_select; #[cfg(feature = "epoll")] diff --git a/api/arceos_posix_api/src/utils.rs b/api/arceos_posix_api/src/utils.rs index 8dab142d54..639f5a06ad 100644 --- a/api/arceos_posix_api/src/utils.rs +++ b/api/arceos_posix_api/src/utils.rs @@ -4,10 +4,12 @@ use axerrno::{LinuxError, LinuxResult}; use core::ffi::{CStr, c_char}; +/// Convert a C string to a Rust string pub fn char_ptr_to_str<'a>(str: *const c_char) -> LinuxResult<&'a str> { if str.is_null() { Err(LinuxError::EFAULT) } else { + let str = str as *const i8; unsafe { CStr::from_ptr(str) } .to_str() .map_err(|_| LinuxError::EINVAL) diff --git a/api/axfeat/Cargo.toml b/api/axfeat/Cargo.toml index ab86e937b5..942aef228f 100644 --- a/api/axfeat/Cargo.toml +++ b/api/axfeat/Cargo.toml @@ -41,6 +41,7 @@ sched_cfs = ["axtask/sched_cfs", "irq"] # File system fs = ["alloc", "paging", "axdriver/virtio-blk", "dep:axfs", "axruntime/fs"] # TODO: try to remove "paging" myfs = ["axfs?/myfs"] +lwext4_rs = ["axfs/lwext4_rs"] # Networking net = ["alloc", "paging", "axdriver/virtio-net", "dep:axnet", "axruntime/net"] diff --git a/modules/axfs/Cargo.toml b/modules/axfs/Cargo.toml index 441a923817..5e8dd68ee7 100644 --- a/modules/axfs/Cargo.toml +++ b/modules/axfs/Cargo.toml @@ -14,6 +14,7 @@ devfs = ["dep:axfs_devfs"] ramfs = ["dep:axfs_ramfs"] procfs = ["dep:axfs_ramfs"] sysfs = ["dep:axfs_ramfs"] +lwext4_rs = ["dep:lwext4_rust"] fatfs = ["dep:fatfs"] myfs = ["dep:crate_interface"] use-ramdisk = [] @@ -28,12 +29,14 @@ cap_access = "0.1" axio = { version = "0.1", features = ["alloc"] } axerrno = "0.1" axfs_vfs = "0.1" +spin = "0.9" axfs_devfs = { version = "0.1", optional = true } axfs_ramfs = { version = "0.1", optional = true } crate_interface = { version = "0.1", optional = true } axsync = { workspace = true } axdriver = { workspace = true, features = ["block"] } axdriver_block = { git = "https://github.com/arceos-org/axdriver_crates.git", tag = "v0.1.0" } +lwext4_rust = { git = "https://github.com/elliott10/lwext4_rust.git", default-features = false, optional = true } axns = { workspace = true } [dependencies.fatfs] diff --git a/modules/axfs/src/api/mod.rs b/modules/axfs/src/api/mod.rs index c8fb3aa30d..0d46de24cc 100644 --- a/modules/axfs/src/api/mod.rs +++ b/modules/axfs/src/api/mod.rs @@ -87,3 +87,8 @@ pub fn remove_file(path: &str) -> io::Result<()> { pub fn rename(old: &str, new: &str) -> io::Result<()> { crate::root::rename(old, new) } + +/// check whether absolute path exists. +pub fn absolute_path_exists(path: &str) -> bool { + crate::root::lookup(None, path).is_ok() +} diff --git a/modules/axfs/src/dev.rs b/modules/axfs/src/dev.rs index 47cc2d2f35..f7832db623 100644 --- a/modules/axfs/src/dev.rs +++ b/modules/axfs/src/dev.rs @@ -40,8 +40,11 @@ impl Disk { pub fn read_one(&mut self, buf: &mut [u8]) -> DevResult { let read_size = if self.offset == 0 && buf.len() >= BLOCK_SIZE { // whole block - self.dev - .read_block(self.block_id, &mut buf[0..BLOCK_SIZE])?; + let mut data = [0u8; BLOCK_SIZE]; + self.dev.read_block(self.block_id, &mut data)?; + buf[0..BLOCK_SIZE].copy_from_slice(&data); + // self.dev + // .read_block(self.block_id, &mut buf[0..BLOCK_SIZE])?; self.block_id += 1; BLOCK_SIZE } else { @@ -89,4 +92,28 @@ impl Disk { }; Ok(write_size) } + + /// Read a single block starting from the specified offset. + #[allow(unused)] + pub fn read_offset(&mut self, offset: usize) -> [u8; BLOCK_SIZE] { + let block_id = offset / BLOCK_SIZE; + let mut block_data = [0u8; BLOCK_SIZE]; + self.dev + .read_block(block_id as u64, &mut block_data) + .unwrap(); + block_data + } + + /// Write single block starting from the specified offset. + #[allow(unused)] + pub fn write_offset(&mut self, offset: usize, buf: &[u8]) -> DevResult { + assert!( + buf.len() == BLOCK_SIZE, + "Buffer length must be equal to BLOCK_SIZE" + ); + assert!(offset % BLOCK_SIZE == 0); + let block_id = offset / BLOCK_SIZE; + self.dev.write_block(block_id as u64, buf).unwrap(); + Ok(buf.len()) + } } diff --git a/modules/axfs/src/fops.rs b/modules/axfs/src/fops.rs index 9d28723d71..44749a566f 100644 --- a/modules/axfs/src/fops.rs +++ b/modules/axfs/src/fops.rs @@ -40,21 +40,17 @@ pub struct OpenOptions { // generic read: bool, write: bool, + execute: bool, append: bool, truncate: bool, create: bool, create_new: bool, + directory: bool, // system-specific _custom_flags: i32, _mode: u32, } -impl Default for OpenOptions { - fn default() -> Self { - Self::new() - } -} - impl OpenOptions { /// Creates a blank new set of options ready for configuration. pub const fn new() -> Self { @@ -62,10 +58,12 @@ impl OpenOptions { // generic read: false, write: false, + execute: false, append: false, truncate: false, create: false, create_new: false, + directory: false, // system-specific _custom_flags: 0, _mode: 0o666, @@ -79,6 +77,10 @@ impl OpenOptions { pub fn write(&mut self, write: bool) { self.write = write; } + /// Sets the option for execute access. + pub fn execute(&mut self, execute: bool) { + self.execute = execute; + } /// Sets the option for the append mode. pub fn append(&mut self, append: bool) { self.append = append; @@ -95,9 +97,36 @@ impl OpenOptions { pub fn create_new(&mut self, create_new: bool) { self.create_new = create_new; } + /// Sets the option to open a directory. + pub fn directory(&mut self, directory: bool) { + self.directory = directory; + } + /// check whether contains directory. + pub fn has_directory(&self) -> bool { + self.directory + } + + /// Sets the create flags. + pub fn set_create(mut self, create: bool, create_new: bool) -> Self { + self.create = create; + self.create_new = create_new; + self + } + + /// Sets the read flag. + pub fn set_read(mut self, read: bool) -> Self { + self.read = read; + self + } + + /// Sets the write flag. + pub fn set_write(mut self, write: bool) -> Self { + self.write = write; + self + } const fn is_valid(&self) -> bool { - if !self.read && !self.write && !self.append { + if !self.read && !self.write && !self.append && !self.directory { return false; } match (self.write, self.append) { @@ -149,7 +178,13 @@ impl File { let attr = node.get_attr()?; if attr.is_dir() - && (opts.create || opts.create_new || opts.write || opts.append || opts.truncate) + && (opts.create + || opts.create_new + || opts.write + || opts.append + || opts.truncate + || opts.execute + || opts.read) { return ax_err!(IsADirectory); } @@ -399,6 +434,9 @@ impl From<&OpenOptions> for Cap { if opts.write | opts.append { cap |= Cap::WRITE; } + if opts.execute { + cap |= Cap::EXECUTE; + } cap } } diff --git a/modules/axfs/src/fs/fatfs.rs b/modules/axfs/src/fs/fatfs.rs index 323d69a06a..4414077d0f 100644 --- a/modules/axfs/src/fs/fatfs.rs +++ b/modules/axfs/src/fs/fatfs.rs @@ -15,15 +15,17 @@ pub struct FatFileSystem { root_dir: UnsafeCell>, } -pub struct FileWrapper<'a>(Mutex>); -pub struct DirWrapper<'a>(Dir<'a, Disk, NullTimeProvider, LossyOemCpConverter>); +pub struct FileWrapper<'a, IO: IoTrait>(Mutex>); +pub struct DirWrapper<'a, IO: IoTrait>(Dir<'a, IO, NullTimeProvider, LossyOemCpConverter>); + +pub trait IoTrait: Read + Write + Seek {} unsafe impl Sync for FatFileSystem {} unsafe impl Send for FatFileSystem {} -unsafe impl Send for FileWrapper<'_> {} -unsafe impl Sync for FileWrapper<'_> {} -unsafe impl Send for DirWrapper<'_> {} -unsafe impl Sync for DirWrapper<'_> {} +unsafe impl<'a, IO: IoTrait> Send for FileWrapper<'a, IO> {} +unsafe impl<'a, IO: IoTrait> Sync for FileWrapper<'a, IO> {} +unsafe impl<'a, IO: IoTrait> Send for DirWrapper<'a, IO> {} +unsafe impl<'a, IO: IoTrait> Sync for DirWrapper<'a, IO> {} impl FatFileSystem { #[cfg(feature = "use-ramdisk")] @@ -53,21 +55,25 @@ impl FatFileSystem { unsafe { *self.root_dir.get() = Some(Self::new_dir(self.inner.root_dir())) } } - fn new_file(file: File<'_, Disk, NullTimeProvider, LossyOemCpConverter>) -> Arc { + fn new_file( + file: File<'_, IO, NullTimeProvider, LossyOemCpConverter>, + ) -> Arc> { Arc::new(FileWrapper(Mutex::new(file))) } - fn new_dir(dir: Dir<'_, Disk, NullTimeProvider, LossyOemCpConverter>) -> Arc { + fn new_dir( + dir: Dir<'_, IO, NullTimeProvider, LossyOemCpConverter>, + ) -> Arc> { Arc::new(DirWrapper(dir)) } } -impl VfsNodeOps for FileWrapper<'static> { +impl VfsNodeOps for FileWrapper<'static, IO> { axfs_vfs::impl_vfs_non_dir_default! {} fn get_attr(&self) -> VfsResult { let size = self.0.lock().seek(SeekFrom::End(0)).map_err(as_vfs_err)?; - let blocks = size.div_ceil(BLOCK_SIZE as u64); + let blocks = (size + BLOCK_SIZE as u64 - 1) / BLOCK_SIZE as u64; // FAT fs doesn't support permissions, we just set everything to 755 let perm = VfsNodePerm::from_bits_truncate(0o755); Ok(VfsNodeAttr::new(perm, VfsNodeType::File, size, blocks)) @@ -92,7 +98,7 @@ impl VfsNodeOps for FileWrapper<'static> { } } -impl VfsNodeOps for DirWrapper<'static> { +impl VfsNodeOps for DirWrapper<'static, IO> { axfs_vfs::impl_vfs_dir_default! {} fn get_attr(&self) -> VfsResult { @@ -209,6 +215,8 @@ impl fatfs::IoBase for Disk { type Error = (); } +impl IoTrait for Disk {} + impl Read for Disk { fn read(&mut self, mut buf: &mut [u8]) -> Result { let mut read_len = 0; @@ -264,7 +272,7 @@ impl Seek for Disk { } } -const fn as_vfs_err(err: fatfs::Error<()>) -> VfsError { +fn as_vfs_err(err: fatfs::Error) -> VfsError { use fatfs::Error::*; match err { AlreadyExists => VfsError::AlreadyExists, @@ -281,3 +289,83 @@ const fn as_vfs_err(err: fatfs::Error<()>) -> VfsError { _ => VfsError::Io, } } + +impl Clone for FileWrapper<'static, Disk> { + fn clone(&self) -> Self { + let file = self.0.lock(); + let cloned_file = file.clone(); + Self(Mutex::new(cloned_file)) + } +} + +pub struct FatFileSystemFromFile { + inner: fatfs::FileSystem, NullTimeProvider, LossyOemCpConverter>, + root_dir: UnsafeCell>, +} + +unsafe impl Sync for FatFileSystemFromFile {} +unsafe impl Send for FatFileSystemFromFile {} + +#[allow(unused)] +impl FatFileSystemFromFile { + pub fn new(file: FileWrapper<'static, Disk>) -> Self { + let inner = fatfs::FileSystem::new(file, fatfs::FsOptions::new()) + .expect("failed to initialize FAT filesystem"); + Self { + inner, + root_dir: UnsafeCell::new(None), + } + } + + pub fn init(&'static self) { + // must be called before later operations + unsafe { *self.root_dir.get() = Some(FatFileSystem::new_dir(self.inner.root_dir())) } + } +} + +impl VfsOps for FatFileSystemFromFile { + fn root_dir(&self) -> VfsNodeRef { + let root_dir = unsafe { (*self.root_dir.get()).as_ref().unwrap() }; + root_dir.clone() + } +} + +impl<'a> fatfs::IoBase for FileWrapper<'a, Disk> { + type Error = (); +} + +impl<'a> IoTrait for FileWrapper<'a, Disk> {} + +impl<'a> fatfs::Read for FileWrapper<'a, Disk> { + fn read(&mut self, buf: &mut [u8]) -> Result { + let mut file = self.0.lock(); + file.read(buf) + .inspect_err(|e| error!("read error: {e:?}")) + .map_err(|_| ()) + } +} + +impl<'a> fatfs::Write for FileWrapper<'a, Disk> { + fn write(&mut self, buf: &[u8]) -> Result { + let mut file = self.0.lock(); + file.write(buf) + .inspect_err(|e| error!("write error: {e:?}")) + .map_err(|_| ()) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + let mut file = self.0.lock(); + file.flush() + .inspect_err(|e| error!("flush error: {e:?}")) + .map_err(|_| ()) + } +} + +impl<'a> fatfs::Seek for FileWrapper<'a, Disk> { + fn seek(&mut self, pos: fatfs::SeekFrom) -> Result { + let mut file = self.0.lock(); + file.seek(pos) + .inspect_err(|e| error!("seek error: {e:?}")) + .map_err(|_| ()) + } +} diff --git a/modules/axfs/src/fs/lwext4_rust.rs b/modules/axfs/src/fs/lwext4_rust.rs new file mode 100644 index 0000000000..845e48f5c1 --- /dev/null +++ b/modules/axfs/src/fs/lwext4_rust.rs @@ -0,0 +1,404 @@ +use crate::alloc::string::String; +use alloc::sync::Arc; +use axerrno::AxError; +use axfs_vfs::{VfsDirEntry, VfsError, VfsNodePerm, VfsResult}; +use axfs_vfs::{VfsNodeAttr, VfsNodeOps, VfsNodeRef, VfsNodeType, VfsOps}; +use axsync::Mutex; +use lwext4_rust::bindings::{ + O_CREAT, O_RDONLY, O_RDWR, O_TRUNC, O_WRONLY, SEEK_CUR, SEEK_END, SEEK_SET, +}; +use lwext4_rust::{Ext4BlockWrapper, Ext4File, InodeTypes, KernelDevOp}; + +use crate::dev::Disk; +pub const BLOCK_SIZE: usize = 512; + +#[allow(dead_code)] +pub struct Ext4FileSystem { + inner: Ext4BlockWrapper, + root: VfsNodeRef, +} + +unsafe impl Sync for Ext4FileSystem {} +unsafe impl Send for Ext4FileSystem {} + +impl Ext4FileSystem { + #[cfg(feature = "use-ramdisk")] + pub fn new(mut disk: Disk) -> Self { + unimplemented!() + } + + #[cfg(not(feature = "use-ramdisk"))] + pub fn new(disk: Disk) -> Self { + info!( + "Got Disk size:{}, position:{}", + disk.size(), + disk.position() + ); + let inner = + Ext4BlockWrapper::::new(disk).expect("failed to initialize EXT4 filesystem"); + let root = Arc::new(FileWrapper::new("/", InodeTypes::EXT4_DE_DIR)); + Self { inner, root } + } +} + +/// The [`VfsOps`] trait provides operations on a filesystem. +impl VfsOps for Ext4FileSystem { + // mount() + + fn root_dir(&self) -> VfsNodeRef { + debug!("Get root_dir"); + //let root_dir = unsafe { (*self.root.get()).as_ref().unwrap() }; + Arc::clone(&self.root) + } +} + +pub struct FileWrapper(Mutex); + +unsafe impl Send for FileWrapper {} +unsafe impl Sync for FileWrapper {} + +impl FileWrapper { + fn new(path: &str, types: InodeTypes) -> Self { + info!("FileWrapper new {:?} {}", types, path); + //file.file_read_test("/test/test.txt", &mut buf); + + Self(Mutex::new(Ext4File::new(path, types))) + } + + fn path_deal_with(&self, path: &str) -> String { + if path.starts_with('/') { + warn!("path_deal_with: {}", path); + } + let p = path.trim_matches('/'); // 首尾去除 + if p.is_empty() || p == "." { + return String::new(); + } + + if let Some(rest) = p.strip_prefix("./") { + //if starts with "./" + return self.path_deal_with(rest); + } + let rest_p = p.replace("//", "/"); + if p != rest_p { + return self.path_deal_with(&rest_p); + } + + //Todo ? ../ + //注:lwext4创建文件必须提供文件path的绝对路径 + let file = self.0.lock(); + let path = file.get_path(); + let fpath = String::from(path.to_str().unwrap().trim_end_matches('/')) + "/" + p; + info!("dealt with full path: {}", fpath.as_str()); + fpath + } +} + +/// The [`VfsNodeOps`] trait provides operations on a file or a directory. +impl VfsNodeOps for FileWrapper { + fn get_attr(&self) -> VfsResult { + let mut file = self.0.lock(); + + let perm = file.file_mode_get().unwrap_or(0o755); + let perm = VfsNodePerm::from_bits_truncate((perm as u16) & 0o777); + + let vtype = file.file_type_get(); + let vtype = match vtype { + InodeTypes::EXT4_INODE_MODE_FIFO => VfsNodeType::Fifo, + InodeTypes::EXT4_INODE_MODE_CHARDEV => VfsNodeType::CharDevice, + InodeTypes::EXT4_INODE_MODE_DIRECTORY => VfsNodeType::Dir, + InodeTypes::EXT4_INODE_MODE_BLOCKDEV => VfsNodeType::BlockDevice, + InodeTypes::EXT4_INODE_MODE_FILE => VfsNodeType::File, + InodeTypes::EXT4_INODE_MODE_SOFTLINK => VfsNodeType::SymLink, + InodeTypes::EXT4_INODE_MODE_SOCKET => VfsNodeType::Socket, + _ => { + warn!("unknown file type: {:?}", vtype); + VfsNodeType::File + } + }; + + let size = if vtype == VfsNodeType::File { + let path = file.get_path(); + let path = path.to_str().unwrap(); + file.file_open(path, O_RDONLY) + .map_err(|e| >::try_into(e).unwrap())?; + let fsize = file.file_size(); + let _ = file.file_close(); + fsize + } else { + 0 // DIR size ? + }; + let blocks = (size + (BLOCK_SIZE as u64 - 1)) / BLOCK_SIZE as u64; + + info!( + "get_attr of {:?} {:?}, size: {}, blocks: {}", + vtype, + file.get_path(), + size, + blocks + ); + + Ok(VfsNodeAttr::new(perm, vtype, size, blocks)) + } + + fn create(&self, path: &str, ty: VfsNodeType) -> VfsResult { + info!("create {:?} on Ext4fs: {}", ty, path); + let fpath = self.path_deal_with(path); + let fpath = fpath.as_str(); + if fpath.is_empty() { + return Ok(()); + } + + let types = match ty { + VfsNodeType::Fifo => InodeTypes::EXT4_DE_FIFO, + VfsNodeType::CharDevice => InodeTypes::EXT4_DE_CHRDEV, + VfsNodeType::Dir => InodeTypes::EXT4_DE_DIR, + VfsNodeType::BlockDevice => InodeTypes::EXT4_DE_BLKDEV, + VfsNodeType::File => InodeTypes::EXT4_DE_REG_FILE, + VfsNodeType::SymLink => InodeTypes::EXT4_DE_SYMLINK, + VfsNodeType::Socket => InodeTypes::EXT4_DE_SOCK, + }; + + let mut file = self.0.lock(); + if file.check_inode_exist(fpath, types.clone()) { + Ok(()) + } else { + if types == InodeTypes::EXT4_DE_DIR { + file.dir_mk(fpath) + .map(|_v| ()) + .map_err(|e| e.try_into().unwrap()) + } else { + file.file_open(fpath, O_WRONLY | O_CREAT | O_TRUNC) + .expect("create file failed"); + file.file_close() + .map(|_v| ()) + .map_err(|e| e.try_into().unwrap()) + } + } + } + + fn remove(&self, path: &str) -> VfsResult { + info!("remove ext4fs: {}", path); + let fpath = self.path_deal_with(path); + let fpath = fpath.as_str(); + + assert!(!fpath.is_empty()); // already check at `root.rs` + + let mut file = self.0.lock(); + if file.check_inode_exist(fpath, InodeTypes::EXT4_DE_DIR) { + // Recursive directory remove + file.dir_rm(fpath) + .map(|_v| ()) + .map_err(|e| e.try_into().unwrap()) + } else { + file.file_remove(fpath) + .map(|_v| ()) + .map_err(|e| e.try_into().unwrap()) + } + } + + /// Get the parent directory of this directory. + /// Return `None` if the node is a file. + fn parent(&self) -> Option { + let file = self.0.lock(); + if file.get_type() == InodeTypes::EXT4_DE_DIR { + let path = file.get_path(); + let path = path.to_str().unwrap(); + info!("Get the parent dir of {}", path); + let path = path.trim_end_matches('/').trim_end_matches(|c| c != '/'); + if !path.is_empty() { + return Some(Arc::new(Self::new(path, InodeTypes::EXT4_DE_DIR))); + } + } + None + } + + /// Read directory entries into `dirents`, starting from `start_idx`. + fn read_dir(&self, start_idx: usize, dirents: &mut [VfsDirEntry]) -> VfsResult { + let file = self.0.lock(); + let (name, inode_type) = file.lwext4_dir_entries().unwrap(); + + let mut name_iter = name.iter().skip(start_idx); + let mut inode_type_iter = inode_type.iter().skip(start_idx); + + for (i, out_entry) in dirents.iter_mut().enumerate() { + let iname = name_iter.next(); + let itypes = inode_type_iter.next(); + + match itypes { + Some(t) => { + let ty = if *t == InodeTypes::EXT4_DE_DIR { + VfsNodeType::Dir + } else if *t == InodeTypes::EXT4_DE_REG_FILE { + VfsNodeType::File + } else if *t == InodeTypes::EXT4_DE_SYMLINK { + VfsNodeType::SymLink + } else { + error!("unknown file type: {:?}", itypes); + unreachable!() + }; + + *out_entry = + VfsDirEntry::new(core::str::from_utf8(iname.unwrap()).unwrap(), ty); + } + _ => return Ok(i), + } + } + + Ok(dirents.len()) + } + + /// Lookup the node with given `path` in the directory. + /// Return the node if found. + fn lookup(self: Arc, path: &str) -> VfsResult { + debug!("lookup ext4fs: {:?}, {}", self.0.lock().get_path(), path); + + let fpath = self.path_deal_with(path); + let fpath = fpath.as_str(); + if fpath.is_empty() { + return Ok(self.clone()); + } + + ///////// + let mut file = self.0.lock(); + if file.check_inode_exist(fpath, InodeTypes::EXT4_DE_DIR) { + debug!("lookup new DIR FileWrapper"); + Ok(Arc::new(Self::new(fpath, InodeTypes::EXT4_DE_DIR))) + } else if file.check_inode_exist(fpath, InodeTypes::EXT4_DE_REG_FILE) { + debug!("lookup new FILE FileWrapper"); + Ok(Arc::new(Self::new(fpath, InodeTypes::EXT4_DE_REG_FILE))) + } else { + Err(VfsError::NotFound) + } + } + + fn read_at(&self, offset: u64, buf: &mut [u8]) -> VfsResult { + let mut file = self.0.lock(); + let path = file.get_path(); + let path = path.to_str().unwrap(); + file.file_open(path, O_RDONLY) + .map_err(|e| >::try_into(e).unwrap())?; + + file.file_seek(offset as i64, SEEK_SET) + .map_err(|e| >::try_into(e).unwrap())?; + let r = file.file_read(buf); + + let _ = file.file_close(); + r.map_err(|e| e.try_into().unwrap()) + } + + fn write_at(&self, offset: u64, buf: &[u8]) -> VfsResult { + let mut file = self.0.lock(); + let path = file.get_path(); + let path = path.to_str().unwrap(); + file.file_open(path, O_RDWR) + .map_err(|e| >::try_into(e).unwrap())?; + + file.file_seek(offset as i64, SEEK_SET) + .map_err(|e| >::try_into(e).unwrap())?; + let r = file.file_write(buf); + + let _ = file.file_close(); + r.map_err(|e| e.try_into().unwrap()) + } + + fn truncate(&self, size: u64) -> VfsResult { + let mut file = self.0.lock(); + let path = file.get_path(); + let path = path.to_str().unwrap(); + file.file_open(path, O_RDWR | O_CREAT | O_TRUNC) + .map_err(|e| >::try_into(e).unwrap())?; + + let t = file.file_truncate(size); + + let _ = file.file_close(); + t.map(|_v| ()).map_err(|e| e.try_into().unwrap()) + } + + fn rename(&self, src_path: &str, dst_path: &str) -> VfsResult { + let mut file = self.0.lock(); + file.file_rename(src_path, dst_path) + .map(|_v| ()) + .map_err(|e| e.try_into().unwrap()) + } + + fn as_any(&self) -> &dyn core::any::Any { + self as &dyn core::any::Any + } +} + +impl Drop for FileWrapper { + fn drop(&mut self) { + let mut file = self.0.lock(); + debug!("Drop struct FileWrapper {:?}", file.get_path()); + file.file_close().expect("failed to close fd"); + drop(file); // todo + } +} + +impl KernelDevOp for Disk { + //type DevType = Box; + type DevType = Disk; + + fn read(dev: &mut Disk, mut buf: &mut [u8]) -> Result { + debug!("READ block device buf={}", buf.len()); + let mut read_len = 0; + while !buf.is_empty() { + match dev.read_one(buf) { + Ok(0) => break, + Ok(n) => { + let tmp = buf; + buf = &mut tmp[n..]; + read_len += n; + } + Err(_e) => return Err(-1), + } + } + debug!("READ rt len={}", read_len); + Ok(read_len) + } + fn write(dev: &mut Self::DevType, mut buf: &[u8]) -> Result { + debug!("WRITE block device buf={}", buf.len()); + let mut write_len = 0; + while !buf.is_empty() { + match dev.write_one(buf) { + Ok(0) => break, + Ok(n) => { + buf = &buf[n..]; + write_len += n; + } + Err(_e) => return Err(-1), + } + } + debug!("WRITE rt len={}", write_len); + Ok(write_len) + } + fn flush(_dev: &mut Self::DevType) -> Result { + Ok(0) + } + fn seek(dev: &mut Disk, off: i64, whence: i32) -> Result { + let size = dev.size(); + debug!( + "SEEK block device size:{}, pos:{}, offset={}, whence={}", + size, + &dev.position(), + off, + whence + ); + let new_pos = match whence as u32 { + SEEK_SET => Some(off), + SEEK_CUR => dev.position().checked_add_signed(off).map(|v| v as i64), + SEEK_END => size.checked_add_signed(off).map(|v| v as i64), + _ => { + error!("invalid seek() whence: {}", whence); + Some(off) + } + } + .ok_or(-1)?; + + if new_pos as u64 > size { + warn!("Seek beyond the end of the block device"); + } + dev.set_position(new_pos as u64); + Ok(new_pos) + } +} diff --git a/modules/axfs/src/fs/mod.rs b/modules/axfs/src/fs/mod.rs index 77aad60f2c..55791c83d1 100644 --- a/modules/axfs/src/fs/mod.rs +++ b/modules/axfs/src/fs/mod.rs @@ -1,9 +1,12 @@ cfg_if::cfg_if! { if #[cfg(feature = "myfs")] { pub mod myfs; + } else if #[cfg(feature = "lwext4_rs")] { + pub mod lwext4_rust; } else if #[cfg(feature = "fatfs")] { pub mod fatfs; } + } #[cfg(feature = "devfs")] diff --git a/modules/axfs/src/lib.rs b/modules/axfs/src/lib.rs index 9cfcb5d134..c7a0ba0b50 100644 --- a/modules/axfs/src/lib.rs +++ b/modules/axfs/src/lib.rs @@ -33,6 +33,7 @@ mod root; pub mod api; pub mod fops; +pub use root::{CURRENT_DIR, CURRENT_DIR_PATH}; use axdriver::{AxDeviceContainer, prelude::*}; diff --git a/modules/axfs/src/root.rs b/modules/axfs/src/root.rs index afa3f937a2..d6e37508c7 100644 --- a/modules/axfs/src/root.rs +++ b/modules/axfs/src/root.rs @@ -8,12 +8,31 @@ use axfs_vfs::{VfsNodeAttr, VfsNodeOps, VfsNodeRef, VfsNodeType, VfsOps, VfsResu use axns::{ResArc, def_resource}; use axsync::Mutex; use lazyinit::LazyInit; +use spin::RwLock; -use crate::{api::FileType, fs, mounts}; +use crate::{ + api::FileType, + fs::{self}, + mounts, +}; def_resource! { - static CURRENT_DIR_PATH: ResArc> = ResArc::new(); - static CURRENT_DIR: ResArc> = ResArc::new(); + pub static CURRENT_DIR_PATH: ResArc> = ResArc::new(); + pub static CURRENT_DIR: ResArc> = ResArc::new(); +} + +impl CURRENT_DIR_PATH { + /// Return a copy of the inner path. + pub fn copy_inner(&self) -> Mutex { + Mutex::new(self.lock().clone()) + } +} + +impl CURRENT_DIR { + /// Return a copy of the CURRENT_DIR_NODE. + pub fn copy_inner(&self) -> Mutex { + Mutex::new(self.lock().clone()) + } } struct MountPoint { @@ -23,7 +42,7 @@ struct MountPoint { struct RootDirectory { main_fs: Arc, - mounts: Vec, + mounts: RwLock>, } static ROOT_DIR: LazyInit> = LazyInit::new(); @@ -44,33 +63,33 @@ impl RootDirectory { pub const fn new(main_fs: Arc) -> Self { Self { main_fs, - mounts: Vec::new(), + mounts: RwLock::new(Vec::new()), } } - pub fn mount(&mut self, path: &'static str, fs: Arc) -> AxResult { + pub fn mount(&self, path: &'static str, fs: Arc) -> AxResult { if path == "/" { return ax_err!(InvalidInput, "cannot mount root filesystem"); } if !path.starts_with('/') { return ax_err!(InvalidInput, "mount path must start with '/'"); } - if self.mounts.iter().any(|mp| mp.path == path) { + if self.mounts.read().iter().any(|mp| mp.path == path) { return ax_err!(InvalidInput, "mount point already exists"); } // create the mount point in the main filesystem if it does not exist self.main_fs.root_dir().create(path, FileType::Dir)?; fs.mount(path, self.main_fs.root_dir().lookup(path)?)?; - self.mounts.push(MountPoint::new(path, fs)); + self.mounts.write().push(MountPoint::new(path, fs)); Ok(()) } - pub fn _umount(&mut self, path: &str) { - self.mounts.retain(|mp| mp.path != path); + pub fn _umount(&self, path: &str) { + self.mounts.write().retain(|mp| mp.path != path); } pub fn contains(&self, path: &str) -> bool { - self.mounts.iter().any(|mp| mp.path == path) + self.mounts.read().iter().any(|mp| mp.path == path) } fn lookup_mounted_fs(&self, path: &str, f: F) -> AxResult @@ -88,7 +107,7 @@ impl RootDirectory { // Find the filesystem that has the longest mounted path match // TODO: more efficient, e.g. trie - for (i, mp) in self.mounts.iter().enumerate() { + for (i, mp) in self.mounts.read().iter().enumerate() { // skip the first '/' if path.starts_with(&mp.path[1..]) && mp.path.len() - 1 > max_len { max_len = mp.path.len() - 1; @@ -99,7 +118,7 @@ impl RootDirectory { if max_len == 0 { f(self.main_fs.clone(), path) // not matched any mount point } else { - f(self.mounts[idx].fs.clone(), &path[max_len..]) // matched at `idx` + f(self.mounts.read()[idx].fs.clone(), &path[max_len..]) // matched at `idx` } } } @@ -150,6 +169,10 @@ pub(crate) fn init_rootfs(disk: crate::dev::Disk) { cfg_if::cfg_if! { if #[cfg(feature = "myfs")] { // override the default filesystem let main_fs = fs::myfs::new_myfs(disk); + } else if #[cfg(feature = "lwext4_rs")] { + static EXT4_FS: LazyInit> = LazyInit::new(); + EXT4_FS.init_once(Arc::new(fs::lwext4_rust::Ext4FileSystem::new(disk))); + let main_fs = EXT4_FS.clone(); } else if #[cfg(feature = "fatfs")] { static FAT_FS: LazyInit> = LazyInit::new(); FAT_FS.init_once(Arc::new(fs::fatfs::FatFileSystem::new(disk))); @@ -158,7 +181,7 @@ pub(crate) fn init_rootfs(disk: crate::dev::Disk) { } } - let mut root_dir = RootDirectory::new(main_fs); + let root_dir = RootDirectory::new(main_fs); #[cfg(feature = "devfs")] root_dir @@ -183,7 +206,9 @@ pub(crate) fn init_rootfs(disk: crate::dev::Disk) { .expect("fail to mount sysfs at /sys"); ROOT_DIR.init_once(Arc::new(root_dir)); + info!("rootfs initialized"); CURRENT_DIR.init_new(Mutex::new(ROOT_DIR.clone())); + info!("test"); CURRENT_DIR_PATH.init_new(Mutex::new("/".into())); } diff --git a/modules/axhal/src/platform/riscv64_qemu_virt/console.rs b/modules/axhal/src/platform/riscv64_qemu_virt/console.rs index e89c15442c..90400a338f 100644 --- a/modules/axhal/src/platform/riscv64_qemu_virt/console.rs +++ b/modules/axhal/src/platform/riscv64_qemu_virt/console.rs @@ -25,6 +25,21 @@ fn try_write_bytes(bytes: &[u8]) -> usize { /// Writes bytes to the console from input u8 slice. pub fn write_bytes(bytes: &[u8]) { + // If the address is from userspace, we need to copy the bytes to kernel space. + #[cfg(feature = "uspace")] + if bytes.as_ptr() as usize & (1 << 63) == 0 { + // Check if the address is valid. + let kernel_bytes = bytes.to_vec(); + let mut write_len = 0; + while write_len < kernel_bytes.len() { + let len = try_write_bytes(&kernel_bytes[write_len..]); + if len == 0 { + break; + } + write_len += len; + } + return; + } let mut write_len = 0; while write_len < bytes.len() { let len = try_write_bytes(&bytes[write_len..]); diff --git a/modules/axhal/src/platform/riscv64_qemu_virt/mod.rs b/modules/axhal/src/platform/riscv64_qemu_virt/mod.rs index e599e22e64..9be7e72962 100644 --- a/modules/axhal/src/platform/riscv64_qemu_virt/mod.rs +++ b/modules/axhal/src/platform/riscv64_qemu_virt/mod.rs @@ -20,6 +20,8 @@ unsafe extern "C" { unsafe extern "C" fn rust_entry(cpu_id: usize, dtb: usize) { crate::mem::clear_bss(); crate::cpu::init_primary(cpu_id); + #[cfg(feature = "uspace")] + riscv::register::sstatus::set_sum(); self::time::init_early(); rust_main(cpu_id, dtb); } diff --git a/modules/axmm/src/aspace.rs b/modules/axmm/src/aspace.rs index 39e377e065..3c0a384de6 100644 --- a/modules/axmm/src/aspace.rs +++ b/modules/axmm/src/aspace.rs @@ -1,5 +1,6 @@ use core::fmt; +use alloc::vec; use axerrno::{AxError, AxResult, ax_err}; use axhal::mem::phys_to_virt; use axhal::paging::{MappingFlags, PageTable}; @@ -9,7 +10,7 @@ use memory_addr::{ use memory_set::{MemoryArea, MemorySet}; use crate::backend::Backend; -use crate::mapping_err_to_ax_err; +use crate::{KERNEL_ASPACE, mapping_err_to_ax_err}; /// The virtual memory address space. pub struct AddrSpace { @@ -147,6 +148,39 @@ impl AddrSpace { Ok(()) } + /// Add a new zero-initialized allocation mapping. + pub fn alloc_for_lazy(&mut self, start: VirtAddr, size: usize) -> AxResult { + let end = (start + size).align_up_4k(); + let mut start = start.align_down_4k(); + let size = end - start; + if !self.contains_range(start, size) { + return ax_err!(InvalidInput, "address out of range"); + } + while let Some(area) = self.areas.find(start) { + let area_backend = area.backend(); + if let Backend::Alloc { populate } = area_backend { + if !*populate { + let count = (area.end().min(end) - start).align_up_4k() / PAGE_SIZE_4K; + for i in 0..count { + let addr = start + i * PAGE_SIZE_4K; + area_backend.handle_page_fault_alloc( + addr, + area.flags(), + &mut self.pt, + *populate, + ); + } + } + } + start = area.end(); + assert!(start.is_aligned_4k()); + } + if start < end { + ax_err!(InvalidInput, "address out of range")?; + } + Ok(()) + } + /// Removes mappings within the specified virtual address range. /// /// Returns an error if the address range is out of the address space or not @@ -165,14 +199,52 @@ impl AddrSpace { Ok(()) } + /// To remove user area mappings from address space. + pub fn unmap_user_areas(&mut self) -> AxResult { + for area in self.areas.iter() { + assert!(area.start().is_aligned_4k()); + assert!(area.size() % PAGE_SIZE_4K == 0); + assert!(area.flags().contains(MappingFlags::USER)); + assert!( + self.va_range + .contains_range(VirtAddrRange::from_start_size(area.start(), area.size())), + "MemorySet contains out-of-va-range area" + ); + } + self.areas.clear(&mut self.pt).unwrap(); + Ok(()) + } + /// To process data in this area with the given function. /// /// Now it supports reading and writing data in the given interval. - fn process_area_data(&self, start: VirtAddr, size: usize, mut f: F) -> AxResult + /// + /// # Arguments + /// - `start`: The start virtual address to process. + /// - `size`: The size of the data to process. + /// - `f`: The function to process the data, whose arguments are the start virtual address, + /// the offset and the size of the data. + /// + /// # Notes + /// The caller must ensure that the permission of the operation is allowed. + fn process_area_data(&self, start: VirtAddr, size: usize, f: F) -> AxResult where F: FnMut(VirtAddr, usize, usize), { - if !self.contains_range(start, size) { + Self::process_area_data_with_page_table(&self.pt, &self.va_range, start, size, f) + } + + fn process_area_data_with_page_table( + pt: &PageTable, + va_range: &VirtAddrRange, + start: VirtAddr, + size: usize, + mut f: F, + ) -> AxResult + where + F: FnMut(VirtAddr, usize, usize), + { + if !va_range.contains_range(VirtAddrRange::from_start_size(start, size)) { return ax_err!(InvalidInput, "address out of range"); } let mut cnt = 0; @@ -181,7 +253,7 @@ impl AddrSpace { for vaddr in PageIter4K::new(start.align_down_4k(), end_align_up) .expect("Failed to create page iterator") { - let (mut paddr, _, _) = self.pt.query(vaddr).map_err(|_| AxError::BadAddress)?; + let (mut paddr, _, _) = pt.query(vaddr).map_err(|_| AxError::BadAddress)?; let mut copy_size = (size - cnt).min(PAGE_SIZE_4K); @@ -268,6 +340,62 @@ impl AddrSpace { } false } + + /// 克隆 AddrSpace。这将创建一个新的页表,并将旧页表中的所有区域(包括内核区域)映射到新的页表中,但仅将用户区域的映射到新的 MemorySet 中。 + /// + /// 如果发生错误,新创建的 MemorySet 将被丢弃并返回错误。 + pub fn clone_or_err(&mut self) -> AxResult { + // 由于要克隆的这个地址空间可能是用户空间,而用户空间在一开始创建时不会在MemorySet中管理内核区域,而是直接把相关的页表项复制到了新页表中,所以在MemorySet中没有内核区域,需要另外处理。 + let mut new_pt = PageTable::try_new().map_err(|_| AxError::NoMemory)?; + // 如果不是 ARMv8 架构,将内核部分复制到用户页表中。 + if !cfg!(target_arch = "aarch64") && !cfg!(target_arch = "loongarch64") { + // ARMv8 使用一个单独的页表 (TTBR0_EL1) 用于用户空间,不需要将内核部分复制到用户页表中。 + let kernel_aspace = KERNEL_ASPACE.lock(); + new_pt.copy_from( + &kernel_aspace.pt, + kernel_aspace.base(), + kernel_aspace.size(), + ); + } + + // 创建一个新的 MemorySet 并将原始区域映射到新的页表中。 + let mut new_areas = MemorySet::new(); + let mut buf = vec![0u8; PAGE_SIZE_4K]; + for area in self.areas.iter() { + let new_area = MemoryArea::new( + area.start(), + area.size(), + area.flags(), + area.backend().clone(), + ); + new_areas + .map(new_area, &mut new_pt, false) + .map_err(mapping_err_to_ax_err)?; + // 将原区域的数据复制到新区域中。 + buf.resize(buf.capacity().max(area.size()), 0); + self.read(area.start(), &mut buf).inspect_err(|_| { + new_areas.clear(&mut new_pt).unwrap(); + })?; + Self::process_area_data_with_page_table( + &new_pt, + &self.va_range, + area.start(), + area.size(), + |dst, offset, write_size| unsafe { + core::ptr::copy_nonoverlapping( + buf.as_ptr().add(offset), + dst.as_mut_ptr(), + write_size, + ); + }, + )?; + } + Ok(Self { + va_range: self.va_range, + areas: new_areas, + pt: new_pt, + }) + } } impl fmt::Debug for AddrSpace { diff --git a/modules/axtask/src/api.rs b/modules/axtask/src/api.rs index 7cf96a043c..743580b8fa 100644 --- a/modules/axtask/src/api.rs +++ b/modules/axtask/src/api.rs @@ -1,6 +1,9 @@ //! Task APIs for multi-task configuration. -use alloc::{string::String, sync::Arc}; +use alloc::{ + string::String, + sync::{Arc, Weak}, +}; use kernel_guard::NoPreemptIrqSave; @@ -16,6 +19,11 @@ pub use crate::wait_queue::WaitQueue; /// The reference type of a task. pub type AxTaskRef = Arc; +/// The weak reference type of a task. +pub type WeakAxTaskRef = Weak; + +pub use crate::task::TaskState; + /// The wrapper type for [`cpumask::CpuMask`] with SMP configuration. pub type AxCpuMask = cpumask::CpuMask<{ axconfig::SMP }>; diff --git a/modules/axtask/src/task.rs b/modules/axtask/src/task.rs index dfb2a844c0..3c2fe52282 100644 --- a/modules/axtask/src/task.rs +++ b/modules/axtask/src/task.rs @@ -23,7 +23,7 @@ pub struct TaskId(u64); /// The possible states of a task. #[repr(u8)] #[derive(Debug, Clone, Copy, Eq, PartialEq)] -pub(crate) enum TaskState { +pub enum TaskState { /// Task is running on some CPU. Running = 1, /// Task is ready to run on some scheduler's ready queue. @@ -38,7 +38,7 @@ pub(crate) enum TaskState { /// The inner task structure. pub struct TaskInner { id: TaskId, - name: String, + name: UnsafeCell, is_idle: bool, is_init: bool, @@ -123,7 +123,7 @@ impl TaskInner { t.entry = Some(Box::into_raw(Box::new(entry))); t.ctx_mut().init(task_entry as usize, kstack.top(), tls); t.kstack = Some(kstack); - if t.name == "idle" { + if t.name() == "idle" { t.is_idle = true; } t @@ -136,7 +136,14 @@ impl TaskInner { /// Gets the name of the task. pub fn name(&self) -> &str { - self.name.as_str() + unsafe { (*self.name.get()).as_str() } + } + + /// Set the name of the task. + pub fn set_name(&self, name: &str) { + unsafe { + *self.name.get() = String::from(name); + } } /// Get a combined string of the task ID and name. @@ -209,6 +216,20 @@ impl TaskInner { pub fn set_cpumask(&self, cpumask: AxCpuMask) { *self.cpumask.lock() = cpumask } + + /// Read the top address of the kernel stack for the task. + #[inline] + pub fn get_kernel_stack_top(&self) -> Option { + if let Some(kstack) = &self.kstack { + return Some(kstack.top().as_usize()); + } + None + } + + /// Returns the exit code of the task. + pub fn exit_code(&self) -> i32 { + self.exit_code.load(Ordering::Acquire) + } } // private methods @@ -216,7 +237,7 @@ impl TaskInner { fn new_common(id: TaskId, name: String) -> Self { Self { id, - name, + name: UnsafeCell::new(name), is_idle: false, is_init: false, entry: None, @@ -255,7 +276,7 @@ impl TaskInner { t.is_init = true; #[cfg(feature = "smp")] t.set_on_cpu(true); - if t.name == "idle" { + if t.name() == "idle" { t.is_idle = true; } t @@ -265,13 +286,15 @@ impl TaskInner { Arc::new(AxTask::new(self)) } + /// Returns the task's current state. #[inline] - pub(crate) fn state(&self) -> TaskState { + pub fn state(&self) -> TaskState { self.state.load(Ordering::Acquire).into() } + /// Set the task's state. #[inline] - pub(crate) fn set_state(&self, state: TaskState) { + pub fn set_state(&self, state: TaskState) { self.state.store(state as u8, Ordering::Release) } diff --git a/ulib/axstd/Cargo.toml b/ulib/axstd/Cargo.toml index 4adb9125b0..25629ce720 100644 --- a/ulib/axstd/Cargo.toml +++ b/ulib/axstd/Cargo.toml @@ -48,6 +48,7 @@ sched_cfs = ["axfeat/sched_cfs"] # File system fs = ["arceos_api/fs", "axfeat/fs"] myfs = ["arceos_api/myfs", "axfeat/myfs"] +lwext4_rs = ["axfeat/lwext4_rs"] # Networking net = ["arceos_api/net", "axfeat/net"]