Switch to using udevadm info to query drive serial numbers (#263)

A user reported that GParted was displaying binary data for the serial
number of their USB key.  This was because hdparm -I was reporting
binary data for the serial number.  It looked like this (except the
question marks were binary non-printable bytes).
    $ LANG=C sudo hdparm -I /dev/sdh

    /dev/sdh:

    ATA device, with non-removable media
            Model Number:       ?|??t??L??|??
            Serial Number:      ?@>??8
                                      u
            Firmware Revision:  u???
    Standards:
            Likely used: 1
    ...

Back in 2015 when reporting of drive serial numbers was being added
[1][2], using hdparm -I was the best option because it reported the
serial number of all hard drives including those in virtual machines,
worked the same everywhere and was available by default in most
distributions, but didn't work for USB keys.  Where as lsblk -o SERIAL
was a new option not available yet on some distributions and didn't
report the serial number of drives in virtual machines.

Now hdparm -I capabilities are still the same; only working for hard
drives, including SSDs, but not USB keys.  Except for the above case
hdparm has always found to not report serial numbers for USB keys, like
this:
    # hdparm -I /dev/sde

    /dev/sde:
    SG_IO: bad/missing sense data, sb[]:  70 00 05 00 00 00 00 15 00 00 00 00 20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 70 00 00 00

    ATA device, with non-removable media
    Standards:
            Likely used: 1
    ....

Now the lsblk -o SERIAL option is available on all distributions and
reports the serial numbers of all drives including for virtual machine
disks and USB keys.

From an older but still supported Ubuntu 20.04 LTS virtual machine:
    # lsblk -o KNAME,VENDOR,MODEL,SERIAL,TYPE,TRAN -d
    KNAME VENDOR   MODEL            SERIAL              TYPE TRAN
    sda   ATA      VBOX HARDDISK    VB13d4e080-b35e62d3 disk sata
    sdb   ATA      VBOX HARDDISK    VB221202cf-092e5857 disk sata
    sdc   ATA      VBOX HARDDISK    VB04fefadd-5a185f96 disk sata
    sr0   VBOX     CD-ROM           VB2-01700376        rom  ata

From a physical machine running older but still supported Rocky Linux 8
with 2 SSDs, 2 HDDs, and 6 USB keys:
    # lsblk -o KNAME,VENDOR,MODEL,SERIAL,TYPE,TRAN -d
    KNAME VENDOR   MODEL            SERIAL                   TYPE TRAN
    sda   ATA      Samsung SSD 860  S3Z9NY0M620872V          disk sata
    sdb   ATA      SAMSUNG SSD UM41 DCF4300940SE940B4507     disk sata
    sdc   ATA      WDC WD10JFCX-68N WD-WXE1EB6C3MHY          disk sata
    sdd   ATA      MM0500EBKAE      9XF132MW                 disk sata
    sde   SanDisk  U3 Cruzer Micro  00001853E473AABA         disk usb
    sdf   Kingston DataTraveler 3.0 408D5C1658F7E31079051DB9 disk usb
    sdg   Kingston DataTraveler 2.0 1C6F654FF40FBE50D9341059 disk usb
    sdh            Patriot Memory   07082AA3E0944E31         disk usb
    sdi   Corsair  Slider 3.0       12240400400016361397     disk usb
    sdj   SMI      USB DISK         SMI_USB_DISK-0:0         disk usb
    sr0   ASUS     DRW-24B3LT       B5D0CL213444             rom  sata
    sr1   SanDisk  U3 Cruzer Micro  00001853E473AABA         rom  usb

lsblk gets it's information from udev.  lsblk reports the SERIAL column
from the udev device property ID_SERIAL_SHORT, except when that is blank
it uses the ID_SERIAL property instead [3].  Note that ID_SERIAL is
composed from vendor and model information [4].

USB key with a serial number:
    # udevadm info --query=property --name=/dev/sde | grep SERIAL
    ID_SERIAL=SanDisk_U3_Cruzer_Micro_00001853E473AABA-0:0
    ID_SERIAL_SHORT=00001853E473AABA
    # lsblk -o KNAME,VENDOR,MODEL,SERIAL,TYPE,TRAN -d /dev/sde
    KNAME VENDOR   MODEL            SERIAL           TYPE TRAN
    sde   SanDisk  U3 Cruzer Micro  00001853E473AABA disk usb

USB key without a serial number:
    # udevadm info --query=property --name=/dev/sdj | grep SERIAL
    ID_SERIAL=SMI_USB_DISK-0:0
    # lsblk -o KNAME,VENDOR,MODEL,SERIAL,TYPE,TRAN -d /dev/sdj
    KNAME VENDOR   MODEL            SERIAL           TYPE TRAN
    sdj   SMI      USB DISK         SMI_USB_DISK-0:0 disk usb

To only get the device serial number, or blank when not available, query
the udev ID_SERIAL_SHORT property directly using udevadm info rather
than using lsblk -o SERIAL.  Previously GParted displayed "none" if
hdparm -I successfully found nothing, including for USB keys, and left
the serial number field blank on failure.  Now the field will be blank
in both cases, no serial number and failure to query it.

[1] 4b72ecd44e
    Display device serial numbers (#751251)
[2] Bug 751251 - Show serial number in device information
    https://bugzilla.gnome.org/show_bug.cgi?id=751251
[3] misc-utils/lsblk-properties.c:get_properties_by_udev()
    https://git.kernel.org/pub/scm/utils/util-linux/util-linux.git/tree/misc-utils/lsblk-properties.c?h=v2.40.2#n121
[4] what is the difference between ID_SERIAL and ID_SERIAL_SHORT udev
    attributes for a USB descriptor
    https://stackoverflow.com/questions/45940014/what-is-the-difference-between-id-serial-and-id-serial-short-udev-attributes-for
        "The ID_SERIAL_SHORT value comes from the iSerial string (if
        present) in the USB device descriptor.  The ID_SERIAL value is
        constructed in software and made up from various strings (vendor
        or manufacturer, model or product, and serial number if present)
        separated with _.
        "

Closes #263 - Serial number for my USB key showing binary data
This commit is contained in:
Mike Fleetwood 2024-08-09 07:38:23 +01:00
parent 188ffcc06b
commit a0f5b02f59
1 changed files with 5 additions and 28 deletions

View File

@ -63,7 +63,6 @@ const std::time_t SETTLE_DEVICE_PROBE_MAX_WAIT_SECONDS = 1;
const std::time_t SETTLE_DEVICE_APPLY_MAX_WAIT_SECONDS = 10;
static bool udevadm_found = false;
static bool hdparm_found = false;
static const Glib::ustring GPARTED_BUG( _("GParted Bug") );
@ -115,7 +114,6 @@ Glib::ustring GParted_Core::get_version_and_config_string()
void GParted_Core::find_supported_core()
{
udevadm_found = ! Glib::find_program_in_path( "udevadm" ).empty();
hdparm_found = ! Glib::find_program_in_path( "hdparm" ).empty();
}
@ -772,38 +770,17 @@ void GParted_Core::set_device_from_disk( Device & device, const Glib::ustring &
void GParted_Core::set_device_serial_number( Device & device )
{
if ( ! hdparm_found )
// Serial number left blank when the hdparm command is not installed.
if (! udevadm_found)
return;
Glib::ustring output;
Glib::ustring error;
Utils::execute_command( "hdparm -I " + Glib::shell_quote( device.get_path() ), output, error, true );
if ( ! error.empty() )
{
// hdparm reported an error message to stderr. Assume it's a device
// without a hard drive serial number.
//
// Using hdparm -I to query Linux software RAID arrays and BIOS fake RAID
// arrays, both devices without their own hard drive serial numbers,
// produce this error:
// HDIO_DRIVE_CMD(identify) failed: Inappropriate ioctl for device
//
// And querying USB flash drives, also a device type without their own
// hard drive serial numbers, generates this error:
// SG_IO: bad/missing sense data, sb[]: 70 00 05 00 00 00 00 0a ...
device.serial_number = "none";
}
else
{
Glib::ustring serial_number = Utils::trim( Utils::regexp_label( output,
"^[[:blank:]]*Serial Number:[[:blank:]]*(.*)[[:blank:]]*$" ) );
if ( ! serial_number.empty() )
device.serial_number = serial_number;
}
// Otherwise serial number left blank when not found in the hdparm output.
Utils::execute_command("udevadm info --query=property --name=" + Glib::shell_quote(device.get_path()),
output, error, true);
device.serial_number = Utils::regexp_label(output, "^ID_SERIAL_SHORT=([^\n]*)$");
}
/**
* Fills the device.partitions member of device by scanning
* all partitions