| //! `RawDir` and `RawDirEntry`. |
| |
| use core::fmt; |
| use core::mem::{align_of, MaybeUninit}; |
| use linux_raw_sys::general::linux_dirent64; |
| |
| use crate::backend::fs::syscalls::getdents_uninit; |
| use crate::fd::AsFd; |
| use crate::ffi::CStr; |
| use crate::fs::FileType; |
| use crate::io; |
| |
| /// A directory iterator implemented with getdents. |
| /// |
| /// Note: This implementation does not handle growing the buffer. If this |
| /// functionality is necessary, you'll need to drop the current iterator, |
| /// resize the buffer, and then re-create the iterator. The iterator is |
| /// guaranteed to continue where it left off provided the file descriptor isn't |
| /// changed. See the example in [`RawDir::new`]. |
| pub struct RawDir<'buf, Fd: AsFd> { |
| fd: Fd, |
| buf: &'buf mut [MaybeUninit<u8>], |
| initialized: usize, |
| offset: usize, |
| } |
| |
| impl<'buf, Fd: AsFd> RawDir<'buf, Fd> { |
| /// Create a new iterator from the given file descriptor and buffer. |
| /// |
| /// Note: the buffer size may be trimmed to accommodate alignment |
| /// requirements. |
| /// |
| /// # Examples |
| /// |
| /// ## Simple but non-portable |
| /// |
| /// These examples are non-portable, because file systems may not have a |
| /// maximum file name length. If you can make assumptions that bound |
| /// this length, then these examples may suffice. |
| /// |
| /// Using the heap: |
| /// |
| /// ``` |
| /// # use std::mem::MaybeUninit; |
| /// # use rustix::fs::{CWD, Mode, OFlags, openat, RawDir}; |
| /// |
| /// let fd = openat( |
| /// CWD, |
| /// ".", |
| /// OFlags::RDONLY | OFlags::DIRECTORY | OFlags::CLOEXEC, |
| /// Mode::empty(), |
| /// ) |
| /// .unwrap(); |
| /// |
| /// let mut buf = Vec::with_capacity(8192); |
| /// let mut iter = RawDir::new(fd, buf.spare_capacity_mut()); |
| /// while let Some(entry) = iter.next() { |
| /// let entry = entry.unwrap(); |
| /// dbg!(&entry); |
| /// } |
| /// ``` |
| /// |
| /// Using the stack: |
| /// |
| /// ``` |
| /// # use std::mem::MaybeUninit; |
| /// # use rustix::fs::{CWD, Mode, OFlags, openat, RawDir}; |
| /// |
| /// let fd = openat( |
| /// CWD, |
| /// ".", |
| /// OFlags::RDONLY | OFlags::DIRECTORY | OFlags::CLOEXEC, |
| /// Mode::empty(), |
| /// ) |
| /// .unwrap(); |
| /// |
| /// let mut buf = [MaybeUninit::uninit(); 2048]; |
| /// let mut iter = RawDir::new(fd, &mut buf); |
| /// while let Some(entry) = iter.next() { |
| /// let entry = entry.unwrap(); |
| /// dbg!(&entry); |
| /// } |
| /// ``` |
| /// |
| /// ## Portable |
| /// |
| /// Heap allocated growing buffer for supporting directory entries with |
| /// arbitrarily large file names: |
| /// |
| /// ```notrust |
| /// # // The `notrust` above can be removed when we can depend on Rust 1.65. |
| /// # use std::mem::MaybeUninit; |
| /// # use rustix::fs::{CWD, Mode, OFlags, openat, RawDir}; |
| /// # use rustix::io::Errno; |
| /// |
| /// let fd = openat( |
| /// CWD, |
| /// ".", |
| /// OFlags::RDONLY | OFlags::DIRECTORY | OFlags::CLOEXEC, |
| /// Mode::empty(), |
| /// ) |
| /// .unwrap(); |
| /// |
| /// let mut buf = Vec::with_capacity(8192); |
| /// 'read: loop { |
| /// 'resize: { |
| /// let mut iter = RawDir::new(&fd, buf.spare_capacity_mut()); |
| /// while let Some(entry) = iter.next() { |
| /// let entry = match entry { |
| /// Err(Errno::INVAL) => break 'resize, |
| /// r => r.unwrap(), |
| /// }; |
| /// dbg!(&entry); |
| /// } |
| /// break 'read; |
| /// } |
| /// |
| /// let new_capacity = buf.capacity() * 2; |
| /// buf.reserve(new_capacity); |
| /// } |
| /// ``` |
| pub fn new(fd: Fd, buf: &'buf mut [MaybeUninit<u8>]) -> Self { |
| Self { |
| fd, |
| buf: { |
| let offset = buf.as_ptr().align_offset(align_of::<linux_dirent64>()); |
| if offset < buf.len() { |
| &mut buf[offset..] |
| } else { |
| &mut [] |
| } |
| }, |
| initialized: 0, |
| offset: 0, |
| } |
| } |
| } |
| |
| /// A raw directory entry, similar to `std::fs::DirEntry`. |
| /// |
| /// Unlike the std version, this may represent the `.` or `..` entries. |
| pub struct RawDirEntry<'a> { |
| file_name: &'a CStr, |
| file_type: u8, |
| inode_number: u64, |
| next_entry_cookie: i64, |
| } |
| |
| impl<'a> fmt::Debug for RawDirEntry<'a> { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| let mut f = f.debug_struct("RawDirEntry"); |
| f.field("file_name", &self.file_name()); |
| f.field("file_type", &self.file_type()); |
| f.field("ino", &self.ino()); |
| f.field("next_entry_cookie", &self.next_entry_cookie()); |
| f.finish() |
| } |
| } |
| |
| impl<'a> RawDirEntry<'a> { |
| /// Returns the file name of this directory entry. |
| #[inline] |
| pub fn file_name(&self) -> &CStr { |
| self.file_name |
| } |
| |
| /// Returns the type of this directory entry. |
| #[inline] |
| pub fn file_type(&self) -> FileType { |
| FileType::from_dirent_d_type(self.file_type) |
| } |
| |
| /// Returns the inode number of this directory entry. |
| #[inline] |
| #[doc(alias = "inode_number")] |
| pub fn ino(&self) -> u64 { |
| self.inode_number |
| } |
| |
| /// Returns the seek cookie to the next directory entry. |
| #[inline] |
| #[doc(alias = "off")] |
| pub fn next_entry_cookie(&self) -> u64 { |
| self.next_entry_cookie as u64 |
| } |
| } |
| |
| impl<'buf, Fd: AsFd> RawDir<'buf, Fd> { |
| /// Identical to [`Iterator::next`] except that [`Iterator::Item`] borrows |
| /// from self. |
| /// |
| /// Note: this interface will be broken to implement a stdlib iterator API |
| /// with GAT support once one becomes available. |
| #[allow(unsafe_code)] |
| #[allow(clippy::should_implement_trait)] |
| pub fn next(&mut self) -> Option<io::Result<RawDirEntry>> { |
| if self.is_buffer_empty() { |
| match getdents_uninit(self.fd.as_fd(), self.buf) { |
| Ok(bytes_read) if bytes_read == 0 => return None, |
| Ok(bytes_read) => { |
| self.initialized = bytes_read; |
| self.offset = 0; |
| } |
| Err(e) => return Some(Err(e)), |
| } |
| } |
| |
| let dirent_ptr = self.buf[self.offset..].as_ptr(); |
| // SAFETY: |
| // - This data is initialized by the check above. |
| // - Assumption: the kernel will not give us partial structs. |
| // - Assumption: the kernel uses proper alignment between structs. |
| // - The starting pointer is aligned (performed in RawDir::new) |
| let dirent = unsafe { &*dirent_ptr.cast::<linux_dirent64>() }; |
| |
| self.offset += usize::from(dirent.d_reclen); |
| |
| Some(Ok(RawDirEntry { |
| file_type: dirent.d_type, |
| inode_number: dirent.d_ino.into(), |
| next_entry_cookie: dirent.d_off.into(), |
| // SAFETY: The kernel guarantees a NUL-terminated string. |
| file_name: unsafe { CStr::from_ptr(dirent.d_name.as_ptr().cast()) }, |
| })) |
| } |
| |
| /// Returns true if the internal buffer is empty and will be refilled when |
| /// calling [`next`]. |
| /// |
| /// [`next`]: Self::next |
| pub fn is_buffer_empty(&self) -> bool { |
| self.offset >= self.initialized |
| } |
| } |