1 /* 2 * ReactOS kernel 3 * Copyright (C) 2002, 2014 ReactOS Team 4 * 5 * This program is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation; either version 2 of the License, or 8 * (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program; if not, write to the Free Software 17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. 18 * 19 * COPYRIGHT: See COPYING in the top level directory 20 * PROJECT: ReactOS kernel 21 * FILE: drivers/filesystem/ntfs/mft.c 22 * PURPOSE: NTFS filesystem driver 23 * PROGRAMMERS: Eric Kohl 24 * Valentin Verkhovsky 25 * Pierre Schweitzer (pierre@reactos.org) 26 * Hervé Poussineau (hpoussin@reactos.org) 27 * Trevor Thompson 28 */ 29 30 /* INCLUDES *****************************************************************/ 31 32 #include "ntfs.h" 33 #include <ntintsafe.h> 34 35 #define NDEBUG 36 #include <debug.h> 37 38 /* FUNCTIONS ****************************************************************/ 39 40 PNTFS_ATTR_CONTEXT 41 PrepareAttributeContext(PNTFS_ATTR_RECORD AttrRecord) 42 { 43 PNTFS_ATTR_CONTEXT Context; 44 45 Context = ExAllocateFromNPagedLookasideList(&NtfsGlobalData->AttrCtxtLookasideList); 46 if(!Context) 47 { 48 DPRINT1("Error: Unable to allocate memory for context!\n"); 49 return NULL; 50 } 51 52 // Allocate memory for a copy of the attribute 53 Context->pRecord = ExAllocatePoolWithTag(NonPagedPool, AttrRecord->Length, TAG_NTFS); 54 if(!Context->pRecord) 55 { 56 DPRINT1("Error: Unable to allocate memory for attribute record!\n"); 57 ExFreeToNPagedLookasideList(&NtfsGlobalData->AttrCtxtLookasideList, Context); 58 return NULL; 59 } 60 61 // Copy the attribute 62 RtlCopyMemory(Context->pRecord, AttrRecord, AttrRecord->Length); 63 64 if (AttrRecord->IsNonResident) 65 { 66 LONGLONG DataRunOffset; 67 ULONGLONG DataRunLength; 68 ULONGLONG NextVBN = 0; 69 PUCHAR DataRun = (PUCHAR)((ULONG_PTR)Context->pRecord + Context->pRecord->NonResident.MappingPairsOffset); 70 71 Context->CacheRun = DataRun; 72 Context->CacheRunOffset = 0; 73 Context->CacheRun = DecodeRun(Context->CacheRun, &DataRunOffset, &DataRunLength); 74 Context->CacheRunLength = DataRunLength; 75 if (DataRunOffset != -1) 76 { 77 /* Normal run. */ 78 Context->CacheRunStartLCN = 79 Context->CacheRunLastLCN = DataRunOffset; 80 } 81 else 82 { 83 /* Sparse run. */ 84 Context->CacheRunStartLCN = -1; 85 Context->CacheRunLastLCN = 0; 86 } 87 Context->CacheRunCurrentOffset = 0; 88 89 // Convert the data runs to a map control block 90 if (!NT_SUCCESS(ConvertDataRunsToLargeMCB(DataRun, &Context->DataRunsMCB, &NextVBN))) 91 { 92 DPRINT1("Unable to convert data runs to MCB!\n"); 93 ExFreePoolWithTag(Context->pRecord, TAG_NTFS); 94 ExFreeToNPagedLookasideList(&NtfsGlobalData->AttrCtxtLookasideList, Context); 95 return NULL; 96 } 97 } 98 99 return Context; 100 } 101 102 103 VOID 104 ReleaseAttributeContext(PNTFS_ATTR_CONTEXT Context) 105 { 106 if (Context->pRecord) 107 { 108 if (Context->pRecord->IsNonResident) 109 { 110 FsRtlUninitializeLargeMcb(&Context->DataRunsMCB); 111 } 112 113 ExFreePoolWithTag(Context->pRecord, TAG_NTFS); 114 } 115 116 ExFreeToNPagedLookasideList(&NtfsGlobalData->AttrCtxtLookasideList, Context); 117 } 118 119 120 /** 121 * @name FindAttribute 122 * @implemented 123 * 124 * Searches a file record for an attribute matching the given type and name. 125 * 126 * @param Offset 127 * Optional pointer to a ULONG that will receive the offset of the found attribute 128 * from the beginning of the record. Can be set to NULL. 129 */ 130 NTSTATUS 131 FindAttribute(PDEVICE_EXTENSION Vcb, 132 PFILE_RECORD_HEADER MftRecord, 133 ULONG Type, 134 PCWSTR Name, 135 ULONG NameLength, 136 PNTFS_ATTR_CONTEXT * AttrCtx, 137 PULONG Offset) 138 { 139 BOOLEAN Found; 140 NTSTATUS Status; 141 FIND_ATTR_CONTXT Context; 142 PNTFS_ATTR_RECORD Attribute; 143 PNTFS_ATTRIBUTE_LIST_ITEM AttrListItem; 144 145 DPRINT("FindAttribute(%p, %p, 0x%x, %S, %lu, %p, %p)\n", Vcb, MftRecord, Type, Name, NameLength, AttrCtx, Offset); 146 147 Found = FALSE; 148 Status = FindFirstAttribute(&Context, Vcb, MftRecord, FALSE, &Attribute); 149 while (NT_SUCCESS(Status)) 150 { 151 if (Attribute->Type == Type && Attribute->NameLength == NameLength) 152 { 153 if (NameLength != 0) 154 { 155 PWCHAR AttrName; 156 157 AttrName = (PWCHAR)((PCHAR)Attribute + Attribute->NameOffset); 158 DPRINT("%.*S, %.*S\n", Attribute->NameLength, AttrName, NameLength, Name); 159 if (RtlCompareMemory(AttrName, Name, NameLength * sizeof(WCHAR)) == (NameLength * sizeof(WCHAR))) 160 { 161 Found = TRUE; 162 } 163 } 164 else 165 { 166 Found = TRUE; 167 } 168 169 if (Found) 170 { 171 /* Found it, fill up the context and return. */ 172 DPRINT("Found context\n"); 173 *AttrCtx = PrepareAttributeContext(Attribute); 174 175 (*AttrCtx)->FileMFTIndex = MftRecord->MFTRecordNumber; 176 177 if (Offset != NULL) 178 *Offset = Context.Offset; 179 180 FindCloseAttribute(&Context); 181 return STATUS_SUCCESS; 182 } 183 } 184 185 Status = FindNextAttribute(&Context, &Attribute); 186 } 187 188 /* No attribute found, check if it is referenced in another file record */ 189 Status = FindFirstAttributeListItem(&Context, &AttrListItem); 190 while (NT_SUCCESS(Status)) 191 { 192 if (AttrListItem->Type == Type && AttrListItem->NameLength == NameLength) 193 { 194 if (NameLength != 0) 195 { 196 PWCHAR AttrName; 197 198 AttrName = (PWCHAR)((PCHAR)AttrListItem + AttrListItem->NameOffset); 199 DPRINT("%.*S, %.*S\n", AttrListItem->NameLength, AttrName, NameLength, Name); 200 if (RtlCompareMemory(AttrName, Name, NameLength * sizeof(WCHAR)) == (NameLength * sizeof(WCHAR))) 201 { 202 Found = TRUE; 203 } 204 } 205 else 206 { 207 Found = TRUE; 208 } 209 210 if (Found == TRUE) 211 { 212 /* Get the MFT Index of attribute */ 213 ULONGLONG MftIndex; 214 PFILE_RECORD_HEADER RemoteHdr; 215 216 MftIndex = AttrListItem->MFTIndex & NTFS_MFT_MASK; 217 RemoteHdr = ExAllocateFromNPagedLookasideList(&Vcb->FileRecLookasideList); 218 219 if (RemoteHdr == NULL) 220 { 221 FindCloseAttribute(&Context); 222 return STATUS_INSUFFICIENT_RESOURCES; 223 } 224 225 /* Check we are not reading ourselves */ 226 if (MftRecord->MFTRecordNumber == MftIndex) 227 { 228 DPRINT1("Attribute list references missing attribute to this file entry !"); 229 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, RemoteHdr); 230 FindCloseAttribute(&Context); 231 return STATUS_OBJECT_NAME_NOT_FOUND; 232 } 233 /* Read the new file record */ 234 ReadFileRecord(Vcb, MftIndex, RemoteHdr); 235 Status = FindAttribute(Vcb, RemoteHdr, Type, Name, NameLength, AttrCtx, Offset); 236 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, RemoteHdr); 237 FindCloseAttribute(&Context); 238 return Status; 239 } 240 } 241 Status = FindNextAttributeListItem(&Context, &AttrListItem); 242 } 243 FindCloseAttribute(&Context); 244 return STATUS_OBJECT_NAME_NOT_FOUND; 245 } 246 247 248 ULONGLONG 249 AttributeAllocatedLength(PNTFS_ATTR_RECORD AttrRecord) 250 { 251 if (AttrRecord->IsNonResident) 252 return AttrRecord->NonResident.AllocatedSize; 253 else 254 return ALIGN_UP_BY(AttrRecord->Resident.ValueLength, ATTR_RECORD_ALIGNMENT); 255 } 256 257 258 ULONGLONG 259 AttributeDataLength(PNTFS_ATTR_RECORD AttrRecord) 260 { 261 if (AttrRecord->IsNonResident) 262 return AttrRecord->NonResident.DataSize; 263 else 264 return AttrRecord->Resident.ValueLength; 265 } 266 267 /** 268 * @name IncreaseMftSize 269 * @implemented 270 * 271 * Increases the size of the master file table on a volume, increasing the space available for file records. 272 * 273 * @param Vcb 274 * Pointer to the VCB (DEVICE_EXTENSION) of the target volume. 275 * 276 * 277 * @param CanWait 278 * Boolean indicating if the function is allowed to wait for exclusive access to the master file table. 279 * This will only be relevant if the MFT doesn't have any free file records and needs to be enlarged. 280 * 281 * @return 282 * STATUS_SUCCESS on success. 283 * STATUS_INSUFFICIENT_RESOURCES if an allocation fails. 284 * STATUS_INVALID_PARAMETER if there was an error reading the Mft's bitmap. 285 * STATUS_CANT_WAIT if CanWait was FALSE and the function could not get immediate, exclusive access to the MFT. 286 * 287 * @remarks 288 * Increases the size of the Master File Table by 64 records. Bitmap entries for the new records are cleared, 289 * and the bitmap is also enlarged if needed. Mimicking Windows' behavior when enlarging the mft is still TODO. 290 * This function will wait for exlusive access to the volume fcb. 291 */ 292 NTSTATUS 293 IncreaseMftSize(PDEVICE_EXTENSION Vcb, BOOLEAN CanWait) 294 { 295 PNTFS_ATTR_CONTEXT BitmapContext; 296 LARGE_INTEGER BitmapSize; 297 LARGE_INTEGER DataSize; 298 LONGLONG BitmapSizeDifference; 299 ULONG NewRecords = ATTR_RECORD_ALIGNMENT * 8; // Allocate one new record for every bit of every byte we'll be adding to the bitmap 300 ULONG DataSizeDifference = Vcb->NtfsInfo.BytesPerFileRecord * NewRecords; 301 ULONG BitmapOffset; 302 PUCHAR BitmapBuffer; 303 ULONGLONG BitmapBytes; 304 ULONGLONG NewBitmapSize; 305 ULONGLONG FirstNewMftIndex; 306 ULONG BytesRead; 307 ULONG LengthWritten; 308 PFILE_RECORD_HEADER BlankFileRecord; 309 ULONG i; 310 NTSTATUS Status; 311 312 DPRINT1("IncreaseMftSize(%p, %s)\n", Vcb, CanWait ? "TRUE" : "FALSE"); 313 314 // We need exclusive access to the mft while we change its size 315 if (!ExAcquireResourceExclusiveLite(&(Vcb->DirResource), CanWait)) 316 { 317 return STATUS_CANT_WAIT; 318 } 319 320 // Create a blank file record that will be used later 321 BlankFileRecord = NtfsCreateEmptyFileRecord(Vcb); 322 if (!BlankFileRecord) 323 { 324 DPRINT1("Error: Unable to create empty file record!\n"); 325 return STATUS_INSUFFICIENT_RESOURCES; 326 } 327 328 // Clear the flags (file record is not in use) 329 BlankFileRecord->Flags = 0; 330 331 // Find the bitmap attribute of master file table 332 Status = FindAttribute(Vcb, Vcb->MasterFileTable, AttributeBitmap, L"", 0, &BitmapContext, NULL); 333 if (!NT_SUCCESS(Status)) 334 { 335 DPRINT1("ERROR: Couldn't find $BITMAP attribute of Mft!\n"); 336 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, BlankFileRecord); 337 ExReleaseResourceLite(&(Vcb->DirResource)); 338 return Status; 339 } 340 341 // Get size of Bitmap Attribute 342 BitmapSize.QuadPart = AttributeDataLength(BitmapContext->pRecord); 343 344 // Calculate the new mft size 345 DataSize.QuadPart = AttributeDataLength(Vcb->MFTContext->pRecord) + DataSizeDifference; 346 347 // Find the index of the first Mft entry that will be created 348 FirstNewMftIndex = AttributeDataLength(Vcb->MFTContext->pRecord) / Vcb->NtfsInfo.BytesPerFileRecord; 349 350 // Determine how many bytes will make up the bitmap 351 BitmapBytes = DataSize.QuadPart / Vcb->NtfsInfo.BytesPerFileRecord / 8; 352 if ((DataSize.QuadPart / Vcb->NtfsInfo.BytesPerFileRecord) % 8 != 0) 353 BitmapBytes++; 354 355 // Windows will always keep the number of bytes in a bitmap as a multiple of 8, so no bytes are wasted on slack 356 BitmapBytes = ALIGN_UP_BY(BitmapBytes, ATTR_RECORD_ALIGNMENT); 357 358 // Determine how much we need to adjust the bitmap size (it's possible we don't) 359 BitmapSizeDifference = BitmapBytes - BitmapSize.QuadPart; 360 NewBitmapSize = max(BitmapSize.QuadPart + BitmapSizeDifference, BitmapSize.QuadPart); 361 362 // Allocate memory for the bitmap 363 BitmapBuffer = ExAllocatePoolWithTag(NonPagedPool, NewBitmapSize, TAG_NTFS); 364 if (!BitmapBuffer) 365 { 366 DPRINT1("ERROR: Unable to allocate memory for bitmap attribute!\n"); 367 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, BlankFileRecord); 368 ExReleaseResourceLite(&(Vcb->DirResource)); 369 ReleaseAttributeContext(BitmapContext); 370 return STATUS_INSUFFICIENT_RESOURCES; 371 } 372 373 // Zero the bytes we'll be adding 374 RtlZeroMemory(BitmapBuffer, NewBitmapSize); 375 376 // Read the bitmap attribute 377 BytesRead = ReadAttribute(Vcb, 378 BitmapContext, 379 0, 380 (PCHAR)BitmapBuffer, 381 BitmapSize.LowPart); 382 if (BytesRead != BitmapSize.LowPart) 383 { 384 DPRINT1("ERROR: Bytes read != Bitmap size!\n"); 385 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, BlankFileRecord); 386 ExReleaseResourceLite(&(Vcb->DirResource)); 387 ExFreePoolWithTag(BitmapBuffer, TAG_NTFS); 388 ReleaseAttributeContext(BitmapContext); 389 return STATUS_INVALID_PARAMETER; 390 } 391 392 // Increase the mft size 393 Status = SetNonResidentAttributeDataLength(Vcb, Vcb->MFTContext, Vcb->MftDataOffset, Vcb->MasterFileTable, &DataSize); 394 if (!NT_SUCCESS(Status)) 395 { 396 DPRINT1("ERROR: Failed to set size of $MFT data attribute!\n"); 397 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, BlankFileRecord); 398 ExReleaseResourceLite(&(Vcb->DirResource)); 399 ExFreePoolWithTag(BitmapBuffer, TAG_NTFS); 400 ReleaseAttributeContext(BitmapContext); 401 return Status; 402 } 403 404 // We'll need to find the bitmap again, because its offset will have changed after resizing the data attribute 405 ReleaseAttributeContext(BitmapContext); 406 Status = FindAttribute(Vcb, Vcb->MasterFileTable, AttributeBitmap, L"", 0, &BitmapContext, &BitmapOffset); 407 if (!NT_SUCCESS(Status)) 408 { 409 DPRINT1("ERROR: Couldn't find $BITMAP attribute of Mft!\n"); 410 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, BlankFileRecord); 411 ExReleaseResourceLite(&(Vcb->DirResource)); 412 return Status; 413 } 414 415 // If the bitmap grew 416 if (BitmapSizeDifference > 0) 417 { 418 // Set the new bitmap size 419 BitmapSize.QuadPart = NewBitmapSize; 420 if (BitmapContext->pRecord->IsNonResident) 421 Status = SetNonResidentAttributeDataLength(Vcb, BitmapContext, BitmapOffset, Vcb->MasterFileTable, &BitmapSize); 422 else 423 Status = SetResidentAttributeDataLength(Vcb, BitmapContext, BitmapOffset, Vcb->MasterFileTable, &BitmapSize); 424 425 if (!NT_SUCCESS(Status)) 426 { 427 DPRINT1("ERROR: Failed to set size of bitmap attribute!\n"); 428 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, BlankFileRecord); 429 ExReleaseResourceLite(&(Vcb->DirResource)); 430 ExFreePoolWithTag(BitmapBuffer, TAG_NTFS); 431 ReleaseAttributeContext(BitmapContext); 432 return Status; 433 } 434 } 435 436 NtfsDumpFileAttributes(Vcb, Vcb->MasterFileTable); 437 438 // Update the file record with the new attribute sizes 439 Status = UpdateFileRecord(Vcb, Vcb->VolumeFcb->MFTIndex, Vcb->MasterFileTable); 440 if (!NT_SUCCESS(Status)) 441 { 442 DPRINT1("ERROR: Failed to update $MFT file record!\n"); 443 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, BlankFileRecord); 444 ExReleaseResourceLite(&(Vcb->DirResource)); 445 ExFreePoolWithTag(BitmapBuffer, TAG_NTFS); 446 ReleaseAttributeContext(BitmapContext); 447 return Status; 448 } 449 450 // Write out the new bitmap 451 Status = WriteAttribute(Vcb, BitmapContext, 0, BitmapBuffer, NewBitmapSize, &LengthWritten, Vcb->MasterFileTable); 452 if (!NT_SUCCESS(Status)) 453 { 454 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, BlankFileRecord); 455 ExReleaseResourceLite(&(Vcb->DirResource)); 456 ExFreePoolWithTag(BitmapBuffer, TAG_NTFS); 457 ReleaseAttributeContext(BitmapContext); 458 DPRINT1("ERROR: Couldn't write to bitmap attribute of $MFT!\n"); 459 return Status; 460 } 461 462 // Create blank records for the new file record entries. 463 for (i = 0; i < NewRecords; i++) 464 { 465 Status = UpdateFileRecord(Vcb, FirstNewMftIndex + i, BlankFileRecord); 466 if (!NT_SUCCESS(Status)) 467 { 468 DPRINT1("ERROR: Failed to write blank file record!\n"); 469 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, BlankFileRecord); 470 ExReleaseResourceLite(&(Vcb->DirResource)); 471 ExFreePoolWithTag(BitmapBuffer, TAG_NTFS); 472 ReleaseAttributeContext(BitmapContext); 473 return Status; 474 } 475 } 476 477 // Update the mft mirror 478 Status = UpdateMftMirror(Vcb); 479 480 // Cleanup 481 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, BlankFileRecord); 482 ExReleaseResourceLite(&(Vcb->DirResource)); 483 ExFreePoolWithTag(BitmapBuffer, TAG_NTFS); 484 ReleaseAttributeContext(BitmapContext); 485 486 return Status; 487 } 488 489 /** 490 * @name MoveAttributes 491 * @implemented 492 * 493 * Moves a block of attributes to a new location in the file Record. The attribute at FirstAttributeToMove 494 * and every attribute after that will be moved to MoveTo. 495 * 496 * @param DeviceExt 497 * Pointer to the DEVICE_EXTENSION (VCB) of the target volume. 498 * 499 * @param FirstAttributeToMove 500 * Pointer to the first NTFS_ATTR_RECORD that needs to be moved. This pointer must reside within a file record. 501 * 502 * @param FirstAttributeOffset 503 * Offset of FirstAttributeToMove relative to the beginning of the file record. 504 * 505 * @param MoveTo 506 * ULONG_PTR with the memory location that will be the new location of the first attribute being moved. 507 * 508 * @return 509 * The new location of the final attribute (i.e. AttributeEnd marker). 510 */ 511 PNTFS_ATTR_RECORD 512 MoveAttributes(PDEVICE_EXTENSION DeviceExt, 513 PNTFS_ATTR_RECORD FirstAttributeToMove, 514 ULONG FirstAttributeOffset, 515 ULONG_PTR MoveTo) 516 { 517 // Get the size of all attributes after this one 518 ULONG MemBlockSize = 0; 519 PNTFS_ATTR_RECORD CurrentAttribute = FirstAttributeToMove; 520 ULONG CurrentOffset = FirstAttributeOffset; 521 PNTFS_ATTR_RECORD FinalAttribute; 522 523 while (CurrentAttribute->Type != AttributeEnd && CurrentOffset < DeviceExt->NtfsInfo.BytesPerFileRecord) 524 { 525 CurrentOffset += CurrentAttribute->Length; 526 MemBlockSize += CurrentAttribute->Length; 527 CurrentAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)CurrentAttribute + CurrentAttribute->Length); 528 } 529 530 FinalAttribute = (PNTFS_ATTR_RECORD)(MoveTo + MemBlockSize); 531 MemBlockSize += sizeof(ULONG) * 2; // Add the AttributeEnd and file record end 532 533 ASSERT(MemBlockSize % ATTR_RECORD_ALIGNMENT == 0); 534 535 // Move the attributes after this one 536 RtlMoveMemory((PCHAR)MoveTo, FirstAttributeToMove, MemBlockSize); 537 538 return FinalAttribute; 539 } 540 541 NTSTATUS 542 InternalSetResidentAttributeLength(PDEVICE_EXTENSION DeviceExt, 543 PNTFS_ATTR_CONTEXT AttrContext, 544 PFILE_RECORD_HEADER FileRecord, 545 ULONG AttrOffset, 546 ULONG DataSize) 547 { 548 PNTFS_ATTR_RECORD Destination = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttrOffset); 549 PNTFS_ATTR_RECORD NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)Destination + Destination->Length); 550 PNTFS_ATTR_RECORD FinalAttribute; 551 ULONG OldAttributeLength = Destination->Length; 552 ULONG NextAttributeOffset; 553 554 DPRINT1("InternalSetResidentAttributeLength( %p, %p, %p, %lu, %lu )\n", DeviceExt, AttrContext, FileRecord, AttrOffset, DataSize); 555 556 ASSERT(!AttrContext->pRecord->IsNonResident); 557 558 // Update ValueLength Field 559 Destination->Resident.ValueLength = DataSize; 560 561 // Calculate the record length and end marker offset 562 Destination->Length = ALIGN_UP_BY(DataSize + AttrContext->pRecord->Resident.ValueOffset, ATTR_RECORD_ALIGNMENT); 563 NextAttributeOffset = AttrOffset + Destination->Length; 564 565 // Ensure NextAttributeOffset is aligned to an 8-byte boundary 566 ASSERT(NextAttributeOffset % ATTR_RECORD_ALIGNMENT == 0); 567 568 // Will the new attribute be larger than the old one? 569 if (Destination->Length > OldAttributeLength) 570 { 571 // Free the old copy of the attribute in the context, as it will be the wrong length 572 ExFreePoolWithTag(AttrContext->pRecord, TAG_NTFS); 573 574 // Create a new copy of the attribute record for the context 575 AttrContext->pRecord = ExAllocatePoolWithTag(NonPagedPool, Destination->Length, TAG_NTFS); 576 if (!AttrContext->pRecord) 577 { 578 DPRINT1("Unable to allocate memory for attribute!\n"); 579 return STATUS_INSUFFICIENT_RESOURCES; 580 } 581 RtlZeroMemory((PVOID)((ULONG_PTR)AttrContext->pRecord + OldAttributeLength), Destination->Length - OldAttributeLength); 582 RtlCopyMemory(AttrContext->pRecord, Destination, OldAttributeLength); 583 } 584 585 // Are there attributes after this one that need to be moved? 586 if (NextAttribute->Type != AttributeEnd) 587 { 588 // Move the attributes after this one 589 FinalAttribute = MoveAttributes(DeviceExt, NextAttribute, NextAttributeOffset, (ULONG_PTR)Destination + Destination->Length); 590 } 591 else 592 { 593 // advance to the final "attribute," adjust for the changed length of the attribute we're resizing 594 FinalAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)NextAttribute - OldAttributeLength + Destination->Length); 595 } 596 597 // Update pRecord's length 598 AttrContext->pRecord->Length = Destination->Length; 599 AttrContext->pRecord->Resident.ValueLength = DataSize; 600 601 // set the file record end 602 SetFileRecordEnd(FileRecord, FinalAttribute, FILE_RECORD_END); 603 604 //NtfsDumpFileRecord(DeviceExt, FileRecord); 605 606 return STATUS_SUCCESS; 607 } 608 609 /** 610 * @parameter FileRecord 611 * Pointer to a file record. Must be a full record at least 612 * Fcb->Vcb->NtfsInfo.BytesPerFileRecord bytes large, not just the header. 613 */ 614 NTSTATUS 615 SetAttributeDataLength(PFILE_OBJECT FileObject, 616 PNTFS_FCB Fcb, 617 PNTFS_ATTR_CONTEXT AttrContext, 618 ULONG AttrOffset, 619 PFILE_RECORD_HEADER FileRecord, 620 PLARGE_INTEGER DataSize) 621 { 622 NTSTATUS Status = STATUS_SUCCESS; 623 624 DPRINT1("SetAttributeDataLength(%p, %p, %p, %lu, %p, %I64u)\n", 625 FileObject, 626 Fcb, 627 AttrContext, 628 AttrOffset, 629 FileRecord, 630 DataSize->QuadPart); 631 632 // are we truncating the file? 633 if (DataSize->QuadPart < AttributeDataLength(AttrContext->pRecord)) 634 { 635 if (!MmCanFileBeTruncated(FileObject->SectionObjectPointer, DataSize)) 636 { 637 DPRINT1("Can't truncate a memory-mapped file!\n"); 638 return STATUS_USER_MAPPED_FILE; 639 } 640 } 641 642 if (AttrContext->pRecord->IsNonResident) 643 { 644 Status = SetNonResidentAttributeDataLength(Fcb->Vcb, 645 AttrContext, 646 AttrOffset, 647 FileRecord, 648 DataSize); 649 } 650 else 651 { 652 // resident attribute 653 Status = SetResidentAttributeDataLength(Fcb->Vcb, 654 AttrContext, 655 AttrOffset, 656 FileRecord, 657 DataSize); 658 } 659 660 if (!NT_SUCCESS(Status)) 661 { 662 DPRINT1("ERROR: Failed to set size of attribute!\n"); 663 return Status; 664 } 665 666 //NtfsDumpFileAttributes(Fcb->Vcb, FileRecord); 667 668 // write the updated file record back to disk 669 Status = UpdateFileRecord(Fcb->Vcb, Fcb->MFTIndex, FileRecord); 670 671 if (NT_SUCCESS(Status)) 672 { 673 if (AttrContext->pRecord->IsNonResident) 674 Fcb->RFCB.AllocationSize.QuadPart = AttrContext->pRecord->NonResident.AllocatedSize; 675 else 676 Fcb->RFCB.AllocationSize = *DataSize; 677 Fcb->RFCB.FileSize = *DataSize; 678 Fcb->RFCB.ValidDataLength = *DataSize; 679 CcSetFileSizes(FileObject, (PCC_FILE_SIZES)&Fcb->RFCB.AllocationSize); 680 } 681 682 return STATUS_SUCCESS; 683 } 684 685 /** 686 * @name SetFileRecordEnd 687 * @implemented 688 * 689 * This small function sets a new endpoint for the file record. It set's the final 690 * AttrEnd->Type to AttributeEnd and recalculates the bytes used by the file record. 691 * 692 * @param FileRecord 693 * Pointer to the file record whose endpoint (length) will be set. 694 * 695 * @param AttrEnd 696 * Pointer to section of memory that will receive the AttributeEnd marker. This must point 697 * to memory allocated for the FileRecord. Must be aligned to an 8-byte boundary (relative to FileRecord). 698 * 699 * @param EndMarker 700 * This value will be written after AttributeEnd but isn't critical at all. When Windows resizes 701 * a file record, it preserves the final ULONG that previously ended the record, even though this 702 * value is (to my knowledge) never used. We emulate this behavior. 703 * 704 */ 705 VOID 706 SetFileRecordEnd(PFILE_RECORD_HEADER FileRecord, 707 PNTFS_ATTR_RECORD AttrEnd, 708 ULONG EndMarker) 709 { 710 // Ensure AttrEnd is aligned on an 8-byte boundary, relative to FileRecord 711 ASSERT(((ULONG_PTR)AttrEnd - (ULONG_PTR)FileRecord) % ATTR_RECORD_ALIGNMENT == 0); 712 713 // mark the end of attributes 714 AttrEnd->Type = AttributeEnd; 715 716 // Restore the "file-record-end marker." The value is never checked but this behavior is consistent with Win2k3. 717 AttrEnd->Length = EndMarker; 718 719 // recalculate bytes in use 720 FileRecord->BytesInUse = (ULONG_PTR)AttrEnd - (ULONG_PTR)FileRecord + sizeof(ULONG) * 2; 721 } 722 723 /** 724 * @name SetNonResidentAttributeDataLength 725 * @implemented 726 * 727 * Called by SetAttributeDataLength() to set the size of a non-resident attribute. Doesn't update the file record. 728 * 729 * @param Vcb 730 * Pointer to a DEVICE_EXTENSION describing the target disk. 731 * 732 * @param AttrContext 733 * PNTFS_ATTR_CONTEXT describing the location of the attribute whose size is being set. 734 * 735 * @param AttrOffset 736 * Offset, from the beginning of the record, of the attribute being sized. 737 * 738 * @param FileRecord 739 * Pointer to a file record containing the attribute to be resized. Must be a complete file record, 740 * not just the header. 741 * 742 * @param DataSize 743 * Pointer to a LARGE_INTEGER describing the new size of the attribute's data. 744 * 745 * @return 746 * STATUS_SUCCESS on success; 747 * STATUS_INSUFFICIENT_RESOURCES if an allocation fails. 748 * STATUS_INVALID_PARAMETER if we can't find the last cluster in the data run. 749 * 750 * @remarks 751 * Called by SetAttributeDataLength() and IncreaseMftSize(). Use SetAttributeDataLength() unless you have a good 752 * reason to use this. Doesn't update the file record on disk. Doesn't inform the cache controller of changes with 753 * any associated files. Synchronization is the callers responsibility. 754 */ 755 NTSTATUS 756 SetNonResidentAttributeDataLength(PDEVICE_EXTENSION Vcb, 757 PNTFS_ATTR_CONTEXT AttrContext, 758 ULONG AttrOffset, 759 PFILE_RECORD_HEADER FileRecord, 760 PLARGE_INTEGER DataSize) 761 { 762 NTSTATUS Status = STATUS_SUCCESS; 763 ULONG BytesPerCluster = Vcb->NtfsInfo.BytesPerCluster; 764 ULONGLONG AllocationSize = ROUND_UP(DataSize->QuadPart, BytesPerCluster); 765 PNTFS_ATTR_RECORD DestinationAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttrOffset); 766 ULONG ExistingClusters = AttrContext->pRecord->NonResident.AllocatedSize / BytesPerCluster; 767 768 ASSERT(AttrContext->pRecord->IsNonResident); 769 770 // do we need to increase the allocation size? 771 if (AttrContext->pRecord->NonResident.AllocatedSize < AllocationSize) 772 { 773 ULONG ClustersNeeded = (AllocationSize / BytesPerCluster) - ExistingClusters; 774 LARGE_INTEGER LastClusterInDataRun; 775 ULONG NextAssignedCluster; 776 ULONG AssignedClusters; 777 778 if (ExistingClusters == 0) 779 { 780 LastClusterInDataRun.QuadPart = 0; 781 } 782 else 783 { 784 if (!FsRtlLookupLargeMcbEntry(&AttrContext->DataRunsMCB, 785 (LONGLONG)AttrContext->pRecord->NonResident.HighestVCN, 786 (PLONGLONG)&LastClusterInDataRun.QuadPart, 787 NULL, 788 NULL, 789 NULL, 790 NULL)) 791 { 792 DPRINT1("Error looking up final large MCB entry!\n"); 793 794 // Most likely, HighestVCN went above the largest mapping 795 DPRINT1("Highest VCN of record: %I64u\n", AttrContext->pRecord->NonResident.HighestVCN); 796 return STATUS_INVALID_PARAMETER; 797 } 798 } 799 800 DPRINT("LastClusterInDataRun: %I64u\n", LastClusterInDataRun.QuadPart); 801 DPRINT("Highest VCN of record: %I64u\n", AttrContext->pRecord->NonResident.HighestVCN); 802 803 while (ClustersNeeded > 0) 804 { 805 Status = NtfsAllocateClusters(Vcb, 806 LastClusterInDataRun.LowPart + 1, 807 ClustersNeeded, 808 &NextAssignedCluster, 809 &AssignedClusters); 810 811 if (!NT_SUCCESS(Status)) 812 { 813 DPRINT1("Error: Unable to allocate requested clusters!\n"); 814 return Status; 815 } 816 817 // now we need to add the clusters we allocated to the data run 818 Status = AddRun(Vcb, AttrContext, AttrOffset, FileRecord, NextAssignedCluster, AssignedClusters); 819 if (!NT_SUCCESS(Status)) 820 { 821 DPRINT1("Error: Unable to add data run!\n"); 822 return Status; 823 } 824 825 ClustersNeeded -= AssignedClusters; 826 LastClusterInDataRun.LowPart = NextAssignedCluster + AssignedClusters - 1; 827 } 828 } 829 else if (AttrContext->pRecord->NonResident.AllocatedSize > AllocationSize) 830 { 831 // shrink allocation size 832 ULONG ClustersToFree = ExistingClusters - (AllocationSize / BytesPerCluster); 833 Status = FreeClusters(Vcb, AttrContext, AttrOffset, FileRecord, ClustersToFree); 834 } 835 836 // TODO: is the file compressed, encrypted, or sparse? 837 838 AttrContext->pRecord->NonResident.AllocatedSize = AllocationSize; 839 AttrContext->pRecord->NonResident.DataSize = DataSize->QuadPart; 840 AttrContext->pRecord->NonResident.InitializedSize = DataSize->QuadPart; 841 842 DestinationAttribute->NonResident.AllocatedSize = AllocationSize; 843 DestinationAttribute->NonResident.DataSize = DataSize->QuadPart; 844 DestinationAttribute->NonResident.InitializedSize = DataSize->QuadPart; 845 846 // HighestVCN seems to be set incorrectly somewhere. Apply a hack-fix to reset it. 847 // HACKHACK FIXME: Fix for sparse files; this math won't work in that case. 848 AttrContext->pRecord->NonResident.HighestVCN = ((ULONGLONG)AllocationSize / Vcb->NtfsInfo.BytesPerCluster) - 1; 849 DestinationAttribute->NonResident.HighestVCN = AttrContext->pRecord->NonResident.HighestVCN; 850 851 DPRINT("Allocated Size: %I64u\n", DestinationAttribute->NonResident.AllocatedSize); 852 853 return Status; 854 } 855 856 /** 857 * @name SetResidentAttributeDataLength 858 * @implemented 859 * 860 * Called by SetAttributeDataLength() to set the size of a non-resident attribute. Doesn't update the file record. 861 * 862 * @param Vcb 863 * Pointer to a DEVICE_EXTENSION describing the target disk. 864 * 865 * @param AttrContext 866 * PNTFS_ATTR_CONTEXT describing the location of the attribute whose size is being set. 867 * 868 * @param AttrOffset 869 * Offset, from the beginning of the record, of the attribute being sized. 870 * 871 * @param FileRecord 872 * Pointer to a file record containing the attribute to be resized. Must be a complete file record, 873 * not just the header. 874 * 875 * @param DataSize 876 * Pointer to a LARGE_INTEGER describing the new size of the attribute's data. 877 * 878 * @return 879 * STATUS_SUCCESS on success; 880 * STATUS_INSUFFICIENT_RESOURCES if an allocation fails. 881 * STATUS_INVALID_PARAMETER if AttrContext describes a non-resident attribute. 882 * STATUS_NOT_IMPLEMENTED if requested to decrease the size of an attribute that isn't the 883 * last attribute listed in the file record. 884 * 885 * @remarks 886 * Called by SetAttributeDataLength() and IncreaseMftSize(). Use SetAttributeDataLength() unless you have a good 887 * reason to use this. Doesn't update the file record on disk. Doesn't inform the cache controller of changes with 888 * any associated files. Synchronization is the callers responsibility. 889 */ 890 NTSTATUS 891 SetResidentAttributeDataLength(PDEVICE_EXTENSION Vcb, 892 PNTFS_ATTR_CONTEXT AttrContext, 893 ULONG AttrOffset, 894 PFILE_RECORD_HEADER FileRecord, 895 PLARGE_INTEGER DataSize) 896 { 897 NTSTATUS Status; 898 899 // find the next attribute 900 ULONG NextAttributeOffset = AttrOffset + AttrContext->pRecord->Length; 901 PNTFS_ATTR_RECORD NextAttribute = (PNTFS_ATTR_RECORD)((PCHAR)FileRecord + NextAttributeOffset); 902 903 ASSERT(!AttrContext->pRecord->IsNonResident); 904 905 //NtfsDumpFileAttributes(Vcb, FileRecord); 906 907 // Do we need to increase the data length? 908 if (DataSize->QuadPart > AttrContext->pRecord->Resident.ValueLength) 909 { 910 // There's usually padding at the end of a record. Do we need to extend past it? 911 ULONG MaxValueLength = AttrContext->pRecord->Length - AttrContext->pRecord->Resident.ValueOffset; 912 if (MaxValueLength < DataSize->LowPart) 913 { 914 // If this is the last attribute, we could move the end marker to the very end of the file record 915 MaxValueLength += Vcb->NtfsInfo.BytesPerFileRecord - NextAttributeOffset - (sizeof(ULONG) * 2); 916 917 if (MaxValueLength < DataSize->LowPart || NextAttribute->Type != AttributeEnd) 918 { 919 // convert attribute to non-resident 920 PNTFS_ATTR_RECORD Destination = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttrOffset); 921 PNTFS_ATTR_RECORD NewRecord; 922 LARGE_INTEGER AttribDataSize; 923 PVOID AttribData; 924 ULONG NewRecordLength; 925 ULONG EndAttributeOffset; 926 ULONG LengthWritten; 927 928 DPRINT1("Converting attribute to non-resident.\n"); 929 930 AttribDataSize.QuadPart = AttrContext->pRecord->Resident.ValueLength; 931 932 // Is there existing data we need to back-up? 933 if (AttribDataSize.QuadPart > 0) 934 { 935 AttribData = ExAllocatePoolWithTag(NonPagedPool, AttribDataSize.QuadPart, TAG_NTFS); 936 if (AttribData == NULL) 937 { 938 DPRINT1("ERROR: Couldn't allocate memory for attribute data. Can't migrate to non-resident!\n"); 939 return STATUS_INSUFFICIENT_RESOURCES; 940 } 941 942 // read data to temp buffer 943 Status = ReadAttribute(Vcb, AttrContext, 0, AttribData, AttribDataSize.QuadPart); 944 if (!NT_SUCCESS(Status)) 945 { 946 DPRINT1("ERROR: Unable to read attribute before migrating!\n"); 947 ExFreePoolWithTag(AttribData, TAG_NTFS); 948 return Status; 949 } 950 } 951 952 // Start by turning this attribute into a 0-length, non-resident attribute, then enlarge it. 953 954 // The size of a 0-length, non-resident attribute will be 0x41 + the size of the attribute name, aligned to an 8-byte boundary 955 NewRecordLength = ALIGN_UP_BY(0x41 + (AttrContext->pRecord->NameLength * sizeof(WCHAR)), ATTR_RECORD_ALIGNMENT); 956 957 // Create a new attribute record that will store the 0-length, non-resident attribute 958 NewRecord = ExAllocatePoolWithTag(NonPagedPool, NewRecordLength, TAG_NTFS); 959 960 // Zero out the NonResident structure 961 RtlZeroMemory(NewRecord, NewRecordLength); 962 963 // Copy the data that's common to both non-resident and resident attributes 964 RtlCopyMemory(NewRecord, AttrContext->pRecord, FIELD_OFFSET(NTFS_ATTR_RECORD, Resident.ValueLength)); 965 966 // if there's a name 967 if (AttrContext->pRecord->NameLength != 0) 968 { 969 // copy the name 970 // An attribute name will be located at offset 0x18 for a resident attribute, 0x40 for non-resident 971 RtlCopyMemory((PCHAR)((ULONG_PTR)NewRecord + 0x40), 972 (PCHAR)((ULONG_PTR)AttrContext->pRecord + 0x18), 973 AttrContext->pRecord->NameLength * sizeof(WCHAR)); 974 } 975 976 // update the mapping pairs offset, which will be 0x40 (size of a non-resident header) + length in bytes of the name 977 NewRecord->NonResident.MappingPairsOffset = 0x40 + (AttrContext->pRecord->NameLength * sizeof(WCHAR)); 978 979 // update the end of the file record 980 // calculate position of end markers (1 byte for empty data run) 981 EndAttributeOffset = AttrOffset + NewRecord->NonResident.MappingPairsOffset + 1; 982 EndAttributeOffset = ALIGN_UP_BY(EndAttributeOffset, ATTR_RECORD_ALIGNMENT); 983 984 // Update the length 985 NewRecord->Length = EndAttributeOffset - AttrOffset; 986 987 ASSERT(NewRecord->Length == NewRecordLength); 988 989 // Copy the new attribute record into the file record 990 RtlCopyMemory(Destination, NewRecord, NewRecord->Length); 991 992 // Update the file record end 993 SetFileRecordEnd(FileRecord, 994 (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + EndAttributeOffset), 995 FILE_RECORD_END); 996 997 // Initialize the MCB, potentially catch an exception 998 _SEH2_TRY 999 { 1000 FsRtlInitializeLargeMcb(&AttrContext->DataRunsMCB, NonPagedPool); 1001 } 1002 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) 1003 { 1004 DPRINT1("Unable to create LargeMcb!\n"); 1005 if (AttribDataSize.QuadPart > 0) 1006 ExFreePoolWithTag(AttribData, TAG_NTFS); 1007 ExFreePoolWithTag(NewRecord, TAG_NTFS); 1008 _SEH2_YIELD(return _SEH2_GetExceptionCode()); 1009 } _SEH2_END; 1010 1011 // Mark the attribute as non-resident (we wait until after we know the LargeMcb was initialized) 1012 NewRecord->IsNonResident = Destination->IsNonResident = 1; 1013 1014 // Update file record on disk 1015 Status = UpdateFileRecord(Vcb, AttrContext->FileMFTIndex, FileRecord); 1016 if (!NT_SUCCESS(Status)) 1017 { 1018 DPRINT1("ERROR: Couldn't update file record to continue migration!\n"); 1019 if (AttribDataSize.QuadPart > 0) 1020 ExFreePoolWithTag(AttribData, TAG_NTFS); 1021 ExFreePoolWithTag(NewRecord, TAG_NTFS); 1022 return Status; 1023 } 1024 1025 // Now we need to free the old copy of the attribute record in the context and replace it with the new one 1026 ExFreePoolWithTag(AttrContext->pRecord, TAG_NTFS); 1027 AttrContext->pRecord = NewRecord; 1028 1029 // Now we can treat the attribute as non-resident and enlarge it normally 1030 Status = SetNonResidentAttributeDataLength(Vcb, AttrContext, AttrOffset, FileRecord, DataSize); 1031 if (!NT_SUCCESS(Status)) 1032 { 1033 DPRINT1("ERROR: Unable to migrate resident attribute!\n"); 1034 if (AttribDataSize.QuadPart > 0) 1035 ExFreePoolWithTag(AttribData, TAG_NTFS); 1036 return Status; 1037 } 1038 1039 // restore the back-up attribute, if we made one 1040 if (AttribDataSize.QuadPart > 0) 1041 { 1042 Status = WriteAttribute(Vcb, AttrContext, 0, AttribData, AttribDataSize.QuadPart, &LengthWritten, FileRecord); 1043 if (!NT_SUCCESS(Status)) 1044 { 1045 DPRINT1("ERROR: Unable to write attribute data to non-resident clusters during migration!\n"); 1046 // TODO: Reverse migration so no data is lost 1047 ExFreePoolWithTag(AttribData, TAG_NTFS); 1048 return Status; 1049 } 1050 1051 ExFreePoolWithTag(AttribData, TAG_NTFS); 1052 } 1053 } 1054 } 1055 } 1056 1057 // set the new length of the resident attribute (if we didn't migrate it) 1058 if (!AttrContext->pRecord->IsNonResident) 1059 return InternalSetResidentAttributeLength(Vcb, AttrContext, FileRecord, AttrOffset, DataSize->LowPart); 1060 1061 return STATUS_SUCCESS; 1062 } 1063 1064 ULONG 1065 ReadAttribute(PDEVICE_EXTENSION Vcb, 1066 PNTFS_ATTR_CONTEXT Context, 1067 ULONGLONG Offset, 1068 PCHAR Buffer, 1069 ULONG Length) 1070 { 1071 ULONGLONG LastLCN; 1072 PUCHAR DataRun; 1073 LONGLONG DataRunOffset; 1074 ULONGLONG DataRunLength; 1075 LONGLONG DataRunStartLCN; 1076 ULONGLONG CurrentOffset; 1077 ULONG ReadLength; 1078 ULONG AlreadyRead; 1079 NTSTATUS Status; 1080 1081 //TEMPTEMP 1082 PUCHAR TempBuffer; 1083 1084 if (!Context->pRecord->IsNonResident) 1085 { 1086 // We need to truncate Offset to a ULONG for pointer arithmetic 1087 // The check below should ensure that Offset is well within the range of 32 bits 1088 ULONG LittleOffset = (ULONG)Offset; 1089 1090 // Ensure that offset isn't beyond the end of the attribute 1091 if (Offset > Context->pRecord->Resident.ValueLength) 1092 return 0; 1093 if (Offset + Length > Context->pRecord->Resident.ValueLength) 1094 Length = (ULONG)(Context->pRecord->Resident.ValueLength - Offset); 1095 1096 RtlCopyMemory(Buffer, (PVOID)((ULONG_PTR)Context->pRecord + Context->pRecord->Resident.ValueOffset + LittleOffset), Length); 1097 return Length; 1098 } 1099 1100 /* 1101 * Non-resident attribute 1102 */ 1103 1104 /* 1105 * I. Find the corresponding start data run. 1106 */ 1107 1108 AlreadyRead = 0; 1109 1110 // FIXME: Cache seems to be non-working. Disable it for now 1111 //if(Context->CacheRunOffset <= Offset && Offset < Context->CacheRunOffset + Context->CacheRunLength * Volume->ClusterSize) 1112 if (0) 1113 { 1114 DataRun = Context->CacheRun; 1115 LastLCN = Context->CacheRunLastLCN; 1116 DataRunStartLCN = Context->CacheRunStartLCN; 1117 DataRunLength = Context->CacheRunLength; 1118 CurrentOffset = Context->CacheRunCurrentOffset; 1119 } 1120 else 1121 { 1122 //TEMPTEMP 1123 ULONG UsedBufferSize; 1124 TempBuffer = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS); 1125 if (TempBuffer == NULL) 1126 { 1127 return STATUS_INSUFFICIENT_RESOURCES; 1128 } 1129 1130 LastLCN = 0; 1131 CurrentOffset = 0; 1132 1133 // This will be rewritten in the next iteration to just use the DataRuns MCB directly 1134 ConvertLargeMCBToDataRuns(&Context->DataRunsMCB, 1135 TempBuffer, 1136 Vcb->NtfsInfo.BytesPerFileRecord, 1137 &UsedBufferSize); 1138 1139 DataRun = TempBuffer; 1140 1141 while (1) 1142 { 1143 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength); 1144 if (DataRunOffset != -1) 1145 { 1146 /* Normal data run. */ 1147 DataRunStartLCN = LastLCN + DataRunOffset; 1148 LastLCN = DataRunStartLCN; 1149 } 1150 else 1151 { 1152 /* Sparse data run. */ 1153 DataRunStartLCN = -1; 1154 } 1155 1156 if (Offset >= CurrentOffset && 1157 Offset < CurrentOffset + (DataRunLength * Vcb->NtfsInfo.BytesPerCluster)) 1158 { 1159 break; 1160 } 1161 1162 if (*DataRun == 0) 1163 { 1164 ExFreePoolWithTag(TempBuffer, TAG_NTFS); 1165 return AlreadyRead; 1166 } 1167 1168 CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster; 1169 } 1170 } 1171 1172 /* 1173 * II. Go through the run list and read the data 1174 */ 1175 1176 ReadLength = (ULONG)min(DataRunLength * Vcb->NtfsInfo.BytesPerCluster - (Offset - CurrentOffset), Length); 1177 if (DataRunStartLCN == -1) 1178 { 1179 RtlZeroMemory(Buffer, ReadLength); 1180 Status = STATUS_SUCCESS; 1181 } 1182 else 1183 { 1184 Status = NtfsReadDisk(Vcb->StorageDevice, 1185 DataRunStartLCN * Vcb->NtfsInfo.BytesPerCluster + Offset - CurrentOffset, 1186 ReadLength, 1187 Vcb->NtfsInfo.BytesPerSector, 1188 (PVOID)Buffer, 1189 FALSE); 1190 } 1191 if (NT_SUCCESS(Status)) 1192 { 1193 Length -= ReadLength; 1194 Buffer += ReadLength; 1195 AlreadyRead += ReadLength; 1196 1197 if (ReadLength == DataRunLength * Vcb->NtfsInfo.BytesPerCluster - (Offset - CurrentOffset)) 1198 { 1199 CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster; 1200 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength); 1201 if (DataRunOffset != (ULONGLONG)-1) 1202 { 1203 DataRunStartLCN = LastLCN + DataRunOffset; 1204 LastLCN = DataRunStartLCN; 1205 } 1206 else 1207 DataRunStartLCN = -1; 1208 } 1209 1210 while (Length > 0) 1211 { 1212 ReadLength = (ULONG)min(DataRunLength * Vcb->NtfsInfo.BytesPerCluster, Length); 1213 if (DataRunStartLCN == -1) 1214 RtlZeroMemory(Buffer, ReadLength); 1215 else 1216 { 1217 Status = NtfsReadDisk(Vcb->StorageDevice, 1218 DataRunStartLCN * Vcb->NtfsInfo.BytesPerCluster, 1219 ReadLength, 1220 Vcb->NtfsInfo.BytesPerSector, 1221 (PVOID)Buffer, 1222 FALSE); 1223 if (!NT_SUCCESS(Status)) 1224 break; 1225 } 1226 1227 Length -= ReadLength; 1228 Buffer += ReadLength; 1229 AlreadyRead += ReadLength; 1230 1231 /* We finished this request, but there still data in this data run. */ 1232 if (Length == 0 && ReadLength != DataRunLength * Vcb->NtfsInfo.BytesPerCluster) 1233 break; 1234 1235 /* 1236 * Go to next run in the list. 1237 */ 1238 1239 if (*DataRun == 0) 1240 break; 1241 CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster; 1242 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength); 1243 if (DataRunOffset != -1) 1244 { 1245 /* Normal data run. */ 1246 DataRunStartLCN = LastLCN + DataRunOffset; 1247 LastLCN = DataRunStartLCN; 1248 } 1249 else 1250 { 1251 /* Sparse data run. */ 1252 DataRunStartLCN = -1; 1253 } 1254 } /* while */ 1255 1256 } /* if Disk */ 1257 1258 // TEMPTEMP 1259 if (Context->pRecord->IsNonResident) 1260 ExFreePoolWithTag(TempBuffer, TAG_NTFS); 1261 1262 Context->CacheRun = DataRun; 1263 Context->CacheRunOffset = Offset + AlreadyRead; 1264 Context->CacheRunStartLCN = DataRunStartLCN; 1265 Context->CacheRunLength = DataRunLength; 1266 Context->CacheRunLastLCN = LastLCN; 1267 Context->CacheRunCurrentOffset = CurrentOffset; 1268 1269 return AlreadyRead; 1270 } 1271 1272 1273 /** 1274 * @name WriteAttribute 1275 * @implemented 1276 * 1277 * Writes an NTFS attribute to the disk. It presently borrows a lot of code from ReadAttribute(), 1278 * and it still needs more documentation / cleaning up. 1279 * 1280 * @param Vcb 1281 * Volume Control Block indicating which volume to write the attribute to 1282 * 1283 * @param Context 1284 * Pointer to an NTFS_ATTR_CONTEXT that has information about the attribute 1285 * 1286 * @param Offset 1287 * Offset, in bytes, from the beginning of the attribute indicating where to start 1288 * writing data 1289 * 1290 * @param Buffer 1291 * The data that's being written to the device 1292 * 1293 * @param Length 1294 * How much data will be written, in bytes 1295 * 1296 * @param RealLengthWritten 1297 * Pointer to a ULONG which will receive how much data was written, in bytes 1298 * 1299 * @param FileRecord 1300 * Optional pointer to a FILE_RECORD_HEADER that contains a copy of the file record 1301 * being written to. Can be NULL, in which case the file record will be read from disk. 1302 * If not-null, WriteAttribute() will skip reading from disk, and FileRecord 1303 * will be updated with the newly-written attribute before the function returns. 1304 * 1305 * @return 1306 * STATUS_SUCCESS if successful, an error code otherwise. STATUS_NOT_IMPLEMENTED if 1307 * writing to a sparse file. 1308 * 1309 * @remarks Note that in this context the word "attribute" isn't referring read-only, hidden, 1310 * etc. - the file's data is actually stored in an attribute in NTFS parlance. 1311 * 1312 */ 1313 1314 NTSTATUS 1315 WriteAttribute(PDEVICE_EXTENSION Vcb, 1316 PNTFS_ATTR_CONTEXT Context, 1317 ULONGLONG Offset, 1318 const PUCHAR Buffer, 1319 ULONG Length, 1320 PULONG RealLengthWritten, 1321 PFILE_RECORD_HEADER FileRecord) 1322 { 1323 ULONGLONG LastLCN; 1324 PUCHAR DataRun; 1325 LONGLONG DataRunOffset; 1326 ULONGLONG DataRunLength; 1327 LONGLONG DataRunStartLCN; 1328 ULONGLONG CurrentOffset; 1329 ULONG WriteLength; 1330 NTSTATUS Status; 1331 PUCHAR SourceBuffer = Buffer; 1332 LONGLONG StartingOffset; 1333 BOOLEAN FileRecordAllocated = FALSE; 1334 1335 //TEMPTEMP 1336 PUCHAR TempBuffer; 1337 1338 1339 DPRINT("WriteAttribute(%p, %p, %I64u, %p, %lu, %p, %p)\n", Vcb, Context, Offset, Buffer, Length, RealLengthWritten, FileRecord); 1340 1341 *RealLengthWritten = 0; 1342 1343 // is this a resident attribute? 1344 if (!Context->pRecord->IsNonResident) 1345 { 1346 ULONG AttributeOffset; 1347 PNTFS_ATTR_CONTEXT FoundContext; 1348 PNTFS_ATTR_RECORD Destination; 1349 1350 // Ensure requested data is within the bounds of the attribute 1351 ASSERT(Offset + Length <= Context->pRecord->Resident.ValueLength); 1352 1353 if (Offset + Length > Context->pRecord->Resident.ValueLength) 1354 { 1355 DPRINT1("DRIVER ERROR: Attribute is too small!\n"); 1356 return STATUS_INVALID_PARAMETER; 1357 } 1358 1359 // Do we need to read the file record? 1360 if (FileRecord == NULL) 1361 { 1362 FileRecord = ExAllocateFromNPagedLookasideList(&Vcb->FileRecLookasideList); 1363 if (!FileRecord) 1364 { 1365 DPRINT1("Error: Couldn't allocate file record!\n"); 1366 return STATUS_NO_MEMORY; 1367 } 1368 1369 FileRecordAllocated = TRUE; 1370 1371 // read the file record 1372 ReadFileRecord(Vcb, Context->FileMFTIndex, FileRecord); 1373 } 1374 1375 // find where to write the attribute data to 1376 Status = FindAttribute(Vcb, FileRecord, 1377 Context->pRecord->Type, 1378 (PCWSTR)((ULONG_PTR)Context->pRecord + Context->pRecord->NameOffset), 1379 Context->pRecord->NameLength, 1380 &FoundContext, 1381 &AttributeOffset); 1382 1383 if (!NT_SUCCESS(Status)) 1384 { 1385 DPRINT1("ERROR: Couldn't find matching attribute!\n"); 1386 if(FileRecordAllocated) 1387 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, FileRecord); 1388 return Status; 1389 } 1390 1391 Destination = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttributeOffset); 1392 1393 DPRINT("Offset: %I64u, AttributeOffset: %u, ValueOffset: %u\n", Offset, AttributeOffset, Context->pRecord->Resident.ValueLength); 1394 1395 // Will we be writing past the end of the allocated file record? 1396 if (Offset + Length + AttributeOffset + Context->pRecord->Resident.ValueOffset > Vcb->NtfsInfo.BytesPerFileRecord) 1397 { 1398 DPRINT1("DRIVER ERROR: Data being written extends past end of file record!\n"); 1399 ReleaseAttributeContext(FoundContext); 1400 if (FileRecordAllocated) 1401 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, FileRecord); 1402 return STATUS_INVALID_PARAMETER; 1403 } 1404 1405 // copy the data being written into the file record. We cast Offset to ULONG, which is safe because it's range has been verified. 1406 RtlCopyMemory((PCHAR)((ULONG_PTR)Destination + Context->pRecord->Resident.ValueOffset + (ULONG)Offset), Buffer, Length); 1407 1408 Status = UpdateFileRecord(Vcb, Context->FileMFTIndex, FileRecord); 1409 1410 // Update the context's copy of the resident attribute 1411 ASSERT(Context->pRecord->Length == Destination->Length); 1412 RtlCopyMemory((PVOID)Context->pRecord, Destination, Context->pRecord->Length); 1413 1414 ReleaseAttributeContext(FoundContext); 1415 if (FileRecordAllocated) 1416 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, FileRecord); 1417 1418 if (NT_SUCCESS(Status)) 1419 *RealLengthWritten = Length; 1420 1421 return Status; 1422 } 1423 1424 // This is a non-resident attribute. 1425 1426 // I. Find the corresponding start data run. 1427 1428 // FIXME: Cache seems to be non-working. Disable it for now 1429 //if(Context->CacheRunOffset <= Offset && Offset < Context->CacheRunOffset + Context->CacheRunLength * Volume->ClusterSize) 1430 /*if (0) 1431 { 1432 DataRun = Context->CacheRun; 1433 LastLCN = Context->CacheRunLastLCN; 1434 DataRunStartLCN = Context->CacheRunStartLCN; 1435 DataRunLength = Context->CacheRunLength; 1436 CurrentOffset = Context->CacheRunCurrentOffset; 1437 } 1438 else*/ 1439 { 1440 ULONG UsedBufferSize; 1441 LastLCN = 0; 1442 CurrentOffset = 0; 1443 1444 // This will be rewritten in the next iteration to just use the DataRuns MCB directly 1445 TempBuffer = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS); 1446 if (TempBuffer == NULL) 1447 { 1448 return STATUS_INSUFFICIENT_RESOURCES; 1449 } 1450 1451 ConvertLargeMCBToDataRuns(&Context->DataRunsMCB, 1452 TempBuffer, 1453 Vcb->NtfsInfo.BytesPerFileRecord, 1454 &UsedBufferSize); 1455 1456 DataRun = TempBuffer; 1457 1458 while (1) 1459 { 1460 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength); 1461 if (DataRunOffset != -1) 1462 { 1463 // Normal data run. 1464 // DPRINT1("Writing to normal data run, LastLCN %I64u DataRunOffset %I64d\n", LastLCN, DataRunOffset); 1465 DataRunStartLCN = LastLCN + DataRunOffset; 1466 LastLCN = DataRunStartLCN; 1467 } 1468 else 1469 { 1470 // Sparse data run. We can't support writing to sparse files yet 1471 // (it may require increasing the allocation size). 1472 DataRunStartLCN = -1; 1473 DPRINT1("FIXME: Writing to sparse files is not supported yet!\n"); 1474 Status = STATUS_NOT_IMPLEMENTED; 1475 goto Cleanup; 1476 } 1477 1478 // Have we reached the data run we're trying to write to? 1479 if (Offset >= CurrentOffset && 1480 Offset < CurrentOffset + (DataRunLength * Vcb->NtfsInfo.BytesPerCluster)) 1481 { 1482 break; 1483 } 1484 1485 if (*DataRun == 0) 1486 { 1487 // We reached the last assigned cluster 1488 // TODO: assign new clusters to the end of the file. 1489 // (Presently, this code will rarely be reached, the write will usually have already failed by now) 1490 // [We can reach here by creating a new file record when the MFT isn't large enough] 1491 DPRINT1("FIXME: Master File Table needs to be enlarged.\n"); 1492 Status = STATUS_END_OF_FILE; 1493 goto Cleanup; 1494 } 1495 1496 CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster; 1497 } 1498 } 1499 1500 // II. Go through the run list and write the data 1501 1502 /* REVIEWME -- As adapted from NtfsReadAttribute(): 1503 We seem to be making a special case for the first applicable data run, but I'm not sure why. 1504 Does it have something to do with (not) caching? Is this strategy equally applicable to writing? */ 1505 1506 WriteLength = (ULONG)min(DataRunLength * Vcb->NtfsInfo.BytesPerCluster - (Offset - CurrentOffset), Length); 1507 1508 StartingOffset = DataRunStartLCN * Vcb->NtfsInfo.BytesPerCluster + Offset - CurrentOffset; 1509 1510 // Write the data to the disk 1511 Status = NtfsWriteDisk(Vcb->StorageDevice, 1512 StartingOffset, 1513 WriteLength, 1514 Vcb->NtfsInfo.BytesPerSector, 1515 (PVOID)SourceBuffer); 1516 1517 // Did the write fail? 1518 if (!NT_SUCCESS(Status)) 1519 { 1520 Context->CacheRun = DataRun; 1521 Context->CacheRunOffset = Offset; 1522 Context->CacheRunStartLCN = DataRunStartLCN; 1523 Context->CacheRunLength = DataRunLength; 1524 Context->CacheRunLastLCN = LastLCN; 1525 Context->CacheRunCurrentOffset = CurrentOffset; 1526 1527 goto Cleanup; 1528 } 1529 1530 Length -= WriteLength; 1531 SourceBuffer += WriteLength; 1532 *RealLengthWritten += WriteLength; 1533 1534 // Did we write to the end of the data run? 1535 if (WriteLength == DataRunLength * Vcb->NtfsInfo.BytesPerCluster - (Offset - CurrentOffset)) 1536 { 1537 // Advance to the next data run 1538 CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster; 1539 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength); 1540 1541 if (DataRunOffset != (ULONGLONG)-1) 1542 { 1543 DataRunStartLCN = LastLCN + DataRunOffset; 1544 LastLCN = DataRunStartLCN; 1545 } 1546 else 1547 DataRunStartLCN = -1; 1548 } 1549 1550 // Do we have more data to write? 1551 while (Length > 0) 1552 { 1553 // Make sure we don't write past the end of the current data run 1554 WriteLength = (ULONG)min(DataRunLength * Vcb->NtfsInfo.BytesPerCluster, Length); 1555 1556 // Are we dealing with a sparse data run? 1557 if (DataRunStartLCN == -1) 1558 { 1559 DPRINT1("FIXME: Don't know how to write to sparse files yet! (DataRunStartLCN == -1)\n"); 1560 Status = STATUS_NOT_IMPLEMENTED; 1561 goto Cleanup; 1562 } 1563 else 1564 { 1565 // write the data to the disk 1566 Status = NtfsWriteDisk(Vcb->StorageDevice, 1567 DataRunStartLCN * Vcb->NtfsInfo.BytesPerCluster, 1568 WriteLength, 1569 Vcb->NtfsInfo.BytesPerSector, 1570 (PVOID)SourceBuffer); 1571 if (!NT_SUCCESS(Status)) 1572 break; 1573 } 1574 1575 Length -= WriteLength; 1576 SourceBuffer += WriteLength; 1577 *RealLengthWritten += WriteLength; 1578 1579 // We finished this request, but there's still data in this data run. 1580 if (Length == 0 && WriteLength != DataRunLength * Vcb->NtfsInfo.BytesPerCluster) 1581 break; 1582 1583 // Go to next run in the list. 1584 1585 if (*DataRun == 0) 1586 { 1587 // that was the last run 1588 if (Length > 0) 1589 { 1590 // Failed sanity check. 1591 DPRINT1("Encountered EOF before expected!\n"); 1592 Status = STATUS_END_OF_FILE; 1593 goto Cleanup; 1594 } 1595 1596 break; 1597 } 1598 1599 // Advance to the next data run 1600 CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster; 1601 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength); 1602 if (DataRunOffset != -1) 1603 { 1604 // Normal data run. 1605 DataRunStartLCN = LastLCN + DataRunOffset; 1606 LastLCN = DataRunStartLCN; 1607 } 1608 else 1609 { 1610 // Sparse data run. 1611 DataRunStartLCN = -1; 1612 } 1613 } // end while (Length > 0) [more data to write] 1614 1615 Context->CacheRun = DataRun; 1616 Context->CacheRunOffset = Offset + *RealLengthWritten; 1617 Context->CacheRunStartLCN = DataRunStartLCN; 1618 Context->CacheRunLength = DataRunLength; 1619 Context->CacheRunLastLCN = LastLCN; 1620 Context->CacheRunCurrentOffset = CurrentOffset; 1621 1622 Cleanup: 1623 // TEMPTEMP 1624 if (Context->pRecord->IsNonResident) 1625 ExFreePoolWithTag(TempBuffer, TAG_NTFS); 1626 1627 return Status; 1628 } 1629 1630 NTSTATUS 1631 ReadFileRecord(PDEVICE_EXTENSION Vcb, 1632 ULONGLONG index, 1633 PFILE_RECORD_HEADER file) 1634 { 1635 ULONGLONG BytesRead; 1636 1637 DPRINT("ReadFileRecord(%p, %I64x, %p)\n", Vcb, index, file); 1638 1639 BytesRead = ReadAttribute(Vcb, Vcb->MFTContext, index * Vcb->NtfsInfo.BytesPerFileRecord, (PCHAR)file, Vcb->NtfsInfo.BytesPerFileRecord); 1640 if (BytesRead != Vcb->NtfsInfo.BytesPerFileRecord) 1641 { 1642 DPRINT1("ReadFileRecord failed: %I64u read, %lu expected\n", BytesRead, Vcb->NtfsInfo.BytesPerFileRecord); 1643 return STATUS_PARTIAL_COPY; 1644 } 1645 1646 /* Apply update sequence array fixups. */ 1647 DPRINT("Sequence number: %u\n", file->SequenceNumber); 1648 return FixupUpdateSequenceArray(Vcb, &file->Ntfs); 1649 } 1650 1651 1652 /** 1653 * Searches a file's parent directory (given the parent's index in the mft) 1654 * for the given file. Upon finding an index entry for that file, updates 1655 * Data Size and Allocated Size values in the $FILE_NAME attribute of that entry. 1656 * 1657 * (Most of this code was copied from NtfsFindMftRecord) 1658 */ 1659 NTSTATUS 1660 UpdateFileNameRecord(PDEVICE_EXTENSION Vcb, 1661 ULONGLONG ParentMFTIndex, 1662 PUNICODE_STRING FileName, 1663 BOOLEAN DirSearch, 1664 ULONGLONG NewDataSize, 1665 ULONGLONG NewAllocationSize, 1666 BOOLEAN CaseSensitive) 1667 { 1668 PFILE_RECORD_HEADER MftRecord; 1669 PNTFS_ATTR_CONTEXT IndexRootCtx; 1670 PINDEX_ROOT_ATTRIBUTE IndexRoot; 1671 PCHAR IndexRecord; 1672 PINDEX_ENTRY_ATTRIBUTE IndexEntry, IndexEntryEnd; 1673 NTSTATUS Status; 1674 ULONG CurrentEntry = 0; 1675 1676 DPRINT("UpdateFileNameRecord(%p, %I64d, %wZ, %s, %I64u, %I64u, %s)\n", 1677 Vcb, 1678 ParentMFTIndex, 1679 FileName, 1680 DirSearch ? "TRUE" : "FALSE", 1681 NewDataSize, 1682 NewAllocationSize, 1683 CaseSensitive ? "TRUE" : "FALSE"); 1684 1685 MftRecord = ExAllocateFromNPagedLookasideList(&Vcb->FileRecLookasideList); 1686 if (MftRecord == NULL) 1687 { 1688 return STATUS_INSUFFICIENT_RESOURCES; 1689 } 1690 1691 Status = ReadFileRecord(Vcb, ParentMFTIndex, MftRecord); 1692 if (!NT_SUCCESS(Status)) 1693 { 1694 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MftRecord); 1695 return Status; 1696 } 1697 1698 ASSERT(MftRecord->Ntfs.Type == NRH_FILE_TYPE); 1699 Status = FindAttribute(Vcb, MftRecord, AttributeIndexRoot, L"$I30", 4, &IndexRootCtx, NULL); 1700 if (!NT_SUCCESS(Status)) 1701 { 1702 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MftRecord); 1703 return Status; 1704 } 1705 1706 IndexRecord = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerIndexRecord, TAG_NTFS); 1707 if (IndexRecord == NULL) 1708 { 1709 ReleaseAttributeContext(IndexRootCtx); 1710 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MftRecord); 1711 return STATUS_INSUFFICIENT_RESOURCES; 1712 } 1713 1714 Status = ReadAttribute(Vcb, IndexRootCtx, 0, IndexRecord, AttributeDataLength(IndexRootCtx->pRecord)); 1715 if (!NT_SUCCESS(Status)) 1716 { 1717 DPRINT1("ERROR: Failed to read Index Root!\n"); 1718 ExFreePoolWithTag(IndexRecord, TAG_NTFS); 1719 ReleaseAttributeContext(IndexRootCtx); 1720 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MftRecord); 1721 return Status; 1722 } 1723 1724 IndexRoot = (PINDEX_ROOT_ATTRIBUTE)IndexRecord; 1725 IndexEntry = (PINDEX_ENTRY_ATTRIBUTE)((PCHAR)&IndexRoot->Header + IndexRoot->Header.FirstEntryOffset); 1726 // Index root is always resident. 1727 IndexEntryEnd = (PINDEX_ENTRY_ATTRIBUTE)(IndexRecord + IndexRoot->Header.TotalSizeOfEntries); 1728 1729 DPRINT("IndexRecordSize: %x IndexBlockSize: %x\n", Vcb->NtfsInfo.BytesPerIndexRecord, IndexRoot->SizeOfEntry); 1730 1731 Status = UpdateIndexEntryFileNameSize(Vcb, 1732 MftRecord, 1733 IndexRecord, 1734 IndexRoot->SizeOfEntry, 1735 IndexEntry, 1736 IndexEntryEnd, 1737 FileName, 1738 &CurrentEntry, 1739 &CurrentEntry, 1740 DirSearch, 1741 NewDataSize, 1742 NewAllocationSize, 1743 CaseSensitive); 1744 1745 if (Status == STATUS_PENDING) 1746 { 1747 // we need to write the index root attribute back to disk 1748 ULONG LengthWritten; 1749 Status = WriteAttribute(Vcb, IndexRootCtx, 0, (PUCHAR)IndexRecord, AttributeDataLength(IndexRootCtx->pRecord), &LengthWritten, MftRecord); 1750 if (!NT_SUCCESS(Status)) 1751 { 1752 DPRINT1("ERROR: Couldn't update Index Root!\n"); 1753 } 1754 1755 } 1756 1757 ReleaseAttributeContext(IndexRootCtx); 1758 ExFreePoolWithTag(IndexRecord, TAG_NTFS); 1759 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MftRecord); 1760 1761 return Status; 1762 } 1763 1764 /** 1765 * Recursively searches directory index and applies the size update to the $FILE_NAME attribute of the 1766 * proper index entry. 1767 * (Heavily based on BrowseIndexEntries) 1768 */ 1769 NTSTATUS 1770 UpdateIndexEntryFileNameSize(PDEVICE_EXTENSION Vcb, 1771 PFILE_RECORD_HEADER MftRecord, 1772 PCHAR IndexRecord, 1773 ULONG IndexBlockSize, 1774 PINDEX_ENTRY_ATTRIBUTE FirstEntry, 1775 PINDEX_ENTRY_ATTRIBUTE LastEntry, 1776 PUNICODE_STRING FileName, 1777 PULONG StartEntry, 1778 PULONG CurrentEntry, 1779 BOOLEAN DirSearch, 1780 ULONGLONG NewDataSize, 1781 ULONGLONG NewAllocatedSize, 1782 BOOLEAN CaseSensitive) 1783 { 1784 NTSTATUS Status; 1785 ULONG RecordOffset; 1786 PINDEX_ENTRY_ATTRIBUTE IndexEntry; 1787 PNTFS_ATTR_CONTEXT IndexAllocationCtx; 1788 ULONGLONG IndexAllocationSize; 1789 PINDEX_BUFFER IndexBuffer; 1790 1791 DPRINT("UpdateIndexEntrySize(%p, %p, %p, %lu, %p, %p, %wZ, %lu, %lu, %s, %I64u, %I64u, %s)\n", 1792 Vcb, 1793 MftRecord, 1794 IndexRecord, 1795 IndexBlockSize, 1796 FirstEntry, 1797 LastEntry, 1798 FileName, 1799 *StartEntry, 1800 *CurrentEntry, 1801 DirSearch ? "TRUE" : "FALSE", 1802 NewDataSize, 1803 NewAllocatedSize, 1804 CaseSensitive ? "TRUE" : "FALSE"); 1805 1806 // find the index entry responsible for the file we're trying to update 1807 IndexEntry = FirstEntry; 1808 while (IndexEntry < LastEntry && 1809 !(IndexEntry->Flags & NTFS_INDEX_ENTRY_END)) 1810 { 1811 if ((IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK) > NTFS_FILE_FIRST_USER_FILE && 1812 *CurrentEntry >= *StartEntry && 1813 IndexEntry->FileName.NameType != NTFS_FILE_NAME_DOS && 1814 CompareFileName(FileName, IndexEntry, DirSearch, CaseSensitive)) 1815 { 1816 *StartEntry = *CurrentEntry; 1817 IndexEntry->FileName.DataSize = NewDataSize; 1818 IndexEntry->FileName.AllocatedSize = NewAllocatedSize; 1819 // indicate that the caller will still need to write the structure to the disk 1820 return STATUS_PENDING; 1821 } 1822 1823 (*CurrentEntry) += 1; 1824 ASSERT(IndexEntry->Length >= sizeof(INDEX_ENTRY_ATTRIBUTE)); 1825 IndexEntry = (PINDEX_ENTRY_ATTRIBUTE)((PCHAR)IndexEntry + IndexEntry->Length); 1826 } 1827 1828 /* If we're already browsing a subnode */ 1829 if (IndexRecord == NULL) 1830 { 1831 return STATUS_OBJECT_PATH_NOT_FOUND; 1832 } 1833 1834 /* If there's no subnode */ 1835 if (!(IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE)) 1836 { 1837 return STATUS_OBJECT_PATH_NOT_FOUND; 1838 } 1839 1840 Status = FindAttribute(Vcb, MftRecord, AttributeIndexAllocation, L"$I30", 4, &IndexAllocationCtx, NULL); 1841 if (!NT_SUCCESS(Status)) 1842 { 1843 DPRINT("Corrupted filesystem!\n"); 1844 return Status; 1845 } 1846 1847 IndexAllocationSize = AttributeDataLength(IndexAllocationCtx->pRecord); 1848 Status = STATUS_OBJECT_PATH_NOT_FOUND; 1849 for (RecordOffset = 0; RecordOffset < IndexAllocationSize; RecordOffset += IndexBlockSize) 1850 { 1851 ReadAttribute(Vcb, IndexAllocationCtx, RecordOffset, IndexRecord, IndexBlockSize); 1852 Status = FixupUpdateSequenceArray(Vcb, &((PFILE_RECORD_HEADER)IndexRecord)->Ntfs); 1853 if (!NT_SUCCESS(Status)) 1854 { 1855 break; 1856 } 1857 1858 IndexBuffer = (PINDEX_BUFFER)IndexRecord; 1859 ASSERT(IndexBuffer->Ntfs.Type == NRH_INDX_TYPE); 1860 ASSERT(IndexBuffer->Header.AllocatedSize + FIELD_OFFSET(INDEX_BUFFER, Header) == IndexBlockSize); 1861 FirstEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)&IndexBuffer->Header + IndexBuffer->Header.FirstEntryOffset); 1862 LastEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)&IndexBuffer->Header + IndexBuffer->Header.TotalSizeOfEntries); 1863 ASSERT(LastEntry <= (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)IndexBuffer + IndexBlockSize)); 1864 1865 Status = UpdateIndexEntryFileNameSize(NULL, 1866 NULL, 1867 NULL, 1868 0, 1869 FirstEntry, 1870 LastEntry, 1871 FileName, 1872 StartEntry, 1873 CurrentEntry, 1874 DirSearch, 1875 NewDataSize, 1876 NewAllocatedSize, 1877 CaseSensitive); 1878 if (Status == STATUS_PENDING) 1879 { 1880 // write the index record back to disk 1881 ULONG Written; 1882 1883 // first we need to update the fixup values for the index block 1884 Status = AddFixupArray(Vcb, &((PFILE_RECORD_HEADER)IndexRecord)->Ntfs); 1885 if (!NT_SUCCESS(Status)) 1886 { 1887 DPRINT1("Error: Failed to update fixup sequence array!\n"); 1888 break; 1889 } 1890 1891 Status = WriteAttribute(Vcb, IndexAllocationCtx, RecordOffset, (const PUCHAR)IndexRecord, IndexBlockSize, &Written, MftRecord); 1892 if (!NT_SUCCESS(Status)) 1893 { 1894 DPRINT1("ERROR Performing write!\n"); 1895 break; 1896 } 1897 1898 Status = STATUS_SUCCESS; 1899 break; 1900 } 1901 if (NT_SUCCESS(Status)) 1902 { 1903 break; 1904 } 1905 } 1906 1907 ReleaseAttributeContext(IndexAllocationCtx); 1908 return Status; 1909 } 1910 1911 /** 1912 * @name UpdateFileRecord 1913 * @implemented 1914 * 1915 * Writes a file record to the master file table, at a given index. 1916 * 1917 * @param Vcb 1918 * Pointer to the DEVICE_EXTENSION of the target drive being written to. 1919 * 1920 * @param MftIndex 1921 * Target index in the master file table to store the file record. 1922 * 1923 * @param FileRecord 1924 * Pointer to the complete file record which will be written to the master file table. 1925 * 1926 * @return 1927 * STATUS_SUCCESSFUL on success. An error passed from WriteAttribute() otherwise. 1928 * 1929 */ 1930 NTSTATUS 1931 UpdateFileRecord(PDEVICE_EXTENSION Vcb, 1932 ULONGLONG MftIndex, 1933 PFILE_RECORD_HEADER FileRecord) 1934 { 1935 ULONG BytesWritten; 1936 NTSTATUS Status = STATUS_SUCCESS; 1937 1938 DPRINT("UpdateFileRecord(%p, 0x%I64x, %p)\n", Vcb, MftIndex, FileRecord); 1939 1940 // Add the fixup array to prepare the data for writing to disk 1941 AddFixupArray(Vcb, &FileRecord->Ntfs); 1942 1943 // write the file record to the master file table 1944 Status = WriteAttribute(Vcb, 1945 Vcb->MFTContext, 1946 MftIndex * Vcb->NtfsInfo.BytesPerFileRecord, 1947 (const PUCHAR)FileRecord, 1948 Vcb->NtfsInfo.BytesPerFileRecord, 1949 &BytesWritten, 1950 FileRecord); 1951 1952 if (!NT_SUCCESS(Status)) 1953 { 1954 DPRINT1("UpdateFileRecord failed: %lu written, %lu expected\n", BytesWritten, Vcb->NtfsInfo.BytesPerFileRecord); 1955 } 1956 1957 // remove the fixup array (so the file record pointer can still be used) 1958 FixupUpdateSequenceArray(Vcb, &FileRecord->Ntfs); 1959 1960 return Status; 1961 } 1962 1963 1964 NTSTATUS 1965 FixupUpdateSequenceArray(PDEVICE_EXTENSION Vcb, 1966 PNTFS_RECORD_HEADER Record) 1967 { 1968 USHORT *USA; 1969 USHORT USANumber; 1970 USHORT USACount; 1971 USHORT *Block; 1972 1973 USA = (USHORT*)((PCHAR)Record + Record->UsaOffset); 1974 USANumber = *(USA++); 1975 USACount = Record->UsaCount - 1; /* Exclude the USA Number. */ 1976 Block = (USHORT*)((PCHAR)Record + Vcb->NtfsInfo.BytesPerSector - 2); 1977 1978 DPRINT("FixupUpdateSequenceArray(%p, %p)\nUSANumber: %u\tUSACount: %u\n", Vcb, Record, USANumber, USACount); 1979 1980 while (USACount) 1981 { 1982 if (*Block != USANumber) 1983 { 1984 DPRINT1("Mismatch with USA: %u read, %u expected\n" , *Block, USANumber); 1985 return STATUS_UNSUCCESSFUL; 1986 } 1987 *Block = *(USA++); 1988 Block = (USHORT*)((PCHAR)Block + Vcb->NtfsInfo.BytesPerSector); 1989 USACount--; 1990 } 1991 1992 return STATUS_SUCCESS; 1993 } 1994 1995 /** 1996 * @name AddNewMftEntry 1997 * @implemented 1998 * 1999 * Adds a file record to the master file table of a given device. 2000 * 2001 * @param FileRecord 2002 * Pointer to a complete file record which will be saved to disk. 2003 * 2004 * @param DeviceExt 2005 * Pointer to the DEVICE_EXTENSION of the target drive. 2006 * 2007 * @param DestinationIndex 2008 * Pointer to a ULONGLONG which will receive the MFT index where the file record was stored. 2009 * 2010 * @param CanWait 2011 * Boolean indicating if the function is allowed to wait for exclusive access to the master file table. 2012 * This will only be relevant if the MFT doesn't have any free file records and needs to be enlarged. 2013 * 2014 * @return 2015 * STATUS_SUCCESS on success. 2016 * STATUS_OBJECT_NAME_NOT_FOUND if we can't find the MFT's $Bitmap or if we weren't able 2017 * to read the attribute. 2018 * STATUS_INSUFFICIENT_RESOURCES if we can't allocate enough memory for a copy of $Bitmap. 2019 * STATUS_CANT_WAIT if CanWait was FALSE and the function could not get immediate, exclusive access to the MFT. 2020 */ 2021 NTSTATUS 2022 AddNewMftEntry(PFILE_RECORD_HEADER FileRecord, 2023 PDEVICE_EXTENSION DeviceExt, 2024 PULONGLONG DestinationIndex, 2025 BOOLEAN CanWait) 2026 { 2027 NTSTATUS Status = STATUS_SUCCESS; 2028 ULONGLONG MftIndex; 2029 RTL_BITMAP Bitmap; 2030 ULONGLONG BitmapDataSize; 2031 ULONGLONG AttrBytesRead; 2032 PUCHAR BitmapData; 2033 PUCHAR BitmapBuffer; 2034 ULONG LengthWritten; 2035 PNTFS_ATTR_CONTEXT BitmapContext; 2036 LARGE_INTEGER BitmapBits; 2037 UCHAR SystemReservedBits; 2038 2039 DPRINT1("AddNewMftEntry(%p, %p, %p, %s)\n", FileRecord, DeviceExt, DestinationIndex, CanWait ? "TRUE" : "FALSE"); 2040 2041 // First, we have to read the mft's $Bitmap attribute 2042 2043 // Find the attribute 2044 Status = FindAttribute(DeviceExt, DeviceExt->MasterFileTable, AttributeBitmap, L"", 0, &BitmapContext, NULL); 2045 if (!NT_SUCCESS(Status)) 2046 { 2047 DPRINT1("ERROR: Couldn't find $Bitmap attribute of master file table!\n"); 2048 return Status; 2049 } 2050 2051 // Get size of bitmap 2052 BitmapDataSize = AttributeDataLength(BitmapContext->pRecord); 2053 2054 // RtlInitializeBitmap wants a ULONG-aligned pointer, and wants the memory passed to it to be a ULONG-multiple 2055 // Allocate a buffer for the $Bitmap attribute plus enough to ensure we can get a ULONG-aligned pointer 2056 BitmapBuffer = ExAllocatePoolWithTag(NonPagedPool, BitmapDataSize + sizeof(ULONG), TAG_NTFS); 2057 if (!BitmapBuffer) 2058 { 2059 ReleaseAttributeContext(BitmapContext); 2060 return STATUS_INSUFFICIENT_RESOURCES; 2061 } 2062 RtlZeroMemory(BitmapBuffer, BitmapDataSize + sizeof(ULONG)); 2063 2064 // Get a ULONG-aligned pointer for the bitmap itself 2065 BitmapData = (PUCHAR)ALIGN_UP_BY((ULONG_PTR)BitmapBuffer, sizeof(ULONG)); 2066 2067 // read $Bitmap attribute 2068 AttrBytesRead = ReadAttribute(DeviceExt, BitmapContext, 0, (PCHAR)BitmapData, BitmapDataSize); 2069 2070 if (AttrBytesRead != BitmapDataSize) 2071 { 2072 DPRINT1("ERROR: Unable to read $Bitmap attribute of master file table!\n"); 2073 ExFreePoolWithTag(BitmapBuffer, TAG_NTFS); 2074 ReleaseAttributeContext(BitmapContext); 2075 return STATUS_OBJECT_NAME_NOT_FOUND; 2076 } 2077 2078 // We need to backup the bits for records 0x10 - 0x17 (3rd byte of bitmap) and mark these records 2079 // as in-use so we don't assign files to those indices. They're reserved for the system (e.g. ChkDsk). 2080 SystemReservedBits = BitmapData[2]; 2081 BitmapData[2] = 0xff; 2082 2083 // Calculate bit count 2084 BitmapBits.QuadPart = AttributeDataLength(DeviceExt->MFTContext->pRecord) / 2085 DeviceExt->NtfsInfo.BytesPerFileRecord; 2086 if (BitmapBits.HighPart != 0) 2087 { 2088 DPRINT1("\tFIXME: bitmap sizes beyond 32bits are not yet supported! (Your NTFS volume is too large)\n"); 2089 NtfsGlobalData->EnableWriteSupport = FALSE; 2090 ExFreePoolWithTag(BitmapBuffer, TAG_NTFS); 2091 ReleaseAttributeContext(BitmapContext); 2092 return STATUS_NOT_IMPLEMENTED; 2093 } 2094 2095 // convert buffer into bitmap 2096 RtlInitializeBitMap(&Bitmap, (PULONG)BitmapData, BitmapBits.LowPart); 2097 2098 // set next available bit, preferrably after 23rd bit 2099 MftIndex = RtlFindClearBitsAndSet(&Bitmap, 1, 24); 2100 if ((LONG)MftIndex == -1) 2101 { 2102 DPRINT1("Couldn't find free space in MFT for file record, increasing MFT size.\n"); 2103 2104 ExFreePoolWithTag(BitmapBuffer, TAG_NTFS); 2105 ReleaseAttributeContext(BitmapContext); 2106 2107 // Couldn't find a free record in the MFT, add some blank records and try again 2108 Status = IncreaseMftSize(DeviceExt, CanWait); 2109 if (!NT_SUCCESS(Status)) 2110 { 2111 DPRINT1("ERROR: Couldn't find space in MFT for file or increase MFT size!\n"); 2112 return Status; 2113 } 2114 2115 return AddNewMftEntry(FileRecord, DeviceExt, DestinationIndex, CanWait); 2116 } 2117 2118 DPRINT1("Creating file record at MFT index: %I64u\n", MftIndex); 2119 2120 // update file record with index 2121 FileRecord->MFTRecordNumber = MftIndex; 2122 2123 // [BitmapData should have been updated via RtlFindClearBitsAndSet()] 2124 2125 // Restore the system reserved bits 2126 BitmapData[2] = SystemReservedBits; 2127 2128 // write the bitmap back to the MFT's $Bitmap attribute 2129 Status = WriteAttribute(DeviceExt, BitmapContext, 0, BitmapData, BitmapDataSize, &LengthWritten, FileRecord); 2130 if (!NT_SUCCESS(Status)) 2131 { 2132 DPRINT1("ERROR encountered when writing $Bitmap attribute!\n"); 2133 ExFreePoolWithTag(BitmapBuffer, TAG_NTFS); 2134 ReleaseAttributeContext(BitmapContext); 2135 return Status; 2136 } 2137 2138 // update the file record (write it to disk) 2139 Status = UpdateFileRecord(DeviceExt, MftIndex, FileRecord); 2140 2141 if (!NT_SUCCESS(Status)) 2142 { 2143 DPRINT1("ERROR: Unable to write file record!\n"); 2144 ExFreePoolWithTag(BitmapBuffer, TAG_NTFS); 2145 ReleaseAttributeContext(BitmapContext); 2146 return Status; 2147 } 2148 2149 *DestinationIndex = MftIndex; 2150 2151 ExFreePoolWithTag(BitmapBuffer, TAG_NTFS); 2152 ReleaseAttributeContext(BitmapContext); 2153 2154 return Status; 2155 } 2156 2157 /** 2158 * @name NtfsAddFilenameToDirectory 2159 * @implemented 2160 * 2161 * Adds a $FILE_NAME attribute to a given directory index. 2162 * 2163 * @param DeviceExt 2164 * Points to the target disk's DEVICE_EXTENSION. 2165 * 2166 * @param DirectoryMftIndex 2167 * Mft index of the parent directory which will receive the file. 2168 * 2169 * @param FileReferenceNumber 2170 * File reference of the file to be added to the directory. This is a combination of the 2171 * Mft index and sequence number. 2172 * 2173 * @param FilenameAttribute 2174 * Pointer to the FILENAME_ATTRIBUTE of the file being added to the directory. 2175 * 2176 * @param CaseSensitive 2177 * Boolean indicating if the function should operate in case-sensitive mode. This will be TRUE 2178 * if an application created the file with the FILE_FLAG_POSIX_SEMANTICS flag. 2179 * 2180 * @return 2181 * STATUS_SUCCESS on success. 2182 * STATUS_INSUFFICIENT_RESOURCES if an allocation fails. 2183 * STATUS_NOT_IMPLEMENTED if target address isn't at the end of the given file record. 2184 * 2185 * @remarks 2186 * WIP - Can only support a few files in a directory. 2187 * One FILENAME_ATTRIBUTE is added to the directory's index for each link to that file. So, each 2188 * file which contains one FILENAME_ATTRIBUTE for a long name and another for the 8.3 name, will 2189 * get both attributes added to its parent directory. 2190 */ 2191 NTSTATUS 2192 NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt, 2193 ULONGLONG DirectoryMftIndex, 2194 ULONGLONG FileReferenceNumber, 2195 PFILENAME_ATTRIBUTE FilenameAttribute, 2196 BOOLEAN CaseSensitive) 2197 { 2198 NTSTATUS Status = STATUS_SUCCESS; 2199 PFILE_RECORD_HEADER ParentFileRecord; 2200 PNTFS_ATTR_CONTEXT IndexRootContext; 2201 PINDEX_ROOT_ATTRIBUTE I30IndexRoot; 2202 ULONG IndexRootOffset; 2203 ULONGLONG I30IndexRootLength; 2204 ULONG LengthWritten; 2205 PINDEX_ROOT_ATTRIBUTE NewIndexRoot; 2206 ULONG AttributeLength; 2207 PNTFS_ATTR_RECORD NextAttribute; 2208 PB_TREE NewTree; 2209 ULONG BtreeIndexLength; 2210 ULONG MaxIndexRootSize; 2211 PB_TREE_KEY NewLeftKey; 2212 PB_TREE_FILENAME_NODE NewRightHandNode; 2213 LARGE_INTEGER MinIndexRootSize; 2214 ULONG NewMaxIndexRootSize; 2215 ULONG NodeSize; 2216 2217 // Allocate memory for the parent directory 2218 ParentFileRecord = ExAllocateFromNPagedLookasideList(&DeviceExt->FileRecLookasideList); 2219 if (!ParentFileRecord) 2220 { 2221 DPRINT1("ERROR: Couldn't allocate memory for file record!\n"); 2222 return STATUS_INSUFFICIENT_RESOURCES; 2223 } 2224 2225 // Open the parent directory 2226 Status = ReadFileRecord(DeviceExt, DirectoryMftIndex, ParentFileRecord); 2227 if (!NT_SUCCESS(Status)) 2228 { 2229 ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord); 2230 DPRINT1("ERROR: Couldn't read parent directory with index %I64u\n", 2231 DirectoryMftIndex); 2232 return Status; 2233 } 2234 2235 #ifndef NDEBUG 2236 DPRINT1("Dumping old parent file record:\n"); 2237 NtfsDumpFileRecord(DeviceExt, ParentFileRecord); 2238 #endif 2239 2240 // Find the index root attribute for the directory 2241 Status = FindAttribute(DeviceExt, 2242 ParentFileRecord, 2243 AttributeIndexRoot, 2244 L"$I30", 2245 4, 2246 &IndexRootContext, 2247 &IndexRootOffset); 2248 if (!NT_SUCCESS(Status)) 2249 { 2250 DPRINT1("ERROR: Couldn't find $I30 $INDEX_ROOT attribute for parent directory with MFT #: %I64u!\n", 2251 DirectoryMftIndex); 2252 ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord); 2253 return Status; 2254 } 2255 2256 // Find the maximum index size given what the file record can hold 2257 // First, find the max index size assuming index root is the last attribute 2258 MaxIndexRootSize = DeviceExt->NtfsInfo.BytesPerFileRecord // Start with the size of a file record 2259 - IndexRootOffset // Subtract the length of everything that comes before index root 2260 - IndexRootContext->pRecord->Resident.ValueOffset // Subtract the length of the attribute header for index root 2261 - sizeof(INDEX_ROOT_ATTRIBUTE) // Subtract the length of the index root header 2262 - (sizeof(ULONG) * 2); // Subtract the length of the file record end marker and padding 2263 2264 // Are there attributes after this one? 2265 NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)ParentFileRecord + IndexRootOffset + IndexRootContext->pRecord->Length); 2266 if (NextAttribute->Type != AttributeEnd) 2267 { 2268 // Find the length of all attributes after this one, not counting the end marker 2269 ULONG LengthOfAttributes = 0; 2270 PNTFS_ATTR_RECORD CurrentAttribute = NextAttribute; 2271 while (CurrentAttribute->Type != AttributeEnd) 2272 { 2273 LengthOfAttributes += CurrentAttribute->Length; 2274 CurrentAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)CurrentAttribute + CurrentAttribute->Length); 2275 } 2276 2277 // Leave room for the existing attributes 2278 MaxIndexRootSize -= LengthOfAttributes; 2279 } 2280 2281 // Allocate memory for the index root data 2282 I30IndexRootLength = AttributeDataLength(IndexRootContext->pRecord); 2283 I30IndexRoot = ExAllocatePoolWithTag(NonPagedPool, I30IndexRootLength, TAG_NTFS); 2284 if (!I30IndexRoot) 2285 { 2286 DPRINT1("ERROR: Couldn't allocate memory for index root attribute!\n"); 2287 ReleaseAttributeContext(IndexRootContext); 2288 ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord); 2289 return STATUS_INSUFFICIENT_RESOURCES; 2290 } 2291 2292 // Read the Index Root 2293 Status = ReadAttribute(DeviceExt, IndexRootContext, 0, (PCHAR)I30IndexRoot, I30IndexRootLength); 2294 if (!NT_SUCCESS(Status)) 2295 { 2296 DPRINT1("ERROR: Couln't read index root attribute for Mft index #%I64u\n", DirectoryMftIndex); 2297 ReleaseAttributeContext(IndexRootContext); 2298 ExFreePoolWithTag(I30IndexRoot, TAG_NTFS); 2299 ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord); 2300 return Status; 2301 } 2302 2303 // Convert the index to a B*Tree 2304 Status = CreateBTreeFromIndex(DeviceExt, 2305 ParentFileRecord, 2306 IndexRootContext, 2307 I30IndexRoot, 2308 &NewTree); 2309 if (!NT_SUCCESS(Status)) 2310 { 2311 DPRINT1("ERROR: Failed to create B-Tree from Index!\n"); 2312 ReleaseAttributeContext(IndexRootContext); 2313 ExFreePoolWithTag(I30IndexRoot, TAG_NTFS); 2314 ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord); 2315 return Status; 2316 } 2317 2318 #ifndef NDEBUG 2319 DumpBTree(NewTree); 2320 #endif 2321 2322 // Insert the key for the file we're adding 2323 Status = NtfsInsertKey(NewTree, 2324 FileReferenceNumber, 2325 FilenameAttribute, 2326 NewTree->RootNode, 2327 CaseSensitive, 2328 MaxIndexRootSize, 2329 I30IndexRoot->SizeOfEntry, 2330 &NewLeftKey, 2331 &NewRightHandNode); 2332 if (!NT_SUCCESS(Status)) 2333 { 2334 DPRINT1("ERROR: Failed to insert key into B-Tree!\n"); 2335 DestroyBTree(NewTree); 2336 ReleaseAttributeContext(IndexRootContext); 2337 ExFreePoolWithTag(I30IndexRoot, TAG_NTFS); 2338 ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord); 2339 return Status; 2340 } 2341 2342 #ifndef NDEBUG 2343 DumpBTree(NewTree); 2344 #endif 2345 2346 // The root node can't be split 2347 ASSERT(NewLeftKey == NULL); 2348 ASSERT(NewRightHandNode == NULL); 2349 2350 // Convert B*Tree back to Index 2351 2352 // Updating the index allocation can change the size available for the index root, 2353 // And if the index root is demoted, the index allocation will need to be updated again, 2354 // which may change the size available for index root... etc. 2355 // My solution is to decrease index root to the size it would be if it was demoted, 2356 // then UpdateIndexAllocation will have an accurate representation of the maximum space 2357 // it can use in the file record. There's still a chance that the act of allocating an 2358 // index node after demoting the index root will increase the size of the file record beyond 2359 // it's limit, but if that happens, an attribute-list will most definitely be needed. 2360 // This a bit hacky, but it seems to be functional. 2361 2362 // Calculate the minimum size of the index root attribute, considering one dummy key and one VCN 2363 MinIndexRootSize.QuadPart = sizeof(INDEX_ROOT_ATTRIBUTE) // size of the index root headers 2364 + 0x18; // Size of dummy key with a VCN for a subnode 2365 ASSERT(MinIndexRootSize.QuadPart % ATTR_RECORD_ALIGNMENT == 0); 2366 2367 // Temporarily shrink the index root to it's minimal size 2368 AttributeLength = MinIndexRootSize.LowPart; 2369 AttributeLength += sizeof(INDEX_ROOT_ATTRIBUTE); 2370 2371 2372 // FIXME: IndexRoot will probably be invalid until we're finished. If we fail before we finish, the directory will probably be toast. 2373 // The potential for catastrophic data-loss exists!!! :) 2374 2375 // Update the length of the attribute in the file record of the parent directory 2376 Status = InternalSetResidentAttributeLength(DeviceExt, 2377 IndexRootContext, 2378 ParentFileRecord, 2379 IndexRootOffset, 2380 AttributeLength); 2381 if (!NT_SUCCESS(Status)) 2382 { 2383 DPRINT1("ERROR: Unable to set length of index root!\n"); 2384 DestroyBTree(NewTree); 2385 ReleaseAttributeContext(IndexRootContext); 2386 ExFreePoolWithTag(I30IndexRoot, TAG_NTFS); 2387 ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord); 2388 return Status; 2389 } 2390 2391 // Update the index allocation 2392 Status = UpdateIndexAllocation(DeviceExt, NewTree, I30IndexRoot->SizeOfEntry, ParentFileRecord); 2393 if (!NT_SUCCESS(Status)) 2394 { 2395 DPRINT1("ERROR: Failed to update index allocation from B-Tree!\n"); 2396 DestroyBTree(NewTree); 2397 ReleaseAttributeContext(IndexRootContext); 2398 ExFreePoolWithTag(I30IndexRoot, TAG_NTFS); 2399 ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord); 2400 return Status; 2401 } 2402 2403 #ifndef NDEBUG 2404 DPRINT1("Index Allocation updated\n"); 2405 DumpBTree(NewTree); 2406 #endif 2407 2408 // Find the maximum index root size given what the file record can hold 2409 // First, find the max index size assuming index root is the last attribute 2410 NewMaxIndexRootSize = 2411 DeviceExt->NtfsInfo.BytesPerFileRecord // Start with the size of a file record 2412 - IndexRootOffset // Subtract the length of everything that comes before index root 2413 - IndexRootContext->pRecord->Resident.ValueOffset // Subtract the length of the attribute header for index root 2414 - sizeof(INDEX_ROOT_ATTRIBUTE) // Subtract the length of the index root header 2415 - (sizeof(ULONG) * 2); // Subtract the length of the file record end marker and padding 2416 2417 // Are there attributes after this one? 2418 NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)ParentFileRecord + IndexRootOffset + IndexRootContext->pRecord->Length); 2419 if (NextAttribute->Type != AttributeEnd) 2420 { 2421 // Find the length of all attributes after this one, not counting the end marker 2422 ULONG LengthOfAttributes = 0; 2423 PNTFS_ATTR_RECORD CurrentAttribute = NextAttribute; 2424 while (CurrentAttribute->Type != AttributeEnd) 2425 { 2426 LengthOfAttributes += CurrentAttribute->Length; 2427 CurrentAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)CurrentAttribute + CurrentAttribute->Length); 2428 } 2429 2430 // Leave room for the existing attributes 2431 NewMaxIndexRootSize -= LengthOfAttributes; 2432 } 2433 2434 // The index allocation and index bitmap may have grown, leaving less room for the index root, 2435 // so now we need to double-check that index root isn't too large 2436 NodeSize = GetSizeOfIndexEntries(NewTree->RootNode); 2437 if (NodeSize > NewMaxIndexRootSize) 2438 { 2439 DPRINT1("Demoting index root.\nNodeSize: 0x%lx\nNewMaxIndexRootSize: 0x%lx\n", NodeSize, NewMaxIndexRootSize); 2440 2441 Status = DemoteBTreeRoot(NewTree); 2442 if (!NT_SUCCESS(Status)) 2443 { 2444 DPRINT1("ERROR: Failed to demote index root!\n"); 2445 DestroyBTree(NewTree); 2446 ReleaseAttributeContext(IndexRootContext); 2447 ExFreePoolWithTag(I30IndexRoot, TAG_NTFS); 2448 ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord); 2449 return Status; 2450 } 2451 2452 // We need to update the index allocation once more 2453 Status = UpdateIndexAllocation(DeviceExt, NewTree, I30IndexRoot->SizeOfEntry, ParentFileRecord); 2454 if (!NT_SUCCESS(Status)) 2455 { 2456 DPRINT1("ERROR: Failed to update index allocation from B-Tree!\n"); 2457 DestroyBTree(NewTree); 2458 ReleaseAttributeContext(IndexRootContext); 2459 ExFreePoolWithTag(I30IndexRoot, TAG_NTFS); 2460 ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord); 2461 return Status; 2462 } 2463 2464 // re-recalculate max size of index root 2465 NewMaxIndexRootSize = 2466 // Find the maximum index size given what the file record can hold 2467 // First, find the max index size assuming index root is the last attribute 2468 DeviceExt->NtfsInfo.BytesPerFileRecord // Start with the size of a file record 2469 - IndexRootOffset // Subtract the length of everything that comes before index root 2470 - IndexRootContext->pRecord->Resident.ValueOffset // Subtract the length of the attribute header for index root 2471 - sizeof(INDEX_ROOT_ATTRIBUTE) // Subtract the length of the index root header 2472 - (sizeof(ULONG) * 2); // Subtract the length of the file record end marker and padding 2473 2474 // Are there attributes after this one? 2475 NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)ParentFileRecord + IndexRootOffset + IndexRootContext->pRecord->Length); 2476 if (NextAttribute->Type != AttributeEnd) 2477 { 2478 // Find the length of all attributes after this one, not counting the end marker 2479 ULONG LengthOfAttributes = 0; 2480 PNTFS_ATTR_RECORD CurrentAttribute = NextAttribute; 2481 while (CurrentAttribute->Type != AttributeEnd) 2482 { 2483 LengthOfAttributes += CurrentAttribute->Length; 2484 CurrentAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)CurrentAttribute + CurrentAttribute->Length); 2485 } 2486 2487 // Leave room for the existing attributes 2488 NewMaxIndexRootSize -= LengthOfAttributes; 2489 } 2490 2491 2492 } 2493 2494 // Create the Index Root from the B*Tree 2495 Status = CreateIndexRootFromBTree(DeviceExt, NewTree, NewMaxIndexRootSize, &NewIndexRoot, &BtreeIndexLength); 2496 if (!NT_SUCCESS(Status)) 2497 { 2498 DPRINT1("ERROR: Failed to create Index root from B-Tree!\n"); 2499 DestroyBTree(NewTree); 2500 ReleaseAttributeContext(IndexRootContext); 2501 ExFreePoolWithTag(I30IndexRoot, TAG_NTFS); 2502 ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord); 2503 return Status; 2504 } 2505 2506 // We're done with the B-Tree now 2507 DestroyBTree(NewTree); 2508 2509 // Write back the new index root attribute to the parent directory file record 2510 2511 // First, we need to resize the attribute. 2512 // CreateIndexRootFromBTree() should have verified that the index root fits within MaxIndexSize. 2513 // We can't set the size as we normally would, because $INDEX_ROOT must always be resident. 2514 AttributeLength = NewIndexRoot->Header.AllocatedSize + FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE, Header); 2515 2516 if (AttributeLength != IndexRootContext->pRecord->Resident.ValueLength) 2517 { 2518 // Update the length of the attribute in the file record of the parent directory 2519 Status = InternalSetResidentAttributeLength(DeviceExt, 2520 IndexRootContext, 2521 ParentFileRecord, 2522 IndexRootOffset, 2523 AttributeLength); 2524 if (!NT_SUCCESS(Status)) 2525 { 2526 ExFreePoolWithTag(NewIndexRoot, TAG_NTFS); 2527 ReleaseAttributeContext(IndexRootContext); 2528 ExFreePoolWithTag(I30IndexRoot, TAG_NTFS); 2529 ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord); 2530 DPRINT1("ERROR: Unable to set resident attribute length!\n"); 2531 return Status; 2532 } 2533 2534 } 2535 2536 NT_ASSERT(ParentFileRecord->BytesInUse <= DeviceExt->NtfsInfo.BytesPerFileRecord); 2537 2538 Status = UpdateFileRecord(DeviceExt, DirectoryMftIndex, ParentFileRecord); 2539 if (!NT_SUCCESS(Status)) 2540 { 2541 DPRINT1("ERROR: Failed to update file record of directory with index: %llx\n", DirectoryMftIndex); 2542 ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord); 2543 ExFreePoolWithTag(NewIndexRoot, TAG_NTFS); 2544 ReleaseAttributeContext(IndexRootContext); 2545 ExFreePoolWithTag(I30IndexRoot, TAG_NTFS); 2546 return Status; 2547 } 2548 2549 // Write the new index root to disk 2550 Status = WriteAttribute(DeviceExt, 2551 IndexRootContext, 2552 0, 2553 (PUCHAR)NewIndexRoot, 2554 AttributeLength, 2555 &LengthWritten, 2556 ParentFileRecord); 2557 if (!NT_SUCCESS(Status) || LengthWritten != AttributeLength) 2558 { 2559 DPRINT1("ERROR: Unable to write new index root attribute to parent directory!\n"); 2560 ExFreePoolWithTag(NewIndexRoot, TAG_NTFS); 2561 ReleaseAttributeContext(IndexRootContext); 2562 ExFreePoolWithTag(I30IndexRoot, TAG_NTFS); 2563 ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord); 2564 return Status; 2565 } 2566 2567 // re-read the parent file record, so we can dump it 2568 Status = ReadFileRecord(DeviceExt, DirectoryMftIndex, ParentFileRecord); 2569 if (!NT_SUCCESS(Status)) 2570 { 2571 DPRINT1("ERROR: Couldn't read parent directory after messing with it!\n"); 2572 } 2573 else 2574 { 2575 #ifndef NDEBUG 2576 DPRINT1("Dumping new B-Tree:\n"); 2577 2578 Status = CreateBTreeFromIndex(DeviceExt, ParentFileRecord, IndexRootContext, NewIndexRoot, &NewTree); 2579 if (!NT_SUCCESS(Status)) 2580 { 2581 DPRINT1("ERROR: Couldn't re-create b-tree\n"); 2582 return Status; 2583 } 2584 2585 DumpBTree(NewTree); 2586 2587 DestroyBTree(NewTree); 2588 2589 NtfsDumpFileRecord(DeviceExt, ParentFileRecord); 2590 #endif 2591 } 2592 2593 // Cleanup 2594 ExFreePoolWithTag(NewIndexRoot, TAG_NTFS); 2595 ReleaseAttributeContext(IndexRootContext); 2596 ExFreePoolWithTag(I30IndexRoot, TAG_NTFS); 2597 ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord); 2598 2599 return Status; 2600 } 2601 2602 NTSTATUS 2603 AddFixupArray(PDEVICE_EXTENSION Vcb, 2604 PNTFS_RECORD_HEADER Record) 2605 { 2606 USHORT *pShortToFixUp; 2607 ULONG ArrayEntryCount = Record->UsaCount - 1; 2608 ULONG Offset = Vcb->NtfsInfo.BytesPerSector - 2; 2609 ULONG i; 2610 2611 PFIXUP_ARRAY fixupArray = (PFIXUP_ARRAY)((UCHAR*)Record + Record->UsaOffset); 2612 2613 DPRINT("AddFixupArray(%p, %p)\n fixupArray->USN: %u, ArrayEntryCount: %u\n", Vcb, Record, fixupArray->USN, ArrayEntryCount); 2614 2615 fixupArray->USN++; 2616 2617 for (i = 0; i < ArrayEntryCount; i++) 2618 { 2619 DPRINT("USN: %u\tOffset: %u\n", fixupArray->USN, Offset); 2620 2621 pShortToFixUp = (USHORT*)((PCHAR)Record + Offset); 2622 fixupArray->Array[i] = *pShortToFixUp; 2623 *pShortToFixUp = fixupArray->USN; 2624 Offset += Vcb->NtfsInfo.BytesPerSector; 2625 } 2626 2627 return STATUS_SUCCESS; 2628 } 2629 2630 NTSTATUS 2631 ReadLCN(PDEVICE_EXTENSION Vcb, 2632 ULONGLONG lcn, 2633 ULONG count, 2634 PVOID buffer) 2635 { 2636 LARGE_INTEGER DiskSector; 2637 2638 DiskSector.QuadPart = lcn; 2639 2640 return NtfsReadSectors(Vcb->StorageDevice, 2641 DiskSector.u.LowPart * Vcb->NtfsInfo.SectorsPerCluster, 2642 count * Vcb->NtfsInfo.SectorsPerCluster, 2643 Vcb->NtfsInfo.BytesPerSector, 2644 buffer, 2645 FALSE); 2646 } 2647 2648 2649 BOOLEAN 2650 CompareFileName(PUNICODE_STRING FileName, 2651 PINDEX_ENTRY_ATTRIBUTE IndexEntry, 2652 BOOLEAN DirSearch, 2653 BOOLEAN CaseSensitive) 2654 { 2655 BOOLEAN Ret, Alloc = FALSE; 2656 UNICODE_STRING EntryName; 2657 2658 EntryName.Buffer = IndexEntry->FileName.Name; 2659 EntryName.Length = 2660 EntryName.MaximumLength = IndexEntry->FileName.NameLength * sizeof(WCHAR); 2661 2662 if (DirSearch) 2663 { 2664 UNICODE_STRING IntFileName; 2665 if (!CaseSensitive) 2666 { 2667 NT_VERIFY(NT_SUCCESS(RtlUpcaseUnicodeString(&IntFileName, FileName, TRUE))); 2668 Alloc = TRUE; 2669 } 2670 else 2671 { 2672 IntFileName = *FileName; 2673 } 2674 2675 Ret = FsRtlIsNameInExpression(&IntFileName, &EntryName, !CaseSensitive, NULL); 2676 2677 if (Alloc) 2678 { 2679 RtlFreeUnicodeString(&IntFileName); 2680 } 2681 2682 return Ret; 2683 } 2684 else 2685 { 2686 return (RtlCompareUnicodeString(FileName, &EntryName, !CaseSensitive) == 0); 2687 } 2688 } 2689 2690 /** 2691 * @name UpdateMftMirror 2692 * @implemented 2693 * 2694 * Backs-up the first ~4 master file table entries to the $MFTMirr file. 2695 * 2696 * @param Vcb 2697 * Pointer to an NTFS_VCB for the volume whose Mft mirror is being updated. 2698 * 2699 * @return 2700 2701 * STATUS_SUCCESS on success. 2702 * STATUS_INSUFFICIENT_RESOURCES if an allocation failed. 2703 * STATUS_UNSUCCESSFUL if we couldn't read the master file table. 2704 * 2705 * @remarks 2706 * NTFS maintains up-to-date copies of the first several mft entries in the $MFTMirr file. Usually, the first 4 file 2707 * records from the mft are stored. The exact number of entries is determined by the size of $MFTMirr's $DATA. 2708 * If $MFTMirr is not up-to-date, chkdsk will reject every change it can find prior to when $MFTMirr was last updated. 2709 * Therefore, it's recommended to call this function if the volume changes considerably. For instance, IncreaseMftSize() 2710 * relies on this function to keep chkdsk from deleting the mft entries it creates. Note that under most instances, creating 2711 * or deleting a file will not affect the first ~four mft entries, and so will not require updating the mft mirror. 2712 */ 2713 NTSTATUS 2714 UpdateMftMirror(PNTFS_VCB Vcb) 2715 { 2716 PFILE_RECORD_HEADER MirrorFileRecord; 2717 PNTFS_ATTR_CONTEXT MirrDataContext; 2718 PNTFS_ATTR_CONTEXT MftDataContext; 2719 PCHAR DataBuffer; 2720 ULONGLONG DataLength; 2721 NTSTATUS Status; 2722 ULONG BytesRead; 2723 ULONG LengthWritten; 2724 2725 // Allocate memory for the Mft mirror file record 2726 MirrorFileRecord = ExAllocateFromNPagedLookasideList(&Vcb->FileRecLookasideList); 2727 if (!MirrorFileRecord) 2728 { 2729 DPRINT1("Error: Failed to allocate memory for $MFTMirr!\n"); 2730 return STATUS_INSUFFICIENT_RESOURCES; 2731 } 2732 2733 // Read the Mft Mirror file record 2734 Status = ReadFileRecord(Vcb, NTFS_FILE_MFTMIRR, MirrorFileRecord); 2735 if (!NT_SUCCESS(Status)) 2736 { 2737 DPRINT1("ERROR: Failed to read $MFTMirr!\n"); 2738 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MirrorFileRecord); 2739 return Status; 2740 } 2741 2742 // Find the $DATA attribute of $MFTMirr 2743 Status = FindAttribute(Vcb, MirrorFileRecord, AttributeData, L"", 0, &MirrDataContext, NULL); 2744 if (!NT_SUCCESS(Status)) 2745 { 2746 DPRINT1("ERROR: Couldn't find $DATA attribute!\n"); 2747 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MirrorFileRecord); 2748 return Status; 2749 } 2750 2751 // Find the $DATA attribute of $MFT 2752 Status = FindAttribute(Vcb, Vcb->MasterFileTable, AttributeData, L"", 0, &MftDataContext, NULL); 2753 if (!NT_SUCCESS(Status)) 2754 { 2755 DPRINT1("ERROR: Couldn't find $DATA attribute!\n"); 2756 ReleaseAttributeContext(MirrDataContext); 2757 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MirrorFileRecord); 2758 return Status; 2759 } 2760 2761 // Get the size of the mirror's $DATA attribute 2762 DataLength = AttributeDataLength(MirrDataContext->pRecord); 2763 2764 ASSERT(DataLength % Vcb->NtfsInfo.BytesPerFileRecord == 0); 2765 2766 // Create buffer for the mirror's $DATA attribute 2767 DataBuffer = ExAllocatePoolWithTag(NonPagedPool, DataLength, TAG_NTFS); 2768 if (!DataBuffer) 2769 { 2770 DPRINT1("Error: Couldn't allocate memory for $DATA buffer!\n"); 2771 ReleaseAttributeContext(MftDataContext); 2772 ReleaseAttributeContext(MirrDataContext); 2773 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MirrorFileRecord); 2774 return STATUS_INSUFFICIENT_RESOURCES; 2775 } 2776 2777 ASSERT(DataLength < ULONG_MAX); 2778 2779 // Back up the first several entries of the Mft's $DATA Attribute 2780 BytesRead = ReadAttribute(Vcb, MftDataContext, 0, DataBuffer, (ULONG)DataLength); 2781 if (BytesRead != (ULONG)DataLength) 2782 { 2783 DPRINT1("Error: Failed to read $DATA for $MFTMirr!\n"); 2784 ReleaseAttributeContext(MftDataContext); 2785 ReleaseAttributeContext(MirrDataContext); 2786 ExFreePoolWithTag(DataBuffer, TAG_NTFS); 2787 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MirrorFileRecord); 2788 return STATUS_UNSUCCESSFUL; 2789 } 2790 2791 // Write the mirror's $DATA attribute 2792 Status = WriteAttribute(Vcb, 2793 MirrDataContext, 2794 0, 2795 (PUCHAR)DataBuffer, 2796 DataLength, 2797 &LengthWritten, 2798 MirrorFileRecord); 2799 if (!NT_SUCCESS(Status)) 2800 { 2801 DPRINT1("ERROR: Failed to write $DATA attribute of $MFTMirr!\n"); 2802 } 2803 2804 // Cleanup 2805 ReleaseAttributeContext(MftDataContext); 2806 ReleaseAttributeContext(MirrDataContext); 2807 ExFreePoolWithTag(DataBuffer, TAG_NTFS); 2808 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MirrorFileRecord); 2809 2810 return Status; 2811 } 2812 2813 #if 0 2814 static 2815 VOID 2816 DumpIndexEntry(PINDEX_ENTRY_ATTRIBUTE IndexEntry) 2817 { 2818 DPRINT1("Entry: %p\n", IndexEntry); 2819 DPRINT1("\tData.Directory.IndexedFile: %I64x\n", IndexEntry->Data.Directory.IndexedFile); 2820 DPRINT1("\tLength: %u\n", IndexEntry->Length); 2821 DPRINT1("\tKeyLength: %u\n", IndexEntry->KeyLength); 2822 DPRINT1("\tFlags: %x\n", IndexEntry->Flags); 2823 DPRINT1("\tReserved: %x\n", IndexEntry->Reserved); 2824 DPRINT1("\t\tDirectoryFileReferenceNumber: %I64x\n", IndexEntry->FileName.DirectoryFileReferenceNumber); 2825 DPRINT1("\t\tCreationTime: %I64u\n", IndexEntry->FileName.CreationTime); 2826 DPRINT1("\t\tChangeTime: %I64u\n", IndexEntry->FileName.ChangeTime); 2827 DPRINT1("\t\tLastWriteTime: %I64u\n", IndexEntry->FileName.LastWriteTime); 2828 DPRINT1("\t\tLastAccessTime: %I64u\n", IndexEntry->FileName.LastAccessTime); 2829 DPRINT1("\t\tAllocatedSize: %I64u\n", IndexEntry->FileName.AllocatedSize); 2830 DPRINT1("\t\tDataSize: %I64u\n", IndexEntry->FileName.DataSize); 2831 DPRINT1("\t\tFileAttributes: %x\n", IndexEntry->FileName.FileAttributes); 2832 DPRINT1("\t\tNameLength: %u\n", IndexEntry->FileName.NameLength); 2833 DPRINT1("\t\tNameType: %x\n", IndexEntry->FileName.NameType); 2834 DPRINT1("\t\tName: %.*S\n", IndexEntry->FileName.NameLength, IndexEntry->FileName.Name); 2835 } 2836 #endif 2837 2838 NTSTATUS 2839 BrowseSubNodeIndexEntries(PNTFS_VCB Vcb, 2840 PFILE_RECORD_HEADER MftRecord, 2841 ULONG IndexBlockSize, 2842 PUNICODE_STRING FileName, 2843 PNTFS_ATTR_CONTEXT IndexAllocationContext, 2844 PRTL_BITMAP Bitmap, 2845 ULONGLONG VCN, 2846 PULONG StartEntry, 2847 PULONG CurrentEntry, 2848 BOOLEAN DirSearch, 2849 BOOLEAN CaseSensitive, 2850 ULONGLONG *OutMFTIndex) 2851 { 2852 PINDEX_BUFFER IndexRecord; 2853 ULONGLONG Offset; 2854 ULONG BytesRead; 2855 PINDEX_ENTRY_ATTRIBUTE FirstEntry; 2856 PINDEX_ENTRY_ATTRIBUTE LastEntry; 2857 PINDEX_ENTRY_ATTRIBUTE IndexEntry; 2858 ULONG NodeNumber; 2859 NTSTATUS Status; 2860 2861 DPRINT("BrowseSubNodeIndexEntries(%p, %p, %lu, %wZ, %p, %p, %I64d, %lu, %lu, %s, %s, %p)\n", 2862 Vcb, 2863 MftRecord, 2864 IndexBlockSize, 2865 FileName, 2866 IndexAllocationContext, 2867 Bitmap, 2868 VCN, 2869 *StartEntry, 2870 *CurrentEntry, 2871 "FALSE", 2872 DirSearch ? "TRUE" : "FALSE", 2873 CaseSensitive ? "TRUE" : "FALSE", 2874 OutMFTIndex); 2875 2876 // Calculate node number as VCN / Clusters per index record 2877 NodeNumber = VCN / (Vcb->NtfsInfo.BytesPerIndexRecord / Vcb->NtfsInfo.BytesPerCluster); 2878 2879 // Is the bit for this node clear in the bitmap? 2880 if (!RtlCheckBit(Bitmap, NodeNumber)) 2881 { 2882 DPRINT1("File system corruption detected, node with VCN %I64u is marked as deleted.\n", VCN); 2883 return STATUS_DATA_ERROR; 2884 } 2885 2886 // Allocate memory for the index record 2887 IndexRecord = ExAllocatePoolWithTag(NonPagedPool, IndexBlockSize, TAG_NTFS); 2888 if (!IndexRecord) 2889 { 2890 DPRINT1("Unable to allocate memory for index record!\n"); 2891 return STATUS_INSUFFICIENT_RESOURCES; 2892 } 2893 2894 // Calculate offset of index record 2895 Offset = VCN * Vcb->NtfsInfo.BytesPerCluster; 2896 2897 // Read the index record 2898 BytesRead = ReadAttribute(Vcb, IndexAllocationContext, Offset, (PCHAR)IndexRecord, IndexBlockSize); 2899 if (BytesRead != IndexBlockSize) 2900 { 2901 DPRINT1("Unable to read index record!\n"); 2902 ExFreePoolWithTag(IndexRecord, TAG_NTFS); 2903 return STATUS_UNSUCCESSFUL; 2904 } 2905 2906 // Assert that we're dealing with an index record here 2907 ASSERT(IndexRecord->Ntfs.Type == NRH_INDX_TYPE); 2908 2909 // Apply the fixup array to the index record 2910 Status = FixupUpdateSequenceArray(Vcb, &((PFILE_RECORD_HEADER)IndexRecord)->Ntfs); 2911 if (!NT_SUCCESS(Status)) 2912 { 2913 ExFreePoolWithTag(IndexRecord, TAG_NTFS); 2914 DPRINT1("Failed to apply fixup array!\n"); 2915 return Status; 2916 } 2917 2918 ASSERT(IndexRecord->Header.AllocatedSize + FIELD_OFFSET(INDEX_BUFFER, Header) == IndexBlockSize); 2919 FirstEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)&IndexRecord->Header + IndexRecord->Header.FirstEntryOffset); 2920 LastEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)&IndexRecord->Header + IndexRecord->Header.TotalSizeOfEntries); 2921 ASSERT(LastEntry <= (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)IndexRecord + IndexBlockSize)); 2922 2923 // Loop through all Index Entries of index, starting with FirstEntry 2924 IndexEntry = FirstEntry; 2925 while (IndexEntry <= LastEntry) 2926 { 2927 // Does IndexEntry have a sub-node? 2928 if (IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE) 2929 { 2930 if (!(IndexRecord->Header.Flags & INDEX_NODE_LARGE) || !IndexAllocationContext) 2931 { 2932 DPRINT1("Filesystem corruption detected!\n"); 2933 } 2934 else 2935 { 2936 Status = BrowseSubNodeIndexEntries(Vcb, 2937 MftRecord, 2938 IndexBlockSize, 2939 FileName, 2940 IndexAllocationContext, 2941 Bitmap, 2942 GetIndexEntryVCN(IndexEntry), 2943 StartEntry, 2944 CurrentEntry, 2945 DirSearch, 2946 CaseSensitive, 2947 OutMFTIndex); 2948 if (NT_SUCCESS(Status)) 2949 { 2950 ExFreePoolWithTag(IndexRecord, TAG_NTFS); 2951 return Status; 2952 } 2953 } 2954 } 2955 2956 // Are we done? 2957 if (IndexEntry->Flags & NTFS_INDEX_ENTRY_END) 2958 break; 2959 2960 // If we've found a file whose index is greater than or equal to StartEntry that matches the search criteria 2961 if ((IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK) >= NTFS_FILE_FIRST_USER_FILE && 2962 *CurrentEntry >= *StartEntry && 2963 IndexEntry->FileName.NameType != NTFS_FILE_NAME_DOS && 2964 CompareFileName(FileName, IndexEntry, DirSearch, CaseSensitive)) 2965 { 2966 *StartEntry = *CurrentEntry; 2967 *OutMFTIndex = (IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK); 2968 ExFreePoolWithTag(IndexRecord, TAG_NTFS); 2969 return STATUS_SUCCESS; 2970 } 2971 2972 // Advance to the next index entry 2973 (*CurrentEntry) += 1; 2974 ASSERT(IndexEntry->Length >= sizeof(INDEX_ENTRY_ATTRIBUTE)); 2975 IndexEntry = (PINDEX_ENTRY_ATTRIBUTE)((PCHAR)IndexEntry + IndexEntry->Length); 2976 } 2977 2978 ExFreePoolWithTag(IndexRecord, TAG_NTFS); 2979 2980 return STATUS_OBJECT_PATH_NOT_FOUND; 2981 } 2982 2983 NTSTATUS 2984 BrowseIndexEntries(PDEVICE_EXTENSION Vcb, 2985 PFILE_RECORD_HEADER MftRecord, 2986 PINDEX_ROOT_ATTRIBUTE IndexRecord, 2987 ULONG IndexBlockSize, 2988 PINDEX_ENTRY_ATTRIBUTE FirstEntry, 2989 PINDEX_ENTRY_ATTRIBUTE LastEntry, 2990 PUNICODE_STRING FileName, 2991 PULONG StartEntry, 2992 PULONG CurrentEntry, 2993 BOOLEAN DirSearch, 2994 BOOLEAN CaseSensitive, 2995 ULONGLONG *OutMFTIndex) 2996 { 2997 NTSTATUS Status; 2998 PINDEX_ENTRY_ATTRIBUTE IndexEntry; 2999 PNTFS_ATTR_CONTEXT IndexAllocationContext; 3000 PNTFS_ATTR_CONTEXT BitmapContext; 3001 PCHAR *BitmapMem; 3002 ULONG *BitmapPtr; 3003 RTL_BITMAP Bitmap; 3004 3005 DPRINT("BrowseIndexEntries(%p, %p, %p, %lu, %p, %p, %wZ, %lu, %lu, %s, %s, %p)\n", 3006 Vcb, 3007 MftRecord, 3008 IndexRecord, 3009 IndexBlockSize, 3010 FirstEntry, 3011 LastEntry, 3012 FileName, 3013 *StartEntry, 3014 *CurrentEntry, 3015 DirSearch ? "TRUE" : "FALSE", 3016 CaseSensitive ? "TRUE" : "FALSE", 3017 OutMFTIndex); 3018 3019 // Find the $I30 index allocation, if there is one 3020 Status = FindAttribute(Vcb, MftRecord, AttributeIndexAllocation, L"$I30", 4, &IndexAllocationContext, NULL); 3021 if (NT_SUCCESS(Status)) 3022 { 3023 ULONGLONG BitmapLength; 3024 // Find the bitmap attribute for the index 3025 Status = FindAttribute(Vcb, MftRecord, AttributeBitmap, L"$I30", 4, &BitmapContext, NULL); 3026 if (!NT_SUCCESS(Status)) 3027 { 3028 DPRINT1("Potential file system corruption detected!\n"); 3029 ReleaseAttributeContext(IndexAllocationContext); 3030 return Status; 3031 } 3032 3033 // Get the length of the bitmap attribute 3034 BitmapLength = AttributeDataLength(BitmapContext->pRecord); 3035 3036 // Allocate memory for the bitmap, including some padding; RtlInitializeBitmap() wants a pointer 3037 // that's ULONG-aligned, and it wants the size of the memory allocated for it to be a ULONG-multiple. 3038 BitmapMem = ExAllocatePoolWithTag(NonPagedPool, BitmapLength + sizeof(ULONG), TAG_NTFS); 3039 if (!BitmapMem) 3040 { 3041 DPRINT1("Error: failed to allocate bitmap!"); 3042 ReleaseAttributeContext(BitmapContext); 3043 ReleaseAttributeContext(IndexAllocationContext); 3044 return STATUS_INSUFFICIENT_RESOURCES; 3045 } 3046 3047 RtlZeroMemory(BitmapMem, BitmapLength + sizeof(ULONG)); 3048 3049 // RtlInitializeBitmap() wants a pointer that's ULONG-aligned. 3050 BitmapPtr = (PULONG)ALIGN_UP_BY((ULONG_PTR)BitmapMem, sizeof(ULONG)); 3051 3052 // Read the existing bitmap data 3053 Status = ReadAttribute(Vcb, BitmapContext, 0, (PCHAR)BitmapPtr, BitmapLength); 3054 if (!NT_SUCCESS(Status)) 3055 { 3056 DPRINT1("ERROR: Failed to read bitmap attribute!\n"); 3057 ExFreePoolWithTag(BitmapMem, TAG_NTFS); 3058 ReleaseAttributeContext(BitmapContext); 3059 ReleaseAttributeContext(IndexAllocationContext); 3060 return Status; 3061 } 3062 3063 // Initialize bitmap 3064 RtlInitializeBitMap(&Bitmap, BitmapPtr, BitmapLength * 8); 3065 } 3066 else 3067 { 3068 // Couldn't find an index allocation 3069 IndexAllocationContext = NULL; 3070 } 3071 3072 3073 // Loop through all Index Entries of index, starting with FirstEntry 3074 IndexEntry = FirstEntry; 3075 while (IndexEntry <= LastEntry) 3076 { 3077 // Does IndexEntry have a sub-node? 3078 if (IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE) 3079 { 3080 if (!(IndexRecord->Header.Flags & INDEX_ROOT_LARGE) || !IndexAllocationContext) 3081 { 3082 DPRINT1("Filesystem corruption detected!\n"); 3083 } 3084 else 3085 { 3086 Status = BrowseSubNodeIndexEntries(Vcb, 3087 MftRecord, 3088 IndexBlockSize, 3089 FileName, 3090 IndexAllocationContext, 3091 &Bitmap, 3092 GetIndexEntryVCN(IndexEntry), 3093 StartEntry, 3094 CurrentEntry, 3095 DirSearch, 3096 CaseSensitive, 3097 OutMFTIndex); 3098 if (NT_SUCCESS(Status)) 3099 { 3100 ExFreePoolWithTag(BitmapMem, TAG_NTFS); 3101 ReleaseAttributeContext(BitmapContext); 3102 ReleaseAttributeContext(IndexAllocationContext); 3103 return Status; 3104 } 3105 } 3106 } 3107 3108 // Are we done? 3109 if (IndexEntry->Flags & NTFS_INDEX_ENTRY_END) 3110 break; 3111 3112 // If we've found a file whose index is greater than or equal to StartEntry that matches the search criteria 3113 if ((IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK) >= NTFS_FILE_FIRST_USER_FILE && 3114 *CurrentEntry >= *StartEntry && 3115 IndexEntry->FileName.NameType != NTFS_FILE_NAME_DOS && 3116 CompareFileName(FileName, IndexEntry, DirSearch, CaseSensitive)) 3117 { 3118 *StartEntry = *CurrentEntry; 3119 *OutMFTIndex = (IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK); 3120 if (IndexAllocationContext) 3121 { 3122 ExFreePoolWithTag(BitmapMem, TAG_NTFS); 3123 ReleaseAttributeContext(BitmapContext); 3124 ReleaseAttributeContext(IndexAllocationContext); 3125 } 3126 return STATUS_SUCCESS; 3127 } 3128 3129 // Advance to the next index entry 3130 (*CurrentEntry) += 1; 3131 ASSERT(IndexEntry->Length >= sizeof(INDEX_ENTRY_ATTRIBUTE)); 3132 IndexEntry = (PINDEX_ENTRY_ATTRIBUTE)((PCHAR)IndexEntry + IndexEntry->Length); 3133 } 3134 3135 if (IndexAllocationContext) 3136 { 3137 ExFreePoolWithTag(BitmapMem, TAG_NTFS); 3138 ReleaseAttributeContext(BitmapContext); 3139 ReleaseAttributeContext(IndexAllocationContext); 3140 } 3141 3142 return STATUS_OBJECT_PATH_NOT_FOUND; 3143 } 3144 3145 NTSTATUS 3146 NtfsFindMftRecord(PDEVICE_EXTENSION Vcb, 3147 ULONGLONG MFTIndex, 3148 PUNICODE_STRING FileName, 3149 PULONG FirstEntry, 3150 BOOLEAN DirSearch, 3151 BOOLEAN CaseSensitive, 3152 ULONGLONG *OutMFTIndex) 3153 { 3154 PFILE_RECORD_HEADER MftRecord; 3155 PNTFS_ATTR_CONTEXT IndexRootCtx; 3156 PINDEX_ROOT_ATTRIBUTE IndexRoot; 3157 PCHAR IndexRecord; 3158 PINDEX_ENTRY_ATTRIBUTE IndexEntry, IndexEntryEnd; 3159 NTSTATUS Status; 3160 ULONG CurrentEntry = 0; 3161 3162 DPRINT("NtfsFindMftRecord(%p, %I64d, %wZ, %lu, %s, %s, %p)\n", 3163 Vcb, 3164 MFTIndex, 3165 FileName, 3166 *FirstEntry, 3167 DirSearch ? "TRUE" : "FALSE", 3168 CaseSensitive ? "TRUE" : "FALSE", 3169 OutMFTIndex); 3170 3171 MftRecord = ExAllocateFromNPagedLookasideList(&Vcb->FileRecLookasideList); 3172 if (MftRecord == NULL) 3173 { 3174 return STATUS_INSUFFICIENT_RESOURCES; 3175 } 3176 3177 Status = ReadFileRecord(Vcb, MFTIndex, MftRecord); 3178 if (!NT_SUCCESS(Status)) 3179 { 3180 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MftRecord); 3181 return Status; 3182 } 3183 3184 ASSERT(MftRecord->Ntfs.Type == NRH_FILE_TYPE); 3185 Status = FindAttribute(Vcb, MftRecord, AttributeIndexRoot, L"$I30", 4, &IndexRootCtx, NULL); 3186 if (!NT_SUCCESS(Status)) 3187 { 3188 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MftRecord); 3189 return Status; 3190 } 3191 3192 IndexRecord = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerIndexRecord, TAG_NTFS); 3193 if (IndexRecord == NULL) 3194 { 3195 ReleaseAttributeContext(IndexRootCtx); 3196 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MftRecord); 3197 return STATUS_INSUFFICIENT_RESOURCES; 3198 } 3199 3200 ReadAttribute(Vcb, IndexRootCtx, 0, IndexRecord, Vcb->NtfsInfo.BytesPerIndexRecord); 3201 IndexRoot = (PINDEX_ROOT_ATTRIBUTE)IndexRecord; 3202 IndexEntry = (PINDEX_ENTRY_ATTRIBUTE)((PCHAR)&IndexRoot->Header + IndexRoot->Header.FirstEntryOffset); 3203 /* Index root is always resident. */ 3204 IndexEntryEnd = (PINDEX_ENTRY_ATTRIBUTE)(IndexRecord + IndexRoot->Header.TotalSizeOfEntries); 3205 ReleaseAttributeContext(IndexRootCtx); 3206 3207 DPRINT("IndexRecordSize: %x IndexBlockSize: %x\n", Vcb->NtfsInfo.BytesPerIndexRecord, IndexRoot->SizeOfEntry); 3208 3209 Status = BrowseIndexEntries(Vcb, 3210 MftRecord, 3211 (PINDEX_ROOT_ATTRIBUTE)IndexRecord, 3212 IndexRoot->SizeOfEntry, 3213 IndexEntry, 3214 IndexEntryEnd, 3215 FileName, 3216 FirstEntry, 3217 &CurrentEntry, 3218 DirSearch, 3219 CaseSensitive, 3220 OutMFTIndex); 3221 3222 ExFreePoolWithTag(IndexRecord, TAG_NTFS); 3223 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MftRecord); 3224 3225 return Status; 3226 } 3227 3228 NTSTATUS 3229 NtfsLookupFileAt(PDEVICE_EXTENSION Vcb, 3230 PUNICODE_STRING PathName, 3231 BOOLEAN CaseSensitive, 3232 PFILE_RECORD_HEADER *FileRecord, 3233 PULONGLONG MFTIndex, 3234 ULONGLONG CurrentMFTIndex) 3235 { 3236 UNICODE_STRING Current, Remaining; 3237 NTSTATUS Status; 3238 ULONG FirstEntry = 0; 3239 3240 DPRINT("NtfsLookupFileAt(%p, %wZ, %s, %p, %p, %I64x)\n", 3241 Vcb, 3242 PathName, 3243 CaseSensitive ? "TRUE" : "FALSE", 3244 FileRecord, 3245 MFTIndex, 3246 CurrentMFTIndex); 3247 3248 FsRtlDissectName(*PathName, &Current, &Remaining); 3249 3250 while (Current.Length != 0) 3251 { 3252 DPRINT("Current: %wZ\n", &Current); 3253 3254 Status = NtfsFindMftRecord(Vcb, CurrentMFTIndex, &Current, &FirstEntry, FALSE, CaseSensitive, &CurrentMFTIndex); 3255 if (!NT_SUCCESS(Status)) 3256 { 3257 return Status; 3258 } 3259 3260 if (Remaining.Length == 0) 3261 break; 3262 3263 FsRtlDissectName(Current, &Current, &Remaining); 3264 } 3265 3266 *FileRecord = ExAllocateFromNPagedLookasideList(&Vcb->FileRecLookasideList); 3267 if (*FileRecord == NULL) 3268 { 3269 DPRINT("NtfsLookupFileAt: Can't allocate MFT record\n"); 3270 return STATUS_INSUFFICIENT_RESOURCES; 3271 } 3272 3273 Status = ReadFileRecord(Vcb, CurrentMFTIndex, *FileRecord); 3274 if (!NT_SUCCESS(Status)) 3275 { 3276 DPRINT("NtfsLookupFileAt: Can't read MFT record\n"); 3277 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, *FileRecord); 3278 return Status; 3279 } 3280 3281 *MFTIndex = CurrentMFTIndex; 3282 3283 return STATUS_SUCCESS; 3284 } 3285 3286 NTSTATUS 3287 NtfsLookupFile(PDEVICE_EXTENSION Vcb, 3288 PUNICODE_STRING PathName, 3289 BOOLEAN CaseSensitive, 3290 PFILE_RECORD_HEADER *FileRecord, 3291 PULONGLONG MFTIndex) 3292 { 3293 return NtfsLookupFileAt(Vcb, PathName, CaseSensitive, FileRecord, MFTIndex, NTFS_FILE_ROOT); 3294 } 3295 3296 void 3297 NtfsDumpData(ULONG_PTR Buffer, ULONG Length) 3298 { 3299 ULONG i, j; 3300 3301 // dump binary data, 8 bytes at a time 3302 for (i = 0; i < Length; i += 8) 3303 { 3304 // display current offset, in hex 3305 DbgPrint("\t%03x\t", i); 3306 3307 // display hex value of each of the next 8 bytes 3308 for (j = 0; j < 8; j++) 3309 DbgPrint("%02x ", *(PUCHAR)(Buffer + i + j)); 3310 DbgPrint("\n"); 3311 } 3312 } 3313 3314 /** 3315 * @name NtfsDumpFileRecord 3316 * @implemented 3317 * 3318 * Provides diagnostic information about a file record. Prints a hex dump 3319 * of the entire record (based on the size reported by FileRecord->ByesInUse), 3320 * then prints a dump of each attribute. 3321 * 3322 * @param Vcb 3323 * Pointer to a DEVICE_EXTENSION describing the volume. 3324 * 3325 * @param FileRecord 3326 * Pointer to the file record to be analyzed. 3327 * 3328 * @remarks 3329 * FileRecord must be a complete file record at least FileRecord->BytesAllocated 3330 * in size, and not just the header. 3331 * 3332 */ 3333 VOID 3334 NtfsDumpFileRecord(PDEVICE_EXTENSION Vcb, 3335 PFILE_RECORD_HEADER FileRecord) 3336 { 3337 ULONG i, j; 3338 3339 // dump binary data, 8 bytes at a time 3340 for (i = 0; i < FileRecord->BytesInUse; i += 8) 3341 { 3342 // display current offset, in hex 3343 DbgPrint("\t%03x\t", i); 3344 3345 // display hex value of each of the next 8 bytes 3346 for (j = 0; j < 8; j++) 3347 DbgPrint("%02x ", *(PUCHAR)((ULONG_PTR)FileRecord + i + j)); 3348 DbgPrint("\n"); 3349 } 3350 3351 NtfsDumpFileAttributes(Vcb, FileRecord); 3352 } 3353 3354 NTSTATUS 3355 NtfsFindFileAt(PDEVICE_EXTENSION Vcb, 3356 PUNICODE_STRING SearchPattern, 3357 PULONG FirstEntry, 3358 PFILE_RECORD_HEADER *FileRecord, 3359 PULONGLONG MFTIndex, 3360 ULONGLONG CurrentMFTIndex, 3361 BOOLEAN CaseSensitive) 3362 { 3363 NTSTATUS Status; 3364 3365 DPRINT("NtfsFindFileAt(%p, %wZ, %lu, %p, %p, %I64x, %s)\n", 3366 Vcb, 3367 SearchPattern, 3368 *FirstEntry, 3369 FileRecord, 3370 MFTIndex, 3371 CurrentMFTIndex, 3372 (CaseSensitive ? "TRUE" : "FALSE")); 3373 3374 Status = NtfsFindMftRecord(Vcb, CurrentMFTIndex, SearchPattern, FirstEntry, TRUE, CaseSensitive, &CurrentMFTIndex); 3375 if (!NT_SUCCESS(Status)) 3376 { 3377 DPRINT("NtfsFindFileAt: NtfsFindMftRecord() failed with status 0x%08lx\n", Status); 3378 return Status; 3379 } 3380 3381 *FileRecord = ExAllocateFromNPagedLookasideList(&Vcb->FileRecLookasideList); 3382 if (*FileRecord == NULL) 3383 { 3384 DPRINT("NtfsFindFileAt: Can't allocate MFT record\n"); 3385 return STATUS_INSUFFICIENT_RESOURCES; 3386 } 3387 3388 Status = ReadFileRecord(Vcb, CurrentMFTIndex, *FileRecord); 3389 if (!NT_SUCCESS(Status)) 3390 { 3391 DPRINT("NtfsFindFileAt: Can't read MFT record\n"); 3392 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, *FileRecord); 3393 return Status; 3394 } 3395 3396 *MFTIndex = CurrentMFTIndex; 3397 3398 return STATUS_SUCCESS; 3399 } 3400 3401 /* EOF */ 3402