The real, definitive, Visual C++ support branch. Accept no substitutes
[reactos.git] / boot / freeldr / freeldr / fs / ntfs.c
1 /*
2 * FreeLoader NTFS support
3 * Copyright (C) 2004 Filip Navara <xnavara@volny.cz>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 */
19
20 /*
21 * Limitations:
22 * - No support for compressed files.
23 * - May crash on corrupted filesystem.
24 */
25
26 #include <freeldr.h>
27 #include <debug.h>
28
29 PNTFS_BOOTSECTOR NtfsBootSector;
30 ULONG NtfsClusterSize;
31 ULONG NtfsMftRecordSize;
32 ULONG NtfsIndexRecordSize;
33 ULONG NtfsDriveNumber;
34 ULONG NtfsSectorOfClusterZero;
35 PNTFS_MFT_RECORD NtfsMasterFileTable;
36 /* FIXME: NtfsMFTContext is never freed. */
37 PNTFS_ATTR_CONTEXT NtfsMFTContext;
38
39 static ULONGLONG NtfsGetAttributeSize(PNTFS_ATTR_RECORD AttrRecord)
40 {
41 if (AttrRecord->IsNonResident)
42 return AttrRecord->NonResident.DataSize;
43 else
44 return AttrRecord->Resident.ValueLength;
45 }
46
47 static PUCHAR NtfsDecodeRun(PUCHAR DataRun, LONGLONG *DataRunOffset, ULONGLONG *DataRunLength)
48 {
49 UCHAR DataRunOffsetSize;
50 UCHAR DataRunLengthSize;
51 CHAR i;
52
53 DataRunOffsetSize = (*DataRun >> 4) & 0xF;
54 DataRunLengthSize = *DataRun & 0xF;
55 *DataRunOffset = 0;
56 *DataRunLength = 0;
57 DataRun++;
58 for (i = 0; i < DataRunLengthSize; i++)
59 {
60 *DataRunLength += *DataRun << (i << 3);
61 DataRun++;
62 }
63
64 /* NTFS 3+ sparse files */
65 if (DataRunOffsetSize == 0)
66 {
67 *DataRunOffset = -1;
68 }
69 else
70 {
71 for (i = 0; i < DataRunOffsetSize - 1; i++)
72 {
73 *DataRunOffset += *DataRun << (i << 3);
74 DataRun++;
75 }
76 /* The last byte contains sign so we must process it different way. */
77 *DataRunOffset = ((CHAR)(*(DataRun++)) << (i << 3)) + *DataRunOffset;
78 }
79
80 DbgPrint((DPRINT_FILESYSTEM, "DataRunOffsetSize: %x\n", DataRunOffsetSize));
81 DbgPrint((DPRINT_FILESYSTEM, "DataRunLengthSize: %x\n", DataRunLengthSize));
82 DbgPrint((DPRINT_FILESYSTEM, "DataRunOffset: %x\n", *DataRunOffset));
83 DbgPrint((DPRINT_FILESYSTEM, "DataRunLength: %x\n", *DataRunLength));
84
85 return DataRun;
86 }
87
88 static PNTFS_ATTR_CONTEXT NtfsPrepareAttributeContext(PNTFS_ATTR_RECORD AttrRecord)
89 {
90 PNTFS_ATTR_CONTEXT Context;
91
92 Context = MmHeapAlloc(FIELD_OFFSET(NTFS_ATTR_CONTEXT, Record) + AttrRecord->Length);
93 RtlCopyMemory(&Context->Record, AttrRecord, AttrRecord->Length);
94 if (AttrRecord->IsNonResident)
95 {
96 LONGLONG DataRunOffset;
97 ULONGLONG DataRunLength;
98
99 Context->CacheRun = (PUCHAR)&Context->Record + Context->Record.NonResident.MappingPairsOffset;
100 Context->CacheRunOffset = 0;
101 Context->CacheRun = NtfsDecodeRun(Context->CacheRun, &DataRunOffset, &DataRunLength);
102 Context->CacheRunLength = DataRunLength;
103 if (DataRunOffset != -1)
104 {
105 /* Normal run. */
106 Context->CacheRunStartLCN =
107 Context->CacheRunLastLCN = DataRunOffset;
108 }
109 else
110 {
111 /* Sparse run. */
112 Context->CacheRunStartLCN = -1;
113 Context->CacheRunLastLCN = 0;
114 }
115 Context->CacheRunCurrentOffset = 0;
116 }
117
118 return Context;
119 }
120
121 static VOID NtfsReleaseAttributeContext(PNTFS_ATTR_CONTEXT Context)
122 {
123 MmHeapFree(Context);
124 }
125
126 /* FIXME: Optimize for multisector reads. */
127 static BOOLEAN NtfsDiskRead(ULONGLONG Offset, ULONGLONG Length, PCHAR Buffer)
128 {
129 USHORT ReadLength;
130
131 DbgPrint((DPRINT_FILESYSTEM, "NtfsDiskRead - Offset: %I64d Length: %I64d\n", Offset, Length));
132 RtlZeroMemory((PCHAR)DISKREADBUFFER, 0x1000);
133
134 /* I. Read partial first sector if needed */
135 if (Offset % NtfsBootSector->BytesPerSector)
136 {
137 if (!MachDiskReadLogicalSectors(NtfsDriveNumber, NtfsSectorOfClusterZero + (Offset / NtfsBootSector->BytesPerSector), 1, (PCHAR)DISKREADBUFFER))
138 return FALSE;
139 ReadLength = min(Length, NtfsBootSector->BytesPerSector - (Offset % NtfsBootSector->BytesPerSector));
140 RtlCopyMemory(Buffer, (PCHAR)DISKREADBUFFER + (Offset % NtfsBootSector->BytesPerSector), ReadLength);
141 Buffer += ReadLength;
142 Length -= ReadLength;
143 Offset += ReadLength;
144 }
145
146 /* II. Read all complete 64-sector blocks. */
147 while (Length >= (ULONGLONG)64 * (ULONGLONG)NtfsBootSector->BytesPerSector)
148 {
149 if (!MachDiskReadLogicalSectors(NtfsDriveNumber, NtfsSectorOfClusterZero + (Offset / NtfsBootSector->BytesPerSector), 64, (PCHAR)DISKREADBUFFER))
150 return FALSE;
151 RtlCopyMemory(Buffer, (PCHAR)DISKREADBUFFER, 64 * NtfsBootSector->BytesPerSector);
152 Buffer += 64 * NtfsBootSector->BytesPerSector;
153 Length -= 64 * NtfsBootSector->BytesPerSector;
154 Offset += 64 * NtfsBootSector->BytesPerSector;
155 }
156
157 /* III. Read the rest of data */
158 if (Length)
159 {
160 ReadLength = ((Length + NtfsBootSector->BytesPerSector - 1) / NtfsBootSector->BytesPerSector);
161 if (!MachDiskReadLogicalSectors(NtfsDriveNumber, NtfsSectorOfClusterZero + (Offset / NtfsBootSector->BytesPerSector), ReadLength, (PCHAR)DISKREADBUFFER))
162 return FALSE;
163 RtlCopyMemory(Buffer, (PCHAR)DISKREADBUFFER, Length);
164 }
165
166 return TRUE;
167 }
168
169 static ULONGLONG NtfsReadAttribute(PNTFS_ATTR_CONTEXT Context, ULONGLONG Offset, PCHAR Buffer, ULONGLONG Length)
170 {
171 ULONGLONG LastLCN;
172 PUCHAR DataRun;
173 LONGLONG DataRunOffset;
174 ULONGLONG DataRunLength;
175 LONGLONG DataRunStartLCN;
176 ULONGLONG CurrentOffset;
177 ULONGLONG ReadLength;
178 ULONGLONG AlreadyRead;
179
180 if (!Context->Record.IsNonResident)
181 {
182 if (Offset > Context->Record.Resident.ValueLength)
183 return 0;
184 if (Offset + Length > Context->Record.Resident.ValueLength)
185 Length = Context->Record.Resident.ValueLength - Offset;
186 RtlCopyMemory(Buffer, (PCHAR)&Context->Record + Context->Record.Resident.ValueOffset + Offset, Length);
187 return Length;
188 }
189
190 /*
191 * Non-resident attribute
192 */
193
194 /*
195 * I. Find the corresponding start data run.
196 */
197
198 AlreadyRead = 0;
199
200 if(Context->CacheRunOffset <= Offset && Offset < Context->CacheRunOffset + Context->CacheRunLength * NtfsClusterSize)
201 {
202 DataRun = Context->CacheRun;
203 LastLCN = Context->CacheRunLastLCN;
204 DataRunStartLCN = Context->CacheRunStartLCN;
205 DataRunLength = Context->CacheRunLength;
206 CurrentOffset = Context->CacheRunCurrentOffset;
207 }
208 else
209 {
210 LastLCN = 0;
211 DataRun = (PUCHAR)&Context->Record + Context->Record.NonResident.MappingPairsOffset;
212 CurrentOffset = 0;
213
214 while (1)
215 {
216 DataRun = NtfsDecodeRun(DataRun, &DataRunOffset, &DataRunLength);
217 if (DataRunOffset != -1)
218 {
219 /* Normal data run. */
220 DataRunStartLCN = LastLCN + DataRunOffset;
221 LastLCN = DataRunStartLCN;
222 }
223 else
224 {
225 /* Sparse data run. */
226 DataRunStartLCN = -1;
227 }
228
229 if (Offset >= CurrentOffset &&
230 Offset < CurrentOffset + (DataRunLength * NtfsClusterSize))
231 {
232 break;
233 }
234
235 if (*DataRun == 0)
236 {
237 return AlreadyRead;
238 }
239
240 CurrentOffset += DataRunLength * NtfsClusterSize;
241 }
242 }
243
244 /*
245 * II. Go through the run list and read the data
246 */
247
248 ReadLength = min(DataRunLength * NtfsClusterSize - (Offset - CurrentOffset), Length);
249 if (DataRunStartLCN == -1)
250 RtlZeroMemory(Buffer, ReadLength);
251 if (NtfsDiskRead(DataRunStartLCN * NtfsClusterSize + Offset - CurrentOffset, ReadLength, Buffer))
252 {
253 Length -= ReadLength;
254 Buffer += ReadLength;
255 AlreadyRead += ReadLength;
256
257 if (ReadLength == DataRunLength * NtfsClusterSize - (Offset - CurrentOffset))
258 {
259 CurrentOffset += DataRunLength * NtfsClusterSize;
260 DataRun = NtfsDecodeRun(DataRun, &DataRunOffset, &DataRunLength);
261 if (DataRunLength != -1)
262 {
263 DataRunStartLCN = LastLCN + DataRunOffset;
264 LastLCN = DataRunStartLCN;
265 }
266 else
267 DataRunStartLCN = -1;
268
269 if (*DataRun == 0)
270 return AlreadyRead;
271 }
272
273 while (Length > 0)
274 {
275 ReadLength = min(DataRunLength * NtfsClusterSize, Length);
276 if (DataRunStartLCN == -1)
277 RtlZeroMemory(Buffer, ReadLength);
278 else if (!NtfsDiskRead(DataRunStartLCN * NtfsClusterSize, ReadLength, Buffer))
279 break;
280
281 Length -= ReadLength;
282 Buffer += ReadLength;
283 AlreadyRead += ReadLength;
284
285 /* We finished this request, but there still data in this data run. */
286 if (Length == 0 && ReadLength != DataRunLength * NtfsClusterSize)
287 break;
288
289 /*
290 * Go to next run in the list.
291 */
292
293 if (*DataRun == 0)
294 break;
295 CurrentOffset += DataRunLength * NtfsClusterSize;
296 DataRun = NtfsDecodeRun(DataRun, &DataRunOffset, &DataRunLength);
297 if (DataRunOffset != -1)
298 {
299 /* Normal data run. */
300 DataRunStartLCN = LastLCN + DataRunOffset;
301 LastLCN = DataRunStartLCN;
302 }
303 else
304 {
305 /* Sparse data run. */
306 DataRunStartLCN = -1;
307 }
308 } /* while */
309
310 } /* if Disk */
311
312 Context->CacheRun = DataRun;
313 Context->CacheRunOffset = Offset + AlreadyRead;
314 Context->CacheRunStartLCN = DataRunStartLCN;
315 Context->CacheRunLength = DataRunLength;
316 Context->CacheRunLastLCN = LastLCN;
317 Context->CacheRunCurrentOffset = CurrentOffset;
318
319 return AlreadyRead;
320 }
321
322 static PNTFS_ATTR_CONTEXT NtfsFindAttributeHelper(PNTFS_ATTR_RECORD AttrRecord, PNTFS_ATTR_RECORD AttrRecordEnd, ULONG Type, const WCHAR *Name, ULONG NameLength)
323 {
324 while (AttrRecord < AttrRecordEnd)
325 {
326 if (AttrRecord->Type == NTFS_ATTR_TYPE_END)
327 break;
328
329 if (AttrRecord->Type == NTFS_ATTR_TYPE_ATTRIBUTE_LIST)
330 {
331 PNTFS_ATTR_CONTEXT Context;
332 PNTFS_ATTR_CONTEXT ListContext;
333 PVOID ListBuffer;
334 ULONGLONG ListSize;
335 PNTFS_ATTR_RECORD ListAttrRecord;
336 PNTFS_ATTR_RECORD ListAttrRecordEnd;
337
338 ListContext = NtfsPrepareAttributeContext(AttrRecord);
339
340 ListSize = NtfsGetAttributeSize(&ListContext->Record);
341 ListBuffer = MmHeapAlloc(ListSize);
342
343 ListAttrRecord = (PNTFS_ATTR_RECORD)ListBuffer;
344 ListAttrRecordEnd = (PNTFS_ATTR_RECORD)((PCHAR)ListBuffer + ListSize);
345
346 if (NtfsReadAttribute(ListContext, 0, ListBuffer, ListSize) == ListSize)
347 {
348 Context = NtfsFindAttributeHelper(ListAttrRecord, ListAttrRecordEnd,
349 Type, Name, NameLength);
350
351 NtfsReleaseAttributeContext(ListContext);
352 MmHeapFree(ListBuffer);
353
354 if (Context != NULL)
355 return Context;
356 }
357 }
358
359 if (AttrRecord->Type == Type)
360 {
361 if (AttrRecord->NameLength == NameLength)
362 {
363 PWCHAR AttrName;
364
365 AttrName = (PWCHAR)((PCHAR)AttrRecord + AttrRecord->NameOffset);
366 if (RtlEqualMemory(AttrName, Name, NameLength << 1))
367 {
368 /* Found it, fill up the context and return. */
369 return NtfsPrepareAttributeContext(AttrRecord);
370 }
371 }
372 }
373
374 AttrRecord = (PNTFS_ATTR_RECORD)((PCHAR)AttrRecord + AttrRecord->Length);
375 }
376
377 return NULL;
378 }
379
380 static PNTFS_ATTR_CONTEXT NtfsFindAttribute(PNTFS_MFT_RECORD MftRecord, ULONG Type, const WCHAR *Name)
381 {
382 PNTFS_ATTR_RECORD AttrRecord;
383 PNTFS_ATTR_RECORD AttrRecordEnd;
384 ULONG NameLength;
385
386 AttrRecord = (PNTFS_ATTR_RECORD)((PCHAR)MftRecord + MftRecord->AttributesOffset);
387 AttrRecordEnd = (PNTFS_ATTR_RECORD)((PCHAR)MftRecord + NtfsMftRecordSize);
388 for (NameLength = 0; Name[NameLength] != 0; NameLength++)
389 ;
390
391 return NtfsFindAttributeHelper(AttrRecord, AttrRecordEnd, Type, Name, NameLength);
392 }
393
394 static BOOLEAN NtfsFixupRecord(PNTFS_RECORD Record)
395 {
396 USHORT *USA;
397 USHORT USANumber;
398 USHORT USACount;
399 USHORT *Block;
400
401 USA = (USHORT*)((PCHAR)Record + Record->USAOffset);
402 USANumber = *(USA++);
403 USACount = Record->USACount - 1; /* Exclude the USA Number. */
404 Block = (USHORT*)((PCHAR)Record + NtfsBootSector->BytesPerSector - 2);
405
406 while (USACount)
407 {
408 if (*Block != USANumber)
409 return FALSE;
410 *Block = *(USA++);
411 Block = (USHORT*)((PCHAR)Block + NtfsBootSector->BytesPerSector);
412 USACount--;
413 }
414
415 return TRUE;
416 }
417
418 static BOOLEAN NtfsReadMftRecord(ULONG MFTIndex, PNTFS_MFT_RECORD Buffer)
419 {
420 ULONGLONG BytesRead;
421
422 BytesRead = NtfsReadAttribute(NtfsMFTContext, MFTIndex * NtfsMftRecordSize, (PCHAR)Buffer, NtfsMftRecordSize);
423 if (BytesRead != NtfsMftRecordSize)
424 return FALSE;
425
426 /* Apply update sequence array fixups. */
427 return NtfsFixupRecord((PNTFS_RECORD)Buffer);
428 }
429
430 #ifdef DBG
431 VOID NtfsPrintFile(PNTFS_INDEX_ENTRY IndexEntry)
432 {
433 PWCHAR FileName;
434 UCHAR FileNameLength;
435 CHAR AnsiFileName[256];
436 UCHAR i;
437
438 FileName = IndexEntry->FileName.FileName;
439 FileNameLength = IndexEntry->FileName.FileNameLength;
440
441 for (i = 0; i < FileNameLength; i++)
442 AnsiFileName[i] = FileName[i];
443 AnsiFileName[i] = 0;
444
445 DbgPrint((DPRINT_FILESYSTEM, "- %s (%x)\n", AnsiFileName, IndexEntry->Data.Directory.IndexedFile));
446 }
447 #endif
448
449 static BOOLEAN NtfsCompareFileName(PCHAR FileName, PNTFS_INDEX_ENTRY IndexEntry)
450 {
451 PWCHAR EntryFileName;
452 UCHAR EntryFileNameLength;
453 UCHAR i;
454
455 EntryFileName = IndexEntry->FileName.FileName;
456 EntryFileNameLength = IndexEntry->FileName.FileNameLength;
457
458 #ifdef DBG
459 NtfsPrintFile(IndexEntry);
460 #endif
461
462 if (strlen(FileName) != EntryFileNameLength)
463 return FALSE;
464
465 /* Do case-sensitive compares for Posix file names. */
466 if (IndexEntry->FileName.FileNameType == NTFS_FILE_NAME_POSIX)
467 {
468 for (i = 0; i < EntryFileNameLength; i++)
469 if (EntryFileName[i] != FileName[i])
470 return FALSE;
471 }
472 else
473 {
474 for (i = 0; i < EntryFileNameLength; i++)
475 if (tolower(EntryFileName[i]) != tolower(FileName[i]))
476 return FALSE;
477 }
478
479 return TRUE;
480 }
481
482 static BOOLEAN NtfsFindMftRecord(ULONG MFTIndex, PCHAR FileName, ULONG *OutMFTIndex)
483 {
484 PNTFS_MFT_RECORD MftRecord;
485 ULONG Magic;
486 PNTFS_ATTR_CONTEXT IndexRootCtx;
487 PNTFS_ATTR_CONTEXT IndexBitmapCtx;
488 PNTFS_ATTR_CONTEXT IndexAllocationCtx;
489 PNTFS_INDEX_ROOT IndexRoot;
490 ULONGLONG BitmapDataSize;
491 ULONGLONG IndexAllocationSize;
492 PCHAR BitmapData;
493 PCHAR IndexRecord;
494 PNTFS_INDEX_ENTRY IndexEntry, IndexEntryEnd;
495 ULONG RecordOffset;
496 ULONG IndexBlockSize;
497
498 MftRecord = MmHeapAlloc(NtfsMftRecordSize);
499 if (MftRecord == NULL)
500 {
501 return FALSE;
502 }
503
504 if (NtfsReadMftRecord(MFTIndex, MftRecord))
505 {
506 Magic = MftRecord->Magic;
507
508 IndexRootCtx = NtfsFindAttribute(MftRecord, NTFS_ATTR_TYPE_INDEX_ROOT, L"$I30");
509 if (IndexRootCtx == NULL)
510 {
511 MmHeapFree(MftRecord);
512 return FALSE;
513 }
514
515 IndexRecord = MmHeapAlloc(NtfsIndexRecordSize);
516 if (IndexRecord == NULL)
517 {
518 MmHeapFree(MftRecord);
519 return FALSE;
520 }
521
522 NtfsReadAttribute(IndexRootCtx, 0, IndexRecord, NtfsIndexRecordSize);
523 IndexRoot = (PNTFS_INDEX_ROOT)IndexRecord;
524 IndexEntry = (PNTFS_INDEX_ENTRY)((PCHAR)&IndexRoot->IndexHeader + IndexRoot->IndexHeader.EntriesOffset);
525 /* Index root is always resident. */
526 IndexEntryEnd = (PNTFS_INDEX_ENTRY)(IndexRecord + IndexRootCtx->Record.Resident.ValueLength);
527 NtfsReleaseAttributeContext(IndexRootCtx);
528
529 DbgPrint((DPRINT_FILESYSTEM, "NtfsIndexRecordSize: %x IndexBlockSize: %x\n", NtfsIndexRecordSize, IndexRoot->IndexBlockSize));
530
531 while (IndexEntry < IndexEntryEnd &&
532 !(IndexEntry->Flags & NTFS_INDEX_ENTRY_END))
533 {
534 if (NtfsCompareFileName(FileName, IndexEntry))
535 {
536 *OutMFTIndex = IndexEntry->Data.Directory.IndexedFile;
537 MmHeapFree(IndexRecord);
538 MmHeapFree(MftRecord);
539 return TRUE;
540 }
541 IndexEntry = (PNTFS_INDEX_ENTRY)((PCHAR)IndexEntry + IndexEntry->Length);
542 }
543
544 if (IndexRoot->IndexHeader.Flags & NTFS_LARGE_INDEX)
545 {
546 DbgPrint((DPRINT_FILESYSTEM, "Large Index!\n"));
547
548 IndexBlockSize = IndexRoot->IndexBlockSize;
549
550 IndexBitmapCtx = NtfsFindAttribute(MftRecord, NTFS_ATTR_TYPE_BITMAP, L"$I30");
551 if (IndexBitmapCtx == NULL)
552 {
553 DbgPrint((DPRINT_FILESYSTEM, "Corrupted filesystem!\n"));
554 MmHeapFree(MftRecord);
555 return FALSE;
556 }
557 BitmapDataSize = NtfsGetAttributeSize(&IndexBitmapCtx->Record);
558 DbgPrint((DPRINT_FILESYSTEM, "BitmapDataSize: %x\n", BitmapDataSize));
559 BitmapData = MmHeapAlloc(BitmapDataSize);
560 if (BitmapData == NULL)
561 {
562 MmHeapFree(IndexRecord);
563 MmHeapFree(MftRecord);
564 return FALSE;
565 }
566 NtfsReadAttribute(IndexBitmapCtx, 0, BitmapData, BitmapDataSize);
567 NtfsReleaseAttributeContext(IndexBitmapCtx);
568
569 IndexAllocationCtx = NtfsFindAttribute(MftRecord, NTFS_ATTR_TYPE_INDEX_ALLOCATION, L"$I30");
570 if (IndexAllocationCtx == NULL)
571 {
572 DbgPrint((DPRINT_FILESYSTEM, "Corrupted filesystem!\n"));
573 MmHeapFree(BitmapData);
574 MmHeapFree(IndexRecord);
575 MmHeapFree(MftRecord);
576 return FALSE;
577 }
578 IndexAllocationSize = NtfsGetAttributeSize(&IndexAllocationCtx->Record);
579
580 RecordOffset = 0;
581
582 for (;;)
583 {
584 DbgPrint((DPRINT_FILESYSTEM, "RecordOffset: %x IndexAllocationSize: %x\n", RecordOffset, IndexAllocationSize));
585 for (; RecordOffset < IndexAllocationSize;)
586 {
587 UCHAR Bit = 1 << ((RecordOffset / IndexBlockSize) & 7);
588 ULONG Byte = (RecordOffset / IndexBlockSize) >> 3;
589 if ((BitmapData[Byte] & Bit))
590 break;
591 RecordOffset += IndexBlockSize;
592 }
593
594 if (RecordOffset >= IndexAllocationSize)
595 {
596 break;
597 }
598
599 NtfsReadAttribute(IndexAllocationCtx, RecordOffset, IndexRecord, IndexBlockSize);
600
601 if (!NtfsFixupRecord((PNTFS_RECORD)IndexRecord))
602 {
603 break;
604 }
605
606 /* FIXME */
607 IndexEntry = (PNTFS_INDEX_ENTRY)(IndexRecord + 0x18 + *(USHORT *)(IndexRecord + 0x18));
608 IndexEntryEnd = (PNTFS_INDEX_ENTRY)(IndexRecord + IndexBlockSize);
609
610 while (IndexEntry < IndexEntryEnd &&
611 !(IndexEntry->Flags & NTFS_INDEX_ENTRY_END))
612 {
613 if (NtfsCompareFileName(FileName, IndexEntry))
614 {
615 DbgPrint((DPRINT_FILESYSTEM, "File found\n"));
616 *OutMFTIndex = IndexEntry->Data.Directory.IndexedFile;
617 MmHeapFree(BitmapData);
618 MmHeapFree(IndexRecord);
619 MmHeapFree(MftRecord);
620 NtfsReleaseAttributeContext(IndexAllocationCtx);
621 return TRUE;
622 }
623 IndexEntry = (PNTFS_INDEX_ENTRY)((PCHAR)IndexEntry + IndexEntry->Length);
624 }
625
626 RecordOffset += IndexBlockSize;
627 }
628
629 NtfsReleaseAttributeContext(IndexAllocationCtx);
630 MmHeapFree(BitmapData);
631 }
632
633 MmHeapFree(IndexRecord);
634 }
635 else
636 {
637 DbgPrint((DPRINT_FILESYSTEM, "Can't read MFT record\n"));
638 }
639 MmHeapFree(MftRecord);
640
641 return FALSE;
642 }
643
644 static BOOLEAN NtfsLookupFile(PCSTR FileName, PNTFS_MFT_RECORD MftRecord, PNTFS_ATTR_CONTEXT *DataContext)
645 {
646 ULONG NumberOfPathParts;
647 CHAR PathPart[261];
648 ULONG CurrentMFTIndex;
649 UCHAR i;
650
651 DbgPrint((DPRINT_FILESYSTEM, "NtfsLookupFile() FileName = %s\n", FileName));
652
653 CurrentMFTIndex = NTFS_FILE_ROOT;
654 NumberOfPathParts = FsGetNumPathParts(FileName);
655 for (i = 0; i < NumberOfPathParts; i++)
656 {
657 FsGetFirstNameFromPath(PathPart, FileName);
658
659 for (; (*FileName != '\\') && (*FileName != '/') && (*FileName != '\0'); FileName++)
660 ;
661 FileName++;
662
663 DbgPrint((DPRINT_FILESYSTEM, "- Lookup: %s\n", PathPart));
664 if (!NtfsFindMftRecord(CurrentMFTIndex, PathPart, &CurrentMFTIndex))
665 {
666 DbgPrint((DPRINT_FILESYSTEM, "- Failed\n"));
667 return FALSE;
668 }
669 DbgPrint((DPRINT_FILESYSTEM, "- Lookup: %x\n", CurrentMFTIndex));
670 }
671
672 if (!NtfsReadMftRecord(CurrentMFTIndex, MftRecord))
673 {
674 DbgPrint((DPRINT_FILESYSTEM, "NtfsLookupFile: Can't read MFT record\n"));
675 return FALSE;
676 }
677
678 *DataContext = NtfsFindAttribute(MftRecord, NTFS_ATTR_TYPE_DATA, L"");
679 if (*DataContext == NULL)
680 {
681 DbgPrint((DPRINT_FILESYSTEM, "NtfsLookupFile: Can't find data attribute\n"));
682 return FALSE;
683 }
684
685 return TRUE;
686 }
687
688 BOOLEAN NtfsOpenVolume(UCHAR DriveNumber, ULONGLONG VolumeStartSector, ULONGLONG PartitionSectorCount)
689 {
690 NtfsBootSector = (PNTFS_BOOTSECTOR)DISKREADBUFFER;
691
692 DbgPrint((DPRINT_FILESYSTEM, "NtfsOpenVolume() DriveNumber = 0x%x VolumeStartSector = 0x%x\n", DriveNumber, VolumeStartSector));
693
694 if (!MachDiskReadLogicalSectors(DriveNumber, VolumeStartSector, 1, (PCHAR)DISKREADBUFFER))
695 {
696 FileSystemError("Failed to read the boot sector.");
697 return FALSE;
698 }
699
700 if (!RtlEqualMemory(NtfsBootSector->SystemId, "NTFS", 4))
701 {
702 FileSystemError("Invalid NTFS signature.");
703 return FALSE;
704 }
705
706 NtfsBootSector = MmHeapAlloc(NtfsBootSector->BytesPerSector);
707 if (NtfsBootSector == NULL)
708 {
709 return FALSE;
710 }
711
712 RtlCopyMemory(NtfsBootSector, (PCHAR)DISKREADBUFFER, ((PNTFS_BOOTSECTOR)DISKREADBUFFER)->BytesPerSector);
713
714 NtfsClusterSize = NtfsBootSector->SectorsPerCluster * NtfsBootSector->BytesPerSector;
715 if (NtfsBootSector->ClustersPerMftRecord > 0)
716 NtfsMftRecordSize = NtfsBootSector->ClustersPerMftRecord * NtfsClusterSize;
717 else
718 NtfsMftRecordSize = 1 << (-NtfsBootSector->ClustersPerMftRecord);
719 if (NtfsBootSector->ClustersPerIndexRecord > 0)
720 NtfsIndexRecordSize = NtfsBootSector->ClustersPerIndexRecord * NtfsClusterSize;
721 else
722 NtfsIndexRecordSize = 1 << (-NtfsBootSector->ClustersPerIndexRecord);
723
724 DbgPrint((DPRINT_FILESYSTEM, "NtfsClusterSize: 0x%x\n", NtfsClusterSize));
725 DbgPrint((DPRINT_FILESYSTEM, "ClustersPerMftRecord: %d\n", NtfsBootSector->ClustersPerMftRecord));
726 DbgPrint((DPRINT_FILESYSTEM, "ClustersPerIndexRecord: %d\n", NtfsBootSector->ClustersPerIndexRecord));
727 DbgPrint((DPRINT_FILESYSTEM, "NtfsMftRecordSize: 0x%x\n", NtfsMftRecordSize));
728 DbgPrint((DPRINT_FILESYSTEM, "NtfsIndexRecordSize: 0x%x\n", NtfsIndexRecordSize));
729
730 NtfsDriveNumber = DriveNumber;
731 NtfsSectorOfClusterZero = VolumeStartSector;
732
733 DbgPrint((DPRINT_FILESYSTEM, "Reading MFT index...\n"));
734 if (!MachDiskReadLogicalSectors(DriveNumber,
735 NtfsSectorOfClusterZero +
736 (NtfsBootSector->MftLocation * NtfsBootSector->SectorsPerCluster),
737 NtfsMftRecordSize / NtfsBootSector->BytesPerSector, (PCHAR)DISKREADBUFFER))
738 {
739 FileSystemError("Failed to read the Master File Table record.");
740 return FALSE;
741 }
742
743 NtfsMasterFileTable = MmHeapAlloc(NtfsMftRecordSize);
744 if (NtfsMasterFileTable == NULL)
745 {
746 MmHeapFree(NtfsBootSector);
747 return FALSE;
748 }
749
750 RtlCopyMemory(NtfsMasterFileTable, (PCHAR)DISKREADBUFFER, NtfsMftRecordSize);
751
752 DbgPrint((DPRINT_FILESYSTEM, "Searching for DATA attribute...\n"));
753 NtfsMFTContext = NtfsFindAttribute(NtfsMasterFileTable, NTFS_ATTR_TYPE_DATA, L"");
754 if (NtfsMFTContext == NULL)
755 {
756 FileSystemError("Can't find data attribute for Master File Table.");
757 return FALSE;
758 }
759
760 return TRUE;
761 }
762
763 FILE* NtfsOpenFile(PCSTR FileName)
764 {
765 PNTFS_FILE_HANDLE FileHandle;
766 PNTFS_MFT_RECORD MftRecord;
767
768 FileHandle = MmHeapAlloc(sizeof(NTFS_FILE_HANDLE) + NtfsMftRecordSize);
769 if (FileHandle == NULL)
770 {
771 return NULL;
772 }
773
774 MftRecord = (PNTFS_MFT_RECORD)(FileHandle + 1);
775 if (!NtfsLookupFile(FileName, MftRecord, &FileHandle->DataContext))
776 {
777 MmHeapFree(FileHandle);
778 return NULL;
779 }
780
781 FileHandle->Offset = 0;
782
783 return (FILE*)FileHandle;
784 }
785
786 VOID NtfsCloseFile(FILE *File)
787 {
788 PNTFS_FILE_HANDLE FileHandle = (PNTFS_FILE_HANDLE)File;
789 NtfsReleaseAttributeContext(FileHandle->DataContext);
790 MmHeapFree(FileHandle);
791 }
792
793 BOOLEAN NtfsReadFile(FILE *File, ULONG BytesToRead, ULONG* BytesRead, PVOID Buffer)
794 {
795 PNTFS_FILE_HANDLE FileHandle = (PNTFS_FILE_HANDLE)File;
796 ULONGLONG BytesRead64;
797 BytesRead64 = NtfsReadAttribute(FileHandle->DataContext, FileHandle->Offset, Buffer, BytesToRead);
798 if (BytesRead64)
799 {
800 *BytesRead = (ULONG)BytesRead64;
801 FileHandle->Offset += BytesRead64;
802 return TRUE;
803 }
804 return FALSE;
805 }
806
807 ULONG NtfsGetFileSize(FILE *File)
808 {
809 PNTFS_FILE_HANDLE FileHandle = (PNTFS_FILE_HANDLE)File;
810 return (ULONG)NtfsGetAttributeSize(&FileHandle->DataContext->Record);
811 }
812
813 VOID NtfsSetFilePointer(FILE *File, ULONG NewFilePointer)
814 {
815 PNTFS_FILE_HANDLE FileHandle = (PNTFS_FILE_HANDLE)File;
816 FileHandle->Offset = NewFilePointer;
817 }
818
819 ULONG NtfsGetFilePointer(FILE *File)
820 {
821 PNTFS_FILE_HANDLE FileHandle = (PNTFS_FILE_HANDLE)File;
822 return FileHandle->Offset;
823 }
824
825 const FS_VTBL NtfsVtbl = {
826 NtfsOpenVolume,
827 NtfsOpenFile,
828 NtfsCloseFile,
829 NtfsReadFile,
830 NtfsGetFileSize,
831 NtfsSetFilePointer,
832 NtfsGetFilePointer,
833 };