Skip to content

Commit

Permalink
api: add follow_root_links() option to WalkDir
Browse files Browse the repository at this point in the history
With it it's possible to control whether symlinks in the traversal
root are followed, while defaulting to 'true' like before, or if
they are handled like ordinary links.

Ref rust-lang/cargo#11634

Fixes #175
  • Loading branch information
Byron committed Sep 5, 2023
1 parent 61a185f commit dcc527d
Show file tree
Hide file tree
Showing 2 changed files with 96 additions and 2 deletions.
27 changes: 26 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ pub struct WalkDir {

struct WalkDirOptions {
follow_links: bool,
follow_root_links: bool,
max_open: usize,
min_depth: usize,
max_depth: usize,
Expand Down Expand Up @@ -265,6 +266,7 @@ impl fmt::Debug for WalkDirOptions {
};
f.debug_struct("WalkDirOptions")
.field("follow_links", &self.follow_links)
.field("follow_root_link", &self.follow_root_links)
.field("max_open", &self.max_open)
.field("min_depth", &self.min_depth)
.field("max_depth", &self.max_depth)
Expand All @@ -287,6 +289,7 @@ impl WalkDir {
WalkDir {
opts: WalkDirOptions {
follow_links: false,
follow_root_links: true,
max_open: 10,
min_depth: 0,
max_depth: ::std::usize::MAX,
Expand Down Expand Up @@ -344,6 +347,25 @@ impl WalkDir {
self
}

/// Follow symbolic links if these are the root of the traversal.
/// By default, this is enabled.
///
/// When `yes` is `true`, symbolic links on root paths are followed
/// which is effective if the symbolic link points to a directory.
/// If a symbolic link is broken or is involved in a loop, an error is yielded
/// as the first entry of the traversal.
///
/// When enabled, the yielded [`DirEntry`] values represent the target of
/// the link while the path corresponds to the link. See the [`DirEntry`]
/// type for more details, and all future entries will be contained within
/// the resolved directory behind the symbolic link of the root path.
///
/// [`DirEntry`]: struct.DirEntry.html
pub fn follow_root_links(mut self, yes: bool) -> Self {
self.opts.follow_root_links = yes;
self
}

/// Set the maximum number of simultaneously open file descriptors used
/// by the iterator.
///
Expand Down Expand Up @@ -830,7 +852,10 @@ impl IntoIter {
} else {
itry!(self.push(&dent));
}
} else if dent.depth() == 0 && dent.file_type().is_symlink() {
} else if dent.depth() == 0
&& dent.file_type().is_symlink()
&& self.opts.follow_root_links
{
// 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 Down
71 changes: 70 additions & 1 deletion src/tests/recursive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,76 @@ fn sym_root_file_follow() {
}

#[test]
fn sym_root_dir_nofollow() {
fn broken_sym_root_dir_nofollow_and_root_nofollow() {
let dir = Dir::tmp();
dir.symlink_dir("broken", "a-link");

let wd = WalkDir::new(dir.join("a-link"))
.follow_links(false)
.follow_root_links(false);
let r = dir.run_recursive(wd);
let ents = r.sorted_ents();
assert_eq!(ents.len(), 1);
let link = &ents[0];
assert_eq!(dir.join("a-link"), link.path());
assert!(link.path_is_symlink());
}

#[test]
fn broken_sym_root_dir_follow_and_root_nofollow() {
let dir = Dir::tmp();
dir.symlink_dir("broken", "a-link");

let wd = WalkDir::new(dir.join("a-link"))
.follow_links(true)
.follow_root_links(false);
let r = dir.run_recursive(wd);
assert!(r.sorted_ents().is_empty());
assert_eq!(
r.errs().len(),
1,
"broken symlink cannot be traversed - they are followed if symlinks are followed"
);
}

#[test]
fn broken_sym_root_dir_root_is_always_followed() {
let dir = Dir::tmp();
dir.symlink_dir("broken", "a-link");

for follow_symlinks in &[true, false] {
let wd =
WalkDir::new(dir.join("a-link")).follow_links(*follow_symlinks);
let r = dir.run_recursive(wd);
assert!(r.sorted_ents().is_empty());
assert_eq!(
r.errs().len(),
1,
"broken symlink in roots cannot be traversed, they are always followed"
);
}
}

#[test]
fn sym_root_dir_nofollow_root_nofollow() {
let dir = Dir::tmp();
dir.mkdirp("a");
dir.symlink_dir("a", "a-link");
dir.touch("a/zzz");

let wd = WalkDir::new(dir.join("a-link")).follow_root_links(false);
let r = dir.run_recursive(wd);
r.assert_no_errors();

let ents = r.sorted_ents();
assert_eq!(1, ents.len());
let link = &ents[0];
assert_eq!(dir.join("a-link"), link.path());
assert_eq!(0, link.depth());
}

#[test]
fn sym_root_dir_nofollow_root_follow() {
let dir = Dir::tmp();
dir.mkdirp("a");
dir.symlink_dir("a", "a-link");
Expand Down

0 comments on commit dcc527d

Please sign in to comment.