1#[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
17pub 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 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 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 } 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}