camino_tempfile/lib.rs
1// Copyright (c) The camino-tempfile Contributors
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4// Setting html_root_url allows links from camino-tempfile-ext to work. This
5// line is updated by cargo-release.
6#![doc(html_root_url = "https://docs.rs/camino-tempfile/1.3.0")]
7
8//! Temporary files and directories with UTF-8 paths.
9//!
10//! `camino-tempfile` is a wrapper around the [`tempfile`](mod@::tempfile) crate that returns
11//! [`Utf8Path`](camino::Utf8Path) rather than [`Path`](std::path::Path).
12//!
13//! If your code mostly uses [`camino`], it can be annoying to have to convert temporary paths to
14//! `Utf8Path` over and over again. This crate takes care of that for you.
15//!
16//! This library and its documentation are adapted from the [`tempfile`](mod@::tempfile) crate, and
17//! are used under the MIT and Apache 2.0 licenses.
18//!
19//! This crate closely mirrors tempfile's interface. For extensions that provide
20//! quality-of-life improvements such as the ability to create files easily, see
21//! [`camino-tempfile-ext`](https://crates.io/crates/camino-tempfile-ext).
22//!
23//! # Synopsis
24//!
25//! - Use the [`tempfile()`] function for temporary files
26//! - Use the [`tempdir()`] function for temporary directories.
27//!
28//! # Design
29//!
30//! This crate provides several approaches to creating temporary files and directories.
31//! [`tempfile()`] relies on the OS to remove the temporary file once the last handle is closed.
32//! [`Utf8TempDir`] and [`NamedUtf8TempFile`] both rely on Rust destructors for cleanup.
33//!
34//! When choosing between the temporary file variants, prefer `tempfile` unless you either need to
35//! know the file's path or to be able to persist it.
36//!
37//! ## Resource Leaking
38//!
39//! [`tempfile()`] will (almost) never fail to cleanup temporary resources. However, [`Utf8TempDir`]
40//! and [`NamedUtf8TempFile`] will fail if their destructors don't run. This is because
41//! [`tempfile()`] relies on the OS to cleanup the underlying file, while [`Utf8TempDir`] and
42//! [`NamedUtf8TempFile`] rely on Rust destructors to do so. Destructors may fail to run if the
43//! process exits through an unhandled signal interrupt (like `SIGINT`), or if the instance is
44//! declared statically, among other possible reasons.
45//!
46//! ## Security
47//!
48//! In the presence of pathological temporary file cleaner, relying on file paths is unsafe because
49//! a temporary file cleaner could delete the temporary file which an attacker could then replace.
50//!
51//! [`tempfile()`] doesn't rely on file paths so this isn't an issue. However, [`NamedUtf8TempFile`]
52//! does rely on file paths for _some_ operations. See the security documentation on the
53//! [`NamedUtf8TempFile`] type for more information.
54//!
55//! ## Early drop pitfall
56//!
57//! Because [`Utf8TempDir`] and [`NamedUtf8TempFile`] rely on their destructors for cleanup, this
58//! can lead to an unexpected early removal of the directory/file, usually when working with APIs
59//! which are generic over `AsRef<Utf8Path>` or `AsRef<Path>`. Consider the following example:
60//!
61//! ```no_run
62//! # use camino_tempfile::tempdir;
63//! # use std::io;
64//! # use std::process::Command;
65//! # fn main() {
66//! # if let Err(_) = run() {
67//! # ::std::process::exit(1);
68//! # }
69//! # }
70//! # fn run() -> Result<(), io::Error> {
71//! // Create a directory inside of `std::env::temp_dir()`.
72//! let temp_dir = tempdir()?;
73//!
74//! // Spawn the `touch` command inside the temporary directory and collect the exit status
75//! // Note that `temp_dir` is **not** moved into `current_dir`, but passed as a reference
76//! let exit_status = Command::new("touch").arg("tmp").current_dir(&temp_dir).status()?;
77//! assert!(exit_status.success());
78//!
79//! # Ok(())
80//! # }
81//! ```
82//!
83//! This works because a reference to `temp_dir` is passed to `current_dir`, resulting in the
84//! destructor of `temp_dir` being run after the `Command` has finished execution. Moving the
85//! [`Utf8TempDir`] into the `current_dir` call would result in the [`Utf8TempDir`] being converted
86//! into an internal representation, with the original value being dropped and the directory thus
87//! being deleted, before the command can be executed.
88//!
89//! The `touch` command would fail with an `No such file or directory` error.
90//!
91//! ## Examples
92//!
93//! Create a temporary file and write some data into it:
94//!
95//! ```
96//! use camino_tempfile::tempfile;
97//! use std::io::{self, Write};
98//!
99//! # fn main() {
100//! # if let Err(_) = run() {
101//! # ::std::process::exit(1);
102//! # }
103//! # }
104//! # fn run() -> Result<(), io::Error> {
105//! // Create a file inside of `std::env::temp_dir()`.
106//! let mut file = tempfile()?;
107//!
108//! writeln!(file, "Brian was here. Briefly.")?;
109//! # Ok(())
110//! # }
111//! ```
112//!
113//! Create a named temporary file and open an independent file handle:
114//!
115//! ```
116//! use camino_tempfile::NamedUtf8TempFile;
117//! use std::io::{self, Write, Read};
118//!
119//! # fn main() {
120//! # if let Err(_) = run() {
121//! # ::std::process::exit(1);
122//! # }
123//! # }
124//! # fn run() -> Result<(), io::Error> {
125//! let text = "Brian was here. Briefly.";
126//!
127//! // Create a file inside of `std::env::temp_dir()`.
128//! let mut file1 = NamedUtf8TempFile::new()?;
129//!
130//! // Re-open it.
131//! let mut file2 = file1.reopen()?;
132//!
133//! // Write some test data to the first handle.
134//! file1.write_all(text.as_bytes())?;
135//!
136//! // Read the test data using the second handle.
137//! let mut buf = String::new();
138//! file2.read_to_string(&mut buf)?;
139//! assert_eq!(buf, text);
140//! # Ok(())
141//! # }
142//! ```
143//!
144//! Create a temporary directory and add a file to it:
145//!
146//! ```
147//! use camino_tempfile::tempdir;
148//! use std::fs::File;
149//! use std::io::{self, Write};
150//!
151//! # fn main() {
152//! # if let Err(_) = run() {
153//! # ::std::process::exit(1);
154//! # }
155//! # }
156//! # fn run() -> Result<(), io::Error> {
157//! // Create a directory inside of `std::env::temp_dir()`.
158//! let dir = tempdir()?;
159//!
160//! let file_path = dir.path().join("my-temporary-note.txt");
161//! let mut file = File::create(file_path)?;
162//! writeln!(file, "Brian was here. Briefly.")?;
163//!
164//! // By closing the `Utf8TempDir` explicitly, we can check that it has
165//! // been deleted successfully. If we don't close it explicitly,
166//! // the directory will still be deleted when `dir` goes out
167//! // of scope, but we won't know whether deleting the directory
168//! // succeeded.
169//! drop(file);
170//! dir.close()?;
171//! # Ok(())
172//! # }
173//! ```
174
175#![deny(rust_2018_idioms)]
176
177mod builder;
178mod dir;
179mod errors;
180mod file;
181mod helpers;
182
183pub use builder::*;
184pub use dir::*;
185pub use file::*;