Серийный номер USB-накопителя под Linux C++
Есть ли способ определить s/n usb-накопителя в Linux с помощью C++?
Если нет C++, есть ли другой способ, отличный от hwinfo -disk
а также hdparm -i
?
4 ответа
Я попытаюсь обобщить мой опыт в отношении получения серийного номера накопителя на 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. Утилита с запятыми в Linux, которая может получить этот VPD, называется sdparm:
> yum install sdparm
> sdparm --quiet --page=sn /dev/sda
Unit serial number VPD page:
3BT1ZQGR000081240XP7
Обратите внимание, что не все устройства имеют этот серийный номер, рынок наводнен дешевыми подделками, и некоторые флэш-диски USB возвращают странные серийные номера (например, мой sandisk cruzer возвращает только букву "u"). Чтобы преодолеть это, некоторые люди выбирают создание уникального идентификатора, смешивая различные строки из VPD, такие как идентификатор продукта, идентификатор поставщика и серийный номер.
Код в с:
#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);
}
}
И этот фрагмент кода получит серийный номер USB... он не настолько впечатляющий, как у clyfe, но, похоже, каждый раз справляется с задачей.
#include <unistd.h>
#include <string.h>
#include <stdio.h>
int main(int arg, char **argv) {
ssize_t len;
char buf[256], *p;
char buf2[256];
int i;
len = readlink("/sys/block/sdb", buf, 256);
buf[len] = 0;
// printf("%s\n", buf);
sprintf(buf2, "%s/%s", "/sys/block/", buf);
for (i=0; i<6; i++) {
p = strrchr(buf2, '/');
*p = 0;
}
// printf("%s\n", buf2);
strcat(buf2, "/serial");
// printf("opening %s\n", buf2);
int f = open(buf2, 0);
len = read(f, buf, 256);
if (len <= 0) {
perror("read()");
}
buf[len] = 0;
printf("serial: %s\n", buf);
}
Я нашел то, что также было бы интересно для вас:
Лучше всего, вероятно, делать то, что делают инструменты командной строки (опять же, вероятно): проверять соответствующие файлы в любом /proc
или же /sys
, но из кода C++.