use crate::scan::error::Error;
use crate::scan::error::ErrorOperation;
use crate::scan::error::IntoScanError;
use crate::scan::Result;
use nix::fcntl::AtFlags;
use nix::fcntl::OFlag;
use nix::sys::stat::Mode;
use nix::sys::stat::SFlag;
use std::ffi::CStr;
use std::ffi::OsStr;
use std::fs::File;
use std::fs::Metadata;
use std::fs::OpenOptions;
use std::io;
use std::os::fd::AsRawFd;
use std::os::fd::FromRawFd;
use std::os::unix::ffi::OsStrExt;
use std::os::unix::fs::MetadataExt;
use std::path::Path;
use std::path::PathBuf;
fn c_str_to_os_str(cstr: &CStr) -> &OsStr {
OsStr::from_bytes(cstr.to_bytes())
}
pub(super) fn open_at<P: AsRef<Path>>(dir: &ReadDir, path: P) -> Result<File> {
let path = path.as_ref();
let dirfd = dir.inner.as_raw_fd();
let fd = nix::fcntl::openat(
dirfd,
path,
OFlag::O_RDONLY | OFlag::O_CLOEXEC | OFlag::O_NOFOLLOW,
Mode::empty(),
);
match fd {
Ok(fd) => Ok(unsafe { File::from_raw_fd(fd) }),
Err(_) => Err(Error::last_os_error(
dir.path.join(path),
ErrorOperation::OpenFile,
)),
}
}
pub(super) fn read_dir<P: AsRef<Path>>(path: P) -> io::Result<ReadDir> {
let dir = OpenOptions::new().read(true).open(path)?;
ReadDir::from_file(PathBuf::new(), dir).map_err(Error::into_source)
}
pub(super) fn read_dir_at<P: AsRef<Path>>(dir: &ReadDir, path: P) -> Result<ReadDir> {
let path = path.as_ref();
let full_path = dir.path.join(path);
let dirfd = dir.inner.as_raw_fd();
let fd = nix::fcntl::openat(
dirfd,
path,
OFlag::O_RDONLY | OFlag::O_CLOEXEC | OFlag::O_NOFOLLOW | OFlag::O_DIRECTORY,
Mode::empty(),
);
let subdir = match fd {
Ok(fd) => unsafe { File::from_raw_fd(fd) },
Err(_) => {
return Err(Error::last_os_error(
full_path,
ErrorOperation::OpenDirectory,
))
}
};
ReadDir::from_file(full_path, subdir)
}
fn file_type(dir: &ReadDir, dir_entry: &nix::dir::Entry) -> io::Result<FileType> {
if let Some(file_type) = dir_entry.file_type() {
Ok(match file_type {
nix::dir::Type::File => FileType::File,
nix::dir::Type::Directory => FileType::Directory,
_ => FileType::Other,
})
} else {
let dirfd = dir.inner.as_raw_fd();
let stat =
nix::sys::stat::fstatat(dirfd, dir_entry.file_name(), AtFlags::AT_SYMLINK_NOFOLLOW)?;
let file_type = SFlag::S_IFMT & SFlag::from_bits_truncate(stat.st_mode);
Ok(match file_type {
SFlag::S_IFREG => FileType::File,
SFlag::S_IFDIR => FileType::Directory,
_ => FileType::Other,
})
}
}
pub(super) struct ReadDir {
inner: nix::dir::OwningIter,
pub(super) path: PathBuf,
pub(super) metadata: Metadata,
}
impl ReadDir {
fn from_file(path: PathBuf, file: File) -> Result<Self> {
let metadata = match file.metadata() {
Ok(metadata) => metadata,
Err(err) => return Err(err.into_scan_error(path, ErrorOperation::ReadMetadata)),
};
let inner = match nix::dir::Dir::from(file) {
Ok(dir) => dir.into_iter(),
Err(err) => return Err(err.into_scan_error(path, ErrorOperation::ReadDirectory)),
};
Ok(Self {
inner,
path,
metadata,
})
}
}
impl Iterator for ReadDir {
type Item = Result<DirEntry>;
fn next(&mut self) -> Option<Self::Item> {
let dir_entry = match self.inner.next() {
Some(Ok(dir_entry)) => dir_entry,
Some(Err(_)) => {
return Some(Err(Error::last_os_error(
self.path.clone(),
ErrorOperation::ReadDirectory,
)))
}
None => return None,
};
let file_type = match file_type(self, &dir_entry) {
Ok(file_type) => file_type,
Err(err) => {
let dir_entry_path = self.path.join(c_str_to_os_str(dir_entry.file_name()));
return Some(Err(
err.into_scan_error(dir_entry_path, ErrorOperation::ReadMetadata)
));
}
};
Some(Ok(DirEntry {
inner: dir_entry,
file_type,
}))
}
}
pub(super) struct DirEntry {
inner: nix::dir::Entry,
pub(super) file_type: FileType,
}
impl DirEntry {
pub(super) fn file_name(&self) -> &OsStr {
c_str_to_os_str(self.inner.file_name())
}
}
pub(super) enum FileType {
File,
Directory,
Other,
}
pub(super) trait SameFile {
fn identifier(&self) -> (u64, u64);
#[inline]
fn is_same_as(&self, other: &Self) -> bool {
self.identifier() == other.identifier()
}
}
impl SameFile for Metadata {
#[inline]
fn identifier(&self) -> (u64, u64) {
(self.dev(), self.ino())
}
}
impl SameFile for ReadDir {
#[inline]
fn identifier(&self) -> (u64, u64) {
self.metadata.identifier()
}
}