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::Context;
12use std::task::Poll;
13
14#[cfg(test)]
15use super::mocks::spawn_blocking;
16#[cfg(test)]
17use super::mocks::JoinHandle;
18#[cfg(not(test))]
19use crate::blocking::spawn_blocking;
20#[cfg(not(test))]
21use crate::blocking::JoinHandle;
22
23const 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`].
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
33pub 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"]
65pub struct ReadDir(State);
66
67#[derive(Debug)]
68enum 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
73impl ReadDir {
74 /// Returns the next entry in the directory stream.
75 ///
76 /// # Cancel safety
77 ///
78 /// This method is cancellation safe.
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.
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
128 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 = "aix",
144 target_os = "nto",
145 target_os = "vita",
146 )))]
147 file_type: std.file_type().ok(),
148 std: Arc::new(std),
149 }));
150
151 if !success {
152 break;
153 }
154 }
155
156 true
157 }
158}
159
160feature! {
161 #![unix]
162
163 use std::os::unix::fs::DirEntryExt;
164
165 impl DirEntry {
166 /// Returns the underlying `d_ino` field in the contained `dirent`
167 /// structure.
168 ///
169 /// # Examples
170 ///
171 /// ```
172 /// use tokio::fs;
173 ///
174 /// # #[tokio::main]
175 /// # async fn main() -> std::io::Result<()> {
176 /// let mut entries = fs::read_dir(".").await?;
177 /// while let Some(entry) = entries.next_entry().await? {
178 /// // Here, `entry` is a `DirEntry`.
179 /// println!("{:?}: {}", entry.file_name(), entry.ino());
180 /// }
181 /// # Ok(())
182 /// # }
183 /// ```
184 pub fn ino(&self) -> u64 {
185 self.as_inner().ino()
186 }
187 }
188}
189
190/// Entries returned by the [`ReadDir`] stream.
191///
192/// [`ReadDir`]: struct@ReadDir
193///
194/// This is a specialized version of [`std::fs::DirEntry`] for usage from the
195/// Tokio runtime.
196///
197/// An instance of `DirEntry` represents an entry inside of a directory on the
198/// filesystem. Each entry can be inspected via methods to learn about the full
199/// path or possibly other metadata through per-platform extension traits.
200#[derive(Debug)]
201pub struct DirEntry {
202 #[cfg(not(any(
203 target_os = "solaris",
204 target_os = "illumos",
205 target_os = "haiku",
206 target_os = "vxworks",
207 target_os = "aix",
208 target_os = "nto",
209 target_os = "vita",
210 )))]
211 file_type: Option<FileType>,
212 std: Arc<std::fs::DirEntry>,
213}
214
215impl DirEntry {
216 /// Returns the full path to the file that this entry represents.
217 ///
218 /// The full path is created by joining the original path to `read_dir`
219 /// with the filename of this entry.
220 ///
221 /// # Examples
222 ///
223 /// ```no_run
224 /// use tokio::fs;
225 ///
226 /// # async fn dox() -> std::io::Result<()> {
227 /// let mut entries = fs::read_dir(".").await?;
228 ///
229 /// while let Some(entry) = entries.next_entry().await? {
230 /// println!("{:?}", entry.path());
231 /// }
232 /// # Ok(())
233 /// # }
234 /// ```
235 ///
236 /// This prints output like:
237 ///
238 /// ```text
239 /// "./whatever.txt"
240 /// "./foo.html"
241 /// "./hello_world.rs"
242 /// ```
243 ///
244 /// The exact text, of course, depends on what files you have in `.`.
245 pub fn path(&self) -> PathBuf {
246 self.std.path()
247 }
248
249 /// Returns the bare file name of this directory entry without any other
250 /// leading path component.
251 ///
252 /// # Examples
253 ///
254 /// ```
255 /// use tokio::fs;
256 ///
257 /// # async fn dox() -> std::io::Result<()> {
258 /// let mut entries = fs::read_dir(".").await?;
259 ///
260 /// while let Some(entry) = entries.next_entry().await? {
261 /// println!("{:?}", entry.file_name());
262 /// }
263 /// # Ok(())
264 /// # }
265 /// ```
266 pub fn file_name(&self) -> OsString {
267 self.std.file_name()
268 }
269
270 /// Returns the metadata for the file that this entry points at.
271 ///
272 /// This function will not traverse symlinks if this entry points at a
273 /// symlink.
274 ///
275 /// # Platform-specific behavior
276 ///
277 /// On Windows this function is cheap to call (no extra system calls
278 /// needed), but on Unix platforms this function is the equivalent of
279 /// calling `symlink_metadata` on the path.
280 ///
281 /// # Examples
282 ///
283 /// ```
284 /// use tokio::fs;
285 ///
286 /// # async fn dox() -> std::io::Result<()> {
287 /// let mut entries = fs::read_dir(".").await?;
288 ///
289 /// while let Some(entry) = entries.next_entry().await? {
290 /// if let Ok(metadata) = entry.metadata().await {
291 /// // Now let's show our entry's permissions!
292 /// println!("{:?}: {:?}", entry.path(), metadata.permissions());
293 /// } else {
294 /// println!("Couldn't get file type for {:?}", entry.path());
295 /// }
296 /// }
297 /// # Ok(())
298 /// # }
299 /// ```
300 pub async fn metadata(&self) -> io::Result<Metadata> {
301 let std = self.std.clone();
302 asyncify(move || std.metadata()).await
303 }
304
305 /// Returns the file type for the file that this entry points at.
306 ///
307 /// This function will not traverse symlinks if this entry points at a
308 /// symlink.
309 ///
310 /// # Platform-specific behavior
311 ///
312 /// On Windows and most Unix platforms this function is free (no extra
313 /// system calls needed), but some Unix platforms may require the equivalent
314 /// call to `symlink_metadata` to learn about the target file type.
315 ///
316 /// # Examples
317 ///
318 /// ```
319 /// use tokio::fs;
320 ///
321 /// # async fn dox() -> std::io::Result<()> {
322 /// let mut entries = fs::read_dir(".").await?;
323 ///
324 /// while let Some(entry) = entries.next_entry().await? {
325 /// if let Ok(file_type) = entry.file_type().await {
326 /// // Now let's show our entry's file type!
327 /// println!("{:?}: {:?}", entry.path(), file_type);
328 /// } else {
329 /// println!("Couldn't get file type for {:?}", entry.path());
330 /// }
331 /// }
332 /// # Ok(())
333 /// # }
334 /// ```
335 pub async fn file_type(&self) -> io::Result<FileType> {
336 #[cfg(not(any(
337 target_os = "solaris",
338 target_os = "illumos",
339 target_os = "haiku",
340 target_os = "vxworks",
341 target_os = "aix",
342 target_os = "nto",
343 target_os = "vita",
344 )))]
345 if let Some(file_type) = self.file_type {
346 return Ok(file_type);
347 }
348
349 let std = self.std.clone();
350 asyncify(move || std.file_type()).await
351 }
352
353 /// Returns a reference to the underlying `std::fs::DirEntry`.
354 #[cfg(unix)]
355 pub(super) fn as_inner(&self) -> &std::fs::DirEntry {
356 &self.std
357 }
358}
359