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