/*++

Copyright (c) 1991  Microsoft Corporation

Module Name:

    cmwrapr.c

Abstract:

    This module contains the source for wrapper routines called by the
    hive code, which in turn call the appropriate NT routines.

Author:

    Bryan M. Willman (bryanwi) 16-Dec-1991

Revision History:

--*/

#include    "cmp.h"

VOID
CmpUnmapCmViewSurroundingOffset(
        IN  PCMHIVE             CmHive,
        IN  ULONG               FileOffset
        );



#ifdef ALLOC_DATA_PRAGMA
#pragma data_seg("PAGEDATA")
#endif
ULONG perftouchbuffer = 0;

#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE,CmpAllocate)
#ifdef POOL_TAGGING
#pragma alloc_text(PAGE,CmpAllocateTag)
#endif
#pragma alloc_text(PAGE,CmpFree)
#pragma alloc_text(PAGE,CmpDoFileSetSize)
#pragma alloc_text(PAGE,CmpCreateEvent)
#pragma alloc_text(PAGE,CmpFileRead)
#pragma alloc_text(PAGE,CmpFileWrite)
#pragma alloc_text(PAGE,CmpFileFlush)
#pragma alloc_text(PAGE,CmpFileWriteThroughCache)
#endif

extern BOOLEAN CmpNoWrite;


//
// never read more than 64k, neither the filesystem nor some disk drivers
// like it much.
//
#define MAX_FILE_IO 0x10000

#define CmpIoFileRead       1
#define CmpIoFileWrite      2
#define CmpIoFileSetSize    3
#define CmpIoFileFlush      4

extern struct {
    ULONG       Action;
    HANDLE      Handle;
    NTSTATUS    Status;
} CmRegistryIODebug;

extern BOOLEAN CmpFlushOnLockRelease;
//
// Storage management
//

PVOID
CmpAllocate(
    ULONG   Size,
    BOOLEAN UseForIo,
    ULONG   Tag
    )
/*++

Routine Description:

    This routine makes more memory available to a hive.

    It is environment specific.

Arguments:

    Size - amount of space caller wants

    UseForIo - TRUE if object allocated will be target of I/O,
               FALSE if not.

Return Value:

    NULL if failure, address of allocated block if not.

--*/
{
    PVOID   result;
    ULONG   pooltype;
#if DBG
    PVOID   Caller;
    PVOID   CallerCaller;
    RtlGetCallersAddress(&Caller, &CallerCaller);
#endif

    if (CmpClaimGlobalQuota(Size) == FALSE) {
        return NULL;
    }

    pooltype = (UseForIo) ? PagedPoolCacheAligned : PagedPool;
    result = ExAllocatePoolWithTag(
                pooltype,
                Size,
                Tag
                );

    CmKdPrintEx((DPFLTR_CONFIG_ID,CML_POOL,"**CmpAllocate: allocate:%08lx, ", Size));
    CmKdPrintEx((DPFLTR_CONFIG_ID,CML_POOL,"type:%d, at:%08lx  ", PagedPool, result));
    CmKdPrintEx((DPFLTR_CONFIG_ID,CML_POOL,"c:%p  cc:%p\n", Caller, CallerCaller));

    if (result == NULL) {
        CmpReleaseGlobalQuota(Size);
    }

    return result;
}

#ifdef POOL_TAGGING
PVOID
CmpAllocateTag(
    ULONG   Size,
    BOOLEAN UseForIo,
    ULONG   Tag
    )
/*++

Routine Description:

    This routine makes more memory available to a hive.

    It is environment specific.

Arguments:

    Size - amount of space caller wants

    UseForIo - TRUE if object allocated will be target of I/O,
               FALSE if not.

Return Value:

    NULL if failure, address of allocated block if not.

--*/
{
    PVOID   result;
    ULONG   pooltype;
#if DBG
    PVOID   Caller;
    PVOID   CallerCaller;
    RtlGetCallersAddress(&Caller, &CallerCaller);
#endif

    if (CmpClaimGlobalQuota(Size) == FALSE) {
        return NULL;
    }

    pooltype = (UseForIo) ? PagedPoolCacheAligned : PagedPool;
    result = ExAllocatePoolWithTag(
                pooltype,
                Size,
                Tag
                );

    CmKdPrintEx((DPFLTR_CONFIG_ID,CML_POOL,"**CmpAllocate: allocate:%08lx, ", Size));
    CmKdPrintEx((DPFLTR_CONFIG_ID,CML_POOL,"type:%d, at:%08lx  ", PagedPool, result));
    CmKdPrintEx((DPFLTR_CONFIG_ID,CML_POOL,"c:%p  cc:%p\n", Caller, CallerCaller));

    if (result == NULL) {
        CmpReleaseGlobalQuota(Size);
    }

    return result;
}
#endif


VOID
CmpFree(
    PVOID   MemoryBlock,
    ULONG   GlobalQuotaSize
    )
/*++

Routine Description:

    This routine frees memory that has been allocated by the registry.

    It is environment specific


Arguments:

    MemoryBlock - supplies address of memory object to free

    GlobalQuotaSize - amount of global quota to release

Return Value:

    NONE

--*/
{
#if DBG
    PVOID   Caller;
    PVOID   CallerCaller;
    RtlGetCallersAddress(&Caller, &CallerCaller);
    CmKdPrintEx((DPFLTR_CONFIG_ID,CML_POOL,"**FREEING:%08lx c,cc:%p,%p\n", MemoryBlock, Caller, CallerCaller));
#endif
    ASSERT(GlobalQuotaSize > 0);
    CmpReleaseGlobalQuota(GlobalQuotaSize);
    ExFreePool(MemoryBlock);
    return;
}


NTSTATUS
CmpDoFileSetSize(
    PHHIVE      Hive,
    ULONG       FileType,
    ULONG       FileSize,
    ULONG       OldFileSize
    )
/*++

Routine Description:

    This routine sets the size of a file.  It must not return until
    the size is guaranteed.

    It is environment specific.

    Must be running in the context of the cmp worker thread.

Arguments:

    Hive - Hive we are doing I/O for

    FileType - which supporting file to use

    FileSize - 32 bit value to set the file's size to

    OldFileSize - old file size, in order to determine if this is a shrink;
                - ignored if file type is not primary, or hive doesn't use 
                the mapped views technique

Return Value:

    FALSE if failure
    TRUE if success

--*/
{
    PCMHIVE                         CmHive;
    HANDLE                          FileHandle;
    NTSTATUS                        Status;
    FILE_END_OF_FILE_INFORMATION    FileInfo;
    IO_STATUS_BLOCK                 IoStatus;
    BOOLEAN                         oldFlag;
    LARGE_INTEGER                   FileOffset;         // where the mapping starts

    ASSERT(FIELD_OFFSET(CMHIVE, Hive) == 0);

    CmHive = (PCMHIVE)Hive;
    FileHandle = CmHive->FileHandles[FileType];
    if (FileHandle == NULL) {
        return TRUE;
    }

    //
    // disable hard error popups, to avoid self deadlock on bogus devices
    //
    oldFlag = IoSetThreadHardErrorMode(FALSE);

    FileInfo.EndOfFile.HighPart = 0L;
    if( FileType == HFILE_TYPE_PRIMARY ) {
        FileInfo.EndOfFile.LowPart  = ROUND_UP(FileSize, CM_FILE_GROW_INCREMENT);
    } else {
        FileInfo.EndOfFile.LowPart  = FileSize;
    }

    ASSERT_PASSIVE_LEVEL();

    Status = ZwSetInformationFile(
                FileHandle,
                &IoStatus,
                (PVOID)&FileInfo,
                sizeof(FILE_END_OF_FILE_INFORMATION),
                FileEndOfFileInformation
                );

    if (NT_SUCCESS(Status)) {
        ASSERT(IoStatus.Status == Status);
    } else {
        
        //
        // set debugging info
        //
        CmRegistryIODebug.Action = CmpIoFileSetSize;
        CmRegistryIODebug.Handle = FileHandle;
        CmRegistryIODebug.Status = Status;
#ifndef _CM_LDR_
        DbgPrintEx(DPFLTR_CONFIG_ID,DPFLTR_TRACE_LEVEL,"CmpFileSetSize:\tHandle=%08lx  OldLength = %08lx NewLength=%08lx  \n", 
                                                        FileHandle, OldFileSize, FileSize);
#endif //_CM_LDR_
        if( (Status == STATUS_DISK_FULL) && ExIsResourceAcquiredExclusiveLite(&CmpRegistryLock) ) {
            DbgPrintEx(DPFLTR_CONFIG_ID,DPFLTR_TRACE_LEVEL,"Disk is full while attempting to grow file %lx; will flush upon lock release\n",FileHandle);
            CmpFlushOnLockRelease = TRUE;;
        }
    }

    //
    // restore hard error popups mode
    //
    IoSetThreadHardErrorMode(oldFlag);
    

    //
    // purge
    //
    if( HiveWritesThroughCache(Hive,FileType) && (OldFileSize > FileSize)) {
        //
        // first we have to unmap any possible mapped views in the last 256K window
        // to avoid deadlock on CcWaitOnActiveCount inside CcPurgeCacheSection call bellow
        //
        ULONG   Offset = FileSize & (~(_256K - 1));
        //
        // we are not allowed to shrink in shared mode.
        //
        ASSERT_CM_LOCK_OWNED_EXCLUSIVE();

        while( Offset < OldFileSize ) {
            CmpUnmapCmViewSurroundingOffset((PCMHIVE)Hive,Offset);
            Offset += CM_VIEW_SIZE;
        }

        //
        // we need to take extra precaution here and unmap the very last view too
        //
        //CmpUnmapCmViewSurroundingOffset((PCMHIVE)Hive,OldFileSize-HBLOCK_SIZE);
        
        FileOffset.HighPart = 0;
        FileOffset.LowPart = FileSize;
        //
        // This is a shrink; Inform cache manager of the change of the size
        //
        CcPurgeCacheSection( ((PCMHIVE)Hive)->FileObject->SectionObjectPointer, (PLARGE_INTEGER)(((ULONG_PTR)(&FileOffset)) + 1), 
                            OldFileSize - FileSize, FALSE );

        //
        // Flush out this view to clear out the Cc dirty hints
        //
        CcFlushCache( ((PCMHIVE)Hive)->FileObject->SectionObjectPointer, (PLARGE_INTEGER)(((ULONG_PTR)(&FileOffset)) + 1),/*we are private writers*/
                            OldFileSize - FileSize,NULL);

    }
    
    return Status;
}

NTSTATUS
CmpCreateEvent(
    IN EVENT_TYPE  eventType,
    OUT PHANDLE eventHandle,
    OUT PKEVENT *event
    )
{
    NTSTATUS status;
    OBJECT_ATTRIBUTES obja;

    InitializeObjectAttributes( &obja, NULL, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL );
    status = ZwCreateEvent(
        eventHandle,
        EVENT_ALL_ACCESS,
        &obja,
        eventType,
        FALSE);
    
    if (!NT_SUCCESS(status)) {
        return status;
    }
    
    status = ObReferenceObjectByHandle(
        *eventHandle,
        EVENT_ALL_ACCESS,
        NULL,
        KernelMode,
        event,
        NULL);
    
    if (!NT_SUCCESS(status)) {
        ZwClose(*eventHandle);
        return status;
    }
    return status;
}

BOOLEAN
CmpFileRead (
    PHHIVE      Hive,
    ULONG       FileType,
    PULONG      FileOffset,
    PVOID       DataBuffer,
    ULONG       DataLength
    )
/*++

Routine Description:

    This routine reads in a buffer from a file.

    It is environment specific.

    NOTE:   We assume the handle is opened for asynchronous access,
            and that we, and not the IO system, are keeping the
            offset pointer.

    NOTE:   Only 32bit offsets are supported, even though the underlying
            IO system on NT supports 64 bit offsets.

Arguments:

    Hive - Hive we are doing I/O for

    FileType - which supporting file to use

    FileOffset - pointer to variable providing 32bit offset on input,
                 and receiving new 32bit offset on output.

    DataBuffer - pointer to buffer

    DataLength - length of buffer

Return Value:

    FALSE if failure
    TRUE if success

--*/
{
    NTSTATUS status;
    LARGE_INTEGER   Offset;
    IO_STATUS_BLOCK IoStatus;
    PCMHIVE CmHive;
    HANDLE  FileHandle;
    ULONG LengthToRead;
    HANDLE eventHandle = NULL;
    PKEVENT eventObject = NULL;

    ASSERT(FIELD_OFFSET(CMHIVE, Hive) == 0);
    CmHive = (PCMHIVE)Hive;
    FileHandle = CmHive->FileHandles[FileType];
    if (FileHandle == NULL) {
        return TRUE;
    }

    CmKdPrintEx((DPFLTR_CONFIG_ID,CML_IO,"CmpFileRead:\n"));
    CmKdPrintEx((DPFLTR_CONFIG_ID,CML_IO,"\tHandle=%08lx  Offset=%08lx  ", FileHandle, *FileOffset));
    CmKdPrintEx((DPFLTR_CONFIG_ID,CML_IO,"Buffer=%p  Length=%08lx\n", DataBuffer, DataLength));

    //
    // Detect attempt to read off end of 2gig file (this should be irrelevent)
    //
    if ((0xffffffff - *FileOffset) < DataLength) {
        CmKdPrintEx((DPFLTR_CONFIG_ID,CML_BUGCHECK,"CmpFileRead: runoff\n"));
        return FALSE;
    }

    status = CmpCreateEvent(
        SynchronizationEvent,
        &eventHandle,
        &eventObject);
    if (!NT_SUCCESS(status))
        return FALSE;

    //
    // We'd really like to just call the filesystems and have them do
    // the right thing.  But the filesystem will attempt to lock our
    // entire buffer into memory, and that may fail for large requests.
    // So we split our reads into 64k chunks and call the filesystem for
    // each one.
    //
    ASSERT_PASSIVE_LEVEL();
    while (DataLength > 0) {

        //
        // Convert ULONG to Large
        //
        Offset.LowPart = *FileOffset;
        Offset.HighPart = 0L;

        //
        // trim request down if necessary.
        //
        if (DataLength > MAX_FILE_IO) {
            LengthToRead = MAX_FILE_IO;
        } else {
            LengthToRead = DataLength;
        }

        status = ZwReadFile(
                    FileHandle,
                    eventHandle,
                    NULL,               // apcroutine
                    NULL,               // apccontext
                    &IoStatus,
                    DataBuffer,
                    LengthToRead,
                    &Offset,
                    NULL                // key
                    );

        if (STATUS_PENDING == status) {
            status = KeWaitForSingleObject(eventObject, Executive,
                                           KernelMode, FALSE, NULL);
            ASSERT(STATUS_SUCCESS == status);
            status = IoStatus.Status;
        }

        //
        // adjust offsets
        //
        *FileOffset = Offset.LowPart + LengthToRead;
        DataLength -= LengthToRead;
        (PUCHAR)DataBuffer += LengthToRead;

        if (NT_SUCCESS(status)) {
            ASSERT(IoStatus.Status == status);
            if (IoStatus.Information != LengthToRead) {
                CmKdPrintEx((DPFLTR_CONFIG_ID,CML_BUGCHECK,"CmpFileRead:\n\t"));
                CmKdPrintEx((DPFLTR_CONFIG_ID,CML_BUGCHECK,"Failure1: status = %08lx  ", status));
                CmKdPrintEx((DPFLTR_CONFIG_ID,CML_BUGCHECK,"IoInformation = %08lx\n", IoStatus.Information));
                ObDereferenceObject(eventObject);
                ZwClose(eventHandle);
                return FALSE;
            }
        } else {
            //
            // set debugging info
            //
            CmRegistryIODebug.Action = CmpIoFileRead;
            CmRegistryIODebug.Handle = FileHandle;
            CmRegistryIODebug.Status = status;
#ifndef _CM_LDR_
            DbgPrintEx(DPFLTR_CONFIG_ID,DPFLTR_TRACE_LEVEL,"CmpFileRead:\tFailure2: status = %08lx  IoStatus = %08lx\n", status, IoStatus.Status);
#endif //_CM_LDR_

            ObDereferenceObject(eventObject);
            ZwClose(eventHandle);
            return FALSE;
        }

    }
    ObDereferenceObject(eventObject);
    ZwClose(eventHandle);
    return TRUE;
}

BOOLEAN
CmpFileWriteThroughCache(
    PHHIVE              Hive,
    ULONG               FileType,
    PCMP_OFFSET_ARRAY   offsetArray,
    ULONG               offsetArrayCount
    )
/*++

Routine Description:

    This is routine writes dirty ranges of data using Cc mapped views.
    The benefit is that writes don't go through Cc Lazy Writer, so there 
    is no danger to be throttled or deffered.

    It also flushes the cache for the written ranges, guaranteeing that 
    the data was commited to the disk upon return.

Arguments:

    Hive - Hive we are doing I/O for

    FileType - which supporting file to use

    offsetArray - array of structures where each structure holds a 32bit offset
                  into the Hive file and pointer the a buffer written to that
                  file offset.

    offsetArrayCount - number of elements in the offsetArray.

Return Value:

    FALSE if failure
    TRUE if success

Note:

    This routine is intended to deal only with paged bins (i.e. bins crossing the 
    CM_VIEW_SIZE boundary or bins that were added after the last sync)

Assumption:

    We work on the assumption that the data to be written at one iteration never spans 
    over the CM_VIEW_SIZE boundary. HvpFindNextDirtyBlock takes care of that !!! 
--*/
{
    ULONG           i;
    PVOID           DataBuffer;
    ULONG           DataLength;
    ULONG           FileOffset;
    PCMHIVE         CmHive;
    NTSTATUS        Status;
    PVOID           Bcb;
    PVOID           FileBuffer;
    LARGE_INTEGER   Offset;
    IO_STATUS_BLOCK IoStatus;

    ASSERT_PASSIVE_LEVEL();

    CmHive = (PCMHIVE)CONTAINING_RECORD(Hive, CMHIVE, Hive);

    ASSERT( ((FileType == HFILE_TYPE_EXTERNAL) && (CmHive->FileObject != NULL)) || HiveWritesThroughCache(Hive,FileType) );

    //ASSERT( IsListEmpty(&(CmHive->PinViewListHead)) == TRUE);
    //ASSERT( CmHive->PinnedViews == 0 );

    Offset.HighPart = 0;
    //
    // iterate through the array of data
    //
    for(i=0;i<offsetArrayCount;i++) {
        DataBuffer =  offsetArray[i].DataBuffer;
        DataLength =  offsetArray[i].DataLength;
        FileOffset = offsetArray[i].FileOffset;
        //
        // data should never span over CM_VIEW_SIZE boundary
        //
        ASSERT( (FileOffset & (~(CM_VIEW_SIZE - 1))) == ((FileOffset + DataLength - 1) & (~(CM_VIEW_SIZE - 1))) );

        //
        // unmap any possible mapped view that could overlapp with this range ; not needed !!!!
        //
        //CmpUnmapCmViewSurroundingOffset(CmHive,FileOffset);

        //
        // map and pin data
        //
        Offset.LowPart = FileOffset;
        try {
            if( !CcPinRead (CmHive->FileObject,&Offset,DataLength,PIN_WAIT,&Bcb,&FileBuffer) ) {
                CmKdPrintEx((DPFLTR_CONFIG_ID,DPFLTR_ERROR_LEVEL,"CmpFileWriteThroughCache - could not pin read view i= %lu\n",i));
#if DBG
                DbgBreakPoint();
#endif //DBG
                return FALSE;        
            }
            //
            // copy data to pinned view; we need to do it inside try except, to protect against devices/volumes
            // dismounting from under us.
            //
            RtlCopyMemory(FileBuffer,DataBuffer, DataLength);

        } except (EXCEPTION_EXECUTE_HANDLER) {
            //
            // in low-memory scenarios, CcPinRead throws a STATUS_INSUFFICIENT_RESOURCES
            // We want to catch this and treat as a  "not enough resources" problem, 
            // rather than letting it to surface the kernel call
            //
            CmKdPrintEx((DPFLTR_CONFIG_ID,DPFLTR_ERROR_LEVEL,"CmpFileWriteThroughCache : CcPinRead has raised :%08lx\n",GetExceptionCode()));
            return FALSE;
        }

        //
        // dirty, unpin and flush
        //
        CcSetDirtyPinnedData (Bcb,NULL);
        CcUnpinData( Bcb );
        CcFlushCache (CmHive->FileObject->SectionObjectPointer,(PLARGE_INTEGER)(((ULONG_PTR)(&Offset)) + 1)/*we are private writers*/,DataLength,&IoStatus);
        if(!NT_SUCCESS(IoStatus.Status) ) {
            return FALSE;
        }
    }

    return TRUE;
}

FAST_MUTEX      CmpWriteLock;   // used to syncronize access to the bellow;
                                // the only case we ned this is when NtSaveKey is called by different threads
                                // at the same time; all other calls to CmpFileWrite are made with the reg_lock 
                                // held exclusively
CM_WRITE_BLOCK CmpWriteBlock;

BOOLEAN
CmpFileWrite(
    PHHIVE              Hive,
    ULONG               FileType,
    PCMP_OFFSET_ARRAY   offsetArray,
    ULONG               offsetArrayCount,
    PULONG              FileOffset
    )
/*++

Routine Description:

    This routine writes an array of buffers out to a file.

    It is environment specific.

    NOTE:   We assume the handle is opened for asynchronous access,
            and that we, and not the IO system, are keeping the
            offset pointer.

    NOTE:   Only 32bit offsets are supported, even though the underlying
            IO system on NT supports 64 bit offsets.

Arguments:

    Hive - Hive we are doing I/O for

    FileType - which supporting file to use

    offsetArray - array of structures where each structure holds a 32bit offset
                  into the Hive file and pointer the a buffer written to that
                  file offset.

    offsetArrayCount - number of elements in the offsetArray.

    FileOffset - returns the file offset after the last write to the file.

Return Value:

    FALSE if failure
    TRUE if success

--*/
{
    NTSTATUS        status;
    LARGE_INTEGER   Offset;
    PCMHIVE         CmHive;
    HANDLE          FileHandle;
    ULONG           LengthToWrite;
    LONG            WaitBufferCount = 0;
    LONG            idx;
    ULONG           arrayCount = 0;
    PVOID           DataBuffer;
    ULONG           DataLength;
    BOOLEAN         ret_val = TRUE;

    if (CmpNoWrite) {
        return TRUE;
    }

    ASSERT(FIELD_OFFSET(CMHIVE, Hive) == 0);
    CmHive = (PCMHIVE)Hive;
    FileHandle = CmHive->FileHandles[FileType];
    if (FileHandle == NULL) {
        return TRUE;
    }

    CmKdPrintEx((DPFLTR_CONFIG_ID,CML_IO,"CmpFileWrite:\n"));
    CmKdPrintEx((DPFLTR_CONFIG_ID,CML_IO,"\tHandle=%08lx  ", FileHandle));

    //ASSERT( !HiveWritesThroughCache(Hive,FileType) );

    ExAcquireFastMutexUnsafe(&CmpWriteLock);
    
    for (idx = 0; idx < MAXIMUM_WAIT_OBJECTS; idx++) {
        CmpWriteBlock.EventHandles[idx] = NULL;
#if DBG
        CmpWriteBlock.EventObjects[idx] = NULL;
#endif
    }
    
    //
    // decide whether we wait for IOs to complete or just issue them and
    // rely on the CcFlushCache to do the job
    //
    
    // Bring pages being written into memory first to allow disk to write
    // buffer contiguously.
    for (idx = 0; (ULONG) idx < offsetArrayCount; idx++) {
        char * start = offsetArray[idx].DataBuffer;
        char * end = (char *) start + offsetArray[idx].DataLength;
        while (start < end) {
            // perftouchbuffer globally declared so that compiler won't try
            // to remove it and this loop (if its smart enough?).
            perftouchbuffer += (ULONG) *start;
            start += PAGE_SIZE;
        }
    }

    //
    // We'd really like to just call the filesystems and have them do
    // the right thing.  But the filesystem will attempt to lock our
    // entire buffer into memory, and that may fail for large requests.
    // So we split our reads into 64k chunks and call the filesystem for
    // each one.
    //
    ASSERT_PASSIVE_LEVEL();
    arrayCount = 0;
    DataLength = 0;
    // This outer loop is hit more than once if the MAXIMUM_WAIT_OBJECTS limit
    // is hit before the offset array is drained.
    while (arrayCount < offsetArrayCount) {
        WaitBufferCount = 0;

        // This loop fills the wait buffer.
        while ((arrayCount < offsetArrayCount) &&
               (WaitBufferCount < MAXIMUM_WAIT_OBJECTS)) {

            // If data length isn't zero than the wait buffer filled before the
            // buffer in the last offsetArray element was sent to write file.
            if (DataLength == 0) {
                *FileOffset = offsetArray[arrayCount].FileOffset;
                DataBuffer =  offsetArray[arrayCount].DataBuffer;
                DataLength =  offsetArray[arrayCount].DataLength;
                //
                // Detect attempt to read off end of 2gig file
                // (this should be irrelevent)
                //
                if ((0xffffffff - *FileOffset) < DataLength) {
                    CmKdPrintEx((DPFLTR_CONFIG_ID,CML_BUGCHECK,"CmpFileWrite: runoff\n"));
                    goto Error_Exit;
                }
            }
            // else still more to write out of last buffer.

            while ((DataLength > 0) && (WaitBufferCount < MAXIMUM_WAIT_OBJECTS)) {

                //
                // Convert ULONG to Large
                //
                Offset.LowPart = *FileOffset;
                Offset.HighPart = 0L;

                //
                // trim request down if necessary.
                //
                if (DataLength > MAX_FILE_IO) {
                    LengthToWrite = MAX_FILE_IO;
                } else {
                    LengthToWrite = DataLength;
                }

                // Previously created events are reused.
                if (CmpWriteBlock.EventHandles[WaitBufferCount] == NULL) {
                    status = CmpCreateEvent(SynchronizationEvent,
                                            &(CmpWriteBlock.EventHandles[WaitBufferCount]),
                                            &(CmpWriteBlock.EventObjects[WaitBufferCount]));
                    if (!NT_SUCCESS(status)) {
                        // Make sure we don't try to clean this up.
                        CmpWriteBlock.EventHandles[WaitBufferCount] = NULL;
                        goto Error_Exit;
                    }
                }
                
                status = ZwWriteFile(FileHandle,
                                     CmpWriteBlock.EventHandles[WaitBufferCount],
                                     NULL,               // apcroutine
                                     NULL,               // apccontext
                                     &(CmpWriteBlock.IoStatus[WaitBufferCount]),
                                     DataBuffer,
                                     LengthToWrite,
                                     &Offset,
                                     NULL);
                        
                if (!NT_SUCCESS(status)) {
                    goto Error_Exit;
                } 

                WaitBufferCount++;
                
                //
                // adjust offsets
                //
                *FileOffset = Offset.LowPart + LengthToWrite;
                DataLength -= LengthToWrite;
                (PUCHAR)DataBuffer += LengthToWrite;
            } // while (DataLength > 0 && WaitBufferCount < MAXIMUM_WAIT_OBJECTS)
            
            arrayCount++;
            
        } // while (arrayCount < offsetArrayCount && 
          //        WaitBufferCount < MAXIMUM_WAIT_OBJECTS)

        status = KeWaitForMultipleObjects(WaitBufferCount, 
                                          CmpWriteBlock.EventObjects,
                                          WaitAll,
                                          Executive,
                                          KernelMode, 
                                          FALSE, 
                                          NULL,
                                          CmpWriteBlock.WaitBlockArray);
    
        if (!NT_SUCCESS(status))
            goto Error_Exit;
    
        for (idx = 0; idx < WaitBufferCount; idx++) {
            if (!NT_SUCCESS(CmpWriteBlock.IoStatus[idx].Status)) {
                status = CmpWriteBlock.IoStatus[idx].Status;
                ret_val = FALSE;
                goto Done;
            }
        }
        
        // There may still be more to do if the last element held a big buffer
        // and the wait buffer filled before it was all sent to the file.
        if (DataLength > 0) {
            arrayCount--;
        }

    } // while (arrayCount < offsetArrayCount)

    ret_val = TRUE;

    goto Done;
Error_Exit:
    //
    // set debugging info
    //
    CmRegistryIODebug.Action = CmpIoFileWrite;
    CmRegistryIODebug.Handle = FileHandle;
    CmRegistryIODebug.Status = status;
#ifndef _CM_LDR_
    DbgPrintEx(DPFLTR_CONFIG_ID,DPFLTR_TRACE_LEVEL,"CmpFileWrite: error exiting %d\n", status);
#endif //_CM_LDR_
    //
    // if WaitBufferCount > 0 then we have successfully issued
    // some I/Os, but not all of them. This is an error, but we
    // cannot return from this routine until all the successfully
    // issued I/Os have completed.
    //
    if (WaitBufferCount > 0) {
        //
        // only if we decided that we want to wait for the write to complete 
        // (log files and hives not using the mapped views technique)
        //
        status = KeWaitForMultipleObjects(WaitBufferCount, 
                                          CmpWriteBlock.EventObjects,
                                          WaitAll,
                                          Executive,
                                          KernelMode, 
                                          FALSE, 
                                          NULL,
                                          CmpWriteBlock.WaitBlockArray);
    }


    ret_val = FALSE;
Done:
    idx = 0;
    // Clean up open event handles and objects.
    while ((idx < MAXIMUM_WAIT_OBJECTS) && (CmpWriteBlock.EventHandles[idx] != NULL)) {
        ASSERT( CmpWriteBlock.EventObjects[idx] );
        ObDereferenceObject(CmpWriteBlock.EventObjects[idx]);
        ZwClose(CmpWriteBlock.EventHandles[idx]);
        idx++;
    }

    ExReleaseFastMutexUnsafe(&CmpWriteLock);

    return ret_val;
}


BOOLEAN
CmpFileFlush (
    PHHIVE          Hive,
    ULONG           FileType,
    PLARGE_INTEGER  FileOffset,
    ULONG           Length
    )
/*++

Routine Description:

    This routine performs a flush on a file handle.

Arguments:

    Hive - Hive we are doing I/O for

    FileType - which supporting file to use

    FileOffset - If this parameter is supplied (not NULL), then only the
                 byte range specified by FileOffset and Length are flushed.

    Length - Defines the length of the byte range to flush, starting at
             FileOffset.  This parameter is ignored if FileOffset is
             specified as NULL.
    

Return Value:

    FALSE if failure
    TRUE if success

Note: 
    
    FileOffset and Length are only taken into account when FileType == HFILE_TYPE_PRIMARY
    and the hive uses the mapped-views method.
--*/
{
    NTSTATUS        status;
    IO_STATUS_BLOCK IoStatus;
    PCMHIVE         CmHive;
    HANDLE          FileHandle;

    ASSERT(FIELD_OFFSET(CMHIVE, Hive) == 0);
    CmHive = (PCMHIVE)Hive;
    FileHandle = CmHive->FileHandles[FileType];
    if (FileHandle == NULL) {
        return TRUE;
    }

    if (CmpNoWrite) {
        return TRUE;
    }

    CmKdPrintEx((DPFLTR_CONFIG_ID,CML_IO,"CmpFileFlush:\n\tHandle = %08lx\n", FileHandle));

    ASSERT_PASSIVE_LEVEL();


    if( HiveWritesThroughCache(Hive,FileType) == TRUE ) {       
        //
        // OK, we need to flush using CcFlushCache
        //
        CcFlushCache (CmHive->FileObject->SectionObjectPointer,(PLARGE_INTEGER)((ULONG_PTR)FileOffset + 1)/*we are private writers*/,Length,&IoStatus);
        status = IoStatus.Status;
    } else {
        //
        // hive loaded into paged pool; or not primary
        //
        status = ZwFlushBuffersFile(
                    FileHandle,
                    &IoStatus
                    );
    }

    if (NT_SUCCESS(status)) {
        ASSERT(IoStatus.Status == status);
        return TRUE;
    } else {
        //
        // set debugging info
        //
        CmRegistryIODebug.Action = CmpIoFileFlush;
        CmRegistryIODebug.Handle = FileHandle;
        CmRegistryIODebug.Status = status;

#ifndef _CM_LDR_
        DbgPrintEx(DPFLTR_CONFIG_ID,DPFLTR_TRACE_LEVEL,"CmpFileFlush:\tFailure1: status = %08lx  IoStatus = %08lx\n",status,IoStatus.Status);
#endif //_CM_LDR_

#ifdef DRAGOSS_PRIVATE_DEBUG
        DbgBreakPoint();
#endif //DRAGOSS_PRIVATE_DEBUG

        return FALSE;
    }
    return TRUE;
}
