Commit 2042d093 authored by Nick Mathewson's avatar Nick Mathewson 🦞
Browse files

Support for "create missing directory".

parent 712b4617
Loading
Loading
Loading
Loading
+5 −0
Original line number Original line Diff line number Diff line
@@ -67,6 +67,10 @@ pub enum Error {
    /// impossible.
    /// impossible.
    #[error("Problem finding current directory")]
    #[error("Problem finding current directory")]
    CurrentDirectory(#[source] Arc<IoError>),
    CurrentDirectory(#[source] Arc<IoError>),

    /// We tried to create a directory, and encountered a failure in doing so.
    #[error("Problem creating directory")]
    CreatingDir(#[source] Arc<IoError>),
}
}


impl Error {
impl Error {
@@ -90,6 +94,7 @@ impl Error {
                Error::Multiple(_) => return None,
                Error::Multiple(_) => return None,
                Error::StepsExceeded => return None,
                Error::StepsExceeded => return None,
                Error::CurrentDirectory(_) => return None,
                Error::CurrentDirectory(_) => return None,
                Error::CreatingDir(_) => return None,
            }
            }
            .as_path(),
            .as_path(),
        )
        )
+82 −2
Original line number Original line Diff line number Diff line
@@ -86,7 +86,6 @@
//!     https://gitlab.torproject.org/tpo/core/tor/-/blob/main/src/lib/fs/dir.c#L70
//!     https://gitlab.torproject.org/tpo/core/tor/-/blob/main/src/lib/fs/dir.c#L70


// TODO: Stuff to add before this crate is ready....
// TODO: Stuff to add before this crate is ready....
//  - Ability to create directory if it doesn't exist.
//  - Test the absolute heck out of it.
//  - Test the absolute heck out of it.


// POSSIBLY TODO:
// POSSIBLY TODO:
@@ -131,7 +130,11 @@ mod imp;
pub(crate) mod testing;
pub(crate) mod testing;
pub mod walk;
pub mod walk;


use std::path::{Path, PathBuf};
use std::{
    fs::DirBuilder,
    path::{Path, PathBuf},
    sync::Arc,
};


pub use err::Error;
pub use err::Error;


@@ -292,6 +295,15 @@ impl Mistrust {
    pub fn check_directory<P: AsRef<Path>>(&self, dir: P) -> Result<()> {
    pub fn check_directory<P: AsRef<Path>>(&self, dir: P) -> Result<()> {
        self.verifier().require_directory().check(dir)
        self.verifier().require_directory().check(dir)
    }
    }

    /// As `check_directory`, but create the directory if needed.
    ///
    /// `m.check_directory(dir)` is equivalent to
    /// `m.verifier().make_directory(dir)`.  If you need different behavior, see
    /// [`Verifier`] for more options.
    pub fn make_directory<P: AsRef<Path>>(&self, dir: P) -> Result<()> {
        self.verifier().make_directory(dir)
    }
}
}


impl<'a> Verifier<'a> {
impl<'a> Verifier<'a> {
@@ -370,6 +382,44 @@ impl<'a> Verifier<'a> {
            None => Ok(()),
            None => Ok(()),
        }
        }
    }
    }

    /// Check whether `path` is a valid directory, and create it if it doesn't
    /// exist.
    ///
    /// Returns `Ok` if the directory already existed or if it was just created,
    /// and it conforms to the requirements of this `Verifier` and the
    /// [`Mistrust`] that created it.
    ///
    /// Return an error if:
    ///  * there was a permissions or ownership problem in the path or any of
    ///    its ancestors,
    ///  * there was a problem when creating the directory
    ///  * after creating the directory, we found that it had a permissions or
    ///    ownership problem.
    pub fn make_directory<P: AsRef<Path>>(mut self, path: P) -> Result<()> {
        self.enforce_type = Some(Type::Dir);

        let path = path.as_ref();
        match self.clone().check(path) {
            Err(Error::NotFound(_)) => {}
            Err(other_error) => return Err(other_error),
            Ok(()) => return Ok(()), // no error; file exists.
        }

        // Looks like we got a "not found", so we're creating the path.
        let mut bld = DirBuilder::new();
        #[cfg(target_family = "unix")]
        {
            use std::os::unix::fs::DirBuilderExt;
            bld.mode(0o700);
        }
        bld.recursive(true)
            .create(path)
            .map_err(|e| Error::CreatingDir(Arc::new(e)))?;

        // We built the path!  But for paranoia's sake, check it again.
        self.check(path)
    }
}
}


#[cfg(test)]
#[cfg(test)]
@@ -576,6 +626,36 @@ mod test {
        assert!(matches!(e, Error::BadPermission(_, _)));
        assert!(matches!(e, Error::BadPermission(_, _)));
    }
    }


    #[test]
    fn make_directory() {
        let d = Dir::new();
        d.dir("a/b");

        let mut m = Mistrust::new();
        m.ignore_prefix(d.canonical_root()).unwrap();

        #[cfg(target_family = "unix")]
        {
            // Try once with bad permissions.
            d.chmod("a", 0o777);
            let e = m.make_directory(d.path("a/b/c/d")).unwrap_err();
            assert!(matches!(e, Error::BadPermission(_, _)));

            // Now make the permissions correct.
            d.chmod("a", 0o0700);
            d.chmod("a/b", 0o0700);
        }

        // Make the directory!
        m.make_directory(d.path("a/b/c/d")).unwrap();

        // Make sure it exists and has good permissions.
        m.check_directory(d.path("a/b/c/d")).unwrap();

        // Try make_directory again and make sure _that_ succeeds.
        m.make_directory(d.path("a/b/c/d")).unwrap();
    }

    // TODO: Write far more tests.
    // TODO: Write far more tests.
    // * Can there be a test for a failed readlink()?  I can't see an easy way
    // * Can there be a test for a failed readlink()?  I can't see an easy way
    //   to provoke that without trying to make a time-of-check/time-of-use race
    //   to provoke that without trying to make a time-of-check/time-of-use race