1#[cfg(feature = "rand_gen")]
2use crate::global_consts::{num_retry, rand_fn_len, valid_chars};
3#[cfg(feature = "mmap_support")]
4use memmap2::{Mmap, MmapMut, MmapOptions};
5#[cfg(feature = "rand_gen")]
6use rand::Rng;
7use std::env;
8use std::fmt::{Debug, Formatter};
9use std::fs::{File, OpenOptions};
10use std::io::{self, IoSlice, IoSliceMut, Read, Seek, SeekFrom, Write};
11use std::ops::{Deref, DerefMut};
12#[cfg(unix)]
13use std::os::fd::{AsRawFd, RawFd};
14use std::path::{Path, PathBuf};
15
16use crate::error::{TempError, TempResult};
17
18pub struct TempFile {
24 pub(crate) path: Option<PathBuf>,
26 file: Option<File>,
28}
29
30impl TempFile {
31 pub fn new<P: AsRef<Path>>(path: P) -> TempResult<TempFile> {
43 let path_ref = path.as_ref();
44 let path_buf = if path_ref.is_absolute() {
45 path_ref.to_owned()
46 } else {
47 env::temp_dir().join(path_ref)
48 };
49 let file = Self::open(&path_buf)?;
50 Ok(Self {
51 path: Some(path_buf),
52 file: Some(file),
53 })
54 }
55
56 pub fn new_here<P: AsRef<Path>>(path: P) -> TempResult<TempFile> {
68 if path.as_ref().is_relative() {
69 Self::new(env::current_dir()?.join(path))
70 } else {
71 Self::new(path)
72 }
73 }
74
75 pub fn persist(&mut self) -> TempResult<File> {
81 self.path = None;
82 self.file.take().ok_or(TempError::FileIsNone)
83 }
84
85 pub fn persist_name<S: AsRef<str>>(&mut self, name: S) -> TempResult<File> {
91 self.rename(name.as_ref())?;
92 self.persist()
93 }
94
95 pub fn persist_here<S: AsRef<str>>(&mut self, name: S) -> TempResult<File> {
101 self.rename(env::current_dir()?.join(name.as_ref()))?;
102 self.persist()
103 }
104
105 #[cfg(feature = "rand_gen")]
106 pub fn new_random<P: AsRef<Path>>(dir: Option<P>) -> TempResult<Self> {
118 let dir_buf = if let Some(d) = dir {
119 let d_ref = d.as_ref();
120 if d_ref.is_absolute() {
121 d_ref.to_path_buf()
122 } else {
123 env::temp_dir().join(d_ref)
124 }
125 } else {
126 env::temp_dir()
127 };
128 let mut rng = rand::rng();
129 for _ in 0..num_retry() {
130 let name: String = (0..rand_fn_len())
131 .map(|_| {
132 let idx = rng.random_range(0..valid_chars().len());
133 valid_chars()[idx] as char
134 })
135 .collect();
136 let full_path = dir_buf.join(&name);
137 if !full_path.exists() {
138 let file = Self::open(&full_path)?;
139 return Ok(Self {
140 path: Some(full_path),
141 file: Some(file),
142 });
143 }
144 }
145 Err(io::Error::new(
146 io::ErrorKind::AlreadyExists,
147 "Could not generate a unique filename",
148 )
149 .into())
150 }
151
152 #[cfg(feature = "rand_gen")]
153 pub fn new_random_here<P: AsRef<Path>>(dir: Option<P>) -> TempResult<Self> {
165 if let Some(dir) = dir {
166 if dir.as_ref().is_absolute() {
167 Self::new_random(Some(dir))
168 } else {
169 Self::new_random(Some(env::current_dir()?.join(dir)))
170 }
171 } else {
172 Self::new_random(Some(env::current_dir()?))
173 }
174 }
175
176 fn open(path: &Path) -> TempResult<File> {
178 OpenOptions::new()
179 .create_new(true)
180 .read(true)
181 .write(true)
182 .open(path)
183 .map_err(Into::into)
184 }
185
186 pub fn file_mut(&mut self) -> TempResult<&mut File> {
192 self.file.as_mut().ok_or(TempError::FileIsNone)
193 }
194
195 pub fn file(&self) -> TempResult<&File> {
201 self.file.as_ref().ok_or(TempError::FileIsNone)
202 }
203
204 #[must_use]
206 pub fn path(&self) -> Option<&Path> {
207 self.path.as_deref()
208 }
209
210 pub fn rename<P: AsRef<Path>>(&mut self, new_path: P) -> TempResult<()> {
220 let mut new_path = new_path.as_ref().to_path_buf();
221 let pat = new_path.to_str().unwrap_or("");
222 let mut mod_path = false;
223 if !pat.contains('/') && !pat.contains('\\') {
224 mod_path = true;
225 }
226 if let Some(ref old_path) = self.path {
227 if mod_path {
228 new_path = old_path
229 .parent()
230 .ok_or(TempError::IO(io::Error::new(
231 io::ErrorKind::NotFound,
232 "Old path parent not found",
233 )))?
234 .to_path_buf()
235 .join(new_path);
236 }
237 std::fs::copy(old_path, new_path.clone())?;
238 std::fs::remove_file(old_path)?;
239 self.path = Some(new_path);
240 }
241 Ok(())
242 }
243
244 pub fn rename_here<P: AsRef<Path>>(&mut self, new_path: P) -> TempResult<()> {
254 let mut new_path = new_path.as_ref().to_path_buf();
255 let pat = new_path.to_str().unwrap_or("");
256 let mut mod_path = false;
257 if !pat.contains('/') && !pat.contains('\\') {
258 mod_path = true;
259 }
260 if let Some(ref old_path) = self.path {
261 if mod_path {
262 new_path = env::current_dir()?.join(new_path);
263 }
264 std::fs::copy(old_path, new_path.clone())?;
265 std::fs::remove_file(old_path)?;
266 self.path = Some(new_path);
267 }
268 Ok(())
269 }
270
271 pub fn sync_all(&self) -> TempResult<()> {
279 self.file()?.sync_all().map_err(Into::into)
280 }
281
282 pub fn disarm(mut self) -> TempResult<()> {
290 self.file_mut()?.flush().map_err(Into::<TempError>::into)?;
291 self.path = None;
292 Ok(())
293 }
294
295 pub fn close(mut self) -> TempResult<()> {
303 self.file_mut()?.flush().map_err(Into::<TempError>::into)?;
304 self.path = None;
305 self.file = None;
306 Ok(())
307 }
308
309 pub fn into_inner(mut self) -> TempResult<File> {
317 self.path = None;
318 self.file.take().ok_or(TempError::FileIsNone)
319 }
320
321 #[must_use]
323 pub fn is_active(&self) -> bool {
324 self.path.is_some()
325 }
326
327 pub fn delete(mut self) -> TempResult<()> {
335 self.file_mut()?.flush().map_err(Into::<TempError>::into)?;
336 if let Some(ref path) = self.path {
337 std::fs::remove_file(path)?;
338 self.path = None;
339 }
340 Ok(())
341 }
342
343 pub fn metadata(&self) -> TempResult<std::fs::Metadata> {
349 if let Some(ref path) = self.path {
350 std::fs::metadata(path).map_err(Into::into)
351 } else {
352 Err(Into::into(io::Error::new(
353 io::ErrorKind::NotFound,
354 "File has been closed",
355 )))
356 }
357 }
358
359 #[cfg(unix)]
365 pub fn from_fp<P: AsRef<Path>>(file: File, path: P) -> TempResult<Self> {
366 if !Self::are_same_file(path.as_ref(), &file)? {
367 return Err(TempError::InvalidFileOrPath);
368 }
369 Ok(Self {
370 path: Some(path.as_ref().to_path_buf()),
371 file: Some(file),
372 })
373 }
374
375 #[cfg(unix)]
377 fn are_same_file(path: &Path, file: &File) -> io::Result<bool> {
378 use std::fs::metadata;
379 use std::os::unix::fs::MetadataExt;
380 let path_metadata = metadata(path)?;
381 let file_metadata = file.metadata()?;
382
383 #[cfg(unix)]
384 {
385 Ok(path_metadata.dev() == file_metadata.dev() && path_metadata.ino() == file_metadata.ino())
387 }
388 }
389}
390
391#[cfg(feature = "mmap_support")]
392impl TempFile {
393 pub unsafe fn mmap(&self) -> TempResult<Mmap> {
403 let file = self.file()?;
404 unsafe { MmapOptions::new().map(file).map_err(Into::into) }
405 }
406
407 pub unsafe fn mmap_mut(&mut self) -> TempResult<MmapMut> {
417 let file = self.file_mut()?;
418 unsafe {
419 MmapOptions::new()
420 .map_mut(Self::immut(file))
421 .map_err(Into::into)
422 }
423 }
424
425 fn immut(file: &mut File) -> &File {
427 Box::leak(Box::new(file))
428 }
429}
430
431impl Write for TempFile {
432 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
433 if let Some(ref mut file) = self.file {
434 file.write(buf)
435 } else {
436 Err(io::Error::new(
437 io::ErrorKind::NotFound,
438 TempError::FileIsNone,
439 ))
440 }
441 }
442 fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
443 if let Some(ref mut file) = self.file {
444 file.write_vectored(bufs)
445 } else {
446 Err(io::Error::new(
447 io::ErrorKind::NotFound,
448 TempError::FileIsNone,
449 ))
450 }
451 }
452 fn flush(&mut self) -> io::Result<()> {
453 if let Some(ref mut file) = self.file {
454 file.flush()
455 } else {
456 Err(io::Error::new(
457 io::ErrorKind::NotFound,
458 TempError::FileIsNone,
459 ))
460 }
461 }
462}
463
464impl Read for TempFile {
465 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
466 if let Some(ref mut file) = self.file {
467 file.read(buf)
468 } else {
469 Err(io::Error::new(
470 io::ErrorKind::NotFound,
471 TempError::FileIsNone,
472 ))
473 }
474 }
475 fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
476 if let Some(ref mut file) = self.file {
477 file.read_vectored(bufs)
478 } else {
479 Err(io::Error::new(
480 io::ErrorKind::NotFound,
481 TempError::FileIsNone,
482 ))
483 }
484 }
485 fn read_to_end(&mut self, buf: &mut Vec<u8>) -> io::Result<usize> {
486 if let Some(ref mut file) = self.file {
487 file.read_to_end(buf)
488 } else {
489 Err(io::Error::new(
490 io::ErrorKind::NotFound,
491 TempError::FileIsNone,
492 ))
493 }
494 }
495 fn read_to_string(&mut self, buf: &mut String) -> io::Result<usize> {
496 if let Some(ref mut file) = self.file {
497 file.read_to_string(buf)
498 } else {
499 Err(io::Error::new(
500 io::ErrorKind::NotFound,
501 TempError::FileIsNone,
502 ))
503 }
504 }
505}
506
507impl Seek for TempFile {
508 fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
509 if let Some(ref mut file) = self.file {
510 file.seek(pos)
511 } else {
512 Err(io::Error::new(
513 io::ErrorKind::NotFound,
514 TempError::FileIsNone,
515 ))
516 }
517 }
518}
519
520impl Debug for TempFile {
521 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
522 f.debug_struct("TempFile")
523 .field("path", &self.path)
524 .field("file", &self.file)
525 .finish()
526 }
527}
528
529impl Drop for TempFile {
530 fn drop(&mut self) {
531 if let Some(ref path) = self.path {
532 let _ = std::fs::remove_file(path);
533 }
534 }
535}
536
537impl AsRef<Path> for TempFile {
538 fn as_ref(&self) -> &Path {
539 self.path.as_deref().unwrap_or_else(|| Path::new(""))
541 }
542}
543
544#[cfg(unix)]
545impl AsRawFd for TempFile {
546 fn as_raw_fd(&self) -> RawFd {
547 self.file.as_ref().map_or(-1, AsRawFd::as_raw_fd)
549 }
550}
551
552impl Deref for TempFile {
553 type Target = File;
554 fn deref(&self) -> &Self::Target {
555 self.file.as_ref().expect("TempFile file is None")
556 }
557}
558
559impl DerefMut for TempFile {
560 fn deref_mut(&mut self) -> &mut Self::Target {
561 self.file.as_mut().expect("TempFile file is None")
562 }
563}
564
565#[cfg(windows)]
566impl std::os::windows::io::AsRawHandle for TempFile {
567 fn as_raw_handle(&self) -> std::os::windows::io::RawHandle {
568 self.file
570 .as_ref()
571 .map(|f| f.as_raw_handle())
572 .unwrap_or(std::ptr::null_mut())
573 }
574}