2010-03-12 6 views

ответ

22

Я попытаюсь обобщить свой опыт по извлечению серийного номера накопителя на Linux.
Я предполагаю, что вы хотите серийный номер устройств хранения идентичности (в соответствии со спецификацией SCSI) не серийный номер устройства USB (в соответствии со спецификацией USB под Device Descriptor), эти две разные сущности.

УВЕДОМЛЕНИЕ!
Большинство устройств имеют тенденцию внедрять серийный номер в USB-контроллер и оставлять серийный номер внутреннего SCSI-диска нереализованным.
Так что, если вы хотите, чтобы однозначно идентифицировать USB-устройство, лучший способ заключается в создании строки из (спецификации USB) Device Descriptor как VendorID-PRODUCTID-HardwareRevision-SerialNumber
Ниже я опишу, как запросить SN накопителя, как было задано.

Диски упали в 2 категории (на самом деле больше, но давайте упростим): ATA-like (hda, hdb ...) и SCSI-like (sda sdb ...). USB-накопители относятся ко второй категории, они называются SCSI прилагаются диски. В обеих ситуациях для извлечения необходимой информации (в нашем случае серийного номера) можно использовать вызовы ioctl.

Для Устройства SCSI (и они включают в себя USB-накопители) драйвер генератора Linux и его API документированы по адресу tldp.
Серийный номер на устройствах SCSI доступен внутри Vital Product Data (короткий: VPD) и извлекается с помощью SCSI Inquiry Command. утилита commad строки в Linux, который может принести этот ДДП является sdparm:

> yum install sdparm 
> sdparm --quiet --page=sn /dev/sda 
    Unit serial number VPD page: 
    3BT1ZQGR000081240XP7 

Обратите внимание, что не все устройства имеют этот серийный номер, рынок наводнен писк подделками, и некоторые флэш-диски USB вернуться странные серийные номера (для Например, мой sandisk cruzer возвращает только букву «u»). Чтобы преодолеть это, некоторые люди предпочитают создавать уникальный идентификатор, смешивая разные строки из VPD, такие как идентификатор продукта, идентификатор поставщика и серийный номер.

код в 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); 
    } 
} 
+0

Отличное описание, и мне нравится разделение между SCSI и ATA. –

+0

отличная работа, единственная недостающая вещь заключается в том, как определить, является ли диск scsi или ide? – chacham15

+0

это замечательно, но есть ошибка, проверка успеха ioctl недостаточна, чтобы проверить, что у вас есть правильный серийный номер ... вам также нужно проверить 'if ((io_hdr.info и SG_INFO_OK_MASK)! = SG_INFO_OK) return -1 ' – Geoffrey

0

Лучший способ, вероятно, сделать то, что делают инструменты командной строки (опять же, возможно): проверить соответствующие файлы в /proc или /sys, но из кода на C++.

2

И этот кусок кода будет получить 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); 


} 
+0

Я пробовал это, и это привело к неправильным результатам. – chacham15

Смежные вопросы