#![cfg(unix)]
use std::{io, iter, marker::PhantomData, mem::MaybeUninit, ops, os::fd, path::Path};
use parking_lot::{Mutex, MutexGuard};
use crate::{
libc_imports,
null_term_str::{NullTermStr, NullTermString},
};
#[repr(transparent)]
pub struct Dir(*mut libc::DIR);
unsafe impl Send for Dir {}
unsafe impl Sync for Dir {}
impl Dir {
pub fn opendir(path: &Path) -> io::Result<Self> {
crate::c_string::run_path_with_cstr(path, |p| {
let dir_ptr = unsafe { libc::opendir(p.as_ptr()) };
if dir_ptr.is_null() {
Err(io::Error::last_os_error())
} else {
Ok(Self(dir_ptr))
}
})
}
fn do_readdir(
&mut self,
) -> Result<Option<(NullTermStr<'_>, libc_imports::dirent64_min)>, io::Error> {
let entry_ptr: *const libc_imports::dirent64 =
crate::errno::with_cleared_errno(|| unsafe { libc_imports::readdir64(self.0) })?;
if entry_ptr.is_null() {
return Ok(None);
}
debug_assert!(crate::errno::errno_is_unset());
let name = unsafe { NullTermStr::from_ptr((&raw const (*entry_ptr).d_name).cast()) };
let entry = unsafe { libc_imports::dirent64_min::new(entry_ptr) };
Ok(Some((name, entry)))
}
}
impl ops::Drop for Dir {
fn drop(&mut self) {
let r = unsafe { libc::closedir(self.0) };
if r != 0 {
let err = io::Error::last_os_error();
match err.kind() {
io::ErrorKind::Interrupted => (),
_ => panic!("unexpected error during closedir: {:?}", err),
}
}
}
}
#[repr(u8)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum EagerFileType {
File = libc::DT_REG,
Dir = libc::DT_DIR,
Symlink = libc::DT_LNK,
Other(libc::c_uchar),
}
impl EagerFileType {
pub(crate) const fn from_type(d_type: libc::c_uchar) -> Self {
match d_type {
libc::DT_REG => Self::File,
libc::DT_DIR => Self::Dir,
libc::DT_LNK => Self::Symlink,
x => Self::Other(x),
}
}
}
pub mod traits {
use std::io;
use super::{Dir, EagerFileType};
use crate::{
libc_imports,
null_term_str::{MeasuredNullTermStr, NullTermStr, NullTermString},
};
pub trait ReaddirErr {
fn into_err(self) -> io::Error;
}
pub trait ReaddirInfo {}
pub trait HasInode: ReaddirInfo {
fn ino(&self) -> libc_imports::ino64_t;
}
pub trait HasEagerType: ReaddirInfo {
fn eager_file_type(&self) -> EagerFileType;
}
pub trait HasNoEagerType: ReaddirInfo {}
pub trait HasName: ReaddirInfo {
fn name(&self) -> NullTermStr<'_>;
}
pub trait HasMeasuredName: HasName {
fn measured_name(&self) -> MeasuredNullTermStr<'_>;
}
pub trait ReaddirResult<'d> {
fn readdir(dir: &'d mut Dir) -> Self
where Self: Sized+'d;
type Info: ReaddirInfo;
type Err: ReaddirErr;
fn into_result(self) -> Result<Option<Self::Info>, Self::Err>;
type OwnInfo: ReaddirInfo;
fn into_owned(info: Self::Info, args: NullTermString) -> Self::OwnInfo;
}
}
pub mod result {
use std::io;
use super::{traits, Dir, EagerFileType};
use crate::{
libc_imports,
null_term_str::{AsNullTermStr, MeasuredNullTermStr, NullTermStr, NullTermString},
};
#[repr(transparent)]
pub struct ReaddirErr(io::Error);
impl ReaddirErr {
pub fn into_err(self) -> io::Error { self.0 }
}
impl traits::ReaddirErr for ReaddirErr {
fn into_err(self) -> io::Error { Self::into_err(self) }
}
#[derive(Copy, Clone, Debug)]
pub struct ReaddirInfo<'d, Info> {
name: NullTermStr<'d>,
info: Info,
}
impl<'d, Info> ReaddirInfo<'d, Info> {
pub fn ino(&self) -> libc_imports::ino64_t
where Info: libc_imports::HasInode {
self.info.ino()
}
pub fn eager_file_type(&self) -> EagerFileType
where Info: libc_imports::HasEagerType {
EagerFileType::from_type(self.info.eager_file_type())
}
pub const fn name(&self) -> NullTermStr<'d> { self.name }
pub fn measured_name(&self) -> MeasuredNullTermStr<'d>
where Info: libc_imports::MeasureNameLen {
self.info.measure_name_len(self.name())
}
}
impl<Info> traits::ReaddirInfo for ReaddirInfo<'_, Info> {}
impl<Info> traits::HasInode for ReaddirInfo<'_, Info>
where Info: libc_imports::HasInode
{
fn ino(&self) -> libc_imports::ino64_t { Self::ino(self) }
}
impl<Info> traits::HasEagerType for ReaddirInfo<'_, Info>
where Info: libc_imports::HasEagerType
{
fn eager_file_type(&self) -> EagerFileType { Self::eager_file_type(self) }
}
impl<Info> traits::HasNoEagerType for ReaddirInfo<'_, Info> where Info: libc_imports::HasNoEagerType {}
impl<'d, Info> traits::HasName for ReaddirInfo<'d, Info> {
fn name(&self) -> NullTermStr<'_> { Self::name(self) }
}
impl<'d, Info> traits::HasMeasuredName for ReaddirInfo<'d, Info>
where Info: libc_imports::MeasureNameLen
{
fn measured_name(&self) -> MeasuredNullTermStr<'_> { Self::measured_name(self) }
}
pub struct OwnedInfo<Info> {
name: NullTermString,
info: Info,
}
impl<Info> OwnedInfo<Info> {
pub fn from_info(info: ReaddirInfo<'_, Info>, mut s: NullTermString) -> Self
where Info: libc_imports::MeasureNameLen {
info.measured_name().clone_into(&mut s);
Self {
name: s,
info: info.info,
}
}
pub fn ino(&self) -> libc_imports::ino64_t
where Info: libc_imports::HasInode {
self.info.ino()
}
pub fn eager_file_type(&self) -> EagerFileType
where Info: libc_imports::HasEagerType {
EagerFileType::from_type(self.info.eager_file_type())
}
pub fn name(&self) -> NullTermStr<'_> { self.measured_name().as_unmeasured() }
pub fn measured_name(&self) -> MeasuredNullTermStr<'_> { self.name.as_null_term_str() }
}
impl<Info> traits::ReaddirInfo for OwnedInfo<Info> {}
impl<Info> traits::HasInode for OwnedInfo<Info>
where Info: libc_imports::HasInode
{
fn ino(&self) -> libc_imports::ino64_t { Self::ino(self) }
}
impl<Info> traits::HasEagerType for OwnedInfo<Info>
where Info: libc_imports::HasEagerType
{
fn eager_file_type(&self) -> EagerFileType { Self::eager_file_type(self) }
}
impl<Info> traits::HasNoEagerType for OwnedInfo<Info> where Info: libc_imports::HasNoEagerType {}
impl<Info> traits::HasName for OwnedInfo<Info> {
fn name(&self) -> NullTermStr<'_> { Self::name(self) }
}
impl<Info> traits::HasMeasuredName for OwnedInfo<Info> {
fn measured_name(&self) -> MeasuredNullTermStr<'_> { Self::measured_name(self) }
}
#[repr(transparent)]
pub struct ReaddirResult<'d>(
Result<Option<(NullTermStr<'d>, libc_imports::dirent64_min)>, io::Error>,
);
impl<'d> ReaddirResult<'d> {
pub fn readdir(dir: &'d mut Dir) -> Self { Self(dir.do_readdir()) }
pub fn into_result(
self,
) -> Result<Option<ReaddirInfo<'d, libc_imports::dirent64_min>>, ReaddirErr> {
match self {
Self(Err(e)) => Err(ReaddirErr(e)),
Self(Ok(v)) => Ok(v.map(|(name, info)| ReaddirInfo { name, info })),
}
}
}
impl<'d> traits::ReaddirResult<'d> for ReaddirResult<'d> {
fn readdir(dir: &'d mut Dir) -> Self
where Self: Sized+'d {
Self::readdir(dir)
}
type Info = ReaddirInfo<'d, libc_imports::dirent64_min>;
type Err = ReaddirErr;
fn into_result(self) -> Result<Option<Self::Info>, Self::Err> { Self::into_result(self) }
type OwnInfo = OwnedInfo<libc_imports::dirent64_min>;
fn into_owned(info: Self::Info, args: NullTermString) -> Self::OwnInfo {
OwnedInfo::from_info(info, args)
}
}
#[cfg(test)]
mod test {
use std::{fs, io};
use tempdir::TempDir;
use super::{super::*, *};
#[test]
fn read_single() -> io::Result<()> {
let td = TempDir::new("asdf")?;
fs::write(td.path().join("f.txt"), "asdf\n")?;
let mut dir = Dir::opendir(td.path())?;
let d = ReaddirResult::readdir(&mut dir)
.into_result()
.map_err(|e| e.into_err())?
.unwrap();
assert_eq!(d.measured_name().as_os_str().to_str().unwrap(), ".");
let d = ReaddirResult::readdir(&mut dir)
.into_result()
.map_err(|e| e.into_err())?
.unwrap();
assert_eq!(d.measured_name().as_os_str().to_str().unwrap(), "..");
let f = ReaddirResult::readdir(&mut dir)
.into_result()
.map_err(|e| e.into_err())?
.unwrap();
assert_eq!(f.measured_name().as_os_str().to_str().unwrap(), "f.txt");
assert!(ReaddirResult::readdir(&mut dir)
.into_result()
.map_err(|e| e.into_err())?
.is_none());
Ok(())
}
}
}
pub struct DirCollector<R> {
dir: Dir,
_ph: PhantomData<R>,
done: bool,
}
impl<R> DirCollector<R> {
pub const fn new(dir: Dir) -> Self {
Self {
dir,
_ph: PhantomData,
done: false,
}
}
pub const fn is_done(&self) -> bool { self.done }
}
impl<R> Iterator for DirCollector<R>
where
for<'e> R: traits::ReaddirResult<'e>,
for<'e> <R as traits::ReaddirResult<'e>>::Info: traits::HasName,
{
type Item = Result<<R as traits::ReaddirResult<'static>>::OwnInfo, io::Error>;
fn next(&mut self) -> Option<Self::Item> {
if self.done {
return None;
}
loop {
use traits::{HasName, ReaddirErr};
let Some(result) = R::readdir(&mut self.dir).into_result().transpose() else {
self.done = true;
return None;
};
return Some(match result {
Err(e) => Err(e.into_err()),
Ok(info) => {
if info.name().match_dir_entries_unmeasured() {
continue;
}
let owned = R::into_owned(info, NullTermString::new());
Ok(owned)
},
});
}
}
}
impl<R> iter::FusedIterator for DirCollector<R>
where
for<'e> R: traits::ReaddirResult<'e>,
for<'e> <R as traits::ReaddirResult<'e>>::Info: traits::HasName,
{
}
pub struct DirFd<R> {
dir: Mutex<DirCollector<R>>,
fd: fd::RawFd,
}
impl<R> DirFd<R> {
const fn new(dir: Dir, fd: fd::RawFd) -> Self {
Self {
dir: Mutex::new(DirCollector::new(dir)),
fd,
}
}
#[allow(clippy::self_named_constructors)]
pub fn dirfd(dir: Dir) -> io::Result<Self> {
let fd = unsafe { libc::dirfd(dir.0) };
if fd == -1 {
Err(io::Error::last_os_error())
} else {
Ok(Self::new(dir, fd))
}
}
pub fn fdopendir(fd: fd::RawFd) -> io::Result<Self> {
let dir_ptr = unsafe { libc::fdopendir(fd) };
if dir_ptr.is_null() {
Err(io::Error::last_os_error())
} else {
let dir = Dir(dir_ptr);
Ok(Self::new(dir, fd))
}
}
fn lock_dir(&self) -> MutexGuard<'_, DirCollector<R>> { self.dir.lock() }
pub fn dir(
&self,
) -> impl ops::DerefMut<
Target=impl Iterator<Item=Result<<R as traits::ReaddirResult<'static>>::OwnInfo, io::Error>>+iter::FusedIterator,
>
where
for<'e> R: traits::ReaddirResult<'e>,
for<'e> <R as traits::ReaddirResult<'e>>::Info: traits::HasName,
{
self.lock_dir()
}
pub const fn fd(&self) -> fd::RawFd { self.fd }
pub fn openat(&self, name: NullTermStr<'_>) -> io::Result<fd::RawFd> {
debug_assert!(
!name.match_dir_entries_unmeasured(),
"can't open dir entries '.' or '..'"
);
let fd = unsafe { libc_imports::openat64(self.fd, name.as_ptr(), libc::O_RDONLY) };
if fd == -1 {
Err(io::Error::last_os_error())
} else {
Ok(fd)
}
}
pub fn fstatat(&self, name: NullTermStr<'_>) -> io::Result<libc_imports::stat64> {
debug_assert!(
!name.match_dir_entries_unmeasured(),
"can't stat dir entries '.' or '..'"
);
let mut stat = MaybeUninit::<libc_imports::stat64>::uninit();
let rc = unsafe {
libc_imports::fstatat64(
self.fd,
name.as_ptr(),
stat.as_mut_ptr(),
libc::AT_SYMLINK_NOFOLLOW,
)
};
if rc == -1 {
Err(io::Error::last_os_error())
} else {
debug_assert_eq!(rc, 0);
let stat = unsafe { stat.assume_init() };
Ok(stat)
}
}
}
#[inline]
fn fd_is_invalid(fd: fd::RawFd) -> bool {
(unsafe { libc::fcntl(fd, libc::F_GETFD) } == -1) && crate::errno::errno() == libc::EBADF
}
impl<R> ops::Drop for DirFd<R> {
fn drop(&mut self) {
debug_assert!(!fd_is_invalid(self.fd));
}
}