/* FUNCTIONS ****************************************************************/
+/**
+* @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, 8);
+ 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 = 0;
+ WCHAR Buffer[MAX_PATH];
+
+ 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;
+
+ FileNameAttribute->FileAttributes = NTFS_FILE_TYPE_ARCHIVE;
+
+ // we need to extract the filename from the path
+ DPRINT1("Pathname: %wZ\n", &FileObject->FileName);
+
+ RtlInitEmptyUnicodeString(&FilenameNoPath, Buffer, MAX_PATH);
+
+ FsRtlDissectName(FileObject->FileName, &Current, &Remaining);
+
+ while (Current.Length != 0)
+ {
+ DPRINT1("Current: %wZ\n", &Current);
+
+ if(Remaining.Length != 0)
+ RtlCopyUnicodeString(&FilenameNoPath, &Remaining);
+
+ Status = NtfsFindMftRecord(DeviceExt,
+ CurrentMFTIndex,
+ &Current,
+ &FirstEntry,
+ FALSE,
+ CaseSensitive,
+ &CurrentMFTIndex);
+ if (!NT_SUCCESS(Status))
+ break;
+
+ if (Remaining.Length == 0 )
+ {
+ if(Current.Length != 0)
+ RtlCopyUnicodeString(&FilenameNoPath, &Current);
+ break;
+ }
+
+ FsRtlDissectName(Current, &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 (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, 8);
+
+ AttributeAddress->Resident.ValueLength = FIELD_OFFSET(FILENAME_ATTRIBUTE, Name) + FilenameNoPath.Length;
+ AttributeAddress->Resident.ValueOffset = ResidentHeaderLength;
+ AttributeAddress->Resident.Flags = 1; // 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 AddRun
* @implemented
*
* @return
* STATUS_SUCCESS on success. STATUS_INVALID_PARAMETER if AttrContext describes a resident attribute.
-* STATUS_INSUFFICIENT_RESOURCES if ConvertDataRunsToLargeMCB() fails.
+* 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).
*
ULONG RunLength)
{
NTSTATUS Status;
- PUCHAR DataRun = (PUCHAR)&AttrContext->Record + AttrContext->Record.NonResident.MappingPairsOffset;
int DataRunMaxLength;
PNTFS_ATTR_RECORD DestinationAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttrOffset);
- LARGE_MCB DataRunsMCB;
ULONG NextAttributeOffset = AttrOffset + AttrContext->Record.Length;
- ULONGLONG NextVBN = AttrContext->Record.NonResident.LowestVCN;
+ ULONGLONG NextVBN = 0;
- // Allocate some memory for the RunBuffer
PUCHAR RunBuffer;
- ULONG RunBufferOffset = 0;
+ ULONG RunBufferSize;
if (!AttrContext->Record.IsNonResident)
return STATUS_INVALID_PARAMETER;
- RunBuffer = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
-
- // Convert the data runs to a map control block
- Status = ConvertDataRunsToLargeMCB(DataRun, &DataRunsMCB, &NextVBN);
- if (!NT_SUCCESS(Status))
- {
- DPRINT1("Unable to convert data runs to MCB (probably ran out of memory)!\n");
- ExFreePoolWithTag(RunBuffer, TAG_NTFS);
- return Status;
- }
+ if (AttrContext->Record.NonResident.AllocatedSize != 0)
+ NextVBN = AttrContext->Record.NonResident.HighestVCN + 1;
// Add newly-assigned clusters to mcb
_SEH2_TRY{
- if (!FsRtlAddLargeMcbEntry(&DataRunsMCB,
+ if (!FsRtlAddLargeMcbEntry(&AttrContext->DataRunsMCB,
NextVBN,
NextAssignedCluster,
RunLength))
{
- FsRtlUninitializeLargeMcb(&DataRunsMCB);
- ExFreePoolWithTag(RunBuffer, TAG_NTFS);
- return STATUS_INSUFFICIENT_RESOURCES;
+ ExRaiseStatus(STATUS_UNSUCCESSFUL);
}
} _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
- FsRtlUninitializeLargeMcb(&DataRunsMCB);
- ExFreePoolWithTag(RunBuffer, TAG_NTFS);
- _SEH2_YIELD(return STATUS_INSUFFICIENT_RESOURCES);
+ _SEH2_YIELD(_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(&DataRunsMCB, RunBuffer, Vcb->NtfsInfo.BytesPerCluster, &RunBufferOffset);
+ 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->Record.Length - AttrContext->Record.NonResident.MappingPairsOffset;
// Do we need to extend the attribute (or convert to attribute list)?
- if (DataRunMaxLength < RunBufferOffset)
+ if (DataRunMaxLength < RunBufferSize)
{
PNTFS_ATTR_RECORD NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + NextAttributeOffset);
DataRunMaxLength += Vcb->NtfsInfo.BytesPerFileRecord - NextAttributeOffset - (sizeof(ULONG) * 2);
// Can we move the end of the attribute?
- if (NextAttribute->Type != AttributeEnd || DataRunMaxLength < RunBufferOffset - 1)
+ if (NextAttribute->Type != AttributeEnd || DataRunMaxLength < RunBufferSize - 1)
{
DPRINT1("FIXME: Need to create attribute list! Max Data Run Length available: %d\n", DataRunMaxLength);
if (NextAttribute->Type != AttributeEnd)
DPRINT1("There's another attribute after this one with type %0xlx\n", NextAttribute->Type);
ExFreePoolWithTag(RunBuffer, TAG_NTFS);
- FsRtlUninitializeLargeMcb(&DataRunsMCB);
return STATUS_NOT_IMPLEMENTED;
}
// calculate position of end markers
- NextAttributeOffset = AttrOffset + AttrContext->Record.NonResident.MappingPairsOffset + RunBufferOffset;
+ NextAttributeOffset = AttrOffset + AttrContext->Record.NonResident.MappingPairsOffset + RunBufferSize;
NextAttributeOffset = ALIGN_UP_BY(NextAttributeOffset, 8);
- // Write the end markers
- NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + NextAttributeOffset);
- NextAttribute->Type = AttributeEnd;
- NextAttribute->Length = FILE_RECORD_END;
-
// Update the length
DestinationAttribute->Length = NextAttributeOffset - AttrOffset;
AttrContext->Record.Length = DestinationAttribute->Length;
- // We need to increase the FileRecord size
- FileRecord->BytesInUse = NextAttributeOffset + (sizeof(ULONG) * 2);
+ // End the file record
+ NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + NextAttributeOffset);
+ SetFileRecordEnd(FileRecord, NextAttribute, FILE_RECORD_END);
}
- // NOTE: from this point on the original attribute record will contain invalid data in it's runbuffer
- // TODO: Elegant fix? Could we free the old Record and allocate a new one without issue?
-
// Update HighestVCN
DestinationAttribute->NonResident.HighestVCN =
- AttrContext->Record.NonResident.HighestVCN = max(NextVBN - 1 + RunLength,
+ AttrContext->Record.NonResident.HighestVCN = max(NextVBN - 1 + RunLength,
AttrContext->Record.NonResident.HighestVCN);
// Write data runs to destination attribute
RtlCopyMemory((PVOID)((ULONG_PTR)DestinationAttribute + DestinationAttribute->NonResident.MappingPairsOffset),
RunBuffer,
- RunBufferOffset);
+ RunBufferSize);
// Update the file record
Status = UpdateFileRecord(Vcb, AttrContext->FileMFTIndex, FileRecord);
ExFreePoolWithTag(RunBuffer, TAG_NTFS);
- FsRtlUninitializeLargeMcb(&DataRunsMCB);
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, 8);
+ 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
* Pointer to an unitialized LARGE_MCB structure.
*
* @return
-* STATUS_SUCCESS on success, STATUS_INSUFFICIENT_RESOURCES if we fail to
+* STATUS_SUCCESS on success, STATUS_INSUFFICIENT_RESOURCES or STATUS_UNSUCCESSFUL if we fail to
* initialize the mcb or add an entry.
*
* @remarks
_SEH2_TRY{
FsRtlInitializeLargeMcb(DataRunsMCB, NonPagedPool);
} _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
- _SEH2_YIELD(return STATUS_INSUFFICIENT_RESOURCES);
+ _SEH2_YIELD(return _SEH2_GetExceptionCode());
} _SEH2_END;
while (*DataRun != 0)
DataRunStartLCN,
DataRunLength))
{
- FsRtlUninitializeLargeMcb(DataRunsMCB);
- return STATUS_INSUFFICIENT_RESOURCES;
+ ExRaiseStatus(STATUS_UNSUCCESSFUL);
}
} _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
FsRtlUninitializeLargeMcb(DataRunsMCB);
- _SEH2_YIELD(return STATUS_INSUFFICIENT_RESOURCES);
+ _SEH2_YIELD(return _SEH2_GetExceptionCode());
} _SEH2_END;
}
LONGLONG DataRunOffset;
ULONGLONG LastLCN = 0;
LONGLONG Vbn, Lbn, Count;
- int i;
+ ULONG i;
DPRINT("\t[Vbn, Lbn, Count]\n");
DataRunLengthSize = GetPackedByteCount(Count, TRUE);
DPRINT("%d bytes needed.\n", DataRunLengthSize);
- // ensure the next data run + end marker would be > Max buffer size
+ // ensure the next data run + end marker would be <= Max buffer size
if (RunBufferOffset + 2 + DataRunLengthSize + DataRunOffsetSize > MaxBufferSize)
{
Status = STATUS_BUFFER_TOO_SMALL;
* @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 ConvertDataRunsToLargeMCB() fails.
+* STATUS_INSUFFICIENT_RESOURCES if allocating a buffer for the data runs fails or
+* if ConvertDataRunsToLargeMCB() fails.
* STATUS_BUFFER_TOO_SMALL if ConvertLargeMCBToDataRuns() fails.
*
*
NTSTATUS Status = STATUS_SUCCESS;
ULONG ClustersLeftToFree = ClustersToFree;
- // convert data runs to mcb
- PUCHAR DataRun = (PUCHAR)&AttrContext->Record + AttrContext->Record.NonResident.MappingPairsOffset;
PNTFS_ATTR_RECORD DestinationAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttrOffset);
- LARGE_MCB DataRunsMCB;
ULONG NextAttributeOffset = AttrOffset + AttrContext->Record.Length;
PNTFS_ATTR_RECORD NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + NextAttributeOffset);
- ULONGLONG NextVBN = AttrContext->Record.NonResident.LowestVCN;
- // Allocate some memory for the RunBuffer
- PUCHAR RunBuffer = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
- ULONG RunBufferOffset = 0;
+ PUCHAR RunBuffer;
+ ULONG RunBufferSize = 0;
PFILE_RECORD_HEADER BitmapRecord;
PNTFS_ATTR_CONTEXT DataContext;
if (!AttrContext->Record.IsNonResident)
{
- ExFreePoolWithTag(RunBuffer, TAG_NTFS);
return STATUS_INVALID_PARAMETER;
}
- // Convert the data runs to a map control block
- Status = ConvertDataRunsToLargeMCB(DataRun, &DataRunsMCB, &NextVBN);
- if (!NT_SUCCESS(Status))
- {
- DPRINT1("Unable to convert data runs to MCB (probably ran out of memory)!\n");
- ExFreePoolWithTag(RunBuffer, TAG_NTFS);
- return Status;
- }
-
+ // Read the $Bitmap file
BitmapRecord = ExAllocatePoolWithTag(NonPagedPool,
Vcb->NtfsInfo.BytesPerFileRecord,
TAG_NTFS);
if (BitmapRecord == NULL)
{
DPRINT1("Error: Unable to allocate memory for bitmap file record!\n");
- FsRtlUninitializeLargeMcb(&DataRunsMCB);
- ExFreePoolWithTag(RunBuffer, TAG_NTFS);
return STATUS_NO_MEMORY;
}
if (!NT_SUCCESS(Status))
{
DPRINT1("Error: Unable to read file record for bitmap!\n");
- FsRtlUninitializeLargeMcb(&DataRunsMCB);
ExFreePoolWithTag(BitmapRecord, TAG_NTFS);
- ExFreePoolWithTag(RunBuffer, TAG_NTFS);
return 0;
}
if (!NT_SUCCESS(Status))
{
DPRINT1("Error: Unable to find data attribute for bitmap file!\n");
- FsRtlUninitializeLargeMcb(&DataRunsMCB);
ExFreePoolWithTag(BitmapRecord, TAG_NTFS);
- ExFreePoolWithTag(RunBuffer, TAG_NTFS);
return 0;
}
{
DPRINT1("Error: Unable to allocate memory for bitmap file data!\n");
ReleaseAttributeContext(DataContext);
- FsRtlUninitializeLargeMcb(&DataRunsMCB);
ExFreePoolWithTag(BitmapRecord, TAG_NTFS);
- ExFreePoolWithTag(RunBuffer, TAG_NTFS);
return 0;
}
{
LONGLONG LargeVbn, LargeLbn;
- if (!FsRtlLookupLastLargeMcbEntry(&DataRunsMCB, &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!",
// deallocate this cluster
RtlClearBits(&Bitmap, LargeLbn, 1);
}
- FsRtlTruncateLargeMcb(&DataRunsMCB, AttrContext->Record.NonResident.HighestVCN);
+ FsRtlTruncateLargeMcb(&AttrContext->DataRunsMCB, AttrContext->Record.NonResident.HighestVCN);
+
+ // decrement HighestVCN, but don't let it go below 0
AttrContext->Record.NonResident.HighestVCN = min(AttrContext->Record.NonResident.HighestVCN, AttrContext->Record.NonResident.HighestVCN - 1);
ClustersLeftToFree--;
}
if (!NT_SUCCESS(Status))
{
ReleaseAttributeContext(DataContext);
- FsRtlUninitializeLargeMcb(&DataRunsMCB);
ExFreePoolWithTag(BitmapData, TAG_NTFS);
ExFreePoolWithTag(BitmapRecord, TAG_NTFS);
- ExFreePoolWithTag(RunBuffer, TAG_NTFS);
return Status;
}
ReleaseAttributeContext(DataContext);
ExFreePoolWithTag(BitmapData, TAG_NTFS);
ExFreePoolWithTag(BitmapRecord, TAG_NTFS);
+
+ // 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(&DataRunsMCB, RunBuffer, Vcb->NtfsInfo.BytesPerCluster, &RunBufferOffset);
+ ConvertLargeMCBToDataRuns(&AttrContext->DataRunsMCB, RunBuffer, Vcb->NtfsInfo.BytesPerCluster, &RunBufferSize);
// Update HighestVCN
DestinationAttribute->NonResident.HighestVCN = AttrContext->Record.NonResident.HighestVCN;
// Write data runs to destination attribute
RtlCopyMemory((PVOID)((ULONG_PTR)DestinationAttribute + DestinationAttribute->NonResident.MappingPairsOffset),
RunBuffer,
- RunBufferOffset);
+ RunBufferSize);
+ // Is DestinationAttribute the last attribute in the file record?
if (NextAttribute->Type == AttributeEnd)
{
// update attribute length
- AttrContext->Record.Length = ALIGN_UP_BY(AttrContext->Record.NonResident.MappingPairsOffset + RunBufferOffset, 8);
+ AttrContext->Record.Length = ALIGN_UP_BY(AttrContext->Record.NonResident.MappingPairsOffset + RunBufferSize, 8);
DestinationAttribute->Length = AttrContext->Record.Length;
// write end markers
NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)DestinationAttribute + DestinationAttribute->Length);
- NextAttribute->Type = AttributeEnd;
- NextAttribute->Length = FILE_RECORD_END;
-
- // update file record length
- FileRecord->BytesInUse = AttrOffset + DestinationAttribute->Length + (sizeof(ULONG) * 2);
+ SetFileRecordEnd(FileRecord, NextAttribute, FILE_RECORD_END);
}
// Update the file record
Status = UpdateFileRecord(Vcb, AttrContext->FileMFTIndex, FileRecord);
- FsRtlUninitializeLargeMcb(&DataRunsMCB);
ExFreePoolWithTag(RunBuffer, TAG_NTFS);
NtfsDumpDataRuns((PUCHAR)((ULONG_PTR)DestinationAttribute + DestinationAttribute->NonResident.MappingPairsOffset), 0);
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);
}
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 (%uB, %u) ", IndexRootAttr->SizeOfEntry, IndexRootAttr->ClustersPerIndexRecord);
+ DbgPrint(" $INDEX_ROOT (%u bytes per index record, %u clusters) ", IndexRootAttr->SizeOfEntry, IndexRootAttr->ClustersPerIndexRecord);
if (IndexRootAttr->Header.Flags == INDEX_ROOT_SMALL)
{
- DbgPrint(" (small) ");
+ DbgPrint(" (small)\n");
}
else
{
ASSERT(IndexRootAttr->Header.Flags == INDEX_ROOT_LARGE);
- DbgPrint(" (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 (currentIndexExtry->Flags & NTFS_INDEX_ENTRY_NODE)
+ DbgPrint(" (Branch)");
+ else
+ DbgPrint(" (Leaf)");
+ if((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 - 8);
+ DbgPrint(" VCN of sub-node: 0x%llx\n", *SubNodeVCN);
+ }
+
+ currentOffset += currentIndexExtry->Length;
+ ASSERT(currentIndexExtry->Length);
}
+
}