Skip to main content

which_fs/
lib.rs

1// SPDX-FileCopyrightText: 2026 Manuel Quarneti <mq1@ik.me>
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4mod magic;
5
6use crate::magic::which_kind;
7use std::{fmt, path::Path};
8
9/// Represents the type of a filesystem.
10#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
11pub enum FsKind {
12    Fat32,
13    ExFat,
14    Apfs,
15    /// Covers ext2, ext3, and ext4.
16    Ext2,
17    Hfs,
18    Btrfs,
19    Bcachefs,
20    Xfs,
21    Fuse,
22    Ntfs,
23    F2fs,
24    /// Filesystem type could not be determined.
25    #[default]
26    Unknown,
27}
28
29impl fmt::Display for FsKind {
30    /// Formats `FsKind` as its common display name (e.g. `"FAT32"`, `"exFAT"`).
31    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32        match self {
33            FsKind::Fat32 => write!(f, "FAT32"),
34            FsKind::ExFat => write!(f, "exFAT"),
35            FsKind::Apfs => write!(f, "APFS"),
36            FsKind::Ext2 => write!(f, "ext2/3/4"),
37            FsKind::Hfs => write!(f, "HFS+"),
38            FsKind::Btrfs => write!(f, "BTRFS"),
39            FsKind::Bcachefs => write!(f, "bcachefs"),
40            FsKind::Xfs => write!(f, "XFS"),
41            FsKind::Fuse => write!(f, "FUSE"),
42            FsKind::Ntfs => write!(f, "NTFS"),
43            FsKind::F2fs => write!(f, "F2FS"),
44            FsKind::Unknown => write!(f, "Unknown"),
45        }
46    }
47}
48
49impl FsKind {
50    /// Detects the filesystem type for the given path.
51    ///
52    /// # Arguments
53    /// * `path` - This can be the mount point or a path to a file within the filesystem.
54    ///
55    /// # Errors
56    /// Returns an error if the underlying OS call fails.
57    #[cfg(unix)]
58    pub fn try_from_path(path: impl AsRef<Path>) -> rustix::io::Result<FsKind> {
59        let stat = rustix::fs::statfs(path.as_ref())?;
60
61        #[cfg(target_os = "linux")]
62        let data = stat.f_type;
63
64        #[cfg(target_os = "macos")]
65        let data = stat.f_fstypename;
66
67        let kind = which_kind(data);
68        Ok(kind)
69    }
70
71    /// Detects the filesystem type for the given path.
72    ///
73    /// # Arguments
74    /// * `path` - This can be the mount point or a path to a directory within the filesystem.
75    ///
76    /// # Errors
77    /// Returns an error if the underlying OS call fails.
78    #[cfg(windows)]
79    pub fn try_from_path(path: impl AsRef<Path>) -> windows::core::Result<FsKind> {
80        use std::os::windows::ffi::OsStrExt;
81        use windows::Win32::Foundation::MAX_PATH;
82        use windows::Win32::Storage::FileSystem::{GetVolumeInformationW, GetVolumePathNameW};
83        use windows::core::PCWSTR;
84
85        // Convert input path to null-terminated UTF-16 wide string
86        let path_wide: Vec<u16> = path
87            .as_ref()
88            .as_os_str()
89            .encode_wide()
90            .chain(std::iter::once(0))
91            .collect();
92
93        // Get the Volume Path Name (Mount Point)
94        let mut volume_path = [0u16; MAX_PATH as usize + 1];
95        unsafe {
96            GetVolumePathNameW(PCWSTR(path_wide.as_ptr()), &mut volume_path)?;
97        }
98
99        let mut fs_name_buffer = [0u16; 16];
100        unsafe {
101            GetVolumeInformationW(
102                PCWSTR(volume_path.as_ptr()),
103                None,
104                None,
105                None,
106                None,
107                Some(&mut fs_name_buffer),
108            )?;
109        }
110
111        let kind = which_kind(fs_name_buffer);
112        Ok(kind)
113    }
114}
115
116#[cfg(test)]
117mod tests {
118    use super::*;
119
120    #[test]
121    fn test_detect() {
122        let path = Path::new(".");
123        let kind = FsKind::try_from_path(path).unwrap();
124        assert_ne!(kind, FsKind::Unknown);
125    }
126}