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}