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