1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
//! Unix-specific extensions to [`AtomicWriteFile`] and [`OpenOptions`].

use crate::imp::Preserve;
use crate::AtomicWriteFile;
use crate::OpenOptions;
use nix::sys::stat::mode_t;
use std::io::Result;
use std::os::unix::fs;

#[cfg(feature = "unstable-unix_file_vectored_at")]
use std::io::IoSlice;

#[cfg(feature = "unstable-unix_file_vectored_at")]
use std::io::IoSliceMut;

impl fs::OpenOptionsExt for OpenOptions {
    #[inline]
    fn mode(&mut self, mode: u32) -> &mut Self {
        self.inner.mode = mode as mode_t;
        self
    }

    #[inline]
    fn custom_flags(&mut self, flags: i32) -> &mut Self {
        self.inner.custom_flags = flags;
        self
    }
}

/// Unix-specific extensions to [`OpenOptions`].
///
/// On Unix, [`AtomicWriteFile`] works by creating a temporary file, and then renaming the file to
/// its final name at commit time. If a file with the same name already exists, it is often
/// desiderable that the new file written by [`AtomicWriteFile`] preserves the same permissions,
/// attributes, and other metadata as the original file. This extension trait allows you to control
/// which metadata should be preserved, and which metadata should use default values instead.
///
/// **Note:** [`AtomicWriteFile`] samples and copies the metadata from the original file (if any)
/// to the temporary file when the [`AtomicWriteFile`] is initially opened. If the original file
/// metadata is changed, or the original file is deleted, or the original file gets (re-)created,
/// before the [`AtomicWriteFile`] is committed, those changes won't be reflected in the final
/// result.
pub trait OpenOptionsExt {
    /// Specifies whether the atomically-written file should have the file permissions of the
    /// original file (if any).
    ///
    /// If `true` (the default), the file permissions of the original file (if any) are copied over
    /// to the atomically-written file when [`OpenOptions::open()`] is called.
    ///
    /// If `false`, or if no original file exists when [`OpenOptions::open()`] is called, the
    /// default mode `0o666` is used, which will be masked with the process umask. The default mask
    /// can be customized using [`std::os::unix::fs::OpenOptionsExt::mode()`].
    ///
    /// This method only preserves the permissions that can be set through `chmod(2)`. This method
    /// has no effect on ACLs (POSIX Access Control Lists).
    ///
    /// # Examples
    ///
    /// ```
    /// # fn main() -> std::io::Result<()> {
    /// # let test_dir = option_env!("TEST_DIR").unwrap_or("target/test-files");
    /// # std::fs::create_dir_all(&test_dir).expect("failed to create test dir");
    /// # std::env::set_current_dir(test_dir).expect("failed to move to test dir");
    /// use atomic_write_file::OpenOptions;
    /// use atomic_write_file::unix::OpenOptionsExt;
    ///
    /// let mut options = OpenOptions::new();
    /// options.preserve_mode(false);
    /// let file = options.open("foo.txt")?;
    /// file.commit()?; // "foo.txt" is saved with the default mode
    /// # Ok(())
    /// # }
    /// ```
    fn preserve_mode(&mut self, preserve_mode: bool) -> &mut Self;

    /// Specifies whether the atomically-written file should have the same ownership (user/group)
    /// of the original file (if any).
    ///
    /// If `true`, the ownership of the original file (if any) is copied over
    /// to the atomically-written file when [`OpenOptions::open()`] is called.
    ///
    /// If `false`, or if no original file exists when [`OpenOptions::open()`] is called, the
    /// ownership is set using the default platform-specific semantics.
    ///
    /// **Note:** setting ownership of files generally requires root privileges on Unix platforms
    /// (or `CAP_CHOWN` on Linux). As such, using `preserve_owner(true)` when the process is not
    /// running as root may result in [`OpenOptions::open()`] to fail with an error.
    ///
    /// The related method [`OpenOptionsExt::try_preserve_owner()`] allows to preserve ownership
    /// only if the process has sufficient privileges. It is a variant of this option that does not
    /// cause [`OpenOptions::open()`] to fail when the process does not have enough privileges to
    /// preserve ownership.
    ///
    /// By default, `try_preserve_owner(true)` is used.
    ///
    /// Calling `preserve_owner()` overrides any previous call to `preserve_owner()` or
    /// `try_preserve_owner()`.
    ///
    /// # Examples
    ///
    /// ```
    /// # fn main() -> std::io::Result<()> {
    /// # let test_dir = option_env!("TEST_DIR").unwrap_or("target/test-files");
    /// # std::fs::create_dir_all(&test_dir).expect("failed to create test dir");
    /// # std::env::set_current_dir(test_dir).expect("failed to move to test dir");
    /// use atomic_write_file::OpenOptions;
    /// use atomic_write_file::unix::OpenOptionsExt;
    ///
    /// let mut options = OpenOptions::new();
    /// options.preserve_owner(true);
    /// let file = options.open("foo.txt")?; // this fails if "foo.txt" exists and its ownership
    ///                                      // cannot be preserved
    /// file.commit()?; // "foo.txt" is saved with the same ownership as the original "foo.txt"
    ///                 // (if any)
    /// # Ok(())
    /// # }
    /// ```
    fn preserve_owner(&mut self, preserve_owner: bool) -> &mut Self;

    /// Specifies whether the atomically-written file should have the ownership information
    /// (user/group) of the original file (if any). If ownership information cannot be set on the
    /// atomically-written file, and if the process is not running as root, then
    /// [`OpenOptions::open()`] silently continues suppressing the error.
    ///
    /// Using `try_preserve_owner(true)` is equivalent to using
    /// [`OpenOptionsExt::preserve_owner(true)`](OpenOptionsExt::preserve_owner), with the
    /// exception that this option does not cause [`OpenOptions::open()`] to fail with a "Operation
    /// not permitted" (`EPERM`) error if the process is not running as root (effective UID 0).
    ///
    /// Note that [`OpenOptions::open()`] may still fail with an error when setting ownership if an
    /// error other than "Operation not permitted" (`EPERM`) is thrown.
    ///
    /// If the process is running as root, then all errors are reported by [`OpenOptions::open()`].
    ///
    /// If `false`, or if no original file exists when [`OpenOptions::open()`] is called, the
    /// ownership is set using the default platform-specific semantics.
    ///
    /// The default value for this option is `true`.
    ///
    /// Calling `try_preserve_owner()` overrides any previous call to `preserve_owner()` or
    /// `try_preserve_owner()`.
    ///
    /// # Examples
    ///
    /// ```
    /// # fn main() -> std::io::Result<()> {
    /// # let test_dir = option_env!("TEST_DIR").unwrap_or("target/test-files");
    /// # std::fs::create_dir_all(&test_dir).expect("failed to create test dir");
    /// # std::env::set_current_dir(test_dir).expect("failed to move to test dir");
    /// use atomic_write_file::OpenOptions;
    /// use atomic_write_file::unix::OpenOptionsExt;
    ///
    /// let mut options = OpenOptions::new();
    /// options.try_preserve_owner(true); // this is the default
    /// let file = options.open("foo.txt")?; // this won't fail if "foo.txt" exists, its ownership
    ///                                      // cannot be preserved, and the process is not running
    ///                                      // as root
    /// file.commit()?; // "foo.txt" is saved with the same ownership as the original "foo.txt"
    ///                 // (if any)
    /// # Ok(())
    /// # }
    /// ```
    fn try_preserve_owner(&mut self, try_preserve_owner: bool) -> &mut Self;
}

impl OpenOptionsExt for OpenOptions {
    #[inline]
    fn preserve_mode(&mut self, preserve_mode: bool) -> &mut Self {
        self.inner.preserve_mode = preserve_mode;
        self
    }

    #[inline]
    fn preserve_owner(&mut self, preserve_owner: bool) -> &mut Self {
        self.inner.preserve_owner = match preserve_owner {
            true => Preserve::Yes,
            false => Preserve::No,
        };
        self
    }

    #[inline]
    fn try_preserve_owner(&mut self, try_preserve_owner: bool) -> &mut Self {
        self.inner.preserve_owner = match try_preserve_owner {
            true => Preserve::Try,
            false => Preserve::No,
        };
        self
    }
}

impl fs::FileExt for AtomicWriteFile {
    #[inline]
    fn read_at(&self, buf: &mut [u8], offset: u64) -> Result<usize> {
        self.temporary_file.file.read_at(buf, offset)
    }
    #[inline]
    fn write_at(&self, buf: &[u8], offset: u64) -> Result<usize> {
        self.temporary_file.file.write_at(buf, offset)
    }

    #[inline]
    #[cfg(feature = "unstable-unix_file_vectored_at")]
    fn read_vectored_at(&self, bufs: &mut [IoSliceMut<'_>], offset: u64) -> Result<usize> {
        self.temporary_file.file.read_vectored_at(bufs, offset)
    }

    #[inline]
    fn read_exact_at(&self, buf: &mut [u8], offset: u64) -> Result<()> {
        self.temporary_file.file.read_exact_at(buf, offset)
    }

    #[inline]
    #[cfg(feature = "unstable-unix_file_vectored_at")]
    fn write_vectored_at(&self, bufs: &[IoSlice<'_>], offset: u64) -> Result<usize> {
        self.temporary_file.file.write_vectored_at(bufs, offset)
    }

    #[inline]
    fn write_all_at(&self, buf: &[u8], offset: u64) -> Result<()> {
        self.temporary_file.file.write_all_at(buf, offset)
    }
}