1use std::io;
2use std::path::Path;
3
4pub fn replace_file(from: &Path, to: &Path) -> io::Result<()> {
11 replace_file_impl(from, to)
12}
13
14pub fn rename_overwrite(from: &Path, to: &Path) -> io::Result<()> {
16 replace_file(from, to)
17}
18
19#[cfg(unix)]
20fn replace_file_impl(from: &Path, to: &Path) -> io::Result<()> {
21 std::fs::rename(from, to)
22}
23
24#[cfg(windows)]
25fn replace_file_impl(from: &Path, to: &Path) -> io::Result<()> {
26 match std::fs::rename(from, to) {
27 Ok(()) => Ok(()),
28 Err(err) => {
29 if !from.exists() {
31 return Err(err);
32 }
33
34 if !to.exists() {
35 return Err(err);
36 }
37
38 match std::fs::remove_file(to) {
39 Ok(()) => {}
40 Err(remove_err) if remove_err.kind() == io::ErrorKind::NotFound => {}
41 Err(remove_err) => {
42 return Err(io::Error::new(
43 io::ErrorKind::Other,
44 format!("rename failed: {err} (remove failed: {remove_err})"),
45 ));
46 }
47 }
48
49 std::fs::rename(from, to).map_err(|err2| {
50 io::Error::new(
51 io::ErrorKind::Other,
52 format!("rename failed: {err} ({err2})"),
53 )
54 })
55 }
56 }
57}
58
59#[cfg(not(any(unix, windows)))]
60fn replace_file_impl(from: &Path, to: &Path) -> io::Result<()> {
61 std::fs::rename(from, to)
62}
63
64#[cfg(test)]
65mod tests {
66 use super::*;
67 use std::fs;
68 use tempfile::TempDir;
69
70 #[test]
71 fn replace_file_overwrites_existing_destination() {
72 let dir = TempDir::new().expect("tempdir");
73 let from = dir.path().join("from.tmp");
74 let to = dir.path().join("to.txt");
75
76 fs::write(&from, "new").expect("write from");
77 fs::write(&to, "old").expect("write to");
78
79 replace_file(&from, &to).expect("replace_file");
80
81 assert!(!from.exists(), "from should be moved away");
82 assert_eq!(fs::read_to_string(&to).expect("read to"), "new");
83 }
84}