1use std::{
2 collections::HashSet,
3 os::unix::ffi::OsStrExt,
4 path::{Path, PathBuf},
5};
6
7#[tracing::instrument]
8pub fn find_dirs(
9 root: &Path,
10 target_name: &str,
11 follow: bool,
12 ignore: &HashSet<PathBuf>,
13) -> impl Iterator<Item = PathBuf> {
14 let root = root.to_path_buf();
15 Dirs {
16 ignore: ignore.to_owned(),
17 follow,
18 target_name: target_name.to_string(),
19 frontier: vec![root],
20 }
21}
22
23#[derive(Debug)]
24struct Dirs {
25 target_name: String,
26 follow: bool,
27 ignore: HashSet<PathBuf>,
28 frontier: Vec<PathBuf>,
29}
30
31impl Iterator for Dirs {
32 type Item = PathBuf;
33
34 fn next(&mut self) -> Option<PathBuf> {
35 use std::fs;
38
39 while let Some(path) = self.frontier.pop() {
40 if self.ignore.contains(&path) {
41 continue;
42 }
43 if !&path.try_exists().is_ok_and(|exists| exists) {
44 continue;
45 }
46 match fs::symlink_metadata(&path) {
47 Ok(meta) if meta.is_symlink() => {
48 if !self.follow {
49 continue;
50 }
51 match fs::read_link(&path) {
52 Ok(path1) => {
53 self.frontier.push(path1);
54 }
55 Err(error) => {
56 tracing::error!(
57 ?path,
58 ?error,
59 "Failed to read link."
60 );
61 }
62 }
63 }
64 Ok(meta) if meta.is_dir() => {
65 if path.file_name().is_some_and(|name| {
66 name.as_bytes() == self.target_name.as_bytes()
67 }) {
68 return Some(path);
69 }
70 match fs::read_dir(&path) {
71 Err(error) => {
72 tracing::error!(
73 ?path,
74 ?error,
75 "Failed to read directory",
76 );
77 }
78 Ok(entries) => {
79 for entry_result in entries {
80 match entry_result {
81 Ok(entry) => {
82 self.frontier.push(entry.path());
83 }
84 Err(error) => {
85 tracing::error!(
86 from = ?path, ?error,
87 "Failed to read an entry",
88 );
89 }
90 }
91 }
92 }
93 }
94 }
95 Ok(_) => {}
96 Err(error) => {
97 tracing::error!(
98 from = ?path, ?error,
99 "Failed to read metadata",
100 );
101 }
102 }
103 }
104 None
105 }
106}