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>
19 } NTFS_RECORD_HEADER
, *PNTFS_RECORD_HEADER
;
21 #define NRH_FILE_TYPE 0x454C4946
25 AttributeStandardInformation
= 0x10,
26 AttributeAttributeList
= 0x20,
27 AttributeFileName
= 0x30,
28 AttributeObjectId
= 0x40,
29 AttributeSecurityDescriptor
= 0x50,
30 AttributeVolumeName
= 0x60,
31 AttributeVolumeInformation
= 0x70,
33 AttributeIndexRoot
= 0x90,
34 AttributeIndexAllocation
= 0xA0,
35 AttributeBitmap
= 0xB0,
36 AttributeReparsePoint
= 0xC0,
37 AttributeEAInformation
= 0xD0,
39 AttributePropertySet
= 0xF0,
40 AttributeLoggedUtilityStream
= 0x100,
41 AttributeEnd
= 0xFFFFFFFF
42 } ATTRIBUTE_TYPE
, *PATTRIBUTE_TYPE
;
44 typedef struct _FILE_RECORD_HEADER
46 NTFS_RECORD_HEADER Ntfs
;
47 USHORT SequenceNumber
;
49 USHORT AttributeOffset
;
53 ULONGLONG BaseFileRecord
;
54 USHORT NextAttributeNumber
;
55 } FILE_RECORD_HEADER
, *PFILE_RECORD_HEADER
;
79 USHORT MappingPairsOffset
;
80 USHORT CompressionUnit
;
82 LONGLONG AllocatedSize
;
84 LONGLONG InitializedSize
;
85 LONGLONG CompressedSize
;
88 } NTFS_ATTR_RECORD
, *PNTFS_ATTR_RECORD
;
92 ULONGLONG DirectoryFileReferenceNumber
;
93 ULONGLONG CreationTime
;
95 ULONGLONG LastWriteTime
;
96 ULONGLONG LastAccessTime
;
97 ULONGLONG AllocatedSize
;
105 USHORT AlignmentOrReserved
;
112 } FILENAME_ATTRIBUTE
, *PFILENAME_ATTRIBUTE
;
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
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
132 PWSTR KnownEntries
[NTFS_FILE_EXTEND
+ 1] =
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)")
148 #define NTFS_MFT_MASK 0x0000FFFFFFFFFFFFULL
150 #define NTFS_FILE_TYPE_DIRECTORY 0x10000000
152 typedef struct _NAME_CACHE_ENTRY
154 struct _NAME_CACHE_ENTRY
* Next
;
158 } NAME_CACHE_ENTRY
, *PNAME_CACHE_ENTRY
;
160 PNAME_CACHE_ENTRY CacheHead
= NULL
;
162 void PrintUsage(void)
167 void AddToCache(PWSTR Name
, DWORD Length
, ULONGLONG MftId
)
169 PNAME_CACHE_ENTRY CacheEntry
;
171 /* Allocate an entry big enough to store name and cache info */
172 CacheEntry
= HeapAlloc(GetProcessHeap(), 0, sizeof(NAME_CACHE_ENTRY
) + Length
);
173 if (CacheEntry
== NULL
)
178 /* Insert in head (likely more perf) */
179 CacheEntry
->Next
= CacheHead
;
180 CacheHead
= CacheEntry
;
181 /* Set up entry with full path */
182 CacheEntry
->MftId
= MftId
;
183 CacheEntry
->NameLen
= Length
;
184 CopyMemory(CacheEntry
->Name
, Name
, Length
);
187 void PrintPrettyName(PNTFS_ATTR_RECORD Attributes
, PNTFS_ATTR_RECORD AttributesEnd
, ULONGLONG MftId
)
189 BOOLEAN FirstRun
, Found
;
190 PNTFS_ATTR_RECORD Attribute
;
195 /* Setup name for "standard" files */
196 if (MftId
<= NTFS_FILE_EXTEND
)
198 _tprintf(_T("%s\n"), KnownEntries
[MftId
]);
200 /* $Extend can contain entries, add it in cache */
201 if (MftId
== NTFS_FILE_EXTEND
)
203 AddToCache(L
"\\$Extend", sizeof(L
"\\$Extend") - sizeof(UNICODE_NULL
), NTFS_FILE_EXTEND
);
209 /* We'll first try to use the Win32 name
210 * If we fail finding it, we'll loop again for any other name
213 /* Loop all the attributes */
214 Attribute
= Attributes
;
215 while (Attribute
< AttributesEnd
&& Attribute
->Type
!= AttributeEnd
)
217 WCHAR Display
[MAX_PATH
];
218 PFILENAME_ATTRIBUTE Name
;
222 /* Move to the next arg if:
224 * - Not resident (should never happen!)
226 if (Attribute
->Type
!= AttributeFileName
|| Attribute
->IsNonResident
)
228 Attribute
= (PNTFS_ATTR_RECORD
)((ULONG_PTR
)Attribute
+ Attribute
->Length
);
232 /* Get the attribute data */
233 Name
= (PFILENAME_ATTRIBUTE
)((ULONG_PTR
)Attribute
+ Attribute
->Resident
.ValueOffset
);
234 /* If not Win32, only accept if it wasn't the first run */
235 if ((Name
->NameType
== NTFS_FILE_NAME_POSIX
|| Name
->NameType
== NTFS_FILE_NAME_DOS
) && FirstRun
)
237 Attribute
= (PNTFS_ATTR_RECORD
)((ULONG_PTR
)Attribute
+ Attribute
->Length
);
241 /* We accepted that name, get the parent ID to setup name */
242 ParentId
= Name
->DirectoryFileReferenceNumber
& NTFS_MFT_MASK
;
243 /* If root, easy, just print \ */
244 if (ParentId
== NTFS_FILE_ROOT
)
247 CopyMemory(&Display
[1], Name
->Name
, Name
->NameLength
* sizeof(WCHAR
));
248 Length
= Name
->NameLength
+ 1;
249 Display
[Length
] = UNICODE_NULL
;
254 PNAME_CACHE_ENTRY CacheEntry
;
256 /* Did we already cache the name? */
257 for (CacheEntry
= CacheHead
; CacheEntry
!= NULL
; CacheEntry
= CacheEntry
->Next
)
259 if (ParentId
== CacheEntry
->MftId
)
265 /* Nothing written yet */
268 if (CacheEntry
!= NULL
)
270 /* Set up name. The cache entry contains full path */
271 Length
= CacheEntry
->NameLen
/ sizeof(WCHAR
);
272 CopyMemory(Display
, CacheEntry
->Name
, CacheEntry
->NameLen
);
273 Display
[Length
] = L
'\\';
278 /* FIXME: Do something, like trying to read parent... */
279 _tprintf(_T("Parent: %I64d\n"), ParentId
);
283 CopyMemory(Display
+ Length
, Name
->Name
, Name
->NameLength
* sizeof(WCHAR
));
284 Length
+= Name
->NameLength
;
285 Display
[Length
] = UNICODE_NULL
;
288 /* Display the name */
289 _tprintf(_T("%s\n"), Display
);
291 /* If that's a directory, put it in the cache */
292 if (Name
->FileAttributes
& NTFS_FILE_TYPE_DIRECTORY
)
294 AddToCache(Display
, Length
* sizeof(WCHAR
), MftId
);
304 /* If was first run (Win32 search), retry with other names */
311 /* If we couldn't find a name, print unknown */
314 _tprintf(_T("(unknown/unnamed)\n"));
318 PUCHAR
DecodeRun(PUCHAR DataRun
, LONGLONG
*DataRunOffset
, ULONGLONG
*DataRunLength
)
320 UCHAR DataRunOffsetSize
;
321 UCHAR DataRunLengthSize
;
324 /* Get the offset size (in bytes) of the run */
325 DataRunOffsetSize
= (*DataRun
>> 4) & 0xF;
326 /* Get the length size (in bytes) of the run */
327 DataRunLengthSize
= *DataRun
& 0xF;
329 /* Initialize values */
333 /* Move to next byte */
336 /* First, extract (byte after byte) run length with the size extracted from header */
337 for (i
= 0; i
< DataRunLengthSize
; i
++)
339 *DataRunLength
+= ((ULONG64
)*DataRun
) << (i
* 8);
344 /* If offset size is 0, return -1 to show that's sparse run */
345 if (DataRunOffsetSize
== 0)
349 /* Otherwise, extract offset */
352 /* Extract (byte after byte) run offset with the size extracted from header */
353 for (i
= 0; i
< DataRunOffsetSize
- 1; i
++)
355 *DataRunOffset
+= ((ULONG64
)*DataRun
) << (i
* 8);
359 /* The last byte contains sign so we must process it different way. */
360 *DataRunOffset
= ((LONG64
)(CHAR
)(*(DataRun
++)) << (i
* 8)) + *DataRunOffset
;
363 /* Return next run */
367 void PrintAttributeInfo(PNTFS_ATTR_RECORD Attribute
, DWORD MaxSize
)
370 WCHAR AttributeName
[0xFF + 3];
372 /* First of all, try to get attribute name */
373 if (Attribute
->NameLength
!= 0 && Attribute
->NameOffset
< MaxSize
&& Attribute
->NameOffset
+ Attribute
->NameLength
< MaxSize
)
375 AttributeName
[0] = L
' ';
376 CopyMemory(AttributeName
+ 1, (PUCHAR
)((ULONG_PTR
)Attribute
+ Attribute
->NameOffset
), Attribute
->NameLength
* sizeof(WCHAR
));
377 AttributeName
[Attribute
->NameLength
+ 1] = L
' ';
378 AttributeName
[Attribute
->NameLength
+ 2] = UNICODE_NULL
;
382 AttributeName
[0] = L
' ';
383 AttributeName
[1] = UNICODE_NULL
;
386 /* Display attribute type, its name (if any) and whether it's resident */
387 switch (Attribute
->Type
)
389 case AttributeFileName
:
390 _tprintf(_T("\t$FILE_NAME%s(%s)\n"), AttributeName
, (Attribute
->IsNonResident
? _T("nonresident") : _T("resident")));
393 case AttributeStandardInformation
:
394 _tprintf(_T("\t$STANDARD_INFORMATION%s(%s)\n"), AttributeName
, (Attribute
->IsNonResident
? _T("nonresident") : _T("resident")));
398 _tprintf(_T("\t$DATA%s(%s)\n"), AttributeName
, (Attribute
->IsNonResident
? _T("nonresident") : _T("resident")));
401 case AttributeBitmap
:
402 _tprintf(_T("\t$BITMAP%s(%s)\n"), AttributeName
, (Attribute
->IsNonResident
? _T("nonresident") : _T("resident")));
405 case AttributeIndexRoot
:
406 _tprintf(_T("\t$INDEX_ROOT%s(%s)\n"), AttributeName
, (Attribute
->IsNonResident
? _T("nonresident") : _T("resident")));
409 case AttributeIndexAllocation
:
410 _tprintf(_T("\t$INDEX_ALLOCATION%s(%s)\n"), AttributeName
, (Attribute
->IsNonResident
? _T("nonresident") : _T("resident")));
413 case AttributeObjectId
:
414 _tprintf(_T("\t$OBJECT_ID%s(%s)\n"), AttributeName
, (Attribute
->IsNonResident
? _T("nonresident") : _T("resident")));
417 case AttributeSecurityDescriptor
:
418 _tprintf(_T("\t$SECURITY_DESCRIPTOR%s(%s)\n"), AttributeName
, (Attribute
->IsNonResident
? _T("nonresident") : _T("resident")));
421 case AttributeVolumeName
:
422 _tprintf(_T("\t$VOLUME_NAME%s(%s)\n"), AttributeName
, (Attribute
->IsNonResident
? _T("nonresident") : _T("resident")));
425 case AttributeVolumeInformation
:
426 _tprintf(_T("\t$VOLUME_INFORMATION%s(%s)\n"), AttributeName
, (Attribute
->IsNonResident
? _T("nonresident") : _T("resident")));
429 case AttributeAttributeList
:
430 _tprintf(_T("\t$ATTRIBUTE_LIST%s(%s)\n"), AttributeName
, (Attribute
->IsNonResident
? _T("nonresident") : _T("resident")));
434 _tprintf(_T("\tUnknown (%x)%s(%s)\n"), Attribute
->Type
, AttributeName
, (Attribute
->IsNonResident
? _T("nonresident") : _T("resident")));
439 /* If attribute is non resident, display the logical sectors it covers */
440 if (Known
&& Attribute
->IsNonResident
)
443 ULONGLONG Offset
= 0;
445 /* Get the runs mapping */
446 Run
= (PUCHAR
)((ULONG_PTR
)Attribute
+ Attribute
->NonResident
.MappingPairsOffset
);
447 /* As long as header isn't 0x00, then, there's a run */
453 /* Decode the run, and move to the next one */
454 Run
= DecodeRun(Run
, &CurrOffset
, &CurrLen
);
456 /* We don't print sparse runs */
457 if (CurrOffset
!= -1)
459 Offset
+= CurrOffset
;
460 _tprintf(_T("\t\tlogical sectors %I64d-%I64d (0x%I64x-0x%I64x)\n"), Offset
, Offset
+ CurrLen
, Offset
, Offset
+ CurrLen
);
468 _tmain(int argc
, const TCHAR
*argv
[])
470 TCHAR VolumeName
[] = _T("\\\\.\\C:");
472 NTFS_VOLUME_DATA_BUFFER VolumeInfo
;
473 DWORD LengthReturned
;
475 PNTFS_FILE_RECORD_OUTPUT_BUFFER OutputBuffer
;
477 PNAME_CACHE_ENTRY CacheEntry
;
485 /* Setup volume name */
487 if ((Letter
>= 'A' && Letter
<= 'Z') ||
488 (Letter
>= 'a' && Letter
<= 'z'))
490 VolumeName
[4] = Letter
;
494 VolumeHandle
= CreateFile(VolumeName
, GENERIC_READ
, FILE_SHARE_READ
| FILE_SHARE_WRITE
, NULL
, OPEN_EXISTING
, 0, 0 );
495 if (VolumeHandle
== INVALID_HANDLE_VALUE
)
497 _ftprintf(stderr
, _T("Failed opening the volume '%s' (%lx)\n"), VolumeName
, GetLastError());
501 /* Get NTFS volume info */
502 if (!DeviceIoControl(VolumeHandle
, FSCTL_GET_NTFS_VOLUME_DATA
, NULL
, 0, &VolumeInfo
, sizeof(VolumeInfo
), &LengthReturned
, NULL
))
504 _ftprintf(stderr
, _T("Failed requesting volume '%s' data (%lx)\n"), VolumeName
, GetLastError());
505 CloseHandle(VolumeHandle
);
509 /* Validate output */
510 if (LengthReturned
< sizeof(VolumeInfo
))
512 _ftprintf(stderr
, _T("Failed reading volume '%s' data (%lx)\n"), VolumeName
, GetLastError());
513 CloseHandle(VolumeHandle
);
517 /* Allocate a buffer big enough to hold a file record */
518 OutputBuffer
= HeapAlloc(GetProcessHeap(), 0, VolumeInfo
.BytesPerFileRecordSegment
+ sizeof(NTFS_FILE_RECORD_OUTPUT_BUFFER
));
519 if (OutputBuffer
== NULL
)
521 _ftprintf(stderr
, _T("Failed to allocate %x bytes\n"), VolumeInfo
.BytesPerFileRecordSegment
+ sizeof(NTFS_FILE_RECORD_OUTPUT_BUFFER
));
522 CloseHandle(VolumeHandle
);
526 /* Forever loop, extract all the files! */
527 for (File
= 0;; ++File
)
529 NTFS_FILE_RECORD_INPUT_BUFFER InputBuffer
;
530 PFILE_RECORD_HEADER FileRecord
;
531 PNTFS_ATTR_RECORD Attribute
, AttributesEnd
;
533 /* Get the file record */
534 InputBuffer
.FileReferenceNumber
.QuadPart
= File
;
535 if (!DeviceIoControl(VolumeHandle
, FSCTL_GET_NTFS_FILE_RECORD
, &InputBuffer
, sizeof(InputBuffer
),
536 OutputBuffer
, VolumeInfo
.BytesPerFileRecordSegment
+ sizeof(NTFS_FILE_RECORD_OUTPUT_BUFFER
),
537 &LengthReturned
, NULL
))
542 /* Don't deal with it if we already browsed it
543 * FSCTL_GET_NTFS_FILE_RECORD always returns previous record if demanded
546 if (OutputBuffer
->FileReferenceNumber
.QuadPart
!= File
)
552 FileRecord
= (PFILE_RECORD_HEADER
)OutputBuffer
->FileRecordBuffer
;
553 if (FileRecord
->Ntfs
.Type
!= NRH_FILE_TYPE
)
559 _tprintf(_T("\nFile %I64d\n"), OutputBuffer
->FileReferenceNumber
.QuadPart
);
561 /* Get attributes list */
562 Attribute
= (PNTFS_ATTR_RECORD
)((ULONG_PTR
)FileRecord
+ FileRecord
->AttributeOffset
);
563 AttributesEnd
= (PNTFS_ATTR_RECORD
)((ULONG_PTR
)FileRecord
+ FileRecord
->BytesInUse
);
565 /* Print the file name */
566 PrintPrettyName(Attribute
, AttributesEnd
, File
);
568 /* And print attributes information for each attribute */
569 while (Attribute
< AttributesEnd
&& Attribute
->Type
!= AttributeEnd
)
571 PrintAttributeInfo(Attribute
, VolumeInfo
.BytesPerFileRecordSegment
);
572 Attribute
= (PNTFS_ATTR_RECORD
)((ULONG_PTR
)Attribute
+ Attribute
->Length
);
577 while (CacheHead
!= NULL
)
579 CacheEntry
= CacheHead
;
580 CacheHead
= CacheEntry
->Next
;
581 HeapFree(GetProcessHeap(), 0, CacheEntry
);
584 /* Cleanup and exit */
585 HeapFree(GetProcessHeap(), 0, OutputBuffer
);
586 CloseHandle(VolumeHandle
);