Skip to main content

proot/fs/
translate.rs

1//! Translating a tracee's `(dirfd, path)` into the object to act on, or
2//! into a host path, and back.
3
4use super::Fs;
5use super::canon::{Resolved, resolve};
6use crate::sys::{PATH_MAX, Result};
7
8/// The guest path of a tracee's working directory.
9///
10/// Without ptrace the kernel's cwd of every tracee is pinned to the alias
11/// directory and the real one is kept in the process table; otherwise it is
12/// read from `/proc`.
13///
14/// # Arguments
15/// * `fs` - Guest filesystem view.
16/// * `pid` - Tracee thread id.
17pub fn guest_cwd(fs: &Fs, pid: u32) -> Result<Vec<u8>> {
18    if let Some(cwd) = fs.virtual_cwd(pid as i32) {
19        return Ok(cwd);
20    }
21    let host = strip_deleted(read_link(format!("/proc/{pid}/cwd").as_bytes())?);
22    Ok(detranslate_path(fs, &host))
23}
24
25/// Drops the ` (deleted)` marker `/proc` appends to links of unlinked targets.
26pub fn strip_deleted(mut host: Vec<u8>) -> Vec<u8> {
27    const MARK: &[u8] = b" (deleted)";
28    if host.len() > MARK.len() + 1 && host.ends_with(MARK) {
29        host.truncate(host.len() - MARK.len());
30    }
31    host
32}
33
34/// Reads the guest path of a tracee's open directory descriptor.
35///
36/// # Arguments
37/// * `fs` - Guest filesystem view.
38/// * `pid` - Tracee thread id.
39/// * `fd` - Descriptor number in the tracee.
40pub fn guest_fd_path(fs: &Fs, pid: u32, fd: i32) -> Result<Vec<u8>> {
41    if fd < 0 {
42        return Err(libc::EBADF);
43    }
44    let host = strip_deleted(read_link(format!("/proc/{pid}/fd/{fd}").as_bytes())?);
45    if host.first() != Some(&b'/') {
46        // Pipes, sockets and anonymous inodes read like `pipe:[123]`.
47        return Err(libc::ENOTDIR);
48    }
49    Ok(detranslate_path(fs, &host))
50}
51
52/// The guest path of `dirfd` itself, or of the tracee's cwd for `AT_FDCWD`:
53/// what an empty path names in the calls that allow one (`AT_EMPTY_PATH`,
54/// and `readlinkat` always).
55///
56/// # Arguments
57/// * `fs` - Guest filesystem view.
58/// * `pid` - Tracee thread id.
59/// * `dirfd` - Descriptor number in the tracee, or `libc::AT_FDCWD`.
60pub fn guest_dirfd_path(fs: &Fs, pid: u32, dirfd: i32) -> Result<Vec<u8>> {
61    if dirfd == libc::AT_FDCWD {
62        guest_cwd(fs, pid)
63    } else {
64        guest_fd_path(fs, pid, dirfd)
65    }
66}
67
68/// Translates a path as seen by a tracee into the object to operate on.
69///
70/// Relative paths are resolved against `dirfd` (`AT_FDCWD` means the tracee's
71/// cwd) by reading the descriptor's own path from `/proc`, so the whole path
72/// is canonicalized inside the guest namespace, on the descriptors the view
73/// holds open (see [`resolve`]).  An empty path fails with `ENOENT`, as the
74/// kernel's lookup fails it; a call that lets it name `dirfd` itself
75/// resolves [`guest_dirfd_path`] instead.
76///
77/// # Arguments
78/// * `fs` - Guest filesystem view.
79/// * `pid` - Tracee thread id.
80/// * `dirfd` - Base descriptor for relative paths, or `libc::AT_FDCWD`.
81/// * `path` - Path bytes as read from the tracee, without NUL.
82/// * `deref_final` - Follow a symlink in the final component.
83///
84/// # Returns
85/// The canonical guest path, its host path, and the pinned directory and
86/// name to act on.
87///
88/// # Errors
89/// `ENOENT` for an empty path, errno from the walk, or `EBADF` when `dirfd`
90/// cannot be resolved.
91pub fn translate(fs: &Fs, pid: u32, dirfd: i32, path: &[u8], deref_final: bool) -> Result<Resolved> {
92    if path.is_empty() {
93        return Err(libc::ENOENT);
94    }
95    let guest = if path.first() == Some(&b'/') {
96        path.to_vec()
97    } else {
98        let mut base = guest_dirfd_path(fs, pid, dirfd)?;
99        if base != b"/" {
100            base.push(b'/');
101        }
102        base.extend_from_slice(path);
103        base
104    };
105    let resolved = resolve(fs, pid, &guest, deref_final)?;
106    if resolved.guest.len() >= PATH_MAX {
107        return Err(libc::ENAMETOOLONG);
108    }
109    Ok(resolved)
110}
111
112/// Translates a path as seen by a tracee into a textual host path, for the
113/// callers that hand the path to a program or to the tracee itself (the
114/// exec plan, the ptrace tier's in-place rewrites): the kernel then resolves
115/// it again on the host.
116///
117/// # Arguments
118/// As [`translate`].
119///
120/// # Returns
121/// An absolute host path.
122pub fn translate_path(fs: &Fs, pid: u32, dirfd: i32, path: &[u8], deref_final: bool) -> Result<Vec<u8>> {
123    translate(fs, pid, dirfd, path, deref_final).map(|r| r.host)
124}
125
126/// Translates a host path back into the guest's view (for `readlink` on
127/// `/proc` magic links and `getcwd`).
128///
129/// # Arguments
130/// * `fs` - Guest filesystem view.
131/// * `host` - Absolute host path.
132///
133/// # Returns
134/// The guest path; a host path outside every binding is returned unchanged.
135pub fn detranslate_path(fs: &Fs, host: &[u8]) -> Vec<u8> {
136    fs.substitute_host_to_guest(host).unwrap_or_else(|| host.to_vec())
137}
138
139/// `readlink(2)` on a host path, as bytes.
140///
141/// # Arguments
142/// * `host` - Host path to read.
143pub fn read_link(host: &[u8]) -> Result<Vec<u8>> {
144    let c = std::ffi::CString::new(host.to_vec()).map_err(|_| libc::EINVAL)?;
145    let mut buf = vec![0u8; PATH_MAX];
146    let n = unsafe { libc::readlink(c.as_ptr(), buf.as_mut_ptr() as *mut libc::c_char, buf.len()) };
147    if n < 0 {
148        return Err(crate::sys::errno());
149    }
150    buf.truncate(n as usize);
151    Ok(buf)
152}