/*++

Copyright (c) 1996 Microsoft Corporation

Module Name:

    group.c

Abstract:

    This module contains Group ID managment routines.

    Group IDs identify an AFD_GROUP_ENTRY structure in a lookup table.
    Each AFD_GROUP_ENTRY contains a reference count and a type (either
    GroupTypeConstrained or GroupTypeUnconstrained). Free group IDs are
    linked together in a doubly-linked list. As group IDs are allocated,
    they are removed from this list. Once the free list becomes empty,
    the lookup table is grown appropriately.

Author:

    Keith Moore (keithmo)        06-Jun-1996

Revision History:

--*/

#include "afdp.h"


//
// Private constants.
//

#define AFD_GROUP_TABLE_GROWTH  32  // entries


//
// Private types.
//

typedef struct _AFD_GROUP_ENTRY {
    union {
        LIST_ENTRY ListEntry;
        struct {
            AFD_GROUP_TYPE GroupType;
            LONG ReferenceCount;
        };
    };
} AFD_GROUP_ENTRY, *PAFD_GROUP_ENTRY;


//
// Private globals.
//

PERESOURCE AfdGroupTableResource;
PAFD_GROUP_ENTRY AfdGroupTable;
LIST_ENTRY AfdFreeGroupList;
LONG AfdGroupTableSize;


//
// Private functions.
//

PAFD_GROUP_ENTRY
AfdMapGroupToEntry(
    IN LONG Group
    );


#ifdef ALLOC_PRAGMA
#pragma alloc_text( INIT, AfdInitializeGroup )
#pragma alloc_text( PAGE, AfdTerminateGroup )
#pragma alloc_text( PAGE, AfdReferenceGroup )
#pragma alloc_text( PAGE, AfdDereferenceGroup )
#pragma alloc_text( PAGE, AfdGetGroup )
#endif


BOOLEAN
AfdInitializeGroup(
    VOID
    )

/*++

Routine Description:

    Initializes any globals necessary for the group ID package.

Return Value:

    BOOLEAN - TRUE if successful, FALSE otherwise.

--*/

{

    //
    // Initialize the group globals.
    //

    AfdGroupTableResource = AFD_ALLOCATE_POOL_PRIORITY(
                                NonPagedPool,
                                sizeof(*AfdGroupTableResource),
                                AFD_RESOURCE_POOL_TAG,
                                HighPoolPriority
                                );

    if( AfdGroupTableResource == NULL ) {

        return FALSE;

    }

    ExInitializeResourceLite( AfdGroupTableResource );

    AfdGroupTable = NULL;
    InitializeListHead( &AfdFreeGroupList );
    AfdGroupTableSize = 0;

    return TRUE;

}   // AfdInitializeGroup


VOID
AfdTerminateGroup(
    VOID
    )

/*++

Routine Description:

    Destroys any globals created for the group ID package.

--*/

{

    if( AfdGroupTableResource != NULL ) {

        ExDeleteResourceLite( AfdGroupTableResource );

        AFD_FREE_POOL(
            AfdGroupTableResource,
            AFD_RESOURCE_POOL_TAG
            );

        AfdGroupTableResource = NULL;

    }

    if( AfdGroupTable != NULL ) {

        AFD_FREE_POOL(
            AfdGroupTable,
            AFD_GROUP_POOL_TAG
            );

        AfdGroupTable = NULL;

    }

    InitializeListHead( &AfdFreeGroupList );
    AfdGroupTableSize = 0;

}   // AfdTerminateGroup


BOOLEAN
AfdReferenceGroup(
    IN LONG Group,
    OUT PAFD_GROUP_TYPE GroupType
    )

/*++

Routine Description:

    Bumps the reference count associated with the given group ID.

Arguments:

    Group - The group ID to reference.

    GroupType - Returns the type of the group.

Returns:

    BOOLEAN - TRUE if the group ID was valid, FALSE otherwise.

--*/

{

    PAFD_GROUP_ENTRY groupEntry;
    AFD_GROUP_TYPE groupType;

    groupEntry = AfdMapGroupToEntry( Group );

    if( groupEntry != NULL ) {

        groupType = groupEntry->GroupType;

        if( groupType == GroupTypeConstrained ||
            groupType == GroupTypeUnconstrained ) {

            groupEntry->ReferenceCount++;
            *GroupType = groupType;

        } else {

            groupEntry = NULL;

        }

        ExReleaseResourceLite( AfdGroupTableResource );
        KeLeaveCriticalRegion ();

    }

    return (BOOLEAN)( groupEntry != NULL );

}   // AfdReferenceGroup


BOOLEAN
AfdDereferenceGroup(
    IN LONG Group
    )

/*++

Routine Description:

    Decrements the reference count associated with the given group ID.
    If the ref count drops to zero, the group ID is freed.

Arguments:

    Group - The group ID to dereference.

Returns:

    BOOLEAN - TRUE if the group ID was valid, FALSE otherwise.

--*/

{

    PAFD_GROUP_ENTRY groupEntry;
    AFD_GROUP_TYPE groupType;

    groupEntry = AfdMapGroupToEntry( Group );

    if( groupEntry != NULL ) {

        groupType = groupEntry->GroupType;

        if( groupType == GroupTypeConstrained ||
            groupType == GroupTypeUnconstrained ) {

            ASSERT( groupEntry->ReferenceCount > 0 );
            groupEntry->ReferenceCount--;

            if( groupEntry->ReferenceCount == 0 ) {

                InsertTailList(
                    &AfdFreeGroupList,
                    &groupEntry->ListEntry
                    );

            }

        } else {

            groupEntry = NULL;

        }

        ExReleaseResourceLite( AfdGroupTableResource );
        KeLeaveCriticalRegion ();

    }

    return (BOOLEAN)( groupEntry != NULL );

}   // AfdDereferenceGroup


BOOLEAN
AfdGetGroup(
    IN OUT PLONG Group,
    OUT PAFD_GROUP_TYPE GroupType
    )

/*++

Routine Description:

    Examines the incoming group. If is zero, then nothing is done. If it
    is SG_CONSTRAINED_GROUP, then a new constrained group ID is created.
    If it is SG_UNCONSTRAINED_GROUP, then a new unconstrained group ID is
    created. Otherwise, it must identify an existing group, so that group
    is referenced.

Arguments:

    Group - Points to the group ID to examine/modify.

    GroupType - Returns the type of the group.

Return Value:

    BOOLEAN - TRUE if successful, FALSE otherwise.

--*/

{

    LONG groupValue;
    PAFD_GROUP_ENTRY groupEntry;
    PAFD_GROUP_ENTRY newGroupTable;
    LONG newGroupTableSize;
    LONG i;
    PLIST_ENTRY listEntry;

    groupValue = *Group;

    //
    // Zero means "no group", so just ignore it.
    //

    if( groupValue == 0 ) {

        *GroupType = GroupTypeNeither;
        return TRUE;

    }

    //
    // If we're being asked to create a new group, do it.
    //

    if( groupValue == SG_CONSTRAINED_GROUP ||
        groupValue == SG_UNCONSTRAINED_GROUP ) {

        //
        // Lock the table.
        //

        //
        // Make sure the thread in which we execute cannot get
        // suspeneded in APC while we own the global resource.
        //
        KeEnterCriticalRegion ();
        ExAcquireResourceExclusiveLite( AfdGroupTableResource, TRUE );

        //
        // See if there's room at the inn.
        //

        if( IsListEmpty( &AfdFreeGroupList ) ) {

            //
            // No room, we'll need to create/expand the table.
            //

            newGroupTableSize = AfdGroupTableSize + AFD_GROUP_TABLE_GROWTH;

            newGroupTable = AFD_ALLOCATE_POOL(
                                PagedPool,
                                newGroupTableSize * sizeof(AFD_GROUP_ENTRY),
                                AFD_GROUP_POOL_TAG
                                );

            if( newGroupTable == NULL ) {

                ExReleaseResourceLite( AfdGroupTableResource );
                KeLeaveCriticalRegion ();
                return FALSE;

            }

            if( AfdGroupTable == NULL ) {

                //
                // This is the initial table allocation, so reserve the
                // first three entries (0, SG_UNCONSTRAINED_GROUP, and
                // SG_CONSTRAINED_GROUP).
                //

                for( ;
                     AfdGroupTableSize <= SG_CONSTRAINED_GROUP ||
                     AfdGroupTableSize <= SG_UNCONSTRAINED_GROUP ;
                     AfdGroupTableSize++ ) {

                    newGroupTable[AfdGroupTableSize].ReferenceCount = 0;
                    newGroupTable[AfdGroupTableSize].GroupType = GroupTypeNeither;

                }

            } else {

                //
                // Copy the old table into the new table, then free the
                // old table.
                //

                RtlCopyMemory(
                    newGroupTable,
                    AfdGroupTable,
                    AfdGroupTableSize * sizeof(AFD_GROUP_ENTRY)
                    );

                AFD_FREE_POOL(
                    AfdGroupTable,
                    AFD_GROUP_POOL_TAG
                    );

            }

            //
            // Add the new entries to the free list.
            //

            for( i = newGroupTableSize - 1 ; i >= AfdGroupTableSize ; i-- ) {

                InsertHeadList(
                    &AfdFreeGroupList,
                    &newGroupTable[i].ListEntry
                    );

            }

            AfdGroupTable = newGroupTable;
            AfdGroupTableSize = newGroupTableSize;

        }

        //
        // Pull the next free entry off the list.
        //

        ASSERT( !IsListEmpty( &AfdFreeGroupList ) );

        listEntry = RemoveHeadList( &AfdFreeGroupList );

        groupEntry = CONTAINING_RECORD(
                         listEntry,
                         AFD_GROUP_ENTRY,
                         ListEntry
                         );

        groupEntry->ReferenceCount = 1;
        groupEntry->GroupType = (AFD_GROUP_TYPE)groupValue;

        *Group = (LONG)( groupEntry - AfdGroupTable );
        *GroupType = groupEntry->GroupType;

        ExReleaseResourceLite( AfdGroupTableResource );
        KeLeaveCriticalRegion ();
        return TRUE;

    }

    //
    // Otherwise, just reference the group.
    //

    return AfdReferenceGroup( groupValue, GroupType );

}   // AfdGetGroup


PAFD_GROUP_ENTRY
AfdMapGroupToEntry(
    IN LONG Group
    )

/*++

Routine Description:

    Maps the given group ID to the corresponding AFD_GROUP_ENTRY structure.

    N.B. This routine returns with AfdGroupTableResource held if successful.

Arguments:

    Group - The group ID to map.

Return Value:

    PAFD_GROUP_ENTRY - The entry corresponding to the group ID if successful,
        NULL otherwise.

--*/

{

    PAFD_GROUP_ENTRY groupEntry;

    //
    // Lock the table.
    //

    //
    // Make sure the thread in which we execute cannot get
    // suspeneded in APC while we own the global resource.
    //
    KeEnterCriticalRegion ();
    ExAcquireResourceExclusiveLite( AfdGroupTableResource, TRUE );

    //
    // Validate the group ID.
    //

    if( Group > 0 && Group < AfdGroupTableSize ) {

        groupEntry = AfdGroupTable + Group;

        //
        // The group ID is within legal range. Ensure it's in use.
        // In the AFD_GROUP_ENTRY structure, the GroupType field is
        // overlayed with ListEntry.Flink due to the internal union.
        // We can use this knowledge to quickly validate that this
        // entry is in use.
        //

        if( groupEntry->GroupType == GroupTypeConstrained ||
            groupEntry->GroupType == GroupTypeUnconstrained ) {

            return groupEntry;

        }

    }

    //
    // Invalid group ID, fail it.
    //

    ExReleaseResourceLite( AfdGroupTableResource );
    KeLeaveCriticalRegion ();
    return NULL;

}   // AfdMapGroupToEntry