#[derive(Debug, thiserror::Error)]
#[allow(missing_docs)]
pub enum Error {
#[error("The maximum allowed number {} of symlinks in path is exceeded", .max_symlinks)]
MaxSymlinksExceeded { max_symlinks: u8 },
#[error("Cannot resolve symlinks in path with more than {max_symlink_checks} components (takes too long)")]
ExcessiveComponentCount { max_symlink_checks: usize },
#[error(transparent)]
ReadLink(std::io::Error),
#[error(transparent)]
CurrentWorkingDir(std::io::Error),
#[error("Empty is not a valid path")]
EmptyPath,
#[error("Ran out of path components while following parent component '..'")]
MissingParent,
}
pub const MAX_SYMLINKS: u8 = 32;
pub(crate) mod function {
use std::path::{
Component::{CurDir, Normal, ParentDir, Prefix, RootDir},
Path, PathBuf,
};
use super::Error;
use crate::realpath::MAX_SYMLINKS;
pub fn realpath(path: impl AsRef<Path>) -> Result<PathBuf, Error> {
let path = path.as_ref();
let cwd = path
.is_relative()
.then(std::env::current_dir)
.unwrap_or_else(|| Ok(PathBuf::default()))
.map_err(Error::CurrentWorkingDir)?;
realpath_opts(path, &cwd, MAX_SYMLINKS)
}
pub fn realpath_opts(path: &Path, cwd: &Path, max_symlinks: u8) -> Result<PathBuf, Error> {
if path.as_os_str().is_empty() {
return Err(Error::EmptyPath);
}
let mut real_path = PathBuf::new();
if path.is_relative() {
real_path.push(cwd);
}
let mut num_symlinks = 0;
let mut path_backing: PathBuf;
let mut components = path.components();
const MAX_SYMLINK_CHECKS: usize = 2048;
let mut symlink_checks = 0;
while let Some(component) = components.next() {
match component {
part @ (RootDir | Prefix(_)) => real_path.push(part),
CurDir => {}
ParentDir => {
if !real_path.pop() {
return Err(Error::MissingParent);
}
}
Normal(part) => {
real_path.push(part);
symlink_checks += 1;
if real_path.is_symlink() {
num_symlinks += 1;
if num_symlinks > max_symlinks {
return Err(Error::MaxSymlinksExceeded { max_symlinks });
}
let mut link_destination = std::fs::read_link(real_path.as_path()).map_err(Error::ReadLink)?;
if link_destination.is_absolute() {
} else {
assert!(real_path.pop(), "we just pushed a component");
}
link_destination.extend(components);
path_backing = link_destination;
components = path_backing.components();
}
if symlink_checks > MAX_SYMLINK_CHECKS {
return Err(Error::ExcessiveComponentCount {
max_symlink_checks: MAX_SYMLINK_CHECKS,
});
}
}
}
}
Ok(real_path)
}
}