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.
17432 lines
407 KiB
17432 lines
407 KiB
/*++
|
|
|
|
Copyright (c) 2000 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
pfsvc.c
|
|
|
|
Abstract:
|
|
|
|
This module contains the main rountines for the prefetcher service
|
|
responsible for maintaining prefetch scenario files.
|
|
|
|
Author:
|
|
|
|
Stuart Sechrest (stuartse)
|
|
Cenk Ergan (cenke)
|
|
Chuck Leinzmeier (chuckl)
|
|
|
|
Environment:
|
|
|
|
User Mode
|
|
|
|
--*/
|
|
|
|
#include <nt.h>
|
|
#include <ntrtl.h>
|
|
#include <nturtl.h>
|
|
#include <windows.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <limits.h>
|
|
#include <aclapi.h>
|
|
#include <dbghelp.h>
|
|
#include <idletask.h>
|
|
#include <prefetch.h>
|
|
#include <shdcom.h>
|
|
#include <tchar.h>
|
|
#include "pfsvc.h"
|
|
|
|
//
|
|
// Routine called to register for notifications when processing of all idle
|
|
// tasks is requested from the idle task server.
|
|
//
|
|
|
|
typedef VOID (*PIT_PROCESS_IDLE_TASKS_NOTIFY_ROUTINE)(VOID);
|
|
|
|
BOOL
|
|
ItSpSetProcessIdleTasksNotifyRoutine (
|
|
PIT_PROCESS_IDLE_TASKS_NOTIFY_ROUTINE NotifyRoutine
|
|
);
|
|
|
|
//
|
|
// Globals.
|
|
//
|
|
|
|
PFSVC_GLOBALS PfSvcGlobals = {0};
|
|
|
|
//
|
|
// Exposed routines:
|
|
//
|
|
|
|
DWORD
|
|
WINAPI
|
|
PfSvcMainThread(
|
|
VOID *Param
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This is the main routine for the prefetcher service. It sets up
|
|
file notification on the input files directory and waits for work
|
|
or the signaling of the termination event.
|
|
|
|
Arguments:
|
|
|
|
Param - Pointer to handle to the event that will signal our
|
|
termination.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
HANDLE hStopEvent;
|
|
HANDLE hTracesReadyEvent;
|
|
HANDLE hParametersChangedEvent;
|
|
HANDLE hEvents[4];
|
|
ULONG NumEvents;
|
|
DWORD ErrorCode;
|
|
ULONG Length;
|
|
ULONG EventIdx;
|
|
BOOLEAN bExitMainLoop;
|
|
DWORD dwWait;
|
|
NTSTATUS Status;
|
|
PPFSVC_SECTION_NODE SectionNode;
|
|
PPFSVC_PAGE_NODE PageNode;
|
|
PLIST_ENTRY ListHead;
|
|
PF_ENABLE_STATUS EnableStatus;
|
|
PF_SCENARIO_TYPE ScenarioType;
|
|
BOOLEAN UpdatedParameters;
|
|
BOOLEAN PrefetchingEnabled;
|
|
HANDLE PrefetcherThreads[1];
|
|
ULONG NumPrefetcherThreads;
|
|
ULONG ThreadIdx;
|
|
|
|
//
|
|
// Initialize locals.
|
|
//
|
|
|
|
NumEvents = sizeof(hEvents) / sizeof(HANDLE);
|
|
hStopEvent = *((HANDLE *) Param);
|
|
hTracesReadyEvent = NULL;
|
|
hParametersChangedEvent = NULL;
|
|
NumPrefetcherThreads = 0;
|
|
|
|
DBGPR((PFID,PFTRC,"PFSVC: MainThread()\n"));
|
|
|
|
//
|
|
// Initialize globals.
|
|
//
|
|
|
|
ErrorCode = PfSvInitializeGlobals();
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
DBGPR((PFID,PFERR,"PFSVC: MainThread()-FailedInitGlobals\n"));
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Save service start time, prefetcher version etc.
|
|
//
|
|
|
|
PfSvSaveStartInfo(PfSvcGlobals.ServiceDataKey);
|
|
|
|
//
|
|
// Get necessary permissions for this thread to perform prefetch
|
|
// service tasks.
|
|
//
|
|
|
|
ErrorCode = PfSvGetPrefetchServiceThreadPrivileges();
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
DBGPR((PFID,PFERR,"PFSVC: MainThread()-FailedGetPrivileges\n"));
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Set permissions on the event that can be set to override
|
|
// waiting for system to be idle before processing traces, so it
|
|
// can be set by administrators.
|
|
//
|
|
|
|
ErrorCode = PfSvSetAdminOnlyPermissions(PFSVC_OVERRIDE_IDLE_EVENT_NAME,
|
|
PfSvcGlobals.OverrideIdleProcessingEvent,
|
|
SE_KERNEL_OBJECT);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
DBGPR((PFID,PFERR,"PFSVC: MainThread()-FailedSetPermissions1\n"));
|
|
goto cleanup;
|
|
}
|
|
|
|
ErrorCode = PfSvSetAdminOnlyPermissions(PFSVC_PROCESSING_COMPLETE_EVENT_NAME,
|
|
PfSvcGlobals.ProcessingCompleteEvent,
|
|
SE_KERNEL_OBJECT);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
DBGPR((PFID,PFERR,"PFSVC: MainThread()-FailedSetPermissions2\n"));
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Get system prefetch parameters.
|
|
//
|
|
|
|
ErrorCode = PfSvQueryPrefetchParameters(&PfSvcGlobals.Parameters);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
DBGPR((PFID,PFERR,"PFSVC: MainThread()-FailedQueryParameters\n"));
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Depending on system type, if various types of prefetching is
|
|
// not specified in the registry (i.e. not specifically disabled),
|
|
// enable it.
|
|
//
|
|
|
|
UpdatedParameters = FALSE;
|
|
|
|
if (PfSvcGlobals.OsVersion.wProductType == VER_NT_WORKSTATION) {
|
|
|
|
//
|
|
// Enable all prefetching types if they are not disabled.
|
|
//
|
|
|
|
for(ScenarioType = 0; ScenarioType < PfMaxScenarioType; ScenarioType++) {
|
|
if (PfSvcGlobals.Parameters.EnableStatus[ScenarioType] == PfSvNotSpecified) {
|
|
PfSvcGlobals.Parameters.EnableStatus[ScenarioType] = PfSvEnabled;
|
|
UpdatedParameters = TRUE;
|
|
}
|
|
}
|
|
|
|
} else if (PfSvcGlobals.OsVersion.wProductType == VER_NT_SERVER ||
|
|
PfSvcGlobals.OsVersion.wProductType == VER_NT_DOMAIN_CONTROLLER) {
|
|
|
|
//
|
|
// Enable only boot prefetching.
|
|
//
|
|
|
|
if (PfSvcGlobals.Parameters.EnableStatus[PfSystemBootScenarioType] == PfSvNotSpecified) {
|
|
PfSvcGlobals.Parameters.EnableStatus[PfSystemBootScenarioType] = PfSvEnabled;
|
|
UpdatedParameters = TRUE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If we enabled prefetching for a scenario type, call the kernel
|
|
// to update the parameters.
|
|
//
|
|
|
|
if (UpdatedParameters) {
|
|
|
|
ErrorCode = PfSvSetPrefetchParameters(&PfSvcGlobals.Parameters);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
DBGPR((PFID,PFERR,"PFSVC: MainThread()-FailedSetParameters\n"));
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Continue only if prefetching for a scenario type is enabled.
|
|
//
|
|
|
|
PrefetchingEnabled = FALSE;
|
|
|
|
for(ScenarioType = 0; ScenarioType < PfMaxScenarioType; ScenarioType++) {
|
|
if (PfSvcGlobals.Parameters.EnableStatus[ScenarioType] == PfSvEnabled) {
|
|
PrefetchingEnabled = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (PrefetchingEnabled == FALSE) {
|
|
ErrorCode = ERROR_NOT_SUPPORTED;
|
|
DBGPR((PFID,PFERR,"PFSVC: MainThread()-PrefetchingNotEnabled\n"));
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Initialize the directory that contains prefetch instructions.
|
|
//
|
|
|
|
ErrorCode = PfSvInitializePrefetchDirectory(PfSvcGlobals.Parameters.RootDirPath);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
DBGPR((PFID,PFERR,"PFSVC: MainThread()-FailedInitPrefetchDir\n"));
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Create the event that the kernel will set when raw traces are
|
|
// available. Then set the event so that the first time into the loop we
|
|
// will immediately process whatever raw traces are already waiting.
|
|
//
|
|
// The event is an autoclearing event, so it resets to the not-signaled
|
|
// state when our wait is satisfied. This allows proper synchronization
|
|
// with the kernel prefetcher.
|
|
//
|
|
|
|
hTracesReadyEvent = CreateEvent(NULL,
|
|
FALSE,
|
|
FALSE,
|
|
PF_COMPLETED_TRACES_EVENT_WIN32_NAME);
|
|
|
|
if (hTracesReadyEvent == NULL) {
|
|
ErrorCode = GetLastError();
|
|
DBGPR((PFID,PFERR,"PFSVC: MainThread()-FailedTracesReadyEvent\n"));
|
|
goto cleanup;
|
|
}
|
|
|
|
SetEvent(hTracesReadyEvent);
|
|
|
|
//
|
|
// Create the event that the kernel will set when system prefetch
|
|
// parameters change.
|
|
//
|
|
|
|
hParametersChangedEvent = CreateEvent(NULL,
|
|
FALSE,
|
|
FALSE,
|
|
PF_PARAMETERS_CHANGED_EVENT_WIN32_NAME);
|
|
|
|
if (hParametersChangedEvent == NULL) {
|
|
ErrorCode = GetLastError();
|
|
DBGPR((PFID,PFERR,"PFSVC: MainThread()-FailedParamsChangedEvent\n"));
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Queue a work item to wait for the shell ready event and notify
|
|
// the kernel.
|
|
//
|
|
|
|
QueueUserWorkItem(PfSvPollShellReadyWorker, NULL, WT_EXECUTELONGFUNCTION);
|
|
|
|
//
|
|
// Create a thread to process traces retrieved from the kernel.
|
|
//
|
|
|
|
PrefetcherThreads[NumPrefetcherThreads] = CreateThread(0,
|
|
0,
|
|
PfSvProcessTraceThread,
|
|
0,
|
|
0,
|
|
0);
|
|
|
|
if (PrefetcherThreads[NumPrefetcherThreads]) {
|
|
NumPrefetcherThreads++;
|
|
}
|
|
|
|
//
|
|
// Register a notification routine with the idle task server.
|
|
//
|
|
|
|
ItSpSetProcessIdleTasksNotifyRoutine(PfSvProcessIdleTasksCallback);
|
|
|
|
//
|
|
// Set up handles we are going to wait on.
|
|
//
|
|
|
|
hEvents[0] = hStopEvent;
|
|
hEvents[1] = hTracesReadyEvent;
|
|
hEvents[2] = hParametersChangedEvent;
|
|
hEvents[3] = PfSvcGlobals.CheckForMissedTracesEvent;
|
|
|
|
//
|
|
// This is the main loop. Wait on the events for work or for exit
|
|
// signal.
|
|
//
|
|
|
|
bExitMainLoop = FALSE;
|
|
|
|
do {
|
|
|
|
DBGPR((PFID,PFWAIT,"PFSVC: MainThread()-WaitForWork\n"));
|
|
dwWait = WaitForMultipleObjects(NumEvents, hEvents, FALSE, INFINITE);
|
|
DBGPR((PFID,PFWAIT,"PFSVC: MainThread()-EndWaitForWork=%x\n",dwWait));
|
|
|
|
switch(dwWait) {
|
|
|
|
case WAIT_OBJECT_0:
|
|
|
|
//
|
|
// Service exit event:
|
|
//
|
|
|
|
//
|
|
// Break out, cleanup and exit.
|
|
//
|
|
|
|
ErrorCode = ERROR_SUCCESS;
|
|
bExitMainLoop = TRUE;
|
|
|
|
break;
|
|
|
|
case WAIT_OBJECT_0 + 3:
|
|
|
|
//
|
|
// The event that is set when we had max number of queued
|
|
// traces and we processed one. We should check for traces
|
|
// we could not pick up because the queue had maxed.
|
|
//
|
|
|
|
//
|
|
// Fall through to retrieve traces from the kernel.
|
|
//
|
|
|
|
case WAIT_OBJECT_0 + 1:
|
|
|
|
//
|
|
// New traces are available event set by the kernel:
|
|
//
|
|
|
|
PfSvGetRawTraces();
|
|
|
|
break;
|
|
|
|
case WAIT_OBJECT_0 + 2:
|
|
|
|
//
|
|
// Prefetch parameters changed event:
|
|
//
|
|
|
|
//
|
|
// Get new system prefetch parameters.
|
|
//
|
|
|
|
ErrorCode = PfSvQueryPrefetchParameters(&PfSvcGlobals.Parameters);
|
|
|
|
//
|
|
// If we were not successful, we should not continue.
|
|
//
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
bExitMainLoop = TRUE;
|
|
DBGPR((PFID,PFERR,"PFSVC: MainThread()-FailedQueryParameters2\n"));
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Update the path to the prefetch instructions directory.
|
|
//
|
|
|
|
ErrorCode = PfSvInitializePrefetchDirectory(PfSvcGlobals.Parameters.RootDirPath);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
bExitMainLoop = TRUE;
|
|
DBGPR((PFID,PFERR,"PFSVC: MainThread()-FailedReinitPrefetchDir\n"));
|
|
break;
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
//
|
|
// Something gone wrong. Break out, cleanup and exit.
|
|
//
|
|
|
|
DBGPR((PFID,PFERR,"PFSVC: MainThread()-WaitForWorkFailed\n"));
|
|
ErrorCode = ERROR_INVALID_HANDLE;
|
|
bExitMainLoop = TRUE;
|
|
|
|
break;
|
|
}
|
|
|
|
} while (!bExitMainLoop);
|
|
|
|
cleanup:
|
|
|
|
//
|
|
// Save exit information.
|
|
//
|
|
|
|
if (PfSvcGlobals.ServiceDataKey) {
|
|
PfSvSaveExitInfo(PfSvcGlobals.ServiceDataKey, ErrorCode);
|
|
}
|
|
|
|
//
|
|
// Make sure the terminate event is set and wait for all our
|
|
// threads to exit.
|
|
//
|
|
|
|
if (NumPrefetcherThreads) {
|
|
|
|
//
|
|
// We could not have created worker threads without having
|
|
// initialized the globals successfully.
|
|
//
|
|
|
|
PFSVC_ASSERT(PfSvcGlobals.TerminateServiceEvent);
|
|
SetEvent(PfSvcGlobals.TerminateServiceEvent);
|
|
|
|
for (ThreadIdx = 0; ThreadIdx < NumPrefetcherThreads; ThreadIdx++) {
|
|
PFSVC_ASSERT(PrefetcherThreads[ThreadIdx]);
|
|
|
|
DBGPR((PFID,PFWAIT,"PFSVC: MainThread()-WaitForThreadIdx(%d)\n", ThreadIdx));
|
|
|
|
WaitForSingleObject(PrefetcherThreads[ThreadIdx], INFINITE);
|
|
|
|
DBGPR((PFID,PFWAIT,"PFSVC: MainThread()-EndWaitForThreadIdx(%d)\n", ThreadIdx));
|
|
|
|
CloseHandle(PrefetcherThreads[ThreadIdx]);
|
|
}
|
|
}
|
|
|
|
if (hTracesReadyEvent != NULL) {
|
|
CloseHandle(hTracesReadyEvent);
|
|
}
|
|
|
|
if (hParametersChangedEvent != NULL) {
|
|
CloseHandle(hParametersChangedEvent);
|
|
}
|
|
|
|
//
|
|
// Cleanup all globals.
|
|
//
|
|
|
|
PfSvCleanupGlobals();
|
|
|
|
DBGPR((PFID,PFTRC,"PFSVC: MainThread()=%x\n", ErrorCode));
|
|
|
|
return ErrorCode;
|
|
}
|
|
|
|
//
|
|
// Internal service routines:
|
|
//
|
|
|
|
//
|
|
// Thread routines:
|
|
//
|
|
|
|
DWORD
|
|
WINAPI
|
|
PfSvProcessTraceThread(
|
|
VOID *Param
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This is the routine for the thread that processes traces and
|
|
updates scenarios.
|
|
|
|
Arguments:
|
|
|
|
Param - Ignored.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
PFSVC_IDLE_TASK LayoutTask;
|
|
PFSVC_IDLE_TASK DirectoryCleanupTask;
|
|
PPFSVC_TRACE_BUFFER TraceBuffer;
|
|
PLIST_ENTRY HeadEntry;
|
|
WCHAR *BuildDefragStatus;
|
|
HANDLE CheckForQueuedTracesEvents[3];
|
|
HANDLE BootTraceEvents[2];
|
|
DWORD ErrorCode;
|
|
ULONG TotalTracesProcessed;
|
|
ULONG NumCheckForQueuedTracesEvents;
|
|
ULONG OrgNumQueuedTraces;
|
|
ULONG WaitResult;
|
|
ULONG NumEvents;
|
|
ULONG NumFailedTraces;
|
|
ULONG BuildDefragStatusSize;
|
|
NTSTATUS Status;
|
|
BOOLEAN AcquiredTracesLock;
|
|
|
|
//
|
|
// Intialize locals.
|
|
//
|
|
|
|
TraceBuffer = NULL;
|
|
TotalTracesProcessed = 0;
|
|
AcquiredTracesLock = FALSE;
|
|
PfSvInitializeTask(&LayoutTask);
|
|
PfSvInitializeTask(&DirectoryCleanupTask);
|
|
BuildDefragStatus = NULL;
|
|
NumFailedTraces = 0;
|
|
|
|
//
|
|
// These are the events we wait on before picking up traces to
|
|
// process.
|
|
//
|
|
|
|
CheckForQueuedTracesEvents[0] = PfSvcGlobals.TerminateServiceEvent;
|
|
CheckForQueuedTracesEvents[1] = PfSvcGlobals.NewTracesToProcessEvent;
|
|
CheckForQueuedTracesEvents[2] = PfSvcGlobals.OverrideIdleProcessingEvent;
|
|
NumCheckForQueuedTracesEvents = sizeof(CheckForQueuedTracesEvents) / sizeof(HANDLE);
|
|
|
|
DBGPR((PFID,PFTRC,"PFSVC: ProcessTraceThread()\n"));
|
|
|
|
//
|
|
// Get necessary permissions for this thread to perform prefetch
|
|
// service tasks.
|
|
//
|
|
|
|
ErrorCode = PfSvGetPrefetchServiceThreadPrivileges();
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// If we are allowed to run the defragger...
|
|
//
|
|
|
|
if (PfSvAllowedToRunDefragger(FALSE)) {
|
|
|
|
//
|
|
// Queue an idle task to check & update the optimal disk layout if
|
|
// necessary. Ignore failure to do so.
|
|
//
|
|
|
|
ErrorCode = PfSvRegisterTask(&LayoutTask,
|
|
ItOptimalDiskLayoutTaskId,
|
|
PfSvCommonTaskCallback,
|
|
PfSvUpdateOptimalLayout);
|
|
|
|
}
|
|
|
|
//
|
|
// Loop forever waiting for traces to process and processing them.
|
|
//
|
|
|
|
while(TRUE) {
|
|
|
|
//
|
|
// Grab queued traces lock to check for queued traces.
|
|
//
|
|
|
|
PFSVC_ASSERT(!AcquiredTracesLock);
|
|
PFSVC_ACQUIRE_LOCK(PfSvcGlobals.TracesLock);
|
|
AcquiredTracesLock = TRUE;
|
|
|
|
if (!IsListEmpty(&PfSvcGlobals.Traces)) {
|
|
|
|
//
|
|
// Dequeue and process the first entry in the list.
|
|
//
|
|
|
|
HeadEntry = RemoveHeadList(&PfSvcGlobals.Traces);
|
|
|
|
TraceBuffer = CONTAINING_RECORD(HeadEntry,
|
|
PFSVC_TRACE_BUFFER,
|
|
TracesLink);
|
|
|
|
|
|
PFSVC_ASSERT(PfSvcGlobals.NumTraces);
|
|
OrgNumQueuedTraces = PfSvcGlobals.NumTraces;
|
|
PfSvcGlobals.NumTraces--;
|
|
|
|
//
|
|
// Release the lock.
|
|
//
|
|
|
|
PFSVC_RELEASE_LOCK(PfSvcGlobals.TracesLock);
|
|
AcquiredTracesLock = FALSE;
|
|
|
|
//
|
|
// If we had maxed the queue, note to check for traces
|
|
// that we may have failed to pick up because the queue
|
|
// was full.
|
|
//
|
|
|
|
if (OrgNumQueuedTraces == PFSVC_MAX_NUM_QUEUED_TRACES) {
|
|
SetEvent(PfSvcGlobals.CheckForMissedTracesEvent);
|
|
|
|
//
|
|
// Let the thread that queries the kernel for traces
|
|
// wake up and run.
|
|
//
|
|
|
|
Sleep(0);
|
|
}
|
|
|
|
//
|
|
// Clear the event that says we don't have traces to
|
|
// process.
|
|
//
|
|
|
|
ResetEvent(PfSvcGlobals.ProcessingCompleteEvent);
|
|
|
|
//
|
|
// If this is a boot trace, wait for a little while for
|
|
// boot to be really over before processing it.
|
|
//
|
|
|
|
if (TraceBuffer->Trace.ScenarioType == PfSystemBootScenarioType) {
|
|
|
|
BootTraceEvents[0] = PfSvcGlobals.TerminateServiceEvent;
|
|
BootTraceEvents[1] = PfSvcGlobals.OverrideIdleProcessingEvent;
|
|
NumEvents = 2;
|
|
|
|
PFSVC_ASSERT(NumEvents <= (sizeof(BootTraceEvents) / sizeof(HANDLE)));
|
|
|
|
WaitResult = WaitForMultipleObjects(NumEvents,
|
|
BootTraceEvents,
|
|
FALSE,
|
|
45000); // 45 seconds.
|
|
|
|
if (WaitResult == WAIT_OBJECT_0) {
|
|
ErrorCode = ERROR_SUCCESS;
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
ErrorCode = PfSvProcessTrace(&TraceBuffer->Trace);
|
|
|
|
//
|
|
// Update statistics.
|
|
//
|
|
|
|
PfSvcGlobals.NumTracesProcessed++;
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
PfSvcGlobals.LastTraceFailure = ErrorCode;
|
|
} else {
|
|
PfSvcGlobals.NumTracesSuccessful++;
|
|
}
|
|
|
|
//
|
|
// Free the trace buffer.
|
|
//
|
|
|
|
VirtualFree(TraceBuffer, 0, MEM_RELEASE);
|
|
TraceBuffer = NULL;
|
|
|
|
//
|
|
// Did we just create too many scenario files in the prefetch directory?
|
|
// Queue an idle task to clean up.
|
|
//
|
|
|
|
if (PfSvcGlobals.NumPrefetchFiles >= PFSVC_MAX_PREFETCH_FILES) {
|
|
|
|
if (!DirectoryCleanupTask.Registered) {
|
|
|
|
//
|
|
// Make sure we've cleaned up after a possible previous run.
|
|
//
|
|
|
|
PfSvCleanupTask(&DirectoryCleanupTask);
|
|
PfSvInitializeTask(&DirectoryCleanupTask);
|
|
|
|
ErrorCode = PfSvRegisterTask(&DirectoryCleanupTask,
|
|
ItPrefetchDirectoryCleanupTaskId,
|
|
PfSvCommonTaskCallback,
|
|
PfSvCleanupPrefetchDirectory);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Every so many scenario launches it is good to see if we should update
|
|
// disk layout.
|
|
//
|
|
|
|
if (((PfSvcGlobals.NumTracesSuccessful + 1) % 32) == 0) {
|
|
|
|
if (PfSvAllowedToRunDefragger(FALSE)) {
|
|
|
|
if (!LayoutTask.Registered) {
|
|
|
|
//
|
|
// Make sure we've cleaned up after a possible previous run.
|
|
//
|
|
|
|
PfSvCleanupTask(&LayoutTask);
|
|
PfSvInitializeTask(&LayoutTask);
|
|
|
|
ErrorCode = PfSvRegisterTask(&LayoutTask,
|
|
ItOptimalDiskLayoutTaskId,
|
|
PfSvCommonTaskCallback,
|
|
PfSvUpdateOptimalLayout);
|
|
}
|
|
}
|
|
}
|
|
|
|
} else {
|
|
|
|
//
|
|
// The list is empty. Signal that we are done with all the
|
|
// queued traces if we don't have idle tasks to complete.
|
|
//
|
|
|
|
if (!LayoutTask.Registered &&
|
|
!DirectoryCleanupTask.Registered) {
|
|
|
|
SetEvent(PfSvcGlobals.ProcessingCompleteEvent);
|
|
}
|
|
|
|
//
|
|
// Release the lock.
|
|
//
|
|
|
|
PFSVC_RELEASE_LOCK(PfSvcGlobals.TracesLock);
|
|
AcquiredTracesLock = FALSE;
|
|
|
|
//
|
|
// Update the statistics if there were new failed traces.
|
|
//
|
|
|
|
if (NumFailedTraces != (PfSvcGlobals.NumTracesProcessed -
|
|
PfSvcGlobals.NumTracesSuccessful)) {
|
|
|
|
NumFailedTraces = PfSvcGlobals.NumTracesProcessed -
|
|
PfSvcGlobals.NumTracesSuccessful;
|
|
|
|
PfSvSaveTraceProcessingStatistics(PfSvcGlobals.ServiceDataKey);
|
|
}
|
|
|
|
//
|
|
// Wait until new traces are queued.
|
|
//
|
|
|
|
DBGPR((PFID,PFWAIT,"PFSVC: ProcessTraceThread()-WaitForTrace\n"));
|
|
|
|
NumEvents = NumCheckForQueuedTracesEvents;
|
|
|
|
WaitResult = WaitForMultipleObjects(NumEvents,
|
|
CheckForQueuedTracesEvents,
|
|
FALSE,
|
|
INFINITE);
|
|
|
|
DBGPR((PFID,PFWAIT,"PFSVC: ProcessTraceThread()-EndWaitForTrace=%x\n", WaitResult));
|
|
|
|
switch(WaitResult) {
|
|
|
|
case WAIT_OBJECT_0:
|
|
|
|
//
|
|
// Service exit event:
|
|
//
|
|
|
|
ErrorCode = ERROR_SUCCESS;
|
|
goto cleanup;
|
|
|
|
break;
|
|
|
|
case WAIT_OBJECT_0 + 1:
|
|
|
|
//
|
|
// New traces queued for processing event:
|
|
//
|
|
|
|
break;
|
|
|
|
case WAIT_OBJECT_0 + 2:
|
|
|
|
//
|
|
// Idle detection was overriden. If we had registered tasks
|
|
// to be run, we will unregister them and run them manually.
|
|
//
|
|
|
|
PfSvSaveTraceProcessingStatistics(PfSvcGlobals.ServiceDataKey);
|
|
|
|
if (LayoutTask.Registered) {
|
|
PfSvUnregisterTask(&LayoutTask, FALSE);
|
|
PfSvCleanupTask(&LayoutTask);
|
|
PfSvInitializeTask(&LayoutTask);
|
|
|
|
PfSvUpdateOptimalLayout(NULL);
|
|
}
|
|
|
|
if (DirectoryCleanupTask.Registered) {
|
|
PfSvUnregisterTask(&DirectoryCleanupTask, FALSE);
|
|
PfSvCleanupTask(&DirectoryCleanupTask);
|
|
PfSvInitializeTask(&DirectoryCleanupTask);
|
|
|
|
PfSvCleanupPrefetchDirectory(NULL);
|
|
}
|
|
|
|
//
|
|
// We will drop out of this block, check & process queued traces
|
|
// and then set the processing complete event.
|
|
//
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
//
|
|
// Something went wrong...
|
|
//
|
|
|
|
ErrorCode = ERROR_INVALID_HANDLE;
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Loop to check if there are new traces.
|
|
//
|
|
}
|
|
|
|
//
|
|
// We should not break out of the loop.
|
|
//
|
|
|
|
PFSVC_ASSERT(FALSE);
|
|
|
|
ErrorCode = ERROR_INVALID_FUNCTION;
|
|
|
|
cleanup:
|
|
|
|
if (AcquiredTracesLock) {
|
|
PFSVC_RELEASE_LOCK(PfSvcGlobals.TracesLock);
|
|
}
|
|
|
|
if (TraceBuffer) {
|
|
VirtualFree(TraceBuffer, 0, MEM_RELEASE);
|
|
}
|
|
|
|
if (BuildDefragStatus) {
|
|
PFSVC_FREE(BuildDefragStatus);
|
|
}
|
|
|
|
PfSvUnregisterTask(&LayoutTask, FALSE);
|
|
PfSvCleanupTask(&LayoutTask);
|
|
|
|
PfSvUnregisterTask(&DirectoryCleanupTask, FALSE);
|
|
PfSvCleanupTask(&DirectoryCleanupTask);
|
|
|
|
DBGPR((PFID,PFTRC,"PFSVC: ProcessTraceThread()=%x,%d\n", ErrorCode, TotalTracesProcessed));
|
|
|
|
return ErrorCode;
|
|
}
|
|
|
|
DWORD
|
|
WINAPI
|
|
PfSvPollShellReadyWorker(
|
|
VOID *Param
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This is the routine for the thread that is spawned to poll the
|
|
ShellReadyEvent.
|
|
|
|
Arguments:
|
|
|
|
Param - Ignored.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
HANDLE ShellReadyEvent;
|
|
HANDLE Events[2];
|
|
ULONG NumEvents;
|
|
ULONG PollPeriod;
|
|
ULONG TotalPollPeriod;
|
|
DWORD WaitResult;
|
|
DWORD ErrorCode;
|
|
NTSTATUS Status;
|
|
PREFETCHER_INFORMATION PrefetcherInformation;
|
|
PF_BOOT_PHASE_ID PhaseId;
|
|
|
|
//
|
|
// Initialize locals.
|
|
//
|
|
|
|
ShellReadyEvent = NULL;
|
|
Events[0] = PfSvcGlobals.TerminateServiceEvent;
|
|
NumEvents = 1;
|
|
|
|
DBGPR((PFID,PFTRC,"PFSVC: PollShellReadyThread()\n"));
|
|
|
|
//
|
|
// Get necessary permissions for this thread to perform prefetch
|
|
// service tasks.
|
|
//
|
|
|
|
ErrorCode = PfSvGetPrefetchServiceThreadPrivileges();
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Until we can open the shell ready event, wait on the service
|
|
// termination event and retry every PollPeriod milliseconds.
|
|
//
|
|
|
|
PollPeriod = 1000;
|
|
TotalPollPeriod = 0;
|
|
|
|
do {
|
|
|
|
//
|
|
// Try to open the shell ready event.
|
|
//
|
|
|
|
ShellReadyEvent = OpenEvent(EVENT_ALL_ACCESS,FALSE,L"ShellReadyEvent");
|
|
|
|
if (ShellReadyEvent) {
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Wait for a while.
|
|
//
|
|
|
|
DBGPR((PFID,PFWAIT,"PFSVC: PollShellReadyThread()-WaitForOpen\n"));
|
|
|
|
WaitResult = WaitForMultipleObjects(NumEvents,
|
|
Events,
|
|
FALSE,
|
|
PollPeriod);
|
|
|
|
DBGPR((PFID,PFWAIT,"PFSVC: PollShellReadyThread()-EndWaitForOpen=%d\n", WaitResult));
|
|
|
|
switch(WaitResult) {
|
|
|
|
case WAIT_OBJECT_0:
|
|
|
|
//
|
|
// Service exit event:
|
|
//
|
|
|
|
ErrorCode = ERROR_PROCESS_ABORTED;
|
|
goto cleanup;
|
|
|
|
break;
|
|
|
|
case WAIT_TIMEOUT:
|
|
|
|
//
|
|
// Fall through and try opening the shell ready event again.
|
|
//
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
//
|
|
// Something gone wrong. Break out, cleanup and exit.
|
|
//
|
|
|
|
ErrorCode = ERROR_INVALID_HANDLE;
|
|
goto cleanup;
|
|
}
|
|
|
|
TotalPollPeriod += PollPeriod;
|
|
|
|
} while (TotalPollPeriod < 180000);
|
|
|
|
//
|
|
// If we could not get the ShellReadyEvent, we timed out.
|
|
//
|
|
|
|
if (ShellReadyEvent == NULL) {
|
|
ErrorCode = ERROR_TIMEOUT;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Wait on the ShellReadyEvent to be signaled.
|
|
//
|
|
|
|
Events[NumEvents] = ShellReadyEvent;
|
|
NumEvents++;
|
|
|
|
DBGPR((PFID,PFWAIT,"PFSVC: PollShellReadyThread()-WaitForShell\n"));
|
|
|
|
WaitResult = WaitForMultipleObjects(NumEvents,
|
|
Events,
|
|
FALSE,
|
|
60000);
|
|
|
|
DBGPR((PFID,PFWAIT,"PFSVC: PollShellReadyThread()-EndWaitForShell=%d\n",WaitResult));
|
|
|
|
switch (WaitResult) {
|
|
|
|
case WAIT_OBJECT_0:
|
|
|
|
//
|
|
// Service exit event:
|
|
//
|
|
|
|
ErrorCode = ERROR_PROCESS_ABORTED;
|
|
goto cleanup;
|
|
|
|
break;
|
|
|
|
case WAIT_OBJECT_0 + 1:
|
|
|
|
//
|
|
// Shell ready event got signaled. Let the kernel mode
|
|
// prefetcher know.
|
|
//
|
|
|
|
PhaseId = PfUserShellReadyPhase;
|
|
|
|
PrefetcherInformation.Magic = PF_SYSINFO_MAGIC_NUMBER;
|
|
PrefetcherInformation.Version = PF_CURRENT_VERSION;
|
|
PrefetcherInformation.PrefetcherInformationClass = PrefetcherBootPhase;
|
|
PrefetcherInformation.PrefetcherInformation = &PhaseId;
|
|
PrefetcherInformation.PrefetcherInformationLength = sizeof(PhaseId);
|
|
|
|
Status = NtSetSystemInformation(SystemPrefetcherInformation,
|
|
&PrefetcherInformation,
|
|
sizeof(PrefetcherInformation));
|
|
|
|
//
|
|
// Fall through with the status.
|
|
//
|
|
|
|
ErrorCode = RtlNtStatusToDosError(Status);
|
|
|
|
break;
|
|
|
|
case WAIT_TIMEOUT:
|
|
|
|
//
|
|
// Shell ready event was created but not signaled...
|
|
//
|
|
|
|
ErrorCode = ERROR_TIMEOUT;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
//
|
|
// Something gone wrong.
|
|
//
|
|
|
|
ErrorCode = GetLastError();
|
|
|
|
if (ErrorCode == ERROR_SUCCESS) {
|
|
ErrorCode = ERROR_INVALID_FUNCTION;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Fall through with status from the switch statement.
|
|
//
|
|
|
|
cleanup:
|
|
|
|
if (ShellReadyEvent) {
|
|
CloseHandle(ShellReadyEvent);
|
|
}
|
|
|
|
DBGPR((PFID,PFTRC,"PFSVC: PollShellReadyThread()=%x\n", ErrorCode));
|
|
|
|
return ErrorCode;
|
|
}
|
|
|
|
//
|
|
// Routines called by the main prefetcher thread.
|
|
//
|
|
|
|
DWORD
|
|
PfSvGetRawTraces(
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine checks for new traces prepared by the kernel. The new
|
|
traces are downloaded and queued so they can be processed.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
DWORD ErrorCode;
|
|
NTSTATUS Status;
|
|
PPFSVC_TRACE_BUFFER TraceBuffer;
|
|
ULONG TraceBufferMaximumLength;
|
|
ULONG TraceBufferLength;
|
|
PREFETCHER_INFORMATION PrefetcherInformation;
|
|
ULONG NumTracesRetrieved;
|
|
ULONG FailedCheck;
|
|
|
|
//
|
|
// Initialize locals.
|
|
//
|
|
|
|
TraceBuffer = NULL;
|
|
TraceBufferMaximumLength = 0;
|
|
NumTracesRetrieved = 0;
|
|
|
|
DBGPR((PFID,PFTRC,"PFSVC: GetRawTraces()\n"));
|
|
|
|
//
|
|
// Clear the event that asks us to check for more traces.
|
|
//
|
|
|
|
ResetEvent(PfSvcGlobals.CheckForMissedTracesEvent);
|
|
|
|
//
|
|
// While we do not already have too many traces to process, get
|
|
// traces from the kernel.
|
|
//
|
|
|
|
while (PfSvcGlobals.NumTraces < PFSVC_MAX_NUM_QUEUED_TRACES) {
|
|
|
|
//
|
|
// Retrieve a trace from the kernel.
|
|
//
|
|
|
|
PrefetcherInformation.Version = PF_CURRENT_VERSION;
|
|
PrefetcherInformation.Magic = PF_SYSINFO_MAGIC_NUMBER;
|
|
PrefetcherInformation.PrefetcherInformationClass = PrefetcherRetrieveTrace;
|
|
PrefetcherInformation.PrefetcherInformation = &TraceBuffer->Trace;
|
|
|
|
if (TraceBufferMaximumLength <= FIELD_OFFSET(PFSVC_TRACE_BUFFER, Trace)) {
|
|
PrefetcherInformation.PrefetcherInformationLength = 0;
|
|
} else {
|
|
PrefetcherInformation.PrefetcherInformationLength =
|
|
TraceBufferMaximumLength - FIELD_OFFSET(PFSVC_TRACE_BUFFER, Trace);
|
|
}
|
|
|
|
Status = NtQuerySystemInformation(SystemPrefetcherInformation,
|
|
&PrefetcherInformation,
|
|
sizeof(PrefetcherInformation),
|
|
&TraceBufferLength);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
|
|
if (Status == STATUS_BUFFER_TOO_SMALL) {
|
|
|
|
if (TraceBuffer != NULL) {
|
|
VirtualFree(TraceBuffer, 0, MEM_RELEASE);
|
|
}
|
|
|
|
//
|
|
// Add room for the header we wrap over it.
|
|
//
|
|
|
|
TraceBufferLength += sizeof(PFSVC_TRACE_BUFFER) - sizeof(PF_TRACE_HEADER);
|
|
|
|
TraceBufferMaximumLength = ROUND_TRACE_BUFFER_SIZE(TraceBufferLength);
|
|
|
|
TraceBuffer = VirtualAlloc(NULL,
|
|
TraceBufferMaximumLength,
|
|
MEM_COMMIT,
|
|
PAGE_READWRITE);
|
|
if (TraceBuffer == NULL) {
|
|
ErrorCode = GetLastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
continue;
|
|
|
|
} else if (Status == STATUS_NO_MORE_ENTRIES) {
|
|
|
|
break;
|
|
}
|
|
|
|
ErrorCode = RtlNtStatusToDosError(Status);
|
|
goto cleanup;
|
|
}
|
|
|
|
#ifdef PFSVC_DBG
|
|
|
|
//
|
|
// Write out the trace to a file:
|
|
//
|
|
|
|
if (PfSvcDbgMaxNumSavedTraces) {
|
|
|
|
WCHAR TraceFilePath[MAX_PATH + 1];
|
|
LONG NumChars;
|
|
|
|
//
|
|
// Build up a file name.
|
|
//
|
|
|
|
InterlockedIncrement(&PfSvcDbgTraceNumber);
|
|
|
|
PFSVC_ACQUIRE_LOCK(PfSvcGlobals.PrefetchRootLock);
|
|
|
|
NumChars = _snwprintf(TraceFilePath,
|
|
MAX_PATH,
|
|
L"%ws\\%ws%d.trc",
|
|
PfSvcGlobals.PrefetchRoot,
|
|
PfSvcDbgTraceBaseName,
|
|
PfSvcDbgTraceNumber % PfSvcDbgMaxNumSavedTraces);
|
|
|
|
PFSVC_RELEASE_LOCK(PfSvcGlobals.PrefetchRootLock);
|
|
|
|
if (NumChars > 0 && NumChars < MAX_PATH) {
|
|
|
|
//
|
|
// Make sure the path is terminated.
|
|
//
|
|
|
|
TraceFilePath[MAX_PATH - 1] = 0;
|
|
|
|
//
|
|
// Write out the trace.
|
|
//
|
|
|
|
PfSvWriteBuffer(TraceFilePath,
|
|
&TraceBuffer->Trace,
|
|
TraceBuffer->Trace.Size);
|
|
}
|
|
}
|
|
|
|
#endif // PFSVC_DBG
|
|
|
|
//
|
|
// Verify integrity of the trace.
|
|
//
|
|
|
|
if (!PfVerifyTraceBuffer(&TraceBuffer->Trace,
|
|
TraceBuffer->Trace.Size,
|
|
&FailedCheck)) {
|
|
DBGPR((PFID,PFWARN,"PFSVC: IGNORING TRACE\n"));
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Put it on the list of traces to process.
|
|
//
|
|
|
|
PFSVC_ACQUIRE_LOCK(PfSvcGlobals.TracesLock);
|
|
|
|
InsertTailList(&PfSvcGlobals.Traces, &TraceBuffer->TracesLink);
|
|
PfSvcGlobals.NumTraces++;
|
|
|
|
PFSVC_RELEASE_LOCK(PfSvcGlobals.TracesLock);
|
|
|
|
//
|
|
// Notify that there are new traces to process.
|
|
//
|
|
|
|
SetEvent(PfSvcGlobals.NewTracesToProcessEvent);
|
|
|
|
//
|
|
// Clean out the loop variables.
|
|
//
|
|
|
|
TraceBuffer = NULL;
|
|
TraceBufferMaximumLength = 0;
|
|
TraceBufferLength = 0;
|
|
|
|
NumTracesRetrieved++;
|
|
}
|
|
|
|
//
|
|
// We should never go above the limit of queued traces.
|
|
//
|
|
|
|
PFSVC_ASSERT(PfSvcGlobals.NumTraces <= PFSVC_MAX_NUM_QUEUED_TRACES);
|
|
|
|
ErrorCode = ERROR_SUCCESS;
|
|
|
|
cleanup:
|
|
|
|
if (TraceBuffer != NULL) {
|
|
VirtualFree(TraceBuffer, 0, MEM_RELEASE);
|
|
}
|
|
|
|
DBGPR((PFID,PFTRC,"PFSVC: GetRawTraces()=%x,%d\n", ErrorCode, NumTracesRetrieved));
|
|
|
|
return ErrorCode;
|
|
}
|
|
|
|
DWORD
|
|
PfSvInitializeGlobals(
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine initializes the global variables / tables etc.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status;
|
|
DWORD ErrorCode;
|
|
ULONG FileIdx;
|
|
WCHAR *CSCRootPath;
|
|
ULONG CSCRootPathMaxChars;
|
|
|
|
//
|
|
// These are the path suffices to recognize files we don't want to
|
|
// prefetch for boot. Keep these sorted lexically going from
|
|
// LAST CHARACTER TO FIRST and UPCASE.
|
|
//
|
|
|
|
static WCHAR *FilesToIgnoreForBoot[] = {
|
|
L"SYSTEM32\\CONFIG\\SOFTWARE",
|
|
L"\\WMI\\TRACE.LOG",
|
|
L"SYSTEM32\\CONFIG\\SOFTWARE.LOG",
|
|
L"SYSTEM32\\CONFIG\\SAM.LOG",
|
|
L"SYSTEM32\\CONFIG\\SYSTEM.LOG",
|
|
L"SYSTEM32\\CONFIG\\DEFAULT.LOG",
|
|
L"SYSTEM32\\CONFIG\\SECURITY.LOG",
|
|
L"\\PERF.ETL",
|
|
L"SYSTEM32\\CONFIG\\SAM",
|
|
L"SYSTEM32\\CONFIG\\SYSTEM",
|
|
L"SYSTEM32\\CONFIG\\SYSTEM.ALT",
|
|
L"SYSTEM32\\CONFIG\\DEFAULT",
|
|
L"SYSTEM32\\CONFIG\\SECURITY",
|
|
};
|
|
|
|
DBGPR((PFID,PFTRC,"PFSVC: InitializeGlobals()\n"));
|
|
|
|
//
|
|
// Zero out the globals structure so we know what to cleanup if
|
|
// the initialization fails in the middle.
|
|
//
|
|
|
|
RtlZeroMemory(&PfSvcGlobals, sizeof(PfSvcGlobals));
|
|
|
|
//
|
|
// Initialize the list of traces to be processed.
|
|
//
|
|
|
|
InitializeListHead(&PfSvcGlobals.Traces);
|
|
PfSvcGlobals.NumTraces = 0;
|
|
|
|
//
|
|
// We have not launched the defragger for anything yet.
|
|
//
|
|
|
|
PfSvcGlobals.DefraggerErrorCode = ERROR_SUCCESS;
|
|
|
|
//
|
|
// Initialize table for registry files that we don't want to
|
|
// prefetch for boot.
|
|
//
|
|
|
|
PfSvcGlobals.FilesToIgnoreForBoot = FilesToIgnoreForBoot;
|
|
PfSvcGlobals.NumFilesToIgnoreForBoot =
|
|
sizeof(FilesToIgnoreForBoot) / sizeof(WCHAR *);
|
|
|
|
//
|
|
// Get OS version information.
|
|
//
|
|
|
|
RtlZeroMemory(&PfSvcGlobals.OsVersion, sizeof(PfSvcGlobals.OsVersion));
|
|
PfSvcGlobals.OsVersion.dwOSVersionInfoSize = sizeof(PfSvcGlobals.OsVersion);
|
|
Status = RtlGetVersion((POSVERSIONINFOW)&PfSvcGlobals.OsVersion);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
DBGPR((PFID,PFERR,"PFSVC: MainThread()-FailedGetOSVersion\n"));
|
|
ErrorCode = RtlNtStatusToDosError(Status);
|
|
goto cleanup;
|
|
}
|
|
|
|
|
|
//
|
|
// Initialize the table of ignored files' suffix lengths.
|
|
//
|
|
|
|
PfSvcGlobals.FileSuffixLengths =
|
|
PFSVC_ALLOC(PfSvcGlobals.NumFilesToIgnoreForBoot * sizeof(ULONG));
|
|
|
|
if (!PfSvcGlobals.FileSuffixLengths) {
|
|
ErrorCode = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto cleanup;
|
|
}
|
|
|
|
for (FileIdx = 0;
|
|
FileIdx < PfSvcGlobals.NumFilesToIgnoreForBoot;
|
|
FileIdx++) {
|
|
|
|
PfSvcGlobals.FileSuffixLengths[FileIdx] =
|
|
wcslen(PfSvcGlobals.FilesToIgnoreForBoot[FileIdx]);
|
|
}
|
|
|
|
//
|
|
// Create an event that will get signaled when the service is
|
|
// exiting.
|
|
//
|
|
|
|
PfSvcGlobals.TerminateServiceEvent = CreateEvent(NULL,
|
|
TRUE,
|
|
FALSE,
|
|
NULL);
|
|
|
|
if (PfSvcGlobals.TerminateServiceEvent == NULL) {
|
|
ErrorCode = GetLastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Initialize the lock for the list of traces to be processed.
|
|
//
|
|
|
|
PfSvcGlobals.TracesLock = CreateMutex(NULL, FALSE, NULL);
|
|
if (PfSvcGlobals.TracesLock == NULL) {
|
|
ErrorCode = GetLastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Initialize the events that are used to communicate between the
|
|
// acquirer and processor of the traces.
|
|
//
|
|
|
|
PfSvcGlobals.NewTracesToProcessEvent = CreateEvent(NULL,
|
|
FALSE,
|
|
FALSE,
|
|
NULL);
|
|
if (PfSvcGlobals.NewTracesToProcessEvent == NULL) {
|
|
ErrorCode = GetLastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
PfSvcGlobals.CheckForMissedTracesEvent = CreateEvent(NULL,
|
|
FALSE,
|
|
FALSE,
|
|
NULL);
|
|
if (PfSvcGlobals.CheckForMissedTracesEvent == NULL) {
|
|
ErrorCode = GetLastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// This named manual-reset event can be set to force all traces to
|
|
// be processed as soon as they become available rather than
|
|
// waiting for the system to become idle first.
|
|
//
|
|
|
|
PfSvcGlobals.OverrideIdleProcessingEvent = CreateEvent(NULL,
|
|
TRUE,
|
|
FALSE,
|
|
PFSVC_OVERRIDE_IDLE_EVENT_NAME);
|
|
if (PfSvcGlobals.OverrideIdleProcessingEvent == NULL) {
|
|
ErrorCode = GetLastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// This named manual-reset event is created signaled. When this
|
|
// event is signaled, it means there are no traces we have to
|
|
// process now.
|
|
//
|
|
|
|
PfSvcGlobals.ProcessingCompleteEvent = CreateEvent(NULL,
|
|
TRUE,
|
|
TRUE,
|
|
PFSVC_PROCESSING_COMPLETE_EVENT_NAME);
|
|
|
|
if (PfSvcGlobals.ProcessingCompleteEvent == NULL) {
|
|
ErrorCode = GetLastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Initialize prefetch root path and the lock to protect it. The
|
|
// real root path will be initialized after parameters are queried
|
|
// from the kernel.
|
|
//
|
|
|
|
PfSvcGlobals.PrefetchRoot[0] = 0;
|
|
PfSvcGlobals.PrefetchRootLock = CreateMutex(NULL, FALSE, NULL);
|
|
if (PfSvcGlobals.PrefetchRootLock == NULL) {
|
|
ErrorCode = GetLastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
PfSvcGlobals.NumPrefetchFiles = 0;
|
|
|
|
//
|
|
// Open the service data registry key, creating it if necessary.
|
|
//
|
|
|
|
ErrorCode = RegCreateKey(HKEY_LOCAL_MACHINE,
|
|
PFSVC_SERVICE_DATA_KEY,
|
|
&PfSvcGlobals.ServiceDataKey);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Check the registry to see if the user does not want us to run
|
|
// the defragger.
|
|
//
|
|
|
|
ErrorCode = PfSvGetDontRunDefragger(&PfSvcGlobals.DontRunDefragger);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
|
|
//
|
|
// By default we will run the defragger.
|
|
//
|
|
|
|
PfSvcGlobals.DontRunDefragger = FALSE;
|
|
}
|
|
|
|
//
|
|
// Determine CSC root path. It won't be used if we can't allocate or
|
|
// determine it, so don't worry about the error code.
|
|
//
|
|
|
|
CSCRootPathMaxChars = MAX_PATH + 1;
|
|
|
|
CSCRootPath = PFSVC_ALLOC(CSCRootPathMaxChars * sizeof(CSCRootPath[0]));
|
|
|
|
if (CSCRootPath) {
|
|
|
|
ErrorCode = PfSvGetCSCRootPath(CSCRootPath, CSCRootPathMaxChars);
|
|
|
|
if (ErrorCode == ERROR_SUCCESS) {
|
|
PfSvcGlobals.CSCRootPath = CSCRootPath;
|
|
}
|
|
}
|
|
|
|
//
|
|
// We are done.
|
|
//
|
|
|
|
ErrorCode = ERROR_SUCCESS;
|
|
|
|
cleanup:
|
|
|
|
DBGPR((PFID,PFTRC,"PFSVC: InitializeGlobals()=%x\n", ErrorCode));
|
|
|
|
return ErrorCode;
|
|
}
|
|
|
|
VOID
|
|
PfSvCleanupGlobals(
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine uninitializes the global variables / tables etc.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
VOID
|
|
|
|
--*/
|
|
|
|
{
|
|
PPFSVC_TRACE_BUFFER TraceBuffer;
|
|
PLIST_ENTRY ListHead;
|
|
PPFSVC_SECTION_NODE SectionNode;
|
|
PPFSVC_PAGE_NODE PageNode;
|
|
|
|
DBGPR((PFID,PFTRC,"PFSVC: CleanupGlobals()\n"));
|
|
|
|
//
|
|
// Free allocated table.
|
|
//
|
|
|
|
if (PfSvcGlobals.FileSuffixLengths) {
|
|
PFSVC_FREE(PfSvcGlobals.FileSuffixLengths);
|
|
}
|
|
|
|
//
|
|
// Free queued traces.
|
|
//
|
|
|
|
while (!IsListEmpty(&PfSvcGlobals.Traces)) {
|
|
|
|
ListHead = RemoveHeadList(&PfSvcGlobals.Traces);
|
|
|
|
PFSVC_ASSERT(PfSvcGlobals.NumTraces);
|
|
PfSvcGlobals.NumTraces--;
|
|
|
|
TraceBuffer = CONTAINING_RECORD(ListHead,
|
|
PFSVC_TRACE_BUFFER,
|
|
TracesLink);
|
|
|
|
VirtualFree(TraceBuffer, 0, MEM_RELEASE);
|
|
}
|
|
|
|
//
|
|
// Close handles to opened events/mutexes.
|
|
//
|
|
|
|
if (PfSvcGlobals.TerminateServiceEvent) {
|
|
CloseHandle(PfSvcGlobals.TerminateServiceEvent);
|
|
}
|
|
|
|
if (PfSvcGlobals.TracesLock) {
|
|
CloseHandle(PfSvcGlobals.TracesLock);
|
|
}
|
|
|
|
if (PfSvcGlobals.NewTracesToProcessEvent) {
|
|
CloseHandle(PfSvcGlobals.NewTracesToProcessEvent);
|
|
}
|
|
|
|
if (PfSvcGlobals.CheckForMissedTracesEvent) {
|
|
CloseHandle(PfSvcGlobals.CheckForMissedTracesEvent);
|
|
}
|
|
|
|
if (PfSvcGlobals.OverrideIdleProcessingEvent) {
|
|
CloseHandle(PfSvcGlobals.OverrideIdleProcessingEvent);
|
|
}
|
|
|
|
if (PfSvcGlobals.ProcessingCompleteEvent) {
|
|
CloseHandle(PfSvcGlobals.ProcessingCompleteEvent);
|
|
}
|
|
|
|
if (PfSvcGlobals.PrefetchRootLock) {
|
|
CloseHandle(PfSvcGlobals.PrefetchRootLock);
|
|
}
|
|
|
|
//
|
|
// Close service data key handle.
|
|
//
|
|
|
|
if (PfSvcGlobals.ServiceDataKey) {
|
|
RegCloseKey(PfSvcGlobals.ServiceDataKey);
|
|
}
|
|
|
|
//
|
|
// Free CSC root path.
|
|
//
|
|
|
|
if (PfSvcGlobals.CSCRootPath) {
|
|
PFSVC_FREE(PfSvcGlobals.CSCRootPath);
|
|
}
|
|
}
|
|
|
|
DWORD
|
|
PfSvGetCSCRootPath (
|
|
WCHAR *CSCRootPath,
|
|
ULONG CSCRootPathMaxChars
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine determines the root path for CSC (client side caching) files.
|
|
|
|
Arguments:
|
|
|
|
CSCRootPath - If successful, a NUL terminated string is copied into this buffer.
|
|
|
|
CSCRootPathMaxChars - Maximum bytes we can copy into CSCRootPath buffer including
|
|
the terminating NUL.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
WCHAR CSCDirName[] = L"CSC";
|
|
HKEY CSCKeyHandle;
|
|
BOOL Success;
|
|
ULONG WindowsDirectoryLength;
|
|
ULONG CSCRootPathLength;
|
|
ULONG RequiredNumChars;
|
|
DWORD ErrorCode;
|
|
DWORD BufferSize;
|
|
DWORD ValueType;
|
|
|
|
//
|
|
// Initialize locals.
|
|
//
|
|
|
|
CSCKeyHandle = NULL;
|
|
|
|
//
|
|
// Open CSC parameters key.
|
|
//
|
|
|
|
ErrorCode = RegOpenKey(HKEY_LOCAL_MACHINE,
|
|
TEXT(REG_STRING_NETCACHE_KEY_A),
|
|
&CSCKeyHandle);
|
|
|
|
if (ErrorCode == ERROR_SUCCESS) {
|
|
|
|
//
|
|
// Query system setting for the CSC root path.
|
|
//
|
|
|
|
BufferSize = CSCRootPathMaxChars * sizeof(CSCRootPath[0]);
|
|
|
|
ErrorCode = RegQueryValueEx(CSCKeyHandle,
|
|
TEXT(REG_STRING_DATABASE_LOCATION_A),
|
|
NULL,
|
|
&ValueType,
|
|
(PVOID)CSCRootPath,
|
|
&BufferSize);
|
|
|
|
if (ErrorCode == ERROR_SUCCESS) {
|
|
|
|
//
|
|
// Sanity check the length.
|
|
//
|
|
|
|
if ((BufferSize / sizeof(CSCRootPath[0])) < MAX_PATH) {
|
|
|
|
//
|
|
// We got what we wanted. Make sure it has room for and is terminated
|
|
// by a slash.
|
|
//
|
|
|
|
CSCRootPathLength = wcslen(CSCRootPath);
|
|
|
|
if (CSCRootPathLength < CSCRootPathMaxChars - 1) {
|
|
|
|
if (CSCRootPath[CSCRootPathLength - 1] != L'\\') {
|
|
CSCRootPath[CSCRootPathLength] = L'\\';
|
|
CSCRootPathLength++;
|
|
CSCRootPath[CSCRootPathLength] = L'\0';
|
|
}
|
|
|
|
ErrorCode = ERROR_SUCCESS;
|
|
goto cleanup;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// If we come here, we have to use the default CSC path i.e. %windir%\CSC
|
|
//
|
|
|
|
WindowsDirectoryLength = GetWindowsDirectory(CSCRootPath,
|
|
CSCRootPathMaxChars - 1);
|
|
|
|
if (WindowsDirectoryLength == 0) {
|
|
|
|
//
|
|
// There was an error.
|
|
//
|
|
|
|
ErrorCode = GetLastError();
|
|
PFSVC_ASSERT(ErrorCode != ERROR_SUCCESS);
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// See if we have room to add \CSC\ and a terminating NUL.
|
|
//
|
|
|
|
RequiredNumChars = WindowsDirectoryLength;
|
|
RequiredNumChars ++; // leading backslash.
|
|
RequiredNumChars += wcslen(CSCDirName); // CSC.
|
|
RequiredNumChars ++; // ending backslash.
|
|
RequiredNumChars ++; // terminating NUL.
|
|
|
|
if (CSCRootPathMaxChars < RequiredNumChars) {
|
|
ErrorCode = ERROR_INSUFFICIENT_BUFFER;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Build up the path:
|
|
//
|
|
|
|
CSCRootPathLength = WindowsDirectoryLength;
|
|
|
|
if (CSCRootPath[CSCRootPathLength - 1] != L'\\') {
|
|
CSCRootPath[CSCRootPathLength] = L'\\';
|
|
CSCRootPathLength++;
|
|
}
|
|
|
|
wcscpy(CSCRootPath + CSCRootPathLength, CSCDirName);
|
|
CSCRootPathLength += wcslen(CSCDirName);
|
|
|
|
CSCRootPath[CSCRootPathLength] = L'\\';
|
|
CSCRootPathLength++;
|
|
|
|
//
|
|
// Terminate the string.
|
|
//
|
|
|
|
CSCRootPath[CSCRootPathLength] = L'\0';
|
|
|
|
//
|
|
// We are done.
|
|
//
|
|
|
|
ErrorCode = ERROR_SUCCESS;
|
|
|
|
cleanup:
|
|
|
|
if (CSCKeyHandle) {
|
|
RegCloseKey(CSCKeyHandle);
|
|
}
|
|
|
|
if (ErrorCode == ERROR_SUCCESS) {
|
|
|
|
//
|
|
// We have the path in CSCRootPath. It should be in the X:\path\
|
|
// format. It should also be somewhat long, otherwise we will mismatch
|
|
// to too many files that we will not prefetch. It should also be
|
|
// terminated by a \ and NUL.
|
|
//
|
|
|
|
PFSVC_ASSERT(CSCRootPathLength < CSCRootPathMaxChars);
|
|
|
|
if ((CSCRootPathLength > 6) &&
|
|
(CSCRootPath[1] == L':') &&
|
|
(CSCRootPath[2] == L'\\') &&
|
|
(CSCRootPath[CSCRootPathLength - 1] == L'\\') &&
|
|
(CSCRootPath[CSCRootPathLength] == L'\0')) {
|
|
|
|
//
|
|
// Remove the X: from the beginning of the path so we can match
|
|
// it to NT paths like \Device\HarddiskVolume1. Note that we have
|
|
// to move the terminating NUL too.
|
|
//
|
|
|
|
MoveMemory(CSCRootPath,
|
|
CSCRootPath + 2,
|
|
(CSCRootPathLength - 1) * sizeof(CSCRootPath[0]));
|
|
|
|
CSCRootPathLength -= 2;
|
|
|
|
//
|
|
// Upcase the path so we don't have to do expensive case insensitive
|
|
// comparisons.
|
|
//
|
|
|
|
_wcsupr(CSCRootPath);
|
|
|
|
} else {
|
|
|
|
ErrorCode = ERROR_BAD_FORMAT;
|
|
}
|
|
}
|
|
|
|
return ErrorCode;
|
|
}
|
|
|
|
DWORD
|
|
PfSvSetPrefetchParameters(
|
|
PPF_SYSTEM_PREFETCH_PARAMETERS Parameters
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine updates the system prefetch parameters in the kernel.
|
|
|
|
Arguments:
|
|
|
|
Parameters - Pointer to parameters structure.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
PREFETCHER_INFORMATION PrefetcherInformation;
|
|
NTSTATUS Status;
|
|
DWORD ErrorCode;
|
|
ULONG Length;
|
|
|
|
PrefetcherInformation.Magic = PF_SYSINFO_MAGIC_NUMBER;
|
|
PrefetcherInformation.Version = PF_CURRENT_VERSION;
|
|
PrefetcherInformation.PrefetcherInformationClass = PrefetcherSystemParameters;
|
|
PrefetcherInformation.PrefetcherInformation = Parameters;
|
|
PrefetcherInformation.PrefetcherInformationLength = sizeof(*Parameters);
|
|
|
|
Status = NtSetSystemInformation(SystemPrefetcherInformation,
|
|
&PrefetcherInformation,
|
|
sizeof(PrefetcherInformation));
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
ErrorCode = RtlNtStatusToDosError(Status);
|
|
goto cleanup;
|
|
}
|
|
|
|
ErrorCode = ERROR_SUCCESS;
|
|
|
|
cleanup:
|
|
|
|
return ErrorCode;
|
|
}
|
|
|
|
DWORD
|
|
PfSvQueryPrefetchParameters(
|
|
PPF_SYSTEM_PREFETCH_PARAMETERS Parameters
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine queries the system prefetch parameters from the kernel.
|
|
The calling thread must have called PfSvGetPrefetchServiceThreadPrivileges.
|
|
|
|
Arguments:
|
|
|
|
Parameters - Pointer to structure to update.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
PREFETCHER_INFORMATION PrefetcherInformation;
|
|
NTSTATUS Status;
|
|
DWORD ErrorCode;
|
|
ULONG Length;
|
|
|
|
PrefetcherInformation.Magic = PF_SYSINFO_MAGIC_NUMBER;
|
|
PrefetcherInformation.Version = PF_CURRENT_VERSION;
|
|
PrefetcherInformation.PrefetcherInformationClass = PrefetcherSystemParameters;
|
|
PrefetcherInformation.PrefetcherInformation = Parameters;
|
|
PrefetcherInformation.PrefetcherInformationLength = sizeof(*Parameters);
|
|
|
|
Status = NtQuerySystemInformation(SystemPrefetcherInformation,
|
|
&PrefetcherInformation,
|
|
sizeof(PrefetcherInformation),
|
|
&Length);
|
|
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
ErrorCode = RtlNtStatusToDosError(Status);
|
|
goto cleanup;
|
|
}
|
|
|
|
ErrorCode = ERROR_SUCCESS;
|
|
|
|
cleanup:
|
|
|
|
return ErrorCode;
|
|
}
|
|
|
|
DWORD
|
|
PfSvInitializePrefetchDirectory(
|
|
WCHAR *PathFromSystemRoot
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine builds up full path for the prefetch instructions
|
|
directory given PathFromSystemRoot, makes sure this directory
|
|
exists, and sets the security information on it. Finally, the
|
|
global PrefetchRoot path is updated with path to the new
|
|
directory.
|
|
|
|
Global NumPrefetchFiles is also updated.
|
|
|
|
The calling thread must have the SE_TAKE_OWNERSHIP_NAME privilege.
|
|
|
|
Arguments:
|
|
|
|
PathFromSystemRoot - Path to the prefetch directory from SystemRoot.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
ULONG PathLength;
|
|
ULONG NumFiles;
|
|
HANDLE DirHandle;
|
|
DWORD ErrorCode;
|
|
DWORD FileAttributes;
|
|
WCHAR FullDirPathBuffer[MAX_PATH + 1];
|
|
|
|
//
|
|
// Initialize locals.
|
|
//
|
|
|
|
DirHandle = INVALID_HANDLE_VALUE;
|
|
|
|
DBGPR((PFID,PFTRC,"PFSVC: InitPrefetchDir(%ws)\n",PathFromSystemRoot));
|
|
|
|
//
|
|
// Build path name to the prefetch files directory.
|
|
// ExpandEnvironmentStrings return length includes space for
|
|
// the terminating NUL character.
|
|
//
|
|
|
|
PathLength = ExpandEnvironmentStrings(L"%SystemRoot%\\",
|
|
FullDirPathBuffer,
|
|
MAX_PATH);
|
|
|
|
|
|
PathLength += wcslen(PathFromSystemRoot);
|
|
|
|
if (PathLength > MAX_PATH) {
|
|
ErrorCode = ERROR_INSUFFICIENT_BUFFER;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Copy the path from system root.
|
|
//
|
|
|
|
wcscat(FullDirPathBuffer, PathFromSystemRoot);
|
|
|
|
//
|
|
// Create the directory if it does not already exist.
|
|
//
|
|
|
|
if (!CreateDirectory(FullDirPathBuffer, NULL)) {
|
|
|
|
ErrorCode = GetLastError();
|
|
|
|
if (ErrorCode == ERROR_ALREADY_EXISTS) {
|
|
|
|
//
|
|
// The directory, or a file with that name may already
|
|
// exist. Make sure it is the former.
|
|
//
|
|
|
|
FileAttributes = GetFileAttributes(FullDirPathBuffer);
|
|
|
|
if (FileAttributes == 0xFFFFFFFF) {
|
|
ErrorCode = GetLastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
if (!(FileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
|
|
ErrorCode = ERROR_CANNOT_MAKE;
|
|
goto cleanup;
|
|
}
|
|
|
|
} else {
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Disable indexing of the prefetch directory.
|
|
//
|
|
|
|
FileAttributes = GetFileAttributes(FullDirPathBuffer);
|
|
|
|
if (FileAttributes == 0xFFFFFFFF) {
|
|
ErrorCode = GetLastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
if (!SetFileAttributes(FullDirPathBuffer,
|
|
FileAttributes | FILE_ATTRIBUTE_NOT_CONTENT_INDEXED)) {
|
|
ErrorCode = GetLastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Set permissions.
|
|
//
|
|
|
|
ErrorCode = PfSvSetAdminOnlyPermissions(FullDirPathBuffer, NULL, SE_FILE_OBJECT);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Count the scenario files in the directory.
|
|
//
|
|
|
|
ErrorCode = PfSvCountFilesInDirectory(FullDirPathBuffer,
|
|
L"*." PF_PREFETCH_FILE_EXTENSION,
|
|
&NumFiles);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Update the global prefetch root directory path.
|
|
//
|
|
|
|
PFSVC_ACQUIRE_LOCK(PfSvcGlobals.PrefetchRootLock);
|
|
|
|
wcscpy(PfSvcGlobals.PrefetchRoot, FullDirPathBuffer);
|
|
PfSvcGlobals.NumPrefetchFiles = NumFiles;
|
|
|
|
PFSVC_RELEASE_LOCK(PfSvcGlobals.PrefetchRootLock);
|
|
|
|
ErrorCode = ERROR_SUCCESS;
|
|
|
|
cleanup:
|
|
|
|
DBGPR((PFID,PFTRC,"PFSVC: InitPrefetchDir(%ws)=%x\n",PathFromSystemRoot,ErrorCode));
|
|
|
|
return ErrorCode;
|
|
}
|
|
|
|
DWORD
|
|
PfSvCountFilesInDirectory(
|
|
WCHAR *DirectoryPath,
|
|
WCHAR *MatchExpression,
|
|
PULONG NumFiles
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This is routine returns the number of files in the specified
|
|
directory whose names match the specified expression.
|
|
|
|
Arguments:
|
|
|
|
DirectoryPath - NULL terminated path to the directory.
|
|
|
|
MatchExpression - Something like "*.pf" Don't go nuts with DOS
|
|
type expressions, this function won't try to transmogrify them.
|
|
|
|
NumFiles - Number of files are returned here. Bogus if returned error.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
IO_STATUS_BLOCK IoStatusBlock;
|
|
OBJECT_ATTRIBUTES ObjectAttributes;
|
|
UNICODE_STRING DirectoryPathU;
|
|
UNICODE_STRING MatchExpressionU;
|
|
HANDLE DirectoryHandle;
|
|
PVOID QueryBuffer;
|
|
PFILE_NAMES_INFORMATION FileInfo;
|
|
ULONG QueryBufferSize;
|
|
ULONG FileCount;
|
|
NTSTATUS Status;
|
|
DWORD ErrorCode;
|
|
BOOLEAN Success;
|
|
BOOLEAN AllocatedDirectoryPathU;
|
|
BOOLEAN OpenedDirectory;
|
|
BOOLEAN RestartScan;
|
|
|
|
//
|
|
// Initialize locals.
|
|
//
|
|
|
|
AllocatedDirectoryPathU = FALSE;
|
|
OpenedDirectory = FALSE;
|
|
QueryBuffer = NULL;
|
|
QueryBufferSize = 0;
|
|
RtlInitUnicodeString(&MatchExpressionU, MatchExpression);
|
|
|
|
DBGPR((PFID,PFTRC,"PFSVC: CountFilesInDirectory(%ws,%ws)\n", DirectoryPath, MatchExpression));
|
|
|
|
//
|
|
// Convert the path to NT path.
|
|
//
|
|
|
|
Success = RtlDosPathNameToNtPathName_U(DirectoryPath,
|
|
&DirectoryPathU,
|
|
NULL,
|
|
NULL);
|
|
|
|
if (!Success) {
|
|
ErrorCode = ERROR_PATH_NOT_FOUND;
|
|
goto cleanup;
|
|
}
|
|
|
|
AllocatedDirectoryPathU = TRUE;
|
|
|
|
//
|
|
// Open the directory.
|
|
//
|
|
|
|
InitializeObjectAttributes(&ObjectAttributes,
|
|
&DirectoryPathU,
|
|
OBJ_CASE_INSENSITIVE,
|
|
NULL,
|
|
NULL);
|
|
|
|
Status = NtOpenFile(&DirectoryHandle,
|
|
FILE_LIST_DIRECTORY | SYNCHRONIZE,
|
|
&ObjectAttributes,
|
|
&IoStatusBlock,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
FILE_DIRECTORY_FILE |
|
|
FILE_SYNCHRONOUS_IO_NONALERT |
|
|
FILE_OPEN_FOR_BACKUP_INTENT);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
ErrorCode = RtlNtStatusToDosError(Status);
|
|
goto cleanup;
|
|
}
|
|
|
|
OpenedDirectory = TRUE;
|
|
|
|
//
|
|
// Allocate a decent sized query buffer.
|
|
//
|
|
|
|
QueryBufferSize = sizeof(FILE_NAMES_INFORMATION) + MAX_PATH * sizeof(WCHAR);
|
|
QueryBufferSize *= 16;
|
|
QueryBuffer = PFSVC_ALLOC(QueryBufferSize);
|
|
|
|
if (!QueryBuffer) {
|
|
ErrorCode = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Loop querying file data. We query FileNamesInformation so
|
|
// we don't have to access file metadata.
|
|
//
|
|
|
|
RestartScan = TRUE;
|
|
FileCount = 0;
|
|
|
|
while (TRUE) {
|
|
|
|
Status = NtQueryDirectoryFile(DirectoryHandle,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
&IoStatusBlock,
|
|
QueryBuffer,
|
|
QueryBufferSize,
|
|
FileNamesInformation,
|
|
FALSE,
|
|
&MatchExpressionU,
|
|
RestartScan);
|
|
|
|
RestartScan = FALSE;
|
|
|
|
//
|
|
// If there are no files that match the format, then we'll get
|
|
// STATUS_NO_SUCH_FILE.
|
|
//
|
|
|
|
if (Status == STATUS_NO_SUCH_FILE && (FileCount == 0)) {
|
|
|
|
//
|
|
// We'll return the fact that there are no such files in the
|
|
// directory.
|
|
//
|
|
|
|
break;
|
|
}
|
|
|
|
if (Status == STATUS_NO_MORE_FILES) {
|
|
|
|
//
|
|
// We are done.
|
|
//
|
|
|
|
break;
|
|
}
|
|
|
|
if (NT_ERROR(Status)) {
|
|
|
|
ErrorCode = RtlNtStatusToDosError(Status);
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Go through the files returned in the buffer.
|
|
//
|
|
|
|
for (FileInfo = QueryBuffer;
|
|
((PUCHAR) FileInfo < ((PUCHAR) QueryBuffer + QueryBufferSize));
|
|
FileInfo = (PVOID) (((PUCHAR) FileInfo) + FileInfo->NextEntryOffset)) {
|
|
|
|
FileCount++;
|
|
|
|
if (!FileInfo->NextEntryOffset) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
*NumFiles = FileCount;
|
|
|
|
ErrorCode = ERROR_SUCCESS;
|
|
|
|
cleanup:
|
|
|
|
DBGPR((PFID,PFTRC,"PFSVC: CountFilesInDirectory(%ws)=%d,%x\n", DirectoryPath, *NumFiles, ErrorCode));
|
|
|
|
if (AllocatedDirectoryPathU) {
|
|
RtlFreeHeap(RtlProcessHeap(), 0, DirectoryPathU.Buffer);
|
|
}
|
|
|
|
if (OpenedDirectory) {
|
|
NtClose(DirectoryHandle);
|
|
}
|
|
|
|
if (QueryBuffer) {
|
|
PFSVC_FREE(QueryBuffer);
|
|
}
|
|
|
|
return ErrorCode;
|
|
}
|
|
|
|
//
|
|
// Routines to process acquired traces:
|
|
//
|
|
|
|
DWORD
|
|
PfSvProcessTrace(
|
|
PPF_TRACE_HEADER Trace
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to process a trace and update the the
|
|
scenario file.
|
|
|
|
Arguments:
|
|
|
|
Trace - Pointer to trace.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
PPF_SCENARIO_HEADER Scenario;
|
|
PFSVC_SCENARIO_INFO ScenarioInfo;
|
|
PPF_SCENARIO_HEADER NewScenHeader;
|
|
WCHAR ScenarioFilePath[MAX_PATH];
|
|
ULONG ScenarioFilePathMaxChars;
|
|
DWORD ErrorCode;
|
|
|
|
//
|
|
// Initialize locals.
|
|
//
|
|
|
|
PfSvInitializeScenarioInfo(&ScenarioInfo,
|
|
&Trace->ScenarioId,
|
|
Trace->ScenarioType);
|
|
|
|
ScenarioFilePathMaxChars = sizeof(ScenarioFilePath) /
|
|
sizeof(ScenarioFilePath[0]);
|
|
|
|
Scenario = NULL;
|
|
|
|
DBGPR((PFID,PFTRC,"PFSVC: ProcessTrace(%p)\n", Trace));
|
|
|
|
//
|
|
// Build file path to existing information for this scenario.
|
|
//
|
|
|
|
ErrorCode = PfSvScenarioGetFilePath(ScenarioFilePath,
|
|
ScenarioFilePathMaxChars,
|
|
&Trace->ScenarioId);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
|
|
//
|
|
// The buffer we specified should have been big enough. This call
|
|
// should not fail.
|
|
//
|
|
|
|
PFSVC_ASSERT(ErrorCode == ERROR_SUCCESS);
|
|
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Map and verify scenario file if it exists. If we cannot open it,
|
|
// NULL Scenario should be returned.
|
|
//
|
|
|
|
ErrorCode = PfSvScenarioOpen(ScenarioFilePath,
|
|
&Trace->ScenarioId,
|
|
Trace->ScenarioType,
|
|
&Scenario);
|
|
|
|
PFSVC_ASSERT(Scenario || ErrorCode);
|
|
|
|
//
|
|
// Allocate memory upfront for trace & scenario processing.
|
|
//
|
|
|
|
ErrorCode = PfSvScenarioInfoPreallocate(&ScenarioInfo,
|
|
Scenario,
|
|
Trace);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Incorporate information from any existing scenario file.
|
|
//
|
|
|
|
if (Scenario) {
|
|
|
|
ErrorCode = PfSvAddExistingScenarioInfo(&ScenarioInfo, Scenario);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Unmap the scenario so we can write over it when done.
|
|
//
|
|
|
|
UnmapViewOfFile(Scenario);
|
|
Scenario = NULL;
|
|
}
|
|
|
|
//
|
|
// If this is the first launch of this scenario, it is likely that we
|
|
// will create a new scenario file for it.
|
|
//
|
|
|
|
if (ScenarioInfo.ScenHeader.NumLaunches == 1) {
|
|
|
|
//
|
|
// Do we already have too many scenario files in the prefetch directory?
|
|
//
|
|
|
|
if (PfSvcGlobals.NumPrefetchFiles > PFSVC_MAX_PREFETCH_FILES) {
|
|
|
|
//
|
|
// If this is not the boot scenario, we'll ignore it. We don't
|
|
// create new scenario files until we clean up the old ones.
|
|
//
|
|
|
|
if (ScenarioInfo.ScenHeader.ScenarioType != PfSystemBootScenarioType) {
|
|
|
|
#ifndef PFSVC_DBG
|
|
|
|
ErrorCode = ERROR_TOO_MANY_OPEN_FILES;
|
|
goto cleanup;
|
|
|
|
#endif // !PFSVC_DBG
|
|
}
|
|
}
|
|
|
|
PfSvcGlobals.NumPrefetchFiles++;
|
|
}
|
|
|
|
//
|
|
// Verify that volume magics from existing scenario match those in
|
|
// the new trace. If volumes change beneath us we'd need to fix
|
|
// file paths in the existing scenario. But that is too much work,
|
|
// so for now we just start new.
|
|
//
|
|
|
|
if (!PfSvVerifyVolumeMagics(&ScenarioInfo, Trace)) {
|
|
|
|
PfSvCleanupScenarioInfo(&ScenarioInfo);
|
|
|
|
PfSvInitializeScenarioInfo(&ScenarioInfo,
|
|
&Trace->ScenarioId,
|
|
Trace->ScenarioType);
|
|
|
|
ErrorCode = PfSvScenarioInfoPreallocate(&ScenarioInfo,
|
|
NULL,
|
|
Trace);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Also delete the existing scenario instruction in case we
|
|
// fail to update them since they are invalid now.
|
|
//
|
|
|
|
DeleteFile(ScenarioFilePath);
|
|
}
|
|
|
|
//
|
|
// Merge information from new trace.
|
|
//
|
|
|
|
ErrorCode = PfSvAddTraceInfo(&ScenarioInfo, Trace);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Decide which pages to actually prefetch next time, and
|
|
// eliminate uninteresting sections and pages.
|
|
//
|
|
|
|
ErrorCode = PfSvApplyPrefetchPolicy(&ScenarioInfo);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// If no pages/sections are left in the scenario after applying
|
|
// the policy, we'll delete the scenario file.
|
|
//
|
|
|
|
if (ScenarioInfo.ScenHeader.NumSections == 0 ||
|
|
ScenarioInfo.ScenHeader.NumPages == 0) {
|
|
|
|
//
|
|
// We cannot have sections without pages or vice versa.
|
|
//
|
|
|
|
PFSVC_ASSERT(ScenarioInfo.ScenHeader.NumSections == 0);
|
|
PFSVC_ASSERT(ScenarioInfo.ScenHeader.NumPages == 0);
|
|
|
|
//
|
|
// Remove the scenario file.
|
|
//
|
|
|
|
DeleteFile(ScenarioFilePath);
|
|
|
|
ErrorCode = ERROR_BAD_FORMAT;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Sort remaining sections by first access.
|
|
//
|
|
|
|
ErrorCode = PfSvSortSectionNodesByFirstAccess(&ScenarioInfo.SectionList);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Write out new scenario file.
|
|
//
|
|
|
|
ErrorCode = PfSvWriteScenario(&ScenarioInfo, ScenarioFilePath);
|
|
|
|
//
|
|
// Fall through with status.
|
|
//
|
|
|
|
cleanup:
|
|
|
|
PfSvCleanupScenarioInfo(&ScenarioInfo);
|
|
|
|
if (Scenario) {
|
|
UnmapViewOfFile(Scenario);
|
|
}
|
|
|
|
DBGPR((PFID,PFTRC,"PFSVC: ProcessTrace(%p)=%x\n", Trace, ErrorCode));
|
|
|
|
return ErrorCode;
|
|
}
|
|
|
|
VOID
|
|
PfSvInitializeScenarioInfo (
|
|
PPFSVC_SCENARIO_INFO ScenarioInfo,
|
|
PPF_SCENARIO_ID ScenarioId,
|
|
PF_SCENARIO_TYPE ScenarioType
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine initializes the specified new scenario structure. It
|
|
sets the fields of the embedded scenario header as if no previous
|
|
scenario information is available.
|
|
|
|
Arguments:
|
|
|
|
ScenarioInfo - Pointer to structure to initialize.
|
|
|
|
ScenarioId & ScenarioType - Identifiers for the scenario.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
//
|
|
// Initialize ScenarioInfo so we know what to cleanup. Zeroing the structure
|
|
// takes care of the following fields:
|
|
// OneBigAllocation
|
|
// NewPages
|
|
// HitPages
|
|
// MissedOpportunityPages
|
|
// IgnoredPages
|
|
// PrefetchedPages
|
|
//
|
|
|
|
RtlZeroMemory(ScenarioInfo, sizeof(PFSVC_SCENARIO_INFO));
|
|
InitializeListHead(&ScenarioInfo->SectionList);
|
|
InitializeListHead(&ScenarioInfo->VolumeList);
|
|
PfSvChunkAllocatorInitialize(&ScenarioInfo->SectionNodeAllocator);
|
|
PfSvChunkAllocatorInitialize(&ScenarioInfo->PageNodeAllocator);
|
|
PfSvChunkAllocatorInitialize(&ScenarioInfo->VolumeNodeAllocator);
|
|
PfSvStringAllocatorInitialize(&ScenarioInfo->PathAllocator);
|
|
|
|
//
|
|
// Initialize the embedded scenario header.
|
|
//
|
|
|
|
ScenarioInfo->ScenHeader.Version = PF_CURRENT_VERSION;
|
|
ScenarioInfo->ScenHeader.MagicNumber = PF_SCENARIO_MAGIC_NUMBER;
|
|
ScenarioInfo->ScenHeader.ServiceVersion = PFSVC_SERVICE_VERSION;
|
|
ScenarioInfo->ScenHeader.Size = 0;
|
|
ScenarioInfo->ScenHeader.ScenarioId = *ScenarioId;
|
|
ScenarioInfo->ScenHeader.ScenarioType = ScenarioType;
|
|
ScenarioInfo->ScenHeader.NumSections = 0;
|
|
ScenarioInfo->ScenHeader.NumPages = 0;
|
|
ScenarioInfo->ScenHeader.FileNameInfoSize = 0;
|
|
ScenarioInfo->ScenHeader.NumLaunches = 1;
|
|
ScenarioInfo->ScenHeader.Sensitivity = PF_MIN_SENSITIVITY;
|
|
|
|
//
|
|
// These fields help us not prefetch if a scenario is getting
|
|
// launched too frequently. RePrefetchTime and ReTraceTime's get
|
|
// set to default values after the scenario is launched a number
|
|
// of times. This allows training scenarios run after clearing the
|
|
// prefetch cache to be traced correctly.
|
|
//
|
|
|
|
ScenarioInfo->ScenHeader.LastLaunchTime.QuadPart = 0;
|
|
ScenarioInfo->ScenHeader.MinRePrefetchTime.QuadPart = 0;
|
|
ScenarioInfo->ScenHeader.MinReTraceTime.QuadPart = 0;
|
|
|
|
return;
|
|
}
|
|
|
|
VOID
|
|
PfSvCleanupScenarioInfo(
|
|
PPFSVC_SCENARIO_INFO ScenarioInfo
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function cleans up a scenario info structure. It does not
|
|
free the structure itself. The structure should have been
|
|
initialized by PfSvInitializeScenarioInfo.
|
|
|
|
Arguments:
|
|
|
|
ScenarioInfo - Pointer to structure.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PPFSVC_SECTION_NODE SectionNode;
|
|
PLIST_ENTRY SectListEntry;
|
|
PPFSVC_VOLUME_NODE VolumeNode;
|
|
PLIST_ENTRY VolumeListEntry;
|
|
|
|
//
|
|
// Walk through the volume nodes and free them. Do this before
|
|
// freeing section nodes, so when we are trying to cleanup a
|
|
// section node, it is not on a volume node's list.
|
|
//
|
|
|
|
while (!IsListEmpty(&ScenarioInfo->VolumeList)) {
|
|
|
|
VolumeListEntry = RemoveHeadList(&ScenarioInfo->VolumeList);
|
|
|
|
VolumeNode = CONTAINING_RECORD(VolumeListEntry,
|
|
PFSVC_VOLUME_NODE,
|
|
VolumeLink);
|
|
|
|
//
|
|
// Cleanup the volume node.
|
|
//
|
|
|
|
PfSvCleanupVolumeNode(ScenarioInfo, VolumeNode);
|
|
|
|
//
|
|
// Free the volume node.
|
|
//
|
|
|
|
PfSvChunkAllocatorFree(&ScenarioInfo->VolumeNodeAllocator, VolumeNode);
|
|
}
|
|
|
|
//
|
|
// Walk through the section nodes and free them.
|
|
//
|
|
|
|
while (!IsListEmpty(&ScenarioInfo->SectionList)) {
|
|
|
|
SectListEntry = RemoveHeadList(&ScenarioInfo->SectionList);
|
|
|
|
SectionNode = CONTAINING_RECORD(SectListEntry,
|
|
PFSVC_SECTION_NODE,
|
|
SectionLink);
|
|
|
|
//
|
|
// Cleanup the section node.
|
|
//
|
|
|
|
PfSvCleanupSectionNode(ScenarioInfo, SectionNode);
|
|
|
|
//
|
|
// Free the section node.
|
|
//
|
|
|
|
PfSvChunkAllocatorFree(&ScenarioInfo->SectionNodeAllocator, SectionNode);
|
|
}
|
|
|
|
//
|
|
// Cleanup allocators.
|
|
//
|
|
|
|
PfSvChunkAllocatorCleanup(&ScenarioInfo->SectionNodeAllocator);
|
|
PfSvChunkAllocatorCleanup(&ScenarioInfo->PageNodeAllocator);
|
|
PfSvChunkAllocatorCleanup(&ScenarioInfo->VolumeNodeAllocator);
|
|
PfSvStringAllocatorCleanup(&ScenarioInfo->PathAllocator);
|
|
|
|
//
|
|
// Free the one big allocation we made.
|
|
//
|
|
|
|
if (ScenarioInfo->OneBigAllocation) {
|
|
PFSVC_FREE(ScenarioInfo->OneBigAllocation);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
DWORD
|
|
PfSvScenarioGetFilePath(
|
|
OUT PWCHAR FilePath,
|
|
IN ULONG FilePathMaxChars,
|
|
IN PPF_SCENARIO_ID ScenarioId
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine builds the file path for the specified scenario.
|
|
|
|
Arguments:
|
|
|
|
FilePath - Output buffer.
|
|
|
|
FilePathMaxChars - Size of FilePath buffer in characters including NUL.
|
|
|
|
ScenarioId - Scenario identifier.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
ULONG NumChars;
|
|
DWORD ErrorCode;
|
|
WCHAR ScenarioFileName[PF_MAX_SCENARIO_FILE_NAME];
|
|
BOOLEAN AcquiredPrefetchRootLock;
|
|
|
|
//
|
|
// Get the lock so the path to prefetch folder does not change
|
|
// beneath our feet.
|
|
//
|
|
|
|
PFSVC_ACQUIRE_LOCK(PfSvcGlobals.PrefetchRootLock);
|
|
AcquiredPrefetchRootLock = TRUE;
|
|
|
|
//
|
|
// Calculate how big an input buffer we will need.
|
|
//
|
|
|
|
NumChars = wcslen(PfSvcGlobals.PrefetchRoot);
|
|
NumChars += wcslen(L"\\");
|
|
NumChars += PF_MAX_SCENARIO_FILE_NAME;
|
|
|
|
if (NumChars >= FilePathMaxChars) {
|
|
ErrorCode = ERROR_INSUFFICIENT_BUFFER;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Build the scenario file name from scenario identifier.
|
|
//
|
|
|
|
swprintf(ScenarioFileName,
|
|
PF_SCEN_FILE_NAME_FORMAT,
|
|
ScenarioId->ScenName,
|
|
ScenarioId->HashId,
|
|
PF_PREFETCH_FILE_EXTENSION);
|
|
|
|
//
|
|
// Build file path from prefetch directory path and file name.
|
|
//
|
|
|
|
swprintf(FilePath,
|
|
L"%ws\\%ws",
|
|
PfSvcGlobals.PrefetchRoot,
|
|
ScenarioFileName);
|
|
|
|
PFSVC_ASSERT(wcslen(FilePath) < FilePathMaxChars);
|
|
|
|
PFSVC_RELEASE_LOCK(PfSvcGlobals.PrefetchRootLock);
|
|
AcquiredPrefetchRootLock = FALSE;
|
|
|
|
ErrorCode = ERROR_SUCCESS;
|
|
|
|
cleanup:
|
|
|
|
if (AcquiredPrefetchRootLock) {
|
|
PFSVC_RELEASE_LOCK(PfSvcGlobals.PrefetchRootLock);
|
|
}
|
|
|
|
return ErrorCode;
|
|
}
|
|
|
|
DWORD
|
|
PfSvScenarioOpen (
|
|
IN PWCHAR FilePath,
|
|
IN PPF_SCENARIO_ID ScenarioId,
|
|
IN PF_SCENARIO_TYPE ScenarioType,
|
|
OUT PPF_SCENARIO_HEADER *Scenario
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine maps & verifies the scenario instructions at FilePath.
|
|
|
|
If a Scenario is returned, caller has to call UnmapViewOfFile to cleanup.
|
|
|
|
Arguments:
|
|
|
|
FilePath - Path to scenario instructions.
|
|
|
|
Scenario - Pointer to base of mapping of scenario instructions or NULL
|
|
if the function returns an error.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
PPF_SCENARIO_HEADER OpenedScenario;
|
|
DWORD FailedCheck;
|
|
DWORD ErrorCode;
|
|
DWORD FileSize;
|
|
|
|
//
|
|
// Initialize locals.
|
|
//
|
|
|
|
OpenedScenario = NULL;
|
|
|
|
//
|
|
// Initialize output parameters.
|
|
//
|
|
|
|
*Scenario = NULL;
|
|
|
|
//
|
|
// Try to map the scenario file.
|
|
//
|
|
|
|
ErrorCode = PfSvGetViewOfFile(FilePath,
|
|
&OpenedScenario,
|
|
&FileSize);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Verify the scenario file.
|
|
//
|
|
|
|
FailedCheck = 0;
|
|
|
|
if (!PfSvVerifyScenarioBuffer(OpenedScenario, FileSize, &FailedCheck) ||
|
|
(OpenedScenario->ScenarioType != ScenarioType) ||
|
|
OpenedScenario->ServiceVersion != PFSVC_SERVICE_VERSION) {
|
|
|
|
//
|
|
// This is a bogus / wrong / outdated scenario file. Remove
|
|
// it.
|
|
//
|
|
|
|
UnmapViewOfFile(OpenedScenario);
|
|
OpenedScenario = NULL;
|
|
|
|
DeleteFile(FilePath);
|
|
|
|
ErrorCode = ERROR_BAD_FORMAT;
|
|
goto cleanup;
|
|
}
|
|
|
|
*Scenario = OpenedScenario;
|
|
ErrorCode = ERROR_SUCCESS;
|
|
|
|
cleanup:
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
|
|
if (OpenedScenario) {
|
|
UnmapViewOfFile(OpenedScenario);
|
|
}
|
|
|
|
*Scenario = NULL;
|
|
|
|
} else {
|
|
|
|
//
|
|
// If we are returning success we should be returning a valid Scenario.
|
|
//
|
|
|
|
PFSVC_ASSERT(*Scenario);
|
|
}
|
|
|
|
return ErrorCode;
|
|
}
|
|
|
|
DWORD
|
|
PfSvScenarioInfoPreallocate(
|
|
IN PPFSVC_SCENARIO_INFO ScenarioInfo,
|
|
OPTIONAL IN PPF_SCENARIO_HEADER Scenario,
|
|
OPTIONAL IN PPF_TRACE_HEADER Trace
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine preallocates a heap to be divided up and used by the
|
|
various allocators when processing a prefetch trace. The default allocation
|
|
size is determined from the Trace and Scenario size.
|
|
|
|
Arguments:
|
|
|
|
ScenarioInfo - Pointer to scenario containing allocators to initialize.
|
|
|
|
Scenario - Pointer to scenario instructions.
|
|
|
|
Trace - Pointer to prefetch trace.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
PUCHAR Allocation;
|
|
PUCHAR ChunkStart;
|
|
DWORD ErrorCode;
|
|
ULONG AllocationSize;
|
|
ULONG NumSections;
|
|
ULONG NumPages;
|
|
ULONG NumVolumes;
|
|
ULONG PathSize;
|
|
|
|
//
|
|
// Initialize locals.
|
|
//
|
|
|
|
Allocation = NULL;
|
|
NumSections = 0;
|
|
NumPages = 0;
|
|
NumVolumes = 0;
|
|
PathSize = 0;
|
|
|
|
//
|
|
// Estimate how much to preallocate. Over-estimate rather than under-
|
|
// estimate because we will have to go to the heap for individual allocations
|
|
// if we underestimate. If we overestimate, as long as we don't touch the extra
|
|
// pages allocated we don't get a hit.
|
|
//
|
|
|
|
if (Trace) {
|
|
NumSections += Trace->NumSections;
|
|
}
|
|
|
|
if (Scenario) {
|
|
NumSections += Scenario->NumSections;
|
|
}
|
|
|
|
if (Trace) {
|
|
NumPages += Trace->NumEntries;
|
|
}
|
|
|
|
if (Scenario) {
|
|
NumPages += Scenario->NumPages;
|
|
}
|
|
|
|
if (Trace) {
|
|
NumVolumes += Trace->NumVolumes;
|
|
}
|
|
|
|
if (Scenario) {
|
|
|
|
NumVolumes += Scenario->NumMetadataRecords;
|
|
|
|
//
|
|
// It is very likely that we will at least share the volume containing the
|
|
// main executables between the trace and existing scenario instructions.
|
|
// So if we have both Trace and Scenario take one volume node off the estimate.
|
|
//
|
|
|
|
if (Trace) {
|
|
PFSVC_ASSERT(NumVolumes);
|
|
NumVolumes--;
|
|
}
|
|
}
|
|
|
|
//
|
|
// It is hard to estimate how much we will allocate for various paths
|
|
// e.g. file paths & each level of parent directory paths etc. It should be less
|
|
// than the size of the total trace, although it probably makes up most of it.
|
|
//
|
|
|
|
if (Trace) {
|
|
PathSize = Trace->Size;
|
|
}
|
|
|
|
if (Scenario) {
|
|
PathSize += Scenario->FileNameInfoSize;
|
|
PathSize += Scenario->MetadataInfoSize;
|
|
}
|
|
|
|
//
|
|
// Add it all up.
|
|
//
|
|
|
|
AllocationSize = 0;
|
|
AllocationSize += _alignof(PFSVC_VOLUME_NODE);
|
|
AllocationSize += NumVolumes * sizeof(PFSVC_VOLUME_NODE);
|
|
AllocationSize += _alignof(PFSVC_SECTION_NODE);
|
|
AllocationSize += NumSections * sizeof(PFSVC_SECTION_NODE);
|
|
AllocationSize += _alignof(PFSVC_PAGE_NODE);
|
|
AllocationSize += NumPages * sizeof(PFSVC_PAGE_NODE);
|
|
AllocationSize += PathSize;
|
|
|
|
//
|
|
// Make one big allocation.
|
|
//
|
|
|
|
Allocation = PFSVC_ALLOC(AllocationSize);
|
|
|
|
if (!Allocation) {
|
|
ErrorCode = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Divide up the big allocation. Since we are providing the buffers,
|
|
// allocators should not fail.
|
|
//
|
|
|
|
ChunkStart = Allocation;
|
|
|
|
//
|
|
// Volume nodes.
|
|
//
|
|
|
|
ErrorCode = PfSvChunkAllocatorStart(&ScenarioInfo->VolumeNodeAllocator,
|
|
ChunkStart,
|
|
sizeof(PFSVC_VOLUME_NODE),
|
|
NumVolumes);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
PFSVC_ASSERT(ErrorCode == ERROR_SUCCESS);
|
|
goto cleanup;
|
|
}
|
|
|
|
ChunkStart += (ULONG_PTR) NumVolumes * sizeof(PFSVC_VOLUME_NODE);
|
|
|
|
//
|
|
// Section nodes.
|
|
//
|
|
|
|
ChunkStart = PF_ALIGN_UP(ChunkStart, _alignof(PFSVC_SECTION_NODE));
|
|
|
|
ErrorCode = PfSvChunkAllocatorStart(&ScenarioInfo->SectionNodeAllocator,
|
|
ChunkStart,
|
|
sizeof(PFSVC_SECTION_NODE),
|
|
NumSections);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
PFSVC_ASSERT(ErrorCode == ERROR_SUCCESS);
|
|
goto cleanup;
|
|
}
|
|
|
|
ChunkStart += (ULONG_PTR) NumSections * sizeof(PFSVC_SECTION_NODE);
|
|
|
|
//
|
|
// Page nodes.
|
|
//
|
|
|
|
ChunkStart = PF_ALIGN_UP(ChunkStart, _alignof(PFSVC_PAGE_NODE));
|
|
|
|
ErrorCode = PfSvChunkAllocatorStart(&ScenarioInfo->PageNodeAllocator,
|
|
ChunkStart,
|
|
sizeof(PFSVC_PAGE_NODE),
|
|
NumPages);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
PFSVC_ASSERT(ErrorCode == ERROR_SUCCESS);
|
|
goto cleanup;
|
|
}
|
|
|
|
ChunkStart += (ULONG_PTR) NumPages * sizeof(PFSVC_PAGE_NODE);
|
|
|
|
//
|
|
// Path names.
|
|
//
|
|
|
|
ErrorCode = PfSvStringAllocatorStart(&ScenarioInfo->PathAllocator,
|
|
ChunkStart,
|
|
PathSize);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
PFSVC_ASSERT(ErrorCode == ERROR_SUCCESS);
|
|
goto cleanup;
|
|
}
|
|
|
|
ChunkStart += (ULONG_PTR) PathSize;
|
|
|
|
//
|
|
// We should not have passed beyond what we allocated.
|
|
//
|
|
|
|
PFSVC_ASSERT(ChunkStart > (PUCHAR) Allocation);
|
|
PFSVC_ASSERT(ChunkStart < (PUCHAR) Allocation + (ULONG_PTR) AllocationSize);
|
|
|
|
ScenarioInfo->OneBigAllocation = Allocation;
|
|
|
|
ErrorCode = ERROR_SUCCESS;
|
|
|
|
cleanup:
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
if (Allocation) {
|
|
PFSVC_FREE(Allocation);
|
|
}
|
|
}
|
|
|
|
return ErrorCode;
|
|
}
|
|
|
|
DWORD
|
|
PfSvAddExistingScenarioInfo(
|
|
PPFSVC_SCENARIO_INFO ScenarioInfo,
|
|
PPF_SCENARIO_HEADER Scenario
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function gets existing scenario information for the specified
|
|
scenario and updates ScenarioInfo.
|
|
|
|
Arguments:
|
|
|
|
ScenarioInfo - Initialized scenario info structure.
|
|
|
|
Scenario - Pointer to mapped scenario instructions.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
DWORD ErrorCode;
|
|
ULONG FileSize;
|
|
PPFSVC_SECTION_NODE SectionNode;
|
|
PPFSVC_PAGE_NODE PageNode;
|
|
PPF_SECTION_RECORD Sections;
|
|
PPF_SECTION_RECORD SectionRecord;
|
|
PPF_PAGE_RECORD Pages;
|
|
PCHAR FileNameInfo;
|
|
WCHAR *FileName;
|
|
ULONG FileNameSize;
|
|
LONG PageIdx;
|
|
ULONG SectionIdx;
|
|
ULONG NumPages;
|
|
PCHAR MetadataInfoBase;
|
|
PPF_METADATA_RECORD MetadataRecordTable;
|
|
PPF_METADATA_RECORD MetadataRecord;
|
|
ULONG MetadataRecordIdx;
|
|
ULONG FailedCheck;
|
|
PWCHAR VolumePath;
|
|
|
|
//
|
|
// Copy over the existing scenario header.
|
|
//
|
|
|
|
ScenarioInfo->ScenHeader = *Scenario;
|
|
|
|
//
|
|
// Update number of launches.
|
|
//
|
|
|
|
ScenarioInfo->ScenHeader.NumLaunches++;
|
|
|
|
//
|
|
// Convert the scenario data into intermediate data structures
|
|
// we can manipulate easier:
|
|
//
|
|
|
|
//
|
|
// Create volume nodes from metadata records.
|
|
//
|
|
|
|
MetadataInfoBase = (PCHAR)Scenario + Scenario->MetadataInfoOffset;
|
|
MetadataRecordTable = (PPF_METADATA_RECORD) MetadataInfoBase;
|
|
|
|
for (MetadataRecordIdx = 0;
|
|
MetadataRecordIdx < Scenario->NumMetadataRecords;
|
|
MetadataRecordIdx++) {
|
|
|
|
MetadataRecord = &MetadataRecordTable[MetadataRecordIdx];
|
|
VolumePath = (PWCHAR)(MetadataInfoBase + MetadataRecord->VolumeNameOffset);
|
|
|
|
ErrorCode = PfSvCreateVolumeNode(ScenarioInfo,
|
|
VolumePath,
|
|
MetadataRecord->VolumeNameLength,
|
|
&MetadataRecord->CreationTime,
|
|
MetadataRecord->SerialNumber);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Convert page and section nodes.
|
|
//
|
|
|
|
Sections = (PPF_SECTION_RECORD) ((PCHAR)Scenario + Scenario->SectionInfoOffset);
|
|
Pages = (PPF_PAGE_RECORD) ((PCHAR)Scenario + Scenario->PageInfoOffset);
|
|
FileNameInfo = (PCHAR)Scenario + Scenario->FileNameInfoOffset;
|
|
|
|
for (SectionIdx = 0; SectionIdx < Scenario->NumSections; SectionIdx++) {
|
|
|
|
//
|
|
// Build a section node from this section record in the
|
|
// scenario file. PfSvGetSectionRecord will insert it into
|
|
// the new scenario by the section record's name.
|
|
//
|
|
|
|
SectionRecord = &Sections[SectionIdx];
|
|
FileName = (PWSTR) (FileNameInfo + SectionRecord->FileNameOffset);
|
|
|
|
SectionNode = PfSvGetSectionRecord (ScenarioInfo,
|
|
FileName,
|
|
SectionRecord->FileNameLength);
|
|
|
|
if (!SectionNode) {
|
|
ErrorCode = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// There should not be duplicate sections in the
|
|
// scenario. The section node we got should have an empty
|
|
// section record.
|
|
//
|
|
|
|
PFSVC_ASSERT(SectionNode->SectionRecord.FirstPageIdx == 0);
|
|
PFSVC_ASSERT(SectionNode->SectionRecord.NumPages == 0);
|
|
PFSVC_ASSERT(SectionNode->OrgSectionIndex == ULONG_MAX);
|
|
|
|
//
|
|
// Update the index of this section in the scenario file.
|
|
//
|
|
|
|
SectionNode->OrgSectionIndex = SectionIdx;
|
|
|
|
//
|
|
// Update the section record in the section node.
|
|
//
|
|
|
|
SectionNode->SectionRecord = *SectionRecord;
|
|
|
|
//
|
|
// Put page records for the section into the list.
|
|
//
|
|
|
|
PageIdx = SectionRecord->FirstPageIdx;
|
|
NumPages = 0;
|
|
|
|
while (PageIdx != PF_INVALID_PAGE_IDX) {
|
|
|
|
if (NumPages >= SectionRecord->NumPages) {
|
|
|
|
//
|
|
// There should not be more pages on the list than
|
|
// what the section record says there is.
|
|
//
|
|
|
|
PFSVC_ASSERT(FALSE);
|
|
break;
|
|
}
|
|
|
|
PageNode = PfSvChunkAllocatorAllocate(&ScenarioInfo->PageNodeAllocator);
|
|
|
|
if (!PageNode) {
|
|
ErrorCode = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Copy over the page record.
|
|
//
|
|
|
|
PageNode->PageRecord = Pages[PageIdx];
|
|
|
|
//
|
|
// Insert it into the section's page list. Note that
|
|
// the page records in the section should be sorted by
|
|
// offset. By inserting to the tail, we maintain that.
|
|
//
|
|
|
|
InsertTailList(&SectionNode->PageList, &PageNode->PageLink);
|
|
|
|
//
|
|
// Shift the usage history for this page record making
|
|
// room for whether this page was used in this launch.
|
|
//
|
|
|
|
PageNode->PageRecord.UsageHistory <<= 1;
|
|
|
|
//
|
|
// Shift the prefetch history for this page record and
|
|
// note whether we had asked this page to be
|
|
// prefetched in this launch.
|
|
//
|
|
|
|
PageNode->PageRecord.PrefetchHistory <<= 1;
|
|
|
|
if (!PageNode->PageRecord.IsIgnore) {
|
|
PageNode->PageRecord.PrefetchHistory |= 0x1;
|
|
}
|
|
|
|
//
|
|
// Keep the count of pages we had asked to be
|
|
// prefetched, so we can calculate hit rate and adjust
|
|
// the sensitivity.
|
|
//
|
|
|
|
if(!PageNode->PageRecord.IsIgnore) {
|
|
if (PageNode->PageRecord.IsImage) {
|
|
ScenarioInfo->PrefetchedPages++;
|
|
}
|
|
if (PageNode->PageRecord.IsData) {
|
|
ScenarioInfo->PrefetchedPages++;
|
|
}
|
|
} else {
|
|
ScenarioInfo->IgnoredPages++;
|
|
}
|
|
|
|
//
|
|
// Update next page idx.
|
|
//
|
|
|
|
PageIdx = Pages[PageIdx].NextPageIdx;
|
|
|
|
//
|
|
// Update number of pages we've copied.
|
|
//
|
|
|
|
NumPages++;
|
|
}
|
|
|
|
//
|
|
// We should have copied as many pages as the section said
|
|
// there were.
|
|
//
|
|
|
|
PFSVC_ASSERT(NumPages == SectionRecord->NumPages);
|
|
}
|
|
|
|
ErrorCode = ERROR_SUCCESS;
|
|
|
|
cleanup:
|
|
|
|
return ErrorCode;
|
|
}
|
|
|
|
DWORD
|
|
PfSvVerifyVolumeMagics(
|
|
PPFSVC_SCENARIO_INFO ScenarioInfo,
|
|
PPF_TRACE_HEADER Trace
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Walk through the volumes in the trace and make sure their magics
|
|
match the ones in ScenarioInfo.
|
|
|
|
Arguments:
|
|
|
|
ScenarioInfo - Pointer to scenario info structure.
|
|
|
|
Trace - Pointer to trace.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
PPFSVC_VOLUME_NODE VolumeNode;
|
|
PPF_VOLUME_INFO VolumeInfo;
|
|
ULONG VolumeInfoSize;
|
|
ULONG VolumeIdx;
|
|
BOOLEAN VolumeMagicsMatch;
|
|
|
|
//
|
|
// Walk the volumes in the trace.
|
|
//
|
|
|
|
VolumeInfo = (PPF_VOLUME_INFO) ((PCHAR)Trace + Trace->VolumeInfoOffset);
|
|
|
|
for (VolumeIdx = 0; VolumeIdx < Trace->NumVolumes; VolumeIdx++) {
|
|
|
|
//
|
|
// Get the scenario info's volume node for this volume.
|
|
//
|
|
|
|
VolumeNode = PfSvGetVolumeNode(ScenarioInfo,
|
|
VolumeInfo->VolumePath,
|
|
VolumeInfo->VolumePathLength);
|
|
|
|
if (VolumeNode) {
|
|
|
|
//
|
|
// Make sure the magics match.
|
|
//
|
|
|
|
if (VolumeNode->SerialNumber != VolumeInfo->SerialNumber ||
|
|
VolumeNode->CreationTime.QuadPart != VolumeInfo->CreationTime.QuadPart) {
|
|
|
|
VolumeMagicsMatch = FALSE;
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Get the next volume.
|
|
//
|
|
|
|
VolumeInfoSize = sizeof(PF_VOLUME_INFO);
|
|
VolumeInfoSize += VolumeInfo->VolumePathLength * sizeof(WCHAR);
|
|
|
|
VolumeInfo = (PPF_VOLUME_INFO) ((PCHAR) VolumeInfo + VolumeInfoSize);
|
|
|
|
//
|
|
// Make sure VolumeInfo is aligned.
|
|
//
|
|
|
|
VolumeInfo = PF_ALIGN_UP(VolumeInfo, _alignof(PF_VOLUME_INFO));
|
|
}
|
|
|
|
//
|
|
// Volume magics for volumes that appear both in the trace and the
|
|
// scenario info matched.
|
|
//
|
|
|
|
VolumeMagicsMatch = TRUE;
|
|
|
|
cleanup:
|
|
|
|
return VolumeMagicsMatch;
|
|
}
|
|
|
|
DWORD
|
|
PfSvAddTraceInfo(
|
|
PPFSVC_SCENARIO_INFO ScenarioInfo,
|
|
PPF_TRACE_HEADER Trace
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Add information in a raw trace to the specified scenario info
|
|
structure.
|
|
|
|
Arguments:
|
|
|
|
ScenarioInfo - Pointer to scenario info structure.
|
|
|
|
Trace - Pointer to trace.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
PPF_SECTION_INFO Section;
|
|
PPF_LOG_ENTRY LogEntries;
|
|
PCHAR pFileName;
|
|
PPFSVC_SECTION_NODE *SectionTable;
|
|
PPFSVC_SECTION_NODE SectionNode;
|
|
PPFSVC_VOLUME_NODE VolumeNode;
|
|
ULONG TraceEndIdx;
|
|
ULONG SectionIdx;
|
|
ULONG EntryIdx;
|
|
DWORD ErrorCode;
|
|
ULONG SectionLength;
|
|
ULONG NextSectionIndex;
|
|
PPF_VOLUME_INFO VolumeInfo;
|
|
ULONG VolumeInfoSize;
|
|
ULONG VolumeIdx;
|
|
ULONG SectionTableSize;
|
|
|
|
//
|
|
// Initialize locals so we know what to clean up.
|
|
//
|
|
|
|
SectionTable = NULL;
|
|
|
|
DBGPR((PFID,PFTRC,"PFSVC: AddTraceInfo()\n"));
|
|
|
|
//
|
|
// Update last launch time.
|
|
//
|
|
|
|
ScenarioInfo->ScenHeader.LastLaunchTime = Trace->LaunchTime;
|
|
|
|
//
|
|
// If this scenario has been launched a number of times, we update
|
|
// the min reprefetch and retrace times. See comment for
|
|
// PFSVC_MIN_LAUNCHES_FOR_LAUNCH_FREQ_CHECK.
|
|
//
|
|
|
|
if (ScenarioInfo->ScenHeader.NumLaunches >= PFSVC_MIN_LAUNCHES_FOR_LAUNCH_FREQ_CHECK) {
|
|
ScenarioInfo->ScenHeader.MinRePrefetchTime.QuadPart = PFSVC_DEFAULT_MIN_REPREFETCH_TIME;
|
|
ScenarioInfo->ScenHeader.MinReTraceTime.QuadPart = PFSVC_DEFAULT_MIN_RETRACE_TIME;
|
|
}
|
|
|
|
#ifdef PFSVC_DBG
|
|
|
|
//
|
|
// On checked build, always set these to 0, so we do prefetch every
|
|
// scenario launch.
|
|
//
|
|
|
|
ScenarioInfo->ScenHeader.MinRePrefetchTime.QuadPart = 0;
|
|
ScenarioInfo->ScenHeader.MinReTraceTime.QuadPart = 0;
|
|
|
|
#endif // PFSVC_DBG
|
|
|
|
//
|
|
// Walk through the volumes in the trace and create volume nodes
|
|
// for them.
|
|
//
|
|
|
|
VolumeInfo = (PPF_VOLUME_INFO) ((PCHAR)Trace + Trace->VolumeInfoOffset);
|
|
|
|
for (VolumeIdx = 0; VolumeIdx < Trace->NumVolumes; VolumeIdx++) {
|
|
|
|
//
|
|
// Upcase the path so we don't have to do expensive case
|
|
// insensitive comparisons.
|
|
//
|
|
|
|
_wcsupr(VolumeInfo->VolumePath);
|
|
|
|
ErrorCode = PfSvCreateVolumeNode(ScenarioInfo,
|
|
VolumeInfo->VolumePath,
|
|
VolumeInfo->VolumePathLength,
|
|
&VolumeInfo->CreationTime,
|
|
VolumeInfo->SerialNumber);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Get the next volume.
|
|
//
|
|
|
|
VolumeInfoSize = sizeof(PF_VOLUME_INFO);
|
|
VolumeInfoSize += VolumeInfo->VolumePathLength * sizeof(WCHAR);
|
|
|
|
VolumeInfo = (PPF_VOLUME_INFO) ((PCHAR) VolumeInfo + VolumeInfoSize);
|
|
|
|
//
|
|
// Make sure VolumeInfo is aligned.
|
|
//
|
|
|
|
VolumeInfo = PF_ALIGN_UP(VolumeInfo, _alignof(PF_VOLUME_INFO));
|
|
}
|
|
|
|
//
|
|
// Allocate section node table so we know where to put the logged
|
|
// page faults.
|
|
//
|
|
|
|
SectionTableSize = sizeof(PPFSVC_SECTION_NODE) * Trace->NumSections;
|
|
|
|
SectionTable = PFSVC_ALLOC(SectionTableSize);
|
|
|
|
if (!SectionTable) {
|
|
ErrorCode = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto cleanup;
|
|
}
|
|
|
|
RtlZeroMemory(SectionTable, SectionTableSize);
|
|
|
|
//
|
|
// Walk through the sections in the trace, and either find
|
|
// existing section records in the new scenario or create new
|
|
// ones.
|
|
//
|
|
|
|
Section = (PPF_SECTION_INFO) ((PCHAR)Trace + Trace->SectionInfoOffset);
|
|
|
|
for (SectionIdx = 0; SectionIdx < Trace->NumSections; SectionIdx++) {
|
|
|
|
//
|
|
// Upcase the path so we don't have to do expensive case
|
|
// insensitive comparisons.
|
|
//
|
|
|
|
_wcsupr(Section->FileName);
|
|
|
|
//
|
|
// If the section is for metafile, simply add it as a directory
|
|
// to be prefetched. We don't keep track of
|
|
//
|
|
|
|
if (Section->Metafile) {
|
|
|
|
VolumeNode = PfSvGetVolumeNode(ScenarioInfo,
|
|
Section->FileName,
|
|
Section->FileNameLength);
|
|
|
|
PFSVC_ASSERT(VolumeNode);
|
|
|
|
if (VolumeNode) {
|
|
PfSvAddParentDirectoriesToList(&VolumeNode->DirectoryList,
|
|
VolumeNode->VolumePathLength,
|
|
Section->FileName,
|
|
Section->FileNameLength);
|
|
}
|
|
|
|
goto NextSection;
|
|
}
|
|
|
|
//
|
|
// Find or create a section record for this section.
|
|
//
|
|
|
|
SectionTable[SectionIdx] = PfSvGetSectionRecord(ScenarioInfo,
|
|
Section->FileName,
|
|
Section->FileNameLength);
|
|
|
|
//
|
|
// If we could not get a record, it is because we had to
|
|
// create one and we did not have enough memory.
|
|
//
|
|
|
|
if (!SectionTable[SectionIdx]) {
|
|
ErrorCode = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto cleanup;
|
|
}
|
|
|
|
NextSection:
|
|
|
|
//
|
|
// Get the next section record in the trace.
|
|
//
|
|
|
|
SectionLength = sizeof(PF_SECTION_INFO) +
|
|
(Section->FileNameLength) * sizeof(WCHAR);
|
|
|
|
Section = (PPF_SECTION_INFO) ((PUCHAR) Section + SectionLength);
|
|
}
|
|
|
|
//
|
|
// Determine after which log entry the trace ends.
|
|
//
|
|
|
|
TraceEndIdx = PfSvGetTraceEndIdx(Trace);
|
|
|
|
//
|
|
// If the determined trace end is zero (as is the case for most
|
|
// applications running under stress that don't get any pagefaults
|
|
// traced for the first few seconds), bail out.
|
|
//
|
|
|
|
if (TraceEndIdx == 0) {
|
|
ErrorCode = ERROR_BAD_FORMAT;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Add logged pagefault information up to the determined trace end
|
|
// to the new scenario info.
|
|
//
|
|
|
|
LogEntries = (PPF_LOG_ENTRY) ((PCHAR)Trace + Trace->TraceBufferOffset);
|
|
|
|
//
|
|
// Keep track of NextSectionIdx so we can order the sections by
|
|
// the first access [i.e. first page fault in the trace]
|
|
//
|
|
|
|
NextSectionIndex = 0;
|
|
|
|
for (EntryIdx = 0; EntryIdx < TraceEndIdx; EntryIdx++) {
|
|
|
|
SectionNode = SectionTable[LogEntries[EntryIdx].SectionId];
|
|
|
|
//
|
|
// For metafile sections we don't create section nodes.
|
|
//
|
|
|
|
if (!SectionNode) {
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// NewSectionIndex fields of all section nodes are initialized
|
|
// to ULONG_MAX. If we have not already seen this section in
|
|
// the trace note its order and update NextSectionIdx.
|
|
//
|
|
|
|
if (SectionNode->NewSectionIndex == ULONG_MAX) {
|
|
SectionNode->NewSectionIndex = NextSectionIndex;
|
|
NextSectionIndex++;
|
|
}
|
|
|
|
//
|
|
// Add fault information to our section record.
|
|
//
|
|
|
|
ErrorCode = PfSvAddFaultInfoToSection(ScenarioInfo,
|
|
&LogEntries[EntryIdx],
|
|
SectionNode);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
ErrorCode = ERROR_SUCCESS;
|
|
|
|
cleanup:
|
|
|
|
if (SectionTable) {
|
|
PFSVC_FREE(SectionTable);
|
|
}
|
|
|
|
DBGPR((PFID,PFTRC,"PFSVC: AddTraceInfo()=%x\n", ErrorCode));
|
|
|
|
return ErrorCode;
|
|
}
|
|
|
|
PPFSVC_SECTION_NODE
|
|
PfSvGetSectionRecord(
|
|
PPFSVC_SCENARIO_INFO ScenarioInfo,
|
|
WCHAR *FilePath,
|
|
ULONG FilePathLength
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Find or create a section node in the scenario info for the
|
|
specified file path.
|
|
|
|
Arguments:
|
|
|
|
ScenarioInfo - Pointer to scenario info structure.
|
|
|
|
FilePath - NUL terminated NT path to file.
|
|
|
|
FilePathLength - Length of FilePath in chars excluding NUL.
|
|
|
|
Return Value:
|
|
|
|
Pointer to created or found section node or NULL if there was a
|
|
problem.
|
|
|
|
--*/
|
|
|
|
{
|
|
PPFSVC_SECTION_NODE SectionNode;
|
|
PLIST_ENTRY HeadEntry;
|
|
PLIST_ENTRY NextEntry;
|
|
LONG ComparisonResult;
|
|
ULONG FilePathSize;
|
|
PPFSVC_SECTION_NODE ReturnNode;
|
|
|
|
//
|
|
// Initialize locals.
|
|
//
|
|
|
|
ReturnNode = NULL;
|
|
|
|
//
|
|
// Walk through the existing sections records looking for a file
|
|
// name match. Section records are on a lexically sorted list.
|
|
//
|
|
|
|
HeadEntry = &ScenarioInfo->SectionList;
|
|
NextEntry = HeadEntry->Flink;
|
|
|
|
while (HeadEntry != NextEntry) {
|
|
|
|
SectionNode = CONTAINING_RECORD(NextEntry,
|
|
PFSVC_SECTION_NODE,
|
|
SectionLink);
|
|
|
|
ComparisonResult = wcscmp(SectionNode->FilePath, FilePath);
|
|
|
|
if (ComparisonResult == 0) {
|
|
|
|
//
|
|
// We found a match. Return this section record.
|
|
//
|
|
|
|
ReturnNode = SectionNode;
|
|
goto cleanup;
|
|
|
|
} else if (ComparisonResult > 0) {
|
|
|
|
//
|
|
// We won't find the name in our list. We have to create a
|
|
// new section record.
|
|
//
|
|
|
|
break;
|
|
}
|
|
|
|
NextEntry = NextEntry->Flink;
|
|
}
|
|
|
|
//
|
|
// We have to create a new section record. NextEntry points to
|
|
// where we have to insert it in the list.
|
|
//
|
|
|
|
SectionNode = PfSvChunkAllocatorAllocate(&ScenarioInfo->SectionNodeAllocator);
|
|
|
|
if (!SectionNode) {
|
|
ReturnNode = NULL;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Initialize the section node.
|
|
//
|
|
|
|
SectionNode->FilePath = NULL;
|
|
InitializeListHead(&SectionNode->PageList);
|
|
InitializeListHead(&SectionNode->SectionVolumeLink);
|
|
SectionNode->NewSectionIndex = ULONG_MAX;
|
|
SectionNode->OrgSectionIndex = ULONG_MAX;
|
|
SectionNode->FileIndexNumber.QuadPart = -1i64;
|
|
|
|
//
|
|
// Initialize the section record.
|
|
//
|
|
|
|
RtlZeroMemory(&SectionNode->SectionRecord, sizeof(PF_SECTION_RECORD));
|
|
|
|
//
|
|
// Allocate and copy over the file name.
|
|
//
|
|
|
|
FilePathSize = (FilePathLength + 1) * sizeof(WCHAR);
|
|
|
|
SectionNode->FilePath = PfSvStringAllocatorAllocate(&ScenarioInfo->PathAllocator,
|
|
FilePathSize);
|
|
|
|
if (!SectionNode->FilePath) {
|
|
|
|
PfSvCleanupSectionNode(ScenarioInfo, SectionNode);
|
|
|
|
PfSvChunkAllocatorFree(&ScenarioInfo->SectionNodeAllocator, SectionNode);
|
|
|
|
ReturnNode = NULL;
|
|
goto cleanup;
|
|
}
|
|
|
|
RtlCopyMemory(SectionNode->FilePath, FilePath, FilePathSize);
|
|
|
|
//
|
|
// Update the file name length on the section record.
|
|
//
|
|
|
|
SectionNode->SectionRecord.FileNameLength = FilePathLength;
|
|
|
|
//
|
|
// Insert the section into the right spot on the scenario's list.
|
|
//
|
|
|
|
InsertTailList(NextEntry, &SectionNode->SectionLink);
|
|
|
|
//
|
|
// Return the newly setup section record.
|
|
//
|
|
|
|
ReturnNode = SectionNode;
|
|
|
|
cleanup:
|
|
|
|
return ReturnNode;
|
|
}
|
|
|
|
DWORD
|
|
PfSvAddFaultInfoToSection(
|
|
PPFSVC_SCENARIO_INFO ScenarioInfo,
|
|
PPF_LOG_ENTRY LogEntry,
|
|
PPFSVC_SECTION_NODE SectionNode
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Add fault information from a trace log entry to proper section
|
|
record in the new scenario.
|
|
|
|
Arguments:
|
|
|
|
ScenarioInfo - Pointer to scenario info structure.
|
|
|
|
LogEntry - Pointer to trace log entry.
|
|
|
|
SectionNode - Pointer to the section node the log entry belongs to.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
DWORD ErrorCode;
|
|
PPFSVC_PAGE_NODE PageNode;
|
|
PLIST_ENTRY HeadEntry;
|
|
PLIST_ENTRY NextEntry;
|
|
|
|
//
|
|
// Walk through the page records for this section.
|
|
//
|
|
|
|
HeadEntry = &SectionNode->PageList;
|
|
NextEntry = HeadEntry->Flink;
|
|
|
|
while (HeadEntry != NextEntry) {
|
|
|
|
PageNode = CONTAINING_RECORD(NextEntry,
|
|
PFSVC_PAGE_NODE,
|
|
PageLink);
|
|
|
|
if (PageNode->PageRecord.FileOffset > LogEntry->FileOffset) {
|
|
|
|
//
|
|
// We won't find this fault in this sorted list.
|
|
//
|
|
|
|
break;
|
|
|
|
} else if (PageNode->PageRecord.FileOffset == LogEntry->FileOffset) {
|
|
|
|
//
|
|
// We found the page, update the page record and section
|
|
// record with the info in log entry.
|
|
//
|
|
|
|
if (LogEntry->IsImage) {
|
|
PageNode->PageRecord.IsImage = 1;
|
|
} else {
|
|
PageNode->PageRecord.IsData = 1;
|
|
}
|
|
|
|
//
|
|
// Note the this page was used in this launch.
|
|
//
|
|
|
|
PageNode->PageRecord.UsageHistory |= 0x1;
|
|
|
|
//
|
|
// See if this page was prefetched for this launch and
|
|
// update appropriate stats.
|
|
//
|
|
|
|
if(PageNode->PageRecord.IsIgnore) {
|
|
ScenarioInfo->MissedOpportunityPages++;
|
|
} else {
|
|
ScenarioInfo->HitPages++;
|
|
}
|
|
|
|
ErrorCode = ERROR_SUCCESS;
|
|
goto cleanup;
|
|
}
|
|
|
|
NextEntry = NextEntry->Flink;
|
|
}
|
|
|
|
//
|
|
// We have to add a new page record before NextEntry in the list.
|
|
//
|
|
|
|
PageNode = PfSvChunkAllocatorAllocate(&ScenarioInfo->PageNodeAllocator);
|
|
|
|
if (!PageNode) {
|
|
ErrorCode = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Set up new page record. First initialize fields.
|
|
//
|
|
|
|
PageNode->PageRecord.IsImage = 0;
|
|
PageNode->PageRecord.IsData = 0;
|
|
PageNode->PageRecord.IsIgnore = 0;
|
|
|
|
PageNode->PageRecord.FileOffset = LogEntry->FileOffset;
|
|
|
|
if (LogEntry->IsImage) {
|
|
PageNode->PageRecord.IsImage = 1;
|
|
} else {
|
|
PageNode->PageRecord.IsData = 1;
|
|
}
|
|
|
|
//
|
|
// Initialize usage history for this new page record noting that
|
|
// it was used in this launch.
|
|
//
|
|
|
|
PageNode->PageRecord.UsageHistory = 0x1;
|
|
|
|
//
|
|
// Initialize prefetch history for this new page record.
|
|
//
|
|
|
|
PageNode->PageRecord.PrefetchHistory = 0;
|
|
|
|
//
|
|
// Insert it into the sections pages list.
|
|
//
|
|
|
|
InsertTailList(NextEntry, &PageNode->PageLink);
|
|
|
|
//
|
|
// Update stats on the new scenario.
|
|
//
|
|
|
|
ScenarioInfo->NewPages++;
|
|
|
|
ErrorCode = ERROR_SUCCESS;
|
|
|
|
cleanup:
|
|
|
|
return ErrorCode;
|
|
}
|
|
|
|
DWORD
|
|
PfSvApplyPrefetchPolicy(
|
|
PPFSVC_SCENARIO_INFO ScenarioInfo
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Go through all the information in ScenarioInfo and determine which
|
|
pages/sections to prefetch for the next launch of the scenario.
|
|
|
|
Arguments:
|
|
|
|
ScenarioInfo - Pointer to scenario info structure.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
ULONG Sensitivity;
|
|
PPFSVC_SECTION_NODE SectionNode;
|
|
PPFSVC_PAGE_NODE PageNode;
|
|
ULONG SectNumPagesToPrefetch;
|
|
ULONG HitPages;
|
|
ULONG MissedOpportunityPages;
|
|
ULONG PrefetchedPages;
|
|
ULONG IgnoredPages;
|
|
PLIST_ENTRY SectHead;
|
|
PLIST_ENTRY SectNext;
|
|
PLIST_ENTRY PageHead;
|
|
PLIST_ENTRY PageNext;
|
|
ULONG NumUsed;
|
|
PPF_SCENARIO_HEADER Scenario;
|
|
ULONG FileNameSize;
|
|
ULONG IgnoredFileIdx;
|
|
BOOLEAN bSkipSection;
|
|
PFSV_SUFFIX_COMPARISON_RESULT ComparisonResult;
|
|
DWORD ErrorCode;
|
|
PPFSVC_VOLUME_NODE VolumeNode;
|
|
PWCHAR MFTSuffix;
|
|
PWCHAR PathSuffix;
|
|
FILE_BASIC_INFORMATION FileInformation;
|
|
ULONG MFTSuffixLength;
|
|
|
|
//
|
|
// Initialize locals.
|
|
//
|
|
|
|
Scenario = &ScenarioInfo->ScenHeader;
|
|
MFTSuffix = L"\\$MFT";
|
|
MFTSuffixLength = wcslen(MFTSuffix);
|
|
|
|
DBGPR((PFID,PFTRC,"PFSVC: ApplyPrefetchPolicy()\n"));
|
|
|
|
//
|
|
// Initialize fields of the scenario header we will set up.
|
|
//
|
|
|
|
Scenario->NumSections = 0;
|
|
Scenario->NumPages = 0;
|
|
Scenario->FileNameInfoSize = 0;
|
|
|
|
//
|
|
// Determine sensitivity based on usage of the pages we prefetched
|
|
// and we ignored.
|
|
//
|
|
|
|
HitPages = ScenarioInfo->HitPages;
|
|
MissedOpportunityPages = ScenarioInfo->MissedOpportunityPages;
|
|
PrefetchedPages = ScenarioInfo->PrefetchedPages;
|
|
IgnoredPages = ScenarioInfo->IgnoredPages;
|
|
|
|
//
|
|
// Check what percent of the pages we brought were used.
|
|
//
|
|
|
|
if (PrefetchedPages &&
|
|
(((HitPages * 100) / PrefetchedPages) < PFSVC_MIN_HIT_PERCENTAGE)) {
|
|
|
|
//
|
|
// Our hit rate is low. Increase sensitivity of the
|
|
// scenario, so for us to prefetch a page, it has to be
|
|
// used in more of the last launches.
|
|
//
|
|
|
|
if (ScenarioInfo->ScenHeader.Sensitivity < PF_MAX_SENSITIVITY) {
|
|
ScenarioInfo->ScenHeader.Sensitivity ++;
|
|
}
|
|
|
|
} else if (IgnoredPages &&
|
|
(((MissedOpportunityPages * 100) / IgnoredPages) > PFSVC_MAX_IGNORED_PERCENTAGE)) {
|
|
|
|
//
|
|
// If we are using most of what we prefetched (or we are not
|
|
// prefetching anything!), but we ignored some pages we could
|
|
// have prefetched, and they were used too, time to decrease
|
|
// sensitivity so we ignore less pages.
|
|
//
|
|
|
|
if (ScenarioInfo->ScenHeader.Sensitivity > PF_MIN_SENSITIVITY) {
|
|
ScenarioInfo->ScenHeader.Sensitivity --;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Don't let the boot scenario's sensitivity to fall below 2.
|
|
// This makes sure we don't pick up all the application setup &
|
|
// configuration updates that happen during boot once.
|
|
//
|
|
|
|
if (ScenarioInfo->ScenHeader.ScenarioType == PfSystemBootScenarioType) {
|
|
PFSVC_ASSERT(PF_MIN_SENSITIVITY <= 2);
|
|
if (ScenarioInfo->ScenHeader.Sensitivity < 2) {
|
|
ScenarioInfo->ScenHeader.Sensitivity = 2;
|
|
}
|
|
}
|
|
|
|
Sensitivity = ScenarioInfo->ScenHeader.Sensitivity;
|
|
|
|
//
|
|
// If number of times this scenario was launched is less
|
|
// than sensitivity, adjust sensitivity. Otherwise we
|
|
// won't end up prefetching anything.
|
|
//
|
|
|
|
if (Sensitivity > ScenarioInfo->ScenHeader.NumLaunches) {
|
|
Sensitivity = ScenarioInfo->ScenHeader.NumLaunches;
|
|
}
|
|
|
|
//
|
|
// Walk through pages for every section and determine if they
|
|
// should be prefetched or not based on scenario sensitivity and
|
|
// their usage history in the last launches.
|
|
//
|
|
|
|
SectHead = &ScenarioInfo->SectionList;
|
|
SectNext = SectHead->Flink;
|
|
|
|
while (SectHead != SectNext) {
|
|
|
|
SectionNode = CONTAINING_RECORD(SectNext,
|
|
PFSVC_SECTION_NODE,
|
|
SectionLink);
|
|
SectNext = SectNext->Flink;
|
|
|
|
//
|
|
// Initialize section records fields.
|
|
//
|
|
|
|
SectionNode->SectionRecord.IsImage = 0;
|
|
SectionNode->SectionRecord.IsData = 0;
|
|
SectionNode->SectionRecord.NumPages = 0;
|
|
|
|
//
|
|
// If we are nearing the limits for number of sections and
|
|
// pages, ignore the rest of the sections.
|
|
//
|
|
|
|
if (Scenario->NumSections >= PF_MAXIMUM_SECTIONS ||
|
|
Scenario->NumPages + PF_MAXIMUM_SECTION_PAGES >= PF_MAXIMUM_PAGES) {
|
|
|
|
//
|
|
// Remove this section node from our list.
|
|
//
|
|
|
|
PfSvCleanupSectionNode(ScenarioInfo, SectionNode);
|
|
|
|
RemoveEntryList(&SectionNode->SectionLink);
|
|
|
|
PfSvChunkAllocatorFree(&ScenarioInfo->SectionNodeAllocator, SectionNode);
|
|
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// If this is the boot scenario, check to see if this is one
|
|
// of the sections we ignore.
|
|
//
|
|
|
|
if (Scenario->ScenarioType == PfSystemBootScenarioType) {
|
|
|
|
bSkipSection = FALSE;
|
|
|
|
for (IgnoredFileIdx = 0;
|
|
IgnoredFileIdx < PfSvcGlobals.NumFilesToIgnoreForBoot;
|
|
IgnoredFileIdx++) {
|
|
|
|
ComparisonResult = PfSvCompareSuffix(SectionNode->FilePath,
|
|
SectionNode->SectionRecord.FileNameLength,
|
|
PfSvcGlobals.FilesToIgnoreForBoot[IgnoredFileIdx],
|
|
PfSvcGlobals.FileSuffixLengths[IgnoredFileIdx],
|
|
TRUE);
|
|
|
|
if (ComparisonResult == PfSvSuffixIdentical) {
|
|
|
|
//
|
|
// The suffix matched.
|
|
//
|
|
|
|
bSkipSection = TRUE;
|
|
break;
|
|
|
|
} else if (ComparisonResult == PfSvSuffixGreaterThan) {
|
|
|
|
//
|
|
// Since the ignore-suffices are lexically sorted,
|
|
// this file name's suffix won't match others
|
|
// either.
|
|
//
|
|
|
|
bSkipSection = FALSE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (bSkipSection) {
|
|
|
|
//
|
|
// Remove this section node from our list.
|
|
//
|
|
|
|
PfSvCleanupSectionNode(ScenarioInfo, SectionNode);
|
|
|
|
RemoveEntryList(&SectionNode->SectionLink);
|
|
|
|
PfSvChunkAllocatorFree(&ScenarioInfo->SectionNodeAllocator, SectionNode);
|
|
|
|
continue;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Keep track of num pages to prefetch for this section.
|
|
//
|
|
|
|
SectNumPagesToPrefetch = 0;
|
|
|
|
PageHead = &SectionNode->PageList;
|
|
PageNext = PageHead->Flink;
|
|
|
|
while (PageHead != PageNext) {
|
|
|
|
PageNode = CONTAINING_RECORD(PageNext,
|
|
PFSVC_PAGE_NODE,
|
|
PageLink);
|
|
PageNext = PageNext->Flink;
|
|
|
|
//
|
|
// Get number of times this page was used in the launches
|
|
// in usage history.
|
|
//
|
|
|
|
NumUsed = PfSvGetNumTimesUsed(PageNode->PageRecord.UsageHistory,
|
|
PF_PAGE_HISTORY_SIZE);
|
|
|
|
|
|
//
|
|
// If it was not used at all in the history we've kept
|
|
// track of, remove it.
|
|
//
|
|
|
|
if (NumUsed == 0) {
|
|
|
|
RemoveEntryList(&PageNode->PageLink);
|
|
|
|
PfSvChunkAllocatorFree(&ScenarioInfo->PageNodeAllocator, PageNode);
|
|
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Update the number of pages for this section.
|
|
//
|
|
|
|
SectionNode->SectionRecord.NumPages++;
|
|
|
|
//
|
|
// Check if this page qualifies to be prefetched next time.
|
|
//
|
|
|
|
if (NumUsed >= Sensitivity) {
|
|
PageNode->PageRecord.IsIgnore = 0;
|
|
|
|
//
|
|
// Update the number of pages we are prefetching for
|
|
// this section.
|
|
//
|
|
|
|
SectNumPagesToPrefetch++;
|
|
|
|
//
|
|
// Update whether we are going to prefetch this
|
|
// section as image, data [or both].
|
|
//
|
|
|
|
SectionNode->SectionRecord.IsImage |= PageNode->PageRecord.IsImage;
|
|
SectionNode->SectionRecord.IsData |= PageNode->PageRecord.IsData;
|
|
|
|
} else {
|
|
|
|
PageNode->PageRecord.IsIgnore = 1;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Check if we want to keep this section in the scenario:
|
|
//
|
|
|
|
bSkipSection = FALSE;
|
|
|
|
if (SectionNode->SectionRecord.NumPages == 0) {
|
|
|
|
//
|
|
// If we don't have any pages left for this section, remove
|
|
// it.
|
|
//
|
|
|
|
bSkipSection = TRUE;
|
|
|
|
} else if (SectionNode->SectionRecord.NumPages >= PF_MAXIMUM_SECTION_PAGES) {
|
|
|
|
//
|
|
// If we ended up with too many pages for this section, remove
|
|
// it.
|
|
//
|
|
|
|
bSkipSection = TRUE;
|
|
|
|
} else if (PfSvcGlobals.CSCRootPath &&
|
|
wcsstr(SectionNode->FilePath, PfSvcGlobals.CSCRootPath)) {
|
|
|
|
//
|
|
// Skip client side cache (CSC) files. These files may get encrypted as
|
|
// LocalSystem, and when the AppData folder is redirected, we may take
|
|
// minutes trying to open them when prefetching for shell launch.
|
|
//
|
|
|
|
bSkipSection = TRUE;
|
|
|
|
} else {
|
|
|
|
//
|
|
// Encrypted files may result in several network accesses during open,
|
|
// even if they are local. This is especially so if the AppData folder is
|
|
// redirected to a server. We cannot afford these network delays when
|
|
// blocking the scenario for prefetching.
|
|
//
|
|
|
|
ErrorCode = PfSvGetFileBasicInformation(SectionNode->FilePath,
|
|
&FileInformation);
|
|
|
|
if ((ErrorCode == ERROR_SUCCESS) &&
|
|
(FileInformation.FileAttributes & FILE_ATTRIBUTE_ENCRYPTED)) {
|
|
|
|
bSkipSection = TRUE;
|
|
|
|
}
|
|
}
|
|
|
|
if (bSkipSection) {
|
|
|
|
PfSvCleanupSectionNode(ScenarioInfo, SectionNode);
|
|
|
|
RemoveEntryList(&SectionNode->SectionLink);
|
|
|
|
PfSvChunkAllocatorFree(&ScenarioInfo->SectionNodeAllocator, SectionNode);
|
|
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Get the volume node for the volume this section is
|
|
// on. The volume node should have been added when the
|
|
// existing scenario information or the new trace
|
|
// information was added to the scenario info.
|
|
//
|
|
|
|
VolumeNode = PfSvGetVolumeNode(ScenarioInfo,
|
|
SectionNode->FilePath,
|
|
SectionNode->SectionRecord.FileNameLength);
|
|
|
|
PFSVC_ASSERT(VolumeNode);
|
|
|
|
if (VolumeNode) {
|
|
VolumeNode->NumAllSections++;
|
|
}
|
|
|
|
//
|
|
// If we are not prefetching any pages from this section for
|
|
// the next launch, mark it ignore.
|
|
//
|
|
|
|
if (SectNumPagesToPrefetch == 0) {
|
|
|
|
SectionNode->SectionRecord.IsIgnore = 1;
|
|
|
|
} else {
|
|
|
|
SectionNode->SectionRecord.IsIgnore = 0;
|
|
|
|
//
|
|
// If this is MFT section for this volume, save it on the volume
|
|
// node. We will add the pages referenced from MFT to the list of
|
|
// files to prefetch metadata for.
|
|
//
|
|
|
|
if ((VolumeNode && VolumeNode->MFTSectionNode == NULL) &&
|
|
(VolumeNode->VolumePathLength == (SectionNode->SectionRecord.FileNameLength - MFTSuffixLength))) {
|
|
|
|
PathSuffix = SectionNode->FilePath + SectionNode->SectionRecord.FileNameLength;
|
|
PathSuffix -= MFTSuffixLength;
|
|
|
|
if (wcscmp(PathSuffix, MFTSuffix) == 0) {
|
|
|
|
//
|
|
// This is the MFT section node for this volume.
|
|
//
|
|
|
|
VolumeNode->MFTSectionNode = SectionNode;
|
|
|
|
//
|
|
// Mark the MFT section node as "ignore" so kernel does
|
|
// not attempt to prefetch it directly.
|
|
//
|
|
|
|
VolumeNode->MFTSectionNode->SectionRecord.IsIgnore = 1;
|
|
|
|
//
|
|
// Save how many pages we'll prefetch from MFT on the section
|
|
// node. We save this instead of FileIndexNumber field, since
|
|
// there won't be one for MFT. We won't try to get one either
|
|
// since we are marking this section node ignore.
|
|
//
|
|
|
|
VolumeNode->MFTSectionNode->MFTNumPagesToPrefetch = SectNumPagesToPrefetch;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// If we are not ignoring this section, update its file system
|
|
// index number so its metadata can be prefetched.
|
|
//
|
|
|
|
if (SectionNode->SectionRecord.IsIgnore == 0) {
|
|
|
|
ErrorCode = PfSvGetFileIndexNumber(SectionNode->FilePath,
|
|
&SectionNode->FileIndexNumber);
|
|
|
|
if (ErrorCode == ERROR_SUCCESS) {
|
|
|
|
if (VolumeNode) {
|
|
|
|
//
|
|
// Insert this section node into the section list of
|
|
// the volume it is on.
|
|
//
|
|
|
|
InsertTailList(&VolumeNode->SectionList,
|
|
&SectionNode->SectionVolumeLink);
|
|
|
|
VolumeNode->NumSections++;
|
|
|
|
//
|
|
// Update volume node's directory list with parent
|
|
// directories of this file.
|
|
//
|
|
|
|
PfSvAddParentDirectoriesToList(&VolumeNode->DirectoryList,
|
|
VolumeNode->VolumePathLength,
|
|
SectionNode->FilePath,
|
|
SectionNode->SectionRecord.FileNameLength);
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Update number of sections, number of pages and file name
|
|
// info length on the scenario.
|
|
//
|
|
|
|
Scenario->NumSections++;
|
|
Scenario->NumPages += SectionNode->SectionRecord.NumPages;
|
|
|
|
FileNameSize = sizeof(WCHAR) *
|
|
(SectionNode->SectionRecord.FileNameLength + 1);
|
|
Scenario->FileNameInfoSize += FileNameSize;
|
|
}
|
|
|
|
//
|
|
// We are done.
|
|
//
|
|
|
|
ErrorCode = ERROR_SUCCESS;
|
|
|
|
DBGPR((PFID,PFTRC,"PFSVC: ApplyPrefetchPolicy()=%x\n", ErrorCode));
|
|
|
|
return ErrorCode;
|
|
}
|
|
|
|
ULONG
|
|
PfSvGetNumTimesUsed(
|
|
ULONG UsageHistory,
|
|
ULONG UsageHistorySize
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Calculate how many times a page seems to be used according to
|
|
UsageHistory.
|
|
|
|
Arguments:
|
|
|
|
UsageHistory - Bitmap. 1's correspond to "was used", 0 = "not used".
|
|
|
|
UsageHistorySize - Size of UsageHistory in bits from the least
|
|
significant bit.
|
|
|
|
Return Value:
|
|
|
|
How many times the page seems to be used.
|
|
|
|
--*/
|
|
|
|
{
|
|
ULONG NumUsed;
|
|
ULONG BitIdx;
|
|
|
|
//
|
|
// Initialize locals.
|
|
//
|
|
|
|
NumUsed = 0;
|
|
|
|
//
|
|
// Walk through the bits in usage history starting from the least
|
|
// significant and count how many bits are on. We can probably do
|
|
// this more efficiently.
|
|
//
|
|
|
|
for (BitIdx = 0; BitIdx < UsageHistorySize; BitIdx++) {
|
|
if (UsageHistory & (1 << BitIdx)) {
|
|
NumUsed++;
|
|
}
|
|
}
|
|
|
|
return NumUsed;
|
|
}
|
|
|
|
ULONG
|
|
PfSvGetTraceEndIdx(
|
|
PPF_TRACE_HEADER Trace
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Determines the index of the last page logged in the trace.
|
|
|
|
Arguments:
|
|
|
|
Trace - Pointer to trace.
|
|
|
|
Return Value:
|
|
|
|
Index of the last page logged.
|
|
|
|
--*/
|
|
|
|
{
|
|
ULONG TotalFaults;
|
|
ULONG PeriodIdx;
|
|
ULONG *Id;
|
|
|
|
DBGPR((PFID,PFSTRC,"PFSVC: GetTraceEndIdx(%p)\n", Trace));
|
|
|
|
TotalFaults = Trace->FaultsPerPeriod[0];
|
|
|
|
for (PeriodIdx = 1; PeriodIdx < PF_MAX_NUM_TRACE_PERIODS; PeriodIdx++) {
|
|
|
|
if(Trace->FaultsPerPeriod[PeriodIdx] < PFSVC_MIN_FAULT_THRESHOLD) {
|
|
|
|
//
|
|
// If this is not the boot scenario, determine that
|
|
// scenario has ended when logged pagefaults for a time
|
|
// slice falls below minimum.
|
|
//
|
|
|
|
if (Trace->ScenarioType != PfSystemBootScenarioType) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
TotalFaults += Trace->FaultsPerPeriod[PeriodIdx];
|
|
}
|
|
|
|
//
|
|
// Sum of entries per period should not be greater than all
|
|
// entries logged.
|
|
//
|
|
|
|
PFSVC_ASSERT(TotalFaults <= Trace->NumEntries);
|
|
|
|
DBGPR((PFID,PFSTRC,"PFSVC: GetTraceEndIdx(%p)=%d\n", Trace, TotalFaults));
|
|
|
|
return TotalFaults;
|
|
}
|
|
|
|
//
|
|
// Routines to write updated scenario instructions to the scenario
|
|
// file.
|
|
//
|
|
|
|
DWORD
|
|
PfSvWriteScenario(
|
|
PPFSVC_SCENARIO_INFO ScenarioInfo,
|
|
PWCHAR ScenarioFilePath
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Prepare scenario instructions structure from the scenarion info
|
|
and write it to the specified file.
|
|
|
|
Arguments:
|
|
|
|
ScenarioInfo - Pointer to scenario info structure.
|
|
|
|
ScenarioFilePath - Path to scenarion file to update.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
DWORD BytesWritten;
|
|
HANDLE OutputHandle;
|
|
DWORD ErrorCode;
|
|
BOOL bResult;
|
|
PPF_SCENARIO_HEADER Scenario;
|
|
ULONG FailedCheck;
|
|
|
|
//
|
|
// Initialize locals.
|
|
//
|
|
|
|
OutputHandle = INVALID_HANDLE_VALUE;
|
|
Scenario = NULL;
|
|
|
|
DBGPR((PFID,PFTRC,"PFSVC: WriteScenario(%ws)\n", ScenarioFilePath));
|
|
|
|
//
|
|
// Build scenario dump from information we gathered.
|
|
//
|
|
|
|
ErrorCode = PfSvPrepareScenarioDump(ScenarioInfo, &Scenario);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Make sure the scenario we built passes the checks.
|
|
//
|
|
|
|
if (!PfSvVerifyScenarioBuffer(Scenario, Scenario->Size, &FailedCheck) ||
|
|
Scenario->ServiceVersion != PFSVC_SERVICE_VERSION) {
|
|
PFSVC_ASSERT(FALSE);
|
|
ErrorCode = ERROR_BAD_FORMAT;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Write out the buffer.
|
|
//
|
|
|
|
ErrorCode = PfSvWriteBuffer(ScenarioFilePath, Scenario, Scenario->Size);
|
|
|
|
//
|
|
// Fall through with ErrorCode.
|
|
//
|
|
|
|
cleanup:
|
|
|
|
if (Scenario) {
|
|
PFSVC_FREE(Scenario);
|
|
}
|
|
|
|
DBGPR((PFID,PFTRC,"PFSVC: WriteScenario(%ws)=%x\n", ScenarioFilePath, ErrorCode));
|
|
|
|
return ErrorCode;
|
|
}
|
|
|
|
DWORD
|
|
PfSvPrepareScenarioDump(
|
|
IN PPFSVC_SCENARIO_INFO ScenarioInfo,
|
|
OUT PPF_SCENARIO_HEADER *ScenarioPtr
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Allocate a contiguous scenario buffer and fill it in with
|
|
information in ScenarioInfo. ScenarioInfo is not modified.
|
|
|
|
Arguments:
|
|
|
|
ScenarioInfo - Pointer to scenario information built from an
|
|
existing scenario file and a scenario trace.
|
|
|
|
ScenarioPtr - If successful, pointer to allocated and built
|
|
scenario is put here. The caller should free this buffer when
|
|
done with it.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
PPF_SCENARIO_HEADER Scenario;
|
|
ULONG Size;
|
|
DWORD ErrorCode;
|
|
PPF_SECTION_RECORD Sections;
|
|
PPF_SECTION_RECORD Section;
|
|
PPFSVC_SECTION_NODE SectionNode;
|
|
ULONG SectionIdx;
|
|
ULONG CurSectionIdx;
|
|
PPF_PAGE_RECORD Pages;
|
|
PPF_PAGE_RECORD Page;
|
|
PPF_PAGE_RECORD PreviousPage;
|
|
PPFSVC_PAGE_NODE PageNode;
|
|
ULONG CurPageIdx;
|
|
PCHAR FileNames;
|
|
ULONG CurFileInfoOffset;
|
|
PCHAR DestPtr;
|
|
PLIST_ENTRY SectHead;
|
|
PLIST_ENTRY SectNext;
|
|
PLIST_ENTRY PageHead;
|
|
PLIST_ENTRY PageNext;
|
|
ULONG FileNameSize;
|
|
PPFSVC_VOLUME_NODE VolumeNode;
|
|
PLIST_ENTRY HeadVolume;
|
|
PLIST_ENTRY NextVolume;
|
|
PCHAR MetadataInfoBase;
|
|
PPF_METADATA_RECORD MetadataRecordTable;
|
|
PPF_METADATA_RECORD MetadataRecord;
|
|
ULONG MetadataInfoSize;
|
|
ULONG NumMetadataRecords;
|
|
ULONG CurMetadataRecordIdx;
|
|
ULONG CopySize;
|
|
ULONG CurFilePrefetchIdx;
|
|
ULONG FilePrefetchInfoSize;
|
|
PFILE_PREFETCH FilePrefetchInfo;
|
|
WCHAR *DirectoryPath;
|
|
ULONG DirectoryPathLength;
|
|
PPFSVC_PATH PathEntry;
|
|
LARGE_INTEGER IndexNumber;
|
|
ULONG DirectoryPathInfoSize;
|
|
ULONG DirectoryPathSize;
|
|
PPF_COUNTED_STRING DirectoryPathCS;
|
|
|
|
//
|
|
// Initialize locals.
|
|
//
|
|
|
|
Scenario = NULL;
|
|
|
|
DBGPR((PFID,PFTRC,"PFSVC: PrepareScenarioDump()\n"));
|
|
|
|
//
|
|
// Calculate how big the scenario is going to be.
|
|
//
|
|
|
|
Size = sizeof(PF_SCENARIO_HEADER);
|
|
Size += ScenarioInfo->ScenHeader.NumSections * sizeof(PF_SECTION_RECORD);
|
|
Size += ScenarioInfo->ScenHeader.NumPages * sizeof(PF_PAGE_RECORD);
|
|
Size += ScenarioInfo->ScenHeader.FileNameInfoSize;
|
|
|
|
//
|
|
// Add space for the metadata prefetch information.
|
|
//
|
|
|
|
//
|
|
// Make some space for aligning the metadata records table.
|
|
//
|
|
|
|
MetadataInfoSize = _alignof(PF_METADATA_RECORD);
|
|
|
|
HeadVolume = &ScenarioInfo->VolumeList;
|
|
NextVolume = HeadVolume->Flink;
|
|
|
|
NumMetadataRecords = 0;
|
|
|
|
while (NextVolume != HeadVolume) {
|
|
|
|
VolumeNode = CONTAINING_RECORD(NextVolume,
|
|
PFSVC_VOLUME_NODE,
|
|
VolumeLink);
|
|
|
|
NextVolume = NextVolume->Flink;
|
|
|
|
//
|
|
// If there are no sections at all on this volume, skip it.
|
|
//
|
|
|
|
if (VolumeNode->NumAllSections == 0) {
|
|
continue;
|
|
}
|
|
|
|
NumMetadataRecords++;
|
|
|
|
//
|
|
// Metadata record:
|
|
//
|
|
|
|
MetadataInfoSize += sizeof(PF_METADATA_RECORD);
|
|
|
|
//
|
|
// Volume Path:
|
|
//
|
|
|
|
MetadataInfoSize += (VolumeNode->VolumePathLength + 1) * sizeof(WCHAR);
|
|
|
|
//
|
|
// FilePrefetchInfo buffer: This has to be ULONGLONG
|
|
// aligned. Add extra space for that in case.
|
|
//
|
|
|
|
MetadataInfoSize += _alignof(FILE_PREFETCH);
|
|
MetadataInfoSize += sizeof(FILE_PREFETCH);
|
|
|
|
if (VolumeNode->NumSections) {
|
|
MetadataInfoSize += (VolumeNode->NumSections - 1) * sizeof(ULONGLONG);
|
|
}
|
|
|
|
MetadataInfoSize += VolumeNode->DirectoryList.NumPaths * sizeof(ULONGLONG);
|
|
|
|
if (VolumeNode->MFTSectionNode) {
|
|
MetadataInfoSize += VolumeNode->MFTSectionNode->MFTNumPagesToPrefetch * sizeof(ULONGLONG);
|
|
}
|
|
|
|
//
|
|
// Add space for the directory paths on this volume.
|
|
//
|
|
|
|
MetadataInfoSize += VolumeNode->DirectoryList.NumPaths * sizeof(PF_COUNTED_STRING);
|
|
MetadataInfoSize += VolumeNode->DirectoryList.TotalLength * sizeof(WCHAR);
|
|
|
|
//
|
|
// Note that PF_COUNTED_STRING contains space for one
|
|
// character. DirectoryList's total length excludes NUL's at
|
|
// the end of each path.
|
|
//
|
|
}
|
|
|
|
Size += MetadataInfoSize;
|
|
|
|
//
|
|
// Allocate scenario buffer.
|
|
//
|
|
|
|
Scenario = PFSVC_ALLOC(Size);
|
|
|
|
if (!Scenario) {
|
|
ErrorCode = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Copy the header and set the size.
|
|
//
|
|
|
|
*Scenario = ScenarioInfo->ScenHeader;
|
|
Scenario->Size = Size;
|
|
|
|
DestPtr = (PCHAR) Scenario + sizeof(*Scenario);
|
|
|
|
//
|
|
// Initialize where our data is going.
|
|
//
|
|
|
|
Sections = (PPF_SECTION_RECORD) DestPtr;
|
|
Scenario->SectionInfoOffset = (ULONG) (DestPtr - (PCHAR) Scenario);
|
|
CurSectionIdx = 0;
|
|
|
|
DestPtr += Scenario->NumSections * sizeof(PF_SECTION_RECORD);
|
|
|
|
Pages = (PPF_PAGE_RECORD) DestPtr;
|
|
Scenario->PageInfoOffset = (ULONG) (DestPtr - (PCHAR) Scenario);
|
|
CurPageIdx = 0;
|
|
|
|
DestPtr += Scenario->NumPages * sizeof(PF_PAGE_RECORD);
|
|
|
|
FileNames = DestPtr;
|
|
Scenario->FileNameInfoOffset = (ULONG) (DestPtr - (PCHAR) Scenario);
|
|
CurFileInfoOffset = 0;
|
|
|
|
DestPtr += Scenario->FileNameInfoSize;
|
|
|
|
//
|
|
// Extra space for this alignment was allocated upfront.
|
|
//
|
|
|
|
PFSVC_ASSERT(PF_IS_POWER_OF_TWO(_alignof(PF_METADATA_RECORD)));
|
|
MetadataInfoBase = PF_ALIGN_UP(DestPtr, _alignof(PF_METADATA_RECORD));
|
|
DestPtr += MetadataInfoSize;
|
|
|
|
MetadataRecordTable = (PPF_METADATA_RECORD) MetadataInfoBase;
|
|
Scenario->MetadataInfoOffset = (ULONG) (MetadataInfoBase - (PCHAR) Scenario);
|
|
Scenario->MetadataInfoSize = (ULONG) (DestPtr - MetadataInfoBase);
|
|
Scenario->NumMetadataRecords = NumMetadataRecords;
|
|
|
|
//
|
|
// Destination pointer should be at the end of the allocated
|
|
// buffer now.
|
|
//
|
|
|
|
if (DestPtr != (PCHAR) Scenario + Scenario->Size) {
|
|
|
|
PFSVC_ASSERT(FALSE);
|
|
|
|
ErrorCode = ERROR_BAD_FORMAT;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Walk through the sections on the new scenario info and copy
|
|
// them.
|
|
//
|
|
|
|
SectHead = &ScenarioInfo->SectionList;
|
|
SectNext = SectHead->Flink;
|
|
|
|
while (SectHead != SectNext) {
|
|
|
|
SectionNode = CONTAINING_RECORD(SectNext,
|
|
PFSVC_SECTION_NODE,
|
|
SectionLink);
|
|
|
|
//
|
|
// The target section record.
|
|
//
|
|
|
|
Section = &Sections[CurSectionIdx];
|
|
|
|
//
|
|
// Copy section record info.
|
|
//
|
|
|
|
*Section = SectionNode->SectionRecord;
|
|
|
|
//
|
|
// Copy pages for the section.
|
|
//
|
|
|
|
Section->FirstPageIdx = PF_INVALID_PAGE_IDX;
|
|
PreviousPage = NULL;
|
|
|
|
PageHead = &SectionNode->PageList;
|
|
PageNext = PageHead->Flink;
|
|
|
|
while (PageNext != PageHead) {
|
|
|
|
PageNode = CONTAINING_RECORD(PageNext,
|
|
PFSVC_PAGE_NODE,
|
|
PageLink);
|
|
|
|
Page = &Pages[CurPageIdx];
|
|
|
|
//
|
|
// If this is the first page in the section, update first
|
|
// page index on the section record.
|
|
//
|
|
|
|
if (Section->FirstPageIdx == PF_INVALID_PAGE_IDX) {
|
|
Section->FirstPageIdx = CurPageIdx;
|
|
}
|
|
|
|
//
|
|
// Copy page record.
|
|
//
|
|
|
|
*Page = PageNode->PageRecord;
|
|
|
|
//
|
|
// Update NextPageIdx on the previous page if there is
|
|
// one.
|
|
//
|
|
|
|
if (PreviousPage) {
|
|
PreviousPage->NextPageIdx = CurPageIdx;
|
|
}
|
|
|
|
//
|
|
// Update previous page.
|
|
//
|
|
|
|
PreviousPage = Page;
|
|
|
|
//
|
|
// Set next link to list termination now. If there is a
|
|
// next page it is going to update this.
|
|
//
|
|
|
|
Page->NextPageIdx = PF_INVALID_PAGE_IDX;
|
|
|
|
//
|
|
// Update position in the page record table.
|
|
//
|
|
|
|
CurPageIdx++;
|
|
|
|
PFSVC_ASSERT(CurPageIdx <= Scenario->NumPages);
|
|
|
|
PageNext = PageNext->Flink;
|
|
}
|
|
|
|
//
|
|
// Copy over file name.
|
|
//
|
|
|
|
FileNameSize = (Section->FileNameLength + 1) * sizeof(WCHAR);
|
|
|
|
RtlCopyMemory(FileNames + CurFileInfoOffset,
|
|
SectionNode->FilePath,
|
|
FileNameSize);
|
|
|
|
//
|
|
// Update section record's file name offset.
|
|
//
|
|
|
|
Section->FileNameOffset = CurFileInfoOffset;
|
|
|
|
//
|
|
// Update current index into file name info.
|
|
//
|
|
|
|
CurFileInfoOffset += FileNameSize;
|
|
|
|
PFSVC_ASSERT(CurFileInfoOffset <= Scenario->FileNameInfoSize);
|
|
|
|
//
|
|
// Update our position in the section table.
|
|
//
|
|
|
|
CurSectionIdx++;
|
|
|
|
PFSVC_ASSERT(CurSectionIdx <= Scenario->NumSections);
|
|
|
|
SectNext = SectNext->Flink;
|
|
}
|
|
|
|
//
|
|
// Make sure we filled up the tables.
|
|
//
|
|
|
|
if (CurSectionIdx != Scenario->NumSections ||
|
|
CurPageIdx != Scenario->NumPages ||
|
|
CurFileInfoOffset != Scenario->FileNameInfoSize) {
|
|
|
|
PFSVC_ASSERT(FALSE);
|
|
|
|
ErrorCode = ERROR_BAD_FORMAT;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Build and copy the metadata prefetch information.
|
|
//
|
|
|
|
//
|
|
// Set our target to right after the metadata records table.
|
|
//
|
|
|
|
DestPtr = MetadataInfoBase + sizeof(PF_METADATA_RECORD) * NumMetadataRecords;
|
|
CurMetadataRecordIdx = 0;
|
|
|
|
HeadVolume = &ScenarioInfo->VolumeList;
|
|
NextVolume = HeadVolume->Flink;
|
|
|
|
while (NextVolume != HeadVolume) {
|
|
|
|
VolumeNode = CONTAINING_RECORD(NextVolume,
|
|
PFSVC_VOLUME_NODE,
|
|
VolumeLink);
|
|
|
|
NextVolume = NextVolume->Flink;
|
|
|
|
//
|
|
// If there are no sections at all on this volume, skip it.
|
|
//
|
|
|
|
if (VolumeNode->NumAllSections == 0) {
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Make sure we are within bounds.
|
|
//
|
|
|
|
if (CurMetadataRecordIdx >= NumMetadataRecords) {
|
|
PFSVC_ASSERT(CurMetadataRecordIdx < NumMetadataRecords);
|
|
ErrorCode = ERROR_BAD_FORMAT;
|
|
goto cleanup;
|
|
}
|
|
|
|
MetadataRecord = &MetadataRecordTable[CurMetadataRecordIdx];
|
|
CurMetadataRecordIdx++;
|
|
|
|
//
|
|
// Copy volume identifiers.
|
|
//
|
|
|
|
MetadataRecord->SerialNumber = VolumeNode->SerialNumber;
|
|
MetadataRecord->CreationTime = VolumeNode->CreationTime;
|
|
|
|
//
|
|
// Copy volume name.
|
|
//
|
|
|
|
MetadataRecord->VolumeNameOffset = (ULONG) (DestPtr - MetadataInfoBase);
|
|
MetadataRecord->VolumeNameLength = VolumeNode->VolumePathLength;
|
|
CopySize = (VolumeNode->VolumePathLength + 1) * sizeof(WCHAR);
|
|
|
|
if (DestPtr + CopySize > (PCHAR) Scenario + Scenario->Size) {
|
|
PFSVC_ASSERT(FALSE);
|
|
ErrorCode = ERROR_BAD_FORMAT;
|
|
goto cleanup;
|
|
}
|
|
|
|
RtlCopyMemory(DestPtr, VolumeNode->VolumePath, CopySize);
|
|
DestPtr += CopySize;
|
|
|
|
//
|
|
// Align and update DestPtr for the FILE_PREFETCH structure.
|
|
//
|
|
|
|
PFSVC_ASSERT(PF_IS_POWER_OF_TWO(_alignof(FILE_PREFETCH)));
|
|
DestPtr = PF_ALIGN_UP(DestPtr, _alignof(FILE_PREFETCH));
|
|
FilePrefetchInfo = (PFILE_PREFETCH) DestPtr;
|
|
MetadataRecord->FilePrefetchInfoOffset = (ULONG) (DestPtr - MetadataInfoBase);
|
|
|
|
//
|
|
// Calculate size of the file prefetch information structure.
|
|
//
|
|
|
|
FilePrefetchInfoSize = sizeof(FILE_PREFETCH);
|
|
|
|
if (VolumeNode->NumSections) {
|
|
FilePrefetchInfoSize += (VolumeNode->NumSections - 1) * sizeof(ULONGLONG);
|
|
}
|
|
|
|
if (VolumeNode->MFTSectionNode) {
|
|
FilePrefetchInfoSize += VolumeNode->MFTSectionNode->MFTNumPagesToPrefetch * sizeof(ULONGLONG);
|
|
}
|
|
|
|
FilePrefetchInfoSize += VolumeNode->DirectoryList.NumPaths * sizeof(ULONGLONG);
|
|
MetadataRecord->FilePrefetchInfoSize = FilePrefetchInfoSize;
|
|
|
|
if (DestPtr + FilePrefetchInfoSize > (PCHAR) Scenario + Scenario->Size) {
|
|
PFSVC_ASSERT(FALSE);
|
|
ErrorCode = ERROR_BAD_FORMAT;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Update destination pointer.
|
|
//
|
|
|
|
DestPtr += FilePrefetchInfoSize;
|
|
|
|
//
|
|
// Initialize file prefetch information structure.
|
|
//
|
|
|
|
FilePrefetchInfo->Type = FILE_PREFETCH_TYPE_FOR_CREATE;
|
|
FilePrefetchInfo->Count = VolumeNode->NumSections + VolumeNode->DirectoryList.NumPaths;
|
|
if (VolumeNode->MFTSectionNode) {
|
|
FilePrefetchInfo->Count += VolumeNode->MFTSectionNode->MFTNumPagesToPrefetch;
|
|
}
|
|
|
|
//
|
|
// Build list of file indexes to prefetch:
|
|
//
|
|
|
|
CurFilePrefetchIdx = 0;
|
|
|
|
//
|
|
// Add file system index numbers for sections.
|
|
//
|
|
|
|
SectHead = &VolumeNode->SectionList;
|
|
SectNext = SectHead->Flink;
|
|
|
|
while(SectNext != SectHead) {
|
|
|
|
SectionNode = CONTAINING_RECORD(SectNext,
|
|
PFSVC_SECTION_NODE,
|
|
SectionVolumeLink);
|
|
|
|
SectNext = SectNext->Flink;
|
|
|
|
if (CurFilePrefetchIdx >= VolumeNode->NumSections) {
|
|
PFSVC_ASSERT(FALSE);
|
|
ErrorCode = ERROR_BAD_FORMAT;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Add the filesystem index number for this section to the list.
|
|
//
|
|
|
|
FilePrefetchInfo->Prefetch[CurFilePrefetchIdx] =
|
|
SectionNode->FileIndexNumber.QuadPart;
|
|
CurFilePrefetchIdx++;
|
|
}
|
|
|
|
//
|
|
// Add file system index numbers for directories.
|
|
//
|
|
|
|
PathEntry = NULL;
|
|
|
|
while (PathEntry = PfSvGetNextPathSorted(&VolumeNode->DirectoryList,
|
|
PathEntry)) {
|
|
|
|
DirectoryPath = PathEntry->Path;
|
|
|
|
//
|
|
// Get the file index number for this directory and add it
|
|
// to the list we'll ask the filesystem to prefetch.
|
|
//
|
|
|
|
ErrorCode = PfSvGetFileIndexNumber(DirectoryPath, &IndexNumber);
|
|
|
|
if (ErrorCode == ERROR_SUCCESS) {
|
|
FilePrefetchInfo->Prefetch[CurFilePrefetchIdx] = IndexNumber.QuadPart;
|
|
} else {
|
|
FilePrefetchInfo->Prefetch[CurFilePrefetchIdx] = 0;
|
|
}
|
|
|
|
CurFilePrefetchIdx++;
|
|
}
|
|
|
|
//
|
|
// Add file system index numbers that we drive from direct MFT access.
|
|
//
|
|
|
|
if (VolumeNode->MFTSectionNode) {
|
|
|
|
SectionNode = VolumeNode->MFTSectionNode;
|
|
|
|
for (PageNext = SectionNode->PageList.Flink;
|
|
PageNext != &SectionNode->PageList;
|
|
PageNext = PageNext->Flink) {
|
|
|
|
PageNode = CONTAINING_RECORD(PageNext,
|
|
PFSVC_PAGE_NODE,
|
|
PageLink);
|
|
|
|
if (!PageNode->PageRecord.IsIgnore) {
|
|
|
|
//
|
|
// We know the file offset in MFT. Every file record is
|
|
// 1KB == 2^10 bytes. To convert fileoffset in MFT to a
|
|
// file record number we just shift it by 10.
|
|
//
|
|
|
|
FilePrefetchInfo->Prefetch[CurFilePrefetchIdx] =
|
|
PageNode->PageRecord.FileOffset >> 10;
|
|
|
|
CurFilePrefetchIdx++;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// We should have specified all the file index numbers.
|
|
//
|
|
|
|
PFSVC_ASSERT(CurFilePrefetchIdx == FilePrefetchInfo->Count);
|
|
|
|
//
|
|
// Add paths for directories accessed on this volume.
|
|
//
|
|
|
|
MetadataRecord->NumDirectories = VolumeNode->DirectoryList.NumPaths;
|
|
MetadataRecord->DirectoryPathsOffset = (ULONG)(DestPtr - MetadataInfoBase);
|
|
|
|
PathEntry = NULL;
|
|
while (PathEntry = PfSvGetNextPathSorted(&VolumeNode->DirectoryList,
|
|
PathEntry)) {
|
|
|
|
DirectoryPath = PathEntry->Path;
|
|
DirectoryPathLength = PathEntry->Length;
|
|
|
|
//
|
|
// Calculate how big the entry for this path is going to
|
|
// be and make sure it will be within bounds.
|
|
//
|
|
|
|
DirectoryPathSize = sizeof(PF_COUNTED_STRING);
|
|
DirectoryPathSize += DirectoryPathLength * sizeof(WCHAR);
|
|
|
|
if (DestPtr + DirectoryPathSize > (PCHAR) Scenario + Scenario->Size) {
|
|
PFSVC_ASSERT(FALSE);
|
|
ErrorCode = ERROR_BAD_FORMAT;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Copy over the directory path.
|
|
//
|
|
|
|
DirectoryPathCS = (PPF_COUNTED_STRING) DestPtr;
|
|
DirectoryPathCS->Length = (USHORT) DirectoryPathLength;
|
|
RtlCopyMemory(DirectoryPathCS->String,
|
|
DirectoryPath,
|
|
(DirectoryPathLength + 1) * sizeof(WCHAR));
|
|
|
|
DestPtr += DirectoryPathSize;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Make sure we are not past the end of the buffer.
|
|
//
|
|
|
|
if (DestPtr > (PCHAR) Scenario + Scenario->Size) {
|
|
PFSVC_ASSERT(FALSE);
|
|
ErrorCode = ERROR_BAD_FORMAT;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Set up return pointer.
|
|
//
|
|
|
|
*ScenarioPtr = Scenario;
|
|
|
|
ErrorCode = ERROR_SUCCESS;
|
|
|
|
cleanup:
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
if (Scenario != NULL) {
|
|
PFSVC_FREE(Scenario);
|
|
}
|
|
}
|
|
|
|
DBGPR((PFID,PFTRC,"PFSVC: PrepareScenarioDump()=%x\n", ErrorCode));
|
|
|
|
return ErrorCode;
|
|
}
|
|
|
|
//
|
|
// Routines to maintain the optimal disk layout file and update disk
|
|
// layout.
|
|
//
|
|
|
|
DWORD
|
|
PfSvUpdateOptimalLayout(
|
|
PPFSVC_IDLE_TASK Task
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine will determine if the optimal disk layout has to be
|
|
updated and if so it will write out a new layout file and launch
|
|
the defragger.
|
|
|
|
Arguments:
|
|
|
|
Task - If specified the function will check Task every once in a
|
|
while to see if it should exit with ERROR_RETRY.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
ULARGE_INTEGER CurrentTimeLI;
|
|
ULARGE_INTEGER LastDiskLayoutTimeLI;
|
|
ULARGE_INTEGER MinTimeBeforeRelayoutLI;
|
|
PFSVC_PATH_LIST OptimalLayout;
|
|
PFSVC_PATH_LIST CurrentLayout;
|
|
FILETIME LastDiskLayoutTime;
|
|
FILETIME FirstDiskLayoutTime;
|
|
FILETIME LayoutFileTime;
|
|
FILETIME CurrentTime;
|
|
PPFSVC_PATH_LIST NewLayout;
|
|
PWCHAR LayoutFilePath;
|
|
ULONG LayoutFilePathBufferSize;
|
|
DWORD ErrorCode;
|
|
DWORD BootScenarioProcessed;
|
|
DWORD BootFilesWereOptimized;
|
|
DWORD MinHoursBeforeRelayout;
|
|
DWORD Size;
|
|
DWORD RegValueType;
|
|
BOOLEAN LayoutChanged;
|
|
BOOLEAN MissingOriginalLayoutFile;
|
|
BOOLEAN BootPrefetchingIsEnabled;
|
|
BOOLEAN CheckForLayoutFrequencyLimit;
|
|
|
|
//
|
|
// Initialize locals.
|
|
//
|
|
|
|
LayoutFilePath = NULL;
|
|
LayoutFilePathBufferSize = 0;
|
|
PfSvInitializePathList(&OptimalLayout, NULL, FALSE);
|
|
PfSvInitializePathList(&CurrentLayout, NULL, FALSE);
|
|
|
|
DBGPR((PFID,PFTRC,"PFSVC: UpdateOptimalLayout(%p)\n", Task));
|
|
|
|
//
|
|
// Determine when we updated the disk layout from the layout file.
|
|
//
|
|
|
|
ErrorCode = PfSvGetLastDiskLayoutTime(&LastDiskLayoutTime);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Query whether boot files have been optimized.
|
|
//
|
|
|
|
Size = sizeof(BootFilesWereOptimized);
|
|
|
|
ErrorCode = RegQueryValueEx(PfSvcGlobals.ServiceDataKey,
|
|
PFSVC_BOOT_FILES_OPTIMIZED_VALUE_NAME,
|
|
NULL,
|
|
&RegValueType,
|
|
(PVOID) &BootFilesWereOptimized,
|
|
&Size);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
BootFilesWereOptimized = FALSE;
|
|
}
|
|
|
|
//
|
|
// Get optimal layout file path.
|
|
//
|
|
|
|
ErrorCode = PfSvGetLayoutFilePath(&LayoutFilePath,
|
|
&LayoutFilePathBufferSize);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Determine when the file was last modified.
|
|
//
|
|
|
|
ErrorCode = PfSvGetLastWriteTime(LayoutFilePath, &LayoutFileTime);
|
|
|
|
if (ErrorCode == ERROR_SUCCESS) {
|
|
|
|
MissingOriginalLayoutFile = FALSE;
|
|
|
|
//
|
|
// If the file was modified after we laid out the files on the disk
|
|
// its contents are not interesting. Otherwise, if the new optimal
|
|
// layout is similar to layout specified in the file, we may not
|
|
// have to re-layout the files.
|
|
//
|
|
|
|
if (CompareFileTime(&LayoutFileTime, &LastDiskLayoutTime) <= 0) {
|
|
|
|
//
|
|
// Read the current layout.
|
|
//
|
|
|
|
ErrorCode = PfSvReadLayout(LayoutFilePath,
|
|
&CurrentLayout,
|
|
&LayoutFileTime);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
|
|
//
|
|
// The layout file seems to be bad / inaccesible.
|
|
// Cleanup the path list, so a brand new one gets
|
|
// built.
|
|
//
|
|
|
|
PfSvCleanupPathList(&CurrentLayout);
|
|
PfSvInitializePathList(&CurrentLayout, NULL, FALSE);
|
|
}
|
|
}
|
|
|
|
} else {
|
|
|
|
//
|
|
// We could not get the timestamp on the original layout file.
|
|
// It might have been deleted.
|
|
//
|
|
|
|
MissingOriginalLayoutFile = TRUE;
|
|
}
|
|
|
|
//
|
|
// Determine what the current optimal layout should be from
|
|
// scenario files.
|
|
//
|
|
|
|
ErrorCode = PfSvDetermineOptimalLayout(Task,
|
|
&OptimalLayout,
|
|
&BootScenarioProcessed);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Update current layout based on what optimal layout should be.
|
|
// If the two are similar we don't need to launch the defragger.
|
|
//
|
|
|
|
ErrorCode = PfSvUpdateLayout(&CurrentLayout,
|
|
&OptimalLayout,
|
|
&LayoutChanged);
|
|
|
|
if (ErrorCode == ERROR_SUCCESS) {
|
|
|
|
if (!LayoutChanged) {
|
|
ErrorCode = ERROR_SUCCESS;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// We'll use the updated layout.
|
|
//
|
|
|
|
NewLayout = &CurrentLayout;
|
|
|
|
} else {
|
|
|
|
//
|
|
// We'll run with the optimal layout.
|
|
//
|
|
|
|
NewLayout = &OptimalLayout;
|
|
}
|
|
|
|
//
|
|
// Optimal way to layout files has changed. Write out the new layout.
|
|
//
|
|
|
|
ErrorCode = PfSvSaveLayout(LayoutFilePath,
|
|
NewLayout,
|
|
&LayoutFileTime);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// If enough time has not passed since last disk layout don't run the
|
|
// defragger again unless...
|
|
//
|
|
|
|
CheckForLayoutFrequencyLimit = TRUE;
|
|
|
|
//
|
|
// - We've been explicitly asked to update the layout (i.e. no idle
|
|
// task context.)
|
|
//
|
|
|
|
if (!Task) {
|
|
CheckForLayoutFrequencyLimit = FALSE;
|
|
}
|
|
|
|
//
|
|
// - Someone seems to have deleted the layout file and we recreated it.
|
|
//
|
|
|
|
if (MissingOriginalLayoutFile) {
|
|
CheckForLayoutFrequencyLimit = FALSE;
|
|
}
|
|
|
|
//
|
|
// - Boot prefetching is enabled but boot files have not been optimized
|
|
// yet and we processed the list of files from the boot this time.
|
|
//
|
|
|
|
if (PfSvcGlobals.Parameters.EnableStatus[PfSystemBootScenarioType] == PfSvEnabled) {
|
|
if (!BootFilesWereOptimized && BootScenarioProcessed) {
|
|
CheckForLayoutFrequencyLimit = FALSE;
|
|
}
|
|
}
|
|
|
|
if (CheckForLayoutFrequencyLimit) {
|
|
|
|
//
|
|
// We will check to see if enough time has passed by getting current
|
|
// time and comparing it to last disk layout time.
|
|
//
|
|
|
|
LastDiskLayoutTimeLI.LowPart = LastDiskLayoutTime.dwLowDateTime;
|
|
LastDiskLayoutTimeLI.HighPart = LastDiskLayoutTime.dwHighDateTime;
|
|
|
|
//
|
|
// Get current time as file time.
|
|
//
|
|
|
|
GetSystemTimeAsFileTime(&CurrentTime);
|
|
|
|
CurrentTimeLI.LowPart = CurrentTime.dwLowDateTime;
|
|
CurrentTimeLI.HighPart = CurrentTime.dwHighDateTime;
|
|
|
|
//
|
|
// Check to make sure that current time is after last disk layout time
|
|
// (in case the user has played with time.)
|
|
//
|
|
|
|
if (CurrentTimeLI.QuadPart > LastDiskLayoutTimeLI.QuadPart) {
|
|
|
|
//
|
|
// Query how long has to pass before we re-layout the files on
|
|
// disk.
|
|
//
|
|
|
|
Size = sizeof(MinHoursBeforeRelayout);
|
|
|
|
ErrorCode = RegQueryValueEx(PfSvcGlobals.ServiceDataKey,
|
|
PFSVC_MIN_RELAYOUT_HOURS_VALUE_NAME,
|
|
NULL,
|
|
&RegValueType,
|
|
(PVOID) &MinHoursBeforeRelayout,
|
|
&Size);
|
|
|
|
if (ErrorCode == ERROR_SUCCESS) {
|
|
MinTimeBeforeRelayoutLI.QuadPart = PFSVC_NUM_100NS_IN_AN_HOUR * MinHoursBeforeRelayout;
|
|
} else {
|
|
MinTimeBeforeRelayoutLI.QuadPart = PFSVC_MIN_TIME_BEFORE_DISK_RELAYOUT;
|
|
}
|
|
|
|
if (CurrentTimeLI.QuadPart < LastDiskLayoutTimeLI.QuadPart +
|
|
MinTimeBeforeRelayoutLI.QuadPart) {
|
|
|
|
//
|
|
// Not enough time has passed before last disk layout.
|
|
//
|
|
|
|
ErrorCode = ERROR_INVALID_TIME;
|
|
goto cleanup;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Launch the defragger for layout optimization.
|
|
//
|
|
|
|
ErrorCode = PfSvLaunchDefragger(Task, TRUE, NULL);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Save whether boot files were optimized.
|
|
//
|
|
|
|
ErrorCode = RegSetValueEx(PfSvcGlobals.ServiceDataKey,
|
|
PFSVC_BOOT_FILES_OPTIMIZED_VALUE_NAME,
|
|
0,
|
|
REG_DWORD,
|
|
(PVOID) &BootScenarioProcessed,
|
|
sizeof(BootScenarioProcessed));
|
|
|
|
//
|
|
// Save the last time we updated disk layout to the registry.
|
|
//
|
|
|
|
ErrorCode = PfSvSetLastDiskLayoutTime(&LayoutFileTime);
|
|
|
|
//
|
|
// Fall through with error code.
|
|
//
|
|
|
|
cleanup:
|
|
|
|
DBGPR((PFID,PFTRC,"PFSVC: UpdateOptimalLayout(%p)=%x\n", Task, ErrorCode));
|
|
|
|
PfSvCleanupPathList(&OptimalLayout);
|
|
PfSvCleanupPathList(&CurrentLayout);
|
|
|
|
if (LayoutFilePath) {
|
|
PFSVC_FREE(LayoutFilePath);
|
|
}
|
|
|
|
return ErrorCode;
|
|
}
|
|
|
|
DWORD
|
|
PfSvUpdateLayout (
|
|
PPFSVC_PATH_LIST CurrentLayout,
|
|
PPFSVC_PATH_LIST OptimalLayout,
|
|
PBOOLEAN LayoutChanged
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine updates the specified layout based on the new optimal
|
|
layout. If the two layouts are similar, CurrentLayout is not updated.
|
|
|
|
An error may be returned while CurrentLayout is being updated. It is the
|
|
caller's responsibility to revert CurrentLayout to its original in that case.
|
|
|
|
Arguments:
|
|
|
|
CurrentLayout - Current file layout.
|
|
|
|
OptimalLayout - Newly determined optimal file layout.
|
|
|
|
LayoutChanged - Whether Layout was changed.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
DWORD ErrorCode;
|
|
PPFSVC_PATH PathEntry;
|
|
ULONG NumOptimalLayoutFiles;
|
|
ULONG NumMissingFiles;
|
|
ULONG NumCommonFiles;
|
|
ULONG NumCurrentLayoutOnlyFiles;
|
|
|
|
//
|
|
// Initialize locals.
|
|
//
|
|
|
|
NumOptimalLayoutFiles = 0;
|
|
NumMissingFiles = 0;
|
|
|
|
//
|
|
// Go through the paths in the new layout counting the differences with
|
|
// the current layout.
|
|
//
|
|
|
|
PathEntry = NULL;
|
|
|
|
while (PathEntry = PfSvGetNextPathInOrder(OptimalLayout, PathEntry)) {
|
|
|
|
NumOptimalLayoutFiles++;
|
|
|
|
if (!PfSvIsInPathList(CurrentLayout, PathEntry->Path, PathEntry->Length)) {
|
|
NumMissingFiles++;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Make some sanity checks about the statistics gathered.
|
|
//
|
|
|
|
PFSVC_ASSERT(NumOptimalLayoutFiles == OptimalLayout->NumPaths);
|
|
PFSVC_ASSERT(NumOptimalLayoutFiles >= NumMissingFiles);
|
|
|
|
NumCommonFiles = NumOptimalLayoutFiles - NumMissingFiles;
|
|
PFSVC_ASSERT(CurrentLayout->NumPaths >= NumCommonFiles);
|
|
|
|
NumCurrentLayoutOnlyFiles = CurrentLayout->NumPaths - NumCommonFiles;
|
|
|
|
//
|
|
// If there are not that many new files: no need to update the layout.
|
|
//
|
|
|
|
if (NumMissingFiles <= 20) {
|
|
|
|
*LayoutChanged = FALSE;
|
|
ErrorCode = ERROR_SUCCESS;
|
|
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// We will be updating the current layout.
|
|
//
|
|
|
|
*LayoutChanged = TRUE;
|
|
|
|
//
|
|
// If there are too many files in the current layout that don't need to be
|
|
// there anymore, rebuild the list.
|
|
//
|
|
|
|
if (NumCurrentLayoutOnlyFiles >= CurrentLayout->NumPaths / 4) {
|
|
PfSvCleanupPathList(CurrentLayout);
|
|
PfSvInitializePathList(CurrentLayout, NULL, FALSE);
|
|
}
|
|
|
|
//
|
|
// Add files from the optimal layout to the end of current layout.
|
|
//
|
|
|
|
while (PathEntry = PfSvGetNextPathInOrder(OptimalLayout, PathEntry)) {
|
|
|
|
ErrorCode = PfSvAddToPathList(CurrentLayout, PathEntry->Path, PathEntry->Length);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
ErrorCode = ERROR_SUCCESS;
|
|
|
|
cleanup:
|
|
|
|
DBGPR((PFID,PFTRC,"PFSVC: UpdateLayout(%p,%p)=%d,%x\n",CurrentLayout,OptimalLayout,*LayoutChanged,ErrorCode));
|
|
|
|
return ErrorCode;
|
|
}
|
|
|
|
DWORD
|
|
PfSvDetermineOptimalLayout (
|
|
PPFSVC_IDLE_TASK Task,
|
|
PPFSVC_PATH_LIST OptimalLayout,
|
|
BOOL *BootScenarioProcessed
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine will determine if the optimal disk layout has to be
|
|
updated by looking at the existing scenario files.
|
|
|
|
Arguments:
|
|
|
|
Task - If specified the function will check Task every once in a
|
|
while to see if it should exit with ERROR_RETRY.
|
|
|
|
OptimalLayout - Initialized empty path list that will be built.
|
|
|
|
BootScenarioProcessed - Whether we got the list of boot files from
|
|
the boot scenario.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
PFSVC_SCENARIO_FILE_CURSOR FileCursor;
|
|
FILETIME LayoutFileTime;
|
|
PNTPATH_TRANSLATION_LIST TranslationList;
|
|
PWCHAR DosPathBuffer;
|
|
ULONG DosPathBufferSize;
|
|
DWORD ErrorCode;
|
|
BOOLEAN AcquiredLock;
|
|
WCHAR BootScenarioFileName[PF_MAX_SCENARIO_FILE_NAME];
|
|
WCHAR BootScenarioFilePath[MAX_PATH + 1];
|
|
|
|
//
|
|
// Initialize locals.
|
|
//
|
|
|
|
PfSvInitializeScenarioFileCursor(&FileCursor);
|
|
TranslationList = NULL;
|
|
AcquiredLock = FALSE;
|
|
DosPathBuffer = NULL;
|
|
DosPathBufferSize = 0;
|
|
|
|
DBGPR((PFID,PFTRC,"PFSVC: DetermineOptimalLayout(%p,%p)\n",Task,OptimalLayout));
|
|
|
|
//
|
|
// Initialize output variables.
|
|
//
|
|
|
|
*BootScenarioProcessed = FALSE;
|
|
|
|
//
|
|
// Acquire the prefetch root directory lock and initialize some locals.
|
|
//
|
|
|
|
PFSVC_ACQUIRE_LOCK(PfSvcGlobals.PrefetchRootLock);
|
|
AcquiredLock = TRUE;
|
|
|
|
//
|
|
// Start the file cursor.
|
|
//
|
|
|
|
ErrorCode = PfSvStartScenarioFileCursor(&FileCursor, PfSvcGlobals.PrefetchRoot);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Build the boot scenario file path.
|
|
//
|
|
|
|
swprintf(BootScenarioFileName,
|
|
PF_SCEN_FILE_NAME_FORMAT,
|
|
PF_BOOT_SCENARIO_NAME,
|
|
PF_BOOT_SCENARIO_HASHID,
|
|
PF_PREFETCH_FILE_EXTENSION);
|
|
|
|
swprintf(BootScenarioFilePath,
|
|
L"%ws\\%ws",
|
|
PfSvcGlobals.PrefetchRoot,
|
|
BootScenarioFileName);
|
|
|
|
PFSVC_RELEASE_LOCK(PfSvcGlobals.PrefetchRootLock);
|
|
AcquiredLock = FALSE;
|
|
|
|
//
|
|
// Get translation list so we can convert NT paths in the trace to
|
|
// Dos paths that the defragger understands.
|
|
//
|
|
|
|
ErrorCode = PfSvBuildNtPathTranslationList(&TranslationList);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Should we continue to run?
|
|
//
|
|
|
|
ErrorCode = PfSvContinueRunningTask(Task);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Add boot loader files to optimal layout.
|
|
//
|
|
|
|
ErrorCode = PfSvBuildBootLoaderFilesList(OptimalLayout);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Add files from the boot scenario.
|
|
//
|
|
|
|
ErrorCode = PfSvUpdateLayoutForScenario(OptimalLayout,
|
|
BootScenarioFilePath,
|
|
TranslationList,
|
|
&DosPathBuffer,
|
|
&DosPathBufferSize);
|
|
|
|
if (ErrorCode == ERROR_SUCCESS) {
|
|
*BootScenarioProcessed = TRUE;
|
|
}
|
|
|
|
//
|
|
// Go through all the other scenario files.
|
|
//
|
|
|
|
while (TRUE) {
|
|
|
|
//
|
|
// Should we continue to run?
|
|
//
|
|
|
|
ErrorCode = PfSvContinueRunningTask(Task);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Get file info for the next scenario file.
|
|
//
|
|
|
|
ErrorCode = PfSvGetNextScenarioFileInfo(&FileCursor);
|
|
|
|
if (ErrorCode == ERROR_NO_MORE_FILES) {
|
|
break;
|
|
}
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
PfSvUpdateLayoutForScenario(OptimalLayout,
|
|
FileCursor.FilePath,
|
|
TranslationList,
|
|
&DosPathBuffer,
|
|
&DosPathBufferSize);
|
|
}
|
|
|
|
//
|
|
// We are done.
|
|
//
|
|
|
|
ErrorCode = ERROR_SUCCESS;
|
|
|
|
cleanup:
|
|
|
|
DBGPR((PFID,PFTRC,"PFSVC: DetermineOptimalLayout(%p,%p)=%x\n",Task,OptimalLayout,ErrorCode));
|
|
|
|
if (AcquiredLock) {
|
|
PFSVC_RELEASE_LOCK(PfSvcGlobals.PrefetchRootLock);
|
|
}
|
|
|
|
PfSvCleanupScenarioFileCursor(&FileCursor);
|
|
|
|
if (TranslationList) {
|
|
PfSvFreeNtPathTranslationList(TranslationList);
|
|
}
|
|
|
|
if (DosPathBuffer) {
|
|
PFSVC_ASSERT(DosPathBufferSize);
|
|
PFSVC_FREE(DosPathBuffer);
|
|
}
|
|
|
|
return ErrorCode;
|
|
}
|
|
|
|
DWORD
|
|
PfSvUpdateLayoutForScenario (
|
|
PPFSVC_PATH_LIST OptimalLayout,
|
|
WCHAR *ScenarioFilePath,
|
|
PNTPATH_TRANSLATION_LIST TranslationList,
|
|
PWCHAR *DosPathBuffer,
|
|
PULONG DosPathBufferSize
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine will add the directories and files referenced in a
|
|
scenario in the order they appear to the specified optimal layout
|
|
path list .
|
|
|
|
Arguments:
|
|
|
|
OptimalLayout - Pointer to path list.
|
|
|
|
ScenarioFilePath - Scenario file.
|
|
|
|
TranslationList, DosPathBuffer, DosPathBufferSize - These are used
|
|
to translate NT path names in the scenario file to Dos path names
|
|
that should be in the layout file.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
PPF_SCENARIO_HEADER Scenario;
|
|
PCHAR MetadataInfoBase;
|
|
PPF_METADATA_RECORD MetadataRecordTable;
|
|
PPF_METADATA_RECORD MetadataRecord;
|
|
PPF_COUNTED_STRING DirectoryPath;
|
|
PPF_SECTION_RECORD Sections;
|
|
PPF_SECTION_RECORD SectionRecord;
|
|
PCHAR FilePathInfo;
|
|
PWCHAR FilePath;
|
|
ULONG FilePathLength;
|
|
ULONG SectionIdx;
|
|
ULONG MetadataRecordIdx;
|
|
ULONG DirectoryIdx;
|
|
DWORD ErrorCode;
|
|
DWORD FileSize;
|
|
DWORD FailedCheck;
|
|
|
|
//
|
|
// Initialize locals.
|
|
//
|
|
|
|
Scenario = NULL;
|
|
|
|
//
|
|
// Map the scenario file.
|
|
//
|
|
|
|
ErrorCode = PfSvGetViewOfFile(ScenarioFilePath,
|
|
&Scenario,
|
|
&FileSize);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Verify scenario file.
|
|
//
|
|
|
|
if (!PfSvVerifyScenarioBuffer(Scenario, FileSize, &FailedCheck) ||
|
|
Scenario->ServiceVersion != PFSVC_SERVICE_VERSION) {
|
|
|
|
ErrorCode = ERROR_BAD_FORMAT;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// First add the directories that need to be accessed.
|
|
//
|
|
|
|
MetadataInfoBase = (PCHAR)Scenario + Scenario->MetadataInfoOffset;
|
|
MetadataRecordTable = (PPF_METADATA_RECORD) MetadataInfoBase;
|
|
|
|
for (MetadataRecordIdx = 0;
|
|
MetadataRecordIdx < Scenario->NumMetadataRecords;
|
|
MetadataRecordIdx++) {
|
|
|
|
MetadataRecord = &MetadataRecordTable[MetadataRecordIdx];
|
|
|
|
DirectoryPath = (PPF_COUNTED_STRING)
|
|
(MetadataInfoBase + MetadataRecord->DirectoryPathsOffset);
|
|
|
|
for (DirectoryIdx = 0;
|
|
DirectoryIdx < MetadataRecord->NumDirectories;
|
|
DirectoryIdx++,
|
|
DirectoryPath = (PPF_COUNTED_STRING) (&DirectoryPath->String[DirectoryPath->Length + 1])) {
|
|
|
|
ErrorCode = PfSvTranslateNtPath(TranslationList,
|
|
DirectoryPath->String,
|
|
DirectoryPath->Length,
|
|
DosPathBuffer,
|
|
DosPathBufferSize);
|
|
|
|
//
|
|
// We may not be able to translate all NT paths to Dos paths.
|
|
//
|
|
|
|
if (ErrorCode == ERROR_SUCCESS) {
|
|
|
|
ErrorCode = PfSvAddToPathList(OptimalLayout,
|
|
*DosPathBuffer,
|
|
wcslen(*DosPathBuffer));
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Now add the file paths.
|
|
//
|
|
|
|
Sections = (PPF_SECTION_RECORD) ((PCHAR)Scenario + Scenario->SectionInfoOffset);
|
|
FilePathInfo = (PCHAR)Scenario + Scenario->FileNameInfoOffset;
|
|
|
|
for (SectionIdx = 0; SectionIdx < Scenario->NumSections; SectionIdx++) {
|
|
|
|
FilePath = (PWSTR) (FilePathInfo + Sections[SectionIdx].FileNameOffset);
|
|
FilePathLength = Sections[SectionIdx].FileNameLength;
|
|
|
|
ErrorCode = PfSvTranslateNtPath(TranslationList,
|
|
FilePath,
|
|
FilePathLength,
|
|
DosPathBuffer,
|
|
DosPathBufferSize);
|
|
|
|
//
|
|
// We may not be able to translate all NT paths to Dos paths.
|
|
//
|
|
|
|
if (ErrorCode == ERROR_SUCCESS) {
|
|
|
|
ErrorCode = PfSvAddToPathList(OptimalLayout,
|
|
*DosPathBuffer,
|
|
wcslen(*DosPathBuffer));
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// We are done.
|
|
//
|
|
|
|
ErrorCode = ERROR_SUCCESS;
|
|
|
|
cleanup:
|
|
|
|
if (Scenario) {
|
|
UnmapViewOfFile(Scenario);
|
|
}
|
|
|
|
DBGPR((PFID,PFTRC,"PFSVC: UpdateLayoutForScenario(%p,%ws)=%x\n",OptimalLayout,ScenarioFilePath,ErrorCode));
|
|
|
|
return ErrorCode;
|
|
}
|
|
|
|
DWORD
|
|
PfSvReadLayout(
|
|
IN WCHAR *FilePath,
|
|
OUT PPFSVC_PATH_LIST Layout,
|
|
OUT FILETIME *LastWriteTime
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function adds contents of the optimal layout file to the
|
|
specified path list. Note that failure may be returned after
|
|
adding several files to the list.
|
|
|
|
Arguments:
|
|
|
|
FilePath - NUL terminated path to optimal layout file.
|
|
|
|
Layout - Pointer to initialized path list.
|
|
|
|
LastWriteTime - Last write time of the read file.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
DWORD ErrorCode;
|
|
FILE *LayoutFile;
|
|
WCHAR *LineBuffer;
|
|
ULONG LineBufferMaxChars;
|
|
ULONG LineLength;
|
|
|
|
//
|
|
// Initialize locals.
|
|
//
|
|
|
|
LayoutFile = NULL;
|
|
LineBuffer = NULL;
|
|
LineBufferMaxChars = 0;
|
|
|
|
//
|
|
// Open the layout file.
|
|
//
|
|
|
|
LayoutFile = _wfopen(FilePath, L"rb");
|
|
|
|
if (!LayoutFile) {
|
|
ErrorCode = ERROR_FILE_NOT_FOUND;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Read and verify header.
|
|
//
|
|
|
|
ErrorCode = PfSvReadLine(LayoutFile,
|
|
&LineBuffer,
|
|
&LineBufferMaxChars,
|
|
&LineLength);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS || !LineLength) {
|
|
ErrorCode = ERROR_BAD_FORMAT;
|
|
goto cleanup;
|
|
}
|
|
|
|
PfSvRemoveEndOfLineChars(LineBuffer, &LineLength);
|
|
|
|
if (wcscmp(LineBuffer, L"[OptimalLayoutFile]")) {
|
|
|
|
//
|
|
// Notepad puts a weird first character in the UNICODE text files.
|
|
// Skip the first character and compare again.
|
|
//
|
|
|
|
if ((LineLength < 1) ||
|
|
wcscmp(&LineBuffer[1], L"[OptimalLayoutFile]")) {
|
|
|
|
ErrorCode = ERROR_BAD_FORMAT;
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Read and verify version.
|
|
//
|
|
|
|
ErrorCode = PfSvReadLine(LayoutFile,
|
|
&LineBuffer,
|
|
&LineBufferMaxChars,
|
|
&LineLength);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS || !LineLength) {
|
|
ErrorCode = ERROR_BAD_FORMAT;
|
|
goto cleanup;
|
|
}
|
|
|
|
PfSvRemoveEndOfLineChars(LineBuffer, &LineLength);
|
|
|
|
if (wcscmp(LineBuffer, L"Version=1")) {
|
|
ErrorCode = ERROR_BAD_FORMAT;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Read in file names.
|
|
//
|
|
|
|
do {
|
|
|
|
ErrorCode = PfSvReadLine(LayoutFile,
|
|
&LineBuffer,
|
|
&LineBufferMaxChars,
|
|
&LineLength);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
if (!LineLength) {
|
|
|
|
//
|
|
// We hit end of file.
|
|
//
|
|
|
|
break;
|
|
}
|
|
|
|
PfSvRemoveEndOfLineChars(LineBuffer, &LineLength);
|
|
|
|
//
|
|
// Add it to the list.
|
|
//
|
|
|
|
ErrorCode = PfSvAddToPathList(Layout,
|
|
LineBuffer,
|
|
LineLength);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
} while (TRUE);
|
|
|
|
//
|
|
// Get the last write time on the file.
|
|
//
|
|
|
|
ErrorCode = PfSvGetLastWriteTime(FilePath, LastWriteTime);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// We are done.
|
|
//
|
|
|
|
ErrorCode = ERROR_SUCCESS;
|
|
|
|
cleanup:
|
|
|
|
if (LayoutFile) {
|
|
fclose(LayoutFile);
|
|
}
|
|
|
|
if (LineBuffer) {
|
|
PFSVC_ASSERT(LineBufferMaxChars);
|
|
PFSVC_FREE(LineBuffer);
|
|
}
|
|
|
|
return ErrorCode;
|
|
}
|
|
|
|
DWORD
|
|
PfSvSaveLayout(
|
|
IN WCHAR *FilePath,
|
|
IN PPFSVC_PATH_LIST Layout,
|
|
OUT FILETIME *LastWriteTime
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine saves the specified file layout list in order to the
|
|
specified file in the right format.
|
|
|
|
Arguments:
|
|
|
|
FilePath - Path to output layout file.
|
|
|
|
Layout - Pointer to layout.
|
|
|
|
LastWriteTime - Last write time on the file after we are done
|
|
saving the layout.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
DWORD ErrorCode;
|
|
HANDLE LayoutFile;
|
|
WCHAR *FileHeader;
|
|
ULONG BufferSize;
|
|
ULONG NumBytesWritten;
|
|
PPFSVC_PATH PathEntry;
|
|
WCHAR *NewLine;
|
|
ULONG SizeOfNewLine;
|
|
|
|
//
|
|
// Initialize locals.
|
|
//
|
|
|
|
LayoutFile = INVALID_HANDLE_VALUE;
|
|
NewLine = L"\r\n";
|
|
SizeOfNewLine = wcslen(NewLine) * sizeof(WCHAR);
|
|
|
|
//
|
|
// Open & truncate the layout file. We are also opening with read
|
|
// permissions so we can query the last write time when we are
|
|
// done.
|
|
//
|
|
|
|
LayoutFile = CreateFile(FilePath,
|
|
GENERIC_READ | GENERIC_WRITE,
|
|
0,
|
|
0,
|
|
CREATE_ALWAYS,
|
|
0,
|
|
NULL);
|
|
|
|
if (LayoutFile == INVALID_HANDLE_VALUE) {
|
|
ErrorCode = GetLastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Write out the header.
|
|
//
|
|
|
|
FileHeader = L"[OptimalLayoutFile]\r\nVersion=1\r\n";
|
|
BufferSize = wcslen(FileHeader) * sizeof(WCHAR);
|
|
|
|
if (!WriteFile(LayoutFile,
|
|
FileHeader,
|
|
BufferSize,
|
|
&NumBytesWritten,
|
|
NULL)) {
|
|
ErrorCode = GetLastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
PathEntry = NULL;
|
|
while (PathEntry = PfSvGetNextPathInOrder(Layout, PathEntry)) {
|
|
|
|
//
|
|
// Write the path.
|
|
//
|
|
|
|
BufferSize = PathEntry->Length * sizeof(WCHAR);
|
|
|
|
if (!WriteFile(LayoutFile,
|
|
PathEntry->Path,
|
|
BufferSize,
|
|
&NumBytesWritten,
|
|
NULL)) {
|
|
ErrorCode = GetLastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Write the newline.
|
|
//
|
|
|
|
if (!WriteFile(LayoutFile,
|
|
NewLine,
|
|
SizeOfNewLine,
|
|
&NumBytesWritten,
|
|
NULL)) {
|
|
ErrorCode = GetLastError();
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Make sure everything is written to the file so our
|
|
// LastWriteTime will be accurate.
|
|
//
|
|
|
|
if (!FlushFileBuffers(LayoutFile)) {
|
|
ErrorCode = GetLastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Get the last write time.
|
|
//
|
|
|
|
if (!GetFileTime(LayoutFile, NULL, NULL, LastWriteTime)) {
|
|
ErrorCode = GetLastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// We are done.
|
|
//
|
|
|
|
ErrorCode = ERROR_SUCCESS;
|
|
|
|
cleanup:
|
|
|
|
if (LayoutFile != INVALID_HANDLE_VALUE) {
|
|
CloseHandle(LayoutFile);
|
|
}
|
|
|
|
return ErrorCode;
|
|
}
|
|
|
|
DWORD
|
|
PfSvGetLayoutFilePath(
|
|
PWCHAR *FilePathBuffer,
|
|
PULONG FilePathBufferSize
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function tries to query the layout file path into the
|
|
specified buffer. If the buffer is too small, or NULL, it is
|
|
reallocated. If not NULL, the buffer should have been allocated by
|
|
PFSVC_ALLOC. It is the callers responsibility to free the returned
|
|
buffer using PFSVC_FREE.
|
|
|
|
In order to avoid having somebody cause us to overwrite any file
|
|
in the system always the default layout file path is saved in the
|
|
registry and returned.
|
|
|
|
Arguments:
|
|
|
|
FilePathBuffer - Layout file path will be put into this buffer
|
|
after it is reallocated if it is NULL or not big enough.
|
|
|
|
FilePathBufferSize - Maximum size of *FilePathBuffer in bytes.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
ULONG DefaultPathSize;
|
|
ULONG DefaultPathLength;
|
|
HKEY DefragParametersKey;
|
|
DWORD ErrorCode;
|
|
BOOLEAN AcquiredPrefetchRootLock;
|
|
BOOLEAN OpenedParametersKey;
|
|
|
|
//
|
|
// Initialize locals.
|
|
//
|
|
|
|
AcquiredPrefetchRootLock = FALSE;
|
|
OpenedParametersKey = FALSE;
|
|
|
|
//
|
|
// Verify parameters.
|
|
//
|
|
|
|
if (*FilePathBufferSize) {
|
|
PFSVC_ASSERT(*FilePathBuffer);
|
|
}
|
|
|
|
PFSVC_ACQUIRE_LOCK(PfSvcGlobals.PrefetchRootLock);
|
|
AcquiredPrefetchRootLock = TRUE;
|
|
|
|
DefaultPathLength = wcslen(PfSvcGlobals.PrefetchRoot);
|
|
DefaultPathLength += 1; // for '\\'
|
|
DefaultPathLength += wcslen(PFSVC_OPTIMAL_LAYOUT_FILE_DEFAULT_NAME);
|
|
|
|
DefaultPathSize = (DefaultPathLength + 1) * sizeof(WCHAR);
|
|
|
|
//
|
|
// Check if we have to allocate/reallocate the buffer.
|
|
//
|
|
|
|
if ((*FilePathBufferSize) <= DefaultPathSize) {
|
|
|
|
if (*FilePathBuffer) {
|
|
PFSVC_ASSERT(*FilePathBufferSize);
|
|
PFSVC_FREE(*FilePathBuffer);
|
|
}
|
|
|
|
(*FilePathBufferSize) = 0;
|
|
|
|
(*FilePathBuffer) = PFSVC_ALLOC(DefaultPathSize);
|
|
|
|
if (!(*FilePathBuffer)) {
|
|
ErrorCode = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto cleanup;
|
|
}
|
|
|
|
(*FilePathBufferSize) = DefaultPathSize;
|
|
}
|
|
|
|
//
|
|
// Build the path in the FilePathBuffer
|
|
//
|
|
|
|
wcscpy((*FilePathBuffer), PfSvcGlobals.PrefetchRoot);
|
|
wcscat((*FilePathBuffer), L"\\");
|
|
wcscat((*FilePathBuffer), PFSVC_OPTIMAL_LAYOUT_FILE_DEFAULT_NAME);
|
|
|
|
PFSVC_RELEASE_LOCK(PfSvcGlobals.PrefetchRootLock);
|
|
AcquiredPrefetchRootLock = FALSE;
|
|
|
|
//
|
|
// Save the default path in the registry so it is used by the
|
|
// defragger:
|
|
//
|
|
|
|
//
|
|
// Open the parameters key, creating it if necessary.
|
|
//
|
|
|
|
ErrorCode = RegCreateKey(HKEY_LOCAL_MACHINE,
|
|
PFSVC_OPTIMAL_LAYOUT_REG_KEY_PATH,
|
|
&DefragParametersKey);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
OpenedParametersKey = TRUE;
|
|
|
|
ErrorCode = RegSetValueEx(DefragParametersKey,
|
|
PFSVC_OPTIMAL_LAYOUT_REG_VALUE_NAME,
|
|
0,
|
|
REG_SZ,
|
|
(PVOID) (*FilePathBuffer),
|
|
(*FilePathBufferSize));
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
cleanup:
|
|
|
|
if (AcquiredPrefetchRootLock) {
|
|
PFSVC_RELEASE_LOCK(PfSvcGlobals.PrefetchRootLock);
|
|
}
|
|
|
|
if (OpenedParametersKey) {
|
|
CloseHandle(DefragParametersKey);
|
|
}
|
|
|
|
return ErrorCode;
|
|
}
|
|
|
|
//
|
|
// Routines to defrag the disks once after setup when the system is idle.
|
|
//
|
|
|
|
DWORD
|
|
PfSvLaunchDefragger(
|
|
PPFSVC_IDLE_TASK Task,
|
|
BOOLEAN ForLayoutOptimization,
|
|
PWCHAR TargetDrive
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine will launch the defragger. It will create an event that
|
|
will be passed to the defragger so the defragger can be stopped if
|
|
the service is stopping or the Task (if one is specified) is being
|
|
unregistered, etc.
|
|
|
|
Arguments:
|
|
|
|
Task - If specified the function will check Task every once in a
|
|
while to see if it should exit with ERROR_RETRY.
|
|
|
|
ForLayoutOptimization - Whether we are launching the defragger
|
|
only for layout optimization.
|
|
|
|
TargetDrive - If we are not launching for layout optimization, the
|
|
drive that we want to defrag.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
PROCESS_INFORMATION ProcessInfo;
|
|
STARTUPINFO StartupInfo;
|
|
WCHAR *CommandLine;
|
|
WCHAR *DefragCommand;
|
|
WCHAR *DoLayoutParameter;
|
|
WCHAR *DriveToDefrag;
|
|
HANDLE StopDefraggerEvent;
|
|
HANDLE ProcessHandle;
|
|
HANDLE Events[4];
|
|
ULONG NumEvents;
|
|
ULONG MaxEvents;
|
|
DWORD ErrorCode;
|
|
DWORD ExitCode;
|
|
DWORD WaitResult;
|
|
DWORD ProcessId;
|
|
ULONG SystemDirLength;
|
|
ULONG CommandLineLength;
|
|
ULONG RetryCount;
|
|
BOOL DefraggerExitOnItsOwn;
|
|
WCHAR SystemDrive[3];
|
|
WCHAR ProcessIdString[35];
|
|
WCHAR StopEventString[35];
|
|
WCHAR SystemDir[MAX_PATH + 1];
|
|
|
|
//
|
|
// Initialize locals.
|
|
//
|
|
|
|
StopDefraggerEvent = NULL;
|
|
DefragCommand = L"\\defrag.exe\" ";
|
|
DoLayoutParameter = L"-b ";
|
|
CommandLine = NULL;
|
|
RtlZeroMemory(&ProcessInfo, sizeof(PROCESS_INFORMATION));
|
|
RtlZeroMemory(&StartupInfo, sizeof(STARTUPINFO));
|
|
StartupInfo.cb = sizeof(STARTUPINFO);
|
|
ProcessHandle = NULL;
|
|
MaxEvents = sizeof(Events) / sizeof(HANDLE);
|
|
|
|
DBGPR((PFID,PFTRC,"PFSVC: LaunchDefragger(%p,%d,%ws)\n",Task,(DWORD)ForLayoutOptimization,TargetDrive));
|
|
|
|
//
|
|
// If we are not allowed to run the defragger, don't.
|
|
//
|
|
|
|
if (!PfSvAllowedToRunDefragger(TRUE)) {
|
|
ErrorCode = ERROR_ACCESS_DENIED;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Get current process ID as string.
|
|
//
|
|
|
|
ProcessId = GetCurrentProcessId();
|
|
swprintf(ProcessIdString, L"-p %x ", ProcessId);
|
|
|
|
//
|
|
// Create a stop event and convert handle value to string.
|
|
//
|
|
|
|
StopDefraggerEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
|
|
|
|
if (!StopDefraggerEvent) {
|
|
ErrorCode = GetLastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
swprintf(StopEventString, L"-s %p ", StopDefraggerEvent);
|
|
|
|
//
|
|
// Get path to system32 directory.
|
|
//
|
|
|
|
SystemDirLength = GetSystemDirectory(SystemDir, MAX_PATH);
|
|
|
|
if (!SystemDirLength) {
|
|
ErrorCode = GetLastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
if (SystemDirLength >= MAX_PATH) {
|
|
ErrorCode = ERROR_INSUFFICIENT_BUFFER;
|
|
goto cleanup;
|
|
}
|
|
|
|
SystemDir[MAX_PATH - 1] = 0;
|
|
|
|
//
|
|
// Determine which drive we will be defragmenting.
|
|
//
|
|
|
|
if (ForLayoutOptimization) {
|
|
|
|
//
|
|
// Get system drive from system directory path.
|
|
//
|
|
|
|
SystemDrive[0] = SystemDir[0];
|
|
SystemDrive[1] = SystemDir[1];
|
|
SystemDrive[2] = 0;
|
|
|
|
DriveToDefrag = SystemDrive;
|
|
|
|
} else {
|
|
|
|
DriveToDefrag = TargetDrive;
|
|
}
|
|
|
|
//
|
|
// Build the command line to launch the process. All strings we put
|
|
// together include a trailing space.
|
|
//
|
|
|
|
CommandLineLength = 0;
|
|
CommandLineLength += wcslen(L"\""); // protect against spaces in SystemDir.
|
|
CommandLineLength += wcslen(SystemDir);
|
|
CommandLineLength += wcslen(DefragCommand);
|
|
CommandLineLength += wcslen(ProcessIdString);
|
|
CommandLineLength += wcslen(StopEventString);
|
|
|
|
if (ForLayoutOptimization) {
|
|
CommandLineLength += wcslen(DoLayoutParameter);
|
|
}
|
|
|
|
CommandLineLength += wcslen(DriveToDefrag);
|
|
|
|
CommandLine = PFSVC_ALLOC((CommandLineLength + 1) * sizeof(WCHAR));
|
|
|
|
if (!CommandLine) {
|
|
ErrorCode = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto cleanup;
|
|
}
|
|
|
|
wcscpy(CommandLine, L"\"");
|
|
wcscat(CommandLine, SystemDir);
|
|
wcscat(CommandLine, DefragCommand);
|
|
wcscat(CommandLine, ProcessIdString);
|
|
wcscat(CommandLine, StopEventString);
|
|
|
|
if (ForLayoutOptimization) {
|
|
wcscat(CommandLine, DoLayoutParameter);
|
|
}
|
|
|
|
wcscat(CommandLine, DriveToDefrag);
|
|
|
|
//
|
|
// We may have to launch the defragger multiple times for it to make
|
|
// or determine the space in which to layout files etc.
|
|
//
|
|
|
|
for (RetryCount = 0; RetryCount < 20; RetryCount++) {
|
|
|
|
PFSVC_ASSERT(!ProcessHandle);
|
|
|
|
//
|
|
// Create the process.
|
|
//
|
|
|
|
// FUTURE-2002/03/29-ScottMa -- CreateProcess is safer if you supply
|
|
// the first parameter. Since the full command-line was built up
|
|
// by this function, the first parameter is readily available.
|
|
|
|
if (!CreateProcess (NULL,
|
|
CommandLine,
|
|
NULL,
|
|
NULL,
|
|
FALSE,
|
|
CREATE_NO_WINDOW,
|
|
NULL,
|
|
NULL,
|
|
&StartupInfo,
|
|
&ProcessInfo)) {
|
|
|
|
ErrorCode = GetLastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Close handle to the thread, save the process handle.
|
|
//
|
|
|
|
CloseHandle(ProcessInfo.hThread);
|
|
ProcessHandle = ProcessInfo.hProcess;
|
|
|
|
//
|
|
// Setup the events we will wait on.
|
|
//
|
|
|
|
NumEvents = 0;
|
|
Events[NumEvents] = ProcessHandle;
|
|
NumEvents++;
|
|
Events[NumEvents] = PfSvcGlobals.TerminateServiceEvent;
|
|
NumEvents ++;
|
|
|
|
if (Task) {
|
|
Events[NumEvents] = Task->StartedUnregisteringEvent;
|
|
NumEvents++;
|
|
Events[NumEvents] = Task->StopEvent;
|
|
NumEvents++;
|
|
}
|
|
|
|
PFSVC_ASSERT(NumEvents <= MaxEvents);
|
|
|
|
DefraggerExitOnItsOwn = FALSE;
|
|
|
|
WaitResult = WaitForMultipleObjects(NumEvents,
|
|
Events,
|
|
FALSE,
|
|
INFINITE);
|
|
|
|
switch(WaitResult) {
|
|
|
|
case WAIT_OBJECT_0:
|
|
|
|
//
|
|
// The defragger process exit.
|
|
//
|
|
|
|
DefraggerExitOnItsOwn = TRUE;
|
|
|
|
break;
|
|
|
|
case WAIT_OBJECT_0 + 1:
|
|
|
|
//
|
|
// The service is exiting, Signal the defragger to exit, but don't
|
|
// wait for it.
|
|
//
|
|
|
|
SetEvent(StopDefraggerEvent);
|
|
|
|
ErrorCode = ERROR_SHUTDOWN_IN_PROGRESS;
|
|
goto cleanup;
|
|
|
|
break;
|
|
|
|
case WAIT_OBJECT_0 + 2:
|
|
case WAIT_OBJECT_0 + 3:
|
|
|
|
//
|
|
// We would have specified these wait events only if a Task was
|
|
// specified.
|
|
//
|
|
|
|
PFSVC_ASSERT(Task);
|
|
|
|
//
|
|
// Signal the defragger process to exit and wait for it to exit.
|
|
//
|
|
|
|
SetEvent(StopDefraggerEvent);
|
|
|
|
NumEvents = 0;
|
|
Events[NumEvents] = ProcessHandle;
|
|
NumEvents++;
|
|
Events[NumEvents] = PfSvcGlobals.TerminateServiceEvent;
|
|
NumEvents++;
|
|
|
|
WaitResult = WaitForMultipleObjects(NumEvents,
|
|
Events,
|
|
FALSE,
|
|
INFINITE);
|
|
|
|
if (WaitResult == WAIT_OBJECT_0) {
|
|
|
|
//
|
|
// Defragger exit,
|
|
//
|
|
|
|
break;
|
|
|
|
} else if (WaitResult == WAIT_OBJECT_0 + 1) {
|
|
|
|
//
|
|
// Service exiting, cannot wait for the defragger anymore.
|
|
//
|
|
|
|
ErrorCode = ERROR_SHUTDOWN_IN_PROGRESS;
|
|
goto cleanup;
|
|
|
|
} else {
|
|
|
|
ErrorCode = GetLastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
ErrorCode = GetLastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// If we came here, the defragger exit. Determine its exit code and
|
|
// propagate it. If the defragger exit because we told it to, this should
|
|
// be ENG_USER_CANCELLED.
|
|
//
|
|
|
|
if (!GetExitCodeProcess(ProcessHandle, &ExitCode)) {
|
|
ErrorCode = GetLastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// If the defragger needs us to launch it again do so.
|
|
//
|
|
|
|
if (DefraggerExitOnItsOwn && (ExitCode == 9)) { // ENGERR_RETRY
|
|
|
|
//
|
|
// Reset the event that tells the defragger to stop.
|
|
//
|
|
|
|
ResetEvent(StopDefraggerEvent);
|
|
|
|
//
|
|
// Close to handle to the old defragger process.
|
|
//
|
|
|
|
CloseHandle(ProcessHandle);
|
|
ProcessHandle = NULL;
|
|
|
|
//
|
|
// Setup the error code to return. If we've already retried
|
|
// too many times, this is the error that we'll return when
|
|
// we end the retry loop.
|
|
//
|
|
|
|
ErrorCode = ERROR_REQUEST_ABORTED;
|
|
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// If the defragger is crashing, note it so we don't attempt to run
|
|
// it again. When the defragger crashes, its exit code is an NT status
|
|
// code that will be error, e.g. 0xC0000005 for AV etc.
|
|
//
|
|
|
|
if (NT_ERROR(ExitCode)) {
|
|
PfSvcGlobals.DefraggerErrorCode = ExitCode;
|
|
}
|
|
|
|
//
|
|
// Translate the return value of the defragger to a Win32 error code.
|
|
// These codes are defined in base\fs\utils\dfrg\inc\dfrgcmn.h.
|
|
// I wish they were in a file I could include.
|
|
//
|
|
|
|
switch(ExitCode) {
|
|
|
|
case 0: ErrorCode = ERROR_SUCCESS; break; // ENG_NOERR
|
|
case 1: ErrorCode = ERROR_RETRY; break; // ENG_USER_CANCELLED
|
|
case 2: ErrorCode = ERROR_INVALID_PARAMETER; break; // ENGERR_BAD_PARAM
|
|
|
|
//
|
|
// If the defragger's children processes AV / die, it will return
|
|
// ENGERR_UNKNOWN == 3.
|
|
//
|
|
|
|
case 3:
|
|
|
|
ErrorCode = ERROR_INVALID_FUNCTION;
|
|
PfSvcGlobals.DefraggerErrorCode = STATUS_UNSUCCESSFUL;
|
|
break;
|
|
|
|
case 4: ErrorCode = ERROR_NOT_ENOUGH_MEMORY; break; // ENGERR_NOMEM
|
|
case 7: ErrorCode = ERROR_DISK_FULL; break; // ENGERR_LOW_FREESPACE
|
|
|
|
//
|
|
// There is no good translation for the other exit codes or we just
|
|
// don't understand them.
|
|
//
|
|
|
|
default: ErrorCode = ERROR_INVALID_FUNCTION;
|
|
}
|
|
|
|
//
|
|
// The defragger returned success or an error other than retry.
|
|
//
|
|
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Fall through with error code.
|
|
//
|
|
|
|
cleanup:
|
|
|
|
DBGPR((PFID,PFTRC,"PFSVC: LaunchDefragger(%p)=%x\n",Task,ErrorCode));
|
|
|
|
if (CommandLine) {
|
|
PFSVC_FREE(CommandLine);
|
|
}
|
|
|
|
if (StopDefraggerEvent) {
|
|
CloseHandle(StopDefraggerEvent);
|
|
}
|
|
|
|
if (ProcessHandle) {
|
|
CloseHandle(ProcessHandle);
|
|
}
|
|
|
|
return ErrorCode;
|
|
}
|
|
|
|
|
|
DWORD
|
|
PfSvGetBuildDefragStatusValueName (
|
|
OSVERSIONINFOEXW *OsVersion,
|
|
PWCHAR *ValueName
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine translates OsVersion to a string allocated with
|
|
PFSVC_ALLOC. The returned string should be freed by caller.
|
|
|
|
Arguments:
|
|
|
|
OsVersion - Version info to translate to string.
|
|
|
|
ValueName - Pointer to output string is returned here.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
PWCHAR BuildName;
|
|
PWCHAR BuildNameFormat;
|
|
ULONG BuildNameMaxLength;
|
|
DWORD ErrorCode;
|
|
|
|
//
|
|
// Initialize locals.
|
|
//
|
|
|
|
BuildName = NULL;
|
|
BuildNameFormat = L"%x.%x.%x.%hx.%hx.%hx.%hx_DefragStatus";
|
|
BuildNameMaxLength = 80;
|
|
|
|
//
|
|
// Allocate the string.
|
|
//
|
|
|
|
BuildName = PFSVC_ALLOC((BuildNameMaxLength + 1) * sizeof(WCHAR));
|
|
|
|
if (!BuildName) {
|
|
ErrorCode = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto cleanup;
|
|
}
|
|
|
|
_snwprintf(BuildName,
|
|
BuildNameMaxLength,
|
|
BuildNameFormat,
|
|
OsVersion->dwBuildNumber,
|
|
OsVersion->dwMajorVersion,
|
|
OsVersion->dwMinorVersion,
|
|
(WORD) OsVersion->wSuiteMask,
|
|
(WORD) OsVersion->wProductType,
|
|
(WORD) OsVersion->wServicePackMajor,
|
|
(WORD) OsVersion->wServicePackMinor);
|
|
|
|
//
|
|
// Make sure the string is terminated.
|
|
//
|
|
|
|
BuildName[BuildNameMaxLength] = 0;
|
|
|
|
*ValueName = BuildName;
|
|
ErrorCode = ERROR_SUCCESS;
|
|
|
|
cleanup:
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
if (BuildName) {
|
|
PFSVC_FREE(BuildName);
|
|
}
|
|
}
|
|
|
|
DBGPR((PFID,PFTRC,"PFSVC: GetBuildName(%.80ws)=%x\n",BuildName,ErrorCode));
|
|
|
|
return ErrorCode;
|
|
}
|
|
|
|
DWORD
|
|
PfSvSetBuildDefragStatus(
|
|
OSVERSIONINFOEXW *OsVersion,
|
|
PWCHAR BuildDefragStatus,
|
|
ULONG Size
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine will set the information on which drives have been
|
|
defragmented and such for the specified build (OsVersion).
|
|
|
|
Defrag status is in REG_MULTI_SZ format. Each element is a drive
|
|
path that has been defragged for this build. If all drives were
|
|
defragged, than the first element is PFSVC_DEFRAG_DRIVES_DONE.
|
|
|
|
Arguments:
|
|
|
|
OsVersion - Build & SP we are setting defrag status for.
|
|
|
|
BuildDefragStatus - A string that describes the status, which is a
|
|
comma delimited list of drives defragged.
|
|
|
|
Size - Size in bytes of the data that has to be saved to the registry.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
PWCHAR ValueName;
|
|
DWORD ErrorCode;
|
|
|
|
//
|
|
// Initialize locals.
|
|
//
|
|
|
|
ValueName = NULL;
|
|
|
|
//
|
|
// Build the value name from OS version info.
|
|
//
|
|
|
|
ErrorCode = PfSvGetBuildDefragStatusValueName(OsVersion, &ValueName);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
ErrorCode = RegSetValueEx(PfSvcGlobals.ServiceDataKey,
|
|
ValueName,
|
|
0,
|
|
REG_MULTI_SZ,
|
|
(PVOID) BuildDefragStatus,
|
|
Size);
|
|
|
|
//
|
|
// Fall through with error code.
|
|
//
|
|
|
|
cleanup:
|
|
|
|
DBGPR((PFID,PFTRC,"PFSVC: SetBuildDefragStatus(%ws)=%x\n",BuildDefragStatus,ErrorCode));
|
|
|
|
if (ValueName) {
|
|
PFSVC_FREE(ValueName);
|
|
}
|
|
|
|
return ErrorCode;
|
|
}
|
|
|
|
DWORD
|
|
PfSvGetBuildDefragStatus(
|
|
OSVERSIONINFOEXW *OsVersion,
|
|
PWCHAR *BuildDefragStatus,
|
|
PULONG ReturnSize
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine will get the information on which drives have been
|
|
defragmented and such for the specified build (OsVersion).
|
|
|
|
Defrag status is in REG_MULTI_SZ format. Each element is a drive
|
|
path that has been defragged for this build. If all drives were
|
|
defragged, than the first element is PFSVC_DEFRAG_DRIVES_DONE.
|
|
|
|
Arguments:
|
|
|
|
OsVersion - Build & SP we are querying defrag status for.
|
|
|
|
BuildDefragStatus - Output for defrag status. If the function returns
|
|
success this should be freed with a call to PFSVC_FREE().
|
|
|
|
ReturnSize - Size of the returned value in bytes.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
PWCHAR ValueBuffer;
|
|
PWCHAR ValueName;
|
|
DWORD ErrorCode;
|
|
DWORD RegValueType;
|
|
ULONG ValueBufferSize;
|
|
ULONG Size;
|
|
ULONG NumTries;
|
|
ULONG DefaultValueSize;
|
|
BOOLEAN InvalidValue;
|
|
|
|
//
|
|
// Initialize locals.
|
|
//
|
|
|
|
ValueName = NULL;
|
|
ValueBuffer = NULL;
|
|
ValueBufferSize = 0;
|
|
InvalidValue = FALSE;
|
|
|
|
//
|
|
// Build the value name from OS version info.
|
|
//
|
|
|
|
ErrorCode = PfSvGetBuildDefragStatusValueName(OsVersion, &ValueName);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Try to allocate a right size buffer to read this value into.
|
|
//
|
|
|
|
NumTries = 0;
|
|
|
|
do {
|
|
|
|
Size = ValueBufferSize;
|
|
|
|
ErrorCode = RegQueryValueEx(PfSvcGlobals.ServiceDataKey,
|
|
ValueName,
|
|
NULL,
|
|
&RegValueType,
|
|
(PVOID) ValueBuffer,
|
|
&Size);
|
|
|
|
//
|
|
// API returns SUCCESS with required size in Size if ValueBuffer
|
|
// is NULL. We have to special case that out.
|
|
//
|
|
|
|
if (ValueBuffer && ErrorCode == ERROR_SUCCESS) {
|
|
|
|
//
|
|
// We got it. Check the type.
|
|
//
|
|
|
|
if (RegValueType != REG_MULTI_SZ) {
|
|
|
|
//
|
|
// Return default value.
|
|
//
|
|
|
|
InvalidValue = TRUE;
|
|
|
|
} else {
|
|
|
|
InvalidValue = FALSE;
|
|
|
|
*ReturnSize = Size;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
if (ErrorCode == ERROR_FILE_NOT_FOUND) {
|
|
|
|
//
|
|
// The value does not exist. Return default value.
|
|
//
|
|
|
|
InvalidValue = TRUE;
|
|
|
|
break;
|
|
}
|
|
|
|
if (ErrorCode != ERROR_MORE_DATA &&
|
|
!(ErrorCode == ERROR_SUCCESS && !ValueBuffer)) {
|
|
|
|
//
|
|
// This is a real error.
|
|
//
|
|
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Allocate a bigger buffer and try again.
|
|
//
|
|
|
|
PFSVC_ASSERT(ValueBufferSize < Size);
|
|
|
|
if (ValueBuffer) {
|
|
PFSVC_ASSERT(ValueBufferSize);
|
|
PFSVC_FREE(ValueBuffer);
|
|
ValueBufferSize = 0;
|
|
}
|
|
|
|
ValueBuffer = PFSVC_ALLOC(Size);
|
|
|
|
if (!ValueBuffer) {
|
|
ErrorCode = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto cleanup;
|
|
}
|
|
|
|
ValueBufferSize = Size;
|
|
|
|
NumTries++;
|
|
|
|
} while (NumTries < 10);
|
|
|
|
//
|
|
// If we did not get a valid value from the registry, make up a default
|
|
// one: empty string.
|
|
//
|
|
|
|
if (InvalidValue) {
|
|
|
|
DefaultValueSize = sizeof(WCHAR);
|
|
|
|
if (ValueBufferSize < DefaultValueSize) {
|
|
|
|
if (ValueBuffer) {
|
|
PFSVC_ASSERT(ValueBufferSize);
|
|
PFSVC_FREE(ValueBuffer);
|
|
ValueBufferSize = 0;
|
|
}
|
|
|
|
ValueBuffer = PFSVC_ALLOC(DefaultValueSize);
|
|
|
|
if (!ValueBuffer) {
|
|
ErrorCode = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto cleanup;
|
|
}
|
|
|
|
ValueBufferSize = DefaultValueSize;
|
|
}
|
|
|
|
ValueBuffer[0] = 0;
|
|
|
|
*ReturnSize = DefaultValueSize;
|
|
}
|
|
|
|
//
|
|
// We should get here only if we got a value in value buffer.
|
|
//
|
|
|
|
PFSVC_ASSERT(ValueBuffer && ValueBufferSize);
|
|
|
|
*BuildDefragStatus = ValueBuffer;
|
|
|
|
ErrorCode = ERROR_SUCCESS;
|
|
|
|
cleanup:
|
|
|
|
DBGPR((PFID,PFTRC,"PFSVC: GetBuildDefragStatus(%.80ws)=%x\n",*BuildDefragStatus,ErrorCode));
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
|
|
if (ValueBuffer) {
|
|
PFSVC_ASSERT(ValueBufferSize);
|
|
PFSVC_FREE(ValueBuffer);
|
|
}
|
|
}
|
|
|
|
if (ValueName) {
|
|
PFSVC_FREE(ValueName);
|
|
}
|
|
|
|
return ErrorCode;
|
|
}
|
|
|
|
DWORD
|
|
PfSvDefragDisks(
|
|
PPFSVC_IDLE_TASK Task
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
If we have not defragged all disks after a setup/upgrade, do so.
|
|
|
|
Arguments:
|
|
|
|
Task - If specified the function will check Task every once in a
|
|
while to see if it should exit with ERROR_RETRY.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
PNTPATH_TRANSLATION_LIST VolumeList;
|
|
PNTPATH_TRANSLATION_ENTRY VolumeEntry;
|
|
PWCHAR DefraggedVolumeName;
|
|
PWCHAR BuildDefragStatus;
|
|
PWCHAR NewBuildDefragStatus;
|
|
PWCHAR FoundPosition;
|
|
PLIST_ENTRY NextEntry;
|
|
ULONG NewBuildDefragStatusLength;
|
|
ULONG BuildDefragStatusSize;
|
|
ULONG NewBuildDefragStatusSize;
|
|
NTSTATUS Status;
|
|
DWORD ErrorCode;
|
|
BOOLEAN AlreadyDefragged;
|
|
|
|
//
|
|
// Initialize locals.
|
|
//
|
|
|
|
NewBuildDefragStatus = NULL;
|
|
NewBuildDefragStatusSize = 0;
|
|
BuildDefragStatus = NULL;
|
|
BuildDefragStatusSize = 0;
|
|
VolumeList = NULL;
|
|
|
|
DBGPR((PFID,PFTRC,"PFSVC: DefragDisks(%p)\n",Task));
|
|
|
|
//
|
|
// Determine defrag status for the current build.
|
|
//
|
|
|
|
ErrorCode = PfSvGetBuildDefragStatus(&PfSvcGlobals.OsVersion,
|
|
&BuildDefragStatus,
|
|
&BuildDefragStatusSize);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Check if we are already done for this build.
|
|
//
|
|
|
|
if (!_wcsicmp(BuildDefragStatus, PFSVC_DEFRAG_DRIVES_DONE)) {
|
|
ErrorCode = ERROR_SUCCESS;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Build a list of volumes that are mounted.
|
|
//
|
|
|
|
ErrorCode = PfSvBuildNtPathTranslationList(&VolumeList);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Walk through the volumes defragging the ones we have not yet
|
|
// defragged after setup.
|
|
//
|
|
|
|
for (NextEntry = VolumeList->Flink;
|
|
NextEntry != VolumeList;
|
|
NextEntry = NextEntry->Flink) {
|
|
|
|
VolumeEntry = CONTAINING_RECORD(NextEntry,
|
|
NTPATH_TRANSLATION_ENTRY,
|
|
Link);
|
|
|
|
//
|
|
// Skip volumes that are not fixed disks.
|
|
//
|
|
|
|
if (DRIVE_FIXED != GetDriveType(VolumeEntry->VolumeName)) {
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Have we already defragged this volume?
|
|
//
|
|
|
|
AlreadyDefragged = FALSE;
|
|
|
|
for (DefraggedVolumeName = BuildDefragStatus;
|
|
DefraggedVolumeName[0] != 0;
|
|
DefraggedVolumeName += wcslen(DefraggedVolumeName) + 1) {
|
|
|
|
PFSVC_ASSERT((PCHAR) DefraggedVolumeName < (PCHAR) BuildDefragStatus + BuildDefragStatusSize);
|
|
|
|
if (!_wcsicmp(DefraggedVolumeName, VolumeEntry->DosPrefix)) {
|
|
AlreadyDefragged = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (AlreadyDefragged) {
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Launch the defragger to defrag this volume.
|
|
//
|
|
|
|
ErrorCode = PfSvLaunchDefragger(Task, FALSE, VolumeEntry->DosPrefix);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Note that we have defragged this volume.
|
|
//
|
|
|
|
NewBuildDefragStatusSize = BuildDefragStatusSize;
|
|
NewBuildDefragStatusSize += (VolumeEntry->DosPrefixLength + 1) * sizeof(WCHAR);
|
|
|
|
NewBuildDefragStatus = PFSVC_ALLOC(NewBuildDefragStatusSize);
|
|
|
|
if (!NewBuildDefragStatus) {
|
|
ErrorCode = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Start with new defragged drive path.
|
|
//
|
|
|
|
wcscpy(NewBuildDefragStatus, VolumeEntry->DosPrefix);
|
|
|
|
//
|
|
// Append original status.
|
|
//
|
|
|
|
RtlCopyMemory(NewBuildDefragStatus + VolumeEntry->DosPrefixLength + 1,
|
|
BuildDefragStatus,
|
|
BuildDefragStatusSize);
|
|
|
|
//
|
|
// The last character and the one before that should be NUL.
|
|
//
|
|
|
|
PFSVC_ASSERT(NewBuildDefragStatus[NewBuildDefragStatusSize/sizeof(WCHAR)-1] == 0);
|
|
PFSVC_ASSERT(NewBuildDefragStatus[NewBuildDefragStatusSize/sizeof(WCHAR)-2] == 0);
|
|
|
|
//
|
|
// Save the new status.
|
|
//
|
|
|
|
ErrorCode = PfSvSetBuildDefragStatus(&PfSvcGlobals.OsVersion,
|
|
NewBuildDefragStatus,
|
|
NewBuildDefragStatusSize);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Update the old variable.
|
|
//
|
|
|
|
PFSVC_ASSERT(BuildDefragStatus && BuildDefragStatusSize);
|
|
PFSVC_FREE(BuildDefragStatus);
|
|
|
|
BuildDefragStatus = NewBuildDefragStatus;
|
|
NewBuildDefragStatus = NULL;
|
|
BuildDefragStatusSize = NewBuildDefragStatusSize;
|
|
NewBuildDefragStatusSize = 0;
|
|
|
|
//
|
|
// Continue to check & defrag other volumes.
|
|
//
|
|
}
|
|
|
|
//
|
|
// If we came here, then we have successfully defragged all the drives
|
|
// we had to. Set the status in the registry. Note that defrag status
|
|
// value has to end with an additional NUL because it is REG_MULTI_SZ.
|
|
//
|
|
|
|
NewBuildDefragStatusSize = (wcslen(PFSVC_DEFRAG_DRIVES_DONE) + 1) * sizeof(WCHAR);
|
|
NewBuildDefragStatusSize += sizeof(WCHAR);
|
|
|
|
NewBuildDefragStatus = PFSVC_ALLOC(NewBuildDefragStatusSize);
|
|
|
|
if (!NewBuildDefragStatus) {
|
|
ErrorCode = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto cleanup;
|
|
}
|
|
|
|
wcscpy(NewBuildDefragStatus, PFSVC_DEFRAG_DRIVES_DONE);
|
|
NewBuildDefragStatus[(NewBuildDefragStatusSize / sizeof(WCHAR)) - 1] = 0;
|
|
|
|
ErrorCode = PfSvSetBuildDefragStatus(&PfSvcGlobals.OsVersion,
|
|
NewBuildDefragStatus,
|
|
NewBuildDefragStatusSize);
|
|
|
|
//
|
|
// Fall through with error code.
|
|
//
|
|
|
|
cleanup:
|
|
|
|
DBGPR((PFID,PFTRC,"PFSVC: DefragDisks(%p)=%x\n",Task,ErrorCode));
|
|
|
|
if (BuildDefragStatus) {
|
|
|
|
//
|
|
// We should have NULL'ed NewBuildDefragStatus, otherwise we will
|
|
// try to free the same memory twice.
|
|
//
|
|
|
|
PFSVC_ASSERT(BuildDefragStatus != NewBuildDefragStatus);
|
|
|
|
PFSVC_ASSERT(BuildDefragStatusSize);
|
|
PFSVC_FREE(BuildDefragStatus);
|
|
}
|
|
|
|
if (NewBuildDefragStatus) {
|
|
PFSVC_ASSERT(NewBuildDefragStatusSize);
|
|
PFSVC_FREE(NewBuildDefragStatus);
|
|
}
|
|
|
|
if (VolumeList) {
|
|
PfSvFreeNtPathTranslationList(VolumeList);
|
|
}
|
|
|
|
return ErrorCode;
|
|
}
|
|
|
|
//
|
|
// Routines to cleanup old scenario files in the prefetch directory.
|
|
//
|
|
|
|
DWORD
|
|
PfSvCleanupPrefetchDirectory(
|
|
PPFSVC_IDLE_TASK Task
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
If we have too many scenario files in the prefetch directory, discard the
|
|
ones that are not as useful to make room for new files.
|
|
|
|
Arguments:
|
|
|
|
Task - If specified the function will check Task every once in a
|
|
while to see if it should exit with ERROR_RETRY.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
PPFSVC_SCENARIO_AGE_INFO Scenarios;
|
|
PFSVC_SCENARIO_FILE_CURSOR FileCursor;
|
|
PPF_SCENARIO_HEADER Scenario;
|
|
ULONG NumPrefetchFiles;
|
|
ULONG AllocationSize;
|
|
ULONG NumScenarios;
|
|
ULONG ScenarioIdx;
|
|
ULONG PrefetchFileIdx;
|
|
ULONG FileSize;
|
|
ULONG FailedCheck;
|
|
ULONG MaxRemainingScenarioFiles;
|
|
ULONG NumLaunches;
|
|
ULONG HoursSinceLastLaunch;
|
|
FILETIME CurrentTime;
|
|
ULARGE_INTEGER CurrentTimeLI;
|
|
ULARGE_INTEGER LastLaunchTimeLI;
|
|
DWORD ErrorCode;
|
|
BOOLEAN AcquiredLock;
|
|
|
|
//
|
|
// Initialize locals.
|
|
//
|
|
|
|
AcquiredLock = FALSE;
|
|
NumScenarios = 0;
|
|
Scenarios = NULL;
|
|
Scenario = NULL;
|
|
GetSystemTimeAsFileTime(&CurrentTime);
|
|
PfSvInitializeScenarioFileCursor(&FileCursor);
|
|
CurrentTimeLI.LowPart = CurrentTime.dwLowDateTime;
|
|
CurrentTimeLI.HighPart = CurrentTime.dwHighDateTime;
|
|
|
|
DBGPR((PFID,PFTRC,"PFSVC: CleanupPrefetchDirectory(%p)\n",Task));
|
|
|
|
//
|
|
// Once we are done cleaning up, we should not have more than this many
|
|
// prefetch files remaining.
|
|
//
|
|
|
|
MaxRemainingScenarioFiles = PFSVC_MAX_PREFETCH_FILES / 4;
|
|
|
|
PFSVC_ACQUIRE_LOCK(PfSvcGlobals.PrefetchRootLock);
|
|
AcquiredLock = TRUE;
|
|
|
|
//
|
|
// Start the file cursor.
|
|
//
|
|
|
|
ErrorCode = PfSvStartScenarioFileCursor(&FileCursor, PfSvcGlobals.PrefetchRoot);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Count the number of files in the directory.
|
|
//
|
|
|
|
ErrorCode = PfSvCountFilesInDirectory(PfSvcGlobals.PrefetchRoot,
|
|
L"*." PF_PREFETCH_FILE_EXTENSION,
|
|
&NumPrefetchFiles);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
PFSVC_RELEASE_LOCK(PfSvcGlobals.PrefetchRootLock);
|
|
AcquiredLock = FALSE;
|
|
|
|
//
|
|
// Allocate an array that we will fill in with information from
|
|
// scenario files to determine which ones need to be discarded.
|
|
//
|
|
|
|
AllocationSize = NumPrefetchFiles * sizeof(PFSVC_SCENARIO_AGE_INFO);
|
|
|
|
Scenarios = PFSVC_ALLOC(AllocationSize);
|
|
|
|
if (!Scenarios) {
|
|
ErrorCode = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Initialize the scenarios array so we know what to clean up.
|
|
//
|
|
|
|
RtlZeroMemory(Scenarios, AllocationSize);
|
|
NumScenarios = 0;
|
|
|
|
//
|
|
// Enumerate the scenario files:
|
|
//
|
|
|
|
ScenarioIdx = 0;
|
|
PrefetchFileIdx = 0;
|
|
|
|
do {
|
|
|
|
//
|
|
// Should we continue to run?
|
|
//
|
|
|
|
ErrorCode = PfSvContinueRunningTask(Task);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Get the file info for the next scenario file.
|
|
//
|
|
|
|
ErrorCode = PfSvGetNextScenarioFileInfo(&FileCursor);
|
|
|
|
if (ErrorCode == ERROR_NO_MORE_FILES) {
|
|
break;
|
|
}
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Is the file name longer than what a valid prefetch file can be max?
|
|
//
|
|
|
|
if (FileCursor.FileNameLength > PF_MAX_SCENARIO_FILE_NAME) {
|
|
|
|
//
|
|
// Bogus file. Remove it.
|
|
//
|
|
|
|
DeleteFile(FileCursor.FilePath);
|
|
goto NextPrefetchFile;
|
|
}
|
|
|
|
//
|
|
// Map the file.
|
|
//
|
|
|
|
ErrorCode = PfSvGetViewOfFile(FileCursor.FilePath,
|
|
&Scenario,
|
|
&FileSize);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto NextPrefetchFile;
|
|
}
|
|
|
|
//
|
|
// Verify the scenario file.
|
|
//
|
|
|
|
if (!PfSvVerifyScenarioBuffer(Scenario, FileSize, &FailedCheck) ||
|
|
Scenario->ServiceVersion != PFSVC_SERVICE_VERSION) {
|
|
DeleteFile(FileCursor.FilePath);
|
|
goto NextPrefetchFile;
|
|
}
|
|
|
|
//
|
|
// Skip boot scenario, we won't discard it.
|
|
//
|
|
|
|
if (Scenario->ScenarioType == PfSystemBootScenarioType) {
|
|
goto NextPrefetchFile;
|
|
}
|
|
|
|
//
|
|
// Determine the last time scenario was updated. I assume this
|
|
// corresponds to the last time scenario was launched...
|
|
//
|
|
|
|
LastLaunchTimeLI.LowPart = FileCursor.FileData.ftLastWriteTime.dwLowDateTime;
|
|
LastLaunchTimeLI.HighPart = FileCursor.FileData.ftLastWriteTime.dwHighDateTime;
|
|
|
|
HoursSinceLastLaunch = (ULONG) ((CurrentTimeLI.QuadPart - LastLaunchTimeLI.QuadPart) /
|
|
PFSVC_NUM_100NS_IN_AN_HOUR);
|
|
|
|
//
|
|
// Calculate weight: bigger weight means scenario file won't get
|
|
// discarded. We calculate the weight by dividing the total number
|
|
// times a scenario was launched by how long has it been since the last
|
|
// launch of the scenario.
|
|
//
|
|
|
|
NumLaunches = Scenario->NumLaunches;
|
|
|
|
//
|
|
// For the calculations below limit how large NumLaunches can be, so
|
|
// values does not overflow.
|
|
//
|
|
|
|
if (NumLaunches > 1 * 1024 * 1024) {
|
|
NumLaunches = 1 * 1024 * 1024;
|
|
}
|
|
|
|
//
|
|
// Since we are going divide by number of hours (e.g. 7*24 for a program
|
|
// launched a week ago) multiplying the number of launches with a number
|
|
// allows us to give a weight other than 0 to scenarios launched long ago.
|
|
//
|
|
|
|
Scenarios[ScenarioIdx].Weight = NumLaunches * 256;
|
|
|
|
if (HoursSinceLastLaunch) {
|
|
|
|
Scenarios[ScenarioIdx].Weight /= HoursSinceLastLaunch;
|
|
}
|
|
|
|
//
|
|
// Copy over the file path.
|
|
//
|
|
|
|
AllocationSize = (FileCursor.FilePathLength + 1) * sizeof(WCHAR);
|
|
|
|
Scenarios[ScenarioIdx].FilePath = PFSVC_ALLOC(AllocationSize);
|
|
|
|
if (!Scenarios[ScenarioIdx].FilePath) {
|
|
ErrorCode = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto cleanup;
|
|
}
|
|
|
|
wcscpy(Scenarios[ScenarioIdx].FilePath, FileCursor.FilePath);
|
|
|
|
ScenarioIdx++;
|
|
|
|
NextPrefetchFile:
|
|
|
|
PrefetchFileIdx++;
|
|
|
|
if (Scenario) {
|
|
UnmapViewOfFile(Scenario);
|
|
Scenario = NULL;
|
|
}
|
|
|
|
} while (PrefetchFileIdx < NumPrefetchFiles);
|
|
|
|
//
|
|
// If we do not have too many scenario files, we don't have to do anything.
|
|
//
|
|
|
|
NumScenarios = ScenarioIdx;
|
|
|
|
if (NumScenarios < MaxRemainingScenarioFiles) {
|
|
ErrorCode = ERROR_SUCCESS;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Sort the age information.
|
|
//
|
|
|
|
qsort(Scenarios,
|
|
NumScenarios,
|
|
sizeof(PFSVC_SCENARIO_AGE_INFO),
|
|
PfSvCompareScenarioAgeInfo);
|
|
|
|
//
|
|
// Delete the files with the smallest weight until we reach our goal.
|
|
//
|
|
|
|
for (ScenarioIdx = 0;
|
|
(ScenarioIdx < NumScenarios) &&
|
|
((NumScenarios - ScenarioIdx) > MaxRemainingScenarioFiles);
|
|
ScenarioIdx++) {
|
|
|
|
//
|
|
// Should we continue to run?
|
|
//
|
|
|
|
ErrorCode = PfSvContinueRunningTask(Task);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
DeleteFile(Scenarios[ScenarioIdx].FilePath);
|
|
}
|
|
|
|
//
|
|
// Count the files in the directory now and update the global.
|
|
//
|
|
|
|
ErrorCode = PfSvCountFilesInDirectory(PfSvcGlobals.PrefetchRoot,
|
|
L"*." PF_PREFETCH_FILE_EXTENSION,
|
|
&NumPrefetchFiles);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Note that global NumPrefetchFiles is not protected, so the new value
|
|
// we are setting it to may be overwritten with an older value. It should
|
|
// not be a big problem though, maybe resulting in this task being requeued.
|
|
//
|
|
|
|
PfSvcGlobals.NumPrefetchFiles = NumPrefetchFiles;
|
|
|
|
ErrorCode = ERROR_SUCCESS;
|
|
|
|
cleanup:
|
|
|
|
DBGPR((PFID,PFTRC,"PFSVC: CleanupPrefetchDirectory(%p)=%x\n",Task,ErrorCode));
|
|
|
|
if (AcquiredLock) {
|
|
PFSVC_RELEASE_LOCK(PfSvcGlobals.PrefetchRootLock);
|
|
}
|
|
|
|
PfSvCleanupScenarioFileCursor(&FileCursor);
|
|
|
|
if (Scenarios) {
|
|
|
|
for (ScenarioIdx = 0; ScenarioIdx < NumScenarios; ScenarioIdx++) {
|
|
if (Scenarios[ScenarioIdx].FilePath) {
|
|
PFSVC_FREE(Scenarios[ScenarioIdx].FilePath);
|
|
}
|
|
}
|
|
|
|
PFSVC_FREE(Scenarios);
|
|
}
|
|
|
|
if (Scenario) {
|
|
UnmapViewOfFile(Scenario);
|
|
}
|
|
|
|
return ErrorCode;
|
|
}
|
|
|
|
int
|
|
__cdecl
|
|
PfSvCompareScenarioAgeInfo(
|
|
const void *Param1,
|
|
const void *Param2
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Qsort comparison function for PFSVC_SCENARIO_AGE_INFO structure.
|
|
|
|
Arguments:
|
|
|
|
Param1, Param2 - pointer to PFSVC_SCENARIO_AGE_INFO structures
|
|
|
|
Return Value:
|
|
|
|
Qsort comparison function return value.
|
|
|
|
--*/
|
|
|
|
|
|
{
|
|
PFSVC_SCENARIO_AGE_INFO *Elem1;
|
|
PFSVC_SCENARIO_AGE_INFO *Elem2;
|
|
|
|
Elem1 = (PVOID) Param1;
|
|
Elem2 = (PVOID) Param2;
|
|
|
|
//
|
|
// Compare precalculated weights.
|
|
//
|
|
|
|
if (Elem1->Weight > Elem2->Weight) {
|
|
|
|
return 1;
|
|
|
|
} else if (Elem1->Weight < Elem2->Weight) {
|
|
|
|
return -1;
|
|
|
|
} else {
|
|
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Routines to enumerate scenario files.
|
|
//
|
|
|
|
VOID
|
|
PfSvInitializeScenarioFileCursor (
|
|
PPFSVC_SCENARIO_FILE_CURSOR FileCursor
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Initializes the cursor structure so it can be safely cleaned up.
|
|
|
|
Arguments:
|
|
|
|
FileCursor - Pointer to structure.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
FileCursor->FilePath = NULL;
|
|
FileCursor->FileNameLength = 0;
|
|
FileCursor->FilePathLength = 0;
|
|
FileCursor->CurrentFileIdx = 0;
|
|
|
|
FileCursor->PrefetchRoot = NULL;
|
|
FileCursor->FindFileHandle = INVALID_HANDLE_VALUE;
|
|
|
|
return;
|
|
}
|
|
|
|
VOID
|
|
PfSvCleanupScenarioFileCursor(
|
|
PPFSVC_SCENARIO_FILE_CURSOR FileCursor
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Cleans up an initialized and possibly started cursor structure.
|
|
|
|
Arguments:
|
|
|
|
FileCursor - Pointer to structure.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
if (FileCursor->FilePath) {
|
|
PFSVC_FREE(FileCursor->FilePath);
|
|
}
|
|
|
|
if (FileCursor->PrefetchRoot) {
|
|
PFSVC_FREE(FileCursor->PrefetchRoot);
|
|
}
|
|
|
|
if (FileCursor->FindFileHandle != INVALID_HANDLE_VALUE) {
|
|
FindClose(FileCursor->FindFileHandle);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
DWORD
|
|
PfSvStartScenarioFileCursor(
|
|
PPFSVC_SCENARIO_FILE_CURSOR FileCursor,
|
|
WCHAR *PrefetchRoot
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
After making this call on an initialized FileCursor, you can start
|
|
enumerating the scenario files in that directory by calling the get
|
|
next file function.
|
|
|
|
You have to call the get next file function after starting the cursor
|
|
to get the information on the first file.
|
|
|
|
If this function fails, you should call cleanup on the FileCursor
|
|
structure and reinitialize it before trying to start the cursor again.
|
|
|
|
Arguments:
|
|
|
|
FileCursor - Pointer to initialized cursor structure.
|
|
|
|
PrefetchRoot - Directory path in which we'll look for prefetch
|
|
files.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
WCHAR *PrefetchFileSearchPattern;
|
|
WCHAR *PrefetchFileSearchPath;
|
|
ULONG PrefetchRootLength;
|
|
ULONG PrefetchFileSearchPathLength;
|
|
ULONG FileNameMaxLength;
|
|
ULONG FilePathMaxLength;
|
|
DWORD ErrorCode;
|
|
|
|
//
|
|
// Initialize locals.
|
|
//
|
|
|
|
PrefetchRootLength = wcslen(PrefetchRoot);
|
|
PrefetchFileSearchPattern = L"\\*." PF_PREFETCH_FILE_EXTENSION;
|
|
PrefetchFileSearchPath = NULL;
|
|
|
|
//
|
|
// The file cursor should have been initialized.
|
|
//
|
|
|
|
PFSVC_ASSERT(!FileCursor->CurrentFileIdx);
|
|
PFSVC_ASSERT(!FileCursor->PrefetchRoot);
|
|
PFSVC_ASSERT(FileCursor->FindFileHandle == INVALID_HANDLE_VALUE);
|
|
|
|
//
|
|
// Copy the prefetch root directory path.
|
|
//
|
|
|
|
FileCursor->PrefetchRoot = PFSVC_ALLOC((PrefetchRootLength + 1) * sizeof(WCHAR));
|
|
|
|
if (!FileCursor->PrefetchRoot) {
|
|
ErrorCode = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto cleanup;
|
|
}
|
|
|
|
wcscpy(FileCursor->PrefetchRoot, PrefetchRoot);
|
|
FileCursor->PrefetchRootLength = PrefetchRootLength;
|
|
|
|
//
|
|
// Build the path we will pass in to enumerate the prefetch files.
|
|
//
|
|
|
|
PrefetchFileSearchPathLength = PrefetchRootLength;
|
|
PrefetchFileSearchPathLength += wcslen(PrefetchFileSearchPattern);
|
|
|
|
PrefetchFileSearchPath = PFSVC_ALLOC((PrefetchFileSearchPathLength + 1) * sizeof(WCHAR));
|
|
|
|
if (!PrefetchFileSearchPath) {
|
|
ErrorCode = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto cleanup;
|
|
}
|
|
|
|
wcscpy(PrefetchFileSearchPath, PrefetchRoot);
|
|
wcscat(PrefetchFileSearchPath, PrefetchFileSearchPattern);
|
|
|
|
//
|
|
// Allocate the string we will use to build the full path of the
|
|
// prefetch files. We can use it for prefetch files with names of
|
|
// max MAX_PATH. This works because that is the max file name that
|
|
// can fit into WIN32_FIND_DATA structure.
|
|
//
|
|
|
|
FileNameMaxLength = MAX_PATH;
|
|
|
|
FilePathMaxLength = PrefetchRootLength;
|
|
FilePathMaxLength += wcslen(L"\\");
|
|
FilePathMaxLength += FileNameMaxLength;
|
|
|
|
FileCursor->FilePath = PFSVC_ALLOC((FilePathMaxLength + 1) * sizeof(WCHAR));
|
|
|
|
if (!FileCursor->FilePath) {
|
|
ErrorCode = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Initialize the first part of the file path and note where we will
|
|
// start copying file names from.
|
|
//
|
|
|
|
wcscpy(FileCursor->FilePath, PrefetchRoot);
|
|
wcscat(FileCursor->FilePath, L"\\");
|
|
FileCursor->FileNameStart = PrefetchRootLength + 1;
|
|
|
|
//
|
|
// Start enumerating the files. Note that this puts the data for the
|
|
// first file into FileData member.
|
|
//
|
|
|
|
FileCursor->FindFileHandle = FindFirstFile(PrefetchFileSearchPath,
|
|
&FileCursor->FileData);
|
|
|
|
if (FileCursor->FindFileHandle == INVALID_HANDLE_VALUE) {
|
|
ErrorCode = GetLastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
ErrorCode = ERROR_SUCCESS;
|
|
|
|
cleanup:
|
|
|
|
DBGPR((PFID,PFTRC,"PFSVC: StartFileCursor(%p,%ws)=%x\n",FileCursor,PrefetchRoot,ErrorCode));
|
|
|
|
if (PrefetchFileSearchPath) {
|
|
PFSVC_FREE(PrefetchFileSearchPath);
|
|
}
|
|
|
|
return ErrorCode;
|
|
}
|
|
|
|
DWORD
|
|
PfSvGetNextScenarioFileInfo(
|
|
PPFSVC_SCENARIO_FILE_CURSOR FileCursor
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Fills in public fields of the FileCursor with information on the next
|
|
scenario file.
|
|
|
|
You have to call the get next file function after starting the cursor
|
|
to get the information on the first file.
|
|
|
|
Files with *names* longer than MAX_PATH will be skipped because it
|
|
is not feasible to handle these with Win32 API.
|
|
|
|
Arguments:
|
|
|
|
FileCursor - Pointer to started cursor structure.
|
|
|
|
Return Value:
|
|
|
|
ERROR_NO_MORE_FILES - No more files to enumerate.
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
DWORD ErrorCode;
|
|
|
|
//
|
|
// File cursor should have been started.
|
|
//
|
|
|
|
PFSVC_ASSERT(FileCursor->PrefetchRoot);
|
|
PFSVC_ASSERT(FileCursor->FindFileHandle != INVALID_HANDLE_VALUE);
|
|
|
|
//
|
|
// If this it the first file, the FileData for it was already set when
|
|
// we started the cursor. Otherwise call FindNextFile.
|
|
//
|
|
|
|
if (FileCursor->CurrentFileIdx != 0) {
|
|
if (!FindNextFile(FileCursor->FindFileHandle, &FileCursor->FileData)) {
|
|
ErrorCode = GetLastError();
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
FileCursor->FileNameLength = wcslen(FileCursor->FileData.cFileName);
|
|
|
|
//
|
|
// We allocated a file path to hold MAX_PATH file name in addition to the
|
|
// directory path. FileData.cFileName is MAX_PATH sized.
|
|
//
|
|
|
|
PFSVC_ASSERT(FileCursor->FileNameLength < MAX_PATH);
|
|
|
|
if (FileCursor->FileNameLength >= MAX_PATH) {
|
|
ErrorCode = ERROR_BAD_FORMAT;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Copy the file name.
|
|
//
|
|
|
|
wcscpy(FileCursor->FilePath + FileCursor->FileNameStart,
|
|
FileCursor->FileData.cFileName);
|
|
|
|
FileCursor->FilePathLength = FileCursor->FileNameStart + FileCursor->FileNameLength;
|
|
|
|
FileCursor->CurrentFileIdx++;
|
|
|
|
ErrorCode = ERROR_SUCCESS;
|
|
|
|
cleanup:
|
|
|
|
DBGPR((PFID,PFTRC,"PFSVC: GetNextScenarioFile(%p)=%ws,%x\n",FileCursor,FileCursor->FileData.cFileName,ErrorCode));
|
|
|
|
return ErrorCode;
|
|
}
|
|
|
|
//
|
|
// File I/O utility routines.
|
|
//
|
|
|
|
DWORD
|
|
PfSvGetViewOfFile(
|
|
IN WCHAR *FilePath,
|
|
OUT PVOID *BasePointer,
|
|
OUT PULONG FileSize
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Map the all of the specified file to memory.
|
|
|
|
Arguments:
|
|
|
|
FilePath - NUL terminated path to file to map.
|
|
|
|
BasePointer - Start address of mapping will be returned here.
|
|
|
|
FileSize - Size of the mapping/file will be returned here.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
HANDLE InputHandle;
|
|
HANDLE InputMappingHandle;
|
|
DWORD ErrorCode;
|
|
DWORD SizeL;
|
|
DWORD SizeH;
|
|
BOOLEAN OpenedFile;
|
|
BOOLEAN CreatedFileMapping;
|
|
|
|
//
|
|
// Initialize locals.
|
|
//
|
|
|
|
OpenedFile = FALSE;
|
|
CreatedFileMapping = FALSE;
|
|
|
|
DBGPR((PFID,PFTRC,"PFSVC: GetViewOfFile(%ws)\n", FilePath));
|
|
|
|
//
|
|
// Note that we are opening the file exclusively. This guarantees
|
|
// that for trace files as long as the kernel is not done writing
|
|
// it we can't open the file, which guarantees we won't have an
|
|
// incomplete file to worry about.
|
|
//
|
|
|
|
InputHandle = CreateFile(FilePath,
|
|
GENERIC_READ,
|
|
0,
|
|
NULL,
|
|
OPEN_EXISTING,
|
|
0,
|
|
NULL);
|
|
|
|
if (INVALID_HANDLE_VALUE == InputHandle)
|
|
{
|
|
ErrorCode = GetLastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
OpenedFile = TRUE;
|
|
|
|
SizeL = GetFileSize(InputHandle, &SizeH);
|
|
|
|
if (SizeL == -1 && (GetLastError() != NO_ERROR )) {
|
|
ErrorCode = GetLastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
if (SizeH) {
|
|
ErrorCode = ERROR_BAD_LENGTH;
|
|
goto cleanup;
|
|
}
|
|
|
|
if (FileSize) {
|
|
*FileSize = SizeL;
|
|
}
|
|
|
|
InputMappingHandle = CreateFileMapping(InputHandle,
|
|
0,
|
|
PAGE_READONLY,
|
|
0,
|
|
0,
|
|
NULL);
|
|
|
|
if (NULL == InputMappingHandle)
|
|
{
|
|
ErrorCode = GetLastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
CreatedFileMapping = TRUE;
|
|
|
|
*BasePointer = MapViewOfFile(InputMappingHandle,
|
|
FILE_MAP_READ,
|
|
0,
|
|
0,
|
|
0);
|
|
|
|
if (NULL == *BasePointer) {
|
|
ErrorCode = GetLastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
ErrorCode = ERROR_SUCCESS;
|
|
|
|
cleanup:
|
|
|
|
if (OpenedFile) {
|
|
CloseHandle(InputHandle);
|
|
}
|
|
|
|
if (CreatedFileMapping) {
|
|
CloseHandle(InputMappingHandle);
|
|
}
|
|
|
|
DBGPR((PFID,PFTRC,"PFSVC: GetViewOfFile(%ws)=%x\n", FilePath, ErrorCode));
|
|
|
|
return ErrorCode;
|
|
}
|
|
|
|
DWORD
|
|
PfSvWriteBuffer(
|
|
PWCHAR FilePath,
|
|
PVOID Buffer,
|
|
ULONG Length
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine creats/overwrites the file at the specified path and
|
|
writes the contents of the buffer to it.
|
|
|
|
Arguments:
|
|
|
|
FilePath - Full path to the file.
|
|
|
|
Buffer - Buffer to write out.
|
|
|
|
Length - Number of bytes to write out from the buffer.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
DWORD BytesWritten;
|
|
HANDLE OutputHandle;
|
|
DWORD ErrorCode;
|
|
BOOL Result;
|
|
|
|
//
|
|
// Initialize locals.
|
|
//
|
|
|
|
OutputHandle = INVALID_HANDLE_VALUE;
|
|
|
|
DBGPR((PFID,PFSTRC,"PFSVC: WriteBuffer(%p,%ws)\n", Buffer, FilePath));
|
|
|
|
//
|
|
// Open file overwriting any existing one. Don't share it so
|
|
// nobody tries to read a half-written file.
|
|
//
|
|
|
|
OutputHandle = CreateFile(FilePath,
|
|
GENERIC_READ | GENERIC_WRITE,
|
|
0,
|
|
NULL,
|
|
CREATE_ALWAYS,
|
|
0,
|
|
NULL);
|
|
|
|
if (INVALID_HANDLE_VALUE == OutputHandle)
|
|
{
|
|
ErrorCode = GetLastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Write out the scenario.
|
|
//
|
|
|
|
Result = WriteFile(OutputHandle,
|
|
Buffer,
|
|
Length,
|
|
&BytesWritten,
|
|
NULL);
|
|
|
|
if (!Result || (BytesWritten != Length)) {
|
|
ErrorCode = GetLastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
ErrorCode = ERROR_SUCCESS;
|
|
|
|
cleanup:
|
|
|
|
if (OutputHandle != INVALID_HANDLE_VALUE) {
|
|
CloseHandle(OutputHandle);
|
|
}
|
|
|
|
DBGPR((PFID,PFSTRC,"PFSVC: WriteBuffer(%p,%ws)=%x\n", Buffer, FilePath, ErrorCode));
|
|
|
|
return ErrorCode;
|
|
}
|
|
|
|
DWORD
|
|
PfSvGetLastWriteTime (
|
|
WCHAR *FilePath,
|
|
PFILETIME LastWriteTime
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function attempts to get the last write time for the
|
|
specified file.
|
|
|
|
Arguments:
|
|
|
|
FilePath - Pointer to NUL terminated path.
|
|
|
|
LastWriteTime - Pointer to return buffer.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
HANDLE FileHandle;
|
|
DWORD ErrorCode;
|
|
|
|
//
|
|
// Initialize locals.
|
|
//
|
|
|
|
FileHandle = INVALID_HANDLE_VALUE;
|
|
|
|
//
|
|
// Open the file.
|
|
//
|
|
|
|
FileHandle = CreateFile(FilePath,
|
|
GENERIC_READ,
|
|
FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
|
|
NULL,
|
|
OPEN_EXISTING,
|
|
FILE_FLAG_BACKUP_SEMANTICS,
|
|
NULL);
|
|
|
|
if (FileHandle == INVALID_HANDLE_VALUE) {
|
|
ErrorCode = GetLastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Query last write time.
|
|
//
|
|
|
|
if (!GetFileTime(FileHandle, NULL, NULL, LastWriteTime)) {
|
|
ErrorCode = GetLastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
ErrorCode = ERROR_SUCCESS;
|
|
|
|
cleanup:
|
|
|
|
if (FileHandle != INVALID_HANDLE_VALUE) {
|
|
CloseHandle(FileHandle);
|
|
}
|
|
|
|
return ErrorCode;
|
|
}
|
|
|
|
DWORD
|
|
PfSvReadLine (
|
|
FILE *File,
|
|
WCHAR **LineBuffer,
|
|
ULONG *LineBufferMaxChars,
|
|
ULONG *LineLength
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function reads a line from the specified file into
|
|
LineBuffer. If LineBuffer is NULL or not big enough, it is
|
|
allocated or reallocated using PFSVC_ALLOC/FREE macros. It is the
|
|
caller's reponsibility to free the returned buffer.
|
|
|
|
Carriage return/Line feed characters are included in the returned
|
|
LineBuffer & LineLength. Thus, a LineLength of 0 means end of file
|
|
is hit. Returned LineBuffer is NUL terminated.
|
|
|
|
Arguments:
|
|
|
|
File - File to read from.
|
|
|
|
LineBuffer - Pointer to Pointer to buffer to read the line into.
|
|
|
|
LineBufferMaxChars - Pointer to size of LineBuffer in characters,
|
|
including room for NUL etc.
|
|
|
|
LineLength - Pointer to length of the read line in characters
|
|
including the carriage return/linefeed, excluding NUL.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
DWORD ErrorCode;
|
|
WCHAR *NewBuffer;
|
|
ULONG NewBufferMaxChars;
|
|
ULONG RequiredLength;
|
|
WCHAR *CurrentReadPosition;
|
|
ULONG MaxCharsToRead;
|
|
|
|
//
|
|
// Verify parameters.
|
|
//
|
|
|
|
PFSVC_ASSERT(LineBuffer && LineBufferMaxChars && LineLength);
|
|
|
|
if (*LineBufferMaxChars) {
|
|
PFSVC_ASSERT(*LineBuffer);
|
|
}
|
|
|
|
//
|
|
// If a zero length but non NULL buffer was passed in, free it so
|
|
// we can allocate a larger initial one.
|
|
//
|
|
|
|
if (((*LineBufferMaxChars) == 0) && (*LineBuffer)) {
|
|
PFSVC_FREE(*LineBuffer);
|
|
(*LineBuffer) = NULL;
|
|
}
|
|
|
|
//
|
|
// If no buffer was passed in, allocate one. We do not want to
|
|
// enter the read line loop with a zero length or NULL buffer.
|
|
//
|
|
|
|
if (!(*LineBuffer)) {
|
|
|
|
PFSVC_ASSERT((*LineBufferMaxChars) == 0);
|
|
|
|
(*LineBuffer) = PFSVC_ALLOC(MAX_PATH * sizeof(WCHAR));
|
|
|
|
if (!(*LineBuffer)) {
|
|
ErrorCode = ERROR_INSUFFICIENT_BUFFER;
|
|
goto cleanup;
|
|
}
|
|
|
|
(*LineBufferMaxChars) = MAX_PATH;
|
|
}
|
|
|
|
//
|
|
// Initialize output length and NUL terminate the output line.
|
|
//
|
|
|
|
(*LineLength) = 0;
|
|
(*(*LineBuffer)) = 0;
|
|
|
|
do {
|
|
|
|
//
|
|
// Try to read a line from the file.
|
|
//
|
|
|
|
CurrentReadPosition = (*LineBuffer) + (*LineLength);
|
|
MaxCharsToRead = (*LineBufferMaxChars) - (*LineLength);
|
|
|
|
if (!fgetws(CurrentReadPosition,
|
|
MaxCharsToRead,
|
|
File)) {
|
|
|
|
//
|
|
// If we have not hit an EOF, we have hit an error.
|
|
//
|
|
|
|
if (!feof(File)) {
|
|
|
|
ErrorCode = ERROR_READ_FAULT;
|
|
goto cleanup;
|
|
|
|
} else {
|
|
|
|
//
|
|
// We hit end of file. Return what we have.
|
|
//
|
|
|
|
ErrorCode = ERROR_SUCCESS;
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Update line length.
|
|
//
|
|
|
|
(*LineLength) += wcslen(CurrentReadPosition);
|
|
|
|
//
|
|
// If we have read a carriage return, we are done. Check to
|
|
// see if we had room to read anything first!
|
|
//
|
|
|
|
if ((*LineLength) && (*LineBuffer)[(*LineLength) - 1] == L'\n') {
|
|
break;
|
|
}
|
|
|
|
//
|
|
// If we read up to the end of the buffer, resize it.
|
|
//
|
|
|
|
if ((*LineLength) == (*LineBufferMaxChars) - 1) {
|
|
|
|
//
|
|
// We should not enter this loop with a zero lengthed or NULL
|
|
// line buffer.
|
|
//
|
|
|
|
PFSVC_ASSERT((*LineBufferMaxChars) && (*LineBuffer));
|
|
|
|
NewBufferMaxChars = (*LineBufferMaxChars) * 2;
|
|
NewBuffer = PFSVC_ALLOC(NewBufferMaxChars * sizeof(WCHAR));
|
|
|
|
if (!NewBuffer) {
|
|
ErrorCode = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Copy contents of the original buffer and free it.
|
|
//
|
|
|
|
RtlCopyMemory(NewBuffer,
|
|
(*LineBuffer),
|
|
((*LineLength) + 1) * sizeof(WCHAR));
|
|
|
|
PFSVC_FREE(*LineBuffer);
|
|
|
|
//
|
|
// Update line buffer.
|
|
//
|
|
|
|
(*LineBuffer) = NewBuffer;
|
|
(*LineBufferMaxChars) = NewBufferMaxChars;
|
|
}
|
|
|
|
//
|
|
// Continue reading this line and appending it to output
|
|
// buffer.
|
|
//
|
|
|
|
} while (TRUE);
|
|
|
|
ErrorCode = ERROR_SUCCESS;
|
|
|
|
cleanup:
|
|
|
|
if (ErrorCode == ERROR_SUCCESS && (*LineBufferMaxChars)) {
|
|
|
|
//
|
|
// Returned length must fit into buffer.
|
|
//
|
|
|
|
PFSVC_ASSERT((*LineLength) < (*LineBufferMaxChars));
|
|
|
|
//
|
|
// Returned buffer should be NUL terminated.
|
|
//
|
|
|
|
PFSVC_ASSERT((*LineBuffer)[(*LineLength)] == 0);
|
|
}
|
|
|
|
return ErrorCode;
|
|
}
|
|
|
|
DWORD
|
|
PfSvGetFileBasicInformation (
|
|
WCHAR *FilePath,
|
|
PFILE_BASIC_INFORMATION FileInformation
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine queries the basic attributes for the specified file.
|
|
|
|
Arguments:
|
|
|
|
FilePath - Pointer to full NT file path, e.g.
|
|
\Device\HarddiskVolume1\boot.ini, NOT Win32 path, e.g. c:\boot.ini
|
|
|
|
FileInformation - If successful the basic file info is returned here.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
OBJECT_ATTRIBUTES ObjectAttributes;
|
|
UNICODE_STRING FilePathU;
|
|
NTSTATUS Status;
|
|
DWORD ErrorCode;
|
|
|
|
//
|
|
// Query the file information.
|
|
//
|
|
|
|
RtlInitUnicodeString(&FilePathU, FilePath);
|
|
|
|
InitializeObjectAttributes(&ObjectAttributes,
|
|
&FilePathU,
|
|
OBJ_CASE_INSENSITIVE,
|
|
NULL,
|
|
NULL);
|
|
|
|
Status = NtQueryAttributesFile(&ObjectAttributes,
|
|
FileInformation);
|
|
|
|
if (NT_SUCCESS(Status)) {
|
|
|
|
//
|
|
// In the typical success case, don't call possibly an expensive
|
|
// routine to convert the error code.
|
|
//
|
|
|
|
ErrorCode = ERROR_SUCCESS;
|
|
|
|
} else {
|
|
|
|
ErrorCode = RtlNtStatusToDosError(Status);
|
|
}
|
|
|
|
return ErrorCode;
|
|
}
|
|
|
|
DWORD
|
|
PfSvGetFileIndexNumber(
|
|
WCHAR *FilePath,
|
|
PLARGE_INTEGER FileIndexNumber
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine queries the file system's IndexNumber for the specified
|
|
file.
|
|
|
|
Arguments:
|
|
|
|
FilePath - Pointer to full NT file path, e.g.
|
|
\Device\HarddiskVolume1\boot.ini, NOT Win32 path, e.g. c:\boot.ini
|
|
|
|
FileIndexNumber - If successful the index number is returned here.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
HANDLE FileHandle;
|
|
BOOLEAN OpenedFile;
|
|
NTSTATUS Status;
|
|
OBJECT_ATTRIBUTES ObjectAttributes;
|
|
UNICODE_STRING FilePathU;
|
|
IO_STATUS_BLOCK IoStatusBlock;
|
|
FILE_INTERNAL_INFORMATION InternalInformation;
|
|
|
|
//
|
|
// Initialize locals.
|
|
//
|
|
|
|
OpenedFile = FALSE;
|
|
|
|
//
|
|
// Open the file.
|
|
//
|
|
|
|
RtlInitUnicodeString(&FilePathU, FilePath);
|
|
|
|
InitializeObjectAttributes(&ObjectAttributes,
|
|
&FilePathU,
|
|
OBJ_CASE_INSENSITIVE,
|
|
NULL,
|
|
NULL);
|
|
|
|
Status = NtCreateFile(&FileHandle,
|
|
STANDARD_RIGHTS_READ |
|
|
FILE_READ_ATTRIBUTES |
|
|
FILE_READ_EA,
|
|
&ObjectAttributes,
|
|
&IoStatusBlock,
|
|
0,
|
|
0,
|
|
FILE_SHARE_READ |
|
|
FILE_SHARE_WRITE |
|
|
FILE_SHARE_DELETE,
|
|
FILE_OPEN,
|
|
0,
|
|
NULL,
|
|
0);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
goto cleanup;
|
|
}
|
|
|
|
OpenedFile = TRUE;
|
|
|
|
//
|
|
// Query internal information.
|
|
//
|
|
|
|
Status = NtQueryInformationFile(FileHandle,
|
|
&IoStatusBlock,
|
|
&InternalInformation,
|
|
sizeof(InternalInformation),
|
|
FileInternalInformation);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
goto cleanup;
|
|
}
|
|
|
|
*FileIndexNumber = InternalInformation.IndexNumber;
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
cleanup:
|
|
|
|
if (OpenedFile) {
|
|
NtClose(FileHandle);
|
|
}
|
|
|
|
return RtlNtStatusToDosError(Status);
|
|
}
|
|
|
|
//
|
|
// String utility routines.
|
|
//
|
|
|
|
PFSV_SUFFIX_COMPARISON_RESULT
|
|
PfSvCompareSuffix(
|
|
WCHAR *String,
|
|
ULONG StringLength,
|
|
WCHAR *Suffix,
|
|
ULONG SuffixLength,
|
|
BOOLEAN CaseSensitive
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This compares the last characters of String to Suffix. The strings
|
|
don't have to be NUL terminated.
|
|
|
|
NOTE: The lexical ordering is done starting from the LAST
|
|
characters.
|
|
|
|
Arguments:
|
|
|
|
String - String to check suffix of.
|
|
|
|
StringLength - Number of characters in String.
|
|
|
|
Suffix - What the suffix of String should match.
|
|
|
|
SuffixLength - Number of characters in Suffix.
|
|
|
|
CaseSensitive - Whether the comparison should be case sensitive.
|
|
|
|
Return Value:
|
|
|
|
PFSV_SUFFIX_COMPARISON_RESULT
|
|
|
|
--*/
|
|
|
|
{
|
|
LONG StringCharIdx;
|
|
WCHAR StringChar;
|
|
LONG SuffixCharIdx;
|
|
WCHAR SuffixChar;
|
|
|
|
//
|
|
// If suffix is longer than the string itself, it cannot match.
|
|
//
|
|
|
|
if (SuffixLength > StringLength) {
|
|
return PfSvSuffixLongerThan;
|
|
}
|
|
|
|
//
|
|
// If the suffix is 0 length it matches anything.
|
|
//
|
|
|
|
if (SuffixLength == 0) {
|
|
return PfSvSuffixIdentical;
|
|
}
|
|
|
|
//
|
|
// If the suffix is not 0 length and it is greater than
|
|
// StringLength, StringLength cannot be 0.
|
|
//
|
|
|
|
PFSVC_ASSERT(StringLength);
|
|
|
|
//
|
|
// Start from the last character of the string and try to match
|
|
// the suffix.
|
|
//
|
|
|
|
StringCharIdx = StringLength - 1;
|
|
SuffixCharIdx = SuffixLength - 1;
|
|
|
|
while (SuffixCharIdx >= 0) {
|
|
|
|
SuffixChar = Suffix[SuffixCharIdx];
|
|
StringChar = String[StringCharIdx];
|
|
|
|
if (!CaseSensitive) {
|
|
SuffixChar = towupper(SuffixChar);
|
|
StringChar = towupper(StringChar);
|
|
}
|
|
|
|
//
|
|
// Is comparing the values of chars same comparing them
|
|
// lexically?
|
|
//
|
|
|
|
if (StringChar < SuffixChar) {
|
|
return PfSvSuffixGreaterThan;
|
|
} else if (StringChar > SuffixChar) {
|
|
return PfSvSuffixLessThan;
|
|
}
|
|
|
|
//
|
|
// Otherwise this character matches. Compare next one.
|
|
//
|
|
|
|
StringCharIdx--;
|
|
SuffixCharIdx--;
|
|
}
|
|
|
|
//
|
|
// All suffix characters matched.
|
|
//
|
|
|
|
return PfSvSuffixIdentical;
|
|
}
|
|
|
|
PFSV_PREFIX_COMPARISON_RESULT
|
|
PfSvComparePrefix(
|
|
WCHAR *String,
|
|
ULONG StringLength,
|
|
WCHAR *Prefix,
|
|
ULONG PrefixLength,
|
|
BOOLEAN CaseSensitive
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This compares the first characters of String to Prefix. The
|
|
strings don't have to be NUL terminated.
|
|
|
|
Arguments:
|
|
|
|
String - String to check prefix of.
|
|
|
|
StringLength - Number of characters in String.
|
|
|
|
Suffix - What the prefix of String should match.
|
|
|
|
SuffixLength - Number of characters in Prefix.
|
|
|
|
CaseSensitive - Whether the comparison should be case sensitive.
|
|
|
|
Return Value:
|
|
|
|
PFSV_PREFIX_COMPARISON_RESULT
|
|
|
|
--*/
|
|
|
|
{
|
|
LONG StrCmpResult;
|
|
|
|
//
|
|
// If prefix is longer than the string itself, it cannot match.
|
|
//
|
|
|
|
if (PrefixLength > StringLength) {
|
|
return PfSvPrefixLongerThan;
|
|
}
|
|
|
|
//
|
|
// If the prefix is 0 length it matches anything.
|
|
//
|
|
|
|
if (PrefixLength == 0) {
|
|
return PfSvPrefixIdentical;
|
|
}
|
|
|
|
//
|
|
// If the prefix is not 0 length and it is greater than
|
|
// StringLength, StringLength cannot be 0.
|
|
//
|
|
|
|
ASSERT(StringLength);
|
|
|
|
//
|
|
// Compare the prefix to the beginning of the string.
|
|
//
|
|
|
|
if (CaseSensitive) {
|
|
StrCmpResult = wcsncmp(Prefix, String, PrefixLength);
|
|
} else {
|
|
StrCmpResult = _wcsnicmp(Prefix, String, PrefixLength);
|
|
}
|
|
|
|
if (StrCmpResult == 0) {
|
|
return PfSvPrefixIdentical;
|
|
} else if (StrCmpResult > 0) {
|
|
return PfSvPrefixGreaterThan;
|
|
} else {
|
|
return PfSvPrefixLessThan;
|
|
}
|
|
}
|
|
|
|
VOID
|
|
FASTCALL
|
|
PfSvRemoveEndOfLineChars (
|
|
WCHAR *Line,
|
|
ULONG *LineLength
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
If the Line ends with \n/\r\n, these characters are removed and
|
|
LineLength is adjusted accordingly.
|
|
|
|
Arguments:
|
|
|
|
Line - Pointer to line string.
|
|
|
|
LineLength - Pointer to length of line string in characters
|
|
excluding any terminating NULs. This is updated if carriage
|
|
return/linefeed characters are removed.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
if ((*LineLength) && (Line[(*LineLength) - 1] == L'\n')) {
|
|
|
|
Line[(*LineLength) - 1] = 0;
|
|
(*LineLength)--;
|
|
|
|
if ((*LineLength) && (Line[(*LineLength) - 1] == L'\r')) {
|
|
|
|
Line[(*LineLength) - 1] = 0;
|
|
(*LineLength)--;
|
|
}
|
|
}
|
|
}
|
|
|
|
PWCHAR
|
|
PfSvcAnsiToUnicode(
|
|
PCHAR str
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine converts an ANSI string into an allocated wide
|
|
character string. The returned string should be freed by
|
|
PfSvcFreeString.
|
|
|
|
Arguments:
|
|
|
|
str - Pointer to string to convert.
|
|
|
|
Return Value:
|
|
|
|
Allocated wide character string or NULL if there is a failure.
|
|
|
|
--*/
|
|
|
|
{
|
|
ULONG len;
|
|
wchar_t *retstr = NULL;
|
|
|
|
len = MultiByteToWideChar(CP_ACP, 0, str, -1, NULL, 0);
|
|
retstr = (wchar_t *)PFSVC_ALLOC(len * sizeof(wchar_t));
|
|
if (!retstr)
|
|
{
|
|
return NULL;
|
|
}
|
|
MultiByteToWideChar(CP_ACP, 0, str, -1, retstr, len);
|
|
return retstr;
|
|
}
|
|
|
|
PCHAR
|
|
PfSvcUnicodeToAnsi(
|
|
PWCHAR wstr
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine converts a unicode string into an allocated ansi
|
|
string. The returned string should be freed by PfSvcFreeString.
|
|
|
|
Arguments:
|
|
|
|
wstr - Pointer to string to convert.
|
|
|
|
Return Value:
|
|
|
|
Allocated ANSI string or NULL if there is a failure.
|
|
|
|
--*/
|
|
|
|
{
|
|
ULONG len;
|
|
char *retstr = NULL;
|
|
|
|
len = WideCharToMultiByte(CP_ACP, 0, wstr, -1, NULL, 0, 0, 0);
|
|
retstr = (char *) PFSVC_ALLOC(len * sizeof(char));
|
|
if (!retstr)
|
|
{
|
|
return NULL;
|
|
}
|
|
WideCharToMultiByte(CP_ACP, 0, wstr, -1, retstr, len, 0, 0);
|
|
return retstr;
|
|
}
|
|
|
|
VOID
|
|
PfSvcFreeString(
|
|
PVOID String
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine frees a string allocated and returned by
|
|
PfSvcUnicodeToAnsi or PfSvcAnsiToUnicode.
|
|
|
|
Arguments:
|
|
|
|
String - Pointer to string to free.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PFSVC_FREE(String);
|
|
}
|
|
|
|
//
|
|
// Routines that deal with information in the registry.
|
|
//
|
|
|
|
DWORD
|
|
PfSvSaveStartInfo (
|
|
HKEY ServiceDataKey
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine saves start time, prefetcher version etc. into the
|
|
registry.
|
|
|
|
Arguments:
|
|
|
|
ServiceDataKey - Key under which the values will be set.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
DWORD ErrorCode;
|
|
DWORD PrefetchVersion;
|
|
SYSTEMTIME LocalTime;
|
|
WCHAR CurrentTime[50];
|
|
ULONG CurrentTimeMaxChars;
|
|
ULONG CurrentTimeSize;
|
|
|
|
//
|
|
// Initialize locals.
|
|
//
|
|
|
|
PrefetchVersion = PF_CURRENT_VERSION;
|
|
CurrentTimeMaxChars = sizeof(CurrentTime) / sizeof(WCHAR);
|
|
|
|
//
|
|
// Save version.
|
|
//
|
|
|
|
ErrorCode = RegSetValueEx(ServiceDataKey,
|
|
PFSVC_VERSION_VALUE_NAME,
|
|
0,
|
|
REG_DWORD,
|
|
(PVOID) &PrefetchVersion,
|
|
sizeof(PrefetchVersion));
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Get system time and convert it to a string.
|
|
//
|
|
|
|
GetLocalTime(&LocalTime);
|
|
|
|
_snwprintf(CurrentTime, CurrentTimeMaxChars,
|
|
L"%04d/%02d/%02d-%02d:%02d:%02d",
|
|
(ULONG)LocalTime.wYear,
|
|
(ULONG)LocalTime.wMonth,
|
|
(ULONG)LocalTime.wDay,
|
|
(ULONG)LocalTime.wHour,
|
|
(ULONG)LocalTime.wMinute,
|
|
(ULONG)LocalTime.wSecond);
|
|
|
|
//
|
|
// Make sure it is terminated.
|
|
//
|
|
|
|
CurrentTime[CurrentTimeMaxChars - 1] = 0;
|
|
|
|
//
|
|
// Save it to the registry.
|
|
//
|
|
|
|
CurrentTimeSize = (wcslen(CurrentTime) + 1) * sizeof(WCHAR);
|
|
|
|
ErrorCode = RegSetValueEx(ServiceDataKey,
|
|
PFSVC_START_TIME_VALUE_NAME,
|
|
0,
|
|
REG_SZ,
|
|
(PVOID) CurrentTime,
|
|
CurrentTimeSize);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Save the initial statistics (which should be mostly zeros).
|
|
//
|
|
|
|
ErrorCode = PfSvSaveTraceProcessingStatistics(ServiceDataKey);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
ErrorCode = ERROR_SUCCESS;
|
|
|
|
cleanup:
|
|
|
|
return ErrorCode;
|
|
}
|
|
|
|
DWORD
|
|
PfSvSaveExitInfo (
|
|
HKEY ServiceDataKey,
|
|
DWORD ExitCode
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine saves the prefetcher service exit information to the
|
|
registry.
|
|
|
|
Arguments:
|
|
|
|
ServiceDataKey - Key under which the values will be set.
|
|
|
|
ExitCode - Win32 error code the service is exiting with.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
DWORD ErrorCode;
|
|
SYSTEMTIME LocalTime;
|
|
WCHAR CurrentTime[50];
|
|
ULONG CurrentTimeMaxChars;
|
|
ULONG CurrentTimeSize;
|
|
|
|
//
|
|
// Initialize locals.
|
|
//
|
|
|
|
CurrentTimeMaxChars = sizeof(CurrentTime) / sizeof(WCHAR);
|
|
|
|
//
|
|
// Save exit code.
|
|
//
|
|
|
|
ErrorCode = RegSetValueEx(ServiceDataKey,
|
|
PFSVC_EXIT_CODE_VALUE_NAME,
|
|
0,
|
|
REG_DWORD,
|
|
(PVOID) &ExitCode,
|
|
sizeof(ExitCode));
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Get system time and convert it to a string.
|
|
//
|
|
|
|
GetLocalTime(&LocalTime);
|
|
|
|
_snwprintf(CurrentTime, CurrentTimeMaxChars,
|
|
L"%04d/%02d/%02d-%02d:%02d:%02d",
|
|
(ULONG)LocalTime.wYear,
|
|
(ULONG)LocalTime.wMonth,
|
|
(ULONG)LocalTime.wDay,
|
|
(ULONG)LocalTime.wHour,
|
|
(ULONG)LocalTime.wMinute,
|
|
(ULONG)LocalTime.wSecond);
|
|
|
|
//
|
|
// Make sure it is terminated.
|
|
//
|
|
|
|
CurrentTime[CurrentTimeMaxChars - 1] = 0;
|
|
|
|
//
|
|
// Save it to the registry.
|
|
//
|
|
|
|
CurrentTimeSize = (wcslen(CurrentTime) + 1) * sizeof(WCHAR);
|
|
|
|
ErrorCode = RegSetValueEx(ServiceDataKey,
|
|
PFSVC_EXIT_TIME_VALUE_NAME,
|
|
0,
|
|
REG_SZ,
|
|
(PVOID) CurrentTime,
|
|
CurrentTimeSize);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Save the final statistics.
|
|
//
|
|
|
|
ErrorCode = PfSvSaveTraceProcessingStatistics(ServiceDataKey);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
ErrorCode = ERROR_SUCCESS;
|
|
|
|
cleanup:
|
|
|
|
return ErrorCode;
|
|
}
|
|
|
|
DWORD
|
|
PfSvSaveTraceProcessingStatistics (
|
|
HKEY ServiceDataKey
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine saves global trace processing statistics to the
|
|
registry.
|
|
|
|
Arguments:
|
|
|
|
ServiceDataKey - Key under which the values will be set.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
DWORD ErrorCode;
|
|
|
|
//
|
|
// Save the various global statistics.
|
|
//
|
|
|
|
ErrorCode = RegSetValueEx(ServiceDataKey,
|
|
PFSVC_TRACES_PROCESSED_VALUE_NAME,
|
|
0,
|
|
REG_DWORD,
|
|
(PVOID) &PfSvcGlobals.NumTracesProcessed,
|
|
sizeof(DWORD));
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
ErrorCode = RegSetValueEx(ServiceDataKey,
|
|
PFSVC_TRACES_SUCCESSFUL_VALUE_NAME,
|
|
0,
|
|
REG_DWORD,
|
|
(PVOID) &PfSvcGlobals.NumTracesSuccessful,
|
|
sizeof(DWORD));
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
ErrorCode = RegSetValueEx(ServiceDataKey,
|
|
PFSVC_LAST_TRACE_FAILURE_VALUE_NAME,
|
|
0,
|
|
REG_DWORD,
|
|
(PVOID) &PfSvcGlobals.LastTraceFailure,
|
|
sizeof(DWORD));
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
ErrorCode = ERROR_SUCCESS;
|
|
|
|
cleanup:
|
|
|
|
return ErrorCode;
|
|
}
|
|
|
|
DWORD
|
|
PfSvGetLastDiskLayoutTime(
|
|
FILETIME *LastDiskLayoutTime
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine queries the last time disk layout was updated from
|
|
the registry under the service data key.
|
|
|
|
Arguments:
|
|
|
|
LastDiskLayoutTime - Pointer to output data.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
ULONG Size;
|
|
DWORD ErrorCode;
|
|
DWORD RegValueType;
|
|
FILETIME CurrentFileTime;
|
|
SYSTEMTIME SystemTime;
|
|
|
|
//
|
|
// Query last disk layout time from the registry and adjust it if
|
|
// necessary.
|
|
//
|
|
|
|
Size = sizeof(FILETIME);
|
|
|
|
ErrorCode = RegQueryValueEx(PfSvcGlobals.ServiceDataKey,
|
|
PFSVC_LAST_DISK_LAYOUT_TIME_VALUE_NAME,
|
|
NULL,
|
|
&RegValueType,
|
|
(PVOID) LastDiskLayoutTime,
|
|
&Size);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
|
|
if (ErrorCode == ERROR_FILE_NOT_FOUND) {
|
|
|
|
//
|
|
// No successful runs of the defragger to update layout has
|
|
// been recorded in the registry.
|
|
//
|
|
|
|
RtlZeroMemory(LastDiskLayoutTime, sizeof(FILETIME));
|
|
|
|
} else {
|
|
|
|
//
|
|
// This is a real error.
|
|
//
|
|
|
|
goto cleanup;
|
|
}
|
|
|
|
} else {
|
|
|
|
//
|
|
// The query was successful, but if the value type is not
|
|
// REG_BINARY, we most likely read in trash.
|
|
//
|
|
|
|
if (RegValueType != REG_BINARY) {
|
|
|
|
RtlZeroMemory(LastDiskLayoutTime, sizeof(FILETIME));
|
|
|
|
} else {
|
|
|
|
//
|
|
// If the time we recorded looks greater than the current
|
|
// time (e.g. because the user played with the system time
|
|
// and such), adjust it.
|
|
//
|
|
|
|
GetSystemTime(&SystemTime);
|
|
|
|
if (!SystemTimeToFileTime(&SystemTime, &CurrentFileTime)) {
|
|
ErrorCode = GetLastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
if (CompareFileTime(LastDiskLayoutTime, &CurrentFileTime) > 0) {
|
|
|
|
//
|
|
// The time in the registry looks bogus. We'll set it
|
|
// to 0, to drive our caller to run the defragger to
|
|
// update the layout again.
|
|
//
|
|
|
|
RtlZeroMemory(LastDiskLayoutTime, sizeof(FILETIME));
|
|
}
|
|
}
|
|
}
|
|
|
|
ErrorCode = ERROR_SUCCESS;
|
|
|
|
cleanup:
|
|
|
|
return ErrorCode;
|
|
}
|
|
|
|
DWORD
|
|
PfSvSetLastDiskLayoutTime(
|
|
FILETIME *LastDiskLayoutTime
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine saves the last time the disk layout was updated to
|
|
the registry under the service data key.
|
|
|
|
Arguments:
|
|
|
|
LastDiskLayoutTime - Pointer to new disk layout time.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
DWORD ErrorCode;
|
|
WCHAR CurrentTime[50];
|
|
ULONG CurrentTimeMaxChars;
|
|
ULONG CurrentTimeSize;
|
|
FILETIME LocalFileTime;
|
|
SYSTEMTIME LocalSystemTime;
|
|
|
|
//
|
|
// Initialize locals.
|
|
//
|
|
|
|
CurrentTimeMaxChars = sizeof(CurrentTime) / sizeof(WCHAR);
|
|
|
|
//
|
|
// Save the specified time.
|
|
//
|
|
|
|
ErrorCode = RegSetValueEx(PfSvcGlobals.ServiceDataKey,
|
|
PFSVC_LAST_DISK_LAYOUT_TIME_VALUE_NAME,
|
|
0,
|
|
REG_BINARY,
|
|
(PVOID) LastDiskLayoutTime,
|
|
sizeof(FILETIME));
|
|
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Also save it in human readable format.
|
|
//
|
|
|
|
if (!FileTimeToLocalFileTime(LastDiskLayoutTime, &LocalFileTime)) {
|
|
ErrorCode = GetLastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
if (!FileTimeToSystemTime(&LocalFileTime, &LocalSystemTime)) {
|
|
ErrorCode = GetLastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
_snwprintf(CurrentTime, CurrentTimeMaxChars,
|
|
L"%04d/%02d/%02d-%02d:%02d:%02d",
|
|
(ULONG)LocalSystemTime.wYear,
|
|
(ULONG)LocalSystemTime.wMonth,
|
|
(ULONG)LocalSystemTime.wDay,
|
|
(ULONG)LocalSystemTime.wHour,
|
|
(ULONG)LocalSystemTime.wMinute,
|
|
(ULONG)LocalSystemTime.wSecond);
|
|
|
|
//
|
|
// Make sure it is terminated.
|
|
//
|
|
|
|
CurrentTime[CurrentTimeMaxChars - 1] = 0;
|
|
|
|
//
|
|
// Save it to the registry.
|
|
//
|
|
|
|
CurrentTimeSize = (wcslen(CurrentTime) + 1) * sizeof(WCHAR);
|
|
|
|
ErrorCode = RegSetValueEx(PfSvcGlobals.ServiceDataKey,
|
|
PFSVC_LAST_DISK_LAYOUT_TIME_STRING_VALUE_NAME,
|
|
0,
|
|
REG_SZ,
|
|
(PVOID) CurrentTime,
|
|
CurrentTimeSize);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
ErrorCode = ERROR_SUCCESS;
|
|
|
|
cleanup:
|
|
|
|
return ErrorCode;
|
|
}
|
|
|
|
DWORD
|
|
PfSvGetDontRunDefragger(
|
|
DWORD *DontRunDefragger
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine queries the registry setting that disables launching
|
|
the defragger when the system is idle.
|
|
|
|
Arguments:
|
|
|
|
DontRunDefragger - Pointer to output data.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
HKEY ParametersKey;
|
|
ULONG Size;
|
|
DWORD Value;
|
|
DWORD ErrorCode;
|
|
DWORD RegValueType;
|
|
BOOLEAN OpenedParametersKey;
|
|
|
|
//
|
|
// Initialize locals.
|
|
//
|
|
|
|
OpenedParametersKey = FALSE;
|
|
|
|
//
|
|
// Open the parameters key, creating it if necessary.
|
|
//
|
|
|
|
ErrorCode = RegCreateKey(HKEY_LOCAL_MACHINE,
|
|
PFSVC_OPTIMAL_LAYOUT_REG_KEY_PATH,
|
|
&ParametersKey);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
OpenedParametersKey = TRUE;
|
|
|
|
//
|
|
// Query whether auto layout is enabled.
|
|
//
|
|
|
|
Size = sizeof(Value);
|
|
|
|
ErrorCode = RegQueryValueEx(ParametersKey,
|
|
PFSVC_OPTIMAL_LAYOUT_ENABLE_VALUE_NAME,
|
|
NULL,
|
|
&RegValueType,
|
|
(PVOID) &Value,
|
|
&Size);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// The query was successful. Make sure value is a DWORD.
|
|
//
|
|
|
|
if (RegValueType != REG_DWORD) {
|
|
ErrorCode = ERROR_BAD_FORMAT;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Set the value.
|
|
//
|
|
|
|
*DontRunDefragger = !(Value);
|
|
|
|
ErrorCode = ERROR_SUCCESS;
|
|
|
|
cleanup:
|
|
|
|
if (OpenedParametersKey) {
|
|
CloseHandle(ParametersKey);
|
|
}
|
|
|
|
return ErrorCode;
|
|
}
|
|
|
|
BOOLEAN
|
|
PfSvAllowedToRunDefragger(
|
|
BOOLEAN CheckRegistry
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine checks the global state/parameters to see if we
|
|
are allowed to try to run the defragger.
|
|
|
|
Arguments:
|
|
|
|
CheckRegistry - Whether to ignore auto-layout enable key in the registry.
|
|
|
|
Return Value:
|
|
|
|
TRUE - Go ahead and run the defragger.
|
|
FALSE - Don't run the defragger.
|
|
|
|
--*/
|
|
|
|
{
|
|
PF_SCENARIO_TYPE ScenarioType;
|
|
BOOLEAN AllowedToRunDefragger;
|
|
BOOLEAN PrefetchingEnabled;
|
|
|
|
//
|
|
// Initialize locals.
|
|
//
|
|
|
|
AllowedToRunDefragger = FALSE;
|
|
|
|
//
|
|
// Is this a server machine?
|
|
//
|
|
|
|
if (PfSvcGlobals.OsVersion.wProductType != VER_NT_WORKSTATION) {
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Is prefetching enabled for any scenario type?
|
|
//
|
|
|
|
PrefetchingEnabled = FALSE;
|
|
|
|
for(ScenarioType = 0; ScenarioType < PfMaxScenarioType; ScenarioType++) {
|
|
if (PfSvcGlobals.Parameters.EnableStatus[ScenarioType] == PfSvEnabled) {
|
|
PrefetchingEnabled = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!PrefetchingEnabled) {
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Did we try to run the defragger and it crashed before?
|
|
//
|
|
|
|
if (PfSvcGlobals.DefraggerErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// If in the registry we were not allowed to run the defragger, don't
|
|
// do so.
|
|
//
|
|
|
|
if (CheckRegistry) {
|
|
if (PfSvcGlobals.DontRunDefragger) {
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If we passed all checks, we are allowed to run the defragger.
|
|
//
|
|
|
|
AllowedToRunDefragger = TRUE;
|
|
|
|
cleanup:
|
|
|
|
return AllowedToRunDefragger;
|
|
}
|
|
|
|
//
|
|
// Routines that deal with security.
|
|
//
|
|
|
|
BOOL
|
|
PfSvSetPrivilege(
|
|
HANDLE hToken,
|
|
LPCTSTR lpszPrivilege,
|
|
ULONG ulPrivilege,
|
|
BOOL bEnablePrivilege
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Enables or disables a privilege in an access token.
|
|
|
|
Arguments:
|
|
|
|
hToken - Access token handle.
|
|
|
|
lpszPrivilege - Name of privilege to enable/disable.
|
|
|
|
ulPrivilege - If a name is not specified, then a ULONG privilege
|
|
should be specified.
|
|
|
|
bEnablePrivilege - Whether to enable or disable privilege
|
|
|
|
Return Value:
|
|
|
|
TRUE - Success.
|
|
FALSE - Failure.
|
|
|
|
--*/
|
|
|
|
{
|
|
TOKEN_PRIVILEGES tp;
|
|
LUID luid;
|
|
|
|
if (lpszPrivilege) {
|
|
if ( !LookupPrivilegeValue(NULL,
|
|
lpszPrivilege,
|
|
&luid)) {
|
|
return FALSE;
|
|
}
|
|
} else {
|
|
luid = RtlConvertUlongToLuid(ulPrivilege);
|
|
}
|
|
|
|
tp.PrivilegeCount = 1;
|
|
tp.Privileges[0].Luid = luid;
|
|
if (bEnablePrivilege)
|
|
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
|
|
else
|
|
tp.Privileges[0].Attributes = 0;
|
|
|
|
//
|
|
// Enable the privilege or disable all privileges.
|
|
//
|
|
|
|
AdjustTokenPrivileges(
|
|
hToken,
|
|
FALSE,
|
|
&tp,
|
|
sizeof(TOKEN_PRIVILEGES),
|
|
(PTOKEN_PRIVILEGES) NULL,
|
|
(PDWORD) NULL);
|
|
|
|
//
|
|
// Call GetLastError to determine whether the function succeeded.
|
|
//
|
|
|
|
if (GetLastError() != ERROR_SUCCESS) {
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
DWORD
|
|
PfSvSetAdminOnlyPermissions(
|
|
WCHAR *ObjectPath,
|
|
HANDLE ObjectHandle,
|
|
SE_OBJECT_TYPE ObjectType
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine makes the built-in administrators group the owner and
|
|
only allowed in the DACL of the specified directory or event
|
|
object.
|
|
|
|
The calling thread must have the SE_TAKE_OWNERSHIP_NAME privilege.
|
|
|
|
Arguments:
|
|
|
|
ObjectPath - File/directory path or event name.
|
|
|
|
ObjectHandle - If this is a SE_KERNEL_OBJECT, handle to it,
|
|
otherwise NULL.
|
|
|
|
ObjectType - Security object type. Only SE_KERNEL_OBJECT and
|
|
SE_FILE_OBJECT are supported.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
DWORD ErrorCode;
|
|
SID_IDENTIFIER_AUTHORITY SIDAuthority = SECURITY_NT_AUTHORITY;
|
|
PSID AdministratorsSID;
|
|
PSECURITY_DESCRIPTOR SecurityDescriptor;
|
|
PACL DiscretionaryACL;
|
|
DWORD ACLRevision;
|
|
ULONG ACESize;
|
|
ULONG ACLSize;
|
|
PACCESS_ALLOWED_ACE AccessAllowedAce;
|
|
BOOL Result;
|
|
|
|
//
|
|
// Initialize locals.
|
|
//
|
|
|
|
AdministratorsSID = NULL;
|
|
SecurityDescriptor = NULL;
|
|
DiscretionaryACL = NULL;
|
|
ACLRevision = ACL_REVISION;
|
|
|
|
//
|
|
// Check parameters.
|
|
//
|
|
|
|
if (ObjectType == SE_KERNEL_OBJECT) {
|
|
if (ObjectHandle == NULL) {
|
|
ErrorCode = ERROR_INVALID_PARAMETER;
|
|
goto cleanup;
|
|
}
|
|
} else if (ObjectType == SE_FILE_OBJECT) {
|
|
if (ObjectHandle != NULL) {
|
|
ErrorCode = ERROR_INVALID_PARAMETER;
|
|
goto cleanup;
|
|
}
|
|
} else {
|
|
ErrorCode = ERROR_INVALID_PARAMETER;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Create a SID for the BUILTIN\Administrators group.
|
|
//
|
|
|
|
if(!AllocateAndInitializeSid(&SIDAuthority,
|
|
2,
|
|
SECURITY_BUILTIN_DOMAIN_RID,
|
|
DOMAIN_ALIAS_RID_ADMINS,
|
|
0, 0, 0, 0, 0, 0,
|
|
&AdministratorsSID)) {
|
|
|
|
ErrorCode = GetLastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Make Administrators the owner.
|
|
//
|
|
|
|
ErrorCode = SetNamedSecurityInfo (ObjectPath,
|
|
ObjectType,
|
|
OWNER_SECURITY_INFORMATION,
|
|
AdministratorsSID,
|
|
NULL,
|
|
NULL,
|
|
NULL);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Setup a discretionary access control list:
|
|
//
|
|
|
|
//
|
|
// Determine size of an ACCESS_ALLOWED access control entry for
|
|
// the administrators group. (Subtract size of SidStart which is
|
|
// both part of ACE and SID.
|
|
//
|
|
|
|
ACESize = sizeof(ACCESS_ALLOWED_ACE);
|
|
ACESize -= sizeof (AccessAllowedAce->SidStart);
|
|
ACESize += GetLengthSid(AdministratorsSID);
|
|
|
|
//
|
|
// Determine size of the access control list.
|
|
//
|
|
|
|
ACLSize = sizeof(ACL);
|
|
ACLSize += ACESize;
|
|
|
|
//
|
|
// Allocate and initialize the access control list.
|
|
//
|
|
|
|
DiscretionaryACL = PFSVC_ALLOC(ACLSize);
|
|
|
|
if (!DiscretionaryACL) {
|
|
ErrorCode = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto cleanup;
|
|
}
|
|
|
|
if (!InitializeAcl(DiscretionaryACL, ACLSize, ACLRevision)) {
|
|
ErrorCode = GetLastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Add an ACE to allow access for the Administrators group.
|
|
//
|
|
|
|
if (!AddAccessAllowedAce(DiscretionaryACL,
|
|
ACLRevision,
|
|
GENERIC_ALL,
|
|
AdministratorsSID)) {
|
|
ErrorCode = GetLastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Initialize a security descriptor.
|
|
//
|
|
|
|
SecurityDescriptor = PFSVC_ALLOC(SECURITY_DESCRIPTOR_MIN_LENGTH);
|
|
|
|
if (!SecurityDescriptor) {
|
|
ErrorCode = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto cleanup;
|
|
}
|
|
|
|
if (!InitializeSecurityDescriptor(SecurityDescriptor,
|
|
SECURITY_DESCRIPTOR_REVISION)) {
|
|
ErrorCode = GetLastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Set the discretionary access control list on the security descriptor.
|
|
//
|
|
|
|
if (!SetSecurityDescriptorDacl(SecurityDescriptor,
|
|
TRUE,
|
|
DiscretionaryACL,
|
|
FALSE)) {
|
|
ErrorCode = GetLastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Set the built security descriptor on the prefetch directory.
|
|
//
|
|
|
|
if (ObjectType == SE_FILE_OBJECT) {
|
|
Result = SetFileSecurity(ObjectPath,
|
|
DACL_SECURITY_INFORMATION,
|
|
SecurityDescriptor);
|
|
|
|
} else {
|
|
|
|
PFSVC_ASSERT(ObjectType == SE_KERNEL_OBJECT);
|
|
PFSVC_ASSERT(ObjectHandle);
|
|
|
|
Result = SetKernelObjectSecurity(ObjectHandle,
|
|
DACL_SECURITY_INFORMATION,
|
|
SecurityDescriptor);
|
|
|
|
}
|
|
|
|
if (!Result) {
|
|
ErrorCode = GetLastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// We are done.
|
|
//
|
|
|
|
ErrorCode = ERROR_SUCCESS;
|
|
|
|
cleanup:
|
|
|
|
if (AdministratorsSID) {
|
|
FreeSid(AdministratorsSID);
|
|
}
|
|
|
|
if (SecurityDescriptor) {
|
|
PFSVC_FREE(SecurityDescriptor);
|
|
}
|
|
|
|
if (DiscretionaryACL) {
|
|
PFSVC_FREE(DiscretionaryACL);
|
|
}
|
|
|
|
return ErrorCode;
|
|
}
|
|
|
|
DWORD
|
|
PfSvGetPrefetchServiceThreadPrivileges (
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine ensures there is a security token for the current
|
|
thread and sets the right privileges on it so the thread can
|
|
communicate with the kernel mode prefetcher. It should be called
|
|
right after a thread is created.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
DWORD ErrorCode;
|
|
BOOLEAN ImpersonatedSelf;
|
|
BOOLEAN OpenedThreadToken;
|
|
HANDLE ThreadToken;
|
|
|
|
//
|
|
// Initialize locals.
|
|
//
|
|
|
|
ImpersonatedSelf = FALSE;
|
|
OpenedThreadToken = FALSE;
|
|
|
|
DBGPR((PFID,PFTRC,"PFSVC: GetThreadPriviliges()\n"));
|
|
|
|
//
|
|
// Obtain a security context for this thread so we can set
|
|
// privileges etc. without effecting the whole process.
|
|
//
|
|
|
|
if (!ImpersonateSelf(SecurityImpersonation)) {
|
|
DBGPR((PFID,PFERR,"PFSVC: GetThreadPriviliges()-FailedImpersonateSelf\n"));
|
|
ErrorCode = GetLastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
ImpersonatedSelf = TRUE;
|
|
|
|
//
|
|
// Set the privileges we will need talk to the kernel mode
|
|
// prefetcher:
|
|
//
|
|
|
|
//
|
|
// Open thread's access token.
|
|
//
|
|
|
|
if (!OpenThreadToken(GetCurrentThread(),
|
|
TOKEN_ADJUST_PRIVILEGES,
|
|
FALSE,
|
|
&ThreadToken)) {
|
|
DBGPR((PFID,PFERR,"PFSVC: GetThreadPriviliges()-FailedOpenToken\n"));
|
|
ErrorCode = GetLastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
OpenedThreadToken = TRUE;
|
|
|
|
//
|
|
// Enable the SE_PROF_SINGLE_PROCESS_PRIVILEGE privilege so the
|
|
// kernel mode prefetcher accepts our queries & set requests.
|
|
//
|
|
//
|
|
|
|
if (!PfSvSetPrivilege(ThreadToken, 0, SE_PROF_SINGLE_PROCESS_PRIVILEGE, TRUE)) {
|
|
DBGPR((PFID,PFERR,"PFSVC: GetThreadPriviliges()-FailedEnableProf\n"));
|
|
ErrorCode = GetLastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Enable the SE_TAKE_OWNERSHIP_NAME privilege so we can get
|
|
// ownership of the prefetch directory.
|
|
//
|
|
|
|
if (!PfSvSetPrivilege(ThreadToken, SE_TAKE_OWNERSHIP_NAME, 0, TRUE)) {
|
|
DBGPR((PFID,PFERR,"PFSVC: GetThreadPriviliges()-FailedEnableOwn\n"));
|
|
ErrorCode = GetLastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
ErrorCode = ERROR_SUCCESS;
|
|
|
|
cleanup:
|
|
|
|
if (OpenedThreadToken) {
|
|
CloseHandle(ThreadToken);
|
|
}
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
if (ImpersonatedSelf) {
|
|
RevertToSelf();
|
|
}
|
|
}
|
|
|
|
DBGPR((PFID,PFTRC,"PFSVC: GetThreadPriviliges()=%x\n",ErrorCode));
|
|
|
|
return ErrorCode;
|
|
}
|
|
|
|
//
|
|
// Routines that deal with volume node structures.
|
|
//
|
|
|
|
DWORD
|
|
PfSvCreateVolumeNode (
|
|
PPFSVC_SCENARIO_INFO ScenarioInfo,
|
|
WCHAR *VolumePath,
|
|
ULONG VolumePathLength,
|
|
PLARGE_INTEGER CreationTime,
|
|
ULONG SerialNumber
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine creates a volume node with the specifed info if it
|
|
does not already exist. If a node already exists, it verifies
|
|
CreationTime and SerialNumber.
|
|
|
|
Arguments:
|
|
|
|
ScenarioInfo - Pointer to new scenario information.
|
|
|
|
VolumePath - UPCASE NT full path of the volume, NUL terminated.
|
|
|
|
VolumePathLength - Number of characters in VolumePath excluding NUL.
|
|
|
|
CreationTime & SerialNumber - For the volume.
|
|
|
|
Return Value:
|
|
|
|
ERROR_REVISION_MISMATCH - There already exists a volume node with
|
|
that path but with a different signature/creation time.
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
PLIST_ENTRY HeadEntry;
|
|
PLIST_ENTRY NextEntry;
|
|
PLIST_ENTRY FoundPosition;
|
|
PPFSVC_VOLUME_NODE VolumeNode;
|
|
DWORD ErrorCode;
|
|
LONG ComparisonResult;
|
|
PPFSVC_VOLUME_NODE NewVolumeNode;
|
|
PWCHAR NewVolumePath;
|
|
|
|
//
|
|
// Initialize locals.
|
|
//
|
|
|
|
NewVolumeNode = NULL;
|
|
NewVolumePath = NULL;
|
|
|
|
DBGPR((PFID,PFSTRC,"PFSVC: CreateVolumeNode(%ws)\n", VolumePath));
|
|
|
|
//
|
|
// Walk through the existing volume nodes list and try to find
|
|
// matching one.
|
|
//
|
|
|
|
HeadEntry = &ScenarioInfo->VolumeList;
|
|
NextEntry = HeadEntry->Flink;
|
|
FoundPosition = NULL;
|
|
|
|
while (NextEntry != HeadEntry) {
|
|
|
|
VolumeNode = CONTAINING_RECORD(NextEntry,
|
|
PFSVC_VOLUME_NODE,
|
|
VolumeLink);
|
|
|
|
NextEntry = NextEntry->Flink;
|
|
|
|
ComparisonResult = wcsncmp(VolumePath,
|
|
VolumeNode->VolumePath,
|
|
VolumePathLength);
|
|
|
|
if (ComparisonResult == 0) {
|
|
|
|
//
|
|
// Make sure VolumePathLengths are equal.
|
|
//
|
|
|
|
if (VolumeNode->VolumePathLength != VolumePathLength) {
|
|
|
|
//
|
|
// Continue searching.
|
|
//
|
|
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// We found our volume. Verify magics.
|
|
//
|
|
|
|
if (VolumeNode->SerialNumber != SerialNumber ||
|
|
VolumeNode->CreationTime.QuadPart != CreationTime->QuadPart) {
|
|
|
|
ErrorCode = ERROR_REVISION_MISMATCH;
|
|
goto cleanup;
|
|
|
|
} else {
|
|
|
|
ErrorCode = ERROR_SUCCESS;
|
|
goto cleanup;
|
|
}
|
|
|
|
} else if (ComparisonResult < 0) {
|
|
|
|
//
|
|
// The volume paths are sorted lexically. The file path
|
|
// would be less than other volumes too. The new node
|
|
// would go right before this node.
|
|
//
|
|
|
|
FoundPosition = &VolumeNode->VolumeLink;
|
|
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Continue looking...
|
|
//
|
|
|
|
}
|
|
|
|
//
|
|
// If we could not find an entry to put the new entry befor, it
|
|
// goes before the list head.
|
|
//
|
|
|
|
if (!FoundPosition) {
|
|
FoundPosition = HeadEntry;
|
|
}
|
|
|
|
//
|
|
// If we break out of the while loop, we could not find a
|
|
// volume. We should create a new node.
|
|
//
|
|
|
|
NewVolumeNode = PfSvChunkAllocatorAllocate(&ScenarioInfo->VolumeNodeAllocator);
|
|
|
|
if (!NewVolumeNode) {
|
|
ErrorCode = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto cleanup;
|
|
}
|
|
|
|
NewVolumePath = PfSvStringAllocatorAllocate(&ScenarioInfo->PathAllocator,
|
|
(VolumePathLength + 1) * sizeof(WCHAR));
|
|
|
|
if (!NewVolumePath) {
|
|
ErrorCode = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Copy file's volume path.
|
|
//
|
|
|
|
wcsncpy(NewVolumePath, VolumePath, VolumePathLength);
|
|
NewVolumePath[VolumePathLength] = 0;
|
|
|
|
//
|
|
// Initialize volume node.
|
|
//
|
|
|
|
NewVolumeNode->VolumePath = NewVolumePath;
|
|
NewVolumeNode->VolumePathLength = VolumePathLength;
|
|
NewVolumeNode->SerialNumber = SerialNumber;
|
|
NewVolumeNode->CreationTime = (*CreationTime);
|
|
InitializeListHead(&NewVolumeNode->SectionList);
|
|
PfSvInitializePathList(&NewVolumeNode->DirectoryList, &ScenarioInfo->PathAllocator, TRUE);
|
|
NewVolumeNode->NumSections = 0;
|
|
NewVolumeNode->NumAllSections = 0;
|
|
NewVolumeNode->MFTSectionNode = NULL;
|
|
|
|
//
|
|
// Add it to the scenario's volume list before the found position.
|
|
//
|
|
|
|
InsertTailList(FoundPosition, &NewVolumeNode->VolumeLink);
|
|
|
|
VolumeNode = NewVolumeNode;
|
|
ErrorCode = ERROR_SUCCESS;
|
|
|
|
cleanup:
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
|
|
if (NewVolumePath) {
|
|
PfSvStringAllocatorFree(&ScenarioInfo->PathAllocator, NewVolumePath);
|
|
}
|
|
|
|
if (NewVolumeNode) {
|
|
PfSvChunkAllocatorFree(&ScenarioInfo->VolumeNodeAllocator, NewVolumeNode);
|
|
}
|
|
|
|
VolumeNode = NULL;
|
|
}
|
|
|
|
DBGPR((PFID,PFSTRC,"PFSVC: CreateVolumeNode()=%x\n", ErrorCode));
|
|
|
|
return ErrorCode;
|
|
}
|
|
|
|
PPFSVC_VOLUME_NODE
|
|
PfSvGetVolumeNode (
|
|
PPFSVC_SCENARIO_INFO ScenarioInfo,
|
|
WCHAR *FilePath,
|
|
ULONG FilePathLength
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine looks for a volume node for the specified file path.
|
|
|
|
Arguments:
|
|
|
|
ScenarioInfo - Pointer to new scenario information.
|
|
|
|
FilePath - NT full path of the file, NUL terminated.
|
|
|
|
FilePathLength - Number of characters in FilePath excluding NUL.
|
|
|
|
Return Value:
|
|
|
|
Pointer to found VolumeNode, or NULL.
|
|
|
|
--*/
|
|
|
|
{
|
|
PLIST_ENTRY HeadEntry;
|
|
PLIST_ENTRY NextEntry;
|
|
PPFSVC_VOLUME_NODE VolumeNode;
|
|
DWORD ErrorCode;
|
|
PFSV_PREFIX_COMPARISON_RESULT ComparisonResult;
|
|
|
|
DBGPR((PFID,PFSTRC,"PFSVC: GetVolumeNode(%ws)\n", FilePath));
|
|
|
|
//
|
|
// Walk through the existing volume nodes list and try to find
|
|
// matching one.
|
|
//
|
|
|
|
HeadEntry = &ScenarioInfo->VolumeList;
|
|
NextEntry = HeadEntry->Flink;
|
|
|
|
while (NextEntry != HeadEntry) {
|
|
|
|
VolumeNode = CONTAINING_RECORD(NextEntry,
|
|
PFSVC_VOLUME_NODE,
|
|
VolumeLink);
|
|
|
|
NextEntry = NextEntry->Flink;
|
|
|
|
ComparisonResult = PfSvComparePrefix(FilePath,
|
|
FilePathLength,
|
|
VolumeNode->VolumePath,
|
|
VolumeNode->VolumePathLength,
|
|
TRUE);
|
|
|
|
if (ComparisonResult == PfSvPrefixIdentical) {
|
|
|
|
//
|
|
// Make sure that there is a slash in the file
|
|
// path after the volume path.
|
|
//
|
|
|
|
if (FilePath[VolumeNode->VolumePathLength] != L'\\') {
|
|
|
|
//
|
|
// Continue searching.
|
|
//
|
|
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// We found our volume.
|
|
//
|
|
|
|
ErrorCode = ERROR_SUCCESS;
|
|
goto cleanup;
|
|
|
|
} else if (ComparisonResult == PfSvPrefixGreaterThan) {
|
|
|
|
//
|
|
// The volume paths are sorted lexically. The file path
|
|
// would be less than other volumes too.
|
|
//
|
|
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Continue looking...
|
|
//
|
|
|
|
}
|
|
|
|
//
|
|
// If we break out of the while loop, we could not find a
|
|
// volume.
|
|
//
|
|
|
|
VolumeNode = NULL;
|
|
ErrorCode = ERROR_NOT_FOUND;
|
|
|
|
cleanup:
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
VolumeNode = NULL;
|
|
}
|
|
|
|
DBGPR((PFID,PFSTRC,"PFSVC: GetVolumeNode()=%p\n", VolumeNode));
|
|
|
|
return VolumeNode;
|
|
}
|
|
|
|
VOID
|
|
PfSvCleanupVolumeNode(
|
|
PPFSVC_SCENARIO_INFO ScenarioInfo,
|
|
PPFSVC_VOLUME_NODE VolumeNode
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function cleans up a volume node structure. It does not free
|
|
the structure itself.
|
|
|
|
Arguments:
|
|
|
|
ScenarioInfo - Pointer to scenario info context this volume node
|
|
belongs to.
|
|
|
|
VolumeNode - Pointer to structure.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PLIST_ENTRY SectListEntry;
|
|
PPFSVC_SECTION_NODE SectionNode;
|
|
|
|
//
|
|
// Cleanup directory list.
|
|
//
|
|
|
|
PfSvCleanupPathList(&VolumeNode->DirectoryList);
|
|
|
|
//
|
|
// If there is a volume path, free it.
|
|
//
|
|
|
|
if (VolumeNode->VolumePath) {
|
|
PfSvStringAllocatorFree(&ScenarioInfo->PathAllocator, VolumeNode->VolumePath);
|
|
VolumeNode->VolumePath = NULL;
|
|
}
|
|
|
|
//
|
|
// Remove the section nodes from our list and re-initialize their
|
|
// links so they know they have been removed.
|
|
//
|
|
|
|
while (!IsListEmpty(&VolumeNode->SectionList)) {
|
|
|
|
SectListEntry = RemoveHeadList(&VolumeNode->SectionList);
|
|
|
|
SectionNode = CONTAINING_RECORD(SectListEntry,
|
|
PFSVC_SECTION_NODE,
|
|
SectionVolumeLink);
|
|
|
|
InitializeListHead(&SectionNode->SectionVolumeLink);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
DWORD
|
|
PfSvAddParentDirectoriesToList(
|
|
PPFSVC_PATH_LIST DirectoryList,
|
|
ULONG VolumePathLength,
|
|
WCHAR *FilePath,
|
|
ULONG FilePathLength
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function will parse a fully qualified NT file path and add
|
|
all parent directories to the specified directory list. The part
|
|
of the path that is the volume path is skipped.
|
|
|
|
Arguments:
|
|
|
|
DirectoryList - Pointer to list to update.
|
|
|
|
VolumePathLength - Position in the file path at which the volume
|
|
path ends and the root directory starts.
|
|
|
|
FilePath - Pointer to NT file path, NUL terminated.
|
|
|
|
FullPathLength - Length of FilePath in characters excluding NUL.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
DWORD ErrorCode;
|
|
WCHAR DirectoryPath[MAX_PATH];
|
|
ULONG DirectoryPathLength;
|
|
WCHAR *CurrentChar;
|
|
WCHAR *FilePathEnd;
|
|
|
|
//
|
|
// Initialize locals.
|
|
//
|
|
|
|
FilePathEnd = FilePath + FilePathLength;
|
|
PFSVC_ASSERT(*FilePathEnd == 0);
|
|
|
|
//
|
|
// Skip the volume path and start from the root directory.
|
|
//
|
|
|
|
CurrentChar = FilePath + VolumePathLength;
|
|
|
|
while (CurrentChar < FilePathEnd) {
|
|
|
|
if (*CurrentChar == L'\\') {
|
|
|
|
//
|
|
// We got a directory.
|
|
//
|
|
|
|
DirectoryPathLength = (ULONG) (CurrentChar - FilePath + 1);
|
|
|
|
if (DirectoryPathLength >= MAX_PATH) {
|
|
ErrorCode = ERROR_INSUFFICIENT_BUFFER;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Copy directory path to buffer and NUL terminate it.
|
|
//
|
|
|
|
wcsncpy(DirectoryPath, FilePath, DirectoryPathLength);
|
|
DirectoryPath[DirectoryPathLength] = 0;
|
|
PFSVC_ASSERT(DirectoryPath[DirectoryPathLength - 1] == L'\\');
|
|
|
|
//
|
|
// Add it to the list.
|
|
//
|
|
|
|
ErrorCode = PfSvAddToPathList(DirectoryList,
|
|
DirectoryPath,
|
|
DirectoryPathLength);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Continue looking for more directories in the path.
|
|
//
|
|
}
|
|
|
|
CurrentChar++;
|
|
}
|
|
|
|
ErrorCode = ERROR_SUCCESS;
|
|
|
|
cleanup:
|
|
|
|
return ErrorCode;
|
|
}
|
|
|
|
//
|
|
// Routines used to allocate / free section & page nodes etc. efficiently.
|
|
//
|
|
|
|
VOID
|
|
PfSvChunkAllocatorInitialize (
|
|
PPFSVC_CHUNK_ALLOCATOR Allocator
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Initializes the allocator structure. Must be called before other allocator
|
|
routines.
|
|
|
|
Arguments:
|
|
|
|
Allocator - Pointer to structure.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
//
|
|
// Zero the structure. This effectively initializes the following fields:
|
|
// Buffer
|
|
// BufferEnd
|
|
// FreePointer
|
|
// ChunkSize
|
|
// MaxHeapAllocs
|
|
// NumHeapAllocs
|
|
// UserSpecifiedBuffer
|
|
//
|
|
|
|
RtlZeroMemory(Allocator, sizeof(PFSVC_CHUNK_ALLOCATOR));
|
|
|
|
return;
|
|
}
|
|
|
|
DWORD
|
|
PfSvChunkAllocatorStart (
|
|
IN PPFSVC_CHUNK_ALLOCATOR Allocator,
|
|
OPTIONAL IN PVOID Buffer,
|
|
IN ULONG ChunkSize,
|
|
IN ULONG MaxChunks
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Must be called before calling alloc/free on an allocator that has
|
|
been initialized.
|
|
|
|
Arguments:
|
|
|
|
Allocator - Pointer to initialized structure.
|
|
|
|
Buffer - If specified, it is the buffer that will be divided up into
|
|
MaxChunks of ChunkSize and given away. Otherwise a buffer will be
|
|
allocated. If specified, the user has to free the buffer after the
|
|
chunk allocator has been cleaned up. It should be aligned right.
|
|
|
|
ChunkSize - In bytes how big each allocated chunk will be.
|
|
e.g. sizeof(PFSVC_PAGE_NODE) It should be greater than
|
|
sizeof(DWORD).
|
|
|
|
MaxChunks - Max number of allocs that will be made from the allocator.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
ULONG AllocationSize;
|
|
DWORD ErrorCode;
|
|
|
|
//
|
|
// Initialize locals.
|
|
//
|
|
|
|
AllocationSize = ChunkSize * MaxChunks;
|
|
|
|
//
|
|
// We should be initialized and we should not get started twice.
|
|
//
|
|
|
|
PFSVC_ASSERT(Allocator->Buffer == NULL);
|
|
|
|
//
|
|
// Chunk size should not be too small.
|
|
//
|
|
|
|
if (ChunkSize < sizeof(DWORD) || !MaxChunks) {
|
|
ErrorCode = ERROR_INVALID_PARAMETER;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Did the user specify the buffer to use?
|
|
//
|
|
|
|
if (Buffer) {
|
|
|
|
Allocator->Buffer = Buffer;
|
|
Allocator->UserSpecifiedBuffer = TRUE;
|
|
|
|
} else {
|
|
|
|
Allocator->Buffer = PFSVC_ALLOC(AllocationSize);
|
|
|
|
if (!Allocator->Buffer) {
|
|
ErrorCode = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto cleanup;
|
|
}
|
|
|
|
Allocator->UserSpecifiedBuffer = FALSE;
|
|
}
|
|
|
|
Allocator->BufferEnd = (PCHAR) Buffer + (ULONG_PTR) AllocationSize;
|
|
Allocator->FreePointer = Buffer;
|
|
Allocator->ChunkSize = ChunkSize;
|
|
|
|
ErrorCode = ERROR_SUCCESS;
|
|
|
|
cleanup:
|
|
|
|
return ErrorCode;
|
|
}
|
|
|
|
PVOID
|
|
PfSvChunkAllocatorAllocate (
|
|
PPFSVC_CHUNK_ALLOCATOR Allocator
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Returns a ChunkSize chunk allocated from Allocator. ChunkSize was specified
|
|
when Allocator was started. If a chunk is return the caller should free it
|
|
to this Allocator before uninitializing the Allocator.
|
|
|
|
Arguments:
|
|
|
|
Allocator - Pointer to started allocator.
|
|
|
|
Return Value:
|
|
|
|
NULL or chunk allocated from Allocator.
|
|
|
|
--*/
|
|
|
|
{
|
|
PVOID ReturnChunk;
|
|
|
|
//
|
|
// We should not be trying to make allocations before we start the
|
|
// allocator.
|
|
//
|
|
|
|
PFSVC_ASSERT(Allocator->Buffer && Allocator->ChunkSize);
|
|
|
|
//
|
|
// If we can allocate from our preallocated buffer do so. Otherwise we
|
|
// have to hit the heap.
|
|
//
|
|
|
|
if (Allocator->FreePointer >= Allocator->BufferEnd) {
|
|
|
|
Allocator->MaxHeapAllocs++;
|
|
|
|
ReturnChunk = PFSVC_ALLOC(Allocator->ChunkSize);
|
|
|
|
if (ReturnChunk) {
|
|
Allocator->NumHeapAllocs++;
|
|
}
|
|
|
|
} else {
|
|
|
|
ReturnChunk = Allocator->FreePointer;
|
|
|
|
Allocator->FreePointer += (ULONG_PTR) Allocator->ChunkSize;
|
|
}
|
|
|
|
return ReturnChunk;
|
|
}
|
|
|
|
VOID
|
|
PfSvChunkAllocatorFree (
|
|
PPFSVC_CHUNK_ALLOCATOR Allocator,
|
|
PVOID Allocation
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Frees a chunk allocated from the allocator. This may not make it available
|
|
for use by further allocations from the allocator.
|
|
|
|
Arguments:
|
|
|
|
Allocator - Pointer to started allocator.
|
|
|
|
Allocation - Allocation to free.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
//
|
|
// Is this within the preallocated block?
|
|
//
|
|
|
|
if ((PUCHAR) Allocation >= Allocator->Buffer &&
|
|
(PUCHAR) Allocation < Allocator->BufferEnd) {
|
|
|
|
//
|
|
// Mark this chunk freed.
|
|
//
|
|
|
|
*(PULONG)Allocation = PFSVC_CHUNK_ALLOCATOR_FREED_MAGIC;
|
|
|
|
} else {
|
|
|
|
//
|
|
// This chunk was allocated from heap.
|
|
//
|
|
|
|
PFSVC_ASSERT(Allocator->NumHeapAllocs && Allocator->MaxHeapAllocs);
|
|
|
|
Allocator->NumHeapAllocs--;
|
|
|
|
PFSVC_FREE(Allocation);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
VOID
|
|
PfSvChunkAllocatorCleanup (
|
|
PPFSVC_CHUNK_ALLOCATOR Allocator
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Cleans up resources associated with the allocator. There should not be
|
|
any outstanding allocations from the allocator when this function is
|
|
called.
|
|
|
|
Arguments:
|
|
|
|
Allocator - Pointer to initialized allocator.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PCHAR CurrentChunk;
|
|
ULONG Magic;
|
|
|
|
if (Allocator->Buffer) {
|
|
|
|
#ifdef PFSVC_DBG
|
|
|
|
//
|
|
// Make sure all real heap allocations have been freed.
|
|
//
|
|
|
|
PFSVC_ASSERT(Allocator->NumHeapAllocs == 0);
|
|
|
|
//
|
|
// Make sure all allocated chunks have been freed. Check
|
|
// ChunkSize first, if it's corrupted we'd loop forever.
|
|
//
|
|
|
|
PFSVC_ASSERT(Allocator->ChunkSize);
|
|
|
|
for (CurrentChunk = Allocator->Buffer;
|
|
CurrentChunk < Allocator->FreePointer;
|
|
CurrentChunk += (ULONG_PTR) Allocator->ChunkSize) {
|
|
|
|
Magic = *(PULONG)CurrentChunk;
|
|
|
|
PFSVC_ASSERT(Magic == PFSVC_CHUNK_ALLOCATOR_FREED_MAGIC);
|
|
}
|
|
|
|
#endif // PFSVC_DBG
|
|
|
|
//
|
|
// If the buffer was allocated by us (and not specified by
|
|
// the user), free it.
|
|
//
|
|
|
|
if (!Allocator->UserSpecifiedBuffer) {
|
|
PFSVC_FREE(Allocator->Buffer);
|
|
}
|
|
|
|
#ifdef PFSVC_DBG
|
|
|
|
//
|
|
// Setup the fields so if we try to make allocations after cleaning up
|
|
// an allocator we'll hit an assert.
|
|
//
|
|
|
|
Allocator->FreePointer = Allocator->Buffer;
|
|
Allocator->Buffer = NULL;
|
|
|
|
#endif // PFSVC_DBG
|
|
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Routines used to allocate / free path strings efficiently.
|
|
//
|
|
|
|
VOID
|
|
PfSvStringAllocatorInitialize (
|
|
PPFSVC_STRING_ALLOCATOR Allocator
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Initializes the allocator structure. Must be called before other allocator
|
|
routines.
|
|
|
|
Arguments:
|
|
|
|
Allocator - Pointer to structure.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
//
|
|
// Zero the structure. This effectively initializes the following fields:
|
|
// Buffer
|
|
// BufferEnd
|
|
// FreePointer
|
|
// MaxHeapAllocs
|
|
// NumHeapAllocs
|
|
// LastAllocationSize
|
|
// UserSpecifiedBuffer
|
|
//
|
|
|
|
RtlZeroMemory(Allocator, sizeof(PFSVC_STRING_ALLOCATOR));
|
|
|
|
return;
|
|
}
|
|
|
|
DWORD
|
|
PfSvStringAllocatorStart (
|
|
IN PPFSVC_STRING_ALLOCATOR Allocator,
|
|
OPTIONAL IN PVOID Buffer,
|
|
IN ULONG MaxSize
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Must be called before calling alloc/free on an allocator that has
|
|
been initialized.
|
|
|
|
Arguments:
|
|
|
|
Allocator - Pointer to initialized structure.
|
|
|
|
Buffer - If specified, it is the buffer that we will allocate strings
|
|
from. Otherwise a buffer will be allocated. If specified, the user
|
|
has to free the buffer after the chunk allocator has been cleaned up.
|
|
It should be aligned right.
|
|
|
|
MaxSize - Max valid size of buffer in bytes.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
DWORD ErrorCode;
|
|
|
|
//
|
|
// We should be initialized and we should not get started twice.
|
|
//
|
|
|
|
PFSVC_ASSERT(Allocator->Buffer == NULL);
|
|
|
|
//
|
|
// Did the user specify the buffer to use?
|
|
//
|
|
|
|
if (Buffer) {
|
|
|
|
Allocator->Buffer = Buffer;
|
|
Allocator->UserSpecifiedBuffer = TRUE;
|
|
|
|
} else {
|
|
|
|
Allocator->Buffer = PFSVC_ALLOC(MaxSize);
|
|
|
|
if (!Allocator->Buffer) {
|
|
ErrorCode = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto cleanup;
|
|
}
|
|
|
|
Allocator->UserSpecifiedBuffer = FALSE;
|
|
}
|
|
|
|
Allocator->BufferEnd = (PCHAR) Buffer + (ULONG_PTR) MaxSize;
|
|
Allocator->FreePointer = Buffer;
|
|
|
|
ErrorCode = ERROR_SUCCESS;
|
|
|
|
cleanup:
|
|
|
|
return ErrorCode;
|
|
}
|
|
|
|
PVOID
|
|
PfSvStringAllocatorAllocate (
|
|
PPFSVC_STRING_ALLOCATOR Allocator,
|
|
ULONG NumBytes
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Returns a ChunkSize chunk allocated from Allocator. ChunkSize was specified
|
|
when Allocator was started. If a chunk is return the caller should free it
|
|
to this Allocator before uninitializing the Allocator.
|
|
|
|
Arguments:
|
|
|
|
Allocator - Pointer to started allocator.
|
|
|
|
NumBytes - Number of bytes to allocate.
|
|
|
|
Return Value:
|
|
|
|
NULL or chunk allocated from Allocator.
|
|
|
|
--*/
|
|
|
|
{
|
|
PVOID ReturnChunk;
|
|
PCHAR UpdatedFreePointer;
|
|
PPFSVC_STRING_ALLOCATION_HEADER AllocationHeader;
|
|
ULONG_PTR RealAllocationSize;
|
|
|
|
//
|
|
// We should not be trying to make allocations before we start the
|
|
// allocator.
|
|
//
|
|
|
|
PFSVC_ASSERT(Allocator->Buffer);
|
|
|
|
//
|
|
// Calculate how much we have to reserve from the buffer to make this
|
|
// allocation.
|
|
//
|
|
|
|
RealAllocationSize = 0;
|
|
RealAllocationSize += sizeof(PFSVC_STRING_ALLOCATION_HEADER);
|
|
RealAllocationSize += NumBytes;
|
|
RealAllocationSize = (ULONG_PTR) PF_ALIGN_UP(RealAllocationSize, _alignof(PFSVC_STRING_ALLOCATION_HEADER));
|
|
|
|
//
|
|
// We can't allocate from our buffer and have to go to the heap if
|
|
// - We've run out of space.
|
|
// - Allocation size is too big to fit in a USHORT.
|
|
// - It is a 0 sized allocation.
|
|
//
|
|
|
|
if (Allocator->FreePointer + RealAllocationSize > Allocator->BufferEnd ||
|
|
NumBytes > PFSVC_STRING_ALLOCATOR_MAX_BUFFER_ALLOCATION_SIZE ||
|
|
NumBytes == 0) {
|
|
|
|
//
|
|
// Hit the heap.
|
|
//
|
|
|
|
Allocator->MaxHeapAllocs++;
|
|
|
|
ReturnChunk = PFSVC_ALLOC(NumBytes);
|
|
|
|
if (ReturnChunk) {
|
|
Allocator->NumHeapAllocs++;
|
|
}
|
|
|
|
} else {
|
|
|
|
AllocationHeader = (PVOID) Allocator->FreePointer;
|
|
AllocationHeader->PrecedingAllocationSize = Allocator->LastAllocationSize;
|
|
|
|
PFSVC_ASSERT(RealAllocationSize < USHRT_MAX);
|
|
AllocationHeader->AllocationSize = (USHORT) RealAllocationSize;
|
|
|
|
Allocator->FreePointer += RealAllocationSize;
|
|
Allocator->LastAllocationSize = (USHORT) RealAllocationSize;
|
|
|
|
//
|
|
// The user's allocation comes right after the allocation header.
|
|
// (Using pointer arithmetic...)
|
|
//
|
|
|
|
ReturnChunk = AllocationHeader + 1;
|
|
}
|
|
|
|
return ReturnChunk;
|
|
}
|
|
|
|
VOID
|
|
PfSvStringAllocatorFree (
|
|
PPFSVC_STRING_ALLOCATOR Allocator,
|
|
PVOID Allocation
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Frees a string allocated from the allocator. This may not make it available
|
|
for use by further allocations from the allocator.
|
|
|
|
Arguments:
|
|
|
|
Allocator - Pointer to started allocator.
|
|
|
|
Allocation - Allocation to free.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
//
|
|
// Is this within the preallocated block?
|
|
//
|
|
|
|
if ((PUCHAR) Allocation >= Allocator->Buffer &&
|
|
(PUCHAR) Allocation < Allocator->BufferEnd) {
|
|
|
|
//
|
|
// Mark this chunk freed.
|
|
//
|
|
|
|
*((PWCHAR)Allocation) = PFSVC_STRING_ALLOCATOR_FREED_MAGIC;
|
|
|
|
} else {
|
|
|
|
//
|
|
// This chunk was allocated from heap.
|
|
//
|
|
|
|
PFSVC_ASSERT(Allocator->NumHeapAllocs && Allocator->MaxHeapAllocs);
|
|
|
|
Allocator->NumHeapAllocs--;
|
|
|
|
PFSVC_FREE(Allocation);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
VOID
|
|
PfSvStringAllocatorCleanup (
|
|
PPFSVC_STRING_ALLOCATOR Allocator
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Cleans up resources associated with the allocator. There should not be
|
|
any outstanding allocations from the allocator when this function is
|
|
called.
|
|
|
|
Arguments:
|
|
|
|
Allocator - Pointer to initialized allocator.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PPFSVC_STRING_ALLOCATION_HEADER AllocationHeader;
|
|
PCHAR NextAllocationHeader;
|
|
WCHAR Magic;
|
|
|
|
if (Allocator->Buffer) {
|
|
|
|
#ifdef PFSVC_DBG
|
|
|
|
//
|
|
// Make sure all real heap allocations have been freed.
|
|
//
|
|
|
|
PFSVC_ASSERT(Allocator->NumHeapAllocs == 0);
|
|
|
|
//
|
|
// Make sure all allocated strings have been freed.
|
|
//
|
|
|
|
for (AllocationHeader = (PVOID) Allocator->Buffer;
|
|
(PCHAR) AllocationHeader < (PCHAR) Allocator->FreePointer;
|
|
AllocationHeader = (PVOID) NextAllocationHeader) {
|
|
|
|
Magic = *((PWCHAR)(AllocationHeader + 1));
|
|
|
|
PFSVC_ASSERT(Magic == PFSVC_STRING_ALLOCATOR_FREED_MAGIC);
|
|
|
|
//
|
|
// Calculate where the NextAllocationHeader will be.
|
|
//
|
|
|
|
NextAllocationHeader = (PCHAR) AllocationHeader +
|
|
(ULONG_PTR) AllocationHeader->AllocationSize;
|
|
}
|
|
|
|
#endif // PFSVC_DBG
|
|
|
|
//
|
|
// If the buffer was allocated by us (and not specified by
|
|
// the user), free it.
|
|
//
|
|
|
|
if (!Allocator->UserSpecifiedBuffer) {
|
|
PFSVC_FREE(Allocator->Buffer);
|
|
}
|
|
|
|
#ifdef PFSVC_DBG
|
|
|
|
//
|
|
// Setup the fields so if we try to make allocations after cleaning up
|
|
// an allocator we'll hit an assert.
|
|
//
|
|
|
|
Allocator->FreePointer = Allocator->Buffer;
|
|
Allocator->Buffer = NULL;
|
|
|
|
#endif // PFSVC_DBG
|
|
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Routines that deal with section node structures.
|
|
//
|
|
|
|
VOID
|
|
PfSvCleanupSectionNode(
|
|
PPFSVC_SCENARIO_INFO ScenarioInfo,
|
|
PPFSVC_SECTION_NODE SectionNode
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function cleans up a section node structure. It does not free
|
|
the structure itself.
|
|
|
|
Arguments:
|
|
|
|
ScenarioInfo - Pointer to scenario info.
|
|
|
|
SectionNode - Pointer to structure.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PPFSVC_PAGE_NODE PageNode;
|
|
PLIST_ENTRY ListHead;
|
|
|
|
//
|
|
// If there is an allocated file name, free it.
|
|
//
|
|
|
|
if (SectionNode->FilePath) {
|
|
PfSvStringAllocatorFree(&ScenarioInfo->PathAllocator, SectionNode->FilePath);
|
|
SectionNode->FilePath = NULL;
|
|
}
|
|
|
|
//
|
|
// Free all the page nodes for this section.
|
|
//
|
|
|
|
while (!IsListEmpty(&SectionNode->PageList)) {
|
|
|
|
ListHead = RemoveHeadList(&SectionNode->PageList);
|
|
PageNode = CONTAINING_RECORD(ListHead, PFSVC_PAGE_NODE, PageLink);
|
|
|
|
PfSvChunkAllocatorFree(&ScenarioInfo->PageNodeAllocator, PageNode);
|
|
}
|
|
|
|
//
|
|
// We should not be on a volume node's list if we are being
|
|
// cleaned up.
|
|
//
|
|
|
|
PFSVC_ASSERT(IsListEmpty(&SectionNode->SectionVolumeLink));
|
|
}
|
|
|
|
//
|
|
// Routines used to sort scenario's section nodes.
|
|
//
|
|
|
|
DWORD
|
|
PfSvSortSectionNodesByFirstAccess(
|
|
PLIST_ENTRY SectionNodeList
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine will sort the specified section node list by first
|
|
access using NewSectionIndex and OrgSectionIndex of the section
|
|
nodes.
|
|
|
|
Arguments:
|
|
|
|
SectionNodeList - Pointer to list of section nodes to be sorted.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
PFSV_SECTNODE_PRIORITY_QUEUE SortQueue;
|
|
PLIST_ENTRY SectHead;
|
|
PPFSVC_SECTION_NODE SectionNode;
|
|
DWORD ErrorCode;
|
|
|
|
//
|
|
// Initialize locals.
|
|
//
|
|
|
|
PfSvInitializeSectNodePriorityQueue(&SortQueue);
|
|
|
|
DBGPR((PFID,PFSTRC,"PFSVC: SortByFirstAccess(%p)\n", SectionNodeList));
|
|
|
|
//
|
|
// We have to sort the section nodes by first access. Remove
|
|
// section nodes from the scenario list and put them on a priority
|
|
// queue. [Bummer, it may have been a little faster if we had
|
|
// built a binary tree and traversed that in the rest of the code]
|
|
//
|
|
|
|
while (!IsListEmpty(SectionNodeList)) {
|
|
|
|
//
|
|
// The section list is sorted by name. It is more likely that
|
|
// we also accessed files by name. So to make the priority
|
|
// queue act better in such cases, remove from the tail of the
|
|
// list to insert into the priority queue.
|
|
//
|
|
|
|
SectHead = RemoveTailList(SectionNodeList);
|
|
|
|
SectionNode = CONTAINING_RECORD(SectHead,
|
|
PFSVC_SECTION_NODE,
|
|
SectionLink);
|
|
|
|
PfSvInsertSectNodePriorityQueue(&SortQueue, SectionNode);
|
|
}
|
|
|
|
//
|
|
// Remove the section nodes from the priority queue sorted by
|
|
// first access and put them to the tail of the section node list.
|
|
//
|
|
|
|
while (SectionNode = PfSvRemoveMinSectNodePriorityQueue(&SortQueue)) {
|
|
InsertTailList(SectionNodeList, &SectionNode->SectionLink);
|
|
}
|
|
|
|
ErrorCode = ERROR_SUCCESS;
|
|
|
|
DBGPR((PFID,PFSTRC,"PFSVC: SortByFirstAccess(%p)=%x\n", SectionNodeList, ErrorCode));
|
|
|
|
return ErrorCode;
|
|
}
|
|
|
|
PFSV_SECTION_NODE_COMPARISON_RESULT
|
|
FASTCALL
|
|
PfSvSectionNodeComparisonRoutine(
|
|
PPFSVC_SECTION_NODE Element1,
|
|
PPFSVC_SECTION_NODE Element2
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to compare to elements when sorting the
|
|
section nodes array by first access.
|
|
|
|
Arguments:
|
|
|
|
Element1, Element2 - The two elements to compare.
|
|
|
|
Return Value:
|
|
|
|
PFSVC_SECTION_NODE_COMPARISON_RESULT
|
|
|
|
--*/
|
|
|
|
{
|
|
//
|
|
// First compare first-access index in the new trace.
|
|
//
|
|
|
|
if (Element1->NewSectionIndex < Element2->NewSectionIndex) {
|
|
|
|
return PfSvSectNode1LessThanSectNode2;
|
|
|
|
} else if (Element1->NewSectionIndex > Element2->NewSectionIndex) {
|
|
|
|
return PfSvSectNode1GreaterThanSectNode2;
|
|
|
|
} else {
|
|
|
|
//
|
|
// Next compare first-access index in the current scenario
|
|
// file.
|
|
//
|
|
|
|
if (Element1->OrgSectionIndex < Element2->OrgSectionIndex) {
|
|
|
|
return PfSvSectNode1LessThanSectNode2;
|
|
|
|
} else if (Element1->OrgSectionIndex > Element2->OrgSectionIndex) {
|
|
|
|
return PfSvSectNode1GreaterThanSectNode2;
|
|
|
|
} else {
|
|
|
|
return PfSvSectNode1EqualToSectNode2;
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Routines that implement a priority queue used to sort section nodes
|
|
// for a scenario.
|
|
//
|
|
|
|
VOID
|
|
PfSvInitializeSectNodePriorityQueue(
|
|
PPFSV_SECTNODE_PRIORITY_QUEUE PriorityQueue
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Initialize a section node priority queue.
|
|
|
|
Arguments:
|
|
|
|
PriorityQueue - Pointer to the queue.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PriorityQueue->Head = NULL;
|
|
}
|
|
|
|
VOID
|
|
PfSvInsertSectNodePriorityQueue(
|
|
PPFSV_SECTNODE_PRIORITY_QUEUE PriorityQueue,
|
|
PPFSVC_SECTION_NODE NewElement
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Insert a section node in the a section node priority queue.
|
|
|
|
Arguments:
|
|
|
|
PriorityQueue - Pointer to the queue.
|
|
|
|
NewElement - Pointer to new element.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PPFSVC_SECTION_NODE *CurrentPosition;
|
|
|
|
//
|
|
// Initialize the link fields of NewElement.
|
|
//
|
|
|
|
NewElement->LeftChild = NULL;
|
|
NewElement->RightChild = NULL;
|
|
|
|
//
|
|
// If the queue is empty, insert this at the head.
|
|
//
|
|
|
|
if (PriorityQueue->Head == NULL) {
|
|
PriorityQueue->Head = NewElement;
|
|
return;
|
|
}
|
|
|
|
//
|
|
// If we are less than the current min element, put us at the
|
|
// head.
|
|
//
|
|
|
|
if (PfSvSectionNodeComparisonRoutine(NewElement, PriorityQueue->Head) <= 0) {
|
|
|
|
NewElement->RightChild = PriorityQueue->Head;
|
|
PriorityQueue->Head = NewElement;
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Insert this node into the tree rooted at the right child of the
|
|
// head node.
|
|
//
|
|
|
|
CurrentPosition = &PriorityQueue->Head->RightChild;
|
|
|
|
while (*CurrentPosition) {
|
|
if (PfSvSectionNodeComparisonRoutine(NewElement, *CurrentPosition) <= 0) {
|
|
CurrentPosition = &(*CurrentPosition)->LeftChild;
|
|
} else {
|
|
CurrentPosition = &(*CurrentPosition)->RightChild;
|
|
}
|
|
}
|
|
|
|
//
|
|
// We found the place.
|
|
//
|
|
|
|
*CurrentPosition = NewElement;
|
|
}
|
|
|
|
PPFSVC_SECTION_NODE
|
|
PfSvRemoveMinSectNodePriorityQueue(
|
|
PPFSV_SECTNODE_PRIORITY_QUEUE PriorityQueue
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Remove the head element of the queue.
|
|
|
|
Arguments:
|
|
|
|
PriorityQueue - Pointer to the queue.
|
|
|
|
Return Value:
|
|
|
|
Pointer to head element of the queue.
|
|
NULL if queue is empty.
|
|
|
|
--*/
|
|
|
|
{
|
|
PPFSVC_SECTION_NODE *CurrentPosition;
|
|
PPFSVC_SECTION_NODE OrgHeadNode;
|
|
PPFSVC_SECTION_NODE NewHeadNode;
|
|
PPFSVC_SECTION_NODE TreeRoot;
|
|
|
|
//
|
|
// If the queue is empty return NULL.
|
|
//
|
|
|
|
if (PriorityQueue->Head == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
//
|
|
// Save pointer to original head node.
|
|
//
|
|
|
|
OrgHeadNode = PriorityQueue->Head;
|
|
|
|
//
|
|
// Find the minimum element of the tree rooted at the right child
|
|
// of the head node. CurrentPosition points to the link of the
|
|
// parent to the smaller child.
|
|
//
|
|
|
|
TreeRoot = OrgHeadNode->RightChild;
|
|
|
|
CurrentPosition = &TreeRoot;
|
|
|
|
while (*CurrentPosition && (*CurrentPosition)->LeftChild) {
|
|
CurrentPosition = &(*CurrentPosition)->LeftChild;
|
|
}
|
|
|
|
NewHeadNode = *CurrentPosition;
|
|
|
|
//
|
|
// Check if there is really a new head node that we have to remove
|
|
// from its current position.
|
|
//
|
|
|
|
if (NewHeadNode) {
|
|
|
|
//
|
|
// We are removing this node to put it at the head. In its
|
|
// place, we'll put its right child. Since we know that this
|
|
// node does not have a left child, that's all we have to do.
|
|
//
|
|
|
|
*CurrentPosition = NewHeadNode->RightChild;
|
|
|
|
//
|
|
// Set the tree rooted at the head's right child.
|
|
//
|
|
|
|
NewHeadNode->RightChild = TreeRoot;
|
|
}
|
|
|
|
//
|
|
// Set the new head.
|
|
//
|
|
|
|
PriorityQueue->Head = NewHeadNode;
|
|
|
|
//
|
|
// Return the original head node.
|
|
//
|
|
|
|
return OrgHeadNode;
|
|
}
|
|
|
|
//
|
|
// Implementation of the Nt path to Dos path translation API.
|
|
//
|
|
|
|
DWORD
|
|
PfSvBuildNtPathTranslationList(
|
|
PNTPATH_TRANSLATION_LIST *NtPathTranslationList
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to build a list that can be used to
|
|
translate Nt paths to Dos paths. If successful, the returned list
|
|
should be freed by calling PfSvFreeNtPathTranslationList.
|
|
|
|
Arguments:
|
|
|
|
TranslationList - Pointer to where a pointer to the built
|
|
translation list is going to be put.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
DWORD ErrorCode;
|
|
ULONG VolumeNameLength;
|
|
ULONG VolumeNameMaxLength;
|
|
PWCHAR VolumeName;
|
|
ULONG NTDevicePathMaxLength;
|
|
ULONG NTDevicePathLength;
|
|
PWCHAR NTDevicePath;
|
|
HANDLE FindVolumeHandle;
|
|
ULONG RequiredLength;
|
|
ULONG VolumePathNamesLength;
|
|
WCHAR *VolumePathNames;
|
|
ULONG MountPathNameLength;
|
|
WCHAR *MountPathName;
|
|
ULONG ShortestMountPathLength;
|
|
WCHAR *ShortestMountPathName;
|
|
ULONG NumMountPoints;
|
|
ULONG NumResizes;
|
|
BOOL Result;
|
|
ULONG NumChars;
|
|
ULONG Length;
|
|
PNTPATH_TRANSLATION_LIST TranslationList;
|
|
PNTPATH_TRANSLATION_ENTRY TranslationEntry;
|
|
PNTPATH_TRANSLATION_ENTRY NextTranslationEntry;
|
|
ULONG AllocationSize;
|
|
PUCHAR DestinationPointer;
|
|
ULONG CopySize;
|
|
PLIST_ENTRY HeadEntry;
|
|
PLIST_ENTRY NextEntry;
|
|
PLIST_ENTRY InsertPosition;
|
|
BOOLEAN TrimmedTerminatingSlash;
|
|
|
|
//
|
|
// Initialize locals.
|
|
//
|
|
|
|
FindVolumeHandle = INVALID_HANDLE_VALUE;
|
|
VolumePathNames = NULL;
|
|
VolumePathNamesLength = 0;
|
|
VolumeName = NULL;
|
|
VolumeNameMaxLength = 0;
|
|
NTDevicePath = NULL;
|
|
NTDevicePathMaxLength = 0;
|
|
TranslationList = NULL;
|
|
|
|
DBGPR((PFID,PFTRC,"PFSVC: BuildTransList()\n"));
|
|
|
|
//
|
|
// Allocate intermediate buffers.
|
|
//
|
|
|
|
Length = MAX_PATH + 1;
|
|
|
|
VolumeName = PFSVC_ALLOC(Length * sizeof(WCHAR));
|
|
NTDevicePath = PFSVC_ALLOC(Length * sizeof(WCHAR));
|
|
|
|
if (!VolumeName || !NTDevicePath) {
|
|
ErrorCode = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto cleanup;
|
|
}
|
|
|
|
VolumeNameMaxLength = Length;
|
|
NTDevicePathMaxLength = Length;
|
|
|
|
|
|
//
|
|
// Allocate and initialize a translation list.
|
|
//
|
|
|
|
TranslationList = PFSVC_ALLOC(sizeof(NTPATH_TRANSLATION_LIST));
|
|
|
|
if (!TranslationList) {
|
|
ErrorCode = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto cleanup;
|
|
}
|
|
|
|
InitializeListHead(TranslationList);
|
|
|
|
//
|
|
// Start enumerating the volumes.
|
|
//
|
|
|
|
FindVolumeHandle = FindFirstVolume(VolumeName, VolumeNameMaxLength);
|
|
|
|
if (FindVolumeHandle == INVALID_HANDLE_VALUE) {
|
|
ErrorCode = GetLastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
VolumeNameLength = wcslen(VolumeName);
|
|
|
|
do {
|
|
|
|
//
|
|
// Get list of where this volume is mounted.
|
|
//
|
|
|
|
NumResizes = 0;
|
|
|
|
do {
|
|
|
|
Result = GetVolumePathNamesForVolumeName(VolumeName,
|
|
VolumePathNames,
|
|
VolumePathNamesLength,
|
|
&RequiredLength);
|
|
|
|
if (Result) {
|
|
|
|
//
|
|
// We got the mount points.
|
|
//
|
|
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Check why we failed.
|
|
//
|
|
|
|
ErrorCode = GetLastError();
|
|
|
|
if (ErrorCode != ERROR_MORE_DATA) {
|
|
|
|
//
|
|
// A real error...
|
|
//
|
|
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// We need to increase the size of our buffer. If there is
|
|
// an existing buffer, first free it.
|
|
//
|
|
|
|
if (VolumePathNames) {
|
|
PFSVC_FREE(VolumePathNames);
|
|
VolumePathNames = NULL;
|
|
VolumePathNamesLength = 0;
|
|
}
|
|
|
|
//
|
|
// Try to allocate a new buffer.
|
|
//
|
|
|
|
VolumePathNames = PFSVC_ALLOC(RequiredLength * sizeof(WCHAR));
|
|
|
|
if (!VolumePathNames) {
|
|
ErrorCode = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto cleanup;
|
|
}
|
|
|
|
VolumePathNamesLength = RequiredLength;
|
|
|
|
//
|
|
// Retry with the resized buffer but make sure we don't
|
|
// loop forever!
|
|
//
|
|
|
|
NumResizes++;
|
|
if (NumResizes > 1000) {
|
|
ErrorCode = ERROR_INVALID_FUNCTION;
|
|
goto cleanup;
|
|
}
|
|
|
|
} while (TRUE);
|
|
|
|
//
|
|
// Loop through the mount points to find the shortest one. It
|
|
// is possible that the depth of it is more.
|
|
//
|
|
|
|
MountPathName = VolumePathNames;
|
|
NumMountPoints = 0;
|
|
|
|
ShortestMountPathName = NULL;
|
|
ShortestMountPathLength = ULONG_MAX;
|
|
|
|
while (*MountPathName) {
|
|
|
|
MountPathNameLength = wcslen(MountPathName);
|
|
|
|
if (MountPathNameLength < ShortestMountPathLength) {
|
|
ShortestMountPathName = MountPathName;
|
|
ShortestMountPathLength = MountPathNameLength;
|
|
}
|
|
|
|
NumMountPoints++;
|
|
|
|
//
|
|
// Update the pointer to next mount point path.
|
|
//
|
|
|
|
MountPathName += MountPathNameLength;
|
|
}
|
|
|
|
//
|
|
// Check if we got a mount point path.
|
|
//
|
|
|
|
if (ShortestMountPathName == NULL) {
|
|
|
|
//
|
|
// Skip this volume.
|
|
//
|
|
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Remove the terminating slash if there is one.
|
|
//
|
|
|
|
if (ShortestMountPathName[ShortestMountPathLength - 1] == L'\\') {
|
|
ShortestMountPathName[ShortestMountPathLength - 1] = 0;
|
|
ShortestMountPathLength--;
|
|
}
|
|
|
|
//
|
|
// Get NT device that is the target of the volume link in
|
|
// Win32 object namespace. We get the dos device name by
|
|
// trimming the first 4 characters [i.e. \\?\] of the
|
|
// VolumeName. Also trim the \ at the very end of the volume
|
|
// name.
|
|
//
|
|
|
|
if (VolumeNameLength <= 4) {
|
|
ErrorCode = ERROR_BAD_FORMAT;
|
|
goto cleanup;
|
|
}
|
|
|
|
if (VolumeName[VolumeNameLength - 1] == L'\\') {
|
|
VolumeName[VolumeNameLength - 1] = 0;
|
|
TrimmedTerminatingSlash = TRUE;
|
|
} else {
|
|
TrimmedTerminatingSlash = FALSE;
|
|
}
|
|
|
|
NumChars = QueryDosDevice(&VolumeName[4],
|
|
NTDevicePath,
|
|
NTDevicePathMaxLength);
|
|
|
|
if (TrimmedTerminatingSlash) {
|
|
VolumeName[VolumeNameLength - 1] = L'\\';
|
|
}
|
|
|
|
if (NumChars == 0) {
|
|
ErrorCode = GetLastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// We are interested only in the current mapping.
|
|
//
|
|
|
|
NTDevicePathLength = wcslen(NTDevicePath);
|
|
|
|
if (NTDevicePathLength == 0) {
|
|
|
|
//
|
|
// Skip this volume.
|
|
//
|
|
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Remove terminating slash if there is one.
|
|
//
|
|
|
|
if (NTDevicePath[NTDevicePathLength - 1] == L'\\') {
|
|
NTDevicePath[NTDevicePathLength - 1] = 0;
|
|
NTDevicePathLength--;
|
|
}
|
|
|
|
//
|
|
// Allocate a translation entry big enough to contain both
|
|
// path names and the volume string.
|
|
//
|
|
|
|
AllocationSize = sizeof(NTPATH_TRANSLATION_ENTRY);
|
|
AllocationSize += (ShortestMountPathLength + 1) * sizeof(WCHAR);
|
|
AllocationSize += (NTDevicePathLength + 1) * sizeof(WCHAR);
|
|
AllocationSize += (VolumeNameLength + 1) * sizeof(WCHAR);
|
|
|
|
TranslationEntry = PFSVC_ALLOC(AllocationSize);
|
|
|
|
if (!TranslationEntry) {
|
|
ErrorCode = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto cleanup;
|
|
}
|
|
|
|
DestinationPointer = (PUCHAR) TranslationEntry;
|
|
DestinationPointer += sizeof(NTPATH_TRANSLATION_ENTRY);
|
|
|
|
//
|
|
// Copy the NT path name and the terminating NUL.
|
|
//
|
|
|
|
TranslationEntry->NtPrefix = (PVOID) DestinationPointer;
|
|
TranslationEntry->NtPrefixLength = NTDevicePathLength;
|
|
|
|
CopySize = (NTDevicePathLength + 1) * sizeof(WCHAR);
|
|
RtlCopyMemory(DestinationPointer, NTDevicePath, CopySize);
|
|
DestinationPointer += CopySize;
|
|
|
|
//
|
|
// Copy the DOS mount point name and the terminating NUL.
|
|
//
|
|
|
|
TranslationEntry->DosPrefix = (PVOID) DestinationPointer;
|
|
TranslationEntry->DosPrefixLength = ShortestMountPathLength;
|
|
|
|
CopySize = (ShortestMountPathLength + 1) * sizeof(WCHAR);
|
|
RtlCopyMemory(DestinationPointer, ShortestMountPathName, CopySize);
|
|
DestinationPointer += CopySize;
|
|
|
|
//
|
|
// Copy the volume name and the terminating NUL.
|
|
//
|
|
|
|
TranslationEntry->VolumeName = (PVOID) DestinationPointer;
|
|
TranslationEntry->VolumeNameLength = VolumeNameLength;
|
|
|
|
CopySize = (VolumeNameLength + 1) * sizeof(WCHAR);
|
|
RtlCopyMemory(DestinationPointer, VolumeName, CopySize);
|
|
DestinationPointer += CopySize;
|
|
|
|
//
|
|
// Find the position for this entry in the sorted translation
|
|
// list.
|
|
//
|
|
|
|
HeadEntry = TranslationList;
|
|
NextEntry = HeadEntry->Flink;
|
|
InsertPosition = HeadEntry;
|
|
|
|
while (NextEntry != HeadEntry) {
|
|
|
|
NextTranslationEntry = CONTAINING_RECORD(NextEntry,
|
|
NTPATH_TRANSLATION_ENTRY,
|
|
Link);
|
|
|
|
if (_wcsicmp(TranslationEntry->NtPrefix,
|
|
NextTranslationEntry->NtPrefix) <= 0) {
|
|
break;
|
|
}
|
|
|
|
InsertPosition = NextEntry;
|
|
NextEntry = NextEntry->Flink;
|
|
}
|
|
|
|
//
|
|
// Insert it after the found position.
|
|
//
|
|
|
|
InsertHeadList(InsertPosition, &TranslationEntry->Link);
|
|
|
|
} while (FindNextVolume(FindVolumeHandle, VolumeName, VolumeNameMaxLength));
|
|
|
|
//
|
|
// We will break out of the loop when FindNextVolume does not
|
|
// return success. Check if it failed for a reason other than that
|
|
// we have enumerated all volumes.
|
|
//
|
|
|
|
ErrorCode = GetLastError();
|
|
|
|
if (ErrorCode != ERROR_NO_MORE_FILES) {
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Set return value.
|
|
//
|
|
|
|
*NtPathTranslationList = TranslationList;
|
|
|
|
ErrorCode = ERROR_SUCCESS;
|
|
|
|
cleanup:
|
|
|
|
if (FindVolumeHandle != INVALID_HANDLE_VALUE) {
|
|
FindVolumeClose(FindVolumeHandle);
|
|
}
|
|
|
|
if (VolumePathNames) {
|
|
PFSVC_FREE(VolumePathNames);
|
|
}
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
if (TranslationList) {
|
|
PfSvFreeNtPathTranslationList(TranslationList);
|
|
}
|
|
}
|
|
|
|
if (VolumeName) {
|
|
PFSVC_FREE(VolumeName);
|
|
}
|
|
|
|
if (NTDevicePath) {
|
|
PFSVC_FREE(NTDevicePath);
|
|
}
|
|
|
|
DBGPR((PFID,PFTRC,"PFSVC: BuildTransList()=%x,%p\n", ErrorCode, TranslationList));
|
|
|
|
return ErrorCode;
|
|
}
|
|
|
|
VOID
|
|
PfSvFreeNtPathTranslationList(
|
|
PNTPATH_TRANSLATION_LIST TranslationList
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to free a translation list returned by
|
|
PfSvBuildNtPathTranslationList.
|
|
|
|
Arguments:
|
|
|
|
TranslationList - Pointer to list to free.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PLIST_ENTRY HeadEntry;
|
|
PNTPATH_TRANSLATION_ENTRY TranslationEntry;
|
|
|
|
DBGPR((PFID,PFTRC,"PFSVC: FreeTransList(%p)\n", TranslationList));
|
|
|
|
//
|
|
// Free all entries in the list.
|
|
//
|
|
|
|
while (!IsListEmpty(TranslationList)) {
|
|
|
|
HeadEntry = RemoveHeadList(TranslationList);
|
|
|
|
TranslationEntry = CONTAINING_RECORD(HeadEntry,
|
|
NTPATH_TRANSLATION_ENTRY,
|
|
Link);
|
|
|
|
PFSVC_FREE(TranslationEntry);
|
|
}
|
|
|
|
//
|
|
// Free the list itself.
|
|
//
|
|
|
|
PFSVC_FREE(TranslationList);
|
|
}
|
|
|
|
DWORD
|
|
PfSvTranslateNtPath(
|
|
PNTPATH_TRANSLATION_LIST TranslationList,
|
|
WCHAR *NtPath,
|
|
ULONG NtPathLength,
|
|
PWCHAR *DosPathBuffer,
|
|
PULONG DosPathBufferSize
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to free a translation list returned by
|
|
PfSvBuildNtPathTranslationList. Note that it may not be possible to
|
|
translate all Nt path's to a Dos path.
|
|
|
|
Arguments:
|
|
|
|
TranslationList - Pointer to list built by PfSvBuildNtPathTranslationList.
|
|
|
|
NtPath - Path to translate.
|
|
|
|
NtPathLength - Length of NtPath in characters excluding terminating NUL.
|
|
|
|
DosPathBuffer - Buffer to put the translation into. If it is NULL
|
|
or not big enough it will get reallocated. If a buffer is passed
|
|
in, it should be allocated by PFSVC_ALLOC. It is the callers
|
|
responsibility to free the buffer with PFSVC_FREE when done.
|
|
|
|
DosPathBufferSize - Size of DosPathBuffer in bytes. Updated if the
|
|
buffer is reallocated.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
DWORD ErrorCode;
|
|
PLIST_ENTRY HeadEntry;
|
|
PLIST_ENTRY NextEntry;
|
|
PNTPATH_TRANSLATION_ENTRY CurrentTranslationEntry;
|
|
PNTPATH_TRANSLATION_ENTRY FoundTranslationEntry;
|
|
PFSV_PREFIX_COMPARISON_RESULT ComparisonResult;
|
|
ULONG RequiredSize;
|
|
|
|
//
|
|
// Initialize locals.
|
|
//
|
|
|
|
FoundTranslationEntry = NULL;
|
|
|
|
DBGPR((PFID,PFPATH,"PFSVC: TranslateNtPath(%ws)\n", NtPath));
|
|
|
|
//
|
|
// Walk through the sorted translation list to find an entry that
|
|
// applies.
|
|
//
|
|
|
|
HeadEntry = TranslationList;
|
|
NextEntry = HeadEntry->Flink;
|
|
|
|
while (NextEntry != HeadEntry) {
|
|
|
|
CurrentTranslationEntry = CONTAINING_RECORD(NextEntry,
|
|
NTPATH_TRANSLATION_ENTRY,
|
|
Link);
|
|
|
|
//
|
|
// Do a case insensitive comparison.
|
|
//
|
|
|
|
ComparisonResult = PfSvComparePrefix(NtPath,
|
|
NtPathLength,
|
|
CurrentTranslationEntry->NtPrefix,
|
|
CurrentTranslationEntry->NtPrefixLength,
|
|
FALSE);
|
|
|
|
if (ComparisonResult == PfSvPrefixIdentical) {
|
|
|
|
//
|
|
// Check to see if the character in NtPath after the
|
|
// prefix is a path seperator [i.e. '\']. Otherwise we may
|
|
// match \Device\CdRom10\DirName\FileName to \Device\Cdrom1.
|
|
//
|
|
|
|
if (NtPathLength == CurrentTranslationEntry->NtPrefixLength ||
|
|
NtPath[CurrentTranslationEntry->NtPrefixLength] == L'\\') {
|
|
|
|
//
|
|
// We found a translation entry that applies to us.
|
|
//
|
|
|
|
FoundTranslationEntry = CurrentTranslationEntry;
|
|
break;
|
|
}
|
|
|
|
} else if (ComparisonResult == PfSvPrefixGreaterThan) {
|
|
|
|
//
|
|
// Since the translation list is sorted in increasing
|
|
// order, following entries will also be greater than
|
|
// NtPath.
|
|
//
|
|
|
|
FoundTranslationEntry = NULL;
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Continue looking for a matching prefix.
|
|
//
|
|
|
|
NextEntry = NextEntry->Flink;
|
|
}
|
|
|
|
//
|
|
// If we could not find an entry that applies we cannot translate
|
|
// the path.
|
|
//
|
|
|
|
if (FoundTranslationEntry == NULL) {
|
|
ErrorCode = ERROR_PATH_NOT_FOUND;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Calculate required size: We will replace the NtPrefix with
|
|
// DosPrefix. Don't forget the terminating NUL character.
|
|
//
|
|
|
|
RequiredSize = (NtPathLength + 1) * sizeof(WCHAR);
|
|
RequiredSize += (FoundTranslationEntry->DosPrefixLength * sizeof(WCHAR));
|
|
RequiredSize -= (FoundTranslationEntry->NtPrefixLength * sizeof(WCHAR));
|
|
|
|
if (RequiredSize > (*DosPathBufferSize)) {
|
|
|
|
//
|
|
// Reallocate the buffer. First free it if there is one.
|
|
//
|
|
|
|
if (*DosPathBufferSize) {
|
|
PFSVC_ASSERT(*DosPathBuffer);
|
|
PFSVC_FREE(*DosPathBuffer);
|
|
(*DosPathBuffer) = NULL;
|
|
(*DosPathBufferSize) = 0;
|
|
}
|
|
|
|
PFSVC_ASSERT((*DosPathBuffer) == NULL);
|
|
|
|
(*DosPathBuffer) = PFSVC_ALLOC(RequiredSize);
|
|
|
|
if (!(*DosPathBuffer)) {
|
|
ErrorCode = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto cleanup;
|
|
}
|
|
|
|
(*DosPathBufferSize) = RequiredSize;
|
|
}
|
|
|
|
//
|
|
// We should have enough room now.
|
|
//
|
|
|
|
PFSVC_ASSERT(RequiredSize <= (*DosPathBufferSize));
|
|
|
|
//
|
|
// Copy the DosPrefix.
|
|
//
|
|
|
|
wcscpy((*DosPathBuffer), FoundTranslationEntry->DosPrefix);
|
|
|
|
//
|
|
// Concatenate the remaining path.
|
|
//
|
|
|
|
wcscat((*DosPathBuffer), NtPath + CurrentTranslationEntry->NtPrefixLength);
|
|
|
|
//
|
|
// We are done.
|
|
//
|
|
|
|
ErrorCode = ERROR_SUCCESS;
|
|
|
|
cleanup:
|
|
|
|
DBGPR((PFID,PFPATH,"PFSVC: TranslateNtPath(%ws)=%x,%ws\n",
|
|
NtPath,ErrorCode,(ErrorCode==ERROR_SUCCESS)?(*DosPathBuffer):L"Failed"));
|
|
|
|
return ErrorCode;
|
|
}
|
|
|
|
//
|
|
// Implementation of the path list API.
|
|
//
|
|
|
|
VOID
|
|
PfSvInitializePathList(
|
|
PPFSVC_PATH_LIST PathList,
|
|
OPTIONAL IN PPFSVC_STRING_ALLOCATOR PathAllocator,
|
|
IN BOOLEAN CaseSensitive
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function initializes a path list structure.
|
|
|
|
Arguments:
|
|
|
|
PathList - Pointer to structure.
|
|
|
|
PathAllocator - If specified path allocations will be made from it.
|
|
|
|
CaseSenstive - Whether list will be case senstive.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
InitializeListHead(&PathList->InOrderList);
|
|
InitializeListHead(&PathList->SortedList);
|
|
PathList->NumPaths = 0;
|
|
PathList->TotalLength = 0;
|
|
PathList->Allocator = PathAllocator;
|
|
PathList->CaseSensitive = CaseSensitive;
|
|
}
|
|
|
|
VOID
|
|
PfSvCleanupPathList(
|
|
PPFSVC_PATH_LIST PathList
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function cleans up a path list structure. It does not free
|
|
the structure itself. The structure should have been initialized
|
|
by PfSvInitializePathList.
|
|
|
|
Arguments:
|
|
|
|
PathList - Pointer to structure.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PLIST_ENTRY ListEntry;
|
|
PPFSVC_PATH Path;
|
|
|
|
while (!IsListEmpty(&PathList->InOrderList)) {
|
|
|
|
PFSVC_ASSERT(PathList->NumPaths);
|
|
PathList->NumPaths--;
|
|
|
|
ListEntry = RemoveHeadList(&PathList->InOrderList);
|
|
Path = CONTAINING_RECORD(ListEntry,
|
|
PFSVC_PATH,
|
|
InOrderLink);
|
|
|
|
if (PathList->Allocator) {
|
|
PfSvStringAllocatorFree(PathList->Allocator, Path);
|
|
} else {
|
|
PFSVC_FREE(Path);
|
|
}
|
|
}
|
|
}
|
|
|
|
BOOLEAN
|
|
PfSvIsInPathList(
|
|
PPFSVC_PATH_LIST PathList,
|
|
WCHAR *Path,
|
|
ULONG PathLength
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function checks if the specified path is already in the path
|
|
list.
|
|
|
|
Arguments:
|
|
|
|
PathList - Pointer to list.
|
|
|
|
Path - Path to look for. Does not have to be NUL terminated.
|
|
|
|
PathLength - Length of Path in characters excluding NUL if there
|
|
is one.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
PLIST_ENTRY HeadEntry;
|
|
PLIST_ENTRY NextEntry;
|
|
PPFSVC_PATH PathEntry;
|
|
INT ComparisonResult;
|
|
BOOLEAN PathIsInPathList;
|
|
|
|
//
|
|
// Walk through the list.
|
|
//
|
|
|
|
HeadEntry = &PathList->SortedList;
|
|
NextEntry = HeadEntry->Flink;
|
|
|
|
while (NextEntry != HeadEntry) {
|
|
|
|
PathEntry = CONTAINING_RECORD(NextEntry,
|
|
PFSVC_PATH,
|
|
SortedLink);
|
|
|
|
if (PathList->CaseSensitive) {
|
|
ComparisonResult = wcsncmp(Path,
|
|
PathEntry->Path,
|
|
PathLength);
|
|
} else {
|
|
ComparisonResult = _wcsnicmp(Path,
|
|
PathEntry->Path,
|
|
PathLength);
|
|
}
|
|
|
|
//
|
|
// Adjust comparison result so we don't match "abcde" to
|
|
// "abcdefg". If string comparison says the first PathLength
|
|
// characters match, check to see if PathEntry's length is
|
|
// longer, which would make it "greater" than the new path.
|
|
//
|
|
|
|
if (ComparisonResult == 0 && PathEntry->Length != PathLength) {
|
|
|
|
//
|
|
// The string comparison would not say The path entry's
|
|
// path is equal to path if its length was smaller.
|
|
//
|
|
|
|
PFSVC_ASSERT(PathEntry->Length > PathLength);
|
|
|
|
//
|
|
// Path is actually less than this path entry.
|
|
//
|
|
|
|
ComparisonResult = -1;
|
|
}
|
|
|
|
//
|
|
// Based on comparison result determine what to do:
|
|
//
|
|
|
|
if (ComparisonResult == 0) {
|
|
|
|
//
|
|
// We found it.
|
|
//
|
|
|
|
PathIsInPathList = TRUE;
|
|
goto cleanup;
|
|
|
|
} else if (ComparisonResult < 0) {
|
|
|
|
//
|
|
// We will be less than the rest of the strings in the
|
|
// list after this too.
|
|
//
|
|
|
|
PathIsInPathList = FALSE;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Continue looking for the path or an available position.
|
|
//
|
|
|
|
NextEntry = NextEntry->Flink;
|
|
}
|
|
|
|
//
|
|
// If we came here, we could not find the path in the list.
|
|
//
|
|
|
|
PathIsInPathList = FALSE;
|
|
|
|
cleanup:
|
|
|
|
return PathIsInPathList;
|
|
}
|
|
|
|
DWORD
|
|
PfSvAddToPathList(
|
|
PPFSVC_PATH_LIST PathList,
|
|
WCHAR *Path,
|
|
ULONG PathLength
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function adds a path to a path list. If the path already
|
|
exists in the list, it is not added again.
|
|
|
|
Arguments:
|
|
|
|
PathList - Pointer to list.
|
|
|
|
Path - Path to add. Does not need to be NUL terminated.
|
|
|
|
PathLength - Length of Path in characters excluding NUL if there
|
|
is one.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
DWORD ErrorCode;
|
|
PLIST_ENTRY HeadEntry;
|
|
PLIST_ENTRY NextEntry;
|
|
PPFSVC_PATH PathEntry;
|
|
PPFSVC_PATH NewPathEntry;
|
|
INT ComparisonResult;
|
|
ULONG AllocationSize;
|
|
|
|
//
|
|
// Initialize locals.
|
|
//
|
|
|
|
NewPathEntry = NULL;
|
|
|
|
//
|
|
// Walk through the list to check if path is already in the list,
|
|
// or to find where it should be so we can insert it there.
|
|
//
|
|
|
|
HeadEntry = &PathList->SortedList;
|
|
NextEntry = HeadEntry->Flink;
|
|
|
|
while (NextEntry != HeadEntry) {
|
|
|
|
PathEntry = CONTAINING_RECORD(NextEntry,
|
|
PFSVC_PATH,
|
|
SortedLink);
|
|
|
|
if (PathList->CaseSensitive) {
|
|
ComparisonResult = wcsncmp(Path,
|
|
PathEntry->Path,
|
|
PathLength);
|
|
} else {
|
|
ComparisonResult = _wcsnicmp(Path,
|
|
PathEntry->Path,
|
|
PathLength);
|
|
}
|
|
|
|
//
|
|
// Adjust comparison result so we don't match "abcde" to
|
|
// "abcdefg". If string comparison says the first PathLength
|
|
// characters match, check to see if PathEntry's length is
|
|
// longer, which would make it "greater" than the new path.
|
|
//
|
|
|
|
if (ComparisonResult == 0 && PathEntry->Length != PathLength) {
|
|
|
|
//
|
|
// The string comparison would not say The path entry's
|
|
// path is equal to path if its length was smaller.
|
|
//
|
|
|
|
PFSVC_ASSERT(PathEntry->Length > PathLength);
|
|
|
|
//
|
|
// Path is actually less than this path entry.
|
|
//
|
|
|
|
ComparisonResult = -1;
|
|
}
|
|
|
|
//
|
|
// Based on comparison result determine what to do:
|
|
//
|
|
|
|
if (ComparisonResult == 0) {
|
|
|
|
//
|
|
// The path already exists in the list.
|
|
//
|
|
|
|
ErrorCode = ERROR_SUCCESS;
|
|
goto cleanup;
|
|
|
|
} else if (ComparisonResult < 0) {
|
|
|
|
//
|
|
// We will be less than the rest of the strings in the
|
|
// list after this too. We should be inserted before this
|
|
// one.
|
|
//
|
|
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Continue looking for the path or an available position.
|
|
//
|
|
|
|
NextEntry = NextEntry->Flink;
|
|
}
|
|
|
|
//
|
|
// We will insert the path before NextEntry. First create an entry
|
|
// we can insert.
|
|
//
|
|
|
|
AllocationSize = sizeof(PFSVC_PATH);
|
|
AllocationSize += PathLength * sizeof(WCHAR);
|
|
|
|
//
|
|
// Note that PFSVC_PATH already contains space for the terminating
|
|
// NUL character.
|
|
//
|
|
|
|
if (PathList->Allocator) {
|
|
NewPathEntry = PfSvStringAllocatorAllocate(PathList->Allocator, AllocationSize);
|
|
} else {
|
|
NewPathEntry = PFSVC_ALLOC(AllocationSize);
|
|
}
|
|
|
|
if (!NewPathEntry) {
|
|
ErrorCode = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Copy path and terminate it.
|
|
//
|
|
|
|
NewPathEntry->Length = PathLength;
|
|
RtlCopyMemory(NewPathEntry->Path,
|
|
Path,
|
|
PathLength * sizeof(WCHAR));
|
|
|
|
NewPathEntry->Path[PathLength] = 0;
|
|
|
|
//
|
|
// Insert it into the sorted list before the current entry.
|
|
//
|
|
|
|
InsertTailList(NextEntry, &NewPathEntry->SortedLink);
|
|
|
|
//
|
|
// Insert it at the end of in-order list.
|
|
//
|
|
|
|
InsertTailList(&PathList->InOrderList, &NewPathEntry->InOrderLink);
|
|
|
|
PathList->NumPaths++;
|
|
PathList->TotalLength += NewPathEntry->Length;
|
|
|
|
ErrorCode = ERROR_SUCCESS;
|
|
|
|
cleanup:
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
if (NewPathEntry) {
|
|
if (PathList->Allocator) {
|
|
PfSvStringAllocatorFree(PathList->Allocator, NewPathEntry);
|
|
} else {
|
|
PFSVC_FREE(NewPathEntry);
|
|
}
|
|
}
|
|
}
|
|
|
|
return ErrorCode;
|
|
}
|
|
|
|
PPFSVC_PATH
|
|
PfSvGetNextPathSorted (
|
|
PPFSVC_PATH_LIST PathList,
|
|
PPFSVC_PATH CurrentPath
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function is used to walk through paths in a path list in
|
|
lexically (case insensitive) sorted order.
|
|
|
|
Arguments:
|
|
|
|
PathList - Pointer to list.
|
|
|
|
CurrentPath - The current path entry. The function will return the
|
|
next entry in the list. If this is NULL, the first entry in the
|
|
list is returned.
|
|
|
|
Return Value:
|
|
|
|
NULL - There are no more entries in the list.
|
|
|
|
or Pointer to next path in the list.
|
|
|
|
--*/
|
|
|
|
{
|
|
PLIST_ENTRY EndOfList;
|
|
PLIST_ENTRY NextEntry;
|
|
PPFSVC_PATH NextPath;
|
|
|
|
//
|
|
// Initialize locals.
|
|
//
|
|
|
|
EndOfList = &PathList->SortedList;
|
|
|
|
//
|
|
// Determine NextEntry based on whether CurrentPath is specified.
|
|
//
|
|
|
|
if (CurrentPath) {
|
|
NextEntry = CurrentPath->SortedLink.Flink;
|
|
} else {
|
|
NextEntry = PathList->SortedList.Flink;
|
|
}
|
|
|
|
//
|
|
// Check if the NextEntry points to the end of list.
|
|
//
|
|
|
|
if (NextEntry == EndOfList) {
|
|
NextPath = NULL;
|
|
} else {
|
|
NextPath = CONTAINING_RECORD(NextEntry,
|
|
PFSVC_PATH,
|
|
SortedLink);
|
|
}
|
|
|
|
return NextPath;
|
|
}
|
|
|
|
PPFSVC_PATH
|
|
PfSvGetNextPathInOrder (
|
|
PPFSVC_PATH_LIST PathList,
|
|
PPFSVC_PATH CurrentPath
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function is used to walk through paths in a path list in
|
|
the order they were inserted into the list.
|
|
|
|
Arguments:
|
|
|
|
PathList - Pointer to list.
|
|
|
|
CurrentPath - The current path entry. The function will return the
|
|
next entry in the list. If this is NULL, the first entry in the
|
|
list is returned.
|
|
|
|
Return Value:
|
|
|
|
NULL - There are no more entries in the list.
|
|
|
|
or Pointer to next path in the list.
|
|
|
|
--*/
|
|
|
|
{
|
|
PLIST_ENTRY EndOfList;
|
|
PLIST_ENTRY NextEntry;
|
|
PPFSVC_PATH NextPath;
|
|
|
|
//
|
|
// Initialize locals.
|
|
//
|
|
|
|
EndOfList = &PathList->InOrderList;
|
|
|
|
//
|
|
// Determine NextEntry based on whether CurrentPath is specified.
|
|
//
|
|
|
|
if (CurrentPath) {
|
|
NextEntry = CurrentPath->InOrderLink.Flink;
|
|
} else {
|
|
NextEntry = PathList->InOrderList.Flink;
|
|
}
|
|
|
|
//
|
|
// Check if the NextEntry points to the end of list.
|
|
//
|
|
|
|
if (NextEntry == EndOfList) {
|
|
NextPath = NULL;
|
|
} else {
|
|
NextPath = CONTAINING_RECORD(NextEntry,
|
|
PFSVC_PATH,
|
|
InOrderLink);
|
|
}
|
|
|
|
return NextPath;
|
|
}
|
|
|
|
//
|
|
// Routines to build the list of files accessed by the boot loader.
|
|
//
|
|
|
|
DWORD
|
|
PfSvBuildBootLoaderFilesList (
|
|
PPFSVC_PATH_LIST PathList
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function attempts to add the list of files loaded in the boot
|
|
loader to the specified file list.
|
|
|
|
Arguments:
|
|
|
|
PathList - Pointer to initialized list.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
DWORD ErrorCode;
|
|
SC_HANDLE ScHandle;
|
|
SC_HANDLE ServiceHandle;
|
|
LPENUM_SERVICE_STATUS_PROCESS EnumBuffer;
|
|
LPENUM_SERVICE_STATUS_PROCESS ServiceInfo;
|
|
ULONG EnumBufferMaxSize;
|
|
ULONG NumResizes;
|
|
BOOL Result;
|
|
ULONG RequiredAdditionalSize;
|
|
ULONG RequiredSize;
|
|
ULONG NumServicesEnumerated;
|
|
ULONG ResumeHandle;
|
|
ULONG ServiceIdx;
|
|
LPQUERY_SERVICE_CONFIG ServiceConfigBuffer;
|
|
ULONG ServiceConfigBufferMaxSize;
|
|
WCHAR FilePath[MAX_PATH + 1];
|
|
ULONG FilePathLength;
|
|
ULONG SystemDirLength;
|
|
ULONG RequiredLength;
|
|
WCHAR *KernelName;
|
|
WCHAR *HalName;
|
|
WCHAR *SystemHive;
|
|
WCHAR *SoftwareHive;
|
|
|
|
//
|
|
// Initialize locals.
|
|
//
|
|
|
|
ScHandle = NULL;
|
|
EnumBuffer = NULL;
|
|
EnumBufferMaxSize = 0;
|
|
NumServicesEnumerated = 0;
|
|
ServiceConfigBuffer = 0;
|
|
ServiceConfigBufferMaxSize = 0;
|
|
KernelName = L"ntoskrnl.exe";
|
|
HalName = L"hal.dll";
|
|
SystemHive = L"config\\system";
|
|
SoftwareHive = L"config\\software";
|
|
|
|
//
|
|
// Add kernel & hal to known files list:
|
|
//
|
|
|
|
//
|
|
// Get path to system directory.
|
|
//
|
|
|
|
SystemDirLength = GetSystemDirectory(FilePath, MAX_PATH);
|
|
|
|
if (!SystemDirLength) {
|
|
ErrorCode = GetLastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Append a trailing \.
|
|
//
|
|
|
|
if (SystemDirLength + 1 < MAX_PATH) {
|
|
FilePath[SystemDirLength] = '\\';
|
|
SystemDirLength++;
|
|
FilePath[SystemDirLength] = 0;
|
|
} else {
|
|
ErrorCode = ERROR_INSUFFICIENT_BUFFER;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Append kernel name and add it to the list.
|
|
//
|
|
|
|
FilePathLength = SystemDirLength;
|
|
FilePathLength += wcslen(KernelName);
|
|
|
|
if (FilePathLength < MAX_PATH) {
|
|
wcscat(FilePath, KernelName);
|
|
ErrorCode = PfSvAddBootImageAndImportsToList(PathList,
|
|
FilePath,
|
|
FilePathLength);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
} else {
|
|
ErrorCode = ERROR_INSUFFICIENT_BUFFER;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Roll FilePath back to system directory. Append hal name and add
|
|
// it to the list.
|
|
//
|
|
|
|
FilePathLength = SystemDirLength;
|
|
FilePathLength += wcslen(HalName);
|
|
|
|
if (FilePathLength < MAX_PATH) {
|
|
FilePath[SystemDirLength] = 0;
|
|
wcscat(FilePath, HalName);
|
|
ErrorCode = PfSvAddBootImageAndImportsToList(PathList,
|
|
FilePath,
|
|
FilePathLength);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
} else {
|
|
ErrorCode = ERROR_INSUFFICIENT_BUFFER;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Roll FilePath back to system directory. Append system hive path
|
|
// and add it to the list.
|
|
//
|
|
|
|
FilePathLength = SystemDirLength;
|
|
FilePathLength += wcslen(SystemHive);
|
|
|
|
if (FilePathLength < MAX_PATH) {
|
|
FilePath[SystemDirLength] = 0;
|
|
wcscat(FilePath, SystemHive);
|
|
|
|
ErrorCode = PfSvAddToPathList(PathList,
|
|
FilePath,
|
|
FilePathLength);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
} else {
|
|
ErrorCode = ERROR_INSUFFICIENT_BUFFER;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Note that we will use FilePath & FilePathLength to add the
|
|
// software hive after we add all the other boot loader files. The
|
|
// software hive is not accessed in the boot loader, but during
|
|
// boot. It is not put into the boot scenario file, however. We
|
|
// don't want to mix it in with the boot loader files, so we don't
|
|
// hurt the boot loader performance.
|
|
//
|
|
|
|
//
|
|
// Add file paths for NLS data & fonts loaded by the boot loader.
|
|
//
|
|
|
|
PfSvGetBootLoaderNlsFileNames(PathList);
|
|
|
|
//
|
|
// Open service controller.
|
|
//
|
|
|
|
ScHandle = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
|
|
|
|
if (ScHandle == NULL) {
|
|
ErrorCode = GetLastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Get the list of boot services we are interested in.
|
|
//
|
|
|
|
NumResizes = 0;
|
|
|
|
do {
|
|
|
|
//
|
|
// We want to get all of services at one call.
|
|
//
|
|
|
|
ResumeHandle = 0;
|
|
|
|
Result = EnumServicesStatusEx (ScHandle,
|
|
SC_ENUM_PROCESS_INFO,
|
|
SERVICE_DRIVER,
|
|
SERVICE_ACTIVE,
|
|
(PVOID)EnumBuffer,
|
|
EnumBufferMaxSize,
|
|
&RequiredAdditionalSize,
|
|
&NumServicesEnumerated,
|
|
&ResumeHandle,
|
|
NULL);
|
|
|
|
if (Result) {
|
|
|
|
//
|
|
// We got it.
|
|
//
|
|
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Check why our call failed.
|
|
//
|
|
|
|
ErrorCode = GetLastError();
|
|
|
|
//
|
|
// If we failed for some other reason than that our buffer was
|
|
// too small, we cannot go on.
|
|
//
|
|
|
|
if (ErrorCode != ERROR_MORE_DATA) {
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Free the old buffer if it exists, and allocate a bigger one.
|
|
//
|
|
|
|
RequiredSize = EnumBufferMaxSize + RequiredAdditionalSize;
|
|
|
|
if (EnumBuffer) {
|
|
PFSVC_FREE(EnumBuffer);
|
|
EnumBuffer = NULL;
|
|
EnumBufferMaxSize = 0;
|
|
}
|
|
|
|
EnumBuffer = PFSVC_ALLOC(RequiredSize);
|
|
|
|
if (EnumBuffer == NULL) {
|
|
ErrorCode = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto cleanup;
|
|
}
|
|
|
|
EnumBufferMaxSize = RequiredSize;
|
|
|
|
//
|
|
// Make sure we don't loop for ever.
|
|
//
|
|
|
|
NumResizes++;
|
|
if (NumResizes > 100) {
|
|
ErrorCode = ERROR_INVALID_FUNCTION;
|
|
goto cleanup;
|
|
}
|
|
|
|
} while (TRUE);
|
|
|
|
//
|
|
// Identify the enumerated services that may be loaded by the boot
|
|
// loader.
|
|
//
|
|
|
|
for (ServiceIdx = 0; ServiceIdx < NumServicesEnumerated; ServiceIdx++) {
|
|
|
|
ServiceInfo = &EnumBuffer[ServiceIdx];
|
|
|
|
//
|
|
// Open the service to get its configuration info.
|
|
//
|
|
|
|
ServiceHandle = OpenService(ScHandle,
|
|
ServiceInfo->lpServiceName,
|
|
SERVICE_QUERY_CONFIG);
|
|
|
|
if (ServiceHandle == NULL) {
|
|
ErrorCode = GetLastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Query service configuration.
|
|
//
|
|
|
|
NumResizes = 0;
|
|
|
|
do {
|
|
|
|
Result = QueryServiceConfig(ServiceHandle,
|
|
ServiceConfigBuffer,
|
|
ServiceConfigBufferMaxSize,
|
|
&RequiredSize);
|
|
|
|
if (Result) {
|
|
|
|
//
|
|
// We got it.
|
|
//
|
|
|
|
break;
|
|
}
|
|
|
|
ErrorCode = GetLastError();
|
|
|
|
if (ErrorCode != ERROR_INSUFFICIENT_BUFFER) {
|
|
|
|
//
|
|
// This is a real error.
|
|
//
|
|
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Resize the buffer and try again.
|
|
//
|
|
|
|
if (ServiceConfigBuffer) {
|
|
PFSVC_FREE(ServiceConfigBuffer);
|
|
ServiceConfigBuffer = NULL;
|
|
ServiceConfigBufferMaxSize = 0;
|
|
}
|
|
|
|
ServiceConfigBuffer = PFSVC_ALLOC(RequiredSize);
|
|
|
|
if (ServiceConfigBuffer == NULL) {
|
|
ErrorCode = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto cleanup;
|
|
}
|
|
|
|
ServiceConfigBufferMaxSize = RequiredSize;
|
|
|
|
//
|
|
// Make sure we don't loop forever.
|
|
//
|
|
|
|
NumResizes++;
|
|
if (NumResizes > 100) {
|
|
ErrorCode = ERROR_INVALID_FUNCTION;
|
|
goto cleanup;
|
|
}
|
|
|
|
} while (TRUE);
|
|
|
|
//
|
|
// We are interested in this service only if it starts as a
|
|
// boot driver or if it is a file system.
|
|
//
|
|
|
|
if (ServiceConfigBuffer->dwStartType == SERVICE_BOOT_START ||
|
|
ServiceConfigBuffer->dwServiceType == SERVICE_FILE_SYSTEM_DRIVER) {
|
|
|
|
//
|
|
// Try to locate the real service binary path.
|
|
//
|
|
|
|
ErrorCode = PfSvGetBootServiceFullPath(ServiceInfo->lpServiceName,
|
|
ServiceConfigBuffer->lpBinaryPathName,
|
|
FilePath,
|
|
MAX_PATH,
|
|
&RequiredLength);
|
|
|
|
if (ErrorCode == ERROR_SUCCESS) {
|
|
PfSvAddBootImageAndImportsToList(PathList,
|
|
FilePath,
|
|
wcslen(FilePath));
|
|
}
|
|
}
|
|
|
|
//
|
|
// Close the handle and continue.
|
|
//
|
|
|
|
CloseServiceHandle(ServiceHandle);
|
|
}
|
|
|
|
//
|
|
// Roll FilePath back to system directory. Append software hive path
|
|
// and add it to the list.
|
|
//
|
|
|
|
FilePathLength = SystemDirLength;
|
|
FilePathLength += wcslen(SoftwareHive);
|
|
|
|
if (FilePathLength < MAX_PATH) {
|
|
FilePath[SystemDirLength] = 0;
|
|
wcscat(FilePath, SoftwareHive);
|
|
|
|
ErrorCode = PfSvAddToPathList(PathList,
|
|
FilePath,
|
|
FilePathLength);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
} else {
|
|
ErrorCode = ERROR_INSUFFICIENT_BUFFER;
|
|
goto cleanup;
|
|
}
|
|
|
|
ErrorCode = ERROR_SUCCESS;
|
|
|
|
cleanup:
|
|
|
|
if (ScHandle) {
|
|
CloseServiceHandle(ScHandle);
|
|
}
|
|
|
|
if (EnumBuffer) {
|
|
PFSVC_FREE(EnumBuffer);
|
|
}
|
|
|
|
if (ServiceConfigBuffer) {
|
|
PFSVC_FREE(ServiceConfigBuffer);
|
|
}
|
|
|
|
return ErrorCode;
|
|
}
|
|
|
|
DWORD
|
|
PfSvAddBootImageAndImportsToList(
|
|
PPFSVC_PATH_LIST PathList,
|
|
WCHAR *FilePath,
|
|
ULONG FilePathLength
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function attempts to add the image file whose fully qualified
|
|
path is in FilePath as well as the modules it imports from to the
|
|
file list, if those modules can be located.
|
|
|
|
NOTE: Ntoskrnl.exe and Hal.dll are special cased out and not added
|
|
to the file list, since most drivers will import from them. They
|
|
can be added to the list seperately. Also note that the file list
|
|
is not checked for duplicates when adding new entries.
|
|
|
|
Arguments:
|
|
|
|
PathList - Pointer to list.
|
|
|
|
FilePath - Fully qualified path of an image file.
|
|
|
|
FilePathLength - Length of the file path in characters excluding NUL.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
DWORD ErrorCode;
|
|
ULONG MaxNumImports;
|
|
ULONG NumImports;
|
|
WCHAR **ImportNames;
|
|
ULONG ImportIdx;
|
|
ULONG BufferSize;
|
|
WCHAR *FileName;
|
|
WCHAR ParentDir[MAX_PATH + 1];
|
|
ULONG ParentDirLength;
|
|
WCHAR *ImportName;
|
|
ULONG ImportNameLength;
|
|
WCHAR ImportPath[MAX_PATH + 1];
|
|
PUCHAR ImportBase;
|
|
ULONG RequiredLength;
|
|
ULONG FileSize;
|
|
PIMAGE_IMPORT_DESCRIPTOR NewImportDescriptor;
|
|
CHAR *NewImportNameAnsi;
|
|
WCHAR *NewImportName;
|
|
ULONG NewImportNameRva;
|
|
ULONG ImportTableSize;
|
|
BOOLEAN AddedToTable;
|
|
PIMAGE_NT_HEADERS NtHeaders;
|
|
ULONG NextImport;
|
|
|
|
//
|
|
// Initialize locals.
|
|
//
|
|
|
|
MaxNumImports = 256;
|
|
ImportNames = NULL;
|
|
NumImports = 0;
|
|
NextImport = 0;
|
|
|
|
//
|
|
// Find the file name from the path.
|
|
//
|
|
|
|
if (FilePathLength == 0 || FilePath[FilePathLength - 1] == L'\\') {
|
|
ErrorCode = ERROR_BAD_LENGTH;
|
|
goto cleanup;
|
|
}
|
|
|
|
FileName = &FilePath[FilePathLength - 1];
|
|
while (FileName > FilePath) {
|
|
|
|
if (*FileName == L'\\') {
|
|
FileName++;
|
|
break;
|
|
}
|
|
|
|
FileName--;
|
|
}
|
|
|
|
//
|
|
// Extract the parent directory.
|
|
//
|
|
|
|
ParentDirLength = (ULONG) (FileName - FilePath);
|
|
|
|
if (ParentDirLength >= MAX_PATH) {
|
|
ErrorCode = ERROR_BAD_LENGTH;
|
|
goto cleanup;
|
|
}
|
|
|
|
wcsncpy(ParentDir, FilePath, ParentDirLength);
|
|
ParentDir[ParentDirLength] = 0;
|
|
|
|
//
|
|
// Allocate a table for keeping track of imported modules.
|
|
//
|
|
|
|
BufferSize = MaxNumImports * sizeof(WCHAR *);
|
|
ImportNames = PFSVC_ALLOC(BufferSize);
|
|
|
|
if (ImportNames == NULL) {
|
|
ErrorCode = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto cleanup;
|
|
}
|
|
|
|
RtlZeroMemory(ImportNames, BufferSize);
|
|
|
|
//
|
|
// Insert the file into the table and kick off import enumeration
|
|
// on the table. Each enumerated import gets appended to the table
|
|
// if it is not already present. Enumeration continues until all
|
|
// appended entries are processed.
|
|
//
|
|
|
|
ImportNames[NumImports] = FileName;
|
|
NumImports++;
|
|
|
|
while (NextImport < NumImports) {
|
|
|
|
//
|
|
// Initialize loop locals.
|
|
//
|
|
|
|
ImportBase = NULL;
|
|
ImportName = ImportNames[NextImport];
|
|
ImportNameLength = wcslen(ImportName);
|
|
|
|
//
|
|
// Locate the file. First look in ParentDir.
|
|
//
|
|
|
|
if (ImportNameLength + ParentDirLength >= MAX_PATH) {
|
|
goto NextImport;
|
|
}
|
|
|
|
wcscpy(ImportPath, ParentDir);
|
|
wcscat(ImportPath, ImportName);
|
|
|
|
if (GetFileAttributes(ImportPath) == 0xFFFFFFFF) {
|
|
|
|
//
|
|
// Look for this file in other known directories.
|
|
//
|
|
|
|
ErrorCode = PfSvLocateBootServiceFile(ImportName,
|
|
ImportNameLength,
|
|
ImportPath,
|
|
MAX_PATH,
|
|
&RequiredLength);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto NextImport;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Add the file to the file list.
|
|
//
|
|
|
|
PfSvAddToPathList(PathList,
|
|
ImportPath,
|
|
wcslen(ImportPath));
|
|
|
|
//
|
|
// Map the file.
|
|
//
|
|
|
|
ErrorCode = PfSvGetViewOfFile(ImportPath, &ImportBase, &FileSize);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto NextImport;
|
|
}
|
|
|
|
//
|
|
// Make sure this is an image file.
|
|
//
|
|
|
|
__try {
|
|
|
|
//
|
|
// This is the first access to the mapped file. Under stress we might not be
|
|
// able to page this in and an exception might be raised. This protects us from
|
|
// the most common failure case.
|
|
//
|
|
|
|
NtHeaders = ImageNtHeader(ImportBase);
|
|
|
|
} __except (EXCEPTION_EXECUTE_HANDLER) {
|
|
|
|
NtHeaders = NULL;
|
|
}
|
|
|
|
if (NtHeaders == NULL) {
|
|
goto NextImport;
|
|
}
|
|
|
|
//
|
|
// Walk through the imports for this binary.
|
|
//
|
|
|
|
NewImportDescriptor = ImageDirectoryEntryToData(ImportBase,
|
|
FALSE,
|
|
IMAGE_DIRECTORY_ENTRY_IMPORT,
|
|
&ImportTableSize);
|
|
|
|
while (NewImportDescriptor &&
|
|
(NewImportDescriptor->Name != 0) &&
|
|
(NewImportDescriptor->FirstThunk != 0)) {
|
|
|
|
//
|
|
// Initialize loop locals.
|
|
//
|
|
|
|
AddedToTable = FALSE;
|
|
NewImportName = NULL;
|
|
|
|
//
|
|
// Get the name for this import.
|
|
//
|
|
|
|
NewImportNameRva = NewImportDescriptor->Name;
|
|
NewImportNameAnsi = ImageRvaToVa(NtHeaders,
|
|
ImportBase,
|
|
NewImportNameRva,
|
|
NULL);
|
|
|
|
ErrorCode = GetLastError();
|
|
|
|
if (NewImportNameAnsi) {
|
|
NewImportName = PfSvcAnsiToUnicode(NewImportNameAnsi);
|
|
}
|
|
|
|
if (NewImportName == NULL) {
|
|
goto NextImportDescriptor;
|
|
}
|
|
|
|
//
|
|
// Skip the kernel and hal imports. See comment in
|
|
// function description.
|
|
//
|
|
|
|
if (!_wcsicmp(NewImportName, L"ntoskrnl.exe") ||
|
|
!_wcsicmp(NewImportName, L"hal.dll")) {
|
|
goto NextImportDescriptor;
|
|
}
|
|
|
|
//
|
|
// Check to see if this import is already in our table.
|
|
//
|
|
|
|
for (ImportIdx = 0; ImportIdx < NumImports; ImportIdx++) {
|
|
if (!_wcsicmp(NewImportName, ImportNames[ImportIdx])) {
|
|
goto NextImportDescriptor;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Append this import to the table.
|
|
//
|
|
|
|
if (NumImports < MaxNumImports) {
|
|
ImportNames[NumImports] = NewImportName;
|
|
NumImports++;
|
|
AddedToTable = TRUE;
|
|
}
|
|
|
|
NextImportDescriptor:
|
|
|
|
if (!AddedToTable && NewImportName) {
|
|
PFSVC_FREE(NewImportName);
|
|
}
|
|
|
|
if (NumImports >= MaxNumImports) {
|
|
break;
|
|
}
|
|
|
|
NewImportDescriptor++;
|
|
}
|
|
|
|
NextImport:
|
|
|
|
if (ImportBase) {
|
|
UnmapViewOfFile(ImportBase);
|
|
}
|
|
|
|
NextImport++;
|
|
}
|
|
|
|
ErrorCode = ERROR_SUCCESS;
|
|
|
|
cleanup:
|
|
|
|
if (ImportNames) {
|
|
|
|
//
|
|
// The first entry in the table is the filename from FilePath,
|
|
// which is not allocated and which should not be freed.
|
|
//
|
|
|
|
for (ImportIdx = 1; ImportIdx < NumImports; ImportIdx++) {
|
|
PfSvcFreeString(ImportNames[ImportIdx]);
|
|
}
|
|
|
|
PFSVC_FREE(ImportNames);
|
|
}
|
|
|
|
return ErrorCode;
|
|
}
|
|
|
|
DWORD
|
|
PfSvLocateBootServiceFile(
|
|
IN WCHAR *FileName,
|
|
IN ULONG FileNameLength,
|
|
OUT WCHAR *FullPathBuffer,
|
|
IN ULONG FullPathBufferLength,
|
|
OUT PULONG RequiredLength
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function looks at known directories in an *attempt* locate
|
|
the file whose name is specified. The logic may have to be
|
|
improved.
|
|
|
|
Arguments:
|
|
|
|
FileName - File name to look for.
|
|
|
|
FileNameLength - Length of file name in characters excluding NUL.
|
|
|
|
FullPathBuffer - The full path will be put here.
|
|
|
|
FullPathBufferLength - Length of the FullPathBuffer in characters.
|
|
|
|
RequiredLength - If FullPathBuffer is too small, this is how big it
|
|
should be in characters.
|
|
|
|
Return Value:
|
|
|
|
ERROR_INSUFFICIENT_BUFFER - The FullPathBuffer is not big enough.
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
DWORD ErrorCode;
|
|
WCHAR *DriversDirName;
|
|
ULONG SystemDirLength;
|
|
|
|
//
|
|
// Initialize locals.
|
|
//
|
|
|
|
DriversDirName = L"drivers\\";
|
|
|
|
//
|
|
// Copy system root path and a trailing \.
|
|
//
|
|
|
|
SystemDirLength = GetSystemDirectory(FullPathBuffer, FullPathBufferLength);
|
|
|
|
if (!SystemDirLength) {
|
|
ErrorCode = GetLastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
SystemDirLength++;
|
|
|
|
//
|
|
// Calculate maximum size of required length.
|
|
//
|
|
|
|
(*RequiredLength) = SystemDirLength;
|
|
(*RequiredLength) += wcslen(DriversDirName);
|
|
(*RequiredLength) += FileNameLength;
|
|
(*RequiredLength) += 1; // terminating NUL.
|
|
|
|
if ((*RequiredLength) > FullPathBufferLength) {
|
|
ErrorCode = ERROR_INSUFFICIENT_BUFFER;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Append slash.
|
|
//
|
|
|
|
wcscat(FullPathBuffer, L"\\");
|
|
|
|
//
|
|
// Append drivers path.
|
|
//
|
|
|
|
wcscat(FullPathBuffer, DriversDirName);
|
|
|
|
//
|
|
// Append file name.
|
|
//
|
|
|
|
wcscat(FullPathBuffer, FileName);
|
|
|
|
if (GetFileAttributes(FullPathBuffer) != 0xFFFFFFFF) {
|
|
ErrorCode = ERROR_SUCCESS;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Roll back and look for the file in the system
|
|
// directory. SystemDirLength includes the slash after system
|
|
// directory path.
|
|
//
|
|
|
|
FullPathBuffer[SystemDirLength] = 0;
|
|
|
|
wcscat(FullPathBuffer, FileName);
|
|
|
|
if (GetFileAttributes(FullPathBuffer) != 0xFFFFFFFF) {
|
|
ErrorCode = ERROR_SUCCESS;
|
|
goto cleanup;
|
|
}
|
|
|
|
ErrorCode = ERROR_FILE_NOT_FOUND;
|
|
|
|
cleanup:
|
|
|
|
return ErrorCode;
|
|
}
|
|
|
|
DWORD
|
|
PfSvGetBootServiceFullPath(
|
|
IN WCHAR *ServiceName,
|
|
IN WCHAR *BinaryPathName,
|
|
OUT WCHAR *FullPathBuffer,
|
|
IN ULONG FullPathBufferLength,
|
|
OUT PULONG RequiredLength
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function *attempts* to locate specified boot service. The
|
|
logic may have to be improved.
|
|
|
|
Arguments:
|
|
|
|
ServiceName - Name of the service.
|
|
|
|
BinaryPathName - From service configuration info. This is supposed
|
|
to be the full path, but it is not.
|
|
|
|
FullPathBuffer - The full path will be put here.
|
|
|
|
FullPathBufferLength - Length of the FullPathBuffer in characters.
|
|
|
|
RequiredLength - If FullPathBuffer is too small, this is how big it
|
|
should be in characters.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
DWORD ErrorCode;
|
|
WCHAR FileName[MAX_PATH];
|
|
WCHAR FoundFilePath;
|
|
ULONG BinaryPathLength;
|
|
BOOLEAN GotFileNameFromBinaryPath;
|
|
LONG CharIdx;
|
|
ULONG CopyLength;
|
|
WCHAR *SysExtension;
|
|
WCHAR *DllExtension;
|
|
WCHAR *FileNamePart;
|
|
|
|
//
|
|
// Initialize locals.
|
|
//
|
|
|
|
GotFileNameFromBinaryPath = FALSE;
|
|
SysExtension = L".sys";
|
|
DllExtension = L".dll";
|
|
|
|
//
|
|
// Check if a binary path was specified.
|
|
//
|
|
|
|
if (BinaryPathName && BinaryPathName[0]) {
|
|
|
|
//
|
|
// See if the file is really there.
|
|
//
|
|
|
|
if (GetFileAttributes(BinaryPathName) != 0xFFFFFFFF) {
|
|
|
|
//
|
|
// BinaryPathName may not be a fully qualified path. Make
|
|
// sure it is.
|
|
//
|
|
|
|
(*RequiredLength) = GetFullPathName(BinaryPathName,
|
|
FullPathBufferLength,
|
|
FullPathBuffer,
|
|
&FileNamePart);
|
|
|
|
if ((*RequiredLength) == 0) {
|
|
ErrorCode = GetLastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
if ((*RequiredLength) > FullPathBufferLength) {
|
|
ErrorCode = ERROR_INSUFFICIENT_BUFFER;
|
|
goto cleanup;
|
|
}
|
|
|
|
ErrorCode = ERROR_SUCCESS;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Try to extract a file name from the binary path.
|
|
//
|
|
|
|
BinaryPathLength = wcslen(BinaryPathName);
|
|
|
|
for (CharIdx = BinaryPathLength - 1;
|
|
CharIdx >= 0;
|
|
CharIdx --) {
|
|
|
|
if (BinaryPathName[CharIdx] == L'\\') {
|
|
|
|
//
|
|
// Check length and copy it.
|
|
//
|
|
|
|
CopyLength = BinaryPathLength - CharIdx;
|
|
|
|
if (CopyLength < MAX_PATH &&
|
|
CopyLength > 1) {
|
|
|
|
//
|
|
// Copy name starting after the \ character.
|
|
//
|
|
|
|
wcscpy(FileName, &BinaryPathName[CharIdx + 1]);
|
|
|
|
GotFileNameFromBinaryPath = TRUE;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// There was not a slash. Maybe the BinaryPathLength is just
|
|
// the file name.
|
|
//
|
|
|
|
if (GotFileNameFromBinaryPath == FALSE &&
|
|
BinaryPathLength &&
|
|
BinaryPathLength < MAX_PATH) {
|
|
|
|
wcscpy(FileName, BinaryPathName);
|
|
GotFileNameFromBinaryPath = TRUE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// After this point we will base our search on file name hints.
|
|
//
|
|
|
|
//
|
|
// If we got a file name from the binary path try that first.
|
|
//
|
|
|
|
if (GotFileNameFromBinaryPath) {
|
|
|
|
ErrorCode = PfSvLocateBootServiceFile(FileName,
|
|
wcslen(FileName),
|
|
FullPathBuffer,
|
|
FullPathBufferLength,
|
|
RequiredLength);
|
|
|
|
if (ErrorCode != ERROR_FILE_NOT_FOUND) {
|
|
|
|
//
|
|
// If we found a path or if the buffer length was not
|
|
// enough we will bubble up that to our caller.
|
|
//
|
|
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Build a file name from service name by appending a .sys.
|
|
//
|
|
|
|
CopyLength = wcslen(ServiceName);
|
|
CopyLength += wcslen(SysExtension);
|
|
|
|
if (CopyLength >= MAX_PATH) {
|
|
|
|
//
|
|
// The service name is too long!
|
|
//
|
|
|
|
ErrorCode = ERROR_BAD_FORMAT;
|
|
goto cleanup;
|
|
}
|
|
|
|
wcscpy(FileName, ServiceName);
|
|
wcscat(FileName, SysExtension);
|
|
|
|
ErrorCode = PfSvLocateBootServiceFile(FileName,
|
|
wcslen(FileName),
|
|
FullPathBuffer,
|
|
FullPathBufferLength,
|
|
RequiredLength);
|
|
|
|
if (ErrorCode != ERROR_FILE_NOT_FOUND) {
|
|
|
|
//
|
|
// If we found a path or if the buffer length was not
|
|
// enough we will bubble up that to our caller.
|
|
//
|
|
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Build a file name from service name by appending a .dll.
|
|
//
|
|
|
|
CopyLength = wcslen(ServiceName);
|
|
CopyLength += wcslen(DllExtension);
|
|
|
|
if (CopyLength >= MAX_PATH) {
|
|
|
|
//
|
|
// The service name is too long!
|
|
//
|
|
|
|
ErrorCode = ERROR_BAD_FORMAT;
|
|
goto cleanup;
|
|
}
|
|
|
|
wcscpy(FileName, ServiceName);
|
|
wcscat(FileName, DllExtension);
|
|
|
|
ErrorCode = PfSvLocateBootServiceFile(FileName,
|
|
wcslen(FileName),
|
|
FullPathBuffer,
|
|
FullPathBufferLength,
|
|
RequiredLength);
|
|
|
|
if (ErrorCode != ERROR_FILE_NOT_FOUND) {
|
|
|
|
//
|
|
// If we found a path or if the buffer length was not
|
|
// enough we will bubble up that to our caller.
|
|
//
|
|
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// We could not find the file...
|
|
//
|
|
|
|
ErrorCode = ERROR_FILE_NOT_FOUND;
|
|
|
|
cleanup:
|
|
|
|
return ErrorCode;
|
|
}
|
|
|
|
DWORD
|
|
PfSvGetBootLoaderNlsFileNames (
|
|
PPFSVC_PATH_LIST PathList
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function attempts to add the list of NLS files loaded in the
|
|
boot loader to the specified file list.
|
|
|
|
Arguments:
|
|
|
|
PathList - Pointer to list.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
DWORD ErrorCode;
|
|
HKEY NlsKeyHandle;
|
|
WCHAR *CodePageKeyName;
|
|
HKEY CodePageKeyHandle;
|
|
WCHAR *LanguageKeyName;
|
|
HKEY LanguageKeyHandle;
|
|
ULONG BufferSize;
|
|
ULONG RequiredSize;
|
|
ULONG RequiredLength;
|
|
WCHAR FileName[MAX_PATH + 1];
|
|
WCHAR FilePath[MAX_PATH + 1];
|
|
WCHAR *AnsiCodePageName;
|
|
WCHAR *OemCodePageName;
|
|
WCHAR *OemHalName;
|
|
WCHAR *DefaultLangName;
|
|
ULONG RegValueType;
|
|
|
|
//
|
|
// Initialize locals.
|
|
//
|
|
|
|
NlsKeyHandle = NULL;
|
|
CodePageKeyHandle = NULL;
|
|
LanguageKeyHandle = NULL;
|
|
CodePageKeyName = L"CodePage";
|
|
LanguageKeyName = L"Language";
|
|
AnsiCodePageName = L"ACP";
|
|
OemCodePageName = L"OEMCP";
|
|
DefaultLangName = L"Default";
|
|
OemHalName = L"OEMHAL";
|
|
|
|
//
|
|
// Open NLS key.
|
|
//
|
|
|
|
ErrorCode = RegOpenKey(HKEY_LOCAL_MACHINE,
|
|
PFSVC_NLS_REG_KEY_PATH,
|
|
&NlsKeyHandle);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Open CodePage key.
|
|
//
|
|
|
|
ErrorCode = RegOpenKey(NlsKeyHandle,
|
|
CodePageKeyName,
|
|
&CodePageKeyHandle);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Open Language key.
|
|
//
|
|
|
|
ErrorCode = RegOpenKey(NlsKeyHandle,
|
|
LanguageKeyName,
|
|
&LanguageKeyHandle);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// AnsiCodePage:
|
|
//
|
|
|
|
ErrorCode = PfSvQueryNlsFileName(CodePageKeyHandle,
|
|
AnsiCodePageName,
|
|
FileName,
|
|
MAX_PATH * sizeof(WCHAR),
|
|
&RequiredSize);
|
|
|
|
if (ErrorCode == ERROR_SUCCESS) {
|
|
|
|
ErrorCode = PfSvLocateNlsFile(FileName,
|
|
FilePath,
|
|
MAX_PATH,
|
|
&RequiredLength);
|
|
|
|
if (ErrorCode == ERROR_SUCCESS) {
|
|
ErrorCode = PfSvAddToPathList(PathList, FilePath, wcslen(FilePath));
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// OemCodePage:
|
|
//
|
|
|
|
ErrorCode = PfSvQueryNlsFileName(CodePageKeyHandle,
|
|
OemCodePageName,
|
|
FileName,
|
|
MAX_PATH * sizeof(WCHAR),
|
|
&RequiredSize);
|
|
|
|
if (ErrorCode == ERROR_SUCCESS) {
|
|
|
|
ErrorCode = PfSvLocateNlsFile(FileName,
|
|
FilePath,
|
|
MAX_PATH,
|
|
&RequiredLength);
|
|
|
|
if (ErrorCode == ERROR_SUCCESS) {
|
|
ErrorCode = PfSvAddToPathList(PathList, FilePath, wcslen(FilePath));
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Default language case conversion.
|
|
//
|
|
|
|
ErrorCode = PfSvQueryNlsFileName(LanguageKeyHandle,
|
|
DefaultLangName,
|
|
FileName,
|
|
MAX_PATH * sizeof(WCHAR),
|
|
&RequiredSize);
|
|
|
|
if (ErrorCode == ERROR_SUCCESS) {
|
|
|
|
ErrorCode = PfSvLocateNlsFile(FileName,
|
|
FilePath,
|
|
MAX_PATH,
|
|
&RequiredLength);
|
|
|
|
if (ErrorCode == ERROR_SUCCESS) {
|
|
ErrorCode = PfSvAddToPathList(PathList, FilePath, wcslen(FilePath));
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// OemHal:
|
|
//
|
|
|
|
BufferSize = MAX_PATH * sizeof(WCHAR);
|
|
ErrorCode = RegQueryValueEx(CodePageKeyHandle,
|
|
OemHalName,
|
|
NULL,
|
|
&RegValueType,
|
|
(PVOID) FileName,
|
|
&BufferSize);
|
|
|
|
if (ErrorCode == ERROR_SUCCESS && RegValueType == REG_SZ) {
|
|
|
|
ErrorCode = PfSvLocateNlsFile(FileName,
|
|
FilePath,
|
|
MAX_PATH,
|
|
&RequiredLength);
|
|
|
|
if (ErrorCode == ERROR_SUCCESS) {
|
|
ErrorCode = PfSvAddToPathList(PathList, FilePath, wcslen(FilePath));
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
}
|
|
}
|
|
|
|
ErrorCode = ERROR_SUCCESS;
|
|
|
|
cleanup:
|
|
|
|
if (NlsKeyHandle) {
|
|
RegCloseKey(NlsKeyHandle);
|
|
}
|
|
|
|
if (CodePageKeyHandle) {
|
|
RegCloseKey(CodePageKeyHandle);
|
|
}
|
|
|
|
if (LanguageKeyHandle) {
|
|
RegCloseKey(LanguageKeyHandle);
|
|
}
|
|
|
|
return ErrorCode;
|
|
}
|
|
|
|
DWORD
|
|
PfSvLocateNlsFile(
|
|
WCHAR *FileName,
|
|
WCHAR *FilePathBuffer,
|
|
ULONG FilePathBufferLength,
|
|
ULONG *RequiredLength
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function attempts to locate a nls/font related file in known
|
|
directories.
|
|
|
|
Arguments:
|
|
|
|
FileName - File name to look for.
|
|
|
|
FullPathBuffer - The full path will be put here.
|
|
|
|
FullPathBufferLength - Length of the FullPathBuffer in characters.
|
|
|
|
RequiredLength - If FullPathBuffer is too small, this is how big it
|
|
should be in characters.
|
|
|
|
Return Value:
|
|
|
|
ERROR_INSUFFICIENT_BUFFER - The FullPathBuffer is not big enough.
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
DWORD ErrorCode;
|
|
ULONG SystemRootLength;
|
|
WCHAR *System32DirName;
|
|
WCHAR *FontsDirName;
|
|
WCHAR *SystemDirName;
|
|
WCHAR *LongestDirName;
|
|
|
|
//
|
|
// Initialize locals. NOTE: The length of the longest directory
|
|
// name to concatenate to SystemRoot is used in RequiredLength
|
|
// calculation.
|
|
//
|
|
|
|
System32DirName = L"System32\\";
|
|
SystemDirName = L"System\\";
|
|
FontsDirName = L"Fonts\\";
|
|
LongestDirName = System32DirName;
|
|
|
|
//
|
|
// Get system root path.
|
|
//
|
|
|
|
SystemRootLength = ExpandEnvironmentStrings(L"%SystemRoot%\\",
|
|
FilePathBuffer,
|
|
FilePathBufferLength);
|
|
|
|
if (SystemRootLength == 0) {
|
|
ErrorCode = ERROR_BAD_FORMAT;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// SystemRootLength includes the terminating NUL. Adjust it.
|
|
//
|
|
|
|
SystemRootLength--;
|
|
|
|
//
|
|
// Calculate required length with space for terminating NUL.
|
|
//
|
|
|
|
(*RequiredLength) = SystemRootLength;
|
|
(*RequiredLength) += wcslen(LongestDirName);
|
|
(*RequiredLength) += wcslen(FileName);
|
|
(*RequiredLength) ++;
|
|
|
|
if ((*RequiredLength) > FilePathBufferLength) {
|
|
ErrorCode = ERROR_INSUFFICIENT_BUFFER;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Look for it under system32 dir.
|
|
//
|
|
|
|
FilePathBuffer[SystemRootLength] = 0;
|
|
wcscat(FilePathBuffer, System32DirName);
|
|
wcscat(FilePathBuffer, FileName);
|
|
|
|
if (GetFileAttributes(FilePathBuffer) != 0xFFFFFFFF) {
|
|
ErrorCode = ERROR_SUCCESS;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Look for it under fonts dir.
|
|
//
|
|
|
|
FilePathBuffer[SystemRootLength] = 0;
|
|
wcscat(FilePathBuffer, FontsDirName);
|
|
wcscat(FilePathBuffer, FileName);
|
|
|
|
if (GetFileAttributes(FilePathBuffer) != 0xFFFFFFFF) {
|
|
ErrorCode = ERROR_SUCCESS;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Look for it under system dir.
|
|
//
|
|
|
|
FilePathBuffer[SystemRootLength] = 0;
|
|
wcscat(FilePathBuffer, SystemDirName);
|
|
wcscat(FilePathBuffer, FileName);
|
|
|
|
if (GetFileAttributes(FilePathBuffer) != 0xFFFFFFFF) {
|
|
ErrorCode = ERROR_SUCCESS;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Look for it at SystemRoot.
|
|
//
|
|
|
|
FilePathBuffer[SystemRootLength] = 0;
|
|
wcscat(FilePathBuffer, FileName);
|
|
|
|
if (GetFileAttributes(FilePathBuffer) != 0xFFFFFFFF) {
|
|
ErrorCode = ERROR_SUCCESS;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Could not find the file.
|
|
//
|
|
|
|
ErrorCode = ERROR_FILE_NOT_FOUND;
|
|
|
|
cleanup:
|
|
|
|
return ErrorCode;
|
|
}
|
|
|
|
DWORD
|
|
PfSvQueryNlsFileName (
|
|
HKEY Key,
|
|
WCHAR *ValueName,
|
|
WCHAR *FileNameBuffer,
|
|
ULONG FileNameBufferSize,
|
|
ULONG *RequiredSize
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function attempts to get a file name from an NLS
|
|
CodePage/Language registry key.
|
|
|
|
Arguments:
|
|
|
|
Key - CodePage or Language key handle.
|
|
|
|
ValueName - What we are trying to get the file name for.
|
|
|
|
FileNameBuffer - Where the file name will be put.
|
|
|
|
FileNameBufferSize - Size in bytes of the file name buffer.
|
|
|
|
RequiredSize - If FileNameBuffer is too small, this is what its
|
|
size should be.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
DWORD ErrorCode;
|
|
WCHAR FileValueName[MAX_PATH + 1];
|
|
ULONG BufferSize;
|
|
ULONG RegValueType;
|
|
|
|
//
|
|
// First we first get the valuename under which the file name is
|
|
// stored, then we get the file name:
|
|
//
|
|
|
|
BufferSize = MAX_PATH * sizeof(WCHAR);
|
|
ErrorCode = RegQueryValueEx(Key,
|
|
ValueName,
|
|
NULL,
|
|
&RegValueType,
|
|
(PVOID) FileValueName,
|
|
&BufferSize);
|
|
|
|
if (ErrorCode == ERROR_MORE_DATA) {
|
|
ErrorCode = ERROR_INVALID_FUNCTION;
|
|
goto cleanup;
|
|
}
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
if (RegValueType != REG_SZ) {
|
|
ErrorCode = ERROR_BAD_FORMAT;
|
|
goto cleanup;
|
|
}
|
|
|
|
*RequiredSize = FileNameBufferSize;
|
|
ErrorCode = RegQueryValueEx(Key,
|
|
FileValueName,
|
|
NULL,
|
|
&RegValueType,
|
|
(PVOID) FileNameBuffer,
|
|
RequiredSize);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
if (RegValueType != REG_SZ) {
|
|
ErrorCode = ERROR_BAD_FORMAT;
|
|
goto cleanup;
|
|
}
|
|
|
|
ErrorCode = ERROR_SUCCESS;
|
|
|
|
cleanup:
|
|
|
|
return ErrorCode;
|
|
}
|
|
|
|
//
|
|
// Routines to manage / run idle tasks.
|
|
//
|
|
|
|
VOID
|
|
PfSvInitializeTask (
|
|
PPFSVC_IDLE_TASK Task
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Initialize the task structure. Should be called before any other
|
|
task functions are called. You should call the cleanup routine
|
|
on the initialized task.
|
|
|
|
Arguments:
|
|
|
|
Task - Pointer to structure.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
//
|
|
// Zero out the structure initializing the following to
|
|
// the right values:
|
|
//
|
|
// Registered
|
|
// WaitUnregisteredEvent
|
|
// CallbackStoppedEvent
|
|
// StartedUnregisteringEvent
|
|
// CompletedUnregisteringEvent
|
|
// Unregistering
|
|
// CallbackRunning
|
|
//
|
|
|
|
RtlZeroMemory(Task, sizeof(PFSVC_IDLE_TASK));
|
|
|
|
Task->Initialized = TRUE;
|
|
}
|
|
|
|
DWORD
|
|
PfSvRegisterTask (
|
|
PPFSVC_IDLE_TASK Task,
|
|
IT_IDLE_TASK_ID TaskId,
|
|
WAITORTIMERCALLBACK Callback,
|
|
PFSVC_IDLE_TASK_WORKER_FUNCTION DoWorkFunction
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Registers the Callback to be called when it is the turn of this
|
|
idle task to run. IFF this function returns success, you should
|
|
call unregister function before calling the cleanup function.
|
|
|
|
Arguments:
|
|
|
|
Task - Pointer to initialized task structure.
|
|
|
|
TaskId - Idle task ID to register.
|
|
|
|
Callback - We'll register a wait on the start event returned by
|
|
idle task registration with this callback. The callback should
|
|
call start/stop task callback functions appropriately.
|
|
|
|
DoWorkFunction - If the caller wants the common callback function
|
|
to be used, then this function will be called to do the actual
|
|
work in the common callback.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
DWORD ErrorCode;
|
|
BOOL Success;
|
|
BOOLEAN CreatedWaitUnregisteredEvent;
|
|
BOOLEAN CreatedStartedUnregisteringEvent;
|
|
BOOLEAN CreatedCompletedUnregisteringEvent;
|
|
BOOLEAN CreatedCallbackStoppedEvent;
|
|
BOOLEAN RegisteredIdleTask;
|
|
|
|
//
|
|
// Initialize locals.
|
|
//
|
|
|
|
RegisteredIdleTask = FALSE;
|
|
CreatedWaitUnregisteredEvent = FALSE;
|
|
CreatedStartedUnregisteringEvent = FALSE;
|
|
CreatedCompletedUnregisteringEvent = FALSE;
|
|
CreatedCallbackStoppedEvent = FALSE;
|
|
|
|
DBGPR((PFID,PFTASK,"PFSVC: RegisterTask(%p,%d,%p,%p)\n",Task,TaskId,Callback,DoWorkFunction));
|
|
|
|
//
|
|
// The task should be initialized and not registered.
|
|
//
|
|
|
|
PFSVC_ASSERT(Task->Initialized);
|
|
PFSVC_ASSERT(!Task->Registered);
|
|
PFSVC_ASSERT(!Task->Unregistering);
|
|
PFSVC_ASSERT(!Task->CallbackRunning);
|
|
|
|
//
|
|
// Create the event that cleanup waits on to make sure
|
|
// the registered wait is fully unregistered.
|
|
//
|
|
|
|
Task->WaitUnregisteredEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
|
|
|
|
if (Task->WaitUnregisteredEvent == NULL) {
|
|
ErrorCode = GetLastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
CreatedWaitUnregisteredEvent = TRUE;
|
|
|
|
//
|
|
// Create the event that will get signaled when we start
|
|
// unregistering the task.
|
|
//
|
|
|
|
Task->StartedUnregisteringEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
|
|
|
|
if (Task->StartedUnregisteringEvent == NULL) {
|
|
ErrorCode = GetLastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
CreatedStartedUnregisteringEvent = TRUE;
|
|
|
|
//
|
|
// Create the event that will get signaled when we complete
|
|
// unregistering the task.
|
|
//
|
|
|
|
Task->CompletedUnregisteringEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
|
|
|
|
if (Task->CompletedUnregisteringEvent == NULL) {
|
|
ErrorCode = GetLastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
CreatedCompletedUnregisteringEvent = TRUE;
|
|
|
|
//
|
|
// Create the event we may wait on for the current running
|
|
// callback to go away.
|
|
//
|
|
|
|
Task->CallbackStoppedEvent = CreateEvent(NULL, TRUE, TRUE, NULL);
|
|
|
|
if (Task->CallbackStoppedEvent == NULL) {
|
|
ErrorCode = GetLastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
CreatedCallbackStoppedEvent = TRUE;
|
|
|
|
//
|
|
// Register the idle task.
|
|
//
|
|
|
|
ErrorCode = RegisterIdleTask(TaskId,
|
|
&Task->ItHandle,
|
|
&Task->StartEvent,
|
|
&Task->StopEvent);
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
RegisteredIdleTask = TRUE;
|
|
|
|
//
|
|
// Register the callback: Note that once this call succeeds the task has to
|
|
// be unregistered via PfSvUnregisterTask.
|
|
//
|
|
|
|
//
|
|
// The callback might fire right away so note that we registered it and set
|
|
// up its fields upfront.
|
|
//
|
|
|
|
Task->Registered = 1;
|
|
Task->Callback = Callback;
|
|
Task->DoWorkFunction = DoWorkFunction;
|
|
|
|
//
|
|
// If the common task callback was specified, a worker function should also
|
|
// be specified.
|
|
//
|
|
|
|
if (Callback == PfSvCommonTaskCallback) {
|
|
PFSVC_ASSERT(DoWorkFunction);
|
|
}
|
|
|
|
Success = RegisterWaitForSingleObject(&Task->WaitHandle,
|
|
Task->StartEvent,
|
|
Task->Callback,
|
|
Task,
|
|
INFINITE,
|
|
WT_EXECUTEONLYONCE | WT_EXECUTELONGFUNCTION);
|
|
|
|
if (!Success) {
|
|
|
|
//
|
|
// We failed to really register the task.
|
|
//
|
|
|
|
Task->Registered = 0;
|
|
|
|
ErrorCode = GetLastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
ErrorCode = ERROR_SUCCESS;
|
|
|
|
cleanup:
|
|
|
|
DBGPR((PFID,PFTASK,"PFSVC: RegisterTask(%p)=%x\n",Task,ErrorCode));
|
|
|
|
if (ErrorCode != ERROR_SUCCESS) {
|
|
|
|
if (CreatedWaitUnregisteredEvent) {
|
|
CloseHandle(Task->WaitUnregisteredEvent);
|
|
Task->WaitUnregisteredEvent = NULL;
|
|
}
|
|
|
|
if (CreatedStartedUnregisteringEvent) {
|
|
CloseHandle(Task->StartedUnregisteringEvent);
|
|
Task->StartedUnregisteringEvent = NULL;
|
|
}
|
|
|
|
if (CreatedCompletedUnregisteringEvent) {
|
|
CloseHandle(Task->CompletedUnregisteringEvent);
|
|
Task->CompletedUnregisteringEvent = NULL;
|
|
}
|
|
|
|
if (CreatedCallbackStoppedEvent) {
|
|
CloseHandle(Task->CallbackStoppedEvent);
|
|
Task->CallbackStoppedEvent = NULL;
|
|
}
|
|
|
|
if (RegisteredIdleTask) {
|
|
UnregisterIdleTask(Task->ItHandle,
|
|
Task->StartEvent,
|
|
Task->StopEvent);
|
|
}
|
|
}
|
|
|
|
return ErrorCode;
|
|
}
|
|
|
|
DWORD
|
|
PfSvUnregisterTask (
|
|
PPFSVC_IDLE_TASK Task,
|
|
BOOLEAN CalledFromCallback
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Unregisters the idle task and the registered wait / callback. You should
|
|
call this function before calling the cleanup routine IFF the register
|
|
function returned success.
|
|
|
|
Arguments:
|
|
|
|
Task - Pointer to registered task.
|
|
|
|
CalledFromCallback - Whether this function is being called from inside
|
|
the queued callback of the task.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
|
|
{
|
|
LONG OldValue;
|
|
LONG NewValue;
|
|
DWORD ErrorCode;
|
|
|
|
DBGPR((PFID,PFTASK,"PFSVC: UnregisterTask(%p,%d)\n",Task,(DWORD)CalledFromCallback));
|
|
|
|
//
|
|
// The task should be initialized. It may already be unregistered.
|
|
//
|
|
|
|
PFSVC_ASSERT(Task->Initialized);
|
|
|
|
if (Task->Registered == 0) {
|
|
ErrorCode = ERROR_SHUTDOWN_IN_PROGRESS;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Distinguish whether we are unregistering the task from a callback.
|
|
//
|
|
|
|
if (CalledFromCallback) {
|
|
NewValue = PfSvcUnregisteringTaskFromCallback;
|
|
} else {
|
|
NewValue = PfSvcUnregisteringTaskFromMainThread;
|
|
}
|
|
|
|
//
|
|
// Is this task already being unregistered?
|
|
//
|
|
|
|
OldValue = InterlockedCompareExchange(&Task->Unregistering,
|
|
NewValue,
|
|
PfSvcNotUnregisteringTask);
|
|
|
|
if (OldValue != PfSvcNotUnregisteringTask) {
|
|
|
|
ErrorCode = ERROR_SHUTDOWN_IN_PROGRESS;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// *We* will be unregistering the task. There is no turning back.
|
|
//
|
|
|
|
SetEvent(Task->StartedUnregisteringEvent);
|
|
|
|
//
|
|
// If we are not inside a callback, wait for no callbacks to be running
|
|
// and cause new ones that start to bail out. We do this so we can safely
|
|
// unregister the wait.
|
|
//
|
|
|
|
if (!CalledFromCallback) {
|
|
|
|
do {
|
|
OldValue = InterlockedCompareExchange(&Task->CallbackRunning,
|
|
PfSvcTaskCallbackDisabled,
|
|
PfSvcTaskCallbackNotRunning);
|
|
|
|
if (OldValue == PfSvcTaskCallbackNotRunning) {
|
|
|
|
//
|
|
// We did it. No callbacks are running and new ones that try to
|
|
// start will bail out.
|
|
//
|
|
|
|
PFSVC_ASSERT(Task->CallbackRunning == PfSvcTaskCallbackDisabled);
|
|
|
|
break;
|
|
}
|
|
|
|
//
|
|
// A callback might be active right now. It will see that we are unregistering and
|
|
// go away. Sleep for a while and try again.
|
|
//
|
|
|
|
PFSVC_ASSERT(OldValue == PfSvcTaskCallbackRunning);
|
|
|
|
//
|
|
// We wait on this event with a timeout, because signaling of it is not
|
|
// 100% reliable because it is not under a lock etc.
|
|
//
|
|
|
|
WaitForSingleObject(Task->CallbackStoppedEvent, 1000);
|
|
|
|
} while (TRUE);
|
|
|
|
} else {
|
|
|
|
//
|
|
// We already have control of this variable as the running callback: just
|
|
// update it.
|
|
//
|
|
|
|
Task->CallbackRunning = PfSvcTaskCallbackDisabled;
|
|
}
|
|
|
|
//
|
|
// Unregister the wait. Note that in cleanup we have to wait to for
|
|
// WaitUnregisteredEvent to be signaled.
|
|
//
|
|
|
|
UnregisterWaitEx(Task->WaitHandle, Task->WaitUnregisteredEvent);
|
|
|
|
//
|
|
// Unregister the idle task.
|
|
//
|
|
|
|
UnregisterIdleTask(Task->ItHandle,
|
|
Task->StartEvent,
|
|
Task->StopEvent);
|
|
|
|
//
|
|
// Note that the task is no longer registered.
|
|
//
|
|
|
|
Task->Registered = FALSE;
|
|
|
|
SetEvent(Task->CompletedUnregisteringEvent);
|
|
|
|
ErrorCode = ERROR_SUCCESS;
|
|
|
|
cleanup:
|
|
|
|
DBGPR((PFID,PFTASK,"PFSVC: UnregisterTask(%p)=%x\n",Task,ErrorCode));
|
|
|
|
return ErrorCode;
|
|
}
|
|
|
|
VOID
|
|
PfSvCleanupTask (
|
|
PPFSVC_IDLE_TASK Task
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Cleans up all fields of an unregistered task or a task that was never
|
|
registered.
|
|
|
|
Arguments:
|
|
|
|
Task - Pointer to task.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
//
|
|
// The task should have been initialized.
|
|
//
|
|
|
|
PFSVC_ASSERT(Task->Initialized);
|
|
|
|
//
|
|
// If there is a WaitUnregisteredEvent, we have to wait on it
|
|
// to make sure the unregister operation is fully complete.
|
|
//
|
|
|
|
if (Task->WaitUnregisteredEvent) {
|
|
WaitForSingleObject(Task->WaitUnregisteredEvent, INFINITE);
|
|
CloseHandle(Task->WaitUnregisteredEvent);
|
|
}
|
|
|
|
//
|
|
// If there is CompletedUnregisteringEvent, wait for it to
|
|
// be signalled to make sure both that the wait is unregistered,
|
|
// and the idle task is unregistered.
|
|
//
|
|
|
|
if (Task->CompletedUnregisteringEvent) {
|
|
WaitForSingleObject(Task->CompletedUnregisteringEvent, INFINITE);
|
|
}
|
|
|
|
//
|
|
// The task should be unregistered before it is cleaned up.
|
|
//
|
|
|
|
PFSVC_ASSERT(Task->Registered == FALSE);
|
|
|
|
//
|
|
// Cleanup task unregistering events.
|
|
//
|
|
|
|
if (Task->StartedUnregisteringEvent) {
|
|
CloseHandle(Task->StartedUnregisteringEvent);
|
|
}
|
|
|
|
if (Task->CompletedUnregisteringEvent) {
|
|
CloseHandle(Task->CompletedUnregisteringEvent);
|
|
}
|
|
|
|
//
|
|
// Clean up callback stopped event.
|
|
//
|
|
|
|
if (Task->CallbackStoppedEvent) {
|
|
CloseHandle(Task->CallbackStoppedEvent);
|
|
}
|
|
|
|
Task->Initialized = FALSE;
|
|
|
|
return;
|
|
}
|
|
|
|
BOOL
|
|
PfSvStartTaskCallback(
|
|
PPFSVC_IDLE_TASK Task
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Callbacks registered via register task function should call this as the
|
|
first thing. If this function returns FALSE, the callback should go away
|
|
immediately without calling the stop-callback function.
|
|
|
|
Arguments:
|
|
|
|
Task - Pointer to task.
|
|
|
|
Return Value:
|
|
|
|
TRUE - Everything is cool.
|
|
FALSE - The task is being unregistered. Exit the callback asap.
|
|
|
|
--*/
|
|
|
|
{
|
|
BOOL ReturnValue;
|
|
LONG OldValue;
|
|
|
|
DBGPR((PFID,PFTASK,"PFSVC: StartTaskCallback(%p)\n",Task));
|
|
|
|
//
|
|
// We should not be called if the task is not initialized.
|
|
//
|
|
|
|
PFSVC_ASSERT(Task->Initialized);
|
|
|
|
do {
|
|
|
|
//
|
|
// First check if we are trying to unregister.
|
|
//
|
|
|
|
if (Task->Unregistering) {
|
|
ReturnValue = FALSE;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Try to mark the callback running.
|
|
//
|
|
|
|
OldValue = InterlockedCompareExchange(&Task->CallbackRunning,
|
|
PfSvcTaskCallbackRunning,
|
|
PfSvcTaskCallbackNotRunning);
|
|
|
|
if (OldValue == PfSvcTaskCallbackNotRunning) {
|
|
|
|
//
|
|
// We are the running callback now. Reset the event that says
|
|
// the current callback stopped running.
|
|
//
|
|
|
|
ResetEvent(Task->CallbackStoppedEvent);
|
|
|
|
ReturnValue = TRUE;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Either another callback is running or we are unregistering.
|
|
//
|
|
|
|
//
|
|
// Are we unregistering?
|
|
//
|
|
|
|
if (Task->Unregistering) {
|
|
|
|
ReturnValue = FALSE;
|
|
goto cleanup;
|
|
|
|
} else {
|
|
|
|
PFSVC_ASSERT(OldValue == PfSvcTaskCallbackRunning);
|
|
}
|
|
|
|
//
|
|
// Sleep for a while and try again. There should not be much conflict in this
|
|
// code, so we should hardly ever need to sleep.
|
|
//
|
|
|
|
Sleep(15);
|
|
|
|
} while (TRUE);
|
|
|
|
//
|
|
// We should not come here.
|
|
//
|
|
|
|
PFSVC_ASSERT(FALSE);
|
|
|
|
cleanup:
|
|
|
|
//
|
|
// If we are starting a callback, the task should not be in unregistered state.
|
|
//
|
|
|
|
PFSVC_ASSERT(!ReturnValue || Task->Registered);
|
|
|
|
DBGPR((PFID,PFTASK,"PFSVC: StartTaskCallback(%p)=%d\n",Task,ReturnValue));
|
|
|
|
return ReturnValue;
|
|
}
|
|
|
|
VOID
|
|
PfSvStopTaskCallback(
|
|
PPFSVC_IDLE_TASK Task
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Callbacks registered via register task function should call this as the
|
|
last thing, only if they successfully called the start callback function and
|
|
they did not unregister the task.
|
|
|
|
Arguments:
|
|
|
|
Task - Pointer to task.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
DBGPR((PFID,PFTASK,"PFSVC: StopTaskCallback(%p)\n",Task));
|
|
|
|
//
|
|
// The task should be registered.
|
|
//
|
|
|
|
PFSVC_ASSERT(Task->Registered);
|
|
|
|
//
|
|
// There should be a running callback.
|
|
//
|
|
|
|
PFSVC_ASSERT(Task->CallbackRunning == PfSvcTaskCallbackRunning);
|
|
|
|
Task->CallbackRunning = PfSvcTaskCallbackNotRunning;
|
|
|
|
//
|
|
// Signal the event the main thread may be waiting on to unregister
|
|
// this task.
|
|
//
|
|
|
|
SetEvent(Task->CallbackStoppedEvent);
|
|
|
|
return;
|
|
}
|
|
|
|
VOID
|
|
CALLBACK
|
|
PfSvCommonTaskCallback(
|
|
PVOID lpParameter,
|
|
BOOLEAN TimerOrWaitFired
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This is the callback for the idle tasks. It is called when the system is idle,
|
|
and it is this tasks turn to run.
|
|
|
|
Note that you cannot call PfSvCleanupTask from this thread, as it would cause
|
|
a deadlock when that function waits for registered wait callbacks to exit.
|
|
|
|
Arguments:
|
|
|
|
lpParameter - Pointer to task.
|
|
|
|
TimerOrWaitFired - Whether the callback was initiated by a timeout or the start
|
|
event getting signaled by the idle task service.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
HANDLE NewWaitHandle;
|
|
PPFSVC_IDLE_TASK Task;
|
|
BOOL StartedCallback;
|
|
BOOL Success;
|
|
DWORD ErrorCode;
|
|
|
|
//
|
|
// Initialize locals.
|
|
//
|
|
|
|
Task = lpParameter;
|
|
StartedCallback = FALSE;
|
|
|
|
DBGPR((PFID,PFTASK,"PFSVC: CommonTaskCallback(%p)\n",Task));
|
|
|
|
//
|
|
// Enter task callback.
|
|
//
|
|
|
|
StartedCallback = PfSvStartTaskCallback(Task);
|
|
|
|
if (!StartedCallback) {
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Do the task.
|
|
//
|
|
|
|
ErrorCode = Task->DoWorkFunction(Task);
|
|
|
|
if (ErrorCode == ERROR_RETRY) {
|
|
|
|
//
|
|
// The stop event was signaled. We will queue another callback.
|
|
//
|
|
|
|
Success = RegisterWaitForSingleObject(&NewWaitHandle,
|
|
Task->StartEvent,
|
|
Task->Callback,
|
|
Task,
|
|
INFINITE,
|
|
WT_EXECUTEONLYONCE | WT_EXECUTELONGFUNCTION);
|
|
|
|
|
|
if (Success) {
|
|
|
|
//
|
|
// Unregister the current wait handle and update it.
|
|
//
|
|
|
|
UnregisterWaitEx(Task->WaitHandle, NULL);
|
|
Task->WaitHandle = NewWaitHandle;
|
|
|
|
goto cleanup;
|
|
|
|
} else {
|
|
|
|
//
|
|
// We could not queue another callback. We will unregister,
|
|
// since we would not be able to respond to start signals
|
|
// from the idle task service. Unregister may fail only if
|
|
// the main thread is already trying to unregister.
|
|
//
|
|
|
|
ErrorCode = PfSvUnregisterTask(Task, TRUE);
|
|
|
|
if (ErrorCode == ERROR_SUCCESS) {
|
|
|
|
//
|
|
// Since *we* unregistered, we should not call stop callback.
|
|
//
|
|
|
|
StartedCallback = FALSE;
|
|
|
|
}
|
|
|
|
goto cleanup;
|
|
}
|
|
|
|
} else {
|
|
|
|
//
|
|
// The task completed. Let's unregister.
|
|
//
|
|
|
|
ErrorCode = PfSvUnregisterTask(Task, TRUE);
|
|
|
|
if (ErrorCode == ERROR_SUCCESS) {
|
|
|
|
//
|
|
// Since *we* unregistered, we should not call stop callback.
|
|
//
|
|
|
|
StartedCallback = FALSE;
|
|
|
|
}
|
|
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// We should not come here.
|
|
//
|
|
|
|
PFSVC_ASSERT(FALSE);
|
|
|
|
cleanup:
|
|
|
|
DBGPR((PFID,PFTASK,"PFSVC: CommonTaskCallback(%p)=%x\n",Task,ErrorCode));
|
|
|
|
if (StartedCallback) {
|
|
PfSvStopTaskCallback(Task);
|
|
}
|
|
}
|
|
|
|
DWORD
|
|
PfSvContinueRunningTask(
|
|
PPFSVC_IDLE_TASK Task
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This is called from a running task to determine if we should continue
|
|
running this task. The task should continue running if ERROR_SUCCESS is
|
|
returned. ERROR_RETRY may be returned if the task is unregistering or
|
|
was asked to stop.
|
|
|
|
Arguments:
|
|
|
|
Task - Pointer to task. If NULL, this parameter is ignored.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
DWORD WaitResult;
|
|
DWORD ErrorCode;
|
|
|
|
if (Task) {
|
|
|
|
//
|
|
// Is the task being unregistered?
|
|
//
|
|
|
|
if (Task->Unregistering) {
|
|
ErrorCode = ERROR_RETRY;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Is the stop event signaled? We don't really wait here since
|
|
// the timeout is 0.
|
|
//
|
|
|
|
WaitResult = WaitForSingleObject(Task->StopEvent, 0);
|
|
|
|
if (WaitResult == WAIT_OBJECT_0) {
|
|
|
|
ErrorCode = ERROR_RETRY;
|
|
goto cleanup;
|
|
|
|
} else if (WaitResult != WAIT_TIMEOUT) {
|
|
|
|
//
|
|
// There was an error.
|
|
//
|
|
|
|
ErrorCode = GetLastError();
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Check if the service is exiting...
|
|
//
|
|
|
|
if (PfSvcGlobals.TerminateServiceEvent) {
|
|
|
|
WaitResult = WaitForSingleObject(PfSvcGlobals.TerminateServiceEvent, 0);
|
|
|
|
if (WaitResult == WAIT_OBJECT_0) {
|
|
|
|
ErrorCode = ERROR_RETRY;
|
|
goto cleanup;
|
|
|
|
} else if (WaitResult != WAIT_TIMEOUT) {
|
|
|
|
//
|
|
// There was an error.
|
|
//
|
|
|
|
ErrorCode = GetLastError();
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
//
|
|
// The task should continue to run.
|
|
//
|
|
|
|
ErrorCode = ERROR_SUCCESS;
|
|
|
|
cleanup:
|
|
|
|
return ErrorCode;
|
|
}
|
|
|
|
//
|
|
// ProcessIdleTasks notify routine and its dependencies.
|
|
//
|
|
|
|
VOID
|
|
PfSvProcessIdleTasksCallback(
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This is routine is registered with the idle task server as a notify
|
|
routine that is called when processing of all idle tasks is requested.
|
|
ProcessIdleTasks is usually called to prepare the system for a benchmark
|
|
run by performing the optimization tasks that would have been performed
|
|
when the system is idle.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
HANDLE Events[2];
|
|
DWORD NumEvents;
|
|
DWORD WaitResult;
|
|
BOOLEAN ResetOverrideIdleEvent;
|
|
|
|
//
|
|
// First flush the idle tasks the prefetcher may have queued:
|
|
//
|
|
|
|
//
|
|
// Determine the current status of the override-idle event.
|
|
//
|
|
|
|
WaitResult = WaitForSingleObject(PfSvcGlobals.OverrideIdleProcessingEvent,
|
|
0);
|
|
|
|
if (WaitResult != WAIT_OBJECT_0) {
|
|
|
|
//
|
|
// Override idle event is not already set. Set it and note to reset
|
|
// it once tasks are completed.
|
|
//
|
|
|
|
SetEvent(PfSvcGlobals.OverrideIdleProcessingEvent);
|
|
ResetOverrideIdleEvent = TRUE;
|
|
|
|
} else {
|
|
|
|
ResetOverrideIdleEvent = FALSE;
|
|
}
|
|
|
|
//
|
|
// Wait for processing complete event to get signaled.
|
|
//
|
|
|
|
Events[0] = PfSvcGlobals.ProcessingCompleteEvent;
|
|
Events[1] = PfSvcGlobals.TerminateServiceEvent;
|
|
NumEvents = 2;
|
|
|
|
WaitForMultipleObjects(NumEvents, Events, FALSE, 30 * 60 * 1000);
|
|
|
|
//
|
|
// If we set the override idle event, reset it.
|
|
//
|
|
|
|
if (ResetOverrideIdleEvent) {
|
|
ResetEvent(PfSvcGlobals.OverrideIdleProcessingEvent);
|
|
}
|
|
|
|
//
|
|
// Force an update of the disk layout in case it did not happen.
|
|
// If we notice no changes we will not launch the defragger again.
|
|
//
|
|
|
|
PfSvUpdateOptimalLayout(NULL);
|
|
|
|
//
|
|
// Signal WMI to complete its idle tasks if it has pending tasks.
|
|
//
|
|
|
|
PfSvForceWMIProcessIdleTasks();
|
|
|
|
return;
|
|
}
|
|
|
|
DWORD
|
|
PfSvForceWMIProcessIdleTasks(
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This is routine is called to force WMI to process all of its idle tasks.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
Win32 error code.
|
|
|
|
--*/
|
|
|
|
{
|
|
HANDLE StartEvent;
|
|
HANDLE DoneEvent;
|
|
HANDLE Events[2];
|
|
DWORD NumEvents;
|
|
DWORD ErrorCode;
|
|
DWORD WaitResult;
|
|
BOOL Success;
|
|
|
|
//
|
|
// Initialize locals.
|
|
//
|
|
|
|
StartEvent = NULL;
|
|
DoneEvent = NULL;
|
|
|
|
//
|
|
// Wait until WMI service is started.
|
|
//
|
|
|
|
Success = PfSvWaitForServiceToStart(L"WINMGMT", 5 * 60 * 1000);
|
|
|
|
if (!Success) {
|
|
ErrorCode = ERROR_SERVICE_NEVER_STARTED;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Open the start and done events.
|
|
//
|
|
|
|
StartEvent = OpenEvent(EVENT_ALL_ACCESS, FALSE, L"WMI_ProcessIdleTasksStart");
|
|
DoneEvent = OpenEvent(EVENT_ALL_ACCESS, FALSE, L"WMI_ProcessIdleTasksComplete");
|
|
|
|
if (!StartEvent || !DoneEvent) {
|
|
ErrorCode = ERROR_FILE_NOT_FOUND;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Reset the done event.
|
|
//
|
|
|
|
ResetEvent(DoneEvent);
|
|
|
|
//
|
|
// Signal the start event.
|
|
//
|
|
|
|
SetEvent(StartEvent);
|
|
|
|
//
|
|
// Wait for the done event to be signaled.
|
|
//
|
|
|
|
Events[0] = DoneEvent;
|
|
Events[1] = PfSvcGlobals.TerminateServiceEvent;
|
|
NumEvents = 2;
|
|
|
|
WaitResult = WaitForMultipleObjects(NumEvents, Events, FALSE, 25 * 60 * 1000);
|
|
|
|
switch(WaitResult) {
|
|
case WAIT_OBJECT_0 : ErrorCode = ERROR_SUCCESS; break;
|
|
case WAIT_OBJECT_0 + 1 : ErrorCode = ERROR_SHUTDOWN_IN_PROGRESS; break;
|
|
case WAIT_FAILED : ErrorCode = GetLastError();
|
|
case WAIT_TIMEOUT : ErrorCode = WAIT_TIMEOUT;
|
|
default : ErrorCode = ERROR_INVALID_FUNCTION;
|
|
}
|
|
|
|
//
|
|
// Fall through with error code.
|
|
//
|
|
|
|
cleanup:
|
|
|
|
if (StartEvent) {
|
|
CloseHandle(StartEvent);
|
|
}
|
|
|
|
if (DoneEvent) {
|
|
CloseHandle(DoneEvent);
|
|
}
|
|
|
|
return ErrorCode;
|
|
}
|
|
|
|
BOOL
|
|
PfSvWaitForServiceToStart (
|
|
LPTSTR lpServiceName,
|
|
DWORD dwMaxWait
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Waits for the service to start.
|
|
|
|
Arguments:
|
|
|
|
lpServiceName - Service to wait for.
|
|
|
|
dwMaxWait - Timeout in ms.
|
|
|
|
Return Value:
|
|
|
|
Whether the service was started.
|
|
|
|
--*/
|
|
|
|
{
|
|
BOOL bStarted = FALSE;
|
|
DWORD dwSize = 512;
|
|
DWORD StartTickCount;
|
|
SC_HANDLE hScManager = NULL;
|
|
SC_HANDLE hService = NULL;
|
|
SERVICE_STATUS ServiceStatus;
|
|
LPQUERY_SERVICE_CONFIG lpServiceConfig = NULL;
|
|
|
|
//
|
|
// OpenSCManager and the service.
|
|
//
|
|
hScManager = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT);
|
|
if (!hScManager) {
|
|
goto Exit;
|
|
}
|
|
|
|
hService = OpenService(hScManager, lpServiceName,
|
|
SERVICE_QUERY_CONFIG | SERVICE_QUERY_STATUS);
|
|
if (!hService) {
|
|
goto Exit;
|
|
}
|
|
|
|
//
|
|
// Query if the service is going to start
|
|
//
|
|
lpServiceConfig = LocalAlloc (LPTR, dwSize);
|
|
if (!lpServiceConfig) {
|
|
goto Exit;
|
|
}
|
|
|
|
if (!QueryServiceConfig (hService, lpServiceConfig, dwSize, &dwSize)) {
|
|
|
|
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
|
|
goto Exit;
|
|
}
|
|
|
|
LocalFree (lpServiceConfig);
|
|
|
|
lpServiceConfig = LocalAlloc (LPTR, dwSize);
|
|
|
|
if (!lpServiceConfig) {
|
|
goto Exit;
|
|
}
|
|
|
|
if (!QueryServiceConfig (hService, lpServiceConfig, dwSize, &dwSize)) {
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
if (lpServiceConfig->dwStartType != SERVICE_AUTO_START) {
|
|
goto Exit;
|
|
}
|
|
|
|
//
|
|
// Loop until the service starts or we think it never will start
|
|
// or we've exceeded our maximum time delay.
|
|
//
|
|
|
|
StartTickCount = GetTickCount();
|
|
|
|
while (!bStarted) {
|
|
|
|
if (WAIT_OBJECT_0 == WaitForSingleObject(PfSvcGlobals.TerminateServiceEvent, 0)) {
|
|
break;
|
|
}
|
|
|
|
if ((GetTickCount() - StartTickCount) > dwMaxWait) {
|
|
break;
|
|
}
|
|
|
|
if (!QueryServiceStatus(hService, &ServiceStatus )) {
|
|
break;
|
|
}
|
|
|
|
if (ServiceStatus.dwCurrentState == SERVICE_STOPPED) {
|
|
if (ServiceStatus.dwWin32ExitCode == ERROR_SERVICE_NEVER_STARTED) {
|
|
Sleep(500);
|
|
} else {
|
|
break;
|
|
}
|
|
} else if ( (ServiceStatus.dwCurrentState == SERVICE_RUNNING) ||
|
|
(ServiceStatus.dwCurrentState == SERVICE_CONTINUE_PENDING) ||
|
|
(ServiceStatus.dwCurrentState == SERVICE_PAUSE_PENDING) ||
|
|
(ServiceStatus.dwCurrentState == SERVICE_PAUSED) ) {
|
|
|
|
bStarted = TRUE;
|
|
|
|
} else if (ServiceStatus.dwCurrentState == SERVICE_START_PENDING) {
|
|
Sleep(500);
|
|
} else {
|
|
Sleep(500);
|
|
}
|
|
}
|
|
|
|
Exit:
|
|
|
|
if (lpServiceConfig) {
|
|
LocalFree (lpServiceConfig);
|
|
}
|
|
|
|
if (hService) {
|
|
CloseServiceHandle(hService);
|
|
}
|
|
|
|
if (hScManager) {
|
|
CloseServiceHandle(hScManager);
|
|
}
|
|
|
|
return bStarted;
|
|
}
|
|
|
|
//
|
|
// Wrappers around the verify routines.
|
|
//
|
|
|
|
BOOLEAN
|
|
PfSvVerifyScenarioBuffer(
|
|
PPF_SCENARIO_HEADER Scenario,
|
|
ULONG BufferSize,
|
|
PULONG FailedCheck
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This wrapper arounding PfVerifyScenarioBuffer traps exceptions such as in-page errors that
|
|
may happen when the system is under stress. Otherwise these non-fatal failures may take
|
|
down a service-host full of important system services.
|
|
|
|
Arguments:
|
|
|
|
See PfVerifyScenarioBuffer.
|
|
|
|
Return Value:
|
|
|
|
See PfVerifyScenarioBuffer.
|
|
|
|
--*/
|
|
|
|
{
|
|
BOOLEAN Success;
|
|
|
|
__try {
|
|
|
|
Success = PfVerifyScenarioBuffer(Scenario, BufferSize, FailedCheck);
|
|
|
|
} __except (EXCEPTION_EXECUTE_HANDLER) {
|
|
|
|
//
|
|
// We should not be masking other types of exceptions.
|
|
//
|
|
|
|
PFSVC_ASSERT(GetExceptionCode() == EXCEPTION_IN_PAGE_ERROR);
|
|
|
|
Success = FALSE;
|
|
*FailedCheck = (ULONG) GetExceptionCode();
|
|
|
|
}
|
|
|
|
return Success;
|
|
|
|
}
|
|
|
|
//
|
|
// Try to keep the verification code below at the end of the file so it is
|
|
// easier to copy.
|
|
//
|
|
|
|
//
|
|
// Verification code shared between the kernel and user mode
|
|
// components. This code should be kept in sync with a simple copy &
|
|
// paste, so don't add any kernel/user specific code/macros. Note that
|
|
// the prefix on the function names are Pf, just like it is with
|
|
// shared structures / constants.
|
|
//
|
|
|
|
BOOLEAN
|
|
PfWithinBounds(
|
|
PVOID Pointer,
|
|
PVOID Base,
|
|
ULONG Length
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Check whether the pointer is within Length bytes from the base.
|
|
|
|
Arguments:
|
|
|
|
Pointer - Pointer to check.
|
|
|
|
Base - Pointer to base of mapping/array etc.
|
|
|
|
Length - Number of bytes that are valid starting from Base.
|
|
|
|
Return Value:
|
|
|
|
TRUE - Pointer is within bounds.
|
|
|
|
FALSE - Pointer is not within bounds.
|
|
|
|
--*/
|
|
|
|
{
|
|
if (((PCHAR)Pointer < (PCHAR)Base) ||
|
|
((PCHAR)Pointer >= ((PCHAR)Base + Length))) {
|
|
|
|
return FALSE;
|
|
} else {
|
|
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
BOOLEAN
|
|
PfVerifyScenarioId (
|
|
PPF_SCENARIO_ID ScenarioId
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Verify that the scenario id is sensible.
|
|
|
|
Arguments:
|
|
|
|
ScenarioId - Scenario Id to verify.
|
|
|
|
Return Value:
|
|
|
|
TRUE - ScenarioId is fine.
|
|
FALSE - ScenarioId is corrupt.
|
|
|
|
--*/
|
|
|
|
{
|
|
LONG CurCharIdx;
|
|
|
|
//
|
|
// Make sure the scenario name is NUL terminated.
|
|
//
|
|
|
|
for (CurCharIdx = PF_SCEN_ID_MAX_CHARS; CurCharIdx >= 0; CurCharIdx--) {
|
|
|
|
if (ScenarioId->ScenName[CurCharIdx] == 0) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (ScenarioId->ScenName[CurCharIdx] != 0) {
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Make sure there is a scenario name.
|
|
//
|
|
|
|
if (CurCharIdx == 0) {
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Checks passed.
|
|
//
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOLEAN
|
|
PfVerifyScenarioBuffer(
|
|
PPF_SCENARIO_HEADER Scenario,
|
|
ULONG BufferSize,
|
|
PULONG FailedCheck
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Verify offset and indices in a scenario file are not beyond
|
|
bounds. This code is shared between the user mode service and
|
|
kernel mode component. If you update this function, update it in
|
|
both.
|
|
|
|
Arguments:
|
|
|
|
Scenario - Base of mapped view of the whole file.
|
|
|
|
BufferSize - Size of the scenario buffer.
|
|
|
|
FailedCheck - If verify failed, Id for the check that was failed.
|
|
|
|
Return Value:
|
|
|
|
TRUE - Scenario is fine.
|
|
FALSE - Scenario is corrupt.
|
|
|
|
--*/
|
|
|
|
{
|
|
PPF_SECTION_RECORD Sections;
|
|
PPF_SECTION_RECORD pSection;
|
|
ULONG SectionIdx;
|
|
PPF_PAGE_RECORD Pages;
|
|
PPF_PAGE_RECORD pPage;
|
|
LONG PageIdx;
|
|
PCHAR FileNames;
|
|
PCHAR pFileNameStart;
|
|
PCHAR pFileNameEnd;
|
|
PWCHAR pwFileName;
|
|
ULONG FileNameIdx;
|
|
LONG FailedCheckId;
|
|
ULONG NumRemainingPages;
|
|
ULONG FileNameDataSize;
|
|
ULONG NumPages;
|
|
LONG PreviousPageIdx;
|
|
ULONG FileNameSize;
|
|
BOOLEAN ScenarioVerified;
|
|
PCHAR MetadataInfoBase;
|
|
PPF_METADATA_RECORD MetadataRecordTable;
|
|
PPF_METADATA_RECORD MetadataRecord;
|
|
ULONG MetadataRecordIdx;
|
|
PWCHAR VolumePath;
|
|
PFILE_PREFETCH FilePrefetchInfo;
|
|
ULONG FilePrefetchInfoSize;
|
|
PPF_COUNTED_STRING DirectoryPath;
|
|
ULONG DirectoryIdx;
|
|
|
|
//
|
|
// Initialize locals.
|
|
//
|
|
|
|
FailedCheckId = 0;
|
|
|
|
//
|
|
// Initialize return value to FALSE. It will be set to TRUE only
|
|
// after all the checks pass.
|
|
//
|
|
|
|
ScenarioVerified = FALSE;
|
|
|
|
//
|
|
// The buffer should at least contain the scenario header.
|
|
//
|
|
|
|
if (BufferSize < sizeof(PF_SCENARIO_HEADER)) {
|
|
|
|
FailedCheckId = 10;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Check version and magic on the header.
|
|
//
|
|
|
|
if (Scenario->Version != PF_CURRENT_VERSION ||
|
|
Scenario->MagicNumber != PF_SCENARIO_MAGIC_NUMBER) {
|
|
|
|
FailedCheckId = 20;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// The buffer should not be greater than max allowed size.
|
|
//
|
|
|
|
if (BufferSize > PF_MAXIMUM_SCENARIO_SIZE) {
|
|
|
|
FailedCheckId = 25;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Check for legal scenario type.
|
|
//
|
|
|
|
if (Scenario->ScenarioType >= PfMaxScenarioType) {
|
|
FailedCheckId = 27;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Check limits on number of pages, sections etc.
|
|
//
|
|
|
|
if (Scenario->NumSections > PF_MAXIMUM_SECTIONS ||
|
|
Scenario->NumMetadataRecords > PF_MAXIMUM_SECTIONS ||
|
|
Scenario->NumPages > PF_MAXIMUM_PAGES ||
|
|
Scenario->FileNameInfoSize > PF_MAXIMUM_FILE_NAME_DATA_SIZE) {
|
|
|
|
FailedCheckId = 30;
|
|
goto cleanup;
|
|
}
|
|
|
|
if (Scenario->NumSections == 0 ||
|
|
Scenario->NumPages == 0 ||
|
|
Scenario->FileNameInfoSize == 0) {
|
|
|
|
FailedCheckId = 33;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Check limit on sensitivity.
|
|
//
|
|
|
|
if (Scenario->Sensitivity < PF_MIN_SENSITIVITY ||
|
|
Scenario->Sensitivity > PF_MAX_SENSITIVITY) {
|
|
|
|
FailedCheckId = 35;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Make sure the scenario id is valid.
|
|
//
|
|
|
|
if (!PfVerifyScenarioId(&Scenario->ScenarioId)) {
|
|
|
|
FailedCheckId = 37;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Initialize pointers to tables.
|
|
//
|
|
|
|
Sections = (PPF_SECTION_RECORD) ((PCHAR)Scenario + Scenario->SectionInfoOffset);
|
|
|
|
if (!PfWithinBounds(Sections, Scenario, BufferSize)) {
|
|
FailedCheckId = 40;
|
|
goto cleanup;
|
|
}
|
|
|
|
if (!PfWithinBounds((PCHAR) &Sections[Scenario->NumSections] - 1,
|
|
Scenario,
|
|
BufferSize)) {
|
|
FailedCheckId = 45;
|
|
goto cleanup;
|
|
}
|
|
|
|
Pages = (PPF_PAGE_RECORD) ((PCHAR)Scenario + Scenario->PageInfoOffset);
|
|
|
|
if (!PfWithinBounds(Pages, Scenario, BufferSize)) {
|
|
FailedCheckId = 50;
|
|
goto cleanup;
|
|
}
|
|
|
|
if (!PfWithinBounds((PCHAR) &Pages[Scenario->NumPages] - 1,
|
|
Scenario,
|
|
BufferSize)) {
|
|
FailedCheckId = 55;
|
|
goto cleanup;
|
|
}
|
|
|
|
FileNames = (PCHAR)Scenario + Scenario->FileNameInfoOffset;
|
|
|
|
if (!PfWithinBounds(FileNames, Scenario, BufferSize)) {
|
|
FailedCheckId = 60;
|
|
goto cleanup;
|
|
}
|
|
|
|
if (!PfWithinBounds(FileNames + Scenario->FileNameInfoSize - 1,
|
|
Scenario,
|
|
BufferSize)) {
|
|
FailedCheckId = 70;
|
|
goto cleanup;
|
|
}
|
|
|
|
MetadataInfoBase = (PCHAR)Scenario + Scenario->MetadataInfoOffset;
|
|
MetadataRecordTable = (PPF_METADATA_RECORD) MetadataInfoBase;
|
|
|
|
if (!PfWithinBounds(MetadataInfoBase, Scenario, BufferSize)) {
|
|
FailedCheckId = 73;
|
|
goto cleanup;
|
|
}
|
|
|
|
if (!PfWithinBounds(MetadataInfoBase + Scenario->MetadataInfoSize - 1,
|
|
Scenario,
|
|
BufferSize)) {
|
|
FailedCheckId = 74;
|
|
goto cleanup;
|
|
}
|
|
|
|
if (!PfWithinBounds(((PCHAR) &MetadataRecordTable[Scenario->NumMetadataRecords]) - 1,
|
|
Scenario,
|
|
BufferSize)) {
|
|
FailedCheckId = 75;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Verify that sections contain valid information.
|
|
//
|
|
|
|
NumRemainingPages = Scenario->NumPages;
|
|
|
|
for (SectionIdx = 0; SectionIdx < Scenario->NumSections; SectionIdx++) {
|
|
|
|
pSection = &Sections[SectionIdx];
|
|
|
|
//
|
|
// Check if file name is within bounds.
|
|
//
|
|
|
|
pFileNameStart = FileNames + pSection->FileNameOffset;
|
|
|
|
if (!PfWithinBounds(pFileNameStart, Scenario, BufferSize)) {
|
|
FailedCheckId = 80;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Make sure there is a valid sized file name.
|
|
//
|
|
|
|
if (pSection->FileNameLength == 0) {
|
|
FailedCheckId = 90;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Check file name max length.
|
|
//
|
|
|
|
if (pSection->FileNameLength > PF_MAXIMUM_SECTION_FILE_NAME_LENGTH) {
|
|
FailedCheckId = 100;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Note that pFileNameEnd gets a -1 so it is the address of
|
|
// the last byte.
|
|
//
|
|
|
|
FileNameSize = (pSection->FileNameLength + 1) * sizeof(WCHAR);
|
|
pFileNameEnd = pFileNameStart + FileNameSize - 1;
|
|
|
|
if (!PfWithinBounds(pFileNameEnd, Scenario, BufferSize)) {
|
|
FailedCheckId = 110;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Check if the file name is NUL terminated.
|
|
//
|
|
|
|
pwFileName = (PWCHAR) pFileNameStart;
|
|
|
|
if (pwFileName[pSection->FileNameLength] != 0) {
|
|
FailedCheckId = 120;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Check max number of pages in a section.
|
|
//
|
|
|
|
if (pSection->NumPages > PF_MAXIMUM_SECTION_PAGES) {
|
|
FailedCheckId = 140;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Make sure NumPages for the section is at least less
|
|
// than the remaining pages in the scenario. Then update the
|
|
// remaining pages.
|
|
//
|
|
|
|
if (pSection->NumPages > NumRemainingPages) {
|
|
FailedCheckId = 150;
|
|
goto cleanup;
|
|
}
|
|
|
|
NumRemainingPages -= pSection->NumPages;
|
|
|
|
//
|
|
// Verify that there are NumPages pages in our page list and
|
|
// they are sorted by file offset.
|
|
//
|
|
|
|
PageIdx = pSection->FirstPageIdx;
|
|
NumPages = 0;
|
|
PreviousPageIdx = PF_INVALID_PAGE_IDX;
|
|
|
|
while (PageIdx != PF_INVALID_PAGE_IDX) {
|
|
|
|
//
|
|
// Check that page idx is within range.
|
|
//
|
|
|
|
if (PageIdx < 0 || (ULONG) PageIdx >= Scenario->NumPages) {
|
|
FailedCheckId = 160;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// If this is not the first page record, make sure it
|
|
// comes after the previous one. We also check for
|
|
// duplicate offset here.
|
|
//
|
|
|
|
if (PreviousPageIdx != PF_INVALID_PAGE_IDX) {
|
|
if (Pages[PageIdx].FileOffset <=
|
|
Pages[PreviousPageIdx].FileOffset) {
|
|
|
|
FailedCheckId = 165;
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Update the last page index.
|
|
//
|
|
|
|
PreviousPageIdx = PageIdx;
|
|
|
|
//
|
|
// Get the next page index.
|
|
//
|
|
|
|
pPage = &Pages[PageIdx];
|
|
PageIdx = pPage->NextPageIdx;
|
|
|
|
//
|
|
// Update the number of pages we've seen on the list so
|
|
// far. If it is greater than what there should be on the
|
|
// list we have a problem. We may have even hit a list.
|
|
//
|
|
|
|
NumPages++;
|
|
if (NumPages > pSection->NumPages) {
|
|
FailedCheckId = 170;
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Make sure the section has exactly the number of pages it
|
|
// says it does.
|
|
//
|
|
|
|
if (NumPages != pSection->NumPages) {
|
|
FailedCheckId = 180;
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
//
|
|
// We should have accounted for all pages in the scenario.
|
|
//
|
|
|
|
if (NumRemainingPages) {
|
|
FailedCheckId = 190;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Make sure metadata prefetch records make sense.
|
|
//
|
|
|
|
for (MetadataRecordIdx = 0;
|
|
MetadataRecordIdx < Scenario->NumMetadataRecords;
|
|
MetadataRecordIdx++) {
|
|
|
|
MetadataRecord = &MetadataRecordTable[MetadataRecordIdx];
|
|
|
|
//
|
|
// Make sure that the volume path is within bounds and NUL
|
|
// terminated.
|
|
//
|
|
|
|
VolumePath = (PWCHAR)(MetadataInfoBase + MetadataRecord->VolumeNameOffset);
|
|
|
|
if (!PfWithinBounds(VolumePath, Scenario, BufferSize)) {
|
|
FailedCheckId = 200;
|
|
goto cleanup;
|
|
}
|
|
|
|
if (!PfWithinBounds(((PCHAR)(VolumePath + MetadataRecord->VolumeNameLength + 1)) - 1,
|
|
Scenario,
|
|
BufferSize)) {
|
|
FailedCheckId = 210;
|
|
goto cleanup;
|
|
}
|
|
|
|
if (VolumePath[MetadataRecord->VolumeNameLength] != 0) {
|
|
FailedCheckId = 220;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Make sure that FilePrefetchInformation is within bounds.
|
|
//
|
|
|
|
FilePrefetchInfo = (PFILE_PREFETCH)
|
|
(MetadataInfoBase + MetadataRecord->FilePrefetchInfoOffset);
|
|
|
|
if (!PfWithinBounds(FilePrefetchInfo, Scenario, BufferSize)) {
|
|
FailedCheckId = 230;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Its size should be greater than size of a FILE_PREFETCH
|
|
// structure (so we can safely access the fields).
|
|
//
|
|
|
|
if (MetadataRecord->FilePrefetchInfoSize < sizeof(FILE_PREFETCH)) {
|
|
FailedCheckId = 240;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// It should be for prefetching file creates.
|
|
//
|
|
|
|
if (FilePrefetchInfo->Type != FILE_PREFETCH_TYPE_FOR_CREATE) {
|
|
FailedCheckId = 250;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// There should not be more entries then are files and
|
|
// directories. The number of inidividual directories may be
|
|
// more than what we allow for, but it would be highly rare to
|
|
// be suspicious and thus ignored.
|
|
//
|
|
|
|
if (FilePrefetchInfo->Count > PF_MAXIMUM_DIRECTORIES + PF_MAXIMUM_SECTIONS) {
|
|
FailedCheckId = 260;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Its size should match the size calculated by number of file
|
|
// index numbers specified in the header.
|
|
//
|
|
|
|
FilePrefetchInfoSize = sizeof(FILE_PREFETCH);
|
|
if (FilePrefetchInfo->Count) {
|
|
FilePrefetchInfoSize += (FilePrefetchInfo->Count - 1) * sizeof(ULONGLONG);
|
|
}
|
|
|
|
if (!PfWithinBounds((PCHAR) FilePrefetchInfo + MetadataRecord->FilePrefetchInfoSize - 1,
|
|
Scenario,
|
|
BufferSize)) {
|
|
FailedCheckId = 270;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Make sure that the directory paths for this volume make
|
|
// sense.
|
|
//
|
|
|
|
if (MetadataRecord->NumDirectories > PF_MAXIMUM_DIRECTORIES) {
|
|
FailedCheckId = 280;
|
|
goto cleanup;
|
|
}
|
|
|
|
DirectoryPath = (PPF_COUNTED_STRING)
|
|
(MetadataInfoBase + MetadataRecord->DirectoryPathsOffset);
|
|
|
|
for (DirectoryIdx = 0;
|
|
DirectoryIdx < MetadataRecord->NumDirectories;
|
|
DirectoryIdx ++) {
|
|
|
|
//
|
|
// Make sure head of the structure is within bounds.
|
|
//
|
|
|
|
if (!PfWithinBounds((PCHAR)DirectoryPath + sizeof(PF_COUNTED_STRING) - 1,
|
|
Scenario,
|
|
BufferSize)) {
|
|
FailedCheckId = 290;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Check the length of the string.
|
|
//
|
|
|
|
if (DirectoryPath->Length >= PF_MAXIMUM_SECTION_FILE_NAME_LENGTH) {
|
|
FailedCheckId = 300;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Make sure end of the string is within bounds.
|
|
//
|
|
|
|
if (!PfWithinBounds((PCHAR)(&DirectoryPath->String[DirectoryPath->Length + 1]) - 1,
|
|
Scenario,
|
|
BufferSize)) {
|
|
FailedCheckId = 310;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Make sure the string is NUL terminated.
|
|
//
|
|
|
|
if (DirectoryPath->String[DirectoryPath->Length] != 0) {
|
|
FailedCheckId = 320;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Set pointer to next DirectoryPath.
|
|
//
|
|
|
|
DirectoryPath = (PPF_COUNTED_STRING)
|
|
(&DirectoryPath->String[DirectoryPath->Length + 1]);
|
|
}
|
|
}
|
|
|
|
//
|
|
// We've passed all the checks.
|
|
//
|
|
|
|
ScenarioVerified = TRUE;
|
|
|
|
cleanup:
|
|
|
|
*FailedCheck = FailedCheckId;
|
|
|
|
return ScenarioVerified;
|
|
}
|
|
|
|
BOOLEAN
|
|
PfVerifyTraceBuffer(
|
|
PPF_TRACE_HEADER Trace,
|
|
ULONG BufferSize,
|
|
PULONG FailedCheck
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Verify offset and indices in a trace buffer are not beyond
|
|
bounds. This code is shared between the user mode service and
|
|
kernel mode component. If you update this function, update it in
|
|
both.
|
|
|
|
Arguments:
|
|
|
|
Trace - Base of Trace buffer.
|
|
|
|
BufferSize - Size of the scenario file / mapping.
|
|
|
|
FailedCheck - If verify failed, Id for the check that was failed.
|
|
|
|
Return Value:
|
|
|
|
TRUE - Trace is fine.
|
|
FALSE - Trace is corrupt;
|
|
|
|
--*/
|
|
|
|
{
|
|
LONG FailedCheckId;
|
|
PVOID Offset;
|
|
PPF_LOG_ENTRY LogEntries;
|
|
PPF_SECTION_INFO Section;
|
|
PPF_VOLUME_INFO VolumeInfo;
|
|
ULONG SectionLength;
|
|
ULONG EntryIdx;
|
|
ULONG SectionIdx;
|
|
ULONG TotalFaults;
|
|
ULONG PeriodIdx;
|
|
ULONG VolumeIdx;
|
|
BOOLEAN TraceVerified;
|
|
ULONG VolumeInfoSize;
|
|
|
|
//
|
|
// Initialize locals:
|
|
//
|
|
|
|
FailedCheckId = 0;
|
|
|
|
//
|
|
// Initialize return value to FALSE. It will be set to TRUE only
|
|
// after all the checks pass.
|
|
//
|
|
|
|
TraceVerified = FALSE;
|
|
|
|
//
|
|
// The buffer should at least contain the scenario header.
|
|
//
|
|
|
|
if (BufferSize < sizeof(PF_TRACE_HEADER)) {
|
|
FailedCheckId = 10;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Check version and magic on the header.
|
|
//
|
|
|
|
if (Trace->Version != PF_CURRENT_VERSION ||
|
|
Trace->MagicNumber != PF_TRACE_MAGIC_NUMBER) {
|
|
FailedCheckId = 20;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// The buffer should not be greater than max allowed size.
|
|
//
|
|
|
|
if (BufferSize > PF_MAXIMUM_TRACE_SIZE) {
|
|
FailedCheckId = 23;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Check for legal scenario type.
|
|
//
|
|
|
|
if (Trace->ScenarioType >= PfMaxScenarioType) {
|
|
FailedCheckId = 25;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Check limits on number of pages, sections etc.
|
|
//
|
|
|
|
if (Trace->NumSections > PF_MAXIMUM_SECTIONS ||
|
|
Trace->NumEntries > PF_MAXIMUM_LOG_ENTRIES ||
|
|
Trace->NumVolumes > PF_MAXIMUM_SECTIONS) {
|
|
FailedCheckId = 30;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Check buffer size and the size of the trace.
|
|
//
|
|
|
|
if (Trace->Size != BufferSize) {
|
|
FailedCheckId = 35;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Make sure the scenario id is valid.
|
|
//
|
|
|
|
if (!PfVerifyScenarioId(&Trace->ScenarioId)) {
|
|
|
|
FailedCheckId = 37;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Check Bounds of Trace Buffer
|
|
//
|
|
|
|
LogEntries = (PPF_LOG_ENTRY) ((PCHAR)Trace + Trace->TraceBufferOffset);
|
|
|
|
if (!PfWithinBounds(LogEntries, Trace, BufferSize)) {
|
|
FailedCheckId = 40;
|
|
goto cleanup;
|
|
}
|
|
|
|
if (!PfWithinBounds((PCHAR)&LogEntries[Trace->NumEntries] - 1,
|
|
Trace,
|
|
BufferSize)) {
|
|
FailedCheckId = 50;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Verify pages contain valid information.
|
|
//
|
|
|
|
for (EntryIdx = 0; EntryIdx < Trace->NumEntries; EntryIdx++) {
|
|
|
|
//
|
|
// Make sure sequence number is within bounds.
|
|
//
|
|
|
|
if (LogEntries[EntryIdx].SectionId >= Trace->NumSections) {
|
|
FailedCheckId = 60;
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Verify section info entries are valid.
|
|
//
|
|
|
|
Section = (PPF_SECTION_INFO) ((PCHAR)Trace + Trace->SectionInfoOffset);
|
|
|
|
for (SectionIdx = 0; SectionIdx < Trace->NumSections; SectionIdx++) {
|
|
|
|
//
|
|
// Make sure the section is within bounds.
|
|
//
|
|
|
|
if (!PfWithinBounds(Section, Trace, BufferSize)) {
|
|
FailedCheckId = 70;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Make sure the file name is not too big.
|
|
//
|
|
|
|
if(Section->FileNameLength > PF_MAXIMUM_SECTION_FILE_NAME_LENGTH) {
|
|
FailedCheckId = 80;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Make sure the file name is NUL terminated.
|
|
//
|
|
|
|
if (Section->FileName[Section->FileNameLength] != 0) {
|
|
FailedCheckId = 90;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Calculate size of this section entry.
|
|
//
|
|
|
|
SectionLength = sizeof(PF_SECTION_INFO) +
|
|
(Section->FileNameLength) * sizeof(WCHAR);
|
|
|
|
//
|
|
// Make sure all of the data in the section info is within
|
|
// bounds.
|
|
//
|
|
|
|
if (!PfWithinBounds((PUCHAR)Section + SectionLength - 1,
|
|
Trace,
|
|
BufferSize)) {
|
|
|
|
FailedCheckId = 100;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Set pointer to next section.
|
|
//
|
|
|
|
Section = (PPF_SECTION_INFO) ((PUCHAR) Section + SectionLength);
|
|
}
|
|
|
|
//
|
|
// Check FaultsPerPeriod information.
|
|
//
|
|
|
|
if (!PfWithinBounds((PCHAR)&Trace->FaultsPerPeriod[PF_MAX_NUM_TRACE_PERIODS] - 1,
|
|
Trace,
|
|
BufferSize)) {
|
|
FailedCheckId = 110;
|
|
goto cleanup;
|
|
}
|
|
|
|
TotalFaults = 0;
|
|
|
|
for (PeriodIdx = 0; PeriodIdx < PF_MAX_NUM_TRACE_PERIODS; PeriodIdx++) {
|
|
TotalFaults += Trace->FaultsPerPeriod[PeriodIdx];
|
|
}
|
|
|
|
if (TotalFaults > Trace->NumEntries) {
|
|
FailedCheckId = 120;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Verify the volume information block.
|
|
//
|
|
|
|
VolumeInfo = (PPF_VOLUME_INFO) ((PCHAR)Trace + Trace->VolumeInfoOffset);
|
|
|
|
if (!PfWithinBounds(VolumeInfo, Trace, BufferSize)) {
|
|
FailedCheckId = 130;
|
|
goto cleanup;
|
|
}
|
|
|
|
if (!PfWithinBounds((PCHAR)VolumeInfo + Trace->VolumeInfoSize - 1,
|
|
Trace,
|
|
BufferSize)) {
|
|
FailedCheckId = 140;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// If there are sections, we should have at least one volume.
|
|
//
|
|
|
|
if (Trace->NumSections && !Trace->NumVolumes) {
|
|
FailedCheckId = 150;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Verify the volume info structures per volume.
|
|
//
|
|
|
|
for (VolumeIdx = 0; VolumeIdx < Trace->NumVolumes; VolumeIdx++) {
|
|
|
|
//
|
|
// Make sure the whole volume structure is within bounds. Note
|
|
// that VolumeInfo structure contains space for the
|
|
// terminating NUL.
|
|
//
|
|
|
|
VolumeInfoSize = sizeof(PF_VOLUME_INFO);
|
|
VolumeInfoSize += VolumeInfo->VolumePathLength * sizeof(WCHAR);
|
|
|
|
if (!PfWithinBounds((PCHAR) VolumeInfo + VolumeInfoSize - 1,
|
|
Trace,
|
|
BufferSize)) {
|
|
FailedCheckId = 160;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Verify that the volume path string is terminated.
|
|
//
|
|
|
|
if (VolumeInfo->VolumePath[VolumeInfo->VolumePathLength] != 0) {
|
|
FailedCheckId = 170;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Get the next volume.
|
|
//
|
|
|
|
VolumeInfo = (PPF_VOLUME_INFO) ((PCHAR) VolumeInfo + VolumeInfoSize);
|
|
|
|
//
|
|
// Make sure VolumeInfo is aligned.
|
|
//
|
|
|
|
VolumeInfo = PF_ALIGN_UP(VolumeInfo, _alignof(PF_VOLUME_INFO));
|
|
}
|
|
|
|
//
|
|
// We've passed all the checks.
|
|
//
|
|
|
|
TraceVerified = TRUE;
|
|
|
|
cleanup:
|
|
|
|
*FailedCheck = FailedCheckId;
|
|
|
|
return TraceVerified;
|
|
}
|
|
|