From dcc527d8326fae4272b66bb55f433a302a8cad6f Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 5 Sep 2023 16:01:38 +0200 Subject: [PATCH] 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 --- src/lib.rs | 27 +++++++++++++++- src/tests/recursive.rs | 71 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 96 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 4d41515..edf702e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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 diff --git a/src/tests/recursive.rs b/src/tests/recursive.rs index 4119f46..e415b91 100644 --- a/src/tests/recursive.rs +++ b/src/tests/recursive.rs @@ -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");