1use std::path::Path;
4use std::{path::PathBuf, sync::Arc};
5
6use std::io::{Error as IoError, ErrorKind as IoErrorKind};
7
8#[cfg(feature = "anon_home")]
9use crate::anon_home::PathExt as _;
10
11#[cfg(not(feature = "anon_home"))]
14#[extend::ext]
15impl Path {
16 #[allow(clippy::disallowed_methods)] fn anonymize_home(&self) -> impl std::fmt::Display + '_ {
19 self.display()
20 }
21}
22
23#[derive(Clone, Debug, thiserror::Error)]
32#[non_exhaustive]
33pub enum Error {
34 #[error(r#"File or directory "{}" not found"#, _0.anonymize_home())]
36 NotFound(PathBuf),
37
38 #[error(r#"Incorrect permissions: "{}" is {}; must be {}"#,
45 _0.anonymize_home(),
46 format_access_bits(* .1, '='),
47 format_access_bits(* .2, '-'))]
48 BadPermission(PathBuf, u32, u32),
49
50 #[error(r#"Bad owner (UID {1}) on file or directory "{anon}""#, anon = _0.anonymize_home())]
56 BadOwner(PathBuf, u32),
57
58 #[error(r#"Wrong type of file at "{}""#, _0.anonymize_home())]
64 BadType(PathBuf),
65
66 #[error(r#"Unable to access "{}""#, _0.anonymize_home())]
74 CouldNotInspect(PathBuf, #[source] Arc<IoError>),
75
76 #[error("Multiple errors found")]
83 Multiple(Vec<Box<Error>>),
84
85 #[error("Too many steps taken or planned: Possible symlink loop?")]
89 StepsExceeded,
90
91 #[error("Problem finding current directory")]
94 CurrentDirectory(#[source] Arc<IoError>),
95
96 #[error("Problem creating directory")]
98 CreatingDir(#[source] Arc<IoError>),
99
100 #[error("Problem in directory contents")]
102 Content(#[source] Box<Error>),
103
104 #[cfg(feature = "walkdir")]
108 #[error("Unable to list directory contents")]
109 Listing(#[source] Arc<walkdir::Error>),
110
111 #[error("Provided path was not valid for use with CheckedDir")]
113 InvalidSubdirectory,
114
115 #[error(r#"IO error on "{}" while attempting to {action}"#, filename.anonymize_home())]
117 Io {
118 filename: PathBuf,
120 action: &'static str,
122 #[source]
124 err: Arc<IoError>,
125 },
126
127 #[error("Missing field when constructing Mistrust")]
130 MissingField(#[from] derive_builder::UninitializedFieldError),
131
132 #[error(r#"Configured with nonexistent group "{0}""#)]
134 NoSuchGroup(String),
135
136 #[error(r#"Configured with nonexistent user: "{0}""#)]
138 NoSuchUser(String),
139
140 #[error("Error accessing passwd/group databases or obtaining our uids/gids")]
142 PasswdGroupIoError(#[source] Arc<IoError>),
143}
144
145impl Error {
146 pub(crate) fn inspecting(err: IoError, fname: impl Into<PathBuf>) -> Self {
149 match err.kind() {
150 IoErrorKind::NotFound => Error::NotFound(fname.into()),
151 _ => Error::CouldNotInspect(fname.into(), Arc::new(err)),
152 }
153 }
154
155 pub(crate) fn io(err: IoError, fname: impl Into<PathBuf>, action: &'static str) -> Self {
158 match err.kind() {
159 IoErrorKind::NotFound => Error::NotFound(fname.into()),
160 _ => Error::Io {
161 filename: fname.into(),
162 action,
163 err: Arc::new(err),
164 },
165 }
166 }
167
168 pub fn path(&self) -> Option<&Path> {
170 Some(
171 match self {
172 Error::NotFound(pb) => pb,
173 Error::BadPermission(pb, ..) => pb,
174 Error::BadOwner(pb, _) => pb,
175 Error::BadType(pb) => pb,
176 Error::CouldNotInspect(pb, _) => pb,
177 Error::Io { filename: pb, .. } => pb,
178 Error::Multiple(_) => return None,
179 Error::StepsExceeded => return None,
180 Error::CurrentDirectory(_) => return None,
181 Error::CreatingDir(_) => return None,
182 Error::InvalidSubdirectory => return None,
183 Error::Content(e) => return e.path(),
184 #[cfg(feature = "walkdir")]
185 Error::Listing(e) => return e.path(),
186 Error::MissingField(_) => return None,
187 Error::NoSuchGroup(_) => return None,
188 Error::NoSuchUser(_) => return None,
189 Error::PasswdGroupIoError(_) => return None,
190 }
191 .as_path(),
192 )
193 }
194
195 pub fn is_bad_permission(&self) -> bool {
201 match self {
202 Error::BadPermission(..) | Error::BadOwner(_, _) | Error::BadType(_) => true,
203
204 Error::NotFound(_)
205 | Error::CouldNotInspect(_, _)
206 | Error::StepsExceeded
207 | Error::CurrentDirectory(_)
208 | Error::CreatingDir(_)
209 | Error::InvalidSubdirectory
210 | Error::Io { .. }
211 | Error::MissingField(_)
212 | Error::NoSuchGroup(_)
213 | Error::NoSuchUser(_)
214 | Error::PasswdGroupIoError(_) => false,
215
216 #[cfg(feature = "walkdir")]
217 Error::Listing(_) => false,
218
219 Error::Multiple(errs) => errs.iter().any(|e| e.is_bad_permission()),
220 Error::Content(err) => err.is_bad_permission(),
221 }
222 }
223
224 pub fn errors<'a>(&'a self) -> impl Iterator<Item = &'a Error> + 'a {
233 let result: Box<dyn Iterator<Item = &Error> + 'a> = match self {
234 Error::Multiple(v) => Box::new(v.iter().map(|e| e.as_ref())),
235 _ => Box::new(vec![self].into_iter()),
236 };
237
238 result
239 }
240}
241
242impl std::iter::FromIterator<Error> for Option<Error> {
243 fn from_iter<T: IntoIterator<Item = Error>>(iter: T) -> Self {
244 let mut iter = iter.into_iter();
245
246 let first_err = iter.next()?;
247
248 if let Some(second_err) = iter.next() {
249 let mut errors = Vec::with_capacity(iter.size_hint().0 + 2);
250 errors.push(Box::new(first_err));
251 errors.push(Box::new(second_err));
252 errors.extend(iter.map(Box::new));
253 Some(Error::Multiple(errors))
254 } else {
255 Some(first_err)
256 }
257 }
258}
259
260pub fn format_access_bits(bits: u32, c: char) -> String {
267 let mut s = String::new();
268
269 for (shift, prefix) in [(6, 'u'), (3, 'g'), (0, 'o')] {
270 let b = (bits >> shift) & 7;
271 if b != 0 {
272 if !s.is_empty() {
273 s.push(',');
274 }
275 s.push(prefix);
276 s.push(c);
277 for (bit, ch) in [(4, 'r'), (2, 'w'), (1, 'x')] {
278 if b & bit != 0 {
279 s.push(ch);
280 }
281 }
282 }
283 }
284
285 s
286}
287
288#[cfg(test)]
289mod test {
290 #![allow(clippy::bool_assert_comparison)]
292 #![allow(clippy::clone_on_copy)]
293 #![allow(clippy::dbg_macro)]
294 #![allow(clippy::mixed_attributes_style)]
295 #![allow(clippy::print_stderr)]
296 #![allow(clippy::print_stdout)]
297 #![allow(clippy::single_char_pattern)]
298 #![allow(clippy::unwrap_used)]
299 #![allow(clippy::unchecked_time_subtraction)]
300 #![allow(clippy::useless_vec)]
301 #![allow(clippy::needless_pass_by_value)]
302 #![allow(clippy::string_slice)] use super::*;
305
306 #[test]
307 fn bits() {
308 assert_eq!(format_access_bits(0o777, '='), "u=rwx,g=rwx,o=rwx");
309 assert_eq!(format_access_bits(0o022, '='), "g=w,o=w");
310 assert_eq!(format_access_bits(0o022, '-'), "g-w,o-w");
311 assert_eq!(format_access_bits(0o020, '-'), "g-w");
312 assert_eq!(format_access_bits(0, ' '), "");
313 }
314
315 #[test]
316 fn bad_perms() {
317 assert_eq!(
318 Error::BadPermission(PathBuf::from("/path"), 0o777, 0o022).to_string(),
319 r#"Incorrect permissions: "/path" is u=rwx,g=rwx,o=rwx; must be g-w,o-w"#
320 );
321 }
322}