/*++ Copyright (c) 1990 Microsoft Corporation Module Name: QueryLog.c Abstract: This module implements the user routines which query for log records in a log file. Author: Brian Andrew [BrianAn] 20-June-1991 Revision History: --*/ #include "lfsprocs.h" // // The debug trace level // #define Dbg (DEBUG_TRACE_QUERY) #undef MODULE_POOL_TAG #define MODULE_POOL_TAG ('QsfL') VOID LfsFindLogRecord ( IN PLFCB Lfcb, IN OUT PLEB Leb, IN LSN Lsn, OUT PLFS_RECORD_TYPE RecordType, OUT TRANSACTION_ID *TransactionId, OUT PLSN UndoNextLsn, OUT PLSN PreviousLsn, OUT PULONG BufferLength, OUT PVOID *Buffer ); BOOLEAN LfsFindClientNextLsn ( IN PLFCB Lfcb, IN PLEB Leb, OUT PLSN Lsn ); BOOLEAN LfsSearchForwardByClient ( IN PLFCB Lfcb, IN OUT PLEB Leb, OUT PLSN Lsn ); #ifdef ALLOC_PRAGMA #pragma alloc_text(PAGE, LfsFindClientNextLsn) #pragma alloc_text(PAGE, LfsFindLogRecord) #pragma alloc_text(PAGE, LfsQueryLastLsn) #pragma alloc_text(PAGE, LfsReadLogRecord) #pragma alloc_text(PAGE, LfsReadNextLogRecord) #pragma alloc_text(PAGE, LfsSearchForwardByClient) #pragma alloc_text(PAGE, LfsTerminateLogQuery) #endif VOID LfsReadLogRecord ( IN LFS_LOG_HANDLE LogHandle, IN LSN FirstLsn, IN LFS_CONTEXT_MODE ContextMode, OUT PLFS_LOG_CONTEXT Context, OUT PLFS_RECORD_TYPE RecordType, OUT TRANSACTION_ID *TransactionId, OUT PLSN UndoNextLsn, OUT PLSN PreviousLsn, OUT PULONG BufferLength, OUT PVOID *Buffer ) /*++ Routine Description: This routine initiates the query operation. It returns the log record in question and a context structure used by the Lfs to return related log records. The caller specifies what mode of query to use. He may walk backwards through the file by Undo records or all records for this client linked through the previous Lsn fields. He may also look forwards through the file for all records for the issuing client. Arguments: LogHandle - Pointer to private Lfs structure used to identify this client. FirstLsn - Starting record for this query operation. ContextMode - Method of query. Context - Supplies the address to store a pointer to the Lfs created context structure. RecordType - Supplies the address to store the record type of this log record. TransactionId - Supplies the address to store the transaction Id of this log record. UndoNextLsn - Supplies the address to store the Undo Next Lsn for this log record. PreviousLsn - Supplies the address to store the Previous Lsn for this log record. BufferLength - This is the length of the log data. Buffer - This is a pointer to the start of the log data. Return Value: None --*/ { PLFS_CLIENT_RECORD ClientRecord; PLCH Lch; PLFCB Lfcb; PLEB Leb = NULL; PAGED_CODE(); DebugTrace( +1, Dbg, "LfsReadLogRecord: Entered\n", 0 ); DebugTrace( 0, Dbg, "Log Handle -> %08lx\n", LogHandle ); DebugTrace( 0, Dbg, "First Lsn (Low) -> %08lx\n", FirstLsn.LowPart ); DebugTrace( 0, Dbg, "First Lsn (High) -> %08lx\n", FirstLsn.HighPart ); DebugTrace( 0, Dbg, "Context Mode -> %08lx\n", ContextMode ); Lch = (PLCH) LogHandle; // // Check that the context mode is valid. // switch (ContextMode) { case LfsContextUndoNext : case LfsContextPrevious : case LfsContextForward : break; default: DebugTrace( 0, Dbg, "Invalid context mode -> %08x\n", ContextMode ); ExRaiseStatus( STATUS_INVALID_PARAMETER ); } // // Check that the structure is a valid log handle structure. // LfsValidateLch( Lch ); // // Use a try-except to catch errors. // // // Use a try-finally to facilitate cleanup. // try { // // Acquire the log file control block for this log file. // LfsAcquireLchExclusive( Lch ); Lfcb = Lch->Lfcb; // // If the Log file has been closed then refuse access. // if (Lfcb == NULL) { ExRaiseStatus( STATUS_ACCESS_DENIED ); } // // Check that the client Id is valid. // LfsValidateClientId( Lfcb, Lch ); // // Check that the given Lsn is in the legal range for this client. // ClientRecord = Add2Ptr( Lfcb->ClientArray, Lch->ClientArrayByteOffset, PLFS_CLIENT_RECORD ); if (!LfsVerifyClientLsnInRange( Lfcb, ClientRecord, FirstLsn )) { ExRaiseStatus( STATUS_DISK_CORRUPT_ERROR ); } // // We can give up the Lfcb as we know the Lsn is within the file. // LfsReleaseLch( Lch ); // // Allocate and initialize an enumeration structure. // LfsAllocateLeb( Lfcb, &Leb ); LfsInitializeLeb( Leb, Lch->ClientId, ContextMode ); // // Find the log record indicated by the given Lsn. // LfsFindLogRecord( Lfcb, Leb, FirstLsn, RecordType, TransactionId, UndoNextLsn, PreviousLsn, BufferLength, Buffer ); // // Update the client's arguments. // *Context = Leb; Leb = NULL; } finally { DebugUnwind( LfsReadLogRecord ); // // Release the log file control block if held. // LfsReleaseLch( Lch ); // // Deallocate the enumeration block if an error occurred. // if (Leb != NULL) { LfsDeallocateLeb( Lfcb, Leb ); } DebugTrace( 0, Dbg, "Context -> %08lx\n", *Context ); DebugTrace( 0, Dbg, "Buffer Length -> %08lx\n", *BufferLength ); DebugTrace( 0, Dbg, "Buffer -> %08lx\n", *Buffer ); DebugTrace( -1, Dbg, "LfsReadLogRecord: Exit\n", 0 ); } return; } BOOLEAN LfsReadNextLogRecord ( IN LFS_LOG_HANDLE LogHandle, IN OUT LFS_LOG_CONTEXT Context, OUT PLFS_RECORD_TYPE RecordType, OUT TRANSACTION_ID *TransactionId, OUT PLSN UndoNextLsn, OUT PLSN PreviousLsn, OUT PLSN Lsn, OUT PULONG BufferLength, OUT PVOID *Buffer ) /*++ Routine Description: This routine is called to continue a query operation. The Lfs uses private information stored in the enumeration structure to determine the next log record to return to the caller. Arguments: LogHandle - Pointer to private Lfs structure used to identify this client. Context - Supplies the address to store a pointer to the Lfs created enumeration structure. Lsn - Lsn for this log record. RecordType - Supplies the address to store the record type of this log record. TransactionId - Supplies the address to store the transaction Id of this log record. UndoNextLsn - Supplies the address to store the Undo Next Lsn for this log record. PreviousLsn - Supplies the address to store the Previous Lsn for this log record. BufferLength - This is the length of the log data. Buffer - This is a pointer to the start of the log data. Return Value: None --*/ { PLCH Lch; PLFCB Lfcb; PLEB Leb; BOOLEAN FoundNextLsn; BOOLEAN UnwindRememberLebFields; PBCB UnwindRecordHeaderBcb; PLFS_RECORD_HEADER UnwindRecordHeader; PVOID UnwindCurrentLogRecord; BOOLEAN UnwindAuxilaryBuffer; PAGED_CODE(); DebugTrace( +1, Dbg, "LfsReadNextLogRecord: Entered\n", 0 ); DebugTrace( 0, Dbg, "Log Handle -> %08lx\n", LogHandle ); DebugTrace( 0, Dbg, "Context -> %08lx\n", Context ); FoundNextLsn = FALSE; UnwindRememberLebFields = FALSE; Lch = (PLCH) LogHandle; Leb = (PLEB) Context; // // Check that the structure is a valid log handle structure. // LfsValidateLch( Lch ); // // Use a try-finally to facilitate cleanup. // try { // // Acquire the log file control block for this log file. // LfsAcquireLchExclusive( Lch ); Lfcb = Lch->Lfcb; // // If the Log file has been closed then refuse access. // if (Lfcb == NULL) { ExRaiseStatus( STATUS_ACCESS_DENIED ); } // // Check that the client Id is valid. // LfsValidateClientId( Lfcb, Lch ); // // Check that the enumeration structure is valid. // LfsValidateLeb( Leb, Lch ); // // Remember any enumeration fields to be overwritten. // UnwindRememberLebFields = TRUE; UnwindRecordHeaderBcb = Leb->RecordHeaderBcb; Leb->RecordHeaderBcb = NULL; UnwindRecordHeader = Leb->RecordHeader; UnwindCurrentLogRecord = Leb->CurrentLogRecord; UnwindAuxilaryBuffer = Leb->AuxilaryBuffer; Leb->AuxilaryBuffer = FALSE; // // Find the next Lsn number based on the current Lsn number in // the enumeration block. // if (LfsFindClientNextLsn( Lfcb, Leb, Lsn )) { // // We can give up the Lfcb as we know the Lsn is within the file. // LfsReleaseLfcb( Lfcb ); // // Cleanup the enumeration block so we can do the next search. // Leb->CurrentLogRecord = NULL; Leb->AuxilaryBuffer = FALSE; // // Perform the work of getting the log record. // LfsFindLogRecord( Lfcb, Leb, *Lsn, RecordType, TransactionId, UndoNextLsn, PreviousLsn, BufferLength, Buffer ); FoundNextLsn = TRUE; } } finally { DebugUnwind( LfsReadNextLogRecord ); // // If we exited due to an error, we have to restore the enumeration // block. // if (UnwindRememberLebFields) { if (AbnormalTermination()) { // // If the record header in the enumeration block is not // the same as we started with. Then we unpin that // data. // if (Leb->RecordHeaderBcb != NULL) { CcUnpinData( Leb->RecordHeaderBcb ); } if (Leb->CurrentLogRecord != NULL && Leb->AuxilaryBuffer == TRUE) { LfsFreeSpanningBuffer( Leb->CurrentLogRecord ); } Leb->RecordHeaderBcb = UnwindRecordHeaderBcb; Leb->RecordHeader = UnwindRecordHeader; Leb->CurrentLogRecord = UnwindCurrentLogRecord; Leb->AuxilaryBuffer = UnwindAuxilaryBuffer; // // Otherwise, if we have successfully found the next Lsn, // we free up any resources being held from the previous search. // } else if (FoundNextLsn ) { if (UnwindRecordHeaderBcb != NULL) { CcUnpinData( UnwindRecordHeaderBcb ); } if (UnwindCurrentLogRecord != NULL && UnwindAuxilaryBuffer == TRUE) { LfsFreeSpanningBuffer( UnwindCurrentLogRecord ); } // // Restore the Bcb and auxilary buffer field for the final // cleanup. // } else { if (UnwindRecordHeaderBcb != NULL) { if (Leb->RecordHeaderBcb != NULL) { CcUnpinData( UnwindRecordHeaderBcb ); } else { Leb->RecordHeaderBcb = UnwindRecordHeaderBcb; } } if (UnwindAuxilaryBuffer) { if (Leb->CurrentLogRecord == UnwindCurrentLogRecord) { Leb->AuxilaryBuffer = TRUE; } else { LfsFreeSpanningBuffer( UnwindCurrentLogRecord ); } } } } // // Release the log file control block if held. // LfsReleaseLch( Lch ); DebugTrace( 0, Dbg, "Lsn (Low) -> %08lx\n", Lsn->LowPart ); DebugTrace( 0, Dbg, "Lsn (High) -> %08lx\n", Lsn->HighPart ); DebugTrace( 0, Dbg, "Buffer Length -> %08lx\n", *BufferLength ); DebugTrace( 0, Dbg, "Buffer -> %08lx\n", *Buffer ); DebugTrace( -1, Dbg, "LfsReadNextLogRecord: Exit\n", 0 ); } return FoundNextLsn; } VOID LfsTerminateLogQuery ( IN LFS_LOG_HANDLE LogHandle, IN LFS_LOG_CONTEXT Context ) /*++ Routine Description: This routine is called when a client has completed his query operation and wishes to deallocate any resources acquired by the Lfs to perform the log file query. Arguments: LogHandle - Pointer to private Lfs structure used to identify this client. Context - Supplies the address to store a pointer to the Lfs created enumeration structure. Return Value: None --*/ { PLCH Lch; PLEB Leb; PLFCB Lfcb; PAGED_CODE(); DebugTrace( +1, Dbg, "LfsTerminateLogQuery: Entered\n", 0 ); DebugTrace( 0, Dbg, "Log Handle -> %08lx\n", LogHandle ); DebugTrace( 0, Dbg, "Context -> %08lx\n", Context ); Lch = (PLCH) LogHandle; Leb = (PLEB) Context; // // Check that the structure is a valid log handle structure. // LfsValidateLch( Lch ); // // Use a try-finally to facilitate cleanup. // try { // // Acquire the log file control block for this log file. // LfsAcquireLchExclusive( Lch ); Lfcb = Lch->Lfcb; // // If the Log file has been closed then refuse access. // if (Lfcb == NULL) { try_return( NOTHING ); } // // Check that the client Id is valid. // LfsValidateClientId( Lfcb, Lch ); // // Check that the enumeration structure is valid. // LfsValidateLeb( Leb, Lch ); // // Deallocate the enumeration block. // LfsDeallocateLeb( Lfcb, Leb ); try_exit: NOTHING; } finally { DebugUnwind( LfsTerminateLogQuery ); // // Release the Lfcb if acquired. // LfsReleaseLch( Lch ); DebugTrace( -1, Dbg, "LfsTerminateLogQuery: Exit\n", 0 ); } return; } LSN LfsQueryLastLsn ( IN LFS_LOG_HANDLE LogHandle ) /*++ Routine Description: This routine will return the most recent Lsn for this log record. Arguments: LogHandle - Pointer to private Lfs structure used to identify this client. Return Value: LSN - This is the last Lsn assigned in this log file. --*/ { PLCH Lch; PLFCB Lfcb; LSN LastLsn; PAGED_CODE(); DebugTrace( +1, Dbg, "LfsQueryLastLsn: Entered\n", 0 ); DebugTrace( 0, Dbg, "Log Handle -> %08lx\n", LogHandle ); Lch = (PLCH) LogHandle; // // Check that the structure is a valid log handle structure. // LfsValidateLch( Lch ); // // Use a try-finally to facilitate cleanup. // try { // // Acquire the log file control block for this log file. // LfsAcquireLchExclusive( Lch ); Lfcb = Lch->Lfcb; // // If the Log file has been closed then refuse access. // if (Lfcb == NULL) { ExRaiseStatus( STATUS_ACCESS_DENIED ); } // // Check that the client Id is valid. // LfsValidateClientId( Lfcb, Lch ); // // Copy the last Lsn out of the Lfcb. If the last Lsn is // does not correspond to a log record, we will return the // zero Lsn. // if (FlagOn( Lfcb->Flags, LFCB_NO_LAST_LSN )) { LastLsn = LfsZeroLsn; } else { LastLsn = Lfcb->RestartArea->CurrentLsn; } } finally { DebugUnwind( LfsQueryLastLsn ); // // Release the Lfcb if acquired. // LfsReleaseLch( Lch ); DebugTrace( 0, Dbg, "Last Lsn (Low) -> %08lx\n", LastLsn.LowPart ); DebugTrace( 0, Dbg, "Last Lsn (High) -> %08lx\n", LastLsn.HighPart ); DebugTrace( -1, Dbg, "LfsQueryLastLsn: Exit\n", 0 ); } return LastLsn; } // // Local support routine. // VOID LfsFindLogRecord ( IN PLFCB Lfcb, IN OUT PLEB Leb, IN LSN Lsn, OUT PLFS_RECORD_TYPE RecordType, OUT TRANSACTION_ID *TransactionId, OUT PLSN UndoNextLsn, OUT PLSN PreviousLsn, OUT PULONG BufferLength, OUT PVOID *Buffer ) /*++ Routine Description: This routine is called recover a log record for a client. Arguments: Lfcb - Log file control block for this file. Leb - Pointer to the enumeration block to update. Lsn - This is the Lsn for the log record. RecordType - Supplies the address to store the record type of this log record. TransactionId - Supplies the address to store the transaction Id of this log record. UndoNextLsn - Supplies the address to store the Undo Next Lsn for this log record. PreviousLsn - Supplies the address to store the Previous Lsn for this log record. BufferLength - Pointer to address to store the length in bytes of the log record. Buffer - Pointer to store the address where the log record data begins. Return Value: None --*/ { PCHAR NewBuffer; BOOLEAN UsaError; LONGLONG LogRecordLength; ULONG PageOffset; PAGED_CODE(); DebugTrace( +1, Dbg, "LfsFindLogRecord: Entered\n", 0 ); DebugTrace( 0, Dbg, "Lfcb -> %08lx\n", Lfcb ); DebugTrace( 0, Dbg, "Enumeration Block -> %08lx\n", Leb ); DebugTrace( 0, Dbg, "Lsn (Low) -> %08lx\n", Lsn.LowPart ); NewBuffer = NULL; // // Use a try-finally to facilitate cleanup. // try { // // Map the record header for this Lsn if we haven't already. // if (Leb->RecordHeader == NULL) { LfsPinOrMapLogRecordHeader( Lfcb, Lsn, FALSE, FALSE, &UsaError, &Leb->RecordHeader, &Leb->RecordHeaderBcb ); } // // We now have the log record desired. If the Lsn in the // log record doesn't match the desired Lsn then the disk is // corrupt. // if ( Lsn.QuadPart != Leb->RecordHeader->ThisLsn.QuadPart ) { //**** xxNeq( Lsn, Leb->RecordHeader->ThisLsn ) ExRaiseStatus( STATUS_DISK_CORRUPT_ERROR ); } // // Check that the length field isn't greater than the total available space // in the log file. // LogRecordLength = Leb->RecordHeader->ClientDataLength + Lfcb->RecordHeaderLength; //**** xxFromUlong( Leb->RecordHeader->ClientDataLength + Lfcb->RecordHeaderLength ); if ( LogRecordLength >= Lfcb->TotalAvailable ) { //**** xxGeq( LogRecordLength, Lfcb->TotalAvailable ) ExRaiseStatus( STATUS_DISK_CORRUPT_ERROR ); } // // If the entire log record is on this log page, put a pointer to // the log record in the enumeration block. // if (!FlagOn( Leb->RecordHeader->Flags, LOG_RECORD_MULTI_PAGE )) { // // If client size indicates that we have to go beyond the end of the current // page, we raise an error. // PageOffset = LfsLsnToPageOffset( Lfcb, Lsn ); if ((PageOffset + Leb->RecordHeader->ClientDataLength + Lfcb->RecordHeaderLength) > (ULONG)Lfcb->LogPageSize) { ExRaiseStatus( STATUS_DISK_CORRUPT_ERROR ); } Leb->CurrentLogRecord = Add2Ptr( Leb->RecordHeader, LFS_RECORD_HEADER_SIZE, PVOID ); Leb->AuxilaryBuffer = FALSE; // // Else we copy the data and remember that we allocated a buffer. // } else { NewBuffer = LfsAllocateSpanningBuffer( Lfcb, Leb->RecordHeader->ClientDataLength ); // // Copy the data into the buffer returned. // LfsCopyReadLogRecord( Lfcb, Leb->RecordHeader, NewBuffer ); Leb->CurrentLogRecord = NewBuffer; Leb->AuxilaryBuffer = TRUE; NewBuffer = NULL; } // // We need to update the caller's parameters and the enumeration block. // *RecordType = Leb->RecordHeader->RecordType; *TransactionId = Leb->RecordHeader->TransactionId; *UndoNextLsn = Leb->RecordHeader->ClientUndoNextLsn; *PreviousLsn = Leb->RecordHeader->ClientPreviousLsn; *Buffer = Leb->CurrentLogRecord; *BufferLength = Leb->RecordHeader->ClientDataLength; } finally { DebugUnwind( LfsFindLogRecord ); // // If an error occurred we unpin the record header and the log // We also free the buffer if allocated by us. // if (NewBuffer != NULL) { LfsFreeSpanningBuffer( NewBuffer ); } DebugTrace( 0, Dbg, "Buffer Length -> %08lx\n", *BufferLength ); DebugTrace( 0, Dbg, "Buffer -> %08lx\n", *Buffer ); DebugTrace( -1, Dbg, "LfsFindLogRecord: Exit\n", 0 ); } return; } // // Local support routine. // BOOLEAN LfsFindClientNextLsn ( IN PLFCB Lfcb, IN PLEB Leb, OUT PLSN Lsn ) /*++ Routine Description: This routine will attempt to find the next Lsn to return to a client based on the context mode. Arguments: Lfcb - File control block for this log file. Leb - Pointer to the enumeration block for this query operation. Lsn - Pointer to store the Lsn found (if any) Return Value: BOOLEAN - TRUE if an Lsn is found, FALSE otherwise. --*/ { LSN NextLsn; BOOLEAN NextLsnFound; PLFS_CLIENT_RECORD ClientRecord; PAGED_CODE(); DebugTrace( +1, Dbg, "LfsFindClientNextLsn: Entered\n", 0 ); DebugTrace( 0, Dbg, "Leb -> %08lx\n", Leb ); ClientRecord = Lfcb->ClientArray + Leb->ClientId.ClientIndex; // // The enumeration block has the last Lsn returned. If the user wanted // one of the Lsn's in that log header then our job is simple. // switch (Leb->ContextMode) { case LfsContextUndoNext: case LfsContextPrevious: NextLsn = (Leb->ContextMode == LfsContextUndoNext ? Leb->RecordHeader->ClientUndoNextLsn : Leb->RecordHeader->ClientPreviousLsn); if ( NextLsn.QuadPart == 0 ) { //**** xxEqlZero( NextLsn ) NextLsnFound = FALSE; } else if (LfsVerifyClientLsnInRange( Lfcb, ClientRecord, NextLsn )) { BOOLEAN UsaError; LfsPinOrMapLogRecordHeader( Lfcb, NextLsn, FALSE, FALSE, &UsaError, &Leb->RecordHeader, &Leb->RecordHeaderBcb ); NextLsnFound = TRUE; } else { NextLsnFound = FALSE; } break; case LfsContextForward: // // We search forward for the next log record for this client. // NextLsnFound = LfsSearchForwardByClient( Lfcb, Leb, &NextLsn ); break; default: NextLsnFound = FALSE; break; } if (NextLsnFound) { *Lsn = NextLsn; } DebugTrace( 0, Dbg, "NextLsn (Low) -> %08lx\n", NextLsn.LowPart ); DebugTrace( 0, Dbg, "NextLsn (High) -> %08lx\n", NextLsn.HighPart ); DebugTrace( -1, Dbg, "LfsFindClientNextLsn: Exit -> %08x\n", NextLsnFound ); return NextLsnFound; } // // Local support routine. // BOOLEAN LfsSearchForwardByClient ( IN PLFCB Lfcb, IN OUT PLEB Leb, OUT PLSN Lsn ) /*++ Routine Description: This routine will attempt to find the next Lsn for this client by searching forward in the file, looking for a match. Arguments: Lfcb - Pointer to the file control block for this log file. Leb - Pointer to the enumeration block for this query operation. Lsn - Points to the location to store the next Lsn if found. Return Value: BOOLEAN - TRUE if another Lsn for this client is found. FALSE otherwise. --*/ { PLFS_RECORD_HEADER CurrentRecordHeader; PBCB CurrentBcb; BOOLEAN FoundNextLsn; LSN CurrentLsn; PAGED_CODE(); DebugTrace( +1, Dbg, "LfsSearchForwardByClient: Entered\n", 0 ); DebugTrace( 0, Dbg, "Leb -> %08lx\n", Leb ); // // The log record header is in the log enumeration // block. We set the current Bcb to NULL so that we don't // unpin the log record in the enumeration block until we're sure // of success. // CurrentRecordHeader = Leb->RecordHeader; CurrentBcb = NULL; // // We use a try-finally to facilitate cleanup. // try { // // We assume we won't find another Lsn. // FoundNextLsn = FALSE; // // Loop as long as another Lsn can be found. // while (LfsFindNextLsn( Lfcb, CurrentRecordHeader, &CurrentLsn )) { BOOLEAN UsaError; // // Unpin the previous log record header. // if (CurrentBcb != NULL) { CcUnpinData( CurrentBcb ); CurrentBcb = NULL; } // // Pin the log record header for this Lsn. // LfsPinOrMapLogRecordHeader( Lfcb, CurrentLsn, FALSE, FALSE, &UsaError, &CurrentRecordHeader, &CurrentBcb ); // // If the client values match, then we update the // enumeration block and exit. // if (LfsClientIdMatch( &CurrentRecordHeader->ClientId, &Leb->ClientId ) && CurrentRecordHeader->RecordType == LfsClientRecord) { // // We remember this one. // Leb->RecordHeader = CurrentRecordHeader; Leb->RecordHeaderBcb = CurrentBcb; CurrentBcb = NULL; FoundNextLsn = TRUE; *Lsn = CurrentLsn; break; } } } finally { DebugUnwind( LfsSearchForwardByClient ); // // Unpin any log record headers still pinned for no reason. // if (CurrentBcb != NULL) { CcUnpinData( CurrentBcb ); } DebugTrace( 0, Dbg, "NextLsn (Low) -> %08lx\n", Lsn->LowPart ); DebugTrace( 0, Dbg, "NextLsn (High) -> %08lx\n", Lsn->HighPart ); DebugTrace( -1, Dbg, "LfsSearchForwardByClient: Exit -> %08x\n", FoundNextLsn ); } return FoundNextLsn; }