Skip to content

Commit

Permalink
bug: fix contents_first with root symlink
Browse files Browse the repository at this point in the history
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
  • Loading branch information
danielparks committed Aug 14, 2022
1 parent abf3a15 commit 9fa3585
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 11 deletions.
29 changes: 18 additions & 11 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -821,16 +821,20 @@ impl IntoIter {
if self.opts.follow_links && dent.file_type().is_symlink() {
dent = itry!(self.follow(dent));
}
let is_normal_dir = !dent.file_type().is_symlink() && dent.is_dir();
if is_normal_dir {
if self.opts.same_file_system && dent.depth() > 0 {
if itry!(self.is_same_file_system(&dent)) {
itry!(self.push(&dent));
let should_descend = if !dent.file_type().is_symlink() {
if dent.is_dir() {
if self.opts.same_file_system && dent.depth() > 0 {
// Only descend into directories on the same filesystem as
// the root directory.
itry!(self.is_same_file_system(&dent))
} else {
// Don't check the filesystem; descend all directories.
true
}
} else {
itry!(self.push(&dent));
false
}
} else if dent.depth() == 0 && dent.file_type().is_symlink() {
} else if dent.depth() == 0 {
// 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
Expand All @@ -841,11 +845,14 @@ impl IntoIter {
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));
}
md.file_type().is_dir()
} else {
false
};
if should_descend {
itry!(self.push(&dent));
}
if is_normal_dir && self.opts.contents_first {
if should_descend && self.opts.contents_first {
self.deferred_dirs.push(dent);
None
} else if self.skippable() {
Expand Down
41 changes: 41 additions & 0 deletions src/tests/recursive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1021,3 +1021,44 @@ fn regression_skip_current_dir() {
wd.skip_current_dir();
wd.next();
}

// Tests that entries are returned in the correct order when contents_first is
// on and the root is a symlink to a directory.
//
// See: https://github.com/BurntSushi/walkdir/issues/163
#[test]
fn regression_sym_root_dir_nofollow_contents_first() {
let dir = Dir::tmp();
dir.mkdirp("dir/a/b");
dir.touch("dir/a-file");
dir.symlink_dir("dir", "link");

let wd = WalkDir::new(dir.join("link")).contents_first(true);
let r = dir.run_recursive(wd);
r.assert_no_errors();

let ents = r.ents();
assert_eq!(4, ents.len());

// link (the root) should always be the final entry.
let link = &ents[3];
assert_eq!(dir.join("link"), link.path());
assert!(link.path_is_symlink());
assert_eq!(dir.join("dir"), fs::read_link(link.path()).unwrap());

// link/a/b must be followed directly by link/a
let path_link_a_b = dir.join("link").join("a").join("b");
let path_link_a = dir.join("link").join("a");
let mut expect_link_a = false;
for ent in ents {
if expect_link_a {
assert_eq!(path_link_a, ent.path());
break;
} else if ent.path() == path_link_a_b {
expect_link_a = true;
}
}
if !expect_link_a {
panic!("Did not find link/a/b in results");
}
}

0 comments on commit 9fa3585

Please sign in to comment.