Merge branch 'dk_usb'

This commit is contained in:
David Kocik 2022-10-13 15:26:37 +02:00
commit a77d0dce61
2 changed files with 216 additions and 6 deletions

View File

@ -2192,6 +2192,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
format(_L("Successfully unmounted. The device %s(%s) can now be safely removed from the computer."), evt.data.first.name, evt.data.first.path)
);
} else {
notification_manager->close_notification_of_type(NotificationType::ExportFinished);
notification_manager->push_notification(NotificationType::CustomNotification,
NotificationManager::NotificationLevel::ErrorNotificationLevel,
format(_L("Ejecting of device %s(%s) has failed."), evt.data.first.name, evt.data.first.path)

View File

@ -10,9 +10,9 @@
#include <tchar.h>
#include <winioctl.h>
#include <shlwapi.h>
#include <Dbt.h>
#include <Setupapi.h>
#include <cfgmgr32.h>
#else
// unix, linux & OSX includes
#include <errno.h>
@ -72,6 +72,192 @@ std::vector<DriveData> RemovableDriveManager::search_for_removable_drives() cons
return current_drives;
}
namespace {
// 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)
{
bool is_floppy = (wcsstr(dos_device_name, L"\\Floppy") != NULL); // TODO: could be tested better?
GUID* guid;
switch (drive_type) {
case DRIVE_REMOVABLE:
if (is_floppy) {
// we are interested only in SD cards or USB sticks
BOOST_LOG_TRIVIAL(debug) << "get_dev_inst_by_device_number failed: Drive is floppy disk.";
return 0;
//guid = (GUID*)&GUID_DEVINTERFACE_FLOPPY;
} else {
guid = (GUID*)&GUID_DEVINTERFACE_DISK;
}
break;
case DRIVE_FIXED:
// we are interested only in SD cards or USB sticks
BOOST_LOG_TRIVIAL(debug) << "get_dev_inst_by_device_number failed: Drive is harddisk.";
return 0;
//guid = (GUID*)&GUID_DEVINTERFACE_DISK;
//break;
case DRIVE_CDROM:
BOOST_LOG_TRIVIAL(debug) << "get_dev_inst_by_device_number failed: Drive is cd-rom.";
// we are interested only in SD cards or USB sticks
return 0;
//guid = (GUID*)&GUID_DEVINTERFACE_CDROM;
//break;
default:
return 0;
}
// Get device interface info set handle for all devices attached to system
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.";
return 0;
}
// Retrieve a context structure for a device interface of a device information set
BYTE buf[1024];
PSP_DEVICE_INTERFACE_DETAIL_DATA pspdidd = (PSP_DEVICE_INTERFACE_DETAIL_DATA)buf;
SP_DEVICE_INTERFACE_DATA spdid;
SP_DEVINFO_DATA spdd;
DWORD size;
spdid.cbSize = sizeof(spdid);
// Loop through devices and compare device numbers
for (DWORD index = 0; SetupDiEnumDeviceInterfaces(h_dev_info, NULL, guid, index, &spdid); ++index) {
SetupDiGetDeviceInterfaceDetail(h_dev_info, &spdid, NULL, 0, &size, NULL);
// check the buffer size
if (size == 0 || size > sizeof(buf)) {
continue;
}
// prepare structures
pspdidd->cbSize = sizeof(*pspdidd);
ZeroMemory(&spdd, sizeof(spdd));
spdd.cbSize = sizeof(spdd);
// fill structures
long res = SetupDiGetDeviceInterfaceDetail(h_dev_info, &spdid, pspdidd, size, &size, &spdd);
if (!res) {
continue;
}
// open the drive with pspdidd->DevicePath to compare device numbers
HANDLE drive_handle = CreateFile(pspdidd->DevicePath, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
if (drive_handle == INVALID_HANDLE_VALUE) {
continue;
}
// get its device number
STORAGE_DEVICE_NUMBER sdn;
DWORD bytes_returned = 0;
res = DeviceIoControl(drive_handle, IOCTL_STORAGE_GET_DEVICE_NUMBER, NULL, 0, &sdn, sizeof(sdn), &bytes_returned, NULL);
CloseHandle(drive_handle);
if (!res) {
continue;
}
//compare
if (device_number != (long)sdn.DeviceNumber) {
continue;
}
// this is the drive, return the device instance
SetupDiDestroyDeviceInfoList(h_dev_info);
return spdd.DevInst;
}
SetupDiDestroyDeviceInfoList(h_dev_info);
BOOST_LOG_TRIVIAL(debug) << "get_dev_inst_by_device_number failed: Enmurating couldn't find the drive.";
return 0;
}
// Perform eject using CM_Request_Device_EjectW.
// Returns 0 if success.
int eject_inner(const std::string& path)
{
// Following implementation is based on https://stackoverflow.com/a/58848961
assert(path.size() > 0);
std::wstring wpath = std::wstring();
wpath += boost::nowide::widen(path)[0]; // drive letter wide
wpath[0] &= ~0x20; // make sure drive letter is uppercase
assert(wpath[0] >= 'A' && wpath[0] <= 'Z');
std::wstring root_path = wpath + L":\\"; // for GetDriveType
std::wstring device_path = wpath + L":"; //for QueryDosDevice
std::wstring volume_access_path = L"\\\\.\\" + wpath + L":"; // for CreateFile
long device_number = -1;
// open the storage volume
HANDLE volume_handle = CreateFileW(volume_access_path.c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, NULL, NULL);
if (volume_handle == INVALID_HANDLE_VALUE) {
BOOST_LOG_TRIVIAL(error) << GUI::format("Ejecting of %1% has failed: Invalid value of file handle.", path);
return 1;
}
// get the volume's device number
STORAGE_DEVICE_NUMBER sdn;
DWORD bytes_returned = 0;
long res = DeviceIoControl(volume_handle, IOCTL_STORAGE_GET_DEVICE_NUMBER, NULL, 0, &sdn, sizeof(sdn), &bytes_returned, NULL);
if (res) {
device_number = sdn.DeviceNumber;
}
CloseHandle(volume_handle);
if (device_number == -1) {
BOOST_LOG_TRIVIAL(error) << GUI::format("Ejecting of %1% has failed: Invalid device number.", path);
return 1;
}
// get the drive type which is required to match the device numbers correctely
UINT drive_type = GetDriveTypeW(root_path.c_str());
// get the dos device name (like \device\floppy0) to decide if it's a floppy or not
WCHAR dos_device_name[MAX_PATH];
res = QueryDosDeviceW(device_path.c_str(), dos_device_name, MAX_PATH);
if (!res) {
BOOST_LOG_TRIVIAL(error) << GUI::format("Ejecting of %1% has failed: Invalid dos device name.", path);
return 1;
}
// 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;
}
PNP_VETO_TYPE veto_type = PNP_VetoTypeUnknown;
WCHAR veto_name[MAX_PATH];
veto_name[0] = 0;
// get drives's parent, e.g. the USB bridge, the SATA port, an IDE channel with two drives!
DEVINST dev_inst_parent = 0;
res = CM_Get_Parent(&dev_inst_parent, dev_inst, 0);
#if 0
// loop with several tries and sleep (this is running on main UI thread)
for (int i = 0; i < 3; ++i) {
veto_name[0] = 0;
// CM_Query_And_Remove_SubTree doesn't work for restricted users
//res = CM_Query_And_Remove_SubTreeW(DevInstParent, &VetoType, VetoNameW, MAX_PATH, CM_REMOVE_NO_RESTART); // CM_Query_And_Remove_SubTreeA is not implemented under W2K!
//res = CM_Query_And_Remove_SubTreeW(DevInstParent, NULL, NULL, 0, CM_REMOVE_NO_RESTART); // with messagebox (W2K, Vista) or balloon (XP)
res = CM_Request_Device_EjectW(dev_inst_parent, &veto_type, veto_name, MAX_PATH, 0);
//res = CM_Request_Device_EjectW(DevInstParent, NULL, NULL, 0, 0); // with messagebox (W2K, Vista) or balloon (XP)
if (res == CR_SUCCESS && veto_type == PNP_VetoTypeUnknown) {
return 0;
}
// Wait for next try.
// This is main thread!
Sleep(500);
}
#endif // 0
// perform eject
res = CM_Request_Device_EjectW(dev_inst_parent, &veto_type, veto_name, MAX_PATH, 0);
if (res == CR_SUCCESS && veto_type == PNP_VetoTypeUnknown) {
return 0;
}
BOOST_LOG_TRIVIAL(error) << GUI::format("Ejecting of %1% has failed: Request to eject device has failed.", path);
return 1;
}
}
// Called from UI therefore it blocks the UI thread.
// It also blocks updates at the worker thread.
// Win32 implementation.
@ -86,6 +272,28 @@ void RemovableDriveManager::eject_drive()
BOOST_LOG_TRIVIAL(info) << "Ejecting started";
std::scoped_lock<std::mutex> lock(m_drives_mutex);
auto it_drive_data = this->find_last_save_path_drive_data();
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)));
} else {
// failed to eject
// this should not happen, throwing exception might be the way here
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
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)));
}
#if 0
// Implementation used until 2.5.x version
// Some usb drives does not eject properly (still visible in file explorer). Some even does not write all content and eject.
if (it_drive_data != m_current_drives.end()) {
// get handle to device
std::string mpath = "\\\\.\\" + m_last_save_path;
@ -102,10 +310,10 @@ void RemovableDriveManager::eject_drive()
//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();
BOOST_LOG_TRIVIAL(error) << "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 but it returns error to me
BOOST_LOG_TRIVIAL(error) << "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);
@ -122,6 +330,7 @@ void RemovableDriveManager::eject_drive()
wxPostEvent(m_callback_evt_handler, RemovableDriveEjectEvent(EVT_REMOVABLE_DRIVE_EJECTED, std::pair< DriveData, bool >(std::move(*it_drive_data), true)));
m_current_drives.erase(it_drive_data);
}
#endif // 0
}
std::string RemovableDriveManager::get_removable_drive_path(const std::string &path)