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