fdf/filetype.rs
1use crate::BytePath as _;
2
3use libc::{
4 DT_BLK, DT_CHR, DT_DIR, DT_FIFO, DT_LNK, DT_REG, DT_SOCK, S_IFBLK, S_IFCHR, S_IFDIR, S_IFIFO,
5 S_IFLNK, S_IFMT, S_IFREG, S_IFSOCK, mode_t,
6};
7
8use std::{os::unix::fs::FileTypeExt as _, path::Path};
9#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
10#[allow(
11 clippy::exhaustive_enums,
12 reason = "This is exhaustive (there aren't anymore filetypes than this)"
13)]
14/// Represents the type of a file in the filesystem
15///
16/// This enum provides a cross-platform abstraction for file types with
17/// specialised support for Unix filesystem semantics. It can be constructed
18/// from various sources including dirent `d_type` values, stat mode bits,
19/// and path-based lookups.
20///
21/// # Examples
22/// ```
23/// use fdf::FileType;
24/// use libc::DT_DIR;
25///
26/// // Create from dirent d_type
27/// let dir_type = FileType::from_dtype(DT_DIR);
28/// assert!(dir_type.is_dir());
29///
30/// // Check if a file type is traversible
31/// assert!(dir_type.is_traversible());
32/// ```
33///
34pub enum FileType {
35 /// Block special device file (e.g., /dev/sda)
36 BlockDevice,
37 /// Character special device file (e.g., /dev/tty)
38 CharDevice,
39 /// Directory
40 Directory,
41 /// FIFO (named pipe)
42 Pipe,
43 /// Symbolic link
44 Symlink,
45 /// Regular file
46 RegularFile,
47 /// Socket file
48 Socket,
49 /// Unknown file type (should be rare on supported filesystems)
50 Unknown,
51}
52
53impl FileType {
54 #[must_use]
55 #[inline]
56 /// Converts a libc `dirent` `d_type` value to a `FileType`
57 ///
58 /// This is the preferred method when `d_type` is available, as it avoids
59 /// expensive filesystem lookups. However, note that some filesystems
60 /// may not support `d_type` or may set it to `DT_UNKNOWN`.
61 ///
62 /// # Parameters
63 /// - `d_type`: The file type from a `dirent` structure
64 ///
65 /// # Examples
66 /// ```
67 /// use fdf::FileType;
68 /// use libc::{DT_DIR, DT_REG};
69 ///
70 /// assert!(FileType::from_dtype(DT_DIR).is_dir());
71 /// assert!(FileType::from_dtype(DT_REG).is_regular_file());
72 /// ```
73 pub const fn from_dtype(d_type: u8) -> Self {
74 match d_type {
75 DT_REG => Self::RegularFile,
76 DT_DIR => Self::Directory,
77 DT_BLK => Self::BlockDevice,
78 DT_CHR => Self::CharDevice,
79 DT_FIFO => Self::Pipe,
80 DT_LNK => Self::Symlink,
81 DT_SOCK => Self::Socket,
82 _ => Self::Unknown, /*DT_UNKNOWN */
83 }
84 }
85 /// Returns true if this represents a directory (cost free check)
86 #[inline]
87 #[must_use]
88 pub const fn is_dir(&self) -> bool {
89 matches!(*self, Self::Directory)
90 }
91
92 /// Returns true if this represents a regular file (cost free check)
93 #[inline]
94 #[must_use]
95 pub const fn is_regular_file(&self) -> bool {
96 matches!(*self, Self::RegularFile)
97 }
98
99 /// Returns true if this represents a symbolic link (cost free check)
100 #[inline]
101 #[must_use]
102 pub const fn is_symlink(&self) -> bool {
103 matches!(*self, Self::Symlink)
104 }
105
106 /// Returns true if this represents a block device (cost free check)
107 #[inline]
108 #[must_use]
109 pub const fn is_block_device(&self) -> bool {
110 matches!(*self, Self::BlockDevice)
111 }
112
113 /// Returns true if this represents a character device (cost free check)
114 #[inline]
115 #[must_use]
116 pub const fn is_char_device(&self) -> bool {
117 matches!(*self, Self::CharDevice)
118 }
119
120 /// Returns true if this represents a FIFO (named pipe) (cost free check)
121 #[inline]
122 #[must_use]
123 pub const fn is_pipe(&self) -> bool {
124 matches!(*self, Self::Pipe)
125 }
126
127 /// Returns true if this represents a socket (cost free check)
128 #[inline]
129 #[must_use]
130 pub const fn is_socket(&self) -> bool {
131 matches!(*self, Self::Socket)
132 }
133
134 /// Returns true if this represents an unknown file type (cost free check)
135 #[inline]
136 #[must_use]
137 pub const fn is_unknown(&self) -> bool {
138 matches!(*self, Self::Unknown)
139 }
140
141 /// Returns true if the file type is traversible (directory or symlink)
142 ///
143 /// This is useful for determining whether a directory entry can be
144 /// explored further during filesystem traversal.
145 #[inline]
146 #[must_use]
147 pub const fn is_traversible(&self) -> bool {
148 matches!(*self, Self::Directory | Self::Symlink)
149 }
150
151 #[must_use]
152 #[inline]
153 /// Fallback method to determine file type when `d_type` is unavailable or `DT_UNKNOWN`
154 ///
155 /// This method first checks the `d_type` value, and if it's `DT_UNKNOWN`,
156 /// falls back to a more expensive lstat-based lookup using the file path.
157 ///
158 /// # Parameters
159 /// - `d_type`: The file type from a dirent structure
160 /// - `file_path`: The path to the file for fallback lookup
161 ///
162 /// # Notes
163 /// While ext4 and BTRFS (and others, not entirely tested!) typically provide reliable `d_type` values,
164 /// other filesystems like NTFS, XFS, or FUSE-based filesystems
165 /// may require the fallback path.
166 pub fn from_dtype_fallback(d_type: u8, file_path: &[u8]) -> Self {
167 //i wouldve just chained the function calls but it's clearer this way
168 match d_type {
169 DT_REG => Self::RegularFile,
170 DT_DIR => Self::Directory,
171 DT_BLK => Self::BlockDevice,
172 DT_CHR => Self::CharDevice,
173 DT_FIFO => Self::Pipe,
174 DT_LNK => Self::Symlink,
175 DT_SOCK => Self::Socket,
176 _ => Self::from_bytes(file_path),
177 }
178 }
179
180 #[must_use]
181 #[inline]
182 /// Determines file type using an lstat call on the provided path
183 ///
184 /// This is more expensive but more reliable than relying on `d_type`,
185 /// especially on filesystems that don't fully support dirent `d_type`.
186 ///
187 /// # Parameters
188 /// - `file_path`: The path to the file to stat (must be a valid filepath)
189 pub fn from_bytes(file_path: &[u8]) -> Self {
190 file_path
191 .get_lstat()
192 .map_or(Self::Unknown, |stat| Self::from_mode(stat.st_mode))
193 }
194
195 #[must_use]
196 #[inline]
197 /// Converts Unix mode bits to a `FileType`
198 ///
199 /// This extracts the file type from the `st_mode` field of a stat structure.
200 ///
201 /// # Parameters
202 /// - `mode`: The mode bits from a stat structure
203 pub const fn from_mode(mode: mode_t) -> Self {
204 match mode & S_IFMT {
205 S_IFREG => Self::RegularFile,
206 S_IFDIR => Self::Directory,
207 S_IFBLK => Self::BlockDevice,
208 S_IFCHR => Self::CharDevice,
209 S_IFIFO => Self::Pipe,
210 S_IFLNK => Self::Symlink,
211 S_IFSOCK => Self::Socket,
212 _ => Self::Unknown,
213 }
214 }
215 /// Determines file type using the standard library's metadata lookup
216 ///
217 /// This method is primarily useful for verification and testing purposes,
218 /// not for use within performance-critical iteration code paths.
219 ///
220 /// # Parameters
221 /// - `path_start`: The path to examine
222 #[must_use]
223 #[inline]
224 pub fn from_path<P: AsRef<Path>>(path_start: P) -> Self {
225 Path::new(path_start.as_ref())
226 .symlink_metadata()
227 .map_or(Self::Unknown, |metadata| match metadata.file_type() {
228 ft if ft.is_dir() => Self::Directory,
229 ft if ft.is_file() => Self::RegularFile,
230 ft if ft.is_symlink() => Self::Symlink,
231 ft if ft.is_block_device() => Self::BlockDevice,
232 ft if ft.is_char_device() => Self::CharDevice,
233 ft if ft.is_fifo() => Self::Pipe,
234 ft if ft.is_socket() => Self::Socket,
235 _ => Self::Unknown,
236 })
237 }
238 #[must_use]
239 #[inline]
240 /// Converts a `libc::stat` structure to a `FileType`
241 ///
242 /// Useful when you already have a stat structure and want to avoid
243 /// additional filesystem lookups.
244 ///
245 /// # Parameters
246 /// - `stat`: The stat structure to extract the file type from
247 pub const fn from_stat(stat: &libc::stat) -> Self {
248 Self::from_mode(stat.st_mode)
249 }
250}
251
252impl core::fmt::Display for FileType {
253 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
254 match *self {
255 Self::BlockDevice => write!(f, "Block device"),
256 Self::CharDevice => write!(f, "Character device"),
257 Self::Directory => write!(f, "Directory"),
258 Self::Pipe => write!(f, "FIFO"),
259 Self::Symlink => write!(f, "Symlink"),
260 Self::RegularFile => write!(f, "Regular file"),
261 Self::Socket => write!(f, "Socket"),
262 Self::Unknown => write!(f, "Unknown"),
263 }
264 }
265}
266
267impl From<libc::stat> for FileType {
268 /// Converts a `libc::stat` directly to a `FileType`
269 fn from(stat: libc::stat) -> Self {
270 Self::from_stat(&stat)
271 }
272}