vmm_sys_util/unix/
tempdir.rs

1// Copyright 2019 Intel Corporation. All Rights Reserved.
2//
3// Copyright 2017 The Chromium OS Authors. All rights reserved.
4//
5// SPDX-License-Identifier: BSD-3-Clause
6
7//! Structure for handling temporary directories.
8use std::env::temp_dir;
9use std::ffi::{CString, OsStr, OsString};
10use std::fs;
11use std::os::unix::ffi::OsStringExt;
12use std::path::{Path, PathBuf};
13
14use crate::errno::{errno_result, Error, Result};
15
16/// Wrapper over a temporary directory.
17///
18/// The directory will be maintained for the lifetime of the `TempDir` object.
19#[derive(Debug)]
20pub struct TempDir {
21    path: PathBuf,
22}
23
24impl TempDir {
25    /// Creates a new temporary directory with `prefix`.
26    ///
27    /// The directory will be removed when the object goes out of scope.
28    ///
29    /// # Examples
30    ///
31    /// ```
32    /// # use vmm_sys_util::tempdir::TempDir;
33    /// let t = TempDir::new_with_prefix("/tmp/testdir").unwrap();
34    /// ```
35    pub fn new_with_prefix<P: AsRef<OsStr>>(prefix: P) -> Result<TempDir> {
36        let mut dir_string = prefix.as_ref().to_os_string();
37        dir_string.push("XXXXXX");
38        // unwrap this result as the internal bytes can't have a null with a valid path.
39        let dir_name = CString::new(dir_string.into_vec()).unwrap();
40        let mut dir_bytes = dir_name.into_bytes_with_nul();
41        // SAFETY: Creating the directory isn't unsafe.  The fact that it modifies the guts of the
42        // path is also OK because it only overwrites the last 6 Xs added above.
43        let ret = unsafe { libc::mkdtemp(dir_bytes.as_mut_ptr() as *mut libc::c_char) };
44        if ret.is_null() {
45            return errno_result();
46        }
47        dir_bytes.pop(); // Remove the null becasue from_vec can't handle it.
48        Ok(TempDir {
49            path: PathBuf::from(OsString::from_vec(dir_bytes)),
50        })
51    }
52
53    /// Creates a new temporary directory with inside `path`.
54    ///
55    /// The directory will be removed when the object goes out of scope.
56    ///
57    /// # Examples
58    ///
59    /// ```
60    /// # use std::path::Path;
61    /// # use vmm_sys_util::tempdir::TempDir;
62    /// let t = TempDir::new_in(Path::new("/tmp/")).unwrap();
63    /// ```
64    pub fn new_in(path: &Path) -> Result<TempDir> {
65        let mut path_buf = path.canonicalize().unwrap();
66        // This `push` adds a trailing slash ("/whatever/path" -> "/whatever/path/").
67        // This is safe for paths with already trailing slash.
68        path_buf.push("");
69        let temp_dir = TempDir::new_with_prefix(path_buf)?;
70        Ok(temp_dir)
71    }
72
73    /// Creates a new temporary directory with inside `$TMPDIR` if set, otherwise in `/tmp`.
74    ///
75    /// The directory will be removed when the object goes out of scope.
76    ///
77    /// # Examples
78    ///
79    /// ```
80    /// # use vmm_sys_util::tempdir::TempDir;
81    /// let t = TempDir::new().unwrap();
82    /// ```
83    pub fn new() -> Result<TempDir> {
84        let mut in_tmp_dir = temp_dir();
85        // This `push` adds a trailing slash ("/tmp" -> "/tmp/").
86        // This is safe for paths with already trailing slash.
87        in_tmp_dir.push("");
88        let temp_dir = TempDir::new_in(in_tmp_dir.as_path())?;
89        Ok(temp_dir)
90    }
91
92    /// Removes the temporary directory.
93    ///
94    /// Calling this is optional as when a `TempDir` object goes out of scope,
95    /// the directory will be removed.
96    /// Calling remove explicitly allows for better error handling.
97    ///
98    /// # Errors
99    ///
100    /// This function can only be called once per object. An error is returned
101    /// otherwise.
102    ///
103    /// # Examples
104    ///
105    /// ```
106    /// # use std::path::Path;
107    /// # use std::path::PathBuf;
108    /// # use vmm_sys_util::tempdir::TempDir;
109    /// let temp_dir = TempDir::new_with_prefix("/tmp/testdir").unwrap();
110    /// temp_dir.remove().unwrap();
111    pub fn remove(&self) -> Result<()> {
112        fs::remove_dir_all(&self.path).map_err(Error::from)
113    }
114
115    /// Returns the path to the tempdir.
116    ///
117    /// # Examples
118    ///
119    /// ```
120    /// # use std::path::Path;
121    /// # use std::path::PathBuf;
122    /// # use vmm_sys_util::tempdir::TempDir;
123    /// let temp_dir = TempDir::new_with_prefix("/tmp/testdir").unwrap();
124    /// assert!(temp_dir.as_path().exists());
125    pub fn as_path(&self) -> &Path {
126        self.path.as_ref()
127    }
128}
129
130impl Drop for TempDir {
131    fn drop(&mut self) {
132        let _ = self.remove();
133    }
134}
135
136#[cfg(test)]
137mod tests {
138    use super::*;
139
140    #[test]
141    fn test_create_dir() {
142        let t = TempDir::new().unwrap();
143        let path = t.as_path();
144        assert!(path.exists());
145        assert!(path.is_dir());
146        assert!(path.starts_with(temp_dir()));
147    }
148
149    #[test]
150    fn test_create_dir_with_prefix() {
151        let t = TempDir::new_with_prefix("/tmp/testdir").unwrap();
152        let path = t.as_path();
153        assert!(path.exists());
154        assert!(path.is_dir());
155        assert!(path.to_str().unwrap().contains("/tmp/testdir"));
156    }
157
158    #[test]
159    fn test_remove_dir() {
160        use crate::tempfile::TempFile;
161        let t = TempDir::new().unwrap();
162        let path = t.as_path().to_owned();
163        assert!(t.remove().is_ok());
164        // Calling remove twice returns error.
165        assert!(t.remove().is_err());
166        assert!(!path.exists());
167
168        let t = TempDir::new().unwrap();
169        let mut file = TempFile::new_in(t.as_path()).unwrap();
170        let t2 = TempDir::new_in(t.as_path()).unwrap();
171        let mut file2 = TempFile::new_in(t2.as_path()).unwrap();
172        let path2 = t2.as_path().to_owned();
173        assert!(t.remove().is_ok());
174        // Calling t2.remove returns error because parent dir has removed
175        assert!(t2.remove().is_err());
176        assert!(!path2.exists());
177        assert!(file.remove().is_err());
178        assert!(file2.remove().is_err());
179    }
180
181    #[test]
182    fn test_create_dir_in() {
183        let t = TempDir::new_in(Path::new("/tmp")).unwrap();
184        let path = t.as_path();
185        assert!(path.exists());
186        assert!(path.is_dir());
187        assert!(path.starts_with("/tmp/"));
188
189        let t = TempDir::new_in(Path::new("/tmp")).unwrap();
190        let path = t.as_path();
191        assert!(path.exists());
192        assert!(path.is_dir());
193        assert!(path.starts_with("/tmp"));
194    }
195
196    #[test]
197    fn test_drop() {
198        use std::mem::drop;
199        let t = TempDir::new_with_prefix("/tmp/asdf").unwrap();
200        let path = t.as_path().to_owned();
201        // Force tempdir object to go out of scope.
202        drop(t);
203
204        assert!(!(path.exists()));
205    }
206}