A disk may be split into several smaller logical units, each of which appear as a separate disk to the user. Even if a hard disk only has a single partition it will contain a partition map, which lists the one or more partitions that have been created from the disk. Mac OS X provides support for many common partition schemes, including the GUID partition scheme that is the default on Mac OS X, the Master Boot Record that is still common on Windows, and the Apple Partition Map, which was the default partition scheme for Mac OS before the transition to Intel-based Macs.
Support for new partition schemes can be added to Mac OS X by writing an I/O Kit driver that is derived from the IOPartitionScheme
class. A partition scheme driver loads whenever a disk is inserted, and scans the disk for a partition table that it recognizes. If a partition table that is supported by the driver is found, it creates an IOMedia
object for each entry in the partition table, and attaches these IOMedia
objects above it on the driver storage stack. The IOMedia
objects created by the partition driver will describe only the section of the disk that is covered by the partition, and not the entire disk contents.
An IOMedia
object can describe either the entire disk or a single partition consisting of a physically contiguous subset of blocks on the disk. When an IOMedia
object is instantiated, its constructor takes a property that identifies whether the IOMedia
object describes the entire disk or not. Although a partition driver will load against an IOMedia
object, it will only load against one that describes the entire disk contents, because typically a partition table is not located within a partition.
After the partition scheme driver has successfully scanned a disk, the end result is the construction of a storage driver stack similar to that shown in Figure 14-7, with an IOMedia
object that describes the entire disk's contents (the object above the IOBlockStorageDriver
in the driver stack) and an IOMedia
object for each partition (the object above the IOGUIDPartitionTableScheme
in the driver stack).
It is worth noting that a partition scheme driver is responsible only for reading a partition table that already exists on the disk; the IOPartitionScheme
class contains no methods for writing a partition table. A partition table could be created by providing a user space utility process that writes to the disk device directly.
In this section we will examine how the driver for a hypothetical partition map would be implemented in the I/O Kit. To begin with, let's examine the property list of the driver, in particular its matching dictionary, as shown in Listing 14-8.
Listing 14-8. A Sample Matching Dictionary from the Property List of a Partition Scheme Driver
<key>IOKitPersonalities</key>
<dict>
<key>SamplePartitionScheme</key>
<dict>
<key>CFBundleIdentifier</key>
<string>com.osxkernel.SamplePartitionScheme</string>
<key>IOClass</key>
<string>com_osxkernel_driver_SamplePartitionScheme</string>
<key>IOMatchCategory</key>
<string>IOStorage</string>
<key>IOProviderClass</key>
<string>IOMedia</string>
<key>IOPropertyMatch</key>
<dict>
<key>Whole</key>
<true/>
</dict>
</dict>
</dict>
There are three important aspects of the matching dictionary:
IOMedia
, so whenever a new disk is inserted (and an IOMedia
object is created to represent that disk), the partition driver will be given the chance to examine the contents of that disk for a supported partition table.IOMedia
object that represents the entire disk, since partition tables cannot be located within a disk partition. To narrow the match to IOMedia
objects that represent an entire disk only, the matching dictionary uses the IOPropertyMatch
key to specify that the I/O Kit should load the driver only against an IOMedia
object that contains a property named “Whole” with the Boolean value of true. This is a standard property of IOMedia
objects that specifies whether the object covers the entire disk, or a partition of that disk.IOMatchCategory
of IOStorage
. This property appears in the property table of the partition driver, and is important for the correct construction of the storage driver stack. In particular, certain drivers will use the IOMatchCategory
property to determine whether they are at the top of the driver stack, or whether the driver on top is also a part of the IOStorage
stack.Although a partition scheme driver is part of the storage driver stack, and is unloaded only after the disk is removed, the driver itself plays a role only when the disk is first inserted, when it is responsible for reading the partition table from the disk and instantiating an IOMedia
object for each partition that it finds. As a driver that is derived from the standard I/O Kit class IOService
, the partition scheme driver does this through the init()
, probe()
, and start()
methods and on unloading through the stop()
and free()
methods.
The probe()
method is of particular significance for an IOPartitionScheme
driver. A partition scheme driver will be instantiated whenever a disk is added to the system, and it is up to the driver to determine whether the disk contains a supported partition table and, if not, to allow an IOPartitionScheme
driver that is better suited to load instead. This is done through the standard IOService
method probe()
. In general, the purpose of the probe()
method is to examine the hardware to determine whether the driver is able to support the device, and if so, to return an integer value that represents how well suited the driver is to the hardware. The driver with the highest probe score is the one that will be loaded by the I/O Kit.
In the case of an IOPartitionScheme
driver, the role of the probe()
method is to read enough of the disk to determine whether the partition table on the disk is supported by the driver and, if so, to go on to read the partition table entries. It isn't strictly necessary to read the entire partition table in the probe()
method, but doing so prevents the need to rescan the partition table when the driver's start()
method is called. The partition scheme drivers provided by Apple as a part of Mac OS X go one step further and actually instantiate an IOMedia
object for each partition that is found in the probe()
method.
If a disk contains a partition table that the partition scheme driver recognizes and the partition driver is selected by the I/O Kit as the most suitable driver, its start()
method will be called. At this point, the partition scheme driver should create an IOMedia
object for each partition entry and attach it to the storage driver stack.
A sample implementation of the probe()
and start()
methods is demonstrated in Listing 14-9. This driver is based on the partition scheme drivers that are included as part of the Darwin source code in the IOStorage
family.
As with the partition drivers included in Darwin, the implementation in Listing 14-9 has a custom method named scan()
to examine the disk, and if a supported partition table is found, to instantiate an IOMedia
object for each partition and return the partition set to the caller through an OSSet
object. If no IOMedia
objects were found during the scan, the probe()
method returns unsuccessfully, as indicated by a NULL result value, and the I/O Kit will continue searching for another partition scheme driver for the disk. If a supported partition table was found instead, the probe method saves the set of IOMedia
objects representing each partition to an instance variable name d m_partitions
.
Listing 14-9. An Implementation of the probe()
and start()
Methods for a Partition Scheme Driver
#include <IOKit/storage/IOPartitionScheme.h>
// Define the superclass
#define super IOPartitionScheme
OSDefineMetaClassAndStructors(com_osxkernel_driver_PartitionScheme, IOPartitionScheme)
IOService* com_osxkernel_driver_PartitionScheme::probe(IOService* provider, SInt32* score)
{
if (super::probe(provider, score) == NULL)
return NULL;
// Scan the IOMedia for a supported partition table.
m_partitions = scan(score);
// If no partition table was found, return NULL.
return m_partitions ? this : NULL;
}
bool com_osxkernel_driver_PartitionScheme::start (IOService *provider)
{
IOMedia* partition;
OSIterator* partitionIterator;
if (super::start(provider) == false)
return false;
// Create an iterator for the IOMedia objects that were
// found and instantiated during probe.
partitionIterator = OSCollectionIterator::withCollection(m_partitions);
if (partitionIterator == NULL)
return false;
// Attach and register each IOMedia object (representing found partitions).
while ((partition = (IOMedia*)partitionIterator->getNextObject()))
{
if (partition->attach(this))
{
attachMediaObjectToDeviceTree(partition);
partition->registerService();
}
}
partitionIterator->release();
return true;
}
If the probe()
method returns successfully and the I/O Kit doesn't find a better driver, our partition driver will be added to the storage stack and its start()
method will be called. The role of a partition driver's start()
method is to attach each of its IOMedia
objects to the storage driver stack, where each IOMedia
object represents a single partition entry on the disk. This is done through the IOService
method named attach()
, which inserts the IOMedia
object into the service plane of the I/O Registry as a child of the partition driver (which is the provider class).
As well as inserting the IOMedia
object into the service plane of the I/O Registry, it may also be necessary to insert the IOMedia
object into the device plane of the I/O Registry. This is only needed if the partition could potentially be used as the boot volume on a PowerPC-based Macintosh. This is because the boot volume on a PowerPC-based Macintosh is identified through its location in the I/O Registry device plane, so the IOMedia
object that represents the boot partition needs to have an entry in the device plane. The IOPartitionScheme
superclass provides a method named attachMediaObjectToDeviceTree()
, which will insert an IOMedia object into the I/O Registry's device plane.
The scan()
method is a custom method that determines whether the disk contains a partition scheme that is supported by the driver and, if so, reads the partition table entries from the disk and creates a set of IOMedia
objects that represent each partition. This requires the partition driver to be able to read the disk, which is performed through the driver's provider object. As specified in the driver's matching dictionary (see Listing 14-8), the partition driver's provider class is an IOMedia
object that represents the entire disk. An example implementation of the scan()
method is provided in Listing 14-10.
Listing 14-10. A Method to Detect the Presence of a Sample Partition Table on a Disk and to Instantiate IOMedia Objects for that Partition Table
OSSet* com_osxkernel_driver_PartitionScheme::scan(SInt32* score)
{
IOBufferMemoryDescriptor* buffer = NULL;
SamplePartitionTable* sampleTable;
1 IOMedia* media = getProvider();
UInt64 mediaBlockSize = media->getPreferredBlockSize();
bool mediaIsOpen = false;
OSSet* partitions = NULL;
IOReturn status;
// Determine whether this media is formatted.
2 if (media->isFormatted() == false)
goto bail;
// Allocate a sector-sized buffer to hold data read from disk.
3 buffer = IOBufferMemoryDescriptor::withCapacity(mediaBlockSize, kIODirectionIn);
if (buffer == NULL)
goto bail;
// Allocate a set to hold the media objects representing disk partitions.
4 partitions = OSSet::withCapacity(8);
if (partitions == NULL)
goto bail;
// Open the storage driver stack that (of which this partition driver is part)
// for read access.
5 mediaIsOpen = open(this, 0, kIOStorageAccessReader);
if (mediaIsOpen == false)
goto bail;
// Read the first sector of the disk.
6 status = media->read(this, 0, buffer);
if (status != kIOReturnSuccess)
goto bail;
sampleTable = (SamplePartitionTable*)buffer->getBytesNoCopy();
// Determine whether the first sector contains our recognized partition signature.
7 if (strcmp(sampleTable->partitionIdentifier, kSamplePartitionIdentifier) != 0)
goto bail;
// Scan for valid partition entries in the partition map.
8 for (int index = 0; index < sampleTable->partitionCount; index++)
{
9 if (isPartitionInvalid(&sampleTable->partitionEntries[index]))
continue;
IOMedia* newMedia;
10 newMedia = instantiateMediaObject(&sampleTable->partitionEntries[index],
1+index);
if ( newMedia )
{
partitions->setObject(newMedia);
newMedia->release();
}
}
// Release temporary resources.
11 close(this);
buffer->release();
return partitions;
bail:
// Non-successful return; release all allocated objects.
12 if ( mediaIsOpen ) close(this);
if ( partitions ) partitions->release();
if ( buffer ) buffer->release();
return NULL;
}
Corresponding to the numbered lines in Listing 14-10, the following is an overview of the steps performed in the listing:
IOMedia
object that represents the entire disk. All disk reads are performed through this object, which includes reading the partition table off the disk.IOMemoryDescriptor
as the destination. We therefore allocate an IOBufferMemoryDescriptor
to hold the contents of the data that this method will read from the disk. Since this memory descriptor will be used in an operation that reads data from the disk, its direction must be set to kIODirectionIn
.OSSet
container to hold the collection of IOMedia
objects that represent each partition that is found on the disk. Although the initial capacity of the OSSet
collection is 8 objects, the OSSet
will automatically expand if more than 8 IOMedia
objects are inserted.read()
method specifies both the destination for the data that is read, and the number of bytes to read.strcmp()
function to determine whether this string exists, and if it cannot be found, it assumes that another partition scheme exists on the disk and returns unsuccessfully.IOMedia
object is instantiated to represent the partition entry.open()
that was made earlier), and the set of IOMedia
objects is returned to the caller.The implementation of the instantiateMediaObject()
method that is called as a part of Step 10 is provided in Listing 14-11. This is a custom method that is defined by our partition scheme driver.
Listing 14-11. A Method to Instantiate IOMedia
Objects That Represent an Individual Disk Partition
IOMedia* com_osxkernel_driver_PartitionScheme::instantiateMediaObject
(SamplePartitionEntry* sampleEntry, int index)
{
IOMedia* media = getProvider();
UInt64 mediaBlockSize = media->getPreferredBlockSize();
IOMedia* newMedia;
1 newMedia = new IOMedia;
if ( newMedia )
{
UInt64 partitionBase, partitionSize;
2 partitionBase = OSSwapLittleToHostInt64(sampleEntry->blockStart) *
mediaBlockSize;
partitionSize = OSSwapLittleToHostInt64(sampleEntry->blockCount) *
mediaBlockSize;
3 if ( newMedia->init(partitionBase, partitionSize, mediaBlockSize,
media->getAttributes(), false, media->isWritable()))
{
4 // Set a name for this partition.
newMedia->setName(sampleEntry->name);
// Set a location value (the partition number) for this partition.
char location[12];
snprintf(location, sizeof(location), "%d", index);
newMedia->setLocation(location);
// Set the "Partition ID" key for this partition.
newMedia->setProperty(kIOMediaPartitionIDKey, index, 32);
}
else
{
5 newMedia->release();
newMedia = NULL;
}
}
6 return newMedia;
}
Corresponding to the numbered lines in Listing 14-11, the following is an overview of the steps performed in the listing:
IOMedia
object is allocated using the C++ “new
” operator.OSSwapLittleToHostInt64()
to make sure that the data is read correctly.IOMedia
object is initialized. The parameters of the IOMedia::init()
method are provided here:
virtual bool init(UInt64 base,
UInt64 size,
UInt64 preferredBlockSize,
IOMediaAttributeMask attributes,
bool isWhole,
bool isWritable,
const char* contentHint = 0,
OSDictionary* properties = 0);
The parameters base
and size
define the location of the partition on the disk, specified in bytes. Another important parameter is the Boolean parameter isWhole
, which is set false to indicate that this IOMedia
object represents a partition, and not the entire disk. The parameter contentHint
describes the content of the partition, such as the file system that the volume uses. A description of the contentHint
property is described in the following section.
IOMedia
object. These include the partition name, and the location and partition IDs, both of which are derived from the index of this partition in the partition table.IOMedia
object could not be successfully initialized, it is released.IOMedia
object is returned to the caller or NULL
if the object could not be successfully initialized.Finally, when the driver for the partition scheme is unloaded, it must remove its IOMedia
objects from the driver stack and release them. A partition driver may be unloaded because the disk has been ejected, or because the disk has been reformatted, in which case a new partition table may have been written to the disk, and potentially even a different partition scheme.
An example of the implementation of the stop()
and free()
methods for a partition scheme driver is shown in Listing 14-12. The stop()
method removes each IOMedia
object from the device plane of the I/O Registry, undoing the call to attachMediaObjectToDeviceTree()
that the partition driver performed in its start()
method. Before the partition driver is unloaded, its free()
method is called, which releases the OSSet
that holds the collection of IOMedia
objects for each partition entry.
Listing 14-12. An Implementation of the stop()
and free()
Methods for a Partition Scheme Driver
void com_osxkernel_driver_PartitionScheme::stop(IOService* provider)
{
IOMedia* partition;
OSIterator* partitionIterator;
// Detach the media objects we previously attached to the device tree.
partitionIterator = OSCollectionIterator::withCollection(m_partitions);
if (partitionIterator)
{
while ((partition = (IOMedia*)partitionIterator->getNextObject()))
{
detachMediaObjectFromDeviceTree(partition);
}
partitionIterator->release();
}
super::stop(provider);
}
void com_osxkernel_driver_PartitionScheme::free (void)
{
if (m_partitions != NULL)
m_partitions->release();
super::free();
}
As we saw in Listing 14-11, the initialization method of the IOMedia
class takes a parameter named contentHint
. Although this parameter is not interpreted by the IOMedia
object, it plays a very important role in the construction of the driver storage stack. The contentHint
parameter is a string value that describes the content that is contained by the IOMedia
object on the disk. For an IOMedia
object that represents an entire disk, the content hint may identify the partition scheme that the disk contains. For an IOMedia
object that represents a single partition, the content hint may identify the type of file system that the volume uses. The content hint can also be used for a custom purpose; for example, a driver that provides disk encryption could use the content hint to describe the encryption scheme that has been used on the disk.
The content hint is not used to describe the content to the user, but rather to provide information that can be used by other drivers on the system. The contentHint
parameter that is passed to the initialization method of the IOMedia
class is set as an I/O Registry property on the IOMedia
object. This makes the value of content hint accessible to other drivers in the storage stack, but more importantly, it provides a property that can be specified and matched against another driver's matching dictionary.
When we created our partition scheme driver, we specified an IOPropertyMatch
item (see Listing 14-8), which limited the driver to matching against specific IOMedia
objects. In the case of the partition scheme driver, we matched against only IOMedia
objects that represented the entire disk. This was done by informing the I/O Kit that the partition driver should only match against an IOMedia
object that contained a property named “Whole
” with the value true
. Similarly, a driver can add an IOPropertyMatch
item to its matching dictionary that contains the key “Content Hint
”, and specify a value that contains the particular content type that the driver is interested in. This could be used, for example, to prevent a disk encryption driver from loading against IOMedia
volumes that are not encrypted.
Another important use of the content hint property is to identify the correct file system driver to load for an IOMedia
volume. Mac OS X will load a file system driver only if the content hint value of the IOMedia
object identifies a supported file system.
Since the content hint value needs to be specified when an IOMedia
object is initialized, any driver that instantiates an IOMedia
object needs to know the content of the disk or partition that is represented by that object. For a partition scheme driver, the content hint will come from the partition table that is stored on the disk. For example, the Apple Partition Map contains a string value for each partition entry that is used as the content hint value directly. The GUID partition table contains a 128-bit GUID for each partition that identifies the file system and content of that partition. This GUID is converted to a string representation, which is then used as the content hint. This means that there may be multiple content hint values that identify a particular file system, so a file system driver must match against each possible value of the IOMedia
's content hint that could identify its file system.
3.147.70.66