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