summaryrefslogtreecommitdiff
path: root/vendor/rustix/src/backend/libc/fs/dir.rs
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/rustix/src/backend/libc/fs/dir.rs')
-rw-r--r--vendor/rustix/src/backend/libc/fs/dir.rs423
1 files changed, 423 insertions, 0 deletions
diff --git a/vendor/rustix/src/backend/libc/fs/dir.rs b/vendor/rustix/src/backend/libc/fs/dir.rs
new file mode 100644
index 0000000..82a0a90
--- /dev/null
+++ b/vendor/rustix/src/backend/libc/fs/dir.rs
@@ -0,0 +1,423 @@
+#[cfg(not(any(solarish, target_os = "haiku", target_os = "nto", target_os = "vita")))]
+use super::types::FileType;
+use crate::backend::c;
+use crate::backend::conv::owned_fd;
+use crate::fd::{AsFd, BorrowedFd, OwnedFd};
+use crate::ffi::{CStr, CString};
+use crate::fs::{fcntl_getfl, openat, Mode, OFlags};
+#[cfg(not(target_os = "vita"))]
+use crate::fs::{fstat, Stat};
+#[cfg(not(any(
+ solarish,
+ target_os = "haiku",
+ target_os = "netbsd",
+ target_os = "nto",
+ target_os = "redox",
+ target_os = "vita",
+ target_os = "wasi",
+)))]
+use crate::fs::{fstatfs, StatFs};
+#[cfg(not(any(
+ solarish,
+ target_os = "haiku",
+ target_os = "redox",
+ target_os = "vita",
+ target_os = "wasi"
+)))]
+use crate::fs::{fstatvfs, StatVfs};
+use crate::io;
+#[cfg(not(any(target_os = "fuchsia", target_os = "vita", target_os = "wasi")))]
+#[cfg(feature = "process")]
+use crate::process::fchdir;
+use alloc::borrow::ToOwned;
+#[cfg(not(any(linux_like, target_os = "hurd")))]
+use c::readdir as libc_readdir;
+#[cfg(any(linux_like, target_os = "hurd"))]
+use c::readdir64 as libc_readdir;
+use core::fmt;
+use core::ptr::NonNull;
+use libc_errno::{errno, set_errno, Errno};
+
+/// `DIR*`
+pub struct Dir {
+ /// The `libc` `DIR` pointer.
+ libc_dir: NonNull<c::DIR>,
+
+ /// Have we seen any errors in this iteration?
+ any_errors: bool,
+}
+
+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> {
+ let raw = owned_fd(fd);
+ unsafe {
+ let libc_dir = c::fdopendir(raw);
+
+ if let Some(libc_dir) = NonNull::new(libc_dir) {
+ Ok(Self {
+ libc_dir,
+ any_errors: false,
+ })
+ } else {
+ let err = io::Errno::last_os_error();
+ let _ = c::close(raw);
+ Err(err)
+ }
+ }
+ }
+
+ /// 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]
+ #[allow(unused_mut)]
+ fn _read_from(fd: BorrowedFd<'_>) -> io::Result<Self> {
+ let mut any_errors = false;
+
+ // Given an arbitrary `OwnedFd`, it's impossible to know whether the
+ // user holds a `dup`'d copy which could continue to modify the
+ // file description state, which would cause Undefined Behavior after
+ // our call to `fdopendir`. To prevent this, we obtain an independent
+ // `OwnedFd`.
+ let flags = fcntl_getfl(fd)?;
+ let fd_for_dir = match openat(fd, cstr!("."), flags | OFlags::CLOEXEC, Mode::empty()) {
+ Ok(fd) => fd,
+ #[cfg(not(target_os = "wasi"))]
+ Err(io::Errno::NOENT) => {
+ // If "." doesn't exist, it means the directory was removed.
+ // We treat that as iterating through a directory with no
+ // entries.
+ any_errors = true;
+ crate::io::dup(fd)?
+ }
+ Err(err) => return Err(err),
+ };
+
+ let raw = owned_fd(fd_for_dir);
+ unsafe {
+ let libc_dir = c::fdopendir(raw);
+
+ if let Some(libc_dir) = NonNull::new(libc_dir) {
+ Ok(Self {
+ libc_dir,
+ any_errors,
+ })
+ } else {
+ let err = io::Errno::last_os_error();
+ let _ = c::close(raw);
+ Err(err)
+ }
+ }
+ }
+
+ /// `rewinddir(self)`
+ #[inline]
+ pub fn rewind(&mut self) {
+ self.any_errors = false;
+ unsafe { c::rewinddir(self.libc_dir.as_ptr()) }
+ }
+
+ /// `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;
+ }
+
+ set_errno(Errno(0));
+ let dirent_ptr = unsafe { libc_readdir(self.libc_dir.as_ptr()) };
+ if dirent_ptr.is_null() {
+ let curr_errno = errno().0;
+ if curr_errno == 0 {
+ // We successfully reached the end of the stream.
+ None
+ } else {
+ // `errno` is unknown or non-zero, so an error occurred.
+ self.any_errors = true;
+ Some(Err(io::Errno(curr_errno)))
+ }
+ } else {
+ // We successfully read an entry.
+ unsafe {
+ let dirent = &*dirent_ptr;
+
+ // We have our own copy of OpenBSD's dirent; check that the
+ // layout minimally matches libc's.
+ #[cfg(target_os = "openbsd")]
+ check_dirent_layout(dirent);
+
+ let result = DirEntry {
+ #[cfg(not(any(
+ solarish,
+ target_os = "aix",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "vita"
+ )))]
+ d_type: dirent.d_type,
+
+ #[cfg(not(any(freebsdlike, netbsdlike, target_os = "vita")))]
+ d_ino: dirent.d_ino,
+
+ #[cfg(any(freebsdlike, netbsdlike))]
+ d_fileno: dirent.d_fileno,
+
+ name: CStr::from_ptr(dirent.d_name.as_ptr()).to_owned(),
+ };
+
+ Some(Ok(result))
+ }
+ }
+ }
+
+ /// `fstat(self)`
+ #[cfg(not(target_os = "vita"))]
+ #[inline]
+ pub fn stat(&self) -> io::Result<Stat> {
+ fstat(unsafe { BorrowedFd::borrow_raw(c::dirfd(self.libc_dir.as_ptr())) })
+ }
+
+ /// `fstatfs(self)`
+ #[cfg(not(any(
+ solarish,
+ target_os = "haiku",
+ target_os = "netbsd",
+ target_os = "nto",
+ target_os = "redox",
+ target_os = "vita",
+ target_os = "wasi",
+ )))]
+ #[inline]
+ pub fn statfs(&self) -> io::Result<StatFs> {
+ fstatfs(unsafe { BorrowedFd::borrow_raw(c::dirfd(self.libc_dir.as_ptr())) })
+ }
+
+ /// `fstatvfs(self)`
+ #[cfg(not(any(
+ solarish,
+ target_os = "haiku",
+ target_os = "redox",
+ target_os = "vita",
+ target_os = "wasi"
+ )))]
+ #[inline]
+ pub fn statvfs(&self) -> io::Result<StatVfs> {
+ fstatvfs(unsafe { BorrowedFd::borrow_raw(c::dirfd(self.libc_dir.as_ptr())) })
+ }
+
+ /// `fchdir(self)`
+ #[cfg(feature = "process")]
+ #[cfg(not(any(target_os = "fuchsia", target_os = "vita", target_os = "wasi")))]
+ #[cfg_attr(doc_cfg, doc(cfg(feature = "process")))]
+ #[inline]
+ pub fn chdir(&self) -> io::Result<()> {
+ fchdir(unsafe { BorrowedFd::borrow_raw(c::dirfd(self.libc_dir.as_ptr())) })
+ }
+}
+
+/// `Dir` implements `Send` but not `Sync`, because we use `readdir` which is
+/// not guaranteed to be thread-safe. Users can wrap this in a `Mutex` if they
+/// need `Sync`, which is effectively what'd need to do to implement `Sync`
+/// ourselves.
+unsafe impl Send for Dir {}
+
+impl Drop for Dir {
+ #[inline]
+ fn drop(&mut self) {
+ unsafe { c::closedir(self.libc_dir.as_ptr()) };
+ }
+}
+
+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 {
+ let mut s = f.debug_struct("Dir");
+ #[cfg(not(target_os = "vita"))]
+ s.field("fd", unsafe { &c::dirfd(self.libc_dir.as_ptr()) });
+ s.finish()
+ }
+}
+
+/// `struct dirent`
+#[derive(Debug)]
+pub struct DirEntry {
+ #[cfg(not(any(
+ solarish,
+ target_os = "aix",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "vita"
+ )))]
+ d_type: u8,
+
+ #[cfg(not(any(freebsdlike, netbsdlike, target_os = "vita")))]
+ d_ino: c::ino_t,
+
+ #[cfg(any(freebsdlike, netbsdlike))]
+ d_fileno: c::ino_t,
+
+ 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.
+ #[cfg(not(any(
+ solarish,
+ target_os = "aix",
+ target_os = "haiku",
+ target_os = "nto",
+ target_os = "vita"
+ )))]
+ #[inline]
+ pub fn file_type(&self) -> FileType {
+ FileType::from_dirent_d_type(self.d_type)
+ }
+
+ /// Return the inode number of this directory entry.
+ #[cfg(not(any(freebsdlike, netbsdlike, target_os = "vita")))]
+ #[inline]
+ pub fn ino(&self) -> u64 {
+ self.d_ino as u64
+ }
+
+ /// Return the inode number of this directory entry.
+ #[cfg(any(freebsdlike, netbsdlike))]
+ #[inline]
+ pub fn ino(&self) -> u64 {
+ #[allow(clippy::useless_conversion)]
+ self.d_fileno.into()
+ }
+}
+
+/// libc's OpenBSD `dirent` has a private field so we can't construct it
+/// directly, so we declare it ourselves to make all fields accessible.
+#[cfg(target_os = "openbsd")]
+#[repr(C)]
+#[derive(Debug)]
+struct libc_dirent {
+ d_fileno: c::ino_t,
+ d_off: c::off_t,
+ d_reclen: u16,
+ d_type: u8,
+ d_namlen: u8,
+ __d_padding: [u8; 4],
+ d_name: [c::c_char; 256],
+}
+
+/// We have our own copy of OpenBSD's dirent; check that the layout
+/// minimally matches libc's.
+#[cfg(target_os = "openbsd")]
+fn check_dirent_layout(dirent: &c::dirent) {
+ use crate::utils::as_ptr;
+
+ // Check that the basic layouts match.
+ #[cfg(test)]
+ {
+ assert_eq_size!(libc_dirent, c::dirent);
+ assert_eq_size!(libc_dirent, c::dirent);
+ }
+
+ // Check that the field offsets match.
+ assert_eq!(
+ {
+ let z = libc_dirent {
+ d_fileno: 0_u64,
+ d_off: 0_i64,
+ d_reclen: 0_u16,
+ d_type: 0_u8,
+ d_namlen: 0_u8,
+ __d_padding: [0_u8; 4],
+ d_name: [0 as c::c_char; 256],
+ };
+ let base = as_ptr(&z) as usize;
+ (
+ (as_ptr(&z.d_fileno) as usize) - base,
+ (as_ptr(&z.d_off) as usize) - base,
+ (as_ptr(&z.d_reclen) as usize) - base,
+ (as_ptr(&z.d_type) as usize) - base,
+ (as_ptr(&z.d_namlen) as usize) - base,
+ (as_ptr(&z.d_name) as usize) - base,
+ )
+ },
+ {
+ let z = dirent;
+ let base = as_ptr(z) as usize;
+ (
+ (as_ptr(&z.d_fileno) as usize) - base,
+ (as_ptr(&z.d_off) as usize) - base,
+ (as_ptr(&z.d_reclen) as usize) - base,
+ (as_ptr(&z.d_type) as usize) - base,
+ (as_ptr(&z.d_namlen) as usize) - base,
+ (as_ptr(&z.d_name) as usize) - base,
+ )
+ }
+ );
+}
+
+#[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 `readdir` to fail.
+ unsafe {
+ let raw_fd = c::dirfd(dir.libc_dir.as_ptr());
+ let mut owned_fd: crate::fd::OwnedFd = crate::fd::FromRawFd::from_raw_fd(raw_fd);
+ crate::io::dup2(&file_fd, &mut owned_fd).unwrap();
+ core::mem::forget(owned_fd);
+ }
+
+ // FreeBSD and macOS seem to read some directory entries before we call
+ // `.next()`.
+ #[cfg(any(apple, freebsdlike))]
+ {
+ dir.rewind();
+ }
+
+ assert!(matches!(dir.next(), Some(Err(_))));
+ assert!(dir.next().is_none());
+}