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