| // Copyright 2019 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| //! Type that represents a path in a file system. Objects of this type own a String that holds the |
| //! "full" path and provide an iterator that goes over individual components of the path. This |
| //! approach is used to allow passing the path string, from one `open()` method to the next, |
| //! without the need to copy the path itself. |
| |
| use {fidl_fuchsia_io as fio, fuchsia_zircon::Status}; |
| |
| #[derive(Clone, Debug)] |
| pub struct Path { |
| is_dir: bool, |
| inner: String, |
| next: usize, |
| } |
| |
| impl Path { |
| /// Returns a path for dot, i.e. the current directory. The `next` will return None for this |
| /// path. |
| pub fn dot() -> Path { |
| // We use an empty string to avoid allocations. as_ref() handles this correctly below and |
| // will return ".". |
| Path { is_dir: false, inner: String::new(), next: 0 } |
| } |
| |
| pub fn is_dot(&self) -> bool { |
| self.inner.is_empty() |
| } |
| |
| /// Splits a `path` string into components, also checking if it is in a canonical form, |
| /// disallowing any ".." components, as well as empty component names. A lone "/" is translated |
| /// to "." (the canonical form). |
| pub fn validate_and_split<Source>(path: Source) -> Result<Path, Status> |
| where |
| Source: Into<String>, |
| { |
| let path = path.into(); |
| |
| // Make sure that we don't accept paths longer than POSIX's PATH_MAX, plus one character |
| // which accounts for the null terminator. |
| if (path.len() as u64) > std::cmp::min(fio::MAX_PATH, libc::PATH_MAX as u64 - 1) { |
| return Err(Status::BAD_PATH); |
| } |
| |
| match path.as_str() { |
| "." | "/" => Ok(Self::dot()), |
| _ => { |
| let is_dir = path.ends_with('/'); |
| |
| // Allow a leading slash. |
| let next = if path.len() > 1 && path.starts_with('/') { 1 } else { 0 }; |
| |
| let mut check = path[next..].split('/'); |
| |
| // Allow trailing slash to indicate a directory. |
| if is_dir { |
| let _ = check.next_back(); |
| } |
| |
| // Disallow empty components, ".", and ".."s. Path is expected to be |
| // canonicalized. See fxbug.dev/28436 for discussion of empty components. |
| for c in check { |
| if c.is_empty() || c == ".." || c == "." { |
| return Err(Status::INVALID_ARGS); |
| } |
| if c.len() as u64 > fio::MAX_FILENAME { |
| return Err(Status::BAD_PATH); |
| } |
| } |
| |
| Ok(Path { is_dir, inner: path, next }) |
| } |
| } |
| } |
| |
| /// Returns `true` when there are no more compoenents left in this `Path`. |
| pub fn is_empty(&self) -> bool { |
| self.next >= self.inner.len() |
| } |
| |
| /// Returns `true` if the canonical path contains '/' as the last symbol. Note that is_dir is |
| /// `false` for ".", even though a directory is implied. The canonical form for "/" is ".". |
| pub fn is_dir(&self) -> bool { |
| self.is_dir |
| } |
| |
| /// Returns `true` when the path contains only one component - that is, it is not empty and |
| /// contains no `/` characters. |
| pub fn is_single_component(&self) -> bool { |
| let end = if self.is_dir { self.inner.len() - 1 } else { self.inner.len() }; |
| self.next < self.inner.len() && self.inner[self.next..end].find('/').is_none() |
| } |
| |
| /// Returns a reference to a portion of the string that names the next component, and move the |
| /// internal pointer to point to the next component. See also [`Path::peek()`]. |
| /// |
| /// Also see [`Path::next_with_ref()`] if you want to use `self` while holding a reference to |
| /// the returned name. |
| pub fn next(&mut self) -> Option<&str> { |
| self.next_with_ref().1 |
| } |
| |
| /// Rust does not allow usage of `self` while the returned reference is alive, even when the |
| /// reference is actually shared. See, for example, |
| /// |
| /// https://internals.rust-lang.org/t/relaxing-the-borrow-checker-for-fn-mut-self-t/3256 |
| /// |
| /// for additional details. So if the caller wants to call any other methods on the `path` |
| /// after calling `next()` while still holding a reference to the returned name they can use |
| /// this method as a workaround. When Rust is extended to cover this use case, `next_with_ref` |
| /// should be merged into [`Self::next()`]. |
| pub fn next_with_ref(&mut self) -> (&Self, Option<&str>) { |
| match self.inner[self.next..].find('/') { |
| Some(i) => { |
| let from = self.next; |
| self.next = self.next + i + 1; |
| (self, Some(&self.inner[from..from + i])) |
| } |
| None => { |
| if self.next >= self.inner.len() { |
| (self, None) |
| } else { |
| let from = self.next; |
| self.next = self.inner.len(); |
| (self, Some(&self.inner[from..])) |
| } |
| } |
| } |
| } |
| |
| /// Returns a reference to a position of the string that names the next component, without |
| /// moving the internal pointer. So calling `peek()` multiple times in a row would return the |
| /// same result. See also [`Self::next()`]. |
| pub fn peek(&self) -> Option<&str> { |
| match self.inner[self.next..].find('/') { |
| Some(i) => Some(&self.inner[self.next..self.next + i]), |
| None => { |
| if self.next >= self.inner.len() { |
| None |
| } else { |
| Some(&self.inner[self.next..]) |
| } |
| } |
| } |
| } |
| |
| /// Converts this `Path` into a `String` holding the rest of the path. Note that if there are |
| /// no more components, this will return an empty string, which is *not* a valid path for |
| /// fuchsia.io. |
| pub fn into_string(mut self) -> String { |
| self.inner.drain(0..self.next); |
| self.inner |
| } |
| |
| /// Like `into_string` but returns a reference and the path returned is valid for fuchsia.io |
| /// i.e. if there are no remaining components, "." is returned. |
| fn remainder(&self) -> &str { |
| if self.is_empty() { |
| "." |
| } else { |
| &self.inner[self.next..] |
| } |
| } |
| } |
| |
| impl PartialEq for Path { |
| fn eq(&self, other: &Self) -> bool { |
| self.remainder() == other.remainder() |
| } |
| } |
| impl Eq for Path {} |
| |
| impl AsRef<str> for Path { |
| fn as_ref(&self) -> &str { |
| self.remainder() |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| |
| #[allow(unknown_lints)] // TODO(fxbug.dev/99424): remove this after toolchain roll when lint is known |
| #[allow(unused_macro_rules)] // TODO(fxbug.dev/100318): remove unused macro rules and re-enable |
| macro_rules! simple_construction_test { |
| (path: $str:expr, $path:ident => $body:block) => { |
| match Path::validate_and_split($str) { |
| Ok($path) => $body, |
| Err(status) => panic!("'{}' construction failed: {}", stringify!(path), status), |
| } |
| }; |
| (path: $str:expr, mut $path:ident => $body:block) => { |
| match Path::validate_and_split($str) { |
| Ok(mut $path) => $body, |
| Err(status) => panic!("'{}' construction failed: {}", stringify!(path), status), |
| } |
| }; |
| } |
| |
| macro_rules! negative_construction_test { |
| (path: $path:expr, $details:expr, $status:expr) => { |
| match Path::validate_and_split($path) { |
| Ok(path) => { |
| panic!("Constructed '{}' with {}: {:?}", stringify!($path), $details, path) |
| } |
| Err(status) => assert_eq!(status, $status), |
| } |
| }; |
| } |
| |
| fn path(s: &str) -> Path { |
| match Path::validate_and_split(s) { |
| Ok(path) => path, |
| Err(e) => panic!("'{}' construction failed: {}", s, e), |
| } |
| } |
| |
| #[test] |
| fn empty() { |
| negative_construction_test! { |
| path: "", |
| "empty path", |
| Status::INVALID_ARGS |
| }; |
| } |
| |
| #[test] |
| fn forward_slash_only() { |
| simple_construction_test! { |
| path: "/", |
| mut path => { |
| assert!(path.is_empty()); |
| assert!(!path.is_dir()); // It is converted into ".". |
| assert!(!path.is_single_component()); |
| assert_eq!(path.as_ref(), "."); |
| assert_eq!(path.peek(), None); |
| assert_eq!(path.next(), None); |
| assert_eq!(path.as_ref(), "."); |
| assert_eq!(path.into_string(), String::new()); |
| } |
| }; |
| } |
| |
| #[test] |
| fn one_component_short() { |
| simple_construction_test! { |
| path: "a", |
| mut path => { |
| assert!(!path.is_empty()); |
| assert!(!path.is_dir()); |
| assert!(path.is_single_component()); |
| assert_eq!(path.peek(), Some("a")); |
| assert_eq!(path.peek(), Some("a")); |
| assert_eq!(path.next(), Some("a")); |
| assert_eq!(path.peek(), None); |
| assert_eq!(path.next(), None); |
| assert_eq!(path.as_ref(), "."); |
| assert_eq!(path.into_string(), String::new()); |
| } |
| }; |
| } |
| |
| #[test] |
| fn one_component() { |
| simple_construction_test! { |
| path: "some", |
| mut path => { |
| assert!(!path.is_empty()); |
| assert!(!path.is_dir()); |
| assert!(path.is_single_component()); |
| assert_eq!(path.peek(), Some("some")); |
| assert_eq!(path.peek(), Some("some")); |
| assert_eq!(path.next(), Some("some")); |
| assert_eq!(path.peek(), None); |
| assert_eq!(path.next(), None); |
| assert_eq!(path.as_ref(), "."); |
| assert_eq!(path.into_string(), String::new()); |
| } |
| }; |
| } |
| |
| #[test] |
| fn one_component_dir() { |
| simple_construction_test! { |
| path: "some/", |
| mut path => { |
| assert!(!path.is_empty()); |
| assert!(path.is_dir()); |
| assert!(path.is_single_component()); |
| assert_eq!(path.peek(), Some("some")); |
| assert_eq!(path.peek(), Some("some")); |
| assert_eq!(path.next(), Some("some")); |
| assert_eq!(path.peek(), None); |
| assert_eq!(path.next(), None); |
| assert_eq!(path.as_ref(), "."); |
| assert_eq!(path.into_string(), String::new()); |
| } |
| }; |
| } |
| |
| #[test] |
| fn two_component_short() { |
| simple_construction_test! { |
| path: "a/b", |
| mut path => { |
| assert!(!path.is_empty()); |
| assert!(!path.is_dir()); |
| assert!(!path.is_single_component()); |
| assert_eq!(path.peek(), Some("a")); |
| assert_eq!(path.peek(), Some("a")); |
| assert_eq!(path.next(), Some("a")); |
| assert_eq!(path.peek(), Some("b")); |
| assert_eq!(path.peek(), Some("b")); |
| assert_eq!(path.next(), Some("b")); |
| assert_eq!(path.peek(), None); |
| assert_eq!(path.next(), None); |
| assert_eq!(path.as_ref(), "."); |
| assert_eq!(path.into_string(), String::new()); |
| } |
| }; |
| } |
| |
| #[test] |
| fn two_component() { |
| simple_construction_test! { |
| path: "some/path", |
| mut path => { |
| assert!(!path.is_empty()); |
| assert!(!path.is_dir()); |
| assert!(!path.is_single_component()); |
| assert_eq!(path.peek(), Some("some")); |
| assert_eq!(path.peek(), Some("some")); |
| assert_eq!(path.next(), Some("some")); |
| assert_eq!(path.peek(), Some("path")); |
| assert_eq!(path.peek(), Some("path")); |
| assert_eq!(path.next(), Some("path")); |
| assert_eq!(path.peek(), None); |
| assert_eq!(path.next(), None); |
| assert_eq!(path.as_ref(), "."); |
| assert_eq!(path.into_string(), String::new()); |
| } |
| }; |
| } |
| |
| #[test] |
| fn two_component_dir() { |
| simple_construction_test! { |
| path: "some/path/", |
| mut path => { |
| assert!(!path.is_empty()); |
| assert!(path.is_dir()); |
| assert!(!path.is_single_component()); |
| assert_eq!(path.peek(), Some("some")); |
| assert_eq!(path.peek(), Some("some")); |
| assert_eq!(path.next(), Some("some")); |
| assert_eq!(path.peek(), Some("path")); |
| assert_eq!(path.peek(), Some("path")); |
| assert_eq!(path.next(), Some("path")); |
| assert_eq!(path.peek(), None); |
| assert_eq!(path.next(), None); |
| assert_eq!(path.as_ref(), "."); |
| assert_eq!(path.into_string(), String::new()); |
| } |
| }; |
| } |
| |
| #[test] |
| fn into_string_half_way() { |
| simple_construction_test! { |
| path: "into/string/half/way", |
| mut path => { |
| assert!(!path.is_empty()); |
| assert!(!path.is_dir()); |
| assert!(!path.is_single_component()); |
| assert_eq!(path.peek(), Some("into")); |
| assert_eq!(path.peek(), Some("into")); |
| assert_eq!(path.next(), Some("into")); |
| assert_eq!(path.peek(), Some("string")); |
| assert_eq!(path.peek(), Some("string")); |
| assert_eq!(path.next(), Some("string")); |
| assert_eq!(path.peek(), Some("half")); |
| assert_eq!(path.peek(), Some("half")); |
| assert_eq!(path.as_ref(), "half/way"); |
| assert_eq!(path.into_string(), "half/way".to_string()); |
| } |
| }; |
| } |
| |
| #[test] |
| fn into_string_half_way_dir() { |
| simple_construction_test! { |
| path: "into/string/half/way/", |
| mut path => { |
| assert!(!path.is_empty()); |
| assert!(path.is_dir()); |
| assert!(!path.is_single_component()); |
| assert_eq!(path.peek(), Some("into")); |
| assert_eq!(path.peek(), Some("into")); |
| assert_eq!(path.next(), Some("into")); |
| assert_eq!(path.peek(), Some("string")); |
| assert_eq!(path.peek(), Some("string")); |
| assert_eq!(path.next(), Some("string")); |
| assert_eq!(path.peek(), Some("half")); |
| assert_eq!(path.peek(), Some("half")); |
| assert_eq!(path.as_ref(), "half/way/"); |
| assert_eq!(path.into_string(), "half/way/".to_string()); |
| } |
| }; |
| } |
| |
| #[test] |
| fn into_string_dir_last_component() { |
| simple_construction_test! { |
| path: "into/string/", |
| mut path => { |
| assert!(!path.is_empty()); |
| assert!(path.is_dir()); |
| assert!(!path.is_single_component()); |
| assert_eq!(path.peek(), Some("into")); |
| assert_eq!(path.peek(), Some("into")); |
| assert_eq!(path.next(), Some("into")); |
| assert_eq!(path.peek(), Some("string")); |
| assert_eq!(path.peek(), Some("string")); |
| assert_eq!(path.next(), Some("string")); |
| assert_eq!(path.peek(), None); |
| assert_eq!(path.as_ref(), "."); |
| assert_eq!(path.into_string(), "".to_string()); |
| } |
| }; |
| } |
| |
| #[test] |
| fn no_empty_components() { |
| negative_construction_test! { |
| path: "//", |
| "empty components", |
| Status::INVALID_ARGS |
| }; |
| } |
| |
| #[test] |
| fn absolute_paths() { |
| simple_construction_test! { |
| path: "/a/b/c", |
| mut path => { |
| assert!(!path.is_empty()); |
| assert!(!path.is_dir()); |
| assert!(!path.is_single_component()); |
| assert_eq!(path.as_ref(), "a/b/c"); |
| assert_eq!(path.clone().into_string(), "a/b/c"); |
| assert_eq!(path.peek(), Some("a")); |
| assert_eq!(path.peek(), Some("a")); |
| assert_eq!(path.next(), Some("a")); |
| assert_eq!(path.next(), Some("b")); |
| assert_eq!(path.next(), Some("c")); |
| assert_eq!(path.peek(), None); |
| assert_eq!(path.next(), None); |
| assert_eq!(path.as_ref(), "."); |
| assert_eq!(path.into_string(), "".to_string()); |
| } |
| } |
| } |
| |
| #[test] |
| fn dot_components() { |
| negative_construction_test! { |
| path: "a/./b", |
| "'.' components", |
| Status::INVALID_ARGS |
| }; |
| } |
| |
| #[test] |
| fn dot_dot_components() { |
| negative_construction_test! { |
| path: "a/../b", |
| "'..' components", |
| Status::INVALID_ARGS |
| }; |
| } |
| |
| #[test] |
| fn too_long_filename() { |
| let string = "a".repeat(fio::MAX_FILENAME as usize + 1); |
| negative_construction_test! { |
| path: &string, |
| "filename too long", |
| Status::BAD_PATH |
| }; |
| } |
| |
| #[test] |
| fn too_long_path() { |
| let filename = "a".repeat(fio::MAX_FILENAME as usize); |
| let mut path = String::new(); |
| while path.len() < fio::MAX_PATH as usize { |
| path.push('/'); |
| path.push_str(&filename); |
| } |
| assert_eq!(path.len(), fio::MAX_PATH as usize); |
| negative_construction_test! { |
| path: &path, |
| "path too long", |
| Status::BAD_PATH |
| }; |
| } |
| |
| #[test] |
| fn long_path() { |
| let mut path = "a/".repeat((fio::MAX_PATH as usize - 1) / 2); |
| if path.len() < fio::MAX_PATH as usize - 1 { |
| path.push('a'); |
| } |
| assert_eq!(path.len(), fio::MAX_PATH as usize - 1); |
| simple_construction_test! { |
| path: &path, |
| mut path => { |
| assert!(!path.is_empty()); |
| assert_eq!(path.next(), Some("a")); |
| } |
| }; |
| } |
| |
| #[test] |
| fn long_filename() { |
| let string = "a".repeat(fio::MAX_FILENAME as usize); |
| simple_construction_test! { |
| path: &string, |
| mut path => { |
| assert!(!path.is_empty()); |
| assert!(path.is_single_component()); |
| assert_eq!(path.next(), Some(string.as_str())); |
| } |
| }; |
| } |
| |
| #[test] |
| fn dot() { |
| for mut path in |
| [Path::dot(), Path::validate_and_split(".").expect("validate_and_split failed")] |
| { |
| assert!(path.is_dot()); |
| assert!(path.is_empty()); |
| assert!(!path.is_dir()); |
| assert!(!path.is_single_component()); |
| assert_eq!(path.next(), None); |
| assert_eq!(path.peek(), None); |
| assert_eq!(path.as_ref(), "."); |
| assert_eq!(path.as_ref(), "."); |
| assert_eq!(path.into_string(), ""); |
| } |
| } |
| |
| #[test] |
| fn eq_compares_remainder() { |
| let mut pos = path("a/b/c"); |
| |
| assert_eq!(pos, path("a/b/c")); |
| assert_ne!(pos, path("b/c")); |
| assert_ne!(pos, path("c")); |
| assert_ne!(pos, path(".")); |
| |
| assert_eq!(pos.next(), Some("a")); |
| |
| assert_ne!(pos, path("a/b/c")); |
| assert_eq!(pos, path("b/c")); |
| assert_ne!(pos, path("c")); |
| assert_ne!(pos, path(".")); |
| |
| assert_eq!(pos.next(), Some("b")); |
| |
| assert_ne!(pos, path("a/b/c")); |
| assert_ne!(pos, path("b/c")); |
| assert_eq!(pos, path("c")); |
| assert_ne!(pos, path(".")); |
| |
| assert_eq!(pos.next(), Some("c")); |
| |
| assert_ne!(pos, path("a/b/c")); |
| assert_ne!(pos, path("b/c")); |
| assert_ne!(pos, path("c")); |
| assert_eq!(pos, path(".")); |
| } |
| |
| #[test] |
| fn eq_considers_is_dir() { |
| let mut pos_not = path("a/b"); |
| let mut pos_dir = path("a/b/"); |
| |
| assert_ne!(pos_not, pos_dir); |
| assert_eq!(pos_not, path("a/b")); |
| assert_eq!(pos_dir, path("a/b/")); |
| |
| pos_not.next(); |
| pos_dir.next(); |
| |
| assert_ne!(pos_not, pos_dir); |
| assert_eq!(pos_not, path("b")); |
| assert_eq!(pos_dir, path("b/")); |
| |
| pos_not.next(); |
| pos_dir.next(); |
| |
| // once all that is left is ".", now they are equivalent |
| assert_eq!(pos_not, pos_dir); |
| assert_eq!(pos_not, path(".")); |
| assert_eq!(pos_dir, path(".")); |
| } |
| |
| #[test] |
| fn eq_does_not_consider_absolute_different_from_relative() { |
| let abs = path("/a/b"); |
| let rel = path("a/b"); |
| |
| assert_eq!(abs, rel); |
| assert_ne!(abs, path("different/path")); |
| assert_ne!(rel, path("different/path")); |
| } |
| |
| #[test] |
| fn as_ref_is_remainder() { |
| let mut path = Path::validate_and_split(".").unwrap(); |
| assert_eq!(path.as_ref(), "."); |
| path.next(); |
| assert_eq!(path.as_ref(), "."); |
| |
| let mut path = Path::validate_and_split("a/b/c").unwrap(); |
| assert_eq!(path.as_ref(), "a/b/c"); |
| path.next(); |
| assert_eq!(path.as_ref(), "b/c"); |
| path.next(); |
| assert_eq!(path.as_ref(), "c"); |
| path.next(); |
| assert_eq!(path.as_ref(), "."); |
| |
| let mut path = Path::validate_and_split("/a/b/c").unwrap(); |
| assert_eq!(path.as_ref(), "a/b/c"); |
| path.next(); |
| assert_eq!(path.as_ref(), "b/c"); |
| path.next(); |
| assert_eq!(path.as_ref(), "c"); |
| path.next(); |
| assert_eq!(path.as_ref(), "."); |
| |
| let mut path = Path::validate_and_split("/a/b/c/").unwrap(); |
| assert_eq!(path.as_ref(), "a/b/c/"); |
| path.next(); |
| assert_eq!(path.as_ref(), "b/c/"); |
| path.next(); |
| assert_eq!(path.as_ref(), "c/"); |
| path.next(); |
| assert_eq!(path.as_ref(), "."); |
| } |
| } |