1 // Copyright 2019 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 file.
4
5 //! Simplified tempfile which doesn't depend on the `rand` crate.
6 //!
7 //! # Example
8 //!
9 //! ```
10 //! use std::io::Result;
11 //! use std::path::{Path, PathBuf};
12 //! use tempfile::TempDir;
13 //!
14 //! fn main() -> Result<()> {
15 //! let t = TempDir::new()?;
16 //! assert!(t.path().exists());
17 //!
18 //! Ok(())
19 //! }
20 //! ```
21
22 use libc::{mkdtemp, mkstemp};
23 use std::env;
24 use std::ffi::CString;
25 use std::fs::{self, File};
26 use std::io::{Error, ErrorKind, Result};
27 use std::mem::ManuallyDrop;
28 use std::os::unix::io::FromRawFd;
29 use std::path::{Path, PathBuf};
30 use std::ptr;
31
temp_path_template(prefix: &str) -> Result<CString>32 fn temp_path_template(prefix: &str) -> Result<CString> {
33 // mkdtemp()/mkstemp() require the template to end in 6 X chars, which will be replaced
34 // with random characters to make the path unique.
35 let path_template = env::temp_dir().join(format!("{}.XXXXXX", prefix));
36 match path_template.to_str() {
37 Some(s) => Ok(CString::new(s)?),
38 None => Err(Error::new(
39 ErrorKind::InvalidData,
40 "Path to string conversion failed",
41 )),
42 }
43 }
44
45 pub struct Builder {
46 prefix: String,
47 }
48
49 // Note: we implement a builder because the protoc-rust crate uses this API from
50 // crates.io's tempfile. Our code mostly uses TempDir::new directly.
51 impl Builder {
new() -> Self52 pub fn new() -> Self {
53 Builder {
54 prefix: ".tmp".to_owned(),
55 }
56 }
57
58 /// Set a custom filename prefix.
59 ///
60 /// Default: `.tmp`
prefix(&mut self, prefix: &str) -> &mut Self61 pub fn prefix(&mut self, prefix: &str) -> &mut Self {
62 self.prefix = prefix.to_owned();
63 self
64 }
65
66 /// Creates a new temporary directory under libc's preferred system
67 /// temporary directory. The new directory will be removed when the returned
68 /// handle of type `TempDir` is dropped.
tempdir(&self) -> Result<TempDir>69 pub fn tempdir(&self) -> Result<TempDir> {
70 let template = temp_path_template(&self.prefix)?;
71 let ptr = template.into_raw();
72 // Safe because ownership of the buffer is handed off to mkdtemp() only
73 // until it returns, and ownership is reclaimed by calling CString::from_raw()
74 // on the same pointer returned by into_raw().
75 let path = unsafe {
76 let ret = mkdtemp(ptr);
77 let path = CString::from_raw(ptr);
78 if ret.is_null() {
79 return Err(Error::last_os_error());
80 }
81 path
82 };
83 Ok(TempDir {
84 path: PathBuf::from(path.to_str().map_err(|_| {
85 Error::new(ErrorKind::InvalidData, "Path to string conversion failed")
86 })?),
87 })
88 }
89
90 /// Creates a new temporary file under libc's preferred system
91 /// temporary directory. The new file will be removed when the returned
92 /// handle of type `NamedTempFile` is dropped.
tempfile(&self) -> Result<NamedTempFile>93 pub fn tempfile(&self) -> Result<NamedTempFile> {
94 let template = temp_path_template(&self.prefix)?;
95 let ptr = template.into_raw();
96 // Safe because ownership of the buffer is handed off to mkstemp() only
97 // until it returns, and ownership is reclaimed by calling CString::from_raw()
98 // on the same pointer returned by into_raw().
99 let (file, path) = unsafe {
100 let ret = mkstemp(ptr);
101 let path = CString::from_raw(ptr);
102 if ret < 0 {
103 return Err(Error::last_os_error());
104 }
105 (File::from_raw_fd(ret), path)
106 };
107
108 Ok(NamedTempFile {
109 path: TempPath {
110 path: PathBuf::from(path.to_str().map_err(|_| {
111 Error::new(ErrorKind::InvalidData, "Path to string conversion failed")
112 })?),
113 },
114 file,
115 })
116 }
117 }
118
119 impl Default for Builder {
default() -> Self120 fn default() -> Self {
121 Self::new()
122 }
123 }
124
125 /// Temporary directory. The directory will be removed when this object is
126 /// dropped.
127 pub struct TempDir {
128 path: PathBuf,
129 // When adding new fields to TempDir: note that anything with a Drop impl
130 // will need to be dropped explicitly via ptr::read inside TempDir::remove
131 // or else it gets leaked (memory safe but not ideal).
132 }
133
134 impl TempDir {
new() -> Result<Self>135 pub fn new() -> Result<Self> {
136 Builder::new().tempdir()
137 }
138
139 /// Accesses the tempdir's [`Path`].
140 ///
141 /// [`Path`]: http://doc.rust-lang.org/std/path/struct.Path.html
path(&self) -> &Path142 pub fn path(&self) -> &Path {
143 self.path.as_ref()
144 }
145
146 /// Removes the temporary directory.
147 ///
148 /// Calling this is optional as dropping a TempDir object will also remove
149 /// the directory. Calling remove explicitly allows for any resulting error
150 /// to be handled.
remove(self) -> Result<()>151 pub fn remove(self) -> Result<()> {
152 // Place self inside ManuallyDrop so its Drop impl doesn't run, but nor
153 // does the path inside get dropped. Then use ptr::read to take out the
154 // PathBuf so that it *does* get dropped correctly at the bottom of this
155 // function.
156 let dont_drop = ManuallyDrop::new(self);
157 let path: PathBuf = unsafe { ptr::read(&dont_drop.path) };
158
159 fs::remove_dir_all(path)
160 }
161 }
162
163 impl Drop for TempDir {
drop(&mut self)164 fn drop(&mut self) {
165 let _ = fs::remove_dir_all(&self.path);
166 }
167 }
168
169 /// Temporary file with a known name. The file will be removed when this object is dropped.
170 pub struct NamedTempFile {
171 path: TempPath,
172 file: File,
173 }
174
175 impl NamedTempFile {
new() -> Result<Self>176 pub fn new() -> Result<Self> {
177 Builder::new().tempfile()
178 }
179
180 /// Accesses the temporary file's `Path`.
path(&self) -> &Path181 pub fn path(&self) -> &Path {
182 self.path.path.as_ref()
183 }
184
185 /// Accesses the temporary file's `File` object.
as_file(&self) -> &File186 pub fn as_file(&self) -> &File {
187 &self.file
188 }
189
190 /// Accesses the temporary file's `File` object mutably.
as_file_mut(&mut self) -> &mut File191 pub fn as_file_mut(&mut self) -> &mut File {
192 &mut self.file
193 }
194
195 /// Convert this `TempFile` into an open `File` and unlink it from the filesystem.
into_file(self) -> File196 pub fn into_file(self) -> File {
197 self.file
198 }
199 }
200
201 // Container for NamedTempFile's path so that it can be dropped separately from the File.
202 struct TempPath {
203 path: PathBuf,
204 }
205
206 impl Drop for TempPath {
drop(&mut self)207 fn drop(&mut self) {
208 let _ = fs::remove_file(&self.path);
209 }
210 }
211
212 /// Create a new anonymous temporary file under the preferred system
213 /// temporary directory. The new file will be removed when the returned
214 /// `File` is dropped.
tempfile() -> Result<File>215 pub fn tempfile() -> Result<File> {
216 Ok(NamedTempFile::new()?.into_file())
217 }
218
219 #[cfg(test)]
220 mod tests {
221 use std::io::{Read, Seek, SeekFrom, Write};
222
223 use crate::{tempfile, NamedTempFile, TempDir};
224
225 #[test]
create_dir()226 fn create_dir() {
227 let t = TempDir::new().unwrap();
228 let path = t.path();
229 assert!(path.exists());
230 assert!(path.is_dir());
231 }
232
233 #[test]
remove_dir()234 fn remove_dir() {
235 let t = TempDir::new().unwrap();
236 let path = t.path().to_owned();
237 assert!(t.remove().is_ok());
238 assert!(!path.exists());
239 }
240
241 #[test]
create_file()242 fn create_file() {
243 let mut f = tempfile().expect("tempfile() failed");
244 f.write_all(&[0, 1, 2, 3]).unwrap();
245 f.seek(SeekFrom::Start(0)).unwrap();
246 let mut data = vec![0u8; 4];
247 f.read_exact(&mut data).unwrap();
248 assert_eq!(data, [0, 1, 2, 3]);
249 }
250
251 #[test]
create_named_file()252 fn create_named_file() {
253 let named_temp = NamedTempFile::new().unwrap();
254 let path = named_temp.path().to_owned();
255 assert!(path.exists());
256
257 // as_file() should not delete the file.
258 let _f = named_temp.as_file();
259 assert!(path.exists());
260
261 // Dropping the NamedTempFile should delete the file.
262 drop(named_temp);
263 assert!(!path.exists());
264 }
265 }
266