Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

5686 lines
159 KiB

/*++
Copyright (c) 2000-2002 Microsoft Corporation
Module Name:
ullog.c (UL IIS6 HIT Logging)
Abstract:
This module implements the logging facilities
for IIS6 including the NCSA, IIS and W3CE types
of logging.
Author:
Ali E. Turkoglu (aliTu) 10-May-2000
Revision History:
--*/
#include "precomp.h"
#include "ullogp.h"
//
// Generic Private globals.
//
LIST_ENTRY g_LogListHead = {NULL,NULL};
LONG g_LogListEntryCount = 0;
BOOLEAN g_InitLogsCalled = FALSE;
BOOLEAN g_InitLogTimersCalled = FALSE;
CHAR g_GMTOffset[SIZE_OF_GMT_OFFSET + 1];
//
// The global parameter keeps track of the changes to the
// utf8 logging which applies to the all sites.
//
BOOLEAN g_UTF8Logging = FALSE;
//
// For Log Buffering and periodic flush of the buffers.
//
UL_LOG_TIMER g_BufferTimer;
//
// For Log File ReCycling based on Local and/or GMT time.
//
UL_LOG_TIMER g_LogTimer;
#ifdef ALLOC_PRAGMA
#pragma alloc_text( INIT, UlInitializeLogs )
#pragma alloc_text( PAGE, UlTerminateLogs )
#pragma alloc_text( PAGE, UlpGetGMTOffset )
#pragma alloc_text( PAGE, UlpRecycleLogFile )
#pragma alloc_text( PAGE, UlCreateLogEntry )
#pragma alloc_text( PAGE, UlpCreateLogFile )
#pragma alloc_text( PAGE, UlRemoveLogEntry )
#pragma alloc_text( PAGE, UlpConstructLogEntry )
#pragma alloc_text( PAGE, UlpAllocateLogDataBuffer )
#pragma alloc_text( PAGE, UlReConfigureLogEntry )
#pragma alloc_text( PAGE, UlBufferTimerHandler )
#pragma alloc_text( PAGE, UlpAppendW3CLogTitle )
#pragma alloc_text( PAGE, UlpWriteToLogFile )
#pragma alloc_text( PAGE, UlSetUTF8Logging )
#pragma alloc_text( PAGE, UlCaptureLogFieldsW3C )
#pragma alloc_text( PAGE, UlCaptureLogFieldsNCSA )
#pragma alloc_text( PAGE, UlCaptureLogFieldsIIS )
#pragma alloc_text( PAGE, UlLogHttpCacheHit )
#pragma alloc_text( PAGE, UlLogHttpHit )
#pragma alloc_text( PAGE, UlpGenerateDateAndTimeFields )
#pragma alloc_text( PAGE, UlpMakeEntryInactive )
#pragma alloc_text( PAGE, UlDisableLogEntry )
#pragma alloc_text( PAGE, UlpEventLogWriteFailure )
#endif // ALLOC_PRAGMA
#if 0
NOT PAGEABLE -- UlLogTimerDpcRoutine
NOT PAGEABLE -- UlpTerminateLogTimer
NOT PAGEABLE -- UlpInsertLogEntry
NOT PAGEABLE -- UlLogTimerHandler
NOT PAGEABLE -- UlBufferTimerDpcRoutine
NOT PAGEABLE -- UlpTerminateTimers
NOT PAGEABLE -- UlpInitializeTimers
NOT PAGEABLE -- UlpBufferFlushAPC
NOT PAGEABLE -- UlDestroyLogDataBuffer
NOT PAGEABLE -- UlDestroyLogDataBufferWorker
#endif
//
// Public functions.
//
/***************************************************************************++
Routine Description:
UlInitializeLogs :
Initialize the resource for log list synchronization
--***************************************************************************/
NTSTATUS
UlInitializeLogs (
VOID
)
{
NTSTATUS Status = STATUS_SUCCESS;
PAGED_CODE();
ASSERT(!g_InitLogsCalled);
if (!g_InitLogsCalled)
{
InitializeListHead(&g_LogListHead);
UlInitializePushLock(
&g_pUlNonpagedData->LogListPushLock,
"LogListPushLock",
0,
UL_LOG_LIST_PUSHLOCK_TAG
);
g_InitLogsCalled = TRUE;
UlpInitializeTimers();
UlpInitializeLogCache();
UlpGetGMTOffset();
}
return Status;
}
/***************************************************************************++
Routine Description:
UlTerminateLogs :
Deletes the resource for log list synchronization
--***************************************************************************/
VOID
UlTerminateLogs(
VOID
)
{
PAGED_CODE();
if (g_InitLogsCalled)
{
ASSERT( IsListEmpty( &g_LogListHead )) ;
//
// Make sure terminate the log timer before
// deleting the log list resource
//
UlpTerminateTimers();
UlDeletePushLock(
&g_pUlNonpagedData->LogListPushLock
);
g_InitLogsCalled = FALSE;
}
}
/***************************************************************************++
Routine Description:
UlSetUTF8Logging :
Sets the UTF8Logging on or off. Only once. Initially Utf8Logging is
FALSE and it may only be set during the init once. Following possible
changes won't be taken.
ReConfiguration code is explicitly missing as WAS will anly call this
only once (init) during the lifetime of the control channel.
--***************************************************************************/
NTSTATUS
UlSetUTF8Logging (
IN BOOLEAN UTF8Logging
)
{
PLIST_ENTRY pLink;
PUL_LOG_FILE_ENTRY pEntry;
NTSTATUS Status;
PAGED_CODE();
Status = STATUS_SUCCESS;
//
// Update & Reycle. Need to acquire the logging resource to prevent
// further log hits to be written to file before we finish our
// business. recycle is necessary because files will be renamed to
// have prefix "u_" once we enabled the UTF8.
//
UlTrace(LOGGING,("Http!UlSetUTF8Logging: UTF8Logging Old %d -> New %d\n",
g_UTF8Logging,UTF8Logging
));
UlAcquirePushLockExclusive(&g_pUlNonpagedData->LogListPushLock);
//
// Drop the change if the setting is not changing.
//
if ( g_UTF8Logging == UTF8Logging )
{
goto end;
}
g_UTF8Logging = UTF8Logging;
for (pLink = g_LogListHead.Flink;
pLink != &g_LogListHead;
pLink = pLink->Flink
)
{
pEntry = CONTAINING_RECORD(
pLink,
UL_LOG_FILE_ENTRY,
LogFileListEntry
);
UlAcquirePushLockExclusive(&pEntry->EntryPushLock);
if (pEntry->Flags.Active && !pEntry->Flags.RecyclePending)
{
pEntry->Flags.StaleSequenceNumber = 1;
Status = UlpRecycleLogFile(pEntry);
}
UlReleasePushLockExclusive(&pEntry->EntryPushLock);
}
end:
UlReleasePushLockExclusive(&g_pUlNonpagedData->LogListPushLock);
return Status;
}
/***************************************************************************++
Routine Description:
UlpWriteToLogFile :
Writes a record to a log file
Arguments:
pFile - Handle to a log file entry
RecordSize - Length of the record to be written.
pRecord - The log record to be written to the log buffer
--***************************************************************************/
NTSTATUS
UlpWriteToLogFile(
IN PUL_LOG_FILE_ENTRY pFile,
IN ULONG RecordSize,
IN PCHAR pRecord,
IN ULONG UsedOffset1,
IN ULONG UsedOffset2
)
{
NTSTATUS Status;
PAGED_CODE();
ASSERT(pRecord!=NULL);
ASSERT(RecordSize!=0);
ASSERT(IS_VALID_LOG_FILE_ENTRY(pFile));
UlTrace(LOGGING, ("Http!UlpWriteToLogFile: pEntry %p\n", pFile));
if ( pFile==NULL ||
pRecord==NULL ||
RecordSize==0 ||
RecordSize>g_UlLogBufferSize
)
{
return STATUS_INVALID_PARAMETER;
}
//
// We are safe here by dealing only with entry eresource since the
// time based recycling, reconfiguration and periodic buffer flushing
// always acquires the global list eresource exclusively and we are
// already holding it shared. But we should still be carefull about
// file size based recyling and we should only do it while we are
// holding the entries eresource exclusive.I.e. look at the exclusive
// writer down below.
//
if (g_UlDisableLogBuffering)
{
//
// Above global variable is safe to look, it doesn't get changed
// during the life-time of the driver. It's get initialized from
// the registry and disables the log buffering.
//
UlAcquirePushLockExclusive(&pFile->EntryPushLock);
Status = UlpWriteToLogFileDebug(
pFile,
RecordSize,
pRecord,
UsedOffset1,
UsedOffset2
);
UlReleasePushLockExclusive(&pFile->EntryPushLock);
return Status;
}
//
// Try UlpWriteToLogFileShared first which merely moves the
// BufferUsed forward and copy the record to LogBuffer->Buffer.
//
UlAcquirePushLockShared(&pFile->EntryPushLock);
Status = UlpWriteToLogFileShared(
pFile,
RecordSize,
pRecord,
UsedOffset1,
UsedOffset2
);
UlReleasePushLockShared(&pFile->EntryPushLock);
if (Status == STATUS_MORE_PROCESSING_REQUIRED)
{
//
// UlpWriteToLogFileShared returns STATUS_MORE_PROCESSING_REQUIRED,
// we need to flush the buffer and try to log again. This time, we
// need to take the entry eresource exclusive.
//
UlAcquirePushLockExclusive(&pFile->EntryPushLock);
Status = UlpWriteToLogFileExclusive(
pFile,
RecordSize,
pRecord,
UsedOffset1,
UsedOffset2
);
UlReleasePushLockExclusive(&pFile->EntryPushLock);
}
return Status;
}
/***************************************************************************++
Routine Description:
UlpAppendToLogBuffer :
Append a record to a log file
REQUIRES you to hold the loglist resource shared and entry mutex
shared or exclusive
Arguments:
pFile - Handle to a log file entry
RecordSize - Length of the record to be written.
pRecord - The log record to be written to the log buffer
--***************************************************************************/
__inline
VOID
UlpAppendToLogBuffer(
IN PUL_LOG_FILE_ENTRY pFile,
IN ULONG BufferUsed,
IN ULONG RecordSize,
IN PCHAR pRecord,
IN ULONG UsedOffset1,
IN ULONG UsedOffset2
)
{
PUL_LOG_FILE_BUFFER pLogBuffer = pFile->LogBuffer;
UlTrace(LOGGING,
("Http!UlpAppendToLogBuffer: pEntry %p TW:%I64d FileBuffer %p (%d + %d)\n",
pFile,
pFile->TotalWritten.QuadPart,
pLogBuffer->Buffer,
BufferUsed,
RecordSize
));
//
// IIS format log line may be fragmented (identified by looking at the
// UsedOffset2), handle it wisely.
//
if (UsedOffset2)
{
RtlCopyMemory(
pLogBuffer->Buffer + BufferUsed,
&pRecord[0],
UsedOffset1
);
RtlCopyMemory(
pLogBuffer->Buffer + BufferUsed + UsedOffset1,
&pRecord[512],
UsedOffset2
);
RtlCopyMemory(
pLogBuffer->Buffer + BufferUsed + UsedOffset1 + UsedOffset2,
&pRecord[1024],
RecordSize - (UsedOffset1 + UsedOffset2)
);
}
else
{
RtlCopyMemory(
pLogBuffer->Buffer + BufferUsed,
pRecord,
RecordSize
);
}
}
/***************************************************************************++
Routine Description:
REQUIRES LogListResource Shared & Entry eresource exclusive.
Appends the W3C log file title to the existing buffer.
Arguments:
pFile - Pointer to the logfile entry
pCurrentTimeFields - Current time fields
--***************************************************************************/
NTSTATUS
UlpAppendW3CLogTitle(
IN PUL_LOG_FILE_ENTRY pEntry,
OUT PCHAR pDestBuffer,
IN OUT PULONG pBytesCopied
)
{
PCHAR TitleBuffer;
LONG BytesCopied;
ULONG LogExtFileFlags;
TIME_FIELDS CurrentTimeFields;
LARGE_INTEGER CurrentTimeStamp;
PUL_LOG_FILE_BUFFER pLogBuffer;
PAGED_CODE();
ASSERT(IS_VALID_LOG_FILE_ENTRY(pEntry));
ASSERT(pEntry->Format == HttpLoggingTypeW3C);
pLogBuffer = pEntry->LogBuffer;
LogExtFileFlags = pEntry->LogExtFileFlags;
KeQuerySystemTime(&CurrentTimeStamp);
RtlTimeToTimeFields(&CurrentTimeStamp, &CurrentTimeFields);
if (pDestBuffer)
{
// Append to the provided buffer
ASSERT(pBytesCopied);
ASSERT(*pBytesCopied >= UL_MAX_TITLE_BUFFER_SIZE);
UlTrace(LOGGING,("Http!UlpAppendW3CLogTitle: Copying to Provided Buffer %p\n",
pDestBuffer));
TitleBuffer = pDestBuffer;
}
else
{
// Append to the entry buffer
ASSERT(pLogBuffer);
ASSERT(pLogBuffer->Buffer);
UlTrace(LOGGING,("Http!UlpAppendW3CLogTitle: Copying to Entry Buffer %p\n",
pLogBuffer));
TitleBuffer = (PCHAR) pLogBuffer->Buffer + pLogBuffer->BufferUsed;
}
BytesCopied = _snprintf(
TitleBuffer,
UL_MAX_TITLE_BUFFER_SIZE,
// TODO: Make this maintainance friendly
"#Software: Microsoft Internet Information Services 6.0\r\n"
"#Version: 1.0\r\n"
"#Date: %4d-%02d-%02d %02d:%02d:%02d\r\n"
"#Fields:%ls%ls%ls%ls%ls%ls%ls%ls%ls%ls%ls%ls%ls%ls%ls%ls%ls%ls%ls%ls%ls%ls \r\n",
CurrentTimeFields.Year,
CurrentTimeFields.Month,
CurrentTimeFields.Day,
CurrentTimeFields.Hour,
CurrentTimeFields.Minute,
CurrentTimeFields.Second,
UL_GET_LOG_TITLE_IF_PICKED(UlLogFieldDate,LogExtFileFlags,MD_EXTLOG_DATE),
UL_GET_LOG_TITLE_IF_PICKED(UlLogFieldTime,LogExtFileFlags,MD_EXTLOG_TIME),
UL_GET_LOG_TITLE_IF_PICKED(UlLogFieldSiteName,LogExtFileFlags,MD_EXTLOG_SITE_NAME),
UL_GET_LOG_TITLE_IF_PICKED(UlLogFieldServerName,LogExtFileFlags,MD_EXTLOG_COMPUTER_NAME),
UL_GET_LOG_TITLE_IF_PICKED(UlLogFieldServerIp,LogExtFileFlags,MD_EXTLOG_SERVER_IP),
UL_GET_LOG_TITLE_IF_PICKED(UlLogFieldMethod,LogExtFileFlags,MD_EXTLOG_METHOD),
UL_GET_LOG_TITLE_IF_PICKED(UlLogFieldUriStem,LogExtFileFlags,MD_EXTLOG_URI_STEM),
UL_GET_LOG_TITLE_IF_PICKED(UlLogFieldUriQuery,LogExtFileFlags,MD_EXTLOG_URI_QUERY),
UL_GET_LOG_TITLE_IF_PICKED(UlLogFieldServerPort,LogExtFileFlags,MD_EXTLOG_SERVER_PORT),
UL_GET_LOG_TITLE_IF_PICKED(UlLogFieldUserName,LogExtFileFlags,MD_EXTLOG_USERNAME),
UL_GET_LOG_TITLE_IF_PICKED(UlLogFieldClientIp,LogExtFileFlags,MD_EXTLOG_CLIENT_IP),
UL_GET_LOG_TITLE_IF_PICKED(UlLogFieldProtocolVersion,LogExtFileFlags,MD_EXTLOG_PROTOCOL_VERSION),
UL_GET_LOG_TITLE_IF_PICKED(UlLogFieldUserAgent,LogExtFileFlags,MD_EXTLOG_USER_AGENT),
UL_GET_LOG_TITLE_IF_PICKED(UlLogFieldCookie,LogExtFileFlags,MD_EXTLOG_COOKIE),
UL_GET_LOG_TITLE_IF_PICKED(UlLogFieldReferrer,LogExtFileFlags,MD_EXTLOG_REFERER),
UL_GET_LOG_TITLE_IF_PICKED(UlLogFieldHost,LogExtFileFlags,MD_EXTLOG_HOST),
UL_GET_LOG_TITLE_IF_PICKED(UlLogFieldProtocolStatus,LogExtFileFlags,MD_EXTLOG_HTTP_STATUS),
UL_GET_LOG_TITLE_IF_PICKED(UlLogFieldSubStatus,LogExtFileFlags,MD_EXTLOG_HTTP_SUB_STATUS),
UL_GET_LOG_TITLE_IF_PICKED(UlLogFieldWin32Status,LogExtFileFlags,MD_EXTLOG_WIN32_STATUS),
UL_GET_LOG_TITLE_IF_PICKED(UlLogFieldBytesSent,LogExtFileFlags,MD_EXTLOG_BYTES_SENT),
UL_GET_LOG_TITLE_IF_PICKED(UlLogFieldBytesReceived,LogExtFileFlags,MD_EXTLOG_BYTES_RECV),
UL_GET_LOG_TITLE_IF_PICKED(UlLogFieldTimeTaken,LogExtFileFlags,MD_EXTLOG_TIME_TAKEN)
);
if (BytesCopied < 0)
{
ASSERT(!"Default title buffer size is too small !");
BytesCopied = UL_MAX_TITLE_BUFFER_SIZE;
}
if (pDestBuffer)
{
*pBytesCopied = BytesCopied;
}
else
{
pLogBuffer->BufferUsed += BytesCopied;
}
return STATUS_SUCCESS;
}
/***************************************************************************++
Routine Description:
Writes a record to the log buffer and flushes.
This func only get called when debug parameter
g_UlDisableLogBuffering is set.
REQUIRES you to hold the entry eresource EXCLUSIVE.
Arguments:
pFile - Handle to a log file entry
RecordSize - Length of the record to be written.
--***************************************************************************/
NTSTATUS
UlpWriteToLogFileDebug(
IN PUL_LOG_FILE_ENTRY pFile,
IN ULONG RecordSize,
IN PCHAR pRecord,
IN ULONG UsedOffset1,
IN ULONG UsedOffset2
)
{
NTSTATUS Status = STATUS_SUCCESS;
PUL_LOG_FILE_BUFFER pLogBuffer;
ULONG RecordSizePlusTitle = RecordSize;
CHAR TitleBuffer[UL_MAX_TITLE_BUFFER_SIZE];
ULONG TitleBufferSize = UL_MAX_TITLE_BUFFER_SIZE;
PAGED_CODE();
ASSERT(IS_VALID_LOG_FILE_ENTRY(pFile));
ASSERT(UlDbgPushLockOwnedExclusive(&pFile->EntryPushLock));
ASSERT(g_UlDisableLogBuffering!=0);
UlTrace(LOGGING,("Http!UlpWriteToLogFileDebug: pEntry %p\n", pFile ));
if (!pFile->Flags.LogTitleWritten)
{
//
// First append to the temp buffer to calculate the size.
//
UlpAppendW3CLogTitle(pFile, TitleBuffer, &TitleBufferSize);
RecordSizePlusTitle += TitleBufferSize;
}
if (UlpIsLogFileOverFlow(pFile,RecordSizePlusTitle))
{
Status = UlpRecycleLogFile(pFile);
}
if (pFile->pLogFile==NULL || !NT_SUCCESS(Status))
{
//
// If we were unable to acquire a new file handle that means logging
// is temporarly ceased because of either STATUS_DISK_FULL or the
// drive went down for some reason. We just bail out.
//
return Status;
}
if (!pFile->LogBuffer)
{
//
// The buffer will be null for each log hit when log buffering
// is disabled.
//
pFile->LogBuffer = UlPplAllocateLogFileBuffer();
if (!pFile->LogBuffer)
{
return STATUS_NO_MEMORY;
}
}
pLogBuffer = pFile->LogBuffer;
ASSERT(pLogBuffer->BufferUsed == 0);
if (!pFile->Flags.LogTitleWritten)
{
ASSERT(pFile->Format == HttpLoggingTypeW3C);
UlpAppendW3CLogTitle(pFile, NULL, NULL);
pFile->Flags.LogTitleWritten = 1;
pFile->Flags.TitleFlushPending = 1;
}
ASSERT(RecordSize + pLogBuffer->BufferUsed <= g_UlLogBufferSize);
UlpAppendToLogBuffer(
pFile,
pLogBuffer->BufferUsed,
RecordSize,
pRecord,
UsedOffset1,
UsedOffset2
);
pLogBuffer->BufferUsed += RecordSize;
Status = UlpFlushLogFile(pFile);
if (!NT_SUCCESS(Status))
{
return Status;
}
return STATUS_SUCCESS;
}
/***************************************************************************++
Routine Description:
Writes an event log to system log for log file write failure.
Entry pushlock should be acquired exclusive prior to calling this function.
Arguments:
pEntry - Log file entry
Status - Result of last write
--***************************************************************************/
VOID
UlpEventLogWriteFailure(
IN PUL_LOG_FILE_ENTRY pEntry,
IN NTSTATUS Status
)
{
NTSTATUS TempStatus = STATUS_SUCCESS;
PWSTR StringList[2];
WCHAR SiteName[MAX_ULONG_STR + 1];
//
// Sanity Check.
//
PAGED_CODE();
ASSERT(IS_VALID_LOG_FILE_ENTRY(pEntry));
//
// There should better be a failure.
//
ASSERT(!NT_SUCCESS(Status));
//
// Bail out if we have already logged the event failure.
//
if (pEntry->Flags.WriteFailureLogged)
{
return;
}
//
// Report the log file name and the site name.
//
ASSERT(pEntry->pShortName);
ASSERT(pEntry->pShortName[0] == L'\\');
StringList[0] = (PWSTR) (pEntry->pShortName + 1); // Skip the L'\'
UlStrPrintUlongW(SiteName, pEntry->SiteId, 0, L'\0');
StringList[1] = (PWSTR) SiteName;
TempStatus = UlWriteEventLogEntry(
(NTSTATUS)EVENT_HTTP_LOGGING_FILE_WRITE_FAILED,
0,
2,
StringList,
sizeof(NTSTATUS),
(PVOID) &Status
);
ASSERT(TempStatus != STATUS_BUFFER_OVERFLOW);
if (TempStatus == STATUS_SUCCESS)
{
pEntry->Flags.WriteFailureLogged = 1;
}
UlTrace(LOGGING,(
"Http!UlpEventLogWriteFailure: Event Logging Status %08lx\n",
TempStatus
));
}
/***************************************************************************++
Routine Description:
Simple wrapper function around global buffer flush.
Arguments:
pEntry - Log file entry
--***************************************************************************/
NTSTATUS
UlpFlushLogFile(
IN PUL_LOG_FILE_ENTRY pEntry
)
{
NTSTATUS Status = STATUS_SUCCESS;
ASSERT(IS_VALID_LOG_FILE_ENTRY(pEntry));
if (NULL != pEntry->LogBuffer && 0 != pEntry->LogBuffer->BufferUsed)
{
Status = UlFlushLogFileBuffer(
&pEntry->LogBuffer,
pEntry->pLogFile,
(BOOLEAN)pEntry->Flags.TitleFlushPending,
&pEntry->TotalWritten.QuadPart
);
if (!NT_SUCCESS(Status))
{
UlpEventLogWriteFailure(pEntry, Status);
}
else
{
//
// If we have successfully flushed some data.
// Reset the event log indication.
//
pEntry->Flags.WriteFailureLogged = 0;
}
if (pEntry->Flags.TitleFlushPending)
{
pEntry->Flags.TitleFlushPending = 0;
if (!NT_SUCCESS(Status))
{
//
// We need to recopy the header, it couldn't make it
// to the log file yet.
//
pEntry->Flags.LogTitleWritten = 0;
}
}
//
// Buffer flush means activity reset the TimeToClose to its max.
//
pEntry->TimeToClose = DEFAULT_MAX_FILE_IDLE_TIME;
}
return Status;
}
/***************************************************************************++
Routine Description:
UlpWriteToLogFileShared :
Writes a record to a log file
REQUIRES you to hold the loglist resource shared
Arguments:
pFile - Handle to a log file entry
RecordSize - Length of the record to be written.
pRecord - The log record to be written to the log buffer
--***************************************************************************/
NTSTATUS
UlpWriteToLogFileShared(
IN PUL_LOG_FILE_ENTRY pFile,
IN ULONG RecordSize,
IN PCHAR pRecord,
IN ULONG UsedOffset1,
IN ULONG UsedOffset2
)
{
PUL_LOG_FILE_BUFFER pLogBuffer;
LONG BufferUsed;
PAGED_CODE();
ASSERT(IS_VALID_LOG_FILE_ENTRY(pFile));
ASSERT(g_UlDisableLogBuffering == 0);
pLogBuffer = pFile->LogBuffer;
UlTrace(LOGGING,("Http!UlpWriteToLogFileShared: pEntry %p\n", pFile));
//
// Bail out and try the exclusive writer for cases;
//
// 1. No log buffer available.
// 2. Logging ceased. (NULL handle)
// 3. Title needs to be written.
// 4. The actual log file itself has to be recycled.
//
// Otherwise proceed with appending to the current buffer
// if there is enough space avialable for us. If not;
//
// 5. Bail out to get a new buffer
//
if ( pLogBuffer==NULL ||
pFile->pLogFile==NULL ||
!pFile->Flags.LogTitleWritten ||
UlpIsLogFileOverFlow(pFile,RecordSize)
)
{
return STATUS_MORE_PROCESSING_REQUIRED;
}
//
// Reserve space in pLogBuffer by InterlockedCompareExchange add
// RecordSize. If we exceed the limit, bail out and take the
// exclusive lock to flush the buffer.
//
do
{
BufferUsed = *((volatile LONG *) &pLogBuffer->BufferUsed);
if ( RecordSize + BufferUsed > g_UlLogBufferSize )
{
return STATUS_MORE_PROCESSING_REQUIRED;
}
PAUSE_PROCESSOR;
} while (BufferUsed != InterlockedCompareExchange(
&pLogBuffer->BufferUsed,
RecordSize + BufferUsed,
BufferUsed
));
//
// Keep buffering until our buffer is full.
//
UlpAppendToLogBuffer(
pFile,
BufferUsed,
RecordSize,
pRecord,
UsedOffset1,
UsedOffset2
);
return STATUS_SUCCESS;
}
/***************************************************************************++
Routine Description:
By assuming that it's holding the entrie's eresource exclusively
this function does various functions;
- It Writes a record to a log file
REQUIRES you to hold the loglist resource shared
Arguments:
pFile - Handle to a log file entry
RecordSize - Length of the record to be written.
--***************************************************************************/
NTSTATUS
UlpWriteToLogFileExclusive(
IN PUL_LOG_FILE_ENTRY pFile,
IN ULONG RecordSize,
IN PCHAR pRecord,
IN ULONG UsedOffset1,
IN ULONG UsedOffset2
)
{
PUL_LOG_FILE_BUFFER pLogBuffer;
NTSTATUS Status = STATUS_SUCCESS;
ULONG RecordSizePlusTitle = RecordSize;
CHAR TitleBuffer[UL_MAX_TITLE_BUFFER_SIZE];
ULONG TitleBufferSize = UL_MAX_TITLE_BUFFER_SIZE;
PAGED_CODE();
ASSERT(IS_VALID_LOG_FILE_ENTRY(pFile));
ASSERT(g_UlDisableLogBuffering == 0);
ASSERT(UlDbgPushLockOwnedExclusive(&pFile->EntryPushLock));
UlTrace(LOGGING,("Http!UlpWriteToLogFileExclusive: pEntry %p\n", pFile));
//
// First append title to the temp buffer to calculate the size of
// the title if we need to write the title as well.
//
if (!pFile->Flags.LogTitleWritten)
{
UlpAppendW3CLogTitle(pFile, TitleBuffer, &TitleBufferSize);
RecordSizePlusTitle += TitleBufferSize;
}
//
// Now check log file overflow.
//
if (UlpIsLogFileOverFlow(pFile,RecordSizePlusTitle))
{
//
// We already acquired the LogListResource Shared and the
// entry eresource exclusive. Therefore ReCycle is fine. Look
// at the comment in UlpWriteToLogFile.
//
Status = UlpRecycleLogFile(pFile);
}
if (pFile->pLogFile==NULL || !NT_SUCCESS(Status))
{
//
// If somehow the logging ceased and handle released,it happens
// when recycle isn't able to write to the log drive.
//
return Status;
}
pLogBuffer = pFile->LogBuffer;
if (pLogBuffer)
{
//
// There are two conditions we execute the following if block
// 1. We were blocked on eresource exclusive and before us some
// other thread already take care of the buffer flush or recycling.
// 2. Reconfiguration happened and log attempt needs to write the
// title again.
//
if (RecordSizePlusTitle + pLogBuffer->BufferUsed <= g_UlLogBufferSize)
{
//
// If this is the first log attempt after a reconfig, then we have
// to write the title here. Reconfig doesn't immediately write the
// title but rather depend on us by setting the LogTitleWritten flag
// to false.
//
if (!pFile->Flags.LogTitleWritten)
{
ASSERT(RecordSizePlusTitle > RecordSize);
ASSERT(pFile->Format == HttpLoggingTypeW3C);
UlpAppendW3CLogTitle(pFile, NULL, NULL);
pFile->Flags.LogTitleWritten = 1;
pFile->Flags.TitleFlushPending = 1;
}
UlpAppendToLogBuffer(
pFile,
pLogBuffer->BufferUsed,
RecordSize,
pRecord,
UsedOffset1,
UsedOffset2
);
pLogBuffer->BufferUsed += RecordSize;
return STATUS_SUCCESS;
}
//
// Flush out the buffer first then proceed with allocating a new one.
//
Status = UlpFlushLogFile(pFile);
if (!NT_SUCCESS(Status))
{
return Status;
}
}
ASSERT(pFile->LogBuffer == NULL);
//
// This can be the very first log attempt or the previous allocation
// of LogBuffer failed, or the previous hit flushed and deallocated
// the old buffer. In either case, we allocate a new one,append the
// (title plus) new record and return for more/shared processing.
//
pLogBuffer = pFile->LogBuffer = UlPplAllocateLogFileBuffer();
if (pLogBuffer == NULL)
{
return STATUS_NO_MEMORY;
}
//
// Very first attempt needs to write the title, as well as the attempt
// which causes the log file recycling. Both cases comes down here
//
if (!pFile->Flags.LogTitleWritten)
{
ASSERT(pFile->Format == HttpLoggingTypeW3C);
UlpAppendW3CLogTitle(pFile, NULL, NULL);
pFile->Flags.LogTitleWritten = 1;
pFile->Flags.TitleFlushPending = 1;
}
UlpAppendToLogBuffer(
pFile,
pLogBuffer->BufferUsed,
RecordSize,
pRecord,
UsedOffset1,
UsedOffset2
);
pLogBuffer->BufferUsed += RecordSize;
return STATUS_SUCCESS;
}
/***************************************************************************++
Routine Description:
Create or open a new file from the existing fully qualifed file name on
the entry.
Arguments:
pEntry : Corresponding entry that we are closing and opening
the log files for.
pConfigGroup : Current configuration for the entry.
--***************************************************************************/
NTSTATUS
UlpCreateLogFile(
IN OUT PUL_LOG_FILE_ENTRY pEntry,
IN PUL_CONFIG_GROUP_OBJECT pConfigGroup
)
{
NTSTATUS Status;
PUNICODE_STRING pDirectory;
//
// Sanity check.
//
PAGED_CODE();
Status = STATUS_SUCCESS;
ASSERT(IS_VALID_LOG_FILE_ENTRY(pEntry));
ASSERT(IS_VALID_CONFIG_GROUP(pConfigGroup));
pDirectory = &pConfigGroup->LoggingConfig.LogFileDir;
UlTrace(LOGGING,("Http!UlpCreateLogFile: pEntry %p\n", pEntry));
//
// It's possible that LogFileDir.Buffer could be NULL,
// if the allocation failed during the Set cgroup ioctl.
//
if (pDirectory == NULL || pDirectory->Buffer == NULL)
{
return STATUS_INVALID_PARAMETER;
}
//
// Build the fully qualified file name.
//
Status = UlRefreshFileName(pDirectory,
&pEntry->FileName,
&pEntry->pShortName
);
if (!NT_SUCCESS(Status))
{
return Status;
}
//
// Set the sequence number stale so that the recylcler below can
// obtain the proper number by scanning the directory.
//
pEntry->Flags.StaleSequenceNumber = 1;
//
// This is the first time we are creating this log file,
// set the time to expire stale so that recycle will
// calculate it for us.
//
pEntry->Flags.StaleTimeToExpire = 1;
//
// After this, recycle does the whole job for us.
//
Status = UlpRecycleLogFile(pEntry);
if (!NT_SUCCESS(Status))
{
UlTrace(LOGGING,(
"Http!UlpCreateLogFile: Filename: %S Status %08lx\n",
pEntry->FileName.Buffer,
Status
));
}
return Status;
}
/***************************************************************************++
Routine Description:
When logging configuration happens we create the entry but not the log
file itself yet. Log file itself will be created when the first request
comes in. Please look at UlpCreateLogFile.
Arguments:
pConfigGroup - Supplies the necessary information for constructing the
log file entry.
pUserConfig - Logging config from the user.
--***************************************************************************/
NTSTATUS
UlCreateLogEntry(
IN OUT PUL_CONFIG_GROUP_OBJECT pConfigGroup,
IN PHTTP_CONFIG_GROUP_LOGGING pUserConfig
)
{
NTSTATUS Status;
PUL_LOG_FILE_ENTRY pNewEntry;
PHTTP_CONFIG_GROUP_LOGGING pConfig;
//
// Sanity check.
//
PAGED_CODE();
ASSERT(IS_VALID_CONFIG_GROUP(pConfigGroup));
Status = STATUS_SUCCESS;
pNewEntry = NULL;
//
// We have to acquire the LogListresource exclusively, prior to
// the operations Create/Remove/ReConfig and anything touches to
// the cgroup log parameters.
//
UlAcquirePushLockExclusive(&g_pUlNonpagedData->LogListPushLock);
ASSERT(pConfigGroup->pLogFileEntry == NULL);
//
// Save the user logging info to the config group.
//
pConfigGroup->LoggingConfig = *pUserConfig;
pConfig = &pConfigGroup->LoggingConfig;
pConfig->LogFileDir.Buffer =
(PWSTR) UL_ALLOCATE_ARRAY(
PagedPool,
UCHAR,
pConfig->LogFileDir.MaximumLength,
UL_CG_LOGDIR_POOL_TAG
);
if (pConfig->LogFileDir.Buffer == NULL)
{
Status = STATUS_NO_MEMORY;
goto end;
}
RtlCopyMemory(
pConfig->LogFileDir.Buffer,
pUserConfig->LogFileDir.Buffer,
pUserConfig->LogFileDir.MaximumLength
);
pConfig->Flags.Present = 1;
pConfig->LoggingEnabled = TRUE;
//
// Now add a new entry to the global list of log entries.
//
Status = UlpConstructLogEntry(pConfig,&pNewEntry);
if (!NT_SUCCESS(Status))
goto end;
//
// Get the site id from the cgroup. Site id doesn't change
// during the lifetime of the cgroup.
//
pNewEntry->SiteId = pConfigGroup->SiteId;
UlpInsertLogEntry(pNewEntry);
pConfigGroup->pLogFileEntry = pNewEntry;
UlTrace(LOGGING,
("Http!UlCreateLogEntry: pEntry %p created for %S pConfig %p Rollover %d\n",
pNewEntry,
pConfig->LogFileDir.Buffer,
pConfigGroup,
pNewEntry->Flags.LocaltimeRollover
));
end:
if (!NT_SUCCESS(Status))
{
UlTrace(LOGGING,("Http!UlCreateLogEntry: dir %S failure %08lx\n",
pConfig->LogFileDir.Buffer,
Status
));
//
// Restore the logging disabled state on the cgroup, free the
// memory for the dir.
//
if (pConfig->LogFileDir.Buffer)
{
UL_FREE_POOL(pConfig->LogFileDir.Buffer,
UL_CG_LOGDIR_POOL_TAG
);
}
pConfig->LogFileDir.Buffer = NULL;
ASSERT(pConfigGroup->pLogFileEntry == NULL);
pConfig->Flags.Present = 0;
pConfig->LoggingEnabled = FALSE;
}
UlReleasePushLockExclusive(&g_pUlNonpagedData->LogListPushLock);
return Status;
}
/***************************************************************************++
Routine Description:
Inserts a log file entry to our global log entry list.
REQUIRES caller to have LogListresource EXCLUSIVELY.
Arguments:
pEntry - The log file entry to be added to the global list
pTimeFields - The current time fields.
--***************************************************************************/
VOID
UlpInsertLogEntry(
IN PUL_LOG_FILE_ENTRY pEntry
)
{
LONG listSize;
HTTP_LOGGING_PERIOD Period;
KIRQL oldIrql;
//
// Sanity check.
//
PAGED_CODE();
ASSERT(IS_VALID_LOG_FILE_ENTRY(pEntry));
//
// add to the list
//
InsertHeadList(&g_LogListHead, &pEntry->LogFileListEntry);
Period = pEntry->Period;
listSize = InterlockedIncrement(&g_LogListEntryCount);
ASSERT(listSize >= 1);
//
// Time to start the Log Timer if we haven't done it yet.
// Once we start this timer it keeps working until the
// termination of the driver. Start the timer only if the
// entry is running on a time dependent log format.
//
if (Period != HttpLoggingPeriodMaxSize)
{
UlAcquireSpinLock(&g_LogTimer.SpinLock, &oldIrql);
if (g_LogTimer.Started == FALSE)
{
UlSetLogTimer(&g_LogTimer);
g_LogTimer.Started = TRUE;
}
UlReleaseSpinLock(&g_LogTimer.SpinLock, oldIrql);
}
//
// Go ahead and start the buffer timer as soon as we have
// a log entry.
//
UlAcquireSpinLock(&g_BufferTimer.SpinLock, &oldIrql);
if (g_BufferTimer.Started == FALSE)
{
UlSetBufferTimer(&g_BufferTimer);
g_BufferTimer.Started = TRUE;
}
UlReleaseSpinLock(&g_BufferTimer.SpinLock, oldIrql);
}
/***************************************************************************++
Routine Description:
Removes a log file entry from our global log entry list. Also cleans up
the config group's logging settings ( only directory string )
Arguments:
pEntry - The log file entry to be removed from the global list
--***************************************************************************/
VOID
UlRemoveLogEntry(
IN PUL_CONFIG_GROUP_OBJECT pConfigGroup
)
{
LONG listSize;
PUL_LOG_FILE_ENTRY pEntry;
//
// Sanity check.
//
PAGED_CODE();
UlAcquirePushLockExclusive(&g_pUlNonpagedData->LogListPushLock);
//
// Clean up config group's directory string.
//
if (pConfigGroup->LoggingConfig.LogFileDir.Buffer)
{
UL_FREE_POOL(
pConfigGroup->LoggingConfig.LogFileDir.Buffer,
UL_CG_LOGDIR_POOL_TAG );
}
pEntry = pConfigGroup->pLogFileEntry;
if (pEntry == NULL)
{
UlReleasePushLockExclusive(&g_pUlNonpagedData->LogListPushLock);
return;
}
ASSERT(IS_VALID_LOG_FILE_ENTRY(pEntry));
RemoveEntryList(&pEntry->LogFileListEntry);
pEntry->LogFileListEntry.Flink =
pEntry->LogFileListEntry.Blink = NULL;
if (pEntry->pLogFile != NULL)
{
//
// Flush the buffer, close the file and mark the entry
// inactive.
//
UlpMakeEntryInactive(pEntry);
}
//
// Free up the FileName (allocated when the entry becomes active
// otherwise it's empty)
//
if (pEntry->FileName.Buffer)
{
UL_FREE_POOL(pEntry->FileName.Buffer,UL_CG_LOGDIR_POOL_TAG);
pEntry->FileName.Buffer = NULL;
}
//
// Delete the entry eresource
//
UlDeletePushLock(&pEntry->EntryPushLock);
listSize = InterlockedDecrement(&g_LogListEntryCount);
ASSERT(listSize >= 0);
UlTrace(LOGGING,
("Http!UlRemoveLogFileEntry: pEntry %p removed\n",
pEntry
));
if (pEntry->LogBuffer)
{
UlPplFreeLogFileBuffer(pEntry->LogBuffer);
}
UL_FREE_POOL_WITH_SIG(pEntry,UL_LOG_FILE_ENTRY_POOL_TAG);
UlReleasePushLockExclusive(&g_pUlNonpagedData->LogListPushLock);
}
/***************************************************************************++
Routine Description:
Initializes the Log recycling and the buffering timers.
--***************************************************************************/
VOID
UlpInitializeTimers(
VOID
)
{
// Guard against multiple inits
if (g_InitLogTimersCalled) return;
g_InitLogTimersCalled = TRUE;
// Log timer
g_LogTimer.Initialized = TRUE;
g_LogTimer.Started = FALSE;
g_LogTimer.Period = -1;
g_LogTimer.PeriodType = UlLogTimerPeriodNone;
UlInitializeSpinLock(&g_LogTimer.SpinLock, "g_LogTimersSpinLock");
KeInitializeDpc(
&g_LogTimer.DpcObject, // DPC object
&UlLogTimerDpcRoutine, // DPC routine
NULL // context
);
KeInitializeTimer(&g_LogTimer.Timer);
// Buffer timer
g_BufferTimer.Initialized = TRUE;
g_BufferTimer.Started = FALSE;
g_BufferTimer.Period = -1; // Not used
g_BufferTimer.PeriodType = UlLogTimerPeriodNone; // Not used
UlInitializeSpinLock(&g_BufferTimer.SpinLock, "g_BufferTimersSpinLock");
KeInitializeDpc(
&g_BufferTimer.DpcObject, // DPC object
&UlBufferTimerDpcRoutine, // DPC routine
NULL // context
);
KeInitializeTimer(&g_BufferTimer.Timer);
}
/***************************************************************************++
Routine Description:
Terminates the Log & buffering Timers
--***************************************************************************/
VOID
UlpTerminateTimers(
VOID
)
{
KIRQL oldIrql;
// Guard against multiple terminates
if (!g_InitLogTimersCalled) return;
g_InitLogTimersCalled = FALSE;
// Log timer
UlAcquireSpinLock(&g_LogTimer.SpinLock, &oldIrql);
g_LogTimer.Initialized = FALSE;
KeCancelTimer(&g_LogTimer.Timer);
UlReleaseSpinLock(&g_LogTimer.SpinLock, oldIrql);
// Buffer timer
UlAcquireSpinLock(&g_BufferTimer.SpinLock, &oldIrql);
g_BufferTimer.Initialized = FALSE;
KeCancelTimer(&g_BufferTimer.Timer);
UlReleaseSpinLock(&g_BufferTimer.SpinLock, oldIrql);
}
/***************************************************************************++
Routine Description:
Work item for the threadpool that goes thru the log list and
cycle the necessary logs.
Arguments:
PUL_WORK_ITEM - Ignored but freed up once we are done.
--***************************************************************************/
VOID
UlLogTimerHandler(
IN PUL_WORK_ITEM pWorkItem
)
{
NTSTATUS Status;
PLIST_ENTRY pLink;
PUL_LOG_FILE_ENTRY pEntry;
BOOLEAN Picked;
KIRQL OldIrql;
PAGED_CODE();
UlTrace(LOGGING,("Http!UlLogTimerHandler: Scanning the log entries ...\n"));
UlAcquirePushLockExclusive(&g_pUlNonpagedData->LogListPushLock);
// Attempt to reinit the GMT offset every hour, to pickup the changes
// because of the day light changes. Synced by the logging eresource.
UlpGetGMTOffset();
for (pLink = g_LogListHead.Flink;
pLink != &g_LogListHead;
pLink = pLink->Flink
)
{
pEntry = CONTAINING_RECORD(
pLink,
UL_LOG_FILE_ENTRY,
LogFileListEntry
);
//
// We should not recycle this entry if it's period
// is not time based but size based.
//
UlAcquirePushLockExclusive(&pEntry->EntryPushLock);
switch(g_LogTimer.PeriodType)
{
//
// Rollover table:
//
// LocaltimeRollover
// TRUE FALSE (Default)
// Format
// ------------------------------
// W3C | Local | GMT |
// -------------------------
// NCSA | Local | Local |
// -------------------------
// IIS | Local | Local |
// -------------------------
//
// If the timer waked up at the beginning of an hour
// for GMT, LocalTime or Both. E.g.
//
// 1) For Pacific Time Zone: (-8:00)
// PeriodType will always be UlLogTimerPeriodBoth
// and all of the entries will rollover regardless
// of their format.
//
// 2) For Adelaide (Australia) (+9:30)
// Timer will wake up seperately for GMT & Local.
// NCSA & IIS entries will always rollover at
// UlLogTimerPeriodLocal, W3C will rollover at
// UlLogTimerPeriodLocal only if LocaltimeRollover
// is set otherwise it will rollover at
// UlLogTimerPeriodGMT.
//
case UlLogTimerPeriodGMT:
//
// Only entries with W3C format type may rollover
// at GMT only time interval.
//
Picked = (BOOLEAN) ((pEntry->Flags.LocaltimeRollover == 0)
&& (pEntry->Format == HttpLoggingTypeW3C));
break;
case UlLogTimerPeriodLocal:
//
// Entries with NCSA or IIS format type always rollover
// at Local time interval. W3C may also rollover if
// LocaltimeRollover is set.
//
Picked = (BOOLEAN) ((pEntry->Flags.LocaltimeRollover == 1)
|| (pEntry->Format != HttpLoggingTypeW3C));
break;
case UlLogTimerPeriodBoth:
//
// We really don't care what format the entry has,
// since the local time and GMT hourly beginnings are
// aligned.
//
Picked = TRUE;
break;
default:
ASSERT(!"Unexpected timer period type !\n");
Picked = FALSE;
break;
}
if (Picked &&
pEntry->Flags.Active &&
pEntry->Period != HttpLoggingPeriodMaxSize
)
{
if (pEntry->TimeToExpire == 1)
{
pEntry->Flags.StaleTimeToExpire = 1;
//
// Mark the entry inactive and postpone the recycle
// until the next request arrives.
//
Status = UlpMakeEntryInactive(pEntry);
}
else
{
//
// Just decrement the hourly counter for this time.
//
pEntry->TimeToExpire -= 1;
}
}
UlReleasePushLockExclusive(&pEntry->EntryPushLock);
}
UlReleasePushLockExclusive(&g_pUlNonpagedData->LogListPushLock);
//
// Free the memory allocated (ByDpcRoutine below) for
// this work item.
//
UL_FREE_POOL( pWorkItem, UL_WORK_ITEM_POOL_TAG );
//
// Now reset the timer for the next hour.
//
UlAcquireSpinLock(&g_LogTimer.SpinLock, &OldIrql);
if (g_LogTimer.Initialized == TRUE)
{
UlSetLogTimer(&g_LogTimer);
}
UlReleaseSpinLock(&g_LogTimer.SpinLock, OldIrql);
}
/***************************************************************************++
Routine Description:
Allocates and queues a work item to do the the actual work at lowered
irql.
Arguments:
Ignored
--***************************************************************************/
VOID
UlLogTimerDpcRoutine(
PKDPC Dpc,
PVOID DeferredContext,
PVOID SystemArgument1,
PVOID SystemArgument2
)
{
PUL_WORK_ITEM pWorkItem;
//
// Parameters are ignored.
//
UNREFERENCED_PARAMETER(Dpc);
UNREFERENCED_PARAMETER(DeferredContext);
UNREFERENCED_PARAMETER(SystemArgument1);
UNREFERENCED_PARAMETER(SystemArgument2);
UlAcquireSpinLockAtDpcLevel(&g_LogTimer.SpinLock);
if (g_LogTimer.Initialized == TRUE)
{
//
// It's not possible to acquire the resource which protects
// the log list at DISPATCH_LEVEL therefore we will queue a
// work item for this.
//
pWorkItem = (PUL_WORK_ITEM) UL_ALLOCATE_POOL(
NonPagedPool,
sizeof(*pWorkItem),
UL_WORK_ITEM_POOL_TAG
);
if (pWorkItem)
{
UlInitializeWorkItem(pWorkItem);
UL_QUEUE_WORK_ITEM(pWorkItem, &UlLogTimerHandler);
}
else
{
UlTrace(LOGGING,("Http!UlLogTimerDpcRoutine: Not enough memory!\n"));
}
}
UlReleaseSpinLockFromDpcLevel(&g_LogTimer.SpinLock);
}
/***************************************************************************++
Routine Description:
Queues a passive worker for the lowered irql.
Arguments:
Ignored
--***************************************************************************/
VOID
UlBufferTimerDpcRoutine(
PKDPC Dpc,
PVOID DeferredContext,
PVOID SystemArgument1,
PVOID SystemArgument2
)
{
PUL_WORK_ITEM pWorkItem;
//
// Parameters are ignored.
//
UNREFERENCED_PARAMETER(Dpc);
UNREFERENCED_PARAMETER(DeferredContext);
UNREFERENCED_PARAMETER(SystemArgument1);
UNREFERENCED_PARAMETER(SystemArgument2);
UlAcquireSpinLockAtDpcLevel(&g_BufferTimer.SpinLock);
if (g_BufferTimer.Initialized == TRUE)
{
//
// It's not possible to acquire the resource which protects
// the log list at DISPATCH_LEVEL therefore we will queue a
// work item for this.
//
pWorkItem = (PUL_WORK_ITEM) UL_ALLOCATE_POOL(
NonPagedPool,
sizeof(*pWorkItem),
UL_WORK_ITEM_POOL_TAG
);
if (pWorkItem)
{
UlInitializeWorkItem(pWorkItem);
UL_QUEUE_WORK_ITEM(pWorkItem, &UlBufferTimerHandler);
}
else
{
UlTrace(LOGGING,("Http!UlBufferTimerDpcRoutine: Not enough memory.\n"));
}
}
UlReleaseSpinLockFromDpcLevel(&g_BufferTimer.SpinLock);
}
/***************************************************************************++
Routine Description:
UlLogBufferTimerHandler :
Work item for the threadpool that goes thru the log list and
flush the log's file buffers.
Arguments:
PUL_WORK_ITEM - Ignored but cleaned up at the end
--***************************************************************************/
VOID
UlBufferTimerHandler(
IN PUL_WORK_ITEM pWorkItem
)
{
NTSTATUS Status;
PLIST_ENTRY pLink;
PUL_LOG_FILE_ENTRY pEntry;
PAGED_CODE();
UlTrace(LOGGING,("Http!UlBufferTimerHandler: Scanning the log entries ...\n"));
UlAcquirePushLockShared(&g_pUlNonpagedData->LogListPushLock);
for (pLink = g_LogListHead.Flink;
pLink != &g_LogListHead;
pLink = pLink->Flink
)
{
pEntry = CONTAINING_RECORD(
pLink,
UL_LOG_FILE_ENTRY,
LogFileListEntry
);
UlAcquirePushLockExclusive(&pEntry->EntryPushLock);
//
// Entry may be staying inactive since no request came in yet.
//
if (pEntry->Flags.Active)
{
if (pEntry->Flags.RecyclePending)
{
//
// Try to resurrect it back.
//
Status = UlpRecycleLogFile(pEntry);
}
else
{
//
// Everything is fine simply flush.
//
if (NULL != pEntry->LogBuffer && 0 != pEntry->LogBuffer->BufferUsed)
{
Status = UlpFlushLogFile(pEntry);
}
else
{
//
// Decrement the idle counter and close the file if necessary.
//
ASSERT( pEntry->TimeToClose > 0 );
if (pEntry->TimeToClose == 1)
{
//
// Entry was staying inactive for too long, disable it.
// But next recycle should recalculate the timeToExpire
// or determine the proper sequence number according to
// the current period type.
//
if (pEntry->Period == HttpLoggingPeriodMaxSize)
{
pEntry->Flags.StaleSequenceNumber = 1;
}
else
{
pEntry->Flags.StaleTimeToExpire = 1;
}
Status = UlpMakeEntryInactive(pEntry);
}
else
{
pEntry->TimeToClose -= 1;
}
}
}
}
UlReleasePushLockExclusive(&pEntry->EntryPushLock);
}
UlReleasePushLockShared(&g_pUlNonpagedData->LogListPushLock);
//
// Free the memory allocated (ByDpcRoutine below) to
// this work item.
//
UL_FREE_POOL( pWorkItem, UL_WORK_ITEM_POOL_TAG );
}
/***************************************************************************++
Routine Description:
UlReconfigureLogEntry :
This function implements the logging reconfiguration per attribute.
Everytime config changes happens we try to update the existing logging
parameters here.
Arguments:
pConfig - corresponding cgroup object
pCfgCurrent - Current logging config on the cgroup object
pCfgNew - New logging config passed down by the user.
--***************************************************************************/
NTSTATUS
UlReConfigureLogEntry(
IN PUL_CONFIG_GROUP_OBJECT pConfigGroup,
IN PHTTP_CONFIG_GROUP_LOGGING pCfgCurrent,
IN PHTTP_CONFIG_GROUP_LOGGING pCfgNew
)
{
NTSTATUS Status ;
PUL_LOG_FILE_ENTRY pEntry;
BOOLEAN HaveToReCycle;
//
// Sanity check first
//
PAGED_CODE();
Status = STATUS_SUCCESS;
HaveToReCycle = FALSE;
UlTrace(LOGGING,("Http!UlReConfigureLogEntry: entry %p\n",
pConfigGroup->pLogFileEntry));
if (pCfgCurrent->LoggingEnabled==FALSE && pCfgNew->LoggingEnabled==FALSE)
{
//
// Do nothing. Not even update the fields. As soon as we get enable,
// field update will take place anyway.
//
return Status;
}
//
// No matter what ReConfiguration should acquire the LogListResource
// exclusively.
//
UlAcquirePushLockExclusive(&g_pUlNonpagedData->LogListPushLock);
pEntry = pConfigGroup->pLogFileEntry;
ASSERT(IS_VALID_LOG_FILE_ENTRY(pEntry));
if (pCfgCurrent->LoggingEnabled==TRUE && pCfgNew->LoggingEnabled==FALSE)
{
//
// Disable the entry if necessary.
//
if (pEntry->Flags.Active == 1)
{
Status = UlpMakeEntryInactive(pEntry);
}
pCfgCurrent->LoggingEnabled = FALSE;
goto end;
}
else
{
pCfgCurrent->LoggingEnabled = TRUE;
}
//
// If LogEntry is Inactive (means no request served for this site yet and
// the LogFile itself hasn't been created yet), all we have to do is flush
// the settings on the LogEntry, the cgroup and then return.
//
if (!pEntry->Flags.Active)
{
ASSERT(pEntry->pLogFile == NULL);
if (RtlCompareUnicodeString(&pCfgNew->LogFileDir,
&pCfgCurrent->LogFileDir, TRUE)
!= 0)
{
//
// Store the new directory in the cgroup even if the entry is
// inactive. Discard the return value, if failure happens we
// keep the old directory.
//
UlCopyLogFileDir(
&pCfgCurrent->LogFileDir,
&pCfgNew->LogFileDir
);
//
// If creation fails later, we should event log.
//
pEntry->Flags.CreateFileFailureLogged = 0;
}
pEntry->Format = pCfgNew->LogFormat;
pCfgCurrent->LogFormat = pCfgNew->LogFormat;
pEntry->Period = (HTTP_LOGGING_PERIOD) pCfgNew->LogPeriod;
pCfgCurrent->LogPeriod = pCfgNew->LogPeriod;
pEntry->TruncateSize = pCfgNew->LogFileTruncateSize;
pCfgCurrent->LogFileTruncateSize = pCfgNew->LogFileTruncateSize;
pEntry->LogExtFileFlags = pCfgNew->LogExtFileFlags;
pCfgCurrent->LogExtFileFlags = pCfgNew->LogExtFileFlags;
pCfgCurrent->LocaltimeRollover = pCfgNew->LocaltimeRollover;
pEntry->Flags.LocaltimeRollover = (pCfgNew->LocaltimeRollover ? 1 : 0);
pCfgCurrent->SelectiveLogging = pCfgNew->SelectiveLogging;
if (pEntry->Format != HttpLoggingTypeW3C)
{
pEntry->Flags.LogTitleWritten = 1;
}
goto end;
}
//
// if the entry was active then proceed down to do proper reconfiguration
// and recyle immediately if it's necessary.
//
Status = UlCheckLogDirectory(&pCfgNew->LogFileDir);
if (!NT_SUCCESS(Status))
{
// Otherwise keep the old settings
goto end;
}
if (RtlCompareUnicodeString(
&pCfgNew->LogFileDir, &pCfgCurrent->LogFileDir, TRUE) != 0)
{
//
// Store the new directory in the config group.
//
Status = UlCopyLogFileDir(&pCfgCurrent->LogFileDir,
&pCfgNew->LogFileDir);
if (!NT_SUCCESS(Status))
{
goto end;
}
//
// Rebuild the fully qualified file name.
//
Status = UlRefreshFileName(&pCfgCurrent->LogFileDir,
&pEntry->FileName,
&pEntry->pShortName
);
if (!NT_SUCCESS(Status))
{
goto end;
}
//
// Set the sequence number stale so that the recylcler below can
// obtain the proper number by scanning the directory.
//
pEntry->Flags.StaleSequenceNumber = 1;
HaveToReCycle = TRUE;
}
if (pCfgNew->LogFormat != pCfgCurrent->LogFormat)
{
pCfgCurrent->LogFormat = pCfgNew->LogFormat;
pEntry->Format = pCfgNew->LogFormat;
pEntry->Flags.StaleTimeToExpire = 1;
pEntry->Flags.StaleSequenceNumber = 1;
HaveToReCycle = TRUE;
}
if (pCfgNew->LogPeriod != pCfgCurrent->LogPeriod)
{
pCfgCurrent->LogPeriod = pCfgNew->LogPeriod;
pEntry->Period = (HTTP_LOGGING_PERIOD) pCfgNew->LogPeriod;
pEntry->Flags.StaleTimeToExpire = 1;
pEntry->Flags.StaleSequenceNumber = 1;
HaveToReCycle = TRUE;
}
if (pCfgNew->LogFileTruncateSize != pCfgCurrent->LogFileTruncateSize)
{
if (TRUE == UlUpdateLogTruncateSize(
pCfgNew->LogFileTruncateSize,
&pCfgCurrent->LogFileTruncateSize,
&pEntry->TruncateSize,
pEntry->TotalWritten
))
{
HaveToReCycle = TRUE;
}
}
if (pCfgNew->LogExtFileFlags != pCfgCurrent->LogExtFileFlags)
{
//
// Just a change in the flags should not cause us to recyle.
// Unless ofcourse something else is also changed.
//
pCfgCurrent->LogExtFileFlags = pCfgNew->LogExtFileFlags;
pEntry->LogExtFileFlags = pCfgNew->LogExtFileFlags;
if (pEntry->Format == HttpLoggingTypeW3C)
{
pEntry->Flags.LogTitleWritten = 0;
}
}
if (pCfgNew->LocaltimeRollover != pCfgCurrent->LocaltimeRollover)
{
//
// Need to reclycle if the format is W3C.
//
pCfgCurrent->LocaltimeRollover = pCfgNew->LocaltimeRollover;
pEntry->Flags.LocaltimeRollover = (pCfgNew->LocaltimeRollover ? 1 : 0);
HaveToReCycle = TRUE;
}
//
// Copy over the new selective logging criteria. No change is required
// at this time.
//
pCfgCurrent->SelectiveLogging = pCfgNew->SelectiveLogging;
if (HaveToReCycle)
{
//
// Mark the entry inactive and postpone the recycle until the next
// request arrives.
//
Status = UlpMakeEntryInactive(pEntry);
}
end:
if (!NT_SUCCESS(Status))
{
UlTrace(LOGGING,("Http!UlReConfigureLogEntry: entry %p, failure %08lx\n",
pEntry,
Status
));
}
UlReleasePushLockExclusive(&g_pUlNonpagedData->LogListPushLock);
return Status;
} // UlReConfigureLogEntry
/***************************************************************************++
Routine Description:
Marks the entry inactive, closes the existing file.
Caller should hold the log list eresource exclusive.
Arguments:
pEntry - The log file entry which we will mark inactive.
--***************************************************************************/
NTSTATUS
UlpMakeEntryInactive(
IN OUT PUL_LOG_FILE_ENTRY pEntry
)
{
//
// Sanity checks
//
PAGED_CODE();
UlTrace(LOGGING,("Http!UlpMakeEntryInactive: entry %p disabled.\n",
pEntry
));
//
// Flush and close the old file until the next recycle.
//
if (pEntry->pLogFile != NULL)
{
UlpFlushLogFile(pEntry);
UlCloseLogFile(&pEntry->pLogFile);
}
//
// Mark this inactive so that the next http hit awakens the entry.
//
pEntry->Flags.Active = 0;
return STATUS_SUCCESS;
}
/***************************************************************************++
Routine Description:
When the config group, the one that owns the log entry is disabled or lost
all of its URLs then we temporarly disable the entry by marking it inactive
until the config group get enabled and/or receives a URL.
Arguments:
pEntry - The log file entry which we will disable.
--***************************************************************************/
NTSTATUS
UlDisableLogEntry(
IN OUT PUL_LOG_FILE_ENTRY pEntry
)
{
NTSTATUS Status = STATUS_SUCCESS;
PAGED_CODE();
ASSERT(IS_VALID_LOG_FILE_ENTRY(pEntry));
UlAcquirePushLockExclusive(&g_pUlNonpagedData->LogListPushLock);
//
// If the entry is already disabled. Perhaps because of a recent reconfig,
// then just bail out.
//
if (pEntry->Flags.Active == 1)
{
//
// Once the entry is disabled, it will be awaken when the next hit
// happens.And that obviously cannot happen before cgroup receives
// a new URL.
//
Status = UlpMakeEntryInactive(pEntry);
}
UlReleasePushLockExclusive(&g_pUlNonpagedData->LogListPushLock);
return Status;
}
/***************************************************************************++
Routine Description:
Allocates the necessary file entry from non-paged pool. This entry
get removed from the list when the corresponding config group object
has been destroyed. At that time RemoveLogFile entry called and
it frees this memory.
Arguments:
pConfig - corresponding cgroup object
ppEntry - will point to newly created entry.
--***************************************************************************/
NTSTATUS
UlpConstructLogEntry(
IN PHTTP_CONFIG_GROUP_LOGGING pConfig,
OUT PUL_LOG_FILE_ENTRY * ppEntry
)
{
NTSTATUS Status;
PUL_LOG_FILE_ENTRY pEntry;
//
// Sanity check and init.
//
PAGED_CODE();
ASSERT(pConfig);
ASSERT(ppEntry);
Status = STATUS_SUCCESS;
pEntry = NULL;
//
// Allocate a memory for our new logfile entry in the list. To avoid the
// frequent reallocs for the log entry.E.g. we receive a timer update
// and the filename changes according to new time.We will try to allocate
// a fixed amount here for all the possible file_names ( But this doesn't
// include the log_dir changes may happen through cgroup. In that case we
// will reallocate a new one) It should be nonpaged because it holds an
// eresource.
//
pEntry = UL_ALLOCATE_STRUCT(
NonPagedPool,
UL_LOG_FILE_ENTRY,
UL_LOG_FILE_ENTRY_POOL_TAG
);
if (pEntry == NULL)
{
return STATUS_NO_MEMORY;
}
pEntry->Signature = UL_LOG_FILE_ENTRY_POOL_TAG;
//
// No filename yet, it will generated when the first hit happens,
// and before we actually create the log file.
//
pEntry->FileName.Buffer = NULL;
pEntry->FileName.Length = 0;
pEntry->FileName.MaximumLength = 0;
//
// Init the entry eresource
//
UlInitializePushLock(
&pEntry->EntryPushLock,
"EntryPushLock",
0,
UL_LOG_FILE_ENTRY_POOL_TAG
);
//
// No file handle or file until a request comes in.
//
pEntry->pLogFile = NULL;
//
// Set the private logging information from config group.
//
pEntry->Format = pConfig->LogFormat;
pEntry->Period = (HTTP_LOGGING_PERIOD) pConfig->LogPeriod;
pEntry->TruncateSize = pConfig->LogFileTruncateSize;
pEntry->LogExtFileFlags = pConfig->LogExtFileFlags;
pEntry->SiteId = 0;
//
// Time to initialize our Log Cycling parameter
//
pEntry->TimeToExpire = 0;
pEntry->TimeToClose = 0;
pEntry->SequenceNumber = 1;
pEntry->TotalWritten.QuadPart = (ULONGLONG) 0;
//
// The entry state bits
//
pEntry->Flags.Value = 0;
if (pEntry->Format != HttpLoggingTypeW3C)
{
pEntry->Flags.LogTitleWritten = 1;
}
if (pConfig->LocaltimeRollover)
{
pEntry->Flags.LocaltimeRollover = 1;
}
//
// LogBuffer get allocated with the first incoming request
//
pEntry->LogBuffer = NULL;
//
// Lets happily return our entry
//
*ppEntry = pEntry;
return STATUS_SUCCESS;
}
/***************************************************************************++
Routine Description:
Small wrapper around handle recycle to ensure it happens under the system
process context.
Arguments:
pEntry - Points to the existing entry.
--***************************************************************************/
NTSTATUS
UlpRecycleLogFile(
IN OUT PUL_LOG_FILE_ENTRY pEntry
)
{
NTSTATUS Status;
PAGED_CODE();
ASSERT(IS_VALID_LOG_FILE_ENTRY(pEntry));
Status = UlQueueLoggingRoutine(
(PVOID) pEntry,
&UlpHandleRecycle
);
return Status;
}
/***************************************************************************++
Routine Description:
This function requires to have the loglist resource shared,as well as
the logfile entry mutex to be acquired.
We do not want anybody to Create/Remove/ReConfig to the entry while
we are working on it, therefore shared access to the loglist.
We do not want anybody to Hit/Flush to the entry, therefore
entry's mutex should be acquired.
Or otherwise caller might have the loglist resource exclusively and
this will automatically ensure the safety as well. As it is not
possible for anybody else to acquire entry mutex first w/o having
the loglist resource shared at least, according to the current
design.
Sometimes it may be necessary to scan the new directory to figure out
the correct sequence numbe rand the file name. Especially after dir
name reconfig and/or the period becomes MaskPeriod.
* Always open/close the log files when running under system process. *
Arguments:
pEntry - Points to the existing entry.
--***************************************************************************/
NTSTATUS
UlpHandleRecycle(
IN OUT PVOID pContext
)
{
NTSTATUS Status;
PUL_LOG_FILE_ENTRY pEntry;
TIME_FIELDS TimeFields;
LARGE_INTEGER TimeStamp;
TIME_FIELDS TimeFieldsLocal;
LARGE_INTEGER TimeStampLocal;
PUL_LOG_FILE_HANDLE pLogFile;
WCHAR _FileName[UL_MAX_FILE_NAME_SUFFIX_LENGTH + 1];
UNICODE_STRING FileName;
BOOLEAN UncShare;
BOOLEAN ACLSupport;
//
// Sanity check.
//
PAGED_CODE();
pEntry = (PUL_LOG_FILE_ENTRY) pContext;
ASSERT(IS_VALID_LOG_FILE_ENTRY(pEntry));
// Always create the log files when running under system process.
ASSERT(g_pUlSystemProcess == (PKPROCESS)IoGetCurrentProcess());
Status = STATUS_SUCCESS;
pLogFile = NULL;
FileName.Buffer = _FileName;
FileName.Length = 0;
FileName.MaximumLength = sizeof(_FileName);
//
// We have two criterions for the log file name
// its LogFormat and its LogPeriod
//
ASSERT(IS_VALID_ANSI_LOGGING_TYPE(pEntry->Format));
ASSERT(pEntry->Period < HttpLoggingPeriodMaximum);
ASSERT(pEntry->FileName.Length!=0);
UlTrace( LOGGING, ("Http!UlpHandleRecycle: pEntry %p \n", pEntry ));
//
// This value is computed for the GMT time zone.
//
KeQuerySystemTime(&TimeStamp);
RtlTimeToTimeFields(&TimeStamp, &TimeFields);
ExSystemTimeToLocalTime(&TimeStamp, &TimeStampLocal);
RtlTimeToTimeFields(&TimeStampLocal, &TimeFieldsLocal);
// If we need to scan the directory. Sequence number should start
// from 1 again. Set this before constructing the log file name.
if (pEntry->Flags.StaleSequenceNumber &&
pEntry->Period==HttpLoggingPeriodMaxSize)
{
// Init otherwise if QueryDirectory doesn't find any it
// will not update this value
pEntry->SequenceNumber = 1;
}
//
// Now construct the filename using the lookup table
// And the current time
//
UlConstructFileName(
pEntry->Period,
UL_GET_LOG_FILE_NAME_PREFIX(pEntry->Format),
DEFAULT_LOG_FILE_EXTENSION,
&FileName,
UL_PICK_TIME_FIELD(pEntry, &TimeFieldsLocal, &TimeFields),
UTF8_LOGGING_ENABLED(),
&pEntry->SequenceNumber
);
if ( pEntry->FileName.MaximumLength <= FileName.Length )
{
ASSERT(!"FileName buffer is not sufficient.");
Status = STATUS_INVALID_PARAMETER;
goto end;
}
//
// Do the magic and renew the filename. Replace the old file
// name with the new one.
//
ASSERT( pEntry->pShortName != NULL );
//
// Get rid of the old filename before flushing the
// directories and reconcataneting the new file name
// to the end again.
//
*((PWCHAR)pEntry->pShortName) = UNICODE_NULL;
pEntry->FileName.Length =
(USHORT) wcslen( pEntry->FileName.Buffer ) * sizeof(WCHAR);
//
// Create/Open the director(ies) first. This might be
// necessary if we get called after an entry reconfiguration
// and directory name change.
//
Status = UlCreateSafeDirectory(&pEntry->FileName,
&UncShare,
&ACLSupport
);
if (!NT_SUCCESS(Status))
goto eventlog;
//
// Now Restore the short file name pointer back
//
pEntry->pShortName = (PWSTR)
&(pEntry->FileName.Buffer[pEntry->FileName.Length/sizeof(WCHAR)]);
//
// Append the new file name ( based on updated current time )
// to the end.
//
Status = RtlAppendUnicodeStringToString( &pEntry->FileName, &FileName );
if (!NT_SUCCESS(Status))
goto end;
//
// Time to close the old file and reopen a new one
//
if (pEntry->pLogFile != NULL)
{
//
// Flush the buffer, close the file and mark the entry
// inactive.
//
UlpMakeEntryInactive(pEntry);
}
ASSERT(pEntry->pLogFile == NULL);
//
// If the sequence is stale because of the nature of the recycle.
// And if our period is size based then rescan the new directory
// to figure out the proper file to open.
//
pEntry->TotalWritten.QuadPart = (ULONGLONG) 0;
if (pEntry->Flags.StaleSequenceNumber &&
pEntry->Period==HttpLoggingPeriodMaxSize)
{
// This call may update the filename, the file size and the
// sequence number if there is an old file in the new dir.
Status = UlQueryDirectory(
&pEntry->FileName,
pEntry->pShortName,
UL_GET_LOG_FILE_NAME_PREFIX(pEntry->Format),
DEFAULT_LOG_FILE_EXTENSION_PLUS_DOT,
&pEntry->SequenceNumber,
&pEntry->TotalWritten.QuadPart
);
if (!NT_SUCCESS(Status))
{
if (Status == STATUS_ACCESS_DENIED)
{
Status = STATUS_INVALID_OWNER;
goto eventlog;
}
else
{
goto end;
}
}
}
//
// Allocate a new log file structure for the new log file we are about
// to open or create.
//
pLogFile = pEntry->pLogFile =
UL_ALLOCATE_STRUCT(
NonPagedPool,
UL_LOG_FILE_HANDLE,
UL_LOG_FILE_HANDLE_POOL_TAG
);
if (pLogFile == NULL)
{
Status = STATUS_NO_MEMORY;
goto end;
}
pLogFile->Signature = UL_LOG_FILE_HANDLE_POOL_TAG;
pLogFile->hFile = NULL;
UlInitializeWorkItem(&pLogFile->WorkItem);
//
// Create the new log file.
//
Status = UlCreateLogFile(&pEntry->FileName,
UncShare,
ACLSupport,
&pLogFile->hFile
);
if (!NT_SUCCESS(Status))
{
goto eventlog;
}
ASSERT(pLogFile->hFile);
pEntry->TotalWritten.QuadPart = UlGetLogFileLength(pLogFile->hFile);
//
// Recalculate the time to expire.
//
if (pEntry->Flags.StaleTimeToExpire &&
pEntry->Period != HttpLoggingPeriodMaxSize)
{
UlCalculateTimeToExpire(
UL_PICK_TIME_FIELD(pEntry, &TimeFieldsLocal, &TimeFields),
pEntry->Period,
&pEntry->TimeToExpire
);
}
//
// Set the time to close to default for a new file. The value is in
// buffer flushup periods.
//
pEntry->TimeToClose = DEFAULT_MAX_FILE_IDLE_TIME;
//
// By setting the flag to zero, we mark that we need to write title
// with the next incoming request.But this only applies to W3C format.
// Otherwise the flag stays as set all the time, and the LogWriter
// does not attempt to write the title for NCSA and IIS log formats
// with the next incoming request.
//
if (pEntry->Format == HttpLoggingTypeW3C)
{
pEntry->Flags.LogTitleWritten = 0;
}
else
{
pEntry->Flags.LogTitleWritten = 1;
}
//
// File is successfully opened and the entry is no longer inactive.
// Update our state flags accordingly.
//
pEntry->Flags.Active = 1;
pEntry->Flags.RecyclePending = 0;
pEntry->Flags.StaleSequenceNumber = 0;
pEntry->Flags.StaleTimeToExpire = 0;
pEntry->Flags.CreateFileFailureLogged = 0;
pEntry->Flags.WriteFailureLogged = 0;
pEntry->Flags.TitleFlushPending = 0;
UlTrace(LOGGING, ("Http!UlpHandleRecycle: entry %p, file %S, handle %lx\n",
pEntry,
pEntry->FileName.Buffer,
pLogFile->hFile
));
eventlog:
if (!NT_SUCCESS(Status))
{
if (!pEntry->Flags.CreateFileFailureLogged)
{
NTSTATUS TempStatus;
TempStatus = UlEventLogCreateFailure(
Status,
UlEventLogNormal,
&pEntry->FileName,
pEntry->SiteId
);
if (TempStatus == STATUS_SUCCESS)
{
//
// Avoid filling up the event log with error entries. This code
// path might get hit every time a request arrives.
//
pEntry->Flags.CreateFileFailureLogged = 1;
}
UlTrace(LOGGING,(
"Http!UlpHandleRecycle: Event Logging Status %08lx\n",
TempStatus
));
}
}
end:
if (!NT_SUCCESS(Status))
{
UlTrace(LOGGING,("Http!UlpHandleRecycle: entry %p, failure %08lx\n",
pEntry,
Status
));
if (pLogFile != NULL)
{
//
// This means we have already closed the old file but failed
// when we try to create or open the new one.
//
ASSERT(pLogFile->hFile == NULL);
UL_FREE_POOL_WITH_SIG(pLogFile,UL_LOG_FILE_HANDLE_POOL_TAG);
pEntry->pLogFile = NULL;
pEntry->Flags.Active = 0;
}
else
{
//
// We were about to recyle the old one but something failed
// lets try to flush and close the existing file if it's still
// around.
//
if (pEntry->pLogFile)
{
UlpMakeEntryInactive(pEntry);
}
}
//
// Mark this entry RecyclePending so that buffer timer can try to
// resurrect this back every minute.
//
pEntry->Flags.RecyclePending = 1;
}
return Status;
}
/***************************************************************************++
Routine Description:
When the log file is on size based recycling and if we write this new
record to the file, we have to recycle. This function returns TRUE.
Otherwise it returns FALSE.
Arguments:
pEntry: The log file entry.
NewRecordSize: The size of the new record going to the buffer. (Bytes)
--***************************************************************************/
__inline
BOOLEAN
UlpIsLogFileOverFlow(
IN PUL_LOG_FILE_ENTRY pEntry,
IN ULONG NewRecordSize
)
{
if (pEntry->Period != HttpLoggingPeriodMaxSize ||
pEntry->TruncateSize == HTTP_LIMIT_INFINITE)
{
return FALSE;
}
else
{
//
// BufferUsed: Amount of log buffer we are >currently< using.
//
ULONG BufferUsed = 0;
if (pEntry->LogBuffer)
{
BufferUsed = pEntry->LogBuffer->BufferUsed;
}
//
// TotalWritten get updated >only< with buffer flush. Therefore
// we have to pay attention to the buffer used.
//
if ((pEntry->TotalWritten.QuadPart
+ (ULONGLONG) BufferUsed
+ (ULONGLONG) NewRecordSize
) >= (ULONGLONG) pEntry->TruncateSize)
{
UlTrace(LOGGING,
("Http!UlpIsLogFileOverFlow: pEntry %p FileBuffer %p "
"TW:%I64d B:%d R:%d T:%d\n",
pEntry,
pEntry->LogBuffer,
pEntry->TotalWritten.QuadPart,
BufferUsed,
NewRecordSize,
pEntry->TruncateSize
));
return TRUE;
}
else
{
return FALSE;
}
}
}
/***************************************************************************++
Routine Description:
UlpInitializeGMTOffset :
Calculates and builds the time difference string.
Get called during the initialization.
And every hour after that.
--***************************************************************************/
VOID
UlpGetGMTOffset()
{
RTL_TIME_ZONE_INFORMATION Tzi;
NTSTATUS Status;
CHAR Sign;
LONG Bias;
ULONG Hour;
ULONG Minute;
ULONG DT = UL_TIME_ZONE_ID_UNKNOWN;
LONG BiasN = 0;
PAGED_CODE();
//
// get the timezone data from the system
//
Status = NtQuerySystemInformation(
SystemCurrentTimeZoneInformation,
(PVOID)&Tzi,
sizeof(Tzi),
NULL
);
if (!NT_SUCCESS(Status))
{
UlTrace(LOGGING,("Http!UlpGetGMTOffset: failure %08lx\n", Status));
}
else
{
DT = UlCalcTimeZoneIdAndBias(&Tzi, &BiasN);
}
if ( BiasN > 0 )
{
//
// UTC = local time + bias
//
Bias = BiasN;
Sign = '-';
}
else
{
Bias = -1 * BiasN;
Sign = '+';
}
Minute = Bias % 60;
Hour = (Bias - Minute) / 60;
UlTrace( LOGGING,
("Http!UlpGetGMTOffset: %c%02d:%02d (h:m) D/S %d BiasN %d\n",
Sign,
Hour,
Minute,
DT,
BiasN
) );
_snprintf( g_GMTOffset,
SIZE_OF_GMT_OFFSET,
"%c%02d%02d",
Sign,
Hour,
Minute
);
}
/***************************************************************************++
Routine Description:
Allocates a log data buffer from pool.
Arguments:
pLogData - The internal buffer to hold logging info. We will keep this
around until we are done with logging.
pRequest - Pointer to the currently logged request.
pConfigGroup - CG from cache or from request
--***************************************************************************/
PUL_LOG_DATA_BUFFER
UlpAllocateLogDataBuffer(
IN ULONG Size,
IN PUL_INTERNAL_REQUEST pRequest,
IN PUL_CONFIG_GROUP_OBJECT pConfigGroup
)
{
PUL_LOG_DATA_BUFFER pLogData = NULL;
//
// Sanity check.
//
PAGED_CODE();
ASSERT(UL_IS_VALID_INTERNAL_REQUEST(pRequest));
ASSERT(IS_VALID_CONFIG_GROUP(pConfigGroup));
if (Size > UL_ANSI_LOG_LINE_BUFFER_SIZE)
{
//
// Provided buffer is not big enough to hold the user data.
//
pLogData = UlReallocLogDataBuffer(Size, FALSE);
}
else
{
//
// Default is enough, try to pop it from the lookaside list.
//
pLogData = UlPplAllocateLogDataBuffer(FALSE);
}
//
// If failed to allocate then bail out. We won't be logging this request.
//
if (pLogData)
{
ASSERT(IS_VALID_LOG_DATA_BUFFER(pLogData));
ASSERT(pLogData->Flags.Binary == 0);
ASSERT(pLogData->Size > 0);
//
// Initialize Log Fields in the Log Buffer
//
UL_REFERENCE_INTERNAL_REQUEST(pRequest);
pLogData->pRequest = pRequest;
pLogData->Flags.CacheAndSendResponse = FALSE;
pLogData->BytesTransferred = 0;
pLogData->Used = 0;
pLogData->Data.Str.Format = pConfigGroup->LoggingConfig.LogFormat;
pLogData->Data.Str.Flags =
UL_GET_LOG_TYPE_MASK(
pConfigGroup->LoggingConfig.LogFormat,
pConfigGroup->LoggingConfig.LogExtFileFlags
);
pLogData->Data.Str.Offset1 = 0;
pLogData->Data.Str.Offset2 = 0;
pLogData->Data.Str.Offset3 = 0;
UlInitializeWorkItem(&pLogData->WorkItem);
pRequest->pLogDataCopy = pLogData;
}
UlTrace(LOGGING,("Http!UlAllocateLogDataBuffer: pLogData %p \n",pLogData));
return pLogData;
}
/***************************************************************************++
Routine Description:
Copy over the user log field by enforcing a limit.
Post filter out the control characters according to the CharMask.
Adds a separator character at the end.
Arguments:
psz: Pointer to log data buffer. Enough space is assumed to be allocated.
pField: Field to be copied over.
FieldLength: It's length.
FieldLimit: Copy will not exceed this limit.
chSeparator: Will be written after the converted field.
bReplace : If field exceeds the limit should we truncate or replace with
LOG_FIELD_TOO_BIG.
RestrictiveMask : Mask for filtering out control chars.
Returns:
the pointer to the log data buffer after the last written separator.
--***************************************************************************/
__inline
PCHAR
UlpCopyField(
IN PCHAR psz,
IN PCSTR pField,
IN ULONG FieldLength,
IN ULONG FieldLimit,
IN CHAR chSeparator,
IN BOOLEAN bReplace,
IN ULONG RestrictiveMask
)
{
if (FieldLength)
{
if ((FieldLength > FieldLimit) && bReplace)
{
psz = UlStrPrintStr(psz, LOG_FIELD_TOO_BIG, chSeparator);
}
else
{
ULONG i = 0;
FieldLength = MIN(FieldLength, FieldLimit);
while (i < FieldLength)
{
if (IS_CHAR_TYPE((*pField),RestrictiveMask))
{
*psz++ = '+';
}
else
{
*psz++ = *pField;
}
pField++;
i++;
}
*psz++ = chSeparator;
}
}
else
{
*psz++ = '-'; *psz++ = chSeparator;
}
return psz;
}
/***************************************************************************++
Routine Description:
Either does Utf8 conversion or Local Code Page conversion.
Post filter out the control characters according to the CharMask.
Adds a separator character at the end.
Arguments:
psz: Pointer to log data buffer. Enough space is assumed to be allocated.
pwField: Unicode field to be converted.
FieldLength: It's length.
FieldLimit: Conversion will not exceed this limit.
chSeparator: Will be written after the converted field.
bUtf8Enabled: If FALSE Local Code Page conversion otherwise Utf8
RestrictiveMask : Mask for filtering out control chars.
Returns:
the pointer to the log data buffer after the last written separator.
--***************************************************************************/
__inline
PCHAR
UlpCopyUnicodeField(
IN PCHAR psz,
IN PCWSTR pwField,
IN ULONG FieldLength, // In Bytes
IN ULONG FieldLimit, // In Bytes
IN CHAR chSeparator,
IN BOOLEAN bUtf8Enabled,
IN ULONG RestrictiveMask
)
{
ASSERT(FieldLimit > (2 * sizeof(WCHAR)));
if (FieldLength)
{
ULONG BytesConverted = 0;
PCHAR pszT = psz;
if (bUtf8Enabled)
{
LONG DstSize; // In Bytes
LONG SrcSize; // In Bytes
// Utf8 Conversion may require up to two times of the source
// buffer because of a possible 2 byte (a wchar) to 4 byte
// conversion.
// TODO: This calculation is slightly pessimistic because the worst case
// TODO: conversion is from 1 wchar to 3 byte sequence.
if ((FieldLength * 2) > FieldLimit)
{
// Conversion may exceed the dest buffer in the worst
// case (where every wchar converted to 4 byte sequence),
// need to truncate the source to avoid overflow.
SrcSize = FieldLimit / 2;
DstSize = FieldLimit;
}
else
{
SrcSize = FieldLength;
DstSize = FieldLength * 2;
}
//
// HttpUnicodeToUTF8 does not truncate and convert. We actually
// use shorter url when utf8 conversion is set, to be able to
// prevent a possible overflow.
//
BytesConverted =
HttpUnicodeToUTF8(
pwField,
SrcSize / sizeof(WCHAR), // In WChars
psz,
DstSize // In Bytes
);
ASSERT(BytesConverted);
}
else
{
NTSTATUS Status;
// Local codepage is normally closer to the half the length,
// but due to the possibility of pre-composed characters,
// the upperbound of the ANSI length is the UNICODE length
// in bytes
Status =
RtlUnicodeToMultiByteN(
psz,
FieldLimit, // Dest in Bytes
&BytesConverted,
(PWSTR) pwField,
FieldLength // Src in Bytes
);
ASSERT(NT_SUCCESS(Status));
}
psz += BytesConverted;
// Post convert control chars.
while (pszT != psz)
{
if (IS_CHAR_TYPE((*pszT),RestrictiveMask))
{
*pszT = '+';
}
pszT++;
}
*psz++ = chSeparator;
}
else
{
*psz++ = '-'; *psz++ = chSeparator;
}
return psz;
}
/***************************************************************************++
Routine Description:
Extended check happens for w3c field against the total buffer size.
Post filter out the control characters according to the CharMask.
Adds a separator character at the end.
Arguments:
psz: Pointer to log data buffer. Enough space is assumed to be allocated.
Mask: Picked flags by the user config.
Flag: Bitmask of the field passed in.
pField: Field to be copied over.
FieldLength: It's length.
FieldLimit: Copy will not exceed this limit.
BufferUsed: Additional limit comparison happens against toatal buffer used
Returns:
the pointer to the log data buffer after the last written separator.
--***************************************************************************/
__inline
PCHAR
UlpCopyW3CFieldEx(
IN PCHAR psz,
IN ULONG Mask,
IN ULONG Flag,
IN PCSTR pField,
IN ULONG FieldLength,
IN ULONG FieldLimit,
IN ULONG BufferUsed,
IN ULONG BufferSize
)
{
if (Mask & Flag)
{
if (FieldLength)
{
if ((FieldLength > FieldLimit) ||
((BufferUsed + FieldLength) > BufferSize))
{
psz = UlStrPrintStr(psz, LOG_FIELD_TOO_BIG, ' ');
}
else
{
ULONG i = 0;
ASSERT(FieldLength <= FieldLimit);
while (i < FieldLength)
{
if (IS_HTTP_WHITE((*pField)))
{
*psz++ = '+';
}
else
{
*psz++ = *pField;
}
pField++;
i++;
}
*psz++ = ' ';
}
}
else
{
*psz++ = '-'; *psz++ = ' ';
}
}
return psz;
}
/***************************************************************************++
Thin wrapper macros for log field copies. See above inline functions.
--***************************************************************************/
#define COPY_W3C_FIELD(psz, \
Mask, \
Flag, \
pField, \
FieldLength, \
FieldLimit, \
bReplace) \
if (Mask & Flag) \
{ \
psz = UlpCopyField( \
psz, \
pField, \
FieldLength, \
FieldLimit, \
' ', \
bReplace, \
HTTP_ISWHITE \
); \
}
#define COPY_W3C_UNICODE_FIELD( \
psz, \
Mask, \
Flag, \
pwField, \
FieldLength, \
FieldLimit, \
bUtf8Enabled) \
if (Mask & Flag) \
{ \
psz = UlpCopyUnicodeField( \
psz, \
pwField, \
FieldLength, \
FieldLimit, \
' ', \
bUtf8Enabled, \
HTTP_ISWHITE \
); \
}
/***************************************************************************++
Routine Description:
For cache-hits extended w3c fields are generated from request headers.
Post filter out the control characters according to the CharMask.
Adds a separator character at the end.
Arguments:
psz: Pointer to log data buffer. Enough space is assumed to be allocated.
Mask: Picked flags by the user config.
Flag: Bitmask of the field passed in.
pRequest: Internal request
HeaderId: Identifies the extended field.
BufferUsed: Additional limit comparison happens against toatal buffer used
Returns:
the pointer to the log data buffer after the last written separator.
--***************************************************************************/
__inline
PCHAR
UlpCopyRequestHeader(
IN PCHAR psz,
IN ULONG Mask,
IN ULONG Flag,
IN PUL_INTERNAL_REQUEST pRequest,
IN HTTP_HEADER_ID HeaderId,
IN ULONG BufferUsed
)
{
ASSERT(UL_IS_VALID_INTERNAL_REQUEST(pRequest));
ASSERT(HeaderId < HttpHeaderRequestMaximum);
psz = UlpCopyW3CFieldEx(
psz,
Mask,
Flag,
(PCSTR)pRequest->Headers[HeaderId].pHeader,
pRequest->HeaderValid[HeaderId] ?
pRequest->Headers[HeaderId].HeaderLength :
0,
MAX_LOG_EXTEND_FIELD_LEN,
BufferUsed,
MAX_LOG_RECORD_LEN
);
return psz;
}
/***************************************************************************++
Routine Description:
Generic function which will generate the TimeStamp field by calculating
the difference between the time request first get started to be parsed
and the current time.
Arguments:
psz: Pointer to log data buffer. Enough space is assumed to be allocated.
pRequest: Pointer to internal request structure. For "TimeStamp"
chSeparator : Once the LONGLONG lifetime converted, separator will be
copied as well.
Returns:
the pointer to the log data buffer after the last written separator.
--***************************************************************************/
__inline
PCHAR
UlpCopyTimeStamp(
IN PCHAR psz,
IN PUL_INTERNAL_REQUEST pRequest,
IN CHAR chSeparator
)
{
LARGE_INTEGER CurrentTimeStamp;
LONGLONG LifeTime;
ASSERT(UL_IS_VALID_INTERNAL_REQUEST(pRequest));
KeQuerySystemTime(&CurrentTimeStamp);
LifeTime = CurrentTimeStamp.QuadPart
- pRequest->TimeStamp.QuadPart;
LifeTime = MAX(LifeTime,0);
LifeTime /= C_NS_TICKS_PER_MSEC;
psz = UlStrPrintUlonglong(
psz,
(ULONGLONG)LifeTime,
chSeparator
);
return psz;
}
/***************************************************************************++
Routine Description:
Small utility which will increment the total used for w3c fields.
Arguments:
pTotal: Will be incremented.
Mask: Picked flags by the user config.
Flag: Bitmask of the field passed in.
FieldLength: It's length.
FieldLimit: Copy will not exceed this limit.
bUtf8Enabled
--***************************************************************************/
__inline
VOID
UlpIncForW3CField(
IN PULONG pTotal,
IN ULONG Mask,
IN ULONG Flag,
IN ULONG FieldLength,
IN ULONG FieldLimit,
IN BOOLEAN bUtf8Enabled
)
{
if (Mask & Flag)
{
if (FieldLength)
{
if (bUtf8Enabled)
{
*pTotal += MIN((FieldLength * 2),FieldLimit) + 1;
}
else
{
*pTotal += MIN(FieldLength,FieldLimit) + 1;
}
}
else
{
*pTotal += 2; // For "- "
}
}
}
/***************************************************************************++
Routine Description:
This is a worst case estimate for the maximum possible buffer required to
generate the W3C Log record for a given set of user fields. All the fields
are assumed to be picked.
Arguments:
pLogData : Captured copy of the user log field data.
--***************************************************************************/
__inline
ULONG
UlpMaxLogLineSizeForW3C(
IN PHTTP_LOG_FIELDS_DATA pLogData,
IN BOOLEAN Utf8Enabled
)
{
ULONG FastLength;
//
// For each field
//
// 1 for separator space.
// + 1 for '-', in case field length is zero.
// + pLogData->FieldLength for field, assuming it's always picked.
// enforce the field limitation to prevent overflow.
//
FastLength =
2 + MIN(pLogData->ClientIpLength, MAX_LOG_DEFAULT_FIELD_LEN)
+ 2 + MIN(pLogData->ServiceNameLength,MAX_LOG_DEFAULT_FIELD_LEN)
+ 2 + MIN(pLogData->ServerNameLength, MAX_LOG_DEFAULT_FIELD_LEN)
+ 2 + MIN(pLogData->ServerIpLength, MAX_LOG_DEFAULT_FIELD_LEN)
+ 2 + MIN(pLogData->MethodLength, MAX_LOG_METHOD_FIELD_LEN)
+ 2 + MIN(pLogData->UriQueryLength, MAX_LOG_EXTEND_FIELD_LEN)
+ 2 + MIN(pLogData->UserAgentLength, MAX_LOG_EXTEND_FIELD_LEN)
+ 2 + MIN(pLogData->CookieLength, MAX_LOG_EXTEND_FIELD_LEN)
+ 2 + MIN(pLogData->ReferrerLength, MAX_LOG_EXTEND_FIELD_LEN)
+ 2 + MIN(pLogData->HostLength, MAX_LOG_EXTEND_FIELD_LEN)
+ MAX_W3C_FIX_FIELD_OVERHEAD
;
//
// If Utf8 logging is enabled, unicode fields require more space.
//
if (Utf8Enabled)
{
//
// Allow only half of the normal limit, so that conversion doesn't
// overflow even in the worst case ( 1 wchar to 4 byte conversion)
//
FastLength +=
2 + MIN((pLogData->UserNameLength * 2),MAX_LOG_USERNAME_FIELD_LEN)
+ 2 + MIN((pLogData->UriStemLength * 2), MAX_LOG_EXTEND_FIELD_LEN)
;
}
else
{
//
// RtlUnicodeToMultiByteN requires no more than the original unicode
// buffer size.
//
FastLength +=
2 + MIN(pLogData->UserNameLength, MAX_LOG_USERNAME_FIELD_LEN)
+ 2 + MIN(pLogData->UriStemLength, MAX_LOG_EXTEND_FIELD_LEN)
;
}
return FastLength;
}
/***************************************************************************++
Routine Description:
Now if the fast length calculation exceeds the default log buffer size.
This function tries to calculate the max required log record length by
paying attention to whether the field is picked or not. This is to avoid
oversize-allocation. And we are in the slow path anyway.
Arguments:
pLogData : Captured copy of the user log field data.
--***************************************************************************/
ULONG
UlpGetLogLineSizeForW3C(
IN PHTTP_LOG_FIELDS_DATA pLogData,
IN ULONG Mask,
IN BOOLEAN bUtf8Enabled
)
{
ULONG TotalLength = 0;
//
// Increment the total length for each picked field.
//
UlpIncForW3CField( &TotalLength,
Mask,
MD_EXTLOG_DATE,
W3C_DATE_FIELD_LEN,
W3C_DATE_FIELD_LEN,
FALSE);
UlpIncForW3CField( &TotalLength,
Mask,
MD_EXTLOG_TIME,
W3C_TIME_FIELD_LEN,
W3C_TIME_FIELD_LEN,
FALSE);
UlpIncForW3CField( &TotalLength,
Mask,
MD_EXTLOG_CLIENT_IP,
pLogData->ClientIpLength,
MAX_LOG_DEFAULT_FIELD_LEN,
FALSE);
UlpIncForW3CField( &TotalLength,
Mask,
MD_EXTLOG_USERNAME,
pLogData->UserNameLength,
MAX_LOG_USERNAME_FIELD_LEN,
bUtf8Enabled);
UlpIncForW3CField( &TotalLength,
Mask,
MD_EXTLOG_SITE_NAME,
pLogData->ServiceNameLength,
MAX_LOG_DEFAULT_FIELD_LEN,
FALSE);
UlpIncForW3CField( &TotalLength,
Mask,
MD_EXTLOG_COMPUTER_NAME,
pLogData->ServerNameLength,
MAX_LOG_DEFAULT_FIELD_LEN,
FALSE);
UlpIncForW3CField( &TotalLength,
Mask,
MD_EXTLOG_SERVER_IP,
pLogData->ServerIpLength,
MAX_LOG_DEFAULT_FIELD_LEN,
FALSE);
UlpIncForW3CField( &TotalLength,
Mask,
MD_EXTLOG_METHOD,
pLogData->MethodLength,
MAX_LOG_METHOD_FIELD_LEN,
FALSE);
UlpIncForW3CField( &TotalLength,
Mask,
MD_EXTLOG_URI_STEM,
pLogData->UriStemLength,
MAX_LOG_EXTEND_FIELD_LEN,
bUtf8Enabled);
UlpIncForW3CField( &TotalLength,
Mask,
MD_EXTLOG_URI_QUERY,
pLogData->UriQueryLength,
MAX_LOG_EXTEND_FIELD_LEN,
FALSE);
UlpIncForW3CField( &TotalLength,
Mask,
MD_EXTLOG_HTTP_STATUS,
UL_MAX_HTTP_STATUS_CODE_LENGTH,
UL_MAX_HTTP_STATUS_CODE_LENGTH,
FALSE);
UlpIncForW3CField( &TotalLength,
Mask,
MD_EXTLOG_HTTP_SUB_STATUS,
MAX_USHORT_STR,
MAX_USHORT_STR,
FALSE);
UlpIncForW3CField( &TotalLength,
Mask,
MD_EXTLOG_WIN32_STATUS,
MAX_ULONG_STR,
MAX_ULONG_STR,
FALSE);
UlpIncForW3CField( &TotalLength,
Mask,
MD_EXTLOG_SERVER_PORT,
MAX_USHORT_STR,
MAX_USHORT_STR,
FALSE);
UlpIncForW3CField( &TotalLength,
Mask,
MD_EXTLOG_PROTOCOL_VERSION,
UL_HTTP_VERSION_LENGTH,
UL_HTTP_VERSION_LENGTH,
FALSE);
UlpIncForW3CField( &TotalLength,
Mask,
MD_EXTLOG_USER_AGENT,
pLogData->UserAgentLength,
MAX_LOG_EXTEND_FIELD_LEN,
FALSE);
UlpIncForW3CField( &TotalLength,
Mask,
MD_EXTLOG_COOKIE,
pLogData->CookieLength,
MAX_LOG_EXTEND_FIELD_LEN,
FALSE);
UlpIncForW3CField( &TotalLength,
Mask,
MD_EXTLOG_REFERER,
pLogData->ReferrerLength,
MAX_LOG_EXTEND_FIELD_LEN,
FALSE);
UlpIncForW3CField( &TotalLength,
Mask,
MD_EXTLOG_HOST,
pLogData->HostLength,
MAX_LOG_EXTEND_FIELD_LEN,
FALSE);
UlpIncForW3CField( &TotalLength,
Mask,
MD_EXTLOG_BYTES_SENT,
MAX_ULONGLONG_STR,
MAX_ULONGLONG_STR,
FALSE);
UlpIncForW3CField( &TotalLength,
Mask,
MD_EXTLOG_BYTES_RECV,
MAX_ULONGLONG_STR,
MAX_ULONGLONG_STR,
FALSE);
UlpIncForW3CField( &TotalLength,
Mask,
MD_EXTLOG_TIME_TAKEN,
MAX_ULONGLONG_STR,
MAX_ULONGLONG_STR,
FALSE);
//
// Finally increment the length for CRLF and terminating null.
//
TotalLength += 3; // \r\n\0
return TotalLength;
}
__inline
ULONG
UlpGetCacheHitLogLineSizeForW3C(
IN ULONG Flags,
IN PUL_INTERNAL_REQUEST pRequest,
IN ULONG SizeOfFieldsFrmCache
)
{
ULONG NewSize;
#define INC_FOR_REQUEST_HEADER(Flags,FieldMask,pRequest,Id,Size) \
if ((Flags & FieldMask) && \
pRequest->HeaderValid[Id]) \
{ \
ASSERT( pRequest->Headers[Id].HeaderLength == \
strlen((const CHAR *)pRequest->Headers[Id].pHeader)); \
\
Size += 2 + pRequest->Headers[Id].HeaderLength; \
}
ASSERT(UL_IS_VALID_INTERNAL_REQUEST(pRequest));
NewSize = SizeOfFieldsFrmCache + MAX_W3C_CACHE_FIELD_OVERHEAD;
//
// And now add extended field's lengths.
//
INC_FOR_REQUEST_HEADER(Flags,
MD_EXTLOG_USER_AGENT,
pRequest,
HttpHeaderUserAgent,
NewSize);
INC_FOR_REQUEST_HEADER(Flags,
MD_EXTLOG_COOKIE,
pRequest,
HttpHeaderCookie,
NewSize);
INC_FOR_REQUEST_HEADER(Flags,
MD_EXTLOG_REFERER,
pRequest,
HttpHeaderReferer,
NewSize);
INC_FOR_REQUEST_HEADER(Flags,
MD_EXTLOG_HOST,
pRequest,
HttpHeaderHost,
NewSize);
return NewSize;
}
__inline
ULONG
UlpGetLogLineSizeForNCSA(
IN PHTTP_LOG_FIELDS_DATA pLogData,
IN BOOLEAN bUtf8Enabled
)
{
ULONG Size;
#define NCSA_FIELD_SIZE(length,limit) (1 + MAX(MIN((length),(limit)),1))
//
// For each field
//
// 1 for separator ' '
// + pLogData->FieldLength for field, limited by a upper bound.
// max(length, 1) if length is zero we still need to log a dash.
//
//
// cIP - UserN [07/Jan/2000:00:02:23 -0800] "MTHD URI?QUERY VER" Status bSent
//
Size = NCSA_FIELD_SIZE(pLogData->ClientIpLength, MAX_LOG_DEFAULT_FIELD_LEN)
+
2 // "- " for remote user name
+
NCSA_FIX_DATE_AND_TIME_FIELD_SIZE // Including the separator
+
1 // '"' : starting double quote
+
NCSA_FIELD_SIZE(pLogData->MethodLength,MAX_LOG_METHOD_FIELD_LEN)
+
1 // '?'
+
NCSA_FIELD_SIZE(pLogData->UriQueryLength,MAX_LOG_EXTEND_FIELD_LEN)
+
UL_HTTP_VERSION_LENGTH + 1
+
1 // "' : ending double quote
+
UL_MAX_HTTP_STATUS_CODE_LENGTH + 1 // Status
+
MAX_ULONGLONG_STR // BytesSent
+
3 // \r\n\0
;
if (bUtf8Enabled)
{
Size +=
NCSA_FIELD_SIZE((pLogData->UserNameLength * 2),MAX_LOG_USERNAME_FIELD_LEN)
+
NCSA_FIELD_SIZE((pLogData->UriStemLength * 2),MAX_LOG_EXTEND_FIELD_LEN)
;
}
else
{
Size +=
NCSA_FIELD_SIZE(pLogData->UserNameLength,MAX_LOG_USERNAME_FIELD_LEN)
+
NCSA_FIELD_SIZE(pLogData->UriStemLength,MAX_LOG_EXTEND_FIELD_LEN)
;
}
return Size;
}
__inline
ULONG
UlpGetLogLineSizeForIIS(
IN PHTTP_LOG_FIELDS_DATA pLogData,
IN BOOLEAN bUtf8Enabled
)
{
ULONG MaxSize,Frag1size,Frag2size,Frag3size;
#define IIS_FIELD_SIZE(length,limit) (2 + MAX(MIN((length),(limit)),1))
Frag1size =
IIS_FIELD_SIZE(pLogData->ClientIpLength,MAX_LOG_DEFAULT_FIELD_LEN)
+
IIS_MAX_DATE_AND_TIME_FIELD_SIZE // Including the separators
;
Frag2size =
IIS_FIELD_SIZE(pLogData->ServiceNameLength,MAX_LOG_DEFAULT_FIELD_LEN)
+
IIS_FIELD_SIZE(pLogData->ServerNameLength,MAX_LOG_DEFAULT_FIELD_LEN)
+
IIS_FIELD_SIZE(pLogData->ServerIpLength,MAX_LOG_DEFAULT_FIELD_LEN)
+
2 + MAX_ULONGLONG_STR // TimeTaken
+
2 + MAX_ULONGLONG_STR // BytesReceived
+
2 + MAX_ULONGLONG_STR // BytesSend
+
2 + UL_MAX_HTTP_STATUS_CODE_LENGTH
+
2 + MAX_ULONG_STR // Win32 Status
;
Frag3size =
IIS_FIELD_SIZE(pLogData->MethodLength,MAX_LOG_METHOD_FIELD_LEN)
+
IIS_FIELD_SIZE(pLogData->UriQueryLength,MAX_LOG_EXTEND_FIELD_LEN)
+
3 // \r\n\0
;
if (bUtf8Enabled)
{
Frag3size +=
IIS_FIELD_SIZE((pLogData->UriStemLength * 2),MAX_LOG_EXTEND_FIELD_LEN);
Frag1size +=
IIS_FIELD_SIZE((pLogData->UserNameLength * 2),MAX_LOG_USERNAME_FIELD_LEN);
}
else
{
Frag3size +=
IIS_FIELD_SIZE(pLogData->UriStemLength,MAX_LOG_EXTEND_FIELD_LEN);
Frag1size +=
IIS_FIELD_SIZE(pLogData->UserNameLength,MAX_LOG_USERNAME_FIELD_LEN);
}
//
// First two fragments must always fit to the default buffer.
//
ASSERT(Frag1size < IIS_LOG_LINE_DEFAULT_FIRST_FRAGMENT_ALLOCATION_SIZE);
ASSERT(Frag2size < IIS_LOG_LINE_DEFAULT_SECOND_FRAGMENT_ALLOCATION_SIZE);
//
// The required third fragment size may be too big for the default
// buffer size.
//
MaxSize = IIS_LOG_LINE_DEFAULT_FIRST_FRAGMENT_ALLOCATION_SIZE +
IIS_LOG_LINE_DEFAULT_SECOND_FRAGMENT_ALLOCATION_SIZE +
Frag3size;
return MaxSize;
}
/***************************************************************************++
Routine Description:
Captures and writes the log fields from user buffer to the log line.
pLogData itself must have been captured by the caller however embedded
pointers inside of the structure are not, therefore we need to be carefull
here and cleanup if an exception raises.
Captures only those necessary fields according to the picked Flags.
Does UTF8 and LocalCode Page conversion for UserName and URI Stem.
Leaves enough space for Date & Time fields for late generation.
WARNING:
Even though the pUserData is already captured to the kernel buffer, it
still holds pointers to user-mode memory for individual log fields,
therefore this function should be called inside a try/except block and
If this function raises an exception caller should cleanup the allocated
pLogBuffer.
Arguments:
pLogData : Captured user data in a kernel buffer.
pRequest : Request.
ppLogBuffer : Returning pLogBuffer.
--***************************************************************************/
NTSTATUS
UlCaptureLogFieldsW3C(
IN PHTTP_LOG_FIELDS_DATA pLogData,
IN PUL_INTERNAL_REQUEST pRequest,
OUT PUL_LOG_DATA_BUFFER *ppLogBuffer
)
{
PUL_LOG_DATA_BUFFER pLogBuffer;
PUL_CONFIG_GROUP_OBJECT pConfigGroup;
ULONG Flags;
PCHAR psz;
PCHAR pBuffer;
ULONG FastLength;
BOOLEAN bUtf8Enabled;
//
// Sanity check and init.
//
PAGED_CODE();
ASSERT(pLogData);
*ppLogBuffer = pLogBuffer = NULL;
pConfigGroup = pRequest->ConfigInfo.pLoggingConfig;
ASSERT(IS_VALID_CONFIG_GROUP(pConfigGroup));
Flags = pConfigGroup->LoggingConfig.LogExtFileFlags;
bUtf8Enabled = UTF8_LOGGING_ENABLED();
//
// Try the fast length calculation first. If this fails then
// we need to re-calculate the required length.
//
FastLength = UlpMaxLogLineSizeForW3C(pLogData, bUtf8Enabled);
if (FastLength > UL_ANSI_LOG_LINE_BUFFER_SIZE)
{
FastLength = UlpGetLogLineSizeForW3C(
pLogData,
Flags,
bUtf8Enabled
);
if (FastLength > MAX_LOG_RECORD_LEN)
{
//
// While we are enforcing 10k upper limit for the log record
// length. We still allocate slightly larger space to include
// the overhead for the post-generated log fields. As well as
// for the replacement strings for "too long" fields.
//
// TODO: (PAGE_SIZE - ALIGN_UP(FastLength,PVOID))
FastLength = MAX_LOG_RECORD_ALLOCATION_LENGTH;
}
}
*ppLogBuffer = pLogBuffer = UlpAllocateLogDataBuffer(
FastLength,
pRequest,
pConfigGroup
);
if (!pLogBuffer)
{
return STATUS_INSUFFICIENT_RESOURCES;
}
ASSERT(pLogBuffer->Data.Str.Format == HttpLoggingTypeW3C);
ASSERT(pLogBuffer->Data.Str.Flags == Flags);
//
// Remember the beginning of the buffer.
//
psz = pBuffer = (PCHAR) pLogBuffer->Line;
//
// Skip the date and time fields, but reserve the space.
//
if ( Flags & MD_EXTLOG_DATE ) psz += W3C_DATE_FIELD_LEN + 1;
if ( Flags & MD_EXTLOG_TIME ) psz += W3C_TIME_FIELD_LEN + 1;
//
// Remember if we have reserved any space for Date and Time or not.
//
pLogBuffer->Data.Str.Offset1 = DIFF_USHORT(psz - pBuffer);
COPY_W3C_FIELD(psz,
Flags,
MD_EXTLOG_SITE_NAME,
pLogData->ServiceName,
pLogData->ServiceNameLength,
MAX_LOG_DEFAULT_FIELD_LEN,
TRUE);
COPY_W3C_FIELD(psz,
Flags,
MD_EXTLOG_COMPUTER_NAME,
pLogData->ServerName,
pLogData->ServerNameLength,
MAX_LOG_DEFAULT_FIELD_LEN,
TRUE);
COPY_W3C_FIELD(psz,
Flags,
MD_EXTLOG_SERVER_IP,
pLogData->ServerIp,
pLogData->ServerIpLength,
MAX_LOG_DEFAULT_FIELD_LEN,
TRUE);
COPY_W3C_FIELD(psz,
Flags,
MD_EXTLOG_METHOD,
pLogData->Method,
pLogData->MethodLength,
MAX_LOG_METHOD_FIELD_LEN,
FALSE);
COPY_W3C_UNICODE_FIELD(
psz,
Flags,
MD_EXTLOG_URI_STEM,
pLogData->UriStem,
pLogData->UriStemLength,
MAX_LOG_EXTEND_FIELD_LEN,
bUtf8Enabled);
COPY_W3C_FIELD(psz,
Flags,
MD_EXTLOG_URI_QUERY,
pLogData->UriQuery,
pLogData->UriQueryLength,
MAX_LOG_EXTEND_FIELD_LEN,
TRUE);
if ( Flags & MD_EXTLOG_SERVER_PORT )
{
psz = UlStrPrintUlong(psz, pLogData->ServerPort,' ');
}
//
// Fields after this point won't be stored in the uri cache entry.
//
pLogBuffer->Data.Str.Offset2 = DIFF_USHORT(psz - pBuffer);
COPY_W3C_UNICODE_FIELD(
psz,
Flags,
MD_EXTLOG_USERNAME,
pLogData->UserName,
pLogData->UserNameLength,
MAX_LOG_USERNAME_FIELD_LEN,
bUtf8Enabled);
COPY_W3C_FIELD(psz,
Flags,
MD_EXTLOG_CLIENT_IP,
pLogData->ClientIp,
pLogData->ClientIpLength,
MAX_LOG_DEFAULT_FIELD_LEN,
TRUE);
if ( Flags & MD_EXTLOG_PROTOCOL_VERSION )
{
psz = UlCopyHttpVersion(psz, pRequest->Version, ' ');
}
ASSERT(DIFF(psz - pBuffer) <= MAX_LOG_RECORD_LEN);
//
// Following fields may still overflow the allocated buffer
// even though they are not exceeding their length limits.
// Ex function does the extra check.
//
psz = UlpCopyW3CFieldEx(
psz,
Flags,
MD_EXTLOG_USER_AGENT,
pLogData->UserAgent,
pLogData->UserAgentLength,
MAX_LOG_EXTEND_FIELD_LEN,
DIFF(psz - pBuffer),
MAX_LOG_RECORD_LEN);
psz = UlpCopyW3CFieldEx(
psz,
Flags,
MD_EXTLOG_COOKIE,
pLogData->Cookie,
pLogData->CookieLength,
MAX_LOG_EXTEND_FIELD_LEN,
DIFF(psz - pBuffer),
MAX_LOG_RECORD_LEN);
psz = UlpCopyW3CFieldEx(
psz,
Flags,
MD_EXTLOG_REFERER,
pLogData->Referrer,
pLogData->ReferrerLength,
MAX_LOG_EXTEND_FIELD_LEN,
DIFF(psz - pBuffer),
MAX_LOG_RECORD_LEN);
psz = UlpCopyW3CFieldEx(
psz,
Flags,
MD_EXTLOG_HOST,
pLogData->Host,
pLogData->HostLength,
MAX_LOG_EXTEND_FIELD_LEN,
DIFF(psz - pBuffer),
MAX_LOG_RECORD_LEN);
//
// Status fields may be updated upon send completion.
//
pLogBuffer->ProtocolStatus =
(USHORT) MIN(pLogData->ProtocolStatus,UL_MAX_HTTP_STATUS_CODE);
pLogBuffer->SubStatus = pLogData->SubStatus;
pLogBuffer->Win32Status = pLogData->Win32Status;
//
// Finally calculate the used space and verify that we did not overflow.
//
pLogBuffer->Used = DIFF_USHORT(psz - pBuffer);
ASSERT( pLogBuffer->Used <= MAX_LOG_RECORD_LEN );
UlTrace(LOGGING,
("Http!UlCaptureLogFields: user %p kernel %p\n",
pLogData,pLogBuffer
));
return STATUS_SUCCESS;
}
NTSTATUS
UlCaptureLogFieldsNCSA(
IN PHTTP_LOG_FIELDS_DATA pLogData,
IN OUT PUL_INTERNAL_REQUEST pRequest,
OUT PUL_LOG_DATA_BUFFER *ppLogBuffer
)
{
PCHAR psz;
PCHAR pBuffer;
ULONG MaxLength;
BOOLEAN bUtf8Enabled;
PUL_LOG_DATA_BUFFER pLogBuffer;
PUL_CONFIG_GROUP_OBJECT pConfigGroup;
//
// Sanity check.
//
PAGED_CODE();
*ppLogBuffer = pLogBuffer = NULL;
pConfigGroup = pRequest->ConfigInfo.pLoggingConfig;
ASSERT(IS_VALID_CONFIG_GROUP(pConfigGroup));
bUtf8Enabled = UTF8_LOGGING_ENABLED();
//
// Estimate the maximum possible length (worst case) and
// allocate a bigger log data buffer line if necessary.
//
MaxLength = UlpGetLogLineSizeForNCSA(pLogData, bUtf8Enabled);
MaxLength = MIN(MaxLength, MAX_LOG_RECORD_LEN);
*ppLogBuffer = pLogBuffer = UlpAllocateLogDataBuffer(
MaxLength,
pRequest,
pConfigGroup
);
if (!pLogBuffer)
{
return STATUS_INSUFFICIENT_RESOURCES;
}
ASSERT(pLogBuffer->Data.Str.Format == HttpLoggingTypeNCSA);
ASSERT(pLogBuffer->Data.Str.Flags == UL_DEFAULT_NCSA_FIELDS);
//
// cIP - UserN [07/Jan/2000:00:02:23 -0800] "MTHD URI?QUERY VER" Status bSent
//
psz = pBuffer = (PCHAR) pLogBuffer->Line;
psz = UlpCopyField(psz,
pLogData->ClientIp,
pLogData->ClientIpLength,
MAX_LOG_DEFAULT_FIELD_LEN,
' ',
TRUE,
HTTP_ISWHITE);
*psz++ = '-'; *psz++ = ' '; // Fixed dash
psz = UlpCopyUnicodeField(
psz,
pLogData->UserName,
pLogData->UserNameLength,
MAX_LOG_USERNAME_FIELD_LEN,
' ',
bUtf8Enabled,
HTTP_ISWHITE);
//
// Reserve a space for the date and time fields.
//
pLogBuffer->Data.Str.Offset1 = DIFF_USHORT(psz - pBuffer);
psz += NCSA_FIX_DATE_AND_TIME_FIELD_SIZE;
//
// "MTHD U-STEM?U-QUERY P-VER"
//
*psz++ = '\"';
psz = UlpCopyField(psz,
pLogData->Method,
pLogData->MethodLength,
MAX_LOG_METHOD_FIELD_LEN,
' ',
FALSE,
HTTP_CTL);
psz = UlpCopyUnicodeField(
psz,
pLogData->UriStem,
pLogData->UriStemLength,
MAX_LOG_EXTEND_FIELD_LEN,
'?',
bUtf8Enabled,
HTTP_CTL);
if (pLogData->UriQueryLength)
{
psz = UlpCopyField(psz,
pLogData->UriQuery,
pLogData->UriQueryLength,
MAX_LOG_EXTEND_FIELD_LEN,
' ',
TRUE,
HTTP_CTL);
}
else
{
psz--;
if ((*psz) == '?') *psz = ' '; // Eat the question mark
psz++;
}
pLogBuffer->Data.Str.Offset2 = DIFF_USHORT(psz - pBuffer);
psz = UlCopyHttpVersion(psz, pRequest->Version, '\"');
*psz++ = ' ';
//
// Set the log record length
//
ASSERT(pLogBuffer->Used == 0);
pLogBuffer->Used = DIFF_USHORT(psz - pBuffer);
//
// Store the status to the kernel buffer.
//
pLogBuffer->ProtocolStatus =
(USHORT) MIN(pLogData->ProtocolStatus,UL_MAX_HTTP_STATUS_CODE);
return STATUS_SUCCESS;
}
NTSTATUS
UlCaptureLogFieldsIIS(
IN PHTTP_LOG_FIELDS_DATA pLogData,
IN PUL_INTERNAL_REQUEST pRequest,
OUT PUL_LOG_DATA_BUFFER *ppLogBuffer
)
{
PUL_LOG_DATA_BUFFER pLogBuffer;
PUL_CONFIG_GROUP_OBJECT pConfigGroup;
PCHAR psz;
PCHAR pBuffer;
ULONG MaxLength;
BOOLEAN bUtf8Enabled;
//
// Sanity check.
//
PAGED_CODE();
*ppLogBuffer = pLogBuffer = NULL;
pConfigGroup = pRequest->ConfigInfo.pLoggingConfig;
ASSERT(IS_VALID_CONFIG_GROUP(pConfigGroup));
bUtf8Enabled = UTF8_LOGGING_ENABLED();
//
// Try worst case allocation.
//
MaxLength = UlpGetLogLineSizeForIIS(pLogData,bUtf8Enabled);
ASSERT(MaxLength <= MAX_LOG_RECORD_LEN);
*ppLogBuffer = pLogBuffer = UlpAllocateLogDataBuffer(
MaxLength,
pRequest,
pConfigGroup
);
if (!pLogBuffer)
{
return STATUS_INSUFFICIENT_RESOURCES;
}
ASSERT(pLogBuffer->Data.Str.Format == HttpLoggingTypeIIS);
ASSERT(pLogBuffer->Data.Str.Flags == UL_DEFAULT_IIS_FIELDS);
// IIS log line is fragmented to three pieces as follows;
//
// [UIP, user, D, T, ][site, Server, SIP, Ttaken, BR, BS, PS, WS, ][M, URI, URIQUERY,]
// 0 511 512 1023 1024 4096
psz = pBuffer = (PCHAR) pLogBuffer->Line;
psz = UlpCopyField(psz,
pLogData->ClientIp,
pLogData->ClientIpLength,
MAX_LOG_DEFAULT_FIELD_LEN,
',',
TRUE,
HTTP_CTL);
*psz++ = ' ';
psz = UlpCopyUnicodeField(
psz,
pLogData->UserName,
pLogData->UserNameLength,
MAX_LOG_USERNAME_FIELD_LEN,
',',
bUtf8Enabled,
HTTP_CTL);
*psz++ = ' ';
// Store the current size of the 1st frag to Offset1
pLogBuffer->Data.Str.Offset1 = DIFF_USHORT(psz - pBuffer);
// Move pointer to the beginning of 2nd frag.
pBuffer = psz = (PCHAR) &pLogBuffer->Line[IIS_LOG_LINE_SECOND_FRAGMENT_OFFSET];
psz = UlpCopyField(psz,
pLogData->ServiceName,
pLogData->ServiceNameLength,
MAX_LOG_DEFAULT_FIELD_LEN,
',',
TRUE,
HTTP_CTL);
*psz++ = ' ';
psz = UlpCopyField(psz,
pLogData->ServerName,
pLogData->ServerNameLength,
MAX_LOG_DEFAULT_FIELD_LEN,
',',
TRUE,
HTTP_CTL);
*psz++ = ' ';
psz = UlpCopyField(psz,
pLogData->ServerIp,
pLogData->ServerIpLength,
MAX_LOG_DEFAULT_FIELD_LEN,
',',
TRUE,
HTTP_CTL);
*psz++ = ' ';
// Store the current size of the 2nd frag to Offset2
pLogBuffer->Data.Str.Offset2 = DIFF_USHORT(psz - pBuffer);
// Following fields might be updated upon send completion
// do not copy them yet, just store their values.
pLogBuffer->ProtocolStatus =
(USHORT) MIN(pLogData->ProtocolStatus,UL_MAX_HTTP_STATUS_CODE);
pLogBuffer->Win32Status = pLogData->Win32Status;
// Move pointer to the beginning of 3rd frag.
pBuffer = psz = (PCHAR) &pLogBuffer->Line[IIS_LOG_LINE_THIRD_FRAGMENT_OFFSET];
psz = UlpCopyField(psz,
pLogData->Method,
pLogData->MethodLength,
MAX_LOG_METHOD_FIELD_LEN,
',',
FALSE,
HTTP_CTL);
*psz++ = ' ';
psz = UlpCopyUnicodeField(
psz,
pLogData->UriStem,
pLogData->UriStemLength,
MAX_LOG_EXTEND_FIELD_LEN,
',',
bUtf8Enabled,
HTTP_CTL);
*psz++ = ' ';
psz = UlpCopyField(psz,
pLogData->UriQuery,
pLogData->UriQueryLength,
MAX_LOG_EXTEND_FIELD_LEN,
',',
TRUE,
HTTP_CTL);
// Terminate the 3rd fragment. It is complete.
*psz++ = '\r'; *psz++ = '\n';
ASSERT(pLogBuffer->Used == 0);
pLogBuffer->Used = DIFF_USHORT(psz - pBuffer);
*psz++ = ANSI_NULL;
return STATUS_SUCCESS;
}
USHORT
UlpCompleteLogRecordW3C(
IN OUT PUL_LOG_DATA_BUFFER pLogData,
IN PUL_URI_CACHE_ENTRY pUriEntry
)
{
PUL_INTERNAL_REQUEST pRequest;
PCHAR psz;
PCHAR pBuffer;
PCHAR pLine;
ULONG BytesWritten;
ULONG Flags;
ASSERT(IS_VALID_LOG_DATA_BUFFER(pLogData));
ASSERT(pLogData->Data.Str.Format == HttpLoggingTypeW3C);
BytesWritten = 0;
Flags = pLogData->Data.Str.Flags;
pRequest = pLogData->pRequest;
ASSERT(UL_IS_VALID_INTERNAL_REQUEST(pRequest));
psz = pLine = (PCHAR) pLogData->Line;
// For cache-miss and cache-and-send hits the space for
// date and time is reserved at the beginning and their
// sizes are already counted for to the "Used". For pure
// cache hits, the buffer is freshly allocated. It's all
// right to copy over.
if (Flags & MD_EXTLOG_DATE)
{
UlGetDateTimeFields(
HttpLoggingTypeW3C,
psz,
&BytesWritten,
NULL,
NULL
);
psz += BytesWritten; *psz++ = ' ';
ASSERT(BytesWritten == W3C_DATE_FIELD_LEN);
}
if (Flags & MD_EXTLOG_TIME)
{
UlGetDateTimeFields(
HttpLoggingTypeW3C,
NULL,
NULL,
psz,
&BytesWritten
);
psz += BytesWritten; *psz++ = ' ';
ASSERT(BytesWritten == W3C_TIME_FIELD_LEN);
}
// If this is a cache hit restore the logging data in
// to the newly allocated log data buffer.
if (IS_PURE_CACHE_HIT(pUriEntry,pLogData))
{
ASSERT(IS_VALID_URI_CACHE_ENTRY(pUriEntry));
ASSERT(pLogData->Used == 0);
// The picked flags should not change during the
// lifetime of a cache entry.
ASSERT(DIFF(psz - pLine) == pUriEntry->UsedOffset1);
if (pUriEntry->LogDataLength)
{
RtlCopyMemory(psz,
pUriEntry->pLogData,
pUriEntry->LogDataLength
);
psz += pUriEntry->LogDataLength;
}
// Some fields need to be generated for each cache hit. These
// are not stored in the cache entry.
if ( Flags & MD_EXTLOG_USERNAME )
{
*psz++ = '-'; *psz++ = ' ';
}
if ( Flags & MD_EXTLOG_CLIENT_IP )
{
psz = UlStrPrintIP(
psz,
pRequest->pHttpConn->pConnection->RemoteAddress,
pRequest->pHttpConn->pConnection->AddressType,
' '
);
}
if ( Flags & MD_EXTLOG_PROTOCOL_VERSION )
{
psz = UlCopyHttpVersion(psz, pRequest->Version, ' ');
}
psz = UlpCopyRequestHeader(
psz,
Flags,
MD_EXTLOG_USER_AGENT,
pRequest,
HttpHeaderUserAgent,
DIFF(psz - pLine)
);
psz = UlpCopyRequestHeader(
psz,
Flags,
MD_EXTLOG_COOKIE,
pRequest,
HttpHeaderCookie,
DIFF(psz - pLine)
);
psz = UlpCopyRequestHeader(
psz,
Flags,
MD_EXTLOG_REFERER,
pRequest,
HttpHeaderReferer,
DIFF(psz - pLine)
);
psz = UlpCopyRequestHeader(
psz,
Flags,
MD_EXTLOG_HOST,
pRequest,
HttpHeaderHost,
DIFF(psz - pLine)
);
// This was a newly allocated buffer, init the "Used" field
// accordingly.
pLogData->Used = DIFF_USHORT(psz - pLine);
}
// Now complete the half baked log record by copying the remaining
// fields to the end.
pBuffer = psz = &pLine[pLogData->Used];
if ( Flags & MD_EXTLOG_HTTP_STATUS )
{
psz = UlStrPrintProtocolStatus(psz,pLogData->ProtocolStatus,' ');
}
if ( Flags & MD_EXTLOG_HTTP_SUB_STATUS )
{
psz = UlStrPrintUlong(psz, pLogData->SubStatus, ' ');
}
if ( Flags & MD_EXTLOG_WIN32_STATUS )
{
psz = UlStrPrintUlong(psz, pLogData->Win32Status,' ');
}
if ( Flags & MD_EXTLOG_BYTES_SENT )
{
psz = UlStrPrintUlonglong(psz, pRequest->BytesSent,' ');
}
if ( Flags & MD_EXTLOG_BYTES_RECV )
{
psz = UlStrPrintUlonglong(psz, pRequest->BytesReceived,' ');
}
if ( Flags & MD_EXTLOG_TIME_TAKEN )
{
psz = UlpCopyTimeStamp(psz, pRequest, ' ');
}
// Now calculate the space we have used
pLogData->Used =
(USHORT) (pLogData->Used + DIFF_USHORT(psz - pBuffer));
// Eat the last space and write the \r\n to the end.
// Only if we have any fields picked and written.
if (pLogData->Used)
{
psz = &pLine[pLogData->Used-1]; // Eat the last space
*psz++ = '\r'; *psz++ = '\n'; *psz++ = ANSI_NULL;
pLogData->Used += 1;
}
// Cleanup the UsedOffsets otherwise it will be interpreted by the
// caller as fragmented.
pLogData->Data.Str.Offset1 = pLogData->Data.Str.Offset2 = 0;
ASSERT(pLogData->Size > pLogData->Used);
return pLogData->Used;
}
USHORT
UlpCompleteLogRecordNCSA(
IN OUT PUL_LOG_DATA_BUFFER pLogData,
IN PUL_URI_CACHE_ENTRY pUriEntry
)
{
PUL_INTERNAL_REQUEST pRequest;
PCHAR psz;
PCHAR pBuffer;
PCHAR pLine;
ULONG BytesWritten;
ASSERT(IS_VALID_LOG_DATA_BUFFER(pLogData));
ASSERT(pLogData->Data.Str.Format == HttpLoggingTypeNCSA);
BytesWritten = 0;
pRequest = pLogData->pRequest;
ASSERT(UL_IS_VALID_INTERNAL_REQUEST(pRequest));
psz = pLine = (PCHAR) pLogData->Line;
// If this is a cache hit restore the logging data in
// to the newly allocated log data buffer.
if (IS_PURE_CACHE_HIT(pUriEntry,pLogData))
{
ASSERT(IS_VALID_URI_CACHE_ENTRY(pUriEntry));
ASSERT(pLogData->Used == 0);
ASSERT(pLogData->Data.Str.Offset1 == 0);
// Client IP
psz = UlStrPrintIP(
psz,
pRequest->pHttpConn->pConnection->RemoteAddress,
pRequest->pHttpConn->pConnection->AddressType,
' '
);
// Fixed dash
*psz++ = '-'; *psz++ = ' ';
// Authenticated users cannot be served from cache.
*psz++ = '-'; *psz++ = ' ';
// Mark the beginning of the date & time fields
pLogData->Data.Str.Offset1 = DIFF_USHORT(psz - pLine);
}
// [date:time GmtOffset] -> "[07/Jan/2000:00:02:23 -0800] "
// Restore the pointer to the reserved space first.
psz = &pLine[pLogData->Data.Str.Offset1];
*psz++ = '[';
UlGetDateTimeFields(
HttpLoggingTypeNCSA,
psz,
&BytesWritten,
NULL,
NULL
);
psz += BytesWritten; *psz++ = ':';
ASSERT(BytesWritten == 11);
UlGetDateTimeFields(
HttpLoggingTypeNCSA,
NULL,
NULL,
psz,
&BytesWritten
);
psz += BytesWritten; *psz++ = ' ';
ASSERT(BytesWritten == 8);
UlAcquirePushLockShared(&g_pUlNonpagedData->LogListPushLock);
psz = UlStrPrintStr(psz, g_GMTOffset,']');
UlReleasePushLockShared(&g_pUlNonpagedData->LogListPushLock);
*psz++ = ' ';
ASSERT(DIFF(psz - &pLine[pLogData->Data.Str.Offset1])
== NCSA_FIX_DATE_AND_TIME_FIELD_SIZE);
if (IS_PURE_CACHE_HIT(pUriEntry,pLogData))
{
ASSERT(pUriEntry->LogDataLength);
ASSERT(pUriEntry->pLogData);
RtlCopyMemory( psz,
pUriEntry->pLogData,
pUriEntry->LogDataLength
);
psz += pUriEntry->LogDataLength;
// Protocol Version
psz = UlCopyHttpVersion(psz, pRequest->Version, '\"');
*psz++ = ' ';
// Init the "Used" according to the cached data and date &
// time fields we have generated.
pLogData->Used = DIFF_USHORT(psz - pLine);
}
// Forward to the end.
pBuffer = psz = &pLine[pLogData->Used];
psz = UlStrPrintProtocolStatus(psz, pLogData->ProtocolStatus,' ');
psz = UlStrPrintUlonglong(psz, pRequest->BytesSent,'\r');
pLogData->Used =
(USHORT) (pLogData->Used + DIFF_USHORT(psz - pBuffer));
// \n\0
*psz++ = '\n'; *psz++ = ANSI_NULL;
pLogData->Used += 1;
// Cleanup the UsedOffsets otherwise length calculation will
// fail down below.
pLogData->Data.Str.Offset1 = pLogData->Data.Str.Offset2 = 0;
ASSERT(pLogData->Size > pLogData->Used);
return pLogData->Used;
}
USHORT
UlpCompleteLogRecordIIS(
IN OUT PUL_LOG_DATA_BUFFER pLogData,
IN PUL_URI_CACHE_ENTRY pUriEntry
)
{
PUL_INTERNAL_REQUEST pRequest;
PCHAR psz;
PCHAR pLine;
PCHAR pTemp;
ULONG BytesWritten;
ASSERT(IS_VALID_LOG_DATA_BUFFER(pLogData));
ASSERT(pLogData->Data.Str.Format == HttpLoggingTypeIIS);
BytesWritten = 0;
pRequest = pLogData->pRequest;
ASSERT(UL_IS_VALID_INTERNAL_REQUEST(pRequest));
psz = pLine = (PCHAR) pLogData->Line;
//
// Now we need to handle two different ways of completing this
// IIS log record; 1) Cache-miss, Build and Send Cache hit case,
// where the buffer interpreted as three different fragments.
// 2) Pure Cache-hit case where the buffer is used contiguously.
//
//
// Complete the first fragment
//
if (IS_PURE_CACHE_HIT(pUriEntry,pLogData))
{
ASSERT(pLogData->Used == 0);
ASSERT(pLogData->Data.Str.Offset1 == 0);
ASSERT(pLogData->Data.Str.Offset2 == 0);
ASSERT(IS_VALID_URI_CACHE_ENTRY(pUriEntry));
// Client IP
psz = UlStrPrintIP(
pLine,
pRequest->pHttpConn->pConnection->RemoteAddress,
pRequest->pHttpConn->pConnection->AddressType,
','
);
*psz++ = ' ';
// Authenticated users cannot be served from cache.
*psz++ = '-'; *psz++ = ','; *psz++ = ' ';
}
else
{
ASSERT(pLogData->Data.Str.Offset1);
psz = pLine + pLogData->Data.Str.Offset1;
}
pTemp = psz;
UlGetDateTimeFields(
HttpLoggingTypeIIS,
psz,
&BytesWritten,
NULL,
NULL
);
psz += BytesWritten; *psz++ = ','; *psz++ = ' ';
UlGetDateTimeFields(
HttpLoggingTypeIIS,
NULL,
NULL,
psz,
&BytesWritten
);
psz += BytesWritten; *psz++ = ','; *psz++ = ' ';
ASSERT(DIFF(psz - pTemp) <= IIS_MAX_DATE_AND_TIME_FIELD_SIZE);
pLogData->Data.Str.Offset1 = DIFF_USHORT(psz - pLine);
//
// Complete the second fragment.
//
if (IS_PURE_CACHE_HIT(pUriEntry,pLogData))
{
ASSERT(pUriEntry->pLogData);
ASSERT(pUriEntry->LogDataLength);
ASSERT(pUriEntry->LogDataLength ==
(ULONG) (pUriEntry->UsedOffset1 +
pUriEntry->UsedOffset2)
);
// Remember the fragment start.
pTemp = psz;
// Restore it from the cache
RtlCopyMemory( psz,
pUriEntry->pLogData,
pUriEntry->UsedOffset1
);
psz += pUriEntry->UsedOffset1;
}
else
{
// Fragmented. And 2nd frag cannot be empty.
ASSERT(pLogData->Data.Str.Offset2);
// Remember the fragment start.
pTemp = pLine
+ IIS_LOG_LINE_SECOND_FRAGMENT_OFFSET;
// Jump to the end of the 2nd frag.
psz = pTemp
+ pLogData->Data.Str.Offset2;
}
psz = UlpCopyTimeStamp(psz, pRequest, ',');
*psz++ = ' ';
psz = UlStrPrintUlonglong(psz, pRequest->BytesReceived,',');
*psz++ = ' ';
psz = UlStrPrintUlonglong(psz, pRequest->BytesSent,',');
*psz++ = ' ';
psz = UlStrPrintProtocolStatus(psz,pLogData->ProtocolStatus,',');
*psz++ = ' ';
psz = UlStrPrintUlong(psz, pLogData->Win32Status,',');
*psz++ = ' ';
pLogData->Data.Str.Offset2 = DIFF_USHORT(psz - pTemp);
//
// Complete the third fragment.
//
if (IS_PURE_CACHE_HIT(pUriEntry,pLogData))
{
RtlCopyMemory( psz,
&pUriEntry->pLogData[pUriEntry->UsedOffset1],
pUriEntry->UsedOffset2
);
// Total record size is whatever we have copied before this
// last copy plus the size of the last copy.
pLogData->Used = (USHORT)
(DIFF_USHORT(psz - pLine) + pUriEntry->UsedOffset2);
// Reset the UsedOffset1 & 2 to zero to tell that the log line
// is not fragmented anymore but a complete line.
pLogData->Data.Str.Offset1 = pLogData->Data.Str.Offset2 = 0;
}
else
{
// It is already there and its size is stored in "Used".
ASSERT(pLogData->Used);
}
ASSERT(pLogData->Size > (pLogData->Data.Str.Offset1 +
pLogData->Data.Str.Offset2 +
pLogData->Used));
//
// Return the complete size of the IIS log record.
//
return (pLogData->Data.Str.Offset1 +
pLogData->Data.Str.Offset2 +
pLogData->Used);
}
/***************************************************************************++
Routine Description:
UlLogHttpHit :
This function ( or its cache pair ) gets called everytime a log hit
happens. Just before completing the SendResponse request to the user.
The most likely places for calling this API or its pair for cache
is just before the send completion when we were about the destroy
send trackers.
Means:
1. UlpCompleteSendRequestWorker for ORDINARY hits; before destroying
the PUL_CHUNK_TRACKER for send operation.
2. UlpCompleteSendCacheEntryWorker for both types of CACHE hits
(cache build&send or just pure cache hit) before destroying the
the PUL_FULL_TRACKER for cache send operation.
3. Fast I/O path.
This function requires Request & Response structures ( whereas its
cache pair only requires the Request ) to successfully generate the
the log fields and even for referencing to the right log configuration
settings for this site ( thru pRequest's pConfigInfo pointer ).
Arguments:
pLogBuffer - Half baked log data allocated at the capture time.
>MUST< be cleaned up by the caller.
--***************************************************************************/
NTSTATUS
UlLogHttpHit(
IN PUL_LOG_DATA_BUFFER pLogData
)
{
NTSTATUS Status;
PUL_CONFIG_GROUP_OBJECT pConfigGroup;
PUL_INTERNAL_REQUEST pRequest;
PUL_LOG_FILE_ENTRY pEntry;
USHORT LogSize;
//
// A LOT of sanity checks.
//
PAGED_CODE();
Status = STATUS_SUCCESS;
UlTrace(LOGGING, ("Http!UlLogHttpHit: pLogData %p\n", pLogData));
ASSERT(IS_VALID_LOG_DATA_BUFFER(pLogData));
pRequest = pLogData->pRequest;
ASSERT(UL_IS_VALID_INTERNAL_REQUEST(pRequest));
//
// If logging is disabled or log settings don't
// exist then do not proceed. Just exit out.
//
if (pRequest->ConfigInfo.pLoggingConfig == NULL ||
IS_LOGGING_DISABLED(pRequest->ConfigInfo.pLoggingConfig)
)
{
return STATUS_SUCCESS;
}
pConfigGroup = pRequest->ConfigInfo.pLoggingConfig;
ASSERT(IS_VALID_CONFIG_GROUP(pConfigGroup));
#ifdef IMPLEMENT_SELECTIVE_LOGGING
//
// See if the selective logging is turned on. If it is on and
// if the request's response code does not match, do not log
// this request.
//
if (!UlpIsRequestSelected(pConfigGroup,pLogData->ProtocolStatus))
{
return STATUS_SUCCESS;
}
#endif
//
// Generate the remaining log fields.
//
switch(pLogData->Data.Str.Format)
{
case HttpLoggingTypeW3C:
{
LogSize = UlpCompleteLogRecordW3C(pLogData, NULL);
if (LogSize == 0)
{
return STATUS_SUCCESS; // No log fields, nothing to log
}
}
break;
case HttpLoggingTypeNCSA:
{
LogSize = UlpCompleteLogRecordNCSA(pLogData, NULL);
ASSERT(LogSize > 0);
}
break;
case HttpLoggingTypeIIS:
{
LogSize = UlpCompleteLogRecordIIS(pLogData, NULL);
ASSERT(LogSize > 0);
}
break;
default:
{
ASSERT(!"Unknown Log Format Type\n");
return STATUS_INVALID_PARAMETER;
}
}
//
// Finally this log line is ready to rock. Lets write it out.
//
UlAcquirePushLockShared(&g_pUlNonpagedData->LogListPushLock);
//
// We might have null pEntry pointer if the allocation failed
// because of a lack of resources. This case should be handled
// by minute timer.
//
pEntry = pConfigGroup->pLogFileEntry;
if (pEntry == NULL)
{
UlTrace(LOGGING,("Http!UlLogHttpHit: Null logfile entry !\n"));
UlReleasePushLockShared(&g_pUlNonpagedData->LogListPushLock);
return STATUS_INVALID_PARAMETER;
}
ASSERT(IS_VALID_LOG_FILE_ENTRY(pEntry));
Status = UlpCheckAndWrite(pEntry, pConfigGroup, pLogData);
if (!NT_SUCCESS(Status))
{
UlTrace(LOGGING, ("Http!UlLogHttpHit: entry %p, failure %08lx \n",
pEntry,
Status
));
}
UlReleasePushLockShared(&g_pUlNonpagedData->LogListPushLock);
return Status;
}
/***************************************************************************++
Routine Description:
If the tracker doesn't supply a pLogData, it pre-calculates the max size
and then completes the log record. Finally it logs the "complete" record
out to the log file buffer.
It also assumes the responsibility of cleaning up the pLogData,regardless
of the fact that it is provided by pTracker or not.
Arguments:
pTracker - Supplies the tracker to complete.
--***************************************************************************/
NTSTATUS
UlLogHttpCacheHit(
IN OUT PUL_FULL_TRACKER pTracker
)
{
NTSTATUS Status;
PUL_LOG_DATA_BUFFER pLogData;
PUL_LOG_FILE_ENTRY pEntry;
ULONG NewLength;
PUL_INTERNAL_REQUEST pRequest;
PUL_URI_CACHE_ENTRY pUriEntry;
PUL_CONFIG_GROUP_OBJECT pConfigGroup;
USHORT LogSize;
//
// A Lot of sanity checks.
//
PAGED_CODE();
ASSERT(pTracker);
ASSERT(IS_VALID_FULL_TRACKER(pTracker));
Status = STATUS_SUCCESS;
pRequest = pTracker->pRequest;
ASSERT(UL_IS_VALID_INTERNAL_REQUEST(pRequest));
pUriEntry = pTracker->pUriEntry;
ASSERT(IS_VALID_URI_CACHE_ENTRY(pUriEntry));
//
// If the tracker has already a log buffer allocated , carry
// over the ownership of that pLogData. This would happen
// for build and send type of cache hits.
//
pLogData = pTracker->pLogData;
pTracker->pLogData = NULL;
//
// If logging is disabled or log settings don't exist, then
// just exit out. However goto cleanup path just in case we
// have acquired a pLogData from the tracker above.
//
pConfigGroup = pUriEntry->ConfigInfo.pLoggingConfig;
if (pConfigGroup == NULL || IS_LOGGING_DISABLED(pConfigGroup))
{
goto end;
}
ASSERT(IS_VALID_CONFIG_GROUP(pConfigGroup));
#ifdef IMPLEMENT_SELECTIVE_LOGGING
//
// See if the selective logging is turned on. If it is on and
// if the request's response code does not match, do not log
// this request.
//
if (!UlpIsRequestSelected(pConfigGroup,pUriEntry->StatusCode))
{
goto end;
}
#endif
//
// If this was a pure cache hit, we will need to allocate a new
// log data buffer here.
//
if (pLogData)
{
ASSERT(IS_VALID_LOG_DATA_BUFFER(pLogData));
ASSERT(pLogData->Flags.CacheAndSendResponse == 1);
}
else
{
switch(pConfigGroup->LoggingConfig.LogFormat)
{
case HttpLoggingTypeW3C:
NewLength = UlpGetCacheHitLogLineSizeForW3C(
pConfigGroup->LoggingConfig.LogExtFileFlags,
pRequest,
pUriEntry->LogDataLength
);
ASSERT(NewLength < MAX_LOG_RECORD_ALLOCATION_LENGTH);
break;
case HttpLoggingTypeNCSA:
NewLength = MAX_NCSA_CACHE_FIELD_OVERHEAD
+ pUriEntry->LogDataLength;
break;
case HttpLoggingTypeIIS:
NewLength = MAX_IIS_CACHE_FIELD_OVERHEAD
+ pUriEntry->LogDataLength;
break;
default:
ASSERT(!"Unknown Log Format.\n");
Status = STATUS_INVALID_DEVICE_STATE;
goto end;
}
pLogData = UlpAllocateLogDataBuffer(
NewLength,
pRequest,
pConfigGroup
);
if (!pLogData)
{
Status = STATUS_INSUFFICIENT_RESOURCES;
goto end;
}
ASSERT(IS_VALID_LOG_DATA_BUFFER(pLogData));
}
UlTrace(LOGGING,("Http!UlLogHttpCacheHit: pLogData %p\n",pLogData));
pLogData->ProtocolStatus = pUriEntry->StatusCode;
pLogData->SubStatus = 0;
LOG_SET_WIN32STATUS(
pLogData,
pTracker->IoStatus.Status
);
// TODO: For cache hits send bytes info is coming from the tracker.
// TODO: Need to update pRequest->BytesSent for cache hits as well.
pRequest->BytesSent = pTracker->IoStatus.Information;
switch(pLogData->Data.Str.Format)
{
case HttpLoggingTypeW3C:
{
LogSize = UlpCompleteLogRecordW3C(pLogData, pUriEntry);
if (LogSize == 0)
{
goto end; // No log fields, nothing to log
}
}
break;
case HttpLoggingTypeNCSA:
{
LogSize = UlpCompleteLogRecordNCSA(pLogData, pUriEntry);
ASSERT(LogSize);
}
break;
case HttpLoggingTypeIIS:
{
LogSize = UlpCompleteLogRecordIIS(pLogData, pUriEntry);
ASSERT(LogSize);
}
break;
default:
ASSERT(!"Unknown Log Format !");
goto end;
}
//
// Finally this log line is ready to rock. Let's log it out.
//
UlAcquirePushLockShared(&g_pUlNonpagedData->LogListPushLock);
pEntry = pConfigGroup->pLogFileEntry;
if (pEntry == NULL)
{
UlTrace(LOGGING,
("Http!UlpLogHttpcacheHit: pEntry is NULL !"));
UlReleasePushLockShared(
&g_pUlNonpagedData->LogListPushLock);
goto end;
}
ASSERT(IS_VALID_LOG_FILE_ENTRY(pEntry));
Status = UlpCheckAndWrite(pEntry, pConfigGroup, pLogData);
if (!NT_SUCCESS(Status))
{
UlTrace(LOGGING,
("Http!UlpLogHttpcacheHit: pEntry %p, Failure %08lx \n",
pEntry,
Status
));
}
UlReleasePushLockShared(&g_pUlNonpagedData->LogListPushLock);
end:
if (pLogData)
{
UlDestroyLogDataBuffer(pLogData);
}
return Status;
}