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 functionality is 16 /// necessary, you'll need to drop the current iterator, resize the buffer, and then 17 /// re-create the iterator. The iterator is guaranteed to continue where it left off provided 18 /// the file descriptor isn't 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 requirements. 30 /// 31 /// # Examples 32 /// 33 /// ## Simple but non-portable 34 /// 35 /// These examples are non-portable, because file systems may not have a maximum file name 36 /// length. If you can make assumptions that bound this length, then these examples may suffice. 37 /// 38 /// Using the heap: 39 /// 40 /// ```notrust 41 /// # // The `notrust` above can be removed when we can depend on Rust 1.60. 42 /// # use std::mem::MaybeUninit; 43 /// # use rustix::fs::{cwd, Mode, OFlags, openat, RawDir}; 44 /// 45 /// let fd = openat(cwd(), ".", OFlags::RDONLY | OFlags::DIRECTORY, Mode::empty()).unwrap(); 46 /// 47 /// let mut buf = Vec::with_capacity(8192); 48 /// let mut iter = RawDir::new(fd, buf.spare_capacity_mut()); 49 /// while let Some(entry) = iter.next() { 50 /// let entry = entry.unwrap(); 51 /// dbg!(&entry); 52 /// } 53 /// ``` 54 /// 55 /// Using the stack: 56 /// 57 /// ``` 58 /// # use std::mem::MaybeUninit; 59 /// # use rustix::fs::{cwd, Mode, OFlags, openat, RawDir}; 60 /// 61 /// let fd = openat(cwd(), ".", OFlags::RDONLY | OFlags::DIRECTORY, Mode::empty()).unwrap(); 62 /// 63 /// let mut buf = [MaybeUninit::uninit(); 2048]; 64 /// let mut iter = RawDir::new(fd, &mut buf); 65 /// while let Some(entry) = iter.next() { 66 /// let entry = entry.unwrap(); 67 /// dbg!(&entry); 68 /// } 69 /// ``` 70 /// 71 /// ## Portable 72 /// 73 /// Heap allocated growing buffer for supporting directory entries with arbitrarily 74 /// large file names: 75 /// 76 /// ```notrust 77 /// # // The `notrust` above can be removed when we can depend on Rust 1.60. 78 /// # use std::mem::MaybeUninit; 79 /// # use rustix::fs::{cwd, Mode, OFlags, openat, RawDir}; 80 /// # use rustix::io::Errno; 81 /// 82 /// let fd = openat(cwd(), ".", OFlags::RDONLY | OFlags::DIRECTORY, Mode::empty()).unwrap(); 83 /// 84 /// let mut buf = Vec::with_capacity(8192); 85 /// 'read: loop { 86 /// 'resize: { 87 /// let mut iter = RawDir::new(&fd, buf.spare_capacity_mut()); 88 /// while let Some(entry) = iter.next() { 89 /// let entry = match entry { 90 /// Err(Errno::INVAL) => break 'resize, 91 /// r => r.unwrap(), 92 /// }; 93 /// dbg!(&entry); 94 /// } 95 /// break 'read; 96 /// } 97 /// 98 /// let new_capacity = buf.capacity() * 2; 99 /// buf.reserve(new_capacity); 100 /// } 101 /// ``` new(fd: Fd, buf: &'buf mut [MaybeUninit<u8>]) -> Self102 pub fn new(fd: Fd, buf: &'buf mut [MaybeUninit<u8>]) -> Self { 103 Self { 104 fd, 105 buf: { 106 let offset = buf.as_ptr().align_offset(align_of::<linux_dirent64>()); 107 if offset < buf.len() { 108 &mut buf[offset..] 109 } else { 110 &mut [] 111 } 112 }, 113 initialized: 0, 114 offset: 0, 115 } 116 } 117 } 118 119 /// A raw directory entry, similar to `std::fs::DirEntry`. 120 /// 121 /// Note that unlike the std version, this may represent the `.` or `..` entries. 122 pub struct RawDirEntry<'a> { 123 file_name: &'a CStr, 124 file_type: u8, 125 inode_number: u64, 126 next_entry_cookie: i64, 127 } 128 129 impl<'a> fmt::Debug for RawDirEntry<'a> { fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result130 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 131 let mut f = f.debug_struct("RawDirEntry"); 132 f.field("file_name", &self.file_name()); 133 f.field("file_type", &self.file_type()); 134 f.field("ino", &self.ino()); 135 f.field("next_entry_cookie", &self.next_entry_cookie()); 136 f.finish() 137 } 138 } 139 140 impl<'a> RawDirEntry<'a> { 141 /// Returns the file name of this directory entry. 142 #[inline] file_name(&self) -> &CStr143 pub fn file_name(&self) -> &CStr { 144 self.file_name 145 } 146 147 /// Returns the type of this directory entry. 148 #[inline] file_type(&self) -> FileType149 pub fn file_type(&self) -> FileType { 150 FileType::from_dirent_d_type(self.file_type) 151 } 152 153 /// Returns the inode number of this directory entry. 154 #[inline] 155 #[doc(alias = "inode_number")] ino(&self) -> u64156 pub fn ino(&self) -> u64 { 157 self.inode_number 158 } 159 160 /// Returns the seek cookie to the next directory entry. 161 #[inline] 162 #[doc(alias = "off")] next_entry_cookie(&self) -> u64163 pub fn next_entry_cookie(&self) -> u64 { 164 self.next_entry_cookie as u64 165 } 166 } 167 168 impl<'buf, Fd: AsFd> RawDir<'buf, Fd> { 169 /// Identical to [Iterator::next] except that [Iterator::Item] borrows from self. 170 /// 171 /// Note: this interface will be broken to implement a stdlib iterator API with 172 /// GAT support once one becomes available. 173 #[allow(unsafe_code)] next(&mut self) -> Option<io::Result<RawDirEntry>>174 pub fn next(&mut self) -> Option<io::Result<RawDirEntry>> { 175 if self.is_buffer_empty() { 176 match getdents_uninit(self.fd.as_fd(), self.buf) { 177 Ok(bytes_read) if bytes_read == 0 => return None, 178 Ok(bytes_read) => { 179 self.initialized = bytes_read; 180 self.offset = 0; 181 } 182 Err(e) => return Some(Err(e)), 183 } 184 } 185 186 let dirent_ptr = self.buf[self.offset..].as_ptr(); 187 // SAFETY: 188 // - This data is initialized by the check above. 189 // - Assumption: the kernel will not give us partial structs. 190 // - Assumption: the kernel uses proper alignment between structs. 191 // - The starting pointer is aligned (performed in RawDir::new) 192 let dirent = unsafe { &*dirent_ptr.cast::<linux_dirent64>() }; 193 194 self.offset += usize::from(dirent.d_reclen); 195 196 Some(Ok(RawDirEntry { 197 file_type: dirent.d_type, 198 inode_number: dirent.d_ino, 199 next_entry_cookie: dirent.d_off, 200 // SAFETY: the kernel guarantees a NUL terminated string. 201 file_name: unsafe { CStr::from_ptr(dirent.d_name.as_ptr().cast()) }, 202 })) 203 } 204 205 /// Returns true if the internal buffer is empty and will be refilled when calling 206 /// [`next`][Self::next]. is_buffer_empty(&self) -> bool207 pub fn is_buffer_empty(&self) -> bool { 208 self.offset >= self.initialized 209 } 210 } 211