1b8a62b91Sopenharmony_ci//! `RawDir` and `RawDirEntry`.
2b8a62b91Sopenharmony_ci
3b8a62b91Sopenharmony_ciuse core::fmt;
4b8a62b91Sopenharmony_ciuse core::mem::{align_of, MaybeUninit};
5b8a62b91Sopenharmony_ciuse linux_raw_sys::general::linux_dirent64;
6b8a62b91Sopenharmony_ci
7b8a62b91Sopenharmony_ciuse crate::backend::fs::syscalls::getdents_uninit;
8b8a62b91Sopenharmony_ciuse crate::fd::AsFd;
9b8a62b91Sopenharmony_ciuse crate::ffi::CStr;
10b8a62b91Sopenharmony_ciuse crate::fs::FileType;
11b8a62b91Sopenharmony_ciuse crate::io;
12b8a62b91Sopenharmony_ci
13b8a62b91Sopenharmony_ci/// A directory iterator implemented with getdents.
14b8a62b91Sopenharmony_ci///
15b8a62b91Sopenharmony_ci/// Note: This implementation does not handle growing the buffer. If this functionality is
16b8a62b91Sopenharmony_ci/// necessary, you'll need to drop the current iterator, resize the buffer, and then
17b8a62b91Sopenharmony_ci/// re-create the iterator. The iterator is guaranteed to continue where it left off provided
18b8a62b91Sopenharmony_ci/// the file descriptor isn't changed. See the example in [`RawDir::new`].
19b8a62b91Sopenharmony_cipub struct RawDir<'buf, Fd: AsFd> {
20b8a62b91Sopenharmony_ci    fd: Fd,
21b8a62b91Sopenharmony_ci    buf: &'buf mut [MaybeUninit<u8>],
22b8a62b91Sopenharmony_ci    initialized: usize,
23b8a62b91Sopenharmony_ci    offset: usize,
24b8a62b91Sopenharmony_ci}
25b8a62b91Sopenharmony_ci
26b8a62b91Sopenharmony_ciimpl<'buf, Fd: AsFd> RawDir<'buf, Fd> {
27b8a62b91Sopenharmony_ci    /// Create a new iterator from the given file descriptor and buffer.
28b8a62b91Sopenharmony_ci    ///
29b8a62b91Sopenharmony_ci    /// Note: the buffer size may be trimmed to accommodate alignment requirements.
30b8a62b91Sopenharmony_ci    ///
31b8a62b91Sopenharmony_ci    /// # Examples
32b8a62b91Sopenharmony_ci    ///
33b8a62b91Sopenharmony_ci    /// ## Simple but non-portable
34b8a62b91Sopenharmony_ci    ///
35b8a62b91Sopenharmony_ci    /// These examples are non-portable, because file systems may not have a maximum file name
36b8a62b91Sopenharmony_ci    /// length. If you can make assumptions that bound this length, then these examples may suffice.
37b8a62b91Sopenharmony_ci    ///
38b8a62b91Sopenharmony_ci    /// Using the heap:
39b8a62b91Sopenharmony_ci    ///
40b8a62b91Sopenharmony_ci    /// ```notrust
41b8a62b91Sopenharmony_ci    /// # // The `notrust` above can be removed when we can depend on Rust 1.60.
42b8a62b91Sopenharmony_ci    /// # use std::mem::MaybeUninit;
43b8a62b91Sopenharmony_ci    /// # use rustix::fs::{cwd, Mode, OFlags, openat, RawDir};
44b8a62b91Sopenharmony_ci    ///
45b8a62b91Sopenharmony_ci    /// let fd = openat(cwd(), ".", OFlags::RDONLY | OFlags::DIRECTORY, Mode::empty()).unwrap();
46b8a62b91Sopenharmony_ci    ///
47b8a62b91Sopenharmony_ci    /// let mut buf = Vec::with_capacity(8192);
48b8a62b91Sopenharmony_ci    /// let mut iter = RawDir::new(fd, buf.spare_capacity_mut());
49b8a62b91Sopenharmony_ci    /// while let Some(entry) = iter.next() {
50b8a62b91Sopenharmony_ci    ///     let entry = entry.unwrap();
51b8a62b91Sopenharmony_ci    ///     dbg!(&entry);
52b8a62b91Sopenharmony_ci    /// }
53b8a62b91Sopenharmony_ci    /// ```
54b8a62b91Sopenharmony_ci    ///
55b8a62b91Sopenharmony_ci    /// Using the stack:
56b8a62b91Sopenharmony_ci    ///
57b8a62b91Sopenharmony_ci    /// ```
58b8a62b91Sopenharmony_ci    /// # use std::mem::MaybeUninit;
59b8a62b91Sopenharmony_ci    /// # use rustix::fs::{cwd, Mode, OFlags, openat, RawDir};
60b8a62b91Sopenharmony_ci    ///
61b8a62b91Sopenharmony_ci    /// let fd = openat(cwd(), ".", OFlags::RDONLY | OFlags::DIRECTORY, Mode::empty()).unwrap();
62b8a62b91Sopenharmony_ci    ///
63b8a62b91Sopenharmony_ci    /// let mut buf = [MaybeUninit::uninit(); 2048];
64b8a62b91Sopenharmony_ci    /// let mut iter = RawDir::new(fd, &mut buf);
65b8a62b91Sopenharmony_ci    /// while let Some(entry) = iter.next() {
66b8a62b91Sopenharmony_ci    ///     let entry = entry.unwrap();
67b8a62b91Sopenharmony_ci    ///     dbg!(&entry);
68b8a62b91Sopenharmony_ci    /// }
69b8a62b91Sopenharmony_ci    /// ```
70b8a62b91Sopenharmony_ci    ///
71b8a62b91Sopenharmony_ci    /// ## Portable
72b8a62b91Sopenharmony_ci    ///
73b8a62b91Sopenharmony_ci    /// Heap allocated growing buffer for supporting directory entries with arbitrarily
74b8a62b91Sopenharmony_ci    /// large file names:
75b8a62b91Sopenharmony_ci    ///
76b8a62b91Sopenharmony_ci    /// ```notrust
77b8a62b91Sopenharmony_ci    /// # // The `notrust` above can be removed when we can depend on Rust 1.60.
78b8a62b91Sopenharmony_ci    /// # use std::mem::MaybeUninit;
79b8a62b91Sopenharmony_ci    /// # use rustix::fs::{cwd, Mode, OFlags, openat, RawDir};
80b8a62b91Sopenharmony_ci    /// # use rustix::io::Errno;
81b8a62b91Sopenharmony_ci    ///
82b8a62b91Sopenharmony_ci    /// let fd = openat(cwd(), ".", OFlags::RDONLY | OFlags::DIRECTORY, Mode::empty()).unwrap();
83b8a62b91Sopenharmony_ci    ///
84b8a62b91Sopenharmony_ci    /// let mut buf = Vec::with_capacity(8192);
85b8a62b91Sopenharmony_ci    /// 'read: loop {
86b8a62b91Sopenharmony_ci    ///     'resize: {
87b8a62b91Sopenharmony_ci    ///         let mut iter = RawDir::new(&fd, buf.spare_capacity_mut());
88b8a62b91Sopenharmony_ci    ///         while let Some(entry) = iter.next() {
89b8a62b91Sopenharmony_ci    ///             let entry = match entry {
90b8a62b91Sopenharmony_ci    ///                 Err(Errno::INVAL) => break 'resize,
91b8a62b91Sopenharmony_ci    ///                 r => r.unwrap(),
92b8a62b91Sopenharmony_ci    ///             };
93b8a62b91Sopenharmony_ci    ///             dbg!(&entry);
94b8a62b91Sopenharmony_ci    ///         }
95b8a62b91Sopenharmony_ci    ///         break 'read;
96b8a62b91Sopenharmony_ci    ///     }
97b8a62b91Sopenharmony_ci    ///
98b8a62b91Sopenharmony_ci    ///     let new_capacity = buf.capacity() * 2;
99b8a62b91Sopenharmony_ci    ///     buf.reserve(new_capacity);
100b8a62b91Sopenharmony_ci    /// }
101b8a62b91Sopenharmony_ci    /// ```
102b8a62b91Sopenharmony_ci    pub fn new(fd: Fd, buf: &'buf mut [MaybeUninit<u8>]) -> Self {
103b8a62b91Sopenharmony_ci        Self {
104b8a62b91Sopenharmony_ci            fd,
105b8a62b91Sopenharmony_ci            buf: {
106b8a62b91Sopenharmony_ci                let offset = buf.as_ptr().align_offset(align_of::<linux_dirent64>());
107b8a62b91Sopenharmony_ci                if offset < buf.len() {
108b8a62b91Sopenharmony_ci                    &mut buf[offset..]
109b8a62b91Sopenharmony_ci                } else {
110b8a62b91Sopenharmony_ci                    &mut []
111b8a62b91Sopenharmony_ci                }
112b8a62b91Sopenharmony_ci            },
113b8a62b91Sopenharmony_ci            initialized: 0,
114b8a62b91Sopenharmony_ci            offset: 0,
115b8a62b91Sopenharmony_ci        }
116b8a62b91Sopenharmony_ci    }
117b8a62b91Sopenharmony_ci}
118b8a62b91Sopenharmony_ci
119b8a62b91Sopenharmony_ci/// A raw directory entry, similar to `std::fs::DirEntry`.
120b8a62b91Sopenharmony_ci///
121b8a62b91Sopenharmony_ci/// Note that unlike the std version, this may represent the `.` or `..` entries.
122b8a62b91Sopenharmony_cipub struct RawDirEntry<'a> {
123b8a62b91Sopenharmony_ci    file_name: &'a CStr,
124b8a62b91Sopenharmony_ci    file_type: u8,
125b8a62b91Sopenharmony_ci    inode_number: u64,
126b8a62b91Sopenharmony_ci    next_entry_cookie: i64,
127b8a62b91Sopenharmony_ci}
128b8a62b91Sopenharmony_ci
129b8a62b91Sopenharmony_ciimpl<'a> fmt::Debug for RawDirEntry<'a> {
130b8a62b91Sopenharmony_ci    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
131b8a62b91Sopenharmony_ci        let mut f = f.debug_struct("RawDirEntry");
132b8a62b91Sopenharmony_ci        f.field("file_name", &self.file_name());
133b8a62b91Sopenharmony_ci        f.field("file_type", &self.file_type());
134b8a62b91Sopenharmony_ci        f.field("ino", &self.ino());
135b8a62b91Sopenharmony_ci        f.field("next_entry_cookie", &self.next_entry_cookie());
136b8a62b91Sopenharmony_ci        f.finish()
137b8a62b91Sopenharmony_ci    }
138b8a62b91Sopenharmony_ci}
139b8a62b91Sopenharmony_ci
140b8a62b91Sopenharmony_ciimpl<'a> RawDirEntry<'a> {
141b8a62b91Sopenharmony_ci    /// Returns the file name of this directory entry.
142b8a62b91Sopenharmony_ci    #[inline]
143b8a62b91Sopenharmony_ci    pub fn file_name(&self) -> &CStr {
144b8a62b91Sopenharmony_ci        self.file_name
145b8a62b91Sopenharmony_ci    }
146b8a62b91Sopenharmony_ci
147b8a62b91Sopenharmony_ci    /// Returns the type of this directory entry.
148b8a62b91Sopenharmony_ci    #[inline]
149b8a62b91Sopenharmony_ci    pub fn file_type(&self) -> FileType {
150b8a62b91Sopenharmony_ci        FileType::from_dirent_d_type(self.file_type)
151b8a62b91Sopenharmony_ci    }
152b8a62b91Sopenharmony_ci
153b8a62b91Sopenharmony_ci    /// Returns the inode number of this directory entry.
154b8a62b91Sopenharmony_ci    #[inline]
155b8a62b91Sopenharmony_ci    #[doc(alias = "inode_number")]
156b8a62b91Sopenharmony_ci    pub fn ino(&self) -> u64 {
157b8a62b91Sopenharmony_ci        self.inode_number
158b8a62b91Sopenharmony_ci    }
159b8a62b91Sopenharmony_ci
160b8a62b91Sopenharmony_ci    /// Returns the seek cookie to the next directory entry.
161b8a62b91Sopenharmony_ci    #[inline]
162b8a62b91Sopenharmony_ci    #[doc(alias = "off")]
163b8a62b91Sopenharmony_ci    pub fn next_entry_cookie(&self) -> u64 {
164b8a62b91Sopenharmony_ci        self.next_entry_cookie as u64
165b8a62b91Sopenharmony_ci    }
166b8a62b91Sopenharmony_ci}
167b8a62b91Sopenharmony_ci
168b8a62b91Sopenharmony_ciimpl<'buf, Fd: AsFd> RawDir<'buf, Fd> {
169b8a62b91Sopenharmony_ci    /// Identical to [Iterator::next] except that [Iterator::Item] borrows from self.
170b8a62b91Sopenharmony_ci    ///
171b8a62b91Sopenharmony_ci    /// Note: this interface will be broken to implement a stdlib iterator API with
172b8a62b91Sopenharmony_ci    /// GAT support once one becomes available.
173b8a62b91Sopenharmony_ci    #[allow(unsafe_code)]
174b8a62b91Sopenharmony_ci    pub fn next(&mut self) -> Option<io::Result<RawDirEntry>> {
175b8a62b91Sopenharmony_ci        if self.is_buffer_empty() {
176b8a62b91Sopenharmony_ci            match getdents_uninit(self.fd.as_fd(), self.buf) {
177b8a62b91Sopenharmony_ci                Ok(bytes_read) if bytes_read == 0 => return None,
178b8a62b91Sopenharmony_ci                Ok(bytes_read) => {
179b8a62b91Sopenharmony_ci                    self.initialized = bytes_read;
180b8a62b91Sopenharmony_ci                    self.offset = 0;
181b8a62b91Sopenharmony_ci                }
182b8a62b91Sopenharmony_ci                Err(e) => return Some(Err(e)),
183b8a62b91Sopenharmony_ci            }
184b8a62b91Sopenharmony_ci        }
185b8a62b91Sopenharmony_ci
186b8a62b91Sopenharmony_ci        let dirent_ptr = self.buf[self.offset..].as_ptr();
187b8a62b91Sopenharmony_ci        // SAFETY:
188b8a62b91Sopenharmony_ci        // - This data is initialized by the check above.
189b8a62b91Sopenharmony_ci        //   - Assumption: the kernel will not give us partial structs.
190b8a62b91Sopenharmony_ci        // - Assumption: the kernel uses proper alignment between structs.
191b8a62b91Sopenharmony_ci        // - The starting pointer is aligned (performed in RawDir::new)
192b8a62b91Sopenharmony_ci        let dirent = unsafe { &*dirent_ptr.cast::<linux_dirent64>() };
193b8a62b91Sopenharmony_ci
194b8a62b91Sopenharmony_ci        self.offset += usize::from(dirent.d_reclen);
195b8a62b91Sopenharmony_ci
196b8a62b91Sopenharmony_ci        Some(Ok(RawDirEntry {
197b8a62b91Sopenharmony_ci            file_type: dirent.d_type,
198b8a62b91Sopenharmony_ci            inode_number: dirent.d_ino,
199b8a62b91Sopenharmony_ci            next_entry_cookie: dirent.d_off,
200b8a62b91Sopenharmony_ci            // SAFETY: the kernel guarantees a NUL terminated string.
201b8a62b91Sopenharmony_ci            file_name: unsafe { CStr::from_ptr(dirent.d_name.as_ptr().cast()) },
202b8a62b91Sopenharmony_ci        }))
203b8a62b91Sopenharmony_ci    }
204b8a62b91Sopenharmony_ci
205b8a62b91Sopenharmony_ci    /// Returns true if the internal buffer is empty and will be refilled when calling
206b8a62b91Sopenharmony_ci    /// [`next`][Self::next].
207b8a62b91Sopenharmony_ci    pub fn is_buffer_empty(&self) -> bool {
208b8a62b91Sopenharmony_ci        self.offset >= self.initialized
209b8a62b91Sopenharmony_ci    }
210b8a62b91Sopenharmony_ci}
211