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
}
}
pub(crate) struct Metadata(libc::stat64);
impl Metadata {
pub fn size(&self) -> u64 {
self.0.st_size as u64
}
pub fn modified(&self) -> std::time::Duration {
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::<libc::stat64>::uninit();
let result = unsafe {
libc::fstatat64(
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 libc::dirent64,
name_len: OnceCell<usize>,
metadata: OnceCell<Result<Metadata>>,
}
impl DirEntry {
pub fn file_type(&self) -> FileType {
FileType(unsafe { *self.entry }.d_type)
}
pub fn file_name(&self) -> &[u8] {
unsafe {
let name = (*self.entry).d_name.as_ptr() as *const c_char;
let name_len = *self.name_len.get_or_init(|| {
let max_len = std::mem::size_of_val(&(*self.entry).d_name);
assert!(max_len >= 128);
libc::strnlen(name, max_len)
});
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::<libc::stat64>::uninit();
let result = unsafe {
libc::fstatat64(
dirfd,
(*self.entry).d_name.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() }))
});
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) };
}
}
}
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 { libc::readdir64(dir) };
if entry.is_null() {
unsafe { libc::closedir(dir) };
self.dir = 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)
}
}