use std::ffi::{CStr, CString, OsStr};
use std::io;
use std::fmt;
use std::mem;
use std::os::unix::ffi::OsStrExt;
use std::sync::Arc;
use crate::{dir::libc_ok, metadata, Metadata, SimpleType};
const DOT: [libc::c_char; 2] = [b'.' as libc::c_char, 0];
const DOTDOT: [libc::c_char; 3] = [b'.' as libc::c_char, b'.' as libc::c_char, 0];
pub struct DirIter {
dir: Arc<DirHandle>,
}
unsafe impl Send for DirIter {}
pub struct DirPosition {
pos: libc::c_long,
}
pub struct Entry {
dir: Arc<DirHandle>,
pub(crate) name: CString,
file_type: Option<SimpleType>,
ino: libc::ino_t,
}
impl Entry {
pub fn file_name(&self) -> &OsStr {
OsStr::from_bytes(self.name.to_bytes())
}
pub fn simple_type(&self) -> Option<SimpleType> {
self.file_type
}
pub fn inode(&self) -> libc::ino_t {
self.ino
}
pub fn metadata(&self) -> io::Result<Metadata> {
unsafe {
let mut stat = mem::zeroed(); libc_ok(libc::fstatat(
libc::dirfd(self.dir.raw()),
self.name.as_ptr(),
&mut stat,
libc::AT_SYMLINK_NOFOLLOW,
))?;
Ok(metadata::new(stat))
}
}
}
impl fmt::Debug for Entry {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Entry")
.field("dir", &Arc::as_ptr(&self.dir))
.field("name", &self.name)
.field("file_type", &self.file_type)
.field("ino", &self.ino)
.finish()
}
}
#[cfg(any(target_os = "linux", target_os = "fuchsia"))]
unsafe fn errno_location() -> *mut libc::c_int {
libc::__errno_location()
}
#[cfg(any(target_os = "openbsd", target_os = "netbsd", target_os = "android"))]
unsafe fn errno_location() -> *mut libc::c_int {
libc::__errno()
}
#[cfg(not(any(
target_os = "linux",
target_os = "openbsd",
target_os = "netbsd",
target_os = "android",
target_os = "fuchsia"
)))]
unsafe fn errno_location() -> *mut libc::c_int {
libc::__error()
}
impl DirIter {
unsafe fn next_entry(&mut self) -> io::Result<Option<&libc::dirent>> {
*errno_location() = 0;
let entry = libc::readdir(self.dir.raw());
if entry.is_null() {
if *errno_location() == 0 {
return Ok(None);
} else {
return Err(io::Error::last_os_error());
}
}
Ok(Some(&*entry))
}
pub fn current_position(&self) -> io::Result<DirPosition> {
let pos = unsafe { libc::telldir(self.dir.raw()) };
if pos == -1 {
Err(io::Error::last_os_error())
} else {
Ok(DirPosition { pos })
}
}
pub fn seek(&self, position: DirPosition) {
unsafe { libc::seekdir(self.dir.raw(), position.pos) };
}
pub fn rewind(&self) {
unsafe { libc::rewinddir(self.dir.raw()) };
}
}
pub fn open_dirfd(fd: libc::c_int) -> io::Result<DirIter> {
let dir = unsafe { libc::fdopendir(fd) };
if dir.is_null() {
Err(io::Error::last_os_error())
} else {
Ok(DirIter {
dir: Arc::new(DirHandle::new(dir)),
})
}
}
impl Iterator for DirIter {
type Item = io::Result<Entry>;
fn next(&mut self) -> Option<Self::Item> {
unsafe {
loop {
let dir = Arc::clone(&self.dir);
match self.next_entry() {
Err(e) => return Some(Err(e)),
Ok(None) => return None,
Ok(Some(e)) if e.d_name[..2] == DOT => continue,
Ok(Some(e)) if e.d_name[..3] == DOTDOT => continue,
Ok(Some(e)) => {
return Some(Ok(Entry {
dir,
name: CStr::from_ptr((e.d_name).as_ptr()).to_owned(),
file_type: match e.d_type {
0 => None,
libc::DT_REG => Some(SimpleType::File),
libc::DT_DIR => Some(SimpleType::Dir),
libc::DT_LNK => Some(SimpleType::Symlink),
_ => Some(SimpleType::Other),
},
ino: e.d_ino,
}));
}
}
}
}
}
}
struct DirHandle(*mut libc::DIR);
impl DirHandle {
fn new(dir: *mut libc::DIR) -> Self {
DirHandle(dir)
}
fn raw(&self) -> *mut libc::DIR {
self.0
}
}
impl Drop for DirHandle {
fn drop(&mut self) {
unsafe {
libc::closedir(self.0);
}
}
}
#[cfg(test)]
mod test {
use crate::Dir;
#[test]
fn test() {
let d = Dir::open(".").unwrap();
for e in d.list_self().unwrap() {
if let Ok(e) = e {
if let Ok(m) = e.metadata() {
eprintln!("{:?} : {:?}", e.file_name(), m);
}
}
}
}
}