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}