1extern crate uuid;
23
24use std::env;
25use std::fs;
26use std::io;
27use std::ops;
28#[cfg(unix)]
29use std::os::unix::fs::{DirBuilderExt, OpenOptionsExt};
30use std::path::{Path, PathBuf};
31use uuid::Uuid;
32
33#[derive(Debug)]
34pub struct Temp {
35 path: PathBuf,
36}
37
38fn create_path() -> PathBuf {
39 create_path_in(env::temp_dir())
40}
41
42fn create_path_in(path: PathBuf) -> PathBuf {
43 let mut path = path;
44 let dir_uuid = Uuid::new_v4();
45
46 path.push(dir_uuid.simple().to_string());
47 path
48}
49
50impl Temp {
51 pub fn new_dir() -> io::Result<Self> {
53 let path = create_path();
54 Self::create_dir(&path)?;
55
56 let temp = Temp { path };
57
58 Ok(temp)
59 }
60
61 pub fn new_dir_in<P: AsRef<Path>>(directory: P) -> io::Result<Self> {
63 let path = create_path_in(directory.as_ref().to_path_buf());
64 Self::create_dir(&path)?;
65
66 let temp = Temp { path };
67
68 Ok(temp)
69 }
70
71 pub fn new_file_in<P: AsRef<Path>>(directory: P) -> io::Result<Self> {
73 let path = create_path_in(directory.as_ref().to_path_buf());
74 Self::create_file(&path)?;
75
76 let temp = Temp { path };
77
78 Ok(temp)
79 }
80
81 pub fn new_file() -> io::Result<Self> {
83 let path = create_path();
84 Self::create_file(&path)?;
85
86 let temp = Temp { path };
87
88 Ok(temp)
89 }
90
91 pub fn new_path() -> Self {
93 let path = create_path();
94
95 Temp { path }
96 }
97
98 pub fn new_path_in<P: AsRef<Path>>(directory: P) -> Self {
101 let path = create_path_in(directory.as_ref().to_path_buf());
102
103 Temp { path }
104 }
105
106 pub fn to_path_buf(&self) -> PathBuf {
117 PathBuf::from(&self.path)
118 }
119
120 pub fn release(self) -> PathBuf {
135 use std::mem::{forget, transmute_copy};
136
137 let path = unsafe { transmute_copy(&self.path) };
138 forget(self);
139 path
140 }
141
142 fn create_file(path: &Path) -> io::Result<()> {
143 let mut builder = fs::OpenOptions::new();
144 builder.write(true).create_new(true);
145
146 #[cfg(unix)]
147 builder.mode(0o600);
148
149 builder.open(path)?;
150 Ok(())
151 }
152
153 fn create_dir(path: &Path) -> io::Result<()> {
154 let mut builder = fs::DirBuilder::new();
155
156 #[cfg(unix)]
157 builder.mode(0o700);
158
159 builder.create(path)
160 }
161}
162
163impl AsRef<Path> for Temp {
164 fn as_ref(&self) -> &Path {
165 self.path.as_path()
166 }
167}
168
169impl ops::Deref for Temp {
170 type Target = PathBuf;
171 fn deref(&self) -> &Self::Target {
172 &self.path
173 }
174}
175
176impl ops::DerefMut for Temp {
177 fn deref_mut(&mut self) -> &mut Self::Target {
178 &mut self.path
179 }
180}
181
182impl Drop for Temp {
183 fn drop(&mut self) {
184 if !self.path.exists() {
186 return;
187 }
188
189 let _result = if self.path.is_dir() {
190 fs::remove_dir_all(&self)
191 } else {
192 fs::remove_file(&self)
193 };
194 }
195}
196
197#[cfg(test)]
198mod tests {
199 use super::*;
200 use std::fs::File;
201 #[cfg(unix)]
202 use std::os::unix::fs::MetadataExt;
203
204 #[test]
205 fn it_should_create_file_in_dir() {
206 let in_dir;
207 {
208 let temp_dir = Temp::new_dir().unwrap();
209
210 in_dir = temp_dir.path.clone();
211
212 {
213 let temp_file = Temp::new_file_in(in_dir).unwrap();
214 assert!(fs::metadata(temp_file).unwrap().is_file());
215 }
216 }
217 }
218
219 #[test]
220 fn it_should_drop_file_out_of_scope() {
221 let path;
222 {
223 let temp_file = Temp::new_file().unwrap();
224
225 path = temp_file.path.clone();
226 assert!(fs::metadata(temp_file).unwrap().is_file());
227 }
228
229 if let Err(e) = fs::metadata(path) {
230 assert_eq!(e.kind(), io::ErrorKind::NotFound);
231 } else {
232 panic!("File was not removed");
233 }
234 }
235
236 #[test]
237 fn it_should_drop_dir_out_of_scope() {
238 let path;
239 {
240 let temp_file = Temp::new_dir().unwrap();
241
242 path = temp_file.path.clone();
243 assert!(fs::metadata(temp_file).unwrap().is_dir());
244 }
245
246 if let Err(e) = fs::metadata(path) {
247 assert_eq!(e.kind(), io::ErrorKind::NotFound);
248 } else {
249 panic!("File was not removed");
250 }
251 }
252
253 #[test]
254 fn it_should_not_drop_released_file() {
255 let path_buf;
256 {
257 let temp_file = Temp::new_file().unwrap();
258 path_buf = temp_file.release();
259 }
260 assert!(path_buf.exists());
261 fs::remove_file(path_buf).unwrap();
262 }
263
264 #[test]
265 fn it_should_not_drop_released_dir() {
266 let path_buf;
267 {
268 let temp_dir = Temp::new_dir().unwrap();
269 path_buf = temp_dir.release();
270 }
271 assert!(path_buf.exists());
272 fs::remove_dir_all(path_buf).unwrap();
273 }
274
275 #[test]
276 #[cfg(unix)]
277 fn temp_file_only_readable_by_owner() {
278 let temp_file = Temp::new_file().unwrap();
279 let mode = fs::metadata(temp_file.as_ref()).unwrap().mode();
280 assert_eq!(0o600, mode & 0o777);
281 }
282
283 #[test]
284 #[cfg(unix)]
285 fn temp_dir_only_readable_by_owner() {
286 let dir = Temp::new_dir().unwrap();
287 let mode = fs::metadata(dir).unwrap().mode();
288 assert_eq!(0o700, mode & 0o777)
289 }
290
291 #[test]
292 fn target_dir_must_exist() {
293 let temp_dir = Temp::new_dir().unwrap();
294 let mut no_such_dir = temp_dir.as_ref().to_owned();
295 no_such_dir.push("no_such_dir");
296
297 match Temp::new_file_in(&no_such_dir) {
298 Err(ref e) if e.kind() == io::ErrorKind::NotFound => (),
299 _ => panic!(),
300 }
301
302 match Temp::new_dir_in(&no_such_dir) {
303 Err(ref e) if e.kind() == io::ErrorKind::NotFound => (),
304 _ => panic!(),
305 }
306 }
307
308 #[test]
309 fn uninitialized_file() {
310 let temp = Temp::new_path();
311 assert!(!temp.exists());
312 let _file = File::create(&temp);
313 assert!(temp.exists());
314 }
315
316 #[test]
317 fn uninitialized_no_panic_on_drop_with_release() {
318 let t = Temp::new_path();
319 t.release();
320 }
321
322 #[test]
323 #[cfg(unix)]
324 fn unix_socket() {
325 let t = Temp::new_path();
326 println!("Path is {:?}", t.to_str());
327 let socket = std::os::unix::net::UnixListener::bind(t.to_str().unwrap());
328 drop(socket);
329 drop(t);
330 }
331}