/*++
*******************************************************************************
* Copyright (c) 1990-98 Digi International Inc., All Rights Reserved				*
* 																										*
* This software contains proprietary and confidential information of Digi		*
* International Inc.  By accepting transfer of this copy, Recipient agrees		*
* to retain this software in confidence, to prevent disclosure to others,		*
* and to make no use of this software other than that for which it was			*
* delivered.  This is an unpublished copyrighted work of Digi International	*
* Inc.  Except as permitted by federal law, 17 USC 117, copying is strictly	*
* prohibited.																						*
* 																										*
* Restricted Rights Legend																		*
*																										*
* Use, duplication, or disclosure by the Government is subject to					*
* restrictions set forth in sub-paragraph (c)(1)(ii) of The Rights in			*
* Technical Data and Computer Software clause at DFARS 252.227-7031 or			*
* subparagraphs (c)(1) and (2) of the Commercial Computer Software -				*
* Restricted Rights at 48 CFR 52.227-19, as applicable.								*
*																										*
* Digi International Inc. 11001 Bren Road East, Minnetonka, MN 55343				*
*																										*
*******************************************************************************

Module Name:

   memprint.c

Abstract:

   See MS abstract below.  Modifications: memprint contains just the lowlevel routines 
   to shuffle debug text off to disk.  DigiDebug now decides whether or not to use
	memprint support.

Author:

   Michael Zarns

--*/

/*++

Copyright (c) 1989  Microsoft Corporation

Module Name:

    memprint.c

Abstract:

    This module contains the routines to implement in-memory DbgPrint.
    DbgPrint text is stored in a large circular buffer and written to a file.
    Output to file is buffered to allow high performance by the file system.

Author:

    David Treadwell (davidtr) 05-Oct-1990

--*/

#include <stdarg.h>
#include <string.h>
#include <stdio.h>

#include "digilib.h"

#ifndef NO_MEMPRINT

#if DBG || DIGIDEBUG_ENABLE

#define MEM_PRINT_DEF_BUFFER_SIZE (65536 * 4)
#define MEM_PRINT_LOG_FILE_NAME "\\SystemRoot\\DigiLog.log"

//
// Global data.  It is all protected by MemPrintSpinLock.
//

STATIC PVOID ThreadObjectPointer;
STATIC HANDLE fileHandle;
STATIC UCHAR TurnOffSniffer=1;
STATIC CLONG MemPrintBufferSize = MEM_PRINT_DEF_BUFFER_SIZE;
STATIC PCHAR MemPrintBuffer;
STATIC ULONG AttemptedTempBufferAllocs=0;
STATIC ULONG MemPrintFailures=0;
STATIC CHAR  DefaultLogFileName[1024]=MEM_PRINT_LOG_FILE_NAME;

STATIC KSPIN_LOCK MemPrintSpinLock;
STATIC BOOLEAN    MemPrintInitialized = FALSE;
STATIC BOOLEAN    UnloadingDriver = FALSE;
STATIC KEVENT     MemPrintWriteToLogEvent;

STATIC LARGE_INTEGER totalBytesWritten;
STATIC LARGE_INTEGER fileAllocationSize;

STATIC ULONG BufferInOffset, BufferOutOffset;


VOID 
MemPrintWriteCompleteApc( 
	IN PVOID            ApcContext,
	IN PIO_STATUS_BLOCK IoStatusBlock )
/*++

Routine Description:

    This APC routine is called when the current write to disk complete.
    It checks for success, printing a message if the write failed.
    It also updates BufferOutOffset.

Arguments:

    ApcContext - contains what the CurrentInOffset was when
                 ZwWriteFile was called.

    IoStatusBlock - the status block for the operation.

Return Value:

    None.

--*/

{
   KIRQL oldIrql;

   if( !NT_SUCCESS(IoStatusBlock->Status) )
   {
      DbgPrint( "ZwWriteFile failed: 0x%x\n", IoStatusBlock->Status );
      return;
   }

   //
   // Acquire the spin lock that protects memory print global variables
   // and set the subbuffer writing boolean to FALSE so that other
   // threads can write to the subbuffer if necessary.
   //

   KeAcquireSpinLock( &MemPrintSpinLock, &oldIrql );

   if( BufferOutOffset + IoStatusBlock->Information > MemPrintBufferSize )
   {
      BufferOutOffset = BufferOutOffset + (ULONG)IoStatusBlock->Information - MemPrintBufferSize;
   }
   else if( BufferOutOffset + IoStatusBlock->Information < MemPrintBufferSize )
   {
      BufferOutOffset += (ULONG)IoStatusBlock->Information;
   }
   else
   {
      BufferOutOffset = 0;
   }

   KeReleaseSpinLock( &MemPrintSpinLock, oldIrql );

   return;

}


VOID 
MemPrintWriteThread ( 
	IN PVOID Dummy )

/*++

Routine Description:

    The log file write thread executes this routine.  It sets up the
    log file for writing, then waits for subbuffers to fill, writing
    them to disk when they do.  When the log file fills, new space
    for it is allocated on disk to prevent the file system from
    having to do it.

Arguments:

    Dummy - Ignored.

Return Value:

    None.

--*/

{
   NTSTATUS status;
   NTSTATUS waitStatus;

   IO_STATUS_BLOCK localIoStatusBlock;

   ULONG NumberBytesToWrite;

   Dummy;

   KeSetPriorityThread( KeGetCurrentThread(), LOW_REALTIME_PRIORITY + 1 );

   //
   // Loop waiting for one of the subbuffer full events to be signaled.
   // When one is signaled, wake up and write the subbuffer to the log
   // file.
   //

   if( UnloadingDriver )
      PsTerminateSystemThread( STATUS_SUCCESS );

   while( TRUE )
   {
      PUCHAR tmpPtr;
      ULONG CurrentOutOffset, CurrentInOffset;
      KIRQL oldIrql;

      waitStatus = KeWaitForSingleObject( &MemPrintWriteToLogEvent, Executive,
                                          KernelMode, TRUE, NULL );

      if( !NT_SUCCESS(waitStatus) )
      {
         DbgPrint( "KeWaitForMultipleObjects failed: 0x%x\n", waitStatus );
         continue;
      }

      if( UnloadingDriver )
         PsTerminateSystemThread( STATUS_SUCCESS );

      //
      // Take a snap shoot of the in pointer.  It is possible it will
      // change on us.
      //
      KeAcquireSpinLock( &MemPrintSpinLock, &oldIrql );

      CurrentInOffset = BufferInOffset;
      CurrentOutOffset = BufferOutOffset;

      KeReleaseSpinLock( &MemPrintSpinLock, oldIrql );

      while( CurrentInOffset != CurrentOutOffset )
      {
         ASSERT( CurrentInOffset <= MemPrintBufferSize );
         ASSERT( CurrentOutOffset <= MemPrintBufferSize );

         if( CurrentInOffset > CurrentOutOffset )
         {
            //
            // We only need to do one write to the log file.
            //
            NumberBytesToWrite = CurrentInOffset - CurrentOutOffset;
         }
         else
         {
            //
            // We have a buffer wrap situation and as a result need
            // to account by making two write's to the log file.
            //
            NumberBytesToWrite = MemPrintBufferSize - CurrentOutOffset;
         }

         ASSERT( (CurrentOutOffset + NumberBytesToWrite) <= MemPrintBufferSize );

         tmpPtr = MemPrintBuffer + CurrentOutOffset;
         //
         // Start the write operation.  The APC routine will handle
         // checking the return status from the write and updating
         // BufferOutOffset.
         //
   
         status = ZwWriteFile( fileHandle, NULL,
                               (PIO_APC_ROUTINE)MemPrintWriteCompleteApc,
                               (PVOID)NULL,
                               &localIoStatusBlock, tmpPtr, NumberBytesToWrite,
                               &totalBytesWritten, NULL );


         if( !NT_SUCCESS(status) )
         {
            DbgPrint( "ZwWriteFile for log file failed: 0x%x\n", status );
         }

         //
         // Update the count of bytes written to the log file.
         //

         CurrentOutOffset += NumberBytesToWrite;
         ASSERT( CurrentOutOffset <= MemPrintBufferSize );

         if( CurrentOutOffset >= MemPrintBufferSize )
            CurrentOutOffset = 0;

         totalBytesWritten.QuadPart += NumberBytesToWrite;

         //
         // Extend the file if we have reached the end of what we have
         // thus far allocated for the file.  This increases performance
         // by extending the file here rather than in the file system,
         // which would have to extend it each time a write past end of
         // file comes in.
         //

         if( (totalBytesWritten.QuadPart >= fileAllocationSize.QuadPart) )
         {
            fileAllocationSize.QuadPart = fileAllocationSize.QuadPart + MemPrintBufferSize;

            DbgPrint( "Enlarging logfile %s to %ld bytes.\n",
                      DefaultLogFileName,
                      fileAllocationSize.LowPart );

            status = ZwSetInformationFile( fileHandle, &localIoStatusBlock, &fileAllocationSize,
                                           sizeof(fileAllocationSize), FileAllocationInformation );

            if( !NT_SUCCESS(status) )
            {
               DbgPrint( "Attempt to extend log file failed: 0x%x\n", status );
               fileAllocationSize.QuadPart = fileAllocationSize.QuadPart - MemPrintBufferSize;
            }
         }
      }

      if( UnloadingDriver )
         PsTerminateSystemThread( STATUS_SUCCESS );
   }

   return;

}


NTSTATUS 
MemPrintInitialize( 
	IN PCSZ pLogFileName )
/*++

Routine Description:

	This is the initialization routine for the in-memory DbgPrint routine.
	It should be called before the first call to MemPrint to set up the
	various structures used and to start the log file write thread.

Arguments:

    pLogFileName
		String defining the name of the logfile to be created.

Return Value:

    STATUS_SUCCESS or error.

--*/
{
   NTSTATUS status;
   HANDLE threadHandle;
   KPRIORITY threadPriorityLevel;

   OBJECT_ATTRIBUTES objectAttributes;
   PCHAR fileName;
   ANSI_STRING fileNameString;

   UNICODE_STRING UnicodeFileName;

   LARGE_INTEGER delayInterval;
   ULONG attempts = 0;

   IO_STATUS_BLOCK localIoStatusBlock;

   if( MemPrintInitialized )
   {
      // we have all ready been called.  Just return.
      return( STATUS_SUCCESS );
   }

   UnloadingDriver = FALSE;

   //
   // Initialize the total bytes written and write size variables.
   //

   totalBytesWritten.QuadPart = 0;
   fileAllocationSize.QuadPart = 0;
   BufferInOffset = BufferOutOffset = 0;

   //
   // Allocate memory for the circular buffer that will receive
   // the text and data.  If we can't do it, try again with a buffer
   // half as large.  If that fails, quit trying.
   //

   MemPrintBuffer = (PCHAR)DigiAllocMem( NonPagedPool, MemPrintBufferSize );

   if( MemPrintBuffer == NULL )
   {
      MemPrintBufferSize /= 2;
      DbgPrint( "Unable to allocate DbgPrint buffer--trying size = %ld\n",
                    MemPrintBufferSize );
      MemPrintBuffer = DigiAllocMem( NonPagedPool, MemPrintBufferSize );

      if( MemPrintBuffer == NULL )
      {
         DbgPrint( "Couldn't allocate DbgPrint buffer.\n" );
         return( STATUS_INSUFFICIENT_RESOURCES );
      }
   }

   //
   // Allocate the spin lock that protects access to the various
   // pointers and the circular buffer.  This ensures integrity of the
   // buffer.
   //

   KeInitializeSpinLock( &MemPrintSpinLock );

   KeInitializeEvent( &MemPrintWriteToLogEvent,
                      SynchronizationEvent,
                      (BOOLEAN)FALSE );

   //
   // Initialize the string containing the file name and the object
   // attributes structure that will describe the log file to open.
   //

   RtlInitAnsiString( &fileNameString, pLogFileName );
   status = RtlAnsiStringToUnicodeString( &UnicodeFileName, &fileNameString, (BOOLEAN)TRUE );
   ASSERT(NT_SUCCESS(status));

   InitializeObjectAttributes( &objectAttributes, &UnicodeFileName, 
                               OBJ_CASE_INSENSITIVE, NULL, NULL );

   //
   // Set the allocation size of the log file to be three times the
   // size of the circular buffer.  When it fills up, we'll extend
   // it.
   //

   fileAllocationSize.QuadPart += MemPrintBufferSize;

	status = ZwCreateFile( &fileHandle, FILE_WRITE_DATA, &objectAttributes,
	                     &localIoStatusBlock, &fileAllocationSize,
	                     FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ, FILE_OVERWRITE_IF, 
	                     FILE_SEQUENTIAL_ONLY, NULL, 0L );

	RtlFreeUnicodeString( &UnicodeFileName );

   if( NT_ERROR(status) )
   {
      DbgPrint( "NtCreateFile for log file failed: 0x%x\n", status );
      DigiFreeMem( MemPrintBuffer );
      return( status );
   }

   //
   // Start the thread that writes subbuffers from the large circular
   // buffer to disk.
   //

   status = PsCreateSystemThread( &threadHandle, THREAD_ALL_ACCESS, NULL, (HANDLE)0L,
                                  NULL, MemPrintWriteThread, NULL );

   if( NT_ERROR(status) )
   {
      DbgPrint( "MemPrintInitialize: PsCreateSystemThread failed: 0x%x\n",
                status );
      //
      // Cleanup
      //
      ZwClose( fileHandle );
      DigiFreeMem( MemPrintBuffer );

      return( status );
   }

   status = ObReferenceObjectByHandle( threadHandle, THREAD_ALL_ACCESS, NULL,
                                       KernelMode, &ThreadObjectPointer, NULL );

   if( NT_ERROR(status) )
   {
      ZwClose( fileHandle );
      DigiFreeMem( MemPrintBuffer );

      return( status );
   }

   MemPrintInitialized = TRUE;

   return( STATUS_SUCCESS );

} // MemPrintInitialize



VOID 
MemPrintQuit( 
	VOID )
/*++

Routine Description:

    Called to cleanup.

Arguments:

    None.

Return Value:

    None.

--*/
{
   KIRQL oldIrql;

   KeAcquireSpinLock( &MemPrintSpinLock, &oldIrql );

   UnloadingDriver = TRUE;

   if( !MemPrintInitialized )
   {
      KeReleaseSpinLock( &MemPrintSpinLock, oldIrql );
      return;
   }

   KeSetEvent( &MemPrintWriteToLogEvent,
               2,
               (BOOLEAN)FALSE );

   KeReleaseSpinLock( &MemPrintSpinLock, oldIrql );

   KeWaitForSingleObject( ThreadObjectPointer, Executive, KernelMode, FALSE, NULL );

   DigiFreeMem( MemPrintBuffer );

   ZwClose( fileHandle );

}


VOID 
MemPrint ( 
	PCSZ pString )
/*++

Routine Description:

    This routine is called to print a pre-formatted string to the debug circular queue
	 to later be flushed out to debug logfile.

Arguments:

    pString
		The string to display

Return Value:

    None.

--*/
{
   KIRQL oldIrql;
   ULONG CurrentOutOffset, CurrentInOffset;
   ULONG BytesToMove;
	PCSZ pIn;
	ULONG cb;

   if( !MemPrintInitialized || UnloadingDriver )
		return ;

   KeAcquireSpinLock( &MemPrintSpinLock, &oldIrql );

   CurrentOutOffset = BufferOutOffset;
   CurrentInOffset = BufferInOffset;

	cb = strlen( pString );
	pIn = pString;

   while( cb )
   {
      if( CurrentOutOffset > CurrentInOffset )
      {
         //
         // Take into account that we don't want to write one less
         // so we don't put the CurrentInOffset equal to CurrentOutOffset.
         //
         BytesToMove = CurrentOutOffset - CurrentInOffset - 1;
      }
      else if( CurrentOutOffset < CurrentInOffset )
      {
         BytesToMove = CurrentOutOffset + MemPrintBufferSize - CurrentInOffset;
      }
      else
      {
         //
         // We have the full buffer,
         //
         BytesToMove = MemPrintBufferSize - 1;
      }

      //
      // We know how many bytes are available in the buffer.  Now determine
      // how many bytes we can actually write.
      //
      if( BytesToMove >= cb )
      {
         //
         // We can put the whole thing into the memprint buffer.
         //
         BytesToMove = cb;
      }

      if( BytesToMove == 0 )
      {
         //
         // More than likely, we ran out of buffer space.
         //
         if( (MemPrintFailures % 1000) == 0 )
            DbgPrint( "Out of buffer space for MemPrint request, failures = %u!\n",
                      MemPrintFailures );

         MemPrintFailures++;
         break;
      }

      //
      // Okay, we know how many bytes we can potentially move.  Now determine
      // if we need to worry about wrapping the circular buffer.
      //
      if( (BytesToMove + CurrentInOffset) >= MemPrintBufferSize )
      {
         //
         // readjust so we only write the number of bytes to the end
         // of the buffer.
         //
         BytesToMove = MemPrintBufferSize - CurrentInOffset;
      }

      RtlMoveMemory( &MemPrintBuffer[CurrentInOffset], pIn, BytesToMove );

      cb -= BytesToMove;

      pIn += BytesToMove;

      CurrentInOffset += BytesToMove;

      //
      // Adjust the CurrentInOffset for circular buffer wrapping.
      //
      ASSERT( CurrentInOffset <= MemPrintBufferSize );

      if( CurrentInOffset == MemPrintBufferSize )
         CurrentInOffset = 0;

   }

   BufferInOffset = CurrentInOffset;

   KeReleaseSpinLock( &MemPrintSpinLock, oldIrql );

   //
   // Set the event that will wake up the thread writing subbuffers
   // to disk.
   //

   KeSetEvent( &MemPrintWriteToLogEvent, 2, (BOOLEAN)FALSE );
}

#endif

#endif