cap_primitives/fs/
remove_file.rs

1//! This defines `remove_file`, the primary entrypoint to sandboxed file
2//! removal.
3
4use crate::fs::remove_file_impl;
5#[cfg(racy_asserts)]
6use crate::fs::{
7    manually, map_result, remove_file_unchecked, stat_unchecked, FollowSymlinks, Metadata,
8};
9use std::path::Path;
10use std::{fs, io};
11
12/// Perform a `remove_fileat`-like operation, ensuring that the resolution of
13/// the path never escapes the directory tree rooted at `start`.
14#[cfg_attr(not(racy_asserts), allow(clippy::let_and_return))]
15#[inline]
16pub fn remove_file(start: &fs::File, path: &Path) -> io::Result<()> {
17    #[cfg(racy_asserts)]
18    let stat_before = stat_unchecked(start, path, FollowSymlinks::No);
19
20    // Call the underlying implementation.
21    let result = remove_file_impl(start, path);
22
23    #[cfg(racy_asserts)]
24    let stat_after = stat_unchecked(start, path, FollowSymlinks::No);
25
26    #[cfg(racy_asserts)]
27    check_remove_file(start, path, &stat_before, &result, &stat_after);
28
29    result
30}
31
32#[cfg(racy_asserts)]
33#[allow(clippy::enum_glob_use)]
34fn check_remove_file(
35    start: &fs::File,
36    path: &Path,
37    stat_before: &io::Result<Metadata>,
38    result: &io::Result<()>,
39    stat_after: &io::Result<Metadata>,
40) {
41    use io::ErrorKind::*;
42
43    match (
44        map_result(stat_before),
45        map_result(result),
46        map_result(stat_after),
47    ) {
48        (Ok(metadata), Ok(()), Err((NotFound, _))) => {
49            // TODO: Check that the path was inside the sandbox.
50            assert!(!metadata.is_dir());
51        }
52
53        (Err((Other, _)), Ok(()), Err((NotFound, _))) => {
54            // TODO: Check that the path was inside the sandbox.
55        }
56
57        (_, Err((_kind, _message)), _) => {
58            match map_result(&manually::canonicalize_with(
59                start,
60                path,
61                FollowSymlinks::No,
62            )) {
63                Ok(canon) => match map_result(&remove_file_unchecked(start, &canon)) {
64                    Err((_unchecked_kind, _unchecked_message)) => {
65                        /* TODO: Check error messages.
66                        assert_eq!(
67                            kind,
68                            unchecked_kind,
69                            "unexpected error kind from remove_file start='{:?}', \
70                             path='{}':\nstat_before={:#?}\nresult={:#?}\nstat_after={:#?}",
71                            start,
72                            path.display(),
73                            stat_before,
74                            result,
75                            stat_after
76                        );
77                        assert_eq!(message, unchecked_message);
78                        */
79                    }
80                    _ => panic!("unsandboxed remove_file success"),
81                },
82                Err((_canon_kind, _canon_message)) => {
83                    /* TODO: Check error messages.
84                    assert_eq!(kind, canon_kind, "'{}' vs '{}'", message, canon_message);
85                    assert_eq!(message, canon_message);
86                    */
87                }
88            }
89        }
90
91        other => panic!(
92            "inconsistent remove_file checks: start='{:?}' path='{}':\n{:#?}",
93            start,
94            path.display(),
95            other,
96        ),
97    }
98
99    match (result, stat_after) {
100        (Ok(()), Ok(_unchecked_metadata)) => panic!(
101            "file still exists after remove_file start='{:?}', path='{}'",
102            start,
103            path.display()
104        ),
105        (Err(e), Ok(unchecked_metadata)) => match e.kind() {
106            #[cfg(io_error_more)]
107            io::ErrorKind::IsADirectory => assert!(unchecked_metadata.is_dir()),
108            io::ErrorKind::PermissionDenied => (),
109            #[cfg(not(io_error_more))]
110            io::ErrorKind::Other if unchecked_metadata.is_dir() => (),
111            _ => panic!(
112                "unexpected error removing file start='{:?}', path='{}': {:?}",
113                start,
114                path.display(),
115                e
116            ),
117        },
118        (Ok(()), Err(_unchecked_error)) => (),
119        (Err(result_error), Err(_unchecked_error)) => match result_error.kind() {
120            io::ErrorKind::PermissionDenied => (),
121            _ => {
122                /* TODO: Check error messages.
123                assert_eq!(result_error.to_string(), unchecked_error.to_string());
124                assert_eq!(result_error.kind(), unchecked_error.kind());
125                */
126            }
127        },
128    }
129}