/*++

Copyright (c) 1993  Microsoft Corporation

Module Name:

    boot.c

Abstract:

    This module contains RPL boot apis: - internal routines only.

Author:

    Vladimir Z. Vulovic     (vladimv)       10 - November - 1993

Revision History:

    10-Nov-1993     vladimv
        Created

--*/

#include "local.h"
#include "rpldb.h"
#include "db.h"
#include "dblib.h"
#include "config.h"
#include "profile.h"
#include "wksta.h"
#define RPLBOOT_ALLOCATE
#include "boot.h"
#undef RPLBOOT_ALLOCATE
#include "database.h"



DWORD BootGetField(
    IN      PRPL_SESSION    pSession,
    IN      DWORD           FieldIndex,
    OUT     LPVOID *        pData,
    IN OUT  LPINT           pSpaceLeft
    )
{
    BYTE                LocalBuffer[ 300];
    PBYTE               Buffer;
    DWORD               DataSize;
    DWORD               BufferSize;
    JET_ERR             JetError;

    switch( FieldIndex) {
    case BOOT_WindowSize:
    case BOOT_Flags:
    case BOOT_VendorId:
        Buffer = (PBYTE)pData;
        BufferSize = sizeof( DWORD);
        break;
    default:
        Buffer = LocalBuffer;
        BufferSize = sizeof( LocalBuffer);
        break;
    }
    JetError = JetRetrieveColumn( pSession->SesId, pSession->BootTableId,
        BootTable[ FieldIndex].ColumnId, Buffer,
        BufferSize, &DataSize, 0, NULL);
    if ( JetError < 0) {
        RplDump( ++RG_Assert, ("JetError=%d", JetError));
        return( NERR_RplBootInfoCorrupted);
    }
    if ( Buffer != LocalBuffer) {
        if ( BufferSize == DataSize) {
            return( NO_ERROR);
        } else {
            RplDump( ++RG_Assert, ("Bad DataSize=0x%x", DataSize));
            return( NERR_RplBootInfoCorrupted);
        }
    }
    //
    //  We have done with fixed data.  From here on we deal with unicode
    //  strings only.
    //
    if ( DataSize > sizeof( LocalBuffer)) {
        RplDump( ++RG_Assert, ( "Too big DataSize=0x%x", DataSize));
        return( NERR_RplBootInfoCorrupted);
    }
    if ( DataSize == 0) {
        if ( JetError != JET_wrnColumnNull) {
            RplDump( ++RG_Assert, ( "JetError=%d", JetError));
            return( NERR_RplBootInfoCorrupted);
        } else {
            *pData = NULL; // so RPC rpcrt4!_tree_size_ndr() does not bomb here
            return( NO_ERROR);
        }
    }
    if ( DataSize & 1 != 0  ||  wcslen((PWCHAR)LocalBuffer) + 1 != DataSize/2) {
        RplDump( ++RG_Assert, ("LocalBuffer=0x%x, DataSize=0x%x", LocalBuffer, DataSize));
        return( NERR_RplBootInfoCorrupted);
    }
    *pData = MIDL_user_allocate( DataSize);
    if ( *pData == NULL) {
        RplDump( ++RG_Assert, ( "Error=%d", GetLastError()));
        RPL_RETURN( ERROR_NOT_ENOUGH_MEMORY);
    }
    memcpy( *pData, LocalBuffer, DataSize);
    *pSpaceLeft -= DataSize;
    return( NO_ERROR);
}


BOOL BootResumeFirst(
    IN      PRPL_SESSION    pSession,
    IN      LPDWORD         pResumeHandle,
    OUT     PBOOL           pTableEnd
    )
/*++
    Set currency to the first boot record following the boot record
    described by the resume handle.
--*/
{
    BYTE        ResumeValue[ RPL_MAX_VENDOR_NAME_SIZE + RPL_MAX_WKSTA_NAME_SIZE];
    PWCHAR      BootName;
    DWORD       BootNameSize;
    DWORD       ResumeSize;
    JET_ERR     JetError;

    *pTableEnd = FALSE;

    CallB( JetSetCurrentIndex( pSession->SesId, pSession->BootTableId, BOOT_INDEX_VendorIdBootName));
    //
    //  The call to move to the beginning of the table is not redundant.
    //  E.g. JET_errNoCurrentRecord will be returned in case of empty table.
    //
    JetError = JetMove( pSession->SesId, pSession->BootTableId, JET_MoveFirst, 0);
    if ( JetError < 0) {
        if ( JetError == JET_errRecordNotFound
                ||  JetError == JET_errNoCurrentRecord) {
            *pTableEnd = TRUE;
            return( TRUE);
        } else {
            RplDump( ++RG_Assert, ("JetError=%d", JetError));
            return( FALSE);
        }
    }
    if ( !(ARGUMENT_PRESENT( pResumeHandle))  || *pResumeHandle == 0) {
       return( TRUE);   //  not resuming, all done
    }
    ResumeSize = sizeof( ResumeValue);
    if ( !ResumeKeyGet( pSession, *pResumeHandle, ResumeValue, &ResumeSize)) {
        return( FALSE);
    }
    CallB( JetMakeKey( pSession->SesId, pSession->BootTableId, ResumeValue, sizeof( DWORD), JET_bitNewKey));
    BootName = (PWCHAR)( ResumeValue + sizeof(DWORD));
    BootNameSize = (DWORD)( ResumeSize - sizeof(DWORD));
    if ( BootNameSize != (wcslen( BootName) + 1) * sizeof( WCHAR)) {
        RplDump( ++RG_Assert, ("ResumeValue=0x%x, ResumeSize=0x%x", ResumeValue, ResumeSize));
        return( FALSE);
    }
    CallB( JetMakeKey( pSession->SesId, pSession->BootTableId, BootName, BootNameSize, 0));
    CallB( JetSeek( pSession->SesId, pSession->BootTableId, JET_bitSeekGT));
    return( TRUE);
}


VOID BootResumeSave(
    IN      PRPL_SESSION    pSession,
    IN      DWORD           ServerHandle,
    IN      DWORD           VendorId,
    IN      PWCHAR          BootName,
    IN      PDWORD          pResumeHandle
    )
/*++
    This all other other functions that save resume keys is void, because there
    is no documented way of returning error cannot resume to the client.
--*/
{
    BYTE        ResumeBuffer[ 30 * sizeof(WCHAR)];
    DWORD       BootSize;

    memcpy( ResumeBuffer, &VendorId, sizeof( VendorId));
    BootSize = ( wcslen( BootName) + 1) * sizeof(WCHAR);
    memcpy( ResumeBuffer + sizeof(VendorId), BootName, BootSize);
    (VOID)ResumeKeySet( pSession, (DWORD)ServerHandle, ResumeBuffer, BootSize+sizeof(VendorId), pResumeHandle);
}


DWORD BootFilterFind(
    IN      PRPL_SESSION    pSession,
    IN OUT  PRPL_FILTER     pFilter,
    IN OUT  PBOOL           pTableEnd
    )
/*++
    Returns name of the the first/next BOOT structure supporting a given
    VendorId.
    The "first" BOOT does not have to be absolutely first, but first behind
    the BOOT specified by RPL_FILTER.
    In case such BOOT structure cannot be found, returns some kind of error.

Arguments:

    pFilter         -   pointer to RPL_FILTER structure with the following fields
        FindFirst       :   find first (TRUE) or find next (FALSE)
        VendorId        :   vendor id to be suppored (input only)
        BootName        :   buffer large enough to hold the longest
                                BootName string.  When input value of
                                BootNameSize is non-zero, this contains the
                                previous value of BootName string.
                            output value is NOT valid if error or end of table
        BootNameSize    :  at input this contains the size of the previous
                                BootName string.  If this size is zero/non-zero,
                                it means we are looking for the first/next
                                BOOT structure.
                            at output this cotains the size of BootName string
                            output value is NOT valid if error or end of table

    pTableEnd       -   pointer to boolean (input value is FALSE) which will be set
                            to TRUE if we reach end of boot table
-**/
{
    DWORD       CheckVendorId;
    DWORD       DataSize;
    JET_ERR     JetError;

    if ( pFilter->FindFirst == FALSE) {
        JetError = JetMove( pSession->SesId, pSession->BootTableId, JET_MoveNext, 0);
        if ( JetError != JET_errSuccess) {
            if ( JetError == JET_errNoCurrentRecord) {
                RplDump( RG_DebugLevel & RPL_DEBUG_BOOT, ("JetError=%d", JetError));
            } else {
                RplDump( ++RG_Assert, ("JetError=%d", JetError));
            }
            goto cleanup;
        }
    } else {
        pFilter->FindFirst = FALSE;
        CallM( JetSetCurrentIndex( pSession->SesId, pSession->BootTableId, BOOT_INDEX_VendorIdBootName));
        CallM( JetMakeKey( pSession->SesId, pSession->BootTableId, &pFilter->VendorId, sizeof( pFilter->VendorId), JET_bitNewKey));
        if ( pFilter->BootNameSize != 0) {
            //
            //  We are continuing the search from the previous BOOT structure.
            //
            CallM( JetMakeKey( pSession->SesId, pSession->BootTableId, pFilter->BootName, pFilter->BootNameSize, 0));
        }
        //
        //  In case of seek errors assume we have reached end of table.
        //
        JetError = JetSeek( pSession->SesId, pSession->BootTableId, JET_bitSeekGT);
        if ( JetError != JET_errSuccess) {
            RplDump( RG_DebugLevel & RPL_DEBUG_BOOT, ("BootFilterFind: Seek(0x%x) => JetError=%d",
                pFilter->VendorId, JetError));
            goto cleanup;
        }
        //
        //  Verify that element we seeked to has the proper vendor id.
        //
        CallM( JetMakeKey( pSession->SesId, pSession->BootTableId, &pFilter->VendorId, sizeof( pFilter->VendorId), JET_bitNewKey));
        JetError = JetSetIndexRange( pSession->SesId, pSession->BootTableId,
                JET_bitRangeInclusive | JET_bitRangeUpperLimit);
        if ( JetError != JET_errSuccess) {
            RplDump( RG_DebugLevel & RPL_DEBUG_BOOT, ("BootFilterFind: SetIndexRange(0x%x) => JetError=%d",
                pFilter->VendorId, JetError));
            goto cleanup;
        }
    }
#ifdef RPL_DEBUG
    CallM( JetRetrieveColumn( pSession->SesId, pSession->BootTableId,
        BootTable[ BOOT_VendorId].ColumnId, &CheckVendorId,
        sizeof( CheckVendorId), &DataSize, 0, NULL));
    if ( CheckVendorId != pFilter->VendorId) {
        RplDump( ++RG_Assert, ( "CheckVendorId=0x%x"));
    }
#endif // RPL_DEBUG
    CallM( JetRetrieveColumn( pSession->SesId, pSession->BootTableId,
        BootTable[ BOOT_BootName].ColumnId, pFilter->BootName,
        RPL_MAX_BOOT_NAME_SIZE, &pFilter->BootNameSize, 0, NULL));
cleanup:
    if ( JetError < 0) {
        *pTableEnd = TRUE;     // pretend it is end of table
    }
    return( NO_ERROR);
}


DWORD BootSetInfo(
    IN      PRPL_SESSION        pSession,
    IN      DWORD               Level,
    IN      LPVOID              Buffer,
    OUT     LPDWORD             pErrorParameter
    )
{
    LPRPL_BOOT_INFO_2         Info = Buffer;
    switch( Level) {
    case 2:
        if ( Info->WindowSize != -1) {
            *pErrorParameter = BOOT_WindowSize;
            CallM( JetSetColumn( pSession->SesId, pSession->BootTableId,
                BootTable[ BOOT_WindowSize].ColumnId,
                &Info->WindowSize, sizeof( Info->WindowSize), 0, NULL));
        }
        if ( Info->BbcFile != NULL) {
            *pErrorParameter = BOOT_BbcFile;
            CallM( JetSetColumn( pSession->SesId, pSession->BootTableId,
                BootTable[ BOOT_BbcFile].ColumnId,
                Info->BbcFile,
                ( wcslen( Info->BbcFile) + 1) * sizeof(WCHAR),
                0, NULL));
        }
        NOTHING;    //  fall through
    case 1:
        if ( Info->VendorName != NULL) {
            DWORD       VendorId;
            *pErrorParameter = BOOT_VendorName;
            CallM( JetSetColumn( pSession->SesId, pSession->BootTableId,
                BootTable[ BOOT_VendorName].ColumnId,
                Info->VendorName,
                ( wcslen( Info->VendorName) + 1) * sizeof(WCHAR),
                0, NULL));
            VendorId = wcstoul( Info->VendorName, NULL, 16);
            CallM( JetSetColumn( pSession->SesId, pSession->BootTableId,
                BootTable[ BOOT_VendorId].ColumnId,
                &VendorId, sizeof( VendorId), 0, NULL));
        }
        if ( Info->Flags != 0) {
            *pErrorParameter = BOOT_Flags;
            CallM( JetSetColumn( pSession->SesId, pSession->BootTableId,
                BootTable[ BOOT_Flags].ColumnId,
                &Info->Flags, sizeof( Info->Flags), 0, NULL));
        }
        NOTHING;    //  fall through
    case 0:
        if ( Info->BootComment != NULL) {
            *pErrorParameter = BOOT_BootComment;
            CallM( JetSetColumn( pSession->SesId, pSession->BootTableId,
                BootTable[ BOOT_BootComment].ColumnId,
                Info->BootComment,
                ( wcslen( Info->BootComment) + 1) * sizeof(WCHAR),
                0, NULL));
        }
        if ( Info->BootName != NULL) {
            *pErrorParameter = BOOT_BootName;
            CallM( JetSetColumn( pSession->SesId, pSession->BootTableId,
                BootTable[ BOOT_BootName].ColumnId,
                Info->BootName,
                ( wcslen( Info->BootName) + 1) * sizeof(WCHAR),
                0, NULL));
        }
        break;
    }
    return( NO_ERROR);
}


DWORD BootGetInfo(
    IN      PRPL_SESSION        pSession,
    OUT     PDWORD              pVendorId,
    IN      DWORD               Level,
    OUT     LPVOID              Buffer,
    IN OUT  PINT                pSpaceLeft
    )
{
    DWORD                   Error;
    DWORD                   WhoCares;
    LPRPL_BOOT_INFO_2       Info = Buffer;

    switch( Level) {
    case 2:
        Error = BootGetField( pSession, BOOT_WindowSize, (LPVOID *)&Info->WindowSize, pSpaceLeft);
        if ( Error != NO_ERROR) {
            return( Error);
        }
        Error = BootGetField( pSession, BOOT_BbcFile, &Info->BbcFile, pSpaceLeft);
        if ( Error != NO_ERROR) {
            return( Error);
        }
        NOTHING;    //  fall through
    case 1:
        Error = BootGetField( pSession, BOOT_Flags, (LPVOID *)&Info->Flags, pSpaceLeft);
        if ( Error != NO_ERROR) {
            return( Error);
        }
        Error = BootGetField( pSession, BOOT_VendorName, &Info->VendorName, pSpaceLeft);
        if ( Error != NO_ERROR) {
            return( Error);
        }
        NOTHING;    //  fall through
    case 0:
        Error = BootGetField( pSession, BOOT_BootComment, &Info->BootComment, pSpaceLeft);
        if ( Error != NO_ERROR) {
            return( Error);
        }
        Error = BootGetField( pSession, BOOT_BootName, &Info->BootName, pSpaceLeft);
        if ( Error != NO_ERROR) {
            return( Error);
        }
        Error = BootGetField( pSession, BOOT_VendorId, (LPVOID *)pVendorId, &WhoCares);
        if ( Error != NO_ERROR) {
            return( Error);
        }
        break;
    default:
        return( ERROR_INVALID_LEVEL);
        break;
    }
    return( NO_ERROR);
}



VOID BootGetInfoCleanup(
    IN      DWORD                       Level,
    IN OUT  LPVOID                      Buffer
    )
{
    LPRPL_BOOT_INFO_2     Info = Buffer;

    switch( Level) {
    case 2:
        if ( Info->BbcFile != NULL) {
            MIDL_user_free( Info->BbcFile);
        }
        NOTHING;    //  fall through
    case 1:
        if ( Info->VendorName != NULL) {
            MIDL_user_free( Info->VendorName);
        }
        NOTHING;    //  fall through
    case 0:
        if ( Info->BootComment != NULL) {
            MIDL_user_free( Info->BootComment);
        }
        if ( Info->BootName != NULL) {
            MIDL_user_free( Info->BootName);
        }
        break;
    }
}


NET_API_STATUS NET_API_FUNCTION
NetrRplBootAdd(
    IN      RPL_HANDLE                  ServerHandle,
    IN      DWORD                       Level,
    OUT     LPRPL_BOOT_INFO_STRUCT      BootInfoStruct,
    OUT     LPDWORD                     pErrorParameter      OPTIONAL
    )
{
    LPRPL_BOOT_INFO_2       Info;
    LPVOID                  Buffer;
    DWORD                   Error;
    DWORD                   ErrorParameter;
    DWORD                   VendorId;
    PRPL_SESSION            pSession = &RG_ApiSession;

    ErrorParameter = INVALID_ERROR_PARAMETER;
    Buffer = Info = BootInfoStruct->BootInfo2;

    switch( Level) {
    case 2:
        if ( Info->BbcFile == NULL || RPL_STRING_TOO_LONG( Info->BootComment)) {
            ErrorParameter = BOOT_BbcFile;
            break;
        }
        if ( !ValidHexName( Info->VendorName, RPL_VENDOR_NAME_LENGTH, TRUE)) {
            ErrorParameter = BOOT_VendorName;
            break;
        }
        _wcsupr( Info->VendorName);
        VendorId = wcstoul( Info->VendorName, NULL, 16);
        switch( Info->Flags) {
        case BOOT_FLAGS_FINAL_ACKNOWLEDGMENT_TRUE:
        case BOOT_FLAGS_FINAL_ACKNOWLEDGMENT_FALSE:
            break;
        default:
            ErrorParameter = BOOT_Flags;
            break;
        }
        if ( ErrorParameter != INVALID_ERROR_PARAMETER) {
            break;
        }
        if ( RPL_STRING_TOO_LONG( Info->BootComment)) {
            ErrorParameter = BOOT_BootComment;
            break;
        }
        if ( !ValidName( Info->BootName, RPL_MAX_BOOT_NAME_LENGTH, TRUE)) {
            ErrorParameter = BOOT_BootName;
            break;
        }
        _wcsupr( Info->BootName);
        break;
    default:
        return( ERROR_INVALID_LEVEL);
        break;
    }

    if ( ErrorParameter != INVALID_ERROR_PARAMETER) {
        if ( ARGUMENT_PRESENT( pErrorParameter)) {
            *pErrorParameter = ErrorParameter;
        }
        return( ERROR_INVALID_PARAMETER);
    }

    EnterCriticalSection( &RG_ProtectDatabase);
    Call( JetBeginTransaction( pSession->SesId));

    //
    //  Verify that BootName + VendorId is available in the database.
    //
    if ( BootFind( pSession, Info->BootName, VendorId)) {
        Error = NERR_RplBootNameUnavailable;
        goto cleanup;
    }

    CallJ( JetPrepareUpdate( pSession->SesId, pSession->BootTableId, JET_prepInsert));

    Error = BootSetInfo( pSession, Level, Buffer, &ErrorParameter);
    if ( Error == ERROR_SUCCESS) {
        ErrorParameter = 0;
        CallJ( JetUpdate( pSession->SesId, pSession->BootTableId, NULL, 0, NULL));
    }

cleanup:
    if ( Error == NO_ERROR) {
        Call( JetCommitTransaction( pSession->SesId, JET_bitCommitFlush));
    } else {
        Call( JetRollback( pSession->SesId, JET_bitRollbackAll));
    }
    LeaveCriticalSection( &RG_ProtectDatabase);

    if ( Error != ERROR_SUCCESS) {
         if ( ARGUMENT_PRESENT( pErrorParameter)) {
             *pErrorParameter = ErrorParameter;
         }
    }
    return( Error);
}



BOOL BootFind(
    IN      PRPL_SESSION        pSession,
    IN      PWCHAR              BootName,
    IN      DWORD               VendorId
    )
{
    JET_ERR         JetError;
    CallB( JetSetCurrentIndex( pSession->SesId, pSession->BootTableId, BOOT_INDEX_VendorIdBootName));
    CallB( JetMakeKey( pSession->SesId, pSession->BootTableId, &VendorId, sizeof(VendorId), JET_bitNewKey));
    CallB( JetMakeKey( pSession->SesId, pSession->BootTableId, BootName, ( wcslen( BootName) + 1) * sizeof(WCHAR), 0));
    JetError = JetSeek( pSession->SesId, pSession->BootTableId, JET_bitSeekEQ);
    if ( JetError < 0) {
#ifdef RPL_DEBUG
        //
        //  JET_errNoCurrentRecord will be returned in case of empty table.
        //
        if ( JetError != JET_errRecordNotFound
                &&  JetError != JET_errNoCurrentRecord) {
            RplDump( ++RG_Assert, ("JetError=%d", JetError));
        }
#endif
        return( FALSE);
    }
    return( TRUE);
}


NET_API_STATUS NET_API_FUNCTION
NetrRplBootDel(
    IN      RPL_HANDLE      ServerHandle,
    IN      LPWSTR          BootName,
    IN      LPWSTR          VendorName
    )
/*++
    If VendorName is NULL we should delete all records with input BootName.
    But for now we insist on valid input for VendorName.
--*/
{
    DWORD                   Error;
    DWORD                   VendorId;
    PRPL_SESSION            pSession = &RG_ApiSession;

    if ( !ValidName( BootName, RPL_MAX_BOOT_NAME_LENGTH, TRUE)) {
        return( ERROR_INVALID_PARAMETER);
    }
    _wcsupr( BootName);
    if ( !ValidHexName( VendorName, RPL_VENDOR_NAME_LENGTH, TRUE)) {
        return( ERROR_INVALID_PARAMETER);
    }
    VendorId = wcstoul( VendorName, NULL, 16);

    EnterCriticalSection( &RG_ProtectDatabase);
    Call( JetBeginTransaction( pSession->SesId));

    if ( RplFindByField( pSession, CONFIG_TABLE_TAG,
            CONFIG_INDEX_BootNameConfigName, BootName)) {
        //
        //  We found a CONFIG record which uses this BOOT.
        //
        Error = NERR_RplBootInUse;
        goto cleanup;
    }
    if ( RplFindByField( pSession, PROFILE_TABLE_TAG,
            PROFILE_INDEX_BootNameProfileName, BootName)) {
        //
        //  We found a PROFILE record which uses this BOOT.
        //
        Error = NERR_RplBootInUse;
        goto cleanup;
    }
    if ( RplFindByField( pSession, WKSTA_TABLE_TAG,
            WKSTA_INDEX_BootNameWkstaName, BootName)) {
        //
        //  We found a WKSTA record which uses this BOOT.
        //
        Error = NERR_RplBootInUse;
        goto cleanup;
    }
    if ( !BootFind( pSession, BootName, VendorId)) {
        Error = NERR_RplBootNotFound;
        goto cleanup;
    }
    CallJ( JetDelete( pSession->SesId, pSession->BootTableId));
    Error = NO_ERROR;

cleanup:
    if ( Error == NO_ERROR) {
        Call( JetCommitTransaction( pSession->SesId, JET_bitCommitFlush));
    } else {
        Call( JetRollback( pSession->SesId, JET_bitRollbackAll));
    }
    LeaveCriticalSection( &RG_ProtectDatabase);
    return( Error);
}


NET_API_STATUS NET_API_FUNCTION
NetrRplBootEnum(
    IN      RPL_RPC_HANDLE      ServerHandle,
    IN OUT  LPRPL_BOOT_ENUM     BootEnum,
    IN      DWORD               PrefMaxLength,
    OUT     LPDWORD             TotalEntries,
    IN OUT  LPDWORD             pResumeHandle        OPTIONAL
    )
/*++

Routine Description:

Arguments:

    pServerHandle - ptr to RPL_HANDLE

    InfoStruct - Pointer to a structure that contains the information that
        RPC needs about the returned data.  This structure contains the
        following information:
            Level - The desired information level - indicates how to
                interpret the structure of the returned buffer.
            EntriesRead - Indicates how many elements are returned in the
                 array of structures that are returned.
            BufferPointer - Location for the pointer to the array of
                 structures that are being returned.

    PrefMaxLen - Indicates a maximum size limit that the caller will allow
        for (the return buffer.

    TotalEntries - Pointer to a value that upon return indicates the total
        number of entries in the "active" database.

    pResumeHandle - Inidcates where to restart the enumeration.  This is an
        optional parameter and can be NULL.

Return Value:
    NO_ERROR if success.

--*/
{
    LPBYTE                  Buffer;
    DWORD                   TypicalSize;
    DWORD                   CoreSize;
    DWORD                   Error;
    INT                     SpaceLeft;
    DWORD                   ArrayLength;
    DWORD                   EntriesRead;
    JET_ERR                 JetError;
    BOOL                    InfoError;
    BOOL                    TableEnd;
    DWORD                   VendorId;
    PRPL_SESSION            pSession = &RG_ApiSession;

    switch( BootEnum->Level) {
    case 2:
        TypicalSize = CoreSize = sizeof( RPL_BOOT_INFO_2);
        TypicalSize += 40 * sizeof( WCHAR); //  typical size of BbcFile
        NOTHING;    //  fall through
    case 1:
        if ( BootEnum->Level == 1) {
            TypicalSize = CoreSize = sizeof( RPL_BOOT_INFO_1);
        }
        TypicalSize +=  8 * sizeof( WCHAR); //  typical size of VendorName
        NOTHING;    //  fall through
    case 0:
        if ( BootEnum->Level == 0) {
            TypicalSize = CoreSize = sizeof( RPL_BOOT_INFO_0);
        }
        TypicalSize += 20 * sizeof( WCHAR); //  typical size of BootComment
        TypicalSize +=  8 * sizeof( WCHAR); //  typical size of BootName
        break;
    default:
        return( ERROR_INVALID_LEVEL);
        break;
    }

    if ( PrefMaxLength == -1) {
        //
        //  If the caller has not specified a size, calculate a size
        //  that will hold the entire enumeration.
        //
        SpaceLeft = DEFAULT_BUFFER_SIZE;
    } else {
        SpaceLeft = PrefMaxLength;
    }

    //
    //  Buffer space is shared by the array and by strings pointed at
    //  by the elements in this array.  We need to decide up front how much
    //  space is allocated for array and how much for the strings.
    //
    ArrayLength = SpaceLeft / TypicalSize;
    if ( ArrayLength == 0) {
        ArrayLength = 1;    //  try to return at least one element
    }

    //
    //  Note that MIDL_user_allocate() returns memory which is NOT initialized
    //  to zero.  Since we do NOT use allocate all nodes, this means that all
    //  fields, especially pointers, in array elements must be properly set.
    //
    Buffer = MIDL_user_allocate( ArrayLength * CoreSize);
    if ( Buffer == NULL) {
        return( ERROR_NOT_ENOUGH_MEMORY);
    }
    RplDump( RG_DebugLevel & RPL_DEBUG_BOOT, (
        "BootEnum: Buffer=0x%x, ArrayLength=0x%x", Buffer, ArrayLength));

    BootEnum->BootInfo.Level0->Buffer = (LPRPL_BOOT_INFO_0)Buffer;

    EntriesRead = 0;
    InfoError = FALSE;
    Error = NO_ERROR;

    EnterCriticalSection( &RG_ProtectDatabase);
    Call( JetBeginTransaction( pSession->SesId));

    if ( !BootResumeFirst( pSession, pResumeHandle, &TableEnd)) {
        Error = NERR_RplCannotEnum;
        goto cleanup;
    }
    if ( TableEnd == TRUE) {
        goto cleanup;
    }
    for ( ; ; ) {
        memset( Buffer, 0, CoreSize); // for cleanup to work properly
        Error = BootGetInfo( pSession, &VendorId, BootEnum->Level, Buffer, &SpaceLeft);
        if ( Error != NO_ERROR) {
            InfoError = TRUE;   //  clean things up without holding crit sec
            break;
        }
        EntriesRead++;
        Buffer += CoreSize;
        SpaceLeft -= CoreSize;
        JetError = JetMove( pSession->SesId, pSession->BootTableId, JET_MoveNext, 0);
        if ( JetError != JET_errSuccess) {
            break; // assume end of table
        }
        if ( SpaceLeft <= 0) {
            Error = ERROR_MORE_DATA;
            break;
        }
        if ( EntriesRead >= ArrayLength) {
            //
            //  We have space available but allocated array is not big enough.
            //  This should NOT happen often as our intent (see above) is to
            //  overestimate array length.  When it happens we can still try
            //  to reallocate array to a larger size here.  This is not done
            //  for now (too cumbersome) & we just stop the enumeration.
            //
            Error = ERROR_MORE_DATA;
            break;
        }
    }
cleanup:
    Call( JetCommitTransaction( pSession->SesId, 0));
    LeaveCriticalSection( &RG_ProtectDatabase);
    if ( InfoError == TRUE) {
        BootGetInfoCleanup( BootEnum->Level, Buffer);
    }
    if ( Error == NO_ERROR) {
        *TotalEntries = EntriesRead;
    } else if ( Error == ERROR_MORE_DATA) {
        *TotalEntries = EntriesRead * 2;    //  we cheat here
    } else {
        //
        //  Cleanup in case of "bad" errors.
        //
        while ( EntriesRead > 0) {
            EntriesRead--;
            Buffer -= CoreSize;
            BootGetInfoCleanup( BootEnum->Level, Buffer);
        }
        MIDL_user_free( Buffer);
    }

    RplDump( RG_DebugLevel & RPL_DEBUG_BOOT, ("BootEnum: EntriesRead = 0x%x", EntriesRead));

    BootEnum->BootInfo.Level0->EntriesRead = EntriesRead;
    if ( EntriesRead == 0) {
        BootEnum->BootInfo.Level0->Buffer = NULL;
    }

    if ( ARGUMENT_PRESENT( pResumeHandle)) {
        if ( Error == ERROR_MORE_DATA  &&  EntriesRead > 0) {
            EnterCriticalSection( &RG_ProtectDatabase);
            Call( JetBeginTransaction( pSession->SesId));
            BootResumeSave( pSession, (DWORD)ServerHandle, VendorId,
                ((LPRPL_BOOT_INFO_0)(Buffer-CoreSize))->BootName,
                pResumeHandle);
            Call( JetCommitTransaction( pSession->SesId, JET_bitCommitFlush));
            LeaveCriticalSection( &RG_ProtectDatabase);
        } else {
            *pResumeHandle = 0; // resume from beginning
        }
    }
    return( Error);
}