/*++

   Copyright    (c)    1995-1996    Microsoft Corporation

   Module  Name :
      filectl.cxx

   Abstract:
      OLE control to handle file logging object

   Author:

       Terence Kwan    ( terryk )    18-Sep-1996

   Project:

       IIS Logging 3.0

--*/

#include "precomp.hxx"
#include "initguid.h"
#include <ilogobj.hxx>
#include "filectl.hxx"
#include <issched.hxx>

#include <atlimpl.cpp>

#define LOG_FILE_SLOP       512

//
// tick minute.
//

#define TICK_MINUTE         (60 * 1000)

//************************************************************************************


VOID
LogWriteEvent(
    IN LPCSTR InstanceName,
    IN BOOL   fResume
    );

//
// globals
//

LPEVENT_LOG   g_eventLog = NULL;


CLogFileCtrl::CLogFileCtrl(
    VOID
    )
:
    m_fFirstLog             ( TRUE),
    m_pLogFile              ( NULL),
    m_fDiskFullShutdown     ( FALSE),
    m_fUsingCustomHeaders   ( FALSE),
    m_sequence              ( 1),
    m_TickResumeOpen        ( 0),
    m_strLogFileName        ( ),
    m_dwSchedulerCookie     ( 0),
    m_fInTerminate          ( FALSE)
/*++

Routine Description:
    Contructor for the log file control

Arguments:

Return Value:

--*/
{
    //
    // initialize all the internal variable
    //

    ZeroMemory( &m_stCurrentFile, sizeof( m_stCurrentFile));
    INITIALIZE_CRITICAL_SECTION( &m_csLock );
}


//************************************************************************************
// CLogFileCtrl::~CLogFileCtrl - Destructor

CLogFileCtrl::~CLogFileCtrl()
/*++

Routine Description:
    destructor for the log file control

Arguments:

Return Value:

--*/
{
    TerminateLog();

    DeleteCriticalSection( &m_csLock );
}

//************************************************************************************

STDMETHODIMP
CLogFileCtrl::InitializeLog(
                LPCSTR szInstanceName,
                LPCSTR pszMetabasePath,
                CHAR* pvIMDCOM )
/*++

Routine Description:
    Initialize log

Arguments:
    cbSize - size of the service name
    RegKey - service name
    dwInstanceOf - instance number

Return Value:

--*/
{
    //
    // get the default parameters
    //

    m_strInstanceName.Copy(szInstanceName);
    m_strMetabasePath.Copy(pszMetabasePath);
    m_pvIMDCOM = (LPVOID)pvIMDCOM;

    //
    // get the registry value
    //

    (VOID)GetRegParameters(
                    pszMetabasePath,
                    pvIMDCOM );

     return 0;
}

//************************************************************************************

STDMETHODIMP
CLogFileCtrl::TerminateLog(
    VOID
    )
/*++

Routine Description:
    clean up the log

Arguments:

Return Value:

--*/
{    
    Lock( );

    m_fInTerminate = TRUE;

    if ( m_pLogFile!=NULL) {
        m_pLogFile->CloseFile( );
        delete m_pLogFile;
        m_pLogFile = NULL;
    }

    if (m_dwSchedulerCookie)
    {
        RemoveWorkItem(m_dwSchedulerCookie);
    }

    m_dwSchedulerCookie = 0;

    m_fInTerminate = FALSE;
    
    Unlock( );

    return(TRUE);
}

//************************************************************************************

STDMETHODIMP
CLogFileCtrl::LogInformation(
            IInetLogInformation * ppvDataObj
            )
/*++

Routine Description:
    log information

Arguments:
    ppvDataObj - COM Logging object

Return Value:

--*/
{
    SYSTEMTIME stNow;

    CHAR    tmpBuf[512];
    DWORD   dwSize = sizeof(tmpBuf);
    PCHAR   pBuf = tmpBuf;
    DWORD   err;

retry:

    err = NO_ERROR;
    
    if ( FormatLogBuffer(ppvDataObj,
                        pBuf,
                        &dwSize,
                        &stNow         // time is returned
                        ) 
       ) 
    {
        WriteLogInformation(stNow, pBuf, dwSize, FALSE, FALSE);
    }
    else 
    {

        err = GetLastError();
        
        IIS_PRINTF((buff,"FormatLogBuffer failed with %d\n",GetLastError()));

        if ( (err == ERROR_INSUFFICIENT_BUFFER) &&
             ( pBuf == tmpBuf ) &&
             (dwSize <= MAX_LOG_RECORD_LEN) ) 
        {
             
            pBuf = (PCHAR)LocalAlloc( 0, dwSize );
            
            if ( pBuf != NULL ) 
            {
                goto retry;
            }
        }
    }

    if ( (pBuf != tmpBuf) && (pBuf != NULL) ) 
    {
        LocalFree( pBuf );
    }
    
    return(0);

} // LogInformation

//************************************************************************************

STDMETHODIMP
CLogFileCtrl::GetConfig( DWORD cbSize, BYTE * log)
/*++

Routine Description:
    get configuration information

Arguments:
    cbSize - size of the data structure
    log - log configuration data structure

Return Value:

--*/
{
    InternalGetConfig( (PINETLOG_CONFIGURATIONA)log );
    return(0L);
}

//************************************************************************************

STDMETHODIMP
CLogFileCtrl::QueryExtraLoggingFields(
    IN PDWORD   pcbSize,
    PCHAR       pszFieldsList
    )
/*++

Routine Description:
    get configuration information

Arguments:
    cbSize - size of the data structure
    log - log configuration data structure

Return Value:

--*/
{
    InternalGetExtraLoggingFields( pcbSize, pszFieldsList );
    return(0L);
}

//************************************************************************************

STDMETHODIMP
CLogFileCtrl::LogCustomInformation( 
    IN  DWORD               cCount, 
    IN  PCUSTOM_LOG_DATA    pCustomLogData,
    IN  LPSTR               szHeaderSuffix
    )
{
    return(0L);
}

//************************************************************************************

void
CLogFileCtrl::InternalGetExtraLoggingFields(
                            PDWORD pcbSize,
                            TCHAR *pszFieldsList
                            )
{
    pszFieldsList[0]=_T('\0');
    pszFieldsList[1]=_T('\0');
    *pcbSize = 2;
}

//************************************************************************************

VOID
CLogFileCtrl::InternalGetConfig(
    IN PINETLOG_CONFIGURATIONA pLogConfig
    )
/*++

Routine Description:
    internal; get configuration information function.

Arguments:
    log - log configuration data structure

Return Value:

--*/
{
    pLogConfig->inetLogType = INET_LOG_TO_FILE;
    strcpy(
        pLogConfig->u.logFile.rgchLogFileDirectory,
        QueryLogFileDirectory()
        );

    pLogConfig->u.logFile.cbSizeForTruncation = QuerySizeForTruncation();
    pLogConfig->u.logFile.ilPeriod = QueryPeriod();
    pLogConfig->u.logFile.ilFormat = QueryLogFormat();
}

//************************************************************************************

STDMETHODIMP
CLogFileCtrl::SetConfig(
                        DWORD cbSize,
                        BYTE * log
                        )
/*++

Routine Description:
    set the log configuration information

Arguments:
    cbSize - size of the configuration data structure
    log - log information

Return Value:

--*/
{
    //
    // write the configuration information to the registry
    //

    PINETLOG_CONFIGURATIONA pLogConfig = (PINETLOG_CONFIGURATIONA)log;
    SetSizeForTruncation( pLogConfig->u.logFile.cbSizeForTruncation );
    SetPeriod( pLogConfig->u.logFile.ilPeriod );
    SetLogFileDirectory( pLogConfig->u.logFile.rgchLogFileDirectory );
    return(0L);
} // CLogFileCtrl::SetConfig

//************************************************************************************

DWORD
CLogFileCtrl::GetRegParameters(
    IN LPCSTR pszRegKey,
    IN LPVOID pvIMDCOM
    )
/*++

Routine Description:
    get the registry value

Arguments:
    strRegKey - registry key

Return Value:

--*/
{

    DWORD err = NO_ERROR;
    MB    mb( (IMDCOM*) m_pvIMDCOM );
    DWORD dwSize;
    CHAR  szTmp[MAX_PATH+1];
    DWORD cbTmp = sizeof(szTmp);
    CHAR  buf[MAX_PATH+1];
    DWORD dwPeriod;

    if ( !mb.Open("") ) {
        err = GetLastError();
        return(err);
    }

    //
    // Get log file period
    //

    if ( mb.GetDword(
            pszRegKey,
            MD_LOGFILE_PERIOD,
            IIS_MD_UT_SERVER,
            &dwPeriod ) )
    {
        //
        // Make sure it is within bounds
        //

        if ( dwPeriod > INET_LOG_PERIOD_HOURLY ) 
        {
            IIS_PRINTF((buff,"Invalid log period %d, set to %d\n",
                dwPeriod, DEFAULT_LOG_FILE_PERIOD));

            dwPeriod = DEFAULT_LOG_FILE_PERIOD;
        }
        
    } 
    else 
    {
        dwPeriod = DEFAULT_LOG_FILE_PERIOD;
    }

    SetPeriod( dwPeriod );

    //
    //  Get truncate size
    //

    if ( dwPeriod == INET_LOG_PERIOD_NONE ) 
    {

        SetSizeForTruncation ( DEFAULT_LOG_FILE_TRUNCATE_SIZE );
        
        if ( mb.GetDword(   pszRegKey,
                            MD_LOGFILE_TRUNCATE_SIZE,
                            IIS_MD_UT_SERVER,
                            &dwSize ) ) 
        {

            if ( dwSize < MIN_FILE_TRUNCATION_SIZE ) 
            {
                dwSize = MIN_FILE_TRUNCATION_SIZE;
                IIS_PRINTF((buff,
                    "Setting truncation size to %d\n", dwSize));
            }

            SetSizeForTruncation( dwSize );
        }
    } 
    else 
    {
        SetSizeForTruncation( NO_FILE_TRUNCATION );
    }

    //
    // Get directory
    //

    if ( !mb.GetExpandString(
                    pszRegKey,
                    MD_LOGFILE_DIRECTORY,
                    IIS_MD_UT_SERVER,
                    szTmp,
                    &cbTmp ) )
    {
        lstrcpy(szTmp,
            TsIsWindows95() ?
                DEFAULT_LOG_FILE_DIRECTORY_W95 :
                DEFAULT_LOG_FILE_DIRECTORY_NT );
    }

    mb.Close();

    ExpandEnvironmentStrings( szTmp, buf, MAX_PATH+1 );
    SetLogFileDirectory( buf );
    
    return(err);

} // CLogFileCtrl::GetRegParameters

//************************************************************************************


BOOL
CLogFileCtrl::OpenLogFile(
    IN PSYSTEMTIME  pst
    )
/*++

Routine Description:
    internal routine to open file.

Arguments:

Return Value:

--*/
{
    BOOL fReturn = TRUE;
    BOOL bRet = FALSE;
    HANDLE hToken = NULL;
    DWORD dwError = NO_ERROR;
    CHAR  rgchPath[ MAX_PATH + 1 + 32];

    if ( m_pLogFile != NULL) {

        //
        // already a log file is open. return silently
        //

        IIS_PRINTF( ( buff,
                    " Log File %s is already open ( %08x)\n",
                    m_strLogFileName.QueryStr(), m_pLogFile));

    } else {

        //
        // If this the first time we opened, get the file name
        //

        if ( m_fFirstLog || (QueryPeriod() != INET_LOG_PERIOD_NONE) ) {
            m_fFirstLog = FALSE;
            FormNewLogFileName( pst );
        }

        //
        // Append log file name to path to form the path of file to be opened.
        //

        if ( (m_strLogFileName.QueryCCH() +
                m_strLogFileDirectory.QueryCCH() >= MAX_PATH) ||
             (m_strLogFileDirectory.QueryCCH() < 3) ) {

            fReturn = FALSE;

            if ( (g_eventLog != NULL) && !m_fDiskFullShutdown) {

                const CHAR*    tmpString[1];
                tmpString[0] = rgchPath;
                g_eventLog->LogEvent(
                    LOG_EVENT_CREATE_DIR_ERROR,
                    1,
                    tmpString,
                    ERROR_BAD_PATHNAME );
            }
            SetLastError( ERROR_BAD_PATHNAME );
            goto exit;
        }

        lstrcpy( rgchPath, QueryLogFileDirectory());
//      if ( rgchPath[strlen(rgchPath)-1] != '\\' ) {

        if ( *CharPrev(rgchPath, rgchPath + strlen(rgchPath)) != '\\' ) {
            lstrcat( rgchPath, "\\");
        }
        lstrcat( rgchPath, QueryInstanceName() );

        //
        // There is a small chance that this function could be called (indirectly)
        // from an INPROC ISAPI completion thread (HSE_REQ_DONE).  In this case
        // the thread token is the impersonated user and may not have permissions
        // to open the log file (especially if the user is the IUSR_ account).  
        // To be paranoid, let's revert to LOCAL_SYSTEM anyways before opening.
        //

        if ( OpenThreadToken( GetCurrentThread(), 
                              TOKEN_ALL_ACCESS, 
                              FALSE, 
                              &hToken ) )
        {
            DBG_ASSERT( hToken != NULL );
            RevertToSelf();
        }

        // Allow logging to mapped drives
        
        bRet = IISCreateDirectory( rgchPath, TRUE );
        dwError = GetLastError();

        if ( hToken != NULL )
        {
            SetThreadToken( NULL, hToken );
            SetLastError( dwError );
        } 
    
        if ( !bRet ) {

            if ( (g_eventLog != NULL) && !m_fDiskFullShutdown) {

                const CHAR*    tmpString[1];
                tmpString[0] = rgchPath;
                g_eventLog->LogEvent(
                    LOG_EVENT_CREATE_DIR_ERROR,
                    1,
                    tmpString,
                    GetLastError()
                    );
            }

            IIS_PRINTF((buff,"IISCreateDir[%s] error %d\n",
                rgchPath, GetLastError()));
            fReturn = FALSE;
            goto exit;
        }

        lstrcat( rgchPath, "\\");
        lstrcat( rgchPath, m_strLogFileName.QueryStr());

        m_pLogFile = new ILOG_FILE( );

        if (m_pLogFile != NULL) {

            if ( m_pLogFile->Open(
                        rgchPath,
                        QuerySizeForTruncation(),
                        !m_fDiskFullShutdown
                        ) ) {

                m_pLogFile->QueryFileSize(&m_cbTotalWritten);
            } else {

                delete m_pLogFile;
                m_pLogFile = NULL;
                fReturn = FALSE;
            }

        } else {

            IIS_PRINTF((buff,"Unable to allocate ILOG_FILE[err %d]\n",
                GetLastError()));

            fReturn = FALSE;
        }
    }

exit:

    return ( fReturn);

} // CLogFileCtrl::OpenLogFile

//************************************************************************************


BOOL
CLogFileCtrl::WriteLogDirectives(
    IN DWORD Sludge
    )
/*++

Routine Description:
    virtual function for the sub class to log directives to the file.

Arguments:

    Sludge - number of additional bytes that needs to be written
        together with the directives

Return Value:

    TRUE, ok
    FALSE, not enough space to write.

--*/
{
    //
    // if we will overflow, open another file
    //

    if ( IsFileOverFlowForCB( Sludge ) ) {
        SetLastError(ERROR_INSUFFICIENT_BUFFER);
        DBGPRINTF((DBG_CONTEXT,
            "Unable to write directive\n"));
        return(FALSE);
    }

    return TRUE;
} // CLogFileCtrl::WriteLogDirectives

//************************************************************************************

BOOL
CLogFileCtrl::WriteCustomLogDirectives(
    IN DWORD Sludge
    )
{
   
    return TRUE;
}

//************************************************************************************

VOID
CLogFileCtrl::I_FormNewLogFileName(
                    IN LPSYSTEMTIME pstNow,
                    IN LPCSTR       LogNamePrefix
                    )
{

    CHAR    tmpBuf[MAX_PATH+1];

    WORD wYear = ( pstNow->wYear % 100);  // retain just last 2 digits.

    switch ( QueryPeriod( ) ) {

    case INET_LOG_PERIOD_HOURLY:

        wsprintf( tmpBuf, "%.2s%02.2u%02u%02u%02u.%s",
                  LogNamePrefix,
                  wYear,
                  pstNow->wMonth,
                  pstNow->wDay,
                  pstNow->wHour,
                  DEFAULT_LOG_FILE_EXTENSION);
        break;

    case INET_LOG_PERIOD_DAILY:

        wsprintf( tmpBuf, "%.2s%02.2u%02u%02u.%s",
                  LogNamePrefix,
                  wYear,
                  pstNow->wMonth,
                  pstNow->wDay,
                  DEFAULT_LOG_FILE_EXTENSION);
        break;

    case INET_LOG_PERIOD_WEEKLY:

        wsprintf( tmpBuf, "%.2s%02.2u%02u%02u.%s",
                  LogNamePrefix,
                  wYear,
                  pstNow->wMonth,
                  WeekOfMonth(pstNow),
                  DEFAULT_LOG_FILE_EXTENSION);
        break;

    case INET_LOG_PERIOD_MONTHLY:
    
        wsprintf( tmpBuf, "%.2s%02u%02u.%s",
                  LogNamePrefix,
                  wYear,
                  pstNow->wMonth,
                  DEFAULT_LOG_FILE_EXTENSION);
        break;

    case INET_LOG_PERIOD_NONE:
    default:

        wsprintf(tmpBuf, "%.6s%u.%s",
              LogNamePrefix,
              m_sequence,
              DEFAULT_LOG_FILE_EXTENSION);

        m_sequence++;
        break;

    } // switch()

    m_strLogFileName.Copy(tmpBuf);

    return;
}

//************************************************************************************

VOID
CLogFileCtrl::SetLogFileDirectory(
        IN LPCSTR pszDir
        )
{

    STR tmpStr;
    HANDLE hFile;
    WIN32_FIND_DATA findData;
    DWORD   maxFileSize = 0;

    m_strLogFileDirectory.Copy(pszDir);

    //
    // if period is not none, then return
    //

    if ( QueryPeriod() != INET_LOG_PERIOD_NONE ) {
        return;
    }

    //
    // Get the starting sequence number
    //

    m_sequence = 1;

    //
    // Append instance name and the pattern.
    // should look like c:\winnt\system32\logfiles\w3svc1\inetsv*.log
    //

    tmpStr.Copy(pszDir);
//    if ( pszDir[tmpStr.QueryCCH()-1] != '\\' ) {
    if ( *CharPrev(pszDir, pszDir + tmpStr.QueryCCH()) != '\\' ) {
        tmpStr.Append("\\");
    }
    tmpStr.Append( QueryInstanceName() );
    tmpStr.Append( "\\" );
    tmpStr.Append( QueryNoPeriodPattern() );

    hFile = FindFirstFile( tmpStr.QueryStr(), &findData );
    if ( hFile == INVALID_HANDLE_VALUE ) {
        return;
    }

    do {

        PCHAR ptr;
        DWORD sequence = 1;

        ptr = strchr(findData.cFileName, '.');
        if (ptr != NULL ) {
            *ptr = '\0';
            ptr = findData.cFileName;

            while ( *ptr != '\0' ) {

                if ( isdigit((UCHAR)(*ptr)) ) {
                    sequence = atoi( ptr );
                    break;
                }
                ptr++;
            }

            if ( sequence > m_sequence ) {
                maxFileSize = findData.nFileSizeLow;
                m_sequence = sequence;
                DBGPRINTF((DBG_CONTEXT,
                    "Sequence start is %d[%d]\n", sequence, maxFileSize));
            }
        }

    } while ( FindNextFile( hFile, &findData ) );

    FindClose(hFile);

    if ( (maxFileSize+LOG_FILE_SLOP) > QuerySizeForTruncation() ) {
        m_sequence++;
    }

    return;

} // SetLogFileDirectory

//************************************************************************************

VOID
CLogFileCtrl::WriteLogInformation(
    IN SYSTEMTIME&     stNow, 
    IN PCHAR           pBuf, 
    IN DWORD           dwSize, 
    IN BOOL            fCustom,
    IN BOOL            fResetHeaders 
    )
/*++

Routine Description:
    write log line to file

Arguments:
    stNow           Present Time
    fResetHeaders   TRUE -> Reset headers, FALSE -> Don't reset headers
    pBuf            Pointer to Log Line
    dwSize          Number of characters in pBuf
    fCustom         TRUE -> Using custom logging, FALSE -> normal logging

Return Value:

--*/
{

    BOOL    fOpenNewFile;
    DWORD   err;
    DWORD   tickCount = 0;


    Lock ( );

    if ( m_pLogFile != NULL ) 
    {
        if ( QueryPeriod() == INET_LOG_PERIOD_DAILY ) 
        {
            fOpenNewFile = (m_stCurrentFile.wDay != stNow.wDay) ||
                           (m_stCurrentFile.wMonth != stNow.wMonth);
        } 
        else 
        {
            fOpenNewFile = IsBeginningOfNewPeriod( QueryPeriod(),
                                                   &m_stCurrentFile,
                                                   &stNow) ||
                           IsFileOverFlowForCB( dwSize);

             //
             // Reset headers if day is over. Used for weekly or unlimited files.
             //

             if ( !fOpenNewFile && !fResetHeaders)
             {
                fResetHeaders = (m_stCurrentFile.wDay != stNow.wDay) ||
                                (m_stCurrentFile.wMonth != stNow.wMonth);
             }
        }
    } 
    else 
    {
        fOpenNewFile = TRUE;
    }

    if (fOpenNewFile ) 
    {

        //
        // open a file only after every minute when we hit disk full
        //

        if ( m_TickResumeOpen != 0 ) 
        {
            tickCount = GetTickCount( );

            if ( (tickCount < m_TickResumeOpen) ||
                 ((tickCount + TICK_MINUTE) < tickCount ) )  // The Tick counter is about to wrap.
            {
                goto exit_tick;
            }
        }

retry_open:

        //
        // Close existing log
        //

        TerminateLog();

        //
        // Open new log file
        //

        if ( OpenLogFile( &stNow ) ) 
        {
            //
            // Schedule Callback for closing log file and set flag for writing directives.
            //

            ScheduleCallback(stNow);
            
            fResetHeaders = TRUE;
        }
        else
        {
            err = GetLastError();

            //
            // The file is already bigger than the truncate size
            // try another one.
            //

            if ( err == ERROR_INSUFFICIENT_BUFFER ) 
            {
                FormNewLogFileName( &stNow );
                err = NO_ERROR;
                goto retry_open;
            }

            goto exit;
        }
    }

    //
    // Reset Headers if needed
    //

    if ((fResetHeaders) || (fCustom != m_fUsingCustomHeaders))
    {
        BOOL fSucceeded;
        
        if (fCustom)
        {
            m_fUsingCustomHeaders = TRUE;
            fSucceeded = WriteCustomLogDirectives(dwSize);
        }
        else
        {
            m_fUsingCustomHeaders = FALSE;
            fSucceeded = WriteLogDirectives(dwSize);
        }
            
        if (!fSucceeded) 
        {
            err = GetLastError( );

            if ( err == ERROR_INSUFFICIENT_BUFFER ) 
            {
                FormNewLogFileName( &stNow );
                err = NO_ERROR;
                goto retry_open;
            }

            TerminateLog();
            goto exit;
        }

        //
        // record the time of opening of this new file
        //

        m_stCurrentFile = stNow;
    }

    //
    // write it to the buffer
    //

    if ( m_pLogFile->Write(pBuf, dwSize) ) 
    {
        IncrementBytesWritten(dwSize);

        //
        // If this had been shutdown, log event for reactivation
        //

        if ( m_fDiskFullShutdown ) 
        {
            m_fDiskFullShutdown = FALSE;
            m_TickResumeOpen = 0;

            LogWriteEvent( QueryInstanceName(), TRUE );
        }
    } 
    else 
    {
        err = GetLastError();
        TerminateLog( );
    }

exit:

    if ( err == ERROR_DISK_FULL ) 
    {
        if ( !m_fDiskFullShutdown ) 
        {
            m_fDiskFullShutdown = TRUE;
            LogWriteEvent( QueryInstanceName(), FALSE );
        }
        
        m_TickResumeOpen = GetTickCount();
        m_TickResumeOpen += TICK_MINUTE;
    }

exit_tick:

    Unlock( );

} // LogInformation


//************************************************************************************

DWORD
CLogFileCtrl::ScheduleCallback(SYSTEMTIME& stNow)
{
    DWORD dwTimeRemaining = 0;
    
    switch (m_dwPeriod)
    {
        case INET_LOG_PERIOD_HOURLY:
            dwTimeRemaining = 60*60 - 
                              (stNow.wMinute*60 + 
                               stNow.wSecond);
            break;
            
        case INET_LOG_PERIOD_DAILY:
            dwTimeRemaining = 24*60*60 - 
                              (stNow.wHour*60*60 + 
                               stNow.wMinute*60 + 
                               stNow.wSecond);
            break;
            
        case INET_LOG_PERIOD_WEEKLY:
            dwTimeRemaining = 7*24*60*60 -
                              (stNow.wDayOfWeek*24*60*60 + 
                               stNow.wHour*60*60 + 
                               stNow.wMinute*60 + 
                               stNow.wSecond);
            break;
            
        case INET_LOG_PERIOD_MONTHLY:
        
            DWORD   dwNumDays = 31;

            if ( (4 == stNow.wMonth) ||     // April
                 (6 == stNow.wMonth) ||     // June
                 (9 == stNow.wMonth) ||     // September
                 (11 == stNow.wMonth)       // November
               )
            {
                dwNumDays = 30;
            }

            if (2 == stNow.wMonth)          // February
            {
		if ((stNow.wYear % 4 == 0 && stNow.wYear % 100 != 0) || stNow.wYear % 400 == 0)
                {
                    //
                    // leap year.
                    //

                    dwNumDays = 29;
                }
                else
                {
                    dwNumDays = 28;
                }
            }
            
            dwTimeRemaining = dwNumDays*24*60*60 -
                              (stNow.wDay*24*60*60 + 
                               stNow.wHour*60*60 + 
                               stNow.wMinute*60 + 
                               stNow.wSecond);
            break;
    }

    //
    // Convert remaining time to millisecs
    //
    
    dwTimeRemaining = dwTimeRemaining*1000 - stNow.wMilliseconds;
    
    if (dwTimeRemaining)
    {
        m_dwSchedulerCookie =  ScheduleWorkItem(
                                    LoggingSchedulerCallback,
                                    this,
                                    dwTimeRemaining,
                                    FALSE);
    }                

    return(m_dwSchedulerCookie);
}

//************************************************************************************

CHAR * SkipWhite( CHAR * pch )
{
    while ( ISWHITEA( *pch ) )
    {
        pch++;
    }

    return pch;
}

//************************************************************************************

DWORD
FastDwToA(
    CHAR*   pBuf,
    DWORD   dwV
    )
/*++

Routine Description:
    Convert DWORD to ascii (decimal )
    returns length ( w/o trailing '\0' )

Arguments:
    pBuf - buffer where to store converted value
    dwV - value to convert

Return Value:
    length of ascii string

--*/
{
    DWORD   v;

    if ( dwV < 10 ) {
        pBuf[0] = (CHAR)('0'+dwV);
        pBuf[1] = '\0';
        return 1;
    } else if ( dwV < 100 ) {
        pBuf[0] = (CHAR)((dwV/10) + '0');
        pBuf[1] = (CHAR)((dwV%10) + '0');
        pBuf[2] = '\0';
        return 2;
    } else if ( dwV < 1000 ) {
        pBuf[0] = (CHAR)((v=dwV/100) + '0');
        dwV -= v * 100;
        pBuf[1] = (CHAR)((dwV/10) + '0');
        pBuf[2] = (CHAR)((dwV%10) + '0');
        pBuf[3] = '\0';
        return 3;
    } else if ( dwV < 10000 ) {

        pBuf[0] = (CHAR)((v=dwV/1000) + '0');
        dwV -= v * 1000;
        pBuf[1] = (CHAR)((v=dwV/100) + '0');
        dwV -= v * 100;
        pBuf[2] = (CHAR)((dwV/10) + '0');
        pBuf[3] = (CHAR)((dwV%10) + '0');
        pBuf[4] = '\0';
        return 4;
    }

    _ultoa(dwV, pBuf, 10);
    return strlen(pBuf);
    
} // FastDwToA

//************************************************************************************

VOID
LogWriteEvent(
    IN LPCSTR InstanceName,
    IN BOOL   fResume
    )
{
    if ( g_eventLog != NULL ) {

        const CHAR*    tmpString[1];
        tmpString[0] = InstanceName;

        g_eventLog->LogEvent(
                fResume ?
                    LOG_EVENT_RESUME_LOGGING :
                    LOG_EVENT_DISK_FULL_SHUTDOWN,
                1,
                tmpString,
                0);
    }
    return;
} // LogWriteEvent

//************************************************************************************

VOID WINAPI LoggingSchedulerCallback( PVOID pContext)
{
    CLogFileCtrl *pLog = (CLogFileCtrl *) pContext;

    //
    // There is a possibility of deadlock if another thread is inside TerminateLog
    // stuck in RemoveWorkItem, waiting for this callback thread to complete. To
    // prevent that we use the synchronization flag - m_fInTerminate.
    //
    
    pLog->m_dwSchedulerCookie = 0;

    if (!pLog->m_fInTerminate)
    {
        pLog->TerminateLog();
    }
}

//************************************************************************************