lfs_core/device_id/
windows.rs

1use {
2    super::{
3        ParseDeviceIdError,
4        ParseDeviceIdSnafu,
5    },
6    crate::WindowsApiSnafu,
7    snafu::prelude::*,
8    std::{
9        ffi::OsStr,
10        fmt,
11        os::windows::ffi::OsStrExt,
12        path::Path,
13        str::FromStr,
14    },
15    windows::{
16        Win32::Storage::FileSystem::{
17            GetVolumeInformationW,
18            GetVolumeNameForVolumeMountPointW,
19            GetVolumePathNameW,
20        },
21        core::PCWSTR,
22    },
23};
24/// Id of a volume, can be found using GetVolumeInformationW
25#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
26pub struct DeviceId {
27    pub serial: u32,
28}
29
30impl fmt::Display for DeviceId {
31    fn fmt(
32        &self,
33        f: &mut fmt::Formatter,
34    ) -> fmt::Result {
35        write!(f, "{:X}-{:X}", self.serial >> 16, self.serial & 0x0000_FFFF)
36    }
37}
38
39impl FromStr for DeviceId {
40    type Err = ParseDeviceIdError;
41
42    fn from_str(string: &str) -> Result<Self, Self::Err> {
43        if let Some((high, low)) = string.split_once('-') {
44            if let (Ok(high), Ok(low)) =
45                (u32::from_str_radix(high, 16), u32::from_str_radix(low, 16))
46            {
47                let serial = (high << 16) | low;
48                return Ok(Self { serial });
49            }
50        }
51
52        u32::from_str_radix(string, 16)
53            .ok()
54            .map(|serial| Self { serial })
55            .with_context(|| ParseDeviceIdSnafu { string })
56    }
57}
58
59impl From<u64> for DeviceId {
60    fn from(num: u64) -> Self {
61        Self { serial: num as u32 }
62    }
63}
64
65impl From<u32> for DeviceId {
66    fn from(num: u32) -> Self {
67        Self { serial: num }
68    }
69}
70
71impl DeviceId {
72    pub fn new(serial: u32) -> Self {
73        Self { serial }
74    }
75    /// Determine the DeviceId for a given file path
76    pub fn of_path(path: &Path) -> Result<Self, crate::Error> {
77        unsafe {
78            let path_wide: Vec<u16> = OsStr::new(path)
79                .encode_wide()
80                .chain(std::iter::once(0)) // null terminator
81                .collect();
82
83            // Step 1: Get the volume path from the file path
84            let mut volume_path_buf = vec![0u16; 260]; // MAX_PATH
85            GetVolumePathNameW(PCWSTR(path_wide.as_ptr()), &mut volume_path_buf).context(
86                WindowsApiSnafu {
87                    api: "GetVolumePathNameW",
88                },
89            )?;
90
91            // Step 2: Get the volume GUID from the volume path
92            let mut volume_guid_buf = vec![0u16; 260]; // MAX_PATH
93            GetVolumeNameForVolumeMountPointW(
94                PCWSTR(volume_path_buf.as_ptr()),
95                &mut volume_guid_buf,
96            )
97            .context(WindowsApiSnafu {
98                api: "GetVolumeNameForVolumeMountPointW",
99            })?;
100
101            // Step 3: Get serial number from the GUID
102            let mut serial: u32 = 0;
103            GetVolumeInformationW(
104                PCWSTR(volume_guid_buf.as_ptr()),
105                None,
106                Some(&mut serial),
107                None,
108                None,
109                None,
110            )
111            .context(WindowsApiSnafu {
112                api: "GetVolumeInformationW",
113            })?;
114
115            Ok(Self { serial })
116        }
117    }
118}
119
120#[test]
121fn test_from_str() {
122    assert_eq!(
123        DeviceId::new(0xABCD_1234),
124        DeviceId::from_str("ABCD-1234").unwrap()
125    );
126    assert_eq!(
127        DeviceId::new(0xABCD_1234),
128        DeviceId::from_str("ABCD1234").unwrap()
129    );
130}
131
132#[test]
133fn test_from_u64() {
134    assert_eq!(
135        DeviceId::new(0xFFFF_FFFF),
136        DeviceId::from(0xFFFF_FFFF_FFFFu64)
137    );
138}