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(missing_debug_implementations)]
170#![warn(unnameable_types)]
171#![warn(unused_macro_rules)]
172#![warn(missing_docs)]
173#![warn(unreachable_pub)]
174#![warn(unused_crate_dependencies)]
175#![warn(unused_qualifications)]
176#![doc(test(attr(deny(warnings))))]
177#![cfg_attr(feature = "unstable-can_vector", feature(can_vector))]
178#![cfg_attr(feature = "unstable-read_buf", feature(core_io_borrowed_buf))]
179#![cfg_attr(feature = "unstable-read_buf", feature(read_buf))]
180#![cfg_attr(feature = "unstable-seek_stream_len", feature(seek_stream_len))]
181#![cfg_attr(
182    feature = "unstable-unix_file_vectored_at",
183    feature(unix_file_vectored_at)
184)]
185#![cfg_attr(feature = "unstable-write_all_vectored", feature(write_all_vectored))]
186
187use std::fmt::Arguments;
188use std::fs::File;
189use std::io::IoSlice;
190use std::io::IoSliceMut;
191use std::io::Read;
192use std::io::Result;
193use std::io::Seek;
194use std::io::SeekFrom;
195use std::io::Write;
196use std::ops::Deref;
197use std::ops::DerefMut;
198use std::path::Path;
199
200#[cfg(feature = "unstable-read_buf")]
201use std::io::BorrowedCursor;
202
203mod imp;
204
205mod dir;
206pub use dir::Directory;
207
208#[cfg(any(unix, target_os = "wasi"))]
209mod fd;
210
211#[cfg(unix)]
212pub mod unix;
213
214#[cfg(test)]
215mod tests;
216
217/// Options to configure how an [`AtomicWriteFile`] is opened.
218///
219/// This struct mimics the standard struct [`std::fs::OpenOptions`], and offers a subset of its
220/// features that are applicable to [`AtomicWriteFile`].
221///
222/// Options can be set using methods like [`read()`](OpenOptions::read). Once the desired options
223/// are set, the file can be opened using [`open()`](OpenOptions::open).
224///
225/// This crate offers some platform-specific extensions for `OpenOptions` in the form of traits:
226///
227/// * [`unix::OpenOptionsExt`]
228///
229/// # Notable differences between `std::fs::OpenOptions` and `atomic_write_file::OpenOptions`
230///
231/// The `OpenOptions` provided in this crate opens all files for writing by default, and the opened
232/// file is always initially empty ("truncated"). As such, the following methods are not provided:
233/// `write()`, `truncate()`, `append()`.
234///
235/// `create()` is not provided because a new file is always created if an original file does not
236/// exist.
237///
238/// `create_new()` is also not provided because there is no way to ensure that a file never exists
239/// from the time an [`AtomicWriteFile`] is opened to the time it is committed.
240///
241/// # Behavior when opening a file that already exists
242///
243/// When passing a path to [`open()`](OpenOptions::open) that points to a file that already exists,
244/// [`AtomicWriteFile`] may preserve some of the metadata of the existing file (permissions,
245/// ownership, and more). This behavior is platform-specific and can be controlled using the
246/// platform-specific `OpenOptionsExt` traits. See also the ["notes and limitations" section on the
247/// module-level documentations](crate#notes-and-limitations) for more information about what
248/// metadata is preserved, what is not preserved, and in what circumstances.
249///
250/// # Examples
251///
252/// Opening a file for writing with default options (equivalent to a call to
253/// [`AtomicWriteFile::open()`]):
254///
255/// ```
256/// # let test_dir = option_env!("TEST_DIR").unwrap_or("target/test-files");
257/// # std::fs::create_dir_all(&test_dir).expect("failed to create test dir");
258/// # std::env::set_current_dir(test_dir).expect("failed to move to test dir");
259/// use atomic_write_file::OpenOptions;
260/// let file = OpenOptions::new().open("foo.txt");
261/// # std::mem::drop(file);
262/// ```
263///
264/// Opening a file for both reading and writing:
265///
266/// ```
267/// # let test_dir = option_env!("TEST_DIR").unwrap_or("target/test-files");
268/// # std::fs::create_dir_all(&test_dir).expect("failed to create test dir");
269/// # std::env::set_current_dir(test_dir).expect("failed to move to test dir");
270/// use atomic_write_file::OpenOptions;
271/// let file = OpenOptions::new().read(true).open("foo.txt");
272/// # std::mem::drop(file)
273/// ```
274#[derive(Clone, Debug)]
275pub struct OpenOptions {
276    inner: imp::OpenOptions,
277}
278
279impl OpenOptions {
280    /// Create a set of options set to their default values.
281    pub fn new() -> Self {
282        Self {
283            inner: imp::OpenOptions::new(),
284        }
285    }
286
287    /// Sets the option for read access.
288    ///
289    /// If `true`, the file will be readable (other than being writeable) once opened using, for
290    /// example, the [`Read`] trait. Note that if opening an already-existing file, the original
291    /// file contents will not be readable. Only the new contents of the file will be readable.
292    ///
293    /// If `false` (the default), the file is opened in write-only mode.
294    ///
295    /// # Examples
296    ///
297    /// ```
298    /// # fn main() -> std::io::Result<()> {
299    /// # let test_dir = option_env!("TEST_DIR").unwrap_or("target/test-files");
300    /// # std::fs::create_dir_all(&test_dir).expect("failed to create test dir");
301    /// # std::env::set_current_dir(test_dir).expect("failed to move to test dir");
302    /// use std::io::Seek;
303    /// use std::io::Write;
304    /// use std::io::read_to_string;
305    /// use atomic_write_file::OpenOptions;
306    ///
307    /// let mut file = OpenOptions::new().read(true).open("foo.txt")?;
308    /// writeln!(file, "hello")?;
309    ///
310    /// file.rewind()?;
311    /// assert_eq!(read_to_string(&file)?, "hello\n");
312    /// # Ok(())
313    /// # }
314    /// ```
315    pub fn read(&mut self, read: bool) -> &mut Self {
316        self.inner.read = read;
317        self
318    }
319
320    /// Opens the file at `path` with this set of options.
321    ///
322    /// This has the same semantics as [`std::fs::OpenOptions::open()`], except that it returns an
323    /// [`AtomicWriteFile`] instead of a [`File`].
324    ///
325    /// # Examples
326    ///
327    /// ```
328    /// # let test_dir = option_env!("TEST_DIR").unwrap_or("target/test-files");
329    /// # std::fs::create_dir_all(&test_dir).expect("failed to create test dir");
330    /// # std::env::set_current_dir(test_dir).expect("failed to move to test dir");
331    /// use atomic_write_file::OpenOptions;
332    /// let file = OpenOptions::new().read(true).open("foo.txt");
333    /// # std::mem::drop(file)
334    /// ```
335    pub fn open<P: AsRef<Path>>(&self, path: P) -> Result<AtomicWriteFile> {
336        let path = path.as_ref().to_path_buf();
337        let temporary_file = imp::TemporaryFile::open(&self.inner, &path)?;
338        Ok(AtomicWriteFile {
339            temporary_file,
340            finalized: false,
341        })
342    }
343}
344
345impl Default for OpenOptions {
346    fn default() -> Self {
347        Self::new()
348    }
349}
350
351/// A file whose contents become visible to users only after the file is committed.
352///
353/// An `AtomicWriteFile` is a file that is assigned to a path, but whose contents won't appear at
354/// that path until the file is [committed](AtomicWriteFile::commit). If `AtomicWriteFile` is used
355/// to open a file that already exists, the contents of the existing file will remain available
356/// until the `AtomicWriteFile` is committed. During that time, the `AtomicWriteFile` may be used
357/// to write new contents, but these new contents won't be visible until after the file is
358/// committed.
359///
360/// Internally, `AtomicWriteFile` is implemented by initally opening a temporary file, and then
361/// renaming the temporary file to its final path on commit. See the [module-level
362/// documentation](crate) for more details about the implementation.
363///
364/// An `AtomicWriteFile` is automatically discarded when it goes out of scope (when it gets
365/// dropped). Any error that occurs on drop is ignored. For this reason, if the file should not be
366/// committed, it is highly recommended that `AtomicWriteFile` is discarded explicitly using the
367/// [`discard()`](AtomicWriteFile::discard) method, which allows callers to detect errors on
368/// cleanup. See [committing or discarding changes](#committing-or-discarding-changes) below for
369/// more information.
370///
371/// # Opening an `AtomicWriteFile`
372///
373/// There are two ways to obtain an `AtomicWriteFile` struct:
374///
375/// * [`AtomicWriteFile::open()`]
376/// * [`OpenOptions::open()`]
377///
378/// The first method opens a file at the specified path with some default options. The second
379/// method using [`OpenOptions`] allows configuring how the file is opened.
380///
381/// # Compatibility with `std::fs::File`
382///
383/// `AtomicWriteFile` implements the same methods and traits of [`std::fs::File`], and aims to be
384/// as much compatible with `File` as possible. In fact, `AtomicWriteFile` can be
385/// [dereferenced](Deref) into a `File` struct: this means that you can use all methods provided by
386/// [`File`] directly on an `AtomicWriteFile` (just like you can use all of [`str`] methods on a
387/// [`String`]).
388///
389/// A reference to the wrapped `File` struct may also be explicitly obtained using
390/// [`as_file()`](AtomicWriteFile::as_file) and [`as_file_mut()`](AtomicWriteFile::as_file_mut).
391///
392/// # Committing or discarding changes
393///
394/// `AtomicWriteFile` provides two additional methods that are not provided by [`File`]:
395/// [`commit()`](AtomicWriteFile::commit) and [`discard()`](AtomicWriteFile::discard). These
396/// methods can be called to save the new contents to the file path, or to destroy the new contents
397/// and leave the original file (if any) unchaged, respectively.
398///
399/// Changes are automatically discarded also when `AtomicWriteFile` is dropped. Therefore calling
400/// [`discard()`](AtomicWriteFile::discard) is not mandatory, but it is highly recommended because
401/// the [`Drop`] implementation ignores all errors.
402///
403/// # Cloning
404///
405/// Cloning a `AtomicWriteFile` is not possible, because this would result in ambiguity and race
406/// conditions when committing the file and its clones. It is however possible to clone the
407/// underlaying [`File`] struct using [`try_clone()`](File::try_clone). Writes to this cloned
408/// [`File`] however won't be atomic after the `AtomicWriteFile` is committed.
409///
410/// # Examples
411///
412/// Opening a file, writing new contents, and committing the changes:
413///
414/// ```
415/// # fn main() -> std::io::Result<()> {
416/// # let test_dir = option_env!("TEST_DIR").unwrap_or("target/test-files");
417/// # std::fs::create_dir_all(&test_dir).expect("failed to create test dir");
418/// # std::env::set_current_dir(test_dir).expect("failed to move to test dir");
419/// use std::io::Write;
420/// use atomic_write_file::AtomicWriteFile;
421///
422/// let mut file = AtomicWriteFile::open("foo.txt")?; // if "foo.txt" already exists, it is not
423///                                                   // initially truncated or deleted
424/// writeln!(file, "hello")?; // "hello" is written to a temporary location; "foo.txt" (if it
425///                           // exists) keeps its old contents after this write
426///
427/// file.commit()?; // only now "foo.txt" gets swapped with the new contents ("hello")
428/// # Ok(())
429/// # }
430/// ```
431#[derive(Debug)]
432pub struct AtomicWriteFile {
433    temporary_file: imp::TemporaryFile,
434    finalized: bool,
435}
436
437impl AtomicWriteFile {
438    /// Opens an atomically-written file at `path`.
439    ///
440    /// See [`OpenOptions`] for more details.
441    ///
442    /// # Examples
443    ///
444    /// ```
445    /// # let test_dir = option_env!("TEST_DIR").unwrap_or("target/test-files");
446    /// # std::fs::create_dir_all(&test_dir).expect("failed to create test dir");
447    /// # std::env::set_current_dir(test_dir).expect("failed to move to test dir");
448    /// use atomic_write_file::AtomicWriteFile;
449    /// let file = AtomicWriteFile::open("foo.txt");
450    /// # std::mem::drop(file);
451    /// ```
452    #[inline]
453    pub fn open<P: AsRef<Path>>(path: P) -> Result<AtomicWriteFile> {
454        OpenOptions::new().open(path)
455    }
456
457    /// Creates a new [`OpenOptions`] with default options.
458    ///
459    /// This is equivalent to [`OpenOptions::new()`], but allows for more readable code.
460    ///
461    /// # Examples
462    ///
463    /// ```
464    /// # let test_dir = option_env!("TEST_DIR").unwrap_or("target/test-files");
465    /// # std::fs::create_dir_all(&test_dir).expect("failed to create test dir");
466    /// # std::env::set_current_dir(test_dir).expect("failed to move to test dir");
467    /// use atomic_write_file::AtomicWriteFile;
468    /// let file = AtomicWriteFile::options().read(true).open("foo.txt");
469    /// # std::mem::drop(file)
470    /// ```
471    #[inline]
472    pub fn options() -> OpenOptions {
473        OpenOptions::new()
474    }
475
476    /// Returns a reference to the underlaying [`File`] struct.
477    ///
478    /// The returned reference can be used to inspect or manipulate the contents and metadata of
479    /// this `AtomicWriteFile`.
480    ///
481    /// # Examples
482    ///
483    /// ```
484    /// # #[cfg(any(unix, target_os = "wasi"))]
485    /// # fn main() -> std::io::Result<()> {
486    /// # let test_dir = option_env!("TEST_DIR").unwrap_or("target/test-files");
487    /// # std::fs::create_dir_all(&test_dir).expect("failed to create test dir");
488    /// # std::env::set_current_dir(test_dir).expect("failed to move to test dir");
489    /// use std::os::fd::AsRawFd;
490    /// use atomic_write_file::AtomicWriteFile;
491    ///
492    /// let file = AtomicWriteFile::open("foo.txt")?;
493    /// assert_eq!(file.as_raw_fd(), file.as_file().as_raw_fd());
494    /// # Ok(())
495    /// # }
496    /// # #[cfg(not(any(unix, target_os = "wasi")))]
497    /// # fn main() -> std::io::Result<()> {
498    /// # Ok(())
499    /// # }
500    /// ```
501    #[inline]
502    pub fn as_file(&self) -> &File {
503        &self.temporary_file.file
504    }
505
506    /// Returns a mutable reference to the underlaying [`File`] struct.
507    ///
508    /// The returned reference can be used to inspect or manipulate the contents and metadata of
509    /// this `AtomicWriteFile`.
510    ///
511    /// # Examples
512    ///
513    /// ```
514    /// # fn main() -> std::io::Result<()> {
515    /// # let test_dir = option_env!("TEST_DIR").unwrap_or("target/test-files");
516    /// # std::fs::create_dir_all(&test_dir).expect("failed to create test dir");
517    /// # std::env::set_current_dir(test_dir).expect("failed to move to test dir");
518    /// use std::io::Write;
519    /// use atomic_write_file::AtomicWriteFile;
520    ///
521    /// let mut file = AtomicWriteFile::open("foo.txt")?;
522    /// writeln!(file.as_file_mut(), "hello")?;
523    /// # Ok(())
524    /// # }
525    /// ```
526    #[inline]
527    pub fn as_file_mut(&mut self) -> &mut File {
528        &mut self.temporary_file.file
529    }
530
531    /// Returns a borrowed reference to the directory containing the file.
532    ///
533    /// This method allows you to obtain the directory file descriptor, without having to open it
534    /// through a call to `open(2)`. This method is guaranteed to make no system calls.
535    ///
536    /// The returned struct supports only two operations:
537    /// - conversion to a borrowed directory file descriptor through
538    ///   [`AsFd::as_fd()`](std::os::fd::AsFd::as_fd)
539    /// - conversion to a raw directory file descriptor through
540    ///   [`AsRawFd::as_raw_fd()`](std::os::fd::AsRawFd::as_raw_fd)
541    ///
542    /// This method will return a result only if the platform supports directory file descriptors,
543    /// and if the `AtomicWriteFile` implementation makes use of them. In all other cases, this
544    /// method returns `None`.
545    ///
546    /// # Examples
547    ///
548    /// ```
549    /// # fn main() -> std::io::Result<()> {
550    /// # let test_dir = option_env!("TEST_DIR").unwrap_or("target/test-files");
551    /// # std::fs::create_dir_all(&test_dir).expect("failed to create test dir");
552    /// # std::env::set_current_dir(test_dir).expect("failed to move to test dir");
553    /// # #[cfg(any(unix, target_os = "wasi"))]
554    /// use std::os::fd::AsFd;
555    /// use atomic_write_file::AtomicWriteFile;
556    ///
557    /// let file = AtomicWriteFile::open("foo.txt")?;
558    /// if let Some(dir) = file.directory() {
559    /// #   #[cfg(any(unix, target_os = "wasi"))]
560    ///     let borrowed_fd = dir.as_fd();
561    /// #   #[cfg(any(unix, target_os = "wasi"))]
562    ///     println!("directory fd: {:?}", borrowed_fd);
563    /// #   #[cfg(not(any(unix, target_os = "wasi")))]
564    /// #   let _ = dir;
565    /// }
566    /// # Ok(())
567    /// # }
568    /// ```
569    #[inline]
570    pub fn directory(&self) -> Option<Directory<'_>> {
571        self.temporary_file.directory().map(Directory::new)
572    }
573
574    /// Saves the contents of this file to its path.
575    ///
576    /// After calling `commit()`, the `AtomicWriteFile` is consumed and can no longer be used.
577    /// Clones of the underlaying [`File`] may still be used after calling `commit()`, although any
578    /// write from that point onwards will no longer be atomic.
579    ///
580    /// See the documentation for [`AtomicWriteFile`] and the [module-level documentation](crate)
581    /// for details about the internal implementation of `commit()`, as well as platform-specific
582    /// details.
583    ///
584    /// See also [`AtomicWriteFile::discard()`].
585    ///
586    /// # Examples
587    ///
588    /// ```
589    /// # fn main() -> std::io::Result<()> {
590    /// # let test_dir = option_env!("TEST_DIR").unwrap_or("target/test-files");
591    /// # std::fs::create_dir_all(&test_dir).expect("failed to create test dir");
592    /// # std::env::set_current_dir(test_dir).expect("failed to move to test dir");
593    /// use std::io::Write;
594    /// use atomic_write_file::AtomicWriteFile;
595    ///
596    /// let file = AtomicWriteFile::open("foo.txt")?;
597    /// writeln!(&file, "hello")?;
598    /// file.commit()?;
599    /// # Ok(())
600    /// # }
601    /// ```
602    #[inline]
603    pub fn commit(mut self) -> Result<()> {
604        self._commit()
605    }
606
607    fn _commit(&mut self) -> Result<()> {
608        if self.finalized {
609            return Ok(());
610        }
611        self.finalized = true;
612        self.sync_all()?;
613        self.temporary_file.rename_file()
614    }
615
616    /// Discard the contents of this file, and leave its path unchanged.
617    ///
618    /// After calling `discard()`, the `AtomicWriteFile` is consumed and can no longer be used.
619    /// Clones of the underlaying [`File`] may still be used after calling `discard()`.
620    ///
621    /// This method is automatically called when `AtomicWriteFile` is dropped, although in that
622    /// case any error produced by `discard()` is ignored.
623    ///
624    /// See also [`AtomicWriteFile::commit()`].
625    ///
626    /// # Examples
627    ///
628    /// ```
629    /// # fn main() -> std::io::Result<()> {
630    /// # let test_dir = option_env!("TEST_DIR").unwrap_or("target/test-files");
631    /// # std::fs::create_dir_all(&test_dir).expect("failed to create test dir");
632    /// # std::env::set_current_dir(test_dir).expect("failed to move to test dir");
633    /// use std::io::Write;
634    /// use atomic_write_file::AtomicWriteFile;
635    ///
636    /// let file = AtomicWriteFile::open("foo.txt")?;
637    /// writeln!(&file, "hello")?;
638    /// file.discard()?;
639    /// # Ok(())
640    /// # }
641    /// ```
642    #[inline]
643    pub fn discard(mut self) -> Result<()> {
644        self._discard()
645    }
646
647    fn _discard(&mut self) -> Result<()> {
648        if self.finalized {
649            return Ok(());
650        }
651        self.finalized = true;
652        self.temporary_file.remove_file()
653    }
654}
655
656impl Drop for AtomicWriteFile {
657    #[inline]
658    fn drop(&mut self) {
659        // Ignore all errors
660        let _ = self._discard();
661    }
662}
663
664impl Deref for AtomicWriteFile {
665    type Target = File;
666
667    #[inline]
668    fn deref(&self) -> &Self::Target {
669        self.as_file()
670    }
671}
672
673impl DerefMut for AtomicWriteFile {
674    #[inline]
675    fn deref_mut(&mut self) -> &mut Self::Target {
676        self.as_file_mut()
677    }
678}
679
680impl Read for AtomicWriteFile {
681    #[inline]
682    fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
683        self.temporary_file.file.read(buf)
684    }
685
686    #[inline]
687    fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> Result<usize> {
688        self.temporary_file.file.read_vectored(bufs)
689    }
690
691    #[inline]
692    #[cfg(feature = "unstable-can_vector")]
693    fn is_read_vectored(&self) -> bool {
694        self.temporary_file.file.is_read_vectored()
695    }
696
697    #[inline]
698    fn read_to_end(&mut self, buf: &mut Vec<u8>) -> Result<usize> {
699        self.temporary_file.file.read_to_end(buf)
700    }
701
702    #[inline]
703    fn read_to_string(&mut self, buf: &mut String) -> Result<usize> {
704        self.temporary_file.file.read_to_string(buf)
705    }
706
707    #[inline]
708    fn read_exact(&mut self, buf: &mut [u8]) -> Result<()> {
709        self.temporary_file.file.read_exact(buf)
710    }
711
712    #[inline]
713    #[cfg(feature = "unstable-read_buf")]
714    fn read_buf(&mut self, buf: BorrowedCursor<'_>) -> Result<()> {
715        self.temporary_file.file.read_buf(buf)
716    }
717
718    #[inline]
719    #[cfg(feature = "unstable-read_buf")]
720    fn read_buf_exact(&mut self, cursor: BorrowedCursor<'_>) -> Result<()> {
721        self.temporary_file.file.read_buf_exact(cursor)
722    }
723}
724
725impl Read for &AtomicWriteFile {
726    #[inline]
727    fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
728        (&self.temporary_file.file).read(buf)
729    }
730
731    #[inline]
732    fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> Result<usize> {
733        (&self.temporary_file.file).read_vectored(bufs)
734    }
735
736    #[inline]
737    #[cfg(feature = "unstable-can_vector")]
738    fn is_read_vectored(&self) -> bool {
739        self.temporary_file.file.is_read_vectored()
740    }
741
742    #[inline]
743    fn read_to_end(&mut self, buf: &mut Vec<u8>) -> Result<usize> {
744        (&self.temporary_file.file).read_to_end(buf)
745    }
746
747    #[inline]
748    fn read_to_string(&mut self, buf: &mut String) -> Result<usize> {
749        (&self.temporary_file.file).read_to_string(buf)
750    }
751
752    #[inline]
753    fn read_exact(&mut self, buf: &mut [u8]) -> Result<()> {
754        (&self.temporary_file.file).read_exact(buf)
755    }
756
757    #[inline]
758    #[cfg(feature = "unstable-read_buf")]
759    fn read_buf(&mut self, buf: BorrowedCursor<'_>) -> Result<()> {
760        (&self.temporary_file.file).read_buf(buf)
761    }
762
763    #[inline]
764    #[cfg(feature = "unstable-read_buf")]
765    fn read_buf_exact(&mut self, cursor: BorrowedCursor<'_>) -> Result<()> {
766        (&self.temporary_file.file).read_buf_exact(cursor)
767    }
768}
769
770impl Write for AtomicWriteFile {
771    #[inline]
772    fn write(&mut self, buf: &[u8]) -> Result<usize> {
773        self.temporary_file.file.write(buf)
774    }
775
776    #[inline]
777    fn flush(&mut self) -> Result<()> {
778        self.temporary_file.file.flush()
779    }
780
781    #[inline]
782    fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> Result<usize> {
783        self.temporary_file.file.write_vectored(bufs)
784    }
785
786    #[inline]
787    #[cfg(feature = "unstable-can_vector")]
788    fn is_write_vectored(&self) -> bool {
789        self.temporary_file.file.is_write_vectored()
790    }
791
792    #[inline]
793    fn write_all(&mut self, buf: &[u8]) -> Result<()> {
794        self.temporary_file.file.write_all(buf)
795    }
796
797    #[inline]
798    #[cfg(feature = "unstable-write_all_vectored")]
799    fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> Result<()> {
800        self.temporary_file.file.write_all_vectored(bufs)
801    }
802
803    #[inline]
804    fn write_fmt(&mut self, fmt: Arguments<'_>) -> Result<()> {
805        self.temporary_file.file.write_fmt(fmt)
806    }
807}
808
809impl Write for &AtomicWriteFile {
810    #[inline]
811    fn write(&mut self, buf: &[u8]) -> Result<usize> {
812        (&self.temporary_file.file).write(buf)
813    }
814
815    #[inline]
816    fn flush(&mut self) -> Result<()> {
817        (&self.temporary_file.file).flush()
818    }
819
820    #[inline]
821    fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> Result<usize> {
822        (&self.temporary_file.file).write_vectored(bufs)
823    }
824
825    #[inline]
826    #[cfg(feature = "unstable-can_vector")]
827    fn is_write_vectored(&self) -> bool {
828        self.temporary_file.file.is_write_vectored()
829    }
830
831    #[inline]
832    fn write_all(&mut self, buf: &[u8]) -> Result<()> {
833        (&self.temporary_file.file).write_all(buf)
834    }
835
836    #[inline]
837    #[cfg(feature = "unstable-write_all_vectored")]
838    fn write_all_vectored(&mut self, bufs: &mut [IoSlice<'_>]) -> Result<()> {
839        (&self.temporary_file.file).write_all_vectored(bufs)
840    }
841
842    #[inline]
843    fn write_fmt(&mut self, fmt: Arguments<'_>) -> Result<()> {
844        (&self.temporary_file.file).write_fmt(fmt)
845    }
846}
847
848impl Seek for AtomicWriteFile {
849    #[inline]
850    fn seek(&mut self, pos: SeekFrom) -> Result<u64> {
851        self.temporary_file.file.seek(pos)
852    }
853
854    #[inline]
855    fn rewind(&mut self) -> Result<()> {
856        self.temporary_file.file.rewind()
857    }
858
859    #[inline]
860    #[cfg(feature = "unstable-seek_stream_len")]
861    fn stream_len(&mut self) -> Result<u64> {
862        self.temporary_file.file.stream_len()
863    }
864
865    #[inline]
866    fn stream_position(&mut self) -> Result<u64> {
867        self.temporary_file.file.stream_position()
868    }
869}
870
871impl Seek for &AtomicWriteFile {
872    #[inline]
873    fn seek(&mut self, pos: SeekFrom) -> Result<u64> {
874        (&self.temporary_file.file).seek(pos)
875    }
876
877    #[inline]
878    fn rewind(&mut self) -> Result<()> {
879        (&self.temporary_file.file).rewind()
880    }
881
882    #[inline]
883    #[cfg(feature = "unstable-seek_stream_len")]
884    fn stream_len(&mut self) -> Result<u64> {
885        (&self.temporary_file.file).stream_len()
886    }
887
888    #[inline]
889    fn stream_position(&mut self) -> Result<u64> {
890        (&self.temporary_file.file).stream_position()
891    }
892}