1 /* 2 * COPYRIGHT: See COPYING in the top level directory 3 * PROJECT: ReactOS NTFS Information tool 4 * FILE: rosinternals/nfi/nfi.c 5 * PURPOSE: Query information from NTFS volume using FSCTL 6 * PROGRAMMERS: Pierre Schweitzer <pierre@reactos.org> 7 */ 8 9 #include <windows.h> 10 #include <tchar.h> 11 #include <stdio.h> 12 13 typedef struct 14 { 15 ULONG Type; 16 USHORT UsaOffset; 17 USHORT UsaCount; 18 ULONGLONG Lsn; 19 } NTFS_RECORD_HEADER, *PNTFS_RECORD_HEADER; 20 21 #define NRH_FILE_TYPE 0x454C4946 22 23 typedef enum 24 { 25 AttributeStandardInformation = 0x10, 26 AttributeAttributeList = 0x20, 27 AttributeFileName = 0x30, 28 AttributeObjectId = 0x40, 29 AttributeSecurityDescriptor = 0x50, 30 AttributeVolumeName = 0x60, 31 AttributeVolumeInformation = 0x70, 32 AttributeData = 0x80, 33 AttributeIndexRoot = 0x90, 34 AttributeIndexAllocation = 0xA0, 35 AttributeBitmap = 0xB0, 36 AttributeReparsePoint = 0xC0, 37 AttributeEAInformation = 0xD0, 38 AttributeEA = 0xE0, 39 AttributePropertySet = 0xF0, 40 AttributeLoggedUtilityStream = 0x100, 41 AttributeEnd = 0xFFFFFFFF 42 } ATTRIBUTE_TYPE, *PATTRIBUTE_TYPE; 43 44 typedef struct _FILE_RECORD_HEADER 45 { 46 NTFS_RECORD_HEADER Ntfs; 47 USHORT SequenceNumber; 48 USHORT LinkCount; 49 USHORT AttributeOffset; 50 USHORT Flags; 51 ULONG BytesInUse; 52 ULONG BytesAllocated; 53 ULONGLONG BaseFileRecord; 54 USHORT NextAttributeNumber; 55 } FILE_RECORD_HEADER, *PFILE_RECORD_HEADER; 56 57 typedef struct 58 { 59 ULONG Type; 60 ULONG Length; 61 UCHAR IsNonResident; 62 UCHAR NameLength; 63 USHORT NameOffset; 64 USHORT Flags; 65 USHORT Instance; 66 union 67 { 68 struct 69 { 70 ULONG ValueLength; 71 USHORT ValueOffset; 72 UCHAR Flags; 73 UCHAR Reserved; 74 } Resident; 75 struct 76 { 77 ULONGLONG LowestVCN; 78 ULONGLONG HighestVCN; 79 USHORT MappingPairsOffset; 80 USHORT CompressionUnit; 81 UCHAR Reserved[4]; 82 LONGLONG AllocatedSize; 83 LONGLONG DataSize; 84 LONGLONG InitializedSize; 85 LONGLONG CompressedSize; 86 } NonResident; 87 }; 88 } NTFS_ATTR_RECORD, *PNTFS_ATTR_RECORD; 89 90 typedef struct 91 { 92 ULONGLONG DirectoryFileReferenceNumber; 93 ULONGLONG CreationTime; 94 ULONGLONG ChangeTime; 95 ULONGLONG LastWriteTime; 96 ULONGLONG LastAccessTime; 97 ULONGLONG AllocatedSize; 98 ULONGLONG DataSize; 99 ULONG FileAttributes; 100 union 101 { 102 struct 103 { 104 USHORT PackedEaSize; 105 USHORT AlignmentOrReserved; 106 } EaInfo; 107 ULONG ReparseTag; 108 } Extended; 109 UCHAR NameLength; 110 UCHAR NameType; 111 WCHAR Name[1]; 112 } FILENAME_ATTRIBUTE, *PFILENAME_ATTRIBUTE; 113 114 #define NTFS_FILE_NAME_POSIX 0 115 #define NTFS_FILE_NAME_WIN32 1 116 #define NTFS_FILE_NAME_DOS 2 117 #define NTFS_FILE_NAME_WIN32_AND_DOS 3 118 119 #define NTFS_FILE_MFT 0 120 #define NTFS_FILE_MFTMIRR 1 121 #define NTFS_FILE_LOGFILE 2 122 #define NTFS_FILE_VOLUME 3 123 #define NTFS_FILE_ATTRDEF 4 124 #define NTFS_FILE_ROOT 5 125 #define NTFS_FILE_BITMAP 6 126 #define NTFS_FILE_BOOT 7 127 #define NTFS_FILE_BADCLUS 8 128 #define NTFS_FILE_QUOTA 9 129 #define NTFS_FILE_UPCASE 10 130 #define NTFS_FILE_EXTEND 11 131 132 PWSTR KnownEntries[NTFS_FILE_EXTEND + 1] = 133 { 134 _T("Master File Table ($Mft)"), 135 _T("Master File Table Mirror ($MftMirr)"), 136 _T("Log File ($LogFile)"), 137 _T("DASD ($Volume)"), 138 _T("Attribute Definition Table ($AttrDef)"), 139 _T("Root Directory"), 140 _T("Volume Bitmap ($BitMap)"), 141 _T("Boot Sectors ($Boot)"), 142 _T("Bad Cluster List ($BadClus)"), 143 _T("Security ($Secure)"), 144 _T("Upcase Table ($UpCase)"), 145 _T("Extend Table ($Extend)") 146 }; 147 148 #define NTFS_MFT_MASK 0x0000FFFFFFFFFFFFULL 149 150 #define NTFS_FILE_TYPE_DIRECTORY 0x10000000 151 152 typedef struct _NAME_CACHE_ENTRY 153 { 154 struct _NAME_CACHE_ENTRY * Next; 155 ULONGLONG MftId; 156 ULONG NameLen; 157 WCHAR Name[1]; 158 } NAME_CACHE_ENTRY, *PNAME_CACHE_ENTRY; 159 160 PNAME_CACHE_ENTRY CacheHead = NULL; 161 162 void PrintUsage(void) 163 { 164 /* FIXME */ 165 } 166 167 PNAME_CACHE_ENTRY FindInCache(ULONGLONG MftId) 168 { 169 PNAME_CACHE_ENTRY CacheEntry; 170 171 for (CacheEntry = CacheHead; CacheEntry != NULL; CacheEntry = CacheEntry->Next) 172 { 173 if (MftId == CacheEntry->MftId) 174 { 175 return CacheEntry; 176 } 177 } 178 179 return NULL; 180 } 181 182 PNAME_CACHE_ENTRY AddToCache(PWSTR Name, DWORD Length, ULONGLONG MftId) 183 { 184 PNAME_CACHE_ENTRY CacheEntry; 185 186 /* Don't add in cache if already there! */ 187 CacheEntry = FindInCache(MftId); 188 if (CacheEntry != NULL) 189 { 190 return CacheEntry; 191 } 192 193 /* Allocate an entry big enough to store name and cache info */ 194 CacheEntry = HeapAlloc(GetProcessHeap(), 0, sizeof(NAME_CACHE_ENTRY) + Length); 195 if (CacheEntry == NULL) 196 { 197 return NULL; 198 } 199 200 /* Insert in head (likely more perf) */ 201 CacheEntry->Next = CacheHead; 202 CacheHead = CacheEntry; 203 /* Set up entry with full path */ 204 CacheEntry->MftId = MftId; 205 CacheEntry->NameLen = Length; 206 CopyMemory(CacheEntry->Name, Name, Length); 207 208 return CacheEntry; 209 } 210 211 PNAME_CACHE_ENTRY HandleFile(HANDLE VolumeHandle, PNTFS_VOLUME_DATA_BUFFER VolumeInfo, ULONGLONG Id, PNTFS_FILE_RECORD_OUTPUT_BUFFER OutputBuffer, BOOLEAN Silent); 212 213 PNAME_CACHE_ENTRY PrintPrettyName(HANDLE VolumeHandle, PNTFS_VOLUME_DATA_BUFFER VolumeInfo, PNTFS_ATTR_RECORD Attributes, PNTFS_ATTR_RECORD AttributesEnd, ULONGLONG MftId, BOOLEAN Silent) 214 { 215 BOOLEAN FirstRun; 216 PNTFS_ATTR_RECORD Attribute; 217 218 FirstRun = TRUE; 219 220 /* Setup name for "standard" files */ 221 if (MftId <= NTFS_FILE_EXTEND) 222 { 223 if (!Silent) 224 { 225 _tprintf(_T("%s\n"), KnownEntries[MftId]); 226 } 227 228 /* $Extend can contain entries, add it in cache */ 229 if (MftId == NTFS_FILE_EXTEND) 230 { 231 return AddToCache(L"\\$Extend", sizeof(L"\\$Extend") - sizeof(UNICODE_NULL), NTFS_FILE_EXTEND); 232 } 233 234 return NULL; 235 } 236 237 /* We'll first try to use the Win32 name 238 * If we fail finding it, we'll loop again for any other name 239 */ 240 TryAgain: 241 /* Loop all the attributes */ 242 Attribute = Attributes; 243 while (Attribute < AttributesEnd && Attribute->Type != AttributeEnd) 244 { 245 WCHAR Display[MAX_PATH]; 246 PFILENAME_ATTRIBUTE Name; 247 ULONGLONG ParentId; 248 ULONG Length; 249 PNAME_CACHE_ENTRY CacheEntry; 250 251 /* Move to the next arg if: 252 * - Not a file name 253 * - Not resident (should never happen!) 254 */ 255 if (Attribute->Type != AttributeFileName || Attribute->IsNonResident) 256 { 257 Attribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)Attribute + Attribute->Length); 258 continue; 259 } 260 261 /* Get the attribute data */ 262 Name = (PFILENAME_ATTRIBUTE)((ULONG_PTR)Attribute + Attribute->Resident.ValueOffset); 263 /* If not Win32, only accept if it wasn't the first run */ 264 if ((Name->NameType == NTFS_FILE_NAME_POSIX || Name->NameType == NTFS_FILE_NAME_DOS) && FirstRun) 265 { 266 Attribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)Attribute + Attribute->Length); 267 continue; 268 } 269 270 /* We accepted that name, get the parent ID to setup name */ 271 ParentId = Name->DirectoryFileReferenceNumber & NTFS_MFT_MASK; 272 /* If root, easy, just print \ */ 273 if (ParentId == NTFS_FILE_ROOT) 274 { 275 Display[0] = L'\\'; 276 CopyMemory(&Display[1], Name->Name, Name->NameLength * sizeof(WCHAR)); 277 Length = Name->NameLength + 1; 278 Display[Length] = UNICODE_NULL; 279 } 280 /* Default case */ 281 else 282 { 283 /* Did we already cache the name? */ 284 CacheEntry = FindInCache(ParentId); 285 286 /* It wasn't in cache? Try to get it in! */ 287 if (CacheEntry == NULL) 288 { 289 PNTFS_FILE_RECORD_OUTPUT_BUFFER OutputBuffer; 290 291 OutputBuffer = HeapAlloc(GetProcessHeap(), 0, VolumeInfo->BytesPerFileRecordSegment + sizeof(NTFS_FILE_RECORD_OUTPUT_BUFFER)); 292 if (OutputBuffer != NULL) 293 { 294 CacheEntry = HandleFile(VolumeHandle, VolumeInfo, ParentId, OutputBuffer, TRUE); 295 HeapFree(GetProcessHeap(), 0, OutputBuffer); 296 } 297 } 298 299 /* Nothing written yet */ 300 Length = 0; 301 302 /* We cached it */ 303 if (CacheEntry != NULL) 304 { 305 /* Set up name. The cache entry contains full path */ 306 Length = CacheEntry->NameLen / sizeof(WCHAR); 307 CopyMemory(Display, CacheEntry->Name, CacheEntry->NameLen); 308 Display[Length] = L'\\'; 309 ++Length; 310 } 311 else 312 { 313 _tprintf(_T("Parent: %I64d\n"), ParentId); 314 } 315 316 /* Copy our name */ 317 CopyMemory(Display + Length, Name->Name, Name->NameLength * sizeof(WCHAR)); 318 Length += Name->NameLength; 319 Display[Length] = UNICODE_NULL; 320 } 321 322 if (!Silent) 323 { 324 /* Display the name */ 325 _tprintf(_T("%s\n"), Display); 326 } 327 328 /* Reset cache entry */ 329 CacheEntry = NULL; 330 331 /* If that's a directory, put it in the cache */ 332 if (Name->FileAttributes & NTFS_FILE_TYPE_DIRECTORY) 333 { 334 CacheEntry = AddToCache(Display, Length * sizeof(WCHAR), MftId); 335 } 336 337 /* Now, just quit */ 338 FirstRun = FALSE; 339 340 return CacheEntry; 341 } 342 343 /* If was first run (Win32 search), retry with other names */ 344 if (FirstRun) 345 { 346 FirstRun = FALSE; 347 goto TryAgain; 348 } 349 350 /* If we couldn't find a name, print unknown */ 351 if (!Silent) 352 { 353 _tprintf(_T("(unknown/unnamed)\n")); 354 } 355 356 return NULL; 357 } 358 359 PUCHAR DecodeRun(PUCHAR DataRun, LONGLONG *DataRunOffset, ULONGLONG *DataRunLength) 360 { 361 UCHAR DataRunOffsetSize; 362 UCHAR DataRunLengthSize; 363 CHAR i; 364 365 /* Get the offset size (in bytes) of the run */ 366 DataRunOffsetSize = (*DataRun >> 4) & 0xF; 367 /* Get the length size (in bytes) of the run */ 368 DataRunLengthSize = *DataRun & 0xF; 369 370 /* Initialize values */ 371 *DataRunOffset = 0; 372 *DataRunLength = 0; 373 374 /* Move to next byte */ 375 DataRun++; 376 377 /* First, extract (byte after byte) run length with the size extracted from header */ 378 for (i = 0; i < DataRunLengthSize; i++) 379 { 380 *DataRunLength += ((ULONG64)*DataRun) << (i * 8); 381 /* Next byte */ 382 DataRun++; 383 } 384 385 /* If offset size is 0, return -1 to show that's sparse run */ 386 if (DataRunOffsetSize == 0) 387 { 388 *DataRunOffset = -1; 389 } 390 /* Otherwise, extract offset */ 391 else 392 { 393 /* Extract (byte after byte) run offset with the size extracted from header */ 394 for (i = 0; i < DataRunOffsetSize - 1; i++) 395 { 396 *DataRunOffset += ((ULONG64)*DataRun) << (i * 8); 397 /* Next byte */ 398 DataRun++; 399 } 400 /* The last byte contains sign so we must process it different way. */ 401 *DataRunOffset = ((LONG64)(CHAR)(*(DataRun++)) << (i * 8)) + *DataRunOffset; 402 } 403 404 /* Return next run */ 405 return DataRun; 406 } 407 408 void PrintAttributeInfo(PNTFS_ATTR_RECORD Attribute, DWORD MaxSize) 409 { 410 BOOL Known = TRUE; 411 WCHAR AttributeName[0xFF + 3]; 412 413 /* First of all, try to get attribute name */ 414 if (Attribute->NameLength != 0 && Attribute->NameOffset < MaxSize && Attribute->NameOffset + Attribute->NameLength < MaxSize) 415 { 416 AttributeName[0] = L' '; 417 CopyMemory(AttributeName + 1, (PUCHAR)((ULONG_PTR)Attribute + Attribute->NameOffset), Attribute->NameLength * sizeof(WCHAR)); 418 AttributeName[Attribute->NameLength + 1] = L' '; 419 AttributeName[Attribute->NameLength + 2] = UNICODE_NULL; 420 } 421 else 422 { 423 AttributeName[0] = L' '; 424 AttributeName[1] = UNICODE_NULL; 425 } 426 427 /* Display attribute type, its name (if any) and whether it's resident */ 428 switch (Attribute->Type) 429 { 430 case AttributeFileName: 431 _tprintf(_T("\t$FILE_NAME%s(%s)\n"), AttributeName, (Attribute->IsNonResident ? _T("nonresident") : _T("resident"))); 432 break; 433 434 case AttributeStandardInformation: 435 _tprintf(_T("\t$STANDARD_INFORMATION%s(%s)\n"), AttributeName, (Attribute->IsNonResident ? _T("nonresident") : _T("resident"))); 436 break; 437 438 case AttributeData: 439 _tprintf(_T("\t$DATA%s(%s)\n"), AttributeName, (Attribute->IsNonResident ? _T("nonresident") : _T("resident"))); 440 break; 441 442 case AttributeBitmap: 443 _tprintf(_T("\t$BITMAP%s(%s)\n"), AttributeName, (Attribute->IsNonResident ? _T("nonresident") : _T("resident"))); 444 break; 445 446 case AttributeIndexRoot: 447 _tprintf(_T("\t$INDEX_ROOT%s(%s)\n"), AttributeName, (Attribute->IsNonResident ? _T("nonresident") : _T("resident"))); 448 break; 449 450 case AttributeIndexAllocation: 451 _tprintf(_T("\t$INDEX_ALLOCATION%s(%s)\n"), AttributeName, (Attribute->IsNonResident ? _T("nonresident") : _T("resident"))); 452 break; 453 454 case AttributeObjectId: 455 _tprintf(_T("\t$OBJECT_ID%s(%s)\n"), AttributeName, (Attribute->IsNonResident ? _T("nonresident") : _T("resident"))); 456 break; 457 458 case AttributeSecurityDescriptor: 459 _tprintf(_T("\t$SECURITY_DESCRIPTOR%s(%s)\n"), AttributeName, (Attribute->IsNonResident ? _T("nonresident") : _T("resident"))); 460 break; 461 462 case AttributeVolumeName: 463 _tprintf(_T("\t$VOLUME_NAME%s(%s)\n"), AttributeName, (Attribute->IsNonResident ? _T("nonresident") : _T("resident"))); 464 break; 465 466 case AttributeVolumeInformation: 467 _tprintf(_T("\t$VOLUME_INFORMATION%s(%s)\n"), AttributeName, (Attribute->IsNonResident ? _T("nonresident") : _T("resident"))); 468 break; 469 470 case AttributeAttributeList: 471 _tprintf(_T("\t$ATTRIBUTE_LIST%s(%s)\n"), AttributeName, (Attribute->IsNonResident ? _T("nonresident") : _T("resident"))); 472 break; 473 474 default: 475 _tprintf(_T("\tUnknown (%x)%s(%s)\n"), Attribute->Type, AttributeName, (Attribute->IsNonResident ? _T("nonresident") : _T("resident"))); 476 Known = FALSE; 477 break; 478 } 479 480 /* If attribute is non resident, display the logical sectors it covers */ 481 if (Known && Attribute->IsNonResident) 482 { 483 PUCHAR Run; 484 ULONGLONG Offset = 0; 485 486 /* Get the runs mapping */ 487 Run = (PUCHAR)((ULONG_PTR)Attribute + Attribute->NonResident.MappingPairsOffset); 488 /* As long as header isn't 0x00, then, there's a run */ 489 while (*Run != 0) 490 { 491 LONGLONG CurrOffset; 492 ULONGLONG CurrLen; 493 494 /* Decode the run, and move to the next one */ 495 Run = DecodeRun(Run, &CurrOffset, &CurrLen); 496 497 /* We don't print sparse runs */ 498 if (CurrOffset != -1) 499 { 500 Offset += CurrOffset; 501 _tprintf(_T("\t\tlogical sectors %I64d-%I64d (0x%I64x-0x%I64x)\n"), Offset, Offset + CurrLen, Offset, Offset + CurrLen); 502 } 503 } 504 } 505 } 506 507 PNAME_CACHE_ENTRY HandleFile(HANDLE VolumeHandle, PNTFS_VOLUME_DATA_BUFFER VolumeInfo, ULONGLONG Id, PNTFS_FILE_RECORD_OUTPUT_BUFFER OutputBuffer, BOOLEAN Silent) 508 { 509 NTFS_FILE_RECORD_INPUT_BUFFER InputBuffer; 510 PFILE_RECORD_HEADER FileRecord; 511 PNTFS_ATTR_RECORD Attribute, AttributesEnd; 512 DWORD LengthReturned; 513 PNAME_CACHE_ENTRY CacheEntry; 514 515 /* Get the file record */ 516 InputBuffer.FileReferenceNumber.QuadPart = Id; 517 if (!DeviceIoControl(VolumeHandle, FSCTL_GET_NTFS_FILE_RECORD, &InputBuffer, sizeof(InputBuffer), 518 OutputBuffer, VolumeInfo->BytesPerFileRecordSegment + sizeof(NTFS_FILE_RECORD_OUTPUT_BUFFER), 519 &LengthReturned, NULL)) 520 { 521 return NULL; 522 } 523 524 /* Don't deal with it if we already browsed it 525 * FSCTL_GET_NTFS_FILE_RECORD always returns previous record if demanded 526 * isn't allocated 527 */ 528 if (OutputBuffer->FileReferenceNumber.QuadPart != Id) 529 { 530 return NULL; 531 } 532 533 /* Sanity check */ 534 FileRecord = (PFILE_RECORD_HEADER)OutputBuffer->FileRecordBuffer; 535 if (FileRecord->Ntfs.Type != NRH_FILE_TYPE) 536 { 537 return NULL; 538 } 539 540 if (!Silent) 541 { 542 /* Print ID */ 543 _tprintf(_T("\nFile %I64d\n"), OutputBuffer->FileReferenceNumber.QuadPart); 544 } 545 546 /* Get attributes list */ 547 Attribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + FileRecord->AttributeOffset); 548 AttributesEnd = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + FileRecord->BytesInUse); 549 550 /* Print the file name */ 551 CacheEntry = PrintPrettyName(VolumeHandle, VolumeInfo, Attribute, AttributesEnd, Id, Silent); 552 553 if (!Silent) 554 { 555 /* And print attributes information for each attribute */ 556 while (Attribute < AttributesEnd && Attribute->Type != AttributeEnd) 557 { 558 PrintAttributeInfo(Attribute, VolumeInfo->BytesPerFileRecordSegment); 559 Attribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)Attribute + Attribute->Length); 560 } 561 } 562 563 return CacheEntry; 564 } 565 566 int 567 __cdecl 568 _tmain(int argc, const TCHAR *argv[]) 569 { 570 TCHAR VolumeName[] = _T("\\\\.\\C:"); 571 HANDLE VolumeHandle; 572 NTFS_VOLUME_DATA_BUFFER VolumeInfo; 573 DWORD LengthReturned; 574 ULONGLONG File; 575 PNTFS_FILE_RECORD_OUTPUT_BUFFER OutputBuffer; 576 TCHAR Letter; 577 PNAME_CACHE_ENTRY CacheEntry; 578 579 if (argc == 1) 580 { 581 PrintUsage(); 582 return 0; 583 } 584 585 /* Setup volume name */ 586 Letter = argv[1][0]; 587 if ((Letter >= 'A' && Letter <= 'Z') || 588 (Letter >= 'a' && Letter <= 'z')) 589 { 590 VolumeName[4] = Letter; 591 } 592 593 /* Open volume */ 594 VolumeHandle = CreateFile(VolumeName, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, 0 ); 595 if (VolumeHandle == INVALID_HANDLE_VALUE) 596 { 597 _ftprintf(stderr, _T("Failed opening the volume '%s' (%lx)\n"), VolumeName, GetLastError()); 598 return 1; 599 } 600 601 /* Get NTFS volume info */ 602 if (!DeviceIoControl(VolumeHandle, FSCTL_GET_NTFS_VOLUME_DATA, NULL, 0, &VolumeInfo, sizeof(VolumeInfo), &LengthReturned, NULL)) 603 { 604 _ftprintf(stderr, _T("Failed requesting volume '%s' data (%lx)\n"), VolumeName, GetLastError()); 605 CloseHandle(VolumeHandle); 606 return 1; 607 } 608 609 /* Validate output */ 610 if (LengthReturned < sizeof(VolumeInfo)) 611 { 612 _ftprintf(stderr, _T("Failed reading volume '%s' data (%lx)\n"), VolumeName, GetLastError()); 613 CloseHandle(VolumeHandle); 614 return 1; 615 } 616 617 /* Allocate a buffer big enough to hold a file record */ 618 OutputBuffer = HeapAlloc(GetProcessHeap(), 0, VolumeInfo.BytesPerFileRecordSegment + sizeof(NTFS_FILE_RECORD_OUTPUT_BUFFER)); 619 if (OutputBuffer == NULL) 620 { 621 _ftprintf(stderr, _T("Failed to allocate %Ix bytes\n"), VolumeInfo.BytesPerFileRecordSegment + sizeof(NTFS_FILE_RECORD_OUTPUT_BUFFER)); 622 CloseHandle(VolumeHandle); 623 return 1; 624 } 625 626 /* Forever loop, extract all the files! */ 627 for (File = 0;; ++File) 628 { 629 HandleFile(VolumeHandle, &VolumeInfo, File, OutputBuffer, FALSE); 630 } 631 632 /* Free memory! */ 633 while (CacheHead != NULL) 634 { 635 CacheEntry = CacheHead; 636 CacheHead = CacheEntry->Next; 637 HeapFree(GetProcessHeap(), 0, CacheEntry); 638 } 639 640 /* Cleanup and exit */ 641 HeapFree(GetProcessHeap(), 0, OutputBuffer); 642 CloseHandle(VolumeHandle); 643 return 0; 644 } 645