Получение серийного номера и типа шины диска
Для получения серийного номера и типа шины я использую IOCTL_STORAGE_QUERY_PROPERTY. Однако STORAGE_DEVICE_DESCRIPTOR не содержит верной информации. На счёт серийного номера, как я понимаю, дела обстоят так: его в принципе может не быть на каких то дисках, а один и тот же набор байт можно по-разному интерпретировать (есть ли универсальный способ, если так?). Поле BusType так же показывает тип шины RAID, хотя на самом деле у меня NVM Express. Как получить верную информацию? Решение должно работать под Windows XP.
Ответы (1 шт):
Не знаю насчет универсального способа, но для любого жесткого диска, поддерживающего набор команд ATA-2, должна работать команда IDENTIFY DEVICE, которая возвращает серийный номер. Разная интерпретация байт тут невозможна, так как там просто ANSI-символы по сути. Хотя конечно, команда возвращает только то, что зашито в устройство. Там может быть просто мусор вместо уникального номера, как со знаменитыми "to be filled by O.E.M." в таблице SMBIOS. По некорректным результатам для одного устройства нельзя сделать вывод, что способ нерабочий. Нужно иметь несколько разных устройств и с ними экспериментировать.
Вот пример кода для получения серийного номера (требуются права администратора):
#include <windows.h>
#include <stdio.h>
typedef struct
{
WORD wGenConfig;
WORD wNumCyls;
WORD wReserved;
WORD wNumHeads;
WORD wBytesPerTrack;
WORD wBytesPerSector;
WORD wSectorsPerTrack;
WORD wVendorUnique[3];
BYTE sSerialNumber[20];
WORD wBufferType;
WORD wBufferSize;
WORD wECCSize;
BYTE sFirmwareRev[8];
BYTE sModelNumber[39];
WORD wMoreVendorUnique;
WORD wDoubleWordIO;
WORD wCapabilities;
WORD wReserved1;
WORD wPIOTiming;
WORD wDMATiming;
WORD wBS;
WORD wNumCurrentCycles;
WORD wNumCurrentHeads;
WORD wNumCurrentSectorsPerTrack;
WORD ulCurrentSectorCapacity;
WORD wMultiSectorStuff;
DWORD ulTotalAddressableSectors;
WORD wSingleWordDMA;
WORD wMultiWordDMA;
BYTE bReserved[127];
}ST_IDSECTOR; //ATA 7.17.7.1 - IDENTIFY DEVICE data
typedef struct
{
BYTE m_ucAttribIndex;
DWORD m_dwAttribValue;
BYTE m_ucValue;
BYTE m_ucWorst;
DWORD m_dwThreshold;
}ST_SMART_INFO;
typedef struct
{
GETVERSIONINPARAMS m_stGVIP;
ST_IDSECTOR m_stInfo;
ST_SMART_INFO m_stSmartInfo[256];
BYTE m_ucSmartValues;
BYTE m_ucDriveIndex;
char m_csErrorString[1000];
}ST_DRIVE_INFO;
#define DRIVE_HEAD_REG 0xA0
#define OUT_BUFFER_SIZE IDENTIFY_BUFFER_SIZE+16
void SwapBytes(char* p, size_t len) {
if (len % 2 != 0)len--;
char t;
for (int i = 0; i < len - 1; i += 2) {
t = p[i];
p[i] = p[i + 1];
p[i + 1] = t;
}
}
BOOL CollectDriveInfo(HANDLE hDevice, UCHAR ucDriveIndex, ST_IDSECTOR* pInfo)
{
BOOL bRet = FALSE;
SENDCMDINPARAMS stCIP = { 0 };
DWORD dwRet = 0;
char szOutput[OUT_BUFFER_SIZE] = { 0 };
stCIP.cBufferSize = IDENTIFY_BUFFER_SIZE;
stCIP.bDriveNumber = ucDriveIndex;
stCIP.irDriveRegs.bFeaturesReg = 0;
stCIP.irDriveRegs.bSectorCountReg = 1;
stCIP.irDriveRegs.bSectorNumberReg = 1;
stCIP.irDriveRegs.bCylLowReg = 0;
stCIP.irDriveRegs.bCylHighReg = 0;
stCIP.irDriveRegs.bDriveHeadReg = DRIVE_HEAD_REG;
stCIP.irDriveRegs.bCommandReg = ID_CMD; //ATA 7.17 - IDENTIFY DEVICE
bRet = DeviceIoControl(hDevice, SMART_RCV_DRIVE_DATA, &stCIP, sizeof(stCIP), szOutput, OUT_BUFFER_SIZE, &dwRet, NULL);
if (bRet)
{
CopyMemory(pInfo, szOutput + 16, sizeof(ST_IDSECTOR));
}
else
dwRet = GetLastError();
return bRet;
}
BOOL GetDriveInfo(BYTE ucDriveIndex, ST_DRIVE_INFO* pInfo)
{
HANDLE hDevice = NULL;
char szT1[MAX_PATH] = { 0 };
BOOL bRet = FALSE;
DWORD dwRet = 0;
sprintf_s(szT1, sizeof(szT1), "\\\\.\\PHYSICALDRIVE%d", ucDriveIndex);
hDevice = CreateFileA(szT1, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM, NULL);
if (hDevice != INVALID_HANDLE_VALUE)
{
bRet = DeviceIoControl(hDevice, SMART_GET_VERSION, NULL, 0, &pInfo->m_stGVIP, sizeof(GETVERSIONINPARAMS), &dwRet, NULL);
if (bRet)
{
if ((pInfo->m_stGVIP.fCapabilities & CAP_ATA_ID_CMD) == CAP_ATA_ID_CMD)
{
bRet = CollectDriveInfo(hDevice, ucDriveIndex,&pInfo->m_stInfo);
}
else printf("ATA IDENTIFY DEVICE command is not supported");
}
else printf("S.M.A.R.T error");
CloseHandle(hDevice);
}
else printf("CreateFile error %d\n",GetLastError());
return bRet;
}
int main()
{
ST_DRIVE_INFO info;
BOOL res = GetDriveInfo(0,&info);
if (res == FALSE) {
printf("GetDriveInfo Error\n");
getchar();
return 1;
}
char sbuf[50] = "";
char* pch = NULL;
ZeroMemory(sbuf, sizeof(sbuf));
strncpy_s(sbuf, sizeof(sbuf), (char*)info.m_stInfo.sSerialNumber, 20);
SwapBytes(sbuf, 20);
pch = &(sbuf[0]);
while (true) {
if (*pch != ' ')break;
if (pch >= &(sbuf[18]))break;
pch++;
}
printf("Serial number: %s\n", pch);
getchar();
return 0;
}
У дисков NVM Express драйвер может работать в режиме эмуляции ATA, тогда устройство может отвечать на команду IDENTIFY DEVICE, на при этом не возвращать флаг CAP_ATA_ID_CMD при проверке возможностей. (У NVM Express есть своя система команд, в которой тоже есть команда IDENTIFY, но другого формата.) Если нужно обрабатывать такие случаи, лучше без предварительной проверки сразу слать команду и смотреть на коды ошибок.
Источники