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}