2010-03-12 18 views

risposta

22

Proverò a riassumere la mia esperienza relativa al recupero del numero di serie dell'unità di archiviazione su linux.
Presumo che si desidera che il numero di serie del dispositivo di memorizzazione identità (secondo la specifica SCSI) non il numero di serie del dispositivo USB (secondo la specifica USB in Device Descriptor), questi due sono entità diverse.

AVVISO!
La maggior parte dei dispositivi tende a implementare un numero seriale nel controller USB e non implementa il numero di serie del disco SCSI interno.
Quindi, se si vuole identificare in modo univoco un dispositivo USB il modo migliore è quello di creare una stringa dal (specifiche USB) Device Descriptor come ID fornitore-ProductId-HardwareRevision-SerialNumber
Nel seguito descriverò come recuperare il SN dell'unità di archiviazione, come richiesto.

Drives cadono in 2 categorie (in realtà più, ma cerchiamo di semplificare): ATA-like (hda, hdb ...) e SCSI-like (sda sdb ...). Le unità USB rientrano nella seconda categoria, vengono chiamate dischi collegati SCSI. In entrambe le situazioni è possibile utilizzare le chiamate ioctl per recuperare le informazioni richieste (nel nostro caso il numero di serie).

Per Dispositivi SCSI (e questi includono unità USB) il driver generico Linux e la sua API sono documentati a tldp.
Il numero di serie sui dispositivi SCSI è disponibile all'interno di Vital Product Data (breve: VPD) ed è recuperabile utilizzando SCSI Inquiry Command. un'utility a riga di commad in Linux che può prendere questo VPD è sdparm:

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

Nota che non tutti i dispositivi hanno questo numero di serie, il mercato è invaso da imitazioni pigolio, e alcuni dischi flash USB tornare strani serial (per esempio il mio sandisk cruzer restituisce solo la lettera "u"). Per ovviare a questo, alcune persone scelgono di creare un identificatore univoco mescolando stringhe diverse da VPD come ID prodotto, ID fornitore e Numero di serie.

codice in 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); 
} 

Per ragioni di completezza Sarò anche fornire il codice per recuperare il numero di serie per dispositivi ATA (HDA, HDB ...). Questo NON funzionerà per i dispositivi 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

Descrizione eccellente e mi piace la separazione tra SCSI e ATA. –

+0

ottimo lavoro, l'unica cosa che manca è come determinare se un'unità è scsi o ide? – chacham15

+0

questo è ottimo, ma c'è un bug, il controllo per il successo di ioctl non è sufficiente per verificare che tu abbia un seriale valido ... devi anche verificare 'if ((io_hdr.info & SG_INFO_OK_MASK)! = SG_INFO_OK) return -1 ' – Geoffrey

0

Il modo migliore è probabilmente quello di fare ciò che gli strumenti della riga di comando (ancora, probabilmente) fanno: ispezionare i file rilevanti in /proc o /sys, ma dal codice C++.

2

E questo pezzo di codice otterrà il numero di serie USB ... non è tecnicamente impressionante come clyfe di , ma sembra fare il trucco ogni volta.

#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

Ho provato questo e ha prodotto risultati sbagliati. – chacham15