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    /// Disable cleanup of the temporary file. If `disable_cleanup` is `true`,
351    /// the temporary file will not be deleted when this `Utf8TempPath` is
352    /// dropped. This method is equivalent to calling
353    /// [`Builder::disable_cleanup`] when creating the original `Utf8TempPath`,
354    /// which see for relevant warnings.
355    ///
356    /// **NOTE:** this method is primarily useful for testing/debugging. If you
357    /// want to simply turn a temporary file-path into a non-temporary
358    /// file-path, prefer [`Utf8TempPath::keep`].
359    pub fn disable_cleanup(&mut self, disable_cleanup: bool) {
360        self.inner.disable_cleanup(disable_cleanup);
361    }
362
363    /// Create a new `Utf8TempPath` from an existing path. This can be done even if no file exists
364    /// at the given path.
365    ///
366    /// This is mostly useful for interacting with libraries and external components that provide
367    /// files to be consumed or expect a path with no existing file to be given.
368    pub fn from_path(path: impl Into<Utf8PathBuf>) -> Self {
369        Self {
370            inner: TempPath::from_path(path.into()),
371        }
372    }
373}
374
375impl fmt::Debug for Utf8TempPath {
376    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
377        self.inner.fmt(f)
378    }
379}
380
381impl Deref for Utf8TempPath {
382    type Target = Utf8Path;
383
384    fn deref(&self) -> &Utf8Path {
385        self.inner
386            .deref()
387            .try_into()
388            .expect("invariant: path is UTF-8")
389    }
390}
391
392impl AsRef<Utf8Path> for Utf8TempPath {
393    #[inline]
394    fn as_ref(&self) -> &Utf8Path {
395        let path: &Path = self.as_ref();
396        path.try_into().expect("invariant: path is valid UTF-8")
397    }
398}
399
400impl AsRef<Path> for Utf8TempPath {
401    #[inline]
402    fn as_ref(&self) -> &Path {
403        self.inner.as_ref()
404    }
405}
406
407impl AsRef<str> for Utf8TempPath {
408    #[inline]
409    fn as_ref(&self) -> &str {
410        let path: &Utf8Path = self.as_ref();
411        path.as_str()
412    }
413}
414
415impl AsRef<OsStr> for Utf8TempPath {
416    #[inline]
417    fn as_ref(&self) -> &OsStr {
418        let path: &Path = self.as_ref();
419        path.as_os_str()
420    }
421}
422
423/// A named temporary file.
424///
425/// The default constructor, [`NamedUtf8TempFile::new()`], creates files in
426/// the location returned by [`std::env::temp_dir()`], but `NamedUtf8TempFile`
427/// can be configured to manage a temporary file in any location
428/// by constructing with [`NamedUtf8TempFile::new_in()`].
429///
430/// # Security
431///
432/// Most operating systems employ temporary file cleaners to delete old
433/// temporary files. Unfortunately these temporary file cleaners don't always
434/// reliably _detect_ whether the temporary file is still being used.
435///
436/// Specifically, the following sequence of events can happen:
437///
438/// 1. A user creates a temporary file with `NamedUtf8TempFile::new()`.
439/// 2. Time passes.
440/// 3. The temporary file cleaner deletes (unlinks) the temporary file from the
441///    filesystem.
442/// 4. Some other program creates a new file to replace this deleted temporary
443///    file.
444/// 5. The user tries to re-open the temporary file (in the same program or in a
445///    different program) by path. Unfortunately, they'll end up opening the
446///    file created by the other program, not the original file.
447///
448/// ## Operating System Specific Concerns
449///
450/// The behavior of temporary files and temporary file cleaners differ by
451/// operating system.
452///
453/// ### Windows
454///
455/// On Windows, open files _can't_ be deleted. This removes most of the concerns
456/// around temporary file cleaners.
457///
458/// Furthermore, temporary files are, by default, created in per-user temporary
459/// file directories so only an application running as the same user would be
460/// able to interfere (which they could do anyways). However, an application
461/// running as the same user can still _accidentally_ re-create deleted
462/// temporary files if the number of random bytes in the temporary file name is
463/// too small.
464///
465/// So, the only real concern on Windows is:
466///
467/// 1. Opening a named temporary file in a world-writable directory.
468/// 2. Using the `into_temp_path()` and/or `into_parts()` APIs to close the file
469///    handle without deleting the underlying file.
470/// 3. Continuing to use the file by path.
471///
472/// ### UNIX
473///
474/// Unlike on Windows, UNIX (and UNIX like) systems allow open files to be
475/// "unlinked" (deleted).
476///
477/// #### MacOS
478///
479/// Like on Windows, temporary files are created in per-user temporary file
480/// directories by default so calling `NamedUtf8TempFile::new()` should be
481/// relatively safe.
482///
483/// #### Linux
484///
485/// Unfortunately, most _Linux_ distributions don't create per-user temporary
486/// file directories. Worse, systemd's tmpfiles daemon (a common temporary file
487/// cleaner) will happily remove open temporary files if they haven't been
488/// modified within the last 10 days.
489///
490/// # Resource Leaking
491///
492/// If the program exits before the `NamedUtf8TempFile` destructor is
493/// run, the temporary file will not be deleted. This can happen
494/// if the process exits using [`std::process::exit()`], a segfault occurs,
495/// receiving an interrupt signal like `SIGINT` that is not handled, or by using
496/// a statically declared `NamedUtf8TempFile` instance (like with `lazy_static`).
497///
498/// Use the [`tempfile()`] function unless you need a named file path.
499pub struct NamedUtf8TempFile<F = File> {
500    // Invariant: inner.path is a valid Utf8TempPath
501    inner: NamedTempFile<F>,
502}
503
504impl<F> NamedUtf8TempFile<F> {
505    pub(crate) fn from_temp_file(inner: NamedTempFile<F>) -> io::Result<Self> {
506        let path = inner.path();
507        // This produces a better error message.
508        Utf8PathBuf::try_from(path.to_owned()).map_err(|error| error.into_io_error())?;
509        Ok(Self { inner })
510    }
511}
512
513impl<F> fmt::Debug for NamedUtf8TempFile<F> {
514    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
515        write!(f, "NamedUtf8TempFile({})", self.path())
516    }
517}
518
519impl<F> AsRef<Utf8Path> for NamedUtf8TempFile<F> {
520    #[inline]
521    fn as_ref(&self) -> &Utf8Path {
522        self.path()
523    }
524}
525impl<F> AsRef<Path> for NamedUtf8TempFile<F> {
526    #[inline]
527    fn as_ref(&self) -> &Path {
528        self.path().as_std_path()
529    }
530}
531
532impl NamedUtf8TempFile<File> {
533    /// Create a new named temporary file.
534    ///
535    /// See [`Builder`] for more configuration.
536    ///
537    /// # Security
538    ///
539    /// This will create a temporary file in the default temporary file
540    /// directory (platform dependent). This has security implications on many
541    /// platforms so please read the security section of this type's
542    /// documentation.
543    ///
544    /// Reasons to use this method:
545    ///
546    ///   1. The file has a short lifetime and your temporary file cleaner is
547    ///      sane (doesn't delete recently accessed files).
548    ///
549    ///   2. You trust every user on your system (i.e. you are the only user).
550    ///
551    ///   3. You have disabled your system's temporary file cleaner or verified
552    ///      that your system doesn't have a temporary file cleaner.
553    ///
554    /// Reasons not to use this method:
555    ///
556    ///   1. You'll fix it later. No you won't.
557    ///
558    ///   2. You don't care about the security of the temporary file. If none of
559    ///      the "reasons to use this method" apply, referring to a temporary
560    ///      file by name may allow an attacker to create/overwrite your
561    ///      non-temporary files. There are exceptions but if you don't already
562    ///      know them, don't use this method.
563    ///
564    /// # Errors
565    ///
566    /// If the file can not be created, `Err` is returned.
567    ///
568    /// # Examples
569    ///
570    /// Create a named temporary file and write some data to it:
571    ///
572    /// ```no_run
573    /// # use std::io::{self, Write};
574    /// use camino_tempfile::NamedUtf8TempFile;
575    ///
576    /// # fn main() {
577    /// #     if let Err(_) = run() {
578    /// #         ::std::process::exit(1);
579    /// #     }
580    /// # }
581    /// # fn run() -> Result<(), ::std::io::Error> {
582    /// let mut file = NamedUtf8TempFile::new()?;
583    ///
584    /// writeln!(file, "Brian was here. Briefly.")?;
585    /// # Ok(())
586    /// # }
587    /// ```
588    ///
589    /// [`Builder`]: struct.Builder.html
590    pub fn new() -> io::Result<NamedUtf8TempFile> {
591        Builder::new().tempfile()
592    }
593
594    /// Create a new named temporary file in the specified directory.
595    ///
596    /// See [`NamedUtf8TempFile::new()`] for details.
597    pub fn new_in<P: AsRef<Utf8Path>>(dir: P) -> io::Result<NamedUtf8TempFile> {
598        Builder::new().tempfile_in(dir)
599    }
600
601    /// Create a new named temporary file with the specified filename prefix.
602    ///
603    /// See [`NamedUtf8TempFile::new()`] for details.
604    pub fn with_prefix<S: AsRef<str>>(prefix: S) -> io::Result<NamedUtf8TempFile> {
605        Builder::new().prefix(&prefix).tempfile()
606    }
607
608    /// Create a new named temporary file with the specified filename prefix,
609    /// in the specified directory.
610    ///
611    /// This is equivalent to:
612    ///
613    /// ```ignore
614    /// Builder::new().prefix(&prefix).tempfile_in(directory)
615    /// ```
616    ///
617    /// See [`NamedUtf8TempFile::new()`] for details.
618    pub fn with_prefix_in<S: AsRef<str>, P: AsRef<Utf8Path>>(
619        prefix: S,
620        dir: P,
621    ) -> io::Result<NamedUtf8TempFile> {
622        Builder::new().prefix(&prefix).tempfile_in(dir)
623    }
624
625    /// Create a new named temporary file with the specified filename suffix.
626    ///
627    /// See [`NamedUtf8TempFile::new()`] for details.
628    pub fn with_suffix<S: AsRef<str>>(suffix: S) -> io::Result<NamedUtf8TempFile> {
629        Builder::new().suffix(&suffix).tempfile()
630    }
631
632    /// Create a new named temporary file with the specified filename suffix,
633    /// in the specified directory.
634    ///
635    /// This is equivalent to:
636    ///
637    /// ```ignore
638    /// Builder::new().suffix(&suffix).tempfile_in(directory)
639    /// ```
640    ///
641    /// See [`NamedUtf8TempFile::new()`] for details.
642    pub fn with_suffix_in<S: AsRef<str>, P: AsRef<Utf8Path>>(
643        suffix: S,
644        dir: P,
645    ) -> io::Result<NamedUtf8TempFile> {
646        Builder::new().suffix(&suffix).tempfile_in(dir)
647    }
648}
649
650impl<F> NamedUtf8TempFile<F> {
651    /// Get the temporary file's path.
652    ///
653    /// # Security
654    ///
655    /// Referring to a temporary file's path may not be secure in all cases.
656    /// Please read the security section on the top level documentation of this
657    /// type for details.
658    ///
659    /// # Examples
660    ///
661    /// ```no_run
662    /// # use std::io::{self, Write};
663    /// use camino_tempfile::NamedUtf8TempFile;
664    ///
665    /// # fn main() {
666    /// #     if let Err(_) = run() {
667    /// #         ::std::process::exit(1);
668    /// #     }
669    /// # }
670    /// # fn run() -> Result<(), ::std::io::Error> {
671    /// let file = NamedUtf8TempFile::new()?;
672    ///
673    /// println!("{}", file.path());
674    /// # Ok(())
675    /// # }
676    /// ```
677    #[inline]
678    pub fn path(&self) -> &Utf8Path {
679        self.inner
680            .path()
681            .try_into()
682            .expect("invariant: path is valid UTF-8")
683    }
684
685    /// Close and remove the temporary file.
686    ///
687    /// Use this if you want to detect errors in deleting the file.
688    ///
689    /// # Errors
690    ///
691    /// If the file cannot be deleted, `Err` is returned.
692    ///
693    /// # Examples
694    ///
695    /// ```no_run
696    /// # use std::io;
697    /// use camino_tempfile::NamedUtf8TempFile;
698    ///
699    /// # fn main() {
700    /// #     if let Err(_) = run() {
701    /// #         ::std::process::exit(1);
702    /// #     }
703    /// # }
704    /// # fn run() -> Result<(), io::Error> {
705    /// let file = NamedUtf8TempFile::new()?;
706    ///
707    /// // By closing the `NamedUtf8TempFile` explicitly, we can check that it
708    /// // has been deleted successfully. If we don't close it explicitly,
709    /// // the file will still be deleted when `file` goes out
710    /// // of scope, but we won't know whether deleting the file
711    /// // succeeded.
712    /// file.close()?;
713    /// # Ok(())
714    /// # }
715    /// ```
716    pub fn close(self) -> io::Result<()> {
717        self.inner.close()
718    }
719
720    /// Persist the temporary file at the target path.
721    ///
722    /// If a file exists at the target path, persist will atomically replace it. If this method
723    /// fails, it will return `self` in the resulting [`Utf8PersistError`].
724    ///
725    /// # Notes
726    ///
727    /// * This method accepts `AsRef<Path>` rather than `AsRef<Utf8Path>` because it returns the
728    ///   underlying file type `F`.
729    /// * Temporary files cannot be persisted across filesystems.
730    /// * Neither the file contents nor the containing directory are synchronized, so the update may
731    ///   not yet have reached the disk when `persist` returns.
732    ///
733    /// # Security
734    ///
735    /// This method persists the temporary file using its path and may not be secure in the in all
736    /// cases. Please read the security section on the top level documentation of this type for
737    /// details.
738    ///
739    /// # Errors
740    ///
741    /// If the file cannot be moved to the new location, `Err` is returned.
742    ///
743    /// # Examples
744    ///
745    /// ```no_run
746    /// # use std::io::{self, Write};
747    /// use camino_tempfile::NamedUtf8TempFile;
748    ///
749    /// # fn main() {
750    /// #     if let Err(_) = run() {
751    /// #         ::std::process::exit(1);
752    /// #     }
753    /// # }
754    /// # fn run() -> Result<(), io::Error> {
755    /// let file = NamedUtf8TempFile::new()?;
756    ///
757    /// let mut persisted_file = file.persist("./saved_file.txt")?;
758    /// writeln!(persisted_file, "Brian was here. Briefly.")?;
759    /// # Ok(())
760    /// # }
761    /// ```
762    ///
763    /// [`PersistError`]: struct.PersistError.html
764    pub fn persist<P: AsRef<Path>>(self, new_path: P) -> Result<F, Utf8PersistError<F>> {
765        self.inner.persist(new_path).map_err(|error| {
766            Utf8PersistError {
767                // This is valid because self is exactly error.file.
768                file: NamedUtf8TempFile { inner: error.file },
769                error: error.error,
770            }
771        })
772    }
773
774    /// Persist the temporary file at the target path if and only if no file exists there.
775    ///
776    /// If a file exists at the target path, fail. If this method fails, it will return `self` in
777    /// the resulting [`Utf8PersistError`].
778    ///
779    /// # Notes
780    ///
781    /// * This method accepts `AsRef<Path>` rather than `AsRef<Utf8Path>` because it returns the
782    ///   underlying file type `F`.
783    /// * Temporary files cannot be persisted across filesystems.
784    /// * This method is not atomic. It can leave the original link to the temporary file behind.
785    ///
786    /// # Security
787    ///
788    /// This method persists the temporary file using its path and may not be secure in the in all
789    /// cases. Please read the security section on the top level documentation of this type for
790    /// details.
791    ///
792    /// # Errors
793    ///
794    /// If the file cannot be moved to the new location or a file already exists there, `Err` is
795    /// returned.
796    ///
797    /// # Examples
798    ///
799    /// ```no_run
800    /// # use std::io::{self, Write};
801    /// use camino_tempfile::NamedUtf8TempFile;
802    ///
803    /// # fn main() {
804    /// #     if let Err(_) = run() {
805    /// #         ::std::process::exit(1);
806    /// #     }
807    /// # }
808    /// # fn run() -> Result<(), io::Error> {
809    /// let file = NamedUtf8TempFile::new()?;
810    ///
811    /// let mut persisted_file = file.persist_noclobber("./saved_file.txt")?;
812    /// writeln!(persisted_file, "Brian was here. Briefly.")?;
813    /// # Ok(())
814    /// # }
815    /// ```
816    pub fn persist_noclobber<P: AsRef<Path>>(self, new_path: P) -> Result<F, Utf8PersistError<F>> {
817        self.inner.persist_noclobber(new_path).map_err(|error| {
818            Utf8PersistError {
819                // This is valid because self is exactly error.file.
820                file: NamedUtf8TempFile { inner: error.file },
821                error: error.error,
822            }
823        })
824    }
825
826    /// Keep the temporary file from being deleted. This function will turn the
827    /// temporary file into a non-temporary file without moving it.
828    ///
829    ///
830    /// # Errors
831    ///
832    /// On some platforms (e.g., Windows), we need to mark the file as
833    /// non-temporary. This operation could fail.
834    ///
835    /// # Examples
836    ///
837    /// ```no_run
838    /// # use std::io::{self, Write};
839    /// use camino_tempfile::NamedUtf8TempFile;
840    ///
841    /// # fn main() {
842    /// #     if let Err(_) = run() {
843    /// #         ::std::process::exit(1);
844    /// #     }
845    /// # }
846    /// # fn run() -> Result<(), io::Error> {
847    /// let mut file = NamedUtf8TempFile::new()?;
848    /// writeln!(file, "Brian was here. Briefly.")?;
849    ///
850    /// let (file, path) = file.keep()?;
851    /// # Ok(())
852    /// # }
853    /// ```
854    ///
855    /// [`PathPersistError`]: struct.PathPersistError.html
856    pub fn keep(self) -> Result<(F, Utf8PathBuf), Utf8PersistError<F>> {
857        match self.inner.keep() {
858            Ok((file, path)) => Ok((
859                file,
860                path.try_into().expect("invariant: path is valid UTF-8"),
861            )),
862            Err(error) => Err(Utf8PersistError {
863                // This is valid because self is exactly error.file.
864                file: NamedUtf8TempFile { inner: error.file },
865                error: error.error,
866            }),
867        }
868    }
869
870    /// Disable cleanup of the temporary file. If `disable_cleanup` is `true`,
871    /// the temporary file will not be deleted when this `NamedUtf8TempFile` is
872    /// dropped. This method is equivalent to calling
873    /// [`Builder::disable_cleanup`] when creating the original
874    /// `NamedUtf8TempFile`, which see for relevant warnings.
875    ///
876    /// **NOTE:** this method is primarily useful for testing/debugging. If you
877    /// want to simply turn a temporary file into a non-temporary file, prefer
878    /// [`NamedUtf8TempFile::keep`].
879    pub fn disable_cleanup(&mut self, disable_cleanup: bool) {
880        self.inner.disable_cleanup(disable_cleanup);
881    }
882
883    /// Get a reference to the underlying file.
884    pub fn as_file(&self) -> &F {
885        self.inner.as_file()
886    }
887
888    /// Get a mutable reference to the underlying file.
889    pub fn as_file_mut(&mut self) -> &mut F {
890        self.inner.as_file_mut()
891    }
892
893    /// Convert the temporary file into a `std::fs::File`.
894    ///
895    /// The inner file will be deleted.
896    pub fn into_file(self) -> F {
897        self.inner.into_file()
898    }
899
900    /// Closes the file, leaving only the temporary file path.
901    ///
902    /// This is useful when another process must be able to open the temporary
903    /// file.
904    pub fn into_temp_path(self) -> Utf8TempPath {
905        Utf8TempPath::from_temp_path(self.inner.into_temp_path())
906            .expect("invariant: inner path is UTF-8")
907    }
908
909    /// Converts the named temporary file into its constituent parts.
910    ///
911    /// Note: When the path is dropped, the file is deleted but the file handle
912    /// is still usable.
913    pub fn into_parts(self) -> (F, Utf8TempPath) {
914        let (file, path) = self.inner.into_parts();
915        let path = Utf8TempPath::from_temp_path(path).expect("invariant: inner path is UTF-8");
916        (file, path)
917    }
918
919    /// Creates a `NamedUtf8TempFile` from its constituent parts.
920    ///
921    /// This can be used with [`NamedUtf8TempFile::into_parts`] to reconstruct the
922    /// `NamedUtf8TempFile`.
923    pub fn from_parts(file: F, path: Utf8TempPath) -> Self {
924        let inner = NamedTempFile::from_parts(file, path.inner);
925        // This is valid because it was constructed from a Utf8TempPath
926        Self { inner }
927    }
928}
929
930impl NamedUtf8TempFile<File> {
931    /// Securely reopen the temporary file.
932    ///
933    /// This function is useful when you need multiple independent handles to the same file. It's
934    /// perfectly fine to drop the original `NamedUtf8TempFile` while holding on to `File`s returned
935    /// by this function; the `File`s will remain usable. However, they may not be nameable.
936    ///
937    /// # Errors
938    ///
939    /// If the file cannot be reopened, `Err` is returned.
940    ///
941    /// # Security
942    ///
943    /// Unlike `File::open(my_temp_file.path())`, `NamedUtf8TempFile::reopen()` guarantees that the
944    /// re-opened file is the _same_ file, even in the presence of pathological temporary file
945    /// cleaners.
946    ///
947    /// # Examples
948    ///
949    /// ```no_run
950    /// # use std::io;
951    /// use camino_tempfile::NamedUtf8TempFile;
952    ///
953    /// # fn main() {
954    /// #     if let Err(_) = run() {
955    /// #         ::std::process::exit(1);
956    /// #     }
957    /// # }
958    /// # fn run() -> Result<(), io::Error> {
959    /// let file = NamedUtf8TempFile::new()?;
960    ///
961    /// let another_handle = file.reopen()?;
962    /// # Ok(())
963    /// # }
964    /// ```
965    pub fn reopen(&self) -> io::Result<File> {
966        self.inner.reopen()
967    }
968}
969
970impl<F: Read> Read for NamedUtf8TempFile<F> {
971    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
972        self.as_file_mut().read(buf).with_err_path(|| self.path())
973    }
974}
975
976impl Read for &NamedUtf8TempFile<File> {
977    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
978        self.as_file().read(buf).with_err_path(|| self.path())
979    }
980}
981
982impl<F: Write> Write for NamedUtf8TempFile<F> {
983    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
984        self.as_file_mut().write(buf).with_err_path(|| self.path())
985    }
986    #[inline]
987    fn flush(&mut self) -> io::Result<()> {
988        self.as_file_mut().flush().with_err_path(|| self.path())
989    }
990}
991
992impl Write for &NamedUtf8TempFile<File> {
993    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
994        self.as_file().write(buf).with_err_path(|| self.path())
995    }
996    #[inline]
997    fn flush(&mut self) -> io::Result<()> {
998        self.as_file().flush().with_err_path(|| self.path())
999    }
1000}
1001
1002impl<F: Seek> Seek for NamedUtf8TempFile<F> {
1003    fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
1004        self.as_file_mut().seek(pos).with_err_path(|| self.path())
1005    }
1006}
1007
1008impl Seek for &NamedUtf8TempFile<File> {
1009    fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
1010        self.as_file().seek(pos).with_err_path(|| self.path())
1011    }
1012}
1013
1014#[cfg(unix)]
1015impl<F> std::os::unix::io::AsRawFd for NamedUtf8TempFile<F>
1016where
1017    F: std::os::unix::io::AsRawFd,
1018{
1019    #[inline]
1020    fn as_raw_fd(&self) -> std::os::unix::io::RawFd {
1021        self.as_file().as_raw_fd()
1022    }
1023}
1024
1025#[cfg(windows)]
1026impl<F> std::os::windows::io::AsRawHandle for NamedUtf8TempFile<F>
1027where
1028    F: std::os::windows::io::AsRawHandle,
1029{
1030    #[inline]
1031    fn as_raw_handle(&self) -> std::os::windows::io::RawHandle {
1032        self.as_file().as_raw_handle()
1033    }
1034}
1035
1036/// Error returned when persisting a temporary file fails.
1037pub struct Utf8PersistError<F = File> {
1038    /// The underlying IO error.
1039    pub error: io::Error,
1040    /// The temporary file that couldn't be persisted.
1041    pub file: NamedUtf8TempFile<F>,
1042}
1043
1044impl<F> fmt::Debug for Utf8PersistError<F> {
1045    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1046        write!(f, "Utf8PersistError({:?})", self.error)
1047    }
1048}
1049
1050impl<F> From<Utf8PersistError<F>> for io::Error {
1051    #[inline]
1052    fn from(error: Utf8PersistError<F>) -> io::Error {
1053        error.error
1054    }
1055}
1056
1057impl<F> From<Utf8PersistError<F>> for NamedUtf8TempFile<F> {
1058    #[inline]
1059    fn from(error: Utf8PersistError<F>) -> NamedUtf8TempFile<F> {
1060        error.file
1061    }
1062}
1063
1064impl<F> fmt::Display for Utf8PersistError<F> {
1065    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1066        write!(f, "failed to persist temporary file: {}", self.error)
1067    }
1068}
1069
1070impl<F> error::Error for Utf8PersistError<F> {
1071    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
1072        Some(&self.error)
1073    }
1074}