diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index 941fe9f8a..7ba7ce22a 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -39,6 +39,7 @@ ida = { path = "src/libs/ida" } intertrait = { path = "src/libs/intertrait" } kdepends = { path = "crates/kdepends" } klog_types = { path = "crates/klog_types" } +path_base = { path = "crates/path_base" } linkme = "=0.2" num = { version = "=0.4.0", default-features = false } num-derive = "=0.3" diff --git a/kernel/crates/path_base/Cargo.toml b/kernel/crates/path_base/Cargo.toml new file mode 100644 index 000000000..57d2204d5 --- /dev/null +++ b/kernel/crates/path_base/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "path_base" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/kernel/crates/path_base/src/clean_path.rs b/kernel/crates/path_base/src/clean_path.rs new file mode 100644 index 000000000..af24121f3 --- /dev/null +++ b/kernel/crates/path_base/src/clean_path.rs @@ -0,0 +1,266 @@ +#![forbid(unsafe_code)] + +//! `clean-path` is a safe fork of the +//! [`path-clean`](https://crates.io/crates/path-clean) crate. +//! +//! # About +//! +//! This fork aims to provide the same utility as +//! [`path-clean`](https://crates.io/crates/path-clean), without using unsafe. Additionally, the api +//! is improved ([`clean`] takes `AsRef` instead of just `&str`) and `Clean` is implemented on +//! `Path` in addition to just `PathBuf`. +//! +//! The main cleaning procedure is implemented using the methods provided by `PathBuf`, thus it should +//! bring portability benefits over [`path-clean`](https://crates.io/crates/path-clean) w.r.t. correctly +//! handling cross-platform filepaths. However, the current implementation is not highly-optimized, so +//! if performance is top-priority, consider using [`path-clean`](https://crates.io/crates/path-clean) +//! instead. +//! +//! # Specification +//! +//! The cleaning works as follows: +//! 1. Reduce multiple slashes to a single slash. +//! 2. Eliminate `.` path name elements (the current directory). +//! 3. Eliminate `..` path name elements (the parent directory) and the non-`.` non-`..`, element that precedes them. +//! 4. Eliminate `..` elements that begin a rooted path, that is, replace `/..` by `/` at the beginning of a path. +//! 5. Leave intact `..` elements that begin a non-rooted path. +//! +//! If the result of this process is an empty string, return the +//! string `"."`, representing the current directory. +//! +//! This transformation is performed lexically, without touching the filesystem. Therefore it doesn't do +//! any symlink resolution or absolute path resolution. For more information you can see ["Getting +//! Dot-Dot Right"](https://9p.io/sys/doc/lexnames.html). +//! +//! This functionality is exposed in the [`clean`] function and [`Clean`] trait implemented for +//! [`crate::PathBuf`] and [`crate::Path`]. +//! +//! +//! # Example +//! +//! ```rust +//! use path_base::{Path, PathBuf}; +//! use path_base::clean_path::{clean, Clean}; +//! +//! assert_eq!(clean("foo/../../bar"), PathBuf::from("../bar")); +//! assert_eq!(Path::new("hello/world/..").clean(), PathBuf::from("hello")); +//! assert_eq!( +//! PathBuf::from("/test/../path/").clean(), +//! PathBuf::from("/path") +//! ); +//! ``` + +use crate::{Path, PathBuf}; + +/// The Clean trait implements the `clean` method. +pub trait Clean { + fn clean(&self) -> PathBuf; +} + +/// Clean implemented for PathBuf +impl Clean for PathBuf { + fn clean(&self) -> PathBuf { + clean(self) + } +} + +/// Clean implemented for PathBuf +impl Clean for Path { + fn clean(&self) -> PathBuf { + clean(self) + } +} + +/** +Clean the given path to according to a set of rules: +1. Reduce multiple slashes to a single slash. +2. Eliminate `.` path name elements (the current directory). +3. Eliminate `..` path name elements (the parent directory) and the non-`.` non-`..`, element that precedes them. +4. Eliminate `..` elements that begin a rooted path, that is, replace `/..` by `/` at the beginning of a path. +5. Leave intact `..` elements that begin a non-rooted path. + +If the result of this process is an empty string, return the string `"."`, representing the current directory. + +Note that symlinks and absolute paths are not resolved. + +# Example + +```rust +# use path_base::PathBuf; +# use path_base::clean_path::{clean, Clean}; +assert_eq!(clean("foo/../../bar"), PathBuf::from("../bar")); +``` +*/ +pub fn clean>(path: P) -> PathBuf { + let path = path.as_ref(); + clean_internal(path) +} + +/// The core implementation. +fn clean_internal(path: &Path) -> PathBuf { + // based off of github.com/rust-lang/cargo/blob/fede83/src/cargo/util/paths.rs#L61 + use crate::Component; + + let mut components = path.components().peekable(); + let mut cleaned = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() { + components.next(); + PathBuf::from(c.as_os_str()) + } else { + PathBuf::new() + }; + + // amount of leading parentdir components in `cleaned` + let mut dotdots = 0; + // amount of components in `cleaned` + // invariant: component_count >= dotdots + let mut component_count = 0; + + for component in components { + match component { + Component::Prefix(..) => unreachable!(), + Component::RootDir => { + cleaned.push(component.as_os_str()); + component_count += 1; + } + Component::CurDir => {} + Component::ParentDir if component_count == 1 && cleaned.is_absolute() => {} + Component::ParentDir if component_count == dotdots => { + cleaned.push(".."); + dotdots += 1; + component_count += 1; + } + Component::ParentDir => { + cleaned.pop(); + component_count -= 1; + } + Component::Normal(c) => { + cleaned.push(c); + component_count += 1; + } + } + } + + if component_count == 0 { + cleaned.push("."); + } + + cleaned +} + +#[cfg(test)] +mod tests { + use alloc::vec; + + use super::{clean, Clean}; + use crate::PathBuf; + + #[test] + fn test_empty_path_is_current_dir() { + assert_eq!(clean(""), PathBuf::from(".")); + } + + #[test] + fn test_clean_paths_dont_change() { + let tests = vec![(".", "."), ("..", ".."), ("/", "/")]; + + for test in tests { + assert_eq!( + clean(test.0), + PathBuf::from(test.1), + "clean({}) == {}", + test.0, + test.1 + ); + } + } + + #[test] + fn test_replace_multiple_slashes() { + let tests = vec![ + ("/", "/"), + ("//", "/"), + ("///", "/"), + (".//", "."), + ("//..", "/"), + ("..//", ".."), + ("/..//", "/"), + ("/.//./", "/"), + ("././/./", "."), + ("path//to///thing", "path/to/thing"), + ("/path//to///thing", "/path/to/thing"), + ]; + + for test in tests { + assert_eq!( + clean(test.0), + PathBuf::from(test.1), + "clean({}) == {}", + test.0, + test.1 + ); + } + } + + #[test] + fn test_eliminate_current_dir() { + let tests = vec![ + ("./", "."), + ("/./", "/"), + ("./test", "test"), + ("./test/./path", "test/path"), + ("/test/./path/", "/test/path"), + ("test/path/.", "test/path"), + ]; + + for test in tests { + assert_eq!( + clean(test.0), + PathBuf::from(test.1), + "clean({}) == {}", + test.0, + test.1 + ); + } + } + + #[test] + fn test_eliminate_parent_dir() { + let tests = vec![ + ("/..", "/"), + ("/../test", "/test"), + ("test/..", "."), + ("test/path/..", "test"), + ("test/../path", "path"), + ("/test/../path", "/path"), + ("test/path/../../", "."), + ("test/path/../../..", ".."), + ("/test/path/../../..", "/"), + ("/test/path/../../../..", "/"), + ("test/path/../../../..", "../.."), + ("test/path/../../another/path", "another/path"), + ("test/path/../../another/path/..", "another"), + ("../test", "../test"), + ("../test/", "../test"), + ("../test/path", "../test/path"), + ("../test/..", ".."), + ]; + + for test in tests { + assert_eq!( + clean(test.0), + PathBuf::from(test.1), + "clean({}) == {}", + test.0, + test.1 + ); + } + } + + #[test] + fn test_trait() { + assert_eq!( + PathBuf::from("/test/../path/").clean(), + PathBuf::from("/path") + ); + } +} diff --git a/kernel/crates/path_base/src/lib.rs b/kernel/crates/path_base/src/lib.rs new file mode 100644 index 000000000..28bb2ac6d --- /dev/null +++ b/kernel/crates/path_base/src/lib.rs @@ -0,0 +1,1938 @@ +#![no_std] +#![feature(strict_provenance)] +extern crate alloc; +pub mod clean_path; +#[cfg(test)] +mod tests; +mod util; + +use core::{ + borrow::Borrow, + cmp, fmt, + hash::{Hash, Hasher}, + iter::FusedIterator, + ops::{self, Deref}, + str::{self, FromStr}, +}; + +use alloc::{ + borrow::{Cow, ToOwned}, + boxed::Box, + collections::TryReserveError, + rc::Rc, + string::{String, ToString}, + sync::Arc, + vec::Vec, +}; + +use self::util::{is_sep_byte, is_verbatim_sep, parse_prefix, MAIN_SEP_STR}; + +#[derive(Copy, Clone, Debug, Hash, PartialOrd, Ord, PartialEq, Eq)] +pub enum Prefix<'a> { + Verbatim(&'a str), + + VerbatimUNC(&'a str, &'a str), + + VerbatimDisk(u8), + + DeviceNS(&'a str), + + UNC(&'a str, &'a str), + + Disk(u8), +} + +impl<'a> Prefix<'a> { + #[inline] + fn len(&self) -> usize { + use self::Prefix::*; + fn os_str_len(s: &str) -> usize { + s.as_bytes().len() + } + match *self { + Verbatim(x) => 4 + os_str_len(x), + VerbatimUNC(x, y) => { + 8 + os_str_len(x) + + if os_str_len(y) > 0 { + 1 + os_str_len(y) + } else { + 0 + } + } + VerbatimDisk(_) => 6, + UNC(x, y) => { + 2 + os_str_len(x) + + if os_str_len(y) > 0 { + 1 + os_str_len(y) + } else { + 0 + } + } + DeviceNS(x) => 4 + os_str_len(x), + Disk(_) => 2, + } + } + + #[inline] + #[must_use] + pub fn is_verbatim(&self) -> bool { + use self::Prefix::*; + matches!(*self, Verbatim(_) | VerbatimDisk(_) | VerbatimUNC(..)) + } + + #[inline] + fn is_drive(&self) -> bool { + matches!(*self, Prefix::Disk(_)) + } + + #[inline] + fn has_implicit_root(&self) -> bool { + !self.is_drive() + } +} + +fn iter_after<'a, 'b, I, J>(mut iter: I, mut prefix: J) -> Option +where + I: Iterator> + Clone, + J: Iterator>, +{ + loop { + let mut iter_next = iter.clone(); + match (iter_next.next(), prefix.next()) { + (Some(ref x), Some(ref y)) if x == y => (), + (Some(_), Some(_)) => return None, + (Some(_), None) => return Some(iter), + (None, None) => return Some(iter), + (None, Some(_)) => return None, + } + iter = iter_next; + } +} + +// Detect scheme on Redox +fn has_redox_scheme(s: &[u8]) -> bool { + cfg!(target_os = "redox") && s.contains(&b':') +} + +/// Says whether the first byte after the prefix is a separator. +fn has_physical_root(s: &[u8], prefix: Option>) -> bool { + let path = if let Some(p) = prefix { + &s[p.len()..] + } else { + s + }; + !path.is_empty() && is_sep_byte(path[0]) +} + +// basic workhorse for splitting stem and extension +fn rsplit_file_at_dot(file: &str) -> (Option<&str>, Option<&str>) { + if file.as_bytes() == b".." { + return (Some(file), None); + } + + // The unsafety here stems from converting between &str and &[u8] + // and back. This is safe to do because (1) we only look at ASCII + // contents of the encoding and (2) new &str values are produced + // only from ASCII-bounded slices of existing &str values. + let mut iter = file.as_bytes().rsplitn(2, |b| *b == b'.'); + let after = iter.next(); + let before = iter.next(); + if before == Some(b"") { + (Some(file), None) + } else { + unsafe { + ( + before.map(|s| str::from_utf8_unchecked(s)), + after.map(|s| str::from_utf8_unchecked(s)), + ) + } + } +} + +fn split_file_at_dot(file: &str) -> (&str, Option<&str>) { + let slice = file.as_bytes(); + if slice == b".." { + return (file, None); + } + + // The unsafety here stems from converting between &str and &[u8] + // and back. This is safe to do because (1) we only look at ASCII + // contents of the encoding and (2) new &str values are produced + // only from ASCII-bounded slices of existing &str values. + let i = match slice[1..].iter().position(|b| *b == b'.') { + Some(i) => i + 1, + None => return (file, None), + }; + let before = &slice[..i]; + let after = &slice[i + 1..]; + unsafe { + ( + str::from_utf8_unchecked(before), + Some(str::from_utf8_unchecked(after)), + ) + } +} + +//////////////////////////////////////////////////////////////////////////////// +// The core iterators +//////////////////////////////////////////////////////////////////////////////// + +/// Component parsing works by a double-ended state machine; the cursors at the +/// front and back of the path each keep track of what parts of the path have +/// been consumed so far. +/// +/// Going front to back, a path is made up of a prefix, a starting +/// directory component, and a body (of normal components) +#[derive(Copy, Clone, PartialEq, PartialOrd, Debug)] +enum State { + Prefix = 0, // c: + StartDir = 1, // / or . or nothing + Body = 2, // foo/bar/baz + Done = 3, +} + +#[derive(Copy, Clone, Eq, Debug)] +pub struct PrefixComponent<'a> { + /// The prefix as an unparsed `str` slice. + raw: &'a str, + + /// The parsed prefix data. + parsed: Prefix<'a>, +} + +impl<'a> PrefixComponent<'a> { + /// Returns the parsed prefix data. + /// + /// See [`Prefix`]'s documentation for more information on the different + /// kinds of prefixes. + + #[must_use] + #[inline] + pub fn kind(&self) -> Prefix<'a> { + self.parsed + } + + /// Returns the raw [`str`] slice for this prefix. + + #[must_use] + #[inline] + pub fn as_os_str(&self) -> &'a str { + self.raw + } +} + +impl<'a> PartialEq for PrefixComponent<'a> { + #[inline] + fn eq(&self, other: &PrefixComponent<'a>) -> bool { + self.parsed == other.parsed + } +} + +impl<'a> PartialOrd for PrefixComponent<'a> { + #[inline] + fn partial_cmp(&self, other: &PrefixComponent<'a>) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for PrefixComponent<'_> { + #[inline] + fn cmp(&self, other: &Self) -> cmp::Ordering { + Ord::cmp(&self.parsed, &other.parsed) + } +} + +impl Hash for PrefixComponent<'_> { + fn hash(&self, h: &mut H) { + self.parsed.hash(h); + } +} + +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] + +pub enum Component<'a> { + /// A Windows path prefix, e.g., `C:` or `\\server\share`. + /// + /// There is a large variety of prefix types, see [`Prefix`]'s documentation + /// for more. + /// + /// Does not occur on Unix. + Prefix(PrefixComponent<'a>), + + /// The root directory component, appears after any prefix and before anything else. + /// + /// It represents a separator that designates that a path starts from root. + RootDir, + + /// A reference to the current directory, i.e., `.`. + CurDir, + + /// A reference to the parent directory, i.e., `..`. + ParentDir, + + /// A normal component, e.g., `a` and `b` in `a/b`. + /// + /// This variant is the most common one, it represents references to files + /// or directories. + Normal(&'a str), +} +impl<'a> Component<'a> { + #[must_use = "`self` will be dropped if the result is not used"] + pub fn as_os_str(self) -> &'a str { + match self { + Component::Prefix(p) => p.as_os_str(), + Component::RootDir => MAIN_SEP_STR, + Component::CurDir => ".", + Component::ParentDir => "..", + Component::Normal(path) => path, + } + } +} + +impl AsRef for Component<'_> { + #[inline] + fn as_ref(&self) -> &str { + self.as_os_str() + } +} + +impl AsRef for Component<'_> { + #[inline] + fn as_ref(&self) -> &Path { + self.as_os_str().as_ref() + } +} + +#[derive(Clone)] +#[must_use = "iterators are lazy and do nothing unless consumed"] + +pub struct Components<'a> { + // The path left to parse components from + path: &'a [u8], + + // The prefix as it was originally parsed, if any + prefix: Option>, + + // true if path *physically* has a root separator; for most Windows + // prefixes, it may have a "logical" root separator for the purposes of + // normalization, e.g., \\server\share == \\server\share\. + has_physical_root: bool, + + // The iterator is double-ended, and these two states keep track of what has + // been produced from either end + front: State, + back: State, +} + +/// An iterator over the [`Component`]s of a [`Path`], as [`str`] slices. +/// +/// This `struct` is created by the [`iter`] method on [`Path`]. +/// See its documentation for more. +/// +/// [`iter`]: Path::iter +#[derive(Clone)] +#[must_use = "iterators are lazy and do nothing unless consumed"] + +pub struct Iter<'a> { + inner: Components<'a>, +} + +impl fmt::Debug for Components<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + struct DebugHelper<'a>(&'a Path); + + impl fmt::Debug for DebugHelper<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_list().entries(self.0.components()).finish() + } + } + + f.debug_tuple("Components") + .field(&DebugHelper(self.as_path())) + .finish() + } +} + +impl<'a> Components<'a> { + // how long is the prefix, if any? + #[inline] + fn prefix_len(&self) -> usize { + self.prefix.as_ref().map(Prefix::len).unwrap_or(0) + } + + #[inline] + fn prefix_verbatim(&self) -> bool { + self.prefix + .as_ref() + .map(Prefix::is_verbatim) + .unwrap_or(false) + } + + /// how much of the prefix is left from the point of view of iteration? + #[inline] + fn prefix_remaining(&self) -> usize { + if self.front == State::Prefix { + self.prefix_len() + } else { + 0 + } + } + + // Given the iteration so far, how much of the pre-State::Body path is left? + #[inline] + fn len_before_body(&self) -> usize { + let root = if self.front <= State::StartDir && self.has_physical_root { + 1 + } else { + 0 + }; + let cur_dir = if self.front <= State::StartDir && self.include_cur_dir() { + 1 + } else { + 0 + }; + self.prefix_remaining() + root + cur_dir + } + + // is the iteration complete? + #[inline] + fn finished(&self) -> bool { + self.front == State::Done || self.back == State::Done || self.front > self.back + } + + #[inline] + fn is_sep_byte(&self, b: u8) -> bool { + if self.prefix_verbatim() { + is_verbatim_sep(b) + } else { + is_sep_byte(b) + } + } + + #[must_use] + + pub fn as_path(&self) -> &'a Path { + let mut comps = self.clone(); + if comps.front == State::Body { + comps.trim_left(); + } + if comps.back == State::Body { + comps.trim_right(); + } + unsafe { Path::from_u8_slice(comps.path) } + } + + /// Is the *original* path rooted? + fn has_root(&self) -> bool { + if self.has_physical_root { + return true; + } + if let Some(p) = self.prefix { + if p.has_implicit_root() { + return true; + } + } + false + } + + /// Should the normalized path include a leading . ? + fn include_cur_dir(&self) -> bool { + if self.has_root() { + return false; + } + let mut iter = self.path[self.prefix_remaining()..].iter(); + match (iter.next(), iter.next()) { + (Some(&b'.'), None) => true, + (Some(&b'.'), Some(&b)) => self.is_sep_byte(b), + _ => false, + } + } + + // parse a given byte sequence following the str encoding into the + // corresponding path component + unsafe fn parse_single_component<'b>(&self, comp: &'b [u8]) -> Option> { + match comp { + b"." if self.prefix_verbatim() => Some(Component::CurDir), + b"." => None, // . components are normalized away, except at + // the beginning of a path, which is treated + // separately via `include_cur_dir` + b".." => Some(Component::ParentDir), + b"" => None, + _ => Some(Component::Normal(unsafe { str::from_utf8_unchecked(comp) })), + } + } + + // parse a component from the left, saying how many bytes to consume to + // remove the component + fn parse_next_component(&self) -> (usize, Option>) { + debug_assert!(self.front == State::Body); + let (extra, comp) = match self.path.iter().position(|b| self.is_sep_byte(*b)) { + None => (0, self.path), + Some(i) => (1, &self.path[..i]), + }; + // SAFETY: `comp` is a valid substring, since it is split on a separator. + (comp.len() + extra, unsafe { + self.parse_single_component(comp) + }) + } + + // parse a component from the right, saying how many bytes to consume to + // remove the component + fn parse_next_component_back(&self) -> (usize, Option>) { + debug_assert!(self.back == State::Body); + let start = self.len_before_body(); + let (extra, comp) = match self.path[start..] + .iter() + .rposition(|b| self.is_sep_byte(*b)) + { + None => (0, &self.path[start..]), + Some(i) => (1, &self.path[start + i + 1..]), + }; + // SAFETY: `comp` is a valid substring, since it is split on a separator. + (comp.len() + extra, unsafe { + self.parse_single_component(comp) + }) + } + + // trim away repeated separators (i.e., empty components) on the left + fn trim_left(&mut self) { + while !self.path.is_empty() { + let (size, comp) = self.parse_next_component(); + if comp.is_some() { + return; + } else { + self.path = &self.path[size..]; + } + } + } + + // trim away repeated separators (i.e., empty components) on the right + fn trim_right(&mut self) { + while self.path.len() > self.len_before_body() { + let (size, comp) = self.parse_next_component_back(); + if comp.is_some() { + return; + } else { + self.path = &self.path[..self.path.len() - size]; + } + } + } +} + +impl AsRef for Components<'_> { + #[inline] + fn as_ref(&self) -> &Path { + self.as_path() + } +} + +impl AsRef for Components<'_> { + #[inline] + fn as_ref(&self) -> &str { + self.as_path().as_os_str() + } +} + +impl fmt::Debug for Iter<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + struct DebugHelper<'a>(&'a Path); + + impl fmt::Debug for DebugHelper<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_list().entries(self.0.iter()).finish() + } + } + + f.debug_tuple("Iter") + .field(&DebugHelper(self.as_path())) + .finish() + } +} + +impl<'a> Iter<'a> { + #[must_use] + #[inline] + pub fn as_path(&self) -> &'a Path { + self.inner.as_path() + } +} + +impl AsRef for Iter<'_> { + #[inline] + fn as_ref(&self) -> &Path { + self.as_path() + } +} + +impl AsRef for Iter<'_> { + #[inline] + fn as_ref(&self) -> &str { + self.as_path().as_os_str() + } +} + +impl<'a> Iterator for Iter<'a> { + type Item = &'a str; + + #[inline] + fn next(&mut self) -> Option<&'a str> { + self.inner.next().map(Component::as_os_str) + } +} + +impl<'a> DoubleEndedIterator for Iter<'a> { + #[inline] + fn next_back(&mut self) -> Option<&'a str> { + self.inner.next_back().map(Component::as_os_str) + } +} + +impl FusedIterator for Iter<'_> {} + +impl<'a> Iterator for Components<'a> { + type Item = Component<'a>; + + fn next(&mut self) -> Option> { + while !self.finished() { + match self.front { + State::Prefix if self.prefix_len() > 0 => { + self.front = State::StartDir; + debug_assert!(self.prefix_len() <= self.path.len()); + let raw = &self.path[..self.prefix_len()]; + self.path = &self.path[self.prefix_len()..]; + return Some(Component::Prefix(PrefixComponent { + raw: unsafe { str::from_utf8_unchecked(raw) }, + parsed: self.prefix.unwrap(), + })); + } + State::Prefix => { + self.front = State::StartDir; + } + State::StartDir => { + self.front = State::Body; + if self.has_physical_root { + debug_assert!(!self.path.is_empty()); + self.path = &self.path[1..]; + return Some(Component::RootDir); + } else if let Some(p) = self.prefix { + if p.has_implicit_root() && !p.is_verbatim() { + return Some(Component::RootDir); + } + } else if self.include_cur_dir() { + debug_assert!(!self.path.is_empty()); + self.path = &self.path[1..]; + return Some(Component::CurDir); + } + } + State::Body if !self.path.is_empty() => { + let (size, comp) = self.parse_next_component(); + self.path = &self.path[size..]; + if comp.is_some() { + return comp; + } + } + State::Body => { + self.front = State::Done; + } + State::Done => unreachable!(), + } + } + None + } +} + +impl<'a> DoubleEndedIterator for Components<'a> { + fn next_back(&mut self) -> Option> { + while !self.finished() { + match self.back { + State::Body if self.path.len() > self.len_before_body() => { + let (size, comp) = self.parse_next_component_back(); + self.path = &self.path[..self.path.len() - size]; + if comp.is_some() { + return comp; + } + } + State::Body => { + self.back = State::StartDir; + } + State::StartDir => { + self.back = State::Prefix; + if self.has_physical_root { + self.path = &self.path[..self.path.len() - 1]; + return Some(Component::RootDir); + } else if let Some(p) = self.prefix { + if p.has_implicit_root() && !p.is_verbatim() { + return Some(Component::RootDir); + } + } else if self.include_cur_dir() { + self.path = &self.path[..self.path.len() - 1]; + return Some(Component::CurDir); + } + } + State::Prefix if self.prefix_len() > 0 => { + self.back = State::Done; + return Some(Component::Prefix(PrefixComponent { + raw: unsafe { str::from_utf8_unchecked(self.path) }, + parsed: self.prefix.unwrap(), + })); + } + State::Prefix => { + self.back = State::Done; + return None; + } + State::Done => unreachable!(), + } + } + None + } +} + +impl FusedIterator for Components<'_> {} + +impl<'a> PartialEq for Components<'a> { + #[inline] + fn eq(&self, other: &Components<'a>) -> bool { + let Components { + path: _, + front: _, + back: _, + has_physical_root: _, + prefix: _, + } = self; + + // Fast path for exact matches, e.g. for hashmap lookups. + // Don't explicitly compare the prefix or has_physical_root fields since they'll + // either be covered by the `path` buffer or are only relevant for `prefix_verbatim()`. + if self.path.len() == other.path.len() + && self.front == other.front + && self.back == State::Body + && other.back == State::Body + && self.prefix_verbatim() == other.prefix_verbatim() + { + // possible future improvement: this could bail out earlier if there were a + // reverse memcmp/bcmp comparing back to front + if self.path == other.path { + return true; + } + } + + // compare back to front since absolute paths often share long prefixes + Iterator::eq(self.clone().rev(), other.clone().rev()) + } +} + +impl Eq for Components<'_> {} + +impl<'a> PartialOrd for Components<'a> { + #[inline] + fn partial_cmp(&self, other: &Components<'a>) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Components<'_> { + #[inline] + fn cmp(&self, other: &Self) -> cmp::Ordering { + compare_components(self.clone(), other.clone()) + } +} + +fn compare_components(mut left: Components<'_>, mut right: Components<'_>) -> cmp::Ordering { + // Fast path for long shared prefixes + // + // - compare raw bytes to find first mismatch + // - backtrack to find separator before mismatch to avoid ambiguous parsings of '.' or '..' characters + // - if found update state to only do a component-wise comparison on the remainder, + // otherwise do it on the full path + // + // The fast path isn't taken for paths with a PrefixComponent to avoid backtracking into + // the middle of one + if left.prefix.is_none() && right.prefix.is_none() && left.front == right.front { + // possible future improvement: a [u8]::first_mismatch simd implementation + let first_difference = match left.path.iter().zip(right.path).position(|(&a, &b)| a != b) { + None if left.path.len() == right.path.len() => return cmp::Ordering::Equal, + None => left.path.len().min(right.path.len()), + Some(diff) => diff, + }; + + if let Some(previous_sep) = left.path[..first_difference] + .iter() + .rposition(|&b| left.is_sep_byte(b)) + { + let mismatched_component_start = previous_sep + 1; + left.path = &left.path[mismatched_component_start..]; + left.front = State::Body; + right.path = &right.path[mismatched_component_start..]; + right.front = State::Body; + } + } + + Iterator::cmp(left, right) +} + +#[derive(Copy, Clone, Debug)] +#[must_use = "iterators are lazy and do nothing unless consumed"] +pub struct Ancestors<'a> { + next: Option<&'a Path>, +} + +impl<'a> Iterator for Ancestors<'a> { + type Item = &'a Path; + + #[inline] + fn next(&mut self) -> Option { + let next = self.next; + self.next = next.and_then(Path::parent); + next + } +} + +impl FusedIterator for Ancestors<'_> {} + +// `PathBuf::as_mut_vec` current implementation relies +// on `PathBuf` being layout-compatible with `Vec`. +// However, `PathBuf` layout is considered an implementation detail and must not be relied upon. We +// want `repr(transparent)` but we don't want it to show up in rustdoc, so we hide it under +// `cfg(doc)`. This is an ad-hoc implementation of attribute privacy. +#[cfg_attr(not(doc), repr(transparent))] +pub struct PathBuf { + inner: String, +} + +impl PathBuf { + #[inline] + fn as_mut_vec(&mut self) -> &mut Vec { + unsafe { &mut *(self as *mut PathBuf as *mut Vec) } + } + + #[must_use] + #[inline] + pub fn new() -> PathBuf { + PathBuf { + inner: String::new(), + } + } + + #[must_use] + #[inline] + pub fn with_capacity(capacity: usize) -> PathBuf { + PathBuf { + inner: String::with_capacity(capacity), + } + } + + #[must_use] + #[inline] + pub fn as_path(&self) -> &Path { + self + } + + pub fn push>(&mut self, path: P) { + self._push(path.as_ref()) + } + + fn _push(&mut self, path: &Path) { + // in general, a separator is needed if the rightmost byte is not a separator + let mut need_sep = self + .as_mut_vec() + .last() + .map(|c| !is_sep_byte(*c)) + .unwrap_or(false); + + // in the special case of `C:` on Windows, do *not* add a separator + let comps = self.components(); + + if comps.prefix_len() > 0 + && comps.prefix_len() == comps.path.len() + && comps.prefix.unwrap().is_drive() + { + need_sep = false + } + + // absolute `path` replaces `self` + if path.is_absolute() || path.prefix().is_some() { + self.as_mut_vec().truncate(0); + + // verbatim paths need . and .. removed + } else if comps.prefix_verbatim() && !path.inner.is_empty() { + let mut buf: Vec<_> = comps.collect(); + for c in path.components() { + match c { + Component::RootDir => { + buf.truncate(1); + buf.push(c); + } + Component::CurDir => (), + Component::ParentDir => { + if let Some(Component::Normal(_)) = buf.last() { + buf.pop(); + } + } + _ => buf.push(c), + } + } + + let mut res = String::new(); + let mut need_sep = false; + + for c in buf { + if need_sep && c != Component::RootDir { + res.push_str(MAIN_SEP_STR); + } + res.push_str(c.as_os_str()); + + need_sep = match c { + Component::RootDir => false, + Component::Prefix(prefix) => { + !prefix.parsed.is_drive() && prefix.parsed.len() > 0 + } + _ => true, + } + } + + self.inner = res; + return; + + // `path` has a root but no prefix, e.g., `\windows` (Windows only) + } else if path.has_root() { + let prefix_len = self.components().prefix_remaining(); + self.as_mut_vec().truncate(prefix_len); + + // `path` is a pure relative path + } else if need_sep { + self.inner.push_str(MAIN_SEP_STR); + } + + self.inner.push_str(path.as_ref()); + } + + pub fn pop(&mut self) -> bool { + match self.parent().map(|p| p.as_u8_slice().len()) { + Some(len) => { + self.as_mut_vec().truncate(len); + true + } + None => false, + } + } + + pub fn set_file_name>(&mut self, file_name: S) { + self._set_file_name(file_name.as_ref()) + } + + fn _set_file_name(&mut self, file_name: &str) { + if self.file_name().is_some() { + let popped = self.pop(); + debug_assert!(popped); + } + self.push(file_name); + } + + pub fn set_extension>(&mut self, extension: S) -> bool { + self._set_extension(extension.as_ref()) + } + + fn _set_extension(&mut self, extension: &str) -> bool { + let file_stem = match self.file_stem() { + None => return false, + Some(f) => f.as_bytes(), + }; + + // truncate until right after the file stem + let end_file_stem = file_stem[file_stem.len()..].as_ptr().addr(); + let start = self.inner.as_bytes().as_ptr().addr(); + let v = self.as_mut_vec(); + v.truncate(end_file_stem.wrapping_sub(start)); + + // add the new extension, if any + let new = extension.as_bytes(); + if !new.is_empty() { + v.reserve_exact(new.len() + 1); + v.push(b'.'); + v.extend_from_slice(new); + } + + true + } + + #[must_use] + #[inline] + pub fn as_mut_os_string(&mut self) -> &mut String { + &mut self.inner + } + + #[must_use = "`self` will be dropped if the result is not used"] + #[inline] + pub fn into_os_string(self) -> String { + self.inner + } + + /// Converts this `PathBuf` into a [boxed](Box) [`Path`]. + #[must_use = "`self` will be dropped if the result is not used"] + #[inline] + pub fn into_boxed_path(self) -> Box { + let rw = Box::into_raw(self.inner.into_boxed_str()) as *mut Path; + unsafe { Box::from_raw(rw) } + } + + /// Invokes [`capacity`] on the underlying instance of [`string`]. + /// + /// [`capacity`]: string::capacity + #[must_use] + #[inline] + pub fn capacity(&self) -> usize { + self.inner.capacity() + } + + /// Invokes [`clear`] on the underlying instance of [`string`]. + /// + /// [`clear`]: string::clear + #[inline] + pub fn clear(&mut self) { + self.inner.clear() + } + + /// Invokes [`reserve`] on the underlying instance of [`string`]. + /// + /// [`reserve`]: string::reserve + #[inline] + pub fn reserve(&mut self, additional: usize) { + self.inner.reserve(additional) + } + + /// Invokes [`try_reserve`] on the underlying instance of [`string`]. + /// + /// [`try_reserve`]: string::try_reserve + #[inline] + pub fn try_reserve(&mut self, additional: usize) -> Result<(), TryReserveError> { + self.inner.try_reserve(additional) + } + + /// Invokes [`reserve_exact`] on the underlying instance of [`string`]. + /// + /// [`reserve_exact`]: string::reserve_exact + #[inline] + pub fn reserve_exact(&mut self, additional: usize) { + self.inner.reserve_exact(additional) + } + + /// Invokes [`try_reserve_exact`] on the underlying instance of [`string`]. + /// + /// [`try_reserve_exact`]: string::try_reserve_exact + #[inline] + pub fn try_reserve_exact(&mut self, additional: usize) -> Result<(), TryReserveError> { + self.inner.try_reserve_exact(additional) + } + + /// Invokes [`shrink_to_fit`] on the underlying instance of [`string`]. + /// + /// [`shrink_to_fit`]: string::shrink_to_fit + #[inline] + pub fn shrink_to_fit(&mut self) { + self.inner.shrink_to_fit() + } + + /// Invokes [`shrink_to`] on the underlying instance of [`string`]. + /// + /// [`shrink_to`]: string::shrink_to + #[inline] + pub fn shrink_to(&mut self, min_capacity: usize) { + self.inner.shrink_to(min_capacity) + } +} + +impl Clone for PathBuf { + #[inline] + fn clone(&self) -> Self { + PathBuf { + inner: self.inner.clone(), + } + } + + #[inline] + fn clone_from(&mut self, source: &Self) { + self.inner.clone_from(&source.inner) + } +} + +impl From<&Path> for Box { + /// Creates a boxed [`Path`] from a reference. + /// + /// This will allocate and clone `path` to it. + fn from(path: &Path) -> Box { + let boxed: Box = path.inner.into(); + let rw = Box::into_raw(boxed) as *mut Path; + unsafe { Box::from_raw(rw) } + } +} + +impl From> for Box { + /// Creates a boxed [`Path`] from a clone-on-write pointer. + /// + /// Converting from a `Cow::Owned` does not clone or allocate. + #[inline] + fn from(cow: Cow<'_, Path>) -> Box { + match cow { + Cow::Borrowed(path) => Box::from(path), + Cow::Owned(path) => Box::from(path), + } + } +} + +impl From> for PathBuf { + /// Converts a [Box]<[Path]> into a [`PathBuf`]. + /// + /// This conversion does not allocate or copy memory. + #[inline] + fn from(boxed: Box) -> PathBuf { + boxed.into_path_buf() + } +} + +impl From for Box { + /// Converts a [`PathBuf`] into a [Box]<[Path]>. + /// + /// This conversion currently should not allocate memory, + /// but this behavior is not guaranteed on all platforms or in all future versions. + #[inline] + fn from(p: PathBuf) -> Box { + p.into_boxed_path() + } +} + +impl Clone for Box { + #[inline] + fn clone(&self) -> Self { + self.to_path_buf().into_boxed_path() + } +} + +impl> From<&T> for PathBuf { + /// Converts a borrowed [`str`] to a [`PathBuf`]. + /// + /// Allocates a [`PathBuf`] and copies the data into it. + #[inline] + fn from(s: &T) -> PathBuf { + PathBuf::from(s.as_ref().to_string()) + } +} + +impl From for PathBuf { + /// Converts an [`String`] into a [`PathBuf`] + /// + /// This conversion does not allocate or copy memory. + #[inline] + fn from(s: String) -> PathBuf { + PathBuf { inner: s } + } +} + +impl From for String { + /// Converts a [`PathBuf`] into an [`String`] + /// + /// This conversion does not allocate or copy memory. + #[inline] + fn from(path_buf: PathBuf) -> String { + path_buf.inner + } +} + +impl FromStr for PathBuf { + type Err = core::convert::Infallible; + + #[inline] + fn from_str(s: &str) -> Result { + Ok(PathBuf::from(s)) + } +} + +impl> FromIterator

for PathBuf { + fn from_iter>(iter: I) -> PathBuf { + let mut buf = PathBuf::new(); + buf.extend(iter); + buf + } +} + +impl> Extend

for PathBuf { + fn extend>(&mut self, iter: I) { + iter.into_iter().for_each(move |p| self.push(p.as_ref())); + } +} + +impl fmt::Debug for PathBuf { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&**self, formatter) + } +} + +impl ops::Deref for PathBuf { + type Target = Path; + #[inline] + fn deref(&self) -> &Path { + Path::new(&self.inner) + } +} + +// #[stable(feature = "path_buf_deref_mut", since = "1.68.0")] +impl ops::DerefMut for PathBuf { + #[inline] + fn deref_mut(&mut self) -> &mut Path { + Path::from_inner_mut(&mut self.inner) + } +} + +impl Borrow for PathBuf { + #[inline] + fn borrow(&self) -> &Path { + self.deref() + } +} + +// #[stable(feature = "default_for_pathbuf", since = "1.17.0")] +impl Default for PathBuf { + #[inline] + fn default() -> Self { + PathBuf::new() + } +} + +// #[stable(feature = "cow_from_path", since = "1.6.0")] +impl<'a> From<&'a Path> for Cow<'a, Path> { + /// Creates a clone-on-write pointer from a reference to + /// [`Path`]. + /// + /// This conversion does not clone or allocate. + #[inline] + fn from(s: &'a Path) -> Cow<'a, Path> { + Cow::Borrowed(s) + } +} + +// #[stable(feature = "cow_from_path", since = "1.6.0")] +impl<'a> From for Cow<'a, Path> { + /// Creates a clone-on-write pointer from an owned + /// instance of [`PathBuf`]. + /// + /// This conversion does not clone or allocate. + #[inline] + fn from(s: PathBuf) -> Cow<'a, Path> { + Cow::Owned(s) + } +} + +// #[stable(feature = "cow_from_pathbuf_ref", since = "1.28.0")] +impl<'a> From<&'a PathBuf> for Cow<'a, Path> { + /// Creates a clone-on-write pointer from a reference to + /// [`PathBuf`]. + /// + /// This conversion does not clone or allocate. + #[inline] + fn from(p: &'a PathBuf) -> Cow<'a, Path> { + Cow::Borrowed(p.as_path()) + } +} + +// #[stable(feature = "pathbuf_from_cow_path", since = "1.28.0")] +impl<'a> From> for PathBuf { + /// Converts a clone-on-write pointer to an owned path. + /// + /// Converting from a `Cow::Owned` does not clone or allocate. + #[inline] + fn from(p: Cow<'a, Path>) -> Self { + p.into_owned() + } +} + +// #[stable(feature = "shared_from_slice2", since = "1.24.0")] +impl From for Arc { + /// Converts a [`PathBuf`] into an [Arc]<[Path]> by moving the [`PathBuf`] data + /// into a new [`Arc`] buffer. + #[inline] + fn from(s: PathBuf) -> Arc { + let arc: Arc = Arc::from(s.into_os_string()); + unsafe { Arc::from_raw(Arc::into_raw(arc) as *const Path) } + } +} + +// #[stable(feature = "shared_from_slice2", since = "1.24.0")] +impl From<&Path> for Arc { + /// Converts a [`Path`] into an [`Arc`] by copying the [`Path`] data into a new [`Arc`] buffer. + #[inline] + fn from(s: &Path) -> Arc { + let arc: Arc = Arc::from(s.as_os_str()); + unsafe { Arc::from_raw(Arc::into_raw(arc) as *const Path) } + } +} + +// #[stable(feature = "shared_from_slice2", since = "1.24.0")] +impl From for Rc { + /// Converts a [`PathBuf`] into an [Rc]<[Path]> by moving the [`PathBuf`] data into + /// a new [`Rc`] buffer. + #[inline] + fn from(s: PathBuf) -> Rc { + let rc: Rc = Rc::from(s.into_os_string()); + unsafe { Rc::from_raw(Rc::into_raw(rc) as *const Path) } + } +} + +// #[stable(feature = "shared_from_slice2", since = "1.24.0")] +impl From<&Path> for Rc { + /// Converts a [`Path`] into an [`Rc`] by copying the [`Path`] data into a new [`Rc`] buffer. + #[inline] + fn from(s: &Path) -> Rc { + let rc: Rc = Rc::from(s.as_os_str()); + unsafe { Rc::from_raw(Rc::into_raw(rc) as *const Path) } + } +} + +impl ToOwned for Path { + type Owned = PathBuf; + #[inline] + fn to_owned(&self) -> PathBuf { + self.to_path_buf() + } + #[inline] + fn clone_into(&self, target: &mut PathBuf) { + self.inner.clone_into(&mut target.inner); + } +} + +impl PartialEq for PathBuf { + #[inline] + fn eq(&self, other: &PathBuf) -> bool { + self.components() == other.components() + } +} + +impl Hash for PathBuf { + fn hash(&self, h: &mut H) { + self.as_path().hash(h) + } +} + +impl Eq for PathBuf {} + +impl PartialOrd for PathBuf { + #[inline] + fn partial_cmp(&self, other: &PathBuf) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for PathBuf { + #[inline] + fn cmp(&self, other: &PathBuf) -> cmp::Ordering { + compare_components(self.components(), other.components()) + } +} + +impl AsRef for PathBuf { + #[inline] + fn as_ref(&self) -> &str { + &self.inner[..] + } +} + +/// A slice of a path (akin to [`str`]). +/// +/// This type supports a number of operations for inspecting a path, including +/// breaking the path into its components (separated by `/` on Unix and by either +/// `/` or `\` on Windows), extracting the file name, determining whether the path +/// is absolute, and so on. +/// +/// This is an *unsized* type, meaning that it must always be used behind a +/// pointer like `&` or [`Box`]. For an owned version of this type, +/// see [`PathBuf`]. +/// +/// More details about the overall approach can be found in +/// the [module documentation](self). + +// `Path::new` current implementation relies +// on `Path` being layout-compatible with `str`. +// However, `Path` layout is considered an implementation detail and must not be relied upon. We +// want `repr(transparent)` but we don't want it to show up in rustdoc, so we hide it under +// `cfg(doc)`. This is an ad-hoc implementation of attribute privacy. +#[cfg_attr(not(doc), repr(transparent))] +pub struct Path { + inner: str, +} + +/// An error returned from [`Path::strip_prefix`] if the prefix was not found. +/// +/// This `struct` is created by the [`strip_prefix`] method on [`Path`]. +/// See its documentation for more. +/// +/// [`strip_prefix`]: Path::strip_prefix +#[derive(Debug, Clone, PartialEq, Eq)] +// #[stable(since = "1.7.0", feature = "strip_prefix")] +pub struct StripPrefixError(()); + +impl Path { + // The following (private!) function allows construction of a path from a u8 + // slice, which is only safe when it is known to follow the str encoding. + unsafe fn from_u8_slice(s: &[u8]) -> &Path { + unsafe { Path::new(str::from_utf8_unchecked(s)) } + } + // The following (private!) function reveals the byte encoding used for str. + fn as_u8_slice(&self) -> &[u8] { + self.inner.as_bytes() + } + + pub fn new + ?Sized>(s: &S) -> &Path { + unsafe { &*(s.as_ref() as *const str as *const Path) } + } + + fn from_inner_mut(inner: &mut str) -> &mut Path { + // SAFETY: Path is just a wrapper around str, + // therefore converting &mut str to &mut Path is safe. + unsafe { &mut *(inner as *mut str as *mut Path) } + } + + #[must_use] + #[inline] + pub fn as_os_str(&self) -> &str { + &self.inner + } + + #[must_use] + #[inline] + pub fn as_mut_os_str(&mut self) -> &mut str { + &mut self.inner + } + + #[must_use = "this returns the result of the operation, \ + without modifying the original"] + #[inline] + pub fn to_str(&self) -> Option<&str> { + Some(&self.inner) + } + + #[must_use = "this returns the result of the operation, \ + without modifying the original"] + + pub fn to_path_buf(&self) -> PathBuf { + PathBuf::from(self.inner.to_string()) + } + + #[must_use] + #[allow(deprecated)] + pub fn is_absolute(&self) -> bool { + self.has_root() + } + + #[must_use] + #[inline] + pub fn is_relative(&self) -> bool { + !self.is_absolute() + } + + fn prefix(&self) -> Option> { + self.components().prefix + } + + #[must_use] + #[inline] + pub fn has_root(&self) -> bool { + self.components().has_root() + } + + #[doc(alias = "dirname")] + #[must_use] + pub fn parent(&self) -> Option<&Path> { + let mut comps = self.components(); + let comp = comps.next_back(); + comp.and_then(|p| match p { + Component::Normal(_) | Component::CurDir | Component::ParentDir => { + Some(comps.as_path()) + } + _ => None, + }) + } + + /// Produces an iterator over `Path` and its ancestors. + /// + /// The iterator will yield the `Path` that is returned if the [`parent`] method is used zero + /// or more times. That means, the iterator will yield `&self`, `&self.parent().unwrap()`, + /// `&self.parent().unwrap().parent().unwrap()` and so on. If the [`parent`] method returns + /// [`None`], the iterator will do likewise. The iterator will always yield at least one value, + /// namely `&self`. + #[inline] + pub fn ancestors(&self) -> Ancestors<'_> { + Ancestors { next: Some(self) } + } + + /// Returns the final component of the `Path`, if there is one. + /// + /// If the path is a normal file, this is the file name. If it's the path of a directory, this + /// is the directory name. + /// + /// Returns [`None`] if the path terminates in `..`. + + #[doc(alias = "basename")] + #[must_use] + pub fn file_name(&self) -> Option<&str> { + self.components().next_back().and_then(|p| match p { + Component::Normal(p) => Some(p), + _ => None, + }) + } + + /// Returns a path that, when joined onto `base`, yields `self`. + /// + /// # Errors + /// + /// If `base` is not a prefix of `self` (i.e., [`starts_with`] + /// returns `false`), returns [`Err`]. + /// + /// [`starts_with`]: Path::starts_with + pub fn strip_prefix

(&self, base: P) -> Result<&Path, StripPrefixError> + where + P: AsRef, + { + self._strip_prefix(base.as_ref()) + } + + fn _strip_prefix(&self, base: &Path) -> Result<&Path, StripPrefixError> { + iter_after(self.components(), base.components()) + .map(|c| c.as_path()) + .ok_or(StripPrefixError(())) + } + + /// Determines whether `base` is a prefix of `self`. + /// + /// Only considers whole path components to match. + #[must_use] + pub fn starts_with>(&self, base: P) -> bool { + self._starts_with(base.as_ref()) + } + + fn _starts_with(&self, base: &Path) -> bool { + iter_after(self.components(), base.components()).is_some() + } + + /// Determines whether `child` is a suffix of `self`. + /// + /// Only considers whole path components to match. + + #[must_use] + pub fn ends_with>(&self, child: P) -> bool { + self._ends_with(child.as_ref()) + } + + fn _ends_with(&self, child: &Path) -> bool { + iter_after(self.components().rev(), child.components().rev()).is_some() + } + + /// Extracts the stem (non-extension) portion of [`self.file_name`]. + /// + /// [`self.file_name`]: Path::file_name + /// + /// The stem is: + /// + /// * [`None`], if there is no file name; + /// * The entire file name if there is no embedded `.`; + /// * The entire file name if the file name begins with `.` and has no other `.`s within; + /// * Otherwise, the portion of the file name before the final `.` + /// + + #[must_use] + pub fn file_stem(&self) -> Option<&str> { + self.file_name() + .map(rsplit_file_at_dot) + .and_then(|(before, after)| before.or(after)) + } + + /// Extracts the prefix of [`self.file_name`]. + /// + /// The prefix is: + /// + /// * [`None`], if there is no file name; + /// * The entire file name if there is no embedded `.`; + /// * The portion of the file name before the first non-beginning `.`; + /// * The entire file name if the file name begins with `.` and has no other `.`s within; + /// * The portion of the file name before the second `.` if the file name begins with `.` + /// + /// [`self.file_name`]: Path::file_name + /// + #[must_use] + pub fn file_prefix(&self) -> Option<&str> { + self.file_name() + .map(split_file_at_dot) + .map(|(before, _after)| before) + } + + /// Extracts the extension (without the leading dot) of [`self.file_name`], if possible. + /// + /// The extension is: + /// + /// * [`None`], if there is no file name; + /// * [`None`], if there is no embedded `.`; + /// * [`None`], if the file name begins with `.` and has no other `.`s within; + /// * Otherwise, the portion of the file name after the final `.` + /// + /// [`self.file_name`]: Path::file_name + /// + + #[must_use] + pub fn extension(&self) -> Option<&str> { + self.file_name() + .map(rsplit_file_at_dot) + .and_then(|(before, after)| before.and(after)) + } + + /// Creates an owned [`PathBuf`] with `path` adjoined to `self`. + /// + /// If `path` is absolute, it replaces the current path. + /// + /// See [`PathBuf::push`] for more details on what it means to adjoin a path. + /// + + #[must_use] + pub fn join>(&self, path: P) -> PathBuf { + self._join(path.as_ref()) + } + + fn _join(&self, path: &Path) -> PathBuf { + let mut buf = self.to_path_buf(); + buf.push(path); + buf + } + + /// Creates an owned [`PathBuf`] like `self` but with the given file name. + /// + /// See [`PathBuf::set_file_name`] for more details. + /// + + #[must_use] + pub fn with_file_name>(&self, file_name: S) -> PathBuf { + self._with_file_name(file_name.as_ref()) + } + + fn _with_file_name(&self, file_name: &str) -> PathBuf { + let mut buf = self.to_path_buf(); + buf.set_file_name(file_name); + buf + } + + /// Creates an owned [`PathBuf`] like `self` but with the given extension. + /// + /// See [`PathBuf::set_extension`] for more details. + /// + + pub fn with_extension>(&self, extension: S) -> PathBuf { + self._with_extension(extension.as_ref()) + } + + fn _with_extension(&self, extension: &str) -> PathBuf { + let self_len = self.as_os_str().len(); + let self_bytes = self.as_os_str(); + + let (new_capacity, slice_to_copy) = match self.extension() { + None => { + // Enough capacity for the extension and the dot + let capacity = self_len + extension.len() + 1; + let whole_path = self_bytes.chars(); + (capacity, whole_path) + } + Some(previous_extension) => { + let capacity = self_len + extension.len() - previous_extension.len(); + let path_till_dot = self_bytes[..self_len - previous_extension.len()].chars(); + (capacity, path_till_dot) + } + }; + + let mut new_path = PathBuf::with_capacity(new_capacity); + new_path.as_mut_vec().extend(slice_to_copy.map(|c| c as u8)); + new_path.set_extension(extension); + new_path + } + + /// Produces an iterator over the [`Component`]s of the path. + /// + /// When parsing the path, there is a small amount of normalization: + /// + /// * Repeated separators are ignored, so `a/b` and `a//b` both have + /// `a` and `b` as components. + /// + /// * Occurrences of `.` are normalized away, except if they are at the + /// beginning of the path. For example, `a/./b`, `a/b/`, `a/b/.` and + /// `a/b` all have `a` and `b` as components, but `./a/b` starts with + /// an additional [`CurDir`] component. + /// + /// * A trailing slash is normalized away, `/a/b` and `/a/b/` are equivalent. + /// + /// Note that no other normalization takes place; in particular, `a/c` + /// and `a/b/../c` are distinct, to account for the possibility that `b` + /// is a symbolic link (so its parent isn't `a`). + /// + /// [`CurDir`]: Component::CurDir + + pub fn components(&self) -> Components<'_> { + let prefix = parse_prefix(self.as_os_str()); + Components { + path: self.as_u8_slice(), + prefix, + has_physical_root: has_physical_root(self.as_u8_slice(), prefix) + || has_redox_scheme(self.as_u8_slice()), + front: State::Prefix, + back: State::Body, + } + } + + /// Produces an iterator over the path's components viewed as [`str`] + /// slices. + /// + /// For more information about the particulars of how the path is separated + /// into components, see [`components`]. + /// + /// [`components`]: Path::components + /// + + #[inline] + pub fn iter(&self) -> Iter<'_> { + Iter { + inner: self.components(), + } + } + + /// Returns an object that implements [`Display`] for safely printing paths + /// that may contain non-Unicode data. This may perform lossy conversion, + /// depending on the platform. If you would like an implementation which + /// escapes the path please use [`Debug`] instead. + /// + /// [`Display`]: fmt::Display + /// [`Debug`]: fmt::Debug + /// + + #[must_use = "this does not display the path, \ + it returns an object that can be displayed"] + #[inline] + pub fn display(&self) -> Display<'_> { + Display { inner: &self.inner } + } + + /// Converts a [`Box`](Box) into a [`PathBuf`] without copying or + /// allocating. + // #[stable(feature = "into_boxed_path", since = "1.20.0")] + #[must_use = "`self` will be dropped if the result is not used"] + pub fn into_path_buf(self: Box) -> PathBuf { + let rw = Box::into_raw(self) as *mut str; + let inner = unsafe { Box::from_raw(rw) }; + PathBuf { + inner: String::from(inner), + } + } +} + +impl AsRef for Path { + #[inline] + fn as_ref(&self) -> &str { + &self.inner + } +} + +impl fmt::Debug for Path { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&self.inner, formatter) + } +} + +/// Helper struct for safely printing paths with [`format!`] and `{}`. +/// +/// A [`Path`] might contain non-Unicode data. This `struct` implements the +/// [`Display`] trait in a way that mitigates that. It is created by the +/// [`display`](Path::display) method on [`Path`]. This may perform lossy +/// conversion, depending on the platform. If you would like an implementation +/// which escapes the path please use [`Debug`] instead. +/// +pub struct Display<'a> { + inner: &'a str, +} + +impl fmt::Debug for Display<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&self.inner, f) + } +} + +impl fmt::Display for Display<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&self.inner, f) + } +} + +impl PartialEq for Path { + #[inline] + fn eq(&self, other: &Path) -> bool { + self.components() == other.components() + } +} + +impl Hash for Path { + fn hash(&self, h: &mut H) { + let bytes = self.as_u8_slice(); + let (prefix_len, verbatim) = match parse_prefix(&self.inner) { + Some(prefix) => { + prefix.hash(h); + (prefix.len(), prefix.is_verbatim()) + } + None => (0, false), + }; + let bytes = &bytes[prefix_len..]; + + let mut component_start = 0; + let mut bytes_hashed = 0; + + for i in 0..bytes.len() { + let is_sep = if verbatim { + is_verbatim_sep(bytes[i]) + } else { + is_sep_byte(bytes[i]) + }; + if is_sep { + if i > component_start { + let to_hash = &bytes[component_start..i]; + h.write(to_hash); + bytes_hashed += to_hash.len(); + } + + // skip over separator and optionally a following CurDir item + // since components() would normalize these away. + component_start = i + 1; + + let tail = &bytes[component_start..]; + + if !verbatim { + component_start += match tail { + [b'.'] => 1, + [b'.', sep, ..] if is_sep_byte(*sep) => 1, + _ => 0, + }; + } + } + } + + if component_start < bytes.len() { + let to_hash = &bytes[component_start..]; + h.write(to_hash); + bytes_hashed += to_hash.len(); + } + + h.write_usize(bytes_hashed); + } +} + +impl Eq for Path {} + +impl PartialOrd for Path { + #[inline] + fn partial_cmp(&self, other: &Path) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Path { + #[inline] + fn cmp(&self, other: &Path) -> cmp::Ordering { + compare_components(self.components(), other.components()) + } +} + +impl AsRef for Path { + #[inline] + fn as_ref(&self) -> &Path { + self + } +} + +impl AsRef for str { + #[inline] + fn as_ref(&self) -> &Path { + Path::new(self) + } +} + +// #[stable(feature = "cow_os_str_as_ref_path", since = "1.8.0")] +impl AsRef for Cow<'_, str> { + #[inline] + fn as_ref(&self) -> &Path { + Path::new(self) + } +} + +impl AsRef for String { + #[inline] + fn as_ref(&self) -> &Path { + Path::new(self) + } +} + +impl AsRef for PathBuf { + #[inline] + fn as_ref(&self) -> &Path { + self + } +} + +// #[stable(feature = "path_into_iter", since = "1.6.0")] +impl<'a> IntoIterator for &'a PathBuf { + type Item = &'a str; + type IntoIter = Iter<'a>; + #[inline] + fn into_iter(self) -> Iter<'a> { + self.iter() + } +} + +// #[stable(feature = "path_into_iter", since = "1.6.0")] +impl<'a> IntoIterator for &'a Path { + type Item = &'a str; + type IntoIter = Iter<'a>; + #[inline] + fn into_iter(self) -> Iter<'a> { + self.iter() + } +} + +macro_rules! impl_cmp { + (<$($life:lifetime),*> $lhs:ty, $rhs: ty) => { + // #[stable(feature = "partialeq_path", since = "1.6.0")] + impl<$($life),*> PartialEq<$rhs> for $lhs { + #[inline] + fn eq(&self, other: &$rhs) -> bool { + ::eq(self, other) + } + } + + // #[stable(feature = "partialeq_path", since = "1.6.0")] + impl<$($life),*> PartialEq<$lhs> for $rhs { + #[inline] + fn eq(&self, other: &$lhs) -> bool { + ::eq(self, other) + } + } + + // #[stable(feature = "cmp_path", since = "1.8.0")] + impl<$($life),*> PartialOrd<$rhs> for $lhs { + #[inline] + fn partial_cmp(&self, other: &$rhs) -> Option { + ::partial_cmp(self, other) + } + } + + // #[stable(feature = "cmp_path", since = "1.8.0")] + impl<$($life),*> PartialOrd<$lhs> for $rhs { + #[inline] + fn partial_cmp(&self, other: &$lhs) -> Option { + ::partial_cmp(self, other) + } + } + }; +} + +impl_cmp!(<> PathBuf, Path); +impl_cmp!(<'a> PathBuf, &'a Path); +impl_cmp!(<'a> Cow<'a, Path>, Path); +impl_cmp!(<'a, 'b> Cow<'a, Path>, &'b Path); +impl_cmp!(<'a> Cow<'a, Path>, PathBuf); diff --git a/kernel/crates/path_base/src/tests.rs b/kernel/crates/path_base/src/tests.rs new file mode 100644 index 000000000..e3f28d32c --- /dev/null +++ b/kernel/crates/path_base/src/tests.rs @@ -0,0 +1,1955 @@ +use crate::clean_path::Clean; + +use super::*; + +// use alloc::collections::hash_map::SipHasher; +// use alloc::collections::BTreeSet; +use alloc::format; +use alloc::rc::Rc; +use alloc::sync::Arc; +use core::hash::{Hash, Hasher, SipHasher}; +use core::hint::black_box; + +#[allow(unknown_lints, unused_macro_rules)] +macro_rules! t ( + // ($path:expr, iter: $iter:expr) => ( + // { + // let path = Path::new($path); + + // // Forward iteration + // let comps = path.iter() + // .collect::>(); + // let exp: &[&str] = &$iter; + // let exps = exp.iter().map(|s| s.to_string()).collect::>(); + // assert!(comps == exps, "iter: Expected {:?}, found {:?}", + // exps, comps); + + // // Reverse iteration + // let comps = Path::new($path).iter().rev() + // .to_string() + // .collect::>(); + // let exps = exps.into_iter().rev().collect::>(); + // assert!(comps == exps, "iter().rev(): Expected {:?}, found {:?}", + // exps, comps); + // } + // ); + + ($path:expr, has_root: $has_root:expr, is_absolute: $is_absolute:expr) => ( + { + let path = Path::new($path); + + let act_root = path.has_root(); + assert!(act_root == $has_root, "has_root: Expected {:?}, found {:?}", + $has_root, act_root); + + let act_abs = path.is_absolute(); + assert!(act_abs == $is_absolute, "is_absolute: Expected {:?}, found {:?}", + $is_absolute, act_abs); + } + ); + + // ($path:expr, parent: $parent:expr, file_name: $file:expr) => ( + // { + // let path = Path::new($path); + + // let parent = path.parent(); + // let exp_parent: Option<&str> = $parent; + // assert!(parent == exp_parent, "parent: Expected {:?}, found {:?}", + // exp_parent, parent); + + // let file = path.file_name(); + // let exp_file: Option<&str> = $file; + // assert!(file == exp_file, "file_name: Expected {:?}, found {:?}", + // exp_file, file); + // } + // ); + + ($path:expr, file_stem: $file_stem:expr, extension: $extension:expr) => ( + { + let path = Path::new($path); + + let stem = path.file_stem(); + let exp_stem: Option<&str> = $file_stem; + assert!(stem == exp_stem, "file_stem: Expected {:?}, found {:?}", + exp_stem, stem); + + let ext = path.extension(); + let exp_ext: Option<&str> = $extension; + assert!(ext == exp_ext, "extension: Expected {:?}, found {:?}", + exp_ext, ext); + } + ); + + ($path:expr, file_prefix: $file_prefix:expr, extension: $extension:expr) => ( + { + let path = Path::new($path); + + let prefix = path.file_prefix(); + let exp_prefix: Option<&str> = $file_prefix; + assert!(prefix == exp_prefix, "file_prefix: Expected {:?}, found {:?}", + exp_prefix, prefix); + + let ext = path.extension(); + let exp_ext: Option<&str> = $extension; + assert!(ext == exp_ext, "extension: Expected {:?}, found {:?}", + exp_ext, ext); + } + ); + + ($path:expr, iter: $iter:expr, + has_root: $has_root:expr, is_absolute: $is_absolute:expr, + parent: $parent:expr, file_name: $file:expr, + file_stem: $file_stem:expr, extension: $extension:expr, + file_prefix: $file_prefix:expr) => ( + { + // t!($path, iter: $iter); + t!($path, has_root: $has_root, is_absolute: $is_absolute); + // t!($path, parent: $parent, file_name: $file); + t!($path, file_stem: $file_stem, extension: $extension); + t!($path, file_prefix: $file_prefix, extension: $extension); + } + ); +); + +#[test] +fn into() { + use alloc::borrow::Cow; + + let static_path = Path::new("/home/foo"); + let static_cow_path: Cow<'static, Path> = static_path.into(); + let pathbuf = PathBuf::from("/home/foo"); + + { + let path: &Path = &pathbuf; + let borrowed_cow_path: Cow<'_, Path> = path.into(); + + assert_eq!(static_cow_path, borrowed_cow_path); + } + + let owned_cow_path: Cow<'static, Path> = pathbuf.into(); + + assert_eq!(static_cow_path, owned_cow_path); +} + +#[test] +#[cfg(unix)] +pub fn test_decompositions_unix() { + t!("", + iter: [], + has_root: false, + is_absolute: false, + parent: None, + file_name: None, + file_stem: None, + extension: None, + file_prefix: None + ); + + t!("foo", + iter: ["foo"], + has_root: false, + is_absolute: false, + parent: Some(""), + file_name: Some("foo"), + file_stem: Some("foo"), + extension: None, + file_prefix: Some("foo") + ); + + t!("/", + iter: ["/"], + has_root: true, + is_absolute: true, + parent: None, + file_name: None, + file_stem: None, + extension: None, + file_prefix: None + ); + + t!("/foo", + iter: ["/", "foo"], + has_root: true, + is_absolute: true, + parent: Some("/"), + file_name: Some("foo"), + file_stem: Some("foo"), + extension: None, + file_prefix: Some("foo") + ); + + t!("foo/", + iter: ["foo"], + has_root: false, + is_absolute: false, + parent: Some(""), + file_name: Some("foo"), + file_stem: Some("foo"), + extension: None, + file_prefix: Some("foo") + ); + + t!("/foo/", + iter: ["/", "foo"], + has_root: true, + is_absolute: true, + parent: Some("/"), + file_name: Some("foo"), + file_stem: Some("foo"), + extension: None, + file_prefix: Some("foo") + ); + + t!("foo/bar", + iter: ["foo", "bar"], + has_root: false, + is_absolute: false, + parent: Some("foo"), + file_name: Some("bar"), + file_stem: Some("bar"), + extension: None, + file_prefix: Some("bar") + ); + + t!("/foo/bar", + iter: ["/", "foo", "bar"], + has_root: true, + is_absolute: true, + parent: Some("/foo"), + file_name: Some("bar"), + file_stem: Some("bar"), + extension: None, + file_prefix: Some("bar") + ); + + t!("///foo///", + iter: ["/", "foo"], + has_root: true, + is_absolute: true, + parent: Some("/"), + file_name: Some("foo"), + file_stem: Some("foo"), + extension: None, + file_prefix: Some("foo") + ); + + t!("///foo///bar", + iter: ["/", "foo", "bar"], + has_root: true, + is_absolute: true, + parent: Some("///foo"), + file_name: Some("bar"), + file_stem: Some("bar"), + extension: None, + file_prefix: Some("bar") + ); + + t!("./.", + iter: ["."], + has_root: false, + is_absolute: false, + parent: Some(""), + file_name: None, + file_stem: None, + extension: None, + file_prefix: None + ); + + t!("/..", + iter: ["/", ".."], + has_root: true, + is_absolute: true, + parent: Some("/"), + file_name: None, + file_stem: None, + extension: None, + file_prefix: None + ); + + t!("../", + iter: [".."], + has_root: false, + is_absolute: false, + parent: Some(""), + file_name: None, + file_stem: None, + extension: None, + file_prefix: None + ); + + t!("foo/.", + iter: ["foo"], + has_root: false, + is_absolute: false, + parent: Some(""), + file_name: Some("foo"), + file_stem: Some("foo"), + extension: None, + file_prefix: Some("foo") + ); + + t!("foo/..", + iter: ["foo", ".."], + has_root: false, + is_absolute: false, + parent: Some("foo"), + file_name: None, + file_stem: None, + extension: None, + file_prefix: None + ); + + t!("foo/./", + iter: ["foo"], + has_root: false, + is_absolute: false, + parent: Some(""), + file_name: Some("foo"), + file_stem: Some("foo"), + extension: None, + file_prefix: Some("foo") + ); + + t!("foo/./bar", + iter: ["foo", "bar"], + has_root: false, + is_absolute: false, + parent: Some("foo"), + file_name: Some("bar"), + file_stem: Some("bar"), + extension: None, + file_prefix: Some("bar") + ); + + t!("foo/../", + iter: ["foo", ".."], + has_root: false, + is_absolute: false, + parent: Some("foo"), + file_name: None, + file_stem: None, + extension: None, + file_prefix: None + ); + + t!("foo/../bar", + iter: ["foo", "..", "bar"], + has_root: false, + is_absolute: false, + parent: Some("foo/.."), + file_name: Some("bar"), + file_stem: Some("bar"), + extension: None, + file_prefix: Some("bar") + ); + + t!("./a", + iter: [".", "a"], + has_root: false, + is_absolute: false, + parent: Some("."), + file_name: Some("a"), + file_stem: Some("a"), + extension: None, + file_prefix: Some("a") + ); + + t!(".", + iter: ["."], + has_root: false, + is_absolute: false, + parent: Some(""), + file_name: None, + file_stem: None, + extension: None, + file_prefix: None + ); + + t!("./", + iter: ["."], + has_root: false, + is_absolute: false, + parent: Some(""), + file_name: None, + file_stem: None, + extension: None, + file_prefix: None + ); + + t!("a/b", + iter: ["a", "b"], + has_root: false, + is_absolute: false, + parent: Some("a"), + file_name: Some("b"), + file_stem: Some("b"), + extension: None, + file_prefix: Some("b") + ); + + t!("a//b", + iter: ["a", "b"], + has_root: false, + is_absolute: false, + parent: Some("a"), + file_name: Some("b"), + file_stem: Some("b"), + extension: None, + file_prefix: Some("b") + ); + + t!("a/./b", + iter: ["a", "b"], + has_root: false, + is_absolute: false, + parent: Some("a"), + file_name: Some("b"), + file_stem: Some("b"), + extension: None, + file_prefix: Some("b") + ); + + t!("a/b/c", + iter: ["a", "b", "c"], + has_root: false, + is_absolute: false, + parent: Some("a/b"), + file_name: Some("c"), + file_stem: Some("c"), + extension: None, + file_prefix: Some("c") + ); + + t!(".foo", + iter: [".foo"], + has_root: false, + is_absolute: false, + parent: Some(""), + file_name: Some(".foo"), + file_stem: Some(".foo"), + extension: None, + file_prefix: Some(".foo") + ); + + t!("a/.foo", + iter: ["a", ".foo"], + has_root: false, + is_absolute: false, + parent: Some("a"), + file_name: Some(".foo"), + file_stem: Some(".foo"), + extension: None, + file_prefix: Some(".foo") + ); + + t!("a/.rustfmt.toml", + iter: ["a", ".rustfmt.toml"], + has_root: false, + is_absolute: false, + parent: Some("a"), + file_name: Some(".rustfmt.toml"), + file_stem: Some(".rustfmt"), + extension: Some("toml"), + file_prefix: Some(".rustfmt") + ); + + t!("a/.x.y.z", + iter: ["a", ".x.y.z"], + has_root: false, + is_absolute: false, + parent: Some("a"), + file_name: Some(".x.y.z"), + file_stem: Some(".x.y"), + extension: Some("z"), + file_prefix: Some(".x") + ); +} + +#[test] +#[cfg(windows)] +pub fn test_decompositions_windows() { + t!("", + iter: [], + has_root: false, + is_absolute: false, + parent: None, + file_name: None, + file_stem: None, + extension: None, + file_prefix: None + ); + + t!("foo", + iter: ["foo"], + has_root: false, + is_absolute: false, + parent: Some(""), + file_name: Some("foo"), + file_stem: Some("foo"), + extension: None, + file_prefix: Some("foo") + ); + + t!("/", + iter: ["\\"], + has_root: true, + is_absolute: false, + parent: None, + file_name: None, + file_stem: None, + extension: None, + file_prefix: None + ); + + t!("\\", + iter: ["\\"], + has_root: true, + is_absolute: false, + parent: None, + file_name: None, + file_stem: None, + extension: None, + file_prefix: None + ); + + t!("c:", + iter: ["c:"], + has_root: false, + is_absolute: false, + parent: None, + file_name: None, + file_stem: None, + extension: None, + file_prefix: None + ); + + t!("c:\\", + iter: ["c:", "\\"], + has_root: true, + is_absolute: true, + parent: None, + file_name: None, + file_stem: None, + extension: None, + file_prefix: None + ); + + t!("c:/", + iter: ["c:", "\\"], + has_root: true, + is_absolute: true, + parent: None, + file_name: None, + file_stem: None, + extension: None, + file_prefix: None + ); + + t!("/foo", + iter: ["\\", "foo"], + has_root: true, + is_absolute: false, + parent: Some("/"), + file_name: Some("foo"), + file_stem: Some("foo"), + extension: None, + file_prefix: Some("foo") + ); + + t!("foo/", + iter: ["foo"], + has_root: false, + is_absolute: false, + parent: Some(""), + file_name: Some("foo"), + file_stem: Some("foo"), + extension: None, + file_prefix: Some("foo") + ); + + t!("/foo/", + iter: ["\\", "foo"], + has_root: true, + is_absolute: false, + parent: Some("/"), + file_name: Some("foo"), + file_stem: Some("foo"), + extension: None, + file_prefix: Some("foo") + ); + + t!("foo/bar", + iter: ["foo", "bar"], + has_root: false, + is_absolute: false, + parent: Some("foo"), + file_name: Some("bar"), + file_stem: Some("bar"), + extension: None, + file_prefix: Some("bar") + ); + + t!("/foo/bar", + iter: ["\\", "foo", "bar"], + has_root: true, + is_absolute: false, + parent: Some("/foo"), + file_name: Some("bar"), + file_stem: Some("bar"), + extension: None, + file_prefix: Some("bar") + ); + + t!("///foo///", + iter: ["\\", "foo"], + has_root: true, + is_absolute: false, + parent: Some("/"), + file_name: Some("foo"), + file_stem: Some("foo"), + extension: None, + file_prefix: Some("foo") + ); + + t!("///foo///bar", + iter: ["\\", "foo", "bar"], + has_root: true, + is_absolute: false, + parent: Some("///foo"), + file_name: Some("bar"), + file_stem: Some("bar"), + extension: None, + file_prefix: Some("bar") + ); + + t!("./.", + iter: ["."], + has_root: false, + is_absolute: false, + parent: Some(""), + file_name: None, + file_stem: None, + extension: None, + file_prefix: None + ); + + t!("/..", + iter: ["\\", ".."], + has_root: true, + is_absolute: false, + parent: Some("/"), + file_name: None, + file_stem: None, + extension: None, + file_prefix: None + ); + + t!("../", + iter: [".."], + has_root: false, + is_absolute: false, + parent: Some(""), + file_name: None, + file_stem: None, + extension: None, + file_prefix: None + ); + + t!("foo/.", + iter: ["foo"], + has_root: false, + is_absolute: false, + parent: Some(""), + file_name: Some("foo"), + file_stem: Some("foo"), + extension: None, + file_prefix: Some("foo") + ); + + t!("foo/..", + iter: ["foo", ".."], + has_root: false, + is_absolute: false, + parent: Some("foo"), + file_name: None, + file_stem: None, + extension: None, + file_prefix: None + ); + + t!("foo/./", + iter: ["foo"], + has_root: false, + is_absolute: false, + parent: Some(""), + file_name: Some("foo"), + file_stem: Some("foo"), + extension: None, + file_prefix: Some("foo") + ); + + t!("foo/./bar", + iter: ["foo", "bar"], + has_root: false, + is_absolute: false, + parent: Some("foo"), + file_name: Some("bar"), + file_stem: Some("bar"), + extension: None, + file_prefix: Some("bar") + ); + + t!("foo/../", + iter: ["foo", ".."], + has_root: false, + is_absolute: false, + parent: Some("foo"), + file_name: None, + file_stem: None, + extension: None, + file_prefix: None + ); + + t!("foo/../bar", + iter: ["foo", "..", "bar"], + has_root: false, + is_absolute: false, + parent: Some("foo/.."), + file_name: Some("bar"), + file_stem: Some("bar"), + extension: None, + file_prefix: Some("bar") + ); + + t!("./a", + iter: [".", "a"], + has_root: false, + is_absolute: false, + parent: Some("."), + file_name: Some("a"), + file_stem: Some("a"), + extension: None, + file_prefix: Some("a") + ); + + t!(".", + iter: ["."], + has_root: false, + is_absolute: false, + parent: Some(""), + file_name: None, + file_stem: None, + extension: None, + file_prefix: None + ); + + t!("./", + iter: ["."], + has_root: false, + is_absolute: false, + parent: Some(""), + file_name: None, + file_stem: None, + extension: None, + file_prefix: None + ); + + t!("a/b", + iter: ["a", "b"], + has_root: false, + is_absolute: false, + parent: Some("a"), + file_name: Some("b"), + file_stem: Some("b"), + extension: None, + file_prefix: Some("b") + ); + + t!("a//b", + iter: ["a", "b"], + has_root: false, + is_absolute: false, + parent: Some("a"), + file_name: Some("b"), + file_stem: Some("b"), + extension: None, + file_prefix: Some("b") + ); + + t!("a/./b", + iter: ["a", "b"], + has_root: false, + is_absolute: false, + parent: Some("a"), + file_name: Some("b"), + file_stem: Some("b"), + extension: None, + file_prefix: Some("b") + ); + + t!("a/b/c", + iter: ["a", "b", "c"], + has_root: false, + is_absolute: false, + parent: Some("a/b"), + file_name: Some("c"), + file_stem: Some("c"), + extension: None, + file_prefix: Some("c") + ); + + t!("a\\b\\c", + iter: ["a", "b", "c"], + has_root: false, + is_absolute: false, + parent: Some("a\\b"), + file_name: Some("c"), + file_stem: Some("c"), + extension: None, + file_prefix: Some("c") + ); + + t!("\\a", + iter: ["\\", "a"], + has_root: true, + is_absolute: false, + parent: Some("\\"), + file_name: Some("a"), + file_stem: Some("a"), + extension: None, + file_prefix: Some("a") + ); + + t!("c:\\foo.txt", + iter: ["c:", "\\", "foo.txt"], + has_root: true, + is_absolute: true, + parent: Some("c:\\"), + file_name: Some("foo.txt"), + file_stem: Some("foo"), + extension: Some("txt"), + file_prefix: Some("foo") + ); + + t!("\\\\server\\share\\foo.txt", + iter: ["\\\\server\\share", "\\", "foo.txt"], + has_root: true, + is_absolute: true, + parent: Some("\\\\server\\share\\"), + file_name: Some("foo.txt"), + file_stem: Some("foo"), + extension: Some("txt"), + file_prefix: Some("foo") + ); + + t!("\\\\server\\share", + iter: ["\\\\server\\share", "\\"], + has_root: true, + is_absolute: true, + parent: None, + file_name: None, + file_stem: None, + extension: None, + file_prefix: None + ); + + t!("\\\\server", + iter: ["\\", "server"], + has_root: true, + is_absolute: false, + parent: Some("\\"), + file_name: Some("server"), + file_stem: Some("server"), + extension: None, + file_prefix: Some("server") + ); + + t!("\\\\?\\bar\\foo.txt", + iter: ["\\\\?\\bar", "\\", "foo.txt"], + has_root: true, + is_absolute: true, + parent: Some("\\\\?\\bar\\"), + file_name: Some("foo.txt"), + file_stem: Some("foo"), + extension: Some("txt"), + file_prefix: Some("foo") + ); + + t!("\\\\?\\bar", + iter: ["\\\\?\\bar"], + has_root: true, + is_absolute: true, + parent: None, + file_name: None, + file_stem: None, + extension: None, + file_prefix: None + ); + + t!("\\\\?\\", + iter: ["\\\\?\\"], + has_root: true, + is_absolute: true, + parent: None, + file_name: None, + file_stem: None, + extension: None, + file_prefix: None + ); + + t!("\\\\?\\UNC\\server\\share\\foo.txt", + iter: ["\\\\?\\UNC\\server\\share", "\\", "foo.txt"], + has_root: true, + is_absolute: true, + parent: Some("\\\\?\\UNC\\server\\share\\"), + file_name: Some("foo.txt"), + file_stem: Some("foo"), + extension: Some("txt"), + file_prefix: Some("foo") + ); + + t!("\\\\?\\UNC\\server", + iter: ["\\\\?\\UNC\\server"], + has_root: true, + is_absolute: true, + parent: None, + file_name: None, + file_stem: None, + extension: None, + file_prefix: None + ); + + t!("\\\\?\\UNC\\", + iter: ["\\\\?\\UNC\\"], + has_root: true, + is_absolute: true, + parent: None, + file_name: None, + file_stem: None, + extension: None, + file_prefix: None + ); + + t!("\\\\?\\C:\\foo.txt", + iter: ["\\\\?\\C:", "\\", "foo.txt"], + has_root: true, + is_absolute: true, + parent: Some("\\\\?\\C:\\"), + file_name: Some("foo.txt"), + file_stem: Some("foo"), + extension: Some("txt"), + file_prefix: Some("foo") + ); + + t!("\\\\?\\C:\\", + iter: ["\\\\?\\C:", "\\"], + has_root: true, + is_absolute: true, + parent: None, + file_name: None, + file_stem: None, + extension: None, + file_prefix: None + ); + + t!("\\\\?\\C:", + iter: ["\\\\?\\C:"], + has_root: true, + is_absolute: true, + parent: None, + file_name: None, + file_stem: None, + extension: None, + file_prefix: None + ); + + t!("\\\\?\\foo/bar", + iter: ["\\\\?\\foo/bar"], + has_root: true, + is_absolute: true, + parent: None, + file_name: None, + file_stem: None, + extension: None, + file_prefix: None + ); + + t!("\\\\?\\C:/foo/bar", + iter: ["\\\\?\\C:", "\\", "foo/bar"], + has_root: true, + is_absolute: true, + parent: Some("\\\\?\\C:/"), + file_name: Some("foo/bar"), + file_stem: Some("foo/bar"), + extension: None, + file_prefix: Some("foo/bar") + ); + + t!("\\\\.\\foo\\bar", + iter: ["\\\\.\\foo", "\\", "bar"], + has_root: true, + is_absolute: true, + parent: Some("\\\\.\\foo\\"), + file_name: Some("bar"), + file_stem: Some("bar"), + extension: None, + file_prefix: Some("bar") + ); + + t!("\\\\.\\foo", + iter: ["\\\\.\\foo", "\\"], + has_root: true, + is_absolute: true, + parent: None, + file_name: None, + file_stem: None, + extension: None, + file_prefix: None + ); + + t!("\\\\.\\foo/bar", + iter: ["\\\\.\\foo", "\\", "bar"], + has_root: true, + is_absolute: true, + parent: Some("\\\\.\\foo/"), + file_name: Some("bar"), + file_stem: Some("bar"), + extension: None, + file_prefix: Some("bar") + ); + + t!("\\\\.\\foo\\bar/baz", + iter: ["\\\\.\\foo", "\\", "bar", "baz"], + has_root: true, + is_absolute: true, + parent: Some("\\\\.\\foo\\bar"), + file_name: Some("baz"), + file_stem: Some("baz"), + extension: None, + file_prefix: Some("baz") + ); + + t!("\\\\.\\", + iter: ["\\\\.\\", "\\"], + has_root: true, + is_absolute: true, + parent: None, + file_name: None, + file_stem: None, + extension: None, + file_prefix: None + ); + + t!("\\\\?\\a\\b\\", + iter: ["\\\\?\\a", "\\", "b"], + has_root: true, + is_absolute: true, + parent: Some("\\\\?\\a\\"), + file_name: Some("b"), + file_stem: Some("b"), + extension: None, + file_prefix: Some("b") + ); + + t!("\\\\?\\C:\\foo.txt.zip", + iter: ["\\\\?\\C:", "\\", "foo.txt.zip"], + has_root: true, + is_absolute: true, + parent: Some("\\\\?\\C:\\"), + file_name: Some("foo.txt.zip"), + file_stem: Some("foo.txt"), + extension: Some("zip"), + file_prefix: Some("foo") + ); + + t!("\\\\?\\C:\\.foo.txt.zip", + iter: ["\\\\?\\C:", "\\", ".foo.txt.zip"], + has_root: true, + is_absolute: true, + parent: Some("\\\\?\\C:\\"), + file_name: Some(".foo.txt.zip"), + file_stem: Some(".foo.txt"), + extension: Some("zip"), + file_prefix: Some(".foo") + ); + + t!("\\\\?\\C:\\.foo", + iter: ["\\\\?\\C:", "\\", ".foo"], + has_root: true, + is_absolute: true, + parent: Some("\\\\?\\C:\\"), + file_name: Some(".foo"), + file_stem: Some(".foo"), + extension: None, + file_prefix: Some(".foo") + ); + + t!("a/.x.y.z", + iter: ["a", ".x.y.z"], + has_root: false, + is_absolute: false, + parent: Some("a"), + file_name: Some(".x.y.z"), + file_stem: Some(".x.y"), + extension: Some("z"), + file_prefix: Some(".x") + ); +} + +#[test] +pub fn test_stem_ext() { + t!("foo", + file_stem: Some("foo"), + extension: None + ); + + t!("foo.", + file_stem: Some("foo"), + extension: Some("") + ); + + t!(".foo", + file_stem: Some(".foo"), + extension: None + ); + + t!("foo.txt", + file_stem: Some("foo"), + extension: Some("txt") + ); + + t!("foo.bar.txt", + file_stem: Some("foo.bar"), + extension: Some("txt") + ); + + t!("foo.bar.", + file_stem: Some("foo.bar"), + extension: Some("") + ); + + t!(".", file_stem: None, extension: None); + + t!("..", file_stem: None, extension: None); + + t!(".x.y.z", file_stem: Some(".x.y"), extension: Some("z")); + + t!("..x.y.z", file_stem: Some("..x.y"), extension: Some("z")); + + t!("", file_stem: None, extension: None); +} + +#[test] +pub fn test_prefix_ext() { + t!("foo", + file_prefix: Some("foo"), + extension: None + ); + + t!("foo.", + file_prefix: Some("foo"), + extension: Some("") + ); + + t!(".foo", + file_prefix: Some(".foo"), + extension: None + ); + + t!("foo.txt", + file_prefix: Some("foo"), + extension: Some("txt") + ); + + t!("foo.bar.txt", + file_prefix: Some("foo"), + extension: Some("txt") + ); + + t!("foo.bar.", + file_prefix: Some("foo"), + extension: Some("") + ); + + t!(".", file_prefix: None, extension: None); + + t!("..", file_prefix: None, extension: None); + + t!(".x.y.z", file_prefix: Some(".x"), extension: Some("z")); + + t!("..x.y.z", file_prefix: Some("."), extension: Some("z")); + + t!("", file_prefix: None, extension: None); +} + +#[test] +pub fn test_push() { + macro_rules! tp ( + ($path:expr, $push:expr, $expected:expr) => ({ + let mut actual = PathBuf::from($path); + actual.push($push); + assert!(actual.to_str() == Some($expected), + "pushing {:?} onto {:?}: Expected {:?}, got {:?}", + $push, $path, $expected, actual.to_str().unwrap()); + }); + ); + + if cfg!(unix) || cfg!(all(target_env = "sgx", target_vendor = "fortanix")) { + tp!("", "foo", "foo"); + tp!("foo", "bar", "foo/bar"); + tp!("foo/", "bar", "foo/bar"); + tp!("foo//", "bar", "foo//bar"); + tp!("foo/.", "bar", "foo/./bar"); + tp!("foo./.", "bar", "foo././bar"); + tp!("foo", "", "foo/"); + tp!("foo", ".", "foo/."); + tp!("foo", "..", "foo/.."); + tp!("foo", "/", "/"); + tp!("/foo/bar", "/", "/"); + tp!("/foo/bar", "/baz", "/baz"); + tp!("/foo/bar", "./baz", "/foo/bar/./baz"); + } else { + tp!("", "foo", "foo"); + tp!("foo", "bar", r"foo\bar"); + tp!("foo/", "bar", r"foo/bar"); + tp!(r"foo\", "bar", r"foo\bar"); + tp!("foo//", "bar", r"foo//bar"); + tp!(r"foo\\", "bar", r"foo\\bar"); + tp!("foo/.", "bar", r"foo/.\bar"); + tp!("foo./.", "bar", r"foo./.\bar"); + tp!(r"foo\.", "bar", r"foo\.\bar"); + tp!(r"foo.\.", "bar", r"foo.\.\bar"); + tp!("foo", "", "foo\\"); + tp!("foo", ".", r"foo\."); + tp!("foo", "..", r"foo\.."); + tp!("foo", "/", "/"); + tp!("foo", r"\", r"\"); + tp!("/foo/bar", "/", "/"); + tp!(r"\foo\bar", r"\", r"\"); + tp!("/foo/bar", "/baz", "/baz"); + tp!("/foo/bar", r"\baz", r"\baz"); + tp!("/foo/bar", "./baz", r"/foo/bar\./baz"); + tp!("/foo/bar", r".\baz", r"/foo/bar\.\baz"); + + tp!("c:\\", "windows", "c:\\windows"); + tp!("c:", "windows", "c:windows"); + + tp!("a\\b\\c", "d", "a\\b\\c\\d"); + tp!("\\a\\b\\c", "d", "\\a\\b\\c\\d"); + tp!("a\\b", "c\\d", "a\\b\\c\\d"); + tp!("a\\b", "\\c\\d", "\\c\\d"); + tp!("a\\b", ".", "a\\b\\."); + tp!("a\\b", "..\\c", "a\\b\\..\\c"); + tp!("a\\b", "C:a.txt", "C:a.txt"); + tp!("a\\b", "C:\\a.txt", "C:\\a.txt"); + tp!("C:\\a", "C:\\b.txt", "C:\\b.txt"); + tp!("C:\\a\\b\\c", "C:d", "C:d"); + tp!("C:a\\b\\c", "C:d", "C:d"); + tp!("C:", r"a\b\c", r"C:a\b\c"); + tp!("C:", r"..\a", r"C:..\a"); + tp!( + "\\\\server\\share\\foo", + "bar", + "\\\\server\\share\\foo\\bar" + ); + tp!("\\\\server\\share\\foo", "C:baz", "C:baz"); + tp!("\\\\?\\C:\\a\\b", "C:c\\d", "C:c\\d"); + tp!("\\\\?\\C:a\\b", "C:c\\d", "C:c\\d"); + tp!("\\\\?\\C:\\a\\b", "C:\\c\\d", "C:\\c\\d"); + tp!("\\\\?\\foo\\bar", "baz", "\\\\?\\foo\\bar\\baz"); + tp!( + "\\\\?\\UNC\\server\\share\\foo", + "bar", + "\\\\?\\UNC\\server\\share\\foo\\bar" + ); + tp!("\\\\?\\UNC\\server\\share", "C:\\a", "C:\\a"); + tp!("\\\\?\\UNC\\server\\share", "C:a", "C:a"); + + // Note: modified from old path API + tp!("\\\\?\\UNC\\server", "foo", "\\\\?\\UNC\\server\\foo"); + + tp!( + "C:\\a", + "\\\\?\\UNC\\server\\share", + "\\\\?\\UNC\\server\\share" + ); + tp!("\\\\.\\foo\\bar", "baz", "\\\\.\\foo\\bar\\baz"); + tp!("\\\\.\\foo\\bar", "C:a", "C:a"); + // again, not sure about the following, but I'm assuming \\.\ should be verbatim + tp!("\\\\.\\foo", "..\\bar", "\\\\.\\foo\\..\\bar"); + + tp!("\\\\?\\C:", "foo", "\\\\?\\C:\\foo"); // this is a weird one + + tp!(r"\\?\C:\bar", "../foo", r"\\?\C:\foo"); + tp!(r"\\?\C:\bar", "../../foo", r"\\?\C:\foo"); + tp!(r"\\?\C:\", "../foo", r"\\?\C:\foo"); + tp!(r"\\?\C:", r"D:\foo/./", r"D:\foo/./"); + tp!(r"\\?\C:", r"\\?\D:\foo\.\", r"\\?\D:\foo\.\"); + tp!(r"\\?\A:\x\y", "/foo", r"\\?\A:\foo"); + tp!(r"\\?\A:", r"..\foo\.", r"\\?\A:\foo"); + tp!(r"\\?\A:\x\y", r".\foo\.", r"\\?\A:\x\y\foo"); + tp!(r"\\?\A:\x\y", r"", r"\\?\A:\x\y\"); + } +} + +#[test] +pub fn test_pop() { + macro_rules! tp ( + ($path:expr, $expected:expr, $output:expr) => ({ + let mut actual = PathBuf::from($path); + let output = actual.pop(); + assert!(actual.to_str() == Some($expected) && output == $output, + "popping from {:?}: Expected {:?}/{:?}, got {:?}/{:?}", + $path, $expected, $output, + actual.to_str().unwrap(), output); + }); + ); + + tp!("", "", false); + tp!("/", "/", false); + tp!("foo", "", true); + tp!(".", "", true); + tp!("/foo", "/", true); + tp!("/foo/bar", "/foo", true); + tp!("foo/bar", "foo", true); + tp!("foo/.", "", true); + tp!("foo//bar", "foo", true); + + if cfg!(windows) { + tp!("a\\b\\c", "a\\b", true); + tp!("\\a", "\\", true); + tp!("\\", "\\", false); + + tp!("C:\\a\\b", "C:\\a", true); + tp!("C:\\a", "C:\\", true); + tp!("C:\\", "C:\\", false); + tp!("C:a\\b", "C:a", true); + tp!("C:a", "C:", true); + tp!("C:", "C:", false); + tp!("\\\\server\\share\\a\\b", "\\\\server\\share\\a", true); + tp!("\\\\server\\share\\a", "\\\\server\\share\\", true); + tp!("\\\\server\\share", "\\\\server\\share", false); + tp!("\\\\?\\a\\b\\c", "\\\\?\\a\\b", true); + tp!("\\\\?\\a\\b", "\\\\?\\a\\", true); + tp!("\\\\?\\a", "\\\\?\\a", false); + tp!("\\\\?\\C:\\a\\b", "\\\\?\\C:\\a", true); + tp!("\\\\?\\C:\\a", "\\\\?\\C:\\", true); + tp!("\\\\?\\C:\\", "\\\\?\\C:\\", false); + tp!( + "\\\\?\\UNC\\server\\share\\a\\b", + "\\\\?\\UNC\\server\\share\\a", + true + ); + tp!( + "\\\\?\\UNC\\server\\share\\a", + "\\\\?\\UNC\\server\\share\\", + true + ); + tp!( + "\\\\?\\UNC\\server\\share", + "\\\\?\\UNC\\server\\share", + false + ); + tp!("\\\\.\\a\\b\\c", "\\\\.\\a\\b", true); + tp!("\\\\.\\a\\b", "\\\\.\\a\\", true); + tp!("\\\\.\\a", "\\\\.\\a", false); + + tp!("\\\\?\\a\\b\\", "\\\\?\\a\\", true); + } +} + +#[test] +pub fn test_set_file_name() { + macro_rules! tfn ( + ($path:expr, $file:expr, $expected:expr) => ({ + let mut p = PathBuf::from($path); + p.set_file_name($file); + assert!(p.to_str() == Some($expected), + "setting file name of {:?} to {:?}: Expected {:?}, got {:?}", + $path, $file, $expected, + p.to_str().unwrap()); + }); + ); + + tfn!("foo", "foo", "foo"); + tfn!("foo", "bar", "bar"); + tfn!("foo", "", ""); + tfn!("", "foo", "foo"); + if cfg!(unix) || cfg!(all(target_env = "sgx", target_vendor = "fortanix")) { + tfn!(".", "foo", "./foo"); + tfn!("foo/", "bar", "bar"); + tfn!("foo/.", "bar", "bar"); + tfn!("..", "foo", "../foo"); + tfn!("foo/..", "bar", "foo/../bar"); + tfn!("/", "foo", "/foo"); + } else { + tfn!(".", "foo", r".\foo"); + tfn!(r"foo\", "bar", r"bar"); + tfn!(r"foo\.", "bar", r"bar"); + tfn!("..", "foo", r"..\foo"); + tfn!(r"foo\..", "bar", r"foo\..\bar"); + tfn!(r"\", "foo", r"\foo"); + } +} + +#[test] +pub fn test_set_extension() { + macro_rules! tfe ( + ($path:expr, $ext:expr, $expected:expr, $output:expr) => ({ + let mut p = PathBuf::from($path); + let output = p.set_extension($ext); + assert!(p.to_str() == Some($expected) && output == $output, + "setting extension of {:?} to {:?}: Expected {:?}/{:?}, got {:?}/{:?}", + $path, $ext, $expected, $output, + p.to_str().unwrap(), output); + }); + ); + + tfe!("foo", "txt", "foo.txt", true); + tfe!("foo.bar", "txt", "foo.txt", true); + tfe!("foo.bar.baz", "txt", "foo.bar.txt", true); + tfe!(".test", "txt", ".test.txt", true); + tfe!("foo.txt", "", "foo", true); + tfe!("foo", "", "foo", true); + tfe!("", "foo", "", false); + tfe!(".", "foo", ".", false); + tfe!("foo/", "bar", "foo.bar", true); + tfe!("foo/.", "bar", "foo.bar", true); + tfe!("..", "foo", "..", false); + tfe!("foo/..", "bar", "foo/..", false); + tfe!("/", "foo", "/", false); +} + +#[test] +pub fn test_with_extension() { + macro_rules! twe ( + ($input:expr, $extension:expr, $expected:expr) => ({ + let input = Path::new($input); + let output = input.with_extension($extension); + + assert!( + output.to_str() == Some($expected), + "calling Path::new({:?}).with_extension({:?}): Expected {:?}, got {:?}", + $input, $extension, $expected, output, + ); + }); + ); + + twe!("foo", "txt", "foo.txt"); + twe!("foo.bar", "txt", "foo.txt"); + twe!("foo.bar.baz", "txt", "foo.bar.txt"); + twe!(".test", "txt", ".test.txt"); + twe!("foo.txt", "", "foo"); + twe!("foo", "", "foo"); + twe!("", "foo", ""); + twe!(".", "foo", "."); + twe!("foo/", "bar", "foo.bar"); + twe!("foo/.", "bar", "foo.bar"); + twe!("..", "foo", ".."); + twe!("foo/..", "bar", "foo/.."); + twe!("/", "foo", "/"); + + // New extension is smaller than file name + twe!("aaa_aaa_aaa", "bbb_bbb", "aaa_aaa_aaa.bbb_bbb"); + // New extension is greater than file name + twe!("bbb_bbb", "aaa_aaa_aaa", "bbb_bbb.aaa_aaa_aaa"); + + // New extension is smaller than previous extension + twe!("ccc.aaa_aaa_aaa", "bbb_bbb", "ccc.bbb_bbb"); + // New extension is greater than previous extension + twe!("ccc.bbb_bbb", "aaa_aaa_aaa", "ccc.aaa_aaa_aaa"); +} + +#[test] +fn test_eq_receivers() { + use alloc::borrow::Cow; + + let borrowed: &Path = Path::new("foo/bar"); + let mut owned: PathBuf = PathBuf::new(); + owned.push("foo"); + owned.push("bar"); + let borrowed_cow: Cow<'_, Path> = borrowed.into(); + let owned_cow: Cow<'_, Path> = owned.clone().into(); + + macro_rules! t { + ($($current:expr),+) => { + $( + assert_eq!($current, borrowed); + assert_eq!($current, owned); + assert_eq!($current, borrowed_cow); + assert_eq!($current, owned_cow); + )+ + } + } + + t!(borrowed, owned, borrowed_cow, owned_cow); +} + +#[test] +pub fn test_compare() { + // use alloc::collections::hash_map::SipHasher; + use core::hash::{Hash, Hasher, SipHasher}; + + fn hash(t: T) -> u64 { + let mut s = SipHasher::new(); + t.hash(&mut s); + s.finish() + } + + // macro_rules! tc ( + // ($path1:expr, $path2:expr, eq: $eq:expr, + // starts_with: $starts_with:expr, ends_with: $ends_with:expr, + // relative_from: $relative_from:expr) => ({ + // let path1 = Path::new($path1); + // let path2 = Path::new($path2); + + // let eq = path1 == path2; + // assert!(eq == $eq, "{:?} == {:?}, expected {:?}, got {:?}", + // $path1, $path2, $eq, eq); + // assert!($eq == (hash(path1) == hash(path2)), + // "{:?} == {:?}, expected {:?}, got {} and {}", + // $path1, $path2, $eq, hash(path1), hash(path2)); + + // let starts_with = path1.starts_with(path2); + // assert!(starts_with == $starts_with, + // "{:?}.starts_with({:?}), expected {:?}, got {:?}", $path1, $path2, + // $starts_with, starts_with); + + // let ends_with = path1.ends_with(path2); + // assert!(ends_with == $ends_with, + // "{:?}.ends_with({:?}), expected {:?}, got {:?}", $path1, $path2, + // $ends_with, ends_with); + + // let relative_from = path1.strip_prefix(path2) + + // .ok(); + // let exp: Option<&str> = $relative_from; + // assert!(relative_from == exp, + // "{:?}.strip_prefix({:?}), expected {:?}, got {:?}", + // $path1, $path2, exp, relative_from); + // }); + // ); + + // tc!("", "", + // eq: true, + // starts_with: true, + // ends_with: true, + // relative_from: Some("") + // ); + + // tc!("foo", "", + // eq: false, + // starts_with: true, + // ends_with: true, + // relative_from: Some("foo") + // ); + + // tc!("", "foo", + // eq: false, + // starts_with: false, + // ends_with: false, + // relative_from: None + // ); + + // tc!("foo", "foo", + // eq: true, + // starts_with: true, + // ends_with: true, + // relative_from: Some("") + // ); + + // tc!("foo/", "foo", + // eq: true, + // starts_with: true, + // ends_with: true, + // relative_from: Some("") + // ); + + // tc!("foo/.", "foo", + // eq: true, + // starts_with: true, + // ends_with: true, + // relative_from: Some("") + // ); + + // tc!("foo/./bar", "foo/bar", + // eq: true, + // starts_with: true, + // ends_with: true, + // relative_from: Some("") + // ); + + // tc!("foo/bar", "foo", + // eq: false, + // starts_with: true, + // ends_with: false, + // relative_from: Some("bar") + // ); + + // tc!("foo/bar/baz", "foo/bar", + // eq: false, + // starts_with: true, + // ends_with: false, + // relative_from: Some("baz") + // ); + + // tc!("foo/bar", "foo/bar/baz", + // eq: false, + // starts_with: false, + // ends_with: false, + // relative_from: None + // ); + + // tc!("./foo/bar/", ".", + // eq: false, + // starts_with: true, + // ends_with: false, + // relative_from: Some("foo/bar") + // ); + + // if cfg!(windows) { + // tc!(r"C:\src\rust\cargo-test\test\Cargo.toml", + // r"c:\src\rust\cargo-test\test", + // eq: false, + // starts_with: true, + // ends_with: false, + // relative_from: Some("Cargo.toml") + // ); + + // tc!(r"c:\foo", r"C:\foo", + // eq: true, + // starts_with: true, + // ends_with: true, + // relative_from: Some("") + // ); + + // tc!(r"C:\foo\.\bar.txt", r"C:\foo\bar.txt", + // eq: true, + // starts_with: true, + // ends_with: true, + // relative_from: Some("") + // ); + + // tc!(r"C:\foo\.", r"C:\foo", + // eq: true, + // starts_with: true, + // ends_with: true, + // relative_from: Some("") + // ); + + // tc!(r"\\?\C:\foo\.\bar.txt", r"\\?\C:\foo\bar.txt", + // eq: false, + // starts_with: false, + // ends_with: false, + // relative_from: None + // ); + // } +} + +#[test] +fn test_components_debug() { + let path = Path::new("/tmp"); + + let mut components = path.components(); + + let expected = "Components([RootDir, Normal(\"tmp\")])"; + let actual = format!("{components:?}"); + assert_eq!(expected, actual); + + let _ = components.next().unwrap(); + let expected = "Components([Normal(\"tmp\")])"; + let actual = format!("{components:?}"); + assert_eq!(expected, actual); + + let _ = components.next().unwrap(); + let expected = "Components([])"; + let actual = format!("{components:?}"); + assert_eq!(expected, actual); +} + +#[cfg(unix)] +#[test] +fn test_iter_debug() { + let path = Path::new("/tmp"); + + let mut iter = path.iter(); + + let expected = "Iter([\"/\", \"tmp\"])"; + let actual = format!("{iter:?}"); + assert_eq!(expected, actual); + + let _ = iter.next().unwrap(); + let expected = "Iter([\"tmp\"])"; + let actual = format!("{iter:?}"); + assert_eq!(expected, actual); + + let _ = iter.next().unwrap(); + let expected = "Iter([])"; + let actual = format!("{iter:?}"); + assert_eq!(expected, actual); +} + +#[test] +fn into_boxed() { + let orig: &str = "some/sort/of/path"; + let path = Path::new(orig); + let boxed: Box = Box::from(path); + let path_buf = path.to_owned().into_boxed_path().into_path_buf(); + assert_eq!(path, &*boxed); + assert_eq!(&*boxed, &*path_buf); + assert_eq!(&*path_buf, path); +} + +#[test] +fn test_clone_into() { + let mut path_buf = PathBuf::from("supercalifragilisticexpialidocious"); + let path = Path::new("short"); + path.clone_into(&mut path_buf); + assert_eq!(path, path_buf); + assert!(path_buf.into_os_string().capacity() >= 15); +} + +#[test] +fn display_format_flags() { + assert_eq!(format!("a{:#<5}b", Path::new("").display()), "a#####b"); + assert_eq!(format!("a{:#<5}b", Path::new("a").display()), "aa####b"); +} + +#[test] +fn into_rc() { + let orig = "hello/world"; + let path = Path::new(orig); + let rc: Rc = Rc::from(path); + let arc: Arc = Arc::from(path); + + assert_eq!(&*rc, path); + assert_eq!(&*arc, path); + + let rc2: Rc = Rc::from(path.to_owned()); + let arc2: Arc = Arc::from(path.to_owned()); + + assert_eq!(&*rc2, path); + assert_eq!(&*arc2, path); +} + +#[test] +fn test_ord() { + macro_rules! ord( + ($ord:ident, $left:expr, $right:expr) => ({ + use core::cmp::Ordering; + + let left = Path::new($left); + let right = Path::new($right); + assert_eq!(left.cmp(&right), Ordering::$ord); + if (core::cmp::Ordering::$ord == Ordering::Equal) { + assert_eq!(left, right); + + let mut hasher = SipHasher::new(); + left.hash(&mut hasher); + let left_hash = hasher.finish(); + hasher = SipHasher::new(); + right.hash(&mut hasher); + let right_hash = hasher.finish(); + + assert_eq!(left_hash, right_hash, "hashes for {:?} and {:?} must match", left, right); + } else { + assert_ne!(left, right); + } + }); + ); + + ord!(Less, "1", "2"); + ord!(Less, "/foo/bar", "/foo./bar"); + ord!(Less, "foo/bar", "foo/bar."); + ord!(Equal, "foo/./bar", "foo/bar/"); + ord!(Equal, "foo/bar", "foo/bar/"); + ord!(Equal, "foo/bar", "foo/bar/."); + ord!(Equal, "foo/bar", "foo/bar//"); +} + +// #[test] +// #[cfg(unix)] +// fn test_unix_absolute() { +// use crate::path::absolute; + +// assert!(absolute("").is_err()); + +// let relative = "a/b"; +// let mut expected = crate::env::current_dir().unwrap(); +// expected.push(relative); +// assert_eq!(absolute(relative).unwrap().as_os_str(), expected.as_os_str()); + +// // Test how components are collected. +// assert_eq!(absolute("/a/b/c").unwrap().as_os_str(), Path::new("/a/b/c").as_os_str()); +// assert_eq!(absolute("/a//b/c").unwrap().as_os_str(), Path::new("/a/b/c").as_os_str()); +// assert_eq!(absolute("//a/b/c").unwrap().as_os_str(), Path::new("//a/b/c").as_os_str()); +// assert_eq!(absolute("///a/b/c").unwrap().as_os_str(), Path::new("/a/b/c").as_os_str()); +// assert_eq!(absolute("/a/b/c/").unwrap().as_os_str(), Path::new("/a/b/c/").as_os_str()); +// assert_eq!( +// absolute("/a/./b/../c/.././..").unwrap().as_os_str(), +// Path::new("/a/b/../c/../..").as_os_str() +// ); + +// // Test leading `.` and `..` components +// let curdir = crate::env::current_dir().unwrap(); +// assert_eq!(absolute("./a").unwrap().as_os_str(), curdir.join("a").as_os_str()); +// assert_eq!(absolute("../a").unwrap().as_os_str(), curdir.join("../a").as_os_str()); // return /pwd/../a +// } + +// #[test] +// #[cfg(windows)] +// fn test_windows_absolute() { +// use crate::path::absolute; +// // An empty path is an error. +// assert!(absolute("").is_err()); + +// let relative = r"a\b"; +// let mut expected = crate::env::current_dir().unwrap(); +// expected.push(relative); +// assert_eq!(absolute(relative).unwrap().as_os_str(), expected.as_os_str()); + +// macro_rules! unchanged( +// ($path:expr) => { +// assert_eq!(absolute($path).unwrap().as_os_str(), Path::new($path).as_os_str()); +// } +// ); + +// unchanged!(r"C:\path\to\file"); +// unchanged!(r"C:\path\to\file\"); +// unchanged!(r"\\server\share\to\file"); +// unchanged!(r"\\server.\share.\to\file"); +// unchanged!(r"\\.\PIPE\name"); +// unchanged!(r"\\.\C:\path\to\COM1"); +// unchanged!(r"\\?\C:\path\to\file"); +// unchanged!(r"\\?\UNC\server\share\to\file"); +// unchanged!(r"\\?\PIPE\name"); +// // Verbatim paths are always unchanged, no matter what. +// unchanged!(r"\\?\path.\to/file.."); + +// assert_eq!( +// absolute(r"C:\path..\to.\file.").unwrap().as_os_str(), +// Path::new(r"C:\path..\to\file").as_os_str() +// ); +// assert_eq!(absolute(r"COM1").unwrap().as_os_str(), Path::new(r"\\.\COM1").as_os_str()); +// } + +// #[bench] +// #[cfg_attr(miri, ignore)] // Miri isn't fast... +// fn bench_path_cmp_fast_path_buf_sort(b: &mut test::Bencher) { +// let prefix = "my/home"; +// let mut paths: Vec<_> = +// (0..1000).map(|num| PathBuf::from(prefix).join(format!("file {num}.rs"))).collect(); + +// paths.sort(); + +// b.iter(|| { +// black_box(paths.as_mut_slice()).sort_unstable(); +// }); +// } + +// #[bench] +// #[cfg_attr(miri, ignore)] // Miri isn't fast... +// fn bench_path_cmp_fast_path_long(b: &mut test::Bencher) { +// let prefix = "/my/home/is/my/castle/and/my/castle/has/a/rusty/workbench/"; +// let paths: Vec<_> = +// (0..1000).map(|num| PathBuf::from(prefix).join(format!("file {num}.rs"))).collect(); + +// let mut set = BTreeSet::new(); + +// paths.iter().for_each(|p| { +// set.insert(p.as_path()); +// }); + +// b.iter(|| { +// set.remove(paths[500].as_path()); +// set.insert(paths[500].as_path()); +// }); +// } + +// #[bench] +// #[cfg_attr(miri, ignore)] // Miri isn't fast... +// fn bench_path_cmp_fast_path_short(b: &mut test::Bencher) { +// let prefix = "my/home"; +// let paths: Vec<_> = +// (0..1000).map(|num| PathBuf::from(prefix).join(format!("file {num}.rs"))).collect(); + +// let mut set = BTreeSet::new(); + +// paths.iter().for_each(|p| { +// set.insert(p.as_path()); +// }); + +// b.iter(|| { +// set.remove(paths[500].as_path()); +// set.insert(paths[500].as_path()); +// }); +// } + +// #[bench] +// #[cfg_attr(miri, ignore)] // Miri isn't fast... +// fn bench_path_hashset(b: &mut test::Bencher) { +// let prefix = "/my/home/is/my/castle/and/my/castle/has/a/rusty/workbench/"; +// let paths: Vec<_> = +// (0..1000).map(|num| PathBuf::from(prefix).join(format!("file {num}.rs"))).collect(); + +// let mut set = HashSet::new(); + +// paths.iter().for_each(|p| { +// set.insert(p.as_path()); +// }); + +// b.iter(|| { +// set.remove(paths[500].as_path()); +// set.insert(black_box(paths[500].as_path())) +// }); +// } + +// #[bench] +// #[cfg_attr(miri, ignore)] // Miri isn't fast... +// fn bench_path_hashset_miss(b: &mut test::Bencher) { +// let prefix = "/my/home/is/my/castle/and/my/castle/has/a/rusty/workbench/"; +// let paths: Vec<_> = +// (0..1000).map(|num| PathBuf::from(prefix).join(format!("file {num}.rs"))).collect(); + +// let mut set = HashSet::new(); + +// paths.iter().for_each(|p| { +// set.insert(p.as_path()); +// }); + +// let probe = PathBuf::from(prefix).join("other"); + +// b.iter(|| set.remove(black_box(probe.as_path()))); +// } + +// #[bench] +// fn bench_hash_path_short(b: &mut test::Bencher) { +// let mut hasher = SipHasher::new(); +// let path = Path::new("explorer.exe"); + +// b.iter(|| black_box(path).hash(&mut hasher)); + +// black_box(hasher.finish()); +// } + +// #[bench] +// fn bench_hash_path_long(b: &mut test::Bencher) { +// let mut hasher = SipHasher::new(); +// let path = +// Path::new("/aaaaa/aaaaaa/./../aaaaaaaa/bbbbbbbbbbbbb/ccccccccccc/ddddddddd/eeeeeee.fff"); + +// b.iter(|| black_box(path).hash(&mut hasher)); + +// black_box(hasher.finish()); +// } + +#[test] +fn fangzhen() { + let input_path = PathBuf::from("/bin/test/some/../something/entry/./hey/the.service"); + let root = PathBuf::from("/bin/test/something"); + let clean = input_path.clean(); + assert_eq!( + "entry/hey/the.service", + clean.strip_prefix(root).unwrap().as_os_str() + ); +} diff --git a/kernel/crates/path_base/src/util.rs b/kernel/crates/path_base/src/util.rs new file mode 100644 index 000000000..67dd7d179 --- /dev/null +++ b/kernel/crates/path_base/src/util.rs @@ -0,0 +1,61 @@ +use crate::Prefix; + +#[inline] +pub fn is_sep_byte(b: u8) -> bool { + b == b'/' +} + +#[inline] +pub fn is_verbatim_sep(b: u8) -> bool { + b == b'/' +} + +#[inline] +pub fn parse_prefix(_: &str) -> Option> { + None +} + +pub const MAIN_SEP_STR: &str = "/"; +#[allow(dead_code)] +pub const MAIN_SEP: char = '/'; + +// /// Make a POSIX path absolute without changing its semantics. +// pub(crate) fn absolute(path: &Path) -> io::Result { +// // This is mostly a wrapper around collecting `Path::components`, with +// // exceptions made where this conflicts with the POSIX specification. +// // See 4.13 Pathname Resolution, IEEE Std 1003.1-2017 +// // https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_13 + +// // Get the components, skipping the redundant leading "." component if it exists. +// let mut components = path.strip_prefix(".").unwrap_or(path).components(); +// let path_os = path.as_os_str().as_encoded_bytes(); + +// let mut normalized = if path.is_absolute() { +// // "If a pathname begins with two successive characters, the +// // first component following the leading characters may be +// // interpreted in an implementation-defined manner, although more than +// // two leading characters shall be treated as a single +// // character." +// if path_os.starts_with(b"//") && !path_os.starts_with(b"///") { +// components.next(); +// PathBuf::from("//") +// } else { +// PathBuf::new() +// } +// } else { +// env::current_dir()? +// }; +// normalized.extend(components); + +// // "Interfaces using pathname resolution may specify additional constraints +// // when a pathname that does not name an existing directory contains at +// // least one non- character and contains one or more trailing +// // characters". +// // A trailing is also meaningful if "a symbolic link is +// // encountered during pathname resolution". +// if path_os.ends_with(b"/") { +// normalized.push(""); +// } + +// Ok(normalized) +// } diff --git a/kernel/src/arch/x86_64/process/syscall.rs b/kernel/src/arch/x86_64/process/syscall.rs index ec488bbd4..900ddb4c6 100644 --- a/kernel/src/arch/x86_64/process/syscall.rs +++ b/kernel/src/arch/x86_64/process/syscall.rs @@ -59,7 +59,6 @@ impl Syscall { // 切换到新的用户地址空间 unsafe { address_space.read().user_mapper.utable.make_current() }; - drop(old_address_space); drop(irq_guard); // kdebug!("to load binary file"); diff --git a/kernel/src/driver/tty/tty_device.rs b/kernel/src/driver/tty/tty_device.rs index 197424010..fa528000f 100644 --- a/kernel/src/driver/tty/tty_device.rs +++ b/kernel/src/driver/tty/tty_device.rs @@ -272,7 +272,7 @@ impl IndexNode for TtyDevice { } fn fs(&self) -> Arc { - todo!() + self.fs.read().upgrade().unwrap() } fn as_any_ref(&self) -> &dyn core::any::Any { diff --git a/kernel/src/filesystem/devfs/mod.rs b/kernel/src/filesystem/devfs/mod.rs index 3b19ed975..421fe242d 100644 --- a/kernel/src/filesystem/devfs/mod.rs +++ b/kernel/src/filesystem/devfs/mod.rs @@ -353,31 +353,30 @@ impl LockedDevFSInode { } // 创建inode - let result: Arc = Arc::new(LockedDevFSInode(SpinLock::new(DevFSInode { - parent: guard.self_ref.clone(), - self_ref: Weak::default(), - children: BTreeMap::new(), - metadata: Metadata { - dev_id: 0, - inode_id: generate_inode_id(), - size: 0, - blk_size: 0, - blocks: 0, - atime: PosixTimeSpec::default(), - mtime: PosixTimeSpec::default(), - ctime: PosixTimeSpec::default(), - file_type, - mode, - nlinks: 1, - uid: 0, - gid: 0, - raw_dev: DeviceNumber::from(data as u32), - }, - fs: guard.fs.clone(), - }))); - - // 初始化inode的自引用的weak指针 - result.0.lock().self_ref = Arc::downgrade(&result); + let result: Arc = Arc::new_cyclic( + |self_ref| LockedDevFSInode(SpinLock::new(DevFSInode { + parent: guard.self_ref.clone(), + self_ref: self_ref.clone(), + children: BTreeMap::new(), + metadata: Metadata { + dev_id: 0, + inode_id: generate_inode_id(), + size: 0, + blk_size: 0, + blocks: 0, + atime: PosixTimeSpec::default(), + mtime: PosixTimeSpec::default(), + ctime: PosixTimeSpec::default(), + file_type, + mode, + nlinks: 1, + uid: 0, + gid: 0, + raw_dev: DeviceNumber::from(data as u32), + }, + fs: guard.fs.clone(), + } + ))); // 将子inode插入父inode的B树中 guard.children.insert(String::from(name), result.clone()); diff --git a/kernel/src/filesystem/fat/fs.rs b/kernel/src/filesystem/fat/fs.rs index 38f558bae..27a43af9f 100644 --- a/kernel/src/filesystem/fat/fs.rs +++ b/kernel/src/filesystem/fat/fs.rs @@ -1,6 +1,4 @@ -use core::cmp::Ordering; -use core::intrinsics::unlikely; -use core::{any::Any, fmt::Debug}; +use core::{any::Any, cmp::Ordering, fmt::Debug, intrinsics::unlikely}; use system_error::SystemError; use alloc::{ @@ -10,17 +8,18 @@ use alloc::{ vec::Vec, }; -use crate::driver::base::device::device_number::DeviceNumber; -use crate::filesystem::vfs::{Magic, SpecialNodeData, SuperBlock}; -use crate::ipc::pipe::LockedPipeInode; use crate::{ - driver::base::block::{block_device::LBA_SIZE, disk_info::Partition, SeekFrom}, + driver::base::{ + block::{block_device::LBA_SIZE, disk_info::Partition, SeekFrom}, + device::device_number::DeviceNumber, + }, filesystem::vfs::{ core::generate_inode_id, file::{FileMode, FilePrivateData}, syscall::ModeType, - FileSystem, FileType, IndexNode, InodeId, Metadata, + FileSystem, FileType, IndexNode, InodeId, Magic, Metadata, SpecialNodeData, SuperBlock, }, + ipc::pipe::LockedPipeInode, kerror, libs::{ spinlock::{SpinLock, SpinLockGuard}, @@ -29,10 +28,9 @@ use crate::{ time::PosixTimeSpec, }; -use super::entry::FATFile; use super::{ bpb::{BiosParameterBlock, FATType}, - entry::{FATDir, FATDirEntry, FATDirIter, FATEntry}, + entry::{FATDir, FATDirEntry, FATDirIter, FATEntry, FATFile}, utils::RESERVED_CLUSTERS, }; @@ -165,6 +163,11 @@ impl FATInode { } } } + + #[inline] + fn key(&self) -> Result { + Ok(self.inode_type.name()) + } } impl LockedFATInode { @@ -1811,6 +1814,10 @@ impl IndexNode for LockedFATInode { fn special_node(&self) -> Option { self.0.lock().special_node.clone() } + + fn entry_name(&self) -> Result { + self.0.lock().key() + } } impl Default for FATFsInfo { diff --git a/kernel/src/filesystem/procfs/mod.rs b/kernel/src/filesystem/procfs/mod.rs index ef69cce1a..cd946ca13 100644 --- a/kernel/src/filesystem/procfs/mod.rs +++ b/kernel/src/filesystem/procfs/mod.rs @@ -815,7 +815,6 @@ pub fn procfs_init() -> Result<(), SystemError> { .expect("Cannot find /proc") .mount(procfs) .expect("Failed to mount proc"); - kinfo!("ProcFS mounted."); result = Some(Ok(())); }); diff --git a/kernel/src/filesystem/ramfs/mod.rs b/kernel/src/filesystem/ramfs/mod.rs index f2505753a..0b4a5918f 100644 --- a/kernel/src/filesystem/ramfs/mod.rs +++ b/kernel/src/filesystem/ramfs/mod.rs @@ -1,16 +1,8 @@ -use core::any::Any; -use core::intrinsics::unlikely; +mod utils; -use crate::filesystem::vfs::FSMAKER; -use crate::libs::rwlock::RwLock; -use crate::{ - driver::base::device::device_number::DeviceNumber, - filesystem::vfs::{core::generate_inode_id, FileType}, - ipc::pipe::LockedPipeInode, - libs::casting::DowncastArc, - libs::spinlock::{SpinLock, SpinLockGuard}, - time::PosixTimeSpec, -}; +use system_error::SystemError; + +use core::{any::Any, intrinsics::unlikely}; use alloc::{ collections::BTreeMap, @@ -18,62 +10,90 @@ use alloc::{ sync::{Arc, Weak}, vec::Vec, }; -use system_error::SystemError; + +use crate::{ + driver::base::device::device_number::DeviceNumber, + ipc::pipe::LockedPipeInode, + libs::{ + casting::DowncastArc, + rwlock::RwLock, + spinlock::{SpinLock, SpinLockGuard}, + }, + time::PosixTimeSpec, +}; use super::vfs::{ - file::FilePrivateData, syscall::ModeType, FileSystem, FileSystemMaker, FsInfo, IndexNode, - InodeId, Metadata, SpecialNodeData, + core::generate_inode_id, + file::FilePrivateData, + syscall::ModeType, + FileSystem, FileSystemMaker, FileType, FsInfo, IndexNode, Magic, Metadata, SpecialNodeData, + SuperBlock, FSMAKER, }; -use super::vfs::{Magic, SuperBlock}; + +use self::utils::Keyer; /// RamFS的inode名称的最大长度 const RAMFS_MAX_NAMELEN: usize = 64; const RAMFS_BLOCK_SIZE: u64 = 512; -/// @brief 内存文件系统的Inode结构体 -#[derive(Debug)] -struct LockedRamFSInode(SpinLock); - -/// @brief 内存文件系统结构体 #[derive(Debug)] pub struct RamFS { - /// RamFS的root inode - root_inode: Arc, + root: Arc, super_block: RwLock, } -/// @brief 内存文件系统的Inode结构体(不包含锁) -#[derive(Debug)] -pub struct RamFSInode { - // parent变量目前只在find函数中使用到 - // 所以只有当inode是文件夹的时候,parent才会生效 - // 对于文件来说,parent就没什么作用了 - // 关于parent的说明: 目录不允许有硬链接 - /// 指向父Inode的弱引用 - parent: Weak, - /// 指向自身的弱引用 - self_ref: Weak, - /// 子Inode的B树 - children: BTreeMap>, - /// 当前inode的数据部分 - data: Vec, - /// 当前inode的元数据 - metadata: Metadata, - /// 指向inode所在的文件系统对象的指针 - fs: Weak, - /// 指向特殊节点 - special_node: Option, +impl RamFS { + pub fn new() -> Arc { + let root = Arc::new(LockedRamfsEntry(SpinLock::new(RamfsEntry { + name: String::new(), + inode: Arc::new(LockedRamFSInode(SpinLock::new(RamFSInode::new( + FileType::Dir, + ModeType::from_bits_truncate(0o777), + )))), + parent: Weak::new(), + self_ref: Weak::new(), + children: BTreeMap::new(), + fs: Weak::new(), + special_node: None, + }))); + let ret = Arc::new(RamFS { + root, + super_block: RwLock::new(SuperBlock::new( + Magic::RAMFS_MAGIC, + RAMFS_BLOCK_SIZE, + RAMFS_MAX_NAMELEN as u64, + )), + }); + { + let mut entry = ret.root.0.lock(); + entry.parent = Arc::downgrade(&ret.root); + entry.self_ref = Arc::downgrade(&ret.root); + entry.fs = Arc::downgrade(&ret); + } + ret + } + + pub fn make_ramfs() -> Result, SystemError> { + let fs = RamFS::new(); + return Ok(fs); + } } +#[distributed_slice(FSMAKER)] +static RAMFSMAKER: FileSystemMaker = FileSystemMaker::new( + "ramfs", + &(RamFS::make_ramfs as fn() -> Result, SystemError>), +); + impl FileSystem for RamFS { fn root_inode(&self) -> Arc { - return self.root_inode.clone(); + self.root.clone() } fn info(&self) -> FsInfo { - return FsInfo { + FsInfo { blk_dev_id: 0, max_name_len: RAMFS_MAX_NAMELEN, - }; + } } /// @brief 本函数用于实现动态转换。 @@ -91,19 +111,50 @@ impl FileSystem for RamFS { } } -impl RamFS { - pub fn new() -> Arc { - let super_block = SuperBlock::new( - Magic::RAMFS_MAGIC, - RAMFS_BLOCK_SIZE, - RAMFS_MAX_NAMELEN as u64, - ); - // 初始化root inode - let root: Arc = Arc::new(LockedRamFSInode(SpinLock::new(RamFSInode { - parent: Weak::default(), - self_ref: Weak::default(), - children: BTreeMap::new(), +#[derive(Debug)] +pub struct RamFSInode { + /// 元数据 + metadata: Metadata, + /// 数据块 + data: Vec, +} + +#[derive(Debug)] +pub struct LockedRamFSInode(SpinLock); + +#[derive(Debug)] +pub struct RamfsEntry { + /// 目录名 + name: String, + /// 文件节点 + inode: Arc, + /// 父目录 + parent: Weak, + /// 自引用 + self_ref: Weak, + /// 子目录 + children: BTreeMap>, + /// 目录所属文件系统 + fs: Weak, + + special_node: Option, +} + +#[derive(Debug)] +pub struct LockedRamfsEntry(SpinLock); + +impl RamFSInode { + pub fn new(file_type: FileType, mode: ModeType) -> RamFSInode { + RamFSInode { data: Vec::new(), + metadata: Metadata::new(file_type, mode), + } + } + + pub fn new_with_data(file_type: FileType, mode: ModeType, data: usize) -> RamFSInode { + RamFSInode { + data: Vec::new(), + metadata: Metadata { dev_id: 0, inode_id: generate_inode_id(), @@ -113,47 +164,21 @@ impl RamFS { atime: PosixTimeSpec::default(), mtime: PosixTimeSpec::default(), ctime: PosixTimeSpec::default(), - file_type: FileType::Dir, - mode: ModeType::from_bits_truncate(0o777), + file_type, + mode, nlinks: 1, uid: 0, gid: 0, - raw_dev: DeviceNumber::default(), + raw_dev: DeviceNumber::from(data as u32), }, - fs: Weak::default(), - special_node: None, - }))); - - let result: Arc = Arc::new(RamFS { - root_inode: root, - super_block: RwLock::new(super_block), - }); - - // 对root inode加锁,并继续完成初始化工作 - let mut root_guard: SpinLockGuard = result.root_inode.0.lock(); - root_guard.parent = Arc::downgrade(&result.root_inode); - root_guard.self_ref = Arc::downgrade(&result.root_inode); - root_guard.fs = Arc::downgrade(&result); - // 释放锁 - drop(root_guard); - - return result; - } - - pub fn make_ramfs() -> Result, SystemError> { - let fs = RamFS::new(); - return Ok(fs); + } } } -#[distributed_slice(FSMAKER)] -static RAMFSMAKER: FileSystemMaker = FileSystemMaker::new( - "ramfs", - &(RamFS::make_ramfs as fn() -> Result, SystemError>), -); -impl IndexNode for LockedRamFSInode { +impl IndexNode for LockedRamfsEntry { fn truncate(&self, len: usize) -> Result<(), SystemError> { - let mut inode = self.0.lock(); + let entry = self.0.lock(); + let mut inode = entry.inode.0.lock(); //如果是文件夹,则报错 if inode.metadata.file_type == FileType::Dir { @@ -164,7 +189,8 @@ impl IndexNode for LockedRamFSInode { if inode.data.len() > len { inode.data.resize(len, 0); } - return Ok(()); + + Ok(()) } fn close(&self, _data: SpinLockGuard) -> Result<(), SystemError> { @@ -190,7 +216,9 @@ impl IndexNode for LockedRamFSInode { return Err(SystemError::EINVAL); } // 加锁 - let inode: SpinLockGuard = self.0.lock(); + let entry = self.0.lock(); + + let inode = entry.inode.0.lock(); // 检查当前inode是否为一个文件夹,如果是的话,就返回错误 if inode.metadata.file_type == FileType::Dir { @@ -223,7 +251,8 @@ impl IndexNode for LockedRamFSInode { } // 加锁 - let mut inode: SpinLockGuard = self.0.lock(); + let entry = self.0.lock(); + let mut inode = entry.inode.0.lock(); // 检查当前inode是否为一个文件夹,如果是的话,就返回错误 if inode.metadata.file_type == FileType::Dir { @@ -239,11 +268,12 @@ impl IndexNode for LockedRamFSInode { let target = &mut data[offset..offset + len]; target.copy_from_slice(&buf[0..len]); + return Ok(len); } fn fs(&self) -> Arc { - return self.0.lock().fs.upgrade().unwrap(); + self.0.lock().fs.upgrade().unwrap() } fn as_any_ref(&self) -> &dyn core::any::Any { @@ -251,15 +281,21 @@ impl IndexNode for LockedRamFSInode { } fn metadata(&self) -> Result { - let inode = self.0.lock(); + let entry = self.0.lock(); + let inode = entry.inode.0.lock(); let mut metadata = inode.metadata.clone(); + metadata.size = inode.data.len() as i64; + drop(inode); + drop(entry); + return Ok(metadata); } fn set_metadata(&self, metadata: &Metadata) -> Result<(), SystemError> { - let mut inode = self.0.lock(); + let entry = self.0.lock(); + let mut inode = entry.inode.0.lock(); inode.metadata.atime = metadata.atime; inode.metadata.mtime = metadata.mtime; inode.metadata.ctime = metadata.ctime; @@ -271,13 +307,13 @@ impl IndexNode for LockedRamFSInode { } fn resize(&self, len: usize) -> Result<(), SystemError> { - let mut inode = self.0.lock(); + let entry = self.0.lock(); + let mut inode = entry.inode.0.lock(); if inode.metadata.file_type == FileType::File { inode.data.resize(len, 0); return Ok(()); - } else { - return Err(SystemError::EINVAL); } + return Err(SystemError::EINVAL); } fn create_with_data( @@ -288,120 +324,155 @@ impl IndexNode for LockedRamFSInode { data: usize, ) -> Result, SystemError> { // 获取当前inode - let mut inode = self.0.lock(); - // 如果当前inode不是文件夹,则返回 - if inode.metadata.file_type != FileType::Dir { - return Err(SystemError::ENOTDIR); + let mut entry = self.0.lock(); + { + let inode = entry.inode.0.lock(); + // 如果当前inode不是文件夹,则返回 + if inode.metadata.file_type != FileType::Dir { + return Err(SystemError::ENOTDIR); + } } + // 如果有重名的,则返回 - if inode.children.contains_key(name) { + if entry.children.contains_key(&Keyer::from_str(name)) { return Err(SystemError::EEXIST); } - // 创建inode - let result: Arc = Arc::new(LockedRamFSInode(SpinLock::new(RamFSInode { - parent: inode.self_ref.clone(), + // 创建Entry-inode + let result = Arc::new(LockedRamfsEntry(SpinLock::new(RamfsEntry { + parent: entry.self_ref.clone(), self_ref: Weak::default(), children: BTreeMap::new(), - data: Vec::new(), - metadata: Metadata { - dev_id: 0, - inode_id: generate_inode_id(), - size: 0, - blk_size: 0, - blocks: 0, - atime: PosixTimeSpec::default(), - mtime: PosixTimeSpec::default(), - ctime: PosixTimeSpec::default(), - file_type, - mode, - nlinks: 1, - uid: 0, - gid: 0, - raw_dev: DeviceNumber::from(data as u32), - }, - fs: inode.fs.clone(), + inode: Arc::new(LockedRamFSInode(SpinLock::new(RamFSInode::new_with_data( + file_type, mode, data, + )))), + fs: entry.fs.clone(), special_node: None, + name: String::from(name), }))); // 初始化inode的自引用的weak指针 result.0.lock().self_ref = Arc::downgrade(&result); // 将子inode插入父inode的B树中 - inode.children.insert(String::from(name), result.clone()); - + entry + .children + .insert(Keyer::from_entry(&result), result.clone()); return Ok(result); } + /// Not Stable, waiting for improvement fn link(&self, name: &str, other: &Arc) -> Result<(), SystemError> { - let other: &LockedRamFSInode = other - .downcast_ref::() + let other: &LockedRamfsEntry = other + .downcast_ref::() .ok_or(SystemError::EPERM)?; - let mut inode: SpinLockGuard = self.0.lock(); - let mut other_locked: SpinLockGuard = other.0.lock(); - // 如果当前inode不是文件夹,那么报错 - if inode.metadata.file_type != FileType::Dir { - return Err(SystemError::ENOTDIR); - } + let mut entry = self.0.lock(); + let other_entry = other.0.lock(); + let mut other_inode = other_entry.inode.0.lock(); + { + let inode = entry.inode.0.lock(); + // 如果当前inode不是文件夹,那么报错 + if inode.metadata.file_type != FileType::Dir { + return Err(SystemError::ENOTDIR); + } + } // 如果另一个inode是文件夹,那么也报错 - if other_locked.metadata.file_type == FileType::Dir { + if other_inode.metadata.file_type == FileType::Dir { return Err(SystemError::EISDIR); } // 如果当前文件夹下已经有同名文件,也报错。 - if inode.children.contains_key(name) { + if entry.children.contains_key(&Keyer::from_str(name)) { return Err(SystemError::EEXIST); } - inode + // 创建新Entry指向other inode + // 并插入子目录序列 + let to_insert = Arc::new(LockedRamfsEntry(SpinLock::new(RamfsEntry { + name: String::from(name), + inode: other_entry.inode.clone(), + parent: entry.self_ref.clone(), + self_ref: Weak::new(), + children: BTreeMap::new(), // File should not have children + fs: other_entry.fs.clone(), + special_node: other_entry.special_node.clone(), + }))); + entry .children - .insert(String::from(name), other_locked.self_ref.upgrade().unwrap()); + .insert(Keyer::from_entry(&to_insert), to_insert); // 增加硬链接计数 - other_locked.metadata.nlinks += 1; + other_inode.metadata.nlinks += 1; + return Ok(()); } fn unlink(&self, name: &str) -> Result<(), SystemError> { - let mut inode: SpinLockGuard = self.0.lock(); - // 如果当前inode不是目录,那么也没有子目录/文件的概念了,因此要求当前inode的类型是目录 - if inode.metadata.file_type != FileType::Dir { - return Err(SystemError::ENOTDIR); + let mut entry = self.0.lock(); + { + let inode = entry.inode.0.lock(); + // 如果当前inode不是目录,那么也没有子目录/文件的概念了,因此要求当前inode的类型是目录 + if inode.metadata.file_type != FileType::Dir { + return Err(SystemError::ENOTDIR); + } } // 不允许删除当前文件夹,也不允许删除上一个目录 if name == "." || name == ".." { return Err(SystemError::ENOTEMPTY); } - - // 获得要删除的文件的inode - let to_delete = inode.children.get(name).ok_or(SystemError::ENOENT)?; - if to_delete.0.lock().metadata.file_type == FileType::Dir { - return Err(SystemError::EPERM); + { + // 获得要删除的文件的inode + let to_del_entry = entry + .children + .get(&Keyer::from_str(name)) + .ok_or(SystemError::ENOENT)? + .0 + .lock(); + let mut to_del_node = to_del_entry.inode.0.lock(); + + if to_del_node.metadata.file_type == FileType::Dir { + return Err(SystemError::EPERM); + } + // 减少硬链接计数 + to_del_node.metadata.nlinks -= 1; } - // 减少硬链接计数 - to_delete.0.lock().metadata.nlinks -= 1; // 在当前目录中删除这个子目录项 - inode.children.remove(name); + entry.children.remove(&Keyer::from_str(name)); + return Ok(()); } fn rmdir(&self, name: &str) -> Result<(), SystemError> { - let mut inode: SpinLockGuard = self.0.lock(); - // 如果当前inode不是目录,那么也没有子目录/文件的概念了,因此要求当前inode的类型是目录 - if inode.metadata.file_type != FileType::Dir { - return Err(SystemError::ENOTDIR); - } - // 获得要删除的文件夹的inode - let to_delete = inode.children.get(name).ok_or(SystemError::ENOENT)?; - if to_delete.0.lock().metadata.file_type != FileType::Dir { - return Err(SystemError::ENOTDIR); + let mut entry = self.0.lock(); + { + let inode = entry.inode.0.lock(); + + // 如果当前inode不是目录,那么也没有子目录/文件的概念了,因此要求当前inode的类型是目录 + if inode.metadata.file_type != FileType::Dir { + return Err(SystemError::ENOTDIR); + } } + // Gain keyer + let keyer = Keyer::from_str(name); + { + // 获得要删除的文件夹的inode + let to_del_ent = entry + .children + .get(&keyer) + .ok_or(SystemError::ENOENT)? + .0 + .lock(); + let mut to_del_nod = to_del_ent.inode.0.lock(); + if to_del_nod.metadata.file_type != FileType::Dir { + return Err(SystemError::ENOTDIR); + } - to_delete.0.lock().metadata.nlinks -= 1; + to_del_nod.metadata.nlinks -= 1; + } // 在当前目录中删除这个子目录项 - inode.children.remove(name); + entry.children.remove(&keyer); return Ok(()); } @@ -414,14 +485,14 @@ impl IndexNode for LockedRamFSInode { let inode: Arc = self.find(old_name)?; // 修改其对父节点的引用 inode - .downcast_ref::() + .downcast_ref::() .ok_or(SystemError::EPERM)? .0 .lock() .parent = Arc::downgrade( &target .clone() - .downcast_arc::() + .downcast_arc::() .ok_or(SystemError::EPERM)?, ); @@ -439,64 +510,54 @@ impl IndexNode for LockedRamFSInode { } fn find(&self, name: &str) -> Result, SystemError> { - let inode = self.0.lock(); + let entry = self.0.lock(); + let inode = entry.inode.0.lock(); if inode.metadata.file_type != FileType::Dir { return Err(SystemError::ENOTDIR); } match name { - "" | "." => { - return Ok(inode.self_ref.upgrade().ok_or(SystemError::ENOENT)?); - } + "" | "." => Ok(entry.self_ref.upgrade().ok_or(SystemError::ENOENT)?), - ".." => { - return Ok(inode.parent.upgrade().ok_or(SystemError::ENOENT)?); - } + ".." => Ok(entry.parent.upgrade().ok_or(SystemError::ENOENT)?), name => { // 在子目录项中查找 - return Ok(inode.children.get(name).ok_or(SystemError::ENOENT)?.clone()); + Ok(entry + .children + .get(&Keyer::from_str(name)) + .ok_or(SystemError::ENOENT)? + .clone()) } } } - fn get_entry_name(&self, ino: InodeId) -> Result { - let inode: SpinLockGuard = self.0.lock(); + /// Potential panic + fn get_entry_name(&self, ino: crate::filesystem::vfs::InodeId) -> Result { + let entry = self.0.lock(); + let inode = entry.inode.0.lock(); if inode.metadata.file_type != FileType::Dir { return Err(SystemError::ENOTDIR); } match ino.into() { - 0 => { - return Ok(String::from(".")); - } - 1 => { - return Ok(String::from("..")); - } + 0 => Ok(String::from(".")), + 1 => Ok(String::from("..")), ino => { - // 暴力遍历所有的children,判断inode id是否相同 - // TODO: 优化这里,这个地方性能很差! - let mut key: Vec = inode + let mut key: Vec = entry .children - .keys() - .filter(|k| { - inode - .children - .get(*k) - .unwrap() - .0 - .lock() - .metadata - .inode_id - .into() - == ino + .iter() + .filter_map(|(_, value)| { + if value.0.lock().inode.0.lock().metadata.inode_id.into() == ino { + return Some(value.0.lock().name.clone()); + } + None }) - .cloned() .collect(); match key.len() { - 0=>{return Err(SystemError::ENOENT);} - 1=>{return Ok(key.remove(0));} + 0 => Err(SystemError::ENOENT), + 1 => Ok(key.remove(0)), _ => panic!("Ramfs get_entry_name: key.len()={key_len}>1, current inode_id={inode_id:?}, to find={to_find:?}", key_len=key.len(), inode_id = inode.metadata.inode_id, to_find=ino) } } @@ -504,6 +565,7 @@ impl IndexNode for LockedRamFSInode { } fn list(&self) -> Result, SystemError> { + // kinfo!("Call Ramfs::list"); let info = self.metadata()?; if info.file_type != FileType::Dir { return Err(SystemError::ENOTDIR); @@ -512,7 +574,15 @@ impl IndexNode for LockedRamFSInode { let mut keys: Vec = Vec::new(); keys.push(String::from(".")); keys.push(String::from("..")); - keys.append(&mut self.0.lock().children.keys().cloned().collect()); + keys.append( + &mut self + .0 + .lock() + .children + .keys() + .map(|k| k.get().unwrap_or(String::from("[unknown_filename]"))) + .collect(), + ); return Ok(keys); } @@ -523,66 +593,60 @@ impl IndexNode for LockedRamFSInode { mode: ModeType, _dev_t: DeviceNumber, ) -> Result, SystemError> { - let mut inode = self.0.lock(); - if inode.metadata.file_type != FileType::Dir { - return Err(SystemError::ENOTDIR); + let mut entry = self.0.lock(); + { + let inode = entry.inode.0.lock(); + if inode.metadata.file_type != FileType::Dir { + return Err(SystemError::ENOTDIR); + } } - // 判断需要创建的类型 if unlikely(mode.contains(ModeType::S_IFREG)) { // 普通文件 return self.create(filename, FileType::File, mode); } - let nod = Arc::new(LockedRamFSInode(SpinLock::new(RamFSInode { - parent: inode.self_ref.clone(), + let nod = Arc::new(LockedRamfsEntry(SpinLock::new(RamfsEntry { + parent: entry.self_ref.clone(), self_ref: Weak::default(), children: BTreeMap::new(), - data: Vec::new(), - metadata: Metadata { - dev_id: 0, - inode_id: generate_inode_id(), - size: 0, - blk_size: 0, - blocks: 0, - atime: PosixTimeSpec::default(), - mtime: PosixTimeSpec::default(), - ctime: PosixTimeSpec::default(), - file_type: FileType::Pipe, + inode: Arc::new(LockedRamFSInode(SpinLock::new(RamFSInode::new( + FileType::Pipe, mode, - nlinks: 1, - uid: 0, - gid: 0, - raw_dev: DeviceNumber::default(), - }, - fs: inode.fs.clone(), + )))), + fs: entry.fs.clone(), special_node: None, + name: String::from(filename), }))); nod.0.lock().self_ref = Arc::downgrade(&nod); - if mode.contains(ModeType::S_IFIFO) { - nod.0.lock().metadata.file_type = FileType::Pipe; + nod.0.lock().inode.0.lock().metadata.file_type = FileType::Pipe; // 创建pipe文件 let pipe_inode = LockedPipeInode::new(); // 设置special_node nod.0.lock().special_node = Some(SpecialNodeData::Pipe(pipe_inode)); } else if mode.contains(ModeType::S_IFBLK) { - nod.0.lock().metadata.file_type = FileType::BlockDevice; - unimplemented!() + nod.0.lock().inode.0.lock().metadata.file_type = FileType::BlockDevice; + unimplemented!() // Todo } else if mode.contains(ModeType::S_IFCHR) { - nod.0.lock().metadata.file_type = FileType::CharDevice; - unimplemented!() + nod.0.lock().inode.0.lock().metadata.file_type = FileType::CharDevice; + unimplemented!() // Todo } - inode - .children - .insert(String::from(filename).to_uppercase(), nod.clone()); - Ok(nod) + entry.children.insert( + Keyer::from_str(String::from(filename).to_uppercase().as_str()), + nod.clone(), + ); + return Ok(nod); + } + + fn special_node(&self) -> Option { + self.0.lock().special_node.clone() } - fn special_node(&self) -> Option { - return self.0.lock().special_node.clone(); + fn entry_name(&self) -> Result { + Ok(self.0.lock().name.clone()) } /// # 用于重命名内存中的文件或目录 diff --git a/kernel/src/filesystem/ramfs/utils.rs b/kernel/src/filesystem/ramfs/utils.rs new file mode 100644 index 000000000..b035114dd --- /dev/null +++ b/kernel/src/filesystem/ramfs/utils.rs @@ -0,0 +1,112 @@ +//! 用于展示如何在保留比较的同时支持从当前inode原地取出目录名 +use super::LockedRamfsEntry; +use alloc::{ + string::String, + sync::{Arc, Weak}, +}; +use core::cmp::Ordering; + +#[derive(Debug)] +pub(super) struct Keyer(Weak, Option); + +impl Keyer { + pub fn from_str(key: &str) -> Self { + Keyer(Weak::new(), Some(String::from(key))) + } + + pub fn from_entry(entry: &Arc) -> Self { + Keyer(Arc::downgrade(entry), None) + } + + /// 获取name + pub fn get(&self) -> Option { + if self.1.is_some() { + return self.1.clone(); + } + Some(self.0.upgrade()?.0.lock().name.clone()) + } +} + +// For Btree insertion +impl PartialEq for Keyer { + fn eq(&self, other: &Self) -> bool { + if self.0.ptr_eq(&other.0) { + return true; + } + if self.1.is_none() && other.1.is_none() { + // cmp between wrapper + let opt1 = self.0.upgrade(); + let opt2 = other.0.upgrade(); + if opt1.is_none() && opt2.is_none() { + panic!("Empty of both"); + } + if opt1.is_none() || opt2.is_none() { + return false; + } + return opt1.unwrap().0.lock().name == opt2.unwrap().0.lock().name; + } + + if self.1.is_none() { + let opt = self.0.upgrade(); + if opt.is_none() { + // kwarn!("depecated"); + return false; + } + + return &opt.unwrap().0.lock().name == other.1.as_ref().unwrap(); + } else { + let opt = other.0.upgrade(); + if opt.is_none() { + // kwarn!("depecated"); + return false; + } + + return &opt.unwrap().0.lock().name == self.1.as_ref().unwrap(); + } + } +} + +impl Eq for Keyer {} + +// Uncheck Stable +impl PartialOrd for Keyer { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Keyer { + fn cmp(&self, other: &Self) -> Ordering { + // let mut ret: Ordering = Ordering::Equal; + if self.0.ptr_eq(&other.0) { + return Ordering::Equal; + } + if self.1.is_none() && other.1.is_none() { + // cmp between wrapper + let opt1 = self.0.upgrade(); + let opt2 = other.0.upgrade(); + if opt1.is_none() && opt2.is_none() { + panic!("All Keys None, compare error!"); + } + if let Some(o1) = opt1 { + if let Some(o2) = opt2 { + return o1.0.lock().name.cmp(&o2.0.lock().name); + } + } + panic!("Empty Key!"); + } else if self.1.is_none() { + let opt = self.0.upgrade(); + if opt.is_none() { + panic!("Empty Key!"); + } + return opt.unwrap().0.lock().name.cmp(other.1.as_ref().unwrap()); + } else { + let opt = other.0.upgrade(); + if opt.is_none() { + panic!("Empty Key!"); + } + + return self.1.as_ref().unwrap().cmp(&opt.unwrap().0.lock().name); + } + } +} diff --git a/kernel/src/filesystem/vfs/Todo.md b/kernel/src/filesystem/vfs/Todo.md new file mode 100644 index 000000000..681eb037f --- /dev/null +++ b/kernel/src/filesystem/vfs/Todo.md @@ -0,0 +1,6 @@ +- lru +- mountfsinode => dentry +- 内存泄露问题 +- move to +- rename +- mount and unmount \ No newline at end of file diff --git a/kernel/src/filesystem/vfs/core.rs b/kernel/src/filesystem/vfs/core.rs index 162b52875..ede1169d8 100644 --- a/kernel/src/filesystem/vfs/core.rs +++ b/kernel/src/filesystem/vfs/core.rs @@ -1,27 +1,24 @@ use core::{hint::spin_loop, sync::atomic::Ordering}; use alloc::{string::ToString, sync::Arc}; +use path_base::Path; use system_error::SystemError; +use utils::MountList; use crate::{ driver::{base::block::disk_info::Partition, disk::ahci}, filesystem::{ - devfs::devfs_init, - fat::fs::FATFileSystem, - procfs::procfs_init, - ramfs::RamFS, + devfs::devfs_init, fat::fs::FATFileSystem, procfs::procfs_init, ramfs::RamFS, sysfs::sysfs_init, - vfs::{mount::MountFS, syscall::ModeType, AtomicInodeId, FileSystem, FileType}, }, kdebug, kerror, kinfo, + libs::casting::DowncastArc, process::ProcessManager, }; use super::{ - file::FileMode, - mount::MountFSInode, - utils::{rsplit_path, user_path_at}, - IndexNode, InodeId, VFS_MAX_FOLLOW_SYMLINK_TIMES, + file::FileMode, mount::*, syscall::ModeType, utils::user_path_at, AtomicInodeId, FileSystem, + FileType, IndexNode, InodeId, VFS_MAX_FOLLOW_SYMLINK_TIMES, }; /// @brief 原子地生成新的Inode号。 @@ -51,6 +48,7 @@ pub fn vfs_init() -> Result<(), SystemError> { // 使用Ramfs作为默认的根文件系统 let ramfs = RamFS::new(); let mount_fs = MountFS::new(ramfs, None); + MountList::init(); let root_inode = mount_fs.root_inode(); unsafe { @@ -126,20 +124,23 @@ fn migrate_virtual_filesystem(new_fs: Arc) -> Result<(), SystemE let new_fs = MountFS::new(new_fs, None); // 获取新的根文件系统的根节点的引用 let new_root_inode = new_fs.root_inode(); + MountList::init(); - // 把上述文件系统,迁移到新的文件系统下 - do_migrate(new_root_inode.clone(), "proc", proc)?; - do_migrate(new_root_inode.clone(), "dev", dev)?; - do_migrate(new_root_inode.clone(), "sys", sys)?; unsafe { - // drop旧的Root inode - let old_root_inode = __ROOT_INODE.take().unwrap(); - drop(old_root_inode); - // 设置全局的新的ROOT Inode + let old_root_inode = __ROOT_INODE.take().unwrap(); __ROOT_INODE = Some(new_root_inode); + + // 把上述文件系统,迁移到新的文件系统下 + do_migrate(ROOT_INODE(), "proc", proc)?; + do_migrate(ROOT_INODE(), "dev", dev)?; + do_migrate(ROOT_INODE(), "sys", sys)?; + + // drop旧的Root inode + drop(old_root_inode); } + MountList::insert("/", &ROOT_INODE().fs().downcast_arc::().unwrap()); kinfo!("VFS: Migrate filesystems done!"); return Ok(()); @@ -178,21 +179,20 @@ pub fn mount_root_fs() -> Result<(), SystemError> { } /// @brief 创建文件/文件夹 -pub fn do_mkdir(path: &str, _mode: FileMode) -> Result { - let path = path.trim(); - +pub fn do_mkdir(path: &Path, _mode: FileMode) -> Result { let inode: Result, SystemError> = ROOT_INODE().lookup(path); if let Err(errno) = inode { // 文件不存在,且需要创建 if errno == SystemError::ENOENT { - let (filename, parent_path) = rsplit_path(path); + let file_name = path.file_name().unwrap(); + let parent_path = path.parent(); // 查找父目录 let parent_inode: Arc = - ROOT_INODE().lookup(parent_path.unwrap_or("/"))?; + ROOT_INODE().lookup(parent_path.unwrap_or(Path::new("/")))?; // 创建文件夹 let _create_inode: Arc = parent_inode.create( - filename, + file_name, FileType::Dir, ModeType::from_bits_truncate(0o755), )?; @@ -206,44 +206,45 @@ pub fn do_mkdir(path: &str, _mode: FileMode) -> Result { } /// @brief 删除文件夹 -pub fn do_remove_dir(dirfd: i32, path: &str) -> Result { - let path = path.trim(); - +pub fn do_remove_dir(dirfd: i32, path: &Path) -> Result { let pcb = ProcessManager::current_pcb(); let (inode_begin, remain_path) = user_path_at(&pcb, dirfd, path)?; - let (filename, parent_path) = rsplit_path(&remain_path); + let file_name = remain_path.file_name().unwrap(); + let parent_path = remain_path.parent(); - // 最后一项文件项为.时返回EINVAL - if filename == "." { - return Err(SystemError::EINVAL); - } + // 不再需要下列判断,挪到上级处理 + // // 最后一项文件项为.时返回EINVAL + // if file_name == "." { + // return Err(SystemError::EINVAL); + // } // 查找父目录 - let parent_inode: Arc = inode_begin - .lookup_follow_symlink(parent_path.unwrap_or("/"), VFS_MAX_FOLLOW_SYMLINK_TIMES)?; + let parent_inode: Arc = inode_begin.lookup_follow_symlink( + parent_path.unwrap_or(Path::new("/")), + VFS_MAX_FOLLOW_SYMLINK_TIMES, + )?; if parent_inode.metadata()?.file_type != FileType::Dir { return Err(SystemError::ENOTDIR); } // 在目标点为symlink时也返回ENOTDIR - let target_inode = parent_inode.find(filename)?; + let target_inode = parent_inode.find(file_name)?; if target_inode.metadata()?.file_type != FileType::Dir { return Err(SystemError::ENOTDIR); } // 删除文件夹 - parent_inode.rmdir(filename)?; + parent_inode.rmdir(file_name)?; return Ok(0); } /// @brief 删除文件 -pub fn do_unlink_at(dirfd: i32, path: &str) -> Result { - let path = path.trim(); - +pub fn do_unlink_at(dirfd: i32, path: &Path) -> Result { let pcb = ProcessManager::current_pcb(); - let (inode_begin, remain_path) = user_path_at(&pcb, dirfd, path)?; + let tmp = user_path_at(&pcb, dirfd, path); + let (inode_begin, remain_path) = tmp?; let inode: Result, SystemError> = inode_begin.lookup_follow_symlink(&remain_path, VFS_MAX_FOLLOW_SYMLINK_TIMES); @@ -259,25 +260,29 @@ pub fn do_unlink_at(dirfd: i32, path: &str) -> Result { return Err(SystemError::EPERM); } - let (filename, parent_path) = rsplit_path(path); + // let (filename, parent_path) = rsplit_path(path); + let file_name = path.file_name(); + let parent_path = path.parent(); // 查找父目录 - let parent_inode: Arc = inode_begin - .lookup_follow_symlink(parent_path.unwrap_or("/"), VFS_MAX_FOLLOW_SYMLINK_TIMES)?; + let parent_inode: Arc = inode_begin.lookup_follow_symlink( + parent_path.unwrap_or(Path::new("/")), + VFS_MAX_FOLLOW_SYMLINK_TIMES, + )?; if parent_inode.metadata()?.file_type != FileType::Dir { return Err(SystemError::ENOTDIR); } // 删除文件 - parent_inode.unlink(filename)?; + parent_inode.unlink(file_name.unwrap())?; return Ok(0); } // @brief mount filesystem -pub fn do_mount(fs: Arc, mount_point: &str) -> Result { +pub fn do_mount(fs: Arc, mount_point: &Path) -> Result { ROOT_INODE() .lookup_follow_symlink(mount_point, VFS_MAX_FOLLOW_SYMLINK_TIMES)? - .mount(fs)?; + .mount(fs.clone())?; Ok(0) } diff --git a/kernel/src/filesystem/vfs/mod.rs b/kernel/src/filesystem/vfs/mod.rs index 6a6a805ad..1314b6737 100644 --- a/kernel/src/filesystem/vfs/mod.rs +++ b/kernel/src/filesystem/vfs/mod.rs @@ -9,6 +9,7 @@ mod utils; use ::core::{any::Any, fmt::Debug, sync::atomic::AtomicUsize}; use alloc::{string::String, sync::Arc, vec::Vec}; use intertrait::CastFromSync; +use path_base::{clean_path::Clean, Path, PathBuf}; use system_error::SystemError; use crate::{ @@ -23,8 +24,16 @@ use crate::{ time::PosixTimeSpec, }; -use self::{core::generate_inode_id, file::FileMode, syscall::ModeType}; -pub use self::{core::ROOT_INODE, file::FilePrivateData, mount::MountFS}; +use self::{ + core::generate_inode_id, + file::FileMode, + syscall::ModeType, +}; +pub use self::{ + core::ROOT_INODE, + file::FilePrivateData, + mount::{utils::MountList, MountFS, dcache::DCache}, +}; /// vfs容许的最大的路径名称长度 pub const MAX_PATHLEN: usize = 1024; @@ -129,7 +138,7 @@ pub trait IndexNode: Any + Sync + Send + Debug + CastFromSync { _mode: &FileMode, ) -> Result<(), SystemError> { // 若文件系统没有实现此方法,则返回“不支持” - return Err(SystemError::EOPNOTSUPP_OR_ENOTSUP); + return Err(SystemError::ENOSYS); } /// @brief 关闭文件 @@ -138,7 +147,7 @@ pub trait IndexNode: Any + Sync + Send + Debug + CastFromSync { /// 失败:Err(错误码) fn close(&self, _data: SpinLockGuard) -> Result<(), SystemError> { // 若文件系统没有实现此方法,则返回“不支持” - return Err(SystemError::EOPNOTSUPP_OR_ENOTSUP); + return Err(SystemError::ENOSYS); } /// @brief 在inode的指定偏移量开始,读取指定大小的数据 @@ -180,7 +189,7 @@ pub trait IndexNode: Any + Sync + Send + Debug + CastFromSync { /// @return PollStatus结构体 fn poll(&self, _private_data: &FilePrivateData) -> Result { // 若文件系统没有实现此方法,则返回“不支持” - return Err(SystemError::EOPNOTSUPP_OR_ENOTSUP); + return Err(SystemError::ENOSYS); } /// @brief 获取inode的元数据 @@ -189,7 +198,7 @@ pub trait IndexNode: Any + Sync + Send + Debug + CastFromSync { /// 失败:Err(错误码) fn metadata(&self) -> Result { // 若文件系统没有实现此方法,则返回“不支持” - return Err(SystemError::EOPNOTSUPP_OR_ENOTSUP); + return Err(SystemError::ENOSYS); } /// @brief 设置inode的元数据 @@ -198,7 +207,7 @@ pub trait IndexNode: Any + Sync + Send + Debug + CastFromSync { /// 失败:Err(错误码) fn set_metadata(&self, _metadata: &Metadata) -> Result<(), SystemError> { // 若文件系统没有实现此方法,则返回“不支持” - return Err(SystemError::EOPNOTSUPP_OR_ENOTSUP); + return Err(SystemError::ENOSYS); } /// @brief 重新设置文件的大小 @@ -210,7 +219,7 @@ pub trait IndexNode: Any + Sync + Send + Debug + CastFromSync { /// 失败:Err(错误码) fn resize(&self, _len: usize) -> Result<(), SystemError> { // 若文件系统没有实现此方法,则返回“不支持” - return Err(SystemError::EOPNOTSUPP_OR_ENOTSUP); + return Err(SystemError::ENOSYS); } /// @brief 在当前目录下创建一个新的inode @@ -227,7 +236,7 @@ pub trait IndexNode: Any + Sync + Send + Debug + CastFromSync { file_type: FileType, mode: ModeType, ) -> Result, SystemError> { - // 若文件系统没有实现此方法,则默认调用其create_with_data方法。如果仍未实现,则会得到一个Err(-EOPNOTSUPP_OR_ENOTSUP)的返回值 + // 若文件系统没有实现此方法,则默认调用其create_with_data方法。如果仍未实现,则会得到一个Err(-ENOSYS)的返回值 return self.create_with_data(name, file_type, mode, 0); } @@ -248,7 +257,7 @@ pub trait IndexNode: Any + Sync + Send + Debug + CastFromSync { _data: usize, ) -> Result, SystemError> { // 若文件系统没有实现此方法,则返回“不支持” - return Err(SystemError::EOPNOTSUPP_OR_ENOTSUP); + return Err(SystemError::ENOSYS); } /// @brief 在当前目录下,创建一个名为Name的硬链接,指向另一个IndexNode @@ -260,7 +269,7 @@ pub trait IndexNode: Any + Sync + Send + Debug + CastFromSync { /// 失败:Err(错误码) fn link(&self, _name: &str, _other: &Arc) -> Result<(), SystemError> { // 若文件系统没有实现此方法,则返回“不支持” - return Err(SystemError::EOPNOTSUPP_OR_ENOTSUP); + return Err(SystemError::ENOSYS); } /// @brief 在当前目录下,删除一个名为Name的硬链接 @@ -271,7 +280,7 @@ pub trait IndexNode: Any + Sync + Send + Debug + CastFromSync { /// 失败:Err(错误码) fn unlink(&self, _name: &str) -> Result<(), SystemError> { // 若文件系统没有实现此方法,则返回“不支持” - return Err(SystemError::EOPNOTSUPP_OR_ENOTSUP); + return Err(SystemError::ENOSYS); } /// @brief 删除文件夹 @@ -281,10 +290,11 @@ pub trait IndexNode: Any + Sync + Send + Debug + CastFromSync { /// @return 成功 Ok(()) /// @return 失败 Err(错误码) fn rmdir(&self, _name: &str) -> Result<(), SystemError> { - return Err(SystemError::EOPNOTSUPP_OR_ENOTSUP); + return Err(SystemError::ENOSYS); } - /// @brief 将指定名称的子目录项的文件内容,移动到target这个目录下。如果_old_name所指向的inode与_target的相同,那么则直接执行重命名的操作。 + /// @brief 将指定名称的子目录项的文件内容,移动到target这个目录下。 + /// 如果_old_name所指向的inode与_target的相同,那么则直接执行重命名的操作。 /// /// @param old_name 旧的名字 /// @@ -301,7 +311,7 @@ pub trait IndexNode: Any + Sync + Send + Debug + CastFromSync { _new_name: &str, ) -> Result<(), SystemError> { // 若文件系统没有实现此方法,则返回“不支持” - return Err(SystemError::EOPNOTSUPP_OR_ENOTSUP); + return Err(SystemError::ENOSYS); } /// # 修改文件名 @@ -318,7 +328,7 @@ pub trait IndexNode: Any + Sync + Send + Debug + CastFromSync { /// fn rename(&self, _old_name: &str, _new_name: &str) -> Result<(), SystemError> { // 若文件系统没有实现此方法,则返回“不支持” - return Err(SystemError::EOPNOTSUPP_OR_ENOTSUP); + return Err(SystemError::ENOSYS); } /// @brief 寻找一个名为Name的inode @@ -329,7 +339,7 @@ pub trait IndexNode: Any + Sync + Send + Debug + CastFromSync { /// 失败:Err(错误码) fn find(&self, _name: &str) -> Result, SystemError> { // 若文件系统没有实现此方法,则返回“不支持” - return Err(SystemError::EOPNOTSUPP_OR_ENOTSUP); + return Err(SystemError::ENOSYS); } /// @brief 根据inode号,获取子目录项的名字 @@ -340,7 +350,7 @@ pub trait IndexNode: Any + Sync + Send + Debug + CastFromSync { /// 失败:Err(错误码) fn get_entry_name(&self, _ino: InodeId) -> Result { // 若文件系统没有实现此方法,则返回“不支持” - return Err(SystemError::EOPNOTSUPP_OR_ENOTSUP); + return Err(SystemError::ENOSYS); } /// @brief 根据inode号,获取子目录项的名字和元数据 @@ -370,7 +380,7 @@ pub trait IndexNode: Any + Sync + Send + Debug + CastFromSync { _private_data: &FilePrivateData, ) -> Result { // 若文件系统没有实现此方法,则返回“不支持” - return Err(SystemError::EOPNOTSUPP_OR_ENOTSUP); + return Err(SystemError::ENOSYS); } /// @brief 获取inode所在的文件系统的指针 @@ -386,14 +396,14 @@ pub trait IndexNode: Any + Sync + Send + Debug + CastFromSync { /// @brief 在当前Inode下,挂载一个新的文件系统 /// 请注意!该函数只能被MountFS实现,其他文件系统不应实现这个函数 fn mount(&self, _fs: Arc) -> Result, SystemError> { - return Err(SystemError::EOPNOTSUPP_OR_ENOTSUP); + return Err(SystemError::ENOSYS); } /// @brief 截断当前inode到指定的长度。如果当前文件长度小于len,则不操作。 /// /// @param len 要被截断到的目标长度 fn truncate(&self, _len: usize) -> Result<(), SystemError> { - return Err(SystemError::EOPNOTSUPP_OR_ENOTSUP); + return Err(SystemError::ENOSYS); } /// @brief 将当前inode的内容同步到具体设备上 @@ -410,13 +420,26 @@ pub trait IndexNode: Any + Sync + Send + Debug + CastFromSync { _mode: ModeType, _dev_t: DeviceNumber, ) -> Result, SystemError> { - return Err(SystemError::EOPNOTSUPP_OR_ENOTSUP); + return Err(SystemError::ENOSYS); } /// ## 返回特殊文件的inode fn special_node(&self) -> Option { None } + + /// 获取绝对路径,由MountFS实现 + fn abs_path(&self) -> Result { + return Err(SystemError::ENOSYS); + } + + /// 获取目录名 + /// + /// 应由持久化储存的文件系统实现,以下文件系统应实现 + /// - fat + fn entry_name(&self) -> Result { + return Err(SystemError::ENOSYS); + } } impl DowncastArc for dyn IndexNode { @@ -432,24 +455,81 @@ impl dyn IndexNode { return self.as_any_ref().downcast_ref::(); } - /// @brief 查找文件(不考虑符号链接) + /// 查找文件,返回找到的节点 /// - /// @param path 文件路径 + /// - path: 路径 /// - /// @return Ok(Arc) 要寻找的目录项的inode - /// @return Err(SystemError) 错误码 - pub fn lookup(&self, path: &str) -> Result, SystemError> { - return self.lookup_follow_symlink(path, 0); + /// # Err + /// - SystemError::ENOENT: 未找到 + /// - SystemError::ENOTDIR: 不是目录 + pub fn lookup(&self, path: &Path) -> Result, SystemError> { + self.lookup_follow_symlink(path, 0) + } + + /// 查找文件(考虑符号链接),返回找到的节点 + /// + /// - path: 路径 + /// - max_follow_times: 最大跟随次数 + /// + /// # Err + /// - SystemError::ENOENT: 未找到 + /// - SystemError::ENOTDIR: 不是目录 + pub fn lookup_follow_symlink>( + &self, + path_any: T, + max_follow_times: usize, + ) -> Result, SystemError> { + // kdebug!("Looking for path {:?}", path); + // 拼接绝对路径 + let path = path_any.as_ref(); + let abs_path = if path.is_absolute() { + path.clean() + } else { + self.abs_path()?.join(path.clean()) + }; + + // 检查根目录缓存 + if let Some((root_path, path_under, fs)) = MountList::get(&abs_path) { + let root_inode = fs.mountpoint_root_inode(); + + if path_under.iter().next().is_none() { + return Ok(root_inode); + } + + // Cache record is found + if let Some((inode, found_path)) = fs.dcache.quick_lookup(&abs_path, &root_path) { + if let Ok(rest_path) = abs_path.strip_prefix(found_path) { + // no path rest + if rest_path.iter().next().is_none() { + return Ok(inode); + } + // path rest + return inode.lookup_walk(rest_path, max_follow_times); + } else { + kwarn!("Unexpected here"); + } + } + + // Cache record not found + return root_inode.lookup_walk(&path_under, max_follow_times); + } + + // Exception Normal lookup, for non-cache fs + kwarn!("Error search to end of lookup follow symlink"); + return self._lookup_follow_symlink(path.as_os_str(), max_follow_times); } - /// @brief 查找文件(考虑符号链接) + /// 无dcache的lookup方法,返回找到的节点 + /// + /// 旧有的跟随lookup方法,不使用dcache /// - /// @param path 文件路径 - /// @param max_follow_times 最大经过的符号链接的大小 + /// - path: 路径 + /// - max_follow_times: 最大跟随次数 /// - /// @return Ok(Arc) 要寻找的目录项的inode - /// @return Err(SystemError) 错误码 - pub fn lookup_follow_symlink( + /// # Err + /// - `ENOENT`: 未找到 + /// - `ENOTDIR`: 不是目录 + fn _lookup_follow_symlink( &self, path: &str, max_follow_times: usize, @@ -516,13 +596,12 @@ impl dyn IndexNode { let new_path = link_path + "/" + &rest_path; // 继续查找符号链接 - return result.lookup_follow_symlink(&new_path, max_follow_times - 1); - } else { - result = inode; + return result._lookup_follow_symlink(&new_path, max_follow_times - 1); } + result = inode; } - return Ok(result); + Ok(result) } } @@ -653,7 +732,7 @@ bitflags! { /// @brief 所有文件系统都应该实现的trait pub trait FileSystem: Any + Sync + Send + Debug { - /// @brief 获取当前文件系统的root inode的指针 + /// @brief 获取当前(self所指)文件系统的root inode的指针,默认为MountFS fn root_inode(&self) -> Arc; /// @brief 获取当前文件系统的信息 diff --git a/kernel/src/filesystem/vfs/mount.rs b/kernel/src/filesystem/vfs/mount.rs index 24ed20dc4..6c64b0e64 100644 --- a/kernel/src/filesystem/vfs/mount.rs +++ b/kernel/src/filesystem/vfs/mount.rs @@ -1,3 +1,6 @@ +pub mod utils; +pub mod dcache; + use core::{ any::Any, sync::atomic::{compiler_fence, Ordering}, @@ -5,20 +8,26 @@ use core::{ use alloc::{ collections::BTreeMap, + string::String, sync::{Arc, Weak}, + vec::Vec, }; + +use hashbrown::HashSet; +use path_base::PathBuf; use system_error::SystemError; use crate::{ driver::base::device::device_number::DeviceNumber, - libs::spinlock::{SpinLock, SpinLockGuard}, + libs::{rwlock::RwLock, spinlock::{SpinLock, SpinLockGuard}, casting::DowncastArc}, }; use super::{ - file::FileMode, syscall::ModeType, FilePrivateData, FileSystem, FileType, IndexNode, InodeId, - Magic, SuperBlock, + file::FileMode, syscall::ModeType, utils::Key, DCache, FilePrivateData, FileSystem, FileType, IndexNode, InodeId, Magic, SuperBlock, ROOT_INODE }; +use self::utils::{MountList, MountNameCmp}; + const MOUNTFS_BLOCK_SIZE: u64 = 512; const MOUNTFS_MAX_NAMELEN: u64 = 64; /// @brief 挂载文件系统 @@ -33,8 +42,17 @@ pub struct MountFS { self_mountpoint: Option>, /// 指向当前MountFS的弱引用 self_ref: Weak, + + self_root_inode: Weak, + + pub dcache: DCache, } +/// # Behavior +/// - parent 指向父节点,倘若当前节点为当前文件系统的目录根,则为Weak::new() +/// - name 目录项名称,在create/move_to 时 创建/更改,若当前节点为当前文件系统的目录根,则为String::new() +/// - children 在create/find 时 创建/更改 +/// - key 当获取节点时,对于挂载点,以父文件系统的挂载点名字,内容为挂载点下文件系统具体内容 /// @brief MountFS的Index Node 注意,这个IndexNode只是一个中间层。它的目的是将具体文件系统的Inode与挂载机制连接在一起。 #[derive(Debug)] #[cast_to([sync] IndexNode)] @@ -45,6 +63,12 @@ pub struct MountFSInode { mount_fs: Arc, /// 指向自身的弱引用 self_ref: Weak, + + name: Arc, + + parent: Weak, + + children: Arc>>>, } impl MountFS { @@ -52,18 +76,36 @@ impl MountFS { inner_fs: Arc, self_mountpoint: Option>, ) -> Arc { - return MountFS { - inner_filesystem: inner_fs, - mountpoints: SpinLock::new(BTreeMap::new()), - self_mountpoint, - self_ref: Weak::default(), - } - .wrap(); + let mut ret = Arc::new_cyclic(|fs_ref| + MountFS { + inner_filesystem: inner_fs.clone(), + mountpoints: SpinLock::new(BTreeMap::new()), + self_mountpoint, + self_ref: fs_ref.clone(), + self_root_inode: Weak::new(), + dcache: DCache::new(), + } + ); + let root = Arc::new_cyclic( |node_self| { + MountFSInode { + inner_inode: inner_fs.root_inode(), + mount_fs: ret.clone(), + self_ref: node_self.clone(), + name: Arc::default(), + parent: Weak::new(), + children: Arc::new(RwLock::new(HashSet::new())), + } + }); + ret.dcache.put(&root); + unsafe { Arc::get_mut_unchecked(&mut ret).self_root_inode = Arc::downgrade(&root); } + return ret; } /// @brief 用Arc指针包裹MountFS对象。 /// 本函数的主要功能为,初始化MountFS对象中的自引用Weak指针 /// 本函数只应在构造器中被调用 + #[allow(dead_code)] + #[deprecated] fn wrap(self) -> Arc { // 创建Arc指针 let mount_fs: Arc = Arc::new(self); @@ -81,12 +123,7 @@ impl MountFS { /// @brief 获取挂载点的文件系统的root inode pub fn mountpoint_root_inode(&self) -> Arc { - return MountFSInode { - inner_inode: self.inner_filesystem.root_inode(), - mount_fs: self.self_ref.upgrade().unwrap(), - self_ref: Weak::default(), - } - .wrap(); + return self.self_root_inode.upgrade().unwrap(); } pub fn inner_filesystem(&self) -> Arc { @@ -102,6 +139,8 @@ impl MountFSInode { /// @brief 用Arc指针包裹MountFSInode对象。 /// 本函数的主要功能为,初始化MountFSInode对象中的自引用Weak指针 /// 本函数只应在构造器中被调用 + #[allow(dead_code)] + #[deprecated] fn wrap(self) -> Arc { // 创建Arc指针 let inode: Arc = Arc::new(self); @@ -159,6 +198,150 @@ impl MountFSInode { pub(super) fn inode_id(&self) -> InodeId { self.metadata().map(|x| x.inode_id).unwrap() } + + pub fn _find(&self, name: &str) -> Result, SystemError> { + match name { + // 查找的是当前目录 + "" | "." => return Ok(self.self_ref.upgrade().unwrap()), + // 往父级查找 + ".." => { + return self._parent(); + } + // 在当前目录下查找 + _ => { + // 直接调用当前inode所在的文件系统的find方法进行查找 + // 由于向下查找可能会跨越文件系统的边界,因此需要尝试替换inode + let inner_inode = self.inner_inode.find(name)?; + let ret = Arc::new_cyclic(|self_ref| MountFSInode { + inner_inode, + mount_fs: self.mount_fs.clone(), + self_ref: self_ref.clone(), + name: Arc::new(String::from(name)), + parent: self.self_ref.clone(), + children: Arc::new(RwLock::new(HashSet::new())), + }).overlaid_inode(); + // 按道理加入挂载点后,这里不需要替换inode + self.children.write(); + self.mount_fs.dcache.put(&ret); + return Ok(ret); + } + } + } + + /// 退化到文件系统中递归查找文件, 并将找到的目录项缓存到dcache中, 返回找到的节点 + /// + /// - rest_path: 剩余路径 + /// - max_follow_times: 最大跟随次数 + /// ## Err + /// - SystemError::ENOENT: 未找到 + pub(super) fn lookup_walk( + &self, + rest_path: &path_base::Path, + max_follow_times: usize, + ) -> Result, SystemError> { + // kdebug!("walking though {:?}", rest_path); + if self.metadata()?.file_type != FileType::Dir { + return Err(SystemError::ENOTDIR); + } + let child = rest_path.iter().next().unwrap(); + // kdebug!("Lookup {:?}", child); + match self._find(child) { + Ok(child_node) => { + if child_node.metadata()?.file_type == FileType::SymLink && max_follow_times > 0 { + // symlink wrapping problem + return ROOT_INODE().lookup_follow_symlink( + child_node.convert_symlink()?.join(rest_path), + max_follow_times - 1, + ); + } + + self.mount_fs.dcache.put(&child_node); + + if let Ok(rest) = rest_path.strip_prefix(child) { + if rest.iter().next().is_some() { + // kdebug!("Rest {:?}", rest); + return child_node.lookup_walk(rest, max_follow_times); + } + } + // kdebug!("return node {:?}", child_node); + + return Ok(child_node); + } + Err(e) => { + return Err(e); + } + } + } + + /// 将符号链接转换为路径 + fn convert_symlink(&self) -> Result { + let mut content = [0u8; 256]; + let len = self.read_at( + 0, + 256, + &mut content, + SpinLock::new(FilePrivateData::Unused).lock(), + )?; + + return Ok(PathBuf::from( + ::core::str::from_utf8(&content[..len]).map_err(|_| SystemError::ENOTDIR)?, + )); + } + + fn _parent(&self) -> Result, SystemError> { + if self.is_mountpoint_root()? { + match &self.mount_fs.self_mountpoint { + Some(inode) => { + return inode._parent(); + } + None => { + return self.self_ref.upgrade().ok_or(SystemError::ENOENT); + } + } + } + + return Ok(self.parent.upgrade().unwrap_or(self.self_ref.upgrade().unwrap())); + } + + fn _create( + &self, + name: Arc, + inner: Arc, + ) -> Arc { + let ret = Arc::new_cyclic(|self_ref| MountFSInode { + inner_inode: inner.clone(), + mount_fs: self.mount_fs.clone(), + self_ref: self_ref.clone(), + name: name.clone(), + parent: self.self_ref.clone(), + children: Arc::new(RwLock::new(HashSet::new())), + }); + + self.children.write().insert(Key::Inner(MountNameCmp(Arc::downgrade(&ret)))); + self.mount_fs.dcache.put(&ret); + + return ret; + } + + fn _link(&self, name: Arc, other: Arc) -> Result<(), SystemError> { + if let Some(target) = other.downcast_arc::() { + + let to_link = Arc::new_cyclic(|self_ref| MountFSInode { + inner_inode: target.inner_inode.clone(), + mount_fs: self.mount_fs.clone(), + self_ref: self_ref.clone(), + name: name.clone(), + parent: self.self_ref.clone(), + children: target.children.clone(), + }); + + self.children.write().insert(Key::Inner(MountNameCmp(Arc::downgrade(&to_link)))); + self.mount_fs.dcache.put(&target); + + return Ok(()); + } + return Err(SystemError::EINVAL); + } } impl IndexNode for MountFSInode { @@ -181,14 +364,20 @@ impl IndexNode for MountFSInode { mode: ModeType, data: usize, ) -> Result, SystemError> { - return Ok(MountFSInode { - inner_inode: self - .inner_inode - .create_with_data(name, file_type, mode, data)?, + let inner_inode = self.inner_inode.create_with_data(name, file_type, mode, data)?; + let ret = Arc::new_cyclic(|self_ref| MountFSInode { + inner_inode, mount_fs: self.mount_fs.clone(), - self_ref: Weak::default(), - } - .wrap()); + self_ref: self_ref.clone(), + name: Arc::new(String::from(name)), + parent: self.self_ref.clone(), + children: Arc::new(RwLock::new(HashSet::new())), + }); + + self.children.write().insert(Key::Inner(MountNameCmp(Arc::downgrade(&ret)))); + self.mount_fs.dcache.put(&ret); + + return Ok(ret); } fn truncate(&self, len: usize) -> Result<(), SystemError> { @@ -247,29 +436,47 @@ impl IndexNode for MountFSInode { file_type: FileType, mode: ModeType, ) -> Result, SystemError> { - return Ok(MountFSInode { - inner_inode: self.inner_inode.create(name, file_type, mode)?, + let inner_inode = self.inner_inode.create(name, file_type, mode)?; + let ret = Arc::new_cyclic(|self_ref| MountFSInode { + inner_inode, mount_fs: self.mount_fs.clone(), - self_ref: Weak::default(), - } - .wrap()); + self_ref: self_ref.clone(), + name: Arc::new(String::from(name)), + parent: self.self_ref.clone(), + children: Arc::new(RwLock::new(HashSet::new())), + }); + + self.children.write().insert(Key::Inner(MountNameCmp(Arc::downgrade(&ret)))); + self.mount_fs.dcache.put(&ret); + + return Ok(ret); } fn link(&self, name: &str, other: &Arc) -> Result<(), SystemError> { - return self.inner_inode.link(name, other); + self.inner_inode.link(name, other)?; + self._link(Arc::new(String::from(name)), other.clone())?; + return Ok(()); } /// @brief 在挂载文件系统中删除文件/文件夹 #[inline] fn unlink(&self, name: &str) -> Result<(), SystemError> { - let inode_id = self.inner_inode.find(name)?.metadata()?.inode_id; + // kdebug!("Call Mountfs unlink: Item {}", name); + let inode_id = self.find(name)?.metadata()?.inode_id; // 先检查这个inode是否为一个挂载点,如果当前inode是一个挂载点,那么就不能删除这个inode if self.mount_fs.mountpoints.lock().contains_key(&inode_id) { return Err(SystemError::EBUSY); } + // 调用内层的inode的方法来删除这个inode - return self.inner_inode.unlink(name); + if let Err(err) = self.inner_inode.unlink(name) { + return Err(err); + } + + self.children.write().remove(&Key::Cmp(Arc::new(String::from(name)))); + + return Ok(()); } #[inline] @@ -281,9 +488,13 @@ impl IndexNode for MountFSInode { return Err(SystemError::EBUSY); } // 调用内层的rmdir的方法来删除这个inode - let r = self.inner_inode.rmdir(name); + if let Err(e) = self.inner_inode.rmdir(name) { + return Err(e); + } - return r; + self.children.write().remove(&Key::Cmp(Arc::new(String::from(name)))); + + return Ok(()); } #[inline] @@ -293,48 +504,21 @@ impl IndexNode for MountFSInode { target: &Arc, new_name: &str, ) -> Result<(), SystemError> { - return self.inner_inode.move_to(old_name, target, new_name); + self.children.write().remove(&Key::Cmp(Arc::new(String::from(old_name)))); + let to_move = self.find(old_name)?; + if let Some(target1) = target.clone().downcast_arc::() { + + target1._link(Arc::new(String::from(new_name)), to_move)?; + self.children.write().remove(&Key::Cmp(Arc::new(String::from(old_name)))); + + self.inner_inode.move_to(old_name, target, new_name)?; + return Ok(()); + } + return Err(SystemError::EINVAL); } fn find(&self, name: &str) -> Result, SystemError> { - match name { - // 查找的是当前目录 - "" | "." => return Ok(self.self_ref.upgrade().unwrap()), - // 往父级查找 - ".." => { - if self.is_mountpoint_root()? { - // 当前inode是它所在的文件系统的root inode - match &self.mount_fs.self_mountpoint { - Some(inode) => { - return inode.find(name); - } - None => { - return Ok(self.self_ref.upgrade().unwrap()); - } - } - } else { - // 向上查找时,不会跨过文件系统的边界,因此直接调用当前inode所在的文件系统的find方法进行查找 - return Ok(MountFSInode { - inner_inode: self.inner_inode.find(name)?, - mount_fs: self.mount_fs.clone(), - self_ref: Weak::default(), - } - .wrap()); - } - } - // 在当前目录下查找 - _ => { - // 直接调用当前inode所在的文件系统的find方法进行查找 - // 由于向下查找可能会跨越文件系统的边界,因此需要尝试替换inode - return Ok(MountFSInode { - inner_inode: self.inner_inode.find(name)?, - mount_fs: self.mount_fs.clone(), - self_ref: Weak::default(), - } - .wrap() - .overlaid_inode()); - } - } + return Ok(self._find(name)?); } #[inline] @@ -360,8 +544,23 @@ impl IndexNode for MountFSInode { return self.inner_inode.ioctl(cmd, data, private_data); } + // Todo: 缓存当前目录下的目录项 #[inline] fn list(&self) -> Result, SystemError> { + // let mut ret: Vec = Vec::new(); + // ret.push(String::from(".")); + // ret.push(String::from("..")); + // let mut ext = self + // .children + // .read() + // .iter() + // .map(|item| { + // (*item.unwrap()).clone() + // }) + // .collect::>(); + // ext.sort(); + // ret.append(&mut ext); + // kdebug!("{:?}", ret); return self.inner_inode.list(); } @@ -377,6 +576,9 @@ impl IndexNode for MountFSInode { // 为新的挂载点创建挂载文件系统 let new_mount_fs: Arc = MountFS::new(fs, Some(self.self_ref.upgrade().unwrap())); self.do_mount(metadata.inode_id, new_mount_fs.clone())?; + + MountList::insert(self.abs_path()?, &new_mount_fs); + return Ok(new_mount_fs); } @@ -387,12 +589,18 @@ impl IndexNode for MountFSInode { mode: ModeType, dev_t: DeviceNumber, ) -> Result, SystemError> { - return Ok(MountFSInode { - inner_inode: self.inner_inode.mknod(filename, mode, dev_t)?, + let inner_inode = self.inner_inode.mknod(filename, mode, dev_t)?; + let ret = Arc::new_cyclic(|self_ref| MountFSInode { + inner_inode, mount_fs: self.mount_fs.clone(), - self_ref: Weak::default(), - } - .wrap()); + self_ref: self_ref.clone(), + name: Arc::new(String::from(filename)), + parent: self.self_ref.clone(), + children: Arc::new(RwLock::new(HashSet::new())), + }); + self.children.write().insert(Key::Inner(MountNameCmp(Arc::downgrade(&ret)))); + self.mount_fs.dcache.put(&ret); + return Ok(ret); } #[inline] @@ -404,15 +612,55 @@ impl IndexNode for MountFSInode { fn poll(&self, private_data: &FilePrivateData) -> Result { self.inner_inode.poll(private_data) } + + #[inline] + fn entry_name(&self) -> Result { + self.inner_inode.entry_name() + } + + fn abs_path(&self) -> Result { + let mut path_stack = Vec::new(); + // kdebug!("Inner: {:?}", self.inner_inode.entry_name()); + path_stack.push(self.name.as_ref().clone()); + + let init = self._parent()?; + if self.metadata()?.inode_id == init.metadata()?.inode_id { + return Ok(PathBuf::from("/")); + } + + let mut inode = init; + path_stack.push(inode.name.as_ref().clone()); + + while inode.metadata()?.inode_id != ROOT_INODE().metadata()?.inode_id { + let tmp = inode._parent()?; + if inode.metadata()?.inode_id == tmp.metadata()?.inode_id { + break; + } + inode = tmp; + path_stack.push(inode.name.as_ref().clone()); + } + + path_stack.reverse(); + let mut path = PathBuf::from("/"); + path.extend(path_stack); + return Ok(path); + } + } impl FileSystem for MountFS { fn root_inode(&self) -> Arc { - match &self.self_mountpoint { - Some(inode) => return inode.mount_fs.root_inode(), + return match &self.self_mountpoint { + Some(inode) => { + // kdebug!("Mount point at {:?}", inode._abs_path()); + inode.mount_fs.root_inode() + } // 当前文件系统是rootfs - None => self.mountpoint_root_inode(), - } + None => { + // kdebug!("Root fs"); + self.mountpoint_root_inode() + } + }; } fn info(&self) -> super::FsInfo { diff --git a/kernel/src/filesystem/vfs/mount/dcache.rs b/kernel/src/filesystem/vfs/mount/dcache.rs new file mode 100644 index 000000000..9770f8092 --- /dev/null +++ b/kernel/src/filesystem/vfs/mount/dcache.rs @@ -0,0 +1,192 @@ +//! 文件系统目录项缓存 +//! Todo: 更改使用的哈希 +use alloc::{collections::LinkedList, string::{String, ToString}, sync::{Arc, Weak}}; + +use core::{ + mem::size_of, +}; + +use path_base::{Path, PathBuf}; +use hashbrown::HashSet; + +use crate::{filesystem::vfs::{utils::{Key, Keyable}, IndexNode}, libs::rwlock::RwLock}; + +use super::MountFSInode; + +#[derive(Debug)] +pub struct DCache { + table: RwLock>>, + lru_list: RwLock>>, + max_size: usize, +} + +#[allow(dead_code)] +const DEFAULT_MEMORY_SIZE: usize = 1024 /* K */ * 1024 /* Byte */; + + +// pub trait DCache { +// /// 创建一个新的目录项缓存 +// fn new(mem_size: Option) -> Self; +// /// 缓存目录项 +// fn put(&self, src: &Arc); +// /// 清除失效目录项,返回清除的数量(可能的usize话) +// fn clean(&self, num: Option<>) -> Option; +// /// 在dcache中快速查找目录项 +// /// - `search_path`: 搜索路径 +// /// - `stop_path`: 停止路径 +// /// - 返回值: 找到的`inode`及其`路径` 或 [`None`] +// fn quick_lookup<'a> ( +// &self, +// search_path: &'a Path, +// stop_path: &'a Path, +// ) -> Option<(Arc, &'a Path)>; +// } + +impl DCache { + pub fn new() -> Self { + DCache { + table: RwLock::new(HashSet::new()), + lru_list: RwLock::new(LinkedList::new()), + max_size: 0, + } + } + + pub fn new_with_max_size(size: usize) -> Self { + DCache { + table: RwLock::new(HashSet::new()), + lru_list: RwLock::new(LinkedList::new()), + max_size: size, + } + } + + pub fn put(&self, src: &Arc) { + self.lru_list.write().push_back(src.clone()); + self.table.write().insert(Key::Inner(Resource(Arc::downgrade(src)))); + if self.max_size != 0 && + self.table.read().len() >= self.max_size + { + self.clean(); + } + } + + pub fn clean(&self) -> usize { + return + self.lru_list.write().extract_if(|elem| + Arc::strong_count(&elem.inner_inode) <= 1 + ) + .count(); + } + + + pub fn quick_lookup<'b>( + &self, + search_path: &'b Path, + stop_path: &'b Path, + ) -> Option<(Arc, &'b Path)> { + if let Some(k) = self.table.read().get(&Key::Cmp(Arc::new(String::from(search_path.as_os_str())))) { + if let Key::Inner(src) = k { + if src.0.strong_count() > 1 { + return Some((src.0.upgrade().unwrap(), search_path)); + } + } + } + if let Some(parent) = search_path.parent() { + if parent == stop_path { + return None; + } + return self.quick_lookup(parent, stop_path); + } + return None; + } +} + +// static mut __DCACHE: Option = None; + +/// Infinite Init +// pub fn init_dcache() { +// unsafe { +// __DCACHE = Some( DCache { +// table: RwLock::new(HashSet::new()), +// lru_list: RwLock::new(LinkedList::new()), +// max_size: 0, +// }) +// } +// } + +// #[inline] +// pub fn dcache_is_uninit() -> bool { +// unsafe { return __DCACHE.is_none(); } +// } + +// #[allow(dead_code)] +// pub fn init_dcache_with_memory_size(mem_size: usize) { +// let max_size = +// mem_size / size_of::>(); +// let hash_table_size = max_size / 7 * 10 /* 0.7 */; +// unsafe { +// __DCACHE = Some( DCache { +// table: RwLock::new(HashSet::with_capacity(hash_table_size)), +// lru_list: RwLock::new(LinkedList::new()), +// max_size, +// }) +// } +// } + +// fn instance() -> &'static DCache { +// unsafe { +// if __DCACHE.is_none() { +// init_dcache(); +// } +// return __DCACHE.as_ref().unwrap(); +// } +// } + +// pub fn put(src: &Arc) { +// instance().lru_list.write().push_back(src.clone()); +// instance().table.write().insert(Key::Inner(Resource(Arc::downgrade(src)))); +// if instance().max_size != 0 && +// instance().table.read().len() >= instance().max_size +// { +// clean(); +// } +// } + +// pub fn clean() -> usize { +// return +// instance().lru_list.write().extract_if(|elem| +// Arc::strong_count(&elem.inner_inode) <= 1 +// ) +// .count(); +// } + +// pub fn quick_lookup<'b>( +// search_path: &'b Path, +// stop_path: &'b Path, +// ) -> Option<(Arc, &'b Path)> { +// if let Some(k) = instance().table.read().get(&Key::Cmp(Arc::new(String::from(search_path.as_os_str())))) { +// if let Key::Inner(src) = k { +// if let Some(inode) = src.0.upgrade() { +// return Some((inode, search_path)); +// } +// } +// } +// if let Some(parent) = search_path.parent() { +// if parent == stop_path { +// return None; +// } +// return quick_lookup(parent, stop_path); +// } +// return None; +// } + +#[derive(Debug)] +struct Resource(Weak); + +impl Keyable for Resource { + fn key(&self) -> Arc { + if let Some(src) = self.0.upgrade() { + return Arc::new(src.abs_path().unwrap().into_os_string()); + } + return Arc::new(String::new()); + } +} diff --git a/kernel/src/filesystem/vfs/mount/utils.rs b/kernel/src/filesystem/vfs/mount/utils.rs new file mode 100644 index 000000000..e1bb6a0f4 --- /dev/null +++ b/kernel/src/filesystem/vfs/mount/utils.rs @@ -0,0 +1,116 @@ +use super::{MountFS, MountFSInode}; +use crate::{filesystem::vfs::utils::Keyable, libs::rwlock::RwLock}; +use alloc::{ + collections::BTreeMap, string::String, sync::{Arc, Weak} +}; +use path_base::{clean_path::Clean, Path, PathBuf}; + +#[derive(PartialEq, Eq, Debug)] +pub struct MountPath(PathBuf); + +impl From<&str> for MountPath { + fn from(value: &str) -> Self { + Self(PathBuf::from(value).clean()) + } +} + +impl From<&Path> for MountPath { + fn from(value: &Path) -> Self { + Self(value.clean()) + } +} + +impl From for MountPath { + fn from(value: PathBuf) -> Self { + Self(value.clean()) + } +} + +impl AsRef for MountPath { + fn as_ref(&self) -> &Path { + &self.0 + } +} + +impl PartialOrd for MountPath { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for MountPath { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + let self_dep = self.0.components().count(); + let othe_dep = other.0.components().count(); + if self_dep == othe_dep { + self.0.cmp(&other.0) + } else { + othe_dep.cmp(&self_dep) + } + } +} + +// 维护一个挂载点的记录,以支持特定于文件系统的索引 +type MountListType = Option>>>>; +pub struct MountList(MountListType); +static mut __MOUNTS_LIST: MountList = MountList(None); + +impl MountList { + /// 初始化挂载点 + pub fn init() { + unsafe { + __MOUNTS_LIST = MountList(Some(Arc::new(RwLock::new(BTreeMap::new())))); + } + } + + fn instance() -> &'static Arc>>> { + unsafe { + if __MOUNTS_LIST.0.is_none() { + MountList::init(); + } + return __MOUNTS_LIST.0.as_ref().unwrap(); + } + } + + /// 在 **路径`path`** 下挂载 **文件系统`fs`** + pub fn insert>(path: T, fs: &Arc) { + MountList::instance() + .write() + .insert(MountPath::from(path.as_ref()), Arc::downgrade(fs)); + } + + /// 获取挂载点信息,返回 + /// + /// - `最近的挂载点` + /// - `挂载点下的路径` + /// - `文件系统` + /// # None + /// 未找到挂载点 + pub fn get>(path: T) -> Option<(PathBuf, PathBuf, Arc)> { + MountList::instance() + .upgradeable_read() + .iter() + .filter_map(|(key, value)| { + let strkey = key.as_ref(); + if let Some(fs) = value.upgrade() { + if let Ok(rest) = path.as_ref().strip_prefix(strkey) { + return Some((strkey.to_path_buf(), rest.to_path_buf(), fs.clone())); + } + } + None + }) + .next() + } +} + +#[derive(Debug)] +pub(super) struct MountNameCmp (pub Weak); + +impl Keyable for MountNameCmp { + fn key(&self) -> Arc { + if let Some(src) = self.0.upgrade() { + return src.name.clone(); + } + return Arc::new(String::new()); + } +} diff --git a/kernel/src/filesystem/vfs/open.rs b/kernel/src/filesystem/vfs/open.rs index 3e2e5660c..701292819 100644 --- a/kernel/src/filesystem/vfs/open.rs +++ b/kernel/src/filesystem/vfs/open.rs @@ -1,4 +1,5 @@ use alloc::sync::Arc; +use path_base::{Path, PathBuf}; use system_error::SystemError; use crate::{ @@ -10,8 +11,8 @@ use super::{ fcntl::AtFlags, file::{File, FileMode}, syscall::{ModeType, OpenHow, OpenHowResolve}, - utils::{rsplit_path, user_path_at}, - FileType, IndexNode, MAX_PATHLEN, ROOT_INODE, VFS_MAX_FOLLOW_SYMLINK_TIMES, + utils::user_path_at, + FileType, IndexNode, MAX_PATHLEN, VFS_MAX_FOLLOW_SYMLINK_TIMES, }; pub(super) fn do_faccessat( @@ -34,24 +35,24 @@ pub(super) fn do_faccessat( // let follow_symlink = flags & AtFlags::AT_SYMLINK_NOFOLLOW.bits() as u32 == 0; - let path = check_and_clone_cstr(path, Some(MAX_PATHLEN))?; + let path = PathBuf::from(check_and_clone_cstr(path, Some(MAX_PATHLEN))?); let (inode, path) = user_path_at(&ProcessManager::current_pcb(), dirfd, &path)?; // 如果找不到文件,则返回错误码ENOENT - let _inode = inode.lookup_follow_symlink(path.as_str(), VFS_MAX_FOLLOW_SYMLINK_TIMES)?; + let _inode = inode.lookup_follow_symlink(&path, VFS_MAX_FOLLOW_SYMLINK_TIMES)?; // todo: 接着完善(可以借鉴linux 6.1.9的do_faccessat) return Ok(0); } pub fn do_fchmodat(dirfd: i32, path: *const u8, _mode: ModeType) -> Result { - let path = check_and_clone_cstr(path, Some(MAX_PATHLEN))?; + let path = PathBuf::from(check_and_clone_cstr(path, Some(MAX_PATHLEN))?); let (inode, path) = user_path_at(&ProcessManager::current_pcb(), dirfd, &path)?; // 如果找不到文件,则返回错误码ENOENT - let _inode = inode.lookup_follow_symlink(path.as_str(), VFS_MAX_FOLLOW_SYMLINK_TIMES)?; + let _inode = inode.lookup_follow_symlink(&path, VFS_MAX_FOLLOW_SYMLINK_TIMES)?; kwarn!("do_fchmodat: not implemented yet\n"); // todo: 真正去改变文件的权限 @@ -61,7 +62,7 @@ pub fn do_fchmodat(dirfd: i32, path: *const u8, _mode: ModeType) -> Result Result { - // kdebug!("open path: {}, how: {:?}", path, how); - let path = path.trim(); - let (inode_begin, path) = user_path_at(&ProcessManager::current_pcb(), dirfd, path)?; let inode: Result, SystemError> = inode_begin.lookup_follow_symlink( &path, @@ -97,13 +95,18 @@ fn do_sys_openat2( && !how.o_flags.contains(FileMode::O_DIRECTORY) && errno == SystemError::ENOENT { - let (filename, parent_path) = rsplit_path(&path); // 查找父目录 - let parent_inode: Arc = - ROOT_INODE().lookup(parent_path.unwrap_or("/"))?; + let parent_inode: Arc = if follow_symlink { + inode_begin.lookup_follow_symlink( + path.parent().unwrap_or(Path::new("/")), + VFS_MAX_FOLLOW_SYMLINK_TIMES, + )? + } else { + inode_begin.lookup(path.parent().unwrap_or(Path::new("/")))? + }; // 创建文件 let inode: Arc = parent_inode.create( - filename, + path.file_name().unwrap(), FileType::File, ModeType::from_bits_truncate(0o755), )?; diff --git a/kernel/src/filesystem/vfs/syscall.rs b/kernel/src/filesystem/vfs/syscall.rs index 292f747e9..faaf45945 100644 --- a/kernel/src/filesystem/vfs/syscall.rs +++ b/kernel/src/filesystem/vfs/syscall.rs @@ -1,8 +1,8 @@ use core::ffi::c_void; use core::mem::size_of; -use alloc::string::ToString; use alloc::{string::String, sync::Arc, vec::Vec}; +use path_base::{clean_path::Clean, Path, PathBuf}; use system_error::SystemError; use crate::producefs; @@ -26,7 +26,7 @@ use super::{ fcntl::{AtFlags, FcntlCommand, FD_CLOEXEC}, file::{File, FileMode}, open::{do_faccessat, do_fchmodat, do_sys_open}, - utils::{rsplit_path, user_path_at}, + utils::user_path_at, Dirent, FileType, IndexNode, FSMAKER, MAX_PATHLEN, ROOT_INODE, VFS_MAX_FOLLOW_SYMLINK_TIMES, }; // use crate::kdebug; @@ -465,7 +465,7 @@ impl Syscall { mode: u32, follow_symlink: bool, ) -> Result { - let path = check_and_clone_cstr(path, Some(MAX_PATHLEN))?; + let path = PathBuf::from(check_and_clone_cstr(path, Some(MAX_PATHLEN))?); let open_flags: FileMode = FileMode::from_bits(o_flags).ok_or(SystemError::EINVAL)?; let mode = ModeType::from_bits(mode).ok_or(SystemError::EINVAL)?; return do_sys_open( @@ -484,7 +484,7 @@ impl Syscall { mode: u32, follow_symlink: bool, ) -> Result { - let path = check_and_clone_cstr(path, Some(MAX_PATHLEN))?; + let path = PathBuf::from(check_and_clone_cstr(path, Some(MAX_PATHLEN))?); let open_flags: FileMode = FileMode::from_bits(o_flags).ok_or(SystemError::EINVAL)?; let mode = ModeType::from_bits(mode).ok_or(SystemError::EINVAL)?; return do_sys_open(dirfd, &path, open_flags, mode, follow_symlink); @@ -663,45 +663,17 @@ impl Syscall { return Err(SystemError::EFAULT); } - let path = check_and_clone_cstr(path, Some(MAX_PATHLEN))?; let proc = ProcessManager::current_pcb(); // Copy path to kernel space to avoid some security issues - let mut new_path = String::from(""); - if !path.is_empty() { - let cwd = match path.as_bytes()[0] { - b'/' => String::from("/"), - _ => proc.basic().cwd(), - }; - let mut cwd_vec: Vec<_> = cwd.split('/').filter(|&x| !x.is_empty()).collect(); - let path_split = path.split('/').filter(|&x| !x.is_empty()); - for seg in path_split { - if seg == ".." { - cwd_vec.pop(); - } else if seg == "." { - // 当前目录 - } else { - cwd_vec.push(seg); - } - } - //proc.basic().set_path(String::from("")); - for seg in cwd_vec { - new_path.push('/'); - new_path.push_str(seg); - } - if new_path.is_empty() { - new_path = String::from("/"); - } - } - let inode = - match ROOT_INODE().lookup_follow_symlink(&new_path, VFS_MAX_FOLLOW_SYMLINK_TIMES) { - Err(_) => { - return Err(SystemError::ENOENT); - } - Ok(i) => i, - }; + let path = PathBuf::from(check_and_clone_cstr(path, Some(MAX_PATHLEN))?); + // path = path.clean(); + // kdebug!("chdir {:?}", path); + let inode = ROOT_INODE() + .lookup_follow_symlink(&path, VFS_MAX_FOLLOW_SYMLINK_TIMES) + .map_err(|_| SystemError::ENOENT)?; let metadata = inode.metadata()?; if metadata.file_type == FileType::Dir { - proc.basic_mut().set_cwd(new_path); + proc.basic_mut().set_cwd(path.into_os_string()); return Ok(0); } else { return Err(SystemError::ENOTDIR); @@ -767,7 +739,7 @@ impl Syscall { /// /// @return uint64_t 负数错误码 / 0表示成功 pub fn mkdir(path: *const u8, mode: usize) -> Result { - let path = check_and_clone_cstr(path, Some(MAX_PATHLEN))?; + let path = PathBuf::from(check_and_clone_cstr(path, Some(MAX_PATHLEN))?); return do_mkdir(&path, FileMode::from_bits_truncate(mode as u32)).map(|x| x as usize); } @@ -784,9 +756,9 @@ impl Syscall { /// pub fn do_linkat( oldfd: i32, - old: &str, + old: &Path, newfd: i32, - new: &str, + new: &Path, flags: AtFlags, ) -> Result { // flag包含其他未规定值时返回EINVAL @@ -802,7 +774,7 @@ impl Syscall { let pcb = ProcessManager::current_pcb(); // 得到源路径的inode - let old_inode: Arc = if old.is_empty() { + let old_inode: Arc = if old.iter().next().is_none() { if flags.contains(AtFlags::AT_EMPTY_PATH) { // 在AT_EMPTY_PATH启用时,old可以为空,old_inode实际为oldfd所指文件,但该文件不能为目录。 let binding = pcb.fd_table(); @@ -827,9 +799,10 @@ impl Syscall { // 得到新创建节点的父节点 let (new_begin_inode, new_remain_path) = user_path_at(&pcb, newfd, new)?; - let (new_name, new_parent_path) = rsplit_path(&new_remain_path); - let new_parent = - new_begin_inode.lookup_follow_symlink(new_parent_path.unwrap_or("/"), symlink_times)?; + let new_name = new_remain_path.file_name().unwrap(); + let new_parent_path = new_remain_path.parent(); + let new_parent = new_begin_inode + .lookup_follow_symlink(new_parent_path.unwrap_or(Path::new("/")), symlink_times)?; // 被调用者利用downcast_ref判断两inode是否为同一文件系统 return new_parent.link(new_name, &old_inode).map(|_| 0); @@ -846,8 +819,8 @@ impl Syscall { } Ok(res) }; - let old = get_path(old)?; - let new = get_path(new)?; + let old = PathBuf::from(get_path(old)?); + let new = PathBuf::from(get_path(new)?); return Self::do_linkat( AtFlags::AT_FDCWD.bits(), &old, @@ -864,13 +837,20 @@ impl Syscall { new: *const u8, flags: i32, ) -> Result { - let old = check_and_clone_cstr(old, Some(MAX_PATHLEN))?; - let new = check_and_clone_cstr(new, Some(MAX_PATHLEN))?; - if old.len() >= MAX_PATHLEN || new.len() >= MAX_PATHLEN { - return Err(SystemError::ENAMETOOLONG); - } + let get_path = |cstr: *const u8| -> Result { + let res = check_and_clone_cstr(cstr, Some(MAX_PATHLEN))?; + if res.len() >= MAX_PATHLEN { + return Err(SystemError::ENAMETOOLONG); + } + if res.is_empty() { + return Err(SystemError::ENOENT); + } + Ok(res) + }; + let old = PathBuf::from(get_path(old)?); + let new = PathBuf::from(get_path(new)?); // old 根据flags & AtFlags::AT_EMPTY_PATH判空 - if new.is_empty() { + if new.iter().next().is_none() { return Err(SystemError::ENOENT); } let flags = AtFlags::from_bits(flags).ok_or(SystemError::EINVAL)?; @@ -889,37 +869,30 @@ impl Syscall { pub fn unlinkat(dirfd: i32, path: *const u8, flags: u32) -> Result { let flags = AtFlags::from_bits(flags as i32).ok_or(SystemError::EINVAL)?; - let path = check_and_clone_cstr(path, Some(MAX_PATHLEN))?; + let path_str = check_and_clone_cstr(path, Some(MAX_PATHLEN))?; - if flags.contains(AtFlags::AT_REMOVEDIR) { - // kdebug!("rmdir"); - match do_remove_dir(dirfd, &path) { - Err(err) => { - return Err(err); - } - Ok(_) => { - return Ok(0); - } - } + if path_str == "." || path_str == ".." { + return Err(SystemError::EINVAL); } - match do_unlink_at(dirfd, &path) { - Err(err) => { - return Err(err); - } - Ok(_) => { - return Ok(0); - } + let path = PathBuf::from(path_str); + + if flags.contains(AtFlags::AT_REMOVEDIR) { + return Ok(do_remove_dir(dirfd, &path)? as usize); } + + do_unlink_at(dirfd, &path)?; + + return Ok(0); } pub fn rmdir(path: *const u8) -> Result { - let path = check_and_clone_cstr(path, Some(MAX_PATHLEN))?; + let path = PathBuf::from(check_and_clone_cstr(path, Some(MAX_PATHLEN))?); return do_remove_dir(AtFlags::AT_FDCWD.bits(), &path).map(|v| v as usize); } pub fn unlink(path: *const u8) -> Result { - let path = check_and_clone_cstr(path, Some(MAX_PATHLEN))?; + let path = PathBuf::from(check_and_clone_cstr(path, Some(MAX_PATHLEN))?); return do_unlink_at(AtFlags::AT_FDCWD.bits(), &path).map(|v| v as usize); } @@ -952,19 +925,28 @@ impl Syscall { if filename_from.len() > MAX_PATHLEN || filename_to.len() > MAX_PATHLEN { return Err(SystemError::ENAMETOOLONG); } + let filename_from = Path::new(&filename_from); + let filename_to = Path::new(&filename_to); //获取pcb,文件节点 let pcb = ProcessManager::current_pcb(); - let (_old_inode_begin, old_remain_path) = user_path_at(&pcb, oldfd, &filename_from)?; - let (_new_inode_begin, new_remain_path) = user_path_at(&pcb, newfd, &filename_to)?; + let (_old_inode_begin, old_remain_path) = user_path_at(&pcb, oldfd, filename_from)?; + let (_new_inode_begin, new_remain_path) = user_path_at(&pcb, newfd, filename_to)?; //获取父目录 - let (old_filename, old_parent_path) = rsplit_path(&old_remain_path); - let old_parent_inode = ROOT_INODE() - .lookup_follow_symlink(old_parent_path.unwrap_or("/"), VFS_MAX_FOLLOW_SYMLINK_TIMES)?; - let (new_filename, new_parent_path) = rsplit_path(&new_remain_path); - let new_parent_inode = ROOT_INODE() - .lookup_follow_symlink(new_parent_path.unwrap_or("/"), VFS_MAX_FOLLOW_SYMLINK_TIMES)?; - old_parent_inode.move_to(old_filename, &new_parent_inode, new_filename)?; + // let (old_filename, old_parent_path) = rsplit_path(&old_remain_path); + let old_file_name = old_remain_path.file_name().unwrap(); + let old_parent_path = old_remain_path.parent(); + let old_parent_inode = ROOT_INODE().lookup_follow_symlink( + old_parent_path.unwrap_or(Path::new("/")), + VFS_MAX_FOLLOW_SYMLINK_TIMES, + )?; + let new_file_name = new_remain_path.file_name().unwrap(); + let new_parent_path = new_remain_path.parent(); + let new_parent_inode = ROOT_INODE().lookup_follow_symlink( + new_parent_path.unwrap_or(Path::new("/")), + VFS_MAX_FOLLOW_SYMLINK_TIMES, + )?; + old_parent_inode.move_to(old_file_name, &new_parent_inode, new_file_name)?; return Ok(0); } @@ -1246,7 +1228,7 @@ impl Syscall { ModeType::empty().bits(), true, )?; - let path = check_and_clone_cstr(path, Some(MAX_PATHLEN)).unwrap(); + let path = PathBuf::from(check_and_clone_cstr(path, Some(MAX_PATHLEN)).unwrap()); let pcb = ProcessManager::current_pcb(); let (_inode_begin, remain_path) = user_path_at(&pcb, fd as i32, &path)?; let inode = ROOT_INODE().lookup_follow_symlink(&remain_path, MAX_PATHLEN)?; @@ -1381,23 +1363,28 @@ impl Syscall { mode: ModeType, dev_t: DeviceNumber, ) -> Result { - let path = check_and_clone_cstr(path, Some(MAX_PATHLEN))?; - let path = path.as_str().trim(); + let path = PathBuf::from(check_and_clone_cstr(path, Some(MAX_PATHLEN))?); + // let path = path.as_str().trim(); + let path = path.clean(); let inode: Result, SystemError> = - ROOT_INODE().lookup_follow_symlink(path, VFS_MAX_FOLLOW_SYMLINK_TIMES); + ROOT_INODE().lookup_follow_symlink(&path, VFS_MAX_FOLLOW_SYMLINK_TIMES); if inode.is_ok() { return Err(SystemError::EEXIST); } - let (filename, parent_path) = rsplit_path(path); + // let (filename, parent_path) = rsplit_path(path); + let file_name = path.file_name().unwrap(); + let parent_path = path.parent(); // 查找父目录 - let parent_inode: Arc = ROOT_INODE() - .lookup_follow_symlink(parent_path.unwrap_or("/"), VFS_MAX_FOLLOW_SYMLINK_TIMES)?; + let parent_inode: Arc = ROOT_INODE().lookup_follow_symlink( + parent_path.unwrap_or(Path::new("/")), + VFS_MAX_FOLLOW_SYMLINK_TIMES, + )?; // 创建nod - parent_inode.mknod(filename, mode, dev_t)?; + parent_inode.mknod(file_name, mode, dev_t)?; return Ok(0); } @@ -1430,13 +1417,13 @@ impl Syscall { user_buf: *mut u8, buf_size: usize, ) -> Result { - let path = check_and_clone_cstr(path, Some(MAX_PATHLEN))?; - let path = path.as_str().trim(); + let path = Path::new(&check_and_clone_cstr(path, Some(MAX_PATHLEN))?).clean(); + let mut user_buf = UserBufferWriter::new(user_buf, buf_size, true)?; - let (inode, path) = user_path_at(&ProcessManager::current_pcb(), dirfd, path)?; + let (inode, path) = user_path_at(&ProcessManager::current_pcb(), dirfd, &path)?; - let inode = inode.lookup(path.as_str())?; + let inode = inode.lookup_follow_symlink(&path, VFS_MAX_FOLLOW_SYMLINK_TIMES)?; if inode.metadata()?.file_type != FileType::SymLink { return Err(SystemError::EINVAL); } @@ -1538,7 +1525,7 @@ impl Syscall { let filesystemtype = producefs!(FSMAKER, filesystemtype)?; - return Vcore::do_mount(filesystemtype, target.to_string().as_str()); + return Vcore::do_mount(filesystemtype, Path::new(&target)); } // 想法:可以在VFS中实现一个文件系统分发器,流程如下: diff --git a/kernel/src/filesystem/vfs/utils.rs b/kernel/src/filesystem/vfs/utils.rs index cd9077c01..326c49067 100644 --- a/kernel/src/filesystem/vfs/utils.rs +++ b/kernel/src/filesystem/vfs/utils.rs @@ -1,7 +1,13 @@ -use alloc::{string::String, sync::Arc}; +use core::{any::Any, cmp::{Ord, Ordering}, fmt::Debug, hash::Hash}; +use alloc::{ + string::{String, ToString}, + sync::Arc, +}; +use intertrait::CastFromSync; +use path_base::{Path, PathBuf}; use system_error::SystemError; -use crate::process::ProcessControlBlock; +use crate::{libs::casting::DowncastArc, process::ProcessControlBlock}; use super::{fcntl::AtFlags, FileType, IndexNode, ROOT_INODE}; @@ -20,6 +26,7 @@ pub fn split_path(path: &str) -> (&str, Option<&str>) { /// @brief 切分路径字符串,返回最右侧那一级的目录名和剩余的部分。 /// /// 举例:对于 /123/456/789/ 本函数返回的第一个值为789, 第二个值为123/456 +#[allow(dead_code)] pub fn rsplit_path(path: &str) -> (&str, Option<&str>) { let mut path_split: core::str::RSplitN<&str> = path.trim_matches('/').rsplitn(2, "/"); let comp = path_split.next().unwrap_or(""); @@ -36,39 +43,111 @@ pub fn rsplit_path(path: &str) -> (&str, Option<&str>) { pub fn user_path_at( pcb: &Arc, dirfd: i32, - path: &str, -) -> Result<(Arc, String), SystemError> { - let mut inode = ROOT_INODE(); - let ret_path; + path: &Path, +) -> Result<(Arc, PathBuf), SystemError> { + if path.is_absolute() { + return Ok((ROOT_INODE(), PathBuf::from(path))); + } + // 如果path不是绝对路径,则需要拼接 - if path.as_bytes()[0] != b'/' { - // 如果dirfd不是AT_FDCWD,则需要检查dirfd是否是目录 - if dirfd != AtFlags::AT_FDCWD.bits() { - let binding = pcb.fd_table(); - let fd_table_guard = binding.read(); - let file = fd_table_guard - .get_file_by_fd(dirfd) - .ok_or(SystemError::EBADF)?; - - // drop guard 以避免无法调度的问题 - drop(fd_table_guard); - - // 如果dirfd不是目录,则返回错误码ENOTDIR - if file.file_type() != FileType::Dir { - return Err(SystemError::ENOTDIR); - } + // 如果dirfd不是AT_FDCWD,则需要检查dirfd是否是目录 + if dirfd != AtFlags::AT_FDCWD.bits() { + let binding = pcb.fd_table(); + let fd_table_guard = binding.read(); + let file = fd_table_guard + .get_file_by_fd(dirfd) + .ok_or(SystemError::EBADF)?; - inode = file.inode(); - ret_path = String::from(path); - } else { - let mut cwd = pcb.basic().cwd(); - cwd.push('/'); - cwd.push_str(path); - ret_path = cwd; + // drop guard 以避免无法调度的问题 + drop(fd_table_guard); + + // 如果dirfd不是目录,则返回错误码ENOTDIR + if file.file_type() != FileType::Dir { + return Err(SystemError::ENOTDIR); } + + return Ok((file.inode(), PathBuf::from(path))); } else { - ret_path = String::from(path); + return Ok((ROOT_INODE(), PathBuf::from(pcb.basic().cwd()).join(path))); + }; +} + +#[allow(dead_code)] +pub fn clean_path(path: &str) -> String { + let mut tmp = path; + let mut clean = String::new(); + loop { + match split_path(tmp) { + (key, Some(rest)) => { + match key { + "." => {} + ".." => { + clean = rsplit_path(&clean).1.unwrap_or("").to_string(); + } + others => { + clean = clean + "/" + others; + } + }; + tmp = rest; + } + (key, None) => { + match key { + "." => {} + ".." => { + clean = rsplit_path(&clean).1.unwrap_or("").to_string(); + } + others => { + clean = clean + "/" + others; + } + }; + break; + } + } } + clean +} + +pub trait Keyable: Any + Sync + Send + Debug { + fn key(&self) -> Arc; +} - return Ok((inode, ret_path)); +#[derive(Debug)] +pub enum Key< T: Keyable> { + Inner(T), + Cmp(Arc), } + +impl Key { + pub fn unwrap(&self) -> Arc { + match self { + Key::Inner(k) => k.key(), + Key::Cmp(k) => k.clone(), + } + } +} + +impl Hash for Key { + fn hash(&self, state: &mut H) { + self.unwrap().hash(state) + } +} + +impl PartialEq for Key { + fn eq(&self, other: &Self) -> bool { + self.unwrap() == other.unwrap() + } +} + +impl Eq for Key {} + +impl PartialOrd for Key { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Key { + fn cmp(&self, other: &Self) -> Ordering { + self.unwrap().cmp(&other.unwrap()) + } +} \ No newline at end of file diff --git a/kernel/src/lib.rs b/kernel/src/lib.rs index 71a7a4ff5..3d3336a9f 100644 --- a/kernel/src/lib.rs +++ b/kernel/src/lib.rs @@ -22,6 +22,8 @@ #![feature(trait_upcasting)] #![feature(slice_ptr_get)] #![feature(vec_into_raw_parts)] +#![feature(linked_list_cursors)] +#![feature(get_mut_unchecked)] #![cfg_attr(target_os = "none", no_std)] // clippy的配置 #![deny(clippy::all)] @@ -92,7 +94,7 @@ use crate::process::ProcessManager; #[cfg(all(feature = "backtrace", target_arch = "x86_64"))] extern crate mini_backtrace; - +#[allow(dead_code)] extern "C" { fn lookup_kallsyms(addr: u64, level: i32) -> i32; } diff --git a/kernel/src/process/exec.rs b/kernel/src/process/exec.rs index 943a8b1e2..6e8d80107 100644 --- a/kernel/src/process/exec.rs +++ b/kernel/src/process/exec.rs @@ -1,13 +1,14 @@ use core::{fmt::Debug, ptr::null}; use alloc::{collections::BTreeMap, string::String, sync::Arc, vec::Vec}; +use path_base::Path; use system_error::SystemError; use crate::{ driver::base::block::SeekFrom, filesystem::vfs::{ file::{File, FileMode}, - ROOT_INODE, + ROOT_INODE, VFS_MAX_FOLLOW_SYMLINK_TIMES, }, libs::elf::ELF_LOADER, mm::{ @@ -116,7 +117,8 @@ impl ExecParam { vm: Arc, flags: ExecParamFlags, ) -> Result { - let inode = ROOT_INODE().lookup(file_path)?; + let inode = ROOT_INODE() + .lookup_follow_symlink(Path::new(file_path), VFS_MAX_FOLLOW_SYMLINK_TIMES)?; // 读取文件头部,用于判断文件类型 let file = File::new(inode, FileMode::O_RDONLY)?; diff --git a/kernel/src/process/stdio.rs b/kernel/src/process/stdio.rs index 500145087..4ea5103cd 100644 --- a/kernel/src/process/stdio.rs +++ b/kernel/src/process/stdio.rs @@ -1,3 +1,4 @@ +use path_base::Path; use system_error::SystemError; use crate::{ @@ -14,7 +15,7 @@ pub fn stdio_init() -> Result<(), SystemError> { return Err(SystemError::EPERM); } let tty_inode = ROOT_INODE() - .lookup("/dev/tty0") + .lookup(Path::new("/dev/tty0")) .expect("Init stdio: can't find tty0"); let stdin = File::new(tty_inode.clone(), FileMode::O_RDONLY).expect("Init stdio: can't create stdin"); diff --git a/serial_opt1.txt b/serial_opt1.txt new file mode 100644 index 000000000..83080a6b3 Binary files /dev/null and b/serial_opt1.txt differ diff --git a/serial_opt2.txt b/serial_opt2.txt new file mode 100644 index 000000000..f96929fb9 Binary files /dev/null and b/serial_opt2.txt differ diff --git a/user/apps/test_ramfs/.gitignore b/user/apps/test_ramfs/.gitignore new file mode 100644 index 000000000..3d1060bb5 --- /dev/null +++ b/user/apps/test_ramfs/.gitignore @@ -0,0 +1 @@ +test_ramfs diff --git a/user/apps/test_ramfs/Makefile b/user/apps/test_ramfs/Makefile new file mode 100644 index 000000000..fa1e746db --- /dev/null +++ b/user/apps/test_ramfs/Makefile @@ -0,0 +1,20 @@ +ifeq ($(ARCH), x86_64) + CROSS_COMPILE=x86_64-linux-musl- +else ifeq ($(ARCH), riscv64) + CROSS_COMPILE=riscv64-linux-musl- +endif + +CC=$(CROSS_COMPILE)gcc + +.PHONY: all +all: main.c + $(CC) -static -o test_ramfs main.c + +.PHONY: install clean +install: all + mv test_ramfs $(DADK_CURRENT_BUILD_DIR)/test_ramfs + +clean: + rm test_ramfs *.o + +fmt: diff --git a/user/apps/test_ramfs/main.c b/user/apps/test_ramfs/main.c new file mode 100644 index 000000000..4314766d6 --- /dev/null +++ b/user/apps/test_ramfs/main.c @@ -0,0 +1,110 @@ +// #include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX_PATH_LENGTH 100 +#define MAX_DIR_DEPTH 4 + + +int main(int argc, char const* argv[]) { + char ramfs_path[MAX_PATH_LENGTH] = "/some/ramfs"; + char another_ramfs_path[MAX_PATH_LENGTH] = "/some/ramfs/some/another"; + + if (mkdir("/some", 0777) == -1) { + perror("Failed to create directory under /some"); + return 1; + } + + // Create a directory under /some/ramfs + if (mkdir("/some/ramfs", 0777) == -1) { + perror("Failed to create directory under /some/ramfs"); + return 1; + } + + // Mount the first ramfs at /some/ramfs + if (mount("", "/some/ramfs", "ramfs", 0, NULL) == -1) { + perror("Failed to mount ramfs at /some/ramfs"); + return 1; + } + + if (mkdir("/some/ramfs/some", 0777) == -1) { + perror("Failed to create directory under /some/ramfs/some"); + return 1; + } + + // Create a directory under /some/ramfs/some/another + if (mkdir("/some/ramfs/some/another", 0777) == -1) { + perror("Failed to create directory under /some/ramfs/some/another"); + return 1; + } + + if (mount("", "/some/ramfs/some/another", "ramfs", 0, NULL) == -1) { + perror("Failed to mount ramfs at /some/ramfs/some/another"); + return 1; + } + if (mkdir("/some/ramfs/some/another/just_another", 0777) == -1) { + perror("Failed to create directory under /some/ramfs/some/another"); + return 1; + } + + if (mount("", "/some/ramfs/some/another/just_another", "ramfs", 0, NULL) == -1) { + perror("Failed to mount ramfs at /some/ramfs/some/another"); + return 1; + } + + + // Write files under /some/ramfs and /some/ramfs/some/another + FILE* file1 = fopen("/some/ramfs/file1.txt", "w"); + if (file1 == NULL) { + perror("Failed to open /some/ramfs/file1.txt"); + return 1; + } + fprintf(file1, "This is file1.txt\n"); + fclose(file1); + + FILE* file2 = fopen("/some/ramfs/some/another/file2.txt", "w"); + if (file2 == NULL) { + perror("Failed to open /some/ramfs/some/another/file2.txt"); + return 1; + } + fprintf(file2, "This is file2.txt\n"); + fclose(file2); + + FILE* file3 = fopen("/some/ramfs/some/another/just_another/file3.txt", "w"); + if (file2 == NULL) { + perror("Failed to open /some/ramfs/some/another/just_another/file3.txt"); + return 1; + } + fprintf(file3, "This is file3.txt\n"); + fclose(file3); + + // // Delete files under /some/ramfs and /some/ramfs/some/another + // if (unlink("/some/ramfs/file1.txt") == -1) { + // perror("Failed to delete /some/ramfs/file1.txt"); + // return 1; + // } + + // if (unlink("/some/ramfs/some/another/file2.txt") == -1) { + // perror("Failed to delete /some/ramfs/some/another/file2.txt"); + // return 1; + // } + + // Remove directories under /some/ramfs and /some/ramfs/some/another + // if (rmdir(another_ramfs_path) == -1) { + // perror("Failed to remove directory under /some/ramfs/some/another"); + // return 1; + // } + // if (rmdir(ramfs_path) == -1) { + // perror("Failed to remove directory under /some/ramfs"); + // return 1; + // } + + + return 0; +} \ No newline at end of file diff --git a/user/dadk/config/test_ramfs-0.1.0.dadk b/user/dadk/config/test_ramfs-0.1.0.dadk new file mode 100644 index 000000000..8eca983e6 --- /dev/null +++ b/user/dadk/config/test_ramfs-0.1.0.dadk @@ -0,0 +1,23 @@ +{ + "name": "test_ramfs", + "version": "0.1.0", + "description": "ramfs test bench", + "task_type": { + "BuildFromSource": { + "Local": { + "path": "apps/test_ramfs" + } + } + }, + "depends": [], + "build": { + "build_command": "make install" + }, + "install": { + "in_dragonos_path": "/bin" + }, + "clean": { + "clean_command": "make clean" + }, + "envs": [] +}