/*++ 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 #include "filectl.hxx" #include #include #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, 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, IN PCUSTOM_LOG_DATA, IN LPSTR ) { 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, 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 ) /*++ 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, 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 ( %p)\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 ) { 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 = NO_ERROR; 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 (DWORD)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(); } } //************************************************************************************