{
LONGLONG DataRunOffset;
ULONGLONG DataRunLength;
+ ULONGLONG NextVBN = 0;
+ PUCHAR DataRun = (PUCHAR)&Context->Record + Context->Record.NonResident.MappingPairsOffset;
- Context->CacheRun = (PUCHAR)&Context->Record + Context->Record.NonResident.MappingPairsOffset;
+ Context->CacheRun = DataRun;
Context->CacheRunOffset = 0;
Context->CacheRun = DecodeRun(Context->CacheRun, &DataRunOffset, &DataRunLength);
Context->CacheRunLength = DataRunLength;
Context->CacheRunLastLCN = 0;
}
Context->CacheRunCurrentOffset = 0;
+
+ // Convert the data runs to a map control block
+ if (!NT_SUCCESS(ConvertDataRunsToLargeMCB(DataRun, &Context->DataRunsMCB, &NextVBN)))
+ {
+ DPRINT1("Unable to convert data runs to MCB!\n");
+ ExFreePoolWithTag(Context, TAG_NTFS);
+ return NULL;
+ }
}
return Context;
VOID
ReleaseAttributeContext(PNTFS_ATTR_CONTEXT Context)
{
+ if (Context->Record.IsNonResident)
+ {
+ FsRtlUninitializeLargeMcb(&Context->DataRunsMCB);
+ }
+
ExFreePoolWithTag(Context, TAG_NTFS);
}
return AttrRecord->Resident.ValueLength;
}
-void
+VOID
InternalSetResidentAttributeLength(PNTFS_ATTR_CONTEXT AttrContext,
PFILE_RECORD_HEADER FileRecord,
ULONG AttrOffset,
Destination->Length += Padding;
}
- // advance Destination to the final "attribute" and write the end type
+ // advance Destination to the final "attribute" and set the file record end
Destination = (PNTFS_ATTR_RECORD)((ULONG_PTR)Destination + Destination->Length);
- Destination->Type = AttributeEnd;
-
- // write the final marker (which shares the same offset and type as the Length field)
- Destination->Length = FILE_RECORD_END;
-
- FileRecord->BytesInUse = NextAttributeOffset + (sizeof(ULONG) * 2);
+ SetFileRecordEnd(FileRecord, Destination, FILE_RECORD_END);
}
/**
ULONG NextAssignedCluster;
ULONG AssignedClusters;
- NTSTATUS Status = GetLastClusterInDataRun(Fcb->Vcb, &AttrContext->Record, (PULONGLONG)&LastClusterInDataRun.QuadPart);
+ if (ExistingClusters == 0)
+ {
+ LastClusterInDataRun.QuadPart = 0;
+ }
+ else
+ {
+ if (!FsRtlLookupLargeMcbEntry(&AttrContext->DataRunsMCB,
+ (LONGLONG)AttrContext->Record.NonResident.HighestVCN,
+ (PLONGLONG)&LastClusterInDataRun.QuadPart,
+ NULL,
+ NULL,
+ NULL,
+ NULL))
+ {
+ DPRINT1("Error looking up final large MCB entry!\n");
+
+ // Most likely, HighestVCN went above the largest mapping
+ DPRINT1("Highest VCN of record: %I64u\n", AttrContext->Record.NonResident.HighestVCN);
+ return STATUS_INVALID_PARAMETER;
+ }
+ }
- DPRINT1("GetLastClusterInDataRun returned: %I64u\n", LastClusterInDataRun.QuadPart);
- DPRINT1("Highest VCN of record: %I64u\n", AttrContext->Record.NonResident.HighestVCN);
+ DPRINT("LastClusterInDataRun: %I64u\n", LastClusterInDataRun.QuadPart);
+ DPRINT("Highest VCN of record: %I64u\n", AttrContext->Record.NonResident.HighestVCN);
while (ClustersNeeded > 0)
{
if (MaxValueLength < DataSize->LowPart || NextAttribute->Type != AttributeEnd)
{
- DPRINT1("FIXME: Need to convert attribute to non-resident!\n");
- return STATUS_NOT_IMPLEMENTED;
+ // convert attribute to non-resident
+ PNTFS_ATTR_RECORD Destination = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttrOffset);
+ LARGE_INTEGER AttribDataSize;
+ PVOID AttribData;
+ ULONG EndAttributeOffset;
+ ULONG LengthWritten;
+
+ DPRINT1("Converting attribute to non-resident.\n");
+
+ AttribDataSize.QuadPart = AttrContext->Record.Resident.ValueLength;
+
+ // Is there existing data we need to back-up?
+ if (AttribDataSize.QuadPart > 0)
+ {
+ AttribData = ExAllocatePoolWithTag(NonPagedPool, AttribDataSize.QuadPart, TAG_NTFS);
+ if (AttribData == NULL)
+ {
+ DPRINT1("ERROR: Couldn't allocate memory for attribute data. Can't migrate to non-resident!\n");
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ // read data to temp buffer
+ Status = ReadAttribute(Fcb->Vcb, AttrContext, 0, AttribData, AttribDataSize.QuadPart);
+ if (!NT_SUCCESS(Status))
+ {
+ DPRINT1("ERROR: Unable to read attribute before migrating!\n");
+ ExFreePoolWithTag(AttribData, TAG_NTFS);
+ return Status;
+ }
+ }
+
+ // Start by turning this attribute into a 0-length, non-resident attribute, then enlarge it.
+
+ // Zero out the NonResident structure
+ RtlZeroMemory(&AttrContext->Record.NonResident.LowestVCN,
+ FIELD_OFFSET(NTFS_ATTR_RECORD, NonResident.CompressedSize) - FIELD_OFFSET(NTFS_ATTR_RECORD, NonResident.LowestVCN));
+ RtlZeroMemory(&Destination->NonResident.LowestVCN,
+ FIELD_OFFSET(NTFS_ATTR_RECORD, NonResident.CompressedSize) - FIELD_OFFSET(NTFS_ATTR_RECORD, NonResident.LowestVCN));
+
+ // update the mapping pairs offset, which will be 0x40 + length in bytes of the name
+ AttrContext->Record.NonResident.MappingPairsOffset = Destination->NonResident.MappingPairsOffset = 0x40 + (Destination->NameLength * 2);
+
+ // mark the attribute as non-resident
+ AttrContext->Record.IsNonResident = Destination->IsNonResident = 1;
+
+ // update the end of the file record
+ // calculate position of end markers (1 byte for empty data run)
+ EndAttributeOffset = AttrOffset + AttrContext->Record.NonResident.MappingPairsOffset + 1;
+ EndAttributeOffset = ALIGN_UP_BY(EndAttributeOffset, 8);
+
+ // Update the length
+ Destination->Length = EndAttributeOffset - AttrOffset;
+ AttrContext->Record.Length = Destination->Length;
+
+ // Update the file record end
+ SetFileRecordEnd(FileRecord,
+ (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + EndAttributeOffset),
+ FILE_RECORD_END);
+
+ // update file record on disk
+ Status = UpdateFileRecord(Fcb->Vcb, AttrContext->FileMFTIndex, FileRecord);
+ if (!NT_SUCCESS(Status))
+ {
+ DPRINT1("ERROR: Couldn't update file record to continue migration!\n");
+ if (AttribDataSize.QuadPart > 0)
+ ExFreePoolWithTag(AttribData, TAG_NTFS);
+ return Status;
+ }
+
+ // Initialize the MCB, potentially catch an exception
+ _SEH2_TRY{
+ FsRtlInitializeLargeMcb(&AttrContext->DataRunsMCB, NonPagedPool);
+ } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
+ _SEH2_YIELD(return _SEH2_GetExceptionCode());
+ } _SEH2_END;
+
+ // Now we can treat the attribute as non-resident and enlarge it normally
+ Status = SetAttributeDataLength(FileObject, Fcb, AttrContext, AttrOffset, FileRecord, DataSize);
+ if (!NT_SUCCESS(Status))
+ {
+ DPRINT1("ERROR: Unable to migrate resident attribute!\n");
+ if (AttribDataSize.QuadPart > 0)
+ ExFreePoolWithTag(AttribData, TAG_NTFS);
+ return Status;
+ }
+
+ // restore the back-up attribute, if we made one
+ if (AttribDataSize.QuadPart > 0)
+ {
+ Status = WriteAttribute(Fcb->Vcb, AttrContext, 0, AttribData, AttribDataSize.QuadPart, &LengthWritten);
+ if (!NT_SUCCESS(Status))
+ {
+ DPRINT1("ERROR: Unable to write attribute data to non-resident clusters during migration!\n");
+ // TODO: Reverse migration so no data is lost
+ ExFreePoolWithTag(AttribData, TAG_NTFS);
+ return Status;
+ }
+
+ ExFreePoolWithTag(AttribData, TAG_NTFS);
+ }
}
}
}
}
}
- InternalSetResidentAttributeLength(AttrContext, FileRecord, AttrOffset, DataSize->LowPart);
+ // set the new length of the resident attribute (if we didn't migrate it)
+ if(!AttrContext->Record.IsNonResident)
+ InternalSetResidentAttributeLength(AttrContext, FileRecord, AttrOffset, DataSize->LowPart);
}
//NtfsDumpFileAttributes(Fcb->Vcb, FileRecord);
return STATUS_SUCCESS;
}
+/**
+* @name SetFileRecordEnd
+* @implemented
+*
+* This small function sets a new endpoint for the file record. It set's the final
+* AttrEnd->Type to AttributeEnd and recalculates the bytes used by the file record.
+*
+* @param FileRecord
+* Pointer to the file record whose endpoint (length) will be set.
+*
+* @param AttrEnd
+* Pointer to section of memory that will receive the AttributeEnd marker. This must point
+* to memory allocated for the FileRecord. Must be aligned to an 8-byte boundary (relative to FileRecord).
+*
+* @param EndMarker
+* This value will be written after AttributeEnd but isn't critical at all. When Windows resizes
+* a file record, it preserves the final ULONG that previously ended the record, even though this
+* value is (to my knowledge) never used. We emulate this behavior.
+*
+*/
+VOID
+SetFileRecordEnd(PFILE_RECORD_HEADER FileRecord,
+ PNTFS_ATTR_RECORD AttrEnd,
+ ULONG EndMarker)
+{
+ // mark the end of attributes
+ AttrEnd->Type = AttributeEnd;
+
+ // Restore the "file-record-end marker." The value is never checked but this behavior is consistent with Win2k3.
+ AttrEnd->Length = EndMarker;
+
+ // recalculate bytes in use
+ FileRecord->BytesInUse = (ULONG_PTR)AttrEnd - (ULONG_PTR)FileRecord + sizeof(ULONG) * 2;
+}
+
ULONG
ReadAttribute(PDEVICE_EXTENSION Vcb,
PNTFS_ATTR_CONTEXT Context,
ULONG ReadLength;
ULONG AlreadyRead;
NTSTATUS Status;
+
+ //TEMPTEMP
+ PUCHAR TempBuffer;
if (!Context->Record.IsNonResident)
{
}
else
{
+ //TEMPTEMP
+ ULONG UsedBufferSize;
+ TempBuffer = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
+
LastLCN = 0;
- DataRun = (PUCHAR)&Context->Record + Context->Record.NonResident.MappingPairsOffset;
CurrentOffset = 0;
+ // This will be rewritten in the next iteration to just use the DataRuns MCB directly
+ ConvertLargeMCBToDataRuns(&Context->DataRunsMCB,
+ TempBuffer,
+ Vcb->NtfsInfo.BytesPerFileRecord,
+ &UsedBufferSize);
+
+ DataRun = TempBuffer;
+
while (1)
{
DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
} /* if Disk */
+ // TEMPTEMP
+ if (Context->Record.IsNonResident)
+ ExFreePoolWithTag(TempBuffer, TAG_NTFS);
+
Context->CacheRun = DataRun;
Context->CacheRunOffset = Offset + AlreadyRead;
Context->CacheRunStartLCN = DataRunStartLCN;
NTSTATUS Status;
PUCHAR SourceBuffer = Buffer;
LONGLONG StartingOffset;
+
+ //TEMPTEMP
+ PUCHAR TempBuffer;
+
DPRINT("WriteAttribute(%p, %p, %I64u, %p, %lu, %p)\n", Vcb, Context, Offset, Buffer, Length, RealLengthWritten);
}
else*/
{
+ ULONG UsedBufferSize;
LastLCN = 0;
- DataRun = (PUCHAR)&Context->Record + Context->Record.NonResident.MappingPairsOffset;
- CurrentOffset = 0;
+ CurrentOffset = 0;
+
+ // This will be rewritten in the next iteration to just use the DataRuns MCB directly
+ TempBuffer = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
+
+ ConvertLargeMCBToDataRuns(&Context->DataRunsMCB,
+ TempBuffer,
+ Vcb->NtfsInfo.BytesPerFileRecord,
+ &UsedBufferSize);
+
+ DataRun = TempBuffer;
while (1)
{
{
// We reached the last assigned cluster
// TODO: assign new clusters to the end of the file.
- // (Presently, this code will never be reached, the write should have already failed by now)
+ // (Presently, this code will rarely be reached, the write will usually have already failed by now)
+ // [We can reach here by creating a new file record when the MFT isn't large enough]
+ DPRINT1("FIXME: Master File Table needs to be enlarged.\n");
return STATUS_END_OF_FILE;
}
}
} // end while (Length > 0) [more data to write]
+ // TEMPTEMP
+ if(Context->Record.IsNonResident)
+ ExFreePoolWithTag(TempBuffer, TAG_NTFS);
+
Context->CacheRun = DataRun;
Context->CacheRunOffset = Offset + *RealLengthWritten;
Context->CacheRunStartLCN = DataRunStartLCN;
}
/**
-* UpdateFileRecord
+* @name UpdateFileRecord
* @implemented
+*
* Writes a file record to the master file table, at a given index.
+*
+* @param Vcb
+* Pointer to the DEVICE_EXTENSION of the target drive being written to.
+*
+* @param MftIndex
+* Target index in the master file table to store the file record.
+*
+* @param FileRecord
+* Pointer to the complete file record which will be written to the master file table.
+*
+* @return
+* STATUS_SUCCESSFUL on success. An error passed from WriteAttribute() otherwise.
+*
*/
NTSTATUS
UpdateFileRecord(PDEVICE_EXTENSION Vcb,
- ULONGLONG index,
- PFILE_RECORD_HEADER file)
+ ULONGLONG MftIndex,
+ PFILE_RECORD_HEADER FileRecord)
{
ULONG BytesWritten;
NTSTATUS Status = STATUS_SUCCESS;
- DPRINT("UpdateFileRecord(%p, %I64x, %p)\n", Vcb, index, file);
+ DPRINT("UpdateFileRecord(%p, 0x%I64x, %p)\n", Vcb, MftIndex, FileRecord);
// Add the fixup array to prepare the data for writing to disk
- AddFixupArray(Vcb, &file->Ntfs);
+ AddFixupArray(Vcb, &FileRecord->Ntfs);
// write the file record to the master file table
- Status = WriteAttribute(Vcb, Vcb->MFTContext, index * Vcb->NtfsInfo.BytesPerFileRecord, (const PUCHAR)file, Vcb->NtfsInfo.BytesPerFileRecord, &BytesWritten);
+ Status = WriteAttribute(Vcb, Vcb->MFTContext, MftIndex * Vcb->NtfsInfo.BytesPerFileRecord, (const PUCHAR)FileRecord, Vcb->NtfsInfo.BytesPerFileRecord, &BytesWritten);
if (!NT_SUCCESS(Status))
{
- DPRINT1("UpdateFileRecord failed: %I64u written, %lu expected\n", BytesWritten, Vcb->NtfsInfo.BytesPerFileRecord);
+ DPRINT1("UpdateFileRecord failed: %lu written, %lu expected\n", BytesWritten, Vcb->NtfsInfo.BytesPerFileRecord);
}
// remove the fixup array (so the file record pointer can still be used)
- FixupUpdateSequenceArray(Vcb, &file->Ntfs);
+ FixupUpdateSequenceArray(Vcb, &FileRecord->Ntfs);
return Status;
}
return STATUS_SUCCESS;
}
+/**
+* @name AddNewMftEntry
+* @implemented
+*
+* Adds a file record to the master file table of a given device.
+*
+* @param FileRecord
+* Pointer to a complete file record which will be saved to disk.
+*
+* @param DeviceExt
+* Pointer to the DEVICE_EXTENSION of the target drive.
+*
+* @return
+* STATUS_SUCCESS on success.
+* STATUS_OBJECT_NAME_NOT_FOUND if we can't find the MFT's $Bitmap or if we weren't able
+* to read the attribute.
+* STATUS_INSUFFICIENT_RESOURCES if we can't allocate enough memory for a copy of $Bitmap.
+* STATUS_NOT_IMPLEMENTED if we need to increase the size of the MFT.
+*
+*/
+NTSTATUS
+AddNewMftEntry(PFILE_RECORD_HEADER FileRecord,
+ PDEVICE_EXTENSION DeviceExt)
+{
+ NTSTATUS Status = STATUS_SUCCESS;
+ ULONGLONG MftIndex;
+ RTL_BITMAP Bitmap;
+ ULONGLONG BitmapDataSize;
+ ULONGLONG AttrBytesRead;
+ PVOID BitmapData;
+ ULONG LengthWritten;
+
+ // First, we have to read the mft's $Bitmap attribute
+ PNTFS_ATTR_CONTEXT BitmapContext;
+ Status = FindAttribute(DeviceExt, DeviceExt->MasterFileTable, AttributeBitmap, L"", 0, &BitmapContext, NULL);
+ if (!NT_SUCCESS(Status))
+ {
+ DPRINT1("ERROR: Couldn't find $Bitmap attribute of master file table!\n");
+ return Status;
+ }
+
+ // allocate a buffer for the $Bitmap attribute
+ BitmapDataSize = AttributeDataLength(&BitmapContext->Record);
+ BitmapData = ExAllocatePoolWithTag(NonPagedPool, BitmapDataSize, TAG_NTFS);
+ if (!BitmapData)
+ {
+ ReleaseAttributeContext(BitmapContext);
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ // read $Bitmap attribute
+ AttrBytesRead = ReadAttribute(DeviceExt, BitmapContext, 0, BitmapData, BitmapDataSize);
+
+ if (AttrBytesRead == 0)
+ {
+ DPRINT1("ERROR: Unable to read $Bitmap attribute of master file table!\n");
+ ExFreePoolWithTag(BitmapData, TAG_NTFS);
+ ReleaseAttributeContext(BitmapContext);
+ return STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ // convert buffer into bitmap
+ RtlInitializeBitMap(&Bitmap, (PULONG)BitmapData, BitmapDataSize * 8);
+
+ // set next available bit, preferrably after 23rd bit
+ MftIndex = RtlFindClearBitsAndSet(&Bitmap, 1, 24);
+ if ((LONG)MftIndex == -1)
+ {
+ DPRINT1("ERROR: Couldn't find free space in MFT for file record!\n");
+
+ ExFreePoolWithTag(BitmapData, TAG_NTFS);
+ ReleaseAttributeContext(BitmapContext);
+
+ // TODO: increase mft size
+ return STATUS_NOT_IMPLEMENTED;
+ }
+
+ DPRINT1("Creating file record at MFT index: %I64u\n", MftIndex);
+
+ // update file record with index
+ FileRecord->MFTRecordNumber = MftIndex;
+
+ // [BitmapData should have been updated via RtlFindClearBitsAndSet()]
+
+ // write the bitmap back to the MFT's $Bitmap attribute
+ Status = WriteAttribute(DeviceExt, BitmapContext, 0, BitmapData, BitmapDataSize, &LengthWritten);
+ if (!NT_SUCCESS(Status))
+ {
+ DPRINT1("ERROR encountered when writing $Bitmap attribute!\n");
+ ExFreePoolWithTag(BitmapData, TAG_NTFS);
+ ReleaseAttributeContext(BitmapContext);
+ return Status;
+ }
+
+ // update the file record (write it to disk)
+ Status = UpdateFileRecord(DeviceExt, MftIndex, FileRecord);
+
+ if (!NT_SUCCESS(Status))
+ {
+ DPRINT1("ERROR: Unable to write file record!\n");
+ ExFreePoolWithTag(BitmapData, TAG_NTFS);
+ ReleaseAttributeContext(BitmapContext);
+ return Status;
+ }
+
+ ExFreePoolWithTag(BitmapData, TAG_NTFS);
+ ReleaseAttributeContext(BitmapContext);
+
+ return Status;
+}
+
NTSTATUS
AddFixupArray(PDEVICE_EXTENSION Vcb,
PNTFS_RECORD_HEADER Record)
return NtfsLookupFileAt(Vcb, PathName, FileRecord, MFTIndex, NTFS_FILE_ROOT);
}
+/**
+* @name NtfsDumpFileRecord
+* @implemented
+*
+* Provides diagnostic information about a file record. Prints a hex dump
+* of the entire record (based on the size reported by FileRecord->ByesInUse),
+* then prints a dump of each attribute.
+*
+* @param Vcb
+* Pointer to a DEVICE_EXTENSION describing the volume.
+*
+* @param FileRecord
+* Pointer to the file record to be analyzed.
+*
+* @remarks
+* FileRecord must be a complete file record at least FileRecord->BytesAllocated
+* in size, and not just the header.
+*
+*/
+VOID
+NtfsDumpFileRecord(PDEVICE_EXTENSION Vcb,
+ PFILE_RECORD_HEADER FileRecord)
+{
+ ULONG i, j;
+
+ // dump binary data, 8 bytes at a time
+ for (i = 0; i < FileRecord->BytesInUse; i += 8)
+ {
+ // display current offset, in hex
+ DbgPrint("\t%03x\t", i);
+
+ // display hex value of each of the next 8 bytes
+ for (j = 0; j < 8; j++)
+ DbgPrint("%02x ", *(PUCHAR)((ULONG_PTR)FileRecord + i + j));
+ DbgPrint("\n");
+ }
+
+ NtfsDumpFileAttributes(Vcb, FileRecord);
+}
+
NTSTATUS
NtfsFindFileAt(PDEVICE_EXTENSION Vcb,
PUNICODE_STRING SearchPattern,