rustix/fs/
raw_dir.rs

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