/*++

Copyright (c) 1992-1997  Microsoft Corporation

Module Name:

    logger.c

Abstract:

    Provides the persistent log used by the cluster registry service

    This is a very simple logger that supports writing arbitrarily
    sized chunks of data in an atomic fashion.

Author:

    John Vert (jvert) 15-Dec-1995

Revision History:
    sunitas : added mount, scan, checkpointing, reset functionality.
    sunitas : added large record support
--*/
#include "service.h"
#include "lmp.h"
#include "clusudef.h"
/****
@doc    EXTERNAL INTERFACES CLUSSVC LM
****/

/****
@func       HLOG | LogCreate| Creates or opens a log file. If the file
            does not exist, it will be created. If the file already exists, and is
            a valid log file, it will be opened.

@parm       IN LPWSTR | lpFileName | Supplies the name of the log file to create or open.

@parm       IN DWORD | dwMaxFileSize | Supplies the maximum file size in bytes, must be
            greater than 8K and    smaller than 4 gigabytes.  If the file is exceeds this
            size, the reset function will be called. If 0, the maximum log file size limit
            is set to the default maximum size.

@parm       IN PLOG_GETCHECKPOINT_CALLBACK | CallbackRoutine | The callback routine that
            will provide a checkpoint file and the transaction associated with that checkpoint
            file when LogCheckPoint() is called for this log file.  If this is NULL, then the checkpointing capabilities are
            not associated with the log file.

@parm       IN PVOID | pGetChkPtContext | Supplies an arbitrary context pointer, which will be
            passed to the CallbackRoutine.

@parm       IN BOOL | bForceReset | If true, this function creates an empty log file 
            if the log file doesnt exist or if it is corrupt.

@parm       LSN | *LastLsn | If present, Returns the last LSN written to the log file.
              (NULL_LSN if the log file was created)

@rdesc      Returns a handle suitable for use in subsequent log calls.  NUll in the case of an error.

@xref       <f LogClose>
****/
HLOG
LogCreate(
    IN LPWSTR lpFileName,
    IN DWORD  dwMaxFileSize,
    IN PLOG_GETCHECKPOINT_CALLBACK CallbackRoutine,
    IN PVOID  pGetChkPtContext,
    IN BOOL     bForceReset,
    OPTIONAL OUT LSN *pLastLsn
    )
{
    PLOG    pLog;
    
    ClRtlLogPrint(LOG_NOISE,
        "[LM] LogCreate : Entry FileName=%1!ls! MaxFileSize=0x%2!08lx!\r\n",
        lpFileName,dwMaxFileSize);

    //create the log structure
    pLog = LogpCreate(lpFileName, dwMaxFileSize, CallbackRoutine, 
        pGetChkPtContext, bForceReset, pLastLsn);

    if (pLog == NULL)       
        goto FnExit;

    //create a timer for this log
    //SS:TODO?? - currently we have a single timer perfile
    //if that is too much of an overhead, we can manage multiple
    //file with a single timer.
    //create a synchronization timer to manage this file
    pLog->hTimer = CreateWaitableTimer(NULL, FALSE, NULL);

    if (!(pLog->hTimer))
    {
    	CL_LOGFAILURE(GetLastError());
    	return (0);
    }

    //register the timer for this log with the periodic activity timer thread
    AddTimerActivity(pLog->hTimer, LOG_MANAGE_INTERVAL, 1, LogpManage, (HLOG)pLog);

FnExit:
    ClRtlLogPrint(LOG_NOISE,
        "[LM] LogCreate : Exit *LastLsn=0x%1!08lx! Log=0x%2!08lx!\r\n",
        *pLastLsn, pLog);
    return((HLOG)pLog);
}

/****
@func       DWORD | LogGetInfo | This is the callback registered to perform
            periodic management functions like flushing for quorum log files.

@parm       IN HLOG | hLog | Supplies the identifier of the log.

@parm       OUT LPWSTR | szFileName | Should be a pointer to a buffer MAX_PATH characters wide.

@parm       OUT LPDWORD | pdwCurLogSize | The current size of the log file
            is returned via this.

@parm       OUT LPDWORD | pdwMaxLogSize | The maximum size of the log file
            is returned via this.

@rdesc      ERROR_SUCCESS if successful. Win32 error code if something horrible happened.

@xref       <f LogCreate>
****/
DWORD LogGetInfo(
    IN  HLOG    hLog,
    OUT LPWSTR  szFileName,
    OUT LPDWORD pdwCurLogSize,
    OUT LPDWORD pdwMaxLogSize
    )
{
    PLOG    pLog;
    DWORD   dwError=ERROR_SUCCESS;
    
    GETLOG(pLog, hLog);

    EnterCriticalSection(&pLog->Lock);

    *pdwMaxLogSize = pLog->MaxFileSize;
    *pdwCurLogSize = pLog->FileSize;
    lstrcpyW(szFileName, pLog->FileName);

    LeaveCriticalSection(&pLog->Lock);

    return(dwError);
}

/****
@func       DWORD | LogSetInfo | This is the callback registered to perform
            periodic management functions like flushing for quorum log files.

@parm       IN HLOG | hLog | Supplies the identifier of the log.

@parm       OUT LPWSTR | szFileName | Should be a pointer to a buffer MAX_PATH characters wide.

@parm       OUT LPDWORD | pdwCurLogSize | The current size of the log file
            is returned via this.

@parm       OUT LPDWORD | pdwMaxLogSize | The maximum size of the log file
            is returned via this.

@rdesc      ERROR_SUCCESS if successful. Win32 error code if something horrible happened.

@xref       <f LogCreate>
****/
DWORD LogSetInfo(
    IN  HLOG    hLog,
    IN  DWORD   dwMaxLogSize
    )
{
    PLOG    pLog;
    DWORD   dwError=ERROR_SUCCESS;
    
    GETLOG(pLog, hLog);

    EnterCriticalSection(&pLog->Lock);

    if (dwMaxLogSize == 0) dwMaxLogSize = CLUSTER_QUORUM_DEFAULT_MAX_LOG_SIZE;

    pLog->MaxFileSize = dwMaxLogSize;

    LeaveCriticalSection(&pLog->Lock);

    return(dwError);
}

/****
@func       DWORD | LogClose | Closes an open log file. All pending log writes are
            committed, all allocations are freed, and all handles are closed.

@parm       HLOG | hLog | Supplies the identifier of the log.

@rdesc      ERROR_SUCCESS if successful. Win32 error code if something horrible happened.

@xref       <f LogCreate>
****/
DWORD
LogClose(
    IN HLOG LogFile
    )
{
    PLOG    pLog;
    LSN     NextLsn;
    BOOL    Success;

    ClRtlLogPrint(LOG_NOISE,
        "[LM] LogClose : Entry LogFile=0x%1!08lx!\r\n",
        LogFile);

    GETLOG(pLog, LogFile);

    //this will close the timer handle
    // we do this while not holding the log so that if a
    // timer event to flush fires it has an opportunity to finish
    if (pLog->hTimer) 
    {
        RemoveTimerActivity(pLog->hTimer);
        pLog->hTimer = NULL;
    }
    EnterCriticalSection(&pLog->Lock);

    
    //if the file is open, LogReset calls LogClose after closing the log handle
    if (pLog->FileHandle)
    {
        NextLsn = LogFlush(LogFile, pLog->NextLsn);
        //close the file handle
        Success = CloseHandle(pLog->FileHandle);
        CL_ASSERT(Success);
    }

    Success = CloseHandle(pLog->Overlapped.hEvent);
    CL_ASSERT(Success);


    AlignFree(pLog->ActivePage);
    CrFree(pLog->FileName);
    LeaveCriticalSection(&pLog->Lock);
    DeleteCriticalSection(&pLog->Lock);
    ZeroMemory(pLog, sizeof(LOG));                   // just in case somebody tries to
                                                    // use this log again.
    CrFree(pLog);

    ClRtlLogPrint(LOG_NOISE,
        "[LM] LogClose : Exit returning success\r\n");

    return(ERROR_SUCCESS);
}


/****
@func       LSN | LogWrite | Writes a log record to the log file. The record is not
            necessarily committed until LogFlush is called with an LSN greater or equal to the returned LSN.

@parm       HLOG | hLog | Supplies the identifier of the log.

@parm       TRID | TransactionId | Supplies a transaction ID of the record.

@parm       TRID | TransactionType | Supplies a transaction type, start/commit/complete/unit
            transaction.

@parm       RMID | ResourceId | Supplies the ID of the resource manager submitting the log record.

@parm       RMTYPE | ResourceFlags |Resource manager associated flags to be associated with this log record.

@parm       PVOID | LogData | Supplies a pointer to the data to be logged.

@parm       DWORD | DataSize | Supplies the number of bytes of data pointed to by LogData

@rdesc      The LSN of the log record that was created. NULL_LSN if something terrible happened.
            GetLastError() will provide the error code.

@xref       <f LogRead> <f LogFlush>
****/
LSN
LogWrite(
    IN HLOG     LogFile,
    IN TRID     TransactionId,
    IN TRTYPE   XsactionType,
    IN RMID     ResourceId,
    IN RMTYPE   ResourceFlags,
    IN PVOID    LogData,
    IN DWORD    DataSize
    )
{
    PLOGPAGE    Page;
    PLOG        Log;
    PLOGRECORD  LogRecord;
    LSN         Lsn=NULL_LSN;
    BOOL        bMaxFileSizeReached;
    DWORD       TotalSize;
    DWORD       dwError;
    DWORD       dwNumPages;
    
    ClRtlLogPrint(LOG_NOISE,
        "[LM] LogWrite : Entry TrId=%1!u! RmId=%2!u! RmType = %3!u! Size=%4!u!\r\n",
        TransactionId, ResourceId, ResourceFlags, DataSize);

    CL_ASSERT(ResourceId > RMAny);     // reserved for logger's use

    GETLOG(Log, LogFile);
    TotalSize = sizeof(LOGRECORD) + (DataSize + 7) & ~7;       // round up to qword size

    EnterCriticalSection(&Log->Lock);

#if DBG    
    {
        DWORD dwOldProtect;
        DWORD Status;
        BOOL VPWorked;

        VPWorked = VirtualProtect(Log->ActivePage, Log->SectorSize, PAGE_READWRITE, &dwOldProtect);
        Status = GetLastError();
        CL_ASSERT( VPWorked );
    }        
#endif        

    Page = LogpAppendPage(Log, TotalSize, &LogRecord, &bMaxFileSizeReached, &dwNumPages);
    //if a new page couldnt be allocated due to file size limits,
    //then try and reset the log
    if ((Page == NULL) && bMaxFileSizeReached)
    {
        //after resetting the log, try and allocate record space again
        LogpWriteWarningToEvtLog(LM_LOG_EXCEEDS_MAXSIZE, Log->FileName);
        dwError = LogReset(LogFile);
        //SS:LogReset sets the page to be readonly again
#if DBG    
        {
            DWORD dwOldProtect;
            DWORD Status;
            BOOL VPWorked;

            VPWorked = VirtualProtect(Log->ActivePage, Log->SectorSize, PAGE_READWRITE, &dwOldProtect);
            Status = GetLastError();
            CL_ASSERT( VPWorked );
        }        
#endif        
        if (dwError == ERROR_SUCCESS)
            Page = LogpAppendPage(Log, TotalSize, &LogRecord, &bMaxFileSizeReached, &dwNumPages);
        else
            SetLastError(dwError);
    }

    if (Page == NULL)
    {
        ClRtlLogPrint(LOG_UNUSUAL,
        "[LM] LogWrite : LogpAppendPage failed.\r\n");
        goto FnExit;
    }

    CL_ASSERT(((ULONG_PTR)LogRecord & 0x7) == 0);      // ensure qword alignment
    Lsn = MAKELSN(Page, LogRecord);

    //
    // Fill in log record.
    //
    LogRecord->Signature = LOGREC_SIG;
    LogRecord->ResourceManager = ResourceId;
    LogRecord->Transaction = TransactionId;
    LogRecord->XsactionType = XsactionType;
    LogRecord->Flags = ResourceFlags;
    GetSystemTimeAsFileTime(&LogRecord->Timestamp);
    LogRecord->NumPages = dwNumPages;
    LogRecord->DataSize = DataSize;
    if (dwNumPages < 1)
        CopyMemory(&LogRecord->Data, LogData, DataSize);
    else
    {
        if (LogpWriteLargeRecordData(Log, LogRecord, LogData, DataSize) 
            != ERROR_SUCCESS)
        {
            ClRtlLogPrint(LOG_UNUSUAL,
                "[LM] LogWrite : LogpWriteLargeRecordData failed. Lsn=0x%1!08lx!\r\n",
                Lsn);
            Lsn = NULL_LSN;                
        }
    }

FnExit:
#if DBG    
    {
        DWORD dwOldProtect;
        DWORD Status;
        BOOL VPWorked;

        VPWorked = VirtualProtect(Log->ActivePage, Log->SectorSize, PAGE_READONLY, & dwOldProtect);
        Status = GetLastError();
        CL_ASSERT( VPWorked );
    }        
#endif        

    LeaveCriticalSection(&Log->Lock);
    ClRtlLogPrint(LOG_NOISE,
        "[LM] LogWrite : Exit returning=0x%1!08lx!\r\n",
        Lsn);

    return(Lsn);
}


/****
@func       LSN | LogCommitSize | Commits the size for a record of this size.

@parm       HLOG | hLog | Supplies the identifier of the log.

@parm       DWORD | dwSize | Supplies the size of the data that needs
            to be logged.

@rdesc      The LSN of the log record that was created. NULL_LSN if something terrible happened.
            GetLastError() will provide the error code.

@xref       <f LogRead> <f LogFlush>
****/
DWORD
LogCommitSize(
    IN HLOG     hLog,
    IN RMID     ResourceId,
    IN DWORD    dwDataSize
    )
{
    PLOGPAGE    Page;
    PLOG        pLog;
    PLOGRECORD  LogRecord;
    LSN         Lsn=NULL_LSN;
    BOOL        bMaxFileSizeReached;
    DWORD       dwTotalSize;
    DWORD       dwError = ERROR_SUCCESS;
    DWORD       dwNumPages;
    
    ClRtlLogPrint(LOG_NOISE,
        "[LM] LogCommitSize : Entry RmId=%1!u! Size=%2!u!\r\n",
        ResourceId, dwDataSize);

#ifdef CLUSTER_TESTPOINT
    TESTPT(TpFailLogCommitSize) {
        dwError = ERROR_CLUSTERLOG_NOT_ENOUGH_SPACE;
        return(dwError);
    }
#endif

    CL_ASSERT(ResourceId > RMAny);     // reserved for logger's use

    GETLOG(pLog, hLog);
    dwTotalSize = sizeof(LOGRECORD) + (dwDataSize + 7) & ~7;       // round up to qword size

    EnterCriticalSection(&pLog->Lock);
    //dont force the file to grow beyond its max limit
    dwError = LogpEnsureSize(pLog, dwTotalSize, FALSE);
    if (dwError == ERROR_SUCCESS)
        goto FnExit;
    if (dwError == ERROR_CLUSTERLOG_EXCEEDS_MAXSIZE)
    {
        //after resetting the log, try and allocate record space again
        LogpWriteWarningToEvtLog(LM_LOG_EXCEEDS_MAXSIZE, pLog->FileName);
        dwError = LogReset(hLog);
        if (dwError == ERROR_SUCCESS)
        {
            //this time force the file to grow beyond its max size if required
            //this is because we do want to log records greater than the max
            //size of the file
            dwError = LogpEnsureSize(pLog, dwTotalSize, TRUE);

        }        
    }
    if (dwError == ERROR_DISK_FULL)
    {
        //map error
        dwError = ERROR_CLUSTERLOG_NOT_ENOUGH_SPACE;
    }
FnExit:
    LeaveCriticalSection(&pLog->Lock);

    ClRtlLogPrint(LOG_NOISE,
        "[LM] LogCommitSize : Exit, returning 0x%1!08lx!\r\n",
        dwError);

    return(dwError);
}

/****
@func       LSN | LogFlush | Ensures that the given LSN is committed to disk. If this
            routine returns successfully, the given LSN is safely stored on disk and
            is guaranteed to survive a system crash.

@parm       HLOG | hLog | Supplies the identifier of the log.

@parm       LSN | MinLsn | Supplies the minimum LSN to be committed to disk.

@rdesc      The last LSN actually committed to disk. This will always be >= the requested MinLsn.
            NULL_LSN on failure

@xref       <f LogWrite>
****/
LSN
LogFlush(
    IN HLOG LogFile,
    IN LSN MinLsn
    )
{
    PLOG        pLog;
    PLOGPAGE    pPage;
    PLOGRECORD  pRecord;
    LSN         Lsn;
    LSN         FlushedLsn = NULL_LSN;
    DWORD       dwBytesWritten;
    DWORD       dwError;

/*
    //SS: avoid clutter in cluster log
    ClRtlLogPrint(LOG_NOISE,
        "[LM] LogFlush : Entry LogFile=0x%1!08lx!\r\n",
        LogFile);

*/
    GETLOG(pLog, LogFile);

    EnterCriticalSection(&pLog->Lock);

    
    //if the MinLSN is greater than a record written to the log file
    if (MinLsn > pLog->NextLsn)
    {
        dwError = ERROR_INVALID_PARAMETER;
        goto FnExit;
    }


    //find the first record on the active page
    pPage = pLog->ActivePage;
    pRecord = &pPage->FirstRecord;
    Lsn = MAKELSN(pPage, pRecord);


    //if the lsn till which a flush is requested is on an unflushed page,
    //and there are records on the unflushed page and if the flush till
    // this lsn hasnt occured before, orchestrate a flush
    // SS: this scheme is not perfect though it shouldnt cause unnecessary
    // flushing as the flushing interval is 2 minutes..ideally we want to delay the
    // flush if the writes are in progress.
    if ((MinLsn >= Lsn) && (Lsn < pLog->NextLsn) &&  (MinLsn > pLog->FlushedLsn))
    {
        //there are uncommited records
        ClRtlLogPrint(LOG_NOISE,
            "[LM] LogFlush : pLog=0x%1!08lx! writing the %2!u! bytes for active page at offset 0x%3!08lx!\r\n",
            pLog, pPage->Size, pPage->Offset);

        (pLog->Overlapped).Offset = pPage->Offset;
        (pLog->Overlapped).OffsetHigh = 0;

        if ((dwError = LogpWrite(pLog, pPage, pPage->Size, &dwBytesWritten))
            != ERROR_SUCCESS)
        {
            ClRtlLogPrint(LOG_UNUSUAL,
                "[LM] LogFlush::LogpWrite failed, error=0x%1!08lx!\r\n",
                dwError);
            CL_LOGFAILURE(dwError);
            goto FnExit;
        }
        pLog->FlushedLsn = FlushedLsn = pLog->NextLsn;
    }


/*
    //SS: avoid clutter in cluster log
    ClRtlLogPrint(LOG_NOISE,
        "[LM] LogFlush : returning 0x%1!08lx!\r\n",
            pLog->NextLsn);
*/
FnExit:
    LeaveCriticalSection(&pLog->Lock);
    return(FlushedLsn);

}



/****
@func       LSN | LogRead | Reads a log record from the given log.

@parm       IN HLOG | hLog | Supplies the identifier of the log.

@parm       IN LSN | Lsn | The LSN of the record to be read.  If NULL_LSN, the first
            record is read.

@parm       OUT RMID | *ResourceId | Returns the resource ID of the requested log record.

@parm       OUT RMTYPE | *ResourceFlags |Returns the resource flags associated with the
            requested log record.

@parm       OUT TRID | *Transaction | Returns the TRID of the requested log record

@parm       TRID | TrType | Returns the transaction type, start/commit/complete/unit
            transaction.

@parm       OUT PVOID | LogData | Returns the data associated with the log record.

@parm       IN OUT DWORD | *DataSize | Supplies the available size of the LogData buffer.
               Returns the number of bytes of data in the log record

@rdesc      Returns the next LSN in the log file.  On failure, returns NULL_LSN.

@xref       <f LogWrite>
****/
LSN
LogRead(
    IN HLOG LogFile,
    IN LSN Lsn,
    OUT RMID *Resource,
    OUT RMTYPE *ResourceFlags,
    OUT TRID *Transaction,
    OUT TRTYPE *TrType,
    OUT PVOID LogData,
    IN OUT DWORD *DataSize
    )
{
    PLOG        pLog;
    DWORD       PageIndex;
    PLOGPAGE    pPage=NULL;
    BOOL        Success;
    DWORD       dwError=ERROR_SUCCESS;
    LSN         NextLsn=NULL_LSN;
    PLOGRECORD  pRecord;
    DWORD       BytesRead;
    LOGPAGE     Dummy;
    PLOGPAGE    pCurPage;
    
    
    GETLOG(pLog, LogFile);
    CL_ASSERT(pLog->SectorSize == SECTOR_SIZE);
    
    EnterCriticalSection(&pLog->Lock);

    Dummy.Size = SECTOR_SIZE;
    if (Lsn == NULL_LSN) 
    {
        Lsn = pLog->SectorSize + RECORDOFFSETINPAGE(&Dummy, &Dummy.FirstRecord);
    }

    if (Lsn >= pLog->NextLsn) 
    {
        CL_LOGFAILURE(dwError = ERROR_INVALID_PARAMETER);
        goto FnExit;
    }

    //
    // Translate LSN to a page number and offset within the page
    //
    PageIndex = LSNTOPAGE(Lsn);

    //if the record exists in the unflushed page, dont need to read 
    //from the disk
    if (pLog->ActivePage->Offset == PageIndex * pLog->SectorSize)
    {
        //if this data is being read, should we flush the page
        pCurPage = pLog->ActivePage;
        goto GetRecordData;
    }

    pPage = AlignAlloc(SECTOR_SIZE);
    if (pPage == NULL) 
    {
        CL_LOGFAILURE(dwError = ERROR_NOT_ENOUGH_MEMORY);
        goto FnExit;
    }

    pCurPage = pPage;

    //
    // Translate LSN to a page number and offset within the page
    //
    PageIndex = LSNTOPAGE(Lsn);

    pLog->Overlapped.Offset = PageIndex * pLog->SectorSize;
    pLog->Overlapped.OffsetHigh = 0;

    Success = ReadFile(pLog->FileHandle,
                       pCurPage,
                       SECTOR_SIZE,
                       &BytesRead,
                       &pLog->Overlapped);
    if (!Success && (GetLastError() == ERROR_IO_PENDING)) {
        Success = GetOverlappedResult(pLog->FileHandle,
                                      &pLog->Overlapped,
                                      &BytesRead,
                                      TRUE);
    }
    if (!Success)
    {
        CL_UNEXPECTED_ERROR(dwError = GetLastError());
        NextLsn = NULL_LSN;
        goto FnExit;
    }
    
GetRecordData:    
    pRecord = LSNTORECORD(pCurPage, Lsn);
    if (pRecord->Signature != LOGREC_SIG)
    {
        dwError = ERROR_CLUSTERLOG_CORRUPT;
        NextLsn = NULL_LSN;
        goto FnExit;
    }

    *Resource = pRecord->ResourceManager;
    *ResourceFlags = pRecord->Flags;
    *Transaction = pRecord->Transaction;
    *TrType = pRecord->XsactionType;
    if (LogData)
        CopyMemory(LogData, pRecord->Data, *DataSize);
    *DataSize = pRecord->RecordSize - sizeof(LOGRECORD);
    NextLsn = Lsn + pRecord->RecordSize;

    pRecord = LSNTORECORD(pCurPage, NextLsn);
    if (pRecord->ResourceManager == RMPageEnd) 
    {
        //
        // The next log record is the end of page marker
        // Adjust the LSN to be the one at the start of the
        // next page.
        //
        NextLsn = pCurPage->Offset + pCurPage->Size + 
            RECORDOFFSETINPAGE(pCurPage, &pCurPage->FirstRecord);
    }

FnExit:
    LeaveCriticalSection(&pLog->Lock);
    if (dwError !=ERROR_SUCCESS)
        SetLastError(dwError);
    if (pPage) AlignFree(pPage);
    return(NextLsn);

}

/****
@cb         BOOL |(WINAPI *PLOG_SCAN_CALLBACK)| The callback called by LogScan.

@parm       IN PVOID | Context | Supplies the CallbackContext specified to LogScan

@parm       IN LSN | Lsn | Supplies the LSN of the record.

@parm       IN RMID | Resource | Supplies the resource identifier of the log record

@parm       IN TRID | Transaction | Supplies the transaction identifier of the log record

@parm       IN PVOID | LogData | Supplies a pointer to the log data. This is a read-
            only pointer and may be referenced only until the callback returns.

@parm       IN DWORD | DataLength | Supplies the length of the log record.

@rdesc      Returns TRUE to  Continue scan or FALSE to Abort scan.

@xref       <f LogScan>
****/

/****
@func       LSN | LogScan| Initiates a scan of the log. The scan can be done either forwards (redo) or
            backwards (undo).

@parm       IN HLOG | hLog | Supplies the identifier of the log.

@parm       IN LSN | First | Supplies the first LSN to start with. If NULL_LSN is specified,
            the scan begins at the start (for a forward scan) or end (for
            a backwards scan) of the log.

@parm       IN BOOL | ScanForward | Supplies the direction to scan the log in.
                TRUE - specifies a forward (redo) scan
                FALSE - specifies a backwards (undo) scan.

@parm       IN PLOG_SCAN_CALLBACK | CallbackRoutine |Supplies the routine to be called for each log record.

@parm       IN PVOID | CaklbackContext | Supplies an arbitrary context pointer, which will be
            passed to the CallbackRoutine

@rdesc      ERROR_SUCCESS if successful.  Win32 status if something terrible happened.

@xref       <f LogRead> <f (WINAPI *PLOG_SCAN_CALLBACK)>
****/
DWORD
LogScan(
    IN HLOG LogFile,
    IN LSN FirstLsn,
    IN BOOL ScanForward,
    IN PLOG_SCAN_CALLBACK CallbackRoutine,
    IN PVOID CallbackContext
    )
{
    PLOG        pLog;
    PLOGRECORD  pRecord;
    LOGPAGE     Dummy;
    DWORD       dwError=ERROR_SUCCESS;
    PUCHAR      Buffer;
    PUCHAR      pLargeBuffer;
    PLOGPAGE    pPage;
    int         PageIndex;
    int         OldPageIndex;
    LSN         Lsn;
    DWORD       dwBytesRead;


    ClRtlLogPrint(LOG_NOISE,
        "[LM] LogScan::Entry Lsn=0x%1!08lx! ScanForward=%2!u! CallbackRoutine=0x%3!08lx!\r\n",
        FirstLsn, ScanForward, CallbackRoutine);

    GETLOG(pLog, LogFile);
    CL_ASSERT(pLog->SectorSize == SECTOR_SIZE);

    Buffer = AlignAlloc(SECTOR_SIZE);
    if (Buffer == NULL) {
        CL_UNEXPECTED_ERROR( ERROR_NOT_ENOUGH_MEMORY );
    }

    Lsn = FirstLsn;
    if ((!CallbackRoutine) || (Lsn >= pLog->NextLsn))
    {
        //set to invaid param
        dwError = ERROR_INVALID_PARAMETER;
        goto FnExit;
    }

    if (Lsn == NULL_LSN)
    {
        if (ScanForward)
        {
            Dummy.Size = SECTOR_SIZE;
            //get the Lsn for the first record
            if (Lsn == NULL_LSN)
                Lsn = pLog->SectorSize + RECORDOFFSETINPAGE(&Dummy, &Dummy.FirstRecord);
        }
        else
        {
            //get the Lsn for the last record
            pPage =pLog->ActivePage;
            pRecord = LSNTORECORD(pPage, pLog->NextLsn);
            Lsn = pRecord->PreviousLsn;
        }
    }

    //initialize to -1 so the first page is read
    OldPageIndex = -1;
    pPage = (PLOGPAGE)Buffer;
    //while there are more records that you can read
    //read the page

    //SS: For now we grab the crtitical section for entire duration
    //Might want to change that
    EnterCriticalSection(&pLog->Lock);

    while ((Lsn != NULL_LSN) & (Lsn < pLog->NextLsn))
    {

        //
        // Translate LSN to a page number and offset within the page
        //
        PageIndex = LSNTOPAGE(Lsn);


        if (PageIndex != OldPageIndex)
        {
            //if the record exists in the unflushed page, dont need to read 
            //from the disk
            if (pLog->ActivePage->Offset == PageIndex * pLog->SectorSize)
            {
                //if this data is being read, should we flush the page
                pPage = pLog->ActivePage;
            }
            else
            {
                //read the page
                pLog->Overlapped.Offset = PageIndex * pLog->SectorSize;
                pLog->Overlapped.OffsetHigh = 0;



                dwError = LogpRead(pLog, pPage, pLog->SectorSize, &dwBytesRead);
                //if it is the last page, then set the new page as the active
                //page
                if (dwError)
                {
                    if (dwError == ERROR_HANDLE_EOF)
                    {
                        //not fatal, set this page as current page
                        dwError = ERROR_SUCCESS;
                        //SS: assume that this page has no
                        //records, set the offset
                        //this will be the current page
                        Lsn = NULL_LSN;
                        continue;
                    }
                    else
                        goto FnExitUnlock;
                }
            }
            //read was successful, no need to read the page unless the
            //record falls on a different page
            OldPageIndex = PageIndex;
        }
        pRecord = LSNTORECORD(pPage, Lsn);

        //SS: skip checkpoint records
        //TBD:: what if the user wants to scan checkpoint records
        //as well
        if (pRecord->ResourceManager < RMAny)
        {
            //
            // The next log record is the end of page marker
            // Adjust the LSN to the next one
            //
            Lsn = GETNEXTLSN(pRecord, ScanForward);

            continue;
        }

        //check if the transaction types are the valid ones
        //for application.
        if ((pRecord->XsactionType != TTStartXsaction) && 
            (pRecord->XsactionType != TTCompleteXsaction))
        {
            //Cant assume that the record will fit in a small page
            //even a transaction unit may be large, even though transaction
            //units records are not returned by this call.
            if (pRecord->NumPages < 1)
            {
                Lsn = GETNEXTLSN(pRecord, ScanForward);
            }                
            else
            {
                //if it is a large record it should be followed by
                //an eop page record
                //get the lsn of the eop record
                Lsn = GETNEXTLSN(pRecord,TRUE);

                //get the page index of the page where the eop record resides
                PageIndex = PageIndex + pRecord->NumPages - 1;
                CL_ASSERT(LSNTOPAGE(Lsn) == (DWORD)PageIndex);
                //read the last page for the large record
                (pLog->Overlapped).Offset = PageIndex * pLog->SectorSize;
                (pLog->Overlapped).OffsetHigh = 0;

                ClRtlLogPrint(LOG_NOISE,
                    "[LM] LogScan::reading %1!u! bytes at offset 0x%2!08lx!\r\n",
                    pLog->SectorSize, PageIndex * pLog->SectorSize);

                dwError = LogpRead(pLog, pPage, pLog->SectorSize, &dwBytesRead);
                //if there are no errors, then check the last record
                if (dwError != ERROR_SUCCESS)
                {

                    goto FnExitUnlock;
                }

                //read the page, make sure that the eop record follows the 
                //large record
                pRecord = (PLOGRECORD)((ULONG_PTR) pPage + 
                    (Lsn - (pLog->Overlapped).Offset));
                CL_ASSERT((pRecord->Signature == LOGREC_SIG) && 
                        (pRecord->ResourceManager == RMPageEnd))
                Lsn = GETNEXTLSN(pRecord, TRUE);
            }
            continue;
        }
            
        ClRtlLogPrint(LOG_NOISE,
            "[LM] LogScan::Calling the scancb for Lsn=0x%1!08lx! Trid=%2!u! RecordSize=%3!u!\r\n",
            Lsn, pRecord->Transaction, pRecord->DataSize);

        if (pRecord->NumPages < 1)
        {
             //if the callback requests to stop scan
            if (!(*CallbackRoutine)(CallbackContext, Lsn, pRecord->ResourceManager,
                pRecord->Flags, pRecord->Transaction, pRecord->XsactionType,
                pRecord->Data, pRecord->DataSize))
            break;
            //else go the the next record
            Lsn = GETNEXTLSN(pRecord, ScanForward);

        }
        else
        {
            //for a large record you need to read in the entire data
            pLargeBuffer = AlignAlloc(pRecord->NumPages * SECTOR_SIZE);
            if (pLargeBuffer == NULL) 
            {
                //exit and put something in the eventlog
                dwError = ERROR_NOT_ENOUGH_MEMORY ;
                CL_LOGFAILURE(ERROR_NOT_ENOUGH_MEMORY);
                break;
            }
            //read the pages
            pLog->Overlapped.Offset = PageIndex * pLog->SectorSize;
            pLog->Overlapped.OffsetHigh = 0;

            dwError = LogpRead(pLog, pLargeBuffer, pRecord->NumPages *
                pLog->SectorSize, &dwBytesRead);
            //if it is the last page, then set the new page as the active
            //page
            if (dwError != ERROR_SUCCESS)
            {
                CL_LOGFAILURE(dwError);
                AlignFree(pLargeBuffer);
                break;
            }
            pRecord = LSNTORECORD((PLOGPAGE)pLargeBuffer, Lsn);

            //if the callback requests to stop scan
            if (!(*CallbackRoutine)(CallbackContext, Lsn, pRecord->ResourceManager,
                pRecord->Flags, pRecord->Transaction, pRecord->XsactionType,
                pRecord->Data, pRecord->DataSize))
            {    
                AlignFree(pLargeBuffer);
                break;
            }
            //the next record should be an eop buffer on the last page
            //of the large record, skip that
            Lsn = GETNEXTLSN(pRecord, ScanForward);
            //since this page doesnt begin with the typical page info,
            //you cant validate this
            pRecord = (PLOGRECORD)(
                (ULONG_PTR)(pLargeBuffer + (Lsn - (pLog->Overlapped).Offset)));

            CL_ASSERT(pRecord->ResourceManager == RMPageEnd);

            //go the the next record
            Lsn = GETNEXTLSN(pRecord, ScanForward);
            AlignFree(pLargeBuffer);
        }
    }

FnExitUnlock:
    LeaveCriticalSection(&pLog->Lock);

    ClRtlLogPrint(LOG_NOISE,
        "[LM] LogScan::Exit - Returning 0x%1!08lx!\r\n",
        dwError);

FnExit:
    AlignFree(Buffer);
    return(dwError);

}


/****
@func       LSN | LogCheckPoint| Initiates a chk point process in the log.

@parm       IN HLOG | hLog | Supplies the identifier of the log.

@parm       IN BOOL | bAllowReset | Allow the reset of log file to occur
            while checkpointing.  In general, this will be set to TRUE.  Since
            When LogReset() internally invokes LogCheckPoint(), this will be
            set to FALSE.

@parm       IN LPCWSTR | lpszChkPtFile | The checkpt file that should be saved.
            If NULL, the callback function registered for checkpointing is invoked
            to get the checkpoint.

@parm       IN DWORD | dwChkPtSeq | If lpszChkPtFile is not NULL, this should point
            to a valid sequence number associated with the checkpoint.

@rdesc      ERROR_SUCCESS if successful.  Win32 status if something terrible happened.

@comm       The log manager writes the start check point record. Then it invokes the call back to get the checkpoint data.  After writing the
            data to a checkpoint file, it records the lsn of the end checkpoint record in the header.

@xref       <f LogGetLastChkPoint>
****/
DWORD
LogCheckPoint(
    IN HLOG     LogFile,
    IN BOOL     bAllowReset,
    IN LPCWSTR  lpszInChkPtFile,
    IN DWORD    dwChkPtSeq
    )
{
    PLOG            pLog;
    PLOGPAGE        pPage;
    PLOG_HEADER     pLogHeader=NULL;
    DWORD           dwError=ERROR_SUCCESS;
    DWORD           dwTotalSize;
    PLOGRECORD      pLogRecord;
    TRID            ChkPtTransaction,Transaction;
    LSN             Lsn,ChkPtLsn;
    LOG_CHKPTINFO   ChkPtInfo;
    DWORD           dwBytesRead,dwBytesWritten;
    RMID            Resource;
    RMTYPE          RmType;
    BOOL            bMaxFileSizeReached;
    WCHAR           szNewChkPtFile[LOG_MAX_FILENAME_LENGTH];
    DWORD           dwNumPages;
    WCHAR           szPathName[MAX_PATH];
    TRTYPE          TrType;
    DWORD           dwCheckSum = 0;
    DWORD           dwHeaderSum = 0;
    DWORD           dwLen;
    
    GETLOG(pLog, LogFile);
    CL_ASSERT(pLog->SectorSize == SECTOR_SIZE);


    ClRtlLogPrint(LOG_NOISE,
        "[LM] LogCheckPoint entry\r\n");

    ZeroMemory( &ChkPtInfo, sizeof(LOG_CHKPTINFO) );
    
    EnterCriticalSection(&pLog->Lock);
    
#if DBG    
    {
        DWORD dwOldProtect;
        DWORD Status;
        BOOL VPWorked;

        VPWorked = VirtualProtect(pLog->ActivePage, pLog->SectorSize, PAGE_READWRITE, & dwOldProtect);
        Status = GetLastError();
        CL_ASSERT( VPWorked );
    }        
#endif        


    //write the start chkpoint record
    dwTotalSize = sizeof(LOGRECORD) + 7 & ~7;       // round up to qword size
    pPage = LogpAppendPage(pLog, dwTotalSize, &pLogRecord, &bMaxFileSizeReached, &dwNumPages);
    if ((pPage == NULL) && (bMaxFileSizeReached) && (bAllowReset))
    {
        //try and reset the log file
        //the checkpoint will be taken as a part of the reset process
        //if no input checkpoint file is specified
        //SS:note here LogCheckPoint will be called recursively
        LeaveCriticalSection(&pLog->Lock);
        return(LogpReset(pLog, lpszInChkPtFile));
    }

    if (pPage == NULL)
    {
        CL_LOGFAILURE(dwError = GetLastError());
        ClRtlLogPrint(LOG_UNUSUAL,
            "[LM] LogCheckPoint: LogpAppendPage failed, error=0x%1!08lx!\r\n",
            dwError);
        goto FnExit;
    }

    CL_ASSERT(((ULONG_PTR)pLogRecord & 0x7) == 0);     // ensure qword alignment
    Lsn = MAKELSN(pPage, pLogRecord);

    pLogRecord->Signature = LOGREC_SIG;
    pLogRecord->ResourceManager = RMBeginChkPt;
    pLogRecord->Transaction = 0;                 
    pLogRecord->XsactionType = TTDontCare;
    pLogRecord->Flags = 0;
    GetSystemTimeAsFileTime(&pLogRecord->Timestamp);
    pLogRecord->NumPages = dwNumPages;
    pLogRecord->DataSize = 0;
    
    //if chkpt is not specifed get one from input checkpoint
    if (!lpszInChkPtFile)
    {
        if (!pLog->pfnGetChkPtCb)
        {
            dwError = ERROR_INVALID_PARAMETER;
            CL_LOGFAILURE(dwError);
            goto FnExit;
        }

        // get a checkpoint
        dwError = DmGetQuorumLogPath(szPathName, sizeof(szPathName));
        if (dwError  != ERROR_SUCCESS)
        {
            dwError = GetLastError();
            ClRtlLogPrint(LOG_UNUSUAL,
                "[LM] LogCheckPoint: DmGetQuorumLogPath failed, error=%1!u!\r\n",
                dwError);
            goto FnExit;
        }

        szNewChkPtFile[0]= TEXT('\0');
        dwError = (*(pLog->pfnGetChkPtCb))(szPathName, pLog->pGetChkPtContext, szNewChkPtFile,
            & ChkPtTransaction);
        
        //if the chkptfile is created and if it could not be created because it
        //already existed, then we are set.
        if ((dwError != ERROR_SUCCESS)  &&
            ((dwError != ERROR_ALREADY_EXISTS) || (!szNewChkPtFile[0])))
        {
            ClRtlLogPrint(LOG_UNUSUAL,
                "[LM] LogCheckPoint: Callback failed to return a checkpoint\r\n");
            CL_LOGCLUSERROR1(LM_CHKPOINT_GETFAILED, pLog->FileName);
            goto FnExit;
        }
    }
    else
    {
        //SS:we trust the application to not write a stale checkpoint
        lstrcpyW(szNewChkPtFile, lpszInChkPtFile);
        ChkPtTransaction = dwChkPtSeq;
    }

    //
    //  Chittur Subbaraman (chitturs) - 1/28/99 
    //
    //  Compute and save the checksum for the checkpoint file
    //
    dwError = MapFileAndCheckSumW( szNewChkPtFile, &dwHeaderSum, &dwCheckSum );
    if ( dwError != CHECKSUM_SUCCESS )  
    {
        ClRtlLogPrint(LOG_UNUSUAL,
            "[LM] LogCheckPoint: MapFileAndCheckSumW returned error=%1!u!\r\n",
              dwError);
        goto FnExit;
    }
    ChkPtInfo.dwCheckSum = dwCheckSum;  

    ClRtlLogPrint(LOG_NOISE,
        "[LM] LogCheckPoint: ChkPtFile=%1!ls! Chkpt Trid=%2!d! CheckSum=%3!d!\r\n",
        szNewChkPtFile, ChkPtTransaction, dwCheckSum);

    //prepare the chkpt info structure
    ChkPtInfo.ChkPtBeginLsn = Lsn;
    lstrcpyW(ChkPtInfo.szFileName, szNewChkPtFile);

    //
    //  Chittur Subbaraman (chitturs) - 1/29/99
    //
    //  Add a signature at the end of the file name to denote that
    //  a checksum has been taken.
    //
    dwLen = lstrlenW( ChkPtInfo.szFileName );
    if ( ( dwLen + lstrlenW( CHKSUM_SIG ) + 2 ) <= LOG_MAX_FILENAME_LENGTH )
    {
        lstrcpyW( &ChkPtInfo.szFileName[dwLen+1], CHKSUM_SIG );
    }

    dwTotalSize = sizeof(LOGRECORD) + (sizeof(LOG_CHKPTINFO) + 7) & ~7;       // round up to qword size
    //write the endchk point record to the file.
    pPage = LogpAppendPage(pLog, dwTotalSize, &pLogRecord, &bMaxFileSizeReached, &dwNumPages);
    if ((pPage == NULL) && bMaxFileSizeReached && bAllowReset)
    {
        //try and reset the log file
        ClRtlLogPrint(LOG_NOISE,
            "[LM] LogCheckPoint: Maxfilesize exceeded. Calling LogpReset\r\n");
        //the checkpoint will be taken as a part of the reset process
        //if no input checkpoint file is specified
        //SS:note here LogCheckPoint will be called recursively
        LeaveCriticalSection(&pLog->Lock);
        return(LogpReset(pLog, lpszInChkPtFile));
    }

    if (pPage == NULL) {
        CL_LOGFAILURE(dwError = GetLastError());
        goto FnExit;
    }

    CL_ASSERT(((ULONG_PTR)pLogRecord & 0x7) == 0);     // ensure qword alignment
    ChkPtLsn = MAKELSN(pPage, pLogRecord);

    pLogRecord->Signature = LOGREC_SIG;
    pLogRecord->ResourceManager = RMEndChkPt;
    pLogRecord->Transaction = ChkPtTransaction;    // transaction id associated with the chkpt
    pLogRecord->XsactionType = TTDontCare;
    pLogRecord->Flags = 0;
    GetSystemTimeAsFileTime(&pLogRecord->Timestamp);
    pLogRecord->NumPages = dwNumPages;
    pLogRecord->DataSize = sizeof(LOG_CHKPTINFO);
    
    CopyMemory(&pLogRecord->Data, (PBYTE)&ChkPtInfo, sizeof(LOG_CHKPTINFO));



    //flush the log file
    LogFlush(pLog,ChkPtLsn);

    ClRtlLogPrint(LOG_NOISE,
        "[LM] LogCheckPoint: EndChkpt written. EndChkPtLsn =0x%1!08lx! ChkPt Seq=%2!d! ChkPt FileName=%3!ls!\r\n",
        ChkPtLsn, ChkPtTransaction, ChkPtInfo.szFileName);

    //read the log header and get the old chk point sequence
    //if null
    pLogHeader = AlignAlloc(pLog->SectorSize);
    if (pLogHeader == NULL)
    {
        dwError = ERROR_NOT_ENOUGH_MEMORY;
        CL_LOGFAILURE(dwError);
        ClRtlLogPrint(LOG_UNUSUAL,
                       "[LM] LogCheckPoint: couldn't allocate memory for the header\r\n");
        goto FnExit;
    }


    (pLog->Overlapped).Offset = 0;
    (pLog->Overlapped).OffsetHigh = 0;

    if ((dwError = LogpRead(pLog, pLogHeader, pLog->SectorSize, &dwBytesRead))
        != ERROR_SUCCESS)
    {
        {
            ClRtlLogPrint(LOG_UNUSUAL,
                "[LM] LogCheckPoint: failed to read the header. Error=0x%1!08lx!\r\n",
                dwError);
            goto FnExit;
        }
    }

    if (dwBytesRead != pLog->SectorSize)
    {
        ClRtlLogPrint(LOG_UNUSUAL,
                "[LM] LogCheckPoint: failed to read the complete header\r\n");
        dwError = ERROR_CLUSTERLOG_CORRUPT;
        //SS: should we do an implicit reset here
        goto FnExit;

    }

    Lsn = pLogHeader->LastChkPtLsn;
    //if there was a previous chkpoint and in most cases
    //there should be one,except when the system just comes up
    ChkPtInfo.szFileName[0]= TEXT('\0');
    if (Lsn != NULL_LSN)
    {
        (pLog->Overlapped).Offset = LSNTOPAGE(Lsn) * pLog->SectorSize;
        (pLog->Overlapped).OffsetHigh = 0;

        //get the old check point file name
        dwBytesRead = sizeof(LOG_CHKPTINFO);
        if ((LogRead(pLog, Lsn, &Resource, &RmType, &Transaction, &TrType,
            &ChkPtInfo, &dwBytesRead)) == NULL_LSN)
        {
            ClRtlLogPrint(LOG_UNUSUAL,
                "[LM] LogCheckPoint: failed to read the chkpt lsn. Error=0x%1!08lx!\r\n",
                dwError);
            goto FnExit;
        }
        if (Resource != RMEndChkPt)
        {
        //SS: this should not happen
#if DBG        
            if (IsDebuggerPresent())
                DebugBreak();
#endif                
            ChkPtInfo.szFileName[0]= TEXT('\0');
            CL_LOGCLUSERROR1(LM_LOG_CORRUPT, pLog->FileName);
        }
    }
    //update the last chkpoint lsn in the header
    pLogHeader->LastChkPtLsn = ChkPtLsn;

    //write the header
    (pLog->Overlapped).Offset = 0;
    (pLog->Overlapped).OffsetHigh = 0;

    ClRtlLogPrint(LOG_NOISE,
        "[LM] LogpCheckpoint : Writing %1!u! bytes to disk at offset 0x%2!08lx!\r\n",
        pLog->SectorSize, pLog->Overlapped.Offset);

    if ((dwError = LogpWrite(pLog, pLogHeader, pLog->SectorSize, &dwBytesWritten))
        != ERROR_SUCCESS)
    {
        ClRtlLogPrint(LOG_UNUSUAL,
                "[LM] LogCheckPoint: failed to update header after checkpointing, Error=0x%1!08lx!\r\n",
                dwError);
        CL_LOGFAILURE(dwError);
        goto FnExit;
    }

    //flush the log file.

    FlushFileBuffers(pLog->FileHandle);

    //delete the old checkpoint file
    //the old checkpoint file may be the same as the current one, dont delete it, if so
    if (Lsn && (ChkPtInfo.szFileName[0]) &&
        (lstrcmpiW(szNewChkPtFile, ChkPtInfo.szFileName)))
        DeleteFile((LPCTSTR)(ChkPtInfo.szFileName));

    ClRtlLogPrint(LOG_NOISE,
        "[LM] LogCheckPoint Exit\r\n");


FnExit:
#if DBG    
    {
        DWORD dwOldProtect;
        DWORD Status;
        BOOL VPWorked;

        VPWorked = VirtualProtect(pLog->ActivePage, pLog->SectorSize, PAGE_READONLY, & dwOldProtect);
        Status = GetLastError();
        CL_ASSERT( VPWorked );
    }        
#endif        

    if (pLogHeader != NULL) {
        AlignFree(pLogHeader);
    }
    LeaveCriticalSection(&pLog->Lock);
    return(dwError);
}


/****
@func       LSN | LogReset| Resets the log file and takes a  new checkpoint.

@parm       IN HLOG | hLog | Supplies the identifier of the log.

@rdesc      ERROR_SUCCESS if successful.  Win32 status if something terrible happened.

@comm       The log manager creates a new log, takes a checkpoint and renames the old log file.

@xref       <f LogCheckPoint>
****/
DWORD
LogReset(
    IN HLOG LogFile
    )
{
    PLOG        pLog;
    DWORD       dwError;

    GETLOG(pLog, LogFile);
    CL_ASSERT(pLog->SectorSize == SECTOR_SIZE);

    ClRtlLogPrint(LOG_NOISE,
        "[LM] LogReset entry...\r\n");

    dwError = LogpReset (pLog, NULL);
    
    ClRtlLogPrint(LOG_NOISE,
        "[LM] LogReset exit, returning 0x%1!08lx!\r\n",
        dwError);
            
    return(dwError);
}


/****
@func       DWORD | LogGetLastChkPoint| This is a callback that is called on
            change of state on quorum resource.

@parm       HLOG | LogFile | A pointer to a DMLOGRECORD structure.
@parm       PVOID | szChkPtFileName | The name of the checkpoint file.
@parm       TRID | *pTransaction | The transaction id associated with the
            checkpoint.
@parm       LSN | *pChkPtLsn | The LSN of the start checkpoint record.

@rdesc      Returns a result code. ERROR_SUCCESS on success.  To find transactions,
            past this checkpoint, the application must scan from the LSN of the start
            checkpoint record.

@xref
****/
DWORD LogGetLastChkPoint(
    IN HLOG     LogFile,
    OUT LPWSTR   szChkPtFileName,
    OUT TRID    *pTransactionId,
    OUT LSN     *pChkPtLsn)
{
    PLOG_HEADER     pLogHeader=NULL;
    PLOG            pLog;
    DWORD           dwError = ERROR_SUCCESS;
    DWORD           dwBytesRead;
    RMID            Resource;
    RMTYPE          RmType;
    TRTYPE          TrType;
    LOG_CHKPTINFO   ChkPtInfo;
    DWORD           dwCheckSum = 0;
    DWORD           dwHeaderSum = 0;
    DWORD           dwLen;

    ClRtlLogPrint(LOG_NOISE,
        "[LM] LogGetLastChkPoint:: Entry\r\n");


    GETLOG(pLog, LogFile);
    CL_ASSERT(pLog->SectorSize == SECTOR_SIZE);


    EnterCriticalSection(&pLog->Lock);
    if (!szChkPtFileName || !pTransactionId || !pChkPtLsn)
    {
        dwError = ERROR_INVALID_PARAMETER;
        goto FnExit;
    }


    *pTransactionId = 0;
    *pChkPtLsn = NULL_LSN;

    pLogHeader = AlignAlloc(pLog->SectorSize);
    if (pLogHeader == NULL) {
        dwError = ERROR_NOT_ENOUGH_MEMORY;
        CL_LOGFAILURE(dwError);
        goto FnExit;
    }

    //read the header
    (pLog->Overlapped).Offset = 0;
    (pLog->Overlapped).OffsetHigh = 0;

    if (LogpRead(LogFile, pLogHeader, pLog->SectorSize, &dwBytesRead) != ERROR_SUCCESS)
    {
        dwError = ERROR_CLUSTERLOG_CORRUPT;
        goto FnExit;
    }

    if (dwBytesRead != pLog->SectorSize)
    {
        dwError = ERROR_CLUSTERLOG_CORRUPT;
        CL_LOGCLUSERROR1(LM_LOG_CORRUPT, pLog->FileName);
        goto FnExit;

    }
    //validate the header
    if (!ISVALIDHEADER((*pLogHeader)))
    {
        ClRtlLogPrint(LOG_UNUSUAL,
            "[LM] LogGetLastChkPoint::the file header is corrupt.\r\n");
        dwError = ERROR_CLUSTERLOG_CORRUPT;
        CL_LOGCLUSERROR1(LM_LOG_CORRUPT, pLog->FileName);
        goto FnExit;
    }

    //read the last Chkpoint end record
    if (pLogHeader->LastChkPtLsn != NULL_LSN)
    {
        dwBytesRead = sizeof(LOG_CHKPTINFO);
        if ((LogRead(LogFile , pLogHeader->LastChkPtLsn, &Resource, &RmType, 
            pTransactionId, &TrType, &ChkPtInfo, &dwBytesRead)) == NULL_LSN)
        {
            ClRtlLogPrint(LOG_UNUSUAL,
                "[LM] LogGetLastChkPoint::LogRead for chkpt lsn 0x%1!08lx! failed\r\n",
                pLogHeader->LastChkPtLsn);
            dwError = GetLastError();
            goto FnExit;
        }
        if (Resource != RMEndChkPt)
        {
        //SS: This should not happen
#if DBG        
            if (IsDebuggerPresent())
                DebugBreak();
#endif                
            dwError = ERROR_CLUSTERLOG_CORRUPT;
            CL_LOGFAILURE(dwError);
            CL_LOGCLUSERROR1(LM_LOG_CORRUPT, pLog->FileName);
            goto FnExit;
        }
        //
        //  Chittur Subbaraman (chitturs) - 1/28/99
        //
        //  Check if the checkpoint file itself got corrupted. But first
        //  make sure that a checksum was indeed recorded.
        //
        dwLen = lstrlenW( ChkPtInfo.szFileName );
        if ( ( dwLen + lstrlenW( CHKSUM_SIG ) + 2 <= LOG_MAX_FILENAME_LENGTH ) &&
             ( lstrcmpW( &ChkPtInfo.szFileName[dwLen+1], CHKSUM_SIG ) == 0 ) )
        {
            dwError = MapFileAndCheckSumW( ChkPtInfo.szFileName, &dwHeaderSum, &dwCheckSum );
            if ( ( dwError != CHECKSUM_SUCCESS ) ||
                 ( dwCheckSum != ChkPtInfo.dwCheckSum ) ||
                 ( dwCheckSum == 0 ) )
            {
                ClRtlLogPrint(LOG_UNUSUAL,
                    "[LM] LogGetLastChkPoint - MapFileAndCheckSumW returned error=%1!u!\r\n",
                    dwError);
                ClRtlLogPrint(LOG_UNUSUAL,
                    "[LM] LogGetLastChkPoint - Stored CheckSum=%1!u!, Retrieved CheckSum=%2!u!\r\n",
                    ChkPtInfo.dwCheckSum,
                    dwCheckSum);
                CL_LOGCLUSERROR1( LM_LOG_CHKPOINT_NOT_FOUND, ChkPtInfo.szFileName );
                dwError = ERROR_CLUSTERLOG_CORRUPT;
                goto FnExit;
            }
        }
   
        lstrcpyW(szChkPtFileName, ChkPtInfo.szFileName);
        *pChkPtLsn = ChkPtInfo.ChkPtBeginLsn;
        ClRtlLogPrint(LOG_NOISE,
                "[LM] LogGetLastChkPoint: ChkPt File %1!ls! ChkPtSeq=%2!d! ChkPtLsn=0x%3!08lx! Checksum=%4!d!\r\n",
                szChkPtFileName, *pTransactionId, *pChkPtLsn, dwCheckSum);
    }
    else
    {
        dwError = ERROR_CLUSTERLOG_CHKPOINT_NOT_FOUND;
        CL_LOGCLUSWARNING1(LM_LOG_CHKPOINT_NOT_FOUND, pLog->FileName);
    }

FnExit:
    if (pLogHeader) AlignFree(pLogHeader);
    LeaveCriticalSection(&pLog->Lock);

    ClRtlLogPrint(LOG_NOISE,
        "[LM] LogGetLastChkPoint exit, returning 0x%1!08lx!\r\n",
        dwError);

    return (dwError);
}