sys_util: Add TempDir class

This will be used to create temporary directories that are lifetime
managed for running jails.

Change-Id: I35dfeae76a211c820db090b65baf72277d9e2d8a
Signed-off-by: Dylan Reid <dgreid@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/520706
Reviewed-by: Zach Reizner <zachr@chromium.org>
diff --git a/sys_util/src/lib.rs b/sys_util/src/lib.rs
index 79a9691..f4bf241 100644
--- a/sys_util/src/lib.rs
+++ b/sys_util/src/lib.rs
@@ -12,6 +12,7 @@
 mod guest_address;
 mod guest_memory;
 mod struct_util;
+mod tempdir;
 
 pub use mmap::*;
 pub use eventfd::*;
@@ -20,3 +21,4 @@
 pub use guest_address::*;
 pub use guest_memory::*;
 pub use struct_util::*;
+pub use tempdir::*;
diff --git a/sys_util/src/tempdir.rs b/sys_util/src/tempdir.rs
new file mode 100644
index 0000000..6a27652
--- /dev/null
+++ b/sys_util/src/tempdir.rs
@@ -0,0 +1,100 @@
+// Copyright 2017 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::ffi::CString;
+use std::ffi::OsStr;
+use std::ffi::OsString;
+use std::fs;
+use std::os::unix::ffi::OsStringExt;
+use std::path::Path;
+use std::path::PathBuf;
+
+use libc;
+
+use {Result, errno_result};
+
+/// Create and remove a temporary directory.  The directory will be maintained for the lifetime of
+/// the `TempDir` object.
+pub struct TempDir {
+    path: Option<PathBuf>,
+}
+
+impl TempDir {
+    /// Creates a new tempory directory.
+    /// The directory will be removed when the object goes out of scope.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # use std::path::Path;
+    /// # use std::path::PathBuf;
+    /// # use sys_util::TempDir;
+    /// # fn test_create_temp_dir() -> Result<(), ()> {
+    ///       let t = TempDir::new("/tmp/testdir").map_err(|_| ())?;
+    ///       assert!(t.as_path().unwrap().exists());
+    /// #     Ok(())
+    /// # }
+    /// ```
+    pub fn new<P: AsRef<OsStr>>(prefix: P) -> Result<TempDir> {
+        let mut dir_string = prefix.as_ref().to_os_string();
+        dir_string.push("XXXXXX");
+        // unwrap this result as the internal bytes can't have a null with a valid path.
+        let dir_name = CString::new(dir_string.into_vec()).unwrap();
+        let mut dir_bytes = dir_name.into_bytes_with_nul();
+        let ret = unsafe {
+            // Creating the directory isn't unsafe.  The fact that it modifies the guts of the path
+            // is also OK because it only overwrites the last 6 Xs added above.
+            libc::mkdtemp(dir_bytes.as_mut_ptr() as *mut i8)
+        };
+        if ret.is_null() {
+            return errno_result();
+        }
+        dir_bytes.pop(); // Remove the null becasue from_vec can't handle it.
+        Ok(TempDir { path: Some(PathBuf::from(OsString::from_vec(dir_bytes))) })
+    }
+
+    /// Removes the temporary directory.  Calling this is optional as dropping a `TempDir` object
+    /// will also remove the directory.  Calling remove explicitly allows for better error handling.
+    pub fn remove(mut self) -> Result<()> {
+        let path = self.path.take();
+        path.map_or(Ok(()), |ref p| fs::remove_dir_all(p))?;
+        Ok(())
+    }
+
+    /// Returns the path to the tempdir if it is currently valid
+    pub fn as_path(&self) -> Option<&Path> {
+        self.path.as_ref().map(|ref p| p.as_path())
+    }
+}
+
+impl Drop for TempDir {
+    fn drop(&mut self) {
+        if let Some(ref p) = self.path {
+            // Nothing can be done here if this returns an error.
+            let _ = fs::remove_dir_all(p);
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn create_dir() {
+        let t = TempDir::new("/tmp/asdf").unwrap();
+        let path = t.as_path().unwrap();
+        assert!(path.exists());
+        assert!(path.is_dir());
+        assert!(path.starts_with("/tmp/"));
+    }
+
+    #[test]
+    fn remove_dir() {
+        let t = TempDir::new("/tmp/asdf").unwrap();
+        let path = t.as_path().unwrap().to_owned();
+        assert!(t.remove().is_ok());
+        assert!(!path.exists());
+    }
+}