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 return Status; 1666 } 1667 1668 IndexRoot = (PINDEX_ROOT_ATTRIBUTE)IndexRecord; 1669 IndexEntry = (PINDEX_ENTRY_ATTRIBUTE)((PCHAR)&IndexRoot->Header + IndexRoot->Header.FirstEntryOffset); 1670 // Index root is always resident. 1671 IndexEntryEnd = (PINDEX_ENTRY_ATTRIBUTE)(IndexRecord + IndexRoot->Header.TotalSizeOfEntries); 1672 1673 DPRINT("IndexRecordSize: %x IndexBlockSize: %x\n", Vcb->NtfsInfo.BytesPerIndexRecord, IndexRoot->SizeOfEntry); 1674 1675 Status = UpdateIndexEntryFileNameSize(Vcb, 1676 MftRecord, 1677 IndexRecord, 1678 IndexRoot->SizeOfEntry, 1679 IndexEntry, 1680 IndexEntryEnd, 1681 FileName, 1682 &CurrentEntry, 1683 &CurrentEntry, 1684 DirSearch, 1685 NewDataSize, 1686 NewAllocationSize, 1687 CaseSensitive); 1688 1689 if (Status == STATUS_PENDING) 1690 { 1691 // we need to write the index root attribute back to disk 1692 ULONG LengthWritten; 1693 Status = WriteAttribute(Vcb, IndexRootCtx, 0, (PUCHAR)IndexRecord, AttributeDataLength(IndexRootCtx->pRecord), &LengthWritten, MftRecord); 1694 if (!NT_SUCCESS(Status)) 1695 { 1696 DPRINT1("ERROR: Couldn't update Index Root!\n"); 1697 } 1698 1699 } 1700 1701 ReleaseAttributeContext(IndexRootCtx); 1702 ExFreePoolWithTag(IndexRecord, TAG_NTFS); 1703 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MftRecord); 1704 1705 return Status; 1706 } 1707 1708 /** 1709 * Recursively searches directory index and applies the size update to the $FILE_NAME attribute of the 1710 * proper index entry. 1711 * (Heavily based on BrowseIndexEntries) 1712 */ 1713 NTSTATUS 1714 UpdateIndexEntryFileNameSize(PDEVICE_EXTENSION Vcb, 1715 PFILE_RECORD_HEADER MftRecord, 1716 PCHAR IndexRecord, 1717 ULONG IndexBlockSize, 1718 PINDEX_ENTRY_ATTRIBUTE FirstEntry, 1719 PINDEX_ENTRY_ATTRIBUTE LastEntry, 1720 PUNICODE_STRING FileName, 1721 PULONG StartEntry, 1722 PULONG CurrentEntry, 1723 BOOLEAN DirSearch, 1724 ULONGLONG NewDataSize, 1725 ULONGLONG NewAllocatedSize, 1726 BOOLEAN CaseSensitive) 1727 { 1728 NTSTATUS Status; 1729 ULONG RecordOffset; 1730 PINDEX_ENTRY_ATTRIBUTE IndexEntry; 1731 PNTFS_ATTR_CONTEXT IndexAllocationCtx; 1732 ULONGLONG IndexAllocationSize; 1733 PINDEX_BUFFER IndexBuffer; 1734 1735 DPRINT("UpdateIndexEntrySize(%p, %p, %p, %lu, %p, %p, %wZ, %lu, %lu, %s, %I64u, %I64u, %s)\n", 1736 Vcb, 1737 MftRecord, 1738 IndexRecord, 1739 IndexBlockSize, 1740 FirstEntry, 1741 LastEntry, 1742 FileName, 1743 *StartEntry, 1744 *CurrentEntry, 1745 DirSearch ? "TRUE" : "FALSE", 1746 NewDataSize, 1747 NewAllocatedSize, 1748 CaseSensitive ? "TRUE" : "FALSE"); 1749 1750 // find the index entry responsible for the file we're trying to update 1751 IndexEntry = FirstEntry; 1752 while (IndexEntry < LastEntry && 1753 !(IndexEntry->Flags & NTFS_INDEX_ENTRY_END)) 1754 { 1755 if ((IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK) > NTFS_FILE_FIRST_USER_FILE && 1756 *CurrentEntry >= *StartEntry && 1757 IndexEntry->FileName.NameType != NTFS_FILE_NAME_DOS && 1758 CompareFileName(FileName, IndexEntry, DirSearch, CaseSensitive)) 1759 { 1760 *StartEntry = *CurrentEntry; 1761 IndexEntry->FileName.DataSize = NewDataSize; 1762 IndexEntry->FileName.AllocatedSize = NewAllocatedSize; 1763 // indicate that the caller will still need to write the structure to the disk 1764 return STATUS_PENDING; 1765 } 1766 1767 (*CurrentEntry) += 1; 1768 ASSERT(IndexEntry->Length >= sizeof(INDEX_ENTRY_ATTRIBUTE)); 1769 IndexEntry = (PINDEX_ENTRY_ATTRIBUTE)((PCHAR)IndexEntry + IndexEntry->Length); 1770 } 1771 1772 /* If we're already browsing a subnode */ 1773 if (IndexRecord == NULL) 1774 { 1775 return STATUS_OBJECT_PATH_NOT_FOUND; 1776 } 1777 1778 /* If there's no subnode */ 1779 if (!(IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE)) 1780 { 1781 return STATUS_OBJECT_PATH_NOT_FOUND; 1782 } 1783 1784 Status = FindAttribute(Vcb, MftRecord, AttributeIndexAllocation, L"$I30", 4, &IndexAllocationCtx, NULL); 1785 if (!NT_SUCCESS(Status)) 1786 { 1787 DPRINT("Corrupted filesystem!\n"); 1788 return Status; 1789 } 1790 1791 IndexAllocationSize = AttributeDataLength(IndexAllocationCtx->pRecord); 1792 Status = STATUS_OBJECT_PATH_NOT_FOUND; 1793 for (RecordOffset = 0; RecordOffset < IndexAllocationSize; RecordOffset += IndexBlockSize) 1794 { 1795 ReadAttribute(Vcb, IndexAllocationCtx, RecordOffset, IndexRecord, IndexBlockSize); 1796 Status = FixupUpdateSequenceArray(Vcb, &((PFILE_RECORD_HEADER)IndexRecord)->Ntfs); 1797 if (!NT_SUCCESS(Status)) 1798 { 1799 break; 1800 } 1801 1802 IndexBuffer = (PINDEX_BUFFER)IndexRecord; 1803 ASSERT(IndexBuffer->Ntfs.Type == NRH_INDX_TYPE); 1804 ASSERT(IndexBuffer->Header.AllocatedSize + FIELD_OFFSET(INDEX_BUFFER, Header) == IndexBlockSize); 1805 FirstEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)&IndexBuffer->Header + IndexBuffer->Header.FirstEntryOffset); 1806 LastEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)&IndexBuffer->Header + IndexBuffer->Header.TotalSizeOfEntries); 1807 ASSERT(LastEntry <= (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)IndexBuffer + IndexBlockSize)); 1808 1809 Status = UpdateIndexEntryFileNameSize(NULL, 1810 NULL, 1811 NULL, 1812 0, 1813 FirstEntry, 1814 LastEntry, 1815 FileName, 1816 StartEntry, 1817 CurrentEntry, 1818 DirSearch, 1819 NewDataSize, 1820 NewAllocatedSize, 1821 CaseSensitive); 1822 if (Status == STATUS_PENDING) 1823 { 1824 // write the index record back to disk 1825 ULONG Written; 1826 1827 // first we need to update the fixup values for the index block 1828 Status = AddFixupArray(Vcb, &((PFILE_RECORD_HEADER)IndexRecord)->Ntfs); 1829 if (!NT_SUCCESS(Status)) 1830 { 1831 DPRINT1("Error: Failed to update fixup sequence array!\n"); 1832 break; 1833 } 1834 1835 Status = WriteAttribute(Vcb, IndexAllocationCtx, RecordOffset, (const PUCHAR)IndexRecord, IndexBlockSize, &Written, MftRecord); 1836 if (!NT_SUCCESS(Status)) 1837 { 1838 DPRINT1("ERROR Performing write!\n"); 1839 break; 1840 } 1841 1842 Status = STATUS_SUCCESS; 1843 break; 1844 } 1845 if (NT_SUCCESS(Status)) 1846 { 1847 break; 1848 } 1849 } 1850 1851 ReleaseAttributeContext(IndexAllocationCtx); 1852 return Status; 1853 } 1854 1855 /** 1856 * @name UpdateFileRecord 1857 * @implemented 1858 * 1859 * Writes a file record to the master file table, at a given index. 1860 * 1861 * @param Vcb 1862 * Pointer to the DEVICE_EXTENSION of the target drive being written to. 1863 * 1864 * @param MftIndex 1865 * Target index in the master file table to store the file record. 1866 * 1867 * @param FileRecord 1868 * Pointer to the complete file record which will be written to the master file table. 1869 * 1870 * @return 1871 * STATUS_SUCCESSFUL on success. An error passed from WriteAttribute() otherwise. 1872 * 1873 */ 1874 NTSTATUS 1875 UpdateFileRecord(PDEVICE_EXTENSION Vcb, 1876 ULONGLONG MftIndex, 1877 PFILE_RECORD_HEADER FileRecord) 1878 { 1879 ULONG BytesWritten; 1880 NTSTATUS Status = STATUS_SUCCESS; 1881 1882 DPRINT("UpdateFileRecord(%p, 0x%I64x, %p)\n", Vcb, MftIndex, FileRecord); 1883 1884 // Add the fixup array to prepare the data for writing to disk 1885 AddFixupArray(Vcb, &FileRecord->Ntfs); 1886 1887 // write the file record to the master file table 1888 Status = WriteAttribute(Vcb, 1889 Vcb->MFTContext, 1890 MftIndex * Vcb->NtfsInfo.BytesPerFileRecord, 1891 (const PUCHAR)FileRecord, 1892 Vcb->NtfsInfo.BytesPerFileRecord, 1893 &BytesWritten, 1894 FileRecord); 1895 1896 if (!NT_SUCCESS(Status)) 1897 { 1898 DPRINT1("UpdateFileRecord failed: %lu written, %lu expected\n", BytesWritten, Vcb->NtfsInfo.BytesPerFileRecord); 1899 } 1900 1901 // remove the fixup array (so the file record pointer can still be used) 1902 FixupUpdateSequenceArray(Vcb, &FileRecord->Ntfs); 1903 1904 return Status; 1905 } 1906 1907 1908 NTSTATUS 1909 FixupUpdateSequenceArray(PDEVICE_EXTENSION Vcb, 1910 PNTFS_RECORD_HEADER Record) 1911 { 1912 USHORT *USA; 1913 USHORT USANumber; 1914 USHORT USACount; 1915 USHORT *Block; 1916 1917 USA = (USHORT*)((PCHAR)Record + Record->UsaOffset); 1918 USANumber = *(USA++); 1919 USACount = Record->UsaCount - 1; /* Exclude the USA Number. */ 1920 Block = (USHORT*)((PCHAR)Record + Vcb->NtfsInfo.BytesPerSector - 2); 1921 1922 DPRINT("FixupUpdateSequenceArray(%p, %p)\nUSANumber: %u\tUSACount: %u\n", Vcb, Record, USANumber, USACount); 1923 1924 while (USACount) 1925 { 1926 if (*Block != USANumber) 1927 { 1928 DPRINT1("Mismatch with USA: %u read, %u expected\n" , *Block, USANumber); 1929 return STATUS_UNSUCCESSFUL; 1930 } 1931 *Block = *(USA++); 1932 Block = (USHORT*)((PCHAR)Block + Vcb->NtfsInfo.BytesPerSector); 1933 USACount--; 1934 } 1935 1936 return STATUS_SUCCESS; 1937 } 1938 1939 /** 1940 * @name AddNewMftEntry 1941 * @implemented 1942 * 1943 * Adds a file record to the master file table of a given device. 1944 * 1945 * @param FileRecord 1946 * Pointer to a complete file record which will be saved to disk. 1947 * 1948 * @param DeviceExt 1949 * Pointer to the DEVICE_EXTENSION of the target drive. 1950 * 1951 * @param DestinationIndex 1952 * Pointer to a ULONGLONG which will receive the MFT index where the file record was stored. 1953 * 1954 * @param CanWait 1955 * Boolean indicating if the function is allowed to wait for exclusive access to the master file table. 1956 * This will only be relevant if the MFT doesn't have any free file records and needs to be enlarged. 1957 * 1958 * @return 1959 * STATUS_SUCCESS on success. 1960 * STATUS_OBJECT_NAME_NOT_FOUND if we can't find the MFT's $Bitmap or if we weren't able 1961 * to read the attribute. 1962 * STATUS_INSUFFICIENT_RESOURCES if we can't allocate enough memory for a copy of $Bitmap. 1963 * STATUS_CANT_WAIT if CanWait was FALSE and the function could not get immediate, exclusive access to the MFT. 1964 */ 1965 NTSTATUS 1966 AddNewMftEntry(PFILE_RECORD_HEADER FileRecord, 1967 PDEVICE_EXTENSION DeviceExt, 1968 PULONGLONG DestinationIndex, 1969 BOOLEAN CanWait) 1970 { 1971 NTSTATUS Status = STATUS_SUCCESS; 1972 ULONGLONG MftIndex; 1973 RTL_BITMAP Bitmap; 1974 ULONGLONG BitmapDataSize; 1975 ULONGLONG AttrBytesRead; 1976 PUCHAR BitmapData; 1977 PUCHAR BitmapBuffer; 1978 ULONG LengthWritten; 1979 PNTFS_ATTR_CONTEXT BitmapContext; 1980 LARGE_INTEGER BitmapBits; 1981 UCHAR SystemReservedBits; 1982 1983 DPRINT1("AddNewMftEntry(%p, %p, %p, %s)\n", FileRecord, DeviceExt, DestinationIndex, CanWait ? "TRUE" : "FALSE"); 1984 1985 // First, we have to read the mft's $Bitmap attribute 1986 1987 // Find the attribute 1988 Status = FindAttribute(DeviceExt, DeviceExt->MasterFileTable, AttributeBitmap, L"", 0, &BitmapContext, NULL); 1989 if (!NT_SUCCESS(Status)) 1990 { 1991 DPRINT1("ERROR: Couldn't find $Bitmap attribute of master file table!\n"); 1992 return Status; 1993 } 1994 1995 // Get size of bitmap 1996 BitmapDataSize = AttributeDataLength(BitmapContext->pRecord); 1997 1998 // RtlInitializeBitmap wants a ULONG-aligned pointer, and wants the memory passed to it to be a ULONG-multiple 1999 // Allocate a buffer for the $Bitmap attribute plus enough to ensure we can get a ULONG-aligned pointer 2000 BitmapBuffer = ExAllocatePoolWithTag(NonPagedPool, BitmapDataSize + sizeof(ULONG), TAG_NTFS); 2001 if (!BitmapBuffer) 2002 { 2003 ReleaseAttributeContext(BitmapContext); 2004 return STATUS_INSUFFICIENT_RESOURCES; 2005 } 2006 RtlZeroMemory(BitmapBuffer, BitmapDataSize + sizeof(ULONG)); 2007 2008 // Get a ULONG-aligned pointer for the bitmap itself 2009 BitmapData = (PUCHAR)ALIGN_UP_BY((ULONG_PTR)BitmapBuffer, sizeof(ULONG)); 2010 2011 // read $Bitmap attribute 2012 AttrBytesRead = ReadAttribute(DeviceExt, BitmapContext, 0, (PCHAR)BitmapData, BitmapDataSize); 2013 2014 if (AttrBytesRead != BitmapDataSize) 2015 { 2016 DPRINT1("ERROR: Unable to read $Bitmap attribute of master file table!\n"); 2017 ExFreePoolWithTag(BitmapBuffer, TAG_NTFS); 2018 ReleaseAttributeContext(BitmapContext); 2019 return STATUS_OBJECT_NAME_NOT_FOUND; 2020 } 2021 2022 // We need to backup the bits for records 0x10 - 0x17 (3rd byte of bitmap) and mark these records 2023 // as in-use so we don't assign files to those indices. They're reserved for the system (e.g. ChkDsk). 2024 SystemReservedBits = BitmapData[2]; 2025 BitmapData[2] = 0xff; 2026 2027 // Calculate bit count 2028 BitmapBits.QuadPart = AttributeDataLength(DeviceExt->MFTContext->pRecord) / 2029 DeviceExt->NtfsInfo.BytesPerFileRecord; 2030 if (BitmapBits.HighPart != 0) 2031 { 2032 DPRINT1("\tFIXME: bitmap sizes beyond 32bits are not yet supported! (Your NTFS volume is too large)\n"); 2033 NtfsGlobalData->EnableWriteSupport = FALSE; 2034 ExFreePoolWithTag(BitmapBuffer, TAG_NTFS); 2035 ReleaseAttributeContext(BitmapContext); 2036 return STATUS_NOT_IMPLEMENTED; 2037 } 2038 2039 // convert buffer into bitmap 2040 RtlInitializeBitMap(&Bitmap, (PULONG)BitmapData, BitmapBits.LowPart); 2041 2042 // set next available bit, preferrably after 23rd bit 2043 MftIndex = RtlFindClearBitsAndSet(&Bitmap, 1, 24); 2044 if ((LONG)MftIndex == -1) 2045 { 2046 DPRINT1("Couldn't find free space in MFT for file record, increasing MFT size.\n"); 2047 2048 ExFreePoolWithTag(BitmapBuffer, TAG_NTFS); 2049 ReleaseAttributeContext(BitmapContext); 2050 2051 // Couldn't find a free record in the MFT, add some blank records and try again 2052 Status = IncreaseMftSize(DeviceExt, CanWait); 2053 if (!NT_SUCCESS(Status)) 2054 { 2055 DPRINT1("ERROR: Couldn't find space in MFT for file or increase MFT size!\n"); 2056 return Status; 2057 } 2058 2059 return AddNewMftEntry(FileRecord, DeviceExt, DestinationIndex, CanWait); 2060 } 2061 2062 DPRINT1("Creating file record at MFT index: %I64u\n", MftIndex); 2063 2064 // update file record with index 2065 FileRecord->MFTRecordNumber = MftIndex; 2066 2067 // [BitmapData should have been updated via RtlFindClearBitsAndSet()] 2068 2069 // Restore the system reserved bits 2070 BitmapData[2] = SystemReservedBits; 2071 2072 // write the bitmap back to the MFT's $Bitmap attribute 2073 Status = WriteAttribute(DeviceExt, BitmapContext, 0, BitmapData, BitmapDataSize, &LengthWritten, FileRecord); 2074 if (!NT_SUCCESS(Status)) 2075 { 2076 DPRINT1("ERROR encountered when writing $Bitmap attribute!\n"); 2077 ExFreePoolWithTag(BitmapBuffer, TAG_NTFS); 2078 ReleaseAttributeContext(BitmapContext); 2079 return Status; 2080 } 2081 2082 // update the file record (write it to disk) 2083 Status = UpdateFileRecord(DeviceExt, MftIndex, FileRecord); 2084 2085 if (!NT_SUCCESS(Status)) 2086 { 2087 DPRINT1("ERROR: Unable to write file record!\n"); 2088 ExFreePoolWithTag(BitmapBuffer, TAG_NTFS); 2089 ReleaseAttributeContext(BitmapContext); 2090 return Status; 2091 } 2092 2093 *DestinationIndex = MftIndex; 2094 2095 ExFreePoolWithTag(BitmapBuffer, TAG_NTFS); 2096 ReleaseAttributeContext(BitmapContext); 2097 2098 return Status; 2099 } 2100 2101 /** 2102 * @name NtfsAddFilenameToDirectory 2103 * @implemented 2104 * 2105 * Adds a $FILE_NAME attribute to a given directory index. 2106 * 2107 * @param DeviceExt 2108 * Points to the target disk's DEVICE_EXTENSION. 2109 * 2110 * @param DirectoryMftIndex 2111 * Mft index of the parent directory which will receive the file. 2112 * 2113 * @param FileReferenceNumber 2114 * File reference of the file to be added to the directory. This is a combination of the 2115 * Mft index and sequence number. 2116 * 2117 * @param FilenameAttribute 2118 * Pointer to the FILENAME_ATTRIBUTE of the file being added to the directory. 2119 * 2120 * @param CaseSensitive 2121 * Boolean indicating if the function should operate in case-sensitive mode. This will be TRUE 2122 * if an application created the file with the FILE_FLAG_POSIX_SEMANTICS flag. 2123 * 2124 * @return 2125 * STATUS_SUCCESS on success. 2126 * STATUS_INSUFFICIENT_RESOURCES if an allocation fails. 2127 * STATUS_NOT_IMPLEMENTED if target address isn't at the end of the given file record. 2128 * 2129 * @remarks 2130 * WIP - Can only support a few files in a directory. 2131 * One FILENAME_ATTRIBUTE is added to the directory's index for each link to that file. So, each 2132 * file which contains one FILENAME_ATTRIBUTE for a long name and another for the 8.3 name, will 2133 * get both attributes added to its parent directory. 2134 */ 2135 NTSTATUS 2136 NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt, 2137 ULONGLONG DirectoryMftIndex, 2138 ULONGLONG FileReferenceNumber, 2139 PFILENAME_ATTRIBUTE FilenameAttribute, 2140 BOOLEAN CaseSensitive) 2141 { 2142 NTSTATUS Status = STATUS_SUCCESS; 2143 PFILE_RECORD_HEADER ParentFileRecord; 2144 PNTFS_ATTR_CONTEXT IndexRootContext; 2145 PINDEX_ROOT_ATTRIBUTE I30IndexRoot; 2146 ULONG IndexRootOffset; 2147 ULONGLONG I30IndexRootLength; 2148 ULONG LengthWritten; 2149 PINDEX_ROOT_ATTRIBUTE NewIndexRoot; 2150 ULONG AttributeLength; 2151 PNTFS_ATTR_RECORD NextAttribute; 2152 PB_TREE NewTree; 2153 ULONG BtreeIndexLength; 2154 ULONG MaxIndexRootSize; 2155 PB_TREE_KEY NewLeftKey; 2156 PB_TREE_FILENAME_NODE NewRightHandNode; 2157 LARGE_INTEGER MinIndexRootSize; 2158 ULONG NewMaxIndexRootSize; 2159 ULONG NodeSize; 2160 2161 // Allocate memory for the parent directory 2162 ParentFileRecord = ExAllocateFromNPagedLookasideList(&DeviceExt->FileRecLookasideList); 2163 if (!ParentFileRecord) 2164 { 2165 DPRINT1("ERROR: Couldn't allocate memory for file record!\n"); 2166 return STATUS_INSUFFICIENT_RESOURCES; 2167 } 2168 2169 // Open the parent directory 2170 Status = ReadFileRecord(DeviceExt, DirectoryMftIndex, ParentFileRecord); 2171 if (!NT_SUCCESS(Status)) 2172 { 2173 ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord); 2174 DPRINT1("ERROR: Couldn't read parent directory with index %I64u\n", 2175 DirectoryMftIndex); 2176 return Status; 2177 } 2178 2179 #ifndef NDEBUG 2180 DPRINT1("Dumping old parent file record:\n"); 2181 NtfsDumpFileRecord(DeviceExt, ParentFileRecord); 2182 #endif 2183 2184 // Find the index root attribute for the directory 2185 Status = FindAttribute(DeviceExt, 2186 ParentFileRecord, 2187 AttributeIndexRoot, 2188 L"$I30", 2189 4, 2190 &IndexRootContext, 2191 &IndexRootOffset); 2192 if (!NT_SUCCESS(Status)) 2193 { 2194 DPRINT1("ERROR: Couldn't find $I30 $INDEX_ROOT attribute for parent directory with MFT #: %I64u!\n", 2195 DirectoryMftIndex); 2196 ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord); 2197 return Status; 2198 } 2199 2200 // Find the maximum index size given what the file record can hold 2201 // First, find the max index size assuming index root is the last attribute 2202 MaxIndexRootSize = DeviceExt->NtfsInfo.BytesPerFileRecord // Start with the size of a file record 2203 - IndexRootOffset // Subtract the length of everything that comes before index root 2204 - IndexRootContext->pRecord->Resident.ValueOffset // Subtract the length of the attribute header for index root 2205 - sizeof(INDEX_ROOT_ATTRIBUTE) // Subtract the length of the index root header 2206 - (sizeof(ULONG) * 2); // Subtract the length of the file record end marker and padding 2207 2208 // Are there attributes after this one? 2209 NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)ParentFileRecord + IndexRootOffset + IndexRootContext->pRecord->Length); 2210 if (NextAttribute->Type != AttributeEnd) 2211 { 2212 // Find the length of all attributes after this one, not counting the end marker 2213 ULONG LengthOfAttributes = 0; 2214 PNTFS_ATTR_RECORD CurrentAttribute = NextAttribute; 2215 while (CurrentAttribute->Type != AttributeEnd) 2216 { 2217 LengthOfAttributes += CurrentAttribute->Length; 2218 CurrentAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)CurrentAttribute + CurrentAttribute->Length); 2219 } 2220 2221 // Leave room for the existing attributes 2222 MaxIndexRootSize -= LengthOfAttributes; 2223 } 2224 2225 // Allocate memory for the index root data 2226 I30IndexRootLength = AttributeDataLength(IndexRootContext->pRecord); 2227 I30IndexRoot = ExAllocatePoolWithTag(NonPagedPool, I30IndexRootLength, TAG_NTFS); 2228 if (!I30IndexRoot) 2229 { 2230 DPRINT1("ERROR: Couldn't allocate memory for index root attribute!\n"); 2231 ReleaseAttributeContext(IndexRootContext); 2232 ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord); 2233 return STATUS_INSUFFICIENT_RESOURCES; 2234 } 2235 2236 // Read the Index Root 2237 Status = ReadAttribute(DeviceExt, IndexRootContext, 0, (PCHAR)I30IndexRoot, I30IndexRootLength); 2238 if (!NT_SUCCESS(Status)) 2239 { 2240 DPRINT1("ERROR: Couln't read index root attribute for Mft index #%I64u\n", DirectoryMftIndex); 2241 ReleaseAttributeContext(IndexRootContext); 2242 ExFreePoolWithTag(I30IndexRoot, TAG_NTFS); 2243 ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord); 2244 return Status; 2245 } 2246 2247 // Convert the index to a B*Tree 2248 Status = CreateBTreeFromIndex(DeviceExt, 2249 ParentFileRecord, 2250 IndexRootContext, 2251 I30IndexRoot, 2252 &NewTree); 2253 if (!NT_SUCCESS(Status)) 2254 { 2255 DPRINT1("ERROR: Failed to create B-Tree from Index!\n"); 2256 ReleaseAttributeContext(IndexRootContext); 2257 ExFreePoolWithTag(I30IndexRoot, TAG_NTFS); 2258 ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord); 2259 return Status; 2260 } 2261 2262 #ifndef NDEBUG 2263 DumpBTree(NewTree); 2264 #endif 2265 2266 // Insert the key for the file we're adding 2267 Status = NtfsInsertKey(NewTree, 2268 FileReferenceNumber, 2269 FilenameAttribute, 2270 NewTree->RootNode, 2271 CaseSensitive, 2272 MaxIndexRootSize, 2273 I30IndexRoot->SizeOfEntry, 2274 &NewLeftKey, 2275 &NewRightHandNode); 2276 if (!NT_SUCCESS(Status)) 2277 { 2278 DPRINT1("ERROR: Failed to insert key into B-Tree!\n"); 2279 DestroyBTree(NewTree); 2280 ReleaseAttributeContext(IndexRootContext); 2281 ExFreePoolWithTag(I30IndexRoot, TAG_NTFS); 2282 ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord); 2283 return Status; 2284 } 2285 2286 #ifndef NDEBUG 2287 DumpBTree(NewTree); 2288 #endif 2289 2290 // The root node can't be split 2291 ASSERT(NewLeftKey == NULL); 2292 ASSERT(NewRightHandNode == NULL); 2293 2294 // Convert B*Tree back to Index 2295 2296 // Updating the index allocation can change the size available for the index root, 2297 // And if the index root is demoted, the index allocation will need to be updated again, 2298 // which may change the size available for index root... etc. 2299 // My solution is to decrease index root to the size it would be if it was demoted, 2300 // then UpdateIndexAllocation will have an accurate representation of the maximum space 2301 // it can use in the file record. There's still a chance that the act of allocating an 2302 // index node after demoting the index root will increase the size of the file record beyond 2303 // it's limit, but if that happens, an attribute-list will most definitely be needed. 2304 // This a bit hacky, but it seems to be functional. 2305 2306 // Calculate the minimum size of the index root attribute, considering one dummy key and one VCN 2307 MinIndexRootSize.QuadPart = sizeof(INDEX_ROOT_ATTRIBUTE) // size of the index root headers 2308 + 0x18; // Size of dummy key with a VCN for a subnode 2309 ASSERT(MinIndexRootSize.QuadPart % ATTR_RECORD_ALIGNMENT == 0); 2310 2311 // Temporarily shrink the index root to it's minimal size 2312 AttributeLength = MinIndexRootSize.LowPart; 2313 AttributeLength += sizeof(INDEX_ROOT_ATTRIBUTE); 2314 2315 2316 // FIXME: IndexRoot will probably be invalid until we're finished. If we fail before we finish, the directory will probably be toast. 2317 // The potential for catastrophic data-loss exists!!! :) 2318 2319 // Update the length of the attribute in the file record of the parent directory 2320 Status = InternalSetResidentAttributeLength(DeviceExt, 2321 IndexRootContext, 2322 ParentFileRecord, 2323 IndexRootOffset, 2324 AttributeLength); 2325 if (!NT_SUCCESS(Status)) 2326 { 2327 DPRINT1("ERROR: Unable to set length of index root!\n"); 2328 DestroyBTree(NewTree); 2329 ReleaseAttributeContext(IndexRootContext); 2330 ExFreePoolWithTag(I30IndexRoot, TAG_NTFS); 2331 ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord); 2332 return Status; 2333 } 2334 2335 // Update the index allocation 2336 Status = UpdateIndexAllocation(DeviceExt, NewTree, I30IndexRoot->SizeOfEntry, ParentFileRecord); 2337 if (!NT_SUCCESS(Status)) 2338 { 2339 DPRINT1("ERROR: Failed to update index allocation from B-Tree!\n"); 2340 DestroyBTree(NewTree); 2341 ReleaseAttributeContext(IndexRootContext); 2342 ExFreePoolWithTag(I30IndexRoot, TAG_NTFS); 2343 ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord); 2344 return Status; 2345 } 2346 2347 #ifndef NDEBUG 2348 DPRINT1("Index Allocation updated\n"); 2349 DumpBTree(NewTree); 2350 #endif 2351 2352 // Find the maximum index root size given what the file record can hold 2353 // First, find the max index size assuming index root is the last attribute 2354 NewMaxIndexRootSize = 2355 DeviceExt->NtfsInfo.BytesPerFileRecord // Start with the size of a file record 2356 - IndexRootOffset // Subtract the length of everything that comes before index root 2357 - IndexRootContext->pRecord->Resident.ValueOffset // Subtract the length of the attribute header for index root 2358 - sizeof(INDEX_ROOT_ATTRIBUTE) // Subtract the length of the index root header 2359 - (sizeof(ULONG) * 2); // Subtract the length of the file record end marker and padding 2360 2361 // Are there attributes after this one? 2362 NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)ParentFileRecord + IndexRootOffset + IndexRootContext->pRecord->Length); 2363 if (NextAttribute->Type != AttributeEnd) 2364 { 2365 // Find the length of all attributes after this one, not counting the end marker 2366 ULONG LengthOfAttributes = 0; 2367 PNTFS_ATTR_RECORD CurrentAttribute = NextAttribute; 2368 while (CurrentAttribute->Type != AttributeEnd) 2369 { 2370 LengthOfAttributes += CurrentAttribute->Length; 2371 CurrentAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)CurrentAttribute + CurrentAttribute->Length); 2372 } 2373 2374 // Leave room for the existing attributes 2375 NewMaxIndexRootSize -= LengthOfAttributes; 2376 } 2377 2378 // The index allocation and index bitmap may have grown, leaving less room for the index root, 2379 // so now we need to double-check that index root isn't too large 2380 NodeSize = GetSizeOfIndexEntries(NewTree->RootNode); 2381 if (NodeSize > NewMaxIndexRootSize) 2382 { 2383 DPRINT1("Demoting index root.\nNodeSize: 0x%lx\nNewMaxIndexRootSize: 0x%lx\n", NodeSize, NewMaxIndexRootSize); 2384 2385 Status = DemoteBTreeRoot(NewTree); 2386 if (!NT_SUCCESS(Status)) 2387 { 2388 DPRINT1("ERROR: Failed to demote index root!\n"); 2389 DestroyBTree(NewTree); 2390 ReleaseAttributeContext(IndexRootContext); 2391 ExFreePoolWithTag(I30IndexRoot, TAG_NTFS); 2392 ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord); 2393 return Status; 2394 } 2395 2396 // We need to update the index allocation once more 2397 Status = UpdateIndexAllocation(DeviceExt, NewTree, I30IndexRoot->SizeOfEntry, ParentFileRecord); 2398 if (!NT_SUCCESS(Status)) 2399 { 2400 DPRINT1("ERROR: Failed to update index allocation from B-Tree!\n"); 2401 DestroyBTree(NewTree); 2402 ReleaseAttributeContext(IndexRootContext); 2403 ExFreePoolWithTag(I30IndexRoot, TAG_NTFS); 2404 ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord); 2405 return Status; 2406 } 2407 2408 // re-recalculate max size of index root 2409 NewMaxIndexRootSize = 2410 // Find the maximum index size given what the file record can hold 2411 // First, find the max index size assuming index root is the last attribute 2412 DeviceExt->NtfsInfo.BytesPerFileRecord // Start with the size of a file record 2413 - IndexRootOffset // Subtract the length of everything that comes before index root 2414 - IndexRootContext->pRecord->Resident.ValueOffset // Subtract the length of the attribute header for index root 2415 - sizeof(INDEX_ROOT_ATTRIBUTE) // Subtract the length of the index root header 2416 - (sizeof(ULONG) * 2); // Subtract the length of the file record end marker and padding 2417 2418 // Are there attributes after this one? 2419 NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)ParentFileRecord + IndexRootOffset + IndexRootContext->pRecord->Length); 2420 if (NextAttribute->Type != AttributeEnd) 2421 { 2422 // Find the length of all attributes after this one, not counting the end marker 2423 ULONG LengthOfAttributes = 0; 2424 PNTFS_ATTR_RECORD CurrentAttribute = NextAttribute; 2425 while (CurrentAttribute->Type != AttributeEnd) 2426 { 2427 LengthOfAttributes += CurrentAttribute->Length; 2428 CurrentAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)CurrentAttribute + CurrentAttribute->Length); 2429 } 2430 2431 // Leave room for the existing attributes 2432 NewMaxIndexRootSize -= LengthOfAttributes; 2433 } 2434 2435 2436 } 2437 2438 // Create the Index Root from the B*Tree 2439 Status = CreateIndexRootFromBTree(DeviceExt, NewTree, NewMaxIndexRootSize, &NewIndexRoot, &BtreeIndexLength); 2440 if (!NT_SUCCESS(Status)) 2441 { 2442 DPRINT1("ERROR: Failed to create Index root from B-Tree!\n"); 2443 DestroyBTree(NewTree); 2444 ReleaseAttributeContext(IndexRootContext); 2445 ExFreePoolWithTag(I30IndexRoot, TAG_NTFS); 2446 ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord); 2447 return Status; 2448 } 2449 2450 // We're done with the B-Tree now 2451 DestroyBTree(NewTree); 2452 2453 // Write back the new index root attribute to the parent directory file record 2454 2455 // First, we need to resize the attribute. 2456 // CreateIndexRootFromBTree() should have verified that the index root fits within MaxIndexSize. 2457 // We can't set the size as we normally would, because $INDEX_ROOT must always be resident. 2458 AttributeLength = NewIndexRoot->Header.AllocatedSize + FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE, Header); 2459 2460 if (AttributeLength != IndexRootContext->pRecord->Resident.ValueLength) 2461 { 2462 // Update the length of the attribute in the file record of the parent directory 2463 Status = InternalSetResidentAttributeLength(DeviceExt, 2464 IndexRootContext, 2465 ParentFileRecord, 2466 IndexRootOffset, 2467 AttributeLength); 2468 if (!NT_SUCCESS(Status)) 2469 { 2470 ExFreePoolWithTag(NewIndexRoot, TAG_NTFS); 2471 ReleaseAttributeContext(IndexRootContext); 2472 ExFreePoolWithTag(I30IndexRoot, TAG_NTFS); 2473 ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord); 2474 DPRINT1("ERROR: Unable to set resident attribute length!\n"); 2475 return Status; 2476 } 2477 2478 } 2479 2480 NT_ASSERT(ParentFileRecord->BytesInUse <= DeviceExt->NtfsInfo.BytesPerFileRecord); 2481 2482 Status = UpdateFileRecord(DeviceExt, DirectoryMftIndex, ParentFileRecord); 2483 if (!NT_SUCCESS(Status)) 2484 { 2485 DPRINT1("ERROR: Failed to update file record of directory with index: %llx\n", DirectoryMftIndex); 2486 ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord); 2487 ExFreePoolWithTag(NewIndexRoot, TAG_NTFS); 2488 ReleaseAttributeContext(IndexRootContext); 2489 ExFreePoolWithTag(I30IndexRoot, TAG_NTFS); 2490 return Status; 2491 } 2492 2493 // Write the new index root to disk 2494 Status = WriteAttribute(DeviceExt, 2495 IndexRootContext, 2496 0, 2497 (PUCHAR)NewIndexRoot, 2498 AttributeLength, 2499 &LengthWritten, 2500 ParentFileRecord); 2501 if (!NT_SUCCESS(Status) || LengthWritten != AttributeLength) 2502 { 2503 DPRINT1("ERROR: Unable to write new index root attribute to parent directory!\n"); 2504 ExFreePoolWithTag(NewIndexRoot, TAG_NTFS); 2505 ReleaseAttributeContext(IndexRootContext); 2506 ExFreePoolWithTag(I30IndexRoot, TAG_NTFS); 2507 ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord); 2508 return Status; 2509 } 2510 2511 // re-read the parent file record, so we can dump it 2512 Status = ReadFileRecord(DeviceExt, DirectoryMftIndex, ParentFileRecord); 2513 if (!NT_SUCCESS(Status)) 2514 { 2515 DPRINT1("ERROR: Couldn't read parent directory after messing with it!\n"); 2516 } 2517 else 2518 { 2519 #ifndef NDEBUG 2520 DPRINT1("Dumping new B-Tree:\n"); 2521 2522 Status = CreateBTreeFromIndex(DeviceExt, ParentFileRecord, IndexRootContext, NewIndexRoot, &NewTree); 2523 if (!NT_SUCCESS(Status)) 2524 { 2525 DPRINT1("ERROR: Couldn't re-create b-tree\n"); 2526 return Status; 2527 } 2528 2529 DumpBTree(NewTree); 2530 2531 DestroyBTree(NewTree); 2532 2533 NtfsDumpFileRecord(DeviceExt, ParentFileRecord); 2534 #endif 2535 } 2536 2537 // Cleanup 2538 ExFreePoolWithTag(NewIndexRoot, TAG_NTFS); 2539 ReleaseAttributeContext(IndexRootContext); 2540 ExFreePoolWithTag(I30IndexRoot, TAG_NTFS); 2541 ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord); 2542 2543 return Status; 2544 } 2545 2546 NTSTATUS 2547 AddFixupArray(PDEVICE_EXTENSION Vcb, 2548 PNTFS_RECORD_HEADER Record) 2549 { 2550 USHORT *pShortToFixUp; 2551 ULONG ArrayEntryCount = Record->UsaCount - 1; 2552 ULONG Offset = Vcb->NtfsInfo.BytesPerSector - 2; 2553 ULONG i; 2554 2555 PFIXUP_ARRAY fixupArray = (PFIXUP_ARRAY)((UCHAR*)Record + Record->UsaOffset); 2556 2557 DPRINT("AddFixupArray(%p, %p)\n fixupArray->USN: %u, ArrayEntryCount: %u\n", Vcb, Record, fixupArray->USN, ArrayEntryCount); 2558 2559 fixupArray->USN++; 2560 2561 for (i = 0; i < ArrayEntryCount; i++) 2562 { 2563 DPRINT("USN: %u\tOffset: %u\n", fixupArray->USN, Offset); 2564 2565 pShortToFixUp = (USHORT*)((PCHAR)Record + Offset); 2566 fixupArray->Array[i] = *pShortToFixUp; 2567 *pShortToFixUp = fixupArray->USN; 2568 Offset += Vcb->NtfsInfo.BytesPerSector; 2569 } 2570 2571 return STATUS_SUCCESS; 2572 } 2573 2574 NTSTATUS 2575 ReadLCN(PDEVICE_EXTENSION Vcb, 2576 ULONGLONG lcn, 2577 ULONG count, 2578 PVOID buffer) 2579 { 2580 LARGE_INTEGER DiskSector; 2581 2582 DiskSector.QuadPart = lcn; 2583 2584 return NtfsReadSectors(Vcb->StorageDevice, 2585 DiskSector.u.LowPart * Vcb->NtfsInfo.SectorsPerCluster, 2586 count * Vcb->NtfsInfo.SectorsPerCluster, 2587 Vcb->NtfsInfo.BytesPerSector, 2588 buffer, 2589 FALSE); 2590 } 2591 2592 2593 BOOLEAN 2594 CompareFileName(PUNICODE_STRING FileName, 2595 PINDEX_ENTRY_ATTRIBUTE IndexEntry, 2596 BOOLEAN DirSearch, 2597 BOOLEAN CaseSensitive) 2598 { 2599 BOOLEAN Ret, Alloc = FALSE; 2600 UNICODE_STRING EntryName; 2601 2602 EntryName.Buffer = IndexEntry->FileName.Name; 2603 EntryName.Length = 2604 EntryName.MaximumLength = IndexEntry->FileName.NameLength * sizeof(WCHAR); 2605 2606 if (DirSearch) 2607 { 2608 UNICODE_STRING IntFileName; 2609 if (!CaseSensitive) 2610 { 2611 NT_VERIFY(NT_SUCCESS(RtlUpcaseUnicodeString(&IntFileName, FileName, TRUE))); 2612 Alloc = TRUE; 2613 } 2614 else 2615 { 2616 IntFileName = *FileName; 2617 } 2618 2619 Ret = FsRtlIsNameInExpression(&IntFileName, &EntryName, !CaseSensitive, NULL); 2620 2621 if (Alloc) 2622 { 2623 RtlFreeUnicodeString(&IntFileName); 2624 } 2625 2626 return Ret; 2627 } 2628 else 2629 { 2630 return (RtlCompareUnicodeString(FileName, &EntryName, !CaseSensitive) == 0); 2631 } 2632 } 2633 2634 /** 2635 * @name UpdateMftMirror 2636 * @implemented 2637 * 2638 * Backs-up the first ~4 master file table entries to the $MFTMirr file. 2639 * 2640 * @param Vcb 2641 * Pointer to an NTFS_VCB for the volume whose Mft mirror is being updated. 2642 * 2643 * @return 2644 2645 * STATUS_SUCCESS on success. 2646 * STATUS_INSUFFICIENT_RESOURCES if an allocation failed. 2647 * STATUS_UNSUCCESSFUL if we couldn't read the master file table. 2648 * 2649 * @remarks 2650 * NTFS maintains up-to-date copies of the first several mft entries in the $MFTMirr file. Usually, the first 4 file 2651 * records from the mft are stored. The exact number of entries is determined by the size of $MFTMirr's $DATA. 2652 * If $MFTMirr is not up-to-date, chkdsk will reject every change it can find prior to when $MFTMirr was last updated. 2653 * Therefore, it's recommended to call this function if the volume changes considerably. For instance, IncreaseMftSize() 2654 * relies on this function to keep chkdsk from deleting the mft entries it creates. Note that under most instances, creating 2655 * or deleting a file will not affect the first ~four mft entries, and so will not require updating the mft mirror. 2656 */ 2657 NTSTATUS 2658 UpdateMftMirror(PNTFS_VCB Vcb) 2659 { 2660 PFILE_RECORD_HEADER MirrorFileRecord; 2661 PNTFS_ATTR_CONTEXT MirrDataContext; 2662 PNTFS_ATTR_CONTEXT MftDataContext; 2663 PCHAR DataBuffer; 2664 ULONGLONG DataLength; 2665 NTSTATUS Status; 2666 ULONG BytesRead; 2667 ULONG LengthWritten; 2668 2669 // Allocate memory for the Mft mirror file record 2670 MirrorFileRecord = ExAllocateFromNPagedLookasideList(&Vcb->FileRecLookasideList); 2671 if (!MirrorFileRecord) 2672 { 2673 DPRINT1("Error: Failed to allocate memory for $MFTMirr!\n"); 2674 return STATUS_INSUFFICIENT_RESOURCES; 2675 } 2676 2677 // Read the Mft Mirror file record 2678 Status = ReadFileRecord(Vcb, NTFS_FILE_MFTMIRR, MirrorFileRecord); 2679 if (!NT_SUCCESS(Status)) 2680 { 2681 DPRINT1("ERROR: Failed to read $MFTMirr!\n"); 2682 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MirrorFileRecord); 2683 return Status; 2684 } 2685 2686 // Find the $DATA attribute of $MFTMirr 2687 Status = FindAttribute(Vcb, MirrorFileRecord, AttributeData, L"", 0, &MirrDataContext, NULL); 2688 if (!NT_SUCCESS(Status)) 2689 { 2690 DPRINT1("ERROR: Couldn't find $DATA attribute!\n"); 2691 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MirrorFileRecord); 2692 return Status; 2693 } 2694 2695 // Find the $DATA attribute of $MFT 2696 Status = FindAttribute(Vcb, Vcb->MasterFileTable, AttributeData, L"", 0, &MftDataContext, NULL); 2697 if (!NT_SUCCESS(Status)) 2698 { 2699 DPRINT1("ERROR: Couldn't find $DATA attribute!\n"); 2700 ReleaseAttributeContext(MirrDataContext); 2701 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MirrorFileRecord); 2702 return Status; 2703 } 2704 2705 // Get the size of the mirror's $DATA attribute 2706 DataLength = AttributeDataLength(MirrDataContext->pRecord); 2707 2708 ASSERT(DataLength % Vcb->NtfsInfo.BytesPerFileRecord == 0); 2709 2710 // Create buffer for the mirror's $DATA attribute 2711 DataBuffer = ExAllocatePoolWithTag(NonPagedPool, DataLength, TAG_NTFS); 2712 if (!DataBuffer) 2713 { 2714 DPRINT1("Error: Couldn't allocate memory for $DATA buffer!\n"); 2715 ReleaseAttributeContext(MftDataContext); 2716 ReleaseAttributeContext(MirrDataContext); 2717 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MirrorFileRecord); 2718 return STATUS_INSUFFICIENT_RESOURCES; 2719 } 2720 2721 ASSERT(DataLength < ULONG_MAX); 2722 2723 // Back up the first several entries of the Mft's $DATA Attribute 2724 BytesRead = ReadAttribute(Vcb, MftDataContext, 0, DataBuffer, (ULONG)DataLength); 2725 if (BytesRead != (ULONG)DataLength) 2726 { 2727 DPRINT1("Error: Failed to read $DATA for $MFTMirr!\n"); 2728 ReleaseAttributeContext(MftDataContext); 2729 ReleaseAttributeContext(MirrDataContext); 2730 ExFreePoolWithTag(DataBuffer, TAG_NTFS); 2731 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MirrorFileRecord); 2732 return STATUS_UNSUCCESSFUL; 2733 } 2734 2735 // Write the mirror's $DATA attribute 2736 Status = WriteAttribute(Vcb, 2737 MirrDataContext, 2738 0, 2739 (PUCHAR)DataBuffer, 2740 DataLength, 2741 &LengthWritten, 2742 MirrorFileRecord); 2743 if (!NT_SUCCESS(Status)) 2744 { 2745 DPRINT1("ERROR: Failed to write $DATA attribute of $MFTMirr!\n"); 2746 } 2747 2748 // Cleanup 2749 ReleaseAttributeContext(MftDataContext); 2750 ReleaseAttributeContext(MirrDataContext); 2751 ExFreePoolWithTag(DataBuffer, TAG_NTFS); 2752 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MirrorFileRecord); 2753 2754 return Status; 2755 } 2756 2757 #if 0 2758 static 2759 VOID 2760 DumpIndexEntry(PINDEX_ENTRY_ATTRIBUTE IndexEntry) 2761 { 2762 DPRINT1("Entry: %p\n", IndexEntry); 2763 DPRINT1("\tData.Directory.IndexedFile: %I64x\n", IndexEntry->Data.Directory.IndexedFile); 2764 DPRINT1("\tLength: %u\n", IndexEntry->Length); 2765 DPRINT1("\tKeyLength: %u\n", IndexEntry->KeyLength); 2766 DPRINT1("\tFlags: %x\n", IndexEntry->Flags); 2767 DPRINT1("\tReserved: %x\n", IndexEntry->Reserved); 2768 DPRINT1("\t\tDirectoryFileReferenceNumber: %I64x\n", IndexEntry->FileName.DirectoryFileReferenceNumber); 2769 DPRINT1("\t\tCreationTime: %I64u\n", IndexEntry->FileName.CreationTime); 2770 DPRINT1("\t\tChangeTime: %I64u\n", IndexEntry->FileName.ChangeTime); 2771 DPRINT1("\t\tLastWriteTime: %I64u\n", IndexEntry->FileName.LastWriteTime); 2772 DPRINT1("\t\tLastAccessTime: %I64u\n", IndexEntry->FileName.LastAccessTime); 2773 DPRINT1("\t\tAllocatedSize: %I64u\n", IndexEntry->FileName.AllocatedSize); 2774 DPRINT1("\t\tDataSize: %I64u\n", IndexEntry->FileName.DataSize); 2775 DPRINT1("\t\tFileAttributes: %x\n", IndexEntry->FileName.FileAttributes); 2776 DPRINT1("\t\tNameLength: %u\n", IndexEntry->FileName.NameLength); 2777 DPRINT1("\t\tNameType: %x\n", IndexEntry->FileName.NameType); 2778 DPRINT1("\t\tName: %.*S\n", IndexEntry->FileName.NameLength, IndexEntry->FileName.Name); 2779 } 2780 #endif 2781 2782 NTSTATUS 2783 BrowseSubNodeIndexEntries(PNTFS_VCB Vcb, 2784 PFILE_RECORD_HEADER MftRecord, 2785 ULONG IndexBlockSize, 2786 PUNICODE_STRING FileName, 2787 PNTFS_ATTR_CONTEXT IndexAllocationContext, 2788 PRTL_BITMAP Bitmap, 2789 ULONGLONG VCN, 2790 PULONG StartEntry, 2791 PULONG CurrentEntry, 2792 BOOLEAN DirSearch, 2793 BOOLEAN CaseSensitive, 2794 ULONGLONG *OutMFTIndex) 2795 { 2796 PINDEX_BUFFER IndexRecord; 2797 ULONGLONG Offset; 2798 ULONG BytesRead; 2799 PINDEX_ENTRY_ATTRIBUTE FirstEntry; 2800 PINDEX_ENTRY_ATTRIBUTE LastEntry; 2801 PINDEX_ENTRY_ATTRIBUTE IndexEntry; 2802 ULONG NodeNumber; 2803 NTSTATUS Status; 2804 2805 DPRINT("BrowseSubNodeIndexEntries(%p, %p, %lu, %wZ, %p, %p, %I64d, %lu, %lu, %s, %s, %p)\n", 2806 Vcb, 2807 MftRecord, 2808 IndexBlockSize, 2809 FileName, 2810 IndexAllocationContext, 2811 Bitmap, 2812 VCN, 2813 *StartEntry, 2814 *CurrentEntry, 2815 "FALSE", 2816 DirSearch ? "TRUE" : "FALSE", 2817 CaseSensitive ? "TRUE" : "FALSE", 2818 OutMFTIndex); 2819 2820 // Calculate node number as VCN / Clusters per index record 2821 NodeNumber = VCN / (Vcb->NtfsInfo.BytesPerIndexRecord / Vcb->NtfsInfo.BytesPerCluster); 2822 2823 // Is the bit for this node clear in the bitmap? 2824 if (!RtlCheckBit(Bitmap, NodeNumber)) 2825 { 2826 DPRINT1("File system corruption detected, node with VCN %I64u is being reused or is marked as deleted.\n", VCN); 2827 return STATUS_DATA_ERROR; 2828 } 2829 2830 // Clear the bit for this node so it can't be recursively referenced 2831 RtlClearBits(Bitmap, NodeNumber, 1); 2832 2833 // Allocate memory for the index record 2834 IndexRecord = ExAllocatePoolWithTag(NonPagedPool, IndexBlockSize, TAG_NTFS); 2835 if (!IndexRecord) 2836 { 2837 DPRINT1("Unable to allocate memory for index record!\n"); 2838 return STATUS_INSUFFICIENT_RESOURCES; 2839 } 2840 2841 // Calculate offset of index record 2842 Offset = VCN * Vcb->NtfsInfo.BytesPerCluster; 2843 2844 // Read the index record 2845 BytesRead = ReadAttribute(Vcb, IndexAllocationContext, Offset, (PCHAR)IndexRecord, IndexBlockSize); 2846 if (BytesRead != IndexBlockSize) 2847 { 2848 DPRINT1("Unable to read index record!\n"); 2849 ExFreePoolWithTag(IndexRecord, TAG_NTFS); 2850 return STATUS_UNSUCCESSFUL; 2851 } 2852 2853 // Assert that we're dealing with an index record here 2854 ASSERT(IndexRecord->Ntfs.Type == NRH_INDX_TYPE); 2855 2856 // Apply the fixup array to the index record 2857 Status = FixupUpdateSequenceArray(Vcb, &((PFILE_RECORD_HEADER)IndexRecord)->Ntfs); 2858 if (!NT_SUCCESS(Status)) 2859 { 2860 ExFreePoolWithTag(IndexRecord, TAG_NTFS); 2861 DPRINT1("Failed to apply fixup array!\n"); 2862 return Status; 2863 } 2864 2865 ASSERT(IndexRecord->Header.AllocatedSize + FIELD_OFFSET(INDEX_BUFFER, Header) == IndexBlockSize); 2866 FirstEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)&IndexRecord->Header + IndexRecord->Header.FirstEntryOffset); 2867 LastEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)&IndexRecord->Header + IndexRecord->Header.TotalSizeOfEntries); 2868 ASSERT(LastEntry <= (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)IndexRecord + IndexBlockSize)); 2869 2870 // Loop through all Index Entries of index, starting with FirstEntry 2871 IndexEntry = FirstEntry; 2872 while (IndexEntry <= LastEntry) 2873 { 2874 // Does IndexEntry have a sub-node? 2875 if (IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE) 2876 { 2877 if (!(IndexRecord->Header.Flags & INDEX_NODE_LARGE) || !IndexAllocationContext) 2878 { 2879 DPRINT1("Filesystem corruption detected!\n"); 2880 } 2881 else 2882 { 2883 Status = BrowseSubNodeIndexEntries(Vcb, 2884 MftRecord, 2885 IndexBlockSize, 2886 FileName, 2887 IndexAllocationContext, 2888 Bitmap, 2889 GetIndexEntryVCN(IndexEntry), 2890 StartEntry, 2891 CurrentEntry, 2892 DirSearch, 2893 CaseSensitive, 2894 OutMFTIndex); 2895 if (NT_SUCCESS(Status)) 2896 { 2897 ExFreePoolWithTag(IndexRecord, TAG_NTFS); 2898 return Status; 2899 } 2900 } 2901 } 2902 2903 // Are we done? 2904 if (IndexEntry->Flags & NTFS_INDEX_ENTRY_END) 2905 break; 2906 2907 // If we've found a file whose index is greater than or equal to StartEntry that matches the search criteria 2908 if ((IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK) >= NTFS_FILE_FIRST_USER_FILE && 2909 *CurrentEntry >= *StartEntry && 2910 IndexEntry->FileName.NameType != NTFS_FILE_NAME_DOS && 2911 CompareFileName(FileName, IndexEntry, DirSearch, CaseSensitive)) 2912 { 2913 *StartEntry = *CurrentEntry; 2914 *OutMFTIndex = (IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK); 2915 ExFreePoolWithTag(IndexRecord, TAG_NTFS); 2916 return STATUS_SUCCESS; 2917 } 2918 2919 // Advance to the next index entry 2920 (*CurrentEntry) += 1; 2921 ASSERT(IndexEntry->Length >= sizeof(INDEX_ENTRY_ATTRIBUTE)); 2922 IndexEntry = (PINDEX_ENTRY_ATTRIBUTE)((PCHAR)IndexEntry + IndexEntry->Length); 2923 } 2924 2925 ExFreePoolWithTag(IndexRecord, TAG_NTFS); 2926 2927 return STATUS_OBJECT_PATH_NOT_FOUND; 2928 } 2929 2930 NTSTATUS 2931 BrowseIndexEntries(PDEVICE_EXTENSION Vcb, 2932 PFILE_RECORD_HEADER MftRecord, 2933 PINDEX_ROOT_ATTRIBUTE IndexRecord, 2934 ULONG IndexBlockSize, 2935 PINDEX_ENTRY_ATTRIBUTE FirstEntry, 2936 PINDEX_ENTRY_ATTRIBUTE LastEntry, 2937 PUNICODE_STRING FileName, 2938 PULONG StartEntry, 2939 PULONG CurrentEntry, 2940 BOOLEAN DirSearch, 2941 BOOLEAN CaseSensitive, 2942 ULONGLONG *OutMFTIndex) 2943 { 2944 NTSTATUS Status; 2945 PINDEX_ENTRY_ATTRIBUTE IndexEntry; 2946 PNTFS_ATTR_CONTEXT IndexAllocationContext; 2947 PNTFS_ATTR_CONTEXT BitmapContext; 2948 PCHAR *BitmapMem; 2949 ULONG *BitmapPtr; 2950 RTL_BITMAP Bitmap; 2951 2952 DPRINT("BrowseIndexEntries(%p, %p, %p, %lu, %p, %p, %wZ, %lu, %lu, %s, %s, %p)\n", 2953 Vcb, 2954 MftRecord, 2955 IndexRecord, 2956 IndexBlockSize, 2957 FirstEntry, 2958 LastEntry, 2959 FileName, 2960 *StartEntry, 2961 *CurrentEntry, 2962 DirSearch ? "TRUE" : "FALSE", 2963 CaseSensitive ? "TRUE" : "FALSE", 2964 OutMFTIndex); 2965 2966 // Find the $I30 index allocation, if there is one 2967 Status = FindAttribute(Vcb, MftRecord, AttributeIndexAllocation, L"$I30", 4, &IndexAllocationContext, NULL); 2968 if (NT_SUCCESS(Status)) 2969 { 2970 ULONGLONG BitmapLength; 2971 // Find the bitmap attribute for the index 2972 Status = FindAttribute(Vcb, MftRecord, AttributeBitmap, L"$I30", 4, &BitmapContext, NULL); 2973 if (!NT_SUCCESS(Status)) 2974 { 2975 DPRINT1("Potential file system corruption detected!\n"); 2976 ReleaseAttributeContext(IndexAllocationContext); 2977 return Status; 2978 } 2979 2980 // Get the length of the bitmap attribute 2981 BitmapLength = AttributeDataLength(BitmapContext->pRecord); 2982 2983 // Allocate memory for the bitmap, including some padding; RtlInitializeBitmap() wants a pointer 2984 // that's ULONG-aligned, and it wants the size of the memory allocated for it to be a ULONG-multiple. 2985 BitmapMem = ExAllocatePoolWithTag(NonPagedPool, BitmapLength + sizeof(ULONG), TAG_NTFS); 2986 if (!BitmapMem) 2987 { 2988 DPRINT1("Error: failed to allocate bitmap!"); 2989 ReleaseAttributeContext(BitmapContext); 2990 ReleaseAttributeContext(IndexAllocationContext); 2991 return STATUS_INSUFFICIENT_RESOURCES; 2992 } 2993 2994 RtlZeroMemory(BitmapMem, BitmapLength + sizeof(ULONG)); 2995 2996 // RtlInitializeBitmap() wants a pointer that's ULONG-aligned. 2997 BitmapPtr = (PULONG)ALIGN_UP_BY((ULONG_PTR)BitmapMem, sizeof(ULONG)); 2998 2999 // Read the existing bitmap data 3000 Status = ReadAttribute(Vcb, BitmapContext, 0, (PCHAR)BitmapPtr, BitmapLength); 3001 if (!NT_SUCCESS(Status)) 3002 { 3003 DPRINT1("ERROR: Failed to read bitmap attribute!\n"); 3004 ExFreePoolWithTag(BitmapMem, TAG_NTFS); 3005 ReleaseAttributeContext(BitmapContext); 3006 ReleaseAttributeContext(IndexAllocationContext); 3007 return Status; 3008 } 3009 3010 // Initialize bitmap 3011 RtlInitializeBitMap(&Bitmap, BitmapPtr, BitmapLength * 8); 3012 } 3013 else 3014 { 3015 // Couldn't find an index allocation 3016 IndexAllocationContext = NULL; 3017 } 3018 3019 3020 // Loop through all Index Entries of index, starting with FirstEntry 3021 IndexEntry = FirstEntry; 3022 while (IndexEntry <= LastEntry) 3023 { 3024 // Does IndexEntry have a sub-node? 3025 if (IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE) 3026 { 3027 if (!(IndexRecord->Header.Flags & INDEX_ROOT_LARGE) || !IndexAllocationContext) 3028 { 3029 DPRINT1("Filesystem corruption detected!\n"); 3030 } 3031 else 3032 { 3033 Status = BrowseSubNodeIndexEntries(Vcb, 3034 MftRecord, 3035 IndexBlockSize, 3036 FileName, 3037 IndexAllocationContext, 3038 &Bitmap, 3039 GetIndexEntryVCN(IndexEntry), 3040 StartEntry, 3041 CurrentEntry, 3042 DirSearch, 3043 CaseSensitive, 3044 OutMFTIndex); 3045 if (NT_SUCCESS(Status)) 3046 { 3047 ExFreePoolWithTag(BitmapMem, TAG_NTFS); 3048 ReleaseAttributeContext(BitmapContext); 3049 ReleaseAttributeContext(IndexAllocationContext); 3050 return Status; 3051 } 3052 } 3053 } 3054 3055 // Are we done? 3056 if (IndexEntry->Flags & NTFS_INDEX_ENTRY_END) 3057 break; 3058 3059 // If we've found a file whose index is greater than or equal to StartEntry that matches the search criteria 3060 if ((IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK) >= NTFS_FILE_FIRST_USER_FILE && 3061 *CurrentEntry >= *StartEntry && 3062 IndexEntry->FileName.NameType != NTFS_FILE_NAME_DOS && 3063 CompareFileName(FileName, IndexEntry, DirSearch, CaseSensitive)) 3064 { 3065 *StartEntry = *CurrentEntry; 3066 *OutMFTIndex = (IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK); 3067 if (IndexAllocationContext) 3068 { 3069 ExFreePoolWithTag(BitmapMem, TAG_NTFS); 3070 ReleaseAttributeContext(BitmapContext); 3071 ReleaseAttributeContext(IndexAllocationContext); 3072 } 3073 return STATUS_SUCCESS; 3074 } 3075 3076 // Advance to the next index entry 3077 (*CurrentEntry) += 1; 3078 ASSERT(IndexEntry->Length >= sizeof(INDEX_ENTRY_ATTRIBUTE)); 3079 IndexEntry = (PINDEX_ENTRY_ATTRIBUTE)((PCHAR)IndexEntry + IndexEntry->Length); 3080 } 3081 3082 if (IndexAllocationContext) 3083 { 3084 ExFreePoolWithTag(BitmapMem, TAG_NTFS); 3085 ReleaseAttributeContext(BitmapContext); 3086 ReleaseAttributeContext(IndexAllocationContext); 3087 } 3088 3089 return STATUS_OBJECT_PATH_NOT_FOUND; 3090 } 3091 3092 NTSTATUS 3093 NtfsFindMftRecord(PDEVICE_EXTENSION Vcb, 3094 ULONGLONG MFTIndex, 3095 PUNICODE_STRING FileName, 3096 PULONG FirstEntry, 3097 BOOLEAN DirSearch, 3098 BOOLEAN CaseSensitive, 3099 ULONGLONG *OutMFTIndex) 3100 { 3101 PFILE_RECORD_HEADER MftRecord; 3102 PNTFS_ATTR_CONTEXT IndexRootCtx; 3103 PINDEX_ROOT_ATTRIBUTE IndexRoot; 3104 PCHAR IndexRecord; 3105 PINDEX_ENTRY_ATTRIBUTE IndexEntry, IndexEntryEnd; 3106 NTSTATUS Status; 3107 ULONG CurrentEntry = 0; 3108 3109 DPRINT("NtfsFindMftRecord(%p, %I64d, %wZ, %lu, %s, %s, %p)\n", 3110 Vcb, 3111 MFTIndex, 3112 FileName, 3113 *FirstEntry, 3114 DirSearch ? "TRUE" : "FALSE", 3115 CaseSensitive ? "TRUE" : "FALSE", 3116 OutMFTIndex); 3117 3118 MftRecord = ExAllocateFromNPagedLookasideList(&Vcb->FileRecLookasideList); 3119 if (MftRecord == NULL) 3120 { 3121 return STATUS_INSUFFICIENT_RESOURCES; 3122 } 3123 3124 Status = ReadFileRecord(Vcb, MFTIndex, MftRecord); 3125 if (!NT_SUCCESS(Status)) 3126 { 3127 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MftRecord); 3128 return Status; 3129 } 3130 3131 ASSERT(MftRecord->Ntfs.Type == NRH_FILE_TYPE); 3132 Status = FindAttribute(Vcb, MftRecord, AttributeIndexRoot, L"$I30", 4, &IndexRootCtx, NULL); 3133 if (!NT_SUCCESS(Status)) 3134 { 3135 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MftRecord); 3136 return Status; 3137 } 3138 3139 IndexRecord = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerIndexRecord, TAG_NTFS); 3140 if (IndexRecord == NULL) 3141 { 3142 ReleaseAttributeContext(IndexRootCtx); 3143 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MftRecord); 3144 return STATUS_INSUFFICIENT_RESOURCES; 3145 } 3146 3147 ReadAttribute(Vcb, IndexRootCtx, 0, IndexRecord, Vcb->NtfsInfo.BytesPerIndexRecord); 3148 IndexRoot = (PINDEX_ROOT_ATTRIBUTE)IndexRecord; 3149 IndexEntry = (PINDEX_ENTRY_ATTRIBUTE)((PCHAR)&IndexRoot->Header + IndexRoot->Header.FirstEntryOffset); 3150 /* Index root is always resident. */ 3151 IndexEntryEnd = (PINDEX_ENTRY_ATTRIBUTE)(IndexRecord + IndexRoot->Header.TotalSizeOfEntries); 3152 ReleaseAttributeContext(IndexRootCtx); 3153 3154 DPRINT("IndexRecordSize: %x IndexBlockSize: %x\n", Vcb->NtfsInfo.BytesPerIndexRecord, IndexRoot->SizeOfEntry); 3155 3156 Status = BrowseIndexEntries(Vcb, 3157 MftRecord, 3158 (PINDEX_ROOT_ATTRIBUTE)IndexRecord, 3159 IndexRoot->SizeOfEntry, 3160 IndexEntry, 3161 IndexEntryEnd, 3162 FileName, 3163 FirstEntry, 3164 &CurrentEntry, 3165 DirSearch, 3166 CaseSensitive, 3167 OutMFTIndex); 3168 3169 ExFreePoolWithTag(IndexRecord, TAG_NTFS); 3170 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MftRecord); 3171 3172 return Status; 3173 } 3174 3175 NTSTATUS 3176 NtfsLookupFileAt(PDEVICE_EXTENSION Vcb, 3177 PUNICODE_STRING PathName, 3178 BOOLEAN CaseSensitive, 3179 PFILE_RECORD_HEADER *FileRecord, 3180 PULONGLONG MFTIndex, 3181 ULONGLONG CurrentMFTIndex) 3182 { 3183 UNICODE_STRING Current, Remaining; 3184 NTSTATUS Status; 3185 ULONG FirstEntry = 0; 3186 3187 DPRINT("NtfsLookupFileAt(%p, %wZ, %s, %p, %p, %I64x)\n", 3188 Vcb, 3189 PathName, 3190 CaseSensitive ? "TRUE" : "FALSE", 3191 FileRecord, 3192 MFTIndex, 3193 CurrentMFTIndex); 3194 3195 FsRtlDissectName(*PathName, &Current, &Remaining); 3196 3197 while (Current.Length != 0) 3198 { 3199 DPRINT("Current: %wZ\n", &Current); 3200 3201 Status = NtfsFindMftRecord(Vcb, CurrentMFTIndex, &Current, &FirstEntry, FALSE, CaseSensitive, &CurrentMFTIndex); 3202 if (!NT_SUCCESS(Status)) 3203 { 3204 return Status; 3205 } 3206 3207 if (Remaining.Length == 0) 3208 break; 3209 3210 FsRtlDissectName(Current, &Current, &Remaining); 3211 } 3212 3213 *FileRecord = ExAllocateFromNPagedLookasideList(&Vcb->FileRecLookasideList); 3214 if (*FileRecord == NULL) 3215 { 3216 DPRINT("NtfsLookupFileAt: Can't allocate MFT record\n"); 3217 return STATUS_INSUFFICIENT_RESOURCES; 3218 } 3219 3220 Status = ReadFileRecord(Vcb, CurrentMFTIndex, *FileRecord); 3221 if (!NT_SUCCESS(Status)) 3222 { 3223 DPRINT("NtfsLookupFileAt: Can't read MFT record\n"); 3224 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, *FileRecord); 3225 return Status; 3226 } 3227 3228 *MFTIndex = CurrentMFTIndex; 3229 3230 return STATUS_SUCCESS; 3231 } 3232 3233 NTSTATUS 3234 NtfsLookupFile(PDEVICE_EXTENSION Vcb, 3235 PUNICODE_STRING PathName, 3236 BOOLEAN CaseSensitive, 3237 PFILE_RECORD_HEADER *FileRecord, 3238 PULONGLONG MFTIndex) 3239 { 3240 return NtfsLookupFileAt(Vcb, PathName, CaseSensitive, FileRecord, MFTIndex, NTFS_FILE_ROOT); 3241 } 3242 3243 void 3244 NtfsDumpData(ULONG_PTR Buffer, ULONG Length) 3245 { 3246 ULONG i, j; 3247 3248 // dump binary data, 8 bytes at a time 3249 for (i = 0; i < Length; i += 8) 3250 { 3251 // display current offset, in hex 3252 DbgPrint("\t%03x\t", i); 3253 3254 // display hex value of each of the next 8 bytes 3255 for (j = 0; j < 8; j++) 3256 DbgPrint("%02x ", *(PUCHAR)(Buffer + i + j)); 3257 DbgPrint("\n"); 3258 } 3259 } 3260 3261 /** 3262 * @name NtfsDumpFileRecord 3263 * @implemented 3264 * 3265 * Provides diagnostic information about a file record. Prints a hex dump 3266 * of the entire record (based on the size reported by FileRecord->ByesInUse), 3267 * then prints a dump of each attribute. 3268 * 3269 * @param Vcb 3270 * Pointer to a DEVICE_EXTENSION describing the volume. 3271 * 3272 * @param FileRecord 3273 * Pointer to the file record to be analyzed. 3274 * 3275 * @remarks 3276 * FileRecord must be a complete file record at least FileRecord->BytesAllocated 3277 * in size, and not just the header. 3278 * 3279 */ 3280 VOID 3281 NtfsDumpFileRecord(PDEVICE_EXTENSION Vcb, 3282 PFILE_RECORD_HEADER FileRecord) 3283 { 3284 ULONG i, j; 3285 3286 // dump binary data, 8 bytes at a time 3287 for (i = 0; i < FileRecord->BytesInUse; i += 8) 3288 { 3289 // display current offset, in hex 3290 DbgPrint("\t%03x\t", i); 3291 3292 // display hex value of each of the next 8 bytes 3293 for (j = 0; j < 8; j++) 3294 DbgPrint("%02x ", *(PUCHAR)((ULONG_PTR)FileRecord + i + j)); 3295 DbgPrint("\n"); 3296 } 3297 3298 NtfsDumpFileAttributes(Vcb, FileRecord); 3299 } 3300 3301 NTSTATUS 3302 NtfsFindFileAt(PDEVICE_EXTENSION Vcb, 3303 PUNICODE_STRING SearchPattern, 3304 PULONG FirstEntry, 3305 PFILE_RECORD_HEADER *FileRecord, 3306 PULONGLONG MFTIndex, 3307 ULONGLONG CurrentMFTIndex, 3308 BOOLEAN CaseSensitive) 3309 { 3310 NTSTATUS Status; 3311 3312 DPRINT("NtfsFindFileAt(%p, %wZ, %lu, %p, %p, %I64x, %s)\n", 3313 Vcb, 3314 SearchPattern, 3315 *FirstEntry, 3316 FileRecord, 3317 MFTIndex, 3318 CurrentMFTIndex, 3319 (CaseSensitive ? "TRUE" : "FALSE")); 3320 3321 Status = NtfsFindMftRecord(Vcb, CurrentMFTIndex, SearchPattern, FirstEntry, TRUE, CaseSensitive, &CurrentMFTIndex); 3322 if (!NT_SUCCESS(Status)) 3323 { 3324 DPRINT("NtfsFindFileAt: NtfsFindMftRecord() failed with status 0x%08lx\n", Status); 3325 return Status; 3326 } 3327 3328 *FileRecord = ExAllocateFromNPagedLookasideList(&Vcb->FileRecLookasideList); 3329 if (*FileRecord == NULL) 3330 { 3331 DPRINT("NtfsFindFileAt: Can't allocate MFT record\n"); 3332 return STATUS_INSUFFICIENT_RESOURCES; 3333 } 3334 3335 Status = ReadFileRecord(Vcb, CurrentMFTIndex, *FileRecord); 3336 if (!NT_SUCCESS(Status)) 3337 { 3338 DPRINT("NtfsFindFileAt: Can't read MFT record\n"); 3339 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, *FileRecord); 3340 return Status; 3341 } 3342 3343 *MFTIndex = CurrentMFTIndex; 3344 3345 return STATUS_SUCCESS; 3346 } 3347 3348 /* EOF */ 3349