Documentos de Académico
Documentos de Profesional
Documentos de Cultura
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).
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
#
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:
# ./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
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.
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
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.”
__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
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
/*
rs
*
* 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
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, &geometry)) {
perror("ERROR: HDIO_GETGEO failed");
}
return &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
args[1] = 0;
args[2] = 0;
args[3] = 1;
pe
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 *
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(&str, 0, sizeof(str));
a
/* find first character */
for (i = 0; i < len; i++) {
nn
p++; i++;
break;
rs
}
p++;
}
pe
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(&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;
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
int first_flag;
dev = ped_device_get(device);
if (!ped_device_open (dev)) {
pe
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:
ly
partlabel = ped_partition_get_name(part);
} else {
parttype = "";
on
partlabel = "";
}
// flags
memset(&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
printf("<size>%s</size>", size);
Fo
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
ly
}
void
dumpcsv(char *device)
on
{
putchar('"');
printf("%s", ascii_string(&id[27],20));
putchar('"'); putchar(','); putchar('"');
printf("%s", ascii_string(&id[10],10));
se
putchar('"'); putchar(','); putchar('"');
printf("%s", ascii_string(&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('"');
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, &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);
ly
}
}
if (csvmode && 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, &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.
Enjoy!
ly
on
se
a lu
o nn
rs
pe
r
Fo