rustix/fs/
raw_dir.rs

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