Я попытаюсь обобщить мой опыт в поиске серийного номера накопителя на Linux.
Я предполагаю, что вам нужен серийный номер идентификатора устройства хранения (согласно спецификации SCSI), а не серийный номер устройства USB (согласно спецификации USB в разделе Дескриптор устройства ), это две разные сущности.
ВНИМАНИЕ!
Большинство устройств, как правило, используют серийный номер в USB-контроллере и не используют серийный номер внутреннего SCSI-диска.
Поэтому, если вы хотите однозначно идентифицировать USB-устройство, лучше всего создать строку из дескриптора устройства (спецификация USB), например VendorId-ProductId-HardwareRevision-SerialNumber
Далее я опишу, как получить SN диска хранения, как было задано.
Диски делятся на 2 категории (на самом деле больше, но давайте упростим): ATA-подобные (hda, hdb ...) и SCSI-подобные (sda sdb ...).
USB-накопители относятся ко второй категории, они называются SCSI-подключенные диски .
В обеих ситуациях ioctl вызовы могут использоваться для получения необходимой информации (в нашем случае серийный номер).
Для устройств SCSI (включая USB-накопители) универсальный драйвер Linux и его API задокументированы в tldp .
Серийный номер на устройствах SCSI доступен в Vital Product Data (сокращение: VPD) и может быть получен с помощью SCSI Inquiry Command .
Утилита запятой линии в Linux, которая может получить этот VPD: sdparm :
> yum install sdparm
> sdparm --quiet --page=sn /dev/sda
Unit serial number VPD page:
3BT1ZQGR000081240XP7
Обратите внимание, что не у всех устройств есть этот серийный номер, рынок наводнен дешевыми подделками, и некоторые флэш-диски usb возвращают странные сериалы (например, мой sandisk cruzer возвращает только букву "u"). Чтобы преодолеть это, некоторые люди предпочитают создавать уникальный идентификатор, смешивая различные строки из VPD, такие как ID продукта, ID поставщика и серийный номер.
Код в c:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <scsi/scsi.h>
#include <scsi/sg.h>
#include <sys/ioctl.h>
int scsi_get_serial(int fd, void *buf, size_t buf_len) {
// we shall retrieve page 0x80 as per http://en.wikipedia.org/wiki/SCSI_Inquiry_Command
unsigned char inq_cmd[] = {INQUIRY, 1, 0x80, 0, buf_len, 0};
unsigned char sense[32];
struct sg_io_hdr io_hdr;
int result;
memset(&io_hdr, 0, sizeof (io_hdr));
io_hdr.interface_id = 'S';
io_hdr.cmdp = inq_cmd;
io_hdr.cmd_len = sizeof (inq_cmd);
io_hdr.dxferp = buf;
io_hdr.dxfer_len = buf_len;
io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
io_hdr.sbp = sense;
io_hdr.mx_sb_len = sizeof (sense);
io_hdr.timeout = 5000;
result = ioctl(fd, SG_IO, &io_hdr);
if (result < 0)
return result;
if ((io_hdr.info & SG_INFO_OK_MASK) != SG_INFO_OK)
return 1;
return 0;
}
int main(int argc, char** argv) {
char *dev = "/dev/sda";
char scsi_serial[255];
int rc;
int fd;
fd = open(dev, O_RDONLY | O_NONBLOCK);
if (fd < 0) {
perror(dev);
}
memset(scsi_serial, 0, sizeof (scsi_serial));
rc = scsi_get_serial(fd, scsi_serial, 255);
// scsi_serial[3] is the length of the serial number
// scsi_serial[4] is serial number (raw, NOT null terminated)
if (rc < 0) {
printf("FAIL, rc=%d, errno=%d\n", rc, errno);
} else
if (rc == 1) {
printf("FAIL, rc=%d, drive doesn't report serial number\n", rc);
} else {
if (!scsi_serial[3]) {
printf("Failed to retrieve serial for %s\n", dev);
return -1;
}
printf("Serial Number: %.*s\n", (size_t) scsi_serial[3], (char *) & scsi_serial[4]);
}
close(fd);
return (EXIT_SUCCESS);
}
Для полноты картины я также предоставлю код для получения серийного номера для устройств ATA (hda, hdb ...). Это НЕ будет работать для USB-устройств.
#include <stdlib.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <linux/hdreg.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <cctype>
#include <unistd.h>
int main(){
struct hd_driveid *id;
char *dev = "/dev/hda";
int fd;
fd = open(dev, O_RDONLY|O_NONBLOCK);
if(fd < 0) {
perror("cannot open");
}
if (ioctl(fd, HDIO_GET_IDENTITY, id) < 0) {
close(fd);
perror("ioctl error");
} else {
// if we want to retrieve only for removable drives use this branching
if ((id->config & (1 << 7)) || (id->command_set_1 & 4)) {
close(fd);
printf("Serial Number: %s\n", id->serial_no);
} else {
perror("support not removable");
}
close(fd);
}
}