bc2e6bfee867fbcabe089ad08bff441b3ee75044
[reactos.git] / boot / freeldr / freeldr / lib / fs / ntfs.c
1 /*
2 * FreeLoader NTFS support
3 * Copyright (C) 2004 Filip Navara <xnavara@volny.cz>
4 * Copyright (C) 2009-2010 Hervé Poussineau
5 *
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.
10 *
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.
15 *
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.
19 */
20
21 /*
22 * Limitations:
23 * - No support for compressed files.
24 * - May crash on corrupted filesystem.
25 */
26
27 #ifndef _M_ARM
28 #include <freeldr.h>
29
30 #include <debug.h>
31 DBG_DEFAULT_CHANNEL(FILESYSTEM);
32
33 #define TAG_NTFS_CONTEXT 'CftN'
34 #define TAG_NTFS_LIST 'LftN'
35 #define TAG_NTFS_MFT 'MftN'
36 #define TAG_NTFS_INDEX_REC 'IftN'
37 #define TAG_NTFS_BITMAP 'BftN'
38 #define TAG_NTFS_FILE 'FftN'
39 #define TAG_NTFS_VOLUME 'VftN'
40 #define TAG_NTFS_DATA 'DftN'
41
42 typedef struct _NTFS_VOLUME_INFO
43 {
44 NTFS_BOOTSECTOR BootSector;
45 ULONG ClusterSize;
46 ULONG MftRecordSize;
47 ULONG IndexRecordSize;
48 PNTFS_MFT_RECORD MasterFileTable;
49 /* FIXME: MFTContext is never freed. */
50 PNTFS_ATTR_CONTEXT MFTContext;
51 ULONG DeviceId;
52 PUCHAR TemporarySector;
53 } NTFS_VOLUME_INFO;
54
55 PNTFS_VOLUME_INFO NtfsVolumes[MAX_FDS];
56
57 static ULONGLONG NtfsGetAttributeSize(PNTFS_ATTR_RECORD AttrRecord)
58 {
59 if (AttrRecord->IsNonResident)
60 return AttrRecord->NonResident.DataSize;
61 else
62 return AttrRecord->Resident.ValueLength;
63 }
64
65 static PUCHAR NtfsDecodeRun(PUCHAR DataRun, LONGLONG *DataRunOffset, ULONGLONG *DataRunLength)
66 {
67 UCHAR DataRunOffsetSize;
68 UCHAR DataRunLengthSize;
69 CHAR i;
70
71 DataRunOffsetSize = (*DataRun >> 4) & 0xF;
72 DataRunLengthSize = *DataRun & 0xF;
73 *DataRunOffset = 0;
74 *DataRunLength = 0;
75 DataRun++;
76 for (i = 0; i < DataRunLengthSize; i++)
77 {
78 *DataRunLength += ((ULONG64)*DataRun) << (i * 8);
79 DataRun++;
80 }
81
82 /* NTFS 3+ sparse files */
83 if (DataRunOffsetSize == 0)
84 {
85 *DataRunOffset = -1;
86 }
87 else
88 {
89 for (i = 0; i < DataRunOffsetSize - 1; i++)
90 {
91 *DataRunOffset += ((ULONG64)*DataRun) << (i * 8);
92 DataRun++;
93 }
94 /* The last byte contains sign so we must process it different way. */
95 *DataRunOffset = ((LONG64)(CHAR)(*(DataRun++)) << (i * 8)) + *DataRunOffset;
96 }
97
98 TRACE("DataRunOffsetSize: %x\n", DataRunOffsetSize);
99 TRACE("DataRunLengthSize: %x\n", DataRunLengthSize);
100 TRACE("DataRunOffset: %x\n", *DataRunOffset);
101 TRACE("DataRunLength: %x\n", *DataRunLength);
102
103 return DataRun;
104 }
105
106 static PNTFS_ATTR_CONTEXT NtfsPrepareAttributeContext(PNTFS_ATTR_RECORD AttrRecord)
107 {
108 PNTFS_ATTR_CONTEXT Context;
109
110 Context = FrLdrTempAlloc(FIELD_OFFSET(NTFS_ATTR_CONTEXT, Record) + AttrRecord->Length,
111 TAG_NTFS_CONTEXT);
112 RtlCopyMemory(&Context->Record, AttrRecord, AttrRecord->Length);
113 if (AttrRecord->IsNonResident)
114 {
115 LONGLONG DataRunOffset;
116 ULONGLONG DataRunLength;
117
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)
123 {
124 /* Normal run. */
125 Context->CacheRunStartLCN =
126 Context->CacheRunLastLCN = DataRunOffset;
127 }
128 else
129 {
130 /* Sparse run. */
131 Context->CacheRunStartLCN = -1;
132 Context->CacheRunLastLCN = 0;
133 }
134 Context->CacheRunCurrentOffset = 0;
135 }
136
137 return Context;
138 }
139
140 static VOID NtfsReleaseAttributeContext(PNTFS_ATTR_CONTEXT Context)
141 {
142 FrLdrTempFree(Context, TAG_NTFS_CONTEXT);
143 }
144
145 static BOOLEAN NtfsDiskRead(PNTFS_VOLUME_INFO Volume, ULONGLONG Offset, ULONGLONG Length, PCHAR Buffer)
146 {
147 LARGE_INTEGER Position;
148 ULONG Count;
149 ULONG ReadLength;
150 ARC_STATUS Status;
151
152 TRACE("NtfsDiskRead - Offset: %I64d Length: %I64d\n", Offset, Length);
153
154 //
155 // I. Read partial first sector if needed
156 //
157 if (Offset % Volume->BootSector.BytesPerSector)
158 {
159 Position.QuadPart = Offset & ~(Volume->BootSector.BytesPerSector - 1);
160 Status = ArcSeek(Volume->DeviceId, &Position, SeekAbsolute);
161 if (Status != ESUCCESS)
162 return FALSE;
163 Status = ArcRead(Volume->DeviceId, Volume->TemporarySector, Volume->BootSector.BytesPerSector, &Count);
164 if (Status != ESUCCESS || Count != Volume->BootSector.BytesPerSector)
165 return FALSE;
166 ReadLength = (USHORT)min(Length, Volume->BootSector.BytesPerSector - (Offset % Volume->BootSector.BytesPerSector));
167
168 //
169 // Copy interesting data
170 //
171 RtlCopyMemory(Buffer,
172 &Volume->TemporarySector[Offset % Volume->BootSector.BytesPerSector],
173 ReadLength);
174
175 //
176 // Move to unfilled buffer part
177 //
178 Buffer += ReadLength;
179 Length -= ReadLength;
180 Offset += ReadLength;
181 }
182
183 //
184 // II. Read all complete blocks
185 //
186 if (Length >= Volume->BootSector.BytesPerSector)
187 {
188 Position.QuadPart = Offset;
189 Status = ArcSeek(Volume->DeviceId, &Position, SeekAbsolute);
190 if (Status != ESUCCESS)
191 return FALSE;
192 ReadLength = Length & ~(Volume->BootSector.BytesPerSector - 1);
193 Status = ArcRead(Volume->DeviceId, Buffer, ReadLength, &Count);
194 if (Status != ESUCCESS || Count != ReadLength)
195 return FALSE;
196
197 //
198 // Move to unfilled buffer part
199 //
200 Buffer += ReadLength;
201 Length -= ReadLength;
202 Offset += ReadLength;
203 }
204
205 //
206 // III. Read the rest of data
207 //
208 if (Length)
209 {
210 Position.QuadPart = Offset;
211 Status = ArcSeek(Volume->DeviceId, &Position, SeekAbsolute);
212 if (Status != ESUCCESS)
213 return FALSE;
214 Status = ArcRead(Volume->DeviceId, Buffer, (ULONG)Length, &Count);
215 if (Status != ESUCCESS || Count != Length)
216 return FALSE;
217 }
218
219 return TRUE;
220 }
221
222 static ULONG NtfsReadAttribute(PNTFS_VOLUME_INFO Volume, PNTFS_ATTR_CONTEXT Context, ULONGLONG Offset, PCHAR Buffer, ULONG Length)
223 {
224 ULONGLONG LastLCN;
225 PUCHAR DataRun;
226 LONGLONG DataRunOffset;
227 ULONGLONG DataRunLength;
228 LONGLONG DataRunStartLCN;
229 ULONGLONG CurrentOffset;
230 ULONG ReadLength;
231 ULONG AlreadyRead;
232
233 if (!Context->Record.IsNonResident)
234 {
235 if (Offset > Context->Record.Resident.ValueLength)
236 return 0;
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);
240 return Length;
241 }
242
243 /*
244 * Non-resident attribute
245 */
246
247 /*
248 * I. Find the corresponding start data run.
249 */
250
251 AlreadyRead = 0;
252
253 // FIXME: Cache seems to be non-working. Disable it for now
254 //if(Context->CacheRunOffset <= Offset && Offset < Context->CacheRunOffset + Context->CacheRunLength * Volume->ClusterSize)
255 if (0)
256 {
257 DataRun = Context->CacheRun;
258 LastLCN = Context->CacheRunLastLCN;
259 DataRunStartLCN = Context->CacheRunStartLCN;
260 DataRunLength = Context->CacheRunLength;
261 CurrentOffset = Context->CacheRunCurrentOffset;
262 }
263 else
264 {
265 LastLCN = 0;
266 DataRun = (PUCHAR)&Context->Record + Context->Record.NonResident.MappingPairsOffset;
267 CurrentOffset = 0;
268
269 while (1)
270 {
271 DataRun = NtfsDecodeRun(DataRun, &DataRunOffset, &DataRunLength);
272 if (DataRunOffset != -1)
273 {
274 /* Normal data run. */
275 DataRunStartLCN = LastLCN + DataRunOffset;
276 LastLCN = DataRunStartLCN;
277 }
278 else
279 {
280 /* Sparse data run. */
281 DataRunStartLCN = -1;
282 }
283
284 if (Offset >= CurrentOffset &&
285 Offset < CurrentOffset + (DataRunLength * Volume->ClusterSize))
286 {
287 break;
288 }
289
290 if (*DataRun == 0)
291 {
292 return AlreadyRead;
293 }
294
295 CurrentOffset += DataRunLength * Volume->ClusterSize;
296 }
297 }
298
299 /*
300 * II. Go through the run list and read the data
301 */
302
303 ReadLength = (ULONG)min(DataRunLength * Volume->ClusterSize - (Offset - CurrentOffset), Length);
304 if (DataRunStartLCN == -1)
305 RtlZeroMemory(Buffer, ReadLength);
306 if (DataRunStartLCN == -1 || NtfsDiskRead(Volume, DataRunStartLCN * Volume->ClusterSize + Offset - CurrentOffset, ReadLength, Buffer))
307 {
308 Length -= ReadLength;
309 Buffer += ReadLength;
310 AlreadyRead += ReadLength;
311
312 if (ReadLength == DataRunLength * Volume->ClusterSize - (Offset - CurrentOffset))
313 {
314 CurrentOffset += DataRunLength * Volume->ClusterSize;
315 DataRun = NtfsDecodeRun(DataRun, &DataRunOffset, &DataRunLength);
316 if (DataRunOffset != (ULONGLONG)-1)
317 {
318 DataRunStartLCN = LastLCN + DataRunOffset;
319 LastLCN = DataRunStartLCN;
320 }
321 else
322 DataRunStartLCN = -1;
323 }
324
325 while (Length > 0)
326 {
327 ReadLength = (ULONG)min(DataRunLength * Volume->ClusterSize, Length);
328 if (DataRunStartLCN == -1)
329 RtlZeroMemory(Buffer, ReadLength);
330 else if (!NtfsDiskRead(Volume, DataRunStartLCN * Volume->ClusterSize, ReadLength, Buffer))
331 break;
332
333 Length -= ReadLength;
334 Buffer += ReadLength;
335 AlreadyRead += ReadLength;
336
337 /* We finished this request, but there still data in this data run. */
338 if (Length == 0 && ReadLength != DataRunLength * Volume->ClusterSize)
339 break;
340
341 /*
342 * Go to next run in the list.
343 */
344
345 if (*DataRun == 0)
346 break;
347 CurrentOffset += DataRunLength * Volume->ClusterSize;
348 DataRun = NtfsDecodeRun(DataRun, &DataRunOffset, &DataRunLength);
349 if (DataRunOffset != -1)
350 {
351 /* Normal data run. */
352 DataRunStartLCN = LastLCN + DataRunOffset;
353 LastLCN = DataRunStartLCN;
354 }
355 else
356 {
357 /* Sparse data run. */
358 DataRunStartLCN = -1;
359 }
360 } /* while */
361
362 } /* if Disk */
363
364 Context->CacheRun = DataRun;
365 Context->CacheRunOffset = Offset + AlreadyRead;
366 Context->CacheRunStartLCN = DataRunStartLCN;
367 Context->CacheRunLength = DataRunLength;
368 Context->CacheRunLastLCN = LastLCN;
369 Context->CacheRunCurrentOffset = CurrentOffset;
370
371 return AlreadyRead;
372 }
373
374 static PNTFS_ATTR_CONTEXT NtfsFindAttributeHelper(PNTFS_VOLUME_INFO Volume, PNTFS_ATTR_RECORD AttrRecord, PNTFS_ATTR_RECORD AttrRecordEnd, ULONG Type, const WCHAR *Name, ULONG NameLength)
375 {
376 while (AttrRecord < AttrRecordEnd)
377 {
378 if (AttrRecord->Type == NTFS_ATTR_TYPE_END)
379 break;
380
381 if (AttrRecord->Type == NTFS_ATTR_TYPE_ATTRIBUTE_LIST)
382 {
383 PNTFS_ATTR_CONTEXT Context;
384 PNTFS_ATTR_CONTEXT ListContext;
385 PVOID ListBuffer;
386 ULONGLONG ListSize;
387 PNTFS_ATTR_RECORD ListAttrRecord;
388 PNTFS_ATTR_RECORD ListAttrRecordEnd;
389
390 ListContext = NtfsPrepareAttributeContext(AttrRecord);
391
392 ListSize = NtfsGetAttributeSize(&ListContext->Record);
393 if(ListSize <= 0xFFFFFFFF)
394 ListBuffer = FrLdrTempAlloc((ULONG)ListSize, TAG_NTFS_LIST);
395 else
396 ListBuffer = NULL;
397
398 if(!ListBuffer)
399 {
400 TRACE("Failed to allocate memory: %x\n", (ULONG)ListSize);
401 continue;
402 }
403
404 ListAttrRecord = (PNTFS_ATTR_RECORD)ListBuffer;
405 ListAttrRecordEnd = (PNTFS_ATTR_RECORD)((PCHAR)ListBuffer + ListSize);
406
407 if (NtfsReadAttribute(Volume, ListContext, 0, ListBuffer, (ULONG)ListSize) == ListSize)
408 {
409 Context = NtfsFindAttributeHelper(Volume, ListAttrRecord, ListAttrRecordEnd,
410 Type, Name, NameLength);
411
412 NtfsReleaseAttributeContext(ListContext);
413 FrLdrTempFree(ListBuffer, TAG_NTFS_LIST);
414
415 if (Context != NULL)
416 return Context;
417 }
418 }
419
420 if (AttrRecord->Type == Type)
421 {
422 if (AttrRecord->NameLength == NameLength)
423 {
424 PWCHAR AttrName;
425
426 AttrName = (PWCHAR)((PCHAR)AttrRecord + AttrRecord->NameOffset);
427 if (RtlEqualMemory(AttrName, Name, NameLength << 1))
428 {
429 /* Found it, fill up the context and return. */
430 return NtfsPrepareAttributeContext(AttrRecord);
431 }
432 }
433 }
434
435 if (AttrRecord->Length == 0)
436 return NULL;
437 AttrRecord = (PNTFS_ATTR_RECORD)((PCHAR)AttrRecord + AttrRecord->Length);
438 }
439
440 return NULL;
441 }
442
443 static PNTFS_ATTR_CONTEXT NtfsFindAttribute(PNTFS_VOLUME_INFO Volume, PNTFS_MFT_RECORD MftRecord, ULONG Type, const WCHAR *Name)
444 {
445 PNTFS_ATTR_RECORD AttrRecord;
446 PNTFS_ATTR_RECORD AttrRecordEnd;
447 ULONG NameLength;
448
449 AttrRecord = (PNTFS_ATTR_RECORD)((PCHAR)MftRecord + MftRecord->AttributesOffset);
450 AttrRecordEnd = (PNTFS_ATTR_RECORD)((PCHAR)MftRecord + Volume->MftRecordSize);
451 for (NameLength = 0; Name[NameLength] != 0; NameLength++)
452 ;
453
454 return NtfsFindAttributeHelper(Volume, AttrRecord, AttrRecordEnd, Type, Name, NameLength);
455 }
456
457 static BOOLEAN NtfsFixupRecord(PNTFS_VOLUME_INFO Volume, PNTFS_RECORD Record)
458 {
459 USHORT *USA;
460 USHORT USANumber;
461 USHORT USACount;
462 USHORT *Block;
463
464 USA = (USHORT*)((PCHAR)Record + Record->USAOffset);
465 USANumber = *(USA++);
466 USACount = Record->USACount - 1; /* Exclude the USA Number. */
467 Block = (USHORT*)((PCHAR)Record + Volume->BootSector.BytesPerSector - 2);
468
469 while (USACount)
470 {
471 if (*Block != USANumber)
472 return FALSE;
473 *Block = *(USA++);
474 Block = (USHORT*)((PCHAR)Block + Volume->BootSector.BytesPerSector);
475 USACount--;
476 }
477
478 return TRUE;
479 }
480
481 static BOOLEAN NtfsReadMftRecord(PNTFS_VOLUME_INFO Volume, ULONGLONG MFTIndex, PNTFS_MFT_RECORD Buffer)
482 {
483 ULONGLONG BytesRead;
484
485 BytesRead = NtfsReadAttribute(Volume, Volume->MFTContext, MFTIndex * Volume->MftRecordSize, (PCHAR)Buffer, Volume->MftRecordSize);
486 if (BytesRead != Volume->MftRecordSize)
487 return FALSE;
488
489 /* Apply update sequence array fixups. */
490 return NtfsFixupRecord(Volume, (PNTFS_RECORD)Buffer);
491 }
492
493 #if DBG
494 VOID NtfsPrintFile(PNTFS_INDEX_ENTRY IndexEntry)
495 {
496 PWCHAR FileName;
497 UCHAR FileNameLength;
498 CHAR AnsiFileName[256];
499 UCHAR i;
500
501 FileName = IndexEntry->FileName.FileName;
502 FileNameLength = min(IndexEntry->FileName.FileNameLength, 255);
503
504 for (i = 0; i < FileNameLength; i++)
505 AnsiFileName[i] = (CHAR)FileName[i];
506 AnsiFileName[i] = 0;
507
508 TRACE("- %s (%x)\n", AnsiFileName, IndexEntry->Data.Directory.IndexedFile);
509 }
510 #endif
511
512 static BOOLEAN NtfsCompareFileName(PCHAR FileName, PNTFS_INDEX_ENTRY IndexEntry)
513 {
514 PWCHAR EntryFileName;
515 UCHAR EntryFileNameLength;
516 UCHAR i;
517
518 EntryFileName = IndexEntry->FileName.FileName;
519 EntryFileNameLength = IndexEntry->FileName.FileNameLength;
520
521 #if DBG
522 NtfsPrintFile(IndexEntry);
523 #endif
524
525 if (strlen(FileName) != EntryFileNameLength)
526 return FALSE;
527
528 /* Do case-sensitive compares for Posix file names. */
529 if (IndexEntry->FileName.FileNameType == NTFS_FILE_NAME_POSIX)
530 {
531 for (i = 0; i < EntryFileNameLength; i++)
532 if (EntryFileName[i] != FileName[i])
533 return FALSE;
534 }
535 else
536 {
537 for (i = 0; i < EntryFileNameLength; i++)
538 if (tolower(EntryFileName[i]) != tolower(FileName[i]))
539 return FALSE;
540 }
541
542 return TRUE;
543 }
544
545 static BOOLEAN NtfsFindMftRecord(PNTFS_VOLUME_INFO Volume, ULONGLONG MFTIndex, PCHAR FileName, ULONGLONG *OutMFTIndex)
546 {
547 PNTFS_MFT_RECORD MftRecord;
548 //ULONG Magic;
549 PNTFS_ATTR_CONTEXT IndexRootCtx;
550 PNTFS_ATTR_CONTEXT IndexBitmapCtx;
551 PNTFS_ATTR_CONTEXT IndexAllocationCtx;
552 PNTFS_INDEX_ROOT IndexRoot;
553 ULONGLONG BitmapDataSize;
554 ULONGLONG IndexAllocationSize;
555 PCHAR BitmapData;
556 PCHAR IndexRecord;
557 PNTFS_INDEX_ENTRY IndexEntry, IndexEntryEnd;
558 ULONG RecordOffset;
559 ULONG IndexBlockSize;
560
561 MftRecord = FrLdrTempAlloc(Volume->MftRecordSize, TAG_NTFS_MFT);
562 if (MftRecord == NULL)
563 {
564 return FALSE;
565 }
566
567 if (NtfsReadMftRecord(Volume, MFTIndex, MftRecord))
568 {
569 //Magic = MftRecord->Magic;
570
571 IndexRootCtx = NtfsFindAttribute(Volume, MftRecord, NTFS_ATTR_TYPE_INDEX_ROOT, L"$I30");
572 if (IndexRootCtx == NULL)
573 {
574 FrLdrTempFree(MftRecord, TAG_NTFS_MFT);
575 return FALSE;
576 }
577
578 IndexRecord = FrLdrTempAlloc(Volume->IndexRecordSize, TAG_NTFS_INDEX_REC);
579 if (IndexRecord == NULL)
580 {
581 FrLdrTempFree(MftRecord, TAG_NTFS_MFT);
582 return FALSE;
583 }
584
585 NtfsReadAttribute(Volume, IndexRootCtx, 0, IndexRecord, Volume->IndexRecordSize);
586 IndexRoot = (PNTFS_INDEX_ROOT)IndexRecord;
587 IndexEntry = (PNTFS_INDEX_ENTRY)((PCHAR)&IndexRoot->IndexHeader + IndexRoot->IndexHeader.EntriesOffset);
588 /* Index root is always resident. */
589 IndexEntryEnd = (PNTFS_INDEX_ENTRY)(IndexRecord + IndexRootCtx->Record.Resident.ValueLength);
590 NtfsReleaseAttributeContext(IndexRootCtx);
591
592 TRACE("IndexRecordSize: %x IndexBlockSize: %x\n", Volume->IndexRecordSize, IndexRoot->IndexBlockSize);
593
594 while (IndexEntry < IndexEntryEnd &&
595 !(IndexEntry->Flags & NTFS_INDEX_ENTRY_END))
596 {
597 if (NtfsCompareFileName(FileName, IndexEntry))
598 {
599 *OutMFTIndex = IndexEntry->Data.Directory.IndexedFile;
600 FrLdrTempFree(IndexRecord, TAG_NTFS_INDEX_REC);
601 FrLdrTempFree(MftRecord, TAG_NTFS_MFT);
602 return TRUE;
603 }
604 IndexEntry = (PNTFS_INDEX_ENTRY)((PCHAR)IndexEntry + IndexEntry->Length);
605 }
606
607 if (IndexRoot->IndexHeader.Flags & NTFS_LARGE_INDEX)
608 {
609 TRACE("Large Index!\n");
610
611 IndexBlockSize = IndexRoot->IndexBlockSize;
612
613 IndexBitmapCtx = NtfsFindAttribute(Volume, MftRecord, NTFS_ATTR_TYPE_BITMAP, L"$I30");
614 if (IndexBitmapCtx == NULL)
615 {
616 TRACE("Corrupted filesystem!\n");
617 FrLdrTempFree(MftRecord, TAG_NTFS_MFT);
618 return FALSE;
619 }
620 BitmapDataSize = NtfsGetAttributeSize(&IndexBitmapCtx->Record);
621 TRACE("BitmapDataSize: %x\n", (ULONG)BitmapDataSize);
622 if(BitmapDataSize <= 0xFFFFFFFF)
623 BitmapData = FrLdrTempAlloc((ULONG)BitmapDataSize, TAG_NTFS_BITMAP);
624 else
625 BitmapData = NULL;
626
627 if (BitmapData == NULL)
628 {
629 FrLdrTempFree(IndexRecord, TAG_NTFS_INDEX_REC);
630 FrLdrTempFree(MftRecord, TAG_NTFS_MFT);
631 return FALSE;
632 }
633 NtfsReadAttribute(Volume, IndexBitmapCtx, 0, BitmapData, (ULONG)BitmapDataSize);
634 NtfsReleaseAttributeContext(IndexBitmapCtx);
635
636 IndexAllocationCtx = NtfsFindAttribute(Volume, MftRecord, NTFS_ATTR_TYPE_INDEX_ALLOCATION, L"$I30");
637 if (IndexAllocationCtx == NULL)
638 {
639 TRACE("Corrupted filesystem!\n");
640 FrLdrTempFree(BitmapData, TAG_NTFS_BITMAP);
641 FrLdrTempFree(IndexRecord, TAG_NTFS_INDEX_REC);
642 FrLdrTempFree(MftRecord, TAG_NTFS_MFT);
643 return FALSE;
644 }
645 IndexAllocationSize = NtfsGetAttributeSize(&IndexAllocationCtx->Record);
646
647 RecordOffset = 0;
648
649 for (;;)
650 {
651 TRACE("RecordOffset: %x IndexAllocationSize: %x\n", RecordOffset, IndexAllocationSize);
652 for (; RecordOffset < IndexAllocationSize;)
653 {
654 UCHAR Bit = 1 << ((RecordOffset / IndexBlockSize) & 7);
655 ULONG Byte = (RecordOffset / IndexBlockSize) >> 3;
656 if ((BitmapData[Byte] & Bit))
657 break;
658 RecordOffset += IndexBlockSize;
659 }
660
661 if (RecordOffset >= IndexAllocationSize)
662 {
663 break;
664 }
665
666 NtfsReadAttribute(Volume, IndexAllocationCtx, RecordOffset, IndexRecord, IndexBlockSize);
667
668 if (!NtfsFixupRecord(Volume, (PNTFS_RECORD)IndexRecord))
669 {
670 break;
671 }
672
673 /* FIXME */
674 IndexEntry = (PNTFS_INDEX_ENTRY)(IndexRecord + 0x18 + *(USHORT *)(IndexRecord + 0x18));
675 IndexEntryEnd = (PNTFS_INDEX_ENTRY)(IndexRecord + IndexBlockSize);
676
677 while (IndexEntry < IndexEntryEnd &&
678 !(IndexEntry->Flags & NTFS_INDEX_ENTRY_END))
679 {
680 if (NtfsCompareFileName(FileName, IndexEntry))
681 {
682 TRACE("File found\n");
683 *OutMFTIndex = IndexEntry->Data.Directory.IndexedFile;
684 FrLdrTempFree(BitmapData, TAG_NTFS_BITMAP);
685 FrLdrTempFree(IndexRecord, TAG_NTFS_INDEX_REC);
686 FrLdrTempFree(MftRecord, TAG_NTFS_MFT);
687 NtfsReleaseAttributeContext(IndexAllocationCtx);
688 return TRUE;
689 }
690 IndexEntry = (PNTFS_INDEX_ENTRY)((PCHAR)IndexEntry + IndexEntry->Length);
691 }
692
693 RecordOffset += IndexBlockSize;
694 }
695
696 NtfsReleaseAttributeContext(IndexAllocationCtx);
697 FrLdrTempFree(BitmapData, TAG_NTFS_BITMAP);
698 }
699
700 FrLdrTempFree(IndexRecord, TAG_NTFS_INDEX_REC);
701 }
702 else
703 {
704 TRACE("Can't read MFT record\n");
705 }
706 FrLdrTempFree(MftRecord, TAG_NTFS_MFT);
707
708 return FALSE;
709 }
710
711 static BOOLEAN NtfsLookupFile(PNTFS_VOLUME_INFO Volume, PCSTR FileName, PNTFS_MFT_RECORD MftRecord, PNTFS_ATTR_CONTEXT *DataContext)
712 {
713 ULONG NumberOfPathParts;
714 CHAR PathPart[261];
715 ULONGLONG CurrentMFTIndex;
716 UCHAR i;
717
718 TRACE("NtfsLookupFile() FileName = %s\n", FileName);
719
720 CurrentMFTIndex = NTFS_FILE_ROOT;
721 NumberOfPathParts = FsGetNumPathParts(FileName);
722 for (i = 0; i < NumberOfPathParts; i++)
723 {
724 FsGetFirstNameFromPath(PathPart, FileName);
725
726 for (; (*FileName != '\\') && (*FileName != '/') && (*FileName != '\0'); FileName++)
727 ;
728 FileName++;
729
730 TRACE("- Lookup: %s\n", PathPart);
731 if (!NtfsFindMftRecord(Volume, CurrentMFTIndex, PathPart, &CurrentMFTIndex))
732 {
733 TRACE("- Failed\n");
734 return FALSE;
735 }
736 TRACE("- Lookup: %x\n", CurrentMFTIndex);
737 }
738
739 if (!NtfsReadMftRecord(Volume, CurrentMFTIndex, MftRecord))
740 {
741 TRACE("NtfsLookupFile: Can't read MFT record\n");
742 return FALSE;
743 }
744
745 *DataContext = NtfsFindAttribute(Volume, MftRecord, NTFS_ATTR_TYPE_DATA, L"");
746 if (*DataContext == NULL)
747 {
748 TRACE("NtfsLookupFile: Can't find data attribute\n");
749 return FALSE;
750 }
751
752 return TRUE;
753 }
754
755 ARC_STATUS NtfsClose(ULONG FileId)
756 {
757 PNTFS_FILE_HANDLE FileHandle = FsGetDeviceSpecific(FileId);
758
759 NtfsReleaseAttributeContext(FileHandle->DataContext);
760 FrLdrTempFree(FileHandle, TAG_NTFS_FILE);
761
762 return ESUCCESS;
763 }
764
765 ARC_STATUS NtfsGetFileInformation(ULONG FileId, FILEINFORMATION* Information)
766 {
767 PNTFS_FILE_HANDLE FileHandle = FsGetDeviceSpecific(FileId);
768
769 RtlZeroMemory(Information, sizeof(*Information));
770 Information->EndingAddress.QuadPart = NtfsGetAttributeSize(&FileHandle->DataContext->Record);
771 Information->CurrentAddress.QuadPart = FileHandle->Offset;
772
773 TRACE("NtfsGetFileInformation(%lu) -> FileSize = %llu, FilePointer = 0x%llx\n",
774 FileId, Information->EndingAddress.QuadPart, Information->CurrentAddress.QuadPart);
775
776 return ESUCCESS;
777 }
778
779 ARC_STATUS NtfsOpen(CHAR* Path, OPENMODE OpenMode, ULONG* FileId)
780 {
781 PNTFS_VOLUME_INFO Volume;
782 PNTFS_FILE_HANDLE FileHandle;
783 PNTFS_MFT_RECORD MftRecord;
784 ULONG DeviceId;
785
786 //
787 // Check parameters
788 //
789 if (OpenMode != OpenReadOnly)
790 return EACCES;
791
792 //
793 // Get underlying device
794 //
795 DeviceId = FsGetDeviceId(*FileId);
796 Volume = NtfsVolumes[DeviceId];
797
798 TRACE("NtfsOpen() FileName = %s\n", Path);
799
800 //
801 // Allocate file structure
802 //
803 FileHandle = FrLdrTempAlloc(sizeof(NTFS_FILE_HANDLE) + Volume->MftRecordSize,
804 TAG_NTFS_FILE);
805 if (!FileHandle)
806 {
807 return ENOMEM;
808 }
809 RtlZeroMemory(FileHandle, sizeof(NTFS_FILE_HANDLE) + Volume->MftRecordSize);
810 FileHandle->Volume = Volume;
811
812 //
813 // Search file entry
814 //
815 MftRecord = (PNTFS_MFT_RECORD)(FileHandle + 1);
816 if (!NtfsLookupFile(Volume, Path, MftRecord, &FileHandle->DataContext))
817 {
818 FrLdrTempFree(FileHandle, TAG_NTFS_FILE);
819 return ENOENT;
820 }
821
822 FsSetDeviceSpecific(*FileId, FileHandle);
823 return ESUCCESS;
824 }
825
826 ARC_STATUS NtfsRead(ULONG FileId, VOID* Buffer, ULONG N, ULONG* Count)
827 {
828 PNTFS_FILE_HANDLE FileHandle = FsGetDeviceSpecific(FileId);
829 ULONGLONG BytesRead64;
830
831 //
832 // Read file
833 //
834 BytesRead64 = NtfsReadAttribute(FileHandle->Volume, FileHandle->DataContext, FileHandle->Offset, Buffer, N);
835 *Count = (ULONG)BytesRead64;
836
837 //
838 // Check for success
839 //
840 if (BytesRead64 > 0)
841 return ESUCCESS;
842 else
843 return EIO;
844 }
845
846 ARC_STATUS NtfsSeek(ULONG FileId, LARGE_INTEGER* Position, SEEKMODE SeekMode)
847 {
848 PNTFS_FILE_HANDLE FileHandle = FsGetDeviceSpecific(FileId);
849 LARGE_INTEGER NewPosition = *Position;
850
851 switch (SeekMode)
852 {
853 case SeekAbsolute:
854 break;
855 case SeekRelative:
856 NewPosition.QuadPart += FileHandle->Offset;
857 break;
858 default:
859 ASSERT(FALSE);
860 return EINVAL;
861 }
862
863 if (NewPosition.QuadPart >= NtfsGetAttributeSize(&FileHandle->DataContext->Record))
864 return EINVAL;
865
866 FileHandle->Offset = NewPosition.QuadPart;
867 return ESUCCESS;
868 }
869
870 const DEVVTBL NtfsFuncTable =
871 {
872 NtfsClose,
873 NtfsGetFileInformation,
874 NtfsOpen,
875 NtfsRead,
876 NtfsSeek,
877 L"ntfs",
878 };
879
880 const DEVVTBL* NtfsMount(ULONG DeviceId)
881 {
882 PNTFS_VOLUME_INFO Volume;
883 LARGE_INTEGER Position;
884 ULONG Count;
885 ARC_STATUS Status;
886
887 TRACE("Enter NtfsMount(%lu)\n", DeviceId);
888
889 //
890 // Allocate data for volume information
891 //
892 Volume = FrLdrTempAlloc(sizeof(NTFS_VOLUME_INFO), TAG_NTFS_VOLUME);
893 if (!Volume)
894 return NULL;
895 RtlZeroMemory(Volume, sizeof(NTFS_VOLUME_INFO));
896
897 //
898 // Read the BootSector
899 //
900 Position.QuadPart = 0;
901 Status = ArcSeek(DeviceId, &Position, SeekAbsolute);
902 if (Status != ESUCCESS)
903 {
904 FrLdrTempFree(Volume, TAG_NTFS_VOLUME);
905 return NULL;
906 }
907 Status = ArcRead(DeviceId, &Volume->BootSector, sizeof(Volume->BootSector), &Count);
908 if (Status != ESUCCESS || Count != sizeof(Volume->BootSector))
909 {
910 FrLdrTempFree(Volume, TAG_NTFS_VOLUME);
911 return NULL;
912 }
913
914 //
915 // Check if BootSector is valid. If no, return early
916 //
917 if (!RtlEqualMemory(Volume->BootSector.SystemId, "NTFS", 4))
918 {
919 FrLdrTempFree(Volume, TAG_NTFS_VOLUME);
920 return NULL;
921 }
922
923 //
924 // Calculate cluster size and MFT record size
925 //
926 Volume->ClusterSize = Volume->BootSector.SectorsPerCluster * Volume->BootSector.BytesPerSector;
927 if (Volume->BootSector.ClustersPerMftRecord > 0)
928 Volume->MftRecordSize = Volume->BootSector.ClustersPerMftRecord * Volume->ClusterSize;
929 else
930 Volume->MftRecordSize = 1 << (-Volume->BootSector.ClustersPerMftRecord);
931 if (Volume->BootSector.ClustersPerIndexRecord > 0)
932 Volume->IndexRecordSize = Volume->BootSector.ClustersPerIndexRecord * Volume->ClusterSize;
933 else
934 Volume->IndexRecordSize = 1 << (-Volume->BootSector.ClustersPerIndexRecord);
935
936 TRACE("ClusterSize: 0x%x\n", Volume->ClusterSize);
937 TRACE("ClustersPerMftRecord: %d\n", Volume->BootSector.ClustersPerMftRecord);
938 TRACE("ClustersPerIndexRecord: %d\n", Volume->BootSector.ClustersPerIndexRecord);
939 TRACE("MftRecordSize: 0x%x\n", Volume->MftRecordSize);
940 TRACE("IndexRecordSize: 0x%x\n", Volume->IndexRecordSize);
941
942 //
943 // Read MFT index
944 //
945 TRACE("Reading MFT index...\n");
946 Volume->MasterFileTable = FrLdrTempAlloc(Volume->MftRecordSize, TAG_NTFS_MFT);
947 if (!Volume->MasterFileTable)
948 {
949 FrLdrTempFree(Volume, TAG_NTFS_VOLUME);
950 return NULL;
951 }
952 Position.QuadPart = Volume->BootSector.MftLocation * Volume->ClusterSize;
953 Status = ArcSeek(DeviceId, &Position, SeekAbsolute);
954 if (Status != ESUCCESS)
955 {
956 FileSystemError("Failed to seek to Master File Table record.");
957 FrLdrTempFree(Volume->MasterFileTable, TAG_NTFS_MFT);
958 FrLdrTempFree(Volume, TAG_NTFS_VOLUME);
959 return NULL;
960 }
961 Status = ArcRead(DeviceId, Volume->MasterFileTable, Volume->MftRecordSize, &Count);
962 if (Status != ESUCCESS || Count != Volume->MftRecordSize)
963 {
964 FileSystemError("Failed to read the Master File Table record.");
965 FrLdrTempFree(Volume->MasterFileTable, TAG_NTFS_MFT);
966 FrLdrTempFree(Volume, TAG_NTFS_VOLUME);
967 return NULL;
968 }
969
970 //
971 // Keep room to read partial sectors
972 //
973 Volume->TemporarySector = FrLdrTempAlloc(Volume->BootSector.BytesPerSector, TAG_NTFS_DATA);
974 if (!Volume->TemporarySector)
975 {
976 FileSystemError("Failed to allocate memory.");
977 FrLdrTempFree(Volume->MasterFileTable, TAG_NTFS_MFT);
978 FrLdrTempFree(Volume, TAG_NTFS_VOLUME);
979 return NULL;
980 }
981
982 //
983 // Keep device id
984 //
985 Volume->DeviceId = DeviceId;
986
987 //
988 // Search DATA attribute
989 //
990 TRACE("Searching for DATA attribute...\n");
991 Volume->MFTContext = NtfsFindAttribute(Volume, Volume->MasterFileTable, NTFS_ATTR_TYPE_DATA, L"");
992 if (!Volume->MFTContext)
993 {
994 FileSystemError("Can't find data attribute for Master File Table.");
995 FrLdrTempFree(Volume->MasterFileTable, TAG_NTFS_MFT);
996 FrLdrTempFree(Volume, TAG_NTFS_VOLUME);
997 return NULL;
998 }
999
1000 //
1001 // Remember NTFS volume information
1002 //
1003 NtfsVolumes[DeviceId] = Volume;
1004
1005 //
1006 // Return success
1007 //
1008 TRACE("NtfsMount(%lu) success\n", DeviceId);
1009 return &NtfsFuncTable;
1010 }
1011
1012 #endif