mirror of
https://github.com/reactos/reactos.git
synced 2024-08-26 13:18:51 +00:00
2004 lines
66 KiB
C
2004 lines
66 KiB
C
/*
|
|
* ReactOS kernel
|
|
* Copyright (C) 2002,2003 ReactOS Team
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
* COPYRIGHT: See COPYING in the top level directory
|
|
* PROJECT: ReactOS kernel
|
|
* FILE: drivers/filesystem/ntfs/attrib.c
|
|
* PURPOSE: NTFS filesystem driver
|
|
* PROGRAMMERS: Eric Kohl
|
|
* Valentin Verkhovsky
|
|
* Hervé Poussineau (hpoussin@reactos.org)
|
|
* Pierre Schweitzer (pierre@reactos.org)
|
|
*/
|
|
|
|
/* INCLUDES *****************************************************************/
|
|
|
|
#include "ntfs.h"
|
|
#include <ntintsafe.h>
|
|
|
|
#define NDEBUG
|
|
#include <debug.h>
|
|
|
|
/* FUNCTIONS ****************************************************************/
|
|
|
|
/**
|
|
* @name AddBitmap
|
|
* @implemented
|
|
*
|
|
* Adds a $BITMAP attribute to a given FileRecord.
|
|
*
|
|
* @param Vcb
|
|
* Pointer to an NTFS_VCB for the destination volume.
|
|
*
|
|
* @param FileRecord
|
|
* Pointer to a complete file record to add the attribute to.
|
|
*
|
|
* @param AttributeAddress
|
|
* Pointer to the region of memory that will receive the $INDEX_ALLOCATION attribute.
|
|
* This address must reside within FileRecord. Must be aligned to an 8-byte boundary (relative to FileRecord).
|
|
*
|
|
* @param Name
|
|
* Pointer to a string of 16-bit Unicode characters naming the attribute. Most often L"$I30".
|
|
*
|
|
* @param NameLength
|
|
* The number of wide-characters in the name. L"$I30" Would use 4 here.
|
|
*
|
|
* @return
|
|
* STATUS_SUCCESS on success. STATUS_NOT_IMPLEMENTED if target address isn't at the end
|
|
* of the given file record, or if the file record isn't large enough for the attribute.
|
|
*
|
|
* @remarks
|
|
* Only adding the attribute to the end of the file record is supported; AttributeAddress must
|
|
* be of type AttributeEnd.
|
|
* This could be improved by adding an $ATTRIBUTE_LIST to the file record if there's not enough space.
|
|
*
|
|
*/
|
|
NTSTATUS
|
|
AddBitmap(PNTFS_VCB Vcb,
|
|
PFILE_RECORD_HEADER FileRecord,
|
|
PNTFS_ATTR_RECORD AttributeAddress,
|
|
PCWSTR Name,
|
|
USHORT NameLength)
|
|
{
|
|
ULONG AttributeLength;
|
|
// Calculate the header length
|
|
ULONG ResidentHeaderLength = FIELD_OFFSET(NTFS_ATTR_RECORD, Resident.Reserved) + sizeof(UCHAR);
|
|
ULONG FileRecordEnd = AttributeAddress->Length;
|
|
ULONG NameOffset;
|
|
ULONG ValueOffset;
|
|
// We'll start out with 8 bytes of bitmap data
|
|
ULONG ValueLength = 8;
|
|
ULONG BytesAvailable;
|
|
|
|
if (AttributeAddress->Type != AttributeEnd)
|
|
{
|
|
DPRINT1("FIXME: Can only add $BITMAP attribute to the end of a file record.\n");
|
|
return STATUS_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
NameOffset = ResidentHeaderLength;
|
|
|
|
// Calculate ValueOffset, which will be aligned to a 4-byte boundary
|
|
ValueOffset = ALIGN_UP_BY(NameOffset + (sizeof(WCHAR) * NameLength), VALUE_OFFSET_ALIGNMENT);
|
|
|
|
// Calculate length of attribute
|
|
AttributeLength = ValueOffset + ValueLength;
|
|
AttributeLength = ALIGN_UP_BY(AttributeLength, ATTR_RECORD_ALIGNMENT);
|
|
|
|
// Make sure the file record is large enough for the new attribute
|
|
BytesAvailable = Vcb->NtfsInfo.BytesPerFileRecord - FileRecord->BytesInUse;
|
|
if (BytesAvailable < AttributeLength)
|
|
{
|
|
DPRINT1("FIXME: Not enough room in file record for index allocation attribute!\n");
|
|
return STATUS_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
// Set Attribute fields
|
|
RtlZeroMemory(AttributeAddress, AttributeLength);
|
|
|
|
AttributeAddress->Type = AttributeBitmap;
|
|
AttributeAddress->Length = AttributeLength;
|
|
AttributeAddress->NameLength = NameLength;
|
|
AttributeAddress->NameOffset = NameOffset;
|
|
AttributeAddress->Instance = FileRecord->NextAttributeNumber++;
|
|
|
|
AttributeAddress->Resident.ValueLength = ValueLength;
|
|
AttributeAddress->Resident.ValueOffset = ValueOffset;
|
|
|
|
// Set the name
|
|
RtlCopyMemory((PCHAR)((ULONG_PTR)AttributeAddress + NameOffset), Name, NameLength * sizeof(WCHAR));
|
|
|
|
// move the attribute-end and file-record-end markers to the end of the file record
|
|
AttributeAddress = (PNTFS_ATTR_RECORD)((ULONG_PTR)AttributeAddress + AttributeAddress->Length);
|
|
SetFileRecordEnd(FileRecord, AttributeAddress, FileRecordEnd);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* @name AddData
|
|
* @implemented
|
|
*
|
|
* Adds a $DATA attribute to a given FileRecord.
|
|
*
|
|
* @param FileRecord
|
|
* Pointer to a complete file record to add the attribute to. Caller is responsible for
|
|
* ensuring FileRecord is large enough to contain $DATA.
|
|
*
|
|
* @param AttributeAddress
|
|
* Pointer to the region of memory that will receive the $DATA attribute.
|
|
* This address must reside within FileRecord. Must be aligned to an 8-byte boundary (relative to FileRecord).
|
|
*
|
|
* @return
|
|
* STATUS_SUCCESS on success. STATUS_NOT_IMPLEMENTED if target address isn't at the end
|
|
* of the given file record.
|
|
*
|
|
* @remarks
|
|
* Only adding the attribute to the end of the file record is supported; AttributeAddress must
|
|
* be of type AttributeEnd.
|
|
* As it's implemented, this function is only intended to assist in creating new file records. It
|
|
* could be made more general-purpose by considering file records with an $ATTRIBUTE_LIST.
|
|
* It's the caller's responsibility to ensure the given file record has enough memory allocated
|
|
* for the attribute.
|
|
*/
|
|
NTSTATUS
|
|
AddData(PFILE_RECORD_HEADER FileRecord,
|
|
PNTFS_ATTR_RECORD AttributeAddress)
|
|
{
|
|
ULONG ResidentHeaderLength = FIELD_OFFSET(NTFS_ATTR_RECORD, Resident.Reserved) + sizeof(UCHAR);
|
|
ULONG FileRecordEnd = AttributeAddress->Length;
|
|
|
|
if (AttributeAddress->Type != AttributeEnd)
|
|
{
|
|
DPRINT1("FIXME: Can only add $DATA attribute to the end of a file record.\n");
|
|
return STATUS_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
AttributeAddress->Type = AttributeData;
|
|
AttributeAddress->Length = ResidentHeaderLength;
|
|
AttributeAddress->Length = ALIGN_UP_BY(AttributeAddress->Length, ATTR_RECORD_ALIGNMENT);
|
|
AttributeAddress->Resident.ValueLength = 0;
|
|
AttributeAddress->Resident.ValueOffset = ResidentHeaderLength;
|
|
|
|
// for unnamed $DATA attributes, NameOffset equals header length
|
|
AttributeAddress->NameOffset = ResidentHeaderLength;
|
|
AttributeAddress->Instance = FileRecord->NextAttributeNumber++;
|
|
|
|
// move the attribute-end and file-record-end markers to the end of the file record
|
|
AttributeAddress = (PNTFS_ATTR_RECORD)((ULONG_PTR)AttributeAddress + AttributeAddress->Length);
|
|
SetFileRecordEnd(FileRecord, AttributeAddress, FileRecordEnd);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* @name AddFileName
|
|
* @implemented
|
|
*
|
|
* Adds a $FILE_NAME attribute to a given FileRecord.
|
|
*
|
|
* @param FileRecord
|
|
* Pointer to a complete file record to add the attribute to. Caller is responsible for
|
|
* ensuring FileRecord is large enough to contain $FILE_NAME.
|
|
*
|
|
* @param AttributeAddress
|
|
* Pointer to the region of memory that will receive the $FILE_NAME attribute.
|
|
* This address must reside within FileRecord. Must be aligned to an 8-byte boundary (relative to FileRecord).
|
|
*
|
|
* @param DeviceExt
|
|
* Points to the target disk's DEVICE_EXTENSION.
|
|
*
|
|
* @param FileObject
|
|
* Pointer to the FILE_OBJECT which represents the new name.
|
|
* This parameter is used to determine the filename and parent directory.
|
|
*
|
|
* @param CaseSensitive
|
|
* Boolean indicating if the function should operate in case-sensitive mode. This will be TRUE
|
|
* if an application opened the file with the FILE_FLAG_POSIX_SEMANTICS flag.
|
|
*
|
|
* @param ParentMftIndex
|
|
* Pointer to a ULONGLONG which will receive the index of the parent directory.
|
|
*
|
|
* @return
|
|
* STATUS_SUCCESS on success. STATUS_NOT_IMPLEMENTED if target address isn't at the end
|
|
* of the given file record.
|
|
*
|
|
* @remarks
|
|
* Only adding the attribute to the end of the file record is supported; AttributeAddress must
|
|
* be of type AttributeEnd.
|
|
* As it's implemented, this function is only intended to assist in creating new file records. It
|
|
* could be made more general-purpose by considering file records with an $ATTRIBUTE_LIST.
|
|
* It's the caller's responsibility to ensure the given file record has enough memory allocated
|
|
* for the attribute.
|
|
*/
|
|
NTSTATUS
|
|
AddFileName(PFILE_RECORD_HEADER FileRecord,
|
|
PNTFS_ATTR_RECORD AttributeAddress,
|
|
PDEVICE_EXTENSION DeviceExt,
|
|
PFILE_OBJECT FileObject,
|
|
BOOLEAN CaseSensitive,
|
|
PULONGLONG ParentMftIndex)
|
|
{
|
|
ULONG ResidentHeaderLength = FIELD_OFFSET(NTFS_ATTR_RECORD, Resident.Reserved) + sizeof(UCHAR);
|
|
PFILENAME_ATTRIBUTE FileNameAttribute;
|
|
LARGE_INTEGER SystemTime;
|
|
ULONG FileRecordEnd = AttributeAddress->Length;
|
|
ULONGLONG CurrentMFTIndex = NTFS_FILE_ROOT;
|
|
UNICODE_STRING Current, Remaining, FilenameNoPath;
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
ULONG FirstEntry;
|
|
|
|
if (AttributeAddress->Type != AttributeEnd)
|
|
{
|
|
DPRINT1("FIXME: Can only add $FILE_NAME attribute to the end of a file record.\n");
|
|
return STATUS_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
AttributeAddress->Type = AttributeFileName;
|
|
AttributeAddress->Instance = FileRecord->NextAttributeNumber++;
|
|
|
|
FileNameAttribute = (PFILENAME_ATTRIBUTE)((LONG_PTR)AttributeAddress + ResidentHeaderLength);
|
|
|
|
// set timestamps
|
|
KeQuerySystemTime(&SystemTime);
|
|
FileNameAttribute->CreationTime = SystemTime.QuadPart;
|
|
FileNameAttribute->ChangeTime = SystemTime.QuadPart;
|
|
FileNameAttribute->LastWriteTime = SystemTime.QuadPart;
|
|
FileNameAttribute->LastAccessTime = SystemTime.QuadPart;
|
|
|
|
// Is this a directory?
|
|
if(FileRecord->Flags & FRH_DIRECTORY)
|
|
FileNameAttribute->FileAttributes = NTFS_FILE_TYPE_DIRECTORY;
|
|
else
|
|
FileNameAttribute->FileAttributes = NTFS_FILE_TYPE_ARCHIVE;
|
|
|
|
// we need to extract the filename from the path
|
|
DPRINT1("Pathname: %wZ\n", &FileObject->FileName);
|
|
|
|
FsRtlDissectName(FileObject->FileName, &Current, &Remaining);
|
|
|
|
FilenameNoPath.Buffer = Current.Buffer;
|
|
FilenameNoPath.MaximumLength = FilenameNoPath.Length = Current.Length;
|
|
|
|
while (Current.Length != 0)
|
|
{
|
|
DPRINT1("Current: %wZ\n", &Current);
|
|
|
|
if (Remaining.Length != 0)
|
|
{
|
|
FilenameNoPath.Buffer = Remaining.Buffer;
|
|
FilenameNoPath.Length = FilenameNoPath.MaximumLength = Remaining.Length;
|
|
}
|
|
|
|
FirstEntry = 0;
|
|
Status = NtfsFindMftRecord(DeviceExt,
|
|
CurrentMFTIndex,
|
|
&Current,
|
|
&FirstEntry,
|
|
FALSE,
|
|
CaseSensitive,
|
|
&CurrentMFTIndex);
|
|
if (!NT_SUCCESS(Status))
|
|
break;
|
|
|
|
if (Remaining.Length == 0 )
|
|
{
|
|
if (Current.Length != 0)
|
|
{
|
|
FilenameNoPath.Buffer = Current.Buffer;
|
|
FilenameNoPath.Length = FilenameNoPath.MaximumLength = Current.Length;
|
|
}
|
|
break;
|
|
}
|
|
|
|
FsRtlDissectName(Remaining, &Current, &Remaining);
|
|
}
|
|
|
|
DPRINT1("MFT Index of parent: %I64u\n", CurrentMFTIndex);
|
|
|
|
// set reference to parent directory
|
|
FileNameAttribute->DirectoryFileReferenceNumber = CurrentMFTIndex;
|
|
*ParentMftIndex = CurrentMFTIndex;
|
|
|
|
DPRINT1("SequenceNumber: 0x%02x\n", FileRecord->SequenceNumber);
|
|
|
|
// The highest 2 bytes should be the sequence number, unless the parent happens to be root
|
|
if (CurrentMFTIndex == NTFS_FILE_ROOT)
|
|
FileNameAttribute->DirectoryFileReferenceNumber |= (ULONGLONG)NTFS_FILE_ROOT << 48;
|
|
else
|
|
FileNameAttribute->DirectoryFileReferenceNumber |= (ULONGLONG)FileRecord->SequenceNumber << 48;
|
|
|
|
DPRINT1("FileNameAttribute->DirectoryFileReferenceNumber: 0x%016I64x\n", FileNameAttribute->DirectoryFileReferenceNumber);
|
|
|
|
FileNameAttribute->NameLength = FilenameNoPath.Length / sizeof(WCHAR);
|
|
RtlCopyMemory(FileNameAttribute->Name, FilenameNoPath.Buffer, FilenameNoPath.Length);
|
|
|
|
// For now, we're emulating the way Windows behaves when 8.3 name generation is disabled
|
|
// TODO: add DOS Filename as needed
|
|
if (!CaseSensitive && RtlIsNameLegalDOS8Dot3(&FilenameNoPath, NULL, NULL))
|
|
FileNameAttribute->NameType = NTFS_FILE_NAME_WIN32_AND_DOS;
|
|
else
|
|
FileNameAttribute->NameType = NTFS_FILE_NAME_POSIX;
|
|
|
|
FileRecord->LinkCount++;
|
|
|
|
AttributeAddress->Length = ResidentHeaderLength +
|
|
FIELD_OFFSET(FILENAME_ATTRIBUTE, Name) + FilenameNoPath.Length;
|
|
AttributeAddress->Length = ALIGN_UP_BY(AttributeAddress->Length, ATTR_RECORD_ALIGNMENT);
|
|
|
|
AttributeAddress->Resident.ValueLength = FIELD_OFFSET(FILENAME_ATTRIBUTE, Name) + FilenameNoPath.Length;
|
|
AttributeAddress->Resident.ValueOffset = ResidentHeaderLength;
|
|
AttributeAddress->Resident.Flags = RA_INDEXED;
|
|
|
|
// move the attribute-end and file-record-end markers to the end of the file record
|
|
AttributeAddress = (PNTFS_ATTR_RECORD)((ULONG_PTR)AttributeAddress + AttributeAddress->Length);
|
|
SetFileRecordEnd(FileRecord, AttributeAddress, FileRecordEnd);
|
|
|
|
return Status;
|
|
}
|
|
|
|
/**
|
|
* @name AddIndexAllocation
|
|
* @implemented
|
|
*
|
|
* Adds an $INDEX_ALLOCATION attribute to a given FileRecord.
|
|
*
|
|
* @param Vcb
|
|
* Pointer to an NTFS_VCB for the destination volume.
|
|
*
|
|
* @param FileRecord
|
|
* Pointer to a complete file record to add the attribute to.
|
|
*
|
|
* @param AttributeAddress
|
|
* Pointer to the region of memory that will receive the $INDEX_ALLOCATION attribute.
|
|
* This address must reside within FileRecord. Must be aligned to an 8-byte boundary (relative to FileRecord).
|
|
*
|
|
* @param Name
|
|
* Pointer to a string of 16-bit Unicode characters naming the attribute. Most often, this will be L"$I30".
|
|
*
|
|
* @param NameLength
|
|
* The number of wide-characters in the name. L"$I30" Would use 4 here.
|
|
*
|
|
* @return
|
|
* STATUS_SUCCESS on success. STATUS_NOT_IMPLEMENTED if target address isn't at the end
|
|
* of the given file record, or if the file record isn't large enough for the attribute.
|
|
*
|
|
* @remarks
|
|
* Only adding the attribute to the end of the file record is supported; AttributeAddress must
|
|
* be of type AttributeEnd.
|
|
* This could be improved by adding an $ATTRIBUTE_LIST to the file record if there's not enough space.
|
|
*
|
|
*/
|
|
NTSTATUS
|
|
AddIndexAllocation(PNTFS_VCB Vcb,
|
|
PFILE_RECORD_HEADER FileRecord,
|
|
PNTFS_ATTR_RECORD AttributeAddress,
|
|
PCWSTR Name,
|
|
USHORT NameLength)
|
|
{
|
|
ULONG RecordLength;
|
|
ULONG FileRecordEnd;
|
|
ULONG NameOffset;
|
|
ULONG DataRunOffset;
|
|
ULONG BytesAvailable;
|
|
|
|
if (AttributeAddress->Type != AttributeEnd)
|
|
{
|
|
DPRINT1("FIXME: Can only add $INDEX_ALLOCATION attribute to the end of a file record.\n");
|
|
return STATUS_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
// Calculate the name offset
|
|
NameOffset = FIELD_OFFSET(NTFS_ATTR_RECORD, NonResident.CompressedSize);
|
|
|
|
// Calculate the offset to the first data run
|
|
DataRunOffset = (sizeof(WCHAR) * NameLength) + NameOffset;
|
|
// The data run offset must be aligned to a 4-byte boundary
|
|
DataRunOffset = ALIGN_UP_BY(DataRunOffset, DATA_RUN_ALIGNMENT);
|
|
|
|
// Calculate the length of the new attribute; the empty data run will consist of a single byte
|
|
RecordLength = DataRunOffset + 1;
|
|
|
|
// The size of the attribute itself must be aligned to an 8 - byte boundary
|
|
RecordLength = ALIGN_UP_BY(RecordLength, ATTR_RECORD_ALIGNMENT);
|
|
|
|
// Back up the last 4-bytes of the file record (even though this value doesn't matter)
|
|
FileRecordEnd = AttributeAddress->Length;
|
|
|
|
// Make sure the file record can contain the new attribute
|
|
BytesAvailable = Vcb->NtfsInfo.BytesPerFileRecord - FileRecord->BytesInUse;
|
|
if (BytesAvailable < RecordLength)
|
|
{
|
|
DPRINT1("FIXME: Not enough room in file record for index allocation attribute!\n");
|
|
return STATUS_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
// Set fields of attribute header
|
|
RtlZeroMemory(AttributeAddress, RecordLength);
|
|
|
|
AttributeAddress->Type = AttributeIndexAllocation;
|
|
AttributeAddress->Length = RecordLength;
|
|
AttributeAddress->IsNonResident = TRUE;
|
|
AttributeAddress->NameLength = NameLength;
|
|
AttributeAddress->NameOffset = NameOffset;
|
|
AttributeAddress->Instance = FileRecord->NextAttributeNumber++;
|
|
|
|
AttributeAddress->NonResident.MappingPairsOffset = DataRunOffset;
|
|
AttributeAddress->NonResident.HighestVCN = (LONGLONG)-1;
|
|
|
|
// Set the name
|
|
RtlCopyMemory((PCHAR)((ULONG_PTR)AttributeAddress + NameOffset), Name, NameLength * sizeof(WCHAR));
|
|
|
|
// move the attribute-end and file-record-end markers to the end of the file record
|
|
AttributeAddress = (PNTFS_ATTR_RECORD)((ULONG_PTR)AttributeAddress + AttributeAddress->Length);
|
|
SetFileRecordEnd(FileRecord, AttributeAddress, FileRecordEnd);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* @name AddIndexRoot
|
|
* @implemented
|
|
*
|
|
* Adds an $INDEX_ROOT attribute to a given FileRecord.
|
|
*
|
|
* @param Vcb
|
|
* Pointer to an NTFS_VCB for the destination volume.
|
|
*
|
|
* @param FileRecord
|
|
* Pointer to a complete file record to add the attribute to. Caller is responsible for
|
|
* ensuring FileRecord is large enough to contain $INDEX_ROOT.
|
|
*
|
|
* @param AttributeAddress
|
|
* Pointer to the region of memory that will receive the $INDEX_ROOT attribute.
|
|
* This address must reside within FileRecord. Must be aligned to an 8-byte boundary (relative to FileRecord).
|
|
*
|
|
* @param NewIndexRoot
|
|
* Pointer to an INDEX_ROOT_ATTRIBUTE containing the index root that will be copied to the new attribute.
|
|
*
|
|
* @param RootLength
|
|
* The length of NewIndexRoot, in bytes.
|
|
*
|
|
* @param Name
|
|
* Pointer to a string of 16-bit Unicode characters naming the attribute. Most often, this will be L"$I30".
|
|
*
|
|
* @param NameLength
|
|
* The number of wide-characters in the name. L"$I30" Would use 4 here.
|
|
*
|
|
* @return
|
|
* STATUS_SUCCESS on success. STATUS_NOT_IMPLEMENTED if target address isn't at the end
|
|
* of the given file record.
|
|
*
|
|
* @remarks
|
|
* This function is intended to assist in creating new folders.
|
|
* Only adding the attribute to the end of the file record is supported; AttributeAddress must
|
|
* be of type AttributeEnd.
|
|
* It's the caller's responsibility to ensure the given file record has enough memory allocated
|
|
* for the attribute, and this memory must have been zeroed.
|
|
*/
|
|
NTSTATUS
|
|
AddIndexRoot(PNTFS_VCB Vcb,
|
|
PFILE_RECORD_HEADER FileRecord,
|
|
PNTFS_ATTR_RECORD AttributeAddress,
|
|
PINDEX_ROOT_ATTRIBUTE NewIndexRoot,
|
|
ULONG RootLength,
|
|
PCWSTR Name,
|
|
USHORT NameLength)
|
|
{
|
|
ULONG AttributeLength;
|
|
// Calculate the header length
|
|
ULONG ResidentHeaderLength = FIELD_OFFSET(NTFS_ATTR_RECORD, Resident.Reserved) + sizeof(UCHAR);
|
|
// Back up the file record's final ULONG (even though it doesn't matter)
|
|
ULONG FileRecordEnd = AttributeAddress->Length;
|
|
ULONG NameOffset;
|
|
ULONG ValueOffset;
|
|
ULONG BytesAvailable;
|
|
|
|
if (AttributeAddress->Type != AttributeEnd)
|
|
{
|
|
DPRINT1("FIXME: Can only add $DATA attribute to the end of a file record.\n");
|
|
return STATUS_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
NameOffset = ResidentHeaderLength;
|
|
|
|
// Calculate ValueOffset, which will be aligned to a 4-byte boundary
|
|
ValueOffset = ALIGN_UP_BY(NameOffset + (sizeof(WCHAR) * NameLength), VALUE_OFFSET_ALIGNMENT);
|
|
|
|
// Calculate length of attribute
|
|
AttributeLength = ValueOffset + RootLength;
|
|
AttributeLength = ALIGN_UP_BY(AttributeLength, ATTR_RECORD_ALIGNMENT);
|
|
|
|
// Make sure the file record is large enough for the new attribute
|
|
BytesAvailable = Vcb->NtfsInfo.BytesPerFileRecord - FileRecord->BytesInUse;
|
|
if (BytesAvailable < AttributeLength)
|
|
{
|
|
DPRINT1("FIXME: Not enough room in file record for index allocation attribute!\n");
|
|
return STATUS_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
// Set Attribute fields
|
|
RtlZeroMemory(AttributeAddress, AttributeLength);
|
|
|
|
AttributeAddress->Type = AttributeIndexRoot;
|
|
AttributeAddress->Length = AttributeLength;
|
|
AttributeAddress->NameLength = NameLength;
|
|
AttributeAddress->NameOffset = NameOffset;
|
|
AttributeAddress->Instance = FileRecord->NextAttributeNumber++;
|
|
|
|
AttributeAddress->Resident.ValueLength = RootLength;
|
|
AttributeAddress->Resident.ValueOffset = ValueOffset;
|
|
|
|
// Set the name
|
|
RtlCopyMemory((PCHAR)((ULONG_PTR)AttributeAddress + NameOffset), Name, NameLength * sizeof(WCHAR));
|
|
|
|
// Copy the index root attribute
|
|
RtlCopyMemory((PCHAR)((ULONG_PTR)AttributeAddress + ValueOffset), NewIndexRoot, RootLength);
|
|
|
|
// move the attribute-end and file-record-end markers to the end of the file record
|
|
AttributeAddress = (PNTFS_ATTR_RECORD)((ULONG_PTR)AttributeAddress + AttributeAddress->Length);
|
|
SetFileRecordEnd(FileRecord, AttributeAddress, FileRecordEnd);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* @name AddRun
|
|
* @implemented
|
|
*
|
|
* Adds a run of allocated clusters to a non-resident attribute.
|
|
*
|
|
* @param Vcb
|
|
* Pointer to an NTFS_VCB for the destination volume.
|
|
*
|
|
* @param AttrContext
|
|
* Pointer to an NTFS_ATTR_CONTEXT describing the destination attribute.
|
|
*
|
|
* @param AttrOffset
|
|
* Byte offset of the destination attribute relative to its file record.
|
|
*
|
|
* @param FileRecord
|
|
* Pointer to a complete copy of the file record containing the destination attribute. Must be at least
|
|
* Vcb->NtfsInfo.BytesPerFileRecord bytes long.
|
|
*
|
|
* @param NextAssignedCluster
|
|
* Logical cluster number of the start of the data run being added.
|
|
*
|
|
* @param RunLength
|
|
* How many clusters are in the data run being added. Can't be 0.
|
|
*
|
|
* @return
|
|
* STATUS_SUCCESS on success. STATUS_INVALID_PARAMETER if AttrContext describes a resident attribute.
|
|
* STATUS_INSUFFICIENT_RESOURCES if ConvertDataRunsToLargeMCB() fails or if we fail to allocate a
|
|
* buffer for the new data runs.
|
|
* STATUS_INSUFFICIENT_RESOURCES or STATUS_UNSUCCESSFUL if FsRtlAddLargeMcbEntry() fails.
|
|
* STATUS_BUFFER_TOO_SMALL if ConvertLargeMCBToDataRuns() fails.
|
|
* STATUS_NOT_IMPLEMENTED if we need to migrate the attribute to an attribute list (TODO).
|
|
*
|
|
* @remarks
|
|
* Clusters should have been allocated previously with NtfsAllocateClusters().
|
|
*
|
|
*
|
|
*/
|
|
NTSTATUS
|
|
AddRun(PNTFS_VCB Vcb,
|
|
PNTFS_ATTR_CONTEXT AttrContext,
|
|
ULONG AttrOffset,
|
|
PFILE_RECORD_HEADER FileRecord,
|
|
ULONGLONG NextAssignedCluster,
|
|
ULONG RunLength)
|
|
{
|
|
NTSTATUS Status;
|
|
int DataRunMaxLength;
|
|
PNTFS_ATTR_RECORD DestinationAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttrOffset);
|
|
ULONG NextAttributeOffset = AttrOffset + AttrContext->pRecord->Length;
|
|
ULONGLONG NextVBN = 0;
|
|
|
|
PUCHAR RunBuffer;
|
|
ULONG RunBufferSize;
|
|
|
|
if (!AttrContext->pRecord->IsNonResident)
|
|
return STATUS_INVALID_PARAMETER;
|
|
|
|
if (AttrContext->pRecord->NonResident.AllocatedSize != 0)
|
|
NextVBN = AttrContext->pRecord->NonResident.HighestVCN + 1;
|
|
|
|
// Add newly-assigned clusters to mcb
|
|
_SEH2_TRY
|
|
{
|
|
if (!FsRtlAddLargeMcbEntry(&AttrContext->DataRunsMCB,
|
|
NextVBN,
|
|
NextAssignedCluster,
|
|
RunLength))
|
|
{
|
|
ExRaiseStatus(STATUS_UNSUCCESSFUL);
|
|
}
|
|
}
|
|
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
|
|
{
|
|
DPRINT1("Failed to add LargeMcb Entry!\n");
|
|
_SEH2_YIELD(return _SEH2_GetExceptionCode());
|
|
}
|
|
_SEH2_END;
|
|
|
|
RunBuffer = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
|
|
if (!RunBuffer)
|
|
{
|
|
DPRINT1("ERROR: Couldn't allocate memory for data runs!\n");
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
// Convert the map control block back to encoded data runs
|
|
ConvertLargeMCBToDataRuns(&AttrContext->DataRunsMCB, RunBuffer, Vcb->NtfsInfo.BytesPerCluster, &RunBufferSize);
|
|
|
|
// Get the amount of free space between the start of the of the first data run and the attribute end
|
|
DataRunMaxLength = AttrContext->pRecord->Length - AttrContext->pRecord->NonResident.MappingPairsOffset;
|
|
|
|
// Do we need to extend the attribute (or convert to attribute list)?
|
|
if (DataRunMaxLength < RunBufferSize)
|
|
{
|
|
PNTFS_ATTR_RECORD NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + NextAttributeOffset);
|
|
PNTFS_ATTR_RECORD NewRecord;
|
|
|
|
// Add free space at the end of the file record to DataRunMaxLength
|
|
DataRunMaxLength += Vcb->NtfsInfo.BytesPerFileRecord - FileRecord->BytesInUse;
|
|
|
|
// Can we resize the attribute?
|
|
if (DataRunMaxLength < RunBufferSize)
|
|
{
|
|
DPRINT1("FIXME: Need to create attribute list! Max Data Run Length available: %d, RunBufferSize: %d\n", DataRunMaxLength, RunBufferSize);
|
|
ExFreePoolWithTag(RunBuffer, TAG_NTFS);
|
|
return STATUS_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
// Are there more attributes after the one we're resizing?
|
|
if (NextAttribute->Type != AttributeEnd)
|
|
{
|
|
PNTFS_ATTR_RECORD FinalAttribute;
|
|
|
|
// Calculate where to move the trailing attributes
|
|
ULONG_PTR MoveTo = (ULONG_PTR)DestinationAttribute + AttrContext->pRecord->NonResident.MappingPairsOffset + RunBufferSize;
|
|
MoveTo = ALIGN_UP_BY(MoveTo, ATTR_RECORD_ALIGNMENT);
|
|
|
|
DPRINT1("Moving attribute(s) after this one starting with type 0x%lx\n", NextAttribute->Type);
|
|
|
|
// Move the trailing attributes; FinalAttribute will point to the end marker
|
|
FinalAttribute = MoveAttributes(Vcb, NextAttribute, NextAttributeOffset, MoveTo);
|
|
|
|
// set the file record end
|
|
SetFileRecordEnd(FileRecord, FinalAttribute, FILE_RECORD_END);
|
|
}
|
|
|
|
// calculate position of end markers
|
|
NextAttributeOffset = AttrOffset + AttrContext->pRecord->NonResident.MappingPairsOffset + RunBufferSize;
|
|
NextAttributeOffset = ALIGN_UP_BY(NextAttributeOffset, ATTR_RECORD_ALIGNMENT);
|
|
|
|
// Update the length of the destination attribute
|
|
DestinationAttribute->Length = NextAttributeOffset - AttrOffset;
|
|
|
|
// Create a new copy of the attribute record
|
|
NewRecord = ExAllocatePoolWithTag(NonPagedPool, DestinationAttribute->Length, TAG_NTFS);
|
|
RtlCopyMemory(NewRecord, AttrContext->pRecord, AttrContext->pRecord->Length);
|
|
NewRecord->Length = DestinationAttribute->Length;
|
|
|
|
// Free the old copy of the attribute record, which won't be large enough
|
|
ExFreePoolWithTag(AttrContext->pRecord, TAG_NTFS);
|
|
|
|
// Set the attribute context's record to the new copy
|
|
AttrContext->pRecord = NewRecord;
|
|
|
|
// if NextAttribute is the AttributeEnd marker
|
|
if (NextAttribute->Type == AttributeEnd)
|
|
{
|
|
// End the file record
|
|
NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + NextAttributeOffset);
|
|
SetFileRecordEnd(FileRecord, NextAttribute, FILE_RECORD_END);
|
|
}
|
|
}
|
|
|
|
// Update HighestVCN
|
|
DestinationAttribute->NonResident.HighestVCN =
|
|
AttrContext->pRecord->NonResident.HighestVCN = max(NextVBN - 1 + RunLength,
|
|
AttrContext->pRecord->NonResident.HighestVCN);
|
|
|
|
// Write data runs to destination attribute
|
|
RtlCopyMemory((PVOID)((ULONG_PTR)DestinationAttribute + DestinationAttribute->NonResident.MappingPairsOffset),
|
|
RunBuffer,
|
|
RunBufferSize);
|
|
|
|
// Update the attribute record in the attribute context
|
|
RtlCopyMemory((PVOID)((ULONG_PTR)AttrContext->pRecord + AttrContext->pRecord->NonResident.MappingPairsOffset),
|
|
RunBuffer,
|
|
RunBufferSize);
|
|
|
|
// Update the file record
|
|
Status = UpdateFileRecord(Vcb, AttrContext->FileMFTIndex, FileRecord);
|
|
|
|
ExFreePoolWithTag(RunBuffer, TAG_NTFS);
|
|
|
|
NtfsDumpDataRuns((PUCHAR)((ULONG_PTR)DestinationAttribute + DestinationAttribute->NonResident.MappingPairsOffset), 0);
|
|
|
|
return Status;
|
|
}
|
|
|
|
/**
|
|
* @name AddStandardInformation
|
|
* @implemented
|
|
*
|
|
* Adds a $STANDARD_INFORMATION attribute to a given FileRecord.
|
|
*
|
|
* @param FileRecord
|
|
* Pointer to a complete file record to add the attribute to. Caller is responsible for
|
|
* ensuring FileRecord is large enough to contain $STANDARD_INFORMATION.
|
|
*
|
|
* @param AttributeAddress
|
|
* Pointer to the region of memory that will receive the $STANDARD_INFORMATION attribute.
|
|
* This address must reside within FileRecord. Must be aligned to an 8-byte boundary (relative to FileRecord).
|
|
*
|
|
* @return
|
|
* STATUS_SUCCESS on success. STATUS_NOT_IMPLEMENTED if target address isn't at the end
|
|
* of the given file record.
|
|
*
|
|
* @remarks
|
|
* Only adding the attribute to the end of the file record is supported; AttributeAddress must
|
|
* be of type AttributeEnd.
|
|
* As it's implemented, this function is only intended to assist in creating new file records. It
|
|
* could be made more general-purpose by considering file records with an $ATTRIBUTE_LIST.
|
|
* It's the caller's responsibility to ensure the given file record has enough memory allocated
|
|
* for the attribute.
|
|
*/
|
|
NTSTATUS
|
|
AddStandardInformation(PFILE_RECORD_HEADER FileRecord,
|
|
PNTFS_ATTR_RECORD AttributeAddress)
|
|
{
|
|
ULONG ResidentHeaderLength = FIELD_OFFSET(NTFS_ATTR_RECORD, Resident.Reserved) + sizeof(UCHAR);
|
|
PSTANDARD_INFORMATION StandardInfo = (PSTANDARD_INFORMATION)((LONG_PTR)AttributeAddress + ResidentHeaderLength);
|
|
LARGE_INTEGER SystemTime;
|
|
ULONG FileRecordEnd = AttributeAddress->Length;
|
|
|
|
if (AttributeAddress->Type != AttributeEnd)
|
|
{
|
|
DPRINT1("FIXME: Can only add $STANDARD_INFORMATION attribute to the end of a file record.\n");
|
|
return STATUS_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
AttributeAddress->Type = AttributeStandardInformation;
|
|
AttributeAddress->Length = sizeof(STANDARD_INFORMATION) + ResidentHeaderLength;
|
|
AttributeAddress->Length = ALIGN_UP_BY(AttributeAddress->Length, ATTR_RECORD_ALIGNMENT);
|
|
AttributeAddress->Resident.ValueLength = sizeof(STANDARD_INFORMATION);
|
|
AttributeAddress->Resident.ValueOffset = ResidentHeaderLength;
|
|
AttributeAddress->Instance = FileRecord->NextAttributeNumber++;
|
|
|
|
// set dates and times
|
|
KeQuerySystemTime(&SystemTime);
|
|
StandardInfo->CreationTime = SystemTime.QuadPart;
|
|
StandardInfo->ChangeTime = SystemTime.QuadPart;
|
|
StandardInfo->LastWriteTime = SystemTime.QuadPart;
|
|
StandardInfo->LastAccessTime = SystemTime.QuadPart;
|
|
StandardInfo->FileAttribute = NTFS_FILE_TYPE_ARCHIVE;
|
|
|
|
// move the attribute-end and file-record-end markers to the end of the file record
|
|
AttributeAddress = (PNTFS_ATTR_RECORD)((ULONG_PTR)AttributeAddress + AttributeAddress->Length);
|
|
SetFileRecordEnd(FileRecord, AttributeAddress, FileRecordEnd);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* @name ConvertDataRunsToLargeMCB
|
|
* @implemented
|
|
*
|
|
* Converts binary data runs to a map control block.
|
|
*
|
|
* @param DataRun
|
|
* Pointer to the run data
|
|
*
|
|
* @param DataRunsMCB
|
|
* Pointer to an unitialized LARGE_MCB structure.
|
|
*
|
|
* @return
|
|
* STATUS_SUCCESS on success, STATUS_INSUFFICIENT_RESOURCES or STATUS_UNSUCCESSFUL if we fail to
|
|
* initialize the mcb or add an entry.
|
|
*
|
|
* @remarks
|
|
* Initializes the LARGE_MCB pointed to by DataRunsMCB. If this function succeeds, you
|
|
* need to call FsRtlUninitializeLargeMcb() when you're done with DataRunsMCB. This
|
|
* function will ensure the LargeMCB has been unitialized in case of failure.
|
|
*
|
|
*/
|
|
NTSTATUS
|
|
ConvertDataRunsToLargeMCB(PUCHAR DataRun,
|
|
PLARGE_MCB DataRunsMCB,
|
|
PULONGLONG pNextVBN)
|
|
{
|
|
LONGLONG DataRunOffset;
|
|
ULONGLONG DataRunLength;
|
|
LONGLONG DataRunStartLCN;
|
|
ULONGLONG LastLCN = 0;
|
|
|
|
// Initialize the MCB, potentially catch an exception
|
|
_SEH2_TRY{
|
|
FsRtlInitializeLargeMcb(DataRunsMCB, NonPagedPool);
|
|
} _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
|
|
_SEH2_YIELD(return _SEH2_GetExceptionCode());
|
|
} _SEH2_END;
|
|
|
|
while (*DataRun != 0)
|
|
{
|
|
DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
|
|
|
|
if (DataRunOffset != -1)
|
|
{
|
|
// Normal data run.
|
|
DataRunStartLCN = LastLCN + DataRunOffset;
|
|
LastLCN = DataRunStartLCN;
|
|
|
|
_SEH2_TRY{
|
|
if (!FsRtlAddLargeMcbEntry(DataRunsMCB,
|
|
*pNextVBN,
|
|
DataRunStartLCN,
|
|
DataRunLength))
|
|
{
|
|
ExRaiseStatus(STATUS_UNSUCCESSFUL);
|
|
}
|
|
} _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
|
|
FsRtlUninitializeLargeMcb(DataRunsMCB);
|
|
_SEH2_YIELD(return _SEH2_GetExceptionCode());
|
|
} _SEH2_END;
|
|
|
|
}
|
|
|
|
*pNextVBN += DataRunLength;
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* @name ConvertLargeMCBToDataRuns
|
|
* @implemented
|
|
*
|
|
* Converts a map control block to a series of encoded data runs (used by non-resident attributes).
|
|
*
|
|
* @param DataRunsMCB
|
|
* Pointer to a LARGE_MCB structure describing the data runs.
|
|
*
|
|
* @param RunBuffer
|
|
* Pointer to the buffer that will receive the encoded data runs.
|
|
*
|
|
* @param MaxBufferSize
|
|
* Size of RunBuffer, in bytes.
|
|
*
|
|
* @param UsedBufferSize
|
|
* Pointer to a ULONG that will receive the size of the data runs in bytes. Can't be NULL.
|
|
*
|
|
* @return
|
|
* STATUS_SUCCESS on success, STATUS_BUFFER_TOO_SMALL if RunBuffer is too small to contain the
|
|
* complete output.
|
|
*
|
|
*/
|
|
NTSTATUS
|
|
ConvertLargeMCBToDataRuns(PLARGE_MCB DataRunsMCB,
|
|
PUCHAR RunBuffer,
|
|
ULONG MaxBufferSize,
|
|
PULONG UsedBufferSize)
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
ULONG RunBufferOffset = 0;
|
|
LONGLONG DataRunOffset;
|
|
ULONGLONG LastLCN = 0;
|
|
LONGLONG Vbn, Lbn, Count;
|
|
ULONG i;
|
|
|
|
|
|
DPRINT("\t[Vbn, Lbn, Count]\n");
|
|
|
|
// convert each mcb entry to a data run
|
|
for (i = 0; FsRtlGetNextLargeMcbEntry(DataRunsMCB, i, &Vbn, &Lbn, &Count); i++)
|
|
{
|
|
UCHAR DataRunOffsetSize = 0;
|
|
UCHAR DataRunLengthSize = 0;
|
|
UCHAR ControlByte = 0;
|
|
|
|
// [vbn, lbn, count]
|
|
DPRINT("\t[%I64d, %I64d,%I64d]\n", Vbn, Lbn, Count);
|
|
|
|
// TODO: check for holes and convert to sparse runs
|
|
DataRunOffset = Lbn - LastLCN;
|
|
LastLCN = Lbn;
|
|
|
|
// now we need to determine how to represent DataRunOffset with the minimum number of bytes
|
|
DPRINT("Determining how many bytes needed to represent %I64x\n", DataRunOffset);
|
|
DataRunOffsetSize = GetPackedByteCount(DataRunOffset, TRUE);
|
|
DPRINT("%d bytes needed.\n", DataRunOffsetSize);
|
|
|
|
// determine how to represent DataRunLengthSize with the minimum number of bytes
|
|
DPRINT("Determining how many bytes needed to represent %I64x\n", Count);
|
|
DataRunLengthSize = GetPackedByteCount(Count, TRUE);
|
|
DPRINT("%d bytes needed.\n", DataRunLengthSize);
|
|
|
|
// ensure the next data run + end marker would be <= Max buffer size
|
|
if (RunBufferOffset + 2 + DataRunLengthSize + DataRunOffsetSize > MaxBufferSize)
|
|
{
|
|
Status = STATUS_BUFFER_TOO_SMALL;
|
|
DPRINT1("FIXME: Ran out of room in buffer for data runs!\n");
|
|
break;
|
|
}
|
|
|
|
// pack and copy the control byte
|
|
ControlByte = (DataRunOffsetSize << 4) + DataRunLengthSize;
|
|
RunBuffer[RunBufferOffset++] = ControlByte;
|
|
|
|
// copy DataRunLength
|
|
RtlCopyMemory(RunBuffer + RunBufferOffset, &Count, DataRunLengthSize);
|
|
RunBufferOffset += DataRunLengthSize;
|
|
|
|
// copy DataRunOffset
|
|
RtlCopyMemory(RunBuffer + RunBufferOffset, &DataRunOffset, DataRunOffsetSize);
|
|
RunBufferOffset += DataRunOffsetSize;
|
|
}
|
|
|
|
// End of data runs
|
|
RunBuffer[RunBufferOffset++] = 0;
|
|
|
|
*UsedBufferSize = RunBufferOffset;
|
|
DPRINT("New Size of DataRuns: %ld\n", *UsedBufferSize);
|
|
|
|
return Status;
|
|
}
|
|
|
|
PUCHAR
|
|
DecodeRun(PUCHAR DataRun,
|
|
LONGLONG *DataRunOffset,
|
|
ULONGLONG *DataRunLength)
|
|
{
|
|
UCHAR DataRunOffsetSize;
|
|
UCHAR DataRunLengthSize;
|
|
CHAR i;
|
|
|
|
DataRunOffsetSize = (*DataRun >> 4) & 0xF;
|
|
DataRunLengthSize = *DataRun & 0xF;
|
|
*DataRunOffset = 0;
|
|
*DataRunLength = 0;
|
|
DataRun++;
|
|
for (i = 0; i < DataRunLengthSize; i++)
|
|
{
|
|
*DataRunLength += ((ULONG64)*DataRun) << (i * 8);
|
|
DataRun++;
|
|
}
|
|
|
|
/* NTFS 3+ sparse files */
|
|
if (DataRunOffsetSize == 0)
|
|
{
|
|
*DataRunOffset = -1;
|
|
}
|
|
else
|
|
{
|
|
for (i = 0; i < DataRunOffsetSize - 1; i++)
|
|
{
|
|
*DataRunOffset += ((ULONG64)*DataRun) << (i * 8);
|
|
DataRun++;
|
|
}
|
|
/* The last byte contains sign so we must process it different way. */
|
|
*DataRunOffset = ((LONG64)(CHAR)(*(DataRun++)) << (i * 8)) + *DataRunOffset;
|
|
}
|
|
|
|
DPRINT("DataRunOffsetSize: %x\n", DataRunOffsetSize);
|
|
DPRINT("DataRunLengthSize: %x\n", DataRunLengthSize);
|
|
DPRINT("DataRunOffset: %x\n", *DataRunOffset);
|
|
DPRINT("DataRunLength: %x\n", *DataRunLength);
|
|
|
|
return DataRun;
|
|
}
|
|
|
|
BOOLEAN
|
|
FindRun(PNTFS_ATTR_RECORD NresAttr,
|
|
ULONGLONG vcn,
|
|
PULONGLONG lcn,
|
|
PULONGLONG count)
|
|
{
|
|
if (vcn < NresAttr->NonResident.LowestVCN || vcn > NresAttr->NonResident.HighestVCN)
|
|
return FALSE;
|
|
|
|
DecodeRun((PUCHAR)((ULONG_PTR)NresAttr + NresAttr->NonResident.MappingPairsOffset), (PLONGLONG)lcn, count);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* @name FreeClusters
|
|
* @implemented
|
|
*
|
|
* Shrinks the allocation size of a non-resident attribute by a given number of clusters.
|
|
* Frees the clusters from the volume's $BITMAP file as well as the attribute's data runs.
|
|
*
|
|
* @param Vcb
|
|
* Pointer to an NTFS_VCB for the destination volume.
|
|
*
|
|
* @param AttrContext
|
|
* Pointer to an NTFS_ATTR_CONTEXT describing the attribute from which the clusters will be freed.
|
|
*
|
|
* @param AttrOffset
|
|
* Byte offset of the destination attribute relative to its file record.
|
|
*
|
|
* @param FileRecord
|
|
* Pointer to a complete copy of the file record containing the attribute. Must be at least
|
|
* Vcb->NtfsInfo.BytesPerFileRecord bytes long.
|
|
*
|
|
* @param ClustersToFree
|
|
* Number of clusters that should be freed from the end of the data stream. Must be no more
|
|
* Than the number of clusters assigned to the attribute (HighestVCN + 1).
|
|
*
|
|
* @return
|
|
* STATUS_SUCCESS on success. STATUS_INVALID_PARAMETER if AttrContext describes a resident attribute,
|
|
* or if the caller requested more clusters be freed than the attribute has been allocated.
|
|
* STATUS_INSUFFICIENT_RESOURCES if allocating a buffer for the data runs fails or
|
|
* if ConvertDataRunsToLargeMCB() fails.
|
|
* STATUS_BUFFER_TOO_SMALL if ConvertLargeMCBToDataRuns() fails.
|
|
*
|
|
*
|
|
*/
|
|
NTSTATUS
|
|
FreeClusters(PNTFS_VCB Vcb,
|
|
PNTFS_ATTR_CONTEXT AttrContext,
|
|
ULONG AttrOffset,
|
|
PFILE_RECORD_HEADER FileRecord,
|
|
ULONG ClustersToFree)
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
ULONG ClustersLeftToFree = ClustersToFree;
|
|
|
|
PNTFS_ATTR_RECORD DestinationAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttrOffset);
|
|
ULONG NextAttributeOffset = AttrOffset + AttrContext->pRecord->Length;
|
|
PNTFS_ATTR_RECORD NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + NextAttributeOffset);
|
|
|
|
PUCHAR RunBuffer;
|
|
ULONG RunBufferSize = 0;
|
|
|
|
PFILE_RECORD_HEADER BitmapRecord;
|
|
PNTFS_ATTR_CONTEXT DataContext;
|
|
ULONGLONG BitmapDataSize;
|
|
PUCHAR BitmapData;
|
|
RTL_BITMAP Bitmap;
|
|
ULONG LengthWritten;
|
|
|
|
if (!AttrContext->pRecord->IsNonResident)
|
|
{
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
// Read the $Bitmap file
|
|
BitmapRecord = ExAllocateFromNPagedLookasideList(&Vcb->FileRecLookasideList);
|
|
if (BitmapRecord == NULL)
|
|
{
|
|
DPRINT1("Error: Unable to allocate memory for bitmap file record!\n");
|
|
return STATUS_NO_MEMORY;
|
|
}
|
|
|
|
Status = ReadFileRecord(Vcb, NTFS_FILE_BITMAP, BitmapRecord);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DPRINT1("Error: Unable to read file record for bitmap!\n");
|
|
ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, BitmapRecord);
|
|
return 0;
|
|
}
|
|
|
|
Status = FindAttribute(Vcb, BitmapRecord, AttributeData, L"", 0, &DataContext, NULL);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DPRINT1("Error: Unable to find data attribute for bitmap file!\n");
|
|
ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, BitmapRecord);
|
|
return 0;
|
|
}
|
|
|
|
BitmapDataSize = AttributeDataLength(DataContext->pRecord);
|
|
BitmapDataSize = min(BitmapDataSize, ULONG_MAX);
|
|
ASSERT((BitmapDataSize * 8) >= Vcb->NtfsInfo.ClusterCount);
|
|
BitmapData = ExAllocatePoolWithTag(NonPagedPool, ROUND_UP(BitmapDataSize, Vcb->NtfsInfo.BytesPerSector), TAG_NTFS);
|
|
if (BitmapData == NULL)
|
|
{
|
|
DPRINT1("Error: Unable to allocate memory for bitmap file data!\n");
|
|
ReleaseAttributeContext(DataContext);
|
|
ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, BitmapRecord);
|
|
return 0;
|
|
}
|
|
|
|
ReadAttribute(Vcb, DataContext, 0, (PCHAR)BitmapData, (ULONG)BitmapDataSize);
|
|
|
|
RtlInitializeBitMap(&Bitmap, (PULONG)BitmapData, Vcb->NtfsInfo.ClusterCount);
|
|
|
|
// free clusters in $BITMAP file
|
|
while (ClustersLeftToFree > 0)
|
|
{
|
|
LONGLONG LargeVbn, LargeLbn;
|
|
|
|
if (!FsRtlLookupLastLargeMcbEntry(&AttrContext->DataRunsMCB, &LargeVbn, &LargeLbn))
|
|
{
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
DPRINT1("DRIVER ERROR: FreeClusters called to free %lu clusters, which is %lu more clusters than are assigned to attribute!",
|
|
ClustersToFree,
|
|
ClustersLeftToFree);
|
|
break;
|
|
}
|
|
|
|
if (LargeLbn != -1)
|
|
{
|
|
// deallocate this cluster
|
|
RtlClearBits(&Bitmap, LargeLbn, 1);
|
|
}
|
|
FsRtlTruncateLargeMcb(&AttrContext->DataRunsMCB, AttrContext->pRecord->NonResident.HighestVCN);
|
|
|
|
// decrement HighestVCN, but don't let it go below 0
|
|
AttrContext->pRecord->NonResident.HighestVCN = min(AttrContext->pRecord->NonResident.HighestVCN, AttrContext->pRecord->NonResident.HighestVCN - 1);
|
|
ClustersLeftToFree--;
|
|
}
|
|
|
|
// update $BITMAP file on disk
|
|
Status = WriteAttribute(Vcb, DataContext, 0, BitmapData, (ULONG)BitmapDataSize, &LengthWritten, FileRecord);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
ReleaseAttributeContext(DataContext);
|
|
ExFreePoolWithTag(BitmapData, TAG_NTFS);
|
|
ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, BitmapRecord);
|
|
return Status;
|
|
}
|
|
|
|
ReleaseAttributeContext(DataContext);
|
|
ExFreePoolWithTag(BitmapData, TAG_NTFS);
|
|
ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, BitmapRecord);
|
|
|
|
// Save updated data runs to file record
|
|
|
|
// Allocate some memory for a new RunBuffer
|
|
RunBuffer = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
|
|
if (!RunBuffer)
|
|
{
|
|
DPRINT1("ERROR: Couldn't allocate memory for data runs!\n");
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
// Convert the map control block back to encoded data runs
|
|
ConvertLargeMCBToDataRuns(&AttrContext->DataRunsMCB, RunBuffer, Vcb->NtfsInfo.BytesPerCluster, &RunBufferSize);
|
|
|
|
// Update HighestVCN
|
|
DestinationAttribute->NonResident.HighestVCN = AttrContext->pRecord->NonResident.HighestVCN;
|
|
|
|
// Write data runs to destination attribute
|
|
RtlCopyMemory((PVOID)((ULONG_PTR)DestinationAttribute + DestinationAttribute->NonResident.MappingPairsOffset),
|
|
RunBuffer,
|
|
RunBufferSize);
|
|
|
|
// Is DestinationAttribute the last attribute in the file record?
|
|
if (NextAttribute->Type == AttributeEnd)
|
|
{
|
|
// update attribute length
|
|
DestinationAttribute->Length = ALIGN_UP_BY(AttrContext->pRecord->NonResident.MappingPairsOffset + RunBufferSize,
|
|
ATTR_RECORD_ALIGNMENT);
|
|
|
|
ASSERT(DestinationAttribute->Length <= AttrContext->pRecord->Length);
|
|
|
|
AttrContext->pRecord->Length = DestinationAttribute->Length;
|
|
|
|
// write end markers
|
|
NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)DestinationAttribute + DestinationAttribute->Length);
|
|
SetFileRecordEnd(FileRecord, NextAttribute, FILE_RECORD_END);
|
|
}
|
|
|
|
// Update the file record
|
|
Status = UpdateFileRecord(Vcb, AttrContext->FileMFTIndex, FileRecord);
|
|
|
|
ExFreePoolWithTag(RunBuffer, TAG_NTFS);
|
|
|
|
NtfsDumpDataRuns((PUCHAR)((ULONG_PTR)DestinationAttribute + DestinationAttribute->NonResident.MappingPairsOffset), 0);
|
|
|
|
return Status;
|
|
}
|
|
|
|
static
|
|
NTSTATUS
|
|
InternalReadNonResidentAttributes(PFIND_ATTR_CONTXT Context)
|
|
{
|
|
ULONGLONG ListSize;
|
|
PNTFS_ATTR_RECORD Attribute;
|
|
PNTFS_ATTR_CONTEXT ListContext;
|
|
|
|
DPRINT("InternalReadNonResidentAttributes(%p)\n", Context);
|
|
|
|
Attribute = Context->CurrAttr;
|
|
ASSERT(Attribute->Type == AttributeAttributeList);
|
|
|
|
if (Context->OnlyResident)
|
|
{
|
|
Context->NonResidentStart = NULL;
|
|
Context->NonResidentEnd = NULL;
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
if (Context->NonResidentStart != NULL)
|
|
{
|
|
return STATUS_FILE_CORRUPT_ERROR;
|
|
}
|
|
|
|
ListContext = PrepareAttributeContext(Attribute);
|
|
ListSize = AttributeDataLength(ListContext->pRecord);
|
|
if (ListSize > 0xFFFFFFFF)
|
|
{
|
|
ReleaseAttributeContext(ListContext);
|
|
return STATUS_BUFFER_OVERFLOW;
|
|
}
|
|
|
|
Context->NonResidentStart = ExAllocatePoolWithTag(NonPagedPool, (ULONG)ListSize, TAG_NTFS);
|
|
if (Context->NonResidentStart == NULL)
|
|
{
|
|
ReleaseAttributeContext(ListContext);
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
if (ReadAttribute(Context->Vcb, ListContext, 0, (PCHAR)Context->NonResidentStart, (ULONG)ListSize) != ListSize)
|
|
{
|
|
ExFreePoolWithTag(Context->NonResidentStart, TAG_NTFS);
|
|
Context->NonResidentStart = NULL;
|
|
ReleaseAttributeContext(ListContext);
|
|
return STATUS_FILE_CORRUPT_ERROR;
|
|
}
|
|
|
|
ReleaseAttributeContext(ListContext);
|
|
Context->NonResidentEnd = (PNTFS_ATTRIBUTE_LIST_ITEM)((PCHAR)Context->NonResidentStart + ListSize);
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static
|
|
PNTFS_ATTRIBUTE_LIST_ITEM
|
|
InternalGetNextAttributeListItem(PFIND_ATTR_CONTXT Context)
|
|
{
|
|
PNTFS_ATTRIBUTE_LIST_ITEM NextItem;
|
|
|
|
if (Context->NonResidentCur == (PVOID)-1)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
if (Context->NonResidentCur == NULL || Context->NonResidentCur->Type == AttributeEnd)
|
|
{
|
|
Context->NonResidentCur = (PVOID)-1;
|
|
return NULL;
|
|
}
|
|
|
|
if (Context->NonResidentCur->Length == 0)
|
|
{
|
|
DPRINT1("Broken length list entry length !");
|
|
Context->NonResidentCur = (PVOID)-1;
|
|
return NULL;
|
|
}
|
|
|
|
NextItem = (PNTFS_ATTRIBUTE_LIST_ITEM)((PCHAR)Context->NonResidentCur + Context->NonResidentCur->Length);
|
|
if (NextItem->Length == 0 || NextItem->Type == AttributeEnd)
|
|
{
|
|
Context->NonResidentCur = (PVOID)-1;
|
|
return NULL;
|
|
}
|
|
|
|
if (NextItem < Context->NonResidentStart || NextItem > Context->NonResidentEnd)
|
|
{
|
|
Context->NonResidentCur = (PVOID)-1;
|
|
return NULL;
|
|
}
|
|
|
|
Context->NonResidentCur = NextItem;
|
|
return NextItem;
|
|
}
|
|
|
|
NTSTATUS
|
|
FindFirstAttributeListItem(PFIND_ATTR_CONTXT Context,
|
|
PNTFS_ATTRIBUTE_LIST_ITEM *Item)
|
|
{
|
|
if (Context->NonResidentStart == NULL || Context->NonResidentStart->Type == AttributeEnd)
|
|
{
|
|
return STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
Context->NonResidentCur = Context->NonResidentStart;
|
|
*Item = Context->NonResidentCur;
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
NTSTATUS
|
|
FindNextAttributeListItem(PFIND_ATTR_CONTXT Context,
|
|
PNTFS_ATTRIBUTE_LIST_ITEM *Item)
|
|
{
|
|
*Item = InternalGetNextAttributeListItem(Context);
|
|
if (*Item == NULL)
|
|
{
|
|
return STATUS_UNSUCCESSFUL;
|
|
}
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static
|
|
PNTFS_ATTR_RECORD
|
|
InternalGetNextAttribute(PFIND_ATTR_CONTXT Context)
|
|
{
|
|
PNTFS_ATTR_RECORD NextAttribute;
|
|
|
|
if (Context->CurrAttr == (PVOID)-1)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
if (Context->CurrAttr >= Context->FirstAttr &&
|
|
Context->CurrAttr < Context->LastAttr)
|
|
{
|
|
if (Context->CurrAttr->Length == 0)
|
|
{
|
|
DPRINT1("Broken length!\n");
|
|
Context->CurrAttr = (PVOID)-1;
|
|
return NULL;
|
|
}
|
|
|
|
NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)Context->CurrAttr + Context->CurrAttr->Length);
|
|
|
|
if (NextAttribute > Context->LastAttr || NextAttribute < Context->FirstAttr)
|
|
{
|
|
DPRINT1("Broken length: 0x%lx!\n", Context->CurrAttr->Length);
|
|
Context->CurrAttr = (PVOID)-1;
|
|
return NULL;
|
|
}
|
|
|
|
Context->Offset += ((ULONG_PTR)NextAttribute - (ULONG_PTR)Context->CurrAttr);
|
|
Context->CurrAttr = NextAttribute;
|
|
|
|
if (Context->CurrAttr < Context->LastAttr &&
|
|
Context->CurrAttr->Type != AttributeEnd)
|
|
{
|
|
return Context->CurrAttr;
|
|
}
|
|
}
|
|
|
|
if (Context->NonResidentStart == NULL)
|
|
{
|
|
Context->CurrAttr = (PVOID)-1;
|
|
return NULL;
|
|
}
|
|
|
|
Context->CurrAttr = (PVOID)-1;
|
|
return NULL;
|
|
}
|
|
|
|
NTSTATUS
|
|
FindFirstAttribute(PFIND_ATTR_CONTXT Context,
|
|
PDEVICE_EXTENSION Vcb,
|
|
PFILE_RECORD_HEADER FileRecord,
|
|
BOOLEAN OnlyResident,
|
|
PNTFS_ATTR_RECORD * Attribute)
|
|
{
|
|
NTSTATUS Status;
|
|
|
|
DPRINT("FindFistAttribute(%p, %p, %p, %p, %u, %p)\n", Context, Vcb, FileRecord, OnlyResident, Attribute);
|
|
|
|
Context->Vcb = Vcb;
|
|
Context->OnlyResident = OnlyResident;
|
|
Context->FirstAttr = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + FileRecord->AttributeOffset);
|
|
Context->CurrAttr = Context->FirstAttr;
|
|
Context->LastAttr = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + FileRecord->BytesInUse);
|
|
Context->NonResidentStart = NULL;
|
|
Context->NonResidentEnd = NULL;
|
|
Context->Offset = FileRecord->AttributeOffset;
|
|
|
|
if (Context->FirstAttr->Type == AttributeEnd)
|
|
{
|
|
Context->CurrAttr = (PVOID)-1;
|
|
return STATUS_END_OF_FILE;
|
|
}
|
|
else if (Context->FirstAttr->Type == AttributeAttributeList)
|
|
{
|
|
Status = InternalReadNonResidentAttributes(Context);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
return Status;
|
|
}
|
|
|
|
*Attribute = InternalGetNextAttribute(Context);
|
|
if (*Attribute == NULL)
|
|
{
|
|
return STATUS_END_OF_FILE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
*Attribute = Context->CurrAttr;
|
|
Context->Offset = (UCHAR*)Context->CurrAttr - (UCHAR*)FileRecord;
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
NTSTATUS
|
|
FindNextAttribute(PFIND_ATTR_CONTXT Context,
|
|
PNTFS_ATTR_RECORD * Attribute)
|
|
{
|
|
NTSTATUS Status;
|
|
|
|
DPRINT("FindNextAttribute(%p, %p)\n", Context, Attribute);
|
|
|
|
*Attribute = InternalGetNextAttribute(Context);
|
|
if (*Attribute == NULL)
|
|
{
|
|
return STATUS_END_OF_FILE;
|
|
}
|
|
|
|
if (Context->CurrAttr->Type != AttributeAttributeList)
|
|
{
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
Status = InternalReadNonResidentAttributes(Context);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
return Status;
|
|
}
|
|
|
|
*Attribute = InternalGetNextAttribute(Context);
|
|
if (*Attribute == NULL)
|
|
{
|
|
return STATUS_END_OF_FILE;
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
VOID
|
|
FindCloseAttribute(PFIND_ATTR_CONTXT Context)
|
|
{
|
|
if (Context->NonResidentStart != NULL)
|
|
{
|
|
ExFreePoolWithTag(Context->NonResidentStart, TAG_NTFS);
|
|
Context->NonResidentStart = NULL;
|
|
}
|
|
}
|
|
|
|
static
|
|
VOID
|
|
NtfsDumpFileNameAttribute(PNTFS_ATTR_RECORD Attribute)
|
|
{
|
|
PFILENAME_ATTRIBUTE FileNameAttr;
|
|
|
|
DbgPrint(" $FILE_NAME ");
|
|
|
|
// DbgPrint(" Length %lu Offset %hu ", Attribute->Resident.ValueLength, Attribute->Resident.ValueOffset);
|
|
|
|
FileNameAttr = (PFILENAME_ATTRIBUTE)((ULONG_PTR)Attribute + Attribute->Resident.ValueOffset);
|
|
DbgPrint(" (%x) '%.*S' ", FileNameAttr->NameType, FileNameAttr->NameLength, FileNameAttr->Name);
|
|
DbgPrint(" '%x' \n", FileNameAttr->FileAttributes);
|
|
DbgPrint(" AllocatedSize: %I64u\nDataSize: %I64u\n", FileNameAttr->AllocatedSize, FileNameAttr->DataSize);
|
|
DbgPrint(" File reference: 0x%016I64x\n", FileNameAttr->DirectoryFileReferenceNumber);
|
|
}
|
|
|
|
|
|
static
|
|
VOID
|
|
NtfsDumpStandardInformationAttribute(PNTFS_ATTR_RECORD Attribute)
|
|
{
|
|
PSTANDARD_INFORMATION StandardInfoAttr;
|
|
|
|
DbgPrint(" $STANDARD_INFORMATION ");
|
|
|
|
// DbgPrint(" Length %lu Offset %hu ", Attribute->Resident.ValueLength, Attribute->Resident.ValueOffset);
|
|
|
|
StandardInfoAttr = (PSTANDARD_INFORMATION)((ULONG_PTR)Attribute + Attribute->Resident.ValueOffset);
|
|
DbgPrint(" '%x' ", StandardInfoAttr->FileAttribute);
|
|
}
|
|
|
|
|
|
static
|
|
VOID
|
|
NtfsDumpVolumeNameAttribute(PNTFS_ATTR_RECORD Attribute)
|
|
{
|
|
PWCHAR VolumeName;
|
|
|
|
DbgPrint(" $VOLUME_NAME ");
|
|
|
|
// DbgPrint(" Length %lu Offset %hu ", Attribute->Resident.ValueLength, Attribute->Resident.ValueOffset);
|
|
|
|
VolumeName = (PWCHAR)((ULONG_PTR)Attribute + Attribute->Resident.ValueOffset);
|
|
DbgPrint(" '%.*S' ", Attribute->Resident.ValueLength / sizeof(WCHAR), VolumeName);
|
|
}
|
|
|
|
|
|
static
|
|
VOID
|
|
NtfsDumpVolumeInformationAttribute(PNTFS_ATTR_RECORD Attribute)
|
|
{
|
|
PVOLINFO_ATTRIBUTE VolInfoAttr;
|
|
|
|
DbgPrint(" $VOLUME_INFORMATION ");
|
|
|
|
// DbgPrint(" Length %lu Offset %hu ", Attribute->Resident.ValueLength, Attribute->Resident.ValueOffset);
|
|
|
|
VolInfoAttr = (PVOLINFO_ATTRIBUTE)((ULONG_PTR)Attribute + Attribute->Resident.ValueOffset);
|
|
DbgPrint(" NTFS Version %u.%u Flags 0x%04hx ",
|
|
VolInfoAttr->MajorVersion,
|
|
VolInfoAttr->MinorVersion,
|
|
VolInfoAttr->Flags);
|
|
}
|
|
|
|
|
|
static
|
|
VOID
|
|
NtfsDumpIndexRootAttribute(PNTFS_ATTR_RECORD Attribute)
|
|
{
|
|
PINDEX_ROOT_ATTRIBUTE IndexRootAttr;
|
|
ULONG CurrentOffset;
|
|
ULONG CurrentNode;
|
|
|
|
IndexRootAttr = (PINDEX_ROOT_ATTRIBUTE)((ULONG_PTR)Attribute + Attribute->Resident.ValueOffset);
|
|
|
|
if (IndexRootAttr->AttributeType == AttributeFileName)
|
|
ASSERT(IndexRootAttr->CollationRule == COLLATION_FILE_NAME);
|
|
|
|
DbgPrint(" $INDEX_ROOT (%u bytes per index record, %u clusters) ", IndexRootAttr->SizeOfEntry, IndexRootAttr->ClustersPerIndexRecord);
|
|
|
|
if (IndexRootAttr->Header.Flags == INDEX_ROOT_SMALL)
|
|
{
|
|
DbgPrint(" (small)\n");
|
|
}
|
|
else
|
|
{
|
|
ASSERT(IndexRootAttr->Header.Flags == INDEX_ROOT_LARGE);
|
|
DbgPrint(" (large)\n");
|
|
}
|
|
|
|
DbgPrint(" Offset to first index: 0x%lx\n Total size of index entries: 0x%lx\n Allocated size of node: 0x%lx\n",
|
|
IndexRootAttr->Header.FirstEntryOffset,
|
|
IndexRootAttr->Header.TotalSizeOfEntries,
|
|
IndexRootAttr->Header.AllocatedSize);
|
|
CurrentOffset = IndexRootAttr->Header.FirstEntryOffset;
|
|
CurrentNode = 0;
|
|
// print details of every node in the index
|
|
while (CurrentOffset < IndexRootAttr->Header.TotalSizeOfEntries)
|
|
{
|
|
PINDEX_ENTRY_ATTRIBUTE currentIndexExtry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)IndexRootAttr + 0x10 + CurrentOffset);
|
|
DbgPrint(" Index Node Entry %lu", CurrentNode++);
|
|
if (BooleanFlagOn(currentIndexExtry->Flags, NTFS_INDEX_ENTRY_NODE))
|
|
DbgPrint(" (Branch)");
|
|
else
|
|
DbgPrint(" (Leaf)");
|
|
if (BooleanFlagOn(currentIndexExtry->Flags, NTFS_INDEX_ENTRY_END))
|
|
{
|
|
DbgPrint(" (Dummy Key)");
|
|
}
|
|
DbgPrint("\n File Reference: 0x%016I64x\n", currentIndexExtry->Data.Directory.IndexedFile);
|
|
DbgPrint(" Index Entry Length: 0x%x\n", currentIndexExtry->Length);
|
|
DbgPrint(" Index Key Length: 0x%x\n", currentIndexExtry->KeyLength);
|
|
|
|
// if this isn't the final (dummy) node, print info about the key (Filename attribute)
|
|
if (!(currentIndexExtry->Flags & NTFS_INDEX_ENTRY_END))
|
|
{
|
|
UNICODE_STRING Name;
|
|
DbgPrint(" Parent File Reference: 0x%016I64x\n", currentIndexExtry->FileName.DirectoryFileReferenceNumber);
|
|
DbgPrint(" $FILENAME indexed: ");
|
|
Name.Length = currentIndexExtry->FileName.NameLength * sizeof(WCHAR);
|
|
Name.MaximumLength = Name.Length;
|
|
Name.Buffer = currentIndexExtry->FileName.Name;
|
|
DbgPrint("'%wZ'\n", &Name);
|
|
}
|
|
|
|
// if this node has a sub-node beneath it
|
|
if (currentIndexExtry->Flags & NTFS_INDEX_ENTRY_NODE)
|
|
{
|
|
// Print the VCN of the sub-node
|
|
PULONGLONG SubNodeVCN = (PULONGLONG)((ULONG_PTR)currentIndexExtry + currentIndexExtry->Length - sizeof(ULONGLONG));
|
|
DbgPrint(" VCN of sub-node: 0x%llx\n", *SubNodeVCN);
|
|
}
|
|
|
|
CurrentOffset += currentIndexExtry->Length;
|
|
ASSERT(currentIndexExtry->Length);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
static
|
|
VOID
|
|
NtfsDumpAttribute(PDEVICE_EXTENSION Vcb,
|
|
PNTFS_ATTR_RECORD Attribute)
|
|
{
|
|
UNICODE_STRING Name;
|
|
|
|
ULONGLONG lcn = 0;
|
|
ULONGLONG runcount = 0;
|
|
|
|
switch (Attribute->Type)
|
|
{
|
|
case AttributeFileName:
|
|
NtfsDumpFileNameAttribute(Attribute);
|
|
break;
|
|
|
|
case AttributeStandardInformation:
|
|
NtfsDumpStandardInformationAttribute(Attribute);
|
|
break;
|
|
|
|
case AttributeObjectId:
|
|
DbgPrint(" $OBJECT_ID ");
|
|
break;
|
|
|
|
case AttributeSecurityDescriptor:
|
|
DbgPrint(" $SECURITY_DESCRIPTOR ");
|
|
break;
|
|
|
|
case AttributeVolumeName:
|
|
NtfsDumpVolumeNameAttribute(Attribute);
|
|
break;
|
|
|
|
case AttributeVolumeInformation:
|
|
NtfsDumpVolumeInformationAttribute(Attribute);
|
|
break;
|
|
|
|
case AttributeData:
|
|
DbgPrint(" $DATA ");
|
|
//DataBuf = ExAllocatePool(NonPagedPool,AttributeLengthAllocated(Attribute));
|
|
break;
|
|
|
|
case AttributeIndexRoot:
|
|
NtfsDumpIndexRootAttribute(Attribute);
|
|
break;
|
|
|
|
case AttributeIndexAllocation:
|
|
DbgPrint(" $INDEX_ALLOCATION ");
|
|
break;
|
|
|
|
case AttributeBitmap:
|
|
DbgPrint(" $BITMAP ");
|
|
break;
|
|
|
|
case AttributeReparsePoint:
|
|
DbgPrint(" $REPARSE_POINT ");
|
|
break;
|
|
|
|
case AttributeEAInformation:
|
|
DbgPrint(" $EA_INFORMATION ");
|
|
break;
|
|
|
|
case AttributeEA:
|
|
DbgPrint(" $EA ");
|
|
break;
|
|
|
|
case AttributePropertySet:
|
|
DbgPrint(" $PROPERTY_SET ");
|
|
break;
|
|
|
|
case AttributeLoggedUtilityStream:
|
|
DbgPrint(" $LOGGED_UTILITY_STREAM ");
|
|
break;
|
|
|
|
default:
|
|
DbgPrint(" Attribute %lx ",
|
|
Attribute->Type);
|
|
break;
|
|
}
|
|
|
|
if (Attribute->Type != AttributeAttributeList)
|
|
{
|
|
if (Attribute->NameLength != 0)
|
|
{
|
|
Name.Length = Attribute->NameLength * sizeof(WCHAR);
|
|
Name.MaximumLength = Name.Length;
|
|
Name.Buffer = (PWCHAR)((ULONG_PTR)Attribute + Attribute->NameOffset);
|
|
|
|
DbgPrint("'%wZ' ", &Name);
|
|
}
|
|
|
|
DbgPrint("(%s)\n",
|
|
Attribute->IsNonResident ? "non-resident" : "resident");
|
|
|
|
if (Attribute->IsNonResident)
|
|
{
|
|
FindRun(Attribute,0,&lcn, &runcount);
|
|
|
|
DbgPrint(" AllocatedSize %I64u DataSize %I64u InitilizedSize %I64u\n",
|
|
Attribute->NonResident.AllocatedSize, Attribute->NonResident.DataSize, Attribute->NonResident.InitializedSize);
|
|
DbgPrint(" logical clusters: %I64u - %I64u\n",
|
|
lcn, lcn + runcount - 1);
|
|
}
|
|
else
|
|
DbgPrint(" %u bytes of data\n", Attribute->Resident.ValueLength);
|
|
}
|
|
}
|
|
|
|
|
|
VOID NtfsDumpDataRunData(PUCHAR DataRun)
|
|
{
|
|
UCHAR DataRunOffsetSize;
|
|
UCHAR DataRunLengthSize;
|
|
CHAR i;
|
|
|
|
DbgPrint("%02x ", *DataRun);
|
|
|
|
if (*DataRun == 0)
|
|
return;
|
|
|
|
DataRunOffsetSize = (*DataRun >> 4) & 0xF;
|
|
DataRunLengthSize = *DataRun & 0xF;
|
|
|
|
DataRun++;
|
|
for (i = 0; i < DataRunLengthSize; i++)
|
|
{
|
|
DbgPrint("%02x ", *DataRun);
|
|
DataRun++;
|
|
}
|
|
|
|
for (i = 0; i < DataRunOffsetSize; i++)
|
|
{
|
|
DbgPrint("%02x ", *DataRun);
|
|
DataRun++;
|
|
}
|
|
|
|
NtfsDumpDataRunData(DataRun);
|
|
}
|
|
|
|
|
|
VOID
|
|
NtfsDumpDataRuns(PVOID StartOfRun,
|
|
ULONGLONG CurrentLCN)
|
|
{
|
|
PUCHAR DataRun = StartOfRun;
|
|
LONGLONG DataRunOffset;
|
|
ULONGLONG DataRunLength;
|
|
|
|
if (CurrentLCN == 0)
|
|
{
|
|
DPRINT1("Dumping data runs.\n\tData:\n\t\t");
|
|
NtfsDumpDataRunData(StartOfRun);
|
|
DbgPrint("\n\tRuns:\n\t\tOff\t\tLCN\t\tLength\n");
|
|
}
|
|
|
|
DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
|
|
|
|
if (DataRunOffset != -1)
|
|
CurrentLCN += DataRunOffset;
|
|
|
|
DbgPrint("\t\t%I64d\t", DataRunOffset);
|
|
if (DataRunOffset < 99999)
|
|
DbgPrint("\t");
|
|
DbgPrint("%I64u\t", CurrentLCN);
|
|
if (CurrentLCN < 99999)
|
|
DbgPrint("\t");
|
|
DbgPrint("%I64u\n", DataRunLength);
|
|
|
|
if (*DataRun == 0)
|
|
DbgPrint("\t\t00\n");
|
|
else
|
|
NtfsDumpDataRuns(DataRun, CurrentLCN);
|
|
}
|
|
|
|
|
|
VOID
|
|
NtfsDumpFileAttributes(PDEVICE_EXTENSION Vcb,
|
|
PFILE_RECORD_HEADER FileRecord)
|
|
{
|
|
NTSTATUS Status;
|
|
FIND_ATTR_CONTXT Context;
|
|
PNTFS_ATTR_RECORD Attribute;
|
|
|
|
Status = FindFirstAttribute(&Context, Vcb, FileRecord, FALSE, &Attribute);
|
|
while (NT_SUCCESS(Status))
|
|
{
|
|
NtfsDumpAttribute(Vcb, Attribute);
|
|
|
|
Status = FindNextAttribute(&Context, &Attribute);
|
|
}
|
|
|
|
FindCloseAttribute(&Context);
|
|
}
|
|
|
|
PFILENAME_ATTRIBUTE
|
|
GetFileNameFromRecord(PDEVICE_EXTENSION Vcb,
|
|
PFILE_RECORD_HEADER FileRecord,
|
|
UCHAR NameType)
|
|
{
|
|
FIND_ATTR_CONTXT Context;
|
|
PNTFS_ATTR_RECORD Attribute;
|
|
PFILENAME_ATTRIBUTE Name;
|
|
NTSTATUS Status;
|
|
|
|
Status = FindFirstAttribute(&Context, Vcb, FileRecord, FALSE, &Attribute);
|
|
while (NT_SUCCESS(Status))
|
|
{
|
|
if (Attribute->Type == AttributeFileName)
|
|
{
|
|
Name = (PFILENAME_ATTRIBUTE)((ULONG_PTR)Attribute + Attribute->Resident.ValueOffset);
|
|
if (Name->NameType == NameType ||
|
|
(Name->NameType == NTFS_FILE_NAME_WIN32_AND_DOS && NameType == NTFS_FILE_NAME_WIN32) ||
|
|
(Name->NameType == NTFS_FILE_NAME_WIN32_AND_DOS && NameType == NTFS_FILE_NAME_DOS))
|
|
{
|
|
FindCloseAttribute(&Context);
|
|
return Name;
|
|
}
|
|
}
|
|
|
|
Status = FindNextAttribute(&Context, &Attribute);
|
|
}
|
|
|
|
FindCloseAttribute(&Context);
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* GetPackedByteCount
|
|
* Returns the minimum number of bytes needed to represent the value of a
|
|
* 64-bit number. Used to encode data runs.
|
|
*/
|
|
UCHAR
|
|
GetPackedByteCount(LONGLONG NumberToPack,
|
|
BOOLEAN IsSigned)
|
|
{
|
|
if (!IsSigned)
|
|
{
|
|
if (NumberToPack >= 0x0100000000000000)
|
|
return 8;
|
|
if (NumberToPack >= 0x0001000000000000)
|
|
return 7;
|
|
if (NumberToPack >= 0x0000010000000000)
|
|
return 6;
|
|
if (NumberToPack >= 0x0000000100000000)
|
|
return 5;
|
|
if (NumberToPack >= 0x0000000001000000)
|
|
return 4;
|
|
if (NumberToPack >= 0x0000000000010000)
|
|
return 3;
|
|
if (NumberToPack >= 0x0000000000000100)
|
|
return 2;
|
|
return 1;
|
|
}
|
|
|
|
if (NumberToPack > 0)
|
|
{
|
|
// we have to make sure the number that gets encoded won't be interpreted as negative
|
|
if (NumberToPack >= 0x0080000000000000)
|
|
return 8;
|
|
if (NumberToPack >= 0x0000800000000000)
|
|
return 7;
|
|
if (NumberToPack >= 0x0000008000000000)
|
|
return 6;
|
|
if (NumberToPack >= 0x0000000080000000)
|
|
return 5;
|
|
if (NumberToPack >= 0x0000000000800000)
|
|
return 4;
|
|
if (NumberToPack >= 0x0000000000008000)
|
|
return 3;
|
|
if (NumberToPack >= 0x0000000000000080)
|
|
return 2;
|
|
}
|
|
else
|
|
{
|
|
// negative number
|
|
if (NumberToPack <= 0xff80000000000000)
|
|
return 8;
|
|
if (NumberToPack <= 0xffff800000000000)
|
|
return 7;
|
|
if (NumberToPack <= 0xffffff8000000000)
|
|
return 6;
|
|
if (NumberToPack <= 0xffffffff80000000)
|
|
return 5;
|
|
if (NumberToPack <= 0xffffffffff800000)
|
|
return 4;
|
|
if (NumberToPack <= 0xffffffffffff8000)
|
|
return 3;
|
|
if (NumberToPack <= 0xffffffffffffff80)
|
|
return 2;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
NTSTATUS
|
|
GetLastClusterInDataRun(PDEVICE_EXTENSION Vcb, PNTFS_ATTR_RECORD Attribute, PULONGLONG LastCluster)
|
|
{
|
|
LONGLONG DataRunOffset;
|
|
ULONGLONG DataRunLength;
|
|
LONGLONG DataRunStartLCN;
|
|
|
|
ULONGLONG LastLCN = 0;
|
|
PUCHAR DataRun = (PUCHAR)Attribute + Attribute->NonResident.MappingPairsOffset;
|
|
|
|
if (!Attribute->IsNonResident)
|
|
return STATUS_INVALID_PARAMETER;
|
|
|
|
while (1)
|
|
{
|
|
DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
|
|
|
|
if (DataRunOffset != -1)
|
|
{
|
|
// Normal data run.
|
|
DataRunStartLCN = LastLCN + DataRunOffset;
|
|
LastLCN = DataRunStartLCN;
|
|
*LastCluster = LastLCN + DataRunLength - 1;
|
|
}
|
|
|
|
if (*DataRun == 0)
|
|
break;
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
PSTANDARD_INFORMATION
|
|
GetStandardInformationFromRecord(PDEVICE_EXTENSION Vcb,
|
|
PFILE_RECORD_HEADER FileRecord)
|
|
{
|
|
NTSTATUS Status;
|
|
FIND_ATTR_CONTXT Context;
|
|
PNTFS_ATTR_RECORD Attribute;
|
|
PSTANDARD_INFORMATION StdInfo;
|
|
|
|
Status = FindFirstAttribute(&Context, Vcb, FileRecord, FALSE, &Attribute);
|
|
while (NT_SUCCESS(Status))
|
|
{
|
|
if (Attribute->Type == AttributeStandardInformation)
|
|
{
|
|
StdInfo = (PSTANDARD_INFORMATION)((ULONG_PTR)Attribute + Attribute->Resident.ValueOffset);
|
|
FindCloseAttribute(&Context);
|
|
return StdInfo;
|
|
}
|
|
|
|
Status = FindNextAttribute(&Context, &Attribute);
|
|
}
|
|
|
|
FindCloseAttribute(&Context);
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* @name GetFileNameAttributeLength
|
|
* @implemented
|
|
*
|
|
* Returns the size of a given FILENAME_ATTRIBUTE, in bytes.
|
|
*
|
|
* @param FileNameAttribute
|
|
* Pointer to a FILENAME_ATTRIBUTE to determine the size of.
|
|
*
|
|
* @remarks
|
|
* The length of a FILENAME_ATTRIBUTE is variable and is dependent on the length of the file name stored at the end.
|
|
* This function operates on the FILENAME_ATTRIBUTE proper, so don't try to pass it a PNTFS_ATTR_RECORD.
|
|
*/
|
|
ULONG GetFileNameAttributeLength(PFILENAME_ATTRIBUTE FileNameAttribute)
|
|
{
|
|
ULONG Length = FIELD_OFFSET(FILENAME_ATTRIBUTE, Name) + (FileNameAttribute->NameLength * sizeof(WCHAR));
|
|
return Length;
|
|
}
|
|
|
|
PFILENAME_ATTRIBUTE
|
|
GetBestFileNameFromRecord(PDEVICE_EXTENSION Vcb,
|
|
PFILE_RECORD_HEADER FileRecord)
|
|
{
|
|
PFILENAME_ATTRIBUTE FileName;
|
|
|
|
FileName = GetFileNameFromRecord(Vcb, FileRecord, NTFS_FILE_NAME_POSIX);
|
|
if (FileName == NULL)
|
|
{
|
|
FileName = GetFileNameFromRecord(Vcb, FileRecord, NTFS_FILE_NAME_WIN32);
|
|
if (FileName == NULL)
|
|
{
|
|
FileName = GetFileNameFromRecord(Vcb, FileRecord, NTFS_FILE_NAME_DOS);
|
|
}
|
|
}
|
|
|
|
return FileName;
|
|
}
|
|
|
|
/* EOF */
|