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