cap_primitives/fs/via_parent/
open_parent.rs

1/// `open_parent` is the key building block in all `via_parent` functions.
2/// It opens the parent directory of the given path, and returns the basename,
3/// so that all the `via_parent` functions need to do is make sure they
4/// don't follow symlinks in the basename.
5use crate::fs::{errors, open_dir, path_requires_dir, MaybeOwnedFile};
6use std::ffi::OsStr;
7use std::io;
8use std::path::{Component, Path};
9
10/// Open the "parent" of `path`, relative to `start`. The return value on
11/// success is a tuple of the newly opened directory and an `OsStr` referencing
12/// the single remaining path component. This last component will not be `..`,
13/// though it may be `.` or a symbolic link to anywhere (possibly
14/// including `..` or an absolute path).
15pub(super) fn open_parent<'path, 'borrow>(
16    start: MaybeOwnedFile<'borrow>,
17    path: &'path Path,
18) -> io::Result<(MaybeOwnedFile<'borrow>, &'path OsStr)> {
19    let (dirname, basename) = split_parent(path).ok_or_else(errors::no_such_file_or_directory)?;
20
21    let dir = if dirname.as_os_str().is_empty() {
22        start
23    } else {
24        MaybeOwnedFile::owned(open_dir(&start, dirname)?)
25    };
26
27    Ok((dir, basename.as_os_str()))
28}
29
30/// Split `path` into parent and basename parts. Return `None` if `path`
31/// is empty.
32///
33/// This differs from `path.parent()` and `path.file_name()` in several
34/// respects:
35///  - Treat paths ending in `/` or `/.` as implying a directory.
36///  - Treat the path `.` as a normal component rather than a parent.
37///  - Append a `.` to a path with a trailing `..` to avoid requiring our
38///    callers to special-case `..`.
39///  - Bare absolute paths are ok.
40fn split_parent(path: &Path) -> Option<(&Path, Component<'_>)> {
41    if path.as_os_str().is_empty() {
42        return None;
43    }
44
45    if !path_requires_dir(path) {
46        let mut comps = path.components();
47        if let Some(p) = comps.next_back() {
48            match p {
49                Component::Normal(_) | Component::CurDir => return Some((comps.as_path(), p)),
50                _ => (),
51            }
52        }
53    }
54
55    Some((path, Component::CurDir))
56}
57
58#[test]
59fn split_parent_basics() {
60    assert_eq!(
61        split_parent(Path::new("foo/bar/qux")).unwrap(),
62        (
63            Path::new("foo/bar"),
64            Component::Normal(Path::new("qux").as_ref())
65        )
66    );
67    assert_eq!(
68        split_parent(Path::new("foo/bar")).unwrap(),
69        (
70            Path::new("foo"),
71            Component::Normal(Path::new("bar").as_ref())
72        )
73    );
74    assert_eq!(
75        split_parent(Path::new("foo")).unwrap(),
76        (Path::new(""), Component::Normal(Path::new("foo").as_ref()))
77    );
78}
79
80#[test]
81fn split_parent_special_cases() {
82    assert!(split_parent(Path::new("")).is_none());
83    assert_eq!(
84        split_parent(Path::new("foo/")).unwrap(),
85        (Path::new("foo"), Component::CurDir)
86    );
87    assert_eq!(
88        split_parent(Path::new("foo/.")).unwrap(),
89        (Path::new("foo"), Component::CurDir)
90    );
91    assert_eq!(
92        split_parent(Path::new(".")).unwrap(),
93        (Path::new(""), Component::CurDir)
94    );
95    assert_eq!(
96        split_parent(Path::new("..")).unwrap(),
97        (Path::new(".."), Component::CurDir)
98    );
99    assert_eq!(
100        split_parent(Path::new("../..")).unwrap(),
101        (Path::new("../.."), Component::CurDir)
102    );
103    assert_eq!(
104        split_parent(Path::new("../foo")).unwrap(),
105        (
106            Path::new(".."),
107            Component::Normal(Path::new("foo").as_ref())
108        )
109    );
110    assert_eq!(
111        split_parent(Path::new("foo/..")).unwrap(),
112        (Path::new("foo/.."), Component::CurDir)
113    );
114    assert_eq!(
115        split_parent(Path::new("/foo")).unwrap(),
116        (Path::new("/"), Component::Normal(Path::new("foo").as_ref()))
117    );
118    assert_eq!(
119        split_parent(Path::new("/foo/")).unwrap(),
120        (Path::new("/foo"), Component::CurDir)
121    );
122    assert_eq!(
123        split_parent(Path::new("/")).unwrap(),
124        (Path::new("/"), Component::CurDir)
125    );
126}