madsim_real_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::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}