1 //! `RawDir` and `RawDirEntry`.
4 use core
::mem
::{align_of, MaybeUninit}
;
5 use linux_raw_sys
::general
::linux_dirent64
;
7 use crate::backend
::fs
::syscalls
::getdents_uninit
;
10 use crate::fs
::FileType
;
13 /// A directory iterator implemented with getdents.
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
> {
22 buf
: &'buf
mut [MaybeUninit
<u8>],
27 impl<'buf
, Fd
: AsFd
> RawDir
<'buf
, Fd
> {
28 /// Create a new iterator from the given file descriptor and buffer.
30 /// Note: the buffer size may be trimmed to accommodate alignment
35 /// ## Simple but non-portable
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.
44 /// # use std::mem::MaybeUninit;
45 /// # use rustix::fs::{CWD, Mode, OFlags, openat, RawDir};
50 /// OFlags::RDONLY | OFlags::DIRECTORY | OFlags::CLOEXEC,
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();
66 /// # use std::mem::MaybeUninit;
67 /// # use rustix::fs::{CWD, Mode, OFlags, openat, RawDir};
72 /// OFlags::RDONLY | OFlags::DIRECTORY | OFlags::CLOEXEC,
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();
87 /// Heap allocated growing buffer for supporting directory entries with
88 /// arbitrarily large file names:
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;
99 /// OFlags::RDONLY | OFlags::DIRECTORY | OFlags::CLOEXEC,
104 /// let mut buf = Vec::with_capacity(8192);
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,
118 /// let new_capacity = buf.capacity() * 2;
119 /// buf.reserve(new_capacity);
122 pub fn new(fd
: Fd
, buf
: &'buf
mut [MaybeUninit
<u8>]) -> Self {
126 let offset
= buf
.as_ptr().align_offset(align_of
::<linux_dirent64
>());
127 if offset
< buf
.len() {
139 /// A raw directory entry, similar to [`std::fs::DirEntry`].
141 /// Unlike the std version, this may represent the `.` or `..` entries.
142 pub struct RawDirEntry
<'a
> {
146 next_entry_cookie
: i64,
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());
160 impl<'a
> RawDirEntry
<'a
> {
161 /// Returns the file name of this directory entry.
163 pub fn file_name(&self) -> &CStr
{
167 /// Returns the type of this directory entry.
169 pub fn file_type(&self) -> FileType
{
170 FileType
::from_dirent_d_type(self.file_type
)
173 /// Returns the inode number of this directory entry.
175 #[doc(alias = "inode_number")]
176 pub fn ino(&self) -> u64 {
180 /// Returns the seek cookie to the next directory entry.
182 #[doc(alias = "off")]
183 pub fn next_entry_cookie(&self) -> u64 {
184 self.next_entry_cookie
as u64
188 impl<'buf
, Fd
: AsFd
> RawDir
<'buf
, Fd
> {
189 /// Identical to [`Iterator::next`] except that [`Iterator::Item`] borrows
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
,
201 self.initialized
= bytes_read
;
204 Err(e
) => return Some(Err(e
)),
208 let dirent_ptr
= self.buf
[self.offset
..].as_ptr();
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>() }
;
216 self.offset
+= usize::from(dirent
.d_reclen
);
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()) }
,
227 /// Returns true if the internal buffer is empty and will be refilled when
228 /// calling [`next`].
230 /// [`next`]: Self::next
231 pub fn is_buffer_empty(&self) -> bool
{
232 self.offset
>= self.initialized