Coverage Report

Created: 2025-07-18 06:03

/rust/registry/src/index.crates.io-6f17d22bba15001f/cap-primitives-3.4.4/src/fs/rename.rs
Line
Count
Source (jump to first uncovered line)
1
//! This defines `rename`, the primary entrypoint to sandboxed renaming.
2
3
#[cfg(all(racy_asserts, not(windows)))]
4
use crate::fs::append_dir_suffix;
5
use crate::fs::rename_impl;
6
use std::path::Path;
7
use std::{fs, io};
8
#[cfg(racy_asserts)]
9
use {
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]
22
0
pub fn rename(
23
0
    old_start: &fs::File,
24
0
    old_path: &Path,
25
0
    new_start: &fs::File,
26
0
    new_path: &Path,
27
0
) -> io::Result<()> {
28
0
    #[cfg(racy_asserts)]
29
0
    let (old_metadata_before, new_metadata_before) = (
30
0
        stat_unchecked(old_start, old_path, FollowSymlinks::No),
31
0
        stat_unchecked(new_start, new_path, FollowSymlinks::No),
32
0
    );
33
0
34
0
    // Call the underlying implementation.
35
0
    let result = rename_impl(old_start, old_path, new_start, new_path);
36
0
37
0
    #[cfg(racy_asserts)]
38
0
    let (old_metadata_after, new_metadata_after) = (
39
0
        stat_unchecked(old_start, old_path, FollowSymlinks::No),
40
0
        stat_unchecked(new_start, new_path, FollowSymlinks::No),
41
0
    );
42
0
43
0
    #[cfg(racy_asserts)]
44
0
    check_rename(
45
0
        old_start,
46
0
        old_path,
47
0
        new_start,
48
0
        new_path,
49
0
        &old_metadata_before,
50
0
        &new_metadata_before,
51
0
        &result,
52
0
        &old_metadata_after,
53
0
        &new_metadata_after,
54
0
    );
55
0
56
0
    result
57
0
}
58
59
#[cfg(racy_asserts)]
60
#[allow(clippy::too_many_arguments)]
61
#[allow(clippy::enum_glob_use)]
62
fn 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)]
145
fn 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
}