Partition Schemes

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.

Implementing a Sample Partition Scheme

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:

  • It specifies a provider class of 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.
  • The partition driver is interested only in an 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.
  • The property list specifies an 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:

  1. We obtain a pointer to our provider class, which is an IOMedia object that represents the entire disk. All disk reads are performed through this object, which includes reading the partition table off the disk.
  2. We check any properties of the disk's media before checking for a partition table. If the disk's media is not formatted, we abort the scan. This is also a suitable place to verify requirements, such as a minimum disk block size that may be required by the partition scheme.
  3. All data that is read from the disk is written into an 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.
  4. We allocate an 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.
  5. The storage driver stack (of which the partition driver is a part of) is opened for read access. Since the partition driver will read the partition table from the disk, but not modify the disk contents, it only requires read access.
  6. The first disk sector is read from the disk. This is typically the location of the partition table's header. The hypothetical partition scheme used in this sample stores its header in the first disk sector. The buffer parameter to the read() method specifies both the destination for the data that is read, and the number of bytes to read.
  7. Based on the data read from the disk, the partition driver determines whether the disk contains a partition scheme that it supports. This code will be specific to the partition scheme; the hypothetical partition scheme used by our sample driver is identified through a string constant that is written to the first block on the disk. As such, the driver uses the 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.
  8. The code iterates over each entry in the partition table that was read from the disk. This code will be specific to the partition scheme; the hypothetical partition scheme used in this sample stores the entire partition table in the initial disk sector.
  9. The partition entry is verified. This may involve such checks as making sure that the starting block and length of the partition entry do not exceed the capacity of the disk.
  10. A new IOMedia object is instantiated to represent the partition entry.
  11. If the partition table was successfully scanned, the storage driver stack is closed (to balance the call to open() that was made earlier), and the set of IOMedia objects is returned to the caller.
  12. If an error occurred, the code releases any resources that were partially allocated.

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:

  1. An IOMedia object is allocated using the C++ “new” operator.
  2. The initial disk block number of the partition and the size of the partition are read from the partition table entry. A partition scheme will have a standard endianness that may differ from the native byte order of the host on which the driver is running, so it's important to use byte order macros such as OSSwapLittleToHostInt64() to make sure that the data is read correctly.
  3. The allocated 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.

  4. Various properties of the partition are set on the partition's 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.
  5. If the IOMedia object could not be successfully initialized, it is released.
  6. The initialized 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();
}

The Media Content Hint Property

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.70.66