Translate

Image of Android Wireless Application Development
Image of RHCE Red Hat Certified Engineer Linux Study Guide (Exam RH302) (Certification Press)
Image of Advanced Programming in the UNIX Environment, Second Edition (Addison-Wesley Professional Computing Series)
Image of Linux Kernel Development (3rd Edition)

Hard Disk Metadata

Recently I was asked how to programmatically retrieve the serial number of a hard disk using C++ on a GNU/Linux platform. After a small amount of research, I wrote a short demonstration program and that was that or so I thought. However my curiosity was picqued and I decided to look at how to extract other metadata from a hard disk. To satisfy this curiosity I wrote a small utility that outputs selected metadata from hard disk in a number of formats, i.e. XML, TEXT and CSV (Comma Separated Values).

Here is example of the output in TEXT mode:

# ./hdm  /dev/sda

DEVICE: /dev/sda
----------------
Manufacturer Model: Hitachi HDP725050GLA360
     Serial Number: GEA534RF1MUN5A
 Firmware Revision: GM4OA52A
    Transport Type: SATA Rev 2.6
       Maximum RPM: 7200
          Capacity: 500GB
  Number Cylinders: 60801
    Partition Type: gpt
    No.  Start   End     Size      Type      Filesystem   Name  Flags
    01  17.9kB   210MB   210MB     primary   fat16              boot
    02   210MB   419MB   210MB     primary    ext4              
    03   419MB   500GB   500GB     primary                      lvm
# 


You may be wondering what this disk is used for. It happens to be the boot disk for the Fedora 13 platform which I used to write this article. It uses GPT (GUID Partition Table) rather than the more common MBR partitioning scheme. I use EFI and GRUB2 instead of the traditional BIOS and Legacy GRUB to boot the operating system – hence the FAT16 partition for the ESP (EFI System Partition). As an aside, I like this arrangment because of the significantly faster boot time. The Fedora 13 initrd and kernel images and related files are on the second partition and the third partition is a logical volume which is split into a number of filesystems. If you look closely at the above output, you will see that it contains both the output from a utility such as hdparms or lshw and a partitioning utility such as gdisk or parted.

Here is the XML output in newline mode for the same disk:

[root@ultra hdparm]# ./hdm -x  /dev/sda
<disk dev="/dev/sda"><model>Hitachi HDP725050GLA360</model><serialno>GEA534RF1MUN5A</serialno><firmware>GM4OA52A</firmware><transport>SATA Rev 2.6</transport><rpm>7200</rpm><capacity>500GB</capacity><geometry><cylinders>60801</cylinders><heads>255</heads><sectors>63</sectors></geometry><partitiontype>gpt<paritiontype><partitions><partition number="1"><start>17.9kB</start><end>210MB</end><size>210MB</size><type>primary</type><filesystem>fat16</filesystem><label></label><flags>boot</flags></partition><partition number="2"><start>210MB</start><end>419MB</end><size>210MB</size><type>primary</type><filesystem>ext4</filesystem><label></label><flags></flags></partition><partition number="3"><start>419MB</start><end>500GB</end><size>500GB</size><type>primary</type><filesystem></filesystem><label></label><flags>lvm</flags></partition></partitions></disk>[root@ultra hdparm]# 


Here is the regular XML output for the same disk:

# ./hdm -x -n /dev/sda
<disk dev="/dev/sda">
    <model>Hitachi HDP725050GLA360</model>
    <serialno>GEA534RF1MUN5A</serialno>
    <firmware>GM4OA52A</firmware>
    <transport>SATA Rev 2.6</transport>
    <rpm>7200</rpm>
    <capacity>500GB</capacity>
    <geometry>
        <cylinders>60801</cylinders>
        <heads>255</heads>
        <sectors>63</sectors>
    </geometry>
    <partitiontype>gpt<paritiontype>
    <partitions>
        <partition number="1">
            <start>17.9kB</start>
            <end>210MB</end>
            <size>210MB</size>
            <type>primary</type>
            <filesystem>fat16</filesystem>
            <label></label>
            <flags>boot</flags>
        </partition>
        <partition number="2">
            <start>210MB</start>
            <end>419MB</end>
            <size>210MB</size>
            <type>primary</type>
            <filesystem>ext4</filesystem>
            <label></label>
            <flags></flags>
        </partition>
        <partition number="3">
            <start>419MB</start>
            <end>500GB</end>
            <size>500GB</size>
            <type>primary</type>
            <filesystem></filesystem>
            <label></label>
            <flags>lvm</flags>
        </partition>
    </partitions>
</disk>
#


And, finally, here is the output for the same disk in CSV mode:

# ./hdm -c  /dev/sda
"Hitachi HDP725050GLA360","GEA534RF1MUN5A","GM4OA52A","SATA Rev 2.6","7200","500GB","60801","255","63"","gpt","01","17.9kB","210MB","210MB","primary","fat16","","boot","02","210MB","419MB","210MB","primary","ext4","","","03","419MB","500GB","500GB","primary","","","lvm"[root@ultra hdparm]
#


I decided to use the routines in libparted to retrieve and manipulate the partitioning information. All these routines start with ped_ and are contained within the dump_partition() routine. Many of the ped_ routines return pointers to allocated memory (which contains ASCII strings) and therefore you need to free up this space after use.

For hardware information such as the serial number and firmware revision, it is necessary to use and ioctl to retrieve the information. GNU/Linux provides a number of ioctls and structures for reading and writing metadata and controlling disks. These are detailed in /usr/include/linux/hdreg.h.

#define HDIO_GETGEO             0x0301  /* get device geometry */
#define HDIO_GET_UNMASKINTR     0x0302  /* get current unmask setting */
#define HDIO_GET_MULTCOUNT      0x0304  /* get current IDE blockmode setting */
#define HDIO_GET_QDMA           0x0305  /* get use-qdma flag */

#define HDIO_SET_XFER           0x0306  /* set transfer rate via proc */

#define HDIO_OBSOLETE_IDENTITY  0x0307  /* OBSOLETE, DO NOT USE: returns 142 bytes */
#define HDIO_GET_KEEPSETTINGS   0x0308  /* get keep-settings-on-reset flag */
#define HDIO_GET_32BIT          0x0309  /* get current io_32bit setting */
#define HDIO_GET_NOWERR         0x030a  /* get ignore-write-error flag */
#define HDIO_GET_DMA            0x030b  /* get use-dma flag */
#define HDIO_GET_NICE           0x030c  /* get nice flags */
#define HDIO_GET_IDENTITY       0x030d  /* get IDE identification info */
#define HDIO_GET_WCACHE         0x030e  /* get write cache mode on|off */
#define HDIO_GET_ACOUSTIC       0x030f  /* get acoustic value */
#define HDIO_GET_ADDRESS        0x0310  /* */

#define HDIO_GET_BUSSTATE       0x031a  /* get the bus state of the hwif */
#define HDIO_TRISTATE_HWIF      0x031b  /* execute a channel tristate */
#define HDIO_DRIVE_RESET        0x031c  /* execute a device reset */
#define HDIO_DRIVE_TASKFILE     0x031d  /* execute raw taskfile */
#define HDIO_DRIVE_TASK         0x031e  /* execute task and special drive command */
#define HDIO_DRIVE_CMD          0x031f  /* execute a special drive command */
#define HDIO_DRIVE_CMD_AEB      HDIO_DRIVE_TASK

/* hd/ide ctl's that pass (arg) non-ptr values are numbered 0x032n/0x033n */
#define HDIO_SET_MULTCOUNT      0x0321  /* change IDE blockmode */
#define HDIO_SET_UNMASKINTR     0x0322  /* permit other irqs during I/O */
#define HDIO_SET_KEEPSETTINGS   0x0323  /* keep ioctl settings on reset */
#define HDIO_SET_32BIT          0x0324  /* change io_32bit flags */
#define HDIO_SET_NOWERR         0x0325  /* change ignore-write-error flag */
#define HDIO_SET_DMA            0x0326  /* change use-dma flag */
#define HDIO_SET_PIO_MODE       0x0327  /* reconfig interface to new speed */
#define HDIO_SCAN_HWIF          0x0328  /* register and (re)scan interface */
#define HDIO_UNREGISTER_HWIF    0x032a  /* unregister interface */
#define HDIO_SET_NICE           0x0329  /* set nice flags */
#define HDIO_SET_WCACHE         0x032b  /* change write cache enable-disable */
#define HDIO_SET_ACOUSTIC       0x032c  /* change acoustic behavior */
#define HDIO_SET_BUSSTATE       0x032d  /* set the bus state of the hwif */
#define HDIO_SET_QDMA           0x032e  /* change use-qdma flag */
#define HDIO_SET_ADDRESS        0x032f  /* change lba addressing modes */


The ioctls that I used in the utility are HDIO_DRIVE_CMD, HDIO_GETGEO and HDIO_GET_IDENTITY. The last two ioctls are relativly simple to use. HDIO_DRIVE_CMD, on the other hard, is a complicated routine like many other general purpose ioctls. Read kernel/Documentation/ioctl/hdio.txt for detailed information and examine the code in drivers/ide/ide.c and drivers/block/scsi_ioctl.c for starters and look at the various published hard disk interface specifications. I fully agree with the warning in the section on the HDIO_DRIVE_CMD ioctl, that “If you don’t have a copy of the ANSI ATA specification handy, you should probably ignore this ioctl.”

Here are the inputs and outputs for HDIO_DRIVE_CMD:

        __u8[4+512}
       ioctl(fd, HDIO_DRIVE_CMD, args);

       INPUTS:

       args[0]    COMMAND
       args[1]    NSECTOR
       args[2]    FEATURE
       args[3]    NSECTOR

       OUTPUTS:

       args[0]    status
       args[1]    error
       args[2]    NSECTOR
       args[3]    undefined
       args[4+]   NSECTOR * 512 bytes of data returned by the command.


When a drive is sent the IDENTIFY_DRIVE (0xEC) command, it returns 256 words (512 bytes) of information. The words are numbered 0-255. Word 255 is the checksum and signature (0xA5). For ASCII strings each word contains two characters, the high order byte the first, the low order byte the second. For 32-bit values the low order word is first. That is why I used the kernel __le16_to_cpus() routine to byte swap the words.

Look at the get_diskinfo() routine for a working example of HDIO_DRIVE_CMD, main() for HDIO_GET_IDENTITY and get_geometry() for HDIO_GETGEO. Note that the CHS (Cylinder Head Sector) values returned by HDIO_GETGEO may or may not be accurate. It also has a 2TB limit for the starting sector offset of a hard disk partition. A better way is to use the default LBA (Logical Block Addressing) capacity value returned in words 57-58 or the current LBA capacity value returned in words 60-61, or better still the maximum capacity returned in LBA48 (words 100-103), after a successful HDIO_DRIVE_CMD IDENTIFY_DRIVE command. You can use HDIO_GET_ADDRESS to figure out the current addressing mode. I show you 2 ways of determining the capacity in the get_capacity() routine.

Here is the source code for the utility:

/*
 *   hdm.c - Hard Disk Metadata 
 *
 *   Copyright (C) Finnbarr P. Murphy 2010  <fpm[AT]fpmurphy.com>
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License Version 2 as 
 *   published by the Free Software Foundation.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
 *
 */


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <linux/fs.h>
#include <asm/byteorder.h>
#include <sys/ioctl.h>
#include <linux/hdreg.h>
#include <parted/parted.h>

#define DUMPXML          1
#define DUMPTXT          2
#define DUMPCSV          3
#define NONEWLINE        0

#define DI_VERSION       "1.0"
#define TRANSPORT_MAJOR   0xDE 
#define TRANSPORT_MINOR   0xDF
#define ATA_PIDENTIFY     0xA1
#define ATA_IDENTIFY      0xEC
#define NMRR              0xD9
#define CAPAB             0x31 
#define CMDS_SUPP_1       0x53  
#define VALID             0xC000
#define VALID_VAL         0x4000
#define SUPPORT_48_BIT    0x0400 
#define LBA_SUP           0x0200 
#define LBA_LSB           0x64
#define LBA_MID           0x65
#define LBA_48_MSB        0x66 
#define LBA_64_MSB        0x67 


/* yes - these are shortcuts! */
static __u16 *id = (void *)NULL;
static struct hd_geometry *g;
static int fd = 0;


struct hd_geometry *
get_geometry(int fd)
{
    static struct hd_geometry geometry;

    if (ioctl(fd, HDIO_GETGEO, &geometry)) {
        perror("ERROR: HDIO_GETGEO failed");
    }

    return &geometry;
}


void *
get_diskinfo(int fd)
{
    static __u8 args[4+512];
    __u16 *id = (void *)(args + 4);
    int i;

    memset(args, 0, sizeof(args));
    args[0] = ATA_IDENTIFY;
    args[3] = 1;
    if (ioctl(fd, HDIO_DRIVE_CMD, args)) {
        args[0] = ATA_PIDENTIFY;
        args[1] = 0;
        args[2] = 0;
        args[3] = 1;
        if (ioctl(fd, HDIO_DRIVE_CMD, args)) {
            perror("ERROR: HDIO_DRIVE_CMD failed");
            return "";
        }
    }

    /* byte-swap data to match host endianness */
    for (i = 0; i < 0x100; ++i)
         __le16_to_cpus(&id[i]);

    return id;
}


//
// Routine currently only handles SATA drives.  Extra code needs to be added to support PATA, SCSI, USB, etc.
//
char *
get_transport(__u16 id[])
{
    __u16 major, minor;
    unsigned int ttype, stype;

    major = id[TRANSPORT_MAJOR];
    minor = id[TRANSPORT_MINOR];

    if (major == 0x0000 || major == 0xffff)
        return "";

    ttype = major >> 12;      /* transport type */
    stype = major & 0xfff;    /* subtype */

    if (ttype == 1) {
         if (stype & 0x2f) {
             if (stype & (1<<5))
                 return "SATA Rev 3.0";
             else if (stype & (1<<4))
                 return "SATA Rev 2.6";
             else if (stype & (1<<3))
                 return "SATA Rev 2.5";
             else if (stype & (1<<2))
                 return "SATA II Extensions";
             else if (stype & (1<<1))
                 return "SATA 1.0a";
         }
    }
}


char *
get_rpm(__u16 id[])
{
   static char str[6];
   __u16 i = id[NMRR];

   sprintf(str,"%u", i);

   return str;
}


char *
ascii_string(__u16 *p,
             unsigned int len)
{
    __u8 i, c;
    char cl;
    static char str[60];
    char *s = str;

    memset(&str, 0, sizeof(str));

    /* find first character */
    for (i = 0; i < len; i++) {
        if (( (char)0x00ff & ((*p) >> 8)) != ' ')
            break;
        if ((cl = (char) 0x00ff & (*p)) != ' ') {
            if (cl != '\0') *s++ = cl;
            p++; i++;
            break;
        }
        p++;
    }
    /* copy from here to end */
    for (; i < len; i++) {
        c = (*p) >> 8;
        if (c) *s++ = c;
        c = (*p);
        if (c) *s++ = c;
        p++;
    }

    /* remove trailing blanks */
    s = str;
    while(*s) s++;
    while(*--s == ' ') *s= 0;

    return str;
}


#define USE_CAPAB 
char *
get_capacity(int fd, __u16 id[])
{
    unsigned int sector_bytes = 512;
    static char str[20];
    __u64 sectors = 0;

#ifdef USE_CAPAB 
    memset(&str, 0, sizeof(str));

    if (id[CAPAB] & LBA_SUP) {
        if (((id[CMDS_SUPP_1] & VALID) == VALID_VAL) && (id[CMDS_SUPP_1] & SUPPORT_48_BIT) ) {
            sectors = (__u64)id[LBA_64_MSB] << 48 | (__u64)id[LBA_48_MSB] << 32 |
                      (__u64)id[LBA_MID] << 16 | id[LBA_LSB] ;
        }
    }
#else
    unsigned int sector32 = 0;

    if (!(ioctl(fd, BLKGETSIZE64, &sectors))) {            // bytes
          sectors /= sector_bytes;
    } else if (!(ioctl(fd, BLKGETSIZE, &sector32))) {      // sectors
          sectors = sector32;
    } else
          return "";
#endif

    sectors *= (sector_bytes /512);
    sectors = (sectors << 9)/1000000;
    if (sectors > 1000)
        sprintf(str, "%lluGB", (unsigned long long) sectors/1000);
    else
        sprintf(str, "%lluMB", (unsigned long long) sectors);

    return str;
}


void
dump_partitions(char *device, int dumpmode, int nlmode)
{
    PedDevice *dev = (PedDevice *)NULL;
    PedDiskType* type;
    PedDisk* disk = (PedDisk *)NULL;
    PedPartition* part;
    PedPartitionFlag flag;
    PedUnit default_unit;
    int has_free_arg = 0;

    char *start;
    char *end;
    char *size;
    char flags[100];
    const char *partname;
    const char *parttype;
    const char *partlabel;
    const char *partflags;
    int first_flag;

    dev = ped_device_get(device);
    if (!ped_device_open (dev)) {
       fprintf(stderr, "ERROR: ped-device-opem\n");
       exit(1);
    }

    disk = ped_disk_new(dev);
    if (!disk) {
       fprintf(stderr, "ERROR: ped-disk-new\n");
       exit(1);
    }

    start = ped_unit_format(dev, 0);
    default_unit = ped_unit_get_default();
    end = ped_unit_format_byte (dev, dev->length * dev->sector_size
          - (default_unit == PED_UNIT_CHS || default_unit == PED_UNIT_CYLINDER));

    switch (dumpmode) {
       case DUMPXML:
          if (nlmode) printf("\n    ");
          printf("<partitiontype>%s<paritiontype>", disk->type->name);
          if (nlmode) printf("\n    ");
          printf("<partitions>");
          break;
       case DUMPTXT:
          printf("    Partition Type: %s\n", disk->type->name);
          printf("    No.  Start   End     Size      Type      Filesystem   Name  Flags\n");
          break;
       case DUMPCSV:
          putchar('"'); putchar(','); putchar('"');
          printf("%s", disk->type->name );
          break;
    }

    free(start);
    free(end);

    for (part = ped_disk_next_partition (disk, NULL); part;
        part = ped_disk_next_partition (disk, part)) {

         if ((!has_free_arg && !ped_partition_is_active(part)) ||
             part->type & PED_PARTITION_METADATA)
             continue;

         start = ped_unit_format (dev, part->geom.start);
         end = ped_unit_format_byte (dev, (part->geom.end + 1) * (dev)->sector_size - 1);
         size = ped_unit_format (dev, part->geom.length);

         if (!(part->type & PED_PARTITION_FREESPACE)) {
              parttype = ped_partition_type_get_name (part->type);
              partlabel = ped_partition_get_name(part);
         } else {
              parttype = "";
              partlabel = "";
         }

         // flags 
         memset(&flags, 0, sizeof(flags));
         first_flag = 1;
         for (flag = ped_partition_flag_next(0); flag;
              flag = ped_partition_flag_next(flag)) {
              if (ped_partition_get_flag(part, flag)) {
                   if (first_flag) {
                        first_flag = 0;
                   } else {
                        strcat (flags, ", ");
                   }
                   partflags = ped_partition_flag_get_name(flag);
                   strcat(flags, partflags);
              }
         }
         switch (dumpmode) {
             case DUMPXML:
                 if (nlmode) printf("\n        ");
                 if (part->num >= 0)
                      printf("<partition number=\"%d\">", part->num);
                 else
                      printf("<partition number=\"0\">");
                 if (nlmode) printf("\n            ");
                 printf("<start>%s</start>", start);
                 if (nlmode) printf("\n            ");
                 printf("<end>%s</end>", end);
                 if (nlmode) printf("\n            ");
                 printf("<size>%s</size>", size);
                 if (nlmode) printf("\n            ");
                 printf("<type>%s</type>", parttype);
                 if (nlmode) printf("\n            ");
                 printf("<filesystem>%s</filesystem>", part->fs_type ? part->fs_type->name : "");
                 if (nlmode) printf("\n            ");
                 printf("<label>%s</label>", partlabel);
                 if (nlmode) printf("\n            ");
                 printf("<flags>%s</flags>", flags);
                 if (nlmode) printf("\n        ");
                 printf("</partition>");
                 break;
             case DUMPTXT:
                 if (part->num >= 0)
                      printf("    %02d", part->num);
                 else
                      printf("        ");
                 printf("  %6s  %6s  %6s  %10s", start, end, size, parttype);
                 printf("  %6s", part->fs_type ? part->fs_type->name : "");
                 printf("  %10s  %s\n", partlabel, flags);
                 break;
             case DUMPCSV:
                 putchar('"'); putchar(','); putchar('"');
                 if (part->num >= 0) printf("%02d", part->num);
                 putchar('"'); putchar(','); putchar('"');
                 printf("%s", start);
                 putchar('"'); putchar(','); putchar('"');
                 printf("%s", end);
                 putchar('"'); putchar(','); putchar('"');
                 printf("%s", size);
                 putchar('"'); putchar(','); putchar('"');
                 printf("%s", parttype);
                 putchar('"'); putchar(','); putchar('"');
                 if (part->fs_type) printf("%s", part->fs_type->name);
                 putchar('"'); putchar(','); putchar('"');
                 printf("%s", partlabel);
                 putchar('"'); putchar(','); putchar('"');
                 printf("%s", flags);
                 break;
        }

        free(start);
        free(end);
        free(size);
    }

    switch (dumpmode) {
       case DUMPXML:
            if (nlmode) printf("\n    ");
            printf("</partitions>");
            break;
       case DUMPTXT:
            break;
       case DUMPCSV:
            putchar('"');
            break;
    }
}


void
dump(char *device)
{
    int len = strlen(device) + 8;
    int i = 0;

    printf("\nDEVICE: %s\n", device);
    while(i++ < len) putchar('-');
    putchar('\n');
    printf("Manufacturer Model: %s\n", ascii_string(&id[27],20));
    printf("     Serial Number: %s\n", ascii_string(&id[10],10));
    printf(" Firmware Revision: %s\n", ascii_string(&id[23],4));
    printf("    Transport Type: %s\n", get_transport(id));
    printf("       Maximum RPM: %s\n", get_rpm(id));
    printf("          Capacity: %s\n", get_capacity(fd, id));
    printf("  Number Cylinders: %u\n", g->cylinders);

    dump_partitions(device, DUMPTXT, NONEWLINE);
}


void
dumpxml(char *device, int nlmode)
{
    printf("<disk dev=\"%s\">", device);
    if (nlmode) printf("\n    ");
    printf("<model>%s</model>", ascii_string(&id[27],20));
    if (nlmode) printf("\n    ");
    printf("<serialno>%s</serialno>", ascii_string(&id[10],10));
    if (nlmode) printf("\n    ");
    printf("<firmware>%s</firmware>", ascii_string(&id[23],4));
    if (nlmode) printf("\n    ");
    printf("<transport>%s</transport>", get_transport(id));
    if (nlmode) printf("\n    ");
    printf("<rpm>%s</rpm>", get_rpm(id));
    if (nlmode) printf("\n    ");
    printf("<capacity>%s</capacity>", get_capacity(fd, id));
    if (nlmode) printf("\n    ");
    printf("<geometry>");
    if (nlmode) printf("\n        ");
    printf("<cylinders>%u</cylinders>", (unsigned short) g->cylinders);
    if (nlmode) printf("\n        ");
    printf("<heads>%u</heads>", (unsigned char) g->heads);
    if (nlmode) printf("\n        ");
    printf("<sectors>%u</sectors>", (unsigned char) g->sectors);
    if (nlmode) printf("\n    ");
    printf("</geometry>");
    dump_partitions(device, DUMPXML, nlmode);
    if (nlmode) putchar('\n');
    printf("</disk>");
    if (nlmode) putchar('\n');
}


void
dumpcsv(char *device)
{
    putchar('"');
    printf("%s", ascii_string(&id[27],20));
    putchar('"'); putchar(','); putchar('"');
    printf("%s", ascii_string(&id[10],10));
    putchar('"'); putchar(','); putchar('"');
    printf("%s", ascii_string(&id[23],4));
    putchar('"'); putchar(','); putchar('"');
    printf("%s", get_transport(id));
    putchar('"'); putchar(','); putchar('"');
    printf("%s", get_rpm(id));
    putchar('"'); putchar(','); putchar('"');
    printf("%s", get_capacity(fd, id));
    putchar('"'); putchar(','); putchar('"');
    printf("%u", g->cylinders);
    putchar('"'); putchar(','); putchar('"');
    printf("%u", g->heads);
    putchar('"'); putchar(','); putchar('"');
    printf("%u", g->sectors);
    putchar('"');
    dump_partitions(device, DUMPCSV, NONEWLINE);
}


void
usage()
{
    printf("usage: di [-n] [-c|-csv|-x|--xml] devicepath\n");
    printf("usage: di [-v |--version ]\n");
}


int
main(int argc,
     char *argv[])
{
    static struct hd_driveid hd;
    int option_index = 0, c;
    int xmlmode = 0, nlmode = 0, csvmode = 0;
    char *device;

    static struct option long_options[] = {
        {"csv", no_argument, 0, 'c'},
        {"help", no_argument, 0, 'h'},
        {"newline", no_argument, 0, 'n'},
        {"version", no_argument, 0, 'v'},
        {"xml", no_argument, 0, 'x'},
        {0, 0, 0, 0}
    };

    while ((c = getopt_long(argc, argv, "chnvx", long_options, &option_index)) != -1) {
        switch (c) {
            case 'h':
                usage();
                exit(EXIT_SUCCESS);
            case 'c':
                csvmode = 1;
                break;
            case 'n':
                nlmode = 1;
                break;
            case 'x':
                xmlmode = 1;
                break;
            case 'v':
                fprintf(stdout, "version %s\n", DI_VERSION);
                exit(EXIT_SUCCESS);
            default: /* '?' */
                usage();
                exit(EXIT_FAILURE);
        }
    }

    if (csvmode && xmlmode) {
        fprintf(stderr, "ERROR: Select either XML or CVS for formatted output\n");
        exit(EXIT_FAILURE);
    }

    if (optind >= argc) {
        fprintf(stderr, "ERROR: No devicepath provided\n");
        exit(EXIT_FAILURE);
    }

    if (geteuid() >  0) {
        fprintf(stderr, "ERROR: Must be root to use\n");
        exit(EXIT_FAILURE);
    }

    device = argv[optind];
    if ((fd = open(device, O_RDONLY|O_NONBLOCK)) < 0) {
        fprintf(stderr, "ERROR: Cannot open device %s\n", argv[1]);
        exit(EXIT_FAILURE);
    }

    id = get_diskinfo(fd);
    g  = get_geometry(fd);

    if (ioctl(fd, HDIO_GET_IDENTITY, &hd) < 0 ) {
        if (errno == -ENOMSG) {
            fprintf(stderr, "ERROR: No hard disk identification information available\n");
        } else {
            perror("ERROR: HDIO_GET_IDENTITY");
            exit(1);
        }
    }

    close(fd);

    if (csvmode)
        dumpcsv(device);
    else if (xmlmode)
        dumpxml(device, nlmode);
    else
        dump(device);

    exit(EXIT_SUCCESS);
}

To compile this code, you need to include libparted. If libparted is not available on your platform, download the source code for the parted utility from the GNU Project and build it .

Please feel free to use the source code included in this post for whatever purpose you want to use it for – provided you include the license text. If you use it on platforms which contain PATA, SAS or SCSI drives obviously you will need to extend the code to include those drive types but that is not difficult to do with the right information. One of the best places to find this sort of information is in the various INCITS (International Committee for Information Technology Standards) standards and working groups. For example, the INCITS Technical Committe T10 is a good place to learn about SCSI storage interfaces.

Enjoy!
 

3 comments to Hard Disk Metadata