mirror of https://github.com/tongzx/nt5src
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.
7423 lines
203 KiB
7423 lines
203 KiB
/*++
|
|
|
|
Copyright (c) 2000-2001 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
ullog.cxx (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 "iiscnfg.h"
|
|
#include "ullogp.h"
|
|
|
|
//
|
|
// Generic Private globals.
|
|
//
|
|
|
|
LIST_ENTRY g_LogListHead = {NULL,NULL};
|
|
LONG g_LogListEntryCount = 0;
|
|
BOOLEAN g_InitLogsCalled = FALSE;
|
|
CHAR g_GMTOffset[SIZE_OF_GMT_OFFSET+1];
|
|
|
|
BOOLEAN g_UTF8Logging = FALSE;
|
|
|
|
//
|
|
// For Log Buffering
|
|
//
|
|
ULONG g_AllocationGranularity = 0;
|
|
KTIMER g_BufferTimer;
|
|
BOOLEAN g_BufferTimerInitialized = FALSE;
|
|
UL_SPIN_LOCK g_BufferTimerSpinLock;
|
|
KDPC g_BufferTimerDpcObject;
|
|
|
|
//
|
|
// For Logging Date & Time caching
|
|
//
|
|
UL_LOG_DATE_AND_TIME_CACHE
|
|
g_UlDateTimeCache[HttpLoggingTypeMaximum];
|
|
LARGE_INTEGER g_UlLogSystemTime;
|
|
FAST_MUTEX g_LogCacheFastMutex;
|
|
|
|
//
|
|
// For Log Cycling
|
|
//
|
|
KDPC g_LogTimerDpcObject;
|
|
KTIMER g_LogTimer;
|
|
BOOLEAN g_LogTimerInitialized = FALSE;
|
|
BOOLEAN g_LogTimerStarted = FALSE;
|
|
BOOLEAN g_BufferTimerStarted = FALSE;
|
|
|
|
// This spinlock covers both g_BufferTimerStarted and
|
|
// g_LogTimerStarted variables
|
|
|
|
UL_SPIN_LOCK g_LogTimerSpinLock;
|
|
|
|
//
|
|
// Used to wait for endpoints to close on shutdown
|
|
//
|
|
|
|
UL_SPIN_LOCK g_BufferIoSpinLock;
|
|
BOOLEAN g_BufferWaitingForIoComplete = FALSE;
|
|
KEVENT g_BufferIoCompleteEvent;
|
|
ULONG g_BufferIoCount = 0;
|
|
|
|
#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, UlpGetLogFileLength )
|
|
#pragma alloc_text( PAGE, UlCreateLog )
|
|
#pragma alloc_text( PAGE, UlRemoveLogFileEntry )
|
|
#pragma alloc_text( PAGE, UlpConstructLogFileEntry )
|
|
|
|
#pragma alloc_text( PAGE, UlProbeLogData )
|
|
#pragma alloc_text( PAGE, UlAllocateLogDataBuffer )
|
|
#pragma alloc_text( PAGE, UlpConstructFileName )
|
|
#pragma alloc_text( PAGE, UlpUpdateLogFlags )
|
|
#pragma alloc_text( PAGE, UlpUpdateLogTruncateSize )
|
|
#pragma alloc_text( PAGE, UlpUpdatePeriod )
|
|
#pragma alloc_text( PAGE, UlpUpdateFormat )
|
|
#pragma alloc_text( PAGE, UlReConfigureLogEntry )
|
|
#pragma alloc_text( PAGE, UlpGrowLogEntry )
|
|
|
|
#pragma alloc_text( PAGE, UlLogTimerHandler )
|
|
#pragma alloc_text( PAGE, UlBufferTimerHandler )
|
|
#pragma alloc_text( PAGE, UlpCalculateTimeToExpire )
|
|
#pragma alloc_text( PAGE, UlpCreateSafeDirectory )
|
|
#pragma alloc_text( PAGE, UlpAppendW3CLogTitle )
|
|
#pragma alloc_text( PAGE, UlpWriteToLogFile )
|
|
#pragma alloc_text( PAGE, UlpFlushLogFile )
|
|
#pragma alloc_text( PAGE, UlCheckLogDirectory )
|
|
#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, UlLogHttpCacheHitWorker )
|
|
#pragma alloc_text( PAGE, UlLogHttpHit )
|
|
|
|
#pragma alloc_text( PAGE, UlpGenerateDateAndTimeFields )
|
|
#pragma alloc_text( PAGE, UlpGetDateTimeFields )
|
|
|
|
#pragma alloc_text( PAGE, UlpLogCloseHandle )
|
|
#pragma alloc_text( PAGE, UlpLogCloseHandleWorker )
|
|
|
|
#endif // ALLOC_PRAGMA
|
|
|
|
#if 0
|
|
|
|
NOT PAGEABLE -- UlLogTimerDpcRoutine
|
|
NOT PAGEABLE -- UlpTerminateLogTimer
|
|
NOT PAGEABLE -- UlpInsertLogFileEntry
|
|
NOT PAGEABLE -- UlpSetLogTimer
|
|
NOT PAGEABLE -- UlpSetBufferTimer
|
|
NOT PAGEABLE -- UlBufferTimerDpcRoutine
|
|
NOT PAGEABLE -- UlpTerminateTimers
|
|
NOT PAGEABLE -- UlpInitializeTimers
|
|
NOT PAGEABLE -- UlWaitForBufferIoToComplete
|
|
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);
|
|
|
|
Status = UlInitializeResource(
|
|
&g_pUlNonpagedData->LogListResource,
|
|
"LogListResource",
|
|
0,
|
|
UL_LOG_LIST_RESOURCE_TAG
|
|
);
|
|
|
|
ASSERT(NT_SUCCESS(Status)); // the call always returns success
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
return Status;
|
|
}
|
|
|
|
g_InitLogsCalled = TRUE;
|
|
|
|
UlpInitializeTimers();
|
|
|
|
UlpInitializeLogCache();
|
|
|
|
UlpGetGMTOffset();
|
|
|
|
UlInitializeSpinLock( &g_BufferIoSpinLock, "g_BufferIoSpinLock" );
|
|
|
|
//
|
|
// Get the allocation granularity from the system
|
|
// It will be used as log buffer size if there's no registry
|
|
// overwrites.
|
|
//
|
|
|
|
UlpInitializeLogBufferGranularity();
|
|
if (g_AllocationGranularity == 0)
|
|
{
|
|
g_AllocationGranularity = DEFAULT_MAX_LOG_BUFFER_SIZE;
|
|
}
|
|
|
|
//
|
|
// Overwrite the log buffer size with the above value
|
|
// if registry parameter doesn't exist.
|
|
//
|
|
|
|
if (g_UlLogBufferSize == 0)
|
|
{
|
|
// No registry parameter available use the system granularity
|
|
|
|
g_UlLogBufferSize = g_AllocationGranularity;
|
|
}
|
|
else
|
|
{
|
|
// Proceed with using the registry provided log buffer size
|
|
|
|
UlTrace( LOGGING,
|
|
("Ul!UlInitializeLogs: Log buffer size %d from registry!\n",
|
|
g_UlLogBufferSize
|
|
));
|
|
}
|
|
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
UlTerminateLogs :
|
|
|
|
Deletes the resource for log list synchronization
|
|
|
|
--***************************************************************************/
|
|
|
|
VOID
|
|
UlTerminateLogs(
|
|
VOID
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
|
|
PAGED_CODE();
|
|
|
|
if (g_InitLogsCalled)
|
|
{
|
|
ASSERT( IsListEmpty( &g_LogListHead )) ;
|
|
|
|
//
|
|
// Make sure terminate the log timer before
|
|
// deleting the log list resource
|
|
//
|
|
|
|
UlpTerminateTimers();
|
|
|
|
Status = UlDeleteResource(
|
|
&g_pUlNonpagedData->LogListResource
|
|
);
|
|
|
|
ASSERT(NT_SUCCESS(Status));
|
|
|
|
UlWaitForBufferIoToComplete();
|
|
|
|
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,("Ul!UlSetUTF8Logging: UTF8Logging Old %d -> New %d\n",
|
|
g_UTF8Logging,UTF8Logging
|
|
));
|
|
|
|
UlAcquireResourceExclusive(&g_pUlNonpagedData->LogListResource, TRUE);
|
|
|
|
//
|
|
// 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
|
|
);
|
|
|
|
// TODO: Investigate whether we really need to acquire this or not.
|
|
|
|
UlAcquireResourceExclusive(&pEntry->EntryResource, TRUE);
|
|
|
|
SET_SEQUNCE_NUMBER_STALE(pEntry);
|
|
|
|
Status = UlpRecycleLogFile(pEntry);
|
|
ASSERT(NT_SUCCESS(Status));
|
|
|
|
UlReleaseResource(&pEntry->EntryResource);
|
|
}
|
|
|
|
end:
|
|
UlReleaseResource(&g_pUlNonpagedData->LogListResource);
|
|
|
|
return Status;
|
|
}
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
UlpGetLogFileLength :
|
|
|
|
A utility to get the log file length, for a possible size check
|
|
|
|
Arguments:
|
|
|
|
hFile - handle to file
|
|
|
|
Return Value:
|
|
|
|
ULONG - the length of the file
|
|
|
|
--***************************************************************************/
|
|
|
|
ULONG
|
|
UlpGetLogFileLength(
|
|
IN HANDLE hFile
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
FILE_STANDARD_INFORMATION StandardInformation;
|
|
IO_STATUS_BLOCK IoStatusBlock;
|
|
ULONG Length;
|
|
|
|
PAGED_CODE();
|
|
|
|
Status = ZwQueryInformationFile(
|
|
hFile,
|
|
&IoStatusBlock,
|
|
&StandardInformation,
|
|
sizeof(StandardInformation),
|
|
FileStandardInformation
|
|
);
|
|
ASSERT(NT_SUCCESS(Status));
|
|
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
ASSERT(StandardInformation.EndOfFile.HighPart == 0);
|
|
|
|
Length = StandardInformation.EndOfFile.LowPart;
|
|
}
|
|
else
|
|
{
|
|
Length = 0;
|
|
}
|
|
|
|
return Length;
|
|
}
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Will allocate/fill up a new UNICODE_STRING to hold the directory name info
|
|
based on the LocalDrive/UNC.
|
|
|
|
It's caller's responsibility to cleanup the unicode buffer. If return code
|
|
is SUCCESS otherwise no buffer get allocated at all.
|
|
|
|
Arguments:
|
|
|
|
PWSTR - the directory name as it's received from the user.
|
|
|
|
--***************************************************************************/
|
|
|
|
NTSTATUS
|
|
UlpBuildLogDirectory(
|
|
IN PUNICODE_STRING pSrcDirName,
|
|
IN OUT PUNICODE_STRING pDstDirName
|
|
)
|
|
{
|
|
UNICODE_STRING PathPrefix;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT(pSrcDirName);
|
|
ASSERT(pDstDirName);
|
|
|
|
UlTrace(LOGGING,(
|
|
"Ul!UlpBuildLogDirectory: Directory %S,Length %d,MaxLength %d\n",
|
|
pSrcDirName->Buffer,
|
|
pSrcDirName->Length,
|
|
pSrcDirName->MaximumLength
|
|
));
|
|
|
|
// Allocate a buffer including the terminating NULL and the prefix.
|
|
|
|
pDstDirName->Length = 0;
|
|
pDstDirName->MaximumLength =
|
|
pSrcDirName->Length + (UL_MAX_PATH_PREFIX_LENGTH+1) * sizeof(WCHAR);
|
|
|
|
pDstDirName->Buffer =
|
|
(PWSTR) UL_ALLOCATE_ARRAY(
|
|
PagedPool,
|
|
UCHAR,
|
|
pDstDirName->MaximumLength,
|
|
UL_CG_LOGDIR_POOL_TAG
|
|
);
|
|
if (pDstDirName->Buffer == NULL)
|
|
{
|
|
return STATUS_NO_MEMORY;
|
|
}
|
|
|
|
ASSERT(pSrcDirName->Length > sizeof(WCHAR));
|
|
|
|
// We store the dir name to cgroup as it is. But when we are constructing
|
|
// the filename we skip the second backslash for the UNC shares and for
|
|
// local dirs w/o the drive names.
|
|
|
|
if (pSrcDirName->Buffer[0] == L'\\')
|
|
{
|
|
if (pSrcDirName->Buffer[1] == L'\\')
|
|
{
|
|
// UNC share: "\\alitudev\temp"
|
|
RtlInitUnicodeString( &PathPrefix, UL_UNC_PATH_PREFIX );
|
|
}
|
|
else
|
|
{
|
|
// Local Directory name is missing the device i.e "\temp"
|
|
// It should be fully qualified name.
|
|
|
|
UL_FREE_POOL( pDstDirName->Buffer, UL_CG_LOGDIR_POOL_TAG );
|
|
pDstDirName->Buffer = NULL;
|
|
return STATUS_NOT_SUPPORTED;
|
|
}
|
|
|
|
RtlCopyUnicodeString( pDstDirName, &PathPrefix );
|
|
RtlCopyMemory(
|
|
&pDstDirName->Buffer[pDstDirName->Length/sizeof(WCHAR)],
|
|
&pSrcDirName->Buffer[1],
|
|
pSrcDirName->Length - sizeof(WCHAR)
|
|
);
|
|
pDstDirName->Length += (pSrcDirName->Length - sizeof(WCHAR));
|
|
pDstDirName->Buffer[pDstDirName->Length/sizeof(WCHAR)] = UNICODE_NULL;
|
|
|
|
}
|
|
else
|
|
{
|
|
RtlInitUnicodeString( &PathPrefix, UL_LOCAL_PATH_PREFIX );
|
|
RtlCopyUnicodeString( pDstDirName, &PathPrefix );
|
|
RtlAppendUnicodeStringToString( pDstDirName, pSrcDirName );
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
A utility to check to see if the directory name is correct or not.
|
|
|
|
Arguments:
|
|
|
|
PWSTR - the directory name as it's received from the user.
|
|
|
|
--***************************************************************************/
|
|
|
|
NTSTATUS
|
|
UlCheckLogDirectory(
|
|
IN PUNICODE_STRING pDirName
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
UNICODE_STRING PathPrefix;
|
|
UNICODE_STRING DirectoryName;
|
|
PWCHAR pwsz;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
Status = UlpBuildLogDirectory( pDirName, &DirectoryName );
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
goto end;
|
|
}
|
|
|
|
//
|
|
// Create/Open the director(ies) to see whether it's correct or not.
|
|
//
|
|
|
|
Status = UlpCreateSafeDirectory( &DirectoryName );
|
|
|
|
end:
|
|
UlTrace(LOGGING,("Ul!UlCheckLogDirectory: [%S] -> [%S], Status %08lx\n",
|
|
pDirName->Buffer,
|
|
DirectoryName.Buffer,
|
|
Status
|
|
));
|
|
|
|
if (DirectoryName.Buffer)
|
|
{
|
|
UL_FREE_POOL( DirectoryName.Buffer, UL_CG_LOGDIR_POOL_TAG );
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Everytime Aynsc Write Io happens on Log Buffer This APC get called when
|
|
completion happens and decrement the global Io Count. If shutting down
|
|
we set the event.
|
|
|
|
This is basically to prevent against shutting down before the Io Complete.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
--***************************************************************************/
|
|
|
|
VOID
|
|
UlpBufferFlushAPC(
|
|
IN PVOID ApcContext,
|
|
IN PIO_STATUS_BLOCK pIoStatusBlock,
|
|
IN ULONG Reserved
|
|
)
|
|
{
|
|
PUL_LOG_FILE_BUFFER pLogBuffer;
|
|
ULONG IoCount;
|
|
KIRQL OldIrql;
|
|
|
|
UlTrace( LOGGING,("Ul!UlpBufferFlushAPC: entry %p and status %08lx Count %d\n",
|
|
ApcContext,
|
|
pIoStatusBlock->Status,
|
|
(g_BufferIoCount - 1)
|
|
));
|
|
|
|
//
|
|
// Free the LogBuffer allocated for this write I/o.
|
|
//
|
|
|
|
pLogBuffer = (PUL_LOG_FILE_BUFFER) ApcContext;
|
|
|
|
ASSERT(IS_VALID_LOG_FILE_BUFFER(pLogBuffer));
|
|
|
|
UlPplFreeLogBuffer( pLogBuffer );
|
|
|
|
//
|
|
// Decrement the global outstanding i/o count.
|
|
//
|
|
|
|
IoCount = InterlockedDecrement((PLONG) &g_BufferIoCount);
|
|
|
|
if ( IoCount == 0 )
|
|
{
|
|
UlAcquireSpinLock( &g_BufferIoSpinLock, &OldIrql );
|
|
|
|
//
|
|
// Set the event if we hit to zero and waiting for drain.
|
|
//
|
|
|
|
if ( g_BufferWaitingForIoComplete )
|
|
{
|
|
KeSetEvent( &g_BufferIoCompleteEvent, 0, FALSE );
|
|
}
|
|
|
|
UlReleaseSpinLock( &g_BufferIoSpinLock, OldIrql );
|
|
}
|
|
}
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Waits for Io Completions to complete on Log Buffers before shutdown.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UlWaitForBufferIoToComplete(
|
|
VOID
|
|
)
|
|
{
|
|
KIRQL OldIrql;
|
|
BOOLEAN Wait = FALSE;
|
|
|
|
ASSERT( g_InitLogsCalled );
|
|
|
|
if ( g_InitLogsCalled )
|
|
{
|
|
UlAcquireSpinLock( &g_BufferIoSpinLock, &OldIrql );
|
|
|
|
if ( !g_BufferWaitingForIoComplete )
|
|
{
|
|
g_BufferWaitingForIoComplete = TRUE;
|
|
|
|
KeInitializeEvent(
|
|
&g_BufferIoCompleteEvent,
|
|
NotificationEvent,
|
|
FALSE
|
|
);
|
|
}
|
|
|
|
//
|
|
// If no more i/o operations are happening we are not going to
|
|
// wait for them. It is not possible for global i/o counter to
|
|
// increment at this time because the log file entry list is empty.
|
|
// If there were outstanding i/o then we have to wait them to be
|
|
// complete.
|
|
//
|
|
|
|
if ( g_BufferIoCount > 0 )
|
|
{
|
|
Wait = TRUE;
|
|
}
|
|
|
|
UlReleaseSpinLock( &g_BufferIoSpinLock, OldIrql );
|
|
|
|
if (Wait)
|
|
{
|
|
KeWaitForSingleObject(
|
|
(PVOID)&g_BufferIoCompleteEvent,
|
|
UserRequest,
|
|
KernelMode,
|
|
FALSE,
|
|
NULL
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
UlpFlushLogFile :
|
|
|
|
Assumes you are holding the FastMutex for the buffer & loglist resource
|
|
|
|
Arguments:
|
|
|
|
pFile - Handle to a log file entry
|
|
|
|
--***************************************************************************/
|
|
|
|
NTSTATUS
|
|
UlpFlushLogFile(
|
|
IN PUL_LOG_FILE_ENTRY pFile
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
LARGE_INTEGER EndOfFile;
|
|
PUL_LOG_FILE_BUFFER pLogBuffer;
|
|
PIO_STATUS_BLOCK pIoStatusBlock;
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT(pFile!=NULL);
|
|
|
|
UlTrace( LOGGING, ("Ul!UlpFlushLogFile: pEntry %p\n", pFile ));
|
|
|
|
pLogBuffer = pFile->LogBuffer;
|
|
|
|
if ( pLogBuffer==NULL || pLogBuffer->BufferUsed==0 || pFile->hFile==NULL )
|
|
{
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
pFile->LogBuffer = NULL;
|
|
pIoStatusBlock = &pLogBuffer->IoStatusBlock;
|
|
|
|
EndOfFile.HighPart = -1;
|
|
EndOfFile.LowPart = FILE_WRITE_TO_END_OF_FILE;
|
|
|
|
Status = ZwWriteFile(
|
|
pFile->hFile,
|
|
NULL,
|
|
&UlpBufferFlushAPC,
|
|
pLogBuffer,
|
|
pIoStatusBlock,
|
|
pLogBuffer->Buffer,
|
|
pLogBuffer->BufferUsed,
|
|
&EndOfFile,
|
|
NULL
|
|
);
|
|
|
|
if ( !NT_SUCCESS(Status) )
|
|
{
|
|
//
|
|
// Status maybe STATUS_DISK_FULL,in that case Logging
|
|
// will be ceased. Hence log hits stored in this buffer
|
|
// are lost.
|
|
//
|
|
|
|
UlTrace( LOGGING,
|
|
("Ul!UlpFlushLogFile: ZwWriteFile Failure %08lx \n",
|
|
Status
|
|
));
|
|
|
|
UlPplFreeLogBuffer( pLogBuffer );
|
|
|
|
return Status;
|
|
}
|
|
|
|
//
|
|
// Properly keep the number of outstanding Io
|
|
//
|
|
|
|
InterlockedIncrement( (PLONG) &g_BufferIoCount );
|
|
|
|
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, ("Ul!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_UlDebugLogBufferPeriod)
|
|
{
|
|
//
|
|
// 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.
|
|
//
|
|
|
|
UlAcquireResourceExclusive(&pFile->EntryResource, TRUE);
|
|
|
|
Status = UlpWriteToLogFileDebug(
|
|
pFile,
|
|
RecordSize,
|
|
pRecord,
|
|
UsedOffset1,
|
|
UsedOffset2
|
|
);
|
|
|
|
UlReleaseResource(&pFile->EntryResource);
|
|
|
|
return Status;
|
|
}
|
|
|
|
//
|
|
// Try UlpWriteToLogFileShared first which merely moves the
|
|
// BufferUsed forward and copy the record to LogBuffer->Buffer.
|
|
//
|
|
|
|
UlAcquireResourceShared(&pFile->EntryResource, TRUE);
|
|
|
|
Status = UlpWriteToLogFileShared(
|
|
pFile,
|
|
RecordSize,
|
|
pRecord,
|
|
UsedOffset1,
|
|
UsedOffset2
|
|
);
|
|
|
|
UlReleaseResource(&pFile->EntryResource);
|
|
|
|
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.
|
|
//
|
|
|
|
UlAcquireResourceExclusive(&pFile->EntryResource, TRUE);
|
|
|
|
Status = UlpWriteToLogFileExclusive(
|
|
pFile,
|
|
RecordSize,
|
|
pRecord,
|
|
UsedOffset1,
|
|
UsedOffset2
|
|
);
|
|
|
|
UlReleaseResource(&pFile->EntryResource);
|
|
}
|
|
|
|
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
|
|
FASTCALL
|
|
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, ("Ul!UlpAppendToLogBuffer: pEntry %p\n", pFile ));
|
|
|
|
//
|
|
// 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
|
|
);
|
|
}
|
|
|
|
UlpIncrementBytesWritten(pFile, 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
|
|
)
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
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,("Ul!UlpAppendW3CLogTitle: Copying to Provided Buffer %p\n",
|
|
pDestBuffer));
|
|
|
|
TitleBuffer = pDestBuffer;
|
|
}
|
|
else
|
|
{
|
|
// Append to the entry buffer
|
|
|
|
ASSERT(pLogBuffer);
|
|
ASSERT(pLogBuffer->Buffer);
|
|
|
|
UlTrace(LOGGING,("Ul!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 \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(UlLogFieldProtocolStatus,LogExtFileFlags,MD_EXTLOG_HTTP_STATUS),
|
|
UL_GET_LOG_TITLE_IF_PICKED(UlLogFieldWin32Status,LogExtFileFlags,MD_EXTLOG_WIN32_STATUS),
|
|
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(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;
|
|
UlpIncrementBytesWritten(pEntry, BytesCopied);
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Writes a record to the log buffer and flushes.
|
|
This func only get called when debug parameter
|
|
g_UlDebugLogBufferPeriod 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(UlDbgResourceOwnedExclusive(&pFile->EntryResource));
|
|
ASSERT(g_UlDebugLogBufferPeriod!=0);
|
|
|
|
UlTrace(LOGGING,("Ul!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->hFile==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 = UlPplAllocateLogBuffer();
|
|
if (!pFile->LogBuffer)
|
|
{
|
|
return STATUS_NO_MEMORY;
|
|
}
|
|
}
|
|
|
|
pLogBuffer = pFile->LogBuffer;
|
|
ASSERT(pLogBuffer->BufferUsed == 0);
|
|
|
|
if (!pFile->Flags.LogTitleWritten)
|
|
{
|
|
UlpAppendW3CLogTitle(pFile, NULL, NULL);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
pFile->Flags.LogTitleWritten = 1;
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
/***************************************************************************++
|
|
|
|
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_UlDebugLogBufferPeriod== 0);
|
|
|
|
pLogBuffer = pFile->LogBuffer;
|
|
|
|
UlTrace(LOGGING,("Ul!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->hFile==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;
|
|
}
|
|
|
|
} while (BufferUsed != InterlockedCompareExchange(
|
|
&pLogBuffer->BufferUsed,
|
|
RecordSize + BufferUsed,
|
|
BufferUsed
|
|
));
|
|
|
|
//
|
|
// Keep buffering untill 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_UlDebugLogBufferPeriod== 0);
|
|
ASSERT(UlDbgResourceOwnedExclusive(&pFile->EntryResource));
|
|
|
|
UlTrace(LOGGING,("Ul!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->hFile==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);
|
|
UlpAppendW3CLogTitle(pFile, NULL, NULL);
|
|
pFile->Flags.LogTitleWritten = 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 = UlPplAllocateLogBuffer();
|
|
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)
|
|
{
|
|
UlpAppendW3CLogTitle(pFile, NULL, NULL);
|
|
pFile->Flags.LogTitleWritten = 1;
|
|
}
|
|
|
|
UlpAppendToLogBuffer(
|
|
pFile,
|
|
pLogBuffer->BufferUsed,
|
|
RecordSize,
|
|
pRecord,
|
|
UsedOffset1,
|
|
UsedOffset2
|
|
);
|
|
|
|
pLogBuffer->BufferUsed += RecordSize;
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
UlpQueryDirectory:
|
|
|
|
* What file should IIS write to when logging type is daily/weekly/monthly/
|
|
hourly if there is already a log file there for that day?
|
|
|
|
IIS should write to the current day/week/month/hour's log file. For
|
|
example, let's say there's an extended log file in my log directory
|
|
called ex000727.log. IIS should append new log entries to this log,
|
|
as it is for today.
|
|
|
|
* What file should IIS write to when logging type is MAXSIZE when there are
|
|
already log files there for maxsize (like extend0.log, extend1.log, etc.)?
|
|
|
|
IIS should write to the max extend#.log file, where max(extend#.log)
|
|
is has the largest # in the #field for extend#.log. This is provided,
|
|
of course, that the MAXSIZE in that file hasn't been exceeded.
|
|
|
|
* This function quite similar to the implementation of the FindFirstFile
|
|
Win32 API. Except that it has been shaped to our purposes.
|
|
|
|
Arguments:
|
|
|
|
pEntry - The log file entry which freshly created.
|
|
|
|
--***************************************************************************/
|
|
|
|
NTSTATUS
|
|
UlpQueryDirectory(
|
|
IN OUT PUL_LOG_FILE_ENTRY pEntry
|
|
)
|
|
{
|
|
#define GET_NEXT_FILE(pv, cb) \
|
|
(FILE_DIRECTORY_INFORMATION *) ((VOID *) (((UCHAR *) (pv)) + (cb)))
|
|
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
OBJECT_ATTRIBUTES ObjectAttributes;
|
|
IO_STATUS_BLOCK IoStatusBlock;
|
|
LONG WcharsCopied;
|
|
HANDLE hDirectory;
|
|
ULONG Sequence;
|
|
ULONG LastSequence;
|
|
UNICODE_STRING Temp;
|
|
PWCHAR pTemp;
|
|
UNICODE_STRING FileName;
|
|
WCHAR _FileName[UL_MAX_FILE_NAME_SUFFIX_LENGTH];
|
|
|
|
FILE_DIRECTORY_INFORMATION *pFdi;
|
|
PUCHAR FileInfoBuffer;
|
|
ULARGE_INTEGER FileSize;
|
|
WCHAR OriginalWChar;
|
|
|
|
PAGED_CODE();
|
|
|
|
Status = STATUS_SUCCESS;
|
|
hDirectory = NULL;
|
|
FileInfoBuffer = NULL;
|
|
|
|
ASSERT(IS_VALID_LOG_FILE_ENTRY(pEntry));
|
|
|
|
UlTrace(LOGGING,("Ul!UlpQueryDirectory: %S\n",pEntry->FileName.Buffer));
|
|
|
|
ASSERT(pEntry->Period == HttpLoggingPeriodMaxSize);
|
|
|
|
ASSERT(UL_DIRECTORY_SEARCH_BUFFER_SIZE >=
|
|
(sizeof(FILE_DIRECTORY_INFORMATION)+UL_MAX_FILE_NAME_SUFFIX_LENGTH));
|
|
|
|
//
|
|
// Open the directory for the list access again. Use the filename in
|
|
// pEntry. Where pShortName points to the "\inetsv1.log" portion of
|
|
// the whole "\??\c:\whistler\system32\logfiles\w3svc1\inetsv1.log"
|
|
// Overwrite the pShortName to get the directory name. Once we are
|
|
// done with finding the last sequence we will restore it back later
|
|
// on.
|
|
//
|
|
|
|
OriginalWChar = *((PWCHAR)pEntry->pShortName);
|
|
*((PWCHAR)pEntry->pShortName) = UNICODE_NULL;
|
|
pEntry->FileName.Length =
|
|
wcslen(pEntry->FileName.Buffer) * sizeof(WCHAR);
|
|
|
|
InitializeObjectAttributes(
|
|
&ObjectAttributes,
|
|
&pEntry->FileName,
|
|
OBJ_CASE_INSENSITIVE|UL_KERNEL_HANDLE,
|
|
NULL,
|
|
NULL
|
|
);
|
|
|
|
Status = ZwCreateFile(
|
|
&hDirectory,
|
|
SYNCHRONIZE|FILE_LIST_DIRECTORY,
|
|
&ObjectAttributes,
|
|
&IoStatusBlock,
|
|
NULL,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
|
|
FILE_OPEN,
|
|
FILE_SYNCHRONOUS_IO_NONALERT|FILE_DIRECTORY_FILE,
|
|
NULL,
|
|
0
|
|
);
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
//
|
|
// This call should never fail since CreateLog already created
|
|
// the directory for us.
|
|
//
|
|
|
|
ASSERT(!"Directory Invalid!\n");
|
|
goto end;
|
|
}
|
|
|
|
//
|
|
// Before querrying we need to provide additional DOS-like wildcard
|
|
// matching semantics. In our case, only * to DOS_STAR conversion is
|
|
// enough though. The following is the pattern we will use for query
|
|
// Skipping the first slash character.
|
|
//
|
|
|
|
FileName.Buffer = &_FileName[1];
|
|
WcharsCopied = _snwprintf( _FileName,
|
|
UL_MAX_FILE_NAME_SUFFIX_LENGTH,
|
|
L"%s%c.%s",
|
|
UL_GET_LOG_FILE_NAME_PREFIX(pEntry->Format),
|
|
DOS_STAR,
|
|
DEFAULT_LOG_FILE_EXTENSION
|
|
);
|
|
ASSERT(WcharsCopied > 0);
|
|
|
|
FileName.Length = wcslen(FileName.Buffer) * sizeof(WCHAR);
|
|
FileName.MaximumLength = FileName.Length;
|
|
|
|
//
|
|
// This non-paged buffer should be allocated to be used for storing
|
|
// query results.
|
|
//
|
|
|
|
FileInfoBuffer =
|
|
UL_ALLOCATE_ARRAY(
|
|
NonPagedPool,
|
|
UCHAR,
|
|
UL_DIRECTORY_SEARCH_BUFFER_SIZE + sizeof(WCHAR),
|
|
UL_LOG_GENERIC_POOL_TAG
|
|
);
|
|
if (FileInfoBuffer == NULL)
|
|
{
|
|
Status = STATUS_NO_MEMORY;
|
|
goto end;
|
|
}
|
|
|
|
//
|
|
// The very first call may also fail if there is no log file in the
|
|
// current directory.
|
|
//
|
|
|
|
Status = ZwQueryDirectoryFile (
|
|
hDirectory,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
&IoStatusBlock,
|
|
FileInfoBuffer,
|
|
UL_DIRECTORY_SEARCH_BUFFER_SIZE,
|
|
FileDirectoryInformation,
|
|
FALSE,
|
|
&FileName,
|
|
TRUE
|
|
);
|
|
|
|
if(!NT_SUCCESS(Status))
|
|
{
|
|
//
|
|
// This should never fail with STATUS_BUFFER_OVERFLOW unless the
|
|
// buffer size is ridiculously small i.e. 50 bytes or something
|
|
//
|
|
|
|
UlTrace( LOGGING,
|
|
("Ul!UlpQueryDirectory: Status %08lx for %S & %S\n",
|
|
Status,
|
|
pEntry->FileName.Buffer,
|
|
FileName.Buffer
|
|
));
|
|
|
|
ASSERT(Status == STATUS_NO_SUCH_FILE);
|
|
|
|
Status = STATUS_SUCCESS;
|
|
goto end;
|
|
}
|
|
|
|
//
|
|
// Look into the buffer and get the sequence number from filename.
|
|
//
|
|
|
|
pFdi = (FILE_DIRECTORY_INFORMATION *) FileInfoBuffer;
|
|
Sequence = LastSequence = 1;
|
|
FileSize.QuadPart = 0;
|
|
|
|
while (TRUE)
|
|
{
|
|
//
|
|
// Get the latest Sequence Number from the filename
|
|
//
|
|
|
|
if (pTemp = wcsstr(pFdi->FileName,DEFAULT_LOG_FILE_EXTENSION_PLUS_DOT))
|
|
{
|
|
*pTemp = UNICODE_NULL;
|
|
pTemp = pFdi->FileName;
|
|
|
|
while ( *pTemp != UNICODE_NULL )
|
|
{
|
|
if ( isdigit((CHAR) (*pTemp)) )
|
|
{
|
|
Temp.Length = wcslen(pTemp) * sizeof(WCHAR);
|
|
Temp.MaximumLength = Temp.Length;
|
|
Temp.Buffer = pTemp;
|
|
|
|
RtlUnicodeStringToInteger( &Temp, 10, &LastSequence );
|
|
|
|
break;
|
|
}
|
|
pTemp++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ASSERT(FALSE);
|
|
}
|
|
|
|
if (LastSequence >= Sequence)
|
|
{
|
|
//
|
|
// Bingo ! We have two things to remember though; the file size
|
|
// and the sequence number. Cryptic it's that we are getting the
|
|
// file size from EOF. Its greater than or equal because we want
|
|
// to initialize the FileSize properly even if there's only one
|
|
// match.
|
|
//
|
|
|
|
Sequence = LastSequence;
|
|
|
|
//
|
|
// BUGBUG: The HighPart is zero unless the file size is greater
|
|
// than the MAXDWORD. If so the file size calculated as in the
|
|
// formula; ((HighPart * (MAXDWORD+1)) + LowPart)
|
|
//
|
|
|
|
FileSize.LowPart = pFdi->EndOfFile.LowPart;
|
|
}
|
|
|
|
//
|
|
// Keep going until we see no more files
|
|
//
|
|
|
|
if (pFdi->NextEntryOffset != 0)
|
|
{
|
|
//
|
|
// Search through the buffer as long as there is still something
|
|
// in there.
|
|
//
|
|
|
|
pFdi = GET_NEXT_FILE(pFdi, pFdi->NextEntryOffset);
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Otherwise query again for any other possible log file(s)
|
|
//
|
|
|
|
Status = ZwQueryDirectoryFile (
|
|
hDirectory,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
&IoStatusBlock,
|
|
FileInfoBuffer,
|
|
UL_DIRECTORY_SEARCH_BUFFER_SIZE,
|
|
FileDirectoryInformation,
|
|
FALSE,
|
|
NULL,
|
|
FALSE
|
|
);
|
|
|
|
if (Status == STATUS_NO_MORE_FILES)
|
|
{
|
|
Status = STATUS_SUCCESS;
|
|
break;
|
|
}
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
goto end;
|
|
}
|
|
|
|
pFdi = (FILE_DIRECTORY_INFORMATION *) FileInfoBuffer;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Construct the log file name properly from the sequence number so that
|
|
// our caller can create the log file later on.
|
|
//
|
|
|
|
WcharsCopied = _snwprintf( pEntry->pShortName,
|
|
UL_MAX_FILE_NAME_SUFFIX_LENGTH,
|
|
L"%s%d.%s",
|
|
UL_GET_LOG_FILE_NAME_PREFIX(pEntry->Format),
|
|
Sequence,
|
|
DEFAULT_LOG_FILE_EXTENSION
|
|
);
|
|
ASSERT(WcharsCopied > 0);
|
|
|
|
pEntry->FileName.Length =
|
|
wcslen(pEntry->FileName.Buffer) * sizeof(WCHAR);
|
|
|
|
//
|
|
// Set the next sequence number according to last log file
|
|
//
|
|
|
|
pEntry->SequenceNumber = Sequence + 1;
|
|
|
|
//
|
|
// Update the log file size accordingly in the entry.Otherwise truncation
|
|
// will not work properly.
|
|
//
|
|
|
|
pEntry->TotalWritten.QuadPart = FileSize.QuadPart;
|
|
|
|
UlTrace( LOGGING,
|
|
("Ul!UlpQueryDirectory: %S has been found with size %d.\n",
|
|
pEntry->FileName.Buffer,
|
|
pEntry->TotalWritten.QuadPart
|
|
));
|
|
|
|
end:
|
|
if (*((PWCHAR)pEntry->pShortName) == UNICODE_NULL )
|
|
{
|
|
//
|
|
// We have failed for some reason before reconstructing the filename
|
|
// Perhaps because the directory was empty. Do not forget to restore
|
|
// the pShortName in the pEntry then.
|
|
//
|
|
|
|
*((PWCHAR)pEntry->pShortName) = OriginalWChar;
|
|
pEntry->FileName.Length =
|
|
wcslen(pEntry->FileName.Buffer) * sizeof(WCHAR);
|
|
}
|
|
|
|
if (FileInfoBuffer)
|
|
{
|
|
UL_FREE_POOL( FileInfoBuffer, UL_LOG_GENERIC_POOL_TAG );
|
|
}
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
UlTrace( LOGGING,
|
|
("Ul!UlpQueryDirectory: failure %08lx for %S\n",
|
|
Status,
|
|
pEntry->FileName.Buffer
|
|
));
|
|
}
|
|
|
|
if (hDirectory != NULL)
|
|
{
|
|
ZwClose(hDirectory);
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
UlCreateLog:
|
|
|
|
Creates a new Logging file and insert a corresponding entry
|
|
to the global LoggingList.
|
|
|
|
Each log file belongs to a single ConfigGroup and can be
|
|
created by one. Although we keep a list of already created log files
|
|
for convenience, each config group has a pointer to its log file.
|
|
|
|
If this function fails for any reason, file entry pointer of the
|
|
config group will set to NULL.
|
|
|
|
Arguments:
|
|
|
|
pConfigGroup - Supplies the necessary information for opening the
|
|
log file and gets the result entry pointer.
|
|
|
|
LogListResource also implicitly protects this pointer
|
|
since only possible places which are going to update
|
|
it use the resource exclusively.
|
|
|
|
--***************************************************************************/
|
|
|
|
NTSTATUS
|
|
UlCreateLog(
|
|
IN OUT PUL_CONFIG_GROUP_OBJECT pConfigGroup
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
OBJECT_ATTRIBUTES ObjectAttributes;
|
|
IO_STATUS_BLOCK IoStatusBlock;
|
|
PUL_LOG_FILE_ENTRY pNewEntry;
|
|
TIME_FIELDS CurrentTimeFields;
|
|
LARGE_INTEGER CurrentTimeStamp;
|
|
HANDLE hDirectory;
|
|
UNICODE_STRING DirectoryName;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT(pConfigGroup != NULL);
|
|
|
|
UlTrace(LOGGING, ("Ul!UlCreateLog: pConfigGroup %p Truncate %d\n",
|
|
pConfigGroup, pConfigGroup->LoggingConfig.LogFileTruncateSize
|
|
));
|
|
|
|
//
|
|
// An important check to ensure that no infinite loop occurs because of
|
|
// ridiculusly small truncatesizes. Means smaller than maximum
|
|
// allowed log record line (10*1024)
|
|
//
|
|
|
|
if ( pConfigGroup->LoggingConfig.LogPeriod == HttpLoggingPeriodMaxSize &&
|
|
pConfigGroup->LoggingConfig.LogFileTruncateSize != HTTP_LIMIT_INFINITE &&
|
|
pConfigGroup->LoggingConfig.LogFileTruncateSize < UL_MIN_ALLOWED_TRUNCATESIZE
|
|
)
|
|
{
|
|
UlTrace(LOGGING,
|
|
("Ul!UlCreateLog: Truncate size too small pConfigGroup %p : %d\n",
|
|
pConfigGroup,
|
|
pConfigGroup->LoggingConfig.LogFileTruncateSize
|
|
));
|
|
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
//
|
|
// We have two criteria for the log file name: its LogFormat and its LogPeriod
|
|
//
|
|
|
|
if ( pConfigGroup->LoggingConfig.LogFormat >= HttpLoggingTypeMaximum ||
|
|
pConfigGroup->LoggingConfig.LogPeriod >= HttpLoggingPeriodMaximum )
|
|
{
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
//
|
|
// Setup locals so we know how to cleanup on exit.
|
|
//
|
|
|
|
Status = STATUS_SUCCESS;
|
|
pNewEntry = NULL;
|
|
|
|
//
|
|
// This value is computed for the GMT time zone.
|
|
//
|
|
|
|
KeQuerySystemTime( &CurrentTimeStamp );
|
|
RtlTimeToTimeFields( &CurrentTimeStamp, &CurrentTimeFields );
|
|
|
|
//
|
|
// Allocate a temp buffer to hold the full path name including the
|
|
// device prefix and the filename at the end.
|
|
//
|
|
|
|
Status = UlpBuildLogDirectory(
|
|
&pConfigGroup->LoggingConfig.LogFileDir,
|
|
&DirectoryName
|
|
);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
return Status;
|
|
}
|
|
|
|
//
|
|
// We have to acquire the LogListresource exclusively, prior to
|
|
// the operations Create/Remove/ReConfig. Whenever we acquire the
|
|
// LogListResource exclusively we don't need to have the entry mutex
|
|
// acquired.
|
|
//
|
|
|
|
UlAcquireResourceExclusive(&g_pUlNonpagedData->LogListResource, TRUE);
|
|
|
|
pConfigGroup->pLogFileEntry = NULL;
|
|
|
|
Status = UlpConstructLogFileEntry (
|
|
&pConfigGroup->LoggingConfig,
|
|
&pNewEntry,
|
|
&DirectoryName,
|
|
&CurrentTimeFields
|
|
);
|
|
|
|
if (NT_SUCCESS(Status) == FALSE)
|
|
goto end;
|
|
|
|
//
|
|
// Create/Open the directory(ies) first.
|
|
//
|
|
|
|
Status = UlpCreateSafeDirectory( &DirectoryName );
|
|
|
|
if (NT_SUCCESS(Status) == FALSE)
|
|
goto end;
|
|
|
|
//
|
|
// If the logformat is max_size with truncation we need to scan the
|
|
// directory and find the correct last log file to append. Otherwise
|
|
// just picking the FILE_OPEN_IF when opening the log file will ensure
|
|
// the functionality.
|
|
//
|
|
|
|
if (pNewEntry->Period == HttpLoggingPeriodMaxSize)
|
|
{
|
|
//
|
|
// This call will update the filename and the file size
|
|
//
|
|
|
|
Status = UlpQueryDirectory(pNewEntry);
|
|
if (!NT_SUCCESS(Status))
|
|
goto end;
|
|
}
|
|
|
|
//
|
|
// Create/Open the file.
|
|
//
|
|
|
|
InitializeObjectAttributes(
|
|
&ObjectAttributes,
|
|
&pNewEntry->FileName, // Full path name
|
|
OBJ_CASE_INSENSITIVE | // Attributes
|
|
UL_KERNEL_HANDLE,
|
|
NULL, // RootDirectory
|
|
NULL // SecurityDescriptor
|
|
);
|
|
|
|
//
|
|
// Make the created file Aysnc by not picking the sync flag.
|
|
//
|
|
|
|
Status = ZwCreateFile(
|
|
&pNewEntry->hFile,
|
|
FILE_GENERIC_WRITE,
|
|
&ObjectAttributes,
|
|
&IoStatusBlock,
|
|
NULL,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
FILE_SHARE_READ,
|
|
FILE_OPEN_IF,
|
|
FILE_NON_DIRECTORY_FILE, // |FILE_SYNCHRONOUS_IO_NONALERT,
|
|
NULL,
|
|
0);
|
|
|
|
if (NT_SUCCESS(Status) == FALSE)
|
|
goto end;
|
|
|
|
//
|
|
// Get the file size, etc from the file.
|
|
//
|
|
|
|
Status = ZwQueryInformationFile(
|
|
pNewEntry->hFile,
|
|
&IoStatusBlock,
|
|
&pNewEntry->FileInfo,
|
|
sizeof(pNewEntry->FileInfo),
|
|
FileStandardInformation
|
|
);
|
|
|
|
if (NT_SUCCESS(Status) == FALSE)
|
|
goto end;
|
|
|
|
//
|
|
// Add it to our global log list.
|
|
//
|
|
|
|
UlpInsertLogFileEntry( pNewEntry, &CurrentTimeFields );
|
|
|
|
//
|
|
// Success!
|
|
//
|
|
|
|
pConfigGroup->pLogFileEntry = pNewEntry;
|
|
|
|
UlTrace( LOGGING,
|
|
( "Ul!UlCreateLog: entry %p, file %S, handle %lx created\n",
|
|
pNewEntry,
|
|
pNewEntry->FileName.Buffer,
|
|
pNewEntry->hFile )
|
|
);
|
|
|
|
end:
|
|
if ( !NT_SUCCESS(Status) )
|
|
{
|
|
//
|
|
// If we made it to this point, then the create/open has failed.
|
|
//
|
|
|
|
UlTrace( LOGGING,
|
|
("Ul!UlCreateLog: dirname %S, file %S failure %08lx\n",
|
|
pConfigGroup->LoggingConfig.LogFileDir.Buffer,
|
|
pNewEntry->FileName.Buffer,
|
|
Status
|
|
));
|
|
|
|
if (pNewEntry)
|
|
{
|
|
NTSTATUS TempStatus;
|
|
|
|
//
|
|
// Now release the entry's resources.
|
|
//
|
|
|
|
if (pNewEntry->hFile != NULL)
|
|
{
|
|
ZwClose( pNewEntry->hFile );
|
|
pNewEntry->hFile = NULL;
|
|
}
|
|
|
|
if ( pNewEntry->LogBuffer )
|
|
{
|
|
UlPplFreeLogBuffer( pNewEntry->LogBuffer );
|
|
}
|
|
|
|
// Delete the entry eresource
|
|
TempStatus = UlDeleteResource( &pNewEntry->EntryResource );
|
|
ASSERT(NT_SUCCESS(TempStatus));
|
|
|
|
UL_FREE_POOL_WITH_SIG(pNewEntry,UL_LOG_FILE_ENTRY_POOL_TAG);
|
|
}
|
|
|
|
pConfigGroup->pLogFileEntry = NULL;
|
|
|
|
}
|
|
else
|
|
{
|
|
ASSERT(pConfigGroup->pLogFileEntry != NULL);
|
|
}
|
|
|
|
// Cleanup temp dir name buffer
|
|
if (DirectoryName.Buffer)
|
|
{
|
|
UL_FREE_POOL(DirectoryName.Buffer, UL_CG_LOGDIR_POOL_TAG);
|
|
}
|
|
|
|
UlReleaseResource(&g_pUlNonpagedData->LogListResource);
|
|
|
|
return Status;
|
|
}
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
UlpInsertLogFileEntry :
|
|
|
|
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
|
|
UlpInsertLogFileEntry(
|
|
PUL_LOG_FILE_ENTRY pEntry,
|
|
PTIME_FIELDS pTimeFields
|
|
)
|
|
{
|
|
LONG listSize;
|
|
HTTP_LOGGING_PERIOD period;
|
|
KIRQL oldIrql;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT(IS_VALID_LOG_FILE_ENTRY(pEntry));
|
|
ASSERT(pTimeFields);
|
|
|
|
//
|
|
// 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
|
|
// terminaton of the driver. CODEWORK we may start and stop it
|
|
// more intelligently, i.e. if no log requires it stop it
|
|
//
|
|
|
|
UlAcquireSpinLock( &g_LogTimerSpinLock, &oldIrql );
|
|
|
|
if ( g_LogTimerStarted == FALSE )
|
|
{
|
|
// Only if we are running on time dependent
|
|
// format
|
|
|
|
if ( period != HttpLoggingPeriodMaxSize )
|
|
{
|
|
UlpSetLogTimer( pTimeFields );
|
|
g_LogTimerStarted = TRUE;
|
|
}
|
|
}
|
|
|
|
// Go ahead start the buffer timer
|
|
// as soon as we have a log file
|
|
|
|
if ( g_BufferTimerStarted == FALSE )
|
|
{
|
|
UlpSetBufferTimer();
|
|
g_BufferTimerStarted = TRUE;
|
|
}
|
|
|
|
UlReleaseSpinLock( &g_LogTimerSpinLock, oldIrql );
|
|
}
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Simple utility to close the log file handle on a system thread and set the
|
|
event to notify the caller that it's done.
|
|
|
|
Arguments:
|
|
|
|
pEntry - Acquired from passed-in pWorkItem
|
|
|
|
--***************************************************************************/
|
|
|
|
VOID
|
|
UlpLogCloseHandleWorker(
|
|
IN PUL_WORK_ITEM pWorkItem
|
|
)
|
|
{
|
|
PUL_LOG_FILE_ENTRY pEntry;
|
|
|
|
PAGED_CODE();
|
|
|
|
// For this function to not to cause any threats to the safety of the log
|
|
// entry. The entry should already been acquired exclusively by our caller.
|
|
|
|
ASSERT(!UlDbgResourceUnownedExclusive(
|
|
&g_pUlNonpagedData->LogListResource));
|
|
|
|
// Get the log entry
|
|
|
|
pEntry = CONTAINING_RECORD(
|
|
pWorkItem,
|
|
UL_LOG_FILE_ENTRY,
|
|
WorkItem
|
|
);
|
|
ASSERT(IS_VALID_LOG_FILE_ENTRY(pEntry));
|
|
ASSERT(pEntry->hFile);
|
|
|
|
UlTrace(LOGGING,("Ul!UlpLogCloseHandleWorker: pEntry %p hFile %p\n",
|
|
pEntry, pEntry->hFile ));
|
|
|
|
// Close the handle and set the event for the original caller
|
|
|
|
ZwClose(pEntry->hFile);
|
|
|
|
pEntry->hFile = NULL;
|
|
|
|
KeSetEvent(&pEntry->CloseEvent, 0, FALSE);
|
|
|
|
}
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Simple utility to close the log file handle on a system thread and set the
|
|
event to notify the caller that it's done.
|
|
|
|
Arguments:
|
|
|
|
pEntry - Acquired from passed-in pWorkItem
|
|
|
|
--***************************************************************************/
|
|
|
|
VOID
|
|
UlpLogCloseHandle(
|
|
IN PUL_LOG_FILE_ENTRY pEntry
|
|
)
|
|
{
|
|
// Sanity check
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT(IS_VALID_LOG_FILE_ENTRY(pEntry));
|
|
//ASSERT(UlDbgResourceOwnedExclusive(&pEntry->EntryResource));
|
|
ASSERT(g_pUlSystemProcess);
|
|
|
|
// Close the handle on the system thread and wait until it's done
|
|
|
|
if (g_pUlSystemProcess == (PKPROCESS)PsGetCurrentProcess())
|
|
{
|
|
ZwClose(pEntry->hFile);
|
|
pEntry->hFile = NULL;
|
|
}
|
|
else
|
|
{
|
|
KeAttachProcess(g_pUlSystemProcess);
|
|
|
|
ZwClose(pEntry->hFile);
|
|
pEntry->hFile = NULL;
|
|
|
|
// Following will bugcheck in the checked kernel if there's
|
|
// any user or kernel APCs attached to the thread.
|
|
|
|
KeDetachProcess();
|
|
|
|
// TODO: Find a better solution
|
|
|
|
/*
|
|
UL_QUEUE_WORK_ITEM(
|
|
&pEntry->WorkItem,
|
|
&UlpLogCloseHandleWorker
|
|
);
|
|
KeWaitForSingleObject(
|
|
(PVOID)&pEntry->CloseEvent,
|
|
UserRequest,
|
|
KernelMode,
|
|
FALSE,
|
|
NULL
|
|
);
|
|
KeClearEvent(&pEntry->CloseEvent);
|
|
*/
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
UlRemoveLogFileEntry :
|
|
|
|
Removes a log file entry from our global log entry list.
|
|
|
|
Arguments:
|
|
|
|
pEntry - The log file entry to be removed from the global list
|
|
|
|
--***************************************************************************/
|
|
|
|
VOID
|
|
UlRemoveLogFileEntry(
|
|
PUL_LOG_FILE_ENTRY pEntry
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
LONG listSize;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT(IS_VALID_LOG_FILE_ENTRY(pEntry));
|
|
|
|
UlAcquireResourceExclusive(&g_pUlNonpagedData->LogListResource, TRUE);
|
|
|
|
RemoveEntryList(&pEntry->LogFileListEntry);
|
|
|
|
pEntry->LogFileListEntry.Flink =
|
|
pEntry->LogFileListEntry.Blink = NULL;
|
|
|
|
if (pEntry->hFile != NULL)
|
|
{
|
|
//
|
|
// Make sure that buffer get flushed out before closing the file
|
|
// handle. But flush will cause an APC to be queued to the user
|
|
// thread, therefore we have to close the handle on one of our
|
|
// system threads to avoid the possible bugcheck
|
|
// INVALID_PROCESS_DETACH_ATTEMPT condition.
|
|
//
|
|
|
|
UlpFlushLogFile(pEntry);
|
|
|
|
UlpLogCloseHandle(pEntry);
|
|
}
|
|
|
|
//
|
|
// Delete the entry eresource
|
|
//
|
|
|
|
Status = UlDeleteResource( &pEntry->EntryResource );
|
|
ASSERT(NT_SUCCESS(Status));
|
|
|
|
listSize = InterlockedDecrement( &g_LogListEntryCount );
|
|
|
|
ASSERT(listSize >= 0);
|
|
|
|
UlTrace( LOGGING,
|
|
("Ul!UlRemoveLogFileEntry: entry %p has been removed\n",
|
|
pEntry
|
|
));
|
|
|
|
if ( pEntry->LogBuffer )
|
|
{
|
|
UlPplFreeLogBuffer( pEntry->LogBuffer );
|
|
}
|
|
|
|
UL_FREE_POOL_WITH_SIG(pEntry,UL_LOG_FILE_ENTRY_POOL_TAG);
|
|
|
|
UlReleaseResource(&g_pUlNonpagedData->LogListResource);
|
|
}
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
UlpCreateSafeDirectory :
|
|
|
|
Creates all of the necessary directories in a given UNICODE directory
|
|
pathname.
|
|
|
|
E.g. For given \??\C:\temp\w3svc1
|
|
|
|
-> Directories "C:\temp" & "C:\temp\w3svc1" will be created.
|
|
|
|
This function assumes that directory string starts with "\\??\\"
|
|
|
|
Arguments:
|
|
|
|
pDirectoryName - directroy path name string, WARNING this function makes
|
|
some inplace modification to the passed directory string
|
|
but it restores the original before returning.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Completion status.
|
|
|
|
--***************************************************************************/
|
|
|
|
NTSTATUS
|
|
UlpCreateSafeDirectory(
|
|
IN PUNICODE_STRING pDirectoryName
|
|
)
|
|
{
|
|
OBJECT_ATTRIBUTES ObjectAttributes;
|
|
IO_STATUS_BLOCK IoStatusBlock;
|
|
NTSTATUS Status;
|
|
HANDLE hDirectory;
|
|
PWCHAR pw;
|
|
USHORT i;
|
|
|
|
//
|
|
// Sanity check
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
ASSERT( pDirectoryName );
|
|
ASSERT( pDirectoryName->Buffer );
|
|
ASSERT( pDirectoryName->Length );
|
|
ASSERT( pDirectoryName->MaximumLength > pDirectoryName->Length );
|
|
|
|
pw = pDirectoryName->Buffer;
|
|
pw[pDirectoryName->Length/sizeof(WCHAR)]=UNICODE_NULL;
|
|
|
|
// TODO: Handle network mapped drives. Redirector.
|
|
|
|
if (0 == wcsncmp(pw, UL_UNC_PATH_PREFIX, UL_UNC_PATH_PREFIX_LENGTH))
|
|
{
|
|
// UNC share
|
|
pw += UL_UNC_PATH_PREFIX_LENGTH;
|
|
|
|
// Bypass "\\machine\share"
|
|
|
|
i = 0; // Skip two backslashes before reaching to share name
|
|
|
|
while( *pw != UNICODE_NULL )
|
|
{
|
|
if ( *pw == L'\\' ) i++;
|
|
if ( i == 2 ) break;
|
|
pw++;
|
|
}
|
|
}
|
|
else if (0 == wcsncmp(pw, UL_LOCAL_PATH_PREFIX, UL_LOCAL_PATH_PREFIX_LENGTH))
|
|
{
|
|
// Local Drive
|
|
pw += UL_LOCAL_PATH_PREFIX_LENGTH;
|
|
|
|
// Bypass "C:"
|
|
|
|
while( *pw != L'\\' && *pw != UNICODE_NULL )
|
|
{
|
|
pw++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ASSERT(!"Incorrect logging directory name or type !");
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
if ( *pw == UNICODE_NULL )
|
|
{
|
|
// Dir. Name cannot be only "\??\C:" or "\dosdevices\UNC\machine
|
|
// It should at least be pointing to the root directory.
|
|
|
|
ASSERT(!"Incomplete logging directory name !");
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
//
|
|
// \??\C:\temp\w3svc1 OR \\dosdevices\UNC\machine\share\w3svc1
|
|
// ^ ^
|
|
// pw now points to | OR |
|
|
//
|
|
//
|
|
|
|
ASSERT( *pw == L'\\' );
|
|
|
|
do
|
|
{
|
|
pw++;
|
|
|
|
if ( *pw == L'\\' || *pw == UNICODE_NULL )
|
|
{
|
|
//
|
|
// Remember the original character
|
|
//
|
|
|
|
WCHAR wcOriginal = *pw;
|
|
UNICODE_STRING DirectoryName;
|
|
|
|
//
|
|
// Time to create the directory so far
|
|
//
|
|
|
|
*pw = UNICODE_NULL;
|
|
|
|
RtlInitUnicodeString( &DirectoryName, pDirectoryName->Buffer );
|
|
|
|
InitializeObjectAttributes(
|
|
&ObjectAttributes,
|
|
&DirectoryName,
|
|
OBJ_CASE_INSENSITIVE |
|
|
UL_KERNEL_HANDLE,
|
|
NULL,
|
|
NULL
|
|
);
|
|
|
|
Status = ZwCreateFile(
|
|
&hDirectory,
|
|
FILE_LIST_DIRECTORY|FILE_TRAVERSE,
|
|
&ObjectAttributes,
|
|
&IoStatusBlock,
|
|
NULL,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
FILE_SHARE_READ,
|
|
FILE_OPEN_IF,
|
|
FILE_DIRECTORY_FILE,
|
|
NULL,
|
|
0
|
|
);
|
|
|
|
//
|
|
// Restore the original character
|
|
//
|
|
|
|
*pw = wcOriginal;
|
|
|
|
if (NT_SUCCESS(Status) == FALSE)
|
|
{
|
|
goto end;
|
|
}
|
|
|
|
Status = ZwClose(hDirectory);
|
|
|
|
if (NT_SUCCESS(Status) == FALSE)
|
|
{
|
|
goto end;
|
|
}
|
|
}
|
|
}
|
|
while( *pw != UNICODE_NULL );
|
|
|
|
end:
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
UlTrace(LOGGING,
|
|
("Ul!UlpCreateSafeDirectory: directory %S, failure %08lx\n",
|
|
pDirectoryName->Buffer,
|
|
Status
|
|
));
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
UlpCalculateTimeToExpire :
|
|
|
|
Shamelessly stolen from IIS 5.1 Logging code and adapted here.
|
|
This routine returns the time-to-expire in hours. 1 means the log
|
|
will expire in the next timer-fire and so ...
|
|
|
|
Arguments:
|
|
|
|
PTIME_FIELDS - Current Time Fields
|
|
HTTP_LOGGING_PERIOD - Logging Period
|
|
PULONG - Pointer to a buffer to receive result
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Completion status.
|
|
|
|
--***************************************************************************/
|
|
|
|
NTSTATUS
|
|
UlpCalculateTimeToExpire(
|
|
PTIME_FIELDS pDueTime,
|
|
HTTP_LOGGING_PERIOD LogPeriod,
|
|
PULONG pTimeRemaining
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
ULONG NumDays;
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT(pDueTime!=NULL);
|
|
ASSERT(pTimeRemaining!=NULL);
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
switch (LogPeriod)
|
|
{
|
|
case HttpLoggingPeriodMaxSize:
|
|
return Status;
|
|
|
|
case HttpLoggingPeriodHourly:
|
|
*pTimeRemaining = 1;
|
|
break;
|
|
|
|
case HttpLoggingPeriodDaily:
|
|
*pTimeRemaining = 24 - pDueTime->Hour;
|
|
break;
|
|
|
|
case HttpLoggingPeriodWeekly:
|
|
{
|
|
ULONG TimeRemainingInTheMonth;
|
|
NumDays = UlpGetMonthDays(pDueTime);
|
|
|
|
TimeRemainingInTheMonth =
|
|
NumDays*24 - ((pDueTime->Day-1)*24 + pDueTime->Hour);
|
|
|
|
// Time Remaining in the week
|
|
// Sunday = 0, Monday = 1 ... Saturday = 6
|
|
|
|
*pTimeRemaining =
|
|
7*24 - (pDueTime->Weekday*24 + pDueTime->Hour);
|
|
|
|
//
|
|
// If the time remaining in the month less than time remaining in
|
|
// the week then we have to recycle at the end of the month.
|
|
// Otherwise we have to recycle at the end of the week. (next sunday)
|
|
//
|
|
|
|
if (TimeRemainingInTheMonth < *pTimeRemaining)
|
|
{
|
|
*pTimeRemaining = TimeRemainingInTheMonth;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case HttpLoggingPeriodMonthly:
|
|
{
|
|
NumDays = UlpGetMonthDays(pDueTime);
|
|
|
|
//
|
|
// Lets not forget that the day starts from 1 .. 31
|
|
// Therefore we have to subtract one from the day value.
|
|
//
|
|
|
|
*pTimeRemaining =
|
|
NumDays*24 - ((pDueTime->Day-1)*24 + pDueTime->Hour);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
ASSERT(FALSE);
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Initializes the Log & buffering Timers
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UlpInitializeTimers(
|
|
VOID
|
|
)
|
|
{
|
|
/* Log timer */
|
|
|
|
g_LogTimerInitialized = TRUE;
|
|
|
|
UlInitializeSpinLock( &g_LogTimerSpinLock, "g_LogTimerSpinLock" );
|
|
|
|
KeInitializeDpc(
|
|
&g_LogTimerDpcObject, // DPC object
|
|
&UlLogTimerDpcRoutine, // DPC routine
|
|
NULL // context
|
|
);
|
|
|
|
KeInitializeTimer( &g_LogTimer );
|
|
|
|
/* Buffer timer */
|
|
|
|
g_BufferTimerInitialized = TRUE;
|
|
|
|
UlInitializeSpinLock( &g_BufferTimerSpinLock, "g_BufferTimerSpinLock" );
|
|
|
|
KeInitializeDpc(
|
|
&g_BufferTimerDpcObject, // DPC object
|
|
&UlBufferTimerDpcRoutine, // DPC routine
|
|
NULL // context
|
|
);
|
|
|
|
KeInitializeTimer( &g_BufferTimer );
|
|
}
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Terminates the Log & buffering Timers
|
|
|
|
--***************************************************************************/
|
|
|
|
VOID
|
|
UlpTerminateTimers(
|
|
VOID
|
|
)
|
|
{
|
|
KIRQL oldIrql;
|
|
|
|
/* Log timer */
|
|
|
|
if ( g_LogTimerInitialized )
|
|
{
|
|
UlAcquireSpinLock( &g_LogTimerSpinLock, &oldIrql );
|
|
|
|
g_LogTimerInitialized = FALSE;
|
|
|
|
UlReleaseSpinLock( &g_LogTimerSpinLock, oldIrql );
|
|
|
|
KeCancelTimer( &g_LogTimer );
|
|
}
|
|
|
|
/* Buffer timer */
|
|
|
|
if ( g_BufferTimerInitialized )
|
|
{
|
|
UlAcquireSpinLock( &g_BufferTimerSpinLock, &oldIrql );
|
|
|
|
g_BufferTimerInitialized = FALSE;
|
|
|
|
UlReleaseSpinLock( &g_BufferTimerSpinLock, oldIrql );
|
|
|
|
KeCancelTimer( &g_BufferTimer );
|
|
}
|
|
}
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
UlpSetTimer :
|
|
|
|
This routine provides the initial due time for the upcoming
|
|
periodic hourly timer. We have to align the timer so that it
|
|
get signaled at the beginning of each hour. Then it goes with
|
|
an hour period until stops.
|
|
|
|
We keep ONLY one timer for all log periods. A DPC routine will
|
|
get called every hour, and it will traverse the log list and
|
|
do the cycling properly.
|
|
|
|
Arguments:
|
|
|
|
PTIME_FIELDS - Current Time
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Completion status.
|
|
|
|
--***************************************************************************/
|
|
|
|
VOID
|
|
UlpSetLogTimer(
|
|
PTIME_FIELDS pFields
|
|
)
|
|
{
|
|
LONGLONG InitialDueTime100Ns;
|
|
LARGE_INTEGER InitialDueTime;
|
|
|
|
ASSERT(pFields!=NULL);
|
|
|
|
//
|
|
// Remaining time to next hour tick. In seconds
|
|
//
|
|
|
|
InitialDueTime100Ns =
|
|
1*60*60 - ( pFields->Minute*60 + pFields->Second );
|
|
|
|
//
|
|
// Also convert it to 100-ns
|
|
//
|
|
|
|
InitialDueTime100Ns =
|
|
(InitialDueTime100Ns*1000 - pFields->Milliseconds ) * 1000 * 10;
|
|
|
|
//
|
|
// Negative time for relative value.
|
|
//
|
|
|
|
InitialDueTime.QuadPart = -InitialDueTime100Ns;
|
|
|
|
KeSetTimerEx(
|
|
&g_LogTimer,
|
|
InitialDueTime,
|
|
1*60*60*1000, // Period of 1 Hours in millisec
|
|
&g_LogTimerDpcObject
|
|
);
|
|
}
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
UlpSetBufferTimer :
|
|
|
|
We have to introduce a new timer for the log buffering mechanism.
|
|
Each log file keeps a system default (64K) buffer do not flush this
|
|
out unless it's full or this timer get fired every MINUTE.
|
|
|
|
The hourly timer get aligned for the beginning of each hour. Therefore
|
|
using that existing timer would introduce alot of complexity.
|
|
|
|
Arguments:
|
|
|
|
PTIME_FIELDS - Current Time
|
|
|
|
--***************************************************************************/
|
|
|
|
VOID
|
|
UlpSetBufferTimer(
|
|
VOID
|
|
)
|
|
{
|
|
LONGLONG BufferPeriodTime100Ns;
|
|
LONG BufferPeriodTimeMs;
|
|
LARGE_INTEGER BufferPeriodTime;
|
|
|
|
//
|
|
// Remaining time to next tick. DEFAULT_BUFFER_TIMER_PERIOD is in minutes
|
|
//
|
|
|
|
BufferPeriodTimeMs = DEFAULT_BUFFER_TIMER_PERIOD * 60 * 1000;
|
|
BufferPeriodTime100Ns = (LONGLONG) BufferPeriodTimeMs * 10 * 1000;
|
|
|
|
UlTrace(LOGGING,
|
|
("Ul!UlpSetBufferTimer: %d seconds\n",
|
|
BufferPeriodTimeMs / 1000
|
|
));
|
|
|
|
//
|
|
// Negative time for relative value.
|
|
//
|
|
|
|
BufferPeriodTime.QuadPart = -BufferPeriodTime100Ns;
|
|
|
|
KeSetTimerEx(
|
|
&g_BufferTimer,
|
|
BufferPeriodTime, // Must be in nanosec
|
|
BufferPeriodTimeMs, // Must be in millisec
|
|
&g_BufferTimerDpcObject
|
|
);
|
|
}
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
UlLogTimerHandler :
|
|
|
|
Work item for the threadpool that goes thru the log list and
|
|
cycle the necessary logs.
|
|
|
|
Arguments:
|
|
|
|
PUL_WORK_ITEM - Ignored
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--***************************************************************************/
|
|
|
|
VOID
|
|
UlLogTimerHandler(
|
|
IN PUL_WORK_ITEM pWorkItem
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
PLIST_ENTRY pLink;
|
|
PUL_LOG_FILE_ENTRY pEntry;
|
|
|
|
PAGED_CODE();
|
|
|
|
UlTrace(LOGGING,("Ul!UlLogTimerHandler: Scanning the log entries\n"));
|
|
|
|
UlAcquireResourceExclusive(&g_pUlNonpagedData->LogListResource, TRUE);
|
|
|
|
// 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.
|
|
//
|
|
|
|
UlAcquireResourceExclusive(&pEntry->EntryResource, TRUE);
|
|
|
|
if (pEntry->Period != HttpLoggingPeriodMaxSize)
|
|
{
|
|
if (pEntry->TimeToExpire==1)
|
|
{
|
|
// TODO: Don't recycle if the entries logging is disabled.
|
|
|
|
SET_TIME_TO_EXPIRE_STALE(pEntry);
|
|
|
|
Status = UlpRecycleLogFile(pEntry);
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Just decrement the hourly counter
|
|
// for this time.
|
|
//
|
|
|
|
pEntry->TimeToExpire -= 1;
|
|
}
|
|
}
|
|
|
|
UlReleaseResource(&pEntry->EntryResource);
|
|
}
|
|
|
|
UlReleaseResource(&g_pUlNonpagedData->LogListResource);
|
|
|
|
//
|
|
// Free the memory allocated (ByDpcRoutine below) to
|
|
// this work item.
|
|
//
|
|
|
|
UL_FREE_POOL( pWorkItem, UL_WORK_ITEM_POOL_TAG );
|
|
|
|
}
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
UlLogTimerDpcRoutine :
|
|
|
|
Arguments:
|
|
|
|
Ignored
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Completion status.
|
|
|
|
--***************************************************************************/
|
|
|
|
VOID
|
|
UlLogTimerDpcRoutine(
|
|
PKDPC Dpc,
|
|
PVOID DeferredContext,
|
|
PVOID SystemArgument1,
|
|
PVOID SystemArgument2
|
|
)
|
|
{
|
|
PUL_WORK_ITEM pWorkItem;
|
|
|
|
UlAcquireSpinLockAtDpcLevel(
|
|
&g_LogTimerSpinLock
|
|
);
|
|
|
|
if( g_LogTimerInitialized )
|
|
{
|
|
//
|
|
// 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 )
|
|
{
|
|
UL_QUEUE_WORK_ITEM(
|
|
pWorkItem,
|
|
&UlLogTimerHandler
|
|
);
|
|
}
|
|
else
|
|
{
|
|
UlTrace(LOGGING,("Ul!UlLogTimerDpcRoutine: Not enough memory.\n"));
|
|
}
|
|
}
|
|
|
|
UlReleaseSpinLockFromDpcLevel(
|
|
&g_LogTimerSpinLock
|
|
);
|
|
}
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
UlBufferTimerDpcRoutine :
|
|
|
|
Arguments:
|
|
|
|
Ignored
|
|
|
|
--***************************************************************************/
|
|
|
|
VOID
|
|
UlBufferTimerDpcRoutine(
|
|
PKDPC Dpc,
|
|
PVOID DeferredContext,
|
|
PVOID SystemArgument1,
|
|
PVOID SystemArgument2
|
|
)
|
|
{
|
|
PUL_WORK_ITEM pWorkItem;
|
|
|
|
UlAcquireSpinLockAtDpcLevel( &g_BufferTimerSpinLock );
|
|
|
|
if( g_BufferTimerInitialized )
|
|
{
|
|
//
|
|
// 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 )
|
|
{
|
|
UL_QUEUE_WORK_ITEM(
|
|
pWorkItem,
|
|
&UlBufferTimerHandler
|
|
);
|
|
}
|
|
else
|
|
{
|
|
UlTrace(LOGGING,("Ul!UlBufferTimerDpcRoutine: Not enough memory.\n"));
|
|
}
|
|
}
|
|
|
|
UlReleaseSpinLockFromDpcLevel( &g_BufferTimerSpinLock );
|
|
}
|
|
|
|
/***************************************************************************++
|
|
|
|
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,("Ul!UlBufferTimerHandler: scanning the log entries ...\n"));
|
|
|
|
UlAcquireResourceShared(&g_pUlNonpagedData->LogListResource, TRUE);
|
|
|
|
for (pLink = g_LogListHead.Flink;
|
|
pLink != &g_LogListHead;
|
|
pLink = pLink->Flink
|
|
)
|
|
{
|
|
pEntry = CONTAINING_RECORD(
|
|
pLink,
|
|
UL_LOG_FILE_ENTRY,
|
|
LogFileListEntry
|
|
);
|
|
|
|
UlAcquireResourceExclusive(&pEntry->EntryResource, TRUE);
|
|
|
|
Status = UlpFlushLogFile( pEntry );
|
|
|
|
// TODO: Handle STATUS_DISK_FULL
|
|
|
|
UlReleaseResource(&pEntry->EntryResource);
|
|
}
|
|
|
|
UlReleaseResource(&g_pUlNonpagedData->LogListResource);
|
|
|
|
//
|
|
// Free the memory allocated (ByDpcRoutine below) to
|
|
// this work item.
|
|
//
|
|
|
|
UL_FREE_POOL( pWorkItem, UL_WORK_ITEM_POOL_TAG );
|
|
|
|
}
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
UlpGrowLogEntry:
|
|
|
|
All it does is to grow the log file entry in the log list. So that
|
|
recycle function later on can successfully updates the new log file
|
|
name. This function get called only when Dir Name is changed. Because
|
|
only that case causes us to enlarge the already allocated name buffer.
|
|
Name changes originated from timer based or size based recycling
|
|
does not require us to enlarge as we initially allocates more than enough
|
|
buffer for those changes.
|
|
|
|
You should HAVE the log list resource exclusively before calling
|
|
this function.
|
|
|
|
Arguments:
|
|
|
|
too many
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Completion status.
|
|
|
|
--***************************************************************************/
|
|
|
|
NTSTATUS
|
|
UlpGrowLogEntry(
|
|
IN PUL_CONFIG_GROUP_OBJECT pConfigGroup,
|
|
IN PUL_LOG_FILE_ENTRY pOldEntry
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
USHORT FullPathFileNameLength;
|
|
UNICODE_STRING JunkFileName;
|
|
PUL_LOG_FILE_ENTRY pEntry;
|
|
UNICODE_STRING DosDevice;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT(pConfigGroup != NULL);
|
|
ASSERT(pOldEntry != NULL);
|
|
|
|
Status = STATUS_SUCCESS;
|
|
pEntry = NULL;
|
|
|
|
// TODO: Get rid of this function by making sure that log entry never grows.
|
|
// TODO: It means Allocating the Log Dir Name separately not inline.
|
|
|
|
UlTrace( LOGGING, ("Ul!UlpGrowLogEntry: old_entry %p\n", pOldEntry ));
|
|
|
|
RtlInitUnicodeString( &JunkFileName, L"\\none.log" );
|
|
|
|
|
|
FullPathFileNameLength= UL_MAX_PATH_PREFIX_LENGTH +
|
|
pConfigGroup->LoggingConfig.LogFileDir.Length +
|
|
(UL_MAX_FILE_NAME_SUFFIX_LENGTH+1) * sizeof(WCHAR);
|
|
|
|
pEntry = UL_ALLOCATE_STRUCT_WITH_SPACE(
|
|
NonPagedPool,
|
|
UL_LOG_FILE_ENTRY,
|
|
FullPathFileNameLength,
|
|
UL_LOG_FILE_ENTRY_POOL_TAG
|
|
);
|
|
|
|
if ( pEntry == NULL )
|
|
{
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto end;
|
|
}
|
|
|
|
RtlZeroMemory( pEntry, sizeof(*pEntry) );
|
|
pEntry->Signature = UL_LOG_FILE_ENTRY_POOL_TAG;
|
|
|
|
*pEntry = *pOldEntry;
|
|
|
|
//
|
|
// Construct a default dir_name. Don't worry about the
|
|
// L"none.log", it will be overwritten by the recycler
|
|
// later on, as long as there's MAX_LOG_FILE_NAME_SIZE
|
|
// space for the time/type dependent part of the file
|
|
// name ( aka short file name ), it's all right.
|
|
//
|
|
|
|
pEntry->FileName.Length = 0;
|
|
pEntry->FileName.MaximumLength = FullPathFileNameLength;
|
|
pEntry->FileName.Buffer = (PWSTR) ( pEntry + 1 );
|
|
|
|
if (pConfigGroup->LoggingConfig.LogFileDir.Buffer[0] == L'\\')
|
|
{
|
|
if (pConfigGroup->LoggingConfig.LogFileDir.Buffer[1] == L'\\')
|
|
{
|
|
// UNC share: "\\alitudev\temp"
|
|
RtlInitUnicodeString( &DosDevice, UL_UNC_PATH_PREFIX );
|
|
}
|
|
else
|
|
{
|
|
// Local Directory name is missing the device i.e "\temp"
|
|
// It should be fully qualified name.
|
|
Status = STATUS_NOT_SUPPORTED;
|
|
goto end;
|
|
}
|
|
|
|
// Skip second backslash for the UNC path
|
|
RtlCopyUnicodeString( &pEntry->FileName, &DosDevice );
|
|
RtlCopyMemory(
|
|
&(pEntry->FileName.Buffer[pEntry->FileName.Length/sizeof(WCHAR)]),
|
|
&(pConfigGroup->LoggingConfig.LogFileDir.Buffer[1]),
|
|
pConfigGroup->LoggingConfig.LogFileDir.Length - sizeof(WCHAR)
|
|
);
|
|
pEntry->FileName.Length +=
|
|
(pConfigGroup->LoggingConfig.LogFileDir.Length - sizeof(WCHAR));
|
|
}
|
|
else
|
|
{
|
|
RtlInitUnicodeString( &DosDevice, UL_LOCAL_PATH_PREFIX );
|
|
RtlCopyUnicodeString( &pEntry->FileName, &DosDevice );
|
|
RtlAppendUnicodeStringToString(
|
|
&pEntry->FileName,
|
|
&(pConfigGroup->LoggingConfig.LogFileDir)
|
|
);
|
|
}
|
|
|
|
pEntry->pShortName =
|
|
(PWSTR) &pEntry->FileName.Buffer[pEntry->FileName.Length/sizeof(WCHAR)];
|
|
|
|
Status = RtlAppendUnicodeStringToString(
|
|
&pEntry->FileName,
|
|
&JunkFileName
|
|
);
|
|
if (NT_SUCCESS(Status) == FALSE)
|
|
goto end;
|
|
|
|
//
|
|
// Do the replacement here
|
|
//
|
|
|
|
RemoveEntryList(&pOldEntry->LogFileListEntry);
|
|
|
|
pOldEntry->LogFileListEntry.Flink =
|
|
pOldEntry->LogFileListEntry.Blink = NULL;
|
|
|
|
//
|
|
// Restore/Carry the buffer from the old entry
|
|
//
|
|
|
|
pEntry->Flags.LogTitleWritten = pOldEntry->Flags.LogTitleWritten;
|
|
|
|
pEntry->LogBuffer = pOldEntry->LogBuffer;
|
|
|
|
KeInitializeEvent(&pEntry->CloseEvent, NotificationEvent, FALSE);
|
|
|
|
// Create the new entries eresource
|
|
Status = UlInitializeResource(&pEntry->EntryResource,"EntryResource",0,
|
|
UL_LOG_FILE_ENTRY_POOL_TAG);
|
|
ASSERT(NT_SUCCESS(Status));
|
|
|
|
// Delete old entry eresource
|
|
Status = UlDeleteResource( &pOldEntry->EntryResource );
|
|
ASSERT(NT_SUCCESS(Status));
|
|
|
|
UL_FREE_POOL_WITH_SIG(pOldEntry,UL_LOG_FILE_ENTRY_POOL_TAG);
|
|
|
|
InsertHeadList(&g_LogListHead, &pEntry->LogFileListEntry);
|
|
|
|
pConfigGroup->pLogFileEntry = pEntry;
|
|
|
|
//
|
|
// Lets return happily
|
|
//
|
|
|
|
end:
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
UlTrace(LOGGING,
|
|
("Ul!UlpGrowLogEntry: old_entry %p, file %S, failure %08lx\n",
|
|
pOldEntry,
|
|
pOldEntry->FileName.Buffer,
|
|
Status
|
|
));
|
|
if (pEntry)
|
|
{
|
|
NTSTATUS TempStatus;
|
|
|
|
// Delete the entry eresource
|
|
TempStatus = UlDeleteResource( &pEntry->EntryResource );
|
|
ASSERT(NT_SUCCESS(TempStatus));
|
|
|
|
UL_FREE_POOL_WITH_SIG(pEntry,UL_LOG_FILE_ENTRY_POOL_TAG);
|
|
}
|
|
}
|
|
return Status;
|
|
}
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
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
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Completion status.
|
|
|
|
--***************************************************************************/
|
|
|
|
NTSTATUS
|
|
UlReConfigureLogEntry(
|
|
IN PUL_CONFIG_GROUP_OBJECT pConfigGroup,
|
|
IN PHTTP_CONFIG_GROUP_LOGGING pCfgOld,
|
|
IN PHTTP_CONFIG_GROUP_LOGGING pCfgNew
|
|
)
|
|
{
|
|
NTSTATUS Status ;
|
|
BOOLEAN HaveToReCycle;
|
|
|
|
//
|
|
// Sanity check first
|
|
//
|
|
|
|
PAGED_CODE();
|
|
Status = STATUS_SUCCESS;
|
|
HaveToReCycle = FALSE;
|
|
|
|
UlTrace( LOGGING, ("Ul!UlReConfigureLogEntry: entry %p\n",
|
|
pConfigGroup->pLogFileEntry ));
|
|
|
|
if ( pCfgOld->LoggingEnabled==FALSE && pCfgNew->LoggingEnabled==FALSE )
|
|
{
|
|
//
|
|
// Do Nothing. Not Even update the fields
|
|
// as soon as we get enable request,
|
|
// field update will take place anyway.
|
|
//
|
|
|
|
return Status;
|
|
}
|
|
|
|
if ( pCfgOld->LoggingEnabled==TRUE && pCfgNew->LoggingEnabled==FALSE )
|
|
{
|
|
//
|
|
// Stop logging but keep the entry in the
|
|
// list. CODEWORK do we have to keep this info
|
|
// in the entry itself and when we recycle the
|
|
// logs based on timer, we do not recycle the
|
|
// ones with logging disabled ??
|
|
//
|
|
|
|
pCfgOld->LoggingEnabled = FALSE;
|
|
return Status;
|
|
}
|
|
else
|
|
{
|
|
pCfgOld->LoggingEnabled = TRUE;
|
|
}
|
|
|
|
if ( pCfgNew->LogFormat >= HttpLoggingTypeMaximum ||
|
|
pCfgNew->LogPeriod >= HttpLoggingPeriodMaximum )
|
|
{
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
//
|
|
// An important check to ensure that no infinite loop occurs because of
|
|
// ridiculusly small truncatesizes. Means smaller than maximum
|
|
// allowed log record line (10*1024)
|
|
//
|
|
|
|
if ( pCfgNew->LogPeriod == HttpLoggingPeriodMaxSize &&
|
|
pCfgNew->LogFileTruncateSize != HTTP_LIMIT_INFINITE &&
|
|
pCfgNew->LogFileTruncateSize < UL_MIN_ALLOWED_TRUNCATESIZE
|
|
)
|
|
{
|
|
UlTrace( LOGGING,
|
|
("Ul!UlReConfigureLogEntry: Truncate size too small %d !\n",
|
|
pCfgNew->LogFileTruncateSize
|
|
));
|
|
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
//
|
|
// No matter what ReConfiguration should acquire the LogListResource
|
|
// exclusively.
|
|
//
|
|
|
|
UlAcquireResourceExclusive(&g_pUlNonpagedData->LogListResource, TRUE);
|
|
|
|
//
|
|
// Proceed down to see if anything else changed
|
|
// as well ...
|
|
//
|
|
|
|
if ( RtlCompareUnicodeString(
|
|
&pCfgNew->LogFileDir, &pCfgOld->LogFileDir, TRUE ) != 0
|
|
)
|
|
{
|
|
//
|
|
// Always force ReGrow otherwise dir name won't be updated
|
|
// Update the cgroup to hold the new log_dir name
|
|
//
|
|
|
|
if (pCfgOld->LogFileDir.Buffer != NULL)
|
|
{
|
|
UL_FREE_POOL(
|
|
pCfgOld->LogFileDir.Buffer,
|
|
UL_CG_LOGDIR_POOL_TAG
|
|
);
|
|
pCfgOld->LogFileDir.Buffer = NULL;
|
|
}
|
|
|
|
pCfgOld->LogFileDir.Buffer =
|
|
(PWSTR) UL_ALLOCATE_ARRAY(
|
|
PagedPool,
|
|
UCHAR,
|
|
pCfgNew->LogFileDir.MaximumLength,
|
|
UL_CG_LOGDIR_POOL_TAG
|
|
);
|
|
if (pCfgOld->LogFileDir.Buffer == NULL)
|
|
{
|
|
pCfgOld->LogFileDir.Length = 0;
|
|
pCfgOld->LogFileDir.MaximumLength = 0;
|
|
Status = STATUS_NO_MEMORY;
|
|
goto end;
|
|
}
|
|
|
|
RtlCopyMemory(
|
|
pCfgOld->LogFileDir.Buffer,
|
|
pCfgNew->LogFileDir.Buffer,
|
|
pCfgNew->LogFileDir.MaximumLength
|
|
);
|
|
|
|
pCfgOld->LogFileDir.Length = pCfgNew->LogFileDir.Length;
|
|
pCfgOld->LogFileDir.MaximumLength = pCfgNew->LogFileDir.MaximumLength;
|
|
|
|
UlpGrowLogEntry( pConfigGroup, pConfigGroup->pLogFileEntry );
|
|
|
|
// Need to find the proper sequence number after scanning the new
|
|
// log directory.
|
|
|
|
SET_SEQUNCE_NUMBER_STALE(pConfigGroup->pLogFileEntry);
|
|
|
|
HaveToReCycle = TRUE;
|
|
|
|
}
|
|
|
|
if ( pCfgNew->LogFormat != pCfgOld->LogFormat )
|
|
{
|
|
Status = UlpUpdateFormat(
|
|
pConfigGroup->pLogFileEntry,
|
|
pCfgOld,
|
|
pCfgNew
|
|
);
|
|
goto end;
|
|
}
|
|
|
|
if ( pCfgNew->LogPeriod != pCfgOld->LogPeriod )
|
|
{
|
|
Status = UlpUpdatePeriod(
|
|
pConfigGroup->pLogFileEntry,
|
|
pCfgOld,
|
|
pCfgNew
|
|
);
|
|
goto end;
|
|
}
|
|
|
|
//
|
|
// Both LogFormat and LogPeriod always trigger the recycle and they also
|
|
// handle changes to the LogFileTruncateSize and LogExtFileFlags before
|
|
// triggering the recycle. Therefore we can bail out safely.
|
|
//
|
|
|
|
if ( pCfgNew->LogFileTruncateSize != pCfgOld->LogFileTruncateSize )
|
|
{
|
|
Status = UlpUpdateLogTruncateSize(
|
|
pConfigGroup->pLogFileEntry,
|
|
pCfgOld,
|
|
pCfgNew,
|
|
&HaveToReCycle
|
|
);
|
|
}
|
|
|
|
if ( pCfgNew->LogExtFileFlags != pCfgOld->LogExtFileFlags )
|
|
{
|
|
//
|
|
// Just a change in the flags should not cause us to recyle.
|
|
// Unless something else is also changed. If that's the case
|
|
// then it's either handled above us, or will be handled down
|
|
//
|
|
|
|
Status = UlpUpdateLogFlags(
|
|
pConfigGroup->pLogFileEntry,
|
|
pCfgOld,
|
|
pCfgNew
|
|
);
|
|
}
|
|
|
|
if ( HaveToReCycle )
|
|
{
|
|
//
|
|
// If we are here that means directory name has been changed
|
|
// and nobody has recycled the log file yet. So time to go
|
|
//
|
|
|
|
Status = UlpRecycleLogFile(pConfigGroup->pLogFileEntry);
|
|
}
|
|
|
|
end:
|
|
|
|
if (NT_SUCCESS(Status) == FALSE)
|
|
{
|
|
UlTrace( LOGGING,
|
|
("Ul!UlReConfigureLogEntry: entry %p, failure %08lx\n",
|
|
pConfigGroup->pLogFileEntry,
|
|
Status
|
|
));
|
|
}
|
|
|
|
UlReleaseResource(&g_pUlNonpagedData->LogListResource);
|
|
|
|
return Status;
|
|
} // UlReConfigureLogEntry
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
UlpUpdateFormat :
|
|
|
|
Arguments:
|
|
|
|
pConfig - corresponding cgroup object
|
|
|
|
--***************************************************************************/
|
|
|
|
NTSTATUS
|
|
UlpUpdateFormat(
|
|
OUT PUL_LOG_FILE_ENTRY pEntry,
|
|
IN PHTTP_CONFIG_GROUP_LOGGING pCfgOld,
|
|
IN PHTTP_CONFIG_GROUP_LOGGING pCfgNew
|
|
)
|
|
{
|
|
NTSTATUS Status ;
|
|
TIME_FIELDS CurrentTimeFields;
|
|
LARGE_INTEGER CurrentTimeStamp;
|
|
|
|
PAGED_CODE();
|
|
Status = STATUS_SUCCESS;
|
|
|
|
if ( pEntry == NULL )
|
|
{
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
ASSERT(IS_VALID_LOG_FILE_ENTRY(pEntry));
|
|
ASSERT(pCfgOld->LogFormat==pEntry->Format);
|
|
|
|
pCfgOld->LogFormat = pCfgNew->LogFormat;
|
|
pEntry->Format = pCfgNew->LogFormat;
|
|
|
|
pCfgOld->LogPeriod = pCfgNew->LogPeriod;
|
|
pEntry->Period = (HTTP_LOGGING_PERIOD) pCfgNew->LogPeriod;
|
|
|
|
pEntry->TruncateSize = pCfgNew->LogFileTruncateSize;
|
|
pCfgOld->LogFileTruncateSize= pCfgNew->LogFileTruncateSize;
|
|
|
|
pEntry->LogExtFileFlags = pCfgNew->LogExtFileFlags;
|
|
pCfgOld->LogExtFileFlags = pCfgNew->LogExtFileFlags;
|
|
|
|
KeQuerySystemTime( &CurrentTimeStamp );
|
|
RtlTimeToTimeFields( &CurrentTimeStamp, &CurrentTimeFields );
|
|
|
|
if ( pEntry->Period != HttpLoggingPeriodMaxSize )
|
|
{
|
|
Status = UlpCalculateTimeToExpire(
|
|
&CurrentTimeFields,
|
|
pEntry->Period,
|
|
&pEntry->TimeToExpire
|
|
);
|
|
|
|
ASSERT(NT_SUCCESS(Status)==TRUE);
|
|
}
|
|
|
|
ASSERT(NT_SUCCESS(Status)==TRUE);
|
|
|
|
SET_SEQUNCE_NUMBER_STALE(pEntry);
|
|
|
|
Status = UlpRecycleLogFile( pEntry );
|
|
|
|
return Status;
|
|
}
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
UlpUpdatePeriod :
|
|
|
|
Arguments:
|
|
|
|
pConfig - corresponding cgroup object
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Completion status.
|
|
|
|
--***************************************************************************/
|
|
|
|
NTSTATUS
|
|
UlpUpdatePeriod(
|
|
OUT PUL_LOG_FILE_ENTRY pEntry,
|
|
IN PHTTP_CONFIG_GROUP_LOGGING pCfgOld,
|
|
IN PHTTP_CONFIG_GROUP_LOGGING pCfgNew
|
|
)
|
|
{
|
|
NTSTATUS Status ;
|
|
TIME_FIELDS CurrentTimeFields;
|
|
LARGE_INTEGER CurrentTimeStamp;
|
|
|
|
PAGED_CODE();
|
|
Status = STATUS_SUCCESS;
|
|
|
|
if ( pEntry == NULL )
|
|
{
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
ASSERT(IS_VALID_LOG_FILE_ENTRY(pEntry));
|
|
ASSERT(pCfgOld->LogPeriod==(ULONG)pEntry->Period);
|
|
|
|
pCfgOld->LogPeriod = pCfgNew->LogPeriod;
|
|
pEntry->Period = (HTTP_LOGGING_PERIOD) pCfgNew->LogPeriod;
|
|
|
|
pEntry->TruncateSize = pCfgNew->LogFileTruncateSize;
|
|
pCfgOld->LogFileTruncateSize= pCfgNew->LogFileTruncateSize;
|
|
|
|
pEntry->LogExtFileFlags = pCfgNew->LogExtFileFlags;
|
|
pCfgOld->LogExtFileFlags = pCfgNew->LogExtFileFlags;
|
|
|
|
KeQuerySystemTime( &CurrentTimeStamp );
|
|
RtlTimeToTimeFields( &CurrentTimeStamp, &CurrentTimeFields );
|
|
|
|
if ( pEntry->Period != HttpLoggingPeriodMaxSize )
|
|
{
|
|
Status = UlpCalculateTimeToExpire(
|
|
&CurrentTimeFields,
|
|
pEntry->Period,
|
|
&pEntry->TimeToExpire
|
|
);
|
|
|
|
ASSERT(NT_SUCCESS(Status)==TRUE);
|
|
}
|
|
|
|
SET_SEQUNCE_NUMBER_STALE(pEntry);
|
|
|
|
Status = UlpRecycleLogFile( pEntry );
|
|
|
|
return Status;
|
|
}
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
UlpUpdateLogTruncateSize :
|
|
|
|
Arguments:
|
|
|
|
pConfig - corresponding cgroup object
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Completion status.
|
|
|
|
--***************************************************************************/
|
|
|
|
NTSTATUS
|
|
UlpUpdateLogTruncateSize(
|
|
OUT PUL_LOG_FILE_ENTRY pEntry,
|
|
IN PHTTP_CONFIG_GROUP_LOGGING pCfgOld,
|
|
IN PHTTP_CONFIG_GROUP_LOGGING pCfgNew,
|
|
OUT BOOLEAN * pHaveToReCycle
|
|
)
|
|
{
|
|
NTSTATUS Status ;
|
|
|
|
//
|
|
// Sanity check first
|
|
//
|
|
|
|
PAGED_CODE();
|
|
Status = STATUS_SUCCESS;
|
|
|
|
//
|
|
// For MAX_SIZE period type we should check if
|
|
// limited => unlimited:
|
|
// we can still use the last log file
|
|
// unlimited => limited:
|
|
// we should open a new one if old size is larger than
|
|
// the new limitation
|
|
//
|
|
|
|
if ( pEntry == NULL )
|
|
{
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
ASSERT(IS_VALID_LOG_FILE_ENTRY(pEntry));
|
|
ASSERT(pCfgOld->LogFileTruncateSize==pEntry->TruncateSize);
|
|
|
|
if ( pCfgOld->LogPeriod == HttpLoggingPeriodMaxSize )
|
|
{
|
|
if ( pCfgOld->LogFileTruncateSize == HTTP_LIMIT_INFINITE )
|
|
{
|
|
//
|
|
// Unlimited to Limited
|
|
//
|
|
|
|
if ( pEntry->TotalWritten.QuadPart >
|
|
(ULONGLONG)pCfgNew->LogFileTruncateSize )
|
|
{
|
|
// In case flags get changed lets take it too
|
|
pEntry->LogExtFileFlags = pCfgNew->LogExtFileFlags;
|
|
pCfgOld->LogExtFileFlags = pCfgNew->LogExtFileFlags;
|
|
|
|
UlpRecycleLogFile( pEntry );
|
|
|
|
*pHaveToReCycle = FALSE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Limited to Unlimited
|
|
// Nothing special to do
|
|
//
|
|
}
|
|
}
|
|
|
|
pEntry->TruncateSize = pCfgNew->LogFileTruncateSize;
|
|
pCfgOld->LogFileTruncateSize = pCfgNew->LogFileTruncateSize;
|
|
|
|
return Status;
|
|
}
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
UlpUpdateLogFlags :
|
|
|
|
REQUIRES caller to have loglist resource exclusively.
|
|
|
|
Arguments:
|
|
|
|
pEntry - corresponding logfile entry
|
|
|
|
old & new configuration
|
|
|
|
--***************************************************************************/
|
|
|
|
NTSTATUS
|
|
UlpUpdateLogFlags(
|
|
OUT PUL_LOG_FILE_ENTRY pEntry,
|
|
IN PHTTP_CONFIG_GROUP_LOGGING pCfgOld,
|
|
IN PHTTP_CONFIG_GROUP_LOGGING pCfgNew
|
|
)
|
|
{
|
|
NTSTATUS Status ;
|
|
TIME_FIELDS CurrentTimeFields;
|
|
LARGE_INTEGER CurrentTimeStamp;
|
|
|
|
//
|
|
// No need to do anything else, we will
|
|
// just display a new title with the
|
|
// new fields.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
Status = STATUS_SUCCESS;
|
|
|
|
if (pEntry == NULL)
|
|
{
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
ASSERT(IS_VALID_LOG_FILE_ENTRY(pEntry));
|
|
ASSERT(pCfgOld->LogExtFileFlags==pEntry->LogExtFileFlags);
|
|
|
|
pCfgOld->LogExtFileFlags = pCfgNew->LogExtFileFlags;
|
|
pEntry->LogExtFileFlags = pCfgNew->LogExtFileFlags;
|
|
|
|
if (pEntry->Format == HttpLoggingTypeW3C)
|
|
{
|
|
pEntry->Flags.LogTitleWritten = 0;
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
UlpConstructLogFileEntry :
|
|
|
|
Finds out the correct file name for the newly created log file
|
|
from our - current time dependent- time-to-name converters. Also
|
|
allocates the necessary file entry from 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
|
|
pDirectoryName - the directory name to store newcoming log file
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Completion status.
|
|
|
|
--***************************************************************************/
|
|
|
|
NTSTATUS
|
|
UlpConstructLogFileEntry(
|
|
IN PHTTP_CONFIG_GROUP_LOGGING pConfig,
|
|
OUT PUL_LOG_FILE_ENTRY * ppEntry,
|
|
OUT PUNICODE_STRING pDirectoryName,
|
|
IN PTIME_FIELDS pCurrentTimeFields
|
|
)
|
|
{
|
|
NTSTATUS Status,TmpStatus;
|
|
USHORT FullPathFileNameLength;
|
|
ULONG SequenceNumber;
|
|
PUL_LOG_FILE_ENTRY pEntry;
|
|
WCHAR _FileName[UL_MAX_FILE_NAME_SUFFIX_LENGTH+1];
|
|
UNICODE_STRING FileName =
|
|
{ 0, (UL_MAX_FILE_NAME_SUFFIX_LENGTH+1)*sizeof(WCHAR), _FileName };
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT(pConfig != NULL);
|
|
ASSERT(ppEntry != NULL);
|
|
|
|
Status = STATUS_SUCCESS;
|
|
SequenceNumber = 1;
|
|
pEntry = NULL;
|
|
|
|
FullPathFileNameLength = pDirectoryName->Length;
|
|
|
|
UlpConstructFileName(
|
|
(HTTP_LOGGING_PERIOD) pConfig->LogPeriod,
|
|
UL_GET_LOG_FILE_NAME_PREFIX(pConfig->LogFormat),
|
|
&FileName,
|
|
pCurrentTimeFields,
|
|
&SequenceNumber
|
|
);
|
|
|
|
FullPathFileNameLength += FileName.Length;
|
|
|
|
//
|
|
// 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 filename changes according to
|
|
// new time - , We will try to allocate a fixed amount here
|
|
// for all the possible file_names ( this doesn't include
|
|
// the log_dir changes may happen from WAS through cgroup
|
|
// in that case we will realloc a new entry ). It has to
|
|
// be nonpaged because it holds a eresource.
|
|
//
|
|
|
|
pEntry = UL_ALLOCATE_STRUCT_WITH_SPACE(
|
|
NonPagedPool,
|
|
UL_LOG_FILE_ENTRY,
|
|
(UL_MAX_FILE_NAME_SUFFIX_LENGTH + 1) * sizeof(WCHAR) +
|
|
pDirectoryName->Length,
|
|
UL_LOG_FILE_ENTRY_POOL_TAG
|
|
);
|
|
|
|
if ( pEntry == NULL )
|
|
{
|
|
Status = STATUS_NO_MEMORY;
|
|
goto end;
|
|
}
|
|
|
|
RtlZeroMemory( pEntry, sizeof(*pEntry) );
|
|
pEntry->Signature = UL_LOG_FILE_ENTRY_POOL_TAG;
|
|
|
|
//
|
|
// Concat the directory & file name properly.
|
|
//
|
|
|
|
pEntry->FileName.Length = FullPathFileNameLength;
|
|
pEntry->FileName.MaximumLength= (UL_MAX_FILE_NAME_SUFFIX_LENGTH+1) * sizeof(WCHAR) +
|
|
pDirectoryName->Length;
|
|
|
|
pEntry->FileName.Buffer = (PWSTR) ( pEntry + 1 );
|
|
|
|
RtlCopyUnicodeString( &(pEntry->FileName), pDirectoryName );
|
|
Status = RtlAppendUnicodeStringToString( &(pEntry->FileName), &FileName );
|
|
ASSERT(NT_SUCCESS(Status));
|
|
|
|
pEntry->FileName.Buffer[FullPathFileNameLength/sizeof(WCHAR)] = UNICODE_NULL;
|
|
pEntry->pShortName = (PWSTR)
|
|
&(pEntry->FileName.Buffer[pDirectoryName->Length/sizeof(WCHAR)]);
|
|
|
|
//
|
|
// Create a log entry buffer of system dependent size
|
|
// typically 64K
|
|
//
|
|
|
|
Status = UlInitializeResource(&pEntry->EntryResource,"EntryResource",0,
|
|
UL_LOG_FILE_ENTRY_POOL_TAG);
|
|
ASSERT(NT_SUCCESS(Status));
|
|
|
|
//
|
|
// Initialize the file handle
|
|
//
|
|
|
|
pEntry->hFile = NULL;
|
|
|
|
KeInitializeEvent(&pEntry->CloseEvent, NotificationEvent, FALSE);
|
|
|
|
//
|
|
// Set the logging information from config group
|
|
// easier for other routines to use this values
|
|
// w/o reaching to the config-group
|
|
//
|
|
|
|
pEntry->Format = pConfig->LogFormat;
|
|
pEntry->Period = (HTTP_LOGGING_PERIOD) pConfig->LogPeriod;
|
|
pEntry->TruncateSize = pConfig->LogFileTruncateSize;
|
|
pEntry->LogExtFileFlags = pConfig->LogExtFileFlags;
|
|
|
|
//
|
|
// Time to initialize our Log Cycling parameter
|
|
//
|
|
|
|
pEntry->TimeToExpire = 0;
|
|
pEntry->SequenceNumber = SequenceNumber;
|
|
pEntry->TotalWritten.QuadPart = (ULONGLONG)0;
|
|
|
|
pEntry->Flags.Value = 0;
|
|
|
|
if (pEntry->Format == HttpLoggingTypeW3C)
|
|
{
|
|
pEntry->Flags.LogTitleWritten = 0;
|
|
}
|
|
else
|
|
{
|
|
pEntry->Flags.LogTitleWritten = 1;
|
|
}
|
|
|
|
pEntry->LogBuffer = NULL;
|
|
|
|
Status = UlpCalculateTimeToExpire(
|
|
pCurrentTimeFields,
|
|
pEntry->Period,
|
|
&pEntry->TimeToExpire
|
|
);
|
|
if ( !NT_SUCCESS(Status) )
|
|
{
|
|
goto end;
|
|
}
|
|
|
|
//
|
|
// Lets happily return our entry
|
|
//
|
|
|
|
*ppEntry = pEntry;
|
|
|
|
end:
|
|
if ( !NT_SUCCESS(Status) )
|
|
{
|
|
if ( pEntry )
|
|
{
|
|
NTSTATUS TempStatus;
|
|
|
|
if ( pEntry->LogBuffer )
|
|
{
|
|
UlPplFreeLogBuffer( pEntry->LogBuffer );
|
|
}
|
|
// Delete the entry eresource
|
|
TempStatus = UlDeleteResource( &pEntry->EntryResource );
|
|
ASSERT(NT_SUCCESS(TempStatus));
|
|
|
|
UL_FREE_POOL_WITH_SIG( pEntry, UL_LOG_FILE_ENTRY_POOL_TAG );
|
|
}
|
|
}
|
|
return Status;
|
|
}
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
UlpConstructFileName:
|
|
|
|
A bunch of current_time TO file_name conversions comes here ...
|
|
|
|
Arguments:
|
|
|
|
period - period type of the log
|
|
prefix - any prefix to be added to the file name
|
|
filename - result file name
|
|
fields - time fields
|
|
|
|
Return Value:
|
|
|
|
VOID - No return value.
|
|
|
|
--***************************************************************************/
|
|
|
|
VOID
|
|
UlpConstructFileName(
|
|
IN HTTP_LOGGING_PERIOD period,
|
|
IN PCWSTR prefix,
|
|
OUT PUNICODE_STRING filename,
|
|
IN PTIME_FIELDS fields,
|
|
IN OUT PULONG sequenceNu //OPTIONAL
|
|
)
|
|
{
|
|
WCHAR _tmp[UL_MAX_FILE_NAME_SUFFIX_LENGTH+1];
|
|
UNICODE_STRING tmp = { 0, 0, _tmp };
|
|
CSHORT Year;
|
|
LONG WcharsCopied = 0L;
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Retain just last 2 digits of the Year
|
|
//
|
|
|
|
tmp.MaximumLength = (UL_MAX_FILE_NAME_SUFFIX_LENGTH+1) * sizeof(WCHAR);
|
|
|
|
if (fields)
|
|
{
|
|
Year = fields->Year % 100;
|
|
}
|
|
|
|
switch ( period )
|
|
{
|
|
case HttpLoggingPeriodHourly:
|
|
{
|
|
WcharsCopied =
|
|
_snwprintf( _tmp,
|
|
UL_MAX_FILE_NAME_SUFFIX_LENGTH,
|
|
(UTF8_LOGGING_ENABLED() ?
|
|
L"%.5s%02.2d%02d%02d%02d.%s" :
|
|
L"%.3s%02.2d%02d%02d%02d.%s"),
|
|
prefix,
|
|
Year,
|
|
fields->Month,
|
|
fields->Day,
|
|
fields->Hour,
|
|
DEFAULT_LOG_FILE_EXTENSION
|
|
);
|
|
}
|
|
break;
|
|
|
|
case HttpLoggingPeriodDaily:
|
|
{
|
|
WcharsCopied =
|
|
_snwprintf( _tmp,
|
|
UL_MAX_FILE_NAME_SUFFIX_LENGTH,
|
|
(UTF8_LOGGING_ENABLED() ?
|
|
L"%.5s%02.2d%02d%02d.%s" :
|
|
L"%.3s%02.2d%02d%02d.%s"),
|
|
prefix,
|
|
Year,
|
|
fields->Month,
|
|
fields->Day,
|
|
DEFAULT_LOG_FILE_EXTENSION
|
|
);
|
|
}
|
|
break;
|
|
|
|
case HttpLoggingPeriodWeekly:
|
|
{
|
|
WcharsCopied =
|
|
_snwprintf( _tmp,
|
|
UL_MAX_FILE_NAME_SUFFIX_LENGTH,
|
|
(UTF8_LOGGING_ENABLED() ?
|
|
L"%.5s%02.2d%02d%02d.%s" :
|
|
L"%.3s%02.2d%02d%02d.%s"),
|
|
prefix,
|
|
Year,
|
|
fields->Month,
|
|
UlpWeekOfMonth(fields),
|
|
DEFAULT_LOG_FILE_EXTENSION
|
|
);
|
|
}
|
|
break;
|
|
|
|
case HttpLoggingPeriodMonthly:
|
|
{
|
|
WcharsCopied =
|
|
_snwprintf( _tmp,
|
|
UL_MAX_FILE_NAME_SUFFIX_LENGTH,
|
|
(UTF8_LOGGING_ENABLED() ?
|
|
L"%.5s%02.2d%02d.%s" :
|
|
L"%.3s%02.2d%02d.%s"),
|
|
prefix,
|
|
Year,
|
|
fields->Month,
|
|
DEFAULT_LOG_FILE_EXTENSION
|
|
);
|
|
}
|
|
break;
|
|
|
|
case HttpLoggingPeriodMaxSize:
|
|
{
|
|
if ( sequenceNu != NULL )
|
|
{
|
|
WcharsCopied =
|
|
_snwprintf( _tmp,
|
|
UL_MAX_FILE_NAME_SUFFIX_LENGTH,
|
|
(UTF8_LOGGING_ENABLED() ?
|
|
L"%.9s%d.%s" :
|
|
L"%.7s%d.%s"),
|
|
prefix,
|
|
(*sequenceNu),
|
|
DEFAULT_LOG_FILE_EXTENSION
|
|
);
|
|
|
|
(*sequenceNu) += 1;
|
|
}
|
|
else
|
|
{
|
|
ASSERT(!"Improper sequence number !");
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
{
|
|
//
|
|
// This should never happen ...
|
|
//
|
|
|
|
ASSERT(!"Unknown Log Format !");
|
|
|
|
WcharsCopied =
|
|
_snwprintf( _tmp,
|
|
UL_MAX_FILE_NAME_SUFFIX_LENGTH,
|
|
L"%.7s?.%s",
|
|
prefix,
|
|
DEFAULT_LOG_FILE_EXTENSION
|
|
);
|
|
}
|
|
}
|
|
|
|
//
|
|
// As long as we allocate an enough space for a possible
|
|
// log filename we should never hit to this assert here.
|
|
//
|
|
|
|
ASSERT(WcharsCopied >0 );
|
|
|
|
if ( WcharsCopied < 0 )
|
|
{
|
|
//
|
|
// This should never happen but lets cover it
|
|
// anyway.
|
|
//
|
|
|
|
WcharsCopied = UL_MAX_FILE_NAME_SUFFIX_LENGTH*sizeof(WCHAR);
|
|
tmp.Buffer[UL_MAX_FILE_NAME_SUFFIX_LENGTH] = UNICODE_NULL;
|
|
}
|
|
|
|
tmp.Length = (USHORT) WcharsCopied*sizeof(WCHAR);
|
|
tmp.MaximumLength = (UL_MAX_FILE_NAME_SUFFIX_LENGTH+1)*sizeof(WCHAR);
|
|
|
|
RtlCopyUnicodeString( filename, &tmp );
|
|
}
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
UlpRecycleLogFile :
|
|
|
|
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.
|
|
|
|
Arguments:
|
|
|
|
pEntry - Points to the existing entry.
|
|
NeedToReCalculate - shows if we have to recalculate the time-to-expire.
|
|
|
|
--***************************************************************************/
|
|
|
|
NTSTATUS
|
|
UlpRecycleLogFile(
|
|
IN PUL_LOG_FILE_ENTRY pEntry
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
OBJECT_ATTRIBUTES ObjectAttributes;
|
|
IO_STATUS_BLOCK IoStatusBlock;
|
|
TIME_FIELDS CurrentTimeFields;
|
|
LARGE_INTEGER CurrentTimeStamp;
|
|
PWCHAR pSrc;
|
|
PWCHAR pDst;
|
|
USHORT Index;
|
|
USHORT OldFileNameLength;
|
|
|
|
WCHAR _FileName[UL_MAX_FILE_NAME_SUFFIX_LENGTH];
|
|
UNICODE_STRING FileName =
|
|
{ 0, UL_MAX_FILE_NAME_SUFFIX_LENGTH*sizeof(WCHAR), _FileName };
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT(IS_VALID_LOG_FILE_ENTRY(pEntry));
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
//
|
|
// We have two criterions for the log file name
|
|
// its LogFormat and its LogPeriod
|
|
//
|
|
|
|
ASSERT(pEntry->Format < HttpLoggingTypeMaximum);
|
|
ASSERT(pEntry->Period < HttpLoggingPeriodMaximum);
|
|
ASSERT(pEntry->FileName.Length!=0);
|
|
|
|
UlTrace( LOGGING, ("Ul!UlpRecycleLogFile: pEntry %p \n", pEntry ));
|
|
|
|
//
|
|
// This value is computed for the GMT time zone.
|
|
//
|
|
|
|
KeQuerySystemTime( &CurrentTimeStamp );
|
|
RtlTimeToTimeFields( &CurrentTimeStamp, &CurrentTimeFields );
|
|
|
|
// Init total written to zero. It may get updated if we scan
|
|
// the directory down below.
|
|
|
|
pEntry->TotalWritten.QuadPart = (ULONGLONG)0;
|
|
|
|
// 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 values
|
|
pEntry->SequenceNumber = 1;
|
|
}
|
|
|
|
//
|
|
// Now construct the filename using the lookup table
|
|
// And the current time
|
|
//
|
|
|
|
UlpConstructFileName(
|
|
pEntry->Period,
|
|
UL_GET_LOG_FILE_NAME_PREFIX(pEntry->Format),
|
|
&FileName,
|
|
&CurrentTimeFields,
|
|
&pEntry->SequenceNumber
|
|
);
|
|
|
|
if ( pEntry->FileName.MaximumLength <= FileName.Length )
|
|
{
|
|
ASSERT(FALSE);
|
|
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 );
|
|
if ( 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 =
|
|
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 = UlpCreateSafeDirectory( &pEntry->FileName );
|
|
if (!NT_SUCCESS(Status))
|
|
goto end;
|
|
|
|
//
|
|
// 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;
|
|
}
|
|
|
|
//
|
|
// If the sequence is stale because of the nature of the recycle.
|
|
// And if our preiod is size based then rescan the new directory
|
|
// to figure out the proper file to open.
|
|
//
|
|
|
|
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 = UlpQueryDirectory(pEntry);
|
|
if (!NT_SUCCESS(Status))
|
|
goto end;
|
|
}
|
|
|
|
//
|
|
// Time to close the old file and reopen a new one
|
|
//
|
|
|
|
if (pEntry->hFile != NULL)
|
|
{
|
|
//
|
|
// Before closing the old one we need to flush the buffer
|
|
//
|
|
|
|
UlpFlushLogFile(pEntry);
|
|
|
|
UlpLogCloseHandle(pEntry);
|
|
}
|
|
|
|
InitializeObjectAttributes(
|
|
&ObjectAttributes,
|
|
&pEntry->FileName, // Full path name
|
|
OBJ_CASE_INSENSITIVE | // Attributes
|
|
UL_KERNEL_HANDLE,
|
|
NULL, // RootDirectory
|
|
NULL // SecurityDescriptor
|
|
);
|
|
|
|
//
|
|
// Make the created file Aysnc by not picking the sync flag.
|
|
//
|
|
|
|
Status = ZwCreateFile(
|
|
&pEntry->hFile,
|
|
FILE_GENERIC_WRITE,
|
|
&ObjectAttributes,
|
|
&IoStatusBlock,
|
|
NULL,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
FILE_SHARE_READ,
|
|
FILE_OPEN_IF,
|
|
FILE_NON_DIRECTORY_FILE, // | FILE_SYNCHRONOUS_IO_NONALERT,
|
|
NULL,
|
|
0);
|
|
|
|
if (NT_SUCCESS(Status) == FALSE)
|
|
goto end;
|
|
|
|
//
|
|
// Get the file size, etc from the file.
|
|
//
|
|
|
|
Status = ZwQueryInformationFile(
|
|
pEntry->hFile,
|
|
&IoStatusBlock,
|
|
&pEntry->FileInfo,
|
|
sizeof(pEntry->FileInfo),
|
|
FileStandardInformation
|
|
);
|
|
|
|
if (NT_SUCCESS(Status) == FALSE)
|
|
goto end;
|
|
|
|
//
|
|
// Recalculate the time to expire.
|
|
//
|
|
if (pEntry->Flags.StaleTimeToExpire &&
|
|
pEntry->Period != HttpLoggingPeriodMaxSize)
|
|
{
|
|
UlpCalculateTimeToExpire(
|
|
&CurrentTimeFields,
|
|
pEntry->Period,
|
|
&pEntry->TimeToExpire
|
|
);
|
|
}
|
|
|
|
//
|
|
// 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 doesn't 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;
|
|
}
|
|
|
|
UlTrace( LOGGING, ("Ul!UlpRecycleLogFile: entry %p, file %S, handle %lx\n",
|
|
pEntry,
|
|
pEntry->FileName.Buffer,
|
|
pEntry->hFile
|
|
));
|
|
end:
|
|
// Mark fields non-stale again;
|
|
|
|
RESET_SEQUNCE_NUMBER_STALE(pEntry);
|
|
RESET_TIME_TO_EXPIRE_STALE(pEntry);
|
|
|
|
// TODO: Handle STATUS_DISK_FULL case gracefully.
|
|
|
|
if ( Status == STATUS_DISK_FULL )
|
|
{
|
|
UlTrace(LOGGING,("UlpRecycleLogFile: DISK FULL entry %p, failure %08lx\n",
|
|
pEntry,
|
|
Status
|
|
));
|
|
|
|
if (pEntry->hFile != NULL)
|
|
{
|
|
ZwClose( pEntry->hFile );
|
|
pEntry->hFile = NULL;
|
|
}
|
|
|
|
// TODO: pEntry->Flags.RecyclePending = 1;
|
|
// TODO: UlpFireDiskFullTimer();
|
|
}
|
|
|
|
if ( !NT_SUCCESS(Status) && Status != STATUS_DISK_FULL)
|
|
{
|
|
//
|
|
// If we made it to this point, then the create/open has failed.
|
|
//
|
|
|
|
UlTrace( LOGGING,("Ul!UlpRecycleLogFile: entry %p, failure %08lx\n",
|
|
pEntry,
|
|
Status
|
|
));
|
|
|
|
if (pEntry->hFile != NULL)
|
|
{
|
|
ZwClose( pEntry->hFile );
|
|
pEntry->hFile = NULL;
|
|
}
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
IsLogFileOverFlow:
|
|
|
|
Arguments:
|
|
|
|
--***************************************************************************/
|
|
|
|
__inline
|
|
BOOLEAN
|
|
UlpIsLogFileOverFlow(
|
|
IN PUL_LOG_FILE_ENTRY pEntry,
|
|
IN ULONG ReqdBytes
|
|
)
|
|
{
|
|
if (pEntry->Period != HttpLoggingPeriodMaxSize ||
|
|
pEntry->TruncateSize == HTTP_LIMIT_INFINITE)
|
|
{
|
|
return FALSE;
|
|
}
|
|
else
|
|
{
|
|
return((pEntry->TotalWritten.QuadPart + (ULONGLONG)ReqdBytes)
|
|
>=
|
|
(ULONGLONG)pEntry->TruncateSize
|
|
);
|
|
}
|
|
}
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
UlpIncrementBytesWritten:
|
|
|
|
Arguments:
|
|
|
|
--***************************************************************************/
|
|
|
|
__inline
|
|
VOID
|
|
UlpIncrementBytesWritten(
|
|
IN PUL_LOG_FILE_ENTRY pEntry,
|
|
IN ULONG BytesWritten
|
|
)
|
|
{
|
|
UlInterlockedAdd64((PLONGLONG) &(pEntry->TotalWritten.QuadPart),
|
|
(ULONGLONG)BytesWritten);
|
|
};
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
UlpWeekOfMonth : Ordinal Number of the week of the current month
|
|
|
|
Stolen from IIS 5.1 code base.
|
|
|
|
Example
|
|
|
|
July 2000 ... :
|
|
|
|
S M T W T F S WeekOfMonth
|
|
1 1
|
|
2 3 4 5 6 7 8 2
|
|
9 10 11 12 13 14 15 3
|
|
16 17 18 19 20 21 22 4
|
|
23 24 25 26 27 28 29 5
|
|
30 31 6
|
|
|
|
Finds the ordinal number of the week of current month.
|
|
The numbering of weeks starts from 1 and run through 6 per month (max).
|
|
The week number changes only on sundays.
|
|
|
|
The calculation to be use is:
|
|
|
|
1 + (dayOfMonth - 1)/7 + ((dayOfMonth - 1) % 7 > dayOfWeek);
|
|
(a) (b) (c) (d)
|
|
|
|
(a) to set the week numbers to begin from week numbered "1"
|
|
(b) used to calculate the rough number of the week on which a given
|
|
day falls based on the date.
|
|
(c) calculates what is the offset from the start of week for a given
|
|
day based on the fact that a week had 7 days.
|
|
(d) is the raw day of week given to us.
|
|
(c) > (d) indicates that the week is rolling forward and hence
|
|
the week count should be offset by 1 more.
|
|
|
|
Arguments:
|
|
|
|
PTIME_FIELDS - system time fields
|
|
|
|
Return Value:
|
|
|
|
ULONG - This func magically returns the week of the month
|
|
|
|
|
|
--***************************************************************************/
|
|
|
|
__inline
|
|
ULONG UlpWeekOfMonth(
|
|
IN PTIME_FIELDS fields
|
|
)
|
|
{
|
|
ULONG Tmp;
|
|
|
|
Tmp = (fields->Day - 1);
|
|
Tmp = ( 1 + Tmp/7 + (((Tmp % 7) > ((ULONG) fields->Weekday)) ? 1 : 0));
|
|
|
|
return Tmp;
|
|
}
|
|
|
|
/***************************************************************************++
|
|
|
|
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,("Ul!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,
|
|
("Ul!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:
|
|
|
|
UlpInitializeLogBufferGranularity :
|
|
|
|
This will determine the (MAX) size of the buffer we will be using
|
|
for the eac log file entry.
|
|
|
|
--***************************************************************************/
|
|
|
|
NTSTATUS
|
|
UlpInitializeLogBufferGranularity()
|
|
{
|
|
SYSTEM_BASIC_INFORMATION sbi;
|
|
NTSTATUS Status;
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
//
|
|
// Get the granularity from the system
|
|
//
|
|
|
|
Status = NtQuerySystemInformation(
|
|
SystemBasicInformation,
|
|
(PVOID)&sbi,
|
|
sizeof(sbi),
|
|
NULL
|
|
);
|
|
|
|
if ( !NT_SUCCESS(Status) )
|
|
{
|
|
UlTrace( LOGGING,
|
|
("Ul!UlpInitializeLogBufferGranularity: failure %08lx\n",
|
|
Status) );
|
|
|
|
return Status;
|
|
}
|
|
|
|
g_AllocationGranularity = sbi.AllocationGranularity;
|
|
|
|
UlTrace( LOGGING,
|
|
("Ul!UlpInitializeLogBufferGranularity: %d\n",
|
|
g_AllocationGranularity
|
|
) );
|
|
|
|
return Status;
|
|
}
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
UlProbeLogField :
|
|
|
|
Probes the content of a user log field, including the terminating
|
|
null.
|
|
|
|
Arguments:
|
|
|
|
- the log field to be probed
|
|
|
|
--***************************************************************************/
|
|
|
|
__inline
|
|
VOID
|
|
UlpProbeLogField(
|
|
IN PVOID pField,
|
|
IN SIZE_T FieldLength,
|
|
IN ULONG Alignment
|
|
)
|
|
{
|
|
if ( pField )
|
|
{
|
|
ProbeTestForRead(
|
|
pField,
|
|
FieldLength + Alignment,
|
|
Alignment
|
|
);
|
|
}
|
|
}
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
UlProbeLogData :
|
|
|
|
Probes the content of the user buffer of Log Data
|
|
|
|
Note: pUserLogData holds untrusted data sent down from user mode.
|
|
The caller MUST have a __try/__except block to catch any exceptions
|
|
or access violations that occur while probing this data.
|
|
|
|
Arguments:
|
|
|
|
PHTTP_LOG_FIELDS_DATA - The log data ( from WP ) to be probed and verified.
|
|
|
|
--***************************************************************************/
|
|
|
|
NTSTATUS
|
|
UlProbeLogData(
|
|
IN PHTTP_LOG_FIELDS_DATA pUserLogData
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
|
|
PAGED_CODE();
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
if (pUserLogData)
|
|
{
|
|
UlTrace( LOGGING, ("Ul!UlProbeLogData: pUserLogData %p\n",
|
|
pUserLogData ));
|
|
|
|
//
|
|
// Check for the log fields data structure
|
|
//
|
|
|
|
ProbeTestForRead(
|
|
pUserLogData,
|
|
sizeof(HTTP_LOG_FIELDS_DATA),
|
|
sizeof(USHORT)
|
|
);
|
|
|
|
//
|
|
// Now check for the individual strings
|
|
//
|
|
|
|
UlpProbeLogField(pUserLogData->ClientIp,
|
|
pUserLogData->ClientIpLength,
|
|
sizeof(CHAR));
|
|
UlpProbeLogField(pUserLogData->ServiceName,
|
|
pUserLogData->ServiceNameLength,
|
|
sizeof(CHAR));
|
|
UlpProbeLogField(pUserLogData->ServerName,
|
|
pUserLogData->ServerNameLength,
|
|
sizeof(CHAR));
|
|
UlpProbeLogField(pUserLogData->ServerIp,
|
|
pUserLogData->ServerIpLength,
|
|
sizeof(CHAR));
|
|
UlpProbeLogField(pUserLogData->UriQuery,
|
|
pUserLogData->UriQueryLength,
|
|
sizeof(CHAR));
|
|
UlpProbeLogField(pUserLogData->Host,
|
|
pUserLogData->HostLength,
|
|
sizeof(CHAR));
|
|
UlpProbeLogField(pUserLogData->UserAgent,
|
|
pUserLogData->UserAgentLength,
|
|
sizeof(CHAR));
|
|
UlpProbeLogField(pUserLogData->Cookie,
|
|
pUserLogData->CookieLength,
|
|
sizeof(CHAR));
|
|
UlpProbeLogField(pUserLogData->Referrer,
|
|
pUserLogData->ReferrerLength,
|
|
sizeof(CHAR));
|
|
UlpProbeLogField(pUserLogData->Method,
|
|
pUserLogData->MethodLength,
|
|
sizeof(CHAR));
|
|
UlpProbeLogField(pUserLogData->UserName,
|
|
pUserLogData->UserNameLength,
|
|
sizeof(WCHAR));
|
|
UlpProbeLogField(pUserLogData->UriStem,
|
|
pUserLogData->UriStemLength,
|
|
sizeof(WCHAR));
|
|
|
|
#if DBG
|
|
|
|
// CODEWORK: should we do this all the time? Remember, this is
|
|
// untrusted user-mode data
|
|
|
|
//
|
|
// Few more controls for chk bits
|
|
//
|
|
|
|
if ((pUserLogData->ClientIp &&
|
|
pUserLogData->ClientIpLength != strlen(pUserLogData->ClientIp))
|
|
||
|
|
(pUserLogData->ServiceName &&
|
|
pUserLogData->ServiceNameLength != strlen(pUserLogData->ServiceName))
|
|
||
|
|
(pUserLogData->ServerName &&
|
|
pUserLogData->ServerNameLength != strlen(pUserLogData->ServerName))
|
|
||
|
|
(pUserLogData->ServerIp &&
|
|
pUserLogData->ServerIpLength != strlen(pUserLogData->ServerIp))
|
|
||
|
|
(pUserLogData->Method &&
|
|
pUserLogData->MethodLength != strlen(pUserLogData->Method))
|
|
||
|
|
(pUserLogData->UriQuery &&
|
|
pUserLogData->UriQueryLength != strlen(pUserLogData->UriQuery))
|
|
||
|
|
(pUserLogData->Host &&
|
|
pUserLogData->HostLength != strlen(pUserLogData->Host))
|
|
||
|
|
(pUserLogData->UserAgent &&
|
|
pUserLogData->UserAgentLength != strlen(pUserLogData->UserAgent))
|
|
||
|
|
(pUserLogData->Cookie &&
|
|
pUserLogData->CookieLength != strlen(pUserLogData->Cookie))
|
|
||
|
|
(pUserLogData->Referrer &&
|
|
pUserLogData->ReferrerLength != strlen(pUserLogData->Referrer))
|
|
||
|
|
(pUserLogData->UserName &&
|
|
pUserLogData->UserNameLength != wcslen(pUserLogData->UserName)*sizeof(WCHAR))
|
|
//
|
|
// Disabled because UriSTem may not be null terminated.
|
|
// ||
|
|
// (pUserLogData->UriStem &&
|
|
// pUserLogData->UriStemLength != wcslen(pUserLogData->UriStem)*sizeof(WCHAR))
|
|
)
|
|
{
|
|
//
|
|
// Invalid log field has been pushed down by the WP.
|
|
// Complain and reject the request.
|
|
//
|
|
|
|
UlTrace(LOGGING,(
|
|
"Ul!UlProbeLogData: INVALID field rcvd from WP in pLogFields %p \n",
|
|
pUserLogData
|
|
));
|
|
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
#endif // DBG
|
|
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
UlAllocateLogDataBuffer :
|
|
|
|
We capture the log fields from user ( WP ) buffer to our internal
|
|
buffer that we allocate here.
|
|
|
|
Also we set our pointer to pRequest here.
|
|
|
|
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.
|
|
|
|
--***************************************************************************/
|
|
|
|
NTSTATUS
|
|
UlAllocateLogDataBuffer(
|
|
OUT PUL_LOG_DATA_BUFFER pLogData,
|
|
IN PUL_INTERNAL_REQUEST pRequest,
|
|
IN PUL_CONFIG_GROUP_OBJECT pConfigGroup // CG from cache or request
|
|
)
|
|
{
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT(pLogData);
|
|
ASSERT(UL_IS_VALID_INTERNAL_REQUEST(pRequest));
|
|
ASSERT(IS_VALID_CONFIG_GROUP(pConfigGroup));
|
|
|
|
UlTrace(LOGGING, ("Ul!UlAllocateLogDataBuffer: pLogData %p \n",
|
|
pLogData
|
|
));
|
|
//
|
|
// Initialize Log Fields in the Log Buffer
|
|
//
|
|
|
|
UL_REFERENCE_INTERNAL_REQUEST(pRequest);
|
|
pLogData->pRequest = pRequest;
|
|
|
|
pLogData->CacheAndSendResponse= FALSE;
|
|
pLogData->BytesTransferred= 0;
|
|
pLogData->pConfigGroup= NULL;
|
|
pLogData->Line = pLogData->Buffer;
|
|
|
|
//
|
|
// Capture Format & Flags from the Request's Config Group. Or
|
|
// from the cache entries.
|
|
// There's a possiblity that the values inside the log entry
|
|
// maybe stale but that's acceptable. We do not want to acquire
|
|
// the log resource so that we can avoid the contention.
|
|
//
|
|
|
|
pLogData->Format= pConfigGroup->LoggingConfig.LogFormat;
|
|
pLogData->Flags = UL_GET_LOG_TYPE_MASK(
|
|
pConfigGroup->LoggingConfig.LogFormat,
|
|
pConfigGroup->LoggingConfig.LogExtFileFlags
|
|
);
|
|
|
|
pLogData->Used= 0; // For NCSA & W3C this is size of the line.
|
|
pLogData->Length= 0; // Allocation length,the default is 4k
|
|
pLogData->UsedOffset1= 0; // Used by all formats.
|
|
pLogData->UsedOffset2= 0; // This SHOULD only be nonzero if format is IIS
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
UlDestroyLogDataBuffer :
|
|
|
|
After we are done with writing this record we have to clean up
|
|
the internal log buffer here.
|
|
|
|
Arguments:
|
|
|
|
pLogData - The buffer to be destroyed
|
|
|
|
--***************************************************************************/
|
|
|
|
VOID
|
|
UlDestroyLogDataBufferWorker(
|
|
IN PUL_WORK_ITEM pWorkItem
|
|
)
|
|
{
|
|
PUL_LOG_DATA_BUFFER pLogData;
|
|
|
|
//
|
|
// Sanity check
|
|
//
|
|
|
|
ASSERT(pWorkItem);
|
|
|
|
pLogData = CONTAINING_RECORD(
|
|
pWorkItem,
|
|
UL_LOG_DATA_BUFFER,
|
|
WorkItem
|
|
);
|
|
|
|
//
|
|
// If we are keeping a private pointer to the cgroup release it
|
|
// as well
|
|
//
|
|
|
|
if (pLogData->pConfigGroup)
|
|
{
|
|
DEREFERENCE_CONFIG_GROUP(pLogData->pConfigGroup);
|
|
pLogData->pConfigGroup = NULL;
|
|
}
|
|
|
|
//
|
|
// Now release the possibly allocated large log line buffer
|
|
//
|
|
|
|
if (pLogData->Length > UL_LOG_LINE_BUFFER_SIZE)
|
|
{
|
|
// Large log line get allocated from paged pool
|
|
// we better be running on lowered IRQL for this case.
|
|
|
|
PAGED_CODE();
|
|
|
|
UL_FREE_POOL(pLogData->Line, UL_LOG_DATA_BUFFER_POOL_TAG);
|
|
}
|
|
|
|
//
|
|
// Last release our pointer to request structure here.
|
|
//
|
|
|
|
if (pLogData->pRequest)
|
|
{
|
|
PUL_INTERNAL_REQUEST pRequest;
|
|
|
|
pRequest = pLogData->pRequest;
|
|
pLogData->pRequest = NULL;
|
|
|
|
UL_DEREFERENCE_INTERNAL_REQUEST(pRequest);
|
|
}
|
|
|
|
UlTrace(LOGGING,("Ul!UlpDestroyLogDataBufferWorker: pLogData %p \n",
|
|
pLogData
|
|
));
|
|
|
|
}
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Captures and writes the log fields from user (WP) buffer to the log line.
|
|
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.
|
|
Does SpaceToPlus conversion for UserAgent, Cookie, Referrer & Host.
|
|
|
|
Arguments:
|
|
|
|
pLogData : User Buffer which holds the fields and their lengths
|
|
Version : Version information from Request
|
|
pLogBuffer : Structure which holds final log line and additional
|
|
information.
|
|
|
|
--***************************************************************************/
|
|
|
|
NTSTATUS
|
|
UlCaptureLogFieldsW3C(
|
|
IN PHTTP_LOG_FIELDS_DATA pLogData,
|
|
IN HTTP_VERSION Version,
|
|
OUT PUL_LOG_DATA_BUFFER pLogBuffer
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
ULONG Flags;
|
|
PCHAR psz;
|
|
PCHAR pBuffer;
|
|
ULONG BytesConverted;
|
|
ULONG FastLength;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT(pLogBuffer);
|
|
|
|
Status = STATUS_SUCCESS;
|
|
Flags = pLogBuffer->Flags;
|
|
BytesConverted = 0;
|
|
|
|
//
|
|
// Try fast length calculation for the the default case. If this fails
|
|
// Recalc function will precisely calculate the required length by
|
|
// paying attention to the picked flags.
|
|
//
|
|
|
|
FastLength = pLogData->ClientIpLength
|
|
+ pLogData->UserNameLength
|
|
+ pLogData->ServiceNameLength
|
|
+ pLogData->ServerNameLength
|
|
+ pLogData->ServerIpLength
|
|
+ pLogData->MethodLength
|
|
+ pLogData->UriStemLength
|
|
+ pLogData->UriQueryLength
|
|
+ pLogData->UserAgentLength
|
|
+ pLogData->CookieLength
|
|
+ pLogData->ReferrerLength
|
|
+ pLogData->HostLength
|
|
+ MAX_W3C_FIX_FIELD_OVERHEAD
|
|
;
|
|
if (UTF8_LOGGING_ENABLED() || (FastLength > UL_LOG_LINE_BUFFER_SIZE))
|
|
{
|
|
FastLength = UlpCalcLogLineLengthW3C(
|
|
pLogData,
|
|
Flags,
|
|
(UTF8_LOGGING_ENABLED() ? 2 : 1)
|
|
);
|
|
if (FastLength > UL_LOG_LINE_BUFFER_SIZE)
|
|
{
|
|
Status = UlpReallocLogLine(pLogBuffer, FastLength);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
return Status;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Leave enough space for the date & time fields: "2000-01-31 00:12:23 "
|
|
// For W3C format the field "Used" shows the size of the log line we have filled.
|
|
// UsedOffset1 shows how much space we have saved for date and/or time logfields.
|
|
// UsedOffset2 shows the size of the log line which will be stored in the cache
|
|
// entry, not including reserved space for date & time and the logfields after
|
|
// ServerPort. Therefore we only store the fragment which starts from logfield
|
|
// UserName to ServerPort. Others will be generated per cache hit.
|
|
//
|
|
|
|
psz = pBuffer = &pLogBuffer->Line[0];
|
|
|
|
if ( Flags & MD_EXTLOG_DATE ) psz += 11;
|
|
if ( Flags & MD_EXTLOG_TIME ) psz += 9;
|
|
|
|
// Generate all the fields except BytesSend, BytesReceived & TimeTaken
|
|
// They will be added when the send is complete and if they are picked
|
|
// We will only copy the fields after this point to the cache entry.
|
|
// Lets set and remember the size of the fields we are discarding.
|
|
|
|
pLogBuffer->UsedOffset1 = (USHORT) DIFF(psz - pBuffer);
|
|
|
|
if ( Flags & MD_EXTLOG_SITE_NAME )
|
|
{
|
|
if (pLogData->ServiceNameLength)
|
|
{
|
|
psz = UlStrPrintStr(psz, pLogData->ServiceName,' ');
|
|
}
|
|
else
|
|
{
|
|
*psz++ = '-'; *psz++ = ' ';
|
|
}
|
|
}
|
|
|
|
if ( Flags & MD_EXTLOG_COMPUTER_NAME )
|
|
{
|
|
if (pLogData->ServerNameLength)
|
|
{
|
|
psz = UlStrPrintStr(psz, pLogData->ServerName,' ');
|
|
}
|
|
else
|
|
{
|
|
*psz++ = '-'; *psz++ = ' ';
|
|
}
|
|
}
|
|
|
|
if ( Flags & MD_EXTLOG_SERVER_IP )
|
|
{
|
|
if (pLogData->ServerIpLength)
|
|
{
|
|
psz = UlStrPrintStr(psz, pLogData->ServerIp,' ');
|
|
}
|
|
else
|
|
{
|
|
*psz++ = '-'; *psz++ = ' ';
|
|
}
|
|
}
|
|
|
|
if ( Flags & MD_EXTLOG_METHOD )
|
|
{
|
|
if (pLogData->MethodLength)
|
|
{
|
|
psz = UlStrPrintStr(psz, pLogData->Method,' ');
|
|
}
|
|
else
|
|
{
|
|
*psz++ = '-'; *psz++ = ' ';
|
|
}
|
|
}
|
|
|
|
if ( Flags & MD_EXTLOG_URI_STEM )
|
|
{
|
|
BytesConverted = 0;
|
|
if (pLogData->UriStemLength)
|
|
{
|
|
PCHAR pszT = psz;
|
|
|
|
if (UTF8_LOGGING_ENABLED())
|
|
{
|
|
BytesConverted =
|
|
HttpUnicodeToUTF8(
|
|
pLogData->UriStem,
|
|
pLogData->UriStemLength/sizeof(WCHAR),
|
|
psz,
|
|
pLogData->UriStemLength * 2
|
|
);
|
|
ASSERT(BytesConverted);
|
|
}
|
|
else
|
|
{
|
|
RtlUnicodeToMultiByteN(
|
|
psz,
|
|
pLogData->UriStemLength,
|
|
&BytesConverted,
|
|
pLogData->UriStem,
|
|
pLogData->UriStemLength
|
|
);
|
|
}
|
|
psz += BytesConverted;
|
|
|
|
// Do SpeaceToPlus conversion before writting out
|
|
// the terminator space.
|
|
|
|
while (pszT != psz)
|
|
{
|
|
if (*pszT == ' ') *pszT = '+';
|
|
pszT++;
|
|
}
|
|
|
|
*psz++ = ' ';
|
|
|
|
}
|
|
else
|
|
{
|
|
*psz++ = '-'; *psz++ = ' ';
|
|
}
|
|
|
|
}
|
|
|
|
if ( Flags & MD_EXTLOG_URI_QUERY )
|
|
{
|
|
if (pLogData->UriQueryLength)
|
|
{
|
|
psz = UlStrPrintStr(psz, pLogData->UriQuery,' ');
|
|
}
|
|
else
|
|
{
|
|
*psz++ = '-'; *psz++ = ' ';
|
|
}
|
|
}
|
|
|
|
if ( Flags & MD_EXTLOG_HTTP_STATUS )
|
|
{
|
|
psz = UlStrPrintProtocolStatus(psz,(USHORT)pLogData->ProtocolStatus,' ');
|
|
}
|
|
|
|
if ( Flags & MD_EXTLOG_WIN32_STATUS )
|
|
{
|
|
psz = UlStrPrintUlong(psz, pLogData->Win32Status,' ');
|
|
}
|
|
|
|
if ( Flags & MD_EXTLOG_SERVER_PORT )
|
|
{
|
|
psz = UlStrPrintUlong(psz, pLogData->ServerPort,' ');
|
|
}
|
|
|
|
//
|
|
// Cache builder won't be storing the fields after this line.
|
|
// They have to be generated per hit for cache hits.
|
|
//
|
|
|
|
pLogBuffer->UsedOffset2 = (USHORT) DIFF(psz - pBuffer);
|
|
|
|
if ( Flags & MD_EXTLOG_USERNAME )
|
|
{
|
|
BytesConverted = 0;
|
|
if (pLogData->UserNameLength)
|
|
{
|
|
// Do either UTF8 or LocalCodePage Conversion
|
|
// not including the terminating null.
|
|
|
|
PCHAR pszT = psz;
|
|
|
|
if (UTF8_LOGGING_ENABLED())
|
|
{
|
|
// UTF8 Conversion may require upto two times because of a
|
|
// possible 2 byte to 4 byte conversion.
|
|
|
|
BytesConverted =
|
|
HttpUnicodeToUTF8(
|
|
pLogData->UserName,
|
|
pLogData->UserNameLength/sizeof(WCHAR),
|
|
psz,
|
|
pLogData->UserNameLength * 2
|
|
);
|
|
ASSERT(BytesConverted);
|
|
}
|
|
else
|
|
{
|
|
// 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
|
|
|
|
RtlUnicodeToMultiByteN(
|
|
psz,
|
|
pLogData->UserNameLength,
|
|
&BytesConverted,
|
|
pLogData->UserName,
|
|
pLogData->UserNameLength
|
|
);
|
|
}
|
|
|
|
// Forward the psz by BytesConverted
|
|
psz += BytesConverted;
|
|
|
|
// Do SpaceToPlus conversion
|
|
while (pszT != psz)
|
|
{
|
|
if (*pszT == ' ') *pszT = '+';
|
|
pszT++;
|
|
}
|
|
|
|
*psz++ = ' ';
|
|
|
|
}
|
|
else
|
|
{
|
|
*psz++ = '-'; *psz++ = ' ';
|
|
}
|
|
|
|
}
|
|
|
|
if ( Flags & MD_EXTLOG_CLIENT_IP )
|
|
{
|
|
if (pLogData->ClientIpLength)
|
|
{
|
|
psz = UlStrPrintStr(psz, pLogData->ClientIp,' ');
|
|
}
|
|
else
|
|
{
|
|
*psz++ = '-'; *psz++ = ' ';
|
|
}
|
|
}
|
|
|
|
if ( Flags & MD_EXTLOG_PROTOCOL_VERSION )
|
|
{
|
|
psz = UlStrPrintStr(psz, UL_GET_NAME_FOR_HTTP_VERSION(Version),' ');
|
|
}
|
|
|
|
if ( Flags & MD_EXTLOG_USER_AGENT )
|
|
{
|
|
if (pLogData->UserAgentLength)
|
|
{
|
|
psz = UlStrPrintStrC(psz, pLogData->UserAgent,' ');
|
|
}
|
|
else
|
|
{
|
|
*psz++ = '-'; *psz++ = ' ';
|
|
}
|
|
}
|
|
|
|
if ( Flags & MD_EXTLOG_COOKIE )
|
|
{
|
|
if (pLogData->CookieLength)
|
|
{
|
|
psz = UlStrPrintStrC(psz, pLogData->Cookie,' ');
|
|
}
|
|
else
|
|
{
|
|
*psz++ = '-'; *psz++ = ' ';
|
|
}
|
|
}
|
|
|
|
if ( Flags & MD_EXTLOG_REFERER )
|
|
{
|
|
if (pLogData->ReferrerLength)
|
|
{
|
|
psz = UlStrPrintStrC(psz, pLogData->Referrer,' ');
|
|
}
|
|
else
|
|
{
|
|
*psz++ = '-'; *psz++ = ' ';
|
|
}
|
|
}
|
|
|
|
if ( Flags & MD_EXTLOG_HOST )
|
|
{
|
|
if (pLogData->HostLength)
|
|
{
|
|
psz = UlStrPrintStrC(psz, pLogData->Host,' ');
|
|
}
|
|
else
|
|
{
|
|
*psz++ = '-'; *psz++ = ' ';
|
|
}
|
|
}
|
|
|
|
// Finally calculate the used space
|
|
|
|
pLogBuffer->Used = (ULONG) DIFF(psz - pBuffer);
|
|
|
|
// Date & Time fields will be filled in when the LogHit completes.
|
|
// As well as the fields BytesSent,BytesReceived and TimeTaken will
|
|
// be added to the end of the log line, then.
|
|
|
|
UlTrace(LOGGING, ("Ul!UlCaptureLogFields: user %p kernel %p\n",
|
|
pLogData,pLogBuffer
|
|
));
|
|
return Status;
|
|
|
|
}
|
|
|
|
NTSTATUS
|
|
UlCaptureLogFieldsNCSA(
|
|
IN PHTTP_LOG_FIELDS_DATA pLogData,
|
|
IN HTTP_VERSION Version,
|
|
OUT PUL_LOG_DATA_BUFFER pLogBuffer
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
ULONG BytesConverted;
|
|
PCHAR psz;
|
|
PCHAR pBuffer;
|
|
ULONG Utf8Multiplier;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT(pLogBuffer);
|
|
|
|
Status = STATUS_SUCCESS;
|
|
Utf8Multiplier = (UTF8_LOGGING_ENABLED() ? 2 : 1);
|
|
|
|
// Estimate the length and reallocate the log data buffer line
|
|
// if necessary
|
|
|
|
pLogBuffer->Length =
|
|
2 + pLogData->ClientIpLength +
|
|
2 + // For remote user log name
|
|
2 + pLogData->UserNameLength * Utf8Multiplier +
|
|
29 + // Fixed Date & Time Space
|
|
2 + pLogData->MethodLength + // "MTHD U-STEM?U-QUERY P-VER"
|
|
2 + pLogData->UriStemLength * Utf8Multiplier +
|
|
2 + pLogData->UriQueryLength +
|
|
2 + UL_HTTP_VERSION_LENGTH + // Version plus Quotes ""
|
|
2 + MAX_ULONG_STR + // ProtocolStatus
|
|
2 + MAX_ULONGLONG_STR + // BytesSend
|
|
3 // \r\n\0
|
|
;
|
|
|
|
if ( pLogBuffer->Length > UL_LOG_LINE_BUFFER_SIZE )
|
|
{
|
|
Status = UlpReallocLogLine(pLogBuffer, pLogBuffer->Length);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
return Status;
|
|
}
|
|
}
|
|
|
|
//
|
|
// UCIP - username [date:time offset] "MTHD U-STEM?U-QUERY P-VER" Status BSent
|
|
//
|
|
|
|
// Set and remember the beginning
|
|
psz = pBuffer = &pLogBuffer->Line[0];
|
|
|
|
// Client IP
|
|
if (pLogData->ClientIpLength)
|
|
{
|
|
psz = UlStrPrintStr(psz, pLogData->ClientIp,' ');
|
|
}
|
|
else
|
|
{
|
|
*psz++ = '-'; *psz++ = ' ';
|
|
}
|
|
|
|
// Fixed dash
|
|
*psz++ = '-'; *psz++ = ' ';
|
|
|
|
// UserName
|
|
if (pLogData->UserNameLength)
|
|
{
|
|
BytesConverted = 0;
|
|
if (UTF8_LOGGING_ENABLED())
|
|
{
|
|
BytesConverted =
|
|
HttpUnicodeToUTF8(
|
|
pLogData->UserName,
|
|
pLogData->UserNameLength/sizeof(WCHAR),
|
|
psz,
|
|
pLogData->UserNameLength * 2
|
|
);
|
|
ASSERT(BytesConverted);
|
|
}
|
|
else
|
|
{
|
|
RtlUnicodeToMultiByteN(
|
|
psz,
|
|
pLogData->UserNameLength,
|
|
&BytesConverted,
|
|
pLogData->UserName,
|
|
pLogData->UserNameLength
|
|
);
|
|
}
|
|
psz += BytesConverted; *psz++ = ' ';
|
|
|
|
}
|
|
else
|
|
{
|
|
*psz++ = '-'; *psz++ = ' ';
|
|
}
|
|
|
|
// [Date:Time GmtOffset] -> "[07/Jan/2000:00:02:23 -0800] "
|
|
// Just leave the space for the time being. But remember the
|
|
// offset of the beginning of the reserved space of 29 bytes.
|
|
|
|
pLogBuffer->UsedOffset1 = (USHORT) DIFF(psz - pBuffer);
|
|
|
|
// Forward psz to bypass the reserved space
|
|
psz += NCSA_FIX_DATE_AND_TIME_FIELD_SIZE;
|
|
|
|
// "MTHD U-STEM?U-QUERY P-VER"
|
|
|
|
*psz++ = '\"';
|
|
if (pLogData->MethodLength)
|
|
{
|
|
psz = UlStrPrintStr(psz, pLogData->Method,' ');
|
|
}
|
|
else
|
|
{
|
|
*psz++ = '-'; *psz++ = ' ';
|
|
}
|
|
|
|
if (pLogData->UriStemLength)
|
|
{
|
|
BytesConverted = 0;
|
|
if (UTF8_LOGGING_ENABLED())
|
|
{
|
|
BytesConverted =
|
|
HttpUnicodeToUTF8(
|
|
pLogData->UriStem,
|
|
pLogData->UriStemLength/sizeof(WCHAR),
|
|
psz,
|
|
pLogData->UriStemLength * 2
|
|
);
|
|
ASSERT(BytesConverted);
|
|
}
|
|
else
|
|
{
|
|
RtlUnicodeToMultiByteN(
|
|
psz,
|
|
pLogData->UriStemLength,
|
|
&BytesConverted,
|
|
pLogData->UriStem,
|
|
pLogData->UriStemLength
|
|
);
|
|
}
|
|
psz += BytesConverted;
|
|
|
|
}
|
|
else
|
|
{
|
|
*psz++ = '-';
|
|
}
|
|
|
|
if (pLogData->UriQueryLength)
|
|
{
|
|
*psz++ = '?';
|
|
psz = UlStrPrintStr(psz, pLogData->UriQuery,' ');
|
|
}
|
|
else
|
|
{
|
|
*psz++ = ' ';
|
|
}
|
|
|
|
pLogBuffer->UsedOffset2 = (USHORT) DIFF(psz - pBuffer);
|
|
|
|
psz = UlStrPrintStr(psz, UL_GET_NAME_FOR_HTTP_VERSION(Version),'\"');
|
|
*psz++ = ' ';
|
|
|
|
// ProtocolStatus
|
|
psz = UlStrPrintProtocolStatus(psz, (USHORT)pLogData->ProtocolStatus,' ');
|
|
|
|
// Calculate the length upto now
|
|
pLogBuffer->Used += (ULONG) DIFF(psz - pBuffer);
|
|
|
|
// BytesSend will be filled later on during hit processing
|
|
|
|
return Status;
|
|
|
|
}
|
|
|
|
NTSTATUS
|
|
UlCaptureLogFieldsIIS(
|
|
IN PHTTP_LOG_FIELDS_DATA pLogData,
|
|
IN HTTP_VERSION Version,
|
|
OUT PUL_LOG_DATA_BUFFER pLogBuffer
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
ULONG BytesConverted;
|
|
PCHAR psz;
|
|
PCHAR pBuffer;
|
|
ULONG Utf8Multiplier;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
ASSERT(pLogBuffer);
|
|
|
|
Status = STATUS_SUCCESS;
|
|
Utf8Multiplier = (UTF8_LOGGING_ENABLED() ? 2 : 1);
|
|
|
|
// Estimate the length and reallocate the log data buffer line
|
|
// if necessary
|
|
|
|
pLogBuffer->Length =
|
|
2 + pLogData->ClientIpLength +
|
|
2 + pLogData->UserNameLength * Utf8Multiplier +
|
|
22 + // Fixed Date & Time Space
|
|
2 + pLogData->ServiceNameLength +
|
|
2 + pLogData->ServerNameLength +
|
|
2 + pLogData->ServerIpLength +
|
|
2 + MAX_ULONGLONG_STR + // TimeTaken
|
|
2 + MAX_ULONGLONG_STR + // BytesReceived
|
|
2 + MAX_ULONGLONG_STR + // BytesSend
|
|
2 + MAX_ULONG_STR + // ProtocolStatus
|
|
2 + MAX_ULONG_STR + // Win32 Status
|
|
2 + pLogData->MethodLength +
|
|
2 + pLogData->UriStemLength * Utf8Multiplier +
|
|
2 + pLogData->UriQueryLength +
|
|
3 // \r\n\0
|
|
;
|
|
|
|
if ( pLogBuffer->Length > UL_LOG_LINE_BUFFER_SIZE )
|
|
{
|
|
// TODO: Fix this and do the limit checks per field
|
|
|
|
if (pLogBuffer->Length > UL_MAX_LOG_LINE_BUFFER_SIZE)
|
|
{
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
Status = UlpReallocLogLine(pLogBuffer, UL_MAX_LOG_LINE_BUFFER_SIZE);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
return Status;
|
|
}
|
|
}
|
|
|
|
//
|
|
// UIP,user,D,T,site,Server,SIP,Ttaken,BR,BS,PS,WS,M,URI,URIQUERY,
|
|
//
|
|
|
|
// We will store the fragmented IIS log line as follows. If captured fields
|
|
// won't fit to the default 4k buffer the fragments will be increased by mul
|
|
// tiplier of 2 until they fit. But this calculation happened earlier and now
|
|
// we know the actual size of each fragment. All the available fields will be
|
|
// written to the beginning of their corresponding fragments and unavailable ones
|
|
// for the first two fragments ( i.e. date and time for 1 and timetaken
|
|
// bytesreceived and bytessent for 2, none for the three ) will be appended later
|
|
// when the hit happens. To be able to do that we will remember used_offsets for
|
|
// fragments one and two, both in the log_data and in the cache entry. But for this
|
|
// schema we need a special LogWriter which expects and handles 3 fragments rather
|
|
// than one complete line.
|
|
|
|
// <- upto time -> <- from siteName to BytesSent -> <- From p status to query ->
|
|
// 0 511 512 1023 1024 4096
|
|
|
|
// FRAGMENT ONE
|
|
// -----------------------------------------------------
|
|
// Cache entry will not store this fragment, the fields in
|
|
// here have to be regenerated for the (pure) cache hits.
|
|
|
|
psz = pBuffer = &pLogBuffer->Line[0];
|
|
|
|
// Client IP
|
|
if (pLogData->ClientIpLength)
|
|
{
|
|
psz = UlStrPrintStr(psz, pLogData->ClientIp,',');
|
|
}
|
|
else
|
|
{
|
|
*psz++ = '-'; *psz++ = ',';
|
|
}
|
|
*psz++ = ' ';
|
|
|
|
// UserName
|
|
if (pLogData->UserNameLength)
|
|
{
|
|
BytesConverted = 0;
|
|
if (UTF8_LOGGING_ENABLED())
|
|
{
|
|
BytesConverted =
|
|
HttpUnicodeToUTF8(
|
|
pLogData->UserName,
|
|
pLogData->UserNameLength/sizeof(WCHAR),
|
|
psz,
|
|
pLogData->UserNameLength * 2
|
|
);
|
|
ASSERT(BytesConverted);
|
|
}
|
|
else
|
|
{
|
|
RtlUnicodeToMultiByteN(
|
|
psz,
|
|
pLogData->UserNameLength,
|
|
&BytesConverted,
|
|
pLogData->UserName,
|
|
pLogData->UserNameLength
|
|
);
|
|
}
|
|
psz += BytesConverted; *psz++ = ',';
|
|
|
|
}
|
|
else
|
|
{
|
|
*psz++ = '-'; *psz++ = ',';
|
|
}
|
|
*psz++ = ' ';
|
|
|
|
// Date and Time will be added later to the end of this fragment.
|
|
|
|
pLogBuffer->UsedOffset1 = (USHORT) DIFF(psz - pBuffer);
|
|
|
|
|
|
// FRAGMENT TWO
|
|
// -----------------------------------------------------
|
|
|
|
pBuffer = psz = &pLogBuffer->Line[512];
|
|
|
|
// SiteName
|
|
if (pLogData->ServiceNameLength)
|
|
{
|
|
psz = UlStrPrintStr(psz, pLogData->ServiceName,',');
|
|
}
|
|
else
|
|
{
|
|
*psz++ = '-'; *psz++ = ',';
|
|
}
|
|
*psz++ = ' ';
|
|
|
|
// ServerName
|
|
if (pLogData->ServerNameLength)
|
|
{
|
|
psz = UlStrPrintStr(psz, pLogData->ServerName,',');
|
|
}
|
|
else
|
|
{
|
|
*psz++ = '-'; *psz++ = ',';
|
|
}
|
|
*psz++ = ' ';
|
|
|
|
// ServerIp
|
|
if (pLogData->ServerIpLength)
|
|
{
|
|
psz = UlStrPrintStr(psz, pLogData->ServerIp,',');
|
|
}
|
|
else
|
|
{
|
|
*psz++ = '-'; *psz++ = ',';
|
|
}
|
|
*psz++ = ' ';
|
|
|
|
// TimeTaken BytesSent and BytesReceived will be added later
|
|
// to the end of this fragment
|
|
|
|
pLogBuffer->UsedOffset2 = (USHORT) DIFF(psz - pBuffer);
|
|
|
|
|
|
// FRAGMENT THREE
|
|
// -----------------------------------------------------
|
|
|
|
pBuffer = psz = &pLogBuffer->Line[1024];
|
|
|
|
// ProtocolStatus
|
|
psz = UlStrPrintProtocolStatus(psz, (USHORT)pLogData->ProtocolStatus,',');
|
|
*psz++ = ' ';
|
|
|
|
// Win32 Status
|
|
psz = UlStrPrintUlong(psz, pLogData->Win32Status,',');
|
|
*psz++ = ' ';
|
|
|
|
// Method
|
|
if (pLogData->MethodLength)
|
|
{
|
|
psz = UlStrPrintStr(psz, pLogData->Method,',');
|
|
}
|
|
else
|
|
{
|
|
*psz++ = '-'; *psz++ = ',';
|
|
}
|
|
*psz++ = ' ';
|
|
|
|
// URI Stem
|
|
if (pLogData->UriStemLength)
|
|
{
|
|
BytesConverted = 0;
|
|
if (UTF8_LOGGING_ENABLED())
|
|
{
|
|
BytesConverted =
|
|
HttpUnicodeToUTF8(
|
|
pLogData->UriStem,
|
|
pLogData->UriStemLength/sizeof(WCHAR),
|
|
psz,
|
|
pLogData->UriStemLength * 2
|
|
);
|
|
ASSERT(BytesConverted);
|
|
}
|
|
else
|
|
{
|
|
RtlUnicodeToMultiByteN(
|
|
psz,
|
|
pLogData->UriStemLength,
|
|
&BytesConverted,
|
|
pLogData->UriStem,
|
|
pLogData->UriStemLength
|
|
);
|
|
}
|
|
psz += BytesConverted; *psz++ = ',';
|
|
|
|
}
|
|
else
|
|
{
|
|
*psz++ = '-'; *psz++ = ',';
|
|
}
|
|
*psz++ = ' ';
|
|
|
|
// URI Query
|
|
if (pLogData->UriQueryLength)
|
|
{
|
|
psz = UlStrPrintStr(psz, pLogData->UriQuery,',');
|
|
}
|
|
else
|
|
{
|
|
*psz++ = '-'; *psz++ = ',';
|
|
}
|
|
*psz++ = '\r'; *psz++ = '\n';
|
|
|
|
// The size of the fragment 3 goes to Used
|
|
pLogBuffer->Used += (ULONG) DIFF(psz - pBuffer);
|
|
|
|
*psz++ = ANSI_NULL;
|
|
|
|
return Status;
|
|
}
|
|
|
|
/***************************************************************************++
|
|
|
|
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.
|
|
|
|
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 ).
|
|
|
|
Unfortunately the major concern is untimely resetting of the connection
|
|
(when client terminates the connection before we get a chance to reach
|
|
to the -above- mentioned places) In that case UlConnectionDestroyedWorker
|
|
will asynchrously destroy our pRequest pointer ( in the pHttpConnection
|
|
structure ) and cause us to miss log hits. So we cannot trust on
|
|
HttpConnection for pRequest pointer. To solve this issue we keeep our
|
|
own pointer to Request. No need to worry about pResponse as it's going
|
|
to be preserved by chunk_trucker until its own destruction.
|
|
|
|
Arguments:
|
|
|
|
pResponse - pointer to the internal response structure, surely
|
|
passed down by the chunk tracker. We will grap our Log Data
|
|
buffer from this structure which originaly allocated when
|
|
capturing the Response from the user.
|
|
See UlCaptureHttpResponse.
|
|
|
|
--***************************************************************************/
|
|
|
|
NTSTATUS
|
|
UlLogHttpHit(
|
|
IN PUL_LOG_DATA_BUFFER pLogBuffer
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
PUL_CONFIG_GROUP_OBJECT pConfigGroup;
|
|
PUL_INTERNAL_REQUEST pRequest;
|
|
PCHAR psz;
|
|
PCHAR pBuffer;
|
|
ULONG BytesWritten;
|
|
LONGLONG LifeTime;
|
|
LARGE_INTEGER CurrentTimeStamp;
|
|
PUL_LOG_FILE_ENTRY pEntry;
|
|
|
|
//
|
|
// A LOT of sanity checks.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
UlTrace( LOGGING, ("Ul!UlLogHttpHit: pLogData %p\n", pLogBuffer ));
|
|
|
|
ASSERT(pLogBuffer);
|
|
|
|
pRequest = pLogBuffer->pRequest;
|
|
ASSERT(UL_IS_VALID_INTERNAL_REQUEST(pRequest));
|
|
|
|
if (pRequest->ConfigInfo.pLoggingConfig == NULL ||
|
|
IS_LOGGING_DISABLED(pRequest->ConfigInfo.pLoggingConfig)
|
|
)
|
|
{
|
|
//
|
|
// If logging is disabled or log settings don't
|
|
// exist then do not proceed. Just exit out.
|
|
//
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
pConfigGroup = pRequest->ConfigInfo.pLoggingConfig;
|
|
ASSERT(IS_VALID_CONFIG_GROUP(pConfigGroup));
|
|
|
|
//
|
|
// Construct the remaining log fields
|
|
//
|
|
|
|
switch(pLogBuffer->Format)
|
|
{
|
|
case HttpLoggingTypeW3C:
|
|
{
|
|
// First write the date & time fields to the beginning reserved
|
|
// space. Do not increment the used counter for date & time, b/c
|
|
// CaptureLogFields already did this when reserving the space.
|
|
|
|
psz = &pLogBuffer->Line[0];
|
|
if ( pLogBuffer->Flags & MD_EXTLOG_DATE )
|
|
{
|
|
UlpGetDateTimeFields(
|
|
HttpLoggingTypeW3C,
|
|
psz,
|
|
&BytesWritten,
|
|
NULL,
|
|
NULL
|
|
);
|
|
psz += BytesWritten; *psz++ = ' ';
|
|
ASSERT(BytesWritten == 10);
|
|
}
|
|
|
|
if ( pLogBuffer->Flags & MD_EXTLOG_TIME )
|
|
{
|
|
UlpGetDateTimeFields(
|
|
HttpLoggingTypeW3C,
|
|
NULL,
|
|
NULL,
|
|
psz,
|
|
&BytesWritten
|
|
);
|
|
psz += BytesWritten; *psz++ = ' ';
|
|
ASSERT(BytesWritten == 8);
|
|
}
|
|
|
|
// Set and remember where we started
|
|
pBuffer = psz = &pLogBuffer->Line[pLogBuffer->Used];
|
|
|
|
// Now proceed with the remaining fields, but add them to the end.
|
|
|
|
if ( pLogBuffer->Flags & MD_EXTLOG_BYTES_SENT )
|
|
{
|
|
psz = UlStrPrintUlonglong(psz, pRequest->BytesSent,' ');
|
|
}
|
|
if ( pLogBuffer->Flags & MD_EXTLOG_BYTES_RECV )
|
|
{
|
|
psz = UlStrPrintUlonglong(psz, pRequest->BytesReceived,' ');
|
|
}
|
|
if ( pLogBuffer->Flags & MD_EXTLOG_TIME_TAKEN )
|
|
{
|
|
KeQuerySystemTime( &CurrentTimeStamp );
|
|
LifeTime = CurrentTimeStamp.QuadPart - pRequest->TimeStamp.QuadPart;
|
|
|
|
if (LifeTime < 0)
|
|
{
|
|
LifeTime = 0;
|
|
UlTrace(LOGGING, ("CopyTimeStampField failure.\n"));
|
|
}
|
|
LifeTime /= (10*1000); // Conversion from 100-nanosecond to millisecs.
|
|
|
|
psz = UlStrPrintUlonglong(psz, (ULONGLONG)LifeTime,' ');
|
|
}
|
|
|
|
// Now calculate the space we have used
|
|
|
|
pLogBuffer->Used += (ULONG) DIFF(psz - pBuffer);
|
|
|
|
// Eat the last space and write the \r\n to the end.
|
|
// Only if we have any fields picked and written
|
|
|
|
if (pLogBuffer->Used)
|
|
{
|
|
psz = &pLogBuffer->Line[pLogBuffer->Used-1]; // Eat the last space
|
|
*psz++ = '\r'; *psz++ = '\n'; *psz++ = ANSI_NULL;
|
|
|
|
pLogBuffer->Used += 1;
|
|
}
|
|
else
|
|
{
|
|
return STATUS_SUCCESS; // No log fields nothing to log
|
|
}
|
|
|
|
// Cleanup the UsedOffsets otherwise length calculation will
|
|
// fail down below.
|
|
|
|
pLogBuffer->UsedOffset1 = pLogBuffer->UsedOffset2 = 0;
|
|
}
|
|
break;
|
|
|
|
case HttpLoggingTypeNCSA:
|
|
{
|
|
// [date:time GmtOffset] -> "[07/Jan/2000:00:02:23 -0800] "
|
|
// Restore the pointer to the reserved space first.
|
|
|
|
psz = &pLogBuffer->Line[pLogBuffer->UsedOffset1];
|
|
*psz++ = '[';
|
|
|
|
UlpGetDateTimeFields(
|
|
HttpLoggingTypeNCSA,
|
|
psz,
|
|
&BytesWritten,
|
|
NULL,
|
|
NULL
|
|
);
|
|
psz += BytesWritten; *psz++ = ':';
|
|
ASSERT(BytesWritten == 11);
|
|
|
|
UlpGetDateTimeFields(
|
|
HttpLoggingTypeNCSA,
|
|
NULL,
|
|
NULL,
|
|
psz,
|
|
&BytesWritten
|
|
);
|
|
psz += BytesWritten; *psz++ = ' ';
|
|
ASSERT(BytesWritten == 8);
|
|
|
|
UlAcquireResourceShared(&g_pUlNonpagedData->LogListResource, TRUE);
|
|
psz = UlStrPrintStr(psz, g_GMTOffset,']');
|
|
UlReleaseResource(&g_pUlNonpagedData->LogListResource);
|
|
*psz++ = ' ';
|
|
|
|
ASSERT(((ULONG) DIFF(psz - &pLogBuffer->Line[pLogBuffer->UsedOffset1])) == 29);
|
|
|
|
// BytesSent
|
|
|
|
pBuffer = psz = &pLogBuffer->Line[pLogBuffer->Used];
|
|
psz = UlStrPrintUlonglong(psz, pRequest->BytesSent,'\r');
|
|
pLogBuffer->Used += (ULONG) DIFF(psz - pBuffer);
|
|
|
|
// \n\0
|
|
|
|
*psz++ = '\n'; *psz++ = ANSI_NULL;
|
|
pLogBuffer->Used += 1;
|
|
|
|
// Cleanup the UsedOffsets otherwise length calculation will
|
|
// fail down below.
|
|
|
|
pLogBuffer->UsedOffset1 = pLogBuffer->UsedOffset2 = 0;
|
|
}
|
|
break;
|
|
|
|
case HttpLoggingTypeIIS:
|
|
{
|
|
// At this time to construct the IIS log line is slightly
|
|
// different than the cache completion case. We can either
|
|
// use memmoves to get rid of the gaps between the three
|
|
// fragments. Or we provide a special log writer to actually
|
|
// handle three fragments when writting to the final log buffer
|
|
// I have picked the latter.
|
|
|
|
// Complete Fragment 1
|
|
|
|
pBuffer = psz = &pLogBuffer->Line[pLogBuffer->UsedOffset1];
|
|
|
|
UlpGetDateTimeFields(
|
|
HttpLoggingTypeIIS,
|
|
psz,
|
|
&BytesWritten,
|
|
NULL,
|
|
NULL
|
|
);
|
|
psz += BytesWritten; *psz++ = ','; *psz++ = ' ';
|
|
|
|
UlpGetDateTimeFields(
|
|
HttpLoggingTypeIIS,
|
|
NULL,
|
|
NULL,
|
|
psz,
|
|
&BytesWritten
|
|
);
|
|
psz += BytesWritten; *psz++ = ','; *psz++ = ' ';
|
|
|
|
pLogBuffer->UsedOffset1 += (USHORT) DIFF(psz - pBuffer);
|
|
|
|
|
|
// Complete Fragment 2
|
|
|
|
pBuffer = psz = &pLogBuffer->Line[512 + pLogBuffer->UsedOffset2];
|
|
|
|
KeQuerySystemTime( &CurrentTimeStamp );
|
|
LifeTime = CurrentTimeStamp.QuadPart - pRequest->TimeStamp.QuadPart;
|
|
if (LifeTime < 0)
|
|
{
|
|
LifeTime = 0;
|
|
UlTrace(LOGGING, ("CopyTimeStampField: failure.\n"));
|
|
}
|
|
LifeTime /= (10*1000); // Conversion from 100-nanosecond to millisecs.
|
|
|
|
psz = UlStrPrintUlonglong(psz, (ULONGLONG)LifeTime,',');
|
|
*psz++ = ' ';
|
|
|
|
psz = UlStrPrintUlonglong(psz, pRequest->BytesReceived,',');
|
|
*psz++ = ' ';
|
|
|
|
psz = UlStrPrintUlonglong(psz, pRequest->BytesSent,',');
|
|
*psz++ = ' ';
|
|
|
|
pLogBuffer->UsedOffset2 += (USHORT) DIFF(psz - pBuffer);
|
|
|
|
// Size of the final log line should be
|
|
// pLogBuffer->UsedOffset1 + pLogBuffer->UsedOffset2 + pLogBuffer->Used
|
|
// for IIS log format down below.
|
|
}
|
|
break;
|
|
|
|
default:
|
|
{
|
|
ASSERT(!"Unknown Log Format Type\n");
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Finally this log line is ready to rock. Lets write it out.
|
|
//
|
|
|
|
UlAcquireResourceShared(&g_pUlNonpagedData->LogListResource, TRUE);
|
|
|
|
//
|
|
// Unless otherwise there's a problem in the set config group Ioctl
|
|
// we should never come here with having a null entry pointer. Even
|
|
// in the case of log reconfiguration, cgroup should acquire the log
|
|
// list resource to update its entry pointer.
|
|
//
|
|
|
|
pEntry = pConfigGroup->pLogFileEntry;
|
|
|
|
if (pEntry)
|
|
{
|
|
ASSERT(IS_VALID_LOG_FILE_ENTRY(pEntry));
|
|
|
|
Status =
|
|
UlpWriteToLogFile (
|
|
pEntry,
|
|
pLogBuffer->UsedOffset1 + pLogBuffer->UsedOffset2 + pLogBuffer->Used,
|
|
&pLogBuffer->Line[0],
|
|
pLogBuffer->UsedOffset1,
|
|
pLogBuffer->UsedOffset2
|
|
);
|
|
}
|
|
#if DBG
|
|
else
|
|
{
|
|
//
|
|
// Practically every time we leak a url_cache entry we will not close
|
|
// the corresponding cgroup and the corresponding log entry. In that
|
|
// case next time we try to run http.sys we might see this assertion
|
|
// going on. Not usefull at this time because it's too late to cacth
|
|
// the original leak. Also if we run out of system resources when
|
|
// allocation the log file entry we will come here as well.
|
|
//
|
|
|
|
UlTrace(LOGGING,("Ul!UlLogHttpHit:null logfile entry !\n"));
|
|
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
UlTrace( LOGGING, ("Ul!UlLogHttpHit: entry %p, failure %08lx \n",
|
|
pEntry,
|
|
Status
|
|
));
|
|
}
|
|
#endif // DBG
|
|
|
|
UlReleaseResource(&g_pUlNonpagedData->LogListResource);
|
|
|
|
return Status;
|
|
|
|
}
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Queues a work item for the actual log hit function.
|
|
|
|
Arguments:
|
|
|
|
pTracker - Supplies the tracker to complete.
|
|
|
|
--***************************************************************************/
|
|
|
|
NTSTATUS
|
|
UlLogHttpCacheHit(
|
|
PUL_FULL_TRACKER pTracker
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
PUL_LOG_DATA_BUFFER pLogData;
|
|
ULONG NewLength;
|
|
PUL_INTERNAL_REQUEST pRequest;
|
|
|
|
//
|
|
// A Lot of sanity checks.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT(pTracker);
|
|
ASSERT(IS_VALID_FULL_TRACKER(pTracker));
|
|
ASSERT(pTracker->pLogData);
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
pLogData = pTracker->pLogData;
|
|
pLogData->BytesTransferred = pTracker->IoStatus.Information;
|
|
|
|
pRequest = pLogData->pRequest;
|
|
ASSERT(UL_IS_VALID_INTERNAL_REQUEST(pRequest));
|
|
|
|
UlTrace(LOGGING,("Ul!UlLogHttpCacheHit: pLogData %p\n",pTracker->pLogData));
|
|
|
|
//
|
|
// Restore the logging data from cache. If this hasn't been done by
|
|
// the BuildCacheEntry for cache&send responses.
|
|
//
|
|
|
|
if ( pLogData->CacheAndSendResponse == FALSE )
|
|
{
|
|
ASSERT(pTracker->pUriEntry);
|
|
|
|
// Make sure that internal buffer is big enough
|
|
// before proceeding.
|
|
|
|
// pLogData->Length = pTracker->pUriEntry->MaxLength;
|
|
|
|
switch( pLogData->Format )
|
|
{
|
|
case HttpLoggingTypeW3C:
|
|
{
|
|
// Recalculate the log line size to see if need to realloc
|
|
|
|
NewLength = UlpRecalcLogLineLengthW3C(
|
|
pLogData->Flags,
|
|
pLogData->pRequest,
|
|
pTracker->pUriEntry->LogDataLength
|
|
);
|
|
|
|
if ( NewLength > UL_LOG_LINE_BUFFER_SIZE )
|
|
{
|
|
Status = UlpReallocLogLine(pLogData, NewLength);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
// Worker won't get called. Cleanup immediately
|
|
UlDestroyLogDataBuffer(pLogData);
|
|
return Status;
|
|
}
|
|
}
|
|
|
|
if ( pTracker->pUriEntry->LogDataLength )
|
|
{
|
|
RtlCopyMemory( &pLogData->Line[pTracker->pUriEntry->UsedOffset1],
|
|
pTracker->pUriEntry->pLogData,
|
|
pTracker->pUriEntry->LogDataLength
|
|
);
|
|
}
|
|
|
|
// Cache data plus the reserved space for date and time
|
|
|
|
pLogData->Used = pTracker->pUriEntry->LogDataLength +
|
|
pTracker->pUriEntry->UsedOffset1;
|
|
}
|
|
break;
|
|
|
|
case HttpLoggingTypeNCSA:
|
|
{
|
|
PCHAR psz,pBuffer;
|
|
|
|
ASSERT( pTracker->pUriEntry->LogDataLength );
|
|
ASSERT( pTracker->pUriEntry->pLogData );
|
|
|
|
NewLength = MAX_NCSA_CACHE_FIELD_OVERHEAD
|
|
+ pTracker->pUriEntry->LogDataLength;
|
|
|
|
if ( NewLength > UL_LOG_LINE_BUFFER_SIZE )
|
|
{
|
|
Status = UlpReallocLogLine(pLogData, NewLength);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
// Worker won't get called. Cleanup immediately
|
|
UlDestroyLogDataBuffer(pLogData);
|
|
return Status;
|
|
}
|
|
}
|
|
|
|
psz = pBuffer = &pLogData->Line[0];
|
|
|
|
// Client IP
|
|
psz = UlStrPrintIP(
|
|
psz,
|
|
pRequest->pHttpConn->pConnection->RemoteAddress,
|
|
' '
|
|
);
|
|
|
|
// Fixed dash
|
|
*psz++ = '-'; *psz++ = ' ';
|
|
|
|
// Authenticated users cannot be served from cache.
|
|
*psz++ = '-'; *psz++ = ' ';
|
|
|
|
// Mark the beginning of the date & time fields
|
|
pLogData->UsedOffset1 = (USHORT) DIFF(psz - pBuffer);
|
|
|
|
// Forward psz to bypass the reserved space
|
|
psz += NCSA_FIX_DATE_AND_TIME_FIELD_SIZE;
|
|
|
|
RtlCopyMemory( psz,
|
|
pTracker->pUriEntry->pLogData,
|
|
pTracker->pUriEntry->LogDataLength
|
|
);
|
|
psz += pTracker->pUriEntry->LogDataLength;
|
|
|
|
// Protocol Version
|
|
psz = UlStrPrintStr(
|
|
psz,
|
|
UL_GET_NAME_FOR_HTTP_VERSION(pRequest->Version),
|
|
'\"'
|
|
);
|
|
*psz++ = ' ';
|
|
|
|
// ProtocolStatus
|
|
psz = UlStrPrintProtocolStatus(
|
|
psz,
|
|
pTracker->pUriEntry->StatusCode,
|
|
' '
|
|
);
|
|
|
|
// Calculate the length until now
|
|
pLogData->Used += (ULONG) DIFF(psz - pBuffer);
|
|
}
|
|
break;
|
|
|
|
case HttpLoggingTypeIIS:
|
|
{
|
|
PCHAR psz;
|
|
ULONG BytesWritten;
|
|
LONGLONG LifeTime;
|
|
LARGE_INTEGER CurrentTimeStamp;
|
|
|
|
ASSERT( pTracker->pUriEntry->LogDataLength );
|
|
ASSERT( pTracker->pUriEntry->pLogData );
|
|
|
|
NewLength = MAX_IIS_CACHE_FIELD_OVERHEAD
|
|
+ pTracker->pUriEntry->LogDataLength;
|
|
|
|
if ( NewLength > UL_LOG_LINE_BUFFER_SIZE )
|
|
{
|
|
Status = UlpReallocLogLine(pLogData, NewLength);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
// Worker won't get called. Cleanup immediately
|
|
UlDestroyLogDataBuffer(pLogData);
|
|
return Status;
|
|
}
|
|
}
|
|
|
|
BytesWritten = 0;
|
|
|
|
// Followings specify the size of iis log line fragments
|
|
// two and three. We are constructing the fragment one
|
|
// completely from scratch for pure cache hits.
|
|
|
|
pLogData->UsedOffset1 = pTracker->pUriEntry->UsedOffset1;
|
|
pLogData->UsedOffset2 = pTracker->pUriEntry->UsedOffset2;
|
|
|
|
ASSERT(pLogData->UsedOffset1 + pLogData->UsedOffset2
|
|
== pTracker->pUriEntry->LogDataLength );
|
|
|
|
psz = &pLogData->Line[0];
|
|
|
|
// Client IP
|
|
psz = UlStrPrintIP(
|
|
psz,
|
|
pRequest->pHttpConn->pConnection->RemoteAddress,
|
|
','
|
|
);
|
|
*psz++ = ' ';
|
|
|
|
// Authenticated users cannot be served from cache.
|
|
*psz++ = '-'; *psz++ = ','; *psz++ = ' ';
|
|
|
|
// Date & Time fields
|
|
UlpGetDateTimeFields(
|
|
HttpLoggingTypeIIS,
|
|
psz,
|
|
&BytesWritten,
|
|
NULL,
|
|
NULL
|
|
);
|
|
psz += BytesWritten; *psz++ = ','; *psz++ = ' ';
|
|
|
|
UlpGetDateTimeFields(
|
|
HttpLoggingTypeIIS,
|
|
NULL,
|
|
NULL,
|
|
psz,
|
|
&BytesWritten
|
|
);
|
|
psz += BytesWritten; *psz++ = ','; *psz++ = ' ';
|
|
|
|
// Fragment two
|
|
RtlCopyMemory( psz,
|
|
pTracker->pUriEntry->pLogData,
|
|
pLogData->UsedOffset1
|
|
);
|
|
psz += pLogData->UsedOffset1;
|
|
|
|
KeQuerySystemTime( &CurrentTimeStamp );
|
|
LifeTime = CurrentTimeStamp.QuadPart - pRequest->TimeStamp.QuadPart;
|
|
if (LifeTime < 0)
|
|
{
|
|
LifeTime = 0;
|
|
UlTrace(LOGGING, ("CopyTimeStampField: failure.\n"));
|
|
}
|
|
LifeTime /= (10*1000); // Conversion from 100-nanosecond to millisecs.
|
|
|
|
psz = UlStrPrintUlonglong(psz, (ULONGLONG)LifeTime,',');
|
|
*psz++ = ' ';
|
|
|
|
psz = UlStrPrintUlonglong(psz, pRequest->BytesReceived,',');
|
|
*psz++ = ' ';
|
|
|
|
psz = UlStrPrintUlonglong(psz, pLogData->BytesTransferred,',');
|
|
*psz++ = ' ';
|
|
|
|
// Fragment three
|
|
RtlCopyMemory( psz,
|
|
&pTracker->pUriEntry->pLogData[pLogData->UsedOffset1],
|
|
pLogData->UsedOffset2
|
|
);
|
|
psz += pLogData->UsedOffset2;
|
|
|
|
// Calculate the full size of the final log line
|
|
|
|
pLogData->Used += (ULONG) DIFF(psz - &pLogData->Line[0]);
|
|
|
|
// Reset the UsedOffset1 & 2 to zero to tell that the log line
|
|
// is not fragmented anymore but a complete line.
|
|
|
|
pLogData->UsedOffset1 = pLogData->UsedOffset2 = 0;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
ASSERT(!"Unknown Log Format.\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// For cache hits we can get the corresponding cgroup from uri_cache
|
|
// entry to avoid the costly cgroup lookup. And also not to keep the
|
|
// entry around during the logging process we will have a direct ref
|
|
// to the actual cgroup. DestroyLogBuffer will release this refcount
|
|
//
|
|
|
|
if ( pTracker->pUriEntry->ConfigInfo.pLoggingConfig != NULL )
|
|
{
|
|
REFERENCE_CONFIG_GROUP(pTracker->pUriEntry->ConfigInfo.pLoggingConfig);
|
|
pLogData->pConfigGroup = pTracker->pUriEntry->ConfigInfo.pLoggingConfig;
|
|
}
|
|
|
|
pTracker->pLogData = NULL;
|
|
|
|
//
|
|
// We cannot allow send-response to resume parsing otherwise request buffers
|
|
// will be freed up. Therefore complete inline for the time being.
|
|
//
|
|
|
|
// UL_QUEUE_WORK_ITEM( &pLogData->WorkItem, &UlLogHttpCacheHitWorker);
|
|
|
|
UlLogHttpCacheHitWorker( &pLogData->WorkItem );
|
|
|
|
return Status;
|
|
}
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
UlLogHttpCacheHitWorker :
|
|
|
|
Please read the description of UlLogHttpHit first.
|
|
|
|
Here where we log the cache hits. We use cache entry itself -
|
|
acquired from tracker - . Some fields are generated at this
|
|
time; i.e. timetaken,date,time...
|
|
|
|
Arguments:
|
|
|
|
PUL_WORK_ITEM : Will point us to the right pLogData
|
|
|
|
--***************************************************************************/
|
|
|
|
VOID
|
|
UlLogHttpCacheHitWorker(
|
|
IN PUL_WORK_ITEM pWorkItem
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
PUL_INTERNAL_REQUEST pRequest;
|
|
PUL_LOG_DATA_BUFFER pLogData;
|
|
PCHAR psz;
|
|
PCHAR pBuffer;
|
|
LONGLONG LifeTime;
|
|
LARGE_INTEGER CurrentTimeStamp;
|
|
ULONG BytesWritten;
|
|
PUL_LOG_FILE_ENTRY pEntry;
|
|
ULONG Flags;
|
|
|
|
//
|
|
// A Lot of sanity checks.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
pLogData = CONTAINING_RECORD(
|
|
pWorkItem,
|
|
UL_LOG_DATA_BUFFER,
|
|
WorkItem
|
|
);
|
|
|
|
ASSERT(pLogData);
|
|
|
|
UlTrace(LOGGING,("Ul!UlLogHttpCacheHitWorker: pLogData %p\n", pLogData));
|
|
|
|
pRequest = pLogData->pRequest;
|
|
ASSERT(UL_IS_VALID_INTERNAL_REQUEST(pRequest));
|
|
|
|
//
|
|
// Unlike the LogHttpHit function for non-cache hits we do not require
|
|
// the pResponse here. Tracker & pRequest already provides the info
|
|
// about how much data we sent & the time taken.
|
|
//
|
|
|
|
if (pLogData->pConfigGroup == NULL ||
|
|
IS_LOGGING_DISABLED(pLogData->pConfigGroup))
|
|
{
|
|
ASSERT(pLogData->pConfigGroup != NULL);
|
|
|
|
UlTrace( LOGGING,
|
|
("Http!UlLogHttpCacheHitWorker: Skipping pLogData->pConfigGroup %p\n",
|
|
pLogData->pConfigGroup
|
|
));
|
|
//
|
|
// If logging is disabled or log settings don't exist then do not
|
|
// proceed. Just exit out. But not before cleanup still goto end.
|
|
//
|
|
|
|
Status = STATUS_SUCCESS;
|
|
goto end;
|
|
}
|
|
|
|
ASSERT(IS_VALID_CONFIG_GROUP(pLogData->pConfigGroup));
|
|
|
|
//
|
|
// Construct the remaining fields before logging out this hit.
|
|
//
|
|
|
|
switch(pLogData->Format)
|
|
{
|
|
case HttpLoggingTypeW3C:
|
|
{
|
|
Flags = pLogData->Flags;
|
|
|
|
// First write the date & time fields to the beginning reserved
|
|
// space. Do not increment the used counter for date & time, b/c
|
|
// CaptureLogFields already did this when reserving the space.
|
|
|
|
psz = &pLogData->Line[0];
|
|
|
|
if ( Flags & MD_EXTLOG_DATE )
|
|
{
|
|
BytesWritten = 0;
|
|
UlpGetDateTimeFields(
|
|
HttpLoggingTypeW3C,
|
|
psz,
|
|
&BytesWritten,
|
|
NULL,
|
|
NULL
|
|
);
|
|
psz += BytesWritten; *psz++ = ' ';
|
|
ASSERT(BytesWritten == 10);
|
|
}
|
|
|
|
if ( Flags & MD_EXTLOG_TIME )
|
|
{
|
|
BytesWritten = 0;
|
|
UlpGetDateTimeFields(
|
|
HttpLoggingTypeW3C,
|
|
NULL,
|
|
NULL,
|
|
psz,
|
|
&BytesWritten
|
|
);
|
|
psz += BytesWritten; *psz++ = ' ';
|
|
ASSERT(BytesWritten == 8);
|
|
}
|
|
|
|
pBuffer = psz = &pLogData->Line[pLogData->Used];
|
|
|
|
// For pure cache hits we have to generate the few more fields
|
|
|
|
if ( pLogData->CacheAndSendResponse == FALSE )
|
|
{
|
|
// Capture log fields from Request alive
|
|
|
|
if ( Flags & MD_EXTLOG_USERNAME )
|
|
{
|
|
*psz++ = '-'; *psz++ = ' ';
|
|
}
|
|
|
|
if ( Flags & MD_EXTLOG_CLIENT_IP )
|
|
{
|
|
psz = UlStrPrintIP(
|
|
psz,
|
|
pRequest->pHttpConn->pConnection->RemoteAddress,
|
|
' '
|
|
);
|
|
}
|
|
|
|
if ( Flags & MD_EXTLOG_PROTOCOL_VERSION )
|
|
{
|
|
psz = UlStrPrintStr(
|
|
psz,
|
|
UL_GET_NAME_FOR_HTTP_VERSION(pRequest->Version),
|
|
' '
|
|
);
|
|
}
|
|
|
|
if ( Flags & MD_EXTLOG_USER_AGENT )
|
|
{
|
|
if (pRequest->HeaderValid[HttpHeaderUserAgent] &&
|
|
pRequest->Headers[HttpHeaderUserAgent].HeaderLength)
|
|
{
|
|
psz = UlStrPrintStrC(
|
|
psz,
|
|
(const CHAR *)pRequest->Headers[HttpHeaderUserAgent].pHeader,
|
|
' '
|
|
);
|
|
}
|
|
else
|
|
{
|
|
*psz++ = '-'; *psz++ = ' ';
|
|
}
|
|
}
|
|
|
|
if ( Flags & MD_EXTLOG_COOKIE )
|
|
{
|
|
if (pRequest->HeaderValid[HttpHeaderCookie] &&
|
|
pRequest->Headers[HttpHeaderCookie].HeaderLength)
|
|
{
|
|
psz = UlStrPrintStrC(
|
|
psz,
|
|
(const CHAR *)pRequest->Headers[HttpHeaderCookie].pHeader,
|
|
' '
|
|
);
|
|
}
|
|
else
|
|
{
|
|
*psz++ = '-'; *psz++ = ' ';
|
|
}
|
|
}
|
|
|
|
if ( Flags & MD_EXTLOG_REFERER )
|
|
{
|
|
if (pRequest->HeaderValid[HttpHeaderReferer] &&
|
|
pRequest->Headers[HttpHeaderReferer].HeaderLength)
|
|
{
|
|
psz = UlStrPrintStrC(
|
|
psz,
|
|
(const CHAR *)pRequest->Headers[HttpHeaderReferer].pHeader,
|
|
' '
|
|
);
|
|
}
|
|
else
|
|
{
|
|
*psz++ = '-'; *psz++ = ' ';
|
|
}
|
|
}
|
|
|
|
if ( Flags & MD_EXTLOG_HOST )
|
|
{
|
|
if (pRequest->HeaderValid[HttpHeaderHost] &&
|
|
pRequest->Headers[HttpHeaderHost].HeaderLength)
|
|
{
|
|
psz = UlStrPrintStrC(
|
|
psz,
|
|
(const CHAR *)pRequest->Headers[HttpHeaderHost].pHeader,
|
|
' '
|
|
);
|
|
}
|
|
else
|
|
{
|
|
*psz++ = '-'; *psz++ = ' ';
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// Now proceed with the remaining fields, but add them to the end.
|
|
|
|
if ( pLogData->Flags & MD_EXTLOG_BYTES_SENT )
|
|
{
|
|
psz = UlStrPrintUlonglong(psz, pLogData->BytesTransferred,' ');
|
|
}
|
|
if ( pLogData->Flags & MD_EXTLOG_BYTES_RECV )
|
|
{
|
|
psz = UlStrPrintUlonglong(psz, pRequest->BytesReceived,' ');
|
|
}
|
|
if ( pLogData->Flags & MD_EXTLOG_TIME_TAKEN )
|
|
{
|
|
KeQuerySystemTime( &CurrentTimeStamp );
|
|
LifeTime = CurrentTimeStamp.QuadPart - pRequest->TimeStamp.QuadPart;
|
|
|
|
if (LifeTime < 0)
|
|
{
|
|
LifeTime = 0;
|
|
UlTrace( LOGGING, ("CopyTimeStampField failure.\n"));
|
|
}
|
|
LifeTime /= (10*1000); // Conversion from 100-nanosecond to millisecs.
|
|
|
|
psz = UlStrPrintUlonglong(psz, (ULONGLONG)LifeTime,' ');
|
|
}
|
|
|
|
// Calculate the used space
|
|
|
|
pLogData->Used += (ULONG) DIFF(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 = &pLogData->Line[pLogData->Used-1]; // Eat the last space
|
|
*psz++ = '\r'; *psz++ = '\n'; *psz++ = ANSI_NULL;
|
|
|
|
pLogData->Used += 1;
|
|
|
|
if ( pLogData->Length == 0 )
|
|
{
|
|
ASSERT( pLogData->Used <= UL_LOG_LINE_BUFFER_SIZE );
|
|
}
|
|
else
|
|
{
|
|
ASSERT( pLogData->Used <= pLogData->Length );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
goto end; // No log fields nothing to log
|
|
}
|
|
|
|
// Cleanup the UsedOffsets
|
|
pLogData->UsedOffset1 = pLogData->UsedOffset2 = 0;
|
|
}
|
|
break;
|
|
|
|
case HttpLoggingTypeNCSA:
|
|
{
|
|
// [date:time GmtOffset] -> "[07/Jan/2000:00:02:23 -0800] "
|
|
// Restore the pointer to the reserved space first.
|
|
|
|
psz = &pLogData->Line[pLogData->UsedOffset1];
|
|
*psz++ = '[';
|
|
|
|
BytesWritten = 0;
|
|
UlpGetDateTimeFields(
|
|
HttpLoggingTypeNCSA,
|
|
psz,
|
|
&BytesWritten,
|
|
NULL,
|
|
NULL
|
|
);
|
|
psz += BytesWritten; *psz++ = ':';
|
|
ASSERT(BytesWritten == 11);
|
|
|
|
BytesWritten = 0;
|
|
UlpGetDateTimeFields(
|
|
HttpLoggingTypeNCSA,
|
|
NULL,
|
|
NULL,
|
|
psz,
|
|
&BytesWritten
|
|
);
|
|
psz += BytesWritten; *psz++ = ' ';
|
|
ASSERT(BytesWritten == 8);
|
|
|
|
UlAcquireResourceShared(&g_pUlNonpagedData->LogListResource, TRUE);
|
|
psz = UlStrPrintStr(psz, g_GMTOffset,']');
|
|
UlReleaseResource(&g_pUlNonpagedData->LogListResource);
|
|
*psz++ = ' ';
|
|
|
|
ASSERT(((ULONG) DIFF(psz - &pLogData->Line[pLogData->UsedOffset1]) == 29));
|
|
|
|
// BytesSent
|
|
|
|
pBuffer = psz = &pLogData->Line[pLogData->Used];
|
|
psz = UlStrPrintUlonglong(psz, pLogData->BytesTransferred,'\r');
|
|
pLogData->Used += (ULONG) DIFF(psz - pBuffer);
|
|
|
|
// \n\0
|
|
|
|
*psz++ = '\n'; *psz++ = ANSI_NULL;
|
|
pLogData->Used += 1;
|
|
|
|
// Cleanup the UsedOffsets
|
|
pLogData->UsedOffset1 = pLogData->UsedOffset2 = 0;
|
|
}
|
|
break;
|
|
|
|
case HttpLoggingTypeIIS:
|
|
{
|
|
if ( pLogData->CacheAndSendResponse == TRUE )
|
|
{
|
|
// We need to work on the fragmented log line
|
|
// which is coming from the originaly allocated
|
|
// line but not from cache.
|
|
|
|
// Complete Fragment 1
|
|
|
|
pBuffer = psz = &pLogData->Line[pLogData->UsedOffset1];
|
|
UlpGetDateTimeFields(
|
|
HttpLoggingTypeIIS,
|
|
psz,
|
|
&BytesWritten,
|
|
NULL,
|
|
NULL
|
|
);
|
|
psz += BytesWritten; *psz++ = ','; *psz++ = ' ';
|
|
|
|
UlpGetDateTimeFields(
|
|
HttpLoggingTypeIIS,
|
|
NULL,
|
|
NULL,
|
|
psz,
|
|
&BytesWritten
|
|
);
|
|
psz += BytesWritten; *psz++ = ','; *psz++ = ' ';
|
|
|
|
pLogData->UsedOffset1 += (USHORT) DIFF(psz - pBuffer);
|
|
|
|
|
|
// Complete Fragment 2
|
|
|
|
pBuffer = psz = &pLogData->Line[512 + pLogData->UsedOffset2];
|
|
|
|
KeQuerySystemTime( &CurrentTimeStamp );
|
|
LifeTime = CurrentTimeStamp.QuadPart - pRequest->TimeStamp.QuadPart;
|
|
if (LifeTime < 0)
|
|
{
|
|
LifeTime = 0;
|
|
UlTrace(LOGGING, ("CopyTimeStampField: failure.\n"));
|
|
}
|
|
LifeTime /= (10*1000); // Conversion from 100-nanosecond to millisecs.
|
|
|
|
psz = UlStrPrintUlonglong(psz, (ULONGLONG)LifeTime,',');
|
|
*psz++ = ' ';
|
|
|
|
psz = UlStrPrintUlonglong(psz, pRequest->BytesReceived,',');
|
|
*psz++ = ' ';
|
|
|
|
psz = UlStrPrintUlonglong(psz, pLogData->BytesTransferred,',');
|
|
*psz++ = ' ';
|
|
|
|
pLogData->UsedOffset2 += (USHORT) DIFF(psz - pBuffer);
|
|
|
|
// Size of the final log line is
|
|
// pLogData->UsedOffset1 + pLogData->UsedOffset2 + pLogData->Used
|
|
|
|
}
|
|
|
|
// Or else IIS log line is already done. We have completed it before
|
|
// relasing the cache entry.
|
|
}
|
|
break;
|
|
|
|
default:
|
|
{
|
|
ASSERT(!"Unknown Log Format Type\n");
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto end;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Finally this log line is ready to rock. Lets write it out.
|
|
//
|
|
|
|
UlAcquireResourceShared(&g_pUlNonpagedData->LogListResource, TRUE);
|
|
|
|
pEntry = pLogData->pConfigGroup->pLogFileEntry;
|
|
|
|
if (pEntry)
|
|
{
|
|
ASSERT(IS_VALID_LOG_FILE_ENTRY(pEntry));
|
|
|
|
Status =
|
|
UlpWriteToLogFile(
|
|
pEntry,
|
|
pLogData->UsedOffset1 + pLogData->UsedOffset2 + pLogData->Used,
|
|
&pLogData->Line[0],
|
|
pLogData->UsedOffset1,
|
|
pLogData->UsedOffset2
|
|
);
|
|
}
|
|
|
|
UlReleaseResource(&g_pUlNonpagedData->LogListResource);
|
|
|
|
end:
|
|
if (pLogData)
|
|
{
|
|
//
|
|
// Cleanup the references & the log buffer
|
|
//
|
|
|
|
UlDestroyLogDataBuffer(pLogData);
|
|
}
|
|
|
|
#if DBG
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
UlTrace(LOGGING,("Http!UlLogHttpcacheHitWorker: failure 0x%08lx \n",
|
|
Status
|
|
));
|
|
}
|
|
#endif
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Initializes the Log Date & Time Cache
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UlpInitializeLogCache(
|
|
VOID
|
|
)
|
|
{
|
|
LARGE_INTEGER SystemTime;
|
|
ULONG LogType;
|
|
|
|
ExInitializeFastMutex( &g_LogCacheFastMutex);
|
|
|
|
KeQuerySystemTime(&SystemTime);
|
|
|
|
for ( LogType=0; LogType<HttpLoggingTypeMaximum; LogType++ )
|
|
{
|
|
UlpGenerateDateAndTimeFields( (HTTP_LOGGING_TYPE) LogType,
|
|
SystemTime,
|
|
g_UlDateTimeCache[LogType].Date,
|
|
&g_UlDateTimeCache[LogType].DateLength,
|
|
g_UlDateTimeCache[LogType].Time,
|
|
&g_UlDateTimeCache[LogType].TimeLength
|
|
);
|
|
|
|
g_UlDateTimeCache[LogType].LastSystemTime.QuadPart = SystemTime.QuadPart;
|
|
}
|
|
}
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Generates all possible types of date/time fields from a LARGE_INTEGER.
|
|
|
|
Arguments:
|
|
|
|
CurrentTime: A 64 bit Time value to be converted.
|
|
|
|
--***************************************************************************/
|
|
|
|
VOID
|
|
UlpGenerateDateAndTimeFields(
|
|
IN HTTP_LOGGING_TYPE LogType,
|
|
IN LARGE_INTEGER CurrentTime,
|
|
OUT PCHAR pDate,
|
|
OUT PULONG pDateLength,
|
|
OUT PCHAR pTime,
|
|
OUT PULONG pTimeLength
|
|
)
|
|
{
|
|
TIME_FIELDS CurrentTimeFields;
|
|
LARGE_INTEGER CurrentTimeLoc;
|
|
TIME_FIELDS CurrentTimeFieldsLoc;
|
|
PCHAR psz;
|
|
LONG Length;
|
|
|
|
// This routine does touch to pageable memory if the default log buffer
|
|
// wasn't sufficent enough to hold log fields and get reallocated from
|
|
// paged pool. For this reason the date&time cache can not use SpinLocks.
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT(LogType < HttpLoggingTypeMaximum);
|
|
|
|
RtlTimeToTimeFields( &CurrentTime, &CurrentTimeFields );
|
|
|
|
switch(LogType)
|
|
{
|
|
case HttpLoggingTypeW3C:
|
|
//
|
|
// Uses GMT with format as follows;
|
|
//
|
|
// 2000-01-31 00:12:23
|
|
//
|
|
|
|
if (pDate)
|
|
{
|
|
psz = pDate;
|
|
psz = UlStrPrintUlongPad(psz, CurrentTimeFields.Year, 4, '-' );
|
|
psz = UlStrPrintUlongPad(psz, CurrentTimeFields.Month,2, '-' );
|
|
psz = UlStrPrintUlongPad(psz, CurrentTimeFields.Day, 2, '\0');
|
|
*pDateLength = (ULONG) DIFF(psz - pDate);
|
|
}
|
|
|
|
if (pTime)
|
|
{
|
|
psz = pTime;
|
|
psz = UlStrPrintUlongPad(psz, CurrentTimeFields.Hour, 2, ':' );
|
|
psz = UlStrPrintUlongPad(psz, CurrentTimeFields.Minute,2, ':' );
|
|
psz = UlStrPrintUlongPad(psz, CurrentTimeFields.Second,2, '\0');
|
|
*pTimeLength = (ULONG) DIFF(psz - pTime);
|
|
}
|
|
break;
|
|
|
|
case HttpLoggingTypeNCSA:
|
|
//
|
|
// Uses GMT Time with format as follows;
|
|
//
|
|
// 07/Jan/2000 00:02:23
|
|
//
|
|
|
|
ExSystemTimeToLocalTime( &CurrentTime, &CurrentTimeLoc );
|
|
RtlTimeToTimeFields( &CurrentTimeLoc, &CurrentTimeFieldsLoc );
|
|
|
|
if(pDate)
|
|
{
|
|
psz = pDate;
|
|
psz = UlStrPrintUlongPad(psz, CurrentTimeFieldsLoc.Day, 2, '/' );
|
|
psz = UlStrPrintStr(psz, UL_GET_MONTH_AS_STR(CurrentTimeFieldsLoc.Month),'/');
|
|
psz = UlStrPrintUlongPad(psz, CurrentTimeFieldsLoc.Year,4, '\0');
|
|
*pDateLength = (ULONG) DIFF(psz - pDate);
|
|
}
|
|
|
|
if(pTime)
|
|
{
|
|
psz = pTime;
|
|
psz = UlStrPrintUlongPad(psz, CurrentTimeFieldsLoc.Hour, 2, ':' );
|
|
psz = UlStrPrintUlongPad(psz, CurrentTimeFieldsLoc.Minute,2, ':' );
|
|
psz = UlStrPrintUlongPad(psz, CurrentTimeFieldsLoc.Second,2, '\0');
|
|
*pTimeLength = (ULONG) DIFF(psz - pTime);
|
|
}
|
|
break;
|
|
|
|
case HttpLoggingTypeIIS:
|
|
//
|
|
// Uses LOCAL Time with format as follows;
|
|
// This should be localised if we can solve the problem.
|
|
//
|
|
// 1/31/2000 0:02:03
|
|
//
|
|
|
|
ExSystemTimeToLocalTime( &CurrentTime, &CurrentTimeLoc );
|
|
RtlTimeToTimeFields( &CurrentTimeLoc, &CurrentTimeFieldsLoc );
|
|
|
|
if (pDate)
|
|
{
|
|
psz = pDate;
|
|
psz = UlStrPrintUlongPad(psz, CurrentTimeFieldsLoc.Month, 0, '/' );
|
|
psz = UlStrPrintUlongPad(psz, CurrentTimeFieldsLoc.Day, 0, '/' );
|
|
psz = UlStrPrintUlongPad(psz, CurrentTimeFieldsLoc.Year, 0, '\0');
|
|
*pDateLength = (ULONG) DIFF(psz - pDate);
|
|
}
|
|
|
|
if(pTime)
|
|
{
|
|
psz = pTime;
|
|
psz = UlStrPrintUlongPad(psz, CurrentTimeFieldsLoc.Hour, 0, ':' );
|
|
psz = UlStrPrintUlongPad(psz, CurrentTimeFieldsLoc.Minute,2, ':' );
|
|
psz = UlStrPrintUlongPad(psz, CurrentTimeFieldsLoc.Second,2, '\0');
|
|
*pTimeLength = (ULONG) DIFF(psz - pTime);
|
|
}
|
|
break;
|
|
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Generates a date header and updates cached value if required.
|
|
|
|
Caller should overwrite the terminating null by a space or comma.
|
|
|
|
Arguments:
|
|
|
|
Date and Time are optional. But one of them should be provided.
|
|
|
|
--***************************************************************************/
|
|
|
|
VOID
|
|
UlpGetDateTimeFields(
|
|
IN HTTP_LOGGING_TYPE LogType,
|
|
OUT PCHAR pDate,
|
|
OUT PULONG pDateLength,
|
|
OUT PCHAR pTime,
|
|
OUT PULONG pTimeLength
|
|
)
|
|
{
|
|
LARGE_INTEGER SystemTime;
|
|
LARGE_INTEGER CacheTime;
|
|
LONG Length;
|
|
LONGLONG Timediff;
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT(LogType < HttpLoggingTypeMaximum);
|
|
ASSERT(pDate || pTime);
|
|
|
|
//
|
|
// Get the current time.
|
|
//
|
|
KeQuerySystemTime( &SystemTime );
|
|
|
|
CacheTime.QuadPart =
|
|
g_UlDateTimeCache[LogType].LastSystemTime.QuadPart;
|
|
|
|
//
|
|
// Check the difference between the current time, and
|
|
// the cached time.
|
|
//
|
|
Timediff = SystemTime.QuadPart - CacheTime.QuadPart;
|
|
|
|
if (Timediff < ONE_SECOND)
|
|
{
|
|
//
|
|
// The cached date&time hasn't gone stale yet.We can copy.
|
|
// Force a barrier around reading the string into memory.
|
|
//
|
|
|
|
UL_READMOSTLY_READ_BARRIER();
|
|
if (pDate)
|
|
{
|
|
RtlCopyMemory( pDate,
|
|
g_UlDateTimeCache[LogType].Date,
|
|
g_UlDateTimeCache[LogType].DateLength
|
|
);
|
|
*pDateLength = g_UlDateTimeCache[LogType].DateLength;
|
|
}
|
|
if (pTime)
|
|
{
|
|
RtlCopyMemory( pTime,
|
|
g_UlDateTimeCache[LogType].Time,
|
|
g_UlDateTimeCache[LogType].TimeLength
|
|
);
|
|
*pTimeLength = g_UlDateTimeCache[LogType].TimeLength;
|
|
}
|
|
UL_READMOSTLY_READ_BARRIER();
|
|
|
|
//
|
|
// Get grab the cached time value again in case it has been changed.
|
|
// As you notice we do not have a lock around this part of the code.
|
|
//
|
|
|
|
if (CacheTime.QuadPart ==
|
|
g_UlDateTimeCache[LogType].LastSystemTime.QuadPart)
|
|
{
|
|
//
|
|
// Value hasn't changed. We are all set.
|
|
//
|
|
return;
|
|
}
|
|
//
|
|
// Otherwise fall down and flush the cache, and then recopy.
|
|
//
|
|
|
|
}
|
|
|
|
//
|
|
// The cached date & time is stale. We need to update it.
|
|
//
|
|
|
|
ExAcquireFastMutex( &g_LogCacheFastMutex );
|
|
|
|
//
|
|
// Has someone else updated the time while we were blocked?
|
|
//
|
|
|
|
CacheTime.QuadPart =
|
|
g_UlDateTimeCache[LogType].LastSystemTime.QuadPart;
|
|
|
|
Timediff = SystemTime.QuadPart - CacheTime.QuadPart;
|
|
|
|
if (Timediff >= ONE_SECOND)
|
|
{
|
|
g_UlDateTimeCache[LogType].LastSystemTime.QuadPart = 0;
|
|
KeQuerySystemTime( &SystemTime );
|
|
|
|
UL_READMOSTLY_WRITE_BARRIER();
|
|
UlpGenerateDateAndTimeFields(
|
|
LogType,
|
|
SystemTime,
|
|
g_UlDateTimeCache[LogType].Date,
|
|
&g_UlDateTimeCache[LogType].DateLength,
|
|
g_UlDateTimeCache[LogType].Time,
|
|
&g_UlDateTimeCache[LogType].TimeLength
|
|
);
|
|
UL_READMOSTLY_WRITE_BARRIER();
|
|
|
|
g_UlDateTimeCache[LogType].LastSystemTime.QuadPart =
|
|
SystemTime.QuadPart;
|
|
}
|
|
|
|
//
|
|
// The time has been updated. Copy the new string into
|
|
// the caller's buffer.
|
|
//
|
|
if (pDate)
|
|
{
|
|
RtlCopyMemory( pDate,
|
|
g_UlDateTimeCache[LogType].Date,
|
|
g_UlDateTimeCache[LogType].DateLength
|
|
);
|
|
*pDateLength = g_UlDateTimeCache[LogType].DateLength;
|
|
}
|
|
|
|
if (pTime)
|
|
{
|
|
RtlCopyMemory( pTime,
|
|
g_UlDateTimeCache[LogType].Time,
|
|
g_UlDateTimeCache[LogType].TimeLength
|
|
);
|
|
*pTimeLength = g_UlDateTimeCache[LogType].TimeLength;
|
|
}
|
|
|
|
ExReleaseFastMutex( &g_LogCacheFastMutex );
|
|
|
|
return;
|
|
}
|
|
|