api: add follow_root_links() option to WalkDir

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 https://github.com/rust-lang/cargo/pull/11634

Fixes #175
This commit is contained in:
Sebastian Thiel 2023-09-05 16:01:38 +02:00 committed by GitHub
parent 61a185fe49
commit dcc527d832
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 96 additions and 2 deletions

View File

@ -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,
@ -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)
@ -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,
@ -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.
///
@ -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

View File

@ -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");