use crate::sys::utils::HandleWrapper;
use crate::{Disk, DiskKind, DiskRefreshKind, DiskUsage};
use std::ffi::{OsStr, OsString};
use std::mem::size_of;
use std::os::windows::ffi::OsStringExt;
use std::path::Path;
use windows::Win32::Foundation::MAX_PATH;
use windows::Win32::Storage::FileSystem::{
FindFirstVolumeW, FindNextVolumeW, FindVolumeClose, GetDiskFreeSpaceExW, GetDriveTypeW,
GetVolumeInformationW, GetVolumePathNamesForVolumeNameW,
};
use windows::Win32::System::IO::DeviceIoControl;
use windows::Win32::System::Ioctl::{
DEVICE_SEEK_PENALTY_DESCRIPTOR, DISK_PERFORMANCE, IOCTL_DISK_PERFORMANCE,
IOCTL_STORAGE_QUERY_PROPERTY, PropertyStandardQuery, STORAGE_PROPERTY_QUERY,
StorageDeviceSeekPenaltyProperty,
};
use windows::Win32::System::SystemServices::FILE_READ_ONLY_VOLUME;
use windows::Win32::System::WindowsProgramming::{DRIVE_FIXED, DRIVE_REMOVABLE};
use windows::core::{Error, HRESULT, PCWSTR};
fn from_zero_terminated(buf: &[u16]) -> Vec<u16> {
let end = buf.iter().position(|&x| x == 0).unwrap_or(buf.len());
buf[..=end].to_vec()
}
const VOLUME_NAME_SIZE: usize = MAX_PATH as usize + 1;
const ERROR_NO_MORE_FILES: HRESULT = windows::Win32::Foundation::ERROR_NO_MORE_FILES.to_hresult();
const ERROR_MORE_DATA: HRESULT = windows::Win32::Foundation::ERROR_MORE_DATA.to_hresult();
pub(crate) fn get_volume_guid_paths() -> Vec<Vec<u16>> {
let mut volume_names = Vec::new();
unsafe {
let mut buf = Box::new([0u16; VOLUME_NAME_SIZE]);
let Ok(handle) = FindFirstVolumeW(&mut buf[..]) else {
sysinfo_debug!(
"Error: FindFirstVolumeW() = {:?}",
Error::from_thread().code()
);
return Vec::new();
};
volume_names.push(from_zero_terminated(&buf[..]));
loop {
if FindNextVolumeW(handle, &mut buf[..]).is_err() {
if Error::from_thread().code() != ERROR_NO_MORE_FILES {
sysinfo_debug!("Error: FindNextVolumeW = {}", Error::from_thread().code());
}
break;
}
volume_names.push(from_zero_terminated(&buf[..]));
}
if FindVolumeClose(handle).is_err() {
sysinfo_debug!("Error: FindVolumeClose = {:?}", Error::from_thread().code());
};
}
volume_names
}
pub(crate) unsafe fn get_volume_path_names_for_volume_name(
volume_guid_path: &[u16],
) -> Vec<Vec<u16>> {
let volume_guid_path = PCWSTR::from_raw(volume_guid_path.as_ptr());
let mut path_names_buf = vec![0u16; MAX_PATH as usize];
let mut path_names_output_size = 0u32;
for _ in 0..10 {
let volume_path_names = unsafe {
GetVolumePathNamesForVolumeNameW(
volume_guid_path,
Some(path_names_buf.as_mut_slice()),
&mut path_names_output_size,
)
};
let code = volume_path_names.map_err(|_| Error::from_thread().code());
match code {
Ok(()) => break,
Err(ERROR_MORE_DATA) => {
path_names_buf = vec![0u16; path_names_output_size as usize];
continue;
}
Err(_e) => {
sysinfo_debug!("Error: GetVolumePathNamesForVolumeNameW() = {}", _e);
return Vec::new();
}
}
}
let mut path_names = Vec::new();
let mut buf = &path_names_buf[..];
while !buf.is_empty() && buf[0] != 0 {
let path = from_zero_terminated(buf);
buf = &buf[path.len()..];
path_names.push(path);
}
path_names
}
pub(crate) struct DiskInner {
type_: DiskKind,
name: OsString,
pub(crate) file_system: OsString,
mount_point: Vec<u16>,
s_mount_point: OsString,
total_space: u64,
available_space: u64,
is_removable: bool,
is_read_only: bool,
device_path: Vec<u16>,
old_written_bytes: u64,
old_read_bytes: u64,
written_bytes: u64,
read_bytes: u64,
updated: bool,
}
#[cfg(test)]
impl Default for DiskInner {
fn default() -> Self {
Self {
type_: DiskKind::Unknown(0),
name: OsString::new(),
file_system: OsString::new(),
mount_point: Vec::new(),
s_mount_point: OsString::new(),
total_space: 0,
available_space: 0,
is_removable: false,
is_read_only: false,
device_path: Vec::new(),
old_written_bytes: 0,
old_read_bytes: 0,
written_bytes: 0,
read_bytes: 0,
updated: false,
}
}
}
impl DiskInner {
pub(crate) fn kind(&self) -> DiskKind {
self.type_
}
pub(crate) fn name(&self) -> &OsStr {
&self.name
}
pub(crate) fn file_system(&self) -> &OsStr {
&self.file_system
}
pub(crate) fn mount_point(&self) -> &Path {
self.s_mount_point.as_ref()
}
pub(crate) fn total_space(&self) -> u64 {
self.total_space
}
pub(crate) fn available_space(&self) -> u64 {
self.available_space
}
pub(crate) fn is_removable(&self) -> bool {
self.is_removable
}
pub(crate) fn is_read_only(&self) -> bool {
self.is_read_only
}
pub(crate) fn refresh_specifics(&mut self, refreshes: DiskRefreshKind) -> bool {
if refreshes.kind() || refreshes.io_usage() {
unsafe {
if let Some(handle) =
HandleWrapper::new_from_file(&self.device_path, Default::default())
{
if refreshes.kind() && self.type_ == DiskKind::Unknown(-1) {
self.type_ = get_disk_kind(&handle);
}
if refreshes.io_usage() {
if let Some((read_bytes, written_bytes)) = get_disk_io(handle) {
self.old_read_bytes = self.read_bytes;
self.old_written_bytes = self.written_bytes;
self.read_bytes = read_bytes;
self.written_bytes = written_bytes;
} else {
sysinfo_debug!("Failed to update disk i/o stats");
}
}
}
}
}
if refreshes.storage()
&& let Some((total_space, available_space)) =
unsafe { get_drive_size(&self.mount_point) }
{
self.total_space = total_space;
self.available_space = available_space;
}
true
}
pub(crate) fn usage(&self) -> DiskUsage {
DiskUsage {
read_bytes: self.read_bytes.saturating_sub(self.old_read_bytes),
total_read_bytes: self.read_bytes,
written_bytes: self.written_bytes.saturating_sub(self.old_written_bytes),
total_written_bytes: self.written_bytes,
}
}
}
pub(crate) struct DisksInner {
pub(crate) disks: Vec<Disk>,
}
impl DisksInner {
pub(crate) fn new() -> Self {
Self {
disks: Vec::with_capacity(2),
}
}
pub(crate) fn from_vec(disks: Vec<Disk>) -> Self {
Self { disks }
}
pub(crate) fn into_vec(self) -> Vec<Disk> {
self.disks
}
pub(crate) fn refresh_specifics(
&mut self,
remove_not_listed_disks: bool,
refreshes: DiskRefreshKind,
) {
unsafe {
get_list(&mut self.disks, remove_not_listed_disks, refreshes);
}
}
pub(crate) fn list(&self) -> &[Disk] {
&self.disks
}
pub(crate) fn list_mut(&mut self) -> &mut [Disk] {
&mut self.disks
}
}
unsafe fn get_drive_size(mount_point: &[u16]) -> Option<(u64, u64)> {
let mut total_size = 0;
let mut available_space = 0;
let lpdirectoryname = PCWSTR::from_raw(mount_point.as_ptr());
if unsafe {
GetDiskFreeSpaceExW(
lpdirectoryname,
None,
Some(&mut total_size),
Some(&mut available_space),
)
}
.is_ok()
{
Some((total_size, available_space))
} else {
None
}
}
pub(crate) unsafe fn get_list(
disks: &mut Vec<Disk>,
remove_not_listed_disks: bool,
refreshes: DiskRefreshKind,
) {
for volume_name in get_volume_guid_paths() {
let mount_paths = unsafe { get_volume_path_names_for_volume_name(&volume_name[..]) };
if mount_paths.is_empty() {
continue;
}
let raw_volume_name = PCWSTR::from_raw(volume_name.as_ptr());
let drive_type = unsafe { GetDriveTypeW(raw_volume_name) };
if drive_type != DRIVE_FIXED && drive_type != DRIVE_REMOVABLE {
continue;
}
let is_removable = drive_type == DRIVE_REMOVABLE;
let mut name = [0u16; MAX_PATH as usize + 1];
let mut file_system = [0u16; 32];
let mut flags = 0;
let volume_info_res = unsafe {
GetVolumeInformationW(
raw_volume_name,
Some(&mut name),
None,
None,
Some(&mut flags),
Some(&mut file_system),
)
}
.is_ok();
if !volume_info_res {
sysinfo_debug!(
"Error: GetVolumeInformationW = {:?}",
Error::from_thread().code()
);
continue;
}
let is_read_only = (flags & FILE_READ_ONLY_VOLUME) != 0;
let device_path = volume_name[..(volume_name.len() - 2)]
.iter()
.copied()
.chain([0])
.collect::<Vec<_>>();
let name = os_string_from_zero_terminated(&name);
let file_system = os_string_from_zero_terminated(&file_system);
for mount_path in mount_paths {
if let Some(disk) = disks
.iter_mut()
.find(|d| d.inner.mount_point == mount_path && d.inner.file_system == file_system)
{
disk.refresh_specifics(refreshes);
disk.inner.updated = true;
continue;
}
let mut disk = DiskInner {
type_: DiskKind::Unknown(-1),
name: name.clone(),
file_system: file_system.clone(),
s_mount_point: OsString::from_wide(&mount_path[..mount_path.len() - 1]),
mount_point: mount_path,
total_space: 0,
available_space: 0,
is_removable,
is_read_only,
device_path: device_path.clone(),
old_read_bytes: 0,
old_written_bytes: 0,
read_bytes: 0,
written_bytes: 0,
updated: true,
};
disk.refresh_specifics(refreshes);
disks.push(Disk { inner: disk });
}
}
if remove_not_listed_disks {
disks.retain_mut(|disk| {
if !disk.inner.updated {
return false;
}
disk.inner.updated = false;
true
});
} else {
for c in disks.iter_mut() {
c.inner.updated = false;
}
}
}
fn os_string_from_zero_terminated(name: &[u16]) -> OsString {
let len = name.iter().position(|&x| x == 0).unwrap_or(name.len());
OsString::from_wide(&name[..len])
}
unsafe fn get_disk_kind(handle: &HandleWrapper) -> DiskKind {
let spq_trim = STORAGE_PROPERTY_QUERY {
PropertyId: StorageDeviceSeekPenaltyProperty,
QueryType: PropertyStandardQuery,
AdditionalParameters: [0],
};
let mut result: DEVICE_SEEK_PENALTY_DESCRIPTOR = unsafe { std::mem::zeroed() };
let mut dw_size = 0;
let device_io_control = unsafe {
DeviceIoControl(
handle.0,
IOCTL_STORAGE_QUERY_PROPERTY,
Some(&spq_trim as *const STORAGE_PROPERTY_QUERY as *const _),
size_of::<STORAGE_PROPERTY_QUERY>() as _,
Some(&mut result as *mut DEVICE_SEEK_PENALTY_DESCRIPTOR as *mut _),
size_of::<DEVICE_SEEK_PENALTY_DESCRIPTOR>() as _,
Some(&mut dw_size),
None,
)
.is_ok()
};
if !device_io_control || dw_size != size_of::<DEVICE_SEEK_PENALTY_DESCRIPTOR>() as u32 {
DiskKind::Unknown(-1)
} else {
let is_hdd = result.IncursSeekPenalty;
if is_hdd { DiskKind::HDD } else { DiskKind::SSD }
}
}
fn get_disk_io(handle: HandleWrapper) -> Option<(u64, u64)> {
let mut disk_perf = DISK_PERFORMANCE::default();
let mut bytes_returned = 0;
unsafe {
DeviceIoControl(
handle.0,
IOCTL_DISK_PERFORMANCE,
None, 0,
Some(&mut disk_perf as *mut _ as _),
size_of::<DISK_PERFORMANCE>() as u32,
Some(&mut bytes_returned),
None,
)
}
.inspect_err(|_err| {
sysinfo_debug!(
"Error: DeviceIoControl(IOCTL_DISK_PERFORMANCE) = {:?}",
_err
);
})
.ok()?;
Some((
disk_perf.BytesRead.try_into().ok()?,
disk_perf.BytesWritten.try_into().ok()?,
))
}