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