atomic_file_install/
lib.rs1use std::{fs, io, path::Path};
6
7use reflink_copy::reflink_or_copy;
8use tempfile::{NamedTempFile, TempPath};
9use tracing::{debug, warn};
10
11#[cfg(unix)]
12use std::os::unix::fs::symlink as symlink_file_inner;
13
14#[cfg(windows)]
15use std::os::windows::fs::symlink_file as symlink_file_inner;
16
17fn parent(p: &Path) -> io::Result<&Path> {
18 p.parent().ok_or_else(|| {
19 io::Error::new(
20 io::ErrorKind::InvalidData,
21 format!("`{}` does not have a parent", p.display()),
22 )
23 })
24}
25
26fn copy_to_tempfile(src: &Path, dst: &Path) -> io::Result<NamedTempFile> {
27 let parent = parent(dst)?;
28 debug!("Creating named tempfile at '{}'", parent.display());
29 let tempfile = NamedTempFile::new_in(parent)?;
30
31 debug!(
32 "Copying from '{}' to '{}'",
33 src.display(),
34 tempfile.path().display()
35 );
36 fs::remove_file(tempfile.path())?;
37 reflink_or_copy(src, tempfile.path())?;
42
43 debug!("Retrieving permissions of '{}'", src.display());
44 let permissions = src.metadata()?.permissions();
45
46 debug!(
47 "Setting permissions of '{}' to '{permissions:#?}'",
48 tempfile.path().display()
49 );
50 tempfile.as_file().set_permissions(permissions)?;
51
52 Ok(tempfile)
53}
54
55pub fn atomic_install_noclobber(src: &Path, dst: &Path) -> io::Result<()> {
59 debug!(
60 "Attempting to rename from '{}' to '{}'.",
61 src.display(),
62 dst.display()
63 );
64
65 let tempfile = copy_to_tempfile(src, dst)?;
66
67 debug!(
68 "Persisting '{}' to '{}', fail if dst already exists",
69 tempfile.path().display(),
70 dst.display()
71 );
72 tempfile.persist_noclobber(dst)?;
73
74 Ok(())
75}
76
77pub fn atomic_install(src: &Path, dst: &Path) -> io::Result<()> {
81 debug!(
82 "Attempting to atomically rename from '{}' to '{}'",
83 src.display(),
84 dst.display()
85 );
86
87 if let Err(err) = fs::rename(src, dst) {
88 warn!("Attempting at atomic rename failed: {err}, fallback to other methods.");
89
90 #[cfg(windows)]
91 {
92 match win::replace_file(src, dst) {
93 Ok(()) => {
94 debug!("ReplaceFileW succeeded.");
95 return Ok(());
96 }
97 Err(err) => {
98 warn!("ReplaceFileW failed: {err}, fallback to using tempfile plus rename")
99 }
100 }
101 }
102
103 persist(copy_to_tempfile(src, dst)?.into_temp_path(), dst)?;
108 } else {
109 debug!("Attempting at atomically succeeded.");
110 }
111
112 Ok(())
113}
114
115pub fn atomic_symlink_file_noclobber(dest: &Path, link: &Path) -> io::Result<()> {
120 match symlink_file_inner(dest, link) {
121 Ok(_) => Ok(()),
122
123 #[cfg(windows)]
124 Err(_) => atomic_install_noclobber(dest, link),
127
128 #[cfg(not(windows))]
129 Err(err) => Err(err),
130 }
131}
132
133pub fn atomic_symlink_file(dest: &Path, link: &Path) -> io::Result<()> {
138 let parent = parent(link)?;
139
140 debug!("Creating tempPath at '{}'", parent.display());
141 let temp_path = NamedTempFile::new_in(parent)?.into_temp_path();
142 fs::remove_file(&temp_path)?;
145
146 debug!(
147 "Creating symlink '{}' to file '{}'",
148 temp_path.display(),
149 dest.display()
150 );
151
152 match symlink_file_inner(dest, &temp_path) {
153 Ok(_) => persist(temp_path, link),
154
155 #[cfg(windows)]
156 Err(_) => atomic_install(dest, link),
159
160 #[cfg(not(windows))]
161 Err(err) => Err(err),
162 }
163}
164
165fn persist(temp_path: TempPath, to: &Path) -> io::Result<()> {
166 debug!("Persisting '{}' to '{}'", temp_path.display(), to.display());
167 match temp_path.persist(to) {
168 Ok(()) => Ok(()),
169 #[cfg(windows)]
170 Err(tempfile::PathPersistError {
171 error,
172 path: temp_path,
173 }) => {
174 warn!(
175 "Failed to persist symlink '{}' to '{}': {error}, fallback to ReplaceFileW",
176 temp_path.display(),
177 to.display(),
178 );
179 win::replace_file(&temp_path, to).map_err(io::Error::from)
180 }
181 #[cfg(not(windows))]
182 Err(err) => Err(err.into()),
183 }
184}
185
186#[cfg(windows)]
187mod win {
188 use std::{os::windows::ffi::OsStrExt, path::Path};
189
190 use windows::{
191 core::{Error, PCWSTR},
192 Win32::Storage::FileSystem::{ReplaceFileW, REPLACE_FILE_FLAGS},
193 };
194
195 pub(super) fn replace_file(src: &Path, dst: &Path) -> Result<(), Error> {
196 let mut src: Vec<_> = src.as_os_str().encode_wide().collect();
197 let mut dst: Vec<_> = dst.as_os_str().encode_wide().collect();
198
199 src.push(0);
201 dst.push(0);
202
203 unsafe {
209 ReplaceFileW(
210 PCWSTR::from_raw(dst.as_ptr()), PCWSTR::from_raw(src.as_ptr()), PCWSTR::null(), REPLACE_FILE_FLAGS(0), None, None, )
217 }
218 }
219}