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