1extern crate walkdir;
6
7use std::fs;
8use std::io::{Error, ErrorKind, Result};
9use std::path::Path;
10
11macro_rules! push_error {
12 ($expr:expr, $vec:ident) => {
13 match $expr {
14 Err(e) => $vec.push(e),
15 Ok(_) => (),
16 }
17 };
18}
19
20macro_rules! make_err {
21 ($text:expr, $kind:expr) => {
22 Error::new($kind, $text)
23 };
24
25 ($text:expr) => {
26 make_err!($text, ErrorKind::Other)
27 };
28}
29
30pub fn copy_dir<Q: AsRef<Path>, P: AsRef<Path>>(from: P, to: Q) -> Result<Vec<Error>> {
68 if !from.as_ref().exists() {
69 return Err(make_err!("source path does not exist", ErrorKind::NotFound));
70 } else if to.as_ref().exists() {
71 return Err(make_err!("target path exists", ErrorKind::AlreadyExists));
72 }
73
74 let mut errors = Vec::new();
75
76 if from.as_ref().is_file() {
78 return fs::copy(&from, &to).map(|_| Vec::new());
79 }
80
81 fs::create_dir(&to)?;
82
83 let target_is_under_source = from
92 .as_ref()
93 .canonicalize()
94 .and_then(|fc| to.as_ref().canonicalize().map(|tc| (fc, tc)))
95 .map(|(fc, tc)| tc.starts_with(fc))?;
96
97 if target_is_under_source {
98 fs::remove_dir(&to)?;
99
100 return Err(make_err!(
101 "cannot copy to a path prefixed by the source path"
102 ));
103 }
104
105 for entry in walkdir::WalkDir::new(&from)
106 .min_depth(1)
107 .into_iter()
108 .filter_map(|e| e.ok())
109 {
110 let relative_path = match entry.path().strip_prefix(&from) {
111 Ok(rp) => rp,
112 Err(_) => panic!("strip_prefix failed; this is a probably a bug in copy_dir"),
113 };
114
115 let target_path = {
116 let mut target_path = to.as_ref().to_path_buf();
117 target_path.push(relative_path);
118 target_path
119 };
120
121 let source_metadata = match entry.metadata() {
122 Err(_) => {
123 errors.push(make_err!(format!(
124 "walkdir metadata error for {:?}",
125 entry.path()
126 )));
127
128 continue;
129 }
130
131 Ok(md) => md,
132 };
133
134 if source_metadata.is_dir() {
135 push_error!(fs::create_dir(&target_path), errors);
136 push_error!(
137 fs::set_permissions(&target_path, source_metadata.permissions()),
138 errors
139 );
140 } else {
141 push_error!(fs::copy(entry.path(), &target_path), errors);
142 }
143 }
144
145 Ok(errors)
146}
147
148#[cfg(test)]
149mod tests {
150 #![allow(unused_variables)]
151
152 extern crate std;
153 use std::fs;
154 use std::path::Path;
155 use std::process::Command;
156
157 extern crate tempdir;
158 extern crate walkdir;
159
160 #[test]
161 fn single_file() {
162 let file = File("foo.file");
163 assert_we_match_the_real_thing(&file, true, None);
164 }
165
166 #[test]
167 fn directory_with_file() {
168 let dir = Dir(
169 "foo",
170 vec![File("bar"), Dir("baz", vec![File("quux"), File("fobe")])],
171 );
172 assert_we_match_the_real_thing(&dir, true, None);
173 }
174
175 #[test]
176 fn source_does_not_exist() {
177 let base_dir = tempdir::TempDir::new("copy_dir_test").unwrap();
178 let source_path = base_dir.as_ref().join("noexist.file");
179 match super::copy_dir(&source_path, "dest.file") {
180 Ok(_) => panic!("expected Err"),
181 Err(err) => match err.kind() {
182 std::io::ErrorKind::NotFound => (),
183 _ => panic!("expected kind NotFound"),
184 },
185 }
186 }
187
188 #[test]
189 fn target_exists() {
190 let base_dir = tempdir::TempDir::new("copy_dir_test").unwrap();
191 let source_path = base_dir.as_ref().join("exist.file");
192 let target_path = base_dir.as_ref().join("exist2.file");
193
194 {
195 fs::File::create(&source_path).unwrap();
196 fs::File::create(&target_path).unwrap();
197 }
198
199 match super::copy_dir(&source_path, &target_path) {
200 Ok(_) => panic!("expected Err"),
201 Err(err) => match err.kind() {
202 std::io::ErrorKind::AlreadyExists => (),
203 _ => panic!("expected kind AlreadyExists"),
204 },
205 }
206 }
207
208 #[test]
209 fn attempt_copy_under_self() {
210 let base_dir = tempdir::TempDir::new("copy_dir_test").unwrap();
211 let dir = Dir(
212 "foo",
213 vec![File("bar"), Dir("baz", vec![File("quux"), File("fobe")])],
214 );
215 dir.create(&base_dir).unwrap();
216
217 let from = base_dir.as_ref().join("foo");
218 let to = from.as_path().join("beez");
219
220 let copy_result = super::copy_dir(&from, &to);
221 assert!(copy_result.is_err());
222
223 let copy_err = copy_result.unwrap_err();
224 assert_eq!(copy_err.kind(), std::io::ErrorKind::Other);
225 }
226
227 enum DirMaker<'a> {
230 Dir(&'a str, Vec<DirMaker<'a>>),
231 File(&'a str),
232 }
233
234 use self::DirMaker::*;
235
236 impl<'a> DirMaker<'a> {
237 fn create<P: AsRef<Path>>(&self, base: P) -> std::io::Result<()> {
238 match *self {
239 Dir(ref name, ref contents) => {
240 let path = base.as_ref().join(name);
241 fs::create_dir(&path)?;
242
243 for thing in contents {
244 thing.create(&path)?;
245 }
246 }
247
248 File(ref name) => {
249 let path = base.as_ref().join(name);
250 fs::File::create(path)?;
251 }
252 }
253
254 Ok(())
255 }
256
257 fn name(&self) -> &str {
258 match *self {
259 Dir(name, _) => name,
260 File(name) => name,
261 }
262 }
263 }
264
265 fn assert_dirs_same<P: AsRef<Path>>(a: P, b: P) {
266 let mut wa = walkdir::WalkDir::new(a.as_ref()).into_iter();
267 let mut wb = walkdir::WalkDir::new(b.as_ref()).into_iter();
268
269 loop {
270 let o_na = wa.next();
271 let o_nb = wb.next();
272
273 if o_na.is_some() && o_nb.is_some() {
274 let r_na = o_na.unwrap();
275 let r_nb = o_nb.unwrap();
276
277 if r_na.is_ok() && r_nb.is_ok() {
278 let na = r_na.unwrap();
279 let nb = r_nb.unwrap();
280
281 assert_eq!(
282 na.path().strip_prefix(a.as_ref()),
283 nb.path().strip_prefix(b.as_ref())
284 );
285
286 assert_eq!(na.file_type(), nb.file_type());
287
288 }
290 } else if o_na.is_none() && o_nb.is_none() {
291 return;
292 } else {
293 assert!(false);
294 }
295 }
296 }
297
298 fn assert_we_match_the_real_thing(
299 dir: &DirMaker,
300 explicit_name: bool,
301 o_pre_state: Option<&DirMaker>,
302 ) {
303 let base_dir = tempdir::TempDir::new("copy_dir_test").unwrap();
304
305 let source_dir = base_dir.as_ref().join("source");
306 let our_dir = base_dir.as_ref().join("ours");
307 let their_dir = base_dir.as_ref().join("theirs");
308
309 fs::create_dir(&source_dir).unwrap();
310 fs::create_dir(&our_dir).unwrap();
311 fs::create_dir(&their_dir).unwrap();
312
313 dir.create(&source_dir).unwrap();
314 let source_path = source_dir.as_path().join(dir.name());
315
316 let (our_target, their_target) = if explicit_name {
317 (
318 our_dir.as_path().join(dir.name()),
319 their_dir.as_path().join(dir.name()),
320 )
321 } else {
322 (our_dir.clone(), their_dir.clone())
323 };
324
325 if let Some(pre_state) = o_pre_state {
326 pre_state.create(&our_dir).unwrap();
327 pre_state.create(&their_dir).unwrap();
328 }
329
330 let we_good = super::copy_dir(&source_path, &our_target).is_ok();
331
332 let their_status = Command::new("cp")
333 .arg("-r")
334 .arg(source_path.as_os_str())
335 .arg(their_target.as_os_str())
336 .status()
337 .unwrap();
338
339 assert_dirs_same(&their_dir, &our_dir);
343 }
344
345 #[test]
346 fn dir_maker_and_assert_dirs_same_baseline() {
347 let dir = Dir("foobar", vec![File("bar"), Dir("baz", Vec::new())]);
348
349 let base_dir = tempdir::TempDir::new("copy_dir_test").unwrap();
350
351 let a_path = base_dir.as_ref().join("a");
352 let b_path = base_dir.as_ref().join("b");
353
354 fs::create_dir(&a_path).unwrap();
355 fs::create_dir(&b_path).unwrap();
356
357 dir.create(&a_path).unwrap();
358 dir.create(&b_path).unwrap();
359
360 assert_dirs_same(&a_path, &b_path);
361 }
362
363 #[test]
364 #[should_panic]
365 fn assert_dirs_same_properly_fails() {
366 let dir = Dir("foobar", vec![File("bar"), Dir("baz", Vec::new())]);
367
368 let dir2 = Dir("foobar", vec![File("fobe"), File("beez")]);
369
370 let base_dir = tempdir::TempDir::new("copy_dir_test").unwrap();
371
372 let a_path = base_dir.as_ref().join("a");
373 let b_path = base_dir.as_ref().join("b");
374
375 fs::create_dir(&a_path).unwrap();
376 fs::create_dir(&b_path).unwrap();
377
378 dir.create(&a_path).unwrap();
379 dir2.create(&b_path).unwrap();
380
381 assert_dirs_same(&a_path, &b_path);
382 }
383}