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