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