[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
This commit is contained in:
Pete Batard 2013-11-14 01:21:50 +00:00
parent 803a4bff1c
commit aa0bf0ee2b
7 changed files with 165 additions and 38 deletions

View File

@ -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;

View File

@ -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();

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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)
{

View File

@ -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"

View File

@ -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<ARRAYSIZE(manufacturer_str); i++) {
mlen = strlen(manufacturer_str[i].name);
if (mlen > 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<ARRAYSIZE(manufacturer_vid); i++) {
if (vid == manufacturer_vid[i].vid) {
score += manufacturer_vid[i].score;
break;
}
}
// TODO: try to perform inquiry if uncertain
// TODO: lower the score for well known UFD manufacturers (ADATA, SanDisk, etc.)
return score;
}