cap_primitives/fs/
rename.rs

1//! This defines `rename`, the primary entrypoint to sandboxed renaming.
2
3#[cfg(all(racy_asserts, not(windows)))]
4use crate::fs::append_dir_suffix;
5use crate::fs::rename_impl;
6use std::path::Path;
7use std::{fs, io};
8#[cfg(racy_asserts)]
9use {
10    crate::fs::{
11        manually, map_result, path_requires_dir, rename_unchecked, stat_unchecked, FollowSymlinks,
12        Metadata,
13    },
14    std::path::PathBuf,
15};
16
17/// Perform a `renameat`-like operation, ensuring that the resolution of both
18/// the old and new paths never escape the directory tree rooted at their
19/// respective starts.
20#[cfg_attr(not(racy_asserts), allow(clippy::let_and_return))]
21#[inline]
22pub fn rename(
23    old_start: &fs::File,
24    old_path: &Path,
25    new_start: &fs::File,
26    new_path: &Path,
27) -> io::Result<()> {
28    #[cfg(racy_asserts)]
29    let (old_metadata_before, new_metadata_before) = (
30        stat_unchecked(old_start, old_path, FollowSymlinks::No),
31        stat_unchecked(new_start, new_path, FollowSymlinks::No),
32    );
33
34    // Call the underlying implementation.
35    let result = rename_impl(old_start, old_path, new_start, new_path);
36
37    #[cfg(racy_asserts)]
38    let (old_metadata_after, new_metadata_after) = (
39        stat_unchecked(old_start, old_path, FollowSymlinks::No),
40        stat_unchecked(new_start, new_path, FollowSymlinks::No),
41    );
42
43    #[cfg(racy_asserts)]
44    check_rename(
45        old_start,
46        old_path,
47        new_start,
48        new_path,
49        &old_metadata_before,
50        &new_metadata_before,
51        &result,
52        &old_metadata_after,
53        &new_metadata_after,
54    );
55
56    result
57}
58
59#[cfg(racy_asserts)]
60#[allow(clippy::too_many_arguments)]
61#[allow(clippy::enum_glob_use)]
62fn check_rename(
63    old_start: &fs::File,
64    old_path: &Path,
65    new_start: &fs::File,
66    new_path: &Path,
67    old_metadata_before: &io::Result<Metadata>,
68    new_metadata_before: &io::Result<Metadata>,
69    result: &io::Result<()>,
70    old_metadata_after: &io::Result<Metadata>,
71    new_metadata_after: &io::Result<Metadata>,
72) {
73    use io::ErrorKind::*;
74
75    match (
76        map_result(old_metadata_before),
77        map_result(new_metadata_before),
78        map_result(result),
79        map_result(old_metadata_after),
80        map_result(new_metadata_after),
81    ) {
82        (
83            Ok(old_metadata_before),
84            Err((NotFound, _)),
85            Ok(()),
86            Err((NotFound, _)),
87            Ok(new_metadata_after),
88        ) => {
89            assert_same_file_metadata!(&old_metadata_before, &new_metadata_after);
90        }
91
92        (_, Ok(new_metadata_before), Err((AlreadyExists, _)), _, Ok(new_metadata_after)) => {
93            assert_same_file_metadata!(&new_metadata_before, &new_metadata_after);
94        }
95
96        (_, _, Err((kind, message)), _, _) => match (
97            map_result(&canonicalize_for_rename(old_start, old_path)),
98            map_result(&canonicalize_for_rename(new_start, new_path)),
99        ) {
100            (Ok(old_canon), Ok(new_canon)) => match map_result(&rename_unchecked(
101                old_start, &old_canon, new_start, &new_canon,
102            )) {
103                Err((_unchecked_kind, _unchecked_message)) => {
104                    /* TODO: Check error messages.
105                    assert_eq!(kind, unchecked_kind);
106                    assert_eq!(message, unchecked_message);
107                    */
108                }
109                other => panic!(
110                    "unsandboxed rename success:\n{:#?}\n{:?} {:?}",
111                    other, kind, message
112                ),
113            },
114            (Err((_old_canon_kind, _old_canon_message)), _) => {
115                /* TODO: Check error messages.
116                assert_eq!(kind, old_canon_kind);
117                assert_eq!(message, old_canon_message);
118                */
119            }
120            (_, Err((_new_canon_kind, _new_canon_message))) => {
121                /* TODO: Check error messages.
122                assert_eq!(kind, new_canon_kind);
123                assert_eq!(message, new_canon_message);
124                */
125            }
126        },
127
128        _other => {
129            /* TODO: Check error messages.
130            panic!(
131                "inconsistent rename checks: old_start='{:?}', old_path='{}', new_start='{:?}', \
132                 new_path='{}':\n{:#?}",
133                old_start,
134                old_path.display(),
135                new_start,
136                new_path.display(),
137                other
138            )
139            */
140        }
141    }
142}
143
144#[cfg(racy_asserts)]
145fn canonicalize_for_rename(start: &fs::File, path: &Path) -> io::Result<PathBuf> {
146    let mut canon = manually::canonicalize_with(start, path, FollowSymlinks::No)?;
147
148    // Rename on paths ending in `.` or `/.` fails due to the directory already
149    // being open. Ensure that this happens on the canonical paths too.
150    #[cfg(not(windows))]
151    if path_requires_dir(path) {
152        canon = append_dir_suffix(path.to_path_buf());
153
154        assert!(path_requires_dir(&canon));
155    }
156
157    Ok(canon)
158}