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}