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}