From 6be2aa29dc830bddf22f30a54dcbc12e8a3613f1 Mon Sep 17 00:00:00 2001 From: David Kocik Date: Fri, 31 Mar 2023 13:10:56 +0200 Subject: [PATCH] Alternative eject function (IOCTL_STORAGE_EJECT_MEDIA) Detect if device has CARD READER configuration in Configuration Descriptor log --- src/slic3r/GUI/RemovableDriveManager.cpp | 315 ++++++++++++++++++++++- 1 file changed, 303 insertions(+), 12 deletions(-) diff --git a/src/slic3r/GUI/RemovableDriveManager.cpp b/src/slic3r/GUI/RemovableDriveManager.cpp index b6881f1aead..ecd8e0727b3 100644 --- a/src/slic3r/GUI/RemovableDriveManager.cpp +++ b/src/slic3r/GUI/RemovableDriveManager.cpp @@ -13,6 +13,11 @@ #include #include #include + +#include // include before devpropdef.h +#include +#include +#include #else // unix, linux & OSX includes #include @@ -73,6 +78,287 @@ std::vector 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(*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({"",""}, false)));