From aa0bf0ee2b7ea5e0f97246cf4ab1822157a4f405 Mon Sep 17 00:00:00 2001 From: Pete Batard Date: Thu, 14 Nov 2013 01:21:50 +0000 Subject: [PATCH] [core] add HDD vs UFD detection * Initial scoring to try to differentiate UFDs from HDDs (#219) * Also improve GetDriveLetter() and add a global for fixed vs removable * Also fix a bug with reporting of VID:PID with multiple devices * Also fix a warning in localization --- src/drive.c | 38 +++++++-------- src/format.c | 6 ++- src/localization.c | 2 +- src/rufus.c | 29 ++++++++---- src/rufus.h | 5 +- src/rufus.rc | 10 ++-- src/smart.c | 113 +++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 165 insertions(+), 38 deletions(-) diff --git a/src/drive.c b/src/drive.c index 8967ed42..7f193e0e 100644 --- a/src/drive.c +++ b/src/drive.c @@ -36,7 +36,7 @@ * Globals */ RUFUS_DRIVE_INFO SelectedDrive; -extern BOOL enable_fixed_disks; +extern UINT drive_type; // TODO: add a DetectSectorSize()? // http://msdn.microsoft.com/en-us/library/ff800831.aspx @@ -136,11 +136,11 @@ char* GetLogicalName(DWORD DriveIndex, BOOL bKeepTrailingBackslash, BOOL bSilent char path[MAX_PATH]; VOLUME_DISK_EXTENTS DiskExtents; DWORD size; - UINT drive_type; int i, j; static const char* ignore_device[] = { "\\Device\\CdRom", "\\Device\\Floppy" }; static const char* volume_start = "\\\\?\\"; + drive_type = DRIVE_UNKNOWN; CheckDriveIndex(DriveIndex); for (i=0; hDrive == INVALID_HANDLE_VALUE; i++) { @@ -167,9 +167,7 @@ char* GetLogicalName(DWORD DriveIndex, BOOL bKeepTrailingBackslash, BOOL bSilent } drive_type = GetDriveTypeA(volume_name); - // NB: the HP utility allows drive_type == DRIVE_FIXED, which we don't allow by default - // Using Alt-F in Rufus does enable listing, but this mode is unsupported. - if ((drive_type != DRIVE_REMOVABLE) && ((!enable_fixed_disks) || (drive_type != DRIVE_FIXED))) + if ((drive_type != DRIVE_REMOVABLE) && (drive_type != DRIVE_FIXED)) continue; volume_name[len-1] = 0; @@ -257,16 +255,17 @@ HANDLE GetLogicalHandle(DWORD DriveIndex, BOOL bWriteAccess, BOOL bLockDrive) /* * Returns the first drive letter for a volume located on the drive identified by DriveIndex */ -char GetDriveLetter(DWORD DriveIndex) +BOOL GetDriveLetter(DWORD DriveIndex, char* drive_letter) { DWORD size; - BOOL r; + BOOL r = FALSE; STORAGE_DEVICE_NUMBER_REDEF device_number = {0}; - UINT drive_type; HANDLE hDrive = INVALID_HANDLE_VALUE; char *drive, drives[26*4]; /* "D:\", "E:\", etc. */ char logical_drive[] = "\\\\.\\#:"; - char drive_letter = ' '; + + drive_type = DRIVE_UNKNOWN; + *drive_letter = ' '; CheckDriveIndex(DriveIndex); size = GetLogicalDriveStringsA(sizeof(drives), drives); @@ -279,6 +278,7 @@ char GetDriveLetter(DWORD DriveIndex) goto out; } + r = TRUE; for (drive = drives ;*drive; drive += safe_strlen(drive)+1) { if (!isalpha(*drive)) continue; @@ -292,9 +292,8 @@ char GetDriveLetter(DWORD DriveIndex) value there => Use GetDriveType() to filter out unwanted devices. See https://github.com/pbatard/rufus/issues/32 for details. */ drive_type = GetDriveTypeA(drive); - // NB: the HP utility allows drive_type == DRIVE_FIXED, which we don't allow by default - // Using Alt-F in Rufus does enable listing, but this mode is unsupported. - if ((drive_type != DRIVE_REMOVABLE) && ((!enable_fixed_disks) || (drive_type != DRIVE_FIXED))) + + if ((drive_type != DRIVE_REMOVABLE) && (drive_type != DRIVE_FIXED)) continue; safe_sprintf(logical_drive, sizeof(logical_drive), "\\\\.\\%c:", drive[0]); @@ -305,19 +304,19 @@ char GetDriveLetter(DWORD DriveIndex) } r = DeviceIoControl(hDrive, IOCTL_STORAGE_GET_DEVICE_NUMBER, NULL, - 0, &device_number, sizeof(device_number), &size, NULL); + 0, &device_number, sizeof(device_number), &size, NULL) && (size > 0); safe_closehandle(hDrive); - if ((!r) || (size <= 0)) { + if (!r) { uprintf("Could not get device number for device %s: %s\n", logical_drive, WindowsErrorString()); } else if (device_number.DeviceNumber == DriveIndex) { - drive_letter = *drive; + *drive_letter = *drive; break; } } out: - return drive_letter; + return r; } /* @@ -368,10 +367,11 @@ BOOL GetDriveLabel(DWORD DriveIndex, char* letter, char** label) *label = STR_NO_LABEL; - *letter = GetDriveLetter(DriveIndex); + if (!GetDriveLetter(DriveIndex, letter)) + return FALSE; if (*letter == ' ') { - // Drive without volume assigned - Tie to the display of fixed disks - return enable_fixed_disks; + // Drive without volume assigned - always enabled + return TRUE; } AutorunPath[0] = *letter; wDrivePath[0] = *letter; diff --git a/src/format.c b/src/format.c index 20312cd0..b9873c9e 100644 --- a/src/format.c +++ b/src/format.c @@ -1195,7 +1195,11 @@ DWORD WINAPI FormatThread(LPVOID param) } // At this stage with have both a handle and a lock to the physical drive... - drive_name[0] = GetDriveLetter(DriveIndex); + if (!GetDriveLetter(DriveIndex, &drive_name[0])) { + uprintf("Failed to get a drive letter\n"); + FormatStatus = ERROR_SEVERITY_ERROR|FAC(FACILITY_STORAGE)|APPERR(ERROR_CANT_ASSIGN_LETTER); + goto out; + } if (drive_name[0] == ' ') { uprintf("No drive letter was assigned...\n"); drive_name[0] = GetUnusedDriveLetter(); diff --git a/src/localization.c b/src/localization.c index a7634d40..80b88f26 100644 --- a/src/localization.c +++ b/src/localization.c @@ -199,7 +199,7 @@ static uint32_t htab_hash(char* str) do { // Because size is prime this guarantees to step through all available indexes if (idx <= hval2) { - idx = htab_size + idx - hval2; + idx = ((uint32_t)htab_size) + idx - hval2; } else { idx -= hval2; } diff --git a/src/rufus.c b/src/rufus.c index 7b7da40c..7055a64f 100644 --- a/src/rufus.c +++ b/src/rufus.c @@ -117,7 +117,8 @@ HWND hDeviceList, hPartitionScheme, hFileSystem, hClusterSize, hLabel, hBootType HWND hISOProgressDlg = NULL, hLogDlg = NULL, hISOProgressBar, hISOFileName, hDiskID; BOOL use_own_c32[NB_OLD_C32] = {FALSE, FALSE}, detect_fakes = TRUE, mbr_selected_by_user = FALSE; BOOL iso_op_in_progress = FALSE, format_op_in_progress = FALSE; -BOOL enable_fixed_disks = FALSE, advanced_mode = TRUE, force_update = FALSE; +BOOL enable_HDDs = FALSE, advanced_mode = TRUE, force_update = FALSE; +UINT drive_type = DRIVE_UNKNOWN; int dialog_showing = 0; uint16_t rufus_version[4]; RUFUS_UPDATE update = { {0,0,0,0}, {0,0}, NULL, NULL}; @@ -602,6 +603,7 @@ static BOOL GetUSBDevices(DWORD devnum) HANDLE hDrive; LONG maxwidth = 0; RECT rect; + int score; char drive_letter, *devid, *devid_list = NULL; char *label, *entry, buffer[MAX_PATH], str[sizeof("0000:0000")+1]; const char* usbstor_name = "USBSTOR"; @@ -653,7 +655,7 @@ static BOOL GetUSBDevices(DWORD devnum) } else { // Get the VID:PID of the device. We could avoid doing this lookup every time by keeping // a lookup table, but there shouldn't be that many USB storage devices connected... - for (devid = devid_list; *devid; devid += strlen(devid_list) + 1) { + for (devid = devid_list; *devid; devid += strlen(devid) + 1) { if ( (CM_Locate_DevNodeA(&parent_inst, devid, 0) == 0) && (CM_Get_Child(&device_inst, parent_inst, 0) == 0) && (device_inst == dev_info_data.DevInst) ) { @@ -684,7 +686,7 @@ static BOOL GetUSBDevices(DWORD devnum) if(GetLastError() != ERROR_NO_MORE_ITEMS) { uprintf("SetupDiEnumDeviceInterfaces failed: %s\n", WindowsErrorString()); } else { - uprintf("A device was eliminated because it didn't report itself as a non fixed USB disk\n"); + uprintf("A device was eliminated because it didn't report itself as a disk\n"); } break; } @@ -730,12 +732,19 @@ static BOOL GetUSBDevices(DWORD devnum) continue; } -// Identify(hDrive); - if (GetDriveLabel(device_number.DeviceNumber + DRIVE_INDEX_MIN, &drive_letter, &label)) { // Must ensure that the combo box is UNSORTED for indexes to be the same StrArrayAdd(&DriveID, buffer); StrArrayAdd(&DriveLabel, label); + + if ((!enable_HDDs) && ((score = IsHDD(drive_type, vid, pid, buffer)) > IS_HDD_THRESHOLD)) { + uprintf("USB HDD device removed (score %d > %d) " + "[Note: You can enable USB HDDs in the Advanced Options]\n", score, IS_HDD_THRESHOLD); + safe_closehandle(hDrive); + safe_free(devint_detail_data); + break; + } + // Drive letter ' ' is returned for drives that don't have a volume assigned yet if (drive_letter == ' ') { entry = lmprintf(MSG_046, label, device_number.DeviceNumber); @@ -1809,8 +1818,8 @@ static INT_PTR CALLBACK MainCallback(HWND hDlg, UINT message, WPARAM wParam, LPA break; case IDC_ENABLE_FIXED_DISKS: if ((HIWORD(wParam)) == BN_CLICKED) { - enable_fixed_disks = !enable_fixed_disks; - PrintStatus2000(lmprintf(MSG_253), enable_fixed_disks); + enable_HDDs = !enable_HDDs; + PrintStatus2000(lmprintf(MSG_253), enable_HDDs); GetUSBDevices(0); } break; @@ -2030,7 +2039,7 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine while ((opt = getopt_long(argc, argv, "?fhi:w:l:", long_options, &option_index)) != EOF) switch (opt) { case 'f': - enable_fixed_disks = TRUE; + enable_HDDs = TRUE; break; case 'i': if (_access(optarg, 0) != -1) { @@ -2166,8 +2175,8 @@ relaunch: // This is a safety feature, to avoid someone unintentionally formatting a backup // drive instead of an USB key. If this is enabled, Rufus will allow fixed disk formatting. if ((msg.message == WM_SYSKEYDOWN) && (msg.wParam == 'F')) { - enable_fixed_disks = !enable_fixed_disks; - PrintStatus2000(lmprintf(MSG_253), enable_fixed_disks); + enable_HDDs = !enable_HDDs; + PrintStatus2000(lmprintf(MSG_253), enable_HDDs); GetUSBDevices(0); continue; } diff --git a/src/rufus.h b/src/rufus.h index a9a49600..19dee111 100644 --- a/src/rufus.h +++ b/src/rufus.h @@ -56,6 +56,7 @@ #define UDF_FORMAT_SPEED 3.1f // Speed estimate at which we expect UDF drives to be formatted (GB/s) #define UDF_FORMAT_WARN 20 // Duration (in seconds) above which we warn about long UDF formatting times #define MAX_FAT32_SIZE 2.0f // Threshold above which we disable FAT32 formatting (in TB) +#define IS_HDD_THRESHOLD 5 #define WHITE RGB(255,255,255) #define SEPARATOR_GREY RGB(223,223,223) #define RUFUS_URL "http://rufus.akeo.ie" @@ -319,7 +320,7 @@ extern HANDLE GetPhysicalHandle(DWORD DriveIndex, BOOL bWriteAccess, BOOL bLockD extern BOOL WaitForLogical(DWORD DriveIndex); extern char* GetLogicalName(DWORD DriveIndex, BOOL bKeepTrailingBackslash, BOOL bSilent); extern HANDLE GetLogicalHandle(DWORD DriveIndex, BOOL bWriteAccess, BOOL bLockDrive); -extern char GetDriveLetter(DWORD DriveIndex); +extern BOOL GetDriveLetter(DWORD DriveIndex, char* drive_letter); extern char GetUnusedDriveLetter(void); extern BOOL CreatePartition(HANDLE hDrive, int partition_style, int file_system, BOOL mbr_uefi_marker); extern BOOL DeletePartitions(HANDLE hDrive); @@ -350,7 +351,7 @@ extern char* replace_in_token_data(const char* filename, const char* token, cons extern void parse_update(char* buf, size_t len); extern BOOL WimExtractCheck(void); extern BOOL WimExtractFile(const char* wim_image, int index, const char* src, const char* dst); -extern BOOL Identify(HANDLE hPhysical); +extern int IsHDD(UINT drive_type, uint16_t vid, uint16_t pid, const char* strid); static __inline BOOL UnlockDrive(HANDLE hDrive) { diff --git a/src/rufus.rc b/src/rufus.rc index 70cc4500..9baf7778 100644 --- a/src/rufus.rc +++ b/src/rufus.rc @@ -33,7 +33,7 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL IDD_DIALOG DIALOGEX 12, 12, 206, 329 STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_APPWINDOW -CAPTION "Rufus v1.4.0.315" +CAPTION "Rufus v1.4.0.316" FONT 8, "MS Shell Dlg", 400, 0, 0x1 BEGIN DEFPUSHBUTTON "Start",IDC_START,94,291,50,14 @@ -289,8 +289,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 1,4,0,315 - PRODUCTVERSION 1,4,0,315 + FILEVERSION 1,4,0,316 + PRODUCTVERSION 1,4,0,316 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -307,13 +307,13 @@ BEGIN BEGIN VALUE "CompanyName", "Akeo Consulting (http://akeo.ie)" VALUE "FileDescription", "Rufus" - VALUE "FileVersion", "1.4.0.315" + VALUE "FileVersion", "1.4.0.316" VALUE "InternalName", "Rufus" VALUE "LegalCopyright", "© 2011-2013 Pete Batard (GPL v3)" VALUE "LegalTrademarks", "http://www.gnu.org/copyleft/gpl.html" VALUE "OriginalFilename", "rufus.exe" VALUE "ProductName", "Rufus" - VALUE "ProductVersion", "1.4.0.315" + VALUE "ProductVersion", "1.4.0.316" END END BLOCK "VarFileInfo" diff --git a/src/smart.c b/src/smart.c index 7fa7f471..417af4ae 100644 --- a/src/smart.c +++ b/src/smart.c @@ -397,4 +397,117 @@ BOOL SmartGetVersion(HANDLE hdevice) * - if IDENTIFY reports SMART capabilities * - if it has extra non hidden partitions that aren't Windows * - if the VID:PID (or VID) is of known USB to IDE/SATA bridge or known UFD maker + * - removable flag (how do you actually find that one?) */ + +typedef struct { + const char* name; + const int score; +} str_score; + +typedef struct { + const uint16_t vid; + const int score; +} vid_score; + +// If a disk ID starts with these, we consider it likely to be an HDD +// The info from http://knowledge.seagate.com/articles/en_US/FAQ/204763en is a start, but not +// entirely accurate for our usage as some models will be prefixed with the manufacturer name +// '#' below means any number in [0-9] +static str_score manufacturer_str[] = { + { "HP ", 10 }, + { "ST#", 10 }, + { "MX#", 10 }, + { "WDC", 10 }, + { "IBM", 10 }, + { "STM#", 10 }, + { "HTS#", 10 }, + { "MAXTOR", 10 }, + { "HITACHI", 10 }, + { "SEAGATE", 10 }, + { "SAMSUNG", 10 }, + { "FUJITSU", 10 }, + { "TOSHIBA", 10 }, + { "QUANTUM", 10 }, +}; + +// http://www.linux-usb.org/usb.ids +static vid_score manufacturer_vid[] = { + { 0x04b4, 10 }, // Cypress + { 0x067b, 10 }, // Prolific + { 0x0bc2, 10 }, // Seagate + { 0x152d, 10 }, // JMicron +}; + +/* + * This attempts to detect whether a drive is an USB HDD or an USB Flash Drive (UFD). + * If someone already has an USB HDD plugged in (say as a backup drive) and plugs an + * UFD we *try* to do what we can to avoid them formatting that drive by mistake. + * But because there is no foolproof (let alone easy), way to differentiate UFDs from + * HDDs, thanks to every manufacturer, Microsoft, and their mothers making it + * exceedingly troublesome to find out what type of hardware we are actually accessing + * please pay heed to the following warning: + * + * WARNING: NO PROMISE IS MADE ABOUT THIS ALGORITHM BEING ABLE TO CORRECTLY + * DIFFERENTIATE AN USB HDD FROM A FLASH DRIVE. ALSO, REMEMBER THAT THE LICENSE OF THIS + * APPLICATION MAKES ABSOLUETLY NO PROMISE ABOUT DATA PRESERVATION (PROVIDED "AS IS"). + * THUS, IF DATA LOSS IS INCURRED DUE TO THE ALGORITHM BELOW, OR ANY OTHER PART OF THIS + * APPLICATION, THE RESPONSIBILITY IS ENTIRELY ON YOU! + * + * But let me just elaborate further on why differentiating UFDs from HDDs is not as + * 'simple' as it seems: + * - many USB flash drives manufacturer will present UFDs as non-removable, which used + * to be reserved for HDDs => we can't use that as differentiator. + * - some UFDs (SanDisk Extreme) have added S.M.A.R.T. support, which also used to be + * reserved for HDDs => can't use that either + * - even if S.M.A.R.T. was enough, not all USB->IDE or USB->SATA bridges support ATA + * passthrough, which is required S.M.A.R.T. data, and each manufacturer of an + * USB<->(S)ATA bridge seem to have their own method of implementing passthrough. + * - SSDs have also changed the deal completely, as you can get something that looks + * like Flash but that is really an HDD. + * - Some manufacturers (eg. ALI) provide both USB Flash controllers and USB IDE/SATA + * controllers, so we can't exactly use the VID to say for sure what we're looking at. + * - Finally, Microsoft is abdsolutely no help either (which is kind of understandable + * from the above) => there is no magic API we can query that will tell us what we're + * really looking at. + * + * What you have below, then, is our *current best guess* at differentiating an UFD from + * an HDD. Short of a crystal ball however, this remains just a guess, which may be way + * off mark. Still, Rufus does produce PROMINENT warnings before you format a drive, and + * also provides extensive info about the drive (from the toolips and the log) => PAY + * ATTENTION TO THESE OR PAY THE PRICE! + */ +int IsHDD(UINT drive_type, uint16_t vid, uint16_t pid, const char* strid) +{ + int score = 0; + size_t i, mlen, ilen; + BOOL wc; + + if (drive_type == DRIVE_FIXED) + score += 3; + + ilen = safe_strlen(strid); + for (i=0; i ilen) + break; + wc = (manufacturer_str[i].name[mlen-1] == '#'); + if ( (_strnicmp(strid, manufacturer_str[i].name, mlen-((wc)?1:0)) == 0) + && ((!wc) || ((strid[mlen] >= '0') && (strid[mlen] <= '9'))) ) { + score += manufacturer_str[i].score; + break; + } + } + + for (i=0; i