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
PrintUsage(void)162 void PrintUsage(void)
163 {
164 /* FIXME */
165 }
166
FindInCache(ULONGLONG MftId)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
AddToCache(PWSTR Name,DWORD Length,ULONGLONG MftId)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
PrintPrettyName(HANDLE VolumeHandle,PNTFS_VOLUME_DATA_BUFFER VolumeInfo,PNTFS_ATTR_RECORD Attributes,PNTFS_ATTR_RECORD AttributesEnd,ULONGLONG MftId,BOOLEAN Silent)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
DecodeRun(PUCHAR DataRun,LONGLONG * DataRunOffset,ULONGLONG * DataRunLength)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
PrintAttributeInfo(PNTFS_ATTR_RECORD Attribute,DWORD MaxSize)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
HandleFile(HANDLE VolumeHandle,PNTFS_VOLUME_DATA_BUFFER VolumeInfo,ULONGLONG Id,PNTFS_FILE_RECORD_OUTPUT_BUFFER OutputBuffer,BOOLEAN Silent)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
_tmain(int argc,const TCHAR * argv[])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