use crate::fs::FileDes;
use core::ffi::CStr;
use libc::{
AT_SYMLINK_FOLLOW, AT_SYMLINK_NOFOLLOW, DT_BLK, DT_CHR, DT_DIR, DT_FIFO, DT_LNK, DT_REG,
DT_SOCK, DT_UNKNOWN, S_IFMT, fstatat, mode_t,
};
use std::{os::unix::fs::FileTypeExt as _, path::Path};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(u8)]
#[expect(
clippy::exhaustive_enums,
reason = "This is exhaustive (there aren't anymore filetypes than this)"
)]
pub enum FileType {
BlockDevice = DT_BLK,
CharDevice = DT_CHR,
Directory = DT_DIR,
Pipe = DT_FIFO,
Symlink = DT_LNK,
RegularFile = DT_REG,
Socket = DT_SOCK,
Unknown = DT_UNKNOWN,
}
impl FileType {
#[must_use]
#[inline]
#[expect(clippy::indexing_slicing, reason = "All slicing is trivially in range")]
pub const fn from_dtype(d_type: u8) -> Self {
use FileType as FT;
const LUT: [FT; 0xFF + 1] = {
let mut t = [FT::Unknown; 0xFF + 1];
t[DT_FIFO as usize] = FT::Pipe;
t[DT_CHR as usize] = FT::CharDevice;
t[DT_DIR as usize] = FT::Directory;
t[DT_BLK as usize] = FT::BlockDevice;
t[DT_REG as usize] = FT::RegularFile;
t[DT_LNK as usize] = FT::Symlink;
t[DT_SOCK as usize] = FT::Socket;
t
};
LUT[d_type as usize]
}
#[inline]
#[must_use]
pub fn from_fd_no_follow(fd: &FileDes, filename: &CStr) -> Self {
stat_syscall!(fstatat, fd.0, filename.as_ptr(), AT_SYMLINK_NOFOLLOW, DTYPE)
}
#[inline]
#[must_use]
pub fn from_fd_follow(fd: &FileDes, filename: &CStr) -> Self {
stat_syscall!(fstatat, fd.0, filename.as_ptr(), AT_SYMLINK_FOLLOW, DTYPE)
}
#[inline]
#[must_use]
pub const fn is_dir(&self) -> bool {
matches!(*self, Self::Directory)
}
#[inline]
#[must_use]
pub const fn is_regular_file(&self) -> bool {
matches!(*self, Self::RegularFile)
}
#[inline]
#[must_use]
pub const fn is_symlink(&self) -> bool {
matches!(*self, Self::Symlink)
}
#[inline]
#[must_use]
pub const fn is_block_device(&self) -> bool {
matches!(*self, Self::BlockDevice)
}
#[inline]
#[must_use]
pub const fn is_char_device(&self) -> bool {
matches!(*self, Self::CharDevice)
}
#[inline]
#[must_use]
pub const fn is_pipe(&self) -> bool {
matches!(*self, Self::Pipe)
}
#[inline]
#[must_use]
pub const fn is_socket(&self) -> bool {
matches!(*self, Self::Socket)
}
#[inline]
#[must_use]
pub const fn is_unknown(&self) -> bool {
matches!(*self, Self::Unknown)
}
#[must_use]
#[inline]
pub const fn from_mode(mode: mode_t) -> Self {
Self::from_dtype(((mode & S_IFMT) >> 12) as u8)
}
#[must_use]
#[inline]
#[allow(clippy::filetype_is_file)] pub fn from_path<P: AsRef<Path>>(path_start: P) -> Self {
Path::new(path_start.as_ref())
.symlink_metadata()
.map_or(Self::Unknown, |metadata| match metadata.file_type() {
ft if ft.is_dir() => Self::Directory,
ft if ft.is_file() => Self::RegularFile,
ft if ft.is_symlink() => Self::Symlink,
ft if ft.is_block_device() => Self::BlockDevice,
ft if ft.is_char_device() => Self::CharDevice,
ft if ft.is_fifo() => Self::Pipe,
ft if ft.is_socket() => Self::Socket,
_ => Self::Unknown,
})
}
#[must_use]
#[inline]
pub const fn from_stat(stat: &libc::stat) -> Self {
Self::from_mode(stat.st_mode)
}
}
impl core::fmt::Display for FileType {
#[allow(clippy::missing_inline_in_public_items)]
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match *self {
Self::BlockDevice => write!(f, "Block device"),
Self::CharDevice => write!(f, "Character device"),
Self::Directory => write!(f, "Directory"),
Self::Pipe => write!(f, "FIFO"),
Self::Symlink => write!(f, "Symlink"),
Self::RegularFile => write!(f, "Regular file"),
Self::Socket => write!(f, "Socket"),
Self::Unknown => write!(f, "Unknown"),
}
}
}
impl From<libc::stat> for FileType {
#[inline]
fn from(stat: libc::stat) -> Self {
Self::from_stat(&stat)
}
}