1 use crate::fs::asyncify;
2 
3 use std::collections::VecDeque;
4 use std::ffi::OsString;
5 use std::fs::{FileType, Metadata};
6 use std::future::Future;
7 use std::io;
8 use std::path::{Path, PathBuf};
9 use std::pin::Pin;
10 use std::sync::Arc;
11 use std::task::{ready, Context, Poll};
12 
13 #[cfg(test)]
14 use super::mocks::spawn_blocking;
15 #[cfg(test)]
16 use super::mocks::JoinHandle;
17 #[cfg(not(test))]
18 use crate::blocking::spawn_blocking;
19 #[cfg(not(test))]
20 use crate::blocking::JoinHandle;
21 
22 const 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
read_dir(path: impl AsRef<Path>) -> io::Result<ReadDir>32 pub 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"]
64 pub struct ReadDir(State);
65 
66 #[derive(Debug)]
67 enum 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 
72 impl ReadDir {
73     /// Returns the next entry in the directory stream.
74     ///
75     /// # Cancel safety
76     ///
77     /// This method is cancellation safe.
next_entry(&mut self) -> io::Result<Option<DirEntry>>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.
poll_next_entry(&mut self, cx: &mut Context<'_>) -> Poll<io::Result<Option<DirEntry>>>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 
next_chunk(buf: &mut VecDeque<io::Result<DirEntry>>, std: &mut std::fs::ReadDir) -> bool127     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 
159 feature! {
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)]
200 pub 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 
214 impl 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 `.`.
path(&self) -> PathBuf244     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     /// ```
file_name(&self) -> OsString265     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     /// ```
metadata(&self) -> io::Result<Metadata>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     /// ```
file_type(&self) -> io::Result<FileType>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)]
as_inner(&self) -> &std::fs::DirEntry354     pub(super) fn as_inner(&self) -> &std::fs::DirEntry {
355         &self.std
356     }
357 }
358