1use crate::api::{DirEntry, ReadDirOptions, TraversalCompletion, WalkOptions};
3use crate::{VfError, VfResult};
4use std::path::Path;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum WalkControl {
9 Continue,
10 SkipSubtree,
11 Stop,
12}
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum WalkEventKind {
16 Enter,
17 Entry,
18 Leave,
19}
20#[derive(Debug, Clone)]
22pub struct WalkEvent {
23 pub kind: WalkEventKind,
24 pub entry: DirEntry,
25 pub depth: usize,
26}
27
28#[doc(hidden)]
32pub fn walk_events(
33 root: DirEntry,
34 options: WalkOptions,
35 sort_by_name: bool,
36 mut read_dir: impl FnMut(&Path, ReadDirOptions) -> VfResult<Vec<DirEntry>>,
37 mut callback: impl FnMut(&WalkEvent) -> VfResult<WalkControl>,
38) -> VfResult<TraversalCompletion> {
39 let mut count = 1usize;
40 let mut bytes = root.path().as_os_str().len();
41 if count > options.entry_limit() || bytes > options.path_byte_limit() {
42 return Err(VfError::client(0, libc::EFBIG as u32));
43 }
44 let kind = if root.attrs().is_dir() {
45 WalkEventKind::Enter
46 } else {
47 WalkEventKind::Entry
48 };
49 let mut pending = vec![WalkEvent {
50 kind,
51 entry: root,
52 depth: 0,
53 }];
54 while let Some(event) = pending.pop() {
55 match callback(&event)? {
56 WalkControl::Stop => return Ok(TraversalCompletion::Stopped),
57 decision if event.kind == WalkEventKind::Enter => {
58 pending.push(WalkEvent {
59 kind: WalkEventKind::Leave,
60 ..event.clone()
61 });
62 if decision == WalkControl::SkipSubtree {
63 continue;
64 }
65 if event.depth >= options.depth_limit() && options.truncates_at_depth_limit() {
66 continue;
67 }
68 let remaining = ReadDirOptions::new()
69 .max_entries(options.entry_limit().saturating_sub(count))
70 .max_path_bytes(options.path_byte_limit().saturating_sub(bytes));
71 let mut entries = read_dir(event.entry.path(), remaining)?;
72 for entry in &entries {
73 count = count
74 .checked_add(1)
75 .ok_or_else(|| VfError::client(0, libc::EFBIG as u32))?;
76 bytes = bytes
77 .checked_add(entry.path().as_os_str().len())
78 .ok_or_else(|| VfError::client(0, libc::EFBIG as u32))?;
79 if count > options.entry_limit()
80 || bytes > options.path_byte_limit()
81 || event.depth >= options.depth_limit()
82 {
83 return Err(VfError::client(0, libc::EFBIG as u32)
84 .with_context("walk_events", entry.path()));
85 }
86 }
87 if sort_by_name {
88 entries.sort_by(|a, b| a.file_name().cmp(&b.file_name()));
89 }
90 for entry in entries.into_iter().rev() {
91 let kind = if entry.attrs().is_dir() {
92 WalkEventKind::Enter
93 } else {
94 WalkEventKind::Entry
95 };
96 pending.push(WalkEvent {
97 kind,
98 entry,
99 depth: event.depth + 1,
100 });
101 }
102 }
103 _ => {}
104 }
105 }
106 Ok(TraversalCompletion::Complete)
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112 use crate::{AttrMask, VfAttrs, VfFile, VfType};
113 fn entry(path: &str, dir: bool) -> DirEntry {
114 DirEntry::new(
115 path.into(),
116 crate::metadata_from_attrs(VfAttrs {
117 file: VfFile::from_path(path),
118 masks: AttrMask::MODE,
119 ftype: if dir {
120 VfType::Directory
121 } else {
122 VfType::Regular
123 },
124 ..Default::default()
125 }),
126 )
127 }
128 #[test]
129 fn prune_prevents_io_and_events_have_depth_first_order() {
130 let mut events = Vec::new();
131 walk_events(
132 entry("/", true),
133 WalkOptions::default(),
134 true,
135 |path, _| {
136 assert_eq!(path, Path::new("/"), "pruned directory must not be read");
137 Ok(vec![entry("/z", false), entry("/a", true)])
138 },
139 |event| {
140 events.push((event.kind, event.entry.path().to_path_buf(), event.depth));
141 Ok(if event.entry.path() == Path::new("/a") {
142 WalkControl::SkipSubtree
143 } else {
144 WalkControl::Continue
145 })
146 },
147 )
148 .unwrap();
149 assert_eq!(
150 events
151 .iter()
152 .map(|e| (e.0, e.1.as_path(), e.2))
153 .collect::<Vec<_>>(),
154 vec![
155 (WalkEventKind::Enter, Path::new("/"), 0),
156 (WalkEventKind::Enter, Path::new("/a"), 1),
157 (WalkEventKind::Leave, Path::new("/a"), 1),
158 (WalkEventKind::Entry, Path::new("/z"), 1),
159 (WalkEventKind::Leave, Path::new("/"), 0)
160 ]
161 );
162 }
163 #[test]
164 fn stop_error_and_limits_do_not_read_descendants() {
165 assert_eq!(
166 walk_events(
167 entry("/", true),
168 WalkOptions::default(),
169 false,
170 |_, _| panic!("stopped"),
171 |_| Ok(WalkControl::Stop)
172 )
173 .unwrap(),
174 TraversalCompletion::Stopped
175 );
176 assert!(
177 walk_events(
178 entry("/", true),
179 WalkOptions::default(),
180 false,
181 |_, _| panic!("callback failed"),
182 |_| Err(VfError::client(0, libc::EIO as u32))
183 )
184 .is_err()
185 );
186 for options in [
187 WalkOptions::new().max_entries(1),
188 WalkOptions::new().max_path_bytes(1),
189 WalkOptions::new().max_depth(0),
190 ] {
191 assert_eq!(
192 walk_events(
193 entry("/", true),
194 options,
195 false,
196 |path, _| {
197 assert_eq!(path, Path::new("/"));
198 Ok(vec![entry("/a", true)])
199 },
200 |_| Ok(WalkControl::Continue)
201 )
202 .unwrap_err()
203 .err_no(),
204 libc::EFBIG as u32
205 );
206 }
207 }
208}