Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

3522 lines
143 KiB

/*****************************************************************************************************************
FILENAME: NtfsSubs.cpp
COPYRIGHT© 2001 Microsoft Corporation and Executive Software International, Inc.
Contains routines for manipulating the NTFS filesystem.
*/
#include "stdafx.h"
extern "C"{
#include <stdio.h>
}
#ifdef BOOTIME
#include "Offline.h"
#else
#include <Windows.h>
#endif
//#include <winioctl.h>
extern "C" {
#include "SysStruc.h"
}
#include "ErrMacro.h"
#include "DfrgCmn.h"
#include "DfrgEngn.h"
#include "DfrgRes.h"
#include "DasdRead.h"
#include "Devio.h"
#include "FsSubs.h"
#include "NtfsSubs.h"
#include "Alloc.h"
#include "IntFuncs.h"
#include "ErrMsg.h"
#include "Message.h"
#include "Logging.h"
#include "GetDfrgRes.h"
#include "vString.hpp"
#include "getreg.h"
#define THIS_MODULE 'T'
#include "logfile.h"
#ifdef OFFLINEDK
#include "OffNtfs.h"
#include "OffLog.h"
#endif
#define BOOT_OPTIMIZE_REGISTRY_PATH TEXT("SOFTWARE\\Microsoft\\Dfrg\\BootOptimizeFunction")
#define BOOT_OPTIMIZE_REGISTRY_LCNSTARTLOCATION TEXT("LcnStartLocation")
#define BOOT_OPTIMIZE_REGISTRY_LCNENDLOCATION TEXT("LcnEndLocation")
//Whew! I love this code. Take good care of it for me. Zack :-)
/***************************************************************************************************************
FORMAT OF NTFS FILE RECORDS:
An NTFS file is referenced by an entry in the MFT (Master File Table) called a filerecord
(or FileRecordSegment) which is a header and a variable number of structures called Attributes.
For each file there is a Base filerecord (refered to as THE filerecord) and may be one or more
secondary filerecords. The attributes come in different types such as $FILE_NAME, $DATA, etc.
The structure of filerecord attributes contains a FormCode field which indicates RESIDENT_FORM or
NONRESIDENT_FORM. If the attribute is RESIDENT_FORM then the attribute's information is contained
within the attribute. If the attribute is NONRESIDENT_FORM then the attribute contains a list of
extent data (called MappingPairs) which describe a set of clusters on the volume that contain the
attribute's information.
There are four file types: Small, Large, Huge and Mega. These types refer to levels of fragmentation,
not file size. These classifications refer to the complexity in accessing the file's data.
A contiguous file of enormous size only requires location of the file's first cluster and total
clusters in the file to access the file. A much smaller file that is heavily fragmented requires the
same amount of information for each fragment. Once the level of fragmentation reaches the point that
the methodology of the file's current type can no longer describe the file's data the file graduates
to the next file type. Increasing the size of other attributes (thus reducing the amount of space
available for the data information) can also cause a file to graduate to the next file type. Examples
of increasing the size of other attributes are renaming the file with a longer filename or adding
more security information.
Mega File = A non-resident $ATTRIBUTE_LIST attribute in the FRS contains a mapping pairs set
for the clusters that contain resident $ATTRIBUTE_LIST attributes
Small files have a RESIDENT_FORM $DATA attribute containing the file's data.
Large file's have a NONRESIDENT_FORM $DATA attribute containing MappingPairs that describe a set of
clusters which contain the file's data.
Huge files have a RESIDENT_FORM $ATTRIBUTE_LIST attribute which contains a list of secondary file
records containing NONRESIDENT_FORM $DATA attributes.
Mega files have a NONRESIDENT_FORM $ATTRIBUTE_LIST attribute containing MappingPairs that describe
a set of clusters which contain RESIDENT_FORM $ATTRIBUTE_LIST attributes holding a list of secondary
file records containing NONRESIDENT_FORM $DATA attributes.
Once the MappingPairs in the NONRESIDENT_FORM $ATTRIBUTE_LIST attribute exceeds the capacity of the
base filerecord the file system gives up.
A file with a RESIDENT_FORM $DATA attribute is a Small type file.
A file with a NONRESIDENT_FORM $DATA attribute is a Large type file.
A file with a RESIDENT_FORM $ATTRIBUTE_LIST attribute is a Huge type file.
A file with a NONRESIDENT_FORM $ATTRIBUTE_LIST attribute is a Mega type file.
This routine locates all the MappingPairs for a file and converts them into an extent list.
First the filerecord is scanned for a $STANDARD_INFORMATION attribute to determine if the file is
compressed.
Then the filerecord is scanned for an $ATTRIBUTE_LIST or $DATA attribute and looks at the FormCode
of whichever attribute it finds to determine which file type the file is and calls the appropriate
routine to build the extent list.
Finally: It is possible for a single extent to be described with multiple MappingPairs making it
appear to be more than one extent. Also, compressed files are compressed in blocks with a
MappingPair describing each block which also makes it appear that the file has more extents than it
actually does. CollapseExtentList consolidates adjacent extents and derives a true count of extents
and fragments in the file. CollapseExtentList also calls GetLowestStartingLcn to determine the
earliest cluster in the file for the purpose of processing the files on the disk in sequential order.
*/
/*****************************************************************************************************************
COPYRIGHT© 2001 Microsoft Corporation and Executive Software International, Inc.
ROUTINE DESCRIPTION:
Build an extent list for a file.
If TypeCode != 0 then the file's filerecord should already be in VolData.pFileRecord.
If TypeCode = 0 then GetExtentList will load the filerecord.
INPUT + OUTPUT:
IN TypeCode - which attribute in the file's filerecord to look in for the file's extent data. See also description above.
IN pFrs - The FRS for the file go get the extent list of. Set to NULL if GetExtentList has to load the FRS from disk.
GLOBALS:
IN OUT VolData.pFileRecord - The file record for the file.
IN OUT VolData.FileRecordNumber - The file record number to use.
OUT VolData.hExtentList - HANDLE to buffer of EXTENT_LIST structure
OUT VolData.pExtentList - file's extent list
OUT VolData.NumberOfExtents - extents in the list
OUT VolData.NumberOfFragments - fragments in the file
OUT VolData.NumberOfClusters - clusters in the file
OUT VolData.FileSize - bytes in the file
OUT VolData.bFragmented - whether the file is contiguous or fragmented
OUT VolData.bCompressed - whether the file is compressed or not
OUT VolData.bDirectory - whether the file is a directoty or not
OUT VolData.StartingLcn - earliest cluster in the file
RETURN:
TRUE = Success
FALSE = Failure
*/
BOOL
GetExtentList(
DWORD dwEnabledStreams,
FILE_RECORD_SEGMENT_HEADER* pFrs
)
{
ATTRIBUTE_RECORD_HEADER* pArh = NULL; //The specific attribute we're looking at now.
ATTRIBUTE_LIST_ENTRY* pAle = NULL; //The attribute entry we're looking at now (only used for huge and mega files).
ATTRIBUTE_LIST_ENTRY* pAleStart = NULL; //The start of the attribute list (only used for huge and mega files).
HANDLE hAle = NULL; //Handle to memory for attribute list if it was nonresident and had to be read off the disk.
LONGLONG SaveFrn = 0; //Used to compare the FRN before and after getting a new FRS to make sure we got the one we want.
DWORD dwAttrListLength = 0; //The length of the attribute list in a huge or mega file.
EXTENT_LIST_DATA ExtentData;
UINT i;
BOOL fRC;
// Set up the Extent pointers structure to fill in the extent list in VolData.
ZeroMemory(&ExtentData, sizeof(EXTENT_LIST_DATA));
ExtentData.hExtents = VolData.hExtentList;
ExtentData.pExtents = VolData.pExtentList;
ExtentData.ExtentListAlloced = (DWORD)VolData.ExtentListAlloced;
ExtentData.ExtentListSize = 0;
ExtentData.dwEnabledStreams = dwEnabledStreams;
ExtentData.BytesRead = 0;
ExtentData.TotalClusters = 0;
// Initialize the VolData fields.
VolData.FileSize = 0;
VolData.bFragmented = FALSE;
VolData.bCompressed = FALSE;
VolData.bDirectory = FALSE;
VolData.bInBootExcludeZone = FALSE;
VolData.bBootOptimiseFile = FALSE;
VolData.NumberOfClusters = 0;
VolData.NumberOfRealClusters = 0;
VolData.NumberOfFragments = 0;
// Get the FRS if it wasn't already loaded.
if(!pFrs) {
// Set the pFrs to point to a buffer to hold the FRS.
pFrs = (FILE_RECORD_SEGMENT_HEADER*)VolData.pFileRecord; //A shortcut to reduce code + cycles when accessing the frs.
// Save the FRN so we know if we get the wrong FRS later.
SaveFrn = VolData.FileRecordNumber;
//Read in the FRS for the attribute.
// Get the next file record.
EF(GetInUseFrs(VolData.hVolume, &VolData.FileRecordNumber, pFrs, (ULONG)VolData.BytesPerFRS));
// Make sure we got the FRS we requested.
EF_ASSERT(VolData.FileRecordNumber == SaveFrn);
}
// Detect if it is a directory and set the flag
if (pFrs->Flags & FILE_FILE_NAME_INDEX_PRESENT) {
VolData.bDirectory = TRUE;
}
else {
VolData.bDirectory = FALSE;
}
// Find $STANDARD_INFORMATION attribute -- if there is none then don't use this FRS.
if(!FindAttributeByType($STANDARD_INFORMATION, pFrs, &pArh, (ULONG)VolData.BytesPerFRS)) {
return FALSE;
}
// Note if the file is compressed or not.
VolData.bCompressed = (((STANDARD_INFORMATION*)((UCHAR*)pArh+pArh->Form.Resident.ValueOffset))->FileAttributes
& FILE_ATTRIBUTE_COMPRESSED) ? TRUE : FALSE;
// Initialize the FILE_EXTENT_HEADER structure.
ExtentData.pFileExtentHeader = (FILE_EXTENT_HEADER*)ExtentData.pExtents;
ExtentData.pFileExtentHeader->FileRecordNumber = VolData.FileRecordNumber;
ExtentData.pFileExtentHeader->NumberOfStreams = 0; //Contains the number of streams with extents.
ExtentData.pFileExtentHeader->TotalNumberOfStreams = 0; //Contains the total number of streams.
ExtentData.pFileExtentHeader->ExcessExtents = 0;
// Initialize the FILE_STREAM_HEADER structure.
ExtentData.pStreamExtentHeader = (STREAM_EXTENT_HEADER*)((char*)VolData.pExtentList
+ sizeof(FILE_EXTENT_HEADER));
ExtentData.pStreamExtentHeader->StreamNumber = 0;
ExtentData.pStreamExtentHeader->ExtentCount = 0;
ExtentData.pStreamExtentHeader->ExcessExtents = 0;
ExtentData.pStreamExtentHeader->AllocatedLength = 0;
ExtentData.pStreamExtentHeader->FileSize = 0;
__try {
// Get non-data stream extents.
EF(GetNonDataStreamExtents());
// Look for an $ATTRIBUTE_LIST
if(!FindAttributeByType($ATTRIBUTE_LIST, pFrs, &pArh, (ULONG)VolData.BytesPerFRS)) {
// If no $ATTRIBUTE_LIST was found, move to the first stream in the FRS.
if(!FindStreamInFrs(pFrs, &pArh, &ExtentData)) {
// If no stream was found, then there is no extent list for this file.
VolData.TotalSmallDirs++;
return TRUE;
}
do{
// If this is a nonresident stream, then get its extents, and add it to the extent list.
if(pArh->FormCode == NONRESIDENT_FORM) {
// Set the data in the stream header since we've found a stream;
ExtentData.pStreamExtentHeader->StreamNumber = ExtentData.pFileExtentHeader->TotalNumberOfStreams;
ExtentData.pStreamExtentHeader->ExtentCount = 0;
ExtentData.pStreamExtentHeader->ExcessExtents = 0;
ExtentData.pStreamExtentHeader->AllocatedLength = 0;
ExtentData.pStreamExtentHeader->FileSize = 0;
// Load the stream extents from the attributes in this FRS.
GetLargeStreamExtentList(pFrs, pArh, &ExtentData);
// Reset this variable for the next stream.
ExtentData.BytesRead = 0;
// Keep track of the number of streams with extent lists.
ExtentData.pFileExtentHeader->NumberOfStreams++;
// Bump up the file stream header pointer to the beginning of
// where the next stream will be if it's found.
ExtentData.pStreamExtentHeader = (STREAM_EXTENT_HEADER*)((UCHAR*)ExtentData.pStreamExtentHeader
+ sizeof(STREAM_EXTENT_HEADER)
+ ExtentData.pStreamExtentHeader->ExtentCount
* sizeof(EXTENT_LIST));
// Check to see if the extent list is going to overrun with the
// addition of another stream header.
if(((UCHAR*)ExtentData.pStreamExtentHeader + sizeof(STREAM_EXTENT_HEADER))
> (ExtentData.pExtents + ExtentData.ExtentListAlloced)) {
UCHAR* pLastExtentList = ExtentData.pExtents;
// If so, realloc it larger, 64K at a time.
EF(AllocateMemory(ExtentData.ExtentListAlloced + 0x10000,
&ExtentData.hExtents,
(void**)&ExtentData.pExtents));
ExtentData.ExtentListAlloced += 0x10000;
// Reset the VolData fields which are dependent on pExtentList.
if(pLastExtentList != ExtentData.pExtents) {
ExtentData.pStreamExtentHeader = (STREAM_EXTENT_HEADER*)((UCHAR*)ExtentData.pStreamExtentHeader
- pLastExtentList
+ ExtentData.pExtents);
ExtentData.pFileExtentHeader = (FILE_EXTENT_HEADER*)((UCHAR*)ExtentData.pFileExtentHeader
- pLastExtentList
+ ExtentData.pExtents);
}
}
}
// Keep track of the fact the stream exists so they can be numbered correctly.
ExtentData.pFileExtentHeader->TotalNumberOfStreams++;
// Go to the next stream.
}
while(FindNextStreamInFrs(pFrs, &pArh, &ExtentData));
}
//If an $ATTRIBUTE_LIST was found
else {
// If the $ATTRIBUTE_LIST is nonresident get the entire
// attribute list from the disk before proceeding.
if(pArh->FormCode == NONRESIDENT_FORM) {
// Load the attribute extents from the disk.
LoadExtentDataToMem(pArh, &hAle, &dwAttrListLength);
// Get a pointer to the allocated memory.
EF_ASSERT(pAleStart = (ATTRIBUTE_LIST_ENTRY*)GlobalLock(hAle));
VolData.NumberOfClusters = 0;
}
// If it was a resident attribute list, then the length of
// the attribute list can be determined from the attribute.
else {
pAleStart = (ATTRIBUTE_LIST_ENTRY*)(pArh->Form.Resident.ValueOffset + (UCHAR*)pArh);
dwAttrListLength = pArh->Form.Resident.ValueLength;
}
// Start at the beginning of the attribute list.
pAle = pAleStart;
// Move to the first stream.
if(!FindStreamInAttrList(pAleStart, &pAle, dwAttrListLength, &ExtentData)) {
// If no stream was found, then there is no extent list for this file.
return TRUE;
}
do {
// If this is a nonresident stream, then get it's extents, and add it to the extent list.
if(AttributeFormCode(pAle, &ExtentData) == NONRESIDENT_FORM) {
// Set the data in the stream header since we've found a stream;
ExtentData.pStreamExtentHeader->StreamNumber = ExtentData.pFileExtentHeader->TotalNumberOfStreams;
ExtentData.pStreamExtentHeader->ExtentCount = 0;
ExtentData.pStreamExtentHeader->ExcessExtents = 0;
ExtentData.pStreamExtentHeader->AllocatedLength = 0;
ExtentData.pStreamExtentHeader->FileSize = 0;
// Load the stream extents from the attributes in the various FRS's.
GetHugeStreamExtentList(pAleStart, &pAle, dwAttrListLength, &ExtentData);
// Reset this variable for the next stream.
ExtentData.BytesRead = 0;
// Keep track of the number of streams with extent lists.
ExtentData.pFileExtentHeader->NumberOfStreams++;
// Bump up the file stream header pointer to the beginning of where
// the next stream will be if it's found.
ExtentData.pStreamExtentHeader = (STREAM_EXTENT_HEADER*)((UCHAR*)ExtentData.pStreamExtentHeader + sizeof(STREAM_EXTENT_HEADER)
+ ExtentData.pStreamExtentHeader->ExtentCount
* sizeof(EXTENT_LIST));
// Check to see if the extent list is going to overrun with the addition of another stream header.
if(((UCHAR*)ExtentData.pStreamExtentHeader+sizeof(STREAM_EXTENT_HEADER))
> (ExtentData.pExtents + ExtentData.ExtentListAlloced)) {
UCHAR* pLastExtentList = ExtentData.pExtents;
// If so, realloc it larger, 64K at a time.
EF(AllocateMemory(ExtentData.ExtentListAlloced + 0x10000,
&ExtentData.hExtents,
(void**)&ExtentData.pExtents));
ExtentData.ExtentListAlloced += 0x10000;
// Reset the VolData fields which are dependent on pExtentList.
if(pLastExtentList != ExtentData.pExtents) {
ExtentData.pStreamExtentHeader = (STREAM_EXTENT_HEADER*)((UCHAR*)ExtentData.pStreamExtentHeader
- pLastExtentList
+ ExtentData.pExtents);
ExtentData.pFileExtentHeader = (FILE_EXTENT_HEADER*)((UCHAR*)ExtentData.pFileExtentHeader
- pLastExtentList
+ ExtentData.pExtents);
}
}
}
// Keep track of the fact the stream exists
// so they can be numbered correctly.
ExtentData.pFileExtentHeader->TotalNumberOfStreams++;
// Find the next stream.
}
while(FindNextStreamInAttrList(pAleStart, &pAle, dwAttrListLength, &ExtentData));
}
// Reset the stream extent header to the first stream.
ExtentData.pStreamExtentHeader = (STREAM_EXTENT_HEADER*)((UCHAR*)ExtentData.pFileExtentHeader
+ sizeof(FILE_EXTENT_HEADER));
// Loop through the streams to get a pointer to the end of the extent list.
for(i=0; i<ExtentData.pFileExtentHeader->NumberOfStreams; i++) {
// Set the stream extent header pointer to the next stream
// (current position + header + # extents * sizeof each extent).
ExtentData.pStreamExtentHeader = (STREAM_EXTENT_HEADER*)((UCHAR*)ExtentData.pStreamExtentHeader
+ sizeof(STREAM_EXTENT_HEADER)
+ ExtentData.pStreamExtentHeader->ExtentCount
* sizeof(EXTENT_LIST));
}
// Since pStreamExtentHeader now points to just after the last stream,
// we can use it to determine the size of the extent list.
ExtentData.ExtentListSize = (DWORD)((UCHAR*)ExtentData.pStreamExtentHeader
- (UCHAR*)ExtentData.pFileExtentHeader);
// Concatenate adjacent extents and keep track of the number of fragments.
EF(CollapseExtentList(&ExtentData));
}
__finally {
// Count the total number of excess extents for the file in all the streams.
// First set the excess extents to zero.
ExtentData.pFileExtentHeader->ExcessExtents = 0;
// Reset the stream extent header to the first stream.
ExtentData.pStreamExtentHeader = (STREAM_EXTENT_HEADER*)((UCHAR*)ExtentData.pFileExtentHeader
+ sizeof(FILE_EXTENT_HEADER));
// Loop through the streams counting excess extents.
for(i=0; i<ExtentData.pFileExtentHeader->NumberOfStreams; i++) {
// Add this stream's excess extents (those after the the first extent)
// to the total excess extents count.
ExtentData.pFileExtentHeader->ExcessExtents += ExtentData.pStreamExtentHeader->ExcessExtents;
// Set the stream extent header pointer to the next stream
// (current position + header + # extents * sizeof each extent).
ExtentData.pStreamExtentHeader = (STREAM_EXTENT_HEADER*)((UCHAR*)ExtentData.pStreamExtentHeader
+ sizeof(STREAM_EXTENT_HEADER)
+ ExtentData.pStreamExtentHeader->ExtentCount
* sizeof(EXTENT_LIST));
}
// Since pStreamExtentHeader now points to just after the last stream,
// we can use it to determine the size of the extent list.
ExtentData.ExtentListSize = (DWORD)((UCHAR*)ExtentData.pStreamExtentHeader
- (UCHAR*)ExtentData.pFileExtentHeader);
// Now that we have the full extent list, put it back into VolData.
VolData.hExtentList = ExtentData.hExtents;
VolData.pExtentList = ExtentData.pExtents;
VolData.ExtentListAlloced = ExtentData.ExtentListAlloced;
// Free memory.
if (hAle) {
EH_ASSERT(GlobalUnlock(hAle) == FALSE);
EH_ASSERT(GlobalFree(hAle) == NULL);
}
// Note how much memory is actually used by the extent list.
// First get a pointer to the end.
ExtentData.pStreamExtentHeader = (STREAM_EXTENT_HEADER*)((char*)VolData.pExtentList
+ sizeof(FILE_EXTENT_HEADER));
// As long as we've got a pointer to the first stream, get the file size
// (which is the allocated length for this stream.) The file size is
// considered to the the file size for only the first stream.
VolData.FileSize = ExtentData.pStreamExtentHeader->FileSize;
// Loop through the streams.
fRC = TRUE;
for(i=0; i<ExtentData.pFileExtentHeader->NumberOfStreams; i++) {
// If we hit a stream with a header but no extents, ERROR.
if (!(ExtentData.pStreamExtentHeader->ExtentCount)) {
LOG_ERR();
fRC = FALSE;
goto EndFinally;
}
// Set the stream extent header pointer to the next stream
// (current position + header + # extents * sizeof each extent).
ExtentData.pStreamExtentHeader = (STREAM_EXTENT_HEADER*)((UCHAR*)ExtentData.pStreamExtentHeader
+ sizeof(STREAM_EXTENT_HEADER)
+ ExtentData.pStreamExtentHeader->ExtentCount
* sizeof(EXTENT_LIST));
}
// Now that pStreamExtentHeader points to just after the last stream,
// we can use it's position to determine the size of the whole extent list.
VolData.ExtentListSize = (UINT_PTR)ExtentData.pStreamExtentHeader - (UINT_PTR)ExtentData.pExtents;
// Keep track of the number of clusters and real clusters in all streams of the file.
VolData.NumberOfClusters = ExtentData.TotalClusters;
VolData.NumberOfRealClusters = ExtentData.TotalRealClusters;
// If there are any excess extents then this file is fragmented.
if(ExtentData.pFileExtentHeader->ExcessExtents) {
VolData.bFragmented = TRUE;
}
EndFinally:;
}
return fRC;
}
//Loops through the attributes in an FRS looking for a valid stream.
//A valid stream is a $DATA or $INDEX_ALLOCATION attribute. $BITMAP is also valid if pExtentData->dwEnabledStreams is TRUE;
BOOL
FindStreamInFrs(
IN PFILE_RECORD_SEGMENT_HEADER pFrs,
OUT PATTRIBUTE_RECORD_HEADER* ppArh,
EXTENT_LIST_DATA* pExtentData
)
{
PATTRIBUTE_RECORD_HEADER pArh;
//Check validitity of input parameters.
EF_ASSERT(pFrs);
EF_ASSERT(ppArh);
//Initialize this to zero.
*ppArh = 0;
//Point to the first attribute in the FRS.
pArh = (ATTRIBUTE_RECORD_HEADER*)((PUCHAR)pFrs + pFrs->FirstAttributeOffset);
//Look for a $DATA, $INDEX_ALLOCATION, or $BITMAP attribute, as these are the valid stream types.
// Don't go beyond the end of the FRS.
// Look or $DATA, $INDEX_ALLOCATION, or $BITMAP.
// Another check to not go beyond the end of the valid data in the FRS.
while(pArh < (ATTRIBUTE_RECORD_HEADER*)((PUCHAR)pFrs + VolData.BytesPerFRS) &&
pArh->TypeCode != $DATA &&
pArh->TypeCode != $INDEX_ALLOCATION &&
(pExtentData->dwEnabledStreams ? pArh->TypeCode != $BITMAP : TRUE) &&
pArh->TypeCode != $END){
//That wasn't a valid attribute. Go to the next.
pArh = (ATTRIBUTE_RECORD_HEADER*)((PUCHAR)pArh + pArh->RecordLength);
}
//If no attribute was found, return FALSE since we didn't find a stream.
if((pArh >= (ATTRIBUTE_RECORD_HEADER*)((PUCHAR)pFrs + VolData.BytesPerFRS))
|| (pArh->TypeCode != $DATA &&
pArh->TypeCode != $INDEX_ALLOCATION &&
(pExtentData->dwEnabledStreams ? pArh->TypeCode != $BITMAP : TRUE))
){
return FALSE;
}
//If found, but not unnamed, report a corrupt disk.
//The unnamed stream must always exist and must always precede the named streams,
//hence we must find one if any streams are found since we proceed from the beginning of the FRS.
//$INDEX_ALLOCATIONS may exist and may have any name (usually $I30 or $O).
//Found an unnamed stream. Success!
*ppArh = pArh;
return TRUE;
}
//Finds the next valid stream in an FRS. This will continue the loop which FindStreamInFrs started until it finds another, or none.
BOOL
FindNextStreamInFrs(
IN PFILE_RECORD_SEGMENT_HEADER pFrs,
OUT PATTRIBUTE_RECORD_HEADER* ppArh,
EXTENT_LIST_DATA* pExtentData
)
{
PATTRIBUTE_RECORD_HEADER pArh;
BOOL bNewStreamFound = FALSE;
//Check validitity of input parameters.
EF_ASSERT(pFrs);
EF_ASSERT(ppArh);
EF_ASSERT(*ppArh);
EF_ASSERT(pExtentData);
//Set the pointer to the next attribute from the one currently pointed to.
pArh = (ATTRIBUTE_RECORD_HEADER*)((char*)*ppArh + (*ppArh)->RecordLength);
do{
//Look for a $DATA, $INDEX_ALLOCATION or $BITMAP attribute after the last one found
// Don't go beyond the end of the FRS.
// Look or $DATA, $INDEX_ALLOCATION, or $BITMAP.
// Another check to not go beyond the end of the valid data in the FRS.
while(pArh < (ATTRIBUTE_RECORD_HEADER*)((PUCHAR)pFrs + VolData.BytesPerFRS) &&
pArh->TypeCode != $DATA &&
pArh->TypeCode != $INDEX_ALLOCATION &&
(pExtentData->dwEnabledStreams ? pArh->TypeCode != $BITMAP : TRUE) &&
pArh->TypeCode != $END){
//That wasn't a valid attribute. Go to the next.
pArh = (ATTRIBUTE_RECORD_HEADER*)((PUCHAR)pArh + pArh->RecordLength);
}
//If no attribute was found, return FALSE since we didn't find a stream.
if((pArh >= (ATTRIBUTE_RECORD_HEADER*)((PUCHAR)pFrs + VolData.BytesPerFRS)) || (pArh->TypeCode != $DATA && pArh->TypeCode != $INDEX_ALLOCATION && (pExtentData->dwEnabledStreams ? pArh->TypeCode != $BITMAP : TRUE))){
return FALSE;
}
//Check to see if this attribute is part of the last attribute's stream.
//First compare TypeCodes.
if(pArh->TypeCode == ((PATTRIBUTE_RECORD_HEADER)*ppArh)->TypeCode){
//Then compare the streamnames.
// Check to see if both attribs are unnamed.
// Check to see if both attribs are named and the same name.
if((pArh->NameLength == 0 && (*ppArh)->NameLength == 0) ||
((pArh->NameLength == (*ppArh)->NameLength) && !memcmp((UCHAR*)pArh+pArh->NameOffset, (UCHAR*)(*ppArh)+(*ppArh)->NameOffset, pArh->NameLength*sizeof(WCHAR)))){
//The two stream names matched, so this attribute is part of the same stream as the previous attribute. Keep looking for another attribute.
continue;
}
}
//Found an another stream. Success!
bNewStreamFound = TRUE;
}while(!bNewStreamFound);
//Return a pointer to the stream.
*ppArh = pArh;
return TRUE;
}
//Given an non-resident attribute, AddMappingPointersToStream will extract the mapping pointers, convert then to EXTENT_LISTs and
//append them to the extent list.
BOOL
AddMappingPointersToStream(
IN PATTRIBUTE_RECORD_HEADER pArh,
EXTENT_LIST_DATA* pExtentData
)
{
PUCHAR pMappingPairs = 0;
LONGLONG ExtentLengthFieldSize = 0;
LONGLONG ExtentOffsetFieldSize = 0;
LONGLONG ExtentLength = 0;
LONGLONG ExtentOffset = 0;
LONGLONG ThisExtentStart = 0;
LONGLONG BytesReadMax = 0;
LONGLONG BytesRead = 0;
LONGLONG LastExtentEnd = 0;
EXTENT_LIST* pExtentList = NULL;
ULONG Extent;
//Make sure we have a stream header to add extents to.
EF_ASSERT(pArh);
EF_ASSERT(pExtentData->pStreamExtentHeader);
//Make sure this is a non resident attribute.
EF_ASSERT(pArh->FormCode == NONRESIDENT_FORM);
//Get an extent list pointer that points to the current end of the stream.
pExtentList = (EXTENT_LIST*)((UCHAR*)pExtentData->pStreamExtentHeader + sizeof(STREAM_EXTENT_HEADER) + pExtentData->pStreamExtentHeader->ExtentCount * sizeof(EXTENT_LIST));
//Note the end of the last extent so that future contiguous extents won't be counted as separate extents.
//If there are no extents in this stream yet, then de facto there is not an end to the previous extent.
if(pExtentData->pStreamExtentHeader->ExtentCount == 0){
LastExtentEnd = 0xFFFFFFFFFFFFFFFF;
}
else{
LastExtentEnd = pExtentList[-1].StartingLcn + pExtentList[-1].ClusterCount;
}
//0.0E00 Get a pointer to the mapping pairs in this attribute.
pMappingPairs = (PUCHAR)pArh + pArh->Form.Nonresident.MappingPairsOffset;
//Note how many bytes are allocated in the stream for data.
//Sometimes an extent list will contain garbage pointers at the end beyond the last allocated cluster, so don't add these to the extent list.
BytesReadMax = pExtentData->pStreamExtentHeader->AllocatedLength;
//Note how many bytes have already been read from this stream.
BytesRead = pExtentData->BytesRead;
//Loop through each mapping pair in the attribute. The last mapping pair is filled with zeros, if there are not enough extents to go to AllocatedLength.
// Initialize ThisExtentStart.
// Don't go beyond the number of bytes allocated to the stream and into invalid extent data.
// Don't go beyond the end of the record.
// Don't go past a zero extent which indicates an end of extent list -- sometimes. (Sometimes it's determined by alloced size, or end of frs.)
// Go to the next extent.
for( ThisExtentStart=0, Extent=0;
(BytesRead<BytesReadMax) &&
(pMappingPairs < (PUCHAR)((char*)pArh + pArh->RecordLength)) &&
(*pMappingPairs!=0);
Extent ++){
//Check to see if the extent list is going to overrun with the addition of another extent.
if((UCHAR*)&pExtentList[Extent+1] > (pExtentData->pExtents + pExtentData->ExtentListAlloced)){
UCHAR* pLastExtentList = pExtentData->pExtents;
//If so, realloc it larger, 64K at a time.
EF(AllocateMemory(pExtentData->ExtentListAlloced + 0x10000, &pExtentData->hExtents, (void**)&pExtentData->pExtents));
pExtentData->ExtentListAlloced += 0x10000;
//Reset the fields which are dependent on pExtents.
if(pLastExtentList != pExtentData->pExtents){
pExtentData->pStreamExtentHeader = (STREAM_EXTENT_HEADER*)((UCHAR*)pExtentData->pStreamExtentHeader - pLastExtentList + pExtentData->pExtents);
pExtentData->pFileExtentHeader = (FILE_EXTENT_HEADER*)((UCHAR*)pExtentData->pFileExtentHeader - pLastExtentList + pExtentData->pExtents);
pExtentList = (EXTENT_LIST*)((UCHAR*)pExtentList - pLastExtentList + pExtentData->pExtents);
}
}
//First, get the size of the field that stores the length of the run of clusters,
//Then, get the size of the field that stores the length of the offset field.
//0.1E00 The lower 4 bits hold a number from 0-15 that specifies how many bytes the length field is.
ExtentLengthFieldSize = *pMappingPairs & 0x0F;
//0.1E00 The upper bits of the UCHAR hold a number that specifies how many bytes the offset field is.
ExtentOffsetFieldSize = *(pMappingPairs++) / 16;
//0.1E00 Check for virtual extents. (Extents indicating clusters not on disk because the file is compressed and doesn't need all the clusters on disk that it occupies in memory.)
ExtentLength = (*(pMappingPairs + ExtentLengthFieldSize - 1) & 0x80) ? -1 : 0;
EF_ASSERT(ExtentLengthFieldSize);
//0.1E00 Get the length of this extent from the extent length field.
CopyMemory(&ExtentLength, pMappingPairs, (DWORD)ExtentLengthFieldSize);
pMappingPairs += ExtentLengthFieldSize;
//0.1E00 If the offset field has a value -- i.e. it isn't a virtual extent, then copy it over.
if(ExtentOffsetFieldSize != 0){
//0.1E00 Check for virtual extents.
ExtentOffset = (*(pMappingPairs + ExtentOffsetFieldSize - 1) & 0x80) ? -1 : 0;
//0.1E00 Get the length of this extent from the extent length field.
CopyMemory(&ExtentOffset, pMappingPairs, (DWORD)ExtentOffsetFieldSize);
pMappingPairs += ExtentOffsetFieldSize;
//0.1E00 Since the offset field holds an offset from the last extent, translate this into an absolute offset from the
//beginning of the disk. The first extent has an absolute offset, and each subsequent extent has a relative offset.
ThisExtentStart += ExtentOffset;
//0.0E00 If this extent is not adjacent to last extent, bump fragment count.
//If this is the first extent the fragment count will not be incremented because LastExtentEnd = 0xFFFFFFFFFFFFFFFF.
if((LastExtentEnd != 0xFFFFFFFFFFFFFFFF) && (ThisExtentStart != LastExtentEnd)){
VolData.NumberOfFragments ++;
}
//0.1E00 Keep track of the last cluster of this extent so we can know if the next extent is adjacent or not.
LastExtentEnd = ThisExtentStart + ExtentLength;
//0.0E00 Update extent list
pExtentList[Extent].StartingLcn = ThisExtentStart;
}
//0.1E00 If this was a virtual extent, then fill the starting lcn field with F's
else{
pExtentList[Extent].StartingLcn = 0xFFFFFFFFFFFFFFFF;
}
//0.1E00 Copy in the cluster count we calculated earlier.
pExtentList[Extent].ClusterCount = ExtentLength;
//Note that we added an extent to the stream.
pExtentData->pStreamExtentHeader->ExtentCount++;
//Keep track of the number of bytes we've read.
pExtentData->BytesRead += ExtentLength*VolData.BytesPerCluster;
//Keep track of the total number of clusters in the file.
pExtentData->TotalClusters += ExtentLength;
//Keep track of the total number of real clusters in the file.
if(pExtentList[Extent].StartingLcn != 0xFFFFFFFFFFFFFFFF){
pExtentData->TotalRealClusters += ExtentLength;
}
}
return TRUE;
}
//Given the first attribute for a valid stream, GetLargeStreamExtentList will get the extent list for the stream.
//This will also include other attributes that immediately follow which are part of the same stream.
BOOL
GetLargeStreamExtentList(
IN PFILE_RECORD_SEGMENT_HEADER pFrs,
IN PATTRIBUTE_RECORD_HEADER pArh,
EXTENT_LIST_DATA* pExtentData
)
{
PATTRIBUTE_RECORD_HEADER pPreviousArh = NULL;
//check validity of input,
EF_ASSERT(pFrs);
EF_ASSERT(pArh);
//Check the validity of the attribute passed in.
EF_ASSERT(pArh->TypeCode == $DATA || pArh->TypeCode == $INDEX_ALLOCATION || (pExtentData->dwEnabledStreams ? pArh->TypeCode == $BITMAP : TRUE));
//Note the number of bytes allocated to the stream so we don't overwrite it.
pExtentData->pStreamExtentHeader->AllocatedLength = pArh->Form.Nonresident.AllocatedLength;
//Note the file size for the stream for statistics for the user.
pExtentData->pStreamExtentHeader->FileSize = pArh->Form.Nonresident.FileSize;
while(TRUE){
//Get the attribute's extent list.
EF(AddMappingPointersToStream(pArh, pExtentData));
//See if the next attribute is part of the same stream, and if so, count its extents too.
//Remember which attribute this is.
pPreviousArh = pArh;
//Set the pointer to the next attribute from the one currently pointed to.
pArh = (ATTRIBUTE_RECORD_HEADER*)((char*)pArh + pArh->RecordLength);
//Look at the next attribute after the last one found and see if it is the same type.
// Don't go beyond the end of the FRS.
// Look for an attribute of the same type as the last.
// Another check to not go beyond the end of the valid data in the FRS.
if(pArh < (ATTRIBUTE_RECORD_HEADER*)((PUCHAR)pFrs + VolData.BytesPerFRS) &&
pArh->TypeCode == pPreviousArh->TypeCode &&
pArh->TypeCode != $END){
//We found another attribute of the same type. Now compare the names to see if they're the same stream.
//If both attributes have name lengths of zero they are both part of the unnamed stream.
if(pArh->NameLength == 0 && pPreviousArh == 0){
continue;
}
//If both attribs have the same name length, and the names match, they are part of the same named stream.
if(pArh->NameLength == pPreviousArh->NameLength && !memcmp((char*)pArh+pArh->NameOffset, (char*)pPreviousArh+pPreviousArh->NameOffset, pArh->NameLength*sizeof(WCHAR))){
continue;
}
}
//The next attribute is not part of the same stream, so we're done.
break;
}
return TRUE;
}
//Identical to FindStreamInFrs only it is fed an $ATTRIBUTE_LIST attribute instead of an FRS.
BOOL
FindStreamInAttrList(
ATTRIBUTE_LIST_ENTRY* pAleStart,
ATTRIBUTE_LIST_ENTRY** ppAle,
LONGLONG ValueLength,
EXTENT_LIST_DATA* pExtentData
)
{
UCHAR* pAleEnd = NULL;
ATTRIBUTE_LIST_ENTRY* pAle = NULL;
//Check validitity of input parameters.
EF_ASSERT(pAleStart);
EF_ASSERT(ppAle);
EF_ASSERT(ValueLength);
EF_ASSERT(pExtentData);
//Initialize this to zero.
*ppAle = 0;
//Initialize this to the start of the attribute list.
pAle = pAleStart;
//Note the end of the attribute list.
pAleEnd = (UCHAR*)pAleStart + ValueLength;
//Look for a $DATA, $INDEX_ALLOCATION, or $BITMAP attribute, as these are the valid stream types.
// Don't go beyond the end of the attribute list.
// Look for $DATA, $INDEX_ALLOCATION or $BITMAP
while((UCHAR*)pAle < pAleEnd &&
pAle->AttributeTypeCode != $DATA &&
pAle->AttributeTypeCode != $INDEX_ALLOCATION &&
pAle->AttributeTypeCode != $UNUSED &&
(pExtentData->dwEnabledStreams ? pAle->AttributeTypeCode != $BITMAP : TRUE)){
if (pAle->RecordLength == 0) {
// This better have a Record Length > 0; else we're going to loop
// around indefinitely
VolData.bMFTCorrupt = TRUE;
return FALSE;
}
pAle = (ATTRIBUTE_LIST_ENTRY*)((UCHAR*)pAle + pAle->RecordLength);
}
//If no attribute was found, return FALSE since we didn't find a stream.
if(((UCHAR*)pAle >= pAleEnd) || (pAle->AttributeTypeCode == $UNUSED)){
return FALSE;
}
//If found, but not unnamed, report a corrupt disk.
//The unnamed stream must always exist and must always precede the named streams,
//hence we must find one if any streams are found since we proceed from the beginning of the FRS.
//$INDEX_ALLOCATIONS may exist and may have any name (usually $I30 or $O).
//Found an unnamed stream. Success!
*ppAle = pAle;
return TRUE;
}
//Same as FindNextStreamInFrs only it is fed an $ATTRIBUTE_LIST attribute instead of an FRS.
BOOL
FindNextStreamInAttrList(
ATTRIBUTE_LIST_ENTRY* pAleStart,
ATTRIBUTE_LIST_ENTRY** ppAle,
LONGLONG ValueLength,
EXTENT_LIST_DATA* pExtentData
)
{
UCHAR* pAleEnd = NULL;
ATTRIBUTE_LIST_ENTRY* pAle = NULL;
BOOL bNewStreamFound = FALSE;
//Check validitity of input parameters.
EF_ASSERT(pAleStart);
EF_ASSERT(ppAle);
EF_ASSERT(*ppAle);
EF_ASSERT(ValueLength);
EF_ASSERT(pExtentData);
//Initialize this to the attribute after this stream in the attribute list.
pAle = (ATTRIBUTE_LIST_ENTRY*)((UCHAR*)(*ppAle) + (*ppAle)->RecordLength);
//Note the end of the attribute list.
pAleEnd = (UCHAR*)pAleStart + ValueLength;
do{
//Look for a $DATA, $INDEX_ALLOCATION, or $BITMAP attribute, as these are the valid stream types.
// Don't go beyond the end of the attribute list.
// Look or $DATA, $INDEX_ALLOCATION, or $BITMAP
while((UCHAR*)pAle < pAleEnd &&
pAle->AttributeTypeCode != $DATA &&
pAle->AttributeTypeCode != $INDEX_ALLOCATION &&
pAle->AttributeTypeCode != $UNUSED &&
(pExtentData->dwEnabledStreams ? pAle->AttributeTypeCode != $BITMAP : TRUE)){
pAle = (ATTRIBUTE_LIST_ENTRY*)((UCHAR*)pAle + pAle->RecordLength);
}
//If no attribute was found, return FALSE since we didn't find a stream.
if(((UCHAR*)pAle >= pAleEnd) || (pAle->AttributeTypeCode == $UNUSED)){
return FALSE;
}
//Check to see if this attribute is part of the last attribute's stream.
//First compare TypeCodes.
if(pAle->AttributeTypeCode == (*ppAle)->AttributeTypeCode){
//Then compare the streamnames.
// Check to see if both attribs are unnamed.
// Check to see if both attribs are named and the same name.
if((pAle->AttributeNameLength == 0 && (*ppAle)->AttributeNameLength == 0) ||
((pAle->AttributeNameLength == (*ppAle)->AttributeNameLength) && !memcmp((UCHAR*)pAle+pAle->AttributeNameOffset, (UCHAR*)(*ppAle)+(*ppAle)->AttributeNameOffset, pAle->AttributeNameLength*sizeof(WCHAR)))){
//The two stream names matched, so this attribute is part of the same stream as the previous attribute. Keep looking for another attribute.
pAle = (ATTRIBUTE_LIST_ENTRY*)((UCHAR*)pAle + pAle->RecordLength);
continue;
}
}
//Found an another stream. Success!
bNewStreamFound = TRUE;
}while(!bNewStreamFound);
//Return a pointer to the stream.
(*ppAle) = pAle;
return TRUE;
}
//Will get the actual attribute out of an FRS when given the pointer to it from an $ATTRIBUTE_LIST.
ATTRIBUTE_RECORD_HEADER*
FindAttributeByInstanceNumber(
HANDLE* phFrs,
ATTRIBUTE_LIST_ENTRY* pAle,
EXTENT_LIST_DATA* pExtentData
)
{
HANDLE hFrs = NULL;
FILE_RECORD_SEGMENT_HEADER* pFrs = NULL;
LONGLONG FileRecordNumber = 0;
LONGLONG FileRecordNumberStore = 0;
PATTRIBUTE_RECORD_HEADER pArh = NULL;
EF_ASSERT(phFrs);
EF_ASSERT(pAle);
#ifdef OFFLINEDK
//0.0E00 Get a pointer to the MFT bitmap
UCHAR* pMftBitmap = (UCHAR*) GlobalLock(VolData.hMftBitmap);
if (pMftBitmap) {
BOOL isLocked = GlobalUnlock(VolData.hMftBitmap);
EH(!isLocked);
}
#endif
__try {
// If a buffer for the FRS isn't already loaded, load it.
if(!*phFrs){
// Allocate a buffer to hold the file records, one at a time. (this locks the memory)
if (!AllocateMemory((DWORD)(VolData.BytesPerFRS + VolData.BytesPerSector), phFrs, (void**)&pFrs)){
EH(FALSE);
__leave;
}
}
else{
// memory already exists, so lock it
pFrs = (FILE_RECORD_SEGMENT_HEADER*) GlobalLock(*phFrs);
if (!pFrs){
EH_ASSERT(FALSE);
__leave;
}
}
//The file record number = the segment ref low part bitwise anded with the high part shifted left as many bits as the low part.
FileRecordNumber = (pAle->SegmentReference.LowPart | (pAle->SegmentReference.HighPart << (sizeof(ULONG)*8)));
FileRecordNumberStore = FileRecordNumber;
//Read in the FRS for the attribute.
#ifdef OFFLINEDK
//0.1E00 Get an FRS.
if (!GetFrs(&FileRecordNumber, VolData.pMftExtentList, pMftBitmap, VolData.pMftBuffer, pFrs)){
EH(FALSE);
__leave;
}
#else
//0.1E00 Get the next file record. (does not lock the memory)
if (!GetInUseFrs(VolData.hVolume, &FileRecordNumber, pFrs, (ULONG)VolData.BytesPerFRS)){
EH(FALSE);
__leave;
}
#endif
//Make sure we got the FRS we requested.
if (FileRecordNumber != FileRecordNumberStore){
EH_ASSERT(FALSE);
__leave;
}
//Find the attribute indicated by the attribute list entry.
//Set pArh to the first attribute in the FRS.
pArh = (ATTRIBUTE_RECORD_HEADER*)((PUCHAR)pFrs + pFrs->FirstAttributeOffset);
// Look for a $DATA, $INDEX_ALLOCATION, or $BITMAP attribute, as these are the valid stream types.
// Don't go beyond the end of the FRS.
// Check for a match of the instance numbers.
// Another check to not go beyond the end of the valid data in the FRS.
while(pArh < (ATTRIBUTE_RECORD_HEADER*)((PUCHAR)pFrs + VolData.BytesPerFRS) &&
pArh->Instance != pAle->Instance &&
pArh->TypeCode != $END){
//That wasn't a the attribute. Go to the next.
pArh = (ATTRIBUTE_RECORD_HEADER*)((PUCHAR)pArh + pArh->RecordLength);
}
//If the attribute wasn't found, return NULL since we didn't find a stream.
if((pArh >= (ATTRIBUTE_RECORD_HEADER*)((PUCHAR)pFrs + VolData.BytesPerFRS)) || (pArh->Instance != pAle->Instance) || (pArh->TypeCode == $END)){
pArh = NULL;
__leave;
}
//Error if the attribute doesn't match what was expected.
if (pArh->TypeCode != pAle->AttributeTypeCode){
EH_ASSERT(FALSE);
__leave;
}
//Make sure the form code field is valid.
if (pArh->FormCode != RESIDENT_FORM && pArh->FormCode != NONRESIDENT_FORM){
EH_ASSERT(FALSE);
__leave;
}
}
__finally {
// unlock the memory (it will be freed later)
if(*phFrs){
BOOL isLocked = GlobalUnlock(*phFrs);
EH(!isLocked);
}
}
//Return the attribute.
return pArh;
}
//Given an ATTRIBUTE_LIST_ENTRY, will find out the form code of the attribute
//(which requires reading the actual attribute out of the FRS, since that data is not stored in the ATTRIBUTE_LIST_ENTRY.)
UCHAR
AttributeFormCode(
ATTRIBUTE_LIST_ENTRY* pAle,
EXTENT_LIST_DATA* pExtentData
)
{
HANDLE hFrs = NULL;
// FILE_RECORD_SEGMENT_HEADER* pFrs = NULL;
ATTRIBUTE_RECORD_HEADER* pArh = NULL; //The specific attribute we're looking at now.
UCHAR uFormCode = 0; // assumes an error condition
EF_ASSERT(pAle);
EF_ASSERT(pExtentData);
__try {
// Get the attribute.
//EF(pArh = FindAttributeByInstanceNumber(&hFrs, pAle, pExtentData));
pArh = FindAttributeByInstanceNumber(&hFrs, pAle, pExtentData);
if (!pArh){
EH(FALSE);
__leave;
}
// Error if the attribute doesn't match what was expected.
//EF_ASSERT(pArh->TypeCode == pAle->AttributeTypeCode);
if (pArh->TypeCode != pAle->AttributeTypeCode){
EH(FALSE);
__leave;
}
// Make sure the form code field is valid.
//EF_ASSERT(pArh->FormCode == RESIDENT_FORM || pArh->FormCode == NONRESIDENT_FORM);
if (pArh->FormCode != RESIDENT_FORM && pArh->FormCode != NONRESIDENT_FORM){
EH(FALSE);
__leave;
}
// Return its FormCode field.
uFormCode = pArh->FormCode;
}
__finally {
if(hFrs){
// it is already unlocked EH_ASSERT(GlobalUnlock(hFrs) == FALSE);
EH_ASSERT(GlobalFree(hFrs) == NULL);
}
}
return uFormCode;
}
//Given an ATTRIBUTE_LIST_ENTRY, will find out the AllocatedLength of the attribute
//(which requires reading the actual attribute out of the FRS, since that data is not stored in the ATTRIBUTE_LIST_ENTRY.)
LONGLONG
AttributeAllocatedLength(
ATTRIBUTE_LIST_ENTRY* pAle,
EXTENT_LIST_DATA* pExtentData
)
{
HANDLE hFrs = NULL;
// FILE_RECORD_SEGMENT_HEADER* pFrs = NULL;
ATTRIBUTE_RECORD_HEADER* pArh = NULL; //The specific attribute we're looking at now.
LONGLONG lReturnValue = (LONGLONG) -1;
EM_ASSERT(pAle);
EM_ASSERT(pExtentData);
__try {
//Get the attribute.
//EM(pArh = FindAttributeByInstanceNumber(&hFrs, pAle, pExtentData));
pArh = FindAttributeByInstanceNumber(&hFrs, pAle, pExtentData);
if (!pArh){
EH(FALSE);
__leave;
}
//Error if the attribute doesn't match what was expected.
//EM_ASSERT(pArh->TypeCode == pAle->AttributeTypeCode);
if (pArh->TypeCode != pAle->AttributeTypeCode){
EH(FALSE);
__leave;
}
//Make sure the form code field is valid.
EM_ASSERT(pArh->FormCode == NONRESIDENT_FORM);
if (pArh->FormCode != NONRESIDENT_FORM){
EH(FALSE);
__leave;
}
// Return its AllocatedLength field.
lReturnValue = pArh->Form.Nonresident.AllocatedLength;
}
__finally {
if(hFrs){
// it is already unlocked EH_ASSERT(GlobalUnlock(hFrs) == FALSE);
EH_ASSERT(GlobalFree(hFrs) == NULL);
}
}
// Return its AllocatedLength field.
return lReturnValue;
}
//Given an ATTRIBUTE_LIST_ENTRY, will find out the FileSize field in the attribute
//(which requires reading the actual attribute out of the FRS, since that data is not stored in the ATTRIBUTE_LIST_ENTRY.)
LONGLONG
AttributeFileSize(
ATTRIBUTE_LIST_ENTRY* pAle,
EXTENT_LIST_DATA* pExtentData
)
{
HANDLE hFrs = NULL;
// FILE_RECORD_SEGMENT_HEADER* pFrs = NULL;
ATTRIBUTE_RECORD_HEADER* pArh = NULL; //The specific attribute we're looking at now.
LONGLONG lReturnValue = -1;
EM_ASSERT(pAle);
EM_ASSERT(pExtentData);
__try {
//Get the attribute.
//EM(pArh = FindAttributeByInstanceNumber(&hFrs, pAle, pExtentData));
pArh = FindAttributeByInstanceNumber(&hFrs, pAle, pExtentData);
if (!pArh){
EH(FALSE);
__leave;
}
//Error if the attribute doesn't match what was expected.
//EM_ASSERT(pArh->TypeCode == pAle->AttributeTypeCode);
if (pArh->TypeCode != pAle->AttributeTypeCode){
EH(FALSE);
__leave;
}
//Make sure the form code field is valid.
//EM_ASSERT(pArh->FormCode == NONRESIDENT_FORM);
if (pArh->FormCode != NONRESIDENT_FORM){
EH(FALSE);
__leave;
}
//Return its FileSize field.
lReturnValue = pArh->Form.Nonresident.FileSize;
}
__finally {
if(hFrs){
// it is already unlocked EH_ASSERT(GlobalUnlock(hFrs) == FALSE);
EH_ASSERT(GlobalFree(hFrs) == NULL);
}
}
// Return its FileSize field.
return lReturnValue;
}
//Given an ATTRIBUTE_LIST_ENTRY pointing to the primary attribute in a stream, GetHugeStreamExtentList will build the extent list
//by querying all attributes in all FRS's in order that are a part of the stream.
BOOL
GetHugeStreamExtentList(
ATTRIBUTE_LIST_ENTRY* pAleStart,
ATTRIBUTE_LIST_ENTRY** ppAle,
LONGLONG ValueLength,
EXTENT_LIST_DATA* pExtentData
)
{
UCHAR* pAleEnd = NULL;
ATTRIBUTE_LIST_ENTRY* pAle = NULL;
HANDLE hFrs = NULL;
ATTRIBUTE_RECORD_HEADER* pArh = NULL;
BOOL bReturnValue = FALSE;
//Check validitity of input parameters.
EF_ASSERT(pAleStart);
EF_ASSERT(ppAle);
EF_ASSERT(*ppAle);
EF_ASSERT(ValueLength);
EF_ASSERT(pExtentData);
//Initialize this to the start of the stream in the attribute list.
pAle = *ppAle;
//Note the end of the attribute list.
pAleEnd = (UCHAR*)pAleStart + ValueLength;
//Get the type of this attribute and make sure it's a valid stream beginning.
EF_ASSERT(pAle->AttributeTypeCode == $DATA || pAle->AttributeTypeCode == $INDEX_ALLOCATION || (pExtentData->dwEnabledStreams ? pAle->AttributeTypeCode == $BITMAP : TRUE));
//Note the number of bytes allocated to the stream so we don't overwrite it.
//EF((pExtentData->pStreamExtentHeader->AllocatedLength = AttributeAllocatedLength(pAle, pExtentData)) != -1);
pExtentData->pStreamExtentHeader->AllocatedLength = AttributeAllocatedLength(pAle, pExtentData);
EF(pExtentData->pStreamExtentHeader->AllocatedLength != -1);
//Note the file size for user statistics.
//EF((pExtentData->pStreamExtentHeader->FileSize = AttributeFileSize(pAle, pExtentData)) != -1);
pExtentData->pStreamExtentHeader->FileSize = AttributeFileSize(pAle, pExtentData);
EF(pExtentData->pStreamExtentHeader->FileSize != -1);
__try {
//Go through the attribute list and the extents from all the attributes that belong to this stream in order.
do{
//Look for a $DATA, $INDEX_ALLOCATION, or $BITMAP attribute, as these are the valid stream types.
// Don't go beyond the end of the attribute list.
// Look or $DATA, $INDEX_ALLOCATION, or $BITMAP
while((UCHAR*)pAle < pAleEnd &&
pAle->AttributeTypeCode != $DATA &&
pAle->AttributeTypeCode != $INDEX_ALLOCATION &&
pAle->AttributeTypeCode != $UNUSED &&
(pExtentData->dwEnabledStreams ? pAle->AttributeTypeCode != $BITMAP : TRUE)
){
pAle = (ATTRIBUTE_LIST_ENTRY*)((UCHAR*)pAle + pAle->RecordLength);
}
//If no attribute was found, break out of the loop.
if(((UCHAR*)pAle >= pAleEnd) || (pAle->AttributeTypeCode == $UNUSED)){
break;
}
//Check to see if this attribute is part of the stream.
//First compare TypeCodes.
if(pAle->AttributeTypeCode == (*ppAle)->AttributeTypeCode){
//Then compare the streamnames.
//Check to see if both attribs are unnamed.
//Check to see if both attribs are named and the same name.
if((pAle->AttributeNameLength == 0 && (*ppAle)->AttributeNameLength == 0) ||
(pAle->AttributeNameLength == (*ppAle)->AttributeNameLength && !memcmp((UCHAR*)pAle+pAle->AttributeNameOffset, (UCHAR*)(*ppAle)+(*ppAle)->AttributeNameOffset, pAle->AttributeNameLength*sizeof(WCHAR)))){
//The two stream names matched, so this attribute is part of the same stream as the previous attribute.
//Get the attribute from its FRS.
//EF_ASSERT(pArh = FindAttributeByInstanceNumber(&hFrs, pAle, pExtentData));
pArh = FindAttributeByInstanceNumber(&hFrs, pAle, pExtentData);
if (!pArh){
EH_ASSERT(FALSE);
__leave;
}
//Add this FRS and attribute to the stream's attribute list.
//EF(AddMappingPointersToStream(pArh, pExtentData));
if (!AddMappingPointersToStream(pArh, pExtentData)){
EH(FALSE);
__leave;
}
}
}
//Go to the next attribute.
pAle = (ATTRIBUTE_LIST_ENTRY*)((UCHAR*)pAle + pAle->RecordLength);
//Loop until no more attributes in the stream are found.
} while(TRUE);
//Success.
bReturnValue = TRUE;
}
__finally {
if(hFrs){
// it is already unlocked EH_ASSERT(GlobalUnlock(hFrs) == FALSE);
EH_ASSERT(GlobalFree(hFrs) == NULL);
}
}
return bReturnValue;
}
//Given an non-resident attribute, LoadExtentDataToMem will get the extent list for the attribute, and read
//the extents themselves contiguously into memory.
BOOL
LoadExtentDataToMem(
ATTRIBUTE_RECORD_HEADER* pArh,
HANDLE* phAle,
DWORD* pdwByteLen
)
{
DWORD i;
LONGLONG NumClusters;
EXTENT_LIST_DATA ExtentData;
EXTENT_LIST* pExtents;
UCHAR* pAle = NULL;
BOOL bReturn = FALSE; // assume an error
//Zero out our local ExtentData structure.
ZeroMemory(&ExtentData, sizeof(EXTENT_LIST_DATA));
//Make sure the inputs match. Either hAle was already alloced, or it wasn't.
EF_ASSERT((*phAle && *pdwByteLen) || (!*phAle && !*pdwByteLen));
//Allocate some memory for the extent list. 64K should do the trick.
ExtentData.ExtentListAlloced = 0x10000;
EF(AllocateMemory(ExtentData.ExtentListAlloced, &ExtentData.hExtents, (void**)&ExtentData.pExtents));
__try{
//There will be no FILE_EXTENT_HEADER for this extent list since we're just getting a single set of extents which might be data or non-data. (Only need a stream header, either way.)
ExtentData.pFileExtentHeader = NULL;
//Initialize the FILE_STREAM_HEADER structure.
ExtentData.pStreamExtentHeader = (STREAM_EXTENT_HEADER*)ExtentData.pExtents;
ExtentData.pStreamExtentHeader->StreamNumber = 0;
ExtentData.pStreamExtentHeader->ExtentCount = 0;
ExtentData.pStreamExtentHeader->ExcessExtents = 0;
ExtentData.pStreamExtentHeader->AllocatedLength = 0;
ExtentData.pStreamExtentHeader->FileSize = 0;
//Note the number of bytes allocated to the stream so we don't overwrite it.
ExtentData.pStreamExtentHeader->AllocatedLength = pArh->Form.Nonresident.AllocatedLength;
ExtentData.BytesRead = 0;
//Note the file size for the stream for statistics data for the user.
ExtentData.pStreamExtentHeader->FileSize = pArh->Form.Nonresident.FileSize;
//This returns the size of the data.
*pdwByteLen = (DWORD)ExtentData.pStreamExtentHeader->FileSize;
//Get the extents of the non-res attribute to read to memory.
if (!AddMappingPointersToStream(pArh, &ExtentData)){
EH(FALSE);
__leave;
}
//Get a pointer to the extents themselves.
pExtents = (EXTENT_LIST*)((UCHAR*)ExtentData.pExtents + sizeof(STREAM_EXTENT_HEADER));
//Count the number of clusters in all the extents so we know how much mem to alloc for the attribute list.
for(i=0, NumClusters=0; i<ExtentData.pStreamExtentHeader->ExtentCount; i++){
NumClusters += pExtents[i].ClusterCount;
}
//If there wasn't already memory allocated by the calling function, or if it was a different size, allocate/reallocate the memory to the correct size.
if(*pdwByteLen != (DWORD)(NumClusters * VolData.BytesPerCluster)){
//0.0E00 Allocate a buffer for the next extent
if (!AllocateMemory((DWORD)((NumClusters * VolData.BytesPerCluster) + VolData.BytesPerSector),
phAle,
(void**)&pAle)){
EH(FALSE);
__leave;
}
}
//bug #181935
//added 8/22/2000 by Scott Sipe, fix that was added some time ago in Glendale
//by John Joesph to fix the problem where pAle is NULL sometimes
//If there still isn't an I/O buffer, allocate one the correct size.
if(pAle == NULL){
//0.0E00 Allocate a buffer for the next extent
if (!AllocateMemory((DWORD)((NumClusters * VolData.BytesPerCluster) + VolData.BytesPerSector),
phAle,
(void**)&pAle)){
EH(FALSE);
__leave;
}
}
//Read in each of those extents.
for(i=0; i<ExtentData.pStreamExtentHeader->ExtentCount; i++){
//0.0E00 Read the next extent
if (!DasdReadClusters(VolData.hVolume,
pExtents[i].StartingLcn,
pExtents[i].ClusterCount,
pAle,
VolData.BytesPerSector,
VolData.BytesPerCluster)){
EH(FALSE);
__leave;
}
//Make pAle point to the memory block to write the next extent.
pAle += pExtents[i].ClusterCount * VolData.BytesPerCluster;
}
bReturn = TRUE; // everything is OK
}
__finally {
//Unlock our pointer to the attribute list memory.
if(*phAle){
BOOL isLocked = GlobalUnlock(*phAle);
EH(!isLocked);
}
//Free up our extent list.
if(ExtentData.hExtents){
EH_ASSERT(GlobalUnlock(ExtentData.hExtents) == FALSE);
EH_ASSERT(GlobalFree(ExtentData.hExtents) == NULL);
ExtentData.pExtents = NULL;
ExtentData.hExtents = NULL;
}
}
return bReturn;
}
//Given the name and type of a stream, GetStreamExtentsByNameAndType will track down the stream and build
//the extent list for it.
BOOL
GetStreamExtentsByNameAndType(
TCHAR* StreamName,
ATTRIBUTE_TYPE_CODE StreamType,
FILE_RECORD_SEGMENT_HEADER* pFrs
)
{
ULONG StreamNumber = 0;
FILE_EXTENT_HEADER* pFileExtentHeader;
STREAM_EXTENT_HEADER* pStreamExtentHeader;
EXTENT_LIST* pExtents;
BOOL bExtentFound = FALSE;
UINT i;
//validate input not null
EF_ASSERT(pFrs);
//Validate the input criteria to make sure it could represent a valid stream.
//($DATA, $INDEX_ALLOCATION, $ATTRIBUTE_LIST, $BITMAP).
EF_ASSERT(StreamType == $DATA || $INDEX_ALLOCATION || $BITMAP);
EF(GetStreamNumberFromNameAndType(&StreamNumber, StreamName, StreamType, pFrs));
EF(GetExtentList(ALL_STREAMS, pFrs));
//Pull out that number of stream.
//First set the file extent header to the beginning of the extent list.
pFileExtentHeader = (FILE_EXTENT_HEADER*)VolData.pExtentList;
//Set the stream extent header to point to the first stream.
pStreamExtentHeader = (STREAM_EXTENT_HEADER*)((char*)VolData.pExtentList + sizeof(FILE_EXTENT_HEADER));
//Make sure he's asking for a valid stream number for this file. Error out if not.
EF(StreamNumber < pFileExtentHeader->TotalNumberOfStreams);
//Loop through each of the streams in the file.
//Check against both NumberOfStreams and TotalNumberOfStreams to ensure we don't go beyond the end of the list.
for(i=0; i<pFileExtentHeader->NumberOfStreams; i++){
//If the stream number matches the one we're looking for, move the memory to the beginning of the extents list.
if(StreamNumber == pStreamExtentHeader->StreamNumber){
//Note that we found the stream and break;
bExtentFound = TRUE;
break;
}
//Set the pointer to the next stream header.
pStreamExtentHeader = (STREAM_EXTENT_HEADER*)((UCHAR*)pStreamExtentHeader + sizeof(STREAM_EXTENT_HEADER) + pStreamExtentHeader->ExtentCount*sizeof(EXTENT_LIST));
//Check to make sure that we haven't gone beyond the stream number the user was asking for.
if(pStreamExtentHeader->StreamNumber > StreamNumber){
break;
}
}
//If we didn't find the stream number the user was asking for, that means it doesn't have any extents.
if(!bExtentFound){
//Fill in a stream header for an empty stream for the return.
pStreamExtentHeader = (STREAM_EXTENT_HEADER*)VolData.pExtentList;
pStreamExtentHeader->StreamNumber = StreamNumber;
pStreamExtentHeader->ExtentCount = 0;
}
//Otherwise a stream was found, so move it to the top of the extent list buffer for the return.
else{
//Move the stream extent header and it's extents to the beginning and overwrite the file extent header and anything else.
MoveMemory(pFileExtentHeader, pStreamExtentHeader, UINT(sizeof(STREAM_EXTENT_HEADER) + pStreamExtentHeader->ExtentCount*sizeof(EXTENT_LIST)));
}
//Note how much memory is actually used by the extent list.
//First get a pointer to the end.
pStreamExtentHeader = (STREAM_EXTENT_HEADER*)(char*)VolData.pExtentList;
//If we hit a stream with a header but no extents, error.
EF(pStreamExtentHeader->ExtentCount);
//Set the stream extent header pointer to the next stream (current position + header + # extents * sizeof each extent).
pStreamExtentHeader = (STREAM_EXTENT_HEADER*)((UCHAR*)pStreamExtentHeader + sizeof(STREAM_EXTENT_HEADER) + pStreamExtentHeader->ExtentCount * sizeof(EXTENT_LIST));
//Now that pStreamExtentHeader points to just after the stream, we can use it's position to determine the size of the whole extent list.
VolData.ExtentListSize = (UINT_PTR)pStreamExtentHeader - (UINT_PTR)pFileExtentHeader;
//Count the number of clusters in the stream.
VolData.NumberOfClusters = 0;
VolData.NumberOfRealClusters = 0;
//First get a pointer to the end.
pStreamExtentHeader = (STREAM_EXTENT_HEADER*)(char*)VolData.pExtentList;
pExtents = (EXTENT_LIST*)((UCHAR*)pStreamExtentHeader + sizeof(STREAM_EXTENT_HEADER));
for(i=0; i<pStreamExtentHeader->ExtentCount; i++){
//Count the number of virtual clusters in the file.
VolData.NumberOfClusters += pExtents[i].ClusterCount;
//Count the number of real clusters in the file.
if(pExtents[i].StartingLcn != 0xFFFFFFFFFFFFFFFF){
VolData.NumberOfRealClusters += pExtents[i].ClusterCount;
}
}
//Get the file size for the stream.
VolData.FileSize = pStreamExtentHeader->FileSize;
//If there are any excess extents then this file is fragmented.
if(pStreamExtentHeader->ExtentCount > 1){
VolData.bFragmented = TRUE;
}
//Keep track of the number of extents in this stream.
VolData.NumberOfFragments = pStreamExtentHeader->ExcessExtents + 1;
return TRUE;
}
//Given the name and type of a stream, GetStreamNumberFromNameAndType finds which stream number corresponds.
BOOL
GetStreamNumberFromNameAndType(
ULONG* pStreamNumber,
TCHAR* StreamName,
ATTRIBUTE_TYPE_CODE TypeCode,
FILE_RECORD_SEGMENT_HEADER* pFrs
)
{
ATTRIBUTE_RECORD_HEADER* pArh = NULL; //The specific attribute we're looking at now.
ATTRIBUTE_LIST_ENTRY* pAle = NULL; //The attribute entry we're looking at now (only used for huge and mega files).
HANDLE hAle = NULL; //Handle to memory for attribute list if it was nonresident and had to be read off the disk.
LONGLONG SaveFrn = 0; //Used to compare the FRN before and after getting a new FRS to make sure we got the one we want.
DWORD dwAttrListLength = 0; //The length of the attribute list in a huge or mega file.
EXTENT_LIST_DATA ExtentData;
DWORD TotalNumberOfStreams;
//Validate the input criteria to make sure it could represent a valid stream.
//($DATA, $INDEX_ALLOCATION, $ATTRIBUTE_LIST, $BITMAP).
EF_ASSERT(TypeCode == $DATA || $INDEX_ALLOCATION || $BITMAP);
//Set up the Extent pointers structure to fill in the extent list in VolData.
ZeroMemory(&ExtentData, sizeof(EXTENT_LIST_DATA));
ExtentData.hExtents = VolData.hExtentList;
ExtentData.pExtents = VolData.pExtentList;
ExtentData.ExtentListAlloced = (ULONG)VolData.ExtentListAlloced;
ExtentData.dwEnabledStreams = TRUE; //Since the user is asking for the stream by name and type, it's valid to accept a request for a $BITMAP stream.
#ifdef OFFLINEDK
//Do initializtion to get the FRS if it wasn't already loaded.
if(!pFrs){
UCHAR* pMftBitmap = NULL;
FILE_RECORD_SEGMENT_HEADER* pFileRecord = (FILE_RECORD_SEGMENT_HEADER*)VolData.pFileRecord;
//0.0E00 Get a pointer to the MFT bitmap since we will be reading from the disk directly rather than via a hook.
EF((pMftBitmap = (UCHAR*)GlobalLock(VolData.hMftBitmap)) != NULL);
if (pMftBitmap != NULL) {
BOOL isLocked = GlobalUnlock(VolData.hMftBitmap);
EH(!isLocked);
}
}
#endif
//Initialize the VolData fields.
VolData.FileSize = 0;
VolData.bFragmented = FALSE;
VolData.bCompressed = FALSE;
VolData.bDirectory = FALSE;
//Get the FRS if it wasn't already loaded.
if(!pFrs){
//Set the pFrs to point to a buffer to hold the FRS.
pFrs = (FILE_RECORD_SEGMENT_HEADER*)VolData.pFileRecord; //A shortcut to reduce code + cycles when accessing the frs.
//Save the FRN so we know if we get the wrong FRS later.
SaveFrn = VolData.FileRecordNumber;
//Read in the FRS for the attribute.
#ifdef OFFLINEDK
//0.1E00 Get an FRS.
EF(GetFrs(&VolData.FileRecordNumber, VolData.pMftExtentList, pMftBitmap, VolData.pMftBuffer, pFileRecord));
#else
//0.1E00 Get the next file record.
EF(GetInUseFrs(VolData.hVolume, &VolData.FileRecordNumber, (FILE_RECORD_SEGMENT_HEADER*)VolData.pFileRecord, (ULONG)VolData.BytesPerFRS));
#endif
//Make sure we got the FRS we requested.
EF_ASSERT(VolData.FileRecordNumber == SaveFrn);
}
//Detect if it is a directory and set flag
if(pFrs->Flags & FILE_FILE_NAME_INDEX_PRESENT){
VolData.bDirectory = TRUE;
}
else{
VolData.bDirectory = FALSE;
}
//Find $STANDARD_INFORMATION attribute -- if there is none then don't use this FRS.
if(!FindAttributeByType($STANDARD_INFORMATION, pFrs, &pArh, (ULONG)VolData.BytesPerFRS)){
return FALSE;
}
//Note if the file is compressed or not.
VolData.bCompressed = (((PSTANDARD_INFORMATION)((UCHAR*)pArh+pArh->Form.Resident.ValueOffset))->FileAttributes & FILE_ATTRIBUTE_COMPRESSED) ? TRUE : FALSE;
//Note that there were no streams found yet.
TotalNumberOfStreams = 0;
__try{
//Look for an $ATTRIBUTE_LIST
if(!FindAttributeByType($ATTRIBUTE_LIST, pFrs, &pArh, (ULONG)VolData.BytesPerFRS)){
//If no $ATTRIBUTE_LIST was found, move to the first stream in the FRS.
if(!FindStreamInFrs(pFrs, &pArh, &ExtentData)){
//If no stream was found, then there is no extent list for this file.
return TRUE;
}
do{
//Check for a match on the stream.
// If the type code matches and
// Either they are both nonamed or
// They are the same namelength, and the text in the names compare, then they are the stream we're looking for.
if(pArh->TypeCode == TypeCode &&
((!pArh->NameLength && !lstrlen(StreamName)) ||
((pArh->NameLength == lstrlen(StreamName)) && !memcmp((UCHAR*)pArh+pArh->NameOffset, StreamName, lstrlenW(StreamName)*sizeof(WCHAR))))){
*pStreamNumber = TotalNumberOfStreams;
return TRUE;
}
//Keep track of the fact the stream exists so they can be numbered correctly.
TotalNumberOfStreams++;
//Go to the next stream.
}while(FindNextStreamInFrs(pFrs, &pArh, &ExtentData));
}
//If an $ATTRIBUTE_LIST was found
else{
//If the $ATTRIBUTE_LIST is nonresident get the entire attribute list from the disk before proceeding.
if(pArh->FormCode == NONRESIDENT_FORM){
//Load the attribute extents from the disk.
LoadExtentDataToMem(pArh, &hAle, &dwAttrListLength);
//Get a pointer to the allocated memory.
EF_ASSERT(pAle = (ATTRIBUTE_LIST_ENTRY*)GlobalLock(hAle));
}
//If it was a resident attribute list, then the length of the attribute list can be determined from the attribute.
else{
pAle = (ATTRIBUTE_LIST_ENTRY*)(pArh->Form.Resident.ValueOffset + (UCHAR*)pArh);
dwAttrListLength = pArh->Form.Resident.ValueLength;
}
//Move to the first stream.
if(!FindStreamInAttrList(pAle, &pAle, dwAttrListLength, &ExtentData)){
//If no stream was found, then there is no extent list for this file.
return TRUE;
}
do{
//Check for a match on the stream.
// If the type code matches and
// Either they are both unnamed or
// They are the same namelength, and the text in the names compare, then they are the stream we're looking for.
if(pAle->AttributeTypeCode == TypeCode &&
((!pAle->AttributeNameLength && !lstrlen(StreamName)) ||
((pAle->AttributeNameLength == lstrlen(StreamName)) && !memcmp((UCHAR*)pAle+pAle->AttributeNameOffset, StreamName, lstrlenW(StreamName)*sizeof(WCHAR))))){
*pStreamNumber = TotalNumberOfStreams;
return TRUE;
}
//Keep track of the fact the stream exists so they can be numbered correctly.
TotalNumberOfStreams++;
//Go to the next stream.
}while(FindNextStreamInAttrList(pAle, &pAle, dwAttrListLength, &ExtentData));
}
//Invalid stream number because no stream was found.
*pStreamNumber = 0xFFFFFFFF;
}
__finally{
//If hAle was allocated, then it needs to be unlocked and freed.
if(hAle){
EH_ASSERT(GlobalUnlock(hAle) == FALSE);
EH_ASSERT(GlobalFree(hAle) == NULL);
}
}
return FALSE;
}
//Given the number of a stream, GetStreamNameAndTypeFromNumber finds what the stream's name and type are.
BOOL
GetStreamNameAndTypeFromNumber(
ULONG StreamNumber,
TCHAR* StreamName,
ATTRIBUTE_TYPE_CODE* pTypeCode,
FILE_RECORD_SEGMENT_HEADER* pFrs
)
{
ATTRIBUTE_RECORD_HEADER* pArh = NULL; //The specific attribute we're looking at now.
ATTRIBUTE_LIST_ENTRY* pAle = NULL; //The attribute entry we're looking at now (only used for huge and mega files).
HANDLE hAle = NULL; //Handle to memory for attribute list if it was nonresident and had to be read off the disk.
LONGLONG SaveFrn = 0; //Used to compare the FRN before and after getting a new FRS to make sure we got the one we want.
DWORD dwAttrListLength = 0; //The length of the attribute list in a huge or mega file.
EXTENT_LIST_DATA ExtentData;
DWORD TotalNumberOfStreams;
//Validate the input criteria to make sure it could represent a valid stream.
//($DATA, $INDEX_ALLOCATION, $ATTRIBUTE_LIST, $BITMAP).
EF_ASSERT(*pTypeCode == $DATA || $INDEX_ALLOCATION || $BITMAP);
//Set up the Extent pointers structure to fill in the extent list in VolData.
ZeroMemory(&ExtentData, sizeof(EXTENT_LIST_DATA));
ExtentData.hExtents = VolData.hExtentList;
ExtentData.pExtents = VolData.pExtentList;
ExtentData.ExtentListAlloced = (UINT)VolData.ExtentListAlloced;
#ifdef OFFLINEDK
//Do initializtion to get the FRS if it wasn't already loaded.
if(!pFrs){
UCHAR* pMftBitmap = NULL;
FILE_RECORD_SEGMENT_HEADER* pFileRecord = (FILE_RECORD_SEGMENT_HEADER*)VolData.pFileRecord;
//0.0E00 Get a pointer to the MFT bitmap since we will be reading from the disk directly rather than via a hook.
EF((pMftBitmap = (UCHAR*)GlobalLock(VolData.hMftBitmap)) != NULL);
if (pMftBitmap != NULL) {
BOOL isLocked = GlobalUnlock(VolData.hMftBitmap);
EH(!isLocked);
}
}
#endif
//Initialize the VolData fields.
VolData.FileSize = 0;
VolData.bFragmented = FALSE;
VolData.bCompressed = FALSE;
VolData.bDirectory = FALSE;
//Get the FRS if it wasn't already loaded.
if(!pFrs){
//Set the pFrs to point to a buffer to hold the FRS.
pFrs = (FILE_RECORD_SEGMENT_HEADER*)VolData.pFileRecord; //A shortcut to reduce code + cycles when accessing the frs.
//Save the FRN so we know if we get the wrong FRS later.
SaveFrn = VolData.FileRecordNumber;
//Read in the FRS for the attribute.
#ifdef OFFLINEDK
//0.1E00 Get an FRS.
EF(GetFrs(&VolData.FileRecordNumber, VolData.pMftExtentList, pMftBitmap, VolData.pMftBuffer, pFileRecord));
#else
//0.1E00 Get the next file record.
EF(GetInUseFrs(VolData.hVolume, &VolData.FileRecordNumber, (FILE_RECORD_SEGMENT_HEADER*)VolData.pFileRecord, (ULONG)VolData.BytesPerFRS));
#endif
//Make sure we got the FRS we requested.
EF_ASSERT(VolData.FileRecordNumber == SaveFrn);
}
//Detect if it is a directory and set the flag
if(pFrs->Flags & FILE_FILE_NAME_INDEX_PRESENT){
VolData.bDirectory = TRUE;
}
else{
VolData.bDirectory = FALSE;
}
//Find $STANDARD_INFORMATION attribute -- if there is none then don't use this FRS.
if(!FindAttributeByType($STANDARD_INFORMATION, pFrs, &pArh, (ULONG)VolData.BytesPerFRS)){
return FALSE;
}
//Note if the file is compressed or not.
VolData.bCompressed = (((PSTANDARD_INFORMATION)((UCHAR*)pArh+pArh->Form.Resident.ValueOffset))->FileAttributes & FILE_ATTRIBUTE_COMPRESSED) ? TRUE : FALSE;
//Note that there were no streams found yet.
TotalNumberOfStreams = 0;
__try{
//Look for an $ATTRIBUTE_LIST
if(!FindAttributeByType($ATTRIBUTE_LIST, pFrs, &pArh, (ULONG)VolData.BytesPerFRS)){
//If no $ATTRIBUTE_LIST was found, move to the first stream in the FRS.
if(!FindStreamInFrs(pFrs, &pArh, &ExtentData)){
//If no stream was found, then there is no extent list for this file.
return TRUE;
}
do{
//Check for a match on the stream.
if(StreamNumber == TotalNumberOfStreams){
*pTypeCode = pArh->TypeCode;
if(pArh->NameLength){
CopyMemory(StreamName, (UCHAR*)pArh+pArh->NameOffset, pArh->NameLength*sizeof(TCHAR));
StreamName[pArh->NameLength] = 0;
}
else{
StreamName[0] = 0;
}
return TRUE;
}
//Keep track of the fact the stream exists so they can be numbered correctly.
TotalNumberOfStreams++;
//Go to the next stream.
}while(FindNextStreamInFrs(pFrs, &pArh, &ExtentData));
}
//If an $ATTRIBUTE_LIST was found
else{
//If the $ATTRIBUTE_LIST is nonresident get the entire attribute list from the disk before proceeding.
if(pArh->FormCode == NONRESIDENT_FORM){
//Load the attribute extents from the disk.
LoadExtentDataToMem(pArh, &hAle, &dwAttrListLength);
//Get a pointer to the allocated memory.
EF_ASSERT(pAle = (ATTRIBUTE_LIST_ENTRY*)GlobalLock(hAle));
}
//If it was a resident attribute list, then the length of the attribute list can be determined from the attribute.
else{
pAle = (ATTRIBUTE_LIST_ENTRY*)(pArh->Form.Resident.ValueOffset + (UCHAR*)pArh);
dwAttrListLength = pArh->Form.Resident.ValueLength;
}
//Move to the first stream.
if(!FindStreamInAttrList(pAle, &pAle, dwAttrListLength, &ExtentData)){
//If no stream was found, then there is no extent list for this file.
return TRUE;
}
do{
//Check for a match on the stream.
if(StreamNumber == TotalNumberOfStreams){
*pTypeCode = pAle->AttributeTypeCode;
if(pAle->AttributeNameLength){
CopyMemory(StreamName, (UCHAR*)pAle+pAle->AttributeNameOffset, pAle->AttributeNameLength*sizeof(TCHAR));
StreamName[pAle->AttributeNameLength] = 0;
}
else{
StreamName[0] = 0;
}
return TRUE;
}
//Keep track of the fact the stream exists so they can be numbered correctly.
TotalNumberOfStreams++;
//Go to the next stream.
}while(FindNextStreamInAttrList(pAle, &pAle, dwAttrListLength, &ExtentData));
}
//Invalid stream number because no stream was found.
StreamNumber = 0xFFFFFFFF;
}
__finally {
//If hAle was allocated, then it needs to be unlocked and freed.
if(hAle){
EH_ASSERT(GlobalUnlock(hAle) == FALSE);
EH_ASSERT(GlobalFree(hAle) == NULL);
}
}
return FALSE;
}
//Reads in the extents which are not part of the file's data. (I.E. extents which are contained in non-resident
//ATTRIBUTE_LIST's or other non-resident attributes other than $DATA and $INDEX_ALLOCATION.)
//Note that this function operates nearly identical to GetExtentList only it is looking for any attribute other than
//$DATA and $INDEX_ALLOCATION. The code can be copied and revised from GetExtentlist.
//Even cooler, see if you can integrate them into one. I'm not sure that's reasonably possible because:
//The most major difference between GetNonDataStreamExtents and GetExtentList is that GetNonDataStreamExtents should
//concatenate extent lists from all non-data attributes. Therefore, this function will create only one "stream" in the
//extent list. The reason for this is so that Offline can simply defrag all the non-data stream stuff in one shibang.
//Online won't be defragging it and will be simply coloring it system space color, so having only one stream simplifies
//the process for both Offline and Online and DKMS.
BOOL
GetNonDataStreamExtents(
)
{
//Look for an $ATTRIBUTE_LIST
//If not found
//Move to the first stream in the FRS.
//If this attribute is not a $DATA or $INDEX_ALLOCATION and it is nonresident…
//Load the extents from the attribute.
//Go to the next stream.
//If an $ATTRIBUTE_LIST was found
//If the $ATTRIBUTE_LIST is resident
//Move to the first stream.
//If the attribute is not a $DATA or $INDEX_ALLOCATION…
//Load its FRS.
//Go to the attribute.
//If the attribute is nonresident
//Load the extents from the attribute.
//Find the next stream.
//If the $ATTRIBUTE_LIST is non-resident
//Load the attribute extents from the disk.
//Move to the first stream.
//If the attribute is not a $DATA or $INDEX_ALLOCATION…
//Load its FRS.
//Go to the attribute.
//If the attribute is nonresident
//Load the extents from the attribute.
//Find the next stream.
return TRUE;
}
//A really quickie version of GetExtentList which only does enough work to identify whether the file has an extent list or not.
//This function is used only to identify how many entries to make in the file lists.
BOOL
IsNonresidentFile(
DWORD dwEnabledStreams,
FILE_RECORD_SEGMENT_HEADER* pFrs
)
{
EXTENT_LIST_DATA ExtentData;
ATTRIBUTE_RECORD_HEADER* pArh = NULL; //The specific attribute we're looking at now.
//bug occurred during stress. We do not test for NULL condition.
if(pFrs == NULL)
{
return FALSE;
}
//Set up the Extent pointers structure to fill in the extent list in VolData.
ZeroMemory(&ExtentData, sizeof(EXTENT_LIST_DATA));
ExtentData.dwEnabledStreams = dwEnabledStreams;
//Look for an $ATTRIBUTE_LIST
if(!FindAttributeByType($ATTRIBUTE_LIST, pFrs, &pArh, (ULONG)VolData.BytesPerFRS)){
if (VolData.bMFTCorrupt) {
return FALSE;
}
//If no $ATTRIBUTE_LIST was found, move to the first stream in the FRS.
if(!FindStreamInFrs(pFrs, &pArh, &ExtentData)){
//If no stream was found, then there is no extent list for this file -- therefore it is resident.
return FALSE;
}
do{
//If this is a nonresident stream, then this is a nonresident file.
if(pArh->FormCode == NONRESIDENT_FORM){
return TRUE;
}
//Go to the next stream.
}while(FindNextStreamInFrs(pFrs, &pArh, &ExtentData));
}
else{
//An attribute list was found, therefore, this is a nonresident file.
return TRUE;
}
//No nonresident attributes were found. Therefore this is a resident file.
return FALSE;
}
//Here is an alternate algorithm for IsNonresidentFile which would be more efficient. Check for robustness before implementing.
// //0.0E00 Skip this filerecord if it has no attribute list or data attribute
// if(!FindAttributeByType($ATTRIBUTE_LIST, pFileRecord, &pArh, (ULONG)VolData.BytesPerFRS)){
//
// //0.0E00 If there are no $DATA or $INDEX_ALLOCATION attributes then this is a small file and has no extents. Ignore it.
// if((!FindAttributeByType($DATA, pFileRecord, &pArh, (ULONG)VolData.BytesPerFRS) &&
// !FindAttributeByType($INDEX_ALLOCATION, pFileRecord, &pArh, (ULONG)VolData.BytesPerFRS))){
// continue;
// }
//
// //0.0E00 Skip small files -- there's no defragmentation to do.
// //That is, resident files, or nonresident files with no data on disk.
// if(pArh->FormCode == RESIDENT_FORM || (pArh->FormCode == NONRESIDENT_FORM && pArh->Form.Nonresident.TotalAllocated == 0)){
// continue;
// }
// }
/*****************************************************************************************************************
COPYRIGHT© 2001 Microsoft Corporation and Executive Software International, Inc.
ROUTINE DESCRIPTION:
Directories can't really have their extent lists collapsed. But, we would
like to know if they need defragmenting, so the fragmentation state has to
be verified or denied, so we know what to do later. This was scavenged from
CollapseExtents, which calls GetLowestStartingLcn; that routine does its
thing based on the number of fragments, not the number of extents (??) so
we can't use that, either.
This routine is only used by OFFLINE.
INPUT + OUTPUT:
None.
GLOBALS:
OUT VolData.ExtentsState - Whether or not the file is contiguous or fragmented.
RETURN:
TRUE - Success.
FALSE - Fatal Error.
*/
#ifdef OFFLINEDK
//This function needs to be rewritten to fit the new extents scheme.
//This is Offline code, aka. John's.
/*
BOOL
CheckFragged(
)
{
LONGLONG NumberOfExtents = VolData.NumberOfExtents;
LONGLONG Extent = 0;
EXTENT_LIST* pExtentList = VolData.pExtentList;
LONGLONG MinCluster = 0;
BOOL bSetMin =FALSE;
LONGLONG LastRealExtent;
//0.1E00 say it isn't fragmented to start with (innocent till proven guilty...)
VolData.bFragmented = FALSE;
VolData.NumberOfFragments = 1;
//Don't bother looping if nothing to loop on...
if(VolData.NumberOfExtents < 2){
VolData.StartingLcn = pExtentList[0].StartingLcn;
return TRUE;
}
//0.1E00 Go through each extent and see if it is really adjacent to the next
for(Extent = 0; Extent < NumberOfExtents; Extent ++){
//Ensure we skip virtual extents
if (pExtentList[Extent].StartingLcn >= 0) {
//ensure we get a valid minimum starting spot
if (!bSetMin) {
bSetMin = TRUE;
MinCluster = pExtentList[Extent].StartingLcn;
LastRealExtent = Extent;
continue;
}
//1.0E00 If this Lcn is lower than whatever was lowest so far...
if (pExtentList[Extent].StartingLcn < MinCluster) {
//Record it so it can be returned
MinCluster = pExtentList[Extent].StartingLcn;
}
//1.0E00 If this extent is adjacent to the last extent
if(pExtentList[LastRealExtent].StartingLcn +
pExtentList[LastRealExtent].ClusterCount !=
pExtentList[Extent].StartingLcn){
//Okay, it's not contiguous...
VolData.bFragmented = TRUE;
VolData.NumberOfFragments++;
} //end "if not adjacent" test
LastRealExtent = Extent;
} //end if Lcn >= 0
} //end "for Extent" loop
//0.1E00 Set the lowest Lcn of the file. This is used in our algorithms to determine which files to move.
VolData.StartingLcn = MinCluster;
return TRUE;
}
*/
#endif
/*****************************************************************************************************************
FORMAT OF MAPPING PAIRS:
The first byte contains the sizes of the mapping pair fields. The low nibble contains the number of bytes
in the extent length field, and the high nibble contains the number of bytes in the extent offset field.
The next bytes are the extent length field, whose length is determined by the first byte.
The next bytes are the extent offset field, whoese length is determined by the first byte.
The extent offset field contains an offset in clusters from the previous extent, not an absolute value on the
disk. If this is the first extent, its offset is from zero.
The list of mapping pairs is null terminated -- the first byte describing the length of the fields will
contain a zero, and the extent length value will be zero.
If the size of the extent offset field is zero (and the extent length is not) then this is a virtual extent:
Virtual extents are used in compressed files; each block of a compressed file is represented by a
physical extent and a virtual extent (if the block did not compress there is no virtual extent).
The physical extent describes the clusters actually used on the disk by the block in it's compressed
form; the virtual extent only indicates the difference between the uncompressed block size and the
compressed block size. ie: if the uncompressed block is 16 clusters and the block compresses down to
8 clusters then the block will have 2 mapping pairs: the physical extent length will be 8, the virtual
extent length will be 8, and the block will use 8 clusters of disk space.
/*****************************************************************************************************************/
/*****************************************************************************************************************
COPYRIGHT© 2001 Microsoft Corporation and Executive Software International, Inc.
ROUTINE DESCRIPTION:
Get the basic statistics for an NTFS volume.
INPUT + OUTPUT:
None.
GLOBALS:
The VolData structure should be zeroed out with the exception of any fields already filled in before calling this function.
IN VolData.cDrive - The drive letter for the volume we wish to access.
OUT VolData.hVolume - Handle to the volume.
OUT VolData.FileSystem - FS_NTFS.
OUT VolData.MftStartLcn - First cluster of the MFT.
OUT VolData.Mft2StartLcn - First cluster of the MFT Mirror.
OUT VolData.BytesPerSector - Bytes Per Sector.
OUT VolData.BytesPerCluster - Bytes Per Cluster.
OUT VolData.BytesPerFRS - Bytes Per FileRecord.
OUT VolData.ClustersPerFRS - Clusters Per FileRecord.
OUT VolData.TotalSectors - Total Sectors on the volume.
OUT VolData.TotalClusters - Total Clusters on the volume.
OUT VolData.MftZoneStart - Mft Zone Start.
OUT VolData.MftZoneEnd - Mft Zone End.
OUT VolData.FreeSpace - Free Space on the volume.
OUT VolData.UsedSpace - Used Space on the volume.
OUT VolData.UsedClusters - Used Clusters on the volume.
OUT VolData.SectorsPerCluster - Sectors Per Cluster.
OUT VolData.MftStartOffset - Byte offset into the volume of the MFT.
OUT VolData.TotalFileRecords - Total FileRecords on the volume.
OUT VolData.FileRecordNumber - End of the MFT; where we start scanning filerecords.
OUT VolData.CenterOfDisk - Cluster at the center of the disk.
OUT VolData.CenterOfBitmap - Word at the Center Of the Bitmap.
OUT VolData.CenterMask - Mask for the bit in the word at the Center Of the Bitmap.
OUT VolData.CenterBit - Bit in the word at the Center Of the Bitmap.
OUT VolData.BitmapSize - Bitmap Size.
OUT VolData.hFile - INVALID_HANDLE_VALUE.
RETURN:
TRUE = Success
FALSE = Failure
*/
BOOL
GetNtfsVolumeStats(
)
{
//TCHAR cVolume[10];
ULONG BytesReturned = 0;
NTFS_VOLUME_DATA_BUFFER VolumeData = {0};
//0.1E00 Fill in the handle values which aren't zero.
VolData.hVolume = INVALID_HANDLE_VALUE;
VolData.hFile = INVALID_HANDLE_VALUE;
//0.1E00 Get the file system on this drive.
EF(GetFileSystem(VolData.cVolumeName, &VolData.FileSystem, VolData.cVolumeLabel));
//0.1E00 If this volume is not NTFS, error out.
if(VolData.FileSystem != FS_NTFS){
return FALSE;
}
//0.0E00 Get handle to volume
VolData.hVolume = CreateFile(
VolData.cVolumeName,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ|FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL|FILE_FLAG_NO_BUFFERING,
NULL);
EF_ASSERT(VolData.hVolume != INVALID_HANDLE_VALUE);
//0.0E00 Get the volume data
EF(ESDeviceIoControl(VolData.hVolume,
FSCTL_GET_NTFS_VOLUME_DATA,
NULL,
0,
&VolumeData,
sizeof(VolumeData),
&BytesReturned,
NULL));
//0.1E00 Copy over the values from the volume data that belong in the VolData as well.
VolData.MftStartLcn = VolumeData.MftStartLcn.QuadPart; //First cluster of the MFT.
VolData.Mft2StartLcn = VolumeData.Mft2StartLcn.QuadPart; //First Cluster of the MFT2.
VolData.BytesPerSector = VolumeData.BytesPerSector; //# of bytes per sector on the disk.
VolData.BytesPerCluster = VolumeData.BytesPerCluster; //# of bytes per cluster on the disk.
VolData.BytesPerFRS = VolumeData.BytesPerFileRecordSegment; //# of bytes in a file record in the MFT.
VolData.ClustersPerFRS = VolumeData.ClustersPerFileRecordSegment; //# of clusters in a file record in the MFT.
VolData.TotalSectors = VolumeData.NumberSectors.QuadPart; //# of sectors on the disk.
VolData.TotalClusters = VolumeData.TotalClusters.QuadPart; //# of clusters on the disk.
VolData.MftZoneStart = VolumeData.MftZoneStart.QuadPart; //Cluster # the MFT Zone starts on.
VolData.MftZoneEnd = VolumeData.MftZoneEnd.QuadPart; //Cluster # the MFT Zone ends on.
//0.1E00 Check for invalid values.
EF(VolData.BytesPerSector != 0);
EF(VolData.BytesPerFRS != 0);
//It turns out that the MFTZone values can be outside the boundaries of the disk
//this in turn causes the volume bitmap manipulators to fail with access vio's
//so we will ensure that this cannot happen JLJ 21Apr98 6June98
if(VolData.MftZoneStart > VolData.TotalClusters-1){
VolData.MftZoneStart = VolData.TotalClusters-1;
}
if(VolData.MftZoneEnd < VolData.MftZoneStart){
VolData.MftZoneEnd = VolData.MftZoneStart;
}
if(VolData.MftZoneEnd > VolData.TotalClusters-1){
VolData.MftZoneEnd = VolData.TotalClusters-1;
}
//0.1E00 Calulate a few values in VolData from the volume data.
VolData.FreeSpace = VolumeData.FreeClusters.QuadPart * VolData.BytesPerCluster; //Number of free bytes on the drive.
VolData.UsedSpace = (VolData.TotalClusters * VolData.BytesPerCluster) - VolData.FreeSpace; //Number of used bytes on the drive.
VolData.UsedClusters = VolData.TotalClusters - VolumeData.FreeClusters.QuadPart; //Number of used clusters on the disk.
VolData.SectorsPerCluster = VolData.BytesPerCluster / VolData.BytesPerSector; //Number of Sectors per cluster
VolData.MftStartOffset = VolData.MftStartLcn * VolData.BytesPerCluster; //How many bytes into the disk the MFT starts on.
VolData.TotalFileRecords = VolumeData.MftValidDataLength.QuadPart / VolData.BytesPerFRS; //Total number of flie records in use.
VolData.FileRecordNumber = VolData.TotalFileRecords; //Start at the last file record on the disk.
VolData.CenterOfDisk = VolData.TotalClusters / 2; //Find the center cluster on the disk.
//0.0E00 Compute number of bits needed for bitmap, rounded up to the nearest 32-bit number.
VolData.BitmapSize = (((VolData.TotalClusters /32) +
((VolData.TotalClusters % 32) ? 1 : 0)) + 1) * 32;
VolData.BootOptimizeBeginClusterExclude = 0;
VolData.BootOptimizeEndClusterExclude = 0;
if (IsBootVolume(VolData.cDrive)) {
//get the registry value for BootOptimizeBeginClusterExclude
HKEY hValue = NULL;
DWORD dwRegValueSize = 0;
long ret = 0;
TCHAR cRegValue[100];
// get Boot Optimize Begin Cluster Exclude from registry
dwRegValueSize = sizeof(cRegValue);
ret = GetRegValue(
&hValue,
BOOT_OPTIMIZE_REGISTRY_PATH,
BOOT_OPTIMIZE_REGISTRY_LCNSTARTLOCATION,
cRegValue,
&dwRegValueSize);
RegCloseKey(hValue);
//check to see if the key exists, else exit from routine
if (ret == ERROR_SUCCESS) {
VolData.BootOptimizeBeginClusterExclude = _ttoi(cRegValue);
}
// get Boot Optimize End Cluster Exclude from registry
hValue = NULL;
dwRegValueSize = sizeof(cRegValue);
ret = GetRegValue(
&hValue,
BOOT_OPTIMIZE_REGISTRY_PATH,
BOOT_OPTIMIZE_REGISTRY_LCNENDLOCATION,
cRegValue,
&dwRegValueSize);
RegCloseKey(hValue);
//check to see if the key exists, else exit from routine
if (ret == ERROR_SUCCESS) {
VolData.BootOptimizeEndClusterExclude = _ttoi(cRegValue);
}
}
return TRUE;
}
/*****************************************************************************************************************
COPYRIGHT© 2001 Microsoft Corporation and Executive Software International, Inc.
ROUTINE DESCRIPTION:
Get the next file record that is in use..
INPUT + OUTPUT:
IN hVolume - The handle to the volume we're getting the FRS from.
IN OUT pFileRecordNumber - What file record number is desired. It's a pointer so we can write in what
file record number is actually obtained.
OUT pFrs - Where to write the file record after it is read.
IN uBytesPerFRS - The number of bytes per FRS.
GLOBALS:
None.
RETURN:
TRUE = Success
FALSE = Failure
*/
BOOL
GetInUseFrs(
IN HANDLE hVolume,
IN OUT LONGLONG* pFileRecordNumber,
OUT FILE_RECORD_SEGMENT_HEADER* pFrs,
IN ULONG uBytesPerFRS
)
{
NTFS_FILE_RECORD_INPUT_BUFFER NtfsFileRecordInputBuffer;
PNTFS_FILE_RECORD_OUTPUT_BUFFER pNtfsFileRecordOutputBuffer;
PUCHAR pFileRecord;
ULONG BytesReturned = 0;
require(hVolume);
require(uBytesPerFRS);
//0.1E00 Make a pointer to a global buffer for the file records.
pNtfsFileRecordOutputBuffer = (PNTFS_FILE_RECORD_OUTPUT_BUFFER)&FileRecordOutputBuffer;
//0.1E00 Get a pointer to the file record in it.
pFileRecord = (PUCHAR)&pNtfsFileRecordOutputBuffer->FileRecordBuffer;
//0.1E00 Set the number of the file record we wish to read.
NtfsFileRecordInputBuffer.FileReferenceNumber.QuadPart = *pFileRecordNumber;
//0.1E00 Set the number of bytes to read.
pNtfsFileRecordOutputBuffer->FileRecordLength = (ULONG)uBytesPerFRS;
//0.1E00 Do the read via our hooks in the OS.
EF(ESDeviceIoControl(
hVolume,
FSCTL_GET_NTFS_FILE_RECORD,
&NtfsFileRecordInputBuffer,
sizeof(NtfsFileRecordInputBuffer),
pNtfsFileRecordOutputBuffer,
sizeof(FileRecordOutputBuffer),
&BytesReturned,
NULL));
//0.1E00 Copy the memory out of the file record buffer.
CopyMemory(pFrs, pFileRecord, (ULONG)uBytesPerFRS);
//0.1E00 Copy out the file record number that was actually obtained.
*pFileRecordNumber = (LONGLONG)pNtfsFileRecordOutputBuffer->FileReferenceNumber.QuadPart;
return TRUE;
}
/*****************************************************************************************************************
COPYRIGHT© 2001 Microsoft Corporation and Executive Software International, Inc.
ROUTINE DESCRIPTION:
This routine builds the full path for a file from its file record
INPUT + OUTPUT:
None.
GLOBALS:
IN OUT Various VolData fields.
RETURN:
TRUE = Success
FALSE = Failure
*/
BOOL
GetNtfsFilePath(
)
{
TCHAR cName[MAX_PATH+1];
LONGLONG SaveFileRecordNumber;
LONGLONG ParentFileRecordNumber;
HANDLE hFrs = NULL;
FILE_RECORD_SEGMENT_HEADER* pFrs = NULL;
static LONGLONG LastPathFrs = 0;
#ifdef OFFLINEDK
// todo max_path UCHAR* pMftBitmap = NULL;
FILE_RECORD_SEGMENT_HEADER* pFileRecord = (FILE_RECORD_SEGMENT_HEADER*)VolData.pFileRecord;
#endif
//0.0E00 Get file's name and file record number of its parent directory-
EF(GetNameFromFileRecord((FILE_RECORD_SEGMENT_HEADER*)VolData.pFileRecord, cName, &ParentFileRecordNumber));
if (VolData.bMFTCorrupt) {
return FALSE;
}
//0.0E00 If this file is in the same dir as the last file was, use the last file's path
if ((ParentFileRecordNumber == LastPathFrs) && (VolData.vFileName.GetLength() != 0)){
//0.1E00 Find the last slash in the previous file name.
TCHAR *pFileName = wcsrchr(VolData.vFileName.GetBuffer(), L'\\');
//0.1E00 Error if no slash.
EF_ASSERT(pFileName);
// move past the backslash and put a NULL
pFileName++;
*pFileName = (TCHAR) NULL;
// append the new file name
VolData.vFileName += cName;
//0.1E00 We have our path so return.
return TRUE;
}
// Save the parent's FRS # in case we can
// detect in the above if-block that we are in the same sub-directory
LastPathFrs = ParentFileRecordNumber;
//0.0E00 If this file's dir is the root, tack the drive letter to the beginning and return
if(ParentFileRecordNumber == ROOT_FILE_NAME_INDEX_NUMBER){
//0.1E00 Save our name.
// start with the volume name (guid or UNC)
VolData.vFileName = VolData.cVolumeName;
//0.1E00 Reformat our name with the drive letter.
// tack on the file name
VolData.vFileName.AddChar(L'\\');
//
// Append the file name. Note that for the root directory, we don't
// want to append the filename ("."), since we won't be able to get
// a handle to the directory then.
//
if (VolData.FileRecordNumber != ROOT_FILE_NAME_INDEX_NUMBER) {
VolData.vFileName += cName;
}
//0.1E00 We have our path so return.
return TRUE;
}
// we need to rebuild the entire path, so start off with the file name
VolData.vFileName = cName;
//0.0E00 Allocate and lock a buffer for the parent directory's file record
EF(AllocateMemory((DWORD)(VolData.BytesPerFRS + VolData.BytesPerSector), &hFrs, (void**)&pFrs));
BOOL bReturnValue = FALSE; // assume an error
VString tmpPath;
//0.0E00 Loop while moving up the tree until we hit the root.
while(ParentFileRecordNumber != ROOT_FILE_NAME_INDEX_NUMBER){
//0.1E00 Save the parent's file record number.
SaveFileRecordNumber = ParentFileRecordNumber;
#if OFFLINEDK
//0.0E00 Get parent dir's File Record Segment
if (!GetFrs(&ParentFileRecordNumber, VolData.pMftExtentList, pMftBitmap, VolData.pMftBuffer, pFrs)){
EH(FALSE);
//__leave;
goto END;
}
#else
//0.0E00 Get parent dir's File Record Segment
if (!GetInUseFrs(VolData.hVolume, &ParentFileRecordNumber, pFrs, (ULONG)VolData.BytesPerFRS)){
EH(FALSE);
//__leave;
goto END;
}
#endif
//0.1E00 Error out if we didn't get the parent's File Record Segment
if (ParentFileRecordNumber != SaveFileRecordNumber){
EH_ASSERT(FALSE);
//__leave;
goto END;
}
//0.0E00 Get parent dir's name and its parent's file record number
if (!GetNameFromFileRecord(pFrs, cName, &ParentFileRecordNumber)){
if (VolData.bMFTCorrupt) {
bReturnValue = FALSE;
}
EH(FALSE);
//__leave;
goto END;
}
//0.0E00 Append prior path to current dir name
wcscat(cName, L"\\");
// start off with the dir we just got
tmpPath = cName;
// add the existing path to create a complete path
tmpPath += VolData.vFileName;
// assign that back to the final resting place
VolData.vFileName = tmpPath;
}
// pre-pend the volume name
tmpPath = VolData.cVolumeName;
tmpPath.AddChar(L'\\');
tmpPath += VolData.vFileName;
// assign that back to the final resting place
VolData.vFileName = tmpPath;
bReturnValue = TRUE; // no errors
END:
if(hFrs) {
EH_ASSERT(GlobalUnlock(hFrs) == FALSE);
EH_ASSERT(GlobalFree(hFrs) == NULL);
}
return bReturnValue;
}
/*****************************************************************************************************************
COPYRIGHT© 2001 Microsoft Corporation and Executive Software International, Inc.
ROUTINE DESCRIPTION:
This routine gets the wide character NTFS long name from a file record, and returns it as a character string.
This also returns it's parent file record number.
INPUT + OUTPUT:
IN pFrs - Pointer to buffer containing file's file record
OUT pcName - Pointer to character buffer to hold the name from this file record
IN pParentFileRecordNumber - pointer to file record number of parent dir
GLOBALS:
IN VolData.BytesPerFRS - The number of bytes in a file record segment.
RETURN:
TRUE = Success
FALSE = Failure
*/
BOOL
GetNameFromFileRecord(
IN FILE_RECORD_SEGMENT_HEADER* pFrs,
OUT TCHAR* pcName,
IN LONGLONG* pParentFileRecordNumber
)
{
PATTRIBUTE_RECORD_HEADER pArh = NULL;
PFILE_NAME pFileNameStruc = NULL;
PFILE_NAME pLastFileNameStruc = NULL;
PWCHAR pwName = NULL;
int i;
ATTRIBUTE_LIST_ENTRY* pAle = NULL; //The attribute entry we're looking at now (only used for huge and mega files).
ATTRIBUTE_LIST_ENTRY* pAleStart = NULL; //The start of the attribute list (only used for huge and mega files).
HANDLE hAle = NULL; //Handle to memory for attribute list if it was nonresident and had to be read off the disk.
LONGLONG SaveFrn = 0; //Used to compare the FRN before and after getting a new FRS to make sure we got the one we want.
DWORD dwAttrListLength = 0; //The length of the attribute list in a huge or mega file.
HANDLE hLocalFRS=NULL;
FILE_RECORD_SEGMENT_HEADER* pLocalFRS=NULL;
FILE_RECORD_SEGMENT_HEADER* pLocalFRSSave=NULL;
LONGLONG llLocalFRN = 0;
BOOL bReturnValue = FALSE; // assume an error condition
//1.0E00 The pointer to the current MFT record better not be NULL...
EF_ASSERT(pFrs);
__try {
//1.1E00 Look for an $ATTRIBUTE_LIST first
if(!FindAttributeByType($ATTRIBUTE_LIST, pFrs, &pArh, (ULONG)VolData.BytesPerFRS)){
//1.1E00 This paragraph handles the minimal case; this is where
//both $FILE_NAME attributes are in the base MFT record
//1.1E00 Find the filename attribute; error if it can't be found
if (!FindAttributeByType($FILE_NAME, pFrs, &pArh, (ULONG)VolData.BytesPerFRS)){
EH(FALSE);
__leave;
}
//1.1E00 Find the NTFS long name attribute
while(TRUE){
//1.1E00 Get pointer to name structure in name attribute
pFileNameStruc = (PFILE_NAME)((char*)pArh+pArh->Form.Resident.ValueOffset);
//1.1E00 If this is the NTFS name attribute then we found it, so break
//Only bits 1 and 2 have meaning in the Flags:
// Neither bits set: Long Name Only, use this one
// Bit 0x01 only: Long file name, but 8.3 is also available, keep looking and you'll find it
// Bit 0x02 only: 8.3 name, but long file name is also available, keep looking
// Both bits: This is both the NTFS and 8.3 name (long name IS 8.3 format)
if(pFileNameStruc->Flags & FILE_NAME_NTFS || pFileNameStruc->Flags == 0){
break;
}
//1.1E00 if we didn't get the NTFS filename, null the pointer
//so we realize it at the end of the loop
pFileNameStruc = NULL;
//1.1E00 Update our pointer to the next attribute.
pArh = (PATTRIBUTE_RECORD_HEADER)((char*)pArh + pArh->RecordLength);
//1.1E00 find the next name attribute
while(pArh < (ATTRIBUTE_RECORD_HEADER*)((PUCHAR)pFrs + (ULONG)VolData.BytesPerFRS) &&
pArh->TypeCode != $FILE_NAME &&
pArh->TypeCode != $END){
//1.1E00 We did not find our attribute, so go to the next.
pArh = (ATTRIBUTE_RECORD_HEADER*)((PUCHAR)pArh + pArh->RecordLength);
}
//1.1E00 Error if attribute not found
if(pArh->TypeCode != $FILE_NAME){
EH(FALSE);
__leave;
}
} // end while (TRUE)
} // end if no $ATTRIBUTE_LIST found
// 1.1E00 we come here if an $ATTRIBUTE_LIST was found
// (resident or non-resident)
else{
//1.1E00 This (much longer) paragraph handles the case where
//either $FILE_NAME attribute is not within the base MFT record.
//Where it actually can be found depends on whether the
//ATTRIBUTE_LIST attribute lists are resident (within the base
//MFT record) or non-resident (within external randomly-allocated
//clusters).
//1.1E00 If the $ATTRIBUTE_LIST is nonresident get the entire
//attribute list from the disk before proceeding.
if(pArh->FormCode == NONRESIDENT_FORM){
//1.1E00 Load the attribute extents from the disk (this allocate memory)
LoadExtentDataToMem(pArh, &hAle, &dwAttrListLength);
//1.1E00 Get a pointer to the allocated memory.
pAleStart = (ATTRIBUTE_LIST_ENTRY *) GlobalLock(hAle);
if (!pAleStart){
EH(FALSE);
__leave;
}
}
//1.1E00 If it was a resident attribute list, then the length
//of the attribute list can be determined from the attribute.
else{
pAleStart = (ATTRIBUTE_LIST_ENTRY*)(pArh->Form.Resident.ValueOffset + (UCHAR*)pArh);
dwAttrListLength = pArh->Form.Resident.ValueLength;
}
//1.1E00 Start at the beginning of the attribute list.
pAle = pAleStart;
while(TRUE) {
//1.1E00 find the next name attribute
while(pAle < (ATTRIBUTE_LIST_ENTRY*)((PUCHAR)pAleStart + (ULONG)dwAttrListLength) &&
pAle->AttributeTypeCode != $FILE_NAME &&
pAle->AttributeTypeCode != $END){
//1.1E00 We did not find our attribute, so go to the next.
pAle = (ATTRIBUTE_LIST_ENTRY*)((PUCHAR)pAle + pAle->RecordLength);
}
//The attribute list must end with a $END.
if (pAle >= (ATTRIBUTE_LIST_ENTRY*)((PUCHAR)pAleStart + (ULONG)dwAttrListLength)) {
VolData.bMFTCorrupt = TRUE;
bReturnValue = FALSE;
__leave;
}
//1.1E00 Error if attribute not found
if(pAle->AttributeTypeCode != $FILE_NAME){
__leave;
}
//1.1E00 grab the FRS# out of the attribute list
llLocalFRN = (pAle->SegmentReference.LowPart | (pAle->SegmentReference.HighPart << (sizeof(ULONG)*8)));
//1.1E00 if we make it this far, we'll need a buffer for
//examining MFT records so, only get the buffer if we haven't
//already gotten one
if (hLocalFRS == NULL) {
//1.1E00 Allocate and lock a buffer for the file record
if (!AllocateMemory((DWORD)(VolData.BytesPerFRS + VolData.BytesPerSector),
&hLocalFRS, (void**)&pLocalFRS)){
EH(FALSE);
__leave;
}
//1.1E00 Save the pointer to we can ensure the right
//pointer is always passed to the MFT record get routine
pLocalFRSSave = pLocalFRS;
}
else {
//1.1E00 if we already got the buffer, refresh the pointer
pLocalFRS = pLocalFRSSave;
}
#if OFFLINEDK
//1.1E00 Get the filename's File Record Segment
EF(GetFrs(&llLocalFRN, VolData.pMftExtentList, pMftBitmap, VolData.pMftBuffer, pLocalFrs));
#else
//1.1E00 Get the filename's File Record Segment
if (!GetInUseFrs(VolData.hVolume, &llLocalFRN, pLocalFRS, (ULONG)VolData.BytesPerFRS)){
EH(FALSE);
__leave;
}
#endif
//1.1E00 if we didn't get the NTFS filename, null the pointer
//so we realize we didn't get it at the end of the loop
pFileNameStruc = NULL;
//1.1E00 Find the filename attribute; error if it can't be found
if (!FindAttributeByType($FILE_NAME, pLocalFRS, &pArh, (ULONG)VolData.BytesPerFRS)) {
EH(FALSE);
__leave;
}
//1.1E00 Find the NTFS long name attribute
while(TRUE){
//1.1E00 Get pointer to name structure in name attribute
pFileNameStruc = (PFILE_NAME)((char*)pArh+pArh->Form.Resident.ValueOffset);
//1.1E00 If this is the NTFS name attribute then we found it, so break
//Only bits 1 and 2 have meaning in the Flags:
// Neither bits set: Long Name Only, use this one
// Bit 0x01 only: Long file name, but 8.3 is also available, keep looking and you'll find it
// Bit 0x02 only: 8.3 name, but long file name is also available, keep looking
// Both bits: This is both the NTFS and 8.3 name (long name IS 8.3 format)
if(pFileNameStruc->Flags & FILE_NAME_NTFS || pFileNameStruc->Flags == 0){
break;
}
//1.1E00 if we didn't get the NTFS filename, null the
//pointer so we realize it at the end of the loop
pFileNameStruc = NULL;
//1.1E00 Update our pointer to the next attribute.
pArh = (PATTRIBUTE_RECORD_HEADER)((char*)pArh + pArh->RecordLength);
//1.1E00 find the next name attribute
while(pArh < (ATTRIBUTE_RECORD_HEADER*)((PUCHAR)pLocalFRS + (ULONG)VolData.BytesPerFRS) &&
pArh->TypeCode != $FILE_NAME &&
pArh->TypeCode != $END){
//1.1E00 We did not find our attribute, so go to the next.
pArh = (ATTRIBUTE_RECORD_HEADER*)((PUCHAR)pArh + pArh->RecordLength);
}
//1.1E00 Error if attribute not found
if(pArh->TypeCode != $FILE_NAME){
break;
}
} // end while (TRUE)
//1.1E00 break out of loop if we found a NTFS filename
if (pFileNameStruc != NULL) {
break;
}
//1.1E00 We did not find our attribute, so go to the next.
pAle = (ATTRIBUTE_LIST_ENTRY*)((PUCHAR)pAle + pAle->RecordLength);
} // end while (TRUE)
} // end else (if $ATTRIBUTE_LIST present)
//1.1E00 This is the main point of this routine...we should never get
//here with a NULL pointer to the FileName in the MFT record
if (!pFileNameStruc){
EH_ASSERT(FALSE);
__leave;
}
//0.0E00 Get pointer to wide character name field
pwName = (PWCHAR)&pFileNameStruc->FileName;
//0.0E00 Copy the filename over.
for(i=0; (i<pFileNameStruc->FileNameLength) && (i<MAX_PATH) && (*pwName!=TEXT('\\'));pcName[i++] = (TCHAR)*pwName++)
;
//EF_ASSERT(*pwName!=TEXT('\\'));
if (*pwName == TEXT('\\')){
EH_ASSERT(FALSE);
__leave;
}
//EF_ASSERT(i<MAX_PATH);
if (i >= MAX_PATH){
EH_ASSERT(FALSE);
__leave;
}
//0.0E00 Null terminate the string
pcName[i] = 0;
//0.0E00 Return the parent's file record number
*pParentFileRecordNumber = pFileNameStruc->ParentDirectory.LowPart + (pFileNameStruc->ParentDirectory.HighPart<<32);
//0.0E00 Success!
bReturnValue = TRUE;
}
//1.1E00 on exit, make sure we've released any memory fetched.
__finally {
if(pLocalFRS) {
EH_ASSERT(GlobalUnlock(hLocalFRS) == FALSE);
EH_ASSERT(GlobalFree(hLocalFRS) == NULL);
}
if(hAle) {
EH_ASSERT(GlobalUnlock(hAle) == FALSE);
EH_ASSERT(GlobalFree(hAle) == NULL);
}
}
return bReturnValue;
}
BOOL
GetNextNtfsFile(
IN CONST PRTL_GENERIC_TABLE pTable,
IN CONST BOOLEAN Restart,
IN CONST LONGLONG ClusterCount,
IN CONST PFILE_LIST_ENTRY pEntry
)
{
LONGLONG fileRecordNumber = 0;
PFILE_LIST_ENTRY pFileListEntry = NULL;
BOOLEAN bNext = (Restart ? FALSE : TRUE),
done = TRUE;
static PVOID pRestartKey = NULL;
static ULONG ulDeleteCount = 0;
static FILE_LIST_ENTRY entry;
if (Restart) {
if (pEntry) {
CopyMemory(&entry, pEntry, sizeof(FILE_LIST_ENTRY));
}
else {
pFileListEntry = (PFILE_LIST_ENTRY) RtlEnumerateGenericTableAvl(pTable, TRUE);
if (pFileListEntry) {
CopyMemory(&entry, pFileListEntry, sizeof(FILE_LIST_ENTRY));
}
else {
ZeroMemory(&entry, sizeof(FILE_LIST_ENTRY));
}
}
pRestartKey = NULL;
}
do {
done = TRUE;
//
// Find the next entry in the table
//
do {
pFileListEntry = (PFILE_LIST_ENTRY) RtlEnumerateGenericTableLikeADirectory(
pTable,
NULL,
NULL,
bNext,
&pRestartKey,
&ulDeleteCount,
&entry);
bNext = TRUE;
} while ((ClusterCount) && (pFileListEntry) && (pFileListEntry->ClusterCount >= ClusterCount));
VolData.pFileListEntry = pFileListEntry;
if (!pFileListEntry) {
//
// We're done with this enumeration
//
return FALSE;
}
//
// Keep a copy of this entry around, in case it gets deleted and we need
// to carry on with our enumeration. (Usually, the enumeration will
// continue on the basis of pRestartKey, but if that key is deleted,
// it continues based on this).
//
CopyMemory(&entry, pFileListEntry, sizeof(FILE_LIST_ENTRY));
//1.0E00 Since a file was found in one of these lists, set the return values accordingly.
VolData.LastStartingLcn = VolData.StartingLcn = pFileListEntry->StartingLcn;
VolData.NumberOfClusters = pFileListEntry->ClusterCount;
//Get the file record number of the file from the list.
VolData.FileRecordNumber = pFileListEntry->FileRecordNumber;
// Set the flags
VolData.bDirectory = (pFileListEntry->Flags & FLE_DIRECTORY) ? TRUE : FALSE;
VolData.bFragmented = (pFileListEntry->Flags & FLE_FRAGMENTED) ? TRUE : FALSE;
VolData.bBootOptimiseFile = (pFileListEntry->Flags & FLE_BOOTOPTIMISE) ? TRUE : FALSE;
//1.0E00 This is used to compare with the FileRecordNumber returned by GetInUseFrs below to see if a file was deleted.
fileRecordNumber = VolData.FileRecordNumber;
//Check to see if the file record number is out of range.
if(fileRecordNumber > VolData.TotalFileRecords){
Trace(log, "GetNextNtfsFile: Invalid File Record Number: %x (TotalFileRecords:%x)",
fileRecordNumber, VolData.FileRecordNumber);
return FALSE;
}
EF(GetInUseFrs(VolData.hVolume, &VolData.FileRecordNumber, (FILE_RECORD_SEGMENT_HEADER*)VolData.pFileRecord, (ULONG)VolData.BytesPerFRS));
//0.0E00 Check to see if the file has been deleted out from under us.
if (VolData.FileRecordNumber != fileRecordNumber) {
//If GetInUseFrs doesn't find the file record number, then it has been deleted.
//Remove this file from our list
RtlDeleteElementGenericTable(pTable, pFileListEntry);
done = FALSE;
}
} while (!done);
return TRUE;
}
BOOL
UpdateFileTables(
IN OUT PRTL_GENERIC_TABLE pFragmentedTable,
IN OUT PRTL_GENERIC_TABLE pContiguousTable
)
{
FILE_EXTENT_HEADER* pFileExtentHeader;
PFREE_SPACE_ENTRY pFreeSpaceEntry = VolData.pFreeSpaceEntry;
PFILE_LIST_ENTRY pFileListEntry = VolData.pFileListEntry;
FILE_LIST_ENTRY NewEntry;
PVOID p = NULL;
BOOLEAN bNewElement = TRUE;
//Update the information on this file.
pFileListEntry->StartingLcn = pFreeSpaceEntry->StartingLcn;
memcpy(&NewEntry, pFileListEntry, sizeof(FILE_LIST_ENTRY));
NewEntry.Flags &= ~FLE_FRAGMENTED;
// 1. Update the FreeSpaceEntry's count
pFreeSpaceEntry->StartingLcn += VolData.NumberOfClusters;
pFreeSpaceEntry->ClusterCount -= VolData.NumberOfClusters;
// 2. Add this file to the contiguous-files table
p = RtlInsertElementGenericTable(
pContiguousTable,
(PVOID) &NewEntry,
sizeof(FILE_LIST_ENTRY),
&bNewElement);
if (!p) {
// An allocation failed
return FALSE;
};
// 3. Remove this file from the fragmented-files table
bNewElement = RtlDeleteElementGenericTable(pFragmentedTable, pFileListEntry);
if (!bNewElement) {
assert(FALSE);
}
VolData.pFileListEntry = NULL;
return TRUE;
}
BOOL
UpdateInFileList(
)
{
LONGLONG EarliestStartingLcn = 0xFFFFFFFFFFFFFFFF;
FILE_EXTENT_HEADER* pFileExtentHeader;
FILE_LIST_ENTRY* pFileListEntry;
//Get a pointer to the entry.
pFileListEntry = VolData.pFileListEntry;
//Sanity check the file record number. They should be the same.
EF(pFileListEntry->FileRecordNumber == (UINT)VolData.FileRecordNumber);
//Get a pointer to the file extent header.
pFileExtentHeader = (FILE_EXTENT_HEADER*)VolData.pExtentList;
//Get the earliest starting lcn for the file which we'll use in our algorithms to determine which file to move next.
EF(GetLowestStartingLcn(&EarliestStartingLcn, pFileExtentHeader));
//Update the information on this file.
pFileListEntry->StartingLcn = EarliestStartingLcn;
pFileListEntry->ExcessExtentCount = (UINT)VolData.NumberOfFragments - pFileExtentHeader->NumberOfStreams; //Only count *excess* extents since otherwise files with multiple streams would be "fragmented".
//Set or clear the fragmented flag depending on whether the file is fragmented or not.
if(VolData.bFragmented){
//Set the fragmented flag.
pFileListEntry->Flags |= FLE_FRAGMENTED;
}
else{
//Clear the fragmented flag.
pFileListEntry->Flags &= ~FLE_FRAGMENTED;
}
//Set or clear the directory flag depending on whether the file is a directory or not.
if(VolData.bDirectory){
//Set the directory flag.
pFileListEntry->Flags |= FLE_DIRECTORY;
}
else{
//Clear the directory flag.
pFileListEntry->Flags &= ~FLE_DIRECTORY;
}
return TRUE;
}
/*****************************************************************************************************************
COPYRIGHT© 2001 Microsoft Corporation and Executive Software International, Inc.
ROUTINE DESCRIPTION:
Opens a file on an NTFS volume.
INPUT + OUTPUT:
None.
GLOBALS:
IN OUT Various VolData fields.
RETURN:
TRUE = Success
FALSE = Failure
*/
BOOL
OpenNtfsFile(
)
{
TCHAR cPath[MAX_PATH] = {0};
DWORD Error;
// TCHAR cString[300];
//0.0E00 Display the File path and name.
// Message(TEXT("NtfsSubs::OpenNtfsFile"), -1, VolData.cFileName);
//0.0E00 Display File Number, number of extents and number of fragments.
// wsprintf(cString,
// TEXT(" FileNumber = 0x%lX Extents = 0x%lX Fragments = 0x%lX"),
// (ULONG)VolData.FileRecordNumber,
// (ULONG)VolData.NumberOfExtents,
// (ULONG)VolData.NumberOfFragments);
// Message(TEXT("NtfsSubs::OpenNtfsFile"), -1, cString);
// wsprintf(cString, TEXT(" %s file at Lcn 0x%lX for Cluster Count of 0x%lX"),
// (VolData.bFragmented == TRUE) ? TEXT("Fragmented") : TEXT("Contiguous"),
// (ULONG)VolData.StartingLcn,
// (ULONG)VolData.NumberOfClusters);
// Message(TEXT("NtfsSubs::OpenNtfsFile"), -1, cString);
if(VolData.bDirectory){
Error = 0;
}
if(VolData.hFile != INVALID_HANDLE_VALUE){
CloseHandle(VolData.hFile);
}
if (VolData.vFileName.IsEmpty()) {
VolData.hFile = INVALID_HANDLE_VALUE;
return FALSE;
}
DWORD dwCreationDisposition;
dwCreationDisposition = FILE_FLAG_BACKUP_SEMANTICS | FILE_OPEN_REPARSE_POINT; //sks createfile parameters
// Get a transparent handle to the file.
VolData.hFile = CreateFile(
VolData.vFileName.GetBuffer(),
FILE_READ_ATTRIBUTES | SYNCHRONIZE, //sks bug #184735
0,
NULL,
OPEN_EXISTING,
dwCreationDisposition,
NULL);
if(VolData.hFile == INVALID_HANDLE_VALUE) {
//
// Handle the special $Extend files: see bug 303250
//
PWSTR pwszFileName = VolData.vFileName.GetBuffer();
if (!pwszFileName) {
return FALSE;
}
if (_tcsstr(pwszFileName, TEXT("\\$Extend")) != (PTCHAR) NULL){
if (_tcsstr(pwszFileName, TEXT("\\$Quota")) != (PTCHAR) NULL){
VolData.vFileName += L":$Q:$INDEX_ALLOCATION";
}
else if (_tcsstr(pwszFileName, TEXT("\\$ObjId")) != (PTCHAR) NULL){
VolData.vFileName += L":$O:$INDEX_ALLOCATION";
}
else if (_tcsstr(pwszFileName, TEXT("\\$Reparse")) != (PTCHAR) NULL){
VolData.vFileName += L":$R:$INDEX_ALLOCATION";
}
VolData.hFile = CreateFile(
VolData.vFileName.GetBuffer(),
FILE_READ_ATTRIBUTES | SYNCHRONIZE, //sks bug #184735
0,
NULL,
OPEN_EXISTING,
dwCreationDisposition,
NULL);
}
}
if(VolData.hFile == INVALID_HANDLE_VALUE) {
Error = GetLastError();
Message(TEXT("OpenNtfsFile - CreateFile"), GetLastError(), ESICompressFilePath(VolData.vFileName.GetBuffer()));
SetLastError(Error);
return FALSE; // Try later.
}
Message(ESICompressFilePath(VolData.vFileName.GetBuffer()), -1, NULL);
return TRUE;
}
/*****************************************************************************************************************
COPYRIGHT© 2001 Microsoft Corporation and Executive Software International, Inc.
ROUTINE DESCRIPTION:
Gets the extent list for the system files -- the MFT, MFT2, and the Uppercase file.
INPUT + OUTPUT:
None.
GLOBALS:
IN VolData.hVolume - The handle to the volume.
IN VolData.BytesPerFRS - The number of bytes in a file record.
IN OUT VolData.pExtentList - Where to put the extents for all the system files.
IN VolData.NumberOfExtents - The number of extents in the extent list.
IN VolData.ExtentListAlloced - The number of bytes allocated for the extent list.
RETURN:
TRUE = Success
FALSE = Failure
*/
BOOL
GetSystemsExtentList(
)
{
LONGLONG StartingLcn;
LONGLONG EndingLcn;
LONGLONG FileRecordNumber;
STREAM_EXTENT_HEADER* pStreamExtentHeader;
EXTENT_LIST* pExtents;
//1.0E00 Get the start of the Mft mirror
//Get the FRS.
FileRecordNumber = 1;
EF(GetInUseFrs(VolData.hVolume, &FileRecordNumber, (FILE_RECORD_SEGMENT_HEADER*)VolData.pFileRecord, (ULONG)VolData.BytesPerFRS));
EF_ASSERT(FileRecordNumber == 1);
VolData.FileRecordNumber = FileRecordNumber;
//Get the extent list.
EF(GetStreamExtentsByNameAndType(TEXT(""), $DATA, (FILE_RECORD_SEGMENT_HEADER*)VolData.pFileRecord));
//Get a pointer to the stream header.
pStreamExtentHeader = (STREAM_EXTENT_HEADER*)VolData.pExtentList;
//Make sure this is the first stream.
EF_ASSERT(pStreamExtentHeader->StreamNumber == 0);
//Make sure there are extents in this stream.
EF_ASSERT(pStreamExtentHeader->ExtentCount);
//Get a pointer to the extents in this stream.
pExtents = (EXTENT_LIST*)((UCHAR*)pStreamExtentHeader + sizeof(STREAM_EXTENT_HEADER));
//The first extent points to the beginning of the mft mirror.
StartingLcn = pExtents->StartingLcn;
//1.0E00 Get the end of the Upcase file
//Get the FRS.
FileRecordNumber = 0x0a;
EF(GetInUseFrs(VolData.hVolume, &FileRecordNumber, (FILE_RECORD_SEGMENT_HEADER*)VolData.pFileRecord, (ULONG)VolData.BytesPerFRS));
EF_ASSERT(FileRecordNumber == 0x0a);
VolData.FileRecordNumber = FileRecordNumber;
//Get the extent list.
EF(GetStreamExtentsByNameAndType(TEXT(""), $DATA, (FILE_RECORD_SEGMENT_HEADER*)VolData.pFileRecord));
//Get a pointer to the stream header.
pStreamExtentHeader = (STREAM_EXTENT_HEADER*)VolData.pExtentList;
//Make sure this is the first stream.
EF_ASSERT(pStreamExtentHeader->StreamNumber == 0);
//Make sure there are extents in this stream.
EF_ASSERT(pStreamExtentHeader->ExtentCount);
//Get a pointer to the extents in this stream.
pExtents = (EXTENT_LIST*)((UCHAR*)pStreamExtentHeader + sizeof(STREAM_EXTENT_HEADER));
//The first extent points to the whole upcase file which ends off the MFT mirror section.
EndingLcn = pExtents->StartingLcn + pExtents->ClusterCount;
//1.0E00 Get the Mft extent list
//Get the FRS
FileRecordNumber = 0;
EF(GetInUseFrs(VolData.hVolume, &FileRecordNumber, (FILE_RECORD_SEGMENT_HEADER*)VolData.pFileRecord, (ULONG)VolData.BytesPerFRS));
EF_ASSERT(FileRecordNumber == 0);
VolData.FileRecordNumber = FileRecordNumber;
//Get the extent list.
EF(GetStreamExtentsByNameAndType(TEXT(""), $DATA, (FILE_RECORD_SEGMENT_HEADER*)VolData.pFileRecord));
//Get a pointer to the stream header.
pStreamExtentHeader = (STREAM_EXTENT_HEADER*)VolData.pExtentList;
//Make sure this is the first stream.
EF_ASSERT(pStreamExtentHeader->StreamNumber == 0);
//Make sure there are extents in this stream.
EF_ASSERT(pStreamExtentHeader->ExtentCount);
//Get a pointer to the extents in this stream.
pExtents = (EXTENT_LIST*)((UCHAR*)pStreamExtentHeader + sizeof(STREAM_EXTENT_HEADER));
//1.0E00 Extend the MFT extent list to add an extent for MFT2
pStreamExtentHeader->ExtentCount ++;
//0.0E00 Extend the extent list buffer as necessary
if((sizeof(STREAM_EXTENT_HEADER) + pStreamExtentHeader->ExtentCount*sizeof(EXTENT_LIST)) > (size_t) VolData.ExtentListAlloced){
//If so, realloc it larger, 64K at a time.
EF(AllocateMemory((UINT)VolData.ExtentListAlloced + 0x10000, &VolData.hExtentList, (void**)&VolData.pExtentList));
VolData.ExtentListAlloced += 0x10000;
//Reset pointers that might have changed.
pStreamExtentHeader = (STREAM_EXTENT_HEADER*)VolData.pExtentList;
pExtents = (EXTENT_LIST*)((UCHAR*)VolData.pExtentList + sizeof(STREAM_EXTENT_HEADER));
}
//1.0E00 Add an extent for Mirror thru Upcase
pExtents[pStreamExtentHeader->ExtentCount - 1].StartingLcn = StartingLcn;
pExtents[pStreamExtentHeader->ExtentCount - 1].ClusterCount = EndingLcn - StartingLcn;
//We do not increase ExcessExtents because the MFT2 region is not considered an excess fragment.
//We do need to change the allocated length for the system extents, however, to encompass the second extent.
pStreamExtentHeader->AllocatedLength += pExtents[pStreamExtentHeader->ExtentCount - 1].ClusterCount * VolData.BytesPerCluster;
//Increase the file size for the system extents list since the purpose of the file size in this case is to tell the user how many bytes
//the system files occupy, and there is no similar statistic that can be obtained from the file system to conform to.
pStreamExtentHeader->FileSize += pExtents[pStreamExtentHeader->ExtentCount - 1].ClusterCount * VolData.BytesPerCluster;
//The caller code will want to know how many extents there are here.
VolData.MftNumberOfExtents = pStreamExtentHeader->ExtentCount;
return TRUE;
}
/*****************************************************************************************************************
COPYRIGHT© 2001 Microsoft Corporation and Executive Software International, Inc.
ROUTINE DESCRIPTION:
Given an extent list it adds it to a file list.
INPUT + OUTPUT:
OUT pList - A pointer to the file list.
IN pListIndex - An index of how far the list has been written into.
IN ListEntries - The number of entries in the extent list.
IN FileRecordNumber - The File Record Number for this file.
IN pExtentList - The extent list.
GLOBALS:
IN VolData.bDirectory - Whether or not this file a directory.
IN VolData.NumberOfFragments - The number of fragments in this file.
IN VolData.NumberOfExtents - The number of extents in this file.
IN VolData.FileSize - The size of the file.
RETURN:
TRUE = Success
FALSE = Failure
*/
BOOL
AddFileToListNtfs(
IN PRTL_GENERIC_TABLE Table,
IN LONGLONG FileRecordNumber
)
{
FILE_LIST_ENTRY FileListEntry;
FILE_EXTENT_HEADER* pFileExtentHeader = NULL;
BOOLEAN bNewElement = FALSE;
PVOID p = NULL;
//Get a pointer to the file extent header.
pFileExtentHeader = (FILE_EXTENT_HEADER*)VolData.pExtentList;
//Fill in the file info
FileListEntry.StartingLcn = VolData.StartingLcn;
FileListEntry.FileRecordNumber = FileRecordNumber;
FileListEntry.ExcessExtentCount = (UINT)VolData.NumberOfFragments - pFileExtentHeader->NumberOfStreams; //Only count *excess* extents since otherwise files with multiple streams would be "fragmented".
FileListEntry.ClusterCount = VolData.NumberOfClusters;
FileListEntry.Flags = 0;
//Set or clear the fragmented flag depending on whether the file is fragmented or not.
if(VolData.bFragmented){
//Set the fragmented flag.
FileListEntry.Flags |= FLE_FRAGMENTED;
}
else{
//Clear the fragmented flag.
FileListEntry.Flags &= ~FLE_FRAGMENTED;
}
//Set or clear the directory flag depending on whether the file is a directory or not.
if(VolData.bDirectory){
//Set the directory flag.
FileListEntry.Flags |= FLE_DIRECTORY;
}
else{
//Clear the directory flag.
FileListEntry.Flags &= ~FLE_DIRECTORY;
}
if (VolData.bBootOptimiseFile) {
FileListEntry.Flags |= FLE_BOOTOPTIMISE;
}
else {
FileListEntry.Flags &= ~FLE_BOOTOPTIMISE;
}
p = RtlInsertElementGenericTable(
Table,
(PVOID) &FileListEntry,
sizeof(FILE_LIST_ENTRY),
&bNewElement);
if (!p) {
//
// An allocation failed
//
return FALSE;
};
// This better be a recognised as a new element. If it isn't, something's
// wrong with our compare routine
// ASSERT(bNewElement);
return TRUE;
}
/*****************************************************************************************************************
COPYRIGHT© 2001 Microsoft Corporation and Executive Software International, Inc.
ROUTINE DESCRIPTION:
Locate an attribute in a File Record Segment
INPUT + OUTPUT:
IN TypeCode - TypeCode of the attribute to find
IN pFrs - Pointer to the File Record Segment
OUT ppArh - Returns the pointer to the attribute
IN uBytesPerFrs - The number of bytes in an FRS.
GLOBALS:
None.
RETURN:
TRUE = Success
FALSE = Failure
*/
BOOL
FindAttributeByType(
IN ATTRIBUTE_TYPE_CODE TypeCode,
IN PFILE_RECORD_SEGMENT_HEADER pFrs,
OUT PATTRIBUTE_RECORD_HEADER* ppArh,
IN ULONG uBytesPerFRS
)
{
PATTRIBUTE_RECORD_HEADER pArh = (ATTRIBUTE_RECORD_HEADER*)((PUCHAR)pFrs + pFrs->FirstAttributeOffset);
require(pFrs);
require(ppArh);
require(uBytesPerFRS);
//0.0E00 Seach through the attributes in this file record for the first one of the requested type
//0.1E00 Make sure our attribute pointer doesn't go beyond the end of the FRS and check if this is the attribute we're
//looking for or the end of the record.
while(pArh < (ATTRIBUTE_RECORD_HEADER*)((PUCHAR)pFrs + uBytesPerFRS) &&
pArh->TypeCode != TypeCode &&
pArh->TypeCode != $END){
//0.1E00 We did not find our attribute, so go to the next.
pArh = (ATTRIBUTE_RECORD_HEADER*)((PUCHAR)pArh + pArh->RecordLength);
}
if (pArh >= (ATTRIBUTE_RECORD_HEADER*)((PUCHAR)pFrs + uBytesPerFRS)) {
VolData.bMFTCorrupt = TRUE;
return FALSE;
}
//0.0E00 Error if attribute not found
if(pArh->TypeCode != TypeCode){
return FALSE;
}
//0.0E00 Return pointer to the attribute
*ppArh = pArh;
return TRUE;
}