linux C++下的U盘序列号

Posted

技术标签:

【中文标题】linux C++下的U盘序列号【英文标题】:USB-drive serial number under linux C++ 【发布时间】:2010-03-12 12:48:17 【问题描述】:

有什么方法可以使用 C++ 确定 linux 中 USB 驱动器的序列号吗?

如果不是 C++,与hwinfo -diskhdparm -i 有什么不同吗?

【问题讨论】:

【参考方案1】:

我将尝试总结一下我在 linux 上检索存储驱动器序列号的经验。 我假设您想要 storage device 身份的序列号(根据 SCSI 规范)而不是 USB 设备 的序列号(根据 Device Descriptor 下的 USB 规范) ,这两个是不同的实体。

注意! 大多数设备倾向于在 USB 控制器中实现序列号,而未实现内部 SCSI 磁盘的序列号。 因此,如果您想唯一标识 USB 设备,最好的方法是从 Device Descriptor(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);
    

【讨论】:

很好的描述,我喜欢 SCSI 和 ATA 之间的分离。 干得好,唯一缺少的是如何确定驱动器是scsi还是ide?​​span> 这很好,但是有一个错误,检查 ioctl 成功并不足以验证您获得了有效的序列号...您还需要验证if ((io_hdr.info &amp; SG_INFO_OK_MASK) != SG_INFO_OK) return -1 使用上述修复更新了答案。【参考方案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);



【讨论】:

【参考方案3】:

我发现了一些对你来说也很有趣的东西:

“How to detect if /dev/* is a USB device?”

【讨论】:

【参考方案4】:

最好的方法可能是做命令行工具(同样,可能)做的事情:检查 /proc/sys 中的相关文件,但来自 C++ 代码。

【讨论】:

以上是关于linux C++下的U盘序列号的主要内容,如果未能解决你的问题,请参考以下文章

U盘序列号怎么获取?怎么清楚U盘插拔痕迹?

电脑里怎么查询使用过的u盘序列号?

关于U盘序列号读取工具的详解与分析

golang读取u盘序列号(通过读取序列号实现)

mac系统无法复制文件到硬盘U盘的解决方法

联想Thinkpad申请系统恢复U盘,通过UltraISO提取ISO镜像