The Partition Table

Written by Andy Pitonyak

Introduction
I was becoming annoyed that I could not access my FAT32 partition from within OS/2 and I decided that I would write a driver that would allow me to do this. I have not come close to accomplishing this but Henk Kelder has, so if you have any interest in this then check out his beta driver at os2fat32.zip. I did, however, start poking around my hard drive and studying my partition tables. I would like to say that this was an easy process, but it was not, so I decided to share my experiences with others to hopefully save them the aggravation that I faced. Another good resource can be found at http://www.uruk.org/~erich/, look for the GRUB stuff by Erich Boleyn (erich@uruk.org).

[Note: here is a link to the source code for this article. Ed.]

Reading The Partition Table
Before worrying about interpreting the partition table, I had to be able to read it. After browsing through the online help which came with the Borland compiler, I opted to use DosDevIOCtl category 9, function 0x64h, read sectors. The call is essentially DosDevIOCtl(handle, 9L, 0x64, parmList, parmSize, &szParam,            data, szData, &szDataU); I had some difficulties dealing with the parameter list so I encapsulated it in the class IOCtlParam. The structure is as follows: Size	Description Byte	Command Information Word	Head Word	Cylinder Word	First Sector Word	Number of Sectors Bytes	Track Layout Tables The command information must be entirely zero except for the first bit where one means that the sectors are stored consecutively starting with sector one and a zero means that at least one of these conditions is not true. I must admit that I have no idea how to determine what the track layout is, and I always assume that the sector size is 512 bytes. Although these values have worked on every system which I have tried, the existence of the variable parameters implies to me that this is not a safe assumption. Further information on this will be gladly accepted but this is not the thrust of this article. My initialization code is similar to the following: command = 1; // Assume consecutive head = 0; cylinder = 0; firstSector = 0; numSectorsToDo = 1; for (WORD i=0; i<numSectors; ++i) { bytes[2*i] = i+1; bytes[2*i+1] = 512; } Note that the DosDevIOCtl function does not move the heads so you can not read more than a single track worth of information with one command.

The primary boot partition is located at head zero, cylinder zero, sector zero which is the first sector on the drive.

The Partition Table
So now it is time to read the partition table. The program which I wrote, contains the ability to dump physical sectors on the hard drive. Note that physical drives are accessed with the numbers where drive 1 refers to the first physical drive, usually drive C:. The parameters to obtain a dump include "dump  ". To see the partition table, type the following: [d:\myos2\devsrc\myprogs\drive\edm]driveinf DUMP 1 0 1 Drive 1: sector 0 0 - fa b8 30 00 8e d0 bc 00 01 fb fc b8 00 00 8e d8  10 - 8e c0 be 00 7c bf 00 7e b9 00 02 f3 a5 b8 22 7e 20 - 50 c3 be cf 7e bb be 7f 80 7f 04 0a 74 45 83 c3 <... data deleted ...> 190 - 7c bf 05 00 b8 00 00 cd 13 b8 01 02 cd 13 73 03 1a0 - 4f 7f f1 c3 00 00 00 00 00 00 00 00 00 00 00 00 1b0 - 00 00 00 00 00 00 01 00 00 00 00 00 33 cc 00 01 1c0 - 01 01 06 fe 7f 05 00 3f 00 00 86 fa 3f 00 00 00 1d0 - 41 06 05 fe bf 0f 86 39 40 00 8a 34 41 00 80 01 1e0 - 01 00 0a fe 3f 00 3f 00 00 00 82 3e 00 00 00 00 1f0 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 aa [d:\myos2\devsrc\myprogs\drive\edm] The actual layout of the partition table is as follows: Offset	Description 0x000	Boot code 0x1BE	16 BYTE Entry in the partition table 0x1CE	16 BYTE Entry in the partition table 0x1DE	16 BYTE Entry in the partition table 0x1EE	16 BYTE Entry in the partition table 0x1FE	2 BYTE IDcode(0xAA55) identifies partition sector For my purposes, the first 0x1BE bytes are not very interesting since I am not currently looking at the boot code. If you have any interest in this, check out http://www.uruk.org/~erich/. Each table entry is represented as follows: Offset	 Description BYTE	 Partition status, 0x80 = Active, 0x00 = inactive BYTE	 First head used by partition BYTE[2] First sector and cylinder used by partition BYTE	 Partition type BYTE	 Last head used by partition BYTE[2] Last sector and cylinder used by partition ULONG	 Location of boot sector ULONG	 Number of sectors for partition These items are encapsulated with the following structures. typedef struct { BYTE status_; BYTE startHead_; BYTE startSecCyl_[2]; BYTE type_; BYTE endHead_; BYTE endSecCyl_[2]; ULONG bootSec_; ULONG numSec_; } PartitionEntryStruct; typedef struct { BYTE bootCode_[0x1BE]; PartitionEntryStruct entries_[4]; WORD iDCode_; } PartitionSectorStruct; The order that the partition table entries appear in the partition table is not defined, so do not assume that they appear in any particular order. The example shown above has the following values: Status Hd sec/cyl Typ Hd sec/cyl Boot Sector Num Sectors ====== == ======= === == ======= =========== ===========  00   01  01 01  06  fe  7f 05  00 3f 00 00 86 fa 3f 00 00  00  41 06  05  fe  bf 0f  86 39 40 00 8a 34 41 00 80  01  01 00  0a  fe  3f 00  3f 00 00 00 82 3e 00 00 00  00  00 00  00  00  00 00  00 00 00 00 00 00 00 00 Which correspond to: Partition at offset 0 Stat Type                Head Cyl  Sect Head Cyl  Sect Boot Sct Num Sct ==== ==================== ==== ==== ==== ==== ==== ==== ======== ======= 0x00 0x06=DOS >32M         1    1    1  254  261   63    16128  4192902 0x00 0x05=Extended         0  262    1  254  527   63  4209030  4273290 0x80 0x0a=Boot Mgr         1    0    1  254    0   63       63    16002 0x00 0x00=Empty            0    0    0    0    0    0        0        0 The status byte of the partition can have two values, 0, which means the partition is not the active primary partition, and 0x80 (high bit set) which means that this is the active primary partition. The next byte indicates on which head the partition starts. The next two bytes indicate the sector and the cylinder in a compressed format. The sector is held in the first six bits (bits 0 through five) and bits 6 through 15 contain the cylinder. The sector is given by (*startSecCyl_) & (0x3F) and the packed cylinder is obtained with (startSecCyl_[1]+((startSecCyl_[0] & 0xC0) << 2). The type byte indicates what the partition really is. I do not know what all of the valid values are, but I have gleaned the following types: Value	Type               Value  Type 0x00	Empty               0x07   OS/2 HPFS 0x01	FAT-12              0x0A   Boot Manager 0x02	XENIX               0x0B   FAT-32 0x03	XENIX               0x64   Novell 0x04	FAT-16              0x75   PCIX 0x05	Extended Partition  0xDB   CPM/Concurrent 0x06	DOS >32Meg          0xFF   BBT The next three bytes indicate the end head, sector, and cylinder of the partition. The following four bytes contain the boot sector and the final four contain a count on the number of sectors used.

Although this is not required, partitions generally start with head 0 and sector 1. This leaves some unused sectors. These extra sectors can hide either a boot sector virus or boot manager code.

An extended partition table can contain as many as two secondary partition entries (logical drives), and two extended partitions. If there are three partitions inside of a single extended partition, then extra partition tables will be used. These extra partition tables will be referenced as extended partitions inside of an existing extended partition.

In the primary partition table, the boot sector indicates where the extended partition is located (for extended partitions). If the entry is already inside of an extended partition then the referenced extended partition is at the location of the stated boot sector plus the offset of the main enclosing extended partition. Consider the following pseudo code to traverse the partition tables. printPartitionInformation(absoluteSector, offset) { table = readSector(absoluteSector); PrintThisInformation(table); for (i=0; i<4; ++i) if (table->entries_[i].type_ == 0x05) { sect = table->entries[i].bootSec_; newOffset = offset == 0 ? sect : offset; printPartitionInformation(sect + offset, newOffset); } } An initial call of printPartitionInformation(0, 0) gets things started. Note that for the first partition table, the offset is zero. The offset is then changed once to the start of the first partition table. Any subsequent embedded partition tables will still use the same offset.

Source Files
I created five source files which I have attempted to document. I wanted to have a nice method of printing error messages so I created errprnt.hpp and errprnt.cpp. These files contain a single function printErrorMessage(APIRET error, char *msg) This routine accepts a standard error number and it prints both the associated text message and a supplied text message. By using this routine, I was not forced to continually lookup error codes.

The file drvstrct.hpp contains structures which are useful for certain low level routines. This includes the PartitionEntryStruct and the PartitionSectorStruct for example.

DriveInf.hpp and DriveInf.cpp contain the primary code used for these examples.

I used the Borland C++ compiler for OS/2 version 2. Strangely, it reports itself as Version 4.11. I use the following command line to compile my code: bcc driveinf.cpp errPrnt.cpp or icc /sp1 driveinf.cpp errPrnt.cpp I lost my Visual Age stuff during a move from Germany, so I do not have my documentation, and thus I have not done much testing with the IBM Visual Age compiler. If you want to use this with the IBM compiler, be certain to use the /SP1 compile option to align the data on 1 byte boundaries as opposed to the default four byte boundaries. You could also use compiler directives.

After the program has been compiled, you can determine how many partitionable drives are present in the system. [d:\test]driveinf num physical Number of partitionable drives = 2 You can print the partition tables for a physical drive on the system. [d:\test]driveinf partition print 2: Partition info for physical drive 2 ********************************************************* Partition at offset 0 Stat Type                Head Cyl  Sect Head Cyl  Sect Boot Sct Num Sct ==== ==================== ==== ==== ==== ==== ==== ==== ======== ======== 0x00 0x05=Extended         0    1    1   63  531   32     2048  1087488 0x00 0x00=Empty            0    0    0    0    0    0        0        0 0x00 0x00=Empty            0    0    0    0    0    0        0        0 0x00 0x00=Empty            0    0    0    0    0    0        0        0 Partition at offset 2048 Stat Type                Head Cyl  Sect Head Cyl  Sect Boot Sct Num Sct ==== ==================== ==== ==== ==== ==== ==== ==== ======== ======== 0x00 0x0b=FAT-32           1    1    1   63  531   32       32  1087456 0x00 0x00=Empty            0    0    0    0    0    0        0        0 0x00 0x00=Empty            0    0    0    0    0    0        0        0 0x00 0x00=Empty            0    0    0    0    0    0        0        0 There is also an option to query the device parameters for a physical drive. This uses DosDevIOCtl category 9 function 0x63. This will fail if the drive can not be locked. [d:\test]driveinf.exe params physical 1: ********************************************************* queryPhysicalDeviceParams for physical drive 1 cyl = 0 heads = 0 sect = 0 Error: (5) ACCESS_DENIED from queryPhysicalDeviceParams [d:\test]driveinf.exe params physical 2: ********************************************************* queryPhysicalDeviceParams for physical drive 2 cyl = 528 heads = 255 sect = 63 There is also a dump command which takes three parameters, the logical drive, the first sector to read and the number of sectors. An example of this is shown early on as driveinf DUMP 1 0 1 which reads from the first logical drive numbered 1, the first sector numbered 0, and one sector is read.

Wrap-up
The code samples are pretty straight forward and should give you a good start on reading and interpreting your own partition tables. In the development of this code, I used the Gamma Tech Utilities to poke around my hard disks; this is highly recommended. I also used the book "PC Intern: The Encyclopedia of System Programming", the sixth edition by Michael Tischer and Bruno Jennrich published by Abacus.

If you have any questions or comments on the code, drop me a line at andyp@primatech.com.