diff options
-rw-r--r-- | src/lib.rs | 71 | ||||
-rw-r--r-- | src/tests.rs | 45 |
2 files changed, 99 insertions, 17 deletions
@@ -277,7 +277,9 @@ impl WalkDir { /// file path `root`. If `root` is a directory, then it is the first item /// yielded by the iterator. If `root` is a file, then it is the first /// and only item yielded by the iterator. If `root` is a symlink, then it - /// is always followed. + /// is always followed for the purposes of directory traversal. (A root + /// `DirEntry` still obeys its documentation with respect to symlinks and + /// the `follow_links` setting.) pub fn new<P: AsRef<Path>>(root: P) -> Self { WalkDir { opts: WalkDirOptions { @@ -681,7 +683,7 @@ impl Iterator for IntoIter { .map_err(|e| Error::from_path(0, start.clone(), e)); self.root_device = Some(itry!(result)); } - let dent = itry!(DirEntry::from_path(0, start, false)); + let mut dent = itry!(DirEntry::from_path(0, start, false)); if let Some(result) = self.handle_entry(dent) { return Some(result); } @@ -844,6 +846,20 @@ impl IntoIter { } else { itry!(self.push(&dent)); } + } else if dent.depth() == 0 && dent.file_type().is_symlink() { + // As a special case, if we are processing a root entry, then we + // always follow it even if it's a symlink and follow_links is + // false. We are careful to not let this change the semantics of + // the DirEntry however. Namely, the DirEntry should still respect + // the follow_links setting. When it's disabled, it should report + // itself as a symlink. When it's enabled, it should always report + // itself as the target. + let md = itry!(fs::metadata(dent.path()).map_err(|err| { + Error::from_path(dent.depth(), dent.path().to_path_buf(), err) + })); + if md.file_type().is_dir() { + itry!(self.push(&dent)); + } } if is_normal_dir && self.opts.contents_first { self.deferred_dirs.push(dent); @@ -1181,44 +1197,65 @@ impl DirEntry { } #[cfg(windows)] - fn from_path(depth: usize, pb: PathBuf, link: bool) -> Result<DirEntry> { - let md = fs::metadata(&pb).map_err(|err| { - Error::from_path(depth, pb.clone(), err) - })?; + fn from_path(depth: usize, pb: PathBuf, follow: bool) -> Result<DirEntry> { + let md = + if follow { + fs::metadata(&pb).map_err(|err| { + Error::from_path(depth, pb.clone(), err) + })? + } else { + fs::symlink_metadata(&pb).map_err(|err| { + Error::from_path(depth, pb.clone(), err) + })? + }; Ok(DirEntry { path: pb, ty: md.file_type(), - follow_link: link, + follow_link: follow, depth: depth, metadata: md, }) } #[cfg(unix)] - fn from_path(depth: usize, pb: PathBuf, link: bool) -> Result<DirEntry> { + fn from_path(depth: usize, pb: PathBuf, follow: bool) -> Result<DirEntry> { use std::os::unix::fs::MetadataExt; - let md = fs::metadata(&pb).map_err(|err| { - Error::from_path(depth, pb.clone(), err) - })?; + let md = + if follow { + fs::metadata(&pb).map_err(|err| { + Error::from_path(depth, pb.clone(), err) + })? + } else { + fs::symlink_metadata(&pb).map_err(|err| { + Error::from_path(depth, pb.clone(), err) + })? + }; Ok(DirEntry { path: pb, ty: md.file_type(), - follow_link: link, + follow_link: follow, depth: depth, ino: md.ino(), }) } #[cfg(not(any(unix, windows)))] - fn from_path(depth: usize, pb: PathBuf, link: bool) -> Result<DirEntry> { - let md = fs::metadata(&pb).map_err(|err| { - Error::from_path(depth, pb.clone(), err) - })?; + fn from_path(depth: usize, pb: PathBuf, follow: bool) -> Result<DirEntry> { + let md = + if follow { + fs::metadata(&pb).map_err(|err| { + Error::from_path(depth, pb.clone(), err) + })? + } else { + fs::symlink_metadata(&pb).map_err(|err| { + Error::from_path(depth, pb.clone(), err) + })? + }; Ok(DirEntry { path: pb, ty: md.file_type(), - follow_link: link, + follow_link: follow, depth: depth, }) } diff --git a/src/tests.rs b/src/tests.rs index 1fb7682..ddce795 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -531,6 +531,51 @@ fn first_path_not_symlink() { assert!(!dents[0].path_is_symlink()); } +// Like first_path_not_symlink, but checks that the first path is not reported +// as a symlink even when we are supposed to be following them. +#[test] +#[cfg(unix)] +fn first_path_not_symlink_follow() { + let exp = td("foo", vec![]); + let (tmp, _got) = dir_setup(&exp); + + let dents = WalkDir::new(tmp.path().join("foo")) + .follow_links(true) + .into_iter() + .collect::<Result<Vec<_>, _>>() + .unwrap(); + assert_eq!(1, dents.len()); + assert!(!dents[0].path_is_symlink()); +} + +// See: https://github.com/BurntSushi/walkdir/issues/115 +#[test] +#[cfg(unix)] +fn first_path_is_followed() { + let exp = td("foo", vec![ + td("a", vec![tf("a1"), tf("a2")]), + td("b", vec![tlf("../a/a1", "alink")]), + ]); + let (tmp, _got) = dir_setup(&exp); + + let dents = WalkDir::new(tmp.path().join("foo/b/alink")) + .into_iter() + .collect::<Result<Vec<_>, _>>() + .unwrap(); + assert_eq!(1, dents.len()); + assert!(dents[0].file_type().is_symlink()); + assert!(dents[0].metadata().unwrap().file_type().is_symlink()); + + let dents = WalkDir::new(tmp.path().join("foo/b/alink")) + .follow_links(true) + .into_iter() + .collect::<Result<Vec<_>, _>>() + .unwrap(); + assert_eq!(1, dents.len()); + assert!(!dents[0].file_type().is_symlink()); + assert!(!dents[0].metadata().unwrap().file_type().is_symlink()); +} + #[test] #[cfg(unix)] fn walk_dir_sym_detect_no_follow_no_loop() { |