Source to iokit/Families/IOStorage/IOApplePartitionScheme.cpp


Enter a symbol's name here to quickly find it.

/*
 * Copyright (c) 1998-2000 Apple Computer, Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * The contents of this file constitute Original Code as defined in and
 * are subject to the Apple Public Source License Version 1.1 (the
 * "License").  You may not use this file except in compliance with the
 * License.  Please obtain a copy of the License at
 * http://www.apple.com/publicsource and read it before using this file.
 * 
 * This Original Code and all software distributed under the License are
 * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT.  Please see the
 * License for the specific language governing rights and limitations
 * under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 */

//
// Notes:
// -----
// o the on-disk dpme structure is packed for PowerPC and has big endian fields
//
// o the dpme_pblock_start block value is relative to the containing media's
//   boundary, and the implicit block size is hardcoded to 512 bytes
//
// o the dpme_pblocks block value's implicit block size is 512 bytes
//
// Policies:
// --------
// o the apple partition scheme accepts probes only on "whole" media objects --
//   this is done in the spirit of minimizing reads, since in general practice,
//   subpartitions of the apple type do not exist
//
// o the default probe score of the apple partition scheme is 1200
//
// o the "Apple_partition_map" partition is always published as a non-writable
//   media -- this is because its contents are nobody's business but ours
//
// o the "Apple_Free" partition is always skipped -- this is because it would
//   never be used by a serious user application, and it cries out to harbour
//   viruses;  the partition also often exceeds the confines of the media for
//   CDs, which would arguably require ugly truncation logic to be consistent
//
// o the checksum for each partition entry is not validated -- this is done in
//   the spirit of non-necessity and perhaps a bit of laziness
//

#include <IOKit/assert.h>
#include <IOKit/IOLib.h>
#include <IOKit/storage/IOApplePartitionScheme.h>
#include <libkern/OSByteOrder.h>

#define super IOPartitionScheme
OSDefineMetaClassAndStructors(IOApplePartitionScheme, IOPartitionScheme);

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

bool IOApplePartitionScheme::init(OSDictionary * properties = 0)
{
    //
    // Initialize this object's minimal state.
    //

    if (super::init(properties) == false)  return false;

    _buffer        = 0;
    _bufferSize    = 0;
    _masteredAt512 = false;

    // Validate the compiled size of our important fixed-size structures.

    assert(sizeof(dpme)   == 512);
    assert(sizeof(DDMap)  ==   8);
    assert(sizeof(Block0) == 512);

    return true;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

void IOApplePartitionScheme::free()
{
    //
    // Free all of this object's outstanding resources.
    //

    if (_buffer)  _buffer->release();

    super::free();
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

IOService * IOApplePartitionScheme::probe(IOService * provider, SInt32 * score)
{
    //
    // Determine whether the provider media contains an apple partition map.  If
    // it does, we return "this" to indicate success, otherwise we return zero.
    //

    IOMedia * media = (IOMedia *) provider;

    // State our assumptions.

    assert(OSDynamicCast(IOMedia, provider));

    // Ask superclass' opinion about this probe.

    if (super::probe(provider, score) == 0)  return 0;

    // Determine whether this media object is unformatted.

    if (media->isFormatted() == false)  return 0;

    // Determine whether this media's block size is below our assumed minimum.

    if (media->getPreferredBlockSize() < sizeof(dpme))  return 0;

    // Determine whether we can rule out the media object as an apple partition
    // map container without reading actual data from the media.    We rule out
    // all non-whole media objects.

    if (media->isWhole() == false)  return 0;

    // Allocate a buffer large enough to hold one media block.

    _bufferSize = media->getPreferredBlockSize();
    _buffer     = IOBufferMemoryDescriptor::withCapacity(_bufferSize,
                                                         kIODirectionIn);

    if (_buffer == 0)  return 0;

    // Search for a valid apple partition on the provider media.

    return identify(media) ? this : 0;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

bool IOApplePartitionScheme::start(IOService * provider)
{
    //
    // This method is called once we have been attached to the media object.  We
    // generate the new media objects that will represent our partitions here.
    //

    UInt64    block;    
    UInt32    entriesCount;
    IOMedia * media = (IOMedia *) provider;
    dpme *    partition;
    UInt32    partitionsPerBlock;
    UInt32    partitionsLeftOnThisBlock;
    bool      success = false;

    // State our assumptions.

    assert(_buffer);

    // Ask our superclass' opinion.

    if ( !super::start(provider) )  return false;

    // Open the media object for access.

    if ( media->open(this, 0, kAccessReader) == false )  return false;

    // Set up our iteration variables, based on whether the media was mastered
    // on a 512-byte-block medium or at the media's preferred block size.

    if ( _masteredAt512 )
    {
        block = 0;
        partition = (dpme*)((UInt8*)_buffer->getBytesNoCopy() + sizeof(Block0));
        partitionsPerBlock = _bufferSize / sizeof(dpme);
        partitionsLeftOnThisBlock = partitionsPerBlock - 1;
    }
    else
    {
        block = 1;
        partition = (dpme *) _buffer->getBytesNoCopy();
        partitionsPerBlock = 1;
        partitionsLeftOnThisBlock = 1;
    }

    // Scan through all the partition entries.

    entriesCount = OSSwapBigToHostInt32(partition->dpme_map_entries);

    for ( unsigned index = 0; index < entriesCount; index++, partition += 1 )
    {
        // Determine whether we've exhausted the current buffer of partitions.

        if ( partitionsLeftOnThisBlock == 0 )
        {
            // Read the next block into our buffer.

            block++;
            _buffer->setDirection(kIODirectionIn); // (a read)
            _buffer->setLength(_bufferSize);       // (transfer one full block)

            if ( media->read( /* client    */ this,
                              /* byteStart */ block * _bufferSize,
                              /* buffer    */ _buffer ) != kIOReturnSuccess )
            {
                IOLog("%s: %s: Unable to read block %d.\n", 
                      getName(), media->getName(), (int) block);
                break; // (failure)
            }

            partition = (dpme *) _buffer->getBytesNoCopy();
            partitionsLeftOnThisBlock = partitionsPerBlock;
        }

        partitionsLeftOnThisBlock--;

        // Determine whether this partition has a valid 'PM' signature.

        if ( OSSwapBigToHostInt16(partition->dpme_signature) != DPME_SIGNATURE )
        {
            IOLog("%s on %s: Partition %d has an invalid signature.\n",
                  getName(), media->getName(), index + 1);
            continue; // (skip)
        }

        // Determine whether this partition's type is 'Apple_Free'.

        if ( !strcmp(partition->dpme_type, "Apple_Free") )
            continue; // (skip)

        // Determine whether this partition's type is 'Apple_partition_map'.

        bool isWritable = media->isWritable();

        if ( !strcmp(partition->dpme_type, "Apple_partition_map") )
            isWritable = false;

        // Compute the relative position and size of the new partition.  The
        // block values are always in terms of 512-byte blocks.

        UInt64 base = OSSwapBigToHostInt32(partition->dpme_pblock_start)*512ULL;
        UInt64 size = OSSwapBigToHostInt32(partition->dpme_pblocks) * 512ULL;

        if ( base == 0 || size == 0 )
            continue; // (skip)

        // Look up a name and a type for this partition.

        const char * aName = partition->dpme_name;
        const char * aHint = partition->dpme_type;

        // Ensure the partition definition does not leave the confines of the
        // containing media.  Note the "Apple_Free" partition often leaves its
        // confines on CD media, however it is our policy to ignore Apple_Free
        // partitions anyway.

        if ( base + size > media->getSize() )
        {
            IOLog("%s on %s: \"%s\" (partition %d) exceeds confines of "
                  "containing media.\n",
                  getName(), media->getName(), aName, index+1);
            continue; // (skip)
        }

        // The partition base may be unaligned with respect the parent media's
        // block boundaries.   We warn the user in this event since every read
        // or write is going to cause deblocking.  We presume, of course, that
        // the parent media's base is aligned in making this determination.

        if ( base % media->getPreferredBlockSize() )
        {
            IOLog("%s on %s: Access to \"%s\" (partition %d) may be slow due "
                  "to a misaligned block boundary.\n",
                  getName(), media->getName(), aName, index + 1);
        }        

        // Create the new media object.

        IOMedia * newMedia = new IOMedia;

        if ( !newMedia ||
             !newMedia->init( // base (bytes; relative to provider media)
                              base,
                              // size (bytes)
                              size,
                              // natural block size (bytes)
///m:2361246:workaround:added:start
                              (!strcmp(aHint, "Apple_HFS")) ? (512) : 
///m:2361246:workaround:added:stop
                              media->getPreferredBlockSize(),
                              // is ejectable
                              media->isEjectable(),
                              // is whole
                              false,
                              // is writable
                              isWritable,
                              // content hint
                              aHint ) ||
             !newMedia->attach(this) )
        {
            IOLog("%s on %s: Unable to create media object for \"%s\" "
                  "(partition %d).\n",
                  getName(), media->getName(), aName, index + 1);

            if ( newMedia )  newMedia->release();
            continue; // (skip)
        }

        newMedia->setName(aName);

        // Set a location value (the partition number) for this partition.

        char location[12];
        sprintf(location, "%d", index + 1);
        newMedia->setLocation(location);

        // Create the "Partition ID" key.

        newMedia->setProperty(kIOMediaPartitionID, index + 1, 32);

        // Release our retain on the new media object (in registry).

        newMedia->release();

        // We don't register the new media object with the matching system
        // until we've closed our open on the media object below us.

        success = true;
    } // (for all partition entries)

    // Release our buffer now that we no longer need it.

    _buffer->release();

    _buffer     = 0;
    _bufferSize = 0;

    // Close the media object we opened earlier.

    media->close(this);

    // We now register the new media objects with the matching system, now
    // that we've closed our open on the media object below us.

    if ( success )
    {
        OSIterator * clients = getClientIterator();

        if (clients)
        {
            IOService * client;
            while ( (client = (IOService *) clients->getNextObject()) )
                client->registerService();
            clients->release();
        }
    }

    // Return success if we found at least one partition.

    return success;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

bool IOApplePartitionScheme::identify(IOMedia * media)
{
    //
    // Searches for the existence of an apple partition map on the given media.
    //

    bool success = false;

    // State our assumptions.

    assert(_buffer);

    // Open the media object for access.

    if ( media->open(this, 0, kAccessReader) == false )  return false;

    // Determine whether this is a larger-than-512-byte-block media that was
    // mastered from a 512-byte-block media  --  the mastering of 2048-byte-
    // block CDs is the prime example, from a 512-byte-block hard drive.

    if ( _bufferSize > sizeof(Block0) )
    {
        // State our assumptions.

        assert(_bufferSize >= sizeof(Block0) + sizeof(dpme));

        // Read the first block into our buffer.

        _buffer->setDirection(kIODirectionIn);   // (a read)
        _buffer->setLength(_bufferSize);         // (transfer one full block)

        if ( media->read(this, 0, _buffer) != kIOReturnSuccess )
        {
            media->close(this);
            return false; // (failure)
        }

        // Determine whether the partition signature 'PM' is present at a 512
        // byte offset into the block.

        dpme * partition;
        partition = (dpme*)((UInt8*)_buffer->getBytesNoCopy() + sizeof(Block0));

        if ( OSSwapBigToHostInt16(partition->dpme_signature) == DPME_SIGNATURE )
        {
            _masteredAt512 = true;
            success        = true;
        }
    }

    // Determine whether there is a valid apple partition map on the media;
    // note if the masteredAt512 flag is set, we already confirmed it does.

    if ( _masteredAt512 == false )
    {
        // Read the second block into our buffer.

        _buffer->setDirection(kIODirectionIn);   // (a read)
        _buffer->setLength(_bufferSize);         // (transfer one full block)

        if ( media->read(this, _bufferSize, _buffer) == kIOReturnSuccess )
        {
            // Determine whether the partition map signature 'PM' is present.

            dpme * partition = (dpme *) _buffer->getBytesNoCopy();

            if (OSSwapBigToHostInt16(partition->dpme_signature)==DPME_SIGNATURE)
                success = true;
        }
    }

    // Close the media object we opened earlier.

    media->close(this);

    return success;
}