cap_primitives/rustix/linux/fs/
canonicalize_impl.rs

1//! Path canonicalization using `/proc/self/fd`.
2
3use super::procfs::get_path_from_proc_self_fd;
4use crate::fs::OpenOptionsExt;
5use crate::fs::{manually, open_beneath, FollowSymlinks, OpenOptions};
6use rustix::fs::OFlags;
7use std::path::{Component, Path, PathBuf};
8use std::{fs, io};
9
10/// Implement `canonicalize` by using readlink on `/proc/self/fd/*`.
11pub(crate) fn canonicalize_impl(start: &fs::File, path: &Path) -> io::Result<PathBuf> {
12    // Open the path with `O_PATH`. Use `read(true)` even though we don't need
13    // `read` permissions, because Rust's libstd requires an access mode, and
14    // Linux ignores `O_RDONLY` with `O_PATH`.
15    let result = open_beneath(
16        start,
17        path,
18        OpenOptions::new()
19            .read(true)
20            .follow(FollowSymlinks::Yes)
21            .custom_flags(OFlags::PATH.bits() as i32),
22    );
23
24    // If that worked, call `readlink`.
25    match result {
26        Ok(file) => {
27            if let Ok(start_path) = get_path_from_proc_self_fd(start) {
28                if let Ok(file_path) = get_path_from_proc_self_fd(&file) {
29                    if let Ok(canonical_path) = file_path.strip_prefix(start_path) {
30                        #[cfg(racy_asserts)]
31                        if canonical_path.as_os_str().is_empty() {
32                            assert_eq!(
33                                Component::CurDir.as_os_str(),
34                                manually::canonicalize(start, path).unwrap()
35                            );
36                        } else {
37                            assert_eq!(
38                                canonical_path,
39                                manually::canonicalize(start, path).unwrap()
40                            );
41                        }
42
43                        let mut path_buf = canonical_path.to_path_buf();
44
45                        // Replace "" with ".", since "" as a relative path is
46                        // interpreted as an error.
47                        if path_buf.as_os_str().is_empty() {
48                            path_buf.push(Component::CurDir);
49                        }
50
51                        return Ok(path_buf);
52                    }
53                }
54            }
55        }
56        Err(err) => match rustix::io::Errno::from_io_error(&err) {
57            // `ENOSYS` from `open_beneath` means `openat2` is unavailable
58            // and we should use a fallback.
59            Some(rustix::io::Errno::NOSYS) => (),
60            _ => return Err(err),
61        },
62    }
63
64    // Use a fallback.
65    manually::canonicalize(start, path)
66}