atomic_write_file/
lib.rs

1//! This crate offers functionality to write and overwrite files *atomically*, that is: without
2//! leaving the file in an intermediate state. Either the new contents of the files are written to
3//! the filesystem, or the old contents (if any) are preserved.
4//!
5//! This crate implements two main structs: [`AtomicWriteFile`] and [`OpenOptions`], which mimic
6//! the standard [`std::fs::File`] and [`std::fs::OpenOptions`] as much as possible.
7//!
8//! This crate supports all major platforms, including: Unix systems, Windows, and WASI.
9//!
10//! # Motivation and Example
11//!
12//! Consider the following snippet of code to write a configuration file in JSON format:
13//!
14//! ```
15//! # fn main() -> std::io::Result<()> {
16//! # let test_dir = option_env!("TEST_DIR").unwrap_or("target/test-files");
17//! # std::fs::create_dir_all(&test_dir).expect("failed to create test dir");
18//! # std::env::set_current_dir(test_dir).expect("failed to move to test dir");
19//! use std::io::Write;
20//! use std::fs::File;
21//!
22//! let mut file = File::options()
23//!                     .write(true)
24//!                     .create(true)
25//!                     .open("config.json")?;
26//!
27//! writeln!(file, "{{")?;
28//! writeln!(file, "  \"key1\": \"value1\",")?;
29//! writeln!(file, "  \"key2\": \"value2\"")?;
30//! writeln!(file, "}}")?;
31//! # Ok(())
32//! # }
33//! ```
34//!
35//! This code opens a file named `config.json`, truncates its contents (if the file already
36//! existed), and writes the JSON content line-by-line.
37//!
38//! If the code is interrupted before all of the `writeln!` calls are completed (because of a
39//! panic, or a signal is received, or the process is killed, or a filesystem error occurs), then
40//! the file will be left in a broken state: it will not contain valid JSON data, and the original
41//! contents (if any) will be lost.
42//!
43//! [`AtomicWriteFile`] solves this problem by placing the new contents into the destination file
44//! only after it has been completely written to the filesystem. The snippet above can be rewritten
45//! using [`AtomicWriteFile`] instead of [`File`] as follows:
46//!
47//! ```
48//! # fn main() -> std::io::Result<()> {
49//! # let test_dir = option_env!("TEST_DIR").unwrap_or("target/test-files");
50//! # std::fs::create_dir_all(&test_dir).expect("failed to create test dir");
51//! # std::env::set_current_dir(test_dir).expect("failed to move to test dir");
52//! use std::io::Write;
53//! use atomic_write_file::AtomicWriteFile;
54//!
55//! let mut file = AtomicWriteFile::options()
56//!                                .open("config.json")?;
57//!
58//! writeln!(file, "{{")?;
59//! writeln!(file, "  \"key1\": \"value1\",")?;
60//! writeln!(file, "  \"key2\": \"value2\"")?;
61//! writeln!(file, "}}")?;
62//!
63//! file.commit()?;
64//! # Ok(())
65//! # }
66//! ```
67//!
68//! Note that this code is almost the same as the original, except that it now uses
69//! `AtomicWriteFile` instead of `File` and there's an additional call to `commit()`.
70//!
71//! If the code is interrupted early, before the call to `commit()`, the original file
72//! `config.json` will be left untouched. Only if the new contents are fully written to the
73//! filesystem, `config.json` will get them.
74//!
75//! # How it works
76//!
77//! This crate works by creating a temporary file in the same directory as the destination file,
78//! and then replacing the destination file with the temporary file once the new contents are fully
79//! written to the filesystem.
80//!
81//! On **Unix**, the implementation is roughly equivalent to this pseudocode:
82//!
83//! ```text
84//! fd = open("/path/to/directory/.filename.XXXXXX", O_WRONLY | O_CLOEXEC);
85//! /* ... write contents ... */
86//! fsync(fd);
87//! rename("/path/to/directory/.filename.XXXXXX", "/path/to/directory/filename");
88//! ```
89//!
90//! Where `XXXXXX` represents a random suffix. On **non-Unix** platforms, the implementation is
91//! similar and uses the equivalent platform-specific system calls.
92//!
93//! On **Unix**, the actual implementation is more robust and makes use of directory file
94//! descriptors (and the system calls `openat`, `linkat`, `renameat`) to make sure that, if the
95//! directory is renamed or remounted during the operations, the file still ends up in the original
96//! destination directory, and no cross-device writes happen.
97//!
98//! # Notes and Limitations
99//!
100//! * If the path of an [`AtomicWriteFile`] is a directory or a file that cannot be removed (due to
101//!   permissions or special attributes), an error will be produced when the [`AtomicWriteFile`] is
102//!   committed. This is in contrast with the standard `File`, which would instead produce an error
103//!   at `open()` time.
104//!
105//! * [`AtomicWriteFile`] is designed so that the temporary files it creates are automatically
106//!   removed if an error (such as a panic) occurs. However, if the process is interrupted abruptly
107//!   (without unwinding or running destructors), temporary files may be left on the filesystem.
108//!
109//! * If the path of an [`AtomicWriteFile`] is a symlink to another file, the symlink is replaced,
110//!   and the target of the original symlink is left untouched. If you intend to modify the file
111//!   pointed by a symlink at open time, call [`Path::canonicalize()`] prior to calling
112//!   [`AtomicWriteFile::open()`] or [`OpenOptions::open()`]. In the future, handling of symlinks
113//!   will be better customizable.
114//!
115//! * Because [`AtomicWriteFile`] works by creating a temporary file, and then replacing the
116//!   original file (see ["how it works"](#how-it-works) above), some metadata of the original file
117//!   may be lost:
118//!
119//!   * On Unix, it is possible to preserve permissions and ownership of the original file.
120//!     However, it is not generally possible to preserve the same owner user/group of the original
121//!     file unless the process runs as root (or with the `CAP_CHOWN` capability on Linux). See
122//!     [`OpenOptionsExt::try_preserve_owner()`](crate::unix::OpenOptionsExt::try_preserve_owner)
123//!     for more details on the behavior of [`open()`](OpenOptions::open) when ownership cannot be
124//!     preserved.
125//!
126//!   * On non-Unix platform, there is no support for preserving file permissions or ownership.
127//!     Support may be added in the future.
128//!
129//!   * On all platforms, there is no support for preserving timestamps, ACLs (POSIX Access Control
130//!     Lists), Linux extended attributes (xattrs), or SELinux contexts. Support may be added in
131//!     the future.
132//!
133//! # Cargo features
134//!
135//! ## `unnamed-tmpfile` (Linux only)
136//!
137//! As explained in [how it works](#how-it-works), this crate works by creating a temporary file,
138//! which is then renamed at commit time. By default, the temporary file has a path on the
139//! filesystem, and as such if a crash occurs, there is a chance that the temporary file may be
140//! left on the filesystem.
141//!
142//! On **Linux**, the implementation of this crate can make use of anonymous temporary files (files
143//! opened with [`O_TMPFILE`](https://www.man7.org/linux/man-pages/man2/open.2.html)) if supported,
144//! and the implementation is roughly equivalent to this pseudocode:
145//!
146//! ```text
147//! fd = open("/path/to/directory", O_TMPFILE | O_WRONLY | O_CLOEXEC);
148//! /* ... write contents ... */
149//! fsync(fd);
150//! link("/proc/self/fd/$fd", "/path/to/directory/.filename.XXXXXX");
151//! rename("/path/to/directory/.filename.XXXXXX", "/path/to/directory/filename");
152//! ```
153//!
154//! This feature has the following limitations:
155//!
156//! * The use of anonymous temporary files ensures that, if the process is interrupted abruptly
157//!   *before* a commit, the temporary file is automatically cleaned up by the operating system.
158//!   However, if the process is interrupted *during* a commit, it's still possible (although
159//!   unlikely) that a named temporary file will be left inside the destination directory.
160//!
161//! * This feature requires the `/proc` filesystem to be mounted. This makes [`AtomicWriteFile`]
162//!   with `unnamed-tmpfile` unsuitable for use in processes that run early at boot.
163//!
164//! This feature has no effect on platforms other than Linux.
165
166#![warn(clippy::dbg_macro)]
167#![warn(clippy::print_stderr)]
168#![warn(clippy::print_stdout)]
169#![warn(clippy::missing_safety_doc)]
170#![warn(clippy::unnecessary_safety_doc)]
171#![warn(clippy::unnecessary_safety_comment)]
172#![warn(clippy::undocumented_unsafe_blocks)]
173#![warn(missing_debug_implementations)]
174#![warn(unnameable_types)]
175#![warn(unused_macro_rules)]
176#![warn(missing_docs)]
177#![warn(unreachable_pub)]
178#![warn(unused_crate_dependencies)]
179#![warn(unused_qualifications)]
180#![doc(test(attr(deny(warnings))))]
181#![cfg_attr(feature = "unstable-can_vector", feature(can_vector))]
182#![cfg_attr(feature = "unstable-read_buf", feature(core_io_borrowed_buf))]
183#![cfg_attr(feature = "unstable-read_buf", feature(read_buf))]
184#![cfg_attr(feature = "unstable-seek_stream_len", feature(seek_stream_len))]
185#![cfg_attr(
186    feature = "unstable-unix_file_vectored_at",
187    feature(unix_file_vectored_at)
188)]
189#![cfg_attr(feature = "unstable-write_all_vectored", feature(write_all_vectored))]
190
191use std::fmt::Arguments;
192use std::fs::File;
193use std::io::IoSlice;
194use std::io::IoSliceMut;
195use std::io::Read;
196use std::io::Result;
197use std::io::Seek;
198use std::io::SeekFrom;
199use std::io::Write;
200use std::ops::Deref;
201use std::ops::DerefMut;
202use std::path::Path;
203
204#[cfg(feature = "unstable-read_buf")]
205use std::io::BorrowedCursor;
206
207mod imp;
208
209mod dir;
210pub use dir::Directory;
211
212#[cfg(any(unix, target_os = "wasi"))]
213mod fd;
214
215#[cfg(unix)]
216pub mod unix;
217
218#[cfg(test)]
219mod tests;
220
221/// Options to configure how an [`AtomicWriteFile`] is opened.
222///
223/// This struct mimics the standard struct [`std::fs::OpenOptions`], and offers a subset of its
224/// features that are applicable to [`AtomicWriteFile`].
225///
226/// Options can be set using methods like [`read()`](OpenOptions::read). Once the desired options
227/// are set, the file can be opened using [`open()`](OpenOptions::open).
228///
229/// This crate offers some platform-specific extensions for `OpenOptions` in the form of traits:
230///
231/// * [`unix::OpenOptionsExt`]
232///
233/// # Notable differences between `std::fs::OpenOptions` and `atomic_write_file::OpenOptions`
234///
235/// The `OpenOptions` provided in this crate opens all files for writing by default, and the opened
236/// file is always initially empty ("truncated"). As such, the following methods are not provided:
237/// `write()`, `truncate()`, `append()`.
238///
239/// `create()` is not provided because a new file is always created if an original file does not
240/// exist.
241///
242/// `create_new()` is also not provided because there is no way to ensure that a file never exists
243/// from the time an [`AtomicWriteFile`] is opened to the time it is committed.
244///
245/// # Behavior when opening a file that already exists
246///
247/// When passing a path to [`open()`](OpenOptions::open) that points to a file that already exists,
248/// [`AtomicWriteFile`] may preserve some of the metadata of the existing file (permissions,
249/// ownership, and more). This behavior is platform-specific and can be controlled using the
250/// platform-specific `OpenOptionsExt` traits. See also the ["notes and limitations" section on the
251/// module-level documentations](crate#notes-and-limitations) for more information about what
252/// metadata is preserved, what is not preserved, and in what circumstances.
253///
254/// # Examples
255///
256/// Opening a file for writing with default options (equivalent to a call to
257/// [`AtomicWriteFile::open()`]):
258///
259/// ```
260/// # let test_dir = option_env!("TEST_DIR").unwrap_or("target/test-files");
261/// # std::fs::create_dir_all(&test_dir).expect("failed to create test dir");
262/// # std::env::set_current_dir(test_dir).expect("failed to move to test dir");
263/// use atomic_write_file::OpenOptions;
264/// let file = OpenOptions::new().open("foo.txt");
265/// # std::mem::drop(file);
266/// ```
267///
268/// Opening a file for both reading and writing:
269///
270/// ```
271/// # let test_dir = option_env!("TEST_DIR").unwrap_or("target/test-files");
272/// # std::fs::create_dir_all(&test_dir).expect("failed to create test dir");
273/// # std::env::set_current_dir(test_dir).expect("failed to move to test dir");
274/// use atomic_write_file::OpenOptions;
275/// let file = OpenOptions::new().read(true).open("foo.txt");
276/// # std::mem::drop(file)
277/// ```
278#[derive(Clone, Debug)]
279pub struct OpenOptions {
280    inner: imp::OpenOptions,
281}
282
283impl OpenOptions {
284    /// Create a set of options set to their default values.
285    pub fn new() -> Self {
286        Self {
287            inner: imp::OpenOptions::new(),
288        }
289    }
290
291    /// Sets the option for read access.
292    ///
293    /// If `true`, the file will be readable (other than being writeable) once opened using, for
294    /// example, the [`Read`] trait. Note that if opening an already-existing file, the original
295    /// file contents will not be readable. Only the new contents of the file will be readable.
296    ///
297    /// If `false` (the default), the file is opened in write-only mode.
298    ///
299    /// # Examples
300    ///
301    /// ```
302    /// # fn main() -> std::io::Result<()> {
303    /// # let test_dir = option_env!("TEST_DIR").unwrap_or("target/test-files");
304    /// # std::fs::create_dir_all(&test_dir).expect("failed to create test dir");
305    /// # std::env::set_current_dir(test_dir).expect("failed to move to test dir");
306    /// use std::io::Seek;
307    /// use std::io::Write;
308    /// use std::io::read_to_string;
309    /// use atomic_write_file::OpenOptions;
310    ///
311    /// let mut file = OpenOptions::new().read(true).open("foo.txt")?;
312    /// writeln!(file, "hello")?;
313    ///
314    /// file.rewind()?;
315    /// assert_eq!(read_to_string(&file)?, "hello\n");
316    /// # Ok(())
317    /// # }
318    /// ```
319    pub fn read(&mut self, read: bool) -> &mut Self {
320        self.inner.read = read;
321        self
322    }
323
324    /// Opens the file at `path` with this set of options.
325    ///
326    /// This has the same semantics as [`std::fs::OpenOptions::open()`], except that it returns an
327    /// [`AtomicWriteFile`] instead of a [`File`].
328    ///
329    /// # Examples
330    ///
331    /// ```
332    /// # let test_dir = option_env!("TEST_DIR").unwrap_or("target/test-files");
333    /// # std::fs::create_dir_all(&test_dir).expect("failed to create test dir");
334    /// # std::env::set_current_dir(test_dir).expect("failed to move to test dir");
335    /// use atomic_write_file::OpenOptions;
336    /// let file = OpenOptions::new().read(true).open("foo.txt");
337    /// # std::mem::drop(file)
338    /// ```
339    pub fn open<P: AsRef<Path>>(&self, path: P) -> Result<AtomicWriteFile> {
340        let path = path.as_ref().to_path_buf();
341        let temporary_file = imp::TemporaryFile::open(&self.inner, &path)?;
342        Ok(AtomicWriteFile {
343            temporary_file,
344            finalized: false,
345        })
346    }
347}
348
349impl Default for OpenOptions {
350    fn default() -> Self {
351        Self::new()
352    }
353}
354
355/// A file whose contents become visible to users only after the file is committed.
356///
357/// An `AtomicWriteFile` is a file that is assigned to a path, but whose contents won't appear at
358/// that path until the file is [committed](AtomicWriteFile::commit). If `AtomicWriteFile` is used
359/// to open a file that already exists, the contents of the existing file will remain available
360/// until the `AtomicWriteFile` is committed. During that time, the `AtomicWriteFile` may be used
361/// to write new contents, but these new contents won't be visible until after the file is
362/// committed.
363///
364/// Internally, `AtomicWriteFile` is implemented by initally opening a temporary file, and then
365/// renaming the temporary file to its final path on commit. See the [module-level
366/// documentation](crate) for more details about the implementation.
367///
368/// An `AtomicWriteFile` is automatically discarded when it goes out of scope (when it gets
369/// dropped). Any error that occurs on drop is ignored. For this reason, if the file should not be
370/// committed, it is highly recommended that `AtomicWriteFile` is discarded explicitly using the
371/// [`discard()`](AtomicWriteFile::discard) method, which allows callers to detect errors on
372/// cleanup. See [committing or discarding changes](#committing-or-discarding-changes) below for
373/// more information.
374///
375/// # Opening an `AtomicWriteFile`
376///
377/// There are two ways to obtain an `AtomicWriteFile` struct:
378///
379/// * [`AtomicWriteFile::open()`]
380/// * [`OpenOptions::open()`]
381///
382/// The first method opens a file at the specified path with some default options. The second
383/// method using [`OpenOptions`] allows configuring how the file is opened.
384///
385/// # Compatibility with `std::fs::File`
386///
387/// `AtomicWriteFile` implements the same methods and traits of [`std::fs::File`], and aims to be
388/// as much compatible with `File` as possible. In fact, `AtomicWriteFile` can be
389/// [dereferenced](Deref) into a `File` struct: this means that you can use all methods provided by
390/// [`File`] directly on an `AtomicWriteFile` (just like you can use all of [`str`] methods on a
391/// [`String`]).
392///
393/// A reference to the wrapped `File` struct may also be explicitly obtained using
394/// [`as_file()`](AtomicWriteFile::as_file) and [`as_file_mut()`](AtomicWriteFile::as_file_mut).
395///
396/// # Committing or discarding changes
397///
398/// `AtomicWriteFile` provides two additional methods that are not provided by [`File`]:
399/// [`commit()`](AtomicWriteFile::commit) and [`discard()`](AtomicWriteFile::discard). These
400/// methods can be called to save the new contents to the file path, or to destroy the new contents
401/// and leave the original file (if any) unchaged, respectively.
402///
403/// Changes are automatically discarded also when `AtomicWriteFile` is dropped. Therefore calling
404/// [`discard()`](AtomicWriteFile::discard) is not mandatory, but it is highly recommended because
405/// the [`Drop`] implementation ignores all errors.
406///
407/// # Cloning
408///
409/// Cloning a `AtomicWriteFile` is not possible, because this would result in ambiguity and race
410/// conditions when committing the file and its clones. It is however possible to clone the
411/// underlaying [`File`] struct using [`try_clone()`](File::try_clone). Writes to this cloned
412/// [`File`] however won't be atomic after the `AtomicWriteFile` is committed.
413///
414/// # Examples
415///
416/// Opening a file, writing new contents, and committing the changes:
417///
418/// ```
419/// # fn main() -> std::io::Result<()> {
420/// # let test_dir = option_env!("TEST_DIR").unwrap_or("target/test-files");
421/// # std::fs::create_dir_all(&test_dir).expect("failed to create test dir");
422/// # std::env::set_current_dir(test_dir).expect("failed to move to test dir");
423/// use std::io::Write;
424/// use atomic_write_file::AtomicWriteFile;
425///
426/// let mut file = AtomicWriteFile::open("foo.txt")?; // if "foo.txt" already exists, it is not
427///                                                   // initially truncated or deleted
428/// writeln!(file, "hello")?; // "hello" is written to a temporary location; "foo.txt" (if it
429///                           // exists) keeps its old contents after this write
430///
431/// file.commit()?; // only now "foo.txt" gets swapped with the new contents ("hello")
432/// # Ok(())
433/// # }
434/// ```
435#[derive(Debug)]
436pub struct AtomicWriteFile {
437    temporary_file: imp::TemporaryFile,
438    finalized: bool,
439}
440
441impl AtomicWriteFile {
442    /// Opens an atomically-written file at `path`.
443    ///
444    /// See [`OpenOptions`] for more details.
445    ///
446    /// # Examples
447    ///
448    /// ```
449    /// # let test_dir = option_env!("TEST_DIR").unwrap_or("target/test-files");
450    /// # std::fs::create_dir_all(&test_dir).expect("failed to create test dir");
451    /// # std::env::set_current_dir(test_dir).expect("failed to move to test dir");
452    /// use atomic_write_file::AtomicWriteFile;
453    /// let file = AtomicWriteFile::open("foo.txt");
454    /// # std::mem::drop(file);
455    /// ```
456    #[inline]
457    pub fn open<P: AsRef<Path>>(path: P) -> Result<AtomicWriteFile> {
458        OpenOptions::new().open(path)
459    }
460
461    /// Creates a new [`OpenOptions`] with default options.
462    ///
463    /// This is equivalent to [`OpenOptions::new()`], but allows for more readable code.
464    ///
465    /// # Examples
466    ///
467    /// ```
468    /// # let test_dir = option_env!("TEST_DIR").unwrap_or("target/test-files");
469    /// # std::fs::create_dir_all(&test_dir).expect("failed to create test dir");
470    /// # std::env::set_current_dir(test_dir).expect("failed to move to test dir");
471    /// use atomic_write_file::AtomicWriteFile;
472    /// let file = AtomicWriteFile::options().read(true).open("foo.txt");
473    /// # std::mem::drop(file)
474    /// ```
475    #[inline]
476    pub fn options() -> OpenOptions {
477        OpenOptions::new()
478    }
479
480    /// Returns a reference to the underlaying [`File`] struct.
481    ///
482    /// The returned reference can be used to inspect or manipulate the contents and metadata of
483    /// this `AtomicWriteFile`.
484    ///
485    /// # Examples
486    ///
487    /// ```
488    /// # #[cfg(any(unix, target_os = "wasi"))]
489    /// # fn main() -> std::io::Result<()> {
490    /// # let test_dir = option_env!("TEST_DIR").unwrap_or("target/test-files");
491    /// # std::fs::create_dir_all(&test_dir).expect("failed to create test dir");
492    /// # std::env::set_current_dir(test_dir).expect("failed to move to test dir");
493    /// use std::os::fd::AsRawFd;
494    /// use atomic_write_file::AtomicWriteFile;
495    ///
496    /// let file = AtomicWriteFile::open("foo.txt")?;
497    /// assert_eq!(file.as_raw_fd(), file.as_file().as_raw_fd());
498    /// # Ok(())
499    /// # }
500    /// # #[cfg(not(any(unix, target_os = "wasi")))]
501    /// # fn main() -> std::io::Result<()> {
502    /// # Ok(())
503    /// # }
504    /// ```
505    #[inline]
506    pub fn as_file(&self) -> &File {
507        &self.temporary_file.file
508    }
509
510    /// Returns a mutable reference to the underlaying [`File`] struct.
511    ///
512    /// The returned reference can be used to inspect or manipulate the contents and metadata of
513    /// this `AtomicWriteFile`.
514    ///
515    /// # Examples
516    ///
517    /// ```
518    /// # fn main() -> std::io::Result<()> {
519    /// # let test_dir = option_env!("TEST_DIR").unwrap_or("target/test-files");
520    /// # std::fs::create_dir_all(&test_dir).expect("failed to create test dir");
521    /// # std::env::set_current_dir(test_dir).expect("failed to move to test dir");
522    /// use std::io::Write;
523    /// use atomic_write_file::AtomicWriteFile;
524    ///
525    /// let mut file = AtomicWriteFile::open("foo.txt")?;
526    /// writeln!(file.as_file_mut(), "hello")?;
527    /// # Ok(())
528    /// # }
529    /// ```
530    #[inline]
531    pub fn as_file_mut(&mut self) -> &mut File {
532        &mut self.temporary_file.file
533    }
534
535    /// Returns a borrowed reference to the directory containing the file.
536    ///
537    /// This method allows you to obtain the directory file descriptor, without having to open it
538    /// through a call to `open(2)`. This method is guaranteed to make no system calls.
539    ///
540    /// The returned struct supports only two operations:
541    /// - conversion to a borrowed directory file descriptor through
542    ///   [`AsFd::as_fd()`](std::os::fd::AsFd::as_fd)
543    /// - conversion to a raw directory file descriptor through
544    ///   [`AsRawFd::as_raw_fd()`](std::os::fd::AsRawFd::as_raw_fd)
545    ///
546    /// This method will return a result only if the platform supports directory file descriptors,
547    /// and if the `AtomicWriteFile` implementation makes use of them. In all other cases, this
548    /// method returns `None`.
549    ///
550    /// # Examples
551    ///
552    /// ```
553    /// # fn main() -> std::io::Result<()> {
554    /// # let test_dir = option_env!("TEST_DIR").unwrap_or("target/test-files");
555    /// # std::fs::create_dir_all(&test_dir).expect("failed to create test dir");
556    /// # std::env::set_current_dir(test_dir).expect("failed to move to test dir");
557    /// # #[cfg(any(unix, target_os = "wasi"))]
558    /// use std::os::fd::AsFd;
559    /// use atomic_write_file::AtomicWriteFile;
560    ///
561    /// let file = AtomicWriteFile::open("foo.txt")?;
562    /// if let Some(dir) = file.directory() {
563    /// #   #[cfg(any(unix, target_os = "wasi"))]
564    ///     let borrowed_fd = dir.as_fd();
565    /// #   #[cfg(any(unix, target_os = "wasi"))]
566    ///     println!("directory fd: {:?}", borrowed_fd);
567    /// #   #[cfg(not(any(unix, target_os = "wasi")))]
568    /// #   let _ = dir;
569    /// }
570    /// # Ok(())
571    /// # }
572    /// ```
573    #[inline]
574    pub fn directory(&self) -> Option<Directory<'_>> {
575        self.temporary_file.directory().map(Directory::new)
576    }
577
578    /// Saves the contents of this file to its path.
579    ///
580    /// After calling `commit()`, the `AtomicWriteFile` is consumed and can no longer be used.
581    /// Clones of the underlaying [`File`] may still be used after calling `commit()`, although any
582    /// write from that point onwards will no longer be atomic.
583    ///
584    /// See the documentation for [`AtomicWriteFile`] and the [module-level documentation](crate)
585    /// for details about the internal implementation of `commit()`, as well as platform-specific
586    /// details.
587    ///
588    /// See also [`AtomicWriteFile::discard()`].
589    ///
590    /// # Examples
591    ///
592    /// ```
593    /// # fn main() -> std::io::Result<()> {
594    /// # let test_dir = option_env!("TEST_DIR").unwrap_or("target/test-files");
595    /// # std::fs::create_dir_all(&test_dir).expect("failed to create test dir");
596    /// # std::env::set_current_dir(test_dir).expect("failed to move to test dir");
597    /// use std::io::Write;
598    /// use atomic_write_file::AtomicWriteFile;
599    ///
600    /// let file = AtomicWriteFile::open("foo.txt")?;
601    /// writeln!(&file, "hello")?;
602    /// file.commit()?;
603    /// # Ok(())
604    /// # }
605    /// ```
606    #[inline]
607    pub fn commit(mut self) -> Result<()> {
608        self._commit()
609    }
610
611    fn _commit(&mut self) -> Result<()> {
612        if self.finalized {
613            return Ok(());
614        }
615        self.finalized = true;
616        self.sync_all()?;
617        self.temporary_file.rename_file()
618    }
619
620    /// Discard the contents of this file, and leave its path unchanged.
621    ///
622    /// After calling `discard()`, the `AtomicWriteFile` is consumed and can no longer be used.
623    /// Clones of the underlaying [`File`] may still be used after calling `discard()`.
624    ///
625    /// This method is automatically called when `AtomicWriteFile` is dropped, although in that
626    /// case any error produced by `discard()` is ignored.
627    ///
628    /// See also [`AtomicWriteFile::commit()`].
629    ///
630    /// # Examples
631    ///
632    /// ```
633    /// # fn main() -> std::io::Result<()> {
634    /// # let test_dir = option_env!("TEST_DIR").unwrap_or("target/test-files");
635    /// # std::fs::create_dir_all(&test_dir).expect("failed to create test dir");
636    /// # std::env::set_current_dir(test_dir).expect("failed to move to test dir");
637    /// use std::io::Write;
638    /// use atomic_write_file::AtomicWriteFile;
639    ///
640    /// let file = AtomicWriteFile::open("foo.txt")?;
641    /// writeln!(&file, "hello")?;
642    /// file.discard()?;
643    /// # Ok(())
644    /// # }
645    /// ```
646    #[inline]
647    pub fn discard(mut self) -> Result<()> {
648        self._discard()
649    }
650
651    fn _discard(&mut self) -> Result<()> {
652        if self.finalized {
653            return Ok(());
654        }
655        self.finalized = true;
656        self.temporary_file.remove_file()
657    }
658}
659
660impl Drop for AtomicWriteFile {
661    #[inline]
662    fn drop(&mut self) {
663        // Ignore all errors
664        let _ = self._discard();
665    }
666}
667
668impl Deref for AtomicWriteFile {
669    type Target = File;
670
671    #[inline]
672    fn deref(&self) -> &Self::Target {
673        self.as_file()
674    }
675}
676
677impl DerefMut for AtomicWriteFile {
678    #[inline]
679    fn deref_mut(&mut self) -> &mut Self::Target {
680        self.as_file_mut()
681    }
682}
683
684impl Read for AtomicWriteFile {
685    #[inline]
686    fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
687        self.temporary_file.file.read(buf)
688    }
689
690    #[inline]
691    fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> Result<usize> {
692        self.temporary_file.file.read_vectored(bufs)
693    }
694
695    #[inline]
696    #[cfg(feature = "unstable-can_vector")]
697    fn is_read_vectored(&self) -> bool {
698        self.temporary_file.file.is_read_vectored()
699    }
700
701    #[inline]
702    fn read_to_end(&mut self, buf: &mut Vec<u8>) -> Result<usize> {
703        self.temporary_file.file.read_to_end(buf)
704    }
705
706    #[inline]
707    fn read_to_string(&mut self, buf: &mut String) -> Result<usize> {
708        self.temporary_file.file.read_to_string(buf)
709    }
710
711    #[inline]
712    fn read_exact(&mut self, buf: &mut [u8]) -> Result<()> {
713        self.temporary_file.file.read_exact(buf)
714    }
715
716    #[inline]
717    #[cfg(feature = "unstable-read_buf")]
718    fn read_buf(&mut self, buf: BorrowedCursor<'_>) -> Result<()> {
719        self.temporary_file.file.read_buf(buf)
720    }
721
722    #[inline]
723    #[cfg(feature = "unstable-read_buf")]
724    fn read_buf_exact(&mut self, cursor: BorrowedCursor<'_>) -> Result<()> {
725        self.temporary_file.file.read_buf_exact(cursor)
726    }
727}
728
729impl Read for &AtomicWriteFile {
730    #[inline]
731    fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
732        (&self.temporary_file.file).read(buf)
733    }
734
735    #[inline]
736    fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> Result<usize> {
737        (&self.temporary_file.file).read_vectored(bufs)
738    }
739
740    #[inline]
741    #[cfg(feature = "unstable-can_vector")]
742    fn is_read_vectored(&self) -> bool {
743        self.temporary_file.file.is_read_vectored()
744    }
745
746    #[inline]
747    fn read_to_end(&mut self, buf: &mut Vec<u8>) -> Result<usize> {
748        (&self.temporary_file.file).read_to_end(buf)
749    }
750
751    #[inline]
752    fn read_to_string(&mut self, buf: &mut String) -> Result<usize> {
753        (&self.temporary_file.file).read_to_string(buf)
754    }
755
756    #[inline]
757    fn read_exact(&mut self, buf: &mut [u8]) -> Result<()> {
758        (&self.temporary_file.file).read_exact(buf)
759    }
760
761    #[inline]
762    #[cfg(feature = "unstable-read_buf")]
763    fn read_buf(&mut self, buf: BorrowedCursor<'_>) -> Result<()> {
764        (&self.temporary_file.file).read_buf(buf)
765    }
766
767    #[inline]
768    #[cfg(feature = "unstable-read_buf")]
769    fn read_buf_exact(&mut self, cursor: BorrowedCursor<'_>) -> Result<()> {
770        (&self.temporary_file.file).read_buf_exact(cursor)
771    }
772}
773
774impl Write for AtomicWriteFile {
775    #[inline]
776    fn write(&mut self, buf: &[u8]) -> Result<usize> {
777        self.temporary_file.file.write(buf)
778    }
779
780    #[inline]
781    fn flush(&mut self) -> Result<()> {
782        self.temporary_file.file.flush()
783    }
784
785    #[inline]
786    fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> Result<usize> {
787        self.temporary_file.file.write_vectored(bufs)
788    }
789
790    #[inline]
791    #[cfg(feature = "unstable-can_vector")]
792    fn is_write_vectored(&self) -> bool {
793        self.temporary_file.file.is_write_vectored()
794    }
795
796    #[inline]
797    fn write_all(&mut self, buf: &[u8]) -> Result<()> {
798        self.temporary_file.file.write_all(buf)
799    }
800
801    #[inline]
802    #[cfg(feature = "unstable-write_all_vectored")]
803    fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> Result<()> {
804        self.temporary_file.file.write_all_vectored(bufs)
805    }
806
807    #[inline]
808    fn write_fmt(&mut self, fmt: Arguments<'_>) -> Result<()> {
809        self.temporary_file.file.write_fmt(fmt)
810    }
811}
812
813impl Write for &AtomicWriteFile {
814    #[inline]
815    fn write(&mut self, buf: &[u8]) -> Result<usize> {
816        (&self.temporary_file.file).write(buf)
817    }
818
819    #[inline]
820    fn flush(&mut self) -> Result<()> {
821        (&self.temporary_file.file).flush()
822    }
823
824    #[inline]
825    fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> Result<usize> {
826        (&self.temporary_file.file).write_vectored(bufs)
827    }
828
829    #[inline]
830    #[cfg(feature = "unstable-can_vector")]
831    fn is_write_vectored(&self) -> bool {
832        self.temporary_file.file.is_write_vectored()
833    }
834
835    #[inline]
836    fn write_all(&mut self, buf: &[u8]) -> Result<()> {
837        (&self.temporary_file.file).write_all(buf)
838    }
839
840    #[inline]
841    #[cfg(feature = "unstable-write_all_vectored")]
842    fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> Result<()> {
843        (&self.temporary_file.file).write_all_vectored(bufs)
844    }
845
846    #[inline]
847    fn write_fmt(&mut self, fmt: Arguments<'_>) -> Result<()> {
848        (&self.temporary_file.file).write_fmt(fmt)
849    }
850}
851
852impl Seek for AtomicWriteFile {
853    #[inline]
854    fn seek(&mut self, pos: SeekFrom) -> Result<u64> {
855        self.temporary_file.file.seek(pos)
856    }
857
858    #[inline]
859    fn rewind(&mut self) -> Result<()> {
860        self.temporary_file.file.rewind()
861    }
862
863    #[inline]
864    #[cfg(feature = "unstable-seek_stream_len")]
865    fn stream_len(&mut self) -> Result<u64> {
866        self.temporary_file.file.stream_len()
867    }
868
869    #[inline]
870    fn stream_position(&mut self) -> Result<u64> {
871        self.temporary_file.file.stream_position()
872    }
873}
874
875impl Seek for &AtomicWriteFile {
876    #[inline]
877    fn seek(&mut self, pos: SeekFrom) -> Result<u64> {
878        (&self.temporary_file.file).seek(pos)
879    }
880
881    #[inline]
882    fn rewind(&mut self) -> Result<()> {
883        (&self.temporary_file.file).rewind()
884    }
885
886    #[inline]
887    #[cfg(feature = "unstable-seek_stream_len")]
888    fn stream_len(&mut self) -> Result<u64> {
889        (&self.temporary_file.file).stream_len()
890    }
891
892    #[inline]
893    fn stream_position(&mut self) -> Result<u64> {
894        (&self.temporary_file.file).stream_position()
895    }
896}