You are on page 1of 12

Hard Disk Metadata

Hard Disk Metadata


Finnbarr P. Murphy
(fpm@fpmurphy.com)

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

ly
CSV (Comma Separated Values).

Here is example of the output in TEXT mode:

on
# ./hdm /dev/sda

se
DEVICE: /dev/sda
----------------
Manufacturer Model: Hitachi HDP725050GLA360
Serial Number: GEA534RF1MUN5A
Firmware Revision: GM4OA52A
lu
Transport Type: SATA Rev 2.6
Maximum RPM: 7200
Capacity: 500GB
a
Number Cylinders: 60801
Partition Type: gpt
nn

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
o

#
rs

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
pe

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
r

partition is a logical volume which is split into a number of filesystems. If you look closely at the
Fo

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</seria
lno><firmware>GM4OA52A</firmware><transport>SATA Rev 2.6</transport><rpm>7200</rpm><capaci
ty>500GB</capacity><geometry><cylinders>60801</cylinders><heads>255</heads><sectors>63</se
ctors></geometry><partitiontype>gpt<paritiontype><partitions><partition number="1"><start>
17.9kB</start><end>210MB</end><size>210MB</size><type>primary</type><filesystem>fat16</fil
esystem><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>50
0GB</end><size>500GB</size><type>primary</type><filesystem></filesystem><label></label><fl
ags>lvm</flags></partition></partitions></disk>[root@ultra hdparm]#

03-10-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 1/12


Hard Disk Metadata

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>

ly
<sectors>63</sectors>
</geometry>
<partitiontype>gpt<paritiontype>

on
<partitions>
<partition number="1">
<start>17.9kB</start>
<end>210MB</end>
<size>210MB</size>

se
<type>primary</type>
<filesystem>fat16</filesystem>
<label></label>
<flags>boot</flags>
lu
</partition>
<partition number="2">
<start>210MB</start>
a
<end>419MB</end>
<size>210MB</size>
nn

<type>primary</type>
<filesystem>ext4</filesystem>
<label></label>
<flags></flags>
o

</partition>
<partition number="3">
rs

<start>419MB</start>
<end>500GB</end>
<size>500GB</size>
pe

<type>primary</type>
<filesystem></filesystem>
<label></label>
<flags>lvm</flags>
</partition>
r

</partitions>
</disk>
Fo

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

03-10-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 2/12


Hard Disk Metadata

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 */

ly
#define HDIO_GET_32BIT 0x0309 /* get current io_32bit setting */
#define HDIO_GET_NOWERR 0x030a /* get ignore-write-error flag */

on
#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 */

se
#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
lu
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
a
/* hd/ide ctl's that pass (arg) non-ptr values are numbered 0x032n/0x033n */
#define HDIO_SET_MULTCOUNT 0x0321 /* change IDE blockmode */
nn

#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 */
o

#define HDIO_SET_DMA 0x0326 /* change use-dma flag */


#define HDIO_SET_PIO_MODE 0x0327 /* reconfig interface to new speed */
rs

#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 */
pe

#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 */
r
Fo

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

03-10-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 3/12


Hard Disk Metadata

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

ly
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

on
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

se
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
lu
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
a
HDIO_GET_ADDRESS to figure out the current addressing mode. I show you 2 ways of
determining the capacity in the get_capacity() routine.
nn

Here is the source code for the utility:


o

/*
rs

* hdm.c - Hard Disk Metadata


*
* Copyright (C) Finnbarr P. Murphy 2010 <fpm[AT]fpmurphy.com>
pe

*
* 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.
r

*
* This program is distributed in the hope that it will be useful,
Fo

* 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

03-10-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 4/12


Hard Disk Metadata

#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

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

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

se
if (ioctl(fd, HDIO_GETGEO, &amp;geometry)) {
perror("ERROR: HDIO_GETGEO failed");
}
return &amp;geometry;
}
lu
void *
get_diskinfo(int fd)
{
a
static __u8 args[4+512];
__u16 *id = (void *)(args + 4);
nn

int i;
memset(args, 0, sizeof(args));
args[0] = ATA_IDENTIFY;
args[3] = 1;
o

if (ioctl(fd, HDIO_DRIVE_CMD, args)) {


args[0] = ATA_PIDENTIFY;
rs

args[1] = 0;
args[2] = 0;
args[3] = 1;
pe

if (ioctl(fd, HDIO_DRIVE_CMD, args)) {


perror("ERROR: HDIO_DRIVE_CMD failed");
return "";
}
}
r

/* byte-swap data to match host endianness */


Fo

for (i = 0; i < 0x100; ++i)


__le16_to_cpus(&amp;id[i]);
return id;
}
//
// Routine currently only handles SATA drives. Extra code needs to be added to support PA
TA, 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 &amp; 0xfff; /* subtype */

03-10-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 5/12


Hard Disk Metadata

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

ly
get_rpm(__u16 id[])
{
static char str[6];

on
__u16 i = id[NMRR];
sprintf(str,"%u", i);
return str;
}
char *

se
ascii_string(__u16 *p,
unsigned int len)
{
__u8 i, c;
char cl;
lu
static char str[60];
char *s = str;
memset(&amp;str, 0, sizeof(str));
a
/* find first character */
for (i = 0; i < len; i++) {
nn

if (( (char)0x00ff &amp; ((*p) >> 8)) != ' ')


break;
if ((cl = (char) 0x00ff &amp; (*p)) != ' ') {
if (cl != '&#92;&#48;') *s++ = cl;
o

p++; i++;
break;
rs

}
p++;
}
pe

/* copy from here to end */


for (; i < len; i++) {
c = (*p) >> 8;
if (c) *s++ = c;
c = (*p);
r

if (c) *s++ = c;
Fo

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(&amp;str, 0, sizeof(str));
if (id[CAPAB] &amp; LBA_SUP) {
if (((id[CMDS_SUPP_1] &amp; VALID) == VALID_VAL) &amp;&amp; (id[CMDS_SUPP_1] &amp;

03-10-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 6/12


Hard Disk Metadata

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, &amp;sectors))) { // bytes
sectors /= sector_bytes;
} else if (!(ioctl(fd, BLKGETSIZE, &amp;sector32))) { // sectors
sectors = sector32;
} else
return "";
#endif
sectors *= (sector_bytes /512);
sectors = (sectors << 9)/1000000;

ly
if (sectors > 1000)
sprintf(str, "%lluGB", (unsigned long long) sectors/1000);
else

on
sprintf(str, "%lluMB", (unsigned long long) sectors);
return str;
}
void
dump_partitions(char *device, int dumpmode, int nlmode)

se
{
PedDevice *dev = (PedDevice *)NULL;
PedDiskType* type;
PedDisk* disk = (PedDisk *)NULL;
PedPartition* part;
lu
PedPartitionFlag flag;
PedUnit default_unit;
int has_free_arg = 0;
a
char *start;
char *end;
nn

char *size;
char flags[100];
const char *partname;
const char *parttype;
o

const char *partlabel;


const char *partflags;
rs

int first_flag;
dev = ped_device_get(device);
if (!ped_device_open (dev)) {
pe

fprintf(stderr, "ERROR: ped-device-opem\n");


exit(1);
}
disk = ped_disk_new(dev);
if (!disk) {
r

fprintf(stderr, "ERROR: ped-disk-new\n");


Fo

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:

03-10-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 7/12


Hard Disk Metadata

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 &amp;&amp; !ped_partition_is_active(part)) ||
part->type &amp; 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 &amp; PED_PARTITION_FREESPACE)) {
parttype = ped_partition_type_get_name (part->type);

ly
partlabel = ped_partition_get_name(part);
} else {
parttype = "";

on
partlabel = "";
}
// flags
memset(&amp;flags, 0, sizeof(flags));
first_flag = 1;

se
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;
lu
} else {
strcat (flags, ", ");
}
a
partflags = ped_partition_flag_get_name(flag);
strcat(flags, partflags);
nn

}
}
switch (dumpmode) {
case DUMPXML:
o

if (nlmode) printf("\n ");


if (part->num >= 0)
rs

printf("<partition number=\"%d\">", part->num);


else
printf("<partition number=\"0\">");
pe

if (nlmode) printf("\n ");


printf("<start>%s</start>", start);
if (nlmode) printf("\n ");
printf("<end>%s</end>", end);
if (nlmode) printf("\n ");
r

printf("<size>%s</size>", size);
Fo

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 : "");

03-10-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 8/12


Hard Disk Metadata

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('"');

ly
printf("%s", partlabel);
putchar('"'); putchar(','); putchar('"');
printf("%s", flags);

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

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

break;
}
}
void
o

dump(char *device)
{
rs

int len = strlen(device) + 8;


int i = 0;
printf("\nDEVICE: %s\n", device);
pe

while(i++ < len) putchar('-');


putchar('\n');
printf("Manufacturer Model: %s\n", ascii_string(&amp;id[27],20));
printf(" Serial Number: %s\n", ascii_string(&amp;id[10],10));
printf(" Firmware Revision: %s\n", ascii_string(&amp;id[23],4));
r

printf(" Transport Type: %s\n", get_transport(id));


Fo

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(&amp;id[27],20));
if (nlmode) printf("\n ");
printf("<serialno>%s</serialno>", ascii_string(&amp;id[10],10));
if (nlmode) printf("\n ");
printf("<firmware>%s</firmware>", ascii_string(&amp;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));

03-10-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 9/12


Hard Disk Metadata

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');

ly
}
void
dumpcsv(char *device)

on
{
putchar('"');
printf("%s", ascii_string(&amp;id[27],20));
putchar('"'); putchar(','); putchar('"');
printf("%s", ascii_string(&amp;id[10],10));

se
putchar('"'); putchar(','); putchar('"');
printf("%s", ascii_string(&amp;id[23],4));
putchar('"'); putchar(','); putchar('"');
printf("%s", get_transport(id));
putchar('"'); putchar(','); putchar('"');
lu
printf("%s", get_rpm(id));
putchar('"'); putchar(','); putchar('"');
printf("%s", get_capacity(fd, id));
a
putchar('"'); putchar(','); putchar('"');
printf("%u", g->cylinders);
nn

putchar('"'); putchar(','); putchar('"');


printf("%u", g->heads);
putchar('"'); putchar(','); putchar('"');
printf("%u", g->sectors);
o

putchar('"');
dump_partitions(device, DUMPCSV, NONEWLINE);
rs

}
void
usage()
pe

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

main(int argc,
Fo

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, &amp;option_index)) != -1)
{
switch (c) {
case 'h':
usage();

03-10-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 10/12


Hard Disk Metadata

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

ly
}
}
if (csvmode &amp;&amp; xmlmode) {

on
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");

se
exit(EXIT_FAILURE);
}
if (geteuid() > 0) {
fprintf(stderr, "ERROR: Must be root to use\n");
exit(EXIT_FAILURE);
lu
}
device = argv[optind];
if ((fd = open(device, O_RDONLY|O_NONBLOCK)) < 0) {
a
fprintf(stderr, "ERROR: Cannot open device %s\n", argv[1]);
exit(EXIT_FAILURE);
nn

}
id = get_diskinfo(fd);
g = get_geometry(fd);
if (ioctl(fd, HDIO_GET_IDENTITY, &amp;hd) < 0 ) {
o

if (errno == -ENOMSG) {
fprintf(stderr, "ERROR: No hard disk identification information available\n");
rs

} else {
perror("ERROR: HDIO_GET_IDENTITY");
exit(1);
pe

}
}
close(fd);
if (csvmode)
dumpcsv(device);
r

else if (xmlmode)
Fo

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.

03-10-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 11/12


Hard Disk Metadata

Enjoy!

ly
on
se
a lu
o nn
rs
pe
r
Fo

03-10-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 12/12