git_path/
realpath.rs

1/// The error returned by [`realpath()`][super::realpath()].
2#[derive(Debug, thiserror::Error)]
3#[allow(missing_docs)]
4pub enum Error {
5    #[error("The maximum allowed number {} of symlinks in path is exceeded", .max_symlinks)]
6    MaxSymlinksExceeded { max_symlinks: u8 },
7    #[error(transparent)]
8    ReadLink(std::io::Error),
9    #[error(transparent)]
10    CurrentWorkingDir(std::io::Error),
11    #[error("Empty is not a valid path")]
12    EmptyPath,
13    #[error("Ran out of path components while following parent component '..'")]
14    MissingParent,
15}
16
17/// The default amount of symlinks we may follow when resolving a path in [`realpath()`][crate::realpath()].
18pub const MAX_SYMLINKS: u8 = 32;
19
20pub(crate) mod function {
21    use std::path::{
22        Component::{CurDir, Normal, ParentDir, Prefix, RootDir},
23        Path, PathBuf,
24    };
25
26    use super::Error;
27    use crate::realpath::MAX_SYMLINKS;
28
29    /// Check each component of `path` and see if it is a symlink. If so, resolve it.
30    /// Do not fail for non-existing components, but assume these are as is.
31    ///
32    /// If `path` is relative, the current working directory be used to make it absolute.
33    pub fn realpath(path: impl AsRef<Path>) -> Result<PathBuf, Error> {
34        let cwd = path
35            .as_ref()
36            .is_relative()
37            .then(std::env::current_dir)
38            .unwrap_or_else(|| Ok(PathBuf::default()))
39            .map_err(Error::CurrentWorkingDir)?;
40        realpath_opts(path, cwd, MAX_SYMLINKS)
41    }
42
43    /// The same as [`realpath()`], but allow to configure `max_symlinks` to configure how many symbolic links we are going to follow.
44    /// This serves to avoid running into cycles or doing unreasonable amounts of work.
45    pub fn realpath_opts(path: impl AsRef<Path>, cwd: impl AsRef<Path>, max_symlinks: u8) -> Result<PathBuf, Error> {
46        let path = path.as_ref();
47        if path.as_os_str().is_empty() {
48            return Err(Error::EmptyPath);
49        }
50
51        let mut real_path = PathBuf::new();
52        if path.is_relative() {
53            real_path.push(cwd);
54        }
55
56        let mut num_symlinks = 0;
57        let mut path_backing: PathBuf;
58        let mut components = path.components();
59        while let Some(component) = components.next() {
60            match component {
61                part @ RootDir | part @ Prefix(_) => real_path.push(part),
62                CurDir => {}
63                ParentDir => {
64                    if !real_path.pop() {
65                        return Err(Error::MissingParent);
66                    }
67                }
68                Normal(part) => {
69                    real_path.push(part);
70                    if real_path.is_symlink() {
71                        num_symlinks += 1;
72                        if num_symlinks > max_symlinks {
73                            return Err(Error::MaxSymlinksExceeded { max_symlinks });
74                        }
75                        let mut link_destination = std::fs::read_link(real_path.as_path()).map_err(Error::ReadLink)?;
76                        if link_destination.is_absolute() {
77                            // pushing absolute path to real_path resets it to the pushed absolute path
78                        } else {
79                            assert!(real_path.pop(), "we just pushed a component");
80                        }
81                        link_destination.extend(components);
82                        path_backing = link_destination;
83                        components = path_backing.components();
84                    }
85                }
86            }
87        }
88        Ok(real_path)
89    }
90}