/*
 * Copyright (c) 2003 Apple Computer, Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 * 
 * The 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, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 */

#include "DAQueue.h"

#include "DABase.h"
#include "DACallback.h"
#include "DAInternal.h"
#include "DALog.h"
#include "DAMain.h"
#include "DARequest.h"
#include "DASession.h"
#include "DAStage.h"

struct __DAResponseContext
{
    DAResponseCallback callback;
    void *             callbackContext;
    CFTypeRef          response;
};

typedef struct __DAResponseContext __DAResponseContext;

const CFGregorianUnits __kDAResponseTimerGrace = { 0, 0, 0, 0, 0,  1 };
const CFGregorianUnits __kDAResponseTimerLimit = { 0, 0, 0, 0, 0, 15 };

static void __DAResponseTimerRefresh( void );

static void __DAQueueCallbacks( _DACallbackKind kind, DADiskRef argument0, CFTypeRef argument1 )
{
    CFIndex count;
    CFIndex index;

    count = CFArrayGetCount( gDASessionList );

    for ( index = 0; index < count; index++ )
    {
        DASessionRef session;

        session = ( void * ) CFArrayGetValueAtIndex( gDASessionList, index );

        if ( kind == _kDAIdleCallback )
        {
            if ( DASessionGetState( session, kDASessionStateIdle ) )
            {
                continue;
            }
        }

        DAQueueCallbacks( session, kind, argument0, argument1 );

        if ( kind == _kDAIdleCallback )
        {
            DASessionSetState( session, kDASessionStateIdle, TRUE );
        }
    }
}

static void __DAQueueRequest( _DARequestKind kind, DADiskRef argument0, CFIndex argument1, CFTypeRef argument2, CFTypeRef argument3, DACallbackRef callback )
{
    DARequestRef request;

///w:start
    if ( kind == _kDADiskMount )
    {
        request = DARequestCreate( kCFAllocatorDefault, kind, argument0, argument1, argument2, argument3, ___UID_UNKNOWN, ___GID_UNKNOWN, callback );
    }
    else
///w:stop
    request = DARequestCreate( kCFAllocatorDefault, kind, argument0, argument1, argument2, argument3, ___UID_ROOT, ___GID_ADMIN, callback );

    if ( request )
    {
        DAQueueRequest( request );

        CFRelease( request );
    }
}

static void __DAResponseComplete( DADiskRef disk )
{
    CFIndex count;
    CFIndex index;

    count = CFArrayGetCount( gDAResponseList );

    for ( index = 0; index < count; index++ )
    {
        DACallbackRef callback;

        callback = ( void * ) CFArrayGetValueAtIndex( gDAResponseList, index );

        if ( DACallbackGetDisk( callback ) == disk )
        {
            break;
        }
    }

    if ( index == count )
    {
        __DAResponseContext context;

        context = *( ( __DAResponseContext * ) CFDataGetBytePtr( DADiskGetContextRe( disk ) ) );

        DADiskSetContextRe( disk, NULL );

        ( context.callback )( context.response, context.callbackContext );

        if ( context.response )  CFRelease( context.response );
    }

    __DAResponseTimerRefresh( );
}

static void __DAResponsePrepare( DADiskRef disk, DAResponseCallback callback, void * callbackContext )
{
    CFDataRef data;

    data = CFDataCreateMutable( kCFAllocatorDefault, sizeof( __DAResponseContext ) );

    if ( data )
    {
        __DAResponseContext * context;

        context = ( void * ) CFDataGetBytePtr( data );

        bzero( context, sizeof( __DAResponseContext ) );

        context->callback        = callback;
        context->callbackContext = callbackContext;

        DADiskSetContextRe( disk, data );

        CFRelease( data );
    }
}

static void __DAResponseTimerCallback( CFRunLoopTimerRef timer, void * info )
{
    CFAbsoluteTime clock;
    CFIndex        count;
    CFIndex        index;

    clock = CFAbsoluteTimeGetCurrent( );

    count = CFArrayGetCount( gDAResponseList );

    for ( index = count - 1; index > -1; index-- )
    {
        DACallbackRef callback;

        callback = ( void * ) CFArrayGetValueAtIndex( gDAResponseList, index );

        if ( callback )
        {
            DASessionRef session;

            session = DACallbackGetSession( callback );

            if ( DASessionGetOption( session, kDASessionOptionNoTimeout ) == FALSE )
            {
                CFAbsoluteTime timeout;

                timeout = DACallbackGetTime( callback );

                timeout = CFAbsoluteTimeAddGregorianUnits( timeout, NULL, __kDAResponseTimerLimit );

                if ( timeout < clock )
                {
                    DADiskRef disk;

                    disk = DACallbackGetDisk( callback );

                    if ( DASessionGetState( session, kDASessionStateTimeout ) == FALSE )
                    {
                        DALogDebugHeader( "%s -> %s", gDAProcessNameID, gDAProcessNameID );

                        DALogDebug( "  timed out session, id = %@.", session );

                        DALogError( "%@ not responding.", session );

                        DASessionSetState( session, kDASessionStateTimeout, TRUE );
                    }

                    CFRetain( disk );

                    CFArrayRemoveValueAtIndex( gDAResponseList, index );

                    __DAResponseComplete( disk );

                    CFRelease( disk );
                }
            }
        }
    }
}

static void __DAResponseTimerRefresh( void )
{
    static CFRunLoopTimerRef timer = NULL;

    CFAbsoluteTime clock;
    CFIndex        count;
    CFIndex        index;

    clock = kCFAbsoluteTimeIntervalSince1904;

    count = CFArrayGetCount( gDAResponseList );

    for ( index = 0; index < count; index++ )
    {
        DACallbackRef callback;

        callback = ( void * ) CFArrayGetValueAtIndex( gDAResponseList, index );

        if ( callback )
        {
            DASessionRef session;

            session = DACallbackGetSession( callback );

            if ( DASessionGetOption( session, kDASessionOptionNoTimeout ) == FALSE )
            {
                CFAbsoluteTime timeout;

                timeout = DACallbackGetTime( callback );

                timeout = CFAbsoluteTimeAddGregorianUnits( timeout, NULL, __kDAResponseTimerLimit );

                if ( timeout < clock )
                {
                    clock = timeout;
                }
            }
        }
    }

    clock = CFAbsoluteTimeAddGregorianUnits( clock, NULL, __kDAResponseTimerGrace );

    if ( timer )
    {
        CFRunLoopTimerSetNextFireDate( timer, clock );
    }
    else
    {
        timer = CFRunLoopTimerCreate( kCFAllocatorDefault, clock, kCFAbsoluteTimeIntervalSince1904, 0, 0, __DAResponseTimerCallback, NULL );

        if ( timer )
        {
            CFRunLoopAddTimer( CFRunLoopGetCurrent( ), timer, kCFRunLoopDefaultMode );
        }
    }
}

Boolean _DAResponseDispatch( CFTypeRef response, SInt32 responseID )
{
    CFIndex count;
    CFIndex index;

    count = CFArrayGetCount( gDAResponseList );

    for ( index = 0; index < count; index++ )
    {
        DACallbackRef callback;

        callback = ( void * ) CFArrayGetValueAtIndex( gDAResponseList, index );

        if ( ___CFNumberGetIntegerValue( DACallbackGetArgument1( callback ) ) == responseID )
        {
            DADiskRef disk;

            disk = DACallbackGetDisk( callback );

            switch ( DACallbackGetKind( callback ) )
            {
                case _kDADiskClaimReleaseCallback:
                case _kDADiskEjectApprovalCallback:
                case _kDADiskMountApprovalCallback:
                case _kDADiskUnmountApprovalCallback:
                {
                    DADissenterRef dissenter;

                    dissenter = ( void * ) response;

                    if ( dissenter )
                    {
                        CFDataRef data;

                        data = DADiskGetContextRe( disk );

                        if ( data )
                        {
                            __DAResponseContext * context;

                            context = ( void * ) CFDataGetBytePtr( data );

                            if ( context->response == NULL )
                            {
                                context->response = CFRetain( dissenter );
                            }
                        }

                        DALogDebug( "  dispatched response, id = %08X:%08X, kind = %s, disk = %@, dissented, status = 0x%08X.",
                                    DACallbackGetAddress( callback ),
                                    DACallbackGetContext( callback ),
                                    _DACallbackKindGetName( DACallbackGetKind( callback ) ),
                                    disk,
                                    DADissenterGetStatus( dissenter ) );
                    }
                    else
                    {
                        DALogDebug( "  dispatched response, id = %08X:%08X, kind = %s, disk = %@, approved.",
                                    DACallbackGetAddress( callback ),
                                    DACallbackGetContext( callback ),
                                    _DACallbackKindGetName( DACallbackGetKind( callback ) ),
                                    disk );
                    }

                    break;
                }
                case _kDADiskPeekCallback:
                {
                    DALogDebug( "  dispatched response, id = %08X:%08X, kind = %s, disk = %@.",
                                DACallbackGetAddress( callback ),
                                DACallbackGetContext( callback ),
                                _DACallbackKindGetName( DACallbackGetKind( callback ) ),
                                disk );

                    break;
                }
            }

            CFArrayRemoveValueAtIndex( gDAResponseList, index );

            __DAResponseComplete( disk );

            break;
        }
    }

    return ( index < count ) ? TRUE : FALSE;
}

void DADiskAppearedCallback( DADiskRef disk )
{
    __DAQueueCallbacks( _kDADiskAppearedCallback, disk, NULL );
}

void DADiskClaimReleaseCallback( DADiskRef disk, DACallbackRef callback, DAResponseCallback response, void * responseContext )
{
    __DAResponsePrepare( disk, response, responseContext );

    DAQueueCallback( callback, disk, NULL );

    __DAResponseComplete( disk );
}

void DADiskClassicCallback( DADiskRef disk )
{
    __DAQueueCallbacks( _kDADiskClassicCallback, disk, NULL );
}

void DADiskDescriptionChangedCallback( DADiskRef disk, CFTypeRef key )
{
    if ( CFGetTypeID( key ) == CFArrayGetTypeID( ) )
    {
        __DAQueueCallbacks( _kDADiskDescriptionChangedCallback, disk, key );
    }
    else
    {
        CFMutableArrayRef keys;

        keys = CFArrayCreateMutable( kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks );

        CFArrayAppendValue( keys, key );

        __DAQueueCallbacks( _kDADiskDescriptionChangedCallback, disk, keys );

        CFRelease( keys );
    }
}

void DADiskDisappearedCallback( DADiskRef disk )
{
    __DAQueueCallbacks( _kDADiskDisappearedCallback, disk, NULL );
}

void DADiskEject( DADiskRef disk, DACallbackRef callback )
{
    __DAQueueRequest( _kDADiskEject, disk, 0, NULL, NULL, callback );
}

void DADiskEjectApprovalCallback( DADiskRef disk, DAResponseCallback response, void * responseContext )
{
    __DAResponsePrepare( disk, response, responseContext );

    __DAQueueCallbacks( _kDADiskEjectApprovalCallback, disk, NULL );

    __DAResponseComplete( disk );
}

void DADiskMount( DADiskRef disk, CFURLRef mountpoint, CFStringRef arguments, DACallbackRef callback )
{
    __DAQueueRequest( _kDADiskMount, disk, 0, mountpoint, arguments, callback );
}

void DADiskMountApprovalCallback( DADiskRef disk, DAResponseCallback response, void * responseContext )
{
    __DAResponsePrepare( disk, response, responseContext );

    __DAQueueCallbacks( _kDADiskMountApprovalCallback, disk, NULL );

    __DAResponseComplete( disk );
}

void DADiskPeekCallback( DADiskRef disk, DACallbackRef callback, DAResponseCallback response, void * responseContext )
{
    __DAResponsePrepare( disk, response, responseContext );
    
    DAQueueCallback( callback, disk, NULL );

    __DAResponseComplete( disk );
}

void DADiskRefresh( DADiskRef disk, DACallbackRef callback )
{
    __DAQueueRequest( _kDADiskRefresh, disk, 0, NULL, NULL, callback );
}

void DADiskUnmount( DADiskRef disk, DACallbackRef callback )
{
    __DAQueueRequest( _kDADiskUnmount, disk, 0, NULL, NULL, callback );
}

void DADiskUnmountApprovalCallback( DADiskRef disk, DAResponseCallback response, void * responseContext )
{
    __DAResponsePrepare( disk, response, responseContext );

    __DAQueueCallbacks( _kDADiskUnmountApprovalCallback, disk, NULL );

    __DAResponseComplete( disk );
}

void DAIdleCallback( void )
{
    __DAQueueCallbacks( _kDAIdleCallback, NULL, NULL );
}

void DAQueueCallback( DACallbackRef callback, DADiskRef argument0, CFTypeRef argument1 )
{
    static SInt32 responseID = 0;
    
    DASessionRef session;

    session = DACallbackGetSession( callback );

    DALogDebugHeader( "%s -> %@", gDAProcessNameID, session );

    if ( DASessionGetState( session, kDASessionStateZombie ) == FALSE )
    {
        if ( DACallbackGetAddress( callback ) )
        {
            CFDictionaryRef match;

            match = DACallbackGetMatch( callback );

            if ( match )
            {
                if ( DADiskMatch( argument0, match ) == FALSE )
                {
                    return;
                }
            }

            switch ( DACallbackGetKind( callback ) )
            {
                case _kDADiskAppearedCallback:
                case _kDADiskClassicCallback:
                case _kDADiskDisappearedCallback:
                {
                    if ( DADiskGetOption( argument0, kDADiskOptionPrivate ) == FALSE )
                    {
                        callback = DACallbackCreateCopy( kCFAllocatorDefault, callback );

                        if ( callback )
                        {
                            DACallbackSetDisk( callback, argument0 );

                            DACallbackSetArgument0( callback, DADiskGetSerialization( argument0 ) );

                            DASessionQueueCallback( session, callback );

                            DALogDebug( "  dispatched callback, id = %08X:%08X, kind = %s, disk = %@.",
                                        DACallbackGetAddress( callback ),
                                        DACallbackGetContext( callback ),
                                        _DACallbackKindGetName( DACallbackGetKind( callback ) ),
                                        argument0 );

                            CFRelease( callback );
                        }
                    }

                    break;
                }
                case _kDADiskClaimCallback:
                case _kDADiskEjectCallback:
                case _kDADiskMountCallback:
                case _kDADiskRenameCallback:
                case _kDADiskUnmountCallback:
                {
                    DACallbackSetDisk( callback, argument0 );

                    DACallbackSetArgument0( callback, DADiskGetSerialization( argument0 ) );

                    DACallbackSetArgument1( callback, argument1 );

                    DASessionQueueCallback( session, callback );

                    if ( argument1 )
                    {
                        DALogDebug( "  dispatched callback, id = %08X:%08X, kind = %s, disk = %@, status = 0x%08X.",
                                    DACallbackGetAddress( callback ),
                                    DACallbackGetContext( callback ),
                                    _DACallbackKindGetName( DACallbackGetKind( callback ) ),
                                    argument0,
                                    DADissenterGetStatus( argument1 ) );
                    }
                    else
                    {
                        DALogDebug( "  dispatched callback, id = %08X:%08X, kind = %s, disk = %@, success.",
                                    DACallbackGetAddress( callback ),
                                    DACallbackGetContext( callback ),
                                    _DACallbackKindGetName( DACallbackGetKind( callback ) ),
                                    argument0 );
                    }

                    break;
                }
                case _kDADiskClaimReleaseCallback:
                {
                    if ( DASessionGetState( session, kDASessionStateTimeout ) == FALSE )
                    {
                        assert( argument1 == NULL );

                        argument1 = ___CFNumberCreateWithIntegerValue( kCFAllocatorDefault, responseID );

                        if ( argument1 )
                        {
                            DACallbackRef response;

                            response = DACallbackCreateCopy( kCFAllocatorDefault, callback );

                            if ( response )
                            {
                                DACallbackSetDisk( response, argument0 );

                                DACallbackSetArgument1( response, argument1 );

                                DACallbackSetTime( response, CFAbsoluteTimeGetCurrent( ) );

                                CFArrayAppendValue( gDAResponseList, response );

                                CFRelease( response );
                            }

                            callback = DACallbackCreateCopy( kCFAllocatorDefault, callback );

                            if ( callback )
                            {
                                DACallbackSetDisk( callback, argument0 );

                                DACallbackSetArgument0( callback, DADiskGetSerialization( argument0 ) );

                                DACallbackSetArgument1( callback, argument1 );

                                DASessionQueueCallback( session, callback );

                                DALogDebug( "  dispatched callback, id = %08X:%08X, kind = %s, disk = %@.",
                                            DACallbackGetAddress( callback ),
                                            DACallbackGetContext( callback ),
                                            _DACallbackKindGetName( DACallbackGetKind( callback ) ),
                                            argument0 );

                                CFRelease( callback );
                            }

                            CFRelease( argument1 );
                        }

                        responseID++;
                    }

                    break;
                }
                case _kDADiskEjectApprovalCallback:
                case _kDADiskMountApprovalCallback:
                case _kDADiskPeekCallback:
                case _kDADiskUnmountApprovalCallback:
                {
                    if ( DASessionGetState( session, kDASessionStateTimeout ) == FALSE )
                    {
                        if ( DADiskGetOption( argument0, kDADiskOptionPrivate ) == FALSE )
                        {
                            assert( argument1 == NULL );

                            argument1 = ___CFNumberCreateWithIntegerValue( kCFAllocatorDefault, responseID );

                            if ( argument1 )
                            {
                                DACallbackRef response;

                                response = DACallbackCreateCopy( kCFAllocatorDefault, callback );

                                if ( response )
                                {
                                    DACallbackSetDisk( response, argument0 );

                                    DACallbackSetArgument1( response, argument1 );

                                    DACallbackSetTime( response, CFAbsoluteTimeGetCurrent( ) );

                                    CFArrayAppendValue( gDAResponseList, response );

                                    CFRelease( response );
                                }

                                callback = DACallbackCreateCopy( kCFAllocatorDefault, callback );

                                if ( callback )
                                {
                                    DACallbackSetDisk( callback, argument0 );

                                    DACallbackSetArgument0( callback, DADiskGetSerialization( argument0 ) );

                                    DACallbackSetArgument1( callback, argument1 );

                                    DASessionQueueCallback( session, callback );

                                    DALogDebug( "  dispatched callback, id = %08X:%08X, kind = %s, disk = %@.",
                                                DACallbackGetAddress( callback ),
                                                DACallbackGetContext( callback ),
                                                _DACallbackKindGetName( DACallbackGetKind( callback ) ),
                                                argument0 );

                                    CFRelease( callback );
                                }

                                CFRelease( argument1 );
                            }

                            responseID++;
                        }
                    }

                    break;
                }
                case _kDADiskDescriptionChangedCallback:
                {
                    if ( DADiskGetState( argument0, kDADiskStateZombie ) == FALSE )
                    {
                        CFMutableArrayRef intersection;

                        if ( DADiskGetOption( argument0, kDADiskOptionPrivate ) == FALSE )
                        {
                            CFArrayRef watch;

                            watch = DACallbackGetWatch( callback );

                            if ( watch )
                            {
                                intersection = CFArrayCreateMutableCopy( kCFAllocatorDefault, 0, argument1 );

                                if ( intersection )
                                {
                                    ___CFArrayIntersect( intersection, watch );
                                }
                            }
                            else
                            {
                                intersection = ( void * ) CFRetain( argument1 );
                            }

                            if ( intersection )
                            {
                                if ( CFArrayGetCount( intersection ) )
                                {
                                    callback = DACallbackCreateCopy( kCFAllocatorDefault, callback );

                                    if ( callback )
                                    {
                                        CFIndex count;
                                        CFIndex index;

                                        count = CFArrayGetCount( intersection );

                                        for ( index = 0; index < count; index++ )
                                        {
                                            DALogDebug( "  dispatched callback, id = %08X:%08X, kind = %s, disk = %@, key = %@.",
                                                        DACallbackGetAddress( callback ),
                                                        DACallbackGetContext( callback ),
                                                        _DACallbackKindGetName( DACallbackGetKind( callback ) ),
                                                        argument0,
                                                        CFArrayGetValueAtIndex( intersection, index ) );
                                        }

                                        DACallbackSetDisk( callback, argument0 );

                                        DACallbackSetArgument0( callback, DADiskGetSerialization( argument0 ) );

                                        DACallbackSetArgument1( callback, intersection );

                                        DASessionQueueCallback( session, callback );

                                        CFRelease( callback );
                                    }
                                }

                                CFRelease( intersection );
                            }
                        }
                    }

                    break;
                }
                case _kDAIdleCallback:
                {
                    callback = DACallbackCreateCopy( kCFAllocatorDefault, callback );

                    if ( callback )
                    {
                        DASessionQueueCallback( session, callback );

                        DALogDebug( "  dispatched callback, id = %08X:%08X, kind = %s.",
                                    DACallbackGetAddress( callback ),
                                    DACallbackGetContext( callback ),
                                    _DACallbackKindGetName( DACallbackGetKind( callback ) ) );

                        CFRelease( callback );
                    }

                    break;
                }
            }
        }
    }
}

void DAQueueCallbacks( DASessionRef session, _DACallbackKind kind, DADiskRef argument0, CFTypeRef argument1 )
{
    CFArrayRef callbacks;

    callbacks = DASessionGetCallbackRegister( session );

    if ( callbacks )
    {
        CFIndex count;
        CFIndex index;

        count = CFArrayGetCount( callbacks );

        for ( index = 0; index < count; index++ )
        {
            DACallbackRef callback;

            callback = ( void * ) CFArrayGetValueAtIndex( callbacks, index );

            if ( DACallbackGetKind( callback ) == kind )
            {
                DAQueueCallback( callback, argument0, argument1 );
            }
        }
    }
}

void DAQueueReleaseDisk( DADiskRef disk )
{
    CFIndex count;
    CFIndex index;

    count = CFArrayGetCount( gDAResponseList );

    for ( index = count - 1; index > -1; index-- )
    {
        DACallbackRef callback;

        callback = ( void * ) CFArrayGetValueAtIndex( gDAResponseList, index );

        if ( DACallbackGetDisk( callback ) == disk )
        {
            CFArrayRemoveValueAtIndex( gDAResponseList, index );

            __DAResponseComplete( disk );
        }
    }

    count = CFArrayGetCount( gDARequestList );

    for ( index = count - 1; index > -1; index-- )
    {
        DARequestRef request;

        request = ( void * ) CFArrayGetValueAtIndex( gDARequestList, index );

        if ( DARequestGetDisk( request ) == disk )
        {
            DARequestDispatchCallback( request, kDAReturnNotFound );

            CFArrayRemoveValueAtIndex( gDARequestList, index );
        }            
    }
}

void DAQueueReleaseSession( DASessionRef session )
{
    CFIndex count;
    CFIndex index;

    count = CFArrayGetCount( gDAResponseList );

    for ( index = count - 1; index > -1; index-- )
    {
        DACallbackRef callback;

        callback = ( void * ) CFArrayGetValueAtIndex( gDAResponseList, index );

        if ( DACallbackGetSession( callback ) == session )
        {
            DADiskRef disk;

            disk = DACallbackGetDisk( callback );

            CFArrayRemoveValueAtIndex( gDAResponseList, index );

            __DAResponseComplete( disk );
        }
    }

    count = CFArrayGetCount( gDARequestList );

    for ( index = count - 1; index > -1; index-- )
    {
        DARequestRef request;

        request = ( void * ) CFArrayGetValueAtIndex( gDARequestList, index );

        if ( request )
        {
            DACallbackRef callback;

            callback = DARequestGetCallback( request );

            if ( callback )
            {
                if ( DACallbackGetSession( callback ) == session )
                {
                    DARequestSetCallback( request, NULL );
                }
            }
        }
    }

    count = CFArrayGetCount( gDADiskList );

    for ( index = count - 1; index > -1; index-- )
    {
        DADiskRef disk;

        disk = ( void * ) CFArrayGetValueAtIndex( gDADiskList, index );

        if ( disk )
        {
            DACallbackRef callback;

            callback = DADiskGetClaim( disk );

            if ( callback )
            {
                if ( DACallbackGetSession( callback ) == session )
                {
                    DADiskSetClaim( disk, NULL );
                }
            }
        }
    }
}

void DAQueueRequest( DARequestRef request )
{
    DAReturn status;

    status = kDAReturnSuccess;

    switch ( DARequestGetKind( request ) )
    {
        case _kDADiskEject:
        case _kDADiskMount:
        case _kDADiskUnmount:
        {
            DADiskMountOptions options;

            options = DARequestGetArgument1( request );

            assert( kDADiskMountOptionWhole == kDADiskUnmountOptionWhole );

            if ( DARequestGetKind( request ) == _kDADiskEject )
            {
                options |= kDADiskMountOptionWhole;
            }

            if ( ( options & kDADiskMountOptionWhole ) )
            {
                DADiskRef disk;

                disk = DARequestGetDisk( request );

                if ( DARequestGetArgument2( request ) )
                {
                    status = kDAReturnBadArgument;
                }

                if ( DARequestGetArgument3( request ) )
                {
                    status = kDAReturnBadArgument;
                }

                if ( DADiskGetDescription( disk, kDADiskDescriptionMediaWholeKey ) == NULL )
                {
                    status = kDAReturnUnsupported;
                }
                
                if ( DADiskGetDescription( disk, kDADiskDescriptionMediaWholeKey ) == kCFBooleanFalse )
                {
                    status = kDAReturnUnsupported;
                }

                if ( status )
                {
                    break;
                }
                else
                {
                    CFMutableArrayRef link;

                    link = CFArrayCreateMutable( kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks );

                    if ( link )
                    {
                        CFIndex count;
                        CFIndex index;

                        count = CFArrayGetCount( gDADiskList );

                        for ( index = 0; index < count; index++ )
                        {
                            DADiskRef subdisk;

                            subdisk = ( void * ) CFArrayGetValueAtIndex( gDADiskList, index );

                            if ( disk != subdisk )
                            {
                                if ( DADiskGetBSDUnit( disk ) == DADiskGetBSDUnit( subdisk ) )
                                {
                                    DARequestRef subrequest;

                                    subrequest = DARequestCreate( kCFAllocatorDefault,
                                                                  DARequestGetKind( request ),
                                                                  subdisk,
                                                                  options,
                                                                  NULL,
                                                                  NULL,
                                                                  DARequestGetUserUID( request ),
                                                                  DARequestGetUserGID( request ),
                                                                  NULL );

                                    if ( subrequest )
                                    {
                                        CFArrayAppendValue( link, subrequest );

                                        CFArrayAppendValue( gDARequestList, subrequest );

                                        CFRelease( subrequest );
                                    }
                                }
                            }
                        }

                        DARequestSetLink( request, link );

                        CFRelease( link );
                    }
                }
            }

            break;
        }
    }

    if ( status )
    {
        DARequestDispatchCallback( request, status );
    }
    else
    {
        CFArrayAppendValue( gDARequestList, request );

        DAStageSignal( );
    }
}
