Alternative eject function (IOCTL_STORAGE_EJECT_MEDIA)

Detect if device has CARD READER configuration in Configuration Descriptor


log
This commit is contained in:
David Kocik 2023-03-31 13:10:56 +02:00
parent e1861d220c
commit 6be2aa29dc

View File

@ -13,6 +13,11 @@
#include <Dbt.h>
#include <Setupapi.h>
#include <cfgmgr32.h>
#include <initguid.h> // include before devpropdef.h
#include <devpropdef.h>
#include <devpkey.h>
#include <usbioctl.h>
#else
// unix, linux & OSX includes
#include <errno.h>
@ -73,6 +78,287 @@ std::vector<DriveData> RemovableDriveManager::search_for_removable_drives() cons
}
namespace {
int eject_alt(const std::wstring& volume_access_path)
{
HANDLE handle = CreateFileW(volume_access_path.c_str(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr);
if (handle == INVALID_HANDLE_VALUE) {
BOOST_LOG_TRIVIAL(error) << "Alt Ejecting " << volume_access_path << " failed (handle == INVALID_HANDLE_VALUE): " << GetLastError();
return 1;
}
DWORD deviceControlRetVal(0);
//these 3 commands should eject device safely but they dont, the device does disappear from file explorer but the "device was safely remove" notification doesnt trigger.
//sd cards does trigger WM_DEVICECHANGE messege, usb drives dont
BOOL e1 = DeviceIoControl(handle, FSCTL_LOCK_VOLUME, nullptr, 0, nullptr, 0, &deviceControlRetVal, nullptr);
BOOST_LOG_TRIVIAL(debug) << "FSCTL_LOCK_VOLUME " << e1 << " ; " << deviceControlRetVal << " ; " << GetLastError();
BOOL e2 = DeviceIoControl(handle, FSCTL_DISMOUNT_VOLUME, nullptr, 0, nullptr, 0, &deviceControlRetVal, nullptr);
BOOST_LOG_TRIVIAL(debug) << "FSCTL_DISMOUNT_VOLUME " << e2 << " ; " << deviceControlRetVal << " ; " << GetLastError();
// some implemenatations also calls IOCTL_STORAGE_MEDIA_REMOVAL here with FALSE as third parameter, which should set PreventMediaRemoval
BOOL error = DeviceIoControl(handle, IOCTL_STORAGE_EJECT_MEDIA, nullptr, 0, nullptr, 0, &deviceControlRetVal, nullptr);
if (error == 0) {
CloseHandle(handle);
BOOST_LOG_TRIVIAL(error) << "Alt Ejecting " << volume_access_path << " failed (IOCTL_STORAGE_EJECT_MEDIA)" << deviceControlRetVal << " " << GetLastError();
return 1;
}
CloseHandle(handle);
BOOST_LOG_TRIVIAL(info) << "Alt Ejecting finished";
return 0;
}
// From https://github.com/microsoft/Windows-driver-samples/tree/main/usb/usbview
typedef struct _STRING_DESCRIPTOR_NODE
{
struct _STRING_DESCRIPTOR_NODE* Next;
UCHAR DescriptorIndex;
USHORT LanguageID;
USB_STRING_DESCRIPTOR StringDescriptor[1];
} STRING_DESCRIPTOR_NODE, * PSTRING_DESCRIPTOR_NODE;
// Based at https://github.com/microsoft/Windows-driver-samples/tree/main/usb/usbview
PSTRING_DESCRIPTOR_NODE GetStringDescriptor(
HANDLE handle_hub_device,
ULONG connection_index,
UCHAR descriptor_index,
USHORT language_ID
)
{
BOOL success = 0;
ULONG nbytes = 0;
ULONG nbytes_returned = 0;
UCHAR string_desc_req_buf[sizeof(USB_DESCRIPTOR_REQUEST) + MAXIMUM_USB_STRING_LENGTH];
PUSB_DESCRIPTOR_REQUEST string_desc_req = NULL;
PUSB_STRING_DESCRIPTOR string_desc = NULL;
PSTRING_DESCRIPTOR_NODE string_desc_node = NULL;
nbytes = sizeof(string_desc_req_buf);
string_desc_req = (PUSB_DESCRIPTOR_REQUEST)string_desc_req_buf;
string_desc = (PUSB_STRING_DESCRIPTOR)(string_desc_req + 1);
// Zero fill the entire request structure
memset(string_desc_req, 0, nbytes);
// Indicate the port from which the descriptor will be requested
string_desc_req->ConnectionIndex = connection_index;
// USBHUB uses URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE to process this
// IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION request.
//
// USBD will automatically initialize these fields:
// bmRequest = 0x80
// bRequest = 0x06
//
// We must inititialize these fields:
// wValue = Descriptor Type (high) and Descriptor Index (low byte)
// wIndex = Zero (or Language ID for String Descriptors)
// wLength = Length of descriptor buffer
string_desc_req->SetupPacket.wValue = (USB_STRING_DESCRIPTOR_TYPE << 8)
| descriptor_index;
string_desc_req->SetupPacket.wIndex = language_ID;
string_desc_req->SetupPacket.wLength = (USHORT)(nbytes - sizeof(USB_DESCRIPTOR_REQUEST));
// Now issue the get descriptor request.
success = DeviceIoControl(handle_hub_device,
IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION,
string_desc_req,
nbytes,
string_desc_req,
nbytes,
&nbytes_returned,
NULL);
// Do some sanity checks on the return from the get descriptor request.
if (!success) {
return NULL;
}
if (nbytes_returned < 2) {
return NULL;
}
if (string_desc->bDescriptorType != USB_STRING_DESCRIPTOR_TYPE) {
return NULL;
}
if (string_desc->bLength != nbytes_returned - sizeof(USB_DESCRIPTOR_REQUEST)) {
return NULL;
}
if (string_desc->bLength % 2 != 0) {
return NULL;
}
// Looks good, allocate some (zero filled) space for the string descriptor
// node and copy the string descriptor to it.
string_desc_node = (PSTRING_DESCRIPTOR_NODE)malloc(sizeof(STRING_DESCRIPTOR_NODE) + string_desc->bLength * sizeof(DWORD));
if (string_desc_node == NULL) {
return NULL;
}
string_desc_node->Next = NULL;
string_desc_node->DescriptorIndex = descriptor_index;
string_desc_node->LanguageID = language_ID;
memcpy(string_desc_node->StringDescriptor,
string_desc,
string_desc->bLength);
return string_desc_node;
}
// Based at https://github.com/microsoft/Windows-driver-samples/tree/main/usb/usbview
HRESULT GetStringDescriptors(
_In_ HANDLE handle_hub_device,
_In_ ULONG connection_index,
_In_ UCHAR descriptor_index,
_In_ ULONG num_language_IDs,
_In_reads_(num_language_IDs) USHORT* language_IDs,
_In_ PSTRING_DESCRIPTOR_NODE string_desc_node_head,
std::wstring& result
)
{
PSTRING_DESCRIPTOR_NODE tail = NULL;
PSTRING_DESCRIPTOR_NODE trailing = NULL;
ULONG i = 0;
// Go to the end of the linked list, searching for the requested index to
// see if we've already retrieved it
for (tail = string_desc_node_head; tail != NULL; tail = tail->Next) {
if (tail->DescriptorIndex == descriptor_index) {
// copy string descriptor to result
for(int i = 0; i < tail->StringDescriptor->bLength / 2 - 1; i++) {
result += tail->StringDescriptor->bString[i];
}
return S_OK;
}
trailing = tail;
}
tail = trailing;
// Get the next String Descriptor. If this is NULL, then we're done (return)
// Otherwise, loop through all Language IDs
for (i = 0; (tail != NULL) && (i < num_language_IDs); i++) {
tail->Next = GetStringDescriptor(handle_hub_device,
connection_index,
descriptor_index,
language_IDs[i]);
tail = tail->Next;
}
if (tail == NULL) {
return E_FAIL;
} else {
// copy string descriptor to result
for (int i = 0; i < tail->StringDescriptor->bLength / 2 - 1; i++) {
result += tail->StringDescriptor->bString[i];
}
return S_OK;
}
}
bool get_handle_from_devinst(DEVINST devinst, HANDLE& handle)
{
// create path consisting of device id and guid
wchar_t device_id[MAX_PATH];
CM_Get_Device_ID(devinst, device_id, MAX_PATH, 0);
//convert device id string to device path - https://stackoverflow.com/a/32641140/981766
std::wstring dev_id_wstr(device_id);
dev_id_wstr = std::regex_replace(dev_id_wstr, std::wregex(LR"(\\)"), L"#"); // '\' is special for regex
dev_id_wstr = std::regex_replace(dev_id_wstr, std::wregex(L"^"), LR"(\\?\)", std::regex_constants::format_first_only);
dev_id_wstr = std::regex_replace(dev_id_wstr, std::wregex(L"$"), L"#", std::regex_constants::format_first_only);
// guid
wchar_t guid_wchar[64];//guid is 32 chars+4 hyphens+2 paranthesis+null => 64 should be more than enough
StringFromGUID2(GUID_DEVINTERFACE_USB_HUB, guid_wchar, 64);
dev_id_wstr.append(guid_wchar);
// get handle
std::wstring& usb_hub_path = dev_id_wstr;
handle = CreateFileW(usb_hub_path.c_str(), GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
if (handle == INVALID_HANDLE_VALUE) {
// Sometimes device is not GUID_DEVINTERFACE_USB_HUB, than we need to check parent recursively
DEVINST parent_devinst = 0;
if (CM_Get_Parent(&parent_devinst, devinst, 0) != CR_SUCCESS)
return false;
return get_handle_from_devinst(parent_devinst, handle);
}
return true;
}
// Read Configuration Descriptor - configuration string indexed by iConfiguration and decide if card reader
bool is_card_reader(HDEVINFO h_dev_info, SP_DEVINFO_DATA& spdd)
{
// First get port number of device.
DEVINST parent_devinst = 0;
HANDLE handle; // usb hub handle
DWORD usb_port_number = 0;
DWORD required_size = 0;
// First we need handle for GUID_DEVINTERFACE_USB_HUB device.
if (CM_Get_Parent(&parent_devinst, spdd.DevInst, 0) != CR_SUCCESS) {
BOOST_LOG_TRIVIAL(warning) << "is_card_reader failed: Couldn't get parent DEVINST.";
return false;
}
if(!get_handle_from_devinst(parent_devinst, handle) || handle == INVALID_HANDLE_VALUE) {
BOOST_LOG_TRIVIAL(warning) << "is_card_reader failed: Couldn't get HANDLE for parent DEVINST.";
return false;
}
// Get port number to which the usb device is attached on the hub.
if (SetupDiGetDeviceRegistryProperty(h_dev_info, &spdd, SPDRP_ADDRESS, nullptr, (PBYTE)&usb_port_number, sizeof(usb_port_number), &required_size) == 0) {
BOOST_LOG_TRIVIAL(warning) << "is_card_reader failed: Couldn't get port number.";
return false;
}
// Fill USB request packet to get iConfiguration value.
int buffer_size = sizeof(USB_DESCRIPTOR_REQUEST) + sizeof(USB_CONFIGURATION_DESCRIPTOR);
BYTE* buffer = new BYTE[buffer_size];
USB_DESCRIPTOR_REQUEST* request_packet = (USB_DESCRIPTOR_REQUEST*)buffer;
USB_CONFIGURATION_DESCRIPTOR* configuration_descriptor = (USB_CONFIGURATION_DESCRIPTOR*)((BYTE*)buffer + sizeof(USB_DESCRIPTOR_REQUEST));
DWORD bytes_returned = 0;
// Fill information in packet.
request_packet->SetupPacket.bmRequest = 0x80;
request_packet->SetupPacket.bRequest = USB_REQUEST_GET_CONFIGURATION;
request_packet->ConnectionIndex = usb_port_number;
request_packet->SetupPacket.wValue = (USB_CONFIGURATION_DESCRIPTOR_TYPE << 8 | 0 /*Since only 1 device descriptor => index : 0*/);
request_packet->SetupPacket.wLength = sizeof(USB_CONFIGURATION_DESCRIPTOR);
// Issue ioctl.
if (DeviceIoControl(handle, IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION, buffer, buffer_size, buffer, buffer_size, &bytes_returned, nullptr) == 0) {
BOOST_LOG_TRIVIAL(warning) << "is_card_reader failed: Couldn't get Configuration Descriptor.";
return false;
}
// Nothing to read.
if (configuration_descriptor->iConfiguration == 0) {
BOOST_LOG_TRIVIAL(warning) << "is_card_reader failed: iConfiguration value is 0.";
return false;
}
// Get string descriptor and read string on address given by iConfiguration index .
// Based at https://github.com/microsoft/Windows-driver-samples/tree/main/usb/usbview
PSTRING_DESCRIPTOR_NODE supported_languages_string = NULL;
ULONG num_language_IDs = 0;
USHORT* language_IDs = NULL;
std::wstring configuration_string;
// Get languages.
supported_languages_string = GetStringDescriptor(handle, usb_port_number, 0, 0);
if (supported_languages_string == NULL) {
BOOST_LOG_TRIVIAL(warning) << "is_card_reader failed: Couldn't get language string descriptor.";
return false;
}
num_language_IDs = (supported_languages_string->StringDescriptor->bLength - 2) / 2;
language_IDs = (USHORT*)&supported_languages_string->StringDescriptor->bString[0];
// Get configration string.
if (GetStringDescriptors(handle, usb_port_number, configuration_descriptor->iConfiguration, num_language_IDs, language_IDs, supported_languages_string, configuration_string) == E_FAIL) {
BOOST_LOG_TRIVIAL(warning) << "is_card_reader failed: Couldn't get configuration string descriptor.";
return false;
}
// Final compare.
BOOST_LOG_TRIVIAL(error) << "Ejecting information: Retrieved configuration string: " << configuration_string;
if (configuration_string.find(L"CARD READER") != std::wstring::npos) {
BOOST_LOG_TRIVIAL(info) << "Detected external reader.";
return true;
}
return false;
}
// returns the device instance handle of a storage volume or 0 on error
// called from eject_inner, based on https://stackoverflow.com/a/58848961
DEVINST get_dev_inst_by_device_number(long device_number, UINT drive_type, WCHAR* dos_device_name)
@ -80,7 +366,7 @@ DEVINST get_dev_inst_by_device_number(long device_number, UINT drive_type, WCHAR
bool is_floppy = (wcsstr(dos_device_name, L"\\Floppy") != NULL); // TODO: could be tested better?
if (drive_type != DRIVE_REMOVABLE || is_floppy) {
BOOST_LOG_TRIVIAL(debug) << "get_dev_inst_by_device_number failed: Drive is not removable.";
BOOST_LOG_TRIVIAL(warning) << "get_dev_inst_by_device_number failed: Drive is not removable.";
return 0;
}
@ -89,7 +375,7 @@ DEVINST get_dev_inst_by_device_number(long device_number, UINT drive_type, WCHAR
HDEVINFO h_dev_info = SetupDiGetClassDevs(guid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
if (h_dev_info == INVALID_HANDLE_VALUE) {
BOOST_LOG_TRIVIAL(debug) << "get_dev_inst_by_device_number failed: Invalid dev info handle.";
BOOST_LOG_TRIVIAL(warning) << "get_dev_inst_by_device_number failed: Invalid dev info handle.";
return 0;
}
@ -135,13 +421,16 @@ DEVINST get_dev_inst_by_device_number(long device_number, UINT drive_type, WCHAR
if (device_number != (long)sdn.DeviceNumber) {
continue;
}
// this is the drive, return the device instance
// check if is sd card reader - if yes, indicate by returning invalid value.
bool reader = is_card_reader(h_dev_info, spdd);
SetupDiDestroyDeviceInfoList(h_dev_info);
return spdd.DevInst;
return !reader ? spdd.DevInst : 0;
}
SetupDiDestroyDeviceInfoList(h_dev_info);
BOOST_LOG_TRIVIAL(debug) << "get_dev_inst_by_device_number failed: Enmurating couldn't find the drive.";
BOOST_LOG_TRIVIAL(warning) << "get_dev_inst_by_device_number failed: Enmurating couldn't find the drive.";
return 0;
}
@ -194,10 +483,9 @@ int eject_inner(const std::string& path)
// get the device instance handle of the storage volume by means of a SetupDi enum and matching the device number
DEVINST dev_inst = get_dev_inst_by_device_number(device_number, drive_type, dos_device_name);
if (dev_inst == 0) {
BOOST_LOG_TRIVIAL(error) << GUI::format("Ejecting of %1% has failed: Invalid device instance handle.", path);
return 1;
BOOST_LOG_TRIVIAL(error) << GUI::format("Ejecting of %1%: Invalid device instance handle. Going to try alternative ejecting method.", path);
return eject_alt(volume_access_path);
}
PNP_VETO_TYPE veto_type = PNP_VetoTypeUnknown;
@ -249,7 +537,7 @@ int eject_inner(const std::string& path)
return 1;
}
}
} // namespace
// Called from UI therefore it blocks the UI thread.
// It also blocks updates at the worker thread.
// Win32 implementation.
@ -268,18 +556,21 @@ void RemovableDriveManager::eject_drive()
if (it_drive_data != m_current_drives.end()) {
if (!eject_inner(m_last_save_path)) {
// success
assert(m_callback_evt_handler);
if (m_callback_evt_handler)
wxPostEvent(m_callback_evt_handler, RemovableDriveEjectEvent(EVT_REMOVABLE_DRIVE_EJECTED, std::pair< DriveData, bool >(std::move(*it_drive_data), true)));
BOOST_LOG_TRIVIAL(info) << "Ejecting has succeeded.";
assert(m_callback_evt_handler);
if (m_callback_evt_handler)
wxPostEvent(m_callback_evt_handler, RemovableDriveEjectEvent(EVT_REMOVABLE_DRIVE_EJECTED, std::pair< DriveData, bool >(std::move(*it_drive_data), true)));
} else {
// failed to eject
// this should not happen, throwing exception might be the way here
BOOST_LOG_TRIVIAL(error) << "Ejecting has failed.";
assert(m_callback_evt_handler);
if (m_callback_evt_handler)
wxPostEvent(m_callback_evt_handler, RemovableDriveEjectEvent(EVT_REMOVABLE_DRIVE_EJECTED, std::pair<DriveData, bool>(*it_drive_data, false)));
}
} else {
// drive not found in m_current_drives
BOOST_LOG_TRIVIAL(error) << "Ejecting has failed. Drive not found in m_current_drives.";
assert(m_callback_evt_handler);
if (m_callback_evt_handler)
wxPostEvent(m_callback_evt_handler, RemovableDriveEjectEvent(EVT_REMOVABLE_DRIVE_EJECTED, std::pair<DriveData, bool>({"",""}, false)));