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
#[cfg(target_os = "macos")]
#[macro_use]
extern crate lazy_static;
mod platform;
use std::{fs, io};
use std::path::Path;
pub fn swap<A, B>(a: A, b: B) -> io::Result<()> where A: AsRef<Path>, B: AsRef<Path> {
platform::swap(a, b)
}
pub fn swap_nonatomic<A, B>(a: A, b: B) -> io::Result<()> where A: AsRef<Path>, B: AsRef<Path> {
const TMP_SWAP_FILE: &'static str = "tmp.fs_swap";
let a = a.as_ref();
let b = b.as_ref();
let tmp = a.parent()
.or_else(|| b.parent())
.ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "Could not find a parent directory"))?
.join(TMP_SWAP_FILE);
match fs::metadata(&tmp) {
Ok(ref meta) if meta.is_dir() => fs::remove_dir_all(&tmp)?,
Ok(_) => fs::remove_file(&tmp)?,
Err(ref err) if err.kind() == io::ErrorKind::NotFound => (),
Err(err) => return Err(err),
}
fs::rename(a, &tmp)?;
match fs::rename(b, a) {
Ok(_) => (),
error => {
fs::rename(&tmp, a)?;
return error
},
}
match fs::rename(&tmp, b) {
Ok(_) => Ok(()),
error => {
fs::rename(a, b)?;
fs::rename(&tmp, a)?;
error
},
}
}
#[cfg(test)]
mod tests {
extern crate tempdir;
use std::fs;
use std::path::Path;
use std::io::{Write, Read};
use self::tempdir::TempDir;
use super::{swap, swap_nonatomic};
fn write_to_file<P: AsRef<Path>>(file: P, text: &str) {
let mut file = fs::OpenOptions::new()
.create(true)
.write(true)
.open(file)
.unwrap();
file.write_all(text.as_ref()).unwrap();
file.flush().unwrap();
}
fn read_from_file<P: AsRef<Path>>(file: P) -> String {
let mut buffer = String::new();
let mut file = fs::OpenOptions::new()
.read(true)
.open(file)
.unwrap();
file.read_to_string(&mut buffer).unwrap();
buffer
}
#[test]
fn test_swap_files() {
let dir = TempDir::new("").unwrap();
let path_a = dir.path().join("file_a");
let path_b = dir.path().join("file_b");
write_to_file(&path_a, "foo");
write_to_file(&path_b, "bar");
swap(&path_a, &path_b).unwrap();
let read_a = read_from_file(&path_a);
let read_b = read_from_file(&path_b);
assert_eq!("bar", read_a);
assert_eq!("foo", read_b);
}
#[cfg(not(target_os = "macos"))]
#[test]
fn test_swap_dirs() {
let dir_a = TempDir::new("a").unwrap();
let dir_b = TempDir::new("b").unwrap();
let path_a = dir_a.path().join("file");
let path_b = dir_b.path().join("file");
write_to_file(&path_a, "foo");
write_to_file(&path_b, "bar");
swap(&dir_a, &dir_b).unwrap();
let read_a = read_from_file(&path_a);
let read_b = read_from_file(&path_b);
assert_eq!("bar", read_a);
assert_eq!("foo", read_b);
}
#[test]
fn test_swap_nonatomic_files() {
let dir = TempDir::new("").unwrap();
let path_a = dir.path().join("file_a");
let path_b = dir.path().join("file_b");
write_to_file(&path_a, "foo");
write_to_file(&path_b, "bar");
swap_nonatomic(&path_a, &path_b).unwrap();
let read_a = read_from_file(&path_a);
let read_b = read_from_file(&path_b);
assert_eq!("bar", read_a);
assert_eq!("foo", read_b);
}
#[test]
fn test_swap_nonatomic_dirs() {
let dir_a = TempDir::new("a").unwrap();
let dir_b = TempDir::new("b").unwrap();
let path_a = dir_a.path().join("file");
let path_b = dir_b.path().join("file");
write_to_file(&path_a, "foo");
write_to_file(&path_b, "bar");
swap_nonatomic(&dir_a, &dir_b).unwrap();
let read_a = read_from_file(&path_a);
let read_b = read_from_file(&path_b);
assert_eq!("bar", read_a);
assert_eq!("foo", read_b);
}
}