use std::cell::OnceCell;
use std::ffi::CStr;
use std::ffi::c_char;
use std::os::unix::ffi::OsStringExt;
use std::path::Path;
use crate::Tag;
type Result<T> = std::result::Result<T, std::io::Error>;
pub(crate) struct FileType(u8);
impl FileType {
pub fn is_dir(&self) -> bool {
self.0 == libc::DT_DIR
}
pub fn is_unknown(&self) -> bool {
self.0 == libc::DT_UNKNOWN
}
}
#[cfg(any(all(target_os = "linux", not(target_env = "musl")), target_os = "hurd"))]
mod libc64 {
pub(super) use libc::stat64 as stat;
pub(super) use libc::fstatat64 as fstatat;
pub(super) use libc::dirent64 as dirent;
pub(super) use libc::readdir64 as readdir;
}
#[cfg(not(any(all(target_os = "linux", not(target_env = "musl")), target_os = "hurd")))]
mod libc64 {
pub(super) use libc::stat;
pub(super) use libc::fstatat;
pub(super) use libc::dirent;
pub(super) use libc::readdir;
}
pub(crate) struct Metadata(libc64::stat);
impl Metadata {
pub fn size(&self) -> u64 {
self.0.st_size as u64
}
pub fn modified(&self) -> std::time::Duration {
#[cfg(any(target_os = "openbsd", target_os = "netbsd"))]
return std::time::Duration::new(
self.0.st_mtime as u64,
self.0.st_mtimensec as u32);
#[cfg(not(any(target_os = "openbsd", target_os = "netbsd")))]
return std::time::Duration::new(
self.0.st_mtime as u64,
self.0.st_mtime_nsec as u32);
}
pub fn is_dir(&self) -> bool {
(self.0.st_mode & libc::S_IFMT) == libc::S_IFDIR
}
}
impl std::convert::From<&crate::unixdir::Metadata> for Tag {
fn from(m: &Metadata) -> Self {
let d = m.modified();
let size = m.size();
Tag::new(d.as_secs(), d.subsec_nanos(), size, m.is_dir())
}
}
impl Metadata {
fn fstat(dir: *mut libc::DIR,
nul_terminated_filename: &[u8])
-> Result<Self>
{
assert_eq!(nul_terminated_filename[nul_terminated_filename.len() - 1],
0);
let dirfd = unsafe { libc::dirfd(dir) };
if dirfd == -1 {
return Err(std::io::Error::last_os_error());
}
let mut statbuf = std::mem::MaybeUninit::<libc64::stat>::uninit();
let result = unsafe {
libc64::fstatat(
dirfd,
nul_terminated_filename.as_ptr() as *const c_char,
statbuf.as_mut_ptr(),
libc::AT_SYMLINK_NOFOLLOW,
)
};
if result == -1 {
return Err(std::io::Error::last_os_error());
}
Ok(Metadata(unsafe { statbuf.assume_init() }))
}
}
pub(crate) struct DirEntry {
dir: *mut libc::DIR,
entry: *mut libc64::dirent,
name_len: OnceCell<usize>,
metadata: OnceCell<Result<Metadata>>,
}
impl DirEntry {
fn entry_d_type(&self) -> libc::c_uchar {
unsafe { (&*self.entry).d_type }
}
fn entry_d_name(&self) -> *const c_char {
unsafe { (&*self.entry).d_name.as_ptr() as *const c_char }
}
pub fn file_type(&self) -> FileType {
FileType(self.entry_d_type())
}
pub fn file_name(&self) -> &[u8] {
unsafe {
let name = self.entry_d_name();
let name_len = *self.name_len.get_or_init(|| {
libc::strlen(name)
});
std::slice::from_raw_parts(
name as *const u8,
name_len)
}
}
pub fn metadata(&self) -> Result<&Metadata> {
let result = self.metadata.get_or_init(|| {
let dirfd = unsafe { libc::dirfd(self.dir) };
if dirfd == -1 {
return Err(std::io::Error::last_os_error());
}
let mut statbuf = std::mem::MaybeUninit::<libc64::stat>::uninit();
let result = unsafe {
libc64::fstatat(
dirfd,
self.entry_d_name(),
statbuf.as_mut_ptr(),
libc::AT_SYMLINK_NOFOLLOW,
)
};
if result == -1 {
return Err(std::io::Error::last_os_error());
}
Ok(Metadata(unsafe { statbuf.assume_init() }))
});
match result {
Ok(metadata) => Ok(metadata),
Err(err) => {
if let Some(underlying) = err.get_ref() {
Err(std::io::Error::new(
err.kind(),
underlying.to_string()))
} else {
Err(std::io::Error::from(err.kind()))
}
},
}
}
}
pub(crate) struct Dir {
dir: Option<*mut libc::DIR>,
entry: Option<DirEntry>,
}
impl Drop for Dir {
fn drop(&mut self) {
if let Some(dir) = self.dir.take() {
unsafe { libc::closedir(dir) };
}
self.entry = None;
}
}
impl Dir {
pub fn open(dir: &Path) -> Result<Self> {
let mut dir = dir.as_os_str().to_os_string().into_vec();
dir.push(0);
let dir = unsafe { CStr::from_ptr(dir.as_ptr() as *const c_char) };
let dir = unsafe { libc::opendir(dir.as_ptr().cast()) };
if dir.is_null() {
return Err(std::io::Error::last_os_error());
}
let dir = Dir {
dir: Some(dir),
entry: None,
};
Ok(dir)
}
pub fn readdir(&mut self) -> Option<&mut DirEntry> {
let dir = self.dir?;
let entry = unsafe { libc64::readdir(dir) };
if entry.is_null() {
unsafe { libc::closedir(dir) };
self.dir = None;
self.entry = None;
return None;
}
self.entry = Some(DirEntry {
dir,
entry,
name_len: OnceCell::default(),
metadata: OnceCell::default(),
});
self.entry.as_mut()
}
pub fn fstat(&mut self, nul_terminated_filename: &[u8]) -> Result<Metadata> {
let dir = self.dir.ok_or_else(|| {
std::io::Error::new(std::io::ErrorKind::Other, "Directory closed")
})?;
Metadata::fstat(dir, nul_terminated_filename)
}
}