/*++

Copyright (c) 1998  Microsoft Corporation

Module Name:

    vs_trace.cxx

Abstract:

    This module defines the global debug\trace facilities used by the
	Long Term Storage service.
	
	Previous name: bsdebug.cxx

Author:


Revision History:
	Name		Date		Comments
    ssteiner    06/03/98    Made numerious changes and removed iostream
                            dependencies, added a few new registry entries and
                            added serialization.
	aoltean		06/06/99	Taken from atl30\atlbase.h in order to avoid linking ATL with BSCommon.lib
    ssteiner    05/15/00    Fixed bug #116688.  Added file locking to prevent multiple processes from
                            interferring with writing to the trace file.  Added code to place a UNICODE
                            BOM at the beginning of the trace file.
--*/

//
//  ***** Includes *****
//

#pragma warning(disable:4290)
#pragma warning(disable:4127)

#include <wtypes.h>
#pragma warning( disable: 4201 )    // C4201: nonstandard extension used : nameless struct/union
#include <winioctl.h>
#pragma warning( default: 4201 )	// C4201: nonstandard extension used : nameless struct/union
#include <winbase.h>
#include <wchar.h>
#include <string.h>
#include <iostream.h>
#include <fstream.h>
#include <stdio.h>
#include <process.h>
#include <stdlib.h>
#include <time.h>
#include <errno.h>
#include <vssmsg.h>

// Enabling asserts in ATL and VSS
#include "vs_assert.hxx"


#include <oleauto.h>
#include <stddef.h>
#pragma warning( disable: 4127 )    // warning C4127: conditional expression is constant
#include <atlconv.h>
#include <atlbase.h>
#include <ntverp.h>



#include "vs_inc.hxx"
#include "vs_idl.hxx"

////////////////////////////////////////////////////////////////////////
//  Standard foo for file name aliasing.  This code block must be after
//  all includes of VSS header files.
//
#ifdef VSS_FILE_ALIAS
#undef VSS_FILE_ALIAS
#endif
#define VSS_FILE_ALIAS "TRCTRCC"
//
////////////////////////////////////////////////////////////////////////

//
//  The following global, g_cDbgTrace must be declared BEFORE any of our
//  objects including _Module, since some of our static objects have destructors
//  that call trace methods.  The following pragma ensures that this
//  module's static objects are initialized before any of our other
//  static objects, assuming they don't use this same pragma.
//
#pragma warning(disable:4073) // ignore init_seg warning
#pragma init_seg(lib)

CBsDbgTrace g_cDbgTrace;


static VOID MakeFileUnicode(
    IN HANDLE hFile
    );

/////////////////////////////////////////////////////////////////////////////
// constants
//

const WCHAR	VSS_TRACINGKEYPATH[]	=
			L"SYSTEM\\CurrentControlSet\\Services\\VSS\\Debug\\Tracing";

const WCHAR	SETUP_KEY[]	=
			L"SYSTEM\\Setup";

const WCHAR	SETUP_INPROGRESS_REG[]	=
			L"SystemSetupInProgress";

const DWORD SETUP_INPROGRESS_VALUE = 1;


/////////////////////////////////////////////////////////////////////////////
//  Globals
//

//
//  NOTE: g_cDbgTrace, the global instance of this class is declared in
//  ltss\modules\ltssvc\src\ltssvc.cxx since we have to make sure
//  this object is the last one being destructed, otherwise possible
//  calls to this object will fail.
//

//
// Define a TLS var, stores the CLTermStg & intention list index.
// The index is a counter that is incremented and set for each thread
// coming into the service, in the CLTermStg::FinalConstruct method.
// The counter is also incremented and set for each intention list
// thread that is created by the service.
//
//  WARNING
//
//
//_declspec( thread ) DWORD CBsDbgTrace::m_dwContextNum = 0;

//
//  Queries a registry value name and if found sets dwValue to the value.
//  If the value name is not found, dwValue remains unchanged.
//
static DWORD
QuerySetValue (
    IN CRegKey &cRegKey,
    IN OUT DWORD &dwValue,
    IN LPCWSTR pwszValueName
    )
{
    DWORD dwReadValue = 0;
    DWORD dwResult = cRegKey.QueryValue( dwReadValue, pwszValueName );
    
    if ( dwResult == ERROR_SUCCESS )
        dwValue = dwReadValue;

    return dwResult;
}

//
//  Queries a registry value name and if found sets bValue to the value.
//  If the value name is not found, bValue remains unchanged.
//
static DWORD
QuerySetValue (
    IN CRegKey &cRegKey,
    IN OUT BOOL &bValue,
    IN LPCWSTR pwszValueName
    )
{
    DWORD dwReadValue = 0;
    DWORD dwResult = cRegKey.QueryValue( dwReadValue, pwszValueName );

    if ( dwResult == ERROR_SUCCESS )
        bValue = (BOOL)(dwReadValue != 0);

    return dwResult;
}

//
//  Queries a registry value name and if found sets wsValue to the value.
//  If the value name is not found, wsValue remains unchanged.
//
static DWORD
QuerySetValue (
    IN CRegKey &cRegKey,
    OUT LPWSTR &wsValue, // If allocated, must be freed before calling with ::VssFreeString
    IN LPCWSTR pwszValueName
    )
{
	WCHAR pszValueBuffer[_MAX_PATH];
    DWORD dwCount = _MAX_PATH;
    DWORD dwResult = cRegKey.QueryValue( pszValueBuffer, pwszValueName, &dwCount );

    BS_ASSERT(wsValue == NULL);
    if ( dwResult == ERROR_SUCCESS ) 
        ::VssDuplicateStr(wsValue, pszValueBuffer);

    return dwResult;
}

//
//  ***** class definitions *****
//


CBsDbgTrace::CBsDbgTrace()

/*++

Routine Description:

    Constructor method. Default values are given to operational
    parameters and overwritten using values from the registry if
    set.  Also prints out the trace file banner.

Arguments:

    NONE

Return Value:

    NONE

--*/
{
	m_bInitialized = false;
	m_bTracingEnabled = false;
	m_pcs = NULL;
    Initialize( TRUE );
}


CBsDbgTrace::~CBsDbgTrace()
/*++

Routine Description:

    Destructor method.  Prints out the last record in the NTMS


Arguments:

	LONG Indent - NOT USED YET [todo] this is the indentation indicator
	LONG Level - this is the debug trace level

Return Value:

    BOOL

--*/
{
    if ( !m_bInitialized )
        return;

    if ( m_bTracingEnabled ) {
        //
        //  Write out a finished tracing message
        //
        m_pcs->Enter();
        BsDebugTraceAlways( 0, DEBUG_TRACE_ALL, ( L"****************************************************************" ) );
        BsDebugTraceAlways( 0, DEBUG_TRACE_ALL, ( L"**  TRACING FINISHED - ProcessId: 0x%x, ContextId: 0x%x",
            m_dwCurrentProcessId, m_dwContextId ) );
        WCHAR pwszCurrentTime[128];
        time_t ltime;
        struct tm *pToday;
        time( &ltime );
        pToday = localtime( &ltime );
        wcsftime( pwszCurrentTime, sizeof pwszCurrentTime, L"%c", pToday );
        BsDebugTraceAlways( 0, DEBUG_TRACE_ALL, ( L"**  Current time: %s", pwszCurrentTime ) );
        BsDebugTraceAlways( 0, DEBUG_TRACE_ALL, ( L"**  Elapsed time: %d seconds", ltime- m_lTimeStarted ) );
        BsDebugTraceAlways( 0, DEBUG_TRACE_ALL, ( L"****************************************************************" ) );
        m_pcs->Leave();

        //
        //  Make sure the file is flushed before leaving
        //
        if ( m_bTraceToFile ) {
            m_hTraceFile = ::CreateFile( m_pwszTraceFileName?
                                            m_pwszTraceFileName :
                                            BS_DBG_TRACE_FILE_NAME_DFLT,
                                         GENERIC_WRITE,
                                         FILE_SHARE_READ,
                                         NULL,
                                         OPEN_ALWAYS,
                                         FILE_ATTRIBUTE_NORMAL,
                                         NULL );
            if ( m_hTraceFile != INVALID_HANDLE_VALUE ) {
                ::FlushFileBuffers( m_hTraceFile );
                ::CloseHandle( m_hTraceFile );
            }
        }
    }

    ::VssFreeString(m_pwszTraceFileName);

    //
    //  Delete the critical section
    //
    delete m_pcs;
    m_pcs = NULL;
    m_bInitialized = FALSE;
}

//
//  In certain cases the global trace object doesn't seem to get it's constructor called.
//  To fix this problem, this function was added to perform the initialization of the
//  object.  This function is called both in the constructor and the set context call
//  which all DLLs that use the trace class call.
//
VOID
CBsDbgTrace::Initialize(
    IN  BOOL bInConstructor
    )
{
    if ( !m_bInitialized )
    {
		try
			{
			//
			//  Get the critical section created first
			//
			m_pcs = new CBsCritSec;
			if ( m_pcs == NULL )
				throw E_OUTOFMEMORY;
			m_bInitialized = TRUE;

			m_bTracingEnabled       = FALSE;
			m_bTraceToFile          = BS_DBG_TRACE_TO_FILE_DFLT;
			m_bTraceToDebugger      = BS_DBG_TRACE_TO_DEBUGGER_DFLT;
			m_bTraceEnterExit       = BS_DBG_TRACE_ENTER_EXIT_DFLT;
			m_dwTraceLevel          = BS_DBG_TRACE_LEVEL_DFLT;
			m_bTraceFileLineInfo    = BS_DBG_TRACE_FILE_LINE_INFO_DFLT;
			m_bTraceTimestamp       = BS_DBG_TRACE_TIMESTAMP_DFLT;
			m_pwszTraceFileName     = NULL;
			m_bForceFlush           = BS_DBG_TRACE_FORCE_FLUSH_DFLT;
			m_dwTraceIndent         = 0;
			m_bInTrace              = FALSE;
			m_hTraceFile            = INVALID_HANDLE_VALUE;
			m_dwLineNum             = 0;
			m_dwCurrentProcessId    = GetCurrentProcessId();
			m_bIsDuringSetup        = FALSE;
			
			LARGE_INTEGER liTimer;
			if ( ::QueryPerformanceCounter( &liTimer ) )
				{
				//  Got high performance counter, use the low part
				m_dwContextId = liTimer.LowPart;
				}
			else
				{
				m_dwContextId = ::GetTickCount();
				}

			ReadRegistry();

			BsDebugTraceAlways( 0, DEBUG_TRACE_ALL, ( L"****************************************************************" ) );
			BsDebugTraceAlways( 0, DEBUG_TRACE_ALL, ( L"**  TRACING STARTED - ProcessId: 0x%x, ContextId: 0x%x",
				m_dwCurrentProcessId, m_dwContextId ) );
			if ( !bInConstructor )
				BsDebugTraceAlways( 0, DEBUG_TRACE_ALL, ( L"**  N.B. NOT INITIALIZED BY THE CONSTRUCTOR" ) );

			WCHAR pwszCurrentTime[128];
			struct tm *pToday;
			time( &m_lTimeStarted );
			pToday = localtime( &m_lTimeStarted );
			wcsftime( pwszCurrentTime, sizeof pwszCurrentTime, L"%c", pToday );
			BsDebugTraceAlways( 0, DEBUG_TRACE_ALL, ( L"**  Current time: %s", pwszCurrentTime ) );
			BsDebugTraceAlways( 0, DEBUG_TRACE_ALL, ( L"**  Product version: %d.%d.%d.%d", VER_PRODUCTVERSION ) );
			BsDebugTraceAlways( 0, DEBUG_TRACE_ALL, ( L"****************************************************************" ) );
			}
		catch(...)
			{
			delete m_pcs;
			m_pcs = NULL;
			m_bInitialized = false;
			m_bTracingEnabled = false;
			}
		}

}


BOOL
CBsDbgTrace::IsDuringSetup()
{
    return m_bIsDuringSetup;
}



HRESULT
CBsDbgTrace::ReadRegistry()
/*++

Routine Description:

    Tries to read debug specific values from the registry and adds
    the values if they don't exist.

Arguments:

    NONE

Return Value:

    HRESULT

--*/
{

	DWORD dwRes;
	CRegKey cRegKeySetup;
	CRegKey cRegKeyTracing;


    m_bTracingEnabled = FALSE;
    m_bIsDuringSetup = FALSE;
    
	//
	// Open the Setup key
	//

	dwRes = cRegKeySetup.Open( HKEY_LOCAL_MACHINE, SETUP_KEY, KEY_READ );
	if ( dwRes == ERROR_SUCCESS ) {
        DWORD dwSetupInProgress = 0;
        QuerySetValue( cRegKeySetup, dwSetupInProgress, SETUP_INPROGRESS_REG );
        m_bIsDuringSetup = ( dwSetupInProgress == SETUP_INPROGRESS_VALUE );
	}
	
	//
	// Open the VSS tracing key
	//

	dwRes = cRegKeyTracing.Open( HKEY_LOCAL_MACHINE, VSS_TRACINGKEYPATH, KEY_READ );
	if ( dwRes == ERROR_SUCCESS ) {
		
        // The name of the optional trace file
        QuerySetValue( cRegKeyTracing, m_pwszTraceFileName, BS_DBG_TRACE_FILE_NAME_REG );

        // The trace level determines what type of traciung will occur. Zero
        // indicates that no tracing will occur, and is the default.
        QuerySetValue( cRegKeyTracing, m_dwTraceLevel, BS_DBG_TRACE_LEVEL_REG );

        // The TraceEnterExit flag determines whether or not function entry & exit
        // information is output to the trace file & the debug output stream.
        QuerySetValue( cRegKeyTracing, m_bTraceEnterExit, BS_DBG_TRACE_ENTER_EXIT_REG );

        // The TraceToFile flag determines whether or not trace information is output to
        // the trace file. If this value is FALSE, no output is sent to the trace file.
        QuerySetValue( cRegKeyTracing, m_bTraceToFile, BS_DBG_TRACE_TO_FILE_REG );

        // The TraceToDebugger flag determines whether or not trace information is output
        // to the debugger. If this value is FALSE, no output is sent to the debugger.
        QuerySetValue( cRegKeyTracing, m_bTraceToDebugger, BS_DBG_TRACE_TO_DEBUGGER_REG );

        // The Timestamp flag determines whether or not timestamp
        // information is output to the trace file & the debug output stream.
        QuerySetValue( cRegKeyTracing, m_bTraceTimestamp, BS_DBG_TRACE_TIMESTAMP_REG );

        // The FileLineInfo flag determines whether or not the module file name
        // and line number information is output to the trace file & the debug
        // output stream.
        QuerySetValue( cRegKeyTracing, m_bTraceFileLineInfo, BS_DBG_TRACE_FILE_LINE_INFO_REG );

        // The TraceForceFlush flag specifies whether or not after each trace message is
        // written to the trace file a forced flush occurs.  If enabled, no trace records
        // are ever lost, however, performance is greatly reduced.
        QuerySetValue( cRegKeyTracing, m_bForceFlush, BS_DBG_TRACE_FORCE_FLUSH_REG );

        // Determine if tracing should be enabled
        if ( m_bTraceToDebugger || m_bTraceToFile )
            m_bTracingEnabled = TRUE;

	}

    return S_OK;
}


HRESULT
CBsDbgTrace::PrePrint(
    IN LPCWSTR pwszSourceFileName,
    IN DWORD dwLineNum,
    IN DWORD dwIndent,
    IN DWORD dwLevel,
    IN LPCWSTR pwszFunctionName,
    IN BOOL bTraceEnter
    )
/*++

Routine Description:

    Acquires the critical section so that other threads are
    now serialized.  Opens the trace file if necessary.
    N.B. Any A/V's in this code can cause a hang since the SEH translator function
    calls these trace functions.

Arguments:

    pszSourceFileName - Source file name of the module whose
        code called this method.
    dwLineNum - Line number in the source
    dwIndent - Number to increase or decrease the indendation level
    dwLevel - Trace level that specifies for which component
        the code resides in.
    pwszFunctionName - For entry/exit tracing.  Specifies the
        function name constains a call the a trace macro.
    bTraceEnter - True if this is a entry trace.

Return Value:

    HRESULT

--*/
{
    m_pcs->Enter();

    //
    //  Assume the trace macros have already filtered out traces based
    //  on m_bTracingEnabled and on the active trace level.
    //

    if ( m_bTracingEnabled && (dwLevel & m_dwTraceLevel) != 0) {
        if ( pwszSourceFileName == NULL )
            m_pwszSourceFileName = L"(Unknown source file)";
        else
        {
            //
            //  Keep only two levels deep of directory components
            //
            LPCWSTR pwszTemp = pwszSourceFileName + ::wcslen( pwszSourceFileName ) - 1;
            for ( int i = 0; pwszTemp > pwszSourceFileName && i < 3; ++i )
            {
                do
                {
                    --pwszTemp;
                }
                while( *pwszTemp != L'\\' && pwszTemp > pwszSourceFileName ) ;
            }
            if ( pwszTemp > pwszSourceFileName )
                m_pwszSourceFileName = pwszTemp + 1;
            else
                m_pwszSourceFileName = pwszSourceFileName;
        }

        m_pwszFunctionName   = pwszFunctionName;
        m_dwLineNum        = dwLineNum;
        m_bTraceEnter      = bTraceEnter;

        BS_ASSERT( m_hTraceFile == INVALID_HANDLE_VALUE );

        if ( m_bTraceToFile ) {
            m_hTraceFile = ::CreateFile( m_pwszTraceFileName?
                                            m_pwszTraceFileName :
                                            BS_DBG_TRACE_FILE_NAME_DFLT,
                                         GENERIC_WRITE,
                                         FILE_SHARE_READ | FILE_SHARE_WRITE,
                                         NULL,
                                         OPEN_ALWAYS,
                                         FILE_ATTRIBUTE_NORMAL,
                                         NULL );
            if ( m_hTraceFile == INVALID_HANDLE_VALUE ) {
                //
                //  Error opening the file, print a message to the debugger if debugger
                //  tracing is enabled
                //
                Print( L"CBsDbgTrace::PrePrint: TRACING ERROR: Unable to open trace file, dwRet: %u", ::GetLastError() );
            } else {
                //
                //  Now lock the process from other processes and threads that are concurrently
                //  accessing the file.  Just lock the first byte of the file.
                //
                OVERLAPPED ovStart = { NULL, NULL, { 0, 0 }, 0 };
                if ( !::LockFileEx( m_hTraceFile,
                                    LOCKFILE_EXCLUSIVE_LOCK,
                                    0,
                                    1,
                                    0,
                                    &ovStart ) ) {
                    //
                    //  Tracing to file will be skipped for this record.  This should
                    //  never happen in practice.
                    //
                    ::CloseHandle( m_hTraceFile );
                    m_hTraceFile = INVALID_HANDLE_VALUE;

                    //
                    //  Try printing a trace message that will get to the debugger if debugger
                    //  tracing is enabled
                    //
                    Print( L"CBsDbgTrace::PrePrint: TRACING ERROR: Unable to lock trace file, skipping trace record, dwRet: %u", ::GetLastError() );
                } else {
                    //
                    //  If the file is new (empty) put the UNICODE BOM at the beginning of the file
                    //
                    LARGE_INTEGER liPointer;
                    if ( ::GetFileSizeEx( m_hTraceFile, &liPointer ) ) {
                        if ( liPointer.QuadPart == 0 )
                            ::MakeFileUnicode( m_hTraceFile );
                    }

                    //
                    //  Now move the file pointer to the end of the file
                    //
                    liPointer.QuadPart = 0;
                    if ( !::SetFilePointerEx( m_hTraceFile,
                                              liPointer,
                                              NULL,
                                              FILE_END ) ) {
                        //
                        //  Don't write to the file since it might overwrite valid records.
                        //  Tracing to file will be skipped for this record.  This should
                        //  never happen in practice.
                        //
                        ::CloseHandle( m_hTraceFile );
                        m_hTraceFile = INVALID_HANDLE_VALUE;

                        //
                        //  Try printing a trace message that will get to the debugger if debugger
                        //  tracing is enabled
                        //
                        Print( L"CBsDbgTrace::PrePrint: TRACING ERROR: Unable to set end of file, skipping trace record, dwRet: %u", ::GetLastError() );
                    }
                }
            }
        }

        m_bInTrace = TRUE;
    }

    return S_OK;
    UNREFERENCED_PARAMETER( dwIndent );
}

HRESULT
CBsDbgTrace::PostPrint(
    IN DWORD dwIndent
    )
/*++

Routine Description:

    Releases the critical section so that other threads
    can now call perform tracing.  Closes the trace file
    and resets variables.

Arguments:

    dwIndent - Number to increase or decrease the indendation level

Return Value:

    HRESULT

--*/
{
    if ( m_hTraceFile != INVALID_HANDLE_VALUE ) {
        OVERLAPPED ovStart = { NULL, NULL, { 0, 0 }, 0 };
        if ( !::UnlockFileEx( m_hTraceFile,
                              0,
                              1,
                              0,
                              &ovStart ) ) {
            Print( L"CBsDbgTrace::PrePrint: TRACING ERROR: Unable to unlock trace file, dwRet: %u", ::GetLastError() );
        }
        if ( m_bForceFlush )
            ::FlushFileBuffers( m_hTraceFile );
        ::CloseHandle( m_hTraceFile );
        m_hTraceFile = INVALID_HANDLE_VALUE;
    }

    m_pwszSourceFileName = NULL;
    m_pwszFunctionName = NULL;
    m_dwLineNum = 0;
    m_bInTrace  = FALSE;

    m_pcs->Leave();

    return S_OK;
    UNREFERENCED_PARAMETER( dwIndent );
}

HRESULT _cdecl
CBsDbgTrace::Print(
    IN LPCWSTR pwszFormatStr,
    IN ...
    )
/*++

Routine Description:

    Formats the trace message out to the trace file and/or debugger.

Arguments:

    pwszFormatStr - printf style format string
    ... - Arguments for the message

Return Value:

    HRESULT

--*/
{
    va_list pArg;

    if ( m_bInTrace ) {
        if ( m_bTraceTimestamp )
            swprintf( m_pwszOutBuf,
                      L"[%010u,",
                      GetTickCount() );
        else
            swprintf( m_pwszOutBuf,
                      L"[-," );

        swprintf( m_pwszOutBuf + wcslen( m_pwszOutBuf ),
                  L"0x%06x:0x%04x:0x%08x] ",
                  m_dwCurrentProcessId,
                  GetCurrentThreadId(),
                  m_dwContextId );

        if ( m_bTraceFileLineInfo )
          swprintf( m_pwszOutBuf + wcslen( m_pwszOutBuf ),
                    L"%s(%04u): ",
                    m_pwszSourceFileName,
                    m_dwLineNum );

        OutputString();

	    //
        // read the variable length parameter list into a formatted string
        //

        va_start( pArg, pwszFormatStr );
	    _vsnwprintf( m_pwszOutBuf, BS_DBG_OUT_BUF_SIZE-1, pwszFormatStr, pArg );
	    va_end( pArg );

        OutputString();

        //
        //  Finish up with a carriage return.
        //
        wcscpy( m_pwszOutBuf, L"\r\n" );
        OutputString();
    }

    return S_OK;
}

HRESULT _cdecl
CBsDbgTrace::PrintEnterExit(
    IN LPCWSTR pwszFormatStr,
    IN ...
    )
/*++

Routine Description:

    Formats the entry/exit trace message out to the trace file and/or debugger.

Arguments:

    pwszFormatStr - printf style format string
    ... - Arguments for the message

Return Value:

    HRESULT

--*/
{
    va_list pArg;

    if ( m_bInTrace ) {
        if ( m_bTraceTimestamp )
            swprintf( m_pwszOutBuf,
                      L"[%010u,",
                      GetTickCount() );
        else
            swprintf( m_pwszOutBuf,
                      L"[-," );

        swprintf( m_pwszOutBuf + wcslen( m_pwszOutBuf ),
                  L"0x%06x:0x%04x:0x%08x] %s {%s}: ",
                  m_dwCurrentProcessId,
                  GetCurrentThreadId(),
                  m_dwContextId,
                  m_bTraceEnter ? L"ENTER" : L"EXIT ",
                  m_pwszFunctionName );

        OutputString();

	    //
        // read the variable length parameter list into a formatted string
        //

        va_start( pArg, pwszFormatStr );
	    _vsnwprintf( m_pwszOutBuf, BS_DBG_OUT_BUF_SIZE-1, pwszFormatStr, pArg );
	    va_end( pArg );

        OutputString();

        //
        //  Finish up with a carriage return.
        //
        wcscpy( m_pwszOutBuf, L"\r\n" );
        OutputString();
    }

    return S_OK;
}

HRESULT
CBsDbgTrace::OutputString()
/*++

Routine Description:

    Prints the trace message out to the trace file and/or debugger.

Arguments:

    Assumes m_pwszOutBuf has the string to be printed.

Return Value:

    HRESULT

--*/
{
    //
    //  Make sure we didn't go off the end.  Can't use BS_ASSERT(), it
    //  will cause an deadlock.
    //
    _ASSERTE( wcslen( m_pwszOutBuf ) < BS_DBG_OUT_BUF_SIZE );

    //
    // Print to the debug stream for debug builds
    //
    if ( m_bTraceToDebugger )
        OutputDebugString( m_pwszOutBuf );

    //
    // If file tracing is enabled, dump to file
    //
    if ( m_hTraceFile != INVALID_HANDLE_VALUE ) {
        DWORD dwBytesWritten;
        ::WriteFile( m_hTraceFile,
                     m_pwszOutBuf,
                     (DWORD)(wcslen( m_pwszOutBuf ) * sizeof(WCHAR)),
                     &dwBytesWritten,
                     NULL );
    }

    return S_OK;
}


VOID CBsDbgTrace::SetContextNum(
    IN DWORD dwContextNum
    )
/*++

Routine Description:

    Use to be used to set the context number of the operation.  Now it is only
    used to determine if a DLL is loading using the trace class.

Arguments:

    LTS_CONTEXT_DELAYED_DLL - DLL is using the class object.

--*/
{
    if (dwContextNum == LTS_CONTEXT_DELAYED_DLL && !m_bInitialized )
    {
        Initialize();
    }

}


/*++

Routine Description:

    Puts the UNICODE UCS-2 BOM (Byte Order Mark) at the beginning of the file
    to let applications know that 1. this is a UCS-2 UNICODE file and 2. that
    the byte ordering is little-endian.

    Assumes the file is empty.

Arguments:

    hFile - Handle to the file

Return Value:

    <Enter return values here>

--*/
static VOID MakeFileUnicode(
    IN HANDLE hFile
    )
{
    BS_ASSERT( hFile != INVALID_HANDLE_VALUE );
    BYTE byteBOM[2] = { 0xFF, 0xFE };

    DWORD dwBytesWritten;
    ::WriteFile( hFile,
                 byteBOM,
                 sizeof byteBOM,
                 &dwBytesWritten,
                 NULL );
}


void __cdecl CVssFunctionTracer::TranslateError
		(
		IN CVssDebugInfo dbgInfo,          // Caller debugging info
		IN HRESULT hr,
		IN LPCWSTR wszRoutine
		)
/*++

Routine Description:

    Translates an error into a well defined error code.  May log to
	the event log if the error is unexpected

--*/

    {
	if (hr == E_OUTOFMEMORY ||
		hr == HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY) ||
		hr == HRESULT_FROM_WIN32(ERROR_NO_MORE_SEARCH_HANDLES) ||
		hr == HRESULT_FROM_WIN32(ERROR_NO_LOG_SPACE) ||
		hr == HRESULT_FROM_WIN32(ERROR_DISK_FULL) ||
		hr == HRESULT_FROM_WIN32(ERROR_NO_MORE_USER_HANDLES))
		Throw(dbgInfo, E_OUTOFMEMORY, L"Out of memory detected in function %s", wszRoutine);
	else
		{
		LogError(VSS_ERROR_UNEXPECTED_CALLING_ROUTINE, dbgInfo << wszRoutine << hr);
		Throw(dbgInfo, E_UNEXPECTED, L"Unexpected error in routine %s.  hr = 0x%08lx", wszRoutine, hr);
		}
	}


void __cdecl CVssFunctionTracer::TranslateGenericError
		(
		IN CVssDebugInfo dbgInfo,          // Caller debugging info
		IN HRESULT hr,
		IN LPCWSTR wszErrorTextFormat,
		IN ...
		)
/*++

Routine Description:

    Translates an error into a well defined error code.  May log to
	the event log if the error is unexpected

Throws:

    E_UNEXPECTED
        - on unrecognized error codes

--*/

    {
    WCHAR wszOutputBuffer[nVssMsgBufferSize + 1];
    va_list marker;
    va_start( marker, wszErrorTextFormat );
    _vsnwprintf( wszOutputBuffer, nVssMsgBufferSize, wszErrorTextFormat, marker );
    va_end( marker );

	if (hr == E_OUTOFMEMORY ||
		hr == HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY) ||
		hr == HRESULT_FROM_WIN32(ERROR_NO_MORE_SEARCH_HANDLES) ||
		hr == HRESULT_FROM_WIN32(ERROR_NO_LOG_SPACE) ||
		hr == HRESULT_FROM_WIN32(ERROR_DISK_FULL) ||
		hr == HRESULT_FROM_WIN32(ERROR_NO_MORE_USER_HANDLES))
		Throw(dbgInfo, E_OUTOFMEMORY, L"Out of memory detected. %s", wszOutputBuffer);
	else
		{
		LogError(VSS_ERROR_UNEXPECTED_ERRORCODE, dbgInfo << wszOutputBuffer << hr);
		Throw(dbgInfo, E_UNEXPECTED, L"Unexpected error: %s  [hr = 0x%08lx]", wszOutputBuffer, hr);
		}
	}



void __cdecl CVssFunctionTracer::TranslateProviderError
		(
		IN CVssDebugInfo dbgInfo,          // Caller debugging info
		IN GUID ProviderID,
		IN LPCWSTR wszErrorTextFormat,
		IN ...
		)
/*++

Routine Description:

    Translates an error into a well defined error code.  May log to
	the event log if the error is unexpected

	The error is coming from a provider call

Throws:

    E_OUTOFMEMORY
    VSS_E_UNEXPECTED_PROVIDER_ERROR
        - Unexpected provider error. The error code is logged into the event log.
    VSS_E_PROVIDER_VETO
        - Expected provider error. The provider already did the logging.

--*/

    {
    WCHAR wszOutputBuffer[nVssMsgBufferSize + 1];
    va_list marker;
    va_start( marker, wszErrorTextFormat );
    _vsnwprintf( wszOutputBuffer, nVssMsgBufferSize, wszErrorTextFormat, marker );
    va_end( marker );

	if (hr == E_OUTOFMEMORY)
		Throw(dbgInfo, E_OUTOFMEMORY, L"Out of memory detected. %s. Provider ID = " WSTR_GUID_FMT, 
		    wszOutputBuffer, GUID_PRINTF_ARG(ProviderID));
	else if (hr == E_INVALIDARG) {
		LogError(VSS_ERROR_CALLING_PROVIDER_ROUTINE_INVALIDARG, dbgInfo << ProviderID << wszOutputBuffer );
		Throw(dbgInfo, E_INVALIDARG, L"Invalid argument detected. %s. Provider ID = " WSTR_GUID_FMT, 
		    wszOutputBuffer, GUID_PRINTF_ARG(ProviderID));
	}
	else if (hr == VSS_E_PROVIDER_VETO)
		Throw(dbgInfo, VSS_E_PROVIDER_VETO, L"Provider veto detected. %s. Provider ID = " WSTR_GUID_FMT, 
		    wszOutputBuffer, GUID_PRINTF_ARG(ProviderID));
    else
		{
		LogError(VSS_ERROR_CALLING_PROVIDER_ROUTINE, dbgInfo << ProviderID << wszOutputBuffer << hr );
		Throw(dbgInfo, VSS_E_UNEXPECTED_PROVIDER_ERROR, 
		    L"Unexpected error calling a provider routine: %s  [hr = 0x%08lx] Provider ID = " WSTR_GUID_FMT, 
		    wszOutputBuffer, hr, GUID_PRINTF_ARG(ProviderID));
		}
	}



void __cdecl CVssFunctionTracer::TranslateInternalProviderError
		(
		IN CVssDebugInfo dbgInfo,          // Caller debugging info
		IN HRESULT hrToBeTreated,
		IN HRESULT hrToBeThrown,
		IN LPCWSTR wszErrorTextFormat,
		IN ...
		)
/*++

Routine Description:

    Translates an error into a well defined error code.  May log to
	the event log if the error is unexpected

	The error is coming from a provider call

Throws:

    E_OUTOFMEMORY
    
    hrToBeThrown

--*/

    {
    WCHAR wszOutputBuffer[nVssMsgBufferSize + 1];
    va_list marker;
    va_start( marker, wszErrorTextFormat );
    _vsnwprintf( wszOutputBuffer, nVssMsgBufferSize, wszErrorTextFormat, marker );
    va_end( marker );

    hr = hrToBeTreated;

	if (hr == E_OUTOFMEMORY ||
		hr == HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY) ||
		hr == HRESULT_FROM_WIN32(ERROR_NO_MORE_SEARCH_HANDLES) ||
		hr == HRESULT_FROM_WIN32(ERROR_NO_LOG_SPACE) ||
		hr == HRESULT_FROM_WIN32(ERROR_DISK_FULL) ||
		hr == HRESULT_FROM_WIN32(ERROR_NO_MORE_USER_HANDLES))
		Throw(dbgInfo, E_OUTOFMEMORY, L"Out of memory detected. %s.", wszOutputBuffer);
	else if ( hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) ||
	    hr == HRESULT_FROM_WIN32(ERROR_DEVICE_NOT_CONNECTED) || 
		hr == HRESULT_FROM_WIN32(ERROR_NOT_READY))
	    {
		LogError(VSS_ERROR_DEVICE_NOT_CONNECTED, dbgInfo << wszOutputBuffer );
		Throw(dbgInfo, VSS_E_OBJECT_NOT_FOUND, 
		    L"Invalid device when calling a provider routine: %s", wszOutputBuffer);
    	}
    else
		{
		LogError(VSS_ERROR_UNEXPECTED_ERRORCODE, dbgInfo << wszOutputBuffer << hr );
		Throw(dbgInfo, hrToBeThrown, 
		    L"Unexpected error calling a provider routine: %s  [hr = 0x%08lx] ", wszOutputBuffer, hr );
		}
	}



void __cdecl CVssFunctionTracer::TranslateWriterReturnCode
		(
		IN CVssDebugInfo dbgInfo,          // Caller debugging info
		IN LPCWSTR wszErrorTextFormat,
		IN ...
		)
/*++

Routine Description:

    Translates an error into a well defined error code.  May log to
	the event log if the error is unexpected

	The error is coming from a writer call (CoCreteInstance of the writer COM+ event class
	or sending an event.

Throws:

    E_OUTOFMEMORY
    VSS_E_UNEXPECTED_WRITER_ERROR
        - Unexpected writer error. The error code is logged into the event log.

--*/

    {

    if (HrSucceeded()) {
        BS_ASSERT(hr == S_OK || hr == EVENT_S_NOSUBSCRIBERS || hr == EVENT_S_SOME_SUBSCRIBERS_FAILED);
        hr = S_OK;
        return;
    }
    
    WCHAR wszOutputBuffer[nVssMsgBufferSize + 1];
    va_list marker;
    va_start( marker, wszErrorTextFormat );
    _vsnwprintf( wszOutputBuffer, nVssMsgBufferSize, wszErrorTextFormat, marker );
    va_end( marker );
    
    if (hr == EVENT_E_ALL_SUBSCRIBERS_FAILED) {
		Warning( VSSDBG_COORD, L"%s event failed at one writer. hr = 0x%08lx", wszOutputBuffer, hr);
		// ignore the error;
        return;
        }
	else if (hr == E_OUTOFMEMORY)
		Throw(dbgInfo, E_OUTOFMEMORY, L"Out of memory detected. %s.", wszOutputBuffer);
    else
		{
		LogError(VSS_ERROR_UNEXPECTED_WRITER_ERROR, dbgInfo << wszOutputBuffer << hr );
		Throw(dbgInfo, VSS_E_UNEXPECTED_WRITER_ERROR, 
		    L"Unexpected error calling a provider routine: %s  [hr = 0x%08lx] ", wszOutputBuffer, hr);
		}
    }



void __cdecl CVssFunctionTracer::LogGenericWarning
		(
		IN CVssDebugInfo dbgInfo,          // Caller debugging info
		IN LPCWSTR wszErrorTextFormat,
		IN ...
		)
/*++

Routine Description:

    Log a generic warning.

--*/

    {
    WCHAR wszOutputBuffer[nVssMsgBufferSize + 1];
    va_list marker;
    va_start( marker, wszErrorTextFormat );
    _vsnwprintf( wszOutputBuffer, nVssMsgBufferSize, wszErrorTextFormat, marker );
    va_end( marker );

	LogError(VSS_WARNING_UNEXPECTED, dbgInfo << wszOutputBuffer << hr, EVENTLOG_WARNING_TYPE);
	Trace(dbgInfo, L"WARNING: %s [hr = 0x%08lx]", wszOutputBuffer, hr);
	}


// This method must be called prior to calling a CoCreateInstance that may start VSS
void CVssFunctionTracer::LogVssStartupAttempt()
{
    // the name of the Volume Snapshot Service
    const LPCWSTR wszVssvcServiceName = L"VSS";
    
    SC_HANDLE		shSCManager = NULL;
    SC_HANDLE		shSCService = NULL;

    try
	{
        //
        //  Check to see if VSSVC is running. If not, we are putting an entry into the trace log if enabled.
        //

    	// Connect to the local service control manager
        shSCManager = OpenSCManager (NULL, NULL, SC_MANAGER_CONNECT);
        if (!shSCManager) 
            TranslateGenericError(VSSDBG_GEN, HRESULT_FROM_WIN32(GetLastError()), 
                L"OpenSCManager(NULL,NULL,SC_MANAGER_CONNECT)");

    	// Get a handle to the service
        shSCService = OpenService (shSCManager, wszVssvcServiceName, SERVICE_QUERY_STATUS);
        if (!shSCService) 
            TranslateGenericError(VSSDBG_GEN, HRESULT_FROM_WIN32(GetLastError()), 
                L" OpenService (shSCManager, \'%s\', SERVICE_QUERY_STATUS)", wszVssvcServiceName);

    	// Now query the service to see what state it is in at the moment.
        SERVICE_STATUS	sSStat;
        if (!QueryServiceStatus (shSCService, &sSStat))
            TranslateGenericError(VSSDBG_GEN, HRESULT_FROM_WIN32(GetLastError()), 
                L"QueryServiceStatus (shSCService, &sSStat)");

        // If the service is not running, then we will put an informational error log entry 
        // if (sSStat.dwCurrentState != SERVICE_RUNNING)
        //     LogError(VSS_INFO_SERVICE_STARTUP, 
        //         VSSDBG_GEN << GetCommandLineW() << (HRESULT)sSStat.dwCurrentState, 
        //         EVENTLOG_INFORMATION_TYPE);
        if (sSStat.dwCurrentState != SERVICE_RUNNING)
            Trace( VSSDBG_GEN, 
                L"Volume Snapshots Service information: Service starting at request of process '%s'. [0x%08x]",
                GetCommandLineW(), sSStat.dwCurrentState );        
	} VSS_STANDARD_CATCH ((*this));

    // Close handles
    if (NULL != shSCService) CloseServiceHandle (shSCService);
    if (NULL != shSCManager) CloseServiceHandle (shSCManager);
}