aboutsummaryrefslogtreecommitdiff
path: root/vendor/rustix/src/backend/linux_raw/fs/dir.rs
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/rustix/src/backend/linux_raw/fs/dir.rs')
-rw-r--r--vendor/rustix/src/backend/linux_raw/fs/dir.rs315
1 files changed, 315 insertions, 0 deletions
diff --git a/vendor/rustix/src/backend/linux_raw/fs/dir.rs b/vendor/rustix/src/backend/linux_raw/fs/dir.rs
new file mode 100644
index 0000000..dbddd58
--- /dev/null
+++ b/vendor/rustix/src/backend/linux_raw/fs/dir.rs
@@ -0,0 +1,315 @@
+use crate::fd::{AsFd, BorrowedFd, OwnedFd};
+use crate::ffi::{CStr, CString};
+use crate::fs::{
+ fcntl_getfl, fstat, fstatfs, fstatvfs, openat, FileType, Mode, OFlags, Stat, StatFs, StatVfs,
+};
+use crate::io;
+#[cfg(feature = "process")]
+use crate::process::fchdir;
+use crate::utils::as_ptr;
+use alloc::borrow::ToOwned;
+use alloc::vec::Vec;
+use core::fmt;
+use core::mem::size_of;
+use linux_raw_sys::general::{linux_dirent64, SEEK_SET};
+
+/// `DIR*`
+pub struct Dir {
+ /// The `OwnedFd` that we read directory entries from.
+ fd: OwnedFd,
+
+ /// Have we seen any errors in this iteration?
+ any_errors: bool,
+
+ /// Should we rewind the stream on the next iteration?
+ rewind: bool,
+
+ /// The buffer for `linux_dirent64` entries.
+ buf: Vec<u8>,
+
+ /// Where we are in the buffer.
+ pos: usize,
+}
+
+impl Dir {
+ /// Take ownership of `fd` and construct a `Dir` that reads entries from
+ /// the given directory file descriptor.
+ #[inline]
+ pub fn new<Fd: Into<OwnedFd>>(fd: Fd) -> io::Result<Self> {
+ Self::_new(fd.into())
+ }
+
+ #[inline]
+ fn _new(fd: OwnedFd) -> io::Result<Self> {
+ Ok(Self {
+ fd,
+ any_errors: false,
+ rewind: false,
+ buf: Vec::new(),
+ pos: 0,
+ })
+ }
+
+ /// Borrow `fd` and construct a `Dir` that reads entries from the given
+ /// directory file descriptor.
+ #[inline]
+ pub fn read_from<Fd: AsFd>(fd: Fd) -> io::Result<Self> {
+ Self::_read_from(fd.as_fd())
+ }
+
+ #[inline]
+ fn _read_from(fd: BorrowedFd<'_>) -> io::Result<Self> {
+ let flags = fcntl_getfl(fd)?;
+ let fd_for_dir = openat(fd, cstr!("."), flags | OFlags::CLOEXEC, Mode::empty())?;
+
+ Ok(Self {
+ fd: fd_for_dir,
+ any_errors: false,
+ rewind: false,
+ buf: Vec::new(),
+ pos: 0,
+ })
+ }
+
+ /// `rewinddir(self)`
+ #[inline]
+ pub fn rewind(&mut self) {
+ self.any_errors = false;
+ self.rewind = true;
+ self.pos = self.buf.len();
+ }
+
+ /// `readdir(self)`, where `None` means the end of the directory.
+ pub fn read(&mut self) -> Option<io::Result<DirEntry>> {
+ // If we've seen errors, don't continue to try to read anyting further.
+ if self.any_errors {
+ return None;
+ }
+
+ // If a rewind was requested, seek to the beginning.
+ if self.rewind {
+ self.rewind = false;
+ match io::retry_on_intr(|| {
+ crate::backend::fs::syscalls::_seek(self.fd.as_fd(), 0, SEEK_SET)
+ }) {
+ Ok(_) => (),
+ Err(err) => {
+ self.any_errors = true;
+ return Some(Err(err));
+ }
+ }
+ }
+
+ // Compute linux_dirent64 field offsets.
+ let z = linux_dirent64 {
+ d_ino: 0_u64,
+ d_off: 0_i64,
+ d_type: 0_u8,
+ d_reclen: 0_u16,
+ d_name: Default::default(),
+ };
+ let base = as_ptr(&z) as usize;
+ let offsetof_d_reclen = (as_ptr(&z.d_reclen) as usize) - base;
+ let offsetof_d_name = (as_ptr(&z.d_name) as usize) - base;
+ let offsetof_d_ino = (as_ptr(&z.d_ino) as usize) - base;
+ let offsetof_d_type = (as_ptr(&z.d_type) as usize) - base;
+
+ // Test if we need more entries, and if so, read more.
+ if self.buf.len() - self.pos < size_of::<linux_dirent64>() {
+ match self.read_more()? {
+ Ok(()) => (),
+ Err(err) => return Some(Err(err)),
+ }
+ }
+
+ // We successfully read an entry. Extract the fields.
+ let pos = self.pos;
+
+ // Do an unaligned u16 load.
+ let d_reclen = u16::from_ne_bytes([
+ self.buf[pos + offsetof_d_reclen],
+ self.buf[pos + offsetof_d_reclen + 1],
+ ]);
+ assert!(self.buf.len() - pos >= d_reclen as usize);
+ self.pos += d_reclen as usize;
+
+ // Read the NUL-terminated name from the `d_name` field. Without
+ // `unsafe`, we need to scan for the NUL twice: once to obtain a size
+ // for the slice, and then once within `CStr::from_bytes_with_nul`.
+ let name_start = pos + offsetof_d_name;
+ let name_len = self.buf[name_start..]
+ .iter()
+ .position(|x| *x == b'\0')
+ .unwrap();
+ let name = CStr::from_bytes_with_nul(&self.buf[name_start..][..=name_len]).unwrap();
+ let name = name.to_owned();
+ assert!(name.as_bytes().len() <= self.buf.len() - name_start);
+
+ // Do an unaligned u64 load.
+ let d_ino = u64::from_ne_bytes([
+ self.buf[pos + offsetof_d_ino],
+ self.buf[pos + offsetof_d_ino + 1],
+ self.buf[pos + offsetof_d_ino + 2],
+ self.buf[pos + offsetof_d_ino + 3],
+ self.buf[pos + offsetof_d_ino + 4],
+ self.buf[pos + offsetof_d_ino + 5],
+ self.buf[pos + offsetof_d_ino + 6],
+ self.buf[pos + offsetof_d_ino + 7],
+ ]);
+
+ let d_type = self.buf[pos + offsetof_d_type];
+
+ // Check that our types correspond to the `linux_dirent64` types.
+ let _ = linux_dirent64 {
+ d_ino,
+ d_off: 0,
+ d_type,
+ d_reclen,
+ d_name: Default::default(),
+ };
+
+ Some(Ok(DirEntry {
+ d_ino,
+ d_type,
+ name,
+ }))
+ }
+
+ #[must_use]
+ fn read_more(&mut self) -> Option<io::Result<()>> {
+ // The first few times we're called, we allocate a relatively small
+ // buffer, because many directories are small. If we're called more,
+ // use progressively larger allocations, up to a fixed maximum.
+ //
+ // The specific sizes and policy here have not been tuned in detail yet
+ // and may need to be adjusted. In doing so, we should be careful to
+ // avoid unbounded buffer growth. This buffer only exists to share the
+ // cost of a `getdents` call over many entries, so if it gets too big,
+ // cache and heap usage will outweigh the benefit. And ultimately,
+ // directories can contain more entries than we can allocate contiguous
+ // memory for, so we'll always need to cap the size at some point.
+ if self.buf.len() < 1024 * size_of::<linux_dirent64>() {
+ self.buf.reserve(32 * size_of::<linux_dirent64>());
+ }
+ self.buf.resize(self.buf.capacity(), 0);
+ let nread = match io::retry_on_intr(|| {
+ crate::backend::fs::syscalls::getdents(self.fd.as_fd(), &mut self.buf)
+ }) {
+ Ok(nread) => nread,
+ Err(io::Errno::NOENT) => {
+ self.any_errors = true;
+ return None;
+ }
+ Err(err) => {
+ self.any_errors = true;
+ return Some(Err(err));
+ }
+ };
+ self.buf.resize(nread, 0);
+ self.pos = 0;
+ if nread == 0 {
+ None
+ } else {
+ Some(Ok(()))
+ }
+ }
+
+ /// `fstat(self)`
+ #[inline]
+ pub fn stat(&self) -> io::Result<Stat> {
+ fstat(&self.fd)
+ }
+
+ /// `fstatfs(self)`
+ #[inline]
+ pub fn statfs(&self) -> io::Result<StatFs> {
+ fstatfs(&self.fd)
+ }
+
+ /// `fstatvfs(self)`
+ #[inline]
+ pub fn statvfs(&self) -> io::Result<StatVfs> {
+ fstatvfs(&self.fd)
+ }
+
+ /// `fchdir(self)`
+ #[cfg(feature = "process")]
+ #[cfg_attr(doc_cfg, doc(cfg(feature = "process")))]
+ #[inline]
+ pub fn chdir(&self) -> io::Result<()> {
+ fchdir(&self.fd)
+ }
+}
+
+impl Iterator for Dir {
+ type Item = io::Result<DirEntry>;
+
+ #[inline]
+ fn next(&mut self) -> Option<Self::Item> {
+ Self::read(self)
+ }
+}
+
+impl fmt::Debug for Dir {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.debug_struct("Dir").field("fd", &self.fd).finish()
+ }
+}
+
+/// `struct dirent`
+#[derive(Debug)]
+pub struct DirEntry {
+ d_ino: u64,
+ d_type: u8,
+ name: CString,
+}
+
+impl DirEntry {
+ /// Returns the file name of this directory entry.
+ #[inline]
+ pub fn file_name(&self) -> &CStr {
+ &self.name
+ }
+
+ /// Returns the type of this directory entry.
+ #[inline]
+ pub fn file_type(&self) -> FileType {
+ FileType::from_dirent_d_type(self.d_type)
+ }
+
+ /// Return the inode number of this directory entry.
+ #[inline]
+ pub fn ino(&self) -> u64 {
+ self.d_ino
+ }
+}
+
+#[test]
+fn dir_iterator_handles_io_errors() {
+ // create a dir, keep the FD, then delete the dir
+ let tmp = tempfile::tempdir().unwrap();
+ let fd = crate::fs::openat(
+ crate::fs::CWD,
+ tmp.path(),
+ crate::fs::OFlags::RDONLY | crate::fs::OFlags::CLOEXEC,
+ crate::fs::Mode::empty(),
+ )
+ .unwrap();
+
+ let file_fd = crate::fs::openat(
+ &fd,
+ tmp.path().join("test.txt"),
+ crate::fs::OFlags::WRONLY | crate::fs::OFlags::CREATE,
+ crate::fs::Mode::RWXU,
+ )
+ .unwrap();
+
+ let mut dir = Dir::read_from(&fd).unwrap();
+
+ // Reach inside the `Dir` and replace its directory with a file, which
+ // will cause the subsequent `getdents64` to fail.
+ crate::io::dup2(&file_fd, &mut dir.fd).unwrap();
+
+ assert!(matches!(dir.next(), Some(Err(_))));
+ assert!(dir.next().is_none());
+}