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