|
|
/**************************************************************************************************
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; }
|