summaryrefslogtreecommitdiffstats
path: root/vendor/gix-fs/src/dir/create.rs
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-18 02:49:50 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-18 02:49:50 +0000
commit9835e2ae736235810b4ea1c162ca5e65c547e770 (patch)
tree3fcebf40ed70e581d776a8a4c65923e8ec20e026 /vendor/gix-fs/src/dir/create.rs
parentReleasing progress-linux version 1.70.0+dfsg2-1~progress7.99u1. (diff)
downloadrustc-9835e2ae736235810b4ea1c162ca5e65c547e770.tar.xz
rustc-9835e2ae736235810b4ea1c162ca5e65c547e770.zip
Merging upstream version 1.71.1+dfsg1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/gix-fs/src/dir/create.rs')
-rw-r--r--vendor/gix-fs/src/dir/create.rs202
1 files changed, 202 insertions, 0 deletions
diff --git a/vendor/gix-fs/src/dir/create.rs b/vendor/gix-fs/src/dir/create.rs
new file mode 100644
index 000000000..7c7c9a033
--- /dev/null
+++ b/vendor/gix-fs/src/dir/create.rs
@@ -0,0 +1,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<'a> fmt::Display for Error<'a> {
+ 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<'a> std::error::Error for Error<'a> {
+ 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 pernanent_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.pernanent_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.pernanent_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.pernanent_failure(dir, NotFound);
+ };
+ self.cursors.push(dir);
+ self.cursors.push(match dir.parent() {
+ None => return self.pernanent_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.pernanent_failure(dir, Interrupted);
+ };
+ self.cursors.push(dir);
+ self.intermediate_failure(dir, err)
+ }
+ _unexpected_kind => self.pernanent_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)
+}