1#[cfg(unix)]
3extern crate rustix;
4extern crate tempfile;
5
6use std::convert::AsRef;
7use std::error::Error as ErrorTrait;
8use std::fmt;
9use std::fs;
10use std::io;
11use std::path;
12
13pub use OverwriteBehavior::{AllowOverwrite, DisallowOverwrite};
14
15#[derive(Clone, Copy)]
17pub enum OverwriteBehavior {
18 AllowOverwrite,
20
21 DisallowOverwrite,
24}
25
26#[derive(Debug)]
28pub enum Error<E> {
29 Internal(io::Error),
32 User(E),
34}
35
36impl From<Error<io::Error>> for io::Error {
38 fn from(e: Error<io::Error>) -> Self {
39 match e {
40 Error::Internal(x) => x,
41 Error::User(x) => x,
42 }
43 }
44}
45
46impl<E: fmt::Display> fmt::Display for Error<E> {
47 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
48 match *self {
49 Error::Internal(ref e) => e.fmt(f),
50 Error::User(ref e) => e.fmt(f),
51 }
52 }
53}
54
55impl<E: ErrorTrait> ErrorTrait for Error<E> {
56 fn cause(&self) -> Option<&dyn ErrorTrait> {
57 match *self {
58 Error::Internal(ref e) => Some(e),
59 Error::User(ref e) => Some(e),
60 }
61 }
62}
63
64fn safe_parent(p: &path::Path) -> Option<&path::Path> {
65 match p.parent() {
66 None => None,
67 Some(x) if x.as_os_str().is_empty() => Some(path::Path::new(".")),
68 x => x,
69 }
70}
71
72pub struct AtomicFile {
74 path: path::PathBuf,
76 overwrite: OverwriteBehavior,
77 tmpdir: path::PathBuf,
79}
80
81impl AtomicFile {
82 pub fn new<P>(path: P, overwrite: OverwriteBehavior) -> Self
91 where
92 P: AsRef<path::Path>,
93 {
94 let p = path.as_ref();
95 AtomicFile::new_with_tmpdir(
96 p,
97 overwrite,
98 safe_parent(p).unwrap_or_else(|| path::Path::new(".")),
99 )
100 }
101
102 pub fn new_with_tmpdir<P, Q>(path: P, overwrite: OverwriteBehavior, tmpdir: Q) -> Self
106 where
107 P: AsRef<path::Path>,
108 Q: AsRef<path::Path>,
109 {
110 AtomicFile {
111 path: path.as_ref().to_path_buf(),
112 overwrite,
113 tmpdir: tmpdir.as_ref().to_path_buf(),
114 }
115 }
116
117 fn commit(&self, tmppath: &path::Path) -> io::Result<()> {
119 match self.overwrite {
120 AllowOverwrite => replace_atomic(tmppath, self.path()),
121 DisallowOverwrite => move_atomic(tmppath, self.path()),
122 }
123 }
124
125 pub fn path(&self) -> &path::Path {
127 &self.path
128 }
129
130 pub fn write<T, E, F>(&self, f: F) -> Result<T, Error<E>>
135 where
136 F: FnOnce(&mut fs::File) -> Result<T, E>,
137 {
138 let mut options = fs::OpenOptions::new();
139 options.write(true).create(true).truncate(true);
141 self.write_with_options(f, options)
142 }
143
144 pub fn write_with_options<T, E, F>(&self, f: F, options: fs::OpenOptions) -> Result<T, Error<E>>
152 where
153 F: FnOnce(&mut fs::File) -> Result<T, E>,
154 {
155 let tmpdir = tempfile::Builder::new()
156 .prefix(".atomicwrite")
157 .tempdir_in(&self.tmpdir)
158 .map_err(Error::Internal)?;
159
160 let tmppath = tmpdir.path().join("tmpfile.tmp");
161 let rv = {
162 let mut tmpfile = options.open(&tmppath).map_err(Error::Internal)?;
163 let r = f(&mut tmpfile).map_err(Error::User)?;
164 tmpfile.sync_all().map_err(Error::Internal)?;
165 r
166 };
167 self.commit(&tmppath).map_err(Error::Internal)?;
168 Ok(rv)
169 }
170}
171
172#[cfg(all(unix, not(target_os = "redox")))]
173mod imp {
174 use super::safe_parent;
175
176 use rustix::fs::AtFlags;
177 use std::{fs, io, path};
178
179 pub fn replace_atomic(src: &path::Path, dst: &path::Path) -> io::Result<()> {
180 let src_parent_path = safe_parent(src).unwrap();
181 let dst_parent_path = safe_parent(dst).unwrap();
182 let src_child_path = src.file_name().unwrap();
183 let dst_child_path = dst.file_name().unwrap();
184
185 let src_parent = fs::File::open(src_parent_path)?;
188 let dst_parent;
189 let dst_parent = if src_parent_path == dst_parent_path {
190 &src_parent
191 } else {
192 dst_parent = fs::File::open(dst_parent_path)?;
193 &dst_parent
194 };
195
196 rustix::fs::renameat(&src_parent, src_child_path, dst_parent, dst_child_path)?;
198
199 src_parent.sync_all()?;
201 if src_parent_path != dst_parent_path {
202 dst_parent.sync_all()?;
203 }
204
205 Ok(())
206 }
207
208 pub fn move_atomic(src: &path::Path, dst: &path::Path) -> io::Result<()> {
209 let src_parent_path = safe_parent(src).unwrap();
210 let dst_parent_path = safe_parent(dst).unwrap();
211 let src_child_path = src.file_name().unwrap();
212 let dst_child_path = dst.file_name().unwrap();
213
214 let src_parent = fs::File::open(src_parent_path)?;
217 let dst_parent;
218 let dst_parent = if src_parent_path == dst_parent_path {
219 &src_parent
220 } else {
221 dst_parent = fs::File::open(dst_parent_path)?;
222 &dst_parent
223 };
224
225 #[cfg(any(target_os = "android", target_os = "linux"))]
228 {
229 use rustix::fs::RenameFlags;
230 use std::sync::atomic::AtomicBool;
231 use std::sync::atomic::Ordering::Relaxed;
232
233 static NO_RENAMEAT2: AtomicBool = AtomicBool::new(false);
234 if !NO_RENAMEAT2.load(Relaxed) {
235 match rustix::fs::renameat_with(
236 &src_parent,
237 src_child_path,
238 dst_parent,
239 dst_child_path,
240 RenameFlags::NOREPLACE,
241 ) {
242 Ok(()) => {
243 src_parent.sync_all()?;
246 if src_parent_path != dst_parent_path {
247 dst_parent.sync_all()?;
248 }
249 return Ok(());
250 }
251 Err(rustix::io::Errno::NOSYS) => {
252 NO_RENAMEAT2.store(true, Relaxed);
255 }
256 Err(e) => return Err(e.into()),
257 }
258 }
259 }
260
261 rustix::fs::linkat(
263 &src_parent,
264 src_child_path,
265 dst_parent,
266 dst_child_path,
267 AtFlags::empty(),
268 )?;
269 rustix::fs::unlinkat(&src_parent, src_child_path, AtFlags::empty())?;
270
271 src_parent.sync_all()?;
273 if src_parent_path != dst_parent_path {
274 dst_parent.sync_all()?;
275 }
276
277 Ok(())
278 }
279}
280
281#[cfg(target_os = "redox")]
282mod imp {
283 use std::{fs, io, path};
284
285 pub fn replace_atomic(src: &path::Path, dst: &path::Path) -> io::Result<()> {
286 fs::rename(src, dst)
287 }
288
289 pub fn move_atomic(src: &path::Path, dst: &path::Path) -> io::Result<()> {
290 fs::hard_link(src, dst)?;
291 fs::remove_file(src)
292 }
293}
294
295#[cfg(windows)]
296mod imp {
297 extern crate windows_sys;
298
299 use std::ffi::OsStr;
300 use std::os::windows::ffi::OsStrExt;
301 use std::{io, path};
302
303 macro_rules! call {
304 ($e: expr) => {
305 if $e != 0 {
306 Ok(())
307 } else {
308 Err(io::Error::last_os_error())
309 }
310 };
311 }
312
313 fn path_to_windows_str<T: AsRef<OsStr>>(x: T) -> Vec<u16> {
314 x.as_ref().encode_wide().chain(Some(0)).collect()
315 }
316
317 pub fn replace_atomic(src: &path::Path, dst: &path::Path) -> io::Result<()> {
318 call!(unsafe {
319 windows_sys::Win32::Storage::FileSystem::MoveFileExW(
320 path_to_windows_str(src).as_ptr(),
321 path_to_windows_str(dst).as_ptr(),
322 windows_sys::Win32::Storage::FileSystem::MOVEFILE_WRITE_THROUGH
323 | windows_sys::Win32::Storage::FileSystem::MOVEFILE_REPLACE_EXISTING,
324 )
325 })
326 }
327
328 pub fn move_atomic(src: &path::Path, dst: &path::Path) -> io::Result<()> {
329 call!(unsafe {
330 windows_sys::Win32::Storage::FileSystem::MoveFileExW(
331 path_to_windows_str(src).as_ptr(),
332 path_to_windows_str(dst).as_ptr(),
333 windows_sys::Win32::Storage::FileSystem::MOVEFILE_WRITE_THROUGH,
334 )
335 })
336 }
337}
338
339pub fn replace_atomic(src: &path::Path, dst: &path::Path) -> io::Result<()> {
343 imp::replace_atomic(src, dst)
344}
345
346pub fn move_atomic(src: &path::Path, dst: &path::Path) -> io::Result<()> {
350 imp::move_atomic(src, dst)
351}