Coverage Report

Created: 2026-01-22 07:12

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/vmm-sys-util-0.14.0/src/tempfile.rs
Line
Count
Source
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)]
57
0
    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
0
        let mut os_fname = prefix.as_ref().to_os_string();
62
0
        os_fname.push("XXXXXX");
63
64
0
        let c_tempname = CString::new(os_fname.as_bytes()).map_err(|_| Error::new(libc::EINVAL))?;
65
0
        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
0
        let ret = unsafe { libc::mkstemp(raw_tempname) };
70
71
        // SAFETY: `raw_tempname` originates from `CString::into_raw`.
72
0
        let c_tempname = unsafe { CString::from_raw(raw_tempname) };
73
74
0
        let fd = match ret {
75
0
            -1 => return errno_result(),
76
0
            _ => ret,
77
        };
78
79
0
        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
0
        let file = unsafe { File::from_raw_fd(fd) };
85
86
0
        Ok(TempFile {
87
0
            path: PathBuf::from(os_tempname),
88
0
            file: Some(file),
89
0
        })
90
0
    }
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
0
    pub fn new_in(path: &Path) -> Result<Self> {
131
0
        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
0
        path_buf.push("");
135
0
        let temp_file = TempFile::new_with_prefix(path_buf.as_path())?;
136
0
        Ok(temp_file)
137
0
    }
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
0
    pub fn new() -> Result<Self> {
144
0
        let in_tmp_dir = temp_dir();
145
0
        let temp_file = TempFile::new_in(in_tmp_dir.as_path())?;
146
0
        Ok(temp_file)
147
0
    }
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
0
    pub fn remove(&mut self) -> Result<()> {
155
0
        fs::remove_file(&self.path).map_err(Error::from)
156
0
    }
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
0
    pub fn as_path(&self) -> &Path {
167
0
        &self.path
168
0
    }
169
170
    /// Returns a reference to the File.
171
0
    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
0
        self.file.as_ref().unwrap()
175
0
    }
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
0
    pub fn into_file(mut self) -> File {
182
0
        self.file.take().unwrap()
183
0
    }
184
}
185
186
impl Drop for TempFile {
187
0
    fn drop(&mut self) {
188
0
        let _ = self.remove();
189
0
    }
190
}
191
192
#[cfg(test)]
193
mod 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
}