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 
29 use std::env::temp_dir;
30 use std::ffi::OsStr;
31 use std::fs;
32 use std::fs::File;
33 use std::path::{Path, PathBuf};
34 
35 use libc;
36 
37 use 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)]
43 pub struct TempFile {
44     path: PathBuf,
45     file: Option<File>,
46 }
47 
48 impl 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)]
new_with_prefix<P: AsRef<OsStr>>(prefix: P) -> Result<TempFile>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)]
new_with_prefix<P: AsRef<OsStr>>(prefix: P) -> Result<TempFile>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.
new_in(path: &Path) -> Result<Self>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.
new() -> Result<Self>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.
remove(&mut self) -> Result<()>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.
as_path(&self) -> &Path166     pub fn as_path(&self) -> &Path {
167         &self.path
168     }
169 
170     /// Returns a reference to the File.
as_file(&self) -> &File171     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.
into_file(mut self) -> File181     pub fn into_file(mut self) -> File {
182         self.file.take().unwrap()
183     }
184 }
185 
186 impl Drop for TempFile {
drop(&mut self)187     fn drop(&mut self) {
188         let _ = self.remove();
189     }
190 }
191 
192 #[cfg(test)]
193 mod tests {
194     use super::*;
195     use std::io::{Read, Write};
196 
197     #[test]
test_create_file_with_prefix()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]
test_create_file_new()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]
test_create_file_new_in()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]
test_remove_file()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]
test_drop_file()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]
test_into_file()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 }
305