use std::{
ffi::OsString,
io, mem,
os::windows::prelude::{OsStrExt, OsStringExt},
path::{Path, PathBuf},
};
use anyhow::bail;
use windows::Win32::{
Foundation::{self, CloseHandle, HANDLE},
Storage::FileSystem::{
CreateFileW, FILE_FLAGS_AND_ATTRIBUTES, FILE_SHARE_READ, FILE_SHARE_WRITE,
FindFirstVolumeW, FindNextVolumeW, FindVolumeClose, GetVolumeNameForVolumeMountPointW,
OPEN_EXISTING,
},
System::{
IO::DeviceIoControl,
Ioctl::{DISK_PERFORMANCE, IOCTL_DISK_PERFORMANCE},
},
};
fn volume_io(volume: &Path) -> anyhow::Result<DISK_PERFORMANCE> {
if volume.is_file() {
bail!("Expects a directory to be passed in.");
}
let volume = {
let mut wide_path = volume.as_os_str().encode_wide().collect::<Vec<_>>();
wide_path.pop();
wide_path.push(0x0000);
wide_path
};
let h_device = unsafe {
CreateFileW(
windows::core::PCWSTR(volume.as_ptr()),
0,
FILE_SHARE_READ | FILE_SHARE_WRITE,
None,
OPEN_EXISTING,
FILE_FLAGS_AND_ATTRIBUTES(0),
Some(Foundation::HANDLE::default()),
)?
};
if h_device.is_invalid() {
bail!("Invalid handle value: {:?}", io::Error::last_os_error());
}
let mut disk_performance = DISK_PERFORMANCE::default();
let mut bytes_returned = 0;
let ret = unsafe {
DeviceIoControl(
h_device,
IOCTL_DISK_PERFORMANCE,
None,
0,
Some(&mut disk_performance as *mut _ as _),
mem::size_of::<DISK_PERFORMANCE>() as u32,
Some(&mut bytes_returned),
None,
)
};
let handle_result = unsafe { CloseHandle(h_device) };
if let Err(err) = handle_result {
bail!("Handle error: {err:?}");
}
if let Err(err) = ret {
bail!("Device I/O error: {err:?}");
} else {
Ok(disk_performance)
}
}
fn current_volume(buffer: &[u16]) -> PathBuf {
let first_null = buffer.iter().position(|byte| *byte == 0x00).unwrap_or(0);
let path_string = OsString::from_wide(&buffer[..first_null]);
PathBuf::from(path_string)
}
fn close_find_handle(handle: HANDLE) -> anyhow::Result<()> {
let res = unsafe { FindVolumeClose(handle) };
Ok(res?)
}
pub(crate) fn all_volume_io() -> anyhow::Result<Vec<anyhow::Result<(DISK_PERFORMANCE, String)>>> {
const ERROR_NO_MORE_FILES: i32 = Foundation::ERROR_NO_MORE_FILES.0 as i32;
let mut ret = vec![];
let mut buffer = [0_u16; Foundation::MAX_PATH as usize];
let handle = unsafe { FindFirstVolumeW(&mut buffer) }?;
if handle.is_invalid() {
bail!("Invalid handle value: {:?}", io::Error::last_os_error());
}
{
let volume = current_volume(&buffer);
ret.push(volume_io(&volume).map(|res| (res, volume.to_string_lossy().to_string())));
}
while unsafe { FindNextVolumeW(handle, &mut buffer) }.is_ok() {
let volume = current_volume(&buffer);
ret.push(volume_io(&volume).map(|res| (res, volume.to_string_lossy().to_string())));
}
let err = io::Error::last_os_error();
match err.raw_os_error() {
Some(ERROR_NO_MORE_FILES) => {
}
_ => {
close_find_handle(handle)?;
bail!("Error while iterating over volumes: {err:?}");
}
}
close_find_handle(handle)?;
Ok(ret)
}
pub(crate) fn volume_name_from_mount(mount: &str) -> anyhow::Result<String> {
const VOLUME_MAX_LEN: usize = 50;
let mount = {
let mount_path = Path::new(mount);
let mut wide_path = mount_path.as_os_str().encode_wide().collect::<Vec<_>>();
wide_path.push(0x0000);
wide_path
};
let mut buffer = [0_u16; VOLUME_MAX_LEN];
let result = unsafe {
GetVolumeNameForVolumeMountPointW(windows::core::PCWSTR(mount.as_ptr()), &mut buffer)
};
if let Err(err) = result {
bail!("Could not get volume name for mount point: {err:?}");
} else {
Ok(current_volume(&buffer).to_string_lossy().to_string())
}
}