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