1 | //! `RawDir` and `RawDirEntry`. |
2 | |
3 | use core::fmt; |
4 | use core::mem::{align_of, MaybeUninit}; |
5 | use linux_raw_sys::general::linux_dirent64; |
6 | |
7 | use crate::backend::fs::syscalls::getdents_uninit; |
8 | use crate::fd::AsFd; |
9 | use crate::ffi::CStr; |
10 | use crate::fs::FileType; |
11 | use 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`]. |
20 | pub struct RawDir<'buf, Fd: AsFd> { |
21 | fd: Fd, |
22 | buf: &'buf mut [MaybeUninit<u8>], |
23 | initialized: usize, |
24 | offset: usize, |
25 | } |
26 | |
27 | impl<'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 | /// ```notrust |
44 | /// # // The `notrust` above can be removed when we can depend on Rust 1.60. |
45 | /// # use std::mem::MaybeUninit; |
46 | /// # use rustix::fs::{cwd, Mode, OFlags, openat, RawDir}; |
47 | /// |
48 | /// let fd = openat( |
49 | /// cwd(), |
50 | /// ".", |
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 | /// |
70 | /// let fd = openat( |
71 | /// cwd(), |
72 | /// "." , |
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 | /// ```notrust |
92 | /// # // The `notrust` above can be removed when we can depend on Rust 1.60. |
93 | /// # use std::mem::MaybeUninit; |
94 | /// # use rustix::fs::{cwd, Mode, OFlags, openat, RawDir}; |
95 | /// # use rustix::io::Errno; |
96 | /// |
97 | /// let fd = openat( |
98 | /// cwd(), |
99 | /// ".", |
100 | /// OFlags::RDONLY | OFlags::DIRECTORY | OFlags::CLOEXEC, |
101 | /// Mode::empty(), |
102 | /// ) |
103 | /// .unwrap(); |
104 | /// |
105 | /// let mut buf = Vec::with_capacity(8192); |
106 | /// 'read: loop { |
107 | /// 'resize: { |
108 | /// let mut iter = RawDir::new(&fd, buf.spare_capacity_mut()); |
109 | /// while let Some(entry) = iter.next() { |
110 | /// let entry = match entry { |
111 | /// Err(Errno::INVAL) => break 'resize, |
112 | /// r => r.unwrap(), |
113 | /// }; |
114 | /// dbg!(&entry); |
115 | /// } |
116 | /// break 'read; |
117 | /// } |
118 | /// |
119 | /// let new_capacity = buf.capacity() * 2; |
120 | /// buf.reserve(new_capacity); |
121 | /// } |
122 | /// ``` |
123 | pub fn new(fd: Fd, buf: &'buf mut [MaybeUninit<u8>]) -> Self { |
124 | Self { |
125 | fd, |
126 | buf: { |
127 | let offset = buf.as_ptr().align_offset(align_of::<linux_dirent64>()); |
128 | if offset < buf.len() { |
129 | &mut buf[offset..] |
130 | } else { |
131 | &mut [] |
132 | } |
133 | }, |
134 | initialized: 0, |
135 | offset: 0, |
136 | } |
137 | } |
138 | } |
139 | |
140 | /// A raw directory entry, similar to `std::fs::DirEntry`. |
141 | /// |
142 | /// Unlike the std version, this may represent the `.` or `..` entries. |
143 | pub struct RawDirEntry<'a> { |
144 | file_name: &'a CStr, |
145 | file_type: u8, |
146 | inode_number: u64, |
147 | next_entry_cookie: i64, |
148 | } |
149 | |
150 | impl<'a> fmt::Debug for RawDirEntry<'a> { |
151 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
152 | let mut f: DebugStruct<'_, '_> = f.debug_struct(name:"RawDirEntry" ); |
153 | f.field(name:"file_name" , &self.file_name()); |
154 | f.field(name:"file_type" , &self.file_type()); |
155 | f.field(name:"ino" , &self.ino()); |
156 | f.field(name:"next_entry_cookie" , &self.next_entry_cookie()); |
157 | f.finish() |
158 | } |
159 | } |
160 | |
161 | impl<'a> RawDirEntry<'a> { |
162 | /// Returns the file name of this directory entry. |
163 | #[inline ] |
164 | pub fn file_name(&self) -> &CStr { |
165 | self.file_name |
166 | } |
167 | |
168 | /// Returns the type of this directory entry. |
169 | #[inline ] |
170 | pub fn file_type(&self) -> FileType { |
171 | FileType::from_dirent_d_type(self.file_type) |
172 | } |
173 | |
174 | /// Returns the inode number of this directory entry. |
175 | #[inline ] |
176 | #[doc (alias = "inode_number" )] |
177 | pub fn ino(&self) -> u64 { |
178 | self.inode_number |
179 | } |
180 | |
181 | /// Returns the seek cookie to the next directory entry. |
182 | #[inline ] |
183 | #[doc (alias = "off" )] |
184 | pub fn next_entry_cookie(&self) -> u64 { |
185 | self.next_entry_cookie as u64 |
186 | } |
187 | } |
188 | |
189 | impl<'buf, Fd: AsFd> RawDir<'buf, Fd> { |
190 | /// Identical to [`Iterator::next`] except that [`Iterator::Item`] borrows |
191 | /// from self. |
192 | /// |
193 | /// Note: this interface will be broken to implement a stdlib iterator API |
194 | /// with GAT support once one becomes available. |
195 | #[allow (unsafe_code)] |
196 | #[allow (clippy::should_implement_trait)] |
197 | pub fn next(&mut self) -> Option<io::Result<RawDirEntry>> { |
198 | if self.is_buffer_empty() { |
199 | match getdents_uninit(self.fd.as_fd(), self.buf) { |
200 | Ok(bytes_read) if bytes_read == 0 => return None, |
201 | Ok(bytes_read) => { |
202 | self.initialized = bytes_read; |
203 | self.offset = 0; |
204 | } |
205 | Err(e) => return Some(Err(e)), |
206 | } |
207 | } |
208 | |
209 | let dirent_ptr = self.buf[self.offset..].as_ptr(); |
210 | // SAFETY: |
211 | // - This data is initialized by the check above. |
212 | // - Assumption: the kernel will not give us partial structs. |
213 | // - Assumption: the kernel uses proper alignment between structs. |
214 | // - The starting pointer is aligned (performed in RawDir::new) |
215 | let dirent = unsafe { &*dirent_ptr.cast::<linux_dirent64>() }; |
216 | |
217 | self.offset += usize::from(dirent.d_reclen); |
218 | |
219 | Some(Ok(RawDirEntry { |
220 | file_type: dirent.d_type, |
221 | inode_number: dirent.d_ino.into(), |
222 | next_entry_cookie: dirent.d_off.into(), |
223 | // SAFETY: The kernel guarantees a NUL-terminated string. |
224 | file_name: unsafe { CStr::from_ptr(dirent.d_name.as_ptr().cast()) }, |
225 | })) |
226 | } |
227 | |
228 | /// Returns true if the internal buffer is empty and will be refilled when |
229 | /// calling [`next`]. |
230 | /// |
231 | /// [`next`]: Self::next |
232 | pub fn is_buffer_empty(&self) -> bool { |
233 | self.offset >= self.initialized |
234 | } |
235 | } |
236 | |