tokio/fs/read_dir.rs
1use crate::fs::asyncify;
2
3use std::collections::VecDeque;
4use std::ffi::OsString;
5use std::fs::{FileType, Metadata};
6use std::future::Future;
7use std::io;
8use std::path::{Path, PathBuf};
9use std::pin::Pin;
10use std::sync::Arc;
11use std::task::{ready, Context, Poll};
12
13#[cfg(test)]
14use super::mocks::spawn_blocking;
15#[cfg(test)]
16use super::mocks::JoinHandle;
17#[cfg(not(test))]
18use crate::blocking::spawn_blocking;
19#[cfg(not(test))]
20use crate::blocking::JoinHandle;
21
22const CHUNK_SIZE: usize = 32;
23
24/// Returns a stream over the entries within a directory.
25///
26/// This is an async version of [`std::fs::read_dir`].
27///
28/// This operation is implemented by running the equivalent blocking
29/// operation on a separate thread pool using [`spawn_blocking`].
30///
31/// [`spawn_blocking`]: crate::task::spawn_blocking
32pub async fn read_dir(path: impl AsRef<Path>) -> io::Result<ReadDir> {
33 let path = path.as_ref().to_owned();
34 asyncify(|| -> io::Result<ReadDir> {
35 let mut std = std::fs::read_dir(path)?;
36 let mut buf = VecDeque::with_capacity(CHUNK_SIZE);
37 let remain = ReadDir::next_chunk(&mut buf, &mut std);
38
39 Ok(ReadDir(State::Idle(Some((buf, std, remain)))))
40 })
41 .await
42}
43
44/// Reads the entries in a directory.
45///
46/// This struct is returned from the [`read_dir`] function of this module and
47/// will yield instances of [`DirEntry`]. Through a [`DirEntry`] information
48/// like the entry's path and possibly other metadata can be learned.
49///
50/// A `ReadDir` can be turned into a `Stream` with [`ReadDirStream`].
51///
52/// [`ReadDirStream`]: https://docs.rs/tokio-stream/0.1/tokio_stream/wrappers/struct.ReadDirStream.html
53///
54/// # Errors
55///
56/// This stream will return an [`Err`] if there's some sort of intermittent
57/// IO error during iteration.
58///
59/// [`read_dir`]: read_dir
60/// [`DirEntry`]: DirEntry
61/// [`Err`]: std::result::Result::Err
62#[derive(Debug)]
63#[must_use = "streams do nothing unless polled"]
64pub struct ReadDir(State);
65
66#[derive(Debug)]
67enum State {
68 Idle(Option<(VecDeque<io::Result<DirEntry>>, std::fs::ReadDir, bool)>),
69 Pending(JoinHandle<(VecDeque<io::Result<DirEntry>>, std::fs::ReadDir, bool)>),
70}
71
72impl ReadDir {
73 /// Returns the next entry in the directory stream.
74 ///
75 /// # Cancel safety
76 ///
77 /// This method is cancellation safe.
78 pub async fn next_entry(&mut self) -> io::Result<Option<DirEntry>> {
79 use std::future::poll_fn;
80 poll_fn(|cx| self.poll_next_entry(cx)).await
81 }
82
83 /// Polls for the next directory entry in the stream.
84 ///
85 /// This method returns:
86 ///
87 /// * `Poll::Pending` if the next directory entry is not yet available.
88 /// * `Poll::Ready(Ok(Some(entry)))` if the next directory entry is available.
89 /// * `Poll::Ready(Ok(None))` if there are no more directory entries in this
90 /// stream.
91 /// * `Poll::Ready(Err(err))` if an IO error occurred while reading the next
92 /// directory entry.
93 ///
94 /// When the method returns `Poll::Pending`, the `Waker` in the provided
95 /// `Context` is scheduled to receive a wakeup when the next directory entry
96 /// becomes available on the underlying IO resource.
97 ///
98 /// Note that on multiple calls to `poll_next_entry`, only the `Waker` from
99 /// the `Context` passed to the most recent call is scheduled to receive a
100 /// wakeup.
101 pub fn poll_next_entry(&mut self, cx: &mut Context<'_>) -> Poll<io::Result<Option<DirEntry>>> {
102 loop {
103 match self.0 {
104 State::Idle(ref mut data) => {
105 let (buf, _, ref remain) = data.as_mut().unwrap();
106
107 if let Some(ent) = buf.pop_front() {
108 return Poll::Ready(ent.map(Some));
109 } else if !remain {
110 return Poll::Ready(Ok(None));
111 }
112
113 let (mut buf, mut std, _) = data.take().unwrap();
114
115 self.0 = State::Pending(spawn_blocking(move || {
116 let remain = ReadDir::next_chunk(&mut buf, &mut std);
117 (buf, std, remain)
118 }));
119 }
120 State::Pending(ref mut rx) => {
121 self.0 = State::Idle(Some(ready!(Pin::new(rx).poll(cx))?));
122 }
123 }
124 }
125 }
126
127 fn next_chunk(buf: &mut VecDeque<io::Result<DirEntry>>, std: &mut std::fs::ReadDir) -> bool {
128 for _ in 0..CHUNK_SIZE {
129 let ret = match std.next() {
130 Some(ret) => ret,
131 None => return false,
132 };
133
134 let success = ret.is_ok();
135
136 buf.push_back(ret.map(|std| DirEntry {
137 #[cfg(not(any(
138 target_os = "solaris",
139 target_os = "illumos",
140 target_os = "haiku",
141 target_os = "vxworks",
142 target_os = "aix",
143 target_os = "nto",
144 target_os = "vita",
145 )))]
146 file_type: std.file_type().ok(),
147 std: Arc::new(std),
148 }));
149
150 if !success {
151 break;
152 }
153 }
154
155 true
156 }
157}
158
159feature! {
160 #![unix]
161
162 use std::os::unix::fs::DirEntryExt;
163
164 impl DirEntry {
165 /// Returns the underlying `d_ino` field in the contained `dirent`
166 /// structure.
167 ///
168 /// # Examples
169 ///
170 /// ```
171 /// use tokio::fs;
172 ///
173 /// # #[tokio::main]
174 /// # async fn main() -> std::io::Result<()> {
175 /// let mut entries = fs::read_dir(".").await?;
176 /// while let Some(entry) = entries.next_entry().await? {
177 /// // Here, `entry` is a `DirEntry`.
178 /// println!("{:?}: {}", entry.file_name(), entry.ino());
179 /// }
180 /// # Ok(())
181 /// # }
182 /// ```
183 pub fn ino(&self) -> u64 {
184 self.as_inner().ino()
185 }
186 }
187}
188
189/// Entries returned by the [`ReadDir`] stream.
190///
191/// [`ReadDir`]: struct@ReadDir
192///
193/// This is a specialized version of [`std::fs::DirEntry`] for usage from the
194/// Tokio runtime.
195///
196/// An instance of `DirEntry` represents an entry inside of a directory on the
197/// filesystem. Each entry can be inspected via methods to learn about the full
198/// path or possibly other metadata through per-platform extension traits.
199#[derive(Debug)]
200pub struct DirEntry {
201 #[cfg(not(any(
202 target_os = "solaris",
203 target_os = "illumos",
204 target_os = "haiku",
205 target_os = "vxworks",
206 target_os = "aix",
207 target_os = "nto",
208 target_os = "vita",
209 )))]
210 file_type: Option<FileType>,
211 std: Arc<std::fs::DirEntry>,
212}
213
214impl DirEntry {
215 /// Returns the full path to the file that this entry represents.
216 ///
217 /// The full path is created by joining the original path to `read_dir`
218 /// with the filename of this entry.
219 ///
220 /// # Examples
221 ///
222 /// ```no_run
223 /// use tokio::fs;
224 ///
225 /// # async fn dox() -> std::io::Result<()> {
226 /// let mut entries = fs::read_dir(".").await?;
227 ///
228 /// while let Some(entry) = entries.next_entry().await? {
229 /// println!("{:?}", entry.path());
230 /// }
231 /// # Ok(())
232 /// # }
233 /// ```
234 ///
235 /// This prints output like:
236 ///
237 /// ```text
238 /// "./whatever.txt"
239 /// "./foo.html"
240 /// "./hello_world.rs"
241 /// ```
242 ///
243 /// The exact text, of course, depends on what files you have in `.`.
244 pub fn path(&self) -> PathBuf {
245 self.std.path()
246 }
247
248 /// Returns the bare file name of this directory entry without any other
249 /// leading path component.
250 ///
251 /// # Examples
252 ///
253 /// ```
254 /// use tokio::fs;
255 ///
256 /// # async fn dox() -> std::io::Result<()> {
257 /// let mut entries = fs::read_dir(".").await?;
258 ///
259 /// while let Some(entry) = entries.next_entry().await? {
260 /// println!("{:?}", entry.file_name());
261 /// }
262 /// # Ok(())
263 /// # }
264 /// ```
265 pub fn file_name(&self) -> OsString {
266 self.std.file_name()
267 }
268
269 /// Returns the metadata for the file that this entry points at.
270 ///
271 /// This function will not traverse symlinks if this entry points at a
272 /// symlink.
273 ///
274 /// # Platform-specific behavior
275 ///
276 /// On Windows this function is cheap to call (no extra system calls
277 /// needed), but on Unix platforms this function is the equivalent of
278 /// calling `symlink_metadata` on the path.
279 ///
280 /// # Examples
281 ///
282 /// ```
283 /// use tokio::fs;
284 ///
285 /// # async fn dox() -> std::io::Result<()> {
286 /// let mut entries = fs::read_dir(".").await?;
287 ///
288 /// while let Some(entry) = entries.next_entry().await? {
289 /// if let Ok(metadata) = entry.metadata().await {
290 /// // Now let's show our entry's permissions!
291 /// println!("{:?}: {:?}", entry.path(), metadata.permissions());
292 /// } else {
293 /// println!("Couldn't get file type for {:?}", entry.path());
294 /// }
295 /// }
296 /// # Ok(())
297 /// # }
298 /// ```
299 pub async fn metadata(&self) -> io::Result<Metadata> {
300 let std = self.std.clone();
301 asyncify(move || std.metadata()).await
302 }
303
304 /// Returns the file type for the file that this entry points at.
305 ///
306 /// This function will not traverse symlinks if this entry points at a
307 /// symlink.
308 ///
309 /// # Platform-specific behavior
310 ///
311 /// On Windows and most Unix platforms this function is free (no extra
312 /// system calls needed), but some Unix platforms may require the equivalent
313 /// call to `symlink_metadata` to learn about the target file type.
314 ///
315 /// # Examples
316 ///
317 /// ```
318 /// use tokio::fs;
319 ///
320 /// # async fn dox() -> std::io::Result<()> {
321 /// let mut entries = fs::read_dir(".").await?;
322 ///
323 /// while let Some(entry) = entries.next_entry().await? {
324 /// if let Ok(file_type) = entry.file_type().await {
325 /// // Now let's show our entry's file type!
326 /// println!("{:?}: {:?}", entry.path(), file_type);
327 /// } else {
328 /// println!("Couldn't get file type for {:?}", entry.path());
329 /// }
330 /// }
331 /// # Ok(())
332 /// # }
333 /// ```
334 pub async fn file_type(&self) -> io::Result<FileType> {
335 #[cfg(not(any(
336 target_os = "solaris",
337 target_os = "illumos",
338 target_os = "haiku",
339 target_os = "vxworks",
340 target_os = "aix",
341 target_os = "nto",
342 target_os = "vita",
343 )))]
344 if let Some(file_type) = self.file_type {
345 return Ok(file_type);
346 }
347
348 let std = self.std.clone();
349 asyncify(move || std.file_type()).await
350 }
351
352 /// Returns a reference to the underlying `std::fs::DirEntry`.
353 #[cfg(unix)]
354 pub(super) fn as_inner(&self) -> &std::fs::DirEntry {
355 &self.std
356 }
357}