Skip to main content

fs_swap/
lib.rs

1//! Cross-platform implementation of path swap.
2
3#[cfg(target_os = "macos")]
4#[macro_use]
5extern crate lazy_static;
6
7mod platform;
8
9use std::{fs, io};
10use std::path::Path;
11
12/// Swaps the content of paths `a` and `b`.
13pub fn swap<A, B>(a: A, b: B) -> io::Result<()> where A: AsRef<Path>, B: AsRef<Path> {
14	platform::swap(a, b)
15}
16
17/// Nonatomic swap.
18pub fn swap_nonatomic<A, B>(a: A, b: B) -> io::Result<()> where A: AsRef<Path>, B: AsRef<Path> {
19	const TMP_SWAP_FILE: &'static str = "tmp.fs_swap";
20
21	let a = a.as_ref();
22	let b = b.as_ref();
23
24	let tmp = a.parent()
25		.or_else(|| b.parent())
26		.ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "Could not find a parent directory"))?
27		.join(TMP_SWAP_FILE);
28
29	// cleanup
30	match fs::metadata(&tmp) {
31		Ok(ref meta) if meta.is_dir() => fs::remove_dir_all(&tmp)?,
32		Ok(_) => fs::remove_file(&tmp)?,
33		Err(ref err) if err.kind() == io::ErrorKind::NotFound => (),
34		Err(err) => return Err(err),
35	}
36
37	// rename a to tmp
38	// if it fails, the directories are unchanged
39	fs::rename(a, &tmp)?;
40
41	match fs::rename(b, a) {
42		Ok(_) => (),
43		error => {
44			// let's try to recover the previous state
45			// if it fails, there is nothing we can do
46			fs::rename(&tmp, a)?;
47			return error
48		},
49	}
50
51	// rename tmp to b
52	match fs::rename(&tmp, b) {
53		Ok(_) => Ok(()),
54		error => {
55			// let's try to recover to previous state
56			// if it fails, there is nothing we can do
57			fs::rename(a, b)?;
58			fs::rename(&tmp, a)?;
59			error
60		},
61	}
62}
63
64#[cfg(test)]
65mod tests {
66	extern crate tempdir;
67	use std::fs;
68	use std::path::Path;
69	use std::io::{Write, Read};
70	use self::tempdir::TempDir;
71	use super::{swap, swap_nonatomic};
72
73	fn write_to_file<P: AsRef<Path>>(file: P, text: &str) {
74		let mut file = fs::OpenOptions::new()
75			.create(true)
76			.write(true)
77			.open(file)
78			.unwrap();
79		file.write_all(text.as_ref()).unwrap();
80		file.flush().unwrap();
81	}
82
83	fn read_from_file<P: AsRef<Path>>(file: P) -> String {
84		let mut buffer = String::new();
85		let mut file = fs::OpenOptions::new()
86			.read(true)
87			.open(file)
88			.unwrap();
89		file.read_to_string(&mut buffer).unwrap();
90		buffer
91	}
92
93	#[test]
94	fn test_swap_files() {
95		let dir = TempDir::new("").unwrap();
96		let path_a = dir.path().join("file_a");
97		let path_b = dir.path().join("file_b");
98		write_to_file(&path_a, "foo");
99		write_to_file(&path_b, "bar");
100		swap(&path_a, &path_b).unwrap();
101		let read_a = read_from_file(&path_a);
102		let read_b = read_from_file(&path_b);
103		assert_eq!("bar", read_a);
104		assert_eq!("foo", read_b);
105	}
106
107	// atomic swaps of dirs are not supported on travis machines
108	#[cfg(not(target_os = "macos"))]
109	#[test]
110	fn test_swap_dirs() {
111		let dir_a = TempDir::new("a").unwrap();
112		let dir_b = TempDir::new("b").unwrap();
113		let path_a = dir_a.path().join("file");
114		let path_b = dir_b.path().join("file");
115		write_to_file(&path_a, "foo");
116		write_to_file(&path_b, "bar");
117		swap(&dir_a, &dir_b).unwrap();
118		let read_a = read_from_file(&path_a);
119		let read_b = read_from_file(&path_b);
120		assert_eq!("bar", read_a);
121		assert_eq!("foo", read_b);
122	}
123
124	#[test]
125	fn test_swap_nonatomic_files() {
126		let dir = TempDir::new("").unwrap();
127		let path_a = dir.path().join("file_a");
128		let path_b = dir.path().join("file_b");
129		write_to_file(&path_a, "foo");
130		write_to_file(&path_b, "bar");
131		swap_nonatomic(&path_a, &path_b).unwrap();
132		let read_a = read_from_file(&path_a);
133		let read_b = read_from_file(&path_b);
134		assert_eq!("bar", read_a);
135		assert_eq!("foo", read_b);
136	}
137
138	// atomic swaps of dirs are not supported on travis machines
139	#[test]
140	fn test_swap_nonatomic_dirs() {
141		let dir_a = TempDir::new("a").unwrap();
142		let dir_b = TempDir::new("b").unwrap();
143		let path_a = dir_a.path().join("file");
144		let path_b = dir_b.path().join("file");
145		write_to_file(&path_a, "foo");
146		write_to_file(&path_b, "bar");
147		swap_nonatomic(&dir_a, &dir_b).unwrap();
148		let read_a = read_from_file(&path_a);
149		let read_b = read_from_file(&path_b);
150		assert_eq!("bar", read_a);
151		assert_eq!("foo", read_b);
152	}
153}