2010-03-12 19 views

Respuesta

22

Trataré de resumir mi experiencia con respecto a la recuperación del número de serie de la unidad de almacenamiento en Linux.
me supuesto de que desea el número de serie del dispositivo de almacenamiento identidad (según la especificación SCSI) no el número de serie del dispositivo USB (según la especificación USB en Device Descriptor), estos dos son entidades diferentes.

¡AVISO!
La mayoría de los dispositivos tienden a implementar un número de serie en el controlador USB y dejan sin implementar el número de serie del disco SCSI interno.
lo tanto, si desea identificar de manera única un dispositivo USB de la mejor manera es crear una cadena de la (especificación USB) Device Descriptor como vendorID-ProductId-HardwareRevision-SerialNumber
En la siguiente describiré cómo recuperar el SN de la unidad de almacenamiento, como se le preguntó.

Las unidades se dividen en 2 categorías (en realidad más, pero simplifiquemos): ATA-like (hda, hdb ...) y SCSI-like (sda sdb ...). Las unidades USB caen en la segunda categoría, se llaman Discos SCSI conectados. En ambas situaciones, las llamadas ioctl se pueden usar para recuperar la información requerida (en nuestro caso, el número de serie).

Para dispositivos SCSI (y éstos incluyen unidades USB) el controlador genérico de Linux y es API está documentado en tldp.
El número de serie de los dispositivos SCSI está disponible dentro del Vital Product Data (abreviado: VPD) y se puede recuperar utilizando el SCSI Inquiry Command. Una utilidad de línea commad en Linux que puede traer esta VPD es sdparm:

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

Tenga en cuenta que no todos los dispositivos tienen este número de serie, el mercado está inundado de imitaciones pío, y algunos discos flash USB regresar seriales extraños (por ejemplo mi sandisk cruzer devuelve solo la letra "u"). Para superar esto, algunas personas optan por crear un identificador único mezclando diferentes cadenas de VPD, como Id. De producto, Id. De proveedor y Número de serie.

Código

en 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); 
} 

En aras de la completitud También voy a proporcionar el código para recuperar el número de serie de los dispositivos ATA (HDA, HDB ...). Esto NO funcionará para dispositivos 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

Excelente descripción y me gusta la separación entre SCSI y ATA. –

+0

buen trabajo, lo único que falta es cómo determinar si un disco es scsi o ide? – chacham15

+0

esto es genial, pero hay un error, verificar el éxito de ioctl no es suficiente para verificar que tienes una serie válida ... también debes verificar 'if ((io_hdr.info & SG_INFO_OKASK)! = SG_INFO_OK) return -1 ' – Geoffrey

0

La mejor manera es probablemente hacer lo que hacen las herramientas de línea de comandos (nuevamente, probablemente): inspeccionar los archivos relevantes en /proc o /sys, pero desde el código C++.

2

Y esta pieza de código obtendrá el número de serie USB ... no es tan técnicamente impresionante como de clyfe , pero parece hacer el truco cada vez.

#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

Intenté esto y produjo los resultados incorrectos. – chacham15

Cuestiones relacionadas