gix_fs/dir/
create.rs

1//!
2use std::path::Path;
3
4/// The amount of retries to do during various aspects of the directory creation.
5#[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq)]
6pub struct Retries {
7    /// How many times the whole directory can be created in the light of racy interference.
8    /// This count combats racy situations where another process is trying to remove a directory that we want to create,
9    /// and is deliberately higher than those who do deletion. That way, creation usually wins.
10    pub to_create_entire_directory: usize,
11    /// The amount of times we can try to create a directory because we couldn't as the parent didn't exist.
12    /// This amounts to the maximum subdirectory depth we allow to be created. Counts once per attempt to create the entire directory.
13    pub on_create_directory_failure: usize,
14    /// How often to retry to create a single directory if an interrupt happens, as caused by signals.
15    pub on_interrupt: usize,
16}
17
18impl Default for Retries {
19    fn default() -> Self {
20        Retries {
21            on_interrupt: 10,
22            to_create_entire_directory: 5,
23            on_create_directory_failure: 25,
24        }
25    }
26}
27
28mod error {
29    use std::{fmt, path::Path};
30
31    use crate::dir::create::Retries;
32
33    /// The error returned by [all()][super::all()].
34    #[allow(missing_docs)]
35    #[derive(Debug)]
36    pub enum Error<'a> {
37        /// A failure we will probably recover from by trying again.
38        Intermediate { dir: &'a Path, kind: std::io::ErrorKind },
39        /// A failure that ends the operation.
40        Permanent {
41            dir: &'a Path,
42            err: std::io::Error,
43            /// The retries left after running the operation
44            retries_left: Retries,
45            /// The original amount of retries to allow determining how many were actually used
46            retries: Retries,
47        },
48    }
49
50    impl fmt::Display for Error<'_> {
51        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52            match self {
53                Error::Intermediate { dir, kind } => write!(
54                    f,
55                    "Intermediae failure creating {:?} with error: {:?}",
56                    dir.display(),
57                    kind
58                ),
59                Error::Permanent {
60                    err: _,
61                    dir,
62                    retries_left,
63                    retries,
64                } => write!(
65                    f,
66                    "Permanently failing to create directory '{dir}' ({retries_left:?} of {retries:?})",
67                    dir = dir.display(),
68                ),
69            }
70        }
71    }
72
73    impl std::error::Error for Error<'_> {
74        fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
75            match self {
76                Error::Permanent { err, .. } => Some(err),
77                _ => None,
78            }
79        }
80    }
81}
82pub use error::Error;
83
84enum State {
85    CurrentlyCreatingDirectories,
86    SearchingUpwardsForExistingDirectory,
87}
88
89/// A special iterator which communicates its operation through results where…
90///
91/// * `Some(Ok(created_directory))` is yielded once or more success, followed by `None`
92/// * `Some(Err(Error::Intermediate))` is yielded zero or more times while trying to create the directory.
93/// * `Some(Err(Error::Permanent))` is yielded exactly once on failure.
94pub struct Iter<'a> {
95    cursors: Vec<&'a Path>,
96    retries: Retries,
97    original_retries: Retries,
98    state: State,
99}
100
101/// Construction
102impl<'a> Iter<'a> {
103    /// Create a new instance that creates `target` when iterated with the default amount of [`Retries`].
104    pub fn new(target: &'a Path) -> Self {
105        Self::new_with_retries(target, Default::default())
106    }
107
108    /// Create a new instance that creates `target` when iterated with the specified amount of `retries`.
109    pub fn new_with_retries(target: &'a Path, retries: Retries) -> Self {
110        Iter {
111            cursors: vec![target],
112            original_retries: retries,
113            retries,
114            state: State::SearchingUpwardsForExistingDirectory,
115        }
116    }
117}
118
119impl<'a> Iter<'a> {
120    fn permanent_failure(
121        &mut self,
122        dir: &'a Path,
123        err: impl Into<std::io::Error>,
124    ) -> Option<Result<&'a Path, Error<'a>>> {
125        self.cursors.clear();
126        Some(Err(Error::Permanent {
127            err: err.into(),
128            dir,
129            retries_left: self.retries,
130            retries: self.original_retries,
131        }))
132    }
133
134    fn intermediate_failure(&self, dir: &'a Path, err: std::io::Error) -> Option<Result<&'a Path, Error<'a>>> {
135        Some(Err(Error::Intermediate { dir, kind: err.kind() }))
136    }
137}
138
139impl<'a> Iterator for Iter<'a> {
140    type Item = Result<&'a Path, Error<'a>>;
141
142    fn next(&mut self) -> Option<Self::Item> {
143        use std::io::ErrorKind::*;
144        match self.cursors.pop() {
145            Some(dir) => match std::fs::create_dir(dir) {
146                Ok(()) => {
147                    self.state = State::CurrentlyCreatingDirectories;
148                    Some(Ok(dir))
149                }
150                Err(err) => match err.kind() {
151                    AlreadyExists if dir.is_dir() => {
152                        self.state = State::CurrentlyCreatingDirectories;
153                        Some(Ok(dir))
154                    }
155                    AlreadyExists => self.permanent_failure(dir, err), // is non-directory
156                    NotFound => {
157                        self.retries.on_create_directory_failure -= 1;
158                        if let State::CurrentlyCreatingDirectories = self.state {
159                            self.state = State::SearchingUpwardsForExistingDirectory;
160                            self.retries.to_create_entire_directory -= 1;
161                            if self.retries.to_create_entire_directory < 1 {
162                                return self.permanent_failure(dir, NotFound);
163                            }
164                            self.retries.on_create_directory_failure =
165                                self.original_retries.on_create_directory_failure;
166                        }
167                        if self.retries.on_create_directory_failure < 1 {
168                            return self.permanent_failure(dir, NotFound);
169                        }
170                        self.cursors.push(dir);
171                        self.cursors.push(match dir.parent() {
172                            None => return self.permanent_failure(dir, InvalidInput),
173                            Some(parent) => parent,
174                        });
175                        self.intermediate_failure(dir, err)
176                    }
177                    Interrupted => {
178                        self.retries.on_interrupt -= 1;
179                        if self.retries.on_interrupt <= 1 {
180                            return self.permanent_failure(dir, Interrupted);
181                        }
182                        self.cursors.push(dir);
183                        self.intermediate_failure(dir, err)
184                    }
185                    _unexpected_kind => self.permanent_failure(dir, err),
186                },
187            },
188            None => None,
189        }
190    }
191}
192
193/// Create all directories leading to `dir` including `dir` itself with the specified amount of `retries`.
194/// Returns the input `dir` on success that make it useful in expressions.
195pub fn all(dir: &Path, retries: Retries) -> std::io::Result<&Path> {
196    for res in Iter::new_with_retries(dir, retries) {
197        match res {
198            Err(Error::Permanent { err, .. }) => return Err(err),
199            Err(Error::Intermediate { .. }) | Ok(_) => continue,
200        }
201    }
202    Ok(dir)
203}