diff options
Diffstat (limited to 'vendor/rustix/src/fs/raw_dir.rs')
-rw-r--r-- | vendor/rustix/src/fs/raw_dir.rs | 237 |
1 files changed, 237 insertions, 0 deletions
diff --git a/vendor/rustix/src/fs/raw_dir.rs b/vendor/rustix/src/fs/raw_dir.rs new file mode 100644 index 0000000..93686b1 --- /dev/null +++ b/vendor/rustix/src/fs/raw_dir.rs @@ -0,0 +1,237 @@ +//! `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}; + /// # use rustix::cstr; + /// + /// let fd = openat( + /// CWD, + /// cstr!("."), + /// 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}; + /// # use rustix::cstr; + /// + /// let fd = openat( + /// CWD, + /// cstr!("."), + /// 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; + /// # use rustix::cstr; + /// + /// let fd = openat( + /// CWD, + /// cstr!("."), + /// 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(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 + } +} |