atomic_write_file/
unix.rs

1//! Unix-specific extensions to [`AtomicWriteFile`] and [`OpenOptions`].
2
3use crate::imp::Preserve;
4use crate::AtomicWriteFile;
5use crate::OpenOptions;
6use nix::sys::stat::mode_t;
7use std::io::Result;
8use std::os::unix::fs;
9
10#[cfg(feature = "unstable-unix_file_vectored_at")]
11use std::io::IoSlice;
12
13#[cfg(feature = "unstable-unix_file_vectored_at")]
14use std::io::IoSliceMut;
15
16impl fs::OpenOptionsExt for OpenOptions {
17    #[inline]
18    fn mode(&mut self, mode: u32) -> &mut Self {
19        self.inner.mode = mode as mode_t;
20        self
21    }
22
23    #[inline]
24    fn custom_flags(&mut self, flags: i32) -> &mut Self {
25        self.inner.custom_flags = flags;
26        self
27    }
28}
29
30/// Unix-specific extensions to [`OpenOptions`].
31///
32/// On Unix, [`AtomicWriteFile`] works by creating a temporary file, and then renaming the file to
33/// its final name at commit time. If a file with the same name already exists, it is often
34/// desiderable that the new file written by [`AtomicWriteFile`] preserves the same permissions,
35/// attributes, and other metadata as the original file. This extension trait allows you to control
36/// which metadata should be preserved, and which metadata should use default values instead.
37///
38/// **Note:** [`AtomicWriteFile`] samples and copies the metadata from the original file (if any)
39/// to the temporary file when the [`AtomicWriteFile`] is initially opened. If the original file
40/// metadata is changed, or the original file is deleted, or the original file gets (re-)created,
41/// before the [`AtomicWriteFile`] is committed, those changes won't be reflected in the final
42/// result.
43pub trait OpenOptionsExt {
44    /// Specifies whether the atomically-written file should have the file permissions of the
45    /// original file (if any).
46    ///
47    /// If `true` (the default), the file permissions of the original file (if any) are copied over
48    /// to the atomically-written file when [`OpenOptions::open()`] is called.
49    ///
50    /// If `false`, or if no original file exists when [`OpenOptions::open()`] is called, the
51    /// default mode `0o666` is used, which will be masked with the process umask. The default mask
52    /// can be customized using [`std::os::unix::fs::OpenOptionsExt::mode()`].
53    ///
54    /// This method only preserves the permissions that can be set through `chmod(2)`. This method
55    /// has no effect on ACLs (POSIX Access Control Lists).
56    ///
57    /// # Examples
58    ///
59    /// ```
60    /// # fn main() -> std::io::Result<()> {
61    /// # let test_dir = option_env!("TEST_DIR").unwrap_or("target/test-files");
62    /// # std::fs::create_dir_all(&test_dir).expect("failed to create test dir");
63    /// # std::env::set_current_dir(test_dir).expect("failed to move to test dir");
64    /// use atomic_write_file::OpenOptions;
65    /// use atomic_write_file::unix::OpenOptionsExt;
66    ///
67    /// let mut options = OpenOptions::new();
68    /// options.preserve_mode(false);
69    /// let file = options.open("foo.txt")?;
70    /// file.commit()?; // "foo.txt" is saved with the default mode
71    /// # Ok(())
72    /// # }
73    /// ```
74    fn preserve_mode(&mut self, preserve_mode: bool) -> &mut Self;
75
76    /// Specifies whether the atomically-written file should have the same ownership (user/group)
77    /// of the original file (if any).
78    ///
79    /// If `true`, the ownership of the original file (if any) is copied over
80    /// to the atomically-written file when [`OpenOptions::open()`] is called.
81    ///
82    /// If `false`, or if no original file exists when [`OpenOptions::open()`] is called, the
83    /// ownership is set using the default platform-specific semantics.
84    ///
85    /// **Note:** setting ownership of files generally requires root privileges on Unix platforms
86    /// (or `CAP_CHOWN` on Linux). As such, using `preserve_owner(true)` when the process is not
87    /// running as root may result in [`OpenOptions::open()`] to fail with an error.
88    ///
89    /// The related method [`OpenOptionsExt::try_preserve_owner()`] allows to preserve ownership
90    /// only if the process has sufficient privileges. It is a variant of this option that does not
91    /// cause [`OpenOptions::open()`] to fail when the process does not have enough privileges to
92    /// preserve ownership.
93    ///
94    /// By default, `try_preserve_owner(true)` is used.
95    ///
96    /// Calling `preserve_owner()` overrides any previous call to `preserve_owner()` or
97    /// `try_preserve_owner()`.
98    ///
99    /// # Examples
100    ///
101    /// ```
102    /// # fn main() -> std::io::Result<()> {
103    /// # let test_dir = option_env!("TEST_DIR").unwrap_or("target/test-files");
104    /// # std::fs::create_dir_all(&test_dir).expect("failed to create test dir");
105    /// # std::env::set_current_dir(test_dir).expect("failed to move to test dir");
106    /// use atomic_write_file::OpenOptions;
107    /// use atomic_write_file::unix::OpenOptionsExt;
108    ///
109    /// let mut options = OpenOptions::new();
110    /// options.preserve_owner(true);
111    /// let file = options.open("foo.txt")?; // this fails if "foo.txt" exists and its ownership
112    ///                                      // cannot be preserved
113    /// file.commit()?; // "foo.txt" is saved with the same ownership as the original "foo.txt"
114    ///                 // (if any)
115    /// # Ok(())
116    /// # }
117    /// ```
118    fn preserve_owner(&mut self, preserve_owner: bool) -> &mut Self;
119
120    /// Specifies whether the atomically-written file should have the ownership information
121    /// (user/group) of the original file (if any). If ownership information cannot be set on the
122    /// atomically-written file, and if the process is not running as root, then
123    /// [`OpenOptions::open()`] silently continues suppressing the error.
124    ///
125    /// Using `try_preserve_owner(true)` is equivalent to using
126    /// [`OpenOptionsExt::preserve_owner(true)`](OpenOptionsExt::preserve_owner), with the
127    /// exception that this option does not cause [`OpenOptions::open()`] to fail with a "Operation
128    /// not permitted" (`EPERM`) error if the process is not running as root (effective UID 0).
129    ///
130    /// Note that [`OpenOptions::open()`] may still fail with an error when setting ownership if an
131    /// error other than "Operation not permitted" (`EPERM`) is thrown.
132    ///
133    /// If the process is running as root, then all errors are reported by [`OpenOptions::open()`].
134    ///
135    /// If `false`, or if no original file exists when [`OpenOptions::open()`] is called, the
136    /// ownership is set using the default platform-specific semantics.
137    ///
138    /// The default value for this option is `true`.
139    ///
140    /// Calling `try_preserve_owner()` overrides any previous call to `preserve_owner()` or
141    /// `try_preserve_owner()`.
142    ///
143    /// # Examples
144    ///
145    /// ```
146    /// # fn main() -> std::io::Result<()> {
147    /// # let test_dir = option_env!("TEST_DIR").unwrap_or("target/test-files");
148    /// # std::fs::create_dir_all(&test_dir).expect("failed to create test dir");
149    /// # std::env::set_current_dir(test_dir).expect("failed to move to test dir");
150    /// use atomic_write_file::OpenOptions;
151    /// use atomic_write_file::unix::OpenOptionsExt;
152    ///
153    /// let mut options = OpenOptions::new();
154    /// options.try_preserve_owner(true); // this is the default
155    /// let file = options.open("foo.txt")?; // this won't fail if "foo.txt" exists, its ownership
156    ///                                      // cannot be preserved, and the process is not running
157    ///                                      // as root
158    /// file.commit()?; // "foo.txt" is saved with the same ownership as the original "foo.txt"
159    ///                 // (if any)
160    /// # Ok(())
161    /// # }
162    /// ```
163    fn try_preserve_owner(&mut self, try_preserve_owner: bool) -> &mut Self;
164}
165
166impl OpenOptionsExt for OpenOptions {
167    #[inline]
168    fn preserve_mode(&mut self, preserve_mode: bool) -> &mut Self {
169        self.inner.preserve_mode = preserve_mode;
170        self
171    }
172
173    #[inline]
174    fn preserve_owner(&mut self, preserve_owner: bool) -> &mut Self {
175        self.inner.preserve_owner = match preserve_owner {
176            true => Preserve::Yes,
177            false => Preserve::No,
178        };
179        self
180    }
181
182    #[inline]
183    fn try_preserve_owner(&mut self, try_preserve_owner: bool) -> &mut Self {
184        self.inner.preserve_owner = match try_preserve_owner {
185            true => Preserve::Try,
186            false => Preserve::No,
187        };
188        self
189    }
190}
191
192impl fs::FileExt for AtomicWriteFile {
193    #[inline]
194    fn read_at(&self, buf: &mut [u8], offset: u64) -> Result<usize> {
195        self.temporary_file.file.read_at(buf, offset)
196    }
197    #[inline]
198    fn write_at(&self, buf: &[u8], offset: u64) -> Result<usize> {
199        self.temporary_file.file.write_at(buf, offset)
200    }
201
202    #[inline]
203    #[cfg(feature = "unstable-unix_file_vectored_at")]
204    fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> Result<usize> {
205        self.temporary_file.file.read_vectored_at(bufs, offset)
206    }
207
208    #[inline]
209    fn read_exact_at(&self, buf: &mut [u8], offset: u64) -> Result<()> {
210        self.temporary_file.file.read_exact_at(buf, offset)
211    }
212
213    #[inline]
214    #[cfg(feature = "unstable-unix_file_vectored_at")]
215    fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> Result<usize> {
216        self.temporary_file.file.write_vectored_at(bufs, offset)
217    }
218
219    #[inline]
220    fn write_all_at(&self, buf: &[u8], offset: u64) -> Result<()> {
221        self.temporary_file.file.write_all_at(buf, offset)
222    }
223}