最近碰到了個新問題,記錄下來作爲windows的磁盤操作那個系列的續篇吧。
一些時候我們的程序需要區分本地存儲設備和USB存儲設備。在網上搜一搜一般會找到一個最直接的API,GetDriveType,其原型爲
UINT GetDriveType(LPCTSTR lpRootPathName)
參數lpRootPathName是存儲設備的根目錄,例如C:\,返回值即爲設備類型。
Return code
|
Description
|
DRIVE_REMOVABLE
|
The drive has removable media; for example, a floppy drive, thumb drive, or flash card reader.
|
DRIVE_FIXED
|
The drive has fixed media; for example, a hard drive or flash drive.
|
或者採用一種稍微複雜一點的方法,使用我們第一節http://cutebunny.blog.51cto.com/301216/624027中介紹的GetDriveGeometry()函數,其輸出參數DISK_GEOMETRY *pdg中的MediaType字段代表設備類型。
typedef enum _MEDIA_TYPE
{
…
RemovableMedia
FixedMedia
…
}MEDIA_TYPE;
這兩個方法看似能方便快捷的解決我們的需求,但事實上當你使用GetDriveType()去獲取一塊移動硬盤的類型時,程序會坑爹的告訴你這塊移動硬盤的類型是DRIVE_FIXED,根本無法與本地磁盤區分開來。GetDriveGeometry()函數的結果也是如此。
事實上,上述方法只對小容量的U盤有效,會返回給你DRIVE_REMOVABLE的結果;而對移動硬盤甚至是一塊稍大容量的U盤(比如我有一塊格式化爲FAT32格式的4G U盤),就無能爲力了。
所以,我們必須採用別的思路了,這裏我介紹一種通過查看總線類型來區分本地磁盤和USB磁盤的方法。當然,其基礎還是我們那萬能的DeviceIoControl,不過這次的控制碼爲IOCTL_STORAGE_QUERY_PROPERTY。同時對應的輸入參數爲STORAGE_PROPERTY_QUERY結構,輸出參數爲STORAGE_DEVICE_DESCRIPTOR結構體。
typedef struct _STORAGE_PROPERTY_QUERY {
STORAGE_PROPERTY_ID PropertyId;
STORAGE_QUERY_TYPE QueryType;
UCHAR AdditionalParameters[1];
} STORAGE_PROPERTY_QUERY, *PSTORAGE_PROPERTY_QUERY;
STORAGE_PROPERTY_ID PropertyId;
STORAGE_QUERY_TYPE QueryType;
UCHAR AdditionalParameters[1];
} STORAGE_PROPERTY_QUERY, *PSTORAGE_PROPERTY_QUERY;
調用時需設置輸入參數中的字段
PropertyId = StorageDeviceProperty;
QueryType = PropertyStandardQuery;
以表明我們要查詢一個device descriptor,也就是說,只有指定這種類型,輸出參數纔會得到STORAGE_DEVICE_DESCRIPTOR類型數據。
typedef struct _STORAGE_DEVICE_DESCRIPTOR {
ULONG Version;
ULONG Size;
UCHAR DeviceType;
UCHAR DeviceTypeModifier;
BOOLEAN RemovableMedia;
BOOLEAN CommandQueueing;
ULONG VendorIdOffset;
ULONG ProductIdOffset;
ULONG ProductRevisionOffset;
ULONG SerialNumberOffset;
STORAGE_BUS_TYPE BusType;
ULONG RawPropertiesLength;
UCHAR RawDeviceProperties[1];
} STORAGE_DEVICE_DESCRIPTOR, *PSTORAGE_DEVICE_DESCRIPTOR;
ULONG Version;
ULONG Size;
UCHAR DeviceType;
UCHAR DeviceTypeModifier;
BOOLEAN RemovableMedia;
BOOLEAN CommandQueueing;
ULONG VendorIdOffset;
ULONG ProductIdOffset;
ULONG ProductRevisionOffset;
ULONG SerialNumberOffset;
STORAGE_BUS_TYPE BusType;
ULONG RawPropertiesLength;
UCHAR RawDeviceProperties[1];
} STORAGE_DEVICE_DESCRIPTOR, *PSTORAGE_DEVICE_DESCRIPTOR;
其中,BusType定義爲
typedef enum _STORAGE_BUS_TYPE {
BusTypeUnknown = 0x00,
BusTypeScsi,
BusTypeAtapi,
BusTypeAta,
BusType1394,
BusTypeSsa,
BusTypeFibre,
BusTypeUsb,
BusTypeRAID,
BusTypeiScsi,
BusTypeSas,
BusTypeSata,
BusTypeSd,
BusTypeMmc,
BusTypeMax,
BusTypeMaxReserved = 0x7F
} STORAGE_BUS_TYPE, *PSTORAGE_BUS_TYPE;
BusTypeUnknown = 0x00,
BusTypeScsi,
BusTypeAtapi,
BusTypeAta,
BusType1394,
BusTypeSsa,
BusTypeFibre,
BusTypeUsb,
BusTypeRAID,
BusTypeiScsi,
BusTypeSas,
BusTypeSata,
BusTypeSd,
BusTypeMmc,
BusTypeMax,
BusTypeMaxReserved = 0x7F
} STORAGE_BUS_TYPE, *PSTORAGE_BUS_TYPE;
明白了吧,如果總線類型爲BusTypeUsb,就是找到了我們的USB移動硬盤了。
但此時還需要解決一個問題,STORAGE_DEVICE_DESCRIPTOR可以理解爲一個變長緩衝區,最後一個字段RawDeviceProperties[1]是可以動態擴展的(windows API經常有這種情況),那麼函數DeviceIoControl()中的參數nOutBufferSize應該填多少呢?這時我們需要藉助另一個數據結構STORAGE_DESCRIPTOR_HEADER,在我們不知道device descriptor實際需要多大的緩衝區時,可以先把STORAGE_DESCRIPTOR_HEADER作爲輸出參數以獲得device descriptor的緩衝區大小,其大小被存入header的size字段中。
typedef struct _STORAGE_DESCRIPTOR_HEADER {
ULONG Version;
ULONG Size;
} STORAGE_DESCRIPTOR_HEADER, *PSTORAGE_DESCRIPTOR_HEADER;
ULONG Version;
ULONG Size;
} STORAGE_DESCRIPTOR_HEADER, *PSTORAGE_DESCRIPTOR_HEADER;
以下是具體代碼
/******************************************************************************
* Function: get the bus type of an disk
* input: drive name (c:)
* output: bus type
* return: Succeed, 0
* Fail, -1
******************************************************************************/
DWORD GetDriveTypeByBus(const CHAR *drive, WORD *type)
{
HANDLE hDevice; // handle to the drive to be examined
BOOL result; // results flag
DWORD readed; // discard results
STORAGE_DESCRIPTOR_HEADER *pDevDescHeader;
STORAGE_DEVICE_DESCRIPTOR *pDevDesc;
DWORD devDescLength;
STORAGE_PROPERTY_QUERY query;
hDevice = CreateFile(
drive, // drive to open
GENERIC_READ | GENERIC_WRITE, // access to the drive
FILE_SHARE_READ | FILE_SHARE_WRITE, //share mode
NULL, // default security attributes
OPEN_EXISTING, // disposition
0, // file attributes
NULL // do not copy file attribute
);
if (hDevice == INVALID_HANDLE_VALUE) // cannot open the drive
{
fprintf(stderr, "CreateFile() Error: %ld\n", GetLastError());
return DWORD(-1);
}
query.PropertyId = StorageDeviceProperty;
query.QueryType = PropertyStandardQuery;
pDevDescHeader = (STORAGE_DESCRIPTOR_HEADER *)malloc(sizeof(STORAGE_DESCRIPTOR_HEADER));
if (NULL == pDevDescHeader)
{
return (DWORD)-1;
}
result = DeviceIoControl(
hDevice, // device to be queried
IOCTL_STORAGE_QUERY_PROPERTY, // operation to perform
&query,
sizeof query, // no input buffer
pDevDescHeader,
sizeof(STORAGE_DESCRIPTOR_HEADER), // output buffer
&readed, // # bytes returned
NULL); // synchronous I/O
if (!result) //fail
{
fprintf(stderr, "IOCTL_STORAGE_QUERY_PROPERTY Error: %ld\n", GetLastError());
free(pDevDescHeader);
(void)CloseHandle(hDevice);
return DWORD(-1);
}
devDescLength = pDevDescHeader->Size;
pDevDesc = (STORAGE_DEVICE_DESCRIPTOR *)malloc(devDescLength);
if (NULL == pDevDesc)
{
free(pDevDescHeader);
return (DWORD)-1;
}
result = DeviceIoControl(
hDevice, // device to be queried
IOCTL_STORAGE_QUERY_PROPERTY, // operation to perform
&query,
sizeof query, // no input buffer
pDevDesc,
devDescLength, // output buffer
&readed, // # bytes returned
NULL); // synchronous I/O
if (!result) //fail
{
fprintf(stderr, "IOCTL_STORAGE_QUERY_PROPERTY Error: %ld\n", GetLastError());
free(pDevDescHeader);
free(pDevDesc);
(void)CloseHandle(hDevice);
return DWORD(-1);
}
//printf("%d\n", pDevDesc->BusType);
*type = (WORD)pDevDesc->BusType;
free(pDevDescHeader);
free(pDevDesc);
(void)CloseHandle(hDevice);
return 0;
}
代碼說明:
1. 調用CreateFile打開並獲得設備句柄。
2. 在輸入參數STORAGE_PROPERTY_QUERY query中指定查詢類型。
3. 以STORAGE_DESCRIPTOR_HEADER *pDevDescHeader爲輸出參數,調用操作碼爲IOCTL_STORAGE_QUERY_PROPERTY的DeviceIoControl函數獲得輸出緩衝區大小。
4. 按3中獲得的緩衝區大小爲STORAGE_DEVICE_DESCRIPTOR *pDevDesc分配空間,以pDevDesc爲輸出參數,調用操作碼爲IOCTL_STORAGE_QUERY_PROPERTY的DeviceIoControl函數獲得device descriptor。
5. 從device descriptor中獲得BusType。