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