/*++

Copyright (c) 1990  Microsoft Corporation

Module Name:

    LbcbSup.c

Abstract:

    This module provides support for manipulating log buffer control blocks.

Author:

    Brian Andrew    [BrianAn]   20-June-1991

Revision History:

--*/

#include "lfsprocs.h"

//
//  The debug trace level
//

#define Dbg                              (DEBUG_TRACE_LBCB_SUP)

#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE, LfsFlushLbcb)
#pragma alloc_text(PAGE, LfsFlushToLsnPriv)
#pragma alloc_text(PAGE, LfsGetLbcb)
#endif


VOID
LfsFlushLbcb (
    IN PLFCB Lfcb,
    IN PLBCB Lbcb
    )

/*++

Routine Description:

    This routine is called to make sure the data within an Lbcb makes it out
    to disk.  The Lbcb must either already be in the workque or it must be
    a restart Lbcb.

Arguments:

    Lfcb - This is the file control block for the log file.

    Lbcb - This is the Lbcb to flush.

Return Value:

    None.

--*/

{
    LSN LastLsn;
    PLSN FlushedLsn;

    PAGED_CODE();

    DebugTrace( +1, Dbg, "LfsFlushLbcb:  Entered\n", 0 );
    DebugTrace(  0, Dbg, "Lfcb      -> %08lx\n", Lfcb );
    DebugTrace(  0, Dbg, "Lbcb      -> %08lx\n", Lbcb );

    LastLsn = Lbcb->LastEndLsn;

    //
    //  If this is a restart area we use the restart counter in the
    //  Lfcb.  Otherwise we can use the LastFlushedLsn value in the
    //  Lfcb.  This way we can determine that the Lbcb that interests
    //  us has made it out to disk.
    //

    if (LfsLbcbIsRestart( Lbcb )) {

        FlushedLsn = &Lfcb->LastFlushedRestartLsn;

    } else {

        FlushedLsn = &Lfcb->LastFlushedLsn;
    }

    //
    //  We loop here until the desired Lsn has made it to disk.
    //  If we are able to do the I/O, we will perform it.
    //

    do {

        //
        //
        //  If we can do the Io, call down to flush the Lfcb.
        //

        if (Lfcb->LfsIoState == LfsNoIoInProgress) {

            LfsFlushLfcb( Lfcb, Lbcb );

            break;
        }

        //
        //  Otherwise we release the Lfcb and immediately wait on the event.
        //

        Lfcb->Waiters += 1;

        LfsReleaseLfcb( Lfcb );

        KeWaitForSingleObject( &Lfcb->Sync->Event,
                               Executive,
                               KernelMode,
                               FALSE,
                               NULL );

        LfsAcquireLfcb( Lfcb );
        Lfcb->Waiters -= 1;

    } while ( LastLsn.QuadPart > FlushedLsn->QuadPart );

    DebugTrace( -1, Dbg, "LfsFlushLbcb:  Exit\n", 0 );
    return;
}


VOID
LfsFlushToLsnPriv (
    IN PLFCB Lfcb,
    IN LSN Lsn
    )

/*++

Routine Description:

    This routine is the worker routine which performs the work of flushing
    a particular Lsn to disk.  This routine is always called with the
    Lfcb acquired.  This routines makes no guarantee about whether the Lfcb
    is acquired on exit.

Arguments:

    Lfcb - This is the file control block for the log file.

    Lsn - This is the Lsn to flush to disk.

Return Value:

    None.

--*/

{
    BOOLEAN UseLastRecordLbcb = FALSE;
    PLBCB LastRecordLbcb = NULL;

    PAGED_CODE();

    DebugTrace( +1, Dbg, "LfsFlushToLsnPriv:  Entered\n", 0 );
    DebugTrace(  0, Dbg, "Lfcb          -> %08lx\n", Lfcb );
    DebugTrace(  0, Dbg, "Lsn (Low)     -> %08lx\n", Lsn.LowPart );
    DebugTrace(  0, Dbg, "Lsn (High)    -> %08lx\n", Lsn.HighPart );

    //
    //  We check if the Lsn is in the valid range.  Raising an
    //  exception if not.
    //

    if (Lsn.QuadPart > Lfcb->RestartArea->CurrentLsn.QuadPart) {

        UseLastRecordLbcb = TRUE;
    }

    //
    //  If the Lsn has already been flushed we are done.
    //  Otherwise we need to look through the workqueues and the
    //  active queue.
    //

    if (Lsn.QuadPart > Lfcb->LastFlushedLsn.QuadPart) {

        PLIST_ENTRY ThisEntry;
        PLBCB ThisLbcb;

        //
        //  Check the workqueue first.  We are looking for the last
        //  buffer block of a log page block which contains this
        //  Lsn.
        //

        ThisEntry = Lfcb->LbcbWorkque.Flink;

        //
        //  We keep looping.
        //

        while (TRUE) {

            ThisLbcb = CONTAINING_RECORD( ThisEntry,
                                          LBCB,
                                          WorkqueLinks );

            //
            //  We pass over any restart areas.  We also skip any
            //  Lbcb's which do not contain the end of a log record.
            //

            if (!LfsLbcbIsRestart( ThisLbcb )
                && FlagOn( ThisLbcb->Flags, LOG_PAGE_LOG_RECORD_END )) {

                LastRecordLbcb = ThisLbcb;

                //
                //  If the last complete Lsn in this Lbcb is greater or equal
                //  to the desired Lsn, we exit the loop.
                //

                if (ThisLbcb->LastEndLsn.QuadPart >= Lsn.QuadPart) {

                    break;
                }
            }

            //
            //  Otherwise move to the next Lbcb.
            //

            ThisEntry = ThisEntry->Flink;

            //
            //  If we have reached the end of the list then break out.  We
            //  were given an Lsn which is larger than any flushed Lsn so
            //  we will just flush to the end of the log file.
            //

            if (ThisEntry == &Lfcb->LbcbWorkque) {

                if (UseLastRecordLbcb) {

                    ThisLbcb = LastRecordLbcb;
                }

                break;
            }
        }

        if (ThisLbcb != NULL) {

            //
            //  If we are not supporting a packed log file and this Lbcb is from
            //  the active queue, we need to check that losing the tail of the
            //  will not swallow up any of our reserved space.
            //

            if (!FlagOn( Lfcb->Flags, LFCB_PACK_LOG )
                && FlagOn( ThisLbcb->LbcbFlags, LBCB_ON_ACTIVE_QUEUE )) {

                LONGLONG CurrentAvail;
                LONGLONG UnusedBytes;

                //
                //  Find the unused bytes.
                //

                UnusedBytes = 0;

                LfsCurrentAvailSpace( Lfcb,
                                      &CurrentAvail,
                                      (PULONG)&UnusedBytes );

                CurrentAvail = CurrentAvail - Lfcb->TotalUndoCommitment;

                if (UnusedBytes > CurrentAvail) {

                    DebugTrace( -1, Dbg, "Have to preserve these bytes for possible aborts\n", 0 );

                    ExRaiseStatus( STATUS_LOG_FILE_FULL );
                }

                //
                //  We want to make sure we don't write any more data into this
                //  page.  Remove this from the active queue.
                //

                RemoveEntryList( &ThisLbcb->ActiveLinks );
                ClearFlag( ThisLbcb->LbcbFlags, LBCB_ON_ACTIVE_QUEUE );
            }

            //
            //  We now have the Lbcb we want to flush to disk.
            //

            LfsFlushLbcb( Lfcb, ThisLbcb );
        }
    }

    DebugTrace( -1, Dbg, "LfsFlushToLsnPriv:  Exit\n", 0 );

    return;
}


PLBCB
LfsGetLbcb (
    IN PLFCB Lfcb
    )

/*++

Routine Description:

    This routine is called to add a Lbcb to the active queue.

Arguments:

    Lfcb - This is the file control block for the log file.

Return Value:

    PLBCB - Pointer to the Lbcb allocated.

--*/

{
    PLBCB Lbcb = NULL;
    PVOID PageHeader;
    PBCB PageHeaderBcb = NULL;

    BOOLEAN WrappedOrUsaError;

    PAGED_CODE();

    DebugTrace( +1, Dbg, "LfsGetLbcb:  Entered\n", 0 );
    DebugTrace(  0, Dbg, "Lfcb      -> %08lx\n", Lfcb );

    //
    //  Use a try-finally to facilitate cleanup.
    //

    try {

        //
        //  Pin the desired record page.
        //

        LfsPreparePinWriteData( Lfcb,
                                Lfcb->NextLogPage,
                                (ULONG)Lfcb->LogPageSize,
                                FlagOn( Lfcb->Flags, LFCB_REUSE_TAIL ),
                                &PageHeader,
                                &PageHeaderBcb );

#ifdef LFS_CLUSTER_CHECK
        //
        //  Check the page to see if there is already data on this page with the current sequence
        //  number.  Useful to track cases where ntfs didn't find the correct end of the log or
        //  where the cluster service has the volume mounted twice.
        //

        if (LfsTestCheckLbcb &&
            *((PULONG) PageHeader) == LFS_SIGNATURE_RECORD_PAGE_ULONG) {

            LSN LastLsn = ((PLFS_RECORD_PAGE_HEADER) PageHeader)->Copy.LastLsn;

            //
            //  This is not an exhaustive test but should be sufficient to catch the typical case.
            //

            ASSERT( FlagOn( Lfcb->Flags, LFCB_NO_LAST_LSN | LFCB_REUSE_TAIL ) ||
                    (LfsLsnToSeqNumber( Lfcb, LastLsn ) < (ULONGLONG) Lfcb->SeqNumber) ||
                    (Lfcb->NextLogPage == Lfcb->FirstLogPage) );
        }
#endif

        //
        //  Put our signature into the page so we won't fail if we
        //  see a previous 'BAAD' signature.
        //

        *((PULONG) PageHeader) = LFS_SIGNATURE_RECORD_PAGE_ULONG;

        //
        //  Now allocate an Lbcb.
        //

        LfsAllocateLbcb( Lfcb, &Lbcb );

        //
        //  If we are at the beginning of the file we test that the
        //  sequence number won't wrap to 0.
        //

        if (!FlagOn( Lfcb->Flags, LFCB_NO_LAST_LSN | LFCB_REUSE_TAIL )
            && ( Lfcb->NextLogPage == Lfcb->FirstLogPage )) {

            Lfcb->SeqNumber = Lfcb->SeqNumber + 1;

            //
            //  If the sequence number is going from 0 to 1, then
            //  this is the first time the log file has wrapped.  We want
            //  to remember this because it means that we can now do
            //  large spiral writes.
            //

            if (Int64ShllMod32( Lfcb->SeqNumber, Lfcb->FileDataBits ) == 0) {

                DebugTrace( 0, Dbg, "Log sequence number about to wrap:  Lfcb -> %08lx\n", Lfcb );
                KeBugCheckEx( FILE_SYSTEM, 4, 0, 0, 0 );
            }

            //
            //  If this number is greater or equal to  the wrap sequence number in
            //  the Lfcb, set the wrap flag in the Lbcb.
            //

            if (!FlagOn( Lfcb->Flags, LFCB_LOG_WRAPPED )
                && ( Lfcb->SeqNumber >= Lfcb->SeqNumberForWrap )) {

                SetFlag( Lbcb->LbcbFlags, LBCB_LOG_WRAPPED );
                SetFlag( Lfcb->Flags, LFCB_LOG_WRAPPED );
            }
        }

        //
        //  Now initialize the rest of the Lbcb fields.
        //

        Lbcb->FileOffset = Lfcb->NextLogPage;
        Lbcb->SeqNumber = Lfcb->SeqNumber;
        Lbcb->BufferOffset = Lfcb->LogPageDataOffset;

        //
        //  Store the next page in the Lfcb.
        //

        LfsNextLogPageOffset( Lfcb,
                              Lfcb->NextLogPage,
                              &Lfcb->NextLogPage,
                              &WrappedOrUsaError );

        Lbcb->Length = Lfcb->LogPageSize;
        Lbcb->PageHeader = PageHeader;
        Lbcb->LogPageBcb = PageHeaderBcb;

        Lbcb->ResourceThread = ExGetCurrentResourceThread();
        Lbcb->ResourceThread = (ERESOURCE_THREAD) ((ULONG) Lbcb->ResourceThread | 3);

        //
        //  If we are reusing a previous page then set a flag in
        //  the Lbcb to indicate that we should flush a copy
        //  first.
        //

        if (FlagOn( Lfcb->Flags, LFCB_REUSE_TAIL )) {

            SetFlag( Lbcb->LbcbFlags, LBCB_FLUSH_COPY );
            ClearFlag( Lfcb->Flags, LFCB_REUSE_TAIL );

            (ULONG)Lbcb->BufferOffset = Lfcb->ReusePageOffset;

            Lbcb->Flags = ((PLFS_RECORD_PAGE_HEADER) PageHeader)->Flags;
            Lbcb->LastLsn = ((PLFS_RECORD_PAGE_HEADER) PageHeader)->Copy.LastLsn;
            Lbcb->LastEndLsn = ((PLFS_RECORD_PAGE_HEADER) PageHeader)->Header.Packed.LastEndLsn;
        }

        //
        //  Put the Lbcb on the active queue
        //

        InsertTailList( &Lfcb->LbcbActive, &Lbcb->ActiveLinks );

        SetFlag( Lbcb->LbcbFlags, LBCB_ON_ACTIVE_QUEUE );

        //
        //  Now that we have succeeded, set the owner thread to Thread + 1 so the resource
        //  package will know not to peek in this thread.  It may be deallocated before
        //  we release the Bcb during flush.
        //

        CcSetBcbOwnerPointer( Lbcb->LogPageBcb, (PVOID) Lbcb->ResourceThread );

    } finally {

        DebugUnwind( LfsGetLbcb );

        //
        //  If an error occurred, we need to clean up any blocks which
        //  have not been added to the active queue.
        //

        if (AbnormalTermination()) {

            if (Lbcb != NULL) {

                LfsDeallocateLbcb( Lfcb, Lbcb );
                Lbcb = NULL;
            }

            //
            //  Unpin the system page if pinned.
            //

            if (PageHeaderBcb != NULL) {

                CcUnpinData( PageHeaderBcb );
            }
        }

        DebugTrace( -1, Dbg, "LfsGetLbcb:  Exit\n", 0 );
    }

    return Lbcb;
}
