Skip to content

Commit 18d6c79

Browse files
committed
bug: fix contents_first with root symlink
This fixes two incorrect behaviors when `contents_first` is on and the root path is a symlink: 1. The root path was returned first in the iterator instead of last. 2. Some subdirectories were returned out of order. The issue was that root symlinks were returned immediately rather than being pushed onto the `deferred_dirs` vec. That lead to `deferred_dirs` and `depth` being out of sync, which lead to deferred directories being processed one ascent too late. This also changes the internal handling of the `same_file_system` option slightly. Previously, if a directory was found to be on a different file system it would _not_ be pushed to the stack, but it _would_ be pushed to the deferred list. This did not matter because in those cases `handle_entry()` would return to `next()`, which would immediately pop the directory off the deferred list and return it. This ensures that the directory is returned immediately, and is not pushed to either the stack or the deferred list. (I think this makes the code clearer.) Fixes: #163
1 parent 61a185f commit 18d6c79

2 files changed

Lines changed: 59 additions & 11 deletions

File tree

src/lib.rs

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -821,16 +821,20 @@ impl IntoIter {
821821
if self.opts.follow_links && dent.file_type().is_symlink() {
822822
dent = itry!(self.follow(dent));
823823
}
824-
let is_normal_dir = !dent.file_type().is_symlink() && dent.is_dir();
825-
if is_normal_dir {
826-
if self.opts.same_file_system && dent.depth() > 0 {
827-
if itry!(self.is_same_file_system(&dent)) {
828-
itry!(self.push(&dent));
824+
let should_descend = if !dent.file_type().is_symlink() {
825+
if dent.is_dir() {
826+
if self.opts.same_file_system && dent.depth() > 0 {
827+
// Only descend into directories on the same filesystem as
828+
// the root directory.
829+
itry!(self.is_same_file_system(&dent))
830+
} else {
831+
// Don't check the filesystem; descend all directories.
832+
true
829833
}
830834
} else {
831-
itry!(self.push(&dent));
835+
false
832836
}
833-
} else if dent.depth() == 0 && dent.file_type().is_symlink() {
837+
} else if dent.depth() == 0 {
834838
// As a special case, if we are processing a root entry, then we
835839
// always follow it even if it's a symlink and follow_links is
836840
// false. We are careful to not let this change the semantics of
@@ -841,11 +845,14 @@ impl IntoIter {
841845
let md = itry!(fs::metadata(dent.path()).map_err(|err| {
842846
Error::from_path(dent.depth(), dent.path().to_path_buf(), err)
843847
}));
844-
if md.file_type().is_dir() {
845-
itry!(self.push(&dent));
846-
}
848+
md.file_type().is_dir()
849+
} else {
850+
false
851+
};
852+
if should_descend {
853+
itry!(self.push(&dent));
847854
}
848-
if is_normal_dir && self.opts.contents_first {
855+
if should_descend && self.opts.contents_first {
849856
self.deferred_dirs.push(dent);
850857
None
851858
} else if self.skippable() {

src/tests/recursive.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1021,3 +1021,44 @@ fn regression_skip_current_dir() {
10211021
wd.skip_current_dir();
10221022
wd.next();
10231023
}
1024+
1025+
// Tests that entries are returned in the correct order when contents_first is
1026+
// on and the root is a symlink to a directory.
1027+
//
1028+
// See: https://github.com/BurntSushi/walkdir/issues/163
1029+
#[test]
1030+
fn regression_sym_root_dir_nofollow_contents_first() {
1031+
let dir = Dir::tmp();
1032+
dir.mkdirp("dir/a/b");
1033+
dir.touch("dir/a-file");
1034+
dir.symlink_dir("dir", "link");
1035+
1036+
let wd = WalkDir::new(dir.join("link")).contents_first(true);
1037+
let r = dir.run_recursive(wd);
1038+
r.assert_no_errors();
1039+
1040+
let ents = r.ents();
1041+
assert_eq!(4, ents.len());
1042+
1043+
// link (the root) should always be the final entry.
1044+
let link = &ents[3];
1045+
assert_eq!(dir.join("link"), link.path());
1046+
assert!(link.path_is_symlink());
1047+
assert_eq!(dir.join("dir"), fs::read_link(link.path()).unwrap());
1048+
1049+
// link/a/b must be followed directly by link/a
1050+
let path_link_a_b = dir.join("link").join("a").join("b");
1051+
let path_link_a = dir.join("link").join("a");
1052+
let mut expect_link_a = false;
1053+
for ent in ents {
1054+
if expect_link_a {
1055+
assert_eq!(path_link_a, ent.path());
1056+
break;
1057+
} else if ent.path() == path_link_a_b {
1058+
expect_link_a = true;
1059+
}
1060+
}
1061+
if !expect_link_a {
1062+
panic!("Did not find link/a/b in results");
1063+
}
1064+
}

0 commit comments

Comments
 (0)