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}