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)]
6MaxSymlinksExceeded { max_symlinks: u8 },
7#[error(transparent)]
8ReadLink(std::io::Error),
9#[error(transparent)]
10CurrentWorkingDir(std::io::Error),
11#[error("Empty is not a valid path")]
12EmptyPath,
13#[error("Ran out of path components while following parent component '..'")]
14MissingParent,
15}
1617/// The default amount of symlinks we may follow when resolving a path in [`realpath()`][crate::realpath()].
18pub const MAX_SYMLINKS: u8 = 32;
1920pub(crate) mod function {
21use std::path::{
22 Component::{CurDir, Normal, ParentDir, Prefix, RootDir},
23 Path, PathBuf,
24 };
2526use super::Error;
27use crate::realpath::MAX_SYMLINKS;
2829/// 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.
33pub fn realpath(path: impl AsRef<Path>) -> Result<PathBuf, Error> {
34let 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 }
4243/// 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.
45pub fn realpath_opts(path: impl AsRef<Path>, cwd: impl AsRef<Path>, max_symlinks: u8) -> Result<PathBuf, Error> {
46let path = path.as_ref();
47if path.as_os_str().is_empty() {
48return Err(Error::EmptyPath);
49 }
5051let mut real_path = PathBuf::new();
52if path.is_relative() {
53 real_path.push(cwd);
54 }
5556let mut num_symlinks = 0;
57let mut path_backing: PathBuf;
58let mut components = path.components();
59while let Some(component) = components.next() {
60match component {
61 part @ RootDir | part @ Prefix(_) => real_path.push(part),
62 CurDir => {}
63 ParentDir => {
64if !real_path.pop() {
65return Err(Error::MissingParent);
66 }
67 }
68 Normal(part) => {
69 real_path.push(part);
70if real_path.is_symlink() {
71 num_symlinks += 1;
72if num_symlinks > max_symlinks {
73return Err(Error::MaxSymlinksExceeded { max_symlinks });
74 }
75let mut link_destination = std::fs::read_link(real_path.as_path()).map_err(Error::ReadLink)?;
76if link_destination.is_absolute() {
77// pushing absolute path to real_path resets it to the pushed absolute path
78} else {
79assert!(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 }
88Ok(real_path)
89 }
90}