use crate::errno::Errno;
use crate::fcntl::{self, OFlag};
use crate::sys;
use crate::{NixPath, Result};
use cfg_if::cfg_if;
use std::ffi::{CStr, CString};
use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd};
use std::ptr;
#[derive(Debug, Eq, Hash, PartialEq)]
pub struct Dir(ptr::NonNull<libc::DIR>);
impl Dir {
pub fn open<P: ?Sized + NixPath>(
path: &P,
oflag: OFlag,
mode: sys::stat::Mode,
) -> Result<Self> {
let fd = fcntl::open(path, oflag, mode)?;
Dir::from_fd(fd)
}
pub fn openat<Fd: std::os::fd::AsFd, P: ?Sized + NixPath>(
dirfd: Fd,
path: &P,
oflag: OFlag,
mode: sys::stat::Mode,
) -> Result<Self> {
let fd = fcntl::openat(dirfd, path, oflag, mode)?;
Dir::from_fd(fd)
}
#[inline]
#[deprecated(
since = "0.30.0",
note = "Deprecate this since it is not I/O-safe, use from_fd instead."
)]
pub unsafe fn from<F: IntoRawFd>(fd: F) -> Result<Self> {
use std::os::fd::FromRawFd;
use std::os::fd::OwnedFd;
let owned_fd = unsafe { OwnedFd::from_raw_fd(fd.into_raw_fd()) };
Dir::from_fd(owned_fd)
}
#[doc(alias("fdopendir"))]
pub fn from_fd(fd: std::os::fd::OwnedFd) -> Result<Self> {
let raw_fd = fd.into_raw_fd();
let d = ptr::NonNull::new(unsafe { libc::fdopendir(raw_fd) })
.ok_or(Errno::last())?;
Ok(Dir(d))
}
pub fn iter(&mut self) -> Iter<'_> {
Iter(self)
}
}
unsafe impl Send for Dir {}
impl std::os::fd::AsFd for Dir {
fn as_fd(&self) -> std::os::fd::BorrowedFd<'_> {
let raw_fd = self.as_raw_fd();
unsafe { std::os::fd::BorrowedFd::borrow_raw(raw_fd) }
}
}
impl AsRawFd for Dir {
fn as_raw_fd(&self) -> RawFd {
unsafe { libc::dirfd(self.0.as_ptr()) }
}
}
impl Drop for Dir {
fn drop(&mut self) {
let e = Errno::result(unsafe { libc::closedir(self.0.as_ptr()) });
if !std::thread::panicking() && e == Err(Errno::EBADF) {
panic!("Closing an invalid file descriptor!");
};
}
}
#[allow(clippy::needless_pass_by_ref_mut)]
fn readdir(dir: &mut Dir) -> Option<Result<Entry>> {
Errno::clear();
unsafe {
let de = libc::readdir(dir.0.as_ptr());
if de.is_null() {
if Errno::last_raw() == 0 {
None
} else {
Some(Err(Errno::last()))
}
} else {
Some(Ok(Entry::from_raw(&*de)))
}
}
}
#[derive(Debug, Eq, Hash, PartialEq)]
pub struct Iter<'d>(&'d mut Dir);
impl Iterator for Iter<'_> {
type Item = Result<Entry>;
fn next(&mut self) -> Option<Self::Item> {
readdir(self.0)
}
}
impl Drop for Iter<'_> {
fn drop(&mut self) {
unsafe { libc::rewinddir((self.0).0.as_ptr()) }
}
}
#[derive(Debug, Eq, Hash, PartialEq)]
pub struct OwningIter(Dir);
impl Iterator for OwningIter {
type Item = Result<Entry>;
fn next(&mut self) -> Option<Self::Item> {
readdir(&mut self.0)
}
}
impl AsRawFd for OwningIter {
fn as_raw_fd(&self) -> RawFd {
self.0.as_raw_fd()
}
}
impl IntoIterator for Dir {
type Item = Result<Entry>;
type IntoIter = OwningIter;
fn into_iter(self) -> Self::IntoIter {
OwningIter(self)
}
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct Entry {
ino: u64,
type_: Option<Type>,
name: CString,
}
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
pub enum Type {
Fifo,
CharacterDevice,
Directory,
BlockDevice,
File,
Symlink,
Socket,
}
impl Entry {
pub fn ino(&self) -> u64 {
self.ino
}
pub fn file_name(&self) -> &CStr {
self.name.as_c_str()
}
pub fn file_type(&self) -> Option<Type> {
self.type_
}
#[allow(clippy::useless_conversion)] #[allow(clippy::unnecessary_cast)]
fn from_raw(de: &libc::dirent) -> Self {
cfg_if! {
if #[cfg(any(target_os = "aix",
target_os = "emscripten",
target_os = "fuchsia",
target_os = "haiku",
target_os = "hurd",
target_os = "cygwin",
solarish,
linux_android,
apple_targets))] {
let ino = de.d_ino as u64;
} else {
let ino = u64::from(de.d_fileno);
}
}
cfg_if! {
if #[cfg(not(any(solarish, target_os = "aix", target_os = "haiku")))] {
let type_ = match de.d_type {
libc::DT_FIFO => Some(Type::Fifo),
libc::DT_CHR => Some(Type::CharacterDevice),
libc::DT_DIR => Some(Type::Directory),
libc::DT_BLK => Some(Type::BlockDevice),
libc::DT_REG => Some(Type::File),
libc::DT_LNK => Some(Type::Symlink),
libc::DT_SOCK => Some(Type::Socket),
_ => None,
};
} else {
#[cfg(any(solarish, target_os = "aix", target_os = "haiku"))]
let type_ = None;
}
}
let name = unsafe { CStr::from_ptr(de.d_name.as_ptr()) }.to_owned();
Entry { ino, type_, name }
}
}