use std::ffi::{CStr, CString, OsStr};
use std::io;
use std::os::unix::prelude::*;
use std::ptr::NonNull;
use std::sync::Arc;
use crate::util;
use super::{FileType, Metadata};
#[derive(Debug)]
struct Dstream {
dir: NonNull<libc::DIR>,
}
impl Dstream {
#[inline]
fn as_ptr(&self) -> *mut libc::DIR {
self.dir.as_ptr()
}
}
impl AsRawFd for Dstream {
#[inline]
fn as_raw_fd(&self) -> RawFd {
unsafe { libc::dirfd(self.dir.as_ptr()) }
}
}
impl Drop for Dstream {
#[inline]
fn drop(&mut self) {
unsafe {
libc::closedir(self.dir.as_ptr());
}
}
}
#[derive(Debug)]
pub struct ReadDirIter {
dstream: Arc<Dstream>,
}
impl ReadDirIter {
#[inline]
pub(crate) fn new_consume(fd: RawFd) -> io::Result<Self> {
match NonNull::new(unsafe { libc::fdopendir(fd) }) {
Some(dir) => Ok(Self {
dstream: Arc::new(Dstream { dir }),
}),
None => {
let err = io::Error::last_os_error();
unsafe {
libc::close(fd);
}
Err(err)
}
}
}
#[inline]
pub fn rewind(&mut self) {
unsafe {
libc::rewinddir(self.dstream.as_ptr());
}
}
#[inline]
pub fn tell(&self) -> SeekPos {
SeekPos(unsafe { libc::telldir(self.dstream.as_ptr()) })
}
#[inline]
pub fn seek(&mut self, pos: SeekPos) {
unsafe {
libc::seekdir(self.dstream.as_ptr(), pos.0);
}
}
}
impl Iterator for ReadDirIter {
type Item = io::Result<Entry>;
fn next(&mut self) -> Option<Self::Item> {
unsafe {
*util::errno_ptr() = 0;
}
loop {
let raw_entry = unsafe { libc::readdir(self.dstream.as_ptr()) };
if raw_entry.is_null() {
return match unsafe { *util::errno_ptr() } {
0 => None,
eno => Some(Err(io::Error::from_raw_os_error(eno))),
};
} else if let Some(entry) = unsafe { Entry::from_raw(&self, raw_entry) } {
return Some(Ok(entry));
}
}
}
}
#[derive(Copy, Clone, Debug)]
pub struct SeekPos(libc::c_long);
#[derive(Clone, Debug)]
pub struct Entry {
fname: CString,
ino: u64,
ftype: Option<FileType>,
dstream: Arc<Dstream>,
}
impl Entry {
#[inline]
unsafe fn from_raw(rdir_it: &ReadDirIter, entry: *const libc::dirent) -> Option<Self> {
let entry = &*entry;
cfg_if::cfg_if! {
if #[cfg(any(
target_os = "freebsd",
target_os = "dragonfly",
target_os = "openbsd",
target_os = "netbsd",
target_os = "macos",
))] {
debug_assert!((entry.d_namlen as usize) < entry.d_name.len());
let fname_bytes = std::slice::from_raw_parts(
entry.d_name.as_ptr() as *const _, entry.d_namlen as usize + 1
);
#[cfg(debug_assertions)]
CStr::from_bytes_with_nul(fname_bytes).unwrap();
let c_fname = CStr::from_bytes_with_nul_unchecked(fname_bytes);
} else {
let c_fname = CStr::from_ptr(entry.d_name.as_ptr());
}
}
let fname_bytes = c_fname.to_bytes();
if fname_bytes == b"." || fname_bytes == b".." {
return None;
}
cfg_if::cfg_if! {
if #[cfg(any(
target_os = "freebsd",
target_os = "dragonfly",
target_os = "openbsd",
target_os = "netbsd",
))] {
let ino = entry.d_fileno as u64;
} else {
let ino = entry.d_ino as u64;
}
}
Some(Self {
fname: c_fname.to_owned(),
ino,
ftype: match entry.d_type {
libc::DT_REG => Some(FileType::File),
libc::DT_DIR => Some(FileType::Directory),
libc::DT_LNK => Some(FileType::Symlink),
libc::DT_SOCK => Some(FileType::Socket),
libc::DT_BLK => Some(FileType::Block),
libc::DT_CHR => Some(FileType::Character),
libc::DT_FIFO => Some(FileType::Fifo),
_ => None,
},
dstream: rdir_it.dstream.clone(),
})
}
#[inline]
pub fn name(&self) -> &OsStr {
OsStr::from_bytes(self.fname.as_bytes())
}
#[inline]
pub fn ino(&self) -> u64 {
self.ino
}
#[inline]
pub fn file_type(&self) -> Option<FileType> {
self.ftype
}
pub fn metadata(&self) -> io::Result<Metadata> {
util::fstatat(
self.dstream.as_raw_fd(),
&self.fname,
libc::AT_SYMLINK_NOFOLLOW,
)
.map(Metadata::new)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_consume_error() {
assert_eq!(
ReadDirIter::new_consume(-1).unwrap_err().raw_os_error(),
Some(libc::EBADF)
);
}
}