SampleFilterScheme.cpp

/*
    File:           SampleFilterScheme.cpp
    
    Description:    This sample demonstrates a simple filter scheme that matches an HFS+ media object
                    with a custom content hint property. This is a null filter scheme which passes all
                    operations through to its provider unchanged. 
 
                    The sample also illustrates how such a media object can be created using the command 
                    line disk image tool.
    
    Copyright:      © Copyright 2002-2005 Apple Computer, Inc. All rights reserved.
    
    Disclaimer:     IMPORTANT:  This Apple software is supplied to you by Apple Computer,
                    Inc. (“Apple”) in consideration of your agreement to the following
                    terms, and your use, installation, modification or redistribution of
                    this Apple software constitutes acceptance of these terms.  If you do
                    not agree with these terms, please do not use, install, modify or
                    redistribute this Apple software.
                    
                    In consideration of your agreement to abide by the following terms, and
                    subject to these terms, Apple grants you a personal, non-exclusive
                    license, under Apple’s copyrights in this original Apple software (the
                    “Apple Software”), to use, reproduce, modify and redistribute the Apple
                    Software, with or without modifications, in source and/or binary forms;
                    provided that if you redistribute the Apple Software in its entirety and
                    without modifications, you must retain this notice and the following
                    text and disclaimers in all such redistributions of the Apple Software. 
                    Neither the name, trademarks, service marks or logos of Apple Computer,
                    Inc. may be used to endorse or promote products derived from the Apple
                    Software without specific prior written permission from Apple.  Except
                    as expressly stated in this notice, no other rights or licenses, express
                    or implied, are granted by Apple herein, including but not limited to
                    any patent rights that may be infringed by your derivative works or by
                    other works in which the Apple Software may be incorporated.
                    
                    The Apple Software is provided by Apple on an "AS IS" basis.  APPLE
                    MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION
                    THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS
                    FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND
                    OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.
                    
                    IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL
                    OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
                    SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
                    INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,
                    MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED
                    AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),
                    STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE
                    POSSIBILITY OF SUCH DAMAGE.
                    
    Change History (most recent first):
        
            <3>     11/01/2005          Updated to produce a universal binary. Now requires
                                        Xcode 2.2 or later to build.
            <2>     03/03/2005          Updated hdiutil instructions for 10.3 and later.
                                        Added support for filtering the boot volume.
            <1>     01/22/2002          New sample.
        
*/
 
#include <IOKit/assert.h>
#include <IOKit/IODeviceTreeSupport.h>
#include <IOKit/IOLib.h>
#include "SampleFilterScheme.h"
 
#ifdef DEBUG
#define DEBUG_LOG IOLog
#else
#define DEBUG_LOG(...)
#endif
 
 
//  This filter scheme is set up to match IOMedia objects with the Content Hint
//  property set to "Apple_DTS_Filtered_HFS" (see the matching personality in this project's
//  Info.plist). To test this filter scheme, please see the instructions in the Read Me file
//  that comes with this sample.    
 
#define super IOStorage
OSDefineMetaClassAndStructors(com_apple_dts_driver_SampleFilterScheme, IOStorage)
 
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
bool com_apple_dts_driver_SampleFilterScheme::init(OSDictionary* properties)
{    
    //
    // Initialize this object's minimal state.
    //
 
    // State our assumptions.
 
    // Ask our superclass' opinion.
 
    bool initSuccessful = super::init(properties);
 
    // The DEBUG_LOG call must follow the call to super::init(). Otherwise getName() 
    // will panic because the metaclass info isn't initialized until then.
    
    DEBUG_LOG("%s[%p]::%s(%p)\n", getName(), this, __FUNCTION__, properties);
 
    if (initSuccessful) {
        // Initialize our state.
 
        _childMedia = 0;
    }
 
    return initSuccessful;
}
 
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
void com_apple_dts_driver_SampleFilterScheme::free(void)
{
    DEBUG_LOG("%s[%p]::%s()\n", getName(), this, __FUNCTION__);
    
    //
    // Free all of this object's outstanding resources.
    //
 
    if (_childMedia) {
        _childMedia->release();
    }
 
    super::free();
}
 
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
IOMedia* com_apple_dts_driver_SampleFilterScheme::getProvider(void) const
{
    DEBUG_LOG("%s[%p]::%s()\n", getName(), this, __FUNCTION__);
    
    //
    // Obtain this object's provider.  We override the superclass's method
    // to return a more specific subclass of OSObject -- an IOMedia.  This
    // method serves simply as a convenience to subclass developers.
    //
 
    return (IOMedia*) IOService::getProvider();
}
 
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
bool com_apple_dts_driver_SampleFilterScheme::start(IOService* provider)
{    
    DEBUG_LOG("%s[%p]::%s(%p)\n", getName(), this, __FUNCTION__, provider);
    
    //
    // Publish the new media object which represents our filtered content.
    //
 
    IOMedia* media = OSDynamicCast(IOMedia, provider);
    
    // State our assumptions.
 
    assert(media);
    
    // Ask our superclass' opinion.
 
    if (super::start(provider) == false) {
        return false;
    }
 
    // Attach and register the new media object.
 
    IOMedia* childMedia = new IOMedia;
 
    if (childMedia) {
        if (childMedia->init(
                /* base               */ 0,
                /* size               */ media->getSize(),
                /* preferredBlockSize */ media->getPreferredBlockSize(),
                /* isEjectable        */ media->isEjectable(),
                /* isWhole            */ false,
                /* isWritable         */ media->isWritable(),
                /* contentHint        */ "Apple_HFS" )) {
            
            // Set a name for this partition.
            
            UInt32 partitionID = 1;
            
            char name[24];
            sprintf(name, "Apple_DTS_Filtered %ld", partitionID);
            childMedia->setName(name);
 
            // Set a location value (the partition number) for this partition.
 
            char location[12];
            sprintf(location, "%ld", partitionID);
            childMedia->setLocation(location);
 
            // Attach the new media to this driver
 
            _childMedia = childMedia;
            
            childMedia->attach(this);
            
#ifdef FILTER_BOOT_VOLUME
 
            /*
                This next call allows this filter scheme to be installed on the boot partition.
                
                First, some background on why this is necessary.
                
                Once Open Firmware (OF) has chosen the volume to boot from, it loads the secondary loader (BootX)  
                from that volume and jumps to it. The secondary loader is responsible for actually loading
                and running the kernel, passing to it various parameters that are inherited from OF
                (primarily the device tree).
                
                Once the kernel comes up, it has to mount the root volume. By this point OF is no
                longer running, so the kernel determines the root volume by interpreting a parameter that  
                OF passed to it. This parameter is the "rootpath" property of the "/chosen" node in the OF
                device tree. The kernel gets this value and looks in the I/O Registry for a node whose
                OF path matches this value. The kernel then uses this node as the root device.
                
                The kernel has no knowledge that a filter scheme was installed on top of that node,
                so it continues booting from the unfiltered media object. Later on the  
                Disk Arbitration server comes up, notices that the filter scheme is  
                publishing a new leaf node that hasn't been mounted on, and mounts the file  
                system on that node. Hence two copies of the boot volume appear on the desktop with
                separate data paths, a recipe for quickly corrupting the contents of the volume.
                
                The solution is for the filter scheme to "move" the last device tree component from its parent
                to its child. That is, it needs to detach the parent from the device tree path, keeping track
                of its name@location value, then attach it to the child. This must be done before publishing the
                new media object via registerService(). The reverse needs to be done in stop().
                
                Note that if you want this filter scheme to be loaded at boot time (so that it can be installed
                on top of the boot volume), this project has a second target called "Filter Boot Volume". Building
                this target will include the code bracketed by #ifdef FILTER_BOOT_VOLUME and will include a
                different Info.plist file containing the property OSBundleRequired = "Local-Root". (Keep this
                detail in mind: if you change something in Info.plist, be sure to make the same change in
                Info-FILTER_BOOT_VOLUME.plist. There's no equivalent to #ifdef for plists.)
                
                Only build the "Filter Boot Volume" target if you really need to filter the boot volume.
                Otherwise, build the default "Don't Filter Boot" target.
            */
 
            (void) attachMediaObjectToDeviceTree(childMedia);
 
#endif
            
            // Now publish the child media object.
            
            childMedia->registerService();
 
            return true;
        }
        else {
            childMedia->release();
            childMedia = 0;
        }
    }
 
    return false;
}
 
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
void com_apple_dts_driver_SampleFilterScheme::stop(IOService* provider)
{
    DEBUG_LOG("%s[%p]::%s(%p)\n", getName(), this, __FUNCTION__, provider);
    
    //
    // Clean up after the media object we published before terminating.
    //
 
    // State our assumptions.
 
    assert(_childMedia);
 
#ifdef FILTER_BOOT_VOLUME
 
    // Detach the media object we previously attached to the device tree.
    // See start() for an explanation of this call.
    
    if (_childMedia) {
        detachMediaObjectFromDeviceTree(_childMedia);
    }
    
#endif
 
    super::stop(provider);
}
 
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
bool com_apple_dts_driver_SampleFilterScheme::handleOpen(IOService* client,
                                                         IOOptionBits options,
                                                         void* argument)
{
    //
    // The handleOpen method grants or denies permission to access this object
    // to an interested client.  The argument is an IOStorageAccess value that
    // specifies the level of access desired -- reader or reader-writer.
    //
    // This method can be invoked to upgrade or downgrade the access level for
    // an existing client as well.  The previous access level will prevail for
    // upgrades that fail, of course.   A downgrade should never fail.  If the
    // new access level should be the same as the old for a given client, this
    // method will do nothing and return success.  In all cases, one, singular
    // close-per-client is expected for all opens-per-client received.
    //
    // This implementation replaces the IOService definition of handleOpen().
    //
    // We are guaranteed that no other opens or closes will be processed until
    // we make our decision, change our state, and return from this method.
    //
 
    DEBUG_LOG("%s[%p]::%s(%p, %lu, %p)\n", getName(), this, __FUNCTION__, client, options, argument);
 
    return getProvider()->open(this, options, (IOStorageAccess) argument);
}
 
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
bool com_apple_dts_driver_SampleFilterScheme::handleIsOpen(const IOService* client) const
{
    //
    // The handleIsOpen method determines whether the specified client, or any
    // client if none is specified, presently has an open on this object.
    //
    // This implementation replaces the IOService definition of handleIsOpen().
    //
    // We are guaranteed that no other opens or closes will be processed until
    // we return from this method.
    //
 
    DEBUG_LOG("%s[%p]::%s(%p)\n", getName(), this, __FUNCTION__, client);
 
    return getProvider()->isOpen(this);
}
 
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
void com_apple_dts_driver_SampleFilterScheme::handleClose(IOService* client, IOOptionBits options)
{
    //
    // The handleClose method closes the client's access to this object.
    //
    // This implementation replaces the IOService definition of handleClose().
    //
    // We are guaranteed that no other opens or closes will be processed until
    // we change our state and return from this method.
    //
 
    DEBUG_LOG("%s[%p]::%s(%p, %lu)\n", getName(), this, __FUNCTION__, client, options);
 
    assert(client);
 
    getProvider()->close(this, options);
}
 
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
void com_apple_dts_driver_SampleFilterScheme::read(IOService* __attribute__ ((unused)) client,
                                                   UInt64 byteStart,
                                                   IOMemoryDescriptor* buffer,
                                                   IOStorageCompletion completion)
{
    //
    // Read data from the storage object at the specified byte offset into the
    // specified buffer, asynchronously. When the read completes, the caller
    // will be notified via the specified completion action.
    //
    // The buffer will be retained for the duration of the read.
    //
    // For simple partition schemes, the default behavior is to simply pass the
    // read through to the provider media.  More complex partition schemes such
    // as RAID will need to do extra processing here.
    //
 
    DEBUG_LOG("%s[%p]::%s(%p, %llu, %p, %p)\n", getName(), this, __FUNCTION__, client, byteStart, buffer, &completion);
 
    getProvider()->read(this, byteStart, buffer, completion);
}
 
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
void com_apple_dts_driver_SampleFilterScheme::write(IOService* __attribute__ ((unused)) client,
                                                    UInt64 byteStart,
                                                    IOMemoryDescriptor* buffer,
                                                    IOStorageCompletion completion)
{
    //
    // Write data into the storage object at the specified byte offset from the
    // specified buffer, asynchronously. When the write completes, the caller
    // will be notified via the specified completion action.
    //
    // The buffer will be retained for the duration of the write.
    //
    // For simple partition schemes, the default behavior is to simply pass the
    // write through to the provider media. More complex partition schemes such
    // as RAID will need to do extra processing here.
    //
 
    DEBUG_LOG("%s[%p]::%s(%p, %llu, %p, %p)\n", getName(), this, __FUNCTION__, client, byteStart, buffer, &completion);
 
    getProvider()->write(this, byteStart, buffer, completion);
}
 
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
IOReturn com_apple_dts_driver_SampleFilterScheme::synchronizeCache(IOService* client)
{
    //
    // I/O Kit has provisions for data caches at the driver level, but this is
    // rarely needed and is discouraged by Apple. 99+% of the time the following
    // implementation is just fine.
    //
      
    DEBUG_LOG("%s[%p]::%s(%p)\n", getName(), this, __FUNCTION__, client);
 
    return getProvider()->synchronizeCache(this);
}
 
#ifdef FILTER_BOOT_VOLUME
 
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
bool com_apple_dts_driver_SampleFilterScheme::attachMediaObjectToDeviceTree(IOMedia* media)
{
    //
    // Attach the given media object to the device tree plane.
    //
 
    IORegistryEntry* child;
 
    DEBUG_LOG("%s[%p]::%s(%p)\n", getName(), this, __FUNCTION__, media);
 
    if ((child = getParentEntry(gIOServicePlane))) {
 
        IORegistryEntry* parent;
 
        if ((parent = child->getParentEntry(gIODTPlane))) {
 
            const char* location = child->getLocation(gIODTPlane);
            const char* name     = child->getName(gIODTPlane);
 
            if (media->attachToParent(parent, gIODTPlane)) {
                media->setLocation(location, gIODTPlane);
                media->setName(name, gIODTPlane);
 
                child->detachFromParent(parent, gIODTPlane);
 
                return true;
            }
        }
    }
 
    return false;
}
 
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
void com_apple_dts_driver_SampleFilterScheme::detachMediaObjectFromDeviceTree(IOMedia* media)
{
    //
    // Detach the given media object from the device tree plane.
    //
 
    IORegistryEntry* child;
 
    DEBUG_LOG("%s[%p]::%s(%p)\n", getName(), this, __FUNCTION__, media);
 
    if ((child = getParentEntry(gIOServicePlane))) {
     
        IORegistryEntry * parent;
 
        if ((parent = media->getParentEntry(gIODTPlane))) {
 
            const char* location = media->getLocation(gIODTPlane);
            const char* name     = media->getName(gIODTPlane);
 
            if (child->attachToParent(parent, gIODTPlane)) {
                child->setLocation(location, gIODTPlane);
                child->setName(name, gIODTPlane);
            }
 
            media->detachFromParent(parent, gIODTPlane);
        }
    }
}
 
#endif