use std::collections::HashMap;
use std::io;
use std::sync::{Arc, Mutex};
lazy_static::lazy_static! {
static ref VOLUME_MAP: Arc<Mutex<HashMap<String, String>>> = {
Arc::new(Mutex::new(
build_volume_map().unwrap_or_else(|e| {
eprintln!("Failed to initialize volume map: {}", e);
HashMap::new()
})
))
};
}
mod winapi {
#[link(name = "kernel32")]
unsafe extern "system" {
pub fn FindFirstVolumeW(
lpsz_volume_name: *mut u16,
cch_buffer_length: u32,
) -> *mut std::ffi::c_void;
pub fn FindNextVolumeW(
h_find_volume: *mut std::ffi::c_void,
lpsz_volume_name: *mut u16,
cch_buffer_length: u32,
) -> i32;
pub fn FindVolumeClose(h_find_volume: *mut std::ffi::c_void) -> i32;
pub fn GetVolumePathNamesForVolumeNameW(
lpsz_volume_name: *const u16,
lpsz_volume_path_names: *mut u16,
cch_buffer_length: u32,
pcch_return_length: *mut u32,
) -> i32;
pub fn GetFullPathNameW(
lp_file_name: *const u16,
n_buffer_length: u32,
lp_buffer: *mut u16,
lp_file_part: *mut *mut u16,
) -> u32;
pub fn GetVolumePathNameW(
lpsz_file_name: *const u16,
lpsz_volume_path_name: *mut u16,
cch_buffer_length: u32,
) -> i32;
}
}
type WinResult<T> = Result<T, io::Error>;
fn wide_string(s: &str) -> Vec<u16> {
use std::ffi::OsStr;
use std::os::windows::ffi::OsStrExt as _;
OsStr::new(s)
.encode_wide() .chain(Some(0)) .collect() }
fn from_wide_buf(buffer: &[u16]) -> WinResult<String> {
let end = buffer.iter().position(|&c| c == 0).unwrap_or(buffer.len());
String::from_utf16(&buffer[..end])
.map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "UTF-16 conversion failed"))
}
fn build_volume_map() -> WinResult<HashMap<String, String>> {
let mut volume_map = HashMap::new();
let mut buffer = [0u16; 50];
let handle = unsafe { winapi::FindFirstVolumeW(buffer.as_mut_ptr(), buffer.len() as u32) };
if handle.is_null() {
return Err(io::Error::last_os_error());
}
loop {
let volume_name = from_wide_buf(&buffer)?;
let mut paths_buffer = [0u16; 4096];
let mut returned_len = 0;
let success = unsafe {
winapi::GetVolumePathNamesForVolumeNameW(
buffer.as_ptr(), paths_buffer.as_mut_ptr(), paths_buffer.len() as u32, &mut returned_len, )
};
if success != 0 && returned_len > 0 {
let mut offset = 0;
while offset < paths_buffer.len() {
if paths_buffer[offset] == 0 {
break; }
let end = paths_buffer[offset..]
.iter()
.position(|&c| c == 0)
.unwrap_or(paths_buffer.len() - offset);
let path = from_wide_buf(&paths_buffer[offset..offset + end])?;
let normalized_path = path.replace('/', "\\");
let key = if !normalized_path.ends_with('\\') {
format!("{}\\", normalized_path) } else {
normalized_path
};
volume_map.insert(key, volume_name.clone());
offset += end + 1; }
}
let next = unsafe {
buffer.fill(0); winapi::FindNextVolumeW(handle, buffer.as_mut_ptr(), buffer.len() as u32)
};
if next == 0 {
break;
}
}
unsafe { winapi::FindVolumeClose(handle) };
Ok(volume_map)
}
fn get_volume_mount_point(path: &str) -> WinResult<String> {
let path_wide = wide_string(path);
let mut full_path = [0u16; 4096];
let mut mount_point = [0u16; 4096];
let len = unsafe {
winapi::GetFullPathNameW(
path_wide.as_ptr(), full_path.len() as u32, full_path.as_mut_ptr(), std::ptr::null_mut(), )
};
if len == 0 {
return Err(io::Error::last_os_error());
}
let success = unsafe {
winapi::GetVolumePathNameW(
full_path.as_ptr(), mount_point.as_mut_ptr(), mount_point.len() as u32, )
};
if success == 0 {
return Err(io::Error::last_os_error());
}
from_wide_buf(&mount_point).map(|s| {
if s.ends_with('\\') { s } else { format!("{}\\", s) }
})
}
pub fn reinitialize_volume_map() -> Result<usize, io::Error> {
let new_map = build_volume_map()?;
let count = new_map.len();
let mut map = VOLUME_MAP.lock()
.map_err(|e| io::Error::new(io::ErrorKind::Other, format!("Mutex poison error: {}", e)))?;
*map = new_map;
Ok(count)
}
pub fn resolve_device_path(path: &str) -> Option<String> {
let mount_point = match get_volume_mount_point(path) {
Ok(m) => m,
Err(_) => return None,
};
let map = VOLUME_MAP.lock().ok()?;
let candidates = map.keys()
.filter(|k| mount_point.starts_with(*k))
.collect::<Vec<_>>();
let mount_path = candidates.iter()
.max_by_key(|k| k.len())?;
map.get(*mount_path).cloned()
}
pub fn is_same_vol(path1: &str, path2: &str) -> bool {
let vol1 = resolve_device_path(path1);
let vol2 = resolve_device_path(path2);
vol1.zip(vol2).is_some_and(|(v1, v2)| v1 == v2)
}