//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright (C) Microsoft Corporation, 1997.
//
//  File:       T R A C E . C P P
//
//  Contents:   The actual tracing code (loading from ini, calling the
//              trace routines, etc.
//
//  Notes:
//
//  Author:     jeffspr   9 Apr 1997
//
//----------------------------------------------------------------------------

#include <pch.h>
#pragma hdrstop

#ifdef ENABLETRACE

#include <stdio.h>
#include "ncdebug.h"
#include "ncbase.h"

#define DBG_BUFFER_SIZE_SMALL     (256)
#define DBG_BUFFER_SIZE_LARGE     (1024)

//---[ CTracing class ]-------------------------------------------------------
//
// Don't give this class a constructor or destructor.  We declare a global
// (static to this module) instance of this class and, by definition, static
// variables are automatically initialized to zero.
//
class CTracing
{
public:
    CTracing();
    // Initialize/Deinitialize this class
    //
    HRESULT HrInit();
    HRESULT HrUnInit();

    VOID Trace( TraceTagId    ttid,
                PCSTR         pszaTrace );

private:
    BOOL    m_fInitialized;                 // Has the object been initialized
    BOOL    m_fAttemptedLogFileOpen;        // Already attempted to open log
    BOOL    m_fDisableLogFile;              // Disable use of file logging?
    HANDLE  m_hLogFile;                     // Handle for debug output file
    HANDLE  m_hMutex;
    CHAR    m_szLogFilePath[MAX_PATH+1];    // File for debug output
    BOOL    m_fDebugFlagsLoaded;            // Have these been loaded yet.

    VOID    CorruptionCheck();              // Validate the tracetag structure

    HRESULT HrLoadOptionsFromIniFile();
    HRESULT HrLoadSectionsFromIniFile();
    HRESULT HrLoadDebugFlagsFromIniFile();
    HRESULT HrWriteDebugFlagsToIniFile();
    HRESULT HrOpenLogFile();

    HRESULT HrProcessTagSection(TraceTagElement *   ptte);
    HRESULT HrGetPrivateProfileString(  PCSTR   lpAppName,
                                        PCSTR   lpKeyName,
                                        PCSTR   lpDefault,
                                        PSTR    lpReturnedString,
                                        DWORD   nSize,
                                        PCSTR   lpFileName,
                                        DWORD * pcchReturn );
    BOOL    FIniFileExists();
};


//---[ Static Variables ]-----------------------------------------------------

static CTracing g_Tracing;                      // Our global tracing object

//---[ Constants ]------------------------------------------------------------

static const CHAR   c_szaDebugIniFileName[] = "upnpdbg.ini";       // .INI file
static const CHAR   c_szaTraceLogFileName[] = "nctrace.log";      // .LOG file

// constants for the INI file labels
static const CHAR   c_szaOptions[]          = "Options";
static const CHAR   c_szaLogFilePath[]      = "LogFilePath";
static const CHAR   c_szaDisableLogFile[]   = "DisableLogFile";
static const CHAR   c_szaLogFileMutex[]     = "UPNP_LOG_FILE_MUTEX";

const INT   c_iDefaultDisableLogFile    = 0;

//+---------------------------------------------------------------------------
//
//  Function:   HrInitTracing
//
//  Purpose:    Initialize the Tracing object and other random data.
//
//  Arguments:
//      (none)
//
//  Returns:    S_OK or valid Win32 HRESULT
//
//  Author:     jeffspr   9 Apr 1997
//
//  Notes:
//
HRESULT HrInitTracing()
{
    HRESULT hr = g_Tracing.HrInit();
    return hr;
}


//+---------------------------------------------------------------------------
//
//  Function:   HrUnInitTracing
//
//  Purpose:    Uninitialize the tracing object.
//
//  Arguments:
//      (none)
//
//  Returns:    S_OK or a valid Win32 HRESULT
//
//  Author:     jeffspr   14 Apr 1997
//
//  Notes:
//
HRESULT HrUnInitTracing()
{
    HRESULT hr = g_Tracing.HrUnInit();
    return hr;
}

const DWORD TI_HRESULT      = 0x00000001;
const DWORD TI_WIN32        = 0x00000002;
const DWORD TI_IGNORABLE    = 0x00000004;

//+---------------------------------------------------------------------------
//
//  Function:   TraceInternal
//
//  Purpose:    The one and only place that a string to be traced is formed
//              and traced.
//
//  Arguments:
//
//  Returns:    nothing.
//
//  Author:     shaunco   13 Mar 1998
//
//  Notes:      Restructured from lots of other code that was added to this
//              module over the past year.
//
VOID
TraceInternal (
    TRACETAGID  ttid,
    PCSTR       pszaFile,
    INT         nLine,
    DWORD       dwFlags,
    PCSTR       pszaCallerText,
    DWORD       dwErrorCode)
{
    // If this tracetag is turned off, don't do anything.
    //
    if (!g_TraceTags[ttid].fOutputDebugString &&
        !g_TraceTags[ttid].fOutputToFile)
    {
        return;
    }

    BOOL fError     = dwFlags & (TI_HRESULT | TI_WIN32);
    BOOL fIgnorable = dwFlags & TI_IGNORABLE;

    HRESULT hr           = (dwFlags & TI_HRESULT) ? dwErrorCode : S_OK;
    DWORD   dwWin32Error = (dwFlags & TI_WIN32)   ? dwErrorCode : ERROR_SUCCESS;

    // Ignore if told and we're not set to trace ignored errors or warnings.
    //
    if (fError && fIgnorable &&
        !FIsDebugFlagSet (dfidShowIgnoredErrors) &&
        !FIsDebugFlagSet (dfidExtremeTracing))
    {
        return;
    }

    // Don't do anything if we're tracing for an error and we don't have one,
    // unless the "Extreme Tracing" flag is on, in which case we trace
    // everything in the world (for debugger use only, really)
    // This is the path taken by TraceError ("...", S_OK) or
    // TraceLastWin32Error when there is no last Win32 error.
    //
    if (fError && !dwErrorCode && !FIsDebugFlagSet(dfidExtremeTracing))
    {
        return;
    }

    CHAR szaBuf [DBG_BUFFER_SIZE_LARGE];
    PSTR pcha = szaBuf;

    // Form the prefix, process id and thread id.
    //
    static const CHAR c_szaFmtPrefix [] = "UPNPDBG";
    lstrcpyA (pcha, c_szaFmtPrefix);
    pcha += lstrlenA (c_szaFmtPrefix);

    // Add process and thread ids if the debug flags indicate to do so.
    //
    if (FIsDebugFlagSet (dfidShowProcessAndThreadIds))
    {
        static const CHAR c_szaFmtPidAndTidDecimal [] = " %03d.%03d";
        static const CHAR c_szaFmtPidAndTidHex [] = " 0x%04x.0x%04x";

        LPCSTR pszaFmtPidAndTid = NULL;

        if (FIsDebugFlagSet (dfidShowIdsInHex))
        {
            pszaFmtPidAndTid = c_szaFmtPidAndTidHex;
        }
        else
        {
            pszaFmtPidAndTid = c_szaFmtPidAndTidDecimal;
        }

        pcha += wsprintfA (pcha, pszaFmtPidAndTid,
                    GetCurrentProcessId (),
                    GetCurrentThreadId ());
    }

    // Add a time stamp if the debug flags indicate to do so.
    //
    if (FIsDebugFlagSet (dfidTracingTimeStamps))
    {
        static const CHAR c_szaFmtTime [] = " [%02d:%02d:%02d.%03d]";

        SYSTEMTIME stLocal;
        GetLocalTime (&stLocal);
        pcha += wsprintfA (pcha, c_szaFmtTime,
                    stLocal.wHour,
                    stLocal.wMinute,
                    stLocal.wSecond,
                    stLocal.wMilliseconds);
    }

    // Add a severity indicator if this trace is for an error or warning.
    //
    if (fError || (ttidError == ttid))
    {
        static const CHAR c_szaSevIgnored [] = " Ignored:";
        static const CHAR c_szaSevError   [] = " *ERROR*:";
        static const CHAR c_szaSevWarning [] = " Warning:";

        PCSTR pszaSev = NULL;

        if (fError && SUCCEEDED(hr) && !dwWin32Error && !fIgnorable)
        {
            pszaSev = c_szaSevWarning;
        }
        else
        {
            if (fIgnorable && FIsDebugFlagSet (dfidShowIgnoredErrors))
            {
                pszaSev = c_szaSevIgnored;
            }
            else
            {
                pszaSev = c_szaSevError;
            }
        }
        Assert (pszaSev);

        lstrcatA (pcha, pszaSev);
        pcha += lstrlenA (pszaSev);
    }

    // Add the tracetag short name.  Don't do this for ttidError if
    // we already have the severity indicator from above.
    //
    if (ttid && (ttid < g_nTraceTagCount) && (ttid != ttidError))
    {
        static const CHAR c_szaFmtTraceTag [] = " (%s)";

        pcha += wsprintfA (pcha, c_szaFmtTraceTag,
                    g_TraceTags[ttid].szShortName);
    }

    int nBytesCopied;
    int nBytesRemaining;

    // monitor bytes remaining from here on out...
    nBytesRemaining = sizeof(szaBuf) - (pcha - szaBuf);

    // Add the caller's text.
    //
    if (pszaCallerText)
    {
        static const CHAR c_szaFmtCallerText [] = " %s";

    // copy as many bytes as possible
    nBytesCopied = _snprintf (pcha,
                nBytesRemaining,
                c_szaFmtCallerText,
                pszaCallerText);

    // check for overflow
    if (nBytesCopied >= 0)
    {
        // increment pointer
        pcha += nBytesCopied;

        Assert (pcha > szaBuf);
        if ('\n' == *(pcha-1))
        {
        pcha--;
        *pcha = 0;
        }

        // update the number of bytes remaining...
        nBytesRemaining = sizeof(szaBuf) - (pcha - szaBuf);
    }
    else
    {
        // modify count
        nBytesRemaining = 0;
    }

    }

    // Add descriptive error text if this is an error and we can get some.
    //
    if ((nBytesRemaining > 0) && (FAILED(hr) || dwWin32Error))
    {
        BOOL fFacilityWin32 = (FACILITY_WIN32 == HRESULT_FACILITY(hr));

        // dwError will be the error code we pass to FormatMessage.  It may
        // come from hr or dwWin32Error.  Give preference to hr.
        //
        DWORD dwError = 0;

        if (fFacilityWin32)
        {
            dwError = HRESULT_CODE(hr);
        }
        else if (FAILED(hr))
        {
            dwError = hr;
        }
        else
        {
            dwError = dwWin32Error;
        }
        Assert (dwError);

        if (!FIsDebugFlagSet (dfidNoErrorText))
        {
            PSTR pszaErrorText = NULL;
            FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER |
                           FORMAT_MESSAGE_FROM_SYSTEM,
                           NULL, dwError,
                           MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL),
                           (PSTR)&pszaErrorText, 0, NULL);

            if (pszaErrorText)
            {
                // Strip off newline characters.
                //
                PSTR pchText = pszaErrorText;
                while (*pchText && (*pchText != '\r') && (*pchText != '\n'))
                {
                    pchText++;
                }
                *pchText = 0;

                // Add the error text.
                //
                static const CHAR c_szaFmtErrorText [] = " [%s]";

        // copy as many bytes as possible
        nBytesCopied = _snprintf (pcha,
                    nBytesRemaining,
                    c_szaFmtErrorText,
                    pszaErrorText);

        // check for overflow
        if (nBytesCopied >= 0)
        {
            // increment pointer
            pcha += nBytesCopied;

            // update the number of bytes remaining...
            nBytesRemaining = sizeof(szaBuf) - (pcha - szaBuf);
        }
        else
        {
            // modify count
            nBytesRemaining = 0;
        }

        LocalFree (pszaErrorText);
            }
        }

        // Add the Win32 error code.
        //
    if ((nBytesRemaining > 0) && (fFacilityWin32 || dwWin32Error))
        {
            static const CHAR c_szaFmtWin32Error [] = " Win32=%d,0x%08X";

        // copy as many bytes as possible
        nBytesCopied = _snprintf (pcha,
                nBytesRemaining,
                c_szaFmtWin32Error,
                dwError, dwError);

        // check for overflow
        if (nBytesCopied >= 0)
        {
        // increment pointer
        pcha += nBytesCopied;

        // update the number of bytes remaining...
        nBytesRemaining = sizeof(szaBuf) - (pcha - szaBuf);
        }
        else
        {
        // modify count
        nBytesRemaining = 0;
        }
        }
    }

    // Add the HRESULT.
    //
    if ((nBytesRemaining > 0) && (S_OK != hr))
    {
        static const CHAR c_szaFmtHresult [] = " hr=0x%08X";

    // copy as many bytes as possible
    nBytesCopied = _snprintf (pcha,
                nBytesRemaining,
                c_szaFmtHresult,
                hr);

    // check for overflow
    if (nBytesCopied >= 0)
    {
        // increment pointer
        pcha += nBytesCopied;

        // update the number of bytes remaining...
        nBytesRemaining = sizeof(szaBuf) - (pcha - szaBuf);
    }
    else
    {
        // modify count
        nBytesRemaining = 0;
    }
    }

    // Add the file and line.
    //
    if ((nBytesRemaining > 0) && (pszaFile))
    {
        static const CHAR c_szaFmtFileAndLine [] = " File:%s,%d";

    // copy as many bytes as possible
    nBytesCopied = _snprintf (pcha,
                nBytesRemaining,
                c_szaFmtFileAndLine,
                pszaFile, nLine);

    // check for overflow
    if (nBytesCopied >= 0)
    {
        // increment pointer
        pcha += nBytesCopied;

        // update the number of bytes remaining...
        nBytesRemaining = sizeof(szaBuf) - (pcha - szaBuf);
    }
    else
    {
        // modify count
        nBytesRemaining = 0;
    }
    }

    // check for full string
    if (nBytesRemaining > 2)
    {
    lstrcatA (pcha, "\n");
    }
    else
    {
    // add new line & terminate full string
    szaBuf[sizeof(szaBuf) - 2] = '\n';
    szaBuf[sizeof(szaBuf) - 1] = '\0';
    }

    g_Tracing.Trace (ttid, szaBuf);

    // Now that the message is on the debugger, break if we have an error
    // and the debug flag to break on error is set.
    //
    if ((FAILED(hr) || dwWin32Error || (ttidError == ttid)) &&
        !fIgnorable && FIsDebugFlagSet(dfidBreakOnError))
    {
        DebugBreak();
    }
}

//+---------------------------------------------------------------------------
//
//  Function:   TraceErrorFn
//
//  Purpose:    Output debug trace of an HRESULT, allowing an additional
//              caller-defined error string.
//
//  Arguments:
//      sz          []  Caller-defined additional error text
//      hr          []  The error HRESULT.
//
//  Returns:
//
//  Author:     jeffspr   14 Apr 1997
//
//  Notes:
//
VOID
WINAPI
TraceErrorFn (
    PCSTR   pszaFile,
    INT     nLine,
    PCSTR   psza,
    HRESULT hr)
{
    TraceInternal (ttidError, pszaFile, nLine, TI_HRESULT, psza, hr);
}

VOID
WINAPI
TraceResultFn (
    PCSTR  pszaFile,
    INT    nLine,
    PCSTR  psza,
    BOOL   f)
{
    if (!f)
    {
        TraceInternal (ttidError, pszaFile, nLine, TI_WIN32, psza,
                       GetLastError());
    }
}

//+---------------------------------------------------------------------------
//
//  Function:   TraceErrorOptionalFn
//
//  Purpose:    Implements TraceErrorOptional macro
//
//  Arguments:
//      pszaFile [in]     __FILE__ value
//      nLine    [in]     __LINE__ value
//      psza     [in]     String to trace.
//      hr       [in]     HRESULT value to trace.
//      fOpt     [in]     TRUE if error should be treated as optional, FALSE if
//                        ERROR is not optional and should be reported thru
//                        TraceError().
//
//  Returns:    Nothing.
//
//  Author:     danielwe   12 May 1997
//
//  Notes:
//
VOID
WINAPI
TraceErrorOptionalFn (
    PCSTR   pszaFile,
    INT     nLine,
    PCSTR   psza,
    HRESULT hr,
    BOOL    fIgnorable)
{
    DWORD dwFlags = TI_HRESULT;
    if (fIgnorable)
    {
        dwFlags |= TI_IGNORABLE;
    }

    TraceInternal (ttidError, pszaFile, nLine, dwFlags, psza, hr);
}


//+---------------------------------------------------------------------------
//
//  Function:   TraceErrorSkipFn
//
//  Purpose:    Implements TraceErrorOptional macro
//
//  Arguments:
//      pszaFile [in]     __FILE__ value
//      nLine    [in]     __LINE__ value
//      psza     [in]     String to trace.
//      hr       [in]     HRESULT value to trace.
//      c        [in]     count of pass-through Hresults.  if hr is any of these
//                        the error is treated as optional.
//      ...      [in]     list of hresults.
//
//  Returns:    Nothing.
//
//  Author:     sumitc      08 Jan 1998
//
//  Notes:
//
VOID WINAPI
TraceErrorSkipFn (
    PCSTR   pszaFile,
    INT     nLine,
    PCSTR   psza,
    HRESULT hr,
    UINT    c, ...)
{
    va_list valMarker;
    BOOL fIgnorable = FALSE;

    va_start(valMarker, c);
    for (UINT i = 0; i < c; ++i)
    {
        fIgnorable = (va_arg(valMarker, HRESULT) == hr);
        if (fIgnorable)
        {
            break;
        }
    }
    va_end(valMarker);

    DWORD dwFlags = TI_HRESULT;
    if (fIgnorable)
    {
        dwFlags |= TI_IGNORABLE;
    }
    TraceInternal (ttidError, pszaFile, nLine, dwFlags, psza, hr);
}

//+---------------------------------------------------------------------------
//
//  Function:   TraceLastWin32ErrorFn
//
//  Purpose:    Trace the last Win32 error, which we get with GetLastError().
//              Not a whole lot to it.
//
//  Arguments:
//      sz []   Additional error text.
//
//  Returns:
//
//  Author:     jeffspr   14 Apr 1997
//
//  Notes:
//
VOID
WINAPIV
TraceLastWin32ErrorFn (
    PCSTR  pszaFile,
    INT    nLine,
    PCSTR  psza)
{
    TraceInternal (ttidError, pszaFile, nLine, TI_WIN32, psza, GetLastError());
}

//+---------------------------------------------------------------------------
//
//  Function:   TraceHr
//
//  Purpose:    Generic replacement for the TraceErrorOptional, TraceError,
//              and a couple other random functions.
//
//  Arguments:
//      ttid       [] TraceTag to use for the debug output
//      pszaFile   [] Source file to log
//      nLine      [] Line number to log
//      hr         [] Error to log
//      fIgnorable [] Ignore this error? (The optional bit)
//      pszaFmt    [] Format of the vargs
//
//  Returns:
//
//  Author:     jeffspr   10 Oct 1997
//
//  Notes:
//
VOID
WINAPIV
TraceHr (
    TRACETAGID  ttid,
    PCSTR       pszaFile,
    INT         nLine,
    HRESULT     hr,
    BOOL        fIgnorable,
    PCSTR       pszaFmt,
    ...)
{
    // If this tracetag is turned off, don't do anything.
    //
    if (!g_TraceTags[ttid].fOutputDebugString &&
        !g_TraceTags[ttid].fOutputToFile)
    {
        return;
    }

    CHAR szaBuf [DBG_BUFFER_SIZE_SMALL];

    // Build the string from the varg list
    //
    va_list valMarker;
    va_start (valMarker, pszaFmt);

    // copy as many bytes as possible
    _vsnprintf (szaBuf, sizeof(szaBuf), pszaFmt, valMarker);

    // terminate full string
    szaBuf[sizeof(szaBuf) - 1] = '\0';

    va_end (valMarker);

    DWORD dwFlags = TI_HRESULT;
    if (fIgnorable)
    {
        dwFlags |= TI_IGNORABLE;
    }
    TraceInternal (ttid, pszaFile, nLine, dwFlags, szaBuf, hr);
}

//+---------------------------------------------------------------------------
//
//  Function:   TraceTag
//
//  Purpose:    Output a debug trace to one or more trace targets (ODS,
//              File, COM port, etc.). This function determines the targets
//              and performs the actual trace.
//
//  Arguments:
//      ttid    []  TraceTag to use for the debug output
//      pszaFmt []  Format of the vargs.
//
//  Returns:
//
//  Author:     jeffspr   14 Apr 1997
//
//  Notes:
//
VOID
WINAPIV
TraceTag (
    TRACETAGID  ttid,
    PCSTR       pszaFmt,
    ...)
{
    // If this tracetag is turned off, don't do anything.
    //
    if (!g_TraceTags[ttid].fOutputDebugString &&
        !g_TraceTags[ttid].fOutputToFile)
    {
        return;
    }

    CHAR szaBuf [DBG_BUFFER_SIZE_SMALL];

    // Build the string from the varg list
    //
    va_list valMarker;
    va_start (valMarker, pszaFmt);

    // copy as many bytes as possible
    _vsnprintf (szaBuf, sizeof(szaBuf), pszaFmt, valMarker);

    // terminate full string
    szaBuf[sizeof(szaBuf) - 1] = '\0';

    va_end (valMarker);

    TraceInternal (ttid, NULL, 0, 0, szaBuf, 0);
}

//+---------------------------------------------------------------------------
//
//  Member:     CTracing::CTracing
//
//  Purpose:    Constructor for CTracing. Initialize all vars.
//
//  Arguments:
//      (none)
//
//  Returns:
//
//  Author:     jeffspr   23 Jan 1999
//
//  Notes:
//
CTracing::CTracing()
{
    m_fInitialized          = FALSE;    // Has the object been initialized
    m_fAttemptedLogFileOpen = FALSE;    // Already attempted to open log
    m_fDisableLogFile       = FALSE;    // Disable use of file logging?
    m_hLogFile              = NULL;     // Handle for debug output file
    m_szLogFilePath[0]      = '\0';     // File for debug output
    m_fDebugFlagsLoaded     = FALSE;    // Have these been loaded yet.
}

//+---------------------------------------------------------------------------
//
//  Member:     CTracing::HrInit
//
//  Purpose:    Initialize the CTracing object.
//
//  Arguments:
//      (none)
//
//  Returns:    S_OK or valid Win32 HRESULT
//
//  Author:     jeffspr   9 Apr 1997
//
//  Notes:      This should get called from some standard exe initialization
//              point. And make sure to call HrDeinit when you're done, eh?
//
HRESULT CTracing::HrInit()
{
    HRESULT hr  = S_OK;

    AssertSz(!m_fInitialized,
            "CTracing::HrInit -- Let's not go overboard. Already initialized");

    // Temporarily set this so the called functions don't believe that we're
    // uninitialized. At Exit, if we fail, we'll set it back so no-one tries
    // to call these functions when uninitialized.
    //
    m_fInitialized = TRUE;

    // Check for corruptions in the tracing structure. This can't fail, but
    // it will send up asserts all over the place if something is amiss.
    //
    CorruptionCheck();

    // Load the "options" section from the ini file
    //
    hr = HrLoadOptionsFromIniFile();
    if (FAILED(hr))
    {
        goto Exit;
    }

    // Load the DebugFlags section from the ini file.
    //
    hr = HrLoadDebugFlagsFromIniFile();
    if (FAILED(hr))
    {
        goto Exit;
    }

    // Load the tracetag sections from the ini file.
    // Make sure this is called after HrLoadDebugFlagsFromIniFile(),
    // as those options will affect the tracetag sections (we also
    // assert on this)
    //
    hr = HrLoadSectionsFromIniFile();
    if (FAILED(hr))
    {
        goto Exit;
    }

Exit:
    if (FAILED(hr))
    {
        m_fInitialized = FALSE;
    }

    return hr;
}

//+---------------------------------------------------------------------------
//
//  Member:     CTracing::HrUnInit
//
//  Purpose:    Uninitialize the Tracing object
//
//  Arguments:
//      (none)
//
//  Returns:    S_OK or valid Win32 HRESULT
//
//  Author:     jeffspr   12 Apr 1997
//
//  Notes:
//
HRESULT CTracing::HrUnInit()
{
    HRESULT hr  = S_OK;

    // Don't assert on m_fInitialized here, because we allow this to
    // be called even if initialization failed.
    //
    if (m_fInitialized)
    {
        hr = HrWriteDebugFlagsToIniFile();
        if (FAILED(hr))
        {
            // continue on, but I want to know why this is failing.
            AssertSz(FALSE, "Whoa, why can't we write the debug flags?");
        }

        // Close the log file, if there's one open
        //
        if (m_hLogFile)
        {
            CloseHandle(m_hLogFile);
            m_hLogFile = NULL;
            m_fAttemptedLogFileOpen = FALSE;
        }

        if (m_hMutex)
        {
            CloseHandle(m_hMutex);
        }

        // Mark us as being uninitialized.
        //
        m_fInitialized = FALSE;
    }

    return hr;
}

//+---------------------------------------------------------------------------
//
//  Member:     CTracing::HrGetPrivateProfileString
//
//  Purpose:
//
//  Arguments:
//      lpAppName        [] points to section name
//      lpKeyName        [] points to key name
//      lpDefault        [] points to default string
//      lpReturnedString [] points to destination buffer
//      nSize            [] size of destination buffer
//      lpFileName       [] points to initialization filename
//      pcchReturn          return buffer for the old Win32 API return code
//
//
//  Returns:    S_OK or valid Win32 HRESULT
//
//  Author:     jeffspr   12 Apr 1997
//
//  Notes:
//
HRESULT CTracing::HrGetPrivateProfileString(    PCSTR  lpAppName,
                                                PCSTR  lpKeyName,
                                                PCSTR  lpDefault,
                                                PSTR   lpReturnedString,
                                                DWORD  nSize,
                                                PCSTR  lpFileName,
                                                DWORD* pcchReturn
)
{
    HRESULT hr              = S_OK;

    Assert(m_fInitialized);

    // Assert on the known conditions required for this API call
    //
    Assert(lpDefault);
    Assert(lpFileName);

    // Call the Win32 API
    //
    DWORD dwGPPSResult = GetPrivateProfileStringA(
            lpAppName,
            lpKeyName,
            lpDefault,
            lpReturnedString,
            nSize,
            lpFileName);

    // Check to see if we've gotten a string-size error
    if (lpAppName && lpKeyName)
    {
        // If we get back (nSize - 1), then our string buffer wasn't
        // large enough
        //
        if (dwGPPSResult == (nSize - 1))
        {
            hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
            goto Exit;
        }
    }
    else
    {
        // Since either of the app name or key name are NULL, then
        // we're supposed to be receiving a doubly-NULL terminated
        // list of strings. If we're at (nSize - 2), that means
        // our buffer was too small.
        //
        if (dwGPPSResult == (nSize - 2))
        {
            hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
            goto Exit;
        }
    }

Exit:
    *pcchReturn = dwGPPSResult;

    return hr;
}

//+---------------------------------------------------------------------------
//
//  Member:     CTracing::HrLoadOptionsFromIniFile
//
//  Purpose:    Load the options section from the ini file, and set our
//              state accordingly
//
//  Arguments:
//      (none)
//
//  Returns:    S_OK or valid Win32 HRESULT
//
//  Author:     jeffspr   10 Apr 1997
//
//  Notes:
//
HRESULT CTracing::HrLoadOptionsFromIniFile()
{
    HRESULT hr                          = S_OK;
    DWORD   cchReturnBufferSize         = 0;
    CHAR    szaLogFilePath[MAX_PATH+1]  = { 0 };
    DWORD   dwTempPathLength            = 0;

    // Get the explicit log file path, if any. If it doesn't exist, then
    // use the default path, which is the temp file path plus the default
    // trace file name
    //

    // Get the location of the "temporary files" path
    dwTempPathLength = GetTempPathA(MAX_PATH, szaLogFilePath);
    if ((dwTempPathLength == 0) ||
        (dwTempPathLength > MAX_PATH))
    {
        TraceLastWin32Error("GetTempPath failure");

        hr = HrFromLastWin32Error();
        goto Exit;
    }

    // Tack the log file name onto the end.
    //
    wsprintfA(m_szLogFilePath, "%s%s", szaLogFilePath, c_szaTraceLogFileName);

    // This will overwrite the log file path if one exists in the INI file
    //
    hr = HrGetPrivateProfileString(
            c_szaOptions,           // "Options"
            c_szaLogFilePath,       // "LogFilePath
            m_szLogFilePath,        // Default string, already filled
            m_szLogFilePath,        // Return string (same string)
            MAX_PATH+1,
            c_szaDebugIniFileName,
            &cchReturnBufferSize);
    if (FAILED(hr))
    {
        // This should not cause problems with recursive failure, since
        // Traces will work regardless of the state of trace initialization.
        //
        TraceError(
                "GetPrivateProfileString failed on Options::LogFilePath", hr);
        goto Exit;
    }

    // Get the "disable log file option". No return code here.
    m_fDisableLogFile = GetPrivateProfileIntA(
            c_szaOptions,               // "Options"
            c_szaDisableLogFile,        // "DisableLogFile"
            c_iDefaultDisableLogFile,
            c_szaDebugIniFileName);
    if (FAILED(hr))
    {
        TraceError(
                "GetPrivateProfileInt failed on Options::DisableLogFile", hr);
        goto Exit;
    }

Exit:
    return hr;
}

//+---------------------------------------------------------------------------
//
//  Member:     CTracing::HrLoadSectionsFromIniFile
//
//  Purpose:    Load the individual tracetag sections from the ini file, and
//              set our array elements accordingly, defaulting if necessary.
//
//  Arguments:
//      (none)
//
//  Returns:    S_OK or valid Win32 HRESULT
//
//  Author:     jeffspr   10 Apr 1997
//
//  Notes:
//
HRESULT CTracing::HrLoadSectionsFromIniFile()
{
    HRESULT hr = S_OK;

    // Make sure that we've loaded the debug flags first, as they can
    // affect each tracetag section
    //
    Assert(m_fDebugFlagsLoaded);

    // Loop through the array and load the data.
    //
    for (INT nLoop = 0; nLoop < g_nTraceTagCount; nLoop++ )
    {
        // Process the individual lines from the section
        hr = HrProcessTagSection(&(g_TraceTags[nLoop]));
        if (FAILED(hr))
        {
            break;
        }
    }

    return hr;
}

//+---------------------------------------------------------------------------
//
//  Member:     CTracing::HrLoadDebugFlagsFromIniFile
//
//  Purpose:    Load the individual debugflag values from the ini file, and
//              set our array elements accordingly, defaulting if necessary.
//
//  Arguments:
//      (none)
//
//  Returns:    S_OK or valid Win32 HRESULT
//
//  Author:     jeffspr   10 Apr 1997
//
//  Notes:
//
HRESULT CTracing::HrLoadDebugFlagsFromIniFile()
{
    HRESULT hr                  = S_OK;
    INT     nLoop;

    // Loop through the array and load the data.
    //
    for (nLoop = 0; nLoop < g_nDebugFlagCount; nLoop++)
    {
        // Get the enabled file param
        //
        g_DebugFlags[nLoop].dwValue = GetPrivateProfileIntA(
                "DebugFlags",
                g_DebugFlags[nLoop].szShortName,
                FALSE,
                c_szaDebugIniFileName);
    }

    if (SUCCEEDED(hr))
    {
        m_fDebugFlagsLoaded = TRUE;
    }

    return hr;
}

BOOL CTracing::FIniFileExists()
{
    BOOL    fReturn             = TRUE;
    CHAR    szaPath[MAX_PATH+1] = "";
    UINT    uiCharsReturned     = 0;
    HANDLE  hFile               = INVALID_HANDLE_VALUE;

    uiCharsReturned =
            GetWindowsDirectoryA(szaPath, MAX_PATH);
    if ((uiCharsReturned == 0) || (uiCharsReturned > MAX_PATH))
    {
        AssertSz(FALSE, "GetWindowsDirectory failed in CTracing::FIniFileExists");

        fReturn = FALSE;
        goto Exit;
    }

    lstrcatA (szaPath, "\\");
    lstrcatA (szaPath, c_szaDebugIniFileName);

    hFile = CreateFileA(
            szaPath,
            GENERIC_READ,
            FILE_SHARE_READ | FILE_SHARE_WRITE,
            NULL,
            OPEN_EXISTING,
            FILE_ATTRIBUTE_NORMAL,
            NULL);
    if (hFile == INVALID_HANDLE_VALUE)
    {
        DWORD dwLastError = GetLastError();

        if (dwLastError != ERROR_FILE_NOT_FOUND)
        {
            AssertSz(FALSE, "FIniFileExists failed for some reason other than FILE_NOT_FOUND");
        }

        fReturn = FALSE;
        goto Exit;
    }

Exit:
    if (hFile)
    {
        CloseHandle(hFile);
        hFile = NULL;
    }

    return fReturn;
}

HRESULT CTracing::HrWriteDebugFlagsToIniFile()
{
    HRESULT hr = S_OK;

    // First, check to see if the file exists. If it doesn't, then we don't want
    // to write the entries.
    //
    if (FIniFileExists())
    {
        // Loop through the array and write the data.
        //
        for (INT nLoop = 0; nLoop < g_nDebugFlagCount; nLoop++)
        {
            CHAR   szInt[16];      // Sure, it's arbitrary, but it's also OK.

            switch(nLoop)
            {
                // These store a DWORD in its standard form
                //
                case dfidBreakOnHr:
                case dfidBreakOnHrIteration:
                case dfidBreakOnIteration:
                    wsprintfA( szInt, "%d", g_DebugFlags[nLoop].dwValue);
                    break;

                // default are treated as boolean, and stored that way
                //
                default:
                    // !! means it will always be 1 or 0.
                    wsprintfA( szInt, "%d", (!!g_DebugFlags[nLoop].dwValue));
                    break;
            }

            // Write the param to the ini file
            WritePrivateProfileStringA(
                    "DebugFlags",
                    g_DebugFlags[nLoop].szShortName,
                    szInt,
                    c_szaDebugIniFileName);
        }
    }

    // For now, this is always S_OK, since there's nothing above that can
    // fail.
    //
    return hr;
}


//+---------------------------------------------------------------------------
//
//  Member:     CTracing::HrProcessTagSection
//
//  Purpose:    Grab the parameters from the ini file. If they're not
//              available, then use the settings in default. Note - this
//              will always work because ttidDefault will always be the first
//              element. If a [default] section wasn't present, then it will
//              be using the settings that were in the struct initialization,
//              which is also fine.
//
//  Arguments:
//      ptte []     TraceTag element to load
//
//  Returns:
//
//  Author:     jeffspr   15 Apr 1997
//
//  Notes:
//
HRESULT CTracing::HrProcessTagSection(  TraceTagElement *   ptte )
{
    HRESULT hr                      = S_OK;

    AssertSz(m_fInitialized,
            "CTracing::HrProcessTagSection. Class not initialized");

    AssertSz(ptte, "CTracing::HrProcessTagSection -- invalid ptte");

    // Get the output to file param
    //
    ptte->fOutputToFile = GetPrivateProfileIntA(
            ptte->szShortName,
            "OutputToFile",
            ptte->fVerboseOnly ?
                FALSE : g_TraceTags[ttidDefault].fOutputToFile,
            c_szaDebugIniFileName);

    // Get the OutputDebugString param. Require that the error tag
    // always has at least output debug string on.
    //
    if (ptte->ttid == ttidError)
    {
        ptte->fOutputDebugString = TRUE;
    }
    else
    {
        // Load the OutputToDebug
        ptte->fOutputDebugString = GetPrivateProfileIntA(
                ptte->szShortName,
                "OutputToDebug",
                ptte->fVerboseOnly ?
                    FALSE : g_TraceTags[ttidDefault].fOutputDebugString,
                c_szaDebugIniFileName);
    }

    return hr;
}

//+---------------------------------------------------------------------------
//
//  Member:     CTracing::CorruptionCheck
//
//  Purpose:    Validate the tracetag array. Check to see that the
//              shortnames are valid, that the descriptions are valid,
//              and that the tracetag elements are not out of order.
//              Also verify that the correct number of tracetag elements
//              exist.
//
//  Arguments:
//      (none)
//
//  Returns:
//
//  Author:     jeffspr   15 Apr 1997
//
//  Notes:
//      (shaunco) 16 Jul 1997: This is #if'defd out until JVert
//      gives us a fix for the alpha compiler.  It blows up compiling this
//      function in retail.
//
//      (jeffspr) Tough noogies for JVert - I need this code. Hopefully
//      this has been fixed by now.
//
VOID CTracing::CorruptionCheck()
{
    INT nLoop = 0;

    // Validate the tracetag structure
    //
    for (nLoop = 0; nLoop < g_nTraceTagCount; nLoop++)
    {
        // Verify that we're not out of order or missing ttids
        //
        AssertSz(g_TraceTags[nLoop].ttid == nLoop,
                "Invalid ttid in the tracetag structure. Out of order. " \
                "CTracing::CorruptionCheck");
        AssertSz(g_TraceTags[nLoop].ttid < g_nTraceTagCount,
                "Invalid ttid (out of range) in CTracing::CorruptionCheck");

        // Validate the shortname (verify not NULL or empty strings)
        //
        AssertSz(g_TraceTags[nLoop].szShortName,
                "Invalid tracetag short name (NULL) in CTracing::CorruptionCheck");
        AssertSz(g_TraceTags[nLoop].szShortName[0] != 0,
                "Invalid tracetagshort name (empty) in CTracing::CorruptionCheck");

        // Validate the descriptions (verify not NULL or empty strings)
        //
        AssertSz(g_TraceTags[nLoop].szDescription,
                "Invalid tracetagdescription in CTracing::CorruptionCheck");
        AssertSz(g_TraceTags[nLoop].szDescription[0] != 0,
                "Invalid tracetagdescription (empty) in CTracing::CorruptionCheck");
    }

    // Validate the debug flags structure
    //
    for (nLoop = 0; nLoop < g_nDebugFlagCount; nLoop++)
    {
        // Verify that we're not out of order or missing dfids
        //
        AssertSz(g_DebugFlags[nLoop].dfid == nLoop,
                "Invalid dfid in the debugflag structure. Out of order. " \
                "CTracing::CorruptionCheck");
        AssertSz(g_DebugFlags[nLoop].dfid < g_nDebugFlagCount,
                "Invalid dfid (out of range) in CTracing::CorruptionCheck");

        // Validate the shortname (verify not NULL or empty strings)
        //
        AssertSz(g_DebugFlags[nLoop].szShortName,
                "Invalid debug flag short name (NULL) in CTracing::CorruptionCheck");
        AssertSz(g_DebugFlags[nLoop].szShortName[0] != 0,
                "Invalid debug flag short name (empty) in CTracing::CorruptionCheck");

        // Validate the descriptions (verify not NULL or empty strings)
        //
        AssertSz(g_DebugFlags[nLoop].szDescription,
                "Invalid debug flag description in CTracing::CorruptionCheck");
        AssertSz(g_DebugFlags[nLoop].szDescription[0] != 0,
                "Invalid debug flag description (empty) in CTracing::CorruptionCheck");
    }

    return;
}

//+---------------------------------------------------------------------------
//
//  Member:     CTracing::Trace
//
//  Purpose:    The actual trace call that takes care of doing the output
//              to each trace target (file, OutputDebugString, etc.)
//
//  Arguments:
//      ttid      []     The tracetag to use for output
//      pszaTrace []     The trace string itself.
//
//  Returns:
//
//  Author:     jeffspr   12 Apr 1997
//
//  Notes:
//
VOID CTracing::Trace( TraceTagId    ttid,
                      PCSTR         pszaTrace )
{
    // HrInit should have called a corruption checker for the entire trace
    // block, but we'll check again just to make sure.
    //
    AssertSz(g_nTraceTagCount > ttid, "ttid out of range in CTracing::Trace");
    AssertSz(g_TraceTags[ttid].ttid == ttid,
            "TraceTag structure is corrupt in CTracing::Trace");

    // If they want debug string output
    //
    if (g_TraceTags[ttid].fOutputDebugString)
    {
        // Then output the string
        //
        OutputDebugStringA(pszaTrace);
    }

    // If they want file output
    if (g_TraceTags[ttid].fOutputToFile)
    {
        if (!m_hLogFile)
        {
            // Assuming that we haven't already tried to open the file
            // and failed, open it.
            if (!m_fAttemptedLogFileOpen)
            {
                HRESULT hr = HrOpenLogFile();
                if (FAILED(hr))
                {
                    AssertSz(FALSE, "Failed to open log file for tracing. No, "
                             "this isn't a coding error, but hey, figured that "
                             "you'd want to know...");
                }
            }
        }

        // If we were already open, or the open has now succeeded, do the
        // trace
        //
        if (m_hLogFile)
        {
            Assert(pszaTrace);

            // Since pszTrace is guaranteed to be a single-byte trace, we
            // don't need to do the WCHAR multiply on the length, just
            // a char multiply.
            //
            DWORD   dwBytesToWrite  = lstrlenA(pszaTrace) * sizeof(CHAR);
            DWORD   dwBytesWritten  = 0;
            BOOL    fWriteResult    = FALSE;

            WaitForSingleObject(m_hMutex, INFINITE);

            fWriteResult = WriteFile(
                    m_hLogFile,         // handle to file to write to
                    pszaTrace,           // pointer to data to write to file
                    dwBytesToWrite,     // size of trace
                    &dwBytesWritten,    // Bytes actually written.
                    NULL );             // No overlapped

            ReleaseMutex(m_hMutex);

            if (!fWriteResult || (dwBytesToWrite != dwBytesWritten))
            {
                AssertSz(FALSE, "CTracing failure: Can't write to log file."
                         " Can't trace or we'll be recursing on this failure.");
            }
        }
    }
}

HRESULT CTracing::HrOpenLogFile()
{
    HRESULT             hr  = S_OK;
    SECURITY_ATTRIBUTES sa = {0};
    SECURITY_DESCRIPTOR sd = {0};

    InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION);
    SetSecurityDescriptorDacl(&sd, TRUE, NULL, FALSE);
    sa.nLength = sizeof(SECURITY_ATTRIBUTES);
    sa.bInheritHandle = FALSE;
    sa.lpSecurityDescriptor = &sd;

    AssertSz(m_fInitialized,
            "CTracing not initialized in HrOpenLogFile()");
    AssertSz(!m_hLogFile,
            "File already open before call to HrOpenLogFile()");

    // Mark us as having attempted to open the file, so we don't call this
    // function everytime we log, if we can't open it.
    //
    m_fAttemptedLogFileOpen = TRUE;

    m_hMutex = CreateMutexA(&sa, FALSE, c_szaLogFileMutex);
    if (!m_hMutex)
    {
        hr = HrFromLastWin32Error();
        goto Exit;
    }

    WaitForSingleObject(m_hMutex, INFINITE);

    // $$TODO (jeffspr) - Allow flags in the Options section of the ini
    // file specify the create flags and attributes, which would allow
    // us to control the overwriting of log files and/or the write-through
    // properties.
    //

    // Actually open the file, creating if necessary.
    //
    m_hLogFile = CreateFileA(
            m_szLogFilePath,        // Pointer to name of file
            GENERIC_READ | GENERIC_WRITE,          // access (read-write) mode
            FILE_SHARE_READ | FILE_SHARE_WRITE,        // share mode (allow read access)
            NULL,                   // pointer to security attributes
            OPEN_ALWAYS,            // how to create
            FILE_ATTRIBUTE_NORMAL | FILE_FLAG_WRITE_THROUGH,
            NULL);
    if (INVALID_HANDLE_VALUE == m_hLogFile)
    {
        m_hLogFile = NULL;

        hr = HrFromLastWin32Error();
        goto ExitAndRelease;
    }

    SetFilePointer(m_hLogFile, 0, NULL, FILE_END);

ExitAndRelease:
    ReleaseMutex(m_hMutex);

Exit:
    return hr;
}


#endif // ENABLETRACE