windows-nt/Source/XPSP1/NT/base/fs/utils/dfrg/dfrgntfs/bootoptimizentfs.cpp
2020-09-26 16:20:57 +08:00

2245 lines
72 KiB
C++

/**************************************************************************************************
FILENAME: BootOptimize.cpp
COPYRIGHT© 2001 Microsoft Corporation and Executive Software International, Inc.
DESCRIPTION:
Boot Optimize for NTFS.
**************************************************************************************************/
#include "stdafx.h"
extern "C"{
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
}
#include<nt.h>
#include<ntrtl.h>
#include<nturtl.h>
#include "Windows.h"
#include <winioctl.h>
#include <math.h>
#include <fcntl.h>
extern "C" {
#include "SysStruc.h"
}
#include "BootOptimizeNtfs.h"
#include "DfrgCmn.h"
#include "GetReg.h"
#include "defragcommon.h"
#include "Devio.h"
#include "movefile.h"
#include "fssubs.h"
#include "Alloc.h"
#define THIS_MODULE 'B'
#include "logfile.h"
#include "ntfssubs.h"
#include "dfrgengn.h"
#include "FreeSpace.h"
#include "extents.h"
#include "dfrgntfs.h"
//
// Hard-coded registry keys that we access to find the path to layout.ini,
// and other persisted data of interest (such as the boot optimise exclude
// zone beginning and end markers).
//
#define OPTIMAL_LAYOUT_KEY_PATH TEXT("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\OptimalLayout")
#define OPTIMAL_LAYOUT_FILE_VALUE_NAME TEXT("LayoutFilePath")
#define BOOT_OPTIMIZE_REGISTRY_PATH TEXT("SOFTWARE\\Microsoft\\Dfrg\\BootOptimizeFunction")
#define BOOT_OPTIMIZE_ENABLE_FLAG TEXT("Enable")
#define BOOT_OPTIMIZE_REGISTRY_LCNSTARTLOCATION TEXT("LcnStartLocation")
#define BOOT_OPTIMIZE_REGISTRY_LCNENDLOCATION TEXT("LcnEndLocation")
#define BOOT_OPTIMIZE_REGISTRY_COMPLETE TEXT("OptimizeComplete")
#define BOOT_OPTIMIZE_REGISTRY_ERROR TEXT("OptimizeError")
#define BOOT_OPTIMIZE_LAST_WRITTEN_DATETIME TEXT("FileTimeStamp")
#define BOOT_OPTIMIZE_MAX_FILE_SIZE_BYTES (32 * 1024 * 1024)
#define BOOT_OPTIMIZE_MAX_ZONE_SIZE_MB ((LONGLONG) (4 * 1024))
#define BOOT_OPTIMIZE_MAX_ZONE_SIZE_PERCENT (50)
#define BOOT_OPTIMIZE_ZONE_EXTEND_PERCENT (150)
#define BOOT_OPTIMISE_ZONE_RELOCATE_THRESHOLD (90)
#define BOOT_OPTIMIZE_ZONE_EXTEND_MIN_SIZE_BYTES (100 * 1024 * 1024)
BOOL
UpdateInMultipleTrees(
IN PFREE_SPACE_ENTRY pOldEntry,
IN PFREE_SPACE_ENTRY pNewEntry
);
/*****************************************************************************************************************
ROUTINE DESCRIPTION:
Get a rough idea of how many records are in the file and triple it, to make an estimation
of how many files are in the boot optimize file, and I triple it to account for multiple
stream files. Also make the assumption that the file count is atleast 300, so that I can
allocate enough memory to hold all the records.
INPUT:
full path name to the boot optimize file
RETURN:
triple the number of records in the boot optimize file.
*/
DWORD CountNumberofRecordsinFile(
IN LPCTSTR lpBootOptimzePath
)
{
DWORD dwNumberofRecords = 0; //the number of records in the input file
TCHAR tBuffer [MAX_PATH]; //temporary buffer to the input string
ULONG ulLength; //length of the line read in by fgetts
FILE* fBootOptimizeFile; //File Pointer to fBootOptimizeFile
//set read mode to binary
_fmode = _O_BINARY;
//open the file
//if I can't open the file, return a record count of zero
fBootOptimizeFile = _tfopen(lpBootOptimzePath,TEXT("r"));
if(fBootOptimizeFile == NULL)
{
return 0;
}
//read the entire file and count the number of records
while(_fgetts(tBuffer,MAX_PATH - 1,fBootOptimizeFile) != 0)
{
// check for terminating carriage return.
ulLength = wcslen(tBuffer);
if (ulLength && (tBuffer[ulLength - 1] == TEXT('\n'))) {
dwNumberofRecords++;
}
}
fclose(fBootOptimizeFile);
//triple the number of records we have
if(dwNumberofRecords < 100)
{
dwNumberofRecords = 100;
}
return dwNumberofRecords;
}
/******************************************************************************
ROUTINE DESCRIPTION:
This allocates memory of size cbSize bytes. Note that cbSize MUST be the
size we're expecting it to be (based on the slab-allocator initialisation),
since our slab allocator can only handle packets of one size.
INPUT:
pTable - The table that the comparison is being made for (not used)
cbSize - The count in bytes of the memory needed
RETURN:
Pointer to allocated memory of size cbSize; NULL if the system is out
of memory, or cbSize is not what the slab allocator was initialised with.
*/
PVOID
NTAPI
BootOptimiseAllocateRoutine(
IN PRTL_GENERIC_TABLE pTable,
IN CLONG cbSize
)
{
PVOID pMemory = NULL;
//
// Sanity-check to make sure that we're being asked for packets of the
// "correct" size, since our slab-allocator can only deal with packets
// of a given size
//
if ((cbSize + sizeof(PVOID)) == VolData.SaBootOptimiseFilesContext.dwPacketSize) {
//
// size was correct; call our allocator
//
pMemory = SaAllocatePacket(&VolData.SaBootOptimiseFilesContext);
}
else {
//
// Oops, we have a problem!
//
Trace(error, "Internal Error. BootOptimiseAllocateRoutine called with "
"unexpected size (%lu instead of %lu).",
cbSize, VolData.SaBootOptimiseFilesContext.dwPacketSize - sizeof(PVOID));
assert(FALSE);
}
return pMemory;
UNREFERENCED_PARAMETER(pTable);
}
/******************************************************************************
ROUTINE DESCRIPTION:
This frees a packet allocated by BootOptimiseAllocateRoutine
INPUT:
pTable - The table that the comparison is being made for (not used)
pvBuffer - Pointer to the memory to be freed. This pointer should not
be used after this routine is called.
RETURN:
VOID
*/
VOID
NTAPI
BootOptimiseFreeRoutine(
IN PRTL_GENERIC_TABLE pTable,
IN PVOID pvBuffer
)
{
assert(pvBuffer);
SaFreePacket(&VolData.SaBootOptimiseFilesContext, pvBuffer);
UNREFERENCED_PARAMETER(pTable);
}
/******************************************************************************
ROUTINE DESCRIPTION:
Comparison routine to compare the FileRecordNumber of two FILE_LIST_ENTRY
records.
INPUT:
pTable - the table that the comparison is being made for (not used)
pNode1 - the first FILE_LIST_ENTRY to be compared
pNode2 - the second FILE_LIST_ENTRY to be compared
RETURN:
RtlGenericLessThan if pNode1 < pNode2
RtlGenericGreaterThan if pNode1 > pNode2
RtlGenericEqual if pNode1 == pNode2
*/
RTL_GENERIC_COMPARE_RESULTS
NTAPI
BootOptimiseFrnCompareRoutine(
IN PRTL_GENERIC_TABLE pTable,
IN PVOID pNode1,
IN PVOID pNode2
)
{
PFILE_LIST_ENTRY pEntry1 = (PFILE_LIST_ENTRY) pNode1;
PFILE_LIST_ENTRY pEntry2 = (PFILE_LIST_ENTRY) pNode2;
RTL_GENERIC_COMPARE_RESULTS result = GenericEqual;
//
// These shouldn't ever be NULL
//
assert(pNode1 && pNode2);
if (pEntry1->FileRecordNumber < pEntry2->FileRecordNumber) {
result = GenericLessThan;
}
else if (pEntry1->FileRecordNumber > pEntry2->FileRecordNumber) {
result = GenericGreaterThan;
}
//
// Default is GenericEqual
//
return result;
}
/******************************************************************************
ROUTINE DESCRIPTION:
Comparison routine to compare the StartingLcn of two FILE_LIST_ENTRY
records.
INPUT:
pTable - the table that the comparison is being made for (not used)
pNode1 - the first FILE_LIST_ENTRY to be compared
pNode2 - the second FILE_LIST_ENTRY to be compared
RETURN:
RtlGenericLessThan if pNode1 < pNode2
RtlGenericGreaterThan if pNode1 > pNode2
RtlGenericEqual if pNode1 == pNode2
*/
RTL_GENERIC_COMPARE_RESULTS
NTAPI
BootOptimiseStartLcnCompareRoutine(
IN PRTL_GENERIC_TABLE pTable,
PVOID pNode1,
PVOID pNode2
)
{
PFILE_LIST_ENTRY pEntry1 = (PFILE_LIST_ENTRY) pNode1;
PFILE_LIST_ENTRY pEntry2 = (PFILE_LIST_ENTRY) pNode2;
RTL_GENERIC_COMPARE_RESULTS result = GenericEqual;
//
// These shouldn't ever be NULL
//
assert(pNode1 && pNode2);
if (pEntry1->StartingLcn < pEntry2->StartingLcn) {
result = GenericLessThan;
}
else if (pEntry1->StartingLcn > pEntry2->StartingLcn) {
result = GenericGreaterThan;
}
//
// Default is GenericEqual
//
return result;
}
/******************************************************************************
ROUTINE DESCRIPTION:
Initialisation routine for the BootOptimiseTables.
INPUT:
pBootOptimiseTable - pointer to table that will contain a list of
files that are to be preferentially laid out at the start of the disk
pFilesInExcludeZoneTable - pointer to the table that will contain a list
of all the files that are in the boot-optimise zone but not in the
boot-optimise table (i.e, this table containts the list of files that
need to be evicted)
RETURN:
TRUE - Initialisation completed successfully
FALSE - Fatal errors were encountered during initialisation
*/
BOOL
InitialiseBootOptimiseTables(
IN PRTL_GENERIC_TABLE pBootOptimiseTable,
IN PRTL_GENERIC_TABLE pFilesInExcludeZoneTable
)
{
PVOID pTableContext = NULL;
BOOL bResult = FALSE;
//
// Initialise the Slab Allocator context that will be used to allocate
// packets for these two tables. The two tables will be holding
// FILE_LIST_ENTRYs.
//
bResult = SaInitialiseContext(&VolData.SaBootOptimiseFilesContext,
sizeof(FILE_LIST_ENTRY),
64*1024);
//
// And initialise the two tables
//
if (bResult) {
RtlInitializeGenericTable(pBootOptimiseTable,
BootOptimiseFrnCompareRoutine,
BootOptimiseAllocateRoutine,
BootOptimiseFreeRoutine,
pTableContext);
RtlInitializeGenericTable(pFilesInExcludeZoneTable,
BootOptimiseStartLcnCompareRoutine,
BootOptimiseAllocateRoutine,
BootOptimiseFreeRoutine,
pTableContext);
}
return bResult;
}
/******************************************************************************
ROUTINE DESCRIPTION:
Routine to free all the packets belonging to the two tables, and re-init
them.
INPUT:
pBootOptimiseTable - pointer to table that contains a list of files that
are to be preferentially laid out at the beginning of the disk
pFilesInExcludeZoneTable - pointer to the table that contains a list
of all the files that are in the boot-optimise zone but not in the
boot-optimise table (i.e, files that need to be evicted)
RETURN:
VOID
*/
VOID
UnInitialiseBootOptimiseTables(
IN PRTL_GENERIC_TABLE pBootOptimiseTable,
IN PRTL_GENERIC_TABLE pFilesInExcludeZoneTable
)
{
PVOID pTableContext = NULL;
BOOL bResult = FALSE;
RtlInitializeGenericTable(pBootOptimiseTable,
BootOptimiseFrnCompareRoutine,
BootOptimiseAllocateRoutine,
BootOptimiseFreeRoutine,
pTableContext);
RtlInitializeGenericTable(pFilesInExcludeZoneTable,
BootOptimiseStartLcnCompareRoutine,
BootOptimiseAllocateRoutine,
BootOptimiseFreeRoutine,
pTableContext);
SaFreeAllPackets(&VolData.SaBootOptimiseFilesContext);
}
/******************************************************************************
ROUTINE DESCRIPTION:
Open the specified file with read and synchronize attributes, and return
a handle to it.
INPUT:
lpFilePath - file to be opened
RETURN:
HANDLE to the file or INVALID_HANDLE_VALUE
*/
HANDLE
GetFileHandle(
IN LPCTSTR lpFilePath
)
{
HANDLE hFile = INVALID_HANDLE_VALUE;
hFile = CreateFile(lpFilePath,
FILE_READ_ATTRIBUTES | SYNCHRONIZE,
0,
NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_OPEN_REPARSE_POINT,
NULL
);
return hFile;
}
/******************************************************************************
ROUTINE DESCRIPTION:
Get the FileRecordNumber for a file given a handle to it
INPUT:
hFile - handle to the file of interest
RETURN:
FRN for the given file, -1 if errors were encountered.
*/
LONGLONG
GetFileRecordNumber(
IN CONST HANDLE hFile
)
{
FILE_INTERNAL_INFORMATION internalInformation;
IO_STATUS_BLOCK ioStatusBlock;
LONGLONG fileRecordNumber = -1;
NTSTATUS ntStatus = STATUS_UNSUCCESSFUL;
ZeroMemory(&internalInformation, sizeof(FILE_INTERNAL_INFORMATION));
ZeroMemory(&ioStatusBlock, sizeof(IO_STATUS_BLOCK));
//
// The FileRecordNumber is the lower part of the InternalInformation
// returned for the file
//
ntStatus = NtQueryInformationFile(hFile,
&ioStatusBlock,
&internalInformation,
sizeof(FILE_INTERNAL_INFORMATION),
FileInternalInformation
);
if (NT_SUCCESS(ntStatus) && (NT_SUCCESS(ioStatusBlock.Status))) {
//
// The FRN is the lower 48-bits of the value returned
//
fileRecordNumber = (LONGLONG) (internalInformation.IndexNumber.QuadPart & 0x0000FFFFFFFFFFFF);
}
return fileRecordNumber;
}
/******************************************************************************
ROUTINE DESCRIPTION:
Get the size of the file in clusters from calling FSCL_GET_RETRIEVAL_POINTERS.
INPUT:
hFile - The handle to the file of interest
RETURN:
The size of the file in clusters
*/
LONGLONG
GetFileSizeInfo(
IN HANDLE hFile
)
{
ULONGLONG ulSizeofFileInClusters = 0; //size of the file in clusters
int i;
ULONGLONG startVcn = 0; //starting VCN of the file, always 0
STARTING_VCN_INPUT_BUFFER startingVcn; //starting VCN Buffer
ULONG BytesReturned = 0; //number of bytes returned by ESDeviceIoControl
HANDLE hRetrievalPointersBuffer = NULL; //Handle to the Retrieval Pointers Buffer
PRETRIEVAL_POINTERS_BUFFER pRetrievalPointersBuffer = NULL; //pointer to the Retrieval Pointer
PLARGE_INTEGER pRetrievalPointers = NULL; //Pointer to retrieval pointers
ULONG RetrievalPointers = 0x100; //Number of extents for the file, try 256 first
BOOL bGetRetrievalPointersMore = TRUE; //boolean to test the end of getting retrieval pointers
if (INVALID_HANDLE_VALUE == hFile) {
return 0;
}
// zero the memory of the starting VCN input buffer
ZeroMemory(&startVcn, sizeof(STARTING_VCN_INPUT_BUFFER));
// Read the retrieval pointers into a buffer in memory.
while (bGetRetrievalPointersMore) {
//0.0E00 Allocate a RetrievalPointersBuffer.
if (!AllocateMemory(sizeof(RETRIEVAL_POINTERS_BUFFER) + (RetrievalPointers * 2 * sizeof(LARGE_INTEGER)),
&hRetrievalPointersBuffer,
(void**)(PCHAR*)&pRetrievalPointersBuffer)) {
return 0;
}
startingVcn.StartingVcn.QuadPart = 0;
if(ESDeviceIoControl(hFile,
FSCTL_GET_RETRIEVAL_POINTERS,
&startingVcn,
sizeof(STARTING_VCN_INPUT_BUFFER),
pRetrievalPointersBuffer,
(DWORD)GlobalSize(hRetrievalPointersBuffer),
&BytesReturned,
NULL)) {
bGetRetrievalPointersMore = FALSE;
}
else {
//This occurs on a zero length file (no clusters allocated).
if(GetLastError() == ERROR_HANDLE_EOF) {
//file is zero lenght, so return 0
//free the memory for the retrival pointers
//the while loop makes sure all occurances are unlocked
while (GlobalUnlock(hRetrievalPointersBuffer))
{
;
}
GlobalFree(hRetrievalPointersBuffer);
hRetrievalPointersBuffer = NULL;
return 0;
}
//0.0E00 Check to see if the error is not because the buffer is too small.
if(GetLastError() == ERROR_MORE_DATA)
{
//0.1E00 Double the buffer size until it's large enough to hold the file's extent list.
RetrievalPointers *= 2;
} else
{
//some other error, return 0
//free the memory for the retrival pointers
//the while loop makes sure all occurances are unlocked
while (GlobalUnlock(hRetrievalPointersBuffer))
{
;
}
GlobalFree(hRetrievalPointersBuffer);
hRetrievalPointersBuffer = NULL;
return 0;
}
}
}
//loop through the retrival pointer list and add up the size of the file
startVcn = pRetrievalPointersBuffer->StartingVcn.QuadPart;
for (i = 0; i < (ULONGLONG) pRetrievalPointersBuffer->ExtentCount; i++)
{
ulSizeofFileInClusters += pRetrievalPointersBuffer->Extents[i].NextVcn.QuadPart - startVcn;
startVcn = pRetrievalPointersBuffer->Extents[i].NextVcn.QuadPart;
}
if(hRetrievalPointersBuffer != NULL)
{
//free the memory for the retrival pointers
//the while loop makes sure all occurances are unlocked
while (GlobalUnlock(hRetrievalPointersBuffer))
{
;
}
GlobalFree(hRetrievalPointersBuffer);
hRetrievalPointersBuffer = NULL;
}
return ulSizeofFileInClusters;
}
/******************************************************************************
ROUTINE DESCRIPTION:
Checks if we have a valid file to be laid out at the beginning of the disk.
INPUT:
lpFilePath - The file name input from the list--typically, a line from
layout.ini
tcBootVolumeDriveLetter - Drive letter of the boot volume
bIsNtfs - TRUE if the volume is NTFS, FALSE otherwise
OUTPUT:
pFileRecordNumber - The FRN of the file, if it is a valid file
pClusterCount - The file-size (in clusters), if it is a valid file
RETURN:
TRUE if this is a valid file,
FALSE if it is not.
*/
BOOL IsAValidFile(
IN LPTSTR lpFilePath,
IN CONST TCHAR tcBootVolumeDriveLetter,
IN CONST BOOL bIsNtfs,
OUT LONGLONG *pFileRecordNumber OPTIONAL,
OUT LONGLONG *pClusterCount OPTIONAL
)
{
TCHAR tcFileName[MAX_PATH+1]; // Just the file name portion of lpFilePath
TCHAR tcFileDriveLetter; // Drive letter for current file (lpFilePath)
HANDLE hFile = NULL; // Temporary handle to check file size, etc
BOOL bFileIsDirectory = FALSE; // Flag to check if current file is a dir
LONGLONG FileSizeClusters = 0;
BY_HANDLE_FILE_INFORMATION FileInformation; // For checking if this is a directory
// Ignore blank lines, and the root directory, in layout.ini
if (!lpFilePath || _tcslen(lpFilePath) <= 2) {
return FALSE;
}
// Ignore the group headers
if (NULL != _tcsstr(lpFilePath, TEXT("[OptimalLayoutFile]"))) {
return FALSE;
}
// Ignore the file = and version = lines
if(NULL != _tcsstr(lpFilePath, TEXT("Version="))) {
return FALSE;
}
//get the drive the file is on, if its not the boot drive, skip the file
tcFileDriveLetter = towupper(lpFilePath[0]);
if(tcFileDriveLetter != tcBootVolumeDriveLetter) { //files are on boot drive else skip them
return FALSE;
}
if ((lpFilePath[1] != TEXT(':')) ||
(lpFilePath[2] != TEXT('\\'))) {
return FALSE;
}
//get just the file name from the end of the path
if(_tcsrchr(lpFilePath,TEXT('\\')) != NULL) {
_tcscpy(tcFileName,_tcsrchr(lpFilePath,TEXT('\\'))+1);
}
else {
//not a valid name
return FALSE;
}
if(_tcsicmp(tcFileName,TEXT("BOOTSECT.DOS")) == 0) {
return FALSE;
}
if(_tcsicmp(tcFileName,TEXT("SAFEBOOT.FS")) == 0) {
return FALSE;
}
if(_tcsicmp(tcFileName,TEXT("SAFEBOOT.CSV")) == 0) {
return FALSE;
}
if(_tcsicmp(tcFileName,TEXT("SAFEBOOT.RSV")) == 0) {
return FALSE;
}
if(_tcsicmp(tcFileName,TEXT("HIBERFIL.SYS")) == 0) {
return FALSE;
}
if(_tcsicmp(tcFileName,TEXT("MEMORY.DMP")) == 0) {
return FALSE;
}
if(_tcsicmp(tcFileName,TEXT("PAGEFILE.SYS")) == 0) {
return FALSE;
}
// so far, so good. Now, we need to check if the file exists, and is
// too big
hFile = GetFileHandle(lpFilePath);
if (INVALID_HANDLE_VALUE == hFile) {
return FALSE;
}
// determine if directory file.
bFileIsDirectory = FALSE;
if (GetFileInformationByHandle(hFile, &FileInformation)) {
bFileIsDirectory = (FileInformation.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
}
if ((bFileIsDirectory) && (!bIsNtfs)) {
CloseHandle(hFile);
return FALSE;
}
if (pFileRecordNumber) {
*pFileRecordNumber = GetFileRecordNumber(hFile);
}
FileSizeClusters = GetFileSizeInfo(hFile);
if (pClusterCount) {
*pClusterCount = FileSizeClusters;
}
CloseHandle(hFile);
// We won't move files that are bigger than BOOT_OPTIMIZE_MAX_FILE_SIZE_BYTES MB
if (FileSizeClusters > (BOOT_OPTIMIZE_MAX_FILE_SIZE_BYTES / VolData.BytesPerCluster)) {
return FALSE;
}
//file is OK, return TRUE
return TRUE;
}
/******************************************************************************
ROUTINE DESCRIPTION:
Reads the layout.ini at the given location, and builds a list of valid
files that should be preferentially laid out at the start of the disk.
INPUT:
lpLayoutIni - Fill path to the file (layout.ini) containing the
list of files to be preferentially laid out.
tcBootVolumeDriveLetter - Drive letter of the boot volume
OUTPUT:
pBootOptimiseTable - Table to contain the files of interest
pClustersNeeded - The size (in clusters), that is needed for all the files
in the list.
RETURN:
TRUE if the list could successfully be built
FALSE otherwise
*/
BOOL
BuildBootOptimiseFileList(
IN OUT PRTL_GENERIC_TABLE pBootOptimiseTable,
IN LPCTSTR lpLayoutIni,
IN CONST TCHAR tcBootVolumeDriveLetter,
IN CONST BOOL bIsNtfs,
OUT LONGLONG *pClustersNeeded
)
{
PVOID pTableContext = NULL; // Temporary value used for the AVL Tables
BOOL bResult = TRUE; // The value to be returned
TCHAR tBuffer [MAX_PATH+1]; // Temporary buffer to the input string
ULONG ulLength = 0; // Length of the line read in by fgetts
FILE* fpLayoutIni = NULL; // File pointer to layout.ini
LONGLONG llClusterCount = 0, // ClusterCount of current File
llFileRecordNumber = -1; // FRN of current file
PVOID pvTemp = NULL; // Temporary value used for AVL Tables
BOOLEAN bNewElement = FALSE; // Temporary value used for AVL Tables
FILE_LIST_ENTRY FileEntry; // Current File
DWORD dwNumberofRecords = 0,
dwIndex = 0;
// Initialise out parameters
*pClustersNeeded = 0;
// Zero out local structs
ZeroMemory(&FileEntry, sizeof(FILE_LIST_ENTRY));
//
// Get a count of the number of entries in layout.ini, so that we can
// allocate an array to keep track of the LayoutIniEntryIndex <-> FRN
// mapping
//
dwNumberofRecords = 10 + CountNumberofRecordsinFile(lpLayoutIni);
if (dwNumberofRecords <= 10) {
bResult = FALSE;
goto EXIT;
}
Trace(log, "Number of Layout.Ini entries: %d", dwNumberofRecords-10);
if (!AllocateMemory(
(DWORD) (sizeof(LONGLONG) * dwNumberofRecords),
&(VolData.hBootOptimiseFrnList),
(PVOID*) &(VolData.pBootOptimiseFrnList)
)) {
bResult = FALSE;
goto EXIT;
}
// Set read mode to binary: layout.ini is a UNICODE file
_fmode = _O_BINARY;
// Open the file
fpLayoutIni = _tfopen(lpLayoutIni,TEXT("r"));
if (fpLayoutIni) {
// Read the entire file and check each file to make sure its valid,
// and then add to the list
while (_fgetts(tBuffer,MAX_PATH,fpLayoutIni) != 0) {
// Remove terminating carriage return.
ulLength = wcslen(tBuffer);
if (ulLength < 3) {
continue;
}
if (tBuffer[ulLength - 1] == TEXT('\n')) {
tBuffer[ulLength - 1] = 0;
ulLength--;
if (tBuffer[ulLength - 1] == TEXT('\r')) {
tBuffer[ulLength - 1] = 0;
ulLength--;
}
} else {
continue;
}
if (IsAValidFile(
tBuffer,
tcBootVolumeDriveLetter,
bIsNtfs,
&llFileRecordNumber,
&llClusterCount)
) {
// This is a valid file, copy the information of interest to
// the FILE_LIST_ENTRY structure and add it to our list.
//
// We set the starting LCN to max value at first (since we
// don't have this information at this time)--this will be
// set to the correct value during the analysis phase.
//
FileEntry.StartingLcn = VolData.TotalClusters;
FileEntry.ClusterCount = llClusterCount;
FileEntry.FileRecordNumber = llFileRecordNumber;
// Keep track of the total clusters needed
(*pClustersNeeded) += llClusterCount;
// And add this entry to our tree
pvTemp = RtlInsertElementGenericTable(
pBootOptimiseTable,
(PVOID) &FileEntry,
sizeof(FILE_LIST_ENTRY),
&bNewElement);
if (!pvTemp) {
// An allocation failed
bResult = FALSE;
assert(FALSE);
break;
}
if (dwIndex < dwNumberofRecords) {
VolData.pBootOptimiseFrnList[dwIndex] = llFileRecordNumber;
++dwIndex;
}
}
}
//
// Make sure we have an FRN of -1, at the end of the list, even if it
// means wiping the last real FRN (which should never be the case)
//
if (dwIndex >= dwNumberofRecords) {
dwIndex = dwNumberofRecords - 1;
}
VolData.pBootOptimiseFrnList[dwIndex] = -1;
//close the file at the end
fclose(fpLayoutIni);
}
else {
// Layout.Ini could not be opened for read access
bResult = FALSE;
}
EXIT:
return bResult;
}
/******************************************************************************
ROUTINE DESCRIPTION:
If the current file is on the list of files to be preferentially laid out
at the beginning of the disk, this routine updates the file record in our
AVL-tree with fields from VolData.
INPUT:
(Global) Various VolData fields
OUTPUT:
None;
May change an entry in VolData.BootOptimiseFileTable
RETURN:
TRUE if the file exists in our preferred list, and was updated
FALSE if the file is not one we're interesed in preferentially laying out
*/
BOOL
UpdateInBootOptimiseList(
IN PFILE_LIST_ENTRY pFileListEntry
)
{
FILE_LIST_ENTRY FileEntryToSearchFor;
PFILE_LIST_ENTRY pClosestMatchEntry = NULL;
PFILE_EXTENT_HEADER pFileExtentHeader = NULL;
static ULONG ulDeleteCount = 0;
PVOID pRestartKey = NULL;
LONGLONG FileRecordNumberToSearchFor = 0;
ZeroMemory(&FileEntryToSearchFor, sizeof(FILE_LIST_ENTRY));
if (pFileListEntry) {
FileRecordNumberToSearchFor = pFileListEntry->FileRecordNumber;
}
else {
FileRecordNumberToSearchFor = VolData.FileRecordNumber;
}
FileEntryToSearchFor.FileRecordNumber = FileRecordNumberToSearchFor;
pClosestMatchEntry = (PFILE_LIST_ENTRY) RtlEnumerateGenericTableLikeADirectory(
&VolData.BootOptimiseFileTable,
NULL,
NULL,
FALSE,
&pRestartKey,
&ulDeleteCount,
&FileEntryToSearchFor
);
if (!pClosestMatchEntry) {
//
// We couldn't find the closest match?
//
return FALSE;
}
if (pClosestMatchEntry->FileRecordNumber == FileRecordNumberToSearchFor) {
//
// We found an exact match. Update the fields of interest.
//
pClosestMatchEntry->StartingLcn =
(pFileListEntry ? pFileListEntry->StartingLcn : VolData.StartingLcn);
pClosestMatchEntry->ClusterCount = VolData.NumberOfClusters;
// Get a pointer to the file extent header.
pFileExtentHeader = (FILE_EXTENT_HEADER*)VolData.pExtentList;
//
// Fill in the file info. We only count *excess* extents since
// otherwise files with multiple streams would be "fragmented".
//
pClosestMatchEntry->ExcessExtentCount =
(UINT)VolData.NumberOfFragments - pFileExtentHeader->NumberOfStreams;
pClosestMatchEntry->Flags = 0;
// Set or clear the fragmented and directory flags as needed
if(VolData.bFragmented){
//Set the fragmented flag.
pClosestMatchEntry->Flags |= FLE_FRAGMENTED;
}
else{
//Clear the fragmented flag.
pClosestMatchEntry->Flags &= ~FLE_FRAGMENTED;
}
if(VolData.bDirectory){
//Set the directory flag.
pClosestMatchEntry->Flags |= FLE_DIRECTORY;
}
else{
//Clear the directory flag.
pClosestMatchEntry->Flags &= ~FLE_DIRECTORY;
}
pClosestMatchEntry->Flags |= FLE_BOOTOPTIMISE;
VolData.bBootOptimiseFile = TRUE;
VolData.BootOptimiseFileListTotalSize += VolData.NumberOfClusters;
if ((!VolData.bFragmented) &&
(VolData.StartingLcn >= VolData.BootOptimizeBeginClusterExclude) &&
((VolData.StartingLcn + VolData.NumberOfClusters) <= VolData.BootOptimizeEndClusterExclude)
) {
VolData.BootOptimiseFilesAlreadyInZoneSize += VolData.NumberOfClusters;
}
//
// We found and udpated this entry
//
if (!VolData.bFragmented) {
return TRUE;
}
}
//
// We didn't find an exact match, or the file is fragmented
//
return FALSE;
}
/******************************************************************************
ROUTINE DESCRIPTION:
Moves the file referred to by VolData to a location outside the
BootOptimise zone, if possible
INPUT:
(Global) Various VolData fields
OUTPUT:
None
File referred to by VolData is moved to a new location outside the
BootOptimise zone
RETURN:
TRUE if the file could successfully be moved
FALSE otherwise
*/
BOOL
EvictFile(
)
{
FILE_LIST_ENTRY NewFileListEntry; // entry for the file after the move
FREE_SPACE_ENTRY NewFreeSpaceEntry; // entry for the free space after the move
PRTL_GENERIC_TABLE pMoveToTable = NULL; // Table that will contain the file-entry after the move
PRTL_GENERIC_TABLE pMoveFromTable = NULL; // Table that contains the file-entry before the move
PVOID pvTemp = NULL; // Temporary pointer used for AVL-Tables
BOOL bDone = FALSE;
BOOL bResult = TRUE,
bFragmented = VolData.bFragmented;
BOOLEAN bNewElement = FALSE,
bElementDeleted = FALSE;
ZeroMemory(&NewFileListEntry, sizeof(FILE_LIST_ENTRY));
ZeroMemory(&NewFreeSpaceEntry, sizeof(FREE_SPACE_ENTRY));
//
// If the file is fragmented, the entry should be present in the
// FragementedFilesTable. If it isn't fragmented, the entry should be in
// the ContiguousFileTable
//
pMoveFromTable = (VolData.bFragmented ?
&VolData.FragmentedFileTable : &VolData.ContiguousFileTable);
// Get the extent list & number of fragments in the file.
if (GetExtentList(DEFAULT_STREAMS, NULL)) {
bDone = FALSE;
while (!bDone) {
bDone = TRUE;
if (FindSortedFreeSpace(&VolData.FreeSpaceTable)) {
//
// Found a free space chunk that was big enough. If it's
// before the file, move the file towards the start of the disk
//
//
// First, make a copy of the free-space and file-list entries,
// and delete them from our tables. We'll add in modified
// entries after the move.
//
CopyMemory(&NewFreeSpaceEntry,
VolData.pFreeSpaceEntry,
sizeof(FREE_SPACE_ENTRY)
);
bElementDeleted = RtlDeleteElementGenericTable(
&VolData.FreeSpaceTable,
(PVOID) VolData.pFreeSpaceEntry
);
if (!bElementDeleted) {
Trace(warn, "Errors encountered while moving file. "
"Could not find element in free space table. "
"StartingLCN: %I64u ClusterCount: %I64u",
NewFreeSpaceEntry.StartingLcn,
NewFreeSpaceEntry.ClusterCount
);
assert(FALSE);
}
VolData.pFreeSpaceEntry = &NewFreeSpaceEntry;
CopyMemory(&NewFileListEntry,
VolData.pFileListEntry,
sizeof(FILE_LIST_ENTRY)
);
bElementDeleted = RtlDeleteElementGenericTable(
pMoveFromTable,
(PVOID) VolData.pFileListEntry
);
if (bElementDeleted) {
VolData.pFileListEntry = &NewFileListEntry;
if (MoveNtfsFile()) {
//
// The file was successfully moved! Update our file-
// and free-space entries with the results of the move.
// We'll add these back to the appropriate trees in a bit.
//
NewFileListEntry.StartingLcn = VolData.pFreeSpaceEntry->StartingLcn;
VolData.pFreeSpaceEntry->StartingLcn += VolData.NumberOfClusters;
VolData.pFreeSpaceEntry->ClusterCount -= VolData.NumberOfClusters;
VolData.bFragmented = FALSE;
VolData.pFileListEntry->Flags &= ~FLE_FRAGMENTED;
//
// Since we successfully moved (defragmented) this file,
// it needs to be added to the ContiguousFilesTable
//
pMoveToTable = &VolData.ContiguousFileTable;
if (UpdateInBootOptimiseList(&NewFileListEntry)) {
//
// Prevent this file from being counted twice
//
VolData.BootOptimiseFileListTotalSize -= VolData.NumberOfClusters;
}
}
else {
//
// We could not move this file. Note that this could be
// because of a number of reasons, such as:
// 1. The free-space region is not really free
// 2. The file is on the list of unmoveable files, etc
//
GetNtfsFilePath();
Trace(warn, "Movefile failed. File %ws "
"StartingLcn:%I64d ClusterCount:%I64d. Free-space "
"StartingLcn:%I64d ClusterCount:%I64d Status:%lu",
VolData.vFileName.GetBuffer() + 48,
VolData.pFileListEntry->StartingLcn,
VolData.pFileListEntry->ClusterCount,
VolData.pFreeSpaceEntry->StartingLcn,
VolData.pFreeSpaceEntry->ClusterCount,
VolData.Status
);
if (VolData.Status == ERROR_RETRY) {
//
// Free space isn't really free; try again with
// a different free space
//
VolData.pFreeSpaceEntry->ClusterCount = 0;
bDone = FALSE;
}
//
// Since we didn't move this file, we should just add
// it back to the table it originally was in.
//
pMoveToTable = pMoveFromTable;
}
//
// Add this file-entry back to the appropriate file-table
//
pvTemp = RtlInsertElementGenericTable(
pMoveToTable,
(PVOID) VolData.pFileListEntry,
sizeof(FILE_LIST_ENTRY),
&bNewElement);
if (!pvTemp) {
//
// An allocation failed
//
Trace(warn, "Errors encountered while moving file: "
"Unable to add back file-entry to file table");
assert(FALSE);
bResult = FALSE;
break;
}
}
if (VolData.pFreeSpaceEntry->ClusterCount > 0) {
//
// And also, add the (possibly updated) free-space region
// to the FreeSpace list.
//
pvTemp = RtlInsertElementGenericTable(
&VolData.FreeSpaceTable,
(PVOID) VolData.pFreeSpaceEntry,
sizeof(FREE_SPACE_ENTRY),
&bNewElement);
if (!pvTemp) {
//
// An allocation failed
//
Trace(warn, "Errors encountered while moving file: "
"Unable to add back free-space to free-space table");
assert(FALSE);
bResult = FALSE;
break;
}
}
}
else {
//
// We could not find a free-space region big enough to move
// this file.
//
bResult = FALSE;
}
}
}
else {
//
// We could not get the extents for this file
//
bResult = FALSE;
}
//
// Clean-up
//
if(VolData.hFile != INVALID_HANDLE_VALUE) {
CloseHandle(VolData.hFile);
VolData.hFile = INVALID_HANDLE_VALUE;
}
if(VolData.hFreeExtents != NULL) {
while(GlobalUnlock(VolData.hFreeExtents))
;
GlobalFree(VolData.hFreeExtents);
VolData.hFreeExtents = NULL;
}
// update cluster array
PurgeExtentBuffer();
return bResult;
}
/******************************************************************************
ROUTINE DESCRIPTION:
Moves the file referred to by VolData to a location closer to the start of
the disk, if possible
INPUT:
bForce - if the file is not currently in the boot-optimise zone, and has to
be moved forward.
(Global) Various VolData fields
OUTPUT:
None
File referred to by VolData is moved to a new location if possible
RETURN:
TRUE if the file could successfully be moved
FALSE otherwise
*/
BOOL
MoveBootOptimiseFile(
IN CONST BOOL bForce
)
{
FILE_LIST_ENTRY NewFileListEntry; // entry for the file after the move
FREE_SPACE_ENTRY NewFreeSpaceEntry; // entry for the free space after the move
PRTL_GENERIC_TABLE pMoveToTable = NULL; // Table that will contain the file-entry after the move
PRTL_GENERIC_TABLE pMoveFromTable = NULL; // Table that contains the file-entry before the move
PVOID pvTemp = NULL; // Temporary pointer used for AVL-Tables
BOOL bDone = FALSE;
BOOL bResult = TRUE;
BOOLEAN bNewElement = FALSE,
bElementDeleted = FALSE;
ZeroMemory(&NewFileListEntry, sizeof(FILE_LIST_ENTRY));
ZeroMemory(&NewFreeSpaceEntry, sizeof(FREE_SPACE_ENTRY));
//
// If the file is fragmented, the entry should be present in the
// FragementedFilesTable. If it isn't fragmented, the entry should be in
// the ContiguousFileTable
//
pMoveFromTable = (VolData.bFragmented ?
&VolData.FragmentedFileTable : &VolData.ContiguousFileTable);
pMoveToTable = &VolData.ContiguousFileTable;
// Get the extent list & number of fragments in the file.
if (GetExtentList(DEFAULT_STREAMS, NULL)) {
bDone = FALSE;
while (!bDone) {
bDone = TRUE;
if (FindFreeSpaceWithMultipleTrees(VolData.NumberOfClusters,
(bForce ? VolData.TotalClusters : VolData.StartingLcn))
) {
//
// Found a free space chunk that was big enough. If it's
// before the file, move the file towards the start of the disk
//
//
// First, make a copy of the free-space and file-list entries,
// and delete them from our tables. We'll add in modified
// entries after the move.
//
CopyMemory(&NewFileListEntry,
VolData.pFileListEntry,
sizeof(FILE_LIST_ENTRY)
);
bElementDeleted = RtlDeleteElementGenericTable(
pMoveFromTable,
(PVOID)VolData.pFileListEntry
);
if (bElementDeleted) {
VolData.pFileListEntry = &NewFileListEntry;
if (MoveNtfsFile()) {
//
// The file was successfully moved! Update our file-
// and free-space entries with the results of the move.
// We'll add these back to the appropriate trees in a bit.
//
NewFileListEntry.StartingLcn = VolData.pFreeSpaceEntry->StartingLcn;
NewFreeSpaceEntry.StartingLcn = VolData.pFreeSpaceEntry->StartingLcn + VolData.NumberOfClusters;
NewFreeSpaceEntry.ClusterCount = VolData.pFreeSpaceEntry->ClusterCount - VolData.NumberOfClusters;
//
// Since we successfully moved (defragmented) this file,
// it needs to be added to the ContiguousFilesTable
//
pMoveToTable = &VolData.ContiguousFileTable;
//
// Update the free-space entry
//
UpdateInMultipleTrees(VolData.pFreeSpaceEntry, &NewFreeSpaceEntry);
VolData.pFreeSpaceEntry = NULL;
}
else {
//
// We could not move this file. Note that this could be
// because of a number of reasons, such as:
// 1. The free-space region is not really free
// 2. The file is on the list of unmoveable files, etc
//
GetNtfsFilePath();
Trace(warn, "Movefile failed. File %ws "
"StartingLcn:%I64d ClusterCount:%I64d. Free-space "
"StartingLcn:%I64d ClusterCount:%I64d Status:%lu",
VolData.vFileName.GetBuffer() + 48,
VolData.pFileListEntry->StartingLcn,
VolData.pFileListEntry->ClusterCount,
VolData.pFreeSpaceEntry->StartingLcn,
VolData.pFreeSpaceEntry->ClusterCount,
VolData.Status
);
if (VolData.Status == ERROR_RETRY) {
//
// Free space isn't really free; try again with
// a different free space
//
NewFreeSpaceEntry.StartingLcn = VolData.pFreeSpaceEntry->StartingLcn;
NewFreeSpaceEntry.ClusterCount = 0;
UpdateInMultipleTrees(VolData.pFreeSpaceEntry, NULL);
VolData.pFreeSpaceEntry = NULL;
bDone = FALSE;
}
//
// Since we didn't move this file, we should just add
// it back to the table it originally was in.
//
pMoveToTable = pMoveFromTable;
}
//
// Add this file-entry back to the appropriate file-table
//
pvTemp = RtlInsertElementGenericTable(
pMoveToTable,
(PVOID) VolData.pFileListEntry,
sizeof(FILE_LIST_ENTRY),
&bNewElement);
if (!pvTemp) {
//
// An allocation failed
//
Trace(warn, "Errors encountered while moving file: "
"Unable to add back file-entry to file table");
assert(FALSE);
bResult = FALSE;
break;
};
}
else {
bResult = TRUE;
}
}
else {
//
// We could not find a free-space region big enough to move
// this file.
//
if (bForce) {
GetNtfsFilePath();
Trace(warn, "Movefile failed: Insufficient free space. File %ws "
"StartingLcn:%I64d ClusterCount:%I64d FRN:%I64d Frag:%d Dir:%d",
VolData.vFileName.GetBuffer() + 48,
VolData.pFileListEntry->StartingLcn,
VolData.pFileListEntry->ClusterCount,
VolData.pFileListEntry->FileRecordNumber,
VolData.bFragmented, VolData.bDirectory
);
}
bResult = FALSE;
}
}
}
else {
//
// We could not get the extents for this file
//
if (bForce) {
GetNtfsFilePath();
Trace(warn, "Movefile failed: Unable to get extents. File %ws "
"StartingLcn:%I64d ClusterCount:%I64d FRN:%I64d Frag:%d Dir:%d",
VolData.vFileName.GetBuffer() + 48,
VolData.pFileListEntry->StartingLcn,
VolData.pFileListEntry->ClusterCount,
VolData.pFileListEntry->FileRecordNumber,
VolData.bFragmented, VolData.bDirectory
);
}
bResult = FALSE;
}
//
// Clean-up
//
if(VolData.hFile != INVALID_HANDLE_VALUE) {
CloseHandle(VolData.hFile);
VolData.hFile = INVALID_HANDLE_VALUE;
}
if(VolData.hFreeExtents != NULL) {
while(GlobalUnlock(VolData.hFreeExtents))
;
GlobalFree(VolData.hFreeExtents);
VolData.hFreeExtents = NULL;
}
// update cluster array
PurgeExtentBuffer();
return bResult;
}
/******************************************************************************
ROUTINE DESCRIPTION:
Gets the start and end markers for the Boot-Optimise region from the
registry
INPUT:
lpBootOptimiseKey - The key to read
RETURN:
The value at the specified key, 0 if errors were encountered
*/
LONGLONG
GetStartingEndLcnLocations(
IN PTCHAR lpBootOptimiseKey
)
{
HKEY hValue = NULL; //hkey for the registry value
DWORD dwRegValueSize = 0; //size of the registry value string
long ret = 0; //return value from SetRegValue
TCHAR cRegValue[100]; //string to hold the value for the registry
LONGLONG lLcnStartEndLocation = 0;
//get the LcnStartLocation from the registry
dwRegValueSize = sizeof(cRegValue);
ret = GetRegValue(
&hValue,
BOOT_OPTIMIZE_REGISTRY_PATH,
lpBootOptimiseKey,
cRegValue,
&dwRegValueSize);
RegCloseKey(hValue);
//check to see if the key exists, else exit from routine
if (ret != ERROR_SUCCESS) {
hValue = NULL;
_stprintf(cRegValue,TEXT("%d"),0);
//add the LcnStartLocation to the registry
dwRegValueSize = sizeof(cRegValue);
ret = SetRegValue(
&hValue,
BOOT_OPTIMIZE_REGISTRY_PATH,
lpBootOptimiseKey,
cRegValue,
dwRegValueSize,
REG_SZ);
RegCloseKey(hValue);
}
else {
lLcnStartEndLocation = _ttoi(cRegValue);
}
return lLcnStartEndLocation;
}
/*****************************************************************************************************************
COPYRIGHT© 2001 Microsoft Corporation and Executive Software International, Inc.
ROUTINE DESCRIPTION:
Gets the registry entries at the beginning of the program
INPUT:
Success - TRUE
Failed - FALSE
RETURN:
None
*/
BOOL GetRegistryEntries(
OUT TCHAR lpLayoutIni[MAX_PATH]
)
{
HKEY hValue = NULL; //hkey for the registry value
DWORD dwRegValueSize = 0; //size of the registry value string
long ret = 0; //return value from SetRegValue
TCHAR cEnabledString[2]; //holds the enabled flag
// get Boot Optimize file name from registry
dwRegValueSize = sizeof(cEnabledString);
ret = GetRegValue(
&hValue,
BOOT_OPTIMIZE_REGISTRY_PATH,
BOOT_OPTIMIZE_ENABLE_FLAG,
cEnabledString,
&dwRegValueSize);
RegCloseKey(hValue);
//check to see if the key exists, else exit from routine
if (ret != ERROR_SUCCESS)
{
return FALSE;
}
//check to see that boot optimize is enabled
if(cEnabledString[0] != TEXT('Y'))
{
return FALSE;
}
// get Boot Optimize file name from registry
hValue = NULL;
dwRegValueSize = MAX_PATH;
ret = GetRegValue(
&hValue,
OPTIMAL_LAYOUT_KEY_PATH,
OPTIMAL_LAYOUT_FILE_VALUE_NAME,
lpLayoutIni,
&dwRegValueSize);
RegCloseKey(hValue);
//check to see if the key exists, else exit from routine
if (ret != ERROR_SUCCESS)
{
return FALSE;
}
return TRUE;
}
/******************************************************************************
ROUTINE DESCRIPTION:
Initialisation routine for the BootOptimisation.
INPUT/OUTPUT:
Various VolData fields
RETURN:
TRUE - Initialisation completed successfully
FALSE - Fatal errors were encountered during initialisation
*/
BOOL
InitialiseBootOptimise(
IN CONST BOOL bIsNtfs
)
{
LONGLONG lLcnStartLocation = 0; //the starting location of where the files were moved last
LONGLONG lLcnEndLocation = 0; //the ending location of where the files were moved last
LONGLONG lLcnMinEndLocation = 0; //the minimum size the BootOptimise zone needs to be
TCHAR lpLayoutIni[MAX_PATH]; //string to hold the path of the file
LONGLONG ClustersNeededForLayout = 0; //size in clusters of how big the boot optimize files are
BOOL bResult = FALSE;
Trace(log, "Start: Initialising BootOptimise. Volume %c:", VolData.cDrive);
// Initialise the tree to hold the layout.ini entries
bResult = InitialiseBootOptimiseTables(&VolData.BootOptimiseFileTable,
&VolData.FilesInBootExcludeZoneTable);
if (!bResult) {
SaFreeContext(&VolData.SaBootOptimiseFilesContext);
Trace(log, "End: Initialising BootOptimise. Out of memory");
SaveErrorInRegistry(TEXT("No"),TEXT("Insufficient Resources"));
return FALSE;
}
//get the registry entries
bResult = GetRegistryEntries(lpLayoutIni);
if(!bResult) {
Trace(log, "End: Initialising BootOptimise. Missing registry entries");
SaveErrorInRegistry(TEXT("No"),TEXT("Missing Registry Entries"));
return FALSE; //must be some error in getting registry entries
}
// Get the start and end goalposts for our boot-optimize region
lLcnStartLocation = GetStartingEndLcnLocations(BOOT_OPTIMIZE_REGISTRY_LCNSTARTLOCATION);
lLcnEndLocation = GetStartingEndLcnLocations(BOOT_OPTIMIZE_REGISTRY_LCNENDLOCATION);
// And build the list of files to be boot optimised
bResult = BuildBootOptimiseFileList(
&VolData.BootOptimiseFileTable,
lpLayoutIni,
VolData.cDrive,
bIsNtfs,
&ClustersNeededForLayout);
if (!bResult) {
SaFreeContext(&VolData.SaBootOptimiseFilesContext);
Trace(log, "End: Initialising BootOptimise. Out of memory");
SaveErrorInRegistry(TEXT("No"),TEXT("Insufficient Resources"));
return FALSE;
}
//
// If there are files in the "boot optimise zone" that are not in our layout.ini
// list, we shall evict them if possible.
//
lLcnMinEndLocation = lLcnStartLocation + ClustersNeededForLayout;
if (lLcnMinEndLocation > lLcnEndLocation) {
lLcnEndLocation = lLcnMinEndLocation;
}
VolData.BootOptimizeBeginClusterExclude = lLcnStartLocation;
VolData.BootOptimizeEndClusterExclude = lLcnEndLocation;
Trace(log, "End: Initialising BootOptimise. Zone Begins %I64d, Ends %I64d (%I64d clusters, Minimum needed: %I64d clusters).",
VolData.BootOptimizeBeginClusterExclude,
VolData.BootOptimizeEndClusterExclude,
VolData.BootOptimizeEndClusterExclude - VolData.BootOptimizeBeginClusterExclude,
ClustersNeededForLayout
);
return TRUE;
}
/******************************************************************************
ROUTINE DESCRIPTION:
Routine for BootOptimisation.
INPUT/OUTPUT:
Various VolData fields
RETURN:
ENG_NOERR on success; appropriate ENGERR failure codes otherwise
*/
DWORD
ProcessBootOptimise(
)
{
BOOLEAN bRestart = TRUE;
BOOL bResult = FALSE;
BOOL bForce = FALSE,
bRelocateZone = FALSE;
LONGLONG llBiggestFreeSpaceRegionStartingLcn = 0,
llBiggestFreeSpaceRegionClusterCount = 0,
llAdditionalClustersNeeded = 0,
llMaxBootClusterEnd = 0;
LONGLONG llTotalClustersToBeMoved = 0,
llClustersSuccessfullyMoved = 0;
FILE_LIST_ENTRY NextFileEntry;
DWORD dwStatus = ENG_NOERR,
dwIndex = 0;
Trace(log, "Start: Processing BootOptimise");
ZeroMemory(&NextFileEntry, sizeof(FILE_LIST_ENTRY));
if (!VolData.BootOptimizeEndClusterExclude) {
Trace(log, "End: Processing BootOptimise. BootOptimise region "
"uninitialised (not boot volume?)");
return ENGERR_BAD_PARAM;
}
// Exit if the controller wants us to stop.
if (TERMINATE == VolData.EngineState) {
PostMessage(hwndMain, WM_CLOSE, 0, 0);
ExitThread(0);
}
//
// At this point, VolData.BootOptimiseFileTable contains a copy of the file
// records for the files that need to be boot-optimised, and
// VolData.FilesInBootExcludeZoneTable contains the files that are in our
// preferred boot-optimise zone that need to be evicted
//
//
// Build the free space list, excluding the zone that we are interested in.
//
bResult = BuildFreeSpaceList(
&VolData.FreeSpaceTable,
0,
TRUE,
&llBiggestFreeSpaceRegionClusterCount,
&llBiggestFreeSpaceRegionStartingLcn,
TRUE
);
if (!bResult) {
Trace(log, "End: Processing BootOptimise. Errors encountered while determining free space");
return ENGERR_NOMEM;
}
Trace(log, "BiggestCluster LCN %I64u (%I64u clusters). "
"BootOptimiseFilesTotalSize:%I64u AlreadyInZoneSize:%I64u (%d%%)",
llBiggestFreeSpaceRegionStartingLcn, llBiggestFreeSpaceRegionClusterCount,
VolData.BootOptimiseFileListTotalSize,
VolData.BootOptimiseFilesAlreadyInZoneSize,
VolData.BootOptimiseFilesAlreadyInZoneSize * 100 / VolData.BootOptimiseFileListTotalSize
);
if (llBiggestFreeSpaceRegionClusterCount > VolData.BootOptimiseFileListTotalSize) {
//
// There is a free space region that is bigger than the total files
// we want to move--so check if we want to relocate the boot-optimise
// zone.
//
if ((VolData.BootOptimiseFilesAlreadyInZoneSize * 100 / VolData.BootOptimiseFileListTotalSize) < BOOT_OPTIMISE_ZONE_RELOCATE_THRESHOLD) {
//
// Less than 90% of the Boot-Optimise files are already in the zone.
//
Trace(log, "Relocating boot-optimise zone to LCN %I64u (%I64u clusters free). "
"BootOptimiseFilesTotalSize:%I64u AlreadyInZoneSize:%I64u (%d%%)",
llBiggestFreeSpaceRegionStartingLcn, llBiggestFreeSpaceRegionClusterCount,
VolData.BootOptimiseFileListTotalSize,
VolData.BootOptimiseFilesAlreadyInZoneSize,
VolData.BootOptimiseFilesAlreadyInZoneSize * 100 / VolData.BootOptimiseFileListTotalSize
);
bRelocateZone = TRUE;
VolData.BootOptimizeBeginClusterExclude = llBiggestFreeSpaceRegionStartingLcn;
VolData.BootOptimizeEndClusterExclude = VolData.BootOptimizeBeginClusterExclude + VolData.BootOptimiseFileListTotalSize;
}
}
if (!bRelocateZone) {
//
// Go through the VolData.FilesInBootExcludeZoneTable, and evict them.
//
bRestart = TRUE;
do {
// Exit if the controller wants us to stop.
if (TERMINATE == VolData.EngineState) {
PostMessage(hwndMain, WM_CLOSE, 0, 0);
return ENGERR_GENERAL;
}
bResult = GetNextNtfsFile(&VolData.FilesInBootExcludeZoneTable, bRestart);
bRestart = FALSE;
if (bResult) {
llTotalClustersToBeMoved += VolData.NumberOfClusters;
if (EvictFile()) {
llClustersSuccessfullyMoved += VolData.NumberOfClusters;
}
}
} while (bResult);
}
Trace(log, "%I64d of %I64d clusters successfully evicted (%d%%)",
llClustersSuccessfullyMoved, llTotalClustersToBeMoved,
(llTotalClustersToBeMoved > 0 ?
(llClustersSuccessfullyMoved * 100 / llTotalClustersToBeMoved) : 0));
llClustersSuccessfullyMoved = 0;
llTotalClustersToBeMoved = VolData.BootOptimiseFileListTotalSize;
//
// The next step is to move files from layout.ini to the boot optimise
// region. First build a new free-space list, sorted by startingLcn.
//
ClearFreeSpaceTable();
AllocateFreeSpaceListsWithMultipleTrees();
bResult = BuildFreeSpaceListWithMultipleTrees(
&llBiggestFreeSpaceRegionClusterCount,
VolData.BootOptimizeBeginClusterExclude,
VolData.BootOptimizeEndClusterExclude);
if (!bResult) {
Trace(log, "End: Processing BootOptimise. Errors encountered while determining free space");
return ENGERR_NOMEM;
}
//
// Finally go through VolData.BootOptmiseFileTable, and move the files
// to the boot-optimise region. This will also move files forward if needed.
//
if (VolData.pBootOptimiseFrnList) {
do {
NextFileEntry.FileRecordNumber = VolData.pBootOptimiseFrnList[dwIndex];
dwIndex++;
if (NextFileEntry.FileRecordNumber < 0) {
bResult = FALSE;
}
else {
bResult = GetNextNtfsFile(&VolData.BootOptimiseFileTable, TRUE, 0, &NextFileEntry);
}
// Exit if the controller wants us to stop.
if (TERMINATE == VolData.EngineState) {
PostMessage(hwndMain, WM_CLOSE, 0, 0);
return ENGERR_GENERAL;
}
if (bResult) {
if (VolData.FileRecordNumber == 0) {
//
// Ignore the MFT
//
continue;
}
//
// We should only move files less than BOOT_OPTIMIZE_MAX_FILE_SIZE_BYTES MB
//
if ((VolData.NumberOfClusters == 0) ||
(VolData.NumberOfClusters > (BOOT_OPTIMIZE_MAX_FILE_SIZE_BYTES / VolData.BytesPerCluster))) {
continue;
}
//
// Ignore files that we couldn't find during the analyse phase
//
if (VolData.StartingLcn == VolData.TotalClusters) {
continue;
}
if (VolData.bFragmented == TRUE) {
bForce = TRUE;
}
else {
if ((!VolData.bFragmented) &&
(VolData.StartingLcn >= VolData.BootOptimizeBeginClusterExclude) &&
((VolData.StartingLcn + VolData.NumberOfClusters) <= VolData.BootOptimizeEndClusterExclude)
) {
//
// File is fully contained in the boot-optimise zone. Let's just
// try to move it forward if possible, but no worries if we can't.
//
bForce = FALSE;
}
else {
bForce = TRUE;
}
}
if (MoveBootOptimiseFile(bForce)) {
llClustersSuccessfullyMoved += VolData.NumberOfClusters;
}
else {
if (bForce) {
dwStatus = ENGERR_RETRY;
llAdditionalClustersNeeded += VolData.NumberOfClusters;
}
}
if ((VolData.StartingLcn + VolData.NumberOfClusters > llMaxBootClusterEnd) &&
(VolData.StartingLcn <= VolData.TotalClusters)){
llMaxBootClusterEnd = VolData.StartingLcn + VolData.NumberOfClusters;
}
}
} while (bResult);
}
else {
dwStatus = ENGERR_NOMEM;
}
Trace(log, "%I64d of %I64d clusters successfully moved to zone (%d%%).",
llClustersSuccessfullyMoved, llTotalClustersToBeMoved,
(llTotalClustersToBeMoved > 0 ?
(llClustersSuccessfullyMoved * 100 / llTotalClustersToBeMoved) : 0)
);
//
// Clean-up
//
ClearFreeSpaceListWithMultipleTrees();
if (VolData.hBootOptimiseFrnList != NULL) {
while (GlobalUnlock(VolData.hBootOptimiseFrnList)) {
Sleep(1000);
}
GlobalFree(VolData.hBootOptimiseFrnList);
VolData.hBootOptimiseFrnList = NULL;
VolData.pBootOptimiseFrnList = NULL;
}
if (ENGERR_RETRY == dwStatus) {
//
// Some files could not be moved--we need to grow the boot-optimise zone
// and retry.
//
//
// Make sure the boot-optimise zone isn't more than 4GB, and 50% of the
// disk, whichever is smaller
//
if (
((VolData.BootOptimizeEndClusterExclude - VolData.BootOptimizeBeginClusterExclude) >
((LONGLONG) BOOT_OPTIMIZE_MAX_ZONE_SIZE_MB * ((LONGLONG) 1024 * 1024 / (LONGLONG) VolData.BytesPerCluster))) ||
((VolData.BootOptimizeEndClusterExclude - VolData.BootOptimizeBeginClusterExclude) >
(VolData.TotalClusters * BOOT_OPTIMIZE_MAX_ZONE_SIZE_PERCENT / 100))
) {
dwStatus = ENGERR_LOW_FREESPACE;
}
else {
if (llAdditionalClustersNeeded < (BOOT_OPTIMIZE_ZONE_EXTEND_MIN_SIZE_BYTES / VolData.BytesPerCluster)) {
llAdditionalClustersNeeded = BOOT_OPTIMIZE_ZONE_EXTEND_MIN_SIZE_BYTES / VolData.BytesPerCluster;
}
VolData.BootOptimizeEndClusterExclude += (llAdditionalClustersNeeded *
BOOT_OPTIMIZE_ZONE_EXTEND_PERCENT / 100);
}
}
else if (ENG_NOERR == dwStatus) {
VolData.BootOptimizeEndClusterExclude = llMaxBootClusterEnd;
}
SetRegistryEntires(VolData.BootOptimizeBeginClusterExclude,
VolData.BootOptimizeEndClusterExclude);
UnInitialiseBootOptimiseTables(&VolData.BootOptimiseFileTable,
&VolData.FilesInBootExcludeZoneTable);
if (ENG_NOERR == dwStatus) {
SaveErrorInRegistry(TEXT("Yes"),TEXT(" "));
Trace(log, "End: Processing BootOptimise. Done");
}
else {
SaveErrorInRegistry(TEXT("No"),TEXT("Insufficient free space"));
Trace(log, "End: Processing BootOptimise. Insufficient free space");
}
return dwStatus;
}
/*****************************************************************************************************************
COPYRIGHT© 2001 Microsoft Corporation and Executive Software International, Inc.
ROUTINE DESCRIPTION:
Set the registry entries at the end
INPUT:
None
RETURN:
None
*/
VOID SetRegistryEntires(
IN LONGLONG lLcnStartLocation,
IN LONGLONG lLcnEndLocation
)
{
HKEY hValue = NULL; //hkey for the registry value
DWORD dwRegValueSize = 0; //size of the registry value string
long ret = 0; //return value from SetRegValue
TCHAR cRegValue[100]; //string to hold the value for the registry
_stprintf(cRegValue,TEXT("%I64d"),lLcnStartLocation);
//set the LcnEndLocation from the registry
dwRegValueSize = sizeof(cRegValue);
ret = SetRegValue(
&hValue,
BOOT_OPTIMIZE_REGISTRY_PATH,
BOOT_OPTIMIZE_REGISTRY_LCNSTARTLOCATION,
cRegValue,
dwRegValueSize,
REG_SZ);
RegCloseKey(hValue);
hValue = NULL;
_stprintf(cRegValue,TEXT("%I64d"),lLcnEndLocation);
//set the LcnEndLocation from the registry
dwRegValueSize = sizeof(cRegValue);
ret = SetRegValue(
&hValue,
BOOT_OPTIMIZE_REGISTRY_PATH,
BOOT_OPTIMIZE_REGISTRY_LCNENDLOCATION,
cRegValue,
dwRegValueSize,
REG_SZ);
RegCloseKey(hValue);
}
/*****************************************************************************************************************
COPYRIGHT© 2001 Microsoft Corporation and Executive Software International, Inc.
ROUTINE DESCRIPTION:
Save the error that may have occured in the registry
INPUT:
TCHAR tComplete Set to Y when everything worked, set to N when error
TCHAR* tErrorString A description of what error occured.
RETURN:
None
*/
VOID SaveErrorInRegistry(
TCHAR* tComplete,
TCHAR* tErrorString)
{
HKEY hValue = NULL; //hkey for the registry value
DWORD dwRegValueSize = 0; //size of the registry value string
long ret = 0; //return value from SetRegValue
//set the error code of the error in the registry
dwRegValueSize = 2*(_tcslen(tErrorString));
ret = SetRegValue(
&hValue,
BOOT_OPTIMIZE_REGISTRY_PATH,
BOOT_OPTIMIZE_REGISTRY_ERROR,
tErrorString,
dwRegValueSize,
REG_SZ);
RegCloseKey(hValue);
//set the error status in the registry
hValue = NULL;
dwRegValueSize = 2*(_tcslen(tComplete));
ret = SetRegValue(
&hValue,
BOOT_OPTIMIZE_REGISTRY_PATH,
BOOT_OPTIMIZE_REGISTRY_COMPLETE,
tComplete,
dwRegValueSize,
REG_SZ);
RegCloseKey(hValue);
}
/*****************************************************************************************************************
COPYRIGHT© 2001 Microsoft Corporation and Executive Software International, Inc.
ROUTINE DESCRIPTION:
Get the date/time stamp of the input file
INPUT:
full path to the boot optimize file
RETURN:
TRUE if file time does not match what is in the registry
FALSE if the file time matches what is in the registry
*/
BOOL CheckDateTimeStampInputFile(
IN TCHAR cBootOptimzePath[MAX_PATH]
)
{
WIN32_FILE_ATTRIBUTE_DATA extendedAttr; //structure to hold file attributes
LARGE_INTEGER tBootOptimeFileTime; //holds the last write time of the file
LARGE_INTEGER tBootOptimeRegistryFileTime; //holds the last write time of the file from registry
HKEY hValue = NULL; //hkey for the registry value
DWORD dwRegValueSize = 0; //size of the registry value string
long ret = 0; //return value from SetRegValue
tBootOptimeFileTime.LowPart = 0;
tBootOptimeFileTime.HighPart = 0;
tBootOptimeRegistryFileTime.LowPart = 0;
tBootOptimeRegistryFileTime.HighPart = 0;
//get the last write time of the file
//if it fails, return FALSE
if (GetFileAttributesEx (cBootOptimzePath,
GetFileExInfoStandard,
&extendedAttr))
{
tBootOptimeFileTime.LowPart = extendedAttr.ftLastWriteTime.dwLowDateTime;
tBootOptimeFileTime.HighPart = extendedAttr.ftLastWriteTime.dwHighDateTime;
} else
{
return TRUE; //some error happened and we exit and say we cant get the file time
}
//get the time from the registry
hValue = NULL;
dwRegValueSize = sizeof(tBootOptimeFileTime.QuadPart);
ret = GetRegValue(
&hValue,
BOOT_OPTIMIZE_REGISTRY_PATH,
BOOT_OPTIMIZE_LAST_WRITTEN_DATETIME,
&(LONGLONG)tBootOptimeRegistryFileTime.QuadPart,
&dwRegValueSize);
RegCloseKey(hValue);
//check to see if the key exists, if it does, check to see if the date/time stamp
//matches, if it does, exit else write a registry entry
if (ret == ERROR_SUCCESS)
{
if(tBootOptimeFileTime.QuadPart == tBootOptimeRegistryFileTime.QuadPart)
{
return FALSE; //the file times matched and we exit
}
}
hValue = NULL;
//update the date and time of the bootoptimize file to the registry
dwRegValueSize = sizeof(tBootOptimeFileTime.QuadPart);
ret = SetRegValue(
&hValue,
BOOT_OPTIMIZE_REGISTRY_PATH,
BOOT_OPTIMIZE_LAST_WRITTEN_DATETIME,
(LONGLONG)tBootOptimeFileTime.QuadPart,
dwRegValueSize,
REG_QWORD);
RegCloseKey(hValue);
return TRUE;
}