Jump to content

The Partition Table: Difference between revisions

From EDM2
Created page with "Written by Andrew 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 t..."
 
Ak120 (talk | contribs)
mNo edit summary
 
(3 intermediate revisions by 2 users not shown)
Line 1: Line 1:
Written by [[Andrew Pitonyak]]
''Written by [[Andy Pitonyak]]''


===Introduction===
==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 [http://hobbes.nmsu.edu/download/pub/os2/system/drivers/filesys/os2fat32.zip 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).
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 http://ourworld.compuserve.com/homepages/hkelder/. 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/  http://www.uruk.org/~erich/], look for the GRUB stuff by Erich Boleyn ([mailto:erich@uruk.org  erich@uruk.org]).


[Note: [http://www.edm2.com/0603/drive.zip here] is a link to the source code for this article. Ed.]
[Note: [http://www.edm2.com/0603/drive.zip here] is a link to the source code for this article. Ed.]


===Reading The Partition Table===
==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
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,
  <small>
            data, szData, &szDataU);
  DosDevIOCtl(handle, 9L, 0x64, parmList, parmSize, &szParam,
              data, szData, &szDataU);
</small>
 
I had some difficulties dealing with the parameter list so I encapsulated it in the class IOCtlParam. The structure is as follows:
I had some difficulties dealing with the parameter list so I encapsulated it in the class IOCtlParam. The structure is as follows:
 
<pre>
<small>
Size Description
  Size Description
Byte Command Information
  Byte Command Information
Word Head
  Word Head
Word Cylinder
  Word Cylinder
Word First Sector
  Word First Sector
Word Number of Sectors
  Word Number of Sectors
Bytes Track Layout Tables
  Bytes Track Layout Tables
</pre>
</small>
 
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:
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:
 
<pre>
<small>
command = 1; // Assume consecutive
  command = 1; // Assume consecutive
head = 0;
  head = 0;
cylinder = 0;
  cylinder = 0;
firstSector = 0;
  firstSector = 0;
numSectorsToDo = 1;
  numSectorsToDo = 1;
for (WORD i=0; i<numSectors(); ++i) {
  for (WORD i=0; i<numSectors(); ++i) {
    bytes[2*i] = i+1;
      bytes[2*i] = i+1;
    bytes[2*i+1] = 512;
      bytes[2*i+1] = 512;
}
  }
</pre>
</small>
 
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.
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 primary boot partition is located at head zero, cylinder zero, sector zero which is the first sector on the drive.


===The Partition Table===
==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 <physical drive> <first sector> <number of sectors>". To see the partition table, type the following:
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 <physical drive> <first sector> <number of sectors>". To see the partition table, type the following:
 
<pre>
<small>
[d:\myos2\devsrc\myprogs\drive\edm]driveinf DUMP 1 0 1
  [d:\myos2\devsrc\myprogs\drive\edm]driveinf DUMP 1 0 1
   
   
  Drive 1: sector 0
Drive 1: sector 0
   
   
      0 - fa b8 30 00 8e d0 bc 00 01 fb fc b8 00 00 8e d8
    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
  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
  20 - 50 c3 be cf 7e bb be 7f 80 7f 04 0a 74 45 83 c3
  <... data deleted ...>
<... data deleted ...>
    190 - 7c bf 05 00 b8 00 00 cd 13 b8 01 02 cd 13 73 03
  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
  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
  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
  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
  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
  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
  1f0 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 aa
  [d:\myos2\devsrc\myprogs\drive\edm]
[d:\myos2\devsrc\myprogs\drive\edm]
</small>
</pre>
 
The actual layout of the partition table is as follows:
The actual layout of the partition table is as follows:
 
<pre>
<small>
Offset Description
  Offset Description
0x000 Boot code
  0x000 Boot code
0x1BE 16 BYTE Entry in the partition table
  0x1BE 16 BYTE Entry in the partition table
0x1CE 16 BYTE Entry in the partition table
  0x1CE 16 BYTE Entry in the partition table
0x1DE 16 BYTE Entry in the partition table
  0x1DE 16 BYTE Entry in the partition table
0x1EE 16 BYTE Entry in the partition table
  0x1EE 16 BYTE Entry in the partition table
0x1FE 2 BYTE IDcode(0xAA55) identifies partition sector
  0x1FE 2 BYTE IDcode(0xAA55) identifies partition sector
</pre>
</small>
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:
 
<pre>
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/ http://www.uruk.org/~erich/]. Each table entry is represented as follows:
Offset Description
 
BYTE Partition status, 0x80 = Active, 0x00 = inactive
<small>
BYTE First head used by partition
  Offset Description
BYTE[2] First sector and cylinder used by partition
  BYTE Partition status, 0x80 = Active, 0x00 = inactive
BYTE Partition type
  BYTE First head used by partition
BYTE Last head used by partition
  BYTE[2] First sector and cylinder used by partition
BYTE[2] Last sector and cylinder used by partition
  BYTE Partition type
ULONG Location of boot sector
  BYTE Last head used by partition
ULONG Number of sectors for partition
  BYTE[2] Last sector and cylinder used by partition
</pre>
  ULONG Location of boot sector
  ULONG Number of sectors for partition
</small>
 
These items are encapsulated with the following structures.
These items are encapsulated with the following structures.
 
<pre>
<small>
typedef struct {
  typedef struct {
    BYTE  status_;
      BYTE  status_;
    BYTE  startHead_;
      BYTE  startHead_;
    BYTE  startSecCyl_[2];
      BYTE  startSecCyl_[2];
    BYTE  type_;
      BYTE  type_;
    BYTE  endHead_;
      BYTE  endHead_;
    BYTE  endSecCyl_[2];
      BYTE  endSecCyl_[2];
    ULONG bootSec_;
      ULONG bootSec_;
    ULONG numSec_;
      ULONG numSec_;
} PartitionEntryStruct;
  } PartitionEntryStruct;
   
   
  typedef struct {
typedef struct {
      BYTE  bootCode_[0x1BE];
    BYTE  bootCode_[0x1BE];
      PartitionEntryStruct entries_[4];
    PartitionEntryStruct entries_[4];
      WORD  iDCode_;
    WORD  iDCode_;
  } PartitionSectorStruct;
} PartitionSectorStruct;
</small>
</pre>
 
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:
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:
 
<pre>
<small>
Status Hd sec/cyl Typ Hd sec/cyl Boot Sector Num Sectors
  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  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
    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
    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
    00  00  00 00  00  00  00 00  00 00 00 00 00 00 00 00
</pre>
</small>
 
Which correspond to:
Which correspond to:
 
<pre>
<small>
Partition at offset 0
  Partition at offset 0
Stat Type                Head Cyl  Sect Head Cyl  Sect Boot Sct Num Sct
  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 0x06=DOS >32M          1    1    1  254  261  63    16128  4192902
0x00 0x05=Extended          0  262    1  254  527  63  4209030  4273290
  0x00 0x05=Extended          0  262    1  254  527  63  4209030  4273290
0x80 0x0a=Boot Mgr          1    0    1  254    0  63      63    16002
  0x80 0x0a=Boot Mgr          1    0    1  254    0  63      63    16002
0x00 0x00=Empty            0    0    0    0    0    0        0        0
  0x00 0x00=Empty            0    0    0    0    0    0        0        0
</pre>
</small>
 
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:
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:
 
<pre>
<small>
Value Type                Value Type
  Value Type
0x00 Empty               0x07   OS/2 HPFS
  0x00 Empty
0x01 FAT-12              0x0A   Boot Manager
  0x01 FAT-12
0x02 XENIX              0x0B   FAT-32
  0x02 XENIX
0x03 XENIX              0x64   Novell
  0x03 XENIX
0x04 FAT-16              0x75   PCIX
  0x04 FAT-16
0x05 Extended Partition  0xDB   CPM/Concurrent
  0x05 Extended Partition
0x06 DOS >32Meg          0xFF   BBT
  0x06 DOS >32Meg
</pre>
  0x07 OS/2 HPFS
  0x0A Boot Manager
  0x0B FAT-32
  0x64 Novell
  0x75 PCIX
  0xDB CPM/Concurrent
  0xFF BBT
</small>
 
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.
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.


Line 166: Line 134:


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.
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.
 
<pre>
<small>
printPartitionInformation(absoluteSector, offset) {
  printPartitionInformation(absoluteSector, offset) {
  table = readSector(absoluteSector);
    table = readSector(absoluteSector);
  PrintThisInformation(table);
    PrintThisInformation(table);
  for (i=0; i<4; ++i)
    for (i=0; i<4; ++i)
    if (table->entries_[i].type_ == 0x05) {
      if (table->entries_[i].type_ == 0x05) {
      sect = table->entries[i].bootSec_;
        sect = table->entries[i].bootSec_;
      newOffset = offset == 0 ? sect : offset;
        newOffset = offset == 0 ? sect : offset;
      printPartitionInformation(sect + offset, newOffset);
        printPartitionInformation(sect + offset, newOffset);
    }
      }
}
  }
</pre>
</small>
 
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.
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===
===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
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)
  <small>
  printErrorMessage(APIRET error, char *msg)
</small>
 
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.
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.


Line 197: Line 158:


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:
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
  <small>
  bcc driveinf.cpp errPrnt.cpp
</small>
 
or
or
 
  icc /sp1 driveinf.cpp errPrnt.cpp
  <small>
  icc /sp1 driveinf.cpp errPrnt.cpp
</small>
 
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.
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.
After the program has been compiled, you can determine how many partitionable drives are present in the system.
 
  [d:\test]driveinf num physical
  <small>
  Number of partitionable drives = 2
  [d:\test]driveinf num physical
   
  Number of partitionable drives = 2
</small>
 
You can print the partition tables for a physical drive on the system.
You can print the partition tables for a physical drive on the system.
 
<pre>
<small>
[d:\test]driveinf partition print 2:
  [d:\test]driveinf partition print 2:
   
   
  Partition info for physical drive 2
Partition info for physical drive 2
  *********************************************************
*********************************************************
   
   
  Partition at offset 0
Partition at offset 0
  Stat Type                Head Cyl  Sect Head Cyl  Sect Boot Sct Num Sct
Stat Type                Head Cyl  Sect Head Cyl  Sect Boot Sct Num Sct
  ==== ==================== ==== ==== ==== ==== ==== ==== ======== ========
==== ==================== ==== ==== ==== ==== ==== ==== ======== ========
  0x00 0x05=Extended          0    1    1  63  531  32    2048  1087488
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
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
Partition at offset 2048
  Stat Type                Head Cyl  Sect Head Cyl  Sect Boot Sct Num Sct
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 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
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
</small>
</pre>
 
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.
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.
<pre>
 
[d:\test]driveinf.exe params physical 1:
<small>
  [d:\test]driveinf.exe params physical 1:
   
   
  *********************************************************
*********************************************************
  queryPhysicalDeviceParams() for physical drive 1
queryPhysicalDeviceParams() for physical drive 1
   
   
  cyl = 0 heads = 0 sect = 0
cyl = 0 heads = 0 sect = 0
   
   
  Error: (5) ACCESS_DENIED
Error: (5) ACCESS_DENIED
    from queryPhysicalDeviceParams()
  from queryPhysicalDeviceParams()
  [d:\test]driveinf.exe params physical 2:
[d:\test]driveinf.exe params physical 2:
   
   
  *********************************************************
*********************************************************
  queryPhysicalDeviceParams() for physical drive 2
queryPhysicalDeviceParams() for physical drive 2
   
   
  cyl = 528 heads = 255 sect = 63
cyl = 528 heads = 255 sect = 63
</small>
</pre>
 
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
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
  <small>
  driveinf DUMP 1 0 1
</small>
 
which reads from the first logical drive numbered 1, the first sector numbered 0, and one sector is read.
which reads from the first logical drive numbered 1, the first sector numbered 0, and one sector is read.


===Wrap-up===
===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.


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 harddisks; 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.
 
If you have any questions or comments on the code, drop me a line at [mailto:andyp@primatech.com  andyp@primatech.com].
 


[[Image:Category:Miscellaneous Articles]]
[[Category:Miscellaneous Articles]]

Latest revision as of 22:50, 18 October 2018

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 <physical drive> <first sector> <number of sectors>". 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.