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