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