camino_tempfile/file.rs
1// Copyright (c) The camino-tempfile Contributors
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4use crate::{errors::IoResultExt, Builder};
5use camino::{Utf8Path, Utf8PathBuf};
6use std::{
7 convert::{TryFrom, TryInto},
8 error,
9 ffi::OsStr,
10 fmt,
11 fs::File,
12 io,
13 io::{Read, Seek, SeekFrom, Write},
14 ops::Deref,
15 path::Path,
16};
17use tempfile::{NamedTempFile, TempPath};
18
19/// Create a new temporary file.
20///
21/// The file will be created in the location returned by [`std::env::temp_dir()`].
22///
23/// # Security
24///
25/// This variant is secure/reliable in the presence of a pathological temporary file cleaner.
26///
27/// # Resource Leaking
28///
29/// The temporary file will be automatically removed by the OS when the last handle to it is closed.
30/// This doesn't rely on Rust destructors being run, so will (almost) never fail to clean up the
31/// temporary file.
32///
33/// # Errors
34///
35/// If the file can not be created, `Err` is returned.
36///
37/// # Examples
38///
39/// ```
40/// use camino_tempfile::tempfile;
41/// use std::io::{self, Write};
42///
43/// # fn main() {
44/// # if let Err(_) = run() {
45/// # ::std::process::exit(1);
46/// # }
47/// # }
48/// # fn run() -> Result<(), io::Error> {
49/// // Create a file inside of `std::env::temp_dir()`.
50/// let mut file = tempfile()?;
51///
52/// writeln!(file, "Brian was here. Briefly.")?;
53/// # Ok(())
54/// # }
55/// ```
56pub fn tempfile() -> io::Result<File> {
57 tempfile::tempfile()
58}
59
60/// Create a new temporary file in the specified directory.
61///
62/// This method accepts `AsRef<Path>` rather than `AsRef<Utf8Path>` because it returns a
63/// [`std::fs::File`].
64///
65/// # Security
66///
67/// This variant is secure/reliable in the presence of a pathological temporary file cleaner. If the
68/// temporary file isn't created in [`std::env::temp_dir()`] then temporary file cleaners aren't an
69/// issue.
70///
71/// # Resource Leaking
72///
73/// The temporary file will be automatically removed by the OS when the last handle to it is closed.
74/// This doesn't rely on Rust destructors being run, so will (almost) never fail to clean up the
75/// temporary file.
76///
77/// # Errors
78///
79/// If the file can not be created, `Err` is returned.
80///
81/// # Examples
82///
83/// ```
84/// use camino_tempfile::tempfile_in;
85/// use std::io::{self, Write};
86///
87/// # fn main() {
88/// # if let Err(_) = run() {
89/// # ::std::process::exit(1);
90/// # }
91/// # }
92/// # fn run() -> Result<(), io::Error> {
93/// // Create a file inside of the current working directory
94/// let mut file = tempfile_in("./")?;
95///
96/// writeln!(file, "Brian was here. Briefly.")?;
97/// # Ok(())
98/// # }
99/// ```
100pub fn tempfile_in<P: AsRef<Path>>(dir: P) -> io::Result<File> {
101 tempfile::tempfile_in(dir)
102}
103
104/// Error returned when persisting a temporary file path fails.
105#[derive(Debug)]
106pub struct Utf8PathPersistError {
107 /// The underlying IO error.
108 pub error: io::Error,
109 /// The temporary file path that couldn't be persisted.
110 pub path: Utf8TempPath,
111}
112
113impl From<Utf8PathPersistError> for io::Error {
114 #[inline]
115 fn from(error: Utf8PathPersistError) -> io::Error {
116 error.error
117 }
118}
119
120impl From<Utf8PathPersistError> for Utf8TempPath {
121 #[inline]
122 fn from(error: Utf8PathPersistError) -> Utf8TempPath {
123 error.path
124 }
125}
126
127impl fmt::Display for Utf8PathPersistError {
128 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
129 write!(f, "failed to persist temporary file path: {}", self.error)
130 }
131}
132
133impl error::Error for Utf8PathPersistError {
134 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
135 Some(&self.error)
136 }
137}
138
139/// A path to a named temporary file without an open file handle.
140///
141/// This is useful when the temporary file needs to be used by a child process,
142/// for example.
143///
144/// When dropped, the temporary file is deleted.
145pub struct Utf8TempPath {
146 // Invariant: inner stores a UTF-8 path.
147 inner: TempPath,
148}
149
150impl Utf8TempPath {
151 pub(crate) fn from_temp_path(inner: TempPath) -> io::Result<Self> {
152 let path: &Path = inner.as_ref();
153 // This produces a better error message.
154 Utf8PathBuf::try_from(path.to_path_buf()).map_err(|error| error.into_io_error())?;
155 Ok(Self { inner })
156 }
157
158 /// Close and remove the temporary file.
159 ///
160 /// Use this if you want to detect errors in deleting the file.
161 ///
162 /// # Errors
163 ///
164 /// If the file cannot be deleted, `Err` is returned.
165 ///
166 /// # Examples
167 ///
168 /// ```no_run
169 /// # use std::io;
170 /// use camino_tempfile::NamedUtf8TempFile;
171 ///
172 /// # fn main() {
173 /// # if let Err(_) = run() {
174 /// # ::std::process::exit(1);
175 /// # }
176 /// # }
177 /// # fn run() -> Result<(), io::Error> {
178 /// let file = NamedUtf8TempFile::new()?;
179 ///
180 /// // Close the file, but keep the path to it around.
181 /// let path = file.into_temp_path();
182 ///
183 /// // By closing the `Utf8TempPath` explicitly, we can check that it has
184 /// // been deleted successfully. If we don't close it explicitly, the
185 /// // file will still be deleted when `file` goes out of scope, but we
186 /// // won't know whether deleting the file succeeded.
187 /// path.close()?;
188 /// # Ok(())
189 /// # }
190 /// ```
191 pub fn close(self) -> io::Result<()> {
192 self.inner.close()
193 }
194
195 /// Persist the temporary file at the target path.
196 ///
197 /// If a file exists at the target path, persist will atomically replace it. If this method
198 /// fails, it will return `self` in the resulting [`Utf8PathPersistError`].
199 ///
200 /// # Notes
201 ///
202 /// * This method accepts `AsRef<Path>` rather than `AsRef<Utf8Path>`, because in the success
203 /// case it does not return anything.
204 /// * Temporary files cannot be persisted across filesystems.
205 /// * Neither the file contents nor the containing directory are synchronized, so the update may
206 /// not yet have reached the disk when `persist` returns.
207 ///
208 /// # Security
209 ///
210 /// Only use this method if you're positive that a temporary file cleaner won't have deleted
211 /// your file. Otherwise, you might end up persisting an attacker controlled file.
212 ///
213 /// # Errors
214 ///
215 /// If the file cannot be moved to the new location, `Err` is returned.
216 ///
217 /// # Examples
218 ///
219 /// ```no_run
220 /// # use std::io::{self, Write};
221 /// use camino_tempfile::NamedUtf8TempFile;
222 ///
223 /// # fn main() {
224 /// # if let Err(_) = run() {
225 /// # ::std::process::exit(1);
226 /// # }
227 /// # }
228 /// # fn run() -> Result<(), io::Error> {
229 /// let mut file = NamedUtf8TempFile::new()?;
230 /// writeln!(file, "Brian was here. Briefly.")?;
231 ///
232 /// let path = file.into_temp_path();
233 /// path.persist("./saved_file.txt")?;
234 /// # Ok(())
235 /// # }
236 /// ```
237 ///
238 /// [`PathPersistError`]: struct.PathPersistError.html
239 pub fn persist<P: AsRef<Path>>(self, new_path: P) -> Result<(), Utf8PathPersistError> {
240 self.inner.persist(new_path.as_ref()).map_err(|error| {
241 Utf8PathPersistError {
242 error: error.error,
243 // This is OK because the path returned here is self
244 path: Self { inner: error.path },
245 }
246 })
247 }
248
249 /// Persist the temporary file at the target path if and only if no file exists there.
250 ///
251 /// If a file exists at the target path, fail. If this method fails, it will return `self` in
252 /// the resulting [`Utf8PathPersistError`].
253 ///
254 /// # Notes
255 ///
256 /// * This method accepts `AsRef<Path>` rather than `AsRef<Utf8Path>`, because in the success
257 /// case it does not return anything.
258 /// * Temporary files cannot be persisted across filesystems.
259 /// * This method is not atomic. It can leave the original link to the temporary file behind.
260 ///
261 /// # Security
262 ///
263 /// Only use this method if you're positive that a temporary file cleaner won't have deleted
264 /// your file. Otherwise, you might end up persisting an attacker controlled file.
265 ///
266 /// # Errors
267 ///
268 /// If the file cannot be moved to the new location or a file already exists there, `Err` is
269 /// returned.
270 ///
271 /// # Examples
272 ///
273 /// ```no_run
274 /// # use std::io::{self, Write};
275 /// use camino_tempfile::NamedUtf8TempFile;
276 ///
277 /// # fn main() {
278 /// # if let Err(_) = run() {
279 /// # ::std::process::exit(1);
280 /// # }
281 /// # }
282 /// # fn run() -> Result<(), io::Error> {
283 /// let mut file = NamedUtf8TempFile::new()?;
284 /// writeln!(file, "Brian was here. Briefly.")?;
285 ///
286 /// let path = file.into_temp_path();
287 /// path.persist_noclobber("./saved_file.txt")?;
288 /// # Ok(())
289 /// # }
290 /// ```
291 ///
292 /// [`PathPersistError`]: struct.PathPersistError.html
293 pub fn persist_noclobber<P: AsRef<Path>>(
294 self,
295 new_path: P,
296 ) -> Result<(), Utf8PathPersistError> {
297 self.inner
298 .persist_noclobber(new_path.as_ref())
299 .map_err(|error| {
300 Utf8PathPersistError {
301 error: error.error,
302 // This is OK because the path returned here is self
303 path: Self { inner: error.path },
304 }
305 })
306 }
307
308 /// Keep the temporary file from being deleted. This function will turn the temporary file into
309 /// a non-temporary file without moving it.
310 ///
311 ///
312 /// # Errors
313 ///
314 /// On some platforms (e.g., Windows), we need to mark the file as non-temporary. This operation
315 /// could fail.
316 ///
317 /// # Examples
318 ///
319 /// ```no_run
320 /// # use std::io::{self, Write};
321 /// use camino_tempfile::NamedUtf8TempFile;
322 ///
323 /// # fn main() {
324 /// # if let Err(_) = run() {
325 /// # ::std::process::exit(1);
326 /// # }
327 /// # }
328 /// # fn run() -> Result<(), io::Error> {
329 /// let mut file = NamedUtf8TempFile::new()?;
330 /// writeln!(file, "Brian was here. Briefly.")?;
331 ///
332 /// let path = file.into_temp_path();
333 /// let path = path.keep()?;
334 /// # Ok(())
335 /// # }
336 /// ```
337 pub fn keep(self) -> Result<Utf8PathBuf, Utf8PathPersistError> {
338 match self.inner.keep() {
339 Ok(path) => Ok(Utf8PathBuf::try_from(path).expect("invariant: path is UTF-8")),
340 Err(error) => {
341 Err(Utf8PathPersistError {
342 error: error.error,
343 // This is OK because the path returned here is self
344 path: Self { inner: error.path },
345 })
346 }
347 }
348 }
349
350 /// Create a new `Utf8TempPath` from an existing path. This can be done even if no file exists
351 /// at the given path.
352 ///
353 /// This is mostly useful for interacting with libraries and external components that provide
354 /// files to be consumed or expect a path with no existing file to be given.
355 pub fn from_path(path: impl Into<Utf8PathBuf>) -> Self {
356 Self {
357 inner: TempPath::from_path(path.into()),
358 }
359 }
360}
361
362impl fmt::Debug for Utf8TempPath {
363 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
364 self.inner.fmt(f)
365 }
366}
367
368impl Deref for Utf8TempPath {
369 type Target = Utf8Path;
370
371 fn deref(&self) -> &Utf8Path {
372 self.inner
373 .deref()
374 .try_into()
375 .expect("invariant: path is UTF-8")
376 }
377}
378
379impl AsRef<Utf8Path> for Utf8TempPath {
380 #[inline]
381 fn as_ref(&self) -> &Utf8Path {
382 let path: &Path = self.as_ref();
383 path.try_into().expect("invariant: path is valid UTF-8")
384 }
385}
386
387impl AsRef<Path> for Utf8TempPath {
388 #[inline]
389 fn as_ref(&self) -> &Path {
390 self.inner.as_ref()
391 }
392}
393
394impl AsRef<str> for Utf8TempPath {
395 #[inline]
396 fn as_ref(&self) -> &str {
397 let path: &Utf8Path = self.as_ref();
398 path.as_str()
399 }
400}
401
402impl AsRef<OsStr> for Utf8TempPath {
403 #[inline]
404 fn as_ref(&self) -> &OsStr {
405 let path: &Path = self.as_ref();
406 path.as_os_str()
407 }
408}
409
410/// A named temporary file.
411///
412/// The default constructor, [`NamedUtf8TempFile::new()`], creates files in
413/// the location returned by [`std::env::temp_dir()`], but `NamedUtf8TempFile`
414/// can be configured to manage a temporary file in any location
415/// by constructing with [`NamedUtf8TempFile::new_in()`].
416///
417/// # Security
418///
419/// Most operating systems employ temporary file cleaners to delete old
420/// temporary files. Unfortunately these temporary file cleaners don't always
421/// reliably _detect_ whether the temporary file is still being used.
422///
423/// Specifically, the following sequence of events can happen:
424///
425/// 1. A user creates a temporary file with `NamedUtf8TempFile::new()`.
426/// 2. Time passes.
427/// 3. The temporary file cleaner deletes (unlinks) the temporary file from the
428/// filesystem.
429/// 4. Some other program creates a new file to replace this deleted temporary
430/// file.
431/// 5. The user tries to re-open the temporary file (in the same program or in a
432/// different program) by path. Unfortunately, they'll end up opening the
433/// file created by the other program, not the original file.
434///
435/// ## Operating System Specific Concerns
436///
437/// The behavior of temporary files and temporary file cleaners differ by
438/// operating system.
439///
440/// ### Windows
441///
442/// On Windows, open files _can't_ be deleted. This removes most of the concerns
443/// around temporary file cleaners.
444///
445/// Furthermore, temporary files are, by default, created in per-user temporary
446/// file directories so only an application running as the same user would be
447/// able to interfere (which they could do anyways). However, an application
448/// running as the same user can still _accidentally_ re-create deleted
449/// temporary files if the number of random bytes in the temporary file name is
450/// too small.
451///
452/// So, the only real concern on Windows is:
453///
454/// 1. Opening a named temporary file in a world-writable directory.
455/// 2. Using the `into_temp_path()` and/or `into_parts()` APIs to close the file
456/// handle without deleting the underlying file.
457/// 3. Continuing to use the file by path.
458///
459/// ### UNIX
460///
461/// Unlike on Windows, UNIX (and UNIX like) systems allow open files to be
462/// "unlinked" (deleted).
463///
464/// #### MacOS
465///
466/// Like on Windows, temporary files are created in per-user temporary file
467/// directories by default so calling `NamedUtf8TempFile::new()` should be
468/// relatively safe.
469///
470/// #### Linux
471///
472/// Unfortunately, most _Linux_ distributions don't create per-user temporary
473/// file directories. Worse, systemd's tmpfiles daemon (a common temporary file
474/// cleaner) will happily remove open temporary files if they haven't been
475/// modified within the last 10 days.
476///
477/// # Resource Leaking
478///
479/// If the program exits before the `NamedUtf8TempFile` destructor is
480/// run, the temporary file will not be deleted. This can happen
481/// if the process exits using [`std::process::exit()`], a segfault occurs,
482/// receiving an interrupt signal like `SIGINT` that is not handled, or by using
483/// a statically declared `NamedUtf8TempFile` instance (like with `lazy_static`).
484///
485/// Use the [`tempfile()`] function unless you need a named file path.
486pub struct NamedUtf8TempFile<F = File> {
487 // Invariant: inner.path is a valid Utf8TempPath
488 inner: NamedTempFile<F>,
489}
490
491impl<F> NamedUtf8TempFile<F> {
492 pub(crate) fn from_temp_file(inner: NamedTempFile<F>) -> io::Result<Self> {
493 let path = inner.path();
494 // This produces a better error message.
495 Utf8PathBuf::try_from(path.to_owned()).map_err(|error| error.into_io_error())?;
496 Ok(Self { inner })
497 }
498}
499
500impl<F> fmt::Debug for NamedUtf8TempFile<F> {
501 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
502 write!(f, "NamedUtf8TempFile({})", self.path())
503 }
504}
505
506impl<F> AsRef<Utf8Path> for NamedUtf8TempFile<F> {
507 #[inline]
508 fn as_ref(&self) -> &Utf8Path {
509 self.path()
510 }
511}
512impl<F> AsRef<Path> for NamedUtf8TempFile<F> {
513 #[inline]
514 fn as_ref(&self) -> &Path {
515 self.path().as_std_path()
516 }
517}
518
519impl NamedUtf8TempFile<File> {
520 /// Create a new named temporary file.
521 ///
522 /// See [`Builder`] for more configuration.
523 ///
524 /// # Security
525 ///
526 /// This will create a temporary file in the default temporary file
527 /// directory (platform dependent). This has security implications on many
528 /// platforms so please read the security section of this type's
529 /// documentation.
530 ///
531 /// Reasons to use this method:
532 ///
533 /// 1. The file has a short lifetime and your temporary file cleaner is
534 /// sane (doesn't delete recently accessed files).
535 ///
536 /// 2. You trust every user on your system (i.e. you are the only user).
537 ///
538 /// 3. You have disabled your system's temporary file cleaner or verified
539 /// that your system doesn't have a temporary file cleaner.
540 ///
541 /// Reasons not to use this method:
542 ///
543 /// 1. You'll fix it later. No you won't.
544 ///
545 /// 2. You don't care about the security of the temporary file. If none of
546 /// the "reasons to use this method" apply, referring to a temporary
547 /// file by name may allow an attacker to create/overwrite your
548 /// non-temporary files. There are exceptions but if you don't already
549 /// know them, don't use this method.
550 ///
551 /// # Errors
552 ///
553 /// If the file can not be created, `Err` is returned.
554 ///
555 /// # Examples
556 ///
557 /// Create a named temporary file and write some data to it:
558 ///
559 /// ```no_run
560 /// # use std::io::{self, Write};
561 /// use camino_tempfile::NamedUtf8TempFile;
562 ///
563 /// # fn main() {
564 /// # if let Err(_) = run() {
565 /// # ::std::process::exit(1);
566 /// # }
567 /// # }
568 /// # fn run() -> Result<(), ::std::io::Error> {
569 /// let mut file = NamedUtf8TempFile::new()?;
570 ///
571 /// writeln!(file, "Brian was here. Briefly.")?;
572 /// # Ok(())
573 /// # }
574 /// ```
575 ///
576 /// [`Builder`]: struct.Builder.html
577 pub fn new() -> io::Result<NamedUtf8TempFile> {
578 Builder::new().tempfile()
579 }
580
581 /// Create a new named temporary file in the specified directory.
582 ///
583 /// See [`NamedUtf8TempFile::new()`] for details.
584 pub fn new_in<P: AsRef<Utf8Path>>(dir: P) -> io::Result<NamedUtf8TempFile> {
585 Builder::new().tempfile_in(dir)
586 }
587
588 /// Create a new named temporary file with the specified filename prefix.
589 ///
590 /// See [`NamedUtf8TempFile::new()`] for details.
591 pub fn with_prefix<S: AsRef<str>>(prefix: S) -> io::Result<NamedUtf8TempFile> {
592 Builder::new().prefix(&prefix).tempfile()
593 }
594
595 /// Create a new named temporary file with the specified filename prefix,
596 /// in the specified directory.
597 ///
598 /// This is equivalent to:
599 ///
600 /// ```ignore
601 /// Builder::new().prefix(&prefix).tempfile_in(directory)
602 /// ```
603 ///
604 /// See [`NamedUtf8TempFile::new()`] for details.
605 pub fn with_prefix_in<S: AsRef<str>, P: AsRef<Utf8Path>>(
606 prefix: S,
607 dir: P,
608 ) -> io::Result<NamedUtf8TempFile> {
609 Builder::new().prefix(&prefix).tempfile_in(dir)
610 }
611
612 /// Create a new named temporary file with the specified filename suffix.
613 ///
614 /// See [`NamedUtf8TempFile::new()`] for details.
615 pub fn with_suffix<S: AsRef<str>>(suffix: S) -> io::Result<NamedUtf8TempFile> {
616 Builder::new().suffix(&suffix).tempfile()
617 }
618
619 /// Create a new named temporary file with the specified filename suffix,
620 /// in the specified directory.
621 ///
622 /// This is equivalent to:
623 ///
624 /// ```ignore
625 /// Builder::new().suffix(&suffix).tempfile_in(directory)
626 /// ```
627 ///
628 /// See [`NamedUtf8TempFile::new()`] for details.
629 pub fn with_suffix_in<S: AsRef<str>, P: AsRef<Utf8Path>>(
630 suffix: S,
631 dir: P,
632 ) -> io::Result<NamedUtf8TempFile> {
633 Builder::new().suffix(&suffix).tempfile_in(dir)
634 }
635}
636
637impl<F> NamedUtf8TempFile<F> {
638 /// Get the temporary file's path.
639 ///
640 /// # Security
641 ///
642 /// Referring to a temporary file's path may not be secure in all cases.
643 /// Please read the security section on the top level documentation of this
644 /// type for details.
645 ///
646 /// # Examples
647 ///
648 /// ```no_run
649 /// # use std::io::{self, Write};
650 /// use camino_tempfile::NamedUtf8TempFile;
651 ///
652 /// # fn main() {
653 /// # if let Err(_) = run() {
654 /// # ::std::process::exit(1);
655 /// # }
656 /// # }
657 /// # fn run() -> Result<(), ::std::io::Error> {
658 /// let file = NamedUtf8TempFile::new()?;
659 ///
660 /// println!("{}", file.path());
661 /// # Ok(())
662 /// # }
663 /// ```
664 #[inline]
665 pub fn path(&self) -> &Utf8Path {
666 self.inner
667 .path()
668 .try_into()
669 .expect("invariant: path is valid UTF-8")
670 }
671
672 /// Close and remove the temporary file.
673 ///
674 /// Use this if you want to detect errors in deleting the file.
675 ///
676 /// # Errors
677 ///
678 /// If the file cannot be deleted, `Err` is returned.
679 ///
680 /// # Examples
681 ///
682 /// ```no_run
683 /// # use std::io;
684 /// use camino_tempfile::NamedUtf8TempFile;
685 ///
686 /// # fn main() {
687 /// # if let Err(_) = run() {
688 /// # ::std::process::exit(1);
689 /// # }
690 /// # }
691 /// # fn run() -> Result<(), io::Error> {
692 /// let file = NamedUtf8TempFile::new()?;
693 ///
694 /// // By closing the `NamedUtf8TempFile` explicitly, we can check that it
695 /// // has been deleted successfully. If we don't close it explicitly,
696 /// // the file will still be deleted when `file` goes out
697 /// // of scope, but we won't know whether deleting the file
698 /// // succeeded.
699 /// file.close()?;
700 /// # Ok(())
701 /// # }
702 /// ```
703 pub fn close(self) -> io::Result<()> {
704 self.inner.close()
705 }
706
707 /// Persist the temporary file at the target path.
708 ///
709 /// If a file exists at the target path, persist will atomically replace it. If this method
710 /// fails, it will return `self` in the resulting [`Utf8PersistError`].
711 ///
712 /// # Notes
713 ///
714 /// * This method accepts `AsRef<Path>` rather than `AsRef<Utf8Path>` because it returns the
715 /// underlying file type `F`.
716 /// * Temporary files cannot be persisted across filesystems.
717 /// * Neither the file contents nor the containing directory are synchronized, so the update may
718 /// not yet have reached the disk when `persist` returns.
719 ///
720 /// # Security
721 ///
722 /// This method persists the temporary file using its path and may not be secure in the in all
723 /// cases. Please read the security section on the top level documentation of this type for
724 /// details.
725 ///
726 /// # Errors
727 ///
728 /// If the file cannot be moved to the new location, `Err` is returned.
729 ///
730 /// # Examples
731 ///
732 /// ```no_run
733 /// # use std::io::{self, Write};
734 /// use camino_tempfile::NamedUtf8TempFile;
735 ///
736 /// # fn main() {
737 /// # if let Err(_) = run() {
738 /// # ::std::process::exit(1);
739 /// # }
740 /// # }
741 /// # fn run() -> Result<(), io::Error> {
742 /// let file = NamedUtf8TempFile::new()?;
743 ///
744 /// let mut persisted_file = file.persist("./saved_file.txt")?;
745 /// writeln!(persisted_file, "Brian was here. Briefly.")?;
746 /// # Ok(())
747 /// # }
748 /// ```
749 ///
750 /// [`PersistError`]: struct.PersistError.html
751 pub fn persist<P: AsRef<Path>>(self, new_path: P) -> Result<F, Utf8PersistError<F>> {
752 self.inner.persist(new_path).map_err(|error| {
753 Utf8PersistError {
754 // This is valid because self is exactly error.file.
755 file: NamedUtf8TempFile { inner: error.file },
756 error: error.error,
757 }
758 })
759 }
760
761 /// Persist the temporary file at the target path if and only if no file exists there.
762 ///
763 /// If a file exists at the target path, fail. If this method fails, it will return `self` in
764 /// the resulting [`Utf8PersistError`].
765 ///
766 /// # Notes
767 ///
768 /// * This method accepts `AsRef<Path>` rather than `AsRef<Utf8Path>` because it returns the
769 /// underlying file type `F`.
770 /// * Temporary files cannot be persisted across filesystems.
771 /// * This method is not atomic. It can leave the original link to the temporary file behind.
772 ///
773 /// # Security
774 ///
775 /// This method persists the temporary file using its path and may not be secure in the in all
776 /// cases. Please read the security section on the top level documentation of this type for
777 /// details.
778 ///
779 /// # Errors
780 ///
781 /// If the file cannot be moved to the new location or a file already exists there, `Err` is
782 /// returned.
783 ///
784 /// # Examples
785 ///
786 /// ```no_run
787 /// # use std::io::{self, Write};
788 /// use camino_tempfile::NamedUtf8TempFile;
789 ///
790 /// # fn main() {
791 /// # if let Err(_) = run() {
792 /// # ::std::process::exit(1);
793 /// # }
794 /// # }
795 /// # fn run() -> Result<(), io::Error> {
796 /// let file = NamedUtf8TempFile::new()?;
797 ///
798 /// let mut persisted_file = file.persist_noclobber("./saved_file.txt")?;
799 /// writeln!(persisted_file, "Brian was here. Briefly.")?;
800 /// # Ok(())
801 /// # }
802 /// ```
803 pub fn persist_noclobber<P: AsRef<Path>>(self, new_path: P) -> Result<F, Utf8PersistError<F>> {
804 self.inner.persist_noclobber(new_path).map_err(|error| {
805 Utf8PersistError {
806 // This is valid because self is exactly error.file.
807 file: NamedUtf8TempFile { inner: error.file },
808 error: error.error,
809 }
810 })
811 }
812
813 /// Keep the temporary file from being deleted. This function will turn the
814 /// temporary file into a non-temporary file without moving it.
815 ///
816 ///
817 /// # Errors
818 ///
819 /// On some platforms (e.g., Windows), we need to mark the file as
820 /// non-temporary. This operation could fail.
821 ///
822 /// # Examples
823 ///
824 /// ```no_run
825 /// # use std::io::{self, Write};
826 /// use camino_tempfile::NamedUtf8TempFile;
827 ///
828 /// # fn main() {
829 /// # if let Err(_) = run() {
830 /// # ::std::process::exit(1);
831 /// # }
832 /// # }
833 /// # fn run() -> Result<(), io::Error> {
834 /// let mut file = NamedUtf8TempFile::new()?;
835 /// writeln!(file, "Brian was here. Briefly.")?;
836 ///
837 /// let (file, path) = file.keep()?;
838 /// # Ok(())
839 /// # }
840 /// ```
841 ///
842 /// [`PathPersistError`]: struct.PathPersistError.html
843 pub fn keep(self) -> Result<(F, Utf8PathBuf), Utf8PersistError<F>> {
844 match self.inner.keep() {
845 Ok((file, path)) => Ok((
846 file,
847 path.try_into().expect("invariant: path is valid UTF-8"),
848 )),
849 Err(error) => Err(Utf8PersistError {
850 // This is valid because self is exactly error.file.
851 file: NamedUtf8TempFile { inner: error.file },
852 error: error.error,
853 }),
854 }
855 }
856
857 /// Get a reference to the underlying file.
858 pub fn as_file(&self) -> &F {
859 self.inner.as_file()
860 }
861
862 /// Get a mutable reference to the underlying file.
863 pub fn as_file_mut(&mut self) -> &mut F {
864 self.inner.as_file_mut()
865 }
866
867 /// Convert the temporary file into a `std::fs::File`.
868 ///
869 /// The inner file will be deleted.
870 pub fn into_file(self) -> F {
871 self.inner.into_file()
872 }
873
874 /// Closes the file, leaving only the temporary file path.
875 ///
876 /// This is useful when another process must be able to open the temporary
877 /// file.
878 pub fn into_temp_path(self) -> Utf8TempPath {
879 Utf8TempPath::from_temp_path(self.inner.into_temp_path())
880 .expect("invariant: inner path is UTF-8")
881 }
882
883 /// Converts the named temporary file into its constituent parts.
884 ///
885 /// Note: When the path is dropped, the file is deleted but the file handle
886 /// is still usable.
887 pub fn into_parts(self) -> (F, Utf8TempPath) {
888 let (file, path) = self.inner.into_parts();
889 let path = Utf8TempPath::from_temp_path(path).expect("invariant: inner path is UTF-8");
890 (file, path)
891 }
892
893 /// Creates a `NamedUtf8TempFile` from its constituent parts.
894 ///
895 /// This can be used with [`NamedUtf8TempFile::into_parts`] to reconstruct the
896 /// `NamedUtf8TempFile`.
897 pub fn from_parts(file: F, path: Utf8TempPath) -> Self {
898 let inner = NamedTempFile::from_parts(file, path.inner);
899 // This is valid because it was constructed from a Utf8TempPath
900 Self { inner }
901 }
902}
903
904impl NamedUtf8TempFile<File> {
905 /// Securely reopen the temporary file.
906 ///
907 /// This function is useful when you need multiple independent handles to the same file. It's
908 /// perfectly fine to drop the original `NamedUtf8TempFile` while holding on to `File`s returned
909 /// by this function; the `File`s will remain usable. However, they may not be nameable.
910 ///
911 /// # Errors
912 ///
913 /// If the file cannot be reopened, `Err` is returned.
914 ///
915 /// # Security
916 ///
917 /// Unlike `File::open(my_temp_file.path())`, `NamedUtf8TempFile::reopen()` guarantees that the
918 /// re-opened file is the _same_ file, even in the presence of pathological temporary file
919 /// cleaners.
920 ///
921 /// # Examples
922 ///
923 /// ```no_run
924 /// # use std::io;
925 /// use camino_tempfile::NamedUtf8TempFile;
926 ///
927 /// # fn main() {
928 /// # if let Err(_) = run() {
929 /// # ::std::process::exit(1);
930 /// # }
931 /// # }
932 /// # fn run() -> Result<(), io::Error> {
933 /// let file = NamedUtf8TempFile::new()?;
934 ///
935 /// let another_handle = file.reopen()?;
936 /// # Ok(())
937 /// # }
938 /// ```
939 pub fn reopen(&self) -> io::Result<File> {
940 self.inner.reopen()
941 }
942}
943
944impl<F: Read> Read for NamedUtf8TempFile<F> {
945 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
946 self.as_file_mut().read(buf).with_err_path(|| self.path())
947 }
948}
949
950impl Read for &NamedUtf8TempFile<File> {
951 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
952 self.as_file().read(buf).with_err_path(|| self.path())
953 }
954}
955
956impl<F: Write> Write for NamedUtf8TempFile<F> {
957 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
958 self.as_file_mut().write(buf).with_err_path(|| self.path())
959 }
960 #[inline]
961 fn flush(&mut self) -> io::Result<()> {
962 self.as_file_mut().flush().with_err_path(|| self.path())
963 }
964}
965
966impl Write for &NamedUtf8TempFile<File> {
967 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
968 self.as_file().write(buf).with_err_path(|| self.path())
969 }
970 #[inline]
971 fn flush(&mut self) -> io::Result<()> {
972 self.as_file().flush().with_err_path(|| self.path())
973 }
974}
975
976impl<F: Seek> Seek for NamedUtf8TempFile<F> {
977 fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
978 self.as_file_mut().seek(pos).with_err_path(|| self.path())
979 }
980}
981
982impl Seek for &NamedUtf8TempFile<File> {
983 fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
984 self.as_file().seek(pos).with_err_path(|| self.path())
985 }
986}
987
988#[cfg(unix)]
989impl<F> std::os::unix::io::AsRawFd for NamedUtf8TempFile<F>
990where
991 F: std::os::unix::io::AsRawFd,
992{
993 #[inline]
994 fn as_raw_fd(&self) -> std::os::unix::io::RawFd {
995 self.as_file().as_raw_fd()
996 }
997}
998
999#[cfg(windows)]
1000impl<F> std::os::windows::io::AsRawHandle for NamedUtf8TempFile<F>
1001where
1002 F: std::os::windows::io::AsRawHandle,
1003{
1004 #[inline]
1005 fn as_raw_handle(&self) -> std::os::windows::io::RawHandle {
1006 self.as_file().as_raw_handle()
1007 }
1008}
1009
1010/// Error returned when persisting a temporary file fails.
1011pub struct Utf8PersistError<F = File> {
1012 /// The underlying IO error.
1013 pub error: io::Error,
1014 /// The temporary file that couldn't be persisted.
1015 pub file: NamedUtf8TempFile<F>,
1016}
1017
1018impl<F> fmt::Debug for Utf8PersistError<F> {
1019 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1020 write!(f, "Utf8PersistError({:?})", self.error)
1021 }
1022}
1023
1024impl<F> From<Utf8PersistError<F>> for io::Error {
1025 #[inline]
1026 fn from(error: Utf8PersistError<F>) -> io::Error {
1027 error.error
1028 }
1029}
1030
1031impl<F> From<Utf8PersistError<F>> for NamedUtf8TempFile<F> {
1032 #[inline]
1033 fn from(error: Utf8PersistError<F>) -> NamedUtf8TempFile<F> {
1034 error.file
1035 }
1036}
1037
1038impl<F> fmt::Display for Utf8PersistError<F> {
1039 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1040 write!(f, "failed to persist temporary file: {}", self.error)
1041 }
1042}
1043
1044impl<F> error::Error for Utf8PersistError<F> {
1045 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
1046 Some(&self.error)
1047 }
1048}