/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 | | } |