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}