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.4.1")]
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")
77//! .arg("tmp")
78//! .current_dir(&temp_dir)
79//! .status()?;
80//! assert!(exit_status.success());
81//!
82//! # Ok(())
83//! # }
84//! ```
85//!
86//! This works because a reference to `temp_dir` is passed to `current_dir`, resulting in the
87//! destructor of `temp_dir` being run after the `Command` has finished execution. Moving the
88//! [`Utf8TempDir`] into the `current_dir` call would result in the [`Utf8TempDir`] being converted
89//! into an internal representation, with the original value being dropped and the directory thus
90//! being deleted, before the command can be executed.
91//!
92//! The `touch` command would fail with an `No such file or directory` error.
93//!
94//! ## Examples
95//!
96//! Create a temporary file and write some data into it:
97//!
98//! ```
99//! use camino_tempfile::tempfile;
100//! use std::io::{self, Write};
101//!
102//! # fn main() {
103//! # if let Err(_) = run() {
104//! # ::std::process::exit(1);
105//! # }
106//! # }
107//! # fn run() -> Result<(), io::Error> {
108//! // Create a file inside of `std::env::temp_dir()`.
109//! let mut file = tempfile()?;
110//!
111//! writeln!(file, "Brian was here. Briefly.")?;
112//! # Ok(())
113//! # }
114//! ```
115//!
116//! Create a named temporary file and open an independent file handle:
117//!
118//! ```
119//! use camino_tempfile::NamedUtf8TempFile;
120//! use std::io::{self, Read, Write};
121//!
122//! # fn main() {
123//! # if let Err(_) = run() {
124//! # ::std::process::exit(1);
125//! # }
126//! # }
127//! # fn run() -> Result<(), io::Error> {
128//! let text = "Brian was here. Briefly.";
129//!
130//! // Create a file inside of `std::env::temp_dir()`.
131//! let mut file1 = NamedUtf8TempFile::new()?;
132//!
133//! // Re-open it.
134//! let mut file2 = file1.reopen()?;
135//!
136//! // Write some test data to the first handle.
137//! file1.write_all(text.as_bytes())?;
138//!
139//! // Read the test data using the second handle.
140//! let mut buf = String::new();
141//! file2.read_to_string(&mut buf)?;
142//! assert_eq!(buf, text);
143//! # Ok(())
144//! # }
145//! ```
146//!
147//! Create a temporary directory and add a file to it:
148//!
149//! ```
150//! use camino_tempfile::tempdir;
151//! use std::{
152//! fs::File,
153//! io::{self, Write},
154//! };
155//!
156//! # fn main() {
157//! # if let Err(_) = run() {
158//! # ::std::process::exit(1);
159//! # }
160//! # }
161//! # fn run() -> Result<(), io::Error> {
162//! // Create a directory inside of `std::env::temp_dir()`.
163//! let dir = tempdir()?;
164//!
165//! let file_path = dir.path().join("my-temporary-note.txt");
166//! let mut file = File::create(file_path)?;
167//! writeln!(file, "Brian was here. Briefly.")?;
168//!
169//! // By closing the `Utf8TempDir` explicitly, we can check that it has
170//! // been deleted successfully. If we don't close it explicitly,
171//! // the directory will still be deleted when `dir` goes out
172//! // of scope, but we won't know whether deleting the directory
173//! // succeeded.
174//! drop(file);
175//! dir.close()?;
176//! # Ok(())
177//! # }
178//! ```
179
180#![deny(rust_2018_idioms)]
181
182mod builder;
183mod dir;
184mod errors;
185mod file;
186mod helpers;
187
188pub use builder::*;
189pub use dir::*;
190pub use file::*;