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
4pub mod magic;
5
6use crate::magic::{Magic, which_kind};
7use std::{fmt, path::Path};
8
9#[derive(Debug, Clone, Copy, Eq, PartialEq)]
10pub enum FsKind {
11    Fat32,
12    ExFat,
13    Apfs,
14    Ext2,
15    Hfs,
16    Btrfs,
17    Bcachefs,
18    Xfs,
19    Fuse,
20    Ntfs,
21    F2fs,
22    Unknown(Magic),
23}
24
25impl fmt::Display for FsKind {
26    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27        match self {
28            FsKind::Fat32 => write!(f, "FAT32"),
29            FsKind::ExFat => write!(f, "exFAT"),
30            FsKind::Apfs => write!(f, "APFS"),
31            FsKind::Ext2 => write!(f, "ext2/3/4"),
32            FsKind::Hfs => write!(f, "HFS+"),
33            FsKind::Btrfs => write!(f, "BTRFS"),
34            FsKind::Bcachefs => write!(f, "bcachefs"),
35            FsKind::Xfs => write!(f, "XFS"),
36            FsKind::Fuse => write!(f, "FUSE"),
37            FsKind::Ntfs => write!(f, "NTFS"),
38            FsKind::F2fs => write!(f, "F2FS"),
39            FsKind::Unknown(magic) => write!(f, "Unknown: {magic:?}"),
40        }
41    }
42}
43
44#[cfg(unix)]
45pub fn detect(path: &Path) -> rustix::io::Result<FsKind> {
46    let stat = rustix::fs::statfs(path)?;
47
48    #[cfg(target_os = "linux")]
49    let data = stat.f_type;
50
51    #[cfg(target_os = "macos")]
52    let data = stat.f_fstypename;
53
54    let kind = which_kind(data);
55
56    Ok(kind)
57}
58
59#[cfg(windows)]
60pub fn detect(path: &Path) -> windows::core::Result<FsKind> {
61    use std::os::windows::ffi::OsStrExt;
62    use windows::Win32::Foundation::MAX_PATH;
63    use windows::Win32::Storage::FileSystem::{GetVolumeInformationW, GetVolumePathNameW};
64    use windows::core::PCWSTR;
65
66    // Convert input path to null-terminated UTF-16 wide string
67    let path_wide: Vec<u16> = path
68        .as_os_str()
69        .encode_wide()
70        .chain(std::iter::once(0))
71        .collect();
72
73    // Get the Volume Path Name (Mount Point)
74    let mut volume_path = [0u16; MAX_PATH as usize];
75    unsafe {
76        GetVolumePathNameW(PCWSTR(path_wide.as_ptr()), &mut volume_path)?;
77    }
78
79    let mut fs_name_buffer = [0u16; 16];
80    unsafe {
81        GetVolumeInformationW(
82            PCWSTR(volume_path.as_ptr()),
83            None,
84            None,
85            None,
86            None,
87            Some(&mut fs_name_buffer),
88        )?;
89    }
90
91    let kind = which_kind(fs_name_buffer);
92
93    Ok(kind)
94}
95
96#[cfg(test)]
97mod tests {
98    use super::*;
99
100    #[test]
101    fn test_detect() {
102        let path = Path::new(".");
103        let kind = detect(path).unwrap();
104
105        if matches!(kind, FsKind::Unknown(_)) {
106            panic!("Unknown filesystem kind: {kind}");
107        }
108    }
109}