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"))]
14trait PathExt {
15 fn anonymize_home(&self) -> impl std::fmt::Display + '_;
17}
18#[cfg(not(feature = "anon_home"))]
19impl PathExt for Path {
20 #[allow(clippy::disallowed_methods)] fn anonymize_home(&self) -> impl std::fmt::Display + '_ {
22 self.display()
23 }
24}
25
26#[derive(Clone, Debug, thiserror::Error)]
35#[non_exhaustive]
36pub enum Error {
37 #[error("File or directory {} not found", _0.anonymize_home())]
39 NotFound(PathBuf),
40
41 #[error("Incorrect permissions: {} is {}; must be {}",
48 _0.anonymize_home(),
49 format_access_bits(* .1, '='),
50 format_access_bits(* .2, '-'))]
51 BadPermission(PathBuf, u32, u32),
52
53 #[error("Bad owner (UID {1}) on file or directory {anon}", anon = _0.anonymize_home())]
59 BadOwner(PathBuf, u32),
60
61 #[error("Wrong type of file at {}", _0.anonymize_home())]
67 BadType(PathBuf),
68
69 #[error("Unable to access {}", _0.anonymize_home())]
77 CouldNotInspect(PathBuf, #[source] Arc<IoError>),
78
79 #[error("Multiple errors found")]
86 Multiple(Vec<Box<Error>>),
87
88 #[error("Too many steps taken or planned: Possible symlink loop?")]
92 StepsExceeded,
93
94 #[error("Problem finding current directory")]
97 CurrentDirectory(#[source] Arc<IoError>),
98
99 #[error("Problem creating directory")]
101 CreatingDir(#[source] Arc<IoError>),
102
103 #[error("Problem in directory contents")]
105 Content(#[source] Box<Error>),
106
107 #[cfg(feature = "walkdir")]
111 #[error("Unable to list directory contents")]
112 Listing(#[source] Arc<walkdir::Error>),
113
114 #[error("Provided path was not valid for use with CheckedDir")]
116 InvalidSubdirectory,
117
118 #[error("IO error on {} while attempting to {action}", filename.anonymize_home())]
120 Io {
121 filename: PathBuf,
123 action: &'static str,
125 #[source]
127 err: Arc<IoError>,
128 },
129
130 #[error("Missing field when constructing Mistrust")]
133 MissingField(#[from] derive_builder::UninitializedFieldError),
134
135 #[error("Configured with nonexistent group: {0}")]
137 NoSuchGroup(String),
138
139 #[error("Configured with nonexistent user: {0}")]
141 NoSuchUser(String),
142
143 #[error("Error accessing passwd/group databases or obtaining our uids/gids")]
145 PasswdGroupIoError(#[source] Arc<IoError>),
146}
147
148impl Error {
149 pub(crate) fn inspecting(err: IoError, fname: impl Into<PathBuf>) -> Self {
152 match err.kind() {
153 IoErrorKind::NotFound => Error::NotFound(fname.into()),
154 _ => Error::CouldNotInspect(fname.into(), Arc::new(err)),
155 }
156 }
157
158 pub(crate) fn io(err: IoError, fname: impl Into<PathBuf>, action: &'static str) -> Self {
161 match err.kind() {
162 IoErrorKind::NotFound => Error::NotFound(fname.into()),
163 _ => Error::Io {
164 filename: fname.into(),
165 action,
166 err: Arc::new(err),
167 },
168 }
169 }
170
171 pub fn path(&self) -> Option<&Path> {
173 Some(
174 match self {
175 Error::NotFound(pb) => pb,
176 Error::BadPermission(pb, ..) => pb,
177 Error::BadOwner(pb, _) => pb,
178 Error::BadType(pb) => pb,
179 Error::CouldNotInspect(pb, _) => pb,
180 Error::Io { filename: pb, .. } => pb,
181 Error::Multiple(_) => return None,
182 Error::StepsExceeded => return None,
183 Error::CurrentDirectory(_) => return None,
184 Error::CreatingDir(_) => return None,
185 Error::InvalidSubdirectory => return None,
186 Error::Content(e) => return e.path(),
187 #[cfg(feature = "walkdir")]
188 Error::Listing(e) => return e.path(),
189 Error::MissingField(_) => return None,
190 Error::NoSuchGroup(_) => return None,
191 Error::NoSuchUser(_) => return None,
192 Error::PasswdGroupIoError(_) => return None,
193 }
194 .as_path(),
195 )
196 }
197
198 pub fn is_bad_permission(&self) -> bool {
204 match self {
205 Error::BadPermission(..) | Error::BadOwner(_, _) | Error::BadType(_) => true,
206
207 Error::NotFound(_)
208 | Error::CouldNotInspect(_, _)
209 | Error::StepsExceeded
210 | Error::CurrentDirectory(_)
211 | Error::CreatingDir(_)
212 | Error::InvalidSubdirectory
213 | Error::Io { .. }
214 | Error::MissingField(_)
215 | Error::NoSuchGroup(_)
216 | Error::NoSuchUser(_)
217 | Error::PasswdGroupIoError(_) => false,
218
219 #[cfg(feature = "walkdir")]
220 Error::Listing(_) => false,
221
222 Error::Multiple(errs) => errs.iter().any(|e| e.is_bad_permission()),
223 Error::Content(err) => err.is_bad_permission(),
224 }
225 }
226
227 pub fn errors<'a>(&'a self) -> impl Iterator<Item = &'a Error> + 'a {
236 let result: Box<dyn Iterator<Item = &Error> + 'a> = match self {
237 Error::Multiple(v) => Box::new(v.iter().map(|e| e.as_ref())),
238 _ => Box::new(vec![self].into_iter()),
239 };
240
241 result
242 }
243}
244
245impl std::iter::FromIterator<Error> for Option<Error> {
246 fn from_iter<T: IntoIterator<Item = Error>>(iter: T) -> Self {
247 let mut iter = iter.into_iter();
248
249 let first_err = iter.next()?;
250
251 if let Some(second_err) = iter.next() {
252 let mut errors = Vec::with_capacity(iter.size_hint().0 + 2);
253 errors.push(Box::new(first_err));
254 errors.push(Box::new(second_err));
255 errors.extend(iter.map(Box::new));
256 Some(Error::Multiple(errors))
257 } else {
258 Some(first_err)
259 }
260 }
261}
262
263pub fn format_access_bits(bits: u32, c: char) -> String {
270 let mut s = String::new();
271
272 for (shift, prefix) in [(6, 'u'), (3, 'g'), (0, 'o')] {
273 let b = (bits >> shift) & 7;
274 if b != 0 {
275 if !s.is_empty() {
276 s.push(',');
277 }
278 s.push(prefix);
279 s.push(c);
280 for (bit, ch) in [(4, 'r'), (2, 'w'), (1, 'x')] {
281 if b & bit != 0 {
282 s.push(ch);
283 }
284 }
285 }
286 }
287
288 s
289}
290
291#[cfg(test)]
292mod test {
293 #![allow(clippy::bool_assert_comparison)]
295 #![allow(clippy::clone_on_copy)]
296 #![allow(clippy::dbg_macro)]
297 #![allow(clippy::mixed_attributes_style)]
298 #![allow(clippy::print_stderr)]
299 #![allow(clippy::print_stdout)]
300 #![allow(clippy::single_char_pattern)]
301 #![allow(clippy::unwrap_used)]
302 #![allow(clippy::unchecked_duration_subtraction)]
303 #![allow(clippy::useless_vec)]
304 #![allow(clippy::needless_pass_by_value)]
305 use super::*;
307
308 #[test]
309 fn bits() {
310 assert_eq!(format_access_bits(0o777, '='), "u=rwx,g=rwx,o=rwx");
311 assert_eq!(format_access_bits(0o022, '='), "g=w,o=w");
312 assert_eq!(format_access_bits(0o022, '-'), "g-w,o-w");
313 assert_eq!(format_access_bits(0o020, '-'), "g-w");
314 assert_eq!(format_access_bits(0, ' '), "");
315 }
316
317 #[test]
318 fn bad_perms() {
319 assert_eq!(
320 Error::BadPermission(PathBuf::from("/path"), 0o777, 0o022).to_string(),
321 "Incorrect permissions: /path is u=rwx,g=rwx,o=rwx; must be g-w,o-w"
322 );
323 }
324}