/* * Process Hacker Plugins - * Hardware Devices Plugin * * Copyright (C) 2015-2016 dmex * * This file is part of Process Hacker. * * Process Hacker is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Process Hacker is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Process Hacker. If not, see . */ #include "devices.h" #include #define ITEM_CHECKED (INDEXTOSTATEIMAGEMASK(2)) #define ITEM_UNCHECKED (INDEXTOSTATEIMAGEMASK(1)) typedef struct _DISK_ENUM_ENTRY { ULONG DeviceIndex; BOOLEAN DevicePresent; PPH_STRING DevicePath; PPH_STRING DeviceName; PPH_STRING DeviceMountPoints; } DISK_ENUM_ENTRY, *PDISK_ENUM_ENTRY; static int __cdecl DiskEntryCompareFunction( _In_ const void *elem1, _In_ const void *elem2 ) { PDISK_ENUM_ENTRY entry1 = *(PDISK_ENUM_ENTRY *)elem1; PDISK_ENUM_ENTRY entry2 = *(PDISK_ENUM_ENTRY *)elem2; return uint64cmp(entry1->DeviceIndex, entry2->DeviceIndex); } VOID DiskDrivesLoadList( VOID ) { PPH_STRING settingsString; PH_STRINGREF remaining; settingsString = PhaGetStringSetting(SETTING_NAME_DISK_LIST); remaining = settingsString->sr; while (remaining.Length != 0) { PH_STRINGREF part; DV_DISK_ID id; PDV_DISK_ENTRY entry; if (remaining.Length == 0) break; PhSplitStringRefAtChar(&remaining, ',', &part, &remaining); InitializeDiskId(&id, PhCreateString2(&part)); entry = CreateDiskEntry(&id); DeleteDiskId(&id); entry->UserReference = TRUE; } } VOID DiskDrivesSaveList( VOID ) { PH_STRING_BUILDER stringBuilder; PPH_STRING settingsString; PhInitializeStringBuilder(&stringBuilder, 260); PhAcquireQueuedLockShared(&DiskDrivesListLock); for (ULONG i = 0; i < DiskDrivesList->Count; i++) { PDV_DISK_ENTRY entry = PhReferenceObjectSafe(DiskDrivesList->Items[i]); if (!entry) continue; if (entry->UserReference) { PhAppendFormatStringBuilder( &stringBuilder, L"%s,", entry->Id.DevicePath->Buffer // This value is SAFE and does not change. ); } PhDereferenceObjectDeferDelete(entry); } PhReleaseQueuedLockShared(&DiskDrivesListLock); if (stringBuilder.String->Length != 0) PhRemoveEndStringBuilder(&stringBuilder, 1); settingsString = PH_AUTO(PhFinalStringBuilderString(&stringBuilder)); PhSetStringSetting2(SETTING_NAME_DISK_LIST, &settingsString->sr); } BOOLEAN FindDiskEntry( _In_ PDV_DISK_ID Id, _In_ BOOLEAN RemoveUserReference ) { BOOLEAN found = FALSE; PhAcquireQueuedLockShared(&DiskDrivesListLock); for (ULONG i = 0; i < DiskDrivesList->Count; i++) { PDV_DISK_ENTRY currentEntry = PhReferenceObjectSafe(DiskDrivesList->Items[i]); if (!currentEntry) continue; found = EquivalentDiskId(¤tEntry->Id, Id); if (found) { if (RemoveUserReference) { if (currentEntry->UserReference) { PhDereferenceObjectDeferDelete(currentEntry); currentEntry->UserReference = FALSE; } } PhDereferenceObjectDeferDelete(currentEntry); break; } else { PhDereferenceObjectDeferDelete(currentEntry); } } PhReleaseQueuedLockShared(&DiskDrivesListLock); return found; } VOID AddDiskDriveToListView( _In_ PDV_DISK_OPTIONS_CONTEXT Context, _In_ BOOLEAN DiskPresent, _In_ PPH_STRING DiskPath, _In_ PPH_STRING DiskName ) { DV_DISK_ID adapterId; INT lvItemIndex; BOOLEAN found = FALSE; PDV_DISK_ID newId = NULL; InitializeDiskId(&adapterId, DiskPath); for (ULONG i = 0; i < DiskDrivesList->Count; i++) { PDV_DISK_ENTRY entry = PhReferenceObjectSafe(DiskDrivesList->Items[i]); if (!entry) continue; if (EquivalentDiskId(&entry->Id, &adapterId)) { newId = PhAllocate(sizeof(DV_DISK_ID)); CopyDiskId(newId, &entry->Id); if (entry->UserReference) found = TRUE; } PhDereferenceObjectDeferDelete(entry); if (newId) break; } if (!newId) { newId = PhAllocate(sizeof(DV_DISK_ID)); CopyDiskId(newId, &adapterId); PhMoveReference(&newId->DevicePath, DiskPath); } lvItemIndex = AddListViewItemGroupId( Context->ListViewHandle, DiskPresent ? 0 : 1, MAXINT, DiskName->Buffer, newId ); if (found) ListView_SetItemState(Context->ListViewHandle, lvItemIndex, ITEM_CHECKED, LVIS_STATEIMAGEMASK); DeleteDiskId(&adapterId); } VOID FreeListViewDiskDriveEntries( _In_ PDV_DISK_OPTIONS_CONTEXT Context ) { ULONG index = -1; while ((index = PhFindListViewItemByFlags( Context->ListViewHandle, index, LVNI_ALL )) != -1) { PDV_DISK_ID param; if (PhGetListViewItemParam(Context->ListViewHandle, index, ¶m)) { DeleteDiskId(param); PhFree(param); } } } VOID FindDiskDrives( _In_ PDV_DISK_OPTIONS_CONTEXT Context ) { PPH_LIST deviceList; HDEVINFO deviceInfoHandle; SP_DEVICE_INTERFACE_DATA deviceInterfaceData = { sizeof(SP_DEVICE_INTERFACE_DATA) }; SP_DEVINFO_DATA deviceInfoData = { sizeof(SP_DEVINFO_DATA) }; PSP_DEVICE_INTERFACE_DETAIL_DATA deviceInterfaceDetail; ULONG deviceInfoLength = 0; if ((deviceInfoHandle = SetupDiGetClassDevs( &GUID_DEVINTERFACE_DISK, NULL, NULL, DIGCF_DEVICEINTERFACE )) == INVALID_HANDLE_VALUE) { return; } deviceList = PH_AUTO(PhCreateList(1)); for (ULONG i = 0; SetupDiEnumDeviceInterfaces(deviceInfoHandle, NULL, &GUID_DEVINTERFACE_DISK, i, &deviceInterfaceData); i++) { if (SetupDiGetDeviceInterfaceDetail( deviceInfoHandle, &deviceInterfaceData, 0, 0, &deviceInfoLength, &deviceInfoData ) || GetLastError() != ERROR_INSUFFICIENT_BUFFER) { continue; } deviceInterfaceDetail = PhAllocate(deviceInfoLength); deviceInterfaceDetail->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); if (SetupDiGetDeviceInterfaceDetail( deviceInfoHandle, &deviceInterfaceData, deviceInterfaceDetail, deviceInfoLength, &deviceInfoLength, &deviceInfoData )) { HANDLE deviceHandle; PDISK_ENUM_ENTRY diskEntry; WCHAR diskFriendlyName[MAX_PATH] = L""; // This crashes on XP with error 0xC06D007F //SetupDiGetDeviceProperty( // deviceInfoHandle, // &deviceInfoData, // &DEVPKEY_Device_FriendlyName, // &devicePropertyType, // (PBYTE)diskFriendlyName, // ARRAYSIZE(diskFriendlyName), // NULL, // 0 // ); if (!SetupDiGetDeviceRegistryProperty( deviceInfoHandle, &deviceInfoData, SPDRP_FRIENDLYNAME, NULL, (PBYTE)diskFriendlyName, ARRAYSIZE(diskFriendlyName), NULL )) { continue; } diskEntry = PhAllocate(sizeof(DISK_ENUM_ENTRY)); memset(diskEntry, 0, sizeof(DISK_ENUM_ENTRY)); diskEntry->DeviceIndex = ULONG_MAX; // Note: Do not initialize to zero. diskEntry->DeviceName = PhCreateString(diskFriendlyName); diskEntry->DevicePath = PhCreateString(deviceInterfaceDetail->DevicePath); if (NT_SUCCESS(DiskDriveCreateHandle( &deviceHandle, diskEntry->DevicePath ))) { ULONG diskIndex = ULONG_MAX; // Note: Do not initialize to zero if (NT_SUCCESS(DiskDriveQueryDeviceTypeAndNumber( deviceHandle, &diskIndex, NULL ))) { PPH_STRING diskMountPoints = PH_AUTO_T(PH_STRING, DiskDriveQueryDosMountPoints(diskIndex)); diskEntry->DeviceIndex = diskIndex; diskEntry->DevicePresent = TRUE; if (!PhIsNullOrEmptyString(diskMountPoints)) { diskEntry->DeviceMountPoints = PhFormatString( L"Disk %lu (%s) [%s]", diskIndex, diskMountPoints->Buffer, diskFriendlyName ); } else { diskEntry->DeviceMountPoints = PhFormatString( L"Disk %lu [%s]", diskIndex, diskFriendlyName ); } } NtClose(deviceHandle); } PhAddItemList(deviceList, diskEntry); } PhFree(deviceInterfaceDetail); } SetupDiDestroyDeviceInfoList(deviceInfoHandle); // Sort the entries qsort(deviceList->Items, deviceList->Count, sizeof(PVOID), DiskEntryCompareFunction); Context->EnumeratingDisks = TRUE; PhAcquireQueuedLockShared(&DiskDrivesListLock); for (ULONG i = 0; i < deviceList->Count; i++) { PDISK_ENUM_ENTRY entry = deviceList->Items[i]; AddDiskDriveToListView( Context, entry->DevicePresent, entry->DevicePath, entry->DeviceMountPoints ? entry->DeviceMountPoints : entry->DeviceName ); if (entry->DeviceMountPoints) PhDereferenceObject(entry->DeviceMountPoints); if (entry->DeviceName) PhDereferenceObject(entry->DeviceName); // Note: DevicePath is disposed by WM_DESTROY. PhFree(entry); } PhReleaseQueuedLockShared(&DiskDrivesListLock); Context->EnumeratingDisks = FALSE; // HACK: Show all unknown devices. Context->EnumeratingDisks = TRUE; PhAcquireQueuedLockShared(&DiskDrivesListLock); for (ULONG i = 0; i < DiskDrivesList->Count; i++) { ULONG index = -1; BOOLEAN found = FALSE; PDV_DISK_ENTRY entry = PhReferenceObjectSafe(DiskDrivesList->Items[i]); if (!entry) continue; while ((index = PhFindListViewItemByFlags( Context->ListViewHandle, index, LVNI_ALL )) != -1) { PDV_DISK_ID param; if (PhGetListViewItemParam(Context->ListViewHandle, index, ¶m)) { if (EquivalentDiskId(param, &entry->Id)) { found = TRUE; } } } if (!found) { PPH_STRING description; if (description = PhCreateString(L"Unknown disk")) { AddDiskDriveToListView( Context, FALSE, entry->Id.DevicePath, description ); PhDereferenceObject(description); } } PhDereferenceObjectDeferDelete(entry); } PhReleaseQueuedLockShared(&DiskDrivesListLock); Context->EnumeratingDisks = FALSE; } PPH_STRING FindDiskDeviceInstance( _In_ PPH_STRING DevicePath ) { PPH_STRING deviceIdString = NULL; HDEVINFO deviceInfoHandle; SP_DEVICE_INTERFACE_DATA deviceInterfaceData = { sizeof(SP_DEVICE_INTERFACE_DATA) }; SP_DEVINFO_DATA deviceInfoData = { sizeof(SP_DEVINFO_DATA) }; PSP_DEVICE_INTERFACE_DETAIL_DATA deviceInterfaceDetail; ULONG deviceInfoLength = 0; if ((deviceInfoHandle = SetupDiGetClassDevs( &GUID_DEVINTERFACE_DISK, NULL, NULL, DIGCF_DEVICEINTERFACE )) == INVALID_HANDLE_VALUE) { return NULL; } for (ULONG i = 0; SetupDiEnumDeviceInterfaces(deviceInfoHandle, NULL, &GUID_DEVINTERFACE_DISK, i, &deviceInterfaceData); i++) { if (SetupDiGetDeviceInterfaceDetail( deviceInfoHandle, &deviceInterfaceData, 0, 0, &deviceInfoLength, &deviceInfoData ) || GetLastError() != ERROR_INSUFFICIENT_BUFFER) { continue; } deviceInterfaceDetail = PhAllocate(deviceInfoLength); deviceInterfaceDetail->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); if (SetupDiGetDeviceInterfaceDetail( deviceInfoHandle, &deviceInterfaceData, deviceInterfaceDetail, deviceInfoLength, &deviceInfoLength, &deviceInfoData )) { if (PhEqualStringZ(deviceInterfaceDetail->DevicePath, DevicePath->Buffer, TRUE)) { deviceIdString = PhCreateStringEx(NULL, 0x100); SetupDiGetDeviceInstanceId( deviceInfoHandle, &deviceInfoData, deviceIdString->Buffer, (ULONG)deviceIdString->Length, NULL ); PhTrimToNullTerminatorString(deviceIdString); } } PhFree(deviceInterfaceDetail); } SetupDiDestroyDeviceInfoList(deviceInfoHandle); return deviceIdString; } //VOID LoadDiskDriveImages( // _In_ PDV_DISK_OPTIONS_CONTEXT Context // ) //{ // HICON smallIcon = NULL; // // Context->ImageList = ImageList_Create( // GetSystemMetrics(SM_CXSMICON), // GetSystemMetrics(SM_CYSMICON), // ILC_COLOR32, // 1, // 1 // ); // // // We could use SetupDiLoadClassIcon but this works. // // Copied from HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\Class\{4d36e967-e325-11ce-bfc1-08002be10318}\\IconPath // // The index is only valid on Vista and above. // ExtractIconEx( // L"%SystemRoot%\\system32\\imageres.dll", // -32, // NULL, // &smallIcon, // 1 // ); // // if (smallIcon) // { // ImageList_AddIcon(Context->ImageList, smallIcon); // DestroyIcon(smallIcon); // // // Set the imagelist only if the image was loaded. // ListView_SetImageList( // Context->ListViewHandle, // Context->ImageList, // LVSIL_SMALL // ); // } //} INT_PTR CALLBACK DiskDriveOptionsDlgProc( _In_ HWND hwndDlg, _In_ UINT uMsg, _In_ WPARAM wParam, _In_ LPARAM lParam ) { PDV_DISK_OPTIONS_CONTEXT context = NULL; if (uMsg == WM_INITDIALOG) { context = (PDV_DISK_OPTIONS_CONTEXT)PhAllocate(sizeof(DV_DISK_OPTIONS_CONTEXT)); memset(context, 0, sizeof(DV_DISK_OPTIONS_CONTEXT)); SetProp(hwndDlg, L"Context", (HANDLE)context); } else { context = (PDV_DISK_OPTIONS_CONTEXT)GetProp(hwndDlg, L"Context"); if (uMsg == WM_DESTROY) { if (context->OptionsChanged) DiskDrivesSaveList(); FreeListViewDiskDriveEntries(context); RemoveProp(hwndDlg, L"Context"); PhFree(context); } } if (context == NULL) return FALSE; switch (uMsg) { case WM_INITDIALOG: { // Center the property sheet. PhCenterWindow(GetParent(hwndDlg), GetParent(GetParent(hwndDlg))); // Hide the OK button. ShowWindow(GetDlgItem(GetParent(hwndDlg), IDOK), SW_HIDE); // Set the Cancel button text. Button_SetText(GetDlgItem(GetParent(hwndDlg), IDCANCEL), L"Close"); context->ListViewHandle = GetDlgItem(hwndDlg, IDC_DISKDRIVE_LISTVIEW); PhSetListViewStyle(context->ListViewHandle, FALSE, TRUE); ListView_SetExtendedListViewStyleEx(context->ListViewHandle, LVS_EX_CHECKBOXES, LVS_EX_CHECKBOXES); PhSetControlTheme(context->ListViewHandle, L"explorer"); PhAddListViewColumn(context->ListViewHandle, 0, 0, 0, LVCFMT_LEFT, 350, L"Disk Drives"); PhSetExtendedListView(context->ListViewHandle); if (WindowsVersion >= WINDOWS_VISTA) { ListView_EnableGroupView(context->ListViewHandle, TRUE); AddListViewGroup(context->ListViewHandle, 0, L"Connected"); AddListViewGroup(context->ListViewHandle, 1, L"Disconnected"); } FindDiskDrives(context); context->OptionsChanged = FALSE; EnableThemeDialogTexture(hwndDlg, ETDT_ENABLETAB); } break; case WM_NOTIFY: { LPNMHDR header = (LPNMHDR)lParam; if (header->code == LVN_ITEMCHANGED) { LPNM_LISTVIEW listView = (LPNM_LISTVIEW)lParam; if (context->EnumeratingDisks) break; if (listView->uChanged & LVIF_STATE) { switch (listView->uNewState & LVIS_STATEIMAGEMASK) { case 0x2000: // checked { PDV_DISK_ID param = (PDV_DISK_ID)listView->lParam; if (!FindDiskEntry(param, FALSE)) { PDV_DISK_ENTRY entry; entry = CreateDiskEntry(param); entry->UserReference = TRUE; } context->OptionsChanged = TRUE; } break; case 0x1000: // unchecked { PDV_DISK_ID param = (PDV_DISK_ID)listView->lParam; FindDiskEntry(param, TRUE); context->OptionsChanged = TRUE; } break; } } } else if (header->code == NM_RCLICK) { PDV_DISK_ID param; PPH_STRING deviceInstance; if (param = PhGetSelectedListViewItemParam(context->ListViewHandle)) { if (deviceInstance = FindDiskDeviceInstance(param->DevicePath)) { ShowDeviceMenu(hwndDlg, deviceInstance); PhDereferenceObject(deviceInstance); } } } } break; } return FALSE; }