2 * FreeLoader NTFS support
3 * Copyright (C) 2004 Filip Navara <xnavara@volny.cz>
4 * Copyright (C) 2009-2010 Hervé Poussineau
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23 * - No support for compressed files.
24 * - May crash on corrupted filesystem.
31 #define TAG_NTFS_CONTEXT 'CftN'
32 #define TAG_NTFS_LIST 'LftN'
33 #define TAG_NTFS_MFT 'MftN'
34 #define TAG_NTFS_INDEX_REC 'IftN'
35 #define TAG_NTFS_BITMAP 'BftN'
36 #define TAG_NTFS_FILE 'FftN'
37 #define TAG_NTFS_VOLUME 'VftN'
38 #define TAG_NTFS_DATA 'DftN'
40 DBG_DEFAULT_CHANNEL(FILESYSTEM
);
42 typedef struct _NTFS_VOLUME_INFO
44 NTFS_BOOTSECTOR BootSector
;
47 ULONG IndexRecordSize
;
48 PNTFS_MFT_RECORD MasterFileTable
;
49 /* FIXME: MFTContext is never freed. */
50 PNTFS_ATTR_CONTEXT MFTContext
;
52 PUCHAR TemporarySector
;
55 PNTFS_VOLUME_INFO NtfsVolumes
[MAX_FDS
];
57 static ULONGLONG
NtfsGetAttributeSize(PNTFS_ATTR_RECORD AttrRecord
)
59 if (AttrRecord
->IsNonResident
)
60 return AttrRecord
->NonResident
.DataSize
;
62 return AttrRecord
->Resident
.ValueLength
;
65 static PUCHAR
NtfsDecodeRun(PUCHAR DataRun
, LONGLONG
*DataRunOffset
, ULONGLONG
*DataRunLength
)
67 UCHAR DataRunOffsetSize
;
68 UCHAR DataRunLengthSize
;
71 DataRunOffsetSize
= (*DataRun
>> 4) & 0xF;
72 DataRunLengthSize
= *DataRun
& 0xF;
76 for (i
= 0; i
< DataRunLengthSize
; i
++)
78 *DataRunLength
+= ((ULONG64
)*DataRun
) << (i
* 8);
82 /* NTFS 3+ sparse files */
83 if (DataRunOffsetSize
== 0)
89 for (i
= 0; i
< DataRunOffsetSize
- 1; i
++)
91 *DataRunOffset
+= ((ULONG64
)*DataRun
) << (i
* 8);
94 /* The last byte contains sign so we must process it different way. */
95 *DataRunOffset
= ((LONG64
)(CHAR
)(*(DataRun
++)) << (i
* 8)) + *DataRunOffset
;
98 TRACE("DataRunOffsetSize: %x\n", DataRunOffsetSize
);
99 TRACE("DataRunLengthSize: %x\n", DataRunLengthSize
);
100 TRACE("DataRunOffset: %x\n", *DataRunOffset
);
101 TRACE("DataRunLength: %x\n", *DataRunLength
);
106 static PNTFS_ATTR_CONTEXT
NtfsPrepareAttributeContext(PNTFS_ATTR_RECORD AttrRecord
)
108 PNTFS_ATTR_CONTEXT Context
;
110 Context
= FrLdrTempAlloc(FIELD_OFFSET(NTFS_ATTR_CONTEXT
, Record
) + AttrRecord
->Length
,
112 RtlCopyMemory(&Context
->Record
, AttrRecord
, AttrRecord
->Length
);
113 if (AttrRecord
->IsNonResident
)
115 LONGLONG DataRunOffset
;
116 ULONGLONG DataRunLength
;
118 Context
->CacheRun
= (PUCHAR
)&Context
->Record
+ Context
->Record
.NonResident
.MappingPairsOffset
;
119 Context
->CacheRunOffset
= 0;
120 Context
->CacheRun
= NtfsDecodeRun(Context
->CacheRun
, &DataRunOffset
, &DataRunLength
);
121 Context
->CacheRunLength
= DataRunLength
;
122 if (DataRunOffset
!= -1)
125 Context
->CacheRunStartLCN
=
126 Context
->CacheRunLastLCN
= DataRunOffset
;
131 Context
->CacheRunStartLCN
= -1;
132 Context
->CacheRunLastLCN
= 0;
134 Context
->CacheRunCurrentOffset
= 0;
140 static VOID
NtfsReleaseAttributeContext(PNTFS_ATTR_CONTEXT Context
)
142 FrLdrTempFree(Context
, TAG_NTFS_CONTEXT
);
145 static BOOLEAN
NtfsDiskRead(PNTFS_VOLUME_INFO Volume
, ULONGLONG Offset
, ULONGLONG Length
, PCHAR Buffer
)
147 LARGE_INTEGER Position
;
152 TRACE("NtfsDiskRead - Offset: %I64d Length: %I64d\n", Offset
, Length
);
155 // I. Read partial first sector if needed
157 if (Offset
% Volume
->BootSector
.BytesPerSector
)
159 Position
.QuadPart
= Offset
& ~(Volume
->BootSector
.BytesPerSector
- 1);
160 Status
= ArcSeek(Volume
->DeviceId
, &Position
, SeekAbsolute
);
161 if (Status
!= ESUCCESS
)
163 Status
= ArcRead(Volume
->DeviceId
, Volume
->TemporarySector
, Volume
->BootSector
.BytesPerSector
, &Count
);
164 if (Status
!= ESUCCESS
|| Count
!= Volume
->BootSector
.BytesPerSector
)
166 ReadLength
= (USHORT
)min(Length
, Volume
->BootSector
.BytesPerSector
- (Offset
% Volume
->BootSector
.BytesPerSector
));
169 // Copy interesting data
171 RtlCopyMemory(Buffer
,
172 &Volume
->TemporarySector
[Offset
% Volume
->BootSector
.BytesPerSector
],
176 // Move to unfilled buffer part
178 Buffer
+= ReadLength
;
179 Length
-= ReadLength
;
180 Offset
+= ReadLength
;
184 // II. Read all complete blocks
186 if (Length
>= Volume
->BootSector
.BytesPerSector
)
188 Position
.QuadPart
= Offset
;
189 Status
= ArcSeek(Volume
->DeviceId
, &Position
, SeekAbsolute
);
190 if (Status
!= ESUCCESS
)
192 ReadLength
= Length
& ~(Volume
->BootSector
.BytesPerSector
- 1);
193 Status
= ArcRead(Volume
->DeviceId
, Buffer
, ReadLength
, &Count
);
194 if (Status
!= ESUCCESS
|| Count
!= ReadLength
)
198 // Move to unfilled buffer part
200 Buffer
+= ReadLength
;
201 Length
-= ReadLength
;
202 Offset
+= ReadLength
;
206 // III. Read the rest of data
210 Position
.QuadPart
= Offset
;
211 Status
= ArcSeek(Volume
->DeviceId
, &Position
, SeekAbsolute
);
212 if (Status
!= ESUCCESS
)
214 Status
= ArcRead(Volume
->DeviceId
, Buffer
, (ULONG
)Length
, &Count
);
215 if (Status
!= ESUCCESS
|| Count
!= Length
)
222 static ULONG
NtfsReadAttribute(PNTFS_VOLUME_INFO Volume
, PNTFS_ATTR_CONTEXT Context
, ULONGLONG Offset
, PCHAR Buffer
, ULONG Length
)
226 LONGLONG DataRunOffset
;
227 ULONGLONG DataRunLength
;
228 LONGLONG DataRunStartLCN
;
229 ULONGLONG CurrentOffset
;
233 if (!Context
->Record
.IsNonResident
)
235 if (Offset
> Context
->Record
.Resident
.ValueLength
)
237 if (Offset
+ Length
> Context
->Record
.Resident
.ValueLength
)
238 Length
= (ULONG
)(Context
->Record
.Resident
.ValueLength
- Offset
);
239 RtlCopyMemory(Buffer
, (PCHAR
)&Context
->Record
+ Context
->Record
.Resident
.ValueOffset
+ Offset
, Length
);
244 * Non-resident attribute
248 * I. Find the corresponding start data run.
253 // FIXME: Cache seems to be non-working. Disable it for now
254 //if(Context->CacheRunOffset <= Offset && Offset < Context->CacheRunOffset + Context->CacheRunLength * Volume->ClusterSize)
257 DataRun
= Context
->CacheRun
;
258 LastLCN
= Context
->CacheRunLastLCN
;
259 DataRunStartLCN
= Context
->CacheRunStartLCN
;
260 DataRunLength
= Context
->CacheRunLength
;
261 CurrentOffset
= Context
->CacheRunCurrentOffset
;
266 DataRun
= (PUCHAR
)&Context
->Record
+ Context
->Record
.NonResident
.MappingPairsOffset
;
271 DataRun
= NtfsDecodeRun(DataRun
, &DataRunOffset
, &DataRunLength
);
272 if (DataRunOffset
!= -1)
274 /* Normal data run. */
275 DataRunStartLCN
= LastLCN
+ DataRunOffset
;
276 LastLCN
= DataRunStartLCN
;
280 /* Sparse data run. */
281 DataRunStartLCN
= -1;
284 if (Offset
>= CurrentOffset
&&
285 Offset
< CurrentOffset
+ (DataRunLength
* Volume
->ClusterSize
))
295 CurrentOffset
+= DataRunLength
* Volume
->ClusterSize
;
300 * II. Go through the run list and read the data
303 ReadLength
= (ULONG
)min(DataRunLength
* Volume
->ClusterSize
- (Offset
- CurrentOffset
), Length
);
304 if (DataRunStartLCN
== -1)
305 RtlZeroMemory(Buffer
, ReadLength
);
306 if (NtfsDiskRead(Volume
, DataRunStartLCN
* Volume
->ClusterSize
+ Offset
- CurrentOffset
, ReadLength
, Buffer
))
308 Length
-= ReadLength
;
309 Buffer
+= ReadLength
;
310 AlreadyRead
+= ReadLength
;
312 if (ReadLength
== DataRunLength
* Volume
->ClusterSize
- (Offset
- CurrentOffset
))
314 CurrentOffset
+= DataRunLength
* Volume
->ClusterSize
;
315 DataRun
= NtfsDecodeRun(DataRun
, &DataRunOffset
, &DataRunLength
);
316 if (DataRunLength
!= (ULONGLONG
)-1)
318 DataRunStartLCN
= LastLCN
+ DataRunOffset
;
319 LastLCN
= DataRunStartLCN
;
322 DataRunStartLCN
= -1;
330 ReadLength
= (ULONG
)min(DataRunLength
* Volume
->ClusterSize
, Length
);
331 if (DataRunStartLCN
== -1)
332 RtlZeroMemory(Buffer
, ReadLength
);
333 else if (!NtfsDiskRead(Volume
, DataRunStartLCN
* Volume
->ClusterSize
, ReadLength
, Buffer
))
336 Length
-= ReadLength
;
337 Buffer
+= ReadLength
;
338 AlreadyRead
+= ReadLength
;
340 /* We finished this request, but there still data in this data run. */
341 if (Length
== 0 && ReadLength
!= DataRunLength
* Volume
->ClusterSize
)
345 * Go to next run in the list.
350 CurrentOffset
+= DataRunLength
* Volume
->ClusterSize
;
351 DataRun
= NtfsDecodeRun(DataRun
, &DataRunOffset
, &DataRunLength
);
352 if (DataRunOffset
!= -1)
354 /* Normal data run. */
355 DataRunStartLCN
= LastLCN
+ DataRunOffset
;
356 LastLCN
= DataRunStartLCN
;
360 /* Sparse data run. */
361 DataRunStartLCN
= -1;
367 Context
->CacheRun
= DataRun
;
368 Context
->CacheRunOffset
= Offset
+ AlreadyRead
;
369 Context
->CacheRunStartLCN
= DataRunStartLCN
;
370 Context
->CacheRunLength
= DataRunLength
;
371 Context
->CacheRunLastLCN
= LastLCN
;
372 Context
->CacheRunCurrentOffset
= CurrentOffset
;
377 static PNTFS_ATTR_CONTEXT
NtfsFindAttributeHelper(PNTFS_VOLUME_INFO Volume
, PNTFS_ATTR_RECORD AttrRecord
, PNTFS_ATTR_RECORD AttrRecordEnd
, ULONG Type
, const WCHAR
*Name
, ULONG NameLength
)
379 while (AttrRecord
< AttrRecordEnd
)
381 if (AttrRecord
->Type
== NTFS_ATTR_TYPE_END
)
384 if (AttrRecord
->Type
== NTFS_ATTR_TYPE_ATTRIBUTE_LIST
)
386 PNTFS_ATTR_CONTEXT Context
;
387 PNTFS_ATTR_CONTEXT ListContext
;
390 PNTFS_ATTR_RECORD ListAttrRecord
;
391 PNTFS_ATTR_RECORD ListAttrRecordEnd
;
393 ListContext
= NtfsPrepareAttributeContext(AttrRecord
);
395 ListSize
= NtfsGetAttributeSize(&ListContext
->Record
);
396 if(ListSize
<= 0xFFFFFFFF)
397 ListBuffer
= FrLdrTempAlloc((ULONG
)ListSize
, TAG_NTFS_LIST
);
403 TRACE("Failed to allocate memory: %x\n", (ULONG
)ListSize
);
407 ListAttrRecord
= (PNTFS_ATTR_RECORD
)ListBuffer
;
408 ListAttrRecordEnd
= (PNTFS_ATTR_RECORD
)((PCHAR
)ListBuffer
+ ListSize
);
410 if (NtfsReadAttribute(Volume
, ListContext
, 0, ListBuffer
, (ULONG
)ListSize
) == ListSize
)
412 Context
= NtfsFindAttributeHelper(Volume
, ListAttrRecord
, ListAttrRecordEnd
,
413 Type
, Name
, NameLength
);
415 NtfsReleaseAttributeContext(ListContext
);
416 FrLdrTempFree(ListBuffer
, TAG_NTFS_LIST
);
423 if (AttrRecord
->Type
== Type
)
425 if (AttrRecord
->NameLength
== NameLength
)
429 AttrName
= (PWCHAR
)((PCHAR
)AttrRecord
+ AttrRecord
->NameOffset
);
430 if (RtlEqualMemory(AttrName
, Name
, NameLength
<< 1))
432 /* Found it, fill up the context and return. */
433 return NtfsPrepareAttributeContext(AttrRecord
);
438 if (AttrRecord
->Length
== 0)
440 AttrRecord
= (PNTFS_ATTR_RECORD
)((PCHAR
)AttrRecord
+ AttrRecord
->Length
);
446 static PNTFS_ATTR_CONTEXT
NtfsFindAttribute(PNTFS_VOLUME_INFO Volume
, PNTFS_MFT_RECORD MftRecord
, ULONG Type
, const WCHAR
*Name
)
448 PNTFS_ATTR_RECORD AttrRecord
;
449 PNTFS_ATTR_RECORD AttrRecordEnd
;
452 AttrRecord
= (PNTFS_ATTR_RECORD
)((PCHAR
)MftRecord
+ MftRecord
->AttributesOffset
);
453 AttrRecordEnd
= (PNTFS_ATTR_RECORD
)((PCHAR
)MftRecord
+ Volume
->MftRecordSize
);
454 for (NameLength
= 0; Name
[NameLength
] != 0; NameLength
++)
457 return NtfsFindAttributeHelper(Volume
, AttrRecord
, AttrRecordEnd
, Type
, Name
, NameLength
);
460 static BOOLEAN
NtfsFixupRecord(PNTFS_VOLUME_INFO Volume
, PNTFS_RECORD Record
)
467 USA
= (USHORT
*)((PCHAR
)Record
+ Record
->USAOffset
);
468 USANumber
= *(USA
++);
469 USACount
= Record
->USACount
- 1; /* Exclude the USA Number. */
470 Block
= (USHORT
*)((PCHAR
)Record
+ Volume
->BootSector
.BytesPerSector
- 2);
474 if (*Block
!= USANumber
)
477 Block
= (USHORT
*)((PCHAR
)Block
+ Volume
->BootSector
.BytesPerSector
);
484 static BOOLEAN
NtfsReadMftRecord(PNTFS_VOLUME_INFO Volume
, ULONGLONG MFTIndex
, PNTFS_MFT_RECORD Buffer
)
488 BytesRead
= NtfsReadAttribute(Volume
, Volume
->MFTContext
, MFTIndex
* Volume
->MftRecordSize
, (PCHAR
)Buffer
, Volume
->MftRecordSize
);
489 if (BytesRead
!= Volume
->MftRecordSize
)
492 /* Apply update sequence array fixups. */
493 return NtfsFixupRecord(Volume
, (PNTFS_RECORD
)Buffer
);
497 VOID
NtfsPrintFile(PNTFS_INDEX_ENTRY IndexEntry
)
500 UCHAR FileNameLength
;
501 CHAR AnsiFileName
[256];
504 FileName
= IndexEntry
->FileName
.FileName
;
505 FileNameLength
= min(IndexEntry
->FileName
.FileNameLength
, 255);
507 for (i
= 0; i
< FileNameLength
; i
++)
508 AnsiFileName
[i
] = (CHAR
)FileName
[i
];
511 TRACE("- %s (%x)\n", AnsiFileName
, IndexEntry
->Data
.Directory
.IndexedFile
);
515 static BOOLEAN
NtfsCompareFileName(PCHAR FileName
, PNTFS_INDEX_ENTRY IndexEntry
)
517 PWCHAR EntryFileName
;
518 UCHAR EntryFileNameLength
;
521 EntryFileName
= IndexEntry
->FileName
.FileName
;
522 EntryFileNameLength
= IndexEntry
->FileName
.FileNameLength
;
525 NtfsPrintFile(IndexEntry
);
528 if (strlen(FileName
) != EntryFileNameLength
)
531 /* Do case-sensitive compares for Posix file names. */
532 if (IndexEntry
->FileName
.FileNameType
== NTFS_FILE_NAME_POSIX
)
534 for (i
= 0; i
< EntryFileNameLength
; i
++)
535 if (EntryFileName
[i
] != FileName
[i
])
540 for (i
= 0; i
< EntryFileNameLength
; i
++)
541 if (tolower(EntryFileName
[i
]) != tolower(FileName
[i
]))
548 static BOOLEAN
NtfsFindMftRecord(PNTFS_VOLUME_INFO Volume
, ULONGLONG MFTIndex
, PCHAR FileName
, ULONGLONG
*OutMFTIndex
)
550 PNTFS_MFT_RECORD MftRecord
;
552 PNTFS_ATTR_CONTEXT IndexRootCtx
;
553 PNTFS_ATTR_CONTEXT IndexBitmapCtx
;
554 PNTFS_ATTR_CONTEXT IndexAllocationCtx
;
555 PNTFS_INDEX_ROOT IndexRoot
;
556 ULONGLONG BitmapDataSize
;
557 ULONGLONG IndexAllocationSize
;
560 PNTFS_INDEX_ENTRY IndexEntry
, IndexEntryEnd
;
562 ULONG IndexBlockSize
;
564 MftRecord
= FrLdrTempAlloc(Volume
->MftRecordSize
, TAG_NTFS_MFT
);
565 if (MftRecord
== NULL
)
570 if (NtfsReadMftRecord(Volume
, MFTIndex
, MftRecord
))
572 //Magic = MftRecord->Magic;
574 IndexRootCtx
= NtfsFindAttribute(Volume
, MftRecord
, NTFS_ATTR_TYPE_INDEX_ROOT
, L
"$I30");
575 if (IndexRootCtx
== NULL
)
577 FrLdrTempFree(MftRecord
, TAG_NTFS_MFT
);
581 IndexRecord
= FrLdrTempAlloc(Volume
->IndexRecordSize
, TAG_NTFS_INDEX_REC
);
582 if (IndexRecord
== NULL
)
584 FrLdrTempFree(MftRecord
, TAG_NTFS_MFT
);
588 NtfsReadAttribute(Volume
, IndexRootCtx
, 0, IndexRecord
, Volume
->IndexRecordSize
);
589 IndexRoot
= (PNTFS_INDEX_ROOT
)IndexRecord
;
590 IndexEntry
= (PNTFS_INDEX_ENTRY
)((PCHAR
)&IndexRoot
->IndexHeader
+ IndexRoot
->IndexHeader
.EntriesOffset
);
591 /* Index root is always resident. */
592 IndexEntryEnd
= (PNTFS_INDEX_ENTRY
)(IndexRecord
+ IndexRootCtx
->Record
.Resident
.ValueLength
);
593 NtfsReleaseAttributeContext(IndexRootCtx
);
595 TRACE("IndexRecordSize: %x IndexBlockSize: %x\n", Volume
->IndexRecordSize
, IndexRoot
->IndexBlockSize
);
597 while (IndexEntry
< IndexEntryEnd
&&
598 !(IndexEntry
->Flags
& NTFS_INDEX_ENTRY_END
))
600 if (NtfsCompareFileName(FileName
, IndexEntry
))
602 *OutMFTIndex
= IndexEntry
->Data
.Directory
.IndexedFile
;
603 FrLdrTempFree(IndexRecord
, TAG_NTFS_INDEX_REC
);
604 FrLdrTempFree(MftRecord
, TAG_NTFS_MFT
);
607 IndexEntry
= (PNTFS_INDEX_ENTRY
)((PCHAR
)IndexEntry
+ IndexEntry
->Length
);
610 if (IndexRoot
->IndexHeader
.Flags
& NTFS_LARGE_INDEX
)
612 TRACE("Large Index!\n");
614 IndexBlockSize
= IndexRoot
->IndexBlockSize
;
616 IndexBitmapCtx
= NtfsFindAttribute(Volume
, MftRecord
, NTFS_ATTR_TYPE_BITMAP
, L
"$I30");
617 if (IndexBitmapCtx
== NULL
)
619 TRACE("Corrupted filesystem!\n");
620 FrLdrTempFree(MftRecord
, TAG_NTFS_MFT
);
623 BitmapDataSize
= NtfsGetAttributeSize(&IndexBitmapCtx
->Record
);
624 TRACE("BitmapDataSize: %x\n", (ULONG
)BitmapDataSize
);
625 if(BitmapDataSize
<= 0xFFFFFFFF)
626 BitmapData
= FrLdrTempAlloc((ULONG
)BitmapDataSize
, TAG_NTFS_BITMAP
);
630 if (BitmapData
== NULL
)
632 FrLdrTempFree(IndexRecord
, TAG_NTFS_INDEX_REC
);
633 FrLdrTempFree(MftRecord
, TAG_NTFS_MFT
);
636 NtfsReadAttribute(Volume
, IndexBitmapCtx
, 0, BitmapData
, (ULONG
)BitmapDataSize
);
637 NtfsReleaseAttributeContext(IndexBitmapCtx
);
639 IndexAllocationCtx
= NtfsFindAttribute(Volume
, MftRecord
, NTFS_ATTR_TYPE_INDEX_ALLOCATION
, L
"$I30");
640 if (IndexAllocationCtx
== NULL
)
642 TRACE("Corrupted filesystem!\n");
643 FrLdrTempFree(BitmapData
, TAG_NTFS_BITMAP
);
644 FrLdrTempFree(IndexRecord
, TAG_NTFS_INDEX_REC
);
645 FrLdrTempFree(MftRecord
, TAG_NTFS_MFT
);
648 IndexAllocationSize
= NtfsGetAttributeSize(&IndexAllocationCtx
->Record
);
654 TRACE("RecordOffset: %x IndexAllocationSize: %x\n", RecordOffset
, IndexAllocationSize
);
655 for (; RecordOffset
< IndexAllocationSize
;)
657 UCHAR Bit
= 1 << ((RecordOffset
/ IndexBlockSize
) & 7);
658 ULONG Byte
= (RecordOffset
/ IndexBlockSize
) >> 3;
659 if ((BitmapData
[Byte
] & Bit
))
661 RecordOffset
+= IndexBlockSize
;
664 if (RecordOffset
>= IndexAllocationSize
)
669 NtfsReadAttribute(Volume
, IndexAllocationCtx
, RecordOffset
, IndexRecord
, IndexBlockSize
);
671 if (!NtfsFixupRecord(Volume
, (PNTFS_RECORD
)IndexRecord
))
677 IndexEntry
= (PNTFS_INDEX_ENTRY
)(IndexRecord
+ 0x18 + *(USHORT
*)(IndexRecord
+ 0x18));
678 IndexEntryEnd
= (PNTFS_INDEX_ENTRY
)(IndexRecord
+ IndexBlockSize
);
680 while (IndexEntry
< IndexEntryEnd
&&
681 !(IndexEntry
->Flags
& NTFS_INDEX_ENTRY_END
))
683 if (NtfsCompareFileName(FileName
, IndexEntry
))
685 TRACE("File found\n");
686 *OutMFTIndex
= IndexEntry
->Data
.Directory
.IndexedFile
;
687 FrLdrTempFree(BitmapData
, TAG_NTFS_BITMAP
);
688 FrLdrTempFree(IndexRecord
, TAG_NTFS_INDEX_REC
);
689 FrLdrTempFree(MftRecord
, TAG_NTFS_MFT
);
690 NtfsReleaseAttributeContext(IndexAllocationCtx
);
693 IndexEntry
= (PNTFS_INDEX_ENTRY
)((PCHAR
)IndexEntry
+ IndexEntry
->Length
);
696 RecordOffset
+= IndexBlockSize
;
699 NtfsReleaseAttributeContext(IndexAllocationCtx
);
700 FrLdrTempFree(BitmapData
, TAG_NTFS_BITMAP
);
703 FrLdrTempFree(IndexRecord
, TAG_NTFS_INDEX_REC
);
707 TRACE("Can't read MFT record\n");
709 FrLdrTempFree(MftRecord
, TAG_NTFS_MFT
);
714 static BOOLEAN
NtfsLookupFile(PNTFS_VOLUME_INFO Volume
, PCSTR FileName
, PNTFS_MFT_RECORD MftRecord
, PNTFS_ATTR_CONTEXT
*DataContext
)
716 ULONG NumberOfPathParts
;
718 ULONGLONG CurrentMFTIndex
;
721 TRACE("NtfsLookupFile() FileName = %s\n", FileName
);
723 CurrentMFTIndex
= NTFS_FILE_ROOT
;
724 NumberOfPathParts
= FsGetNumPathParts(FileName
);
725 for (i
= 0; i
< NumberOfPathParts
; i
++)
727 FsGetFirstNameFromPath(PathPart
, FileName
);
729 for (; (*FileName
!= '\\') && (*FileName
!= '/') && (*FileName
!= '\0'); FileName
++)
733 TRACE("- Lookup: %s\n", PathPart
);
734 if (!NtfsFindMftRecord(Volume
, CurrentMFTIndex
, PathPart
, &CurrentMFTIndex
))
739 TRACE("- Lookup: %x\n", CurrentMFTIndex
);
742 if (!NtfsReadMftRecord(Volume
, CurrentMFTIndex
, MftRecord
))
744 TRACE("NtfsLookupFile: Can't read MFT record\n");
748 *DataContext
= NtfsFindAttribute(Volume
, MftRecord
, NTFS_ATTR_TYPE_DATA
, L
"");
749 if (*DataContext
== NULL
)
751 TRACE("NtfsLookupFile: Can't find data attribute\n");
758 ARC_STATUS
NtfsClose(ULONG FileId
)
760 PNTFS_FILE_HANDLE FileHandle
= FsGetDeviceSpecific(FileId
);
762 NtfsReleaseAttributeContext(FileHandle
->DataContext
);
763 FrLdrTempFree(FileHandle
, TAG_NTFS_FILE
);
768 ARC_STATUS
NtfsGetFileInformation(ULONG FileId
, FILEINFORMATION
* Information
)
770 PNTFS_FILE_HANDLE FileHandle
= FsGetDeviceSpecific(FileId
);
772 RtlZeroMemory(Information
, sizeof(FILEINFORMATION
));
773 Information
->EndingAddress
.QuadPart
= NtfsGetAttributeSize(&FileHandle
->DataContext
->Record
);
774 Information
->CurrentAddress
.QuadPart
= FileHandle
->Offset
;
776 TRACE("NtfsGetFileInformation() FileSize = %d\n",
777 Information
->EndingAddress
.LowPart
);
778 TRACE("NtfsGetFileInformation() FilePointer = %d\n",
779 Information
->CurrentAddress
.LowPart
);
784 ARC_STATUS
NtfsOpen(CHAR
* Path
, OPENMODE OpenMode
, ULONG
* FileId
)
786 PNTFS_VOLUME_INFO Volume
;
787 PNTFS_FILE_HANDLE FileHandle
;
788 PNTFS_MFT_RECORD MftRecord
;
794 if (OpenMode
!= OpenReadOnly
)
798 // Get underlying device
800 DeviceId
= FsGetDeviceId(*FileId
);
801 Volume
= NtfsVolumes
[DeviceId
];
803 TRACE("NtfsOpen() FileName = %s\n", Path
);
806 // Allocate file structure
808 FileHandle
= FrLdrTempAlloc(sizeof(NTFS_FILE_HANDLE
) + Volume
->MftRecordSize
,
814 RtlZeroMemory(FileHandle
, sizeof(NTFS_FILE_HANDLE
) + Volume
->MftRecordSize
);
815 FileHandle
->Volume
= Volume
;
820 MftRecord
= (PNTFS_MFT_RECORD
)(FileHandle
+ 1);
821 if (!NtfsLookupFile(Volume
, Path
, MftRecord
, &FileHandle
->DataContext
))
823 FrLdrTempFree(FileHandle
, TAG_NTFS_FILE
);
827 FsSetDeviceSpecific(*FileId
, FileHandle
);
831 ARC_STATUS
NtfsRead(ULONG FileId
, VOID
* Buffer
, ULONG N
, ULONG
* Count
)
833 PNTFS_FILE_HANDLE FileHandle
= FsGetDeviceSpecific(FileId
);
834 ULONGLONG BytesRead64
;
839 BytesRead64
= NtfsReadAttribute(FileHandle
->Volume
, FileHandle
->DataContext
, FileHandle
->Offset
, Buffer
, N
);
840 *Count
= (ULONG
)BytesRead64
;
851 ARC_STATUS
NtfsSeek(ULONG FileId
, LARGE_INTEGER
* Position
, SEEKMODE SeekMode
)
853 PNTFS_FILE_HANDLE FileHandle
= FsGetDeviceSpecific(FileId
);
855 TRACE("NtfsSeek() NewFilePointer = %lu\n", Position
->LowPart
);
857 if (SeekMode
!= SeekAbsolute
)
859 if (Position
->HighPart
!= 0)
861 if (Position
->LowPart
>= (ULONG
)NtfsGetAttributeSize(&FileHandle
->DataContext
->Record
))
864 FileHandle
->Offset
= Position
->LowPart
;
868 const DEVVTBL NtfsFuncTable
=
871 NtfsGetFileInformation
,
878 const DEVVTBL
* NtfsMount(ULONG DeviceId
)
880 PNTFS_VOLUME_INFO Volume
;
881 LARGE_INTEGER Position
;
886 // Allocate data for volume information
888 Volume
= FrLdrTempAlloc(sizeof(NTFS_VOLUME_INFO
), TAG_NTFS_VOLUME
);
891 RtlZeroMemory(Volume
, sizeof(NTFS_VOLUME_INFO
));
894 // Read the BootSector
896 Position
.HighPart
= 0;
897 Position
.LowPart
= 0;
898 Status
= ArcSeek(DeviceId
, &Position
, SeekAbsolute
);
899 if (Status
!= ESUCCESS
)
901 FrLdrTempFree(Volume
, TAG_NTFS_VOLUME
);
904 Status
= ArcRead(DeviceId
, &Volume
->BootSector
, sizeof(Volume
->BootSector
), &Count
);
905 if (Status
!= ESUCCESS
|| Count
!= sizeof(Volume
->BootSector
))
907 FrLdrTempFree(Volume
, TAG_NTFS_VOLUME
);
912 // Check if BootSector is valid. If no, return early
914 if (!RtlEqualMemory(Volume
->BootSector
.SystemId
, "NTFS", 4))
916 FrLdrTempFree(Volume
, TAG_NTFS_VOLUME
);
921 // Calculate cluster size and MFT record size
923 Volume
->ClusterSize
= Volume
->BootSector
.SectorsPerCluster
* Volume
->BootSector
.BytesPerSector
;
924 if (Volume
->BootSector
.ClustersPerMftRecord
> 0)
925 Volume
->MftRecordSize
= Volume
->BootSector
.ClustersPerMftRecord
* Volume
->ClusterSize
;
927 Volume
->MftRecordSize
= 1 << (-Volume
->BootSector
.ClustersPerMftRecord
);
928 if (Volume
->BootSector
.ClustersPerIndexRecord
> 0)
929 Volume
->IndexRecordSize
= Volume
->BootSector
.ClustersPerIndexRecord
* Volume
->ClusterSize
;
931 Volume
->IndexRecordSize
= 1 << (-Volume
->BootSector
.ClustersPerIndexRecord
);
933 TRACE("ClusterSize: 0x%x\n", Volume
->ClusterSize
);
934 TRACE("ClustersPerMftRecord: %d\n", Volume
->BootSector
.ClustersPerMftRecord
);
935 TRACE("ClustersPerIndexRecord: %d\n", Volume
->BootSector
.ClustersPerIndexRecord
);
936 TRACE("MftRecordSize: 0x%x\n", Volume
->MftRecordSize
);
937 TRACE("IndexRecordSize: 0x%x\n", Volume
->IndexRecordSize
);
942 TRACE("Reading MFT index...\n");
943 Volume
->MasterFileTable
= FrLdrTempAlloc(Volume
->MftRecordSize
, TAG_NTFS_MFT
);
944 if (!Volume
->MasterFileTable
)
946 FrLdrTempFree(Volume
, TAG_NTFS_VOLUME
);
949 Position
.QuadPart
= Volume
->BootSector
.MftLocation
* Volume
->ClusterSize
;
950 Status
= ArcSeek(DeviceId
, &Position
, SeekAbsolute
);
951 if (Status
!= ESUCCESS
)
953 FileSystemError("Failed to seek to Master File Table record.");
954 FrLdrTempFree(Volume
->MasterFileTable
, TAG_NTFS_MFT
);
955 FrLdrTempFree(Volume
, TAG_NTFS_VOLUME
);
958 Status
= ArcRead(DeviceId
, Volume
->MasterFileTable
, Volume
->MftRecordSize
, &Count
);
959 if (Status
!= ESUCCESS
|| Count
!= Volume
->MftRecordSize
)
961 FileSystemError("Failed to read the Master File Table record.");
962 FrLdrTempFree(Volume
->MasterFileTable
, TAG_NTFS_MFT
);
963 FrLdrTempFree(Volume
, TAG_NTFS_VOLUME
);
968 // Keep room to read partial sectors
970 Volume
->TemporarySector
= FrLdrTempAlloc(Volume
->BootSector
.BytesPerSector
, TAG_NTFS_DATA
);
971 if (!Volume
->TemporarySector
)
973 FileSystemError("Failed to allocate memory.");
974 FrLdrTempFree(Volume
->MasterFileTable
, TAG_NTFS_MFT
);
975 FrLdrTempFree(Volume
, TAG_NTFS_VOLUME
);
982 Volume
->DeviceId
= DeviceId
;
985 // Search DATA attribute
987 TRACE("Searching for DATA attribute...\n");
988 Volume
->MFTContext
= NtfsFindAttribute(Volume
, Volume
->MasterFileTable
, NTFS_ATTR_TYPE_DATA
, L
"");
989 if (!Volume
->MFTContext
)
991 FileSystemError("Can't find data attribute for Master File Table.");
992 FrLdrTempFree(Volume
->MasterFileTable
, TAG_NTFS_MFT
);
993 FrLdrTempFree(Volume
, TAG_NTFS_VOLUME
);
998 // Remember NTFS volume information
1000 NtfsVolumes
[DeviceId
] = Volume
;
1005 return &NtfsFuncTable
;