Coverage Report

Created: 2025-09-27 06:48

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}