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}