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