1 | //! `RawDir` and `RawDirEntry`. |
2 | |
3 | use crate::backend::fs::syscalls::getdents_uninit; |
4 | use crate::fd::AsFd; |
5 | use crate::ffi::CStr; |
6 | use crate::fs::FileType; |
7 | use crate::io; |
8 | use core::fmt; |
9 | use core::mem::{align_of, MaybeUninit}; |
10 | use 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`]. |
19 | pub struct RawDir<'buf, Fd: AsFd> { |
20 | fd: Fd, |
21 | buf: &'buf mut [MaybeUninit<u8>], |
22 | initialized: usize, |
23 | offset: usize, |
24 | } |
25 | |
26 | impl<'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. |
144 | pub struct RawDirEntry<'a> { |
145 | file_name: &'a CStr, |
146 | file_type: u8, |
147 | inode_number: u64, |
148 | next_entry_cookie: i64, |
149 | } |
150 | |
151 | impl<'a> fmt::Debug for RawDirEntry<'a> { |
152 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
153 | let mut f: DebugStruct<'_, '_> = f.debug_struct(name:"RawDirEntry" ); |
154 | f.field(name:"file_name" , &self.file_name()); |
155 | f.field(name:"file_type" , &self.file_type()); |
156 | f.field(name:"ino" , &self.ino()); |
157 | f.field(name:"next_entry_cookie" , &self.next_entry_cookie()); |
158 | f.finish() |
159 | } |
160 | } |
161 | |
162 | impl<'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 | |
190 | impl<'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 | } |
237 | |