/****************************************************************************\

    LOG.C / OPK Library (OPKLIB.LIB)

    Microsoft Confidential
    Copyright (c) Microsoft Corporation 1999
    All rights reserved

    Logging API source file for use in the OPK tools.

    08/00 - Jason Cohen (JCOHEN)
        Added this new source file to Whistler for common logging
        functionality across all the OPK tools.

\****************************************************************************/


//
// Include File(s):
//

#include <pch.h>
#include <stdio.h>


//
// Defines
//

#ifdef CHR_NEWLINE
#undef CHR_NEWLINE
#endif // CHR_NEWLINE
#define CHR_NEWLINE              _T('\n')


#ifdef CHR_CR
#undef CHR_CR
#endif // CHR_CR
#define CHR_CR                   _T('\r')


// Global logging info handle.
//
PLOG_INFO g_pLogInfo = NULL;



//
// Exported Function(s):
//

INT LogFileLst(LPCTSTR lpFileName, LPTSTR lpFormat, va_list lpArgs)
{
    INT     iChars  = 0;
    HANDLE  hFile;

    // Make sure we have the required params and can create the file.
    //
    if ( ( lpFileName && lpFileName[0] && lpFormat ) &&
         ( hFile = _tfopen(lpFileName, _T("a")) ) )
    {
        // Print the debug message to the end of the file.
        //
        iChars = _vftprintf(hFile, lpFormat, lpArgs);

        // Close the handle to the file.
        //
        fclose(hFile);
    }

    // Return the number of chars written from the printf call.
    //
    return iChars;
}

INT LogFileStr(LPCTSTR lpFileName, LPTSTR lpFormat, ...)
{
    va_list lpArgs;

    // Initialize the lpArgs parameter with va_start().
    //
    va_start(lpArgs, lpFormat);

    // Return the return value of the MessageBox() call.  If there was a memory
    // error, 0 will be returned.  This is all 
    //
    return LogFileLst(lpFileName, lpFormat, lpArgs);
}

INT LogFile(LPCTSTR lpFileName, UINT uFormat, ...)
{
    va_list lpArgs;
    INT     nReturn;
    LPTSTR  lpFormat = NULL;

    // Initialize the lpArgs parameter with va_start().
    //
    va_start(lpArgs, uFormat);

    // Get the format and caption strings from the resource.
    //
    if ( uFormat )
        lpFormat = AllocateString(NULL, uFormat);

    // Return the return value of the MessageBox() call.  If there was a memory
    // error, 0 will be returned.
    //
    nReturn = LogFileLst(lpFileName, lpFormat, lpArgs);

    // Free the format and caption strings.
    //
    FREE(lpFormat);

    // Return the value saved from the previous function call.
    //
    return nReturn;
}

//
// Function Implementations
//


/*++

Routine Description:

    This routine ckecks the specified ini file for settings for logging.  Logging 
    is enabled by default if nothing is specified in the ini file.  
    Disables logging by setting pLogInfo->szLogFile = NULL.
    
Arguments:

    None.

Return Value:

    None.

--*/

BOOL OpkInitLogging(LPTSTR lpszIniPath, LPTSTR lpAppName)
{
    TCHAR     szScratch[MAX_PATH] = NULLSTR;
    LPTSTR    lpszScratch;
    BOOL      bWinbom = ( lpszIniPath && *lpszIniPath );
    PLOG_INFO pLogInfo = NULL;

    pLogInfo = MALLOC(sizeof(LOG_INFO));

    if ( NULL == pLogInfo )
    {
        return FALSE;
    }
    
    if ( lpAppName )
    {
        pLogInfo->lpAppName = MALLOC((lstrlen(lpAppName) + 1) * sizeof(TCHAR));
        lstrcpy(pLogInfo->lpAppName, lpAppName);
    }
    
    // First check if logging is disabled in the WinBOM.
    //
    if ( ( bWinbom ) &&
         ( GetPrivateProfileString(INI_SEC_LOGGING, INI_KEY_LOGGING, _T("YES"), szScratch, AS(szScratch), lpszIniPath) ) &&
         ( LSTRCMPI(szScratch, INI_VAL_NO) == 0 ) )
    {
        // FREE macro sets pLogInfo to NULL.
        FREE(pLogInfo->lpAppName);
        FREE(pLogInfo);
    }
    else
    {
        // All these checks can only be done if we have a winbom.
        //
        if ( bWinbom )
        {
            // Check for quiet mode.  If we are in quiet mode don't display any MessageBoxes. 
            //
            if ( ( GetPrivateProfileString(INI_SEC_LOGGING, INI_KEY_QUIET, NULLSTR, szScratch, AS(szScratch), lpszIniPath) ) &&
                 ( 0 == LSTRCMPI(szScratch, INI_VAL_YES) )
               )
            {
                SET_FLAG(pLogInfo->dwLogFlags, LOG_FLAG_QUIET_MODE);
            }

/*            // See if they want to turn on perf logging.
            //
            szScratch[0] = NULLCHR;
            if ( ( GetPrivateProfileString(WBOM_FACTORY_SECTION, INI_KEY_WBOM_LOGPERF, NULLSTR, szScratch, AS(szScratch), lpszIniPath) ) &&
                 ( 0 == lstrcmpi(szScratch, WBOM_YES) ) )
            {
                SET_FLAG(pLogInfo->dwLogFlags, FLAG_LOG_PERF);
            }
*/      
            // Set the logging level.
            //
            pLogInfo->dwLogLevel = (DWORD) GetPrivateProfileInt(INI_SEC_LOGGING, INI_KEY_LOGLEVEL, (DWORD) pLogInfo->dwLogLevel, lpszIniPath);
        }

#ifndef DBG
        if ( pLogInfo->dwLogLevel >= LOG_DEBUG )
            pLogInfo->dwLogLevel = LOG_DEBUG - 1;
#endif
        
        // Check to see if they have a custom log file they want to use.
        //
        if ( ( bWinbom ) &&
             ( lpszScratch = IniGetExpand(lpszIniPath, INI_SEC_LOGGING, INI_KEY_LOGFILE, NULL) ) )
        {
            TCHAR   szFullPath[MAX_PATH]    = NULLSTR;
            LPTSTR  lpFind                  = NULL;

            // Turn the ini key into a full path.
            //
            
            // NTRAID#NTBUG9-551266-2002/03/27-acosma,robertko - Buffer overrun possibility.
            //
            lstrcpy(pLogInfo->szLogFile, lpszScratch);
            if (GetFullPathName(pLogInfo->szLogFile, AS(szFullPath), szFullPath, &lpFind) && szFullPath[0] && lpFind)
            {
                // Copy the full path into the global.
                //
                lstrcpyn(pLogInfo->szLogFile, szFullPath, AS(pLogInfo->szLogFile));

                // Chop off the file part so we can create the
                // path if it doesn't exist.
                //
                *lpFind = NULLCHR;

                // If the directory cannot be created or doesn't exist turn off logging.
                //
                if (!CreatePath(szFullPath))
                    pLogInfo->szLogFile[0] = NULLCHR;
            }

            // Free the original path buffer from the ini file.
            //
            FREE(lpszScratch);
        }
        else  // default case
        {
            // Create it in the current directory.
            //
            GetCurrentDirectory(AS(pLogInfo->szLogFile), pLogInfo->szLogFile);
            
            // NTRAID#NTBUG9-551266-2002/03/27-acosma - Buffer overrun possibility.
            //
            AddPath(pLogInfo->szLogFile, _T("logfile.txt"));
        }

        // Check to see if we have write access to the logfile. If we don't turn off logging.
        // If we're running in WinPE we'll call this function again once the drive becomes
        // writable.
        //
        // Write an FFFE header to the file to identify this as a Unicode text file.
        //
        if ( pLogInfo->szLogFile[0] )
        {
            HANDLE hFile;
            DWORD dwWritten = 0;
            WCHAR cHeader =  0xFEFF;
     
            SetLastError(ERROR_SUCCESS);
   
            if ( INVALID_HANDLE_VALUE != (hFile = CreateFile(pLogInfo->szLogFile, GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL)))
            {
                if ( ERROR_ALREADY_EXISTS != GetLastError() )
                    WriteFile(hFile, &cHeader, sizeof(cHeader), &dwWritten, NULL);
                CloseHandle(hFile);
            }
            else
            {   // There was a problem opening the file.  Most of the time this means that the media is not writable.
                // Disable logging in that case. Macro sets variable to NULL.
                //
                FREE(pLogInfo->lpAppName);
                FREE(pLogInfo);
            }
        }
    }

    g_pLogInfo = pLogInfo;
    return TRUE;
}

// NTRAID#NTBUG9-551266-2002/03/27-acosma,robertko - Buffer overrun possibilities in this function. Use strsafe functions.
//
DWORD OpkLogFileLst(PLOG_INFO pLogInfo, DWORD dwLogOpt, LPTSTR lpFormat, va_list lpArgs)
{
    LPTSTR lpPreOut             = NULL;
    LPTSTR lpOut                = NULL;
    DWORD  dwSize               = 1024;
    TCHAR  szPreLog[MAX_PATH]   = NULLSTR;
    HANDLE hFile;
    DWORD  dwWritten            = 0;
    DWORD  cbAppName            = 0;
    DWORD  dwLogLevel           = (DWORD) (dwLogOpt & LOG_LEVEL_MASK);
    
    
    if ( ( dwLogLevel <= pLogInfo->dwLogLevel) && lpFormat )
    {    
        // Build the output string.
        //
        if ( pLogInfo->lpAppName )
        {
            // Create the prefix string
            //
            lstrcpy(szPreLog, pLogInfo->lpAppName);
            lstrcat(szPreLog, _T("::"));
        }
        
        // This is for skipping the App Name prefix when printing to the log file
        //
        cbAppName = lstrlen(szPreLog);
        
        if ( GET_FLAG(dwLogOpt, LOG_ERR) )
        {
            if ( 0 == dwLogLevel )
                lstrcat(szPreLog, _T("ERROR: "));
            else
                swprintf(szPreLog + cbAppName, _T("WARN%d: "), dwLogLevel);
        }
      
        if ( GET_FLAG(dwLogOpt, LOG_TIME) )
        {
            TCHAR  szTime[100] = NULLSTR;

            GetTimeFormat(LOCALE_SYSTEM_DEFAULT, TIME_FORCE24HOURFORMAT, NULL, _T("'['HH':'mm':'ss'] '"), szTime, AS(szTime));
            lstrcat(szPreLog, szTime);
        }

        // Replace all the parameters in the Error string. Allocate more memory if necessary.  
        // In case something goes seriously wrong here, cap memory allocation at 1 megabyte.
        //
        for ( lpPreOut = (LPTSTR) MALLOC((dwSize) * sizeof(TCHAR));
              lpPreOut && ( -1 == _vsnwprintf(lpPreOut, dwSize, lpFormat, lpArgs)) && dwSize < (1024 * 1024);
              FREE(lpPreOut), lpPreOut = (LPTSTR) MALLOC((dwSize *= 2) * sizeof(TCHAR))
            );

        //
        // We now have the Error string and the prefix string. Copy this to the final 
        // string that we need to output.
        //
        
        if ( lpPreOut )
        {
        
            // Allocate another string that will be the final output string. 
            // We need 1 extra TCHAR for NULL terminator and 2 extra for
            // an optional NewLine + Linefeed TCHAR pair that may be added.
            //
            dwSize = lstrlen(szPreLog) + lstrlen(lpPreOut) + 3;
            lpOut = (LPTSTR) MALLOC( (dwSize) * sizeof(TCHAR) );
            
            if ( lpOut )
            {
                lstrcpy(lpOut, szPreLog);
                lstrcat(lpOut, lpPreOut);
                
                // Make sure that string is terminated by NewLine unless the caller doesn't want to.
                //
                if ( !GET_FLAG(dwLogOpt, LOG_NO_NL) )
                {
                     LPTSTR lpNL = lpOut;
                     TCHAR szCRLF[] = _T("\r\n");
                     BOOL  bStringOk = FALSE;
                     
                     // Find the end of the string.
                     //
                     lpNL = lpNL + lstrlen(lpNL);
                     
                     // Make sure the string is terminated by "\r\n".
                     //
                     // There are three cases here: 
                     //  1. The string is already terminated by \r\n. Leave it alone.
                     //  2. String is terminated by \n.  Replace \n with \r\n.
                     //  3. String is not terminated by anything. Append string with \r\n.
                     //
                                                              
                     if ( CHR_NEWLINE == *(lpNL = (CharPrev(lpOut, lpNL))) )
                     {
                         if ( CHR_CR != *(CharPrev(lpOut, lpNL)) )
                         {
                            *(lpNL) = NULLCHR;
                         }
                         else
                         {
                             bStringOk = TRUE;
                         }
                     }
                     
                     // If there is a need to, fix up the string
                     //
                     if ( !bStringOk )
                     {
                         lstrcat( lpOut, szCRLF );
                     }
                }

                // Write the error to the file and close the file.
                // Skip the "AppName::" at the beginning of the string when printing to the file.
                //
                if ( pLogInfo->szLogFile[0] &&
                    ( INVALID_HANDLE_VALUE != (hFile = CreateFile(pLogInfo->szLogFile, GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL)))
                   )
                {
                    if ( INVALID_SET_FILE_POINTER != SetFilePointer(hFile, 0, 0, FILE_END) )
                    {
                        WriteFile(hFile, (lpOut + cbAppName), lstrlen(lpOut + cbAppName) * sizeof(TCHAR), &dwWritten, NULL);
                    }
                    CloseHandle(hFile);
                }              

                // Output the string to the debugger and free it.
                //
                OutputDebugString(lpOut);
                FREE(lpOut);
            }
            
            // Put up the MessageBox if specified.  This only allows message boxes
            // to be log level 0.
            //
            if ( !GET_FLAG(pLogInfo->dwLogFlags, LOG_FLAG_QUIET_MODE) && 
                 GET_FLAG(dwLogOpt, LOG_MSG_BOX) && 
                 (0 == dwLogLevel)
               )
                 MessageBox(NULL, lpPreOut, pLogInfo->lpAppName, MB_OK | MB_SYSTEMMODAL |
                            (GET_FLAG(dwLogOpt, LOG_ERR) ? MB_ICONERROR : MB_ICONWARNING) );

            // Free the error string
            //
            FREE(lpPreOut);
        }
    }
    

    // Return the number of bytes written to the file.
    //
    return dwWritten;
}


DWORD OpkLogFile(DWORD dwLogOpt, UINT uFormat, ...)
{
    va_list lpArgs;
    DWORD   dwWritten = 0;
    LPTSTR  lpFormat = NULL;
        
    if ( g_pLogInfo )
    {

        // Initialize the lpArgs parameter with va_start().
        //
        va_start(lpArgs, uFormat);

        if  ( lpFormat = AllocateString(NULL, uFormat) )
        {
            dwWritten = OpkLogFileLst(g_pLogInfo, dwLogOpt, lpFormat, lpArgs);
        }

        // Free the format string.
        //
        FREE(lpFormat);
    }
    
    // Return the value saved from the previous function call.
    //
    return dwWritten;
}


DWORD OpkLogFileStr(DWORD dwLogOpt, LPTSTR lpFormat, ...)
{
    va_list lpArgs;
    DWORD dwWritten = 0;
   
    if ( g_pLogInfo )
    {
        // Initialize the lpArgs parameter with va_start().
        //
        va_start(lpArgs, lpFormat);
    
        dwWritten = OpkLogFileLst(g_pLogInfo, dwLogOpt, lpFormat, lpArgs);
    }
    
    // Return the value saved from the previous function call.
    //
    return dwWritten;
}