/*++

   Copyright    (c)    1994-1999    Microsoft Corporation

   Module  Name :

        msg.cpp

   Abstract:

        Message Functions

   Author:

        Ronald Meijer (ronaldm)
        Sergei Antonov (sergeia)

   Project:

        Internet Services Manager (cluster edition)

   Revision History:
        2/18/2000    sergeia     removed dependency on MFC

--*/

#include "stdafx.h"
#include <lmerr.h>
#include <lmcons.h>
#include "common.h"

extern CComModule _Module;

#ifdef _MT

    //
    // Thread protected stuff
    //
    #define RaiseThreadProtection() \
        do {\
            EnterCriticalSection(&_csSect);\
        } while(0)
    #define LowerThreadProtection() \
        do {\
            LeaveCriticalSection(&_csSect);\
        } while (0)

    static CRITICAL_SECTION _csSect;

#else

    #pragma message("Module is not thread-safe.")

    #define RaiseThreadProtection()
    #define LowerThreadProtection()

#endif // _MT

BOOL
InitErrorFunctionality()
/*++

Routine Description:

    Initialize CError class, and allocate static objects

Arguments:

    None:

Return Value:

    TRUE for success, FALSE for failure

--*/
{
#ifdef _MT
   InitializeCriticalSection(&_csSect);
#endif // _MT

    BOOL fOK = CError::AllocateStatics();

    if (fOK)
    {
//        REGISTER_FACILITY(FACILITY_APPSERVER, "iisui2.dll");
    }

    return fOK;
}



void
TerminateErrorFunctionality()
/*++

Routine Description:

    De-initialize CError class, freeing up static objects

Arguments:

    None

Return Value:

    None

--*/
{
    CError::DeAllocateStatics();

#ifdef _MT
    DeleteCriticalSection(&_csSect);
#endif // _MT
}

//
// Static Initialization:
//
// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

const TCHAR g_cszNull[] = _T("(Null)");
const TCHAR CError::s_chEscape = _T('%');        // Error text escape
const TCHAR CError::s_chEscText = _T('h');       // Escape code for text
const TCHAR CError::s_chEscNumber = _T('H');     // Escape code for error code
LPCTSTR CError::s_cszLMDLL = _T("netmsg.dll");   // LM Error File
LPCTSTR CError::s_cszWSDLL = _T("iisui2.dll");   // Winsock error file
LPCTSTR CError::s_cszFacility[] = 
{
    /* FACILITY_NULL        */ NULL,
    /* FACILITY_RPC         */ NULL,
    /* FACILITY_DISPATCH    */ NULL,            
    /* FACILITY_STORAGE     */ NULL,
    /* FACILITY_ITF         */ NULL,
    /* FACILITY_DS          */ NULL,
    /* 6                    */ NULL,
    /* FACILITY_WIN32       */ NULL,
    /* FACILITY_WINDOWS     */ NULL,
    /* FACILITY_SSPI        */ NULL,
    /* FACILITY_CONTROL     */ NULL,
    /* FACILITY_CERT        */ NULL,
    /* FACILITY_INTERNET    */ _T("metadata.dll"),
    /* FACILITY_MEDIASERVER */ NULL,
    /* FACILITY_MSMQ        */ NULL,
    /* FACILITY_SETUPAPI    */ NULL,
    /* FACILITY_SCARD       */ NULL,
    /* 17 (MTX)             */ _T("iisui2.dll"),
};

HRESULT CError::s_cdwMinLMErr = NERR_BASE; 
HRESULT CError::s_cdwMaxLMErr = MAX_NERR;
HRESULT CError::s_cdwMinWSErr = WSABASEERR;    
HRESULT CError::s_cdwMaxWSErr = WSABASEERR + 2000;    
DWORD   CError::s_cdwFacilities = (sizeof(CError::s_cszFacility)\
    / sizeof(CError::s_cszFacility[0]));

//
// Allocated objects
//
CString * CError::s_pstrDefError;
CString * CError::s_pstrDefSuccs;
CFacilityMap * CError::s_pmapFacilities;
BOOL CError::s_fAllocated = FALSE;



/* protected */
/* static */
BOOL
CError::AllocateStatics()
/*++

Routine Description:

    Allocate static objects

Arguments:

    None

Return Value:

    TRUE for successfull allocation, FALSE otherwise

--*/
{
    RaiseThreadProtection();

    if (!AreStaticsAllocated())
    {
        try
        {
            CError::s_pstrDefError   = new CString;
            CError::s_pstrDefSuccs   = new CString(_T("0x%08lx"));
            CError::s_pmapFacilities = new CFacilityMap;
            s_fAllocated = TRUE;

            if (!CError::s_pstrDefError->LoadString(_Module.GetResourceInstance(), IDS_NO_MESSAGE))
            {
                //
                // Just in case we didn't load this message from the resources
                //
                ASSERT_MSG("Unable to load resource message");
                *s_pstrDefError = _T("Error Code: 0x%08lx");
            }
        }
        catch(std::bad_alloc)
        {
            ASSERT_MSG("Initialization Failed");
        }
    }

    LowerThreadProtection();

    return AreStaticsAllocated();
}



/* protected */
/* static */
void
CError::DeAllocateStatics()
/*++

Routine Description:

    Clean up allocations

Arguments:

    N/A

Return Value:

    N/A

--*/
{

    RaiseThreadProtection();

    if (AreStaticsAllocated())
    {
        SAFE_DELETE(CError::s_pstrDefError);
        SAFE_DELETE(CError::s_pstrDefSuccs);
        SAFE_DELETE(CError::s_pmapFacilities);

        s_fAllocated = FALSE;
    }

    LowerThreadProtection();
}


/*static*/ BOOL 
CError::AreStaticsAllocated() 
{ 
   return s_fAllocated; 
}

/* static */
HRESULT 
CError::CvtToInternalFormat(
    IN HRESULT hrCode
    )
/*++

Routine Description:

    Convert WIN32 or HRESULT code to internal (HRESULT) format.

Arguments:

    DWORD dwCode        Error code

Return Value:

    HRESULT

Notes:

    HRESULTS are left as is.  Lanman and Winsock errors are converted
    to HRESULTS using private facility codes.

--*/
{
    if (IS_HRESULT(hrCode))
    {
        return hrCode;
    }

    if(hrCode >= s_cdwMinLMErr && hrCode <= s_cdwMaxLMErr)
    {
        return MAKE_HRESULT(SEVERITY_ERROR, FACILITY_LANMAN, (DWORD)hrCode);
    }

    if (hrCode >= s_cdwMinWSErr && hrCode <= s_cdwMaxWSErr)
    {
        return MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WINSOCK, (DWORD)hrCode);
    }

    return HResult(hrCode);    
}



/* static */ 
void 
CError::RegisterFacility(
    IN DWORD dwFacility,
    IN LPCSTR lpDLL         OPTIONAL
    )
/*++

Routine Description:

    Register a DLL for a given facility code.  Use NULL to unregister
    the DLL name.

Arguments:

    DWORD dwFacility : Facility code
    LPCSTR lpDLL     : DLL Name.

Return Value:

    None

--*/
{
    RaiseThreadProtection();

    ASSERT(AreStaticsAllocated());

    if (lpDLL == NULL)
    {
        //
        // Remove the facility
        //
        s_pmapFacilities->erase(dwFacility);
    }
    else
    {
        CString str(lpDLL);

        //
        // Register facility
        //
        s_pmapFacilities->insert(s_pmapFacilities->begin(), 
           CFacilityMap::value_type(dwFacility, str));
    }

    LowerThreadProtection();
}


        
/* static */
LPCTSTR 
CError::FindFacility(
    IN DWORD dwFacility
    )
/*++

Routine Description:

    Determine if a DLL name has been registered for the given facility
    code.

Arguments:

    DWORD dwFacility        : Facility code

Return Value:

    Returns the DLL name, or NULL.

--*/
{
    RaiseThreadProtection();

    ASSERT(AreStaticsAllocated());

    LPCTSTR pRes = NULL;
    CFacilityMap::iterator it = s_pmapFacilities->find(dwFacility);
    if (it != s_pmapFacilities->end())
    {
        pRes = (*it).second;
    }

    LowerThreadProtection();

    return pRes;
}



CError::~CError()
/*++

Routine Description:

    Destructor

Arguments:

    None

Return Value:

    N/A

--*/
{
}



const CError & 
CError::Construct(
    IN HRESULT hr
    )
/*++

Routine Description:

    construct with new value.

Arguments:
    
    HRESULT hr : New value, either an HRESULT or a WIN32
                 error code.

Return Value:

    Reference to current object

--*/
{
    ASSERT(AreStaticsAllocated());

    m_hrCode = CvtToInternalFormat(hr);

    return *this;
}



const CError & 
CError::Construct(
    IN const CError & err
    )
/*++

Routine Description:

    Assign new value.

Arguments:
    
    CError & err    : Error code

Return Value:

    Reference to current object

--*/
{
    ASSERT(AreStaticsAllocated());

    m_hrCode = err.m_hrCode;

    return *this;
}



int
CError::MessageBox(
    IN UINT    nType,
    IN UINT    nHelpContext OPTIONAL
    ) const
/*++

Routine Description:

    Display error message in a message box

Arguments:

    HRESULT hrCode       : HRESULT error code
    UINT    nType        : See AfxMessageBox for documentation
    UINT    nHelpContext : See AfxMessageBox for documentation

Return Value:

    AfxMessageBox return code

--*/
{
    CString strMsg;
    TextFromHRESULT(strMsg);
    return ::MessageBox(::GetAncestor(::GetFocus(), GA_ROOT), strMsg, NULL, nType);
}




//
// Extend CString just to get at FormatV publically
//
class CStringEx : public CString
{
public:
    void FormatV(LPCTSTR lpszFormat, va_list argList)
    {
        CString::FormatV(lpszFormat, argList);
    }
};



int 
CError::MessageBoxFormat(
    IN HINSTANCE hInst,
    IN UINT nFmt,
    IN UINT nType,
    IN UINT nHelpContext,
    ...
    ) const
/*++

Routine Description:

    Display formatted error message in messagebox.  The format
    string (given as a resource ID) is a normal printf-style
    string, with the additional parameter of %h, which takes
    the text equivalent of the error message, or %H, which takes
    the error return code itself.

Arguments:

    UINT    nFmt         : Resource format
    UINT    nType        : See AfxMessageBox for documentation
    UINT    nHelpContext : See AfxMessageBox for documentation
    ...                    More as needed for sprintf

Return Value:

    AfxMessageBox return code
    
--*/
{
    CString strFmt;
    CStringEx strMsg;

    strFmt.LoadString(hInst, nFmt);

    //
    // First expand the error
    //
    TextFromHRESULTExpand(strFmt);

    va_list marker;
    va_start(marker, nHelpContext);
    strMsg.FormatV(strFmt, marker);
    va_end(marker);

    return ::MessageBox(::GetFocus(), strMsg, NULL, nType);
}


BOOL 
CError::MessageBoxOnFailure(
    IN UINT nType,
    IN UINT nHelpContext    OPTIONAL
    ) const
/*++

Routine Description:

    Display message box if the current error is a failure
    condition, else do nothing

Arguments:

    UINT    nType        : See AfxMessageBox for documentation
    UINT    nHelpContext : See AfxMessageBox for documentation

Return Value:

    TRUE if a messagebox was shown, FALSE otherwise

--*/
{
    if (Failed())
    {
        MessageBox(nType, nHelpContext);
        return TRUE;
    }

    return FALSE;
}



BOOL 
CError::HasOverride(
    OUT UINT * pnMessage        OPTIONAL
    ) const
/*++

Routine Description:

    Check to see if a given HRESULT has an override

Arguments:

    HRESULT hrCode              : HRESULT to check for
    UINT * pnMessage            : Optionally returns the override

Return Value:

    TRUE if there is an override, FALSE if there is not.

--*/
{
   ASSERT(AreStaticsAllocated());

   HRESULT hrCode = CvtToInternalFormat(m_hrCode);
   if (!mapOverrides.empty())
   {
       COverridesMap::const_iterator it = mapOverrides.find(hrCode);
       if (it != mapOverrides.end())
       {
           if (pnMessage != NULL)
              *pnMessage = (*it).second;
           return TRUE;
       }
   }
   return FALSE;
}



UINT
CError::AddOverride(
    IN HRESULT    hrCode,
    IN UINT       nMessage
    )
/*++

Routine Description:

    Add an override for a specific HRESULT.

Arguments:

    HRESULT    hrCode       : HRESULT to override
    UINT       nMessage     : New message, or -1 to remove override

Return Value:

    The previous override, or -1

--*/
{
    ASSERT(AreStaticsAllocated());

    UINT nPrev;
    hrCode = CvtToInternalFormat(hrCode);

    //
    // Fetch the current override
    //
    COverridesMap::iterator it = mapOverrides.find(hrCode);
    nPrev = (it == mapOverrides.end()) ? REMOVE_OVERRIDE : (*it).second;

    if (nMessage == REMOVE_OVERRIDE)
    {
        //
        // Remove the override
        //
        mapOverrides.erase(hrCode);
    }
    else
    {
        //
        // Set new override
        //
        mapOverrides[hrCode] = nMessage;
    }

    return nPrev;
}



void
CError::RemoveAllOverrides()
/*++

Routine Description:

    Remove all overrides

Arguments:

    None

Return Value:

    None

--*/
{
    ASSERT(AreStaticsAllocated());
    mapOverrides.clear();
}



HRESULT
CError::TextFromHRESULT(
    OUT LPTSTR  szBuffer,
    OUT DWORD   cchBuffer
    ) const
/*++

Routine Description:

    Get text from the given HRESULT.  Based on the range that the HRESULT
    falls in and the facility code, find the location of the message,
    and fetch it.

Arguments:

    HRESULT hrCode      HRESULT or (DWORD WIN32 error) whose message to get
    LPTSTR  szBuffer    Buffer to load message text into
    DWORD   cchBuffer   Size of buffer in characters.

Return Value:

    HRESULT error code depending on whether the message was
    found.  If the message was not found, some generic message
    is synthesized in the buffer if a buffer is provided.

    ERROR_FILE_NOT_FOUND        No message found
    ERROR_INSUFFICIENT_BUFFER   Buffer is a NULL pointer or too small

--*/
{
    HRESULT hrReturn = ERROR_SUCCESS;

    //
    // First check to see if this message is overridden
    //
    UINT nID;
    HRESULT hrCode = m_hrCode;

    if (HasOverride(&nID))
    {
        //
        // Message overridden.  Load replacement message
        // instead.
        //
        BOOL fSuccess;

        //
        // Attempt to load from calling process first
        //
        if (!(fSuccess = ::LoadString(
            ::GetModuleHandle(NULL), 
            nID, 
            szBuffer, 
            cchBuffer
            )))
        {
            //
            // Try this dll
            //
            fSuccess = ::LoadString(
                _Module.GetResourceInstance(), 
                nID, 
                szBuffer, 
                cchBuffer
                );
        }

        if (fSuccess)
        {
            //
            // Everything ok
            //
            return hrReturn;
        }

        //
        // Message didn't exist, skip the override, and 
        // load as normal.
        //
        TRACE("Couldn't load %d\n", nID);
        ASSERT_MSG("Attempted override failed");
    }

    LPCTSTR lpDll    = NULL;
    HINSTANCE hDll   = NULL;
    DWORD dwFacility = HRESULT_FACILITY(hrCode);
    DWORD dwSeverity = HRESULT_SEVERITY(hrCode);
    DWORD dwCode     = HRESULT_CODE(hrCode);
    BOOL  fSuccess   = Succeeded(hrCode);

    //
    // Strip off meaningless internal facility codes
    //
    if (dwFacility == FACILITY_LANMAN || dwFacility == FACILITY_WINSOCK)
    {
        dwFacility = FACILITY_NULL;
        hrCode   = (HRESULT)dwCode;
    }

    DWORD dwFlags = FORMAT_MESSAGE_IGNORE_INSERTS | 
                    FORMAT_MESSAGE_MAX_WIDTH_MASK;

    //
    // Since we allow both HRESULTS and WIN32 codes to be
    // used here, we can't rely on the private FACILITY code 
    // for lanman and winsock.
    //
    if(hrCode >= s_cdwMinLMErr && hrCode <= s_cdwMaxLMErr)
    {
        //
        // Lanman error
        //
        lpDll = s_cszLMDLL;
    }
    else if (hrCode >= s_cdwMinWSErr && hrCode <= s_cdwMaxWSErr)
    {
        //
        // Winsock error
        //
        lpDll = s_cszWSDLL;
    }
    else
    {
        //
        // Attempt to determine message location from facility code.
        // Check for registered facility first.
        //
        lpDll = FindFacility(dwFacility);

        if (lpDll == NULL)
        {
            if (dwFacility < s_cdwFacilities)
            {
                lpDll = s_cszFacility[dwFacility];
            }
            else
            {
                ASSERT_MSG("Bogus FACILITY code encountered.");
                lpDll = NULL;
            }
        }
    }

    do
    {
        if (szBuffer == NULL || cchBuffer <= 0)
        {
            hrReturn = HResult(ERROR_INSUFFICIENT_BUFFER);
            break;
        }

        if (lpDll)
        {
            //
            // Load message file
            //
            hDll = ::LoadLibraryEx(
                lpDll,
                NULL,
                LOAD_LIBRARY_AS_DATAFILE
                );

            if (hDll == NULL)
            {
                hrReturn = ::GetLastHRESULT();
                break;
            }

            dwFlags |= FORMAT_MESSAGE_FROM_HMODULE;
        }
        else
        {
            dwFlags |= FORMAT_MESSAGE_FROM_SYSTEM;
        }

        DWORD dwResult = 0L;
        DWORD dwID = hrCode;
        HINSTANCE hSource = hDll;

        while(!dwResult)
        {
            dwResult = ::FormatMessage(
                dwFlags,
                (LPVOID)hSource,
                dwID,
                0,
                szBuffer,
                cchBuffer,
                NULL
                );

            if (dwResult > 0)
            {
                //
                // Successfully got a message
                //
                hrReturn = ERROR_SUCCESS;
                break;
            } 

            hrReturn = ::GetLastHRESULT();
    
            if (dwID != dwCode && !fSuccess)
            {
                //
                // Try the SCODE portion of the error from win32
                // if this is an error message
                //
                dwID = dwCode;
                hSource = NULL;
                continue;
            }

            //
            // Failed to obtain a message
            //
            hrReturn = HResult(ERROR_FILE_NOT_FOUND);
            break;
        }
    }
    while(FALSE);

    if(hDll != NULL)
    {
        ::FreeLibrary(hDll);
    }

    if (Failed(hrReturn))
    {
        //
        // Unable to find the message, synthesize something with
        // the code in it if there's room (+8 for the number)
        //
        CString & strMsg = (fSuccess ? *s_pstrDefSuccs : *s_pstrDefError);

        if (cchBuffer > (DWORD)strMsg.GetLength() + 8)
        {
            TRACE("Substituting default message for %d\n", (DWORD)m_hrCode);
            wsprintf(szBuffer, (LPCTSTR)strMsg, m_hrCode);
        }
        else
        {
            //
            // Not enough room for message code
            //
            ASSERT_MSG("Buffer too small for default message -- left blank");
            *szBuffer = _T('\0');
        }
    }

    return hrReturn;
}



HRESULT 
CError::TextFromHRESULT(
    OUT CString & strBuffer
    ) const
/*++

Routine Description:

    Similar to the function above, but use a CString

Arguments:

    HRESULT hrCode         HRESULT or (DWORD WIN32 error) whose message to get
    CString & strBuffer    Buffer to load message text into

Return Value:

    HRESULT error code depending on whether the message was
    found.  If the message was not found, some generic message
    is synthesized in the buffer if a buffer is provided.

    ERROR_FILE_NOT_FOUND   No message found

--*/
{
   DWORD cchBuffer = 255;
   HRESULT hr = S_OK;
   LPTSTR p = NULL;

   for (;;)
   {
      p = strBuffer.get_allocator().allocate(cchBuffer, p);
      if (p == NULL)
      {
         return HResult(ERROR_NOT_ENOUGH_MEMORY);
      }

      hr = TextFromHRESULT(p, cchBuffer - 1);
      if (Win32Error(hr) != ERROR_INSUFFICIENT_BUFFER)
      {
         //
         // Done!
         //
         strBuffer.assign(p);
         break;
      }

      //
      // Insufficient buffer, enlarge and try again
      //
      cchBuffer *= 2;
   }
   if (p != NULL)
   {
      strBuffer.get_allocator().deallocate(p, cchBuffer);
   }
   return hr;
}



BOOL
CError::ExpandEscapeCode(
    IN  LPTSTR szBuffer,
    IN  DWORD cchBuffer,
    OUT IN LPTSTR & lp,
    IN  CString & strReplacement,
    OUT HRESULT & hr
    ) const
/*++

Routine Description:

    Expand escape code

Arguments:

    LPTSTR szBuffer             Buffer
    DWORD cchBuffer             Size of buffer
    LPTSTR & lp                 Pointer to escape code
    CString & strReplacement    Message to replace the escape code
    HRESULT & hr                Returns HRESULT in case of failure

Return Value:

    TRUE if the replacement was successful, FALSE otherwise.
    In the case of failure, hr will return an HRESULT.
    In the case of success, lp will be advanced past the
    replacement string.

--*/
{
    //
    // Make sure there's room (account for terminating NULL)
    // Free up 2 spaces for the escape code.
    //
    int cchFmt = lstrlen(szBuffer) - 2;
    int cchReplacement = strReplacement.GetLength();
    int cchRemainder = lstrlen(lp + 2);

    if ((DWORD)(cchReplacement + cchFmt) < cchBuffer)
    {
        //
        // Put it in
        //
        MoveMemory(
            lp + cchReplacement,
            lp + 2,
            (cchRemainder + 1) * sizeof(TCHAR)
            );
        CopyMemory(lp, strReplacement, cchReplacement * sizeof(TCHAR));
        lp += cchReplacement;
        
        return TRUE;
    }

    hr = HResult(ERROR_INSUFFICIENT_BUFFER);

    return FALSE;
}



LPCTSTR 
CError::TextFromHRESULTExpand(
    OUT LPTSTR  szBuffer,
    OUT DWORD   cchBuffer,
    OUT HRESULT * phResult  OPTIONAL
    ) const
/*++

Routine Description:

    Expand %h/%H strings in szBuffer to text from HRESULT,
    or error code respectively within the limits of szBuffer.

Arguments:

    LPTSTR  szBuffer    Buffer to load message text into
    DWORD   cchBuffer   Buffer size in characters
    HRESULT * phResult  Optional return code

Return Value:

    Pointer to string.

--*/
{
    HRESULT hr = S_OK;

    if (szBuffer == NULL || cchBuffer <= 0)
    {
        hr = HResult(ERROR_INSUFFICIENT_BUFFER);
    }
    else
    {
        //
        // Look for the escape sequence
        //
        int cReplacements = 0;
        CString strMessage;
        LPTSTR lp = szBuffer;

        while (*lp)
        {
            if (*lp == s_chEscape)
            {
                switch(*(lp + 1))
                {
                case s_chEscText:
                    //
                    // Replace escape code with text message
                    //
                    hr = TextFromHRESULT(strMessage);

                    if (ExpandEscapeCode(
                        szBuffer,
                        cchBuffer,
                        lp,
                        strMessage,
                        hr
                        ))
                    {
                        ++cReplacements;
                    }
                    break;

                case s_chEscNumber:
                    //
                    // Replace escape code with numeric error code
                    //
                    strMessage.Format(_T("0x%08x"), m_hrCode);

                    if (ExpandEscapeCode(
                        szBuffer,
                        cchBuffer,
                        lp,
                        strMessage,
                        hr
                        ))
                    {
                        ++cReplacements;
                    }
                    break;

                default:
                    //
                    // Regular printf-style escape sequence.
                    //
                    break;
                }
            }

            ++lp;
        }

        if (!cReplacements)
        {
            //
            // Got to the end without finding any escape codes.
            //
            hr = HResult(ERROR_INVALID_PARAMETER);
        }
    }

    if (phResult)
    {
        *phResult = hr;
    }

    return szBuffer;
}



LPCTSTR 
CError::TextFromHRESULTExpand(
    OUT CString & strBuffer
    ) const
/*++

Routine Description:

    Expand %h string in strBuffer to text from HRESULT

Arguments:

    CString & strBuffer Buffer to load message text into

Return Value:

    Pointer to string.

--*/
{
   DWORD cchBuffer = strBuffer.GetLength() + 1024;
   LPTSTR p = NULL;
   for (;;)
   {
      p = strBuffer.get_allocator().allocate(cchBuffer, p);

      if (p != NULL)
      {
         HRESULT hr;

         TextFromHRESULTExpand(p, cchBuffer - 1, &hr);

         if (Win32Error(hr) != ERROR_INSUFFICIENT_BUFFER)
         {
            //
            // Done!
            //
            strBuffer.assign(p);
            break;
         }

         //
         // Insufficient buffer, enlarge and try again
         //
         cchBuffer *= 2;
      }
   }
   if (p != NULL)
   {
      strBuffer.get_allocator().deallocate(p, cchBuffer);
   }

   return strBuffer;
}