use std::cell::OnceCell;
use std::error::Error;
use std::ffi::OsStr;
use std::fmt::{self, Display, Formatter};
use std::fs::{self, Metadata};
use std::io::{self, ErrorKind};
#[cfg(unix)]
use std::os::unix::fs::FileTypeExt;
use std::path::{Path, PathBuf};
use walkdir::DirEntry;
use super::Follow;
#[derive(Debug)]
enum Entry {
Explicit(PathBuf, usize),
WalkDir(DirEntry),
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub enum FileType {
Unknown,
Fifo,
CharDevice,
Directory,
BlockDevice,
Regular,
Symlink,
Socket,
}
impl FileType {
pub fn is_dir(self) -> bool {
self == Self::Directory
}
pub fn is_file(self) -> bool {
self == Self::Regular
}
pub fn is_symlink(self) -> bool {
self == Self::Symlink
}
}
impl From<fs::FileType> for FileType {
fn from(t: fs::FileType) -> Self {
if t.is_dir() {
return Self::Directory;
}
if t.is_file() {
return Self::Regular;
}
if t.is_symlink() {
return Self::Symlink;
}
#[cfg(unix)]
{
if t.is_fifo() {
return Self::Fifo;
}
if t.is_char_device() {
return Self::CharDevice;
}
if t.is_block_device() {
return Self::BlockDevice;
}
if t.is_socket() {
return Self::Socket;
}
}
Self::Unknown
}
}
#[derive(Clone, Debug)]
pub struct WalkError {
path: Option<PathBuf>,
depth: Option<usize>,
raw: Option<i32>,
}
impl WalkError {
pub fn path(&self) -> Option<&Path> {
self.path.as_deref()
}
pub fn depth(&self) -> Option<usize> {
self.depth
}
pub fn kind(&self) -> ErrorKind {
io::Error::from(self).kind()
}
pub fn is_not_found(&self) -> bool {
if self.kind() == ErrorKind::NotFound {
return true;
}
#[cfg(unix)]
{
if self.raw == Some(uucore::libc::ENOTDIR) {
return true;
}
}
false
}
pub fn is_loop(&self) -> bool {
#[cfg(unix)]
return self.raw == Some(uucore::libc::ELOOP);
#[cfg(not(unix))]
return false;
}
}
impl Display for WalkError {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
let ioe = io::Error::from(self);
if let Some(path) = &self.path {
write!(f, "{}: {}", path.display(), ioe)
} else {
write!(f, "{}", ioe)
}
}
}
impl Error for WalkError {}
impl From<io::Error> for WalkError {
fn from(e: io::Error) -> Self {
Self::from(&e)
}
}
impl From<&io::Error> for WalkError {
fn from(e: &io::Error) -> Self {
Self {
path: None,
depth: None,
raw: e.raw_os_error(),
}
}
}
impl From<walkdir::Error> for WalkError {
fn from(e: walkdir::Error) -> Self {
Self::from(&e)
}
}
impl From<&walkdir::Error> for WalkError {
fn from(e: &walkdir::Error) -> Self {
Self {
path: e.path().map(std::borrow::ToOwned::to_owned),
depth: Some(e.depth()),
raw: e.io_error().and_then(std::io::Error::raw_os_error),
}
}
}
impl From<WalkError> for io::Error {
fn from(e: WalkError) -> Self {
Self::from(&e)
}
}
impl From<&WalkError> for io::Error {
fn from(e: &WalkError) -> Self {
e.raw
.map_or_else(|| ErrorKind::Other.into(), Self::from_raw_os_error)
}
}
#[derive(Debug)]
pub struct WalkEntry {
inner: Entry,
follow: Follow,
meta: OnceCell<Result<Metadata, WalkError>>,
}
impl WalkEntry {
pub fn new(path: impl Into<PathBuf>, depth: usize, follow: Follow) -> Self {
Self {
inner: Entry::Explicit(path.into(), depth),
follow,
meta: OnceCell::new(),
}
}
pub fn from_walkdir(
result: walkdir::Result<DirEntry>,
follow: Follow,
) -> Result<Self, WalkError> {
let result = result.map_err(WalkError::from);
match result {
Ok(entry) => {
let ret = if entry.depth() == 0 && follow != Follow::Never {
Self::new(entry.path(), 0, follow)
} else {
Self {
inner: Entry::WalkDir(entry),
follow,
meta: OnceCell::new(),
}
};
Ok(ret)
}
Err(e) if e.is_not_found() => {
if let (Some(path), Some(depth)) = (e.path(), e.depth()) {
if let Ok(meta) = path.symlink_metadata() {
return Ok(Self {
inner: Entry::Explicit(path.into(), depth),
follow: Follow::Never,
meta: Ok(meta).into(),
});
}
}
Err(e)
}
Err(e) => Err(e),
}
}
pub fn path(&self) -> &Path {
match &self.inner {
Entry::Explicit(path, _) => path.as_path(),
Entry::WalkDir(ent) => ent.path(),
}
}
pub fn into_path(self) -> PathBuf {
match self.inner {
Entry::Explicit(path, _) => path,
Entry::WalkDir(ent) => ent.into_path(),
}
}
pub fn file_name(&self) -> &OsStr {
match &self.inner {
Entry::Explicit(path, _) => {
path.components()
.next_back()
.map_or_else(|| path.as_os_str(), std::path::Component::as_os_str)
}
Entry::WalkDir(ent) => ent.file_name(),
}
}
pub fn depth(&self) -> usize {
match &self.inner {
Entry::Explicit(_, depth) => *depth,
Entry::WalkDir(ent) => ent.depth(),
}
}
pub fn follow(&self) -> bool {
self.follow.follow_at_depth(self.depth())
}
fn get_metadata(&self) -> Result<Metadata, WalkError> {
self.follow.metadata_at_depth(self.path(), self.depth())
}
pub fn metadata(&self) -> Result<&Metadata, WalkError> {
let result = self.meta.get_or_init(|| match &self.inner {
Entry::Explicit(_, _) => Ok(self.get_metadata()?),
Entry::WalkDir(ent) => Ok(ent.metadata()?),
});
result.as_ref().map_err(std::clone::Clone::clone)
}
pub fn file_type(&self) -> FileType {
match &self.inner {
Entry::Explicit(_, _) => self
.metadata()
.map_or(FileType::Unknown, |m| m.file_type().into()),
Entry::WalkDir(ent) => ent.file_type().into(),
}
}
pub fn path_is_symlink(&self) -> bool {
match &self.inner {
Entry::Explicit(path, _) => {
if self.follow() {
path.symlink_metadata()
.is_ok_and(|m| m.file_type().is_symlink())
} else {
self.file_type().is_symlink()
}
}
Entry::WalkDir(ent) => ent.path_is_symlink(),
}
}
}