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