0

Inspired by balena.io's drivelist, I create a Rust version. The idea is simple, using Windows' Setup API to read all connected storages list (with mount points/drive letters), using winapi crate

///file main.rs
use core::{slice, ffi};
use std::{ptr::{null_mut, addr_of, null}, mem::{zeroed, size_of, MaybeUninit, align_of, transmute}, str::from_utf8};

use winapi::{um::{setupapi::{SetupDiGetClassDevsA, DIGCF_PRESENT, DIGCF_DEVICEINTERFACE, SP_DEVINFO_DATA, SetupDiEnumDeviceInfo, HDEVINFO, PSP_DEVINFO_DATA, SetupDiGetDeviceRegistryPropertyW, SPDRP_FRIENDLYNAME, SPDRP_REMOVAL_POLICY, SPDRP_ENUMERATOR_NAME, SetupDiDestroyDeviceInfoList, SP_DEVICE_INTERFACE_DATA, SetupDiEnumDeviceInterfaces, SetupDiGetDeviceInterfaceDetailW, PSP_DEVICE_INTERFACE_DETAIL_DATA_W, SP_DEVICE_INTERFACE_DETAIL_DATA_W}, winioctl::{GUID_DEVINTERFACE_DISK, VOLUME_DISK_EXTENTS, IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS, PVOLUME_DISK_EXTENTS}, handleapi::{INVALID_HANDLE_VALUE, CloseHandle}, cfgmgr32::{CM_REMOVAL_POLICY_EXPECT_SURPRISE_REMOVAL, CM_REMOVAL_POLICY_EXPECT_ORDERLY_REMOVAL}, errhandlingapi::GetLastError, fileapi::{CreateFileW, OPEN_EXISTING}, winnt::{FILE_SHARE_READ, FILE_SHARE_WRITE, FILE_ATTRIBUTE_NORMAL}, ioapiset::DeviceIoControl}, shared::{minwindef::MAX_PATH, winerror::{ERROR_NO_MORE_ITEMS, ERROR_INSUFFICIENT_BUFFER}}, ctypes::c_void};

fn get_detail_data(h_dev_info:HDEVINFO,device_info_data:PSP_DEVINFO_DATA)
{
    let mut h_device=INVALID_HANDLE_VALUE;
    let mut index=0_u32;

    unsafe{
        loop {
            if h_device!= INVALID_HANDLE_VALUE {
                CloseHandle(h_device);
                h_device=INVALID_HANDLE_VALUE;
            }

            let mut device_interface_data:SP_DEVICE_INTERFACE_DATA=zeroed();
            device_interface_data.cbSize=size_of::<SP_DEVICE_INTERFACE_DATA>() as _;
            
            if SetupDiEnumDeviceInterfaces(h_dev_info, device_info_data, &GUID_DEVINTERFACE_DISK, index, &mut device_interface_data) == 0 {
                let error_code=GetLastError();

                if error_code!=ERROR_NO_MORE_ITEMS {
                    panic!("SetupDiEnumDeviceInterfaces: Error {}",error_code);
                }

                break;
            } else {
                let mut size={
                    let mut required_size=MaybeUninit::<u32>::uninit();

                    if SetupDiGetDeviceInterfaceDetailW(h_dev_info, &mut device_interface_data, null_mut(), 0, required_size.as_mut_ptr(), null_mut())==0 {
                        if GetLastError()==ERROR_INSUFFICIENT_BUFFER {
                            required_size.assume_init()
                        } else {
                            panic!("Error SetupDiGetDeviceInterfaceDetailW");
                        }
                    } else {
                        0
                    }
                };
                let mut buf:Vec<u8>=Vec::with_capacity(TryInto::<usize>::try_into(size).unwrap() + align_of::<SP_DEVICE_INTERFACE_DETAIL_DATA_W>()-1);
                let align_offset=buf.as_mut_ptr().align_offset(align_of::<SP_DEVICE_INTERFACE_DETAIL_DATA_W>());
                let device_iface_detail =&mut *(buf.as_mut_ptr().offset(align_offset.try_into().unwrap()) as *mut MaybeUninit<SP_DEVICE_INTERFACE_DETAIL_DATA_W>);
                device_iface_detail.write(SP_DEVICE_INTERFACE_DETAIL_DATA_W {
                    cbSize: size_of::<SP_DEVICE_INTERFACE_DETAIL_DATA_W>().try_into().unwrap(),
                    DevicePath: [0],
                });

                if SetupDiGetDeviceInterfaceDetailW(h_dev_info, &mut device_interface_data, device_iface_detail.as_mut_ptr(), size, &mut size, null_mut())==0 {
                    println!("Error {}, Couldn't SetupDiGetDeviceInterfaceDetailW",GetLastError());
                    break;
                }

                let device_detail_data=device_iface_detail.assume_init_ref();
                h_device=CreateFileW(device_detail_data.DevicePath.as_ptr(), 0, FILE_SHARE_READ,  null_mut(), OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, null_mut());

                if h_device==INVALID_HANDLE_VALUE {
                    println!("Couldn't open handle to device: Error {}",GetLastError());
                    break;
                }

                get_device_number(h_device);
            }

            index+=1;
        }

        if h_device!= INVALID_HANDLE_VALUE {
            CloseHandle(h_device);
            h_device=INVALID_HANDLE_VALUE;
        }
    }
}

fn get_device_number(h_device:*mut c_void)
{
    unsafe {
        let mut size=0_u32;

        let mut disk_extents=MaybeUninit::<VOLUME_DISK_EXTENTS>::uninit();
        disk_extents.write(zeroed());
        let result=DeviceIoControl(h_device, IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS, null_mut(), 0, disk_extents.as_mut_ptr() as _, size_of::<VOLUME_DISK_EXTENTS>() as _, &mut size, null_mut());
/* It returns 0!! */
        println!("Bytes returned: {}",size);

        if result!=0 {
            println!("Success");
        } else {
/* This will be executed */
            println!("get_device_number fail. Error {}",GetLastError());
        }
    }
}

fn get_enumerator_name(h_dev_info:HDEVINFO,device_info_data:PSP_DEVINFO_DATA) -> String
{
    unsafe {
        let mut buffer:[u8;MAX_PATH]=zeroed();
        
        if SetupDiGetDeviceRegistryPropertyW(h_dev_info, device_info_data, SPDRP_ENUMERATOR_NAME, null_mut(), &mut buffer as _, (size_of::<u8>() * MAX_PATH) as _, null_mut()) != 0 {
            ansi_to_string(&buffer)
        } else {
            "".to_string()
        }
    }
}

fn get_friendly_name(h_dev_info:HDEVINFO,device_info_data:PSP_DEVINFO_DATA) -> String
{
    unsafe {
        let mut buffer:[u8;MAX_PATH]=zeroed();
        
        if SetupDiGetDeviceRegistryPropertyW(h_dev_info, device_info_data, SPDRP_FRIENDLYNAME, null_mut(), &mut buffer as _, (size_of::<u8>() * MAX_PATH) as _, null_mut()) != 0 {
            ansi_to_string(&buffer)
        } else {
            "".to_string()
        }
    }
}

fn is_removable(h_dev_info:HDEVINFO,device_info_data:PSP_DEVINFO_DATA)->bool
{
    unsafe {
        let mut result=0_u8;
        SetupDiGetDeviceRegistryPropertyW(h_dev_info, device_info_data, SPDRP_REMOVAL_POLICY, null_mut(), &mut result as _, size_of::<u32>() as _, null_mut());

        match result as u32
        {
            CM_REMOVAL_POLICY_EXPECT_SURPRISE_REMOVAL|CM_REMOVAL_POLICY_EXPECT_ORDERLY_REMOVAL =>true,
            _=>false
        }
    }
}

fn is_usb_drive(enumerator_name:&str) -> bool
{
    ["USBSTOR", "UASPSTOR", "VUSBSTOR","RTUSER", "CMIUCR", "EUCR","ETRONSTOR", "ASUSSTPT"].contains(&enumerator_name)
}

fn ansi_to_string(unsafe_utf8:&[u8])->String
{
    match from_utf8(&unsafe_utf8.iter().filter(|c| **c != 0).map(|c| *c).collect::<Vec<u8>>() as _)
    {
        Err(err)=>{
            println!("Error {}",err);
            "".to_string()
        },
        Ok(res)=>res.trim().to_string()
    }
}

fn main() {
    unsafe {
        let h_device_info=SetupDiGetClassDevsA(&GUID_DEVINTERFACE_DISK, null_mut(), null_mut(), DIGCF_PRESENT|DIGCF_DEVICEINTERFACE);

        if h_device_info!=INVALID_HANDLE_VALUE {
            let mut i=0;
            let mut device_info_data:SP_DEVINFO_DATA=zeroed();
            device_info_data.cbSize=size_of::<SP_DEVINFO_DATA>() as _;

            while SetupDiEnumDeviceInfo(h_device_info, i, &mut device_info_data)!=0
            {
                let enumerator_name=get_enumerator_name(h_device_info, &mut device_info_data);
                let friendly_name=get_friendly_name(h_device_info, &mut device_info_data);

                if friendly_name.is_empty() {
                    continue;
                }

                println!("Name: {}",friendly_name);
                println!("Is USB drive: {}",is_usb_drive(&enumerator_name));
                println!("Is removable: {}",is_removable(h_device_info, &mut device_info_data));

                get_detail_data(h_device_info, &mut device_info_data);
                i+=1;
            }
        }

        SetupDiDestroyDeviceInfoList(h_device_info);
    }    
}

Terminal output:

Name: SKHynix_HFM512GDHTNI-87A0B
Is USB drive: false
Is removable: false
DevicePath: \\?\scsi#disk&ven_nvme&prod_skhynix_hfm512gd#5&8980ef4&0&000000#{53f56307-b6bf-11d0-94f2-00a0c91efb8b}
Bytes returned: 0
get_device_number fail. Error 1

Everything went fine until DeviceIoControl always returns 0 and GetLastError() returns 1 (inside get_device_number() function). Can someone guide me figuring out what went wrong? Thanks in advance

ir1keren
  • 37
  • 9
  • Error 1 means `ERROR_INVALID_FUNCTION`. I'm guessing that you are using `IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS` with a handle to some device that is not a _volume_, maybe with a harddisk handle? Can you try posting here the value of `device_detail_data.DevicePath` when you are opening it? – rodrigo Dec 27 '22 at 15:19
  • @rodrigo I don't know how to convert ```[u16; 1]``` to ```String``` in Rust. All Windows' C APi calls starts from line 157 on my posted code above. – ir1keren Dec 27 '22 at 23:03
  • I never did it, but I guess you can get an `OsString` by getting first a `WCHAR*` by calling to `device_detail_data.DevicePath.as_ptr()`. Then get the string length with `lstrlenW()`, build a proper `&[u16]` with `slice::from_raw_parts()` and finally `OsString::from_wide()`. And the `OsString` can be converted into a regular `String` with `OsString::into_string()`. – rodrigo Dec 27 '22 at 23:44
  • @rodrigo it printed: ```\\?\scsi#disk&ven_nvme&prod_skhynix_hfm512gd#5&8980ef4&0&000000#{53f56307-b6bf-11d0-94f2-00a0c91efb8b}``` and SKHynix_HFM512GDHTNI-87A0B is my main SSD. I got the idea of allocating memory for SP_DEVICE_INTERFACE_DETAIL_DATA_W from [here](https://stackoverflow.com/a/71101597/1868568) – ir1keren Dec 28 '22 at 01:05
  • 1
    I think that is what I guessed before, that is not a volume name but a disk one. A volume should look like `\\?\Volume{guid}`. I'm afraid I cannot help further, but take a look at this [answer](https://stackoverflow.com/a/29214084/865874). – rodrigo Dec 28 '22 at 02:14
  • @rodrigo ah ok. I got some ideas, I'll post my solution later. Thanks – ir1keren Dec 28 '22 at 06:04

0 Answers0