//
//  REGKNODE.C
//
//  Copyright (C) Microsoft Corporation, 1995
//

#include "pch.h"

DECLARE_DEBUG_COUNT(g_RgKeynodeLockCount);

#define HAS_COMPACT_KEYNODES(lpfi)      ((lpfi)-> Flags & FI_VERSION20)

#define SIZEOF_KEYNODE_BLOCK(lpfi)      \
    ((HAS_COMPACT_KEYNODES(lpfi)) ? sizeof(KEYNODE_BLOCK) : sizeof(W95KEYNODE_BLOCK))

#define SIZEOF_FILE_KEYNODE(lpfi)       \
    ((HAS_COMPACT_KEYNODES(lpfi)) ? sizeof(KEYNODE) : sizeof(W95KEYNODE))

#define ROUND_UP(i, basesize) (((((i) + (basesize) - 1) / (basesize))) * (basesize))

#define BLOCK_DESC_GROW_SIZE 0x400

#define W95KEYNODES_PER_PAGE            (PAGESIZE / sizeof(W95KEYNODE))

typedef BOOL (INTERNAL *LPPROCESSKEYNODEPROC)(LPKEYNODE, LPW95KEYNODE);

//
//  RgOffsetToIndex
//

DWORD
INTERNAL
RgOffsetToIndex(
    DWORD W95KeynodeOffset
    )
{

    return (W95KeynodeOffset == REG_NULL) ? W95KeynodeOffset :
	(W95KeynodeOffset / sizeof(W95KEYNODE));

}

//
//  RgIndexToOffset
//

DWORD
INTERNAL
RgIndexToOffset(
    DWORD KeynodeIndex
    )
{

    if (IsNullKeynodeIndex(KeynodeIndex))
        return REG_NULL;

    else {
        if (KeynodeIndex >= 2 * W95KEYNODES_PER_PAGE) {
            DWORD dwUnroundedOff = (KeynodeIndex * sizeof(W95KEYNODE))
				  + sizeof(W95KEYNODE)-1;
	    DWORD dwRoundPage = ((dwUnroundedOff & PAGEMASK) / sizeof(W95KEYNODE))
				  * sizeof(W95KEYNODE);
	    return((dwUnroundedOff & ~PAGEMASK) + dwRoundPage);
	} else {
            return(((KeynodeIndex-1)*sizeof(W95KEYNODE))+sizeof(KEYNODE_HEADER));
	}
    }

}

//
//  RgPackKeynode
//
//  Packs the data from the provided W95KEYNODE to the KEYNODE structure.
//

BOOL
INTERNAL
RgPackKeynode(
    LPKEYNODE lpKeynode,
    LPW95KEYNODE lpW95Keynode
    )
{
    lpKeynode->Flags = 0;

    //  Don't use a switch statement here.  Apparently the compiler will treat
    //  lpW95Keynode->W95State as an integer, so the 16-bit compiler ends up truncating
    //  the value.

    if (lpW95Keynode->W95State == KNS_USED ||
        lpW95Keynode->W95State == KNS_BIGUSED ||
        lpW95Keynode->W95State == KNS_BIGUSEDEXT) {
        lpKeynode->Flags = KNF_INUSE;
        lpKeynode->ParentIndex = RgOffsetToIndex(lpW95Keynode->W95ParentOffset);
        lpKeynode->NextIndex = RgOffsetToIndex(lpW95Keynode->W95NextOffset);
        lpKeynode->ChildIndex = RgOffsetToIndex(lpW95Keynode->W95ChildOffset);
        lpKeynode->KeyRecordIndex = LOWORD(lpW95Keynode->W95DatablockAddress);
        lpKeynode->BlockIndex = HIWORD(lpW95Keynode->W95DatablockAddress);
        lpKeynode->Hash = (WORD)lpW95Keynode->W95Hash;

        if (lpW95Keynode->W95State == KNS_BIGUSED)
            lpKeynode->Flags |= KNF_BIGKEYROOT;
        else if (lpW95Keynode->W95State == KNS_BIGUSEDEXT)
            lpKeynode->Flags |= KNF_BIGKEYEXT;

    }

    else if (lpW95Keynode->W95State == KNS_FREE || lpW95Keynode->W95State ==
	KNS_ALLFREE) {
	lpKeynode->FreeRecordSize = lpW95Keynode->W95FreeRecordSize;
        lpKeynode->NextIndex = RgOffsetToIndex(lpW95Keynode->W95NextFreeOffset);
	//  Review this later.  Previous versions of this code checked
	//  if the next index was REG_NULL and bailed out of the processing
	//  loop.  It's possible to have a registry with a free keynode sitting
	//  in the middle of some keynode block and that keynode is the last
	//  in the chain.  We don't want to bail out in those cases.
	//
	//  For now, just bail out if the free record size is greater than a
	//  couple keynodes indicating that this is probably the last free
	//  record and the last record of the keynode.
	if (lpKeynode-> FreeRecordSize > (sizeof(W95KEYNODE)*2))
	    return TRUE;
    }

    else {
        DEBUG_OUT(("RgPackKeynode: Unrecognized state (%lx)\n", lpW95Keynode->
            W95State));
    }

    return FALSE;
}

//
//  RgUnpackKeynode
//
//  Unpacks the data from the provided KEYNODE to the W95KEYNODE structure.
//

BOOL
INTERNAL
RgUnpackKeynode(
    LPKEYNODE lpKeynode,
    LPW95KEYNODE lpW95Keynode
    )
{

    if (lpKeynode->Flags & KNF_INUSE) {

        if (lpKeynode->Flags & KNF_BIGKEYROOT)
            lpW95Keynode->W95State = KNS_BIGUSED;
        else if (lpKeynode->Flags & KNF_BIGKEYEXT)
            lpW95Keynode->W95State = KNS_BIGUSEDEXT;
        else
            lpW95Keynode->W95State = KNS_USED;
        lpW95Keynode->W95ParentOffset = RgIndexToOffset(lpKeynode->ParentIndex);
        lpW95Keynode->W95NextOffset = RgIndexToOffset(lpKeynode->NextIndex);
        lpW95Keynode->W95ChildOffset = RgIndexToOffset(lpKeynode->ChildIndex);
        lpW95Keynode->W95Hash = lpKeynode->Hash;

        //  Handle Win95 registries that don't have a key record for the root
        //  key.  The datablock address must be REG_NULL for Win95 to work.
        lpW95Keynode->W95DatablockAddress = IsNullBlockIndex(lpKeynode->
            BlockIndex) ? REG_NULL : MAKELONG(lpKeynode-> KeyRecordIndex,
            lpKeynode-> BlockIndex);

    }

    else {

        lpW95Keynode->W95State = KNS_FREE;
        lpW95Keynode->W95FreeRecordSize = lpKeynode->FreeRecordSize;
        lpW95Keynode->W95NextFreeOffset = RgIndexToOffset(lpKeynode->NextIndex);

    }

    return FALSE;

}

//
//  RgProcessKeynodeBlock
//
//  The provided callback function is called for each pair of KEYNODE and
//  W95KEYNODE structures from the given keynode blocks.
//

VOID
INTERNAL
RgProcessKeynodeBlock(
    DWORD dwStartOffset,
    DWORD dwBlockSize,
    LPKEYNODE_BLOCK lpKeynodeBlock,
    LPW95KEYNODE_BLOCK lpW95KeynodeBlock,
    LPPROCESSKEYNODEPROC lpfnProcessKeynode
    )
{

    DWORD dwCurOffset;
    LPKEYNODE lpKeynode;
    LPW95KEYNODE lpW95Keynode;
    UINT SkipSize;

    dwCurOffset = dwStartOffset;
    lpW95Keynode = &lpW95KeynodeBlock->aW95KN[0];
    SkipSize = (dwStartOffset == 0) ? sizeof(KEYNODE_HEADER) : 0;

    for (;;) {

        lpW95Keynode = (LPW95KEYNODE)(((LPBYTE)lpW95Keynode)+SkipSize);
        dwCurOffset += SkipSize;

	if (dwCurOffset >= dwStartOffset+dwBlockSize) {
	    goto Done;
	}
        lpKeynode = &lpKeynodeBlock->aKN[KN_INDEX_IN_BLOCK(RgOffsetToIndex(dwCurOffset))];
	while ((dwCurOffset < dwStartOffset+dwBlockSize) &&
	       ((dwCurOffset >> PAGESHIFT) == 0) ||
	       ((dwCurOffset >> PAGESHIFT) ==
		((dwCurOffset + sizeof(W95KEYNODE)) >> PAGESHIFT))) {
            if (lpfnProcessKeynode(lpKeynode, lpW95Keynode)) {
		goto Done;
	    }
	    dwCurOffset += sizeof(W95KEYNODE);
            lpW95Keynode++;
            lpKeynode++;
	}
	//
	//  Compute the number of bytes to skip to get to the next page
	//
        SkipSize = PAGESIZE - (UINT) (dwCurOffset & PAGEMASK);
    }
    Done: {};

}

//
//  RgLockKeynode
//

int
INTERNAL
RgLockKeynode(
    LPFILE_INFO lpFileInfo,
    DWORD KeynodeIndex,
    LPKEYNODE FAR* lplpKeynode
    )
{

    int ErrorCode;
    UINT KeynodeBlockIndex;
    LPKEYNODE_BLOCK_INFO lpKeynodeBlockInfo;
    UINT KeynodeBlockSize;
    HFILE hFile;
    LPKEYNODE_BLOCK lpKeynodeBlock;
    LPW95KEYNODE_BLOCK lpW95KeynodeBlock;
    DWORD BlockOffset;
    UINT ReadBlockSize;

    KeynodeBlockIndex = KN_BLOCK_NUMBER(KeynodeIndex);

    if (KeynodeBlockIndex > lpFileInfo-> KeynodeBlockCount) {
        DEBUG_OUT(("RgLockKeynode: invalid keynode offset\n"));
        return ERROR_BADDB;
    }

    //
    //  Is the keynode block currently in memory?
    //

    lpKeynodeBlockInfo = RgIndexKeynodeBlockInfoPtr(lpFileInfo,
        KeynodeBlockIndex);
    lpKeynodeBlock = lpKeynodeBlockInfo-> lpKeynodeBlock;

    if (IsNullPtr(lpKeynodeBlock)) {

        NOISE(("RgLockKeynode: "));
        NOISE((lpFileInfo-> FileName));
        NOISE((", block %d\n", KeynodeBlockIndex));

        if (IsNullPtr((lpKeynodeBlock = (LPKEYNODE_BLOCK)
            RgAllocMemory(sizeof(KEYNODE_BLOCK)))))
            return ERROR_OUTOFMEMORY;

        KeynodeBlockSize = SIZEOF_KEYNODE_BLOCK(lpFileInfo);
        BlockOffset = (DWORD) KeynodeBlockIndex * KeynodeBlockSize;

        if (BlockOffset < lpFileInfo-> KeynodeHeader.FileKnSize) {

            ASSERT(!(lpFileInfo-> Flags & FI_VOLATILE));

            ReadBlockSize = (UINT) min(KeynodeBlockSize, (lpFileInfo->
                KeynodeHeader.FileKnSize - BlockOffset));

            if ((hFile = RgOpenFile(lpFileInfo-> FileName, OF_READ)) ==
                HFILE_ERROR)
                goto CleanupAfterFileError;

            if (HAS_COMPACT_KEYNODES(lpFileInfo)) {

                if (!RgSeekFile(hFile, sizeof(VERSION20_HEADER_PAGE) +
                    BlockOffset))
                    goto CleanupAfterFileError;

                if (!RgReadFile(hFile, lpKeynodeBlock, ReadBlockSize))
                    goto CleanupAfterFileError;

            }

            else {

                if (!RgSeekFile(hFile, sizeof(FILE_HEADER) + BlockOffset))
                    goto CleanupAfterFileError;

                lpW95KeynodeBlock = (LPW95KEYNODE_BLOCK) RgLockWorkBuffer();

                if (!RgReadFile(hFile, lpW95KeynodeBlock, ReadBlockSize)) {
                    RgUnlockWorkBuffer(lpW95KeynodeBlock);
                    goto CleanupAfterFileError;
                }

                RgProcessKeynodeBlock(BlockOffset, ReadBlockSize,
                    lpKeynodeBlock, lpW95KeynodeBlock, RgPackKeynode);

                RgUnlockWorkBuffer(lpW95KeynodeBlock);

            }

            RgCloseFile(hFile);

        }

        lpKeynodeBlockInfo-> lpKeynodeBlock = lpKeynodeBlock;
        lpKeynodeBlockInfo-> Flags = 0;
        lpKeynodeBlockInfo-> LockCount = 0;

    }

    *lplpKeynode = &lpKeynodeBlock-> aKN[KN_INDEX_IN_BLOCK(KeynodeIndex)];
    lpKeynodeBlockInfo-> Flags |= KBIF_ACCESSED;
    lpKeynodeBlockInfo-> LockCount++;

    INCREMENT_DEBUG_COUNT(g_RgKeynodeLockCount);
    return ERROR_SUCCESS;

CleanupAfterFileError:
    ErrorCode = ERROR_REGISTRY_IO_FAILED;

    RgFreeMemory(lpKeynodeBlock);

    if (hFile != HFILE_ERROR)
        RgCloseFile(hFile);

    DEBUG_OUT(("RgLockKeynode() returning error %d\n", ErrorCode));
    return ErrorCode;

}

//
//  RgLockInUseKeynode
//
//  Wrapper for RgLockKeynode that guarantees that the returned keynode is
//  marked as being in-use.  If not, ERROR_BADDB is returned.
//

int
INTERNAL
RgLockInUseKeynode(
    LPFILE_INFO lpFileInfo,
    DWORD KeynodeIndex,
    LPKEYNODE FAR* lplpKeynode
    )
{

    int ErrorCode;

    if ((ErrorCode = RgLockKeynode(lpFileInfo, KeynodeIndex, lplpKeynode)) ==
        ERROR_SUCCESS) {
        if (!((*lplpKeynode)-> Flags & KNF_INUSE)) {
            DEBUG_OUT(("RgLockInUseKeynode: keynode unexpectedly not marked used\n"));
            RgUnlockKeynode(lpFileInfo, KeynodeIndex, FALSE);
            ErrorCode = ERROR_BADDB;
        }
    }

    return ErrorCode;

}

//
//  RgUnlockKeynode
//

VOID
INTERNAL
RgUnlockKeynode(
    LPFILE_INFO lpFileInfo,
    DWORD KeynodeIndex,
    BOOL fMarkDirty
    )
{

    UINT KeynodeBlockIndex;
    LPKEYNODE_BLOCK_INFO lpKeynodeBlockInfo;

    KeynodeBlockIndex = KN_BLOCK_NUMBER(KeynodeIndex);
    ASSERT(KeynodeBlockIndex < lpFileInfo-> KeynodeBlockCount);

    lpKeynodeBlockInfo = RgIndexKeynodeBlockInfoPtr(lpFileInfo,
        KeynodeBlockIndex);

    ASSERT(lpKeynodeBlockInfo-> LockCount > 0);
    lpKeynodeBlockInfo-> LockCount--;

    if (fMarkDirty) {
        lpKeynodeBlockInfo-> Flags |= KBIF_DIRTY;
        lpFileInfo-> Flags |= FI_DIRTY | FI_KEYNODEDIRTY;
        RgDelayFlush();
    }

    DECREMENT_DEBUG_COUNT(g_RgKeynodeLockCount);

}

//
//  RgAllocKeynode
//

int
INTERNAL
RgAllocKeynode(
    LPFILE_INFO lpFileInfo,
    LPDWORD lpKeynodeIndex,
    LPKEYNODE FAR* lplpKeynode
    )
{

    int ErrorCode;
    DWORD FreeKeynodeOffset;
    DWORD FreeKeynodeIndex;
    UINT FreeRecordSize;
    UINT ExtraPadding;
    UINT KeynodeBlockIndex;
    UINT AllocCount;
    LPKEYNODE_BLOCK_INFO lpKeynodeBlockInfo;
    LPKEYNODE lpKeynode;
    DWORD NextFreeKeynodeIndex;
    LPKEYNODE lpNextFreeKeynode;
    UINT KeynodeSize;

    FreeKeynodeIndex = lpFileInfo-> KeynodeHeader.FirstFreeIndex;

    //  If no more free keynodes exist, then we try to extend the keynode table
    //  to provide more entries.
    if (IsNullKeynodeIndex(FreeKeynodeIndex)) {

        if (HAS_COMPACT_KEYNODES(lpFileInfo)) {
            FreeKeynodeIndex = ROUND_UP(lpFileInfo-> CurTotalKnSize, PAGESIZE) /
                sizeof(KEYNODE);
            FreeRecordSize = PAGESIZE;
	    ExtraPadding = 0;
        }

        else {

            //  Handle the special case of a new file being created: for
            //  uncompacted keynode tables, the first offset is immediately
            //  after the keynode header and the size of the free record must
            //  account for  the size of this header.
            if (lpFileInfo-> CurTotalKnSize == sizeof(KEYNODE_HEADER)) {
                FreeKeynodeOffset = sizeof(KEYNODE_HEADER);
		    //  Win95 compatiblity:  Same initial table size, plus
		    //  causes us to stress the below special grow case.
		    FreeRecordSize = PAGESIZE - sizeof(KEYNODE_HEADER) * 2;
		    ExtraPadding = 0;
            }

            else {

		    FreeKeynodeOffset = ROUND_UP(lpFileInfo-> CurTotalKnSize,
                        PAGESIZE);
                    FreeRecordSize = PAGESIZE;
                    ExtraPadding = (UINT) (FreeKeynodeOffset - lpFileInfo->
                        CurTotalKnSize);

		    //  Handle the case of a keynode table with a non-integral
		    //  number of pages.  We'll place the new free keynode at the
		    //  top of the existing keynode table with a size including
		    //  the remaining bytes on the page plus a new page (effectively
		    //  the same as Win95).
		    if (ExtraPadding > sizeof(W95KEYNODE) || FreeKeynodeOffset ==
		        PAGESIZE) {
		        //	Although this code will work for any non-integral
		        //	number of pages, it should ONLY occur for <4K tables.
		        ASSERT(FreeKeynodeOffset == PAGESIZE);
		        FreeRecordSize += ExtraPadding;
		        FreeKeynodeOffset = lpFileInfo-> CurTotalKnSize;
		        ExtraPadding = 0;
                }

            }

            FreeKeynodeIndex = RgOffsetToIndex(FreeKeynodeOffset);

        }

        KeynodeBlockIndex = KN_BLOCK_NUMBER(FreeKeynodeIndex);

        //  Put in some sort of "max" count/KEYNODE_BLOCKS_PER_FILE
        //  check.

        //  Check if lpKeynodeBlockInfo is too small to hold the info for a new
        //  keynode block.  If so, then we must grow it a bit.
        if (KeynodeBlockIndex >= lpFileInfo-> KeynodeBlockInfoAllocCount) {

            AllocCount = KeynodeBlockIndex + KEYNODE_BLOCK_INFO_SLACK_ALLOC;

            if (IsNullPtr((lpKeynodeBlockInfo = (LPKEYNODE_BLOCK_INFO)
                RgSmReAllocMemory(lpFileInfo-> lpKeynodeBlockInfo, AllocCount *
                sizeof(KEYNODE_BLOCK_INFO)))))
                return ERROR_OUTOFMEMORY;

            ZeroMemory(lpKeynodeBlockInfo + lpFileInfo->
                KeynodeBlockInfoAllocCount, (AllocCount - lpFileInfo->
                KeynodeBlockInfoAllocCount) * sizeof(KEYNODE_BLOCK_INFO));

            lpFileInfo-> lpKeynodeBlockInfo = lpKeynodeBlockInfo;
            lpFileInfo-> KeynodeBlockInfoAllocCount = AllocCount;

        }

        if (KeynodeBlockIndex < lpFileInfo-> KeynodeBlockCount)
        {
    	    lpFileInfo-> CurTotalKnSize += (FreeRecordSize + ExtraPadding);
            lpFileInfo-> Flags |= FI_EXTENDED;
            lpFileInfo-> KeynodeHeader.FirstFreeIndex = FreeKeynodeIndex;
        }

        if ((ErrorCode = RgLockKeynode(lpFileInfo, FreeKeynodeIndex,
            &lpKeynode)) != ERROR_SUCCESS)
            return ErrorCode;

        if (KeynodeBlockIndex >= lpFileInfo-> KeynodeBlockCount)
        {
            lpFileInfo-> KeynodeBlockCount = KeynodeBlockIndex + 1;

    	    lpFileInfo-> CurTotalKnSize += (FreeRecordSize + ExtraPadding);
            lpFileInfo-> Flags |= FI_EXTENDED;
            lpFileInfo-> KeynodeHeader.FirstFreeIndex = FreeKeynodeIndex;
        }
        
        lpKeynode-> NextIndex = REG_NULL;
        lpKeynode-> Flags = 0;
        lpKeynode-> FreeRecordSize = FreeRecordSize;

    }

    else {
        if ((ErrorCode = RgLockKeynode(lpFileInfo, FreeKeynodeIndex,
            &lpKeynode)) != ERROR_SUCCESS)
            return ErrorCode;
    }

    NextFreeKeynodeIndex = lpKeynode-> NextIndex;
    KeynodeSize = SIZEOF_FILE_KEYNODE(lpFileInfo);

    //  If the free keynode record can be broken up into smaller chunks, then
    //  create another free record immediately after the one we're about to
    //  snag.
    if ((lpKeynode-> FreeRecordSize >= KeynodeSize * 2) &&
        (RgLockKeynode(lpFileInfo, FreeKeynodeIndex + 1, &lpNextFreeKeynode) ==
        ERROR_SUCCESS)) {

	//  Copy the next link from the current free keynode (likely REG_NULL).
        lpNextFreeKeynode-> NextIndex = NextFreeKeynodeIndex;
        lpNextFreeKeynode-> Flags = 0;
        lpNextFreeKeynode-> FreeRecordSize = lpKeynode-> FreeRecordSize -
            KeynodeSize;

        NextFreeKeynodeIndex = FreeKeynodeIndex + 1;
        RgUnlockKeynode(lpFileInfo, NextFreeKeynodeIndex, TRUE);

    }

    lpFileInfo-> KeynodeHeader.FirstFreeIndex = NextFreeKeynodeIndex;

    lpKeynode-> Flags |= KNF_INUSE;

    //  Mark the keynode block that holds this keynode dirty.
    lpKeynodeBlockInfo = RgIndexKeynodeBlockInfoPtr(lpFileInfo,
        KN_BLOCK_NUMBER(FreeKeynodeIndex));
    lpKeynodeBlockInfo-> Flags |= KBIF_DIRTY;
    lpFileInfo-> Flags |= FI_DIRTY | FI_KEYNODEDIRTY;
    RgDelayFlush();

    //  WARNING:  The following two statements used to be above the block that
    //  dirtied the keynode.  The 16-bit compiler messed up and
    //  lpKeynodeBlockInfo ended up with a bogus offset thus corrupting random
    //  memory.  Be sure to trace through this function if you change it!
    *lpKeynodeIndex = FreeKeynodeIndex;
    *lplpKeynode = lpKeynode;

    return ERROR_SUCCESS;

}

//
//  RgFreeKeynode
//
//  Marks the specified keynode index free and adds it to the hive's free
//  keynode list.
//

int
INTERNAL
RgFreeKeynode(
    LPFILE_INFO lpFileInfo,
    DWORD KeynodeIndex
    )
{

    int ErrorCode;
    LPKEYNODE lpKeynode;

    if ((ErrorCode = RgLockKeynode(lpFileInfo, KeynodeIndex, &lpKeynode)) ==
        ERROR_SUCCESS) {

        lpKeynode-> Flags &= ~(KNF_INUSE | KNF_BIGKEYROOT | KNF_BIGKEYEXT);
        lpKeynode-> NextIndex = lpFileInfo-> KeynodeHeader.FirstFreeIndex;
        lpKeynode-> FreeRecordSize = SIZEOF_FILE_KEYNODE(lpFileInfo);
        lpFileInfo-> KeynodeHeader.FirstFreeIndex = KeynodeIndex;

        RgUnlockKeynode(lpFileInfo, KeynodeIndex, TRUE);

    }

    return ErrorCode;

}

//
//  RgGetKnBlockIOInfo
//

VOID
INTERNAL
RgGetKnBlockIOInfo(
    LPFILE_INFO lpFileInfo,
    DWORD       BaseKeynodeIndex,
    UINT FAR*   lpFileBlockSize,
    LONG FAR*   lpFileOffset
    )
{

    UINT FileBlockSize;
    DWORD FileOffset;
    DWORD BaseKeynodeOffset;

    if (HAS_COMPACT_KEYNODES(lpFileInfo)) {

        FileBlockSize = sizeof(KEYNODE_BLOCK);

        BaseKeynodeOffset = BaseKeynodeIndex * sizeof(KEYNODE);

        if (BaseKeynodeOffset + FileBlockSize > lpFileInfo-> CurTotalKnSize)
            FileBlockSize = (UINT) (lpFileInfo-> CurTotalKnSize -
                BaseKeynodeOffset);

        FileOffset = sizeof(VERSION20_HEADER_PAGE) + BaseKeynodeIndex *
            sizeof(KEYNODE);

    }

    else {

        FileBlockSize = sizeof(W95KEYNODE_BLOCK);

        //  The first keynode block of an uncompacted keynode table should
        //  start writing AFTER the keynode header.
        if (BaseKeynodeIndex == 0) {
            BaseKeynodeIndex = RgOffsetToIndex(sizeof(KEYNODE_HEADER));
            FileBlockSize -= sizeof(KEYNODE_HEADER);
	}

        BaseKeynodeOffset = RgIndexToOffset(BaseKeynodeIndex);

        if (BaseKeynodeOffset + FileBlockSize > lpFileInfo-> CurTotalKnSize)
            FileBlockSize = (UINT) (lpFileInfo-> CurTotalKnSize -
                BaseKeynodeOffset);

        FileOffset = sizeof(FILE_HEADER) + BaseKeynodeOffset;

    }

    *lpFileBlockSize = FileBlockSize;
    *lpFileOffset = FileOffset;

}



int
_inline
RgCopyKeynodeBlock(
    LPFILE_INFO lpFileInfo,
    DWORD BaseIndex,
    HFILE hSrcFile,
    HFILE hDestFile
    )
{
    UINT FileBlockSize;
    LONG FileOffset;
    RgGetKnBlockIOInfo(lpFileInfo, BaseIndex, &FileBlockSize, &FileOffset);
    return RgCopyFileBytes(hSrcFile,
			  FileOffset,
			  hDestFile,
			  FileOffset,
			  FileBlockSize);
}

//
//  RgWriteKeynodeBlock
//

int
INTERNAL
RgWriteKeynodeBlock(
    LPFILE_INFO lpFileInfo,
    DWORD BaseIndex,
    HFILE hDestFile,
    LPKEYNODE_BLOCK_INFO lpKeynodeBlockInfo
    )
{
    int ErrorCode;
    UINT FileBlockSize;
    LONG FileOffset;
    LPW95KEYNODE_BLOCK lpW95KeynodeBlock;

    RgGetKnBlockIOInfo(lpFileInfo, BaseIndex, &FileBlockSize, &FileOffset);

    ErrorCode = ERROR_REGISTRY_IO_FAILED;       // Assume I/O fails
    if (!RgSeekFile(hDestFile, FileOffset)) {
        goto Exit;
    }
    if (HAS_COMPACT_KEYNODES(lpFileInfo)) {
        if (RgWriteFile(hDestFile, lpKeynodeBlockInfo->lpKeynodeBlock, FileBlockSize)) {
            ErrorCode = ERROR_SUCCESS;
        }
    } else {
        LPBYTE lpWriteBlock;
        lpW95KeynodeBlock = (LPW95KEYNODE_BLOCK) RgLockWorkBuffer();
        RgProcessKeynodeBlock(
			    BaseIndex * sizeof(W95KEYNODE),
			    FileBlockSize,
                            lpKeynodeBlockInfo->lpKeynodeBlock,
                            lpW95KeynodeBlock,
                            RgUnpackKeynode);
        lpWriteBlock = (LPBYTE)lpW95KeynodeBlock;
        if (BaseIndex == 0) {
            lpWriteBlock += sizeof(KEYNODE_HEADER);
        }
        if (RgWriteFile(hDestFile, lpWriteBlock, FileBlockSize)) {
            ErrorCode = ERROR_SUCCESS;
        }
        RgUnlockWorkBuffer(lpW95KeynodeBlock);
    }
Exit:   ;
    return(ErrorCode);
}

//
//  RgWriteKeynodes
//

int
INTERNAL
RgWriteKeynodes(
    LPFILE_INFO lpFileInfo,
    HFILE hSrcFile,
    HFILE hDestFile
    )
{

    DWORD SavedRootIndex;
    DWORD SavedFreeIndex;
    DWORD SavedFileKnSize;
    BOOL fResult;
    UINT KeynodeBlockIndex;
    LPKEYNODE_BLOCK_INFO lpKeynodeBlockInfo;

    if ((hSrcFile == HFILE_ERROR) && !(lpFileInfo->Flags & FI_KEYNODEDIRTY))
        return ERROR_SUCCESS;

    NOISE(("writing keynodes of "));
    NOISE((lpFileInfo-> FileName));
    NOISE(("\n"));

    //
    //	Write out the keynode header.  If the keynodes are not compact then
    //	convert to offsets before writing.
    //

    if (!RgSeekFile(hDestFile, sizeof(FILE_HEADER)))
        return ERROR_REGISTRY_IO_FAILED;

    SavedFileKnSize = lpFileInfo-> KeynodeHeader.FileKnSize;
    SavedRootIndex = lpFileInfo-> KeynodeHeader.RootIndex;
    SavedFreeIndex = lpFileInfo-> KeynodeHeader.FirstFreeIndex;

    //  Write the real size of the keynode table to disk.
    lpFileInfo-> KeynodeHeader.FileKnSize = lpFileInfo-> CurTotalKnSize;

    //  Convert keynode indexes back to offsets temporarily for uncompacted
    //  keynode tables.
    if (!HAS_COMPACT_KEYNODES(lpFileInfo)) {
        lpFileInfo-> KeynodeHeader.RootIndex = RgIndexToOffset(lpFileInfo->
            KeynodeHeader.RootIndex);
        lpFileInfo-> KeynodeHeader.FirstFreeIndex = RgIndexToOffset(lpFileInfo->
            KeynodeHeader.FirstFreeIndex);
    }

    fResult = RgWriteFile(hDestFile, &lpFileInfo-> KeynodeHeader,
        sizeof(KEYNODE_HEADER));

    lpFileInfo-> KeynodeHeader.FileKnSize = SavedFileKnSize;
    lpFileInfo-> KeynodeHeader.RootIndex = SavedRootIndex;
    lpFileInfo-> KeynodeHeader.FirstFreeIndex = SavedFreeIndex;

    if (!fResult)
        return ERROR_REGISTRY_IO_FAILED;

    //
    //	Now loop through each block.
    //

    lpKeynodeBlockInfo = lpFileInfo-> lpKeynodeBlockInfo;

    for (KeynodeBlockIndex = 0; KeynodeBlockIndex < lpFileInfo->
        KeynodeBlockCount; KeynodeBlockIndex++, lpKeynodeBlockInfo++) {

        DWORD BaseKeynodeIndex = KeynodeBlockIndex * KEYNODES_PER_BLOCK;

        if (!IsNullPtr(lpKeynodeBlockInfo-> lpKeynodeBlock)) {
            if (hSrcFile != HFILE_ERROR || lpKeynodeBlockInfo-> Flags &
                KBIF_DIRTY) {
                if (RgWriteKeynodeBlock(lpFileInfo, BaseKeynodeIndex, hDestFile,
                    lpKeynodeBlockInfo) != ERROR_SUCCESS)
                    return ERROR_REGISTRY_IO_FAILED;
            }
        }

        else {
            if (hSrcFile != HFILE_ERROR) {
                if (RgCopyKeynodeBlock(lpFileInfo, BaseKeynodeIndex, hSrcFile,
                    hDestFile) != ERROR_SUCCESS)
                    return ERROR_REGISTRY_IO_FAILED;
            }
        }

    }

    return ERROR_SUCCESS;

}

//
//  RgWriteKeynodesComplete
//
//  Called after a file has been successfully written.  We can now safely clear
//  all dirty flags and update our state information with the knowledge that
//  the file is in a consistent state.
//

VOID
INTERNAL
RgWriteKeynodesComplete(
    LPFILE_INFO lpFileInfo
    )
{

    UINT BlocksLeft;
    LPKEYNODE_BLOCK_INFO lpKeynodeBlockInfo;

    lpFileInfo-> Flags &= ~FI_KEYNODEDIRTY;
    lpFileInfo-> KeynodeHeader.FileKnSize = lpFileInfo-> CurTotalKnSize;

    for (BlocksLeft = lpFileInfo-> KeynodeBlockCount, lpKeynodeBlockInfo =
        lpFileInfo-> lpKeynodeBlockInfo; BlocksLeft > 0; BlocksLeft--,
        lpKeynodeBlockInfo++)
        lpKeynodeBlockInfo-> Flags &= ~KBIF_DIRTY;

}

//
//  RgSweepKeynodes
//
//  Makes a pass through all the present keynode blocks of the given FILE_INFO
//  structure and discards keynode blocks that have not been accessed since the
//  last sweep.
//

VOID
INTERNAL
RgSweepKeynodes(
    LPFILE_INFO lpFileInfo
    )
{

    UINT BlocksLeft;
    LPKEYNODE_BLOCK_INFO lpKeynodeBlockInfo;

    for (BlocksLeft = lpFileInfo-> KeynodeBlockCount, lpKeynodeBlockInfo =
        lpFileInfo-> lpKeynodeBlockInfo; BlocksLeft > 0; BlocksLeft--,
        lpKeynodeBlockInfo++) {

        if (!IsNullPtr(lpKeynodeBlockInfo-> lpKeynodeBlock)) {

            if (((lpKeynodeBlockInfo-> Flags & (KBIF_ACCESSED | KBIF_DIRTY)) ==
                0) && (lpKeynodeBlockInfo-> LockCount == 0)) {
                RgFreeMemory(lpKeynodeBlockInfo-> lpKeynodeBlock);
                lpKeynodeBlockInfo-> lpKeynodeBlock = NULL;
            }

            lpKeynodeBlockInfo-> Flags &= ~KBIF_ACCESSED;

	}

    }

}

#ifdef VXD
#pragma VxD_RARE_CODE_SEG
#endif

//
//  RgInitKeynodeInfo
//
//  Initializes fields in the provided FILE_INFO related to the keynode table.
//

int
INTERNAL
RgInitKeynodeInfo(
    LPFILE_INFO lpFileInfo
    )
{

    UINT KeynodeBlockSize;
    UINT BlockCount;
    UINT AllocCount;
    LPKEYNODE_BLOCK_INFO lpKeynodeBlockInfo;

    KeynodeBlockSize = SIZEOF_KEYNODE_BLOCK(lpFileInfo);
    BlockCount = (UINT) ((DWORD) (lpFileInfo-> KeynodeHeader.FileKnSize +
        KeynodeBlockSize - 1) / (DWORD) KeynodeBlockSize);
    AllocCount = BlockCount + KEYNODE_BLOCK_INFO_SLACK_ALLOC;

    if (IsNullPtr((lpKeynodeBlockInfo = (LPKEYNODE_BLOCK_INFO)
        RgSmAllocMemory(AllocCount * sizeof(KEYNODE_BLOCK_INFO)))))
        return ERROR_OUTOFMEMORY;

    ZeroMemory(lpKeynodeBlockInfo, AllocCount * sizeof(KEYNODE_BLOCK_INFO));
    lpFileInfo-> lpKeynodeBlockInfo = lpKeynodeBlockInfo;
    lpFileInfo-> KeynodeBlockCount = BlockCount;
    lpFileInfo-> KeynodeBlockInfoAllocCount = AllocCount;

    lpFileInfo-> KeynodeHeader.Flags &= ~(KHF_DIRTY | KHF_EXTENDED |
        KHF_HASCHECKSUM);

    //  Convert file offsets to index values for uncompressed files
    if (!HAS_COMPACT_KEYNODES(lpFileInfo)) {
        lpFileInfo-> KeynodeHeader.RootIndex = RgOffsetToIndex(lpFileInfo->
            KeynodeHeader.RootIndex);
        lpFileInfo-> KeynodeHeader.FirstFreeIndex = RgOffsetToIndex(lpFileInfo->
            KeynodeHeader.FirstFreeIndex);
    }

    lpFileInfo-> CurTotalKnSize = lpFileInfo-> KeynodeHeader.FileKnSize;

    return ERROR_SUCCESS;

}