vmm_sys_util/tempfile.rs
1// Copyright 2017 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE-BSD-3-Clause file.
4//
5// SPDX-License-Identifier: BSD-3-Clause
6
7//! Struct for handling temporary files as well as any cleanup required.
8//!
9//! The temporary files will be created with a name available as well as having
10//! an exposed `fs::File` for reading/writing.
11//!
12//! The file will be removed when the object goes out of scope.
13//!
14//! # Examples
15//!
16//! ```
17//! use std::env::temp_dir;
18//! use std::io::Write;
19//! use std::path::{Path, PathBuf};
20//! use vmm_sys_util::tempfile::TempFile;
21//!
22//! let mut prefix = temp_dir();
23//! prefix.push("tempfile");
24//! let t = TempFile::new_with_prefix(prefix).unwrap();
25//! let mut f = t.as_file();
26//! f.write_all(b"hello world").unwrap();
27//! f.sync_all().unwrap();
28
29use std::env::temp_dir;
30use std::ffi::OsStr;
31use std::fs;
32use std::fs::File;
33use std::path::{Path, PathBuf};
34
35use libc;
36
37use crate::errno::{errno_result, Error, Result};
38
39/// Wrapper for working with temporary files.
40///
41/// The file will be maintained for the lifetime of the `TempFile` object.
42#[derive(Debug)]
43pub struct TempFile {
44 path: PathBuf,
45 file: Option<File>,
46}
47
48impl TempFile {
49 /// Creates the TempFile using a prefix.
50 ///
51 /// # Arguments
52 ///
53 /// `prefix`: The path and filename where to create the temporary file. Six
54 /// random alphanumeric characters will be added to the end of this to form
55 /// the filename.
56 #[cfg(unix)]
57 pub fn new_with_prefix<P: AsRef<OsStr>>(prefix: P) -> Result<TempFile> {
58 use std::ffi::CString;
59 use std::os::unix::{ffi::OsStrExt, io::FromRawFd};
60
61 let mut os_fname = prefix.as_ref().to_os_string();
62 os_fname.push("XXXXXX");
63
64 let c_tempname = CString::new(os_fname.as_bytes()).map_err(|_| Error::new(libc::EINVAL))?;
65 let raw_tempname = c_tempname.into_raw();
66
67 // SAFETY: Safe because `c_tempname` is a null-terminated string, as it originates from
68 // `CString::into_raw`.
69 let ret = unsafe { libc::mkstemp(raw_tempname) };
70
71 // SAFETY: `raw_tempname` originates from `CString::into_raw`.
72 let c_tempname = unsafe { CString::from_raw(raw_tempname) };
73
74 let fd = match ret {
75 -1 => return errno_result(),
76 _ => ret,
77 };
78
79 let os_tempname = OsStr::from_bytes(c_tempname.as_bytes());
80
81 // SAFETY: Safe because we checked `fd != -1` above and we uniquely own the file
82 // descriptor. This `fd` will be freed etc when `File` and thus
83 // `TempFile` goes out of scope.
84 let file = unsafe { File::from_raw_fd(fd) };
85
86 Ok(TempFile {
87 path: PathBuf::from(os_tempname),
88 file: Some(file),
89 })
90 }
91
92 /// Creates the TempFile using a prefix.
93 ///
94 /// # Arguments
95 ///
96 /// `prefix`: The path and filename where to create the temporary file. Six
97 /// random alphanumeric characters will be added to the end of this to form
98 /// the filename.
99 #[cfg(windows)]
100 pub fn new_with_prefix<P: AsRef<OsStr>>(prefix: P) -> Result<TempFile> {
101 use crate::rand::rand_alphanumerics;
102 use std::fs::OpenOptions;
103
104 let file_path_str = format!(
105 "{}{}",
106 prefix.as_ref().to_str().unwrap_or_default(),
107 rand_alphanumerics(6).to_str().unwrap_or_default()
108 );
109 let file_path_buf = PathBuf::from(&file_path_str);
110
111 let file = OpenOptions::new()
112 .read(true)
113 .write(true)
114 .create(true)
115 .truncate(true)
116 .open(file_path_buf.as_path())?;
117
118 Ok(TempFile {
119 path: file_path_buf,
120 file: Some(file),
121 })
122 }
123
124 /// Creates the TempFile inside a specific location.
125 ///
126 /// # Arguments
127 ///
128 /// `path`: The path where to create a temporary file with a filename formed from
129 /// six random alphanumeric characters.
130 pub fn new_in(path: &Path) -> Result<Self> {
131 let mut path_buf = path.canonicalize().unwrap();
132 // This `push` adds a trailing slash ("/whatever/path" -> "/whatever/path/").
133 // This is safe for paths with an already existing trailing slash.
134 path_buf.push("");
135 let temp_file = TempFile::new_with_prefix(path_buf.as_path())?;
136 Ok(temp_file)
137 }
138
139 /// Creates the TempFile.
140 ///
141 /// Creates a temporary file inside `$TMPDIR` if set, otherwise inside `/tmp`.
142 /// The filename will consist of six random alphanumeric characters.
143 pub fn new() -> Result<Self> {
144 let in_tmp_dir = temp_dir();
145 let temp_file = TempFile::new_in(in_tmp_dir.as_path())?;
146 Ok(temp_file)
147 }
148
149 /// Removes the temporary file.
150 ///
151 /// Calling this is optional as dropping a `TempFile` object will also
152 /// remove the file. Calling remove explicitly allows for better error
153 /// handling.
154 pub fn remove(&mut self) -> Result<()> {
155 fs::remove_file(&self.path).map_err(Error::from)
156 }
157
158 /// Returns the path to the file if the `TempFile` object that is wrapping the file
159 /// is still in scope.
160 ///
161 /// If we remove the file by explicitly calling [`remove`](#method.remove),
162 /// `as_path()` can still be used to return the path to that file (even though that
163 /// path does not point at an existing entity anymore).
164 /// Calling `as_path()` after `remove()` is useful, for example, when you need a
165 /// random path string, but don't want an actual resource at that path.
166 pub fn as_path(&self) -> &Path {
167 &self.path
168 }
169
170 /// Returns a reference to the File.
171 pub fn as_file(&self) -> &File {
172 // It's safe to unwrap because `file` can be `None` only after calling `into_file`
173 // which consumes this object.
174 self.file.as_ref().unwrap()
175 }
176
177 /// Consumes the TempFile, returning the wrapped file.
178 ///
179 /// This also removes the file from the system. The file descriptor remains opened and
180 /// it can be used until the returned file is dropped.
181 pub fn into_file(mut self) -> File {
182 self.file.take().unwrap()
183 }
184}
185
186impl Drop for TempFile {
187 fn drop(&mut self) {
188 let _ = self.remove();
189 }
190}
191
192#[cfg(test)]
193mod tests {
194 use super::*;
195 use std::io::{Read, Write};
196
197 #[test]
198 fn test_create_file_with_prefix() {
199 fn between(lower: u8, upper: u8, to_check: u8) -> bool {
200 (to_check >= lower) && (to_check <= upper)
201 }
202
203 let mut prefix = temp_dir();
204 prefix.push("asdf");
205 let t = TempFile::new_with_prefix(&prefix).unwrap();
206 let path = t.as_path().to_owned();
207
208 // Check filename exists
209 assert!(path.is_file());
210
211 // Check filename is in the correct location
212 assert!(path.starts_with(temp_dir()));
213
214 // Check filename has random added
215 assert_eq!(path.file_name().unwrap().to_string_lossy().len(), 10);
216
217 // Check filename has only ascii letters / numbers
218 for n in path.file_name().unwrap().to_string_lossy().bytes() {
219 assert!(between(b'0', b'9', n) || between(b'a', b'z', n) || between(b'A', b'Z', n));
220 }
221
222 // Check we can write to the file
223 let mut f = t.as_file();
224 f.write_all(b"hello world").unwrap();
225 f.sync_all().unwrap();
226 assert_eq!(f.metadata().unwrap().len(), 11);
227 }
228
229 #[test]
230 fn test_create_file_new() {
231 let t = TempFile::new().unwrap();
232 let path = t.as_path().to_owned();
233
234 // Check filename is in the correct location
235 assert!(path.starts_with(temp_dir().canonicalize().unwrap()));
236 }
237
238 #[test]
239 fn test_create_file_new_in() {
240 let t = TempFile::new_in(temp_dir().as_path()).unwrap();
241 let path = t.as_path().to_owned();
242
243 // Check filename exists
244 assert!(path.is_file());
245
246 // Check filename is in the correct location
247 assert!(path.starts_with(temp_dir().canonicalize().unwrap()));
248
249 let t = TempFile::new_in(temp_dir().as_path()).unwrap();
250 let path = t.as_path().to_owned();
251
252 // Check filename is in the correct location
253 assert!(path.starts_with(temp_dir().canonicalize().unwrap()));
254 }
255
256 #[test]
257 fn test_remove_file() {
258 let mut prefix = temp_dir();
259 prefix.push("asdf");
260
261 let mut t = TempFile::new_with_prefix(prefix).unwrap();
262 let path = t.as_path().to_owned();
263
264 // Check removal.
265 assert!(t.remove().is_ok());
266 assert!(!path.exists());
267
268 // Calling `as_path()` after the file was removed is allowed.
269 let path_2 = t.as_path().to_owned();
270 assert_eq!(path, path_2);
271
272 // Check trying to remove a second time returns an error.
273 assert!(t.remove().is_err());
274 }
275
276 #[test]
277 fn test_drop_file() {
278 let mut prefix = temp_dir();
279 prefix.push("asdf");
280
281 let t = TempFile::new_with_prefix(prefix).unwrap();
282 let path = t.as_path().to_owned();
283
284 assert!(path.starts_with(temp_dir()));
285 drop(t);
286 assert!(!path.exists());
287 }
288
289 #[test]
290 fn test_into_file() {
291 let mut prefix = temp_dir();
292 prefix.push("asdf");
293
294 let text = b"hello world";
295 let temp_file = TempFile::new_with_prefix(prefix).unwrap();
296 let path = temp_file.as_path().to_owned();
297 fs::write(path, text).unwrap();
298
299 let mut file = temp_file.into_file();
300 let mut buf: Vec<u8> = Vec::new();
301 file.read_to_end(&mut buf).unwrap();
302 assert_eq!(buf, text);
303 }
304}